From 1afe0ec73af76fcb2f075719abc3efe2a960b981 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Nov 2022 10:00:41 +0000 Subject: [PATCH] Update dependency @theguild/prettier-config to v1 (#676) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Kamil Kisiela --- .changeset/README.md | 7 +- .eslintrc.cjs | 5 +- .github/workflows/cd.yaml | 16 +- .github/workflows/ci.yaml | 14 +- CODE_OF_CONDUCT.md | 83 +++- README.md | 33 +- codegen.yml | 30 +- deployment/index.ts | 10 +- deployment/package.json | 8 +- deployment/services/app.ts | 2 +- deployment/services/billing.ts | 6 +- deployment/services/cloudflare-security.ts | 9 +- deployment/services/db-migrations.ts | 6 +- deployment/services/emails.ts | 2 +- deployment/services/graphql.ts | 6 +- deployment/services/police.ts | 2 +- deployment/services/proxy.ts | 2 +- deployment/services/rate-limit.ts | 6 +- deployment/services/schema.ts | 2 +- deployment/services/tokens.ts | 6 +- deployment/services/usage-estimation.ts | 2 +- deployment/services/usage-ingestor.ts | 2 +- deployment/services/usage.ts | 2 +- deployment/services/webhooks.ts | 2 +- deployment/utils/botkube.ts | 2 +- deployment/utils/cert-manager.ts | 2 +- deployment/utils/clickhouse.ts | 4 +- deployment/utils/cloudflare.ts | 14 +- deployment/utils/helpers.ts | 4 +- deployment/utils/local-endpoint.ts | 4 +- deployment/utils/observability.ts | 7 +- deployment/utils/pod-builder.ts | 2 +- deployment/utils/police.ts | 7 +- deployment/utils/redis.ts | 4 +- .../utils/remote-artifact-as-service.ts | 10 +- deployment/utils/reverse-proxy.ts | 16 +- docker-compose.community.yml | 13 +- docs/ARCHITECTURE.md | 3 +- docs/DEPLOYMENT.md | 20 +- docs/DEVELOPMENT.md | 55 ++- docs/TESTING.md | 3 +- integration-tests/docker-compose.yml | 13 +- integration-tests/jest-setup.ts | 2 +- integration-tests/package.json | 22 +- integration-tests/scripts/pack.mjs | 16 +- integration-tests/testkit/auth.ts | 25 +- integration-tests/testkit/cli.ts | 8 +- integration-tests/testkit/db.ts | 6 +- integration-tests/testkit/dockest.ts | 6 +- integration-tests/testkit/env.ts | 3 +- integration-tests/testkit/flow.ts | 22 +- integration-tests/testkit/graphql.ts | 4 +- .../tests/api/legacy-auth.spec.ts | 10 +- .../tests/api/oidc-integrations/crud.spec.ts | 80 ++-- .../tests/api/organization/crud.spec.ts | 13 +- .../api/organization/get-started.spec.ts | 27 +- .../tests/api/organization/members.spec.ts | 55 ++- .../api/persisted-operations/publish.spec.ts | 41 +- .../tests/api/project/crud.spec.ts | 16 +- .../tests/api/rate-limit/emails.spec.ts | 33 +- .../tests/api/schema/check.spec.ts | 32 +- .../api/schema/external-composition.spec.ts | 15 +- .../tests/api/schema/publish.spec.ts | 256 +++++----- .../tests/api/schema/sync.spec.ts | 10 +- .../tests/api/target/crud.spec.ts | 10 +- .../tests/api/target/tokens.spec.ts | 16 +- .../tests/api/target/usage.spec.ts | 251 ++++++---- integration-tests/tests/api/tokens.spec.ts | 16 +- integration-tests/tests/cli/schema.spec.ts | 119 ++--- package.json | 50 +- packages/libraries/cli/CHANGELOG.md | 61 ++- packages/libraries/cli/README.md | 36 +- packages/libraries/cli/package.json | 56 +-- packages/libraries/cli/src/base-command.ts | 12 +- .../cli/src/commands/operations/check.ts | 11 +- .../cli/src/commands/operations/publish.ts | 23 +- .../cli/src/commands/schema/check.ts | 8 +- .../cli/src/commands/schema/publish.ts | 31 +- packages/libraries/cli/src/helpers/git.ts | 7 +- .../libraries/cli/src/helpers/operations.ts | 4 +- packages/libraries/client/CHANGELOG.md | 51 +- packages/libraries/client/README.md | 45 +- packages/libraries/client/package.json | 24 +- packages/libraries/client/src/apollo.ts | 19 +- packages/libraries/client/src/client.ts | 32 +- packages/libraries/client/src/gateways.ts | 2 +- .../libraries/client/src/internal/agent.ts | 21 +- .../client/src/internal/operations-store.ts | 8 +- .../client/src/internal/reporting.ts | 26 +- .../libraries/client/src/internal/types.ts | 4 +- .../libraries/client/src/internal/usage.ts | 44 +- .../libraries/client/src/internal/utils.ts | 20 +- packages/libraries/client/tests/info.spec.ts | 8 +- .../libraries/client/tests/reporting.spec.ts | 18 +- .../client/tests/usage-collector.spec.ts | 18 +- packages/libraries/client/tests/usage.spec.ts | 8 +- packages/libraries/core/CHANGELOG.md | 4 +- packages/libraries/core/package.json | 30 +- .../libraries/core/src/normalize/operation.ts | 140 +++--- .../libraries/external-composition/README.md | 8 +- .../external-composition/example.mjs | 2 +- .../external-composition/package.json | 28 +- packages/services/api/package.json | 30 +- packages/services/api/src/create.ts | 10 +- packages/services/api/src/index.ts | 6 +- .../activity/providers/activity-manager.ts | 17 +- .../modules/admin/providers/admin-manager.ts | 11 +- .../alerts/providers/adapters/common.ts | 9 +- .../alerts/providers/adapters/slack.ts | 23 +- .../alerts/providers/adapters/webhook.ts | 4 +- .../alerts/providers/alerts-manager.ts | 36 +- .../services/api/src/modules/auth/index.ts | 9 +- .../modules/auth/providers/auth-manager.ts | 22 +- .../auth/providers/organization-access.ts | 32 +- .../modules/auth/providers/project-access.ts | 25 +- .../modules/auth/providers/target-access.ts | 20 +- .../api/src/modules/auth/resolvers.ts | 5 +- .../api/src/modules/billing/module.graphql.ts | 5 +- .../billing/providers/billing.provider.ts | 16 +- .../api/src/modules/billing/resolvers.ts | 14 +- .../src/modules/cdn/providers/cdn.provider.ts | 24 +- .../providers/github-integration-manager.ts | 22 +- .../providers/slack-integration-manager.ts | 25 +- .../api/src/modules/integrations/resolvers.ts | 4 +- .../services/api/src/modules/lab/resolvers.ts | 2 +- .../providers/oidc-integrations.provider.ts | 35 +- .../operations/providers/clickhouse-client.ts | 8 +- .../modules/operations/providers/helpers.ts | 12 +- .../providers/operations-manager.ts | 37 +- .../operations/providers/operations-reader.ts | 162 ++++--- .../modules/operations/providers/tokens.ts | 2 +- .../api/src/modules/operations/resolvers.ts | 42 +- .../modules/organization/module.graphql.ts | 12 +- .../providers/organization-config.ts | 6 +- .../providers/organization-manager.ts | 42 +- .../api/src/modules/organization/resolvers.ts | 20 +- .../persisted-operations/module.graphql.ts | 8 +- .../providers/persisted-operation-manager.ts | 28 +- .../modules/persisted-operations/resolvers.ts | 16 +- .../api/src/modules/project/module.graphql.ts | 4 +- .../project/providers/project-manager.ts | 8 +- .../api/src/modules/project/resolvers.ts | 3 +- .../providers/rate-limit.provider.ts | 7 +- .../modules/rate-limit/providers/tokens.ts | 4 +- .../services/api/src/modules/schema/index.ts | 9 +- .../api/src/modules/schema/module.graphql.ts | 6 +- .../src/modules/schema/providers/inspector.ts | 22 +- .../schema/providers/orchestrators/custom.ts | 9 +- .../providers/orchestrators/federation.ts | 2 +- .../schema/providers/orchestrators/single.ts | 2 +- .../providers/orchestrators/stitching.ts | 2 +- .../schema/providers/orchestrators/tokens.ts | 4 +- .../schema/providers/schema-manager.ts | 41 +- .../schema/providers/schema-publisher.ts | 68 ++- .../schema/providers/schema-validator.ts | 16 +- .../api/src/modules/schema/resolvers.ts | 27 +- .../modules/shared/__tests__/crypto.spec.ts | 12 +- .../modules/shared/providers/http-client.ts | 2 +- .../modules/shared/providers/id-translator.ts | 32 +- .../shared/providers/idempotent-runner.ts | 67 ++- .../src/modules/shared/providers/storage.ts | 100 ++-- .../api/src/modules/target/module.graphql.ts | 4 +- .../target/providers/target-manager.ts | 22 +- .../api/src/modules/target/resolvers.ts | 12 +- .../modules/token/providers/token-manager.ts | 8 +- .../modules/token/providers/token-storage.ts | 14 +- .../usage-estimation/providers/tokens.ts | 2 +- .../providers/usage-estimation.provider.ts | 6 +- .../src/modules/usage-estimation/resolvers.ts | 2 +- packages/services/api/src/shared/helpers.ts | 31 +- packages/services/api/src/shared/mappers.ts | 31 +- packages/services/api/src/shared/schema.ts | 22 +- packages/services/api/src/shared/sentry.ts | 13 +- packages/services/broker-worker/README.md | 5 +- packages/services/broker-worker/package.json | 14 +- packages/services/broker-worker/src/dev.ts | 2 +- packages/services/broker-worker/src/errors.ts | 8 +- packages/services/broker-worker/src/index.ts | 9 +- packages/services/broker-worker/src/models.ts | 2 +- .../broker-worker/tests/broker.spec.ts | 6 +- packages/services/cdn-worker/README.md | 6 +- packages/services/cdn-worker/package.json | 16 +- packages/services/cdn-worker/src/auth.ts | 10 +- packages/services/cdn-worker/src/dev.ts | 6 +- packages/services/cdn-worker/src/errors.ts | 14 +- packages/services/cdn-worker/src/handler.ts | 4 +- packages/services/cdn-worker/src/index.ts | 9 +- .../services/cdn-worker/tests/cdn.spec.ts | 7 +- packages/services/emails/package.json | 22 +- packages/services/emails/src/environment.ts | 13 +- packages/services/emails/src/providers.ts | 2 +- packages/services/emails/src/scheduler.ts | 15 +- .../src/templates/email-verification.ts | 6 +- .../emails/src/templates/password-reset.ts | 6 +- packages/services/police-worker/README.md | 20 +- packages/services/police-worker/package.json | 6 +- packages/services/police-worker/src/index.ts | 8 +- packages/services/rate-limit/README.md | 4 +- packages/services/rate-limit/package.json | 22 +- packages/services/rate-limit/src/emails.ts | 20 +- .../services/rate-limit/src/environment.ts | 2 +- packages/services/rate-limit/src/index.ts | 7 +- packages/services/rate-limit/src/limiter.ts | 27 +- packages/services/schema/README.md | 3 +- packages/services/schema/package.json | 20 +- packages/services/schema/src/api.ts | 13 +- packages/services/schema/src/environment.ts | 2 +- packages/services/schema/src/orchestrators.ts | 65 ++- packages/services/server/README.md | 3 +- packages/services/server/package.json | 20 +- packages/services/server/src/api.ts | 24 +- packages/services/server/src/environment.ts | 9 +- .../services/server/src/graphql-handler.ts | 30 +- packages/services/server/src/index.ts | 28 +- packages/services/service-common/package.json | 14 +- .../services/service-common/src/helpers.ts | 2 +- .../services/service-common/src/sentry.ts | 17 +- packages/services/storage/docker-compose.yml | 13 +- .../services/storage/migrations/clickhouse.ts | 2 +- packages/services/storage/package.json | 30 +- packages/services/storage/src/db/pool.ts | 6 +- packages/services/storage/src/db/utils.ts | 8 +- packages/services/storage/src/index.ts | 444 +++++++++++------- packages/services/storage/src/tokens.ts | 15 +- packages/services/storage/tools/create-db.mjs | 15 +- packages/services/stripe-billing/package.json | 24 +- packages/services/stripe-billing/src/api.ts | 44 +- .../stripe-billing/src/billing-sync.ts | 8 +- .../stripe-billing/src/environment.ts | 2 +- packages/services/stripe-billing/src/index.ts | 7 +- packages/services/tokens/README.md | 3 +- packages/services/tokens/package.json | 14 +- packages/services/tokens/src/cache.ts | 12 +- packages/services/tokens/src/environment.ts | 2 +- packages/services/tokens/src/helpers.ts | 2 +- packages/services/tokens/src/index.ts | 5 +- packages/services/tokens/src/storage.ts | 4 +- packages/services/usage-common/package.json | 2 +- .../services/usage-estimator/package.json | 18 +- packages/services/usage-estimator/src/api.ts | 14 +- .../usage-estimator/src/environment.ts | 2 +- .../services/usage-estimator/src/estimator.ts | 8 +- .../services/usage-estimator/src/index.ts | 7 +- .../__tests__/serializer.spec.ts | 16 +- packages/services/usage-ingestor/package.json | 8 +- .../usage-ingestor/src/environment.ts | 8 +- .../services/usage-ingestor/src/helpers.ts | 2 +- packages/services/usage-ingestor/src/index.ts | 8 +- .../services/usage-ingestor/src/processor.ts | 37 +- .../services/usage-ingestor/src/serializer.ts | 19 +- .../services/usage-ingestor/src/writer.ts | 16 +- packages/services/usage/README.md | 3 +- .../services/usage/__tests__/buffer.spec.ts | 24 +- .../usage/__tests__/validation.spec.ts | 74 +-- packages/services/usage/package.json | 12 +- packages/services/usage/src/buffer.ts | 37 +- packages/services/usage/src/environment.ts | 8 +- packages/services/usage/src/index.ts | 14 +- packages/services/usage/src/rate-limit.ts | 9 +- packages/services/usage/src/usage.ts | 29 +- packages/services/webhooks/package.json | 14 +- packages/services/webhooks/src/environment.ts | 2 +- packages/services/webhooks/src/jobs.ts | 2 +- packages/services/webhooks/src/scheduler.ts | 13 +- packages/web/app/.babelrc.js | 6 +- packages/web/app/README.md | 3 +- packages/web/app/environment.ts | 4 +- packages/web/app/package.json | 8 +- packages/web/app/pages/404.tsx | 8 +- .../[projectId]/[targetId]/explorer.tsx | 45 +- .../[targetId]/explorer/[typename].tsx | 13 +- .../[projectId]/[targetId]/history.tsx | 30 +- .../[orgId]/[projectId]/[targetId]/index.tsx | 50 +- .../[projectId]/[targetId]/laboratory.tsx | 6 +- .../[projectId]/[targetId]/operations.tsx | 17 +- .../[projectId]/[targetId]/settings.tsx | 144 ++++-- .../app/pages/[orgId]/[projectId]/alerts.tsx | 29 +- .../app/pages/[orgId]/[projectId]/index.tsx | 22 +- .../pages/[orgId]/[projectId]/settings.tsx | 116 +++-- packages/web/app/pages/[orgId]/_new.tsx | 30 +- packages/web/app/pages/[orgId]/index.tsx | 24 +- packages/web/app/pages/[orgId]/members.tsx | 85 +++- packages/web/app/pages/[orgId]/settings.tsx | 80 ++-- .../app/pages/[orgId]/subscription/index.tsx | 18 +- .../app/pages/[orgId]/subscription/manage.tsx | 32 +- packages/web/app/pages/_document.tsx | 4 +- packages/web/app/pages/_index.tsx | 48 +- .../web/app/pages/api/auth/[[...path]].ts | 7 +- packages/web/app/pages/api/github/callback.ts | 2 +- packages/web/app/pages/api/proxy.ts | 4 +- packages/web/app/pages/api/slack/callback.ts | 10 +- packages/web/app/pages/auth/[[...path]].tsx | 15 +- packages/web/app/pages/index.tsx | 9 +- packages/web/app/pages/manage/index.tsx | 11 +- packages/web/app/pages/settings.tsx | 41 +- .../app/src/components/admin/AdminStats.tsx | 16 +- .../components/authenticated-container.tsx | 2 +- .../app/src/components/common/DataWrapper.tsx | 18 +- .../app/src/components/common/Feedback.tsx | 21 +- .../web/app/src/components/common/Logo.tsx | 8 +- .../app/src/components/common/Navigation.tsx | 16 +- .../web/app/src/components/common/Page.tsx | 12 +- .../app/src/components/common/TimeFilter.tsx | 2 +- .../src/components/common/UserSettings.tsx | 10 +- .../components/common/activities/common.tsx | 6 +- .../app/src/components/get-started/wizard.tsx | 31 +- .../src/components/layouts/organization.tsx | 12 +- .../app/src/components/layouts/project.tsx | 21 +- .../web/app/src/components/layouts/target.tsx | 14 +- .../src/components/organization/Creator.tsx | 7 +- .../components/organization/Permissions.tsx | 51 +- .../src/components/organization/Switcher.tsx | 8 +- .../app/src/components/organization/Usage.tsx | 10 +- .../organization/billing/Billing.tsx | 6 +- .../organization/billing/LimitSlider.tsx | 9 +- .../organization/billing/PlanSummary.tsx | 4 +- .../organization/billing/RateLimitWarn.tsx | 4 +- .../settings/oidc-integration-section.tsx | 58 ++- .../app/src/components/project/Switcher.tsx | 8 +- .../project/settings/external-composition.tsx | 76 +-- .../app/src/components/target/Switcher.tsx | 8 +- .../src/components/target/explorer/common.tsx | 24 +- .../src/components/target/explorer/filter.tsx | 11 +- .../target/explorer/object-type.tsx | 6 +- .../components/target/explorer/provider.tsx | 14 +- .../target/explorer/scalar-type.tsx | 4 +- .../components/target/operations/Fallback.tsx | 4 +- .../components/target/operations/Filters.tsx | 17 +- .../src/components/target/operations/List.tsx | 20 +- .../components/target/operations/Stats.tsx | 55 ++- .../web/app/src/components/v2/activities.tsx | 24 +- .../app/src/components/v2/autocomplete.tsx | 2 +- packages/web/app/src/components/v2/avatar.tsx | 2 +- packages/web/app/src/components/v2/badge.tsx | 2 +- packages/web/app/src/components/v2/button.tsx | 15 +- packages/web/app/src/components/v2/card.tsx | 2 +- .../web/app/src/components/v2/combobox.tsx | 2 +- .../web/app/src/components/v2/copy-value.tsx | 8 +- .../web/app/src/components/v2/dropdown.tsx | 6 +- .../web/app/src/components/v2/empty-list.tsx | 8 +- packages/web/app/src/components/v2/header.tsx | 22 +- .../web/app/src/components/v2/heading.tsx | 2 +- .../web/app/src/components/v2/hive-link.tsx | 8 +- packages/web/app/src/components/v2/icon.tsx | 24 +- packages/web/app/src/components/v2/input.tsx | 61 +-- packages/web/app/src/components/v2/link.tsx | 13 +- packages/web/app/src/components/v2/modal.tsx | 5 +- .../src/components/v2/modals/connect-lab.tsx | 12 +- .../components/v2/modals/connect-schema.tsx | 19 +- .../v2/modals/create-access-token.tsx | 50 +- .../src/components/v2/modals/create-alert.tsx | 12 +- .../components/v2/modals/create-channel.tsx | 92 ++-- .../v2/modals/create-organization.tsx | 45 +- .../components/v2/modals/create-project.tsx | 29 +- .../components/v2/modals/create-target.tsx | 47 +- .../components/v2/modals/delete-members.tsx | 6 +- .../v2/modals/delete-organization.tsx | 6 +- .../components/v2/modals/delete-project.tsx | 6 +- .../components/v2/modals/delete-target.tsx | 10 +- .../app/src/components/v2/project-types.tsx | 5 +- .../web/app/src/components/v2/radio-group.tsx | 2 +- packages/web/app/src/components/v2/select.tsx | 2 +- .../web/app/src/components/v2/skeleton.tsx | 14 +- packages/web/app/src/components/v2/switch.tsx | 4 +- packages/web/app/src/components/v2/table.tsx | 2 +- packages/web/app/src/components/v2/tabs.tsx | 11 +- packages/web/app/src/components/v2/tag.tsx | 4 +- .../web/app/src/components/v2/time-ago.tsx | 14 +- .../app/src/components/v2/toggle-group.tsx | 2 +- .../web/app/src/config/supertokens/backend.ts | 98 ++-- .../web/app/src/graphql/query.compare.graphql | 4 +- .../graphql/query.persisted-operation.graphql | 4 +- packages/web/app/src/lib/access/common.ts | 3 +- .../web/app/src/lib/access/organization.ts | 2 +- packages/web/app/src/lib/access/project.ts | 2 +- packages/web/app/src/lib/access/target.ts | 2 +- .../api/extract-access-token-from-request.ts | 7 +- packages/web/app/src/lib/graphql.ts | 4 +- .../web/app/src/lib/hooks/use-clipboard.ts | 2 +- .../src/lib/hooks/use-formatted-duration.ts | 5 +- .../src/lib/hooks/use-formatted-throughput.ts | 10 +- .../app/src/lib/hooks/use-local-storage.ts | 2 +- .../app/src/lib/hooks/use-notifications.tsx | 2 +- .../app/src/lib/hooks/use-route-selector.ts | 30 +- packages/web/app/src/lib/supertokens/guard.ts | 4 +- ...party-email-password-node-oidc-provider.ts | 2 +- ...arty-email-password-react-oidc-provider.ts | 4 +- packages/web/app/src/lib/urql-cache.ts | 137 +++--- .../web/app/src/lib/urql-exchanges/state.ts | 4 +- packages/web/docs/package.json | 6 +- .../pages/features/alerts-notifications.mdx | 6 +- .../src/pages/features/checking-schema.mdx | 15 +- .../features/external-schema-composition.mdx | 29 +- .../docs/src/pages/features/integrations.mdx | 9 +- .../docs/src/pages/features/monitoring.mdx | 52 +- .../src/pages/features/publish-schema.mdx | 30 +- .../src/pages/features/registry-usage.mdx | 47 +- .../src/pages/features/schema-history.mdx | 6 +- .../src/pages/features/sso-oidc-provider.mdx | 36 +- .../web/docs/src/pages/features/tokens.mdx | 14 +- .../src/pages/get-started/organizations.mdx | 6 +- .../docs/src/pages/get-started/projects.mdx | 9 +- .../docs/src/pages/get-started/targets.mdx | 8 +- packages/web/docs/src/pages/index.mdx | 8 +- .../client-and-cli-configuration.mdx | 4 +- .../src/pages/self-hosting/get-started.mdx | 64 ++- .../src/pages/self-hosting/oidc-login.mdx | 33 +- .../web/docs/src/pages/specs/integrations.md | 3 +- .../web/docs/src/pages/specs/usage-reports.md | 6 +- packages/web/landing-page/package.json | 6 +- .../web/landing-page/src/pages/_document.tsx | 9 +- packages/web/landing-page/src/pages/index.tsx | 27 +- packages/web/landing-page/src/pricing.tsx | 24 +- pnpm-lock.yaml | 18 +- rules/enforce-deps-in-dev.cjs | 23 +- rules/package.json | 6 +- scripts/seed-local-env.js | 12 +- scripts/sync-env-files.js | 2 +- scripts/turborepo-setup.js | 4 +- tsconfig.json | 13 +- 420 files changed, 5562 insertions(+), 2863 deletions(-) diff --git a/.changeset/README.md b/.changeset/README.md index e5b6d8d6a..84fd12768 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -1,8 +1,9 @@ # Changesets -Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works -with multi-package repos, or single-package repos to help you version and publish your code. You can -find the full documentation for it [in our repository](https://github.com/changesets/changesets) +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool +that works with multi-package repos, or single-package repos to help you version and publish your +code. You can find the full documentation for it +[in our repository](https://github.com/changesets/changesets) We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 4b5bb7e6a..f6ba95217 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -26,7 +26,10 @@ module.exports = { rules: { 'no-process-env': 'error', 'no-restricted-globals': ['error', 'stop'], - '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', ignoreRestSiblings: true }], + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_', ignoreRestSiblings: true }, + ], 'no-empty': ['error', { allowEmptyCatch: true }], 'import/no-absolute-path': 'error', diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 9652058b0..ef7119e8f 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -45,7 +45,9 @@ jobs: run: pnpm build:libraries - name: Schema Publish - run: ./packages/libraries/cli/bin/dev schema:publish "packages/services/api/src/modules/*/module.graphql.ts" --force --github + run: + ./packages/libraries/cli/bin/dev schema:publish + "packages/services/api/src/modules/*/module.graphql.ts" --force --github - name: Prepare NPM Credentials run: | @@ -65,7 +67,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Extract published version - if: steps.changesets.outputs.published && contains(steps.changesets.outputs.publishedPackages, '"@graphql-hive/cli"') + if: + steps.changesets.outputs.published && contains(steps.changesets.outputs.publishedPackages, + '"@graphql-hive/cli"') id: cli run: | echo '${{steps.changesets.outputs.publishedPackages}}' > cli-ver.json @@ -88,7 +92,9 @@ jobs: working-directory: packages/libraries/cli env: VERSION: ${{ steps.cli.outputs.version }} - run: pnpm oclif promote --no-xz --sha ${GITHUB_SHA:0:7} --version $VERSION || pnpm oclif promote --no-xz --sha ${GITHUB_SHA:0:8} --version $VERSION + run: + pnpm oclif promote --no-xz --sha ${GITHUB_SHA:0:7} --version $VERSION || pnpm oclif + promote --no-xz --sha ${GITHUB_SHA:0:8} --version $VERSION publish_rust: name: Publish Rust @@ -316,7 +322,9 @@ jobs: run: strip target/x86_64-unknown-linux-gnu/release/router - name: Compress - run: ./target/x86_64-unknown-linux-gnu/release/compress ./target/x86_64-unknown-linux-gnu/release/router ./router.tar.gz + run: + ./target/x86_64-unknown-linux-gnu/release/compress + ./target/x86_64-unknown-linux-gnu/release/router ./router.tar.gz - name: Upload artifact uses: actions/upload-artifact@v3 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 54ac8f1ac..1f7148fb5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,10 +17,7 @@ jobs: POSTGRES_USER: postgres POSTGRES_DB: registry options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 + --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 outputs: hive_token_present: ${{ steps.secrets_present.outputs.hive_token }} @@ -128,7 +125,10 @@ jobs: id: pr-label-check - name: Schema Check - run: ./packages/libraries/cli/bin/dev schema:check "packages/services/api/src/modules/*/module.graphql.ts" ${{ steps.pr-label-check.outputs.SAFE_FLAG }} --github + run: + ./packages/libraries/cli/bin/dev schema:check + "packages/services/api/src/modules/*/module.graphql.ts" ${{ + steps.pr-label-check.outputs.SAFE_FLAG }} --github test: name: Tests @@ -283,7 +283,9 @@ jobs: run: strip target/x86_64-unknown-linux-gnu/release/router - name: Compress - run: ./target/x86_64-unknown-linux-gnu/release/compress ./target/x86_64-unknown-linux-gnu/release/router ./router.tar.gz + run: + ./target/x86_64-unknown-linux-gnu/release/compress + ./target/x86_64-unknown-linux-gnu/release/router ./router.tar.gz - name: Upload artifact uses: actions/upload-artifact@v3 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 92ef09c8c..0ed2312d3 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,30 +2,43 @@ ## 1. Purpose -A primary goal of GraphQL Hive is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). +A primary goal of GraphQL Hive is to be inclusive to the largest number of contributors, with the +most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, +safe and welcoming environment for all, regardless of gender, sexual orientation, ability, +ethnicity, socioeconomic status, and religion (or lack thereof). -This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. +This code of conduct outlines our expectations for all those who participate in our community, as +well as the consequences for unacceptable behavior. -We invite all those who participate in GraphQL Hive to help us create safe and positive experiences for everyone. +We invite all those who participate in GraphQL Hive to help us create safe and positive experiences +for everyone. ## 2. Open [Source/Culture/Tech] Citizenship -A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. +A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by +encouraging participants to recognize and strengthen the relationships between our actions and their +effects on our community. -Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. +Communities mirror the societies in which they exist and positive action is essential to counteract +the many forms of inequality and abuses of power that exist in society. -If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. +If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and +encourages all participants to contribute to the fullest extent, we want to know. ## 3. Expected Behavior The following behaviors are expected and requested of all community members: -- Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. +- Participate in an authentic and active way. In doing so, you contribute to the health and + longevity of this community. - Exercise consideration and respect in your speech and actions. - Attempt collaboration before conflict. - Refrain from demeaning, discriminatory, or harassing behavior and speech. -- Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. -- Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. +- Be mindful of your surroundings and of your fellow participants. Alert community leaders if you + notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if + they seem inconsequential. +- Remember that community event venues may be shared with members of the public; please be + respectful to all patrons of these locations. ## 4. Unacceptable Behavior @@ -35,41 +48,62 @@ The following behaviors are considered harassment and are unacceptable within ou - Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. - Posting or displaying sexually explicit or violent material. - Posting or threatening to post other people's personally identifying information ("doxing"). -- Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. +- Personal insults, particularly those related to gender, sexual orientation, race, religion, or + disability. - Inappropriate photography or recording. - Inappropriate physical contact. You should have someone's consent before touching them. -- Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. +- Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, + groping, and unwelcomed sexual advances. - Deliberate intimidation, stalking or following (online or in person). - Advocating for, or encouraging, any of the above behavior. - Sustained disruption of community events, including talks and presentations. ## 5. Weapons Policy -No weapons will be allowed at GraphQL Hive events, community spaces, or in other spaces covered by the scope of this Code of Conduct. Weapons include but are not limited to guns, explosives (including fireworks), and large knives such as those used for hunting or display, as well as any other item used for the purpose of causing injury or harm to others. Anyone seen in possession of one of these items will be asked to leave immediately, and will only be allowed to return without the weapon. Community members are further expected to comply with all state and local laws on this matter. +No weapons will be allowed at GraphQL Hive events, community spaces, or in other spaces covered by +the scope of this Code of Conduct. Weapons include but are not limited to guns, explosives +(including fireworks), and large knives such as those used for hunting or display, as well as any +other item used for the purpose of causing injury or harm to others. Anyone seen in possession of +one of these items will be asked to leave immediately, and will only be allowed to return without +the weapon. Community members are further expected to comply with all state and local laws on this +matter. ## 6. Consequences of Unacceptable Behavior -Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. +Unacceptable behavior from any community member, including sponsors and those with decision-making +authority, will not be tolerated. Anyone asked to stop unacceptable behavior is expected to comply immediately. -If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event). +If a community member engages in unacceptable behavior, the community organizers may take any action +they deem appropriate, up to and including a temporary ban or permanent expulsion from the community +without warning (and without refund in the case of a paid event). ## 7. Reporting Guidelines -If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. contact@the-guild.dev. +If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a +community organizer as soon as possible. contact@the-guild.dev. -Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. +Additionally, community organizers are available to help community members engage with local law +enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context +of in-person events, organizers will also provide escorts as desired by the person experiencing +distress. ## 8. Addressing Grievances -If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. [Policy](https://graphql-hive.com/privacy-policy.pdf) +If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should +notify with a concise description of your grievance. Your grievance will be handled in accordance +with our existing governing policies. [Policy](https://graphql-hive.com/privacy-policy.pdf) ## 9. Scope -We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues--online and in-person--as well as in all one-on-one communications pertaining to community business. +We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) +to abide by this Code of Conduct in all community venues--online and in-person--as well as in all +one-on-one communications pertaining to community business. -This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. +This code of conduct and its related procedures also applies to unacceptable behavior occurring +outside the scope of community activities when such behavior has the potential to adversely affect +the safety and well-being of community members. ## 10. Contact info @@ -77,9 +111,13 @@ contact@the-guild.dev ## 11. License and attribution -The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). +The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) +under a +[Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). -Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). +Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) +and the +[Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). _Revision 2.3. Posted 6 March 2017._ @@ -87,4 +125,5 @@ _Revision 2.2. Posted 4 February 2016._ _Revision 2.1. Posted 23 June 2014._ -_Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10 January 2013. Posted 17 March 2013._ +_Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10 +January 2013. Posted 17 March 2013._ diff --git a/README.md b/README.md index 38f03c661..1bf48928a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # GraphQL Hive -GraphQL Hive provides all the tools the get visibility of your GraphQL architecture at all stages, from standalone APIs to composed schemas (Federation, Stitching). +GraphQL Hive provides all the tools the get visibility of your GraphQL architecture at all stages, +from standalone APIs to composed schemas (Federation, Stitching). -- Visit [graphql-hive.com](https://graphql-hive.com) ([status page](https://status.graphql-hive.com/)) +- Visit [graphql-hive.com](https://graphql-hive.com) + ([status page](https://status.graphql-hive.com/)) - [Read the announcement blog post](https://www.the-guild.dev/blog/announcing-graphql-hive-public) - [Read the docs](https://docs.graphql-hive.com) @@ -10,10 +12,12 @@ GraphQL Hive provides all the tools the get visibility of your GraphQL architect GraphQL Hive has been built with 3 main objectives in mind: -- **Help GraphQL developers to get to know their GraphQL APIs** a little more with our Schema Registry, Performance Monitoring, Alerts, and Integrations. +- **Help GraphQL developers to get to know their GraphQL APIs** a little more with our Schema + Registry, Performance Monitoring, Alerts, and Integrations. - **Support all kinds of GraphQL APIs**, from Federation, and Stitching, to standalone APIs. - **Open Source at the heart**: 100% open-source and build in public with the community. -- **A plug and play SaaS solution**: to give access to Hive to most people with a generous free "Hobby plan" +- **A plug and play SaaS solution**: to give access to Hive to most people with a generous free + "Hobby plan" ## Features Overview @@ -21,18 +25,22 @@ GraphQL Hive has been built with 3 main objectives in mind: GraphQL Hive offers 3 useful features to manage your GraphQL API: -- **Prevent breaking changes** - GraphQL Hive will run a set of checks and notify your team via Slack, GitHub, or within the application. +- **Prevent breaking changes** - GraphQL Hive will run a set of checks and notify your team via + Slack, GitHub, or within the application. - **Data-driven** definition of a “breaking change” based on Operations Monitoring. -- **History of changes** - an access to the full history of changes, even on a complex composed schema (Federation, Stitching). +- **History of changes** - an access to the full history of changes, even on a complex composed + schema (Federation, Stitching). - **High-availability and multi-zone CDN** service based on Cloudflare to access Schema Registry ### Monitoring -Once a Schema is deployed, **it is important to be aware of how it is used and what is the experience of its final users**. +Once a Schema is deployed, **it is important to be aware of how it is used and what is the +experience of its final users**. ## Self-hosted -GraphQL Hive is completely open-source under the MIT license, meaning that you are free to host on your own infrastructure. +GraphQL Hive is completely open-source under the MIT license, meaning that you are free to host on +your own infrastructure. GraphQL Hive helps you get a global overview of the usage of your GraphQL API with: @@ -43,13 +51,16 @@ GraphQL Hive helps you get a global overview of the usage of your GraphQL API wi ### Integrations -GraphQL Hive is well integrated with **Slack** and most **CI/CD** systems to get you up and running as smoothly as possible! +GraphQL Hive is well integrated with **Slack** and most **CI/CD** systems to get you up and running +as smoothly as possible! GraphQL Hive can notify your team when schema changes occur, either via Slack or a custom webhook. -Also, the Hive CLI allows integration of the schema checks mechanism to all CI/CD systems (GitHub, BitBucket, Azure, and others). The same applies for schema publishing and operations checks. +Also, the Hive CLI allows integration of the schema checks mechanism to all CI/CD systems (GitHub, +BitBucket, Azure, and others). The same applies for schema publishing and operations checks. -If you are using GitHub, you can directly benefit from the **GraphQL Hive app that will automatically add status checks to your PRs**! +If you are using GitHub, you can directly benefit from the **GraphQL Hive app that will +automatically add status checks to your PRs**! ### Join us in building the future of GraphQL Hive diff --git a/codegen.yml b/codegen.yml index aec6e82e3..73fdcd005 100644 --- a/codegen.yml +++ b/codegen.yml @@ -21,9 +21,12 @@ generates: DateTime: string SafeInt: number mappers: - SchemaChangeConnection: ../shared/mappers#SchemaChangeConnection as SchemaChangeConnectionMapper - SchemaErrorConnection: ../shared/mappers#SchemaErrorConnection as SchemaErrorConnectionMapper - OrganizationConnection: ../shared/mappers#OrganizationConnection as OrganizationConnectionMapper + SchemaChangeConnection: + ../shared/mappers#SchemaChangeConnection as SchemaChangeConnectionMapper + SchemaErrorConnection: + ../shared/mappers#SchemaErrorConnection as SchemaErrorConnectionMapper + OrganizationConnection: + ../shared/mappers#OrganizationConnection as OrganizationConnectionMapper UserConnection: ../shared/mappers#UserConnection as UserConnectionMapper ActivityConnection: ../shared/mappers#ActivityConnection as ActivityConnectionMapper MemberConnection: ../shared/mappers#MemberConnection as MemberConnectionMapper @@ -31,16 +34,20 @@ generates: TargetConnection: ../shared/mappers#TargetConnection as TargetConnectionMapper SchemaConnection: ../shared/mappers#SchemaConnection as SchemaConnectionMapper TokenConnection: ../shared/mappers#TokenConnection as TokenConnectionMapper - OperationStatsConnection: ../shared/mappers#OperationStatsConnection as OperationStatsConnectionMapper - ClientStatsConnection: ../shared/mappers#ClientStatsConnection as ClientStatsConnectionMapper + OperationStatsConnection: + ../shared/mappers#OperationStatsConnection as OperationStatsConnectionMapper + ClientStatsConnection: + ../shared/mappers#ClientStatsConnection as ClientStatsConnectionMapper OperationsStats: ../shared/mappers#OperationsStats as OperationsStatsMapper DurationStats: ../shared/mappers#DurationStats as DurationStatsMapper SchemaComparePayload: ../shared/mappers#SchemaComparePayload as SchemaComparePayloadMapper SchemaCompareResult: ../shared/mappers#SchemaCompareResult as SchemaCompareResultMapper - SchemaVersionConnection: ../shared/mappers#SchemaVersionConnection as SchemaVersionConnectionMapper + SchemaVersionConnection: + ../shared/mappers#SchemaVersionConnection as SchemaVersionConnectionMapper SchemaVersion: ../shared/mappers#SchemaVersion as SchemaVersionMapper Schema: ../shared/mappers#Schema as SchemaMapper - PersistedOperationConnection: ../shared/mappers#PersistedOperationConnection as PersistedOperationMapper + PersistedOperationConnection: + ../shared/mappers#PersistedOperationConnection as PersistedOperationMapper Organization: ../shared/entities#Organization as OrganizationMapper Project: ../shared/entities#Project as ProjectMapper Target: ../shared/entities#Target as TargetMapper @@ -55,13 +62,15 @@ generates: AdminQuery: '{}' AdminStats: '{ daysLimit?: number | null }' AdminGeneralStats: '{ daysLimit?: number | null }' - AdminOrganizationStats: ../shared/entities#AdminOrganizationStats as AdminOrganizationStatsMapper + AdminOrganizationStats: + ../shared/entities#AdminOrganizationStats as AdminOrganizationStatsMapper UsageEstimation: '../shared/mappers#TargetsEstimationFilter' UsageEstimationScope: '../shared/mappers#TargetsEstimationDateFilter' BillingPaymentMethod: 'StripeTypes.PaymentMethod.Card' BillingDetails: 'StripeTypes.PaymentMethod.BillingDetails' BillingInvoice: 'StripeTypes.Invoice' - OrganizationGetStarted: ../shared/entities#OrganizationGetStarted as OrganizationGetStartedMapper + OrganizationGetStarted: + ../shared/entities#OrganizationGetStarted as OrganizationGetStartedMapper SchemaExplorer: ../shared/mappers#SchemaExplorerMapper GraphQLObjectType: ../shared/mappers#GraphQLObjectTypeMapper GraphQLInterfaceType: ../shared/mappers#GraphQLInterfaceTypeMapper @@ -74,7 +83,8 @@ generates: GraphQLField: ../shared/mappers#GraphQLFieldMapper GraphQLInputField: ../shared/mappers#GraphQLInputFieldMapper GraphQLArgument: ../shared/mappers#GraphQLArgumentMapper - OrganizationInvitation: ../shared/entities#OrganizationInvitation as OrganizationInvitationMapper + OrganizationInvitation: + ../shared/entities#OrganizationInvitation as OrganizationInvitationMapper OIDCIntegration: '../shared/entities#OIDCIntegration as OIDCIntegrationMapper' User: '../shared/entities#User as UserMapper' plugins: diff --git a/deployment/index.ts b/deployment/index.ts index 79fffa61b..284529fb4 100644 --- a/deployment/index.ts +++ b/deployment/index.ts @@ -182,8 +182,14 @@ const schemaApi = deploySchema({ broker: cfBroker, }); -const supertokensApiKey = new random.RandomPassword('supertokens-api-key', { length: 31, special: false }); -const auth0LegacyMigrationKey = new random.RandomPassword('auth0-legacy-migration-key', { length: 69, special: false }); +const supertokensApiKey = new random.RandomPassword('supertokens-api-key', { + length: 31, + special: false, +}); +const auth0LegacyMigrationKey = new random.RandomPassword('auth0-legacy-migration-key', { + length: 69, + special: false, +}); const oauthConfig = new pulumi.Config('oauth'); diff --git a/deployment/package.json b/deployment/package.json index ca03f56aa..c37e5a264 100644 --- a/deployment/package.json +++ b/deployment/package.json @@ -4,10 +4,6 @@ "scripts": { "typecheck": "tsc --noEmit" }, - "devDependencies": { - "@types/mime-types": "2.1.1", - "@types/node": "18.11.5" - }, "dependencies": { "@manypkg/get-packages": "1.1.3", "@pulumi/azure": "5.23.0", @@ -17,5 +13,9 @@ "@pulumi/pulumi": "3.44.1", "@pulumi/random": "4.8.2", "pg-connection-string": "2.5.0" + }, + "devDependencies": { + "@types/mime-types": "2.1.1", + "@types/node": "18.11.5" } } diff --git a/deployment/services/app.ts b/deployment/services/app.ts index 16afb53f3..91bc063f4 100644 --- a/deployment/services/app.ts +++ b/deployment/services/app.ts @@ -206,6 +206,6 @@ export function deployApp({ ], port: 3000, }, - [graphql.service, graphql.deployment, dbMigrations] + [graphql.service, graphql.deployment, dbMigrations], ).deploy(); } diff --git a/deployment/services/billing.ts b/deployment/services/billing.ts index b33fe5596..6735d2fb7 100644 --- a/deployment/services/billing.ts +++ b/deployment/services/billing.ts @@ -29,7 +29,9 @@ export function deployStripeBilling({ dbMigrations: DbMigrations; }) { const rawConnectionString = apiConfig.requireSecret('postgresConnectionString'); - const connectionString = rawConnectionString.apply(rawConnectionString => parse(rawConnectionString)); + const connectionString = rawConnectionString.apply(rawConnectionString => + parse(rawConnectionString), + ); return new RemoteArtifactAsServiceDeployment( 'stripe-billing', @@ -56,6 +58,6 @@ export function deployStripeBilling({ packageInfo: packageHelper.npmPack('@hive/stripe-billing'), port: 4000, }, - [dbMigrations, usageEstimator.service, usageEstimator.deployment] + [dbMigrations, usageEstimator.service, usageEstimator.deployment], ).deploy(); } diff --git a/deployment/services/cloudflare-security.ts b/deployment/services/cloudflare-security.ts index 002c9e540..df2eb7c13 100644 --- a/deployment/services/cloudflare-security.ts +++ b/deployment/services/cloudflare-security.ts @@ -7,11 +7,16 @@ function toExpressionList(items: string[]): string { return items.map(v => `"${v}"`).join(' '); } -export function deployCloudFlareSecurityTransform(options: { envName: string; ignoredPaths: string[] }) { +export function deployCloudFlareSecurityTransform(options: { + envName: string; + ignoredPaths: string[]; +}) { // We deploy it only once, because CloudFlare is not super friendly for multiple deployments of "http_response_headers_transform" rules // The single rule, deployed to prod, covers all other envs, and infers the hostname dynamically. if (options.envName !== 'prod') { - console.warn(`Skipped deploy security headers (see "cloudflare-security.ts") for env ${options.envName}`); + console.warn( + `Skipped deploy security headers (see "cloudflare-security.ts") for env ${options.envName}`, + ); return; } diff --git a/deployment/services/db-migrations.ts b/deployment/services/db-migrations.ts index c0d813d24..4e7633d1f 100644 --- a/deployment/services/db-migrations.ts +++ b/deployment/services/db-migrations.ts @@ -24,7 +24,9 @@ export function deployDbMigrations({ kafka: Kafka; }) { const rawConnectionString = apiConfig.requireSecret('postgresConnectionString'); - const connectionString = rawConnectionString.apply(rawConnectionString => parse(rawConnectionString)); + const connectionString = rawConnectionString.apply(rawConnectionString => + parse(rawConnectionString), + ); const { job } = new RemoteArtifactAsServiceDeployment( 'db-migrations', @@ -50,7 +52,7 @@ export function deployDbMigrations({ packageInfo: packageHelper.npmPack('@hive/storage'), }, [clickhouse.deployment, clickhouse.service], - clickhouse.service + clickhouse.service, ).deployAsJob(); return job; diff --git a/deployment/services/emails.ts b/deployment/services/emails.ts index 0cb6c427a..e9b533cd3 100644 --- a/deployment/services/emails.ts +++ b/deployment/services/emails.ts @@ -55,7 +55,7 @@ export function deployEmails({ packageInfo: packageHelper.npmPack('@hive/emails'), replicas: 1, }, - [redis.deployment, redis.service] + [redis.deployment, redis.service], ).deploy(); return { deployment, service, localEndpoint: serviceLocalEndpoint(service) }; diff --git a/deployment/services/graphql.ts b/deployment/services/graphql.ts index d27fb2170..334794230 100644 --- a/deployment/services/graphql.ts +++ b/deployment/services/graphql.ts @@ -72,7 +72,9 @@ export function deployGraphQL({ }; }) { const rawConnectionString = apiConfig.requireSecret('postgresConnectionString'); - const connectionString = rawConnectionString.apply(rawConnectionString => parse(rawConnectionString)); + const connectionString = rawConnectionString.apply(rawConnectionString => + parse(rawConnectionString), + ); return new RemoteArtifactAsServiceDeployment( 'graphql-api', @@ -148,6 +150,6 @@ export function deployGraphQL({ clickhouse.service, rateLimit.deployment, rateLimit.service, - ] + ], ).deploy(); } diff --git a/deployment/services/police.ts b/deployment/services/police.ts index 580e408ad..3fac1bbde 100644 --- a/deployment/services/police.ts +++ b/deployment/services/police.ts @@ -10,7 +10,7 @@ export function deployCloudflarePolice({ envName, rootDns }: { envName: string; cfCustomConfig.require('zoneId'), cloudflareProviderConfig.require('accountId'), cfCustomConfig.requireSecret('policeApiToken'), - rootDns + rootDns, ); return police.deploy(); diff --git a/deployment/services/proxy.ts b/deployment/services/proxy.ts index 9f6d6e981..49e518da7 100644 --- a/deployment/services/proxy.ts +++ b/deployment/services/proxy.ts @@ -50,7 +50,7 @@ export function deployProxy({ path: '/', service: docs.service, }, - ] + ], ) .registerService({ record: appHostname }, [ { diff --git a/deployment/services/rate-limit.ts b/deployment/services/rate-limit.ts index 0793f8669..570727015 100644 --- a/deployment/services/rate-limit.ts +++ b/deployment/services/rate-limit.ts @@ -32,7 +32,9 @@ export function deployRateLimit({ emails: Emails; }) { const rawConnectionString = apiConfig.requireSecret('postgresConnectionString'); - const connectionString = rawConnectionString.apply(rawConnectionString => parse(rawConnectionString)); + const connectionString = rawConnectionString.apply(rawConnectionString => + parse(rawConnectionString), + ); return new RemoteArtifactAsServiceDeployment( 'rate-limiter', @@ -60,6 +62,6 @@ export function deployRateLimit({ packageInfo: packageHelper.npmPack('@hive/rate-limit'), port: 4000, }, - [dbMigrations, usageEstimator.service, usageEstimator.deployment] + [dbMigrations, usageEstimator.service, usageEstimator.deployment], ).deploy(); } diff --git a/deployment/services/schema.ts b/deployment/services/schema.ts index 3ac7c260c..516d9d820 100644 --- a/deployment/services/schema.ts +++ b/deployment/services/schema.ts @@ -48,6 +48,6 @@ export function deploySchema({ replicas: 2, pdb: true, }, - [redis.deployment, redis.service] + [redis.deployment, redis.service], ).deploy(); } diff --git a/deployment/services/tokens.ts b/deployment/services/tokens.ts index fc4c92fcc..9ef0c61bc 100644 --- a/deployment/services/tokens.ts +++ b/deployment/services/tokens.ts @@ -26,7 +26,9 @@ export function deployTokens({ heartbeat?: string; }) { const rawConnectionString = apiConfig.requireSecret('postgresConnectionString'); - const connectionString = rawConnectionString.apply(rawConnectionString => parse(rawConnectionString)); + const connectionString = rawConnectionString.apply(rawConnectionString => + parse(rawConnectionString), + ); return new RemoteArtifactAsServiceDeployment( 'tokens-service', @@ -50,6 +52,6 @@ export function deployTokens({ HEARTBEAT_ENDPOINT: heartbeat ?? '', }, }, - [dbMigrations] + [dbMigrations], ).deploy(); } diff --git a/deployment/services/usage-estimation.ts b/deployment/services/usage-estimation.ts index 6a5a155e2..4f4374432 100644 --- a/deployment/services/usage-estimation.ts +++ b/deployment/services/usage-estimation.ts @@ -46,6 +46,6 @@ export function deployUsageEstimation({ packageInfo: packageHelper.npmPack('@hive/usage-estimator'), port: 4000, }, - [dbMigrations] + [dbMigrations], ).deploy(); } diff --git a/deployment/services/usage-ingestor.ts b/deployment/services/usage-ingestor.ts index 8ec666c70..603a57b01 100644 --- a/deployment/services/usage-ingestor.ts +++ b/deployment/services/usage-ingestor.ts @@ -87,6 +87,6 @@ export function deployUsageIngestor({ maxReplicas: maxReplicas, }, }, - [clickhouse.deployment, clickhouse.service, dbMigrations] + [clickhouse.deployment, clickhouse.service, dbMigrations], ).deploy(); } diff --git a/deployment/services/usage.ts b/deployment/services/usage.ts index 83669bffe..0e5a96267 100644 --- a/deployment/services/usage.ts +++ b/deployment/services/usage.ts @@ -73,6 +73,6 @@ export function deployUsage({ maxReplicas: maxReplicas, }, }, - [dbMigrations, tokens.deployment, tokens.service, rateLimit.deployment, rateLimit.service] + [dbMigrations, tokens.deployment, tokens.service, rateLimit.deployment, rateLimit.service], ).deploy(); } diff --git a/deployment/services/webhooks.ts b/deployment/services/webhooks.ts index ced511d6a..22e4d20fb 100644 --- a/deployment/services/webhooks.ts +++ b/deployment/services/webhooks.ts @@ -50,6 +50,6 @@ export function deployWebhooks({ packageInfo: packageHelper.npmPack('@hive/webhooks'), replicas: 1, }, - [redis.deployment, redis.service] + [redis.deployment, redis.service], ).deploy(); } diff --git a/deployment/utils/botkube.ts b/deployment/utils/botkube.ts index 3d268207d..b5bab2738 100644 --- a/deployment/utils/botkube.ts +++ b/deployment/utils/botkube.ts @@ -80,7 +80,7 @@ export class BotKube { }, { dependsOn: [ns], - } + }, ); } } diff --git a/deployment/utils/cert-manager.ts b/deployment/utils/cert-manager.ts index 70a6b00b9..488ffc48e 100644 --- a/deployment/utils/cert-manager.ts +++ b/deployment/utils/cert-manager.ts @@ -37,7 +37,7 @@ export class CertManager { }, { dependsOn: [certManager], - } + }, ); return { diff --git a/deployment/utils/clickhouse.ts b/deployment/utils/clickhouse.ts index a6b53b176..fbb2be910 100644 --- a/deployment/utils/clickhouse.ts +++ b/deployment/utils/clickhouse.ts @@ -8,7 +8,7 @@ export class Clickhouse { protected options: { env?: kx.types.Container['env']; sentryDsn: string; - } + }, ) {} deploy() { @@ -79,7 +79,7 @@ export class Clickhouse { }, { annotations: metadata.annotations, - } + }, ), }); const service = deployment.createService({}); diff --git a/deployment/utils/cloudflare.ts b/deployment/utils/cloudflare.ts index 2837c3207..0758be37c 100644 --- a/deployment/utils/cloudflare.ts +++ b/deployment/utils/cloudflare.ts @@ -12,7 +12,7 @@ export class CloudflareCDN { authPrivateKey: pulumi.Output; sentryDsn: string; release: string; - } + }, ) {} deploy() { @@ -21,7 +21,10 @@ export class CloudflareCDN { }); const script = new cf.WorkerScript('hive-ha-worker', { - content: readFileSync(resolve(__dirname, '../../packages/services/cdn-worker/dist/worker.js'), 'utf-8'), + content: readFileSync( + resolve(__dirname, '../../packages/services/cdn-worker/dist/worker.js'), + 'utf-8', + ), name: `hive-storage-cdn-${this.config.envName}`, kvNamespaceBindings: [ { @@ -78,12 +81,15 @@ export class CloudflareBroker { secretSignature: pulumi.Output; sentryDsn: string; release: string; - } + }, ) {} deploy() { const script = new cf.WorkerScript('hive-broker-worker', { - content: readFileSync(resolve(__dirname, '../../packages/services/broker-worker/dist/worker.js'), 'utf-8'), + content: readFileSync( + resolve(__dirname, '../../packages/services/broker-worker/dist/worker.js'), + 'utf-8', + ), name: `hive-broker-${this.config.envName}`, secretTextBindings: [ { diff --git a/deployment/utils/helpers.ts b/deployment/utils/helpers.ts index ad4d3726f..ce1b651d5 100644 --- a/deployment/utils/helpers.ts +++ b/deployment/utils/helpers.ts @@ -5,7 +5,9 @@ export function isProduction(deploymentEnv: DeploymentEnvironment | string): boo } export function isStaging(deploymentEnv: DeploymentEnvironment | string): boolean { - return isDeploymentEnvironment(deploymentEnv) ? deploymentEnv.ENVIRONMENT === 'staging' : deploymentEnv === 'staging'; + return isDeploymentEnvironment(deploymentEnv) + ? deploymentEnv.ENVIRONMENT === 'staging' + : deploymentEnv === 'staging'; } export function isDeploymentEnvironment(value: any): value is DeploymentEnvironment { diff --git a/deployment/utils/local-endpoint.ts b/deployment/utils/local-endpoint.ts index effd1f4cf..f836e11ad 100644 --- a/deployment/utils/local-endpoint.ts +++ b/deployment/utils/local-endpoint.ts @@ -6,7 +6,9 @@ export function serviceLocalEndpoint(service: k8s.types.input.core.v1.Service) { const defaultPort = (spec?.ports || [])[0]; const portText = defaultPort ? `:${defaultPort.port}` : ''; - return `http://${metadata?.name}.${metadata?.namespace || 'default'}.svc.cluster.local${portText}`; + return `http://${metadata?.name}.${ + metadata?.namespace || 'default' + }.svc.cluster.local${portText}`; }); } diff --git a/deployment/utils/observability.ts b/deployment/utils/observability.ts index ba743bba7..da35d8303 100644 --- a/deployment/utils/observability.ts +++ b/deployment/utils/observability.ts @@ -210,7 +210,10 @@ export class Observability { regex: '(.+)', }, { - source_labels: ['__address__', '__meta_kubernetes_pod_annotation_prometheus_io_port'], + source_labels: [ + '__address__', + '__meta_kubernetes_pod_annotation_prometheus_io_port', + ], action: 'replace', regex: '([^:]+)(?::d+)?;(d+)', replacement: '$1:$2', @@ -358,7 +361,7 @@ export class Observability { }, { dependsOn: [ns], - } + }, ); } } diff --git a/deployment/utils/pod-builder.ts b/deployment/utils/pod-builder.ts index c62b68080..6232492ea 100644 --- a/deployment/utils/pod-builder.ts +++ b/deployment/utils/pod-builder.ts @@ -14,7 +14,7 @@ export function normalizeEnv(env: kx.types.Container['env']): any[] { export class PodBuilder extends kx.PodBuilder { public asExtendedDeploymentSpec( args?: kx.types.PodBuilderDeploymentSpec, - metadata?: k8s.types.input.meta.v1.ObjectMeta + metadata?: k8s.types.input.meta.v1.ObjectMeta, ): pulumi.Output { const podName = this.podSpec.containers.apply((containers: any) => { return pulumi.output(containers[0].name); diff --git a/deployment/utils/police.ts b/deployment/utils/police.ts index 8b9a7b0d9..4406490e9 100644 --- a/deployment/utils/police.ts +++ b/deployment/utils/police.ts @@ -9,7 +9,7 @@ export class HivePolice { private zoneId: string, private accountId: string, private cfToken: pulumi.Output, - private rootDns: string + private rootDns: string, ) {} deploy() { @@ -18,7 +18,10 @@ export class HivePolice { }); const script = new cf.WorkerScript('hive-police-worker', { - content: readFileSync(resolve(__dirname, '../../packages/services/police-worker/dist/worker.js'), 'utf-8'), + content: readFileSync( + resolve(__dirname, '../../packages/services/police-worker/dist/worker.js'), + 'utf-8', + ), name: `hive-police-${this.envName}`, kvNamespaceBindings: [ { diff --git a/deployment/utils/redis.ts b/deployment/utils/redis.ts index 9d5443f7a..7186728f4 100644 --- a/deployment/utils/redis.ts +++ b/deployment/utils/redis.ts @@ -10,7 +10,7 @@ export class Redis { protected options: { env?: kx.types.Container['env']; password: string; - } + }, ) {} deploy({ limits }: { limits: k8s.types.input.core.v1.ResourceRequirements['limits'] }) { @@ -105,7 +105,7 @@ fi }, { annotations: metadata.annotations, - } + }, ), }); diff --git a/deployment/utils/remote-artifact-as-service.ts b/deployment/utils/remote-artifact-as-service.ts index e02e79099..9e17b0bff 100644 --- a/deployment/utils/remote-artifact-as-service.ts +++ b/deployment/utils/remote-artifact-as-service.ts @@ -38,7 +38,7 @@ export class RemoteArtifactAsServiceDeployment { }; }, protected dependencies?: Array, - protected parent?: pulumi.Resource | null + protected parent?: pulumi.Resource | null, ) {} deployAsJob() { @@ -50,7 +50,7 @@ export class RemoteArtifactAsServiceDeployment { { spec: pb.asJobSpec(), }, - { dependsOn: this.dependencies?.filter(isDefined) } + { dependsOn: this.dependencies?.filter(isDefined) }, ); return { job }; @@ -214,13 +214,13 @@ export class RemoteArtifactAsServiceDeployment { }, { annotations: metadata.annotations, - } + }, ), }, { dependsOn: this.dependencies?.filter(isDefined), parent: this.parent ?? undefined, - } + }, ); if (this.options.pdb) { @@ -265,7 +265,7 @@ export class RemoteArtifactAsServiceDeployment { }, { dependsOn: [deployment, service], - } + }, ); } diff --git a/deployment/utils/reverse-proxy.ts b/deployment/utils/reverse-proxy.ts index 4d4bc0e33..590d9173b 100644 --- a/deployment/utils/reverse-proxy.ts +++ b/deployment/utils/reverse-proxy.ts @@ -18,7 +18,7 @@ export class Proxy { virtualHost?: Output; httpsUpstream?: boolean; withWwwDomain?: boolean; - }[] + }[], ) { const cert = new k8s.apiextensions.CustomResource(`cert-${dns.record}`, { apiVersion: 'cert-manager.io/v1', @@ -104,7 +104,7 @@ export class Proxy { }, { dependsOn: [cert, this.lbService!], - } + }, ); return this; @@ -183,7 +183,10 @@ export class Proxy { this.lbService = proxyController.getResource('v1/Service', 'contour/contour-proxy-envoy'); - const contourDeployment = proxyController.getResource('apps/v1/Deployment', 'contour/contour-proxy-contour'); + const contourDeployment = proxyController.getResource( + 'apps/v1/Deployment', + 'contour/contour-proxy-contour', + ); new k8s.policy.v1.PodDisruptionBudget('contour-pdb', { spec: { minAvailable: 1, @@ -191,7 +194,10 @@ export class Proxy { }, }); - const envoyDaemonset = proxyController.getResource('apps/v1/ReplicaSet', 'contour/contour-proxy-envoy'); + const envoyDaemonset = proxyController.getResource( + 'apps/v1/ReplicaSet', + 'contour/contour-proxy-envoy', + ); new k8s.policy.v1.PodDisruptionBudget('envoy-pdb', { spec: { minAvailable: 1, @@ -219,7 +225,7 @@ export class Proxy { }, { dependsOn: [this.lbService], - } + }, ); return this; diff --git a/docker-compose.community.yml b/docker-compose.community.yml index 1ab2bf497..ffe07b834 100644 --- a/docker-compose.community.yml +++ b/docker-compose.community.yml @@ -65,7 +65,18 @@ services: soft: 20000 hard: 40000 healthcheck: - test: ['CMD', 'cub', 'kafka-ready', '1', '5', '-b', '127.0.0.1:9092', '-c', '/etc/kafka/kafka.properties'] + test: + [ + 'CMD', + 'cub', + 'kafka-ready', + '1', + '5', + '-b', + '127.0.0.1:9092', + '-c', + '/etc/kafka/kafka.properties', + ] interval: 15s timeout: 10s retries: 6 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 81424b223..9717cd79e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -4,4 +4,5 @@ ![Design Diagram](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/kamilkisiela/graphql-hive/main/docs/architecture.puml) -Note: The relationships in this diagram is based on [this docker-compose](https://github.com/kamilkisiela/graphql-hive/blob/main/docker-compose.community.yml) +Note: The relationships in this diagram is based on +[this docker-compose](https://github.com/kamilkisiela/graphql-hive/blob/main/docker-compose.community.yml) diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index a7a7510f8..f68b0f73e 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -1,15 +1,23 @@ ## Deployment -Deployment is based on NPM packages. That means we are bundling (as much as possible) each service or package, and publish it to the private GitHub Packages artifactory. +Deployment is based on NPM packages. That means we are bundling (as much as possible) each service +or package, and publish it to the private GitHub Packages artifactory. -Doing that allows us to have a simple, super fast deployments, because we don't need to deal with Docker images (which are heavy). +Doing that allows us to have a simple, super fast deployments, because we don't need to deal with +Docker images (which are heavy). -We create an executable package (with `bin` entrypoint) and then use `npx PACKAGE_NAME@PACKAGE_VERSION` as command for a base Docker image of NodeJS. So instead of building a Docker image for each change, we build NPM package, and the Docker image we are using in prod is the same. +We create an executable package (with `bin` entrypoint) and then use +`npx PACKAGE_NAME@PACKAGE_VERSION` as command for a base Docker image of NodeJS. So instead of +building a Docker image for each change, we build NPM package, and the Docker image we are using in +prod is the same. -Think of it as Lambda (bundled JS, runtime is predefined) without all the crap (weird cache, weird pricing, cold start and so on). +Think of it as Lambda (bundled JS, runtime is predefined) without all the crap (weird cache, weird +pricing, cold start and so on). ### How to deploy? -We are using Pulumi (infrastructure as code) to describe and run our deployment. It's managed as GitHub Actions that runs on every bump release by Changesets. +We are using Pulumi (infrastructure as code) to describe and run our deployment. It's managed as +GitHub Actions that runs on every bump release by Changesets. -So changes are aggregated in a Changesets PR, and when merge, it updated the deployment manifest `package.json`, leading to a deployment of only the updated packages to production. +So changes are aggregated in a Changesets PR, and when merge, it updated the deployment manifest +`package.json`, leading to a deployment of only the updated packages to production. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index e4f2fff71..1bc291e10 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -16,14 +16,18 @@ Developing Hive locally requires you to have the following software installed lo - In the root of the repo, run `nvm use` to use the same version of node as mentioned - Run `pnpm i` at the root to install all the dependencies and run the hooks - Run `pnpm setup` to create and apply migrations on the PostgreSQL database -- Run `pnpm generate` to generate the typings from the graphql files (use `pnpm graphql:generate` if you only need to run GraphQL Codegen) +- Run `pnpm generate` to generate the typings from the graphql files (use `pnpm graphql:generate` if + you only need to run GraphQL Codegen) - Run `pnpm build` to build all services - Click on `Start Hive` in the bottom bar of VSCode - If you are not added to the list of guest users, request access from The Guild maintainers - - Alternatively, [configure hive to use your own Auth0 Application](#setting-up-auth0-app-for-developing) + - Alternatively, + [configure hive to use your own Auth0 Application](#setting-up-auth0-app-for-developing) - Open the UI (`http://localhost:3000` by default) and Sign in with any of the identity provider - Once this is done, you should be able to login and use the project -- Once you generate the token against your organization/personal account in hive, the same can be added locally to `hive.json` within `packages/libraries/cli` which can be used to interact via the hive cli with the registry +- Once you generate the token against your organization/personal account in hive, the same can be + added locally to `hive.json` within `packages/libraries/cli` which can be used to interact via the + hive cli with the registry ## Development Seed @@ -33,11 +37,15 @@ We have a script to feed your local instance of Hive. 2. Make sure `usage` and `usage-ingestor` are running as well (with `pnpm dev`) 3. Open Hive app, create a project and a target, then create a token 4. Run the seed script: `TOKEN="MY_TOKEN_HERE" pnpm seed` -5. This should report a dummy schema and some dummy usage data to your local instance of Hive, allowing you to test features e2e +5. This should report a dummy schema and some dummy usage data to your local instance of Hive, + allowing you to test features e2e > Note: You can set `STAGING=1` in order to target staging env and seed a target there. -> To send more operations and test heavy load on Hive instance, you can also set `OPERATIONS` (amount of operations in each interval round, default is `1`) and `INTERVAL` (frequency of sending operations, default: `1000`ms). For example, using `INTERVAL=1000 OPERATIONS=1000` will send 1000 requests per second. +> To send more operations and test heavy load on Hive instance, you can also set `OPERATIONS` +> (amount of operations in each interval round, default is `1`) and `INTERVAL` (frequency of sending +> operations, default: `1000`ms). For example, using `INTERVAL=1000 OPERATIONS=1000` will send 1000 +> requests per second. ## Publish your first schema (manually) @@ -45,14 +53,18 @@ We have a script to feed your local instance of Hive. 2. Create a project and a target 3. Create a token from that target 4. Go to `packages/libraries/cli` and run `pnpm build` -5. Inside `packages/libraries/cli`, run: `pnpm start schema:publish --token "YOUR_TOKEN_HERE" --registry "http://localhost:4000/graphql" examples/single.graphql` +5. Inside `packages/libraries/cli`, run: + `pnpm start schema:publish --token "YOUR_TOKEN_HERE" --registry "http://localhost:4000/graphql" examples/single.graphql` ### Setting up Slack App for developing -1. [Download](https://loophole.cloud/download) Loophole CLI (same as ngrok but supports non-random urls) +1. [Download](https://loophole.cloud/download) Loophole CLI (same as ngrok but supports non-random + urls) 2. Log in to Loophole `$ loophole account login` -3. Start the proxy by running `$ loophole http 3000 --hostname hive-` (@kamilkisiela I use `hive-kamil`). It creates `https://hive-.loophole.site` endpoint. -4. Message @kamilkisiela and send him the url (He will update the list of accepted redirect urls in both Auth0 and Slack App). +3. Start the proxy by running `$ loophole http 3000 --hostname hive-` (@kamilkisiela I + use `hive-kamil`). It creates `https://hive-.loophole.site` endpoint. +4. Message @kamilkisiela and send him the url (He will update the list of accepted redirect urls in + both Auth0 and Slack App). 5. Update `APP_BASE_URL` and `AUTH0_BASE_URL` in [`packages/web/app/.env`](./packages/web/app/.env) 6. Run `packages/web/app` and open `https://hive-.loophole.site`. @@ -61,25 +73,28 @@ We have a script to feed your local instance of Hive. ### Setting up GitHub App for developing 1. Follow the steps above for Slack App -2. Update `Setup URL` in [GraphQL Hive Development](https://github.com/organizations/the-guild-org/settings/apps/graphql-hive-development) app and set it to `https://hive-.loophole.site/api/github/setup-callback` +2. Update `Setup URL` in + [GraphQL Hive Development](https://github.com/organizations/the-guild-org/settings/apps/graphql-hive-development) + app and set it to `https://hive-.loophole.site/api/github/setup-callback` ### Run Hive 1. Click on Start Hive in the bottom bar of VSCode -2. Open the UI (`http://localhost:3000` by default) and register any email and - password -3. Sending e-mails is mocked out during local development, so in order to - verify the account find the verification link by visiting the email server's - `/_history` endpoint - `http://localhost:6260/_history` by default. +2. Open the UI (`http://localhost:3000` by default) and register any email and password +3. Sending e-mails is mocked out during local development, so in order to verify the account find + the verification link by visiting the email server's `/_history` endpoint - + `http://localhost:6260/_history` by default. - Searching for `token` should help you find the link. ### Legacy Auth0 Integration **Note:** If you are not working at The Guild, you can safely ignore this section. -Since we migrated from Auth0 to SuperTokens there is a compatibility layer for importing/migrating accounts from Auth0 to SuperTokens. +Since we migrated from Auth0 to SuperTokens there is a compatibility layer for importing/migrating +accounts from Auth0 to SuperTokens. -By default you don't need to set this up and can just use SuperTokens locally. However, if you need to test some stuff or fix the Auth0 -> SuperTokens migration flow you have to set up some stuff. +By default you don't need to set this up and can just use SuperTokens locally. However, if you need +to test some stuff or fix the Auth0 -> SuperTokens migration flow you have to set up some stuff. 1. Create your own Auth0 application 1. If you haven't already, create an account on [manage.auth0.com](https://manage.auth0.com) @@ -108,9 +123,11 @@ By default you don't need to set this up and can just use SuperTokens locally. H return callback(null, user, context); } ``` -2. Update the `.env` secrets used by your local hive instance that are found when viewing your new application on Auth0: +2. Update the `.env` secrets used by your local hive instance that are found when viewing your new + application on Auth0: - `AUTH_LEGACY_AUTH0` (set this to `1` for enabling the migration.) - `AUTH_LEGACY_AUTH0_CLIENT_ID` (e.g. `rGSrExtM9sfilpF8kbMULkMNYI2SgXro`) - - `AUTH_LEGACY_AUTH0_CLIENT_SECRET` (e.g. `gJjNQJsCaOC0nCKTgqWv2wvrh1XXXb-iqzVdn8pi2nSPq2TxxxJ9FIUYbNjheXxx`) + - `AUTH_LEGACY_AUTH0_CLIENT_SECRET` (e.g. + `gJjNQJsCaOC0nCKTgqWv2wvrh1XXXb-iqzVdn8pi2nSPq2TxxxJ9FIUYbNjheXxx`) - `AUTH_LEGACY_AUTH0_ISSUER_BASE_URL`(e.g. `https://foo-bars.us.auth0.com`) - `AUTH_LEGACY_AUTH0_AUDIENCE` (e.g. `https://foo-bars.us.auth0.com/api/v2/`) diff --git a/docs/TESTING.md b/docs/TESTING.md index ae907d107..db424cf14 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -15,7 +15,8 @@ We are using Dockest to test the following concerns: To run integration tests locally, follow: -1. Make sure you have Docker installed. If you are having issues, try to run `docker system prune` to clean the Docker caches. +1. Make sure you have Docker installed. If you are having issues, try to run `docker system prune` + to clean the Docker caches. 2. Install all deps: `pnpm i` 3. Generate types: `pnpm graphql:generate` 4. Build and pack all services: `pnpm --filter integration-tests build-and-pack` diff --git a/integration-tests/docker-compose.yml b/integration-tests/docker-compose.yml index fc54017da..88e9e8fcb 100644 --- a/integration-tests/docker-compose.yml +++ b/integration-tests/docker-compose.yml @@ -70,7 +70,18 @@ services: soft: 20000 hard: 40000 healthcheck: - test: ['CMD', 'cub', 'kafka-ready', '1', '5', '-b', '127.0.0.1:9092', '-c', '/etc/kafka/kafka.properties'] + test: + [ + 'CMD', + 'cub', + 'kafka-ready', + '1', + '5', + '-b', + '127.0.0.1:9092', + '-c', + '/etc/kafka/kafka.properties', + ] interval: 15s timeout: 10s retries: 6 diff --git a/integration-tests/jest-setup.ts b/integration-tests/jest-setup.ts index 377132c55..0a8d97034 100644 --- a/integration-tests/jest-setup.ts +++ b/integration-tests/jest-setup.ts @@ -16,5 +16,5 @@ beforeEach(() => host: redisAddress.replace(':6379', ''), port: 6379, password: 'test', - }) + }), ); diff --git a/integration-tests/package.json b/integration-tests/package.json index 5834c5eed..4533d7108 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -1,35 +1,35 @@ { "name": "integration-tests", + "version": "0.0.0", "type": "module", "private": true, - "version": "0.0.0", + "scripts": { + "build-and-pack": "(cd ../ && pnpm build:services && pnpm build:libraries && pnpm build:local) && node ./scripts/pack.mjs", + "build:local": "pnpm build-and-pack && (cd ../ && pnpm docker:build)", + "dockest": "tsup-node dockest.ts --format esm --target node16 && PWD=$PWD/.. node dist/dockest.js" + }, "dependencies": { "@app/gql": "link:./testkit/gql", "@graphql-hive/core": "0.2.3", "@graphql-typed-document-node/core": "3.1.1", + "@n1ru4l/dockest": "2.1.0-rc.6", + "@trpc/client": "9.23.2", + "@whatwg-node/fetch": "0.4.7", "auth0": "2.36.2", "axios": "0.27.2", - "dotenv": "10.0.0", "date-fns": "2.25.0", "dependency-graph": "0.11.0", - "@n1ru4l/dockest": "2.1.0-rc.6", + "dotenv": "10.0.0", "ioredis": "4.28.5", "rxjs": "^6.5.4", "slonik": "30.1.2", "tsup": "6.5.0", "yaml": "2.1.0", - "@whatwg-node/fetch": "0.4.7", - "zod": "3.15.1", - "@trpc/client": "9.23.2" + "zod": "3.15.1" }, "devDependencies": { "@hive/server": "workspace:*", "@types/ioredis": "4.28.10", "tslib": "2.4.1" - }, - "scripts": { - "build-and-pack": "(cd ../ && pnpm build:services && pnpm build:libraries && pnpm build:local) && node ./scripts/pack.mjs", - "build:local": "pnpm build-and-pack && (cd ../ && pnpm docker:build)", - "dockest": "tsup-node dockest.ts --format esm --target node16 && PWD=$PWD/.. node dist/dockest.js" } } diff --git a/integration-tests/scripts/pack.mjs b/integration-tests/scripts/pack.mjs index aa3a4ff93..8b925f850 100644 --- a/integration-tests/scripts/pack.mjs +++ b/integration-tests/scripts/pack.mjs @@ -25,7 +25,9 @@ async function main() { fsExtra.mkdirSync(tarballDir, { recursive: true }); function isBackendPackage(manifestPath) { - return JSON.parse(fs.readFileSync(manifestPath, 'utf-8')).buildOptions?.tags.includes('backend'); + return JSON.parse(fs.readFileSync(manifestPath, 'utf-8')).buildOptions?.tags.includes( + 'backend', + ); } function listBackendPackages() { @@ -35,11 +37,15 @@ async function main() { ignore: ['**/node_modules/**', '**/dist/**'], }); - return manifestPathCollection.filter(isBackendPackage).map(filepath => path.relative(cwd, path.dirname(filepath))); + return manifestPathCollection + .filter(isBackendPackage) + .map(filepath => path.relative(cwd, path.dirname(filepath))); } async function pack(location) { - const { version, name } = JSON.parse(await fsExtra.readFile(path.join(cwd, location, 'package.json'), 'utf-8')); + const { version, name } = JSON.parse( + await fsExtra.readFile(path.join(cwd, location, 'package.json'), 'utf-8'), + ); const stdout = await new Promise((resolve, reject) => { exec( `npm pack ${path.join(cwd, location, 'dist')}`, @@ -54,7 +60,7 @@ async function main() { } else { resolve(stdout); } - } + }, ); }); @@ -86,7 +92,7 @@ async function main() { console.error('[pack] Maybe you forgot to build the packages first?'); process.exit(1); } - }) + }), ); } diff --git a/integration-tests/testkit/auth.ts b/integration-tests/testkit/auth.ts index 18f129525..7e89c4cdb 100644 --- a/integration-tests/testkit/auth.ts +++ b/integration-tests/testkit/auth.ts @@ -23,7 +23,7 @@ const SignUpSignInUserResponseModel = z.object({ const signUpUserViaEmail = async ( email: string, - password: string + password: string, ): Promise> => { const response = await fetch(`${ensureEnv('SUPERTOKENS_CONNECTION_URI')}/recipe/signup`, { method: 'POST', @@ -63,7 +63,11 @@ const CreateSessionModel = z.object({ }), }); -const createSession = async (superTokensUserId: string, email: string, oidcIntegrationId: string | null) => { +const createSession = async ( + superTokensUserId: string, + email: string, + oidcIntegrationId: string | null, +) => { await internalApi.mutation('ensureUser', { superTokensUserId, email, @@ -111,18 +115,27 @@ export const userEmails: Record = { admin: 'admin@localhost.localhost', }; -const tokenResponsePromise: Record> | null> = { +const tokenResponsePromise: Record< + UserID, + Promise> | null +> = { main: null, extra: null, admin: null, }; -export function authenticate(userId: UserID, oidcIntegrationId?: string): Promise<{ access_token: string }> { +export function authenticate( + userId: UserID, + oidcIntegrationId?: string, +): Promise<{ access_token: string }> { if (!tokenResponsePromise[userId]) { - tokenResponsePromise[userId] = signUpUserViaEmail(userEmails[userId] ?? `${userId}@localhost.localhost`, password); + tokenResponsePromise[userId] = signUpUserViaEmail( + userEmails[userId] ?? `${userId}@localhost.localhost`, + password, + ); } return tokenResponsePromise[userId]!.then(data => - createSession(data.user.id, data.user.email, oidcIntegrationId ?? null) + createSession(data.user.id, data.user.email, oidcIntegrationId ?? null), ); } diff --git a/integration-tests/testkit/cli.ts b/integration-tests/testkit/cli.ts index 9af86f84f..becae8071 100644 --- a/integration-tests/testkit/cli.ts +++ b/integration-tests/testkit/cli.ts @@ -16,9 +16,13 @@ async function exec(cmd: string) { } export async function schemaPublish(args: string[]) { - return exec(['schema:publish', `--registry`, `http://${registryAddress}/graphql`, ...args].join(' ')); + return exec( + ['schema:publish', `--registry`, `http://${registryAddress}/graphql`, ...args].join(' '), + ); } export async function schemaCheck(args: string[]) { - return exec(['schema:check', `--registry`, `http://${registryAddress}/graphql`, ...args].join(' ')); + return exec( + ['schema:check', `--registry`, `http://${registryAddress}/graphql`, ...args].join(' '), + ); } diff --git a/integration-tests/testkit/db.ts b/integration-tests/testkit/db.ts index 0091bb99d..aa9f7a57f 100644 --- a/integration-tests/testkit/db.ts +++ b/integration-tests/testkit/db.ts @@ -9,14 +9,16 @@ export const resetDb = async (conn: DatabasePoolConnection) => { WHERE "schemaname" = 'public'; `); - const tablenames = result.map(({ tablename }) => tablename).filter(tablename => !migrationTables.includes(tablename)); + const tablenames = result + .map(({ tablename }) => tablename) + .filter(tablename => !migrationTables.includes(tablename)); if (tablenames.length) { await conn.query(sql` TRUNCATE TABLE ${sql.join( tablenames.map(name => sql.identifier([name])), - sql`,` + sql`,`, )} RESTART IDENTITY ; diff --git a/integration-tests/testkit/dockest.ts b/integration-tests/testkit/dockest.ts index cd5199ee2..d33189d38 100644 --- a/integration-tests/testkit/dockest.ts +++ b/integration-tests/testkit/dockest.ts @@ -14,7 +14,7 @@ export const startReadinessCheck: ReadinessCheck = ({ runner }) => { return runner.dockerEventStream$.pipe( filter(ev => ev.action === 'start'), mapTo(undefined), - take(1) + take(1), ); }; @@ -82,7 +82,9 @@ export function createServices() { } export function cleanDockerContainers() { - const output = execa(`docker ps --all --filter "name=integration-tests" --format={{.ID}}:{{.Status}}`); + const output = execa( + `docker ps --all --filter "name=integration-tests" --format={{.ID}}:{{.Status}}`, + ); if (output.stdout.length) { const runningContainers = output.stdout.split('\n'); diff --git a/integration-tests/testkit/env.ts b/integration-tests/testkit/env.ts index 7f817abab..b4bd2f713 100644 --- a/integration-tests/testkit/env.ts +++ b/integration-tests/testkit/env.ts @@ -8,7 +8,7 @@ export function invariant( condition: any, // Can provide a string, or a function that returns a string for cases where // the message takes a fair amount of effort to compute - message?: string | (() => string) + message?: string | (() => string), ): asserts condition { if (condition) { return; @@ -32,6 +32,7 @@ export function ensureEnv(key: string, valueType: 'string'): string; export function ensureEnv(key: string, valueType: 'number'): number; export function ensureEnv(key: string, valueType: 'boolean'): boolean; export function ensureEnv(key: string, valueType?: ValueType) { + // eslint-disable-next-line no-process-env let value = process.env[key]; if (value === '') { diff --git a/integration-tests/testkit/flow.ts b/integration-tests/testkit/flow.ts index 08e30562a..945d0d7f0 100644 --- a/integration-tests/testkit/flow.ts +++ b/integration-tests/testkit/flow.ts @@ -393,7 +393,11 @@ export function updateMemberAccess(input: OrganizationMemberAccessInput, authTok }); } -export function publishSchema(input: SchemaPublishInput, token: string, authHeader?: 'x-api-token' | 'authorization') { +export function publishSchema( + input: SchemaPublishInput, + token: string, + authHeader?: 'x-api-token' | 'authorization', +) { return execute({ document: gql(/* GraphQL */ ` mutation schemaPublish($input: SchemaPublishInput!) { @@ -491,7 +495,7 @@ export function setTargetValidation( } | { authToken: string; - } + }, ) { return execute({ document: gql(/* GraphQL */ ` @@ -519,7 +523,7 @@ export function updateTargetValidationSettings( } | { authToken: string; - } + }, ) { return execute({ document: gql(/* GraphQL */ ` @@ -838,11 +842,14 @@ export async function fetchMetadataFromCDN(selector: TargetSelectorInput, token: export async function updateOrgRateLimit( selector: OrganizationSelectorInput, monthlyLimits: RateLimitInput, - authToken: string + authToken: string, ) { return execute({ document: gql(/* GraphQL */ ` - mutation updateOrgRateLimit($selector: OrganizationSelectorInput!, $monthlyLimits: RateLimitInput!) { + mutation updateOrgRateLimit( + $selector: OrganizationSelectorInput! + $monthlyLimits: RateLimitInput! + ) { updateOrgRateLimit(selector: $selector, monthlyLimits: $monthlyLimits) { id } @@ -856,7 +863,10 @@ export async function updateOrgRateLimit( }); } -export async function enableExternalSchemaComposition(input: EnableExternalSchemaCompositionInput, token: string) { +export async function enableExternalSchemaComposition( + input: EnableExternalSchemaCompositionInput, + token: string, +) { return execute({ document: gql(/* GraphQL */ ` mutation enableExternalSchemaComposition($input: EnableExternalSchemaCompositionInput!) { diff --git a/integration-tests/testkit/graphql.ts b/integration-tests/testkit/graphql.ts index 037546c03..52deb6283 100644 --- a/integration-tests/testkit/graphql.ts +++ b/integration-tests/testkit/graphql.ts @@ -17,7 +17,9 @@ export async function execute( authToken?: string; token?: string; legacyAuthorizationMode?: boolean; - } & (TVariables extends Record ? { variables?: never } : { variables: TVariables }) + } & (TVariables extends Record + ? { variables?: never } + : { variables: TVariables }), ) { const response = await fetch(`http://${registryAddress}/graphql`, { method: 'POST', diff --git a/integration-tests/tests/api/legacy-auth.spec.ts b/integration-tests/tests/api/legacy-auth.spec.ts index 3254d5a98..fc54df27d 100644 --- a/integration-tests/tests/api/legacy-auth.spec.ts +++ b/integration-tests/tests/api/legacy-auth.spec.ts @@ -18,7 +18,7 @@ test('X-API-Token header should work when calling GraphQL API and collecting usa { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -29,7 +29,7 @@ test('X-API-Token header should work when calling GraphQL API and collecting usa type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -45,7 +45,7 @@ test('X-API-Token header should work when calling GraphQL API and collecting usa projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -59,7 +59,7 @@ test('X-API-Token header should work when calling GraphQL API and collecting usa sdl: `type Query { ping: String }`, }, token, - 'x-api-token' + 'x-api-token', ); expect(result.body.errors).not.toBeDefined(); @@ -98,7 +98,7 @@ test('X-API-Token header should work when calling GraphQL API and collecting usa to, }, }, - token + token, ); expect(operationStatsResult.body.errors).not.toBeDefined(); diff --git a/integration-tests/tests/api/oidc-integrations/crud.spec.ts b/integration-tests/tests/api/oidc-integrations/crud.spec.ts index 9ed74bbfd..d44179c00 100644 --- a/integration-tests/tests/api/oidc-integrations/crud.spec.ts +++ b/integration-tests/tests/api/oidc-integrations/crud.spec.ts @@ -47,10 +47,11 @@ describe('create', () => { { name: 'foo', }, - access_token + access_token, ); - const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const result = await execute({ document: CreateOIDCIntegrationMutation, @@ -139,10 +140,11 @@ describe('create', () => { { name: 'foo', }, - access_token + access_token, ); - const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const result = await execute({ document: CreateOIDCIntegrationMutation, @@ -180,10 +182,11 @@ describe('create', () => { { name: 'foo', }, - access_token + access_token, ); - const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const result = await execute({ document: CreateOIDCIntegrationMutation, @@ -221,10 +224,11 @@ describe('create', () => { { name: 'foo', }, - access_token + access_token, ); - const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const result = await execute({ document: CreateOIDCIntegrationMutation, @@ -262,10 +266,11 @@ describe('create', () => { { name: 'foo', }, - access_token + access_token, ); - const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const result = await execute({ document: CreateOIDCIntegrationMutation, @@ -303,10 +308,11 @@ describe('create', () => { { name: 'foo', }, - access_token + access_token, ); - const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const result = await execute({ document: CreateOIDCIntegrationMutation, @@ -344,10 +350,11 @@ describe('create', () => { { name: 'foo', }, - access_token + access_token, ); - const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; let result = await execute({ document: CreateOIDCIntegrationMutation, @@ -429,10 +436,11 @@ describe('delete', () => { { name: 'foo', }, - access_token + access_token, ); - const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const createResult = await execute({ document: CreateOIDCIntegrationMutation, @@ -448,7 +456,8 @@ describe('delete', () => { }); expect(createResult.body.errors).toBeUndefined(); - const oidcIntegrationId = createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id; + const oidcIntegrationId = + createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id; let refetchedOrg = await execute({ document: OrganizationWithOIDCIntegration, @@ -522,10 +531,11 @@ describe('delete', () => { { name: 'foo', }, - access_token + access_token, ); - const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const createResult = await execute({ document: CreateOIDCIntegrationMutation, @@ -541,7 +551,8 @@ describe('delete', () => { }); expect(createResult.body.errors).toBeUndefined(); - const oidcIntegrationId = createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id; + const oidcIntegrationId = + createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id; const { access_token: accessTokenExtra } = await authenticate('extra'); @@ -580,10 +591,11 @@ describe('delete', () => { { name: 'foo', }, - access_token + access_token, ); - const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const createResult = await execute({ document: CreateOIDCIntegrationMutation, @@ -599,7 +611,8 @@ describe('delete', () => { }); expect(createResult.body.errors).toBeUndefined(); - const oidcIntegrationId = createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id; + const oidcIntegrationId = + createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id; const MeQuery = gql(/* GraphQL */ ` query Me { @@ -610,7 +623,10 @@ describe('delete', () => { `); // create new member that belongs to oidc integration - const { access_token: memberAccessToken } = await authenticate('oidc_member', oidcIntegrationId); + const { access_token: memberAccessToken } = await authenticate( + 'oidc_member', + oidcIntegrationId, + ); let meResult = await execute({ document: MeQuery, authToken: memberAccessToken, @@ -694,10 +710,11 @@ describe('update', () => { { name: 'foo', }, - access_token + access_token, ); - const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const createResult = await execute({ document: CreateOIDCIntegrationMutation, @@ -713,7 +730,8 @@ describe('update', () => { }); expect(createResult.body.errors).toBeUndefined(); - const oidcIntegrationId = createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id; + const oidcIntegrationId = + createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id; const updateResult = await execute({ document: UpdateOIDCIntegrationMutation, @@ -749,10 +767,11 @@ describe('update', () => { { name: 'foo', }, - access_token + access_token, ); - const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const createResult = await execute({ document: CreateOIDCIntegrationMutation, @@ -768,7 +787,8 @@ describe('update', () => { }); expect(createResult.body.errors).toBeUndefined(); - const oidcIntegrationId = createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id; + const oidcIntegrationId = + createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id; const { access_token: accessTokenExtra } = await authenticate('extra'); diff --git a/integration-tests/tests/api/organization/crud.spec.ts b/integration-tests/tests/api/organization/crud.spec.ts index feee426e9..85894e66c 100644 --- a/integration-tests/tests/api/organization/crud.spec.ts +++ b/integration-tests/tests/api/organization/crud.spec.ts @@ -7,7 +7,7 @@ test('renaming an organization should result changing its cleanId', async () => { name: 'foo', }, - access_token + access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -16,19 +16,22 @@ test('renaming an organization should result changing its cleanId', async () => organization: org.cleanId, name: 'bar', }, - access_token + access_token, ); expect(renamedOrganizationResult.body.errors).not.toBeDefined(); expect(renamedOrganizationResult.body.data?.updateOrganizationName.error).toBeNull(); expect( - renamedOrganizationResult.body.data?.updateOrganizationName.ok?.updatedOrganizationPayload.organization.name + renamedOrganizationResult.body.data?.updateOrganizationName.ok?.updatedOrganizationPayload + .organization.name, ).toBe('bar'); expect( - renamedOrganizationResult.body.data?.updateOrganizationName.ok?.updatedOrganizationPayload.organization.cleanId + renamedOrganizationResult.body.data?.updateOrganizationName.ok?.updatedOrganizationPayload + .organization.cleanId, ).toBe('bar'); expect( - renamedOrganizationResult.body.data?.updateOrganizationName.ok?.updatedOrganizationPayload.selector.organization + renamedOrganizationResult.body.data?.updateOrganizationName.ok?.updatedOrganizationPayload + .selector.organization, ).toBe('bar'); }); diff --git a/integration-tests/tests/api/organization/get-started.spec.ts b/integration-tests/tests/api/organization/get-started.spec.ts index 082527124..8197cba7e 100644 --- a/integration-tests/tests/api/organization/get-started.spec.ts +++ b/integration-tests/tests/api/organization/get-started.spec.ts @@ -12,7 +12,12 @@ import { } from '../../../testkit/flow'; import { authenticate } from '../../../testkit/auth'; import { collect } from '../../../testkit/usage'; -import { TargetAccessScope, ProjectType, ProjectAccessScope, OrganizationAccessScope } from '@app/gql/graphql'; +import { + TargetAccessScope, + ProjectType, + ProjectAccessScope, + OrganizationAccessScope, +} from '@app/gql/graphql'; async function getSteps({ organization, token }: { organization: string; token: string }) { const result = await getOrganization(organization, token); @@ -28,7 +33,7 @@ test('freshly created organization has Get Started progress at 0%', async () => { name: 'foo', }, - access_token + access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -51,7 +56,7 @@ test('completing each step should result in updated Get Started progress', async { name: 'foo', }, - access_token + access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -63,7 +68,7 @@ test('completing each step should result in updated Get Started progress', async name: 'foo', type: ProjectType.Single, }, - access_token + access_token, ); let steps = await getSteps({ @@ -80,7 +85,9 @@ test('completing each step should result in updated Get Started progress', async expect(projectResult.body.errors).not.toBeDefined(); - const target = projectResult.body.data?.createProject.ok?.createdTargets.find(t => t.name === 'production'); + const target = projectResult.body.data?.createProject.ok?.createdTargets.find( + t => t.name === 'production', + ); const project = projectResult.body.data?.createProject.ok?.createdProject; if (!target || !project) { @@ -102,7 +109,7 @@ test('completing each step should result in updated Get Started progress', async TargetAccessScope.Settings, ], }, - access_token + access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -117,7 +124,7 @@ test('completing each step should result in updated Get Started progress', async commit: 'test', sdl: 'type Query { foo: String }', }, - token + token, ); steps = await getSteps({ @@ -138,7 +145,7 @@ test('completing each step should result in updated Get Started progress', async { sdl: 'type Query { foo: String bar: String }', }, - token + token, ); steps = await getSteps({ @@ -160,7 +167,7 @@ test('completing each step should result in updated Get Started progress', async email: 'some@email.com', organization: org.cleanId, }, - access_token + access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -224,7 +231,7 @@ test('completing each step should result in updated Get Started progress', async }, { token, - } + }, ); steps = await getSteps({ diff --git a/integration-tests/tests/api/organization/members.spec.ts b/integration-tests/tests/api/organization/members.spec.ts index d78f40b54..44c879f82 100644 --- a/integration-tests/tests/api/organization/members.spec.ts +++ b/integration-tests/tests/api/organization/members.spec.ts @@ -1,5 +1,10 @@ import { OrganizationAccessScope, ProjectAccessScope, TargetAccessScope } from '@app/gql/graphql'; -import { createOrganization, inviteToOrganization, joinOrganization, updateMemberAccess } from '../../../testkit/flow'; +import { + createOrganization, + inviteToOrganization, + joinOrganization, + updateMemberAccess, +} from '../../../testkit/flow'; import { authenticate } from '../../../testkit/auth'; import { history } from '../../../testkit/emails'; @@ -9,12 +14,13 @@ test('owner of an organization should have all scopes', async () => { { name: 'foo', }, - access_token + access_token, ); expect(result.body.errors).not.toBeDefined(); - const owner = result.body.data!.createOrganization.ok!.createdOrganizationPayload.organization.owner; + const owner = + result.body.data!.createOrganization.ok!.createdOrganizationPayload.organization.owner; Object.values(OrganizationAccessScope).forEach(scope => { expect(owner.organizationAccessScopes).toContain(scope); @@ -35,7 +41,7 @@ test('regular member of an organization should have basic scopes', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -46,7 +52,7 @@ test('regular member of an organization should have basic scopes', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -89,7 +95,7 @@ test('cannot grant an access scope to another user if user has no access to that { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -100,7 +106,7 @@ test('cannot grant an access scope to another user if user has no access to that email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -125,7 +131,7 @@ test('cannot grant an access scope to another user if user has no access to that targetScopes: [], user: member.id, }, - owner_access_token + owner_access_token, ); // Grant access to target:tokens:write @@ -137,7 +143,7 @@ test('cannot grant an access scope to another user if user has no access to that targetScopes: [TargetAccessScope.TokensWrite], user: member.id, }, - member_access_token + member_access_token, ); expect(accessResult.body.errors).toHaveLength(1); @@ -150,7 +156,7 @@ test('granting no scopes is equal to setting read-only for org, project and targ { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -161,7 +167,7 @@ test('granting no scopes is equal to setting read-only for org, project and targ email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -186,14 +192,15 @@ test('granting no scopes is equal to setting read-only for org, project and targ targetScopes: [], user: member.id, }, - owner_access_token + owner_access_token, ); expect(accessResult.body.errors).not.toBeDefined(); - const memberWithAccess = accessResult.body.data?.updateOrganizationMemberAccess.organization.members.nodes.find( - m => m.id === member.id - ); + const memberWithAccess = + accessResult.body.data?.updateOrganizationMemberAccess.organization.members.nodes.find( + m => m.id === member.id, + ); expect(memberWithAccess?.organizationAccessScopes).toHaveLength(1); expect(memberWithAccess?.organizationAccessScopes).toContainEqual(OrganizationAccessScope.Read); expect(memberWithAccess?.projectAccessScopes).toHaveLength(1); @@ -208,9 +215,10 @@ test('email invitation', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); - const org = createOrgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + createOrgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; // Invite const email = 'invited@invited.com'; @@ -219,7 +227,7 @@ test('email invitation', async () => { email, organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -235,9 +243,10 @@ test('cannot join organization twice using the same invitation code', async () = { name: 'foo', }, - owner_access_token + owner_access_token, ); - const org = createOrgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; + const org = + createOrgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; // Invite const invitationResult = await inviteToOrganization( @@ -245,7 +254,7 @@ test('cannot join organization twice using the same invitation code', async () = email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -264,5 +273,7 @@ test('cannot join organization twice using the same invitation code', async () = const { access_token: another_access_token } = await authenticate('admin'); const secondJoinResult = await joinOrganization(inviteCode!, another_access_token); - expect(secondJoinResult.body.data?.joinOrganization.__typename).toBe('OrganizationInvitationError'); + expect(secondJoinResult.body.data?.joinOrganization.__typename).toBe( + 'OrganizationInvitationError', + ); }); diff --git a/integration-tests/tests/api/persisted-operations/publish.spec.ts b/integration-tests/tests/api/persisted-operations/publish.spec.ts index 06419f7a9..ee9b8b4e3 100644 --- a/integration-tests/tests/api/persisted-operations/publish.spec.ts +++ b/integration-tests/tests/api/persisted-operations/publish.spec.ts @@ -1,5 +1,10 @@ import { ProjectType, ProjectAccessScope } from '@app/gql/graphql'; -import { createOrganization, publishPersistedOperations, createProject, createToken } from '../../../testkit/flow'; +import { + createOrganization, + publishPersistedOperations, + createProject, + createToken, +} from '../../../testkit/flow'; import { authenticate } from '../../../testkit/auth'; test('can publish persisted operations only with project:operations-store:write', async () => { @@ -8,7 +13,7 @@ test('can publish persisted operations only with project:operations-store:write' { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -18,7 +23,7 @@ test('can publish persisted operations only with project:operations-store:write' type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -35,7 +40,7 @@ test('can publish persisted operations only with project:operations-store:write' projectScopes: [], targetScopes: [], }, - owner_access_token + owner_access_token, ); expect(noAccessTokenResult.body.errors).not.toBeDefined(); @@ -50,7 +55,7 @@ test('can publish persisted operations only with project:operations-store:write' projectScopes: [ProjectAccessScope.OperationsStoreRead], targetScopes: [], }, - owner_access_token + owner_access_token, ); expect(readTokenResult.body.errors).not.toBeDefined(); @@ -62,10 +67,13 @@ test('can publish persisted operations only with project:operations-store:write' project: project.cleanId, target: target.cleanId, organizationScopes: [], - projectScopes: [ProjectAccessScope.OperationsStoreRead, ProjectAccessScope.OperationsStoreWrite], + projectScopes: [ + ProjectAccessScope.OperationsStoreRead, + ProjectAccessScope.OperationsStoreWrite, + ], targetScopes: [], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); @@ -113,7 +121,7 @@ test('should skip on already persisted operations', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -123,7 +131,7 @@ test('should skip on already persisted operations', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -137,10 +145,13 @@ test('should skip on already persisted operations', async () => { project: project.cleanId, target: target.cleanId, organizationScopes: [], - projectScopes: [ProjectAccessScope.OperationsStoreRead, ProjectAccessScope.OperationsStoreWrite], + projectScopes: [ + ProjectAccessScope.OperationsStoreRead, + ProjectAccessScope.OperationsStoreWrite, + ], targetScopes: [], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); @@ -181,8 +192,12 @@ test('should skip on already persisted operations', async () => { expect(persisted.summary.unchanged).toEqual(1); expect(persisted.operations).toHaveLength(2); - const meOperation = persisted.operations.find(op => op.operationHash === operations[0].operationHash); - const userOperation = persisted.operations.find(op => op.operationHash === operations[1].operationHash); + const meOperation = persisted.operations.find( + op => op.operationHash === operations[0].operationHash, + ); + const userOperation = persisted.operations.find( + op => op.operationHash === operations[1].operationHash, + ); expect(meOperation?.operationHash).toEqual(operations[0].operationHash); expect(userOperation?.operationHash).toEqual(operations[1].operationHash); diff --git a/integration-tests/tests/api/project/crud.spec.ts b/integration-tests/tests/api/project/crud.spec.ts index 50615a7ea..a115cb6eb 100644 --- a/integration-tests/tests/api/project/crud.spec.ts +++ b/integration-tests/tests/api/project/crud.spec.ts @@ -8,7 +8,7 @@ test('creating a project should result in creating the development, staging and { name: 'foo', }, - access_token + access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -18,7 +18,7 @@ test('creating a project should result in creating the development, staging and type: ProjectType.Single, name: 'foo', }, - access_token + access_token, ); const targets = projectResult.body.data!.createProject.ok!.createdTargets; @@ -28,19 +28,19 @@ test('creating a project should result in creating the development, staging and expect.objectContaining({ cleanId: 'development', name: 'development', - }) + }), ); expect(targets).toContainEqual( expect.objectContaining({ cleanId: 'staging', name: 'staging', - }) + }), ); expect(targets).toContainEqual( expect.objectContaining({ cleanId: 'production', name: 'production', - }) + }), ); }); @@ -50,7 +50,7 @@ test('renaming a project should result changing its cleanId', async () => { { name: 'foo', }, - access_token + access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -60,7 +60,7 @@ test('renaming a project should result changing its cleanId', async () => { type: ProjectType.Single, name: 'foo', }, - access_token + access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -71,7 +71,7 @@ test('renaming a project should result changing its cleanId', async () => { project: project.cleanId, name: 'bar', }, - access_token + access_token, ); expect(renamedProjectResult.body.errors).not.toBeDefined(); diff --git a/integration-tests/tests/api/rate-limit/emails.spec.ts b/integration-tests/tests/api/rate-limit/emails.spec.ts index 2ecb73b4e..049982572 100644 --- a/integration-tests/tests/api/rate-limit/emails.spec.ts +++ b/integration-tests/tests/api/rate-limit/emails.spec.ts @@ -1,5 +1,16 @@ -import { TargetAccessScope, ProjectType, ProjectAccessScope, OrganizationAccessScope } from '@app/gql/graphql'; -import { createOrganization, createProject, createToken, updateOrgRateLimit, waitFor } from '../../../testkit/flow'; +import { + TargetAccessScope, + ProjectType, + ProjectAccessScope, + OrganizationAccessScope, +} from '@app/gql/graphql'; +import { + createOrganization, + createProject, + createToken, + updateOrgRateLimit, + waitFor, +} from '../../../testkit/flow'; import * as emails from '../../../testkit/emails'; import { authenticate, userEmails } from '../../../testkit/auth'; import { collect } from '../../../testkit/usage'; @@ -24,7 +35,7 @@ test('rate limit approaching and reached for organization', async () => { { name: generateUnique(), }, - access_token + access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -34,11 +45,13 @@ test('rate limit approaching and reached for organization', async () => { type: ProjectType.Single, name: 'bar', }, - access_token + access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; - const target = projectResult.body.data!.createProject.ok!.createdTargets.find(t => t.name === 'production')!; + const target = projectResult.body.data!.createProject.ok!.createdTargets.find( + t => t.name === 'production', + )!; await updateOrgRateLimit( { @@ -47,7 +60,7 @@ test('rate limit approaching and reached for organization', async () => { { operations: 11, }, - access_token + access_token, ); const tokenResult = await createToken( @@ -58,9 +71,13 @@ test('rate limit approaching and reached for organization', async () => { target: target.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - access_token + access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); diff --git a/integration-tests/tests/api/schema/check.spec.ts b/integration-tests/tests/api/schema/check.spec.ts index 987bf536d..256b82cf2 100644 --- a/integration-tests/tests/api/schema/check.spec.ts +++ b/integration-tests/tests/api/schema/check.spec.ts @@ -16,7 +16,7 @@ test('can check a schema with target:registry:read access', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -25,7 +25,7 @@ test('can check a schema with target:registry:read access', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -41,7 +41,7 @@ test('can check a schema with target:registry:read access', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -58,7 +58,7 @@ test('can check a schema with target:registry:read access', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -70,7 +70,7 @@ test('can check a schema with target:registry:read access', async () => { commit: 'abc123', sdl: `type Query { ping: String }`, }, - writeToken + writeToken, ); // Schema publish should be successful @@ -88,7 +88,7 @@ test('can check a schema with target:registry:read access', async () => { projectScopes: [], targetScopes: [], }, - owner_access_token + owner_access_token, ); expect(noAccessTokenResult.body.errors).not.toBeDefined(); @@ -103,7 +103,7 @@ test('can check a schema with target:registry:read access', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead], }, - owner_access_token + owner_access_token, ); expect(readTokenResult.body.errors).not.toBeDefined(); @@ -115,7 +115,7 @@ test('can check a schema with target:registry:read access', async () => { { sdl: `type Query { ping: String foo: String }`, }, - noAccessToken + noAccessToken, ); expect(checkResult.body.errors).toHaveLength(1); expect(checkResult.body.errors![0].message).toMatch('target:registry:read'); @@ -125,7 +125,7 @@ test('can check a schema with target:registry:read access', async () => { { sdl: `type Query { ping: String foo: String }`, }, - readToken + readToken, ); expect(checkResult.body.errors).not.toBeDefined(); expect(checkResult.body.data!.schemaCheck.__typename).toBe('SchemaCheckSuccess'); @@ -137,7 +137,7 @@ test('should match indentation of previous description', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -146,7 +146,7 @@ test('should match indentation of previous description', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -162,7 +162,7 @@ test('should match indentation of previous description', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -179,7 +179,7 @@ test('should match indentation of previous description', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -198,7 +198,7 @@ test('should match indentation of previous description', async () => { } `, }, - writeToken + writeToken, ); // Schema publish should be successful @@ -216,7 +216,7 @@ test('should match indentation of previous description', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead], }, - owner_access_token + owner_access_token, ); expect(readTokenResult.body.errors).not.toBeDefined(); @@ -236,7 +236,7 @@ test('should match indentation of previous description', async () => { } `, }, - readToken + readToken, ); expect(checkResult.body.errors).not.toBeDefined(); diff --git a/integration-tests/tests/api/schema/external-composition.spec.ts b/integration-tests/tests/api/schema/external-composition.spec.ts index d107e1c4b..ed35e397f 100644 --- a/integration-tests/tests/api/schema/external-composition.spec.ts +++ b/integration-tests/tests/api/schema/external-composition.spec.ts @@ -15,7 +15,7 @@ test('call an external service to compose and validate services', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -25,7 +25,7 @@ test('call an external service to compose and validate services', async () => { type: ProjectType.Federation, name: 'bar', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -42,7 +42,7 @@ test('call an external service to compose and validate services', async () => { projectScopes: [ProjectAccessScope.Settings, ProjectAccessScope.Read], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -56,7 +56,7 @@ test('call an external service to compose and validate services', async () => { sdl: `type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`, service: usersServiceName, }, - writeToken + writeToken, ); // Schema publish should be successful @@ -70,15 +70,16 @@ test('call an external service to compose and validate services', async () => { const externalCompositionResult = await enableExternalSchemaComposition( { endpoint: `http://${dockerAddress}/compose`, + // eslint-disable-next-line no-process-env secret: process.env.EXTERNAL_COMPOSITION_SECRET!, project: project.cleanId, organization: org.cleanId, }, - writeToken + writeToken, ); expect(externalCompositionResult.body.errors).not.toBeDefined(); expect(externalCompositionResult.body.data!.enableExternalSchemaComposition.ok?.endpoint).toBe( - `http://${dockerAddress}/compose` + `http://${dockerAddress}/compose`, ); const productsServiceName = Math.random().toString(16).substring(2); @@ -90,7 +91,7 @@ test('call an external service to compose and validate services', async () => { sdl: `type Query { products: [Product] } type Product @key(fields: "id") { id: ID! name: String }`, service: productsServiceName, }, - writeToken + writeToken, ); // Schema publish should be successful diff --git a/integration-tests/tests/api/schema/publish.spec.ts b/integration-tests/tests/api/schema/publish.spec.ts index 735d8ba75..28e8d7857 100644 --- a/integration-tests/tests/api/schema/publish.spec.ts +++ b/integration-tests/tests/api/schema/publish.spec.ts @@ -25,7 +25,7 @@ test('cannot publish a schema without target:registry:write access', async () => { name: 'foo', }, - owner_access_token + owner_access_token, ); // Join @@ -37,7 +37,7 @@ test('cannot publish a schema without target:registry:write access', async () => email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -51,7 +51,7 @@ test('cannot publish a schema without target:registry:write access', async () => type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -67,7 +67,7 @@ test('cannot publish a schema without target:registry:write access', async () => projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -78,7 +78,7 @@ test('cannot publish a schema without target:registry:write access', async () => commit: 'abc123', sdl: `type Query { ping: String }`, }, - token + token, ); expect(result.body.errors).toHaveLength(1); @@ -91,7 +91,7 @@ test('can publish a schema with target:registry:write access', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); // Join @@ -103,7 +103,7 @@ test('can publish a schema with target:registry:write access', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -117,7 +117,7 @@ test('can publish a schema with target:registry:write access', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -133,7 +133,7 @@ test('can publish a schema with target:registry:write access', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -146,7 +146,7 @@ test('can publish a schema with target:registry:write access', async () => { commit: 'abc123', sdl: `type Query { ping: String }`, }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -158,7 +158,7 @@ test('can publish a schema with target:registry:write access', async () => { commit: 'abc123', sdl: `type Query { ping: String pong: String }`, }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -171,7 +171,7 @@ test('can publish a schema with target:registry:write access', async () => { target: target.cleanId, }, 3, - token + token, ); expect(versionsResult.body.errors).not.toBeDefined(); @@ -184,7 +184,7 @@ test('base schema should not affect the output schema persisted in db', async () { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -194,7 +194,7 @@ test('base schema should not affect the output schema persisted in db', async () type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -211,7 +211,7 @@ test('base schema should not affect the output schema persisted in db', async () projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -223,7 +223,7 @@ test('base schema should not affect the output schema persisted in db', async () commit: 'abc123', sdl: `type Query { ping: String }`, }, - writeToken + writeToken, ); // Schema publish should be successful @@ -239,7 +239,7 @@ test('base schema should not affect the output schema persisted in db', async () project: project.cleanId, target: target.cleanId, }, - writeToken + writeToken, ); expect(updateBaseResult.body.errors).not.toBeDefined(); @@ -250,7 +250,7 @@ test('base schema should not affect the output schema persisted in db', async () author: 'Kamil', commit: 'abc234', }, - writeToken + writeToken, ); expect(publishResult.body.errors).not.toBeDefined(); expect(publishResult.body.data!.schemaPublish.__typename).toBe('SchemaPublishSuccess'); @@ -262,7 +262,7 @@ test('base schema should not affect the output schema persisted in db', async () target: target.cleanId, }, 5, - writeToken + writeToken, ); expect(versionsResult.body.errors).not.toBeDefined(); @@ -273,10 +273,12 @@ test('base schema should not affect the output schema persisted in db', async () expect(latestResult.body.data!.latestVersion.schemas.total).toBe(1); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].commit).toBe('abc234'); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).toMatch( - 'type Query { ping: String @auth pong: String }' + 'type Query { ping: String @auth pong: String }', ); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).not.toMatch('directive'); - expect(latestResult.body.data!.latestVersion.baseSchema).toMatch('directive @auth on OBJECT | FIELD_DEFINITION'); + expect(latestResult.body.data!.latestVersion.baseSchema).toMatch( + 'directive @auth on OBJECT | FIELD_DEFINITION', + ); }); test('directives should not be removed (federation)', async () => { @@ -285,7 +287,7 @@ test('directives should not be removed (federation)', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -295,7 +297,7 @@ test('directives should not be removed (federation)', async () => { type: ProjectType.Federation, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -312,7 +314,7 @@ test('directives should not be removed (federation)', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -326,7 +328,7 @@ test('directives should not be removed (federation)', async () => { service: 'users', sdl: `type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`, }, - writeToken + writeToken, ); // Schema publish should be successful @@ -340,7 +342,7 @@ test('directives should not be removed (federation)', async () => { target: target.cleanId, }, 5, - writeToken + writeToken, ); expect(versionsResult.body.errors).not.toBeDefined(); @@ -351,7 +353,7 @@ test('directives should not be removed (federation)', async () => { expect(latestResult.body.data!.latestVersion.schemas.total).toBe(1); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].commit).toBe('abc123'); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).toMatch( - `type Query { me: User } type User @key(fields: "id") { id: ID! name: String }` + `type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`, ); }); @@ -361,7 +363,7 @@ test('should allow to update the URL of a Federated service without changing the { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -371,7 +373,7 @@ test('should allow to update the URL of a Federated service without changing the type: ProjectType.Federation, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -388,7 +390,7 @@ test('should allow to update the URL of a Federated service without changing the projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -414,7 +416,7 @@ test('should allow to update the URL of a Federated service without changing the target: target.cleanId, }, 5, - writeToken + writeToken, ); expect(versionsResult.body.errors).not.toBeDefined(); @@ -427,22 +429,24 @@ test('should allow to update the URL of a Federated service without changing the url: `http://localhost:3000/test/graphql`, commit: 'abc1234', }, - writeToken + writeToken, ); expect(updateResult.body.errors).not.toBeDefined(); expect(updateResult.body.data!.schemaPublish.__typename).toBe('SchemaPublishSuccess'); expect((updateResult.body.data!.schemaPublish as any).message).toBe( - 'Updated: New service url: http://localhost:3000/test/graphql (previously: https://api.com/users)' + 'Updated: New service url: http://localhost:3000/test/graphql (previously: https://api.com/users)', ); const latestResult = await fetchLatestSchema(writeToken); expect(latestResult.body.errors).not.toBeDefined(); expect(latestResult.body.data!.latestVersion.schemas.total).toBe(1); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].commit).toBe('abc1234'); - expect(latestResult.body.data!.latestVersion.schemas.nodes[0].url).toBe('http://localhost:3000/test/graphql'); + expect(latestResult.body.data!.latestVersion.schemas.nodes[0].url).toBe( + 'http://localhost:3000/test/graphql', + ); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).toMatch( - `type Query { me: User } type User @key(fields: "id") { id: ID! name: String }` + `type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`, ); }); @@ -452,7 +456,7 @@ test('should allow to update the URL of a Federated service while also changing { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -462,7 +466,7 @@ test('should allow to update the URL of a Federated service while also changing type: ProjectType.Federation, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -479,7 +483,7 @@ test('should allow to update the URL of a Federated service while also changing projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -505,7 +509,7 @@ test('should allow to update the URL of a Federated service while also changing target: target.cleanId, }, 5, - writeToken + writeToken, ); expect(versionsResult.body.errors).not.toBeDefined(); @@ -516,7 +520,7 @@ test('should allow to update the URL of a Federated service while also changing expect(latestResult.body.data!.latestVersion.schemas.total).toBe(1); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].commit).toBe('abc123'); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).toMatch( - `type Query { me: User } type User @key(fields: "id") { id: ID! name: String }` + `type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`, ); // try to update the schema again, with force and url set @@ -528,7 +532,7 @@ test('should allow to update the URL of a Federated service while also changing // here, we also add something minor to the schema, just to trigger the publish flow and not just the URL update flow sdl: `type Query { me: User } type User @key(fields: "id") { id: ID! name: String age: Int }`, }, - writeToken + writeToken, ); expect(updateResult.body.errors).not.toBeDefined(); @@ -541,7 +545,7 @@ test('directives should not be removed (stitching)', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -551,7 +555,7 @@ test('directives should not be removed (stitching)', async () => { type: ProjectType.Stitching, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -568,7 +572,7 @@ test('directives should not be removed (stitching)', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -582,7 +586,7 @@ test('directives should not be removed (stitching)', async () => { service: 'test', url: 'https://api.com/users', }, - writeToken + writeToken, ); // Schema publish should be successful @@ -596,7 +600,7 @@ test('directives should not be removed (stitching)', async () => { target: target.cleanId, }, 5, - writeToken + writeToken, ); expect(versionsResult.body.errors).not.toBeDefined(); @@ -607,7 +611,7 @@ test('directives should not be removed (stitching)', async () => { expect(latestResult.body.data!.latestVersion.schemas.total).toBe(1); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].commit).toBe('abc123'); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).toMatch( - `type Query { me: User } type User @key(selectionSet: "{ id }") { id: ID! name: String }` + `type Query { me: User } type User @key(selectionSet: "{ id }") { id: ID! name: String }`, ); }); @@ -617,7 +621,7 @@ test('directives should not be removed (single)', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -627,7 +631,7 @@ test('directives should not be removed (single)', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -644,7 +648,7 @@ test('directives should not be removed (single)', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -658,7 +662,7 @@ test('directives should not be removed (single)', async () => { service: 'test', url: 'https://api.com/users', }, - writeToken + writeToken, ); // Schema publish should be successful @@ -672,7 +676,7 @@ test('directives should not be removed (single)', async () => { target: target.cleanId, }, 5, - writeToken + writeToken, ); expect(versionsResult.body.errors).not.toBeDefined(); @@ -683,7 +687,7 @@ test('directives should not be removed (single)', async () => { expect(latestResult.body.data!.latestVersion.schemas.total).toBe(1); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].commit).toBe('abc123'); expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).toMatch( - `directive @auth on FIELD_DEFINITION type Query { me: User @auth } type User { id: ID! name: String }` + `directive @auth on FIELD_DEFINITION type Query { me: User @auth } type User { id: ID! name: String }`, ); }); @@ -693,7 +697,7 @@ test('share publication of schema using redis', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -703,7 +707,7 @@ test('share publication of schema using redis', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -720,7 +724,7 @@ test('share publication of schema using redis', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -732,7 +736,7 @@ test('share publication of schema using redis', async () => { commit: 'abc123', sdl: `type Query { ping: String }`, }, - writeToken + writeToken, ); // Schema publish should be successful @@ -746,7 +750,7 @@ test('share publication of schema using redis', async () => { author: 'Kamil', commit: 'abc234', }, - writeToken + writeToken, ), publishSchema( { @@ -754,7 +758,7 @@ test('share publication of schema using redis', async () => { author: 'Kamil', commit: 'abc234', }, - writeToken + writeToken, ), ]); expect(publishResult1.body.errors).not.toBeDefined(); @@ -769,7 +773,7 @@ test("Two targets with the same commit id shouldn't return an error", async () = { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const projectResult = await createProject( @@ -778,7 +782,7 @@ test("Two targets with the same commit id shouldn't return an error", async () = type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; const target = projectResult.body.data!.createProject.ok!.createdTargets[0]; @@ -792,7 +796,7 @@ test("Two targets with the same commit id shouldn't return an error", async () = projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -802,7 +806,7 @@ test("Two targets with the same commit id shouldn't return an error", async () = commit: 'abc123', sdl: `type Query { ping: String }`, }, - writeToken + writeToken, ); const createTargetResult = await createTarget( { @@ -810,7 +814,7 @@ test("Two targets with the same commit id shouldn't return an error", async () = project: project.cleanId, name: 'target2', }, - owner_access_token + owner_access_token, ); const target2 = createTargetResult.body!.data!.createTarget.ok!.createdTarget; const writeTokenResult2 = await createToken( @@ -823,7 +827,7 @@ test("Two targets with the same commit id shouldn't return an error", async () = projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); const writeToken2 = writeTokenResult2.body.data!.createToken.ok!.secret; const publishResult2 = await publishSchema( @@ -832,7 +836,7 @@ test("Two targets with the same commit id shouldn't return an error", async () = commit: 'abc123', sdl: `type Query { ping: String }`, }, - writeToken2 + writeToken2, ); // Schema publish should be successful expect(publishResult.body.errors).not.toBeDefined(); @@ -847,7 +851,7 @@ test('marking versions as valid', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); // Join @@ -859,7 +863,7 @@ test('marking versions as valid', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -873,7 +877,7 @@ test('marking versions as valid', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -889,7 +893,7 @@ test('marking versions as valid', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -903,7 +907,7 @@ test('marking versions as valid', async () => { commit: 'c0', sdl: `type Query { ping: String }`, }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -918,7 +922,7 @@ test('marking versions as valid', async () => { force: true, metadata: JSON.stringify({ c1: true }), }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -932,7 +936,7 @@ test('marking versions as valid', async () => { force: true, metadata: JSON.stringify({ c2: true }), }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -944,7 +948,7 @@ test('marking versions as valid', async () => { target: target.cleanId, }, 3, - token + token, ); expect(versionsResult.body.errors).not.toBeDefined(); @@ -954,7 +958,9 @@ test('marking versions as valid', async () => { let latestValidSchemaResult = await fetchLatestValidSchema(token); expect(latestValidSchemaResult.body.errors).not.toBeDefined(); expect(latestValidSchemaResult.body.data!.latestValidVersion.schemas.total).toEqual(1); - expect(latestValidSchemaResult.body.data!.latestValidVersion.schemas.nodes[0].commit).toEqual('c0'); + expect(latestValidSchemaResult.body.data!.latestValidVersion.schemas.nodes[0].commit).toEqual( + 'c0', + ); const versionId = (commit: string) => versionsResult.body.data!.schemaVersions.nodes.find(node => node.commit.commit === commit)!.id; @@ -968,11 +974,13 @@ test('marking versions as valid', async () => { valid: true, version: versionId('c2'), }, - token + token, ); expect(versionStatusUpdateResult.body.errors).not.toBeDefined(); - expect(versionStatusUpdateResult.body.data!.updateSchemaVersionStatus.id).toEqual(versionId('c2')); + expect(versionStatusUpdateResult.body.data!.updateSchemaVersionStatus.id).toEqual( + versionId('c2'), + ); latestValidSchemaResult = await fetchLatestValidSchema(token); expect(latestValidSchemaResult.body.errors).not.toBeDefined(); @@ -987,7 +995,7 @@ test('marking versions as valid', async () => { valid: true, version: versionId('c1'), }, - token + token, ); expect(versionStatusUpdateResult.body.errors).not.toBeDefined(); @@ -1002,7 +1010,7 @@ test('marking only the most recent version as valid result in an update of CDN', { name: 'foo', }, - owner_access_token + owner_access_token, ); // Join @@ -1014,7 +1022,7 @@ test('marking only the most recent version as valid result in an update of CDN', email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -1028,7 +1036,7 @@ test('marking only the most recent version as valid result in an update of CDN', type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -1044,7 +1052,7 @@ test('marking only the most recent version as valid result in an update of CDN', projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -1059,7 +1067,7 @@ test('marking only the most recent version as valid result in an update of CDN', sdl: `type Query { ping: String }`, metadata: JSON.stringify({ c0: 1 }), }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -1074,7 +1082,7 @@ test('marking only the most recent version as valid result in an update of CDN', force: true, metadata: JSON.stringify({ c1: 1 }), }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -1088,7 +1096,7 @@ test('marking only the most recent version as valid result in an update of CDN', force: true, metadata: JSON.stringify({ c2: 1 }), }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -1121,7 +1129,7 @@ test('marking only the most recent version as valid result in an update of CDN', valid: true, version: versionId('c2'), }, - token + token, ); cdnResult = await fetchSchemaFromCDN(targetSelector, token); @@ -1141,7 +1149,7 @@ test('marking only the most recent version as valid result in an update of CDN', valid: true, version: versionId('c1'), }, - token + token, ); // console.log(JSON.stringify(updateSchemaVersionStatusResult)); @@ -1159,7 +1167,7 @@ test('CDN data can not be fetched with an invalid access token', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); // Join @@ -1171,7 +1179,7 @@ test('CDN data can not be fetched with an invalid access token', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -1185,7 +1193,7 @@ test('CDN data can not be fetched with an invalid access token', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -1201,7 +1209,7 @@ test('CDN data can not be fetched with an invalid access token', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -1216,7 +1224,7 @@ test('CDN data can not be fetched with an invalid access token', async () => { sdl: `type Query { ping: String }`, metadata: JSON.stringify({ c0: 1 }), }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -1253,7 +1261,7 @@ test('CDN data can be fetched with an valid access token', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); // Join @@ -1265,7 +1273,7 @@ test('CDN data can be fetched with an valid access token', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -1279,7 +1287,7 @@ test('CDN data can be fetched with an valid access token', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -1295,7 +1303,7 @@ test('CDN data can be fetched with an valid access token', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -1310,7 +1318,7 @@ test('CDN data can be fetched with an valid access token', async () => { sdl: `type Query { ping: String }`, metadata: JSON.stringify({ c0: 1 }), }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -1347,7 +1355,7 @@ test('linkToWebsite should be available when publishing initial schema', async ( { name: 'bar', }, - owner_access_token + owner_access_token, ); // Join @@ -1359,7 +1367,7 @@ test('linkToWebsite should be available when publishing initial schema', async ( email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -1373,7 +1381,7 @@ test('linkToWebsite should be available when publishing initial schema', async ( type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -1390,7 +1398,7 @@ test('linkToWebsite should be available when publishing initial schema', async ( projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -1403,7 +1411,7 @@ test('linkToWebsite should be available when publishing initial schema', async ( commit: 'abc123', sdl: `type Query { ping: String }`, }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -1423,7 +1431,7 @@ test('linkToWebsite should be available when publishing non-initial schema', asy { name: 'bar', }, - owner_access_token + owner_access_token, ); // Join @@ -1435,7 +1443,7 @@ test('linkToWebsite should be available when publishing non-initial schema', asy email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -1449,7 +1457,7 @@ test('linkToWebsite should be available when publishing non-initial schema', asy type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -1466,7 +1474,7 @@ test('linkToWebsite should be available when publishing non-initial schema', asy projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -1479,7 +1487,7 @@ test('linkToWebsite should be available when publishing non-initial schema', asy commit: 'abc123', sdl: `type Query { ping: String }`, }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -1491,7 +1499,7 @@ test('linkToWebsite should be available when publishing non-initial schema', asy commit: 'abc123', sdl: `type Query { ping: String pong: String }`, }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -1513,7 +1521,7 @@ test('cannot do API request with invalid access token', async () => { sdl: 'type Query { smokeBangBang: String }', author: 'Kamil', }, - 'foobars' + 'foobars', ); expect(orgResult).toEqual({ body: { @@ -1541,7 +1549,7 @@ test('publish new schema when a field is moved from one service to another (stit { name: 'foo', }, - access_token + access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -1552,7 +1560,7 @@ test('publish new schema when a field is moved from one service to another (stit type: ProjectType.Stitching, name: 'foo', }, - access_token + access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -1568,7 +1576,7 @@ test('publish new schema when a field is moved from one service to another (stit projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - access_token + access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -1587,7 +1595,7 @@ test('publish new schema when a field is moved from one service to another (stit `, service: 'cats', }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -1606,7 +1614,7 @@ test('publish new schema when a field is moved from one service to another (stit `, service: 'dogs', }, - token + token, ); expect(result.body.errors).not.toBeDefined(); @@ -1625,7 +1633,7 @@ test('publish new schema when a field is moved from one service to another (stit `, service: 'cats', }, - token + token, ); // We expect to have a new version, even tough the schema (merged) is the same @@ -1640,7 +1648,7 @@ test('publish new schema when a field is moved from one service to another (stit target: target.cleanId, }, 3, - token + token, ); expect(versionsResult.body.errors).not.toBeDefined(); @@ -1653,7 +1661,7 @@ test('(experimental_acceptBreakingChanges) accept breaking changes if schema is { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -1663,7 +1671,7 @@ test('(experimental_acceptBreakingChanges) accept breaking changes if schema is type: ProjectType.Federation, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -1680,7 +1688,7 @@ test('(experimental_acceptBreakingChanges) accept breaking changes if schema is projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -1710,11 +1718,15 @@ test('(experimental_acceptBreakingChanges) accept breaking changes if schema is // We also removed the `name` field (breaking) sdl: `type Query { me: User } type User @key(fields: "id") { id: ID! }`, }, - writeToken + writeToken, ); const latestValid = await fetchLatestValidSchema(writeToken); - expect(composableButBreakingResult.body.data!.schemaPublish.__typename).toBe('SchemaPublishSuccess'); - expect(latestValid.body.data?.latestValidVersion.schemas.nodes[0].commit).toBe('composable-but-breaking'); + expect(composableButBreakingResult.body.data!.schemaPublish.__typename).toBe( + 'SchemaPublishSuccess', + ); + expect(latestValid.body.data?.latestValidVersion.schemas.nodes[0].commit).toBe( + 'composable-but-breaking', + ); }); diff --git a/integration-tests/tests/api/schema/sync.spec.ts b/integration-tests/tests/api/schema/sync.spec.ts index 6eab1fa21..0834de159 100644 --- a/integration-tests/tests/api/schema/sync.spec.ts +++ b/integration-tests/tests/api/schema/sync.spec.ts @@ -15,7 +15,7 @@ test('marking only the most recent version as valid result in an update of CDN', { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -26,7 +26,7 @@ test('marking only the most recent version as valid result in an update of CDN', type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -42,7 +42,7 @@ test('marking only the most recent version as valid result in an update of CDN', projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -56,7 +56,7 @@ test('marking only the most recent version as valid result in an update of CDN', commit: 'c0', sdl: `type Query { ping: String }`, }, - token + token, ); expect(publishResult.body.errors).not.toBeDefined(); @@ -79,7 +79,7 @@ test('marking only the most recent version as valid result in an update of CDN', project: project.cleanId, target: target.cleanId, }, - token + token, ); expect(syncResult.body.errors).not.toBeDefined(); diff --git a/integration-tests/tests/api/target/crud.spec.ts b/integration-tests/tests/api/target/crud.spec.ts index 62fb0c1af..d56378878 100644 --- a/integration-tests/tests/api/target/crud.spec.ts +++ b/integration-tests/tests/api/target/crud.spec.ts @@ -8,7 +8,7 @@ test('renaming a target should result changing its cleanId', async () => { { name: 'foo', }, - access_token + access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -18,11 +18,13 @@ test('renaming a target should result changing its cleanId', async () => { type: ProjectType.Single, name: 'foo', }, - access_token + access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; - const target = projectResult.body.data?.createProject.ok?.createdTargets.find(t => t.name === 'production'); + const target = projectResult.body.data?.createProject.ok?.createdTargets.find( + t => t.name === 'production', + ); expect(target).toBeDefined(); @@ -33,7 +35,7 @@ test('renaming a target should result changing its cleanId', async () => { target: target!.cleanId, name: 'bar', }, - access_token + access_token, ); expect(renamedTargetResult.body.errors).not.toBeDefined(); diff --git a/integration-tests/tests/api/target/tokens.spec.ts b/integration-tests/tests/api/target/tokens.spec.ts index 432a00d25..ad177d4cc 100644 --- a/integration-tests/tests/api/target/tokens.spec.ts +++ b/integration-tests/tests/api/target/tokens.spec.ts @@ -16,7 +16,7 @@ test('setting no scopes equals to readonly for organization, project, target', a { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -27,7 +27,7 @@ test('setting no scopes equals to readonly for organization, project, target', a type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -44,7 +44,7 @@ test('setting no scopes equals to readonly for organization, project, target', a projectScopes: [], targetScopes: [], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -88,7 +88,7 @@ test('cannot set a scope on a token if user has no access to that scope', async { name: 'foo', }, - owner_access_token + owner_access_token, ); // Join @@ -100,7 +100,7 @@ test('cannot set a scope on a token if user has no access to that scope', async email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -114,7 +114,7 @@ test('cannot set a scope on a token if user has no access to that scope', async type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); if (joinResult.body.data!.joinOrganization.__typename !== 'OrganizationPayload') { @@ -139,7 +139,7 @@ test('cannot set a scope on a token if user has no access to that scope', async ], user: member.id, }, - owner_access_token + owner_access_token, ); // member should not have access to target:registry:write @@ -153,7 +153,7 @@ test('cannot set a scope on a token if user has no access to that scope', async projectScopes: [], targetScopes: [TargetAccessScope.RegistryWrite], }, - member_access_token + member_access_token, ); expect(tokenResult.body.errors).toHaveLength(1); diff --git a/integration-tests/tests/api/target/usage.spec.ts b/integration-tests/tests/api/target/usage.spec.ts index e83fc0d44..9e4d870bc 100644 --- a/integration-tests/tests/api/target/usage.spec.ts +++ b/integration-tests/tests/api/target/usage.spec.ts @@ -1,4 +1,9 @@ -import { TargetAccessScope, ProjectType, ProjectAccessScope, OrganizationAccessScope } from '@app/gql/graphql'; +import { + TargetAccessScope, + ProjectType, + ProjectAccessScope, + OrganizationAccessScope, +} from '@app/gql/graphql'; import formatISO from 'date-fns/formatISO'; import subHours from 'date-fns/subHours'; import { @@ -16,7 +21,7 @@ import { import { authenticate } from '../../../testkit/auth'; import { collect, CollectedOperation } from '../../../testkit/usage'; import { clickHouseQuery } from '../../../testkit/clickhouse'; -// eslint-disable-next-line hive/enforce-deps-in-dev, import/no-extraneous-dependencies +// eslint-disable-next-line hive/enforce-deps-in-dev import { normalizeOperation } from '@graphql-hive/core'; // eslint-disable-next-line import/no-extraneous-dependencies import { parse, print } from 'graphql'; @@ -49,7 +54,7 @@ test('collect operation', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -60,7 +65,7 @@ test('collect operation', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -76,7 +81,7 @@ test('collect operation', async () => { projectScopes: [ProjectAccessScope.Read], targetScopes: [TargetAccessScope.Read, TargetAccessScope.Settings], }, - owner_access_token + owner_access_token, ); const tokenResult = await createToken( @@ -87,9 +92,13 @@ test('collect operation', async () => { target: target.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - owner_access_token + owner_access_token, ); expect(settingsTokenResult.body.errors).not.toBeDefined(); @@ -104,7 +113,7 @@ test('collect operation', async () => { commit: 'abc123', sdl: `type Query { ping: String me: String }`, }, - token + token, ); expect(schemaPublishResult.body.errors).not.toBeDefined(); @@ -119,7 +128,7 @@ test('collect operation', async () => { }, { token: tokenForSettings, - } + }, ); expect(targetValidationResult.body.errors).not.toBeDefined(); @@ -132,7 +141,7 @@ test('collect operation', async () => { { sdl: `type Query { me: String }`, }, - token + token, ); expect(unusedCheckResult.body.errors).not.toBeDefined(); expect(unusedCheckResult.body.data!.schemaCheck.__typename).toEqual('SchemaCheckSuccess'); @@ -162,11 +171,13 @@ test('collect operation', async () => { { sdl: `type Query { me: String }`, }, - token + token, ); if (usedCheckResult.body.data!.schemaCheck.__typename !== 'SchemaCheckError') { - throw new Error(`Expected SchemaCheckError, got ${usedCheckResult.body.data!.schemaCheck.__typename}`); + throw new Error( + `Expected SchemaCheckError, got ${usedCheckResult.body.data!.schemaCheck.__typename}`, + ); } expect(usedCheckResult.body.data!.schemaCheck.valid).toEqual(false); @@ -183,7 +194,7 @@ test('collect operation', async () => { to, }, }, - token + token, ); expect(operationStatsResult.body.errors).not.toBeDefined(); @@ -212,7 +223,7 @@ test('normalize and collect operation without breaking its syntax', async () => { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -223,7 +234,7 @@ test('normalize and collect operation without breaking its syntax', async () => type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -239,7 +250,7 @@ test('normalize and collect operation without breaking its syntax', async () => projectScopes: [ProjectAccessScope.Read], targetScopes: [TargetAccessScope.Read, TargetAccessScope.Settings], }, - owner_access_token + owner_access_token, ); const tokenResult = await createToken( @@ -250,9 +261,13 @@ test('normalize and collect operation without breaking its syntax', async () => target: target.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - owner_access_token + owner_access_token, ); expect(settingsTokenResult.body.errors).not.toBeDefined(); @@ -357,7 +372,7 @@ test('normalize and collect operation without breaking its syntax', async () => to, }, }, - token + token, ); expect(operationStatsResult.body.errors).not.toBeDefined(); @@ -389,7 +404,7 @@ test('number of produced and collected operations should match (no errors)', asy { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -400,7 +415,7 @@ test('number of produced and collected operations should match (no errors)', asy type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -414,9 +429,13 @@ test('number of produced and collected operations should match (no errors)', asy target: target.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -439,7 +458,7 @@ test('number of produced and collected operations should match (no errors)', asy errorsTotal: 0, }, }, - token + token, ); } @@ -457,7 +476,7 @@ test('number of produced and collected operations should match (no errors)', asy to, }, }, - token + token, ); expect(operationStatsResult.body.errors).not.toBeDefined(); @@ -487,7 +506,7 @@ test('check usage from two selected targets', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -498,7 +517,7 @@ test('check usage from two selected targets', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -510,7 +529,7 @@ test('check usage from two selected targets', async () => { organization: org.cleanId, project: project.cleanId, }, - owner_access_token + owner_access_token, ); const production = productionTargetResult.body.data!.createTarget.ok!.createdTarget; @@ -523,9 +542,13 @@ test('check usage from two selected targets', async () => { target: staging.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - owner_access_token + owner_access_token, ); const productionTokenResult = await createToken( @@ -536,9 +559,13 @@ test('check usage from two selected targets', async () => { target: production.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - owner_access_token + owner_access_token, ); expect(stagingTokenResult.body.errors).not.toBeDefined(); @@ -553,7 +580,7 @@ test('check usage from two selected targets', async () => { commit: 'usage-check-2', sdl: `type Query { ping: String me: String }`, }, - tokenForStaging + tokenForStaging, ); expect(schemaPublishResult.body.errors).not.toBeDefined(); @@ -568,7 +595,7 @@ test('check usage from two selected targets', async () => { }, { authToken: owner_access_token, - } + }, ); expect(targetValidationResult.body.errors).not.toBeDefined(); @@ -625,7 +652,7 @@ test('check usage from two selected targets', async () => { { sdl: `type Query { me: String }`, // ping is used but on production }, - tokenForStaging + tokenForStaging, ); expect(unusedCheckResult.body.errors).not.toBeDefined(); expect(unusedCheckResult.body.data!.schemaCheck.__typename).toEqual('SchemaCheckSuccess'); @@ -643,19 +670,22 @@ test('check usage from two selected targets', async () => { }, { authToken: owner_access_token, - } + }, ); expect(updateValidationResult.body.errors).not.toBeDefined(); expect(updateValidationResult.body.data!.updateTargetValidationSettings.error).toBeNull(); expect( - updateValidationResult.body.data!.updateTargetValidationSettings.ok!.updatedTargetValidationSettings.percentage + updateValidationResult.body.data!.updateTargetValidationSettings.ok! + .updatedTargetValidationSettings.percentage, ).toEqual(50); expect( - updateValidationResult.body.data!.updateTargetValidationSettings.ok!.updatedTargetValidationSettings.period + updateValidationResult.body.data!.updateTargetValidationSettings.ok! + .updatedTargetValidationSettings.period, ).toEqual(2); expect( - updateValidationResult.body.data!.updateTargetValidationSettings.ok!.updatedTargetValidationSettings.targets + updateValidationResult.body.data!.updateTargetValidationSettings.ok! + .updatedTargetValidationSettings.targets, ).toHaveLength(2); // should be non-breaking because the field is used in production and we are checking staging and production now @@ -664,11 +694,13 @@ test('check usage from two selected targets', async () => { { sdl: `type Query { me: String }`, // ping is used on production and we do check production now }, - tokenForStaging + tokenForStaging, ); if (usedCheckResult.body.data!.schemaCheck.__typename !== 'SchemaCheckSuccess') { - throw new Error(`Expected SchemaCheckSuccess, got ${usedCheckResult.body.data!.schemaCheck.__typename}`); + throw new Error( + `Expected SchemaCheckSuccess, got ${usedCheckResult.body.data!.schemaCheck.__typename}`, + ); } expect(usedCheckResult.body.data!.schemaCheck.valid).toEqual(true); @@ -681,7 +713,7 @@ test('check usage not from excluded client names', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -692,11 +724,13 @@ test('check usage not from excluded client names', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; - const production = projectResult.body.data!.createProject.ok!.createdTargets.find(t => t.name === 'production'); + const production = projectResult.body.data!.createProject.ok!.createdTargets.find( + t => t.name === 'production', + ); if (!production) { throw new Error('No production target'); @@ -710,9 +744,13 @@ test('check usage not from excluded client names', async () => { target: production.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - owner_access_token + owner_access_token, ); expect(productionTokenResult.body.errors).not.toBeDefined(); @@ -725,7 +763,7 @@ test('check usage not from excluded client names', async () => { commit: 'usage-check-2', sdl: `type Query { ping: String me: String }`, }, - tokenForProduction + tokenForProduction, ); expect(schemaPublishResult.body.errors).not.toBeDefined(); @@ -740,7 +778,7 @@ test('check usage not from excluded client names', async () => { }, { authToken: owner_access_token, - } + }, ); expect(targetValidationResult.body.errors).not.toBeDefined(); @@ -814,7 +852,7 @@ test('check usage not from excluded client names', async () => { { sdl: `type Query { ping: String }`, // Query.me is used }, - tokenForProduction + tokenForProduction, ); expect(unusedCheckResult.body.errors).not.toBeDefined(); expect(unusedCheckResult.body.data!.schemaCheck.__typename).toEqual('SchemaCheckError'); @@ -832,19 +870,22 @@ test('check usage not from excluded client names', async () => { }, { authToken: owner_access_token, - } + }, ); expect(updateValidationResult.body.errors).not.toBeDefined(); expect(updateValidationResult.body.data!.updateTargetValidationSettings.error).toBeNull(); expect( - updateValidationResult.body.data!.updateTargetValidationSettings.ok!.updatedTargetValidationSettings.enabled + updateValidationResult.body.data!.updateTargetValidationSettings.ok! + .updatedTargetValidationSettings.enabled, ).toBe(true); expect( - updateValidationResult.body.data!.updateTargetValidationSettings.ok!.updatedTargetValidationSettings.excludedClients + updateValidationResult.body.data!.updateTargetValidationSettings.ok! + .updatedTargetValidationSettings.excludedClients, ).toHaveLength(1); expect( - updateValidationResult.body.data!.updateTargetValidationSettings.ok!.updatedTargetValidationSettings.excludedClients + updateValidationResult.body.data!.updateTargetValidationSettings.ok! + .updatedTargetValidationSettings.excludedClients, ).toContainEqual('app'); // should be safe because the field was not used by the non-excluded clients (cli never requested `Query.me`, but app did) @@ -852,11 +893,13 @@ test('check usage not from excluded client names', async () => { { sdl: `type Query { ping: String }`, }, - tokenForProduction + tokenForProduction, ); if (usedCheckResult.body.data!.schemaCheck.__typename !== 'SchemaCheckSuccess') { - throw new Error(`Expected SchemaCheckSuccess, got ${usedCheckResult.body.data!.schemaCheck.__typename}`); + throw new Error( + `Expected SchemaCheckSuccess, got ${usedCheckResult.body.data!.schemaCheck.__typename}`, + ); } expect(usedCheckResult.body.data!.schemaCheck.valid).toEqual(true); @@ -869,7 +912,7 @@ test('number of produced and collected operations should match', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -880,7 +923,7 @@ test('number of produced and collected operations should match', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -894,9 +937,13 @@ test('number of produced and collected operations should match', async () => { target: target.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -935,7 +982,7 @@ test('number of produced and collected operations should match', async () => { }, }, }, - token + token, ); } @@ -963,7 +1010,7 @@ test('number of produced and collected operations should match', async () => { client_name: 'web', hash: expect.any(String), total: expect.stringMatching('5000'), - }) + }), ); expect(result.data).toContainEqual( expect.objectContaining({ @@ -971,7 +1018,7 @@ test('number of produced and collected operations should match', async () => { client_name: '', hash: expect.any(String), total: expect.stringMatching('5000'), - }) + }), ); }); @@ -981,7 +1028,7 @@ test('different order of schema coordinates should not result in different hash' { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -992,7 +1039,7 @@ test('different order of schema coordinates should not result in different hash' type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -1006,9 +1053,13 @@ test('different order of schema coordinates should not result in different hash' target: target.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -1064,7 +1115,7 @@ test('different order of schema coordinates should not result in different hash' }>( FF_CLICKHOUSE_V2_TABLES ? `SELECT hash FROM operation_collection GROUP BY hash` - : `SELECT hash FROM operations_registry FINAL GROUP BY hash` + : `SELECT hash FROM operations_registry FINAL GROUP BY hash`, ); expect(operationCollectionResult.rows).toEqual(1); @@ -1076,7 +1127,7 @@ test('same operation but with different schema coordinates should result in diff { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -1087,7 +1138,7 @@ test('same operation but with different schema coordinates should result in diff type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -1101,9 +1152,13 @@ test('same operation but with different schema coordinates should result in diff target: target.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -1154,7 +1209,7 @@ test('same operation but with different schema coordinates should result in diff }>( FF_CLICKHOUSE_V2_TABLES ? `SELECT hash FROM operation_collection GROUP BY hash` - : `SELECT hash FROM operations_registry FINAL GROUP BY hash` + : `SELECT hash FROM operations_registry FINAL GROUP BY hash`, ); expect(operationCollectionResult.rows).toEqual(2); @@ -1167,7 +1222,7 @@ test('same operation but with different schema coordinates should result in diff }>( FF_CLICKHOUSE_V2_TABLES ? `SELECT hash FROM operation_collection GROUP BY hash` - : `SELECT hash FROM operations_registry FINAL GROUP BY hash` + : `SELECT hash FROM operations_registry FINAL GROUP BY hash`, ); expect(operationsResult.rows).toEqual(2); @@ -1179,7 +1234,7 @@ test('operations with the same schema coordinates and body but with different na { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -1190,7 +1245,7 @@ test('operations with the same schema coordinates and body but with different na type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -1204,9 +1259,13 @@ test('operations with the same schema coordinates and body but with different na target: target.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -1262,7 +1321,7 @@ test('operations with the same schema coordinates and body but with different na }>( FF_CLICKHOUSE_V2_TABLES ? `SELECT hash FROM operation_collection GROUP BY hash` - : `SELECT hash FROM operations_registry FINAL GROUP BY hash` + : `SELECT hash FROM operations_registry FINAL GROUP BY hash`, ); expect(operationsResult.rows).toEqual(2); @@ -1274,7 +1333,7 @@ test('ignore operations with syntax errors', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -1285,7 +1344,7 @@ test('ignore operations with syntax errors', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -1299,9 +1358,13 @@ test('ignore operations with syntax errors', async () => { target: target.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -1339,7 +1402,7 @@ test('ignore operations with syntax errors', async () => { expect.objectContaining({ rejected: 1, accepted: 1, - }) + }), ); await waitFor(5_000); @@ -1365,7 +1428,7 @@ test('ignore operations with syntax errors', async () => { }>( FF_CLICKHOUSE_V2_TABLES ? `SELECT hash FROM operation_collection GROUP BY hash` - : `SELECT hash FROM operations_registry FINAL GROUP BY hash` + : `SELECT hash FROM operations_registry FINAL GROUP BY hash`, ); expect(operationsResult.rows).toEqual(1); @@ -1377,7 +1440,7 @@ test('ensure correct data', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -1388,7 +1451,7 @@ test('ensure correct data', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -1402,9 +1465,13 @@ test('ensure correct data', async () => { target: target.cleanId, organizationScopes: [OrganizationAccessScope.Read], projectScopes: [ProjectAccessScope.Read], - targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], + targetScopes: [ + TargetAccessScope.Read, + TargetAccessScope.RegistryRead, + TargetAccessScope.RegistryWrite, + ], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -1637,7 +1704,9 @@ test('ensure correct data', async () => { expect(dailyAggOfKnownClient.hash).toHaveLength(32); expect(dailyAggOfKnownClient.target).toEqual(target.id); - const dailyAggOfUnknownClient = clientsDailyResult.data.find(c => c.client_name !== 'test-name')!; + const dailyAggOfUnknownClient = clientsDailyResult.data.find( + c => c.client_name !== 'test-name', + )!; expect(dailyAggOfUnknownClient).toBeDefined(); expect(ensureNumber(dailyAggOfUnknownClient.total)).toEqual(1); expect(dailyAggOfUnknownClient.client_version).toHaveLength(0); @@ -1799,7 +1868,9 @@ test('ensure correct data', async () => { expect(dailyAggOfKnownClient.hash).toHaveLength(32); expect(dailyAggOfKnownClient.target).toEqual(target.id); - const dailyAggOfUnknownClient = clientsDailyResult.data.find(c => c.client_name !== 'test-name')!; + const dailyAggOfUnknownClient = clientsDailyResult.data.find( + c => c.client_name !== 'test-name', + )!; expect(dailyAggOfUnknownClient).toBeDefined(); expect(ensureNumber(dailyAggOfUnknownClient.total)).toEqual(1); expect(dailyAggOfUnknownClient.hash).toHaveLength(32); diff --git a/integration-tests/tests/api/tokens.spec.ts b/integration-tests/tests/api/tokens.spec.ts index 29ef57140..3a69839f6 100644 --- a/integration-tests/tests/api/tokens.spec.ts +++ b/integration-tests/tests/api/tokens.spec.ts @@ -1,5 +1,11 @@ import { ProjectType } from '@app/gql/graphql'; -import { createOrganization, createProject, createToken, readTokenInfo, deleteTokens } from '../../testkit/flow'; +import { + createOrganization, + createProject, + createToken, + readTokenInfo, + deleteTokens, +} from '../../testkit/flow'; import { authenticate } from '../../testkit/auth'; test('deleting a token should clear the cache', async () => { @@ -8,7 +14,7 @@ test('deleting a token should clear the cache', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -19,7 +25,7 @@ test('deleting a token should clear the cache', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -36,7 +42,7 @@ test('deleting a token should clear the cache', async () => { projectScopes: [], targetScopes: [], }, - owner_access_token + owner_access_token, ); expect(tokenResult.body.errors).not.toBeDefined(); @@ -83,7 +89,7 @@ test('deleting a token should clear the cache', async () => { target: target.cleanId, tokens: [createdToken.id], }, - owner_access_token + owner_access_token, ); tokenInfoResult = await readTokenInfo(secret!); diff --git a/integration-tests/tests/cli/schema.spec.ts b/integration-tests/tests/cli/schema.spec.ts index 495d02ad6..650cf9b8f 100644 --- a/integration-tests/tests/cli/schema.spec.ts +++ b/integration-tests/tests/cli/schema.spec.ts @@ -17,7 +17,7 @@ test('can publish and check a schema with target:registry:read access', async () { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -26,7 +26,7 @@ test('can publish and check a schema with target:registry:read access', async () email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -42,7 +42,7 @@ test('can publish and check a schema with target:registry:read access', async () type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -59,7 +59,7 @@ test('can publish and check a schema with target:registry:read access', async () projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -76,9 +76,9 @@ test('can publish and check a schema with target:registry:read access', async () await schemaCheck(['--token', writeToken, 'fixtures/nonbreaking-schema.graphql']); - await expect(schemaCheck(['--token', writeToken, 'fixtures/breaking-schema.graphql'])).rejects.toThrowError( - /breaking/ - ); + await expect( + schemaCheck(['--token', writeToken, 'fixtures/breaking-schema.graphql']), + ).rejects.toThrowError(/breaking/); }); test('publishing a breaking change results in invalid state', async () => { @@ -87,7 +87,7 @@ test('publishing a breaking change results in invalid state', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -96,7 +96,7 @@ test('publishing a breaking change results in invalid state', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -112,7 +112,7 @@ test('publishing a breaking change results in invalid state', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -129,7 +129,7 @@ test('publishing a breaking change results in invalid state', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -144,9 +144,9 @@ test('publishing a breaking change results in invalid state', async () => { 'fixtures/init-schema.graphql', ]); - await expect(schemaPublish(['--token', writeToken, 'fixtures/breaking-schema.graphql'])).rejects.toThrowError( - /breaking/ - ); + await expect( + schemaPublish(['--token', writeToken, 'fixtures/breaking-schema.graphql']), + ).rejects.toThrowError(/breaking/); }); test('publishing invalid schema SDL provides meaningful feedback for the user.', async () => { @@ -155,7 +155,7 @@ test('publishing invalid schema SDL provides meaningful feedback for the user.', { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -164,7 +164,7 @@ test('publishing invalid schema SDL provides meaningful feedback for the user.', email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -180,7 +180,7 @@ test('publishing invalid schema SDL provides meaningful feedback for the user.', type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -197,7 +197,7 @@ test('publishing invalid schema SDL provides meaningful feedback for the user.', projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -229,7 +229,7 @@ test('service url should be available in supergraph', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -238,7 +238,7 @@ test('service url should be available in supergraph', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -254,7 +254,7 @@ test('service url should be available in supergraph', async () => { type: ProjectType.Federation, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -271,7 +271,7 @@ test('service url should be available in supergraph', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -296,7 +296,7 @@ test('service url should be available in supergraph', async () => { project: project.cleanId, target: target.cleanId, }, - writeToken + writeToken, ); expect(supergraph.body).toMatch('(name: "users" url: "https://api.com/users-subgraph")'); @@ -308,7 +308,7 @@ test('service url should be required in Federation', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -317,7 +317,7 @@ test('service url should be required in Federation', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -333,7 +333,7 @@ test('service url should be required in Federation', async () => { type: ProjectType.Federation, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -350,7 +350,7 @@ test('service url should be required in Federation', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -366,7 +366,7 @@ test('service url should be required in Federation', async () => { '--service', 'users', 'fixtures/federation-init.graphql', - ]) + ]), ).rejects.toThrowError(/url/); }); @@ -376,7 +376,7 @@ test('schema:publish should print a link to the website', async () => { { name: 'bar', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -385,7 +385,7 @@ test('schema:publish should print a link to the website', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -401,7 +401,7 @@ test('schema:publish should print a link to the website', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -419,18 +419,18 @@ test('schema:publish should print a link to the website', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; - await expect(schemaPublish(['--token', writeToken, 'fixtures/init-schema.graphql'])).resolves.toMatch( - 'Available at https://app.graphql-hive.com/bar/foo/development' - ); + await expect( + schemaPublish(['--token', writeToken, 'fixtures/init-schema.graphql']), + ).resolves.toMatch('Available at https://app.graphql-hive.com/bar/foo/development'); - await expect(schemaPublish(['--token', writeToken, 'fixtures/nonbreaking-schema.graphql'])).resolves.toMatch( - 'Available at https://app.graphql-hive.com/bar/foo/development/history/' - ); + await expect( + schemaPublish(['--token', writeToken, 'fixtures/nonbreaking-schema.graphql']), + ).resolves.toMatch('Available at https://app.graphql-hive.com/bar/foo/development/history/'); }); test('schema:check should notify user when registry is empty', async () => { @@ -439,7 +439,7 @@ test('schema:check should notify user when registry is empty', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -448,7 +448,7 @@ test('schema:check should notify user when registry is empty', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -464,7 +464,7 @@ test('schema:check should notify user when registry is empty', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -481,12 +481,14 @@ test('schema:check should notify user when registry is empty', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; - await expect(schemaCheck(['--token', writeToken, 'fixtures/init-schema.graphql'])).resolves.toMatch('empty'); + await expect( + schemaCheck(['--token', writeToken, 'fixtures/init-schema.graphql']), + ).resolves.toMatch('empty'); }); test('schema:check should throw on corrupted schema', async () => { @@ -495,7 +497,7 @@ test('schema:check should throw on corrupted schema', async () => { { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -504,7 +506,7 @@ test('schema:check should throw on corrupted schema', async () => { email: 'some@email.com', organization: org.cleanId, }, - owner_access_token + owner_access_token, ); const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code; @@ -520,7 +522,7 @@ test('schema:check should throw on corrupted schema', async () => { type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -537,7 +539,7 @@ test('schema:check should throw on corrupted schema', async () => { projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; @@ -553,7 +555,7 @@ test('schema:publish should see Invalid Token error when token is invalid', asyn { name: 'foo', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; @@ -563,7 +565,7 @@ test('schema:publish should see Invalid Token error when token is invalid', asyn type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const token = createHash('md5').update('nope').digest('hex').substring(0, 31); @@ -579,7 +581,7 @@ test('schema:publish should support experimental_acceptBreakingChanges flag', as { name: 'bar', }, - owner_access_token + owner_access_token, ); const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization; const projectResult = await createProject( @@ -588,7 +590,7 @@ test('schema:publish should support experimental_acceptBreakingChanges flag', as type: ProjectType.Single, name: 'foo', }, - owner_access_token + owner_access_token, ); const project = projectResult.body.data!.createProject.ok!.createdProject; @@ -606,16 +608,21 @@ test('schema:publish should support experimental_acceptBreakingChanges flag', as projectScopes: [], targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite], }, - owner_access_token + owner_access_token, ); expect(writeTokenResult.body.errors).not.toBeDefined(); const writeToken = writeTokenResult.body.data!.createToken.ok!.secret; - await expect(schemaPublish(['--token', writeToken, 'fixtures/init-schema.graphql'])).resolves.toMatch( - 'Available at https://app.graphql-hive.com/bar/foo/development' - ); + await expect( + schemaPublish(['--token', writeToken, 'fixtures/init-schema.graphql']), + ).resolves.toMatch('Available at https://app.graphql-hive.com/bar/foo/development'); await expect( - schemaPublish(['--token', writeToken, '--experimental_acceptBreakingChanges', 'fixtures/breaking-schema.graphql']) + schemaPublish([ + '--token', + writeToken, + '--experimental_acceptBreakingChanges', + 'fixtures/breaking-schema.graphql', + ]), ).resolves.toMatch('Available at https://app.graphql-hive.com/bar/foo/development/history/'); }); diff --git a/package.json b/package.json index 7aa0060c5..767c1b30a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "graphql-hive", + "version": "0.0.0", "type": "module", - "private": true, "repository": { "type": "git", "url": "kamilkisiela/graphql-hive" @@ -12,29 +12,35 @@ "url": "https://the-guild.dev" }, "license": "MIT", + "private": true, + "packageManager": "pnpm@7.14.2", + "engines": { + "node": ">=16", + "pnpm": ">=7" + }, "scripts": { - "seed": "node scripts/seed-local-env.js", + "build": "pnpm graphql:generate && pnpm turbo build --color", + "build:libraries": "pnpm graphql:generate && pnpm turbo build --filter=./packages/libraries/* --color", + "build:local": "pnpm graphql:generate && pnpm turbo build-local --color", + "build:services": "pnpm graphql:generate && pnpm turbo build --filter=./packages/services/* --color", + "build:web": "pnpm graphql:generate && pnpm turbo build --filter=./packages/web/* --color", + "docker:build": "docker buildx bake -f docker.hcl --load build", + "env:sync": "node ./scripts/sync-env-files.js", + "generate": "pnpm --filter @hive/storage db:generate && pnpm graphql:generate", + "graphql:generate": "graphql-codegen", + "lint": "eslint --cache --ignore-path .gitignore \"packages/**/*.{ts,tsx}\"", + "lint:prettier": "prettier --cache --check .", "postinstall": "husky install && node ./scripts/patch-manifests.js && pnpm env:sync && node ./scripts/turborepo-cleanup.js && node ./scripts/turborepo-setup.js", "pre-commit": "lint-staged", "prerelease": "pnpm build:libraries", - "release": "changeset publish", - "upload-sourcemaps": "./scripts/upload-sourcemaps.sh", - "test": "jest", - "lint": "eslint --cache --ignore-path .gitignore \"packages/**/*.{ts,tsx}\"", "prettier": "prettier --cache --write --list-different .", - "lint:prettier": "prettier --cache --check .", + "release": "changeset publish", + "seed": "node scripts/seed-local-env.js", "setup": "pnpm --filter @hive/storage setup", - "generate": "pnpm --filter @hive/storage db:generate && pnpm graphql:generate", - "graphql:generate": "graphql-codegen", - "typecheck": "pnpm turbo typecheck --color", - "build": "pnpm graphql:generate && pnpm turbo build --color", - "build:libraries": "pnpm graphql:generate && pnpm turbo build --filter=./packages/libraries/* --color", - "build:web": "pnpm graphql:generate && pnpm turbo build --filter=./packages/web/* --color", - "build:services": "pnpm graphql:generate && pnpm turbo build --filter=./packages/services/* --color", - "build:local": "pnpm graphql:generate && pnpm turbo build-local --color", - "env:sync": "node ./scripts/sync-env-files.js", + "test": "jest", "turbo": "env-cmd --silent turbo run", - "docker:build": "docker buildx bake -f docker.hcl --load build" + "typecheck": "pnpm turbo typecheck --color", + "upload-sourcemaps": "./scripts/upload-sourcemaps.sh" }, "devDependencies": { "@babel/core": "7.19.1", @@ -55,7 +61,7 @@ "@graphql-codegen/typescript-resolvers": "2.7.5", "@sentry/cli": "2.9.0", "@swc/core": "1.2.185", - "@theguild/prettier-config": "0.1.1", + "@theguild/prettier-config": "1.0.0", "@types/jest": "29.2.0", "@types/lru-cache": "7.6.1", "@types/node": "16.11.22", @@ -106,11 +112,6 @@ "eslint --fix" ] }, - "engines": { - "node": ">=16", - "pnpm": ">=7" - }, - "packageManager": "pnpm@7.14.2", "pnpm": { "patchedDependencies": { "@n1ru4l/dockest@2.1.0-rc.6": "patches/@n1ru4l__dockest@2.1.0-rc.6.patch", @@ -123,6 +124,5 @@ "bob-the-bundler@4.0.0": "patches/bob-the-bundler@4.0.0.patch", "oclif@3.2.25": "patches/oclif@3.2.25.patch" } - }, - "version": "0.0.0" + } } diff --git a/packages/libraries/cli/CHANGELOG.md b/packages/libraries/cli/CHANGELOG.md index 346d5b98a..fb1262539 100644 --- a/packages/libraries/cli/CHANGELOG.md +++ b/packages/libraries/cli/CHANGELOG.md @@ -4,76 +4,102 @@ ### Patch Changes -- Updated dependencies [[`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637)]: +- Updated dependencies + [[`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637)]: - @graphql-hive/core@0.2.3 ## 0.19.9 ### Patch Changes -- [#655](https://github.com/kamilkisiela/graphql-hive/pull/655) [`2cbf27f`](https://github.com/kamilkisiela/graphql-hive/commit/2cbf27fdc9c18749b8969adb6d1598338762dba2) Thanks [@n1ru4l](https://github.com/n1ru4l)! - Add User-Agent header to all http requests +- [#655](https://github.com/kamilkisiela/graphql-hive/pull/655) + [`2cbf27f`](https://github.com/kamilkisiela/graphql-hive/commit/2cbf27fdc9c18749b8969adb6d1598338762dba2) + Thanks [@n1ru4l](https://github.com/n1ru4l)! - Add User-Agent header to all http requests ## 0.19.8 ### Patch Changes -- [#648](https://github.com/kamilkisiela/graphql-hive/pull/648) [`84a78fc`](https://github.com/kamilkisiela/graphql-hive/commit/84a78fc2a4061e05b1bbe4a8d11006601c767384) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - bump +- [#648](https://github.com/kamilkisiela/graphql-hive/pull/648) + [`84a78fc`](https://github.com/kamilkisiela/graphql-hive/commit/84a78fc2a4061e05b1bbe4a8d11006601c767384) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - bump ## 0.19.7 ### Patch Changes -- [#646](https://github.com/kamilkisiela/graphql-hive/pull/646) [`65f3372`](https://github.com/kamilkisiela/graphql-hive/commit/65f3372dfa047238352beee113ccb8506cc180ca) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - I hope it's final bump +- [#646](https://github.com/kamilkisiela/graphql-hive/pull/646) + [`65f3372`](https://github.com/kamilkisiela/graphql-hive/commit/65f3372dfa047238352beee113ccb8506cc180ca) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - I hope it's final bump ## 0.19.6 ### Patch Changes -- [#645](https://github.com/kamilkisiela/graphql-hive/pull/645) [`7110555`](https://github.com/kamilkisiela/graphql-hive/commit/71105559b67f510087223ada2af23564ff053353) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Ignore npm-shrinkwrap.json +- [#645](https://github.com/kamilkisiela/graphql-hive/pull/645) + [`7110555`](https://github.com/kamilkisiela/graphql-hive/commit/71105559b67f510087223ada2af23564ff053353) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Ignore npm-shrinkwrap.json ## 0.19.5 ### Patch Changes -- [#641](https://github.com/kamilkisiela/graphql-hive/pull/641) [`ce55b72`](https://github.com/kamilkisiela/graphql-hive/commit/ce55b724b00ff7fc93f3df4089e698e6f9d5086b) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Do not include npm-shrinkwrap.json +- [#641](https://github.com/kamilkisiela/graphql-hive/pull/641) + [`ce55b72`](https://github.com/kamilkisiela/graphql-hive/commit/ce55b724b00ff7fc93f3df4089e698e6f9d5086b) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Do not include npm-shrinkwrap.json ## 0.19.4 ### Patch Changes -- [#631](https://github.com/kamilkisiela/graphql-hive/pull/631) [`d4ca981`](https://github.com/kamilkisiela/graphql-hive/commit/d4ca98180bd0b2910fb41f623c2f5abb1f4b9214) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump +- [#631](https://github.com/kamilkisiela/graphql-hive/pull/631) + [`d4ca981`](https://github.com/kamilkisiela/graphql-hive/commit/d4ca98180bd0b2910fb41f623c2f5abb1f4b9214) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump -- [#631](https://github.com/kamilkisiela/graphql-hive/pull/631) [`d4ca981`](https://github.com/kamilkisiela/graphql-hive/commit/d4ca98180bd0b2910fb41f623c2f5abb1f4b9214) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump CLI +- [#631](https://github.com/kamilkisiela/graphql-hive/pull/631) + [`d4ca981`](https://github.com/kamilkisiela/graphql-hive/commit/d4ca98180bd0b2910fb41f623c2f5abb1f4b9214) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump CLI ## 0.19.3 ### Patch Changes -- [#629](https://github.com/kamilkisiela/graphql-hive/pull/629) [`750b46d`](https://github.com/kamilkisiela/graphql-hive/commit/750b46d155c5d01ad4b3cee84409793736246603) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump +- [#629](https://github.com/kamilkisiela/graphql-hive/pull/629) + [`750b46d`](https://github.com/kamilkisiela/graphql-hive/commit/750b46d155c5d01ad4b3cee84409793736246603) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump ## 0.19.2 ### Patch Changes -- [#627](https://github.com/kamilkisiela/graphql-hive/pull/627) [`78096dc`](https://github.com/kamilkisiela/graphql-hive/commit/78096dcfbd37059fbb309e8faa6bae1d14e18c79) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - bump +- [#627](https://github.com/kamilkisiela/graphql-hive/pull/627) + [`78096dc`](https://github.com/kamilkisiela/graphql-hive/commit/78096dcfbd37059fbb309e8faa6bae1d14e18c79) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - bump ## 0.19.1 ### Patch Changes -- [#466](https://github.com/kamilkisiela/graphql-hive/pull/466) [`2e036ac`](https://github.com/kamilkisiela/graphql-hive/commit/2e036acc4ce1c27a493e90481bb10f5886c0a00c) Thanks [@ardatan](https://github.com/ardatan)! - Update GraphQL Tools packages +- [#466](https://github.com/kamilkisiela/graphql-hive/pull/466) + [`2e036ac`](https://github.com/kamilkisiela/graphql-hive/commit/2e036acc4ce1c27a493e90481bb10f5886c0a00c) + Thanks [@ardatan](https://github.com/ardatan)! - Update GraphQL Tools packages ## 0.19.0 ### Minor Changes -- [#357](https://github.com/kamilkisiela/graphql-hive/pull/357) [`30f11c4`](https://github.com/kamilkisiela/graphql-hive/commit/30f11c40054debfcbd8b6090316d129eb7851046) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Adds experimental_acceptBreakingChanges +- [#357](https://github.com/kamilkisiela/graphql-hive/pull/357) + [`30f11c4`](https://github.com/kamilkisiela/graphql-hive/commit/30f11c40054debfcbd8b6090316d129eb7851046) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Adds experimental_acceptBreakingChanges ## 0.18.2 ### Patch Changes -- [#292](https://github.com/kamilkisiela/graphql-hive/pull/292) [`efb03e1`](https://github.com/kamilkisiela/graphql-hive/commit/efb03e184d5a878dbcca83295b2d1d53b3c9f8e3) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump @oclif/core dependency range to ^1.13.10 +- [#292](https://github.com/kamilkisiela/graphql-hive/pull/292) + [`efb03e1`](https://github.com/kamilkisiela/graphql-hive/commit/efb03e184d5a878dbcca83295b2d1d53b3c9f8e3) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump @oclif/core dependency range to + ^1.13.10 ## 0.18.1 @@ -187,9 +213,11 @@ - c5bfa4c9: Add a new `metadata` flag for publishing schema metadata (JSON) to Hive. - The `--metadata` can contain anything you wish to have attached to your GraphQL schema, and can support your runtime needs. + The `--metadata` can contain anything you wish to have attached to your GraphQL schema, and can + support your runtime needs. - You can either specify a path to a file: `--metadata my-file.json`, or an inline JSON object: `--metadata '{"test": 1}'`. + You can either specify a path to a file: `--metadata my-file.json`, or an inline JSON object: + `--metadata '{"test": 1}'`. Metadata published to Hive will be available as part of Hive CDN, under `/metadata` route. @@ -403,7 +431,8 @@ ### Minor Changes - 078e758: Token per Target -- 1dd9cdb: --file flag is now replaced with a positional arg at the end, comments in graphql sdl files are now converted to descriptions, docs are updated to mention wildcard file uploads +- 1dd9cdb: --file flag is now replaced with a positional arg at the end, comments in graphql sdl + files are now converted to descriptions, docs are updated to mention wildcard file uploads ### Patch Changes diff --git a/packages/libraries/cli/README.md b/packages/libraries/cli/README.md index 3f1289181..e1853a3f0 100644 --- a/packages/libraries/cli/README.md +++ b/packages/libraries/cli/README.md @@ -63,7 +63,8 @@ DESCRIPTION deletes specific cli configuration ``` -_See code: [dist/commands/config/delete.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/delete.js)_ +_See code: +[dist/commands/config/delete.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/delete.js)_ ## `hive config:get KEY` @@ -80,7 +81,8 @@ DESCRIPTION prints specific cli configuration ``` -_See code: [dist/commands/config/get.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/get.js)_ +_See code: +[dist/commands/config/get.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/get.js)_ ## `hive config:reset` @@ -94,7 +96,8 @@ DESCRIPTION resets local cli configuration ``` -_See code: [dist/commands/config/reset.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/reset.js)_ +_See code: +[dist/commands/config/reset.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/reset.js)_ ## `hive config:set KEY VALUE` @@ -112,7 +115,8 @@ DESCRIPTION updates specific cli configuration ``` -_See code: [dist/commands/config/set.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/set.js)_ +_See code: +[dist/commands/config/set.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/set.js)_ ## `hive help [COMMAND]` @@ -132,7 +136,8 @@ DESCRIPTION Display help for hive. ``` -_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.1.18/src/commands/help.ts)_ +_See code: +[@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.1.18/src/commands/help.ts)_ ## `hive operations:check FILE` @@ -154,7 +159,8 @@ DESCRIPTION checks operations against a published schema ``` -_See code: [dist/commands/operations/check.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/operations/check.js)_ +_See code: +[dist/commands/operations/check.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/operations/check.js)_ ## `hive operations:publish FILE` @@ -177,7 +183,8 @@ DESCRIPTION saves operations to the store ``` -_See code: [dist/commands/operations/publish.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/operations/publish.js)_ +_See code: +[dist/commands/operations/publish.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/operations/publish.js)_ ## `hive schema:check FILE` @@ -204,7 +211,8 @@ DESCRIPTION checks schema ``` -_See code: [dist/commands/schema/check.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/schema/check.js)_ +_See code: +[dist/commands/schema/check.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/schema/check.js)_ ## `hive schema:publish FILE` @@ -239,7 +247,8 @@ DESCRIPTION publishes schema ``` -_See code: [dist/commands/schema/publish.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/schema/publish.js)_ +_See code: +[dist/commands/schema/publish.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/schema/publish.js)_ ## `hive update [CHANNEL]` @@ -276,7 +285,8 @@ EXAMPLES $ hive update --available ``` -_See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v3.0.6/src/commands/update.ts)_ +_See code: +[@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v3.0.6/src/commands/update.ts)_ ## `hive whoami` @@ -294,7 +304,8 @@ DESCRIPTION checks schema ``` -_See code: [dist/commands/whoami.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/whoami.js)_ +_See code: +[dist/commands/whoami.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/whoami.js)_ @@ -302,7 +313,8 @@ _See code: [dist/commands/whoami.js](https://github.com/kamilkisiela/graphql-hiv # Config -In addition to using the CLI args, you can also define your configuration in a JSON file which the CLI would pick up when it runs. +In addition to using the CLI args, you can also define your configuration in a JSON file which the +CLI would pick up when it runs. You can use the `HIVE_CONFIG` environment variable to define the path to the JSON file as follows: diff --git a/packages/libraries/cli/package.json b/packages/libraries/cli/package.json index a9840d531..8d4c7d4cc 100644 --- a/packages/libraries/cli/package.json +++ b/packages/libraries/cli/package.json @@ -1,22 +1,22 @@ { "name": "@graphql-hive/cli", - "description": "A CLI util to manage and control your GraphQL Hive", "version": "0.19.10", - "author": { - "email": "contact@the-guild.dev", - "name": "The Guild", - "url": "https://the-guild.dev" - }, + "description": "A CLI util to manage and control your GraphQL Hive", "repository": { "type": "git", "url": "kamilkisiela/graphql-hive", "directory": "packages/libraries/cli" }, "homepage": "https://graphql-hive.com", + "author": { + "email": "contact@the-guild.dev", + "name": "The Guild", + "url": "https://the-guild.dev" + }, "license": "MIT", - "keywords": [ - "graphql" - ], + "engines": { + "node": ">=14.0.0" + }, "bin": { "hive": "./bin/run" }, @@ -26,19 +26,22 @@ "/dist", "/oclif.manifest.json" ], + "keywords": [ + "graphql" + ], "scripts": { - "start": "./bin/dev", - "prebuild": "rimraf dist", "build": "tsc", - "postpack": "rm -f oclif.manifest.json", - "prepack": "rimraf lib && tsc -b && oclif manifest && oclif readme", "oclif:pack": "npm pack && pnpm oclif pack tarballs --no-xz", "oclif:upload": "pnpm oclif upload tarballs --no-xz", - "version": "oclif readme && git add README.md", + "postpack": "rm -f oclif.manifest.json", + "prebuild": "rimraf dist", + "prepack": "rimraf lib && tsc -b && oclif manifest && oclif readme", + "schema:check:federation": "pnpm start schema:check examples/federation.graphql --service reviews", "schema:check:single": "pnpm start schema:check examples/single.graphql", "schema:check:stitching": "pnpm start schema:check examples/stitching.graphql --service posts", - "schema:check:federation": "pnpm start schema:check examples/federation.graphql --service reviews", - "schema:publish:federation": "pnpm start schema:publish --service reviews examples/federation.reviews.graphql" + "schema:publish:federation": "pnpm start schema:publish --service reviews examples/federation.reviews.graphql", + "start": "./bin/dev", + "version": "oclif readme && git add README.md" }, "dependencies": { "@graphql-hive/core": "0.2.3", @@ -46,8 +49,8 @@ "@graphql-tools/code-file-loader": "~7.3.6", "@graphql-tools/graphql-file-loader": "~7.5.5", "@graphql-tools/json-file-loader": "~7.4.6", - "@graphql-tools/url-loader": "~7.16.4", "@graphql-tools/load": "~7.7.7", + "@graphql-tools/url-loader": "~7.16.4", "@graphql-tools/utils": "8.12.0", "@oclif/core": "^1.20.4", "@oclif/plugin-help": "5.1.19", @@ -61,14 +64,18 @@ "log-symbols": "4.1.0", "mkdirp": "1.0.4", "rimraf": "3.0.2", - "tslib": "2.4.1", - "ts-node": "10.9.1" + "ts-node": "10.9.1", + "tslib": "2.4.1" }, "devDependencies": { - "oclif": "^3.2.25", "@types/env-ci": "3.1.1", "@types/git-parse": "2.1.2", - "@types/mkdirp": "1.0.2" + "@types/mkdirp": "1.0.2", + "oclif": "^3.2.25" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org", + "access": "public" }, "oclif": { "commands": "./dist/commands", @@ -82,12 +89,5 @@ "bucket": "graphql-hive-cli" } } - }, - "publishConfig": { - "registry": "https://registry.npmjs.org", - "access": "public" - }, - "engines": { - "node": ">=14.0.0" } } diff --git a/packages/libraries/cli/src/base-command.ts b/packages/libraries/cli/src/base-command.ts index d642c20ef..c55a51671 100644 --- a/packages/libraries/cli/src/base-command.ts +++ b/packages/libraries/cli/src/base-command.ts @@ -54,7 +54,7 @@ export default abstract class extends Command { TArgs extends { [key: string]: any; }, - TKey extends keyof TArgs + TKey extends keyof TArgs, >({ key, args, @@ -104,7 +104,7 @@ export default abstract class extends Command { TArgs extends { [key: string]: any; }, - TKey extends keyof TArgs + TKey extends keyof TArgs, >(key: TKey, args: TArgs): TArgs[TKey] | undefined { if (args[key]) { return args[key]; @@ -129,7 +129,7 @@ export default abstract class extends Command { 'graphql-client-name': 'Hive CLI', 'graphql-client-version': this.config.version, }, - }) + }), ); } @@ -163,10 +163,12 @@ export default abstract class extends Command { TFlags extends { require: string[]; [key: string]: any; - } + }, >(flags: TFlags) { if (flags.require && flags.require.length > 0) { - await Promise.all(flags.require.map(mod => import(require.resolve(mod, { paths: [process.cwd()] })))); + await Promise.all( + flags.require.map(mod => import(require.resolve(mod, { paths: [process.cwd()] }))), + ); } } } diff --git a/packages/libraries/cli/src/commands/operations/check.ts b/packages/libraries/cli/src/commands/operations/check.ts index cc672a34a..805557746 100644 --- a/packages/libraries/cli/src/commands/operations/check.ts +++ b/packages/libraries/cli/src/commands/operations/check.ts @@ -74,7 +74,7 @@ export default class OperationsCheck extends Command { const invalidOperations = validate( schema, - operations.map(s => new Source(s.content, s.location)) + operations.map(s => new Source(s.content, s.location)), ); if (invalidOperations.length === 0) { @@ -85,7 +85,9 @@ export default class OperationsCheck extends Command { this.fail('Some operations are invalid'); - this.log(['', `Total: ${operations.length}`, `Invalid: ${invalidOperations.length}`, ''].join('\n')); + this.log( + ['', `Total: ${operations.length}`, `Invalid: ${invalidOperations.length}`, ''].join('\n'), + ); this.printInvalidDocuments(invalidOperations, 'errors'); } catch (error) { @@ -98,7 +100,10 @@ export default class OperationsCheck extends Command { } } - private printInvalidDocuments(invalidDocuments: InvalidDocument[], listKey: 'errors' | 'deprecated'): void { + private printInvalidDocuments( + invalidDocuments: InvalidDocument[], + listKey: 'errors' | 'deprecated', + ): void { invalidDocuments.forEach(doc => { if (doc.errors.length) { this.renderErrors(doc.source.name, doc[listKey]).forEach(line => { diff --git a/packages/libraries/cli/src/commands/operations/publish.ts b/packages/libraries/cli/src/commands/operations/publish.ts index c9a42659f..ac4f4ffa8 100644 --- a/packages/libraries/cli/src/commands/operations/publish.ts +++ b/packages/libraries/cli/src/commands/operations/publish.ts @@ -13,7 +13,8 @@ export default class OperationsPublish extends Command { description: 'api token', }), require: Flags.string({ - description: 'Loads specific require.extensions before running the codegen and reading the configuration', + description: + 'Loads specific require.extensions before running the codegen and reading the configuration', default: [], multiple: true, }), @@ -54,9 +55,11 @@ export default class OperationsPublish extends Command { const noMissingHashes = operations.some(op => !!op.operationHash); if (noMissingHashes) { - const comparisonResult = await this.registryApi(registry, token).comparePersistedOperations({ - hashes: operations.map(op => op.operationHash!), - }); + const comparisonResult = await this.registryApi(registry, token).comparePersistedOperations( + { + hashes: operations.map(op => op.operationHash!), + }, + ); const operationsToPublish = comparisonResult.comparePersistedOperations; @@ -67,9 +70,13 @@ export default class OperationsPublish extends Command { if (!operations.length) { return this.success( - [`Nothing to publish`, '', ` Total: ${collectedOperationsTotal}`, ` Unchanged: ${unchangedTotal}`, ''].join( - '\n' - ) + [ + `Nothing to publish`, + '', + ` Total: ${collectedOperationsTotal}`, + ` Unchanged: ${unchangedTotal}`, + '', + ].join('\n'), ); } @@ -86,7 +93,7 @@ export default class OperationsPublish extends Command { ` Total: ${summary.total}`, ` Unchanged: ${summary.unchanged}`, '', - ].join('\n') + ].join('\n'), ); } else { this.error('OOPS! An error occurred in publishing the operation(s)'); diff --git a/packages/libraries/cli/src/commands/schema/check.ts b/packages/libraries/cli/src/commands/schema/check.ts index a13f333c4..48a53378f 100644 --- a/packages/libraries/cli/src/commands/schema/check.ts +++ b/packages/libraries/cli/src/commands/schema/check.ts @@ -25,7 +25,8 @@ export default class SchemaCheck extends Command { default: false, }), require: Flags.string({ - description: 'Loads specific require.extensions before running the codegen and reading the configuration', + description: + 'Loads specific require.extensions before running the codegen and reading the configuration', default: [], multiple: true, }), @@ -70,7 +71,10 @@ export default class SchemaCheck extends Command { invariant(typeof sdl === 'string' && sdl.length > 0, 'Schema seems empty'); if (usesGitHubApp) { - invariant(typeof commit === 'string', `Couldn't resolve commit sha required for GitHub Application`); + invariant( + typeof commit === 'string', + `Couldn't resolve commit sha required for GitHub Application`, + ); } const result = await this.registryApi(registry, token).schemaCheck({ diff --git a/packages/libraries/cli/src/commands/schema/publish.ts b/packages/libraries/cli/src/commands/schema/publish.ts index fce23cd64..47079ecdd 100644 --- a/packages/libraries/cli/src/commands/schema/publish.ts +++ b/packages/libraries/cli/src/commands/schema/publish.ts @@ -42,10 +42,12 @@ export default class SchemaPublish extends Command { default: false, }), experimental_acceptBreakingChanges: Flags.boolean({ - description: '(experimental) accept breaking changes and mark schema as valid (only if composable)', + description: + '(experimental) accept breaking changes and mark schema as valid (only if composable)', }), require: Flags.string({ - description: 'Loads specific require.extensions before running the codegen and reading the configuration', + description: + 'Loads specific require.extensions before running the codegen and reading the configuration', default: [], multiple: true, }), @@ -76,7 +78,7 @@ export default class SchemaPublish extends Command { if (!exists) { throw new Error( - `Failed to load metadata from "${metadata}": Please specify a path to an existing file, or a string with valid JSON.` + `Failed to load metadata from "${metadata}": Please specify a path to an existing file, or a string with valid JSON.`, ); } @@ -87,7 +89,7 @@ export default class SchemaPublish extends Command { return fileContent; } catch (e) { throw new Error( - `Failed to load metadata from file "${metadata}": Please make sure the file is readable and contains a valid JSON` + `Failed to load metadata from file "${metadata}": Please make sure the file is readable and contains a valid JSON`, ); } } @@ -114,7 +116,10 @@ export default class SchemaPublish extends Command { env: 'HIVE_TOKEN', }); const force = this.maybe('force', flags); - const experimental_acceptBreakingChanges = this.maybe('experimental_acceptBreakingChanges', flags); + const experimental_acceptBreakingChanges = this.maybe( + 'experimental_acceptBreakingChanges', + flags, + ); const metadata = this.resolveMetadata(this.maybe('metadata', flags)); const usesGitHubApp = this.maybe('github', flags) === true; @@ -152,7 +157,9 @@ export default class SchemaPublish extends Command { } catch (err) { if (err instanceof GraphQLError) { const location = err.locations?.[0]; - const locationString = location ? ` at line ${location.line}, column ${location.column}` : ''; + const locationString = location + ? ` at line ${location.line}, column ${location.column}` + : ''; throw new Error(`The SDL is not valid${locationString}:\n ${err.message}`); } throw err; @@ -191,10 +198,14 @@ export default class SchemaPublish extends Command { this.info(`Available at ${result.schemaPublish.linkToWebsite}`); } } else if (result.schemaPublish.__typename === 'SchemaPublishMissingServiceError') { - this.fail(`${result.schemaPublish.missingServiceError} Please use the '--service ' parameter.`); + this.fail( + `${result.schemaPublish.missingServiceError} Please use the '--service ' parameter.`, + ); this.exit(1); } else if (result.schemaPublish.__typename === 'SchemaPublishMissingUrlError') { - this.fail(`${result.schemaPublish.missingUrlError} Please use the '--url ' parameter.`); + this.fail( + `${result.schemaPublish.missingUrlError} Please use the '--url ' parameter.`, + ); this.exit(1); } else if (result.schemaPublish.__typename === 'SchemaPublishError') { const changes = result.schemaPublish.changes; @@ -220,7 +231,9 @@ export default class SchemaPublish extends Command { } else if (result.schemaPublish.__typename === 'GitHubSchemaPublishSuccess') { this.success(result.schemaPublish.message); } else { - this.error('message' in result.schemaPublish ? result.schemaPublish.message : 'Unknown error'); + this.error( + 'message' in result.schemaPublish ? result.schemaPublish.message : 'Unknown error', + ); } } catch (error) { if (error instanceof Errors.ExitError) { diff --git a/packages/libraries/cli/src/helpers/git.ts b/packages/libraries/cli/src/helpers/git.ts index 03ac8c3d2..e9d3b4dda 100644 --- a/packages/libraries/cli/src/helpers/git.ts +++ b/packages/libraries/cli/src/helpers/git.ts @@ -42,7 +42,9 @@ function useGitHubAction(): CIRunner { env() { const isPr = // eslint-disable-next-line no-process-env - process.env.GITHUB_EVENT_NAME === 'pull_request' || process.env.GITHUB_EVENT_NAME === 'pull_request_target'; + process.env.GITHUB_EVENT_NAME === 'pull_request' || + // eslint-disable-next-line no-process-env + process.env.GITHUB_EVENT_NAME === 'pull_request_target'; if (isPr) { try { @@ -85,7 +87,8 @@ export async function gitInfo(noGit: () => void) { if (!commit || !author) { const rootFromEnv = 'root' in env ? env.root : null; - const git = rootFromEnv ?? findParentDir(__dirname, '.git') ?? findParentDir(process.cwd(), '.git'); + const git = + rootFromEnv ?? findParentDir(__dirname, '.git') ?? findParentDir(process.cwd(), '.git'); if (git) { const commits = await gitToJs(git); diff --git a/packages/libraries/cli/src/helpers/operations.ts b/packages/libraries/cli/src/helpers/operations.ts index 63dda2822..19ae0e196 100644 --- a/packages/libraries/cli/src/helpers/operations.ts +++ b/packages/libraries/cli/src/helpers/operations.ts @@ -10,7 +10,7 @@ export async function loadOperations( file: string, options?: { normalize?: boolean; - } + }, ): Promise< Array<{ operationHash?: string; @@ -24,7 +24,7 @@ export async function loadOperations( const output: Record = JSON.parse( await fs.readFile(file, { encoding: 'utf-8', - }) + }), ); const operations: Array<{ diff --git a/packages/libraries/client/CHANGELOG.md b/packages/libraries/client/CHANGELOG.md index 45b10221b..8235ca4e4 100644 --- a/packages/libraries/client/CHANGELOG.md +++ b/packages/libraries/client/CHANGELOG.md @@ -4,64 +4,90 @@ ### Patch Changes -- [#668](https://github.com/kamilkisiela/graphql-hive/pull/668) [`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix ESM/CJS issue +- [#668](https://github.com/kamilkisiela/graphql-hive/pull/668) + [`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix ESM/CJS issue -- Updated dependencies [[`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637)]: +- Updated dependencies + [[`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637)]: - @graphql-hive/core@0.2.3 ## 0.21.2 ### Patch Changes -- [#655](https://github.com/kamilkisiela/graphql-hive/pull/655) [`2cbf27f`](https://github.com/kamilkisiela/graphql-hive/commit/2cbf27fdc9c18749b8969adb6d1598338762dba2) Thanks [@n1ru4l](https://github.com/n1ru4l)! - Add User-Agent header to all http requests +- [#655](https://github.com/kamilkisiela/graphql-hive/pull/655) + [`2cbf27f`](https://github.com/kamilkisiela/graphql-hive/commit/2cbf27fdc9c18749b8969adb6d1598338762dba2) + Thanks [@n1ru4l](https://github.com/n1ru4l)! - Add User-Agent header to all http requests ## 0.21.1 ### Patch Changes -- [#466](https://github.com/kamilkisiela/graphql-hive/pull/466) [`2e036ac`](https://github.com/kamilkisiela/graphql-hive/commit/2e036acc4ce1c27a493e90481bb10f5886c0a00c) Thanks [@ardatan](https://github.com/ardatan)! - Update GraphQL Tools packages +- [#466](https://github.com/kamilkisiela/graphql-hive/pull/466) + [`2e036ac`](https://github.com/kamilkisiela/graphql-hive/commit/2e036acc4ce1c27a493e90481bb10f5886c0a00c) + Thanks [@ardatan](https://github.com/ardatan)! - Update GraphQL Tools packages ## 0.21.0 ### Minor Changes -- [#563](https://github.com/kamilkisiela/graphql-hive/pull/563) [`d58a470`](https://github.com/kamilkisiela/graphql-hive/commit/d58a470916b213230f495e896fe99ec0baa225e2) Thanks [@PabloSzx](https://github.com/PabloSzx)! - Fix createServicesFetcher handling null service url +- [#563](https://github.com/kamilkisiela/graphql-hive/pull/563) + [`d58a470`](https://github.com/kamilkisiela/graphql-hive/commit/d58a470916b213230f495e896fe99ec0baa225e2) + Thanks [@PabloSzx](https://github.com/PabloSzx)! - Fix createServicesFetcher handling null service + url ## 0.20.1 ### Patch Changes -- [#456](https://github.com/kamilkisiela/graphql-hive/pull/456) [`fb9b624`](https://github.com/kamilkisiela/graphql-hive/commit/fb9b624ab80ff39658c9ecd45b55d10d906e15e7) Thanks [@dimatill](https://github.com/dimatill)! - (processVariables: true) do not collect input when corresponding variable is missing +- [#456](https://github.com/kamilkisiela/graphql-hive/pull/456) + [`fb9b624`](https://github.com/kamilkisiela/graphql-hive/commit/fb9b624ab80ff39658c9ecd45b55d10d906e15e7) + Thanks [@dimatill](https://github.com/dimatill)! - (processVariables: true) do not collect input + when corresponding variable is missing ## 0.20.0 ### Minor Changes -- [#499](https://github.com/kamilkisiela/graphql-hive/pull/499) [`682cde8`](https://github.com/kamilkisiela/graphql-hive/commit/682cde81092fcb3a55de7f24035be4f2f64abfb3) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Add Self-Hosting options +- [#499](https://github.com/kamilkisiela/graphql-hive/pull/499) + [`682cde8`](https://github.com/kamilkisiela/graphql-hive/commit/682cde81092fcb3a55de7f24035be4f2f64abfb3) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Add Self-Hosting options ## 0.19.0 ### Major Changes -- [#435](https://github.com/kamilkisiela/graphql-hive/pull/435) [`a79c253`](https://github.com/kamilkisiela/graphql-hive/commit/a79c253253614e44d01fef411016d353ef8c255e) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Use ETag and If-None-Match to save bandwidth and improve performance +- [#435](https://github.com/kamilkisiela/graphql-hive/pull/435) + [`a79c253`](https://github.com/kamilkisiela/graphql-hive/commit/a79c253253614e44d01fef411016d353ef8c255e) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Use ETag and If-None-Match to save + bandwidth and improve performance ## 0.18.5 ### Patch Changes -- [#399](https://github.com/kamilkisiela/graphql-hive/pull/399) [`bd6e500`](https://github.com/kamilkisiela/graphql-hive/commit/bd6e500532ed4878a069883fadeaf3bb00e38aeb) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix the wrong cacheKey from #397 +- [#399](https://github.com/kamilkisiela/graphql-hive/pull/399) + [`bd6e500`](https://github.com/kamilkisiela/graphql-hive/commit/bd6e500532ed4878a069883fadeaf3bb00e38aeb) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix the wrong cacheKey from #397 ## 0.18.4 ### Patch Changes -- [#379](https://github.com/kamilkisiela/graphql-hive/pull/379) [`2e7c8f3`](https://github.com/kamilkisiela/graphql-hive/commit/2e7c8f3c94013f890f42ca1054287841478ba7a6) Thanks [@dimatill](https://github.com/dimatill)! - Collect input fields from variables (opt-in with `processVariables` flag) +- [#379](https://github.com/kamilkisiela/graphql-hive/pull/379) + [`2e7c8f3`](https://github.com/kamilkisiela/graphql-hive/commit/2e7c8f3c94013f890f42ca1054287841478ba7a6) + Thanks [@dimatill](https://github.com/dimatill)! - Collect input fields from variables (opt-in + with `processVariables` flag) ## 0.18.3 ### Patch Changes -- [#308](https://github.com/kamilkisiela/graphql-hive/pull/308) [`5a212f6`](https://github.com/kamilkisiela/graphql-hive/commit/5a212f61f206d7b73f8abf04667480851aa6066e) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Avoid marking the same type as used twice +- [#308](https://github.com/kamilkisiela/graphql-hive/pull/308) + [`5a212f6`](https://github.com/kamilkisiela/graphql-hive/commit/5a212f61f206d7b73f8abf04667480851aa6066e) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Avoid marking the same type as used + twice ## 0.18.2 @@ -348,7 +374,8 @@ ### Minor Changes -- 30da7e7: When disabled, run everything in dry mode (only http agent is disabled). This should help to catch errors in development. +- 30da7e7: When disabled, run everything in dry mode (only http agent is disabled). This should help + to catch errors in development. ### Patch Changes diff --git a/packages/libraries/client/README.md b/packages/libraries/client/README.md index de4029c97..bf9b444b8 100644 --- a/packages/libraries/client/README.md +++ b/packages/libraries/client/README.md @@ -1,9 +1,12 @@ # GraphQL Hive Client -[GraphQL Hive](https://graphql-hive.com) is a GraphQL schemas registry where you can host, manage and collaborate on all your GraphQL schemas and operations, compatible with all architecture: schema stitching, federation, or just a good old monolith. +[GraphQL Hive](https://graphql-hive.com) is a GraphQL schemas registry where you can host, manage +and collaborate on all your GraphQL schemas and operations, compatible with all architecture: schema +stitching, federation, or just a good old monolith. -GraphQL Hive is currently available as a hosted service to be used by all. -We take care of the heavy lifting behind the scenes be managing the registry, scaling it for your needs, to free your time to focus on the most important things at hand. +GraphQL Hive is currently available as a hosted service to be used by all. We take care of the heavy +lifting behind the scenes be managing the registry, scaling it for your needs, to free your time to +focus on the most important things at hand. ## Installation @@ -13,11 +16,13 @@ npm install @graphql-hive/client ## Basic Usage -Hive Client comes with generic client and plugins for [Envelop](https://envelop.dev) and [Apollo Server](https://github.com/apollographql/apollo-server) +Hive Client comes with generic client and plugins for [Envelop](https://envelop.dev) and +[Apollo Server](https://github.com/apollographql/apollo-server) ### With GraphQL Yoga -[GraphQL Yoga](https://www.the-guild.dev/graphql/yoga-server) is a cross-platform GraphQL sever built on top of the envelop engine. +[GraphQL Yoga](https://www.the-guild.dev/graphql/yoga-server) is a cross-platform GraphQL sever +built on top of the envelop engine. ```ts import { createYoga } from '@graphql-yoga/node' @@ -45,7 +50,9 @@ server.start() ### With Envelop -If you're not familiar with Envelop - in "short" it's a lightweight JavaScript library for wrapping GraphQL execution layer and flow, allowing developers to develop, share and collaborate on GraphQL-related plugins, while filling the missing pieces in GraphQL implementations. +If you're not familiar with Envelop - in "short" it's a lightweight JavaScript library for wrapping +GraphQL execution layer and flow, allowing developers to develop, share and collaborate on +GraphQL-related plugins, while filling the missing pieces in GraphQL implementations. Here's [more](https://github.com/dotansimha/envelop#envelop) on that topic. @@ -102,10 +109,12 @@ const server = new ApolloServer({ First you need to instantiate the Hive Client. -The `collectUsage` method accepts the same arguments as execute function of graphql-js and returns a function that expects the execution result object. +The `collectUsage` method accepts the same arguments as execute function of graphql-js and returns a +function that expects the execution result object. - `collectUsage(args)` - should be called when a GraphQL execution starts. -- `finish(result)` (function returned by `collectUsage(args)`) - has to be invoked right after execution finishes. +- `finish(result)` (function returned by `collectUsage(args)`) - has to be invoked right after + execution finishes. ```ts import express from 'express' @@ -145,14 +154,17 @@ app.post( ### Using the registry when Stitching -Stitching could be done in many ways, that's why `@graphql-hive/client` provide generic functions, not something dedicated for stitching. Unfortunately the implementation of gateway + polling is up to you. +Stitching could be done in many ways, that's why `@graphql-hive/client` provide generic functions, +not something dedicated for stitching. Unfortunately the implementation of gateway + polling is up +to you. Prerequisites: - `HIVE_CDN_ENDPOINT` - the endpoint Hive generated for you in the previous step - `HIVE_CDN_KEY` - the access key -The `createServicesFetcher` factory function returns another function that is responsible for fetching a list of services from Hive's high-availability endpoint. +The `createServicesFetcher` factory function returns another function that is responsible for +fetching a list of services from Hive's high-availability endpoint. ```ts import { createServicesFetcher } from '@graphql-hive/client' @@ -214,7 +226,9 @@ server.listen().then(({ url }) => { ### Client Info -The schema usage operation information can be enriched with meta information that will be displayed on the Hive dashboard in order to get a better understanding of the origin of an executed GraphQL operation. +The schema usage operation information can be enriched with meta information that will be displayed +on the Hive dashboard in order to get a better understanding of the origin of an executed GraphQL +operation. ### GraphQL Yoga Example @@ -307,9 +321,11 @@ const server = new ApolloServer({ ## Self-Hosting -To align the client with your own instance of GraphQL Hive, you should use `selfHosting` options in the client configuration. +To align the client with your own instance of GraphQL Hive, you should use `selfHosting` options in +the client configuration. -The example is based on GraphQL Yoga, but the same configuration applies to Apollo Server and others. +The example is based on GraphQL Yoga, but the same configuration applies to Apollo Server and +others. ```ts import { createYoga } from '@graphql-yoga/node' @@ -332,4 +348,5 @@ const server = createYoga({ server.start() ``` -> The `selfHosting` options take precedence over the deprecated `options.hosting.endpoint` and `options.usage.endpoint`. +> The `selfHosting` options take precedence over the deprecated `options.hosting.endpoint` and +> `options.usage.endpoint`. diff --git a/packages/libraries/client/package.json b/packages/libraries/client/package.json index 460dadd2c..b4ea1cbb8 100644 --- a/packages/libraries/client/package.json +++ b/packages/libraries/client/package.json @@ -1,21 +1,20 @@ { "name": "@graphql-hive/client", - "description": "A NodeJS client for GraphQL Hive", "version": "0.21.3", - "author": { - "email": "contact@the-guild.dev", - "name": "The Guild", - "url": "https://the-guild.dev" - }, + "type": "module", + "description": "A NodeJS client for GraphQL Hive", "repository": { "type": "git", "url": "kamilkisiela/graphql-hive", "directory": "packages/libraries/client" }, "homepage": "https://graphql-hive.com", + "author": { + "email": "contact@the-guild.dev", + "name": "The Guild", + "url": "https://the-guild.dev" + }, "license": "MIT", - "sideEffects": false, - "type": "module", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "exports": { @@ -36,9 +35,6 @@ "./package.json": "./package.json" }, "typings": "dist/typings/index.d.ts", - "typescript": { - "definition": "dist/typings/index.d.ts" - }, "scripts": { "build": "node scripts/update-version.mjs && bob build", "check:build": "bob check" @@ -56,15 +52,19 @@ "devDependencies": { "@apollo/federation": "0.38.1", "@envelop/types": "3.0.0", - "graphql-yoga": "3.0.0-next.12", "@types/async-retry": "1.4.5", "apollo-server-core": "3.11.1", "apollo-server-plugin-base": "3.5.3", + "graphql-yoga": "3.0.0-next.12", "nock": "13.2.4" }, "publishConfig": { "registry": "https://registry.npmjs.org", "access": "public", "directory": "dist" + }, + "sideEffects": false, + "typescript": { + "definition": "dist/typings/index.d.ts" } } diff --git a/packages/libraries/client/src/apollo.ts b/packages/libraries/client/src/apollo.ts index e882f91b0..c7c7bef60 100644 --- a/packages/libraries/client/src/apollo.ts +++ b/packages/libraries/client/src/apollo.ts @@ -1,6 +1,10 @@ import type { ApolloServerPlugin } from 'apollo-server-plugin-base'; import type { DocumentNode } from 'graphql'; -import type { HiveClient, HivePluginOptions, SupergraphSDLFetcherOptions } from './internal/types.js'; +import type { + HiveClient, + HivePluginOptions, + SupergraphSDLFetcherOptions, +} from './internal/types.js'; import { createHash } from 'crypto'; import axios from 'axios'; import { createHive } from './client.js'; @@ -59,9 +63,14 @@ export function createSupergraphSDLFetcher({ endpoint, key }: SupergraphSDLFetch }; } -export function createSupergraphManager(options: { pollIntervalInMs?: number } & SupergraphSDLFetcherOptions) { +export function createSupergraphManager( + options: { pollIntervalInMs?: number } & SupergraphSDLFetcherOptions, +) { const pollIntervalInMs = options.pollIntervalInMs ?? 30_000; - const fetchSupergraph = createSupergraphSDLFetcher({ endpoint: options.endpoint, key: options.key }); + const fetchSupergraph = createSupergraphSDLFetcher({ + endpoint: options.endpoint, + key: options.key, + }); let timer: ReturnType | null = null; return { @@ -79,7 +88,9 @@ export function createSupergraphManager(options: { pollIntervalInMs?: number } & hooks.update?.(result.supergraphSdl); } } catch (error) { - console.error(`Failed to update supergraph: ${error instanceof Error ? error.message : error}`); + console.error( + `Failed to update supergraph: ${error instanceof Error ? error.message : error}`, + ); } poll(); }, pollIntervalInMs); diff --git a/packages/libraries/client/src/client.ts b/packages/libraries/client/src/client.ts index 37696a8c1..3040daa84 100644 --- a/packages/libraries/client/src/client.ts +++ b/packages/libraries/client/src/client.ts @@ -99,7 +99,7 @@ export function createHive(options: HivePluginOptions): HiveClient { timeout: 30_000, decompress: true, responseType: 'json', - } + }, ); if (response.status >= 200 && response.status < 300) { @@ -108,10 +108,24 @@ export function createHive(options: HivePluginOptions): HiveClient { if (result.data?.tokenInfo.__typename === 'TokenInfo') { const { tokenInfo } = result.data; - const { organization, project, target, canReportSchema, canCollectUsage, canReadOperations } = tokenInfo; - const print = createPrinter([tokenInfo.token.name, organization.name, project.name, target.name]); + const { + organization, + project, + target, + canReportSchema, + canCollectUsage, + canReadOperations, + } = tokenInfo; + const print = createPrinter([ + tokenInfo.token.name, + organization.name, + project.name, + target.name, + ]); - const appUrl = options.selfHosting?.applicationUrl?.replace(/\/$/, '') ?? 'https://app.graphql-hive.com'; + const appUrl = + options.selfHosting?.applicationUrl?.replace(/\/$/, '') ?? + 'https://app.graphql-hive.com'; const organizationUrl = `${appUrl}/${organization.cleanId}`; const projectUrl = `${organizationUrl}/${project.cleanId}`; const targetUrl = `${projectUrl}/${target.cleanId}`; @@ -129,14 +143,18 @@ export function createHive(options: HivePluginOptions): HiveClient { `Can collect usage? ${print(canCollectUsage ? 'Yes' : 'No')}`, `Can read operations? ${print(canReadOperations ? 'Yes' : 'No')}`, '', - ].join('\n') + ].join('\n'), ); } else if (result.data?.tokenInfo.message) { logger.error(`[hive][info] Token not found. Reason: ${result.data?.tokenInfo.message}`); - logger.info(`[hive][info] How to create a token? https://docs.graphql-hive.com/features/tokens`); + logger.info( + `[hive][info] How to create a token? https://docs.graphql-hive.com/features/tokens`, + ); } else { logger.error(`[hive][info] ${result.errors![0].message}`); - logger.info(`[hive][info] How to create a token? https://docs.graphql-hive.com/features/tokens`); + logger.info( + `[hive][info] How to create a token? https://docs.graphql-hive.com/features/tokens`, + ); } } else { logger.error(`[hive][info] Error ${response.status}: ${response.statusText}`); diff --git a/packages/libraries/client/src/gateways.ts b/packages/libraries/client/src/gateways.ts index 7271a784a..a4c454ed1 100644 --- a/packages/libraries/client/src/gateways.ts +++ b/packages/libraries/client/src/gateways.ts @@ -86,7 +86,7 @@ export function createServicesFetcher({ endpoint, key }: ServicesFetcherOptions) .update(service.name) .digest('base64'), ...service, - })) + })), ); }; } diff --git a/packages/libraries/client/src/internal/agent.ts b/packages/libraries/client/src/internal/agent.ts index 227bc6526..8f5514692 100644 --- a/packages/libraries/client/src/internal/agent.ts +++ b/packages/libraries/client/src/internal/agent.ts @@ -60,7 +60,7 @@ export function createAgent( }; body(): Buffer | string | Promise; headers?(): Record; - } + }, ) { const options: Required = { timeout: 30_000, @@ -117,9 +117,18 @@ export function createAgent( return send({ runOnce: true, throwOnError: true }); } - async function send(sendOptions: { runOnce?: boolean; throwOnError: true }): Promise; - async function send(sendOptions: { runOnce?: boolean; throwOnError: false }): Promise; - async function send(sendOptions?: { runOnce?: boolean; throwOnError: boolean }): Promise { + async function send(sendOptions: { + runOnce?: boolean; + throwOnError: true; + }): Promise; + async function send(sendOptions: { + runOnce?: boolean; + throwOnError: false; + }): Promise; + async function send(sendOptions?: { + runOnce?: boolean; + throwOnError: boolean; + }): Promise { const runOnce = sendOptions?.runOnce ?? false; if (!data.size()) { @@ -180,7 +189,9 @@ export function createAgent( }); if (response.status < 200 || response.status >= 300) { - throw new Error(`[hive][${prefix}] Failed to send data (HTTP status ${response.status}): ${response.data}`); + throw new Error( + `[hive][${prefix}] Failed to send data (HTTP status ${response.status}): ${response.data}`, + ); } debugLog(`Sent!`); diff --git a/packages/libraries/client/src/internal/operations-store.ts b/packages/libraries/client/src/internal/operations-store.ts index f7e6d59b3..66985d6f5 100644 --- a/packages/libraries/client/src/internal/operations-store.ts +++ b/packages/libraries/client/src/internal/operations-store.ts @@ -40,7 +40,9 @@ export function createOperationsStore(pluginOptions: HivePluginOptions): Operati const load: OperationsStore['load'] = async () => { const response = await axios.post( - selfHostingOptions?.graphqlEndpoint ?? operationsStoreOptions.endpoint ?? 'https://app.graphql-hive.com/graphql', + selfHostingOptions?.graphqlEndpoint ?? + operationsStoreOptions.endpoint ?? + 'https://app.graphql-hive.com/graphql', { query, operationName: 'loadStoredOperations', @@ -51,7 +53,7 @@ export function createOperationsStore(pluginOptions: HivePluginOptions): Operati 'content-type': 'application/json', Authorization: `Bearer ${token}`, }, - } + }, ); const parsedData: { @@ -70,7 +72,7 @@ export function createOperationsStore(pluginOptions: HivePluginOptions): Operati key, parse(document, { noLocation: true, - }) + }), ); }); }; diff --git a/packages/libraries/client/src/internal/reporting.ts b/packages/libraries/client/src/internal/reporting.ts index 3a4ada08d..06abbaca9 100644 --- a/packages/libraries/client/src/internal/reporting.ts +++ b/packages/libraries/client/src/internal/reporting.ts @@ -27,14 +27,18 @@ export function createReporting(pluginOptions: HivePluginOptions): SchemaReporte logIf( typeof reportingOptions.author !== 'string' || reportingOptions.author.length === 0, '[hive][reporting] author is missing', - logger.error + logger.error, ); logIf( typeof reportingOptions.commit !== 'string' || reportingOptions.commit.length === 0, '[hive][reporting] commit is missing', - logger.error + logger.error, + ); + logIf( + typeof token !== 'string' || token.length === 0, + '[hive][reporting] token is missing', + logger.error, ); - logIf(typeof token !== 'string' || token.length === 0, '[hive][reporting] token is missing', logger.error); let currentSchema: GraphQLSchema | null = null; const agent = createAgent>( @@ -42,7 +46,9 @@ export function createReporting(pluginOptions: HivePluginOptions): SchemaReporte logger, ...(pluginOptions.agent ?? {}), endpoint: - selfHostingOptions?.graphqlEndpoint ?? reportingOptions.endpoint ?? 'https://app.graphql-hive.com/graphql', + selfHostingOptions?.graphqlEndpoint ?? + reportingOptions.endpoint ?? + 'https://app.graphql-hive.com/graphql', token: token, enabled: pluginOptions.enabled, debug: pluginOptions.debug, @@ -83,7 +89,7 @@ export function createReporting(pluginOptions: HivePluginOptions): SchemaReporte }, }); }, - } + }, ); return { @@ -113,7 +119,9 @@ export function createReporting(pluginOptions: HivePluginOptions): SchemaReporte throw new Error('Service url is not defined'); } case 'SchemaPublishError': { - logger.info(`[hive][reporting] Published schema (forced with ${data.errors.total} errors)`); + logger.info( + `[hive][reporting] Published schema (forced with ${data.errors.total} errors)`, + ); data.errors.nodes.slice(0, 5).forEach(error => { logger.info(` - ${error.message}`); }); @@ -124,7 +132,7 @@ export function createReporting(pluginOptions: HivePluginOptions): SchemaReporte logger.error( `[hive][reporting] Failed to report schema: ${ error instanceof Error && 'message' in error ? error.message : error - }` + }`, ); } }, @@ -226,6 +234,8 @@ function printSchemaWithDirectives(schema: GraphQLSchema) { async function printToSDL(schema: GraphQLSchema) { return stripIgnoredCharacters( - isFederatedSchema(schema) ? await extractFederationServiceSDL(schema) : printSchemaWithDirectives(schema) + isFederatedSchema(schema) + ? await extractFederationServiceSDL(schema) + : printSchemaWithDirectives(schema), ); } diff --git a/packages/libraries/client/src/internal/types.ts b/packages/libraries/client/src/internal/types.ts index 239fa40b2..9ef86f831 100644 --- a/packages/libraries/client/src/internal/types.ts +++ b/packages/libraries/client/src/internal/types.ts @@ -13,7 +13,9 @@ export interface HiveClient { export type AsyncIterableIteratorOrValue = AsyncIterableIterator | T; -export type CollectUsageCallback = (result: AsyncIterableIteratorOrValue) => void; +export type CollectUsageCallback = ( + result: AsyncIterableIteratorOrValue, +) => void; export interface ClientInfo { name: string; version: string; diff --git a/packages/libraries/client/src/internal/usage.ts b/packages/libraries/client/src/internal/usage.ts index da536a08d..828e295ed 100644 --- a/packages/libraries/client/src/internal/usage.ts +++ b/packages/libraries/client/src/internal/usage.ts @@ -28,8 +28,20 @@ import { normalizeOperation } from '@graphql-hive/core'; import { createAgent } from './agent.js'; import { randomSampling } from './sampling.js'; import { version } from '../version.js'; -import { cache, cacheDocumentKey, measureDuration, memo, isAsyncIterableIterator, logIf } from './utils.js'; -import type { HivePluginOptions, HiveUsagePluginOptions, CollectUsageCallback, ClientInfo } from './types.js'; +import { + cache, + cacheDocumentKey, + measureDuration, + memo, + isAsyncIterableIterator, + logIf, +} from './utils.js'; +import type { + HivePluginOptions, + HiveUsagePluginOptions, + CollectUsageCallback, + ClientInfo, +} from './types.js'; interface UsageCollector { collect(args: ExecutionArgs): CollectUsageCallback; @@ -51,7 +63,8 @@ export function createUsage(pluginOptions: HivePluginOptions): UsageCollector { map: {}, operations: [], }; - const options = typeof pluginOptions.usage === 'boolean' ? ({} as HiveUsagePluginOptions) : pluginOptions.usage; + const options = + typeof pluginOptions.usage === 'boolean' ? ({} as HiveUsagePluginOptions) : pluginOptions.usage; const selfHostingOptions = pluginOptions.selfHosting; const logger = pluginOptions.agent?.logger ?? console; const collector = memo(createCollector, arg => arg.schema); @@ -62,7 +75,10 @@ export function createUsage(pluginOptions: HivePluginOptions): UsageCollector { ...(pluginOptions.agent ?? { maxSize: 1500, }), - endpoint: selfHostingOptions?.usageEndpoint ?? options.endpoint ?? 'https://app.graphql-hive.com/usage', + endpoint: + selfHostingOptions?.usageEndpoint ?? + options.endpoint ?? + 'https://app.graphql-hive.com/usage', token: pluginOptions.token, enabled: pluginOptions.enabled, debug: pluginOptions.debug, @@ -116,13 +132,13 @@ export function createUsage(pluginOptions: HivePluginOptions): UsageCollector { body() { return JSON.stringify(report); }, - } + }, ); logIf( typeof pluginOptions.token !== 'string' || pluginOptions.token.length === 0, '[hive][usage] token is missing', - logger.error + logger.error, ); const shouldInclude = randomSampling(options.sampleRate ?? 1.0); @@ -141,7 +157,7 @@ export function createUsage(pluginOptions: HivePluginOptions): UsageCollector { } const rootOperation = args.document.definitions.find( - o => o.kind === Kind.OPERATION_DEFINITION + o => o.kind === Kind.OPERATION_DEFINITION, ) as OperationDefinitionNode; const document = args.document; const operationName = args.operationName || rootOperation.name?.value || 'anonymous'; @@ -175,7 +191,8 @@ export function createUsage(pluginOptions: HivePluginOptions): UsageCollector { }, // TODO: operationHash is ready to accept hashes of persisted operations client: - typeof args.contextValue !== 'undefined' && typeof options.clientInfo !== 'undefined' + typeof args.contextValue !== 'undefined' && + typeof options.clientInfo !== 'undefined' ? options.clientInfo(args.contextValue) : null, }); @@ -210,7 +227,7 @@ export function createCollector({ doc: DocumentNode, variables: { [key: string]: unknown; - } | null + } | null, ): CacheResult { const entries = new Set(); const collected_entire_named_types = new Set(); @@ -377,7 +394,7 @@ export function createCollector({ collectNode(node); collectInputType(parentInputTypeName, node.name.value); }, - }) + }), ); for (const inputTypeName in collectedInputTypes) { @@ -407,7 +424,7 @@ export function createCollector({ function cacheKey(doc, variables) { return cacheDocumentKey(doc, processVariables === true ? variables : null); }, - LRU(max, ttl) + LRU(max, ttl), ); } @@ -426,7 +443,10 @@ function unwrapType(type: GraphQLType): GraphQLNamedType { return type; } -type GraphQLNamedInputType = Exclude; +type GraphQLNamedInputType = Exclude< + GraphQLNamedType, + GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType +>; type GraphQLNamedOutputType = Exclude; export interface Report { diff --git a/packages/libraries/client/src/internal/utils.ts b/packages/libraries/client/src/internal/utils.ts index 974ce2d61..6f05390e7 100644 --- a/packages/libraries/client/src/internal/utils.ts +++ b/packages/libraries/client/src/internal/utils.ts @@ -1,7 +1,9 @@ import { createHash } from 'crypto'; import type { HiveClient, HivePluginOptions, AsyncIterableIteratorOrValue } from './types.js'; -export function isAsyncIterableIterator(value: AsyncIterableIteratorOrValue): value is AsyncIterableIterator { +export function isAsyncIterableIterator( + value: AsyncIterableIteratorOrValue, +): value is AsyncIterableIterator { return typeof (value as any)?.[Symbol.asyncIterator] === 'function'; } @@ -29,7 +31,7 @@ export function cache( has(key: K): boolean; set(key: K, value: R): void; get(key: K): R | undefined; - } + }, ) { return (arg: A, arg2: V) => { const key = cacheKeyFn(arg, arg2); @@ -68,7 +70,7 @@ export function cacheDocumentKey(doc: T, variables: V | null) { } return ''; - }) + }), ); } @@ -102,11 +104,15 @@ export function addProperty(key: K, value: undefined | null export function addProperty( key: K, value: V, - obj: T + obj: T, ): T & { [k in K]: V; }; -export function addProperty(key: K, value: V | undefined | null, obj: T): any { +export function addProperty( + key: K, + value: V | undefined | null, + obj: T, +): any { if (value === null || typeof value === 'undefined') { return obj; } @@ -117,7 +123,9 @@ export function addProperty(key: K, value: V | undefined }; } -export function isHiveClient(clientOrOptions: HiveClient | HivePluginOptions): clientOrOptions is HiveClient { +export function isHiveClient( + clientOrOptions: HiveClient | HivePluginOptions, +): clientOrOptions is HiveClient { return 'operationsStore' in clientOrOptions; } diff --git a/packages/libraries/client/tests/info.spec.ts b/packages/libraries/client/tests/info.spec.ts index 7d788f41b..d6f684904 100644 --- a/packages/libraries/client/tests/info.spec.ts +++ b/packages/libraries/client/tests/info.spec.ts @@ -88,13 +88,15 @@ test('should use selfHosting.graphqlEndpoint if provided', async () => { expect(logger.info).toHaveBeenCalledWith(expect.stringContaining(`[hive][info] Token details`)); expect(logger.info).toHaveBeenCalledWith(expect.stringMatching(/Token name: \s+ My Token/)); expect(logger.info).toHaveBeenCalledWith( - expect.stringMatching(/Organization: \s+ Org \s+ http:\/\/localhost\/org-id/) + expect.stringMatching(/Organization: \s+ Org \s+ http:\/\/localhost\/org-id/), ); expect(logger.info).toHaveBeenCalledWith( - expect.stringMatching(/Project: \s+ Project \s+ http:\/\/localhost\/org-id\/project-id/) + expect.stringMatching(/Project: \s+ Project \s+ http:\/\/localhost\/org-id\/project-id/), ); expect(logger.info).toHaveBeenCalledWith( - expect.stringMatching(/Target: \s+ Target \s+ http:\/\/localhost\/org-id\/project-id\/target-id/) + expect.stringMatching( + /Target: \s+ Target \s+ http:\/\/localhost\/org-id\/project-id\/target-id/, + ), ); expect(logger.info).toHaveBeenCalledWith(expect.stringMatching(/Can report schema\? \s+ Yes/)); expect(logger.info).toHaveBeenCalledWith(expect.stringMatching(/Can collect usage\? \s+ Yes/)); diff --git a/packages/libraries/client/tests/reporting.spec.ts b/packages/libraries/client/tests/reporting.spec.ts index e988994b1..3d3bc97af 100644 --- a/packages/libraries/client/tests/reporting.spec.ts +++ b/packages/libraries/client/tests/reporting.spec.ts @@ -51,11 +51,15 @@ test('should not leak the exception', async () => { await hive.dispose(); expect(logger.info).toHaveBeenCalledWith('[hive][reporting] Sending (queue 1) (attempt 1)'); - expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('[hive][reporting] Attempt 1 failed:')); + expect(logger.info).toHaveBeenCalledWith( + expect.stringContaining('[hive][reporting] Attempt 1 failed:'), + ); expect(logger.info).toHaveBeenCalledWith('[hive][reporting] Sending (queue 1) (attempt 2)'); expect(logger.error).toHaveBeenCalledTimes(1); expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining(`[hive][reporting] Failed to report schema: connect ECONNREFUSED 127.0.0.1:55404`) + expect.stringContaining( + `[hive][reporting] Failed to report schema: connect ECONNREFUSED 127.0.0.1:55404`, + ), ); }); @@ -426,7 +430,7 @@ test('should send original schema of a federated service', async () => { type Query { bar: String } - `) + `), ), }); @@ -498,7 +502,9 @@ test('should display SchemaPublishMissingServiceError', async () => { http.done(); expect(logger.info).toHaveBeenCalledWith('[hive][reporting] Sending (queue 1) (attempt 1)'); - expect(logger.error).toHaveBeenCalledWith(`[hive][reporting] Failed to report schema: Service name is not defined`); + expect(logger.error).toHaveBeenCalledWith( + `[hive][reporting] Failed to report schema: Service name is not defined`, + ); }); test('should display SchemaPublishMissingUrlError', async () => { @@ -558,5 +564,7 @@ test('should display SchemaPublishMissingUrlError', async () => { http.done(); expect(logger.info).toHaveBeenCalledWith('[hive][reporting] Sending (queue 1) (attempt 1)'); - expect(logger.error).toHaveBeenCalledWith(`[hive][reporting] Failed to report schema: Service url is not defined`); + expect(logger.error).toHaveBeenCalledWith( + `[hive][reporting] Failed to report schema: Service url is not defined`, + ); }); diff --git a/packages/libraries/client/tests/usage-collector.spec.ts b/packages/libraries/client/tests/usage-collector.spec.ts index 2f414225d..b2464d599 100644 --- a/packages/libraries/client/tests/usage-collector.spec.ts +++ b/packages/libraries/client/tests/usage-collector.spec.ts @@ -121,7 +121,7 @@ test('collect enums and scalars as inputs', async () => { } } `), - {} + {}, ).value; expect(info.fields).toContain(`Int`); @@ -144,7 +144,7 @@ test('collect enum values from object fields', async () => { } } `), - {} + {}, ).value; expect(info.fields).toContain(`Int`); @@ -167,7 +167,7 @@ test('collect enum values from arguments', async () => { } } `), - {} + {}, ).value; expect(info.fields).toContain(`ProjectType.FEDERATION`); @@ -189,7 +189,7 @@ test('collect arguments', async () => { } } `), - {} + {}, ).value; expect(info.fields).toContain(`Query.projects.filter`); @@ -208,7 +208,7 @@ test('collect used-only input fields', async () => { } } `), - {} + {}, ).value; expect(info.fields).toContain(`FilterInput.pagination`); @@ -230,7 +230,7 @@ test('collect all input fields when `processVariables` has not been passed and i } } `), - {} + {}, ).value; expect(info.fields).toContain(`FilterInput.pagination`); @@ -327,7 +327,7 @@ test('(processVariables: true) collect used-only input fields', async () => { limit: 1, }, type: 'STITCHING', - } + }, ).value; expect(info.fields).toContain(`FilterInput.pagination`); @@ -352,7 +352,7 @@ test('(processVariables: true) should collect input object without fields when c `), { type: 'STITCHING', - } + }, ).value; expect(info.fields).toContain(`FilterInput.type`); @@ -391,7 +391,7 @@ test('(processVariables: true) collect used-only input type fields from an array limit: 10, }, }, - } + }, ).value; expect(info.fields).toContain(`FilterInput.pagination`); diff --git a/packages/libraries/client/tests/usage.spec.ts b/packages/libraries/client/tests/usage.spec.ts index eae2bb49b..08b0c7df0 100644 --- a/packages/libraries/client/tests/usage.spec.ts +++ b/packages/libraries/client/tests/usage.spec.ts @@ -319,10 +319,14 @@ test('should not leak the exception', async () => { await hive.dispose(); expect(logger.info).toHaveBeenCalledWith(`[hive][usage] Sending (queue 1) (attempt 1)`); - expect(logger.info).toHaveBeenCalledWith(expect.stringContaining(`[hive][usage] Attempt 1 failed:`)); + expect(logger.info).toHaveBeenCalledWith( + expect.stringContaining(`[hive][usage] Attempt 1 failed:`), + ); expect(logger.info).toHaveBeenCalledWith(`[hive][usage] Sending (queue 1) (attempt 2)`); expect(logger.error).toHaveBeenCalledTimes(1); - expect(logger.error).toHaveBeenCalledWith(expect.stringContaining(`[hive][usage] Failed to send data`)); + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining(`[hive][usage] Failed to send data`), + ); }); test('sendImmediately should not stop the schedule', async () => { diff --git a/packages/libraries/core/CHANGELOG.md b/packages/libraries/core/CHANGELOG.md index b22729473..a0089ac6f 100644 --- a/packages/libraries/core/CHANGELOG.md +++ b/packages/libraries/core/CHANGELOG.md @@ -4,7 +4,9 @@ ### Patch Changes -- [#668](https://github.com/kamilkisiela/graphql-hive/pull/668) [`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix ESM/CJS issue +- [#668](https://github.com/kamilkisiela/graphql-hive/pull/668) + [`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix ESM/CJS issue ## 0.2.2 diff --git a/packages/libraries/core/package.json b/packages/libraries/core/package.json index cc9ce8194..299bfd1b4 100644 --- a/packages/libraries/core/package.json +++ b/packages/libraries/core/package.json @@ -1,20 +1,19 @@ { "name": "@graphql-hive/core", "version": "0.2.3", - "author": { - "email": "contact@the-guild.dev", - "name": "The Guild", - "url": "https://the-guild.dev" - }, + "type": "module", "repository": { "type": "git", "url": "kamilkisiela/graphql-hive", "directory": "packages/libraries/core" }, "homepage": "https://graphql-hive.com", + "author": { + "email": "contact@the-guild.dev", + "name": "The Guild", + "url": "https://the-guild.dev" + }, "license": "MIT", - "type": "module", - "sideEffects": false, "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "exports": { @@ -35,14 +34,6 @@ "./package.json": "./package.json" }, "typings": "dist/typings/index.d.ts", - "typescript": { - "definition": "dist/typings/index.d.ts" - }, - "publishConfig": { - "registry": "https://registry.npmjs.org", - "access": "public", - "directory": "dist" - }, "scripts": { "build": "bob build", "check:build": "bob check" @@ -56,5 +47,14 @@ "devDependencies": { "@types/lodash.sortby": "4.7.7", "tslib": "2.4.1" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org", + "access": "public", + "directory": "dist" + }, + "sideEffects": false, + "typescript": { + "definition": "dist/typings/index.d.ts" } } diff --git a/packages/libraries/core/src/normalize/operation.ts b/packages/libraries/core/src/normalize/operation.ts index e7f29b474..9ad7836a0 100644 --- a/packages/libraries/core/src/normalize/operation.ts +++ b/packages/libraries/core/src/normalize/operation.ts @@ -27,76 +27,86 @@ export function normalizeOperation({ }): string { return stripIgnoredCharacters( print( - visit(dropUnusedDefinitions(document, operationName ?? document.definitions.find(isOperationDef)?.name?.value), { - // hide literals - IntValue(node) { - return hideLiterals ? { ...node, value: '0' } : node; + visit( + dropUnusedDefinitions( + document, + operationName ?? document.definitions.find(isOperationDef)?.name?.value, + ), + { + // hide literals + IntValue(node) { + return hideLiterals ? { ...node, value: '0' } : node; + }, + FloatValue(node) { + return hideLiterals ? { ...node, value: '0' } : node; + }, + StringValue(node) { + return hideLiterals ? { ...node, value: '', block: false } : node; + }, + Field(node) { + return { + ...node, + // remove aliases + alias: removeAliases ? undefined : node.alias, + // sort arguments + arguments: sortNodes(node.arguments), + }; + }, + Document(node) { + return { + ...node, + definitions: sortNodes(node.definitions), + }; + }, + OperationDefinition(node) { + return { + ...node, + variableDefinitions: sortNodes(node.variableDefinitions), + }; + }, + SelectionSet(node) { + return { + ...node, + selections: sortNodes(node.selections), + }; + }, + FragmentSpread(node) { + return { + ...node, + directives: sortNodes(node.directives), + }; + }, + InlineFragment(node) { + return { + ...node, + directives: sortNodes(node.directives), + }; + }, + FragmentDefinition(node) { + return { + ...node, + directives: sortNodes(node.directives), + variableDefinitions: sortNodes(node.variableDefinitions), + }; + }, + Directive(node) { + return { ...node, arguments: sortNodes(node.arguments) }; + }, }, - FloatValue(node) { - return hideLiterals ? { ...node, value: '0' } : node; - }, - StringValue(node) { - return hideLiterals ? { ...node, value: '', block: false } : node; - }, - Field(node) { - return { - ...node, - // remove aliases - alias: removeAliases ? undefined : node.alias, - // sort arguments - arguments: sortNodes(node.arguments), - }; - }, - Document(node) { - return { - ...node, - definitions: sortNodes(node.definitions), - }; - }, - OperationDefinition(node) { - return { - ...node, - variableDefinitions: sortNodes(node.variableDefinitions), - }; - }, - SelectionSet(node) { - return { - ...node, - selections: sortNodes(node.selections), - }; - }, - FragmentSpread(node) { - return { - ...node, - directives: sortNodes(node.directives), - }; - }, - InlineFragment(node) { - return { - ...node, - directives: sortNodes(node.directives), - }; - }, - FragmentDefinition(node) { - return { - ...node, - directives: sortNodes(node.directives), - variableDefinitions: sortNodes(node.variableDefinitions), - }; - }, - Directive(node) { - return { ...node, arguments: sortNodes(node.arguments) }; - }, - }) - ) + ), + ), ); } function sortNodes(nodes: readonly DefinitionNode[]): readonly DefinitionNode[]; function sortNodes(nodes: readonly SelectionNode[]): readonly SelectionNode[]; function sortNodes(nodes: readonly ArgumentNode[] | undefined): readonly ArgumentNode[] | undefined; -function sortNodes(nodes: readonly VariableDefinitionNode[] | undefined): readonly VariableDefinitionNode[] | undefined; -function sortNodes(nodes: readonly DirectiveNode[] | undefined): readonly DirectiveNode[] | undefined; +function sortNodes( + nodes: readonly VariableDefinitionNode[] | undefined, +): readonly VariableDefinitionNode[] | undefined; +function sortNodes( + nodes: readonly DirectiveNode[] | undefined, +): readonly DirectiveNode[] | undefined; function sortNodes(nodes: readonly any[] | undefined): readonly any[] | undefined { if (nodes) { if (nodes.length === 0) { @@ -115,7 +125,9 @@ function sortNodes(nodes: readonly any[] | undefined): readonly any[] | undefine return sortBy(nodes, 'name.value'); } - if (isOfKindList(nodes, [Kind.FIELD, Kind.FRAGMENT_SPREAD, Kind.INLINE_FRAGMENT])) { + if ( + isOfKindList(nodes, [Kind.FIELD, Kind.FRAGMENT_SPREAD, Kind.INLINE_FRAGMENT]) + ) { return sortBy(nodes, 'kind', 'name.value'); } diff --git a/packages/libraries/external-composition/README.md b/packages/libraries/external-composition/README.md index 174a9a8af..c851dd0fa 100644 --- a/packages/libraries/external-composition/README.md +++ b/packages/libraries/external-composition/README.md @@ -1,5 +1,9 @@ # GraphQL Hive - external composition -[GraphQL Hive](https://graphql-hive.com) is a GraphQL schemas registry where you can host, manage and collaborate on all your GraphQL schemas and operations, compatible with all architecture: schema stitching, federation, or just a good old monolith. +[GraphQL Hive](https://graphql-hive.com) is a GraphQL schemas registry where you can host, manage +and collaborate on all your GraphQL schemas and operations, compatible with all architecture: schema +stitching, federation, or just a good old monolith. -Read the ["External schema composition"](https://docs.graphql-hive.com/features/external-schema-composition) to learn more. +Read the +["External schema composition"](https://docs.graphql-hive.com/features/external-schema-composition) +to learn more. diff --git a/packages/libraries/external-composition/example.mjs b/packages/libraries/external-composition/example.mjs index 0fa604c09..bc01061b3 100644 --- a/packages/libraries/external-composition/example.mjs +++ b/packages/libraries/external-composition/example.mjs @@ -32,7 +32,7 @@ const composeFederation = compose(services => { name: service.name, url: service.url, }; - }) + }), ); if (compositionHasErrors(result)) { diff --git a/packages/libraries/external-composition/package.json b/packages/libraries/external-composition/package.json index 8a718abea..cc05178f9 100644 --- a/packages/libraries/external-composition/package.json +++ b/packages/libraries/external-composition/package.json @@ -1,27 +1,22 @@ { "name": "@graphql-hive/external-composition", - "description": "Compose schemas outside GraphQL Hive", "version": "0.0.1", - "author": { - "email": "contact@the-guild.dev", - "name": "The Guild", - "url": "https://the-guild.dev" - }, + "type": "module", + "description": "Compose schemas outside GraphQL Hive", "repository": { "type": "git", "url": "kamilkisiela/graphql-hive", "directory": "packages/libraries/external-composition" }, "homepage": "https://graphql-hive.com", + "author": { + "email": "contact@the-guild.dev", + "name": "The Guild", + "url": "https://the-guild.dev" + }, "license": "MIT", - "type": "module", - "sideEffects": false, "main": "dist/cjs/index.js", "module": "dist/esm/index.js", - "typings": "dist/typings/index.d.ts", - "typescript": { - "definition": "dist/typings/index.d.ts" - }, "exports": { ".": { "require": { @@ -52,6 +47,7 @@ } } }, + "typings": "dist/typings/index.d.ts", "scripts": { "build": "bob build", "build-local": "pnpm build && node build-example.mjs", @@ -62,13 +58,17 @@ }, "devDependencies": { "@apollo/federation": "0.38.1", - "graphql": "16.5.0", "esbuild": "0.14.39", - "fastify": "3.29.4" + "fastify": "3.29.4", + "graphql": "16.5.0" }, "publishConfig": { "registry": "https://registry.npmjs.org", "access": "public", "directory": "dist" + }, + "sideEffects": false, + "typescript": { + "definition": "dist/typings/index.d.ts" } } diff --git a/packages/services/api/package.json b/packages/services/api/package.json index faf7e104e..623ed3e09 100644 --- a/packages/services/api/package.json +++ b/packages/services/api/package.json @@ -1,17 +1,17 @@ { "name": "@hive/api", - "type": "module", - "private": true, "version": "0.0.1", + "type": "module", "license": "MIT", + "private": true, + "engines": { + "node": ">=12" + }, "peerDependencies": { "graphql": "^16.0.0", "reflect-metadata": "^0.1.0" }, "dependencies": { - "graphql-yoga": "3.0.0-next.12", - "@whatwg-node/fetch": "0.4.7", - "@trpc/client": "9.23.2", "@graphql-inspector/core": "3.2.0", "@graphql-tools/load-files": "6.6.1", "@graphql-tools/schema": "9.0.9", @@ -20,44 +20,44 @@ "@sentry/types": "7.20.1", "@slack/web-api": "6.4.0", "@theguild/buddy": "0.1.0", - "agentkeepalive": "4.2.1", + "@trpc/client": "9.23.2", + "@whatwg-node/fetch": "0.4.7", "abort-controller": "3.0.0", + "agentkeepalive": "4.2.1", "dataloader": "2.0.0", "date-fns": "2.25.0", "got": "12.5.3", "graphql-modules": "2.0.0", "graphql-parse-resolve-info": "4.12.3", "graphql-scalars": "1.12.0", + "graphql-yoga": "3.0.0-next.12", "human-id": "2.0.1", "ioredis": "4.28.5", - "jwks-rsa": "2.0.5", "jsonwebtoken": "8.5.1", + "jwks-rsa": "2.0.5", "lodash": "4.17.21", "lru-cache": "7.9.0", "ms": "2.1.3", - "param-case": "3.0.4", "p-retry": "5.1.1", "p-timeout": "5.0.2", + "param-case": "3.0.4", + "supertokens-node": "12.0.5", "uuid": "8.3.2", - "zod": "3.15.1", - "supertokens-node": "12.0.5" + "zod": "3.15.1" }, "devDependencies": { + "@graphql-hive/core": "0.2.3", "@hive/emails": "workspace:*", "@hive/schema": "workspace:*", "@hive/tokens": "workspace:*", "@hive/webhooks": "workspace:*", - "@graphql-hive/core": "0.2.3", "@types/ioredis": "4.28.10", + "@types/ioredis-mock": "5.6.0", "@types/lodash": "4.14.182", "@types/lru-cache": "7.6.1", "@types/ms": "0.7.31", - "@types/ioredis-mock": "5.6.0", "@types/uuid": "8.3.4", "ioredis-mock": "7.4.0", "tslib": "2.4.1" - }, - "engines": { - "node": ">=12" } } diff --git a/packages/services/api/src/create.ts b/packages/services/api/src/create.ts index bbd5fc64f..9e78fca60 100644 --- a/packages/services/api/src/create.ts +++ b/packages/services/api/src/create.ts @@ -28,7 +28,10 @@ import { tokenModule } from './modules/token'; import { feedbackModule } from './modules/feedback'; import { TokensConfig, TOKENS_CONFIG } from './modules/token/providers/tokens'; import { WebhooksConfig, WEBHOOKS_CONFIG } from './modules/alerts/providers/tokens'; -import { SchemaServiceConfig, SCHEMA_SERVICE_CONFIG } from './modules/schema/providers/orchestrators/tokens'; +import { + SchemaServiceConfig, + SCHEMA_SERVICE_CONFIG, +} from './modules/schema/providers/orchestrators/tokens'; import { provideSchemaModuleConfig, SchemaModuleConfig } from './modules/schema/providers/config'; import { CDN_CONFIG, CDNConfig } from './modules/cdn/providers/tokens'; import { cdnModule } from './modules/cdn'; @@ -40,7 +43,10 @@ import { USAGE_ESTIMATION_SERVICE_CONFIG, } from './modules/usage-estimation/providers/tokens'; import { rateLimitModule } from './modules/rate-limit'; -import { RateLimitServiceConfig, RATE_LIMIT_SERVICE_CONFIG } from './modules/rate-limit/providers/tokens'; +import { + RateLimitServiceConfig, + RATE_LIMIT_SERVICE_CONFIG, +} from './modules/rate-limit/providers/tokens'; import { BillingConfig, BILLING_CONFIG } from './modules/billing/providers/tokens'; import { billingModule } from './modules/billing'; import { OIDC_INTEGRATIONS_ENABLED } from './modules/oidc-integrations/providers/tokens'; diff --git a/packages/services/api/src/index.ts b/packages/services/api/src/index.ts index a4e5e8be4..39642da8d 100644 --- a/packages/services/api/src/index.ts +++ b/packages/services/api/src/index.ts @@ -34,4 +34,8 @@ export { reservedOrganizationNames, } from './modules/organization/providers/organization-config'; export { CryptoProvider } from './modules/shared/providers/crypto'; -export { OrganizationAccessScope, ProjectAccessScope, TargetAccessScope } from './__generated__/types'; +export { + OrganizationAccessScope, + ProjectAccessScope, + TargetAccessScope, +} from './__generated__/types'; diff --git a/packages/services/api/src/modules/activity/providers/activity-manager.ts b/packages/services/api/src/modules/activity/providers/activity-manager.ts index 837fc87ad..7b608c498 100644 --- a/packages/services/api/src/modules/activity/providers/activity-manager.ts +++ b/packages/services/api/src/modules/activity/providers/activity-manager.ts @@ -4,7 +4,12 @@ import { AuthManager } from '../../auth/providers/auth-manager'; import { OrganizationAccessScope } from '../../auth/providers/organization-access'; import { ProjectAccessScope } from '../../auth/providers/project-access'; import { Logger } from '../../shared/providers/logger'; -import { Storage, OrganizationSelector, ProjectSelector, TargetSelector } from '../../shared/providers/storage'; +import { + Storage, + OrganizationSelector, + ProjectSelector, + TargetSelector, +} from '../../shared/providers/storage'; import { Activity } from './activities'; interface PaginationSelector { @@ -46,7 +51,7 @@ export class ActivityManager { } public async getByOrganization( - selector: OrganizationSelector & PaginationSelector + selector: OrganizationSelector & PaginationSelector, ): Promise { await this.authManager.ensureOrganizationAccess({ ...selector, @@ -55,7 +60,9 @@ export class ActivityManager { return this.storage.getActivities(selector); } - public async getByProject(selector: ProjectSelector & PaginationSelector): Promise { + public async getByProject( + selector: ProjectSelector & PaginationSelector, + ): Promise { await this.authManager.ensureProjectAccess({ ...selector, scope: ProjectAccessScope.READ, @@ -63,7 +70,9 @@ export class ActivityManager { return this.storage.getActivities(selector); } - public async getByTarget(selector: TargetSelector & PaginationSelector): Promise { + public async getByTarget( + selector: TargetSelector & PaginationSelector, + ): Promise { await this.authManager.ensureProjectAccess({ ...selector, scope: ProjectAccessScope.READ, diff --git a/packages/services/api/src/modules/admin/providers/admin-manager.ts b/packages/services/api/src/modules/admin/providers/admin-manager.ts index 7907382f6..a150b580f 100644 --- a/packages/services/api/src/modules/admin/providers/admin-manager.ts +++ b/packages/services/api/src/modules/admin/providers/admin-manager.ts @@ -19,7 +19,7 @@ export class AdminManager { logger: Logger, private storage: Storage, private authManager: AuthManager, - private operationsReader: OperationsReader + private operationsReader: OperationsReader, ) { this.logger = logger.child({ source: 'AdminManager' }); } @@ -55,7 +55,10 @@ export class AdminManager { @atomic((arg: { daysLimit: number }) => arg.daysLimit + '') async countOperationsPerOrganization({ daysLimit }: { daysLimit: number }) { - this.logger.info('Counting collected operations per organization (admin, daysLimit=%s)', daysLimit); + this.logger.info( + 'Counting collected operations per organization (admin, daysLimit=%s)', + daysLimit, + ); const user = await this.authManager.getCurrentUser(); if (user.isAdmin) { @@ -65,7 +68,9 @@ export class AdminManager { }); const organizationCountMap = new Map(); - const targetOrganizationMap = new Map(pairs.map(p => [p.target, p.organization])); + const targetOrganizationMap = new Map( + pairs.map(p => [p.target, p.organization]), + ); for (const op of operations) { const organizationId = targetOrganizationMap.get(op.target); diff --git a/packages/services/api/src/modules/alerts/providers/adapters/common.ts b/packages/services/api/src/modules/alerts/providers/adapters/common.ts index c90986490..7adb0a519 100644 --- a/packages/services/api/src/modules/alerts/providers/adapters/common.ts +++ b/packages/services/api/src/modules/alerts/providers/adapters/common.ts @@ -1,5 +1,12 @@ import type * as Types from '../../../../__generated__/types'; -import { Alert, AlertChannel, Organization, Project, Target, SchemaVersion } from '../../../../shared/entities'; +import { + Alert, + AlertChannel, + Organization, + Project, + Target, + SchemaVersion, +} from '../../../../shared/entities'; export interface SchemaChangeNotificationInput { event: { diff --git a/packages/services/api/src/modules/alerts/providers/adapters/slack.ts b/packages/services/api/src/modules/alerts/providers/adapters/slack.ts index c5e9f99ee..8f8a08901 100644 --- a/packages/services/api/src/modules/alerts/providers/adapters/slack.ts +++ b/packages/services/api/src/modules/alerts/providers/adapters/slack.ts @@ -27,7 +27,7 @@ export class SlackCommunicationAdapter implements CommunicationAdapter { `Sending Schema Change Notifications over Slack (organization=%s, project=%s, target=%s)`, input.event.organization.id, input.event.project.id, - input.event.target.id + input.event.target.id, ); if (!input.integrations.slack.token) { @@ -65,7 +65,7 @@ export class SlackCommunicationAdapter implements CommunicationAdapter { channel: input.channel.slackChannel!, text: `:bee: Hi, I found *${totalChanges} ${this.pluralize( 'change', - totalChanges + totalChanges, )}* in project ${projectLink}, target ${targetLink} (${viewLink}):`, mrkdwn: true, attachments: createAttachments(input.event.changes), @@ -82,7 +82,7 @@ export class SlackCommunicationAdapter implements CommunicationAdapter { this.logger.debug( `Sending Channel Confirmation over Slack (organization=%s, project=%s)`, input.event.organization.id, - input.event.project.id + input.event.project.id, ); const token = input.integrations.slack.token; @@ -93,7 +93,9 @@ export class SlackCommunicationAdapter implements CommunicationAdapter { } const actionMessage = - input.event.kind === 'created' ? `I will send here notifications` : `I will no longer send here notifications`; + input.event.kind === 'created' + ? `I will send here notifications` + : `I will no longer send here notifications`; try { const projectLink = this.createLink({ @@ -104,9 +106,10 @@ export class SlackCommunicationAdapter implements CommunicationAdapter { const client = new WebClient(token); await client.chat.postMessage({ channel: input.channel.slackChannel!, - text: [`:wave: Hi! I'm the notification :bee:.`, `${actionMessage} about your ${projectLink} project.`].join( - '\n' - ), + text: [ + `:wave: Hi! I'm the notification :bee:.`, + `${actionMessage} about your ${projectLink} project.`, + ].join('\n'), }); } catch (error) { this.logger.error(`Failed to send Slack notification`, error); @@ -131,7 +134,7 @@ function createAttachments(changes: readonly Types.SchemaChange[]) { color: '#E74C3B', title: 'Breaking changes', changes: breakingChanges, - }) + }), ); } @@ -141,7 +144,7 @@ function createAttachments(changes: readonly Types.SchemaChange[]) { color: '#F0C418', title: 'Dangerous changes', changes: dangerousChanges, - }) + }), ); } @@ -151,7 +154,7 @@ function createAttachments(changes: readonly Types.SchemaChange[]) { color: '#23B99A', title: 'Safe changes', changes: safeChanges, - }) + }), ); } diff --git a/packages/services/api/src/modules/alerts/providers/adapters/webhook.ts b/packages/services/api/src/modules/alerts/providers/adapters/webhook.ts index 4b7cd5b33..736aacc8f 100644 --- a/packages/services/api/src/modules/alerts/providers/adapters/webhook.ts +++ b/packages/services/api/src/modules/alerts/providers/adapters/webhook.ts @@ -19,7 +19,7 @@ export class WebhookCommunicationAdapter implements CommunicationAdapter { logger: Logger, private http: HttpClient, @Inject(WEBHOOKS_CONFIG) private config: WebhooksConfig, - @Inject(CONTEXT) context: GraphQLModules.ModuleContext + @Inject(CONTEXT) context: GraphQLModules.ModuleContext, ) { this.logger = logger.child({ service: 'WebhookCommunicationAdapter' }); this.webhooksService = createTRPCClient({ @@ -36,7 +36,7 @@ export class WebhookCommunicationAdapter implements CommunicationAdapter { `Sending Schema Change Notifications over Webhook (organization=%s, project=%s, target=%s)`, input.event.organization.id, input.event.project.id, - input.event.target.id + input.event.target.id, ); try { await this.webhooksService.mutation('schedule', { diff --git a/packages/services/api/src/modules/alerts/providers/alerts-manager.ts b/packages/services/api/src/modules/alerts/providers/alerts-manager.ts index bf2bb80cb..65d376765 100644 --- a/packages/services/api/src/modules/alerts/providers/alerts-manager.ts +++ b/packages/services/api/src/modules/alerts/providers/alerts-manager.ts @@ -31,7 +31,7 @@ export class AlertsManager { private webhook: WebhookCommunicationAdapter, private organizationManager: OrganizationManager, private projectManager: ProjectManager, - private storage: Storage + private storage: Storage, ) { this.logger = logger.child({ source: 'AlertsManager', @@ -43,7 +43,7 @@ export class AlertsManager { 'Adding Alert Channel (organization=%s, project=%s, type=%s)', input.organization, input.project, - input.type + input.type, ); await this.authManager.ensureProjectAccess({ ...input, @@ -65,13 +65,13 @@ export class AlertsManager { async deleteChannels( input: ProjectSelector & { channels: readonly string[]; - } + }, ): Promise { this.logger.debug( 'Deleting Alert Channels (organization=%s, project=%s, size=%s)', input.organization, input.project, - input.channels.length + input.channels.length, ); await this.authManager.ensureProjectAccess({ ...input, @@ -86,8 +86,8 @@ export class AlertsManager { channel, organization: input.organization, project: input.project, - }) - ) + }), + ), ); return channels; @@ -95,7 +95,11 @@ export class AlertsManager { @cache(selector => selector.project + selector.organization) async getChannels(selector: ProjectSelector): Promise { - this.logger.debug('Fetching Alert Channels (organization=%s, project=%s)', selector.organization, selector.project); + this.logger.debug( + 'Fetching Alert Channels (organization=%s, project=%s)', + selector.organization, + selector.project, + ); await this.authManager.ensureProjectAccess({ ...selector, scope: ProjectAccessScope.READ, @@ -108,7 +112,7 @@ export class AlertsManager { 'Adding Alert (organization=%s, project=%s, type=%s)', input.organization, input.project, - input.type + input.type, ); await this.authManager.ensureProjectAccess({ ...input, @@ -121,13 +125,13 @@ export class AlertsManager { async deleteAlerts( input: ProjectSelector & { alerts: readonly string[]; - } + }, ): Promise { this.logger.debug( 'Deleting Alerts (organization=%s, project=%s, size=%s)', input.organization, input.project, - input.alerts.length + input.alerts.length, ); await this.authManager.ensureProjectAccess({ ...input, @@ -137,7 +141,11 @@ export class AlertsManager { } async getAlerts(selector: ProjectSelector): Promise { - this.logger.debug('Fetching Alerts (organization=%s, project=%s)', selector.organization, selector.project); + this.logger.debug( + 'Fetching Alerts (organization=%s, project=%s)', + selector.organization, + selector.project, + ); await this.authManager.ensureProjectAccess({ ...selector, scope: ProjectAccessScope.READ, @@ -155,7 +163,7 @@ export class AlertsManager { organization, project, target, - event.schema.id + event.schema.id, ); await this.authManager.ensureTargetAccess({ @@ -177,7 +185,7 @@ export class AlertsManager { ]); const matchingAlerts = alerts.filter( - alert => alert.type === 'SCHEMA_CHANGE_NOTIFICATIONS' && alert.targetId === target + alert => alert.type === 'SCHEMA_CHANGE_NOTIFICATIONS' && alert.targetId === target, ); const pairs = matchingAlerts.map(alert => { return { @@ -243,7 +251,7 @@ export class AlertsManager { channel, integrations, }); - }) + }), ); } diff --git a/packages/services/api/src/modules/auth/index.ts b/packages/services/api/src/modules/auth/index.ts index 52d4f5ea2..9eb1f45ff 100644 --- a/packages/services/api/src/modules/auth/index.ts +++ b/packages/services/api/src/modules/auth/index.ts @@ -13,5 +13,12 @@ export const authModule = createModule({ dirname: __dirname, typeDefs, resolvers, - providers: [AuthManager, UserManager, ApiTokenProvider, OrganizationAccess, ProjectAccess, TargetAccess], + providers: [ + AuthManager, + UserManager, + ApiTokenProvider, + OrganizationAccess, + ProjectAccess, + TargetAccess, + ], }); diff --git a/packages/services/api/src/modules/auth/providers/auth-manager.ts b/packages/services/api/src/modules/auth/providers/auth-manager.ts index 8c1a69cb5..55659d2ed 100644 --- a/packages/services/api/src/modules/auth/providers/auth-manager.ts +++ b/packages/services/api/src/modules/auth/providers/auth-manager.ts @@ -6,7 +6,11 @@ import { share } from '../../../shared/helpers'; import { Storage } from '../../shared/providers/storage'; import { TokenStorage } from '../../token/providers/token-storage'; import { ApiToken } from './tokens'; -import { OrganizationAccess, OrganizationAccessScope, OrganizationUserScopesSelector } from './organization-access'; +import { + OrganizationAccess, + OrganizationAccessScope, + OrganizationUserScopesSelector, +} from './organization-access'; import { ProjectAccess, ProjectAccessScope, ProjectUserScopesSelector } from './project-access'; import { TargetAccess, TargetAccessScope, TargetUserScopesSelector } from './target-access'; import { UserManager } from './user-manager'; @@ -55,12 +59,14 @@ export class AuthManager { private targetAccess: TargetAccess, private userManager: UserManager, private tokenStorage: TokenStorage, - private storage: Storage + private storage: Storage, ) { this.session = context.session; } - async ensureTargetAccess(selector: Listify): Promise { + async ensureTargetAccess( + selector: Listify, + ): Promise { if (this.apiToken) { if (hasManyTargets(selector)) { await Promise.all( @@ -68,8 +74,8 @@ export class AuthManager { this.ensureTargetAccess({ ...selector, target, - }) - ) + }), + ), ); } else { await this.targetAccess.ensureAccessForToken({ @@ -84,8 +90,8 @@ export class AuthManager { this.ensureTargetAccess({ ...selector, target, - }) - ) + }), + ), ); } else { const user = await this.getCurrentUser(); @@ -218,7 +224,7 @@ export class AuthManager { } function hasManyTargets( - selector: Listify + selector: Listify, ): selector is MapToArray { return Array.isArray(selector.target); } diff --git a/packages/services/api/src/modules/auth/providers/organization-access.ts b/packages/services/api/src/modules/auth/providers/organization-access.ts index 32eed7ce7..0fa971633 100644 --- a/packages/services/api/src/modules/auth/providers/organization-access.ts +++ b/packages/services/api/src/modules/auth/providers/organization-access.ts @@ -46,13 +46,17 @@ export class OrganizationAccess { ReadonlyArray, string >; - private scopes: DataLoader; + private scopes: DataLoader< + OrganizationUserScopesSelector, + readonly OrganizationAccessScope[], + string + >; tokenInfo: DataLoader; constructor( logger: Logger, private storage: Storage, - @Inject(forwardRef(() => TokenStorage)) private tokenStorage: TokenStorage + @Inject(forwardRef(() => TokenStorage)) private tokenStorage: TokenStorage, ) { this.logger = logger.child({ source: 'OrganizationAccess', @@ -65,7 +69,11 @@ export class OrganizationAccess { const scopesForSelector = scopes[i]; if (scopesForSelector instanceof Error) { - this.logger.warn(`OrganizationAccess:user (error=%s, selector=%o)`, scopesForSelector.message, selector); + this.logger.warn( + `OrganizationAccess:user (error=%s, selector=%o)`, + scopesForSelector.message, + selector, + ); return false; } @@ -81,7 +89,7 @@ export class OrganizationAccess { scope: selector.scope, }); }, - } + }, ); this.tokenAccess = new Dataloader( selectors => @@ -94,7 +102,7 @@ export class OrganizationAccess { } return false; - }) + }), ), { cacheKeyFn(selector) { @@ -105,7 +113,7 @@ export class OrganizationAccess { scope: selector.scope, }); }, - } + }, ); this.allScopes = new Dataloader( async selectors => { @@ -121,7 +129,7 @@ export class OrganizationAccess { user: selector.user, }); }, - } + }, ); this.scopes = new Dataloader( async selectors => { @@ -131,7 +139,11 @@ export class OrganizationAccess { const scopes = scopesPerSelector[i]; if (scopes instanceof Error) { - this.logger.warn(`OrganizationAccess:scopes (error=%s, selector=%o)`, scopes.message, selector); + this.logger.warn( + `OrganizationAccess:scopes (error=%s, selector=%o)`, + scopes.message, + selector, + ); return []; } @@ -146,7 +158,7 @@ export class OrganizationAccess { user: selector.user, }); }, - } + }, ); this.tokenInfo = new Dataloader( selectors => Promise.all(selectors.map(selector => this.tokenStorage.getToken(selector))), @@ -154,7 +166,7 @@ export class OrganizationAccess { cacheKeyFn(selector) { return selector.token; }, - } + }, ); } diff --git a/packages/services/api/src/modules/auth/providers/project-access.ts b/packages/services/api/src/modules/auth/providers/project-access.ts index d820df884..69c084a4f 100644 --- a/packages/services/api/src/modules/auth/providers/project-access.ts +++ b/packages/services/api/src/modules/auth/providers/project-access.ts @@ -52,7 +52,11 @@ export class ProjectAccess { const scopesForSelector = scopes[i]; if (scopesForSelector instanceof Error) { - this.logger.warn(`ProjectAccess:user (error=%s, selector=%o)`, scopesForSelector.message, selector); + this.logger.warn( + `ProjectAccess:user (error=%s, selector=%o)`, + scopesForSelector.message, + selector, + ); return false; } @@ -69,7 +73,7 @@ export class ProjectAccess { scope: selector.scope, }); }, - } + }, ); this.tokenAccess = new Dataloader( selectors => @@ -77,12 +81,15 @@ export class ProjectAccess { selectors.map(async selector => { const tokenInfo = await this.organizationAccess.tokenInfo.load(selector); - if (tokenInfo?.organization === selector.organization && tokenInfo?.project === selector.project) { + if ( + tokenInfo?.organization === selector.organization && + tokenInfo?.project === selector.project + ) { return tokenInfo.scopes.includes(selector.scope); } return false; - }) + }), ), { cacheKeyFn(selector) { @@ -94,7 +101,7 @@ export class ProjectAccess { scope: selector.scope, }); }, - } + }, ); this.scopes = new Dataloader( async selectors => { @@ -104,7 +111,11 @@ export class ProjectAccess { const scopes = scopesPerSelector[i]; if (scopes instanceof Error) { - this.logger.debug(`ProjectAccess:scopes (error=%s, selector=%o)`, scopes.message, selector); + this.logger.debug( + `ProjectAccess:scopes (error=%s, selector=%o)`, + scopes.message, + selector, + ); return []; } @@ -119,7 +130,7 @@ export class ProjectAccess { user: selector.user, }); }, - } + }, ); } diff --git a/packages/services/api/src/modules/auth/providers/target-access.ts b/packages/services/api/src/modules/auth/providers/target-access.ts index 7eefc3f6e..70ec14308 100644 --- a/packages/services/api/src/modules/auth/providers/target-access.ts +++ b/packages/services/api/src/modules/auth/providers/target-access.ts @@ -54,7 +54,11 @@ export class TargetAccess { const scopesForSelector = scopes[i]; if (scopesForSelector instanceof Error) { - this.logger.warn(`TargetAccess:user (error=%s, selector=%o)`, scopesForSelector.message, selector); + this.logger.warn( + `TargetAccess:user (error=%s, selector=%o)`, + scopesForSelector.message, + selector, + ); return false; } @@ -72,7 +76,7 @@ export class TargetAccess { scope: selector.scope, }); }, - } + }, ); this.tokenAccess = new Dataloader( selectors => @@ -89,7 +93,7 @@ export class TargetAccess { } return false; - }) + }), ), { cacheKeyFn(selector) { @@ -102,7 +106,7 @@ export class TargetAccess { scope: selector.scope, }); }, - } + }, ); this.scopes = new Dataloader( @@ -113,7 +117,11 @@ export class TargetAccess { const scopes = scopesPerSelector[i]; if (scopes instanceof Error) { - this.logger.warn(`TargetAccess:scopes (error=%s, selector=%o)`, scopes.message, selector); + this.logger.warn( + `TargetAccess:scopes (error=%s, selector=%o)`, + scopes.message, + selector, + ); return []; } @@ -128,7 +136,7 @@ export class TargetAccess { user: selector.user, }); }, - } + }, ); } diff --git a/packages/services/api/src/modules/auth/resolvers.ts b/packages/services/api/src/modules/auth/resolvers.ts index 445d32047..282514ccb 100644 --- a/packages/services/api/src/modules/auth/resolvers.ts +++ b/packages/services/api/src/modules/auth/resolvers.ts @@ -24,7 +24,10 @@ export const resolvers: AuthModule.Resolvers & { Mutation: { async updateMe(_, { input }, { injector }) { const InputModel = z.object({ - displayName: z.string().min(displayNameLengthBoundaries.min).max(displayNameLengthBoundaries.max), + displayName: z + .string() + .min(displayNameLengthBoundaries.min) + .max(displayNameLengthBoundaries.max), fullName: z.string().min(fullNameLengthBoundaries.min).max(fullNameLengthBoundaries.max), }); const result = InputModel.safeParse(input); diff --git a/packages/services/api/src/modules/billing/module.graphql.ts b/packages/services/api/src/modules/billing/module.graphql.ts index 5d0b46906..acce98e80 100644 --- a/packages/services/api/src/modules/billing/module.graphql.ts +++ b/packages/services/api/src/modules/billing/module.graphql.ts @@ -70,7 +70,10 @@ export default gql` extend type Mutation { upgradeToPro(input: UpgradeToProInput!): ChangePlanResult! downgradeToHobby(input: DowngradeToHobbyInput!): ChangePlanResult! - updateOrgRateLimit(selector: OrganizationSelectorInput!, monthlyLimits: RateLimitInput!): Organization! + updateOrgRateLimit( + selector: OrganizationSelectorInput! + monthlyLimits: RateLimitInput! + ): Organization! } input DowngradeToHobbyInput { diff --git a/packages/services/api/src/modules/billing/providers/billing.provider.ts b/packages/services/api/src/modules/billing/providers/billing.provider.ts index 6702526bf..67fcd532f 100644 --- a/packages/services/api/src/modules/billing/providers/billing.provider.ts +++ b/packages/services/api/src/modules/billing/providers/billing.provider.ts @@ -2,7 +2,11 @@ import { Inject, Injectable, Scope } from 'graphql-modules'; import { Logger } from '../../shared/providers/logger'; import { BILLING_CONFIG } from './tokens'; import type { BillingConfig } from './tokens'; -import type { StripeBillingApi, StripeBillingMutationInput, StripeBillingQueryInput } from '@hive/stripe-billing'; +import type { + StripeBillingApi, + StripeBillingMutationInput, + StripeBillingQueryInput, +} from '@hive/stripe-billing'; import { createTRPCClient } from '@trpc/client'; import { fetch } from '@whatwg-node/fetch'; import { OrganizationSelector } from '../../../__generated__/types'; @@ -19,7 +23,11 @@ export class BillingProvider { enabled = false; - constructor(logger: Logger, private storage: Storage, @Inject(BILLING_CONFIG) billingConfig: BillingConfig) { + constructor( + logger: Logger, + private storage: Storage, + @Inject(BILLING_CONFIG) billingConfig: BillingConfig, + ) { this.logger = logger.child({ source: 'BillingProvider' }); this.billingService = billingConfig.endpoint ? createTRPCClient({ @@ -57,7 +65,9 @@ export class BillingProvider { return await this.billingService.query('availablePrices'); } - async getOrganizationBillingParticipant(selector: OrganizationSelector): Promise { + async getOrganizationBillingParticipant( + selector: OrganizationSelector, + ): Promise { this.logger.debug('Fetching organization billing (selector=%o)', selector); return this.storage.getOrganizationBilling({ diff --git a/packages/services/api/src/modules/billing/resolvers.ts b/packages/services/api/src/modules/billing/resolvers.ts index f024b88cb..db4f07c68 100644 --- a/packages/services/api/src/modules/billing/resolvers.ts +++ b/packages/services/api/src/modules/billing/resolvers.ts @@ -7,7 +7,10 @@ import { IdTranslator } from '../shared/providers/id-translator'; import { BillingProvider } from './providers/billing.provider'; import { BillingModule } from './__generated__/types'; -const USAGE_DEFAULT_LIMITATIONS: Record<'HOBBY' | 'PRO' | 'ENTERPRISE', { operations: number; retention: number }> = { +const USAGE_DEFAULT_LIMITATIONS: Record< + 'HOBBY' | 'PRO' | 'ENTERPRISE', + { operations: number; retention: number } +> = { HOBBY: { operations: 1_000_000, retention: 7, @@ -119,7 +122,8 @@ export const resolvers: BillingModule.Resolvers = { planType: 'PRO', basePrice: availablePrices.basePrice.unit_amount! / 100, name: 'Pro', - description: 'For production-ready applications that requires long retention, high ingestion capacity.', + description: + 'For production-ready applications that requires long retention, high ingestion capacity.', includedOperationsLimit: USAGE_DEFAULT_LIMITATIONS.PRO.operations, pricePerOperationsUnit: availablePrices.operationsPrice.tiers![1].unit_amount! / 100, retentionInDays: USAGE_DEFAULT_LIMITATIONS.PRO.retention, @@ -129,7 +133,8 @@ export const resolvers: BillingModule.Resolvers = { id: 'ENTERPRISE', planType: 'ENTERPRISE', name: 'Enterprise', - description: 'For enterprise and organization that requires custom setup and custom data ingestion rates.', + description: + 'For enterprise and organization that requires custom setup and custom data ingestion rates.', includedOperationsLimit: USAGE_DEFAULT_LIMITATIONS.ENTERPRISE.operations, retentionInDays: USAGE_DEFAULT_LIMITATIONS.ENTERPRISE.retention, rateLimit: 'UNLIMITED', @@ -226,7 +231,8 @@ export const resolvers: BillingModule.Resolvers = { organization: organizationId, monthlyRateLimit: { retentionInDays: USAGE_DEFAULT_LIMITATIONS.PRO.retention, - operations: args.input.monthlyLimits.operations || USAGE_DEFAULT_LIMITATIONS.PRO.operations, + operations: + args.input.monthlyLimits.operations || USAGE_DEFAULT_LIMITATIONS.PRO.operations, }, }); diff --git a/packages/services/api/src/modules/cdn/providers/cdn.provider.ts b/packages/services/api/src/modules/cdn/providers/cdn.provider.ts index f69943b2d..1d6c56aed 100644 --- a/packages/services/api/src/modules/cdn/providers/cdn.provider.ts +++ b/packages/services/api/src/modules/cdn/providers/cdn.provider.ts @@ -19,7 +19,11 @@ export class CdnProvider { private encoder: TextEncoder; private secretKeyData: Uint8Array | null; - constructor(logger: Logger, private httpClient: HttpClient, @Inject(CDN_CONFIG) private config: CDNConfig | null) { + constructor( + logger: Logger, + private httpClient: HttpClient, + @Inject(CDN_CONFIG) private config: CDNConfig | null, + ) { this.logger = logger.child({ source: 'CdnProvider' }); this.encoder = new TextEncoder(); this.secretKeyData = this.config ? this.encoder.encode(this.config.authPrivateKey) : null; @@ -42,7 +46,9 @@ export class CdnProvider { throw new HiveError(`CDN is not configured, cannot generate a token.`); } - return createHmac('sha256', this.secretKeyData).update(this.encoder.encode(targetId)).digest('base64'); + return createHmac('sha256', this.secretKeyData) + .update(this.encoder.encode(targetId)) + .digest('base64'); } async pushToCDN(url: string, body: string, span?: Span): Promise<{ success: boolean }> { @@ -67,7 +73,7 @@ export class CdnProvider { request: 10_000, }, }, - span + span, ); } @@ -82,7 +88,7 @@ export class CdnProvider { resourceType: CdnResourceType; value: string; }, - span?: Span + span?: Span, ): Promise { if (this.config === null) { this.logger.info(`Trying to publish to the CDN, but CDN is not configured, skipping`); @@ -90,19 +96,23 @@ export class CdnProvider { } const target = `target:${targetId}`; - this.logger.info(`Publishing data to CDN based on target: "${target}", resourceType is: ${resourceType} ...`); + this.logger.info( + `Publishing data to CDN based on target: "${target}", resourceType is: ${resourceType} ...`, + ); const CDN_SOURCE = `${this.config.cloudflare.basePath}/${this.config.cloudflare.accountId}/storage/kv/namespaces/${this.config.cloudflare.namespaceId}/values/${target}`; this.logger.info(`Data published to CDN: ${value}`); const result = await this.pushToCDN(`${CDN_SOURCE}:${resourceType}`, value, span); if (!result.success) { - return Promise.reject(new HiveError(`Failed to publish to CDN, response: ${JSON.stringify(result)}`)); + return Promise.reject( + new HiveError(`Failed to publish to CDN, response: ${JSON.stringify(result)}`), + ); } this.logger.info( `Published to CDN based on target: "${target}", resourceType is: ${resourceType} is done, response: %o`, - result + result, ); } } diff --git a/packages/services/api/src/modules/integrations/providers/github-integration-manager.ts b/packages/services/api/src/modules/integrations/providers/github-integration-manager.ts index 311a371df..0364894dd 100644 --- a/packages/services/api/src/modules/integrations/providers/github-integration-manager.ts +++ b/packages/services/api/src/modules/integrations/providers/github-integration-manager.ts @@ -11,7 +11,9 @@ export interface GitHubApplicationConfig { privateKey: string; } -export const GITHUB_APP_CONFIG = new InjectionToken('GitHubApplicationConfig'); +export const GITHUB_APP_CONFIG = new InjectionToken( + 'GitHubApplicationConfig', +); @Injectable({ scope: Scope.Operation, @@ -25,7 +27,7 @@ export class GitHubIntegrationManager { logger: Logger, private authManager: AuthManager, private storage: Storage, - @Inject(GITHUB_APP_CONFIG) private config: GitHubApplicationConfig | null + @Inject(GITHUB_APP_CONFIG) private config: GitHubApplicationConfig | null, ) { this.logger = logger.child({ source: 'GitHubIntegrationManager', @@ -47,7 +49,7 @@ export class GitHubIntegrationManager { async register( input: OrganizationSelector & { installationId: string; - } + }, ): Promise { this.logger.debug('Registering GitHub integration (organization=%s)', input.organization); await this.authManager.ensureOrganizationAccess({ @@ -88,7 +90,9 @@ export class GitHubIntegrationManager { }); } - async getRepositories(selector: OrganizationSelector): Promise { + async getRepositories( + selector: OrganizationSelector, + ): Promise { const installationId = await this.getInstallationId(selector); this.logger.debug('Fetching repositories'); @@ -106,7 +110,7 @@ export class GitHubIntegrationManager { return { nameWithOwner: repo.full_name, }; - }) + }), ) .catch(e => { this.logger.warn('Failed to fetch repositories', e); @@ -148,20 +152,22 @@ export class GitHubIntegrationManager { /** The summary of the check run. This parameter supports Markdown. */ summary: string; }; - } + }, ) { this.logger.debug( 'Creating check-run (owner=%s, name=%s, sha=%s)', input.repositoryOwner, input.repositoryName, - input.sha + input.sha, ); const installationId = await this.getInstallationId({ organization: input.organization, }); if (!installationId) { - throw new Error('GitHub Integration not found. Please install our GraphQL Hive GitHub Application.'); + throw new Error( + 'GitHub Integration not found. Please install our GraphQL Hive GitHub Application.', + ); } if (!this.app) { diff --git a/packages/services/api/src/modules/integrations/providers/slack-integration-manager.ts b/packages/services/api/src/modules/integrations/providers/slack-integration-manager.ts index 6069d7283..a777d3a58 100644 --- a/packages/services/api/src/modules/integrations/providers/slack-integration-manager.ts +++ b/packages/services/api/src/modules/integrations/providers/slack-integration-manager.ts @@ -5,7 +5,12 @@ import { ProjectAccessScope } from '../../auth/providers/project-access'; import { TargetAccessScope } from '../../auth/providers/target-access'; import { Logger } from '../../shared/providers/logger'; import { CryptoProvider } from '../../shared/providers/crypto'; -import { Storage, OrganizationSelector, ProjectSelector, TargetSelector } from '../../shared/providers/storage'; +import { + Storage, + OrganizationSelector, + ProjectSelector, + TargetSelector, +} from '../../shared/providers/storage'; import { AccessError } from '../../../shared/errors'; import { IntegrationsAccessContext } from './integrations-access-context'; @@ -20,7 +25,7 @@ export class SlackIntegrationManager { logger: Logger, private authManager: AuthManager, private storage: Storage, - private crypto: CryptoProvider + private crypto: CryptoProvider, ) { this.logger = logger.child({ source: 'SlackIntegrationManager', @@ -30,7 +35,7 @@ export class SlackIntegrationManager { async register( input: OrganizationSelector & { token: string; - } + }, ): Promise { this.logger.debug('Registering Slack integration (organization=%s)', input.organization); await this.authManager.ensureOrganizationAccess({ @@ -67,17 +72,17 @@ export class SlackIntegrationManager { async getToken( selector: OrganizationSelector & { context: IntegrationsAccessContext.Integrations; - } + }, ): Promise; async getToken( selector: ProjectSelector & { context: IntegrationsAccessContext.ChannelConfirmation; - } + }, ): Promise; async getToken( selector: TargetSelector & { context: IntegrationsAccessContext.SchemaPublishing; - } + }, ): Promise; async getToken( selector: @@ -89,14 +94,14 @@ export class SlackIntegrationManager { }) | (TargetSelector & { context: IntegrationsAccessContext.SchemaPublishing; - }) + }), ): Promise { switch (selector.context) { case IntegrationsAccessContext.Integrations: { this.logger.debug( 'Fetching Slack integration token (organization=%s, context: %s)', selector.organization, - selector.context + selector.context, ); await this.authManager.ensureOrganizationAccess({ ...selector, @@ -109,7 +114,7 @@ export class SlackIntegrationManager { 'Fetching Slack integration token (organization=%s, project=%s, context: %s)', selector.organization, selector.project, - selector.context + selector.context, ); await this.authManager.ensureProjectAccess({ ...selector, @@ -123,7 +128,7 @@ export class SlackIntegrationManager { selector.organization, selector.project, selector.target, - selector.context + selector.context, ); await this.authManager.ensureTargetAccess({ ...selector, diff --git a/packages/services/api/src/modules/integrations/resolvers.ts b/packages/services/api/src/modules/integrations/resolvers.ts index 1965cc932..7fbecbc32 100644 --- a/packages/services/api/src/modules/integrations/resolvers.ts +++ b/packages/services/api/src/modules/integrations/resolvers.ts @@ -23,7 +23,9 @@ export const resolvers: IntegrationsModule.Resolvers = { const result = AddSlackTokenIntegrationModel.safeParse(input); if (!result.success) { - throw new HiveError(result.error.formErrors.fieldErrors.token?.[0] ?? 'Please check your input.'); + throw new HiveError( + result.error.formErrors.fieldErrors.token?.[0] ?? 'Please check your input.', + ); } const organization = await injector.get(IdTranslator).translateOrganizationId(input); diff --git a/packages/services/api/src/modules/lab/resolvers.ts b/packages/services/api/src/modules/lab/resolvers.ts index d563469e8..63fd63af4 100644 --- a/packages/services/api/src/modules/lab/resolvers.ts +++ b/packages/services/api/src/modules/lab/resolvers.ts @@ -53,7 +53,7 @@ export const resolvers: LabModule.Resolvers = { const schema = await orchestrator.build( schemas.map(s => helper.createSchemaObject(s)), - externalComposition + externalComposition, ); return { diff --git a/packages/services/api/src/modules/oidc-integrations/providers/oidc-integrations.provider.ts b/packages/services/api/src/modules/oidc-integrations/providers/oidc-integrations.provider.ts index f43bc4532..5b58b0185 100644 --- a/packages/services/api/src/modules/oidc-integrations/providers/oidc-integrations.provider.ts +++ b/packages/services/api/src/modules/oidc-integrations/providers/oidc-integrations.provider.ts @@ -20,7 +20,7 @@ export class OIDCIntegrationsProvider { private storage: Storage, private authManager: AuthManager, private crypto: CryptoProvider, - @Inject(OIDC_INTEGRATIONS_ENABLED) private enabled: boolean + @Inject(OIDC_INTEGRATIONS_ENABLED) private enabled: boolean, ) { this.logger = logger.child({ source: 'OIDCIntegrationsProvider' }); } @@ -48,8 +48,13 @@ export class OIDCIntegrationsProvider { } } - async getOIDCIntegrationForOrganization(args: { organizationId: string }): Promise { - this.logger.debug('getting oidc integration for organization (organizationId=%s)', args.organizationId); + async getOIDCIntegrationForOrganization(args: { + organizationId: string; + }): Promise { + this.logger.debug( + 'getting oidc integration for organization (organizationId=%s)', + args.organizationId, + ); if (this.isEnabled() === false) { this.logger.debug('oidc integrations are disabled.'); return null; @@ -60,7 +65,9 @@ export class OIDCIntegrationsProvider { scope: OrganizationAccessScope.INTEGRATIONS, }); - return await this.storage.getOIDCIntegrationForOrganization({ organizationId: args.organizationId }); + return await this.storage.getOIDCIntegrationForOrganization({ + organizationId: args.organizationId, + }); } async getClientSecretPreview(integration: OIDCIntegration) { @@ -127,7 +134,9 @@ export class OIDCIntegrationsProvider { reason: null, fieldErrors: { clientId: clientIdResult.success ? null : clientIdResult.error.issues[0].message, - clientSecret: clientSecretResult.success ? null : clientSecretResult.error.issues[0].message, + clientSecret: clientSecretResult.success + ? null + : clientSecretResult.error.issues[0].message, oauthApiUrl: oauthApiUrlResult.success ? null : oauthApiUrlResult.error.issues[0].message, }, } as const; @@ -146,7 +155,9 @@ export class OIDCIntegrationsProvider { } as const; } - const integration = await this.storage.getOIDCIntegrationById({ oidcIntegrationId: args.oidcIntegrationId }); + const integration = await this.storage.getOIDCIntegrationById({ + oidcIntegrationId: args.oidcIntegrationId, + }); if (integration === null) { return { @@ -173,7 +184,9 @@ export class OIDCIntegrationsProvider { const oidcIntegration = await this.storage.updateOIDCIntegration({ oidcIntegrationId: args.oidcIntegrationId, clientId: clientIdResult.data, - encryptedClientSecret: clientSecretResult.data ? this.crypto.encrypt(clientSecretResult.data) : null, + encryptedClientSecret: clientSecretResult.data + ? this.crypto.encrypt(clientSecretResult.data) + : null, oauthApiUrl: oauthApiUrlResult.data, }); @@ -188,7 +201,9 @@ export class OIDCIntegrationsProvider { message: "Couldn't update integration.", fieldErrors: { clientId: clientIdResult.success ? null : clientIdResult.error.issues[0].message, - clientSecret: clientSecretResult.success ? null : clientSecretResult.error.issues[0].message, + clientSecret: clientSecretResult.success + ? null + : clientSecretResult.error.issues[0].message, oauthApiUrl: oauthApiUrlResult.success ? null : oauthApiUrlResult.error.issues[0].message, }, } as const; @@ -202,7 +217,9 @@ export class OIDCIntegrationsProvider { } as const; } - const integration = await this.storage.getOIDCIntegrationById({ oidcIntegrationId: args.oidcIntegrationId }); + const integration = await this.storage.getOIDCIntegrationById({ + oidcIntegrationId: args.oidcIntegrationId, + }); if (integration === null) { return { diff --git a/packages/services/api/src/modules/operations/providers/clickhouse-client.ts b/packages/services/api/src/modules/operations/providers/clickhouse-client.ts index 3ded0c8b6..8ed99d0a6 100644 --- a/packages/services/api/src/modules/operations/providers/clickhouse-client.ts +++ b/packages/services/api/src/modules/operations/providers/clickhouse-client.ts @@ -41,7 +41,7 @@ export class ClickHouse { constructor( @Inject(CLICKHOUSE_CONFIG) private config: ClickHouseConfig, private httpClient: HttpClient, - logger: Logger + logger: Logger, ) { this.logger = logger.child({ service: 'ClickHouse', @@ -104,7 +104,7 @@ export class ClickHouse { `Failed to run ClickHouse query, code: %s , error name: %s, message: %s`, info.error.code, info.error.name, - info.error.message + info.error.message, ); this.logger.debug( @@ -112,7 +112,7 @@ export class ClickHouse { delayBy, info.attemptCount, info.error.message, - queryId + queryId, ); return delayBy; @@ -125,7 +125,7 @@ export class ClickHouse { }, }, - span + span, ) .finally(() => { span?.finish(); diff --git a/packages/services/api/src/modules/operations/providers/helpers.ts b/packages/services/api/src/modules/operations/providers/helpers.ts index 251567936..e7f38547d 100644 --- a/packages/services/api/src/modules/operations/providers/helpers.ts +++ b/packages/services/api/src/modules/operations/providers/helpers.ts @@ -2,7 +2,13 @@ import type { DateRange } from '../../../shared/entities'; export const maxResolution = 90; -export function calculateTimeWindow({ period, resolution }: { period: DateRange; resolution: number }): { +export function calculateTimeWindow({ + period, + resolution, +}: { + period: DateRange; + resolution: number; +}): { value: number; unit: 'd' | 'h' | 'm'; } { @@ -11,7 +17,9 @@ export function calculateTimeWindow({ period, resolution }: { period: DateRange; } if (resolution < 10 || resolution > maxResolution) { - throw new Error(`Invalid resolution. Expected 10 <= x <= ${maxResolution}, received ${resolution}`); + throw new Error( + `Invalid resolution. Expected 10 <= x <= ${maxResolution}, received ${resolution}`, + ); } const distanceInMinutes = (period.to.getTime() - period.from.getTime()) / 1000 / 60; diff --git a/packages/services/api/src/modules/operations/providers/operations-manager.ts b/packages/services/api/src/modules/operations/providers/operations-manager.ts index 39fa591f2..79c92a32e 100644 --- a/packages/services/api/src/modules/operations/providers/operations-manager.ts +++ b/packages/services/api/src/modules/operations/providers/operations-manager.ts @@ -62,7 +62,7 @@ export class OperationsManager { logger: Logger, private authManager: AuthManager, private reader: OperationsReader, - private storage: Storage + private storage: Storage, ) { this.logger = logger.child({ source: 'OperationsManager' }); } @@ -105,7 +105,7 @@ export class OperationsManager { .countOperations({ target, }) - .then(r => r.total > 0) + .then(r => r.total > 0), ); } @@ -156,7 +156,9 @@ export class OperationsManager { } async readFieldStats(input: ReadFieldStatsInput): Promise; - async readFieldStats(input: Optional): Promise>; + async readFieldStats( + input: Optional, + ): Promise>; async readFieldStats(input: ReadFieldStatsInput): Promise { const { type, field, argument, period, organization, project, target } = input; this.logger.info('Counting a field (period=%o, target=%s)', period, target); @@ -215,7 +217,7 @@ export class OperationsManager { 'Counting fields (period=%o, target=%s, excludedClients=%s)', period, target, - excludedClients?.join(', ') ?? 'none' + excludedClients?.join(', ') ?? 'none', ); if (!unsafe__itIsMeInspector) { @@ -289,7 +291,12 @@ export class OperationsManager { resolution: number; operations?: readonly string[]; } & TargetSelector) { - this.logger.info('Reading requests over time (period=%o, resolution=%s, target=%s)', period, resolution, target); + this.logger.info( + 'Reading requests over time (period=%o, resolution=%s, target=%s)', + period, + resolution, + target, + ); await this.authManager.ensureTargetAccess({ organization, project, @@ -317,7 +324,12 @@ export class OperationsManager { resolution: number; operations?: readonly string[]; } & TargetSelector) { - this.logger.info('Reading failures over time (period=%o, resolution=%s, target=%s)', period, resolution, target); + this.logger.info( + 'Reading failures over time (period=%o, resolution=%s, target=%s)', + period, + resolution, + target, + ); await this.authManager.ensureTargetAccess({ organization, project, @@ -345,7 +357,12 @@ export class OperationsManager { resolution: number; operations?: readonly string[]; } & TargetSelector) { - this.logger.info('Reading duration over time (period=%o, resolution=%s, target=%s)', period, resolution, target); + this.logger.info( + 'Reading duration over time (period=%o, resolution=%s, target=%s)', + period, + resolution, + target, + ); await this.authManager.ensureTargetAccess({ organization, project, @@ -391,7 +408,11 @@ export class OperationsManager { target, operations, }: { period: DateRange; operations?: readonly string[] } & TargetSelector) { - this.logger.info('Reading detailed duration percentiles (period=%o, target=%s)', period, target); + this.logger.info( + 'Reading detailed duration percentiles (period=%o, target=%s)', + period, + target, + ); await this.authManager.ensureTargetAccess({ organization, project, diff --git a/packages/services/api/src/modules/operations/providers/operations-reader.ts b/packages/services/api/src/modules/operations/providers/operations-reader.ts index 6bce90d18..4c01dc35c 100644 --- a/packages/services/api/src/modules/operations/providers/operations-reader.ts +++ b/packages/services/api/src/modules/operations/providers/operations-reader.ts @@ -96,7 +96,7 @@ function pickQueryByPeriod( }; }, period: DateRange | null, - resolution?: number + resolution?: number, ) { if (!period) { return queryMap.daily; @@ -130,7 +130,13 @@ function pickQueryByPeriod( } // Remove after legacy tables are no longer used -function canUseHourlyAggTable({ period, resolution }: { period?: DateRange; resolution?: number }): boolean { +function canUseHourlyAggTable({ + period, + resolution, +}: { + period?: DateRange; + resolution?: number; +}): boolean { if (period) { const distance = period.to.getTime() - period.from.getTime(); const distanceInHours = distance / 1000 / 60 / 60; @@ -195,7 +201,7 @@ export class OperationsReader { operations?: readonly string[]; excludedClients?: readonly string[]; }, - span?: Span + span?: Span, ): Promise> { const { clientTableName, coordinatesTableName, queryId } = canUseV2(period) ? { @@ -276,7 +282,7 @@ export class OperationsReader { period?: DateRange; operations?: readonly string[]; }, - span?: Span + span?: Span, ): Promise<{ total: number; ok: number; @@ -286,38 +292,44 @@ export class OperationsReader { ? pickQueryByPeriod( { daily: { - query: `SELECT sum(total) as total, sum(total_ok) as totalOk FROM operations_daily ${this.createFilter({ - target, - period, - operations, - })}`, + query: `SELECT sum(total) as total, sum(total_ok) as totalOk FROM operations_daily ${this.createFilter( + { + target, + period, + operations, + }, + )}`, queryId: 'count_operations_daily', timeout: 10_000, span, }, hourly: { - query: `SELECT sum(total) as total, sum(total_ok) as totalOk FROM operations_hourly ${this.createFilter({ - target, - period, - operations, - })}`, + query: `SELECT sum(total) as total, sum(total_ok) as totalOk FROM operations_hourly ${this.createFilter( + { + target, + period, + operations, + }, + )}`, queryId: 'count_operations_hourly', timeout: 15_000, span, }, regular: { - query: `SELECT count() as total, sum(ok) as totalOk FROM operations ${this.createFilter({ - target, - period, - operations, - })} + query: `SELECT count() as total, sum(ok) as totalOk FROM operations ${this.createFilter( + { + target, + period, + operations, + }, + )} `, queryId: 'count_operations_regular', timeout: 30_000, span, }, }, - period ?? null + period ?? null, ) : canUseHourlyAggTable({ period }) ? { @@ -326,18 +338,20 @@ export class OperationsReader { target, period, operations, - } + }, )}`, queryId: 'count_operations_mv', timeout: 15_000, span, } : { - query: `SELECT count() as total, sum(ok) as totalOk FROM operations_new ${this.createFilter({ - target, - period, - operations, - })} + query: `SELECT count() as total, sum(ok) as totalOk FROM operations_new ${this.createFilter( + { + target, + period, + operations, + }, + )} `, queryId: 'count_operations', timeout: 30_000, @@ -383,7 +397,7 @@ export class OperationsReader { period: DateRange; operations?: readonly string[]; }, - span?: Span + span?: Span, ): Promise< Array<{ document: string; @@ -503,7 +517,7 @@ export class OperationsReader { span, }, }, - period + period, ); const result = await this.clickHouse.query<{ @@ -554,7 +568,7 @@ export class OperationsReader { queryId: 'operations_registry', timeout: 15_000, span, - } + }, ); const operationsMap = new Map>(); @@ -596,7 +610,7 @@ export class OperationsReader { period: DateRange; operations?: readonly string[]; }, - span?: Span + span?: Span, ): Promise< Array<{ name: string; @@ -672,7 +686,7 @@ export class OperationsReader { span, }, }, - period + period, ) : { query: ` @@ -691,7 +705,7 @@ export class OperationsReader { queryId: 'count_unique_clients', timeout: 15_000, span, - } + }, ); const total = result.data.reduce((sum, row) => sum + parseInt(row.total, 10), 0); @@ -754,7 +768,7 @@ export class OperationsReader { period: DateRange; operations?: readonly string[]; }, - span?: Span + span?: Span, ): Promise< Array<{ name: string; @@ -801,7 +815,7 @@ export class OperationsReader { queryId: 'count_unique_client_names', timeout: 15_000, span, - } + }, ); return result.data.map(row => { @@ -898,7 +912,7 @@ export class OperationsReader { period: DateRange; operations?: readonly string[]; }, - span?: Span + span?: Span, ): Promise< Array<{ duration: number; @@ -946,7 +960,7 @@ export class OperationsReader { period: DateRange; operations?: readonly string[]; }, - span?: Span + span?: Span, ): Promise { const result = await this.clickHouse.query<{ percentiles: [number, number, number, number]; @@ -988,7 +1002,7 @@ export class OperationsReader { span, }, }, - period + period, ) : canUseHourlyAggTable({ period }) ? { @@ -1012,7 +1026,7 @@ export class OperationsReader { queryId: 'general_duration_percentiles', timeout: 15_000, span, - } + }, ); return toESPercentiles(result.data[0].percentiles); @@ -1029,7 +1043,7 @@ export class OperationsReader { period: DateRange; operations?: readonly string[]; }, - span?: Span + span?: Span, ) { const result = await this.clickHouse.query<{ hash: string; @@ -1078,7 +1092,7 @@ export class OperationsReader { span, }, }, - period + period, ) : canUseHourlyAggTable({ period }) ? { @@ -1106,7 +1120,7 @@ export class OperationsReader { queryId: 'duration_percentiles', timeout: 15_000, span, - } + }, ); const collection = new Map(); @@ -1118,7 +1132,13 @@ export class OperationsReader { return collection; } - async getClientNames({ target, period }: { target: string; period: DateRange }): Promise { + async getClientNames({ + target, + period, + }: { + target: string; + period: DateRange; + }): Promise { const result = await this.clickHouse.query<{ client_name: string; }>( @@ -1138,7 +1158,7 @@ export class OperationsReader { period, })} GROUP BY client_name`, timeout: 10_000, - } + }, ); return result.data.map(row => row.client_name); @@ -1157,7 +1177,7 @@ export class OperationsReader { resolution: number; operations?: readonly string[]; }, - span?: Span + span?: Span, ) { // multiply by 1000 to convert to milliseconds const result = await this.clickHouse.query<{ @@ -1175,7 +1195,7 @@ export class OperationsReader { multiply( toUnixTimestamp( toStartOfInterval(timestamp, INTERVAL ${this.clickHouse.translateWindow( - calculateTimeWindow({ period, resolution }) + calculateTimeWindow({ period, resolution }), )}, 'UTC'), 'UTC'), 1000) as date, @@ -1197,7 +1217,7 @@ export class OperationsReader { multiply( toUnixTimestamp( toStartOfInterval(timestamp, INTERVAL ${this.clickHouse.translateWindow( - calculateTimeWindow({ period, resolution }) + calculateTimeWindow({ period, resolution }), )}, 'UTC'), 'UTC'), 1000) as date, @@ -1219,7 +1239,7 @@ export class OperationsReader { multiply( toUnixTimestamp( toStartOfInterval(timestamp, INTERVAL ${this.clickHouse.translateWindow( - calculateTimeWindow({ period, resolution }) + calculateTimeWindow({ period, resolution }), )}, 'UTC'), 'UTC'), 1000) as date, @@ -1237,7 +1257,7 @@ export class OperationsReader { }, }, period, - resolution + resolution, ) : canUseHourlyAggTable({ period, resolution }) ? { @@ -1246,7 +1266,7 @@ export class OperationsReader { multiply( toUnixTimestamp( toStartOfInterval(timestamp, INTERVAL ${this.clickHouse.translateWindow( - calculateTimeWindow({ period, resolution }) + calculateTimeWindow({ period, resolution }), )}, 'UTC'), 'UTC'), 1000) as date, @@ -1268,7 +1288,7 @@ export class OperationsReader { multiply( toUnixTimestamp( toStartOfInterval(timestamp, INTERVAL ${this.clickHouse.translateWindow( - calculateTimeWindow({ period, resolution }) + calculateTimeWindow({ period, resolution }), )}, 'UTC'), 'UTC'), 1000) as date, @@ -1283,7 +1303,7 @@ export class OperationsReader { queryId: 'duration_and_count_over_time', timeout: 15_000, span, - } + }, ); return result.data.map(row => { @@ -1301,7 +1321,9 @@ export class OperationsReader { total: string; }>({ // TODO: use the operations_daily table once the FF_CLICKHOUSE_V2_TABLES is available for everyone - query: `SELECT sum(total) as total from operations_hourly WHERE target IN ('${targets.join(`', '`)}')`, + query: `SELECT sum(total) as total from operations_hourly WHERE target IN ('${targets.join( + `', '`, + )}')`, queryId: 'count_operations_for_targets', timeout: 15_000, }); @@ -1327,7 +1349,7 @@ export class OperationsReader { target: string; period: DateRange; typename: string; - }> + }>, ) => { const aggregationMap = new Map< string, @@ -1379,7 +1401,7 @@ export class OperationsReader { target: selector.target, period: selector.period, typenames: selector.typenames, - }) + }), ); } @@ -1395,7 +1417,7 @@ export class OperationsReader { return value; }); - } + }, ); private async countCoordinatesOfTypes({ @@ -1407,7 +1429,9 @@ export class OperationsReader { period: DateRange; typenames: string[]; }) { - const typesFilter = typenames.map(t => `coordinate = '${t}' OR coordinate LIKE '${t}.%'`).join(' OR '); + const typesFilter = typenames + .map(t => `coordinate = '${t}' OR coordinate LIKE '${t}.%'`) + .join(' OR '); const result = await this.clickHouse.query<{ coordinate: string; total: number; @@ -1436,7 +1460,7 @@ export class OperationsReader { GROUP BY coordinate`, queryId: 'coordinates_per_types', timeout: 15_000, - } + }, ); return result.data.map(row => ({ @@ -1474,7 +1498,7 @@ export class OperationsReader { `, queryId: 'coordinates_per_target', timeout: 15_000, - } + }, ); return result.data.map(row => ({ @@ -1501,7 +1525,7 @@ export class OperationsReader { query: `SELECT sum(total) as total, target from operations_new_hourly_mv WHERE timestamp >= subtractDays(NOW(), ${daysLimit}) GROUP BY target`, queryId: 'admin_operations_per_target', timeout: 15_000, - } + }, ); return result.data.map(row => ({ @@ -1530,12 +1554,14 @@ export class OperationsReader { calculateTimeWindow({ period, resolution, - }) + }), )}, 'UTC'), 'UTC'), 1000) as date, sum(total) as total - FROM ${daysLimit > 1 && daysLimit >= resolution ? 'operations_daily' : 'operations_hourly'} + FROM ${ + daysLimit > 1 && daysLimit >= resolution ? 'operations_daily' : 'operations_hourly' + } WHERE timestamp >= subtractDays(NOW(), ${daysLimit}) GROUP BY date ORDER BY date @@ -1555,7 +1581,7 @@ export class OperationsReader { to: new Date(), }, resolution, - }) + }), )}, 'UTC'), 'UTC'), 1000) as date, @@ -1567,7 +1593,7 @@ export class OperationsReader { `, queryId: 'admin_operations_per_target', timeout: 15_000, - } + }, ); return result.data.map(row => ({ @@ -1615,7 +1641,15 @@ export class OperationsReader { return statement; } - private makeId({ type, field, argument }: { type: string; field?: string | null; argument?: string | null }): string { + private makeId({ + type, + field, + argument, + }: { + type: string; + field?: string | null; + argument?: string | null; + }): string { return [type, field, argument].filter(Boolean).join('.'); } } diff --git a/packages/services/api/src/modules/operations/providers/tokens.ts b/packages/services/api/src/modules/operations/providers/tokens.ts index 2d1a2d4ee..9d279c0b7 100644 --- a/packages/services/api/src/modules/operations/providers/tokens.ts +++ b/packages/services/api/src/modules/operations/providers/tokens.ts @@ -11,7 +11,7 @@ export interface ClickHouseConfig { timings: { totalSeconds: number; elapsedSeconds: number; - } + }, ) => void; } diff --git a/packages/services/api/src/modules/operations/resolvers.ts b/packages/services/api/src/modules/operations/resolvers.ts index 03a366042..2f3298d79 100644 --- a/packages/services/api/src/modules/operations/resolvers.ts +++ b/packages/services/api/src/modules/operations/resolvers.ts @@ -108,7 +108,11 @@ export const resolvers: OperationsModule.Resolvers = { }, }, OperationsStats: { - async operations({ organization, project, target, period, operations: operationsFilter }, _, { injector }) { + async operations( + { organization, project, target, period, operations: operationsFilter }, + _, + { injector }, + ) { const operationsManager = injector.get(OperationsManager); const [operations, durations] = await Promise.all([ operationsManager.readOperationsStats({ @@ -152,7 +156,11 @@ export const resolvers: OperationsModule.Resolvers = { operations, }); }, - totalFailures({ organization, project, target, period, operations: operationsFilter }, _, { injector }) { + totalFailures( + { organization, project, target, period, operations: operationsFilter }, + _, + { injector }, + ) { return injector.get(OperationsManager).countFailures({ organization, project, @@ -161,7 +169,11 @@ export const resolvers: OperationsModule.Resolvers = { operations: operationsFilter, }); }, - totalOperations({ organization, project, target, period, operations: operationsFilter }, _, { injector }) { + totalOperations( + { organization, project, target, period, operations: operationsFilter }, + _, + { injector }, + ) { return injector.get(OperationsManager).countUniqueOperations({ organization, project, @@ -173,7 +185,7 @@ export const resolvers: OperationsModule.Resolvers = { requestsOverTime( { organization, project, target, period, operations: operationsFilter }, { resolution }, - { injector } + { injector }, ) { return injector.get(OperationsManager).readRequestsOverTime({ target, @@ -187,7 +199,7 @@ export const resolvers: OperationsModule.Resolvers = { failuresOverTime( { organization, project, target, period, operations: operationsFilter }, { resolution }, - { injector } + { injector }, ) { return injector.get(OperationsManager).readFailuresOverTime({ target, @@ -201,7 +213,7 @@ export const resolvers: OperationsModule.Resolvers = { durationOverTime( { organization, project, target, period, operations: operationsFilter }, { resolution }, - { injector } + { injector }, ) { return injector.get(OperationsManager).readDurationOverTime({ target, @@ -212,7 +224,11 @@ export const resolvers: OperationsModule.Resolvers = { operations: operationsFilter, }); }, - clients({ organization, project, target, period, operations: operationsFilter }, _, { injector }) { + clients( + { organization, project, target, period, operations: operationsFilter }, + _, + { injector }, + ) { return injector.get(OperationsManager).readUniqueClients({ target, project, @@ -221,7 +237,11 @@ export const resolvers: OperationsModule.Resolvers = { operations: operationsFilter, }); }, - duration({ organization, project, target, period, operations: operationsFilter }, _, { injector }) { + duration( + { organization, project, target, period, operations: operationsFilter }, + _, + { injector }, + ) { return injector.get(OperationsManager).readGeneralDurationPercentiles({ organization, project, @@ -230,7 +250,11 @@ export const resolvers: OperationsModule.Resolvers = { operations: operationsFilter, }); }, - async durationHistogram({ organization, project, target, period, operations: operationsFilter }, _, { injector }) { + async durationHistogram( + { organization, project, target, period, operations: operationsFilter }, + _, + { injector }, + ) { const histogram = await injector.get(OperationsManager).readDurationHistogram({ organization, project, diff --git a/packages/services/api/src/modules/organization/module.graphql.ts b/packages/services/api/src/modules/organization/module.graphql.ts index 344d7c75c..b4e9b703a 100644 --- a/packages/services/api/src/modules/organization/module.graphql.ts +++ b/packages/services/api/src/modules/organization/module.graphql.ts @@ -12,8 +12,12 @@ export default gql` deleteOrganization(selector: OrganizationSelectorInput!): OrganizationPayload! deleteOrganizationMembers(selector: OrganizationMembersSelectorInput!): OrganizationPayload! joinOrganization(code: String!): JoinOrganizationPayload! - inviteToOrganizationByEmail(input: InviteToOrganizationByEmailInput!): InviteToOrganizationByEmailResult! - deleteOrganizationInvitation(input: DeleteOrganizationInvitationInput!): DeleteOrganizationInvitationResult! + inviteToOrganizationByEmail( + input: InviteToOrganizationByEmailInput! + ): InviteToOrganizationByEmailResult! + deleteOrganizationInvitation( + input: DeleteOrganizationInvitationInput! + ): DeleteOrganizationInvitationResult! updateOrganizationName(input: UpdateOrganizationNameInput!): UpdateOrganizationNameResult! updateOrganizationMemberAccess(input: OrganizationMemberAccessInput!): OrganizationPayload! } @@ -154,7 +158,9 @@ export default gql` union JoinOrganizationPayload = OrganizationInvitationError | OrganizationPayload - union OrganizationByInviteCodePayload = OrganizationInvitationError | OrganizationInvitationPayload + union OrganizationByInviteCodePayload = + OrganizationInvitationError + | OrganizationInvitationPayload type OrganizationPayload { selector: OrganizationSelector! diff --git a/packages/services/api/src/modules/organization/providers/organization-config.ts b/packages/services/api/src/modules/organization/providers/organization-config.ts index 1c8cf2048..5bfe3ce52 100644 --- a/packages/services/api/src/modules/organization/providers/organization-config.ts +++ b/packages/services/api/src/modules/organization/providers/organization-config.ts @@ -1,4 +1,8 @@ -import { OrganizationAccessScope, ProjectAccessScope, TargetAccessScope } from '../../auth/providers/scopes'; +import { + OrganizationAccessScope, + ProjectAccessScope, + TargetAccessScope, +} from '../../auth/providers/scopes'; export const reservedOrganizationNames = [ 'registry', diff --git a/packages/services/api/src/modules/organization/providers/organization-manager.ts b/packages/services/api/src/modules/organization/providers/organization-manager.ts index b1eb058b1..183893b16 100644 --- a/packages/services/api/src/modules/organization/providers/organization-manager.ts +++ b/packages/services/api/src/modules/organization/providers/organization-manager.ts @@ -35,7 +35,7 @@ export class OrganizationManager { private tokenStorage: TokenStorage, private activityManager: ActivityManager, private billingProvider: BillingProvider, - private emails: Emails + private emails: Emails, ) { this.logger = logger.child({ source: 'OrganizationManager' }); } @@ -78,7 +78,11 @@ export class OrganizationManager { return this.storage.getOrganizations({ user: user.id }); } - async getOrganizationByInviteCode({ code }: { code: string }): Promise { + async getOrganizationByInviteCode({ + code, + }: { + code: string; + }): Promise { this.logger.debug('Fetching organization (inviteCode=%s)', code); const organization = await this.storage.getOrganizationByInviteCode({ inviteCode: code, @@ -139,7 +143,10 @@ export class OrganizationManager { this.logger.info('Creating an organization (input=%o)', input); if (user.oidcIntegrationId) { - this.logger.debug('Failed to create organization as oidc user is not allowed to do so (input=%o)', input); + this.logger.debug( + 'Failed to create organization as oidc user is not allowed to do so (input=%o)', + input, + ); throw new HiveError('Cannot create organization with OIDC user.'); } @@ -196,7 +203,7 @@ export class OrganizationManager { async updatePlan( input: { plan: string; - } & OrganizationSelector + } & OrganizationSelector, ): Promise { const { plan } = input; this.logger.info('Updating an organization plan (input=%o)', input); @@ -227,7 +234,9 @@ export class OrganizationManager { return result; } - async updateRateLimits(input: Pick & OrganizationSelector): Promise { + async updateRateLimits( + input: Pick & OrganizationSelector, + ): Promise { const { monthlyRateLimit } = input; this.logger.info('Updating an organization plan (input=%o)', input); await this.authManager.ensureOrganizationAccess({ @@ -258,7 +267,7 @@ export class OrganizationManager { async updateName( input: { name: string; - } & OrganizationSelector + } & OrganizationSelector, ): Promise { const { name } = input; this.logger.info('Updating an organization name (input=%o)', input); @@ -311,14 +320,21 @@ export class OrganizationManager { return this.storage.deleteOrganizationInvitationByEmail(input); } - async inviteByEmail(input: { email: string; organization: string }): Promise { + async inviteByEmail(input: { + email: string; + organization: string; + }): Promise { await this.authManager.ensureOrganizationAccess({ scope: OrganizationAccessScope.MEMBERS, organization: input.organization, }); const { email } = input; - this.logger.info('Inviting to the organization (email=%s, organization=%s)', email, input.organization); + this.logger.info( + 'Inviting to the organization (email=%s, organization=%s)', + email, + input.organization, + ); const organization = await this.getOrganization({ organization: input.organization, }); @@ -444,7 +460,7 @@ export class OrganizationManager { async deleteMembers( selector: { users: readonly string[]; - } & OrganizationSelector + } & OrganizationSelector, ): Promise { this.logger.info('Deleting a member from an organization (selector=%o)', selector); await this.authManager.ensureOrganizationAccess({ @@ -482,7 +498,7 @@ export class OrganizationManager { }, }); } - }) + }), ); // Because we checked the access before, it's stale by now @@ -499,7 +515,7 @@ export class OrganizationManager { organizationScopes: readonly OrganizationAccessScope[]; projectScopes: readonly ProjectAccessScope[]; targetScopes: readonly TargetAccessScope[]; - } & OrganizationSelector + } & OrganizationSelector, ) { this.logger.info('Updating a member access in an organization (input=%o)', input); await this.authManager.ensureOrganizationAccess({ @@ -527,7 +543,9 @@ export class OrganizationManager { // Check if the current user has rights to update these member scopes // User can't manage other user's scope if he's missing the scope as well - const currentUserMissingScopes = modifiedScopes.filter(scope => !currentMember.scopes.includes(scope)); + const currentUserMissingScopes = modifiedScopes.filter( + scope => !currentMember.scopes.includes(scope), + ); if (currentUserMissingScopes.length > 0) { this.logger.debug(`Logged user scopes: %o`, currentMember.scopes); diff --git a/packages/services/api/src/modules/organization/resolvers.ts b/packages/services/api/src/modules/organization/resolvers.ts index 93da85276..8238651fa 100644 --- a/packages/services/api/src/modules/organization/resolvers.ts +++ b/packages/services/api/src/modules/organization/resolvers.ts @@ -100,7 +100,9 @@ export const resolvers: OrganizationModule.Resolvers = { if (!result.success) { return { error: { - message: result.error.formErrors.fieldErrors.name?.[0] ?? 'Changing the organization name failed.', + message: + result.error.formErrors.fieldErrors.name?.[0] ?? + 'Changing the organization name failed.', }, }; } @@ -215,16 +217,22 @@ export const resolvers: OrganizationModule.Resolvers = { return !!organization.id; }, owner(organization, _, { injector }) { - return injector.get(OrganizationManager).getOrganizationOwner({ organization: organization.id }); + return injector + .get(OrganizationManager) + .getOrganizationOwner({ organization: organization.id }); }, async me(organization, _, { injector }) { const me = await injector.get(AuthManager).getCurrentUser(); - const members = await injector.get(OrganizationManager).getOrganizationMembers({ organization: organization.id }); + const members = await injector + .get(OrganizationManager) + .getOrganizationMembers({ organization: organization.id }); return members.find(m => m.id === me.id)!; }, members(organization, _, { injector }) { - return injector.get(OrganizationManager).getOrganizationMembers({ organization: organization.id }); + return injector + .get(OrganizationManager) + .getOrganizationMembers({ organization: organization.id }); }, async invitations(organization, _, { injector }) { const invitations = await injector.get(OrganizationManager).getInvitations({ @@ -239,7 +247,9 @@ export const resolvers: OrganizationModule.Resolvers = { }, OrganizationInvitation: { id(invitation) { - return Buffer.from([invitation.organization_id, invitation.email, invitation.code].join(':')).toString('hex'); + return Buffer.from( + [invitation.organization_id, invitation.email, invitation.code].join(':'), + ).toString('hex'); }, createdAt(invitation) { return invitation.created_at; diff --git a/packages/services/api/src/modules/persisted-operations/module.graphql.ts b/packages/services/api/src/modules/persisted-operations/module.graphql.ts index 0e4c4ac5d..15a8eb74d 100644 --- a/packages/services/api/src/modules/persisted-operations/module.graphql.ts +++ b/packages/services/api/src/modules/persisted-operations/module.graphql.ts @@ -5,11 +5,15 @@ export default gql` """ Requires API Token """ - publishPersistedOperations(input: [PublishPersistedOperationInput!]!): PublishPersistedOperationPayload! + publishPersistedOperations( + input: [PublishPersistedOperationInput!]! + ): PublishPersistedOperationPayload! """ Requires API Token """ - deletePersistedOperation(selector: PersistedOperationSelectorInput!): DeletePersistedOperationPayload! + deletePersistedOperation( + selector: PersistedOperationSelectorInput! + ): DeletePersistedOperationPayload! } extend type Query { diff --git a/packages/services/api/src/modules/persisted-operations/providers/persisted-operation-manager.ts b/packages/services/api/src/modules/persisted-operations/providers/persisted-operation-manager.ts index 16299d39d..f2c3f8af8 100644 --- a/packages/services/api/src/modules/persisted-operations/providers/persisted-operation-manager.ts +++ b/packages/services/api/src/modules/persisted-operations/providers/persisted-operation-manager.ts @@ -5,7 +5,11 @@ import { PersistedOperationsModule } from '../__generated__/types'; import type { PersistedOperation } from '../../../shared/entities'; import { AuthManager } from '../../auth/providers/auth-manager'; import { Logger } from '../../shared/providers/logger'; -import { PersistedOperationSelector, ProjectSelector, Storage } from '../../shared/providers/storage'; +import { + PersistedOperationSelector, + ProjectSelector, + Storage, +} from '../../shared/providers/storage'; import { ProjectAccessScope } from '../../auth/providers/project-access'; /** @@ -25,13 +29,13 @@ export class PersistedOperationManager { async createPersistedOperations( operationList: readonly PersistedOperationsModule.PublishPersistedOperationInput[], project: string, - organization: string + organization: string, ): Promise { this.logger.info( 'Creating persisted operations (project=%s, organization=%s, size=%s)', project, organization, - operationList.length + operationList.length, ); await this.authManager.ensureProjectAccess({ project, @@ -68,12 +72,14 @@ export class PersistedOperationManager { const publishedOperations = await Promise.all( operations .filter(op => hashesToPublish.includes(op.operationHash)) - .map(operation => this.storage.insertPersistedOperation(operation)) + .map(operation => this.storage.insertPersistedOperation(operation)), ); const unchangedOperations = await this.getSelectedPersistedOperations( { organization, project }, - operations.filter(op => !hashesToPublish.includes(op.operationHash)).map(op => op.operationHash) + operations + .filter(op => !hashesToPublish.includes(op.operationHash)) + .map(op => op.operationHash), ); const total = operations.length; const unchanged = total - hashesToPublish.length; @@ -96,7 +102,7 @@ export class PersistedOperationManager { 'Deleting an operation (operation=%s, project=%s, organization=%s)', operation, project, - organization + organization, ); await this.authManager.ensureProjectAccess({ project, @@ -116,7 +122,7 @@ export class PersistedOperationManager { async comparePersistedOperations( selector: ProjectSelector & { hashes: readonly string[]; - } + }, ): Promise { this.logger.debug('Fetching persisted operations (selector=%o)', selector); await this.authManager.ensureProjectAccess({ @@ -146,9 +152,13 @@ export class PersistedOperationManager { private async getSelectedPersistedOperations( selector: ProjectSelector, - hashes: readonly string[] + hashes: readonly string[], ): Promise { - this.logger.debug('Fetching selected persisted operations (selector=%o, size=%s)', selector, hashes.length); + this.logger.debug( + 'Fetching selected persisted operations (selector=%o, size=%s)', + selector, + hashes.length, + ); await this.authManager.ensureProjectAccess({ ...selector, scope: ProjectAccessScope.OPERATIONS_STORE_READ, diff --git a/packages/services/api/src/modules/persisted-operations/resolvers.ts b/packages/services/api/src/modules/persisted-operations/resolvers.ts index 86cc265a1..fa5406a43 100644 --- a/packages/services/api/src/modules/persisted-operations/resolvers.ts +++ b/packages/services/api/src/modules/persisted-operations/resolvers.ts @@ -74,7 +74,9 @@ export const resolvers: PersistedOperationsModule.Resolvers = { injector.get(ProjectManager).getProjectIdByToken(), ]); - return injector.get(PersistedOperationManager).createPersistedOperations(input, project, organization); + return injector + .get(PersistedOperationManager) + .createPersistedOperations(input, project, organization); }, async deletePersistedOperation(_, { selector }, { injector }) { const translator = injector.get(IdTranslator); @@ -84,11 +86,13 @@ export const resolvers: PersistedOperationsModule.Resolvers = { translator.translatePersistedOperationHash(selector), ]); - const persistedOperation = await injector.get(PersistedOperationManager).deletePersistedOperation({ - organization: organizationId, - project: projectId, - operation: operationId, - }); + const persistedOperation = await injector + .get(PersistedOperationManager) + .deletePersistedOperation({ + organization: organizationId, + project: projectId, + operation: operationId, + }); return { selector: { diff --git a/packages/services/api/src/modules/project/module.graphql.ts b/packages/services/api/src/modules/project/module.graphql.ts index 8a29e9ed2..4d1f2b018 100644 --- a/packages/services/api/src/modules/project/module.graphql.ts +++ b/packages/services/api/src/modules/project/module.graphql.ts @@ -9,7 +9,9 @@ export default gql` extend type Mutation { createProject(input: CreateProjectInput!): CreateProjectResult! updateProjectName(input: UpdateProjectNameInput!): UpdateProjectNameResult! - updateProjectGitRepository(input: UpdateProjectGitRepositoryInput!): UpdateProjectGitRepositoryResult! + updateProjectGitRepository( + input: UpdateProjectGitRepositoryInput! + ): UpdateProjectGitRepositoryResult! deleteProject(selector: ProjectSelectorInput!): DeleteProjectPayload! } diff --git a/packages/services/api/src/modules/project/providers/project-manager.ts b/packages/services/api/src/modules/project/providers/project-manager.ts index 6051359ac..d00e0fa2b 100644 --- a/packages/services/api/src/modules/project/providers/project-manager.ts +++ b/packages/services/api/src/modules/project/providers/project-manager.ts @@ -29,7 +29,7 @@ export class ProjectManager { private authManager: AuthManager, private schemaManager: SchemaManager, private tokenStorage: TokenStorage, - private activityManager: ActivityManager + private activityManager: ActivityManager, ) { this.logger = logger.child({ source: 'ProjectManager' }); } @@ -39,7 +39,7 @@ export class ProjectManager { name: string; type: ProjectType; } & OrganizationSelector & - NullableAndPartial + NullableAndPartial, ): Promise { const { name, type, organization, buildUrl, validationUrl } = input; this.logger.info('Creating a project (input=%o)', input); @@ -144,7 +144,7 @@ export class ProjectManager { async updateName( input: { name: string; - } & ProjectSelector + } & ProjectSelector, ): Promise { const { name, organization, project } = input; this.logger.info('Updating a project name (input=%o)', input); @@ -185,7 +185,7 @@ export class ProjectManager { async updateGitRepository( input: { gitRepository?: string | null; - } & ProjectSelector + } & ProjectSelector, ): Promise { const { gitRepository, organization, project } = input; this.logger.info('Updating a project git repository (input=%o)', input); diff --git a/packages/services/api/src/modules/project/resolvers.ts b/packages/services/api/src/modules/project/resolvers.ts index a6283e13a..c7a779106 100644 --- a/packages/services/api/src/modules/project/resolvers.ts +++ b/packages/services/api/src/modules/project/resolvers.ts @@ -164,7 +164,8 @@ export const resolvers: ProjectModule.Resolvers & { ProjectType: any } = { if (!result.success) { return { error: { - message: result.error.formErrors.fieldErrors.gitRepository?.[0] ?? 'Please check your input.', + message: + result.error.formErrors.fieldErrors.gitRepository?.[0] ?? 'Please check your input.', }, }; } diff --git a/packages/services/api/src/modules/rate-limit/providers/rate-limit.provider.ts b/packages/services/api/src/modules/rate-limit/providers/rate-limit.provider.ts index 19b1c5648..7e69685f1 100644 --- a/packages/services/api/src/modules/rate-limit/providers/rate-limit.provider.ts +++ b/packages/services/api/src/modules/rate-limit/providers/rate-limit.provider.ts @@ -19,7 +19,7 @@ export class RateLimitProvider { constructor( logger: Logger, @Inject(RATE_LIMIT_SERVICE_CONFIG) - rateLimitServiceConfig: RateLimitServiceConfig + rateLimitServiceConfig: RateLimitServiceConfig, ) { this.logger = logger.child({ service: 'RateLimitProvider' }); this.rateLimit = rateLimitServiceConfig.endpoint @@ -43,7 +43,10 @@ export class RateLimitProvider { @sentry('RateLimitProvider.checkRateLimit') async checkRateLimit(input: RateLimitQueryInput<'checkRateLimit'>) { if (this.rateLimit === null) { - this.logger.warn(`Unable to check rate-limit for input: %o , service information is not available`, input); + this.logger.warn( + `Unable to check rate-limit for input: %o , service information is not available`, + input, + ); return { limited: false, diff --git a/packages/services/api/src/modules/rate-limit/providers/tokens.ts b/packages/services/api/src/modules/rate-limit/providers/tokens.ts index bc8200288..c9c8252e7 100644 --- a/packages/services/api/src/modules/rate-limit/providers/tokens.ts +++ b/packages/services/api/src/modules/rate-limit/providers/tokens.ts @@ -4,4 +4,6 @@ export interface RateLimitServiceConfig { endpoint: string | null; } -export const RATE_LIMIT_SERVICE_CONFIG = new InjectionToken('rate-limit-service-config'); +export const RATE_LIMIT_SERVICE_CONFIG = new InjectionToken( + 'rate-limit-service-config', +); diff --git a/packages/services/api/src/modules/schema/index.ts b/packages/services/api/src/modules/schema/index.ts index 874aa28ec..199afb863 100644 --- a/packages/services/api/src/modules/schema/index.ts +++ b/packages/services/api/src/modules/schema/index.ts @@ -13,5 +13,12 @@ export const schemaModule = createModule({ dirname: __dirname, typeDefs, resolvers, - providers: [SchemaManager, SchemaValidator, SchemaPublisher, Inspector, SchemaHelper, ...orchestrators], + providers: [ + SchemaManager, + SchemaValidator, + SchemaPublisher, + Inspector, + SchemaHelper, + ...orchestrators, + ], }); diff --git a/packages/services/api/src/modules/schema/module.graphql.ts b/packages/services/api/src/modules/schema/module.graphql.ts index 6ade39df0..57dc5d3fb 100644 --- a/packages/services/api/src/modules/schema/module.graphql.ts +++ b/packages/services/api/src/modules/schema/module.graphql.ts @@ -160,7 +160,11 @@ export default gql` github: Boolean } - union SchemaCheckPayload = SchemaCheckSuccess | SchemaCheckError | GitHubSchemaCheckSuccess | GitHubSchemaCheckError + union SchemaCheckPayload = + SchemaCheckSuccess + | SchemaCheckError + | GitHubSchemaCheckSuccess + | GitHubSchemaCheckError enum CriticalityLevel { Breaking diff --git a/packages/services/api/src/modules/schema/providers/inspector.ts b/packages/services/api/src/modules/schema/providers/inspector.ts index 04e142413..322de9f6d 100644 --- a/packages/services/api/src/modules/schema/providers/inspector.ts +++ b/packages/services/api/src/modules/schema/providers/inspector.ts @@ -21,7 +21,11 @@ const criticalityMap: Record = { export class Inspector { private logger: Logger; - constructor(logger: Logger, private operationsManager: OperationsManager, private targetManager: TargetManager) { + constructor( + logger: Logger, + private operationsManager: OperationsManager, + private targetManager: TargetManager, + ) { this.logger = logger.child({ service: 'Inspector' }); } @@ -29,7 +33,7 @@ export class Inspector { async diff( existing: GraphQLSchema, incoming: GraphQLSchema, - selector?: Types.TargetSelector + selector?: Types.TargetSelector, ): Promise { this.logger.debug('Comparing Schemas'); @@ -65,8 +69,18 @@ export class Inspector { this.logger.debug('Got the stats'); - function useStats({ type, field, argument }: { type: string; field?: string; argument?: string }) { - const stats = statsList!.find(s => s.field === field && s.type === type && s.argument === argument); + function useStats({ + type, + field, + argument, + }: { + type: string; + field?: string; + argument?: string; + }) { + const stats = statsList!.find( + s => s.field === field && s.type === type && s.argument === argument, + ); if (!stats) { return NOT_BREAKING; diff --git a/packages/services/api/src/modules/schema/providers/orchestrators/custom.ts b/packages/services/api/src/modules/schema/providers/orchestrators/custom.ts index 51aac40c9..fca5094ec 100644 --- a/packages/services/api/src/modules/schema/providers/orchestrators/custom.ts +++ b/packages/services/api/src/modules/schema/providers/orchestrators/custom.ts @@ -47,7 +47,10 @@ export class CustomOrchestrator implements Orchestrator { } @sentry('CustomOrchestrator.validate') - async validate(schemas: SchemaObject[], config: CustomOrchestratorConfig): Promise { + async validate( + schemas: SchemaObject[], + config: CustomOrchestratorConfig, + ): Promise { this.logger.debug('Validating Custom Schemas'); return this.http.post(config.validationUrl, { responseType: 'json', @@ -80,7 +83,9 @@ export class CustomOrchestrator implements Orchestrator { if (hasErrors(response)) { throw new HiveError( - [`Schema couldn't be build:`, response.errors.map(error => `\t - ${error.message}`)].join('\n') + [`Schema couldn't be build:`, response.errors.map(error => `\t - ${error.message}`)].join( + '\n', + ), ); } diff --git a/packages/services/api/src/modules/schema/providers/orchestrators/federation.ts b/packages/services/api/src/modules/schema/providers/orchestrators/federation.ts index c9cc25cf0..1aeee08ae 100644 --- a/packages/services/api/src/modules/schema/providers/orchestrators/federation.ts +++ b/packages/services/api/src/modules/schema/providers/orchestrators/federation.ts @@ -27,7 +27,7 @@ export class FederationOrchestrator implements Orchestrator { constructor( logger: Logger, @Inject(SCHEMA_SERVICE_CONFIG) serviceConfig: SchemaServiceConfig, - @Inject(CONTEXT) context: GraphQLModules.ModuleContext + @Inject(CONTEXT) context: GraphQLModules.ModuleContext, ) { this.logger = logger.child({ service: 'FederationOrchestrator' }); this.schemaService = createTRPCClient({ diff --git a/packages/services/api/src/modules/schema/providers/orchestrators/single.ts b/packages/services/api/src/modules/schema/providers/orchestrators/single.ts index 917eac1a3..7f47d6db4 100644 --- a/packages/services/api/src/modules/schema/providers/orchestrators/single.ts +++ b/packages/services/api/src/modules/schema/providers/orchestrators/single.ts @@ -22,7 +22,7 @@ export class SingleOrchestrator implements Orchestrator { constructor( logger: Logger, @Inject(SCHEMA_SERVICE_CONFIG) serviceConfig: SchemaServiceConfig, - @Inject(CONTEXT) context: GraphQLModules.ModuleContext + @Inject(CONTEXT) context: GraphQLModules.ModuleContext, ) { this.logger = logger.child({ service: 'SingleOrchestrator' }); this.schemaService = createTRPCClient({ diff --git a/packages/services/api/src/modules/schema/providers/orchestrators/stitching.ts b/packages/services/api/src/modules/schema/providers/orchestrators/stitching.ts index a145f5701..682d1d561 100644 --- a/packages/services/api/src/modules/schema/providers/orchestrators/stitching.ts +++ b/packages/services/api/src/modules/schema/providers/orchestrators/stitching.ts @@ -21,7 +21,7 @@ export class StitchingOrchestrator implements Orchestrator { constructor( logger: Logger, @Inject(SCHEMA_SERVICE_CONFIG) serviceConfig: SchemaServiceConfig, - @Inject(CONTEXT) context: GraphQLModules.ModuleContext + @Inject(CONTEXT) context: GraphQLModules.ModuleContext, ) { this.logger = logger.child({ service: 'StitchingOrchestrator' }); this.schemaService = createTRPCClient({ diff --git a/packages/services/api/src/modules/schema/providers/orchestrators/tokens.ts b/packages/services/api/src/modules/schema/providers/orchestrators/tokens.ts index e60ae48d7..127ad3dac 100644 --- a/packages/services/api/src/modules/schema/providers/orchestrators/tokens.ts +++ b/packages/services/api/src/modules/schema/providers/orchestrators/tokens.ts @@ -4,4 +4,6 @@ export interface SchemaServiceConfig { endpoint: string; } -export const SCHEMA_SERVICE_CONFIG = new InjectionToken('schema-service-config'); +export const SCHEMA_SERVICE_CONFIG = new InjectionToken( + 'schema-service-config', +); diff --git a/packages/services/api/src/modules/schema/providers/schema-manager.ts b/packages/services/api/src/modules/schema/providers/schema-manager.ts index f47782de1..64343e935 100644 --- a/packages/services/api/src/modules/schema/providers/schema-manager.ts +++ b/packages/services/api/src/modules/schema/providers/schema-manager.ts @@ -6,7 +6,12 @@ import { atomic, stringifySelector } from '../../../shared/helpers'; import { HiveError } from '../../../shared/errors'; import { AuthManager } from '../../auth/providers/auth-manager'; import { Logger } from '../../shared/providers/logger'; -import { Storage, TargetSelector, ProjectSelector, OrganizationSelector } from '../../shared/providers/storage'; +import { + Storage, + TargetSelector, + ProjectSelector, + OrganizationSelector, +} from '../../shared/providers/storage'; import { CustomOrchestrator } from './orchestrators/custom'; import { FederationOrchestrator } from './orchestrators/federation'; import { SingleOrchestrator } from './orchestrators/single'; @@ -49,7 +54,7 @@ export class SchemaManager { private stitchingOrchestrator: StitchingOrchestrator, private federationOrchestrator: FederationOrchestrator, private customOrchestrator: CustomOrchestrator, - private crypto: CryptoProvider + private crypto: CryptoProvider, ) { this.logger = logger.child({ source: 'SchemaManager' }); } @@ -67,7 +72,7 @@ export class SchemaManager { selector: { version: string; includeMetadata?: boolean; - } & TargetSelector + } & TargetSelector, ) { this.logger.debug('Fetching schemas (selector=%o)', selector); await this.authManager.ensureTargetAccess({ @@ -80,7 +85,7 @@ export class SchemaManager { async getSchemasOfPreviousVersion( selector: { version: string; - } & TargetSelector + } & TargetSelector, ) { this.logger.debug('Fetching schemas from the previous version (selector=%o)', selector); await this.authManager.ensureTargetAccess({ @@ -204,7 +209,9 @@ export class SchemaManager { }; } - async updateSchemaVersionStatus(input: TargetSelector & { version: string; valid: boolean }): Promise { + async updateSchemaVersionStatus( + input: TargetSelector & { version: string; valid: boolean }, + ): Promise { this.logger.debug('Updating schema version status (input=%o)', input); await this.authManager.ensureTargetAccess({ ...input, @@ -224,7 +231,7 @@ export class SchemaManager { version: string; commit: string; url?: string | null; - } + }, ) { this.logger.debug('Updating schema version status (input=%o)', input); await this.authManager.ensureTargetAccess({ @@ -267,10 +274,11 @@ export class SchemaManager { url?: string | null; base_schema: string | null; metadata: string | null; - } & TargetSelector + } & TargetSelector, ) { this.logger.info('Creating a new version (input=%o)', lodash.omit(input, ['schema'])); - const { valid, project, organization, target, commit, schema, author, commits, url, metadata } = input; + const { valid, project, organization, target, commit, schema, author, commits, url, metadata } = + input; let service = input.service; await this.authManager.ensureTargetAccess({ @@ -338,7 +346,7 @@ export class SchemaManager { service?: string | null; url?: string | null; metadata: string | null; - } & TargetSelector + } & TargetSelector, ) { this.logger.info('Inserting schema (input=%o)', lodash.omit(input, ['schema'])); await this.authManager.ensureTargetAccess({ @@ -371,7 +379,7 @@ export class SchemaManager { name: string; newName: string; projectType: ProjectType; - } + }, ) { this.logger.debug('Updating service name (input=%o)', input); await this.authManager.ensureTargetAccess({ @@ -379,8 +387,13 @@ export class SchemaManager { scope: TargetAccessScope.REGISTRY_WRITE, }); - if (input.projectType !== ProjectType.FEDERATION && input.projectType !== ProjectType.STITCHING) { - throw new HiveError(`Project type "${input.projectType}" doesn't support service name updates`); + if ( + input.projectType !== ProjectType.FEDERATION && + input.projectType !== ProjectType.STITCHING + ) { + throw new HiveError( + `Project type "${input.projectType}" doesn't support service name updates`, + ); } const schemas = await this.storage.getSchemasOfVersion({ @@ -418,7 +431,7 @@ export class SchemaManager { completeGetStartedCheck( selector: OrganizationSelector & { step: 'publishingSchema' | 'checkingSchema'; - } + }, ): Promise { return this.storage.completeGetStartedStep(selector); } @@ -441,7 +454,7 @@ export class SchemaManager { input: ProjectSelector & { endpoint: string; secret: string; - } + }, ) { this.logger.debug('Enabling external composition (input=%o)', lodash.omit(input, ['secret'])); await this.authManager.ensureProjectAccess({ diff --git a/packages/services/api/src/modules/schema/providers/schema-publisher.ts b/packages/services/api/src/modules/schema/providers/schema-publisher.ts index 28ad665d5..6c779b1c7 100644 --- a/packages/services/api/src/modules/schema/providers/schema-publisher.ts +++ b/packages/services/api/src/modules/schema/providers/schema-publisher.ts @@ -31,7 +31,8 @@ import { SCHEMA_MODULE_CONFIG } from './config'; import { SchemaHelper } from './schema-helper'; import { HiveError } from '../../../shared/errors'; -type CheckInput = Omit & TargetSelector; +type CheckInput = Omit & + TargetSelector; type PublishInput = Types.SchemaPublishInput & TargetSelector & { @@ -62,7 +63,7 @@ export class SchemaPublisher { private gitHubIntegrationManager: GitHubIntegrationManager, private idempotentRunner: IdempotentRunner, private helper: SchemaHelper, - @Inject(SCHEMA_MODULE_CONFIG) private schemaModuleConfig: SchemaModuleConfig + @Inject(SCHEMA_MODULE_CONFIG) private schemaModuleConfig: SchemaModuleConfig, ) { this.logger = logger.child({ service: 'SchemaPublisher' }); } @@ -152,7 +153,9 @@ export class SchemaPublisher { summary = this.changesToMarkdown(validationResult.changes); } } else { - title = `Detected ${validationResult.errors.length} error${validationResult.errors.length === 1 ? '' : 's'}`; + title = `Detected ${validationResult.errors.length} error${ + validationResult.errors.length === 1 ? '' : 's' + }`; summary = [ validationResult.errors ? this.errorsToMarkdown(validationResult.errors) : null, validationResult.changes ? this.changesToMarkdown(validationResult.changes) : null, @@ -242,12 +245,12 @@ export class SchemaPublisher { project.type === ProjectType.FEDERATION ? await this.schemaManager.matchOrchestrator(project.type).supergraph( schemas.map(s => this.helper.createSchemaObject(s)), - project.externalComposition + project.externalComposition, ) : null, schemas, }, - span + span, ); } catch (error) { this.logger.error(`Failed to sync with CDN ` + String(error), error); @@ -296,7 +299,7 @@ export class SchemaPublisher { project.type === ProjectType.FEDERATION ? await this.schemaManager.matchOrchestrator(project.type).supergraph( schemas.map(s => this.helper.createSchemaObject(s)), - project.externalComposition + project.externalComposition, ) : null, schemas, @@ -312,7 +315,9 @@ export class SchemaPublisher { try { return JSON.parse(metadataRaw); } catch (e) { - throw new Error(`Failed to parse schema metadata JSON: ${e instanceof Error ? e.message : e}`); + throw new Error( + `Failed to parse schema metadata JSON: ${e instanceof Error ? e.message : e}`, + ); } } @@ -402,7 +407,10 @@ export class SchemaPublisher { }; } - if (project.type === ProjectType.FEDERATION && (lodash.isNil(input.url) || input.url?.trim() === '')) { + if ( + project.type === ProjectType.FEDERATION && + (lodash.isNil(input.url) || input.url?.trim() === '') + ) { this.logger.debug('Detected missing service url'); const missingServiceUrlMessage = `Can not publish schema for a '${project.type.toLowerCase()}' project without a service url.`; @@ -440,7 +448,10 @@ export class SchemaPublisher { metadata: this.validateMetadata(input.metadata), }; - const { schemas: newSchemas, swappedSchema: previousSchema } = updateSchemas(schemas, incomingSchema); + const { schemas: newSchemas, swappedSchema: previousSchema } = updateSchemas( + schemas, + incomingSchema, + ); this.logger.debug(`Produced ${newSchemas.length} new schemas`); @@ -473,7 +484,9 @@ export class SchemaPublisher { const { changes, errors, valid } = result; const hasNewUrl = - !!latest.version && !!previousSchema && (previousSchema.url ?? null) !== (incomingSchema.url ?? null); + !!latest.version && + !!previousSchema && + (previousSchema.url ?? null) !== (incomingSchema.url ?? null); const hasSchemaChanges = changes.length > 0; const hasErrors = errors.length > 0; const isForced = input.force === true; @@ -559,7 +572,9 @@ export class SchemaPublisher { if (valid && hasNewUrl) { updates.push( - `Updated: New service url: ${incomingSchema.url ?? 'empty'} (previously: ${previousSchema!.url ?? 'empty'})` + `Updated: New service url: ${incomingSchema.url ?? 'empty'} (previously: ${ + previousSchema!.url ?? 'empty' + })`, ); } @@ -577,7 +592,8 @@ export class SchemaPublisher { } const linkToWebsite = - typeof this.schemaModuleConfig.schemaPublishLink === 'function' && typeof newVersionId === 'string' + typeof this.schemaModuleConfig.schemaPublishLink === 'function' && + typeof newVersionId === 'string' ? this.schemaModuleConfig.schemaPublishLink({ organization: { cleanId: organization.cleanId, @@ -702,7 +718,7 @@ export class SchemaPublisher { project.type === ProjectType.FEDERATION ? await orchestrator.supergraph( schemas.map(s => this.helper.createSchemaObject(s)), - project.externalComposition + project.externalComposition, ) : null, }); @@ -724,7 +740,7 @@ export class SchemaPublisher { schemas: readonly Schema[]; supergraph?: string | null; }, - span?: Span + span?: Span, ) { const publishMetadata = async () => { const metadata: Array> = []; @@ -741,7 +757,7 @@ export class SchemaPublisher { resourceType: 'metadata', value: JSON.stringify(metadata.length === 1 ? metadata[0] : metadata), }, - span + span, ); } }; @@ -764,10 +780,10 @@ export class SchemaPublisher { url: schemas[0].url, name: schemas[0].service, date: schemas[0].date, - } + }, ), }, - span + span, ); }; @@ -784,8 +800,8 @@ export class SchemaPublisher { resourceType: 'supergraph', value: supergraph, }, - span - ) + span, + ), ); } } @@ -837,7 +853,10 @@ export class SchemaPublisher { } } else { title = `Detected ${errors.length} error${errors.length === 1 ? '' : 's'}`; - summary = [errors ? this.errorsToMarkdown(errors) : null, changes ? this.changesToMarkdown(changes) : null] + summary = [ + errors ? this.errorsToMarkdown(errors) : null, + changes ? this.changesToMarkdown(changes) : null, + ] .filter(Boolean) .join('\n\n'); } @@ -883,7 +902,10 @@ export class SchemaPublisher { const dangerousChanges = changes.filter(filterChangesByLevel('Dangerous')); const safeChanges = changes.filter(filterChangesByLevel('Safe')); - const lines: string[] = [`## Found ${changes.length} change${changes.length > 1 ? 's' : ''}`, '']; + const lines: string[] = [ + `## Found ${changes.length} change${changes.length > 1 ? 's' : ''}`, + '', + ]; if (breakingChanges.length) { lines.push(`Breaking: ${breakingChanges.length}`); @@ -918,5 +940,7 @@ function filterChangesByLevel(level: Types.CriticalityLevel) { } function writeChanges(type: string, changes: readonly Types.SchemaChange[], lines: string[]): void { - lines.push(...['', `### ${type} changes`].concat(changes.map(change => ` - ${bolderize(change.message)}`))); + lines.push( + ...['', `### ${type} changes`].concat(changes.map(change => ` - ${bolderize(change.message)}`)), + ); } diff --git a/packages/services/api/src/modules/schema/providers/schema-validator.ts b/packages/services/api/src/modules/schema/providers/schema-validator.ts index a7edfe088..b1ca8ef74 100644 --- a/packages/services/api/src/modules/schema/providers/schema-validator.ts +++ b/packages/services/api/src/modules/schema/providers/schema-validator.ts @@ -63,7 +63,9 @@ export class SchemaValidator { target: schema.target, }; }); - const afterSchemasWithBase: SchemaObject[] = afterWithBase.map(s => this.helper.createSchemaObject(s)); + const afterSchemasWithBase: SchemaObject[] = afterWithBase.map(s => + this.helper.createSchemaObject(s), + ); const afterSchemas: SchemaObject[] = after.map(s => this.helper.createSchemaObject(s)); const beforeSchemas: SchemaObject[] = before.map(s => this.helper.createSchemaObject(s)); @@ -80,7 +82,7 @@ export class SchemaValidator { const errors = await orchestrator.validate( afterSchemasWithBase, - project.externalComposition.enabled ? project.externalComposition : null + project.externalComposition.enabled ? project.externalComposition : null, ); if (isInitialSchema) { @@ -99,13 +101,19 @@ export class SchemaValidator { orchestrator.build(afterSchemas, project.externalComposition), ]); if (existingSchema) { - changes = await this.inspector.diff(buildSchema(existingSchema), buildSchema(incomingSchema), selector); + changes = await this.inspector.diff( + buildSchema(existingSchema), + buildSchema(incomingSchema), + selector, + ); const hasBreakingChanges = changes.some(change => change.criticality === 'Breaking'); if (hasBreakingChanges) { if (experimental_acceptBreakingChanges) { - this.logger.debug('Schema contains breaking changes, but the experimental safe mode is enabled'); + this.logger.debug( + 'Schema contains breaking changes, but the experimental safe mode is enabled', + ); } else { changes.forEach(change => { if (change.criticality === 'Breaking') { diff --git a/packages/services/api/src/modules/schema/resolvers.ts b/packages/services/api/src/modules/schema/resolvers.ts index cc61db03b..aff55888a 100644 --- a/packages/services/api/src/modules/schema/resolvers.ts +++ b/packages/services/api/src/modules/schema/resolvers.ts @@ -53,9 +53,10 @@ async function usage( name: string; }; }> - > + >, ) { - const coordinate = 'parent' in source ? `${source.parent.coordinate}.${source.entity.name}` : source.entity.name; + const coordinate = + 'parent' in source ? `${source.parent.coordinate}.${source.entity.name}` : source.entity.name; const usage = (await source.usage)[coordinate]; return usage @@ -97,7 +98,10 @@ export const resolvers: SchemaModule.Resolvers = { ]); const token = injector.get(AuthManager).ensureApiToken(); - const checksum = createHash('md5').update(JSON.stringify(input)).update(token).digest('base64'); + const checksum = createHash('md5') + .update(JSON.stringify(input)) + .update(token) + .digest('base64'); // We only want to resolve to SchemaPublishMissingUrlError if it is selected by the operation. // NOTE: This should be removed once the usage of cli versions that don't request on 'SchemaPublishMissingUrlError' is becomes pretty low. @@ -140,7 +144,8 @@ export const resolvers: SchemaModule.Resolvers = { if (!result.success) { return { error: { - message: result.error.formErrors.fieldErrors?.newBase?.[0] ?? 'Please check your input.', + message: + result.error.formErrors.fieldErrors?.newBase?.[0] ?? 'Please check your input.', }, }; } @@ -304,11 +309,11 @@ export const resolvers: SchemaModule.Resolvers = { return Promise.all([ orchestrator.build( schemasBefore.map(s => helper.createSchemaObject(s)), - project.externalComposition + project.externalComposition, ), orchestrator.build( schemasAfter.map(s => helper.createSchemaObject(s)), - project.externalComposition + project.externalComposition, ), ]).catch(reason => { if (reason instanceof SchemaBuildError) { @@ -358,12 +363,12 @@ export const resolvers: SchemaModule.Resolvers = { schemasBefore.length ? orchestrator.build( schemasBefore.map(s => helper.createSchemaObject(s)), - project.externalComposition + project.externalComposition, ) : null, orchestrator.build( schemasAfter.map(s => helper.createSchemaObject(s)), - project.externalComposition + project.externalComposition, ), ]).catch(reason => { if (reason instanceof SchemaBuildError) { @@ -488,7 +493,7 @@ export const resolvers: SchemaModule.Resolvers = { return orchestrator.supergraph( schemas.map(s => helper.createSchemaObject(s)), - project.externalComposition + project.externalComposition, ); }, async sdl(version, _, { injector }) { @@ -511,7 +516,7 @@ export const resolvers: SchemaModule.Resolvers = { return ( await orchestrator.build( schemas.map(s => helper.createSchemaObject(s)), - project.externalComposition + project.externalComposition, ) ).raw; }, @@ -537,7 +542,7 @@ export const resolvers: SchemaModule.Resolvers = { const schema = await orchestrator.build( schemas.map(s => helper.createSchemaObject(s)), - project.externalComposition + project.externalComposition, ); return { diff --git a/packages/services/api/src/modules/shared/__tests__/crypto.spec.ts b/packages/services/api/src/modules/shared/__tests__/crypto.spec.ts index 1406bfbde..0484307d9 100644 --- a/packages/services/api/src/modules/shared/__tests__/crypto.spec.ts +++ b/packages/services/api/src/modules/shared/__tests__/crypto.spec.ts @@ -3,20 +3,26 @@ import { testkit } from 'graphql-modules'; import { CryptoProvider, encryptionSecretProvider } from '../providers/crypto'; test('should decrypt encrypted value', () => { - const cryptoProvider = testkit.testInjector([CryptoProvider, encryptionSecretProvider('secret')]).get(CryptoProvider); + const cryptoProvider = testkit + .testInjector([CryptoProvider, encryptionSecretProvider('secret')]) + .get(CryptoProvider); const encrypted = cryptoProvider.encrypt('foo'); expect(cryptoProvider.decrypt(encrypted)).toBe('foo'); }); test('should read raw value when decrypting (when possiblyRaw is enabled)', () => { - const cryptoProvider = testkit.testInjector([CryptoProvider, encryptionSecretProvider('secret')]).get(CryptoProvider); + const cryptoProvider = testkit + .testInjector([CryptoProvider, encryptionSecretProvider('secret')]) + .get(CryptoProvider); expect(cryptoProvider.decrypt('foo', true)).toBe('foo'); }); test('should NOT read raw value when decrypting', () => { - const cryptoProvider = testkit.testInjector([CryptoProvider, encryptionSecretProvider('secret')]).get(CryptoProvider); + const cryptoProvider = testkit + .testInjector([CryptoProvider, encryptionSecretProvider('secret')]) + .get(CryptoProvider); expect(() => { cryptoProvider.decrypt('foo'); diff --git a/packages/services/api/src/modules/shared/providers/http-client.ts b/packages/services/api/src/modules/shared/providers/http-client.ts index 5d3b136dd..d13dd2356 100644 --- a/packages/services/api/src/modules/shared/providers/http-client.ts +++ b/packages/services/api/src/modules/shared/providers/http-client.ts @@ -66,7 +66,7 @@ export class HttpClient { span.setStatus(error instanceof TimeoutError ? 'deadline_exceeded' : 'internal_error'); span.finish(); return Promise.reject(error); - } + }, ); } } diff --git a/packages/services/api/src/modules/shared/providers/id-translator.ts b/packages/services/api/src/modules/shared/providers/id-translator.ts index a2df95007..cdfe62732 100644 --- a/packages/services/api/src/modules/shared/providers/id-translator.ts +++ b/packages/services/api/src/modules/shared/providers/id-translator.ts @@ -1,5 +1,10 @@ import { Injectable, Scope } from 'graphql-modules'; -import type { OrganizationSelector, ProjectSelector, TargetSelector, PersistedOperationSelector } from './storage'; +import type { + OrganizationSelector, + ProjectSelector, + TargetSelector, + PersistedOperationSelector, +} from './storage'; import { Storage } from './storage'; import { cache, filterSelector } from '../../../shared/helpers'; import { Logger } from './logger'; @@ -15,13 +20,19 @@ export class IdTranslator { @cache(selector => selector.organization) translateOrganizationId(selector: OrganizationSelector) { - this.logger.debug('Translating Organization Clean ID (selector=%o)', filterSelector('organization', selector)); + this.logger.debug( + 'Translating Organization Clean ID (selector=%o)', + filterSelector('organization', selector), + ); return this.storage.getOrganizationId(selector); } @cache(selector => [selector.organization, selector.project].join(',')) translateProjectId(selector: ProjectSelector) { - this.logger.debug('Translating Project Clean ID (selector=%o)', filterSelector('project', selector)); + this.logger.debug( + 'Translating Project Clean ID (selector=%o)', + filterSelector('project', selector), + ); return this.storage.getProjectId(selector); } @@ -29,23 +40,28 @@ export class IdTranslator { TargetSelector & { useIds?: boolean; } - >(selector => [selector.organization, selector.project, selector.target, selector.useIds].join(',')) + >(selector => + [selector.organization, selector.project, selector.target, selector.useIds].join(','), + ) translateTargetId( selector: TargetSelector & { useIds?: boolean; - } + }, ) { - this.logger.debug('Translating Target Clean ID (selector=%o)', filterSelector('target', selector)); + this.logger.debug( + 'Translating Target Clean ID (selector=%o)', + filterSelector('target', selector), + ); return this.storage.getTargetId(selector); } @cache(selector => - [selector.organization, selector.project, selector.operation].join(',') + [selector.organization, selector.project, selector.operation].join(','), ) translatePersistedOperationHash(selector: PersistedOperationSelector) { this.logger.debug( 'Translating Persisted Operation Hash (selector=%o)', - filterSelector('persistedOperation', selector) + filterSelector('persistedOperation', selector), ); return this.storage.getPersistedOperationId(selector); } diff --git a/packages/services/api/src/modules/shared/providers/idempotent-runner.ts b/packages/services/api/src/modules/shared/providers/idempotent-runner.ts index b22155684..5d4a04d4e 100644 --- a/packages/services/api/src/modules/shared/providers/idempotent-runner.ts +++ b/packages/services/api/src/modules/shared/providers/idempotent-runner.ts @@ -73,7 +73,11 @@ export class IdempotentRunner { }); } - private async set(identifier: string, job: JobPending | JobCompleted, ttl: number): Promise { + private async set( + identifier: string, + job: JobPending | JobCompleted, + ttl: number, + ): Promise { // Set the job as pending if (job.status === JobStatus.PENDING) { // SET if Not eXists @@ -124,13 +128,18 @@ export class IdempotentRunner { ttl: number; context: JobExecutorContext; }): Promise { - this.logger.debug('Starting new job (id=%s, traceId=%s, attempt=%s)', identifier, traceId, context.attempt); + this.logger.debug( + 'Starting new job (id=%s, traceId=%s, attempt=%s)', + identifier, + traceId, + context.attempt, + ); if (context.attempt > 3) { this.logger.error( 'Job failed after 3 attempts (id=%s, traceId=%s, attempt=%s)', identifier, traceId, - context.attempt + context.attempt, ); throw new Error(`Job failed after 3 attempts`); } @@ -138,14 +147,24 @@ export class IdempotentRunner { let job = await this.get(identifier); if (!job) { - this.logger.debug('Job not found (id=%s, traceId=%s, attempt=%s)', identifier, traceId, context.attempt); - this.logger.debug('Trying to create a job (id=%s, traceId=%s, attempt=%s)', identifier, traceId, context.attempt); + this.logger.debug( + 'Job not found (id=%s, traceId=%s, attempt=%s)', + identifier, + traceId, + context.attempt, + ); + this.logger.debug( + 'Trying to create a job (id=%s, traceId=%s, attempt=%s)', + identifier, + traceId, + context.attempt, + ); const created = await this.set( identifier, { status: JobStatus.PENDING, }, - ttl + ttl, ); if (!created) { @@ -164,13 +183,33 @@ export class IdempotentRunner { ttl, }); } else { - this.logger.debug('Job created (id=%s, traceId=%s, attempt=%s)', identifier, traceId, context.attempt); + this.logger.debug( + 'Job created (id=%s, traceId=%s, attempt=%s)', + identifier, + traceId, + context.attempt, + ); } - this.logger.debug('Executing job (id=%s, traceId=%s, attempt=%s)', identifier, traceId, context.attempt); + this.logger.debug( + 'Executing job (id=%s, traceId=%s, attempt=%s)', + identifier, + traceId, + context.attempt, + ); const payload = await executor(context).catch(async error => { - this.logger.debug('Job execution failed (id=%s, traceId=%s, error=%s)', identifier, traceId, error.message); - this.logger.debug('Deleting the job (id=%s, traceId=%s, attempt=%s)', identifier, traceId, context.attempt); + this.logger.debug( + 'Job execution failed (id=%s, traceId=%s, error=%s)', + identifier, + traceId, + error.message, + ); + this.logger.debug( + 'Deleting the job (id=%s, traceId=%s, attempt=%s)', + identifier, + traceId, + context.attempt, + ); await this.del(identifier); return await Promise.reject(error); }); @@ -179,7 +218,7 @@ export class IdempotentRunner { 'Marking job as completed (id=%s, traceId=%s, attempt=%s)', identifier, traceId, - context.attempt + context.attempt, ); await this.set( identifier, @@ -187,7 +226,7 @@ export class IdempotentRunner { status: JobStatus.COMPLETED, payload, }, - ttl + ttl, ); this.logger.debug('Job completed (id=%s, traceId=%s)', identifier, traceId); @@ -211,7 +250,7 @@ export class IdempotentRunner { 'Job not found, probably failed to complete (id=%s, traceId=%s, attempt=%s)', identifier, traceId, - context.attempt + context.attempt, ); context.attempt++; @@ -234,7 +273,7 @@ export class IdempotentRunner { identifier, traceId, context.attempt, - job.status + job.status, ); return job.payload; } diff --git a/packages/services/api/src/modules/shared/providers/storage.ts b/packages/services/api/src/modules/shared/providers/storage.ts index fa371e868..4219dc043 100644 --- a/packages/services/api/src/modules/shared/providers/storage.ts +++ b/packages/services/api/src/modules/shared/providers/storage.ts @@ -59,7 +59,11 @@ export interface Storage { }): Promise<'created' | 'no_action'>; getUserBySuperTokenId(_: { superTokensUserId: string }): Promise; - setSuperTokensUserId(_: { auth0UserId: string; superTokensUserId: string; externalUserId: string }): Promise; + setSuperTokensUserId(_: { + auth0UserId: string; + superTokensUserId: string; + externalUserId: string; + }): Promise; getUserWithoutAssociatedSuperTokenIdByAuth0Email(_: { email: string }): Promise; getUserById(_: { id: string }): Promise; @@ -68,7 +72,9 @@ export interface Storage { getOrganizationId(_: OrganizationSelector): Promise; getOrganizationByInviteCode(_: { inviteCode: string }): Promise; getOrganizationByCleanId(_: { cleanId: string }): Promise; - getOrganizationByGitHubInstallationId(_: { installationId: string }): Promise; + getOrganizationByGitHubInstallationId(_: { + installationId: string; + }): Promise; getOrganization(_: OrganizationSelector): Promise; getMyOrganization(_: { user: string }): Promise; getOrganizations(_: { user: string }): Promise; @@ -77,19 +83,23 @@ export interface Storage { user: string; scopes: ReadonlyArray; reservedNames: string[]; - } + }, ): Promise; deleteOrganization(_: OrganizationSelector): Promise; updateOrganizationName( - _: OrganizationSelector & Pick & { user: string } + _: OrganizationSelector & Pick & { user: string }, + ): Promise; + updateOrganizationPlan( + _: OrganizationSelector & Pick, ): Promise; - updateOrganizationPlan(_: OrganizationSelector & Pick): Promise; updateOrganizationRateLimits( - _: OrganizationSelector & Pick + _: OrganizationSelector & Pick, ): Promise; - createOrganizationInvitation(_: OrganizationSelector & { email: string }): Promise; + createOrganizationInvitation( + _: OrganizationSelector & { email: string }, + ): Promise; deleteOrganizationInvitationByEmail( - _: OrganizationSelector & { email: string } + _: OrganizationSelector & { email: string }, ): Promise; getOrganizationMembers(_: OrganizationSelector): Promise; @@ -98,23 +108,29 @@ export interface Storage { getOrganizationOwner(_: OrganizationSelector): Promise; getOrganizationMember(_: OrganizationSelector & { user: string }): Promise; getOrganizationMemberAccessPairs( - _: readonly (OrganizationSelector & { user: string })[] - ): Promise>>; - hasOrganizationMemberPairs(_: readonly (OrganizationSelector & { user: string })[]): Promise; - hasOrganizationProjectMemberPairs(_: readonly (ProjectSelector & { user: string })[]): Promise; + _: readonly (OrganizationSelector & { user: string })[], + ): Promise< + ReadonlyArray> + >; + hasOrganizationMemberPairs( + _: readonly (OrganizationSelector & { user: string })[], + ): Promise; + hasOrganizationProjectMemberPairs( + _: readonly (ProjectSelector & { user: string })[], + ): Promise; addOrganizationMemberViaInvitationCode( _: OrganizationSelector & { code: string; user: string; scopes: ReadonlyArray; - } + }, ): Promise; deleteOrganizationMembers(_: OrganizationSelector & { users: readonly string[] }): Promise; updateOrganizationMemberAccess( _: OrganizationSelector & { user: string; scopes: ReadonlyArray; - } + }, ): Promise; getPersistedOperationId(_: PersistedOperationSelector): Promise; @@ -124,18 +140,22 @@ export interface Storage { getProjectByCleanId(_: { cleanId: string } & OrganizationSelector): Promise; getProjects(_: OrganizationSelector): Promise; createProject( - _: Pick & NullableAndPartial & OrganizationSelector + _: Pick & + NullableAndPartial & + OrganizationSelector, ): Promise; deleteProject(_: ProjectSelector): Promise; updateProjectName( - _: ProjectSelector & Pick & { user: string } + _: ProjectSelector & Pick & { user: string }, + ): Promise; + updateProjectGitRepository( + _: ProjectSelector & Pick, ): Promise; - updateProjectGitRepository(_: ProjectSelector & Pick): Promise; enableExternalSchemaComposition( _: ProjectSelector & { endpoint: string; encryptedSecret: string; - } + }, ): Promise; disableExternalSchemaComposition(_: ProjectSelector): Promise; @@ -143,25 +163,29 @@ export interface Storage { getTargetByCleanId( _: { cleanId: string; - } & ProjectSelector + } & ProjectSelector, ): Promise; createTarget(_: Pick & ProjectSelector): Promise; - updateTargetName(_: TargetSelector & Pick & { user: string }): Promise; + updateTargetName( + _: TargetSelector & Pick & { user: string }, + ): Promise; deleteTarget(_: TargetSelector): Promise; getTarget(_: TargetSelector): Promise; getTargets(_: ProjectSelector): Promise; getTargetIdsOfOrganization(_: OrganizationSelector): Promise; getTargetSettings(_: TargetSelector): Promise; - setTargetValidation(_: TargetSelector & { enabled: boolean }): Promise; + setTargetValidation( + _: TargetSelector & { enabled: boolean }, + ): Promise; updateTargetValidationSettings( - _: TargetSelector & Omit + _: TargetSelector & Omit, ): Promise; hasSchema(_: TargetSelector): Promise; getLatestSchemas( _: { version?: string; - } & TargetSelector + } & TargetSelector, ): Promise< | { schemas: Schema[]; @@ -178,12 +202,12 @@ export interface Storage { _: { version: string; includeMetadata?: boolean; - } & TargetSelector + } & TargetSelector, ): Promise; getSchemasOfPreviousVersion( _: { version: string; - } & TargetSelector + } & TargetSelector, ): Promise; getVersions(_: Paginated): Promise< | { @@ -194,7 +218,9 @@ export interface Storage { >; getVersion(_: TargetSelector & { version: string }): Promise; - updateSchemaUrlOfVersion(_: TargetSelector & { version: string; url?: string | null; commit: string }): Promise; + updateSchemaUrlOfVersion( + _: TargetSelector & { version: string; url?: string | null; commit: string }, + ): Promise; updateServiceName(_: TargetSelector & { commit: string; name: string }): Promise; insertSchema( @@ -205,7 +231,7 @@ export interface Storage { service?: string | null; url?: string | null; metadata: string | null; - } & TargetSelector + } & TargetSelector, ): Promise; createVersion( @@ -215,14 +241,14 @@ export interface Storage { commit: string; commits: string[]; base_schema: string | null; - } & TargetSelector + } & TargetSelector, ): Promise; updateVersionStatus( _: { valid: boolean; version: string; - } & TargetSelector + } & TargetSelector, ): Promise; getSchema(_: { commit: string; target: string }): Promise; @@ -233,25 +259,25 @@ export interface Storage { type: string; meta: object; } & OrganizationSelector & - Partial> + Partial>, ): Promise; getActivities( _: (OrganizationSelector | ProjectSelector | TargetSelector) & { limit: number; - } + }, ): Promise; getPersistedOperations(_: ProjectSelector): Promise; getSelectedPersistedOperations( - _: ProjectSelector & { hashes: readonly string[] } + _: ProjectSelector & { hashes: readonly string[] }, ): Promise; comparePersistedOperations( _: ProjectSelector & { hashes: readonly string[]; - } + }, ): Promise; getPersistedOperation(_: PersistedOperationSelector): Promise; @@ -262,7 +288,7 @@ export interface Storage { name: string; kind: string; content: string; - } & ProjectSelector + } & ProjectSelector, ): Promise; deletePersistedOperation(_: PersistedOperationSelector): Promise; @@ -279,7 +305,7 @@ export interface Storage { deleteAlertChannels( _: ProjectSelector & { channels: readonly string[]; - } + }, ): Promise; getAlertChannels(_: ProjectSelector): Promise; @@ -287,7 +313,7 @@ export interface Storage { deleteAlerts( _: ProjectSelector & { alerts: readonly string[]; - } + }, ): Promise; getAlerts(_: ProjectSelector): Promise; @@ -334,7 +360,7 @@ export interface Storage { completeGetStartedStep( _: OrganizationSelector & { step: Exclude; - } + }, ): Promise; getOIDCIntegrationForOrganization(_: { organizationId: string }): Promise; diff --git a/packages/services/api/src/modules/target/module.graphql.ts b/packages/services/api/src/modules/target/module.graphql.ts index 14af9c519..b947ff7e3 100644 --- a/packages/services/api/src/modules/target/module.graphql.ts +++ b/packages/services/api/src/modules/target/module.graphql.ts @@ -11,7 +11,9 @@ export default gql` createTarget(input: CreateTargetInput!): CreateTargetResult! updateTargetName(input: UpdateTargetNameInput!): UpdateTargetNameResult! deleteTarget(selector: TargetSelectorInput!): DeleteTargetPayload! - updateTargetValidationSettings(input: UpdateTargetValidationSettingsInput!): UpdateTargetValidationSettingsResult! + updateTargetValidationSettings( + input: UpdateTargetValidationSettingsInput! + ): UpdateTargetValidationSettingsResult! setTargetValidation(input: SetTargetValidationInput!): TargetValidationSettings! } diff --git a/packages/services/api/src/modules/target/providers/target-manager.ts b/packages/services/api/src/modules/target/providers/target-manager.ts index 3a080ce6e..5ffa50010 100644 --- a/packages/services/api/src/modules/target/providers/target-manager.ts +++ b/packages/services/api/src/modules/target/providers/target-manager.ts @@ -27,7 +27,7 @@ export class TargetManager { private storage: Storage, private tokenStorage: TokenStorage, private authManager: AuthManager, - private activityManager: ActivityManager + private activityManager: ActivityManager, ) { this.logger = logger.child({ source: 'TargetManager' }); } @@ -39,7 +39,12 @@ export class TargetManager { }: { name: string; } & ProjectSelector): Promise { - this.logger.info('Creating a target (name=%s, project=%s, organization=%s)', name, project, organization); + this.logger.info( + 'Creating a target (name=%s, project=%s, organization=%s)', + name, + project, + organization, + ); await this.authManager.ensureProjectAccess({ project, organization, @@ -73,7 +78,12 @@ export class TargetManager { } async deleteTarget({ organization, project, target }: TargetSelector): Promise { - this.logger.info('Deleting a target (target=%s, project=%s, organization=%s)', target, project, organization); + this.logger.info( + 'Deleting a target (target=%s, project=%s, organization=%s)', + target, + project, + organization, + ); await this.authManager.ensureTargetAccess({ project, organization, @@ -168,7 +178,7 @@ export class TargetManager { async setTargetValidation( input: { enabled: boolean; - } & TargetSelector + } & TargetSelector, ): Promise { this.logger.debug('Setting target validation (input=%o)', input); await this.authManager.ensureTargetAccess({ @@ -185,7 +195,7 @@ export class TargetManager { } async updateTargetValidationSettings( - input: Omit & TargetSelector + input: Omit & TargetSelector, ): Promise { this.logger.debug('Updating target validation settings (input=%o)', input); await this.authManager.ensureTargetAccess({ @@ -203,7 +213,7 @@ export class TargetManager { async updateName( input: { name: string; - } & TargetSelector + } & TargetSelector, ): Promise { const { name, organization, project, target } = input; this.logger.info('Updating a target name (input=%o)', input); diff --git a/packages/services/api/src/modules/target/resolvers.ts b/packages/services/api/src/modules/target/resolvers.ts index 850fd7d9a..9cbf6a493 100644 --- a/packages/services/api/src/modules/target/resolvers.ts +++ b/packages/services/api/src/modules/target/resolvers.ts @@ -65,8 +65,8 @@ export const resolvers: TargetModule.Resolvers = { organization, project, target: tid, - }) - ) + }), + ), ), }, }; @@ -222,8 +222,8 @@ export const resolvers: TargetModule.Resolvers = { organization, project, target: tid, - }) - ) + }), + ), ), }; }, @@ -280,8 +280,8 @@ export const resolvers: TargetModule.Resolvers = { organization, project, target: tid, - }) - ) + }), + ), ), }, }, diff --git a/packages/services/api/src/modules/token/providers/token-manager.ts b/packages/services/api/src/modules/token/providers/token-manager.ts index a93bf6279..c29ae25ec 100644 --- a/packages/services/api/src/modules/token/providers/token-manager.ts +++ b/packages/services/api/src/modules/token/providers/token-manager.ts @@ -33,7 +33,7 @@ export class TokenManager { private authManager: AuthManager, private tokenStorage: TokenStorage, private storage: Storage, - logger: Logger + logger: Logger, ) { this.logger = logger.child({ source: 'TokenManager', @@ -62,7 +62,9 @@ export class TokenManager { const modifiedScopes = diffArrays(currentMember.scopes, newScopes); // Check if the current user has rights to set these scopes. - const currentUserMissingScopes = modifiedScopes.filter(scope => !currentMember.scopes.includes(scope)); + const currentUserMissingScopes = modifiedScopes.filter( + scope => !currentMember.scopes.includes(scope), + ); if (currentUserMissingScopes.length > 0) { this.logger.debug(`Logged user scopes: %o`, currentMember.scopes); @@ -85,7 +87,7 @@ export class TokenManager { async deleteTokens( input: { tokens: readonly string[]; - } & TargetSelector + } & TargetSelector, ): Promise { await this.authManager.ensureTargetAccess({ project: input.project, diff --git a/packages/services/api/src/modules/token/providers/token-storage.ts b/packages/services/api/src/modules/token/providers/token-storage.ts index d67f976b7..35d427f44 100644 --- a/packages/services/api/src/modules/token/providers/token-storage.ts +++ b/packages/services/api/src/modules/token/providers/token-storage.ts @@ -3,7 +3,11 @@ import { atomic } from '../../../shared/helpers'; import { HiveError } from '../../../shared/errors'; import type { Token } from '../../../shared/entities'; import { Logger } from '../../shared/providers/logger'; -import { TargetSelector, ProjectSelector, OrganizationSelector } from '../../shared/providers/storage'; +import { + TargetSelector, + ProjectSelector, + OrganizationSelector, +} from '../../shared/providers/storage'; import type { TargetAccessScope } from '../../auth/providers/target-access'; import type { ProjectAccessScope } from '../../auth/providers/project-access'; import type { OrganizationAccessScope } from '../../auth/providers/organization-access'; @@ -41,7 +45,7 @@ export class TokenStorage { constructor( logger: Logger, @Inject(TOKENS_CONFIG) tokensConfig: TokensConfig, - @Inject(CONTEXT) context: GraphQLModules.ModuleContext + @Inject(CONTEXT) context: GraphQLModules.ModuleContext, ) { this.logger = logger.child({ source: 'TokenStorage' }); this.tokensService = createTRPCClient({ @@ -70,11 +74,13 @@ export class TokenStorage { async deleteTokens( input: { tokens: readonly string[]; - } & TargetSelector + } & TargetSelector, ): Promise { this.logger.debug('Deleting tokens (input=%o)', input); - await Promise.all(input.tokens.map(token => this.tokensService.mutation('deleteToken', { token }))); + await Promise.all( + input.tokens.map(token => this.tokensService.mutation('deleteToken', { token })), + ); return input.tokens; } diff --git a/packages/services/api/src/modules/usage-estimation/providers/tokens.ts b/packages/services/api/src/modules/usage-estimation/providers/tokens.ts index 3af1757b0..f154b9b71 100644 --- a/packages/services/api/src/modules/usage-estimation/providers/tokens.ts +++ b/packages/services/api/src/modules/usage-estimation/providers/tokens.ts @@ -5,5 +5,5 @@ export interface UsageEstimationServiceConfig { } export const USAGE_ESTIMATION_SERVICE_CONFIG = new InjectionToken( - 'usage-estimation-service-config' + 'usage-estimation-service-config', ); diff --git a/packages/services/api/src/modules/usage-estimation/providers/usage-estimation.provider.ts b/packages/services/api/src/modules/usage-estimation/providers/usage-estimation.provider.ts index 2a2bfe948..b05112252 100644 --- a/packages/services/api/src/modules/usage-estimation/providers/usage-estimation.provider.ts +++ b/packages/services/api/src/modules/usage-estimation/providers/usage-estimation.provider.ts @@ -17,7 +17,7 @@ export class UsageEstimationProvider { constructor( logger: Logger, @Inject(USAGE_ESTIMATION_SERVICE_CONFIG) - usageEstimationConfig: UsageEstimationServiceConfig + usageEstimationConfig: UsageEstimationServiceConfig, ) { this.logger = logger.child({ service: 'UsageEstimationProvider' }); this.usageEstimator = usageEstimationConfig.endpoint @@ -29,7 +29,9 @@ export class UsageEstimationProvider { } @sentry('UsageEstimation.estimateOperations') - async estimateOperations(input: UsageEstimatorQueryInput<'estimateOperationsForTarget'>): Promise { + async estimateOperations( + input: UsageEstimatorQueryInput<'estimateOperationsForTarget'>, + ): Promise { this.logger.debug('Estimation operations, input: %o', input); if (input.targetIds.length === 0) { diff --git a/packages/services/api/src/modules/usage-estimation/resolvers.ts b/packages/services/api/src/modules/usage-estimation/resolvers.ts index 49691be4b..3e619a683 100644 --- a/packages/services/api/src/modules/usage-estimation/resolvers.ts +++ b/packages/services/api/src/modules/usage-estimation/resolvers.ts @@ -52,7 +52,7 @@ export const resolvers: UsageEstimationModule.Resolvers = { organization: organizationId, project: project.id, }); - }) + }), ) ).flat(); diff --git a/packages/services/api/src/shared/helpers.ts b/packages/services/api/src/shared/helpers.ts index 68ef39af9..e316e98ae 100644 --- a/packages/services/api/src/shared/helpers.ts +++ b/packages/services/api/src/shared/helpers.ts @@ -10,7 +10,13 @@ import type { } from '../__generated__/types'; import { DateRange } from './entities'; -export { msToNs, nsToMs, atomicPromise as atomic, sharePromise as share, cacheResult as cache } from '@theguild/buddy'; +export { + msToNs, + nsToMs, + atomicPromise as atomic, + sharePromise as share, + cacheResult as cache, +} from '@theguild/buddy'; export type NullableAndPartial = { [P in keyof T]?: T[P] | undefined | null; @@ -29,14 +35,20 @@ export function uuid(len = 13) { return Math.random().toString(16).substr(2, len); } -export function filterSelector(kind: 'organization', selector: OrganizationSelector): OrganizationSelector; +export function filterSelector( + kind: 'organization', + selector: OrganizationSelector, +): OrganizationSelector; export function filterSelector(kind: 'project', selector: ProjectSelector): ProjectSelector; export function filterSelector(kind: 'target', selector: TargetSelector): TargetSelector; export function filterSelector( kind: 'persistedOperation', - selector: PersistedOperationSelector + selector: PersistedOperationSelector, ): PersistedOperationSelector; -export function filterSelector(kind: 'organization' | 'project' | 'target' | 'persistedOperation', selector: any): any { +export function filterSelector( + kind: 'organization' | 'project' | 'target' | 'persistedOperation', + selector: any, +): any { switch (kind) { case 'organization': return { @@ -65,17 +77,18 @@ export function filterSelector(kind: 'organization' | 'project' | 'target' | 'pe export function stringifySelector< T extends { [key: string]: any; - } + }, >(obj: T): string { return JSON.stringify( Object.keys(obj) .sort() - .map(key => [key, obj[key]]) + .map(key => [key, obj[key]]), ); } function validateDateTime(dateTimeString?: string) { - dateTimeString = dateTimeString === null || dateTimeString === void 0 ? void 0 : dateTimeString.toUpperCase(); + dateTimeString = + dateTimeString === null || dateTimeString === void 0 ? void 0 : dateTimeString.toUpperCase(); if (!dateTimeString) { return false; @@ -168,7 +181,9 @@ export function parseDateTime(value: number | string | Date): Date { } throw new TypeError( - 'DateTime cannot be serialized from a non string, ' + 'non numeric or non Date type ' + JSON.stringify(value) + 'DateTime cannot be serialized from a non string, ' + + 'non numeric or non Date type ' + + JSON.stringify(value), ); } diff --git a/packages/services/api/src/shared/mappers.ts b/packages/services/api/src/shared/mappers.ts index ca4e30876..37a1eb401 100644 --- a/packages/services/api/src/shared/mappers.ts +++ b/packages/services/api/src/shared/mappers.ts @@ -11,7 +11,12 @@ import type { GraphQLSchema, GraphQLArgument, } from 'graphql'; -import type { SchemaChange, SchemaError, OperationStats, ClientStats } from '../__generated__/types'; +import type { + SchemaChange, + SchemaError, + OperationStats, + ClientStats, +} from '../__generated__/types'; import type { Member, Organization, @@ -66,8 +71,12 @@ export type GraphQLInputFieldMapper = WithSchemaCoordinatesUsage< entity: GraphQLInputField; }> >; -export type GraphQLEnumValueMapper = WithSchemaCoordinatesUsage>; -export type GraphQLArgumentMapper = WithSchemaCoordinatesUsage>; +export type GraphQLEnumValueMapper = WithSchemaCoordinatesUsage< + WithGraphQLParentInfo<{ entity: GraphQLEnumValue }> +>; +export type GraphQLArgumentMapper = WithSchemaCoordinatesUsage< + WithGraphQLParentInfo<{ entity: GraphQLArgument }> +>; export type GraphQLUnionTypeMemberMapper = WithSchemaCoordinatesUsage< WithGraphQLParentInfo<{ entity: GraphQLObjectType; @@ -75,10 +84,14 @@ export type GraphQLUnionTypeMemberMapper = WithSchemaCoordinatesUsage< >; export type GraphQLObjectTypeMapper = WithSchemaCoordinatesUsage<{ entity: GraphQLObjectType }>; -export type GraphQLInterfaceTypeMapper = WithSchemaCoordinatesUsage<{ entity: GraphQLInterfaceType }>; +export type GraphQLInterfaceTypeMapper = WithSchemaCoordinatesUsage<{ + entity: GraphQLInterfaceType; +}>; export type GraphQLUnionTypeMapper = WithSchemaCoordinatesUsage<{ entity: GraphQLUnionType }>; export type GraphQLEnumTypeMapper = WithSchemaCoordinatesUsage<{ entity: GraphQLEnumType }>; -export type GraphQLInputObjectTypeMapper = WithSchemaCoordinatesUsage<{ entity: GraphQLInputObjectType }>; +export type GraphQLInputObjectTypeMapper = WithSchemaCoordinatesUsage<{ + entity: GraphQLInputObjectType; +}>; export type GraphQLScalarTypeMapper = WithSchemaCoordinatesUsage<{ entity: GraphQLScalarType }>; export type SchemaChangeConnection = readonly SchemaChange[]; @@ -92,7 +105,9 @@ export type TargetConnection = readonly Target[]; export type PersistedOperationConnection = readonly PersistedOperation[]; export type SchemaConnection = readonly Schema[]; export type TokenConnection = readonly Token[]; -export type OperationStatsConnection = ReadonlyArray & { duration: DurationStats }>; +export type OperationStatsConnection = ReadonlyArray< + Omit & { duration: DurationStats } +>; export type ClientStatsConnection = readonly ClientStats[]; export type SchemaVersionConnection = { nodes: readonly SchemaVersion[]; @@ -103,7 +118,9 @@ export type SchemaComparePayload = | { message: string; }; -export type SchemaCompareResult = readonly [SchemaObject, SchemaObject] | readonly [undefined | null, SchemaObject]; +export type SchemaCompareResult = + | readonly [SchemaObject, SchemaObject] + | readonly [undefined | null, SchemaObject]; export interface Schema { id: string; author: string; diff --git a/packages/services/api/src/shared/schema.ts b/packages/services/api/src/shared/schema.ts index 8d1a10c1f..0fc4c2beb 100644 --- a/packages/services/api/src/shared/schema.ts +++ b/packages/services/api/src/shared/schema.ts @@ -29,7 +29,7 @@ export function buildSchema(schema: SchemaObject): GraphQLSchema { buildASTSchema(schema.document, { assumeValid: true, assumeValidSDL: true, - }) + }), ); } @@ -39,7 +39,7 @@ export function findSchema(schemas: readonly Schema[], expected: Schema): Schema export function updateSchemas( schemas: Schema[], - incoming: Schema + incoming: Schema, ): { schemas: Schema[]; swappedSchema: Schema | null; @@ -218,17 +218,23 @@ export function sortDocumentNode(doc: DocumentNode): DocumentNode { } function sortNodes(nodes: readonly DefinitionNode[]): readonly DefinitionNode[]; -function sortNodes(nodes: readonly ConstDirectiveNode[] | undefined): readonly ConstDirectiveNode[] | undefined; function sortNodes( - nodes: readonly OperationTypeDefinitionNode[] | undefined + nodes: readonly ConstDirectiveNode[] | undefined, +): readonly ConstDirectiveNode[] | undefined; +function sortNodes( + nodes: readonly OperationTypeDefinitionNode[] | undefined, ): readonly OperationTypeDefinitionNode[] | undefined; -function sortNodes(nodes: readonly FieldDefinitionNode[] | undefined): readonly FieldDefinitionNode[] | undefined; -function sortNodes(nodes: readonly NamedTypeNode[] | undefined): readonly NamedTypeNode[] | undefined; function sortNodes( - nodes: readonly EnumValueDefinitionNode[] | undefined + nodes: readonly FieldDefinitionNode[] | undefined, +): readonly FieldDefinitionNode[] | undefined; +function sortNodes( + nodes: readonly NamedTypeNode[] | undefined, +): readonly NamedTypeNode[] | undefined; +function sortNodes( + nodes: readonly EnumValueDefinitionNode[] | undefined, ): readonly EnumValueDefinitionNode[] | undefined; function sortNodes( - nodes: readonly InputValueDefinitionNode[] | undefined + nodes: readonly InputValueDefinitionNode[] | undefined, ): readonly InputValueDefinitionNode[] | undefined; function sortNodes(nodes: readonly any[] | undefined): readonly any[] | undefined { if (nodes) { diff --git a/packages/services/api/src/shared/sentry.ts b/packages/services/api/src/shared/sentry.ts index 9820ff5d2..db039f6c5 100644 --- a/packages/services/api/src/shared/sentry.ts +++ b/packages/services/api/src/shared/sentry.ts @@ -6,7 +6,10 @@ export type SentryContext = Parameters[0] & { captureException?: boolean; }; -export function sentry(name: string, addToContext?: (...args: any[]) => SentryContext): MethodDecorator { +export function sentry( + name: string, + addToContext?: (...args: any[]) => SentryContext, +): MethodDecorator { return function sentryDecorator(_target, _prop, descriptor) { const originalMethod = descriptor.value; @@ -31,7 +34,7 @@ export function sentry(name: string, addToContext?: (...args: any[]) => SentryCo ? { op: context, } - : context + : context, ); if (!span) { @@ -40,7 +43,9 @@ export function sentry(name: string, addToContext?: (...args: any[]) => SentryCo const argsWithoutSpan = passedSpan ? args.slice(0, args.length - 1) : args; - return ((originalMethod as any).apply(this, argsWithoutSpan.concat(span)) as Promise).then( + return ( + (originalMethod as any).apply(this, argsWithoutSpan.concat(span)) as Promise + ).then( result => { span.finish(); return Promise.resolve(result); @@ -52,7 +57,7 @@ export function sentry(name: string, addToContext?: (...args: any[]) => SentryCo span.setStatus('internal_error'); span.finish(); return Promise.reject(error); - } + }, ); } as any; }; diff --git a/packages/services/broker-worker/README.md b/packages/services/broker-worker/README.md index 58832fbd8..442d39baf 100644 --- a/packages/services/broker-worker/README.md +++ b/packages/services/broker-worker/README.md @@ -1,6 +1,7 @@ ## Hive Broker Worker -Idea here is to have a broker worker that can be used to make requests outside GraphQL Hive and without any access to the internal network. -This is very useful for example for making requests to external APIs. +Idea here is to have a broker worker that can be used to make requests outside GraphQL Hive and +without any access to the internal network. This is very useful for example for making requests to +external APIs. Look at [models.ts](./src/models.ts) to see the structure accepted payload. diff --git a/packages/services/broker-worker/package.json b/packages/services/broker-worker/package.json index df9bdefcc..8da189d05 100644 --- a/packages/services/broker-worker/package.json +++ b/packages/services/broker-worker/package.json @@ -1,12 +1,12 @@ { "name": "@hive/broker-script", - "private": true, "version": "0.0.0", "license": "MIT", + "private": true, "scripts": { - "dev": "tsup-node src/dev.ts --target node16 --sourcemap --watch --onSuccess \"node --enable-source-maps dist/dev.js\"", - "build-local": "BUILD_FOR_LOCAL=1 node build.mjs", "build": "node build.mjs", + "build-local": "BUILD_FOR_LOCAL=1 node build.mjs", + "dev": "tsup-node src/dev.ts --target node16 --sourcemap --watch --onSuccess \"node --enable-source-maps dist/dev.js\"", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -14,13 +14,13 @@ "zod": "3.19.1" }, "devDependencies": { - "esbuild": "0.14.39", + "@cloudflare/workers-types": "3.4.0", + "@types/service-worker-mock": "2.0.1", "@whatwg-node/fetch": "0.4.7", "@whatwg-node/server": "0.4.11", + "esbuild": "0.14.39", "itty-router": "2.4.0", "itty-router-extras": "0.4.2", - "nock": "13.2.4", - "@types/service-worker-mock": "2.0.1", - "@cloudflare/workers-types": "3.4.0" + "nock": "13.2.4" } } diff --git a/packages/services/broker-worker/src/dev.ts b/packages/services/broker-worker/src/dev.ts index eb1ddf786..72791a4a5 100644 --- a/packages/services/broker-worker/src/dev.ts +++ b/packages/services/broker-worker/src/dev.ts @@ -16,7 +16,7 @@ function main() { () => new Response(null, { status: 200, - }) + }), ); app.all('*', (request: Request) => handleRequest(request, isSignatureValid)); diff --git a/packages/services/broker-worker/src/errors.ts b/packages/services/broker-worker/src/errors.ts index 81a1dab25..791af3e5d 100644 --- a/packages/services/broker-worker/src/errors.ts +++ b/packages/services/broker-worker/src/errors.ts @@ -7,7 +7,7 @@ export class InvalidRequestFormat extends Response { }), { status: 400, - } + }, ); } } @@ -21,7 +21,7 @@ export class MissingSignature extends Response { }), { status: 401, - } + }, ); } } @@ -35,7 +35,7 @@ export class InvalidSignature extends Response { }), { status: 403, - } + }, ); } } @@ -49,7 +49,7 @@ export class UnexpectedError extends Response { }), { status: 500, - } + }, ); } } diff --git a/packages/services/broker-worker/src/index.ts b/packages/services/broker-worker/src/index.ts index 8e0ba758e..eb66aa2ff 100644 --- a/packages/services/broker-worker/src/index.ts +++ b/packages/services/broker-worker/src/index.ts @@ -12,7 +12,14 @@ self.addEventListener('fetch', event => { environment: SENTRY_ENVIRONMENT, release: SENTRY_RELEASE, context: event, - allowedHeaders: ['user-agent', 'cf-ipcountry', 'accept-encoding', 'accept', 'x-real-ip', 'cf-connecting-ip'], + allowedHeaders: [ + 'user-agent', + 'cf-ipcountry', + 'accept-encoding', + 'accept', + 'x-real-ip', + 'cf-connecting-ip', + ], allowedSearchParams: /(.*)/, }); const eventId = sentry.captureException(error); diff --git a/packages/services/broker-worker/src/models.ts b/packages/services/broker-worker/src/models.ts index 6f1c6b348..16b7fba8d 100644 --- a/packages/services/broker-worker/src/models.ts +++ b/packages/services/broker-worker/src/models.ts @@ -20,7 +20,7 @@ const RequestModelSchema = z.union([ export async function parseIncomingRequest( request: Request, - keyValidator: typeof isSignatureValid + keyValidator: typeof isSignatureValid, ): Promise<{ error: Response } | z.infer> { if (request.method !== 'POST') { return { diff --git a/packages/services/broker-worker/tests/broker.spec.ts b/packages/services/broker-worker/tests/broker.spec.ts index 1376682cc..dc5910fd5 100644 --- a/packages/services/broker-worker/tests/broker.spec.ts +++ b/packages/services/broker-worker/tests/broker.spec.ts @@ -183,7 +183,7 @@ test('GET application/json', async () => { expect(await response.text()).toBe( JSON.stringify({ message: 'OK', - }) + }), ); }); @@ -257,7 +257,7 @@ test('POST application/json', async () => { expect(await response.text()).toBe( JSON.stringify({ message: 'OK', - }) + }), ); }); @@ -305,7 +305,7 @@ test('POST application/json + body', async () => { receivedBody: JSON.stringify({ message: 'OK', }), - }) + }), ); }); diff --git a/packages/services/cdn-worker/README.md b/packages/services/cdn-worker/README.md index ca26554fc..bcc2d458d 100644 --- a/packages/services/cdn-worker/README.md +++ b/packages/services/cdn-worker/README.md @@ -6,7 +6,8 @@ Hive comes with a CDN worker (deployed to CF Workers), along with KV cache to st To run Hive CDN locally, you can use the following command: `pnpm dev`. -> Note: during dev, KV is mocked using JS `Map`, so it's ephermal and will be deleted with any change in code. +> Note: during dev, KV is mocked using JS `Map`, so it's ephermal and will be deleted with any +> change in code. To publish manually a schema, for target id `1`: @@ -34,7 +35,8 @@ curl http://localhost:4010/1/introspection -H "x-hive-cdn-key: fake" Hive server has `CF_BASE_PATH` env var that tells is where to send the published schemas. -To connect your server to the local, mocked CDN, make sure you have the following in `packages/server/.env`: +To connect your server to the local, mocked CDN, make sure you have the following in +`packages/server/.env`: ```dotenv CF_BASE_PATH=http://localhost:4010 diff --git a/packages/services/cdn-worker/package.json b/packages/services/cdn-worker/package.json index adf73c96c..0aba4e0b0 100644 --- a/packages/services/cdn-worker/package.json +++ b/packages/services/cdn-worker/package.json @@ -1,12 +1,12 @@ { "name": "@hive/cdn-script", - "private": true, "version": "0.0.0", "license": "MIT", + "private": true, "scripts": { - "dev": "tsup-node src/dev.ts --target node16 --sourcemap --watch --onSuccess \"node --enable-source-maps dist/dev.js\"", - "build-local": "BUILD_FOR_LOCAL=1 node build.mjs", "build": "node build.mjs", + "build-local": "BUILD_FOR_LOCAL=1 node build.mjs", + "dev": "tsup-node src/dev.ts --target node16 --sourcemap --watch --onSuccess \"node --enable-source-maps dist/dev.js\"", "typecheck": "tsc --noEmit" }, "peerDependencies": { @@ -17,13 +17,13 @@ "toucan-js": "2.7.0" }, "devDependencies": { - "fastify": "3.29.4", - "esbuild": "0.14.39", + "@cloudflare/workers-types": "3.4.0", + "@types/service-worker-mock": "2.0.1", "@whatwg-node/fetch": "0.4.7", "@whatwg-node/server": "0.4.11", + "esbuild": "0.14.39", + "fastify": "3.29.4", "itty-router": "2.4.0", - "itty-router-extras": "0.4.2", - "@types/service-worker-mock": "2.0.1", - "@cloudflare/workers-types": "3.4.0" + "itty-router-extras": "0.4.2" } } diff --git a/packages/services/cdn-worker/src/auth.ts b/packages/services/cdn-worker/src/auth.ts index ff15d3d44..12489471e 100644 --- a/packages/services/cdn-worker/src/auth.ts +++ b/packages/services/cdn-worker/src/auth.ts @@ -13,9 +13,13 @@ export function byteStringToUint8Array(byteString: string) { export async function isKeyValid(targetId: string, headerKey: string): Promise { const headerData = byteStringToUint8Array(atob(headerKey)); const secretKeyData = encoder.encode(KEY_DATA); - const secretKey = await crypto.subtle.importKey('raw', secretKeyData, { name: 'HMAC', hash: 'SHA-256' }, false, [ - 'verify', - ]); + const secretKey = await crypto.subtle.importKey( + 'raw', + secretKeyData, + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['verify'], + ); return await crypto.subtle.verify('HMAC', secretKey, headerData, encoder.encode(targetId)); } diff --git a/packages/services/cdn-worker/src/dev.ts b/packages/services/cdn-worker/src/dev.ts index 169d8ceed..ca3338d0d 100644 --- a/packages/services/cdn-worker/src/dev.ts +++ b/packages/services/cdn-worker/src/dev.ts @@ -23,7 +23,7 @@ function main() { namespaceId: string; key: string; }; - } + }, ) => { if (!request.params.key) { throw new Error(`Missing key`); @@ -42,7 +42,7 @@ function main() { return json({ success: true, }); - } + }, ); app.get('/dump', () => json(Object.fromEntries(devStorage.entries()))); @@ -52,7 +52,7 @@ function main() { () => new Response(null, { status: 200, - }) + }), ); app.get('*', (request: Request) => handleRequest(request, isKeyValid)); diff --git a/packages/services/cdn-worker/src/errors.ts b/packages/services/cdn-worker/src/errors.ts index 5f95816e2..18b063860 100644 --- a/packages/services/cdn-worker/src/errors.ts +++ b/packages/services/cdn-worker/src/errors.ts @@ -10,7 +10,7 @@ export class MissingTargetIDErrorResponse extends Response { }), { status: 400, - } + }, ); } } @@ -25,7 +25,7 @@ export class InvalidArtifactTypeResponse extends Response { }), { status: 400, - } + }, ); } } @@ -40,7 +40,7 @@ export class MissingAuthKey extends Response { }), { status: 400, - } + }, ); } } @@ -55,7 +55,7 @@ export class InvalidAuthKey extends Response { }), { status: 403, - } + }, ); } } @@ -70,7 +70,7 @@ export class CDNArtifactNotFound extends Response { }), { status: 404, - } + }, ); } } @@ -85,7 +85,7 @@ export class InvalidArtifactMatch extends Response { }), { status: 400, - } + }, ); } } @@ -100,7 +100,7 @@ export class UnexpectedError extends Response { }), { status: 500, - } + }, ); } } diff --git a/packages/services/cdn-worker/src/handler.ts b/packages/services/cdn-worker/src/handler.ts index a6dd682a9..4bf44ceac 100644 --- a/packages/services/cdn-worker/src/handler.ts +++ b/packages/services/cdn-worker/src/handler.ts @@ -96,7 +96,7 @@ const AUTH_HEADER_NAME = 'x-hive-cdn-key'; async function parseIncomingRequest( request: Request, - keyValidator: typeof isKeyValid + keyValidator: typeof isKeyValid, ): Promise< | { error: Response } | { @@ -189,7 +189,7 @@ export async function handleRequest(request: Request, keyValidator: typeof isKey } } else { console.log( - `CDN Artifact not found for targetId=${targetId}, artifactType=${artifactType}, storageKeyType=${storageKeyType}` + `CDN Artifact not found for targetId=${targetId}, artifactType=${artifactType}, storageKeyType=${storageKeyType}`, ); return new CDNArtifactNotFound(artifactType, targetId); } diff --git a/packages/services/cdn-worker/src/index.ts b/packages/services/cdn-worker/src/index.ts index 26c84a89f..f3972f90c 100644 --- a/packages/services/cdn-worker/src/index.ts +++ b/packages/services/cdn-worker/src/index.ts @@ -12,7 +12,14 @@ self.addEventListener('fetch', event => { environment: SENTRY_ENVIRONMENT, release: SENTRY_RELEASE, context: event, - allowedHeaders: ['user-agent', 'cf-ipcountry', 'accept-encoding', 'accept', 'x-real-ip', 'cf-connecting-ip'], + allowedHeaders: [ + 'user-agent', + 'cf-ipcountry', + 'accept-encoding', + 'accept', + 'x-real-ip', + 'cf-connecting-ip', + ], allowedSearchParams: /(.*)/, }); sentry.captureException(error); diff --git a/packages/services/cdn-worker/tests/cdn.spec.ts b/packages/services/cdn-worker/tests/cdn.spec.ts index 8648f3da6..93a60873e 100644 --- a/packages/services/cdn-worker/tests/cdn.spec.ts +++ b/packages/services/cdn-worker/tests/cdn.spec.ts @@ -134,7 +134,10 @@ describe('CDN Worker', () => { const SECRET = '123456'; const targetId = 'fake-target-id'; const map = new Map(); - map.set(`target:${targetId}:supergraph`, JSON.stringify({ sdl: `type Query { dummy: String }` })); + map.set( + `target:${targetId}:supergraph`, + JSON.stringify({ sdl: `type Query { dummy: String }` }), + ); mockWorkerEnv({ HIVE_DATA: map, @@ -285,7 +288,7 @@ describe('CDN Worker', () => { `target:${targetId}:schema`, JSON.stringify({ sdl: `type Query { dummy: String }`, - }) + }), ); mockWorkerEnv({ diff --git a/packages/services/emails/package.json b/packages/services/emails/package.json index ffa679832..f3a6d8f41 100644 --- a/packages/services/emails/package.json +++ b/packages/services/emails/package.json @@ -1,34 +1,34 @@ { "name": "@hive/emails", - "private": true, - "type": "module", "version": "0.0.0", + "type": "module", "license": "MIT", + "private": true, "scripts": { - "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "build": "bob runify --single", - "typecheck": "tsc --noEmit", - "postbuild": "copyfiles -f \"node_modules/bullmq/dist/esm/commands/*.lua\" dist && copyfiles -f \"node_modules/bullmq/dist/esm/commands/includes/*.lua\" dist/includes" + "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", + "postbuild": "copyfiles -f \"node_modules/bullmq/dist/esm/commands/*.lua\" dist && copyfiles -f \"node_modules/bullmq/dist/esm/commands/includes/*.lua\" dist/includes", + "typecheck": "tsc --noEmit" }, "dependencies": { - "zod": "3.15.1", - "@trpc/server": "9.23.2", "@sentry/node": "7.20.1", - "bullmq": "1.81.4", + "@trpc/server": "9.23.2", "@whatwg-node/fetch": "0.4.7", + "bullmq": "1.81.4", "dotenv": "10.0.0", "ioredis": "4.28.5", "mjml": "4.13.0", "nodemailer": "6.7.8", "p-timeout": "5.0.2", - "sendmail": "1.6.1" + "sendmail": "1.6.1", + "zod": "3.15.1" }, "devDependencies": { + "@hive/service-common": "workspace:*", + "@types/ioredis": "4.28.10", "@types/mjml": "4.7.0", "@types/nodemailer": "6.4.6", "@types/sendmail": "1.4.4", - "@types/ioredis": "4.28.10", - "@hive/service-common": "workspace:*", "copyfiles": "2.4.1", "pino-pretty": "6.0.0", "tslib": "2.4.1" diff --git a/packages/services/emails/src/environment.ts b/packages/services/emails/src/environment.ts index 0086d350e..a7db028d0 100644 --- a/packages/services/emails/src/environment.ts +++ b/packages/services/emails/src/environment.ts @@ -49,7 +49,9 @@ const PostmarkEmailModel = zod.object({ const SMTPEmailModel = zod.object({ EMAIL_PROVIDER: zod.literal('smtp'), - EMAIL_PROVIDER_SMTP_PROTOCOL: emptyString(zod.union([zod.literal('smtp'), zod.literal('smtps')]).optional()), + EMAIL_PROVIDER_SMTP_PROTOCOL: emptyString( + zod.union([zod.literal('smtp'), zod.literal('smtps')]).optional(), + ), EMAIL_PROVIDER_SMTP_HOST: zod.string(), EMAIL_PROVIDER_SMTP_PORT: NumberFromString, EMAIL_PROVIDER_SMTP_AUTH_USERNAME: zod.string(), @@ -64,7 +66,12 @@ const MockEmailProviderModel = zod.object({ EMAIL_PROVIDER: zod.literal('mock'), }); -const EmailProviderModel = zod.union([PostmarkEmailModel, MockEmailProviderModel, SMTPEmailModel, SendmailEmailModel]); +const EmailProviderModel = zod.union([ + PostmarkEmailModel, + MockEmailProviderModel, + SMTPEmailModel, + SendmailEmailModel, +]); const PrometheusModel = zod.object({ PROMETHEUS_METRICS: emptyString(zod.union([zod.literal('0'), zod.literal('1')]).optional()), @@ -83,7 +90,7 @@ const LogModel = zod.object({ zod.literal('fatal'), zod.literal('silent'), ]) - .optional() + .optional(), ), }); diff --git a/packages/services/emails/src/providers.ts b/packages/services/emails/src/providers.ts index 9c6a6dc53..2e5d458ec 100644 --- a/packages/services/emails/src/providers.ts +++ b/packages/services/emails/src/providers.ts @@ -127,7 +127,7 @@ function sendmail(_config: SendmailEmailProviderConfig, emailFrom: string) { } else { resolve(reply); } - } + }, ); }); }, diff --git a/packages/services/emails/src/scheduler.ts b/packages/services/emails/src/scheduler.ts index 08a05975b..ec4508e25 100644 --- a/packages/services/emails/src/scheduler.ts +++ b/packages/services/emails/src/scheduler.ts @@ -42,7 +42,12 @@ export function createScheduler(config: { } function onFailed(job: Job, error: Error) { - logger.debug(`Job %s failed after %s attempts, reason: %s`, job.name, job.attemptsMade, job.failedReason); + logger.debug( + `Job %s failed after %s attempts, reason: %s`, + job.name, + job.attemptsMade, + job.failedReason, + ); logger.error(error); emailsFailuresTotal.inc(); } @@ -100,7 +105,7 @@ export function createScheduler(config: { prefix, connection: redisConnection, sharedConnection: true, - } + }, ); worker.on('error', onError('emailsWorker')); @@ -167,7 +172,11 @@ export function createScheduler(config: { try { queue?.removeAllListeners(); queueScheduler?.removeAllListeners(), - await pTimeout(Promise.all([queue?.close(), queueScheduler?.close()]), 5000, 'BullMQ close timeout'); + await pTimeout( + Promise.all([queue?.close(), queueScheduler?.close()]), + 5000, + 'BullMQ close timeout', + ); } catch (e) { logger.error('Failed to stop queues', e); } finally { diff --git a/packages/services/emails/src/templates/email-verification.ts b/packages/services/emails/src/templates/email-verification.ts index 3ad1b23e4..ba4638654 100644 --- a/packages/services/emails/src/templates/email-verification.ts +++ b/packages/services/emails/src/templates/email-verification.ts @@ -1,4 +1,8 @@ -export function renderEmailVerificationEmail(params: { subject: string; verificationLink: string; toEmail: string }) { +export function renderEmailVerificationEmail(params: { + subject: string; + verificationLink: string; + toEmail: string; +}) { return ` You can also trigger the scheduled worker manually from CloudFlare dashboard if you need to speed things up. +> You can also trigger the scheduled worker manually from CloudFlare dashboard if you need to speed +> things up. ## Available Rules - Block missing/empty header: `header:HEADER_NAME:empty` -- Block by header value: `header:HEADER_NAME:SOME_VALUE` (or, with method: `header:HEADER_NAME:SOME_VALUE:POST`, or with method and path: `header:HEADER_NAME:SOME_VALUE:POST:/usage`) +- Block by header value: `header:HEADER_NAME:SOME_VALUE` (or, with method: + `header:HEADER_NAME:SOME_VALUE:POST`, or with method and path: + `header:HEADER_NAME:SOME_VALUE:POST:/usage`) - Block by IP: `ip:123.123.123.123` ### Useful Links -- CloudFlare List of KVs: https://dash.cloudflare.com/6d5bc18cd8d13babe7ed321adba3d8ae/workers/kv/namespaces (we use `hive-police-ENV`) +- CloudFlare List of KVs: + https://dash.cloudflare.com/6d5bc18cd8d13babe7ed321adba3d8ae/workers/kv/namespaces (we use + `hive-police-ENV`) - CF Expressions documentation: https://developers.cloudflare.com/ruleset-engine/rules-language/ diff --git a/packages/services/police-worker/package.json b/packages/services/police-worker/package.json index 552fe250f..4089b19f7 100644 --- a/packages/services/police-worker/package.json +++ b/packages/services/police-worker/package.json @@ -1,15 +1,15 @@ { "name": "@hive/police-script", - "private": true, "version": "0.0.0", "license": "MIT", + "private": true, "scripts": { "build": "node build.mjs", "typecheck": "tsc --noEmit" }, "devDependencies": { - "esbuild": "0.14.39", + "@cloudflare/workers-types": "3.4.0", "@types/service-worker-mock": "2.0.1", - "@cloudflare/workers-types": "3.4.0" + "esbuild": "0.14.39" } } diff --git a/packages/services/police-worker/src/index.ts b/packages/services/police-worker/src/index.ts index 41e63b391..4564ea9d0 100644 --- a/packages/services/police-worker/src/index.ts +++ b/packages/services/police-worker/src/index.ts @@ -61,10 +61,14 @@ async function handleSchedule() { for (const headerName of headerNames) { // if header value is 'empty', block all requests without the header if (headerValue === 'empty' || headerValue === 'undefined') { - headerRules.push(`not any(lower(http.request.headers.names[*])[*] contains "${headerName}")`); + headerRules.push( + `not any(lower(http.request.headers.names[*])[*] contains "${headerName}")`, + ); } else { // if header value not 'empty', block all requests with the header of the given value - headerRules.push(`any(http.request.headers["${headerName}"][*] contains "${headerValue}")`); + headerRules.push( + `any(http.request.headers["${headerName}"][*] contains "${headerValue}")`, + ); } } diff --git a/packages/services/rate-limit/README.md b/packages/services/rate-limit/README.md index 1f404401d..7ffa264fd 100644 --- a/packages/services/rate-limit/README.md +++ b/packages/services/rate-limit/README.md @@ -1,7 +1,7 @@ # Rate Limit -The rate limit service is responsible of enforcing account limitations. -If you are self-hosting Hive you don't need this service. +The rate limit service is responsible of enforcing account limitations. If you are self-hosting Hive +you don't need this service. ## Configuration diff --git a/packages/services/rate-limit/package.json b/packages/services/rate-limit/package.json index a57d44ee6..a7137b88f 100644 --- a/packages/services/rate-limit/package.json +++ b/packages/services/rate-limit/package.json @@ -1,26 +1,26 @@ { - "private": true, - "type": "module", "name": "@hive/rate-limit", - "description": "A microservice for Hive SaaS, that exposes information about rate limits per given org/target.", "version": "0.0.0", + "type": "module", + "description": "A microservice for Hive SaaS, that exposes information about rate limits per given org/target.", "license": "MIT", + "private": true, "scripts": { - "dev": "tsup-node src/dev.ts --watch --format esm --target node16 --onSuccess 'node dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "build": "bob runify --single", + "dev": "tsup-node src/dev.ts --watch --format esm --target node16 --onSuccess 'node dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "typecheck": "tsc --noEmit" }, "dependencies": { - "@whatwg-node/fetch": "0.4.7", - "zod": "3.15.1", - "@trpc/server": "9.23.2", - "@trpc/client": "9.23.2", - "reflect-metadata": "0.1.13", "@sentry/node": "7.20.1", "@sentry/tracing": "7.20.1", - "dotenv": "10.0.0", + "@trpc/client": "9.23.2", + "@trpc/server": "9.23.2", + "@whatwg-node/fetch": "0.4.7", "date-fns": "2.28.0", - "got": "12.5.3" + "dotenv": "10.0.0", + "got": "12.5.3", + "reflect-metadata": "0.1.13", + "zod": "3.15.1" }, "devDependencies": { "@hive/emails": "workspace:*", diff --git a/packages/services/rate-limit/src/emails.ts b/packages/services/rate-limit/src/emails.ts index 28ee4ddfb..c445b508c 100644 --- a/packages/services/rate-limit/src/emails.ts +++ b/packages/services/rate-limit/src/emails.ts @@ -62,12 +62,16 @@ export function createEmailScheduler(config?: { endpoint: string }) { Your Hive organization ${ input.organization.name } has reached over 100% of the operations limit quota. - Used ${numberFormatter.format(input.usage.current)} of ${numberFormatter.format(input.usage.quota)}. + Used ${numberFormatter.format(input.usage.current)} of ${numberFormatter.format( + input.usage.quota, + )}. . We recommend to increase the limit. - + Manage your subscription @@ -75,7 +79,7 @@ export function createEmailScheduler(config?: { endpoint: string }) { `, - }) + }), ); }, @@ -122,12 +126,16 @@ export function createEmailScheduler(config?: { endpoint: string }) { Your organization ${ input.organization.name } is approaching its operations limit quota. - Used ${numberFormatter.format(input.usage.current)} of ${numberFormatter.format(input.usage.quota)}. + Used ${numberFormatter.format(input.usage.current)} of ${numberFormatter.format( + input.usage.quota, + )}. . We recommend to increase the limit. - + Manage your subscription @@ -135,7 +143,7 @@ export function createEmailScheduler(config?: { endpoint: string }) { `, - }) + }), ); }, }; diff --git a/packages/services/rate-limit/src/environment.ts b/packages/services/rate-limit/src/environment.ts index 374ba191b..2a632aa31 100644 --- a/packages/services/rate-limit/src/environment.ts +++ b/packages/services/rate-limit/src/environment.ts @@ -62,7 +62,7 @@ const LogModel = zod.object({ zod.literal('fatal'), zod.literal('silent'), ]) - .optional() + .optional(), ), }); diff --git a/packages/services/rate-limit/src/index.ts b/packages/services/rate-limit/src/index.ts index 6310cad70..a599d6769 100644 --- a/packages/services/rate-limit/src/index.ts +++ b/packages/services/rate-limit/src/index.ts @@ -1,7 +1,12 @@ #!/usr/bin/env node import 'reflect-metadata'; import * as Sentry from '@sentry/node'; -import { createServer, startMetrics, registerShutdown, reportReadiness } from '@hive/service-common'; +import { + createServer, + startMetrics, + registerShutdown, + reportReadiness, +} from '@hive/service-common'; import { createRateLimiter } from './limiter'; import { createConnectionString } from '@hive/storage'; import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify/dist/trpc-server-adapters-fastify.cjs.js'; diff --git a/packages/services/rate-limit/src/limiter.ts b/packages/services/rate-limit/src/limiter.ts index daaf6d904..973de39cf 100644 --- a/packages/services/rate-limit/src/limiter.ts +++ b/packages/services/rate-limit/src/limiter.ts @@ -77,7 +77,7 @@ export function createRateLimiter(config: { endTime: endOfMonth(now).toUTCString(), }; config.logger.info( - `Calculating rate-limit information based on window: ${windowAsString.startTime} -> ${windowAsString.endTime}` + `Calculating rate-limit information based on window: ${windowAsString.startTime} -> ${windowAsString.endTime}`, ); const storage = await postgres$; @@ -87,7 +87,9 @@ export function createRateLimiter(config: { ]); logger.debug(`Fetched total of ${Object.keys(records).length} targets from the DB`); - logger.debug(`Fetched total of ${Object.keys(operations).length} targets with usage information`); + logger.debug( + `Fetched total of ${Object.keys(operations).length} targets with usage information`, + ); const newTargetIdToOrgLookup = new Map(); const newCachedResult = new Map(); @@ -110,18 +112,21 @@ export function createRateLimiter(config: { } const orgRecord = newCachedResult.get(pairRecord.organization)!; - orgRecord.operations.current = (orgRecord.operations.current || 0) + (operations[pairRecord.target] || 0); + orgRecord.operations.current = + (orgRecord.operations.current || 0) + (operations[pairRecord.target] || 0); } newCachedResult.forEach((orgRecord, orgId) => { const orgName = orgRecord.orgName; const noLimits = orgRecord.operations.quota === 0; - orgRecord.operations.limited = noLimits ? false : orgRecord.operations.current > orgRecord.operations.quota; + orgRecord.operations.limited = noLimits + ? false + : orgRecord.operations.current > orgRecord.operations.quota; if (orgRecord.operations.limited) { rateLimitOperationsEventOrg.labels({ orgId, orgName }).inc(); logger.info( - `Organization "${orgName}"/"${orgId}" is now being rate-limited for operations (${orgRecord.operations.current}/${orgRecord.operations.quota})` + `Organization "${orgName}"/"${orgId}" is now being rate-limited for operations (${orgRecord.operations.current}/${orgRecord.operations.quota})`, ); emails.limitExceeded({ @@ -162,7 +167,10 @@ export function createRateLimiter(config: { cachedResult = newCachedResult; targetIdToOrgLookup = newTargetIdToOrgLookup; - logger.info(`Built a new rate-limit map: %s`, JSON.stringify(Array.from(newCachedResult.entries()))); + logger.info( + `Built a new rate-limit map: %s`, + JSON.stringify(Array.from(newCachedResult.entries())), + ); const scheduledEmails = emails.drain(); if (scheduledEmails.length > 0) { @@ -192,11 +200,12 @@ export function createRateLimiter(config: { return orgData.retentionInDays; }, checkLimit(input: RateLimitInput): RateLimitCheckResponse { - const orgId = input.entityType === 'organization' ? input.id : targetIdToOrgLookup.get(input.id); + const orgId = + input.entityType === 'organization' ? input.id : targetIdToOrgLookup.get(input.id); if (!orgId) { logger.warn( - `Failed to resolve/find rate limit information for entityId=${input.id} (type=${input.entityType})` + `Failed to resolve/find rate limit information for entityId=${input.id} (type=${input.entityType})`, ); return UNKNOWN_RATE_LIMIT_OBJ; @@ -216,7 +225,7 @@ export function createRateLimiter(config: { }, async start() { logger.info( - `Rate Limiter starting, will update rate-limit information every ${config.rateLimitConfig.interval}ms` + `Rate Limiter starting, will update rate-limit information every ${config.rateLimitConfig.interval}ms`, ); await fetchAndCalculateUsageInformation().catch(e => { logger.error(e, `Failed to fetch rate-limit info from usage-estimator, error: `); diff --git a/packages/services/schema/README.md b/packages/services/schema/README.md index 6e865f820..5da4850bd 100644 --- a/packages/services/schema/README.md +++ b/packages/services/schema/README.md @@ -1,6 +1,7 @@ # `@hive/schema` -Service for validating schemas or verifying whether a composite GraphQL schema can be composed out of subschemas. +Service for validating schemas or verifying whether a composite GraphQL schema can be composed out +of subschemas. ## Configuration diff --git a/packages/services/schema/package.json b/packages/services/schema/package.json index 40165ced3..3d6817871 100644 --- a/packages/services/schema/package.json +++ b/packages/services/schema/package.json @@ -1,34 +1,34 @@ { "name": "@hive/schema", - "private": true, - "type": "module", "version": "0.0.0", + "type": "module", "license": "MIT", + "private": true, "scripts": { - "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "build": "bob runify --single", + "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "typecheck": "tsc --noEmit" }, "dependencies": { "@apollo/federation": "0.38.1", - "async-retry": "1.3.3", - "@whatwg-node/fetch": "0.4.7", - "zod": "3.15.1", - "@trpc/server": "9.23.2", "@graphql-tools/stitch": "8.7.13", "@graphql-tools/stitching-directives": "2.3.11", "@sentry/node": "7.20.1", "@sentry/tracing": "7.20.1", + "@trpc/server": "9.23.2", + "@whatwg-node/fetch": "0.4.7", + "async-retry": "1.3.3", "dotenv": "10.0.0", "graphql": "16.5.0", - "ioredis": "4.28.5" + "ioredis": "4.28.5", + "zod": "3.15.1" }, "devDependencies": { "@hive/service-common": "workspace:*", "@types/async-retry": "1.4.5", "@types/ioredis": "4.28.10", - "pino-pretty": "6.0.0", - "fastify": "3.29.4" + "fastify": "3.29.4", + "pino-pretty": "6.0.0" }, "buildOptions": { "runify": true, diff --git a/packages/services/schema/src/api.ts b/packages/services/schema/src/api.ts index 476566301..aeb2e408e 100644 --- a/packages/services/schema/src/api.ts +++ b/packages/services/schema/src/api.ts @@ -39,7 +39,7 @@ export const schemaBuilderApiRouter = trpc ...SCHEMA_OBJECT_VALIDATION, url: z.string().nullish(), }) - .required() + .required(), ), external: EXTERNAL_VALIDATION.optional(), }) @@ -57,7 +57,7 @@ export const schemaBuilderApiRouter = trpc ...input.external, broker: ctx.broker, } - : null + : null, ); }, }) @@ -82,7 +82,7 @@ export const schemaBuilderApiRouter = trpc ...input.external, broker: ctx.broker, } - : null + : null, ); }, }) @@ -107,7 +107,7 @@ export const schemaBuilderApiRouter = trpc ...input.external, broker: ctx.broker, } - : null + : null, ); }, }); @@ -115,6 +115,5 @@ export const schemaBuilderApiRouter = trpc export type SchemaBuilderApi = typeof schemaBuilderApiRouter; export type SchemaBuilderApiMutate = keyof SchemaBuilderApi['_def']['mutations']; -export type SchemaBuilderMutationInput = inferProcedureInput< - SchemaBuilderApi['_def']['mutations'][TRouteKey] ->; +export type SchemaBuilderMutationInput = + inferProcedureInput; diff --git a/packages/services/schema/src/environment.ts b/packages/services/schema/src/environment.ts index dfaee3ccb..241200725 100644 --- a/packages/services/schema/src/environment.ts +++ b/packages/services/schema/src/environment.ts @@ -68,7 +68,7 @@ const LogModel = zod.object({ zod.literal('fatal'), zod.literal('silent'), ]) - .optional() + .optional(), ), }); diff --git a/packages/services/schema/src/orchestrators.ts b/packages/services/schema/src/orchestrators.ts index cac15c052..91670ce55 100644 --- a/packages/services/schema/src/orchestrators.ts +++ b/packages/services/schema/src/orchestrators.ts @@ -119,7 +119,7 @@ function hash(secret: string, alg: string, value: string) { const createFederation: ( redis: RedisInstance, logger: FastifyLoggerInstance, - decrypt: (value: string) => string + decrypt: (value: string) => string, ) => Orchestrator = (redis, logger, decrypt) => { const compose = reuse< { @@ -137,7 +137,7 @@ const createFederation: ( name: schema.source, url: 'url' in schema && typeof schema.url === 'string' ? schema.url : undefined, }; - }) + }), ); const signature = hash(decrypt(external.encryptedSecret), 'sha256', body); logger.debug('Calling external composition service (url=%s)', external.endpoint); @@ -175,7 +175,9 @@ const createFederation: ( }); if (!response.ok) { - const message = await response.text().catch(_ => Promise.resolve(response.statusText)); + const message = await response + .text() + .catch(_ => Promise.resolve(response.statusText)); throw new Error(`External composition failure: ${response.status} ${message}`); } @@ -183,7 +185,7 @@ const createFederation: ( }, { retries: 3, - } + }, ); const parseResult = EXTERNAL_COMPOSITION_RESULT.safeParse(await response); @@ -212,7 +214,7 @@ const createFederation: ( name: schema.source, url: 'url' in schema && typeof schema.url === 'string' ? schema.url : undefined, }; - }) + }), ); if (compositionHasErrors(result)) { @@ -234,7 +236,7 @@ const createFederation: ( }, 'federation', redis, - logger + logger, ); return { @@ -256,7 +258,10 @@ const createFederation: ( if (result.type === 'failure') { throw new Error( - [`Schemas couldn't be merged:`, result.result.errors.map(error => `\t - ${error.message}`)].join('\n') + [ + `Schemas couldn't be merged:`, + result.result.errors.map(error => `\t - ${error.message}`), + ].join('\n'), ); } @@ -297,18 +302,21 @@ const single: Orchestrator = { }, }; -const createStitching: (redis: RedisInstance, logger: FastifyLoggerInstance) => Orchestrator = (redis, logger) => { +const createStitching: (redis: RedisInstance, logger: FastifyLoggerInstance) => Orchestrator = ( + redis, + logger, +) => { const stitchAndPrint = reuse( async (schemas: ValidationInput) => { return printSchema( stitchSchemas({ typeDefs: schemas.map(schema => trimDescriptions(parse(schema.raw))), - }) + }), ); }, 'stitching', redis, - logger + logger, ); return { @@ -343,14 +351,16 @@ const createStitching: (redis: RedisInstance, logger: FastifyLoggerInstance) => function validateStitchedSchema(doc: DocumentNode) { const { allStitchingDirectivesTypeDefs } = stitchingDirectives(); - return validateSDL(concatAST([parse(allStitchingDirectivesTypeDefs), doc])).map(toValidationError); + return validateSDL(concatAST([parse(allStitchingDirectivesTypeDefs), doc])).map( + toValidationError, + ); } export function pickOrchestrator( type: SchemaType, redis: RedisInstance, logger: FastifyLoggerInstance, - decrypt: (value: string) => string + decrypt: (value: string) => string, ) { switch (type) { case 'federation': @@ -374,12 +384,15 @@ interface ActionCompleted { } function createChecksum(input: TInput, uniqueKey: string): string { - return createHash('sha256').update(JSON.stringify(input)).update(`key:${uniqueKey}`).digest('hex'); + return createHash('sha256') + .update(JSON.stringify(input)) + .update(`key:${uniqueKey}`) + .digest('hex'); } async function readAction( checksum: string, - redis: RedisInstance + redis: RedisInstance, ): Promise | null> { const action = await redis.get(`schema-service:${checksum}`); @@ -390,7 +403,11 @@ async function readAction( return null; } -async function startAction(checksum: string, redis: RedisInstance, logger: FastifyLoggerInstance): Promise { +async function startAction( + checksum: string, + redis: RedisInstance, + logger: FastifyLoggerInstance, +): Promise { const key = `schema-service:${checksum}`; logger.debug('Starting action (checksum=%s)', checksum); // Set and lock + expire @@ -411,7 +428,7 @@ async function completeAction( checksum: string, data: O, redis: RedisInstance, - logger: FastifyLoggerInstance + logger: FastifyLoggerInstance, ): Promise { const key = `schema-service:${checksum}`; logger.debug('Completing action (checksum=%s)', checksum); @@ -421,11 +438,15 @@ async function completeAction( JSON.stringify({ status: 'completed', result: data, - }) + }), ); } -async function removeAction(checksum: string, redis: RedisInstance, logger: FastifyLoggerInstance): Promise { +async function removeAction( + checksum: string, + redis: RedisInstance, + logger: FastifyLoggerInstance, +): Promise { logger.debug('Removing action (checksum=%s)', checksum); const key = `schema-service:${checksum}`; await redis.del(key); @@ -435,7 +456,7 @@ function reuse( factory: (input: I) => Promise, key: string, redis: RedisInstance, - logger: FastifyLoggerInstance + logger: FastifyLoggerInstance, ): (input: I) => Promise { async function reuseFactory(input: I, attempt = 0): Promise { const checksum = createChecksum(input, key); @@ -465,7 +486,11 @@ function reuse( const startedAt = Date.now(); while (cached && cached.status !== 'completed') { - logger.debug('Waiting action to complete (checksum=%s, time=%s)', checksum, Date.now() - startedAt); + logger.debug( + 'Waiting action to complete (checksum=%s, time=%s)', + checksum, + Date.now() - startedAt, + ); await new Promise(resolve => setTimeout(resolve, 500)); cached = await readAction(checksum, redis); diff --git a/packages/services/server/README.md b/packages/services/server/README.md index 82680b26e..f4743da65 100644 --- a/packages/services/server/README.md +++ b/packages/services/server/README.md @@ -42,7 +42,8 @@ The GraphQL API for GraphQL Hive. ## Hive Hosted Configuration -If you are self-hosting GraphQL Hive, you can ignore this section. It is only required for the SaaS version. +If you are self-hosting GraphQL Hive, you can ignore this section. It is only required for the SaaS +version. | Name | Required | Description | Example Value | | ------------------------------------ | ------------------------------------------ | -------------------------------------------- | ---------------------------------- | diff --git a/packages/services/server/package.json b/packages/services/server/package.json index 2b0b1dd55..75caac0ff 100644 --- a/packages/services/server/package.json +++ b/packages/services/server/package.json @@ -1,41 +1,41 @@ { "name": "@hive/server", - "type": "module", - "private": true, "version": "0.0.0", + "type": "module", "license": "MIT", + "private": true, "scripts": { - "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "build": "bob runify --single", + "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "typecheck": "tsc --noEmit" }, "dependencies": { "@envelop/core": "3.0.3", - "@envelop/graphql-modules": "4.0.3", "@envelop/generic-auth": "5.0.3", + "@envelop/graphql-modules": "4.0.3", "@envelop/sentry": "4.0.3", "@envelop/types": "3.0.0", - "graphql-yoga": "3.0.0-next.12", - "@sentry/node": "7.20.1", "@sentry/integrations": "7.20.1", + "@sentry/node": "7.20.1", "@sentry/tracing": "7.20.1", + "@trpc/server": "9.23.2", + "@whatwg-node/fetch": "0.4.7", "dotenv": "10.0.0", "got": "12.5.3", "graphql": "16.5.0", + "graphql-yoga": "3.0.0-next.12", "hyperid": "2.3.1", "reflect-metadata": "0.1.13", - "@trpc/server": "9.23.2", - "zod": "3.15.1", - "@whatwg-node/fetch": "0.4.7" + "zod": "3.15.1" }, "devDependencies": { "@graphql-hive/client": "0.21.3", "@hive/api": "workspace:*", "@hive/service-common": "workspace:*", "@hive/storage": "workspace:*", - "pino-pretty": "6.0.0", "@swc/core": "1.2.185", "fastify": "3.29.4", + "pino-pretty": "6.0.0", "tslib": "2.4.1" }, "buildOptions": { diff --git a/packages/services/server/src/api.ts b/packages/services/server/src/api.ts index 498b0b1ae..3fe0b0c62 100644 --- a/packages/services/server/src/api.ts +++ b/packages/services/server/src/api.ts @@ -12,7 +12,13 @@ import type { Storage } from '@hive/api'; import { z } from 'zod'; import { CryptoProvider } from 'packages/services/api/src/modules/shared/providers/crypto'; -export async function createContext({ storage, crypto }: { storage: Storage; crypto: CryptoProvider }) { +export async function createContext({ + storage, + crypto, +}: { + storage: Storage; + crypto: CryptoProvider; +}) { return { storage, crypto, @@ -59,13 +65,19 @@ export const internalApiRouter = router() lastOrgId: z.union([z.string(), z.null()]), }), async resolve({ input, ctx }) { - const user = await ctx.storage.getUserBySuperTokenId({ superTokensUserId: input.superTokensUserId }); + const user = await ctx.storage.getUserBySuperTokenId({ + superTokensUserId: input.superTokensUserId, + }); // For an OIDC Integration User we want to return the linked organization if (user?.oidcIntegrationId) { - const oidcIntegration = await ctx.storage.getOIDCIntegrationById({ oidcIntegrationId: user.oidcIntegrationId }); + const oidcIntegration = await ctx.storage.getOIDCIntegrationById({ + oidcIntegrationId: user.oidcIntegrationId, + }); if (oidcIntegration) { - const org = await ctx.storage.getOrganization({ organization: oidcIntegration.linkedOrganizationId }); + const org = await ctx.storage.getOrganization({ + organization: oidcIntegration.linkedOrganizationId, + }); return { id: org.id, @@ -107,7 +119,9 @@ export const internalApiRouter = router() oidcIntegrationId: z.string().min(1), }), async resolve({ input, ctx }) { - const result = await ctx.storage.getOIDCIntegrationById({ oidcIntegrationId: input.oidcIntegrationId }); + const result = await ctx.storage.getOIDCIntegrationById({ + oidcIntegrationId: input.oidcIntegrationId, + }); if (result == null) { return null; } diff --git a/packages/services/server/src/environment.ts b/packages/services/server/src/environment.ts index b7a33aebe..538c1d8b7 100644 --- a/packages/services/server/src/environment.ts +++ b/packages/services/server/src/environment.ts @@ -136,7 +136,7 @@ const LogModel = zod.object({ zod.literal('fatal'), zod.literal('silent'), ]) - .optional() + .optional(), ), }); @@ -205,7 +205,8 @@ const hiveConfig = hive.HIVE === '1' ? { token: hive.HIVE_API_TOKEN, - reporting: hive.HIVE_REPORTING === '1' ? { endpoint: hive.HIVE_REPORTING_ENDPOINT ?? null } : null, + reporting: + hive.HIVE_REPORTING === '1' ? { endpoint: hive.HIVE_REPORTING_ENDPOINT ?? null } : null, usage: hive.HIVE_USAGE === '1' ? { endpoint: hive.HIVE_USAGE_ENDPOINT ?? null } : null, } : null; @@ -230,7 +231,9 @@ export const env = { endpoint: base.RATE_LIMIT_ENDPOINT, } : null, - usageEstimator: base.USAGE_ESTIMATOR_ENDPOINT ? { endpoint: base.USAGE_ESTIMATOR_ENDPOINT } : null, + usageEstimator: base.USAGE_ESTIMATOR_ENDPOINT + ? { endpoint: base.USAGE_ESTIMATOR_ENDPOINT } + : null, billing: base.BILLING_ENDPOINT ? { endpoint: base.BILLING_ENDPOINT } : null, emails: base.EMAILS_ENDPOINT ? { endpoint: base.EMAILS_ENDPOINT } : null, webhooks: { endpoint: base.WEBHOOKS_ENDPOINT }, diff --git a/packages/services/server/src/graphql-handler.ts b/packages/services/server/src/graphql-handler.ts index 1a5a171ca..8b8c7108a 100644 --- a/packages/services/server/src/graphql-handler.ts +++ b/packages/services/server/src/graphql-handler.ts @@ -4,7 +4,14 @@ import { Registry } from '@hive/api'; import { cleanRequestId } from '@hive/service-common'; import { createYoga, useErrorHandler, Plugin } from 'graphql-yoga'; import { isGraphQLError } from '@envelop/core'; -import { GraphQLError, ValidationContext, ValidationRule, Kind, OperationDefinitionNode, print } from 'graphql'; +import { + GraphQLError, + ValidationContext, + ValidationRule, + Kind, + OperationDefinitionNode, + print, +} from 'graphql'; import { useGraphQLModules } from '@envelop/graphql-modules'; import { useGenericAuth } from '@envelop/generic-auth'; import { fetch } from '@whatwg-node/fetch'; @@ -95,7 +102,7 @@ export const graphqlHandler = (options: GraphQLHandlerOptions): RouteHandlerMeth operationName: () => 'graphql', transactionName(args) { const rootOperation = args.document.definitions.find( - o => o.kind === Kind.OPERATION_DEFINITION + o => o.kind === Kind.OPERATION_DEFINITION, ) as OperationDefinitionNode; const operationType = rootOperation.operation; const opName = args.operationName || rootOperation.name?.value || 'anonymous'; @@ -111,7 +118,9 @@ export const graphqlHandler = (options: GraphQLHandlerOptions): RouteHandlerMeth // Reduce the number of transactions to avoid overloading Sentry const ctx = args.contextValue as Context; const clientNameHeaderValue = ctx.req.headers['graphql-client-name']; - const clientName = Array.isArray(clientNameHeaderValue) ? clientNameHeaderValue[0] : clientNameHeaderValue; + const clientName = Array.isArray(clientNameHeaderValue) + ? clientNameHeaderValue[0] + : clientNameHeaderValue; if (transaction) { transaction.setTag('graphql_client_name', clientName ?? 'unknown'); @@ -173,7 +182,7 @@ export const graphqlHandler = (options: GraphQLHandlerOptions): RouteHandlerMeth return await verifySuperTokensSession( options.supertokens.connectionUri, options.supertokens.apiKey, - accessToken + accessToken, ); } } @@ -207,7 +216,10 @@ export const graphqlHandler = (options: GraphQLHandlerOptions): RouteHandlerMeth }, }), useGraphQLModules(options.registry), - useNoIntrospection({ signature: options.signature, isNonProductionEnvironment: options.isProduction === false }), + useNoIntrospection({ + signature: options.signature, + isNonProductionEnvironment: options.isProduction === false, + }), ], /* graphiql: request => @@ -251,7 +263,7 @@ export const graphqlHandler = (options: GraphQLHandlerOptions): RouteHandlerMeth reply.send(response.body); return reply; - } + }, ); }; }; @@ -263,7 +275,7 @@ export const graphqlHandler = (options: GraphQLHandlerOptions): RouteHandlerMeth async function verifySuperTokensSession( connectionUri: string, apiKey: string, - accessToken: string + accessToken: string, ): Promise { const response = await fetch(connectionUri + '/recipe/session/verify', { method: 'POST', @@ -280,7 +292,9 @@ async function verifySuperTokensSession( }); const body = await response.text(); if (response.status !== 200) { - console.error(`SuperTokens session verification failed with status ${response.status}.\n` + body); + console.error( + `SuperTokens session verification failed with status ${response.status}.\n` + body, + ); throw new HiveError(`Invalid token.`); } diff --git a/packages/services/server/src/index.ts b/packages/services/server/src/index.ts index a958b8a56..06163f648 100644 --- a/packages/services/server/src/index.ts +++ b/packages/services/server/src/index.ts @@ -1,7 +1,12 @@ #!/usr/bin/env node import 'reflect-metadata'; -import { createServer, startMetrics, registerShutdown, reportReadiness } from '@hive/service-common'; +import { + createServer, + startMetrics, + registerShutdown, + reportReadiness, +} from '@hive/service-common'; import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify/dist/trpc-server-adapters-fastify.cjs.js'; import { createRegistry, LogFn, Logger } from '@hive/api'; import { createStorage as createPostgreSQLStorage, createConnectionString } from '@hive/storage'; @@ -72,7 +77,8 @@ export async function main() { return (error: any, errorLike?: any, ...args: any[]) => { server.log.error(error, errorLike, ...args); - const errorObj = error instanceof Error ? error : errorLike instanceof Error ? errorLike : null; + const errorObj = + error instanceof Error ? error : errorLike instanceof Error ? errorLike : null; if (errorObj instanceof GraphQLError) { return; @@ -187,9 +193,9 @@ export async function main() { schemaConfig: env.hiveServices.webApp ? { schemaPublishLink(input) { - let url = `${env.hiveServices.webApp!.url}/${input.organization.cleanId}/${input.project.cleanId}/${ - input.target.cleanId - }`; + let url = `${env.hiveServices.webApp!.url}/${input.organization.cleanId}/${ + input.project.cleanId + }/${input.target.cleanId}`; if (input.version) { url += `/history/${input.version.id}`; @@ -303,11 +309,15 @@ export async function main() { url: '/__legacy/update_user_id_mapping', async handler(req, reply) { if (req.headers['x-authorization'] !== auth0Config.apiKey) { - reply.status(401).send({ error: 'Invalid update user id mapping key.', code: 'ERR_INVALID_KEY' }); // eslint-disable-line @typescript-eslint/no-floating-promises -- false positive, FastifyReply.then returns void + void reply + .status(401) + .send({ error: 'Invalid update user id mapping key.', code: 'ERR_INVALID_KEY' }); return; } - const { auth0UserId, superTokensUserId } = LegacySetUserIdMappingPayloadModel.parse(req.body); + const { auth0UserId, superTokensUserId } = LegacySetUserIdMappingPayloadModel.parse( + req.body, + ); await storage.setSuperTokensUserId({ auth0UserId: auth0UserId.replace('google|', 'google-oauth2|'), @@ -322,7 +332,9 @@ export async function main() { url: '/__legacy/check_auth0_email_user_without_associated_supertoken_id_exists', async handler(req, reply) { if (req.headers['x-authorization'] !== auth0Config.apiKey) { - reply.status(401).send({ error: 'Invalid update user id mapping key.', code: 'ERR_INVALID_KEY' }); // eslint-disable-line @typescript-eslint/no-floating-promises -- false positive, FastifyReply.then returns void + void reply + .status(401) + .send({ error: 'Invalid update user id mapping key.', code: 'ERR_INVALID_KEY' }); return; } diff --git a/packages/services/service-common/package.json b/packages/services/service-common/package.json index 18cda9347..806f4b8a4 100644 --- a/packages/services/service-common/package.json +++ b/packages/services/service-common/package.json @@ -1,9 +1,14 @@ { - "private": true, - "type": "module", "name": "@hive/service-common", "version": "0.0.0", + "type": "module", "license": "MIT", + "private": true, + "peerDependencies": { + "@sentry/node": "^7.0.0", + "@sentry/tracing": "^7.0.0", + "@sentry/utils": "^7.0.0" + }, "dependencies": { "@whatwg-node/fetch": "0.4.7", "fastify": "3.29.4", @@ -12,11 +17,6 @@ "prom-client": "14.0.1", "zod": "3.15.1" }, - "peerDependencies": { - "@sentry/node": "^7.0.0", - "@sentry/tracing": "^7.0.0", - "@sentry/utils": "^7.0.0" - }, "devDependencies": { "@sentry/node": "7.20.1", "@sentry/tracing": "7.20.1", diff --git a/packages/services/service-common/src/helpers.ts b/packages/services/service-common/src/helpers.ts index 29ed67477..9f7e858d0 100644 --- a/packages/services/service-common/src/helpers.ts +++ b/packages/services/service-common/src/helpers.ts @@ -16,7 +16,7 @@ export function invariant( condition: unknown, // Can provide a string, or a function that returns a string for cases where // the message takes a fair amount of effort to compute - message?: string | (() => string) + message?: string | (() => string), ): asserts condition { if (condition) { return; diff --git a/packages/services/service-common/src/sentry.ts b/packages/services/service-common/src/sentry.ts index d09c60512..a2234a239 100644 --- a/packages/services/service-common/src/sentry.ts +++ b/packages/services/service-common/src/sentry.ts @@ -51,7 +51,7 @@ const plugin: FastifyPluginAsync = async server => { name: `${request.method} ${request.url}`, ...traceparentData, }, - { request: extractedRequestData } + { request: extractedRequestData }, ); (request as any).sentryTransaction = transaction; @@ -74,7 +74,10 @@ const plugin: FastifyPluginAsync = async server => { transaction.setData('query', request.query); transaction.setData('authorization', replaceAuthorization(request.headers.authorization)); - transaction.setData('x-api-token', replaceAuthorization(request.headers['x-api-token'] as any)); + transaction.setData( + 'x-api-token', + replaceAuthorization(request.headers['x-api-token'] as any), + ); transaction.setHttpStatus(reply.statusCode); transaction.finish(); @@ -110,7 +113,7 @@ const plugin: FastifyPluginAsync = async server => { JSON.stringify({ error: 500, message: 'Internal Server Error', - }) + }), ); }); }); @@ -123,7 +126,10 @@ const sentryPlugin = fp(plugin, { /** Default request keys that'll be used to extract data from the request */ const DEFAULT_REQUEST_KEYS = ['data', 'headers', 'method', 'query_string', 'url']; -function extractRequestData(req: FastifyRequest, keys: string[] = DEFAULT_REQUEST_KEYS): ExtractedNodeRequestData { +function extractRequestData( + req: FastifyRequest, + keys: string[] = DEFAULT_REQUEST_KEYS, +): ExtractedNodeRequestData { const requestData: { [key: string]: any } = {}; const headers = req.headers; @@ -152,7 +158,8 @@ function extractRequestData(req: FastifyRequest, keys: string[] = DEFAULT_REQUES break; } if (req.body !== undefined) { - requestData.data = typeof req.body === 'string' ? req.body : JSON.stringify(normalize(req.body)); + requestData.data = + typeof req.body === 'string' ? req.body : JSON.stringify(normalize(req.body)); } break; default: diff --git a/packages/services/storage/docker-compose.yml b/packages/services/storage/docker-compose.yml index bfd8be154..a1ceea278 100644 --- a/packages/services/storage/docker-compose.yml +++ b/packages/services/storage/docker-compose.yml @@ -94,7 +94,18 @@ services: soft: 20000 hard: 40000 healthcheck: - test: ['CMD', 'cub', 'kafka-ready', '1', '5', '-b', '127.0.0.1:9092', '-c', '/etc/kafka/kafka.properties'] + test: + [ + 'CMD', + 'cub', + 'kafka-ready', + '1', + '5', + '-b', + '127.0.0.1:9092', + '-c', + '/etc/kafka/kafka.properties', + ] interval: 15s timeout: 10s retries: 6 diff --git a/packages/services/storage/migrations/clickhouse.ts b/packages/services/storage/migrations/clickhouse.ts index dff5b02b3..f467b913a 100644 --- a/packages/services/storage/migrations/clickhouse.ts +++ b/packages/services/storage/migrations/clickhouse.ts @@ -328,7 +328,7 @@ export async function migrateClickHouse( port: number; username: string; password: string; - } + }, ) { if (isClickHouseMigrator === false) { console.log('Skipping ClickHouse migration'); diff --git a/packages/services/storage/package.json b/packages/services/storage/package.json index c1d9cbc3d..489ceb35e 100644 --- a/packages/services/storage/package.json +++ b/packages/services/storage/package.json @@ -1,24 +1,27 @@ { "name": "@hive/storage", - "type": "module", - "private": true, "version": "0.0.0", + "type": "module", "license": "MIT", + "private": true, + "engines": { + "node": ">=12" + }, "scripts": { - "setup": "pnpm db:start && pnpm db", - "prune": "docker compose down && rm -rf volumes", + "build": "bob runify --single", "db": "pnpm db:create && pnpm migration:run", "db:create": "node tools/create-db.mjs", - "db:migrator": "node --experimental-specifier-resolution=node --loader ts-node/esm migrations/index.ts", - "migration:create": "pnpm db:migrator create", - "migration:run": "pnpm db:migrator up", - "migration:rollback": "pnpm db:migrator down", - "db:generate": "schemats generate --config schemats.cjs -o src/db/types.ts", - "db:start": "docker-compose up -d --remove-orphans", "db:dev": "docker compose up --remove-orphans --abort-on-container-exit", + "db:generate": "schemats generate --config schemats.cjs -o src/db/types.ts", + "db:migrator": "node --experimental-specifier-resolution=node --loader ts-node/esm migrations/index.ts", + "db:start": "docker-compose up -d --remove-orphans", "dev": "pnpm db:dev", - "build": "bob runify --single", - "postbuild": "copyfiles -f \"migrations/actions/*.sql\" dist/actions && copyfiles -f \"migrations/actions/down/*.sql\" dist/actions/down" + "migration:create": "pnpm db:migrator create", + "migration:rollback": "pnpm db:migrator down", + "migration:run": "pnpm db:migrator up", + "postbuild": "copyfiles -f \"migrations/actions/*.sql\" dist/actions && copyfiles -f \"migrations/actions/down/*.sql\" dist/actions/down", + "prune": "docker compose down && rm -rf volumes", + "setup": "pnpm db:start && pnpm db" }, "dependencies": { "@sentry/node": "7.20.1", @@ -41,9 +44,6 @@ "ts-node": "10.9.1", "tslib": "2.4.1" }, - "engines": { - "node": ">=12" - }, "buildOptions": { "runify": true, "tsup": true, diff --git a/packages/services/storage/src/db/pool.ts b/packages/services/storage/src/db/pool.ts index 44cd8a0c6..88cc5d082 100644 --- a/packages/services/storage/src/db/pool.ts +++ b/packages/services/storage/src/db/pool.ts @@ -18,13 +18,15 @@ export async function getPool(connection: string, maximumPoolSize: number) { maximumPoolSize, }); - function interceptError>(methodName: K) { + function interceptError>( + methodName: K, + ) { const original: CommonQueryMethods[K] = pool[methodName]; function interceptor( this: any, sql: TaggedTemplateLiteralInvocation, - values?: QueryResultRowColumn[] + values?: QueryResultRowColumn[], ): any { return (original as any).call(this, sql, values).catch((error: any) => { error.sql = sql.sql; diff --git a/packages/services/storage/src/db/utils.ts b/packages/services/storage/src/db/utils.ts index 2f58985a3..8631fa2bb 100644 --- a/packages/services/storage/src/db/utils.ts +++ b/packages/services/storage/src/db/utils.ts @@ -14,11 +14,11 @@ export function createConnectionString(config: { export function objectToParams>( obj: T, - transformArray?: (key: K, value: T[K]) => any + transformArray?: (key: K, value: T[K]) => any, ) { const identifiers = sql.join( Object.keys(obj).map(k => sql.identifier([k])), - sql`, ` + sql`, `, ); const values = sql.join( @@ -34,7 +34,7 @@ export function objectToParams>( } return obj[key]; }), - sql`, ` + sql`, `, ); return { identifiers, values }; @@ -43,7 +43,7 @@ export function objectToParams>( export function objectToUpdateParams(obj: Record) { return sql.join( Object.keys(obj).map(key => sql`${sql.identifier([key])} = ${obj[key]}`), - sql`, ` + sql`, `, ); } diff --git a/packages/services/storage/src/index.ts b/packages/services/storage/src/index.ts index e6afae7ef..99809e84f 100644 --- a/packages/services/storage/src/index.ts +++ b/packages/services/storage/src/index.ts @@ -65,7 +65,10 @@ export type WithMaybeMetadata = T & { type Connection = DatabasePool | DatabaseTransactionConnection; -const organizationGetStartedMapping: Record, keyof organizations> = { +const organizationGetStartedMapping: Record< + Exclude, + keyof organizations +> = { creatingProject: 'get_started_creating_project', publishingSchema: 'get_started_publishing_schema', checkingSchema: 'get_started_checking_schema', @@ -103,7 +106,9 @@ export async function createStorage(connection: string, maximumPoolSize: number) }; } - function transformMember(user: users & Pick): Member { + function transformMember( + user: users & Pick, + ): Member { return { id: user.id, user: transformUser(user), @@ -135,7 +140,9 @@ export async function createStorage(connection: string, maximumPoolSize: number) }; } - function transformOrganizationInvitation(invitation: organization_invitations): OrganizationInvitation { + function transformOrganizationInvitation( + invitation: organization_invitations, + ): OrganizationInvitation { return { email: invitation.email, organization_id: invitation.organization_id, @@ -176,9 +183,19 @@ export async function createStorage(connection: string, maximumPoolSize: number) function transformSchema( schema: WithUrl< WithMaybeMetadata< - Pick + Pick< + commits, + | 'id' + | 'commit' + | 'author' + | 'content' + | 'created_at' + | 'project_id' + | 'service' + | 'target_id' + > > - > + >, ): Schema { const record: Schema = { id: schema.id, @@ -235,10 +252,13 @@ export async function createStorage(connection: string, maximumPoolSize: number) function transformTargetSettings( row: Pick< targets, - 'validation_enabled' | 'validation_percentage' | 'validation_period' | 'validation_excluded_clients' + | 'validation_enabled' + | 'validation_percentage' + | 'validation_period' + | 'validation_excluded_clients' > & { targets: target_validation['destination_target_id'][] | null; - } + }, ): TargetSettings { return { validation: { @@ -298,7 +318,10 @@ export async function createStorage(connection: string, maximumPoolSize: number) } const shared = { - async getUserBySuperTokenId({ superTokensUserId }: { superTokensUserId: string }, connection: Connection) { + async getUserBySuperTokenId( + { superTokensUserId }: { superTokensUserId: string }, + connection: Connection, + ) { const user = await connection.maybeOne>(sql` SELECT * @@ -331,7 +354,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) externalAuthUserId: string | null; oidcIntegrationId: string | null; }, - connection: Connection + connection: Connection, ) { return transformUser( await connection.one>( @@ -341,13 +364,13 @@ export async function createStorage(connection: string, maximumPoolSize: number) VALUES (${email}, ${superTokensUserId}, ${fullName}, ${displayName}, ${externalAuthUserId}, ${oidcIntegrationId}) RETURNING * - ` - ) + `, + ), ); }, async getOrganization(userId: string, connection: Connection) { const org = await connection.maybeOne>( - sql`SELECT * FROM public.organizations WHERE user_id = ${userId} AND type = ${'PERSONAL'} LIMIT 1` + sql`SELECT * FROM public.organizations WHERE user_id = ${userId} AND type = ${'PERSONAL'} LIMIT 1`, ); return org ? transformOrganization(org) : null; @@ -363,7 +386,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) }: Parameters[0] & { reservedNames: string[]; }, - connection: Connection + connection: Connection, ) { function addRandomHashToId(id: string) { return `${id}-${Math.random().toString(16).substring(2, 6)}`; @@ -375,7 +398,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) } const orgCleanIdExists = await connection.exists( - sql`SELECT 1 FROM public.organizations WHERE clean_id = ${id} LIMIT 1` + sql`SELECT 1 FROM public.organizations WHERE clean_id = ${id} LIMIT 1`, ); if (orgCleanIdExists) { @@ -393,7 +416,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) VALUES (${name}, ${availableCleanId}, ${type}, ${user}) RETURNING * - ` + `, ); await connection.query>( @@ -402,7 +425,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) ("organization_id", "user_id", "scopes") VALUES (${org.id}, ${user}, ${sql.array(scopes, 'text')}) - ` + `, ); return transformOrganization(org); @@ -413,7 +436,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) userId: string; defaultScopes: Array; }, - connection: Connection + connection: Connection, ) { const linkedOrganizationId = await connection.maybeOneFirst(sql` SELECT @@ -436,7 +459,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) (${linkedOrganizationId}, ${args.userId}, ${sql.array(args.defaultScopes, 'text')}) ON CONFLICT DO NOTHING RETURNING * - ` + `, ); }, }; @@ -494,7 +517,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) externalAuthUserId: externalAuthUserId ?? null, oidcIntegrationId: oidcIntegration?.id ?? null, }), - t + t, ); action = 'created'; } @@ -512,7 +535,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) scopes, reservedNames: reservedOrgNames, }, - t + t, ); action = 'created'; } @@ -524,7 +547,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) userId: internalUser.id, defaultScopes: oidcIntegration.defaultScopes, }, - t + t, ); } @@ -565,7 +588,9 @@ export async function createStorage(connection: string, maximumPoolSize: number) `); }, async getUserById({ id }) { - const user = await pool.maybeOne>(sql`SELECT * FROM public.users WHERE id = ${id} LIMIT 1`); + const user = await pool.maybeOne>( + sql`SELECT * FROM public.users WHERE id = ${id} LIMIT 1`, + ); if (user) { return transformUser(user); @@ -580,7 +605,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) SET display_name = ${displayName}, full_name = ${fullName} WHERE id = ${id} RETURNING * - `) + `), ); }, createOrganization(input) { @@ -593,13 +618,20 @@ export async function createStorage(connection: string, maximumPoolSize: number) DELETE FROM public.organizations WHERE id = ${organization} RETURNING * - ` - ) + `, + ), ); return result; }, - async createProject({ name, organization, cleanId, type, buildUrl = null, validationUrl = null }) { + async createProject({ + name, + organization, + cleanId, + type, + buildUrl = null, + validationUrl = null, + }) { return transformProject( await pool.one>( sql` @@ -608,26 +640,28 @@ export async function createStorage(connection: string, maximumPoolSize: number) VALUES (${name}, ${cleanId}, ${type}, ${organization}, ${buildUrl}, ${validationUrl}) RETURNING * - ` - ) + `, + ), ); }, async getOrganizationId({ organization }) { // Based on clean_id, resolve id const result = await pool.one>( - sql`SELECT id FROM public.organizations WHERE clean_id = ${organization} LIMIT 1` + sql`SELECT id FROM public.organizations WHERE clean_id = ${organization} LIMIT 1`, ); return result.id as string; }, getOrganizationOwner: batch(async selectors => { const organizations = selectors.map(s => s.organization); - const owners = await pool.query>>( + const owners = await pool.query< + Slonik> + >( sql` SELECT u.*, om.scopes, om.organization_id FROM public.organizations as o LEFT JOIN public.users as u ON (u.id = o.user_id) LEFT JOIN public.organization_member as om ON (om.user_id = u.id AND om.organization_id = o.id) - WHERE o.id IN (${sql.join(organizations, sql`, `)})` + WHERE o.id IN (${sql.join(organizations, sql`, `)})`, ); return organizations.map(organization => { @@ -642,11 +676,16 @@ export async function createStorage(connection: string, maximumPoolSize: number) }), getOrganizationMembers: batch(async selectors => { const organizations = selectors.map(s => s.organization); - const allMembers = await pool.query>>( + const allMembers = await pool.query< + Slonik> + >( sql` SELECT u.*, om.scopes, om.organization_id FROM public.organization_member as om LEFT JOIN public.users as u ON (u.id = om.user_id) - WHERE om.organization_id IN (${sql.join(organizations, sql`, `)}) ORDER BY u.created_at DESC` + WHERE om.organization_id IN (${sql.join( + organizations, + sql`, `, + )}) ORDER BY u.created_at DESC`, ); return organizations.map(organization => { @@ -660,11 +699,13 @@ export async function createStorage(connection: string, maximumPoolSize: number) }); }), async getOrganizationMember({ organization, user }) { - const member = await pool.one>>( + const member = await pool.one< + Slonik> + >( sql` SELECT u.*, om.scopes, om.organization_id FROM public.organization_member as om LEFT JOIN public.users as u ON (u.id = om.user_id) - WHERE om.organization_id = ${organization} AND om.user_id = ${user} ORDER BY u.created_at DESC LIMIT 1` + WHERE om.organization_id = ${organization} AND om.user_id = ${user} ORDER BY u.created_at DESC LIMIT 1`, ); return transformMember(member); @@ -674,33 +715,39 @@ export async function createStorage(connection: string, maximumPoolSize: number) const allInvitations = await pool.query>( sql` SELECT * FROM public.organization_invitations - WHERE organization_id IN (${sql.join(organizations, sql`, `)}) AND expires_at > NOW() ORDER BY created_at DESC - ` + WHERE organization_id IN (${sql.join( + organizations, + sql`, `, + )}) AND expires_at > NOW() ORDER BY created_at DESC + `, ); return organizations.map(organization => { return Promise.resolve( allInvitations.rows .filter(row => row.organization_id === organization) - .map(transformOrganizationInvitation) ?? [] + .map(transformOrganizationInvitation) ?? [], ); }); }), async getOrganizationMemberAccessPairs(pairs) { - const results = await pool.query>>( + const results = await pool.query< + Slonik> + >( sql` SELECT organization_id, user_id, scopes FROM public.organization_member WHERE (organization_id, user_id) IN ((${sql.join( pairs.map(p => sql`${p.organization}, ${p.user}`), - sql`), (` + sql`), (`, )})) - ` + `, ); return pairs.map(({ organization, user }) => { - return (results.rows.find(row => row.organization_id === organization && row.user_id === user)?.scopes || - []) as Member['scopes']; + return (results.rows.find( + row => row.organization_id === organization && row.user_id === user, + )?.scopes || []) as Member['scopes']; }); }, async hasOrganizationMemberPairs(pairs) { @@ -710,13 +757,13 @@ export async function createStorage(connection: string, maximumPoolSize: number) FROM public.organization_member WHERE (organization_id, user_id) IN ((${sql.join( pairs.map(p => sql`${p.organization}, ${p.user}`), - sql`), (` + sql`), (`, )})) - ` + `, ); return pairs.map(({ organization, user }) => - results.rows.some(row => row.organization_id === organization && row.user_id === user) + results.rows.some(row => row.organization_id === organization && row.user_id === user), ); }, async hasOrganizationProjectMemberPairs(pairs) { @@ -727,15 +774,18 @@ export async function createStorage(connection: string, maximumPoolSize: number) LEFT JOIN public.organization_member as om ON (p.org_id = om.organization_id) WHERE (om.organization_id, om.user_id, p.id) IN ((${sql.join( pairs.map(p => sql`${p.organization}, ${p.user}, ${p.project}`), - sql`), (` + sql`), (`, )})) - ` + `, ); return pairs.map(({ organization, user, project }) => results.rows.some( - row => row.organization_id === organization && row.project_id === project && row.user_id === user - ) + row => + row.organization_id === organization && + row.project_id === project && + row.user_id === user, + ), ); }, async updateOrganizationName({ name, cleanId, organization }) { @@ -745,7 +795,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) SET name = ${name}, clean_id = ${cleanId} WHERE id = ${organization} RETURNING * - `) + `), ); }, async updateOrganizationPlan({ billingPlan, organization }) { @@ -755,7 +805,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) SET plan_name = ${billingPlan} WHERE id = ${organization} RETURNING * - `) + `), ); }, async updateOrganizationRateLimits({ monthlyRateLimit, organization }) { @@ -765,7 +815,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) SET limit_operations_monthly = ${monthlyRateLimit.operations}, limit_retention_days = ${monthlyRateLimit.retentionInDays} WHERE id = ${organization} RETURNING * - `) + `), ); }, async createOrganizationInvitation({ organization, email }) { @@ -774,7 +824,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) INSERT INTO public.organization_invitations (organization_id, email) VALUES (${organization}, ${email}) RETURNING * - `) + `), ); }, async deleteOrganizationInvitationByEmail({ organization, email }) { @@ -799,7 +849,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) VALUES (${organization}, ${user}, ${sql.array(scopes, 'text')}) RETURNING * - ` + `, ); }); }, @@ -808,7 +858,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) sql` DELETE FROM public.organization_member WHERE organization_id = ${organization} AND user_id IN (${sql.join(users, sql`, `)}) - ` + `, ); }, async updateOrganizationMemberAccess({ user, organization, scopes }) { @@ -817,7 +867,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) UPDATE public.organization_member SET scopes = ${sql.array(scopes, 'text')} WHERE organization_id = ${organization} AND user_id = ${user} - ` + `, ); }, async getProjectId({ project, organization }) { @@ -826,7 +876,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) sql`SELECT p.id as id FROM public.projects as p LEFT JOIN public.organizations as org ON (p.org_id = org.id) - WHERE p.clean_id = ${project} AND org.clean_id = ${organization} LIMIT 1` + WHERE p.clean_id = ${project} AND org.clean_id = ${organization} LIMIT 1`, ); return result.id as string; @@ -839,7 +889,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) LEFT JOIN public.projects AS p ON (p.id = t.project_id) LEFT JOIN public.organizations AS o ON (o.id = p.org_id) WHERE t.clean_id = ${target} AND p.id = ${project} AND o.id = ${organization} - LIMIT 1` + LIMIT 1`, ); return result.id as string; @@ -852,7 +902,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) LEFT JOIN public.projects AS p ON (p.id = t.project_id) LEFT JOIN public.organizations AS o ON (o.id = p.org_id) WHERE t.clean_id = ${target} AND p.clean_id = ${project} AND o.clean_id = ${organization} - LIMIT 1` + LIMIT 1`, ); return result.id as string; @@ -863,7 +913,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) SELECT po.id FROM public.persisted_operations as po LEFT JOIN public.projects AS p ON (p.id = po.project_id) WHERE po.operation_hash = ${operation} AND p.clean_id = ${project} - LIMIT 1` + LIMIT 1`, ); return result.id; @@ -871,13 +921,13 @@ export async function createStorage(connection: string, maximumPoolSize: number) async getOrganization({ organization }) { return transformOrganization( await pool.one>( - sql`SELECT * FROM public.organizations WHERE id = ${organization} LIMIT 1` - ) + sql`SELECT * FROM public.organizations WHERE id = ${organization} LIMIT 1`, + ), ); }, async getMyOrganization({ user }) { const org = await pool.maybeOne>( - sql`SELECT * FROM public.organizations WHERE user_id = ${user} AND type = ${'PERSONAL'} LIMIT 1` + sql`SELECT * FROM public.organizations WHERE user_id = ${user} AND type = ${'PERSONAL'} LIMIT 1`, ); return org ? transformOrganization(org) : null; @@ -890,7 +940,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) LEFT JOIN public.organization_member as om ON (om.organization_id = o.id) WHERE om.user_id = ${user} ORDER BY o.created_at DESC - ` + `, ); return results.map(transformOrganization); }, @@ -902,7 +952,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) WHERE i.code = ${inviteCode} AND i.expires_at > NOW() GROUP BY o.id LIMIT 1 - ` + `, ); if (result) { @@ -913,7 +963,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) }, async getOrganizationByCleanId({ cleanId }) { const result = await pool.maybeOne>( - sql`SELECT * FROM public.organizations WHERE clean_id = ${cleanId} LIMIT 1` + sql`SELECT * FROM public.organizations WHERE clean_id = ${cleanId} LIMIT 1`, ); if (!result) { @@ -928,7 +978,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) SELECT * FROM public.organizations WHERE github_app_installation_id = ${installationId} LIMIT 1 - ` + `, ); if (result) { @@ -939,12 +989,14 @@ export async function createStorage(connection: string, maximumPoolSize: number) }, async getProject({ project }) { return transformProject( - await pool.one>(sql`SELECT * FROM public.projects WHERE id = ${project} LIMIT 1`) + await pool.one>( + sql`SELECT * FROM public.projects WHERE id = ${project} LIMIT 1`, + ), ); }, async getProjectByCleanId({ cleanId, organization }) { const result = await pool.maybeOne>( - sql`SELECT * FROM public.projects WHERE clean_id = ${cleanId} AND org_id = ${organization} LIMIT 1` + sql`SELECT * FROM public.projects WHERE clean_id = ${cleanId} AND org_id = ${organization} LIMIT 1`, ); if (!result) { @@ -955,7 +1007,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) }, async getProjects({ organization }) { const result = await pool.query>( - sql`SELECT * FROM public.projects WHERE org_id = ${organization} ORDER BY created_at DESC` + sql`SELECT * FROM public.projects WHERE org_id = ${organization} ORDER BY created_at DESC`, ); return result.rows.map(transformProject); @@ -967,7 +1019,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) SET name = ${name}, clean_id = ${cleanId} WHERE id = ${project} AND org_id = ${organization} RETURNING * - `) + `), ); }, async updateProjectGitRepository({ gitRepository, organization, project }) { @@ -977,7 +1029,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) SET git_repository = ${gitRepository ?? null} WHERE id = ${project} AND org_id = ${organization} RETURNING * - `) + `), ); }, async enableExternalSchemaComposition({ project, endpoint, encryptedSecret }) { @@ -990,7 +1042,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) external_composition_secret = ${encryptedSecret} WHERE id = ${project} RETURNING * - `) + `), ); }, async disableExternalSchemaComposition({ project }) { @@ -1003,7 +1055,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) external_composition_secret = NULL WHERE id = ${project} RETURNING * - `) + `), ); }, @@ -1014,8 +1066,8 @@ export async function createStorage(connection: string, maximumPoolSize: number) DELETE FROM public.projects WHERE id = ${project} AND org_id = ${organization} RETURNING * - ` - ) + `, + ), ); return result; @@ -1029,9 +1081,9 @@ export async function createStorage(connection: string, maximumPoolSize: number) VALUES (${name}, ${cleanId}, ${project}) RETURNING * - ` + `, ), - organization + organization, ); }, async updateTargetName({ organization, project, target, name, cleanId }) { @@ -1042,7 +1094,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) WHERE id = ${target} AND project_id = ${project} RETURNING * `), - organization + organization, ); }, async deleteTarget({ organization, project, target }) { @@ -1052,9 +1104,9 @@ export async function createStorage(connection: string, maximumPoolSize: number) DELETE FROM public.targets WHERE id = ${target} AND project_id = ${project} RETURNING * - ` + `, ), - organization + organization, ); await pool.query(sql`DELETE FROM public.versions WHERE target_id = ${target}`); @@ -1067,7 +1119,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) organization: string; project: string; target: string; - }> + }>, ) => { const uniqueSelectorsMap = new Map(); @@ -1088,27 +1140,31 @@ export async function createStorage(connection: string, maximumPoolSize: number) SELECT * FROM public.targets WHERE (id, project_id) IN ((${sql.join( uniqueSelectors.map(s => sql`${s.target}, ${s.project}`), - sql`), (` + sql`), (`, )})) - ` + `, ); return selectors.map(selector => { - const row = rows.find(row => row.id === selector.target && row.project_id === selector.project); + const row = rows.find( + row => row.id === selector.target && row.project_id === selector.project, + ); if (!row) { return Promise.reject( - new Error(`Target not found (target=${selector.target}, project=${selector.project})`) + new Error( + `Target not found (target=${selector.target}, project=${selector.project})`, + ), ); } return Promise.resolve(transformTarget(row, selector.organization)); }); - } + }, ), async getTargetByCleanId({ organization, project, cleanId }) { const result = await pool.maybeOne>( - sql`SELECT * FROM public.targets WHERE clean_id = ${cleanId} AND project_id = ${project} LIMIT 1` + sql`SELECT * FROM public.targets WHERE clean_id = ${cleanId} AND project_id = ${project} LIMIT 1`, ); if (!result) { @@ -1119,7 +1175,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) }, async getTargets({ organization, project }) { const results = await pool.query>( - sql`SELECT * FROM public.targets WHERE project_id = ${project} ORDER BY created_at DESC` + sql`SELECT * FROM public.targets WHERE project_id = ${project} ORDER BY created_at DESC`, ); return results.rows.map(r => transformTarget(r, organization)); @@ -1131,7 +1187,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) LEFT JOIN public.projects as p ON (p.id = t.project_id) WHERE p.org_id = ${organization} GROUP BY t.id - ` + `, ); return results.rows.map(r => r.id); @@ -1140,7 +1196,10 @@ export async function createStorage(connection: string, maximumPoolSize: number) const row = await pool.one< Pick< targets, - 'validation_enabled' | 'validation_percentage' | 'validation_period' | 'validation_excluded_clients' + | 'validation_enabled' + | 'validation_percentage' + | 'validation_period' + | 'validation_excluded_clients' > & { targets: target_validation['destination_target_id'][]; } @@ -1176,7 +1235,10 @@ export async function createStorage(connection: string, maximumPoolSize: number) return trx.one< Pick< targets, - 'validation_enabled' | 'validation_percentage' | 'validation_period' | 'validation_excluded_clients' + | 'validation_enabled' + | 'validation_percentage' + | 'validation_period' + | 'validation_excluded_clients' > & { targets: target_validation['destination_target_id'][]; } @@ -1197,10 +1259,17 @@ export async function createStorage(connection: string, maximumPoolSize: number) WHERE t.id = ret.id RETURNING ret.id, t.validation_enabled, t.validation_percentage, t.validation_period, t.validation_excluded_clients, ret.targets `); - }) + }), ).validation; }, - async updateTargetValidationSettings({ target, project, percentage, period, targets, excludedClients }) { + async updateTargetValidationSettings({ + target, + project, + percentage, + period, + targets, + excludedClients, + }) { return transformTargetSettings( await pool.transaction(async trx => { await trx.query(sql` @@ -1217,7 +1286,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) ( ${sql.join( targets.map(dest => sql.join([target, dest], sql`, `)), - sql`), (` + sql`), (`, )} ) ON CONFLICT (target_id, destination_target_id) DO NOTHING @@ -1227,7 +1296,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) UPDATE public.targets as t SET validation_percentage = ${percentage}, validation_period = ${period}, validation_excluded_clients = ${sql.array( excludedClients, - 'text' + 'text', )} FROM ( SELECT @@ -1242,25 +1311,27 @@ export async function createStorage(connection: string, maximumPoolSize: number) WHERE t.id = ret.id RETURNING t.id, t.validation_enabled, t.validation_percentage, t.validation_period, t.validation_excluded_clients, ret.targets; `); - }) + }), ).validation; }, async hasSchema({ target }) { return pool.exists( sql` SELECT 1 FROM public.versions as v WHERE v.target_id = ${target} LIMIT 1 - ` + `, ); }, async getMaybeLatestValidVersion({ target }) { - const version = await pool.maybeOne>>( + const version = await pool.maybeOne< + Slonik> + >( sql` SELECT v.*, c.author, c.service, c.commit FROM public.versions as v LEFT JOIN public.commits as c ON (c.id = v.commit_id) WHERE v.target_id = ${target} AND v.valid IS TRUE ORDER BY v.created_at DESC LIMIT 1 - ` + `, ); if (!version) { @@ -1276,14 +1347,16 @@ export async function createStorage(connection: string, maximumPoolSize: number) }; }, async getLatestValidVersion({ target }) { - const version = await pool.one>>( + const version = await pool.one< + Slonik> + >( sql` SELECT v.*, c.author, c.service, c.commit FROM public.versions as v LEFT JOIN public.commits as c ON (c.id = v.commit_id) WHERE v.target_id = ${target} AND v.valid IS TRUE ORDER BY v.created_at DESC LIMIT 1 - ` + `, ); return { @@ -1295,7 +1368,9 @@ export async function createStorage(connection: string, maximumPoolSize: number) }; }, async getLatestVersion({ project, target }) { - const version = await pool.one>>( + const version = await pool.one< + Slonik> + >( sql` SELECT v.*, c.author, c.service, c.commit FROM public.versions as v LEFT JOIN public.commits as c ON (c.id = v.commit_id) @@ -1303,7 +1378,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) WHERE v.target_id = ${target} AND t.project_id = ${project} ORDER BY v.created_at DESC LIMIT 1 - ` + `, ); return { @@ -1326,7 +1401,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) WHERE v.target_id = ${target} AND t.project_id = ${project} ORDER BY v.created_at DESC LIMIT 1 - ` + `, ); if (!version) { @@ -1375,7 +1450,14 @@ export async function createStorage(connection: string, maximumPoolSize: number) WithMaybeMetadata< Pick< commits, - 'id' | 'commit' | 'author' | 'content' | 'created_at' | 'project_id' | 'service' | 'target_id' + | 'id' + | 'commit' + | 'author' + | 'content' + | 'created_at' + | 'project_id' + | 'service' + | 'target_id' > > > @@ -1402,7 +1484,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) vc.version_id = ${version} ORDER BY c.created_at DESC - ` + `, ); return results.map(transformSchema); @@ -1418,7 +1500,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) ) AND v.target_id = ${target} ORDER BY v.created_at DESC LIMIT 1 ) ORDER BY c.created_at DESC - ` + `, ); return results.rows.map(transformSchema); @@ -1429,7 +1511,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) UPDATE public.version_commit SET url = ${url ?? null} WHERE version_id = ${version} AND commit_id = ${commit} - ` + `, ); }, @@ -1439,7 +1521,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) UPDATE public.commits SET service = ${name ?? null} WHERE id = ${commit} - ` + `, ); }, @@ -1470,14 +1552,16 @@ export async function createStorage(connection: string, maximumPoolSize: number) LEFT JOIN public.commits as c ON (c.id = v.commit_id) LEFT JOIN public.targets as t ON (t.id = v.target_id) WHERE v.target_id = ${target} AND t.project_id = ${project} AND v.created_at < ${ - after ? sql`(SELECT va.created_at FROM public.versions as va WHERE va.id = ${after})` : sql`NOW()` + after + ? sql`(SELECT va.created_at FROM public.versions as va WHERE va.id = ${after})` + : sql`NOW()` } ORDER BY v.created_at DESC LIMIT ${limit + 1} `; - const result = await pool.query>>( - query - ); + const result = await pool.query< + Slonik> + >(query); const hasMore = result.rows.length > limit; @@ -1494,7 +1578,16 @@ export async function createStorage(connection: string, maximumPoolSize: number) hasMore, }; }, - async insertSchema({ schema, commit, author, project, target, service = null, url = null, metadata }) { + async insertSchema({ + schema, + commit, + author, + project, + target, + service = null, + url = null, + metadata, + }) { const result = await pool.one>(sql` INSERT INTO public.commits ( @@ -1564,7 +1657,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) const vid = previousVersion.id; // fetch the rest of commits const otherCommits = await trx.many>( - sql`SELECT commit_id, url FROM public.version_commit WHERE version_id = ${vid} AND commit_id != ${input.commit}` + sql`SELECT commit_id, url FROM public.version_commit WHERE version_id = ${vid} AND commit_id != ${input.commit}`, ); commits = commits.concat(otherCommits); @@ -1578,7 +1671,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) VALUES (${newVersion.id}, ${cid}, ${commits.find(c => c.commit_id === cid)?.url || null}) `); - }) + }), ); return newVersion; @@ -1601,7 +1694,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) SET valid = ${valid} WHERE id = ${version} RETURNING * - `) + `), ); }, @@ -1612,20 +1705,24 @@ export async function createStorage(connection: string, maximumPoolSize: number) FROM public.commits as c WHERE (c.id, c.target_id) IN ((${sql.join( selectors.map(s => sql`${s.commit}, ${s.target}`), - sql`), (` + sql`), (`, )})) - ` + `, ); const schemas = rows.map(transformSchema); return selectors.map(selector => { - const schema = schemas.find(row => row.id === selector.commit && row.target === selector.target); + const schema = schemas.find( + row => row.id === selector.commit && row.target === selector.target, + ); if (schema) { return Promise.resolve(schema); } - return Promise.reject(new Error(`Schema not found (commit=${selector.commit}, target=${selector.target})`)); + return Promise.reject( + new Error(`Schema not found (commit=${selector.commit}, target=${selector.target})`), + ); }); }), async createActivity({ organization, project, target, user, type, meta }) { @@ -1639,7 +1736,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) }); await pool.query>( - sql`INSERT INTO public.activities (${identifiers}) VALUES (${values}) RETURNING *;` + sql`INSERT INTO public.activities (${identifiers}) VALUES (${values}) RETURNING *;`, ); }, async getActivities(selector) { @@ -1722,7 +1819,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) VALUES (${operationHash}, ${name}, ${kind}, ${content}, ${project}) RETURNING * - `) + `), ); }, async getPersistedOperations({ project }) { @@ -1730,7 +1827,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) sql` SELECT * FROM public.persisted_operations WHERE project_id = ${project} - ORDER BY created_at DESC` + ORDER BY created_at DESC`, ); return results.rows.map(transformPersistedOperation); @@ -1740,7 +1837,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) sql` SELECT * FROM public.persisted_operations WHERE project_id = ${project} AND operation_hash IN (${sql.join(hashes, sql`, `)}) - ORDER BY created_at DESC` + ORDER BY created_at DESC`, ); return results.rows.map(transformPersistedOperation); @@ -1750,8 +1847,8 @@ export async function createStorage(connection: string, maximumPoolSize: number) await pool.one>( sql` SELECT c.* FROM public.persisted_operations as c - WHERE c.id = ${operation} AND project_id = ${project}` - ) + WHERE c.id = ${operation} AND project_id = ${project}`, + ), ); }, async comparePersistedOperations({ project, hashes }) { @@ -1759,7 +1856,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) sql` SELECT operation_hash FROM public.persisted_operations WHERE project_id = ${project} AND operation_hash IN (${sql.join(hashes, sql`, `)}) - ORDER BY created_at DESC` + ORDER BY created_at DESC`, ); return hashes.filter(hash => !results.rows.some(row => row.operation_hash === hash)); @@ -1771,8 +1868,8 @@ export async function createStorage(connection: string, maximumPoolSize: number) DELETE FROM public.persisted_operations WHERE id = ${operation} AND project_id = ${project} RETURNING * - ` - ) + `, + ), ); return result; @@ -1783,7 +1880,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) UPDATE public.organizations SET slack_token = ${token} WHERE id = ${organization} - ` + `, ); }, async deleteSlackIntegration({ organization }) { @@ -1792,7 +1889,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) UPDATE public.organizations SET slack_token = NULL WHERE id = ${organization} - ` + `, ); }, async getSlackIntegrationToken({ organization }) { @@ -1801,7 +1898,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) SELECT slack_token FROM public.organizations WHERE id = ${organization} - ` + `, ); return result?.slack_token; @@ -1812,7 +1909,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) UPDATE public.organizations SET github_app_installation_id = ${installationId} WHERE id = ${organization} - ` + `, ); }, async deleteGitHubIntegration({ organization }) { @@ -1821,14 +1918,14 @@ export async function createStorage(connection: string, maximumPoolSize: number) UPDATE public.organizations SET github_app_installation_id = NULL WHERE id = ${organization} - ` + `, ); await pool.query>( sql` UPDATE public.projects SET git_repository = NULL WHERE org_id = ${organization} - ` + `, ); }, async getGitHubIntegrationInstallationId({ organization }) { @@ -1837,7 +1934,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) SELECT github_app_installation_id FROM public.organizations WHERE id = ${organization} - ` + `, ); return result?.github_app_installation_id; @@ -1849,10 +1946,12 @@ export async function createStorage(connection: string, maximumPoolSize: number) INSERT INTO public.alert_channels ("name", "type", "project_id", "slack_channel", "webhook_endpoint") VALUES - (${name}, ${type}, ${project}, ${slack?.channel ?? null}, ${webhook?.endpoint ?? null}) + (${name}, ${type}, ${project}, ${slack?.channel ?? null}, ${ + webhook?.endpoint ?? null + }) RETURNING * - ` - ) + `, + ), ); }, async deleteAlertChannels({ project, channels }) { @@ -1863,14 +1962,14 @@ export async function createStorage(connection: string, maximumPoolSize: number) project_id = ${project} AND id IN (${sql.join(channels, sql`, `)}) RETURNING * - ` + `, ); return result.rows.map(transformAlertChannel); }, async getAlertChannels({ project }) { const result = await pool.query>( - sql`SELECT * FROM public.alert_channels WHERE project_id = ${project} ORDER BY created_at DESC` + sql`SELECT * FROM public.alert_channels WHERE project_id = ${project} ORDER BY created_at DESC`, ); return result.rows.map(transformAlertChannel); @@ -1885,9 +1984,9 @@ export async function createStorage(connection: string, maximumPoolSize: number) VALUES (${type}, ${channel}, ${target}, ${project}) RETURNING * - ` + `, ), - organization + organization, ); }, async deleteAlerts({ organization, project, alerts }) { @@ -1898,14 +1997,14 @@ export async function createStorage(connection: string, maximumPoolSize: number) project_id = ${project} AND id IN (${sql.join(alerts, sql`, `)}) RETURNING * - ` + `, ); return result.rows.map(row => transformAlert(row, organization)); }, async getAlerts({ organization, project }) { const result = await pool.query>( - sql`SELECT * FROM public.alerts WHERE project_id = ${project} ORDER BY created_at DESC` + sql`SELECT * FROM public.alerts WHERE project_id = ${project} ORDER BY created_at DESC`, ); return result.rows.map(row => transformAlert(row, organization)); @@ -1924,7 +2023,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) FROM public.targets AS t LEFT JOIN public.projects AS p ON (p.id = t.project_id) LEFT JOIN public.organizations AS o ON (o.id = p.org_id) - ` + `, ); return results.rows; }, @@ -1953,7 +2052,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) LEFT JOIN public.projects AS p ON (p.id = t.project_id) LEFT JOIN public.organizations AS o ON (o.id = p.org_id) LEFT JOIN public.users AS u ON (u.id = o.user_id) - ` + `, ); return results.rows; }, @@ -2049,14 +2148,15 @@ export async function createStorage(connection: string, maximumPoolSize: number) SELECT * FROM organizations `); - const [versions, users, projects, targets, persistedOperations, organizations] = await Promise.all([ - versionsResult, - usersResult, - projectsResult, - targetsResult, - persistedOperationsResult, - organizationsResult, - ]); + const [versions, users, projects, targets, persistedOperations, organizations] = + await Promise.all([ + versionsResult, + usersResult, + projectsResult, + targetsResult, + persistedOperationsResult, + organizationsResult, + ]); const rows: Array<{ organization: Organization; @@ -2072,7 +2172,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) T extends { total: number; id: string; - } + }, >(nodes: readonly T[], id: string) { return nodes.find(node => node.id === id)?.total ?? 0; } @@ -2093,29 +2193,31 @@ export async function createStorage(connection: string, maximumPoolSize: number) }, async getBaseSchema({ project, target }) { const data = await pool.maybeOne>( - sql`SELECT base_schema FROM public.targets WHERE id=${target} AND project_id=${project}` + sql`SELECT base_schema FROM public.targets WHERE id=${target} AND project_id=${project}`, ); return data!.base_schema; }, async updateBaseSchema({ project, target }, base) { if (base) { await pool.query( - sql`UPDATE public.targets SET base_schema = ${base} WHERE id = ${target} AND project_id = ${project}` + sql`UPDATE public.targets SET base_schema = ${base} WHERE id = ${target} AND project_id = ${project}`, ); } else { await pool.query( - sql`UPDATE public.targets SET base_schema = null WHERE id = ${target} AND project_id = ${project}` + sql`UPDATE public.targets SET base_schema = null WHERE id = ${target} AND project_id = ${project}`, ); } }, async getBillingParticipants() { - const results = await pool.query>(sql`SELECT * FROM public.organizations_billing`); + const results = await pool.query>( + sql`SELECT * FROM public.organizations_billing`, + ); return results.rows.map(transformOrganizationBilling); }, async getOrganizationBilling(selector) { const results = await pool.query>( - sql`SELECT * FROM public.organizations_billing WHERE organization_id = ${selector.organization}` + sql`SELECT * FROM public.organizations_billing WHERE organization_id = ${selector.organization}`, ); const mapped = results.rows.map(transformOrganizationBilling); @@ -2125,10 +2227,14 @@ export async function createStorage(connection: string, maximumPoolSize: number) async deleteOrganizationBilling(selector) { await pool.query>( sql`DELETE FROM public.organizations_billing - WHERE organization_id = ${selector.organization}` + WHERE organization_id = ${selector.organization}`, ); }, - async createOrganizationBilling({ billingEmailAddress, organizationId, externalBillingReference }) { + async createOrganizationBilling({ + billingEmailAddress, + organizationId, + externalBillingReference, + }) { return transformOrganizationBilling( await pool.one>( sql` @@ -2137,8 +2243,8 @@ export async function createStorage(connection: string, maximumPoolSize: number) VALUES (${organizationId}, ${externalBillingReference}, ${billingEmailAddress || null}) RETURNING * - ` - ) + `, + ), ); }, async completeGetStartedStep({ organization, step }) { @@ -2150,7 +2256,7 @@ export async function createStorage(connection: string, maximumPoolSize: number) }, { id: organization, - } + }, ); }, diff --git a/packages/services/storage/src/tokens.ts b/packages/services/storage/src/tokens.ts index e3a2b23ca..cca14a42d 100644 --- a/packages/services/storage/src/tokens.ts +++ b/packages/services/storage/src/tokens.ts @@ -18,7 +18,7 @@ export async function createTokenStorage(connection: string, maximumPoolSize: nu target_id = ${target} AND deleted_at IS NULL ORDER BY created_at DESC - ` + `, ); return result.rows; @@ -30,7 +30,7 @@ export async function createTokenStorage(connection: string, maximumPoolSize: nu FROM public.tokens WHERE token = ${token} AND deleted_at IS NULL LIMIT 1 - ` + `, ); }, createToken({ @@ -55,16 +55,19 @@ export async function createTokenStorage(connection: string, maximumPoolSize: nu INSERT INTO public.tokens (name, token, token_alias, target_id, project_id, organization_id, scopes) VALUES - (${name}, ${token}, ${tokenAlias}, ${target}, ${project}, ${organization}, ${sql.array(scopes, 'text')}) + (${name}, ${token}, ${tokenAlias}, ${target}, ${project}, ${organization}, ${sql.array( + scopes, + 'text', + )}) RETURNING * - ` + `, ); }, async deleteToken({ token }: { token: string }) { await pool.query( sql` UPDATE public.tokens SET deleted_at = NOW() WHERE token = ${token} - ` + `, ); }, async touchTokens({ tokens }: { tokens: Array<{ token: string; date: Date }> }) { @@ -75,7 +78,7 @@ export async function createTokenStorage(connection: string, maximumPoolSize: nu VALUES (${sql.join( tokens.map(t => sql`${t.token}, ${toDate(t.date)}`), - sql`), (` + sql`), (`, )}) ) as c(token, last_used_at) WHERE c.token = t.token; diff --git a/packages/services/storage/tools/create-db.mjs b/packages/services/storage/tools/create-db.mjs index 2710b6c54..041eaff3f 100644 --- a/packages/services/storage/tools/create-db.mjs +++ b/packages/services/storage/tools/create-db.mjs @@ -7,24 +7,30 @@ const db = pgp(cn('postgres')); const dbName = 'registry'; +// eslint-disable-next-line no-undef +const log = console.log; + probe().then(() => db .query(`SELECT 1 FROM pg_database WHERE datname = '${dbName}'`) .then(result => { if (!result.length) { - console.log(`Creating "${dbName}" database`); + log(`Creating "${dbName}" database`); return db.query(`CREATE DATABASE ${dbName}`); } - console.log(`Database "${dbName}" already exists`); + log(`Database "${dbName}" already exists`); }) .then(() => { + // eslint-disable-next-line no-undef process.exit(0); }) .catch(error => { + // eslint-disable-next-line no-undef console.error(error); + // eslint-disable-next-line no-undef process.exit(1); - }) + }), ); /** @@ -37,7 +43,8 @@ function probe(numberOfRetries = 0) { if (numberOfRetries === 15) { throw new Error('Database not ready after 15 retries. Exiting.'); } - console.log('Database not ready. Retry in 1000ms\nReason:\n' + err); + log('Database not ready. Retry in 1000ms\nReason:\n' + err); + // eslint-disable-next-line no-undef await new Promise(res => setTimeout(res, 1000)); return probe(numberOfRetries + 1); }); diff --git a/packages/services/stripe-billing/package.json b/packages/services/stripe-billing/package.json index ea8d65e26..fa844234e 100644 --- a/packages/services/stripe-billing/package.json +++ b/packages/services/stripe-billing/package.json @@ -1,27 +1,27 @@ { - "private": true, - "type": "module", "name": "@hive/stripe-billing", - "description": "A microservice for Hive SaaS, that syncs usage information to Stripe (metered billing)", "version": "0.0.0", + "type": "module", + "description": "A microservice for Hive SaaS, that syncs usage information to Stripe (metered billing)", "license": "MIT", + "private": true, "scripts": { - "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "build": "bob runify --single", + "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "typecheck": "tsc --noEmit" }, "dependencies": { - "@whatwg-node/fetch": "0.4.7", - "@trpc/client": "9.23.2", - "@trpc/server": "9.23.2", - "zod": "3.15.1", - "stripe": "8.220.0", - "reflect-metadata": "0.1.13", "@sentry/node": "7.20.1", "@sentry/tracing": "7.20.1", - "dotenv": "10.0.0", + "@trpc/client": "9.23.2", + "@trpc/server": "9.23.2", + "@whatwg-node/fetch": "0.4.7", "date-fns": "2.28.0", - "got": "12.5.3" + "dotenv": "10.0.0", + "got": "12.5.3", + "reflect-metadata": "0.1.13", + "stripe": "8.220.0", + "zod": "3.15.1" }, "devDependencies": { "@hive/service-common": "workspace:*", diff --git a/packages/services/stripe-billing/src/api.ts b/packages/services/stripe-billing/src/api.ts index d2022beef..34f36a27a 100644 --- a/packages/services/stripe-billing/src/api.ts +++ b/packages/services/stripe-billing/src/api.ts @@ -83,7 +83,9 @@ export const stripeBillingApiRouter = trpc throw new Error(`Organization does not have a subscription record!`); } - const customer = await ctx.stripe.customers.retrieve(organizationBillingRecord.externalBillingReference); + const customer = await ctx.stripe.customers.retrieve( + organizationBillingRecord.externalBillingReference, + ); if (customer.deleted === true) { await storage.deleteOrganizationBilling({ @@ -158,10 +160,15 @@ export const stripeBillingApiRouter = trpc } if (Object.keys(updateParams).length > 0) { - await ctx.stripe.customers.update(organizationBillingRecord.externalBillingReference, updateParams); + await ctx.stripe.customers.update( + organizationBillingRecord.externalBillingReference, + updateParams, + ); } } else { - throw new Error(`Failed to sync subscription for organization: failed to find find active record`); + throw new Error( + `Failed to sync subscription for organization: failed to find find active record`, + ); } }, }) @@ -176,7 +183,9 @@ export const stripeBillingApiRouter = trpc }); if (organizationBillingRecord === null) { - throw new Error(`Failed to cancel subscription for organization: no existing participant record`); + throw new Error( + `Failed to cancel subscription for organization: no existing participant record`, + ); } const subscriptions = await ctx.stripe.subscriptions @@ -186,7 +195,9 @@ export const stripeBillingApiRouter = trpc .then(v => v.data.filter(r => r.metadata?.hive_subscription)); if (subscriptions.length === 0) { - throw new Error(`Failed to cancel subscription for organization: failed to find linked Stripe subscriptions`); + throw new Error( + `Failed to cancel subscription for organization: failed to find linked Stripe subscriptions`, + ); } const actualSubscription = subscriptions[0]; @@ -252,7 +263,9 @@ export const stripeBillingApiRouter = trpc let paymentMethodId: string | null = null; if (input.paymentMethodId) { - const paymentMethodConfiguredAlready = existingPaymentMethods.find(v => v.id === input.paymentMethodId); + const paymentMethodConfiguredAlready = existingPaymentMethods.find( + v => v.id === input.paymentMethodId, + ); if (paymentMethodConfiguredAlready) { paymentMethodId = paymentMethodConfiguredAlready.id; @@ -268,7 +281,9 @@ export const stripeBillingApiRouter = trpc } if (!paymentMethodId) { - throw new Error(`Payment method is not specified, and customer does not have it configured.`); + throw new Error( + `Payment method is not specified, and customer does not have it configured.`, + ); } const stripePrices = await ctx.stripeData$; @@ -305,17 +320,14 @@ export const stripeBillingApiRouter = trpc export type StripeBillingApi = typeof stripeBillingApiRouter; export type StripeBillingApiQuery = keyof StripeBillingApi['_def']['queries']; -export type StripeBillingQueryOutput = inferProcedureOutput< - StripeBillingApi['_def']['queries'][TRouteKey] ->; +export type StripeBillingQueryOutput = + inferProcedureOutput; export type StripeBillingQueryInput = inferProcedureInput< StripeBillingApi['_def']['queries'][TRouteKey] >; export type StripeBillingApiMutation = keyof StripeBillingApi['_def']['mutations']; -export type StripeBillingMutationOutput = inferProcedureOutput< - StripeBillingApi['_def']['mutations'][TRouteKey] ->; -export type StripeBillingMutationInput = inferProcedureInput< - StripeBillingApi['_def']['mutations'][TRouteKey] ->; +export type StripeBillingMutationOutput = + inferProcedureOutput; +export type StripeBillingMutationInput = + inferProcedureInput; diff --git a/packages/services/stripe-billing/src/billing-sync.ts b/packages/services/stripe-billing/src/billing-sync.ts index 30f729aa0..05976a161 100644 --- a/packages/services/stripe-billing/src/billing-sync.ts +++ b/packages/services/stripe-billing/src/billing-sync.ts @@ -41,7 +41,9 @@ export function createStripeBilling(config: { .then(r => r.data.filter(v => v.metadata?.hive_plan && v.active === true)); if (relevantProducts.length !== 1) { - throw new Error(`Invalid count of Hive products configured in Stripe: ${relevantProducts.length}`); + throw new Error( + `Invalid count of Hive products configured in Stripe: ${relevantProducts.length}`, + ); } const prices = (await stripeApi.prices.list({ @@ -169,7 +171,9 @@ export function createStripeBilling(config: { return true; }, async start() { - logger.info(`Stripe Billing Sync starting, will sync Stripe every ${config.stripe.syncIntervalMs}ms...`); + logger.info( + `Stripe Billing Sync starting, will sync Stripe every ${config.stripe.syncIntervalMs}ms...`, + ); const stripeData = await loadStripeData$; logger.info(`Stripe is configured correctly, prices info: %o`, stripeData); diff --git a/packages/services/stripe-billing/src/environment.ts b/packages/services/stripe-billing/src/environment.ts index 922fa153e..9418cf9b0 100644 --- a/packages/services/stripe-billing/src/environment.ts +++ b/packages/services/stripe-billing/src/environment.ts @@ -66,7 +66,7 @@ const LogModel = zod.object({ zod.literal('fatal'), zod.literal('silent'), ]) - .optional() + .optional(), ), }); diff --git a/packages/services/stripe-billing/src/index.ts b/packages/services/stripe-billing/src/index.ts index 91ec1eca1..44730d640 100644 --- a/packages/services/stripe-billing/src/index.ts +++ b/packages/services/stripe-billing/src/index.ts @@ -1,7 +1,12 @@ #!/usr/bin/env node import 'reflect-metadata'; import * as Sentry from '@sentry/node'; -import { createServer, startMetrics, registerShutdown, reportReadiness } from '@hive/service-common'; +import { + createServer, + startMetrics, + registerShutdown, + reportReadiness, +} from '@hive/service-common'; import { createConnectionString } from '@hive/storage'; import { createStripeBilling } from './billing-sync'; import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify/dist/trpc-server-adapters-fastify.cjs.js'; diff --git a/packages/services/tokens/README.md b/packages/services/tokens/README.md index de170c489..9b9829a5c 100644 --- a/packages/services/tokens/README.md +++ b/packages/services/tokens/README.md @@ -1,6 +1,7 @@ # `@hive/tokens` -This service takes care of validating and issuing tokens used for accessing the public facing hive APIs (usage service and GraphQL API). +This service takes care of validating and issuing tokens used for accessing the public facing hive +APIs (usage service and GraphQL API). ## Configuration diff --git a/packages/services/tokens/package.json b/packages/services/tokens/package.json index 2b2051ceb..24fb788cb 100644 --- a/packages/services/tokens/package.json +++ b/packages/services/tokens/package.json @@ -1,31 +1,31 @@ { "name": "@hive/tokens", - "type": "module", - "private": true, "version": "0.0.0", + "type": "module", "license": "MIT", + "private": true, "scripts": { - "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "build": "bob runify --single", + "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "typecheck": "tsc --noEmit" }, "dependencies": { - "zod": "3.15.1", - "@trpc/server": "9.23.2", "@sentry/node": "7.20.1", "@sentry/tracing": "7.20.1", + "@trpc/server": "9.23.2", "dotenv": "10.0.0", "ms": "2.1.3", "p-timeout": "5.0.2", + "reflect-metadata": "0.1.13", "tiny-lru": "8.0.2", - "reflect-metadata": "0.1.13" + "zod": "3.15.1" }, "devDependencies": { "@hive/service-common": "workspace:*", "@hive/storage": "workspace:*", "@types/ms": "0.7.31", - "pino-pretty": "6.0.0", "fastify": "3.29.4", + "pino-pretty": "6.0.0", "tslib": "2.4.1" }, "buildOptions": { diff --git a/packages/services/tokens/src/cache.ts b/packages/services/tokens/src/cache.ts index 4fbf047c1..541f79f59 100644 --- a/packages/services/tokens/src/cache.ts +++ b/packages/services/tokens/src/cache.ts @@ -39,7 +39,7 @@ interface CacheStorage extends Omit { // Without the cache we would hit the DB for every request, with the cache we hit it only once (until a token is invalidated). export function useCache( storagePromise: Promise, - logger: FastifyLoggerInstance + logger: FastifyLoggerInstance, ): { start(): Promise; stop(): Promise; @@ -141,10 +141,14 @@ export function useCache( invalidate(target); }, invalidateProject(project) { - relations.getTargetsOfProject(project).forEach(target => cachedStorage.invalidateTarget(target)); + relations + .getTargetsOfProject(project) + .forEach(target => cachedStorage.invalidateTarget(target)); }, invalidateOrganization(organization) { - relations.getTargetsOfOrganization(organization).forEach(target => cachedStorage.invalidateTarget(target)); + relations + .getTargetsOfOrganization(organization) + .forEach(target => cachedStorage.invalidateTarget(target)); }, async readToken(hashed_token, res) { const targetIds = cache.keys(); @@ -288,7 +292,7 @@ function useRelations() { function useTokenTouchScheduler( storage: Storage, logger: FastifyLoggerInstance, - onTouch: (token: string, date: Date) => void + onTouch: (token: string, date: Date) => void, ) { const scheduledTokens = new Map(); diff --git a/packages/services/tokens/src/environment.ts b/packages/services/tokens/src/environment.ts index 2de4f8abb..5645100f3 100644 --- a/packages/services/tokens/src/environment.ts +++ b/packages/services/tokens/src/environment.ts @@ -60,7 +60,7 @@ const LogModel = zod.object({ zod.literal('fatal'), zod.literal('silent'), ]) - .optional() + .optional(), ), }); diff --git a/packages/services/tokens/src/helpers.ts b/packages/services/tokens/src/helpers.ts index 17abf27b9..098cec462 100644 --- a/packages/services/tokens/src/helpers.ts +++ b/packages/services/tokens/src/helpers.ts @@ -53,6 +53,6 @@ export function until(fn: () => boolean, timeout: number): Promise { } }, 200); }), - timeout + timeout, ); } diff --git a/packages/services/tokens/src/index.ts b/packages/services/tokens/src/index.ts index a71625586..6c88e5412 100644 --- a/packages/services/tokens/src/index.ts +++ b/packages/services/tokens/src/index.ts @@ -40,7 +40,10 @@ export async function main() { const errorHandler = createErrorHandler(server); try { - const { start, stop, readiness, getStorage } = useCache(createStorage(env.postgres), server.log); + const { start, stop, readiness, getStorage } = useCache( + createStorage(env.postgres), + server.log, + ); const tokenReadFailuresCache = LRU< | { type: 'error'; diff --git a/packages/services/tokens/src/storage.ts b/packages/services/tokens/src/storage.ts index c45d89fe6..a03c514ea 100644 --- a/packages/services/tokens/src/storage.ts +++ b/packages/services/tokens/src/storage.ts @@ -22,7 +22,9 @@ export interface Storage { touchTokens(tokens: Array<{ token: string; date: Date }>): Promise; } -export async function createStorage(config: Parameters[0]): Promise { +export async function createStorage( + config: Parameters[0], +): Promise { const connectionString = createConnectionString(config); const db = await createTokenStorage(connectionString, 5); diff --git a/packages/services/usage-common/package.json b/packages/services/usage-common/package.json index d8ad4d769..785edb0df 100644 --- a/packages/services/usage-common/package.json +++ b/packages/services/usage-common/package.json @@ -1,8 +1,8 @@ { "name": "@hive/usage-common", - "private": true, "version": "0.0.0", "license": "MIT", + "private": true, "dependencies": { "graphql": "16.5.0" } diff --git a/packages/services/usage-estimator/package.json b/packages/services/usage-estimator/package.json index da4c82e4b..72e11ce8f 100644 --- a/packages/services/usage-estimator/package.json +++ b/packages/services/usage-estimator/package.json @@ -1,24 +1,24 @@ { - "private": true, - "type": "module", "name": "@hive/usage-estimator", - "description": "A microservice for Hive SaaS, that calculates and exposes usage information.", "version": "0.0.0", + "type": "module", + "description": "A microservice for Hive SaaS, that calculates and exposes usage information.", "license": "MIT", + "private": true, "scripts": { - "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "build": "bob runify --single", + "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "typecheck": "tsc --noEmit" }, "dependencies": { - "@whatwg-node/fetch": "0.4.7", - "zod": "3.15.1", - "@trpc/server": "9.23.2", - "reflect-metadata": "0.1.13", "@sentry/node": "7.20.1", "@sentry/tracing": "7.20.1", + "@trpc/server": "9.23.2", + "@whatwg-node/fetch": "0.4.7", "dotenv": "10.0.0", - "got": "12.5.3" + "got": "12.5.3", + "reflect-metadata": "0.1.13", + "zod": "3.15.1" }, "devDependencies": { "@hive/api": "workspace:*", diff --git a/packages/services/usage-estimator/src/api.ts b/packages/services/usage-estimator/src/api.ts index 6d50f74f3..d5f002753 100644 --- a/packages/services/usage-estimator/src/api.ts +++ b/packages/services/usage-estimator/src/api.ts @@ -41,15 +41,15 @@ export const usageEstimatorApiRouter = trpc endTime: new Date(input.endTime), }); - return Object.fromEntries(estimationResponse.data.map(item => [item.target, parseInt(item.total)])); + return Object.fromEntries( + estimationResponse.data.map(item => [item.target, parseInt(item.total)]), + ); }, }); export type UsageEstimatorApi = typeof usageEstimatorApiRouter; export type UsageEstimatorApiQuery = keyof UsageEstimatorApi['_def']['queries']; -export type UsageEstimatorQueryOutput = inferProcedureOutput< - UsageEstimatorApi['_def']['queries'][TRouteKey] ->; -export type UsageEstimatorQueryInput = inferProcedureInput< - UsageEstimatorApi['_def']['queries'][TRouteKey] ->; +export type UsageEstimatorQueryOutput = + inferProcedureOutput; +export type UsageEstimatorQueryInput = + inferProcedureInput; diff --git a/packages/services/usage-estimator/src/environment.ts b/packages/services/usage-estimator/src/environment.ts index df8158524..db25e92f3 100644 --- a/packages/services/usage-estimator/src/environment.ts +++ b/packages/services/usage-estimator/src/environment.ts @@ -50,7 +50,7 @@ const LogModel = zod.object({ zod.literal('fatal'), zod.literal('silent'), ]) - .optional() + .optional(), ), }); diff --git a/packages/services/usage-estimator/src/estimator.ts b/packages/services/usage-estimator/src/estimator.ts index b9239a43f..0540a0bb6 100644 --- a/packages/services/usage-estimator/src/estimator.ts +++ b/packages/services/usage-estimator/src/estimator.ts @@ -16,7 +16,7 @@ export function createEstimator(config: { timings: { totalSeconds: number; elapsedSeconds: number; - } + }, ) => void; }; }) { @@ -59,7 +59,11 @@ export function createEstimator(config: { timeout: 60_000, }); }, - async estimateCollectedOperationsForTargets(input: { targets: string[]; startTime: Date; endTime: Date }) { + async estimateCollectedOperationsForTargets(input: { + targets: string[]; + startTime: Date; + endTime: Date; + }) { const filter = operationsReader.createFilter({ target: input.targets, period: { diff --git a/packages/services/usage-estimator/src/index.ts b/packages/services/usage-estimator/src/index.ts index afa4f0fb2..7fd296615 100644 --- a/packages/services/usage-estimator/src/index.ts +++ b/packages/services/usage-estimator/src/index.ts @@ -1,7 +1,12 @@ #!/usr/bin/env node import 'reflect-metadata'; import * as Sentry from '@sentry/node'; -import { createServer, startMetrics, registerShutdown, reportReadiness } from '@hive/service-common'; +import { + createServer, + startMetrics, + registerShutdown, + reportReadiness, +} from '@hive/service-common'; import { createEstimator } from './estimator'; import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify/dist/trpc-server-adapters-fastify.cjs.js'; import { usageEstimatorApiRouter } from './api'; diff --git a/packages/services/usage-ingestor/__tests__/serializer.spec.ts b/packages/services/usage-ingestor/__tests__/serializer.spec.ts index 265545c2a..a1beabe83 100644 --- a/packages/services/usage-ingestor/__tests__/serializer.spec.ts +++ b/packages/services/usage-ingestor/__tests__/serializer.spec.ts @@ -55,7 +55,7 @@ test('stringify operation in correct format and order', () => { operationType: 'query' as any, // missing metadata, on purpose }, - ].map(stringifyOperation) + ].map(stringifyOperation), ); expect(serialized).toBe( [ @@ -81,7 +81,7 @@ test('stringify operation in correct format and order', () => { /* client_name */ `\\N`, /* client_version */ `\\N`, ].join(','), - ].join('\n') + ].join('\n'), ); }); @@ -110,7 +110,7 @@ test('stringify registry records in correct format and order', () => { operation_kind: 'query', coordinates: ['Query', 'Query.foo'], }, - ].map(stringifyRegistryRecord) + ].map(stringifyRegistryRecord), ); expect(serialized).toBe( [ @@ -136,7 +136,7 @@ test('stringify registry records in correct format and order', () => { /* timestamp */ timestamp.asString, /* expires_at */ expiresAt.asString, ].join(','), - ].join('\n') + ].join('\n'), ); }); @@ -179,7 +179,7 @@ describe('legacy', () => { operationType: 'query' as any, // missing metadata, on purpose }, - ].map(op => stringifyLegacyOperation(op, op.fields)) + ].map(op => stringifyLegacyOperation(op, op.fields)), ); expect(serialized).toBe( [ @@ -207,7 +207,7 @@ describe('legacy', () => { /* client_name */ `\\N`, /* client_version */ `\\N`, ].join(','), - ].join('\n') + ].join('\n'), ); }); @@ -230,7 +230,7 @@ describe('legacy', () => { body: `{ foo }`, operation_kind: 'query', }, - ].map(stringifyLegacyRegistryRecord) + ].map(stringifyLegacyRegistryRecord), ); expect(serialized).toBe( [ @@ -250,7 +250,7 @@ describe('legacy', () => { /* operation */ `"query"`, /* inserted_at */ timestamp.asString, ].join(','), - ].join('\n') + ].join('\n'), ); }); }); diff --git a/packages/services/usage-ingestor/package.json b/packages/services/usage-ingestor/package.json index 4370346a2..82f6e4439 100644 --- a/packages/services/usage-ingestor/package.json +++ b/packages/services/usage-ingestor/package.json @@ -1,12 +1,12 @@ { - "private": true, - "type": "module", "name": "@hive/usage-ingestor", "version": "0.0.0", + "type": "module", "license": "MIT", + "private": true, "scripts": { - "dev": "tsup-node src/dev.ts --watch --format esm --target node16 --onSuccess 'node dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "build": "bob runify --single", + "dev": "tsup-node src/dev.ts --watch --format esm --target node16 --onSuccess 'node dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -15,8 +15,8 @@ "agentkeepalive": "4.2.1", "date-fns": "2.25.0", "dotenv": "10.0.0", - "graphql": "16.5.0", "got": "12.5.3", + "graphql": "16.5.0", "kafkajs": "2.2.2", "tiny-lru": "8.0.2", "zod": "3.15.1" diff --git a/packages/services/usage-ingestor/src/environment.ts b/packages/services/usage-ingestor/src/environment.ts index af5839f6d..b7b4949d7 100644 --- a/packages/services/usage-ingestor/src/environment.ts +++ b/packages/services/usage-ingestor/src/environment.ts @@ -47,7 +47,11 @@ const KafkaModel = zod.union([ KAFKA_SASL_MECHANISM: zod.void(), }), KafkaBaseModel.extend({ - KAFKA_SASL_MECHANISM: zod.union([zod.literal('plain'), zod.literal('scram-sha-256'), zod.literal('scram-sha-512')]), + KAFKA_SASL_MECHANISM: zod.union([ + zod.literal('plain'), + zod.literal('scram-sha-256'), + zod.literal('scram-sha-512'), + ]), KAFKA_SASL_USERNAME: zod.string(), KAFKA_SASL_PASSWORD: zod.string(), }), @@ -89,7 +93,7 @@ const LogModel = zod.object({ zod.literal('fatal'), zod.literal('silent'), ]) - .optional() + .optional(), ), }); diff --git a/packages/services/usage-ingestor/src/helpers.ts b/packages/services/usage-ingestor/src/helpers.ts index 61c317cd6..f1a672ee4 100644 --- a/packages/services/usage-ingestor/src/helpers.ts +++ b/packages/services/usage-ingestor/src/helpers.ts @@ -5,7 +5,7 @@ export function cache( has(key: K): boolean; set(key: K, value: R): void; get(key: K): R | undefined; - } + }, ) { return (arg: A) => { const key = cacheKeyFn(arg); diff --git a/packages/services/usage-ingestor/src/index.ts b/packages/services/usage-ingestor/src/index.ts index 5c6b45f74..92bcc822a 100644 --- a/packages/services/usage-ingestor/src/index.ts +++ b/packages/services/usage-ingestor/src/index.ts @@ -1,6 +1,12 @@ #!/usr/bin/env node import * as Sentry from '@sentry/node'; -import { createServer, startMetrics, registerShutdown, reportReadiness, startHeartbeats } from '@hive/service-common'; +import { + createServer, + startMetrics, + registerShutdown, + reportReadiness, + startHeartbeats, +} from '@hive/service-common'; import { createIngestor } from './ingestor'; import { env } from './environment'; diff --git a/packages/services/usage-ingestor/src/processor.ts b/packages/services/usage-ingestor/src/processor.ts index 8258d792b..bced24d6e 100644 --- a/packages/services/usage-ingestor/src/processor.ts +++ b/packages/services/usage-ingestor/src/processor.ts @@ -3,7 +3,13 @@ import { Kind, parse } from 'graphql'; import LRU from 'tiny-lru'; import { createHash } from 'crypto'; import { cache } from './helpers'; -import { reportSize, totalOperations, reportMessageSize, normalizeCacheMisses, schemaCoordinatesSize } from './metrics'; +import { + reportSize, + totalOperations, + reportMessageSize, + normalizeCacheMisses, + schemaCoordinatesSize, +} from './metrics'; import { stringifyOperation, stringifyRegistryRecord, @@ -19,7 +25,12 @@ import type { RawOperationMapRecord, ProcessedOperation, } from '@hive/usage-common'; -import type { DefinitionNode, DocumentNode, OperationDefinitionNode, OperationTypeNode } from 'graphql'; +import type { + DefinitionNode, + DocumentNode, + OperationDefinitionNode, + OperationTypeNode, +} from 'graphql'; interface NormalizationResult { type: OperationTypeNode; @@ -39,7 +50,7 @@ export function createProcessor(config: { logger: FastifyLoggerInstance }) { const normalize = cache( normalizeOperation, op => op.key, - LRU(10_000, 1_800_000 /* 30 minutes */) + LRU(10_000, 1_800_000 /* 30 minutes */), ); return { @@ -70,7 +81,12 @@ export function createProcessor(config: { logger: FastifyLoggerInstance }) { >(); for (const rawOperation of rawReport.operations) { - const processedOperation = processSingleOperation(rawOperation, rawReport.map, rawReport.target, normalize); + const processedOperation = processSingleOperation( + rawOperation, + rawReport.map, + rawReport.target, + normalize, + ); if (processedOperation === null) { // The operation should be ignored @@ -81,7 +97,7 @@ export function createProcessor(config: { logger: FastifyLoggerInstance }) { // legacy serializedLegacyOperations.push( - stringifyLegacyOperation(processedOperation, processedOperation.legacy.coordinates) + stringifyLegacyOperation(processedOperation, processedOperation.legacy.coordinates), ); const sample = operationSample.get(rawOperation.operationMapKey); @@ -101,7 +117,7 @@ export function createProcessor(config: { logger: FastifyLoggerInstance }) { body: processedOperation.legacy.body, operation_kind: processedOperation.legacy.kind, timestamp: processedOperation.timestamp, - }) + }), ); } else { sample.size += 1; @@ -140,7 +156,7 @@ export function createProcessor(config: { logger: FastifyLoggerInstance }) { coordinates: normalized.coordinates, expires_at: group.operation.expiresAt || timestamp + 30 * DAY_IN_MS, timestamp: timestamp, - }) + }), ); } } @@ -161,7 +177,7 @@ function processSingleOperation( operation: RawOperation, operationMap: RawOperationMap, target: string, - normalize: NormalizeFunction + normalize: NormalizeFunction, ): | (ProcessedOperation & { legacy: { @@ -186,7 +202,10 @@ function processSingleOperation( schemaCoordinatesSize.observe(normalized.coordinates.length); - const timestamp = typeof operation.timestamp === 'string' ? parseInt(operation.timestamp, 10) : operation.timestamp; + const timestamp = + typeof operation.timestamp === 'string' + ? parseInt(operation.timestamp, 10) + : operation.timestamp; return { timestamp: timestamp, diff --git a/packages/services/usage-ingestor/src/serializer.ts b/packages/services/usage-ingestor/src/serializer.ts index 2da57b18f..cd8af3202 100644 --- a/packages/services/usage-ingestor/src/serializer.ts +++ b/packages/services/usage-ingestor/src/serializer.ts @@ -64,7 +64,14 @@ export const registryOrder = [ 'expires_at', ] as const; -export const legacyRegistryOrder = ['target', 'hash', 'name', 'body', 'operation', 'inserted_at'] as const; +export const legacyRegistryOrder = [ + 'target', + 'hash', + 'name', + 'body', + 'operation', + 'inserted_at', +] as const; export function joinIntoSingleMessage(items: string[]): string { return items.join(delimiter); @@ -88,7 +95,10 @@ export function stringifyOperation(operation: ProcessedOperation): string { return Object.values(mapper).join(','); } -export function stringifyLegacyOperation(operation: ProcessedOperation, coordinates: string[]): string { +export function stringifyLegacyOperation( + operation: ProcessedOperation, + coordinates: string[], +): string { const mapper: Record, any> = { target: castValue(operation.target), timestamp: castDate(operation.timestamp), @@ -121,7 +131,10 @@ export function stringifyRegistryRecord(record: ProcessedRegistryRecord): string } export function stringifyLegacyRegistryRecord( - record: Pick + record: Pick< + ProcessedRegistryRecord, + 'body' | 'hash' | 'timestamp' | 'name' | 'operation_kind' | 'target' + >, ): string { const mapper: Record, any> = { target: castValue(record.target), diff --git a/packages/services/usage-ingestor/src/writer.ts b/packages/services/usage-ingestor/src/writer.ts index 311968343..7d24b7df2 100644 --- a/packages/services/usage-ingestor/src/writer.ts +++ b/packages/services/usage-ingestor/src/writer.ts @@ -117,7 +117,7 @@ export function createWriter({ `INSERT INTO operations_new (${legacyOperationsFields}) FORMAT CSV`, await compress(csv), logger, - 3 + 3, ); }, async writeRegistry(records: string[]) { @@ -132,7 +132,7 @@ export function createWriter({ `INSERT INTO operations_registry (${legacyRegistryFields}) FORMAT CSV`, await compress(csv), logger, - 3 + 3, ); }, }, @@ -152,7 +152,7 @@ async function writeCsv( query: string, body: Buffer, logger: FastifyLoggerInstance, - maxRetry: number + maxRetry: number, ) { const stopTimer = writeDuration.startTimer({ query, @@ -178,7 +178,12 @@ async function writeCsv( retry: { calculateDelay(info) { if (info.attemptCount >= maxRetry) { - logger.warn('Exceeded the retry limit (%s/%s) for %s', info.attemptCount, maxRetry, query); + logger.warn( + 'Exceeded the retry limit (%s/%s) for %s', + info.attemptCount, + maxRetry, + query, + ); // After N retries, stop. return 0; } @@ -207,7 +212,8 @@ async function writeCsv( }) .catch(error => { stopTimer({ - status: hasResponse(error) && error.response.statusCode ? error.response.statusCode : 'unknown', + status: + hasResponse(error) && error.response.statusCode ? error.response.statusCode : 'unknown', }); Sentry.captureException(error, { level: 'error', diff --git a/packages/services/usage/README.md b/packages/services/usage/README.md index 574978281..cc5274199 100644 --- a/packages/services/usage/README.md +++ b/packages/services/usage/README.md @@ -2,7 +2,8 @@ This service takes care of handling HTTP requests for usage reporting. -The data is written to a Kafka broker, form Kafka the data is feed into clickhouse via the `usage-ingestor` service. +The data is written to a Kafka broker, form Kafka the data is feed into clickhouse via the +`usage-ingestor` service. ## Configuration diff --git a/packages/services/usage/__tests__/buffer.spec.ts b/packages/services/usage/__tests__/buffer.spec.ts index 79f494c75..05c578ab9 100644 --- a/packages/services/usage/__tests__/buffer.spec.ts +++ b/packages/services/usage/__tests__/buffer.spec.ts @@ -75,7 +75,9 @@ test('increase the defaultBytesPerOperation estimation by 5% when over 100 calls // Interval passes await waitFor(interval + 50); - expect(logger.info).not.toHaveBeenCalledWith(expect.stringContaining('Increasing default bytes per unit')); + expect(logger.info).not.toHaveBeenCalledWith( + expect.stringContaining('Increasing default bytes per unit'), + ); expect(flush).toBeCalledTimes(100); @@ -92,7 +94,9 @@ test('increase the defaultBytesPerOperation estimation by 5% when over 100 calls expect(flush).toBeCalledTimes(112); - expect(logger.info).not.toHaveBeenCalledWith(expect.stringContaining('Increasing default bytes per unit')); + expect(logger.info).not.toHaveBeenCalledWith( + expect.stringContaining('Increasing default bytes per unit'), + ); await waitFor(1000); @@ -109,7 +113,7 @@ test('increase the defaultBytesPerOperation estimation by 5% when over 100 calls expect(logger.info).toHaveBeenCalledWith( expect.stringContaining('Increasing default bytes per unit (ratio=%s, new=%s)'), 0.05, - newDefault + newDefault, ); const flushedTimes = 114; expect(flush).toHaveBeenCalledTimes(flushedTimes); @@ -288,8 +292,18 @@ test('buffer create two chunks out of one buffer when actual buffer size is too // Flush should be retried because the buffer size was too big (twice as big) expect(onRetry).toBeCalledTimes(1); // Buffer should flush two reports, the big report splitted in half - expect(flush).toHaveBeenNthCalledWith(1, 'big-0', bufferSize / 2, expect.stringContaining('--retry-chunk-0')); - expect(flush).toHaveBeenNthCalledWith(2, 'big-1', bufferSize / 2, expect.stringContaining('--retry-chunk-1')); + expect(flush).toHaveBeenNthCalledWith( + 1, + 'big-0', + bufferSize / 2, + expect.stringContaining('--retry-chunk-0'), + ); + expect(flush).toHaveBeenNthCalledWith( + 2, + 'big-1', + bufferSize / 2, + expect.stringContaining('--retry-chunk-1'), + ); await buffer.stop(); }); diff --git a/packages/services/usage/__tests__/validation.spec.ts b/packages/services/usage/__tests__/validation.spec.ts index c457b2bfd..9c12f93b1 100644 --- a/packages/services/usage/__tests__/validation.spec.ts +++ b/packages/services/usage/__tests__/validation.spec.ts @@ -19,8 +19,8 @@ test('correct operation should be valid', () => { operationName: 'foo', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual({ valid: true }); }); @@ -41,8 +41,8 @@ test('operation with missing timestamp should be valid', () => { operationName: 'foo', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual({ valid: true }); }); @@ -63,8 +63,8 @@ test('operation with missing operationName should be valid', () => { operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual({ valid: true }); }); @@ -85,8 +85,8 @@ test('operation with missing metadata should be valid', () => { operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual({ valid: true }); }); @@ -109,8 +109,8 @@ test('operation with empty metadata.client should be valid', () => { operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual({ valid: true }); }); @@ -135,8 +135,8 @@ test('operation with empty metadata.client.name should be valid', () => { operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual({ valid: true }); }); @@ -161,8 +161,8 @@ test('operation with empty metadata.client.version should be valid', () => { operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual({ valid: true }); }); @@ -183,8 +183,8 @@ test('operation with empty list in metadata.client.errors should be valid', () = operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual({ valid: true }); }); @@ -209,8 +209,8 @@ test('operation with empty metadata.client.errors.path should be valid', () => { operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual({ valid: true }); }); @@ -231,8 +231,8 @@ test.skip('operation with empty metadata.client.errors.message should NOT be val operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual(expect.objectContaining({ valid: false })); }); @@ -248,8 +248,8 @@ test('operation with empty in execution should NOT be valid', () => { operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual(expect.objectContaining({ valid: false })); }); @@ -268,8 +268,8 @@ test('operation with empty in execution.ok should NOT be valid', () => { operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual(expect.objectContaining({ valid: false })); }); @@ -288,8 +288,8 @@ test('operation with empty execution.duration should NOT be valid', () => { operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual(expect.objectContaining({ valid: false })); }); @@ -308,8 +308,8 @@ test('operation with empty execution.errorsTotal should NOT be valid', () => { operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual(expect.objectContaining({ valid: false })); }); @@ -329,8 +329,8 @@ test('operation with non-boolean execution.ok should NOT be valid', () => { operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual(expect.objectContaining({ valid: false })); }); @@ -350,8 +350,8 @@ test('operation with non-number execution.duration should NOT be valid', () => { operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual(expect.objectContaining({ valid: false })); }); @@ -371,8 +371,8 @@ test('operation with non-number execution.errorsTotal should NOT be valid', () = operation: 'query foo { foo }', fields: ['Query', 'Query.foo'], }, - } - ) + }, + ), ).toEqual(expect.objectContaining({ valid: false })); }); @@ -382,7 +382,7 @@ test('operation with empty operation should NOT be valid', () => { expect( validateOperationMapRecord({ fields: ['Query', 'Query.foo'], - } as any) + } as any), ).toEqual(expect.objectContaining({ valid: false })); }); @@ -390,12 +390,12 @@ test('operation with empty in fields should NOT be valid', () => { expect( validateOperationMapRecord({ operation: 'query foo { foo }', - } as any) + } as any), ).toEqual(expect.objectContaining({ valid: false })); }); test('operation with empty fields should NOT be valid', () => { expect(validateOperationMapRecord({ operation: 'query foo { foo }', fields: [] })).toEqual( - expect.objectContaining({ valid: false }) + expect.objectContaining({ valid: false }), ); }); diff --git a/packages/services/usage/package.json b/packages/services/usage/package.json index 00f2439f3..9c3e1ce56 100644 --- a/packages/services/usage/package.json +++ b/packages/services/usage/package.json @@ -1,19 +1,19 @@ { "name": "@hive/usage", - "type": "module", - "private": true, "version": "0.0.0", + "type": "module", "license": "MIT", + "private": true, "scripts": { - "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "build": "bob runify --single", + "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "typecheck": "tsc --noEmit" }, "dependencies": { - "@whatwg-node/fetch": "0.4.7", - "@trpc/client": "9.23.2", "@sentry/node": "7.20.1", "@sentry/tracing": "7.20.1", + "@trpc/client": "9.23.2", + "@whatwg-node/fetch": "0.4.7", "ajv": "8.11.2", "dotenv": "10.0.0", "got": "12.5.3", @@ -23,9 +23,9 @@ "zod": "3.15.1" }, "devDependencies": { - "@hive/usage-common": "workspace:*", "@hive/service-common": "workspace:*", "@hive/tokens": "workspace:*", + "@hive/usage-common": "workspace:*", "pino-pretty": "6.0.0" }, "buildOptions": { diff --git a/packages/services/usage/src/buffer.ts b/packages/services/usage/src/buffer.ts index b5a2fdcd3..2bf0bb2a5 100644 --- a/packages/services/usage/src/buffer.ts +++ b/packages/services/usage/src/buffer.ts @@ -16,7 +16,11 @@ export function isBufferTooBigError(error: unknown): error is BufferTooBigError * @param numOfChunks How many chunks to split the list into * @param chunkIndex The index of the chunk to split the list into (0-based) */ -export function calculateChunkSize(totalLength: number, numOfChunks: number, chunkIndex: number): number { +export function calculateChunkSize( + totalLength: number, + numOfChunks: number, + chunkIndex: number, +): number { // If x % n == 0 then the minimum, difference is 0 and all numbers are x / n if (totalLength % numOfChunks == 0) { return totalLength / numOfChunks; @@ -62,8 +66,15 @@ export function createEstimator(config: { if (increaseBy) { // increase the default estimation by X ratio // but don't go higher than 50% of original estimation - defaultBytesPerUnit = Math.min((1 + increaseBy) * defaultBytesPerUnit, 1.5 * config.defaultBytesPerUnit); - config.logger.info('Increasing default bytes per unit (ratio=%s, new=%s)', increaseBy, defaultBytesPerUnit); + defaultBytesPerUnit = Math.min( + (1 + increaseBy) * defaultBytesPerUnit, + 1.5 * config.defaultBytesPerUnit, + ); + config.logger.info( + 'Increasing default bytes per unit (ratio=%s, new=%s)', + increaseBy, + defaultBytesPerUnit, + ); } } @@ -124,7 +135,7 @@ export function createKVBuffer(config: { reports: readonly T[], estimatedSizeInBytes: number, batchId: string, - validateSize: (actualSizeInBytes: number) => void | never + validateSize: (actualSizeInBytes: number) => void | never, ): Promise; }) { const { logger } = config; @@ -156,7 +167,12 @@ export function createKVBuffer(config: { return buffer.reduce((sum, report) => sum + (report as any).size, 0); } - async function flushBuffer(reports: readonly T[], size: number, batchId: string, isRetry = false) { + async function flushBuffer( + reports: readonly T[], + size: number, + batchId: string, + isRetry = false, + ) { logger.info(`Flushing (reports=%s, bufferSize=%s, id=%s)`, reports.length, size, batchId); const estimatedSizeInBytes = estimator.estimate(size); buffer = []; @@ -172,7 +188,7 @@ export function createKVBuffer(config: { bytes, (Math.abs(estimatedSizeInBytes - bytes) / bytes).toFixed(4), estimator.getDefaultBytesPerUnit(), - batchId + batchId, ); estimator.teach({ @@ -210,8 +226,13 @@ export function createKVBuffer(config: { return Promise.all( chunks.map((chunk, chunkIndex) => - flushBuffer(chunk, calculateBufferSize(chunk), batchId + '--retry-chunk-' + chunkIndex, true) - ) + flushBuffer( + chunk, + calculateBufferSize(chunk), + batchId + '--retry-chunk-' + chunkIndex, + true, + ), + ), ); } diff --git a/packages/services/usage/src/environment.ts b/packages/services/usage/src/environment.ts index 80e4a9ff3..e0c8e0eb7 100644 --- a/packages/services/usage/src/environment.ts +++ b/packages/services/usage/src/environment.ts @@ -49,7 +49,11 @@ const KafkaModel = zod.union([ KAFKA_SASL_MECHANISM: zod.void().optional(), }), KafkaBaseModel.extend({ - KAFKA_SASL_MECHANISM: zod.union([zod.literal('plain'), zod.literal('scram-sha-256'), zod.literal('scram-sha-512')]), + KAFKA_SASL_MECHANISM: zod.union([ + zod.literal('plain'), + zod.literal('scram-sha-256'), + zod.literal('scram-sha-512'), + ]), KAFKA_SASL_USERNAME: zod.string(), KAFKA_SASL_PASSWORD: zod.string(), }), @@ -72,7 +76,7 @@ const LogModel = zod.object({ zod.literal('fatal'), zod.literal('silent'), ]) - .optional() + .optional(), ), }); diff --git a/packages/services/usage/src/index.ts b/packages/services/usage/src/index.ts index acb3d3f74..6386b37ea 100644 --- a/packages/services/usage/src/index.ts +++ b/packages/services/usage/src/index.ts @@ -1,6 +1,11 @@ #!/usr/bin/env node import * as Sentry from '@sentry/node'; -import { createServer, startMetrics, registerShutdown, reportReadiness } from '@hive/service-common'; +import { + createServer, + startMetrics, + registerShutdown, + reportReadiness, +} from '@hive/service-common'; import { createTokens } from './tokens'; import { createUsage } from './usage'; import { @@ -118,14 +123,17 @@ async function main() { entityType: 'target', }) ) { - droppedReports.labels({ targetId: tokenInfo.target, orgId: tokenInfo.organization }).inc(); + droppedReports + .labels({ targetId: tokenInfo.target, orgId: tokenInfo.organization }) + .inc(); req.log.info('Rate limited (token=%s)', maskedToken); res.status(429).send(); // eslint-disable-line @typescript-eslint/no-floating-promises -- false positive, FastifyReply.then returns void return; } - const retentionInfo = (await rateLimit?.getRetentionForTargetId?.(tokenInfo.target)) || null; + const retentionInfo = + (await rateLimit?.getRetentionForTargetId?.(tokenInfo.target)) || null; const stopTimer = collectDuration.startTimer(); try { diff --git a/packages/services/usage/src/rate-limit.ts b/packages/services/usage/src/rate-limit.ts index 77dde5fb9..d17249e99 100644 --- a/packages/services/usage/src/rate-limit.ts +++ b/packages/services/usage/src/rate-limit.ts @@ -4,7 +4,10 @@ import type { RateLimitApi, RateLimitQueryInput, RateLimitQueryOutput } from '@h import { createTRPCClient } from '@trpc/client'; import { fetch } from '@whatwg-node/fetch'; -export function createUsageRateLimit(config: { endpoint: string | null; logger: FastifyLoggerInstance }) { +export function createUsageRateLimit(config: { + endpoint: string | null; + logger: FastifyLoggerInstance; +}) { const logger = config.logger; if (!config.endpoint) { @@ -25,13 +28,13 @@ export function createUsageRateLimit(config: { endpoint: string | null; logger: const retentionCache = LRU | null>>(1000, 30_000); async function fetchFreshRetentionInfo( - input: RateLimitQueryInput<'getRetention'> + input: RateLimitQueryInput<'getRetention'>, ): Promise | null> { return rateLimit.query('getRetention', input); } async function fetchFreshLimitInfo( - input: RateLimitQueryInput<'checkRateLimit'> + input: RateLimitQueryInput<'checkRateLimit'>, ): Promise | null> { return rateLimit.query('checkRateLimit', input); } diff --git a/packages/services/usage/src/usage.ts b/packages/services/usage/src/usage.ts index c6bdfdf6f..8bdbbaab0 100644 --- a/packages/services/usage/src/usage.ts +++ b/packages/services/usage/src/usage.ts @@ -174,7 +174,9 @@ export function createUsage(config: { }); const stopTimer = kafkaDuration.startTimer(); - estimationError.observe(Math.abs(estimatedSizeInBytes - value.byteLength) / value.byteLength); + estimationError.observe( + Math.abs(estimatedSizeInBytes - value.byteLength) / value.byteLength, + ); validateSize(value.byteLength); bufferFlushes.inc(); @@ -218,7 +220,7 @@ export function createUsage(config: { async collect( incomingReport: IncomingReport | IncomingLegacyReport, token: TokensResponse, - targetRetentionInDays: number | null + targetRetentionInDays: number | null, ) { if (status !== Status.Ready) { throw new Error('Usage is not ready yet'); @@ -269,7 +271,8 @@ export function createUsage(config: { for (const operation of incoming.operations) { // The validateOperation function drops the operation if the operationMapKey does not exist, we can safely pass the old key in case the new key is missing. - operation.operationMapKey = oldNewKeyMapping.get(operation.operationMapKey) ?? operation.operationMapKey; + operation.operationMapKey = + oldNewKeyMapping.get(operation.operationMapKey) ?? operation.operationMapKey; const validationResult = validateOperation(operation, outgoing.map); if (validationResult.valid) { @@ -295,10 +298,17 @@ export function createUsage(config: { }, }); } else { - logger.warn(`Detected invalid operation (target=%s): %o`, token.target, validationResult.errors); + logger.warn( + `Detected invalid operation (target=%s): %o`, + token.target, + validationResult.errors, + ); invalidRawOperations .labels({ - reason: 'reason' in validationResult && validationResult.reason ? validationResult.reason : 'unknown', + reason: + 'reason' in validationResult && validationResult.reason + ? validationResult.reason + : 'unknown', }) .inc(1); } @@ -337,7 +347,9 @@ export function createUsage(config: { }; } -function isLegacyReport(report: IncomingReport | IncomingLegacyReport): report is IncomingLegacyReport { +function isLegacyReport( + report: IncomingReport | IncomingLegacyReport, +): report is IncomingLegacyReport { return Array.isArray(report); } @@ -361,7 +373,10 @@ function convertLegacyReport(legacy: IncomingLegacyReport): IncomingReport { let operationMapKey = hashMap.get(op.operation); if (!operationMapKey) { - operationMapKey = createHash('sha256').update(op.operation).update(JSON.stringify(op.fields)).digest('hex'); + operationMapKey = createHash('sha256') + .update(op.operation) + .update(JSON.stringify(op.fields)) + .digest('hex'); report.map[operationMapKey] = { operation: op.operation, operationName: op.operationName, diff --git a/packages/services/webhooks/package.json b/packages/services/webhooks/package.json index 9d502114b..b5e20ec93 100644 --- a/packages/services/webhooks/package.json +++ b/packages/services/webhooks/package.json @@ -1,29 +1,29 @@ { "name": "@hive/webhooks", - "type": "module", - "private": true, "version": "0.0.0", + "type": "module", "license": "MIT", + "private": true, "scripts": { - "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "build": "bob runify --single", + "dev": "tsup-node src/dev.ts --format esm --shims --target node16 --watch --sourcemap --onSuccess 'node --enable-source-maps dist/dev.js' | pino-pretty --translateTime HH:MM:ss TT --ignore pid,hostname", "postbuild": "copyfiles -f \"node_modules/bullmq/dist/esm/commands/*.lua\" dist && copyfiles -f \"node_modules/bullmq/dist/esm/commands/includes/*.lua\" dist/includes", "typecheck": "tsc --noEmit" }, "dependencies": { - "zod": "3.15.1", - "@trpc/server": "9.23.2", "@sentry/node": "7.20.1", "@sentry/tracing": "7.20.1", + "@trpc/server": "9.23.2", "bullmq": "1.81.4", "dotenv": "10.0.0", "got": "12.5.3", "ioredis": "4.28.5", - "p-timeout": "5.0.2" + "p-timeout": "5.0.2", + "zod": "3.15.1" }, "devDependencies": { - "@types/ioredis": "4.28.10", "@hive/service-common": "workspace:*", + "@types/ioredis": "4.28.10", "copyfiles": "2.4.1", "pino-pretty": "6.0.0", "tslib": "2.4.1" diff --git a/packages/services/webhooks/src/environment.ts b/packages/services/webhooks/src/environment.ts index 182209d51..513f9e27a 100644 --- a/packages/services/webhooks/src/environment.ts +++ b/packages/services/webhooks/src/environment.ts @@ -68,7 +68,7 @@ const LogModel = zod.object({ zod.literal('fatal'), zod.literal('silent'), ]) - .optional() + .optional(), ), }); diff --git a/packages/services/webhooks/src/jobs.ts b/packages/services/webhooks/src/jobs.ts index 0cb8c9514..3d7e99c19 100644 --- a/packages/services/webhooks/src/jobs.ts +++ b/packages/services/webhooks/src/jobs.ts @@ -36,7 +36,7 @@ export function createWebhookJob({ config }: { config: Config }) { 'Calling webhook (job=%s, attempt=%d of %d)', job.name, job.attemptsMade + 1, - config.maxAttempts + config.maxAttempts, ); if (config.requestBroker) { diff --git a/packages/services/webhooks/src/scheduler.ts b/packages/services/webhooks/src/scheduler.ts index da5b526b9..05535f26d 100644 --- a/packages/services/webhooks/src/scheduler.ts +++ b/packages/services/webhooks/src/scheduler.ts @@ -51,7 +51,7 @@ export function createScheduler(config: Config) { await pTimeout( Promise.all([webhookQueue?.close(), webhookQueueScheduler?.close()]), 5000, - 'BullMQ close timeout' + 'BullMQ close timeout', ); } catch (e) { logger.error('Failed to stop queues', e); @@ -160,7 +160,12 @@ export function createScheduler(config: Config) { } function onFailed(job: Job, error: Error) { - logger.debug(`Job %s failed after %s attempts, reason: %s`, job.name, job.attemptsMade, job.failedReason); + logger.debug( + `Job %s failed after %s attempts, reason: %s`, + job.name, + job.attemptsMade, + job.failedReason, + ); logger.error(error); } @@ -202,7 +207,9 @@ export function createScheduler(config: Config) { return false; } - return webhookQueue !== null && redisConnection !== null && redisConnection?.status === 'ready'; + return ( + webhookQueue !== null && redisConnection !== null && redisConnection?.status === 'ready' + ); }, }; } diff --git a/packages/web/app/.babelrc.js b/packages/web/app/.babelrc.js index 69253e71e..f5a02744e 100644 --- a/packages/web/app/.babelrc.js +++ b/packages/web/app/.babelrc.js @@ -12,5 +12,9 @@ module.exports = { }, ], ], - plugins: ['@emotion/babel-plugin', 'babel-plugin-macros', [babelPlugin, { artifactDirectory: './src/gql' }]], + plugins: [ + '@emotion/babel-plugin', + 'babel-plugin-macros', + [babelPlugin, { artifactDirectory: './src/gql' }], + ], }; diff --git a/packages/web/app/README.md b/packages/web/app/README.md index 62770d9f6..33d547200 100644 --- a/packages/web/app/README.md +++ b/packages/web/app/README.md @@ -51,7 +51,8 @@ This is only important if you are hosting Hive for getting 💰. ### Legacy Auth0 Configuration -If you are not self-hosting GraphQL Hive, you can ignore this section. It is only required for the legacy Auth0 compatibility layer. +If you are not self-hosting GraphQL Hive, you can ignore this section. It is only required for the +legacy Auth0 compatibility layer. | Name | Required | Description | Example Value | | ----------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------- | ------------------------------------------- | diff --git a/packages/web/app/environment.ts b/packages/web/app/environment.ts index 5759e5501..98e8bf291 100644 --- a/packages/web/app/environment.ts +++ b/packages/web/app/environment.ts @@ -24,7 +24,9 @@ const BaseSchema = zod.object({ DOCS_URL: emptyString(zod.string().url().optional()), STRIPE_PUBLIC_KEY: emptyString(zod.string().optional()), RELEASE: emptyString(zod.string().optional()), - AUTH_REQUIRE_EMAIL_VERIFICATION: emptyString(zod.union([zod.literal('1'), zod.literal('0')]).optional()), + AUTH_REQUIRE_EMAIL_VERIFICATION: emptyString( + zod.union([zod.literal('1'), zod.literal('0')]).optional(), + ), }); const IntegrationSlackSchema = zod.union([ diff --git a/packages/web/app/package.json b/packages/web/app/package.json index 2070a217d..238de554d 100644 --- a/packages/web/app/package.json +++ b/packages/web/app/package.json @@ -1,14 +1,14 @@ { "name": "@hive/app", - "private": true, "version": "0.0.0", + "private": true, "scripts": { + "analyze": "pnpm build:config && ANALYZE=1 next build", + "build": "pnpm build:config && BUILD=1 bob runify --single", "build:config": "tsup-node --no-splitting --out-dir . --loader \".mts=ts\" --format esm --target node16 next.config.mts", "dev": "pnpm build:config && next dev", - "start": "node dist/index.js", - "build": "pnpm build:config && BUILD=1 bob runify --single", - "analyze": "pnpm build:config && ANALYZE=1 next build", "postbuild": "pnpm --filter @hive/app deploy dist/deploy && rimraf dist/node_modules && mv dist/deploy/node_modules dist && rimraf dist/deploy", + "start": "node dist/index.js", "typecheck": "tsc" }, "dependencies": { diff --git a/packages/web/app/pages/404.tsx b/packages/web/app/pages/404.tsx index 00051af50..206b03bec 100644 --- a/packages/web/app/pages/404.tsx +++ b/packages/web/app/pages/404.tsx @@ -8,7 +8,13 @@ const NotFoundPage = () => { <>
- Ghost illustration + Ghost illustration

404

Page Not Found

- - {query ? : null} - {mutation ? : null} + + {query ? ( + + ) : null} + {mutation ? ( + + ) : null} {subscription ? ( - + ) : null}
@@ -95,7 +122,9 @@ function ExplorerPage(): ReactElement { <TargetLayout value="explorer"> {props => ( - <SchemaExplorerProvider dataRetentionInDays={props.organization.rateLimit.retentionInDays}> + <SchemaExplorerProvider + dataRetentionInDays={props.organization.rateLimit.retentionInDays} + > <SchemaView {...props} /> </SchemaExplorerProvider> )} diff --git a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/explorer/[typename].tsx b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/explorer/[typename].tsx index b21087066..7094e7778 100644 --- a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/explorer/[typename].tsx +++ b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/explorer/[typename].tsx @@ -20,7 +20,10 @@ import { GraphQLObjectTypeComponent, GraphQLObjectTypeComponent_TypeFragment, } from '@/components/target/explorer/object-type'; -import { SchemaExplorerProvider, useSchemaExplorerContext } from '@/components/target/explorer/provider'; +import { + SchemaExplorerProvider, + useSchemaExplorerContext, +} from '@/components/target/explorer/provider'; import { GraphQLScalarTypeComponent, GraphQLScalarTypeComponent_TypeFragment, @@ -62,7 +65,9 @@ const SchemaTypeExplorer_Type = gql(/* GraphQL */ ` } } } - operationsStats(selector: { organization: $organization, project: $project, target: $target, period: $period }) { + operationsStats( + selector: { organization: $organization, project: $project, target: $target, period: $period } + ) { totalRequests } } @@ -163,7 +168,9 @@ function ExplorerPage(): ReactElement | null { <Title title={`Type ${typename}`} /> <TargetLayout value="explorer"> {props => ( - <SchemaExplorerProvider dataRetentionInDays={props.organization.rateLimit.retentionInDays}> + <SchemaExplorerProvider + dataRetentionInDays={props.organization.rateLimit.retentionInDays} + > <SchemaTypeExplorer {...props} typename={typename} /> </SchemaExplorerProvider> )} diff --git a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/history.tsx b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/history.tsx index 7bd2c630a..331c726a4 100644 --- a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/history.tsx +++ b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/history.tsx @@ -32,7 +32,9 @@ import { withSessionProtection } from '@/lib/supertokens/guard'; function labelize(message: string) { const findSingleQuotes = /'([^']+)'/gim; - return reactStringReplace(message, findSingleQuotes, (match, i) => <Label key={i}>{match}</Label>); + return reactStringReplace(message, findSingleQuotes, (match, i) => ( + <Label key={i}>{match}</Label> + )); } const titleMap: Record<CriticalityLevel, string> = { @@ -61,7 +63,9 @@ const ChangesBlock = ({ return ( <div> - <h2 className="mb-2 text-lg font-medium text-gray-900 dark:text-white">{titleMap[criticality]}</h2> + <h2 className="mb-2 text-lg font-medium text-gray-900 dark:text-white"> + {titleMap[criticality]} + </h2> <ul className="list-inside list-disc pl-3 text-base leading-relaxed"> {filteredChanges.map((change, key) => ( <li key={key} className={clsx(criticalityLevelMapping[criticality] ?? 'text-red-400')}> @@ -73,7 +77,13 @@ const ChangesBlock = ({ ); }; -const DiffView = ({ view, versionId }: { view: 'sdl' | 'list'; versionId: string }): ReactElement | null => { +const DiffView = ({ + view, + versionId, +}: { + view: 'sdl' | 'list'; + versionId: string; +}): ReactElement | null => { const router = useRouteSelector(); const [compareQuery] = useQuery({ query: CompareDocument, @@ -97,7 +107,9 @@ const DiffView = ({ view, versionId }: { view: 'sdl' | 'list'; versionId: string <VscBug className="h-8 w-8 text-red-500" /> <h2 className="text-lg font-medium text-white">Failed to build GraphQL Schema</h2> </div> - <p className="text-base text-gray-500">Schema is most likely incomplete and was force published</p> + <p className="text-base text-gray-500"> + Schema is most likely incomplete and was force published + </p> </div> ); } @@ -160,7 +172,7 @@ const ListPage = ({ <a className={clsx( 'flex flex-col rounded-md p-2.5 hover:bg-gray-800/40', - versionId === version.id && 'bg-gray-800/40' + versionId === version.id && 'bg-gray-800/40', )} > <h3 className="truncate font-bold">{version.commit.commit}</h3> @@ -169,7 +181,8 @@ const ListPage = ({ </div> <div className="mt-2.5 mb-1.5 flex align-middle text-xs font-medium text-[#c4c4c4]"> <div className={clsx('w-1/2 ', !version.valid && 'text-red-500')}> - <Badge color={version.valid ? 'green' : 'red'} /> Published <TimeAgo date={version.date} /> + <Badge color={version.valid ? 'green' : 'red'} /> Published{' '} + <TimeAgo date={version.date} /> </div> {version.commit.service && ( @@ -275,7 +288,10 @@ function HistoryPage(): ReactElement { return ( <> <Title title="History" /> - <TargetLayout value="history" className={versionId ? 'flex h-full items-stretch gap-x-5' : ''}> + <TargetLayout + value="history" + className={versionId ? 'flex h-full items-stretch gap-x-5' : ''} + > {() => (versionId ? <Page versionId={versionId} /> : noSchema)} </TargetLayout> </> diff --git a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/index.tsx b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/index.tsx index 5782d530e..7498beaed 100644 --- a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/index.tsx +++ b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/index.tsx @@ -94,10 +94,13 @@ const SchemaServiceName = ({ }, }); }, - [mutate, organization.cleanId, project.cleanId, schema.service, target.cleanId, version] + [mutate, organization.cleanId, project.cleanId, schema.service, target.cleanId, version], ); - if ((project.type !== ProjectType.Federation && project.type !== ProjectType.Stitching) || !hasAccess) { + if ( + (project.type !== ProjectType.Federation && project.type !== ProjectType.Stitching) || + !hasAccess + ) { return <>{schema.service}</>; } @@ -195,7 +198,9 @@ const SyncSchemaButton = ({ if (result.error) { setStatus('error'); } else { - setStatus(result.data?.schemaSyncCDN.__typename === 'SchemaSyncCDNError' ? 'error' : 'success'); + setStatus( + result.data?.schemaSyncCDN.__typename === 'SchemaSyncCDNError' ? 'error' : 'success', + ); } setTimeout(() => { setStatus('idle'); @@ -208,8 +213,17 @@ const SyncSchemaButton = ({ } return ( - <Tooltip label="Re-upload the latest valid version to Hive CDN" fontSize="xs" placement="bottom-start"> - <Button variant="primary" size="large" onClick={sync} disabled={status !== 'idle' || mutation.fetching}> + <Tooltip + label="Re-upload the latest valid version to Hive CDN" + fontSize="xs" + placement="bottom-start" + > + <Button + variant="primary" + size="large" + onClick={sync} + disabled={status !== 'idle' || mutation.fetching} + > {mutation.fetching ? 'Syncing…' : FetchingMessages[status as keyof typeof FetchingMessages] ?? 'CDN is up to date'} @@ -243,14 +257,15 @@ function SchemaView({ debouncedFilter(event.target.value); setTerm(event.target.value); }, - [debouncedFilter, setTerm] + [debouncedFilter, setTerm], ); const reset = useCallback(() => { setFilterService(''); setTerm(''); }, [setFilterService]); - const isDistributed = project.type === ProjectType.Federation || project.type === ProjectType.Stitching; + const isDistributed = + project.type === ProjectType.Federation || project.type === ProjectType.Stitching; const [query] = useQuery({ query: LatestSchemaDocument, @@ -289,9 +304,20 @@ function SchemaView({ }} > <InputGroup size="sm" variant="filled"> - <Input type="text" placeholder="Find service" value={term} onChange={handleChange} /> + <Input + type="text" + placeholder="Find service" + value={term} + onChange={handleChange} + /> <InputRightElement> - <IconButton aria-label="Reset" size="xs" variant="ghost" onClick={reset} icon={<VscClose />} /> + <IconButton + aria-label="Reset" + size="xs" + variant="ghost" + onClick={reset} + icon={<VscClose />} + /> </InputRightElement> </InputGroup> </form> @@ -299,7 +325,11 @@ function SchemaView({ {canManage ? ( <> <MarkAsValid version={query.data.target.latestSchemaVersion} />{' '} - <SyncSchemaButton target={target} project={project} organization={organization} /> + <SyncSchemaButton + target={target} + project={project} + organization={organization} + /> </> ) : null} </div> diff --git a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/laboratory.tsx b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/laboratory.tsx index 0883cd2c4..95f9266bb 100644 --- a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/laboratory.tsx +++ b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/laboratory.tsx @@ -51,7 +51,11 @@ function LaboratoryPage(): ReactElement { Connect <Link2Icon className="ml-8 h-4 w-4" /> </Button> - <ConnectLabModal isOpen={isModalOpen} toggleModalOpen={toggleModalOpen} endpoint={endpoint} /> + <ConnectLabModal + isOpen={isModalOpen} + toggleModalOpen={toggleModalOpen} + endpoint={endpoint} + /> </> } > diff --git a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/operations.tsx b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/operations.tsx index 65ad95a1b..1bc0d7745 100644 --- a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/operations.tsx +++ b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/operations.tsx @@ -47,7 +47,8 @@ const OperationsView = ({ }): ReactElement => { const router = useRouter(); const [href, periodParam] = router.asPath.split('?'); - const selectedPeriod: PeriodKey = (new URLSearchParams(periodParam).get('period') as PeriodKey) ?? '1d'; + const selectedPeriod: PeriodKey = + (new URLSearchParams(periodParam).get('period') as PeriodKey) ?? '1d'; const [selectedOperations, setSelectedOperations] = useState<string[]>([]); const period = useMemo(() => { @@ -56,7 +57,11 @@ const OperationsView = ({ const value = parseInt(selectedPeriod.replace(sub, '')); const from = formatISO( - sub === 'h' ? subHours(now, value) : sub === 'm' ? subMinutes(now, value) : subDays(now, value) + sub === 'h' + ? subHours(now, value) + : sub === 'm' + ? subMinutes(now, value) + : subDays(now, value), ); const to = formatISO(now); @@ -67,7 +72,7 @@ const OperationsView = ({ ev => { router.push(`${href}?period=${ev.target.value}`); }, - [href, router] + [href, router], ); return ( @@ -75,7 +80,11 @@ const OperationsView = ({ <div className="absolute top-0 right-0"> <Stack direction="row" spacing={4}> <div> - <OperationsFilterTrigger period={period} selected={selectedOperations} onFilter={setSelectedOperations} /> + <OperationsFilterTrigger + period={period} + selected={selectedOperations} + onFilter={setSelectedOperations} + /> </div> <div> <Select diff --git a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/settings.tsx b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/settings.tsx index e672e8c71..7b257578f 100644 --- a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/settings.tsx +++ b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/settings.tsx @@ -9,7 +9,18 @@ import * as Yup from 'yup'; import { authenticated } from '@/components/authenticated-container'; import { TargetLayout } from '@/components/layouts'; import { SchemaEditor } from '@/components/schema-editor'; -import { Button, Card, Checkbox, Heading, Input, Switch, Table, Tag, TimeAgo, Title } from '@/components/v2'; +import { + Button, + Card, + Checkbox, + Heading, + Input, + Switch, + Table, + Tag, + TimeAgo, + Title, +} from '@/components/v2'; import { Combobox } from '@/components/v2/combobox'; import { AlertTriangleIcon } from '@/components/v2/icon'; import { CreateAccessTokenModal, DeleteTargetModal } from '@/components/v2/modals'; @@ -120,7 +131,9 @@ const Tokens = ({ me }: { me: MemberFieldsFragment }): ReactElement => { }))} columns={columns} /> - {isModalOpen && <CreateAccessTokenModal isOpen={isModalOpen} toggleModalOpen={toggleModalOpen} />} + {isModalOpen && ( + <CreateAccessTokenModal isOpen={isModalOpen} toggleModalOpen={toggleModalOpen} /> + )} </Card> ); }; @@ -166,7 +179,9 @@ const ExtendBaseSchema = (props: { baseSchema: string }): ReactElement => { <div className="text-red-500">{mutation.data.updateBaseSchema.error.message}</div> )} {mutation.error && ( - <div className="text-red-500">{mutation.error?.graphQLErrors[0]?.message ?? mutation.error.message}</div> + <div className="text-red-500"> + {mutation.error?.graphQLErrors[0]?.message ?? mutation.error.message} + </div> )} <div className="mt-3 flex items-center gap-x-3"> <Button @@ -187,7 +202,12 @@ const ExtendBaseSchema = (props: { baseSchema: string }): ReactElement => { > Save </Button> - <Button size="large" variant="secondary" className="px-5" onClick={() => setBaseSchema(props.baseSchema)}> + <Button + size="large" + variant="secondary" + className="px-5" + onClick={() => setBaseSchema(props.baseSchema)} + > Reset </Button> {isUnsaved && <span className="text-green-500">Unsaved changes!</span>} @@ -216,7 +236,7 @@ function ClientExclusion( clientsFromSettings: string[]; value: string[]; } & Pick<React.ComponentProps<typeof Combobox>, 'name' | 'disabled' | 'onBlur' | 'onChange'> - > + >, ) { const now = floorDate(new Date()); const [availableClientNamesQuery] = useQuery({ @@ -234,9 +254,10 @@ function ClientExclusion( }, }); - const clientNamesFromStats = availableClientNamesQuery.data?.clientStatsByTargets.nodes.map(n => n.name) ?? []; + const clientNamesFromStats = + availableClientNamesQuery.data?.clientStatsByTargets.nodes.map(n => n.name) ?? []; const allClientNames = clientNamesFromStats.concat( - props.clientsFromSettings.filter(clientName => !clientNamesFromStats.includes(clientName)) + props.clientsFromSettings.filter(clientName => !clientNamesFromStats.includes(clientName)), ); return ( @@ -398,7 +419,7 @@ const ConditionalBreakingChanges = (): ReactElement => { <div className={clsx( 'mb-3 flex flex-col items-start gap-3 font-light text-gray-300', - !isEnabled && 'pointer-events-none opacity-25' + !isEnabled && 'pointer-events-none opacity-25', )} > <div> @@ -432,7 +453,9 @@ const ConditionalBreakingChanges = (): ReactElement => { /> days </div> - {touched.percentage && errors.percentage && <div className="text-red-500">{errors.percentage}</div>} + {touched.percentage && errors.percentage && ( + <div className="text-red-500">{errors.percentage}</div> + )} {mutation.data?.updateTargetValidationSettings.error?.inputErrors.percentage && ( <div className="text-red-500"> {mutation.data.updateTargetValidationSettings.error.inputErrors.percentage} @@ -440,12 +463,16 @@ const ConditionalBreakingChanges = (): ReactElement => { )} {touched.period && errors.period && <div className="text-red-500">{errors.period}</div>} {mutation.data?.updateTargetValidationSettings.error?.inputErrors.period && ( - <div className="text-red-500">{mutation.data.updateTargetValidationSettings.error.inputErrors.period}</div> + <div className="text-red-500"> + {mutation.data.updateTargetValidationSettings.error.inputErrors.period} + </div> )} <div className="my-4 flex flex-col gap-2"> <div> <div>Exclude these clients:</div> - <div className="text-xs">Marks a breaking change as safe when it only affects the following clients.</div> + <div className="text-xs"> + Marks a breaking change as safe when it only affects the following clients. + </div> </div> <div> {values.targets.length > 0 ? ( @@ -460,7 +487,7 @@ const ConditionalBreakingChanges = (): ReactElement => { onChange={options => { setFieldValue( 'excludedClients', - options.map(o => o.value) + options.map(o => o.value), ); }} disabled={isSubmitting} @@ -481,7 +508,9 @@ const ConditionalBreakingChanges = (): ReactElement => { onCheckedChange={isChecked => { setFieldValue( 'targets', - isChecked ? [...values.targets, pt.id] : values.targets.filter(value => value !== pt.id) + isChecked + ? [...values.targets, pt.id] + : values.targets.filter(value => value !== pt.id), ); }} onBlur={() => setFieldTouched('targets', true)} @@ -489,7 +518,9 @@ const ConditionalBreakingChanges = (): ReactElement => { {pt.name} </div> ))} - {touched.targets && errors.targets && <div className="text-red-500">{errors.targets}</div>} + {touched.targets && errors.targets && ( + <div className="text-red-500">{errors.targets}</div> + )} <Tag className="mt-5 flex-col !items-start gap-1"> Example settings: Removal of a field is considered breaking if <div> @@ -506,7 +537,13 @@ const ConditionalBreakingChanges = (): ReactElement => { </div> </Tag> <div> - <Button type="submit" className="px-5" variant="primary" size="large" disabled={isSubmitting}> + <Button + type="submit" + className="px-5" + variant="primary" + size="large" + disabled={isSubmitting} + > Save </Button> {mutation.error && ( @@ -544,7 +581,13 @@ const Settings_UpdateTargetNameMutation = gql(/* GraphQL */ ` } `); -const Page = ({ target, organization }: { target: TargetFieldsFragment; organization: OrganizationFieldsFragment }) => { +const Page = ({ + target, + organization, +}: { + target: TargetFieldsFragment; + organization: OrganizationFieldsFragment; +}) => { const router = useRouteSelector(); const [isModalOpen, setModalOpen] = useState(false); const toggleModalOpen = useCallback(() => { @@ -552,29 +595,30 @@ const Page = ({ target, organization }: { target: TargetFieldsFragment; organiza }, []); const [mutation, mutate] = useMutation(Settings_UpdateTargetNameMutation); - const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = useFormik({ - enableReinitialize: true, - initialValues: { - name: target?.name || '', - }, - validationSchema: Yup.object().shape({ - name: Yup.string().required('Target name is required'), - }), - onSubmit: values => - mutate({ - input: { - organization: router.organizationId, - project: router.projectId, - target: router.targetId, - name: values.name, - }, - }).then(result => { - if (result?.data?.updateTargetName?.ok) { - const newTargetId = result.data.updateTargetName.ok.updatedTarget.cleanId; - router.replace(`/${router.organizationId}/${router.projectId}/${newTargetId}/settings`); - } + const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = + useFormik({ + enableReinitialize: true, + initialValues: { + name: target?.name || '', + }, + validationSchema: Yup.object().shape({ + name: Yup.string().required('Target name is required'), }), - }); + onSubmit: values => + mutate({ + input: { + organization: router.organizationId, + project: router.projectId, + target: router.targetId, + name: values.name, + }, + }).then(result => { + if (result?.data?.updateTargetName?.ok) { + const newTargetId = result.data.updateTargetName.ok.updatedTarget.cleanId; + router.replace(`/${router.organizationId}/${router.projectId}/${newTargetId}/settings`); + } + }), + }); const me = organization?.me; @@ -585,7 +629,9 @@ const Page = ({ target, organization }: { target: TargetFieldsFragment; organiza <> <Card> <Heading className="mb-2">Target Info</Heading> - <p className="mb-3 font-light text-gray-300">Name of your target visible within organization.</p> + <p className="mb-3 font-light text-gray-300"> + Name of your target visible within organization. + </p> <form onSubmit={handleSubmit} className="flex gap-x-2"> <Input placeholder="Target name" @@ -597,7 +643,13 @@ const Page = ({ target, organization }: { target: TargetFieldsFragment; organiza isInvalid={touched.name && Boolean(errors.name)} className="w-96" /> - <Button type="submit" variant="primary" size="large" disabled={isSubmitting} className="px-10"> + <Button + type="submit" + variant="primary" + size="large" + disabled={isSubmitting} + className="px-10" + > Save </Button> </form> @@ -607,7 +659,9 @@ const Page = ({ target, organization }: { target: TargetFieldsFragment; organiza </div> )} {mutation.data?.updateTargetName.error?.inputErrors?.name && ( - <div className="mt-2 text-red-500">{mutation.data.updateTargetName.error.inputErrors.name}</div> + <div className="mt-2 text-red-500"> + {mutation.data.updateTargetName.error.inputErrors.name} + </div> )} </Card> @@ -622,7 +676,13 @@ const Page = ({ target, organization }: { target: TargetFieldsFragment; organiza <Heading className="mb-2">Delete Target</Heading> <p className="mb-3 font-light text-gray-300">Permanently remove your Target</p> <div className="flex items-center gap-x-2"> - <Button variant="primary" size="large" danger onClick={toggleModalOpen} className="px-5"> + <Button + variant="primary" + size="large" + danger + onClick={toggleModalOpen} + className="px-5" + > Delete Target </Button> <Tag color="yellow" className="py-2.5 px-4"> diff --git a/packages/web/app/pages/[orgId]/[projectId]/alerts.tsx b/packages/web/app/pages/[orgId]/[projectId]/alerts.tsx index 710903f16..d2851ace2 100644 --- a/packages/web/app/pages/[orgId]/[projectId]/alerts.tsx +++ b/packages/web/app/pages/[orgId]/[projectId]/alerts.tsx @@ -18,7 +18,11 @@ import { ProjectAccessScope, useProjectAccess } from '@/lib/access/project'; import { useRouteSelector } from '@/lib/hooks/use-route-selector'; import { withSessionProtection } from '@/lib/supertokens/guard'; -const channelAlertsColumns = [{ key: 'checkbox', width: 'auto' }, { key: 'name' }, { key: 'type' }] as const; +const channelAlertsColumns = [ + { key: 'checkbox', width: 'auto' }, + { key: 'name' }, + { key: 'type' }, +] as const; const alertsColumns = [ { key: 'checkbox', width: 'auto' }, @@ -59,13 +63,19 @@ const Channels = (): ReactElement => { checkbox: ( <Checkbox onCheckedChange={isChecked => - setChecked(isChecked ? [...checked, channelAlert.id] : checked.filter(k => k !== channelAlert.id)) + setChecked( + isChecked + ? [...checked, channelAlert.id] + : checked.filter(k => k !== channelAlert.id), + ) } checked={checked.includes(channelAlert.id)} /> ), type: ( - <Tag color={channelAlert.type === AlertChannelType.Webhook ? 'green' : 'yellow'}>{channelAlert.type}</Tag> + <Tag color={channelAlert.type === AlertChannelType.Webhook ? 'green' : 'yellow'}> + {channelAlert.type} + </Tag> ), }))} columns={channelAlertsColumns} @@ -99,7 +109,10 @@ const Channels = (): ReactElement => { ); }; -const Page = (props: { organization: OrganizationFieldsFragment; project: ProjectFieldsFragment }) => { +const Page = (props: { + organization: OrganizationFieldsFragment; + project: ProjectFieldsFragment; +}) => { useProjectAccess({ scope: ProjectAccessScope.Alerts, member: props.organization.me, @@ -133,11 +146,15 @@ const Page = (props: { organization: OrganizationFieldsFragment; project: Projec <Table dataSource={alerts.map(alert => ({ id: alert.id, - type: <span className="capitalize">{alert.type.replaceAll('_', ' ').toLowerCase()}</span>, + type: ( + <span className="capitalize">{alert.type.replaceAll('_', ' ').toLowerCase()}</span> + ), checkbox: ( <Checkbox onCheckedChange={isChecked => - setChecked(isChecked ? [...checked, alert.id] : checked.filter(k => k !== alert.id)) + setChecked( + isChecked ? [...checked, alert.id] : checked.filter(k => k !== alert.id), + ) } checked={checked.includes(alert.id)} /> diff --git a/packages/web/app/pages/[orgId]/[projectId]/index.tsx b/packages/web/app/pages/[orgId]/[projectId]/index.tsx index 3e784abe5..86b632b16 100644 --- a/packages/web/app/pages/[orgId]/[projectId]/index.tsx +++ b/packages/web/app/pages/[orgId]/[projectId]/index.tsx @@ -5,7 +5,17 @@ import { useQuery } from 'urql'; import { authenticated } from '@/components/authenticated-container'; import { ProjectLayout } from '@/components/layouts'; -import { Activities, Badge, Button, Card, DropdownMenu, EmptyList, Heading, TimeAgo, Title } from '@/components/v2'; +import { + Activities, + Badge, + Button, + Card, + DropdownMenu, + EmptyList, + Heading, + TimeAgo, + Title, +} from '@/components/v2'; import { LinkIcon, MoreIcon, SettingsIcon } from '@/components/v2/icon'; import { TargetQuery, TargetsDocument, VersionsDocument } from '@/graphql'; import { getDocsUrl } from '@/lib/docs-url'; @@ -13,7 +23,11 @@ import { useClipboard } from '@/lib/hooks/use-clipboard'; import { useRouteSelector } from '@/lib/hooks/use-route-selector'; import { withSessionProtection } from '@/lib/supertokens/guard'; -const TargetCard = ({ target }: { target: Exclude<TargetQuery['target'], null | undefined> }): ReactElement => { +const TargetCard = ({ + target, +}: { + target: Exclude<TargetQuery['target'], null | undefined>; +}): ReactElement => { const router = useRouteSelector(); const copyToClipboard = useClipboard(); const [versionsQuery] = useQuery({ @@ -57,7 +71,9 @@ const TargetCard = ({ target }: { target: Exclude<TargetQuery['target'], null | <LinkIcon /> Share Link </DropdownMenu.Item> - <NextLink href={`/${router.organizationId}/${router.projectId}/${target.cleanId}#settings`}> + <NextLink + href={`/${router.organizationId}/${router.projectId}/${target.cleanId}#settings`} + > <a> <DropdownMenu.Item> <SettingsIcon /> diff --git a/packages/web/app/pages/[orgId]/[projectId]/settings.tsx b/packages/web/app/pages/[orgId]/[projectId]/settings.tsx index ef53dfbae..c81ffdcc8 100644 --- a/packages/web/app/pages/[orgId]/[projectId]/settings.tsx +++ b/packages/web/app/pages/[orgId]/[projectId]/settings.tsx @@ -51,23 +51,24 @@ const GitHubIntegration = ({ gitRepository }: { gitRepository: string | null }): }); const [mutation, mutate] = useMutation(Settings_UpdateProjectGitRepositoryMutation); - const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = useFormik({ - enableReinitialize: true, - initialValues: { - gitRepository, - }, - validationSchema: Yup.object().shape({ - gitRepository: Yup.string(), - }), - onSubmit: values => - mutate({ - input: { - organization: router.organizationId, - project: router.projectId, - gitRepository: values.gitRepository, - }, + const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = + useFormik({ + enableReinitialize: true, + initialValues: { + gitRepository, + }, + validationSchema: Yup.object().shape({ + gitRepository: Yup.string(), }), - }); + onSubmit: values => + mutate({ + input: { + organization: router.organizationId, + project: router.projectId, + gitRepository: values.gitRepository, + }, + }), + }); if (integrationQuery.fetching) { return <Spinner />; @@ -90,17 +91,27 @@ const GitHubIntegration = ({ gitRepository }: { gitRepository: string | null }): onBlur={handleBlur} isInvalid={!!touched.gitRepository && Boolean(errors.gitRepository)} /> - <Button type="submit" variant="primary" size="large" className="px-10" disabled={isSubmitting}> + <Button + type="submit" + variant="primary" + size="large" + className="px-10" + disabled={isSubmitting} + > Save </Button> </form> {touched.gitRepository && (errors.gitRepository || mutation.error) && ( <div className="mt-2 text-red-500"> - {errors.gitRepository ?? mutation.error?.graphQLErrors[0]?.message ?? mutation.error?.message} + {errors.gitRepository ?? + mutation.error?.graphQLErrors[0]?.message ?? + mutation.error?.message} </div> )} {mutation.data?.updateProjectGitRepository.error && ( - <div className="mt-2 text-red-500">{mutation.data.updateProjectGitRepository.error.message}</div> + <div className="mt-2 text-red-500"> + {mutation.data.updateProjectGitRepository.error.message} + </div> )} </> ); @@ -156,34 +167,37 @@ const Page = ({ const [mutation, mutate] = useMutation(Settings_UpdateProjectNameMutation); - const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = useFormik({ - enableReinitialize: true, - initialValues: { - name: project?.name, - }, - validationSchema: Yup.object().shape({ - name: Yup.string().required('Project name is required'), - }), - onSubmit: values => - mutate({ - input: { - organization: router.organizationId, - project: router.projectId, - name: values.name, - }, - }).then(result => { - if (result?.data?.updateProjectName?.ok) { - const newProjectId = result.data.updateProjectName.ok.updatedProject.cleanId; - router.replace(`/${router.organizationId}/${newProjectId}/settings`); - } + const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = + useFormik({ + enableReinitialize: true, + initialValues: { + name: project?.name, + }, + validationSchema: Yup.object().shape({ + name: Yup.string().required('Project name is required'), }), - }); + onSubmit: values => + mutate({ + input: { + organization: router.organizationId, + project: router.projectId, + name: values.name, + }, + }).then(result => { + if (result?.data?.updateProjectName?.ok) { + const newProjectId = result.data.updateProjectName.ok.updatedProject.cleanId; + router.replace(`/${router.organizationId}/${newProjectId}/settings`); + } + }), + }); return ( <> <Card> <Heading className="mb-2">Project Name</Heading> - <p className="mb-3 font-light text-gray-300">Name of your project visible within organization</p> + <p className="mb-3 font-light text-gray-300"> + Name of your project visible within organization + </p> <form onSubmit={handleSubmit} className="flex gap-x-2"> <Input placeholder="Project name" @@ -195,7 +209,13 @@ const Page = ({ isInvalid={touched.name && Boolean(errors.name)} className="w-96" /> - <Button type="submit" variant="primary" size="large" className="px-10" disabled={isSubmitting}> + <Button + type="submit" + variant="primary" + size="large" + className="px-10" + disabled={isSubmitting} + > Save </Button> </form> @@ -211,7 +231,9 @@ const Page = ({ <Card> <Heading className="mb-2">Git Repository</Heading> - <p className="mb-3 font-light text-gray-300">Connect the project with your Git repository</p> + <p className="mb-3 font-light text-gray-300"> + Connect the project with your Git repository + </p> <GitHubIntegration gitRepository={project.gitRepository ?? null} /> </Card> @@ -226,7 +248,13 @@ const Page = ({ Permanently remove your Project and all targets from the Organization </p> <div className="flex items-center gap-x-2"> - <Button variant="primary" size="large" danger onClick={toggleModalOpen} className="px-5"> + <Button + variant="primary" + size="large" + danger + onClick={toggleModalOpen} + className="px-5" + > Delete Project </Button> <Tag color="yellow" className="py-2.5 px-4"> diff --git a/packages/web/app/pages/[orgId]/_new.tsx b/packages/web/app/pages/[orgId]/_new.tsx index 84782cfc5..71236c32e 100644 --- a/packages/web/app/pages/[orgId]/_new.tsx +++ b/packages/web/app/pages/[orgId]/_new.tsx @@ -49,8 +49,8 @@ const NewPage: FC = () => { <Card tw="w-[450px] mb-7" /> <Title>Welcome

- Create organization Lorem ipsum dolor sit amet, consectetur adipiscing elit. Turpis et dictum mattis - tincidunt quis iaculis arcu proin suspendisse. + Create organization Lorem ipsum dolor sit amet, consectetur adipiscing elit. Turpis et + dictum mattis tincidunt quis iaculis arcu proin suspendisse.

); }; -const InvitationDeleteButton = ({ email, organizationCleanId }: { email: string; organizationCleanId: string }) => { +const InvitationDeleteButton = ({ + email, + organizationCleanId, +}: { + email: string; + organizationCleanId: string; +}) => { const [mutation, mutate] = useMutation(InvitationDeleteButton_DeleteInvitation); return ( @@ -196,7 +224,10 @@ const Invitation = ({ Copy invite link - + @@ -243,11 +274,15 @@ const Page = ({ organization }: { organization: OrganizationFieldsFragment }) => if (!org || isPersonal) return null; const me = meQuery.data?.me; - const selectedMember = selectedMemberId ? members?.find(node => node.id === selectedMemberId) : null; + const selectedMember = selectedMemberId + ? members?.find(node => node.id === selectedMemberId) + : null; return ( <> -

Invite others to your organization and manage access

+

+ Invite others to your organization and manage access +

{selectedMember && ( { return ; } - const isGitHubIntegrationFeatureEnabled = checkIntegrations.data?.isGitHubIntegrationFeatureEnabled; + const isGitHubIntegrationFeatureEnabled = + checkIntegrations.data?.isGitHubIntegrationFeatureEnabled; const hasGitHubIntegration = checkIntegrations.data?.hasGitHubIntegration === true; const hasSlackIntegration = checkIntegrations.data?.hasSlackIntegration === true; @@ -150,34 +155,39 @@ const Page = ({ organization }: { organization: OrganizationFieldsFragment }) => const [mutation, mutate] = useMutation(UpdateOrganizationNameMutation); - const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = useFormik({ - enableReinitialize: true, - initialValues: { - name: organization?.name, - }, - validationSchema: Yup.object().shape({ - name: Yup.string().required('Organization name is required'), - }), - onSubmit: values => - mutate({ - input: { - organization: router.organizationId, - name: values.name, - }, - }).then(result => { - if (result.data?.updateOrganizationName?.ok) { - const newOrgId = result.data?.updateOrganizationName?.ok.updatedOrganizationPayload.selector.organization; - router.replace(`/${newOrgId}/settings`); - } + const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = + useFormik({ + enableReinitialize: true, + initialValues: { + name: organization?.name, + }, + validationSchema: Yup.object().shape({ + name: Yup.string().required('Organization name is required'), }), - }); + onSubmit: values => + mutate({ + input: { + organization: router.organizationId, + name: values.name, + }, + }).then(result => { + if (result.data?.updateOrganizationName?.ok) { + const newOrgId = + result.data?.updateOrganizationName?.ok.updatedOrganizationPayload.selector + .organization; + router.replace(`/${newOrgId}/settings`); + } + }), + }); return ( <> {isRegularOrg && ( Organization Name -

Name of your organization visible within Hive

+

+ Name of your organization visible within Hive +

isInvalid={touched.name && Boolean(errors.name)} className="w-96" /> -
@@ -197,9 +213,13 @@ const Page = ({ organization }: { organization: OrganizationFieldsFragment }) =>
{errors.name || mutation.error?.message}
)} {mutation.data?.updateOrganizationName?.error && ( -
{mutation.data?.updateOrganizationName.error.message}
+
+ {mutation.data?.updateOrganizationName.error.message} +
+ )} + {mutation.error && ( +
{mutation.error.graphQLErrors[0]?.message ?? mutation.error.message}
)} - {mutation.error &&
{mutation.error.graphQLErrors[0]?.message ?? mutation.error.message}
}
)} @@ -220,7 +240,13 @@ const Page = ({ organization }: { organization: OrganizationFieldsFragment }) => Permanently remove your Organization and all projects from the Hive

- diff --git a/packages/web/app/pages/[orgId]/subscription/index.tsx b/packages/web/app/pages/[orgId]/subscription/index.tsx index 59ca621ed..425329798 100644 --- a/packages/web/app/pages/[orgId]/subscription/index.tsx +++ b/packages/web/app/pages/[orgId]/subscription/index.tsx @@ -11,7 +11,11 @@ import { InvoicesList } from '@/components/organization/billing/InvoicesList'; import { RateLimitWarn } from '@/components/organization/billing/RateLimitWarn'; import { OrganizationUsageEstimationView } from '@/components/organization/Usage'; import { Card, Heading, Tabs, Title } from '@/components/v2'; -import { OrganizationFieldsFragment, OrgBillingInfoFieldsFragment, OrgRateLimitFieldsFragment } from '@/graphql'; +import { + OrganizationFieldsFragment, + OrgBillingInfoFieldsFragment, + OrgRateLimitFieldsFragment, +} from '@/graphql'; import { OrganizationAccessScope, useOrganizationAccess } from '@/lib/access/organization'; import { getIsStripeEnabled } from '@/lib/billing/stripe-public-key'; import { withSessionProtection } from '@/lib/supertokens/guard'; @@ -27,7 +31,9 @@ const ManagePage = dynamic(() => import('./manage')); const Page = ({ organization, }: { - organization: OrganizationFieldsFragment & OrgBillingInfoFieldsFragment & OrgRateLimitFieldsFragment; + organization: OrganizationFieldsFragment & + OrgBillingInfoFieldsFragment & + OrgRateLimitFieldsFragment; }): ReactElement | null => { const canAccess = useOrganizationAccess({ scope: OrganizationAccessScope.Settings, @@ -66,10 +72,14 @@ const Page = ({ Next Invoice - {CurrencyFormatter.format(organization.billingConfiguration.upcomingInvoice.amount)} + {CurrencyFormatter.format( + organization.billingConfiguration.upcomingInvoice.amount, + )} - {DateFormatter.format(new Date(organization.billingConfiguration.upcomingInvoice.date))} + {DateFormatter.format( + new Date(organization.billingConfiguration.upcomingInvoice.date), + )} )} diff --git a/packages/web/app/pages/[orgId]/subscription/manage.tsx b/packages/web/app/pages/[orgId]/subscription/manage.tsx index ef197134d..192bb2301 100644 --- a/packages/web/app/pages/[orgId]/subscription/manage.tsx +++ b/packages/web/app/pages/[orgId]/subscription/manage.tsx @@ -43,13 +43,17 @@ const Inner = ({ query: BillingPlansDocument, }); - const [paymentDetailsValid, setPaymentDetailsValid] = useState(!!organization.billingConfiguration?.paymentMethod); + const [paymentDetailsValid, setPaymentDetailsValid] = useState( + !!organization.billingConfiguration?.paymentMethod, + ); const upgradeToProMutation = useMutation(UpgradeToProDocument); const downgradeToHobbyMutation = useMutation(DowngradeToHobbyDocument); const updateOrgRateLimitMutation = useMutation(UpdateOrgRateLimitDocument); const planSummaryRef = useRef(null); - const [plan, setPlan] = useState((organization?.plan || 'HOBBY') as BillingPlanType); + const [plan, setPlan] = useState( + (organization?.plan || 'HOBBY') as BillingPlanType, + ); const onPlan = useCallback( (plan: BillingPlanType) => { setPlan(plan); @@ -63,18 +67,18 @@ const Inner = ({ }, 50); } }, - [setPlan, planSummaryRef] + [setPlan, planSummaryRef], ); const [couponCode, setCouponCode] = useState(''); const [operationsRateLimit, setOperationsRateLimit] = useState( - Math.floor((organization.rateLimit.operations || 1_000_000) / 1_000_000) + Math.floor((organization.rateLimit.operations || 1_000_000) / 1_000_000), ); const onOperationsRateLimitChange = useCallback( (limit: number) => { setOperationsRateLimit(limit); }, - [setOperationsRateLimit] + [setOperationsRateLimit], ); useEffect(() => { @@ -82,7 +86,9 @@ const Inner = ({ if (organization.plan !== plan) { const actualPlan = query.data.billingPlans.find(v => v.planType === plan); - setOperationsRateLimit(Math.floor((actualPlan?.includedOperationsLimit || 1_000_000) / 1_000_000)); + setOperationsRateLimit( + Math.floor((actualPlan?.includedOperationsLimit || 1_000_000) / 1_000_000), + ); } else { setOperationsRateLimit(Math.floor((organization.rateLimit.operations || 0) / 1_000_000)); } @@ -194,7 +200,9 @@ const Inner = ({ }; const error = - upgradeToProMutation[0].error || downgradeToHobbyMutation[0].error || updateOrgRateLimitMutation[0].error; + upgradeToProMutation[0].error || + downgradeToHobbyMutation[0].error || + updateOrgRateLimitMutation[0].error; return ( {result => { // TODO: this is also not safe as billingPlans might be an empty list. - const selectedPlan = result.data.billingPlans.find(v => v.planType === plan) ?? result.data.billingPlans[0]; + const selectedPlan = + result.data.billingPlans.find(v => v.planType === plan) ?? result.data.billingPlans[0]; return (
@@ -249,9 +258,12 @@ const Inner = ({ Pro plan requires to defined quota of reported operations.

- Pick a volume a little higher than you think you'll need to avoid being rate limited. + Pick a volume a little higher than you think you'll need to avoid being rate + limited. +

+

+ Don't worry, you can always adjust it later.

-

Don't worry, you can always adjust it later.

diff --git a/packages/web/app/pages/_index.tsx b/packages/web/app/pages/_index.tsx index 0c751c11a..ff0bff247 100644 --- a/packages/web/app/pages/_index.tsx +++ b/packages/web/app/pages/_index.tsx @@ -30,10 +30,16 @@ const IndexPage: FC = () => {
-

{isLoginPage ? 'Log In' : 'Create an account'}

+

+ {isLoginPage ? 'Log In' : 'Create an account'} +

{isLoginPage ? "Don't Have An Account" : 'Already A Member'}?{' '} -

@@ -56,7 +62,11 @@ const IndexPage: FC = () => {
- + Forgot password?
@@ -83,7 +94,10 @@ const IndexPage: FC = () => { ); }; -const GuildLink: FC<{ className?: string; textClassName?: string }> = ({ className, textClassName }) => ( +const GuildLink: FC<{ className?: string; textClassName?: string }> = ({ + className, + textClassName, +}) => ( = ({ classNa title="The Guild website homepage" className={clsx('flex items-center', className)} > - +
- + - + diff --git a/packages/web/app/pages/api/auth/[[...path]].ts b/packages/web/app/pages/api/auth/[[...path]].ts index 65b5a21cd..4e35315d6 100644 --- a/packages/web/app/pages/api/auth/[[...path]].ts +++ b/packages/web/app/pages/api/auth/[[...path]].ts @@ -12,7 +12,10 @@ supertokens.init(backendConfig()); /** * Route for proxying to the underlying SuperTokens backend. */ -export default async function superTokens(req: NextApiRequest & Request, res: NextApiResponse & Response) { +export default async function superTokens( + req: NextApiRequest & Request, + res: NextApiResponse & Response, +) { // NOTE: We need CORS only if we are querying the APIs from a different origin await NextCors(req, res, { methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'], @@ -26,7 +29,7 @@ export default async function superTokens(req: NextApiRequest & Request, res: Ne await middleware()(req, res, next); }, req, - res + res, ); if (!res.writableEnded) { diff --git a/packages/web/app/pages/api/github/callback.ts b/packages/web/app/pages/api/github/callback.ts index aaa19e542..dcb599099 100644 --- a/packages/web/app/pages/api/github/callback.ts +++ b/packages/web/app/pages/api/github/callback.ts @@ -7,7 +7,7 @@ export async function ensureGithubIntegration( input: { installationId: string; orgId: string; - } + }, ) { const { orgId, installationId } = input; await graphql({ diff --git a/packages/web/app/pages/api/proxy.ts b/packages/web/app/pages/api/proxy.ts index e6b28e872..59ca1bf9a 100644 --- a/packages/web/app/pages/api/proxy.ts +++ b/packages/web/app/pages/api/proxy.ts @@ -37,7 +37,9 @@ async function graphql(req: NextApiRequest, res: NextApiResponse) { const url = env.graphqlEndpoint; const requestIdHeader = req.headers['x-request-id']; - const requestId = Array.isArray(requestIdHeader) ? requestIdHeader[0] : requestIdHeader ?? reqIdGenerate(); + const requestId = Array.isArray(requestIdHeader) + ? requestIdHeader[0] + : requestIdHeader ?? reqIdGenerate(); if (req.method === 'GET') { const response = await fetch(url, { diff --git a/packages/web/app/pages/api/slack/callback.ts b/packages/web/app/pages/api/slack/callback.ts index 0595d5f0e..840706992 100644 --- a/packages/web/app/pages/api/slack/callback.ts +++ b/packages/web/app/pages/api/slack/callback.ts @@ -3,7 +3,15 @@ import { stringify } from 'querystring'; import { graphql } from '@/lib/api/utils'; import { env } from '@/env/backend'; -async function fetchData({ url, headers, body }: { url: string; headers: Record; body: string }) { +async function fetchData({ + url, + headers, + body, +}: { + url: string; + headers: Record; + body: string; +}) { const response = await fetch(url, { headers, method: 'POST', diff --git a/packages/web/app/pages/auth/[[...path]].tsx b/packages/web/app/pages/auth/[[...path]].tsx index 103962eb0..c2be51716 100644 --- a/packages/web/app/pages/auth/[[...path]].tsx +++ b/packages/web/app/pages/auth/[[...path]].tsx @@ -31,9 +31,12 @@ export const getServerSideProps: GetServerSideProps = async context => { }; }; -const SuperTokensComponentNoSSR = dynamic(new Promise(res => res(SuperTokensReact.getRoutingComponent)) as any, { - ssr: false, -}); +const SuperTokensComponentNoSSR = dynamic( + new Promise(res => res(SuperTokensReact.getRoutingComponent)) as any, + { + ssr: false, + }, +); /** * Route for showing the SuperTokens login page. @@ -75,7 +78,11 @@ export default function Auth(props: { oidcProviderId: string | null }): React.Re />
- + {env.auth.legacyAuth0 === true ? : null} {props.oidcProviderId ? null : }
diff --git a/packages/web/app/pages/index.tsx b/packages/web/app/pages/index.tsx index 41e169de7..4176ad848 100644 --- a/packages/web/app/pages/index.tsx +++ b/packages/web/app/pages/index.tsx @@ -15,7 +15,10 @@ import { NextApiRequest, NextApiResponse } from 'next'; import Session from 'supertokens-node/recipe/session'; import { writeLastVisitedOrganization } from '@/lib/cookies'; -async function getSuperTokensUserIdFromRequest(req: NextApiRequest, res: NextApiResponse): Promise { +async function getSuperTokensUserIdFromRequest( + req: NextApiRequest, + res: NextApiResponse, +): Promise { const session = await Session.getSession(req, res, { sessionRequired: false }); return session?.getUserId() ?? null; } @@ -63,7 +66,9 @@ function Home(): ReactElement { useEffect(() => { // Just in case server-side redirect wasn't working if (query.data) { - const org = query.data.organizations.nodes.find(node => node.type === OrganizationType.Personal); + const org = query.data.organizations.nodes.find( + node => node.type === OrganizationType.Personal, + ); if (org) { router.visitOrganization({ organizationId: org.cleanId }); } diff --git a/packages/web/app/pages/manage/index.tsx b/packages/web/app/pages/manage/index.tsx index 304d6fe3f..5c949a8ea 100644 --- a/packages/web/app/pages/manage/index.tsx +++ b/packages/web/app/pages/manage/index.tsx @@ -30,7 +30,7 @@ function Manage() { setFilters(newFilters); }, - [setFilters, filters] + [setFilters, filters], ); return ( @@ -42,7 +42,9 @@ function Manage() { !!filters[key as keyof typeof filters])} + defaultValue={Object.keys(filters).filter( + key => !!filters[key as keyof typeof filters], + )} onChange={onFiltersChange} > @@ -66,7 +68,10 @@ function Manage() { With Collected - +
Manage OpenID Connect Integration -

You are trying to update an OpenID Connect integration for an organization that has no integration.

+

+ You are trying to update an OpenID Connect integration for an organization that has no + integration. +

-
@@ -305,7 +324,9 @@ const UpdateOIDCIntegration_OIDCIntegrationFragment = gql(/* GraphQL */ ` `); const UpdateOIDCIntegrationForm_UpdateOIDCIntegrationMutation = gql(/* GraphQL */ ` - mutation UpdateOIDCIntegrationForm_UpdateOIDCIntegrationMutation($input: UpdateOIDCIntegrationInput!) { + mutation UpdateOIDCIntegrationForm_UpdateOIDCIntegrationMutation( + $input: UpdateOIDCIntegrationInput! + ) { updateOIDCIntegration(input: $input) { ok { updatedOIDCIntegration { @@ -375,11 +396,14 @@ const UpdateOIDCIntegrationForm = (props: {
  • - Set your OIDC Provider Sign-out redirect URI to + Set your OIDC Provider Sign-out redirect URI to{' '} +
  • Your users can login to the organization via{' '} - +
  • @@ -411,7 +435,7 @@ const UpdateOIDCIntegrationForm = (props: { placeholder={ 'Keep old value. (Ending with ' + props.oidcIntegration.clientSecretPreview.substring( - props.oidcIntegration.clientSecretPreview.length - 4 + props.oidcIntegration.clientSecretPreview.length - 4, ) + ')' } @@ -439,7 +463,9 @@ const UpdateOIDCIntegrationForm = (props: { }; const RemoveOIDCIntegrationForm_DeleteOIDCIntegrationMutation = gql(/* GraphQL */ ` - mutation RemoveOIDCIntegrationForm_DeleteOIDCIntegrationMutation($input: DeleteOIDCIntegrationInput!) { + mutation RemoveOIDCIntegrationForm_DeleteOIDCIntegrationMutation( + $input: DeleteOIDCIntegrationInput! + ) { deleteOIDCIntegration(input: $input) { ok { organization { @@ -488,8 +514,8 @@ const RemoveOIDCIntegrationModal = (props: {

    - This action is not reversible and deletes all users that have signed in with this OIDC - integration. + This action is not reversible and deletes all users that have signed in with + this OIDC integration.

    Do you really want to proceed?

    diff --git a/packages/web/app/src/components/project/Switcher.tsx b/packages/web/app/src/components/project/Switcher.tsx index c13113f29..c353c02d9 100644 --- a/packages/web/app/src/components/project/Switcher.tsx +++ b/packages/web/app/src/components/project/Switcher.tsx @@ -43,7 +43,13 @@ export const ProjectSwitcher: React.FC<{ return ( - } variant="ghost" tw="font-normal"> + } + variant="ghost" + tw="font-normal" + > {refinedData.currentProject.name} diff --git a/packages/web/app/src/components/project/settings/external-composition.tsx b/packages/web/app/src/components/project/settings/external-composition.tsx index 3586f9b2d..c89989924 100644 --- a/packages/web/app/src/components/project/settings/external-composition.tsx +++ b/packages/web/app/src/components/project/settings/external-composition.tsx @@ -35,30 +35,31 @@ const ExternalCompositionForm = ({ }) => { const notify = useNotifications(); const [mutation, enable] = useMutation(ExternalCompositionForm_EnableMutation); - const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = useFormik({ - enableReinitialize: true, - initialValues: { - endpoint: endpoint ?? '', - secret: '', - }, - validationSchema: Yup.object().shape({ - endpoint: Yup.string().required(), - secret: Yup.string().required(), - }), - onSubmit: values => - enable({ - input: { - project: project.cleanId, - organization: organization.cleanId, - endpoint: values.endpoint, - secret: values.secret, - }, - }).then(result => { - if (result.data?.enableExternalSchemaComposition?.ok) { - notify('External composition enabled', 'success'); - } + const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = + useFormik({ + enableReinitialize: true, + initialValues: { + endpoint: endpoint ?? '', + secret: '', + }, + validationSchema: Yup.object().shape({ + endpoint: Yup.string().required(), + secret: Yup.string().required(), }), - }); + onSubmit: values => + enable({ + input: { + project: project.cleanId, + organization: organization.cleanId, + endpoint: values.endpoint, + secret: values.secret, + }, + }).then(result => { + if (result.data?.enableExternalSchemaComposition?.ok) { + notify('External composition enabled', 'success'); + } + }), + }); const mutationError = mutation.data?.enableExternalSchemaComposition.error; @@ -78,13 +79,17 @@ const ExternalCompositionForm = ({ className="w-96" /> {touched.endpoint && (errors.endpoint || mutationError?.inputErrors?.endpoint) && ( -
    {errors.endpoint ?? mutationError?.inputErrors?.endpoint}
    +
    + {errors.endpoint ?? mutationError?.inputErrors?.endpoint} +
    )}
    Secret -

    The secret is needed to sign and verify the request.

    +

    + The secret is needed to sign and verify the request. +

    {touched.secret && (errors.secret || mutationError?.inputErrors?.secret) && ( -
    {errors.secret ?? mutationError?.inputErrors?.secret}
    +
    + {errors.secret ?? mutationError?.inputErrors?.secret} +
    )}
    {mutation.error &&
    {mutation.error}
    }
    -
    @@ -163,7 +176,8 @@ export const ExternalCompositionSettings = ({ organization: organization.cleanId, }, }).then(result => { - const error = result.error?.message || result.data?.disableExternalSchemaComposition.error; + const error = + result.error?.message || result.data?.disableExternalSchemaComposition.error; if (error) { notify(error, 'error'); // fallback to the previous state @@ -172,7 +186,7 @@ export const ExternalCompositionSettings = ({ }); } }, - [disableComposition, setEnabled, notify] + [disableComposition, setEnabled, notify], ); const externalCompositionConfig = projectQuery.data?.project?.externalSchemaComposition; @@ -198,7 +212,9 @@ export const ExternalCompositionSettings = ({ )}
    -

    Compose and validate schema outside GraphQL Hive

    +

    + Compose and validate schema outside GraphQL Hive +

    {isFormVisible ? ( - } variant="ghost" tw="font-normal"> + } + variant="ghost" + tw="font-normal" + > {currentTarget.name} diff --git a/packages/web/app/src/components/target/explorer/common.tsx b/packages/web/app/src/components/target/explorer/common.tsx index d4b21c296..21b3b9fa3 100644 --- a/packages/web/app/src/components/target/explorer/common.tsx +++ b/packages/web/app/src/components/target/explorer/common.tsx @@ -11,7 +11,9 @@ import { formatNumber } from '@/lib/hooks/use-formatted-number'; import { useArgumentListToggle } from './provider'; function useCollapsibleList(list: T[], max: number, defaultValue: boolean) { - const [collapsed, setCollapsed] = React.useState(defaultValue === true && list.length > max ? true : false); + const [collapsed, setCollapsed] = React.useState( + defaultValue === true && list.length > max ? true : false, + ); const expand = React.useCallback(() => { setCollapsed(false); }, [setCollapsed]); @@ -28,7 +30,10 @@ function Description(props: { description: string }) { return ( - @@ -136,7 +141,7 @@ export function GraphQLTypeCard( implements?: string[]; totalRequests?: number; usage?: DocumentType; - }> + }>, ) { return (
    @@ -167,7 +172,9 @@ export function GraphQLTypeCard( ); } -export function GraphQLArguments(props: { args: DocumentType[] }) { +export function GraphQLArguments(props: { + args: DocumentType[]; +}) { const { args } = props; const [isCollapsedGlobally] = useArgumentListToggle(); const [collapsed, setCollapsed] = React.useState(isCollapsedGlobally); @@ -231,7 +238,7 @@ export function GraphQLTypeCardListItem( index: number; className?: string; onClick?: () => void; - }> + }>, ) { return (
    {props.children} @@ -313,7 +320,10 @@ export function GraphQLTypeAsLink(props: { type: string }) { const typename = props.type.replace(/[[\]!]+/g, ''); return ( - + {props.type} ); diff --git a/packages/web/app/src/components/target/explorer/filter.tsx b/packages/web/app/src/components/target/explorer/filter.tsx index 54f12e51c..c9ba1376c 100644 --- a/packages/web/app/src/components/target/explorer/filter.tsx +++ b/packages/web/app/src/components/target/explorer/filter.tsx @@ -7,7 +7,12 @@ import { Switch } from '@/components/v2/switch'; import { useArgumentListToggle, usePeriodSelector } from './provider'; const SchemaExplorerFilter_AllTypes = gql(/* GraphQL */ ` - query SchemaExplorerFilter_AllTypes($organization: ID!, $project: ID!, $target: ID!, $period: DateRangeInput!) { + query SchemaExplorerFilter_AllTypes( + $organization: ID! + $project: ID! + $target: ID! + $period: DateRangeInput! + ) { target(selector: { organization: $organization, project: $project, target: $target }) { __typename id @@ -99,7 +104,9 @@ export function SchemaExplorerFilter({ defaultValue={typename ? { value: typename, label: typename } : null} options={types} onChange={option => { - router.push(`/${organization.cleanId}/${project.cleanId}/${target.cleanId}/explorer/${option.value}`); + router.push( + `/${organization.cleanId}/${project.cleanId}/${target.cleanId}/explorer/${option.value}`, + ); }} loading={query.fetching} /> diff --git a/packages/web/app/src/components/target/explorer/object-type.tsx b/packages/web/app/src/components/target/explorer/object-type.tsx index d95f6c6ec..a4987401c 100644 --- a/packages/web/app/src/components/target/explorer/object-type.tsx +++ b/packages/web/app/src/components/target/explorer/object-type.tsx @@ -27,7 +27,11 @@ export function GraphQLObjectTypeComponent(props: { description={props.type.description} implements={props.type.interfaces} > - + ); } diff --git a/packages/web/app/src/components/target/explorer/provider.tsx b/packages/web/app/src/components/target/explorer/provider.tsx index d6d984ef7..8aa354a0f 100644 --- a/packages/web/app/src/components/target/explorer/provider.tsx +++ b/packages/web/app/src/components/target/explorer/provider.tsx @@ -42,11 +42,17 @@ const SchemaExplorerContext = React.createContext<{ export function SchemaExplorerProvider( props: React.PropsWithChildren<{ dataRetentionInDays: number; - }> + }>, ) { const { dataRetentionInDays } = props; - const [isArgumentListCollapsed, setArgumentListCollapsed] = useLocalStorage('hive:schema-explorer:collapsed', true); - const [periodOption, setPeriodOption] = useLocalStorage('hive:schema-explorer:period', '30d'); + const [isArgumentListCollapsed, setArgumentListCollapsed] = useLocalStorage( + 'hive:schema-explorer:collapsed', + true, + ); + const [periodOption, setPeriodOption] = useLocalStorage( + 'hive:schema-explorer:period', + '30d', + ); const [period, setPeriod] = React.useState(createPeriod(periodOption)); React.useEffect(() => { @@ -61,7 +67,7 @@ export function SchemaExplorerProvider( setPeriodOption(option); setPeriod(createPeriod(option)); }, - [setPeriodOption, setPeriod] + [setPeriodOption, setPeriod], ); const availablePeriodOptions = React.useMemo(() => { diff --git a/packages/web/app/src/components/target/explorer/scalar-type.tsx b/packages/web/app/src/components/target/explorer/scalar-type.tsx index 83e77d4a5..ae6ac9750 100644 --- a/packages/web/app/src/components/target/explorer/scalar-type.tsx +++ b/packages/web/app/src/components/target/explorer/scalar-type.tsx @@ -20,7 +20,9 @@ export function GraphQLScalarTypeComponent(props: {
    - {typeof props.type.description === 'string' ? : null} + {typeof props.type.description === 'string' ? ( + + ) : null}
    diff --git a/packages/web/app/src/components/target/operations/Fallback.tsx b/packages/web/app/src/components/target/operations/Fallback.tsx index 1795feb9b..8f0250d0b 100644 --- a/packages/web/app/src/components/target/operations/Fallback.tsx +++ b/packages/web/app/src/components/target/operations/Fallback.tsx @@ -10,7 +10,9 @@ export const OperationsFallback = ({ }: PropsWithChildren<{ isError: boolean; isFetching?: boolean; refetch: () => void }>) => { return (
    -
    {children}
    +
    + {children} +
    {isError ? (
    diff --git a/packages/web/app/src/components/v2/modals/connect-schema.tsx b/packages/web/app/src/components/v2/modals/connect-schema.tsx index 5ab93304b..f72ec6f58 100644 --- a/packages/web/app/src/components/v2/modals/connect-schema.tsx +++ b/packages/web/app/src/components/v2/modals/connect-schema.tsx @@ -70,13 +70,16 @@ export const ConnectSchemaModal = ({ {project && !generating && mutation.data && ( <>

    - With high-availability and multi-zone CDN service based on Cloudflare, Hive allows you to access the - {taxonomy[project.type] ?? 'schema'} of your API, through a secured external service, that's always up - regardless of Hive. + With high-availability and multi-zone CDN service based on Cloudflare, Hive allows you + to access the + {taxonomy[project.type] ?? 'schema'} of your API, through a secured external service, + that's always up regardless of Hive.

    You can use the following endpoint: - To authenticate, use the following HTTP headers: + + To authenticate, use the following HTTP headers: + X-Hive-CDN-Key: {mutation.data.createCdnToken.token}

    Read the{' '} @@ -92,7 +95,13 @@ export const ConnectSchemaModal = ({

    )} - diff --git a/packages/web/app/src/components/v2/modals/create-access-token.tsx b/packages/web/app/src/components/v2/modals/create-access-token.tsx index 24343f797..7d6954750 100644 --- a/packages/web/app/src/components/v2/modals/create-access-token.tsx +++ b/packages/web/app/src/components/v2/modals/create-access-token.tsx @@ -74,25 +74,26 @@ const ModalContent = (props: { }) => { const [mutation, mutate] = useMutation(CreateAccessToken_CreateTokenMutation); - const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = useFormik({ - initialValues: { name: '' }, - validationSchema: Yup.object().shape({ - name: Yup.string().required('Must enter name'), - }), - async onSubmit(values) { - await mutate({ - input: { - organization: props.organizationId, - project: props.projectId, - target: props.targetId, - name: values.name, - organizationScopes: manager.organizationScopes, - projectScopes: manager.projectScopes, - targetScopes: manager.targetScopes, - }, - }); - }, - }); + const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = + useFormik({ + initialValues: { name: '' }, + validationSchema: Yup.object().shape({ + name: Yup.string().required('Must enter name'), + }), + async onSubmit(values) { + await mutate({ + input: { + organization: props.organizationId, + project: props.projectId, + target: props.targetId, + name: values.name, + organizationScopes: manager.organizationScopes, + projectScopes: manager.projectScopes, + targetScopes: manager.targetScopes, + }, + }); + }, + }); const manager = usePermissionsManager({ onSuccess() {}, @@ -108,10 +109,15 @@ const ModalContent = (props: { Token successfully created! - This is your unique API key and it is non-recoverable. If you lose this key, you will need to create a new - one. + This is your unique API key and it is non-recoverable. If you lose this key, you will + need to create a new one. -
    diff --git a/packages/web/app/src/components/v2/modals/create-alert.tsx b/packages/web/app/src/components/v2/modals/create-alert.tsx index a8fb8e867..0cf1e73bf 100644 --- a/packages/web/app/src/components/v2/modals/create-alert.tsx +++ b/packages/web/app/src/components/v2/modals/create-alert.tsx @@ -52,13 +52,13 @@ export const CreateAlertModal = ({ Yup.string() .min(1) .equals(channels.map(channel => channel.id)) - .required('Must select channel') + .required('Must select channel'), ), target: Yup.lazy(() => Yup.string() .min(1) .equals(targets.map(target => target.cleanId)) - .required('Must select target') + .required('Must select target'), ), }), async onSubmit(values) { @@ -117,7 +117,9 @@ export const CreateAlertModal = ({ onChange={handleChange} isInvalid={!!touched.channel && Boolean(errors.channel)} /> - {touched.channel && errors.channel &&
    {errors.channel}
    } + {touched.channel && errors.channel && ( +
    {errors.channel}
    + )}
    @@ -135,7 +137,9 @@ export const CreateAlertModal = ({ onChange={handleChange} isInvalid={!!touched.target && Boolean(errors.target)} /> - {touched.target && errors.target &&
    {errors.target}
    } + {touched.target && errors.target && ( +
    {errors.target}
    + )}
    {mutation.error &&
    {mutation.error.message}
    } diff --git a/packages/web/app/src/components/v2/modals/create-channel.tsx b/packages/web/app/src/components/v2/modals/create-channel.tsx index 714d44508..ea4201d8a 100644 --- a/packages/web/app/src/components/v2/modals/create-channel.tsx +++ b/packages/web/app/src/components/v2/modals/create-channel.tsx @@ -37,45 +37,47 @@ export const CreateChannelModal = ({ }): ReactElement => { const router = useRouteSelector(); const [mutation, mutate] = useMutation(CreateChannel_AddAlertChannelMutation); - const { errors, values, touched, handleChange, handleBlur, handleSubmit, isSubmitting } = useFormik({ - initialValues: { - name: '', - type: '' as AlertChannelType, - slackChannel: '', - endpoint: '', - }, - validationSchema: Yup.object().shape({ - name: Yup.string().required('Must enter name'), - type: Yup.mixed().oneOf(Object.values(AlertChannelType)).required('Must select type'), - slackChannel: Yup.string() - .matches(/^[@#]{1}/, 'Must start with a @ or # character') - .when('type', { - is: AlertChannelType.Slack, - then: Yup.string().required('Must enter slack channel'), - }), - endpoint: Yup.string() - .url() - .when('type', { - is: AlertChannelType.Webhook, - then: Yup.string().required('Must enter endpoint'), - }), - }), - async onSubmit(values) { - const { data } = await mutate({ - input: { - organization: router.organizationId, - project: router.projectId, - name: values.name, - type: values.type, - slack: values.type === AlertChannelType.Slack ? { channel: values.slackChannel } : null, - webhook: values.type === AlertChannelType.Webhook ? { endpoint: values.endpoint } : null, - }, - }); - if (data?.addAlertChannel.ok) { - toggleModalOpen(); - } - }, - }); + const { errors, values, touched, handleChange, handleBlur, handleSubmit, isSubmitting } = + useFormik({ + initialValues: { + name: '', + type: '' as AlertChannelType, + slackChannel: '', + endpoint: '', + }, + validationSchema: Yup.object().shape({ + name: Yup.string().required('Must enter name'), + type: Yup.mixed().oneOf(Object.values(AlertChannelType)).required('Must select type'), + slackChannel: Yup.string() + .matches(/^[@#]{1}/, 'Must start with a @ or # character') + .when('type', { + is: AlertChannelType.Slack, + then: Yup.string().required('Must enter slack channel'), + }), + endpoint: Yup.string() + .url() + .when('type', { + is: AlertChannelType.Webhook, + then: Yup.string().required('Must enter endpoint'), + }), + }), + async onSubmit(values) { + const { data } = await mutate({ + input: { + organization: router.organizationId, + project: router.projectId, + name: values.name, + type: values.type, + slack: values.type === AlertChannelType.Slack ? { channel: values.slackChannel } : null, + webhook: + values.type === AlertChannelType.Webhook ? { endpoint: values.endpoint } : null, + }, + }); + if (data?.addAlertChannel.ok) { + toggleModalOpen(); + } + }, + }); return ( @@ -97,7 +99,9 @@ export const CreateChannelModal = ({ /> {touched.name && errors.name &&
    {errors.name}
    } {mutation.data?.addAlertChannel.error?.inputErrors.name && ( -
    {mutation.data.addAlertChannel.error.inputErrors.name}
    +
    + {mutation.data.addAlertChannel.error.inputErrors.name} +
    )}

    This will be displayed on channels list, we recommend to make it self-explanatory. @@ -138,7 +142,9 @@ export const CreateChannelModal = ({ isInvalid={touched.endpoint && Boolean(errors.endpoint)} className="grow" /> - {touched.endpoint && errors.endpoint &&

    {errors.endpoint}
    } + {touched.endpoint && errors.endpoint && ( +
    {errors.endpoint}
    + )} {mutation.data?.addAlertChannel.error?.inputErrors.webhookEndpoint && (
    {mutation.data.addAlertChannel.error.inputErrors.webhookEndpoint} @@ -167,7 +173,9 @@ export const CreateChannelModal = ({
    {errors.slackChannel}
    )} {mutation.data?.addAlertChannel.error?.inputErrors.slackChannel && ( -
    {mutation.data.addAlertChannel.error.inputErrors.slackChannel}
    +
    + {mutation.data.addAlertChannel.error.inputErrors.slackChannel} +
    )}

    Use #channel or @username form. diff --git a/packages/web/app/src/components/v2/modals/create-organization.tsx b/packages/web/app/src/components/v2/modals/create-organization.tsx index 3386142e0..755cee83c 100644 --- a/packages/web/app/src/components/v2/modals/create-organization.tsx +++ b/packages/web/app/src/components/v2/modals/create-organization.tsx @@ -39,32 +39,35 @@ export const CreateOrganizationModal = ({ const [mutation, mutate] = useMutation(CreateOrganizationMutation); const { push } = useRouter(); - const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = useFormik({ - initialValues: { name: '' }, - validationSchema: Yup.object().shape({ - name: Yup.string().required('Organization name is required'), - }), - async onSubmit(values) { - const mutation = await mutate({ - input: { - name: values.name, - }, - }); + const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } = + useFormik({ + initialValues: { name: '' }, + validationSchema: Yup.object().shape({ + name: Yup.string().required('Organization name is required'), + }), + async onSubmit(values) { + const mutation = await mutate({ + input: { + name: values.name, + }, + }); - if (mutation.data?.createOrganization.ok) { - toggleModalOpen(); - push(`/${mutation.data.createOrganization.ok.createdOrganizationPayload.organization.cleanId}`); - } - }, - }); + if (mutation.data?.createOrganization.ok) { + toggleModalOpen(); + push( + `/${mutation.data.createOrganization.ok.createdOrganizationPayload.organization.cleanId}`, + ); + } + }, + }); return (

    Create an organization

    - An organization is built on top of Projects. You will become an admin and don't worry, you can - add members later. + An organization is built on top of Projects. You will become an admin and + don't worry, you can add members later.

    {errors.name || mutation.error?.message}
    )} {mutation.data?.createOrganization.error?.inputErrors.name && ( -
    {mutation.data.createOrganization.error.inputErrors.name}
    +
    + {mutation.data.createOrganization.error.inputErrors.name} +
    )}