[
  {
    "path": ".credo.exs",
    "content": "%{\n  configs: [\n    %{\n      name: \"default\",\n      files: %{\n        included: [\"lib/\", \"src/\", \"web/\", \"apps/\"],\n        excluded: []\n      },\n      plugins: [],\n      requires: [],\n      strict: false,\n      parse_timeout: 5000,\n      color: true,\n      checks: %{\n        disabled: [\n          {Credo.Check.Design.TagTODO, []},\n          {Credo.Check.Consistency.ExceptionNames, []},\n          {Credo.Check.Refactor.Nesting, []},\n          {Credo.Check.Refactor.CyclomaticComplexity, []},\n          {Credo.Check.Readability.WithSingleClause, []},\n          {Credo.Check.Readability.AliasOrder, []},\n          {Credo.Check.Readability.StringSigils, []},\n          {Credo.Check.Refactor.Apply, []}\n        ]\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": ".dockerignore",
    "content": "# This file excludes paths from the Docker build context.\n#\n# By default, Docker's build context includes all files (and folders) in the\n# current directory. Even if a file isn't copied into the container it is still sent to\n# the Docker daemon.\n#\n# There are multiple reasons to exclude files from the build context:\n#\n# 1. Prevent nested folders from being copied into the container (ex: exclude\n#    /assets/node_modules when copying /assets)\n# 2. Reduce the size of the build context and improve build time (ex. /build, /deps, /doc)\n# 3. Avoid sending files containing sensitive information\n#\n# More information on using .dockerignore is available here:\n# https://docs.docker.com/engine/reference/builder/#dockerignore-file\n\n.dockerignore\n\n# Ignore git, but keep git HEAD and refs to access current commit hash if needed:\n#\n# $ cat .git/HEAD | awk '{print \".git/\"$2}' | xargs cat\n# d0b8727759e1e0e7aa3d41707d12376e373d5ecc\n.git\n!.git/HEAD\n!.git/refs\n\n# Common development/test artifacts\n/cover/\n/doc/\n/test/\n/tmp/\n.elixir_ls\n\n# Mix artifacts\n/_build/\n/deps/\n*.ez\n\n# Generated on crash by the VM\nerl_crash.dump\n\n# Static artifacts - These should be fetched and built inside the Docker image\n/assets/node_modules/\n/priv/static/assets/\n/priv/static/cache_manifest.json\n"
  },
  {
    "path": ".formatter.exs",
    "content": "[\n  import_deps: [:ecto, :ecto_sql, :phoenix, :open_api_spex],\n  subdirectories: [\"priv/*/migrations\"],\n  plugins: [],\n  inputs: [\"*.{heex,ex,exs}\", \"{config,lib,test}/**/*.{heex,ex,exs}\", \"priv/*/*seeds*.exs\"],\n  line_length: 120\n]\n"
  },
  {
    "path": ".github/actionlint.yaml",
    "content": "self-hosted-runner:\n  labels:\n    - blacksmith-4vcpu-ubuntu-2404\n    - blacksmith-8vcpu-ubuntu-2404\n"
  },
  {
    "path": ".github/workflows/beacon_tests.yml",
    "content": "name: Beacon Tests\ndefaults:\n  run:\n    shell: bash\n    working-directory: ./beacon\non:\n  pull_request:\n    paths:\n      - \"beacon/**\"\n      - \".github/workflows/beacon_tests.yml\"\n\n  push:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\nenv:\n  MIX_ENV: test\n\njobs:\n  tests:\n    name: Tests & Lint\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: Setup elixir\n        id: beam\n        uses: erlef/setup-beam@v1\n        with:\n          otp-version: 27.x # Define the OTP version [required]\n          elixir-version: 1.18.x # Define the elixir version [required]\n      - name: Cache Mix\n        uses: actions/cache@v5\n        with:\n          path: |\n            beacon/deps\n            beacon/_build\n          key: ${{ github.workflow }}-${{ runner.os }}-mix-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-${{ hashFiles('beacon/mix.lock') }}\n          restore-keys: |\n            ${{ github.workflow }}-${{ runner.os }}-mix-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-\n      - name: Install dependencies\n        run: mix deps.get\n      - name: Start epmd\n        run: epmd -daemon\n      - name: Run tests\n        run: MIX_ENV=test mix test\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Check for warnings\n        run: mix compile --force --warnings-as-errors\n      - name: Run format check\n        run: mix format --check-formatted\n"
  },
  {
    "path": ".github/workflows/docker-build.yml",
    "content": "name: Docker Build\n\non:\n  pull_request:\n    branches:\n      - main\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Build Docker image\n        run: docker build .\n"
  },
  {
    "path": ".github/workflows/integration_tests.yml",
    "content": "name: Integration Tests\non:\n  pull_request:\n    paths:\n      - \"lib/**\"\n      - \"test/**\"\n      - \"config/**\"\n      - \"priv/**\"\n      - \"assets/**\"\n      - \"rel/**\"\n      - \"mix.exs\"\n      - \"Dockerfile\"\n      - \"run.sh\"\n      - \"docker-compose.test.yml\"\n      - \".github/workflows/integration_tests.yml\"\n\n  push:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\nenv:\n  POSTGRES_IMAGE: supabase/postgres:17.6.1.074\n  DENO_IMAGE: denoland/deno:alpine-2.5.6\n\njobs:\n  tests:\n    name: Tests\n    runs-on: blacksmith-8vcpu-ubuntu-2404\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: Cache Docker images\n        uses: actions/cache@v5\n        id: docker-cache\n        with:\n          path: /tmp/docker-images\n          key: docker-images-integration-zstd-${{ env.POSTGRES_IMAGE }}-${{ env.DENO_IMAGE }}\n      - name: Load Docker images from cache\n        if: steps.docker-cache.outputs.cache-hit == 'true'\n        run: |\n          zstd -d --stdout /tmp/docker-images/postgres.tar.zst | docker image load &\n          PID1=$!\n          zstd -d --stdout /tmp/docker-images/deno.tar.zst | docker image load &\n          PID2=$!\n          wait $PID1 || exit $?\n          wait $PID2 || exit $?\n      - name: Pull and save Docker images\n        if: steps.docker-cache.outputs.cache-hit != 'true'\n        run: |\n          docker pull ${{ env.POSTGRES_IMAGE }} &\n          PID1=$!\n          docker pull ${{ env.DENO_IMAGE }} &\n          PID2=$!\n          wait $PID1 || exit $?\n          wait $PID2 || exit $?\n          mkdir -p /tmp/docker-images\n          docker image save ${{ env.POSTGRES_IMAGE }} | zstd -T0 -o /tmp/docker-images/postgres.tar.zst\n          docker image save ${{ env.DENO_IMAGE }} | zstd -T0 -o /tmp/docker-images/deno.tar.zst\n      - name: Run integration test\n        run: docker compose -f docker-compose.tests.yml up --abort-on-container-exit --exit-code-from test-runner\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\non:\n  pull_request:\n    paths:\n      - \"lib/**\"\n      - \"test/**\"\n      - \"config/**\"\n      - \"priv/**\"\n      - \"assets/**\"\n      - \"rel/**\"\n      - \"mix.exs\"\n      - \"Dockerfile\"\n      - \"run.sh\"\n      - \".github/workflows/lint.yml\"\n\n  push:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  tests:\n    name: Lint\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: Setup elixir\n        id: beam\n        uses: erlef/setup-beam@v1\n        with:\n          otp-version: 27.x # Define the OTP version [required]\n          elixir-version: 1.18.x # Define the elixir version [required]\n      - name: Cache Mix\n        uses: actions/cache@v5\n        with:\n          path: |\n            deps\n            _build\n          key: ${{ github.workflow }}-${{ runner.os }}-mix-${{ env.elixir }}-${{ env.otp }}-${{ hashFiles('**/mix.lock') }}\n          restore-keys: |\n            ${{ github.workflow }}-${{ runner.os }}-mix-${{ env.elixir }}-${{ env.otp }}-\n\n      - name: Install dependencies\n        run: mix deps.get\n      - name: Check for warnings\n        run: mix compile --force --warnings-as-errors\n      - name: Run format check\n        run: mix format --check-formatted\n      - name: Credo checks\n        run: mix credo\n      - name: Run hex audit\n        run: mix hex.audit\n      - name: Run mix_audit\n        run: mix deps.audit\n      - name: Run sobelow\n        run: mix sobelow --config .sobelow-conf\n      - name: Retrieve PLT Cache\n        uses: actions/cache@v5\n        id: plt-cache\n        with:\n          path: priv/plts\n          key: ${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-plts-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}\n      - name: Create PLTs\n        if: steps.plt-cache.outputs.cache-hit != 'true'\n        run: |\n          mkdir -p priv/plts\n          mix dialyzer.build\n      - name: Run dialyzer\n        run: mix dialyzer\n"
  },
  {
    "path": ".github/workflows/manual_prod_build.yml",
    "content": "name: Manual Build Production\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: \"Branch to run the workflow\"\n        required: true\n      docker_tag:\n        description: \"Tag to be used by the docker image on push\"\n        required: true\njobs:\n  docker_x86_release:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 120\n    env:\n      arch: amd64\n    outputs:\n      image_digest: ${{ steps.build.outputs.digest }}\n    steps:\n      - id: meta\n        uses: docker/metadata-action@v5\n        with:\n          images: |\n            supabase/realtime\n          tags: |\n            type=raw,value=v${{ github.event.inputs.docker_tag }}_${{ env.arch }}\n\n      - name: Setup Blacksmith Builder\n        uses: useblacksmith/setup-docker-builder@v1\n\n      - uses: docker/login-action@v3\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n\n      - id: build\n        uses: useblacksmith/build-push-action@v2\n        with:\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          platforms: linux/${{ env.arch }}\n\n  docker_arm_release:\n    runs-on: arm-runner\n    timeout-minutes: 120\n    env:\n      arch: arm64\n    outputs:\n      image_digest: ${{ steps.build.outputs.digest }}\n    steps:\n      - uses: actions/checkout@v6\n\n      - id: meta\n        uses: docker/metadata-action@v5\n        with:\n          images: |\n            supabase/realtime\n          tags: |\n            type=raw,value=v${{ github.event.inputs.docker_tag }}_${{ env.arch }}\n\n      - uses: docker/login-action@v3\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n\n      - name: Setup Blacksmith Builder\n        uses: useblacksmith/setup-docker-builder@v1\n\n      - id: build\n        uses: useblacksmith/build-push-action@v2\n        with:\n          context: .\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          platforms: linux/${{ env.arch }}\n          no-cache: true\n\n  merge_manifest:\n    needs: [docker_x86_release, docker_arm_release]\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    permissions:\n      contents: read\n      packages: write\n      id-token: write\n    steps:\n      - name: Setup Blacksmith Builder\n        uses: useblacksmith/setup-docker-builder@v1\n\n      - uses: docker/login-action@v3\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n\n      - name: Merge multi-arch manifests for custom output\n        run: |\n          docker buildx imagetools create -t supabase/realtime:v${{ github.event.inputs.docker_tag }} \\\n          supabase/realtime@${{ needs.docker_x86_release.outputs.image_digest }} \\\n          supabase/realtime@${{ needs.docker_arm_release.outputs.image_digest }}\n\n      - name: configure aws credentials\n        uses: aws-actions/configure-aws-credentials@v5\n        with:\n          role-to-assume: ${{ secrets.PROD_AWS_ROLE }}\n          aws-region: us-east-1\n\n      - name: Login to ECR\n        uses: docker/login-action@v3\n        with:\n          registry: public.ecr.aws\n\n      - name: Login to GHCR\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Mirror to ECR\n        uses: akhilerm/tag-push-action@v2.2.0\n        with:\n          src: docker.io/supabase/realtime:v${{ github.event.inputs.docker_tag }}\n          dst: |\n            public.ecr.aws/supabase/realtime:v${{ github.event.inputs.docker_tag }}\n            ghcr.io/supabase/realtime:v${{ github.event.inputs.docker_tag }}\n\n"
  },
  {
    "path": ".github/workflows/mirror.yml",
    "content": "name: Mirror Image\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: \"Image tag\"\n        required: true\n        type: string\n\njobs:\n  mirror:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    permissions:\n      contents: read\n      packages: write\n      id-token: write\n    steps:\n      - name: configure aws credentials\n        uses: aws-actions/configure-aws-credentials@v5\n        with:\n          role-to-assume: ${{ secrets.PROD_AWS_ROLE }}\n          aws-region: us-east-1\n      - uses: docker/login-action@v3\n        with:\n          registry: public.ecr.aws\n      - uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n      - uses: akhilerm/tag-push-action@v2.2.0\n        with:\n          src: docker.io/supabase/realtime:${{ inputs.version }}\n          dst: |\n            public.ecr.aws/supabase/realtime:${{ inputs.version }}\n            ghcr.io/supabase/realtime:${{ inputs.version }}\n"
  },
  {
    "path": ".github/workflows/prod_build.yml",
    "content": "name: Build Production\non:\n  push:\n    branches:\n      - \"main\"\n    paths:\n      - \"lib/**\"\n      - \"config/**\"\n      - \"priv/**\"\n      - \"assets/**\"\n      - \"rel/**\"\n      - \"mix.exs\"\n      - \"Dockerfile\"\n      - \"run.sh\"\n      - \".github/workflows/prod_build.yml\"\n\njobs:\n  release:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    outputs:\n      published: ${{ steps.semantic.outputs.new_release_published }}\n      version: ${{ steps.semantic.outputs.new_release_version }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          persist-credentials: false\n\n      - id: semantic\n        uses: cycjimmy/semantic-release-action@v6\n        with:\n          semantic_version: 24\n          extra_plugins: |\n            @semantic-release/exec\n            @semantic-release/git\n        env:\n          GITHUB_TOKEN: ${{ secrets.GH_TOKEN_PROJECT_ACTION }}\n\n  docker_x86_release:\n    needs: release\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    if: needs.release.outputs.published == 'true'\n    timeout-minutes: 120\n    env:\n      arch: amd64\n    outputs:\n      image_digest: ${{ steps.build.outputs.digest }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          ref: v${{ needs.release.outputs.version }}\n\n      - id: meta\n        uses: docker/metadata-action@v5\n        with:\n          images: |\n            supabase/realtime\n          tags: |\n            type=raw,value=v${{ needs.release.outputs.version }}_${{ env.arch }}\n            type=raw,value=latest_${{ env.arch }}\n\n      - name: Setup Blacksmith Builder\n        uses: useblacksmith/setup-docker-builder@v1\n\n      - uses: docker/login-action@v3\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n\n      - id: build\n        uses: useblacksmith/build-push-action@v2\n        with:\n          context: .\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          platforms: linux/${{ env.arch }}\n\n  docker_arm_release:\n    needs: release\n    runs-on: arm-runner\n    if: needs.release.outputs.published == 'true'\n    timeout-minutes: 120\n    env:\n      arch: arm64\n    outputs:\n      image_digest: ${{ steps.build.outputs.digest }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          ref: v${{ needs.release.outputs.version }}\n\n      - id: meta\n        uses: docker/metadata-action@v5\n        with:\n          images: |\n            supabase/realtime\n          tags: |\n            type=raw,value=v${{ needs.release.outputs.version }}_${{ env.arch }}\n            type=raw,value=latest_${{ env.arch }}\n\n      - uses: docker/login-action@v3\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n\n      - name: Setup Blacksmith Builder\n        uses: useblacksmith/setup-docker-builder@v1\n\n      - id: build\n        uses: useblacksmith/build-push-action@v2\n        with:\n          context: .\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          platforms: linux/${{ env.arch }}\n          no-cache: true\n\n  merge_manifest:\n    needs: [release, docker_x86_release, docker_arm_release]\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    permissions:\n      contents: read\n      packages: write\n      id-token: write\n    steps:\n      - name: Setup Blacksmith Builder\n        uses: useblacksmith/setup-docker-builder@v1\n\n      - uses: docker/login-action@v3\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n\n      - name: Merge multi-arch manifests for versioned output\n        run: |\n          docker buildx imagetools create -t supabase/realtime:v${{ needs.release.outputs.version }} \\\n          supabase/realtime@${{ needs.docker_x86_release.outputs.image_digest }} \\\n          supabase/realtime@${{ needs.docker_arm_release.outputs.image_digest }}\n\n      - name: Merge multi-arch manifests for latest output\n        run: |\n          docker buildx imagetools create -t supabase/realtime:latest \\\n          supabase/realtime@${{ needs.docker_x86_release.outputs.image_digest }} \\\n          supabase/realtime@${{ needs.docker_arm_release.outputs.image_digest }}\n\n      - name: configure aws credentials\n        uses: aws-actions/configure-aws-credentials@v5\n        with:\n          role-to-assume: ${{ secrets.PROD_AWS_ROLE }}\n          aws-region: us-east-1\n\n      - name: Login to ECR\n        uses: docker/login-action@v3\n        with:\n          registry: public.ecr.aws\n\n      - name: Login to GHCR\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Mirror to ECR\n        uses: akhilerm/tag-push-action@v2.2.0\n        with:\n          src: docker.io/supabase/realtime:v${{ needs.release.outputs.version }}\n          dst: |\n            public.ecr.aws/supabase/realtime:v${{ needs.release.outputs.version }}\n            ghcr.io/supabase/realtime:v${{ needs.release.outputs.version }}\n\n  update-branch-name:\n    needs: [release, docker_x86_release, docker_arm_release, merge_manifest]\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - name: Checkout branch\n        uses: actions/checkout@v6\n        with:\n          ref: refs/heads/main\n\n      - name: Update branch name\n        run: |\n          git branch -m main releases/v${{ needs.release.outputs.version }}\n          git push origin HEAD:releases/v${{ needs.release.outputs.version }}\n"
  },
  {
    "path": ".github/workflows/prod_linter.yml",
    "content": "name: Production Formatting Checks\non:\n  pull_request:\n    branches:\n      - release\n\njobs:\n  format:\n    name: Formatting Checks\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: Setup elixir\n        id: beam\n        uses: erlef/setup-beam@v1\n        with:\n          otp-version: 27.x # Define the OTP version [required]\n          elixir-version: 1.18.x # Define the elixir version [required]\n      - name: Cache Mix\n        uses: actions/cache@v5\n        with:\n          path: deps\n          key: ${{ runner.os }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}\n          restore-keys: |\n            ${{ runner.os }}-mix-\n\n      - name: Install dependencies\n        run: mix deps.get\n      - name: Set up Postgres\n        run: docker compose -f docker-compose.dbs.yml up -d\n      - name: Run database migrations\n        run: mix ecto.migrate\n      - name: Run format check\n        run: mix format --check-formatted\n      - name: Credo checks\n        run: mix credo --strict --mute-exit-status\n      - name: Retrieve PLT Cache\n        uses: actions/cache@v5\n        id: plt-cache\n        with:\n          path: priv/plts\n          key: ${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-plts-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}\n      - name: Create PLTs\n        if: steps.plt-cache.outputs.cache-hit != 'true'\n        run: |\n          mkdir -p priv/plts\n          mix dialyzer.build\n      - name: Run dialyzer\n        run: mix dialyzer\n      - name: Run tests\n        run: mix test\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\non:\n  pull_request:\n    paths:\n      - \"lib/**\"\n      - \"test/**\"\n      - \"config/**\"\n      - \"priv/**\"\n      - \"assets/**\"\n      - \"rel/**\"\n      - \"native/**\"\n      - \"mix.exs\"\n      - \"Dockerfile\"\n      - \"run.sh\"\n      - \".github/workflows/tests.yml\"\n\n  push:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\nenv:\n  MIX_ENV: test\n  POSTGRES_IMAGE: supabase/postgres:17.6.1.074\n\njobs:\n  tests:\n    name: Tests (Partition ${{ matrix.partition }})\n    runs-on: blacksmith-8vcpu-ubuntu-2404\n    strategy:\n      fail-fast: false\n      matrix:\n        partition: [1, 2, 3, 4]\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: Setup elixir\n        id: beam\n        uses: erlef/setup-beam@v1\n        with:\n          otp-version: 27.x # Define the OTP version [required]\n          elixir-version: 1.18.x # Define the elixir version [required]\n      - name: Cache Mix\n        uses: actions/cache@v5\n        with:\n          path: |\n            deps\n            _build\n            priv/native\n          key: ${{ github.workflow }}-${{ runner.os }}-mix-${{ env.elixir }}-${{ env.otp }}-${{ hashFiles('**/mix.lock') }}\n          restore-keys: |\n            ${{ github.workflow }}-${{ runner.os }}-mix-${{ env.elixir }}-${{ env.otp }}-\n\n      - name: Cache Docker images\n        uses: actions/cache@v5\n        id: docker-cache\n        with:\n          path: /tmp/docker-images\n          key: docker-images-zstd-${{ env.POSTGRES_IMAGE }}\n      - name: Load Docker images from cache\n        if: steps.docker-cache.outputs.cache-hit == 'true'\n        run: zstd -d --stdout /tmp/docker-images/postgres.tar.zst | docker image load\n      - name: Pull and save Docker images\n        if: steps.docker-cache.outputs.cache-hit != 'true'\n        run: |\n          docker pull ${{ env.POSTGRES_IMAGE }}\n          mkdir -p /tmp/docker-images\n          docker image save ${{ env.POSTGRES_IMAGE }} | zstd -T0 -o /tmp/docker-images/postgres.tar.zst\n\n      - name: Install dependencies\n        run: mix deps.get\n      - name: Set up Postgres\n        run: docker compose -f docker-compose.dbs.yml up -d\n      - name: Start epmd\n        run: epmd -daemon\n      - name: Run tests\n        run: MIX_TEST_PARTITION=${{ matrix.partition }} mix coveralls.lcov --partitions 4\n      - name: Upload coverage artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: coverage-partition-${{ matrix.partition }}\n          path: cover/lcov.info\n\n  coverage:\n    name: Merge Coverage\n    needs: tests\n    if: ${{ needs.tests.result == 'success' }}\n    runs-on: blacksmith-8vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v6\n      - name: Download all coverage artifacts\n        uses: actions/download-artifact@v4\n        with:\n          pattern: coverage-partition-*\n          path: coverage\n      - name: Upload merged coverage to Coveralls\n        uses: coverallsapp/github-action@v2\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          files: coverage/coverage-partition-1/lcov.info coverage/coverage-partition-2/lcov.info coverage/coverage-partition-3/lcov.info coverage/coverage-partition-4/lcov.info\n"
  },
  {
    "path": ".github/workflows/update-supabase-js.yml",
    "content": "name: Update @supabase/supabase-js\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: \"Version to update to\"\n        required: true\n        type: string\n      source:\n        description: \"Source of the update\"\n        required: false\n        type: string\n        default: \"manual\"\n\npermissions:\n  pull-requests: read\n  contents: read\n\njobs:\n  update-supabase-js:\n    runs-on: ubuntu-latest\n    concurrency:\n      group: ${{ github.workflow }}-supabase-update-${{ inputs.version }}\n      cancel-in-progress: false\n\n    steps:\n      - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n        with:\n          ref: ${{ github.event.repository.default_branch }}\n\n      - name: Setup Node.js\n        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0\n        with:\n          node-version: \"20\"\n          cache: \"npm\"\n          cache-dependency-path: assets/package-lock.json\n\n      - name: Update @supabase/supabase-js\n        working-directory: assets\n        run: |\n          npm pkg set \"dependencies.@supabase/supabase-js=${{ inputs.version }}\"\n          npm install --package-lock-only --ignore-scripts\n\n      - name: Generate token\n        id: app-token\n        uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1\n        with:\n          app-id: ${{ secrets.GH_AUTOFIX_APP_ID }}\n          private-key: ${{ secrets.GH_AUTOFIX_PRIVATE_KEY }}\n\n      - name: Create pull request\n        uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0\n        with:\n          token: ${{ steps.app-token.outputs.token }}\n          commit-message: \"chore: update @supabase/supabase-js to v${{ inputs.version }}\"\n          title: \"chore: update @supabase/supabase-js to v${{ inputs.version }}\"\n          body: |\n            This PR updates `@supabase/supabase-js` to v${{ inputs.version }}.\n\n            **Source**: ${{ inputs.source }}\n\n            This PR was created automatically.\n          branch: \"gha/auto-update-supabase-js-v${{ inputs.version }}\"\n          base: ${{ github.event.repository.default_branch }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where 3rd-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\nrealtime-*.tar\n\n# Ignore assets that are produced by build tools.\n/priv/static/assets/\n\n# Ignore Dialyzer .plt\n/priv/plts/*\nnode_modules\n.supabase\nconfig/prod.secret.exs\ndemo/.env\n.lexical\n.vscode\n"
  },
  {
    "path": ".releaserc",
    "content": "{\n  \"branches\": [\"main\"],\n  \"plugins\": [\n    \"@semantic-release/commit-analyzer\",\n    \"@semantic-release/release-notes-generator\",\n    [\n      \"@semantic-release/exec\",\n      {\n        \"prepareCmd\": \"sed -i 's/version: \\\"[^\\\"]*\\\"/version: \\\"${nextRelease.version}\\\"/' mix.exs\"\n      }\n    ],\n    [\n      \"@semantic-release/git\",\n      {\n        \"assets\": [\"mix.exs\"],\n        \"message\": \"chore(release): ${nextRelease.version} [skip ci]\"\n      }\n    ],\n    \"@semantic-release/github\"\n  ]\n}\n"
  },
  {
    "path": ".sobelow-conf",
    "content": "[\n  verbose: true,\n  private: false,\n  skip: false,\n  router: nil,\n  exit: :low,\n  format: \"txt\",\n  out: nil,\n  threshold: :medium,\n  ignore: [\"Config.CSP\", \"Config.HTTPS\"],\n  ignore_files: [],\n  version: false\n]\n"
  },
  {
    "path": ".tool-versions",
    "content": "elixir 1.18.4-otp-27\nnodejs 24\nerlang 27\n"
  },
  {
    "path": "Dockerfile",
    "content": "ARG ELIXIR_VERSION=1.18\nARG OTP_VERSION=27.3\nARG DEBIAN_VERSION=bookworm-20250929-slim\nARG BUILDER_IMAGE=\"hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}\"\nARG RUNNER_IMAGE=\"debian:${DEBIAN_VERSION}\"\n\nFROM ${BUILDER_IMAGE} AS builder\n\nENV MIX_ENV=\"prod\"\n\nRUN apt-get update -y \\\n    && apt-get install curl -y \\\n    && apt-get install -y build-essential git \\\n    && apt-get clean\n\nRUN set -uex; \\\n    apt-get update; \\\n    apt-get install -y ca-certificates curl gnupg; \\\n    mkdir -p /etc/apt/keyrings; \\\n    curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \\\n    | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg; \\\n    NODE_MAJOR=24; \\\n    echo \"deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main\" \\\n    > /etc/apt/sources.list.d/nodesource.list; \\\n    apt-get -qy update; \\\n    apt-get -qy install nodejs;\n\n# prepare build dir\nWORKDIR /app\n\n# install hex + rebar\nRUN mix local.hex --force && \\\n    mix local.rebar --force\n\n# install mix dependencies\nCOPY mix.exs mix.lock ./\nCOPY beacon beacon\nRUN mix deps.get --only $MIX_ENV\nRUN mkdir config\n\n# copy compile-time config files before we compile dependencies\n# to ensure any relevant config change will trigger the dependencies\n# to be re-compiled.\nCOPY config/config.exs config/${MIX_ENV}.exs config/\nRUN mix deps.compile\nCOPY priv priv\nCOPY lib lib\nCOPY assets assets\n\n# compile assets with esbuild and npm\nRUN cd assets \\\n    && npm install \\\n    && cd .. \\\n    && mix assets.deploy\n\n# Compile the release\nRUN mix compile\n\n# Changes to config/runtime.exs don't require recompiling the code\nCOPY config/runtime.exs config/\nCOPY rel rel\nRUN mix release\n\n# start a new build stage so that the final image will only contain\n# the compiled release and other runtime necessities\nFROM ${RUNNER_IMAGE}\nARG SLOT_NAME_SUFFIX\n\nENV SLOT_NAME_SUFFIX=\"${SLOT_NAME_SUFFIX}\" \\\n    LANG=\"en_US.UTF-8\" \\\n    LANGUAGE=\"en_US:en\" \\\n    LC_ALL=\"en_US.UTF-8\" \\\n    MIX_ENV=\"prod\" \\\n    ECTO_IPV6=\"true\" \\\n    ERL_AFLAGS=\"-proto_dist inet6_tcp\"\n\nRUN apt-get update -y && \\\n    apt-get install -y libstdc++6 openssl libncurses5 locales iptables sudo tini curl awscli jq && \\\n    apt-get clean && rm -f /var/lib/apt/lists/*_*\n\n# Set the locale\nRUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen\n\nWORKDIR \"/app\"\n\nRUN chown nobody /app\n\nCOPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/realtime ./\nCOPY run.sh run.sh\nRUN ls -la /app\nENTRYPOINT [\"/usr/bin/tini\", \"-s\", \"-g\", \"--\", \"/app/run.sh\"]\nCMD [\"/app/bin/server\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2019 Supabase\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "CLUSTER_STRATEGIES ?= EPMD\nNODE_NAME ?= pink\nPORT ?= 4000\n\n.PHONY: dev dev.orange seed prod bench.% dev_db start start.% stop stop.% rebuild rebuild.%\n\n.DEFAULT_GOAL := help\n\n# Common commands\n\ndev: ## Start a dev server\n\tELIXIR_ERL_OPTIONS=\"+hmax 1000000000\" SLOT_NAME_SUFFIX=some_sha PORT=$(PORT) MIX_ENV=dev SECURE_CHANNELS=true API_JWT_SECRET=dev METRICS_JWT_SECRET=dev REGION=us-east-1 DB_ENC_KEY=\"1234567890123456\" CLUSTER_STRATEGIES=$(CLUSTER_STRATEGIES) ERL_AFLAGS=\"-kernel shell_history enabled\" GEN_RPC_TCP_SERVER_PORT=5369 GEN_RPC_TCP_CLIENT_PORT=5469 iex --name $(NODE_NAME)@127.0.0.1 --cookie cookie  -S mix phx.server\n\ndev.orange: ## Start another dev server (orange) on port 4001\n\tELIXIR_ERL_OPTIONS=\"+hmax 1000000000\" SLOT_NAME_SUFFIX=some_sha PORT=4001 MIX_ENV=dev SECURE_CHANNELS=true API_JWT_SECRET=dev METRICS_JWT_SECRET=dev REGION=eu-west-1 DB_ENC_KEY=\"1234567890123456\" CLUSTER_STRATEGIES=$(CLUSTER_STRATEGIES) ERL_AFLAGS=\"-kernel shell_history enabled\" GEN_RPC_TCP_SERVER_PORT=5469 GEN_RPC_TCP_CLIENT_PORT=5369 iex --name orange@127.0.0.1 --cookie cookie  -S mix phx.server\n\nseed: ## Seed the database\n\tDB_ENC_KEY=\"1234567890123456\" FLY_ALLOC_ID=123e4567-e89b-12d3-a456-426614174000 mix run priv/repo/dev_seeds.exs\n\nprod: ## Start a server with a MIX_ENV=prod\n\tELIXIR_ERL_OPTIONS=\"+hmax 1000000000\" SLOT_NAME_SUFFIX=some_sha MIX_ENV=prod FLY_APP_NAME=realtime-local API_KEY=dev SECURE_CHANNELS=true API_JWT_SECRET=dev METRICS_JWT_SECRET=dev FLY_REGION=fra FLY_ALLOC_ID=123e4567-e89b-12d3-a456-426614174000 DB_ENC_KEY=\"1234567890123456\" SECRET_KEY_BASE=M+55t7f6L9VWyhH03R5N7cIhrdRlZaMDfTE6Udz0eZS7gCbnoLQ8PImxwhEyao6D DASHBOARD_USER=realtime_local DASHBOARD_PASSWORD=password ERL_AFLAGS=\"-kernel shell_history enabled\" iex -S mix phx.server\n\nbench.%: ## Run benchmark with a specific file. e.g. bench.secrets\n\tELIXIR_ERL_OPTIONS=\"+hmax 1000000000\" SLOT_NAME_SUFFIX=some_sha MIX_ENV=dev SECURE_CHANNELS=true API_JWT_SECRET=dev METRICS_JWT_SECRET=dev FLY_REGION=fra FLY_ALLOC_ID=123e4567-e89b-12d3-a456-426614174000 DB_ENC_KEY=\"1234567890123456\" ERL_AFLAGS=\"-kernel shell_history enabled\" mix run bench/$*\n\ndev_db: ## Start dev databases using docker\n\tdocker-compose -f docker-compose.dbs.yml up -d && mix ecto.migrate --log-migrator-sql\n\n# Docker specific commands\n\nstart: ## Start main docker compose\n\tdocker-compose up\n\nstart.%: ## Start docker compose with a specific file. e.g. start.dbs\n\tdocker-compose -f docker-compose.$*.yml up\n\nstop: ## Stop main docker compose\n\tdocker-compose down --remove-orphans\n\nstop.%: ## Stop docker compose with a specific file. e.g. stop.dbs\n\tdocker-compose -f docker-compose.yml -f docker-compose.$*.yml down  --remove-orphans\n\nrebuild: ## Rebuild main docker compose images\n\tmake stop\n\tdocker-compose build\n\tdocker-compose up --force-recreate --build\n\nrebuild.%: ## Rebuild docker compose images with a specific file. e.g. rebuild.dbs\n\tmake stop.$*\n\tdocker-compose -f docker-compose.yml -f docker-compose.$*.yml build\n\tdocker-compose -f docker-compose.yml -f docker-compose.$*.yml up --force-recreate --build\n\n# Based on https://gist.github.com/prwhite/8168133\n.DEFAULT_GOAL:=help\n.PHONY: help\nhelp:  ## Display this help\n\t\t$(info Realtime commands)\n\t\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[%.a-zA-Z0-9_-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n"
  },
  {
    "path": "README.md",
    "content": "<br />\n<p align=\"center\">\n  <a href=\"https://supabase.io\">\n        <picture>\n      <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/supabase/supabase/master/packages/common/assets/images/supabase-logo-wordmark--dark.svg\">\n      <source media=\"(prefers-color-scheme: light)\" srcset=\"https://raw.githubusercontent.com/supabase/supabase/master/packages/common/assets/images/supabase-logo-wordmark--light.svg\">\n      <img alt=\"Supabase Logo\" width=\"300\" src=\"https://raw.githubusercontent.com/supabase/supabase/master/packages/common/assets/images/logo-preview.jpg\">\n    </picture>\n  </a>\n\n  <h1 align=\"center\">Supabase Realtime</h1>\n\n  <p align=\"center\">\n    Send ephemeral messages, track and synchronize shared state, and listen to Postgres changes all over WebSockets.\n    <br />\n    <a href=\"https://multiplayer.dev\">Multiplayer Demo</a>\n    ·\n    <a href=\"https://github.com/supabase/realtime/issues/new?assignees=&labels=enhancement&template=2.Feature_request.md\">Request Feature</a>\n    ·\n    <a href=\"https://github.com/supabase/realtime/issues/new?assignees=&labels=bug&template=1.Bug_report.md\">Report Bug</a>\n    <br />\n  </p>\n</p>\n\n## Status\n\n[![GitHub License](https://img.shields.io/github/license/supabase/realtime)](https://github.com/supabase/realtime/blob/main/LICENSE)\n[![Coverage Status](https://coveralls.io/repos/github/supabase/realtime/badge.svg?branch=main)](https://coveralls.io/github/supabase/realtime?branch=main)\n\n| Features         | v1  | v2  | Status |\n| ---------------- | --- | --- | ------ |\n| Postgres Changes | ✔   | ✔   | GA     |\n| Broadcast        |     | ✔   | GA     |\n| Presence         |     | ✔   | GA     |\n\nThis repository focuses on version 2 but you can still access the previous version's [code](https://github.com/supabase/realtime/tree/v1) and [Docker image](https://hub.docker.com/layers/supabase/realtime/v1.0.0/images/sha256-e2766e0e3b0d03f7e9aa1b238286245697d0892c2f6f192fd2995dca32a4446a). For the latest Docker images go to https://hub.docker.com/r/supabase/realtime.\n\nThe codebase is under heavy development and the documentation is constantly evolving. Give it a try and let us know what you think by creating an issue. Watch [releases](https://github.com/supabase/realtime/releases) of this repo to get notified of updates. And give us a star if you like it!\n\n## Overview\n\n### What is this?\n\nThis is a server built with Elixir using the [Phoenix Framework](https://www.phoenixframework.org) that enables the following functionality:\n\n- Broadcast: Send ephemeral messages from client to clients with low latency.\n- Presence: Track and synchronize shared state between clients.\n- Postgres Changes: Listen to Postgres database changes and send them to authorized clients.\n\nFor a more detailed overview head over to [Realtime guides](https://supabase.com/docs/guides/realtime).\n\n### Does this server guarantee message delivery?\n\nThe server does not guarantee that every message will be delivered to your clients so keep that in mind as you're using Realtime.\n\n## Quick start\n\nYou can check out the [Supabase UI Library](https://supabase.com/ui) Realtime components and the [multiplayer.dev](https://multiplayer.dev) demo app source code [here](https://github.com/supabase/multiplayer.dev)\n\n## Client libraries\n\n- [JavaScript](https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js)\n- [Flutter/Dart](https://github.com/supabase/supabase-flutter/tree/main/packages/realtime_client)\n- [Python](https://github.com/supabase/supabase-py/tree/main/src/realtime)\n- [Swift](https://github.com/supabase/supabase-swift/tree/main/Sources/Realtime)\n\n## Server Setup\n\nTo get started, spin up your Postgres database and Realtime server containers defined in `docker-compose.yml`. As an example, you may run `docker-compose -f docker-compose.yml up`.\n\n> **Note**\n> Supabase runs Realtime in production with a separate database that keeps track of all tenants. However, a schema, `_realtime`, is created when spinning up containers via `docker-compose.yml` to simplify local development.\n\nA tenant has already been added on your behalf. You can confirm this by checking the `_realtime.tenants` and `_realtime.extensions` tables inside the database.\n\nYou can add your own by making a `POST` request to the server. You must change both `name` and `external_id` while you may update other values as you see fit:\n\n```bash\n  curl -X POST \\\n  -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIiLCJpYXQiOjE2NzEyMzc4NzMsImV4cCI6MTcwMjc3Mzk5MywiYXVkIjoiIiwic3ViIjoiIn0._ARixa2KFUVsKBf3UGR90qKLCpGjxhKcXY4akVbmeNQ' \\\n  -d $'{\n    \"tenant\" : {\n      \"name\": \"realtime-dev\",\n      \"external_id\": \"realtime-dev\",\n      \"jwt_secret\": \"a1d99c8b-91b6-47b2-8f3c-aa7d9a9ad20f\",\n      \"extensions\": [\n        {\n          \"type\": \"postgres_cdc_rls\",\n          \"settings\": {\n            \"db_name\": \"postgres\",\n            \"db_host\": \"host.docker.internal\",\n            \"db_user\": \"postgres\",\n            \"db_password\": \"postgres\",\n            \"db_port\": \"5432\",\n            \"region\": \"us-west-1\",\n            \"poll_interval_ms\": 100,\n            \"poll_max_record_bytes\": 1048576,\n            \"ssl_enforced\": false\n          }\n        }\n      ]\n    }\n  }' \\\n  http://localhost:4000/api/tenants\n```\n\n> **Note**\n> The `Authorization` token is signed with the secret set by `API_JWT_SECRET` in `docker-compose.yml`.\n\nIf you want to listen to Postgres changes, you can create a table and then add the table to the `supabase_realtime` publication:\n\n```sql\ncreate table test (\n  id serial primary key\n);\n\nalter publication supabase_realtime add table test;\n```\n\nYou can start playing around with Broadcast, Presence, and Postgres Changes features either with the client libs (e.g. `@supabase/realtime-js`), or use the built in Realtime Inspector on localhost, `http://localhost:4000/inspector/new` (make sure the port is correct for your development environment).\n\nThe WebSocket URL must contain the subdomain, `external_id` of the tenant on the `_realtime.tenants` table, and the token must be signed with the `jwt_secret` that was inserted along with the tenant.\n\nIf you're using the default tenant, the URL is `ws://realtime-dev.localhost:4000/socket` (make sure the port is correct for your development environment), and you can use `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDMwMjgwODcsInJvbGUiOiJwb3N0Z3JlcyJ9.tz_XJ89gd6bN8MBpCl7afvPrZiBH6RB65iA1FadPT3Y` for the token. The token must have `exp` and `role` (database role) keys.\n\n**Environment Variables**\n\n| Variable                                        | Type    | Description                                                                                                                                                                                                                                                                                                                     |\n| ----------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| PORT                                            | number  | Port which you can connect your client/listeners                                                                                                                                                                                                                                                                                |\n| DB_HOST                                         | string  | Database host URL                                                                                                                                                                                                                                                                                                               |\n| DB_PORT                                         | number  | Database port                                                                                                                                                                                                                                                                                                                   |\n| DB_USER                                         | string  | Database user                                                                                                                                                                                                                                                                                                                   |\n| DB_PASSWORD                                     | string  | Database password                                                                                                                                                                                                                                                                                                               |\n| DB_NAME                                         | string  | Postgres database name                                                                                                                                                                                                                                                                                                          |\n| DB_ENC_KEY                                      | string  | Key used to encrypt sensitive fields in \\_realtime.tenants and \\_realtime.extensions tables. Recommended: 16 characters.                                                                                                                                                                                                        |\n| DB_AFTER_CONNECT_QUERY                          | string  | Query that is run after server connects to database.                                                                                                                                                                                                                                                                            |\n| DB_IP_VERSION                                   | string  | Sets the IP Version to be used. Allowed values are \"ipv6\" and \"ipv4\". If none are set we will try to infer the correct version                                                                                                                                                                                                  |\n| DB_SSL                                          | boolean | Whether or not the connection will be set-up using SSL                                                                                                                                                                                                                                                                          |\n| DB_SSL_CA_CERT                                  | string  | Filepath to a CA trust store (e.g.: /etc/cacert.pem). If defined it enables server certificate verification                                                                                                                                                                                                                     |\n| API_JWT_SECRET                                  | string  | Secret that is used to sign tokens used to manage tenants and their extensions via HTTP requests.                                                                                                                                                                                                                               |\n| SECRET_KEY_BASE                                 | string  | Secret used by the server to sign cookies. Recommended: 64 characters.                                                                                                                                                                                                                                                          |\n| ERL_AFLAGS                                      | string  | Set to either \"-proto_dist inet_tcp\" or \"-proto_dist inet6_tcp\" depending on whether or not your network uses IPv4 or IPv6, respectively.                                                                                                                                                                                       |\n| APP_NAME                                        | string  | A name of the server.                                                                                                                                                                                                                                                                                                           |\n| DNS_NODES                                       | string  | Node name used when running server in a cluster.                                                                                                                                                                                                                                                                                |\n| MAX_CONNECTIONS                                 | string  | Set the soft maximum for WebSocket connections. Defaults to '16384'.                                                                                                                                                                                                                                                            |\n| MAX_HEADER_LENGTH                               | string  | Set the maximum header length for connections (in bytes). Defaults to '4096'.                                                                                                                                                                                                                                                   |\n| NUM_ACCEPTORS                                   | string  | Set the number of server processes that will relay incoming WebSocket connection requests. Defaults to '100'.                                                                                                                                                                                                                   |\n| DB_QUEUE_TARGET                                 | string  | Maximum time to wait for a connection from the pool. Defaults to '5000' or 5 seconds. See for more info: [DBConnection](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config).                                                                                                                          |\n| DB_QUEUE_INTERVAL                               | string  | Interval to wait to check if all connections were checked out under DB_QUEUE_TARGET. If all connections surpassed the target during this interval than the target is doubled. Defaults to '5000' or 5 seconds. See for more info: [DBConnection](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config). |\n| DB_POOL_SIZE                                    | string  | Sets the number of connections in the database pool. Defaults to '5'.                                                                                                                                                                                                                                                           |\n| DB_REPLICA_HOST                                 | string  | Hostname for the replica database. If set, enables the main replica connection pool.                                                                                                                                                                                                                                            |\n| DB_REPLICA_POOL_SIZE                            | string  | Sets the number of connections in the replica database pool. Defaults to '5'.                                                                                                                                                                                                                                                   |\n| SLOT_NAME_SUFFIX                                | string  | This is appended to the replication slot which allows making a custom slot name. May contain lowercase letters, numbers, and the underscore character. Together with the default `supabase_realtime_replication_slot`, slot name should be up to 64 characters long.                                                            |\n| TENANT_CACHE_EXPIRATION_IN_MS                   | string  | Set tenant cache TTL in milliseconds                                                                                                                                                                                                                                                                                            |\n| TENANT_MAX_BYTES_PER_SECOND                     | string  | The default value of maximum bytes per second that each tenant can support, used when creating a tenant for the first time. Defaults to '100_000'.                                                                                                                                                                              |\n| TENANT_MAX_CHANNELS_PER_CLIENT                  | string  | The default value of maximum number of channels each tenant can support, used when creating a tenant for the first time. Defaults to '100'.                                                                                                                                                                                     |\n| TENANT_MAX_CONCURRENT_USERS                     | string  | The default value of maximum concurrent users per channel that each tenant can support, used when creating a tenant for the first time. Defaults to '200'.                                                                                                                                                                      |\n| TENANT_MAX_EVENTS_PER_SECOND                    | string  | The default value of maximum events per second that each tenant can support, used when creating a tenant for the first time. Defaults to '100'.                                                                                                                                                                                 |\n| TENANT_MAX_JOINS_PER_SECOND                     | string  | The default value of maximum channel joins per second that each tenant can support, used when creating a tenant for the first time. Defaults to '100'.                                                                                                                                                                          |\n| CLIENT_PRESENCE_MAX_CALLS                       | number  | Maximum number of presence calls allowed per client (per WebSocket connection) within the time window. Defaults to '5'.                                                                                                                                                                                                         |\n| CLIENT_PRESENCE_WINDOW_MS                       | number  | Time window in milliseconds for per-client presence rate limiting. Defaults to '30000' (30 seconds).                                                                                                                                                                                                                            |\n| SEED_SELF_HOST                                  | boolean | Seeds the system with default tenant                                                                                                                                                                                                                                                                                            |\n| SELF_HOST_TENANT_NAME                           | string  | Tenant reference to be used for self host. Do keep in mind to use a URL compatible name                                                                                                                                                                                                                                         |\n| LOG_LEVEL                                       | string  | Sets log level for Realtime logs. Defaults to info, supported levels are: info, emergency, alert, critical, error, warning, notice, debug                                                                                                                                                                                       |\n| DISABLE_HEALTHCHECK_LOGGING                     | boolean | Disables request logging for healthcheck endpoints (/healthcheck and /api/tenants/:tenant_id/health). Defaults to false.                                                                                                                                                                                                        |\n| RUN_JANITOR                                     | boolean | Do you want to janitor tasks to run                                                                                                                                                                                                                                                                                             |\n| JANITOR_SCHEDULE_TIMER_IN_MS                    | number  | Time in ms to run the janitor task                                                                                                                                                                                                                                                                                              |\n| JANITOR_SCHEDULE_RANDOMIZE                      | boolean | Adds a randomized value of minutes to the timer                                                                                                                                                                                                                                                                                 |\n| JANITOR_RUN_AFTER_IN_MS                         | number  | Tells system when to start janitor tasks after boot                                                                                                                                                                                                                                                                             |\n| JANITOR_CLEANUP_MAX_CHILDREN                    | number  | Maximum number of concurrent tasks working on janitor cleanup                                                                                                                                                                                                                                                                   |\n| JANITOR_CLEANUP_CHILDREN_TIMEOUT                | number  | Timeout for each async task for janitor cleanup                                                                                                                                                                                                                                                                                 |\n| JANITOR_CHUNK_SIZE                              | number  | Number of tenants to process per chunk. Each chunk will be processed by a Task                                                                                                                                                                                                                                                  |\n| MIGRATION_PARTITION_SLOTS                       | number  | Number of dynamic supervisor partitions used by the migrations process                                                                                                                                                                                                                                                          |\n| CONNECT_PARTITION_SLOTS                         | number  | Number of dynamic supervisor partitions used by the Connect, ReplicationConnect processes                                                                                                                                                                                                                                       |\n| METRICS_CLEANER_SCHEDULE_TIMER_IN_MS            | number  | Time in ms to run the Metric Cleaner task                                                                                                                                                                                                                                                                                       |\n| METRICS_RPC_TIMEOUT_IN_MS                       | number  | Time in ms to wait for RPC call to fetch Metric per node                                                                                                                                                                                                                                                                        |\n| WEBSOCKET_MAX_HEAP_SIZE                         | number  | Max number of bytes to be allocated as heap for the WebSocket transport process. If the limit is reached the process is brutally killed. Defaults to 50MB.                                                                                                                                                                      |\n| REQUEST_ID_BAGGAGE_KEY                          | string  | OTEL Baggage key to be used as request id                                                                                                                                                                                                                                                                                       |\n| OTEL_SDK_DISABLED                               | boolean | Disable OpenTelemetry tracing completely when 'true'                                                                                                                                                                                                                                                                            |\n| OTEL_TRACES_EXPORTER                            | string  | Possible values: `otlp` or `none`. See [https://github.com/open-telemetry/opentelemetry-erlang/tree/v1.4.0/apps#os-environment] for more details on how to configure the traces exporter.                                                                                                                                       |\n| OTEL_TRACES_SAMPLER                             | string  | Default to `parentbased_always_on` . More info [here](https://opentelemetry.io/docs/languages/erlang/sampling/#environment-variables)                                                                                                                                                                                           |\n| GEN_RPC_TCP_SERVER_PORT                         | number  | Port served by `gen_rpc`. Must be secured just like the Erlang distribution port. Defaults to 5369                                                                                                                                                                                                                              |\n| GEN_RPC_TCP_CLIENT_PORT                         | number  | `gen_rpc` connects to another node using this port. Most of the time it should be the same as GEN_RPC_TCP_SERVER_PORT. Defaults to 5369                                                                                                                                                                                         |\n| GEN_RPC_SSL_SERVER_PORT                         | number  | Port served by `gen_rpc` secured with TLS. Must also define GEN_RPC_CERTFILE, GEN_RPC_KEYFILE and GEN_RPC_CACERTFILE. If this is defined then only TLS connections will be set-up.                                                                                                                                              |\n| GEN_RPC_SSL_CLIENT_PORT                         | number  | `gen_rpc` connects to another node using this port. Most of the time it should be the same as GEN_RPC_SSL_SERVER_PORT. Defaults to 6369                                                                                                                                                                                         |\n| GEN_RPC_CERTFILE                                | string  | Path to the public key in PEM format. Only needs to be provided if GEN_RPC_SSL_SERVER_PORT is defined                                                                                                                                                                                                                           |\n| GEN_RPC_KEYFILE                                 | string  | Path to the private key in PEM format. Only needs to be provided if GEN_RPC_SSL_SERVER_PORT is defined                                                                                                                                                                                                                          |\n| GEN_RPC_CACERTFILE                              | string  | Path to the certificate authority public key in PEM format. Only needs to be provided if GEN_RPC_SSL_SERVER_PORT is defined                                                                                                                                                                                                     |\n| GEN_RPC_CONNECT_TIMEOUT_IN_MS                   | number  | `gen_rpc` client connect timeout in milliseconds. Defaults to 10000.                                                                                                                                                                                                                                                            |\n| GEN_RPC_SEND_TIMEOUT_IN_MS                      | number  | `gen_rpc` client and server send timeout in milliseconds. Defaults to 10000.                                                                                                                                                                                                                                                    |\n| GEN_RPC_SOCKET_IP                               | string  | Interface which `gen_rpc` will bind to. Defaults to \"0.0.0.0\" (ipv4) which means that all interfaces are going to expose the `gen_rpc` port.                                                                                                                                                                                    |\n| GEN_RPC_IPV6_ONLY                               | boolean | Configure `gen_rpc` to use IPv6 only.                                                                                                                                                                                                                                                                                           |\n| GEN_RPC_MAX_BATCH_SIZE                          | integer | Configure `gen_rpc` to batch when possible RPC casts. Defaults to 0                                                                                                                                                                                                                                                             |\n| GEN_RPC_COMPRESS                                | integer | Configure `gen_rpc` to compress or not payloads. 0 means no compression and 9 max compression level. Defaults to 0.                                                                                                                                                                                                             |\n| GEN_RPC_COMPRESSION_THRESHOLD_IN_BYTES          | integer | Configure `gen_rpc` to compress only above a certain threshold in bytes. Defaults to 1000.                                                                                                                                                                                                                                      |\n| MAX_GEN_RPC_CLIENTS                             | number  | Max amount of `gen_rpc` TCP connections per node-to-node channel                                                                                                                                                                                                                                                                |\n| REBALANCE_CHECK_INTERVAL_IN_MS                  | number  | Time in ms to check if process is in the right region                                                                                                                                                                                                                                                                           |\n| NODE_BALANCE_UPTIME_THRESHOLD_IN_MS             | number  | Minimum node uptime in ms before using load-aware node picker. Nodes below this threshold use random selection as their metrics are not yet reliable. Defaults to 5 minutes.                                                                                                                                                    |\n| DISCONNECT_SOCKET_ON_NO_CHANNELS_INTERVAL_IN_MS | number  | Time in ms to check if a socket has no channels open and if so, disconnect it                                                                                                                                                                                                                                                   |\n| BROADCAST_POOL_SIZE                             | number  | Number of processes to relay Phoenix.PubSub messages across the cluster                                                                                                                                                                                                                                                         |\n| PRESENCE_POOL_SIZE                              | number  | Number of tracker processes for Presence feature. Defaults to 10. Higher values improve concurrency for presence tracking across many channels.                                                                                                                                                                                 |\n| PRESENCE_BROADCAST_PERIOD_IN_MS                 | number  | Interval in milliseconds to send presence delta broadcasts across the cluster. Defaults to 1500 (1.5 seconds). Lower values increase network traffic but reduce presence sync latency.                                                                                                                                          |\n| PRESENCE_PERMDOWN_PERIOD_IN_MS                  | number  | Interval in milliseconds to flag a replica as permanently down and discard its state. Defaults to 1200000 (20 minutes). Must be greater than down_period. Higher values are more forgiving of temporary network issues but slower to clean up truly dead replicas.                                                              |\n| POSTGRES_CDC_SCOPE_SHARDS                       | number  | Number of dynamic supervisor partitions used by the Postgres CDC extension. Defaults to 5.                                                                                                                                                                                                                                      |\n| USERS_SCOPE_SHARDS                              | number  | Number of dynamic supervisor partitions used by the Users extension. Defaults to 5.                                                                                                                                                                                                                                             |\n| REGION_MAPPING                                  | string  | Custom mapping of platform regions to tenant regions. Must be a valid JSON object with string keys and values (e.g., `{\"custom-region-1\": \"us-east-1\", \"eu-north-1\": \"eu-west-2\"}`). If not provided, uses the default hardcoded region mapping. When set, only the specified mappings are used (no fallback to defaults).      |\n| METRICS_PUSHER_ENABLED                          | boolean | Enable periodic push of Prometheus metrics. Defaults to 'false'. Requires METRICS_PUSHER_URL to be set.                                                                                                                                                                                                                         |\n| METRICS_PUSHER_URL                              | string  | Full URL endpoint to push metrics using Prometheus exposition format (e.g., 'https://example.com/api/v1/import/prometheus'). Required when METRICS_PUSHER_ENABLED is 'true'.                                                                                                                                                    |\n| METRICS_PUSHER_USER                             | string  | Username for Basic auth (RFC 7617) on metrics pushes. Defaults to 'realtime'. Used together with METRICS_PUSHER_AUTH to form the Authorization header as `Basic Base64(\"user:password\")`.                                                                                                                                       |\n| METRICS_PUSHER_AUTH                             | string  | Password for Basic auth (RFC 7617) on metrics pushes. Used together with METRICS_PUSHER_USER to form the Authorization header as `Basic Base64(\"user:password\")`. If not set, requests will be sent without authorization. Keep this secret if used.                                                                            |\n| METRICS_PUSHER_INTERVAL_MS                      | number  | Interval in milliseconds between metrics pushes. Defaults to '30000' (30 seconds).                                                                                                                                                                                                                                              |\n| METRICS_PUSHER_TIMEOUT_MS                       | number  | HTTP request timeout in milliseconds for metrics push operations. Defaults to '15000' (15 seconds).                                                                                                                                                                                                                             |\n| METRICS_PUSHER_COMPRESS                         | boolean | Enable gzip compression for metrics payloads. Defaults to 'true'.                                                                                                                                                                                                                                                               |\n| DASHBOARD_AUTH                                  | string  | Authentication method for the admin dashboard (`/admin`). Accepted values: `basic_auth` (default) or `zta`. When `basic_auth`, `DASHBOARD_USER` and `DASHBOARD_PASSWORD` are required. When `zta`, `CF_TEAM_DOMAIN` is required.                                                                                                 |\n| DASHBOARD_USER                                  | string  | Username for admin dashboard basic auth. Required when `DASHBOARD_AUTH` is `basic_auth`.                                                                                                                                                                                                                                        |\n| DASHBOARD_PASSWORD                              | string  | Password for admin dashboard basic auth. Required when `DASHBOARD_AUTH` is `basic_auth`.                                                                                                                                                                                                                                        |\n| CF_TEAM_DOMAIN                                  | string  | Cloudflare Zero Trust team domain used for ZTA authentication. Required when `DASHBOARD_AUTH` is `zta`.                                                                                                                                                                                                                          |\n\nThe OpenTelemetry variables mentioned above are not an exhaustive list of all [supported environment variables](https://opentelemetry.io/docs/languages/sdk-configuration/).\n\n## WebSocket URL\n\nThe WebSocket URL is in the following format for local development: `ws://[external_id].localhost:4000/socket/websocket`\n\nIf you're using Supabase's hosted Realtime in production the URL is `wss://[project-ref].supabase.co/realtime/v1/websocket?apikey=[anon-token]&log_level=info&vsn=1.0.0\"`\n\n## WebSocket Connection Authorization\n\nWebSocket connections are authorized via symmetric JWT verification. Only supports JWTs signed with the following algorithms:\n\n- HS256\n- HS384\n- HS512\n\nVerify JWT claims by setting JWT_CLAIM_VALIDATORS:\n\n> e.g. {'iss': 'Issuer', 'nbf': 1610078130}\n>\n> Then JWT's \"iss\" value must equal \"Issuer\" and \"nbf\" value must equal 1610078130.\n\n**Note:**\n\n> JWT expiration is checked automatically. `exp` and `role` (database role) keys are mandatory.\n\n**Authorizing Client Connection**: You can pass in the JWT by following the instructions under the Realtime client lib. For example, refer to the **Usage** section in the [@supabase/realtime-js](https://github.com/supabase/realtime-js) client library.\n\n## Error Operational Codes\n\nThis is the list of operational codes that can help you understand your deployment and your usage.\n\n| Code                               | Description                                                                                                                                                                                           |\n| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| TopicNameRequired                  | You are trying to use Realtime without a topic name set                                                                                                                                               |\n| InvalidJoinPayload                 | The payload provided to Realtime on connect is invalid                                                                                                                                                |\n| RealtimeDisabledForConfiguration   | The configuration provided to Realtime on connect will not be able to provide you any Postgres Changes                                                                                                |\n| TenantNotFound                     | The tenant you are trying to connect to does not exist                                                                                                                                                |\n| ErrorConnectingToWebsocket         | Error when trying to connect to the WebSocket server                                                                                                                                                  |\n| ErrorAuthorizingWebsocket          | Error when trying to authorize the WebSocket connection                                                                                                                                               |\n| TableHasSpacesInName               | The table you are trying to listen to has spaces in its name which we are unable to support                                                                                                           |\n| UnableToDeleteTenant               | Error when trying to delete a tenant                                                                                                                                                                  |\n| UnableToSetPolicies                | Error when setting up Authorization Policies                                                                                                                                                          |\n| UnableCheckoutConnection           | Error when trying to checkout a connection from the tenant pool                                                                                                                                       |\n| UnableToSubscribeToPostgres        | Error when trying to subscribe to Postgres changes                                                                                                                                                    |\n| ReconnectSubscribeToPostgres       | Postgres changes still waiting to be subscribed                                                                                                                                                       |\n| ChannelRateLimitReached            | The number of channels you can create has reached its limit                                                                                                                                           |\n| ConnectionRateLimitReached         | The number of connected clients as reached its limit                                                                                                                                                  |\n| ClientJoinRateLimitReached         | The rate of joins per second from your clients has reached the channel limits                                                                                                                         |\n| DatabaseConnectionRateLimitReached | The rate of attempts to connect to tenants database has reached the limit                                                                                                                             |\n| MessagePerSecondRateLimitReached   | The rate of messages per second from your clients has reached the channel limits                                                                                                                      |\n| RealtimeDisabledForTenant          | Realtime has been disabled for the tenant                                                                                                                                                             |\n| UnableToConnectToTenantDatabase    | Realtime was not able to connect to the tenant's database                                                                                                                                             |\n| DatabaseLackOfConnections          | Realtime was not able to connect to the tenant's database due to not having enough available connections                                                                                              |\n| RealtimeNodeDisconnected           | Realtime is a distributed application and this means that one the system is unable to communicate with one of the distributed nodes                                                                   |\n| MigrationsFailedToRun              | Error when running the migrations against the Tenant database that are required by Realtime                                                                                                           |\n| StartReplicationFailed             | Error when starting the replication and listening of errors for database broadcasting                                                                                                                 |\n| ReplicationConnectionTimeout       | Replication connection timed out during initialization                                                                                                                                                |\n| ReplicationMaxWalSendersReached    | Maximum number of WAL senders reached in tenant database, check how to increase this value in this [link](https://supabase.com/docs/guides/database/custom-postgres-config#cli-configurable-settings) |\n| MigrationCheckFailed               | Check to see if we require to run migrations fails                                                                                                                                                    |\n| PartitionCreationFailed            | Error when creating partitions for realtime.messages                                                                                                                                                  |\n| ErrorStartingPostgresCDCStream     | Error when starting the Postgres CDC stream which is used for Postgres Changes                                                                                                                        |\n| UnknownDataProcessed               | An unknown data type was processed by the Realtime system                                                                                                                                             |\n| ErrorStartingPostgresCDC           | Error when starting the Postgres CDC extension which is used for Postgres Changes                                                                                                                     |\n| ReplicationSlotBeingUsed           | The replication slot is being used by another transaction                                                                                                                                             |\n| PoolingReplicationPreparationError | Error when preparing the replication slot                                                                                                                                                             |\n| PoolingReplicationError            | Error when pooling the replication slot                                                                                                                                                               |\n| SubscriptionCleanupFailed          | Error when trying to clean up all subscriptions on subscription manager initialization or OID change                                                                                                   |\n| SubscriptionDeletionFailed         | Error when trying to delete a subscription for postgres changes                                                                                                                                       |\n| SubscriptionsCheckerConnectionFailed | Error when the subscriptions checker process fails to connect to the database on startup                                                                                                             |\n| ReplicationPollerConnectionFailed  | Error when the replication poller process fails to connect to the database on startup                                                                                                                 |\n| SubscriptionManagerConnectionFailed | Error when the subscription manager process fails to connect to the database on startup                                                                                                              |\n| PgStatActivityQueryFailed          | Error when querying pg_stat_activity to diagnose a replication slot conflict                                                                                                                          |\n| RateCounterError                   | Error when retrieving the subscription rate counter, falling back to blocking new subscriptions                                                                                                       |\n| UnableToDeletePhantomSubscriptions | Error when trying to delete subscriptions that are no longer being used                                                                                                                               |\n| UnableToCheckProcessesOnRemoteNode | Error when trying to check the processes on a remote node                                                                                                                                             |\n| UnhandledProcessMessage            | Unhandled message received by a Realtime process                                                                                                                                                      |\n| UnableToTrackPresence              | Error when handling track presence for this socket                                                                                                                                                    |\n| UnknownPresenceEvent               | Presence event type not recognized by service                                                                                                                                                         |\n| IncreaseConnectionPool             | The number of connections you have set for Realtime are not enough to handle your current use case                                                                                                    |\n| RlsPolicyError                     | Error on RLS policy used for authorization                                                                                                                                                            |\n| ConnectionInitializing             | Database is initializing connection                                                                                                                                                                   |\n| DatabaseConnectionIssue            | Database had connection issues and connection was not able to be established                                                                                                                          |\n| UnableToConnectToProject           | Unable to connect to Project database                                                                                                                                                                 |\n| InvalidJWTExpiration               | JWT exp claim value it's incorrect                                                                                                                                                                    |\n| JwtSignatureError                  | JWT signature was not able to be validated                                                                                                                                                            |\n| MalformedJWT                       | Token received does not comply with the JWT format                                                                                                                                                    |\n| Unauthorized                       | Unauthorized access to Realtime channel                                                                                                                                                               |\n| RealtimeRestarting                 | Realtime is currently restarting                                                                                                                                                                      |\n| UnableToProcessListenPayload       | Payload sent in NOTIFY operation was JSON parsable                                                                                                                                                    |\n| UnprocessableEntity                | Received a HTTP request with a body that was not able to be processed by the endpoint                                                                                                                 |\n| InitializingProjectConnection      | Connection against Tenant database is still starting                                                                                                                                                  |\n| TimeoutOnRpcCall                   | RPC request within the Realtime server as timed out.                                                                                                                                                  |\n| ErrorOnRpcCall                     | Error when calling another realtime node                                                                                                                                                              |\n| ErrorExecutingTransaction          | Error executing a database transaction in tenant database                                                                                                                                             |\n| SynInitializationError             | Our framework to syncronize processes has failed to properly startup a connection to the database                                                                                                     |\n| JanitorFailedToDeleteOldMessages   | Scheduled task for realtime.message cleanup was unable to run                                                                                                                                         |\n| UnableToEncodeJson                 | An error were we are not handling correctly the response to be sent to the end user                                                                                                                   |\n| UnableToBroadcastChanges           | Error when trying to broadcast database changes to subscribers                                                                                                                                        |\n| UnexpectedMessageReceived          | An unexpected message was received by the replication connection process                                                                                                                              |\n| ErrorRunningQuery                  | Error when running a query against the tenant database                                                                                                                                                |\n| UnknownError                       | An unhandled error occurred                                                                                                                                                                           |\n| UnknownErrorOnController           | An error we are not handling correctly was triggered on a controller                                                                                                                                  |\n| UnknownErrorOnChannel              | An error we are not handling correctly was triggered on a channel                                                                                                                                     |\n| PresenceRateLimitReached           | Limit of presence events reached                                                                                                                                                                      |\n| ClientPresenceRateLimitReached     | Limit of presence events reached on socket                                                                                                                                                            |\n| UnableToReplayMessages             | An error while replaying messages                                                                                                                                                                     |\n| JwtSignerError                     | Failed to generate a JWT signer — check your JWT secret or JWKS configuration                                                                                                                         |\n| MalformedWebSocketMessage          | Received a WebSocket message that is empty, invalid JSON, or missing required fields (`ref`, `topic`, or `event`). The connection is kept alive but the message is dropped                            |\n| UnknownErrorOnWebSocketMessage     | An unexpected error occurred while processing an incoming WebSocket message. The connection is kept alive but the message is dropped                                                                  |\n\n## Observability and Metrics\n\nSupabase Realtime exposes comprehensive metrics for monitoring performance, resource usage, and application behavior. These metrics are exposed in Prometheus format and can be scraped by any compatible monitoring system (Victoria Metrics, Prometheus, Grafana Agent, etc.).\n\n### Metrics Endpoints\n\nMetrics are split across two endpoints with different priorities, allowing you to configure different scrape intervals in your monitoring system:\n\n| Endpoint                      | Priority | Recommended Scrape Interval | Contents                                                                                         |\n| ----------------------------- | -------- | --------------------------- | ------------------------------------------------------------------------------------------------ |\n| `GET /metrics`                | **High** | 30s                         | BEAM/VM, OS, Phoenix, distributed infra, and global aggregated tenant totals (no `tenant` label) |\n| `GET /tenant-metrics`         | **Low**  | 60s                         | Per-tenant labeled metrics (connection counts, channel events, replication, authorization)       |\n| `GET /metrics/:region`        | **High** | 30s                         | Same as `/metrics` scoped to a specific region                                                   |\n| `GET /tenant-metrics/:region` | **Low**  | 60s                         | Same as `/tenant-metrics` scoped to a specific region                                            |\n\nAll endpoints require a `Bearer` JWT token in the `Authorization` header signed with `METRICS_JWT_SECRET`.\n\n**Victoria Metrics scrape configuration example:**\n\n```yaml\nscrape_configs:\n  - job_name: realtime_global\n    scrape_interval: 30s\n    bearer_token: <METRICS_JWT_SECRET_TOKEN>\n    static_configs:\n      - targets: [\"<host>:4000\"]\n    metrics_path: /metrics\n\n  - job_name: realtime_tenant\n    scrape_interval: 60s\n    bearer_token: <METRICS_JWT_SECRET_TOKEN>\n    static_configs:\n      - targets: [\"<host>:4000\"]\n    metrics_path: /tenant-metrics\n```\n\n### Metric Scopes\n\nMetrics are classified by their scope to help you understand what they measure:\n\n- **Per-Tenant**: Metrics tagged with a `tenant` label measure activity scoped to individual tenants. Exposed on `/tenant-metrics`.\n- **Global Aggregate**: Metrics prefixed with `realtime_channel_global_*` or `realtime_connections_global_*` aggregate tenant data without the `tenant` label, suitable for cluster-wide dashboards. Exposed on `/metrics`.\n- **Per-Node**: Metrics measure activity on the current Realtime node. Without explicit per-node indication, assume metrics apply to the local node.\n- **BEAM/Erlang VM**: Metrics prefixed with `beam_*` and `phoenix_*` expose Erlang runtime internals. Exposed on `/metrics`.\n- **Infrastructure**: Metrics prefixed with `osmon_*`, `gen_rpc_*`, and `dist_*` measure system-level resources and cluster communication. Exposed on `/metrics`.\n\n### Connection & Tenant Metrics\n\nThese metrics track WebSocket connections and tenant activity across the Realtime cluster.\n\n| Metric                                          | Type    | Description                                                                                                                                       | Scope            | Endpoint          |\n| ----------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ----------------- |\n| `realtime_tenants_connected`                    | Gauge   | Number of connected tenants per Realtime node. Use this to understand tenant distribution across your cluster and identify load imbalances.       | Per-Node         | `/metrics`        |\n| `realtime_connections_global_connected`         | Gauge   | Node total of active WebSocket connections across all tenants. Aggregated without a `tenant` label for cluster-wide dashboards.                   | Global Aggregate | `/metrics`        |\n| `realtime_connections_global_connected_cluster` | Gauge   | Cluster-wide total of active WebSocket connections across all tenants.                                                                            | Global Aggregate | `/metrics`        |\n| `realtime_connections_connected`                | Gauge   | Active WebSocket connections that have at least one subscribed channel. Indicates active client engagement with Realtime features.                | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_connections_connected_cluster`        | Gauge   | Cluster-wide active WebSocket connections for each individual tenant.                                                                             | **Per-Tenant**   | `/tenant-metrics` |\n| `phoenix_connections_total`                     | Gauge   | Total open connections to the Ranch listener (includes idle connections waiting for data).                                                        | Per-Node         | `/metrics`        |\n| `phoenix_connections_active`                    | Gauge   | Connections actively processing a WebSocket frame or HTTP request. Divide by `phoenix_connections_max` to get a saturation ratio.                 | Per-Node         | `/metrics`        |\n| `phoenix_connections_max`                       | Gauge   | The configured Ranch connection limit. When `phoenix_connections_total` approaches this the node is saturated and new connections will be queued. | Per-Node         | `/metrics`        |\n| `realtime_channel_joins`                        | Counter | Rate of channel join attempts per second per tenant.                                                                                              | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_channel_global_joins`                 | Counter | Global rate of channel join attempts per second across all tenants.                                                                               | Global Aggregate | `/metrics`        |\n\n### Event Metrics\n\nThese metrics measure the volume and types of events flowing through your Realtime system, segmented by feature type.\n\n| Metric                                    | Type    | Description                                                                                                                 | Scope            | Endpoint          |\n| ----------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------- | ----------------- |\n| `realtime_channel_events`                 | Counter | Broadcast events per second per tenant.                                                                                     | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_channel_presence_events`        | Counter | Presence events per second per tenant. Includes online/offline status updates and custom presence metadata synchronization. | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_channel_db_events`              | Counter | Postgres Changes events per second per tenant.                                                                              | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_channel_global_events`          | Counter | Global broadcast events per second across all tenants. Compare against per-tenant values for outlier detection.             | Global Aggregate | `/metrics`        |\n| `realtime_channel_global_presence_events` | Counter | Global presence events per second across all tenants.                                                                       | Global Aggregate | `/metrics`        |\n| `realtime_channel_global_db_events`       | Counter | Global Postgres Changes events per second across all tenants.                                                               | Global Aggregate | `/metrics`        |\n\n### Payload & Traffic Metrics\n\nThese metrics provide insight into data volume, message sizes, and network I/O characteristics.\n\n| Metric                                 | Type      | Description                                                                                                                     | Scope            | Endpoint          |\n| -------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ----------------- |\n| `realtime_payload_size_bucket`         | Histogram | Global payload size distribution across all tenants, tagged by message type. Use for cluster-wide sizing and capacity planning. | Global Aggregate | `/metrics`        |\n| `realtime_tenants_payload_size_bucket` | Histogram | Per-tenant payload size distribution. Use this to identify tenants generating unusually large messages.                         | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_channel_input_bytes`         | Counter   | Total ingress bytes per tenant.                                                                                                 | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_channel_output_bytes`        | Counter   | Total egress bytes per tenant.                                                                                                  | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_channel_global_input_bytes`  | Counter   | Global total ingress bytes across all tenants.                                                                                  | Global Aggregate | `/metrics`        |\n| `realtime_channel_global_output_bytes` | Counter   | Global total egress bytes across all tenants.                                                                                   | Global Aggregate | `/metrics`        |\n\n### Latency & Performance Metrics\n\nThese metrics measure end-to-end latency and processing performance across different Realtime operations.\n\n| Metric                                                                 | Type      | Description                                                                                                      | Scope            | Endpoint          |\n| ---------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------- | ---------------- | ----------------- |\n| `realtime_replication_poller_query_duration_bucket`                    | Histogram | Postgres Changes query latency in milliseconds per tenant. High values may indicate database performance issues. | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_replication_poller_query_duration_count`                     | Counter   | Number of database polling queries executed per tenant.                                                          | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_tenants_broadcast_from_database_latency_committed_at_bucket` | Histogram | Time from database commit to client broadcast per tenant.                                                        | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_tenants_broadcast_from_database_latency_inserted_at_bucket`  | Histogram | Alternative latency using insert timestamp per tenant.                                                           | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_tenants_replay_bucket`                                       | Histogram | Broadcast replay latency per tenant.                                                                             | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_global_rpc_bucket`                                           | Histogram | Inter-node RPC call latency distribution, tagged by `success` and `mechanism`.                                   | Global Aggregate | `/metrics`        |\n| `realtime_global_rpc_count`                                            | Counter   | Total inter-node RPC calls. Divide failed by total to get error rate.                                            | Global Aggregate | `/metrics`        |\n| `realtime_tenants_read_authorization_check_bucket`                     | Histogram | RLS policy evaluation time for read operations per tenant.                                                       | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_tenants_read_authorization_check_count`                      | Counter   | Number of read authorization checks per tenant.                                                                  | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_tenants_write_authorization_check_bucket`                    | Histogram | RLS policy evaluation time for write operations per tenant.                                                      | **Per-Tenant**   | `/tenant-metrics` |\n| `phoenix_channel_handled_in_duration_milliseconds_bucket`              | Histogram | Time for the application to respond to a channel message. High p99 values indicate slow message handlers.        | Per-Node         | `/metrics`        |\n| `phoenix_socket_connected_duration_milliseconds_bucket`                | Histogram | Time to establish a WebSocket socket connection, tagged by `result`/`transport`/`serializer`.                    | Per-Node         | `/metrics`        |\n\n### Authorization & Error Metrics\n\nThese metrics track security policy enforcement and error rates.\n\n| Metric                          | Type    | Description                                                                                                                                     | Scope            | Endpoint          |\n| ------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ----------------- |\n| `realtime_channel_error`        | Counter | Unhandled channel errors per tenant. Any non-zero value warrants investigation.                                                                 | **Per-Tenant**   | `/tenant-metrics` |\n| `realtime_channel_global_error` | Counter | Global unhandled channel error count across all tenants, tagged by error code.                                                                  | Global Aggregate | `/metrics`        |\n| `phoenix_channel_joined_total`  | Counter | WebSocket channel join attempts tagged by `result` (`ok`/`error`) and `transport`. Use `result=\"error\"` rate to detect client or policy issues. | Per-Node         | `/metrics`        |\n\n### BEAM/Erlang VM Metrics\n\nThese metrics provide insight into the underlying Erlang runtime that powers Realtime, critical for capacity planning and debugging performance issues.\n\nAll BEAM/Erlang VM metrics are served from `GET /metrics`.\n\n#### Memory Metrics\n\n| Metric                                    | Type  | Description                                                                                                                                                               |\n| ----------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `beam_memory_allocated_bytes`             | Gauge | Total memory allocated by the Erlang VM. Compare this to the container memory limit to ensure you have headroom. Steady increase may indicate a memory leak.              |\n| `beam_memory_atom_total_bytes`            | Gauge | Memory used by the atom table. Atoms in Erlang are never garbage collected, so this should remain relatively stable. Unbounded growth indicates a bug creating new atoms. |\n| `beam_memory_binary_total_bytes`          | Gauge | Memory used by binary data (WebSocket payloads, database results). This metric closely correlates with active connection volume and message sizes.                        |\n| `beam_memory_code_total_bytes`            | Gauge | Memory used by compiled Erlang bytecode. Changes only during code reloads and should remain stable in production.                                                         |\n| `beam_memory_ets_total_bytes`             | Gauge | Memory used by ETS (in-memory tables) including channel subscriptions and presence state. Monitor this to understand session storage overhead.                            |\n| `beam_memory_processes_total_bytes`       | Gauge | Memory used by Erlang processes themselves. Each channel connection and background task consumes memory; this scales with concurrency.                                    |\n| `beam_memory_persistent_term_total_bytes` | Gauge | Memory used by persistent terms (immutable shared state). Should be minimal and stable in typical Realtime deployments.                                                   |\n\n#### Process & Resource Metrics\n\n| Metric                     | Type  | Description                                                                                                                                                           |\n| -------------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `beam_stats_process_count` | Gauge | Number of active Erlang processes. Each WebSocket connection spawns processes; high values correlate with connection count. Sudden spikes may indicate process leaks. |\n| `beam_stats_port_count`    | Gauge | Number of open port connections (network sockets, pipes). Should correlate roughly with connection count plus internal cluster communications.                        |\n| `beam_stats_ets_count`     | Gauge | Number of active ETS tables used for caching and state. Changes reflect dynamic supervisor activity and feature usage patterns.                                       |\n| `beam_stats_atom_count`    | Gauge | Total atoms in the atom table. Should remain relatively stable; unbounded growth indicates code bugs.                                                                 |\n\n#### Performance Metrics\n\n| Metric                                 | Type    | Description                                                                                                                                                           |\n| -------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `beam_stats_uptime_milliseconds_count` | Counter | Node uptime in milliseconds. Use this to track restarts and validate deployment stability. Unexpected resets indicate crashes.                                        |\n| `beam_stats_port_io_byte_count`        | Counter | Total bytes transferred through network ports. Compare ingress and egress to identify asymmetric traffic patterns.                                                    |\n| `beam_stats_gc_count`                  | Counter | Garbage collection events executed by the Erlang VM. Frequent GC indicates high memory churn; infrequent GC suggests stable state.                                    |\n| `beam_stats_gc_reclaimed_bytes`        | Counter | Bytes reclaimed by garbage collection. Divide by GC count to understand average cleanup size. Low reclaim per GC may indicate inefficient memory allocation patterns. |\n| `beam_stats_reduction_count`           | Counter | Total reductions (work units) executed by the VM. Correlates with CPU usage; high reduction rates under stable load indicate inefficient algorithms.                  |\n| `beam_stats_context_switch_count`      | Counter | Process context switches by the Erlang scheduler. High values indicate contention between many processes; compare with process count to gauge congestion.             |\n| `beam_stats_active_task_count`         | Gauge   | Tasks currently executing on dirty schedulers (non-Erlang operations). High values indicate CPU-bound work or blocking I/O.                                           |\n| `beam_stats_run_queue_count`           | Gauge   | Processes waiting to be scheduled. High values indicate CPU saturation; the node cannot keep up with work demand.                                                     |\n\n### Infrastructure Metrics\n\nThese metrics expose system-level resource usage and inter-node cluster communication. All infrastructure metrics are served from `GET /metrics`.\n\n#### Node Metrics\n\n| Metric            | Type  | Description                                                                                                                                             |\n| ----------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `osmon_cpu_util`  | Gauge | Current CPU utilization percentage (0-100). Monitor this to trigger horizontal scaling and identify CPU-bound bottlenecks.                              |\n| `osmon_cpu_avg1`  | Gauge | 1-minute CPU load average. Sharp increases indicate sudden load spikes; values > CPU count indicate sustained overload.                                 |\n| `osmon_cpu_avg5`  | Gauge | 5-minute CPU load average. Smooths short-term spikes; use this to detect sustained load increases.                                                      |\n| `osmon_cpu_avg15` | Gauge | 15-minute CPU load average. Indicates long-term trends; use for capacity planning and detecting gradual load growth.                                    |\n| `osmon_ram_usage` | Gauge | RAM utilization percentage (0-100). Combined with `beam_memory_allocated_bytes`, this indicates kernel memory overhead and other processes on the node. |\n\n#### Distributed System Metrics\n\n| Metric                       | Type    | Description                                                                                                                                 |\n| ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- |\n| `gen_rpc_queue_size_bytes`   | Gauge   | Outbound queue size for gen_rpc inter-node communication in bytes. Large values indicate a receiving node cannot keep up with message rate. |\n| `gen_rpc_send_pending_bytes` | Gauge   | Bytes pending transmission in gen_rpc queues. Combined with queue size, helps identify network saturation or slow receivers.                |\n| `gen_rpc_send_bytes`         | Counter | Total bytes sent via gen_rpc across the cluster. Monitor this to understand inter-node traffic and plan network capacity.                   |\n| `gen_rpc_recv_bytes`         | Counter | Total bytes received via gen_rpc from other nodes. Compare with send bytes to identify asymmetric communication patterns.                   |\n| `dist_queue_size`            | Gauge   | Erlang distribution queue size for cluster communication. High values indicate network congestion or unbalanced load across nodes.          |\n| `dist_send_pending_bytes`    | Gauge   | Bytes pending in Erlang distribution queues. Works with queue size to diagnose cluster communication issues.                                |\n| `dist_send_bytes`            | Counter | Total bytes sent via Erlang distribution protocol. Includes all cluster metadata and RPC traffic.                                           |\n| `dist_recv_bytes`            | Counter | Total bytes received via Erlang distribution protocol. Compare with send to validate symmetric communication.                               |\n\n## License\n\nThis repo is licensed under Apache 2.0.\n\n## Credits\n\n- [Phoenix](https://github.com/phoenixframework/phoenix) - `Realtime` server is built with the amazing Elixir framework.\n- [Phoenix Channels JavaScript Client](https://github.com/phoenixframework/phoenix/tree/master/assets/js/phoenix) - [@supabase/realtime-js](https://github.com/supabase/realtime-js) client library heavily draws from the Phoenix Channels client library.\n"
  },
  {
    "path": "assets/css/app.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;"
  },
  {
    "path": "assets/css/phoenix.css",
    "content": "/* Includes some default style for the starter application.\n * This can be safely deleted to start fresh.\n */\n\n/* Milligram v1.3.0 https://milligram.github.io\n * Copyright (c) 2017 CJ Patoilo Licensed under the MIT license\n */\n\n*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#000000;font-family:'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#0069d9;border:0.1rem solid #0069d9;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#0069d9;border-color:#0069d9}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#0069d9}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#0069d9}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#0069d9}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#0069d9}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #0069d9;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='email'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem;width:100%}input[type='email']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,textarea:focus,select:focus{border-color:#0069d9;outline:0}select{background:url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"14\" viewBox=\"0 0 29 14\" width=\"29\"><path fill=\"%23d1d1d1\" d=\"M9.37727 3.625l5.08154 6.93523L19.54036 3.625\"/></svg>') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"14\" viewBox=\"0 0 29 14\" width=\"29\"><path fill=\"%230069d9\" d=\"M9.37727 3.625l5.08154 6.93523L19.54036 3.625\"/></svg>')}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{-ms-grid-row-align:center;align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#0069d9;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem;text-align:left}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right}\n\n/* General style */\nh1{font-size: 3.6rem; line-height: 1.25}\nh2{font-size: 2.8rem; line-height: 1.3}\nh3{font-size: 2.2rem; letter-spacing: -.08rem; line-height: 1.35}\nh4{font-size: 1.8rem; letter-spacing: -.05rem; line-height: 1.5}\nh5{font-size: 1.6rem; letter-spacing: 0; line-height: 1.4}\nh6{font-size: 1.4rem; letter-spacing: 0; line-height: 1.2}\npre{padding: 1em;}\n\n.container{\n  margin: 0 auto;\n  max-width: 80.0rem;\n  padding: 0 2.0rem;\n  position: relative;\n  width: 100%\n}\nselect {\n  width: auto;\n}\n\n/* Phoenix promo and logo */\n.phx-hero {\n  text-align: center;\n  border-bottom: 1px solid #e3e3e3;\n  background: #eee;\n  border-radius: 6px;\n  padding: 3em 3em 1em;\n  margin-bottom: 3rem;\n  font-weight: 200;\n  font-size: 120%;\n}\n.phx-hero input {\n  background: #ffffff;\n}\n.phx-logo {\n  min-width: 300px;\n  margin: 1rem;\n  display: block;\n}\n.phx-logo img {\n  width: auto;\n  display: block;\n}\n\n/* Headers */\nheader {\n  width: 100%;\n  background: #fdfdfd;\n  border-bottom: 1px solid #eaeaea;\n  margin-bottom: 2rem;\n}\nheader section {\n  align-items: center;\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n}\nheader section :first-child {\n  order: 2;\n}\nheader section :last-child {\n  order: 1;\n}\nheader nav ul,\nheader nav li {\n  margin: 0;\n  padding: 0;\n  display: block;\n  text-align: right;\n  white-space: nowrap;\n}\nheader nav ul {\n  margin: 1rem;\n  margin-top: 0;\n}\nheader nav a {\n  display: block;\n}\n\n@media (min-width: 40.0rem) { /* Small devices (landscape phones, 576px and up) */\n  header section {\n    flex-direction: row;\n  }\n  header nav ul {\n    margin: 1rem;\n  }\n  .phx-logo {\n    flex-basis: 527px;\n    margin: 2rem 1rem;\n  }\n}\n"
  },
  {
    "path": "assets/js/app.js",
    "content": "import \"../css/app.css\";\nimport \"phoenix_html\";\nimport { Socket } from \"phoenix\";\nimport { LiveSocket } from \"phoenix_live_view\";\nimport topbar from \"../vendor/topbar\";\nimport { createClient } from \"@supabase/supabase-js\";\n\n// LiveView is managing this page because we have Phoenix running\n// We're using LiveView to handle the Realtime client via LiveView Hooks\n\nconst Hooks = {};\nHooks.payload = {\n  initRealtime(\n    channelName,\n    host,\n    log_level,\n    token,\n    schema,\n    table,\n    filter,\n    bearer,\n    enable_presence,\n    enable_db_changes,\n    private_channel\n  ) {\n    // Instantiate our client with the Realtime server and params to connect with\n    const opts = {\n      realtime: {\n        params: {\n          log_level: log_level,\n        },\n      },\n    };\n\n    this.realtimeSocket = createClient(host, token, opts);\n\n    if (bearer !== \"\") {\n      this.realtimeSocket.realtime.setAuth(bearer);\n    }\n\n    private_channel = private_channel === \"true\";\n\n    // Join the Channel 'any'\n    // Channels can be named anything\n    // All clients on the same Channel will get messages sent to that Channel\n    this.channel = this.realtimeSocket.channel(channelName, {\n      config: {\n        broadcast: { self: true },\n        private: private_channel,\n      },\n    });\n\n    // Hack to confirm Postgres is subscribed\n    // Need to add 'extension' key in the 'payload'\n    this.channel.on(\"system\", {}, (payload) => {\n      if (payload.extension === \"postgres_changes\" && payload.status === \"ok\") {\n        this.pushEventTo(\"#conn_info\", \"postgres_subscribed\", {});\n      }\n      const ts = new Date();\n      const line = `<tr class=\"bg-white border-b hover:bg-gray-50\">\n      <td class=\"py-4 px-6\">SYSTEM</td>\n      <td class=\"py-4 px-6\">${ts.toISOString()}</td>\n      <td class=\"py-4 px-6\">${JSON.stringify(payload)}</td>\n    </tr>`;\n      const list = document.querySelector(\"#plist\");\n      list.innerHTML = line + list.innerHTML;\n    });\n\n    // Listen for all (`*`) `broadcast` events\n    // The event name can by anything\n    // Match on specific event names to filter for only those types of events and do something with them\n    this.channel.on(\"broadcast\", { event: \"*\" }, (payload) => {\n      const ts = new Date();\n      const line = `<tr class=\"bg-white border-b hover:bg-gray-50\">\n        <td class=\"py-4 px-6\">BROADCAST</td>\n        <td class=\"py-4 px-6\">${ts.toISOString()}</td>\n        <td class=\"py-4 px-6\">${JSON.stringify(payload)}</td>\n      </tr>`;\n      const list = document.querySelector(\"#plist\");\n      list.innerHTML = line + list.innerHTML;\n    });\n\n    // Listen for all (`*`) `presence` events\n    if (enable_presence === \"true\") {\n      console.log(\"enable_presence\", enable_presence);\n\n      this.channel.on(\"presence\", { event: \"*\" }, (payload) => {\n        this.pushEventTo(\"#conn_info\", \"presence_subscribed\", {});\n        const ts = new Date();\n        const line = `<tr class=\"bg-white border-b hover:bg-gray-50\">\n        <td class=\"py-4 px-6\">PRESENCE</td>\n        <td class=\"py-4 px-6\">${ts.toISOString()}</td>\n        <td class=\"py-4 px-6\">${JSON.stringify(payload)}</td>\n      </tr>`;\n        const list = document.querySelector(\"#plist\");\n        list.innerHTML = line + list.innerHTML;\n      });\n    }\n\n    // Listen for all (`*`) `postgres_changes` events on tables in the `public` schema\n    if (enable_db_changes === \"true\") {\n      const postgres_changes_opts = {\n        event: \"*\",\n        schema: schema,\n        table: table,\n      };\n      if (filter !== \"\") {\n        postgres_changes_opts.filter = filter;\n      }\n      this.channel.on(\"postgres_changes\", postgres_changes_opts, (payload) => {\n        const ts = performance.now() + performance.timeOrigin;\n        const iso_ts = new Date();\n        const payload_ts = Date.parse(payload.commit_timestamp);\n        const latency = ts - payload_ts;\n        const line = `<tr class=\"bg-white border-b hover:bg-gray-50\">\n        <td class=\"py-4 px-6\">POSTGRES</td>\n        <td class=\"py-4 px-6\">${iso_ts.toISOString()}</td>\n        <td class=\"py-4 px-6\">\n          <div class=\"pb-3\">${JSON.stringify(payload)}</div>\n          <div class=\"pt-3 border-t hover:bg-gray-50\">Latency: ${latency.toFixed(\n            1\n          )} ms</div>\n        </td>\n      </tr>`;\n        const list = document.querySelector(\"#plist\");\n        list.innerHTML = line + list.innerHTML;\n      });\n    }\n\n    // Finally, subscribe to the Channel we just setup\n    this.channel.subscribe(async (status, error) => {\n      if (status === \"SUBSCRIBED\") {\n        console.log(`Realtime Channel status: ${status}`);\n\n        // Let LiveView know we connected so we can update the button text\n        this.pushEventTo(\"#conn_info\", \"broadcast_subscribed\", { host: host });\n\n        // Save params to local storage if `SUBSCRIBED`\n        localStorage.setItem(\"host\", host);\n        localStorage.setItem(\"token\", token);\n        localStorage.setItem(\"log_level\", log_level);\n        localStorage.setItem(\"channel\", channelName);\n        localStorage.setItem(\"schema\", schema);\n        localStorage.setItem(\"table\", table);\n        localStorage.setItem(\"filter\", filter);\n        localStorage.setItem(\"bearer\", bearer);\n        localStorage.setItem(\"enable_presence\", enable_presence);\n        localStorage.setItem(\"enable_db_changes\", enable_db_changes);\n        localStorage.setItem(\"private_channel\", private_channel);\n\n        // Initiate Presence for a connected user\n        // Now when a new user connects and sends a `TRACK` message all clients will receive a message like:\n        // {\n        //     \"event\":\"join\",\n        //     \"key\":\"2b88be54-3b41-11ed-9887-1a9e1a785cf8\",\n        //     \"currentPresences\":[\n        //\n        //     ],\n        //     \"newPresences\":[\n        //        {\n        //           \"name\":\"realtime_presence_55\",\n        //           \"t\":1968.1000000238419,\n        //           \"presence_ref\":\"Fxd_ZWlhIIfuIwlD\"\n        //        }\n        //     ]\n        // }\n        //\n        // And when `TRACK`ed users leave we'll receive an event like:\n        //\n        // {\n        //     \"event\":\"leave\",\n        //     \"key\":\"2b88be54-3b41-11ed-9887-1a9e1a785cf8\",\n        //     \"currentPresences\":[\n        //\n        //     ],\n        //     \"leftPresences\":[\n        //        {\n        //           \"name\":\"realtime_presence_55\",\n        //           \"t\":1968.1000000238419,\n        //           \"presence_ref\":\"Fxd_ZWlhIIfuIwlD\"\n        //        }\n        //     ]\n        // }\n        if (enable_presence === \"true\") {\n          const name = \"user_name_\" + Math.floor(Math.random() * 100);\n          await this.channel.track({\n            name: name,\n            t: performance.now(),\n          });\n        }\n      } else {\n        console.error(`Realtime Channel error status: ${status}`);\n        console.error(`Realtime Channel error: ${error}`);\n      }\n    });\n  },\n\n  sendRealtime(event, payload) {\n    // Send a `broadcast` message over the Channel\n    // All connected clients will receive this message if they're subscribed\n    // to `broadcast` events and matching on the `event` name or using `*` to match all event names\n    this.channel.send({\n      type: \"broadcast\",\n      event: event,\n      payload: payload,\n    });\n  },\n\n  disconnectRealtime() {\n    // Send a `broadcast` message over the Channel\n    // All connected clients will receive this message if they're subscribed\n    // to `broadcast` events and matching on the `event` name or using `*` to match all event names\n    this.channel.unsubscribe();\n  },\n\n  clearLocalStorage() {\n    localStorage.clear();\n  },\n\n  mounted() {\n    const params = {\n      log_level: localStorage.getItem(\"log_level\"),\n      token: localStorage.getItem(\"token\"),\n      host: localStorage.getItem(\"host\"),\n      channel: localStorage.getItem(\"channel\"),\n      schema: localStorage.getItem(\"schema\"),\n      table: localStorage.getItem(\"table\"),\n      filter: localStorage.getItem(\"filter\"),\n      bearer: localStorage.getItem(\"bearer\"),\n      enable_presence: localStorage.getItem(\"enable_presence\"),\n      enable_db_changes: localStorage.getItem(\"enable_db_changes\"),\n      private_channel: localStorage.getItem(\"private_channel\"),\n    };\n\n    this.pushEventTo(\"#conn_form\", \"local_storage\", params);\n\n    this.handleEvent(\"connect\", ({ connection }) =>\n      this.initRealtime(\n        connection.channel,\n        connection.host,\n        connection.log_level,\n        connection.token,\n        connection.schema,\n        connection.table,\n        connection.filter,\n        connection.bearer,\n        connection.enable_presence,\n        connection.enable_db_changes,\n        connection.private_channel\n      )\n    );\n\n    this.handleEvent(\"send_message\", ({ message }) =>\n      this.sendRealtime(message.event, message.payload)\n    );\n\n    this.handleEvent(\"disconnect\", () => this.disconnectRealtime());\n\n    this.handleEvent(\"clear_local_storage\", () => this.clearLocalStorage());\n  },\n};\n\nHooks.latency = {\n  mounted() {\n    this.handleEvent(\"ping\", (params) => this.pong(params));\n  },\n\n  pong(params) {\n    this.pushEventTo(\"#ping\", \"pong\", params);\n  },\n};\n\nconst csrfToken = document\n  .querySelector(\"meta[name='csrf-token']\")\n  .getAttribute(\"content\");\n\nconst liveSocket = new LiveSocket(\"/live\", Socket, {\n  hooks: Hooks,\n  params: { _csrf_token: csrfToken },\n});\n\ntopbar.config({ barColors: { 0: \"#29d\" }, shadowColor: \"rgba(0, 0, 0, .3)\" });\nwindow.addEventListener(\"phx:page-loading-start\", () => topbar.show());\nwindow.addEventListener(\"phx:page-loading-stop\", () => topbar.hide());\n\nliveSocket.connect();\n\nwindow.liveSocket = liveSocket;\n"
  },
  {
    "path": "assets/package.json",
    "content": "{\n  \"dependencies\": {\n    \"@supabase/supabase-js\": \"2.100.0-canary.0\"\n  }\n}\n"
  },
  {
    "path": "assets/tailwind.config.js",
    "content": "const plugin = require(\"tailwindcss/plugin\")\nconst colors = require('tailwindcss/colors')\n\nmodule.exports = {\n  content: [\n    './js/**/*.js',\n    '../lib/*_web.ex',\n    '../lib/*_web/**/*.*ex',\n  ],\n  theme: {\n    colors: {\n      transparent: 'transparent',\n      current: 'currentColor',\n      black: colors.black,\n      white: colors.white,\n      gray: colors.gray,\n      emerald: colors.emerald,\n      indigo: colors.indigo,\n      yellow: colors.yellow,\n      green: colors.green\n    },\n    fontFamily: {\n      sans: ['custom-font', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'],\n      mono: ['Source Code Pro', 'Menlo', 'monospace'],\n    },\n  },\n  plugins: [\n    require(\"@tailwindcss/forms\"),\n    require('@tailwindcss/typography'),\n    plugin(({addVariant}) => addVariant(\"phx-no-feedback\", [\".phx-no-feedback&\", \".phx-no-feedback &\"])),\n    plugin(({addVariant}) => addVariant(\"phx-click-loading\", [\".phx-click-loading&\", \".phx-click-loading &\"])),\n    plugin(({addVariant}) => addVariant(\"phx-submit-loading\", [\".phx-submit-loading&\", \".phx-submit-loading &\"])),\n    plugin(({addVariant}) => addVariant(\"phx-change-loading\", [\".phx-change-loading&\", \".phx-change-loading &\"]))\n  ]\n};\n"
  },
  {
    "path": "assets/vendor/topbar.js",
    "content": "/**\n * @license MIT\n * topbar 1.0.0, 2021-01-06\n * https://buunguyen.github.io/topbar\n * Copyright (c) 2021 Buu Nguyen\n */\n(function (window, document) {\n  \"use strict\";\n\n  // https://gist.github.com/paulirish/1579671\n  (function () {\n    var lastTime = 0;\n    var vendors = [\"ms\", \"moz\", \"webkit\", \"o\"];\n    for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {\n      window.requestAnimationFrame =\n        window[vendors[x] + \"RequestAnimationFrame\"];\n      window.cancelAnimationFrame =\n        window[vendors[x] + \"CancelAnimationFrame\"] ||\n        window[vendors[x] + \"CancelRequestAnimationFrame\"];\n    }\n    if (!window.requestAnimationFrame)\n      window.requestAnimationFrame = function (callback, element) {\n        var currTime = new Date().getTime();\n        var timeToCall = Math.max(0, 16 - (currTime - lastTime));\n        var id = window.setTimeout(function () {\n          callback(currTime + timeToCall);\n        }, timeToCall);\n        lastTime = currTime + timeToCall;\n        return id;\n      };\n    if (!window.cancelAnimationFrame)\n      window.cancelAnimationFrame = function (id) {\n        clearTimeout(id);\n      };\n  })();\n\n  var canvas,\n    progressTimerId,\n    fadeTimerId,\n    currentProgress,\n    showing,\n    addEvent = function (elem, type, handler) {\n      if (elem.addEventListener) elem.addEventListener(type, handler, false);\n      else if (elem.attachEvent) elem.attachEvent(\"on\" + type, handler);\n      else elem[\"on\" + type] = handler;\n    },\n    options = {\n      autoRun: true,\n      barThickness: 3,\n      barColors: {\n        0: \"rgba(26,  188, 156, .9)\",\n        \".25\": \"rgba(52,  152, 219, .9)\",\n        \".50\": \"rgba(241, 196, 15,  .9)\",\n        \".75\": \"rgba(230, 126, 34,  .9)\",\n        \"1.0\": \"rgba(211, 84,  0,   .9)\",\n      },\n      shadowBlur: 10,\n      shadowColor: \"rgba(0,   0,   0,   .6)\",\n      className: null,\n    },\n    repaint = function () {\n      canvas.width = window.innerWidth;\n      canvas.height = options.barThickness * 5; // need space for shadow\n\n      var ctx = canvas.getContext(\"2d\");\n      ctx.shadowBlur = options.shadowBlur;\n      ctx.shadowColor = options.shadowColor;\n\n      var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);\n      for (var stop in options.barColors)\n        lineGradient.addColorStop(stop, options.barColors[stop]);\n      ctx.lineWidth = options.barThickness;\n      ctx.beginPath();\n      ctx.moveTo(0, options.barThickness / 2);\n      ctx.lineTo(\n        Math.ceil(currentProgress * canvas.width),\n        options.barThickness / 2\n      );\n      ctx.strokeStyle = lineGradient;\n      ctx.stroke();\n    },\n    createCanvas = function () {\n      canvas = document.createElement(\"canvas\");\n      var style = canvas.style;\n      style.position = \"fixed\";\n      style.top = style.left = style.right = style.margin = style.padding = 0;\n      style.zIndex = 100001;\n      style.display = \"none\";\n      if (options.className) canvas.classList.add(options.className);\n      document.body.appendChild(canvas);\n      addEvent(window, \"resize\", repaint);\n    },\n    topbar = {\n      config: function (opts) {\n        for (var key in opts)\n          if (options.hasOwnProperty(key)) options[key] = opts[key];\n      },\n      show: function () {\n        if (showing) return;\n        showing = true;\n        if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);\n        if (!canvas) createCanvas();\n        canvas.style.opacity = 1;\n        canvas.style.display = \"block\";\n        topbar.progress(0);\n        if (options.autoRun) {\n          (function loop() {\n            progressTimerId = window.requestAnimationFrame(loop);\n            topbar.progress(\n              \"+\" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)\n            );\n          })();\n        }\n      },\n      progress: function (to) {\n        if (typeof to === \"undefined\") return currentProgress;\n        if (typeof to === \"string\") {\n          to =\n            (to.indexOf(\"+\") >= 0 || to.indexOf(\"-\") >= 0\n              ? currentProgress\n              : 0) + parseFloat(to);\n        }\n        currentProgress = to > 1 ? 1 : to;\n        repaint();\n        return currentProgress;\n      },\n      hide: function () {\n        if (!showing) return;\n        showing = false;\n        if (progressTimerId != null) {\n          window.cancelAnimationFrame(progressTimerId);\n          progressTimerId = null;\n        }\n        (function loop() {\n          if (topbar.progress(\"+.1\") >= 1) {\n            canvas.style.opacity -= 0.05;\n            if (canvas.style.opacity <= 0.05) {\n              canvas.style.display = \"none\";\n              fadeTimerId = null;\n              return;\n            }\n          }\n          fadeTimerId = window.requestAnimationFrame(loop);\n        })();\n      },\n    };\n\n  if (typeof module === \"object\" && typeof module.exports === \"object\") {\n    module.exports = topbar;\n  } else if (typeof define === \"function\" && define.amd) {\n    define(function () {\n      return topbar;\n    });\n  } else {\n    this.topbar = topbar;\n  }\n}.call(this, window, document));\n"
  },
  {
    "path": "beacon/.formatter.exs",
    "content": "# Used by \"mix format\"\n[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"]\n]\n"
  },
  {
    "path": "beacon/.gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\nbeacon-*.tar\n\n# Temporary files, for example, from tests.\n/tmp/\n"
  },
  {
    "path": "beacon/README.md",
    "content": "# Beacon\n\nBeacon is a scalable process group manager. The main use case for this library is to have membership counts available on the cluster without spamming whenever a process joins or leaves a group. A node can have thousands of processes joining and leaving hundreds of groups while sending just the membership count to other nodes.\n\nThe main features are:\n\n* Process pids are available only to the node the where the processes reside;\n* Groups are partitioned locally to allow greater concurrency while joining different groups;\n* Group counts are periodically broadcasted (defaults to every 5 seconds) to update group membership numbers to all participating nodes;\n* Sub-cluster nodes join by using same scope;\n\n## Installation\n\nThe package can be installed by adding `beacon` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:beacon, \"~> 1.0\"}\n  ]\nend\n```\n\n## Using\n\nAdd Beacon to your application's supervision tree specifying a scope name (here it's `:users`)\n\n```elixir\ndef start(_type, _args) do\n  children =\n    [\n      {Beacon, :users},\n      # Or passing options:\n      # {Beacon, [:users, opts]}\n      # See Beacon.start_link/2 for the options\n```\n\nNow process can join groups\n\n```elixir\niex> pid = self()\n#PID<0.852.0>\niex> Beacon.join(:users, {:tenant, 123}, pid)\n:ok\niex> Beacon.local_member_count(:users, {:tenant, 123})\n1\niex> Beacon.local_members(:users, {:tenant, 123})\n[#PID<0.852.0>]\niex> Beacon.local_member?(:users, {:tenant, 123}, pid)\ntrue\n```\n\nFrom another node part of the same scope:\n\n```elixir\niex> Beacon.member_counts(:users)\n%{{:tenant, 123} => 1}\niex> Beacon.member_count(:users, {:tenant, 123})\n1\n```\n"
  },
  {
    "path": "beacon/config/config.exs",
    "content": "import Config\n\n# Print nothing during tests unless captured or a test failure happens\nconfig :logger, backends: [], level: :debug\n"
  },
  {
    "path": "beacon/lib/beacon/adapter/erl_dist.ex",
    "content": "defmodule Beacon.Adapter.ErlDist do\n  @moduledoc false\n\n  import Kernel, except: [send: 2]\n\n  @behaviour Beacon.Adapter\n\n  @impl true\n  def register(scope) do\n    Process.register(self(), Beacon.Supervisor.name(scope))\n    :ok\n  end\n\n  @impl true\n  def broadcast(scope, message) do\n    name = Beacon.Supervisor.name(scope)\n    Enum.each(Node.list(), fn node -> :erlang.send({name, node}, message, [:noconnect]) end)\n  end\n\n  @impl true\n  def broadcast(scope, nodes, message) do\n    name = Beacon.Supervisor.name(scope)\n    Enum.each(nodes, fn node -> :erlang.send({name, node}, message, [:noconnect]) end)\n  end\n\n  @impl true\n  def send(scope, node, message) do\n    :erlang.send({Beacon.Supervisor.name(scope), node}, message, [:noconnect])\n  end\nend\n"
  },
  {
    "path": "beacon/lib/beacon/adapter.ex",
    "content": "defmodule Beacon.Adapter do\n  @moduledoc \"\"\"\n  Behaviour module for Beacon messaging adapters.\n  \"\"\"\n\n  @doc \"Register the current process to receive messages for the given scope\"\n  @callback register(scope :: atom) :: :ok\n\n  @doc \"Broadcast a message to all nodes in the given scope\"\n  @callback broadcast(scope :: atom, message :: term) :: any\n\n  @doc \"Broadcast a message to specific nodes in the given scope\"\n  @callback broadcast(scope :: atom, [node], message :: term) :: any\n\n  @doc \"Send a message to a specific node in the given scope\"\n  @callback send(scope :: atom, node, message :: term) :: any\nend\n"
  },
  {
    "path": "beacon/lib/beacon/partition.ex",
    "content": "defmodule Beacon.Partition do\n  @moduledoc false\n\n  use GenServer\n  require Logger\n\n  defmodule State do\n    @moduledoc false\n    @type t :: %__MODULE__{\n            name: atom,\n            scope: atom,\n            entries_table: atom,\n            monitors: %{{Beacon.group(), pid} => reference}\n          }\n    defstruct [:name, :scope, :entries_table, monitors: %{}]\n  end\n\n  @spec join(atom, Beacon.group(), pid) :: :ok\n  def join(partition_name, group, pid), do: GenServer.call(partition_name, {:join, group, pid})\n\n  @spec leave(atom, Beacon.group(), pid) :: :ok\n  def leave(partition_name, group, pid), do: GenServer.call(partition_name, {:leave, group, pid})\n\n  @spec members(atom, Beacon.group()) :: [pid]\n  def members(partition_name, group) do\n    partition_name\n    |> Beacon.Supervisor.partition_entries_table()\n    |> :ets.select([{{{group, :\"$1\"}}, [], [:\"$1\"]}])\n  end\n\n  @spec member_count(atom, Beacon.group()) :: non_neg_integer\n  def member_count(partition_name, group), do: :ets.lookup_element(partition_name, group, 2, 0)\n\n  @spec member_counts(atom) :: %{Beacon.group() => non_neg_integer}\n  def member_counts(partition_name) do\n    partition_name\n    |> :ets.tab2list()\n    |> Map.new()\n  end\n\n  @spec member?(atom, Beacon.group(), pid) :: boolean\n  def member?(partition_name, group, pid) do\n    partition_name\n    |> Beacon.Supervisor.partition_entries_table()\n    |> :ets.lookup({group, pid})\n    |> case do\n      [{{^group, ^pid}}] -> true\n      [] -> false\n    end\n  end\n\n  @spec groups(atom) :: [Beacon.group()]\n  def groups(partition_name), do: :ets.select(partition_name, [{{:\"$1\", :_}, [], [:\"$1\"]}])\n\n  @spec group_count(atom) :: non_neg_integer\n  def group_count(partition_name), do: :ets.info(partition_name, :size)\n\n  @spec start_link(atom, atom, atom) :: GenServer.on_start()\n  def start_link(scope, partition_name, partition_entries_table),\n    do:\n      GenServer.start_link(__MODULE__, [scope, partition_name, partition_entries_table],\n        name: partition_name\n      )\n\n  @impl true\n  @spec init(any) :: {:ok, State.t()}\n  def init([scope, name, entries_table]) do\n    {:ok, %State{scope: scope, name: name, entries_table: entries_table},\n     {:continue, :rebuild_monitors_and_counters}}\n  end\n\n  @impl true\n  @spec handle_continue(:rebuild_monitors_and_counters, State.t()) :: {:noreply, State.t()}\n  def handle_continue(:rebuild_monitors_and_counters, state) do\n    # Here we delete all counters and rebuild them based on entries table\n    :ets.delete_all_objects(state.name)\n\n    monitors =\n      :ets.tab2list(state.entries_table)\n      |> Enum.reduce(%{}, fn {{group, pid}}, monitors_acc ->\n        ref = Process.monitor(pid, tag: {:DOWN, group})\n        :ets.update_counter(state.name, group, {2, 1}, {group, 0})\n        Map.put(monitors_acc, {group, pid}, ref)\n      end)\n\n    {:noreply, %{state | monitors: monitors}}\n  end\n\n  @impl true\n  @spec handle_call({:join, Beacon.group(), pid}, GenServer.from(), State.t()) ::\n          {:reply, :ok, State.t()}\n  def handle_call({:join, group, pid}, _from, state) do\n    if :ets.insert_new(state.entries_table, {{group, pid}}) do\n      case :ets.lookup_element(state.name, group, 2, 0) do\n        0 ->\n          :ets.insert(state.name, {group, 1})\n          :telemetry.execute([:beacon, state.scope, :group, :occupied], %{}, %{group: group})\n\n        count when count > 0 ->\n          :ets.insert(state.name, {group, count + 1})\n      end\n\n      ref = Process.monitor(pid, tag: {:DOWN, group})\n      monitors = Map.put(state.monitors, {group, pid}, ref)\n      {:reply, :ok, %{state | monitors: monitors}}\n    else\n      {:reply, :ok, state}\n    end\n  end\n\n  def handle_call({:leave, group, pid}, _from, state) do\n    state = remove(group, pid, state)\n    {:reply, :ok, state}\n  end\n\n  @impl true\n  @spec handle_info({{:DOWN, Beacon.group()}, reference, :process, pid, term}, State.t()) ::\n          {:noreply, State.t()}\n  def handle_info({{:DOWN, group}, _ref, :process, pid, _reason}, state) do\n    state = remove(group, pid, state)\n    {:noreply, state}\n  end\n\n  def handle_info(_, state), do: {:noreply, state}\n\n  defp remove(group, pid, state) do\n    case :ets.lookup(state.entries_table, {group, pid}) do\n      [{{^group, ^pid}}] ->\n        :ets.delete(state.entries_table, {group, pid})\n\n        # Delete or decrement counter\n        case :ets.lookup_element(state.name, group, 2, 0) do\n          1 ->\n            :ets.delete(state.name, group)\n            :telemetry.execute([:beacon, state.scope, :group, :vacant], %{}, %{group: group})\n\n          count when count > 1 ->\n            :ets.update_counter(state.name, group, {2, -1})\n        end\n\n      [] ->\n        Logger.warning(\n          \"Beacon[#{node()}|#{state.scope}] Trying to remove an unknown process #{inspect(pid)}\"\n        )\n\n        :ok\n    end\n\n    case Map.pop(state.monitors, {group, pid}) do\n      {nil, _} ->\n        state\n\n      {ref, new_monitors} ->\n        Process.demonitor(ref, [:flush])\n        %{state | monitors: new_monitors}\n    end\n  end\nend\n"
  },
  {
    "path": "beacon/lib/beacon/scope.ex",
    "content": "defmodule Beacon.Scope do\n  @moduledoc false\n  # Responsible to discover and keep track of all Beacon peers in the cluster\n\n  use GenServer\n  require Logger\n\n  @default_broadcast_interval 5_000\n\n  @spec member_counts(atom) :: %{Beacon.group() => non_neg_integer}\n  def member_counts(scope) do\n    scope\n    |> table_name()\n    |> :ets.select([{{:_, :\"$1\"}, [], [:\"$1\"]}])\n    |> Enum.reduce(%{}, fn member_counts, acc ->\n      Map.merge(acc, member_counts, fn _k, v1, v2 -> v1 + v2 end)\n    end)\n  end\n\n  @spec member_count(atom, Beacon.group()) :: non_neg_integer\n  def member_count(scope, group) do\n    scope\n    |> table_name()\n    |> :ets.select([{{:_, %{group => :\"$1\"}}, [], [:\"$1\"]}])\n    |> Enum.sum()\n  end\n\n  @spec member_count(atom, Beacon.group(), node) :: non_neg_integer\n  def member_count(scope, group, node) do\n    case :ets.lookup(table_name(scope), node) do\n      [{^node, member_counts}] -> Map.get(member_counts, group, 0)\n      [] -> 0\n    end\n  end\n\n  @spec groups(atom) :: MapSet.t(Beacon.group())\n  def groups(scope) do\n    scope\n    |> table_name()\n    |> :ets.select([{{:_, :\"$1\"}, [], [:\"$1\"]}])\n    |> Enum.reduce(MapSet.new(), fn member_counts, acc ->\n      member_counts\n      |> Map.keys()\n      |> MapSet.new()\n      |> MapSet.union(acc)\n    end)\n  end\n\n  @typep member_counts :: %{Beacon.group() => non_neg_integer}\n\n  defp table_name(scope), do: :\"#{scope}_beacon_peer_counts\"\n\n  defmodule State do\n    @moduledoc false\n    @type t :: %__MODULE__{\n            scope: atom,\n            message_module: module,\n            broadcast_interval: non_neg_integer,\n            peer_counts_table: :ets.tid(),\n            peers: %{pid => reference}\n          }\n    defstruct [\n      :scope,\n      :message_module,\n      :broadcast_interval,\n      :peer_counts_table,\n      peers: %{}\n    ]\n  end\n\n  @spec start_link(atom, Keyword.t()) :: GenServer.on_start()\n  def start_link(scope, opts \\\\ []), do: GenServer.start_link(__MODULE__, [scope, opts])\n\n  @impl true\n  def init([scope, opts]) do\n    :ok = :net_kernel.monitor_nodes(true)\n\n    peer_counts_table =\n      :ets.new(table_name(scope), [:set, :protected, :named_table, read_concurrency: true])\n\n    broadcast_interval =\n      Keyword.get(opts, :broadcast_interval_in_ms, @default_broadcast_interval)\n\n    message_module = Keyword.get(opts, :message_module, Beacon.Adapter.ErlDist)\n\n    Logger.info(\"Beacon[#{node()}|#{scope}] Starting\")\n\n    :ok = message_module.register(scope)\n\n    {:ok,\n     %State{\n       scope: scope,\n       message_module: message_module,\n       broadcast_interval: broadcast_interval,\n       peer_counts_table: peer_counts_table\n     }, {:continue, :discover}}\n  end\n\n  @impl true\n  @spec handle_continue(:discover, State.t()) :: {:noreply, State.t()}\n  def handle_continue(:discover, state) do\n    state.message_module.broadcast(state.scope, {:discover, self()})\n    Process.send_after(self(), :broadcast_counts, state.broadcast_interval)\n    {:noreply, state}\n  end\n\n  @impl true\n  @spec handle_info(\n          {:discover, pid}\n          | {:sync, pid, member_counts}\n          | :broadcast_counts\n          | {:nodeup, node}\n          | {:nodedown, node}\n          | {:DOWN, reference, :process, pid, term},\n          State.t()\n        ) :: {:noreply, State.t()}\n  # A remote peer is discovering us\n  def handle_info({:discover, peer}, state) do\n    Logger.info(\n      \"Beacon[#{node()}|#{state.scope}] Received DISCOVER request from node #{node(peer)}\"\n    )\n\n    state.message_module.send(\n      state.scope,\n      node(peer),\n      {:sync, self(), Beacon.local_member_counts(state.scope)}\n    )\n\n    # We don't do anything if we already know about this peer\n    if Map.has_key?(state.peers, peer) do\n      Logger.debug(\n        \"Beacon[#{node()}|#{state.scope}] already know peer #{inspect(peer)} from node #{node(peer)}\"\n      )\n\n      {:noreply, state}\n    else\n      Logger.debug(\n        \"Beacon[#{node()}|#{state.scope}] discovered peer #{inspect(peer)} from node #{node(peer)}\"\n      )\n\n      ref = Process.monitor(peer)\n      new_peers = Map.put(state.peers, peer, ref)\n      state.message_module.send(state.scope, node(peer), {:discover, self()})\n      {:noreply, %State{state | peers: new_peers}}\n    end\n  end\n\n  # A remote peer has sent us its local member counts\n  def handle_info({:sync, peer, member_counts}, state) do\n    :ets.insert(state.peer_counts_table, {node(peer), member_counts})\n    {:noreply, state}\n  end\n\n  # Periodic broadcast of our local member counts to all known peers\n  def handle_info(:broadcast_counts, state) do\n    nodes =\n      state.peers\n      |> Map.keys()\n      |> Enum.map(&node/1)\n\n    state.message_module.broadcast(\n      state.scope,\n      nodes,\n      {:sync, self(), Beacon.local_member_counts(state.scope)}\n    )\n\n    Process.send_after(self(), :broadcast_counts, state.broadcast_interval)\n    {:noreply, state}\n  end\n\n  # Do nothing if the node that came up is our own node\n  def handle_info({:nodeup, node}, state) when node == node(), do: {:noreply, state}\n\n  # Send a discover message to the node that just connected\n  def handle_info({:nodeup, node}, state) do\n    :telemetry.execute([:beacon, state.scope, :node, :up], %{}, %{node: node})\n\n    Logger.info(\n      \"Beacon[#{node()}|#{state.scope}] Node #{node} has joined the cluster, sending discover message\"\n    )\n\n    state.message_module.send(state.scope, node, {:discover, self()})\n    {:noreply, state}\n  end\n\n  # Do nothing and wait for the DOWN message from monitor\n  def handle_info({:nodedown, _node}, state), do: {:noreply, state}\n\n  # A remote peer has disconnected/crashed\n  # We forget about it and remove its member counts\n  def handle_info({:DOWN, ref, :process, peer, reason}, state) do\n    Logger.info(\n      \"Beacon[#{node()}|#{state.scope}] Scope process is DOWN on node #{node(peer)}: #{inspect(reason)}\"\n    )\n\n    case Map.pop(state.peers, peer) do\n      {nil, _} ->\n        {:noreply, state}\n\n      {^ref, new_peers} ->\n        :ets.delete(state.peer_counts_table, node(peer))\n        :telemetry.execute([:beacon, state.scope, :node, :down], %{}, %{node: node(peer)})\n        {:noreply, %State{state | peers: new_peers}}\n    end\n  end\n\n  def handle_info(_msg, state), do: {:noreply, state}\nend\n"
  },
  {
    "path": "beacon/lib/beacon/supervisor.ex",
    "content": "defmodule Beacon.Supervisor do\n  @moduledoc false\n  use Supervisor\n\n  def name(scope), do: :\"#{scope}_beacon\"\n  def supervisor_name(scope), do: :\"#{scope}_beacon_supervisor\"\n  def partition_name(scope, partition), do: :\"#{scope}_beacon_partition_#{partition}\"\n  def partition_entries_table(partition_name), do: :\"#{partition_name}_entries\"\n\n  @spec partition(atom, Scope.group()) :: atom\n  def partition(scope, group) do\n    case :persistent_term.get(scope, :unknown) do\n      :unknown -> raise \"Beacon for scope #{inspect(scope)} is not started\"\n      partition_names -> elem(partition_names, :erlang.phash2(group, tuple_size(partition_names)))\n    end\n  end\n\n  @spec partitions(atom) :: [atom]\n  def partitions(scope) do\n    case :persistent_term.get(scope, :unknown) do\n      :unknown -> raise \"Beacon for scope #{inspect(scope)} is not started\"\n      partition_names -> Tuple.to_list(partition_names)\n    end\n  end\n\n  @spec start_link(atom, pos_integer(), Keyword.t()) :: Supervisor.on_start()\n  def start_link(scope, partitions, opts \\\\ []) do\n    args = [scope, partitions, opts]\n    Supervisor.start_link(__MODULE__, args, name: supervisor_name(scope))\n  end\n\n  @impl true\n  def init([scope, partitions, opts]) do\n    children =\n      for i <- 0..(partitions - 1) do\n        partition_name = partition_name(scope, i)\n        partition_entries_table = partition_entries_table(partition_name)\n\n        ^partition_entries_table =\n          :ets.new(partition_entries_table, [:set, :public, :named_table, read_concurrency: true])\n\n        ^partition_name =\n          :ets.new(partition_name, [:set, :public, :named_table, read_concurrency: true])\n\n        %{\n          id: i,\n          start: {Beacon.Partition, :start_link, [scope, partition_name, partition_entries_table]}\n        }\n      end\n\n    partition_names = for i <- 0..(partitions - 1), do: partition_name(scope, i)\n\n    :persistent_term.put(scope, List.to_tuple(partition_names))\n\n    children = [\n      %{id: :scope, start: {Beacon.Scope, :start_link, [scope, opts]}} | children\n    ]\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\nend\n"
  },
  {
    "path": "beacon/lib/beacon.ex",
    "content": "defmodule Beacon do\n  @moduledoc \"\"\"\n  Distributed process group membership tracking.\n  \"\"\"\n\n  alias Beacon.Partition\n  alias Beacon.Scope\n\n  @type group :: any\n  @type start_option ::\n          {:partitions, pos_integer()} | {:broadcast_interval_in_ms, non_neg_integer()}\n\n  @doc \"Returns a supervisor child specification for a Beacon scope\"\n  def child_spec([scope]) when is_atom(scope), do: child_spec([scope, []])\n  def child_spec(scope) when is_atom(scope), do: child_spec([scope, []])\n\n  def child_spec([scope, opts]) when is_atom(scope) and is_list(opts) do\n    %{\n      id: Beacon,\n      start: {__MODULE__, :start_link, [scope, opts]},\n      type: :supervisor\n    }\n  end\n\n  @doc \"\"\"\n  Starts the Beacon supervision tree for `scope`.\n\n  Options:\n\n  * `:partitions` - number of partitions to use (default: number of schedulers online)\n  * `:broadcast_interval_in_ms`: - interval in milliseconds to broadcast membership counts to other nodes (default: 5000 ms)\n  * `:message_module` - module implementing `Beacon.Adapter` behaviour (default: `Beacon.Adapter.ErlDist`)\n  \"\"\"\n  @spec start_link(atom, [start_option]) :: Supervisor.on_start()\n  def start_link(scope, opts \\\\ []) when is_atom(scope) do\n    {partitions, opts} = Keyword.pop(opts, :partitions, System.schedulers_online())\n    broadcast_interval_in_ms = Keyword.get(opts, :broadcast_interval_in_ms)\n\n    if not (is_integer(partitions) and partitions >= 1) do\n      raise ArgumentError,\n            \"expected :partitions to be a positive integer, got: #{inspect(partitions)}\"\n    end\n\n    if broadcast_interval_in_ms != nil and\n         not (is_integer(broadcast_interval_in_ms) and broadcast_interval_in_ms > 0) do\n      raise ArgumentError,\n            \"expected :broadcast_interval_in_ms to be a positive integer, got: #{inspect(broadcast_interval_in_ms)}\"\n    end\n\n    Beacon.Supervisor.start_link(scope, partitions, opts)\n  end\n\n  @doc \"Join pid to group in scope\"\n  @spec join(atom, any, pid) :: :ok | {:error, :not_local}\n  def join(_scope, _group, pid) when is_pid(pid) and node(pid) != node(), do: {:error, :not_local}\n\n  def join(scope, group, pid) when is_atom(scope) and is_pid(pid) do\n    Partition.join(Beacon.Supervisor.partition(scope, group), group, pid)\n  end\n\n  @doc \"Leave pid from group in scope\"\n  @spec leave(atom, group, pid) :: :ok\n  def leave(scope, group, pid) when is_atom(scope) and is_pid(pid) do\n    Partition.leave(Beacon.Supervisor.partition(scope, group), group, pid)\n  end\n\n  @doc \"Get total members count per group in scope\"\n  @spec member_counts(atom) :: %{group => non_neg_integer}\n  def member_counts(scope) when is_atom(scope) do\n    remote_counts = Scope.member_counts(scope)\n\n    scope\n    |> local_member_counts()\n    |> Map.merge(remote_counts, fn _k, v1, v2 -> v1 + v2 end)\n  end\n\n  @doc \"Get total member count of group in scope\"\n  @spec member_count(atom, group) :: non_neg_integer\n  def member_count(scope, group) do\n    local_member_count(scope, group) + Scope.member_count(scope, group)\n  end\n\n  @doc \"Get total member count of group in scope on specific node\"\n  @spec member_count(atom, group, node) :: non_neg_integer\n  def member_count(scope, group, node) when node == node(), do: local_member_count(scope, group)\n  def member_count(scope, group, node), do: Scope.member_count(scope, group, node)\n\n  @doc \"Get local members of group in scope\"\n  @spec local_members(atom, group) :: [pid]\n  def local_members(scope, group) when is_atom(scope) do\n    Partition.members(Beacon.Supervisor.partition(scope, group), group)\n  end\n\n  @doc \"Get local member count of group in scope\"\n  @spec local_member_count(atom, group) :: non_neg_integer\n  def local_member_count(scope, group) when is_atom(scope) do\n    Partition.member_count(Beacon.Supervisor.partition(scope, group), group)\n  end\n\n  @doc \"Get local members count per group in scope\"\n  @spec local_member_counts(atom) :: %{group => non_neg_integer}\n  def local_member_counts(scope) when is_atom(scope) do\n    Enum.reduce(Beacon.Supervisor.partitions(scope), %{}, fn partition_name, acc ->\n      Map.merge(acc, Partition.member_counts(partition_name))\n    end)\n  end\n\n  @doc \"Check if pid is a local member of group in scope\"\n  @spec local_member?(atom, group, pid) :: boolean\n  def local_member?(scope, group, pid) when is_atom(scope) and is_pid(pid) do\n    Partition.member?(Beacon.Supervisor.partition(scope, group), group, pid)\n  end\n\n  @doc \"Get all local groups in scope\"\n  @spec local_groups(atom) :: [group]\n  def local_groups(scope) when is_atom(scope) do\n    Enum.flat_map(Beacon.Supervisor.partitions(scope), fn partition_name ->\n      Partition.groups(partition_name)\n    end)\n  end\n\n  @doc \"Get local group count in scope\"\n  @spec local_group_count(atom) :: non_neg_integer\n  def local_group_count(scope) when is_atom(scope) do\n    Enum.sum_by(Beacon.Supervisor.partitions(scope), fn partition_name ->\n      Partition.group_count(partition_name)\n    end)\n  end\n\n  @doc \"Get groups in scope\"\n  @spec groups(atom) :: [group]\n  def groups(scope) when is_atom(scope) do\n    remote_groups = Scope.groups(scope)\n\n    scope\n    |> local_groups()\n    |> MapSet.new()\n    |> MapSet.union(remote_groups)\n    |> MapSet.to_list()\n  end\n\n  @doc \"Get group count in scope\"\n  @spec group_count(atom) :: non_neg_integer\n  def group_count(scope) when is_atom(scope) do\n    remote_groups = Scope.groups(scope)\n\n    scope\n    |> local_groups()\n    |> MapSet.new()\n    |> MapSet.union(remote_groups)\n    |> MapSet.size()\n  end\nend\n"
  },
  {
    "path": "beacon/mix.exs",
    "content": "defmodule Beacon.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :beacon,\n      version: \"1.0.0\",\n      elixir: \"~> 1.18\",\n      start_permanent: Mix.env() == :prod,\n      elixirc_paths: elixirc_paths(Mix.env()),\n      deps: deps()\n    ]\n  end\n\n  # Run \"mix help compile.app\" to learn about applications.\n  def application do\n    [\n      extra_applications: [:logger]\n    ]\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n  defp elixirc_paths(_), do: [\"lib\"]\n\n  # Run \"mix help deps\" to learn about dependencies.\n  defp deps do\n    [\n      {:telemetry, \"~> 1.3\"},\n      {:mix_test_watch, \"~> 1.0\", only: [:dev, :test], runtime: false}\n      # {:dep_from_git, git: \"https://github.com/elixir-lang/my_dep.git\", tag: \"0.1.0\"}\n    ]\n  end\nend\n"
  },
  {
    "path": "beacon/test/beacon/partition_test.exs",
    "content": "defmodule Beacon.PartitionTest do\n  use ExUnit.Case, async: true\n  alias Beacon.Partition\n\n  @scope __MODULE__\n\n  setup do\n    partition_name = Beacon.Supervisor.partition_name(@scope, System.unique_integer([:positive]))\n    entries_table = Beacon.Supervisor.partition_entries_table(partition_name)\n\n    ^partition_name =\n      :ets.new(partition_name, [:set, :public, :named_table, read_concurrency: true])\n\n    ^entries_table =\n      :ets.new(entries_table, [:set, :public, :named_table, read_concurrency: true])\n\n    spec = %{\n      id: partition_name,\n      start: {Partition, :start_link, [@scope, partition_name, entries_table]},\n      type: :supervisor,\n      restart: :temporary\n    }\n\n    pid = start_supervised!(spec)\n\n    ref =\n      :telemetry_test.attach_event_handlers(self(), [\n        [:beacon, @scope, :group, :occupied],\n        [:beacon, @scope, :group, :vacant]\n      ])\n\n    {:ok, partition_name: partition_name, partition_pid: pid, ref: ref}\n  end\n\n  test \"members/2 returns empty list for non-existent group\", %{partition_name: partition} do\n    assert Partition.members(partition, :nonexistent) == []\n  end\n\n  test \"member_count/2 returns 0 for non-existent group\", %{partition_name: partition} do\n    assert Partition.member_count(partition, :nonexistent) == 0\n  end\n\n  test \"member?/3 returns false for non-member\", %{partition_name: partition} do\n    pid = spawn_link(fn -> Process.sleep(:infinity) end)\n    refute Partition.member?(partition, :group1, pid)\n  end\n\n  test \"join and query member\", %{partition_name: partition, ref: ref} do\n    pid = spawn_link(fn -> Process.sleep(:infinity) end)\n\n    assert :ok = Partition.join(partition, :group9, pid)\n\n    assert Partition.member?(partition, :group9, pid)\n    assert Partition.member_count(partition, :group9) == 1\n    assert pid in Partition.members(partition, :group9)\n    assert_receive {[:beacon, @scope, :group, :occupied], ^ref, %{}, %{group: :group9}}\n\n    refute_receive {_, ^ref, _, _}\n  end\n\n  test \"join multiple times and query member\", %{partition_name: partition, ref: ref} do\n    pid = spawn_link(fn -> Process.sleep(:infinity) end)\n\n    assert :ok = Partition.join(partition, :group1, pid)\n    assert :ok = Partition.join(partition, :group1, pid)\n    assert :ok = Partition.join(partition, :group1, pid)\n\n    assert Partition.member?(partition, :group1, pid)\n    assert Partition.member_count(partition, :group1) == 1\n    assert pid in Partition.members(partition, :group1)\n\n    assert_receive {[:beacon, @scope, :group, :occupied], ^ref, %{}, %{group: :group1}}\n    refute_receive {_, ^ref, _, _}\n  end\n\n  test \"occupied event only when first member joins\", %{partition_name: partition, ref: ref} do\n    pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n    pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n\n    Partition.join(partition, :group1, pid1)\n    Partition.join(partition, :group1, pid2)\n\n    assert_receive {[:beacon, @scope, :group, :occupied], ^ref, %{}, %{group: :group1}}\n\n    refute_receive {_, ^ref, _, _}\n  end\n\n  test \"leave removes member\", %{partition_name: partition, ref: ref} do\n    pid = spawn_link(fn -> Process.sleep(:infinity) end)\n\n    Partition.join(partition, :group1, pid)\n    assert Partition.member?(partition, :group1, pid)\n\n    Partition.leave(partition, :group1, pid)\n    refute Partition.member?(partition, :group1, pid)\n    assert_receive {[:beacon, @scope, :group, :occupied], ^ref, %{}, %{group: :group1}}\n\n    assert_receive {[:beacon, @scope, :group, :vacant], ^ref, %{}, %{group: :group1}}\n\n    refute_receive {_, ^ref, _, _}\n  end\n\n  test \"vacant event only when no members left\", %{partition_name: partition, ref: ref} do\n    pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n    pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n\n    Partition.join(partition, :group1, pid1)\n    Partition.join(partition, :group1, pid2)\n\n    assert_receive {[:beacon, @scope, :group, :occupied], ^ref, %{}, %{group: :group1}}\n    refute_receive {_, ^ref, _, _}\n\n    Partition.leave(partition, :group1, pid1)\n\n    refute_receive {_, ^ref, _, _}\n\n    Partition.leave(partition, :group1, pid2)\n\n    assert_receive {[:beacon, @scope, :group, :vacant], ^ref, %{}, %{group: :group1}}\n    refute_receive {_, ^ref, _, _}\n  end\n\n  test \"leave multiple times removes member\", %{partition_name: partition, ref: ref} do\n    pid = spawn_link(fn -> Process.sleep(:infinity) end)\n\n    Partition.join(partition, :group1, pid)\n    assert Partition.member?(partition, :group1, pid)\n\n    Partition.leave(partition, :group1, pid)\n    Partition.leave(partition, :group1, pid)\n    Partition.leave(partition, :group1, pid)\n    refute Partition.member?(partition, :group1, pid)\n    assert_receive {[:beacon, @scope, :group, :occupied], ^ref, %{}, %{group: :group1}}\n\n    assert_receive {[:beacon, @scope, :group, :vacant], ^ref, %{}, %{group: :group1}}\n\n    refute_receive {_, ^ref, _, _}\n  end\n\n  test \"member_counts returns counts for all groups\", %{partition_name: partition} do\n    pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n    pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n    pid3 = spawn_link(fn -> Process.sleep(:infinity) end)\n\n    Partition.join(partition, :group1, pid1)\n    Partition.join(partition, :group1, pid2)\n    Partition.join(partition, :group2, pid3)\n\n    counts = Partition.member_counts(partition)\n    assert map_size(counts) == 2\n    assert counts[:group1] == 2\n    assert counts[:group2] == 1\n  end\n\n  test \"groups returns all groups\", %{partition_name: partition} do\n    pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n    pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n\n    Partition.join(partition, :group1, pid1)\n    Partition.join(partition, :group2, pid2)\n\n    groups = Partition.groups(partition)\n    assert :group1 in groups\n    assert :group2 in groups\n  end\n\n  test \"group_counts returns number of groups\", %{partition_name: partition} do\n    pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n    pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n    pid3 = spawn_link(fn -> Process.sleep(:infinity) end)\n    pid4 = spawn_link(fn -> Process.sleep(:infinity) end)\n\n    Partition.join(partition, :group1, pid1)\n    Partition.join(partition, :group1, pid2)\n    Partition.join(partition, :group2, pid3)\n    Partition.join(partition, :group3, pid4)\n\n    assert Partition.group_count(partition) == 3\n  end\n\n  test \"process death removes member from group\", %{partition_name: partition} do\n    pid = spawn(fn -> Process.sleep(:infinity) end)\n\n    Partition.join(partition, :group1, pid)\n    assert Partition.member?(partition, :group1, pid)\n\n    Process.exit(pid, :kill)\n    Process.sleep(50)\n\n    refute Partition.member?(partition, :group1, pid)\n    assert Partition.member_count(partition, :group1) == 0\n  end\n\n  test \"partition recovery monitors processes again\", %{\n    partition_name: partition,\n    partition_pid: partition_pid\n  } do\n    pid1 = spawn(fn -> Process.sleep(:infinity) end)\n    pid2 = spawn(fn -> Process.sleep(:infinity) end)\n\n    Partition.join(partition, :group1, pid1)\n    Partition.join(partition, :group2, pid2)\n\n    monitors = Process.info(partition_pid, [:monitors])[:monitors] |> Enum.map(&elem(&1, 1))\n    assert length(monitors)\n    assert monitors |> Enum.member?(pid1)\n    assert monitors |> Enum.member?(pid2)\n\n    assert %{{:group1, ^pid1} => _ref1, {:group2, ^pid2} => _ref2} =\n             :sys.get_state(partition_pid).monitors\n\n    Process.monitor(partition_pid)\n    Process.exit(partition_pid, :kill)\n    assert_receive {:DOWN, _ref, :process, ^partition_pid, :killed}\n\n    spec = %{\n      id: :recover,\n      start:\n        {Partition, :start_link,\n         [@scope, partition, Beacon.Supervisor.partition_entries_table(partition)]},\n      type: :supervisor\n    }\n\n    partition_pid = start_supervised!(spec)\n\n    assert %{{:group1, ^pid1} => _ref1, {:group2, ^pid2} => _ref2} =\n             :sys.get_state(partition_pid).monitors\n\n    monitors = Process.info(partition_pid, [:monitors])[:monitors] |> Enum.map(&elem(&1, 1))\n    assert length(monitors)\n    assert monitors |> Enum.member?(pid1)\n    assert monitors |> Enum.member?(pid2)\n\n    assert Partition.member_count(partition, :group1) == 1\n    assert Partition.member_count(partition, :group2) == 1\n\n    assert Partition.member?(partition, :group1, pid1)\n    assert Partition.member?(partition, :group2, pid2)\n  end\nend\n"
  },
  {
    "path": "beacon/test/beacon_test.exs",
    "content": "defmodule BeaconTest do\n  use ExUnit.Case, async: true\n\n  setup do\n    scope = :\"test_scope#{System.unique_integer([:positive])}\"\n\n    %{scope: scope}\n  end\n\n  defp spec(scope, opts) do\n    %{\n      id: scope,\n      start: {Beacon, :start_link, [scope, opts]},\n      type: :supervisor\n    }\n  end\n\n  describe \"start_link/2\" do\n    test \"starts beacon with default partitions\", %{scope: scope} do\n      pid = start_supervised!({Beacon, [scope, []]})\n      assert Process.alive?(pid)\n      assert is_list(Beacon.Supervisor.partitions(scope))\n      assert length(Beacon.Supervisor.partitions(scope)) == System.schedulers_online()\n    end\n\n    test \"starts beacon with custom partition count\", %{scope: scope} do\n      pid = start_supervised!(spec(scope, partitions: 3))\n      assert Process.alive?(pid)\n      assert length(Beacon.Supervisor.partitions(scope)) == 3\n    end\n\n    test \"raises on invalid partition count\", %{scope: scope} do\n      assert_raise ArgumentError, ~r/expected :partitions to be a positive integer/, fn ->\n        Beacon.start_link(scope, partitions: 0)\n      end\n\n      assert_raise ArgumentError, ~r/expected :partitions to be a positive integer/, fn ->\n        Beacon.start_link(scope, partitions: -1)\n      end\n\n      assert_raise ArgumentError, ~r/expected :partitions to be a positive integer/, fn ->\n        Beacon.start_link(scope, partitions: :invalid)\n      end\n    end\n\n    test \"raises on invalid broadcast_interval_in_ms\", %{scope: scope} do\n      assert_raise ArgumentError,\n                   ~r/expected :broadcast_interval_in_ms to be a positive integer/,\n                   fn ->\n                     Beacon.start_link(scope, broadcast_interval_in_ms: 0)\n                   end\n\n      assert_raise ArgumentError,\n                   ~r/expected :broadcast_interval_in_ms to be a positive integer/,\n                   fn ->\n                     Beacon.start_link(scope, broadcast_interval_in_ms: -1)\n                   end\n\n      assert_raise ArgumentError,\n                   ~r/expected :broadcast_interval_in_ms to be a positive integer/,\n                   fn ->\n                     Beacon.start_link(scope, broadcast_interval_in_ms: :invalid)\n                   end\n    end\n  end\n\n  describe \"join/3 and leave/3\" do\n    setup %{scope: scope} do\n      start_supervised!(spec(scope, partitions: 2))\n      :ok\n    end\n\n    test \"can join a group\", %{scope: scope} do\n      pid = spawn_link(fn -> Process.sleep(:infinity) end)\n      assert :ok = Beacon.join(scope, :group1, pid)\n      assert Beacon.local_member?(scope, :group1, pid)\n    end\n\n    test \"can leave a group\", %{scope: scope} do\n      pid = spawn_link(fn -> Process.sleep(:infinity) end)\n      assert :ok = Beacon.join(scope, :group1, pid)\n      assert Beacon.local_member?(scope, :group1, pid)\n\n      assert :ok = Beacon.leave(scope, :group1, pid)\n      refute Beacon.local_member?(scope, :group1, pid)\n    end\n\n    test \"joining same group twice is idempotent\", %{scope: scope} do\n      pid = spawn_link(fn -> Process.sleep(:infinity) end)\n      assert :ok = Beacon.join(scope, :group1, pid)\n      assert :ok = Beacon.join(scope, :group1, pid)\n      assert Beacon.local_member_count(scope, :group1) == 1\n    end\n\n    test \"multiple processes can join same group\", %{scope: scope} do\n      pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n\n      assert :ok = Beacon.join(scope, :group1, pid1)\n      assert :ok = Beacon.join(scope, :group1, pid2)\n\n      members = Beacon.local_members(scope, :group1)\n      assert length(members) == 2\n      assert pid1 in members\n      assert pid2 in members\n    end\n\n    test \"process can join multiple groups\", %{scope: scope} do\n      pid = spawn_link(fn -> Process.sleep(:infinity) end)\n\n      assert :ok = Beacon.join(scope, :group1, pid)\n      assert :ok = Beacon.join(scope, :group2, pid)\n\n      assert Beacon.local_member?(scope, :group1, pid)\n      assert Beacon.local_member?(scope, :group2, pid)\n    end\n\n    test \"automatically removes member when process dies\", %{scope: scope} do\n      pid = spawn(fn -> Process.sleep(:infinity) end)\n      assert :ok = Beacon.join(scope, :group1, pid)\n      assert Beacon.local_member?(scope, :group1, pid)\n\n      Process.exit(pid, :kill)\n      Process.sleep(50)\n\n      refute Beacon.local_member?(scope, :group1, pid)\n      assert Beacon.local_member_count(scope, :group1) == 0\n    end\n  end\n\n  describe \"local_members/2\" do\n    setup %{scope: scope} do\n      start_supervised!(spec(scope, partitions: 2))\n      :ok\n    end\n\n    test \"returns empty list for non-existent group\", %{scope: scope} do\n      assert Beacon.local_members(scope, :nonexistent) == []\n    end\n\n    test \"returns all members of a group\", %{scope: scope} do\n      pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid3 = spawn_link(fn -> Process.sleep(:infinity) end)\n\n      Beacon.join(scope, :group1, pid1)\n      Beacon.join(scope, :group1, pid2)\n      Beacon.join(scope, :group2, pid3)\n\n      members = Beacon.local_members(scope, :group1)\n      assert length(members) == 2\n      assert pid1 in members\n      assert pid2 in members\n      refute pid3 in members\n    end\n  end\n\n  describe \"local_member_count/2\" do\n    setup %{scope: scope} do\n      start_supervised!(spec(scope, partitions: 2))\n      :ok\n    end\n\n    test \"returns 0 for non-existent group\", %{scope: scope} do\n      assert Beacon.local_member_count(scope, :nonexistent) == 0\n    end\n\n    test \"returns correct count\", %{scope: scope} do\n      pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n\n      assert Beacon.local_member_count(scope, :group1) == 0\n\n      Beacon.join(scope, :group1, pid1)\n      assert Beacon.local_member_count(scope, :group1) == 1\n\n      Beacon.join(scope, :group1, pid2)\n      assert Beacon.local_member_count(scope, :group1) == 2\n\n      Beacon.leave(scope, :group1, pid1)\n      assert Beacon.local_member_count(scope, :group1) == 1\n    end\n  end\n\n  describe \"local_member_counts/1\" do\n    setup %{scope: scope} do\n      start_supervised!(spec(scope, partitions: 2))\n      :ok\n    end\n\n    test \"returns empty map when no groups exist\", %{scope: scope} do\n      assert Beacon.local_member_counts(scope) == %{}\n    end\n\n    test \"returns counts for all groups\", %{scope: scope} do\n      pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid3 = spawn_link(fn -> Process.sleep(:infinity) end)\n\n      Beacon.join(scope, :group1, pid1)\n      Beacon.join(scope, :group1, pid2)\n      Beacon.join(scope, :group2, pid3)\n\n      assert Beacon.local_member_counts(scope) == %{\n               group1: 2,\n               group2: 1\n             }\n    end\n  end\n\n  describe \"local_member?/3\" do\n    setup %{scope: scope} do\n      start_supervised!(spec(scope, partitions: 2))\n      :ok\n    end\n\n    test \"returns false for non-member\", %{scope: scope} do\n      pid = spawn_link(fn -> Process.sleep(:infinity) end)\n      refute Beacon.local_member?(scope, :group1, pid)\n    end\n\n    test \"returns true for member\", %{scope: scope} do\n      pid = spawn_link(fn -> Process.sleep(:infinity) end)\n      Beacon.join(scope, :group1, pid)\n      assert Beacon.local_member?(scope, :group1, pid)\n    end\n\n    test \"returns false after leaving\", %{scope: scope} do\n      pid = spawn_link(fn -> Process.sleep(:infinity) end)\n\n      Beacon.join(scope, :group1, pid)\n      Beacon.leave(scope, :group1, pid)\n\n      refute Beacon.local_member?(scope, :group1, pid)\n    end\n  end\n\n  describe \"local_groups/1\" do\n    setup %{scope: scope} do\n      start_supervised!(spec(scope, partitions: 2))\n      :ok\n    end\n\n    test \"returns empty list when no groups exist\", %{scope: scope} do\n      assert Beacon.local_groups(scope) == []\n    end\n\n    test \"returns all groups with members\", %{scope: scope} do\n      pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n\n      Beacon.join(scope, :group1, pid1)\n      Beacon.join(scope, :group2, pid2)\n      Beacon.join(scope, :group3, pid1)\n\n      groups = Beacon.local_groups(scope)\n      assert :group1 in groups\n      assert :group2 in groups\n      assert :group3 in groups\n      assert length(groups) == 3\n    end\n\n    test \"removes group from list when last member leaves\", %{scope: scope} do\n      pid = spawn_link(fn -> Process.sleep(:infinity) end)\n      Beacon.join(scope, :group1, pid)\n      assert :group1 in Beacon.local_groups(scope)\n\n      Beacon.leave(scope, :group1, pid)\n      refute :group1 in Beacon.local_groups(scope)\n    end\n  end\n\n  describe \"local_group_count/1\" do\n    setup %{scope: scope} do\n      start_supervised!(spec(scope, partitions: 2))\n      :ok\n    end\n\n    test \"returns 0 when no groups exist\", %{scope: scope} do\n      assert Beacon.local_group_count(scope) == 0\n    end\n\n    test \"returns correct count of groups\", %{scope: scope} do\n      pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n      Beacon.join(scope, :group1, pid1)\n      Beacon.join(scope, :group2, pid2)\n      Beacon.join(scope, :group3, pid2)\n      Beacon.join(scope, :group3, pid1)\n      assert Beacon.local_group_count(scope) == 3\n      Beacon.leave(scope, :group2, pid2)\n      assert Beacon.local_group_count(scope) == 2\n    end\n  end\n\n  describe \"member_counts/1\" do\n    setup %{scope: scope} do\n      start_supervised!(spec(scope, partitions: 2))\n      :ok\n    end\n\n    test \"returns local counts when no peers\", %{scope: scope} do\n      pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n\n      Beacon.join(scope, :group1, pid1)\n      Beacon.join(scope, :group1, pid2)\n\n      counts = Beacon.member_counts(scope)\n      assert counts[:group1] == 2\n    end\n  end\n\n  describe \"partition distribution\" do\n    setup %{scope: scope} do\n      start_supervised!(spec(scope, partitions: 4))\n      :ok\n    end\n\n    test \"distributes groups across partitions\", %{scope: scope} do\n      # Create multiple processes and verify they're split against different partitions\n      pids = for _ <- 1..20, do: spawn_link(fn -> Process.sleep(:infinity) end)\n\n      Enum.each(pids, fn pid ->\n        Beacon.join(scope, pid, pid)\n      end)\n\n      # Check that multiple partitions are being used\n      partition_names = Beacon.Supervisor.partitions(scope)\n\n      Enum.map(partition_names, fn partition_name ->\n        assert Beacon.Partition.member_counts(partition_name) > 1\n      end)\n    end\n\n    test \"same group always maps to same partition\", %{scope: scope} do\n      partition1 = Beacon.Supervisor.partition(scope, :my_group)\n      partition2 = Beacon.Supervisor.partition(scope, :my_group)\n      partition3 = Beacon.Supervisor.partition(scope, :my_group)\n\n      assert partition1 == partition2\n      assert partition2 == partition3\n    end\n  end\n\n  @aux_mod (quote do\n              defmodule PeerAux do\n                def start(scope) do\n                  spawn(fn ->\n                    {:ok, _} = Beacon.start_link(scope, broadcast_interval_in_ms: 50)\n\n                    pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n                    pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n                    Beacon.join(scope, :group1, pid1)\n                    Beacon.join(scope, :group2, pid2)\n                    Beacon.join(scope, :group3, pid2)\n\n                    Process.sleep(:infinity)\n                  end)\n                end\n              end\n            end)\n\n  describe \"distributed tests\" do\n    setup do\n      scope = :\"broadcast_scope#{System.unique_integer([:positive])}\"\n      supervisor_pid = start_supervised!(spec(scope, partitions: 2, broadcast_interval_in_ms: 50))\n      {:ok, peer, node} = Peer.start_disconnected(aux_mod: @aux_mod)\n\n      ref =\n        :telemetry_test.attach_event_handlers(self(), [\n          [:beacon, scope, :node, :up],\n          [:beacon, scope, :node, :down]\n        ])\n\n      %{scope: scope, supervisor_pid: supervisor_pid, peer: peer, node: node, telemetry_ref: ref}\n    end\n\n    test \"node up\", %{scope: scope, peer: peer, node: node, telemetry_ref: telemetry_ref} do\n      pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n      Beacon.join(scope, :group1, pid1)\n      Beacon.join(scope, :group1, pid2)\n      Beacon.join(scope, :group2, pid2)\n\n      true = Node.connect(node)\n      :peer.call(peer, PeerAux, :start, [scope])\n\n      assert_receive {[:beacon, ^scope, :node, :up], ^telemetry_ref, %{}, %{node: ^node}}\n\n      # Wait for at least one broadcast interval\n      Process.sleep(150)\n      assert Beacon.group_count(scope) == 3\n      groups = Beacon.groups(scope)\n\n      assert length(groups) == 3\n      assert :group1 in groups\n      assert :group2 in groups\n      assert :group3 in groups\n\n      assert Beacon.member_counts(scope) == %{group1: 3, group2: 2, group3: 1}\n      assert Beacon.member_count(scope, :group1) == 3\n      assert Beacon.member_count(scope, :group3, node) == 1\n      assert Beacon.member_count(scope, :group1, node()) == 2\n    end\n\n    test \"node down\", %{scope: scope, peer: peer, node: node, telemetry_ref: telemetry_ref} do\n      pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n      Beacon.join(scope, :group1, pid1)\n      Beacon.join(scope, :group1, pid2)\n      Beacon.join(scope, :group2, pid2)\n\n      true = Node.connect(node)\n      :peer.call(peer, PeerAux, :start, [scope])\n      assert_receive {[:beacon, ^scope, :node, :up], ^telemetry_ref, %{}, %{node: ^node}}\n      # Wait for remote scope to communicate with local\n      Process.sleep(150)\n\n      true = Node.disconnect(node)\n\n      assert_receive {[:beacon, ^scope, :node, :down], ^telemetry_ref, %{}, %{node: ^node}}\n\n      assert Beacon.member_counts(scope) == %{group1: 2, group2: 1}\n      assert Beacon.member_count(scope, :group1) == 2\n    end\n\n    test \"scope restart can recover\", %{\n      scope: scope,\n      supervisor_pid: supervisor_pid,\n      peer: peer,\n      node: node,\n      telemetry_ref: telemetry_ref\n    } do\n      pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n      Beacon.join(scope, :group1, pid1)\n      Beacon.join(scope, :group1, pid2)\n      Beacon.join(scope, :group2, pid2)\n\n      true = Node.connect(node)\n      :peer.call(peer, PeerAux, :start, [scope])\n      assert_receive {[:beacon, ^scope, :node, :up], ^telemetry_ref, %{}, %{node: ^node}}\n\n      # Wait for remote scope to communicate with local\n      Process.sleep(150)\n\n      [\n        {1, _, :worker, [Beacon.Partition]},\n        {0, _, :worker, [Beacon.Partition]},\n        {:scope, scope_pid, :worker, [Beacon.Scope]}\n      ] = Supervisor.which_children(supervisor_pid)\n\n      # Restart the scope process\n      Process.monitor(scope_pid)\n      Process.exit(scope_pid, :kill)\n      assert_receive {:DOWN, _ref, :process, ^scope_pid, :killed}\n      # Wait for recovery and communication\n      Process.sleep(200)\n      assert Beacon.group_count(scope) == 3\n      groups = Beacon.groups(scope)\n      assert length(groups) == 3\n      assert :group1 in groups\n      assert :group2 in groups\n      assert :group3 in groups\n      assert Beacon.member_counts(scope) == %{group1: 3, group2: 2, group3: 1}\n    end\n  end\nend\n"
  },
  {
    "path": "beacon/test/support/peer.ex",
    "content": "defmodule Peer do\n  @moduledoc \"\"\"\n  Uses the gist https://gist.github.com/ityonemo/177cbc96f8c8722bfc4d127ff9baec62 to start a node for testing\n  \"\"\"\n\n  @doc \"\"\"\n  Starts a node for testing.\n\n  Can receive an auxiliary module to be evaluated in the node so you are able to setup functions within the test context and outside of the normal code context\n\n  e.g.\n  ```\n  @aux_mod (quote do\n              defmodule Aux do\n                def checker(res), do: res\n              end\n            end)\n\n  Code.eval_quoted(@aux_mod)\n  test \"clustered call\" do\n    {:ok, node} = Clustered.start(@aux_mod)\n    assert ok = :rpc.call(node, Aux, :checker, [:ok])\n  end\n  ```\n  \"\"\"\n  @spec start(Keyword.t()) :: {:ok, :peer.server_ref(), node}\n  def start(opts \\\\ []) do\n    {:ok, peer, node} = start_disconnected(opts)\n\n    true = Node.connect(node)\n\n    {:ok, peer, node}\n  end\n\n  @doc \"\"\"\n  Similar to `start/2` but the node is not connected automatically\n  \"\"\"\n  @spec start_disconnected(Keyword.t()) :: {:ok, :peer.server_ref(), node}\n  def start_disconnected(opts \\\\ []) do\n    extra_config = Keyword.get(opts, :extra_config, [])\n    name = Keyword.get(opts, :name, :peer.random_name())\n    aux_mod = Keyword.get(opts, :aux_mod, nil)\n\n    true = :erlang.set_cookie(:cookie)\n\n    {:ok, pid, node} =\n      ExUnit.Callbacks.start_supervised(%{\n        id: {:peer, name},\n        start:\n          {:peer, :start_link,\n           [\n             %{\n               name: name,\n               host: ~c\"127.0.0.1\",\n               longnames: true,\n               connection: :standard_io\n             }\n           ]}\n      })\n\n    :peer.call(pid, :erlang, :set_cookie, [:cookie])\n\n    :ok = :peer.call(pid, :code, :add_paths, [:code.get_path()])\n\n    for {app_name, _, _} <- Application.loaded_applications(),\n        {key, value} <- Application.get_all_env(app_name) do\n      :ok = :peer.call(pid, Application, :put_env, [app_name, key, value])\n    end\n\n    # Override with extra config\n    for {app_name, key, value} <- extra_config do\n      :ok = :peer.call(pid, Application, :put_env, [app_name, key, value])\n    end\n\n    {:ok, _} = :peer.call(pid, Application, :ensure_all_started, [:mix])\n    :ok = :peer.call(pid, Mix, :env, [Mix.env()])\n\n    Enum.map(\n      [:logger, :runtime_tools, :mix, :os_mon, :beacon],\n      fn app -> {:ok, _} = :peer.call(pid, Application, :ensure_all_started, [app]) end\n    )\n\n    if aux_mod do\n      {{:module, _, _, _}, []} = :peer.call(pid, Code, :eval_quoted, [aux_mod])\n    end\n\n    {:ok, pid, node}\n  end\nend\n"
  },
  {
    "path": "beacon/test/test_helper.exs",
    "content": "ExUnit.start(capture_log: true)\n\n:net_kernel.start([:\"beacon@127.0.0.1\"])\n"
  },
  {
    "path": "bench/gen_counter.exs",
    "content": "alias Realtime.GenCounter\n\ncounter = :counters.new(1, [:write_concurrency])\n_gen_counter = GenCounter.new(:any_term)\n\nBenchee.run(\n  %{\n    \":counters.add\" => fn -> :counters.add(counter, 1, 1) end,\n    \"GenCounter.add\" => fn -> GenCounter.add(:any_term) end\n  }\n)\n"
  },
  {
    "path": "bench/secrets.exs",
    "content": "alias RealtimeWeb.ChannelsAuthorization\nalias Realtime.Helpers, as: H\n\njwt =\n  \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxvY2FsaG9zdCIsInJvbGUiOiJhbm9uIiwiaWF0IjoxNjU4NjAwNzkxLCJleHAiOjE5NzQxNzY3OTF9.Iki--9QilZ7vySEUJHj0a1T8BDHkR7rmdWStXImCZfk\"\n\njwt_secret = \"d3v_HtNXEpT+zfsyy1LE1WPGmNKLWRfw/rpjnVtCEEM2cSFV2s+kUh5OKX7TPYmG\"\n\nsecret_key = \"1234567890123456\"\nstring_to_encrypt = \"supabase_realtime\"\nstring_to_decrypt = \"A5mS7ggkPXm0FaKKoZtrsYNlZA3qZxFe9XA9w2YYqgU=\"\n\nBenchee.run(%{\n  \"authorize_jwt\" => fn ->\n    {:ok, _} = ChannelsAuthorization.authorize_conn(jwt, jwt_secret, nil)\n  end,\n  \"encrypt_string\" => fn ->\n    H.encrypt!(string_to_encrypt, secret_key)\n  end,\n  \"decrypt_string\" => fn ->\n    H.decrypt!(string_to_decrypt, secret_key)\n  end\n})\n"
  },
  {
    "path": "config/config.exs",
    "content": "# This file is responsible for configuring your application\n# and its dependencies with the aid of the Mix.Config module.\n#\n# This configuration file is loaded before any dependency and\n# is restricted to this project.\n\n# General application configuration\nimport Config\n\nconfig :realtime,\n  websocket_fullsweep_after: 20,\n  ecto_repos: [Realtime.Repo],\n  version: Mix.Project.config()[:version],\n  replication_watchdog_interval: :timer.minutes(5),\n  replication_watchdog_timeout: :timer.minutes(1)\n\n# Configures the endpoint\nconfig :realtime, RealtimeWeb.Endpoint,\n  url: [host: \"127.0.0.1\"],\n  secret_key_base: \"ktyW57usZxrivYdvLo9os7UGcUUZYKchOMHT3tzndmnHuxD09k+fQnPUmxlPMUI3\",\n  render_errors: [view: RealtimeWeb.ErrorView, accepts: ~w(html json), layout: false],\n  pubsub_server: Realtime.PubSub,\n  live_view: [signing_salt: \"wUMBeR8j\"]\n\nconfig :realtime, :extensions,\n  postgres_cdc_rls: %{\n    type: :postgres_cdc,\n    key: \"postgres_cdc_rls\",\n    driver: Extensions.PostgresCdcRls,\n    supervisor: Extensions.PostgresCdcRls.Supervisor,\n    db_settings: Extensions.PostgresCdcRls.DbSettings\n  }\n\nconfig :esbuild,\n  version: \"0.14.29\",\n  default: [\n    args:\n      ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),\n    cd: Path.expand(\"../assets\", __DIR__),\n    env: %{\"NODE_PATH\" => Path.expand(\"../deps\", __DIR__)}\n  ]\n\nconfig :tailwind,\n  version: \"3.3.2\",\n  default: [\n    args: ~w(\n      --config=tailwind.config.js\n      --input=css/app.css\n      --output=../priv/static/assets/app.css\n    ),\n    cd: Path.expand(\"../assets\", __DIR__)\n  ]\n\n# Configures Elixir's Logger\nconfig :logger, :console,\n  format: \"$time $metadata[$level] $message\\n\",\n  metadata: [:request_id, :project, :external_id, :application_name, :error_code, :sub, :iss, :exp]\n\n# Use Jason for JSON parsing in Phoenix\nconfig :phoenix, :json_library, Jason\n\nconfig :open_api_spex, :cache_adapter, OpenApiSpex.Plug.PersistentTermCache\n\nconfig :logflare_logger_backend,\n  flush_interval: 1_000,\n  max_batch_size: 50,\n  metadata: :all\n\nconfig :phoenix, :filter_parameters, {:keep, []}\n\nconfig :opentelemetry,\n  resource_detectors: [:otel_resource_app_env, :otel_resource_env_var],\n  resource: %{\n    :\"service.name\" => \"realtime\"\n  },\n  text_map_propagators: [:baggage, :trace_context],\n  # Exporter must be configured through environment variables\n  traces_exporter: :none,\n  span_processor: :batch\n\nconfig :gen_rpc,\n  # Inactivity period in milliseconds after which a pending process holding an async_call return value will exit.\n  # This is used for process sanitation purposes so please make sure to set it in a sufficiently high number\n  async_call_inactivity_timeout: 300_000\n\nconfig :prom_ex, :storage_adapter, Realtime.PromEx.Store\nconfig :realtime, Realtime.PromEx, ets_flush_interval: 90_000\nconfig :realtime, Realtime.TenantPromEx, ets_flush_interval: 90_000\n\n# Import environment specific config. This must remain at the bottom\n# of this file so it overrides the configuration defined above.\nimport_config \"#{Mix.env()}.exs\"\n"
  },
  {
    "path": "config/dev.exs",
    "content": "import Config\n\n# For development, we disable any cache and enable\n# debugging and code reloading.\n#\n# The watchers configuration can be used to run external\n# watchers to your application. For example, we use it\n# with webpack to recompile .js and .css sources.\n\n# Channels are not secured by default in development and\n# are secured by default in production.\npresence = System.get_env(\"PRESENCE\", \"false\") == \"false\"\n\nconfig :realtime,\n  presence: presence,\n  node_balance_uptime_threshold_in_ms: 100\n\nconfig :realtime, RealtimeWeb.Endpoint,\n  http: [port: System.get_env(\"PORT\", \"4000\"), compress: true],\n  debug_errors: true,\n  code_reloader: true,\n  check_origin: false,\n  watchers: [\n    # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)\n    esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},\n    tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}\n  ]\n\n# watchers: [\n#   node: [\n#     \"node_modules/webpack/bin/webpack.js\",\n#     \"--mode\",\n#     \"development\",\n#     \"--watch-stdin\",\n#     cd: Path.expand(\"../assets\", __DIR__)\n#   ]\n# ]\n\n# ## SSL Support\n#\n# In order to use HTTPS in development, a self-signed\n# certificate can be generated by running the following\n# Mix task:\n#\n#     mix phx.gen.cert\n#\n# Note that this task requires Erlang/OTP 20 or later.\n# Run `mix help phx.gen.cert` for more information.\n#\n# The `http:` config above can be replaced with:\n#\n#     https: [\n#       port: 4001,\n#       cipher_suite: :strong,\n#       keyfile: \"priv/cert/selfsigned_key.pem\",\n#       certfile: \"priv/cert/selfsigned.pem\"\n#     ],\n#\n# If desired, both `http:` and `https:` keys can be\n# configured to run both http and https servers on\n# different ports.\n\n# Watch static and templates for browser reloading.\nconfig :realtime, RealtimeWeb.Endpoint,\n  live_reload: [\n    patterns: [\n      ~r\"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$\",\n      ~r\"priv/gettext/.*(po)$\",\n      ~r\"lib/realtime_web/(live|views)/.*(ex)$\",\n      ~r\"lib/realtime_web/templates/.*(eex)$\"\n    ]\n  ]\n\n# Do not include metadata nor timestamps in development logs\nconfig :logger, :console,\n  format: \"$time [$level] $message $metadata\\n\",\n  metadata: [\n    :error_code,\n    :file,\n    :pid,\n    :project,\n    :external_id,\n    :application_name,\n    :region,\n    :request_id,\n    :sub,\n    :iss,\n    :exp\n  ]\n\n# Set a higher stacktrace during development. Avoid configuring such\n# in production as building large stacktraces may be expensive.\nconfig :phoenix, :stacktrace_depth, 20\n\n# Initialize plugs at runtime for faster development compilation\nconfig :phoenix, :plug_init_mode, :runtime\n\n# Disable caching to ensure the rendered spec is refreshed\nconfig :open_api_spex, :cache_adapter, OpenApiSpex.Plug.NoneCache\n\n# Disabled but can print to stdout with:\n# config :opentelemetry, traces_exporter: {:otel_exporter_stdout, []}\nconfig :opentelemetry, traces_exporter: :none\n\nconfig :mix_test_watch, clear: true\n"
  },
  {
    "path": "config/prod.exs",
    "content": "import Config\n\n# For production, don't forget to configure the url host\n# to something meaningful, Phoenix uses this information\n# when generating URLs.\n#\n# Note we also include the path to a cache manifest\n# containing the digested version of static files. This\n# manifest is generated by the `mix phx.digest` task,\n# which you should run after static files are built and\n# before starting your production server.\n# config :realtime, RealtimeWeb.Endpoint,\n#   url: [host: \"realtime.dev\", port: 80]\n\n# Do not print debug messages in production\nconfig :logger, :warning,\n  format: \"$time [$level] $message $metadata\\n\",\n  metadata: [\n    :error_code,\n    :file,\n    :pid,\n    :project,\n    :external_id,\n    :application_name,\n    :cluster,\n    :region,\n    :request_id,\n    :sub,\n    :iss,\n    :exp\n  ]\n\n# ## SSL Support\n#\n# To get SSL working, you will need to add the `https` key\n# to the previous section and set your `:url` port to 443:\n#\n#     config :realtime, RealtimeWeb.Endpoint,\n#       ...\n#       url: [host: \"example.com\", port: 443],\n#       https: [\n#         port: 443,\n#         cipher_suite: :strong,\n#         keyfile: System.get_env(\"SOME_APP_SSL_KEY_PATH\"),\n#         certfile: System.get_env(\"SOME_APP_SSL_CERT_PATH\"),\n#         transport_options: [socket_opts: [:inet6]]\n#       ]\n#\n# The `cipher_suite` is set to `:strong` to support only the\n# latest and more secure SSL ciphers. This means old browsers\n# and clients may not be supported. You can set it to\n# `:compatible` for wider support.\n#\n# `:keyfile` and `:certfile` expect an absolute path to the key\n# and cert in disk or a relative path inside priv, for example\n# \"priv/ssl/server.key\". For all supported SSL configuration\n# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1\n#\n# We also recommend setting `force_ssl` in your endpoint, ensuring\n# no data is ever sent via http, always redirecting to https:\n#\n#     config :realtime, RealtimeWeb.Endpoint,\n#       force_ssl: [hsts: true]\n#\n# Check `Plug.SSL` for all available options in `force_ssl`.\n\n# Finally import the config/prod.secret.exs which loads secrets\n# and configuration from environment variables.\n"
  },
  {
    "path": "config/runtime.exs",
    "content": "import Config\n\ndefmodule Env do\n  def get_integer(env, default) do\n    value = System.get_env(env)\n    if value, do: String.to_integer(value), else: default\n  end\n\n  def get_charlist(env, default) do\n    value = System.get_env(env)\n    if value, do: String.to_charlist(value), else: default\n  end\n\n  def get_boolean(env, default) do\n    value = System.get_env(env)\n    if value, do: value |> String.downcase() |> String.to_existing_atom(), else: default\n  end\nend\n\napp_name = System.get_env(\"APP_NAME\", \"\")\n\n# Setup Database\ndefault_db_host = System.get_env(\"DB_HOST\", \"127.0.0.1\")\nusername = System.get_env(\"DB_USER\", \"supabase_admin\")\npassword = System.get_env(\"DB_PASSWORD\", \"postgres\")\ndatabase = System.get_env(\"DB_NAME\", \"postgres\")\nport = System.get_env(\"DB_PORT\", \"5432\")\ndb_version = System.get_env(\"DB_IP_VERSION\")\nslot_name_suffix = System.get_env(\"SLOT_NAME_SUFFIX\")\ndb_ssl_enabled? = Env.get_boolean(\"DB_SSL\", false)\ndb_ssl_ca_cert = System.get_env(\"DB_SSL_CA_CERT\")\nqueue_target = Env.get_integer(\"DB_QUEUE_TARGET\", 5000)\nqueue_interval = Env.get_integer(\"DB_QUEUE_INTERVAL\", 5000)\npool_size = Env.get_integer(\"DB_POOL_SIZE\", 5)\nmaster_region = System.get_env(\"DB_MASTER_REGION\")\nregion = System.get_env(\"REGION\")\nregion_mapping = System.get_env(\"REGION_MAPPING\")\n\nafter_connect_query_args =\n  case System.get_env(\"DB_AFTER_CONNECT_QUERY\") do\n    nil -> nil\n    query -> {Postgrex, :query!, [query, []]}\n  end\n\nssl_opts =\n  cond do\n    db_ssl_enabled? and is_binary(db_ssl_ca_cert) -> [cacertfile: db_ssl_ca_cert]\n    db_ssl_enabled? -> [verify: :verify_none]\n    true -> false\n  end\n\ntenant_cache_expiration = Env.get_integer(\"TENANT_CACHE_EXPIRATION_IN_MS\", :timer.seconds(30))\nmigration_partition_slots = Env.get_integer(\"MIGRATION_PARTITION_SLOTS\", System.schedulers_online() * 2)\nconnect_partition_slots = Env.get_integer(\"CONNECT_PARTITION_SLOTS\", System.schedulers_online() * 2)\nmetrics_cleaner_schedule_timer_in_ms = Env.get_integer(\"METRICS_CLEANER_SCHEDULE_TIMER_IN_MS\", :timer.minutes(30))\nmetrics_rpc_timeout_in_ms = Env.get_integer(\"METRICS_RPC_TIMEOUT_IN_MS\", :timer.seconds(15))\nrebalance_check_interval_in_ms = Env.get_integer(\"REBALANCE_CHECK_INTERVAL_IN_MS\", :timer.minutes(10))\nnode_balance_uptime_threshold_in_ms = Env.get_integer(\"NODE_BALANCE_UPTIME_THRESHOLD_IN_MS\", :timer.minutes(5))\ntenant_max_bytes_per_second = Env.get_integer(\"TENANT_MAX_BYTES_PER_SECOND\", 100_000)\ntenant_max_channels_per_client = Env.get_integer(\"TENANT_MAX_CHANNELS_PER_CLIENT\", 100)\ntenant_max_concurrent_users = Env.get_integer(\"TENANT_MAX_CONCURRENT_USERS\", 200)\ntenant_max_events_per_second = Env.get_integer(\"TENANT_MAX_EVENTS_PER_SECOND\", 100)\ntenant_max_joins_per_second = Env.get_integer(\"TENANT_MAX_JOINS_PER_SECOND\", 100)\nclient_presence_max_calls = Env.get_integer(\"CLIENT_PRESENCE_MAX_CALLS\", 5)\nclient_presence_window_ms = Env.get_integer(\"CLIENT_PRESENCE_WINDOW_MS\", 30_000)\nrpc_timeout = Env.get_integer(\"RPC_TIMEOUT\", :timer.seconds(30))\nmax_gen_rpc_clients = Env.get_integer(\"MAX_GEN_RPC_CLIENTS\", 5)\nrun_janitor? = Env.get_boolean(\"RUN_JANITOR\", false)\ndisable_healthcheck_logging = Env.get_boolean(\"DISABLE_HEALTHCHECK_LOGGING\", false)\njanitor_schedule_randomize = Env.get_boolean(\"JANITOR_SCHEDULE_RANDOMIZE\", true)\njanitor_max_children = Env.get_integer(\"JANITOR_MAX_CHILDREN\", 5)\njanitor_chunk_size = Env.get_integer(\"JANITOR_CHUNK_SIZE\", 10)\njanitor_run_after_in_ms = Env.get_integer(\"JANITOR_RUN_AFTER_IN_MS\", :timer.minutes(10))\njanitor_children_timeout = Env.get_integer(\"JANITOR_CHILDREN_TIMEOUT\", :timer.seconds(5))\njanitor_schedule_timer = Env.get_integer(\"JANITOR_SCHEDULE_TIMER_IN_MS\", :timer.hours(4))\nplatform = if System.get_env(\"AWS_EXECUTION_ENV\") == \"AWS_ECS_FARGATE\", do: :aws, else: :fly\nbroadcast_pool_size = Env.get_integer(\"BROADCAST_POOL_SIZE\", 10)\npresence_pool_size = Env.get_integer(\"PRESENCE_POOL_SIZE\", 10)\npresence_broadcast_period = Env.get_integer(\"PRESENCE_BROADCAST_PERIOD_IN_MS\", 1_500)\npresence_permdown_period = Env.get_integer(\"PRESENCE_PERMDOWN_PERIOD_IN_MS\", 1_200_000)\npubsub_adapter = System.get_env(\"PUBSUB_ADAPTER\", \"gen_rpc\") |> String.to_atom()\nwebsocket_max_heap_size = div(Env.get_integer(\"WEBSOCKET_MAX_HEAP_SIZE\", 50_000_000), :erlang.system_info(:wordsize))\nusers_scope_shards = Env.get_integer(\"USERS_SCOPE_SHARDS\", 5)\npostgres_cdc_scope_shards = Env.get_integer(\"POSTGRES_CDC_SCOPE_SHARDS\", 5)\nregional_broadcasting = Env.get_boolean(\"REGIONAL_BROADCASTING\", false)\nno_channel_timeout_in_ms = Env.get_integer(\"NO_CHANNEL_TIMEOUT_IN_MS\", :timer.minutes(10))\nmeasure_traffic_interval_in_ms = Env.get_integer(\"MEASURE_TRAFFIC_INTERVAL_IN_MS\", :timer.seconds(10))\nmetrics_pusher_enabled = Env.get_boolean(\"METRICS_PUSHER_ENABLED\", false)\nmetrics_separation_enabled = Env.get_boolean(\"METRICS_SEPARATION_ENABLED\", false)\nmetrics_pusher_url = System.get_env(\"METRICS_PUSHER_URL\")\nmetrics_pusher_user = System.get_env(\"METRICS_PUSHER_USER\", \"realtime\")\nmetrics_pusher_auth = System.get_env(\"METRICS_PUSHER_AUTH\")\nmetrics_pusher_interval_ms = Env.get_integer(\"METRICS_PUSHER_INTERVAL_MS\", :timer.seconds(30))\nmetrics_pusher_timeout_ms = Env.get_integer(\"METRICS_PUSHER_TIMEOUT_MS\", :timer.seconds(15))\nmetrics_pusher_compress = Env.get_boolean(\"METRICS_PUSHER_COMPRESS\", true)\n\nif !(db_version in [nil, \"ipv6\", \"ipv4\"]),\n  do: raise(\"Invalid IP version, please set either ipv6 or ipv4\")\n\nsocket_options =\n  cond do\n    db_version == \"ipv6\" ->\n      [:inet6]\n\n    db_version == \"ipv4\" ->\n      [:inet]\n\n    true ->\n      case Realtime.Database.detect_ip_version(default_db_host) do\n        {:ok, ip_version} -> [ip_version]\n        {:error, reason} -> raise \"Failed to detect IP version for DB_HOST: #{reason}\"\n      end\n  end\n\n[_, node_host] = node() |> Atom.to_string() |> String.split(\"@\")\n\nmetrics_tags = %{\n  region: region,\n  host: node_host,\n  id: Realtime.Nodes.short_node_id_from_name(node())\n}\n\nconfig :realtime, Realtime.Repo,\n  hostname: default_db_host,\n  username: username,\n  password: password,\n  database: database,\n  port: port,\n  pool_size: pool_size,\n  queue_target: queue_target,\n  queue_interval: queue_interval,\n  parameters: [application_name: \"supabase_mt_realtime\"],\n  after_connect: after_connect_query_args,\n  socket_options: socket_options,\n  ssl: ssl_opts\n\nconfig :realtime,\n  websocket_max_heap_size: websocket_max_heap_size,\n  migration_partition_slots: migration_partition_slots,\n  connect_partition_slots: connect_partition_slots,\n  rebalance_check_interval_in_ms: rebalance_check_interval_in_ms,\n  tenant_max_bytes_per_second: tenant_max_bytes_per_second,\n  tenant_max_channels_per_client: tenant_max_channels_per_client,\n  tenant_max_concurrent_users: tenant_max_concurrent_users,\n  tenant_max_events_per_second: tenant_max_events_per_second,\n  tenant_max_joins_per_second: tenant_max_joins_per_second,\n  metrics_cleaner_schedule_timer_in_ms: metrics_cleaner_schedule_timer_in_ms,\n  metrics_rpc_timeout: metrics_rpc_timeout_in_ms,\n  tenant_cache_expiration: tenant_cache_expiration,\n  rpc_timeout: rpc_timeout,\n  no_channel_timeout_in_ms: no_channel_timeout_in_ms,\n  platform: platform,\n  pubsub_adapter: pubsub_adapter,\n  broadcast_pool_size: broadcast_pool_size,\n  presence_pool_size: presence_pool_size,\n  presence_broadcast_period: presence_broadcast_period,\n  presence_permdown_period: presence_permdown_period,\n  users_scope_shards: users_scope_shards,\n  postgres_cdc_scope_shards: postgres_cdc_scope_shards,\n  regional_broadcasting: regional_broadcasting,\n  master_region: master_region,\n  region_mapping: region_mapping,\n  metrics_tags: metrics_tags,\n  measure_traffic_interval_in_ms: measure_traffic_interval_in_ms,\n  client_presence_rate_limit: [\n    max_calls: client_presence_max_calls,\n    window_ms: client_presence_window_ms\n  ],\n  disable_healthcheck_logging: disable_healthcheck_logging,\n  metrics_pusher_enabled: metrics_pusher_enabled,\n  metrics_pusher_url: metrics_pusher_url,\n  metrics_pusher_user: metrics_pusher_user,\n  metrics_pusher_auth: metrics_pusher_auth,\n  metrics_pusher_interval_ms: metrics_pusher_interval_ms,\n  metrics_pusher_timeout_ms: metrics_pusher_timeout_ms,\n  metrics_pusher_compress: metrics_pusher_compress,\n  metrics_separation_enabled: metrics_separation_enabled\n\nif config_env() != :test && run_janitor? do\n  config :realtime,\n    run_janitor: true,\n    janitor_schedule_randomize: janitor_schedule_randomize,\n    janitor_max_children: janitor_max_children,\n    janitor_chunk_size: janitor_chunk_size,\n    janitor_run_after_in_ms: janitor_run_after_in_ms,\n    janitor_children_timeout: janitor_children_timeout,\n    janitor_schedule_timer: janitor_schedule_timer\nend\n\ndefault_cluster_strategy =\n  case config_env() do\n    :prod -> \"POSTGRES\"\n    _ -> \"EPMD\"\n  end\n\ncluster_topologies =\n  System.get_env(\"CLUSTER_STRATEGIES\", default_cluster_strategy)\n  |> String.upcase()\n  |> String.split(\",\")\n  |> Enum.reduce([], fn strategy, acc ->\n    strategy\n    |> String.trim()\n    |> then(fn\n      \"DNS\" ->\n        [\n          dns: [\n            strategy: Cluster.Strategy.DNSPoll,\n            config: [polling_interval: 5_000, query: System.get_env(\"DNS_NODES\"), node_basename: app_name]\n          ]\n        ] ++ acc\n\n      \"POSTGRES\" ->\n        [\n          postgres: [\n            strategy: LibclusterPostgres.Strategy,\n            config: [\n              hostname: default_db_host,\n              username: username,\n              password: password,\n              database: database,\n              port: port,\n              parameters: [application_name: \"cluster_node_#{node()}\"],\n              socket_options: socket_options,\n              ssl: ssl_opts,\n              heartbeat_interval: 5_000\n            ]\n          ]\n        ] ++ acc\n\n      \"EPMD\" ->\n        [\n          dev: [\n            strategy: Cluster.Strategy.Epmd,\n            config: [hosts: [:\"orange@127.0.0.1\", :\"pink@127.0.0.1\"]],\n            connect: {:net_kernel, :connect_node, []},\n            disconnect: {:net_kernel, :disconnect_node, []}\n          ]\n        ] ++ acc\n\n      _ ->\n        acc\n    end)\n  end)\n\n# Setup Logging\n\nif System.get_env(\"LOGS_ENGINE\") == \"logflare\" do\n  config :logflare_logger_backend, url: System.get_env(\"LOGFLARE_LOGGER_BACKEND_URL\", \"https://api.logflare.app\")\n\n  if !System.get_env(\"LOGFLARE_API_KEY\") or !System.get_env(\"LOGFLARE_SOURCE_ID\") do\n    raise \"\"\"\n    Environment variable LOGFLARE_API_KEY or LOGFLARE_SOURCE_ID is missing.\n    Check those variables or choose another LOGS_ENGINE.\n    \"\"\"\n  end\n\n  config :logger,\n    sync_threshold: 6_000,\n    discard_threshold: 6_000,\n    backends: [LogflareLogger.HttpBackend]\nend\n\n# Setup production and development environments\nif config_env() != :test do\n  gen_rpc_socket_ip = System.get_env(\"GEN_RPC_SOCKET_IP\", \"0.0.0.0\") |> to_charlist()\n\n  gen_rpc_ssl_server_port = System.get_env(\"GEN_RPC_SSL_SERVER_PORT\")\n\n  gen_rpc_ssl_server_port =\n    if gen_rpc_ssl_server_port do\n      String.to_integer(gen_rpc_ssl_server_port)\n    end\n\n  gen_rpc_default_driver = if gen_rpc_ssl_server_port, do: :ssl, else: :tcp\n\n  if gen_rpc_default_driver == :ssl do\n    gen_rpc_ssl_opts = [\n      certfile: System.fetch_env!(\"GEN_RPC_CERTFILE\"),\n      keyfile: System.fetch_env!(\"GEN_RPC_KEYFILE\"),\n      cacertfile: System.fetch_env!(\"GEN_RPC_CACERTFILE\")\n    ]\n\n    config :gen_rpc,\n      ssl_server_port: gen_rpc_ssl_server_port,\n      ssl_client_port: System.get_env(\"GEN_RPC_SSL_CLIENT_PORT\", \"6369\") |> String.to_integer(),\n      ssl_client_options: gen_rpc_ssl_opts,\n      ssl_server_options: gen_rpc_ssl_opts,\n      tcp_server_port: false,\n      tcp_client_port: false\n  else\n    config :gen_rpc,\n      ssl_server_port: false,\n      ssl_client_port: false,\n      tcp_server_port: System.get_env(\"GEN_RPC_TCP_SERVER_PORT\", \"5369\") |> String.to_integer(),\n      tcp_client_port: System.get_env(\"GEN_RPC_TCP_CLIENT_PORT\", \"5369\") |> String.to_integer()\n  end\n\n  case :inet.parse_address(gen_rpc_socket_ip) do\n    {:ok, address} ->\n      config :gen_rpc,\n        default_client_driver: gen_rpc_default_driver,\n        connect_timeout: System.get_env(\"GEN_RPC_CONNECT_TIMEOUT_IN_MS\", \"10000\") |> String.to_integer(),\n        send_timeout: System.get_env(\"GEN_RPC_SEND_TIMEOUT_IN_MS\", \"10000\") |> String.to_integer(),\n        ipv6_only: System.get_env(\"GEN_RPC_IPV6_ONLY\", \"false\") == \"true\",\n        socket_ip: address,\n        max_batch_size: System.get_env(\"GEN_RPC_MAX_BATCH_SIZE\", \"0\") |> String.to_integer(),\n        compress: System.get_env(\"GEN_RPC_COMPRESS\", \"0\") |> String.to_integer(),\n        compression_threshold: System.get_env(\"GEN_RPC_COMPRESSION_THRESHOLD_IN_BYTES\", \"1000\") |> String.to_integer()\n\n    _ ->\n      raise \"\"\"\n      Environment variable GEN_RPC_SOCKET_IP is not a valid IP Address\n      Most likely it should be \"0.0.0.0\" (ipv4) or \"::\" (ipv6) to bind to all interfaces\n      \"\"\"\n  end\n\n  config :logger, level: System.get_env(\"LOG_LEVEL\", \"info\") |> String.to_existing_atom()\n\n  config :realtime,\n    request_id_baggage_key: System.get_env(\"REQUEST_ID_BAGGAGE_KEY\", \"request-id\"),\n    jwt_claim_validators: System.get_env(\"JWT_CLAIM_VALIDATORS\", \"{}\"),\n    api_jwt_secret: System.get_env(\"API_JWT_SECRET\"),\n    api_blocklist: System.get_env(\"API_TOKEN_BLOCKLIST\", \"\") |> String.split(\",\"),\n    metrics_blocklist: System.get_env(\"METRICS_TOKEN_BLOCKLIST\", \"\") |> String.split(\",\"),\n    metrics_jwt_secret: System.fetch_env!(\"METRICS_JWT_SECRET\"),\n    db_enc_key: System.get_env(\"DB_ENC_KEY\"),\n    region: region,\n    prom_poll_rate: Env.get_integer(\"PROM_POLL_RATE\", 5000),\n    slot_name_suffix: slot_name_suffix,\n    max_gen_rpc_clients: max_gen_rpc_clients\nend\n\n# Setup Production\n\nif config_env() == :prod do\n  config :libcluster, debug: false, topologies: cluster_topologies\n  config :realtime, node_balance_uptime_threshold_in_ms: node_balance_uptime_threshold_in_ms\n  secret_key_base = System.fetch_env!(\"SECRET_KEY_BASE\")\n  if app_name == \"\", do: raise(\"APP_NAME not available\")\n\n  config :realtime, RealtimeWeb.Endpoint,\n    server: true,\n    url: [host: \"#{app_name}.supabase.co\", port: 443],\n    http: [\n      compress: true,\n      port: Env.get_integer(\"PORT\", 4000),\n      protocol_options: [\n        max_header_value_length: Env.get_integer(\"MAX_HEADER_LENGTH\", 4096)\n      ],\n      transport_options: [\n        max_connections: Env.get_integer(\"MAX_CONNECTIONS\", 1000),\n        num_acceptors: Env.get_integer(\"NUM_ACCEPTORS\", 100),\n        socket_opts: [:inet6]\n      ]\n    ],\n    check_origin: false,\n    secret_key_base: secret_key_base\n\n  alias Realtime.Repo.Replica\n\n  replica_repos = %{\n    Realtime.Repo.Replica.FRA => System.get_env(\"DB_HOST_REPLICA_FRA\", default_db_host),\n    Realtime.Repo.Replica.IAD => System.get_env(\"DB_HOST_REPLICA_IAD\", default_db_host),\n    Realtime.Repo.Replica.SIN => System.get_env(\"DB_HOST_REPLICA_SIN\", default_db_host),\n    Realtime.Repo.Replica.SJC => System.get_env(\"DB_HOST_REPLICA_SJC\", default_db_host),\n    Realtime.Repo.Replica.Singapore => System.get_env(\"DB_HOST_REPLICA_SIN\", default_db_host),\n    Realtime.Repo.Replica.London => System.get_env(\"DB_HOST_REPLICA_FRA\", default_db_host),\n    Realtime.Repo.Replica.NorthVirginia => System.get_env(\"DB_HOST_REPLICA_IAD\", default_db_host),\n    Realtime.Repo.Replica.Oregon => System.get_env(\"DB_HOST_REPLICA_SJC\", default_db_host),\n    Realtime.Repo.Replica.SanJose => System.get_env(\"DB_HOST_REPLICA_SJC\", default_db_host),\n    Realtime.Repo.Replica.Local => default_db_host\n  }\n\n  # Legacy repos\n  # username, password, database, and port must match primary credentials\n  for {replica_repo, hostname} <- replica_repos do\n    config :realtime, replica_repo,\n      hostname: hostname,\n      username: username,\n      password: password,\n      database: database,\n      port: port,\n      pool_size: System.get_env(\"DB_REPLICA_POOL_SIZE\", \"5\") |> String.to_integer(),\n      queue_target: queue_target,\n      queue_interval: queue_interval,\n      parameters: [\n        application_name: \"supabase_mt_realtime_ro\"\n      ],\n      socket_options: socket_options,\n      ssl: ssl_opts\n  end\n\n  # New main replica repo\n  replica_host = System.get_env(\"DB_REPLICA_HOST\")\n\n  if replica_host do\n    config :realtime, Realtime.Repo.Replica,\n      hostname: replica_host,\n      username: username,\n      password: password,\n      database: database,\n      port: port,\n      pool_size: System.get_env(\"DB_REPLICA_POOL_SIZE\", \"5\") |> String.to_integer(),\n      queue_target: queue_target,\n      queue_interval: queue_interval,\n      parameters: [\n        application_name: \"supabase_mt_realtime_ro\"\n      ],\n      socket_options: socket_options,\n      ssl: ssl_opts\n  end\nend\n\nif config_env() != :test do\n  case System.get_env(\"DASHBOARD_AUTH\", \"basic_auth\") do\n    \"zta\" ->\n      config :realtime, dashboard_auth: :zta\n\n    _ ->\n      config :realtime,\n        dashboard_auth: :basic_auth,\n        dashboard_credentials:\n          {System.get_env(\"DASHBOARD_USER\") || raise(\"DASHBOARD_USER is not set\"),\n           System.get_env(\"DASHBOARD_PASSWORD\") || raise(\"DASHBOARD_PASSWORD is not set\")}\n  end\nend\n"
  },
  {
    "path": "config/test.exs",
    "content": "import Config\n\npartition = System.get_env(\"MIX_TEST_PARTITION\")\n\nfor repo <- [\n      Realtime.Repo,\n      Realtime.Repo.Replica.FRA,\n      Realtime.Repo.Replica.IAD,\n      Realtime.Repo.Replica.SIN,\n      Realtime.Repo.Replica.SJC,\n      Realtime.Repo.Replica.Singapore,\n      Realtime.Repo.Replica.London,\n      Realtime.Repo.Replica.NorthVirginia,\n      Realtime.Repo.Replica.Oregon,\n      Realtime.Repo.Replica.SanJose\n    ] do\n  config :realtime, repo,\n    username: \"supabase_admin\",\n    password: \"postgres\",\n    database: \"realtime_test#{partition}\",\n    hostname: \"127.0.0.1\",\n    pool: Ecto.Adapters.SQL.Sandbox\nend\n\nhttp_port = if partition, do: 4002 + String.to_integer(partition), else: 4002\n\nconfig :realtime, RealtimeWeb.Endpoint,\n  http: [port: http_port],\n  server: true\n\n# that's what config/runtime.exs expects to see as region\nSystem.put_env(\"REGION\", \"us-east-1\")\n\nconfig :realtime,\n  regional_broadcasting: true,\n  region: \"us-east-1\",\n  db_enc_key: \"1234567890123456\",\n  jwt_claim_validators: System.get_env(\"JWT_CLAIM_VALIDATORS\", \"{}\"),\n  api_jwt_secret: System.get_env(\"API_JWT_SECRET\", \"secret\"),\n  metrics_jwt_secret: \"test\",\n  prom_poll_rate: 5_000,\n  request_id_baggage_key: \"sb-request-id\",\n  node_balance_uptime_threshold_in_ms: 999_999_999_999,\n  max_gen_rpc_clients: 5,\n  metrics_pusher_req_options: [\n    plug: {Req.Test, Realtime.MetricsPusher}\n  ]\n\n# Print nothing during tests unless captured or a test failure happens\nconfig :logger,\n  backends: [],\n  level: :info\n\n# Configures Elixir's Logger\nconfig :logger, :console,\n  format: \"$time $metadata[$level] $message\\n\",\n  metadata: [:error_code, :request_id, :project, :external_id, :application_name, :sub, :iss, :exp]\n\nconfig :opentelemetry,\n  span_processor: :simple,\n  traces_exporter: :none,\n  processors: [{:otel_simple_processor, %{}}]\n\n# Using different ports so that a remote node during test can connect using the same local network\n# See Clustered module\ngen_rpc_offset = if partition, do: String.to_integer(partition) * 10, else: 0\n\nconfig :gen_rpc,\n  tcp_server_port: 5969 + gen_rpc_offset,\n  tcp_client_port: 5970 + gen_rpc_offset,\n  connect_timeout: 500\n\nconfig :realtime, :dashboard_auth, :basic_auth\nconfig :realtime, :dashboard_credentials, {\"test_user\", \"test_password\"}\n"
  },
  {
    "path": "coveralls.json",
    "content": "{\n    \"skip_files\": [\n        \"lib/realtime_web/api_spec.ex\",\n        \"lib/realtime_web/channels/presence.ex\",\n        \"lib/realtime_web/controllers/page_controller.ex\",\n        \"lib/realtime_web/dashboard/\",\n        \"lib/realtime_web/endpoint.ex\",\n        \"lib/realtime_web/gettext.ex\",\n        \"lib/realtime_web/live/\",\n        \"lib/realtime_web/open_api_schemas.ex\",\n        \"lib/realtime_web/telemetry.ex\",\n        \"lib/realtime_web/views/\",\n        \"lib/realtime.ex\",\n        \"lib/realtime/adapters/changes.ex\",\n        \"lib/realtime/adapters/postgres/decoder.ex\",\n        \"lib/realtime/adapters/postgres/oid_database.ex\",\n        \"lib/realtime/adapters/postgres/protocol/\",\n        \"lib/realtime/application.ex\",\n        \"lib/realtime/monitoring/prom_ex/plugins/phoenix.ex\",\n        \"lib/realtime/operations.ex\",\n        \"lib/realtime/release.ex\",\n        \"lib/realtime/tenants/authorization/policies/broadcast_policies.ex\",\n        \"lib/realtime/tenants/authorization/policies/presence_policies.ex\",\n        \"lib/realtime/tenants/repo/migrations/\",\n        \"/lib/realtime/tenants/cache_supervisor.ex\",\n        \"test/\"\n    ]\n}"
  },
  {
    "path": "deploy/fly/prod.toml",
    "content": "# fly.toml app configuration file generated for realtime-prod on 2023-08-08T09:07:09-07:00\n#\n# See https://fly.io/docs/reference/configuration/ for information about how to use this file.\n#\n\napp = \"realtime-prod\"\nprimary_region = \"sea\"\nkill_signal = \"SIGTERM\"\nkill_timeout = \"5s\"\n\n[experimental]\n  auto_rollback = true\n\n[deploy]\n  release_command = \"/app/bin/migrate\"\n  strategy = \"rolling\"\n\n[env]\n  DNS_NODES = \"realtime-prod.internal\"\n  ERL_CRASH_DUMP = \"/data/erl_crash.dump\"\n  ERL_CRASH_DUMP_SECONDS = \"30\"\n\n\n[[services]]\n  protocol = \"tcp\"\n  internal_port = 4000\n  processes = [\"app\"]\n\n  [[services.ports]]\n    port = 80\n    handlers = [\"http\"]\n    force_https = true\n\n  [[services.ports]]\n    port = 443\n    handlers = [\"tls\", \"http\"]\n  [services.concurrency]\n    type = \"connections\"\n    hard_limit = 100000\n    soft_limit = 100000\n\n  [[services.tcp_checks]]\n    interval = \"15s\"\n    timeout = \"2s\"\n    grace_period = \"30s\"\n\n  [[services.http_checks]]\n    interval = \"10s\"\n    timeout = \"2s\"\n    grace_period = \"5s\"\n    method = \"get\"\n    path = \"/\"\n    protocol = \"http\"\n    tls_skip_verify = false\n"
  },
  {
    "path": "deploy/fly/qa.toml",
    "content": "app = \"realtime-qa\"\nkill_signal = \"SIGTERM\"\nkill_timeout = 5\nprocesses = []\n\n[deploy]\n  release_command = \"/app/bin/migrate\"\n  strategy = \"rolling\"\n\n[env]\n  DNS_NODES = \"realtime-qa.internal\"\n  ERL_CRASH_DUMP = \"/data/erl_crash.dump\"\n  ERL_CRASH_DUMP_SECONDS = 30\n\n[experimental]\n  allowed_public_ports = []\n  auto_rollback = true\n\n[[services]]\n  internal_port = 4000\n  processes = [\"app\"]\n  protocol = \"tcp\"\n  script_checks = []\n  [services.concurrency]\n    hard_limit = 100000\n    soft_limit = 100000\n    type = \"connections\"\n\n  [[services.ports]]\n    force_https = true\n    handlers = [\"http\"]\n    port = 80\n\n  [[services.ports]]\n    handlers = [\"tls\", \"http\"]\n    port = 443\n\n  [[services.tcp_checks]]\n    grace_period = \"30s\"\n    interval = \"15s\"\n    restart_limit = 6\n    timeout = \"2s\"\n\n  [[services.http_checks]]\n    interval = 10000\n    grace_period = \"5s\"\n    method = \"get\"\n    path = \"/\"\n    protocol = \"http\"\n    restart_limit = 0\n    timeout = 2000\n    tls_skip_verify = false\n    [services.http_checks.headers]\n"
  },
  {
    "path": "deploy/fly/staging.toml",
    "content": "# fly.toml app configuration file generated for realtime-staging on 2023-06-27T07:39:20-07:00\n#\n# See https://fly.io/docs/reference/configuration/ for information about how to use this file.\n#\n\napp = \"realtime-staging\"\nprimary_region = \"lhr\"\nkill_signal = \"SIGTERM\"\nkill_timeout = \"5s\"\n\n[experimental]\n  auto_rollback = true\n\n[deploy]\n  release_command = \"/app/bin/migrate\"\n  strategy = \"rolling\"\n\n[env]\n  DNS_NODES = \"realtime-staging.internal\"\n  ERL_CRASH_DUMP = \"/data/erl_crash.dump\"\n  ERL_CRASH_DUMP_SECONDS = \"30\"\n\n[[mounts]]\n  source = \"data_vol_machines\"\n  destination = \"/data\"\n  processes = [\"app\"]\n\n[[services]]\n  protocol = \"tcp\"\n  internal_port = 4000\n  processes = [\"app\"]\n\n  [[services.ports]]\n    port = 80\n    handlers = [\"http\"]\n    force_https = true\n\n  [[services.ports]]\n    port = 443\n    handlers = [\"tls\", \"http\"]\n  [services.concurrency]\n    type = \"connections\"\n    hard_limit = 16384\n    soft_limit = 16384\n\n  [[services.tcp_checks]]\n    interval = \"15s\"\n    timeout = \"2s\"\n    grace_period = \"30s\"\n    restart_limit = 6\n\n  [[services.http_checks]]\n    interval = \"10s\"\n    timeout = \"2s\"\n    grace_period = \"5s\"\n    restart_limit = 0\n    method = \"get\"\n    path = \"/\"\n    protocol = \"http\"\n"
  },
  {
    "path": "dev/postgres/00-supabase-schema.sql",
    "content": "create schema if not exists _realtime;\ncreate schema if not exists realtime;\n"
  },
  {
    "path": "docker-compose.dbs.yml",
    "content": "version: '3'\n\nservices:\n  db:\n    image: supabase/postgres:17.6.1.074\n    container_name: realtime-db\n    ports:\n      - \"5432:5432\"\n    volumes:\n      - ./dev/postgres/00-supabase-schema.sql:/docker-entrypoint-initdb.d/00-supabase-schema.sql\n    command: postgres -c config_file=/etc/postgresql/postgresql.conf\n    environment:\n      POSTGRES_HOST: /var/run/postgresql\n      POSTGRES_PASSWORD: postgres\n  tenant_db:\n    image: supabase/postgres:17.6.1.074\n    container_name: tenant-db\n    ports:\n      - \"5433:5432\"\n    command: postgres -c config_file=/etc/postgresql/postgresql.conf\n    environment:\n      POSTGRES_HOST: /var/run/postgresql\n      POSTGRES_PASSWORD: postgres\n"
  },
  {
    "path": "docker-compose.tests.yml",
    "content": "services:\n  # Supabase Realtime service\n  test_db:\n    image: supabase/postgres:17.6.1.074\n    container_name: test-realtime-db\n    ports:\n      - \"5532:5432\"\n    volumes:\n      - ./dev/postgres:/docker-entrypoint-initdb.d/\n    command: postgres -c config_file=/etc/postgresql/postgresql.conf\n    environment:\n      POSTGRES_HOST: /var/run/postgresql\n      POSTGRES_PASSWORD: postgres\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U postgres\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n  test_realtime:\n    depends_on:\n      - test_db\n    build: .\n    container_name: test-realtime-server\n    ports:\n      - \"4100:4100\"\n    extra_hosts:\n      - \"host.docker.internal:host-gateway\"\n    environment:\n      PORT: 4100\n      DB_HOST: host.docker.internal\n      DB_PORT: 5532\n      DB_USER: supabase_admin\n      DB_PASSWORD: postgres\n      DB_NAME: postgres\n      DB_ENC_KEY: 1234567890123456\n      DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'\n      API_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long\n      METRICS_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long\n      SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq\n      ERL_AFLAGS: -proto_dist inet_tcp\n      DNS_NODES: \"''\"\n      APP_NAME: realtime\n      RUN_JANITOR: true\n      JANITOR_INTERVAL: 60000\n      LOG_LEVEL: \"info\"\n      SEED_SELF_HOST: true\n      DASHBOARD_USER: admin\n      DASHBOARD_PASSWORD: admin\n    networks:\n      test-network:\n        aliases:\n          - realtime-dev.local\n          - realtime-dev.localhost\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:4100/\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n      start_period: 5s\n\n  # Deno test runner\n  test-runner:\n    image: denoland/deno:alpine-2.5.6\n    container_name: deno-test-runner\n    depends_on:\n      test_realtime:\n        condition: service_healthy\n      test_db:\n        condition: service_healthy\n    volumes:\n      - ./test/integration/tests.ts:/app/tests.ts:ro\n    working_dir: /app\n    command: >\n      sh -c \"\n      echo 'Running tests...' &&\n      deno test tests.ts --allow-import --no-check --allow-read --allow-net --trace-leaks --allow-env=WS_NO_BUFFER_UTIL\n      \"\n    networks:\n      - test-network\n    extra_hosts:\n      - \"realtime-dev.localhost:host-gateway\"\n\nnetworks:\n  test-network:\n    driver: bridge\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  db:\n    image: supabase/postgres:17.6.1.074\n    container_name: realtime-db\n    ports:\n      - \"5432:5432\"\n    volumes:\n      - ./dev/postgres/00-supabase-schema.sql:/docker-entrypoint-initdb.d/00-supabase-schema.sql\n    command: postgres -c config_file=/etc/postgresql/postgresql.conf\n    environment:\n      POSTGRES_HOST: /var/run/postgresql\n      POSTGRES_PASSWORD: postgres\n  tenant_db:\n    image: supabase/postgres:17.6.1.074\n    container_name: tenant-db\n    ports:\n      - \"5433:5432\"\n    command: postgres -c config_file=/etc/postgresql/postgresql.conf\n    environment:\n      POSTGRES_HOST: /var/run/postgresql\n      POSTGRES_PASSWORD: postgres\n  realtime:\n    depends_on:\n      - db\n    build: .\n    container_name: realtime-server\n    ports:\n      - \"4000:4000\"\n    extra_hosts:\n      - \"host.docker.internal:host-gateway\"\n    environment:\n      PORT: 4000\n      DB_HOST: host.docker.internal\n      DB_PORT: 5432\n      DB_USER: supabase_admin\n      DB_PASSWORD: postgres\n      DB_NAME: postgres\n      DB_ENC_KEY: supabaserealtime\n      DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'\n      API_JWT_SECRET: dc447559-996d-4761-a306-f47a5eab1623\n      SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq\n      ERL_AFLAGS: -proto_dist inet_tcp\n      RLIMIT_NOFILE: 1000000\n      DNS_NODES: \"''\"\n      APP_NAME: realtime\n      RUN_JANITOR: true\n      JANITOR_INTERVAL: 60000\n      LOG_LEVEL: \"info\"\n      SEED_SELF_HOST: true\n      METRICS_JWT_SECRET: dc447559-996d-4761-a306-f47a5eab1623\n      DASHBOARD_USER: admin\n      DASHBOARD_PASSWORD: admin\n\n"
  },
  {
    "path": "lib/extensions/extensions.ex",
    "content": "defmodule Realtime.Extensions do\n  @moduledoc \"\"\"\n  This module provides functions to get extension settings.\n  \"\"\"\n  def db_settings(type) do\n    db_settings =\n      Application.get_env(:realtime, :extensions)\n      |> Enum.reduce(nil, fn\n        {_, %{key: ^type, db_settings: db_settings}}, _ -> db_settings\n        _, acc -> acc\n      end)\n\n    if db_settings do\n      %{default: apply(db_settings, :default, []), required: apply(db_settings, :required, [])}\n    else\n      %{default: %{}, required: []}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/extensions/postgres_cdc_rls/cdc_rls.ex",
    "content": "defmodule Extensions.PostgresCdcRls do\n  @moduledoc \"\"\"\n  Callbacks for initiating a Postgres connection and creating a Realtime subscription for database changes.\n  \"\"\"\n\n  @behaviour Realtime.PostgresCdc\n  use Realtime.Logs\n\n  alias Extensions.PostgresCdcRls, as: Rls\n  alias Realtime.GenCounter\n  alias Realtime.GenRpc\n  alias RealtimeWeb.Endpoint\n  alias Rls.Subscriptions\n\n  @impl true\n  @spec handle_connect(map()) :: {:ok, {pid(), pid()}} | nil\n  def handle_connect(args) do\n    case get_manager_conn(args[\"id\"]) do\n      {:error, nil} ->\n        start_distributed(args)\n        nil\n\n      {:error, :wait} ->\n        nil\n\n      {:ok, pid, conn} ->\n        {:ok, {pid, conn}}\n    end\n  end\n\n  @impl true\n  def handle_after_connect({manager_pid, conn}, settings, params_list, tenant) do\n    with {:ok, subscription_list} <- subscription_list(params_list) do\n      pool_size = Map.get(settings, \"subcriber_pool_size\", 4)\n      publication = settings[\"publication\"]\n      create_subscription(conn, tenant, publication, pool_size, subscription_list, manager_pid, self())\n    end\n  end\n\n  @database_timeout_reason \"Too many database timeouts\"\n\n  def create_subscription(conn, tenant, publication, pool_size, subscription_list, manager_pid, caller)\n      when node(conn) == node() do\n    with_rate_counter(tenant, pool_size, fn rate_counter ->\n      case Subscriptions.create(conn, publication, subscription_list, manager_pid, caller) do\n        {:error, %DBConnection.ConnectionError{}} ->\n          GenCounter.add(rate_counter.id)\n          {:error, @database_timeout_reason}\n\n        {:error, {:exit, _}} ->\n          GenCounter.add(rate_counter.id)\n          {:error, @database_timeout_reason}\n\n        response ->\n          response\n      end\n    end)\n  end\n\n  def create_subscription(conn, tenant, publication, pool_size, subscription_list, manager_pid, caller) do\n    with_rate_counter(tenant, pool_size, fn rate_counter ->\n      args = [conn, tenant, publication, pool_size, subscription_list, manager_pid, caller]\n\n      case GenRpc.call(node(conn), __MODULE__, :create_subscription, args, timeout: 15_000, tenant_id: tenant) do\n        {:error, @database_timeout_reason} ->\n          GenCounter.add(rate_counter.id)\n          {:error, @database_timeout_reason}\n\n        response ->\n          response\n      end\n    end)\n  end\n\n  defp with_rate_counter(tenant, pool_size, fun) do\n    with {:ok, %{limit: %{triggered: false}} = rate_counter} <- rate_counter(tenant, pool_size) do\n      fun.(rate_counter)\n    else\n      {:ok, _} ->\n        {:error, @database_timeout_reason}\n\n      {:error, reason} ->\n        log_error(\"RateCounterError\", reason)\n        {:error, @database_timeout_reason}\n    end\n  end\n\n  defp rate_counter(tenant_id, pool_size) do\n    rate_counter_args = Realtime.Tenants.subscription_errors_per_second_rate(tenant_id, pool_size)\n    Realtime.RateCounter.get(rate_counter_args)\n  rescue\n    e -> {:error, e}\n  end\n\n  defp subscription_list(params_list) do\n    Enum.reduce_while(params_list, {:ok, []}, fn params, {:ok, acc} ->\n      case Subscriptions.parse_subscription_params(params[:params]) do\n        {:ok, subscription_params} ->\n          {:cont, {:ok, [%{id: params.id, claims: params.claims, subscription_params: subscription_params} | acc]}}\n\n        {:error, reason} ->\n          {:halt, {:error, {:malformed_subscription_params, reason}}}\n      end\n    end)\n  end\n\n  @impl true\n  def handle_subscribe(_, tenant, metadata) do\n    Endpoint.subscribe(\"realtime:postgres:\" <> tenant, metadata)\n  end\n\n  @impl true\n  @doc \"\"\"\n  Stops the Supervision tree for a tenant.\n\n  Expects an `external_id` as the `tenant`.\n  \"\"\"\n\n  @spec handle_stop(String.t(), non_neg_integer()) :: :ok\n  def handle_stop(tenant, timeout) when is_binary(tenant) do\n    scope = Realtime.Syn.PostgresCdc.scope(tenant)\n\n    case :syn.whereis_name({scope, tenant}) do\n      :undefined ->\n        Logger.warning(\"Database supervisor not found for tenant #{tenant}\")\n        :ok\n\n      pid ->\n        DynamicSupervisor.stop(pid, :shutdown, timeout)\n    end\n  end\n\n  ## Internal functions\n\n  def start_distributed(%{\"region\" => region, \"id\" => tenant} = args) do\n    platform_region = Realtime.Nodes.platform_region_translator(region)\n    launch_node = Realtime.Nodes.launch_node(platform_region, node(), tenant)\n\n    Logger.warning(\n      \"Starting distributed postgres extension #{inspect(lauch_node: launch_node, region: region, platform_region: platform_region)}\"\n    )\n\n    case GenRpc.call(launch_node, __MODULE__, :start, [args], timeout: 30_000, tenant_id: tenant) do\n      {:ok, _pid} = ok ->\n        ok\n\n      {:error, {:already_started, _pid}} = error ->\n        Logger.info(\"Postgres Extension already started on node #{inspect(launch_node)}\")\n        error\n\n      error ->\n        log_error(\"ErrorStartingPostgresCDC\", error)\n        error\n    end\n  end\n\n  @doc \"\"\"\n  Start db poller. Expects an `external_id` as a `tenant`.\n  \"\"\"\n\n  @spec start(map()) :: {:ok, pid} | {:error, :already_started | :reserved}\n  def start(%{\"id\" => tenant} = args) when is_binary(tenant) do\n    Logger.debug(\"Starting #{__MODULE__} extension with args: #{inspect(args, pretty: true)}\")\n\n    DynamicSupervisor.start_child(\n      {:via, PartitionSupervisor, {Rls.DynamicSupervisor, tenant}},\n      %{\n        id: tenant,\n        start: {Rls.WorkerSupervisor, :start_link, [args]},\n        restart: :temporary\n      }\n    )\n  end\n\n  @spec get_manager_conn(String.t()) :: {:error, nil | :wait} | {:ok, pid(), pid()}\n  def get_manager_conn(id) do\n    scope = Realtime.Syn.PostgresCdc.scope(id)\n\n    case :syn.lookup(scope, id) do\n      {_, %{manager: nil, subs_pool: nil}} -> {:error, :wait}\n      {_, %{manager: manager, subs_pool: conn}} -> {:ok, manager, conn}\n      _ -> {:error, nil}\n    end\n  end\n\n  @spec supervisor_id(String.t(), String.t()) :: {atom(), String.t(), map()}\n  def supervisor_id(tenant, region) do\n    scope = Realtime.Syn.PostgresCdc.scope(tenant)\n    {scope, tenant, %{region: region, manager: nil, subs_pool: nil}}\n  end\n\n  @spec update_meta(String.t(), pid(), pid()) :: {:ok, {pid(), term()}} | {:error, term()}\n  def update_meta(tenant, manager_pid, subs_pool) do\n    scope = Realtime.Syn.PostgresCdc.scope(tenant)\n\n    :syn.update_registry(scope, tenant, fn pid, meta ->\n      if node(pid) == node(manager_pid) do\n        %{meta | manager: manager_pid, subs_pool: subs_pool}\n      else\n        Logger.warning(\"Node mismatch for tenant #{tenant} #{inspect(node(pid))} #{inspect(node(manager_pid))}\")\n\n        meta\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "lib/extensions/postgres_cdc_rls/db_settings.ex",
    "content": "defmodule Extensions.PostgresCdcRls.DbSettings do\n  @moduledoc \"\"\"\n  Schema callbacks for CDC RLS implementation.\n  \"\"\"\n\n  def default do\n    %{\n      \"poll_interval_ms\" => 100,\n      \"poll_max_changes\" => 100,\n      \"poll_max_record_bytes\" => 1_048_576,\n      \"publication\" => \"supabase_realtime\",\n      \"slot_name\" => \"supabase_realtime_replication_slot\"\n    }\n  end\n\n  def required do\n    [\n      {\"region\", &is_binary/1, false},\n      {\"db_host\", &is_binary/1, true},\n      {\"db_name\", &is_binary/1, true},\n      {\"db_user\", &is_binary/1, true},\n      {\"db_port\", &is_binary/1, true},\n      {\"db_password\", &is_binary/1, true}\n    ]\n  end\nend\n"
  },
  {
    "path": "lib/extensions/postgres_cdc_rls/message_dispatcher.ex",
    "content": "# This file draws from https://github.com/phoenixframework/phoenix/blob/9941711736c8464b27b40914a4d954ed2b4f5958/lib/phoenix/channel/server.ex\n# License: https://github.com/phoenixframework/phoenix/blob/518a4640a70aa4d1370a64c2280d598e5b928168/LICENSE.md\n\ndefmodule Extensions.PostgresCdcRls.MessageDispatcher do\n  @moduledoc \"\"\"\n  Hook invoked by Phoenix.PubSub dispatch.\n  \"\"\"\n\n  alias Phoenix.Socket.Broadcast\n\n  def dispatch([_ | _] = topic_subscriptions, _from, {type, payload, sub_ids}) do\n    _ =\n      Enum.reduce(topic_subscriptions, %{}, fn\n        {_pid, {:subscriber_fastlane, fastlane_pid, serializer, ids, join_topic, is_new_api}}, cache ->\n          for {bin_id, id} <- ids, reduce: [] do\n            acc ->\n              if MapSet.member?(sub_ids, bin_id) do\n                [id | acc]\n              else\n                acc\n              end\n          end\n          |> case do\n            [_ | _] = valid_ids ->\n              new_payload =\n                if is_new_api do\n                  %Broadcast{\n                    topic: join_topic,\n                    event: \"postgres_changes\",\n                    payload: %{ids: valid_ids, data: Jason.Fragment.new(payload)}\n                  }\n                else\n                  %Broadcast{topic: join_topic, event: type, payload: Jason.Fragment.new(payload)}\n                end\n\n              broadcast_message(cache, fastlane_pid, new_payload, serializer)\n\n            _ ->\n              cache\n          end\n      end)\n\n    :ok\n  end\n\n  defp broadcast_message(cache, fastlane_pid, msg, serializer) do\n    case cache do\n      %{^msg => encoded_msg} ->\n        send(fastlane_pid, encoded_msg)\n        cache\n\n      %{} ->\n        encoded_msg = serializer.fastlane!(msg)\n        send(fastlane_pid, encoded_msg)\n        Map.put(cache, msg, encoded_msg)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/extensions/postgres_cdc_rls/replication_poller.ex",
    "content": "defmodule Extensions.PostgresCdcRls.ReplicationPoller do\n  @moduledoc \"\"\"\n  Polls the write ahead log, applies row level sucurity policies for each subscriber\n  and broadcast records to the `MessageDispatcher`.\n  \"\"\"\n\n  use GenServer\n  use Realtime.Logs\n\n  import Realtime.Helpers\n\n  alias DBConnection.Backoff\n\n  alias Extensions.PostgresCdcRls.MessageDispatcher\n  alias Extensions.PostgresCdcRls.Replications\n\n  alias Realtime.Adapters.Changes.DeletedRecord\n  alias Realtime.Adapters.Changes.NewRecord\n  alias Realtime.Adapters.Changes.UpdatedRecord\n  alias Realtime.Database\n  alias Realtime.RateCounter\n  alias Realtime.Tenants\n\n  alias RealtimeWeb.TenantBroadcaster\n\n  def start_link(opts), do: GenServer.start_link(__MODULE__, opts)\n\n  @impl true\n  def init(args) do\n    Process.flag(:fullsweep_after, 20)\n    tenant_id = args[\"id\"]\n    Logger.metadata(external_id: tenant_id, project: tenant_id)\n\n    %Realtime.Api.Tenant{} = tenant = Tenants.Cache.get_tenant_by_external_id(tenant_id)\n    rate_counter_args = Tenants.db_events_per_second_rate(tenant)\n    extension = Realtime.PostgresCdc.filter_settings(\"postgres_cdc_rls\", tenant.extensions)\n\n    RateCounter.new(rate_counter_args)\n\n    state = %{\n      backoff: Backoff.new(backoff_min: 100, backoff_max: 5_000, backoff_type: :rand_exp),\n      max_changes: extension[\"poll_max_changes\"],\n      max_record_bytes: extension[\"poll_max_record_bytes\"],\n      poll_interval_ms: extension[\"poll_interval_ms\"],\n      poll_ref: nil,\n      publication: extension[\"publication\"],\n      retry_ref: nil,\n      retry_count: 0,\n      slot_name: extension[\"slot_name\"] <> slot_name_suffix(),\n      tenant_id: tenant_id,\n      rate_counter_args: rate_counter_args,\n      subscribers_nodes_table: args[\"subscribers_nodes_table\"]\n    }\n\n    {:ok, _} = Registry.register(__MODULE__.Registry, tenant_id, %{})\n    {:ok, state, {:continue, {:connect, tenant}}}\n  end\n\n  @impl true\n  def handle_continue({:connect, tenant}, state) do\n    realtime_rls_settings = Database.from_tenant(tenant, \"realtime_rls\")\n\n    with {:ok, conn} <- Database.connect_db(realtime_rls_settings) do\n      {:noreply, Map.put(state, :conn, conn), {:continue, :prepare}}\n    else\n      {:error, reason} ->\n        log_error(\"ReplicationPollerConnectionFailed\", reason)\n        {:stop, reason, state}\n    end\n  end\n\n  def handle_continue(:prepare, state) do\n    {:noreply, prepare_replication(state)}\n  end\n\n  @impl true\n  def handle_info(\n        :poll,\n        %{\n          backoff: backoff,\n          poll_interval_ms: poll_interval_ms,\n          poll_ref: poll_ref,\n          publication: publication,\n          retry_ref: retry_ref,\n          retry_count: retry_count,\n          slot_name: slot_name,\n          max_record_bytes: max_record_bytes,\n          max_changes: max_changes,\n          conn: conn,\n          tenant_id: tenant_id,\n          subscribers_nodes_table: subscribers_nodes_table,\n          rate_counter_args: rate_counter_args\n        } = state\n      ) do\n    cancel_timer(poll_ref)\n    cancel_timer(retry_ref)\n\n    args = [conn, slot_name, publication, max_changes, max_record_bytes]\n    {time, list_changes} = :timer.tc(Replications, :list_changes, args)\n    record_list_changes_telemetry(time, tenant_id)\n\n    case handle_list_changes_result(list_changes, subscribers_nodes_table, tenant_id, rate_counter_args) do\n      {:ok, row_count} ->\n        Backoff.reset(backoff)\n\n        pool_ref =\n          if row_count > 0 do\n            send(self(), :poll)\n            nil\n          else\n            Process.send_after(self(), :poll, poll_interval_ms)\n          end\n\n        {:noreply, %{state | backoff: backoff, poll_ref: pool_ref}}\n\n      {:error, %Postgrex.Error{postgres: %{code: :object_in_use, message: msg}}} ->\n        log_error(\"ReplicationSlotBeingUsed\", msg)\n        [_, db_pid] = Regex.run(~r/PID\\s(\\d*)$/, msg)\n        db_pid = String.to_integer(db_pid)\n\n        case Replications.get_pg_stat_activity_diff(conn, db_pid) do\n          {:ok, diff} ->\n            Logger.warning(\"Database PID #{db_pid} found in pg_stat_activity with state_change diff of #{diff}\")\n\n          {:error, reason} ->\n            log_error(\"PgStatActivityQueryFailed\", reason)\n        end\n\n        if retry_count > 3 do\n          case Replications.terminate_backend(conn, slot_name) do\n            {:ok, :terminated} -> Logger.warning(\"Replication slot in use - terminating\")\n            {:error, :slot_not_found} -> Logger.warning(\"Replication slot not found\")\n            {:error, error} -> Logger.warning(\"Error terminating backend: #{inspect(error)}\")\n          end\n        end\n\n        {timeout, backoff} = Backoff.backoff(backoff)\n        retry_ref = Process.send_after(self(), :retry, timeout)\n\n        {:noreply, %{state | backoff: backoff, retry_ref: retry_ref, retry_count: retry_count + 1}}\n\n      {:error, reason} ->\n        log_error(\"PoolingReplicationError\", reason)\n\n        {timeout, backoff} = Backoff.backoff(backoff)\n        retry_ref = Process.send_after(self(), :retry, timeout)\n\n        {:noreply, %{state | backoff: backoff, retry_ref: retry_ref, retry_count: retry_count + 1}}\n    end\n  end\n\n  @impl true\n  def handle_info(:retry, %{retry_ref: retry_ref} = state) do\n    cancel_timer(retry_ref)\n    {:noreply, prepare_replication(state)}\n  end\n\n  def slot_name_suffix do\n    case Application.get_env(:realtime, :slot_name_suffix) do\n      nil -> \"\"\n      slot_name_suffix -> \"_\" <> slot_name_suffix\n    end\n  end\n\n  defp convert_errors([_ | _] = errors), do: errors\n\n  defp convert_errors(_), do: nil\n\n  defp prepare_replication(%{backoff: backoff, conn: conn, slot_name: slot_name, retry_count: retry_count} = state) do\n    case Replications.prepare_replication(conn, slot_name) do\n      {:ok, _} ->\n        send(self(), :poll)\n        state\n\n      {:error, error} ->\n        log_error(\"PoolingReplicationPreparationError\", error)\n\n        {timeout, backoff} = Backoff.backoff(backoff)\n        retry_ref = Process.send_after(self(), :retry, timeout)\n        %{state | backoff: backoff, retry_ref: retry_ref, retry_count: retry_count + 1}\n    end\n  end\n\n  defp record_list_changes_telemetry(time, tenant_id) do\n    Realtime.Telemetry.execute(\n      [:realtime, :replication, :poller, :query, :stop],\n      %{duration: time},\n      %{tenant: tenant_id}\n    )\n  end\n\n  defp handle_list_changes_result(\n         {:ok,\n          %Postgrex.Result{\n            columns: columns,\n            rows: [_ | _] = rows,\n            num_rows: rows_count\n          }},\n         subscribers_nodes_table,\n         tenant_id,\n         rate_counter_args\n       ) do\n    case RateCounter.get(rate_counter_args) do\n      {:ok, %{limit: %{triggered: true}}} ->\n        :ok\n\n      _ ->\n        for row <- rows,\n            change <- columns |> Enum.zip(row) |> generate_record() |> List.wrap() do\n          topic = \"realtime:postgres:\" <> tenant_id\n\n          Realtime.GenCounter.add(rate_counter_args.id, MapSet.size(change.subscription_ids))\n\n          payload =\n            change\n            |> Map.drop([:subscription_ids])\n            |> Jason.encode!()\n\n          case collect_subscription_nodes(subscribers_nodes_table, change.subscription_ids) do\n            {:ok, nodes} ->\n              for {node, subscription_ids} <- nodes do\n                TenantBroadcaster.pubsub_direct_broadcast(\n                  node,\n                  tenant_id,\n                  topic,\n                  # Send only the subscription IDs relevant to this node\n                  {change.type, payload, MapSet.new(subscription_ids)},\n                  MessageDispatcher,\n                  :postgres_changes\n                )\n              end\n\n            {:error, :node_not_found} ->\n              TenantBroadcaster.pubsub_broadcast(\n                tenant_id,\n                topic,\n                {change.type, payload, change.subscription_ids},\n                MessageDispatcher,\n                :postgres_changes\n              )\n          end\n        end\n    end\n\n    {:ok, rows_count}\n  end\n\n  defp handle_list_changes_result({:ok, _}, _, _, _), do: {:ok, 0}\n  defp handle_list_changes_result({:error, reason}, _, _, _), do: {:error, reason}\n\n  defp collect_subscription_nodes(subscribers_nodes_table, subscription_ids) do\n    Enum.reduce_while(subscription_ids, {:ok, %{}}, fn subscription_id, {:ok, acc} ->\n      case :ets.lookup(subscribers_nodes_table, subscription_id) do\n        [{_, node}] ->\n          updated_acc =\n            Map.update(acc, node, [subscription_id], fn existing_ids -> [subscription_id | existing_ids] end)\n\n          {:cont, {:ok, updated_acc}}\n\n        _ ->\n          {:halt, {:error, :node_not_found}}\n      end\n    end)\n  rescue\n    _ -> {:error, :node_not_found}\n  end\n\n  def generate_record([\n        {\"type\", \"INSERT\" = type},\n        {\"schema\", schema},\n        {\"table\", table},\n        {\"columns\", columns},\n        {\"record\", record},\n        {\"old_record\", _},\n        {\"commit_timestamp\", commit_timestamp},\n        {\"subscription_ids\", subscription_ids},\n        {\"errors\", errors}\n      ])\n      when is_list(subscription_ids) do\n    %NewRecord{\n      columns: Jason.Fragment.new(columns),\n      commit_timestamp: commit_timestamp,\n      errors: convert_errors(errors),\n      schema: schema,\n      table: table,\n      type: type,\n      subscription_ids: MapSet.new(subscription_ids),\n      record: Jason.Fragment.new(record)\n    }\n  end\n\n  def generate_record([\n        {\"type\", \"UPDATE\" = type},\n        {\"schema\", schema},\n        {\"table\", table},\n        {\"columns\", columns},\n        {\"record\", record},\n        {\"old_record\", old_record},\n        {\"commit_timestamp\", commit_timestamp},\n        {\"subscription_ids\", subscription_ids},\n        {\"errors\", errors}\n      ])\n      when is_list(subscription_ids) do\n    %UpdatedRecord{\n      columns: Jason.Fragment.new(columns),\n      commit_timestamp: commit_timestamp,\n      errors: convert_errors(errors),\n      schema: schema,\n      table: table,\n      type: type,\n      subscription_ids: MapSet.new(subscription_ids),\n      old_record: Jason.Fragment.new(old_record),\n      record: Jason.Fragment.new(record)\n    }\n  end\n\n  def generate_record([\n        {\"type\", \"DELETE\" = type},\n        {\"schema\", schema},\n        {\"table\", table},\n        {\"columns\", columns},\n        {\"record\", _},\n        {\"old_record\", old_record},\n        {\"commit_timestamp\", commit_timestamp},\n        {\"subscription_ids\", subscription_ids},\n        {\"errors\", errors}\n      ])\n      when is_list(subscription_ids) do\n    %DeletedRecord{\n      columns: Jason.Fragment.new(columns),\n      commit_timestamp: commit_timestamp,\n      errors: convert_errors(errors),\n      schema: schema,\n      table: table,\n      type: type,\n      subscription_ids: MapSet.new(subscription_ids),\n      old_record: Jason.Fragment.new(old_record)\n    }\n  end\n\n  def generate_record(_), do: nil\nend\n"
  },
  {
    "path": "lib/extensions/postgres_cdc_rls/replications.ex",
    "content": "defmodule Extensions.PostgresCdcRls.Replications do\n  @moduledoc \"\"\"\n  SQL queries that use PostgresCdcRls.ReplicationPoller to create a temporary slot and poll the write-ahead log.\n  \"\"\"\n\n  import Postgrex, only: [query: 3]\n\n  @spec prepare_replication(pid(), String.t()) ::\n          {:ok, Postgrex.Result.t()} | {:error, Postgrex.Error.t()}\n  def prepare_replication(conn, slot_name) do\n    query(\n      conn,\n      \"select\n        case when not exists (\n          select 1\n          from pg_replication_slots\n          where slot_name = $1\n        )\n        then (\n          select 1 from pg_create_logical_replication_slot($1, 'wal2json', 'true')\n        )\n        else 1\n        end;\",\n      [slot_name]\n    )\n  end\n\n  @spec terminate_backend(pid(), String.t()) ::\n          {:ok, :terminated} | {:error, :slot_not_found | Postgrex.Error.t()}\n  def terminate_backend(conn, slot_name) do\n    slots =\n      query(conn, \"select active_pid from pg_replication_slots where slot_name = $1\", [slot_name])\n\n    case slots do\n      {:ok, %Postgrex.Result{rows: [[nil]]}} ->\n        {:error, :slot_not_found}\n\n      {:ok, %Postgrex.Result{rows: [[backend]]}} ->\n        case query(conn, \"select pg_terminate_backend($1)\", [backend]) do\n          {:ok, _resp} -> {:ok, :terminated}\n          {:error, erroer} -> {:error, erroer}\n        end\n\n      {:ok, %Postgrex.Result{num_rows: 0}} ->\n        {:error, :slot_not_found}\n\n      {:error, error} ->\n        {:error, error}\n    end\n  end\n\n  @spec get_pg_stat_activity_diff(pid(), integer()) ::\n          {:ok, integer()} | {:error, Postgrex.Error.t()}\n  def get_pg_stat_activity_diff(conn, db_pid) do\n    query =\n      query(\n        conn,\n        \"select\n         extract(\n          epoch from (now() - state_change)\n         )::int as diff\n         from pg_stat_activity where application_name = 'realtime_rls' and pid = $1\",\n        [db_pid]\n      )\n\n    case query do\n      {:ok, %{rows: [[diff]]}} -> {:ok, diff}\n      {:ok, _} -> {:error, :pid_not_found}\n      {:error, error} -> {:error, error}\n    end\n  end\n\n  def list_changes(conn, slot_name, publication, max_changes, max_record_bytes) do\n    query(\n      conn,\n      \"\"\"\n      SELECT wal->>'type' as type,\n             wal->>'schema' as schema,\n             wal->>'table' as table,\n             COALESCE(wal->>'columns', '[]') as columns,\n             COALESCE(wal->>'record', '{}') as record,\n             COALESCE(wal->>'old_record', '{}') as old_record,\n             wal->>'commit_timestamp' as commit_timestamp,\n             subscription_ids,\n             errors\n      FROM realtime.list_changes($1, $2, $3, $4)\n      \"\"\",\n      [\n        publication,\n        slot_name,\n        max_changes,\n        max_record_bytes\n      ]\n    )\n  end\nend\n"
  },
  {
    "path": "lib/extensions/postgres_cdc_rls/subscription_manager.ex",
    "content": "defmodule Extensions.PostgresCdcRls.SubscriptionManager do\n  @moduledoc \"\"\"\n  Handles subscriptions from tenant's database.\n  \"\"\"\n  use GenServer\n  use Realtime.Logs\n\n  alias Realtime.Tenants.Rebalancer\n  alias Extensions.PostgresCdcRls, as: Rls\n\n  alias Realtime.Database\n  alias Realtime.Helpers\n\n  alias Rls.Subscriptions\n\n  @timeout 15_000\n  @max_delete_records 1000\n  @check_oids_interval 60_000\n  @check_no_users_interval 60_000\n  @stop_after 60_000 * 10\n\n  defmodule State do\n    @moduledoc false\n    defstruct [\n      :id,\n      :publication,\n      :subscribers_pids_table,\n      :subscribers_nodes_table,\n      :conn,\n      :delete_queue,\n      :no_users_ref,\n      no_users_ts: nil,\n      oids: %{},\n      check_oid_ref: nil,\n      check_region_interval: nil\n    ]\n\n    @type t :: %__MODULE__{\n            id: String.t(),\n            publication: String.t(),\n            subscribers_pids_table: :ets.tid(),\n            subscribers_nodes_table: :ets.tid(),\n            conn: Postgrex.conn(),\n            oids: map(),\n            check_oid_ref: reference() | nil,\n            delete_queue: %{\n              ref: reference(),\n              queue: :queue.queue()\n            },\n            no_users_ref: reference(),\n            no_users_ts: non_neg_integer() | nil,\n            check_region_interval: non_neg_integer\n          }\n  end\n\n  @spec start_link(GenServer.options()) :: GenServer.on_start()\n  def start_link(opts) do\n    GenServer.start_link(__MODULE__, opts)\n  end\n\n  ## Callbacks\n\n  @impl true\n  def init(args) do\n    %{\"id\" => id} = args\n    Logger.metadata(external_id: id, project: id)\n    {:ok, nil, {:continue, {:connect, args}}}\n  end\n\n  @impl true\n  def handle_continue({:connect, args}, _) do\n    %{\n      \"id\" => id,\n      \"subscribers_pids_table\" => subscribers_pids_table,\n      \"subscribers_nodes_table\" => subscribers_nodes_table\n    } = args\n\n    %Realtime.Api.Tenant{} = tenant = Realtime.Tenants.Cache.get_tenant_by_external_id(id)\n    extension = Realtime.PostgresCdc.filter_settings(\"postgres_cdc_rls\", tenant.extensions)\n    extension = Map.merge(extension, %{\"subs_pool_size\" => Map.get(extension, \"subcriber_pool_size\", 4)})\n\n    subscription_manager_settings = Database.from_settings(extension, \"realtime_subscription_manager\")\n    subscription_manager_pub_settings = Database.from_settings(extension, \"realtime_subscription_manager_pub\")\n\n    with {:ok, conn} <- Database.connect_db(subscription_manager_settings),\n         {:ok, conn_pub} <- Database.connect_db(subscription_manager_pub_settings) do\n      Subscriptions.delete_all_if_table_exists(conn)\n\n      Rls.update_meta(id, self(), conn_pub)\n\n      publication = extension[\"publication\"]\n      oids = Subscriptions.fetch_publication_tables(conn, publication)\n\n      check_region_interval = Map.get(args, :check_region_interval, rebalance_check_interval_in_ms())\n      send_region_check_message(check_region_interval)\n\n      state =\n        %State{\n          id: id,\n          conn: conn,\n          publication: publication,\n          subscribers_pids_table: subscribers_pids_table,\n          subscribers_nodes_table: subscribers_nodes_table,\n          oids: oids,\n          delete_queue: %{\n            ref: check_delete_queue(),\n            queue: :queue.new()\n          },\n          no_users_ref: check_no_users(),\n          check_region_interval: check_region_interval\n        }\n\n      send(self(), :check_oids)\n      {:noreply, state}\n    else\n      {:error, reason} ->\n        log_error(\"SubscriptionManagerConnectionFailed\", reason)\n        {:stop, reason, nil}\n    end\n  end\n\n  @impl true\n  def handle_info({:subscribed, {pid, id}}, state) do\n    case :ets.match(state.subscribers_pids_table, {pid, id, :\"$1\", :_}) do\n      [] -> :ets.insert(state.subscribers_pids_table, {pid, id, Process.monitor(pid), node(pid)})\n      _ -> :ok\n    end\n\n    :ets.insert(state.subscribers_nodes_table, {UUID.string_to_binary!(id), node(pid)})\n\n    {:noreply, %{state | no_users_ts: nil}}\n  end\n\n  def handle_info(\n        :check_oids,\n        %State{check_oid_ref: ref, conn: conn, publication: publication, oids: old_oids} = state\n      ) do\n    Helpers.cancel_timer(ref)\n\n    oids =\n      case Subscriptions.fetch_publication_tables(conn, publication) do\n        ^old_oids ->\n          old_oids\n\n        new_oids ->\n          Logger.warning(\"Found new oids #{inspect(new_oids, pretty: true)}\")\n\n          Subscriptions.delete_all(conn)\n\n          fn {pid, _id, ref, _node}, _acc ->\n            Process.demonitor(ref, [:flush])\n            send(pid, :postgres_subscribe)\n          end\n          |> :ets.foldl([], state.subscribers_pids_table)\n\n          new_oids\n      end\n\n    {:noreply, %{state | oids: oids, check_oid_ref: check_oids()}}\n  end\n\n  def handle_info(\n        {:DOWN, _ref, :process, pid, _reason},\n        %State{\n          subscribers_pids_table: subscribers_pids_table,\n          subscribers_nodes_table: subscribers_nodes_table,\n          delete_queue: %{queue: q}\n        } = state\n      ) do\n    q1 =\n      case :ets.take(subscribers_pids_table, pid) do\n        [] ->\n          q\n\n        values ->\n          for {_pid, id, _ref, _node} <- values, reduce: q do\n            acc ->\n              bin_id = UUID.string_to_binary!(id)\n\n              :ets.delete(subscribers_nodes_table, bin_id)\n\n              :queue.in(bin_id, acc)\n          end\n      end\n\n    {:noreply, put_in(state.delete_queue.queue, q1)}\n  end\n\n  def handle_info(:check_delete_queue, %State{delete_queue: %{ref: ref, queue: q}} = state) do\n    Helpers.cancel_timer(ref)\n\n    q1 =\n      if :queue.is_empty(q) do\n        q\n      else\n        {ids, q1} = Helpers.queue_take(q, @max_delete_records)\n        Logger.debug(\"delete sub id #{inspect(ids)}\")\n\n        case Subscriptions.delete_multi(state.conn, ids) do\n          {:ok, _} ->\n            q1\n\n          {:error, reason} ->\n            log_error(\"SubscriptionDeletionFailed\", reason)\n\n            q\n        end\n      end\n\n    ref = if :queue.is_empty(q1), do: check_delete_queue(), else: check_delete_queue(1_000)\n\n    {:noreply, %{state | delete_queue: %{ref: ref, queue: q1}}}\n  end\n\n  def handle_info(:check_no_users, %{subscribers_pids_table: tid, no_users_ts: ts} = state) do\n    Helpers.cancel_timer(state.no_users_ref)\n\n    ts_new =\n      case {:ets.info(tid, :size), ts != nil && ts + @stop_after < now()} do\n        {0, true} ->\n          Logger.info(\"Stop tenant #{state.id} because of no connected users\")\n          Rls.handle_stop(state.id, 15_000)\n          ts\n\n        {0, false} ->\n          if ts != nil, do: ts, else: now()\n\n        _ ->\n          nil\n      end\n\n    {:noreply, %{state | no_users_ts: ts_new, no_users_ref: check_no_users()}}\n  end\n\n  def handle_info({:check_region, previous_nodes_set}, state) do\n    current_nodes_set = MapSet.new(Node.list())\n\n    case Rebalancer.check(previous_nodes_set, current_nodes_set, state.id) do\n      :ok ->\n        # Let's check again in the future\n        send_region_check_message(state.check_region_interval)\n        {:noreply, state}\n\n      {:error, :wrong_region} ->\n        Logger.warning(\"Rebalancing Postgres Changes replication for a closer region\")\n        Rls.handle_stop(state.id, 15_000)\n        {:noreply, state}\n    end\n  end\n\n  def handle_info(msg, state) do\n    log_error(\"UnhandledProcessMessage\", msg)\n\n    {:noreply, state}\n  end\n\n  ## Internal functions\n\n  defp check_oids, do: Process.send_after(self(), :check_oids, @check_oids_interval)\n\n  defp now, do: System.system_time(:millisecond)\n\n  defp check_no_users, do: Process.send_after(self(), :check_no_users, @check_no_users_interval)\n\n  defp check_delete_queue(timeout \\\\ @timeout),\n    do: Process.send_after(self(), :check_delete_queue, timeout)\n\n  defp send_region_check_message(check_region_interval) do\n    Process.send_after(self(), {:check_region, MapSet.new(Node.list())}, check_region_interval)\n  end\n\n  defp rebalance_check_interval_in_ms(), do: Application.fetch_env!(:realtime, :rebalance_check_interval_in_ms)\nend\n"
  },
  {
    "path": "lib/extensions/postgres_cdc_rls/subscriptions.ex",
    "content": "defmodule Extensions.PostgresCdcRls.Subscriptions do\n  @moduledoc \"\"\"\n  This module consolidates subscriptions handling\n  \"\"\"\n  use Realtime.Logs\n\n  import Postgrex, only: [transaction: 2, query: 3, rollback: 2]\n\n  @type conn() :: Postgrex.conn()\n  @type filter :: {binary, binary, binary}\n  @type subscription_params :: {action_filter :: binary, schema :: binary, table :: binary, [filter]}\n  @type subscription_list :: [%{id: binary, claims: map, subscription_params: subscription_params}]\n\n  @filter_types [\"eq\", \"neq\", \"lt\", \"lte\", \"gt\", \"gte\", \"in\"]\n\n  @spec create(conn(), String.t(), subscription_list, pid(), pid()) ::\n          {:ok, Postgrex.Result.t()}\n          | {:error, Exception.t() | {:exit, term} | {:subscription_insert_failed, String.t()}}\n\n  def create(conn, publication, subscription_list, manager, caller) do\n    transaction(conn, fn conn ->\n      Enum.map(subscription_list, fn %{id: id, claims: claims, subscription_params: params} ->\n        case query(conn, publication, id, claims, params) do\n          {:ok, %{num_rows: num} = result} when num > 0 ->\n            send(manager, {:subscribed, {caller, id}})\n            result\n\n          {:ok, _} ->\n            msg =\n              \"Unable to subscribe to changes with given parameters. Please check Realtime is enabled for the given connect parameters: [#{params_to_log(params)}]\"\n\n            rollback(conn, {:subscription_insert_failed, msg})\n\n          {:error, exception} ->\n            msg =\n              \"Unable to subscribe to changes with given parameters. An exception happened so please check your connect parameters: [#{params_to_log(params)}]. Exception: #{Exception.message(exception)}\"\n\n            rollback(conn, {:subscription_insert_failed, msg})\n        end\n      end)\n    end)\n  rescue\n    e in DBConnection.ConnectionError -> {:error, e}\n  catch\n    :exit, reason -> {:error, {:exit, reason}}\n  end\n\n  defp query(conn, publication, id, claims, subscription_params) do\n    sql = \"with sub_tables as (\n        select\n        rr.entity\n        from\n        pg_publication_tables pub,\n        lateral (\n        select\n        format('%I.%I', pub.schemaname, pub.tablename)::regclass entity\n        ) rr\n        where\n        pub.pubname = $1\n        and pub.schemaname like (case $2 when '*' then '%' else $2 end)\n        and pub.tablename like (case $3 when '*' then '%' else $3 end)\n     )\n     insert into realtime.subscription as x(\n        subscription_id,\n        entity,\n        filters,\n        claims,\n        action_filter\n      )\n      select\n        $4::text::uuid,\n        sub_tables.entity,\n        $6,\n        $5,\n        $7\n      from\n        sub_tables\n        on conflict\n        (subscription_id, entity, filters, action_filter)\n        do update set\n        claims = excluded.claims,\n        created_at = now()\n      returning\n         id\"\n    {action_filter, schema, table, filters} = subscription_params\n    query(conn, sql, [publication, schema, table, id, claims, filters, action_filter])\n  end\n\n  defp params_to_log({action_filter, schema, table, filters}) do\n    [event: action_filter, schema: schema, table: table, filters: filters]\n    |> Enum.map_join(\", \", fn {k, v} -> \"#{k}: #{to_log(v)}\" end)\n  end\n\n  @spec delete(conn(), String.t()) :: {:ok, Postgrex.Result.t()} | {:error, any()}\n  def delete(conn, id) do\n    Logger.debug(\"Delete subscription\")\n    sql = \"delete from realtime.subscription where subscription_id = $1\"\n\n    case query(conn, sql, [id]) do\n      {:error, reason} ->\n        log_error(\"SubscriptionDeletionFailed\", reason)\n        {:error, reason}\n\n      result ->\n        result\n    end\n  catch\n    :exit, reason ->\n      log_error(\"SubscriptionDeletionFailed\", {:exit, reason})\n      {:error, {:exit, reason}}\n  end\n\n  @spec delete_all(conn()) :: :ok\n  def delete_all(conn) do\n    Logger.debug(\"Delete all subscriptions\")\n\n    case query(conn, \"delete from realtime.subscription;\", []) do\n      {:ok, _} -> :ok\n      {:error, reason} -> log_error(\"SubscriptionDeletionFailed\", reason)\n    end\n  catch\n    :exit, reason -> log_error(\"SubscriptionDeletionFailed\", {:exit, reason})\n  end\n\n  @spec delete_multi(conn(), [Ecto.UUID.t()]) ::\n          {:ok, Postgrex.Result.t()} | {:error, Exception.t()}\n  def delete_multi(conn, ids) do\n    Logger.debug(\"Delete multi ids subscriptions\")\n    sql = \"delete from realtime.subscription where subscription_id = ANY($1::uuid[])\"\n    query(conn, sql, [ids])\n  end\n\n  @spec delete_all_if_table_exists(conn()) :: :ok\n  def delete_all_if_table_exists(conn) do\n    case query(\n           conn,\n           \"do $$\n        begin\n          if exists (\n            select 1\n            from pg_tables\n            where schemaname = 'realtime'\n              and tablename  = 'subscription'\n          )\n          then\n            delete from realtime.subscription;\n          end if;\n      end $$\",\n           []\n         ) do\n      {:ok, _} -> :ok\n      {:error, reason} -> log_error(\"SubscriptionCleanupFailed\", reason)\n    end\n  catch\n    :exit, reason -> log_error(\"SubscriptionCleanupFailed\", {:exit, reason})\n  end\n\n  @spec fetch_publication_tables(conn(), String.t()) ::\n          %{\n            {<<_::1>>} => [integer()],\n            {String.t()} => [integer()],\n            {String.t(), String.t()} => [integer()]\n          }\n          | %{}\n  def fetch_publication_tables(conn, publication) do\n    sql = \"select\n    schemaname, tablename, format('%I.%I', schemaname, tablename)::regclass as oid\n    from pg_publication_tables where pubname = $1\"\n\n    case query(conn, sql, [publication]) do\n      {:ok, %{columns: [\"schemaname\", \"tablename\", \"oid\"], rows: rows}} ->\n        Enum.reduce(rows, %{}, fn [schema, table, oid], acc ->\n          if String.contains?(table, \" \") do\n            log_error(\n              \"TableHasSpacesInName\",\n              \"Table name cannot have spaces: \\\"#{schema}\\\".\\\"#{table}\\\"\"\n            )\n          end\n\n          Map.put(acc, {schema, table}, [oid])\n          |> Map.update({schema}, [oid], &[oid | &1])\n          |> Map.update({\"*\"}, [oid], &[oid | &1])\n        end)\n        |> Enum.reduce(%{}, fn {k, v}, acc -> Map.put(acc, k, Enum.sort(v)) end)\n\n      _ ->\n        %{}\n    end\n  end\n\n  @doc \"\"\"\n  Parses subscription filter parameters into something we can pass into our `create_subscription` query.\n\n  We currently support the following filters: 'eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'in'\n\n  ## Examples\n\n      iex> parse_subscription_params(%{\"schema\" => \"public\", \"table\" => \"messages\", \"filter\" => \"subject=eq.hey\"})\n      {:ok, {\"*\", \"public\", \"messages\", [{\"subject\", \"eq\", \"hey\"}]}}\n\n  `in` filter:\n\n      iex> parse_subscription_params(%{\"schema\" => \"public\", \"table\" => \"messages\", \"filter\" => \"subject=in.(hidee,ho)\"})\n      {:ok, {\"*\", \"public\", \"messages\", [{\"subject\", \"in\", \"{hidee,ho}\"}]}}\n\n  no filter:\n\n      iex> parse_subscription_params(%{\"schema\" => \"public\", \"table\" => \"messages\"})\n      {:ok, {\"*\", \"public\", \"messages\", []}}\n\n  only schema:\n\n      iex> parse_subscription_params(%{\"schema\" => \"public\"})\n      {:ok, {\"*\", \"public\", \"*\", []}}\n\n  only table:\n\n      iex> parse_subscription_params(%{\"table\" => \"messages\"})\n      {:ok, {\"*\", \"public\", \"messages\", []}}\n\n  An unsupported filter will respond with an error tuple:\n\n      iex> parse_subscription_params(%{\"schema\" => \"public\", \"table\" => \"messages\", \"filter\" => \"subject=like.hey\"})\n      {:error, ~s(Error parsing `filter` params: [\"like\", \"hey\"])}\n\n  Catch `undefined` filters:\n\n      iex> parse_subscription_params(%{\"schema\" => \"public\", \"table\" => \"messages\", \"filter\" => \"undefined\"})\n      {:error, ~s(Error parsing `filter` params: [\"undefined\"])}\n\n  Catch `missing params`:\n\n      iex> parse_subscription_params(%{})\n      {:error, ~s(No subscription params provided. Please provide at least a `schema` or `table` to subscribe to: %{})}\n\n  \"\"\"\n\n  @spec parse_subscription_params(map()) :: {:ok, subscription_params} | {:error, binary()}\n  def parse_subscription_params(params) do\n    action_filter = action_filter(params)\n\n    case params do\n      %{\"schema\" => schema, \"table\" => table, \"filter\" => filter}\n      when is_binary(schema) and is_binary(table) and is_binary(filter) ->\n        with [col, rest] <- String.split(filter, \"=\", parts: 2),\n             [filter_type, value] when filter_type in @filter_types <-\n               String.split(rest, \".\", parts: 2),\n             {:ok, formatted_value} <- format_filter_value(filter_type, value) do\n          {:ok, {action_filter, schema, table, [{col, filter_type, formatted_value}]}}\n        else\n          {:error, msg} ->\n            {:error, \"Error parsing `filter` params: #{msg}\"}\n\n          e ->\n            {:error, \"Error parsing `filter` params: #{inspect(e)}\"}\n        end\n\n      %{\"schema\" => schema, \"table\" => table}\n      when is_binary(schema) and is_binary(table) and not is_map_key(params, \"filter\") ->\n        {:ok, {action_filter, schema, table, []}}\n\n      %{\"schema\" => schema}\n      when is_binary(schema) and not is_map_key(params, \"table\") and not is_map_key(params, \"filter\") ->\n        {:ok, {action_filter, schema, \"*\", []}}\n\n      %{\"table\" => table}\n      when is_binary(table) and not is_map_key(params, \"schema\") and not is_map_key(params, \"filter\") ->\n        {:ok, {action_filter, \"public\", table, []}}\n\n      map when is_map_key(map, \"user_token\") or is_map_key(map, \"auth_token\") ->\n        {:error,\n         \"No subscription params provided. Please provide at least a `schema` or `table` to subscribe to: <redacted>\"}\n\n      error ->\n        {:error,\n         \"No subscription params provided. Please provide at least a `schema` or `table` to subscribe to: #{inspect(error)}\"}\n    end\n  end\n\n  defp action_filter(%{\"event\" => \"*\"}), do: \"*\"\n\n  defp action_filter(%{\"event\" => event}) when is_binary(event) do\n    case String.upcase(event) do\n      \"INSERT\" -> \"INSERT\"\n      \"UPDATE\" -> \"UPDATE\"\n      \"DELETE\" -> \"DELETE\"\n      _ -> \"*\"\n    end\n  end\n\n  defp action_filter(_), do: \"*\"\n\n  defp format_filter_value(filter, value) do\n    case filter do\n      \"in\" ->\n        case Regex.run(~r/^\\((.*)\\)$/, value) do\n          nil ->\n            {:error, \"`in` filter value must be wrapped by parentheses\"}\n\n          [_, new_value] ->\n            {:ok, \"{#{new_value}}\"}\n        end\n\n      _ ->\n        {:ok, value}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/extensions/postgres_cdc_rls/subscriptions_checker.ex",
    "content": "defmodule Extensions.PostgresCdcRls.SubscriptionsChecker do\n  @moduledoc false\n  use GenServer\n  use Realtime.Logs\n\n  alias Extensions.PostgresCdcRls, as: Rls\n\n  alias Realtime.Database\n  alias Realtime.Helpers\n  alias Realtime.GenRpc\n  alias Realtime.Telemetry\n\n  alias Rls.Subscriptions\n\n  @timeout 120_000\n  @max_delete_records 1000\n\n  defmodule State do\n    @moduledoc false\n    defstruct [:id, :conn, :check_active_pids, :subscribers_pids_table, :subscribers_nodes_table, :delete_queue]\n\n    @type t :: %__MODULE__{\n            id: String.t(),\n            conn: Postgrex.conn(),\n            check_active_pids: reference(),\n            subscribers_pids_table: :ets.tid(),\n            subscribers_nodes_table: :ets.tid(),\n            delete_queue: %{\n              ref: reference(),\n              queue: :queue.queue()\n            }\n          }\n  end\n\n  @spec start_link(GenServer.options()) :: GenServer.on_start()\n  def start_link(opts) do\n    GenServer.start_link(__MODULE__, opts)\n  end\n\n  ## Callbacks\n\n  @impl true\n  def init(args) do\n    %{\"id\" => id} = args\n    Logger.metadata(external_id: id, project: id)\n    {:ok, nil, {:continue, {:connect, args}}}\n  end\n\n  @impl true\n  def handle_continue({:connect, args}, _) do\n    %{\n      \"id\" => id,\n      \"subscribers_pids_table\" => subscribers_pids_table,\n      \"subscribers_nodes_table\" => subscribers_nodes_table\n    } = args\n\n    %Realtime.Api.Tenant{} = tenant = Realtime.Tenants.Cache.get_tenant_by_external_id(id)\n\n    realtime_subscription_checker_settings = Database.from_tenant(tenant, \"realtime_subscription_checker\")\n\n    with {:ok, conn} <- Database.connect_db(realtime_subscription_checker_settings) do\n      state = %State{\n        id: id,\n        conn: conn,\n        check_active_pids: check_active_pids(),\n        subscribers_pids_table: subscribers_pids_table,\n        subscribers_nodes_table: subscribers_nodes_table,\n        delete_queue: %{ref: nil, queue: :queue.new()}\n      }\n\n      {:noreply, state}\n    else\n      {:error, reason} ->\n        log_error(\"SubscriptionsCheckerConnectionFailed\", reason)\n        {:stop, reason, nil}\n    end\n  end\n\n  @impl true\n  def handle_info(:check_active_pids, %State{check_active_pids: ref, delete_queue: delete_queue, id: id} = state) do\n    Helpers.cancel_timer(ref)\n\n    ids =\n      state.subscribers_pids_table\n      |> subscribers_by_node()\n      |> not_alive_pids_dist()\n      |> pop_not_alive_pids(state.subscribers_pids_table, state.subscribers_nodes_table, id)\n\n    new_delete_queue =\n      if length(ids) > 0 do\n        q =\n          Enum.reduce(ids, delete_queue.queue, fn id, acc ->\n            if :queue.member(id, acc), do: acc, else: :queue.in(id, acc)\n          end)\n\n        %{\n          ref: check_delete_queue(),\n          queue: q\n        }\n      else\n        delete_queue\n      end\n\n    {:noreply, %{state | check_active_pids: check_active_pids(), delete_queue: new_delete_queue}}\n  end\n\n  def handle_info(:check_delete_queue, %State{delete_queue: %{ref: ref, queue: q}} = state) do\n    Helpers.cancel_timer(ref)\n\n    new_queue =\n      if :queue.is_empty(q) do\n        q\n      else\n        {ids, q1} = Helpers.queue_take(q, @max_delete_records)\n        Logger.warning(\"Delete #{length(ids)} phantom subscribers from db\")\n\n        case Subscriptions.delete_multi(state.conn, ids) do\n          {:ok, _} ->\n            q1\n\n          {:error, reason} ->\n            log_error(\"UnableToDeletePhantomSubscriptions\", reason)\n\n            q\n        end\n      end\n\n    new_ref = if :queue.is_empty(new_queue), do: ref, else: check_delete_queue()\n\n    {:noreply, %{state | delete_queue: %{ref: new_ref, queue: new_queue}}}\n  end\n\n  ## Internal functions\n\n  @spec pop_not_alive_pids([pid()], :ets.tid(), :ets.tid(), binary()) :: [Ecto.UUID.t()]\n  def pop_not_alive_pids(pids, subscribers_pids_table, subscribers_nodes_table, tenant_id) do\n    Enum.reduce(pids, [], fn pid, acc ->\n      case :ets.lookup(subscribers_pids_table, pid) do\n        [] ->\n          Telemetry.execute(\n            [:realtime, :subscriptions_checker, :pid_not_found],\n            %{quantity: 1},\n            %{tenant_id: tenant_id}\n          )\n\n          acc\n\n        results ->\n          for {^pid, postgres_id, _ref, _node} <- results do\n            Telemetry.execute(\n              [:realtime, :subscriptions_checker, :phantom_pid_detected],\n              %{quantity: 1},\n              %{tenant_id: tenant_id}\n            )\n\n            :ets.delete(subscribers_pids_table, pid)\n            bin_id = UUID.string_to_binary!(postgres_id)\n\n            :ets.delete(subscribers_nodes_table, bin_id)\n            bin_id\n          end ++ acc\n      end\n    end)\n  end\n\n  @spec subscribers_by_node(:ets.tid()) :: %{node() => MapSet.t(pid())}\n  def subscribers_by_node(tid) do\n    fn {pid, _postgres_id, _ref, node}, acc ->\n      set = if Map.has_key?(acc, node), do: MapSet.put(acc[node], pid), else: MapSet.new([pid])\n\n      Map.put(acc, node, set)\n    end\n    |> :ets.foldl(%{}, tid)\n  end\n\n  @spec not_alive_pids_dist(%{node() => MapSet.t(pid())}) :: [pid()] | []\n  def not_alive_pids_dist(pids) do\n    Enum.reduce(pids, [], fn {node, pids}, acc ->\n      if node == node() do\n        acc ++ not_alive_pids(pids)\n      else\n        case GenRpc.call(node, __MODULE__, :not_alive_pids, [pids], timeout: 15_000) do\n          {:error, :rpc_error, _} = error ->\n            log_error(\"UnableToCheckProcessesOnRemoteNode\", error)\n            acc\n\n          pids ->\n            acc ++ pids\n        end\n      end\n    end)\n  end\n\n  @spec not_alive_pids(MapSet.t(pid())) :: [pid()] | []\n  def not_alive_pids(pids) do\n    Enum.reduce(pids, [], fn pid, acc -> if Process.alive?(pid), do: acc, else: [pid | acc] end)\n  end\n\n  defp check_delete_queue, do: Process.send_after(self(), :check_delete_queue, 1000)\n\n  defp check_active_pids, do: Process.send_after(self(), :check_active_pids, @timeout)\nend\n"
  },
  {
    "path": "lib/extensions/postgres_cdc_rls/supervisor.ex",
    "content": "defmodule Extensions.PostgresCdcRls.Supervisor do\n  @moduledoc \"\"\"\n  Supervisor to spin up the Postgres CDC RLS tree.\n  \"\"\"\n  use Supervisor\n\n  alias Extensions.PostgresCdcRls\n\n  @spec start_link :: :ignore | {:error, any} | {:ok, pid}\n  def start_link do\n    Supervisor.start_link(__MODULE__, [], name: __MODULE__)\n  end\n\n  @impl true\n  def init(_args) do\n    load_migrations_modules()\n\n    :syn.add_node_to_scopes(Realtime.Syn.PostgresCdc.scopes())\n\n    children = [\n      {\n        PartitionSupervisor,\n        partitions: 20, child_spec: DynamicSupervisor, strategy: :one_for_one, name: PostgresCdcRls.DynamicSupervisor\n      }\n    ]\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\n\n  defp load_migrations_modules do\n    {:ok, modules} = :application.get_key(:realtime, :modules)\n\n    modules\n    |> Enum.filter(&String.starts_with?(to_string(&1), \"Elixir.Realtime.Tenants.Migrations\"))\n    |> Enum.each(&Code.ensure_loaded!/1)\n  end\nend\n"
  },
  {
    "path": "lib/extensions/postgres_cdc_rls/worker_supervisor.ex",
    "content": "defmodule Extensions.PostgresCdcRls.WorkerSupervisor do\n  @moduledoc false\n  use Supervisor\n\n  alias Extensions.PostgresCdcRls\n  alias PostgresCdcRls.ReplicationPoller\n  alias PostgresCdcRls.SubscriptionManager\n  alias PostgresCdcRls.SubscriptionsChecker\n  alias Realtime.Tenants.Cache\n  alias Realtime.PostgresCdc.Exception\n\n  def start_link(args) do\n    name = PostgresCdcRls.supervisor_id(args[\"id\"], args[\"region\"])\n    Supervisor.start_link(__MODULE__, args, name: {:via, :syn, name})\n  end\n\n  @impl true\n  def init(%{\"id\" => tenant} = args) when is_binary(tenant) do\n    Logger.metadata(external_id: tenant, project: tenant)\n    unless Cache.get_tenant_by_external_id(tenant), do: raise(Exception)\n\n    subscribers_pids_table = :ets.new(__MODULE__, [:public, :bag])\n    subscribers_nodes_table = :ets.new(__MODULE__, [:public, :set])\n\n    tid_args =\n      Map.merge(args, %{\n        \"subscribers_pids_table\" => subscribers_pids_table,\n        \"subscribers_nodes_table\" => subscribers_nodes_table\n      })\n\n    children = [\n      %{\n        id: ReplicationPoller,\n        start: {ReplicationPoller, :start_link, [tid_args]},\n        restart: :transient\n      },\n      %{\n        id: SubscriptionManager,\n        start: {SubscriptionManager, :start_link, [tid_args]},\n        restart: :transient\n      },\n      %{\n        id: SubscriptionsChecker,\n        start: {SubscriptionsChecker, :start_link, [tid_args]},\n        restart: :transient\n      }\n    ]\n\n    Supervisor.init(children, strategy: :rest_for_one, max_restarts: 10, max_seconds: 60)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/adapters/changes.ex",
    "content": "# This file draws heavily from https://github.com/cainophile/cainophile\n# License: https://github.com/cainophile/cainophile/blob/master/LICENSE\n\nrequire Protocol\n\ndefmodule Realtime.Adapters.Changes do\n  @moduledoc \"\"\"\n  This module provides structures of CDC changes.\n  \"\"\"\n  defmodule Transaction do\n    @moduledoc false\n    defstruct [:changes, :commit_timestamp]\n  end\n\n  defmodule NewRecord do\n    @moduledoc false\n    @derive {Jason.Encoder, except: [:subscription_ids]}\n    defstruct [\n      :columns,\n      :commit_timestamp,\n      :errors,\n      :schema,\n      :table,\n      :record,\n      :subscription_ids,\n      :type\n    ]\n  end\n\n  defmodule UpdatedRecord do\n    @moduledoc false\n    @derive {Jason.Encoder, except: [:subscription_ids]}\n    defstruct [\n      :columns,\n      :commit_timestamp,\n      :errors,\n      :schema,\n      :table,\n      :old_record,\n      :record,\n      :subscription_ids,\n      :type\n    ]\n  end\n\n  defmodule DeletedRecord do\n    @moduledoc false\n    @derive {Jason.Encoder, except: [:subscription_ids]}\n    defstruct [\n      :columns,\n      :commit_timestamp,\n      :errors,\n      :schema,\n      :table,\n      :old_record,\n      :subscription_ids,\n      :type\n    ]\n  end\n\n  defmodule TruncatedRelation do\n    @moduledoc false\n    defstruct [:type, :schema, :table, :commit_timestamp]\n  end\nend\n\nProtocol.derive(Jason.Encoder, Realtime.Adapters.Changes.Transaction)\nProtocol.derive(Jason.Encoder, Realtime.Adapters.Changes.TruncatedRelation)\nProtocol.derive(Jason.Encoder, Realtime.Adapters.Postgres.Decoder.Messages.Relation.Column)\n"
  },
  {
    "path": "lib/realtime/adapters/postgres/decoder.ex",
    "content": "# This file draws heavily from https://github.com/cainophile/pgoutput_decoder\n# License: https://github.com/cainophile/pgoutput_decoder/blob/master/LICENSE\n\ndefmodule Realtime.Adapters.Postgres.Decoder do\n  @moduledoc \"\"\"\n  Functions for decoding different types of logical replication messages.\n  \"\"\"\n  defmodule Messages do\n    @moduledoc \"\"\"\n    Different types of logical replication messages from Postgres\n    \"\"\"\n    defmodule Begin do\n      @moduledoc \"\"\"\n      Struct representing the BEGIN message in PostgreSQL's logical decoding output.\n\n      * `final_lsn` - The LSN of the commit that this transaction ended at.\n      * `commit_timestamp` - The timestamp of the commit that this transaction ended at.\n      * `xid` - The transaction ID of this transaction.\n      \"\"\"\n      defstruct [:final_lsn, :commit_timestamp, :xid]\n    end\n\n    defmodule Commit do\n      @moduledoc \"\"\"\n      Struct representing the COMMIT message in PostgreSQL's logical decoding output.\n\n      * `flags` - Bitmask of flags associated with this commit.\n      * `lsn` - The LSN of the commit.\n      * `end_lsn` - The LSN of the next record in the WAL stream.\n      * `commit_timestamp` - The timestamp of the commit.\n      \"\"\"\n      defstruct [:flags, :lsn, :end_lsn, :commit_timestamp]\n    end\n\n    defmodule Origin do\n      @moduledoc \"\"\"\n      Struct representing the ORIGIN message in PostgreSQL's logical decoding output.\n\n      * `origin_commit_lsn` - The LSN of the commit in the database that the change originated from.\n      * `name` - The name of the origin.\n      \"\"\"\n      defstruct [:origin_commit_lsn, :name]\n    end\n\n    defmodule Relation do\n      @moduledoc \"\"\"\n      Struct representing the RELATION message in PostgreSQL's logical decoding output.\n\n      * `id` - The OID of the relation.\n      * `namespace` - The OID of the namespace that the relation belongs to.\n      * `name` - The name of the relation.\n      * `replica_identity` - The replica identity setting of the relation.\n      * `columns` - A list of columns in the relation.\n      \"\"\"\n      defstruct [:id, :namespace, :name, :replica_identity, :columns]\n\n      defmodule Column do\n        @moduledoc \"\"\"\n        Struct representing a column in a relation.\n\n        * `flags` - Bitmask of flags associated with this column.\n        * `name` - The name of the column.\n        * `type` - The OID of the data type of the column.\n        * `type_modifier` - The type modifier of the column.\n        \"\"\"\n        defstruct [:flags, :name, :type, :type_modifier]\n      end\n    end\n\n    defmodule Insert do\n      @moduledoc \"\"\"\n      Struct representing the INSERT message in PostgreSQL's logical decoding output.\n\n      * `relation_id` - The OID of the relation that the tuple was inserted into.\n      * `tuple_data` - The data of the inserted tuple.\n      \"\"\"\n      defstruct [:relation_id, :tuple_data]\n    end\n\n    defmodule Update do\n      @moduledoc \"\"\"\n      Struct representing the UPDATE message in PostgreSQL's logical decoding output.\n\n      * `relation_id` - The OID of the relation that the tuple was updated in.\n      * `changed_key_tuple_data` - The data of the tuple with the old key values.\n      * `old_tuple_data` - The data of the tuple before the update.\n      * `tuple_data` - The data of the tuple after the update.\n      \"\"\"\n      defstruct [:relation_id, :changed_key_tuple_data, :old_tuple_data, :tuple_data]\n    end\n\n    defmodule Delete do\n      @moduledoc \"\"\"\n      Struct representing the DELETE message in PostgreSQL's logical decoding output.\n\n      * `relation_id` - The OID of the relation that the tuple was deleted from.\n      * `changed_key_tuple_data` - The data of the tuple with the old key values.\n      * `old_tuple_data` - The data of the tuple before the delete.\n      \"\"\"\n      defstruct [:relation_id, :changed_key_tuple_data, :old_tuple_data]\n    end\n\n    defmodule Truncate do\n      @moduledoc \"\"\"\n      Struct representing the TRUNCATE message in PostgreSQL's logical decoding output.\n\n      * `number_of_relations` - The number of truncated relations.\n      * `options` - Additional options provided when truncating the relations.\n      * `truncated_relations` - List of relations that have been truncated.\n      \"\"\"\n      defstruct [:number_of_relations, :options, :truncated_relations]\n    end\n\n    defmodule Type do\n      @moduledoc \"\"\"\n      Struct representing the TYPE message in PostgreSQL's logical decoding output.\n\n      * `id` - The OID of the type.\n      * `namespace` - The namespace of the type.\n      * `name` - The name of the type.\n      \"\"\"\n      defstruct [:id, :namespace, :name]\n    end\n\n    defmodule Unsupported do\n      @moduledoc \"\"\"\n      Struct representing an unsupported message in PostgreSQL's logical decoding output.\n\n      * `data` - The raw data of the unsupported message.\n      \"\"\"\n      defstruct [:data]\n    end\n  end\n\n  require Logger\n\n  @pg_epoch DateTime.from_iso8601(\"2000-01-01T00:00:00Z\")\n\n  alias Messages.Begin\n  alias Messages.Commit\n  alias Messages.Origin\n  alias Messages.Relation\n  alias Messages.Relation.Column\n  alias Messages.Insert\n  alias Messages.Type\n  alias Messages.Unsupported\n\n  alias Realtime.Adapters.Postgres.OidDatabase\n\n  @doc \"\"\"\n  Parses logical replication messages from Postgres\n  \"\"\"\n  def decode_message(message, relations) when is_binary(message) do\n    decode_message_impl(message, relations)\n  end\n\n  defp decode_message_impl(<<\"B\", lsn::binary-8, timestamp::integer-64, xid::integer-32>>, _relations) do\n    %Begin{\n      final_lsn: decode_lsn(lsn),\n      commit_timestamp: pgtimestamp_to_timestamp(timestamp),\n      xid: xid\n    }\n  end\n\n  defp decode_message_impl(\n         <<\"C\", _flags::binary-1, lsn::binary-8, end_lsn::binary-8, timestamp::integer-64>>,\n         _relations\n       ) do\n    %Commit{\n      flags: [],\n      lsn: decode_lsn(lsn),\n      end_lsn: decode_lsn(end_lsn),\n      commit_timestamp: pgtimestamp_to_timestamp(timestamp)\n    }\n  end\n\n  # TODO: Verify this is correct with real data from Postgres\n  defp decode_message_impl(<<\"O\", lsn::binary-8, name::binary>>, _relations) do\n    %Origin{\n      origin_commit_lsn: decode_lsn(lsn),\n      name: name\n    }\n  end\n\n  defp decode_message_impl(<<\"R\", id::integer-32, rest::binary>>, _relations) do\n    [\n      namespace\n      | [name | [<<replica_identity::binary-1, _number_of_columns::integer-16, columns::binary>>]]\n    ] = String.split(rest, <<0>>, parts: 3)\n\n    # TODO: Handle case where pg_catalog is blank, we should still return the schema as pg_catalog\n    friendly_replica_identity =\n      case replica_identity do\n        \"d\" -> :default\n        \"n\" -> :nothing\n        \"f\" -> :all_columns\n        \"i\" -> :index\n      end\n\n    %Relation{\n      id: id,\n      namespace: namespace,\n      name: name,\n      replica_identity: friendly_replica_identity,\n      columns: decode_columns(columns)\n    }\n  end\n\n  defp decode_message_impl(\n         <<\"I\", relation_id::integer-32, \"N\", number_of_columns::integer-16, tuple_data::binary>>,\n         relations\n       ) do\n    relation = relations |> get_in([relation_id, :columns])\n\n    if relation do\n      {<<>>, decoded_tuple_data} = decode_tuple_data(tuple_data, number_of_columns, relation)\n\n      %Insert{relation_id: relation_id, tuple_data: decoded_tuple_data}\n    else\n      %Unsupported{}\n    end\n  end\n\n  defp decode_message_impl(<<\"Y\", data_type_id::integer-32, namespace_and_name::binary>>, _relations) do\n    [namespace, name_with_null] = :binary.split(namespace_and_name, <<0>>)\n    name = String.slice(name_with_null, 0..-2//1)\n\n    %Type{\n      id: data_type_id,\n      namespace: namespace,\n      name: name\n    }\n  end\n\n  defp decode_message_impl(binary, _relations), do: %Unsupported{data: binary}\n\n  defp decode_tuple_data(binary, columns_remaining, relations, accumulator \\\\ [])\n\n  defp decode_tuple_data(remaining_binary, 0, _relations, accumulator) when is_binary(remaining_binary),\n    do: {remaining_binary, accumulator |> Enum.reverse() |> List.to_tuple()}\n\n  defp decode_tuple_data(<<\"n\", rest::binary>>, columns_remaining, [_ | relations], accumulator),\n    do: decode_tuple_data(rest, columns_remaining - 1, relations, [nil | accumulator])\n\n  defp decode_tuple_data(<<\"u\", rest::binary>>, columns_remaining, [_ | relations], accumulator),\n    do: decode_tuple_data(rest, columns_remaining - 1, relations, [:unchanged_toast | accumulator])\n\n  @start_date \"2000-01-01T00:00:00Z\"\n  defp decode_tuple_data(\n         <<\"b\", column_length::integer-32, rest::binary>>,\n         columns_remaining,\n         [%Column{type: type} | relations],\n         accumulator\n       ) do\n    data = :erlang.binary_part(rest, {0, column_length})\n    remainder = :erlang.binary_part(rest, {byte_size(rest), -(byte_size(rest) - column_length)})\n\n    data =\n      case type do\n        \"bool\" ->\n          data == <<1>>\n\n        \"jsonb\" ->\n          <<1, rest::binary>> = data\n          rest\n\n        \"timestamp\" ->\n          <<microseconds::signed-big-64>> = data\n\n          @start_date\n          |> NaiveDateTime.from_iso8601!()\n          |> NaiveDateTime.add(microseconds, :microsecond)\n\n        \"text\" ->\n          data\n\n        \"uuid\" ->\n          UUID.binary_to_string!(data)\n      end\n\n    decode_tuple_data(remainder, columns_remaining - 1, relations, [data | accumulator])\n  end\n\n  defp decode_columns(binary, accumulator \\\\ [])\n  defp decode_columns(<<>>, accumulator), do: Enum.reverse(accumulator)\n\n  defp decode_columns(<<flags::integer-8, rest::binary>>, accumulator) do\n    [name | [<<data_type_id::integer-32, type_modifier::integer-32, columns::binary>>]] =\n      String.split(rest, <<0>>, parts: 2)\n\n    decoded_flags =\n      case flags do\n        1 -> [:key]\n        _ -> []\n      end\n\n    decode_columns(columns, [\n      %Column{\n        name: name,\n        flags: decoded_flags,\n        type: OidDatabase.name_for_type_id(data_type_id),\n        type_modifier: type_modifier\n      }\n      | accumulator\n    ])\n  end\n\n  defp pgtimestamp_to_timestamp(microsecond_offset) when is_integer(microsecond_offset) do\n    {:ok, epoch, 0} = @pg_epoch\n\n    DateTime.add(epoch, microsecond_offset, :microsecond)\n  end\n\n  defp decode_lsn(<<xlog_file::integer-32, xlog_offset::integer-32>>),\n    do: {xlog_file, xlog_offset}\nend\n"
  },
  {
    "path": "lib/realtime/adapters/postgres/oid_database.ex",
    "content": "# CREDITS\n# This file draws heavily from https://github.com/cainophile/pgoutput_decoder\n# License: https://github.com/cainophile/pgoutput_decoder/blob/master/LICENSE\n\n# Lifted from epgsql (src/epgsql_binary.erl), this module licensed under\n# 3-clause BSD found here: https://raw.githubusercontent.com/epgsql/epgsql/devel/LICENSE\n\n# https://github.com/brianc/node-pg-types/blob/master/lib/builtins.js\n# MIT License (MIT)\n\n#  Following query was used to generate this file:\n#  SELECT json_object_agg(UPPER(PT.typname), PT.oid::int4 ORDER BY pt.oid)\n#  FROM pg_type PT\n#  WHERE typnamespace = (SELECT pgn.oid FROM pg_namespace pgn WHERE nspname = 'pg_catalog') -- Take only builting Postgres types with stable OID (extension types are not guaranteed to be stable)\n#  AND typtype = 'b' -- Only basic types\n#  AND typisdefined -- Ignore undefined types\n\ndefmodule Realtime.Adapters.Postgres.OidDatabase do\n  @moduledoc \"This module maps a numeric PostgreSQL type ID to a descriptive string.\"\n\n  @doc \"\"\"\n  Maps a numeric PostgreSQL type ID to a descriptive string.\n\n  ## Examples\n\n      iex> name_for_type_id(1700)\n      \"numeric\"\n\n      iex> name_for_type_id(25)\n      \"text\"\n\n      iex> name_for_type_id(3802)\n      \"jsonb\"\n\n  \"\"\"\n  def name_for_type_id(type_id) do\n    case type_id do\n      16 -> \"bool\"\n      17 -> \"bytea\"\n      18 -> \"char\"\n      19 -> \"name\"\n      20 -> \"int8\"\n      21 -> \"int2\"\n      22 -> \"int2vector\"\n      23 -> \"int4\"\n      24 -> \"regproc\"\n      25 -> \"text\"\n      26 -> \"oid\"\n      27 -> \"tid\"\n      28 -> \"xid\"\n      29 -> \"cid\"\n      30 -> \"oidvector\"\n      114 -> \"json\"\n      142 -> \"xml\"\n      143 -> \"_xml\"\n      194 -> \"pg_node_tree\"\n      199 -> \"_json\"\n      210 -> \"smgr\"\n      600 -> \"point\"\n      601 -> \"lseg\"\n      602 -> \"path\"\n      603 -> \"box\"\n      604 -> \"polygon\"\n      628 -> \"line\"\n      629 -> \"_line\"\n      650 -> \"cidr\"\n      651 -> \"_cidr\"\n      700 -> \"float4\"\n      701 -> \"float8\"\n      702 -> \"abstime\"\n      703 -> \"reltime\"\n      704 -> \"tinterval\"\n      718 -> \"circle\"\n      719 -> \"_circle\"\n      774 -> \"macaddr8\"\n      775 -> \"_macaddr8\"\n      790 -> \"money\"\n      791 -> \"_money\"\n      829 -> \"macaddr\"\n      869 -> \"inet\"\n      1000 -> \"_bool\"\n      1001 -> \"_bytea\"\n      1002 -> \"_char\"\n      1003 -> \"_name\"\n      1005 -> \"_int2\"\n      1006 -> \"_int2vector\"\n      1007 -> \"_int4\"\n      1008 -> \"_regproc\"\n      1009 -> \"_text\"\n      1010 -> \"_tid\"\n      1011 -> \"_xid\"\n      1012 -> \"_cid\"\n      1013 -> \"_oidvector\"\n      1014 -> \"_bpchar\"\n      1015 -> \"_varchar\"\n      1016 -> \"_int8\"\n      1017 -> \"_point\"\n      1018 -> \"_lseg\"\n      1019 -> \"_path\"\n      1020 -> \"_box\"\n      1021 -> \"_float4\"\n      1022 -> \"_float8\"\n      1023 -> \"_abstime\"\n      1024 -> \"_reltime\"\n      1025 -> \"_tinterval\"\n      1027 -> \"_polygon\"\n      1028 -> \"_oid\"\n      1033 -> \"aclitem\"\n      1034 -> \"_aclitem\"\n      1040 -> \"_macaddr\"\n      1041 -> \"_inet\"\n      1042 -> \"bpchar\"\n      1043 -> \"varchar\"\n      1082 -> \"date\"\n      1083 -> \"time\"\n      1114 -> \"timestamp\"\n      1115 -> \"_timestamp\"\n      1182 -> \"_date\"\n      1183 -> \"_time\"\n      1184 -> \"timestamptz\"\n      1185 -> \"_timestamptz\"\n      1186 -> \"interval\"\n      1187 -> \"_interval\"\n      1231 -> \"_numeric\"\n      1263 -> \"_cstring\"\n      1266 -> \"timetz\"\n      1270 -> \"_timetz\"\n      1560 -> \"bit\"\n      1561 -> \"_bit\"\n      1562 -> \"varbit\"\n      1563 -> \"_varbit\"\n      1700 -> \"numeric\"\n      1790 -> \"refcursor\"\n      2201 -> \"_refcursor\"\n      2202 -> \"regprocedure\"\n      2203 -> \"regoper\"\n      2204 -> \"regoperator\"\n      2205 -> \"regclass\"\n      2206 -> \"regtype\"\n      2207 -> \"_regprocedure\"\n      2208 -> \"_regoper\"\n      2209 -> \"_regoperator\"\n      2210 -> \"_regclass\"\n      2211 -> \"_regtype\"\n      2949 -> \"_txid_snapshot\"\n      2950 -> \"uuid\"\n      2951 -> \"_uuid\"\n      2970 -> \"txid_snapshot\"\n      3220 -> \"pg_lsn\"\n      3221 -> \"_pg_lsn\"\n      3361 -> \"pg_ndistinct\"\n      3402 -> \"pg_dependencies\"\n      3614 -> \"tsvector\"\n      3615 -> \"tsquery\"\n      3642 -> \"gtsvector\"\n      3643 -> \"_tsvector\"\n      3644 -> \"_gtsvector\"\n      3645 -> \"_tsquery\"\n      3734 -> \"regconfig\"\n      3735 -> \"_regconfig\"\n      3769 -> \"regdictionary\"\n      3770 -> \"_regdictionary\"\n      3802 -> \"jsonb\"\n      3807 -> \"_jsonb\"\n      3905 -> \"_int4range\"\n      3907 -> \"_numrange\"\n      3909 -> \"_tsrange\"\n      3911 -> \"_tstzrange\"\n      3913 -> \"_daterange\"\n      3927 -> \"_int8range\"\n      4089 -> \"regnamespace\"\n      4090 -> \"_regnamespace\"\n      4096 -> \"regrole\"\n      4097 -> \"_regrole\"\n      _ -> type_id\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/adapters/postgres/protocol/keep_alive.ex",
    "content": "defmodule Realtime.Adapters.Postgres.Protocol.KeepAlive do\n  @moduledoc \"\"\"\n  Primary keepalive message (B)\n  Byte1('k')\n  Identifies the message as a sender keepalive.\n\n  Int64\n  The current end of WAL on the server.\n\n  Int64\n  The server's system clock at the time of transmission, as microseconds since midnight on 2000-01-01.\n\n  Byte1\n  1 means that the client should reply to this message as soon as possible, to avoid a timeout disconnect. 0 otherwise.\n\n  The receiving process can send replies back to the sender at any time, using one of the following message formats (also in the payload of a CopyData message):\n  \"\"\"\n  @type t :: %__MODULE__{\n          wal_end: integer(),\n          clock: integer(),\n          reply: :now | :await\n        }\n  defstruct [:wal_end, :clock, :reply]\nend\n"
  },
  {
    "path": "lib/realtime/adapters/postgres/protocol/write.ex",
    "content": "defmodule Realtime.Adapters.Postgres.Protocol.Write do\n  @moduledoc \"\"\"\n  XLogData (B)\n  Byte1('w')\n  Identifies the message as WAL data.\n\n  Int64\n  The starting point of the WAL data in this message.\n\n  Int64\n  The current end of WAL on the server.\n\n  Int64\n  The server's system clock at the time of transmission, as microseconds since midnight on 2000-01-01.\n\n  Byten\n  A section of the WAL data stream.\n\n  A single WAL record is never split across two XLogData messages. When a WAL record crosses a WAL page boundary, and is therefore already split using continuation records, it can be split at the page boundary. In other words, the first main WAL record and its continuation records can be sent in different XLogData messages.\n  \"\"\"\n  defstruct [:server_wal_start, :server_wal_end, :server_system_clock, :message]\nend\n"
  },
  {
    "path": "lib/realtime/adapters/postgres/protocol.ex",
    "content": "defmodule Realtime.Adapters.Postgres.Protocol do\n  @moduledoc \"\"\"\n  This module is responsible for parsing the Postgres WAL messages.\n  \"\"\"\n  alias Realtime.Adapters.Postgres.Protocol.Write\n  alias Realtime.Adapters.Postgres.Protocol.KeepAlive\n\n  defguard is_write(value) when binary_part(value, 0, 1) == <<?w>>\n  defguard is_keep_alive(value) when binary_part(value, 0, 1) == <<?k>>\n\n  def parse(<<?w, server_wal_start::64, server_wal_end::64, server_system_clock::64, message::binary>>) do\n    %Write{\n      server_wal_start: server_wal_start,\n      server_wal_end: server_wal_end,\n      server_system_clock: server_system_clock,\n      message: message\n    }\n  end\n\n  def parse(<<?k, wal_end::64, clock::64, reply::8>>) do\n    reply =\n      case reply do\n        0 -> :later\n        1 -> :now\n      end\n\n    %KeepAlive{wal_end: wal_end, clock: clock, reply: reply}\n  end\n\n  @doc \"\"\"\n  Message to send to the server to request a standby status update.\n\n  Check https://www.postgresql.org/docs/current/protocol-replication.html#PROTOCOL-REPLICATION-STANDBY-STATUS-UPDATE for more information\n  \"\"\"\n  @spec standby_status(integer(), integer(), integer(), :now | :later, integer() | nil) :: [\n          binary()\n        ]\n  def standby_status(last_wal_received, last_wal_flushed, last_wal_applied, reply, clock \\\\ nil)\n\n  def standby_status(last_wal_received, last_wal_flushed, last_wal_applied, reply, nil) do\n    standby_status(last_wal_received, last_wal_flushed, last_wal_applied, reply, current_time())\n  end\n\n  def standby_status(last_wal_received, last_wal_flushed, last_wal_applied, reply, clock) do\n    reply =\n      case reply do\n        :now -> 1\n        :later -> 0\n      end\n\n    [\n      <<?r, last_wal_received::64, last_wal_flushed::64, last_wal_applied::64, clock::64, reply::8>>\n    ]\n  end\n\n  @doc \"\"\"\n  Message to send the server to not do any operation since the server can wait\n  \"\"\"\n  def hold, do: []\n\n  @epoch DateTime.to_unix(~U[2000-01-01 00:00:00Z], :microsecond)\n  def current_time, do: System.os_time(:microsecond) - @epoch\nend\n"
  },
  {
    "path": "lib/realtime/api/extensions.ex",
    "content": "defmodule Realtime.Api.Extensions do\n  @moduledoc \"\"\"\n  Schema for Realtime Extension settings.\n  \"\"\"\n\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  alias Realtime.Crypto\n\n  @primary_key {:id, :binary_id, autogenerate: true}\n  @foreign_key_type :binary_id\n  @derive {Jason.Encoder, only: [:type, :inserted_at, :updated_at, :settings]}\n  schema \"extensions\" do\n    field(:type, :string)\n    field(:settings, :map)\n    belongs_to(:tenant, Realtime.Api.Tenant, foreign_key: :tenant_external_id, type: :string)\n    timestamps()\n  end\n\n  def changeset(extension, attrs) do\n    {attrs1, required_settings} =\n      case attrs[\"type\"] do\n        nil ->\n          {attrs, []}\n\n        type ->\n          %{default: default, required: required} = Realtime.Extensions.db_settings(type)\n\n          {\n            %{attrs | \"settings\" => Map.merge(default, attrs[\"settings\"])},\n            required\n          }\n      end\n\n    extension\n    |> cast(attrs1, [:type, :tenant_external_id, :settings])\n    |> validate_required([:type, :settings])\n    |> unique_constraint([:tenant_external_id, :type])\n    |> validate_required_settings(required_settings)\n    |> encrypt_settings(required_settings)\n  end\n\n  def encrypt_settings(changeset, required) do\n    update_change(changeset, :settings, fn settings ->\n      Enum.reduce(required, settings, fn\n        {field, _, true}, acc ->\n          encrypted = Crypto.encrypt!(settings[field])\n          %{acc | field => encrypted}\n\n        _, acc ->\n          acc\n      end)\n    end)\n  end\n\n  def validate_required_settings(changeset, required) do\n    validate_change(changeset, :settings, fn\n      _, value ->\n        Enum.reduce(required, [], fn {field, checker, _}, acc ->\n          case value[field] do\n            nil ->\n              [{:settings, \"#{field} can't be blank\"} | acc]\n\n            data ->\n              if checker.(data) do\n                acc\n              else\n                [{:settings, \"#{field} is invalid\"} | acc]\n              end\n          end\n        end)\n    end)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/api/message.ex",
    "content": "defmodule Realtime.Api.Message do\n  @moduledoc \"\"\"\n  Defines the Message schema to be used to check RLS authorization policies\n  \"\"\"\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  @primary_key {:id, Ecto.UUID, autogenerate: true}\n  @schema_prefix \"realtime\"\n\n  @type t :: %__MODULE__{}\n  @timestamps_opts [type: :naive_datetime_usec]\n  schema \"messages\" do\n    field(:topic, :string)\n    field(:extension, Ecto.Enum, values: [:broadcast, :presence])\n    field(:payload, :map)\n    field(:event, :string)\n    field(:private, :boolean)\n\n    timestamps()\n  end\n\n  def changeset(message, attrs) do\n    message\n    |> cast(attrs, [\n      :topic,\n      :extension,\n      :payload,\n      :event,\n      :private,\n      :inserted_at,\n      :updated_at\n    ])\n    |> validate_required([:topic, :extension])\n    |> put_timestamp(:updated_at)\n    |> maybe_put_timestamp(:inserted_at)\n  end\n\n  defp put_timestamp(changeset, field) do\n    put_change(changeset, field, NaiveDateTime.utc_now(:microsecond))\n  end\n\n  defp maybe_put_timestamp(changeset, field) do\n    case get_field(changeset, field) do\n      nil -> put_timestamp(changeset, field)\n      _ -> changeset\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/api/tenant.ex",
    "content": "defmodule Realtime.Api.Tenant do\n  @moduledoc \"\"\"\n  Describes a database/tenant which makes use of the realtime service.\n  \"\"\"\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias Realtime.Api.Extensions\n  alias Realtime.Crypto\n\n  @type t :: %__MODULE__{}\n\n  @primary_key {:id, :binary_id, autogenerate: true}\n  @foreign_key_type :binary_id\n  schema \"tenants\" do\n    field(:name, :string)\n    field(:external_id, :string)\n    field(:jwt_secret, :string)\n    field(:jwt_jwks, :map)\n    field(:postgres_cdc_default, :string)\n    field(:max_concurrent_users, :integer)\n    field(:max_events_per_second, :integer)\n    field(:max_presence_events_per_second, :integer, default: 1000)\n    field(:max_payload_size_in_kb, :integer, default: 3000)\n    field(:max_bytes_per_second, :integer)\n    field(:max_channels_per_client, :integer)\n    field(:max_joins_per_second, :integer)\n    field(:suspend, :boolean, default: false)\n    field(:events_per_second_rolling, :float, virtual: true)\n    field(:events_per_second_now, :integer, virtual: true)\n    field(:private_only, :boolean, default: false)\n    field(:migrations_ran, :integer, default: 0)\n    field(:broadcast_adapter, Ecto.Enum, values: [:phoenix, :gen_rpc], default: :gen_rpc)\n    field(:max_client_presence_events_per_window, :integer)\n    field(:client_presence_window_ms, :integer)\n    field(:presence_enabled, :boolean, default: false)\n\n    has_many(:extensions, Realtime.Api.Extensions,\n      foreign_key: :tenant_external_id,\n      references: :external_id,\n      on_delete: :delete_all,\n      on_replace: :delete\n    )\n\n    timestamps()\n  end\n\n  @doc false\n  def changeset(tenant, attrs) do\n    # TODO: remove after infra update\n    extension_key = if attrs[:extensions], do: :extensions, else: \"extensions\"\n\n    attrs =\n      if attrs[extension_key] do\n        ext =\n          Enum.map(attrs[extension_key], fn\n            %{\"type\" => \"postgres\"} = e -> %{e | \"type\" => \"postgres_cdc_rls\"}\n            e -> e\n          end)\n\n        %{attrs | extension_key => ext}\n      else\n        attrs\n      end\n\n    tenant\n    |> cast(attrs, [\n      :name,\n      :external_id,\n      :jwt_secret,\n      :jwt_jwks,\n      :max_concurrent_users,\n      :max_events_per_second,\n      :postgres_cdc_default,\n      :max_bytes_per_second,\n      :max_channels_per_client,\n      :max_joins_per_second,\n      :max_presence_events_per_second,\n      :max_payload_size_in_kb,\n      :suspend,\n      :private_only,\n      :migrations_ran,\n      :broadcast_adapter,\n      :max_client_presence_events_per_window,\n      :client_presence_window_ms,\n      :presence_enabled\n    ])\n    |> validate_required([:external_id])\n    |> check_constraint(:jwt_secret,\n      name: :jwt_secret_or_jwt_jwks_required,\n      message: \"either jwt_secret or jwt_jwks must be provided\"\n    )\n    |> unique_constraint([:external_id])\n    |> encrypt_jwt_secret()\n    |> maybe_set_default(:max_bytes_per_second, :tenant_max_bytes_per_second)\n    |> maybe_set_default(:max_channels_per_client, :tenant_max_channels_per_client)\n    |> maybe_set_default(:max_concurrent_users, :tenant_max_concurrent_users)\n    |> maybe_set_default(:max_events_per_second, :tenant_max_events_per_second)\n    |> maybe_set_default(:max_joins_per_second, :tenant_max_joins_per_second)\n    |> cast_assoc(:extensions, with: &Extensions.changeset/2)\n  end\n\n  def maybe_set_default(changeset, property, config_key) do\n    has_key? = Map.get(changeset.data, property) || Map.get(changeset.changes, property)\n\n    if has_key? do\n      changeset\n    else\n      put_change(changeset, property, Application.fetch_env!(:realtime, config_key))\n    end\n  end\n\n  def encrypt_jwt_secret(%Ecto.Changeset{valid?: true} = changeset),\n    do: update_change(changeset, :jwt_secret, &Crypto.encrypt!/1)\n\n  def encrypt_jwt_secret(changeset), do: changeset\nend\n"
  },
  {
    "path": "lib/realtime/api.ex",
    "content": "defmodule Realtime.Api do\n  @moduledoc \"\"\"\n  The Api context.\n  \"\"\"\n  require Logger\n\n  import Ecto.Query\n\n  alias Ecto.Changeset\n  alias Extensions.PostgresCdcRls\n  alias Realtime.Api.Extensions\n  alias Realtime.Api.Tenant\n  alias Realtime.GenCounter\n  alias Realtime.GenRpc\n  alias Realtime.Nodes\n  alias Realtime.RateCounter\n  alias Realtime.Repo\n  alias Realtime.Repo.Replica\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Cache\n  alias Realtime.Tenants.Connect\n  alias RealtimeWeb.SocketDisconnect\n\n  defguard requires_disconnect(changeset)\n           when changeset.valid? == true and\n                  (is_map_key(changeset.changes, :jwt_secret) or\n                     is_map_key(changeset.changes, :jwt_jwks) or\n                     is_map_key(changeset.changes, :private_only) or\n                     is_map_key(changeset.changes, :suspend))\n\n  defguard requires_restarting_db_connection(changeset)\n           when changeset.valid? == true and\n                  (is_map_key(changeset.changes, :extensions) or\n                     is_map_key(changeset.changes, :jwt_secret) or\n                     is_map_key(changeset.changes, :jwt_jwks) or\n                     is_map_key(changeset.changes, :suspend))\n\n  @doc \"\"\"\n  Returns the list of tenants.\n\n  ## Examples\n\n      iex> list_tenants()\n      [%Tenant{}, ...]\n\n  \"\"\"\n  def list_tenants do\n    repo_replica = Replica.replica()\n\n    Tenant\n    |> repo_replica.all()\n    |> repo_replica.preload(:extensions)\n  end\n\n  @doc \"\"\"\n  Returns list of tenants with filter options:\n  * order_by\n  * search external id\n  * limit\n  * ordering (desc / asc)\n  \"\"\"\n  def list_tenants(opts) when is_list(opts) do\n    repo_replica = Replica.replica()\n\n    field = Keyword.get(opts, :order_by, \"inserted_at\") |> String.to_atom()\n    external_id = Keyword.get(opts, :search)\n    limit = Keyword.get(opts, :limit, 50)\n    order = Keyword.get(opts, :order, \"desc\") |> String.to_atom()\n\n    query =\n      Tenant\n      |> order_by({^order, ^field})\n      |> limit(^limit)\n\n    ilike = \"#{external_id}%\"\n\n    query = if external_id, do: query |> where([t], ilike(t.external_id, ^ilike)), else: query\n\n    query\n    |> repo_replica.all()\n    |> repo_replica.preload(:extensions)\n  end\n\n  @doc \"\"\"\n  Gets a single tenant.\n\n  Raises `Ecto.NoResultsError` if the Tenant does not exist.\n\n  ## Examples\n\n      iex> _by_host!(123) do\n\n      end\n\n      %Tenant{}\n\n      iex> get_tenant!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_tenant!(id), do: Replica.replica().get!(Tenant, id)\n\n  @doc \"\"\"\n  Creates a tenant.\n\n  ## Examples\n\n      iex> create_tenant(%{field: value})\n      {:ok, %Tenant{}}\n\n      iex> create_tenant(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_tenant(attrs) do\n    Logger.debug(\"create_tenant #{inspect(attrs, pretty: true)}\")\n    tenant_id = Map.get(attrs, :external_id) || Map.get(attrs, \"external_id\")\n\n    if master_region?() do\n      %Tenant{}\n      |> Tenant.changeset(attrs)\n      |> Repo.insert()\n      |> case do\n        {:ok, tenant} ->\n          Cache.global_cache_update(tenant)\n          {:ok, tenant}\n\n        error ->\n          error\n      end\n    else\n      call(:create_tenant, [attrs], tenant_id)\n    end\n  end\n\n  @doc \"\"\"\n  Updates a tenant.\n  \"\"\"\n  @spec update_tenant_by_external_id(binary(), map()) :: {:ok, Tenant.t()} | {:error, term()}\n  def update_tenant_by_external_id(tenant_id, attrs) when is_binary(tenant_id) do\n    if master_region?() do\n      tenant_id\n      |> get_tenant_by_external_id(use_replica?: false)\n      |> update_tenant(attrs)\n    else\n      call(:update_tenant_by_external_id, [tenant_id, attrs], tenant_id)\n    end\n  end\n\n  defp update_tenant(%Tenant{} = tenant, attrs) do\n    changeset = Tenant.changeset(tenant, attrs)\n    updated = Repo.update(changeset)\n\n    case updated do\n      {:ok, tenant} ->\n        maybe_update_cache(tenant, changeset)\n        maybe_trigger_disconnect(changeset)\n        maybe_restart_db_connection(changeset)\n        maybe_restart_rate_counters(changeset)\n        Logger.debug(\"Tenant updated: #{inspect(tenant, pretty: true)}\")\n\n      {:error, error} ->\n        Logger.error(\"Failed to update tenant: #{inspect(error, pretty: true)}\")\n    end\n\n    updated\n  end\n\n  @spec delete_tenant_by_external_id(String.t()) :: boolean()\n  def delete_tenant_by_external_id(id) do\n    if master_region?() do\n      query = from(t in Tenant, where: t.external_id == ^id)\n      {num, _} = Repo.delete_all(query)\n      num > 0\n    else\n      call(:delete_tenant_by_external_id, [id], id)\n    end\n  end\n\n  @spec get_tenant_by_external_id(String.t(), Keyword.t()) :: Tenant.t() | nil\n  def get_tenant_by_external_id(external_id, opts \\\\ []) do\n    use_replica? = Keyword.get(opts, :use_replica?, true)\n\n    cond do\n      use_replica? ->\n        Replica.replica().get_by(Tenant, external_id: external_id) |> Replica.replica().preload(:extensions)\n\n      !use_replica? and master_region?() ->\n        Repo.get_by(Tenant, external_id: external_id) |> Repo.preload(:extensions)\n\n      true ->\n        call(:get_tenant_by_external_id, [external_id, opts], external_id)\n    end\n  end\n\n  defp list_extensions(type) do\n    query = from(e in Extensions, where: e.type == ^type, select: e)\n    replica = Replica.replica()\n    replica.all(query)\n  end\n\n  def rename_settings_field(from, to) do\n    if master_region?() do\n      for extension <- list_extensions(\"postgres_cdc_rls\") do\n        {value, settings} = Map.pop(extension.settings, from)\n        new_settings = Map.put(settings, to, value)\n\n        extension\n        |> Changeset.cast(%{settings: new_settings}, [:settings])\n        |> Repo.update()\n      end\n    else\n      call(:rename_settings_field, [from, to], from)\n    end\n  end\n\n  @spec preload_counters(nil | Realtime.Api.Tenant.t(), any()) :: nil | Realtime.Api.Tenant.t()\n  @doc \"\"\"\n  Updates the migrations_ran field for a tenant.\n  \"\"\"\n  @spec update_migrations_ran(binary(), integer()) :: {:ok, Tenant.t()} | {:error, term()}\n  def update_migrations_ran(external_id, count) do\n    if master_region?() do\n      tenant = get_tenant_by_external_id(external_id, use_replica?: false)\n\n      tenant\n      |> Tenant.changeset(%{migrations_ran: count})\n      |> Repo.update()\n      |> tap(fn result ->\n        case result do\n          {:ok, tenant} -> Cache.global_cache_update(tenant)\n          _ -> :ok\n        end\n      end)\n    else\n      call(:update_migrations_ran, [external_id, count], external_id)\n    end\n  end\n\n  def preload_counters(nil), do: nil\n\n  def preload_counters(%Tenant{} = tenant) do\n    rate = Tenants.requests_per_second_rate(tenant)\n\n    preload_counters(tenant, rate)\n  end\n\n  def preload_counters(nil, _rate), do: nil\n\n  def preload_counters(%Tenant{} = tenant, counters_rate) do\n    current = GenCounter.get(counters_rate.id)\n    {:ok, %RateCounter{avg: avg}} = RateCounter.get(counters_rate)\n\n    tenant\n    |> Map.put(:events_per_second_rolling, avg)\n    |> Map.put(:events_per_second_now, current)\n  end\n\n  @field_to_rate_counter_key %{\n    max_events_per_second: [\n      &Tenants.events_per_second_key/1,\n      &Tenants.db_events_per_second_key/1\n    ],\n    max_joins_per_second: [\n      &Tenants.joins_per_second_key/1\n    ],\n    max_presence_events_per_second: [\n      &Tenants.presence_events_per_second_key/1\n    ],\n    extensions: [\n      &Tenants.connect_errors_per_second_key/1,\n      &Tenants.subscription_errors_per_second_key/1,\n      &Tenants.authorization_errors_per_second_key/1\n    ]\n  }\n\n  defp maybe_restart_rate_counters(changeset) do\n    tenant_id = Changeset.fetch_field!(changeset, :external_id)\n\n    Enum.each(@field_to_rate_counter_key, fn {field, key_fns} ->\n      if Changeset.changed?(changeset, field) do\n        Enum.each(key_fns, fn key_fn ->\n          tenant_id\n          |> key_fn.()\n          |> RateCounter.publish_update()\n        end)\n      end\n    end)\n  end\n\n  defp maybe_update_cache(tenant, %Changeset{changes: changes, valid?: true}) when changes != %{} do\n    Tenants.Cache.global_cache_update(tenant)\n  end\n\n  defp maybe_update_cache(_tenant, _changeset), do: :ok\n\n  defp maybe_trigger_disconnect(%Changeset{data: %{external_id: external_id}} = changeset)\n       when requires_disconnect(changeset) do\n    SocketDisconnect.distributed_disconnect(external_id)\n  end\n\n  defp maybe_trigger_disconnect(_changeset), do: nil\n\n  defp maybe_restart_db_connection(%Changeset{data: %{external_id: external_id}} = changeset)\n       when requires_restarting_db_connection(changeset) do\n    Connect.shutdown(external_id)\n\n    try do\n      PostgresCdcRls.handle_stop(external_id, 5_000)\n    catch\n      kind, reason ->\n        Logger.warning(\"Failed to stop CDC processes for tenant #{external_id}: #{inspect(kind)} #{inspect(reason)}\")\n\n        :ok\n    end\n  end\n\n  defp maybe_restart_db_connection(_changeset), do: nil\n\n  defp master_region? do\n    region = Application.get_env(:realtime, :region)\n    master_region = Application.get_env(:realtime, :master_region) || region\n    region == master_region\n  end\n\n  defp call(operation, args, tenant_id) do\n    master_region = Application.get_env(:realtime, :master_region)\n\n    with {:ok, master_node} <- Nodes.node_from_region(master_region, self()),\n         {:ok, result} <- wrapped_call(master_node, operation, args, tenant_id) do\n      result\n    end\n  end\n\n  defp wrapped_call(master_node, operation, args, tenant_id) do\n    case GenRpc.call(master_node, __MODULE__, operation, args, tenant_id: tenant_id) do\n      {:error, :rpc_error, reason} -> {:error, reason}\n      {:error, reason} -> {:error, reason}\n      result -> {:ok, result}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/application.ex",
    "content": "defmodule Realtime.Application do\n  # See https://hexdocs.pm/elixir/Application.html\n  # for more information on OTP Applications\n  @moduledoc false\n\n  use Application\n  require Logger\n\n  alias Realtime.Repo.Replica\n  alias Realtime.Tenants.ReplicationConnection\n  alias Realtime.Tenants.Connect\n  alias Realtime.Tenants.Migrations\n\n  defmodule JwtSecretError, do: defexception([:message])\n  defmodule JwtClaimValidatorsError, do: defexception([:message])\n  defmodule RegionMappingError, do: defexception([:message])\n\n  def start(_type, _args) do\n    opentelemetry_setup()\n    Realtime.LogFilter.setup()\n    primary_config = :logger.get_primary_config()\n\n    # add the region to logs\n    :ok =\n      :logger.set_primary_config(\n        :metadata,\n        Enum.into([region: System.get_env(\"REGION\"), cluster: System.get_env(\"CLUSTER\")], primary_config.metadata)\n      )\n\n    topologies = Application.get_env(:libcluster, :topologies) || []\n\n    case Application.fetch_env!(:realtime, :jwt_claim_validators) |> Jason.decode() do\n      {:ok, claims} when is_map(claims) ->\n        Application.put_env(:realtime, :jwt_claim_validators, claims)\n\n      _ ->\n        raise JwtClaimValidatorsError,\n          message: \"JWT claim validators is not a valid JSON object\"\n    end\n\n    setup_region_mapping()\n\n    :ok =\n      :gen_event.swap_sup_handler(\n        :erl_signal_server,\n        {:erl_signal_handler, []},\n        {Realtime.SignalHandler, %{handler_mod: :erl_signal_handler}}\n      )\n\n    :ets.new(Realtime.Tenants.Connect, [:named_table, :set, :public])\n    :syn.set_event_handler(Realtime.SynHandler)\n    :ok = :syn.add_node_to_scopes([RegionNodes, Realtime.Tenants.Connect])\n\n    region = Application.get_env(:realtime, :region)\n    broadcast_pool_size = Application.get_env(:realtime, :broadcast_pool_size, 10)\n    presence_pool_size = Application.get_env(:realtime, :presence_pool_size, 10)\n    presence_broadcast_period = Application.get_env(:realtime, :presence_broadcast_period, 1_500)\n    presence_permdown_period = Application.get_env(:realtime, :presence_permdown_period, 1_200_000)\n    migration_partition_slots = Application.get_env(:realtime, :migration_partition_slots)\n    connect_partition_slots = Application.get_env(:realtime, :connect_partition_slots)\n    no_channel_timeout_in_ms = Application.get_env(:realtime, :no_channel_timeout_in_ms)\n    master_region = Application.get_env(:realtime, :master_region) || region\n    user_scope_shards = Application.fetch_env!(:realtime, :users_scope_shards)\n    user_scope_broadast_interval_in_ms = Application.get_env(:realtime, :users_scope_broadcast_interval_in_ms, 10_000)\n\n    :syn.join(RegionNodes, region, self(), node: node())\n\n    zta_children =\n      case Application.get_env(:realtime, :dashboard_auth) do\n        :zta -> [{NimbleZTA.Cloudflare, name: Realtime.ZTA, identity_key: System.fetch_env!(\"CF_TEAM_DOMAIN\")}]\n        _ -> []\n      end\n\n    children =\n      [\n        Realtime.ErlSysMon,\n        Realtime.GenCounter,\n        Realtime.PromEx,\n        Realtime.TenantPromEx,\n        {Realtime.Telemetry.Logger, handler_id: \"telemetry-logger\"},\n        RealtimeWeb.Telemetry,\n        {Cluster.Supervisor, [topologies, [name: Realtime.ClusterSupervisor]]},\n        {Phoenix.PubSub,\n         name: Realtime.PubSub, pool_size: 10, adapter: pubsub_adapter(), broadcast_pool_size: broadcast_pool_size},\n        {Beacon,\n         [\n           :users,\n           [\n             partitions: user_scope_shards,\n             broadcast_interval_in_ms: user_scope_broadast_interval_in_ms,\n             message_module: Realtime.BeaconPubSubAdapter\n           ]\n         ]},\n        {Cachex, name: Realtime.RateCounter},\n        Realtime.Tenants.Cache,\n        Realtime.RateCounter.DynamicSupervisor,\n        Realtime.Latency,\n        {Registry, keys: :duplicate, name: Realtime.Registry},\n        {Registry, keys: :unique, name: Realtime.Registry.Unique},\n        {Registry, keys: :unique, name: Realtime.Tenants.Connect.Registry},\n        {Registry, keys: :unique, name: Extensions.PostgresCdcRls.ReplicationPoller.Registry},\n        {Registry,\n         keys: :duplicate, partitions: System.schedulers_online() * 2, name: RealtimeWeb.SocketDisconnect.Registry},\n        {Task.Supervisor, name: Realtime.TaskSupervisor},\n        {Task.Supervisor, name: Realtime.Tenants.Migrations.TaskSupervisor},\n        {PartitionSupervisor,\n         child_spec: {DynamicSupervisor, max_restarts: 0},\n         strategy: :one_for_one,\n         name: Migrations.DynamicSupervisor,\n         partitions: migration_partition_slots},\n        {PartitionSupervisor,\n         child_spec: DynamicSupervisor,\n         strategy: :one_for_one,\n         name: ReplicationConnection.DynamicSupervisor,\n         partitions: connect_partition_slots},\n        {PartitionSupervisor,\n         child_spec: DynamicSupervisor,\n         strategy: :one_for_one,\n         name: Connect.DynamicSupervisor,\n         partitions: connect_partition_slots},\n        {RealtimeWeb.RealtimeChannel.Tracker, check_interval_in_ms: no_channel_timeout_in_ms},\n        RealtimeWeb.Endpoint,\n        {RealtimeWeb.Presence,\n         pool_size: presence_pool_size,\n         broadcast_period: presence_broadcast_period,\n         permdown_period: presence_permdown_period}\n      ] ++ extensions_supervisors() ++ janitor_tasks() ++ metrics_pusher_children() ++ zta_children\n\n    database_connections = if master_region == region, do: [Realtime.Repo], else: [Replica.replica()]\n\n    children = database_connections ++ children\n\n    # See https://hexdocs.pm/elixir/Supervisor.html\n    # for other strategies and supported options\n    opts = [strategy: :one_for_one, name: Realtime.Supervisor]\n    Supervisor.start_link(children, opts)\n  end\n\n  defp extensions_supervisors do\n    Enum.reduce(Application.get_env(:realtime, :extensions), [], fn\n      {_, %{supervisor: name}}, acc ->\n        opts = %{\n          id: name,\n          start: {name, :start_link, []},\n          restart: :transient\n        }\n\n        [opts | acc]\n\n      _, acc ->\n        acc\n    end)\n  end\n\n  defp janitor_tasks do\n    if Application.get_env(:realtime, :run_janitor) do\n      janitor_max_children = Application.get_env(:realtime, :janitor_max_children)\n      janitor_children_timeout = Application.get_env(:realtime, :janitor_children_timeout)\n\n      [\n        {\n          Task.Supervisor,\n          name: Realtime.Tenants.Janitor.TaskSupervisor,\n          max_children: janitor_max_children,\n          max_seconds: janitor_children_timeout,\n          max_restarts: 1\n        },\n        Realtime.Tenants.Janitor,\n        Realtime.MetricsCleaner\n      ]\n    else\n      []\n    end\n  end\n\n  defp metrics_pusher_children do\n    if Application.get_env(:realtime, :metrics_pusher_enabled) do\n      [Realtime.MetricsPusher]\n    else\n      []\n    end\n  end\n\n  defp opentelemetry_setup do\n    :opentelemetry_cowboy.setup()\n    OpentelemetryPhoenix.setup(adapter: :cowboy2)\n    OpentelemetryEcto.setup([:realtime, :repo], db_statement: :enabled)\n  end\n\n  defp pubsub_adapter do\n    if Application.fetch_env!(:realtime, :pubsub_adapter) == :gen_rpc do\n      Realtime.GenRpcPubSub\n    else\n      Phoenix.PubSub.PG2\n    end\n  end\n\n  defp setup_region_mapping do\n    case Application.get_env(:realtime, :region_mapping) do\n      nil ->\n        :ok\n\n      mapping_json when is_binary(mapping_json) ->\n        case Jason.decode(mapping_json) do\n          {:ok, mapping} when is_map(mapping) ->\n            if Enum.all?(mapping, fn {k, v} -> is_binary(k) and is_binary(v) end) do\n              Application.put_env(:realtime, :region_mapping, mapping)\n            else\n              raise RegionMappingError,\n                message: \"REGION_MAPPING must contain only string keys and values\"\n            end\n\n          {:ok, _} ->\n            raise RegionMappingError,\n              message: \"REGION_MAPPING must be a JSON object\"\n\n          {:error, %Jason.DecodeError{} = error} ->\n            raise RegionMappingError,\n              message: \"Failed to parse REGION_MAPPING: #{Exception.message(error)}\"\n        end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/beacon_pub_sub_adapter.ex",
    "content": "defmodule Realtime.BeaconPubSubAdapter do\n  @moduledoc \"Beacon adapter to use PubSub\"\n\n  import Kernel, except: [send: 2]\n\n  @behaviour Beacon.Adapter\n\n  @impl true\n  def register(scope) do\n    :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, topic(scope))\n  end\n\n  @impl true\n  def broadcast(scope, message) do\n    Phoenix.PubSub.broadcast_from(Realtime.PubSub, self(), topic(scope), message)\n  end\n\n  @impl true\n  def broadcast(scope, _nodes, message) do\n    # Notice here that we don't filter by nodes, as PubSub broadcasts to all subscribers\n    # We are broadcasting to everyone because we want to use the fact that Realtime.PubSub uses\n    # regional broadcasting which is more efficient in this multi-region setup\n\n    broadcast(scope, message)\n  end\n\n  @impl true\n  def send(scope, node, message) do\n    Phoenix.PubSub.direct_broadcast(node, Realtime.PubSub, topic(scope), message)\n  end\n\n  defp topic(scope), do: \"beacon:#{scope}\"\nend\n"
  },
  {
    "path": "lib/realtime/crypto.ex",
    "content": "defmodule Realtime.Crypto do\n  @moduledoc \"\"\"\n  Encrypt and decrypt operations required by Realtime. It uses the secret set on Application.get_env(:realtime, :db_enc_key)\n  \"\"\"\n\n  @doc \"\"\"\n  Encrypts the given text\n  \"\"\"\n  @spec encrypt!(binary()) :: binary()\n  def encrypt!(text) do\n    secret_key = Application.get_env(:realtime, :db_enc_key)\n\n    :aes_128_ecb\n    |> :crypto.crypto_one_time(secret_key, pad(text), true)\n    |> Base.encode64()\n  end\n\n  @doc \"\"\"\n  Decrypts the given base64 encoded text\n  \"\"\"\n  @spec decrypt!(binary()) :: binary()\n  def decrypt!(base64_text) do\n    secret_key = Application.get_env(:realtime, :db_enc_key)\n    crypto_text = Base.decode64!(base64_text)\n\n    :aes_128_ecb\n    |> :crypto.crypto_one_time(secret_key, crypto_text, false)\n    |> unpad()\n  end\n\n  defp pad(data) do\n    to_add = 16 - rem(byte_size(data), 16)\n    data <> :binary.copy(<<to_add>>, to_add)\n  end\n\n  defp unpad(data) do\n    to_remove = :binary.last(data)\n    :binary.part(data, 0, byte_size(data) - to_remove)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/database.ex",
    "content": "defmodule Realtime.Database do\n  @moduledoc \"\"\"\n  Handles tenant database operations\n  \"\"\"\n  use Realtime.Logs\n\n  alias Realtime.Api.Tenant\n  alias Realtime.Crypto\n  alias Realtime.PostgresCdc\n  alias Realtime.Rpc\n  alias Realtime.Telemetry\n\n  defstruct [\n    :hostname,\n    :port,\n    :database,\n    :username,\n    :password,\n    :pool_size,\n    :queue_target,\n    :application_name,\n    :max_restarts,\n    :socket_options,\n    ssl: true,\n    backoff_type: :rand_exp\n  ]\n\n  @type t :: %__MODULE__{\n          hostname: binary(),\n          database: binary(),\n          username: binary(),\n          password: binary(),\n          port: non_neg_integer(),\n          pool_size: non_neg_integer(),\n          queue_target: non_neg_integer(),\n          application_name: binary(),\n          max_restarts: non_neg_integer() | nil,\n          ssl: boolean(),\n          socket_options: list(),\n          backoff_type: :stop | :exp | :rand | :rand_exp\n        }\n\n  @cdc \"postgres_cdc_rls\"\n  @doc \"\"\"\n  Creates a database connection struct from the given tenant.\n  \"\"\"\n  @spec from_tenant(Tenant.t(), binary(), :stop | :exp | :rand | :rand_exp) :: t()\n  def from_tenant(%Tenant{} = tenant, application_name, backoff \\\\ :rand_exp) do\n    tenant\n    |> then(&Realtime.PostgresCdc.filter_settings(@cdc, &1.extensions))\n    |> then(&from_settings(&1, application_name, backoff))\n  end\n\n  @doc \"\"\"\n  Creates a database connection struct from the given settings.\n  \"\"\"\n  @spec from_settings(map(), binary(), :stop | :exp | :rand | :rand_exp) :: t()\n  def from_settings(settings, application_name, backoff \\\\ :rand_exp) do\n    pool = pool_size_by_application_name(application_name, settings)\n\n    settings =\n      settings\n      |> Map.take([\"db_host\", \"db_port\", \"db_name\", \"db_user\", \"db_password\"])\n      |> Enum.map(fn {k, v} -> {k, Crypto.decrypt!(v)} end)\n      |> Map.new()\n      |> then(&Map.merge(settings, &1))\n\n    {:ok, addrtype} = detect_ip_version(settings[\"db_host\"])\n    ssl = if default_ssl_param(settings), do: [verify: :verify_none], else: false\n\n    %__MODULE__{\n      hostname: settings[\"db_host\"],\n      port: String.to_integer(settings[\"db_port\"]),\n      database: settings[\"db_name\"],\n      username: settings[\"db_user\"],\n      password: settings[\"db_password\"],\n      pool_size: pool,\n      queue_target: settings[\"db_queue_target\"] || 5_000,\n      application_name: application_name,\n      backoff_type: backoff,\n      socket_options: [addrtype],\n      ssl: ssl\n    }\n  end\n\n  @available_connection_factor 0.95\n\n  @doc \"\"\"\n  Checks if the Tenant CDC extension information is properly configured and that we're able to query against the tenant database.\n  \"\"\"\n\n  @spec check_tenant_connection(Tenant.t() | nil) :: {:error, atom()} | {:ok, pid(), non_neg_integer()}\n  def check_tenant_connection(nil), do: {:error, :tenant_not_found}\n\n  def check_tenant_connection(tenant) do\n    tenant\n    |> then(&PostgresCdc.filter_settings(@cdc, &1.extensions))\n    |> then(fn settings ->\n      required_pool = tenant_pool_requirements(settings)\n      check_settings = from_settings(settings, \"realtime_connect\", :stop)\n      check_settings = Map.put(check_settings, :max_restarts, 0)\n\n      with {:ok, conn} <- connect_db(check_settings),\n           {:ok, [available_connections, migrations_ran]} <- query_connection_info(conn) do\n        requirement = ceil(required_pool * @available_connection_factor)\n\n        if requirement < available_connections do\n          {:ok, conn, migrations_ran}\n        else\n          msg = \"Only #{available_connections} available connections. At least #{requirement} connections are required.\"\n          log_error(\"DatabaseLackOfConnections\", msg)\n          GenServer.stop(conn)\n          {:error, :tenant_db_too_many_connections}\n        end\n      else\n        {:error, e} ->\n          log_error(\"UnableToConnectToTenantDatabase\", e)\n          {:error, e}\n      end\n    end)\n  end\n\n  @migrations_table_exists_query \"\"\"\n  SELECT to_regclass('realtime.schema_migrations') IS NOT NULL\n  \"\"\"\n\n  @migrations_count_query \"\"\"\n  SELECT count(*)::int FROM realtime.schema_migrations\n  \"\"\"\n\n  @connections_query \"\"\"\n  SELECT (current_setting('max_connections')::int - count(*))::int\n  FROM pg_stat_activity\n  WHERE application_name != 'realtime_connect'\n  \"\"\"\n\n  defp query_connection_info(conn) do\n    Postgrex.transaction(conn, fn conn ->\n      %{rows: [[available_connections]]} = Postgrex.query!(conn, @connections_query, [])\n      %{rows: [[table_exists]]} = Postgrex.query!(conn, @migrations_table_exists_query, [])\n\n      %{rows: [[migrations_ran]]} =\n        if table_exists, do: Postgrex.query!(conn, @migrations_count_query, []), else: %{rows: [[0]]}\n\n      [available_connections, migrations_ran]\n    end)\n  rescue\n    e ->\n      GenServer.stop(conn)\n      {:error, e}\n  end\n\n  @doc \"\"\"\n  Connects to the database using the given settings.\n  \"\"\"\n  @spec connect(Tenant.t(), binary(), :stop | :exp | :rand | :rand_exp) ::\n          {:ok, pid()} | {:error, any()}\n  def connect(tenant, application_name, backoff \\\\ :stop) do\n    tenant\n    |> from_tenant(application_name, backoff)\n    |> connect_db()\n  end\n\n  @doc \"\"\"\n  If the param `ssl_enforced` is not set, it defaults to true.\n  \"\"\"\n  @spec default_ssl_param(map) :: boolean\n  def default_ssl_param(%{\"ssl_enforced\" => ssl_enforced}) when is_boolean(ssl_enforced),\n    do: ssl_enforced\n\n  def default_ssl_param(_), do: true\n\n  @doc \"\"\"\n  Runs database transaction in local node or against a target node withing a Postgrex transaction\n  \"\"\"\n  @spec transaction(pid | DBConnection.t(), fun(), keyword(), keyword()) :: {:ok, any()} | {:error, any()}\n  def transaction(db_conn, func, opts \\\\ [], metadata \\\\ [])\n\n  def transaction(%DBConnection{} = db_conn, func, opts, metadata),\n    do: transaction_catched(db_conn, func, opts, metadata)\n\n  def transaction(db_conn, func, opts, metadata) when node() == node(db_conn),\n    do: transaction_catched(db_conn, func, opts, metadata)\n\n  def transaction(db_conn, func, opts, metadata) do\n    metadata = Keyword.put(metadata, :target, node(db_conn))\n    args = [db_conn, func, opts, metadata]\n\n    case Rpc.enhanced_call(node(db_conn), __MODULE__, :transaction, args, metadata) do\n      {:ok, value} -> {:ok, value}\n      {:error, :rpc_error, error} -> {:error, error}\n      {:error, error} -> {:error, error}\n    end\n  end\n\n  defp transaction_catched(db_conn, func, opts, metadata) do\n    telemetry = Keyword.get(opts, :telemetry, nil)\n\n    if telemetry do\n      tenant_id = Keyword.get(opts, :tenant_id, nil)\n      {latency, value} = :timer.tc(Postgrex, :transaction, [db_conn, func, opts], :millisecond)\n      Telemetry.execute(telemetry, %{latency: latency}, %{tenant: tenant_id})\n      value\n    else\n      Postgrex.transaction(db_conn, func, opts)\n    end\n  rescue\n    e ->\n      log_error(\"ErrorExecutingTransaction\", e, metadata)\n      {:error, e}\n  catch\n    :exit, reason ->\n      log_error(\"ErrorExecutingTransaction\", reason, metadata)\n      {:error, {:exit, reason}}\n  end\n\n  @spec connect_db(__MODULE__.t()) :: {:ok, pid()} | {:error, any()}\n  def connect_db(%__MODULE__{} = settings) do\n    %__MODULE__{\n      hostname: hostname,\n      port: port,\n      database: database,\n      username: username,\n      password: password,\n      pool_size: pool_size,\n      queue_target: queue_target,\n      application_name: application_name,\n      backoff_type: backoff_type,\n      max_restarts: max_restarts,\n      socket_options: socket_options,\n      ssl: ssl\n    } = settings\n\n    metadata = Logger.metadata()\n\n    [\n      hostname: hostname,\n      port: port,\n      database: database,\n      username: username,\n      password: password,\n      pool_size: pool_size,\n      queue_target: queue_target,\n      parameters: [application_name: application_name],\n      socket_options: socket_options,\n      backoff_type: backoff_type,\n      ssl: ssl,\n      configure: fn args ->\n        metadata\n        |> Keyword.put(:application_name, application_name)\n        |> Logger.metadata()\n\n        args\n      end\n    ]\n    |> then(fn opts ->\n      if max_restarts, do: Keyword.put(opts, :max_restarts, max_restarts), else: opts\n    end)\n    |> Postgrex.start_link()\n  end\n\n  @doc \"\"\"\n  Returns the pool size for a given application name. Override pool size if provided.\n\n  `realtime_rls` and `realtime_broadcast_changes` will be handled as a special scenario as it will need to be hardcoded as 1 otherwise replication slots will be tried to be reused leading to errors\n  `realtime_migrations` will be handled as a special scenario as it requires 2 connections.\n  \"\"\"\n  @spec pool_size_by_application_name(binary(), map() | nil) :: non_neg_integer()\n  def pool_size_by_application_name(application_name, settings) do\n    case application_name do\n      \"realtime_subscription_manager\" -> 1\n      \"realtime_subscription_manager_pub\" -> settings[\"subs_pool_size\"] || 1\n      \"realtime_subscription_checker\" -> 1\n      \"realtime_connect\" -> settings[\"db_pool\"] || 1\n      \"realtime_health_check\" -> 1\n      \"realtime_janitor\" -> 1\n      \"realtime_migrations\" -> 2\n      \"realtime_broadcast_changes\" -> 1\n      \"realtime_rls\" -> 1\n      \"realtime_replication_slot_teardown\" -> 1\n      _ -> 1\n    end\n  end\n\n  @doc \"\"\"\n  Gets the external id from a host connection string found in the conn.\n  \"\"\"\n  @spec get_external_id(String.t()) :: {:ok, String.t()} | {:error, atom()}\n  def get_external_id(host) when is_binary(host) do\n    case String.split(host, \".\", parts: 2) do\n      [id] -> {:ok, id}\n      [id, _] -> {:ok, id}\n    end\n  end\n\n  @doc \"\"\"\n  Detects the IP version for a given host.\n  \"\"\"\n  @spec detect_ip_version(String.t()) :: {:ok, :inet | :inet6} | {:error, :nxdomain}\n  def detect_ip_version(host) when is_binary(host) do\n    host = String.to_charlist(host)\n\n    if match?({:ok, _}, :inet6_tcp.getaddr(host)) do\n      {:ok, :inet6}\n    else\n      case :inet.gethostbyname(host) do\n        {:ok, hostent} ->\n          [addr | _] = elem(hostent, 5)\n          resolved = addr |> :inet.ntoa() |> to_string()\n          log_warning(\"IpV4Detected\", \"IPv4 detected for host #{inspect(host)} resolved to #{resolved}\")\n\n          {:ok, :inet}\n\n        _ ->\n          {:error, :nxdomain}\n      end\n    end\n  end\n\n  @doc \"\"\"\n  Terminates all replication slots with the name containing 'realtime' in the tenant database.\n  \"\"\"\n  @spec replication_slot_teardown(Tenant.t()) :: :ok\n  def replication_slot_teardown(tenant) do\n    {:ok, conn} = connect(tenant, \"realtime_replication_slot_teardown\")\n\n    query =\n      \"select slot_name from pg_replication_slots where slot_name like '%realtime%'\"\n\n    with {:ok, %{rows: [rows]}} <- Postgrex.query(conn, query, []) do\n      rows\n      |> Enum.reject(&is_nil/1)\n      |> Enum.each(&replication_slot_teardown(conn, &1))\n    end\n\n    GenServer.stop(conn)\n    :ok\n  end\n\n  @doc \"\"\"\n  Terminates replication slot with a given name in the tenant database.\n  \"\"\"\n  @spec replication_slot_teardown(pid() | Tenant.t(), String.t()) :: :ok\n  def replication_slot_teardown(%Tenant{} = tenant, slot_name) do\n    {:ok, conn} = connect(tenant, \"realtime_replication_slot_teardown\")\n    replication_slot_teardown(conn, slot_name)\n    :ok\n  end\n\n  def replication_slot_teardown(conn, slot_name) do\n    Postgrex.query(\n      conn,\n      \"select active_pid, pg_terminate_backend(active_pid), pg_drop_replication_slot(slot_name) from pg_replication_slots where slot_name = $1\",\n      [slot_name]\n    )\n\n    Postgrex.query(conn, \"select pg_drop_replication_slot($1)\", [slot_name])\n    :ok\n  end\n\n  @doc \"\"\"\n  Transforms database settings into keyword list to be used by Postgrex.\n  ## Examples\n\n  iex> Database.opts(%Database{hostname: \"localhost\", port: 5432, database: \"realtime\", username: \"postgres\", password: \"postgres\", application_name: \"test\", backoff_type: :stop, pool_size: 10, queue_target: 10_000, socket_options: [:inet], ssl: true}) |> Enum.sort()\n  [\n    application_name: \"test\",\n    backoff_type: :stop,\n    database: \"realtime\",\n    hostname: \"localhost\",\n    max_restarts: nil,\n    password: \"postgres\",\n    pool_size: 10,\n    port: 5432,\n    queue_target: 10000,\n    socket_options: [:inet],\n    ssl: true,\n    username: \"postgres\"\n  ]\n  \"\"\"\n\n  @spec opts(__MODULE__.t()) :: keyword()\n  def opts(%__MODULE__{} = settings) do\n    settings\n    |> Map.from_struct()\n    |> Map.to_list()\n    |> Keyword.new()\n  end\n\n  defp tenant_pool_requirements(settings) do\n    application_names = [\n      \"realtime_subscription_manager\",\n      \"realtime_subscription_manager_pub\",\n      \"realtime_subscription_checker\",\n      \"realtime_health_check\",\n      \"realtime_janitor\",\n      \"realtime_migrations\",\n      \"realtime_broadcast_changes\",\n      \"realtime_rls\",\n      \"realtime_replication_slot_teardown\",\n      \"realtime_connect\"\n    ]\n\n    Enum.reduce(application_names, 0, fn application_name, acc ->\n      acc + pool_size_by_application_name(application_name, settings)\n    end)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/gen_counter/gen_counter.ex",
    "content": "defmodule Realtime.GenCounter do\n  @moduledoc \"\"\"\n  Process holds an ETS table where each row is a key and a counter\n  \"\"\"\n\n  use GenServer\n\n  @name __MODULE__\n  @table :gen_counter\n\n  @spec start_link(any) :: GenServer.on_start()\n  def start_link(_), do: GenServer.start_link(__MODULE__, :ok, name: @name)\n\n  @spec add(term, integer) :: integer\n  def add(term), do: add(term, 1)\n\n  def add(term, count), do: :ets.update_counter(@table, term, count, {term, 0})\n\n  @spec get(term) :: integer\n  def get(term) do\n    case :ets.lookup(@table, term) do\n      [{^term, value}] -> value\n      [] -> 0\n    end\n  end\n\n  @doc \"Reset counter to 0 and return previous value\"\n  @spec reset(term) :: integer\n  def reset(term) do\n    # We might lose some updates between lookup and the update\n    case :ets.lookup(@table, term) do\n      [{^term, 0}] ->\n        0\n\n      [{^term, previous}] ->\n        :ets.update_element(@table, term, {2, 0}, {term, 0})\n        previous\n\n      [] ->\n        0\n    end\n  end\n\n  @spec delete(term) :: :ok\n  def delete(term) do\n    :ets.delete(@table, term)\n    :ok\n  end\n\n  @impl true\n  def init(_) do\n    table =\n      :ets.new(@table, [\n        :set,\n        :public,\n        :named_table,\n        {:decentralized_counters, true},\n        {:write_concurrency, :auto}\n      ])\n\n    {:ok, table}\n  end\nend\n"
  },
  {
    "path": "lib/realtime/gen_rpc/pub_sub.ex",
    "content": "defmodule Realtime.GenRpcPubSub do\n  @moduledoc \"\"\"\n  gen_rpc Phoenix.PubSub adapter\n  \"\"\"\n\n  @behaviour Phoenix.PubSub.Adapter\n  alias Realtime.GenRpc\n  alias Realtime.GenRpcPubSub.Worker\n  alias Realtime.Nodes\n  use Supervisor\n\n  @impl true\n  def node_name(_), do: node()\n\n  # Supervisor callbacks\n\n  def start_link(opts) do\n    adapter_name = Keyword.fetch!(opts, :adapter_name)\n    name = Keyword.fetch!(opts, :name)\n    pool_size = Keyword.get(opts, :pool_size, 1)\n    broadcast_pool_size = Keyword.get(opts, :broadcast_pool_size, pool_size)\n\n    Supervisor.start_link(__MODULE__, {adapter_name, name, broadcast_pool_size},\n      name: :\"#{name}#{adapter_name}_supervisor\"\n    )\n  end\n\n  @impl true\n  def init({adapter_name, pubsub, pool_size}) do\n    workers = for number <- 1..pool_size, do: :\"#{pubsub}#{adapter_name}_#{number}\"\n\n    :persistent_term.put(adapter_name, List.to_tuple(workers))\n\n    children =\n      for worker <- workers do\n        Supervisor.child_spec({Realtime.GenRpcPubSub.Worker, {pubsub, worker}}, id: worker)\n      end\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\n\n  defp worker_name(adapter_name, key) do\n    workers = :persistent_term.get(adapter_name)\n    elem(workers, :erlang.phash2(key, tuple_size(workers)))\n  end\n\n  @impl true\n  def broadcast(adapter_name, topic, message, dispatcher) do\n    worker = worker_name(adapter_name, self())\n\n    if Application.get_env(:realtime, :regional_broadcasting, false) do\n      my_region = Application.get_env(:realtime, :region)\n      # broadcast to all other nodes in the region\n\n      other_nodes = for node <- Realtime.Nodes.region_nodes(my_region), node != node(), do: node\n      GenRpc.abcast(other_nodes, worker, Worker.forward_to_local(topic, message, dispatcher), key: self())\n\n      # send a message to a node in each region to forward to the rest of the region\n      other_region_nodes = nodes_from_other_regions(my_region, self())\n\n      GenRpc.abcast(other_region_nodes, worker, Worker.forward_to_region(topic, message, dispatcher), key: self())\n    else\n      GenRpc.abcast(Node.list(), worker, Worker.forward_to_local(topic, message, dispatcher), key: self())\n    end\n\n    :ok\n  end\n\n  defp nodes_from_other_regions(my_region, key) do\n    Enum.flat_map(Nodes.all_node_regions(), fn\n      ^my_region ->\n        []\n\n      region ->\n        case Nodes.node_from_region(region, key) do\n          {:ok, node} -> [node]\n          _ -> []\n        end\n    end)\n  end\n\n  @impl true\n  def direct_broadcast(adapter_name, node_name, topic, message, dispatcher) do\n    worker = worker_name(adapter_name, self())\n    GenRpc.abcast([node_name], worker, Worker.forward_to_local(topic, message, dispatcher), key: self())\n  end\nend\n\ndefmodule Realtime.GenRpcPubSub.Worker do\n  @moduledoc false\n  use GenServer\n\n  def forward_to_local(topic, message, dispatcher), do: {:ftl, topic, message, dispatcher}\n  def forward_to_region(topic, message, dispatcher), do: {:ftr, topic, message, dispatcher}\n\n  @doc false\n  def start_link({pubsub, worker}), do: GenServer.start_link(__MODULE__, {pubsub, worker}, name: worker)\n\n  @impl true\n  def init({pubsub, worker}) do\n    Process.flag(:message_queue_data, :off_heap)\n    Process.flag(:fullsweep_after, 20)\n    {:ok, {pubsub, worker}}\n  end\n\n  @impl true\n  # Forward to local\n  def handle_info({:ftl, topic, message, dispatcher}, {pubsub, worker}) do\n    Phoenix.PubSub.local_broadcast(pubsub, topic, message, dispatcher)\n    {:noreply, {pubsub, worker}}\n  end\n\n  # Forward to the rest of the region\n  def handle_info({:ftr, topic, message, dispatcher}, {pubsub, worker}) do\n    # Forward to local first\n    Phoenix.PubSub.local_broadcast(pubsub, topic, message, dispatcher)\n\n    # Then broadcast to the rest of my region\n    my_region = Application.get_env(:realtime, :region)\n    other_nodes = for node <- Realtime.Nodes.region_nodes(my_region), node != node(), do: node\n\n    if other_nodes != [] do\n      Realtime.GenRpc.abcast(other_nodes, worker, forward_to_local(topic, message, dispatcher), [])\n    end\n\n    {:noreply, {pubsub, worker}}\n  end\n\n  @impl true\n  def handle_info(_, pubsub), do: {:noreply, pubsub}\nend\n"
  },
  {
    "path": "lib/realtime/gen_rpc.ex",
    "content": "defmodule Realtime.GenRpc do\n  @moduledoc \"\"\"\n  RPC module for Realtime using :gen_rpc\n\n  :max_gen_rpc_clients is the maximum number of clients (TCP connections) used by gen_rpc\n  between two nodes\n  \"\"\"\n  use Realtime.Logs\n  alias Realtime.Telemetry\n\n  @type result :: any | {:error, :rpc_error, reason :: any}\n\n  @doc \"\"\"\n  Broadcasts the message `msg` asynchronously to the registered process `name` on the specified `nodes`.\n\n  Options:\n\n  - `:key` - Optional key to consistently select the same gen_rpc clients to guarantee message order between nodes\n  \"\"\"\n  @spec abcast([node], atom, any, keyword()) :: :ok\n  def abcast(nodes, name, msg, opts) when is_list(nodes) and is_atom(name) and is_list(opts) do\n    key = Keyword.get(opts, :key, nil)\n    nodes = rpc_nodes(nodes, key)\n\n    :gen_rpc.abcast(nodes, name, msg)\n    :ok\n  end\n\n  @doc \"\"\"\n  Fire and forget apply(mod, func, args) on one node\n\n  Options:\n\n  - `:key` - Optional key to consistently select the same gen_rpc client to guarantee some message order between nodes\n  \"\"\"\n  @spec cast(node, module, atom, list(any), keyword()) :: :ok\n  def cast(node, mod, func, args, opts \\\\ [])\n\n  # Local\n  def cast(node, mod, func, args, _opts) when node == node() do\n    :erpc.cast(node, mod, func, args)\n    :ok\n  end\n\n  def cast(node, mod, func, args, opts)\n      when is_atom(node) and is_atom(mod) and is_atom(func) and is_list(args) and is_list(opts) do\n    key = Keyword.get(opts, :key, nil)\n\n    # Ensure this node is part of the connected nodes\n    if node in Node.list() do\n      node_key = rpc_node(node, key)\n\n      :gen_rpc.cast(node_key, mod, func, args)\n    end\n\n    :ok\n  end\n\n  @doc \"\"\"\n  Fire and forget apply(mod, func, args) on all nodes\n\n  Options:\n\n  - `:key` - Optional key to consistently select the same gen_rpc clients to guarantee message order between nodes\n  \"\"\"\n  @spec multicast(module, atom, list(any), keyword()) :: :ok\n  def multicast(mod, func, args, opts \\\\ []) when is_atom(mod) and is_atom(func) and is_list(args) and is_list(opts) do\n    key = Keyword.get(opts, :key, nil)\n\n    nodes = rpc_nodes(Node.list(), key)\n\n    # Use erpc for the local node because :gen_rpc tries to connect with the local node\n    :ok = :erpc.cast(Node.self(), mod, func, args)\n    :gen_rpc.eval_everywhere(nodes, mod, func, args)\n    :ok\n  end\n\n  @doc \"\"\"\n  Calls node to apply(mod, func, args)\n\n  Options:\n\n  - `:key` - Optional key to consistently select the same gen_rpc clients to guarantee message order between nodes\n  - `:tenant_id` - Tenant ID for logging, defaults to nil\n  - `:timeout` - timeout in milliseconds for the RPC call, defaults to 5000ms\n  \"\"\"\n  @spec call(node, module, atom, list(any), keyword()) :: result\n  def call(node, mod, func, args, opts)\n      when is_atom(node) and is_atom(mod) and is_atom(func) and is_list(args) and is_list(opts) do\n    if node == node() or node in Node.list() do\n      do_call(node, mod, func, args, opts)\n    else\n      tenant_id = Keyword.get(opts, :tenant_id)\n\n      log_error(\n        \"ErrorOnRpcCall\",\n        %{target: node, mod: mod, func: func, error: :badnode},\n        project: tenant_id,\n        external_id: tenant_id\n      )\n\n      {:error, :rpc_error, :badnode}\n    end\n  end\n\n  defp do_call(node, mod, func, args, opts) do\n    timeout = Keyword.get(opts, :timeout, default_rpc_timeout())\n    tenant_id = Keyword.get(opts, :tenant_id)\n    key = Keyword.get(opts, :key, nil)\n\n    node_key = rpc_node(node, key)\n    {latency, response} = :timer.tc(fn -> :gen_rpc.call(node_key, mod, func, args, timeout) end)\n\n    case response do\n      {:badrpc, reason} ->\n        reason = unwrap_reason(reason)\n\n        log_error(\n          \"ErrorOnRpcCall\",\n          %{target: node, mod: mod, func: func, error: reason},\n          project: tenant_id,\n          external_id: tenant_id\n        )\n\n        telemetry_failure(node, latency)\n\n        {:error, :rpc_error, reason}\n\n      {:error, _} ->\n        telemetry_failure(node, latency)\n        response\n\n      _ ->\n        telemetry_success(node, latency)\n        response\n    end\n  end\n\n  # Not using :gen_rpc.multicall here because we can't see the actual results on errors\n  @doc \"\"\"\n  Evaluates apply(mod, func, args) on all nodes\n\n  Options:\n\n  - `:timeout` - timeout for the RPC call, defaults to 5000ms\n  - `:tenant_id` - tenant ID for telemetry and logging, defaults to nil\n  - `:key` - Optional key to consistently select the same gen_rpc clients to guarantee message order between nodes\n  \"\"\"\n  @spec multicall(module, atom, list(any), keyword()) :: [{node, result}]\n  def multicall(mod, func, args, opts \\\\ []) when is_atom(mod) and is_atom(func) and is_list(args) and is_list(opts) do\n    timeout = Keyword.get(opts, :timeout, default_rpc_timeout())\n    tenant_id = Keyword.get(opts, :tenant_id)\n    key = Keyword.get(opts, :key, nil)\n\n    nodes = rpc_nodes([node() | Node.list()], key)\n    # Latency here is the amount of time that it takes for this node to gather the result.\n    # If one node takes a while to reply the remaining calls will have at least the latency reported by this node\n    # Example:\n    # Node A, B and C receive the calls in this order\n    # Node A takes 500ms to return on nb_yield\n    # Node B and C will report at least 500ms to return regardless how long it took for them to actually reply back\n    results =\n      nodes\n      |> Enum.map(&{&1, :erlang.monotonic_time(), async_call(&1, mod, func, args)})\n      |> Enum.map(fn {{node, _key}, start_time, ref} ->\n        result =\n          case nb_yield(node, ref, timeout) do\n            :timeout -> {:error, :rpc_error, :timeout}\n            {:value, {:badrpc, reason}} -> {:error, :rpc_error, unwrap_reason(reason)}\n            {:value, result} -> result\n          end\n\n        end_time = :erlang.monotonic_time()\n        latency = :erlang.convert_time_unit(end_time - start_time, :native, :microsecond)\n        {node, latency, result}\n      end)\n\n    Enum.map(results, fn\n      {node, latency, {:error, :rpc_error, reason} = result} ->\n        log_error(\n          \"ErrorOnRpcCall\",\n          %{target: node, mod: mod, func: func, error: reason},\n          project: tenant_id,\n          external_id: tenant_id\n        )\n\n        telemetry_failure(node, latency)\n        {node, result}\n\n      {node, latency, {:ok, _} = result} ->\n        telemetry_success(node, latency)\n        {node, result}\n\n      {node, latency, result} ->\n        telemetry_failure(node, latency)\n        {node, result}\n    end)\n  end\n\n  defp telemetry_success(node, latency) do\n    Telemetry.execute(\n      [:realtime, :rpc],\n      %{latency: latency},\n      %{origin_node: node(), target_node: node, success: true, mechanism: :gen_rpc}\n    )\n  end\n\n  defp telemetry_failure(node, latency) do\n    Telemetry.execute(\n      [:realtime, :rpc],\n      %{latency: latency},\n      %{origin_node: node(), target_node: node, success: false, mechanism: :gen_rpc}\n    )\n  end\n\n  # Max amount of clients (TCP connections) used by gen_rpc\n  defp max_clients(), do: Application.fetch_env!(:realtime, :max_gen_rpc_clients)\n\n  defp rpc_nodes(nodes, key), do: Enum.map(nodes, &rpc_node(&1, key))\n\n  # Tag the node with a random number from 1 to max_clients\n  # This ensures that we don't use the same client/tcp connection for this node\n  defp rpc_node(node, nil), do: {node, :rand.uniform(max_clients())}\n\n  # Tag the node with a random number from 1 to max_clients\n  # Using phash2 to ensure the same key and the same client per node\n  defp rpc_node(node, key), do: {node, :erlang.phash2(key, max_clients()) + 1}\n\n  defp unwrap_reason({:unknown_error, {{:badrpc, reason}, _}}), do: reason\n  defp unwrap_reason(reason), do: reason\n\n  defp default_rpc_timeout, do: Application.get_env(:realtime, :rpc_timeout, 5_000)\n\n  # Here we run the async_call on all nodes using gen_rpc except the local node\n  # This is because gen_rpc does not have a bypass for local node on multicall\n  # For the local node we use rpc instead\n  defp async_call({node, _}, mod, func, args) when node == node(), do: :rpc.async_call(node, mod, func, args)\n  defp async_call(node, mod, func, args), do: :gen_rpc.async_call(node, mod, func, args)\n\n  defp nb_yield(node, ref, timeout) when node == node(), do: :rpc.nb_yield(ref, timeout)\n  defp nb_yield(_node, ref, timeout), do: :gen_rpc.nb_yield(ref, timeout)\nend\n"
  },
  {
    "path": "lib/realtime/helpers.ex",
    "content": "defmodule Realtime.Helpers do\n  @moduledoc \"\"\"\n  This module includes helper functions for different contexts that can't be union in one module.\n  \"\"\"\n  require Logger\n\n  @spec cancel_timer(reference() | nil) :: non_neg_integer() | false | :ok | nil\n  def cancel_timer(nil), do: nil\n  def cancel_timer(ref), do: Process.cancel_timer(ref)\n\n  @doc \"\"\"\n  Takes the first N items from the queue and returns the list of items and the new queue.\n\n  ## Examples\n\n      iex> q = :queue.new()\n      iex> q = :queue.in(1, q)\n      iex> q = :queue.in(2, q)\n      iex> q = :queue.in(3, q)\n      iex> Realtime.Helpers.queue_take(q, 2)\n      {[2, 1], {[], [3]}}\n  \"\"\"\n\n  @spec queue_take(:queue.queue(), non_neg_integer()) :: {list(), :queue.queue()}\n  def queue_take(q, count) do\n    Enum.reduce_while(1..count, {[], q}, fn _, {items, queue} ->\n      case :queue.out(queue) do\n        {{:value, item}, new_q} ->\n          {:cont, {[item | items], new_q}}\n\n        {:empty, new_q} ->\n          {:halt, {items, new_q}}\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/log_filter.ex",
    "content": "defmodule Realtime.LogFilter do\n  @moduledoc \"\"\"\n  Primary logger filter that suppresses noisy errors from dependencies.\n  \"\"\"\n\n  @filter_id :connection_noise\n\n  @doc \"\"\"\n  Installs the primary filter into the Erlang logger. Safe to call multiple times.\n  \"\"\"\n  def setup do\n    case :logger.add_primary_filter(@filter_id, {&filter/2, []}) do\n      :ok -> :ok\n      {:error, {:already_exist, @filter_id}} -> :ok\n    end\n  end\n\n  @doc \"\"\"\n  Filter function passed to `:logger.add_primary_filter/2`.\n\n  Returns `:stop` to suppress the event or the original event map to allow it through.\n  \"\"\"\n  def filter(\n        %{msg: {:report, %{label: {:gen_statem, :terminate}, reason: {_, %DBConnection.ConnectionError{}, _}}}},\n        _\n      ),\n      do: :stop\n\n  def filter(%{meta: %{mfa: {DBConnection.Connection, _, _}}}, _), do: :stop\n\n  @ranch_format \"Ranch listener ~p had connection process started with ~p:start_link/3 at ~p exit with reason: ~0p~n\"\n  def filter(%{msg: {:format, @ranch_format, [_, _, _, :killed]}}, _), do: :stop\n\n  def filter(event, _), do: event\nend\n"
  },
  {
    "path": "lib/realtime/logs.ex",
    "content": "defmodule Realtime.Logs do\n  @moduledoc \"\"\"\n  Logging operations for Realtime\n  \"\"\"\n  require Logger\n\n  defmacro __using__(_opts) do\n    quote do\n      require Logger\n\n      import Realtime.Logs\n    end\n  end\n\n  @doc \"\"\"\n  Prepares a value to be logged\n  \"\"\"\n  def to_log(value) when is_binary(value), do: value\n  def to_log(value), do: inspect(value, pretty: true)\n\n  defmacro log_error(code, error, metadata \\\\ []) do\n    quote bind_quoted: [code: code, error: error, metadata: metadata], location: :keep do\n      Logger.error(\"#{code}: #{Realtime.Logs.to_log(error)}\", [error_code: code] ++ metadata)\n    end\n  end\n\n  defmacro log_warning(code, warning, metadata \\\\ []) do\n    quote bind_quoted: [code: code, warning: warning, metadata: metadata], location: :keep do\n      Logger.warning(\"#{code}: #{Realtime.Logs.to_log(warning)}\", [{:error_code, code} | metadata])\n    end\n  end\nend\n\ndefimpl Jason.Encoder, for: DBConnection.ConnectionError do\n  def encode(\n        %DBConnection.ConnectionError{message: message, reason: reason, severity: severity},\n        _opts\n      ) do\n    inspect(%{message: message, reason: reason, severity: severity}, pretty: true)\n  end\nend\n\ndefimpl Jason.Encoder, for: Postgrex.Error do\n  def encode(\n        %Postgrex.Error{\n          message: message,\n          postgres: %{code: code, schema: schema, table: table}\n        },\n        _opts\n      ) do\n    inspect(%{message: message, schema: schema, table: table, code: code}, pretty: true)\n  end\nend\n\ndefimpl Jason.Encoder, for: Tuple do\n  require Logger\n\n  def encode(tuple, _opts) do\n    Logger.error(\"UnableToEncodeJson: Tuple encoding not supported: #{inspect(tuple)}\")\n    inspect(%{error: \"unable to parse response\"}, pretty: true)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/messages.ex",
    "content": "defmodule Realtime.Messages do\n  @moduledoc \"\"\"\n  Handles `realtime.messages` table operations\n  \"\"\"\n\n  alias Realtime.Api.Message\n\n  import Ecto.Query, only: [from: 2]\n\n  @hard_limit 25\n  @default_timeout 5_000\n\n  @doc \"\"\"\n  Fetch last `limit ` messages for a given `topic` inserted after `since`\n\n  Automatically uses RPC if the database connection is not in the same node\n\n  Only allowed for private channels\n  \"\"\"\n  @spec replay(pid, String.t(), String.t(), non_neg_integer, non_neg_integer) ::\n          {:ok, Message.t(), [String.t()]} | {:error, term} | {:error, :rpc_error, term}\n  def replay(conn, tenant_id, topic, since, limit)\n      when node(conn) == node() and is_integer(since) and is_integer(limit) do\n    limit = max(min(limit, @hard_limit), 1)\n\n    with {:ok, since} <- DateTime.from_unix(since, :millisecond),\n         {:ok, messages} <- messages(conn, tenant_id, topic, since, limit) do\n      {:ok, Enum.reverse(messages), MapSet.new(messages, & &1.id)}\n    else\n      {:error, :postgrex_exception} -> {:error, :failed_to_replay_messages}\n      {:error, :invalid_unix_time} -> {:error, :invalid_replay_params}\n      error -> error\n    end\n  end\n\n  def replay(conn, tenant_id, topic, since, limit) when is_integer(since) and is_integer(limit) do\n    Realtime.GenRpc.call(node(conn), __MODULE__, :replay, [conn, tenant_id, topic, since, limit],\n      key: topic,\n      tenant_id: tenant_id\n    )\n  end\n\n  def replay(_, _, _, _, _), do: {:error, :invalid_replay_params}\n\n  defp messages(conn, tenant_id, topic, since, limit) do\n    since = DateTime.to_naive(since)\n    # We want to avoid searching partitions in the future as they should be empty\n    # so we limit to 1 minute in the future to account for any potential drift\n    now = NaiveDateTime.utc_now() |> NaiveDateTime.add(1, :minute)\n\n    query =\n      from m in Message,\n        where:\n          m.topic == ^topic and\n            m.private == true and\n            m.extension == :broadcast and\n            m.inserted_at >= ^since and\n            m.inserted_at < ^now,\n        limit: ^limit,\n        order_by: [desc: m.inserted_at]\n\n    {latency, value} =\n      :timer.tc(Realtime.Tenants.Repo, :all, [conn, query, Message, [timeout: @default_timeout]], :millisecond)\n\n    :telemetry.execute([:realtime, :tenants, :replay], %{latency: latency}, %{tenant: tenant_id})\n    value\n  end\n\n  @doc \"\"\"\n  Deletes messages older than 72 hours for a given tenant connection\n  \"\"\"\n  @spec delete_old_messages(pid()) :: :ok\n  def delete_old_messages(conn) do\n    limit =\n      NaiveDateTime.utc_now()\n      |> NaiveDateTime.add(-72, :hour)\n      |> NaiveDateTime.to_date()\n\n    %{rows: rows} =\n      Postgrex.query!(\n        conn,\n        \"\"\"\n        SELECT child.relname\n        FROM pg_inherits\n        JOIN pg_class parent ON pg_inherits.inhparent = parent.oid\n        JOIN pg_class child ON pg_inherits.inhrelid = child.oid\n        JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace\n        JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace\n        WHERE parent.relname = 'messages'\n        AND nmsp_child.nspname = 'realtime'\n        \"\"\",\n        []\n      )\n\n    rows\n    |> Enum.filter(fn [\"messages_\" <> date] ->\n      date |> String.replace(\"_\", \"-\") |> Date.from_iso8601!() |> Date.compare(limit) == :lt\n    end)\n    |> Enum.each(&Postgrex.query!(conn, \"DROP TABLE IF EXISTS realtime.#{&1}\", []))\n\n    :ok\n  end\nend\n"
  },
  {
    "path": "lib/realtime/metrics_cleaner.ex",
    "content": "defmodule Realtime.MetricsCleaner do\n  @moduledoc false\n\n  use GenServer\n  require Logger\n\n  defstruct [:check_ref, :interval]\n\n  def handle_beacon_event([:beacon, :users, :group, :vacant], _, %{group: tenant_id}, vacant_websockets) do\n    :ets.insert(vacant_websockets, {tenant_id, DateTime.to_unix(DateTime.utc_now(), :second)})\n  end\n\n  def handle_beacon_event([:beacon, :users, :group, :occupied], _, %{group: tenant_id}, vacant_websockets) do\n    :ets.delete(vacant_websockets, tenant_id)\n  end\n\n  def handle_syn_event([:syn, Realtime.Tenants.Connect, :unregistered], _, %{name: tenant_id}, disconnected_tenants) do\n    :ets.insert(disconnected_tenants, {tenant_id, DateTime.to_unix(DateTime.utc_now(), :second)})\n  end\n\n  def handle_syn_event([:syn, Realtime.Tenants.Connect, :registered], _, %{name: tenant_id}, disconnected_tenants) do\n    :ets.delete(disconnected_tenants, tenant_id)\n  end\n\n  def start_link(opts), do: GenServer.start_link(__MODULE__, opts)\n\n  # 10 minutes\n  @default_vacant_metric_threshold_in_seconds 600\n\n  @impl true\n  def init(opts) do\n    interval =\n      opts[:metrics_cleaner_schedule_timer_in_ms] ||\n        Application.fetch_env!(:realtime, :metrics_cleaner_schedule_timer_in_ms)\n\n    vacant_metric_threshold_in_seconds =\n      opts[:vacant_metric_threshold_in_seconds] || @default_vacant_metric_threshold_in_seconds\n\n    Logger.info(\"Starting MetricsCleaner\")\n\n    vacant_websockets = :ets.new(:vacant_websockets, [:set, :public, read_concurrency: false, write_concurrency: :auto])\n\n    disconnected_tenants =\n      :ets.new(:disconnected_tenants, [:set, :public, read_concurrency: false, write_concurrency: :auto])\n\n    :ok =\n      :telemetry.attach_many(\n        [self(), :vacant_websockets],\n        [[:beacon, :users, :group, :occupied], [:beacon, :users, :group, :vacant]],\n        &__MODULE__.handle_beacon_event/4,\n        vacant_websockets\n      )\n\n    :ok =\n      :telemetry.attach_many(\n        [self(), :disconnected_tenants],\n        [[:syn, Realtime.Tenants.Connect, :registered], [:syn, Realtime.Tenants.Connect, :unregistered]],\n        &__MODULE__.handle_syn_event/4,\n        disconnected_tenants\n      )\n\n    {:ok,\n     %{\n       check_ref: check(interval),\n       interval: interval,\n       vacant_metric_threshold_in_seconds: vacant_metric_threshold_in_seconds,\n       vacant_websockets: vacant_websockets,\n       disconnected_tenants: disconnected_tenants\n     }}\n  end\n\n  @impl true\n  def terminate(_reason, _state) do\n    :telemetry.detach([self(), :vacant_websockets])\n    :telemetry.detach([self(), :disconnected_tenants])\n    :ok\n  end\n\n  @impl true\n  def handle_info(:check, %{interval: interval} = state) do\n    Process.cancel_timer(state.check_ref)\n\n    {exec_time, _} =\n      :timer.tc(\n        fn ->\n          loop_and_cleanup_metrics_table(state.vacant_websockets, state.vacant_metric_threshold_in_seconds)\n          loop_and_cleanup_metrics_table(state.disconnected_tenants, state.vacant_metric_threshold_in_seconds)\n        end,\n        :millisecond\n      )\n\n    if exec_time > :timer.seconds(5),\n      do: Logger.warning(\"Metrics check took: #{exec_time} ms\")\n\n    {:noreply, %{state | check_ref: check(interval)}}\n  end\n\n  def handle_info(msg, state) do\n    Logger.error(\"Unexpected message: #{inspect(msg)}\")\n    {:noreply, state}\n  end\n\n  defp check(interval), do: Process.send_after(self(), :check, interval)\n\n  defp loop_and_cleanup_metrics_table(cleaner_table, vacant_metric_cleanup_threshold_in_seconds) do\n    threshold =\n      DateTime.utc_now()\n      |> DateTime.add(-vacant_metric_cleanup_threshold_in_seconds, :second)\n      |> DateTime.to_unix(:second)\n\n    # We do this to have a consistent view of the table while we read and delete\n    :ets.safe_fixtable(cleaner_table, true)\n\n    try do\n      # Look for tenant_ids that have been vacant for more than threshold\n      vacant_tenant_ids =\n        :ets.select(cleaner_table, [\n          {{:\"$1\", :\"$2\"}, [{:<, :\"$2\", threshold}], [:\"$1\"]}\n        ])\n\n      vacant_tenant_ids\n      |> Enum.map(fn tenant_id -> %{tenant: tenant_id} end)\n      |> then(&Peep.prune_tags(Realtime.TenantPromEx.Metrics, &1))\n\n      # Delete them from the table\n      :ets.select_delete(cleaner_table, [\n        {{:\"$1\", :\"$2\"}, [{:<, :\"$2\", threshold}], [true]}\n      ])\n    after\n      :ets.safe_fixtable(cleaner_table, false)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/metrics_pusher.ex",
    "content": "defmodule Realtime.MetricsPusher do\n  @moduledoc \"\"\"\n  GenServer that periodically pushes Prometheus metrics to an endpoint.\n\n  Only starts if `url` is configured.\n  Pushes metrics every 30 seconds (configurable) to the configured URL endpoint.\n  \"\"\"\n\n  use GenServer\n  require Logger\n\n  defstruct [:push_ref, :interval, :req_options, :auth]\n\n  @spec start_link(keyword()) :: {:ok, pid()} | :ignore\n  def start_link(opts) do\n    url = opts[:url] || Application.get_env(:realtime, :metrics_pusher_url)\n\n    if is_binary(url) do\n      GenServer.start_link(__MODULE__, opts, name: __MODULE__)\n    else\n      Logger.warning(\"MetricsPusher not started: url must be configured\")\n\n      :ignore\n    end\n  end\n\n  @impl true\n  def init(opts) do\n    url = opts[:url] || Application.get_env(:realtime, :metrics_pusher_url)\n    user = opts[:user] || Application.get_env(:realtime, :metrics_pusher_user, \"realtime\")\n    auth = opts[:auth] || Application.get_env(:realtime, :metrics_pusher_auth)\n\n    interval =\n      Keyword.get(\n        opts,\n        :interval,\n        Application.get_env(:realtime, :metrics_pusher_interval_ms, :timer.seconds(30))\n      )\n\n    timeout =\n      Keyword.get(\n        opts,\n        :timeout,\n        Application.get_env(:realtime, :metrics_pusher_timeout_ms, :timer.seconds(15))\n      )\n\n    compress =\n      Keyword.get(\n        opts,\n        :compress,\n        Application.get_env(:realtime, :metrics_pusher_compress, true)\n      )\n\n    Logger.info(\"Starting MetricsPusher (url: #{url}, interval: #{interval}ms, compress: #{compress})\")\n\n    headers = [{\"content-type\", \"text/plain\"}]\n\n    basic_auth = if auth, do: [auth: {:basic, \"#{user}:#{auth}\"}], else: []\n\n    req_options =\n      [\n        method: :post,\n        url: url,\n        headers: headers,\n        compress_body: compress,\n        receive_timeout: timeout\n      ]\n      |> Keyword.merge(basic_auth)\n      |> Keyword.merge(Application.get_env(:realtime, :metrics_pusher_req_options, []))\n\n    state = %__MODULE__{\n      push_ref: schedule_push(interval),\n      interval: interval,\n      req_options: req_options\n    }\n\n    {:ok, state}\n  end\n\n  @impl true\n  def handle_info(:push, state) do\n    {exec_time, _} = :timer.tc(fn -> push(state.req_options) end, :millisecond)\n\n    if exec_time > :timer.seconds(5) do\n      Logger.warning(\"Metrics push took: #{exec_time} ms\")\n    end\n\n    {:noreply, %{state | push_ref: schedule_push(state.interval)}}\n  end\n\n  @impl true\n  def handle_info(msg, state) do\n    Logger.error(\"MetricsPusher received unexpected message: #{inspect(msg)}\")\n    {:noreply, state}\n  end\n\n  defp schedule_push(delay), do: Process.send_after(self(), :push, delay)\n\n  defp push(req_options) do\n    try do\n      metrics = Realtime.PromEx.get_metrics()\n\n      case send_metrics(req_options, metrics) do\n        :ok ->\n          :ok\n\n        {:error, reason} ->\n          Logger.error(\"MetricsPusher: Failed to push metrics to #{req_options[:url]}: #{inspect(reason)}\")\n          :ok\n      end\n    rescue\n      error ->\n        Logger.error(\"MetricsPusher: Exception during push: #{inspect(error)}\")\n        :ok\n    end\n  end\n\n  defp send_metrics(req_options, metrics) do\n    [{:body, metrics} | req_options] |> Req.request() |> handle_response()\n  end\n\n  defp handle_response({:ok, %{status: status}}) when status in 200..299, do: :ok\n  defp handle_response({:ok, %{status: status} = response}), do: {:error, {:http_error, status, response.body}}\n  defp handle_response({:error, reason}), do: {:error, reason}\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/distributed_metrics.ex",
    "content": "defmodule Realtime.DistributedMetrics do\n  @moduledoc \"\"\"\n  Gather stats for each connected node\n  \"\"\"\n\n  require Record\n  Record.defrecordp(:net_address, Record.extract(:net_address, from_lib: \"kernel/include/net_address.hrl\"))\n  @spec info() :: %{node => map}\n  def info do\n    # First check if Erlang distribution is started\n    if :net_kernel.get_state()[:started] != :no do\n      {:ok, nodes_info} = :net_kernel.nodes_info()\n      # Ignore \"hidden\" nodes (remote shell)\n      nodes_info = Enum.filter(nodes_info, fn {_k, v} -> v[:type] == :normal end)\n\n      port_addresses =\n        :erlang.ports()\n        |> Stream.filter(fn port ->\n          :erlang.port_info(port, :name) == {:name, ~c\"tcp_inet\"}\n        end)\n        |> Stream.map(&{:inet.peername(&1), &1})\n        |> Stream.filter(fn\n          {{:ok, _peername}, _port} -> true\n          _ -> false\n        end)\n        |> Enum.map(fn {{:ok, peername}, port} -> {peername, port} end)\n        |> Enum.into(%{})\n\n      Map.new(nodes_info, &info(&1, port_addresses))\n    else\n      %{}\n    end\n  end\n\n  defp info({node, info}, port_addresses) do\n    dist_pid = info[:owner]\n    state = info[:state]\n\n    case info[:address] do\n      net_address(address: address) when address != :undefined ->\n        {node, info(node, port_addresses, dist_pid, state, address)}\n\n      _ ->\n        {node, %{pid: dist_pid, state: state}}\n    end\n  end\n\n  defp info(node, port_addresses, dist_pid, state, address) do\n    if dist_port = port_addresses[address] do\n      %{\n        inet_stats: inet_stats(dist_port),\n        port: dist_port,\n        pid: dist_pid,\n        state: state\n      }\n    else\n      %{pid: dist_pid, state: state}\n    end\n    |> Map.merge(%{\n      queue_size: node_queue_size(node)\n    })\n  end\n\n  defp inet_stats(port) do\n    case :inet.getstat(port) do\n      {:ok, stats} ->\n        stats\n\n      _ ->\n        nil\n    end\n  end\n\n  defp node_queue_size(node) do\n    case :ets.lookup(:sys_dist, node) do\n      [dist] ->\n        conn_id = elem(dist, 2)\n\n        with {:ok, _, _, queue_size} <- :erlang.dist_get_stat(conn_id) do\n          {:ok, queue_size}\n        else\n          _ -> {:error, :not_found}\n        end\n\n      _ ->\n        {:error, :not_found}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/erl_sys_mon.ex",
    "content": "defmodule Realtime.ErlSysMon do\n  @moduledoc \"\"\"\n  Logs Erlang System Monitor events.\n  \"\"\"\n\n  use GenServer\n\n  require Logger\n\n  @defaults [\n    :busy_dist_port,\n    :busy_port,\n    {:long_gc, 500},\n    {:long_schedule, 500},\n    {:long_message_queue, {0, 1_000}}\n  ]\n\n  def start_link(args), do: GenServer.start_link(__MODULE__, args)\n\n  def init(args) do\n    config = Keyword.get(args, :config, @defaults)\n    :erlang.system_monitor(self(), config)\n\n    {:ok, []}\n  end\n\n  def handle_info({:monitor, pid, _type, _meta} = msg, state) when is_pid(pid) do\n    log_process_info(msg, pid)\n    {:noreply, state}\n  end\n\n  def handle_info(msg, state) do\n    Logger.warning(\"#{__MODULE__} message: \" <> inspect(msg))\n    {:noreply, state}\n  end\n\n  defp log_process_info(msg, pid) do\n    pid_info =\n      pid\n      |> Process.info(:dictionary)\n      |> case do\n        {:dictionary, dict} when is_list(dict) ->\n          {List.keyfind(dict, :\"$initial_call\", 0), List.keyfind(dict, :\"$ancestors\", 0)}\n\n        other ->\n          other\n      end\n\n    extra_info = Process.info(pid, [:registered_name, :message_queue_len, :total_heap_size])\n\n    Logger.warning(\n      \"#{__MODULE__} message: \" <>\n        inspect(msg) <> \"|\\n process info: #{inspect(pid_info)} #{inspect(extra_info)}\"\n    )\n  rescue\n    _ ->\n      Logger.warning(\"#{__MODULE__} message: \" <> inspect(msg))\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/gen_rpc_metrics.ex",
    "content": "defmodule Realtime.GenRpcMetrics do\n  @moduledoc \"\"\"\n  Gather stats for gen_rpc TCP sockets\n  \"\"\"\n\n  require Record\n  Record.defrecordp(:net_address, Record.extract(:net_address, from_lib: \"kernel/include/net_address.hrl\"))\n\n  @spec info() :: %{node() => %{inet_stats: %{:inet.stat_option() => integer}, queue_size: non_neg_integer()}}\n  def info do\n    if :net_kernel.get_state()[:started] != :no do\n      {:ok, nodes_info} = :net_kernel.nodes_info()\n      # Ignore \"hidden\" nodes (remote shell)\n      nodes_info = Enum.filter(nodes_info, fn {_k, v} -> v[:type] == :normal end)\n      gen_rpc_server_port = server_port()\n      ip_address_node = ip_address_node(nodes_info)\n\n      {client_ports, server_ports} =\n        :erlang.ports()\n        |> Stream.filter(fn port -> :erlang.port_info(port, :name) == {:name, ~c\"tcp_inet\"} end)\n        |> Stream.map(&{:inet.peername(&1), :inet.sockname(&1), &1})\n        |> Stream.filter(fn\n          {{:ok, _peername}, {:ok, _sockname}, _port} -> true\n          _ -> false\n        end)\n        |> Stream.map(fn {{:ok, {peername_ipaddress, peername_port}}, {:ok, {_, server_port}}, port} ->\n          {ip_address_node[peername_ipaddress], peername_port, server_port, port}\n        end)\n        |> Stream.filter(fn\n          {nil, _, _} ->\n            false\n\n          {node, peername_port, server_port, _port} ->\n            {_, client_tcp_or_ssl_port} = :gen_rpc_helper.get_client_config_per_node(node)\n            # Only keep Erlang ports that are either serving on the gen_rpc server tcp/ssl port or\n            # connecting to other nodes using the expected client tcp/ssl port for that node\n            peername_port == client_tcp_or_ssl_port or server_port == gen_rpc_server_port\n        end)\n        |> Enum.reduce({%{}, %{}}, fn {node, _peername_port, server_port, port}, {clients, servers} ->\n          if server_port == gen_rpc_server_port do\n            # This Erlang port is serving gen_rpc\n            {clients, update_in(servers, [node], fn value -> [port | value || []] end)}\n          else\n            # This Erlang port is requesting gen_rpc\n            {update_in(clients, [node], fn value -> [port | value || []] end), servers}\n          end\n        end)\n\n      Map.new(nodes_info, &info(&1, client_ports, server_ports))\n    else\n      %{}\n    end\n  end\n\n  defp info({node, _}, client_ports, server_ports) do\n    gen_rpc_ports = Map.get(client_ports, node, []) ++ Map.get(server_ports, node, [])\n\n    if gen_rpc_ports != [] do\n      {node,\n       %{\n         inet_stats: inet_stats(gen_rpc_ports),\n         queue_size: queue_size(gen_rpc_ports),\n         connections: length(gen_rpc_ports)\n       }}\n    else\n      {node, %{}}\n    end\n  end\n\n  defp inet_stats(ports) do\n    Enum.reduce(ports, %{}, fn port, acc ->\n      case :inet.getstat(port) do\n        {:ok, stats} -> Map.merge(acc, Map.new(stats), fn _k, v1, v2 -> v1 + v2 end)\n        _ -> acc\n      end\n    end)\n  end\n\n  defp queue_size(ports) do\n    Enum.reduce(ports, 0, fn port, acc ->\n      {:queue_size, queue_size} = :erlang.port_info(port, :queue_size)\n      acc + queue_size\n    end)\n  end\n\n  defp server_port() do\n    if Application.fetch_env!(:gen_rpc, :default_client_driver) == :tcp do\n      Application.fetch_env!(:gen_rpc, :tcp_server_port)\n    else\n      Application.fetch_env!(:gen_rpc, :ssl_server_port)\n    end\n  end\n\n  defp ip_address_node(nodes_info) do\n    nodes_info\n    |> Stream.map(fn {node, info} ->\n      case info[:address] do\n        net_address(address: {ip_address, _}) ->\n          {ip_address, node}\n\n        _ ->\n          {nil, node}\n      end\n    end)\n    |> Stream.filter(fn {ip_address, _node} -> ip_address != nil end)\n    |> Map.new()\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/latency.ex",
    "content": "defmodule Realtime.Latency do\n  @moduledoc \"\"\"\n    Measures the latency of the cluster from each node and broadcasts it over PubSub.\n  \"\"\"\n\n  use GenServer\n  use Realtime.Logs\n\n  alias Realtime.Nodes\n  alias Realtime.GenRpc\n\n  defmodule Payload do\n    @moduledoc false\n\n    defstruct [\n      :from_node,\n      :from_region,\n      :node,\n      :region,\n      :latency,\n      :response,\n      :timestamp\n    ]\n\n    @type t :: %__MODULE__{\n            node: atom(),\n            region: String.t() | nil,\n            from_node: atom(),\n            from_region: String.t(),\n            latency: integer(),\n            response: {:ok, :pong} | {:badrpc, any()},\n            timestamp: DateTime\n          }\n  end\n\n  @every 15_000\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  def init(_args) do\n    ping_after()\n\n    {:ok, []}\n  end\n\n  def handle_info(:ping, state) do\n    ping()\n    ping_after()\n\n    {:noreply, state}\n  end\n\n  def handle_info(msg, state) do\n    Logger.warning(\"Unexpected message: #{inspect(msg)}\")\n    {:noreply, state}\n  end\n\n  def handle_cast({:ping, pong_timeout, timer_timeout, yield_timeout}, state) do\n    # For testing\n    ping(pong_timeout, timer_timeout, yield_timeout)\n\n    {:noreply, state}\n  end\n\n  @doc \"\"\"\n  Pings all the nodes in the cluster one after another and returns with their responses.\n  There is a timeout for a single node rpc, and a timeout to yield_many which should really\n  never get hit because these pings happen async under the Realtime.TaskSupervisor.\n  \"\"\"\n\n  @spec ping :: [{Task.t(), tuple() | nil}]\n  def ping(pong_timeout \\\\ 0, timer_timeout \\\\ 5_000, yield_timeout \\\\ 5_000) do\n    tasks =\n      for n <- [Node.self() | Node.list()] do\n        Task.Supervisor.async(Realtime.TaskSupervisor, fn ->\n          {latency, response} =\n            :timer.tc(fn ->\n              GenRpc.call(n, __MODULE__, :pong, [pong_timeout], timeout: timer_timeout)\n            end)\n\n          latency_ms = latency / 1_000\n          region = Application.get_env(:realtime, :region, \"not_set\")\n          short_name = Nodes.short_node_id_from_name(n)\n          from_node = Nodes.short_node_id_from_name(Node.self())\n\n          case response do\n            {:error, :rpc_error, reason} ->\n              log_error(\n                \"RealtimeNodeDisconnected\",\n                \"Unable to connect to #{short_name} from #{region}: #{reason}\"\n              )\n\n              payload = %Payload{\n                from_node: from_node,\n                from_region: region,\n                node: short_name,\n                region: nil,\n                latency: latency_ms,\n                response: response,\n                timestamp: DateTime.utc_now()\n              }\n\n              RealtimeWeb.Endpoint.broadcast(\"admin:cluster\", \"pong\", payload)\n\n              payload\n\n            {:ok, {:pong, remote_region}} ->\n              if latency_ms > 1_000,\n                do:\n                  Logger.warning(\n                    \"Network warning: latency to #{remote_region} (#{short_name}) from #{region} (#{from_node}) is #{latency_ms} ms\"\n                  )\n\n              payload = %Payload{\n                from_node: from_node,\n                from_region: region,\n                node: short_name,\n                region: remote_region,\n                latency: latency_ms,\n                response: response,\n                timestamp: DateTime.utc_now()\n              }\n\n              RealtimeWeb.Endpoint.broadcast(\"admin:cluster\", \"pong\", payload)\n\n              payload\n          end\n        end)\n      end\n      |> Task.yield_many(yield_timeout)\n\n    for {task, result} <- tasks do\n      unless result, do: Task.shutdown(task, :brutal_kill)\n    end\n\n    tasks\n  end\n\n  @doc \"\"\"\n  A noop function to call from a remote server.\n  \"\"\"\n\n  @spec pong :: {:ok, {:pong, String.t()}}\n  def pong do\n    region = Application.get_env(:realtime, :region, \"not_set\")\n    {:ok, {:pong, region}}\n  end\n\n  @spec pong(:infinity | non_neg_integer) :: {:ok, {:pong, String.t()}}\n  def pong(latency) when is_integer(latency) do\n    Process.sleep(latency)\n    pong()\n  end\n\n  defp ping_after do\n    Process.send_after(self(), :ping, @every)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/os_metrics.ex",
    "content": "defmodule Realtime.OsMetrics do\n  @moduledoc \"\"\"\n  This module provides functions to get CPU and RAM usage.\n  \"\"\"\n\n  @spec ram_usage() :: float()\n  def ram_usage do\n    mem = :memsup.get_system_memory_data()\n    free_mem = if Mix.env() in [:dev, :test], do: mem[:free_memory], else: mem[:available_memory]\n    100 - free_mem / mem[:total_memory] * 100\n  end\n\n  @spec cpu_la() :: %{avg1: float(), avg5: float(), avg15: float()}\n  def cpu_la do\n    %{\n      avg1: :cpu_sup.avg1() / 256,\n      avg5: :cpu_sup.avg5() / 256,\n      avg15: :cpu_sup.avg15() / 256\n    }\n  end\n\n  @spec cpu_util() :: float() | {:error, term()}\n  def cpu_util do\n    :cpu_sup.util()\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/peep/partitioned.ex",
    "content": "defmodule Realtime.Monitoring.Peep.Partitioned do\n  @moduledoc \"\"\"\n  Peep.Storage implementation using a single ETS table with a configurable number of partitions\n  \"\"\"\n  alias Peep.Storage\n  alias Telemetry.Metrics\n\n  @behaviour Peep.Storage\n\n  @spec new(pos_integer) :: {:ets.tid(), pos_integer}\n  @impl true\n  def new(partitions) when is_integer(partitions) and partitions > 0 do\n    opts = [\n      :public,\n      # Enabling read_concurrency makes switching between reads and writes\n      # more expensive. The goal is to ruthlessly optimize writes, even at\n      # the cost of read performance.\n      read_concurrency: false,\n      write_concurrency: true,\n      decentralized_counters: true\n    ]\n\n    {:ets.new(__MODULE__, opts), partitions}\n  end\n\n  @impl true\n  def storage_size({tid, _}) do\n    %{\n      size: :ets.info(tid, :size),\n      memory: :ets.info(tid, :memory) * :erlang.system_info(:wordsize)\n    }\n  end\n\n  @impl true\n  def insert_metric({tid, partitions}, id, %Metrics.Counter{}, _value, %{} = tags) do\n    key = {id, tags, :rand.uniform(partitions)}\n    :ets.update_counter(tid, key, {2, 1}, {key, 0})\n  end\n\n  def insert_metric({tid, partitions}, id, %Metrics.Sum{}, value, %{} = tags) do\n    key = {id, tags, :rand.uniform(partitions)}\n    :ets.update_counter(tid, key, {2, value}, {key, 0})\n  end\n\n  def insert_metric({tid, _partitions}, id, %Metrics.LastValue{}, value, %{} = tags) do\n    key = {id, tags}\n    :ets.insert(tid, {key, value})\n  end\n\n  def insert_metric({tid, _partitions}, id, %Metrics.Distribution{} = metric, value, %{} = tags) do\n    key = {id, tags}\n\n    atomics =\n      case :ets.lookup(tid, key) do\n        [{_key, ref}] ->\n          ref\n\n        [] ->\n          # Race condition: Multiple processes could be attempting\n          # to write to this key. Thankfully, :ets.insert_new/2 will break ties,\n          # and concurrent writers should agree on which :atomics object to\n          # increment.\n          new_atomics = Storage.Atomics.new(metric)\n\n          case :ets.insert_new(tid, {key, new_atomics}) do\n            true ->\n              new_atomics\n\n            false ->\n              [{_key, atomics}] = :ets.lookup(tid, key)\n              atomics\n          end\n      end\n\n    Storage.Atomics.insert(atomics, value)\n  end\n\n  @impl true\n  def get_all_metrics({tid, _partitions}, %Peep.Persistent{ids_to_metrics: itm}) do\n    :ets.tab2list(tid)\n    |> group_metrics(itm, %{})\n  end\n\n  @impl true\n  def get_metric({tid, _partitions}, id, %Metrics.Counter{}, tags) do\n    :ets.select(tid, [{{{id, :\"$2\", :_}, :\"$1\"}, [{:==, :\"$2\", tags}], [:\"$1\"]}])\n    |> Enum.reduce(0, fn count, acc -> count + acc end)\n  end\n\n  def get_metric({tid, _partitions}, id, %Metrics.Sum{}, tags) do\n    :ets.select(tid, [{{{id, :\"$2\", :_}, :\"$1\"}, [{:==, :\"$2\", tags}], [:\"$1\"]}])\n    |> Enum.reduce(0, fn count, acc -> count + acc end)\n  end\n\n  def get_metric({tid, _partitions}, id, %Metrics.LastValue{}, tags) do\n    case :ets.lookup(tid, {id, tags}) do\n      [{_key, value}] -> value\n      _ -> nil\n    end\n  end\n\n  def get_metric({tid, _partitions}, id, %Metrics.Distribution{}, tags) do\n    key = {id, tags}\n\n    case :ets.lookup(tid, key) do\n      [{_key, atomics}] -> Storage.Atomics.values(atomics)\n      _ -> nil\n    end\n  end\n\n  @impl true\n  def prune_tags({tid, _partitions}, patterns) do\n    match_spec =\n      patterns\n      |> Enum.flat_map(fn pattern ->\n        counter_or_sum_key = {:_, pattern, :_}\n        dist_or_last_value_key = {:_, pattern}\n\n        [\n          {\n            {counter_or_sum_key, :_},\n            [],\n            [true]\n          },\n          {\n            {dist_or_last_value_key, :_},\n            [],\n            [true]\n          }\n        ]\n      end)\n\n    :ets.select_delete(tid, match_spec)\n    :ok\n  end\n\n  defp group_metrics([], _itm, acc) do\n    acc\n  end\n\n  defp group_metrics([metric | rest], itm, acc) do\n    acc2 = group_metric(metric, itm, acc)\n    group_metrics(rest, itm, acc2)\n  end\n\n  defp group_metric({{id, tags, _}, value}, itm, acc) do\n    %{^id => metric} = itm\n    update_in(acc, [Access.key(metric, %{}), Access.key(tags, 0)], &(&1 + value))\n  end\n\n  defp group_metric({{id, tags}, %Storage.Atomics{} = atomics}, itm, acc) do\n    %{^id => metric} = itm\n    put_in(acc, [Access.key(metric, %{}), Access.key(tags)], Storage.Atomics.values(atomics))\n  end\n\n  defp group_metric({{id, tags}, value}, itm, acc) do\n    %{^id => metric} = itm\n    put_in(acc, [Access.key(metric, %{}), Access.key(tags)], value)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/prom_ex/plugins/channels.ex",
    "content": "defmodule Realtime.PromEx.Plugins.Channels do\n  @moduledoc \"\"\"\n  Realtime channels monitoring plugin for PromEx\n  \"\"\"\n  use PromEx.Plugin\n  require Logger\n\n  @impl true\n  def event_metrics(_opts) do\n    Event.build(:realtime, [\n      counter(\n        [:realtime, :channel, :error],\n        event_name: [:realtime, :channel, :error],\n        measurement: :code,\n        tags: [:code],\n        description: \"Count of errors in the Realtime channels initialization\"\n      )\n    ])\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/prom_ex/plugins/distributed.ex",
    "content": "defmodule Realtime.PromEx.Plugins.Distributed do\n  @moduledoc \"\"\"\n  Distributed erlang metrics\n  \"\"\"\n\n  use PromEx.Plugin\n  alias Realtime.DistributedMetrics\n\n  @event_node_queue_size [:prom_ex, :plugin, :dist, :queue_size]\n  @event_recv_bytes [:prom_ex, :plugin, :dist, :recv, :bytes]\n  @event_recv_count [:prom_ex, :plugin, :dist, :recv, :count]\n  @event_send_bytes [:prom_ex, :plugin, :dist, :send, :bytes]\n  @event_send_count [:prom_ex, :plugin, :dist, :send, :count]\n  @event_send_pending_bytes [:prom_ex, :plugin, :dist, :send, :pending, :bytes]\n\n  @impl true\n  def polling_metrics(opts) do\n    poll_rate = Keyword.get(opts, :poll_rate)\n\n    [\n      metrics(poll_rate)\n    ]\n  end\n\n  defp metrics(poll_rate) do\n    Polling.build(\n      :realtime_vm_dist,\n      poll_rate,\n      {__MODULE__, :execute_metrics, []},\n      [\n        last_value(\n          [:dist, :queue_size],\n          event_name: @event_node_queue_size,\n          description: \"Number of bytes in the output distribution queue\",\n          measurement: :size,\n          tags: [:origin_node, :target_node]\n        ),\n        last_value(\n          [:dist, :recv_bytes],\n          event_name: @event_recv_bytes,\n          description: \"Number of bytes received by the socket.\",\n          measurement: :size,\n          tags: [:origin_node, :target_node]\n        ),\n        last_value(\n          [:dist, :recv_count],\n          event_name: @event_recv_count,\n          description: \"Number of packets received by the socket.\",\n          measurement: :size,\n          tags: [:origin_node, :target_node]\n        ),\n        last_value(\n          [:dist, :send_bytes],\n          event_name: @event_send_bytes,\n          description: \"Number of bytes sent by the socket.\",\n          measurement: :size,\n          tags: [:origin_node, :target_node]\n        ),\n        last_value(\n          [:dist, :send_count],\n          event_name: @event_send_count,\n          description: \"Number of packets sent by the socket.\",\n          measurement: :size,\n          tags: [:origin_node, :target_node]\n        ),\n        last_value(\n          [:dist, :send_pending_bytes],\n          event_name: @event_send_pending_bytes,\n          description: \"Number of bytes waiting to be sent by the socket.\",\n          measurement: :size,\n          tags: [:origin_node, :target_node]\n        )\n      ],\n      detach_on_error: false\n    )\n  end\n\n  def execute_metrics do\n    dist_info = DistributedMetrics.info()\n\n    Enum.each(dist_info, fn {node, info} ->\n      execute_queue_size(node, info)\n      execute_inet_stats(node, info)\n    end)\n  end\n\n  defp execute_inet_stats(node, info) do\n    if stats = info[:inet_stats] do\n      :telemetry.execute(@event_recv_bytes, %{size: stats[:recv_oct]}, %{origin_node: node(), target_node: node})\n      :telemetry.execute(@event_recv_count, %{size: stats[:recv_cnt]}, %{origin_node: node(), target_node: node})\n\n      :telemetry.execute(@event_send_bytes, %{size: stats[:send_oct]}, %{origin_node: node(), target_node: node})\n      :telemetry.execute(@event_send_count, %{size: stats[:send_cnt]}, %{origin_node: node(), target_node: node})\n\n      :telemetry.execute(@event_send_pending_bytes, %{size: stats[:send_pend]}, %{\n        origin_node: node(),\n        target_node: node\n      })\n    end\n  end\n\n  defp execute_queue_size(node, info) do\n    with {:ok, size} <- info[:queue_size] do\n      :telemetry.execute(@event_node_queue_size, %{size: size}, %{origin_node: node(), target_node: node})\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/prom_ex/plugins/gen_rpc.ex",
    "content": "defmodule Realtime.PromEx.Plugins.GenRpc do\n  @moduledoc \"\"\"\n  GenRpc metrics\n  \"\"\"\n\n  use PromEx.Plugin\n\n  alias Realtime.GenRpcMetrics\n\n  @event_queue_size_bytes [:prom_ex, :plugin, :gen_rpc, :queue_size, :bytes]\n  @event_recv_bytes [:prom_ex, :plugin, :gen_rpc, :recv, :bytes]\n  @event_recv_count [:prom_ex, :plugin, :gen_rpc, :recv, :count]\n  @event_send_bytes [:prom_ex, :plugin, :gen_rpc, :send, :bytes]\n  @event_send_count [:prom_ex, :plugin, :gen_rpc, :send, :count]\n  @event_send_pending_bytes [:prom_ex, :plugin, :gen_rpc, :send, :pending, :bytes]\n\n  @impl true\n  def polling_metrics(opts) do\n    poll_rate = Keyword.get(opts, :poll_rate)\n\n    [\n      metrics(poll_rate)\n    ]\n  end\n\n  defp metrics(poll_rate) do\n    Polling.build(\n      :realtime_gen_rpc,\n      poll_rate,\n      {__MODULE__, :execute_metrics, []},\n      [\n        last_value(\n          [:gen_rpc, :queue_size_bytes],\n          event_name: @event_queue_size_bytes,\n          description: \"The total number of bytes queued by the port using the ERTS driver queue implementation\",\n          measurement: :size,\n          tags: [:origin_node, :target_node]\n        ),\n        last_value(\n          [:gen_rpc, :recv_bytes],\n          event_name: @event_recv_bytes,\n          description: \"Number of bytes received by the socket.\",\n          measurement: :size,\n          tags: [:origin_node, :target_node]\n        ),\n        last_value(\n          [:gen_rpc, :recv_count],\n          event_name: @event_recv_count,\n          description: \"Number of packets received by the socket.\",\n          measurement: :size,\n          tags: [:origin_node, :target_node]\n        ),\n        last_value(\n          [:gen_rpc, :send_bytes],\n          event_name: @event_send_bytes,\n          description: \"Number of bytes sent by the socket.\",\n          measurement: :size,\n          tags: [:origin_node, :target_node]\n        ),\n        last_value(\n          [:gen_rpc, :send_count],\n          event_name: @event_send_count,\n          description: \"Number of packets sent by the socket.\",\n          measurement: :size,\n          tags: [:origin_node, :target_node]\n        ),\n        last_value(\n          [:gen_rpc, :send_pending_bytes],\n          event_name: @event_send_pending_bytes,\n          description: \"Number of bytes waiting to be sent by the socket.\",\n          measurement: :size,\n          tags: [:origin_node, :target_node]\n        )\n      ],\n      detach_on_error: false\n    )\n  end\n\n  def execute_metrics do\n    dist_info = GenRpcMetrics.info()\n\n    Enum.each(dist_info, fn {node, info} ->\n      execute_queue_size(node, info)\n      execute_inet_stats(node, info)\n    end)\n  end\n\n  defp execute_inet_stats(node, info) do\n    if stats = info[:inet_stats] do\n      :telemetry.execute(@event_recv_bytes, %{size: stats[:recv_oct]}, %{origin_node: node(), target_node: node})\n      :telemetry.execute(@event_recv_count, %{size: stats[:recv_cnt]}, %{origin_node: node(), target_node: node})\n\n      :telemetry.execute(@event_send_bytes, %{size: stats[:send_oct]}, %{origin_node: node(), target_node: node})\n      :telemetry.execute(@event_send_count, %{size: stats[:send_cnt]}, %{origin_node: node(), target_node: node})\n\n      :telemetry.execute(@event_send_pending_bytes, %{size: stats[:send_pend]}, %{\n        origin_node: node(),\n        target_node: node\n      })\n    end\n  end\n\n  defp execute_queue_size(node, info) do\n    :telemetry.execute(@event_queue_size_bytes, %{size: info[:queue_size]}, %{origin_node: node(), target_node: node})\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/prom_ex/plugins/osmon.ex",
    "content": "defmodule Realtime.PromEx.Plugins.OsMon do\n  @moduledoc \"\"\"\n  Polls os_mon metrics.\n  \"\"\"\n\n  use PromEx.Plugin\n  require Logger\n  alias Realtime.OsMetrics\n\n  @event_ram_usage [:prom_ex, :plugin, :osmon, :ram_usage]\n  @event_cpu_util [:prom_ex, :plugin, :osmon, :cpu_util]\n  @event_cpu_la [:prom_ex, :plugin, :osmon, :cpu_avg1]\n\n  @impl true\n  def polling_metrics(opts) do\n    poll_rate = Keyword.get(opts, :poll_rate)\n\n    [\n      metrics(poll_rate)\n    ]\n  end\n\n  defp metrics(poll_rate) do\n    Polling.build(\n      :realtime_osmon_events,\n      poll_rate,\n      {__MODULE__, :execute_metrics, []},\n      [\n        last_value(\n          [:osmon, :ram_usage],\n          event_name: @event_ram_usage,\n          description: \"The total percentage usage of operative memory.\",\n          measurement: :ram\n        ),\n        last_value(\n          [:osmon, :cpu_util],\n          event_name: @event_cpu_util,\n          description:\n            \"The sum of the percentage shares of the CPU cycles spent in all busy processor states in average on all CPUs.\",\n          measurement: :cpu\n        ),\n        last_value(\n          [:osmon, :cpu_avg1],\n          event_name: @event_cpu_la,\n          description: \"The average system load in the last minute.\",\n          measurement: :avg1\n        ),\n        last_value(\n          [:osmon, :cpu_avg5],\n          event_name: @event_cpu_la,\n          description: \"The average system load in the last five minutes.\",\n          measurement: :avg5\n        ),\n        last_value(\n          [:osmon, :cpu_avg15],\n          event_name: @event_cpu_la,\n          description: \"The average system load in the last 15 minutes.\",\n          measurement: :avg15\n        )\n      ],\n      detach_on_error: false\n    )\n  end\n\n  def execute_metrics do\n    execute_metrics(@event_ram_usage, %{ram: OsMetrics.ram_usage()})\n    execute_metrics(@event_cpu_util, %{cpu: OsMetrics.cpu_util()})\n    execute_metrics(@event_cpu_la, OsMetrics.cpu_la())\n  end\n\n  defp execute_metrics(event, metrics) do\n    :telemetry.execute(event, metrics, %{})\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/prom_ex/plugins/phoenix.ex",
    "content": "# copied from https://github.com/akoutmos/prom_ex/blob/master/lib/prom_ex/plugins/phoenix.ex\n\nif Code.ensure_loaded?(Phoenix) do\n  defmodule Realtime.PromEx.Plugins.Phoenix do\n    @moduledoc false\n    use PromEx.Plugin\n\n    require Logger\n\n    alias Phoenix.Socket\n    alias RealtimeWeb.Endpoint.HTTP, as: HTTP\n\n    @stop_event [:prom_ex, :plugin, :phoenix, :stop]\n    @event_all_connections [:prom_ex, :plugin, :phoenix, :all_connections]\n\n    @impl true\n    def event_metrics(opts) do\n      otp_app = Keyword.fetch!(opts, :otp_app)\n      metric_prefix = Keyword.get(opts, :metric_prefix, PromEx.metric_prefix(otp_app, :phoenix))\n      phoenix_event_prefixes = fetch_event_prefixes!(opts)\n\n      set_up_telemetry_proxy(phoenix_event_prefixes)\n\n      # Event metrics definitions\n      [\n        channel_events(metric_prefix),\n        socket_events(metric_prefix)\n      ]\n    end\n\n    @impl true\n    def polling_metrics(opts) do\n      otp_app = Keyword.fetch!(opts, :otp_app)\n      metric_prefix = Keyword.get(opts, :metric_prefix, PromEx.metric_prefix(otp_app, :phoenix))\n      poll_rate = Keyword.get(opts, :poll_rate)\n\n      [\n        metrics(metric_prefix, poll_rate)\n      ]\n    end\n\n    def metrics(metric_prefix, poll_rate) do\n      Polling.build(\n        :phoenix_all_connections,\n        poll_rate,\n        {__MODULE__, :execute_metrics, []},\n        [\n          last_value(\n            metric_prefix ++ [:connections, :total],\n            event_name: @event_all_connections,\n            description: \"The total open connections to ranch.\",\n            measurement: :total\n          ),\n          last_value(\n            metric_prefix ++ [:connections, :active],\n            event_name: @event_all_connections,\n            description: \"Connections actively processing a request or WebSocket frame.\",\n            measurement: :active\n          ),\n          last_value(\n            metric_prefix ++ [:connections, :max],\n            event_name: @event_all_connections,\n            description: \"The configured maximum connections limit for the ranch listener.\",\n            measurement: :max\n          )\n        ],\n        detach_on_error: false\n      )\n    end\n\n    def execute_metrics do\n      info = if :ranch.info()[HTTP], do: :ranch.info(HTTP), else: %{}\n\n      :telemetry.execute(\n        @event_all_connections,\n        %{\n          total: Map.get(info, :all_connections, -1),\n          active: Map.get(info, :active_connections, -1),\n          max: Map.get(info, :max_connections, -1)\n        },\n        %{}\n      )\n    end\n\n    defmodule Buckets do\n      @moduledoc false\n      use Peep.Buckets.Custom, buckets: [10, 100, 500, 1_000, 5_000, 10_000]\n    end\n\n    defp channel_events(metric_prefix) do\n      Event.build(\n        :phoenix_channel_event_metrics,\n        [\n          # Capture the number of channel joins that have occurred\n          counter(\n            metric_prefix ++ [:channel, :joined, :total],\n            event_name: [:phoenix, :channel_joined],\n            description: \"The number of channel joins that have occurred.\",\n            tag_values: fn %{\n                             result: result,\n                             socket: %Socket{transport: transport, endpoint: endpoint}\n                           } ->\n              %{\n                transport: transport,\n                result: result,\n                endpoint: normalize_module_name(endpoint)\n              }\n            end,\n            tags: [:result, :transport, :endpoint]\n          ),\n\n          # Capture channel handle_in duration\n          distribution(\n            metric_prefix ++ [:channel, :handled_in, :duration, :milliseconds],\n            event_name: [:phoenix, :channel_handled_in],\n            measurement: :duration,\n            description: \"The time it takes for the application to respond to channel messages.\",\n            reporter_options: [peep_bucket_calculator: Buckets],\n            tag_values: fn %{socket: %Socket{endpoint: endpoint}} ->\n              %{\n                endpoint: normalize_module_name(endpoint)\n              }\n            end,\n            tags: [:endpoint],\n            unit: {:native, :millisecond}\n          )\n        ]\n      )\n    end\n\n    defp socket_events(metric_prefix) do\n      Event.build(\n        :phoenix_socket_event_metrics,\n        [\n          # Capture socket connection duration\n          distribution(\n            metric_prefix ++ [:socket, :connected, :duration, :milliseconds],\n            event_name: [:phoenix, :socket_connected],\n            measurement: :duration,\n            description: \"The time it takes for the application to establish a socket connection.\",\n            reporter_options: [peep_bucket_calculator: Buckets],\n            tag_values: fn %{result: result, endpoint: endpoint, transport: transport, serializer: serializer} ->\n              %{\n                transport: transport,\n                result: result,\n                endpoint: normalize_module_name(endpoint),\n                serializer: serializer\n              }\n            end,\n            tags: [:result, :transport, :endpoint, :serializer],\n            unit: {:native, :millisecond}\n          )\n        ]\n      )\n    end\n\n    defp set_up_telemetry_proxy(phoenix_event_prefixes) do\n      phoenix_event_prefixes\n      |> Enum.each(fn telemetry_prefix ->\n        stop_event = telemetry_prefix ++ [:stop]\n\n        :telemetry.attach(\n          [:prom_ex, :phoenix, :proxy] ++ telemetry_prefix,\n          stop_event,\n          &__MODULE__.handle_proxy_phoenix_event/4,\n          %{}\n        )\n      end)\n    end\n\n    @doc false\n    def handle_proxy_phoenix_event(_event_name, event_measurement, event_metadata, _config) do\n      :telemetry.execute(@stop_event, event_measurement, event_metadata)\n    end\n\n    defp normalize_module_name(name) when is_atom(name) do\n      name\n      |> Atom.to_string()\n      |> String.trim_leading(\"Elixir.\")\n    end\n\n    defp normalize_module_name(name), do: name\n\n    defp fetch_event_prefixes!(opts) do\n      opts\n      |> fetch_either!(:router, :endpoints)\n      |> case do\n        endpoints when is_list(endpoints) ->\n          endpoints\n          |> Enum.map(fn\n            {_endpoint, endpoint_opts} ->\n              Keyword.get(endpoint_opts, :event_prefix, [:phoenix, :endpoint])\n          end)\n\n        _router ->\n          [Keyword.get(opts, :event_prefix, [:phoenix, :endpoint])]\n      end\n      |> MapSet.new()\n      |> MapSet.to_list()\n    end\n\n    defp fetch_either!(keywordlist, key1, key2) do\n      case {Keyword.has_key?(keywordlist, key1), Keyword.has_key?(keywordlist, key2)} do\n        {true, _} ->\n          keywordlist[key1]\n\n        {false, true} ->\n          keywordlist[key2]\n\n        {false, false} ->\n          raise KeyError,\n                \"Neither #{inspect(key1)} nor #{inspect(key2)} found in #{inspect(keywordlist)}\"\n      end\n    end\n  end\nelse\n  defmodule PromEx.Plugins.Phoenix do\n    @moduledoc false\n    use PromEx.Plugin\n\n    @impl true\n    def event_metrics(_opts) do\n      PromEx.Plugin.no_dep_raise(__MODULE__, \"Phoenix\")\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/prom_ex/plugins/tenant.ex",
    "content": "defmodule Realtime.PromEx.Plugins.Tenant do\n  @moduledoc false\n\n  use PromEx.Plugin\n  require Logger\n  alias Realtime.Telemetry\n  alias Realtime.Tenants\n  alias Realtime.UsersCounter\n\n  @impl true\n  def polling_metrics(opts) do\n    poll_rate = Keyword.get(opts, :poll_rate, 5_000)\n\n    [\n      concurrent_connections(poll_rate)\n    ]\n  end\n\n  @impl true\n  def event_metrics(_opts) do\n    # Event metrics definitions\n    [\n      channel_events(),\n      replication_metrics(),\n      payload_size_metrics()\n    ]\n  end\n\n  defmodule PayloadSize.Buckets do\n    @moduledoc false\n    use Peep.Buckets.Custom,\n      buckets: [250, 500, 1000, 3000, 5000, 10_000, 25_000, 100_000, 500_000, 1_000_000, 3_000_000]\n  end\n\n  defp payload_size_metrics do\n    Event.build(\n      :realtime_tenant_payload_size_metrics,\n      [\n        distribution(\n          [:realtime, :tenants, :payload, :size],\n          event_name: [:realtime, :tenants, :payload, :size],\n          measurement: :size,\n          description: \"Tenant payload size\",\n          tags: [:tenant, :message_type],\n          unit: :byte,\n          reporter_options: [peep_bucket_calculator: PayloadSize.Buckets]\n        )\n      ]\n    )\n  end\n\n  defp concurrent_connections(poll_rate) do\n    Polling.build(\n      :realtime_concurrent_connections,\n      poll_rate,\n      {__MODULE__, :execute_tenant_metrics, []},\n      [\n        last_value(\n          [:realtime, :connections, :connected],\n          event_name: [:realtime, :connections],\n          description: \"The node total count of connected clients for a tenant.\",\n          measurement: :connected,\n          tags: [:tenant]\n        ),\n        last_value(\n          [:realtime, :connections, :connected_cluster],\n          event_name: [:realtime, :connections],\n          description: \"The cluster total count of connected clients for a tenant.\",\n          measurement: :connected_cluster,\n          tags: [:tenant]\n        )\n      ],\n      detach_on_error: false\n    )\n  end\n\n  def execute_tenant_metrics do\n    cluster_counts = UsersCounter.tenant_counts()\n    local_tenant_counts = UsersCounter.local_tenant_counts()\n\n    for {t, count} <- local_tenant_counts do\n      tenant = Tenants.Cache.get_tenant_by_external_id(t)\n\n      if tenant != nil do\n        Telemetry.execute(\n          [:realtime, :connections],\n          %{\n            connected: count,\n            connected_cluster: Map.get(cluster_counts, t, 0),\n            limit: tenant.max_concurrent_users\n          },\n          %{tenant: t}\n        )\n      end\n    end\n  end\n\n  defmodule Replication.Buckets do\n    @moduledoc false\n    use Peep.Buckets.Custom,\n      buckets: [250, 500, 1000, 3000, 5000, 10_000, 25_000, 100_000, 500_000, 1_000_000, 3_000_000]\n  end\n\n  defp replication_metrics do\n    Event.build(\n      :realtime_tenant_replication_event_metrics,\n      [\n        distribution(\n          [:realtime, :replication, :poller, :query, :duration],\n          event_name: [:realtime, :replication, :poller, :query, :stop],\n          measurement: :duration,\n          description: \"Duration of the logical replication slot polling query for Realtime RLS.\",\n          tags: [:tenant],\n          unit: {:microsecond, :millisecond},\n          reporter_options: [peep_bucket_calculator: Replication.Buckets]\n        )\n      ]\n    )\n  end\n\n  defmodule PolicyAuthorization.Buckets do\n    @moduledoc false\n    use Peep.Buckets.Custom, buckets: [10, 250, 5000, 15_000]\n  end\n\n  defmodule BroadcastFromDatabase.Buckets do\n    @moduledoc false\n    use Peep.Buckets.Custom, buckets: [10, 250, 5000]\n  end\n\n  defmodule Replay.Buckets do\n    @moduledoc false\n    use Peep.Buckets.Custom, buckets: [10, 250, 5000, 15_000]\n  end\n\n  defp channel_events do\n    Event.build(\n      :realtime_tenant_channel_event_metrics,\n      [\n        sum(\n          [:realtime, :channel, :events],\n          event_name: [:realtime, :rate_counter, :channel, :events],\n          measurement: :sum,\n          description: \"Sum of messages sent on a Realtime Channel.\",\n          tags: [:tenant]\n        ),\n        sum(\n          [:realtime, :channel, :presence_events],\n          event_name: [:realtime, :rate_counter, :channel, :presence_events],\n          measurement: :sum,\n          description: \"Sum of presence messages sent on a Realtime Channel.\",\n          tags: [:tenant]\n        ),\n        sum(\n          [:realtime, :channel, :db_events],\n          event_name: [:realtime, :rate_counter, :channel, :db_events],\n          measurement: :sum,\n          description: \"Sum of db messages sent on a Realtime Channel.\",\n          tags: [:tenant]\n        ),\n        sum(\n          [:realtime, :channel, :joins],\n          event_name: [:realtime, :rate_counter, :channel, :joins],\n          measurement: :sum,\n          description: \"Sum of Realtime Channel joins.\",\n          tags: [:tenant]\n        ),\n        sum(\n          [:realtime, :channel, :input_bytes],\n          event_name: [:realtime, :channel, :input_bytes],\n          description: \"Sum of input bytes sent on sockets.\",\n          measurement: :size,\n          tags: [:tenant]\n        ),\n        sum(\n          [:realtime, :channel, :output_bytes],\n          event_name: [:realtime, :channel, :output_bytes],\n          description: \"Sum of output bytes sent on sockets.\",\n          measurement: :size,\n          tags: [:tenant]\n        ),\n        distribution(\n          [:realtime, :tenants, :read_authorization_check],\n          event_name: [:realtime, :tenants, :read_authorization_check],\n          measurement: :latency,\n          unit: :millisecond,\n          description: \"Latency of read authorization checks.\",\n          tags: [:tenant],\n          reporter_options: [peep_bucket_calculator: PolicyAuthorization.Buckets]\n        ),\n        distribution(\n          [:realtime, :tenants, :write_authorization_check],\n          event_name: [:realtime, :tenants, :write_authorization_check],\n          measurement: :latency,\n          unit: :millisecond,\n          description: \"Latency of write authorization checks.\",\n          tags: [:tenant],\n          reporter_options: [peep_bucket_calculator: PolicyAuthorization.Buckets]\n        ),\n        distribution(\n          [:realtime, :tenants, :broadcast_from_database, :latency_committed_at],\n          event_name: [:realtime, :tenants, :broadcast_from_database],\n          measurement: :latency_committed_at,\n          unit: :millisecond,\n          description: \"Latency of database transaction start until reaches server to be broadcasted\",\n          tags: [:tenant],\n          reporter_options: [peep_bucket_calculator: BroadcastFromDatabase.Buckets]\n        ),\n        distribution(\n          [:realtime, :tenants, :broadcast_from_database, :latency_inserted_at],\n          event_name: [:realtime, :tenants, :broadcast_from_database],\n          measurement: :latency_inserted_at,\n          unit: {:microsecond, :millisecond},\n          description: \"Latency of database inserted_at until reaches server to be broadcasted\",\n          tags: [:tenant],\n          reporter_options: [peep_bucket_calculator: BroadcastFromDatabase.Buckets]\n        ),\n        distribution(\n          [:realtime, :tenants, :replay],\n          event_name: [:realtime, :tenants, :replay],\n          measurement: :latency,\n          unit: :millisecond,\n          description: \"Latency of broadcast replay\",\n          tags: [:tenant],\n          reporter_options: [peep_bucket_calculator: Replay.Buckets]\n        )\n      ]\n    )\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/prom_ex/plugins/tenant_global.ex",
    "content": "defmodule Realtime.PromEx.Plugins.TenantGlobal do\n  @moduledoc \"\"\"\n  Global aggregated variants of per-tenant metrics.\n\n  Subscribes to the same telemetry events as the Tenant plugin but records\n  metrics without the tenant tag, enabling cluster-wide aggregation.\n  These live on the global endpoint (/metrics) for high-priority scraping.\n  \"\"\"\n\n  use PromEx.Plugin\n  alias Realtime.PromEx.Plugins.Tenant\n  alias Realtime.Telemetry\n  alias Realtime.UsersCounter\n\n  @global_connections_event [:prom_ex, :plugin, :realtime, :connections, :global]\n\n  @impl true\n  def polling_metrics(opts) do\n    poll_rate = Keyword.get(opts, :poll_rate, 5_000)\n\n    [\n      Polling.build(\n        :realtime_global_connections,\n        poll_rate,\n        {__MODULE__, :execute_global_connection_metrics, []},\n        [\n          last_value(\n            [:realtime, :connections, :global, :connected],\n            event_name: @global_connections_event,\n            description: \"The node total count of connected clients across all tenants.\",\n            measurement: :connected\n          ),\n          last_value(\n            [:realtime, :connections, :global, :connected_cluster],\n            event_name: @global_connections_event,\n            description: \"The cluster total count of connected clients across all tenants.\",\n            measurement: :connected_cluster\n          )\n        ],\n        detach_on_error: false\n      )\n    ]\n  end\n\n  @impl true\n  def event_metrics(_opts) do\n    [\n      channel_global_events(),\n      payload_global_size_metrics()\n    ]\n  end\n\n  def execute_global_connection_metrics do\n    cluster_counts = UsersCounter.tenant_counts()\n    local_tenant_counts = UsersCounter.local_tenant_counts()\n\n    connected = local_tenant_counts |> Map.values() |> Enum.sum()\n    connected_cluster = cluster_counts |> Map.values() |> Enum.sum()\n\n    Telemetry.execute(\n      @global_connections_event,\n      %{connected: connected, connected_cluster: connected_cluster},\n      %{}\n    )\n  end\n\n  defp payload_global_size_metrics do\n    Event.build(\n      :realtime_global_payload_size_metrics,\n      [\n        distribution(\n          [:realtime, :payload, :size],\n          event_name: [:realtime, :tenants, :payload, :size],\n          measurement: :size,\n          description: \"Global payload size across all tenants\",\n          tags: [:message_type],\n          unit: :byte,\n          reporter_options: [peep_bucket_calculator: Tenant.PayloadSize.Buckets]\n        )\n      ]\n    )\n  end\n\n  defp channel_global_events do\n    Event.build(\n      :realtime_global_channel_event_metrics,\n      [\n        sum(\n          [:realtime, :channel, :global, :events],\n          event_name: [:realtime, :rate_counter, :channel, :events],\n          measurement: :sum,\n          description: \"Global sum of messages sent on a Realtime Channel.\"\n        ),\n        sum(\n          [:realtime, :channel, :global, :presence_events],\n          event_name: [:realtime, :rate_counter, :channel, :presence_events],\n          measurement: :sum,\n          description: \"Global sum of presence messages sent on a Realtime Channel.\"\n        ),\n        sum(\n          [:realtime, :channel, :global, :db_events],\n          event_name: [:realtime, :rate_counter, :channel, :db_events],\n          measurement: :sum,\n          description: \"Global sum of db messages sent on a Realtime Channel.\"\n        ),\n        sum(\n          [:realtime, :channel, :global, :joins],\n          event_name: [:realtime, :rate_counter, :channel, :joins],\n          measurement: :sum,\n          description: \"Global sum of Realtime Channel joins.\"\n        ),\n        sum(\n          [:realtime, :channel, :global, :input_bytes],\n          event_name: [:realtime, :channel, :input_bytes],\n          description: \"Global sum of input bytes sent on sockets.\",\n          measurement: :size\n        ),\n        sum(\n          [:realtime, :channel, :global, :output_bytes],\n          event_name: [:realtime, :channel, :output_bytes],\n          description: \"Global sum of output bytes sent on sockets.\",\n          measurement: :size\n        ),\n        counter(\n          [:realtime, :channel, :global, :error],\n          event_name: [:realtime, :channel, :error],\n          measurement: :code,\n          tags: [:code],\n          description: \"Global count of errors in Realtime channel initialization.\"\n        )\n      ]\n    )\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/prom_ex/plugins/tenants.ex",
    "content": "defmodule Realtime.PromEx.Plugins.Tenants do\n  @moduledoc false\n\n  use PromEx.Plugin\n\n  alias PromEx.MetricTypes.Event\n  alias Realtime.Tenants.Connect\n\n  require Logger\n\n  defmodule Buckets do\n    @moduledoc false\n    use Peep.Buckets.Custom, buckets: [10, 250, 5000, 15_000]\n  end\n\n  @event_connected [:prom_ex, :plugin, :realtime, :tenants, :connected]\n\n  @impl true\n  def event_metrics(_) do\n    Event.build(:realtime, [\n      distribution(\n        [:realtime, :global, :rpc],\n        event_name: [:realtime, :rpc],\n        description: \"Global Latency of rpc calls\",\n        measurement: :latency,\n        unit: {:microsecond, :millisecond},\n        tags: [:success, :mechanism],\n        reporter_options: [peep_bucket_calculator: Buckets]\n      )\n    ])\n  end\n\n  @impl true\n  def polling_metrics(opts) do\n    poll_rate = Keyword.get(opts, :poll_rate)\n\n    [\n      Polling.build(\n        :realtime_tenants_events,\n        poll_rate,\n        {__MODULE__, :execute_metrics, []},\n        [\n          last_value(\n            [:realtime, :tenants, :connected],\n            event_name: @event_connected,\n            description: \"The total count of connected tenants.\",\n            measurement: :connected\n          )\n        ],\n        detach_on_error: false\n      )\n    ]\n  end\n\n  def execute_metrics do\n    connected =\n      if Enum.member?(:syn.node_scopes(), Connect),\n        do: :syn.local_registry_count(Connect),\n        else: -1\n\n    execute_metrics(@event_connected, %{connected: connected})\n  end\n\n  defp execute_metrics(event, metrics) do\n    :telemetry.execute(event, metrics, %{})\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/prom_ex.ex",
    "content": "defmodule Realtime.PromEx do\n  alias Realtime.PromEx.Plugins.Distributed\n  alias Realtime.PromEx.Plugins.GenRpc\n  alias Realtime.PromEx.Plugins.OsMon\n  alias Realtime.PromEx.Plugins.Phoenix\n  alias Realtime.PromEx.Plugins.TenantGlobal\n  alias Realtime.PromEx.Plugins.Tenants\n\n  @moduledoc \"\"\"\n  PromEx configuration for global metrics (BEAM, OS, Phoenix, distributed infrastructure).\n\n  These are higher-priority metrics. Configure your Victoria Metrics scrape interval\n  lower compared to the tenant metrics endpoint.\n\n  Exposes metrics via `/metrics` and `/metrics/:region`.\n\n  Be sure to add the following to finish setting up PromEx:\n\n  1. Update your configuration (config.exs, dev.exs, prod.exs, releases.exs, etc) to\n     configure the necessary bit of PromEx. Be sure to check out `PromEx.Config` for\n     more details regarding configuring PromEx:\n     ```\n     config :realtime, Realtime.PromEx,\n       disabled: false,\n       manual_metrics_start_delay: :no_delay,\n       drop_metrics_groups: [],\n       grafana: :disabled,\n       metrics_server: :disabled\n     ```\n\n  2. Add this module to your application supervision tree. It should be one of the first\n     things that is started so that no Telemetry events are missed. For example, if PromEx\n     is started after your Repo module, you will miss Ecto's init events and the dashboards\n     will be missing some data points:\n     ```\n     def start(_type, _args) do\n       children = [\n         Realtime.PromEx,\n\n         ...\n       ]\n\n       ...\n     end\n     ```\n\n  3. Update your `endpoint.ex` file to expose your metrics (or configure a standalone\n     server using the `:metrics_server` config options). Be sure to put this plug before\n     your `Plug.Telemetry` entry so that you can avoid having calls to your `/metrics`\n     endpoint create their own metrics and logs which can pollute your logs/metrics given\n     that Prometheus will scrape at a regular interval and that can get noisy:\n     ```\n     defmodule RealtimeWeb.Endpoint do\n       use Phoenix.Endpoint, otp_app: :realtime\n\n       ...\n\n       plug PromEx.Plug, prom_ex_module: Realtime.PromEx\n\n       ...\n     end\n     ```\n\n  4. Update the list of plugins in the `plugins/0` function return list to reflect your\n     application's dependencies. Also update the list of dashboards that are to be uploaded\n     to Grafana in the `dashboards/0` function.\n  \"\"\"\n\n  use PromEx, otp_app: :realtime\n\n  alias PromEx.Plugins\n\n  defmodule Store do\n    @moduledoc false\n    # Custom store to set global tags\n\n    @behaviour PromEx.Storage\n\n    @impl true\n    def scrape(name) do\n      Peep.get_all_metrics(name)\n      |> Realtime.Monitoring.Prometheus.export()\n    end\n\n    @impl true\n    def child_spec(name, metrics) do\n      Peep.child_spec(\n        name: name,\n        metrics: metrics,\n        global_tags: Application.get_env(:realtime, :metrics_tags, %{}),\n        storage: {Realtime.Monitoring.Peep.Partitioned, 4}\n      )\n    end\n  end\n\n  @impl true\n  def plugins do\n    poll_rate = Application.get_env(:realtime, :prom_poll_rate)\n\n    [\n      {Plugins.Beam, poll_rate: poll_rate, metric_prefix: [:beam]},\n      {Phoenix, router: RealtimeWeb.Router, poll_rate: poll_rate, metric_prefix: [:phoenix]},\n      {OsMon, poll_rate: poll_rate},\n      {Tenants, poll_rate: poll_rate},\n      {TenantGlobal, poll_rate: poll_rate},\n      {Distributed, poll_rate: poll_rate},\n      {GenRpc, poll_rate: poll_rate}\n    ]\n  end\n\n  @impl true\n  def dashboard_assigns do\n    [\n      datasource_id: \"YOUR_PROMETHEUS_DATASOURCE_ID\"\n    ]\n  end\n\n  @impl true\n  def dashboards do\n    [\n      # PromEx built in Grafana dashboards\n      # {:prom_ex, \"application.json\"},\n      # {:prom_ex, \"beam.json\"},\n      # {:prom_ex, \"phoenix.json\"}\n      # {:prom_ex, \"ecto.json\"},\n      # {:prom_ex, \"oban.json\"},\n      # {:prom_ex, \"phoenix_live_view.json\"}\n\n      # Add your dashboard definitions here with the format: {:otp_app, \"path_in_priv\"}\n      # {:realtime, \"/grafana_dashboards/user_metrics.json\"}\n    ]\n  end\n\n  def get_global_metrics do\n    metrics = PromEx.get_metrics(Realtime.PromEx)\n\n    Realtime.PromEx.__ets_cron_flusher_name__()\n    |> PromEx.ETSCronFlusher.defer_ets_flush()\n\n    metrics\n  end\n\n  @doc deprecated: \"Use get_global_metrics/0 instead\"\n  def get_metrics, do: get_global_metrics()\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/prometheus.ex",
    "content": "# Based on https://github.com/rkallos/peep/blob/708546ed069aebdf78ac1f581130332bd2e8b5b1/lib/peep/prometheus.ex\ndefmodule Realtime.Monitoring.Prometheus do\n  @moduledoc \"\"\"\n  Prometheus exporter module\n\n  Use a temporary ets table to cache formatted names and label values\n  \"\"\"\n\n  alias Telemetry.Metrics.{Counter, Distribution, LastValue, Sum}\n\n  def export(metrics) do\n    cache = :ets.new(:cache, [:set, :private, read_concurrency: false, write_concurrency: :auto])\n\n    result = [Enum.map(metrics, &format(&1, cache)), \"# EOF\\n\"]\n    :ets.delete(cache)\n    result\n  end\n\n  defp format({%Counter{}, _series} = metric, cache) do\n    format_standard(metric, \"counter\", cache)\n  end\n\n  defp format({%Sum{} = spec, _series} = metric, cache) do\n    format_standard(metric, spec.reporter_options[:prometheus_type] || \"counter\", cache)\n  end\n\n  defp format({%LastValue{} = spec, _series} = metric, cache) do\n    format_standard(metric, spec.reporter_options[:prometheus_type] || \"gauge\", cache)\n  end\n\n  defp format({%Distribution{} = metric, tagged_series}, cache) do\n    name = format_name(metric.name, cache)\n    help = [\"# HELP \", name, \" \", escape_help(metric.description)]\n    type = [\"# TYPE \", name, \" histogram\"]\n\n    distributions =\n      Enum.map(tagged_series, fn {tags, buckets} ->\n        format_distribution(name, tags, buckets, cache)\n      end)\n\n    [help, ?\\n, type, ?\\n, distributions]\n  end\n\n  defp format_distribution(name, tags, buckets, cache) do\n    has_labels? = not Enum.empty?(tags)\n\n    buckets_as_floats =\n      Map.drop(buckets, [:sum, :infinity])\n      |> Enum.map(fn {bucket_string, count} -> {String.to_float(bucket_string), count} end)\n      |> Enum.sort()\n\n    {prefix_sums, count} = prefix_sums(buckets_as_floats)\n\n    {labels_done, bucket_partial} =\n      if has_labels? do\n        labels = format_labels(tags, cache)\n        {[?{, labels, \"} \"], [name, \"_bucket{\", labels, \",le=\\\"\"]}\n      else\n        {?\\s, [name, \"_bucket{le=\\\"\"]}\n      end\n\n    samples =\n      prefix_sums\n      |> Enum.map(fn {upper_bound, count} ->\n        [bucket_partial, format_value(upper_bound), \"\\\"} \", Integer.to_string(count), ?\\n]\n      end)\n\n    sum = Map.get(buckets, :sum, 0)\n    inf = Map.get(buckets, :infinity, 0)\n\n    [\n      samples,\n      [bucket_partial, \"+Inf\\\"} \", Integer.to_string(count + inf), ?\\n],\n      [name, \"_sum\", labels_done, Integer.to_string(sum), ?\\n],\n      [name, \"_count\", labels_done, Integer.to_string(count + inf), ?\\n]\n    ]\n  end\n\n  defp format_standard({metric, series}, type, cache) do\n    name = format_name(metric.name, cache)\n    help = [\"# HELP \", name, \" \", escape_help(metric.description)]\n    type = [\"# TYPE \", name, \" \", to_string(type)]\n\n    samples =\n      Enum.map(series, fn {labels, value} ->\n        has_labels? = not Enum.empty?(labels)\n\n        if has_labels? do\n          [name, ?{, format_labels(labels, cache), ?}, \" \", format_value(value), ?\\n]\n        else\n          [name, \" \", format_value(value), ?\\n]\n        end\n      end)\n\n    [help, ?\\n, type, ?\\n, samples]\n  end\n\n  defp format_labels(labels, cache) do\n    labels\n    |> Enum.sort()\n    |> Enum.map_intersperse(?,, fn {k, v} -> [to_string(k), \"=\\\"\", escape(v, cache), ?\"] end)\n  end\n\n  defp format_name(name, cache) do\n    case :ets.lookup_element(cache, name, 2, nil) do\n      nil ->\n        result =\n          name\n          |> Enum.join(\"_\")\n          |> format_name_start()\n          |> IO.iodata_to_binary()\n\n        :ets.insert(cache, {name, result})\n        result\n\n      result ->\n        result\n    end\n  end\n\n  # Name must start with an ascii letter\n  defp format_name_start(<<h, rest::binary>>) when h not in ?A..?Z and h not in ?a..?z,\n    do: format_name_start(rest)\n\n  defp format_name_start(<<rest::binary>>),\n    do: format_name_rest(rest, <<>>)\n\n  # Otherwise only letters, numbers, or _\n  defp format_name_rest(<<h, rest::binary>>, acc)\n       when h in ?A..?Z or h in ?a..?z or h in ?0..?9 or h == ?_,\n       do: format_name_rest(rest, [acc, h])\n\n  defp format_name_rest(<<_, rest::binary>>, acc), do: format_name_rest(rest, acc)\n  defp format_name_rest(<<>>, acc), do: acc\n\n  defp format_value(true), do: \"1\"\n  defp format_value(false), do: \"0\"\n  defp format_value(nil), do: \"0\"\n  defp format_value(n) when is_integer(n), do: Integer.to_string(n)\n  defp format_value(f) when is_float(f), do: Float.to_string(f)\n\n  defp escape(nil, _cache), do: \"nil\"\n\n  defp escape(value, cache) do\n    case :ets.lookup_element(cache, value, 2, nil) do\n      nil ->\n        result =\n          value\n          |> safe_to_string()\n          |> do_escape(<<>>)\n          |> IO.iodata_to_binary()\n\n        :ets.insert(cache, {value, result})\n        result\n\n      result ->\n        result\n    end\n  end\n\n  defp safe_to_string(value) do\n    case String.Chars.impl_for(value) do\n      nil -> inspect(value)\n      _ -> to_string(value)\n    end\n  end\n\n  defp do_escape(<<?\\\", rest::binary>>, acc), do: do_escape(rest, [acc, ?\\\\, ?\\\"])\n  defp do_escape(<<?\\\\, rest::binary>>, acc), do: do_escape(rest, [acc, ?\\\\, ?\\\\])\n  defp do_escape(<<?\\n, rest::binary>>, acc), do: do_escape(rest, [acc, ?\\\\, ?n])\n  defp do_escape(<<h, rest::binary>>, acc), do: do_escape(rest, [acc, h])\n  defp do_escape(<<>>, acc), do: acc\n\n  defp escape_help(value) do\n    value\n    |> to_string()\n    |> escape_help(<<>>)\n  end\n\n  defp escape_help(<<?\\\\, rest::binary>>, acc), do: escape_help(rest, <<acc::binary, ?\\\\, ?\\\\>>)\n  defp escape_help(<<?\\n, rest::binary>>, acc), do: escape_help(rest, <<acc::binary, ?\\\\, ?n>>)\n  defp escape_help(<<h, rest::binary>>, acc), do: escape_help(rest, <<acc::binary, h>>)\n  defp escape_help(<<>>, acc), do: acc\n\n  defp prefix_sums(buckets), do: prefix_sums(buckets, [], 0)\n  defp prefix_sums([], acc, sum), do: {Enum.reverse(acc), sum}\n\n  defp prefix_sums([{bucket, count} | rest], acc, sum) do\n    new_sum = sum + count\n    new_bucket = {bucket, new_sum}\n    prefix_sums(rest, [new_bucket | acc], new_sum)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/monitoring/tenant_prom_ex.ex",
    "content": "defmodule Realtime.TenantPromEx do\n  alias Realtime.PromEx.Plugins.Channels\n  alias Realtime.PromEx.Plugins.Tenant\n\n  @moduledoc \"\"\"\n  PromEx configuration for tenant-level metrics.\n\n  These metrics are per-tenant and considered secondary priority for scraping.\n  Configure your Victoria Metrics scrape interval higher (e.g. 60s) compared\n  to the global metrics endpoint.\n\n  Exposes metrics via `/metrics/tenant` and `/metrics/:region/tenant`.\n  \"\"\"\n\n  use PromEx, otp_app: :realtime\n\n  @impl true\n  def plugins do\n    poll_rate = Application.get_env(:realtime, :prom_poll_rate)\n\n    [\n      {Tenant, poll_rate: poll_rate},\n      {Channels, poll_rate: poll_rate}\n    ]\n  end\n\n  def get_metrics do\n    metrics = PromEx.get_metrics(Realtime.TenantPromEx)\n\n    Realtime.TenantPromEx.__ets_cron_flusher_name__()\n    |> PromEx.ETSCronFlusher.defer_ets_flush()\n\n    metrics\n  end\nend\n"
  },
  {
    "path": "lib/realtime/nodes.ex",
    "content": "defmodule Realtime.Nodes do\n  @moduledoc \"\"\"\n  Handles common needs for :syn module operations\n  \"\"\"\n  require Logger\n  alias Realtime.Api.Tenant\n  alias Realtime.Tenants\n\n  @doc \"\"\"\n  Gets the node to launch the Postgres connection on for a tenant.\n  \"\"\"\n  @spec get_node_for_tenant(Tenant.t()) :: {:ok, node(), binary()} | {:error, term()}\n  def get_node_for_tenant(nil), do: {:error, :tenant_not_found}\n\n  def get_node_for_tenant(%Tenant{} = tenant) do\n    with region <- Tenants.region(tenant),\n         tenant_region <- platform_region_translator(region),\n         node <- launch_node(tenant_region, node(), tenant.external_id) do\n      {:ok, node, tenant_region}\n    end\n  end\n\n  @doc \"\"\"\n  Translates a region from a platform to the closest Supabase tenant region.\n\n  Region mapping can be customized via the REGION_MAPPING environment variable.\n  If not provided, uses the default hardcoded mapping.\n  \"\"\"\n  @spec platform_region_translator(String.t() | nil) :: nil | binary()\n  def platform_region_translator(nil), do: nil\n\n  def platform_region_translator(tenant_region) when is_binary(tenant_region) do\n    case Application.get_env(:realtime, :region_mapping) do\n      nil -> default_region_mapping(tenant_region)\n      mapping when is_map(mapping) -> Map.get(mapping, tenant_region)\n    end\n  end\n\n  # Private function with hardcoded defaults\n  defp default_region_mapping(tenant_region) do\n    case tenant_region do\n      \"ap-east-1\" -> \"ap-southeast-1\"\n      \"ap-northeast-1\" -> \"ap-southeast-1\"\n      \"ap-northeast-2\" -> \"ap-southeast-1\"\n      \"ap-south-1\" -> \"ap-southeast-1\"\n      \"ap-southeast-1\" -> \"ap-southeast-1\"\n      \"ap-southeast-2\" -> \"ap-southeast-2\"\n      \"ca-central-1\" -> \"us-east-1\"\n      \"eu-central-1\" -> \"eu-west-2\"\n      \"eu-central-2\" -> \"eu-west-2\"\n      \"eu-north-1\" -> \"eu-west-2\"\n      \"eu-west-1\" -> \"eu-west-2\"\n      \"eu-west-2\" -> \"eu-west-2\"\n      \"eu-west-3\" -> \"eu-west-2\"\n      \"sa-east-1\" -> \"us-east-1\"\n      \"us-east-1\" -> \"us-east-1\"\n      \"us-east-2\" -> \"us-east-1\"\n      \"us-west-1\" -> \"us-west-1\"\n      \"us-west-2\" -> \"us-west-1\"\n      _ -> nil\n    end\n  end\n\n  @doc \"\"\"\n  Lists the nodes in a region. Sorts by node name in case the list order\n  is unstable.\n  \"\"\"\n\n  @spec region_nodes(String.t() | nil) :: [atom()]\n  def region_nodes(region) when is_binary(region) do\n    :syn.members(RegionNodes, region)\n    |> Enum.map(fn {_pid, [node: node]} -> node end)\n    |> Enum.sort()\n  end\n\n  def region_nodes(nil), do: []\n\n  @doc \"\"\"\n  Picks a node from a region based on the provided key\n  \"\"\"\n  @spec node_from_region(String.t(), term()) :: {:ok, node} | {:error, :not_available}\n  def node_from_region(region, key) when is_binary(region) do\n    nodes = region_nodes(region)\n\n    case nodes do\n      [] ->\n        {:error, :not_available}\n\n      _ ->\n        member_count = Enum.count(nodes)\n        index = :erlang.phash2(key, member_count)\n\n        {:ok, Enum.fetch!(nodes, index)}\n    end\n  end\n\n  def node_from_region(_, _), do: {:error, :not_available}\n\n  @doc \"\"\"\n  Picks the node to launch the Postgres connection on.\n\n  Selection is deterministic within time buckets to prevent syn conflicts from\n  concurrent requests for the same tenant. Uses time-bucketed seeded random\n  selection to pick 2 candidate nodes, compares their loads, and picks the\n  least loaded one.\n\n  The time bucket approach ensures:\n  - Requests within same time window (default: 60s) pick same nodes → prevents conflicts\n  - Requests in different time windows pick different random nodes → better long-term distribution\n\n  If the uptime of the node is below the configured threshold for load balancing,\n  a consistent node is picked based on hashing the tenant ID.\n\n  If there are not two nodes in a region, the connection is established from\n  the `default` node given.\n  \"\"\"\n  @spec launch_node(String.t() | nil, atom(), String.t()) :: atom()\n  def launch_node(region, default, tenant_id) when is_binary(tenant_id) do\n    case region_nodes(region) do\n      [] ->\n        Logger.warning(\"Zero region nodes for #{region} using #{inspect(default)}\")\n        default\n\n      [single_node] ->\n        single_node\n\n      nodes ->\n        load_aware_node_picker(nodes, tenant_id)\n    end\n  end\n\n  @node_selection_time_bucket_seconds Application.compile_env(\n                                        :realtime,\n                                        :node_selection_time_bucket_seconds,\n                                        60\n                                      )\n\n  defp load_aware_node_picker(regions_nodes, tenant_id) when is_binary(tenant_id) do\n    case regions_nodes do\n      nodes ->\n        node_count = length(nodes)\n\n        {node1, node2} = two_random_nodes(tenant_id, nodes, node_count)\n\n        # Compare loads and pick least loaded\n        load1 = node_load(node1)\n        load2 = node_load(node2)\n\n        if is_number(load1) and is_number(load2) do\n          if load1 <= load2, do: node1, else: node2\n        else\n          # Fallback to consistently picking a node if load data is not available\n          index = :erlang.phash2(tenant_id, node_count)\n          Enum.fetch!(nodes, index)\n        end\n    end\n  end\n\n  defp two_random_nodes(tenant_id, nodes, node_count) do\n    # Get current time bucket (unix timestamp / bucket_size)\n    time_bucket = div(System.system_time(:second), @node_selection_time_bucket_seconds)\n\n    # Seed the RNG without storing into the process dictionary\n    seed_value = :erlang.phash2({tenant_id, time_bucket})\n    rand_state = :rand.seed_s(:exsss, seed_value)\n\n    {id1, rand_state2} = :rand.uniform_s(node_count, rand_state)\n    {id2, _rand_state3} = :rand.uniform_s(node_count, rand_state2)\n\n    # Ensure id2 is different from id1 when multiple nodes available\n    id2 =\n      if id1 == id2 and node_count > 1 do\n        # Pick next node (wraps around using rem)\n        rem(id1, node_count) + 1\n      else\n        id2\n      end\n\n    node1 = Enum.at(nodes, id1 - 1)\n    node2 = Enum.at(nodes, id2 - 1)\n    {node1, node2}\n  end\n\n  @doc \"\"\"\n  Gets the node load for a node either locally or remotely. Returns {:error, :not_enough_data} if the node has not been running for long enough to get reliable metrics.\n  \"\"\"\n  @spec node_load(atom()) :: integer() | {:error, :not_enough_data}\n  def node_load(node) when node() == node do\n    if uptime_ms() < Application.fetch_env!(:realtime, :node_balance_uptime_threshold_in_ms),\n      do: {:error, :not_enough_data},\n      else: :cpu_sup.avg5()\n  end\n\n  def node_load(node) when node() != node, do: Realtime.GenRpc.call(node, __MODULE__, :node_load, [node], [])\n\n  @doc \"\"\"\n  Gets a short node name from a node name when a node name looks like `realtime-prod@fdaa:0:cc:a7b:b385:83c3:cfe3:2`\n\n  ## Examples\n\n      iex> node = Node.self()\n      iex> Realtime.Helpers.short_node_id_from_name(node)\n      \"nohost\"\n\n      iex> node = :\"realtime-prod@fdaa:0:cc:a7b:b385:83c3:cfe3:2\"\n      iex> Realtime.Helpers.short_node_id_from_name(node)\n      \"83c3cfe3\"\n\n      iex> node = :\"pink@127.0.0.1\"\n      iex> Realtime.Helpers.short_node_id_from_name(node)\n      \"pink@127.0.0.1\"\n\n      iex> node = :\"pink@10.0.1.1\"\n      iex> Realtime.Helpers.short_node_id_from_name(node)\n      \"10.0.1.1\"\n\n      iex> node = :\"realtime@host.name.internal\"\n      iex> Realtime.Helpers.short_node_id_from_name(node)\n      \"host.name.internal\"\n  \"\"\"\n\n  @spec short_node_id_from_name(atom()) :: String.t()\n  def short_node_id_from_name(name) when is_atom(name) do\n    [_, host] = name |> Atom.to_string() |> String.split(\"@\", parts: 2)\n\n    case String.split(host, \":\", parts: 8) do\n      [_, _, _, _, _, one, two, _] ->\n        one <> two\n\n      [\"127.0.0.1\"] ->\n        Atom.to_string(name)\n\n      _other ->\n        host\n    end\n  end\n\n  @spec all_node_regions() :: [String.t()]\n  @doc \"List all the regions where nodes can be launched\"\n  def all_node_regions(), do: :syn.group_names(RegionNodes)\n\n  defp uptime_ms do\n    start_time = :erlang.system_info(:start_time)\n    now = :erlang.monotonic_time()\n    :erlang.convert_time_unit(now - start_time, :native, :millisecond)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/operations.ex",
    "content": "defmodule Realtime.Operations do\n  @moduledoc \"\"\"\n  Support operations for Realtime.\n  \"\"\"\n  alias Realtime.Rpc\n\n  @doc \"\"\"\n  Ensures connected users are connected to the closest region by killing and restart the connection process.\n  \"\"\"\n  def rebalance do\n    Enum.reduce(:syn.group_names(:users), 0, fn tenant, acc ->\n      scope = Realtime.Syn.PostgresCdc.scope(tenant)\n\n      case :syn.lookup(scope, tenant) do\n        {pid, %{region: region}} ->\n          platform_region = Realtime.Nodes.platform_region_translator(region)\n          current_node = node(pid)\n\n          case Realtime.Nodes.launch_node(platform_region, false, tenant) do\n            ^current_node -> acc\n            _ -> stop_user_tenant_process(tenant, platform_region, acc)\n          end\n\n        _ ->\n          acc\n      end\n    end)\n  end\n\n  @doc \"\"\"\n  Kills all connections to a tenant database in all connected nodes\n  \"\"\"\n  @spec kill_connections_to_tenant_id_in_all_nodes(String.t(), atom()) :: list()\n  def kill_connections_to_tenant_id_in_all_nodes(tenant_id, reason \\\\ :normal) do\n    [node() | Node.list()]\n    |> Task.async_stream(\n      fn node ->\n        Rpc.enhanced_call(node, __MODULE__, :kill_connections_to_tenant_id, [tenant_id, reason])\n      end,\n      timeout: 5000\n    )\n    |> Enum.map(& &1)\n  end\n\n  @doc \"\"\"\n  Kills all connections to a tenant database in the current node\n  \"\"\"\n  @spec kill_connections_to_tenant_id(String.t(), atom()) :: :ok\n  def kill_connections_to_tenant_id(tenant_id, reason) do\n    Logger.metadata(external_id: tenant_id, project: tenant_id)\n\n    pids_to_kill =\n      for pid <- Process.list(),\n          info = Process.info(pid),\n          dict = Keyword.get(info, :dictionary, []),\n          match?({DBConnection.Connection, :init, 1}, dict[:\"$initial_call\"]),\n          Keyword.get(dict, :\"$logger_metadata$\")[:external_id] == tenant_id,\n          links = Keyword.get(info, :links) do\n        links\n        |> Enum.filter(fn pid ->\n          is_pid(pid) &&\n            pid |> Process.info() |> Keyword.get(:dictionary, []) |> Keyword.get(:\"$initial_call\") ==\n              {:supervisor, DBConnection.ConnectionPool.Pool, 1}\n        end)\n      end\n\n    Enum.each(pids_to_kill, &Process.exit(&1, reason))\n  end\n\n  @doc \"\"\"\n  Kills all Ecto.Migration.Runner processes that are linked only to Ecto.MigratorSupervisor\n  \"\"\"\n  @spec dirty_terminate_runners :: list()\n  def dirty_terminate_runners do\n    Ecto.MigratorSupervisor\n    |> DynamicSupervisor.which_children()\n    |> Enum.reduce([], fn\n      {_, pid, :worker, [Ecto.Migration.Runner]}, acc ->\n        if length(Process.info(pid)[:links]) < 2 do\n          [{pid, Agent.stop(pid, :normal, 5_000)} | acc]\n        else\n          acc\n        end\n\n      _, acc ->\n        acc\n    end)\n  end\n\n  defp stop_user_tenant_process(tenant, platform_region, acc) do\n    Extensions.PostgresCdcRls.handle_stop(tenant, 5_000)\n    # credo:disable-for-next-line\n    IO.inspect({\"Stopped\", tenant, platform_region})\n    Process.sleep(1_500)\n    acc + 1\n  catch\n    kind, reason ->\n      # credo:disable-for-next-line\n      IO.inspect({\"Failed to stop\", tenant, kind, reason})\n  end\nend\n"
  },
  {
    "path": "lib/realtime/postgres_cdc.ex",
    "content": "defmodule Realtime.PostgresCdc do\n  @moduledoc false\n\n  require Logger\n\n  alias Realtime.Api.Tenant\n\n  @timeout 10_000\n  @extensions Application.compile_env(:realtime, :extensions)\n\n  defmodule Exception do\n    defexception message: \"PostgresCdc error!\"\n  end\n\n  def connect(module, opts) do\n    apply(module, :handle_connect, [opts])\n  end\n\n  def after_connect(module, connect_response, extension, params, tenant) do\n    apply(module, :handle_after_connect, [connect_response, extension, params, tenant])\n  end\n\n  def subscribe(module, pg_change_params, tenant, metadata) do\n    RealtimeWeb.Endpoint.subscribe(\"postgres_cdc_rls:\" <> tenant)\n    apply(module, :handle_subscribe, [pg_change_params, tenant, metadata])\n  end\n\n  @spec stop(module, Tenant.t(), pos_integer) :: :ok\n  def stop(module, tenant, timeout \\\\ @timeout) do\n    apply(module, :handle_stop, [tenant.external_id, timeout])\n  end\n\n  @doc \"\"\"\n  Stops all available drivers within a specified timeout.\n\n  Expects all handle_stop calls to return `:ok` within the `stop_timeout`.\n\n  We want all available drivers to stop within the `timeout`.\n  \"\"\"\n\n  @spec stop_all(Tenant.t(), pos_integer) :: :ok | :error\n  def stop_all(tenant, timeout \\\\ @timeout) do\n    count = Enum.count(available_drivers())\n    stop_timeout = Kernel.ceil(timeout / count)\n\n    stops = Enum.map(available_drivers(), fn module -> stop(module, tenant, stop_timeout) end)\n\n    case Enum.all?(stops, &(&1 == :ok)) do\n      true -> :ok\n      false -> :error\n    end\n  end\n\n  @spec available_drivers :: list\n  def available_drivers do\n    @extensions\n    |> Enum.filter(fn {_, e} -> e.type == :postgres_cdc end)\n    |> Enum.map(fn {_, e} -> e.driver end)\n  end\n\n  @spec filter_settings(binary(), list()) :: map()\n  def filter_settings(key, extensions) do\n    [cdc] = Enum.filter(extensions, fn e -> e.type == key end)\n\n    cdc.settings\n  end\n\n  @doc \"\"\"\n  Gets the extension module for a tenant.\n  \"\"\"\n\n  @spec driver(String.t()) :: {:ok, module()} | {:error, String.t()}\n  def driver(tenant_key) do\n    @extensions\n    |> Enum.filter(fn {_, %{key: key}} -> tenant_key == key end)\n    |> case do\n      [{_, %{driver: driver}}] -> {:ok, driver}\n      _ -> {:error, \"No driver found for key #{tenant_key}\"}\n    end\n  end\n\n  @callback handle_connect(any()) :: {:ok, any()} | nil\n  @callback handle_after_connect(any(), any(), any(), tenant_id :: String.t()) ::\n              {:ok, any()} | {:error, any()} | {:error, any(), any()}\n  @callback handle_subscribe(any(), any(), any()) :: :ok\n  @callback handle_stop(any(), any()) :: any()\nend\n"
  },
  {
    "path": "lib/realtime/rate_counter/dynamic_supervisor.ex",
    "content": "defmodule Realtime.RateCounter.DynamicSupervisor do\n  @moduledoc \"\"\"\n  Dynamic Supervisor to spin up `RateCounter`s as needed.\n  \"\"\"\n\n  use DynamicSupervisor\n\n  @spec start_link(list()) :: {:error, any} | {:ok, pid}\n  def start_link(args) do\n    DynamicSupervisor.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  @impl true\n  def init(_args) do\n    DynamicSupervisor.init(strategy: :one_for_one)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/rate_counter/rate_counter.ex",
    "content": "defmodule Realtime.RateCounter do\n  @moduledoc \"\"\"\n  Start a RateCounter for any Erlang term.\n\n  These rate counters use the GenCounter module.\n  Start your RateCounter here and increment it with a `GenCounter.add/1` call, for example.\n  \"\"\"\n\n  use GenServer\n\n  require Logger\n\n  alias Realtime.GenCounter\n  alias Realtime.RateCounter\n  alias Realtime.Telemetry\n\n  defmodule Args do\n    @moduledoc false\n    @type t :: %__MODULE__{id: term(), opts: keyword}\n    defstruct id: nil, opts: []\n  end\n\n  @idle_shutdown :timer.minutes(10)\n  @tick :timer.seconds(1)\n  @max_bucket_len 60\n  @cache __MODULE__\n  @app_name Mix.Project.config()[:app]\n\n  defstruct id: nil,\n            avg: 0.0,\n            sum: 0,\n            bucket: [],\n            max_bucket_len: @max_bucket_len,\n            tick: @tick,\n            tick_ref: nil,\n            idle_shutdown: @idle_shutdown,\n            idle_shutdown_ref: nil,\n            limit: %{log: false},\n            telemetry: %{emit: false}\n\n  @type t :: %__MODULE__{\n          id: term(),\n          avg: float(),\n          sum: non_neg_integer(),\n          bucket: list(),\n          max_bucket_len: integer(),\n          tick: integer(),\n          tick_ref: reference() | nil,\n          idle_shutdown: integer() | :infinity,\n          idle_shutdown_ref: reference() | nil,\n          limit:\n            %{log: false}\n            | %{\n                log: true,\n                value: integer(),\n                measurement: :sum | :avg,\n                triggered: boolean(),\n                log_fn: (-> term())\n              },\n          telemetry:\n            %{emit: false}\n            | %{\n                emit: true,\n                event_name: :telemetry.event_name(),\n                measurements: :telemetry.event_measurements(),\n                metadata: :telemetry.event_metadata()\n              }\n        }\n\n  @spec start_link([keyword()]) :: {:ok, pid()} | {:error, {:already_started, pid()}}\n  def start_link(args) do\n    id = Keyword.get(args, :id)\n    if !id, do: raise(\"Supply an identifier to start a counter!\")\n\n    GenServer.start_link(__MODULE__, args,\n      name: {:via, Registry, {Realtime.Registry.Unique, {__MODULE__, :rate_counter, id}}}\n    )\n  end\n\n  @doc \"\"\"\n  Starts a new RateCounter under a DynamicSupervisor\n  \"\"\"\n\n  @spec new(Args.t(), keyword) :: DynamicSupervisor.on_start_child()\n  def new(%Args{id: id} = args, opts \\\\ []) do\n    opts = [id: id] ++ Keyword.merge(args.opts, opts)\n\n    DynamicSupervisor.start_child(RateCounter.DynamicSupervisor, %{\n      id: id,\n      start: {__MODULE__, :start_link, [opts]},\n      restart: :transient\n    })\n  end\n\n  @doc \"Publish an update to the RateCounter with the given id\"\n  @spec publish_update(term()) :: :ok\n  def publish_update(id), do: Phoenix.PubSub.broadcast(Realtime.PubSub, update_topic(id), :update)\n\n  @doc \"\"\"\n  Gets the state of the RateCounter.\n\n  Automatically starts the RateCounter if it does not exist or if it\n  has stopped due to idleness.\n  \"\"\"\n  @spec get(term() | Args.t()) :: {:ok, t} | {:error, term()}\n  def get(%Args{id: id} = args) do\n    case do_get(id) do\n      {:ok, state} ->\n        {:ok, state}\n\n      {:error, :not_found} ->\n        case new(args) do\n          {:ok, _} -> do_get(id)\n          {:error, {:already_started, _}} -> do_get(id)\n          {:error, reason} -> {:error, reason}\n        end\n    end\n  end\n\n  defp do_get(id) do\n    case Cachex.get(@cache, id) do\n      {:ok, nil} -> {:error, :not_found}\n      {:ok, state} -> {:ok, state}\n    end\n  end\n\n  defp update_topic(id), do: \"rate_counter:#{inspect(id)}\"\n\n  @impl true\n  def init(args) do\n    id = Keyword.fetch!(args, :id)\n    telem_opts = Keyword.get(args, :telemetry)\n    every = Keyword.get(args, :tick, @tick)\n    max_bucket_len = Keyword.get(args, :max_bucket_len, @max_bucket_len)\n    idle_shutdown_ms = Keyword.get(args, :idle_shutdown, @idle_shutdown)\n    limit_opts = Keyword.get(args, :limit)\n\n    Logger.info(\"Starting #{__MODULE__} for: #{inspect(id)}\")\n\n    # Always reset the counter in case the counter had already accumulated without\n    # a RateCounter running to calculate avg and buckets\n    GenCounter.reset(id)\n\n    :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, update_topic(id))\n\n    telemetry =\n      if telem_opts do\n        Logger.metadata(telem_opts.metadata)\n\n        %{\n          emit: true,\n          event_name: [@app_name] ++ [:rate_counter] ++ telem_opts.event_name,\n          measurements: Map.merge(%{sum: 0}, telem_opts.measurements),\n          metadata: Map.merge(%{id: id}, telem_opts.metadata)\n        }\n      else\n        %{emit: false}\n      end\n\n    limit =\n      if limit_opts do\n        %{\n          log: true,\n          value: Keyword.fetch!(limit_opts, :value),\n          measurement: Keyword.fetch!(limit_opts, :measurement),\n          log_fn: Keyword.fetch!(limit_opts, :log_fn),\n          triggered: false\n        }\n      else\n        %{log: false}\n      end\n\n    ticker = tick(0)\n\n    idle_shutdown_ref =\n      if idle_shutdown_ms != :infinity, do: shutdown_after(idle_shutdown_ms), else: nil\n\n    state = %__MODULE__{\n      id: id,\n      tick: every,\n      tick_ref: ticker,\n      max_bucket_len: max_bucket_len,\n      idle_shutdown: idle_shutdown_ms,\n      idle_shutdown_ref: idle_shutdown_ref,\n      telemetry: telemetry,\n      limit: limit\n    }\n\n    Cachex.put!(@cache, id, state)\n\n    {:ok, state}\n  end\n\n  @impl true\n  def handle_info(:tick, state) do\n    Process.cancel_timer(state.tick_ref)\n    count = GenCounter.reset(state.id)\n\n    if state.telemetry.emit and count > 0,\n      do:\n        Telemetry.execute(\n          state.telemetry.event_name,\n          %{state.telemetry.measurements | sum: count},\n          state.telemetry.metadata\n        )\n\n    bucket = [count | state.bucket] |> Enum.take(state.max_bucket_len)\n    bucket_len = Enum.count(bucket)\n\n    sum = Enum.sum(bucket)\n    avg = sum / bucket_len\n\n    state = %{state | bucket: bucket, sum: sum, avg: avg}\n\n    state = maybe_trigger_limit(state)\n    tick(state.tick)\n\n    Cachex.put!(@cache, state.id, state)\n\n    {:noreply, state}\n  end\n\n  def handle_info(:idle_shutdown, state) do\n    if Enum.all?(state.bucket, &(&1 == 0)) do\n      # All the buckets are empty, so we can assume this RateCounter has not been useful recently\n      Logger.warning(\"#{__MODULE__} idle_shutdown reached for: #{inspect(state.id)}\")\n      shutdown(state)\n    else\n      Process.cancel_timer(state.idle_shutdown_ref)\n      idle_shutdown_ref = shutdown_after(state.idle_shutdown)\n      {:noreply, %{state | idle_shutdown_ref: idle_shutdown_ref}}\n    end\n  end\n\n  def handle_info(:update, state) do\n    # When we get an update message we shutdown so that this RateCounter\n    # can be restarted with new parameters\n    shutdown(state)\n  end\n\n  def handle_info(_, state), do: {:noreply, state}\n\n  defp shutdown(state) do\n    GenCounter.delete(state.id)\n    # We are expiring in the near future instead of deleting so that\n    # The process dies before the cache information disappears\n    # If we were using Cachex.delete instead then the following rare scenario would be possible:\n    # * RateCounter.get/2 is called;\n    # * Cache was deleted but the process has not stopped yet;\n    # * RateCounter.get/2 will then try to start a new RateCounter but the supervisor will return :already_started;\n    # * Process finally stops;\n    # * The cache is still empty because no new process was started causing an error\n\n    Cachex.expire(@cache, state.id, :timer.seconds(1))\n    {:stop, :normal, state}\n  end\n\n  defp maybe_trigger_limit(%{limit: %{log: false}} = state), do: state\n\n  defp maybe_trigger_limit(%{limit: %{triggered: true, measurement: measurement}} = state) do\n    # Limit has been triggered, but we need to check if it is still above the limit\n    if Map.fetch!(state, measurement) < state.limit.value do\n      %{state | limit: %{state.limit | triggered: false}}\n    else\n      # Limit is still above the threshold, so we keep the state as is\n      state\n    end\n  end\n\n  defp maybe_trigger_limit(%{limit: %{measurement: measurement}} = state) do\n    if Map.fetch!(state, measurement) >= state.limit.value do\n      state.limit.log_fn.()\n\n      %{state | limit: %{state.limit | triggered: true}}\n    else\n      state\n    end\n  end\n\n  defp tick(every) do\n    Process.send_after(self(), :tick, every)\n  end\n\n  defp shutdown_after(ms) do\n    Process.send_after(self(), :idle_shutdown, ms)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/release.ex",
    "content": "defmodule Realtime.Release do\n  @moduledoc \"\"\"\n  Used for executing DB release tasks when run in production without Mix\n  installed.\n  \"\"\"\n  @app :realtime\n\n  def migrate do\n    load_app()\n\n    for repo <- repos() do\n      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))\n    end\n  end\n\n  def rollback(repo, version) do\n    load_app()\n    {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))\n  end\n\n  def seeds(repo) do\n    load_app()\n    {:ok, _} = Application.ensure_all_started(:realtime)\n\n    {:ok, {:ok, _}, _} =\n      Ecto.Migrator.with_repo(repo, fn _repo ->\n        seeds_file = \"#{:code.priv_dir(@app)}/repo/seeds.exs\"\n\n        if File.regular?(seeds_file) do\n          {:ok, Code.eval_file(seeds_file)}\n        else\n          {:error, \"Seeds file not found.\"}\n        end\n      end)\n  end\n\n  defp repos do\n    Application.fetch_env!(@app, :ecto_repos)\n  end\n\n  defp load_app do\n    Application.load(@app)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/repo.ex",
    "content": "defmodule Realtime.Repo do\n  use Ecto.Repo,\n    otp_app: :realtime,\n    adapter: Ecto.Adapters.Postgres\n\n  def with_dynamic_repo(config, callback) do\n    default_dynamic_repo = get_dynamic_repo()\n    {:ok, repo} = [name: nil, pool_size: 2] |> Keyword.merge(config) |> Realtime.Repo.start_link()\n\n    try do\n      put_dynamic_repo(repo)\n      callback.(repo)\n    after\n      put_dynamic_repo(default_dynamic_repo)\n      Supervisor.stop(repo)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/repo_replica.ex",
    "content": "defmodule Realtime.Repo.Replica do\n  @moduledoc \"\"\"\n  Generates a read-only replica repo for the region specified in config/runtime.exs.\n  \"\"\"\n  require Logger\n\n  use Ecto.Repo,\n    otp_app: :realtime,\n    adapter: Ecto.Adapters.Postgres,\n    read_only: true\n\n  @replicas_fly %{\n    \"sea\" => Realtime.Repo.Replica.SJC,\n    \"sjc\" => Realtime.Repo.Replica.SJC,\n    \"gru\" => Realtime.Repo.Replica.IAD,\n    \"iad\" => Realtime.Repo.Replica.IAD,\n    \"sin\" => Realtime.Repo.Replica.SIN,\n    \"maa\" => Realtime.Repo.Replica.SIN,\n    \"syd\" => Realtime.Repo.Replica.SIN,\n    \"lhr\" => Realtime.Repo.Replica.FRA,\n    \"fra\" => Realtime.Repo.Replica.FRA\n  }\n\n  @replicas_aws %{\n    \"ap-southeast-1\" => Realtime.Repo.Replica.Singapore,\n    \"ap-southeast-2\" => Realtime.Repo.Replica.Singapore,\n    \"eu-west-2\" => Realtime.Repo.Replica.London,\n    \"us-east-1\" => Realtime.Repo.Replica.NorthVirginia,\n    \"us-west-2\" => Realtime.Repo.Replica.Oregon,\n    \"us-west-1\" => Realtime.Repo.Replica.SanJose\n  }\n\n  @ast (quote do\n          use Ecto.Repo,\n            otp_app: :realtime,\n            adapter: Ecto.Adapters.Postgres,\n            read_only: true\n        end)\n\n  @doc \"\"\"\n  Returns the replica repo module for the region specified in config/runtime.exs.\n  \"\"\"\n  @spec replica() :: module()\n  def replica do\n    region = Application.get_env(:realtime, :region)\n    master_region = Application.get_env(:realtime, :master_region) || region\n    replica = configured_replica_module(region)\n    replica_conf = Application.get_env(:realtime, replica)\n\n    # Do not create module if replica isn't set or configuration is not present\n    cond do\n      is_nil(replica) ->\n        Realtime.Repo\n\n      is_nil(replica_conf) ->\n        Realtime.Repo\n\n      region == master_region ->\n        Realtime.Repo\n\n      true ->\n        # Check if module is present\n        case Code.ensure_compiled(replica) do\n          {:module, _} -> nil\n          _ -> {:module, _, _, _} = Module.create(replica, @ast, Macro.Env.location(__ENV__))\n        end\n\n        replica\n    end\n  end\n\n  defp configured_replica_module(region) do\n    main_replica_config = Application.get_env(:realtime, __MODULE__)\n\n    # If the main replica module is configured we don't bother with specific replica modules\n    if main_replica_config do\n      __MODULE__\n    else\n      replicas =\n        case Application.get_env(:realtime, :platform) do\n          :aws -> @replicas_aws\n          :fly -> @replicas_fly\n          _ -> %{}\n        end\n\n      Map.get(replicas, region)\n    end\n  end\n\n  if Mix.env() == :test do\n    def replicas_aws, do: @replicas_aws\n\n    def replicas_fly, do: @replicas_fly\n  end\nend\n"
  },
  {
    "path": "lib/realtime/rpc.ex",
    "content": "defmodule Realtime.Rpc do\n  @moduledoc \"\"\"\n  RPC module for Realtime with the intent of standardizing the RPC interface and collect telemetry\n  \"\"\"\n  use Realtime.Logs\n  alias Realtime.Telemetry\n\n  @doc \"\"\"\n  Calls external node using :rpc.call/5 and collects telemetry\n  \"\"\"\n  @spec call(atom(), atom(), atom(), any(), keyword()) :: any()\n  def call(node, mod, func, args, opts \\\\ []) do\n    timeout = Keyword.get(opts, :timeout, Application.get_env(:realtime, :rpc_timeout))\n    {latency, response} = :timer.tc(fn -> :rpc.call(node, mod, func, args, timeout) end)\n\n    Telemetry.execute(\n      [:realtime, :rpc],\n      %{latency: latency},\n      %{mod: mod, func: func, target_node: node, origin_node: node(), mechanism: :rpc, success: nil}\n    )\n\n    response\n  end\n\n  @doc \"\"\"\n  Calls external node using :erpc.call/5 and collects telemetry\n  \"\"\"\n  @spec enhanced_call(atom(), atom(), atom(), any(), keyword()) ::\n          {:ok, any()} | {:error, :rpc_error, term()} | {:error, term()}\n  def enhanced_call(node, mod, func, args \\\\ [], opts \\\\ []) do\n    timeout = Keyword.get(opts, :timeout, Application.get_env(:realtime, :rpc_timeout))\n    tenant_id = Keyword.get(opts, :tenant_id)\n\n    try do\n      with {latency, response} <- :timer.tc(fn -> :erpc.call(node, mod, func, args, timeout) end) do\n        case response do\n          {:ok, _} ->\n            Telemetry.execute(\n              [:realtime, :rpc],\n              %{latency: latency},\n              %{\n                mod: mod,\n                func: func,\n                target_node: node,\n                origin_node: node(),\n                success: true,\n                mechanism: :erpc\n              }\n            )\n\n            response\n\n          error ->\n            Telemetry.execute(\n              [:realtime, :rpc],\n              %{latency: latency},\n              %{\n                mod: mod,\n                func: func,\n                target_node: node,\n                origin_node: node(),\n                success: false,\n                mechanism: :erpc\n              }\n            )\n\n            error\n        end\n      end\n    catch\n      _, reason ->\n        reason =\n          case reason do\n            {_, reason} -> reason\n            {_, reason, _} -> reason\n          end\n\n        Telemetry.execute(\n          [:realtime, :rpc],\n          %{latency: 0},\n          %{\n            mod: mod,\n            func: func,\n            target_node: node,\n            origin_node: node(),\n            success: false,\n            mechanism: :erpc\n          }\n        )\n\n        log_error(\n          \"ErrorOnRpcCall\",\n          %{target: node, mod: mod, func: func, error: reason},\n          project: tenant_id,\n          external_id: tenant_id\n        )\n\n        {:error, :rpc_error, reason}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/signal_handler.ex",
    "content": "defmodule Realtime.SignalHandler do\n  @moduledoc false\n  @behaviour :gen_event\n  require Logger\n\n  @spec shutdown_in_progress? :: :ok | {:error, :shutdown_in_progress}\n  def shutdown_in_progress? do\n    case !!Application.get_env(:realtime, :shutdown_in_progress) do\n      true -> {:error, :shutdown_in_progress}\n      false -> :ok\n    end\n  end\n\n  @impl true\n  def init({%{handler_mod: _} = args, :ok}) do\n    {:ok, args}\n  end\n\n  @impl true\n  def handle_event(signal, %{handler_mod: handler_mod} = state) do\n    Logger.error(\"#{__MODULE__}: #{inspect(signal)} received\")\n\n    if signal == :sigterm do\n      Application.put_env(:realtime, :shutdown_in_progress, true)\n    end\n\n    handler_mod.handle_event(signal, state)\n  end\n\n  @impl true\n  defdelegate handle_info(info, state), to: :erl_signal_handler\n\n  @impl true\n  defdelegate handle_call(request, state), to: :erl_signal_handler\nend\n"
  },
  {
    "path": "lib/realtime/syn/postgres_cdc.ex",
    "content": "defmodule Realtime.Syn.PostgresCdc do\n  @moduledoc \"\"\"\n  Scope for the PostgresCdc module.\n  \"\"\"\n\n  @doc \"\"\"\n  Returns the scope for a given tenant id.\n  \"\"\"\n  @spec scope(String.t()) :: atom()\n  def scope(tenant_id) do\n    shards = Application.fetch_env!(:realtime, :postgres_cdc_scope_shards)\n    shard = :erlang.phash2(tenant_id, shards)\n    :\"realtime_postgres_cdc_#{shard}\"\n  end\n\n  def scopes() do\n    shards = Application.fetch_env!(:realtime, :postgres_cdc_scope_shards)\n    Enum.map(0..(shards - 1), fn shard -> :\"realtime_postgres_cdc_#{shard}\" end)\n  end\n\n  def syn_topic_prefix(), do: \"realtime_postgres_cdc_\"\n  def syn_topic(tenant_id), do: \"#{syn_topic_prefix()}#{tenant_id}\"\nend\n"
  },
  {
    "path": "lib/realtime/syn_handler.ex",
    "content": "defmodule Realtime.SynHandler do\n  @moduledoc \"\"\"\n  Custom defined Syn's callbacks\n  \"\"\"\n  require Logger\n  alias Realtime.Syn.PostgresCdc\n  alias Realtime.Tenants.Connect\n  alias RealtimeWeb.Endpoint\n\n  @behaviour :syn_event_handler\n\n  @postgres_cdc_scope_prefix PostgresCdc.syn_topic_prefix()\n\n  @impl true\n  def on_registry_process_updated(Connect, tenant_id, pid, %{conn: conn}, :normal) when is_pid(conn) do\n    # Update that a database connection is ready\n    Endpoint.local_broadcast(Connect.syn_topic(tenant_id), \"ready\", %{pid: pid, conn: conn})\n  end\n\n  def on_registry_process_updated(scope, tenant_id, _pid, meta, _reason) do\n    scope = Atom.to_string(scope)\n\n    case scope do\n      @postgres_cdc_scope_prefix <> _ ->\n        Endpoint.local_broadcast(PostgresCdc.syn_topic(tenant_id), \"ready\", meta)\n\n      _ ->\n        :ok\n    end\n  end\n\n  @impl true\n  def on_process_registered(scope, name, _pid, _meta, _reason) do\n    :telemetry.execute([:syn, scope, :registered], %{}, %{name: name})\n  end\n\n  @doc \"\"\"\n  When processes registered with :syn are unregistered, either manually or by stopping, this\n  callback is invoked.\n\n  Other processes can subscribe to these events via PubSub to respond to them.\n\n  We want to log conflict resolutions to know when more than one process on the cluster\n  was started, and subsequently stopped because :syn handled the conflict.\n  \"\"\"\n  @impl true\n  def on_process_unregistered(scope, name, pid, _meta, reason) do\n    :telemetry.execute([:syn, scope, :unregistered], %{}, %{name: name})\n\n    case Atom.to_string(scope) do\n      @postgres_cdc_scope_prefix <> _ = scope ->\n        Endpoint.local_broadcast(PostgresCdc.syn_topic(name), scope <> \"_down\", %{pid: pid, reason: reason})\n\n      _ ->\n        topic = topic(scope)\n        Endpoint.local_broadcast(topic <> \":\" <> name, topic <> \"_down\", %{pid: pid, reason: reason})\n    end\n\n    if reason == :syn_conflict_resolution,\n      do: log(\"#{scope} terminated due to syn conflict resolution: #{inspect(name)} #{inspect(pid)}\")\n\n    :ok\n  end\n\n  @doc \"\"\"\n  We try to keep the oldest process. If the time they were registered is exactly the same we use\n  their node names to decide.\n\n  The most important part is that both nodes must 100% of the time agree on the decision\n\n  We first send an exit with reason {:shutdown, :syn_conflict_resolution}\n  If it times out an exit with reason :kill that can't be trapped\n  \"\"\"\n  @impl true\n  def resolve_registry_conflict(mod, name, {pid1, _meta1, _time1}, {pid2, _meta2, _time2}) do\n    {pid_to_keep, pid_to_stop} = decide(pid1, pid2, name)\n\n    # Is this function running on the node that should stop?\n    if node(pid_to_stop) == node() do\n      log(\n        \"Resolving conflict on scope #{inspect(mod)} for name #{inspect(name)} {#{node(pid1)}, #{inspect(pid1)}} vs {#{node(pid2)}, #{inspect(pid2)}}, stop local process: #{inspect(pid_to_stop)}\"\n      )\n\n      stop(pid_to_stop)\n    else\n      log(\n        \"Resolving conflict on scope #{inspect(mod)} for name #{inspect(name)} {#{node(pid1)}, #{inspect(pid1)}} vs {#{node(pid2)}, #{inspect(pid2)}}, remote process will be stopped: #{inspect(pid_to_stop)}\"\n      )\n    end\n\n    pid_to_keep\n  end\n\n  defp stop(pid_to_stop) do\n    spawn(fn ->\n      Process.monitor(pid_to_stop)\n      Process.exit(pid_to_stop, {:shutdown, :syn_conflict_resolution})\n\n      receive do\n        {:DOWN, _ref, :process, ^pid_to_stop, reason} ->\n          log(\"Successfully stopped #{inspect(pid_to_stop)}. Reason: #{inspect(reason)}\")\n      after\n        5000 ->\n          log(\"Timed out while waiting for process #{inspect(pid_to_stop)} to stop. Sending kill exit signal\")\n          Process.exit(pid_to_stop, :kill)\n      end\n    end)\n  end\n\n  defp log(message), do: Logger.warning(\"SynHandler(#{node()}): #{message}\")\n\n  # We use node and the name to decide who lives and who dies\n  # This way both nodes will always agree on the same outcome\n  # regardless of timing issues\n  defp decide(pid1, pid2, name) do\n    # We hash the name to not always pick one specific node when a conflict happens\n    # between these 2 nodes\n    hash = :erlang.phash2(name, 2)\n\n    if hash == 1 do\n      if node(pid1) < node(pid2) do\n        {pid1, pid2}\n      else\n        {pid2, pid1}\n      end\n    else\n      if node(pid1) < node(pid2) do\n        {pid2, pid1}\n      else\n        {pid1, pid2}\n      end\n    end\n  end\n\n  defp topic(mod) do\n    mod\n    |> Macro.underscore()\n    |> String.split(\"/\")\n    |> Enum.take(-1)\n    |> hd()\n  end\nend\n"
  },
  {
    "path": "lib/realtime/telemetry/logger.ex",
    "content": "defmodule Realtime.Telemetry.Logger do\n  @moduledoc \"\"\"\n  We can log less frequent Telemetry events to get data into BigQuery.\n  \"\"\"\n\n  require Logger\n\n  use GenServer\n\n  @events [\n    [:realtime, :connections],\n    [:realtime, :rate_counter, :channel, :events],\n    [:realtime, :rate_counter, :channel, :joins],\n    [:realtime, :rate_counter, :channel, :db_events],\n    [:realtime, :rate_counter, :channel, :presence_events]\n  ]\n\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args)\n  end\n\n  def init(handler_id: handler_id) do\n    :telemetry.attach_many(handler_id, @events, &__MODULE__.handle_event/4, [])\n\n    {:ok, []}\n  end\n\n  @doc \"\"\"\n  Logs billing metrics for a tenant aggregated and emitted by a PromEx metric poller.\n  \"\"\"\n  def handle_event(event, measurements, %{tenant: tenant}, _config) do\n    meta = %{project: tenant, measurements: measurements}\n    Logger.info([\"Billing metrics: \", inspect(event)], meta)\n    :ok\n  end\n\n  def handle_event(_event, _measurements, _metadata, _config) do\n    :ok\n  end\n\n  def handle_info(_msg, state) do\n    {:noreply, state}\n  end\nend\n"
  },
  {
    "path": "lib/realtime/telemetry/telemetry.ex",
    "content": "defmodule Realtime.Telemetry do\n  @moduledoc \"\"\"\n  Telemetry wrapper\n  \"\"\"\n\n  @doc \"\"\"\n  Dispatches Telemetry events.\n  \"\"\"\n\n  @spec execute([atom, ...], map, map) :: :ok\n  def execute(event, measurements, metadata \\\\ %{}) do\n    :telemetry.execute(event, measurements, metadata)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/authorization/policies/broadcast_policies.ex",
    "content": "defmodule Realtime.Tenants.Authorization.Policies.BroadcastPolicies do\n  @moduledoc \"\"\"\n  BroadcastPolicies structure that holds the required authorization information for a given connection within the scope of a sending / receiving broadcasts messages\n  \"\"\"\n  require Logger\n\n  defstruct read: nil, write: nil\n\n  @type t :: %__MODULE__{\n          read: boolean() | nil,\n          write: boolean() | nil\n        }\nend\n"
  },
  {
    "path": "lib/realtime/tenants/authorization/policies/presence_policies.ex",
    "content": "defmodule Realtime.Tenants.Authorization.Policies.PresencePolicies do\n  @moduledoc \"\"\"\n    PresencePolicies structure that holds the required authorization information for a given connection within the scope of a tracking / receiving presence messages\n  \"\"\"\n  require Logger\n\n  defstruct read: nil, write: nil\n\n  @type t :: %__MODULE__{\n          read: boolean() | nil,\n          write: boolean() | nil\n        }\nend\n"
  },
  {
    "path": "lib/realtime/tenants/authorization/policies.ex",
    "content": "defmodule Realtime.Tenants.Authorization.Policies do\n  @moduledoc \"\"\"\n  Policies structure that holds the required authorization information for a given connection.\n\n  Currently there are two types of policies:\n  * Realtime.Tenants.Authorization.Policies.BroadcastPolicies - Used to store the access to Broadcast feature on a given Topic\n  * Realtime.Tenants.Authorization.Policies.PresencePolicies - Used to store the access to Presence feature on a given Topic\n  \"\"\"\n\n  alias Realtime.Tenants.Authorization.Policies.BroadcastPolicies\n  alias Realtime.Tenants.Authorization.Policies.PresencePolicies\n\n  defstruct broadcast: %BroadcastPolicies{},\n            presence: %PresencePolicies{}\n\n  @type t :: %__MODULE__{\n          broadcast: BroadcastPolicies.t(),\n          presence: PresencePolicies.t()\n        }\n\n  @doc \"\"\"\n  Updates the Policies struct sub key with the given value.\n  \"\"\"\n  @spec update_policies(t(), atom, atom, boolean) :: t()\n  def update_policies(policies, key, sub_key, value) do\n    Map.update!(policies, key, fn map -> Map.put(map, sub_key, value) end)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/authorization.ex",
    "content": "defmodule Realtime.Tenants.Authorization do\n  @moduledoc \"\"\"\n  Runs validations based on RLS policies to return policies and\n  creates a Realtime.Tenants.Policies struct with the accumulated results of the policies\n  for a given user and a given channel context\n\n  Each extension will have its own set of ways to check Policies against the Authorization context\n  but we will create some setup data to be used by the policies.\n\n  Check more information at Realtime.Tenants.Authorization.Policies\n  \"\"\"\n  import Ecto.Query\n\n  alias DBConnection.ConnectionError\n  alias Realtime.Api.Message\n  alias Realtime.Api.Tenant\n  alias Realtime.Database\n  alias Realtime.GenCounter\n  alias Realtime.GenRpc\n  alias Realtime.Tenants.Repo\n  alias Realtime.Tenants.Authorization.Policies\n\n  defstruct [:tenant_id, :topic, :headers, :jwt, :claims, :role, :sub]\n\n  @type t :: %__MODULE__{\n          :tenant_id => binary | nil,\n          :topic => binary | nil,\n          :claims => map,\n          :headers => list({binary, binary}),\n          :role => binary,\n          :sub => binary | nil\n        }\n\n  @doc \"\"\"\n  Builds a new authorization struct which will be used to retain the information required to check Policies.\n\n  Requires a map with the following keys:\n  * tenant_id: The tenant id\n  * topic: The name of the channel being accessed taken from the request\n  * headers: Request headers when the connection was made or WS was upgraded\n  * claims: JWT claims\n  * role: JWT role claim\n  * sub: JWT sub claim\n  \"\"\"\n  @spec build_authorization_params(map()) :: t()\n  def build_authorization_params(map) do\n    %__MODULE__{\n      tenant_id: Map.get(map, :tenant_id),\n      topic: Map.get(map, :topic),\n      headers: Map.get(map, :headers),\n      claims: Map.get(map, :claims),\n      role: Map.get(map, :role),\n      sub: Map.get(map, :sub)\n    }\n  end\n\n  @doc \"\"\"\n  Runs validations based on RLS policies to return policies for read policies\n\n  Automatically uses RPC if the database connection is not in the same node\n  \"\"\"\n  @spec get_read_authorizations(Policies.t(), pid(), t(), keyword()) ::\n          {:ok, Policies.t()} | {:error, any()} | {:error, :rls_policy_error, any()}\n  def get_read_authorizations(policies, db_conn, authorization_context, opts \\\\ [])\n\n  def get_read_authorizations(policies, db_conn, authorization_context, opts) when node() == node(db_conn) do\n    rate_counter = rate_counter(authorization_context.tenant_id)\n\n    if rate_counter.limit.triggered == false do\n      db_conn\n      |> get_read_policies_for_connection(authorization_context, policies, opts)\n      |> handle_policies_result(rate_counter)\n    else\n      {:error, :increase_connection_pool}\n    end\n  end\n\n  # Remote call\n  def get_read_authorizations(policies, db_conn, authorization_context, opts) do\n    rate_counter = rate_counter(authorization_context.tenant_id)\n\n    if rate_counter.limit.triggered == false do\n      case GenRpc.call(\n             node(db_conn),\n             __MODULE__,\n             :get_read_authorizations,\n             [policies, db_conn, authorization_context, opts],\n             tenant_id: authorization_context.tenant_id,\n             key: authorization_context.tenant_id\n           ) do\n        {:error, :increase_connection_pool} = error ->\n          GenCounter.add(rate_counter.id)\n          error\n\n        {:error, :rpc_error, reason} ->\n          {:error, reason}\n\n        response ->\n          response\n      end\n    else\n      {:error, :increase_connection_pool}\n    end\n  end\n\n  @doc \"\"\"\n  Runs validations based on RLS policies to return policies for write policies\n\n  Automatically uses RPC if the database connection is not in the same node\n  \"\"\"\n  @spec get_write_authorizations(Policies.t(), pid(), __MODULE__.t(), keyword()) ::\n          {:ok, Policies.t()} | {:error, any()} | {:error, :rls_policy_error, any()}\n  def get_write_authorizations(policies, db_conn, authorization_context, opts \\\\ [])\n\n  def get_write_authorizations(policies, db_conn, authorization_context, opts) when node() == node(db_conn) do\n    rate_counter = rate_counter(authorization_context.tenant_id)\n\n    if rate_counter.limit.triggered == false do\n      db_conn\n      |> get_write_policies_for_connection(authorization_context, policies, opts)\n      |> handle_policies_result(rate_counter)\n    else\n      {:error, :increase_connection_pool}\n    end\n  end\n\n  # Remote call\n  def get_write_authorizations(policies, db_conn, authorization_context, opts) do\n    rate_counter = rate_counter(authorization_context.tenant_id)\n\n    if rate_counter.limit.triggered == false do\n      case GenRpc.call(\n             node(db_conn),\n             __MODULE__,\n             :get_write_authorizations,\n             [policies, db_conn, authorization_context, opts],\n             tenant_id: authorization_context.tenant_id,\n             key: authorization_context.tenant_id\n           ) do\n        {:error, :increase_connection_pool} = error ->\n          GenCounter.add(rate_counter.id)\n          error\n\n        {:error, :rpc_error, reason} ->\n          {:error, reason}\n\n        response ->\n          response\n      end\n    else\n      {:error, :increase_connection_pool}\n    end\n  end\n\n  def get_write_authorizations(db_conn, authorization_context) do\n    get_write_authorizations(%Policies{}, db_conn, authorization_context)\n  end\n\n  defp handle_policies_result(result, rate_counter) do\n    case result do\n      {:ok, %Policies{} = policies} ->\n        {:ok, policies}\n\n      {:ok, {:error, %Postgrex.Error{} = error}} ->\n        {:error, :rls_policy_error, error}\n\n      {:error, %ConnectionError{reason: :queue_timeout}} ->\n        GenCounter.add(rate_counter.id)\n        {:error, :increase_connection_pool}\n\n      {:error, {:exit, _}} ->\n        GenCounter.add(rate_counter.id)\n        {:error, :increase_connection_pool}\n\n      {:error, error} ->\n        {:error, error}\n    end\n  end\n\n  @doc \"\"\"\n  Sets the current connection configuration with the following config values:\n  * role: The role of the user\n  * realtime.topic: The name of the channel being accessed\n  * request.jwt.claim.role: The role of the user\n  * request.jwt.claim.sub: The sub claim of the JWT token\n  * request.jwt.claims: The claims of the JWT token\n  * request.headers: The headers of the request\n  \"\"\"\n  @spec set_conn_config(DBConnection.t(), t()) :: Postgrex.Result.t()\n  def set_conn_config(conn, authorization_context) do\n    %__MODULE__{\n      topic: topic,\n      headers: headers,\n      claims: claims,\n      role: role,\n      sub: sub\n    } = authorization_context\n\n    claims = Jason.encode!(claims)\n    headers = headers |> Map.new() |> Jason.encode!()\n\n    Postgrex.query!(\n      conn,\n      \"\"\"\n      SELECT\n        set_config('role', $1, true),\n        set_config('realtime.topic', $2, true),\n        set_config('request.jwt.claims', $3, true),\n        set_config('request.jwt.claim.sub', $4, true),\n        set_config('request.jwt.claim.role', $5, true),\n        set_config('request.headers', $6, true)\n      \"\"\",\n      [role, topic, claims, sub, role, headers]\n    )\n  end\n\n  defp get_read_policies_for_connection(conn, authorization_context, policies, caller_opts) do\n    tenant_id = authorization_context.tenant_id\n    opts = [telemetry: [:realtime, :tenants, :read_authorization_check], tenant_id: tenant_id]\n    metadata = [project: tenant_id, external_id: tenant_id, tenant_id: tenant_id]\n    extensions = extensions_to_check(caller_opts)\n\n    Database.transaction(\n      conn,\n      fn transaction_conn ->\n        changesets =\n          Enum.map(extensions, fn ext ->\n            Message.changeset(%Message{}, %{topic: authorization_context.topic, extension: ext})\n          end)\n\n        {:ok, messages} = Repo.insert_all_entries(transaction_conn, changesets, Message)\n        messages_by_extension = Map.new(messages, &{&1.extension, &1.id})\n\n        set_conn_config(transaction_conn, authorization_context)\n\n        policies = check_read_policies(transaction_conn, authorization_context, messages_by_extension, policies)\n\n        Postgrex.query!(transaction_conn, \"ROLLBACK AND CHAIN\", [])\n        policies\n      end,\n      opts,\n      metadata\n    )\n  end\n\n  defp get_write_policies_for_connection(conn, authorization_context, policies, caller_opts) do\n    tenant_id = authorization_context.tenant_id\n    opts = [telemetry: [:realtime, :tenants, :write_authorization_check], tenant_id: tenant_id]\n    metadata = [project: tenant_id, external_id: tenant_id]\n    extensions = extensions_to_check(caller_opts)\n\n    Database.transaction(\n      conn,\n      fn transaction_conn ->\n        set_conn_config(transaction_conn, authorization_context)\n        policies = check_write_policies(transaction_conn, authorization_context, extensions, policies)\n\n        Postgrex.query!(transaction_conn, \"ROLLBACK AND CHAIN\", [])\n        policies\n      end,\n      opts,\n      metadata\n    )\n  end\n\n  @all_extensions [:broadcast, :presence]\n\n  defp extensions_to_check(opts) do\n    if Keyword.get(opts, :presence_enabled?, true),\n      do: @all_extensions,\n      else: [:broadcast]\n  end\n\n  defp check_read_policies(conn, authorization_context, messages_by_extension, policies) do\n    ids = Map.values(messages_by_extension)\n\n    query = from(m in Message, where: m.topic == ^authorization_context.topic and m.id in ^ids)\n\n    with {:ok, res} <- Repo.all(conn, query, Message) do\n      returned_ids = MapSet.new(res, & &1.id)\n\n      Enum.reduce(@all_extensions, policies, fn extension, acc ->\n        can? =\n          Map.has_key?(messages_by_extension, extension) and\n            MapSet.member?(returned_ids, messages_by_extension[extension])\n\n        Policies.update_policies(acc, extension, :read, can?)\n      end)\n    end\n  end\n\n  defp check_write_policies(conn, authorization_context, extensions, policies) do\n    Enum.reduce(@all_extensions, policies, fn extension, acc ->\n      if extension in extensions do\n        changeset = Message.changeset(%Message{}, %{topic: authorization_context.topic, extension: extension})\n\n        case Repo.insert(conn, changeset, Message, mode: :savepoint) do\n          {:ok, _} ->\n            Policies.update_policies(acc, extension, :write, true)\n\n          {:error, %Postgrex.Error{postgres: %{code: :insufficient_privilege}}} ->\n            Policies.update_policies(acc, extension, :write, false)\n\n          e ->\n            e\n        end\n      else\n        Policies.update_policies(acc, extension, :write, false)\n      end\n    end)\n  end\n\n  defp rate_counter(tenant_id) do\n    %Tenant{} = tenant = Realtime.Tenants.Cache.get_tenant_by_external_id(tenant_id)\n    rate_counter = Realtime.Tenants.authorization_errors_per_second_rate(tenant)\n    {:ok, rate_counter} = Realtime.RateCounter.get(rate_counter)\n    rate_counter\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/batch_broadcast.ex",
    "content": "defmodule Realtime.Tenants.BatchBroadcast do\n  @moduledoc \"\"\"\n  Virtual schema with a representation of a batched broadcast.\n  \"\"\"\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  alias Realtime.Api.Tenant\n  alias Realtime.GenCounter\n  alias Realtime.RateCounter\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Authorization\n  alias Realtime.Tenants.Authorization.Policies\n  alias Realtime.Tenants.Authorization.Policies.BroadcastPolicies\n  alias Realtime.Tenants.Connect\n\n  alias RealtimeWeb.RealtimeChannel\n  alias RealtimeWeb.TenantBroadcaster\n\n  embedded_schema do\n    embeds_many :messages, Message do\n      field :event, :string\n      field :topic, :string\n      field :payload, :map\n      field :private, :boolean, default: false\n    end\n  end\n\n  @spec broadcast(\n          auth_params :: map() | nil,\n          tenant :: Tenant.t(),\n          messages :: %{\n            messages: list(%{id: String.t(), topic: String.t(), payload: map(), event: String.t(), private: boolean()})\n          },\n          super_user :: boolean()\n        ) :: :ok | {:error, atom() | Ecto.Changeset.t()}\n  def broadcast(auth_params, tenant, messages, super_user \\\\ false)\n\n  def broadcast(%Plug.Conn{} = conn, %Tenant{} = tenant, messages, super_user) do\n    auth_params = %{\n      tenant_id: tenant.external_id,\n      headers: conn.req_headers,\n      claims: conn.assigns.claims,\n      role: conn.assigns.role,\n      sub: conn.assigns.sub\n    }\n\n    broadcast(auth_params, %Tenant{} = tenant, messages, super_user)\n  end\n\n  def broadcast(auth_params, %Tenant{} = tenant, messages, super_user) do\n    with %Ecto.Changeset{valid?: true} = changeset <- changeset(%__MODULE__{}, messages, tenant),\n         %Ecto.Changeset{changes: %{messages: messages}} = changeset,\n         events_per_second_rate = Tenants.events_per_second_rate(tenant),\n         :ok <- check_rate_limit(events_per_second_rate, tenant, length(messages)) do\n      events =\n        messages\n        |> Enum.map(fn %{changes: event} -> event end)\n        |> Enum.group_by(fn event -> Map.get(event, :private, false) end)\n\n      # Handle events for public channel\n      events\n      |> Map.get(false, [])\n      |> Enum.each(fn message ->\n        send_message_and_count(tenant, events_per_second_rate, message, true)\n      end)\n\n      # Handle events for private channel\n      events\n      |> Map.get(true, [])\n      |> Enum.group_by(fn event -> Map.get(event, :topic) end)\n      |> Enum.each(fn {topic, events} ->\n        if super_user do\n          Enum.each(events, fn message -> send_message_and_count(tenant, events_per_second_rate, message, false) end)\n        else\n          case permissions_for_message(tenant, auth_params, topic) do\n            %Policies{broadcast: %BroadcastPolicies{write: true}} ->\n              Enum.each(events, fn message -> send_message_and_count(tenant, events_per_second_rate, message, false) end)\n\n            _ ->\n              nil\n          end\n        end\n      end)\n\n      :ok\n    else\n      %Ecto.Changeset{valid?: false} = changeset -> {:error, changeset}\n      error -> error\n    end\n  end\n\n  def broadcast(_, nil, _, _), do: {:error, :tenant_not_found}\n\n  defp changeset(payload, attrs, tenant) do\n    payload\n    |> cast(attrs, [])\n    |> cast_embed(:messages, required: true, with: fn message, attrs -> message_changeset(message, tenant, attrs) end)\n  end\n\n  defp message_changeset(message, tenant, attrs) do\n    message\n    |> cast(attrs, [:id, :topic, :payload, :event, :private])\n    |> maybe_put_private_change()\n    |> validate_required([:topic, :payload, :event])\n    |> validate_payload_size(tenant)\n  end\n\n  defp maybe_put_private_change(changeset) do\n    case get_change(changeset, :private) do\n      nil -> put_change(changeset, :private, false)\n      _ -> changeset\n    end\n  end\n\n  defp validate_payload_size(changeset, tenant) do\n    payload = get_change(changeset, :payload)\n\n    case Tenants.validate_payload_size(tenant, payload) do\n      :ok -> changeset\n      _ -> add_error(changeset, :payload, \"Payload size exceeds tenant limit\")\n    end\n  end\n\n  @event_type \"broadcast\"\n  defp send_message_and_count(tenant, events_per_second_rate, message, public?) do\n    tenant_topic = Tenants.tenant_topic(tenant, message.topic, public?)\n\n    payload = %{\"payload\" => message.payload, \"event\" => message.event, \"type\" => \"broadcast\"}\n\n    payload =\n      if message[:id],\n        do: Map.put(payload, \"meta\", %{\"id\" => message.id}),\n        else: payload\n\n    broadcast = %Phoenix.Socket.Broadcast{topic: message.topic, event: @event_type, payload: payload}\n\n    GenCounter.add(events_per_second_rate.id)\n\n    TenantBroadcaster.pubsub_broadcast(\n      tenant.external_id,\n      tenant_topic,\n      broadcast,\n      RealtimeChannel.MessageDispatcher,\n      :broadcast\n    )\n  end\n\n  defp permissions_for_message(_, nil, _), do: nil\n\n  defp permissions_for_message(tenant, auth_params, topic) do\n    with {:ok, db_conn} <- Connect.lookup_or_start_connection(tenant.external_id) do\n      auth_params =\n        auth_params\n        |> Map.put(:topic, topic)\n        |> Authorization.build_authorization_params()\n\n      case Authorization.get_write_authorizations(db_conn, auth_params) do\n        {:ok, policies} -> policies\n        {:error, :not_found} -> nil\n        error -> error\n      end\n    end\n  end\n\n  defp check_rate_limit(events_per_second_rate, %Tenant{} = tenant, total_messages_to_broadcast) do\n    %{max_events_per_second: max_events_per_second} = tenant\n    {:ok, %{avg: events_per_second}} = RateCounter.get(events_per_second_rate)\n\n    cond do\n      events_per_second > max_events_per_second ->\n        {:error, :too_many_requests, \"You have exceeded your rate limit\"}\n\n      total_messages_to_broadcast + events_per_second > max_events_per_second ->\n        {:error, :too_many_requests, \"Too many messages to broadcast, please reduce the batch size\"}\n\n      true ->\n        :ok\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/cache.ex",
    "content": "defmodule Realtime.Tenants.Cache do\n  @moduledoc \"\"\"\n  Cache for Tenants.\n  \"\"\"\n  require Cachex.Spec\n  require Logger\n\n  alias Realtime.GenRpc\n  alias Realtime.Tenants\n\n  def child_spec(_) do\n    tenant_cache_expiration = Application.get_env(:realtime, :tenant_cache_expiration)\n\n    %{\n      id: __MODULE__,\n      start: {Cachex, :start_link, [__MODULE__, [expiration: Cachex.Spec.expiration(default: tenant_cache_expiration)]]}\n    }\n  end\n\n  def get_tenant_by_external_id(tenant_id) do\n    case Cachex.fetch(__MODULE__, cache_key(tenant_id), fn _key ->\n           case Tenants.get_tenant_by_external_id(tenant_id) do\n             nil -> {:ignore, nil}\n             tenant -> {:commit, tenant}\n           end\n         end) do\n      {:commit, value} -> value\n      {:ok, value} -> value\n      {:ignore, value} -> value\n    end\n  end\n\n  defp cache_key(tenant_id), do: {:get_tenant_by_external_id, tenant_id}\n\n  @doc \"\"\"\n  Invalidates the cache for a tenant in the local node\n  \"\"\"\n  def invalidate_tenant_cache(tenant_id), do: Cachex.del(__MODULE__, cache_key(tenant_id))\n\n  def distributed_invalidate_tenant_cache(tenant_id) when is_binary(tenant_id) do\n    GenRpc.multicast(__MODULE__, :invalidate_tenant_cache, [tenant_id])\n  end\n\n  @doc \"\"\"\n  Update the cache for a tenant\n  \"\"\"\n  def update_cache(tenant) do\n    Cachex.put(__MODULE__, cache_key(tenant.external_id), tenant)\n  end\n\n  @doc \"\"\"\n  Update the cache for a tenant in all nodes\n  \"\"\"\n  @spec global_cache_update(Realtime.Api.Tenant.t()) :: :ok\n  def global_cache_update(tenant) do\n    GenRpc.multicast(__MODULE__, :update_cache, [tenant])\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/connect/check_connection.ex",
    "content": "defmodule Realtime.Tenants.Connect.CheckConnection do\n  @moduledoc \"\"\"\n  Check tenant database connection.\n  \"\"\"\n\n  @behaviour Realtime.Tenants.Connect.Piper\n  @impl true\n  def run(acc) do\n    %{tenant: tenant} = acc\n\n    case Realtime.Database.check_tenant_connection(tenant) do\n      {:ok, conn, migrations_ran} ->\n        db_conn_reference = Process.monitor(conn)\n\n        {:ok,\n         %{\n           acc\n           | db_conn_pid: conn,\n             db_conn_reference: db_conn_reference,\n             migrations_ran_on_database: migrations_ran\n         }}\n\n      {:error, error} ->\n        {:error, error}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/connect/get_tenant.ex",
    "content": "defmodule Realtime.Tenants.Connect.GetTenant do\n  @moduledoc \"\"\"\n  Get tenant database connection.\n  \"\"\"\n\n  alias Realtime.Api.Tenant\n  alias Realtime.Tenants\n  @behaviour Realtime.Tenants.Connect.Piper\n\n  @impl Realtime.Tenants.Connect.Piper\n  def run(acc) do\n    %{tenant_id: tenant_id} = acc\n\n    case Tenants.Cache.get_tenant_by_external_id(tenant_id) do\n      %Tenant{} = tenant -> {:ok, Map.put(acc, :tenant, tenant)}\n      _ -> {:error, :tenant_not_found}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/connect/piper.ex",
    "content": "defmodule Realtime.Tenants.Connect.Piper do\n  @moduledoc \"\"\"\n  Pipes different commands to execute specific actions during the connection process.\n  \"\"\"\n  require Logger\n  @callback run(any()) :: {:ok, any()} | {:error, any()}\n\n  def run(pipers, init) do\n    Enum.reduce_while(pipers, {:ok, init}, fn piper, {:ok, acc} ->\n      case :timer.tc(fn -> piper.run(acc) end, :millisecond) do\n        {exec_time, {:ok, result}} ->\n          Logger.info(\"#{inspect(piper)} executed in #{exec_time} ms\")\n          {:cont, {:ok, result}}\n\n        {exec_time, {:error, error}} ->\n          Logger.error(\"#{inspect(piper)} failed in #{exec_time} ms\")\n          {:halt, {:error, error}}\n\n        _ ->\n          raise ArgumentError, \"must return {:ok, _} or {:error, _}\"\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/connect/reconcile_migrations.ex",
    "content": "defmodule Realtime.Tenants.Connect.ReconcileMigrations do\n  @moduledoc \"\"\"\n  Reconciles the tenant's cached migrations_ran counter with the actual\n  migration count from the tenant database's schema_migrations table.\n\n  This handles the case where a project restore causes the database schema\n  to revert while the migrations_ran counter remains at the latest value.\n  \"\"\"\n\n  use Realtime.Logs\n\n  alias Realtime.Api\n\n  @behaviour Realtime.Tenants.Connect.Piper\n\n  @impl true\n  def run(%{tenant: tenant, migrations_ran_on_database: migrations_ran_on_database} = acc) do\n    if tenant.migrations_ran != migrations_ran_on_database do\n      log_warning(\n        \"MigrationCountMismatch\",\n        \"cached=#{tenant.migrations_ran} database=#{migrations_ran_on_database}\"\n      )\n\n      case Api.update_migrations_ran(tenant.external_id, migrations_ran_on_database) do\n        {:ok, updated_tenant} -> {:ok, %{acc | tenant: updated_tenant}}\n        {:error, error} -> {:error, error}\n      end\n    else\n      {:ok, acc}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/connect/register_process.ex",
    "content": "defmodule Realtime.Tenants.Connect.RegisterProcess do\n  @moduledoc \"\"\"\n  Registers the database process in :syn\n  \"\"\"\n  alias Realtime.Tenants.Connect\n  @behaviour Realtime.Tenants.Connect.Piper\n\n  @impl true\n  def run(acc) do\n    %{tenant_id: tenant_id, db_conn_pid: conn} = acc\n\n    with {:ok, _} <- :syn.update_registry(Connect, tenant_id, fn _pid, meta -> %{meta | conn: conn} end),\n         {:ok, _} <- Registry.register(Connect.Registry, tenant_id, %{}) do\n      {:ok, acc}\n    else\n      {:error, :undefined} -> {:error, :process_not_found}\n      {:error, {:already_registered, _}} -> {:error, :already_registered}\n      {:error, reason} -> {:error, reason}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/connect.ex",
    "content": "defmodule Realtime.Tenants.Connect do\n  @moduledoc \"\"\"\n  This module is responsible for attempting to connect to a tenant's database and store the DBConnection in a Syn registry.\n\n  ## Options\n  * `:check_connected_user_interval` - The interval in milliseconds to check if there are any connected users to a tenant channel. If there are no connected users, the connection will be stopped.\n  * `:check_connect_region_interval` - The interval in milliseconds to check if this process is in the correct region. If the region is not correct it stops the connection.\n  * `:erpc_timeout` - The timeout in milliseconds for the `:erpc` calls to the tenant's database.\n  \"\"\"\n  use GenServer, restart: :temporary\n\n  use Realtime.Logs\n\n  alias Realtime.Api.Tenant\n  alias Realtime.GenCounter\n  alias Realtime.RateCounter\n  alias Realtime.Rpc\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Connect.CheckConnection\n  alias Realtime.Tenants.Connect.GetTenant\n  alias Realtime.Tenants.Connect.Piper\n  alias Realtime.Tenants.Connect.ReconcileMigrations\n  alias Realtime.Tenants.Connect.RegisterProcess\n  alias Realtime.Tenants.Migrations\n  alias Realtime.Tenants.Rebalancer\n  alias Realtime.Tenants.ReplicationConnection\n  alias Realtime.UsersCounter\n  alias DBConnection.Backoff\n\n  @rpc_timeout_default 30_000\n  @check_connected_user_interval_default 50_000\n  @connected_users_bucket_shutdown [0, 0, 0, 0, 0, 0]\n  @type t :: %__MODULE__{\n          tenant_id: binary(),\n          db_conn_reference: reference(),\n          db_conn_pid: pid(),\n          replication_connection_pid: pid(),\n          replication_connection_reference: reference(),\n          backoff: Backoff.t(),\n          replication_recovery_started_at: non_neg_integer() | nil,\n          check_connected_user_interval: non_neg_integer(),\n          connected_users_bucket: list(non_neg_integer()),\n          check_connect_region_interval: non_neg_integer(),\n          migrations_ran_on_database: non_neg_integer()\n        }\n\n  defstruct tenant_id: nil,\n            db_conn_reference: nil,\n            db_conn_pid: nil,\n            replication_connection_pid: nil,\n            replication_connection_reference: nil,\n            backoff: nil,\n            replication_recovery_started_at: nil,\n            check_connected_user_interval: nil,\n            connected_users_bucket: [1],\n            check_connect_region_interval: nil,\n            migrations_ran_on_database: 0\n\n  @tenant_id_spec [{{:\"$1\", :_, :_, :_, :_, :_}, [], [:\"$1\"]}]\n  @spec list_tenants() :: [binary]\n  def list_tenants() do\n    :syn_registry_by_name\n    |> :syn_backbone.get_table_name(__MODULE__)\n    |> :ets.select(@tenant_id_spec)\n  end\n\n  @doc \"Check if Connect has finished setting up connections\"\n  def ready?(tenant_id) do\n    case whereis(tenant_id) do\n      pid when is_pid(pid) -> GenServer.call(pid, :ready?)\n      _ -> false\n    end\n  end\n\n  @doc \"\"\"\n  Returns the database connection for a tenant. If the tenant is not connected, it will attempt to connect to the tenant's database.\n  \"\"\"\n  @spec lookup_or_start_connection(binary(), keyword()) ::\n          {:ok, pid()}\n          | {:error, :tenant_database_unavailable}\n          | {:error, :initializing}\n          | {:error, :tenant_database_connection_initializing}\n          | {:error, :tenant_db_too_many_connections}\n          | {:error, :connect_rate_limit_reached}\n          | {:error, :rpc_error, term()}\n  def lookup_or_start_connection(tenant_id, opts \\\\ []) when is_binary(tenant_id) do\n    rate_args = Tenants.connect_errors_per_second_rate(tenant_id)\n    RateCounter.new(rate_args)\n\n    with {:ok, %{limit: %{triggered: false}}} <- RateCounter.get(rate_args),\n         {:ok, conn} <- get_status(tenant_id) do\n      {:ok, conn}\n    else\n      {:ok, %{limit: %{triggered: true}}} ->\n        {:error, :connect_rate_limit_reached}\n\n      {:error, :tenant_database_connection_initializing} ->\n        case call_external_node(tenant_id, opts) do\n          {:ok, pid} ->\n            {:ok, pid}\n\n          error ->\n            GenCounter.add(rate_args.id)\n            error\n        end\n\n      {:error, :initializing} ->\n        {:error, :tenant_database_unavailable}\n\n      {:error, reason} ->\n        GenCounter.add(rate_args.id)\n        {:error, reason}\n    end\n  end\n\n  @doc \"\"\"\n  Returns the database connection pid from :syn if it exists.\n  \"\"\"\n  @spec get_status(binary()) ::\n          {:ok, pid()}\n          | {:error, :tenant_database_unavailable}\n          | {:error, :initializing}\n          | {:error, :tenant_database_connection_initializing}\n          | {:error, :tenant_db_too_many_connections}\n  def get_status(tenant_id) do\n    case :syn.lookup(__MODULE__, tenant_id) do\n      {pid, %{conn: nil}} ->\n        wait_for_connection(pid, tenant_id)\n\n      {_, %{conn: conn, replication_conn: nil}} ->\n        {:ok, conn}\n\n      {_, %{conn: conn}} ->\n        {:ok, conn}\n\n      :undefined ->\n        {:error, :tenant_database_connection_initializing}\n\n      error ->\n        log_error(\"SynInitializationError\", error)\n        {:error, :tenant_database_unavailable}\n    end\n  end\n\n  def syn_topic(tenant_id), do: \"connect:#{tenant_id}\"\n\n  defp wait_for_connection(pid, tenant_id) do\n    RealtimeWeb.Endpoint.subscribe(syn_topic(tenant_id))\n\n    # We do a lookup after subscribing because we could've missed a message while subscribing\n    case :syn.lookup(__MODULE__, tenant_id) do\n      {_pid, %{conn: conn}} when is_pid(conn) ->\n        {:ok, conn}\n\n      _ ->\n        # Wait for up to 5 seconds for the ready event\n        receive do\n          %{event: \"ready\", payload: %{pid: ^pid, conn: conn}} ->\n            {:ok, conn}\n\n          %{event: \"connect_down\", payload: %{pid: ^pid, reason: {:shutdown, :tenant_db_too_many_connections}}} ->\n            {:error, :tenant_db_too_many_connections}\n\n          %{event: \"connect_down\", payload: %{pid: ^pid, reason: _reason}} ->\n            metadata = [external_id: tenant_id, project: tenant_id]\n            log_error(\"UnableToConnectToTenantDatabase\", \"Unable to connect to tenant database\", metadata)\n            {:error, :tenant_database_unavailable}\n        after\n          15_000 -> {:error, :initializing}\n        end\n    end\n  after\n    RealtimeWeb.Endpoint.unsubscribe(syn_topic(tenant_id))\n  end\n\n  @doc \"\"\"\n  Connects to a tenant's database and stores the DBConnection in the process :syn metadata\n  \"\"\"\n  @spec connect(binary(), binary(), keyword()) :: {:ok, DBConnection.t()} | {:error, term()}\n  def connect(tenant_id, region, opts \\\\ []) do\n    supervisor =\n      {:via, PartitionSupervisor, {Realtime.Tenants.Connect.DynamicSupervisor, tenant_id}}\n\n    spec = {__MODULE__, [tenant_id: tenant_id, region: region] ++ opts}\n    metadata = [external_id: tenant_id, project: tenant_id]\n\n    case DynamicSupervisor.start_child(supervisor, spec) do\n      {:ok, _} ->\n        get_status(tenant_id)\n\n      {:error, {:already_started, _}} ->\n        get_status(tenant_id)\n\n      {:error, error} ->\n        log_error(\"UnableToConnectToTenantDatabase\", error, metadata)\n        {:error, :tenant_database_unavailable}\n    end\n  end\n\n  @doc \"\"\"\n  Returns the pid of the tenant Connection process and db_conn pid\n  \"\"\"\n  @spec whereis(binary()) :: pid() | nil\n  def whereis(tenant_id) do\n    case :syn.lookup(__MODULE__, tenant_id) do\n      {pid, _} when is_pid(pid) -> pid\n      _ -> nil\n    end\n  end\n\n  @doc \"\"\"\n  Returns the replication connection status from :syn metadata without RPC calls.\n  \"\"\"\n  @spec replication_status(binary()) :: {:ok, pid()} | {:error, :not_connected}\n  def replication_status(tenant_id) do\n    case :syn.lookup(__MODULE__, tenant_id) do\n      {_, %{replication_conn: pid}} when is_pid(pid) -> {:ok, pid}\n      _ -> {:error, :not_connected}\n    end\n  end\n\n  @doc \"\"\"\n  Shutdown the tenant Connection and linked processes\n  \"\"\"\n  @spec shutdown(binary()) :: :ok | nil\n  def shutdown(tenant_id) do\n    case whereis(tenant_id) do\n      pid when is_pid(pid) ->\n        send(pid, :shutdown_connect)\n        :ok\n\n      _ ->\n        :ok\n    end\n  end\n\n  def start_link(opts) do\n    tenant_id = Keyword.get(opts, :tenant_id)\n    region = Keyword.get(opts, :region)\n\n    check_connected_user_interval =\n      Keyword.get(opts, :check_connected_user_interval, @check_connected_user_interval_default)\n\n    check_connect_region_interval = Keyword.get(opts, :check_connect_region_interval, rebalance_check_interval_in_ms())\n\n    name = {__MODULE__, tenant_id, %{conn: nil, region: region, replication_conn: nil}}\n\n    state = %__MODULE__{\n      tenant_id: tenant_id,\n      check_connected_user_interval: check_connected_user_interval,\n      check_connect_region_interval: check_connect_region_interval,\n      backoff: Backoff.new(backoff_min: :timer.seconds(1), backoff_max: :timer.seconds(15), backoff_type: :rand_exp)\n    }\n\n    opts = Keyword.put(opts, :name, {:via, :syn, name})\n\n    GenServer.start_link(__MODULE__, state, opts)\n  end\n\n  ## GenServer callbacks\n  # Needs to be done on init/1 to guarantee the GenServer only starts if we are able to connect to the database\n  @impl GenServer\n  def init(%{tenant_id: tenant_id} = state) do\n    Logger.metadata(external_id: tenant_id, project: tenant_id)\n\n    {:ok, state, {:continue, :db_connect}}\n  end\n\n  @impl true\n  def handle_continue(:db_connect, state) do\n    pipes = [\n      GetTenant,\n      CheckConnection,\n      ReconcileMigrations,\n      RegisterProcess\n    ]\n\n    case Piper.run(pipes, state) do\n      {:ok, acc} ->\n        {:noreply, acc, {:continue, :run_migrations}}\n\n      {:error, :tenant_not_found} ->\n        {:stop, {:shutdown, :tenant_not_found}, state}\n\n      {:error, :tenant_db_too_many_connections} ->\n        {:stop, {:shutdown, :tenant_db_too_many_connections}, state}\n\n      {:error, error} ->\n        log_error(\"UnableToConnectToTenantDatabase\", error)\n        {:stop, :shutdown, state}\n    end\n  end\n\n  def handle_continue(:run_migrations, state) do\n    %{tenant: tenant, db_conn_pid: db_conn_pid} = state\n    Logger.warning(\"Tenant #{tenant.external_id} is initializing: #{inspect(node())}\")\n\n    with res when res in [:ok, :noop] <- Migrations.run_migrations(tenant),\n         :ok <- Migrations.create_partitions(db_conn_pid) do\n      {:noreply, state, {:continue, :start_replication}}\n    else\n      error ->\n        log_error(\"MigrationsFailedToRun\", error)\n        {:stop, :shutdown, state}\n    end\n  rescue\n    error ->\n      log_error(\"MigrationsFailedToRun\", error)\n      {:stop, :shutdown, state}\n  end\n\n  def handle_continue(:start_replication, state) do\n    case start_replication_connection(state) do\n      {:ok, state} -> {:noreply, state, {:continue, :setup_connected_user_events}}\n      {:error, _error} -> {:stop, :shutdown, state}\n    end\n  end\n\n  def handle_continue(:setup_connected_user_events, state) do\n    %{\n      check_connected_user_interval: check_connected_user_interval,\n      connected_users_bucket: connected_users_bucket,\n      tenant_id: tenant_id\n    } = state\n\n    :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, \"realtime:operations:\" <> tenant_id)\n    send_connected_user_check_message(connected_users_bucket, check_connected_user_interval)\n    :ets.insert(__MODULE__, {tenant_id})\n    {:noreply, state, {:continue, :start_connect_region_check}}\n  end\n\n  def handle_continue(:start_connect_region_check, state) do\n    send_connect_region_check_message(state.check_connect_region_interval)\n    {:noreply, state}\n  end\n\n  @impl GenServer\n  def handle_info(\n        :check_connected_users,\n        %{\n          tenant_id: tenant_id,\n          check_connected_user_interval: check_connected_user_interval,\n          connected_users_bucket: connected_users_bucket\n        } = state\n      ) do\n    connected_users_bucket =\n      tenant_id\n      |> update_connected_users_bucket(connected_users_bucket)\n      |> send_connected_user_check_message(check_connected_user_interval)\n\n    {:noreply, %{state | connected_users_bucket: connected_users_bucket}}\n  end\n\n  def handle_info({:check_connect_region, previous_nodes_set}, state) do\n    current_nodes_set = MapSet.new(Node.list())\n\n    case Rebalancer.check(previous_nodes_set, current_nodes_set, state.tenant_id) do\n      :ok ->\n        # Let's check again in the future\n        send_connect_region_check_message(state.check_connect_region_interval)\n        {:noreply, state}\n\n      {:error, :wrong_region} ->\n        Logger.warning(\"Rebalancing Tenant database connection for a closer region\")\n        {:stop, {:shutdown, :rebalancing}, state}\n    end\n  end\n\n  def handle_info(:shutdown_no_connected_users, state) do\n    Logger.info(\"Tenant has no connected users, database connection will be terminated\")\n    {:stop, :shutdown, state}\n  end\n\n  def handle_info(:shutdown_connect, state) do\n    Logger.warning(\"Shutdowning tenant connection\")\n    {:stop, :shutdown, state}\n  end\n\n  # Handle database connection termination\n  def handle_info(\n        {:DOWN, db_conn_reference, _, _, _},\n        %{db_conn_reference: db_conn_reference} = state\n      ) do\n    Logger.warning(\"Database connection has been terminated\")\n    {:stop, :shutdown, state}\n  end\n\n  # Handle replication connection termination\n  def handle_info(\n        {:DOWN, replication_connection_reference, _, _, _},\n        %{replication_connection_reference: replication_connection_reference, tenant_id: tenant_id} = state\n      ) do\n    %{backoff: backoff} = state\n    log_warning(\"ReplicationConnectionDown\", \"Replication connection has been terminated, recovery window opened\")\n    update_syn_replication_conn(tenant_id, nil)\n    {timeout, backoff} = Backoff.backoff(backoff)\n    Process.send_after(self(), :recover_replication_connection, timeout)\n\n    recovery_started_at = state.replication_recovery_started_at || System.monotonic_time(:millisecond)\n\n    state = %{\n      state\n      | replication_connection_pid: nil,\n        replication_connection_reference: nil,\n        backoff: backoff,\n        replication_recovery_started_at: recovery_started_at\n    }\n\n    {:noreply, state}\n  end\n\n  @replication_connection_query \"SELECT 1 from pg_stat_activity where application_name='realtime_replication_connection'\"\n  @max_replication_recovery_ms :timer.hours(2)\n  def handle_info(:recover_replication_connection, %{replication_recovery_started_at: nil} = state) do\n    {:noreply, state}\n  end\n\n  def handle_info(:recover_replication_connection, state) do\n    %{backoff: backoff, db_conn_pid: db_conn_pid, replication_recovery_started_at: started_at} = state\n    elapsed = System.monotonic_time(:millisecond) - started_at\n\n    if elapsed > @max_replication_recovery_ms do\n      log_warning(\n        \"ReplicationRecoveryWindowExceeded\",\n        \"Replication recovery window exceeded after #{elapsed}ms, terminating connection\"\n      )\n\n      {:stop, :shutdown, state}\n    else\n      with {:query, {:ok, %{num_rows: 0}}} <- {:query, Postgrex.query(db_conn_pid, @replication_connection_query, [])},\n           {:start, {:ok, state}} <- {:start, start_replication_connection(state)} do\n        {:noreply, %{state | backoff: Backoff.reset(backoff), replication_recovery_started_at: nil}}\n      else\n        {:query, {:ok, %{num_rows: _}}} ->\n          Logger.info(\"Waiting for old walsender to exit\")\n          {:noreply, schedule_replication_retry(state)}\n\n        {:query, {:error, error}} ->\n          log_error(\"ReplicationConnectionRecoveryFailed\", \"DB check failed during recovery: #{inspect(error)}\")\n          {:noreply, schedule_replication_retry(state)}\n\n        {:start, {:error, error}} ->\n          log_error(\"ReplicationConnectionRecoveryFailed\", \"Replication connection recovery failed: #{inspect(error)}\")\n          {:noreply, schedule_replication_retry(state)}\n      end\n    end\n  end\n\n  def handle_info(_, state), do: {:noreply, state}\n\n  @impl true\n  def handle_call(:ready?, _from, state) do\n    # We just want to know if the process is ready to reply to the client\n    # Essentially checking if all handle_continue's were completed\n    {:reply, true, state}\n  end\n\n  @impl true\n  def terminate(reason, %{tenant_id: tenant_id}) do\n    Logger.info(\"Tenant #{tenant_id} has been terminated: #{inspect(reason)}\")\n    :ok\n  end\n\n  ## Private functions\n  defp call_external_node(tenant_id, opts) do\n    Logger.warning(\"Connection process starting up\")\n    rpc_timeout = Keyword.get(opts, :rpc_timeout, @rpc_timeout_default)\n\n    with tenant <- Tenants.Cache.get_tenant_by_external_id(tenant_id),\n         :ok <- tenant_suspended?(tenant),\n         {:ok, node, region} <- Realtime.Nodes.get_node_for_tenant(tenant) do\n      Rpc.enhanced_call(node, __MODULE__, :connect, [tenant_id, region, opts],\n        timeout: rpc_timeout,\n        tenant_id: tenant_id\n      )\n    end\n  end\n\n  defp update_connected_users_bucket(tenant_id, connected_users_bucket) do\n    connected_users_bucket\n    |> then(&(&1 ++ [UsersCounter.tenant_users(tenant_id)]))\n    |> Enum.take(-6)\n  end\n\n  defp send_connected_user_check_message(\n         @connected_users_bucket_shutdown,\n         check_connected_user_interval\n       ) do\n    Process.send_after(self(), :shutdown_no_connected_users, check_connected_user_interval)\n  end\n\n  defp send_connected_user_check_message(connected_users_bucket, check_connected_user_interval) do\n    Process.send_after(self(), :check_connected_users, check_connected_user_interval)\n    connected_users_bucket\n  end\n\n  defp send_connect_region_check_message(check_connect_region_interval) do\n    Process.send_after(self(), {:check_connect_region, MapSet.new(Node.list())}, check_connect_region_interval)\n  end\n\n  defp tenant_suspended?(%Tenant{suspend: true}), do: {:error, :tenant_suspended}\n  defp tenant_suspended?(_), do: :ok\n\n  defp rebalance_check_interval_in_ms(), do: Application.fetch_env!(:realtime, :rebalance_check_interval_in_ms)\n\n  defp schedule_replication_retry(%{backoff: backoff} = state) do\n    {timeout, backoff} = Backoff.backoff(backoff)\n    Process.send_after(self(), :recover_replication_connection, timeout)\n    %{state | backoff: backoff}\n  end\n\n  defp update_syn_replication_conn(tenant_id, pid) do\n    :syn.update_registry(__MODULE__, tenant_id, fn _pid, meta -> %{meta | replication_conn: pid} end)\n  end\n\n  defp start_replication_connection(state) do\n    %{tenant: tenant, tenant_id: tenant_id} = state\n\n    with {:ok, replication_connection_pid} <- ReplicationConnection.start(tenant, self()),\n         {:ok, _} <- update_syn_replication_conn(tenant_id, replication_connection_pid) do\n      replication_connection_reference = Process.monitor(replication_connection_pid)\n\n      state = %{\n        state\n        | replication_connection_pid: replication_connection_pid,\n          replication_connection_reference: replication_connection_reference\n      }\n\n      {:ok, state}\n    else\n      {:error, :max_wal_senders_reached} ->\n        log_error(\"ReplicationMaxWalSendersReached\", \"Tenant database has reached the maximum number of WAL senders\")\n        {:error, :max_wal_senders_reached}\n\n      {:error, :replication_connection_timeout} ->\n        log_error(\"ReplicationConnectionTimeout\", \"Replication connection timed out during initialization\")\n        {:error, :replication_connection_timeout}\n\n      {:error, error} ->\n        log_error(\"StartReplicationFailed\", error)\n        {:error, error}\n    end\n  rescue\n    error ->\n      log_error(\"StartReplicationFailed\", error)\n      {:error, error}\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/janitor/maintenance_task.ex",
    "content": "defmodule Realtime.Tenants.Janitor.MaintenanceTask do\n  @moduledoc \"\"\"\n  Perform maintenance on the messages table.\n  * Delete old messages\n  * Create new partitions\n  \"\"\"\n\n  @spec run(String.t()) :: :ok | {:error, any}\n  def run(tenant_external_id) do\n    with %Realtime.Api.Tenant{} = tenant <- Realtime.Tenants.Cache.get_tenant_by_external_id(tenant_external_id),\n         {:ok, conn} <- Realtime.Database.connect(tenant, \"realtime_janitor\"),\n         :ok <- Realtime.Messages.delete_old_messages(conn),\n         :ok <- Realtime.Tenants.Migrations.create_partitions(conn) do\n      GenServer.stop(conn)\n      :ok\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/janitor.ex",
    "content": "defmodule Realtime.Tenants.Janitor do\n  @moduledoc \"\"\"\n  Scheduled tasks for the Tenants.\n  \"\"\"\n\n  use GenServer\n  use Realtime.Logs\n\n  alias Realtime.Tenants.Janitor.MaintenanceTask\n\n  @type t :: %__MODULE__{\n          timer: pos_integer() | nil,\n          region: String.t() | nil,\n          chunks: pos_integer() | nil,\n          start_after: pos_integer() | nil,\n          randomize: boolean() | nil,\n          tasks: map()\n        }\n\n  defstruct timer: nil,\n            region: nil,\n            chunks: nil,\n            start_after: nil,\n            randomize: nil,\n            tasks: %{}\n\n  def start_link(_args) do\n    timer = Application.get_env(:realtime, :janitor_schedule_timer)\n    start_after = Application.get_env(:realtime, :janitor_run_after_in_ms, 0)\n    chunks = Application.get_env(:realtime, :janitor_chunk_size)\n    randomize = Application.get_env(:realtime, :janitor_schedule_randomize)\n    region = Application.get_env(:realtime, :region)\n\n    state = %__MODULE__{\n      timer: timer,\n      region: region,\n      chunks: chunks,\n      start_after: start_after,\n      randomize: randomize\n    }\n\n    GenServer.start_link(__MODULE__, state, name: __MODULE__)\n  end\n\n  @impl true\n  def init(%__MODULE__{start_after: start_after} = state) do\n    timer = timer(state) + start_after\n    Process.send_after(self(), :delete_old_messages, timer)\n\n    Logger.info(\"Janitor started\")\n    {:ok, state}\n  end\n\n  @table_name Realtime.Tenants.Connect\n  @syn_table :\"syn_registry_by_name_Elixir.Realtime.Tenants.Connect\"\n\n  @impl true\n  def handle_info(:delete_old_messages, state) do\n    Logger.info(\"Janitor started\")\n    %{chunks: chunks, tasks: tasks} = state\n    all_tenants = :ets.select(@table_name, [{{:\"$1\"}, [], [:\"$1\"]}])\n\n    connected_tenants =\n      :ets.select(@syn_table, [{{:\"$1\", :_, :_, :_, :_, :\"$2\"}, [{:==, :\"$2\", {:const, Node.self()}}], [:\"$1\"]}])\n\n    new_tasks =\n      MapSet.new(all_tenants ++ connected_tenants)\n      |> Enum.to_list()\n      |> Stream.chunk_every(chunks)\n      |> Stream.map(fn chunks ->\n        task =\n          Task.Supervisor.async_nolink(\n            __MODULE__.TaskSupervisor,\n            fn -> perform_maintenance_tasks(chunks) end,\n            ordered: false\n          )\n\n        {task.ref, chunks}\n      end)\n      |> Map.new()\n\n    Process.send_after(self(), :delete_old_messages, timer(state))\n\n    {:noreply, %{state | tasks: Map.merge(tasks, new_tasks)}}\n  end\n\n  def handle_info({:DOWN, ref, _, _, :normal}, state) do\n    %{tasks: tasks} = state\n    {tenants, tasks} = Map.pop(tasks, ref)\n    Logger.info(\"Janitor finished for tenants: #{inspect(tenants)}\")\n    {:noreply, %{state | tasks: tasks}}\n  end\n\n  def handle_info({:DOWN, ref, _, _, :killed}, state) do\n    %{tasks: tasks} = state\n    tenants = Map.get(tasks, ref)\n\n    log_error(\n      \"JanitorFailedToDeleteOldMessages\",\n      \"Scheduled cleanup failed for tenants: #{inspect(tenants)}\"\n    )\n\n    {:noreply, %{state | tasks: tasks}}\n  end\n\n  def handle_info(_, state) do\n    {:noreply, state}\n  end\n\n  # Ignore in coverage has the tests would require to await a random amount of minutes up to an hour\n  # coveralls-ignore-start\n  defp timer(%{timer: timer, randomize: true}), do: timer + :timer.minutes(Enum.random(1..59))\n  # coveralls-ignore-stop\n\n  defp timer(%{timer: timer}), do: timer\n\n  defp perform_maintenance_tasks(tenants), do: Enum.map(tenants, &perform_maintenance_task/1)\n\n  defp perform_maintenance_task(tenant_external_id) do\n    Logger.metadata(project: tenant_external_id, external_id: tenant_external_id)\n    Logger.info(\"Janitor starting realtime.messages cleanup\")\n    :ets.delete(@table_name, tenant_external_id)\n\n    with :ok <- MaintenanceTask.run(tenant_external_id) do\n      Logger.info(\"Janitor finished\")\n\n      :ok\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/migrations.ex",
    "content": "defmodule Realtime.Tenants.Migrations do\n  @moduledoc \"\"\"\n  Run Realtime database migrations for tenant's database.\n  \"\"\"\n  use GenServer, restart: :transient\n  use Realtime.Logs\n\n  alias Realtime.Tenants\n  alias Realtime.Database\n  alias Realtime.Registry.Unique\n  alias Realtime.Repo\n  alias Realtime.Api.Tenant\n  alias Realtime.Api\n  alias Realtime.Nodes\n  alias Realtime.GenRpc\n\n  alias Realtime.Tenants.Migrations.{\n    CreateRealtimeSubscriptionTable,\n    CreateRealtimeCheckFiltersTrigger,\n    CreateRealtimeQuoteWal2jsonFunction,\n    CreateRealtimeCheckEqualityOpFunction,\n    CreateRealtimeBuildPreparedStatementSqlFunction,\n    CreateRealtimeCastFunction,\n    CreateRealtimeIsVisibleThroughFiltersFunction,\n    CreateRealtimeApplyRlsFunction,\n    GrantRealtimeUsageToAuthenticatedRole,\n    EnableRealtimeApplyRlsFunctionPostgrest9Compatibility,\n    UpdateRealtimeSubscriptionCheckFiltersFunctionSecurity,\n    UpdateRealtimeBuildPreparedStatementSqlFunctionForCompatibilityWithAllTypes,\n    EnableGenericSubscriptionClaims,\n    AddWalPayloadOnErrorsInApplyRlsFunction,\n    UpdateChangeTimestampToIso8601ZuluFormat,\n    UpdateSubscriptionCheckFiltersFunctionDynamicTableName,\n    UpdateApplyRlsFunctionToApplyIso8601,\n    AddQuotedRegtypesSupport,\n    AddOutputForDataLessThanEqual64BytesWhenPayloadTooLarge,\n    AddQuotedRegtypesBackwardCompatibilitySupport,\n    RecreateRealtimeBuildPreparedStatementSqlFunction,\n    NullPassesFiltersRecreateIsVisibleThroughFilters,\n    UpdateApplyRlsFunctionToPassThroughDeleteEventsOnFilter,\n    MillisecondPrecisionForWalrus,\n    AddInOpToFilters,\n    EnableFilteringOnDeleteRecord,\n    UpdateSubscriptionCheckFiltersForInFilterNonTextTypes,\n    ConvertCommitTimestampToUtc,\n    OutputFullRecordWhenUnchangedToast,\n    CreateListChangesFunction,\n    CreateChannels,\n    SetRequiredGrants,\n    CreateRlsHelperFunctions,\n    EnableChannelsRls,\n    AddChannelsColumnForWriteCheck,\n    AddUpdateGrantToChannels,\n    AddBroadcastsPoliciesTable,\n    AddInsertAndDeleteGrantToChannels,\n    AddPresencesPoliciesTable,\n    CreateRealtimeAdminAndMoveOwnership,\n    RemoveCheckColumns,\n    RedefineAuthorizationTables,\n    FixWalrusRoleHandling,\n    UnloggedMessagesTable,\n    LoggedMessagesTable,\n    FilterDeletePostgresChanges,\n    AddPayloadToMessages,\n    ChangeMessagesIdType,\n    UuidAutoGeneration,\n    MessagesPartitioning,\n    MessagesUsingUuid,\n    FixSendFunction,\n    RecreateEntityIndexUsingBtree,\n    FixSendFunctionPartitionCreation,\n    RealtimeSendHandleExceptionsRemovePartitionCreation,\n    RealtimeSendSetsConfig,\n    RealtimeSubscriptionUnlogged,\n    RealtimeSubscriptionLogged,\n    RemoveUnusedPublications,\n    RealtimeSendSetsTopicConfig,\n    SubscriptionIndexBridgingDisabled,\n    RunSubscriptionIndexBridgingDisabled,\n    BroadcastSendErrorLogging,\n    CreateMessagesReplayIndex,\n    BroadcastSendIncludePayloadId,\n    AddActionToSubscriptions,\n    FilterActionPostgresChanges,\n    FixByteaDoubleEncodingInCast\n  }\n\n  @migrations [\n    {20_211_116_024_918, CreateRealtimeSubscriptionTable},\n    {20_211_116_045_059, CreateRealtimeCheckFiltersTrigger},\n    {20_211_116_050_929, CreateRealtimeQuoteWal2jsonFunction},\n    {20_211_116_051_442, CreateRealtimeCheckEqualityOpFunction},\n    {20_211_116_212_300, CreateRealtimeBuildPreparedStatementSqlFunction},\n    {20_211_116_213_355, CreateRealtimeCastFunction},\n    {20_211_116_213_934, CreateRealtimeIsVisibleThroughFiltersFunction},\n    {20_211_116_214_523, CreateRealtimeApplyRlsFunction},\n    {20_211_122_062_447, GrantRealtimeUsageToAuthenticatedRole},\n    {20_211_124_070_109, EnableRealtimeApplyRlsFunctionPostgrest9Compatibility},\n    {20_211_202_204_204, UpdateRealtimeSubscriptionCheckFiltersFunctionSecurity},\n    {20_211_202_204_605, UpdateRealtimeBuildPreparedStatementSqlFunctionForCompatibilityWithAllTypes},\n    {20_211_210_212_804, EnableGenericSubscriptionClaims},\n    {20_211_228_014_915, AddWalPayloadOnErrorsInApplyRlsFunction},\n    {20_220_107_221_237, UpdateChangeTimestampToIso8601ZuluFormat},\n    {20_220_228_202_821, UpdateSubscriptionCheckFiltersFunctionDynamicTableName},\n    {20_220_312_004_840, UpdateApplyRlsFunctionToApplyIso8601},\n    {20_220_603_231_003, AddQuotedRegtypesSupport},\n    {20_220_603_232_444, AddOutputForDataLessThanEqual64BytesWhenPayloadTooLarge},\n    {20_220_615_214_548, AddQuotedRegtypesBackwardCompatibilitySupport},\n    {20_220_712_093_339, RecreateRealtimeBuildPreparedStatementSqlFunction},\n    {20_220_908_172_859, NullPassesFiltersRecreateIsVisibleThroughFilters},\n    {20_220_916_233_421, UpdateApplyRlsFunctionToPassThroughDeleteEventsOnFilter},\n    {20_230_119_133_233, MillisecondPrecisionForWalrus},\n    {20_230_128_025_114, AddInOpToFilters},\n    {20_230_128_025_212, EnableFilteringOnDeleteRecord},\n    {20_230_227_211_149, UpdateSubscriptionCheckFiltersForInFilterNonTextTypes},\n    {20_230_228_184_745, ConvertCommitTimestampToUtc},\n    {20_230_308_225_145, OutputFullRecordWhenUnchangedToast},\n    {20_230_328_144_023, CreateListChangesFunction},\n    {20_231_018_144_023, CreateChannels},\n    {20_231_204_144_023, SetRequiredGrants},\n    {20_231_204_144_024, CreateRlsHelperFunctions},\n    {20_231_204_144_025, EnableChannelsRls},\n    {20_240_108_234_812, AddChannelsColumnForWriteCheck},\n    {20_240_109_165_339, AddUpdateGrantToChannels},\n    {20_240_227_174_441, AddBroadcastsPoliciesTable},\n    {20_240_311_171_622, AddInsertAndDeleteGrantToChannels},\n    {20_240_321_100_241, AddPresencesPoliciesTable},\n    {20_240_401_105_812, CreateRealtimeAdminAndMoveOwnership},\n    {20_240_418_121_054, RemoveCheckColumns},\n    {20_240_523_004_032, RedefineAuthorizationTables},\n    {20_240_618_124_746, FixWalrusRoleHandling},\n    {20_240_801_235_015, UnloggedMessagesTable},\n    {20_240_805_133_720, LoggedMessagesTable},\n    {20_240_827_160_934, FilterDeletePostgresChanges},\n    {20_240_919_163_303, AddPayloadToMessages},\n    {20_240_919_163_305, ChangeMessagesIdType},\n    {20_241_019_105_805, UuidAutoGeneration},\n    {20_241_030_150_047, MessagesPartitioning},\n    {20_241_108_114_728, MessagesUsingUuid},\n    {20_241_121_104_152, FixSendFunction},\n    {20_241_130_184_212, RecreateEntityIndexUsingBtree},\n    {20_241_220_035_512, FixSendFunctionPartitionCreation},\n    {20_241_220_123_912, RealtimeSendHandleExceptionsRemovePartitionCreation},\n    {20_241_224_161_212, RealtimeSendSetsConfig},\n    {20_250_107_150_512, RealtimeSubscriptionUnlogged},\n    {20_250_110_162_412, RealtimeSubscriptionLogged},\n    {20_250_123_174_212, RemoveUnusedPublications},\n    {20_250_128_220_012, RealtimeSendSetsTopicConfig},\n    {20_250_506_224_012, SubscriptionIndexBridgingDisabled},\n    {20_250_523_164_012, RunSubscriptionIndexBridgingDisabled},\n    {20_250_714_121_412, BroadcastSendErrorLogging},\n    {20_250_905_041_441, CreateMessagesReplayIndex},\n    {20_251_103_001_201, BroadcastSendIncludePayloadId},\n    {20_251_120_212_548, AddActionToSubscriptions},\n    {20_251_120_215_549, FilterActionPostgresChanges},\n    {20_260_218_120_000, FixByteaDoubleEncodingInCast}\n  ]\n\n  defstruct [:tenant_external_id, :settings, migrations_ran: 0]\n\n  @type t :: %__MODULE__{\n          tenant_external_id: binary(),\n          settings: map()\n        }\n\n  @doc \"\"\"\n  Run migrations for the given tenant.\n  \"\"\"\n  @spec run_migrations(Tenant.t()) :: :ok | :noop | {:error, any()}\n  def run_migrations(%Tenant{} = tenant) do\n    if Tenants.run_migrations?(tenant) do\n      %{extensions: [%{settings: settings} | _]} = tenant\n\n      attrs = %__MODULE__{\n        tenant_external_id: tenant.external_id,\n        settings: settings,\n        migrations_ran: tenant.migrations_ran\n      }\n\n      node =\n        case Nodes.get_node_for_tenant(tenant) do\n          {:ok, node, _} -> node\n          {:error, _} -> node()\n        end\n\n      GenRpc.call(node, __MODULE__, :start_migration, [attrs], tenant_id: tenant.external_id, timeout: 50_000)\n    else\n      :noop\n    end\n  end\n\n  def start_migration(attrs) do\n    supervisor =\n      {:via, PartitionSupervisor, {Realtime.Tenants.Migrations.DynamicSupervisor, attrs.tenant_external_id}}\n\n    spec = {__MODULE__, attrs}\n\n    case DynamicSupervisor.start_child(supervisor, spec) do\n      :ignore -> :ok\n      error -> error\n    end\n  end\n\n  def start_link(%__MODULE__{tenant_external_id: tenant_external_id} = attrs) do\n    name = {:via, Registry, {Unique, {__MODULE__, :host, tenant_external_id}}}\n    GenServer.start_link(__MODULE__, attrs, name: name)\n  end\n\n  def init(%__MODULE__{tenant_external_id: tenant_external_id, settings: settings}) do\n    Logger.metadata(external_id: tenant_external_id, project: tenant_external_id)\n\n    case migrate(settings) do\n      :ok ->\n        Task.Supervisor.async_nolink(__MODULE__.TaskSupervisor, Api, :update_migrations_ran, [\n          tenant_external_id,\n          Enum.count(@migrations)\n        ])\n\n        :ignore\n\n      {:error, error} ->\n        {:stop, error}\n    end\n  end\n\n  defp migrate(settings) do\n    settings = Database.from_settings(settings, \"realtime_migrations\", :stop)\n\n    [\n      hostname: settings.hostname,\n      port: settings.port,\n      database: settings.database,\n      password: settings.password,\n      username: settings.username,\n      pool_size: settings.pool_size,\n      backoff_type: settings.backoff_type,\n      socket_options: settings.socket_options,\n      parameters: [application_name: settings.application_name],\n      ssl: settings.ssl\n    ]\n    |> Repo.with_dynamic_repo(fn repo ->\n      Logger.info(\"Applying migrations to #{settings.hostname}\")\n\n      try do\n        opts = [all: true, prefix: \"realtime\", dynamic_repo: repo]\n        Ecto.Migrator.run(Repo, @migrations, :up, opts)\n\n        :ok\n      rescue\n        error ->\n          log_error(\"MigrationsFailedToRun\", error)\n          {:error, error}\n      end\n    end)\n  end\n\n  @doc \"\"\"\n  Create partitions against tenant db connection\n  \"\"\"\n  @spec create_partitions(pid()) :: :ok\n  def create_partitions(db_conn_pid) do\n    Logger.info(\"Creating partitions for realtime.messages\")\n    today = Date.utc_today()\n    yesterday = Date.add(today, -1)\n    future = Date.add(today, 3)\n\n    dates = Date.range(yesterday, future)\n\n    Enum.each(dates, fn date ->\n      partition_name = \"messages_#{date |> Date.to_iso8601() |> String.replace(\"-\", \"_\")}\"\n      start_timestamp = Date.to_string(date)\n      end_timestamp = Date.to_string(Date.add(date, 1))\n\n      Database.transaction(db_conn_pid, fn conn ->\n        query = \"\"\"\n        CREATE TABLE IF NOT EXISTS realtime.#{partition_name}\n        PARTITION OF realtime.messages\n        FOR VALUES FROM ('#{start_timestamp}') TO ('#{end_timestamp}');\n        \"\"\"\n\n        case Postgrex.query(conn, query, []) do\n          {:ok, _} -> Logger.debug(\"Partition #{partition_name} created\")\n          {:error, %Postgrex.Error{postgres: %{code: :duplicate_table}}} -> :ok\n          {:error, error} -> log_error(\"PartitionCreationFailed\", error)\n        end\n      end)\n    end)\n\n    :ok\n  end\n\n  def migrations(), do: @migrations\nend\n"
  },
  {
    "path": "lib/realtime/tenants/rebalancer.ex",
    "content": "defmodule Realtime.Tenants.Rebalancer do\n  @moduledoc \"\"\"\n  Responsible to tell if the executing node is in the correct region for this tenant\n  \"\"\"\n\n  alias Realtime.Api.Tenant\n\n  @spec check(MapSet.t(node), MapSet.t(node), binary) :: :ok | {:error, :wrong_region}\n  def check(previous_nodes_set, current_nodes_set, tenant_id)\n      when is_struct(previous_nodes_set, MapSet) and is_struct(current_nodes_set, MapSet) and is_binary(tenant_id) do\n    # Check if the current nodes set is equal to the previous nodes set\n    # If they are equal it means that the cluster is relatively stable\n    # We can check now if this Connect process is in the correct region\n    if MapSet.equal?(current_nodes_set, previous_nodes_set) do\n      with %Tenant{} = tenant <- Realtime.Tenants.Cache.get_tenant_by_external_id(tenant_id),\n           {:ok, _node, expected_region} <- Realtime.Nodes.get_node_for_tenant(tenant),\n           region when is_binary(region) <- Application.get_env(:realtime, :region) do\n        if region == expected_region do\n          :ok\n        else\n          {:error, :wrong_region}\n        end\n      else\n        _ -> :ok\n      end\n    else\n      # Nodes have changed, we can assume that the cluster is not stable enough to rebalance\n      :ok\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/replication_connection/watchdog.ex",
    "content": "defmodule Realtime.Tenants.ReplicationConnection.Watchdog do\n  @moduledoc \"\"\"\n  Monitors ReplicationConnection health by performing periodic call checks.\n  If the call times out, logs an error and shuts down, which cascades to ReplicationConnection.\n  \"\"\"\n  use GenServer\n  use Realtime.Logs\n\n  @default_check_interval :timer.minutes(5)\n  @default_timeout :timer.minutes(1)\n\n  defstruct [:parent_pid, :tenant_id, :check_interval, :timeout]\n\n  def start_link(opts), do: GenServer.start_link(__MODULE__, opts)\n\n  @impl true\n  def init(opts) do\n    parent_pid = Keyword.fetch!(opts, :parent_pid)\n    tenant_id = Keyword.fetch!(opts, :tenant_id)\n\n    check_interval =\n      Keyword.get(\n        opts,\n        :watchdog_interval,\n        Application.get_env(:realtime, :replication_watchdog_interval, @default_check_interval)\n      )\n\n    timeout =\n      Keyword.get(\n        opts,\n        :watchdog_timeout,\n        Application.get_env(:realtime, :replication_watchdog_timeout, @default_timeout)\n      )\n\n    Logger.metadata(external_id: tenant_id, project: tenant_id)\n\n    # Schedule first health check\n    Process.send_after(self(), :health_check, check_interval)\n\n    state = %__MODULE__{\n      parent_pid: parent_pid,\n      tenant_id: tenant_id,\n      check_interval: check_interval,\n      timeout: timeout\n    }\n\n    {:ok, state}\n  end\n\n  @impl true\n  def handle_info(:health_check, state) do\n    try do\n      case Realtime.Tenants.ReplicationConnection.health_check(state.parent_pid, state.timeout) do\n        :ok ->\n          Process.send_after(self(), :health_check, state.check_interval)\n          {:noreply, state}\n      end\n    catch\n      :exit, {:timeout, _} ->\n        log_error(\n          \"ReplicationConnectionWatchdogTimeout\",\n          \"ReplicationConnection is not responding\"\n        )\n\n        {:stop, :watchdog_timeout, state}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/replication_connection.ex",
    "content": "defmodule Realtime.Tenants.ReplicationConnection do\n  @moduledoc \"\"\"\n  ReplicationConnection it's the module that provides a way to stream data from a PostgreSQL database using logical replication.\n\n  ## Struct parameters\n  * `connection_opts` - The connection options to connect to the database.\n  * `table` - The table to replicate. If `:all` is passed, it will replicate all tables.\n  * `schema` - The schema of the table to replicate. If not provided, it will use the `public` schema. If `:all` is passed, this option is ignored.\n  * `opts` - The options to pass to this module\n  * `step` - The current step of the replication process\n  * `publication_name` - The name of the publication to create. If not provided, it will use the schema and table name.\n  * `replication_slot_name` - The name of the replication slot to create. If not provided, it will use the schema and table name.\n  * `output_plugin` - The output plugin to use. Default is `pgoutput`.\n  * `proto_version` - The protocol version to use. Default is `1`.\n  * `handler_module` - The module that will handle the data received from the replication stream.\n  * `metadata` - The metadata to pass to the handler module.\n\n  \"\"\"\n  use Postgrex.ReplicationConnection\n  use Realtime.Logs\n\n  import Realtime.Adapters.Postgres.Protocol\n  import Realtime.Adapters.Postgres.Decoder\n\n  alias Realtime.Adapters.Postgres.Decoder\n  alias Realtime.Adapters.Postgres.Protocol.KeepAlive\n  alias Realtime.Adapters.Postgres.Protocol.Write\n  alias Realtime.Api.Tenant\n  alias Realtime.Database\n  alias Realtime.Telemetry\n  alias Realtime.Tenants.BatchBroadcast\n  alias Realtime.Tenants.Cache\n\n  @type t :: %__MODULE__{\n          tenant_id: String.t(),\n          opts: Keyword.t(),\n          step:\n            :disconnected\n            | :check_replication_slot\n            | :create_publication\n            | :check_publication\n            | :validate_publication\n            | :create_slot\n            | :start_replication_slot\n            | :streaming,\n          publication_name: String.t(),\n          replication_slot_name: String.t(),\n          output_plugin: String.t(),\n          proto_version: integer(),\n          relations: map(),\n          buffer: list(),\n          monitored_pid: pid(),\n          latency_committed_at: integer()\n        }\n  defstruct tenant_id: nil,\n            opts: [],\n            step: :disconnected,\n            publication_name: nil,\n            replication_slot_name: nil,\n            output_plugin: \"pgoutput\",\n            proto_version: 2,\n            relations: %{},\n            buffer: [],\n            monitored_pid: nil,\n            latency_committed_at: nil\n\n  defmodule Wrapper do\n    @moduledoc \"\"\"\n    This GenServer exists at the moment so that we can have an init timeout for ReplicationConnection\n    \"\"\"\n    use GenServer\n\n    def start_link(args, init_timeout) do\n      GenServer.start_link(__MODULE__, args, timeout: init_timeout)\n    end\n\n    @impl true\n    def init(args) do\n      case Realtime.Tenants.ReplicationConnection.start_link(args) do\n        {:ok, pid} -> {:ok, pid}\n        {:error, reason} -> {:stop, reason}\n      end\n    end\n  end\n\n  @default_init_timeout 30_000\n  @table \"messages\"\n  @schema \"realtime\"\n  @doc \"\"\"\n  Starts the replication connection for a tenant and monitors a given pid to stop the ReplicationConnection.\n  \"\"\"\n  @spec start(Realtime.Api.Tenant.t(), pid()) :: {:ok, pid()} | {:error, any()}\n  def start(tenant, monitored_pid, init_timeout \\\\ @default_init_timeout) do\n    Logger.info(\"Starting replication for Broadcast Changes\")\n    opts = %__MODULE__{tenant_id: tenant.external_id, monitored_pid: monitored_pid}\n    supervisor_spec = supervisor_spec(tenant)\n\n    child_spec = %{\n      id: __MODULE__,\n      start: {Wrapper, :start_link, [opts, init_timeout]},\n      restart: :temporary,\n      type: :worker\n    }\n\n    case DynamicSupervisor.start_child(supervisor_spec, child_spec) do\n      {:ok, pid} ->\n        {:ok, pid}\n\n      {:error, {:already_started, pid}} ->\n        {:ok, pid}\n\n      {:error, {:bad_return_from_init, {:stop, error, _}}} ->\n        {:error, error}\n\n      {:error, %Postgrex.Error{postgres: %{pg_code: pg_code}}} when pg_code in ~w(53300 53400) ->\n        {:error, :max_wal_senders_reached}\n\n      {:error, :timeout} ->\n        {:error, :replication_connection_timeout}\n\n      error ->\n        error\n    end\n  end\n\n  @doc \"\"\"\n  Finds replication connection by tenant_id\n  \"\"\"\n  @spec whereis(String.t()) :: pid() | nil\n  def whereis(tenant_id) do\n    case Registry.lookup(Realtime.Registry.Unique, {__MODULE__, tenant_id}) do\n      [{pid, _}] -> pid\n      [] -> nil\n    end\n  end\n\n  @spec health_check(pid(), timeout()) :: :ok | no_return()\n  def health_check(pid, timeout), do: Postgrex.ReplicationConnection.call(pid, :health_check, timeout)\n\n  def start_link(%__MODULE__{tenant_id: tenant_id} = attrs) do\n    tenant = Cache.get_tenant_by_external_id(tenant_id)\n    connection_opts = Database.from_tenant(tenant, \"realtime_broadcast_changes\", :stop)\n\n    connection_opts =\n      [\n        name: {:via, Registry, {Realtime.Registry.Unique, {__MODULE__, tenant_id}}},\n        hostname: connection_opts.hostname,\n        username: connection_opts.username,\n        password: connection_opts.password,\n        database: connection_opts.database,\n        port: connection_opts.port,\n        socket_options: connection_opts.socket_options,\n        ssl: connection_opts.ssl,\n        sync_connect: true,\n        auto_reconnect: false,\n        parameters: [application_name: \"realtime_replication_connection\"]\n      ]\n\n    case Postgrex.ReplicationConnection.start_link(__MODULE__, attrs, connection_opts) do\n      {:ok, pid} -> {:ok, pid}\n      {:error, {:already_started, pid}} -> {:ok, pid}\n      {:error, {:bad_return_from_init, {:stop, error}}} -> {:error, error}\n      {:error, error} -> {:error, error}\n    end\n  end\n\n  @impl true\n  def init(%__MODULE__{tenant_id: tenant_id, monitored_pid: monitored_pid} = state) do\n    Process.flag(:fullsweep_after, 20)\n    Logger.metadata(external_id: tenant_id, project: tenant_id)\n    Process.monitor(monitored_pid)\n\n    {:ok, _watchdog_pid} =\n      Realtime.Tenants.ReplicationConnection.Watchdog.start_link(parent_pid: self(), tenant_id: tenant_id)\n\n    state = %{\n      state\n      | publication_name: publication_name(@schema, @table),\n        replication_slot_name: replication_slot_name(@schema, @table)\n    }\n\n    Logger.info(\"Initializing connection with the status: #{inspect(state, pretty: true)}\")\n\n    {:ok, state}\n  end\n\n  @impl true\n  def handle_connect(state) do\n    replication_slot_name = replication_slot_name(@schema, @table)\n    Logger.info(\"Checking if replication slot #{replication_slot_name} exists\")\n\n    query = \"SELECT * FROM pg_replication_slots WHERE slot_name = '#{replication_slot_name}'\"\n\n    {:query, query, %{state | step: :check_replication_slot}}\n  end\n\n  @impl true\n  def handle_result([%Postgrex.Result{num_rows: 1}], %__MODULE__{step: :check_replication_slot} = _state) do\n    Logger.info(\"Replication slot already exists and in use, deferring connection\")\n    {:disconnect, {:shutdown, :replication_slot_in_use}}\n  end\n\n  def handle_result([%Postgrex.Result{num_rows: 0}], %__MODULE__{step: :check_replication_slot} = state) do\n    %__MODULE__{\n      output_plugin: output_plugin,\n      replication_slot_name: replication_slot_name,\n      step: :check_replication_slot\n    } = state\n\n    Logger.info(\"Create replication slot #{replication_slot_name} using plugin #{output_plugin}\")\n\n    query = \"CREATE_REPLICATION_SLOT #{replication_slot_name} TEMPORARY LOGICAL #{output_plugin} NOEXPORT_SNAPSHOT\"\n\n    {:query, query, %{state | step: :check_publication}}\n  end\n\n  def handle_result([%Postgrex.Result{}], %__MODULE__{step: :check_publication} = state) do\n    %__MODULE__{publication_name: publication_name} = state\n\n    Logger.info(\"Check publication #{publication_name} for table #{@schema}.#{@table} exists\")\n    query = \"SELECT * FROM pg_publication WHERE pubname = '#{publication_name}'\"\n\n    {:query, query, %{state | step: :create_publication}}\n  end\n\n  def handle_result([%Postgrex.Result{num_rows: 0}], %__MODULE__{step: :create_publication} = state) do\n    %__MODULE__{publication_name: publication_name} = state\n\n    Logger.info(\"Create publication #{publication_name} for table #{@schema}.#{@table}\")\n    query = \"CREATE PUBLICATION #{publication_name} FOR TABLE #{@schema}.#{@table}\"\n\n    {:query, query, %{state | step: :start_replication_slot}}\n  end\n\n  def handle_result([%Postgrex.Result{num_rows: 1}], %__MODULE__{step: :create_publication} = state) do\n    %__MODULE__{publication_name: publication_name} = state\n\n    Logger.info(\"Publication #{publication_name} exists, validating contents\")\n\n    query = \"\"\"\n      SELECT schemaname, tablename\n      FROM pg_publication_tables\n      WHERE pubname = '#{publication_name}'\n    \"\"\"\n\n    {:query, query, %{state | step: :validate_publication}}\n  end\n\n  def handle_result([%Postgrex.Result{rows: rows}], %__MODULE__{step: :validate_publication} = state) do\n    %__MODULE__{publication_name: publication_name} = state\n\n    valid_tables =\n      Enum.all?(rows, fn [schema, table] ->\n        schema == @schema and (table == @table or String.starts_with?(table, \"#{@table}_\"))\n      end)\n\n    if valid_tables and rows != [] do\n      {:query, \"SELECT 1\", %{state | step: :start_replication_slot}}\n    else\n      query =\n        \"DROP PUBLICATION IF EXISTS #{publication_name}; CREATE PUBLICATION #{publication_name} FOR TABLE #{@schema}.#{@table}\"\n\n      Logger.warning(\"Publication #{publication_name} contains unexpected tables. Recreating...\")\n      {:query, query, %{state | step: :start_replication_slot}}\n    end\n  end\n\n  def handle_result(%Postgrex.Error{postgres: %{message: message}}, %__MODULE__{step: :start_replication_slot} = _state) do\n    {:disconnect, \"Error starting replication: #{message}\"}\n  end\n\n  def handle_result(%Postgrex.Error{message: message}, %__MODULE__{step: :start_replication_slot} = _state) do\n    {:disconnect, \"Error starting replication: #{message}\"}\n  end\n\n  def handle_result(results, %__MODULE__{step: :start_replication_slot} = state) do\n    error = Enum.find(results, fn res -> match?(%Postgrex.Error{}, res) end)\n\n    if error do\n      {:disconnect, \"Error starting replication: #{error.message}\"}\n    else\n      %__MODULE__{\n        proto_version: proto_version,\n        replication_slot_name: replication_slot_name,\n        publication_name: publication_name\n      } = state\n\n      Logger.info(\n        \"Starting stream replication for slot #{replication_slot_name} using publication #{publication_name} and protocol version #{proto_version}\"\n      )\n\n      query =\n        \"START_REPLICATION SLOT #{replication_slot_name} LOGICAL 0/0 (proto_version '#{proto_version}', publication_names '#{publication_name}', binary 'true')\"\n\n      {:stream, query, [], %{state | step: :streaming}}\n    end\n  end\n\n  def handle_result(%Postgrex.Error{postgres: %{pg_code: pg_code}}, _state) when pg_code in ~w(53300 53400) do\n    {:disconnect, :max_wal_senders_reached}\n  end\n\n  def handle_result(%Postgrex.Error{postgres: %{message: message}}, _state) do\n    {:disconnect, \"Error starting replication: #{message}\"}\n  end\n\n  @impl true\n  def handle_data(data, state) when is_keep_alive(data) do\n    %KeepAlive{reply: reply, wal_end: wal_end} = parse(data)\n    wal_end = wal_end + 1\n\n    message =\n      case reply do\n        :now -> standby_status(wal_end, wal_end, wal_end, reply)\n        :later -> hold()\n      end\n\n    {:noreply, message, state}\n  end\n\n  def handle_data(data, state) when is_write(data) do\n    %Write{message: message} = parse(data)\n    message |> decode_message(state.relations) |> then(&handle_message(&1, state))\n  end\n\n  def handle_data(e, state) do\n    log_error(\"UnexpectedMessageReceived\", e)\n    {:noreply, [], state}\n  end\n\n  @impl true\n  def handle_call(:health_check, from, state) do\n    Postgrex.ReplicationConnection.reply(from, :ok)\n    {:noreply, state}\n  end\n\n  @impl true\n\n  def handle_info({:DOWN, _, :process, _, _}, _), do: {:disconnect, :shutdown}\n  def handle_info(_, state), do: {:noreply, state}\n\n  defp handle_message(%Decoder.Messages.Begin{commit_timestamp: commit_timestamp}, state) do\n    latency_committed_at = NaiveDateTime.utc_now() |> NaiveDateTime.diff(commit_timestamp, :millisecond)\n    {:noreply, %{state | latency_committed_at: latency_committed_at}}\n  end\n\n  defp handle_message(%Decoder.Messages.Relation{} = msg, state) do\n    %Decoder.Messages.Relation{id: id, namespace: namespace, name: name, columns: columns} = msg\n    # Only care about relations with namespace=realtime and name starting with messages\n    if namespace == @schema and String.starts_with?(name, @table) do\n      %{relations: relations} = state\n      relation = %{name: name, columns: columns, namespace: namespace}\n      relations = Map.put(relations, id, relation)\n      {:noreply, %{state | relations: relations}}\n    else\n      Logger.warning(\"Unexpected relation on schema '#{namespace}' and table '#{name}'\")\n      {:noreply, state}\n    end\n  rescue\n    e ->\n      log_error(\"UnableToBroadcastChanges\", e)\n      {:noreply, state}\n  catch\n    e ->\n      log_error(\"UnableToBroadcastChanges\", e)\n      {:noreply, state}\n  end\n\n  defp handle_message(%Decoder.Messages.Insert{} = msg, state) do\n    %Decoder.Messages.Insert{relation_id: relation_id, tuple_data: tuple_data} = msg\n    %{relations: relations, tenant_id: tenant_id, latency_committed_at: latency_committed_at} = state\n\n    with %{columns: columns} <- Map.get(relations, relation_id),\n         to_broadcast = tuple_to_map(tuple_data, columns),\n         {:ok, payload} <- get_or_error(to_broadcast, \"payload\", :payload_missing),\n         {:ok, inserted_at} <- get_or_error(to_broadcast, \"inserted_at\", :inserted_at_missing),\n         {:ok, event} <- get_or_error(to_broadcast, \"event\", :event_missing),\n         {:ok, id} <- get_or_error(to_broadcast, \"id\", :id_missing),\n         {:ok, topic} <- get_or_error(to_broadcast, \"topic\", :topic_missing),\n         {:ok, private} <- get_or_error(to_broadcast, \"private\", :private_missing),\n         %Tenant{} = tenant <- Cache.get_tenant_by_external_id(tenant_id),\n         broadcast_message = %{\n           id: id,\n           topic: topic,\n           event: event,\n           private: private,\n           payload: Jason.Fragment.new(payload)\n         },\n         :ok <- BatchBroadcast.broadcast(nil, tenant, %{messages: [broadcast_message]}, true) do\n      latency_inserted_at = NaiveDateTime.utc_now(:microsecond) |> NaiveDateTime.diff(inserted_at, :microsecond)\n\n      Telemetry.execute(\n        [:realtime, :tenants, :broadcast_from_database],\n        %{latency_committed_at: latency_committed_at, latency_inserted_at: latency_inserted_at},\n        %{tenant: tenant_id}\n      )\n\n      {:noreply, state}\n    else\n      {:error, %Ecto.Changeset{valid?: false} = changeset} ->\n        error = Ecto.Changeset.traverse_errors(changeset, &elem(&1, 0))\n        log_error(\"UnableToBroadcastChanges\", error)\n        {:noreply, state}\n\n      {:error, error} ->\n        log_error(\"UnableToBroadcastChanges\", error)\n        {:noreply, state}\n\n      _ ->\n        {:noreply, state}\n    end\n  rescue\n    e ->\n      log_error(\"UnableToBroadcastChanges\", e)\n      {:noreply, state}\n  catch\n    e ->\n      log_error(\"UnableToBroadcastChanges\", e)\n      {:noreply, state}\n  end\n\n  defp handle_message(_, state), do: {:noreply, state}\n  @impl true\n  def handle_disconnect(state) do\n    Logger.warning(\"Disconnecting broadcast changes handler in the step : #{inspect(state.step)}\")\n    {:noreply, %{state | step: :disconnected}}\n  end\n\n  @spec supervisor_spec(Tenant.t()) :: term()\n  def supervisor_spec(%Tenant{external_id: tenant_id}) do\n    {:via, PartitionSupervisor, {__MODULE__.DynamicSupervisor, tenant_id}}\n  end\n\n  def publication_name(schema, table) do\n    \"supabase_#{schema}_#{table}_publication\"\n  end\n\n  def replication_slot_name(schema, table) do\n    \"supabase_#{schema}_#{table}_replication_slot_#{slot_suffix()}\"\n  end\n\n  defp slot_suffix, do: Application.get_env(:realtime, :slot_name_suffix)\n\n  defp tuple_to_map(tuple_data, columns) do\n    tuple_data\n    |> Tuple.to_list()\n    |> Enum.zip(columns)\n    |> Map.new(fn\n      {nil, %{name: name}} -> {name, nil}\n      {value, %{name: name, type: \"bool\"}} -> {name, value}\n      {value, %{name: name}} -> {name, value}\n    end)\n  end\n\n  defp get_or_error(map, key, error_type) do\n    case Map.get(map, key) do\n      nil -> {:error, error_type}\n      value -> {:ok, value}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211116024918_create_realtime_subscription_table.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateRealtimeSubscriptionTable do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\"\"\n    DO $$\n    BEGIN\n        IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'equality_op') THEN\n            CREATE TYPE realtime.equality_op AS ENUM(\n              'eq', 'neq', 'lt', 'lte', 'gt', 'gte'\n            );\n        END IF;\n    END$$;\n    \"\"\")\n\n    execute(\"\"\"\n    DO $$\n    BEGIN\n        IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'user_defined_filter') THEN\n            CREATE TYPE realtime.user_defined_filter as (\n              column_name text,\n              op realtime.equality_op,\n              value text\n            );\n        END IF;\n    END$$;\n    \"\"\")\n\n    execute(\"create table if not exists realtime.subscription (\n      -- Tracks which users are subscribed to each table\n      id bigint not null generated always as identity,\n      user_id uuid not null,\n      -- Populated automatically by trigger. Required to enable auth.email()\n      email varchar(255),\n      entity regclass not null,\n      filters realtime.user_defined_filter[] not null default '{}',\n      created_at timestamp not null default timezone('utc', now()),\n\n      constraint pk_subscription primary key (id),\n      unique (entity, user_id, filters)\n    )\")\n\n    execute(\"create index if not exists ix_realtime_subscription_entity on realtime.subscription using hash (entity)\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211116045059_create_realtime_check_filters_trigger.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateRealtimeCheckFiltersTrigger do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"create or replace function realtime.subscription_check_filters()\n      returns trigger\n      language plpgsql\n    as $$\n    /*\n    Validates that the user defined filters for a subscription:\n    - refer to valid columns that 'authenticated' may access\n    - values are coercable to the correct column type\n    */\n    declare\n      col_names text[] = coalesce(\n        array_agg(c.column_name order by c.ordinal_position),\n        '{}'::text[]\n      )\n        from\n          information_schema.columns c\n        where\n          (quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))::regclass = new.entity\n          and pg_catalog.has_column_privilege('authenticated', new.entity, c.column_name, 'SELECT');\n      filter realtime.user_defined_filter;\n      col_type text;\n    begin\n      for filter in select * from unnest(new.filters) loop\n        -- Filtered column is valid\n        if not filter.column_name = any(col_names) then\n          raise exception 'invalid column for filter %', filter.column_name;\n        end if;\n\n        -- Type is sanitized and safe for string interpolation\n        col_type = (\n          select atttypid::regtype\n          from pg_catalog.pg_attribute\n          where attrelid = new.entity\n            and attname = filter.column_name\n        )::text;\n        if col_type is null then\n          raise exception 'failed to lookup type for column %', filter.column_name;\n        end if;\n        -- raises an exception if value is not coercable to type\n        perform format('select %s::%I', filter.value, col_type);\n      end loop;\n\n      -- Apply consistent order to filters so the unique constraint on\n      -- (user_id, entity, filters) can't be tricked by a different filter order\n      new.filters = coalesce(\n        array_agg(f order by f.column_name, f.op, f.value),\n        '{}'\n      ) from unnest(new.filters) f;\n\n      return new;\n    end;\n    $$;\")\n\n    execute(\"create trigger tr_check_filters\n    before insert or update on realtime.subscription\n    for each row\n    execute function realtime.subscription_check_filters();\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211116050929_create_realtime_quote_wal2json_function.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateRealtimeQuoteWal2jsonFunction do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"create or replace function realtime.quote_wal2json(entity regclass)\n      returns text\n      language sql\n      immutable\n      strict\n    as $$\n      select\n        (\n          select string_agg('\\' || ch,'')\n          from unnest(string_to_array(nsp.nspname::text, null)) with ordinality x(ch, idx)\n          where\n            not (x.idx = 1 and x.ch = '\\\"')\n            and not (\n              x.idx = array_length(string_to_array(nsp.nspname::text, null), 1)\n              and x.ch = '\\\"'\n            )\n        )\n        || '.'\n        || (\n          select string_agg('\\' || ch,'')\n          from unnest(string_to_array(pc.relname::text, null)) with ordinality x(ch, idx)\n          where\n            not (x.idx = 1 and x.ch = '\\\"')\n            and not (\n              x.idx = array_length(string_to_array(nsp.nspname::text, null), 1)\n              and x.ch = '\\\"'\n            )\n          )\n      from\n        pg_class pc\n        join pg_namespace nsp\n          on pc.relnamespace = nsp.oid\n      where\n        pc.oid = entity\n    $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211116051442_create_realtime_check_equality_op_function.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateRealtimeCheckEqualityOpFunction do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"create or replace function realtime.check_equality_op(\n      op realtime.equality_op,\n      type_ regtype,\n      val_1 text,\n      val_2 text\n    )\n      returns bool\n      immutable\n      language plpgsql\n    as $$\n    /*\n    Casts *val_1* and *val_2* as type *type_* and check the *op* condition for truthiness\n    */\n    declare\n      op_symbol text = (\n        case\n          when op = 'eq' then '='\n          when op = 'neq' then '!='\n          when op = 'lt' then '<'\n          when op = 'lte' then '<='\n          when op = 'gt' then '>'\n          when op = 'gte' then '>='\n          else 'UNKNOWN OP'\n        end\n      );\n      res boolean;\n    begin\n      execute format('select %L::'|| type_::text || ' ' || op_symbol || ' %L::'|| type_::text, val_1, val_2) into res;\n      return res;\n    end;\n    $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211116212300_create_realtime_build_prepared_statement_sql_function.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateRealtimeBuildPreparedStatementSqlFunction do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\"\"\n    DO $$\n    DECLARE\n        type_oid oid;\n    BEGIN\n        SELECT oid INTO type_oid\n        FROM pg_type\n        WHERE typname = 'wal_column' AND typnamespace = 'realtime'::regnamespace;\n\n        -- Drop if it exists without the legacy 'type' column (e.g. pre-initialized by supabase-postgres)\n        IF type_oid IS NOT NULL AND NOT EXISTS (\n            SELECT 1 FROM pg_attribute WHERE attrelid = (SELECT typrelid FROM pg_type WHERE oid = type_oid) AND attname = 'type'\n        ) THEN\n            DROP TYPE realtime.wal_column CASCADE;\n            type_oid := NULL;\n        END IF;\n\n        IF type_oid IS NULL THEN\n            CREATE TYPE realtime.wal_column AS (\n              name text,\n              type text,\n              value jsonb,\n              is_pkey boolean,\n              is_selectable boolean\n            );\n        END IF;\n    END$$;\n    \"\"\")\n\n    execute(\"create or replace function realtime.build_prepared_statement_sql(\n      prepared_statement_name text,\n      entity regclass,\n      columns realtime.wal_column[]\n    )\n      returns text\n      language sql\n    as $$\n    /*\n    Builds a sql string that, if executed, creates a prepared statement to\n    tests retrive a row from *entity* by its primary key columns.\n\n    Example\n      select realtime.build_prepared_statment_sql('public.notes', '{\\\"id\\\"}'::text[], '{\\\"bigint\\\"}'::text[])\n    */\n      select\n    'prepare ' || prepared_statement_name || ' as\n      select\n        exists(\n          select\n            1\n          from\n            ' || entity || '\n          where\n            ' || string_agg(quote_ident(pkc.name) || '=' || quote_nullable(pkc.value) , ' and ') || '\n        )'\n      from\n        unnest(columns) pkc\n      where\n        pkc.is_pkey\n      group by\n        entity\n    $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211116213355_create_realtime_cast_function.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateRealtimeCastFunction do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"create or replace function realtime.cast(val text, type_ regtype)\n      returns jsonb\n      immutable\n      language plpgsql\n    as $$\n    declare\n      res jsonb;\n    begin\n      execute format('select to_jsonb(%L::'|| type_::text || ')', val)  into res;\n      return res;\n    end\n    $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211116213934_create_realtime_is_visible_through_filters_function.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateRealtimeIsVisibleThroughFiltersFunction do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\n      \"create or replace function realtime.is_visible_through_filters(columns realtime.wal_column[], filters realtime.user_defined_filter[])\n      returns bool\n      language sql\n      immutable\n    as $$\n    /*\n    Should the record be visible (true) or filtered out (false) after *filters* are applied\n    */\n    select\n      -- Default to allowed when no filters present\n      coalesce(\n        sum(\n          realtime.check_equality_op(\n            op:=f.op,\n            type_:=col.type::regtype,\n            -- cast jsonb to text\n            val_1:=col.value #>> '{}',\n            val_2:=f.value\n          )::int\n        ) = count(1),\n        true\n      )\n    from\n      unnest(filters) f\n      join unnest(columns) col\n          on f.column_name = col.name;\n    $$;\"\n    )\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211116214523_create_realtime_apply_rls_function.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateRealtimeApplyRlsFunction do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\"\"\n    DO $$\n    BEGIN\n        IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'action') THEN\n            CREATE TYPE realtime.action AS ENUM (\n              'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'ERROR'\n            );\n        END IF;\n    END$$;\n    \"\"\")\n\n    execute(\"\"\"\n    DO $$\n    BEGIN\n        IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'wal_rls') THEN\n            CREATE TYPE realtime.wal_rls AS (\n              wal jsonb,\n              is_rls_enabled boolean,\n              users uuid[],\n              errors text[]\n            );\n        END IF;\n    END$$;\n    \"\"\")\n\n    execute(\"create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n      returns realtime.wal_rls\n      language plpgsql\n      volatile\n    as $$\n    declare\n      -- Regclass of the table e.g. public.notes\n      entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n      -- I, U, D, T: insert, update ...\n      action realtime.action = (\n        case wal ->> 'action'\n          when 'I' then 'INSERT'\n          when 'U' then 'UPDATE'\n          when 'D' then 'DELETE'\n          when 'T' then 'TRUNCATE'\n          else 'ERROR'\n        end\n      );\n\n      -- Is row level security enabled for the table\n      is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n      -- Subscription vars\n      user_id uuid;\n      email varchar(255);\n      user_has_access bool;\n      is_visible_to_user boolean;\n      visible_to_user_ids uuid[] = '{}';\n\n      -- user subscriptions to the wal record's table\n      subscriptions realtime.subscription[] =\n        array_agg(sub)\n        from\n          realtime.subscription sub\n        where\n          sub.entity = entity_;\n\n      -- structured info for wal's columns\n      columns realtime.wal_column[] =\n        array_agg(\n          (\n            x->>'name',\n            x->>'type',\n            realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),\n            (pks ->> 'name') is not null,\n            pg_catalog.has_column_privilege('authenticated', entity_, x->>'name', 'SELECT')\n          )::realtime.wal_column\n        )\n        from\n          jsonb_array_elements(wal -> 'columns') x\n          left join jsonb_array_elements(wal -> 'pk') pks\n            on (x ->> 'name') = (pks ->> 'name');\n\n      -- previous identity values for update/delete\n      old_columns realtime.wal_column[] =\n        array_agg(\n          (\n            x->>'name',\n            x->>'type',\n            realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),\n            (pks ->> 'name') is not null,\n            pg_catalog.has_column_privilege('authenticated', entity_, x->>'name', 'SELECT')\n          )::realtime.wal_column\n        )\n        from\n          jsonb_array_elements(wal -> 'identity') x\n          left join jsonb_array_elements(wal -> 'pk') pks\n            on (x ->> 'name') = (pks ->> 'name');\n\n      output jsonb;\n\n      -- Error states\n      error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n      error_unauthorized boolean = not pg_catalog.has_any_column_privilege('authenticated', entity_, 'SELECT');\n\n      errors text[] = case\n        when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n        else '{}'::text[]\n      end;\n    begin\n\n      -- The 'authenticated' user does not have SELECT permission on any of the columns for the entity_\n      if error_unauthorized is true then\n        return (\n          null,\n          null,\n          visible_to_user_ids,\n          array['Error 401: Unauthorized']\n        )::realtime.wal_rls;\n      end if;\n\n      -------------------------------\n      -- Build Output JSONB Object --\n      -------------------------------\n      output = jsonb_build_object(\n        'schema', wal ->> 'schema',\n        'table', wal ->> 'table',\n        'type', action,\n        'commit_timestamp', (wal ->> 'timestamp')::text::timestamp with time zone,\n        'columns', (\n          select\n            jsonb_agg(\n              jsonb_build_object(\n                'name', pa.attname,\n                'type', pt.typname\n              )\n              order by pa.attnum asc\n            )\n            from\n              pg_attribute pa\n              join pg_type pt\n                on pa.atttypid = pt.oid\n            where\n              attrelid = entity_\n              and attnum > 0\n              and pg_catalog.has_column_privilege('authenticated', entity_, pa.attname, 'SELECT')\n        )\n      )\n      -- Add \\\"record\\\" key for insert and update\n      || case\n        when error_record_exceeds_max_size then jsonb_build_object('record', '{}'::jsonb)\n        when action in ('INSERT', 'UPDATE') then\n          jsonb_build_object(\n            'record',\n            (select jsonb_object_agg((c).name, (c).value) from unnest(columns) c where (c).is_selectable)\n          )\n        else '{}'::jsonb\n      end\n      -- Add \\\"old_record\\\" key for update and delete\n      || case\n        when error_record_exceeds_max_size then jsonb_build_object('old_record', '{}'::jsonb)\n        when action in ('UPDATE', 'DELETE') then\n          jsonb_build_object(\n            'old_record',\n            (select jsonb_object_agg((c).name, (c).value) from unnest(old_columns) c where (c).is_selectable)\n          )\n        else '{}'::jsonb\n      end;\n\n      if action in ('TRUNCATE', 'DELETE') then\n        visible_to_user_ids = array_agg(s.user_id) from unnest(subscriptions) s;\n      else\n        -- If RLS is on and someone is subscribed to the table prep\n        if is_rls_enabled and array_length(subscriptions, 1) > 0 then\n          perform\n            set_config('role', 'authenticated', true),\n            set_config('request.jwt.claim.role', 'authenticated', true);\n\n          if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n            deallocate walrus_rls_stmt;\n          end if;\n          execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n\n        end if;\n\n        -- For each subscribed user\n        for user_id, email, is_visible_to_user in (\n          select\n            subs.user_id,\n            subs.email,\n            realtime.is_visible_through_filters(columns, subs.filters)\n          from\n            unnest(subscriptions) subs\n        )\n        loop\n          if is_visible_to_user then\n            -- If RLS is off, add to visible users\n            if not is_rls_enabled then\n              visible_to_user_ids = visible_to_user_ids || user_id;\n            else\n              -- Check if RLS allows the user to see the record\n              perform\n                set_config('request.jwt.claim.sub', user_id::text, true),\n                set_config('request.jwt.claim.email', email::text, true);\n              execute 'execute walrus_rls_stmt' into user_has_access;\n\n              if user_has_access then\n                visible_to_user_ids = visible_to_user_ids || user_id;\n              end if;\n\n              end if;\n            end if;\n        end loop;\n\n        perform (\n          set_config('role', null, true)\n        );\n\n    end if;\n\n    return (\n      output,\n      is_rls_enabled,\n      visible_to_user_ids,\n      errors\n    )::realtime.wal_rls;\n  end;\n  $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211122062447_grant_realtime_usage_to_authenticated_role.ex",
    "content": "defmodule Realtime.Tenants.Migrations.GrantRealtimeUsageToAuthenticatedRole do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"grant usage on schema realtime to authenticated;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211124070109_enable_realtime_apply_rls_function_postgrest_9_compatibility.ex",
    "content": "defmodule Realtime.Tenants.Migrations.EnableRealtimeApplyRlsFunctionPostgrest9Compatibility do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n      returns realtime.wal_rls\n      language plpgsql\n      volatile\n    as $$\n    declare\n      -- Regclass of the table e.g. public.notes\n      entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n      -- I, U, D, T: insert, update ...\n      action realtime.action = (\n        case wal ->> 'action'\n          when 'I' then 'INSERT'\n          when 'U' then 'UPDATE'\n          when 'D' then 'DELETE'\n          when 'T' then 'TRUNCATE'\n          else 'ERROR'\n        end\n      );\n\n      -- Is row level security enabled for the table\n      is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n      -- Subscription vars\n      user_id uuid;\n      email varchar(255);\n      user_has_access bool;\n      is_visible_to_user boolean;\n      visible_to_user_ids uuid[] = '{}';\n\n      -- user subscriptions to the wal record's table\n      subscriptions realtime.subscription[] =\n        array_agg(sub)\n        from\n          realtime.subscription sub\n        where\n          sub.entity = entity_;\n\n      -- structured info for wal's columns\n      columns realtime.wal_column[] =\n        array_agg(\n          (\n            x->>'name',\n            x->>'type',\n            realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),\n            (pks ->> 'name') is not null,\n            pg_catalog.has_column_privilege('authenticated', entity_, x->>'name', 'SELECT')\n          )::realtime.wal_column\n        )\n        from\n          jsonb_array_elements(wal -> 'columns') x\n          left join jsonb_array_elements(wal -> 'pk') pks\n            on (x ->> 'name') = (pks ->> 'name');\n\n      -- previous identity values for update/delete\n      old_columns realtime.wal_column[] =\n        array_agg(\n          (\n            x->>'name',\n            x->>'type',\n            realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),\n            (pks ->> 'name') is not null,\n            pg_catalog.has_column_privilege('authenticated', entity_, x->>'name', 'SELECT')\n          )::realtime.wal_column\n        )\n        from\n          jsonb_array_elements(wal -> 'identity') x\n          left join jsonb_array_elements(wal -> 'pk') pks\n            on (x ->> 'name') = (pks ->> 'name');\n\n      output jsonb;\n\n      -- Error states\n      error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n      error_unauthorized boolean = not pg_catalog.has_any_column_privilege('authenticated', entity_, 'SELECT');\n\n      errors text[] = case\n        when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n        else '{}'::text[]\n      end;\n    begin\n\n      -- The 'authenticated' user does not have SELECT permission on any of the columns for the entity_\n      if error_unauthorized is true then\n        return (\n          null,\n          null,\n          visible_to_user_ids,\n          array['Error 401: Unauthorized']\n        )::realtime.wal_rls;\n      end if;\n\n      -------------------------------\n      -- Build Output JSONB Object --\n      -------------------------------\n      output = jsonb_build_object(\n        'schema', wal ->> 'schema',\n        'table', wal ->> 'table',\n        'type', action,\n        'commit_timestamp', (wal ->> 'timestamp')::text::timestamp with time zone,\n        'columns', (\n          select\n            jsonb_agg(\n              jsonb_build_object(\n                'name', pa.attname,\n                'type', pt.typname\n              )\n              order by pa.attnum asc\n            )\n            from\n              pg_attribute pa\n              join pg_type pt\n                on pa.atttypid = pt.oid\n            where\n              attrelid = entity_\n              and attnum > 0\n              and pg_catalog.has_column_privilege('authenticated', entity_, pa.attname, 'SELECT')\n        )\n      )\n      -- Add \\\"record\\\" key for insert and update\n      || case\n        when error_record_exceeds_max_size then jsonb_build_object('record', '{}'::jsonb)\n        when action in ('INSERT', 'UPDATE') then\n          jsonb_build_object(\n            'record',\n            (select jsonb_object_agg((c).name, (c).value) from unnest(columns) c where (c).is_selectable)\n          )\n        else '{}'::jsonb\n      end\n      -- Add \\\"old_record\\\" key for update and delete\n      || case\n        when error_record_exceeds_max_size then jsonb_build_object('old_record', '{}'::jsonb)\n        when action in ('UPDATE', 'DELETE') then\n          jsonb_build_object(\n            'old_record',\n            (select jsonb_object_agg((c).name, (c).value) from unnest(old_columns) c where (c).is_selectable)\n          )\n        else '{}'::jsonb\n      end;\n\n      if action in ('TRUNCATE', 'DELETE') then\n        visible_to_user_ids = array_agg(s.user_id) from unnest(subscriptions) s;\n      else\n        -- If RLS is on and someone is subscribed to the table prep\n        if is_rls_enabled and array_length(subscriptions, 1) > 0 then\n          perform set_config('role', 'authenticated', true);\n          if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n            deallocate walrus_rls_stmt;\n          end if;\n          execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n\n        end if;\n\n        -- For each subscribed user\n        for user_id, email, is_visible_to_user in (\n          select\n            subs.user_id,\n            subs.email,\n            realtime.is_visible_through_filters(columns, subs.filters)\n          from\n            unnest(subscriptions) subs\n        )\n        loop\n          if is_visible_to_user then\n            -- If RLS is off, add to visible users\n            if not is_rls_enabled then\n              visible_to_user_ids = visible_to_user_ids || user_id;\n            else\n              -- Check if RLS allows the user to see the record\n              perform\n                set_config(\n                  'request.jwt.claims',\n                  jsonb_build_object(\n                    'sub', user_id::text,\n                    'email', email::text,\n                    'role', 'authenticated'\n                  )::text,\n                  true\n                );\n              execute 'execute walrus_rls_stmt' into user_has_access;\n\n              if user_has_access then\n                visible_to_user_ids = visible_to_user_ids || user_id;\n              end if;\n\n              end if;\n            end if;\n        end loop;\n\n        perform (\n          set_config('role', null, true)\n        );\n\n    end if;\n\n    return (\n      output,\n      is_rls_enabled,\n      visible_to_user_ids,\n      errors\n    )::realtime.wal_rls;\n  end;\n  $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211202204204_update_realtime_subscription_check_filters_function_security.ex",
    "content": "defmodule Realtime.Tenants.Migrations.UpdateRealtimeSubscriptionCheckFiltersFunctionSecurity do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"create or replace function realtime.subscription_check_filters()\n      returns trigger\n      language plpgsql\n    as $$\n    /*\n    Validates that the user defined filters for a subscription:\n    - refer to valid columns that 'authenticated' may access\n    - values are coercable to the correct column type\n    */\n    declare\n      col_names text[] = coalesce(\n        array_agg(c.column_name order by c.ordinal_position),\n        '{}'::text[]\n      )\n        from\n          information_schema.columns c\n        where\n          (quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))::regclass = new.entity\n          and pg_catalog.has_column_privilege('authenticated', new.entity, c.column_name, 'SELECT');\n      filter realtime.user_defined_filter;\n      col_type regtype;\n    begin\n      for filter in select * from unnest(new.filters) loop\n        -- Filtered column is valid\n        if not filter.column_name = any(col_names) then\n          raise exception 'invalid column for filter %', filter.column_name;\n        end if;\n\n        -- Type is sanitized and safe for string interpolation\n        col_type = (\n          select atttypid::regtype\n          from pg_catalog.pg_attribute\n          where attrelid = new.entity\n            and attname = filter.column_name\n        );\n        if col_type is null then\n          raise exception 'failed to lookup type for column %', filter.column_name;\n        end if;\n        -- raises an exception if value is not coercable to type\n        perform realtime.cast(filter.value, col_type);\n      end loop;\n\n      -- Apply consistent order to filters so the unique constraint on\n      -- (user_id, entity, filters) can't be tricked by a different filter order\n      new.filters = coalesce(\n        array_agg(f order by f.column_name, f.op, f.value),\n        '{}'\n      ) from unnest(new.filters) f;\n\n      return new;\n    end;\n    $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211202204605_update_realtime_build_prepared_statement_sql_function_for_compatibility_with_all_types.ex",
    "content": "defmodule Realtime.Tenants.Migrations.UpdateRealtimeBuildPreparedStatementSqlFunctionForCompatibilityWithAllTypes do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"create or replace function realtime.build_prepared_statement_sql(\n      prepared_statement_name text,\n      entity regclass,\n      columns realtime.wal_column[]\n    )\n      returns text\n      language sql\n    as $$\n    /*\n    Builds a sql string that, if executed, creates a prepared statement to\n    tests retrive a row from *entity* by its primary key columns.\n\n    Example\n      select realtime.build_prepared_statment_sql('public.notes', '{\\\"id\\\"}'::text[], '{\\\"bigint\\\"}'::text[])\n    */\n      select\n    'prepare ' || prepared_statement_name || ' as\n      select\n        exists(\n          select\n            1\n          from\n            ' || entity || '\n          where\n            ' || string_agg(quote_ident(pkc.name) || '=' || quote_nullable(pkc.value #>> '{}') , ' and ') || '\n        )'\n      from\n        unnest(columns) pkc\n      where\n        pkc.is_pkey\n      group by\n        entity\n    $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211210212804_enable_generic_subscription_claims.ex",
    "content": "defmodule Realtime.Tenants.Migrations.EnableGenericSubscriptionClaims do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"truncate table realtime.subscription restart identity\")\n\n    execute(\"alter table realtime.subscription\n      drop constraint subscription_entity_user_id_filters_key cascade,\n      drop column email cascade,\n      drop column created_at cascade\")\n\n    execute(\"alter table realtime.subscription rename user_id to subscription_id\")\n\n    execute(\"create or replace function realtime.to_regrole(role_name text)\n      returns regrole\n      immutable\n      language sql\n      -- required to allow use in generated clause\n      as $$ select role_name::regrole $$;\")\n\n    execute(\"alter table realtime.subscription\n      add column claims jsonb not null,\n      add column claims_role regrole not null generated always as (realtime.to_regrole(claims ->> 'role')) stored,\n      add column created_at timestamp not null default timezone('utc', now())\")\n\n    execute(\n      \"create unique index subscription_subscription_id_entity_filters_key on realtime.subscription (subscription_id, entity, filters)\"\n    )\n\n    execute(\"revoke usage on schema realtime from authenticated;\")\n    execute(\"revoke all on realtime.subscription from authenticated;\")\n\n    execute(\"create or replace function realtime.subscription_check_filters()\n      returns trigger\n      language plpgsql\n      as $$\n        /*\n          Validates that the user defined filters for a subscription:\n            - refer to valid columns that the claimed role may access\n            - values are coercable to the correct column type\n        */\n      declare\n        col_names text[] = coalesce(\n            array_agg(c.column_name order by c.ordinal_position),\n            '{}'::text[]\n          )\n          from\n            information_schema.columns c\n          where\n            format('%I.%I', c.table_schema, c.table_name)::regclass = new.entity\n            and pg_catalog.has_column_privilege((new.claims ->> 'role'), new.entity, c.column_name, 'SELECT');\n        filter realtime.user_defined_filter;\n        col_type regtype;\n      begin\n        for filter in select * from unnest(new.filters) loop\n          -- Filtered column is valid\n          if not filter.column_name = any(col_names) then\n            raise exception 'invalid column for filter %', filter.column_name;\n          end if;\n\n          -- Type is sanitized and safe for string interpolation\n          col_type = (\n            select atttypid::regtype\n            from pg_catalog.pg_attribute\n            where attrelid = new.entity\n              and attname = filter.column_name\n          );\n          if col_type is null then\n            raise exception 'failed to lookup type for column %', filter.column_name;\n          end if;\n          -- raises an exception if value is not coercable to type\n          perform realtime.cast(filter.value, col_type);\n        end loop;\n\n        -- Apply consistent order to filters so the unique constraint on\n        -- (subscription_id, entity, filters) can't be tricked by a different filter order\n        new.filters = coalesce(\n          array_agg(f order by f.column_name, f.op, f.value),\n          '{}'\n        ) from unnest(new.filters) f;\n\n        return new;\n      end;\n    $$;\")\n\n    execute(\"alter type realtime.wal_rls rename attribute users to subscription_ids cascade;\")\n\n    execute(\"drop function realtime.apply_rls(jsonb, integer);\")\n    execute(\"create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n      returns setof realtime.wal_rls\n      language plpgsql\n      volatile\n      as $$\n      declare\n        -- Regclass of the table e.g. public.notes\n        entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n        -- I, U, D, T: insert, update ...\n        action realtime.action = (\n          case wal ->> 'action'\n            when 'I' then 'INSERT'\n            when 'U' then 'UPDATE'\n            when 'D' then 'DELETE'\n            else 'ERROR'\n          end\n        );\n\n        -- Is row level security enabled for the table\n        is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n        subscriptions realtime.subscription[] = array_agg(subs)\n          from\n            realtime.subscription subs\n          where\n            subs.entity = entity_;\n\n        -- Subscription vars\n        roles regrole[] = array_agg(distinct us.claims_role)\n          from\n            unnest(subscriptions) us;\n\n        working_role regrole;\n        claimed_role regrole;\n        claims jsonb;\n\n        subscription_id uuid;\n        subscription_has_access bool;\n        visible_to_subscription_ids uuid[] = '{}';\n\n        -- structured info for wal's columns\n        columns realtime.wal_column[];\n        -- previous identity values for update/delete\n        old_columns realtime.wal_column[];\n\n        error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n        -- Primary jsonb output for record\n        output jsonb;\n\n      begin\n        perform set_config('role', null, true);\n\n        columns =\n          array_agg(\n            (\n              x->>'name',\n              x->>'type',\n              realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),\n              (pks ->> 'name') is not null,\n              true\n            )::realtime.wal_column\n          )\n          from\n            jsonb_array_elements(wal -> 'columns') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n              on (x ->> 'name') = (pks ->> 'name');\n\n        old_columns =\n          array_agg(\n            (\n              x->>'name',\n              x->>'type',\n              realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),\n              (pks ->> 'name') is not null,\n              true\n            )::realtime.wal_column\n          )\n          from\n            jsonb_array_elements(wal -> 'identity') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n              on (x ->> 'name') = (pks ->> 'name');\n\n        for working_role in select * from unnest(roles) loop\n\n          -- Update `is_selectable` for columns and old_columns\n          columns =\n            array_agg(\n              (\n                c.name,\n                c.type,\n                c.value,\n                c.is_pkey,\n                pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n              )::realtime.wal_column\n            )\n            from\n              unnest(columns) c;\n\n          old_columns =\n            array_agg(\n              (\n                c.name,\n                c.type,\n                c.value,\n                c.is_pkey,\n                pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n              )::realtime.wal_column\n            )\n            from\n              unnest(old_columns) c;\n\n          if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n            return next (\n              null,\n              is_rls_enabled,\n              -- subscriptions is already filtered by entity\n              (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n              array['Error 400: Bad Request, no primary key']\n            )::realtime.wal_rls;\n\n          -- The claims role does not have SELECT permission to the primary key of entity\n          elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n            return next (\n              null,\n              is_rls_enabled,\n              (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n              array['Error 401: Unauthorized']\n            )::realtime.wal_rls;\n\n          else\n            output = jsonb_build_object(\n              'schema', wal ->> 'schema',\n              'table', wal ->> 'table',\n              'type', action,\n              'commit_timestamp', (wal ->> 'timestamp')::text::timestamp with time zone,\n              'columns', (\n                select\n                  jsonb_agg(\n                    jsonb_build_object(\n                      'name', pa.attname,\n                      'type', pt.typname\n                    )\n                    order by pa.attnum asc\n                  )\n                    from\n                      pg_attribute pa\n                      join pg_type pt\n                        on pa.atttypid = pt.oid\n                    where\n                      attrelid = entity_\n                      and attnum > 0\n                      and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n              )\n            )\n            -- Add \\\"record\\\" key for insert and update\n            || case\n                when error_record_exceeds_max_size then jsonb_build_object('record', '{}'::jsonb)\n                when action in ('INSERT', 'UPDATE') then\n                  jsonb_build_object(\n                    'record',\n                    (select jsonb_object_agg((c).name, (c).value) from unnest(columns) c where (c).is_selectable)\n                  )\n                else '{}'::jsonb\n            end\n            -- Add \\\"old_record\\\" key for update and delete\n            || case\n                when error_record_exceeds_max_size then jsonb_build_object('old_record', '{}'::jsonb)\n                when action in ('UPDATE', 'DELETE') then\n                  jsonb_build_object(\n                    'old_record',\n                    (select jsonb_object_agg((c).name, (c).value) from unnest(old_columns) c where (c).is_selectable)\n                  )\n                else '{}'::jsonb\n            end;\n\n            -- Create the prepared statement\n            if is_rls_enabled and action <> 'DELETE' then\n              if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                deallocate walrus_rls_stmt;\n              end if;\n              execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n            end if;\n\n            visible_to_subscription_ids = '{}';\n\n            for subscription_id, claims in (\n                select\n                  subs.subscription_id,\n                  subs.claims\n                from\n                  unnest(subscriptions) subs\n                where\n                  subs.entity = entity_\n                  and subs.claims_role = working_role\n                  and realtime.is_visible_through_filters(columns, subs.filters)\n              ) loop\n\n              if not is_rls_enabled or action = 'DELETE' then\n                visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n              else\n                -- Check if RLS allows the role to see the record\n                perform\n                  set_config('role', working_role::text, true),\n                  set_config('request.jwt.claims', claims::text, true);\n\n                execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                if subscription_has_access then\n                  visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                end if;\n              end if;\n            end loop;\n\n            perform set_config('role', null, true);\n\n            return next (\n              output,\n              is_rls_enabled,\n              visible_to_subscription_ids,\n              case\n                when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                else '{}'\n              end\n            )::realtime.wal_rls;\n\n          end if;\n        end loop;\n\n        perform set_config('role', null, true);\n      end;\n      $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20211228014915_add_wal_payload_on_errors_in_apply_rls_function.ex",
    "content": "defmodule Realtime.Tenants.Migrations.AddWalPayloadOnErrorsInApplyRlsFunction do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n      returns setof realtime.wal_rls\n      language plpgsql\n      volatile\n    as $$\n    declare\n      -- Regclass of the table e.g. public.notes\n      entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n      -- I, U, D, T: insert, update ...\n      action realtime.action = (\n        case wal ->> 'action'\n          when 'I' then 'INSERT'\n          when 'U' then 'UPDATE'\n          when 'D' then 'DELETE'\n          else 'ERROR'\n        end\n      );\n\n      -- Is row level security enabled for the table\n      is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n      subscriptions realtime.subscription[] = array_agg(subs)\n        from\n          realtime.subscription subs\n        where\n          subs.entity = entity_;\n\n      -- Subscription vars\n      roles regrole[] = array_agg(distinct us.claims_role)\n        from\n          unnest(subscriptions) us;\n\n    working_role regrole;\n    claimed_role regrole;\n    claims jsonb;\n\n    subscription_id uuid;\n    subscription_has_access bool;\n    visible_to_subscription_ids uuid[] = '{}';\n\n    -- structured info for wal's columns\n    columns realtime.wal_column[];\n    -- previous identity values for update/delete\n    old_columns realtime.wal_column[];\n\n    error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n    -- Primary jsonb output for record\n    output jsonb;\n\n  begin\n    perform set_config('role', null, true);\n\n    columns =\n      array_agg(\n        (\n          x->>'name',\n          x->>'type',\n          realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),\n          (pks ->> 'name') is not null,\n          true\n        )::realtime.wal_column\n      )\n      from\n        jsonb_array_elements(wal -> 'columns') x\n        left join jsonb_array_elements(wal -> 'pk') pks\n          on (x ->> 'name') = (pks ->> 'name');\n\n    old_columns =\n      array_agg(\n        (\n          x->>'name',\n          x->>'type',\n          realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),\n          (pks ->> 'name') is not null,\n          true\n        )::realtime.wal_column\n      )\n      from\n        jsonb_array_elements(wal -> 'identity') x\n        left join jsonb_array_elements(wal -> 'pk') pks\n          on (x ->> 'name') = (pks ->> 'name');\n\n    for working_role in select * from unnest(roles) loop\n\n      -- Update `is_selectable` for columns and old_columns\n      columns =\n        array_agg(\n          (\n            c.name,\n            c.type,\n            c.value,\n            c.is_pkey,\n            pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n          )::realtime.wal_column\n        )\n        from\n          unnest(columns) c;\n\n      old_columns =\n        array_agg(\n          (\n            c.name,\n            c.type,\n            c.value,\n            c.is_pkey,\n            pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n          )::realtime.wal_column\n        )\n        from\n          unnest(old_columns) c;\n\n        if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n          return next (\n            jsonb_build_object(\n              'schema', wal ->> 'schema',\n              'table', wal ->> 'table',\n              'type', action\n            ),\n            is_rls_enabled,\n            -- subscriptions is already filtered by entity\n            (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n            array['Error 400: Bad Request, no primary key']\n          )::realtime.wal_rls;\n\n        -- The claims role does not have SELECT permission to the primary key of entity\n        elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n          return next (\n            jsonb_build_object(\n              'schema', wal ->> 'schema',\n              'table', wal ->> 'table',\n              'type', action\n            ),\n            is_rls_enabled,\n            (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n            array['Error 401: Unauthorized']\n          )::realtime.wal_rls;\n\n        else\n          output = jsonb_build_object(\n            'schema', wal ->> 'schema',\n            'table', wal ->> 'table',\n            'type', action,\n            'commit_timestamp', (wal ->> 'timestamp')::text::timestamp with time zone,\n            'columns', (\n              select\n                jsonb_agg(\n                  jsonb_build_object(\n                    'name', pa.attname,\n                    'type', pt.typname\n                  )\n                  order by pa.attnum asc\n                )\n              from\n                pg_attribute pa\n                join pg_type pt\n                  on pa.atttypid = pt.oid\n              where\n                attrelid = entity_\n                and attnum > 0\n                and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n              )\n          )\n          -- Add \\\"record\\\" key for insert and update\n          || case\n            when error_record_exceeds_max_size then jsonb_build_object('record', '{}'::jsonb)\n            when action in ('INSERT', 'UPDATE') then\n              jsonb_build_object(\n                'record',\n                (select jsonb_object_agg((c).name, (c).value) from unnest(columns) c where (c).is_selectable)\n              )\n            else '{}'::jsonb\n          end\n          -- Add \\\"old_record\\\" key for update and delete\n          || case\n            when error_record_exceeds_max_size then jsonb_build_object('old_record', '{}'::jsonb)\n            when action in ('UPDATE', 'DELETE') then\n              jsonb_build_object(\n                'old_record',\n                (select jsonb_object_agg((c).name, (c).value) from unnest(old_columns) c where (c).is_selectable)\n              )\n            else '{}'::jsonb\n          end;\n\n          -- Create the prepared statement\n          if is_rls_enabled and action <> 'DELETE' then\n            if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n              deallocate walrus_rls_stmt;\n            end if;\n            execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n          end if;\n\n          visible_to_subscription_ids = '{}';\n\n          for subscription_id, claims in (\n            select\n              subs.subscription_id,\n              subs.claims\n            from\n              unnest(subscriptions) subs\n            where\n              subs.entity = entity_\n              and subs.claims_role = working_role\n              and realtime.is_visible_through_filters(columns, subs.filters)\n            ) loop\n\n            if not is_rls_enabled or action = 'DELETE' then\n              visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n            else\n                -- Check if RLS allows the role to see the record\n                perform\n                  set_config('role', working_role::text, true),\n                  set_config('request.jwt.claims', claims::text, true);\n\n                execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                if subscription_has_access then\n                  visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                end if;\n            end if;\n          end loop;\n\n          perform set_config('role', null, true);\n\n          return next (\n            output,\n            is_rls_enabled,\n            visible_to_subscription_ids,\n            case\n              when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n              else '{}'\n            end\n          )::realtime.wal_rls;\n\n        end if;\n    end loop;\n\n    perform set_config('role', null, true);\n  end;\n  $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20220107221237_update_change_timestamp_to_iso_8601_zulu_format.ex",
    "content": "defmodule Realtime.Tenants.Migrations.UpdateChangeTimestampToIso8601ZuluFormat do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n      returns setof realtime.wal_rls\n      language plpgsql\n      volatile\n      as $$\n      declare\n        -- Regclass of the table e.g. public.notes\n        entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n        -- I, U, D, T: insert, update ...\n        action realtime.action = (\n          case wal ->> 'action'\n            when 'I' then 'INSERT'\n            when 'U' then 'UPDATE'\n            when 'D' then 'DELETE'\n            else 'ERROR'\n          end\n        );\n\n        -- Is row level security enabled for the table\n        is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n        subscriptions realtime.subscription[] = array_agg(subs)\n          from\n            realtime.subscription subs\n          where\n            subs.entity = entity_;\n\n        -- Subscription vars\n        roles regrole[] = array_agg(distinct us.claims_role)\n          from\n            unnest(subscriptions) us;\n\n        working_role regrole;\n        claimed_role regrole;\n        claims jsonb;\n\n        subscription_id uuid;\n        subscription_has_access bool;\n        visible_to_subscription_ids uuid[] = '{}';\n\n        -- structured info for wal's columns\n        columns realtime.wal_column[];\n        -- previous identity values for update/delete\n        old_columns realtime.wal_column[];\n\n        error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n        -- Primary jsonb output for record\n        output jsonb;\n\n      begin\n        perform set_config('role', null, true);\n\n        columns =\n          array_agg(\n            (\n              x->>'name',\n              x->>'type',\n              realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),\n              (pks ->> 'name') is not null,\n              true\n            )::realtime.wal_column\n          )\n          from\n            jsonb_array_elements(wal -> 'columns') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n              on (x ->> 'name') = (pks ->> 'name');\n\n        old_columns =\n          array_agg(\n            (\n              x->>'name',\n              x->>'type',\n              realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),\n              (pks ->> 'name') is not null,\n              true\n            )::realtime.wal_column\n          )\n          from\n            jsonb_array_elements(wal -> 'identity') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n              on (x ->> 'name') = (pks ->> 'name');\n\n        for working_role in select * from unnest(roles) loop\n\n          -- Update `is_selectable` for columns and old_columns\n          columns =\n            array_agg(\n              (\n                c.name,\n                c.type,\n                c.value,\n                c.is_pkey,\n                pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n              )::realtime.wal_column\n            )\n            from\n              unnest(columns) c;\n\n          old_columns =\n            array_agg(\n              (\n                c.name,\n                c.type,\n                c.value,\n                c.is_pkey,\n                pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n              )::realtime.wal_column\n            )\n            from\n              unnest(old_columns) c;\n\n          if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n            return next (\n              null,\n              is_rls_enabled,\n              -- subscriptions is already filtered by entity\n              (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n              array['Error 400: Bad Request, no primary key']\n            )::realtime.wal_rls;\n\n          -- The claims role does not have SELECT permission to the primary key of entity\n          elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n            return next (\n              null,\n              is_rls_enabled,\n              (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n              array['Error 401: Unauthorized']\n            )::realtime.wal_rls;\n\n          else\n            output = jsonb_build_object(\n              'schema', wal ->> 'schema',\n              'table', wal ->> 'table',\n              'type', action,\n              'commit_timestamp', to_char(\n                (wal ->> 'timestamp')::timestamptz,\n                'YYYY-MM-DD\\\"T\\\"HH24:MI:SS\\\"Z\\\"'\n              ),\n              'columns', (\n                select\n                  jsonb_agg(\n                    jsonb_build_object(\n                      'name', pa.attname,\n                      'type', pt.typname\n                    )\n                    order by pa.attnum asc\n                  )\n                    from\n                      pg_attribute pa\n                      join pg_type pt\n                        on pa.atttypid = pt.oid\n                    where\n                      attrelid = entity_\n                      and attnum > 0\n                      and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n              )\n            )\n            -- Add \\\"record\\\" key for insert and update\n            || case\n                when error_record_exceeds_max_size then jsonb_build_object('record', '{}'::jsonb)\n                when action in ('INSERT', 'UPDATE') then\n                  jsonb_build_object(\n                    'record',\n                    (select jsonb_object_agg((c).name, (c).value) from unnest(columns) c where (c).is_selectable)\n                  )\n                else '{}'::jsonb\n            end\n            -- Add \\\"old_record\\\" key for update and delete\n            || case\n                when error_record_exceeds_max_size then jsonb_build_object('old_record', '{}'::jsonb)\n                when action in ('UPDATE', 'DELETE') then\n                  jsonb_build_object(\n                    'old_record',\n                    (select jsonb_object_agg((c).name, (c).value) from unnest(old_columns) c where (c).is_selectable)\n                  )\n                else '{}'::jsonb\n            end;\n\n            -- Create the prepared statement\n            if is_rls_enabled and action <> 'DELETE' then\n              if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                deallocate walrus_rls_stmt;\n              end if;\n              execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n            end if;\n\n            visible_to_subscription_ids = '{}';\n\n            for subscription_id, claims in (\n                select\n                  subs.subscription_id,\n                  subs.claims\n                from\n                  unnest(subscriptions) subs\n                where\n                  subs.entity = entity_\n                  and subs.claims_role = working_role\n                  and realtime.is_visible_through_filters(columns, subs.filters)\n              ) loop\n\n              if not is_rls_enabled or action = 'DELETE' then\n                visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n              else\n                -- Check if RLS allows the role to see the record\n                perform\n                  set_config('role', working_role::text, true),\n                  set_config('request.jwt.claims', claims::text, true);\n\n                execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                if subscription_has_access then\n                  visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                end if;\n              end if;\n            end loop;\n\n            perform set_config('role', null, true);\n\n            return next (\n              output,\n              is_rls_enabled,\n              visible_to_subscription_ids,\n              case\n                when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                else '{}'\n              end\n            )::realtime.wal_rls;\n\n          end if;\n        end loop;\n\n        perform set_config('role', null, true);\n      end;\n      $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20220228202821_update_subscription_check_filters_function_dynamic_table_name.ex",
    "content": "defmodule Realtime.Tenants.Migrations.UpdateSubscriptionCheckFiltersFunctionDynamicTableName do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"create or replace function realtime.subscription_check_filters()\n      returns trigger\n      language plpgsql\n    as $$\n    /*\n    Validates that the user defined filters for a subscription:\n    - refer to valid columns that the claimed role may access\n    - values are coercable to the correct column type\n    */\n    declare\n      col_names text[] = coalesce(\n        array_agg(c.column_name order by c.ordinal_position),\n        '{}'::text[]\n      )\n      from\n        information_schema.columns c\n      where\n        format('%I.%I', c.table_schema, c.table_name)::regclass = new.entity\n        and pg_catalog.has_column_privilege(\n          (new.claims ->> 'role'),\n          format('%I.%I', c.table_schema, c.table_name)::regclass,\n          c.column_name,\n          'SELECT'\n        );\n      filter realtime.user_defined_filter;\n      col_type regtype;\n    begin\n      for filter in select * from unnest(new.filters) loop\n        -- Filtered column is valid\n        if not filter.column_name = any(col_names) then\n          raise exception 'invalid column for filter %', filter.column_name;\n        end if;\n\n        -- Type is sanitized and safe for string interpolation\n        col_type = (\n          select atttypid::regtype\n          from pg_catalog.pg_attribute\n          where attrelid = new.entity\n            and attname = filter.column_name\n        );\n        if col_type is null then\n          raise exception 'failed to lookup type for column %', filter.column_name;\n        end if;\n        -- raises an exception if value is not coercable to type\n        perform realtime.cast(filter.value, col_type);\n      end loop;\n\n      -- Apply consistent order to filters so the unique constraint on\n      -- (subscription_id, entity, filters) can't be tricked by a different filter order\n      new.filters = coalesce(\n        array_agg(f order by f.column_name, f.op, f.value),\n        '{}'\n      ) from unnest(new.filters) f;\n\n    return new;\n  end;\n  $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20220312004840_update_apply_rls_function_to_apply_iso_8601.ex",
    "content": "defmodule Realtime.Tenants.Migrations.UpdateApplyRlsFunctionToApplyIso8601 do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n      returns setof realtime.wal_rls\n      language plpgsql\n      volatile\n      as $$\n      declare\n        -- Regclass of the table e.g. public.notes\n        entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n        -- I, U, D, T: insert, update ...\n        action realtime.action = (\n          case wal ->> 'action'\n            when 'I' then 'INSERT'\n            when 'U' then 'UPDATE'\n            when 'D' then 'DELETE'\n            else 'ERROR'\n          end\n        );\n\n        -- Is row level security enabled for the table\n        is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n        subscriptions realtime.subscription[] = array_agg(subs)\n          from\n            realtime.subscription subs\n          where\n            subs.entity = entity_;\n\n        -- Subscription vars\n        roles regrole[] = array_agg(distinct us.claims_role)\n          from\n            unnest(subscriptions) us;\n\n        working_role regrole;\n        claimed_role regrole;\n        claims jsonb;\n\n        subscription_id uuid;\n        subscription_has_access bool;\n        visible_to_subscription_ids uuid[] = '{}';\n\n        -- structured info for wal's columns\n        columns realtime.wal_column[];\n        -- previous identity values for update/delete\n        old_columns realtime.wal_column[];\n\n        error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n        -- Primary jsonb output for record\n        output jsonb;\n\n      begin\n        perform set_config('role', null, true);\n\n        columns =\n          array_agg(\n            (\n              x->>'name',\n              x->>'type',\n              realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),\n              (pks ->> 'name') is not null,\n              true\n            )::realtime.wal_column\n          )\n          from\n            jsonb_array_elements(wal -> 'columns') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n              on (x ->> 'name') = (pks ->> 'name');\n\n        old_columns =\n          array_agg(\n            (\n              x->>'name',\n              x->>'type',\n              realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),\n              (pks ->> 'name') is not null,\n              true\n            )::realtime.wal_column\n          )\n          from\n            jsonb_array_elements(wal -> 'identity') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n              on (x ->> 'name') = (pks ->> 'name');\n\n        for working_role in select * from unnest(roles) loop\n\n          -- Update `is_selectable` for columns and old_columns\n          columns =\n            array_agg(\n              (\n                c.name,\n                c.type,\n                c.value,\n                c.is_pkey,\n                pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n              )::realtime.wal_column\n            )\n            from\n              unnest(columns) c;\n\n          old_columns =\n            array_agg(\n              (\n                c.name,\n                c.type,\n                c.value,\n                c.is_pkey,\n                pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n              )::realtime.wal_column\n            )\n            from\n              unnest(old_columns) c;\n\n          if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n            return next (\n              null,\n              is_rls_enabled,\n              -- subscriptions is already filtered by entity\n              (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n              array['Error 400: Bad Request, no primary key']\n            )::realtime.wal_rls;\n\n          -- The claims role does not have SELECT permission to the primary key of entity\n          elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n            return next (\n              null,\n              is_rls_enabled,\n              (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n              array['Error 401: Unauthorized']\n            )::realtime.wal_rls;\n\n          else\n            output = jsonb_build_object(\n              'schema', wal ->> 'schema',\n              'table', wal ->> 'table',\n              'type', action,\n              'commit_timestamp', to_char(\n                (wal ->> 'timestamp')::timestamptz,\n                'YYYY-MM-DD\\\"T\\\"HH24:MI:SS\\\"Z\\\"'\n              ),\n              'columns', (\n                select\n                  jsonb_agg(\n                    jsonb_build_object(\n                      'name', pa.attname,\n                      'type', pt.typname\n                    )\n                    order by pa.attnum asc\n                  )\n                    from\n                      pg_attribute pa\n                      join pg_type pt\n                        on pa.atttypid = pt.oid\n                    where\n                      attrelid = entity_\n                      and attnum > 0\n                      and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n              )\n            )\n            -- Add \\\"record\\\" key for insert and update\n            || case\n                when error_record_exceeds_max_size then jsonb_build_object('record', '{}'::jsonb)\n                when action in ('INSERT', 'UPDATE') then\n                  jsonb_build_object(\n                    'record',\n                    (select jsonb_object_agg((c).name, (c).value) from unnest(columns) c where (c).is_selectable)\n                  )\n                else '{}'::jsonb\n            end\n            -- Add \\\"old_record\\\" key for update and delete\n            || case\n                when error_record_exceeds_max_size then jsonb_build_object('old_record', '{}'::jsonb)\n                when action in ('UPDATE', 'DELETE') then\n                  jsonb_build_object(\n                    'old_record',\n                    (select jsonb_object_agg((c).name, (c).value) from unnest(old_columns) c where (c).is_selectable)\n                  )\n                else '{}'::jsonb\n            end;\n\n            -- Create the prepared statement\n            if is_rls_enabled and action <> 'DELETE' then\n              if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                deallocate walrus_rls_stmt;\n              end if;\n              execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n            end if;\n\n            visible_to_subscription_ids = '{}';\n\n            for subscription_id, claims in (\n                select\n                  subs.subscription_id,\n                  subs.claims\n                from\n                  unnest(subscriptions) subs\n                where\n                  subs.entity = entity_\n                  and subs.claims_role = working_role\n                  and realtime.is_visible_through_filters(columns, subs.filters)\n              ) loop\n\n              if not is_rls_enabled or action = 'DELETE' then\n                visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n              else\n                -- Check if RLS allows the role to see the record\n                perform\n                  set_config('role', working_role::text, true),\n                  set_config('request.jwt.claims', claims::text, true);\n\n                execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                if subscription_has_access then\n                  visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                end if;\n              end if;\n            end loop;\n\n            perform set_config('role', null, true);\n\n            return next (\n              output,\n              is_rls_enabled,\n              visible_to_subscription_ids,\n              case\n                when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                else '{}'\n              end\n            )::realtime.wal_rls;\n\n          end if;\n        end loop;\n\n        perform set_config('role', null, true);\n      end;\n      $$;\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20220603231003_add_quoted_regtypes_support.ex",
    "content": "defmodule Realtime.Tenants.Migrations.AddQuotedRegtypesSupport do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"drop type if exists realtime.wal_column cascade;\")\n\n    execute(\"\"\"\n    DO $$\n    BEGIN\n        IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'wal_column') THEN\n            CREATE TYPE realtime.wal_column AS (\n                name text,\n                type_name text,\n                type_oid oid,\n                value jsonb,\n                is_pkey boolean,\n                is_selectable boolean\n            );\n        END IF;\n    END$$;\n    \"\"\")\n\n    execute(\"\n      create or replace function realtime.is_visible_through_filters(columns realtime.wal_column[], filters realtime.user_defined_filter[])\n          returns bool\n          language sql\n          immutable\n      as $$\n      /*\n      Should the record be visible (true) or filtered out (false) after *filters* are applied\n      */\n          select\n              -- Default to allowed when no filters present\n              coalesce(\n                  sum(\n                      realtime.check_equality_op(\n                          op:=f.op,\n                          type_:=col.type_oid::regtype,\n                          -- cast jsonb to text\n                          val_1:=col.value #>> '{}',\n                          val_2:=f.value\n                      )::int\n                  ) = count(1),\n                  true\n              )\n          from\n              unnest(filters) f\n              join unnest(columns) col\n                  on f.column_name = col.name;\n      $$;\")\n\n    execute(\"\n        create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n            returns setof realtime.wal_rls\n            language plpgsql\n            volatile\n        as $$\n        declare\n            -- Regclass of the table e.g. public.notes\n            entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n            -- I, U, D, T: insert, update ...\n            action realtime.action = (\n                case wal ->> 'action'\n                    when 'I' then 'INSERT'\n                    when 'U' then 'UPDATE'\n                    when 'D' then 'DELETE'\n                    else 'ERROR'\n                end\n            );\n            -- Is row level security enabled for the table\n            is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n            subscriptions realtime.subscription[] = array_agg(subs)\n                from\n                    realtime.subscription subs\n                where\n                    subs.entity = entity_;\n            -- Subscription vars\n            roles regrole[] = array_agg(distinct us.claims_role)\n                from\n                    unnest(subscriptions) us;\n            working_role regrole;\n            claimed_role regrole;\n            claims jsonb;\n            subscription_id uuid;\n            subscription_has_access bool;\n            visible_to_subscription_ids uuid[] = '{}';\n            -- structured info for wal's columns\n            columns realtime.wal_column[];\n            -- previous identity values for update/delete\n            old_columns realtime.wal_column[];\n\n            error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n            -- Primary jsonb output for record\n            output jsonb;\n\n        begin\n            perform set_config('role', null, true);\n\n            columns =\n                array_agg(\n                    (\n                        x->>'name',\n                        x->>'type',\n                        x->>'typeoid',\n                        realtime.cast(\n                            (x->'value') #>> '{}',\n                            (x->>'typeoid')::regtype\n                        ),\n                        (pks ->> 'name') is not null,\n                        true\n                    )::realtime.wal_column\n                )\n                from\n                    jsonb_array_elements(wal -> 'columns') x\n                    left join jsonb_array_elements(wal -> 'pk') pks\n                        on (x ->> 'name') = (pks ->> 'name');\n\n            old_columns =\n                array_agg(\n                    (\n                        x->>'name',\n                        x->>'type',\n                        x->>'typeoid',\n                        realtime.cast(\n                            (x->'value') #>> '{}',\n                            (x->>'typeoid')::regtype\n                        ),\n                        (pks ->> 'name') is not null,\n                        true\n                    )::realtime.wal_column\n                )\n                from\n                    jsonb_array_elements(wal -> 'identity') x\n                    left join jsonb_array_elements(wal -> 'pk') pks\n                        on (x ->> 'name') = (pks ->> 'name');\n\n            for working_role in select * from unnest(roles) loop\n\n                -- Update `is_selectable` for columns and old_columns\n                columns =\n                    array_agg(\n                        (\n                            c.name,\n                            c.type_name,\n                            c.type_oid,\n                            c.value,\n                            c.is_pkey,\n                            pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                        )::realtime.wal_column\n                    )\n                    from\n                        unnest(columns) c;\n\n                old_columns =\n                        array_agg(\n                            (\n                                c.name,\n                                c.type_name,\n                                c.type_oid,\n                                c.value,\n                                c.is_pkey,\n                                pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                            )::realtime.wal_column\n                        )\n                        from\n                            unnest(old_columns) c;\n\n                if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n                    return next (\n                        jsonb_build_object(\n                            'schema', wal ->> 'schema',\n                            'table', wal ->> 'table',\n                            'type', action\n                        ),\n                        is_rls_enabled,\n                        -- subscriptions is already filtered by entity\n                        (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                        array['Error 400: Bad Request, no primary key']\n                    )::realtime.wal_rls;\n\n                -- The claims role does not have SELECT permission to the primary key of entity\n                elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n                    return next (\n                        jsonb_build_object(\n                            'schema', wal ->> 'schema',\n                            'table', wal ->> 'table',\n                            'type', action\n                        ),\n                        is_rls_enabled,\n                        (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                        array['Error 401: Unauthorized']\n                    )::realtime.wal_rls;\n\n                else\n                    output = jsonb_build_object(\n                        'schema', wal ->> 'schema',\n                        'table', wal ->> 'table',\n                        'type', action,\n                        'commit_timestamp', to_char(\n                            (wal ->> 'timestamp')::timestamptz,\n                            'YYYY-MM-DD\\\"T\\\"HH24:MI:SS\\\"Z\\\"'\n                        ),\n                        'columns', (\n                            select\n                                jsonb_agg(\n                                    jsonb_build_object(\n                                        'name', pa.attname,\n                                        'type', pt.typname\n                                    )\n                                    order by pa.attnum asc\n                                )\n                            from\n                                pg_attribute pa\n                                join pg_type pt\n                                    on pa.atttypid = pt.oid\n                            where\n                                attrelid = entity_\n                                and attnum > 0\n                                and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n                        )\n                    )\n                    -- Add \\\"record\\\" key for insert and update\n                    || case\n                        when error_record_exceeds_max_size then jsonb_build_object('record', '{}'::jsonb)\n                        when action in ('INSERT', 'UPDATE') then\n                            jsonb_build_object(\n                                'record',\n                                (select jsonb_object_agg((c).name, (c).value) from unnest(columns) c where (c).is_selectable)\n                            )\n                        else '{}'::jsonb\n                    end\n                    -- Add \\\"old_record\\\" key for update and delete\n                    || case\n                        when error_record_exceeds_max_size then jsonb_build_object('old_record', '{}'::jsonb)\n                        when action in ('UPDATE', 'DELETE') then\n                            jsonb_build_object(\n                                'old_record',\n                                (select jsonb_object_agg((c).name, (c).value) from unnest(old_columns) c where (c).is_selectable)\n                            )\n                        else '{}'::jsonb\n                    end;\n\n                    -- Create the prepared statement\n                    if is_rls_enabled and action <> 'DELETE' then\n                        if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                            deallocate walrus_rls_stmt;\n                        end if;\n                        execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n                    end if;\n\n                    visible_to_subscription_ids = '{}';\n\n                    for subscription_id, claims in (\n                            select\n                                subs.subscription_id,\n                                subs.claims\n                            from\n                                unnest(subscriptions) subs\n                            where\n                                subs.entity = entity_\n                                and subs.claims_role = working_role\n                                and realtime.is_visible_through_filters(columns, subs.filters)\n                    ) loop\n\n                        if not is_rls_enabled or action = 'DELETE' then\n                            visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                        else\n                            -- Check if RLS allows the role to see the record\n                            perform\n                                set_config('role', working_role::text, true),\n                                set_config('request.jwt.claims', claims::text, true);\n\n                            execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                            if subscription_has_access then\n                                visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                            end if;\n                        end if;\n                    end loop;\n\n                    perform set_config('role', null, true);\n\n                    return next (\n                        output,\n                        is_rls_enabled,\n                        visible_to_subscription_ids,\n                        case\n                            when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                            else '{}'\n                        end\n                    )::realtime.wal_rls;\n\n                end if;\n            end loop;\n\n            perform set_config('role', null, true);\n        end;\n      $$;\n      \")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20220603232444_add_output_for_data_less_than_equal_64_bytes_when_payload_too_large.ex",
    "content": "defmodule Realtime.Tenants.Migrations.AddOutputForDataLessThanEqual64BytesWhenPayloadTooLarge do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\n      create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n          returns setof realtime.wal_rls\n          language plpgsql\n          volatile\n      as $$\n      declare\n          -- Regclass of the table e.g. public.notes\n          entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n          -- I, U, D, T: insert, update ...\n          action realtime.action = (\n              case wal ->> 'action'\n                  when 'I' then 'INSERT'\n                  when 'U' then 'UPDATE'\n                  when 'D' then 'DELETE'\n                  else 'ERROR'\n              end\n          );\n          -- Is row level security enabled for the table\n          is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n          subscriptions realtime.subscription[] = array_agg(subs)\n              from\n                  realtime.subscription subs\n              where\n                  subs.entity = entity_;\n          -- Subscription vars\n          roles regrole[] = array_agg(distinct us.claims_role)\n              from\n                  unnest(subscriptions) us;\n          working_role regrole;\n          claimed_role regrole;\n          claims jsonb;\n          subscription_id uuid;\n          subscription_has_access bool;\n          visible_to_subscription_ids uuid[] = '{}';\n          -- structured info for wal's columns\n          columns realtime.wal_column[];\n          -- previous identity values for update/delete\n          old_columns realtime.wal_column[];\n\n          error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n          -- Primary jsonb output for record\n          output jsonb;\n\n      begin\n          perform set_config('role', null, true);\n\n          columns =\n              array_agg(\n                  (\n                      x->>'name',\n                      x->>'type',\n                      x->>'typeoid',\n                      realtime.cast(\n                          (x->'value') #>> '{}',\n                          (x->>'typeoid')::regtype\n                      ),\n                      (pks ->> 'name') is not null,\n                      true\n                  )::realtime.wal_column\n              )\n              from\n                  jsonb_array_elements(wal -> 'columns') x\n                  left join jsonb_array_elements(wal -> 'pk') pks\n                      on (x ->> 'name') = (pks ->> 'name');\n\n          old_columns =\n              array_agg(\n                  (\n                      x->>'name',\n                      x->>'type',\n                      x->>'typeoid',\n                      realtime.cast(\n                          (x->'value') #>> '{}',\n                          (x->>'typeoid')::regtype\n                      ),\n                      (pks ->> 'name') is not null,\n                      true\n                  )::realtime.wal_column\n              )\n              from\n                  jsonb_array_elements(wal -> 'identity') x\n                  left join jsonb_array_elements(wal -> 'pk') pks\n                      on (x ->> 'name') = (pks ->> 'name');\n\n          for working_role in select * from unnest(roles) loop\n\n              -- Update `is_selectable` for columns and old_columns\n              columns =\n                  array_agg(\n                      (\n                          c.name,\n                          c.type_name,\n                          c.type_oid,\n                          c.value,\n                          c.is_pkey,\n                          pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                      )::realtime.wal_column\n                  )\n                  from\n                      unnest(columns) c;\n\n              old_columns =\n                      array_agg(\n                          (\n                              c.name,\n                              c.type_name,\n                              c.type_oid,\n                              c.value,\n                              c.is_pkey,\n                              pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                          )::realtime.wal_column\n                      )\n                      from\n                          unnest(old_columns) c;\n\n              if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n                  return next (\n                      jsonb_build_object(\n                          'schema', wal ->> 'schema',\n                          'table', wal ->> 'table',\n                          'type', action\n                      ),\n                      is_rls_enabled,\n                      -- subscriptions is already filtered by entity\n                      (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                      array['Error 400: Bad Request, no primary key']\n                  )::realtime.wal_rls;\n\n              -- The claims role does not have SELECT permission to the primary key of entity\n              elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n                  return next (\n                      jsonb_build_object(\n                          'schema', wal ->> 'schema',\n                          'table', wal ->> 'table',\n                          'type', action\n                      ),\n                      is_rls_enabled,\n                      (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                      array['Error 401: Unauthorized']\n                  )::realtime.wal_rls;\n\n              else\n                  output = jsonb_build_object(\n                      'schema', wal ->> 'schema',\n                      'table', wal ->> 'table',\n                      'type', action,\n                      'commit_timestamp', to_char(\n                          (wal ->> 'timestamp')::timestamptz,\n                          'YYYY-MM-DD\\\"T\\\"HH24:MI:SS\\\"Z\\\"'\n                      ),\n                      'columns', (\n                          select\n                              jsonb_agg(\n                                  jsonb_build_object(\n                                      'name', pa.attname,\n                                      'type', pt.typname\n                                  )\n                                  order by pa.attnum asc\n                              )\n                          from\n                              pg_attribute pa\n                              join pg_type pt\n                                  on pa.atttypid = pt.oid\n                          where\n                              attrelid = entity_\n                              and attnum > 0\n                              and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n                      )\n                  )\n                  -- Add \\\"record\\\" key for insert and update\n                  || case\n                      when action in ('INSERT', 'UPDATE') then\n                          case\n                              when error_record_exceeds_max_size then\n                                  jsonb_build_object(\n                                      'record',\n                                      (\n                                          select jsonb_object_agg((c).name, (c).value)\n                                          from unnest(columns) c\n                                          where (c).is_selectable and (octet_length((c).value::text) <= 64)\n                                      )\n                                  )\n                              else\n                                  jsonb_build_object(\n                                      'record',\n                                      (select jsonb_object_agg((c).name, (c).value) from unnest(columns) c where (c).is_selectable)\n                                  )\n                          end\n                      else '{}'::jsonb\n                  end\n                  -- Add \\\"old_record\\\" key for update and delete\n                  || case\n                      when action in ('UPDATE', 'DELETE') then\n                          case\n                              when error_record_exceeds_max_size then\n                                  jsonb_build_object(\n                                      'old_record',\n                                      (\n                                          select jsonb_object_agg((c).name, (c).value)\n                                          from unnest(old_columns) c\n                                          where (c).is_selectable and (octet_length((c).value::text) <= 64)\n                                      )\n                                  )\n                              else\n                                  jsonb_build_object(\n                                      'old_record',\n                                      (select jsonb_object_agg((c).name, (c).value) from unnest(old_columns) c where (c).is_selectable)\n                                  )\n                          end\n                      else '{}'::jsonb\n                  end;\n\n                  -- Create the prepared statement\n                  if is_rls_enabled and action <> 'DELETE' then\n                      if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                          deallocate walrus_rls_stmt;\n                      end if;\n                      execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n                  end if;\n\n                  visible_to_subscription_ids = '{}';\n\n                  for subscription_id, claims in (\n                          select\n                              subs.subscription_id,\n                              subs.claims\n                          from\n                              unnest(subscriptions) subs\n                          where\n                              subs.entity = entity_\n                              and subs.claims_role = working_role\n                              and realtime.is_visible_through_filters(columns, subs.filters)\n                  ) loop\n\n                      if not is_rls_enabled or action = 'DELETE' then\n                          visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                      else\n                          -- Check if RLS allows the role to see the record\n                          perform\n                              set_config('role', working_role::text, true),\n                              set_config('request.jwt.claims', claims::text, true);\n\n                          execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                          if subscription_has_access then\n                              visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                          end if;\n                      end if;\n                  end loop;\n\n                  perform set_config('role', null, true);\n\n                  return next (\n                      output,\n                      is_rls_enabled,\n                      visible_to_subscription_ids,\n                      case\n                          when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                          else '{}'\n                      end\n                  )::realtime.wal_rls;\n\n              end if;\n          end loop;\n\n          perform set_config('role', null, true);\n      end;\n      $$;\n    \")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20220615214548_add_quoted_regtypes_backward_compatibility_support.ex",
    "content": "defmodule Realtime.Tenants.Migrations.AddQuotedRegtypesBackwardCompatibilitySupport do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\n      create or replace function realtime.is_visible_through_filters(columns realtime.wal_column[], filters realtime.user_defined_filter[])\n            returns bool\n            language sql\n            immutable\n        as $$\n        /*\n        Should the record be visible (true) or filtered out (false) after *filters* are applied\n        */\n            select\n                -- Default to allowed when no filters present\n                coalesce(\n                    sum(\n                        realtime.check_equality_op(\n                            op:=f.op,\n                            type_:=coalesce(\n                                col.type_oid::regtype, -- null when wal2json version <= 2.4\n                                col.type_name::regtype\n                            ),\n                            -- cast jsonb to text\n                            val_1:=col.value #>> '{}',\n                            val_2:=f.value\n                        )::int\n                    ) = count(1),\n                    true\n                )\n            from\n                unnest(filters) f\n                join unnest(columns) col\n                    on f.column_name = col.name;\n        $$;\n    \")\n\n    execute(\"\n    create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n          returns setof realtime.wal_rls\n          language plpgsql\n          volatile\n      as $$\n      declare\n          -- Regclass of the table e.g. public.notes\n          entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n          -- I, U, D, T: insert, update ...\n          action realtime.action = (\n              case wal ->> 'action'\n                  when 'I' then 'INSERT'\n                  when 'U' then 'UPDATE'\n                  when 'D' then 'DELETE'\n                  else 'ERROR'\n              end\n          );\n\n          -- Is row level security enabled for the table\n          is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n          subscriptions realtime.subscription[] = array_agg(subs)\n              from\n                  realtime.subscription subs\n              where\n                  subs.entity = entity_;\n\n          -- Subscription vars\n          roles regrole[] = array_agg(distinct us.claims_role)\n              from\n                  unnest(subscriptions) us;\n\n          working_role regrole;\n          claimed_role regrole;\n          claims jsonb;\n\n          subscription_id uuid;\n          subscription_has_access bool;\n          visible_to_subscription_ids uuid[] = '{}';\n\n          -- structured info for wal's columns\n          columns realtime.wal_column[];\n          -- previous identity values for update/delete\n          old_columns realtime.wal_column[];\n\n          error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n          -- Primary jsonb output for record\n          output jsonb;\n\n      begin\n          perform set_config('role', null, true);\n\n          columns =\n              array_agg(\n                  (\n                      x->>'name',\n                      x->>'type',\n                      x->>'typeoid',\n                      realtime.cast(\n                          (x->'value') #>> '{}',\n                          coalesce(\n                              (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                              (x->>'type')::regtype\n                          )\n                      ),\n                      (pks ->> 'name') is not null,\n                      true\n                  )::realtime.wal_column\n              )\n              from\n                  jsonb_array_elements(wal -> 'columns') x\n                  left join jsonb_array_elements(wal -> 'pk') pks\n                      on (x ->> 'name') = (pks ->> 'name');\n\n          old_columns =\n              array_agg(\n                  (\n                      x->>'name',\n                      x->>'type',\n                      x->>'typeoid',\n                      realtime.cast(\n                          (x->'value') #>> '{}',\n                          coalesce(\n                              (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                              (x->>'type')::regtype\n                          )\n                      ),\n                      (pks ->> 'name') is not null,\n                      true\n                  )::realtime.wal_column\n              )\n              from\n                  jsonb_array_elements(wal -> 'identity') x\n                  left join jsonb_array_elements(wal -> 'pk') pks\n                      on (x ->> 'name') = (pks ->> 'name');\n\n          for working_role in select * from unnest(roles) loop\n\n              -- Update `is_selectable` for columns and old_columns\n              columns =\n                  array_agg(\n                      (\n                          c.name,\n                          c.type_name,\n                          c.type_oid,\n                          c.value,\n                          c.is_pkey,\n                          pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                      )::realtime.wal_column\n                  )\n                  from\n                      unnest(columns) c;\n\n              old_columns =\n                      array_agg(\n                          (\n                              c.name,\n                              c.type_name,\n                              c.type_oid,\n                              c.value,\n                              c.is_pkey,\n                              pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                          )::realtime.wal_column\n                      )\n                      from\n                          unnest(old_columns) c;\n\n              if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n                  return next (\n                      jsonb_build_object(\n                          'schema', wal ->> 'schema',\n                          'table', wal ->> 'table',\n                          'type', action\n                      ),\n                      is_rls_enabled,\n                      -- subscriptions is already filtered by entity\n                      (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                      array['Error 400: Bad Request, no primary key']\n                  )::realtime.wal_rls;\n\n              -- The claims role does not have SELECT permission to the primary key of entity\n              elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n                  return next (\n                      jsonb_build_object(\n                          'schema', wal ->> 'schema',\n                          'table', wal ->> 'table',\n                          'type', action\n                      ),\n                      is_rls_enabled,\n                      (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                      array['Error 401: Unauthorized']\n                  )::realtime.wal_rls;\n\n              else\n                  output = jsonb_build_object(\n                      'schema', wal ->> 'schema',\n                      'table', wal ->> 'table',\n                      'type', action,\n                      'commit_timestamp', to_char(\n                          (wal ->> 'timestamp')::timestamptz,\n                          'YYYY-MM-DD\\\"T\\\"HH24:MI:SS\\\"Z\\\"'\n                      ),\n                      'columns', (\n                          select\n                              jsonb_agg(\n                                  jsonb_build_object(\n                                      'name', pa.attname,\n                                      'type', pt.typname\n                                  )\n                                  order by pa.attnum asc\n                              )\n                          from\n                              pg_attribute pa\n                              join pg_type pt\n                                  on pa.atttypid = pt.oid\n                          where\n                              attrelid = entity_\n                              and attnum > 0\n                              and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n                      )\n                  )\n                  -- Add \\\"record\\\" key for insert and update\n                  || case\n                      when action in ('INSERT', 'UPDATE') then\n                          case\n                              when error_record_exceeds_max_size then\n                                  jsonb_build_object(\n                                      'record',\n                                      (\n                                          select jsonb_object_agg((c).name, (c).value)\n                                          from unnest(columns) c\n                                          where (c).is_selectable and (octet_length((c).value::text) <= 64)\n                                      )\n                                  )\n                              else\n                                  jsonb_build_object(\n                                      'record',\n                                      (select jsonb_object_agg((c).name, (c).value) from unnest(columns) c where (c).is_selectable)\n                                  )\n                          end\n                      else '{}'::jsonb\n                  end\n                  -- Add \\\"old_record\\\" key for update and delete\n                  || case\n                      when action in ('UPDATE', 'DELETE') then\n                          case\n                              when error_record_exceeds_max_size then\n                                  jsonb_build_object(\n                                      'old_record',\n                                      (\n                                          select jsonb_object_agg((c).name, (c).value)\n                                          from unnest(old_columns) c\n                                          where (c).is_selectable and (octet_length((c).value::text) <= 64)\n                                      )\n                                  )\n                              else\n                                  jsonb_build_object(\n                                      'old_record',\n                                      (select jsonb_object_agg((c).name, (c).value) from unnest(old_columns) c where (c).is_selectable)\n                                  )\n                          end\n                      else '{}'::jsonb\n                  end;\n\n                  -- Create the prepared statement\n                  if is_rls_enabled and action <> 'DELETE' then\n                      if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                          deallocate walrus_rls_stmt;\n                      end if;\n                      execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n                  end if;\n\n                  visible_to_subscription_ids = '{}';\n\n                  for subscription_id, claims in (\n                          select\n                              subs.subscription_id,\n                              subs.claims\n                          from\n                              unnest(subscriptions) subs\n                          where\n                              subs.entity = entity_\n                              and subs.claims_role = working_role\n                              and realtime.is_visible_through_filters(columns, subs.filters)\n                  ) loop\n\n                      if not is_rls_enabled or action = 'DELETE' then\n                          visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                      else\n                          -- Check if RLS allows the role to see the record\n                          perform\n                              set_config('role', working_role::text, true),\n                              set_config('request.jwt.claims', claims::text, true);\n\n                          execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                          if subscription_has_access then\n                              visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                          end if;\n                      end if;\n                  end loop;\n\n                  perform set_config('role', null, true);\n\n                  return next (\n                      output,\n                      is_rls_enabled,\n                      visible_to_subscription_ids,\n                      case\n                          when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                          else '{}'\n                      end\n                  )::realtime.wal_rls;\n\n              end if;\n          end loop;\n\n          perform set_config('role', null, true);\n      end;\n    $$;\n    \")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20220712093339_recreate_realtime_build_prepared_statement_sql_function.ex",
    "content": "defmodule Realtime.Tenants.Migrations.RecreateRealtimeBuildPreparedStatementSqlFunction do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\n      create or replace function realtime.build_prepared_statement_sql(\n          prepared_statement_name text,\n          entity regclass,\n          columns realtime.wal_column[]\n      )\n          returns text\n          language sql\n      as $$\n      /*\n      Builds a sql string that, if executed, creates a prepared statement to\n      tests retrive a row from *entity* by its primary key columns.\n      Example\n          select realtime.build_prepared_statement_sql('public.notes', '{\\\"id\\\"}'::text[], '{\\\"bigint\\\"}'::text[])\n      */\n          select\n      'prepare ' || prepared_statement_name || ' as\n          select\n              exists(\n                  select\n                      1\n                  from\n                      ' || entity || '\n                  where\n                      ' || string_agg(quote_ident(pkc.name) || '=' || quote_nullable(pkc.value #>> '{}') , ' and ') || '\n              )'\n          from\n              unnest(columns) pkc\n          where\n              pkc.is_pkey\n          group by\n              entity\n      $$;\n    \")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20220908172859_null_passes_filters_recreate_is_visible_through_filters.ex",
    "content": "defmodule Realtime.Tenants.Migrations.NullPassesFiltersRecreateIsVisibleThroughFilters do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\n    create or replace function realtime.is_visible_through_filters(columns realtime.wal_column[], filters realtime.user_defined_filter[])\n      returns bool\n      language sql\n      immutable\n    as $$\n    /*\n    Should the record be visible (true) or filtered out (false) after *filters* are applied\n    */\n        select\n            -- Default to allowed when no filters present\n            $2 is null -- no filters. this should not happen because subscriptions has a default\n            or array_length($2, 1) is null -- array length of an empty array is null\n            or bool_and(\n                coalesce(\n                    realtime.check_equality_op(\n                        op:=f.op,\n                        type_:=coalesce(\n                            col.type_oid::regtype, -- null when wal2json version <= 2.4\n                            col.type_name::regtype\n                        ),\n                        -- cast jsonb to text\n                        val_1:=col.value #>> '{}',\n                        val_2:=f.value\n                    ),\n                    false -- if null, filter does not match\n                )\n            )\n        from\n            unnest(filters) f\n            join unnest(columns) col\n                on f.column_name = col.name;\n    $$;\n    \")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20220916233421_update_apply_rls_function_to_pass_through_delete_events_on_filter.ex",
    "content": "defmodule Realtime.Tenants.Migrations.UpdateApplyRlsFunctionToPassThroughDeleteEventsOnFilter do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\n      create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n          returns setof realtime.wal_rls\n          language plpgsql\n          volatile\n      as $$\n      declare\n          -- Regclass of the table e.g. public.notes\n          entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n          -- I, U, D, T: insert, update ...\n          action realtime.action = (\n              case wal ->> 'action'\n                  when 'I' then 'INSERT'\n                  when 'U' then 'UPDATE'\n                  when 'D' then 'DELETE'\n                  else 'ERROR'\n              end\n          );\n\n          -- Is row level security enabled for the table\n          is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n          subscriptions realtime.subscription[] = array_agg(subs)\n              from\n                  realtime.subscription subs\n              where\n                  subs.entity = entity_;\n\n          -- Subscription vars\n          roles regrole[] = array_agg(distinct us.claims_role)\n              from\n                  unnest(subscriptions) us;\n\n          working_role regrole;\n          claimed_role regrole;\n          claims jsonb;\n\n          subscription_id uuid;\n          subscription_has_access bool;\n          visible_to_subscription_ids uuid[] = '{}';\n\n          -- structured info for wal's columns\n          columns realtime.wal_column[];\n          -- previous identity values for update/delete\n          old_columns realtime.wal_column[];\n\n          error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n          -- Primary jsonb output for record\n          output jsonb;\n\n      begin\n          perform set_config('role', null, true);\n\n          columns =\n              array_agg(\n                  (\n                      x->>'name',\n                      x->>'type',\n                      x->>'typeoid',\n                      realtime.cast(\n                          (x->'value') #>> '{}',\n                          coalesce(\n                              (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                              (x->>'type')::regtype\n                          )\n                      ),\n                      (pks ->> 'name') is not null,\n                      true\n                  )::realtime.wal_column\n              )\n              from\n                  jsonb_array_elements(wal -> 'columns') x\n                  left join jsonb_array_elements(wal -> 'pk') pks\n                      on (x ->> 'name') = (pks ->> 'name');\n\n          old_columns =\n              array_agg(\n                  (\n                      x->>'name',\n                      x->>'type',\n                      x->>'typeoid',\n                      realtime.cast(\n                          (x->'value') #>> '{}',\n                          coalesce(\n                              (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                              (x->>'type')::regtype\n                          )\n                      ),\n                      (pks ->> 'name') is not null,\n                      true\n                  )::realtime.wal_column\n              )\n              from\n                  jsonb_array_elements(wal -> 'identity') x\n                  left join jsonb_array_elements(wal -> 'pk') pks\n                      on (x ->> 'name') = (pks ->> 'name');\n\n          for working_role in select * from unnest(roles) loop\n\n              -- Update `is_selectable` for columns and old_columns\n              columns =\n                  array_agg(\n                      (\n                          c.name,\n                          c.type_name,\n                          c.type_oid,\n                          c.value,\n                          c.is_pkey,\n                          pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                      )::realtime.wal_column\n                  )\n                  from\n                      unnest(columns) c;\n\n              old_columns =\n                      array_agg(\n                          (\n                              c.name,\n                              c.type_name,\n                              c.type_oid,\n                              c.value,\n                              c.is_pkey,\n                              pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                          )::realtime.wal_column\n                      )\n                      from\n                          unnest(old_columns) c;\n\n              if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n                  return next (\n                      jsonb_build_object(\n                          'schema', wal ->> 'schema',\n                          'table', wal ->> 'table',\n                          'type', action\n                      ),\n                      is_rls_enabled,\n                      -- subscriptions is already filtered by entity\n                      (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                      array['Error 400: Bad Request, no primary key']\n                  )::realtime.wal_rls;\n\n              -- The claims role does not have SELECT permission to the primary key of entity\n              elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n                  return next (\n                      jsonb_build_object(\n                          'schema', wal ->> 'schema',\n                          'table', wal ->> 'table',\n                          'type', action\n                      ),\n                      is_rls_enabled,\n                      (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                      array['Error 401: Unauthorized']\n                  )::realtime.wal_rls;\n\n              else\n                  output = jsonb_build_object(\n                      'schema', wal ->> 'schema',\n                      'table', wal ->> 'table',\n                      'type', action,\n                      'commit_timestamp', to_char(\n                          (wal ->> 'timestamp')::timestamptz,\n                          'YYYY-MM-DD\\\"T\\\"HH24:MI:SS\\\"Z\\\"'\n                      ),\n                      'columns', (\n                          select\n                              jsonb_agg(\n                                  jsonb_build_object(\n                                      'name', pa.attname,\n                                      'type', pt.typname\n                                  )\n                                  order by pa.attnum asc\n                              )\n                          from\n                              pg_attribute pa\n                              join pg_type pt\n                                  on pa.atttypid = pt.oid\n                          where\n                              attrelid = entity_\n                              and attnum > 0\n                              and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n                      )\n                  )\n                  -- Add \\\"record\\\" key for insert and update\n                  || case\n                      when action in ('INSERT', 'UPDATE') then\n                          case\n                              when error_record_exceeds_max_size then\n                                  jsonb_build_object(\n                                      'record',\n                                      (\n                                          select jsonb_object_agg((c).name, (c).value)\n                                          from unnest(columns) c\n                                          where (c).is_selectable and (octet_length((c).value::text) <= 64)\n                                      )\n                                  )\n                              else\n                                  jsonb_build_object(\n                                      'record',\n                                      (select jsonb_object_agg((c).name, (c).value) from unnest(columns) c where (c).is_selectable)\n                                  )\n                          end\n                      else '{}'::jsonb\n                  end\n                  -- Add \\\"old_record\\\" key for update and delete\n                  || case\n                      when action in ('UPDATE', 'DELETE') then\n                          case\n                              when error_record_exceeds_max_size then\n                                  jsonb_build_object(\n                                      'old_record',\n                                      (\n                                          select jsonb_object_agg((c).name, (c).value)\n                                          from unnest(old_columns) c\n                                          where (c).is_selectable and (octet_length((c).value::text) <= 64)\n                                      )\n                                  )\n                              else\n                                  jsonb_build_object(\n                                      'old_record',\n                                      (select jsonb_object_agg((c).name, (c).value) from unnest(old_columns) c where (c).is_selectable)\n                                  )\n                          end\n                      else '{}'::jsonb\n                  end;\n\n                  -- Create the prepared statement\n                  if is_rls_enabled and action <> 'DELETE' then\n                      if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                          deallocate walrus_rls_stmt;\n                      end if;\n                      execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n                  end if;\n\n                  visible_to_subscription_ids = '{}';\n\n                  for subscription_id, claims in (\n                          select\n                              subs.subscription_id,\n                              subs.claims\n                          from\n                              unnest(subscriptions) subs\n                          where\n                              subs.entity = entity_\n                              and subs.claims_role = working_role\n                              and (\n                                  realtime.is_visible_through_filters(columns, subs.filters)\n                                  or action = 'DELETE'\n                              )\n                  ) loop\n\n                      if not is_rls_enabled or action = 'DELETE' then\n                          visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                      else\n                          -- Check if RLS allows the role to see the record\n                          perform\n                              set_config('role', working_role::text, true),\n                              set_config('request.jwt.claims', claims::text, true);\n\n                          execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                          if subscription_has_access then\n                              visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                          end if;\n                      end if;\n                  end loop;\n\n                  perform set_config('role', null, true);\n\n                  return next (\n                      output,\n                      is_rls_enabled,\n                      visible_to_subscription_ids,\n                      case\n                          when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                          else '{}'\n                      end\n                  )::realtime.wal_rls;\n\n              end if;\n          end loop;\n\n          perform set_config('role', null, true);\n      end;\n      $$;\n    \")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20230119133233_millisecond_precision_for_walrus.ex",
    "content": "defmodule Realtime.Tenants.Migrations.MillisecondPrecisionForWalrus do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\n      create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n          returns setof realtime.wal_rls\n          language plpgsql\n          volatile\n      as $$\n      declare\n          -- Regclass of the table e.g. public.notes\n          entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n          -- I, U, D, T: insert, update ...\n          action realtime.action = (\n              case wal ->> 'action'\n                  when 'I' then 'INSERT'\n                  when 'U' then 'UPDATE'\n                  when 'D' then 'DELETE'\n                  else 'ERROR'\n              end\n          );\n\n          -- Is row level security enabled for the table\n          is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n          subscriptions realtime.subscription[] = array_agg(subs)\n              from\n                  realtime.subscription subs\n              where\n                  subs.entity = entity_;\n\n          -- Subscription vars\n          roles regrole[] = array_agg(distinct us.claims_role)\n              from\n                  unnest(subscriptions) us;\n\n          working_role regrole;\n          claimed_role regrole;\n          claims jsonb;\n\n          subscription_id uuid;\n          subscription_has_access bool;\n          visible_to_subscription_ids uuid[] = '{}';\n\n          -- structured info for wal's columns\n          columns realtime.wal_column[];\n          -- previous identity values for update/delete\n          old_columns realtime.wal_column[];\n\n          error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n          -- Primary jsonb output for record\n          output jsonb;\n\n      begin\n          perform set_config('role', null, true);\n\n          columns =\n              array_agg(\n                  (\n                      x->>'name',\n                      x->>'type',\n                      x->>'typeoid',\n                      realtime.cast(\n                          (x->'value') #>> '{}',\n                          coalesce(\n                              (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                              (x->>'type')::regtype\n                          )\n                      ),\n                      (pks ->> 'name') is not null,\n                      true\n                  )::realtime.wal_column\n              )\n              from\n                  jsonb_array_elements(wal -> 'columns') x\n                  left join jsonb_array_elements(wal -> 'pk') pks\n                      on (x ->> 'name') = (pks ->> 'name');\n\n          old_columns =\n              array_agg(\n                  (\n                      x->>'name',\n                      x->>'type',\n                      x->>'typeoid',\n                      realtime.cast(\n                          (x->'value') #>> '{}',\n                          coalesce(\n                              (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                              (x->>'type')::regtype\n                          )\n                      ),\n                      (pks ->> 'name') is not null,\n                      true\n                  )::realtime.wal_column\n              )\n              from\n                  jsonb_array_elements(wal -> 'identity') x\n                  left join jsonb_array_elements(wal -> 'pk') pks\n                      on (x ->> 'name') = (pks ->> 'name');\n\n          for working_role in select * from unnest(roles) loop\n\n              -- Update `is_selectable` for columns and old_columns\n              columns =\n                  array_agg(\n                      (\n                          c.name,\n                          c.type_name,\n                          c.type_oid,\n                          c.value,\n                          c.is_pkey,\n                          pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                      )::realtime.wal_column\n                  )\n                  from\n                      unnest(columns) c;\n\n              old_columns =\n                      array_agg(\n                          (\n                              c.name,\n                              c.type_name,\n                              c.type_oid,\n                              c.value,\n                              c.is_pkey,\n                              pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                          )::realtime.wal_column\n                      )\n                      from\n                          unnest(old_columns) c;\n\n              if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n                  return next (\n                      jsonb_build_object(\n                          'schema', wal ->> 'schema',\n                          'table', wal ->> 'table',\n                          'type', action\n                      ),\n                      is_rls_enabled,\n                      -- subscriptions is already filtered by entity\n                      (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                      array['Error 400: Bad Request, no primary key']\n                  )::realtime.wal_rls;\n\n              -- The claims role does not have SELECT permission to the primary key of entity\n              elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n                  return next (\n                      jsonb_build_object(\n                          'schema', wal ->> 'schema',\n                          'table', wal ->> 'table',\n                          'type', action\n                      ),\n                      is_rls_enabled,\n                      (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                      array['Error 401: Unauthorized']\n                  )::realtime.wal_rls;\n\n              else\n                  output = jsonb_build_object(\n                      'schema', wal ->> 'schema',\n                      'table', wal ->> 'table',\n                      'type', action,\n                      'commit_timestamp', to_char(\n                          (wal ->> 'timestamp')::timestamptz,\n                          'YYYY-MM-DD\\\"T\\\"HH24:MI:SS.MS\\\"Z\\\"'\n                      ),\n                      'columns', (\n                          select\n                              jsonb_agg(\n                                  jsonb_build_object(\n                                      'name', pa.attname,\n                                      'type', pt.typname\n                                  )\n                                  order by pa.attnum asc\n                              )\n                          from\n                              pg_attribute pa\n                              join pg_type pt\n                                  on pa.atttypid = pt.oid\n                          where\n                              attrelid = entity_\n                              and attnum > 0\n                              and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n                      )\n                  )\n                  -- Add \\\"record\\\" key for insert and update\n                  || case\n                      when action in ('INSERT', 'UPDATE') then\n                          jsonb_build_object(\n                              'record',\n                              (\n                                  select jsonb_object_agg((c).name, (c).value)\n                                  from unnest(columns) c\n                                  where\n                                      (c).is_selectable\n                                      and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                              )\n                          )\n                      else '{}'::jsonb\n                  end\n                  -- Add \\\"old_record\\\" key for update and delete\n                  || case\n                      when action = 'UPDATE' then\n                          jsonb_build_object(\n                                  'old_record',\n                                  (\n                                      select jsonb_object_agg((c).name, (c).value)\n                                      from unnest(old_columns) c\n                                      where\n                                          (c).is_selectable\n                                          and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                                  )\n                              )\n                      when action = 'DELETE' then\n                          jsonb_build_object(\n                              'old_record',\n                              (\n                                  select jsonb_object_agg((c).name, (c).value)\n                                  from unnest(old_columns) c\n                                  where\n                                      (c).is_selectable\n                                      and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                                      and ( not is_rls_enabled or (c).is_pkey ) -- if RLS enabled, we can't secure deletes so filter to pkey\n                              )\n                          )\n                      else '{}'::jsonb\n                  end;\n\n                  -- Create the prepared statement\n                  if is_rls_enabled and action <> 'DELETE' then\n                      if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                          deallocate walrus_rls_stmt;\n                      end if;\n                      execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n                  end if;\n\n                  visible_to_subscription_ids = '{}';\n\n                  for subscription_id, claims in (\n                          select\n                              subs.subscription_id,\n                              subs.claims\n                          from\n                              unnest(subscriptions) subs\n                          where\n                              subs.entity = entity_\n                              and subs.claims_role = working_role\n                              and (\n                                  realtime.is_visible_through_filters(columns, subs.filters)\n                                  or action = 'DELETE'\n                              )\n                  ) loop\n\n                      if not is_rls_enabled or action = 'DELETE' then\n                          visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                      else\n                          -- Check if RLS allows the role to see the record\n                          perform\n                              set_config('role', working_role::text, true),\n                              set_config('request.jwt.claims', claims::text, true);\n\n                          execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                          if subscription_has_access then\n                              visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                          end if;\n                      end if;\n                  end loop;\n\n                  perform set_config('role', null, true);\n\n                  return next (\n                      output,\n                      is_rls_enabled,\n                      visible_to_subscription_ids,\n                      case\n                          when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                          else '{}'\n                      end\n                  )::realtime.wal_rls;\n\n              end if;\n          end loop;\n\n          perform set_config('role', null, true);\n      end;\n      $$;\n    \")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20230128025114_add_in_op_to_filters.ex",
    "content": "defmodule Realtime.Tenants.Migrations.AddInOpToFilters do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"alter type realtime.equality_op add value 'in';\")\n\n    execute(\"\n      create or replace function realtime.check_equality_op(\n          op realtime.equality_op,\n          type_ regtype,\n          val_1 text,\n          val_2 text\n      )\n          returns bool\n          immutable\n          language plpgsql\n      as $$\n      /*\n      Casts *val_1* and *val_2* as type *type_* and check the *op* condition for truthiness\n      */\n      declare\n          op_symbol text = (\n              case\n                  when op = 'eq' then '='\n                  when op = 'neq' then '!='\n                  when op = 'lt' then '<'\n                  when op = 'lte' then '<='\n                  when op = 'gt' then '>'\n                  when op = 'gte' then '>='\n                  when op = 'in' then '= any'\n                  else 'UNKNOWN OP'\n              end\n          );\n          res boolean;\n      begin\n          execute format(\n              'select %L::'|| type_::text || ' ' || op_symbol\n              || ' ( %L::'\n              || (\n                  case\n                      when op = 'in' then type_::text || '[]'\n                      else type_::text end\n              )\n              || ')', val_1, val_2) into res;\n          return res;\n      end;\n      $$;\n    \")\n\n    execute(\"\n      create or replace function realtime.subscription_check_filters()\n          returns trigger\n          language plpgsql\n      as $$\n      /*\n      Validates that the user defined filters for a subscription:\n      - refer to valid columns that the claimed role may access\n      - values are coercable to the correct column type\n      */\n      declare\n          col_names text[] = coalesce(\n                  array_agg(c.column_name order by c.ordinal_position),\n                  '{}'::text[]\n              )\n              from\n                  information_schema.columns c\n              where\n                  format('%I.%I', c.table_schema, c.table_name)::regclass = new.entity\n                  and pg_catalog.has_column_privilege(\n                      (new.claims ->> 'role'),\n                      format('%I.%I', c.table_schema, c.table_name)::regclass,\n                      c.column_name,\n                      'SELECT'\n                  );\n          filter realtime.user_defined_filter;\n          col_type regtype;\n\n          in_val jsonb;\n      begin\n          for filter in select * from unnest(new.filters) loop\n              -- Filtered column is valid\n              if not filter.column_name = any(col_names) then\n                  raise exception 'invalid column for filter %', filter.column_name;\n              end if;\n\n              -- Type is sanitized and safe for string interpolation\n              col_type = (\n                  select atttypid::regtype\n                  from pg_catalog.pg_attribute\n                  where attrelid = new.entity\n                        and attname = filter.column_name\n              );\n              if col_type is null then\n                  raise exception 'failed to lookup type for column %', filter.column_name;\n              end if;\n\n              -- Set maximum number of entries for in filter\n              if filter.op = 'in'::realtime.equality_op then\n                  in_val = realtime.cast(filter.value, (col_type::text || '[]')::regtype);\n                  if coalesce(jsonb_array_length(in_val), 0) > 100 then\n                      raise exception 'too many values for `in` filter. Maximum 100';\n                  end if;\n              end if;\n\n              -- raises an exception if value is not coercable to type\n              perform realtime.cast(filter.value, col_type);\n          end loop;\n\n          -- Apply consistent order to filters so the unique constraint on\n          -- (subscription_id, entity, filters) can't be tricked by a different filter order\n          new.filters = coalesce(\n              array_agg(f order by f.column_name, f.op, f.value),\n              '{}'\n          ) from unnest(new.filters) f;\n\n          return new;\n      end;\n      $$;\n    \")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20230128025212_enable_filtering_on_delete_record.ex",
    "content": "defmodule Realtime.Tenants.Migrations.EnableFilteringOnDeleteRecord do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\n      create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n          returns setof realtime.wal_rls\n          language plpgsql\n          volatile\n      as $$\n      declare\n          -- Regclass of the table e.g. public.notes\n          entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n          -- I, U, D, T: insert, update ...\n          action realtime.action = (\n              case wal ->> 'action'\n                  when 'I' then 'INSERT'\n                  when 'U' then 'UPDATE'\n                  when 'D' then 'DELETE'\n                  else 'ERROR'\n              end\n          );\n\n          -- Is row level security enabled for the table\n          is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n          subscriptions realtime.subscription[] = array_agg(subs)\n              from\n                  realtime.subscription subs\n              where\n                  subs.entity = entity_;\n\n          -- Subscription vars\n          roles regrole[] = array_agg(distinct us.claims_role)\n              from\n                  unnest(subscriptions) us;\n\n          working_role regrole;\n          claimed_role regrole;\n          claims jsonb;\n\n          subscription_id uuid;\n          subscription_has_access bool;\n          visible_to_subscription_ids uuid[] = '{}';\n\n          -- structured info for wal's columns\n          columns realtime.wal_column[];\n          -- previous identity values for update/delete\n          old_columns realtime.wal_column[];\n\n          error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n          -- Primary jsonb output for record\n          output jsonb;\n\n      begin\n          perform set_config('role', null, true);\n\n          columns =\n              array_agg(\n                  (\n                      x->>'name',\n                      x->>'type',\n                      x->>'typeoid',\n                      realtime.cast(\n                          (x->'value') #>> '{}',\n                          coalesce(\n                              (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                              (x->>'type')::regtype\n                          )\n                      ),\n                      (pks ->> 'name') is not null,\n                      true\n                  )::realtime.wal_column\n              )\n              from\n                  jsonb_array_elements(wal -> 'columns') x\n                  left join jsonb_array_elements(wal -> 'pk') pks\n                      on (x ->> 'name') = (pks ->> 'name');\n\n          old_columns =\n              array_agg(\n                  (\n                      x->>'name',\n                      x->>'type',\n                      x->>'typeoid',\n                      realtime.cast(\n                          (x->'value') #>> '{}',\n                          coalesce(\n                              (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                              (x->>'type')::regtype\n                          )\n                      ),\n                      (pks ->> 'name') is not null,\n                      true\n                  )::realtime.wal_column\n              )\n              from\n                  jsonb_array_elements(wal -> 'identity') x\n                  left join jsonb_array_elements(wal -> 'pk') pks\n                      on (x ->> 'name') = (pks ->> 'name');\n\n          for working_role in select * from unnest(roles) loop\n\n              -- Update `is_selectable` for columns and old_columns\n              columns =\n                  array_agg(\n                      (\n                          c.name,\n                          c.type_name,\n                          c.type_oid,\n                          c.value,\n                          c.is_pkey,\n                          pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                      )::realtime.wal_column\n                  )\n                  from\n                      unnest(columns) c;\n\n              old_columns =\n                      array_agg(\n                          (\n                              c.name,\n                              c.type_name,\n                              c.type_oid,\n                              c.value,\n                              c.is_pkey,\n                              pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                          )::realtime.wal_column\n                      )\n                      from\n                          unnest(old_columns) c;\n\n              if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n                  return next (\n                      jsonb_build_object(\n                          'schema', wal ->> 'schema',\n                          'table', wal ->> 'table',\n                          'type', action\n                      ),\n                      is_rls_enabled,\n                      -- subscriptions is already filtered by entity\n                      (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                      array['Error 400: Bad Request, no primary key']\n                  )::realtime.wal_rls;\n\n              -- The claims role does not have SELECT permission to the primary key of entity\n              elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n                  return next (\n                      jsonb_build_object(\n                          'schema', wal ->> 'schema',\n                          'table', wal ->> 'table',\n                          'type', action\n                      ),\n                      is_rls_enabled,\n                      (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                      array['Error 401: Unauthorized']\n                  )::realtime.wal_rls;\n\n              else\n                  output = jsonb_build_object(\n                      'schema', wal ->> 'schema',\n                      'table', wal ->> 'table',\n                      'type', action,\n                      'commit_timestamp', to_char(\n                          (wal ->> 'timestamp')::timestamptz,\n                          'YYYY-MM-DD\\\"T\\\"HH24:MI:SS\\\"Z\\\"'\n                      ),\n                      'columns', (\n                          select\n                              jsonb_agg(\n                                  jsonb_build_object(\n                                      'name', pa.attname,\n                                      'type', pt.typname\n                                  )\n                                  order by pa.attnum asc\n                              )\n                          from\n                              pg_attribute pa\n                              join pg_type pt\n                                  on pa.atttypid = pt.oid\n                          where\n                              attrelid = entity_\n                              and attnum > 0\n                              and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n                      )\n                  )\n                  -- Add \\\"record\\\" key for insert and update\n                  || case\n                      when action in ('INSERT', 'UPDATE') then\n                          jsonb_build_object(\n                              'record',\n                              (\n                                  select jsonb_object_agg((c).name, (c).value)\n                                  from unnest(columns) c\n                                  where\n                                      (c).is_selectable\n                                      and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                              )\n                          )\n                      else '{}'::jsonb\n                  end\n                  -- Add \\\"old_record\\\" key for update and delete\n                  || case\n                      when action = 'UPDATE' then\n                          jsonb_build_object(\n                                  'old_record',\n                                  (\n                                      select jsonb_object_agg((c).name, (c).value)\n                                      from unnest(old_columns) c\n                                      where\n                                          (c).is_selectable\n                                          and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                                  )\n                              )\n                      when action = 'DELETE' then\n                          jsonb_build_object(\n                              'old_record',\n                              (\n                                  select jsonb_object_agg((c).name, (c).value)\n                                  from unnest(old_columns) c\n                                  where\n                                      (c).is_selectable\n                                      and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                                      and ( not is_rls_enabled or (c).is_pkey ) -- if RLS enabled, we can't secure deletes so filter to pkey\n                              )\n                          )\n                      else '{}'::jsonb\n                  end;\n\n                  -- Create the prepared statement\n                  if is_rls_enabled and action <> 'DELETE' then\n                      if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                          deallocate walrus_rls_stmt;\n                      end if;\n                      execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n                  end if;\n\n                  visible_to_subscription_ids = '{}';\n\n                  for subscription_id, claims in (\n                          select\n                              subs.subscription_id,\n                              subs.claims\n                          from\n                              unnest(subscriptions) subs\n                          where\n                              subs.entity = entity_\n                              and subs.claims_role = working_role\n                              and (\n                                  realtime.is_visible_through_filters(columns, subs.filters)\n                                  or action = 'DELETE'\n                              )\n                  ) loop\n\n                      if not is_rls_enabled or action = 'DELETE' then\n                          visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                      else\n                          -- Check if RLS allows the role to see the record\n                          perform\n                              set_config('role', working_role::text, true),\n                              set_config('request.jwt.claims', claims::text, true);\n\n                          execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                          if subscription_has_access then\n                              visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                          end if;\n                      end if;\n                  end loop;\n\n                  perform set_config('role', null, true);\n\n                  return next (\n                      output,\n                      is_rls_enabled,\n                      visible_to_subscription_ids,\n                      case\n                          when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                          else '{}'\n                      end\n                  )::realtime.wal_rls;\n\n              end if;\n          end loop;\n\n          perform set_config('role', null, true);\n      end;\n      $$;\n    \")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20230227211149_update_subscription_check_filters_for_in_filter_non_text_types.ex",
    "content": "defmodule Realtime.Tenants.Migrations.UpdateSubscriptionCheckFiltersForInFilterNonTextTypes do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\n    create or replace function realtime.subscription_check_filters()\n        returns trigger\n        language plpgsql\n    as $$\n    /*\n    Validates that the user defined filters for a subscription:\n    - refer to valid columns that the claimed role may access\n    - values are coercable to the correct column type\n    */\n    declare\n        col_names text[] = coalesce(\n                array_agg(c.column_name order by c.ordinal_position),\n                '{}'::text[]\n            )\n            from\n                information_schema.columns c\n            where\n                format('%I.%I', c.table_schema, c.table_name)::regclass = new.entity\n                and pg_catalog.has_column_privilege(\n                    (new.claims ->> 'role'),\n                    format('%I.%I', c.table_schema, c.table_name)::regclass,\n                    c.column_name,\n                    'SELECT'\n                );\n        filter realtime.user_defined_filter;\n        col_type regtype;\n\n        in_val jsonb;\n    begin\n        for filter in select * from unnest(new.filters) loop\n            -- Filtered column is valid\n            if not filter.column_name = any(col_names) then\n                raise exception 'invalid column for filter %', filter.column_name;\n            end if;\n\n            -- Type is sanitized and safe for string interpolation\n            col_type = (\n                select atttypid::regtype\n                from pg_catalog.pg_attribute\n                where attrelid = new.entity\n                      and attname = filter.column_name\n            );\n            if col_type is null then\n                raise exception 'failed to lookup type for column %', filter.column_name;\n            end if;\n\n            -- Set maximum number of entries for in filter\n            if filter.op = 'in'::realtime.equality_op then\n                in_val = realtime.cast(filter.value, (col_type::text || '[]')::regtype);\n                if coalesce(jsonb_array_length(in_val), 0) > 100 then\n                    raise exception 'too many values for `in` filter. Maximum 100';\n                end if;\n            else\n                -- raises an exception if value is not coercable to type\n                perform realtime.cast(filter.value, col_type);\n            end if;\n\n        end loop;\n\n        -- Apply consistent order to filters so the unique constraint on\n        -- (subscription_id, entity, filters) can't be tricked by a different filter order\n        new.filters = coalesce(\n            array_agg(f order by f.column_name, f.op, f.value),\n            '{}'\n        ) from unnest(new.filters) f;\n\n        return new;\n    end;\n    $$;\n    \")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20230228184745_convert_commit_timestamp_to_utc.ex",
    "content": "defmodule Realtime.Tenants.Migrations.ConvertCommitTimestampToUtc do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\n    create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n        returns setof realtime.wal_rls\n        language plpgsql\n        volatile\n    as $$\n    declare\n        -- Regclass of the table e.g. public.notes\n        entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n        -- I, U, D, T: insert, update ...\n        action realtime.action = (\n            case wal ->> 'action'\n                when 'I' then 'INSERT'\n                when 'U' then 'UPDATE'\n                when 'D' then 'DELETE'\n                else 'ERROR'\n            end\n        );\n\n        -- Is row level security enabled for the table\n        is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n        subscriptions realtime.subscription[] = array_agg(subs)\n            from\n                realtime.subscription subs\n            where\n                subs.entity = entity_;\n\n        -- Subscription vars\n        roles regrole[] = array_agg(distinct us.claims_role)\n            from\n                unnest(subscriptions) us;\n\n        working_role regrole;\n        claimed_role regrole;\n        claims jsonb;\n\n        subscription_id uuid;\n        subscription_has_access bool;\n        visible_to_subscription_ids uuid[] = '{}';\n\n        -- structured info for wal's columns\n        columns realtime.wal_column[];\n        -- previous identity values for update/delete\n        old_columns realtime.wal_column[];\n\n        error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n        -- Primary jsonb output for record\n        output jsonb;\n\n    begin\n        perform set_config('role', null, true);\n\n        columns =\n            array_agg(\n                (\n                    x->>'name',\n                    x->>'type',\n                    x->>'typeoid',\n                    realtime.cast(\n                        (x->'value') #>> '{}',\n                        coalesce(\n                            (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                            (x->>'type')::regtype\n                        )\n                    ),\n                    (pks ->> 'name') is not null,\n                    true\n                )::realtime.wal_column\n            )\n            from\n                jsonb_array_elements(wal -> 'columns') x\n                left join jsonb_array_elements(wal -> 'pk') pks\n                    on (x ->> 'name') = (pks ->> 'name');\n\n        old_columns =\n            array_agg(\n                (\n                    x->>'name',\n                    x->>'type',\n                    x->>'typeoid',\n                    realtime.cast(\n                        (x->'value') #>> '{}',\n                        coalesce(\n                            (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                            (x->>'type')::regtype\n                        )\n                    ),\n                    (pks ->> 'name') is not null,\n                    true\n                )::realtime.wal_column\n            )\n            from\n                jsonb_array_elements(wal -> 'identity') x\n                left join jsonb_array_elements(wal -> 'pk') pks\n                    on (x ->> 'name') = (pks ->> 'name');\n\n        for working_role in select * from unnest(roles) loop\n\n            -- Update `is_selectable` for columns and old_columns\n            columns =\n                array_agg(\n                    (\n                        c.name,\n                        c.type_name,\n                        c.type_oid,\n                        c.value,\n                        c.is_pkey,\n                        pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                    )::realtime.wal_column\n                )\n                from\n                    unnest(columns) c;\n\n            old_columns =\n                    array_agg(\n                        (\n                            c.name,\n                            c.type_name,\n                            c.type_oid,\n                            c.value,\n                            c.is_pkey,\n                            pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                        )::realtime.wal_column\n                    )\n                    from\n                        unnest(old_columns) c;\n\n            if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n                return next (\n                    jsonb_build_object(\n                        'schema', wal ->> 'schema',\n                        'table', wal ->> 'table',\n                        'type', action\n                    ),\n                    is_rls_enabled,\n                    -- subscriptions is already filtered by entity\n                    (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                    array['Error 400: Bad Request, no primary key']\n                )::realtime.wal_rls;\n\n            -- The claims role does not have SELECT permission to the primary key of entity\n            elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n                return next (\n                    jsonb_build_object(\n                        'schema', wal ->> 'schema',\n                        'table', wal ->> 'table',\n                        'type', action\n                    ),\n                    is_rls_enabled,\n                    (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                    array['Error 401: Unauthorized']\n                )::realtime.wal_rls;\n\n            else\n                output = jsonb_build_object(\n                    'schema', wal ->> 'schema',\n                    'table', wal ->> 'table',\n                    'type', action,\n                    'commit_timestamp', to_char(\n                        ((wal ->> 'timestamp')::timestamptz at time zone 'utc'),\n                        'YYYY-MM-DD\\\"T\\\"HH24:MI:SS.MS\\\"Z\\\"'\n                    ),\n                    'columns', (\n                        select\n                            jsonb_agg(\n                                jsonb_build_object(\n                                    'name', pa.attname,\n                                    'type', pt.typname\n                                )\n                                order by pa.attnum asc\n                            )\n                        from\n                            pg_attribute pa\n                            join pg_type pt\n                                on pa.atttypid = pt.oid\n                        where\n                            attrelid = entity_\n                            and attnum > 0\n                            and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n                    )\n                )\n                -- Add \\\"record\\\" key for insert and update\n                || case\n                    when action in ('INSERT', 'UPDATE') then\n                        jsonb_build_object(\n                            'record',\n                            (\n                                select jsonb_object_agg((c).name, (c).value)\n                                from unnest(columns) c\n                                where\n                                    (c).is_selectable\n                                    and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                            )\n                        )\n                    else '{}'::jsonb\n                end\n                -- Add \\\"old_record\\\" key for update and delete\n                || case\n                    when action = 'UPDATE' then\n                        jsonb_build_object(\n                                'old_record',\n                                (\n                                    select jsonb_object_agg((c).name, (c).value)\n                                    from unnest(old_columns) c\n                                    where\n                                        (c).is_selectable\n                                        and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                                )\n                            )\n                    when action = 'DELETE' then\n                        jsonb_build_object(\n                            'old_record',\n                            (\n                                select jsonb_object_agg((c).name, (c).value)\n                                from unnest(old_columns) c\n                                where\n                                    (c).is_selectable\n                                    and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                                    and ( not is_rls_enabled or (c).is_pkey ) -- if RLS enabled, we can't secure deletes so filter to pkey\n                            )\n                        )\n                    else '{}'::jsonb\n                end;\n\n                -- Create the prepared statement\n                if is_rls_enabled and action <> 'DELETE' then\n                    if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                        deallocate walrus_rls_stmt;\n                    end if;\n                    execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n                end if;\n\n                visible_to_subscription_ids = '{}';\n\n                for subscription_id, claims in (\n                        select\n                            subs.subscription_id,\n                            subs.claims\n                        from\n                            unnest(subscriptions) subs\n                        where\n                            subs.entity = entity_\n                            and subs.claims_role = working_role\n                            and (\n                                realtime.is_visible_through_filters(columns, subs.filters)\n                                or action = 'DELETE'\n                            )\n                ) loop\n\n                    if not is_rls_enabled or action = 'DELETE' then\n                        visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                    else\n                        -- Check if RLS allows the role to see the record\n                        perform\n                            set_config('role', working_role::text, true),\n                            set_config('request.jwt.claims', claims::text, true);\n\n                        execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                        if subscription_has_access then\n                            visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                        end if;\n                    end if;\n                end loop;\n\n                perform set_config('role', null, true);\n\n                return next (\n                    output,\n                    is_rls_enabled,\n                    visible_to_subscription_ids,\n                    case\n                        when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                        else '{}'\n                    end\n                )::realtime.wal_rls;\n\n            end if;\n        end loop;\n\n        perform set_config('role', null, true);\n    end;\n    $$;\n    \")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20230308225145_output_full_record_when_unchanged_toast.ex",
    "content": "defmodule Realtime.Tenants.Migrations.OutputFullRecordWhenUnchangedToast do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\n    create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n        returns setof realtime.wal_rls\n        language plpgsql\n        volatile\n    as $$\n    declare\n        -- Regclass of the table e.g. public.notes\n        entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n        -- I, U, D, T: insert, update ...\n        action realtime.action = (\n            case wal ->> 'action'\n                when 'I' then 'INSERT'\n                when 'U' then 'UPDATE'\n                when 'D' then 'DELETE'\n                else 'ERROR'\n            end\n        );\n\n        -- Is row level security enabled for the table\n        is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n        subscriptions realtime.subscription[] = array_agg(subs)\n            from\n                realtime.subscription subs\n            where\n                subs.entity = entity_;\n\n        -- Subscription vars\n        roles regrole[] = array_agg(distinct us.claims_role)\n            from\n                unnest(subscriptions) us;\n\n        working_role regrole;\n        claimed_role regrole;\n        claims jsonb;\n\n        subscription_id uuid;\n        subscription_has_access bool;\n        visible_to_subscription_ids uuid[] = '{}';\n\n        -- structured info for wal's columns\n        columns realtime.wal_column[];\n        -- previous identity values for update/delete\n        old_columns realtime.wal_column[];\n\n        error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n        -- Primary jsonb output for record\n        output jsonb;\n\n    begin\n        perform set_config('role', null, true);\n\n        columns =\n            array_agg(\n                (\n                    x->>'name',\n                    x->>'type',\n                    x->>'typeoid',\n                    realtime.cast(\n                        (x->'value') #>> '{}',\n                        coalesce(\n                            (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                            (x->>'type')::regtype\n                        )\n                    ),\n                    (pks ->> 'name') is not null,\n                    true\n                )::realtime.wal_column\n            )\n            from\n                jsonb_array_elements(wal -> 'columns') x\n                left join jsonb_array_elements(wal -> 'pk') pks\n                    on (x ->> 'name') = (pks ->> 'name');\n\n        old_columns =\n            array_agg(\n                (\n                    x->>'name',\n                    x->>'type',\n                    x->>'typeoid',\n                    realtime.cast(\n                        (x->'value') #>> '{}',\n                        coalesce(\n                            (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                            (x->>'type')::regtype\n                        )\n                    ),\n                    (pks ->> 'name') is not null,\n                    true\n                )::realtime.wal_column\n            )\n            from\n                jsonb_array_elements(wal -> 'identity') x\n                left join jsonb_array_elements(wal -> 'pk') pks\n                    on (x ->> 'name') = (pks ->> 'name');\n\n        for working_role in select * from unnest(roles) loop\n\n            -- Update `is_selectable` for columns and old_columns\n            columns =\n                array_agg(\n                    (\n                        c.name,\n                        c.type_name,\n                        c.type_oid,\n                        c.value,\n                        c.is_pkey,\n                        pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                    )::realtime.wal_column\n                )\n                from\n                    unnest(columns) c;\n\n            old_columns =\n                    array_agg(\n                        (\n                            c.name,\n                            c.type_name,\n                            c.type_oid,\n                            c.value,\n                            c.is_pkey,\n                            pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                        )::realtime.wal_column\n                    )\n                    from\n                        unnest(old_columns) c;\n\n            if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n                return next (\n                    jsonb_build_object(\n                        'schema', wal ->> 'schema',\n                        'table', wal ->> 'table',\n                        'type', action\n                    ),\n                    is_rls_enabled,\n                    -- subscriptions is already filtered by entity\n                    (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                    array['Error 400: Bad Request, no primary key']\n                )::realtime.wal_rls;\n\n            -- The claims role does not have SELECT permission to the primary key of entity\n            elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n                return next (\n                    jsonb_build_object(\n                        'schema', wal ->> 'schema',\n                        'table', wal ->> 'table',\n                        'type', action\n                    ),\n                    is_rls_enabled,\n                    (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                    array['Error 401: Unauthorized']\n                )::realtime.wal_rls;\n\n            else\n                output = jsonb_build_object(\n                    'schema', wal ->> 'schema',\n                    'table', wal ->> 'table',\n                    'type', action,\n                    'commit_timestamp', to_char(\n                        ((wal ->> 'timestamp')::timestamptz at time zone 'utc'),\n                        'YYYY-MM-DD\\\"T\\\"HH24:MI:SS.MS\\\"Z\\\"'\n                    ),\n                    'columns', (\n                        select\n                            jsonb_agg(\n                                jsonb_build_object(\n                                    'name', pa.attname,\n                                    'type', pt.typname\n                                )\n                                order by pa.attnum asc\n                            )\n                        from\n                            pg_attribute pa\n                            join pg_type pt\n                                on pa.atttypid = pt.oid\n                        where\n                            attrelid = entity_\n                            and attnum > 0\n                            and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n                    )\n                )\n                -- Add \\\"record\\\" key for insert and update\n                || case\n                    when action in ('INSERT', 'UPDATE') then\n                        jsonb_build_object(\n                            'record',\n                            (\n                                select\n                                    jsonb_object_agg(\n                                        -- if unchanged toast, get column name and value from old record\n                                        coalesce((c).name, (oc).name),\n                                        case\n                                            when (c).name is null then (oc).value\n                                            else (c).value\n                                        end\n                                    )\n                                from\n                                    unnest(columns) c\n                                    full outer join unnest(old_columns) oc\n                                        on (c).name = (oc).name\n                                where\n                                    coalesce((c).is_selectable, (oc).is_selectable)\n                                    and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                            )\n                        )\n                    else '{}'::jsonb\n                end\n                -- Add \\\"old_record\\\" key for update and delete\n                || case\n                    when action = 'UPDATE' then\n                        jsonb_build_object(\n                                'old_record',\n                                (\n                                    select jsonb_object_agg((c).name, (c).value)\n                                    from unnest(old_columns) c\n                                    where\n                                        (c).is_selectable\n                                        and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                                )\n                            )\n                    when action = 'DELETE' then\n                        jsonb_build_object(\n                            'old_record',\n                            (\n                                select jsonb_object_agg((c).name, (c).value)\n                                from unnest(old_columns) c\n                                where\n                                    (c).is_selectable\n                                    and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                                    and ( not is_rls_enabled or (c).is_pkey ) -- if RLS enabled, we can't secure deletes so filter to pkey\n                            )\n                        )\n                    else '{}'::jsonb\n                end;\n\n                -- Create the prepared statement\n                if is_rls_enabled and action <> 'DELETE' then\n                    if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                        deallocate walrus_rls_stmt;\n                    end if;\n                    execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n                end if;\n\n                visible_to_subscription_ids = '{}';\n\n                for subscription_id, claims in (\n                        select\n                            subs.subscription_id,\n                            subs.claims\n                        from\n                            unnest(subscriptions) subs\n                        where\n                            subs.entity = entity_\n                            and subs.claims_role = working_role\n                            and (\n                                realtime.is_visible_through_filters(columns, subs.filters)\n                                or action = 'DELETE'\n                            )\n                ) loop\n\n                    if not is_rls_enabled or action = 'DELETE' then\n                        visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                    else\n                        -- Check if RLS allows the role to see the record\n                        perform\n                            set_config('role', working_role::text, true),\n                            set_config('request.jwt.claims', claims::text, true);\n\n                        execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                        if subscription_has_access then\n                            visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                        end if;\n                    end if;\n                end loop;\n\n                perform set_config('role', null, true);\n\n                return next (\n                    output,\n                    is_rls_enabled,\n                    visible_to_subscription_ids,\n                    case\n                        when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                        else '{}'\n                    end\n                )::realtime.wal_rls;\n\n            end if;\n        end loop;\n\n        perform set_config('role', null, true);\n    end;\n    $$;\n    \")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20230328144023_create_list_changes_function.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateListChangesFunction do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\n      \"create or replace function realtime.list_changes(publication name, slot_name name, max_changes int, max_record_bytes int)\n      returns setof realtime.wal_rls\n      language sql\n      set log_min_messages to 'fatal'\n    as $$\n      with pub as (\n        select\n          concat_ws(\n            ',',\n            case when bool_or(pubinsert) then 'insert' else null end,\n            case when bool_or(pubupdate) then 'update' else null end,\n            case when bool_or(pubdelete) then 'delete' else null end\n          ) as w2j_actions,\n          coalesce(\n            string_agg(\n              realtime.quote_wal2json(format('%I.%I', schemaname, tablename)::regclass),\n              ','\n            ) filter (where ppt.tablename is not null and ppt.tablename not like '% %'),\n            ''\n          ) w2j_add_tables\n        from\n          pg_publication pp\n          left join pg_publication_tables ppt\n            on pp.pubname = ppt.pubname\n        where\n          pp.pubname = publication\n        group by\n          pp.pubname\n        limit 1\n      ),\n      w2j as (\n        select\n          x.*, pub.w2j_add_tables\n        from\n          pub,\n          pg_logical_slot_get_changes(\n            slot_name, null, max_changes,\n            'include-pk', 'true',\n            'include-transaction', 'false',\n            'include-timestamp', 'true',\n            'include-type-oids', 'true',\n            'format-version', '2',\n            'actions', pub.w2j_actions,\n            'add-tables', pub.w2j_add_tables\n          ) x\n      )\n      select\n        xyz.wal,\n        xyz.is_rls_enabled,\n        xyz.subscription_ids,\n        xyz.errors\n      from\n        w2j,\n        realtime.apply_rls(\n          wal := w2j.data::jsonb,\n          max_record_bytes := max_record_bytes\n        ) xyz(wal, is_rls_enabled, subscription_ids, errors)\n      where\n        w2j.w2j_add_tables <> ''\n        and xyz.subscription_ids[1] is not null\n    $$;\"\n    )\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20231018144023_create_channels.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateChannels do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    create table(:channels, prefix: \"realtime\") do\n      add(:name, :string, null: false)\n      timestamps()\n    end\n\n    create unique_index(:channels, [:name], prefix: \"realtime\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20231204144023_set_required_grants.ex",
    "content": "defmodule Realtime.Tenants.Migrations.SetRequiredGrants do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\"\"\n    GRANT USAGE ON SCHEMA realtime TO postgres, anon, authenticated, service_role\n    \"\"\")\n\n    execute(\"\"\"\n    GRANT SELECT ON ALL TABLES IN SCHEMA realtime TO postgres, anon, authenticated, service_role\n    \"\"\")\n\n    execute(\"\"\"\n    GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA realtime TO postgres, anon, authenticated, service_role\n    \"\"\")\n\n    execute(\"\"\"\n    GRANT USAGE ON ALL SEQUENCES IN SCHEMA realtime TO postgres, anon, authenticated, service_role\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20231204144024_create_rls_helper_functions.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateRlsHelperFunctions do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\"\"\n    create or replace function realtime.channel_name() returns text as $$\n    select nullif(current_setting('realtime.channel_name', true), '')::text;\n    $$ language sql stable;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20231204144025_enable_channels_rls.ex",
    "content": "defmodule Realtime.Tenants.Migrations.EnableChannelsRls do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"ALTER TABLE realtime.channels ENABLE row level security\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240108234812_add_channels_column_for_write_check.ex",
    "content": "defmodule Realtime.Tenants.Migrations.AddChannelsColumnForWriteCheck do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    alter table(:channels, prefix: \"realtime\") do\n      add :check, :boolean, default: false\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240109165339_add_update_grant_to_channels.ex",
    "content": "defmodule Realtime.Tenants.Migrations.AddUpdateGrantToChannels do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\"\"\n    GRANT UPDATE ON realtime.channels TO postgres, anon, authenticated, service_role\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240227174441_add_broadcast_permissions_table.ex",
    "content": "defmodule Realtime.Tenants.Migrations.AddBroadcastsPoliciesTable do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    create table(:broadcasts) do\n      add :channel_id, references(:channels, on_delete: :delete_all), null: false\n      add :check, :boolean, default: false, null: false\n      timestamps()\n    end\n\n    create unique_index(:broadcasts, :channel_id)\n\n    execute(\"ALTER TABLE realtime.broadcasts ENABLE row level security\")\n    execute(\"GRANT SELECT ON realtime.broadcasts TO postgres, anon, authenticated, service_role\")\n    execute(\"GRANT UPDATE ON realtime.broadcasts TO postgres, anon, authenticated, service_role\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240311171622_add_insert_and_delete_grant_to_channels.ex",
    "content": "defmodule Realtime.Tenants.Migrations.AddInsertAndDeleteGrantToChannels do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\"\"\n    GRANT INSERT, DELETE ON realtime.channels TO postgres, anon, authenticated, service_role\n    \"\"\")\n\n    execute(\"\"\"\n    GRANT INSERT ON realtime.broadcasts TO postgres, anon, authenticated, service_role\n    \"\"\")\n\n    execute(\"\"\"\n    GRANT USAGE ON SEQUENCE realtime.broadcasts_id_seq TO postgres, anon, authenticated, service_role\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240321100241_add_presences_permissions_table.ex",
    "content": "defmodule Realtime.Tenants.Migrations.AddPresencesPoliciesTable do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    create table(:presences) do\n      add :channel_id, references(:channels, on_delete: :delete_all), null: false\n      add :check, :boolean, default: false, null: false\n      timestamps()\n    end\n\n    create unique_index(:presences, :channel_id)\n\n    execute(\"ALTER TABLE realtime.presences ENABLE row level security\")\n    execute(\"GRANT SELECT ON realtime.presences TO postgres, anon, authenticated, service_role\")\n    execute(\"GRANT UPDATE ON realtime.presences TO postgres, anon, authenticated, service_role\")\n\n    execute(\"\"\"\n    GRANT INSERT ON realtime.presences TO postgres, anon, authenticated, service_role\n    \"\"\")\n\n    execute(\"\"\"\n    GRANT USAGE ON SEQUENCE realtime.presences_id_seq TO postgres, anon, authenticated, service_role\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240401105812_create_realtime_admin_and_move_ownership.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateRealtimeAdminAndMoveOwnership do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    execute(\"\"\"\n    DO\n    $do$\n    BEGIN\n       IF EXISTS (\n          SELECT FROM pg_catalog.pg_roles\n          WHERE  rolname = 'supabase_realtime_admin') THEN\n\n          RAISE NOTICE 'Role \"supabase_realtime_admin\" already exists. Skipping.';\n       ELSE\n          CREATE ROLE supabase_realtime_admin WITH NOINHERIT NOLOGIN NOREPLICATION;\n       END IF;\n    END\n    $do$;\n    \"\"\")\n\n    execute(\"GRANT ALL PRIVILEGES ON SCHEMA realtime TO supabase_realtime_admin\")\n    execute(\"GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA realtime TO supabase_realtime_admin\")\n    execute(\"GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA realtime TO supabase_realtime_admin\")\n    execute(\"GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA realtime TO supabase_realtime_admin\")\n\n    execute(\"ALTER table realtime.channels OWNER to supabase_realtime_admin\")\n    execute(\"ALTER table realtime.broadcasts OWNER to supabase_realtime_admin\")\n    execute(\"ALTER table realtime.presences OWNER TO supabase_realtime_admin\")\n    execute(\"ALTER function realtime.channel_name() owner to supabase_realtime_admin\")\n\n    execute(\"GRANT supabase_realtime_admin TO postgres\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240418121054_remove_check_columns.ex",
    "content": "defmodule Realtime.Tenants.Migrations.RemoveCheckColumns do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    alter table(:channels) do\n      remove :check\n    end\n\n    alter table(:broadcasts) do\n      remove :check\n    end\n\n    alter table(:presences) do\n      remove :check\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240523004032_redefine_authorization_tables.ex",
    "content": "defmodule Realtime.Tenants.Migrations.RedefineAuthorizationTables do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    drop table(:broadcasts), mode: :cascade\n    drop table(:presences), mode: :cascade\n    drop table(:channels), mode: :cascade\n\n    create_if_not_exists table(:messages) do\n      add :topic, :text, null: false\n      add :extension, :text, null: false\n      timestamps()\n    end\n\n    create_if_not_exists index(:messages, [:topic])\n\n    execute(\"ALTER TABLE realtime.messages ENABLE row level security\")\n    execute(\"GRANT SELECT ON realtime.messages TO postgres, anon, authenticated, service_role\")\n    execute(\"GRANT UPDATE ON realtime.messages TO postgres, anon, authenticated, service_role\")\n\n    execute(\"\"\"\n    GRANT INSERT ON realtime.messages TO postgres, anon, authenticated, service_role\n    \"\"\")\n\n    execute(\"\"\"\n    GRANT USAGE ON SEQUENCE realtime.messages_id_seq TO postgres, anon, authenticated, service_role\n    \"\"\")\n\n    execute(\"ALTER table realtime.messages OWNER to supabase_realtime_admin\")\n\n    execute(\"\"\"\n    DROP function realtime.channel_name\n    \"\"\")\n\n    execute(\"\"\"\n    create or replace function realtime.topic() returns text as $$\n    select nullif(current_setting('realtime.topic', true), '')::text;\n    $$ language sql stable;\n    \"\"\")\n\n    execute(\"ALTER function realtime.topic() owner to supabase_realtime_admin\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240618124746_fix_walrus_role_handling.ex",
    "content": "defmodule Realtime.Tenants.Migrations.FixWalrusRoleHandling do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    execute \"\"\"\n    create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n    returns setof realtime.wal_rls\n    language plpgsql\n    volatile\n    as $$\n    declare\n    -- Regclass of the table e.g. public.notes\n    entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n    -- I, U, D, T: insert, update ...\n    action realtime.action = (\n        case wal ->> 'action'\n            when 'I' then 'INSERT'\n            when 'U' then 'UPDATE'\n            when 'D' then 'DELETE'\n            else 'ERROR'\n        end\n    );\n\n    -- Is row level security enabled for the table\n    is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n    subscriptions realtime.subscription[] = array_agg(subs)\n        from\n            realtime.subscription subs\n        where\n            subs.entity = entity_;\n\n    -- Subscription vars\n    roles regrole[] = array_agg(distinct us.claims_role::text)\n        from\n            unnest(subscriptions) us;\n\n    working_role regrole;\n    claimed_role regrole;\n    claims jsonb;\n\n    subscription_id uuid;\n    subscription_has_access bool;\n    visible_to_subscription_ids uuid[] = '{}';\n\n    -- structured info for wal's columns\n    columns realtime.wal_column[];\n    -- previous identity values for update/delete\n    old_columns realtime.wal_column[];\n\n    error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n    -- Primary jsonb output for record\n    output jsonb;\n\n    begin\n    perform set_config('role', null, true);\n\n    columns =\n        array_agg(\n            (\n                x->>'name',\n                x->>'type',\n                x->>'typeoid',\n                realtime.cast(\n                    (x->'value') #>> '{}',\n                    coalesce(\n                        (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                        (x->>'type')::regtype\n                    )\n                ),\n                (pks ->> 'name') is not null,\n                true\n            )::realtime.wal_column\n        )\n        from\n            jsonb_array_elements(wal -> 'columns') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n                on (x ->> 'name') = (pks ->> 'name');\n\n    old_columns =\n        array_agg(\n            (\n                x->>'name',\n                x->>'type',\n                x->>'typeoid',\n                realtime.cast(\n                    (x->'value') #>> '{}',\n                    coalesce(\n                        (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                        (x->>'type')::regtype\n                    )\n                ),\n                (pks ->> 'name') is not null,\n                true\n            )::realtime.wal_column\n        )\n        from\n            jsonb_array_elements(wal -> 'identity') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n                on (x ->> 'name') = (pks ->> 'name');\n\n    for working_role in select * from unnest(roles) loop\n\n        -- Update `is_selectable` for columns and old_columns\n        columns =\n            array_agg(\n                (\n                    c.name,\n                    c.type_name,\n                    c.type_oid,\n                    c.value,\n                    c.is_pkey,\n                    pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                )::realtime.wal_column\n            )\n            from\n                unnest(columns) c;\n\n        old_columns =\n                array_agg(\n                    (\n                        c.name,\n                        c.type_name,\n                        c.type_oid,\n                        c.value,\n                        c.is_pkey,\n                        pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                    )::realtime.wal_column\n                )\n                from\n                    unnest(old_columns) c;\n\n        if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n            return next (\n                jsonb_build_object(\n                    'schema', wal ->> 'schema',\n                    'table', wal ->> 'table',\n                    'type', action\n                ),\n                is_rls_enabled,\n                -- subscriptions is already filtered by entity\n                (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                array['Error 400: Bad Request, no primary key']\n            )::realtime.wal_rls;\n\n        -- The claims role does not have SELECT permission to the primary key of entity\n        elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n            return next (\n                jsonb_build_object(\n                    'schema', wal ->> 'schema',\n                    'table', wal ->> 'table',\n                    'type', action\n                ),\n                is_rls_enabled,\n                (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                array['Error 401: Unauthorized']\n            )::realtime.wal_rls;\n\n        else\n            output = jsonb_build_object(\n                'schema', wal ->> 'schema',\n                'table', wal ->> 'table',\n                'type', action,\n                'commit_timestamp', to_char(\n                    ((wal ->> 'timestamp')::timestamptz at time zone 'utc'),\n                    'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'\n                ),\n                'columns', (\n                    select\n                        jsonb_agg(\n                            jsonb_build_object(\n                                'name', pa.attname,\n                                'type', pt.typname\n                            )\n                            order by pa.attnum asc\n                        )\n                    from\n                        pg_attribute pa\n                        join pg_type pt\n                            on pa.atttypid = pt.oid\n                    where\n                        attrelid = entity_\n                        and attnum > 0\n                        and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n                )\n            )\n            -- Add \"record\" key for insert and update\n            || case\n                when action in ('INSERT', 'UPDATE') then\n                    jsonb_build_object(\n                        'record',\n                        (\n                            select\n                                jsonb_object_agg(\n                                    -- if unchanged toast, get column name and value from old record\n                                    coalesce((c).name, (oc).name),\n                                    case\n                                        when (c).name is null then (oc).value\n                                        else (c).value\n                                    end\n                                )\n                            from\n                                unnest(columns) c\n                                full outer join unnest(old_columns) oc\n                                    on (c).name = (oc).name\n                            where\n                                coalesce((c).is_selectable, (oc).is_selectable)\n                                and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                        )\n                    )\n                else '{}'::jsonb\n            end\n            -- Add \"old_record\" key for update and delete\n            || case\n                when action = 'UPDATE' then\n                    jsonb_build_object(\n                            'old_record',\n                            (\n                                select jsonb_object_agg((c).name, (c).value)\n                                from unnest(old_columns) c\n                                where\n                                    (c).is_selectable\n                                    and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                            )\n                        )\n                when action = 'DELETE' then\n                    jsonb_build_object(\n                        'old_record',\n                        (\n                            select jsonb_object_agg((c).name, (c).value)\n                            from unnest(old_columns) c\n                            where\n                                (c).is_selectable\n                                and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                                and ( not is_rls_enabled or (c).is_pkey ) -- if RLS enabled, we can't secure deletes so filter to pkey\n                        )\n                    )\n                else '{}'::jsonb\n            end;\n\n            -- Create the prepared statement\n            if is_rls_enabled and action <> 'DELETE' then\n                if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                    deallocate walrus_rls_stmt;\n                end if;\n                execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n            end if;\n\n            visible_to_subscription_ids = '{}';\n\n            for subscription_id, claims in (\n                    select\n                        subs.subscription_id,\n                        subs.claims\n                    from\n                        unnest(subscriptions) subs\n                    where\n                        subs.entity = entity_\n                        and subs.claims_role = working_role\n                        and (\n                            realtime.is_visible_through_filters(columns, subs.filters)\n                            or action = 'DELETE'\n                        )\n            ) loop\n\n                if not is_rls_enabled or action = 'DELETE' then\n                    visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                else\n                    -- Check if RLS allows the role to see the record\n                    perform\n                        -- Trim leading and trailing quotes from working_role because set_config\n                        -- doesn't recognize the role as valid if they are included\n                        set_config('role', trim(both '\"' from working_role::text), true),\n                        set_config('request.jwt.claims', claims::text, true);\n\n                    execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                    if subscription_has_access then\n                        visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                    end if;\n                end if;\n            end loop;\n\n            perform set_config('role', null, true);\n\n            return next (\n                output,\n                is_rls_enabled,\n                visible_to_subscription_ids,\n                case\n                    when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                    else '{}'\n                end\n            )::realtime.wal_rls;\n\n        end if;\n    end loop;\n\n    perform set_config('role', null, true);\n    end;\n    $$;\n    \"\"\"\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240801235015_unlogged_messages_table.ex",
    "content": "defmodule Realtime.Tenants.Migrations.UnloggedMessagesTable do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    execute \"\"\"\n    -- Commented to have oriole compatability\n    -- ALTER TABLE realtime.messages SET UNLOGGED;\n    \"\"\"\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240805133720_logged_messages_table.ex",
    "content": "defmodule Realtime.Tenants.Migrations.LoggedMessagesTable do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    execute \"\"\"\n    -- Commented to have oriole compatability\n    -- ALTER TABLE realtime.messages SET LOGGED;\n    \"\"\"\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240827160934_filter_delete_postgres_changes.ex",
    "content": "defmodule Realtime.Tenants.Migrations.FilterDeletePostgresChanges do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    execute \"\"\"\n    create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n    returns setof realtime.wal_rls\n    language plpgsql\n    volatile\n    as $$\n    declare\n    -- Regclass of the table e.g. public.notes\n    entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n    -- I, U, D, T: insert, update ...\n    action realtime.action = (\n        case wal ->> 'action'\n            when 'I' then 'INSERT'\n            when 'U' then 'UPDATE'\n            when 'D' then 'DELETE'\n            else 'ERROR'\n        end\n    );\n\n    -- Is row level security enabled for the table\n    is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n    subscriptions realtime.subscription[] = array_agg(subs)\n        from\n            realtime.subscription subs\n        where\n            subs.entity = entity_;\n\n    -- Subscription vars\n    roles regrole[] = array_agg(distinct us.claims_role::text)\n        from\n            unnest(subscriptions) us;\n\n    working_role regrole;\n    claimed_role regrole;\n    claims jsonb;\n\n    subscription_id uuid;\n    subscription_has_access bool;\n    visible_to_subscription_ids uuid[] = '{}';\n\n    -- structured info for wal's columns\n    columns realtime.wal_column[];\n    -- previous identity values for update/delete\n    old_columns realtime.wal_column[];\n\n    error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n    -- Primary jsonb output for record\n    output jsonb;\n\n    begin\n    perform set_config('role', null, true);\n\n    columns =\n        array_agg(\n            (\n                x->>'name',\n                x->>'type',\n                x->>'typeoid',\n                realtime.cast(\n                    (x->'value') #>> '{}',\n                    coalesce(\n                        (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                        (x->>'type')::regtype\n                    )\n                ),\n                (pks ->> 'name') is not null,\n                true\n            )::realtime.wal_column\n        )\n        from\n            jsonb_array_elements(wal -> 'columns') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n                on (x ->> 'name') = (pks ->> 'name');\n\n    old_columns =\n        array_agg(\n            (\n                x->>'name',\n                x->>'type',\n                x->>'typeoid',\n                realtime.cast(\n                    (x->'value') #>> '{}',\n                    coalesce(\n                        (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                        (x->>'type')::regtype\n                    )\n                ),\n                (pks ->> 'name') is not null,\n                true\n            )::realtime.wal_column\n        )\n        from\n            jsonb_array_elements(wal -> 'identity') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n                on (x ->> 'name') = (pks ->> 'name');\n\n    for working_role in select * from unnest(roles) loop\n\n        -- Update `is_selectable` for columns and old_columns\n        columns =\n            array_agg(\n                (\n                    c.name,\n                    c.type_name,\n                    c.type_oid,\n                    c.value,\n                    c.is_pkey,\n                    pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                )::realtime.wal_column\n            )\n            from\n                unnest(columns) c;\n\n        old_columns =\n                array_agg(\n                    (\n                        c.name,\n                        c.type_name,\n                        c.type_oid,\n                        c.value,\n                        c.is_pkey,\n                        pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                    )::realtime.wal_column\n                )\n                from\n                    unnest(old_columns) c;\n\n        if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n            return next (\n                jsonb_build_object(\n                    'schema', wal ->> 'schema',\n                    'table', wal ->> 'table',\n                    'type', action\n                ),\n                is_rls_enabled,\n                -- subscriptions is already filtered by entity\n                (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                array['Error 400: Bad Request, no primary key']\n            )::realtime.wal_rls;\n\n        -- The claims role does not have SELECT permission to the primary key of entity\n        elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n            return next (\n                jsonb_build_object(\n                    'schema', wal ->> 'schema',\n                    'table', wal ->> 'table',\n                    'type', action\n                ),\n                is_rls_enabled,\n                (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                array['Error 401: Unauthorized']\n            )::realtime.wal_rls;\n\n        else\n            output = jsonb_build_object(\n                'schema', wal ->> 'schema',\n                'table', wal ->> 'table',\n                'type', action,\n                'commit_timestamp', to_char(\n                    ((wal ->> 'timestamp')::timestamptz at time zone 'utc'),\n                    'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'\n                ),\n                'columns', (\n                    select\n                        jsonb_agg(\n                            jsonb_build_object(\n                                'name', pa.attname,\n                                'type', pt.typname\n                            )\n                            order by pa.attnum asc\n                        )\n                    from\n                        pg_attribute pa\n                        join pg_type pt\n                            on pa.atttypid = pt.oid\n                    where\n                        attrelid = entity_\n                        and attnum > 0\n                        and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n                )\n            )\n            -- Add \"record\" key for insert and update\n            || case\n                when action in ('INSERT', 'UPDATE') then\n                    jsonb_build_object(\n                        'record',\n                        (\n                            select\n                                jsonb_object_agg(\n                                    -- if unchanged toast, get column name and value from old record\n                                    coalesce((c).name, (oc).name),\n                                    case\n                                        when (c).name is null then (oc).value\n                                        else (c).value\n                                    end\n                                )\n                            from\n                                unnest(columns) c\n                                full outer join unnest(old_columns) oc\n                                    on (c).name = (oc).name\n                            where\n                                coalesce((c).is_selectable, (oc).is_selectable)\n                                and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                        )\n                    )\n                else '{}'::jsonb\n            end\n            -- Add \"old_record\" key for update and delete\n            || case\n                when action = 'UPDATE' then\n                    jsonb_build_object(\n                            'old_record',\n                            (\n                                select jsonb_object_agg((c).name, (c).value)\n                                from unnest(old_columns) c\n                                where\n                                    (c).is_selectable\n                                    and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                            )\n                        )\n                when action = 'DELETE' then\n                    jsonb_build_object(\n                        'old_record',\n                        (\n                            select jsonb_object_agg((c).name, (c).value)\n                            from unnest(old_columns) c\n                            where\n                                (c).is_selectable\n                                and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                                and ( not is_rls_enabled or (c).is_pkey ) -- if RLS enabled, we can't secure deletes so filter to pkey\n                        )\n                    )\n                else '{}'::jsonb\n            end;\n\n            -- Create the prepared statement\n            if is_rls_enabled and action <> 'DELETE' then\n                if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                    deallocate walrus_rls_stmt;\n                end if;\n                execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n            end if;\n\n            visible_to_subscription_ids = '{}';\n\n            for subscription_id, claims in (\n                    select\n                        subs.subscription_id,\n                        subs.claims\n                    from\n                        unnest(subscriptions) subs\n                    where\n                        subs.entity = entity_\n                        and subs.claims_role = working_role\n                        and (\n                            realtime.is_visible_through_filters(columns, subs.filters)\n                            or (\n                              action = 'DELETE'\n                              and realtime.is_visible_through_filters(old_columns, subs.filters)\n                            )\n                        )\n            ) loop\n\n                if not is_rls_enabled or action = 'DELETE' then\n                    visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                else\n                    -- Check if RLS allows the role to see the record\n                    perform\n                        -- Trim leading and trailing quotes from working_role because set_config\n                        -- doesn't recognize the role as valid if they are included\n                        set_config('role', trim(both '\"' from working_role::text), true),\n                        set_config('request.jwt.claims', claims::text, true);\n\n                    execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                    if subscription_has_access then\n                        visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                    end if;\n                end if;\n            end loop;\n\n            perform set_config('role', null, true);\n\n            return next (\n                output,\n                is_rls_enabled,\n                visible_to_subscription_ids,\n                case\n                    when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                    else '{}'\n                end\n            )::realtime.wal_rls;\n\n        end if;\n    end loop;\n\n    perform set_config('role', null, true);\n    end;\n    $$;\n    \"\"\"\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240919163303_add_payload_to_messages.ex",
    "content": "defmodule Realtime.Tenants.Migrations.AddPayloadToMessages do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    alter table(:messages) do\n      add_if_not_exists :payload, :map\n      add_if_not_exists :event, :text\n      add_if_not_exists :topic, :text\n      add_if_not_exists :private, :boolean, default: true\n\n      modify :inserted_at, :utc_datetime, default: fragment(\"now()\")\n      modify :updated_at, :utc_datetime, default: fragment(\"now()\")\n    end\n\n    execute \"\"\"\n    CREATE OR REPLACE FUNCTION realtime.send(payload jsonb, event text, topic text, private boolean DEFAULT true)\n    RETURNS void\n    AS $$\n    BEGIN\n        INSERT INTO realtime.messages (payload, event, topic, private, extension)\n        VALUES (payload, event, topic, private, 'broadcast');\n    END;\n    $$\n    LANGUAGE plpgsql;\n    \"\"\"\n\n    execute \"\"\"\n    CREATE OR REPLACE FUNCTION realtime.broadcast_changes (topic_name text, event_name text, operation text, table_name text, table_schema text, NEW record, OLD record, level text DEFAULT 'ROW')\n        RETURNS void\n        AS $$\n    DECLARE\n        -- Declare a variable to hold the JSONB representation of the row\n        row_data jsonb := '{}'::jsonb;\n    BEGIN\n        IF level = 'STATEMENT' THEN\n            RAISE EXCEPTION 'function can only be triggered for each row, not for each statement';\n        END IF;\n        -- Check the operation type and handle accordingly\n        IF operation = 'INSERT' OR operation = 'UPDATE' OR operation = 'DELETE' THEN\n            row_data := jsonb_build_object('old_record', OLD, 'record', NEW, 'operation', operation, 'table', table_name, 'schema', table_schema);\n            PERFORM realtime.send (row_data, event_name, topic_name);\n        ELSE\n            RAISE EXCEPTION 'Unexpected operation type: %', operation;\n        END IF;\n    EXCEPTION\n        WHEN OTHERS THEN\n            RAISE EXCEPTION 'Failed to process the row: %', SQLERRM;\n    END;\n\n    $$\n    LANGUAGE plpgsql;\n    \"\"\"\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20240919163305_change_messages_id_type.ex",
    "content": "defmodule Realtime.Tenants.Migrations.ChangeMessagesIdType do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    alter table(:messages) do\n      add_if_not_exists :uuid, :uuid\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20241019105805_uuid_auto_generation.ex",
    "content": "defmodule Realtime.Tenants.Migrations.UuidAutoGeneration do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    alter table(:messages) do\n      modify :uuid, :uuid, null: false, default: fragment(\"gen_random_uuid()\")\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20241030150047_messages_partitioning.ex",
    "content": "defmodule Realtime.Tenants.Migrations.MessagesPartitioning do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    execute(\"\"\"\n        CREATE TABLE IF NOT EXISTS realtime.messages_new (\n          id BIGSERIAL,\n          uuid TEXT DEFAULT gen_random_uuid(),\n          topic TEXT NOT NULL,\n          extension TEXT NOT NULL,\n          payload JSONB,\n          event TEXT,\n          private BOOLEAN DEFAULT FALSE,\n          updated_at TIMESTAMP NOT NULL DEFAULT NOW(),\n          inserted_at TIMESTAMP NOT NULL DEFAULT NOW(),\n          PRIMARY KEY (id, inserted_at)\n        ) PARTITION BY RANGE (inserted_at)\n    \"\"\")\n\n    execute(\"ALTER TABLE realtime.messages_new ENABLE ROW LEVEL SECURITY\")\n\n    execute(\"\"\"\n    DO $$\n    DECLARE\n      rec record;\n      sql text;\n      role_list text;\n    BEGIN\n      FOR rec IN\n        SELECT *\n        FROM pg_policies\n        WHERE schemaname = 'realtime'\n        AND tablename = 'messages'\n      LOOP\n        -- Start constructing the create policy statement\n        sql := 'CREATE POLICY ' || quote_ident(rec.policyname) ||\n             ' ON realtime.messages_new ';\n\n        IF (rec.permissive = 'PERMISSIVE') THEN\n          sql := sql || 'AS PERMISSIVE ';\n        ELSE\n          sql := sql || 'AS RESTRICTIVE ';\n        END IF;\n\n        sql := sql || ' FOR ' || rec.cmd;\n\n        -- Include roles if specified\n        IF rec.roles IS NOT NULL AND array_length(rec.roles, 1) > 0 THEN\n          role_list := (\n            SELECT string_agg(quote_ident(role), ', ')\n            FROM unnest(rec.roles) AS role\n          );\n          sql := sql || ' TO ' || role_list;\n        END IF;\n\n        -- Include using clause if specified\n        IF rec.qual IS NOT NULL THEN\n          sql := sql || ' USING (' || rec.qual || ')';\n        END IF;\n\n        -- Include with check clause if specified\n        IF rec.with_check IS NOT NULL THEN\n          sql := sql || ' WITH CHECK (' || rec.with_check || ')';\n        END IF;\n\n        -- Output the constructed sql for debugging purposes\n        RAISE NOTICE 'Executing: %', sql;\n\n        -- Execute the constructed sql statement\n        EXECUTE sql;\n      END LOOP;\n    END\n    $$\n    \"\"\")\n\n    execute(\"ALTER TABLE realtime.messages RENAME TO messages_old\")\n    execute(\"ALTER TABLE realtime.messages_new RENAME TO messages\")\n    execute(\"DROP TABLE realtime.messages_old\")\n\n    execute(\"CREATE SEQUENCE IF NOT EXISTS realtime.messages_id_seq\")\n\n    execute(\"ALTER TABLE realtime.messages ALTER COLUMN id SET DEFAULT nextval('realtime.messages_id_seq')\")\n\n    execute(\"ALTER table realtime.messages OWNER to supabase_realtime_admin\")\n\n    execute(\"GRANT USAGE ON SEQUENCE realtime.messages_id_seq TO postgres, anon, authenticated, service_role\")\n\n    execute(\"GRANT SELECT ON realtime.messages TO postgres, anon, authenticated, service_role\")\n    execute(\"GRANT UPDATE ON realtime.messages TO postgres, anon, authenticated, service_role\")\n    execute(\"GRANT INSERT ON realtime.messages TO postgres, anon, authenticated, service_role\")\n\n    execute(\"ALTER TABLE realtime.messages ENABLE ROW LEVEL SECURITY\")\n\n    execute(\"\"\"\n    CREATE OR REPLACE FUNCTION realtime.send(payload jsonb, event text, topic text, private boolean DEFAULT true)\n    RETURNS void\n    AS $$\n    DECLARE\n      partition_name text;\n    BEGIN\n      partition_name := 'messages_' || to_char(NOW(), 'YYYY_MM_DD');\n\n      IF NOT EXISTS (\n        SELECT 1\n        FROM pg_class c\n        JOIN pg_namespace n ON n.oid = c.relnamespace\n        WHERE n.nspname = 'realtime'\n        AND c.relname = partition_name\n      ) THEN\n        EXECUTE format(\n          'CREATE TABLE %I PARTITION OF realtime.messages FOR VALUES FROM (%L) TO (%L)',\n          partition_name,\n          NOW(),\n          (NOW() + interval '1 day')::timestamp\n        );\n      END IF;\n\n      INSERT INTO realtime.messages (payload, event, topic, private, extension)\n      VALUES (payload, event, topic, private, 'broadcast');\n    END;\n    $$\n    LANGUAGE plpgsql;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20241108114728_messages_using_uuid.ex",
    "content": "defmodule Realtime.Tenants.Migrations.MessagesUsingUuid do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    alter table(:messages) do\n      remove(:id)\n      remove(:uuid)\n      add(:id, :uuid, null: false, default: fragment(\"gen_random_uuid()\"))\n    end\n\n    execute(\"ALTER TABLE realtime.messages ADD PRIMARY KEY (id, inserted_at)\")\n    execute(\"DROP SEQUENCE realtime.messages_id_seq\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20241121104152_fix_send_function_.ex",
    "content": "defmodule Realtime.Tenants.Migrations.FixSendFunction do\n  @moduledoc false\n  use Ecto.Migration\n\n  # We missed the schema prefix of `realtime.` in the create table partition statement\n  def change do\n    execute(\"\"\"\n    CREATE OR REPLACE FUNCTION realtime.send(payload jsonb, event text, topic text, private boolean DEFAULT true)\n    RETURNS void\n    AS $$\n    DECLARE\n      partition_name text;\n    BEGIN\n      partition_name := 'messages_' || to_char(NOW(), 'YYYY_MM_DD');\n\n      IF NOT EXISTS (\n        SELECT 1\n        FROM pg_class c\n        JOIN pg_namespace n ON n.oid = c.relnamespace\n        WHERE n.nspname = 'realtime'\n        AND c.relname = partition_name\n      ) THEN\n        EXECUTE format(\n          'CREATE TABLE realtime.%I PARTITION OF realtime.messages FOR VALUES FROM (%L) TO (%L)',\n          partition_name,\n          NOW(),\n          (NOW() + interval '1 day')::timestamp\n        );\n      END IF;\n\n      INSERT INTO realtime.messages (payload, event, topic, private, extension)\n      VALUES (payload, event, topic, private, 'broadcast');\n    END;\n    $$\n    LANGUAGE plpgsql;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20241130184212_recreate_entity_index_using_btree.ex",
    "content": "defmodule Realtime.Tenants.Migrations.RecreateEntityIndexUsingBtree do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    execute(\"drop index if exists \\\"realtime\\\".\\\"ix_realtime_subscription_entity\\\"\")\n\n    execute(\"\"\"\n    do $$\n    begin\n      create index concurrently if not exists ix_realtime_subscription_entity on realtime.subscription using btree (entity);\n    exception\n      when others then\n        create index if not exists ix_realtime_subscription_entity on realtime.subscription using btree (entity);\n    end$$;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20241220035512_fix_send_function_partition_creation.ex",
    "content": "defmodule Realtime.Tenants.Migrations.FixSendFunctionPartitionCreation do\n  @moduledoc false\n  use Ecto.Migration\n\n  # We missed the schema prefix of `realtime.` in the create table partition statement\n  def change do\n    execute(\"\"\"\n    CREATE OR REPLACE FUNCTION realtime.send(payload jsonb, event text, topic text, private boolean DEFAULT true)\n    RETURNS void\n    AS $$\n    DECLARE\n    partition_name text;\n    partition_start timestamp;\n    partition_end timestamp;\n    BEGIN\n      partition_start := date_trunc('day', NOW());\n      partition_end := partition_start + interval '1 day';\n      partition_name := 'messages_' || to_char(partition_start, 'YYYY_MM_DD');\n\n      BEGIN\n        EXECUTE format(\n          'CREATE TABLE IF NOT EXISTS realtime.%I PARTITION OF realtime.messages FOR VALUES FROM (%L) TO (%L)',\n          partition_name,\n          partition_start,\n          partition_end\n        );\n        EXCEPTION WHEN duplicate_table THEN\n        -- Ignore; table already exists\n      END;\n\n      INSERT INTO realtime.messages (payload, event, topic, private, extension)\n      VALUES (payload, event, topic, private, 'broadcast');\n    END;\n    $$\n    LANGUAGE plpgsql;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20241220123912_realtime_send_handle_exceptions_remove_partition_creation.ex",
    "content": "defmodule Realtime.Tenants.Migrations.RealtimeSendHandleExceptionsRemovePartitionCreation do\n  @moduledoc false\n  use Ecto.Migration\n\n  # We missed the schema prefix of `realtime.` in the create table partition statement\n  def change do\n    execute(\"\"\"\n    CREATE OR REPLACE FUNCTION realtime.send(payload jsonb, event text, topic text, private boolean DEFAULT true ) RETURNS void\n    AS $$\n    BEGIN\n      BEGIN\n        -- Attempt to insert the message\n        INSERT INTO realtime.messages (payload, event, topic, private, extension)\n        VALUES (payload, event, topic, private, 'broadcast');\n      EXCEPTION\n        WHEN OTHERS THEN\n          -- Capture and notify the error\n          PERFORM pg_notify(\n              'realtime:system',\n              jsonb_build_object(\n                  'error', SQLERRM,\n                  'function', 'realtime.send',\n                  'event', event,\n                  'topic', topic,\n                  'private', private\n              )::text\n          );\n      END;\n    END;\n    $$\n    LANGUAGE plpgsql;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20241224161212_realtime_send_sets_config.ex",
    "content": "defmodule Realtime.Tenants.Migrations.RealtimeSendSetsConfig do\n  @moduledoc false\n  use Ecto.Migration\n\n  # We missed the schema prefix of `realtime.` in the create table partition statement\n  def change do\n    execute(\"\"\"\n    CREATE OR REPLACE FUNCTION realtime.send(payload jsonb, event text, topic text, private boolean DEFAULT true ) RETURNS void\n    AS $$\n    BEGIN\n      BEGIN\n        -- Set the topic configuration\n        SET LOCAL realtime.topic TO topic;\n\n        -- Attempt to insert the message\n        INSERT INTO realtime.messages (payload, event, topic, private, extension)\n        VALUES (payload, event, topic, private, 'broadcast');\n      EXCEPTION\n        WHEN OTHERS THEN\n          -- Capture and notify the error\n          PERFORM pg_notify(\n              'realtime:system',\n              jsonb_build_object(\n                  'error', SQLERRM,\n                  'function', 'realtime.send',\n                  'event', event,\n                  'topic', topic,\n                  'private', private\n              )::text\n          );\n      END;\n    END;\n    $$\n    LANGUAGE plpgsql;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20250107150512_realtime_subscription_unlogged.ex",
    "content": "defmodule Realtime.Tenants.Migrations.RealtimeSubscriptionUnlogged do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    execute(\"\"\"\n    -- Commented to have oriole compatability\n    -- ALTER TABLE realtime.subscription SET UNLOGGED;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20250110162412_realtime_subscription_logged.ex",
    "content": "defmodule Realtime.Tenants.Migrations.RealtimeSubscriptionLogged do\n  @moduledoc false\n  use Ecto.Migration\n\n  # PG Updates doesn't allow us to use UNLOGGED tables due to the fact that Sequences on PG14 still need to be logged\n  def change do\n    execute(\"\"\"\n    -- Commented to have oriole compatability\n    -- ALTER TABLE realtime.subscription SET LOGGED;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20250123174212_remove_unused_publications.ex",
    "content": "defmodule Realtime.Tenants.Migrations.RemoveUnusedPublications do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    execute(\"\"\"\n    DO $$\n    DECLARE\n      r RECORD;\n    BEGIN\n    FOR r IN\n        SELECT pubname FROM pg_publication WHERE pubname LIKE 'realtime_messages%' or pubname LIKE 'supabase_realtime_messages%'\n    LOOP\n        EXECUTE 'DROP PUBLICATION IF EXISTS ' || quote_ident(r.pubname) || ';' ;\n    END LOOP;\n    END $$;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20250128220012_realtime_send_sets_topic_config.ex",
    "content": "defmodule Realtime.Tenants.Migrations.RealtimeSendSetsTopicConfig do\n  @moduledoc false\n  use Ecto.Migration\n\n  # We missed the schema prefix of `realtime.` in the create table partition statement\n  def change do\n    execute(\"\"\"\n    CREATE OR REPLACE FUNCTION realtime.send(payload jsonb, event text, topic text, private boolean DEFAULT true ) RETURNS void\n    AS $$\n    BEGIN\n      BEGIN\n        -- Set the topic configuration\n        EXECUTE format('SET LOCAL realtime.topic TO %L', topic);\n\n        -- Attempt to insert the message\n        INSERT INTO realtime.messages (payload, event, topic, private, extension)\n        VALUES (payload, event, topic, private, 'broadcast');\n      EXCEPTION\n        WHEN OTHERS THEN\n          -- Capture and notify the error\n          PERFORM pg_notify(\n              'realtime:system',\n              jsonb_build_object(\n                  'error', SQLERRM,\n                  'function', 'realtime.send',\n                  'event', event,\n                  'topic', topic,\n                  'private', private\n              )::text\n          );\n      END;\n    END;\n    $$\n    LANGUAGE plpgsql;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20250506224012_subscription_index_bridging_disabled.ex",
    "content": "defmodule Realtime.Tenants.Migrations.SubscriptionIndexBridgingDisabled do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    \"\"\"\n    alter table realtime.subscription reset (index_bridging);\n    \"\"\"\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20250523164012_run_subscription_index_bridging_disabled.ex",
    "content": "defmodule Realtime.Tenants.Migrations.RunSubscriptionIndexBridgingDisabled do\n  @moduledoc false\n  use Ecto.Migration\n\n  def change do\n    execute(\"\"\"\n    alter table realtime.subscription reset (index_bridging);\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20250714121412_broadcast_send_error_logging.ex",
    "content": "defmodule Realtime.Tenants.Migrations.BroadcastSendErrorLogging do\n  @moduledoc false\n  use Ecto.Migration\n  # Removes pg_notification to use postgres logging instead\n  def change do\n    execute(\"\"\"\n    CREATE OR REPLACE FUNCTION realtime.send(payload jsonb, event text, topic text, private boolean DEFAULT true ) RETURNS void\n    AS $$\n    BEGIN\n      BEGIN\n        -- Set the topic configuration\n        EXECUTE format('SET LOCAL realtime.topic TO %L', topic);\n\n        -- Attempt to insert the message\n        INSERT INTO realtime.messages (payload, event, topic, private, extension)\n        VALUES (payload, event, topic, private, 'broadcast');\n      EXCEPTION\n        WHEN OTHERS THEN\n          -- Capture and notify the error\n          RAISE WARNING 'ErrorSendingBroadcastMessage: %', SQLERRM;\n      END;\n    END;\n    $$\n    LANGUAGE plpgsql;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20250905041441_create_messages_replay_index.ex",
    "content": "defmodule Realtime.Tenants.Migrations.CreateMessagesReplayIndex do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def change do\n    create_if_not_exists index(:messages, [{:desc, :inserted_at}, :topic],\n                           where: \"extension = 'broadcast' and private IS TRUE\"\n                         )\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20251103001201_broadcast_send_include_payload_id.ex",
    "content": "defmodule Realtime.Tenants.Migrations.BroadcastSendIncludePayloadId do\n  @moduledoc false\n  use Ecto.Migration\n\n  # Include ID in the payload if not defined\n  def change do\n    execute(\"\"\"\n    CREATE OR REPLACE FUNCTION realtime.send(payload jsonb, event text, topic text, private boolean DEFAULT true ) RETURNS void\n    AS $$\n    DECLARE\n      generated_id uuid;\n      final_payload jsonb;\n    BEGIN\n      BEGIN\n        -- Generate a new UUID for the id\n        generated_id := gen_random_uuid();\n\n        -- Check if payload has an 'id' key, if not, add the generated UUID\n        IF payload ? 'id' THEN\n          final_payload := payload;\n        ELSE\n          final_payload := jsonb_set(payload, '{id}', to_jsonb(generated_id));\n        END IF;\n\n        -- Set the topic configuration\n        EXECUTE format('SET LOCAL realtime.topic TO %L', topic);\n\n        -- Attempt to insert the message\n        INSERT INTO realtime.messages (id, payload, event, topic, private, extension)\n        VALUES (generated_id, final_payload, event, topic, private, 'broadcast');\n      EXCEPTION\n        WHEN OTHERS THEN\n          -- Capture and notify the error\n          RAISE WARNING 'ErrorSendingBroadcastMessage: %', SQLERRM;\n      END;\n    END;\n    $$\n    LANGUAGE plpgsql;\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20251120212548_add_action_to_subscriptions.ex",
    "content": "defmodule Realtime.Tenants.Migrations.AddActionToSubscriptions do\n  @moduledoc false\n  use Ecto.Migration\n\n  def up do\n    execute(\"\"\"\n    ALTER TABLE realtime.subscription\n    ADD COLUMN action_filter text DEFAULT '*' CHECK (action_filter IN ('*', 'INSERT', 'UPDATE', 'DELETE'));\n    \"\"\")\n\n    execute(\"\"\"\n    CREATE UNIQUE INDEX subscription_subscription_id_entity_filters_action_filter_key on realtime.subscription (subscription_id, entity, filters, action_filter);\n    \"\"\")\n\n    execute(\"\"\"\n    DROP INDEX IF EXISTS \"realtime\".\"subscription_subscription_id_entity_filters_key\";\n    \"\"\")\n  end\n\n  def down do\n    execute(\"\"\"\n    ALTER TABLE realtime.subscription DROP COLUMN action_filter;\n    \"\"\")\n\n    execute(\"\"\"\n    CREATE UNIQUE INDEX subscription_subscription_id_entity_filters_key on realtime.subscription (subscription_id, entity, filters)\n    \"\"\")\n\n    execute(\"\"\"\n    DROP INDEX IF EXISTS \"realtime\".\"subscription_subscription_id_entity_filters_action_filter_key\";\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20251120215549_filter_action_postgres_changes.ex",
    "content": "defmodule Realtime.Tenants.Migrations.FilterActionPostgresChanges do\n  @moduledoc false\n  use Ecto.Migration\n\n  def up do\n    execute \"\"\"\n    create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n    returns setof realtime.wal_rls\n    language plpgsql\n    volatile\n    as $$\n    declare\n    -- Regclass of the table e.g. public.notes\n    entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n    -- I, U, D, T: insert, update ...\n    action realtime.action = (\n        case wal ->> 'action'\n            when 'I' then 'INSERT'\n            when 'U' then 'UPDATE'\n            when 'D' then 'DELETE'\n            else 'ERROR'\n        end\n    );\n\n    -- Is row level security enabled for the table\n    is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n    subscriptions realtime.subscription[] = array_agg(subs)\n        from\n            realtime.subscription subs\n        where\n            subs.entity = entity_\n            -- Filter by action early - only get subscriptions interested in this action\n            -- action_filter column can be: '*' (all), 'INSERT', 'UPDATE', or 'DELETE'\n            and (subs.action_filter = '*' or subs.action_filter = action::text);\n\n    -- Subscription vars\n    roles regrole[] = array_agg(distinct us.claims_role::text)\n        from\n            unnest(subscriptions) us;\n\n    working_role regrole;\n    claimed_role regrole;\n    claims jsonb;\n\n    subscription_id uuid;\n    subscription_has_access bool;\n    visible_to_subscription_ids uuid[] = '{}';\n\n    -- structured info for wal's columns\n    columns realtime.wal_column[];\n    -- previous identity values for update/delete\n    old_columns realtime.wal_column[];\n\n    error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n    -- Primary jsonb output for record\n    output jsonb;\n\n    begin\n    perform set_config('role', null, true);\n\n    columns =\n        array_agg(\n            (\n                x->>'name',\n                x->>'type',\n                x->>'typeoid',\n                realtime.cast(\n                    (x->'value') #>> '{}',\n                    coalesce(\n                        (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                        (x->>'type')::regtype\n                    )\n                ),\n                (pks ->> 'name') is not null,\n                true\n            )::realtime.wal_column\n        )\n        from\n            jsonb_array_elements(wal -> 'columns') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n                on (x ->> 'name') = (pks ->> 'name');\n\n    old_columns =\n        array_agg(\n            (\n                x->>'name',\n                x->>'type',\n                x->>'typeoid',\n                realtime.cast(\n                    (x->'value') #>> '{}',\n                    coalesce(\n                        (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                        (x->>'type')::regtype\n                    )\n                ),\n                (pks ->> 'name') is not null,\n                true\n            )::realtime.wal_column\n        )\n        from\n            jsonb_array_elements(wal -> 'identity') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n                on (x ->> 'name') = (pks ->> 'name');\n\n    for working_role in select * from unnest(roles) loop\n\n        -- Update `is_selectable` for columns and old_columns\n        columns =\n            array_agg(\n                (\n                    c.name,\n                    c.type_name,\n                    c.type_oid,\n                    c.value,\n                    c.is_pkey,\n                    pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                )::realtime.wal_column\n            )\n            from\n                unnest(columns) c;\n\n        old_columns =\n                array_agg(\n                    (\n                        c.name,\n                        c.type_name,\n                        c.type_oid,\n                        c.value,\n                        c.is_pkey,\n                        pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                    )::realtime.wal_column\n                )\n                from\n                    unnest(old_columns) c;\n\n        if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n            return next (\n                jsonb_build_object(\n                    'schema', wal ->> 'schema',\n                    'table', wal ->> 'table',\n                    'type', action\n                ),\n                is_rls_enabled,\n                -- subscriptions is already filtered by entity\n                (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                array['Error 400: Bad Request, no primary key']\n            )::realtime.wal_rls;\n\n        -- The claims role does not have SELECT permission to the primary key of entity\n        elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n            return next (\n                jsonb_build_object(\n                    'schema', wal ->> 'schema',\n                    'table', wal ->> 'table',\n                    'type', action\n                ),\n                is_rls_enabled,\n                (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                array['Error 401: Unauthorized']\n            )::realtime.wal_rls;\n\n        else\n            output = jsonb_build_object(\n                'schema', wal ->> 'schema',\n                'table', wal ->> 'table',\n                'type', action,\n                'commit_timestamp', to_char(\n                    ((wal ->> 'timestamp')::timestamptz at time zone 'utc'),\n                    'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'\n                ),\n                'columns', (\n                    select\n                        jsonb_agg(\n                            jsonb_build_object(\n                                'name', pa.attname,\n                                'type', pt.typname\n                            )\n                            order by pa.attnum asc\n                        )\n                    from\n                        pg_attribute pa\n                        join pg_type pt\n                            on pa.atttypid = pt.oid\n                    where\n                        attrelid = entity_\n                        and attnum > 0\n                        and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n                )\n            )\n            -- Add \"record\" key for insert and update\n            || case\n                when action in ('INSERT', 'UPDATE') then\n                    jsonb_build_object(\n                        'record',\n                        (\n                            select\n                                jsonb_object_agg(\n                                    -- if unchanged toast, get column name and value from old record\n                                    coalesce((c).name, (oc).name),\n                                    case\n                                        when (c).name is null then (oc).value\n                                        else (c).value\n                                    end\n                                )\n                            from\n                                unnest(columns) c\n                                full outer join unnest(old_columns) oc\n                                    on (c).name = (oc).name\n                            where\n                                coalesce((c).is_selectable, (oc).is_selectable)\n                                and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                        )\n                    )\n                else '{}'::jsonb\n            end\n            -- Add \"old_record\" key for update and delete\n            || case\n                when action = 'UPDATE' then\n                    jsonb_build_object(\n                            'old_record',\n                            (\n                                select jsonb_object_agg((c).name, (c).value)\n                                from unnest(old_columns) c\n                                where\n                                    (c).is_selectable\n                                    and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                            )\n                        )\n                when action = 'DELETE' then\n                    jsonb_build_object(\n                        'old_record',\n                        (\n                            select jsonb_object_agg((c).name, (c).value)\n                            from unnest(old_columns) c\n                            where\n                                (c).is_selectable\n                                and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                                and ( not is_rls_enabled or (c).is_pkey ) -- if RLS enabled, we can't secure deletes so filter to pkey\n                        )\n                    )\n                else '{}'::jsonb\n            end;\n\n            -- Create the prepared statement\n            if is_rls_enabled and action <> 'DELETE' then\n                if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                    deallocate walrus_rls_stmt;\n                end if;\n                execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n            end if;\n\n            visible_to_subscription_ids = '{}';\n\n            for subscription_id, claims in (\n                    select\n                        subs.subscription_id,\n                        subs.claims\n                    from\n                        unnest(subscriptions) subs\n                    where\n                        subs.entity = entity_\n                        and subs.claims_role = working_role\n                        and (\n                            realtime.is_visible_through_filters(columns, subs.filters)\n                            or (\n                              action = 'DELETE'\n                              and realtime.is_visible_through_filters(old_columns, subs.filters)\n                            )\n                        )\n            ) loop\n\n                if not is_rls_enabled or action = 'DELETE' then\n                    visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                else\n                    -- Check if RLS allows the role to see the record\n                    perform\n                        -- Trim leading and trailing quotes from working_role because set_config\n                        -- doesn't recognize the role as valid if they are included\n                        set_config('role', trim(both '\"' from working_role::text), true),\n                        set_config('request.jwt.claims', claims::text, true);\n\n                    execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                    if subscription_has_access then\n                        visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                    end if;\n                end if;\n            end loop;\n\n            perform set_config('role', null, true);\n\n            return next (\n                output,\n                is_rls_enabled,\n                visible_to_subscription_ids,\n                case\n                    when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                    else '{}'\n                end\n            )::realtime.wal_rls;\n\n        end if;\n    end loop;\n\n    perform set_config('role', null, true);\n    end;\n    $$;\n    \"\"\"\n  end\n\n  def down do\n    execute \"\"\"\n    create or replace function realtime.apply_rls(wal jsonb, max_record_bytes int = 1024 * 1024)\n    returns setof realtime.wal_rls\n    language plpgsql\n    volatile\n    as $$\n    declare\n    -- Regclass of the table e.g. public.notes\n    entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;\n\n    -- I, U, D, T: insert, update ...\n    action realtime.action = (\n        case wal ->> 'action'\n            when 'I' then 'INSERT'\n            when 'U' then 'UPDATE'\n            when 'D' then 'DELETE'\n            else 'ERROR'\n        end\n    );\n\n    -- Is row level security enabled for the table\n    is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;\n\n    subscriptions realtime.subscription[] = array_agg(subs)\n        from\n            realtime.subscription subs\n        where\n            subs.entity = entity_;\n\n    -- Subscription vars\n    roles regrole[] = array_agg(distinct us.claims_role::text)\n        from\n            unnest(subscriptions) us;\n\n    working_role regrole;\n    claimed_role regrole;\n    claims jsonb;\n\n    subscription_id uuid;\n    subscription_has_access bool;\n    visible_to_subscription_ids uuid[] = '{}';\n\n    -- structured info for wal's columns\n    columns realtime.wal_column[];\n    -- previous identity values for update/delete\n    old_columns realtime.wal_column[];\n\n    error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;\n\n    -- Primary jsonb output for record\n    output jsonb;\n\n    begin\n    perform set_config('role', null, true);\n\n    columns =\n        array_agg(\n            (\n                x->>'name',\n                x->>'type',\n                x->>'typeoid',\n                realtime.cast(\n                    (x->'value') #>> '{}',\n                    coalesce(\n                        (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                        (x->>'type')::regtype\n                    )\n                ),\n                (pks ->> 'name') is not null,\n                true\n            )::realtime.wal_column\n        )\n        from\n            jsonb_array_elements(wal -> 'columns') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n                on (x ->> 'name') = (pks ->> 'name');\n\n    old_columns =\n        array_agg(\n            (\n                x->>'name',\n                x->>'type',\n                x->>'typeoid',\n                realtime.cast(\n                    (x->'value') #>> '{}',\n                    coalesce(\n                        (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4\n                        (x->>'type')::regtype\n                    )\n                ),\n                (pks ->> 'name') is not null,\n                true\n            )::realtime.wal_column\n        )\n        from\n            jsonb_array_elements(wal -> 'identity') x\n            left join jsonb_array_elements(wal -> 'pk') pks\n                on (x ->> 'name') = (pks ->> 'name');\n\n    for working_role in select * from unnest(roles) loop\n\n        -- Update `is_selectable` for columns and old_columns\n        columns =\n            array_agg(\n                (\n                    c.name,\n                    c.type_name,\n                    c.type_oid,\n                    c.value,\n                    c.is_pkey,\n                    pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                )::realtime.wal_column\n            )\n            from\n                unnest(columns) c;\n\n        old_columns =\n                array_agg(\n                    (\n                        c.name,\n                        c.type_name,\n                        c.type_oid,\n                        c.value,\n                        c.is_pkey,\n                        pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')\n                    )::realtime.wal_column\n                )\n                from\n                    unnest(old_columns) c;\n\n        if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then\n            return next (\n                jsonb_build_object(\n                    'schema', wal ->> 'schema',\n                    'table', wal ->> 'table',\n                    'type', action\n                ),\n                is_rls_enabled,\n                -- subscriptions is already filtered by entity\n                (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                array['Error 400: Bad Request, no primary key']\n            )::realtime.wal_rls;\n\n        -- The claims role does not have SELECT permission to the primary key of entity\n        elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then\n            return next (\n                jsonb_build_object(\n                    'schema', wal ->> 'schema',\n                    'table', wal ->> 'table',\n                    'type', action\n                ),\n                is_rls_enabled,\n                (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),\n                array['Error 401: Unauthorized']\n            )::realtime.wal_rls;\n\n        else\n            output = jsonb_build_object(\n                'schema', wal ->> 'schema',\n                'table', wal ->> 'table',\n                'type', action,\n                'commit_timestamp', to_char(\n                    ((wal ->> 'timestamp')::timestamptz at time zone 'utc'),\n                    'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'\n                ),\n                'columns', (\n                    select\n                        jsonb_agg(\n                            jsonb_build_object(\n                                'name', pa.attname,\n                                'type', pt.typname\n                            )\n                            order by pa.attnum asc\n                        )\n                    from\n                        pg_attribute pa\n                        join pg_type pt\n                            on pa.atttypid = pt.oid\n                    where\n                        attrelid = entity_\n                        and attnum > 0\n                        and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')\n                )\n            )\n            -- Add \"record\" key for insert and update\n            || case\n                when action in ('INSERT', 'UPDATE') then\n                    jsonb_build_object(\n                        'record',\n                        (\n                            select\n                                jsonb_object_agg(\n                                    -- if unchanged toast, get column name and value from old record\n                                    coalesce((c).name, (oc).name),\n                                    case\n                                        when (c).name is null then (oc).value\n                                        else (c).value\n                                    end\n                                )\n                            from\n                                unnest(columns) c\n                                full outer join unnest(old_columns) oc\n                                    on (c).name = (oc).name\n                            where\n                                coalesce((c).is_selectable, (oc).is_selectable)\n                                and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                        )\n                    )\n                else '{}'::jsonb\n            end\n            -- Add \"old_record\" key for update and delete\n            || case\n                when action = 'UPDATE' then\n                    jsonb_build_object(\n                            'old_record',\n                            (\n                                select jsonb_object_agg((c).name, (c).value)\n                                from unnest(old_columns) c\n                                where\n                                    (c).is_selectable\n                                    and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                            )\n                        )\n                when action = 'DELETE' then\n                    jsonb_build_object(\n                        'old_record',\n                        (\n                            select jsonb_object_agg((c).name, (c).value)\n                            from unnest(old_columns) c\n                            where\n                                (c).is_selectable\n                                and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))\n                                and ( not is_rls_enabled or (c).is_pkey ) -- if RLS enabled, we can't secure deletes so filter to pkey\n                        )\n                    )\n                else '{}'::jsonb\n            end;\n\n            -- Create the prepared statement\n            if is_rls_enabled and action <> 'DELETE' then\n                if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then\n                    deallocate walrus_rls_stmt;\n                end if;\n                execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);\n            end if;\n\n            visible_to_subscription_ids = '{}';\n\n            for subscription_id, claims in (\n                    select\n                        subs.subscription_id,\n                        subs.claims\n                    from\n                        unnest(subscriptions) subs\n                    where\n                        subs.entity = entity_\n                        and subs.claims_role = working_role\n                        and (\n                            realtime.is_visible_through_filters(columns, subs.filters)\n                            or (\n                              action = 'DELETE'\n                              and realtime.is_visible_through_filters(old_columns, subs.filters)\n                            )\n                        )\n            ) loop\n\n                if not is_rls_enabled or action = 'DELETE' then\n                    visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                else\n                    -- Check if RLS allows the role to see the record\n                    perform\n                        -- Trim leading and trailing quotes from working_role because set_config\n                        -- doesn't recognize the role as valid if they are included\n                        set_config('role', trim(both '\"' from working_role::text), true),\n                        set_config('request.jwt.claims', claims::text, true);\n\n                    execute 'execute walrus_rls_stmt' into subscription_has_access;\n\n                    if subscription_has_access then\n                        visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;\n                    end if;\n                end if;\n            end loop;\n\n            perform set_config('role', null, true);\n\n            return next (\n                output,\n                is_rls_enabled,\n                visible_to_subscription_ids,\n                case\n                    when error_record_exceeds_max_size then array['Error 413: Payload Too Large']\n                    else '{}'\n                end\n            )::realtime.wal_rls;\n\n        end if;\n    end loop;\n\n    perform set_config('role', null, true);\n    end;\n    $$;\n    \"\"\"\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo/migrations/20260218120000_fix_bytea_double_encoding_in_cast.ex",
    "content": "defmodule Realtime.Tenants.Migrations.FixByteaDoubleEncodingInCast do\n  @moduledoc false\n\n  use Ecto.Migration\n\n  def up do\n    execute \"\"\"\n    create or replace function realtime.cast(val text, type_ regtype)\n      returns jsonb\n      immutable\n      language plpgsql\n    as $$\n    declare\n      res jsonb;\n    begin\n      if type_::text = 'bytea' then\n        return to_jsonb(val);\n      end if;\n      execute format('select to_jsonb(%L::'|| type_::text || ')', val) into res;\n      return res;\n    end\n    $$;\n    \"\"\"\n  end\n\n  def down do\n    execute \"\"\"\n    create or replace function realtime.cast(val text, type_ regtype)\n      returns jsonb\n      immutable\n      language plpgsql\n    as $$\n    declare\n      res jsonb;\n    begin\n      execute format('select to_jsonb(%L::'|| type_::text || ')', val) into res;\n      return res;\n    end\n    $$;\n    \"\"\"\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants/repo.ex",
    "content": "defmodule Realtime.Tenants.Repo do\n  @moduledoc \"\"\"\n  Database operations done against the tenant database\n  \"\"\"\n  use Realtime.Logs\n  import Ecto.Query\n  alias Realtime.Repo.Replica\n\n  @doc \"\"\"\n  Lists all records for a given query and converts them into a given struct\n  \"\"\"\n  @spec all(DBConnection.conn(), Ecto.Queryable.t(), module(), [Postgrex.execute_option()]) ::\n          {:ok, list(struct())} | {:error, any()}\n  def all(conn, query, result_struct, opts \\\\ []) do\n    conn\n    |> run_all_query(query, opts)\n    |> result_to_structs(result_struct)\n  end\n\n  @doc \"\"\"\n  Fetches one record for a given query and converts it into a given struct\n  \"\"\"\n  @spec one(\n          DBConnection.conn(),\n          Ecto.Query.t(),\n          module(),\n          Postgrex.option() | Keyword.t()\n        ) ::\n          {:error, any()} | {:ok, struct()} | Ecto.Changeset.t()\n  def one(conn, query, result_struct, opts \\\\ []) do\n    conn\n    |> run_all_query(query, opts)\n    |> result_to_single_struct(result_struct, nil)\n  end\n\n  @doc \"\"\"\n  Inserts a given changeset into the database and converts the result into a given struct\n  \"\"\"\n  @spec insert(\n          DBConnection.conn(),\n          Ecto.Changeset.t(),\n          module(),\n          Postgrex.option() | Keyword.t()\n        ) ::\n          {:ok, struct()} | {:error, any()} | Ecto.Changeset.t()\n  def insert(conn, changeset, result_struct, opts \\\\ []) do\n    with {:ok, {query, args}} <- insert_query_from_changeset(changeset) do\n      conn\n      |> run_query_with_trap(query, args, opts)\n      |> result_to_single_struct(result_struct, changeset)\n    end\n  end\n\n  @doc \"\"\"\n  Inserts all changesets into the database and converts the result into a given list of structs\n  \"\"\"\n  @spec insert_all_entries(\n          DBConnection.conn(),\n          [Ecto.Changeset.t()],\n          module(),\n          Postgrex.option() | Keyword.t()\n        ) ::\n          {:ok, [struct()]} | {:error, any()} | Ecto.Changeset.t()\n  def insert_all_entries(conn, changesets, result_struct, opts \\\\ []) do\n    with {:ok, {query, args}} <- insert_all_query_from_changeset(changesets) do\n      conn\n      |> run_query_with_trap(query, args, opts)\n      |> result_to_structs(result_struct)\n    end\n  end\n\n  @doc \"\"\"\n  Deletes records for a given query and returns the number of deleted records\n  \"\"\"\n  @spec del(DBConnection.conn(), Ecto.Queryable.t()) ::\n          {:ok, non_neg_integer()} | {:error, any()}\n  def del(conn, query) do\n    with {:ok, %Postgrex.Result{num_rows: num_rows}} <- run_delete_query(conn, query) do\n      {:ok, num_rows}\n    end\n  end\n\n  @doc \"\"\"\n  Updates an entry based on the changeset and returns the updated entry\n  \"\"\"\n  @spec update(DBConnection.conn(), Ecto.Changeset.t(), module()) ::\n          {:ok, struct()} | {:error, any()} | Ecto.Changeset.t()\n  def update(conn, changeset, result_struct, opts \\\\ []) do\n    with {:ok, {query, args}} <- update_query_from_changeset(changeset) do\n      conn\n      |> run_query_with_trap(query, args, opts)\n      |> result_to_single_struct(result_struct, changeset)\n    end\n  end\n\n  defp result_to_single_struct(\n         {:error, %Postgrex.Error{postgres: %{code: :unique_violation, constraint: \"channels_name_index\"}}},\n         _struct,\n         changeset\n       ) do\n    Ecto.Changeset.add_error(changeset, :name, \"has already been taken\")\n  end\n\n  defp result_to_single_struct({:error, _} = error, _, _), do: error\n\n  defp result_to_single_struct({:ok, %Postgrex.Result{rows: []}}, _, _) do\n    {:error, :not_found}\n  end\n\n  defp result_to_single_struct({:ok, %Postgrex.Result{rows: [row], columns: columns}}, struct, _) do\n    repo_module = Replica.replica()\n    {:ok, repo_module.load(struct, Enum.zip(columns, row))}\n  end\n\n  defp result_to_single_struct({:ok, %Postgrex.Result{num_rows: num_rows}}, _, _) do\n    raise(\"expected at most one result but got #{num_rows} in result\")\n  end\n\n  defp result_to_structs({:error, _} = error, _), do: error\n\n  defp result_to_structs({:ok, %Postgrex.Result{rows: rows, columns: columns}}, struct) do\n    repo_module = Replica.replica()\n    {:ok, Enum.map(rows, &repo_module.load(struct, Enum.zip(columns, &1)))}\n  end\n\n  defp insert_query_from_changeset(%{valid?: false} = changeset), do: {:error, changeset}\n\n  defp insert_query_from_changeset(changeset) do\n    schema = changeset.data.__struct__\n    source = schema.__schema__(:source)\n    prefix = schema.__schema__(:prefix)\n    acc = %{header: [], rows: []}\n\n    %{header: header, rows: rows} =\n      Enum.reduce(changeset.changes, acc, fn {field, row}, %{header: header, rows: rows} ->\n        row =\n          case row do\n            row when is_boolean(row) -> row\n            row when is_atom(row) -> Atom.to_string(row)\n            _ -> row\n          end\n\n        %{\n          header: [Atom.to_string(field) | header],\n          rows: [row | rows]\n        }\n      end)\n\n    table = \"\\\"#{prefix}\\\".\\\"#{source}\\\"\"\n    header = \"(#{Enum.map_join(header, \",\", &\"\\\"#{&1}\\\"\")})\"\n\n    arg_index =\n      rows\n      |> Enum.with_index(1)\n      |> Enum.map_join(\",\", fn {_, index} -> \"$#{index}\" end)\n\n    {:ok, {\"INSERT INTO #{table} #{header} VALUES (#{arg_index}) RETURNING *\", rows}}\n  end\n\n  defp insert_all_query_from_changeset(changesets) do\n    invalid = Enum.filter(changesets, &(!&1.valid?))\n\n    if invalid != [] do\n      {:error, changesets}\n    else\n      [schema] = changesets |> Enum.map(& &1.data.__struct__) |> Enum.uniq()\n\n      source = schema.__schema__(:source)\n      prefix = schema.__schema__(:prefix)\n      changes = Enum.map(changesets, & &1.changes)\n\n      %{header: header, rows: rows} =\n        Enum.reduce(changes, %{header: [], rows: []}, fn v, changes_acc ->\n          Enum.reduce(v, changes_acc, fn {field, row}, %{header: header, rows: rows} ->\n            row =\n              case row do\n                row when is_boolean(row) -> row\n                row when is_atom(row) -> Atom.to_string(row)\n                _ -> row\n              end\n\n            %{\n              header: Enum.uniq([Atom.to_string(field) | header]),\n              rows: [row | rows]\n            }\n          end)\n        end)\n\n      args_index =\n        rows\n        |> Enum.chunk_every(length(header))\n        |> Enum.reduce({\"\", 1}, fn row, {acc, count} ->\n          arg_index =\n            row\n            |> Enum.with_index(count)\n            |> Enum.map_join(\"\", fn {_, index} -> \"$#{index},\" end)\n            |> String.trim_trailing(\",\")\n            |> then(&\"(#{&1})\")\n\n          {\"#{acc},#{arg_index}\", count + length(row)}\n        end)\n        |> elem(0)\n        |> String.trim_leading(\",\")\n\n      table = \"\\\"#{prefix}\\\".\\\"#{source}\\\"\"\n      header = \"(#{Enum.map_join(header, \",\", &\"\\\"#{&1}\\\"\")})\"\n      {:ok, {\"INSERT INTO #{table} #{header} VALUES #{args_index} RETURNING *\", rows}}\n    end\n  end\n\n  defp update_query_from_changeset(%{valid?: false} = changeset), do: {:error, changeset}\n\n  defp update_query_from_changeset(changeset) do\n    repo_module = Replica.replica()\n    %Ecto.Changeset{data: %{id: id, __struct__: struct}, changes: changes} = changeset\n    changes = Keyword.new(changes)\n    query = from(c in struct, where: c.id == ^id, select: c, update: [set: ^changes])\n    {:ok, repo_module.to_sql(:update_all, query)}\n  end\n\n  defp run_all_query(conn, query, opts) do\n    repo_module = Replica.replica()\n    {query, args} = repo_module.to_sql(:all, query)\n    run_query_with_trap(conn, query, args, opts)\n  end\n\n  defp run_delete_query(conn, query) do\n    repo_module = Replica.replica()\n    {query, args} = repo_module.to_sql(:delete_all, query)\n    run_query_with_trap(conn, query, args)\n  end\n\n  defp run_query_with_trap(conn, query, args, opts \\\\ []) do\n    Postgrex.query(conn, query, args, opts)\n  rescue\n    e ->\n      log_error(\"ErrorRunningQuery\", e)\n      {:error, :postgrex_exception}\n  catch\n    :exit, {:noproc, {DBConnection.Holder, :checkout, _}} ->\n      log_error(\n        \"UnableCheckoutConnection\",\n        \"Unable to checkout connection, please check your connection pool configuration\"\n      )\n\n      {:error, :postgrex_exception}\n\n    :exit, reason ->\n      log_error(\"UnknownError\", reason)\n\n      {:error, :postgrex_exception}\n  end\nend\n"
  },
  {
    "path": "lib/realtime/tenants.ex",
    "content": "defmodule Realtime.Tenants do\n  @moduledoc \"\"\"\n  Everything to do with Tenants.\n  \"\"\"\n\n  require Logger\n\n  alias Realtime.Api\n  alias Realtime.Api.Tenant\n  alias Realtime.Database\n  alias Realtime.RateCounter\n  alias Realtime.Repo.Replica\n  alias Realtime.Tenants.Cache\n  alias Realtime.Tenants.Connect\n  alias Realtime.Tenants.Migrations\n  alias Realtime.UsersCounter\n\n  @doc \"\"\"\n  Gets the database connection pid managed by the Tenants.Connect process.\n\n  ## Examples\n\n      iex> Realtime.Tenants.get_health_conn(%Realtime.Api.Tenant{external_id: \"not_found_tenant\"})\n      {:error, :tenant_database_connection_initializing}\n  \"\"\"\n  @spec get_health_conn(Tenant.t()) :: {:error, term()} | {:ok, pid()}\n  def get_health_conn(%Tenant{external_id: external_id}) do\n    Connect.get_status(external_id)\n  end\n\n  @doc \"\"\"\n  Checks if a tenant is healthy. A tenant is healthy if:\n  - Tenant has no db connection and zero client connections\n  - Tenant has a db connection and >0 client connections\n\n  A tenant is not healthy if a tenant has client connections and no database connection.\n\n  The response includes `replication_connected` to indicate if the replication connection\n  for broadcast changes is active. This is informational and does not affect the healthy status.\n  \"\"\"\n\n  @spec health_check(binary) ::\n          {:error,\n           :tenant_not_found\n           | String.t()\n           | %{\n               connected_cluster: pos_integer,\n               db_connected: boolean,\n               replication_connected: boolean,\n               healthy: false,\n               region: String.t(),\n               node: String.t()\n             }}\n          | {:ok,\n             %{\n               connected_cluster: non_neg_integer,\n               db_connected: boolean,\n               replication_connected: boolean,\n               healthy: true,\n               region: String.t(),\n               node: String.t()\n             }}\n  def health_check(external_id) when is_binary(external_id) do\n    region = Application.get_env(:realtime, :region)\n    node = Node.self() |> to_string()\n\n    with %Tenant{} = tenant <- Cache.get_tenant_by_external_id(external_id),\n         {:error, _} <- get_health_conn(tenant),\n         connected_cluster when connected_cluster > 0 <- UsersCounter.tenant_users(external_id) do\n      {:error,\n       %{\n         healthy: false,\n         db_connected: false,\n         replication_connected: false,\n         connected_cluster: connected_cluster,\n         region: region,\n         node: node\n       }}\n    else\n      nil ->\n        {:error, :tenant_not_found}\n\n      {:ok, _health_conn} ->\n        connected_cluster = UsersCounter.tenant_users(external_id)\n        replication_connected = replication_connected?(external_id)\n\n        {:ok,\n         %{\n           healthy: true,\n           db_connected: true,\n           replication_connected: replication_connected,\n           connected_cluster: connected_cluster,\n           region: region,\n           node: node\n         }}\n\n      connected_cluster when is_integer(connected_cluster) ->\n        tenant = Cache.get_tenant_by_external_id(external_id)\n        result? = Migrations.run_migrations(tenant)\n\n        {:ok,\n         %{\n           healthy: result? == :ok || result? == :noop,\n           db_connected: false,\n           replication_connected: false,\n           connected_cluster: connected_cluster,\n           region: region,\n           node: node\n         }}\n    end\n  end\n\n  defp replication_connected?(external_id) do\n    case Connect.replication_status(external_id) do\n      {:ok, _pid} -> true\n      {:error, :not_connected} -> false\n    end\n  end\n\n  @doc \"\"\"\n  All the keys that we use to create counters and RateLimiters for tenants.\n  \"\"\"\n  @spec limiter_keys(Tenant.t()) :: [{atom(), atom(), String.t()}]\n  def limiter_keys(%Tenant{} = tenant) do\n    [\n      requests_per_second_key(tenant),\n      channels_per_client_key(tenant),\n      joins_per_second_key(tenant),\n      events_per_second_key(tenant),\n      db_events_per_second_key(tenant),\n      presence_events_per_second_key(tenant)\n    ]\n  end\n\n  @spec requests_per_second_rate(Tenant.t()) :: RateCounter.Args.t()\n  def requests_per_second_rate(%Tenant{} = tenant) do\n    %RateCounter.Args{id: requests_per_second_key(tenant), opts: []}\n  end\n\n  @doc \"The GenCounter key to use for counting requests through Plug.\"\n  @spec requests_per_second_key(Tenant.t() | String.t()) :: {:plug, :requests, String.t()}\n  def requests_per_second_key(%Tenant{} = tenant) do\n    {:plug, :requests, tenant.external_id}\n  end\n\n  @doc \"RateCounter arguments for counting joins per second.\"\n  @spec joins_per_second_rate(Tenant.t()) :: RateCounter.Args.t()\n  def joins_per_second_rate(%Tenant{} = tenant),\n    do: joins_per_second_rate(tenant.external_id, tenant.max_joins_per_second)\n\n  @spec joins_per_second_rate(String.t(), non_neg_integer) :: RateCounter.Args.t()\n  def joins_per_second_rate(tenant_id, max_joins_per_second) when is_binary(tenant_id) do\n    opts = [\n      telemetry: %{\n        event_name: [:channel, :joins],\n        measurements: %{limit: max_joins_per_second},\n        metadata: %{tenant: tenant_id}\n      },\n      limit: [\n        value: max_joins_per_second,\n        measurement: :avg,\n        log_fn: fn ->\n          Logger.critical(\"ClientJoinRateLimitReached: Too many joins per second\",\n            external_id: tenant_id,\n            project: tenant_id\n          )\n        end\n      ]\n    ]\n\n    %RateCounter.Args{id: joins_per_second_key(tenant_id), opts: opts}\n  end\n\n  @doc \"The GenCounter key to use for counting RealtimeChannel joins.\"\n  @spec joins_per_second_key(Tenant.t() | String.t()) :: {:channel, :joins, String.t()}\n  def joins_per_second_key(tenant) when is_binary(tenant) do\n    {:channel, :joins, tenant}\n  end\n\n  def joins_per_second_key(%Tenant{} = tenant) do\n    {:channel, :joins, tenant.external_id}\n  end\n\n  @doc \"The Register key to use to limit the amount of channels connected to the websocket.\"\n  @spec channels_per_client_key(Tenant.t() | String.t()) :: {:channel, :clients_per, String.t()}\n  def channels_per_client_key(tenant) when is_binary(tenant) do\n    {:channel, :clients_per, tenant}\n  end\n\n  def channels_per_client_key(%Tenant{} = tenant) do\n    {:channel, :clients_per, tenant.external_id}\n  end\n\n  @doc \"RateCounter arguments for counting events per second.\"\n  @spec events_per_second_rate(Tenant.t()) :: RateCounter.Args.t()\n  def events_per_second_rate(tenant), do: events_per_second_rate(tenant.external_id, tenant.max_events_per_second)\n\n  def events_per_second_rate(tenant_id, max_events_per_second) do\n    opts = [\n      telemetry: %{\n        event_name: [:channel, :events],\n        measurements: %{limit: max_events_per_second},\n        metadata: %{tenant: tenant_id}\n      },\n      limit: [\n        value: max_events_per_second,\n        measurement: :avg,\n        log: true,\n        log_fn: fn ->\n          Logger.error(\"MessagePerSecondRateLimitReached: Too many messages per second\",\n            external_id: tenant_id,\n            project: tenant_id\n          )\n        end\n      ]\n    ]\n\n    %RateCounter.Args{id: events_per_second_key(tenant_id), opts: opts}\n  end\n\n  @doc \"\"\"\n  The GenCounter key to use when counting events for RealtimeChannel events.\n  ## Examples\n    iex> Realtime.Tenants.events_per_second_key(\"tenant_id\")\n    {:channel, :events, \"tenant_id\"}\n    iex> Realtime.Tenants.events_per_second_key(%Realtime.Api.Tenant{external_id: \"tenant_id\"})\n    {:channel, :events, \"tenant_id\"}\n  \"\"\"\n  @spec events_per_second_key(Tenant.t() | String.t()) :: {:channel, :events, String.t()}\n  def events_per_second_key(tenant) when is_binary(tenant) do\n    {:channel, :events, tenant}\n  end\n\n  def events_per_second_key(%Tenant{} = tenant) do\n    {:channel, :events, tenant.external_id}\n  end\n\n  @doc \"RateCounter arguments for counting database events per second.\"\n  @spec db_events_per_second_rate(Tenant.t()) :: RateCounter.Args.t()\n  def db_events_per_second_rate(%Tenant{} = tenant),\n    do: db_events_per_second_rate(tenant.external_id, tenant.max_events_per_second)\n\n  @doc \"RateCounter arguments for counting database events per second with a limit.\"\n  @spec db_events_per_second_rate(String.t(), non_neg_integer) :: RateCounter.Args.t()\n  def db_events_per_second_rate(tenant_id, max_events_per_second) when is_binary(tenant_id) do\n    opts = [\n      telemetry: %{\n        event_name: [:channel, :db_events],\n        measurements: %{},\n        metadata: %{tenant: tenant_id}\n      },\n      limit: [\n        value: max_events_per_second,\n        measurement: :avg,\n        log: true,\n        log_fn: fn ->\n          Logger.error(\"MessagePerSecondRateLimitReached: Too many postgres changes messages per second\",\n            external_id: tenant_id,\n            project: tenant_id\n          )\n        end\n      ]\n    ]\n\n    %RateCounter.Args{id: db_events_per_second_key(tenant_id), opts: opts}\n  end\n\n  @doc \"\"\"\n  The GenCounter key to use when counting events for RealtimeChannel events.\n    iex> Realtime.Tenants.db_events_per_second_key(\"tenant_id\")\n    {:channel, :db_events, \"tenant_id\"}\n    iex> Realtime.Tenants.db_events_per_second_key(%Realtime.Api.Tenant{external_id: \"tenant_id\"})\n    {:channel, :db_events, \"tenant_id\"}\n  \"\"\"\n  @spec db_events_per_second_key(Tenant.t() | String.t()) :: {:channel, :db_events, String.t()}\n  def db_events_per_second_key(tenant) when is_binary(tenant) do\n    {:channel, :db_events, tenant}\n  end\n\n  def db_events_per_second_key(%Tenant{} = tenant) do\n    {:channel, :db_events, tenant.external_id}\n  end\n\n  @doc \"RateCounter arguments for counting presence events per second.\"\n  @spec presence_events_per_second_rate(Tenant.t()) :: RateCounter.Args.t()\n  def presence_events_per_second_rate(tenant) do\n    presence_events_per_second_rate(tenant.external_id, tenant.max_presence_events_per_second)\n  end\n\n  @spec presence_events_per_second_rate(String.t(), non_neg_integer) :: RateCounter.Args.t()\n  def presence_events_per_second_rate(tenant_id, max_presence_events_per_second) do\n    opts = [\n      telemetry: %{\n        event_name: [:channel, :presence_events],\n        measurements: %{limit: max_presence_events_per_second},\n        metadata: %{tenant: tenant_id}\n      },\n      limit: [\n        value: max_presence_events_per_second,\n        measurement: :avg,\n        log_fn: fn ->\n          Logger.error(\"PresenceRateLimitReached: Too many presence events per second\",\n            external_id: tenant_id,\n            project: tenant_id\n          )\n        end\n      ]\n    ]\n\n    %RateCounter.Args{id: presence_events_per_second_key(tenant_id), opts: opts}\n  end\n\n  @doc \"\"\"\n  The GenCounter key to use when counting presence events for RealtimeChannel events.\n  ## Examples\n    iex> Realtime.Tenants.presence_events_per_second_key(\"tenant_id\")\n    {:channel, :presence_events, \"tenant_id\"}\n    iex> Realtime.Tenants.presence_events_per_second_key(%Realtime.Api.Tenant{external_id: \"tenant_id\"})\n    {:channel, :presence_events, \"tenant_id\"}\n  \"\"\"\n  @spec presence_events_per_second_key(Tenant.t() | String.t()) :: {:channel, :presence_events, String.t()}\n  def presence_events_per_second_key(tenant) when is_binary(tenant) do\n    {:channel, :presence_events, tenant}\n  end\n\n  def presence_events_per_second_key(%Tenant{} = tenant) do\n    {:channel, :presence_events, tenant.external_id}\n  end\n\n  @spec authorization_errors_per_second_rate(Tenant.t()) :: RateCounter.Args.t()\n  def authorization_errors_per_second_rate(%Tenant{external_id: external_id} = tenant) do\n    opts = [\n      max_bucket_len: 30,\n      limit: [\n        value: authorization_pool_size(tenant),\n        measurement: :sum,\n        log_fn: fn ->\n          Logger.critical(\"IncreaseConnectionPool: Too many database timeouts\",\n            external_id: external_id,\n            project: external_id\n          )\n        end\n      ]\n    ]\n\n    %RateCounter.Args{id: authorization_errors_per_second_key(external_id), opts: opts}\n  end\n\n  def authorization_errors_per_second_key(tenant_id), do: {:channel, :authorization_errors, tenant_id}\n\n  @spec subscription_errors_per_second_rate(String.t(), non_neg_integer) :: RateCounter.Args.t()\n  def subscription_errors_per_second_rate(tenant_id, pool_size) do\n    opts = [\n      max_bucket_len: 30,\n      limit: [\n        value: pool_size,\n        measurement: :sum,\n        log_fn: fn ->\n          Logger.error(\"IncreaseSubscriptionConnectionPool: Too many database timeouts\",\n            external_id: tenant_id,\n            project: tenant_id\n          )\n        end\n      ]\n    ]\n\n    %RateCounter.Args{id: subscription_errors_per_second_key(tenant_id), opts: opts}\n  end\n\n  def subscription_errors_per_second_key(tenant_id), do: {:channel, :subscription_errors, tenant_id}\n\n  @connect_errors_limit 3\n  @connect_errors_tick 200\n  @connect_errors_bucket_len 25\n  @doc \"RateCounter arguments for counting connect errors. Uses a 200ms tick with a 25-bucket window (5s) and triggers after 3 errors.\"\n  @spec connect_errors_per_second_rate(Tenant.t() | String.t()) :: RateCounter.Args.t()\n  def connect_errors_per_second_rate(%Tenant{external_id: external_id}) do\n    connect_errors_per_second_rate(external_id)\n  end\n\n  def connect_errors_per_second_rate(tenant_id) do\n    opts = [\n      tick: @connect_errors_tick,\n      max_bucket_len: @connect_errors_bucket_len,\n      limit: [\n        value: @connect_errors_limit,\n        measurement: :sum,\n        log_fn: fn ->\n          Logger.critical(\n            \"DatabaseConnectionRateLimitReached: Too many connection attempts against the tenant database\",\n            external_id: tenant_id,\n            project: tenant_id\n          )\n        end\n      ]\n    ]\n\n    %RateCounter.Args{id: connect_errors_per_second_key(tenant_id), opts: opts}\n  end\n\n  def connect_errors_per_second_key(tenant_id), do: {:database, :connect, tenant_id}\n\n  defp authorization_pool_size(%{extensions: [%{settings: settings} | _]}) do\n    Database.pool_size_by_application_name(\"realtime_connect\", settings)\n  end\n\n  defp authorization_pool_size(_), do: 1\n\n  @spec get_tenant_limits(Realtime.Api.Tenant.t(), maybe_improper_list) :: list\n  def get_tenant_limits(%Tenant{} = tenant, keys) when is_list(keys) do\n    nodes = [Node.self() | Node.list()]\n\n    nodes\n    |> Enum.map(fn node ->\n      Task.Supervisor.async({Realtime.TaskSupervisor, node}, fn ->\n        for key <- keys do\n          response = Realtime.GenCounter.get(key)\n\n          %{\n            external_id: tenant.external_id,\n            node: node,\n            limiter: key,\n            counter: response\n          }\n        end\n      end)\n    end)\n    |> Task.await_many()\n    |> List.flatten()\n  end\n\n  @spec get_tenant_by_external_id(String.t()) :: Tenant.t() | nil\n  def get_tenant_by_external_id(external_id) do\n    repo_replica = Replica.replica()\n\n    Tenant\n    |> repo_replica.get_by(external_id: external_id)\n    |> repo_replica.preload(:extensions)\n  end\n\n  @doc \"\"\"\n  Builds a PubSub topic from a tenant and a sub-topic.\n  ## Examples\n\n      iex> Realtime.Tenants.tenant_topic(%Realtime.Api.Tenant{external_id: \"tenant_id\"}, \"sub_topic\")\n      \"tenant_id:sub_topic\"\n      iex> Realtime.Tenants.tenant_topic(\"tenant_id\", \"sub_topic\")\n      \"tenant_id:sub_topic\"\n      iex> Realtime.Tenants.tenant_topic(%Realtime.Api.Tenant{external_id: \"tenant_id\"}, \"sub_topic\", false)\n      \"tenant_id-private:sub_topic\"\n      iex> Realtime.Tenants.tenant_topic(\"tenant_id\", \"sub_topic\", false)\n      \"tenant_id-private:sub_topic\"\n      iex> Realtime.Tenants.tenant_topic(\"tenant_id\", \":sub_topic\", false)\n      \"tenant_id-private::sub_topic\"\n  \"\"\"\n  @spec tenant_topic(Tenant.t() | binary(), String.t(), boolean()) :: String.t()\n  def tenant_topic(external_id, sub_topic, public? \\\\ true)\n\n  def tenant_topic(%Tenant{external_id: external_id}, sub_topic, public?),\n    do: tenant_topic(external_id, sub_topic, public?)\n\n  def tenant_topic(external_id, sub_topic, false),\n    do: \"#{external_id}-private:#{sub_topic}\"\n\n  def tenant_topic(external_id, sub_topic, true),\n    do: \"#{external_id}:#{sub_topic}\"\n\n  @doc \"\"\"\n  Sets tenant as suspended. New connections won't be accepted\n  \"\"\"\n  @spec suspend_tenant_by_external_id(String.t()) :: {:ok, Tenant.t()} | {:error, term()}\n  def suspend_tenant_by_external_id(external_id) do\n    external_id\n    |> Api.update_tenant_by_external_id(%{suspend: true})\n    |> tap(fn _ -> broadcast_operation_event(:suspend_tenant, external_id) end)\n  end\n\n  @doc \"\"\"\n  Sets tenant as unsuspended. New connections will be accepted\n  \"\"\"\n  @spec unsuspend_tenant_by_external_id(String.t()) :: {:ok, Tenant.t()} | {:error, term()}\n  def unsuspend_tenant_by_external_id(external_id) do\n    external_id\n    |> Api.update_tenant_by_external_id(%{suspend: false})\n    |> tap(fn _ -> broadcast_operation_event(:unsuspend_tenant, external_id) end)\n  end\n\n  @doc \"\"\"\n  Checks if migrations for a given tenant need to run.\n  \"\"\"\n  @spec run_migrations?(Tenant.t() | integer()) :: boolean()\n  def run_migrations?(%Tenant{} = tenant), do: run_migrations?(tenant.migrations_ran)\n\n  def run_migrations?(migrations_ran) when is_integer(migrations_ran),\n    do: migrations_ran < Enum.count(Migrations.migrations())\n\n  @doc \"\"\"\n  Broadcasts an operation event to the tenant's operations channel.\n  \"\"\"\n  @spec broadcast_operation_event(:suspend_tenant | :unsuspend_tenant | :disconnect, String.t()) :: :ok\n  def broadcast_operation_event(action, external_id),\n    do: Phoenix.PubSub.broadcast!(Realtime.PubSub, \"realtime:operations:\" <> external_id, action)\n\n  @doc \"\"\"\n  Returns the region of the tenant based on its extensions.\n  If the region is not set, it returns nil.\n  \"\"\"\n  @spec region(Tenant.t()) :: String.t() | nil\n  def region(%Tenant{extensions: [%{settings: settings}]}), do: Map.get(settings, \"region\")\n  def region(_), do: nil\n\n  @doc \"\"\"\n  \"\"\"\n  @spec validate_payload_size(Tenant.t() | binary(), map()) :: :ok | {:error, :payload_size_exceeded}\n  def validate_payload_size(tenant_id, payload) when is_binary(tenant_id) do\n    tenant_id\n    |> Cache.get_tenant_by_external_id()\n    |> validate_payload_size(payload)\n  end\n\n  @payload_size_padding 500\n  def validate_payload_size(%Tenant{max_payload_size_in_kb: max_payload_size_in_kb}, payload) do\n    max_payload_size = max_payload_size_in_kb * 1000 + @payload_size_padding\n    payload_size = :erlang.external_size(payload)\n    if payload_size > max_payload_size, do: {:error, :payload_size_exceeded}, else: :ok\n  end\nend\n"
  },
  {
    "path": "lib/realtime/users_counter.ex",
    "content": "defmodule Realtime.UsersCounter do\n  @moduledoc \"\"\"\n  Counts of connected clients for a tenant across the whole cluster or for a single node.\n  \"\"\"\n\n  @doc \"\"\"\n  Adds a RealtimeChannel pid to the `:users` scope for a tenant so we can keep track of all connected clients for a tenant.\n  \"\"\"\n  @spec add(pid(), String.t()) :: :ok\n  def add(pid, tenant_id) when is_pid(pid) and is_binary(tenant_id) do\n    :ok = Beacon.join(:users, tenant_id, pid)\n  end\n\n  @doc \"Return true if pid is already counted for tenant_id\"\n  @spec already_counted?(pid(), String.t()) :: boolean()\n  def already_counted?(pid, tenant_id), do: Beacon.local_member?(:users, tenant_id, pid)\n\n  @doc \"List all local tenants with connected clients on this node.\"\n  @spec local_tenants() :: [String.t()]\n  def local_tenants(), do: Beacon.local_groups(:users)\n\n  @doc \"\"\"\n  Returns the count of all connected clients for a tenant for the cluster.\n  \"\"\"\n  @spec tenant_users(String.t()) :: non_neg_integer()\n  def tenant_users(tenant_id), do: Beacon.member_count(:users, tenant_id)\n\n  @doc \"\"\"\n  Returns the counts of all connected clients for all tenants for the cluster.\n  \"\"\"\n  @spec tenant_counts() :: %{String.t() => non_neg_integer()}\n  def tenant_counts(), do: Beacon.member_counts(:users)\n\n  @doc \"\"\"\n  Returns the counts of all connected clients for all tenants for the local node.\n  \"\"\"\n  @spec local_tenant_counts() :: %{String.t() => non_neg_integer()}\n  def local_tenant_counts(), do: Beacon.local_member_counts(:users)\nend\n"
  },
  {
    "path": "lib/realtime.ex",
    "content": "defmodule Realtime do\n  @moduledoc false\nend\n"
  },
  {
    "path": "lib/realtime_web/api_spec.ex",
    "content": "defmodule RealtimeWeb.ApiSpec do\n  @moduledoc false\n\n  alias OpenApiSpex.Components\n  alias OpenApiSpex.Info\n  alias OpenApiSpex.OpenApi\n  alias OpenApiSpex.Paths\n  alias OpenApiSpex.SecurityScheme\n  alias OpenApiSpex.Server\n  alias OpenApiSpex.ServerVariable\n\n  alias RealtimeWeb.Router\n\n  @behaviour OpenApi\n\n  @impl OpenApi\n  def spec do\n    url =\n      case Mix.env() do\n        :prod -> \"https://{tenant}.supabase.co/realtime/v1\"\n        _ -> \"http://{tenant}.localhost:4000/\"\n      end\n\n    %OpenApi{\n      servers: [\n        %Server{\n          url: url,\n          variables: %{\"tenant\" => %ServerVariable{default: \"tenant\"}}\n        }\n      ],\n      info: %Info{\n        title: to_string(Application.spec(:realtime, :description)),\n        version: to_string(Application.spec(:realtime, :vsn))\n      },\n      paths: Paths.from_router(Router),\n      components: %Components{\n        securitySchemes: %{\"authorization\" => %SecurityScheme{type: \"http\", scheme: \"bearer\"}}\n      }\n    }\n    |> OpenApiSpex.resolve_schema_modules()\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/auth/channels_authorization.ex",
    "content": "defmodule RealtimeWeb.ChannelsAuthorization do\n  @moduledoc \"\"\"\n  Check connection is authorized to access channel\n  \"\"\"\n  require Logger\n\n  @doc \"\"\"\n  Authorize connection to access channel\n  \"\"\"\n  @spec authorize(binary(), binary(), binary() | nil) ::\n          {:ok, map()} | {:error, any()} | {:error, :expired_token, String.t()}\n  def authorize(token, jwt_secret, jwt_jwks) when is_binary(token) do\n    token\n    |> clean_token()\n    |> RealtimeWeb.JwtVerification.verify(jwt_secret, jwt_jwks)\n  end\n\n  def authorize(_token, _jwt_secret, _jwt_jwks), do: {:error, :invalid_token}\n\n  def authorize_conn(token, jwt_secret, jwt_jwks) do\n    case authorize(token, jwt_secret, jwt_jwks) do\n      {:ok, claims} ->\n        required = [\"role\", \"exp\"]\n        claims_keys = Map.keys(claims)\n\n        if Enum.all?(required, &(&1 in claims_keys)),\n          do: {:ok, claims},\n          else: {:error, :missing_claims}\n\n      {:error, [message: validation_timer, claim: \"exp\", claim_val: claim_val]} when is_integer(validation_timer) ->\n        msg = \"Token has expired #{validation_timer - claim_val} seconds ago\"\n        {:error, :expired_token, msg}\n\n      {:error, reason} ->\n        {:error, reason}\n    end\n  end\n\n  defp clean_token(token), do: Regex.replace(~r/\\s|\\n/, URI.decode(token), \"\")\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/auth/jwt_verification.ex",
    "content": "defmodule RealtimeWeb.JwtVerification do\n  @moduledoc \"\"\"\n  Parse JWT and verify claims\n  \"\"\"\n  # Matching error in Dialyzer when using Joken.peek_claims/1 but {:ok, []} is actually possible and covered by our testing\n  @dialyzer {:nowarn_function, check_claims_format: 1}\n\n  defmodule JwtAuthToken do\n    @moduledoc false\n    use Joken.Config\n\n    @impl true\n    def token_config do\n      Application.fetch_env!(:realtime, :jwt_claim_validators)\n      |> Enum.reduce(%{}, fn {claim_key, expected_val}, claims ->\n        add_claim_validator(claims, claim_key, expected_val)\n      end)\n      |> add_claim_validator(\"exp\")\n    end\n\n    defp add_claim_validator(claims, \"exp\") do\n      current_time = current_time()\n      add_claim(claims, \"exp\", nil, &(&1 > current_time), message: current_time)\n    end\n\n    defp add_claim_validator(claims, claim_key, expected_val) do\n      add_claim(claims, claim_key, nil, &(&1 == expected_val))\n    end\n  end\n\n  @hs_algorithms [\"HS256\", \"HS384\", \"HS512\"]\n  @rs_algorithms [\"RS256\", \"RS384\", \"RS512\"]\n  @es_algorithms [\"ES256\", \"ES384\", \"ES512\"]\n  @ed_algorithms [\"Ed25519\", \"Ed448\"]\n\n  @doc \"\"\"\n  Verify JWT token and validate claims\n  \"\"\"\n  @spec verify(binary(), binary(), binary() | nil) :: {:ok, map()} | {:error, any()}\n  def verify(token, jwt_secret, jwt_jwks) when is_binary(token) do\n    with {:ok, _claims} <- check_claims_format(token),\n         {:ok, header} <- check_header_format(token),\n         {:ok, signer} <- generate_signer(header, jwt_secret, jwt_jwks) do\n      JwtAuthToken.verify_and_validate(token, signer)\n    else\n      {:error, _e} = error -> error\n    end\n  end\n\n  def verify(_token, _jwt_secret, _jwt_jwks), do: {:error, :not_a_string}\n\n  defp check_claims_format(token) do\n    case Joken.peek_claims(token) do\n      {:ok, claims} when is_map(claims) -> {:ok, claims}\n      {:ok, _} -> {:error, :expected_claims_map}\n      {:error, :token_malformed} -> {:error, :token_malformed}\n    end\n  end\n\n  defp check_header_format(token) do\n    case Joken.peek_header(token) do\n      {:ok, header} when is_map(header) -> {:ok, header}\n      _error -> {:error, :expected_header_map}\n    end\n  end\n\n  defp generate_signer(%{\"alg\" => alg, \"kid\" => kid}, _jwt_secret, %{\n         \"keys\" => keys\n       })\n       when is_binary(kid) and alg in @rs_algorithms do\n    jwk = Enum.find(keys, fn jwk -> jwk[\"kty\"] == \"RSA\" and jwk[\"kid\"] == kid end)\n\n    case jwk do\n      nil -> {:error, :error_generating_signer}\n      _ -> {:ok, Joken.Signer.create(alg, jwk)}\n    end\n  end\n\n  defp generate_signer(%{\"alg\" => alg, \"kid\" => kid}, _jwt_secret, %{\"keys\" => keys})\n       when is_binary(kid) and alg in @es_algorithms do\n    jwk = Enum.find(keys, fn jwk -> jwk[\"kty\"] == \"EC\" and jwk[\"kid\"] == kid end)\n\n    case jwk do\n      nil -> {:error, :error_generating_signer}\n      _ -> {:ok, Joken.Signer.create(alg, jwk)}\n    end\n  end\n\n  defp generate_signer(%{\"alg\" => alg, \"kid\" => kid}, _jwt_secret, %{\"keys\" => keys})\n       when is_binary(kid) and alg in @ed_algorithms do\n    jwk = Enum.find(keys, fn jwk -> jwk[\"kty\"] == \"OKP\" and jwk[\"kid\"] == kid end)\n\n    case jwk do\n      nil -> {:error, :error_generating_signer}\n      _ -> {:ok, Joken.Signer.create(alg, jwk)}\n    end\n  end\n\n  # Most Supabase Auth JWTs fall in this case, as they're usually signed with\n  # HS256, have a kid header, but there's no JWK as this is sensitive. In this\n  # case, the jwt_secret should be used.\n  defp generate_signer(%{\"alg\" => alg, \"kid\" => kid}, jwt_secret, %{\n         \"keys\" => keys\n       })\n       when is_binary(kid) and alg in @hs_algorithms do\n    jwk = Enum.find(keys, fn jwk -> jwk[\"kty\"] == \"oct\" and jwk[\"kid\"] == kid and is_binary(jwk[\"k\"]) end)\n\n    if jwk do\n      case Base.url_decode64(jwk[\"k\"], padding: false) do\n        {:ok, secret} -> {:ok, Joken.Signer.create(alg, secret)}\n        _ -> {:error, :error_generating_signer}\n      end\n    else\n      # If there's no JWK, and HS* is being used, instead of erroring, try\n      # the jwt_secret instead.\n      {:ok, Joken.Signer.create(alg, jwt_secret)}\n    end\n  end\n\n  defp generate_signer(%{\"alg\" => alg}, jwt_secret, _jwt_jwks) when alg in @hs_algorithms do\n    {:ok, Joken.Signer.create(alg, jwt_secret)}\n  end\n\n  defp generate_signer(_header, _jwt_secret, _jwt_jwks), do: {:error, :error_generating_signer}\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/payloads/broadcast/replay.ex",
    "content": "defmodule RealtimeWeb.Channels.Payloads.Broadcast.Replay do\n  @moduledoc \"\"\"\n  Validate broadcast replay field of the join payload.\n  \"\"\"\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias RealtimeWeb.Channels.Payloads.Join\n\n  embedded_schema do\n    field :limit, :integer, default: 10\n    field :since, :integer, default: 0\n  end\n\n  def changeset(broadcast, attrs) do\n    cast(broadcast, attrs, [:limit, :since], message: &Join.error_message/2)\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/payloads/broadcast.ex",
    "content": "defmodule RealtimeWeb.Channels.Payloads.Broadcast do\n  @moduledoc \"\"\"\n  Validate broadcast field of the join payload.\n  \"\"\"\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias RealtimeWeb.Channels.Payloads.Join\n  alias RealtimeWeb.Channels.Payloads.FlexibleBoolean\n\n  embedded_schema do\n    field :ack, FlexibleBoolean, default: false\n    field :self, FlexibleBoolean, default: false\n    embeds_one :replay, RealtimeWeb.Channels.Payloads.Broadcast.Replay\n  end\n\n  def changeset(broadcast, attrs) do\n    broadcast\n    |> cast(attrs, [:ack, :self], message: &Join.error_message/2)\n    |> cast_embed(:replay, invalid_message: \"unable to parse, expected a map\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/payloads/config.ex",
    "content": "defmodule RealtimeWeb.Channels.Payloads.Config do\n  @moduledoc \"\"\"\n  Validate config field of the join payload.\n  \"\"\"\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias RealtimeWeb.Channels.Payloads.Join\n  alias RealtimeWeb.Channels.Payloads.Broadcast\n  alias RealtimeWeb.Channels.Payloads.Presence\n  alias RealtimeWeb.Channels.Payloads.PostgresChange\n  alias RealtimeWeb.Channels.Payloads.FlexibleBoolean\n\n  embedded_schema do\n    embeds_one :broadcast, Broadcast\n    embeds_one :presence, Presence\n    embeds_many :postgres_changes, PostgresChange\n    field :private, FlexibleBoolean, default: false\n  end\n\n  def changeset(config, attrs) do\n    attrs =\n      attrs\n      |> Enum.map(fn\n        {k, v} when is_list(v) -> {k, Enum.filter(v, fn v -> v != nil end)}\n        {\"postgres_changes\", nil} -> {\"postgres_changes\", []}\n        {k, v} -> {k, v}\n      end)\n      |> Map.new()\n\n    config\n    |> cast(attrs, [:private], message: &Join.error_message/2)\n    |> cast_embed(:broadcast, invalid_message: \"unable to parse, expected a map\")\n    |> cast_embed(:presence, invalid_message: \"unable to parse, expected a map\")\n    |> cast_embed(:postgres_changes, invalid_message: \"unable to parse, expected an array of maps\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/payloads/flexible_boolean.ex",
    "content": "defmodule RealtimeWeb.Channels.Payloads.FlexibleBoolean do\n  @moduledoc \"\"\"\n  Custom Ecto type that handles boolean values coming as strings.\n\n  Accepts:\n  - Boolean values (true/false) - used as-is\n  - Strings \"true\", \"True\", \"TRUE\", etc. - cast to true\n  - Strings \"false\", \"False\", \"FALSE\", etc. - cast to false\n  - Any other value - returns error\n  \"\"\"\n  use Ecto.Type\n\n  @impl true\n  def type, do: :boolean\n\n  @impl true\n  def cast(value) when is_boolean(value), do: {:ok, value}\n\n  def cast(value) when is_binary(value) do\n    case String.downcase(value) do\n      \"true\" -> {:ok, true}\n      \"false\" -> {:ok, false}\n      _ -> :error\n    end\n  end\n\n  def cast(_), do: :error\n\n  @impl true\n  def load(value), do: {:ok, value}\n\n  @impl true\n  def dump(value) when is_boolean(value), do: {:ok, value}\n  def dump(_), do: :error\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/payloads/join.ex",
    "content": "defmodule RealtimeWeb.Channels.Payloads.Join do\n  @moduledoc \"\"\"\n  Payload validation for the phx_join event.\n  \"\"\"\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias RealtimeWeb.Channels.Payloads.Config\n  alias RealtimeWeb.Channels.Payloads.Broadcast\n  alias RealtimeWeb.Channels.Payloads.Presence\n\n  embedded_schema do\n    embeds_one :config, Config\n    field :access_token, :string\n    field :user_token, :string\n  end\n\n  def changeset(join, attrs) do\n    join\n    |> cast(attrs, [:access_token, :user_token], message: &error_message/2)\n    |> cast_embed(:config, invalid_message: \"unable to parse, expected a map\")\n  end\n\n  @spec validate(map()) :: {:ok, %__MODULE__{}} | {:error, :invalid_join_payload, map()}\n  def validate(params) do\n    case changeset(%__MODULE__{}, params) do\n      %Ecto.Changeset{valid?: true} = changeset ->\n        {:ok, Ecto.Changeset.apply_changes(changeset)}\n\n      %Ecto.Changeset{valid?: false} = changeset ->\n        errors = Ecto.Changeset.traverse_errors(changeset, &elem(&1, 0))\n        {:error, :invalid_join_payload, errors}\n    end\n  end\n\n  def presence_enabled?(%__MODULE__{config: %Config{presence: %Presence{enabled: enabled}}}), do: enabled\n  def presence_enabled?(_), do: true\n\n  def presence_key(%__MODULE__{config: %Config{presence: %Presence{key: \"\"}}}), do: UUID.uuid1()\n  def presence_key(%__MODULE__{config: %Config{presence: %Presence{key: key}}}), do: key\n  def presence_key(_), do: UUID.uuid1()\n\n  def ack_broadcast?(%__MODULE__{config: %Config{broadcast: %Broadcast{ack: ack}}}), do: ack\n  def ack_broadcast?(_), do: false\n\n  def self_broadcast?(%__MODULE__{config: %Config{broadcast: %Broadcast{self: self}}}), do: self\n  def self_broadcast?(_), do: false\n\n  def private?(%__MODULE__{config: %Config{private: private}}), do: private\n  def private?(_), do: false\n\n  def error_message(_field, meta) do\n    type = Keyword.get(meta, :type)\n\n    if type,\n      do: \"unable to parse, expected #{format_type(type)}\",\n      else: \"unable to parse\"\n  end\n\n  defp format_type(RealtimeWeb.Channels.Payloads.FlexibleBoolean), do: :boolean\n  defp format_type(type), do: type\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/payloads/postgres_change.ex",
    "content": "defmodule RealtimeWeb.Channels.Payloads.PostgresChange do\n  @moduledoc \"\"\"\n  Validate postgres_changes field of the join payload.\n  \"\"\"\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias RealtimeWeb.Channels.Payloads.Join\n\n  embedded_schema do\n    field :event, :string\n    field :schema, :string\n    field :table, :string\n    field :filter, :string\n  end\n\n  def changeset(postgres_change, attrs) do\n    cast(postgres_change, attrs, [:event, :schema, :table, :filter], message: &Join.error_message/2)\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/payloads/presence.ex",
    "content": "defmodule RealtimeWeb.Channels.Payloads.Presence do\n  @moduledoc \"\"\"\n  Validate presence field of the join payload.\n  \"\"\"\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias RealtimeWeb.Channels.Payloads.Join\n  alias RealtimeWeb.Channels.Payloads.FlexibleBoolean\n\n  embedded_schema do\n    field :enabled, FlexibleBoolean, default: true\n    field :key, :any, default: UUID.uuid1(), virtual: true\n  end\n\n  def changeset(presence, attrs) do\n    cast(presence, attrs, [:enabled, :key], message: &Join.error_message/2)\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/presence.ex",
    "content": "defmodule RealtimeWeb.Presence do\n  @moduledoc \"\"\"\n  Provides presence tracking to channels and processes.\n\n  See the [`Phoenix.Presence`](http://hexdocs.pm/phoenix/Phoenix.Presence.html)\n  docs for more details.\n  \"\"\"\n  use Phoenix.Presence,\n    otp_app: :realtime,\n    pubsub_server: Realtime.PubSub,\n    dispatcher: RealtimeWeb.RealtimeChannel.MessageDispatcher\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/realtime_channel/assign.ex",
    "content": "defmodule RealtimeWeb.RealtimeChannel.Assigns do\n  @moduledoc \"\"\"\n  Assigns for RealtimeChannel\n  \"\"\"\n\n  defstruct [\n    :tenant,\n    :log_level,\n    :rate_counter,\n    :limits,\n    :tenant_topic,\n    :pg_sub_ref,\n    :pg_change_params,\n    :postgres_extension,\n    :claims,\n    :jwt_secret,\n    :jwt_jwks,\n    :tenant_token,\n    :access_token,\n    :postgres_cdc_module,\n    :channel_name,\n    :headers\n  ]\n\n  @type t :: %__MODULE__{\n          tenant: String.t(),\n          log_level: Logger.level(),\n          rate_counter: Realtime.RateCounter.t(),\n          limits: %{\n            max_events_per_second: integer(),\n            max_concurrent_users: integer(),\n            max_bytes_per_second: integer(),\n            max_channels_per_client: integer(),\n            max_joins_per_second: integer()\n          },\n          tenant_topic: String.t(),\n          pg_sub_ref: reference() | nil,\n          pg_change_params: map(),\n          postgres_extension: map(),\n          claims: map(),\n          jwt_secret: String.t(),\n          jwt_jwks: map(),\n          tenant_token: String.t(),\n          access_token: String.t(),\n          channel_name: String.t()\n        }\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/realtime_channel/broadcast_handler.ex",
    "content": "defmodule RealtimeWeb.RealtimeChannel.BroadcastHandler do\n  @moduledoc \"\"\"\n  Handles the Broadcast feature from Realtime\n  \"\"\"\n  use Realtime.Logs\n\n  import Phoenix.Socket, only: [assign: 3]\n\n  alias Realtime.Tenants\n  alias RealtimeWeb.RealtimeChannel\n  alias RealtimeWeb.TenantBroadcaster\n  alias Phoenix.Socket\n  alias Realtime.GenCounter\n  alias Realtime.Tenants.Authorization\n  alias Realtime.Tenants.Authorization.Policies\n  alias Realtime.Tenants.Authorization.Policies.BroadcastPolicies\n\n  @type payload :: map | {String.t(), :json | :binary, binary}\n\n  @event_type \"broadcast\"\n  @spec handle(payload, Socket.t()) :: {:reply, :ok, Socket.t()} | {:noreply, Socket.t()}\n  def handle(payload, %{assigns: %{private?: false}} = socket), do: handle(payload, nil, socket)\n\n  @spec handle(payload, pid() | nil, Socket.t()) :: {:reply, :ok, Socket.t()} | {:noreply, Socket.t()}\n  def handle(payload, db_conn, %{assigns: %{private?: true}} = socket) do\n    %{\n      assigns: %{\n        self_broadcast: self_broadcast,\n        tenant_topic: tenant_topic,\n        authorization_context: authorization_context,\n        policies: policies,\n        tenant: tenant_id\n      }\n    } = socket\n\n    case run_authorization_check(policies || %Policies{}, db_conn, authorization_context) do\n      {:ok, %Policies{broadcast: %BroadcastPolicies{write: true}} = policies} ->\n        socket =\n          socket\n          |> assign(:policies, policies)\n          |> increment_rate_counter()\n\n        %{ack_broadcast: ack_broadcast} = socket.assigns\n\n        res =\n          case Tenants.validate_payload_size(tenant_id, payload) do\n            :ok -> send_message(tenant_id, self_broadcast, tenant_topic, payload)\n            {:error, error} -> {:error, error}\n          end\n\n        cond do\n          ack_broadcast && match?({:error, :payload_size_exceeded}, res) ->\n            {:reply, {:error, :payload_size_exceeded}, socket}\n\n          ack_broadcast ->\n            {:reply, :ok, socket}\n\n          true ->\n            {:noreply, socket}\n        end\n\n      {:ok, policies} ->\n        {:noreply, assign(socket, :policies, policies)}\n\n      {:error, :rls_policy_error, error} ->\n        log_error(\"RlsPolicyError\", error)\n        {:noreply, socket}\n\n      {:error, error} ->\n        log_error(\"UnableToSetPolicies\", error)\n        {:noreply, socket}\n    end\n  end\n\n  def handle(payload, _db_conn, %{assigns: %{private?: false}} = socket) do\n    %{\n      assigns: %{\n        tenant_topic: tenant_topic,\n        self_broadcast: self_broadcast,\n        ack_broadcast: ack_broadcast,\n        tenant: tenant_id\n      }\n    } = socket\n\n    socket = increment_rate_counter(socket)\n\n    res =\n      case Tenants.validate_payload_size(tenant_id, payload) do\n        :ok -> send_message(tenant_id, self_broadcast, tenant_topic, payload)\n        {:error, error} -> {:error, error}\n      end\n\n    cond do\n      ack_broadcast && match?({:error, :payload_size_exceeded}, res) ->\n        {:reply, {:error, :payload_size_exceeded}, socket}\n\n      ack_broadcast ->\n        {:reply, :ok, socket}\n\n      true ->\n        {:noreply, socket}\n    end\n  end\n\n  defp send_message(tenant_id, self_broadcast, tenant_topic, payload) do\n    broadcast = build_broadcast(tenant_topic, payload)\n\n    if self_broadcast do\n      TenantBroadcaster.pubsub_broadcast(\n        tenant_id,\n        tenant_topic,\n        broadcast,\n        RealtimeChannel.MessageDispatcher,\n        :broadcast\n      )\n    else\n      TenantBroadcaster.pubsub_broadcast_from(\n        tenant_id,\n        self(),\n        tenant_topic,\n        broadcast,\n        RealtimeChannel.MessageDispatcher,\n        :broadcast\n      )\n    end\n  end\n\n  # No idea why Dialyzer is complaining here\n  @dialyzer {:nowarn_function, build_broadcast: 2}\n\n  # Message payload was built by V2 Serializer which was originally UserBroadcastPush\n  # We are not using the metadata for anything just yet.\n  defp build_broadcast(topic, {user_event, user_payload_encoding, user_payload, _metadata}) do\n    %RealtimeWeb.Socket.UserBroadcast{\n      topic: topic,\n      user_event: user_event,\n      user_payload_encoding: user_payload_encoding,\n      user_payload: user_payload\n    }\n  end\n\n  defp build_broadcast(topic, payload) do\n    %Phoenix.Socket.Broadcast{topic: topic, event: @event_type, payload: payload}\n  end\n\n  defp increment_rate_counter(%{assigns: %{policies: %Policies{broadcast: %BroadcastPolicies{write: false}}}} = socket) do\n    socket\n  end\n\n  defp increment_rate_counter(%{assigns: %{rate_counter: counter}} = socket) do\n    GenCounter.add(counter.id)\n    socket\n  end\n\n  defp run_authorization_check(\n         %Policies{broadcast: %BroadcastPolicies{write: nil}} = policies,\n         db_conn,\n         authorization_context\n       ) do\n    Authorization.get_write_authorizations(policies, db_conn, authorization_context)\n  end\n\n  defp run_authorization_check(socket, _db_conn, _authorization_context) do\n    {:ok, socket}\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/realtime_channel/logging.ex",
    "content": "defmodule RealtimeWeb.RealtimeChannel.Logging do\n  @moduledoc \"\"\"\n  Log functions for Realtime channels\n  \"\"\"\n\n  alias Realtime.Telemetry\n  require Logger\n\n  defmacro __using__(_opts) do\n    quote do\n      require Logger\n      import RealtimeWeb.RealtimeChannel.Logging\n    end\n  end\n\n  @doc \"\"\"\n  Logs an error message\n  \"\"\"\n  @spec log_error(socket :: Phoenix.Socket.t(), code :: binary(), msg :: any()) ::\n          {:error, %{reason: binary}}\n  def log_error(socket, code, msg) do\n    msg = build_msg(code, msg)\n    emit_system_error(:error, code)\n    log(socket, :error, code, msg)\n    {:error, %{reason: msg}}\n  end\n\n  @doc \"\"\"\n  Logs a warning message\n  \"\"\"\n  @spec log_warning(socket :: Phoenix.Socket.t(), code :: binary(), msg :: any()) ::\n          {:error, %{reason: binary}}\n  def log_warning(socket, code, msg) do\n    msg = build_msg(code, msg)\n    log(socket, :warning, code, msg)\n    {:error, %{reason: msg}}\n  end\n\n  @doc \"\"\"\n  Logs an error if the log level is set to error\n  \"\"\"\n  @spec maybe_log_error(socket :: Phoenix.Socket.t(), code :: binary(), msg :: any()) :: {:error, %{reason: binary}}\n  def maybe_log_error(socket, code, msg), do: maybe_log(socket, :error, code, msg)\n\n  @doc \"\"\"\n  Logs a warning if the log level is set to warning\n  \"\"\"\n  @spec maybe_log_warning(socket :: Phoenix.Socket.t(), code :: binary(), msg :: any()) :: {:error, %{reason: binary}}\n  def maybe_log_warning(socket, code, msg), do: maybe_log(socket, :warning, code, msg)\n\n  @doc \"\"\"\n  Logs an info if the log level is set to info\n  \"\"\"\n  @spec maybe_log_info(socket :: Phoenix.Socket.t(), msg :: any()) :: :ok\n  def maybe_log_info(socket, msg), do: maybe_log(socket, :info, nil, msg)\n\n  defp build_msg(code, msg) do\n    msg = stringify!(msg)\n    if code, do: \"#{code}: #{msg}\", else: msg\n  end\n\n  defp log(%{assigns: %{tenant: tenant, access_token: access_token}}, level, code, msg) do\n    Logger.metadata(external_id: tenant, project: tenant)\n    if level in [:error, :warning], do: update_metadata_with_token_claims(access_token)\n    Logger.log(level, msg, error_code: code)\n  end\n\n  defp maybe_log(%{assigns: %{log_level: log_level}} = socket, level, code, msg) do\n    msg = build_msg(code, msg)\n    emit_system_error(level, code)\n    if Logger.compare_levels(log_level, level) != :gt, do: log(socket, level, code, msg)\n    if level in [:error, :warning], do: {:error, %{reason: msg}}, else: :ok\n  end\n\n  @system_errors [\n    \"UnableToSetPolicies\",\n    \"InitializingProjectConnection\",\n    \"DatabaseConnectionIssue\",\n    \"UnknownErrorOnChannel\"\n  ]\n\n  def system_errors, do: @system_errors\n\n  defp emit_system_error(:error, code) when code in @system_errors,\n    do: Telemetry.execute([:realtime, :channel, :error], %{code: code}, %{code: code})\n\n  defp emit_system_error(_, _), do: nil\n\n  defp stringify!(msg) when is_binary(msg), do: msg\n  defp stringify!(msg), do: inspect(msg, pretty: true)\n\n  defp update_metadata_with_token_claims(nil), do: nil\n\n  defp update_metadata_with_token_claims(token) do\n    case Joken.peek_claims(token) do\n      {:ok, claims} ->\n        sub = Map.get(claims, \"sub\")\n        exp = Map.get(claims, \"exp\")\n        iss = Map.get(claims, \"iss\")\n        Logger.metadata(sub: sub, exp: exp, iss: iss)\n\n      _ ->\n        nil\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/realtime_channel/message_dispatcher.ex",
    "content": "defmodule RealtimeWeb.RealtimeChannel.MessageDispatcher do\n  @moduledoc \"\"\"\n  Inspired by Phoenix.Channel.Server.dispatch/3\n  \"\"\"\n\n  require Logger\n  alias Phoenix.Socket.Broadcast\n  alias RealtimeWeb.Socket.UserBroadcast\n\n  def fastlane_metadata(fastlane_pid, serializer, topic, log_level, tenant_id, replayed_message_ids \\\\ MapSet.new()) do\n    {:rc_fastlane, fastlane_pid, serializer, topic, log_level, tenant_id, replayed_message_ids}\n  end\n\n  @presence_diff \"presence_diff\"\n\n  @doc \"\"\"\n  This dispatch function caches encoded messages if fastlane is used\n  It also sends  an :update_rate_counter to the subscriber and it can conditionally log\n\n  fastlane_pid is the actual socket transport pid\n  \"\"\"\n  @spec dispatch(list, pid, Broadcast.t() | UserBroadcast.t()) :: :ok\n  def dispatch(subscribers, from, %Broadcast{event: @presence_diff} = msg) do\n    {_cache, count} =\n      Enum.reduce(subscribers, {%{}, 0}, fn\n        {pid, _}, {cache, count} when pid == from ->\n          {cache, count}\n\n        {_pid, {:rc_fastlane, fastlane_pid, serializer, join_topic, log_level, tenant_id, _replayed_message_ids}},\n        {cache, count} ->\n          maybe_log(log_level, join_topic, msg, tenant_id)\n\n          cache = do_dispatch(msg, fastlane_pid, serializer, join_topic, cache, tenant_id, log_level)\n          {cache, count + 1}\n\n        {pid, _}, {cache, count} ->\n          send(pid, msg)\n          {cache, count}\n      end)\n\n    tenant_id = tenant_id(subscribers)\n    increment_presence_counter(tenant_id, msg.event, count)\n\n    :ok\n  end\n\n  def dispatch(subscribers, from, msg) do\n    message_id = message_id(msg)\n\n    _ =\n      Enum.reduce(subscribers, %{}, fn\n        {pid, _}, cache when pid == from ->\n          cache\n\n        {pid, {:rc_fastlane, fastlane_pid, serializer, join_topic, log_level, tenant_id, replayed_message_ids}},\n        cache ->\n          if already_replayed?(message_id, replayed_message_ids) do\n            # skip already replayed message\n            cache\n          else\n            send(pid, :update_rate_counter)\n\n            maybe_log(log_level, join_topic, msg, tenant_id)\n\n            do_dispatch(msg, fastlane_pid, serializer, join_topic, cache, tenant_id, log_level)\n          end\n\n        {pid, _}, cache ->\n          send(pid, msg)\n          cache\n      end)\n\n    :ok\n  end\n\n  defp maybe_log(:info, join_topic, msg, tenant_id) when is_struct(msg) do\n    log = \"Received message on #{join_topic} with payload: #{inspect(msg, pretty: true)}\"\n    Logger.info(log, external_id: tenant_id, project: tenant_id)\n  end\n\n  defp maybe_log(:info, join_topic, msg, tenant_id) when is_binary(msg) do\n    log = \"Received message on #{join_topic}. #{msg}\"\n    Logger.info(log, external_id: tenant_id, project: tenant_id)\n  end\n\n  defp maybe_log(_level, _join_topic, _msg, _tenant_id), do: :ok\n\n  defp do_dispatch(msg, fastlane_pid, serializer, join_topic, cache, tenant_id, log_level) do\n    case cache do\n      %{^serializer => {:ok, encoded_msg}} ->\n        send(fastlane_pid, encoded_msg)\n        cache\n\n      %{^serializer => {:error, _reason}} ->\n        # We do nothing at this stage. It has been already logged depending on the log level\n        cache\n\n      %{} ->\n        # Use the original topic that was joined without the external_id\n        msg = %{msg | topic: join_topic}\n\n        result =\n          case fastlane!(serializer, msg) do\n            {:ok, encoded_msg} ->\n              send(fastlane_pid, encoded_msg)\n              {:ok, encoded_msg}\n\n            {:error, reason} ->\n              maybe_log(log_level, join_topic, reason, tenant_id)\n              {:error, reason}\n          end\n\n        Map.put(cache, serializer, result)\n    end\n  end\n\n  # We have to convert because V1 does not know how to process UserBroadcast\n  defp fastlane!(Phoenix.Socket.V1.JSONSerializer = serializer, %UserBroadcast{} = msg) do\n    with {:ok, msg} <- UserBroadcast.convert_to_json_broadcast(msg) do\n      {:ok, serializer.fastlane!(msg)}\n    end\n  end\n\n  defp fastlane!(serializer, msg), do: {:ok, serializer.fastlane!(msg)}\n\n  defp tenant_id([{_pid, {:rc_fastlane, _, _, _, _, tenant_id, _}} | _]), do: tenant_id\n  defp tenant_id(_), do: nil\n\n  defp increment_presence_counter(tenant_id, \"presence_diff\", count) when is_binary(tenant_id) do\n    tenant_id\n    |> Realtime.Tenants.presence_events_per_second_key()\n    |> Realtime.GenCounter.add(count)\n  end\n\n  defp increment_presence_counter(_tenant_id, _event, _count), do: :ok\n\n  defp message_id(%Broadcast{payload: %{\"meta\" => %{\"id\" => id}}}), do: id\n  defp message_id(_), do: nil\n\n  defp already_replayed?(nil, _replayed_message_ids), do: false\n  defp already_replayed?(message_id, replayed_message_ids), do: MapSet.member?(replayed_message_ids, message_id)\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/realtime_channel/presence_handler.ex",
    "content": "defmodule RealtimeWeb.RealtimeChannel.PresenceHandler do\n  @moduledoc \"\"\"\n  Handles the Presence feature from Realtime\n  \"\"\"\n  use Realtime.Logs\n\n  import Phoenix.Socket, only: [assign: 3]\n  import Phoenix.Channel, only: [push: 3]\n\n  alias Phoenix.Socket\n  alias Phoenix.Tracker.Shard\n  alias Realtime.GenCounter\n  alias Realtime.RateCounter\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Authorization\n  alias RealtimeWeb.Presence\n  alias RealtimeWeb.RealtimeChannel.Logging\n\n  defguard is_private?(socket) when socket.assigns.private?\n\n  defguard can_read_presence?(socket) when is_private?(socket) and socket.assigns.policies.presence.read\n\n  defguard can_write_presence?(socket) when is_private?(socket) and socket.assigns.policies.presence.write\n\n  @doc \"\"\"\n  Sends presence state to connected clients\n  \"\"\"\n  @spec sync(Socket.t()) :: :ok | {:error, :rate_limit_exceeded}\n  def sync(%{assigns: %{presence_enabled?: false}}), do: :ok\n\n  def sync(socket) when not is_private?(socket) do\n    %{assigns: %{tenant_topic: topic}} = socket\n\n    with :ok <- limit_presence_event(socket) do\n      push(socket, \"presence_state\", presence_dirty_list(topic))\n      Logging.maybe_log_info(socket, :sync_presence)\n\n      :ok\n    end\n  end\n\n  def sync(socket) when not can_read_presence?(socket), do: :ok\n\n  def sync(socket) when can_read_presence?(socket) do\n    %{tenant_topic: topic} = socket.assigns\n\n    with :ok <- limit_presence_event(socket) do\n      push(socket, \"presence_state\", presence_dirty_list(topic))\n      Logging.maybe_log_info(socket, :sync_presence)\n\n      :ok\n    end\n  end\n\n  @spec handle(map(), pid() | nil, Socket.t()) ::\n          {:ok, Socket.t()}\n          | {:error,\n             :rls_policy_error\n             | :unable_to_set_policies\n             | :rate_limit_exceeded\n             | :client_rate_limit_exceeded\n             | :unable_to_track_presence\n             | :payload_size_exceeded}\n  def handle(%{\"event\" => event} = payload, db_conn, socket) do\n    event = String.downcase(event, :ascii)\n\n    with {:ok, socket} <- limit_client_presence_event(socket) do\n      handle_presence_event(event, payload, db_conn, socket)\n    else\n      {:error, :client_rate_limit_exceeded} = error -> error\n    end\n  end\n\n  def handle(_, _, socket), do: {:ok, socket}\n\n  defp handle_presence_event(\"track\", payload, _, socket) when not is_private?(socket) do\n    track(socket, payload)\n  end\n\n  defp handle_presence_event(\"track\", payload, db_conn, socket)\n       when is_private?(socket) and is_nil(socket.assigns.policies.presence.write) do\n    %{assigns: %{authorization_context: authorization_context, policies: policies}} = socket\n\n    case Authorization.get_write_authorizations(policies, db_conn, authorization_context) do\n      {:ok, policies} ->\n        socket = assign(socket, :policies, policies)\n        handle_presence_event(\"track\", payload, db_conn, socket)\n\n      {:error, :rls_policy_error, error} ->\n        log_error(\"RlsPolicyError\", error)\n        {:error, :rls_policy_error}\n\n      {:error, error} ->\n        log_error(\"UnableToSetPolicies\", error)\n        {:error, :unable_to_set_policies}\n    end\n  end\n\n  defp handle_presence_event(\"track\", payload, _, socket) when can_write_presence?(socket) do\n    track(socket, payload)\n  end\n\n  defp handle_presence_event(\"track\", _, _, socket) when not can_write_presence?(socket) do\n    {:error, :unauthorized}\n  end\n\n  defp handle_presence_event(\"untrack\", _, _, socket) do\n    %{assigns: %{presence_key: presence_key, tenant_topic: tenant_topic}} = socket\n    :ok = Presence.untrack(self(), tenant_topic, presence_key)\n    {:ok, assign(socket, :presence_track_payload, nil)}\n  end\n\n  defp handle_presence_event(event, _, _, _) do\n    log_error(\"UnknownPresenceEvent\", event)\n    {:error, :unknown_presence_event}\n  end\n\n  defp track(socket, payload) do\n    %{assigns: %{presence_key: presence_key, tenant_topic: tenant_topic}} = socket\n    payload = Map.get(payload, \"payload\", %{})\n\n    with :ok <- check_track_payload(socket.assigns, payload),\n         tenant <- Tenants.Cache.get_tenant_by_external_id(socket.assigns.tenant),\n         :ok <- validate_payload_size(tenant, payload),\n         _ <- RealtimeWeb.TenantBroadcaster.collect_payload_size(socket.assigns.tenant, payload, :presence),\n         :ok <- limit_presence_event(socket),\n         {:ok, _} <- Presence.track(self(), tenant_topic, presence_key, payload) do\n      socket =\n        socket\n        |> assign(:presence_enabled?, true)\n        |> assign(:presence_track_payload, payload)\n\n      {:ok, socket}\n    else\n      {:error, :no_payload_change} ->\n        # no-op if payload hasn't changed\n        {:ok, socket}\n\n      {:error, {:already_tracked, pid, _, _}} ->\n        case Presence.update(pid, tenant_topic, presence_key, payload) do\n          {:ok, _} ->\n            socket = assign(socket, :presence_track_payload, payload)\n            {:ok, socket}\n\n          {:error, _} ->\n            {:error, :unable_to_track_presence}\n        end\n\n      {:error, :rate_limit_exceeded} ->\n        {:error, :rate_limit_exceeded}\n\n      {:error, :payload_size_exceeded} ->\n        {:error, :payload_size_exceeded}\n\n      {:error, error} ->\n        log_error(\"UnableToTrackPresence\", error)\n        {:error, :unable_to_track_presence}\n    end\n  end\n\n  defp check_track_payload(assigns, new_payload) do\n    if assigns[:presence_track_payload] != new_payload do\n      :ok\n    else\n      {:error, :no_payload_change}\n    end\n  end\n\n  defp presence_dirty_list(topic) do\n    [{:pool_size, size}] = :ets.lookup(Presence, :pool_size)\n\n    Presence\n    |> Shard.name_for_topic(topic, size)\n    |> Shard.dirty_list(topic)\n    |> Phoenix.Presence.group()\n  end\n\n  defp limit_presence_event(socket) do\n    %{assigns: %{presence_rate_counter: presence_counter, tenant: _tenant_id}} = socket\n    {:ok, rate_counter} = RateCounter.get(presence_counter)\n    tenant = Tenants.Cache.get_tenant_by_external_id(socket.assigns.tenant)\n\n    if rate_counter.avg > tenant.max_presence_events_per_second do\n      {:error, :rate_limit_exceeded}\n    else\n      GenCounter.add(presence_counter.id)\n      :ok\n    end\n  end\n\n  defp limit_client_presence_event(socket) do\n    %{assigns: %{presence_client_rate_limit: limit_config}} = socket\n\n    current_time = System.monotonic_time(:millisecond)\n\n    # Check if we need to reset the window\n    cond do\n      is_nil(limit_config.reset_at) or current_time > limit_config.reset_at ->\n        # Start new window or reset expired window\n        updated_limit_config = %{limit_config | counter: 1, reset_at: current_time + limit_config.window_ms}\n        updated_socket = assign(socket, :presence_client_rate_limit, updated_limit_config)\n        {:ok, updated_socket}\n\n      limit_config.counter >= limit_config.max_calls ->\n        {:error, :client_rate_limit_exceeded}\n\n      true ->\n        # Increment counter\n        updated_limit_config = %{limit_config | counter: limit_config.counter + 1}\n        updated_socket = assign(socket, :presence_client_rate_limit, updated_limit_config)\n        {:ok, updated_socket}\n    end\n  end\n\n  defp validate_payload_size(tenant, payload), do: Tenants.validate_payload_size(tenant, payload)\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/realtime_channel/tracker.ex",
    "content": "defmodule RealtimeWeb.RealtimeChannel.Tracker do\n  @moduledoc \"\"\"\n  Tracks if the user has any channels open.\n\n  Stores in :ets table the data.\n\n  If the user has no channels open, we kill the transport pid.\n  \"\"\"\n  use GenServer\n  require Logger\n\n  @table :channel_tracker\n  @zero_count_match [{{:\"$1\", :\"$2\"}, [{:\"=<\", :\"$2\", 0}], [:\"$1\"]}]\n  @zero_count_delete [{{:\"$1\", :\"$2\"}, [{:\"=<\", :\"$2\", 0}], [true]}]\n  @doc \"\"\"\n  Tracks a transport pid.\n  \"\"\"\n  @spec track(pid()) :: integer()\n  def track(pid), do: :ets.update_counter(@table, pid, 1, {pid, 0})\n\n  @doc \"\"\"\n  Un-tracks a transport pid.\n  \"\"\"\n  @spec untrack(pid()) :: integer()\n  def untrack(pid), do: :ets.update_counter(@table, pid, -1, {pid, 0})\n\n  @doc \"\"\"\n  Returns the number of channels open for a transport pid.\n  \"\"\"\n  @spec count(pid()) :: integer()\n  def count(pid) do\n    case :ets.lookup(@table, pid) do\n      [{^pid, count}] -> count\n      [] -> 0\n    end\n  end\n\n  @doc \"\"\"\n  Returns a list of all pids in the table and their count.\n  \"\"\"\n  @spec list_pids() :: [{pid(), integer()}]\n  def list_pids, do: :ets.tab2list(@table)\n\n  def start_link(opts) do\n    if :ets.whereis(@table) == :undefined do\n      :ets.new(@table, [\n        :set,\n        :public,\n        :named_table,\n        {:decentralized_counters, true},\n        {:write_concurrency, true}\n      ])\n    end\n\n    GenServer.start_link(__MODULE__, opts)\n  end\n\n  @impl true\n  def init(opts) do\n    check_interval_in_ms = Keyword.fetch!(opts, :check_interval_in_ms)\n    Process.send_after(self(), :check_channels, check_interval_in_ms)\n    {:ok, %{check_interval_in_ms: check_interval_in_ms}}\n  end\n\n  @impl true\n  def handle_info(:check_channels, state) do\n    chunked_killing()\n    :ets.select_delete(@table, @zero_count_delete)\n    Process.send_after(self(), :check_channels, state.check_interval_in_ms)\n    {:noreply, state}\n  end\n\n  defp chunked_killing(cont \\\\ nil) do\n    result = if cont, do: :ets.select(cont), else: :ets.select(@table, @zero_count_match, 1000)\n\n    case result do\n      :\"$end_of_table\" ->\n        :ok\n\n      {pids, cont} ->\n        Logger.info(\"Killing #{length(pids)} transport pids with no channels open\")\n        Enum.each(pids, fn pid -> if Process.alive?(pid), do: Process.exit(pid, :kill) end)\n        chunked_killing(cont)\n    end\n  end\n\n  def table_name, do: @table\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/realtime_channel.ex",
    "content": "defmodule RealtimeWeb.RealtimeChannel do\n  @moduledoc \"\"\"\n  Used for handling channels and subscriptions.\n  \"\"\"\n  use RealtimeWeb, :channel\n  use RealtimeWeb.RealtimeChannel.Logging\n\n  alias RealtimeWeb.SocketDisconnect\n  alias DBConnection.Backoff\n\n  alias Realtime.Api.Tenant\n  alias Realtime.Crypto\n  alias Realtime.GenCounter\n  alias Realtime.Helpers\n  alias Realtime.PostgresCdc\n  alias Realtime.RateCounter\n  alias Realtime.SignalHandler\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Authorization\n  alias Realtime.Tenants.Authorization.Policies\n  alias Realtime.Tenants.Authorization.Policies.BroadcastPolicies\n  alias Realtime.Tenants.Cache\n  alias Realtime.Tenants.Connect\n  alias Realtime.UsersCounter\n\n  alias RealtimeWeb.Channels.Payloads.Join\n  alias RealtimeWeb.ChannelsAuthorization\n  alias RealtimeWeb.RealtimeChannel.BroadcastHandler\n  alias RealtimeWeb.RealtimeChannel.MessageDispatcher\n  alias RealtimeWeb.RealtimeChannel.PresenceHandler\n  alias RealtimeWeb.RealtimeChannel.Tracker\n\n  @confirm_token_ms_interval :timer.minutes(5)\n  @fullsweep_after Application.compile_env!(:realtime, :websocket_fullsweep_after)\n\n  @impl true\n  def join(\"realtime:\", _params, socket) do\n    log_error(socket, \"TopicNameRequired\", \"You must provide a topic name\")\n  end\n\n  def join(\"realtime:\" <> sub_topic = topic, params, socket) do\n    %{\n      assigns: %{tenant: tenant_id, log_level: log_level},\n      channel_pid: channel_pid,\n      serializer: serializer,\n      transport_pid: transport_pid\n    } = socket\n\n    Process.flag(:max_heap_size, max_heap_size())\n    Process.flag(:fullsweep_after, @fullsweep_after)\n    Tracker.track(socket.transport_pid)\n    Logger.metadata(external_id: tenant_id, project: tenant_id)\n    Logger.put_process_level(self(), log_level)\n\n    presence_enabled? =\n      case get_in(params, [\"config\", \"presence\", \"enabled\"]) do\n        enabled when is_boolean(enabled) -> enabled\n        _ -> false\n      end\n\n    socket =\n      socket\n      |> assign_access_token(params)\n      |> assign(:private?, !!params[\"config\"][\"private\"])\n      |> assign(:policies, nil)\n      |> assign(:presence_enabled?, presence_enabled?)\n\n    case Join.validate(params) do\n      {:ok, _join} ->\n        nil\n\n      {:error, :invalid_join_payload, errors} ->\n        log_params = params |> Map.put(\"access_token\", \"<redacted>\") |> Map.put(\"user_token\", \"<redacted>\")\n        log_error(socket, \"InvalidJoinPayload\", %{changeset_errors: errors, params: log_params})\n    end\n\n    with :ok <- SignalHandler.shutdown_in_progress?(),\n         %Tenant{} = tenant <- Cache.get_tenant_by_external_id(tenant_id),\n         socket =\n           assign(socket, :presence_enabled?, presence_enabled?(socket.assigns.presence_enabled?, tenant)),\n         :ok <- only_private?(tenant, socket),\n         :ok <- limit_max_users(tenant, transport_pid),\n         :ok <- limit_joins(tenant, socket),\n         :ok <- limit_channels(tenant, socket),\n         {:ok, claims, confirm_token_ref} <- confirm_token(socket),\n         socket = assign_authorization_context(socket, sub_topic, claims),\n         {:ok, db_conn} <- Connect.lookup_or_start_connection(tenant_id),\n         {:ok, socket} <- maybe_assign_policies(sub_topic, db_conn, socket),\n         {:ok, replayed_message_ids} <-\n           maybe_replay_messages(params[\"config\"], sub_topic, db_conn, tenant_id, socket.assigns.private?) do\n      tenant_topic = Tenants.tenant_topic(tenant_id, sub_topic, !socket.assigns.private?)\n\n      # fastlane subscription\n      metadata =\n        MessageDispatcher.fastlane_metadata(\n          transport_pid,\n          serializer,\n          topic,\n          log_level,\n          tenant_id,\n          replayed_message_ids\n        )\n\n      RealtimeWeb.Endpoint.subscribe(tenant_topic, metadata: metadata)\n\n      Phoenix.PubSub.subscribe(Realtime.PubSub, \"realtime:operations:\" <> tenant_id)\n\n      is_new_api = new_api?(params)\n      presence_enabled? = socket.assigns.presence_enabled?\n\n      pg_change_params = pg_change_params(is_new_api, params, channel_pid, claims, sub_topic)\n\n      opts = %{\n        is_new_api: is_new_api,\n        pg_change_params: pg_change_params,\n        transport_pid: transport_pid,\n        serializer: serializer,\n        topic: topic,\n        tenant: tenant_id\n      }\n\n      postgres_cdc_subscribe(tenant, opts)\n\n      state = %{postgres_changes: add_id_to_postgres_changes(pg_change_params)}\n\n      assigns = %{\n        ack_broadcast: !!params[\"config\"][\"broadcast\"][\"ack\"],\n        confirm_token_ref: confirm_token_ref,\n        is_new_api: is_new_api,\n        pg_sub_ref: nil,\n        pg_change_params: pg_change_params,\n        presence_key: presence_key(params),\n        self_broadcast: !!params[\"config\"][\"broadcast\"][\"self\"],\n        tenant_topic: tenant_topic,\n        channel_name: sub_topic,\n        presence_enabled?: presence_enabled?\n      }\n\n      socket =\n        socket\n        |> assign_counter(tenant)\n        |> assign_presence_counter(tenant)\n        |> assign_client_presence_rate_limit(tenant)\n\n      # Start presence and add user if presence is enabled\n      if presence_enabled?, do: send(self(), :sync_presence)\n\n      UsersCounter.add(transport_pid, tenant_id)\n      SocketDisconnect.add(tenant_id, socket)\n\n      {:ok, state, assign(socket, assigns)}\n    else\n      {:error, :expired_token, msg} ->\n        maybe_log_warning(socket, \"InvalidJWTToken\", msg)\n\n      {:error, :missing_claims} ->\n        msg = \"Fields `role` and `exp` are required in JWT\"\n        maybe_log_warning(socket, \"InvalidJWTToken\", msg)\n\n      {:error, :unauthorized, msg} ->\n        log_error(socket, \"Unauthorized\", msg)\n\n      {:error, :too_many_channels} ->\n        msg = \"Too many channels\"\n        log_error(socket, \"ChannelRateLimitReached\", msg)\n\n      {:error, :too_many_connections} ->\n        msg = \"Too many connected users\"\n        log_error(socket, \"ConnectionRateLimitReached\", msg)\n\n      {:error, :too_many_joins} ->\n        msg = \"ClientJoinRateLimitReached: Too many joins per second\"\n        send(transport_pid, %Phoenix.Socket.Broadcast{event: \"disconnect\"})\n        {:error, %{reason: msg}}\n\n      {:error, :increase_connection_pool} ->\n        msg = \"Please increase your connection pool size\"\n        log_error(socket, \"IncreaseConnectionPool\", msg)\n\n      {:error, :tenant_db_too_many_connections} ->\n        msg = \"Database can't accept more connections, Realtime won't connect\"\n        log_error(socket, \"DatabaseLackOfConnections\", msg)\n\n      {:error, :connect_rate_limit_reached} ->\n        msg = \"Too many database connections attempts per second\"\n        log_error(socket, \"DatabaseConnectionRateLimitReached\", msg)\n\n      {:error, :unable_to_set_policies, error} ->\n        log_error(socket, \"UnableToSetPolicies\", error)\n        {:error, %{reason: \"Realtime was unable to connect to the project database\"}}\n\n      {:error, :tenant_database_unavailable} ->\n        log_error(socket, \"UnableToConnectToProject\", \"Realtime was unable to connect to the project database\")\n\n      {:error, :rpc_error, :timeout} ->\n        log_error(socket, \"TimeoutOnRpcCall\", \"Node request timeout\")\n\n      {:error, :rpc_error, reason} ->\n        log_error(socket, \"ErrorOnRpcCall\", \"RPC call error: \" <> inspect(reason))\n\n      {:error, :initializing} ->\n        log_error(socket, \"InitializingProjectConnection\", \"Realtime is initializing the project connection\")\n\n      {:error, :tenant_database_connection_initializing} ->\n        log_error(socket, \"InitializingProjectConnection\", \"Connecting to the project database\")\n\n      {:error, :token_malformed, msg} ->\n        log_error(socket, \"MalformedJWT\", msg)\n\n      {:error, invalid_exp} when is_integer(invalid_exp) and invalid_exp <= 0 ->\n        log_error(socket, \"InvalidJWTToken\", \"Token expiration time is invalid\")\n\n      {:error, :private_only} ->\n        log_error(socket, \"PrivateOnly\", \"This project only allows private channels\")\n\n      {:error, :tenant_not_found} ->\n        log_error(socket, \"TenantNotFound\", \"Tenant with the given ID does not exist\")\n\n      {:error, :tenant_suspended} ->\n        log_error(socket, \"RealtimeDisabledForTenant\", \"Realtime disabled for this tenant\")\n\n      {:error, :signature_error} ->\n        log_error(socket, \"JwtSignatureError\", \"Failed to validate JWT signature\")\n\n      {:error, :shutdown_in_progress} ->\n        log_error(socket, \"RealtimeRestarting\", \"Realtime is restarting, please standby\")\n\n      {:error, :failed_to_replay_messages} ->\n        log_error(socket, \"UnableToReplayMessages\", \"Realtime was unable to replay messages\")\n\n      {:error, :invalid_replay_params} ->\n        log_error(socket, \"UnableToReplayMessages\", \"Replay params are not valid\")\n\n      {:error, :invalid_replay_channel} ->\n        log_error(socket, \"UnableToReplayMessages\", \"Replay is not allowed for public channels\")\n\n      {:error, :error_generating_signer} ->\n        log_error(\n          socket,\n          \"JwtSignerError\",\n          \"Failed to generate JWT signer, check your JWT secret or JWKS configuration\"\n        )\n\n      {:error, error} ->\n        log_error(socket, \"UnknownErrorOnChannel\", error)\n        {:error, %{reason: \"Unknown Error on Channel\"}}\n    end\n  end\n\n  @impl true\n  def handle_info({:replay, messages}, socket) do\n    for message <- messages do\n      meta = %{\"replayed\" => true, \"id\" => message.id}\n      payload = %{\"payload\" => message.payload, \"event\" => message.event, \"type\" => \"broadcast\", \"meta\" => meta}\n\n      push(socket, \"broadcast\", payload)\n    end\n\n    {:noreply, socket}\n  end\n\n  def handle_info(:update_rate_counter, socket) do\n    count(socket)\n\n    {:ok, rate_counter} = RateCounter.get(socket.assigns.rate_counter)\n\n    if rate_counter.limit.triggered do\n      message = \"Too many messages per second\"\n      shutdown_response(socket, message)\n    else\n      {:noreply, socket}\n    end\n  end\n\n  def handle_info(%{event: \"postgres_cdc_rls_down\"}, socket) do\n    %{assigns: %{pg_sub_ref: pg_sub_ref}} = socket\n    Helpers.cancel_timer(pg_sub_ref)\n    pg_sub_ref = postgres_subscribe()\n\n    {:noreply, assign(socket, %{pg_sub_ref: pg_sub_ref})}\n  end\n\n  def handle_info(_msg, %{assigns: %{policies: %Policies{broadcast: %BroadcastPolicies{read: false}}}} = socket) do\n    Logger.warning(\"Broadcast message ignored\")\n    {:noreply, socket}\n  end\n\n  def handle_info(%{event: type, payload: payload} = msg, socket) do\n    count(socket)\n    maybe_log_info(socket, msg)\n    push(socket, type, payload)\n    {:noreply, socket}\n  end\n\n  def handle_info(:postgres_subscribe, %{assigns: %{channel_name: channel_name}} = socket) do\n    %{\n      assigns: %{\n        tenant: tenant_id,\n        pg_sub_ref: pg_sub_ref,\n        pg_change_params: pg_change_params\n      }\n    } = socket\n\n    Helpers.cancel_timer(pg_sub_ref)\n\n    %Tenant{} = tenant = Cache.get_tenant_by_external_id(tenant_id)\n    {:ok, module} = PostgresCdc.driver(tenant.postgres_cdc_default)\n    postgres_extension = PostgresCdc.filter_settings(tenant.postgres_cdc_default, tenant.extensions)\n\n    args = %{\"region\" => postgres_extension[\"region\"], \"id\" => tenant_id}\n\n    case PostgresCdc.connect(module, args) do\n      {:ok, response} ->\n        case PostgresCdc.after_connect(module, response, postgres_extension, pg_change_params, tenant_id) do\n          {:ok, _response} ->\n            message = \"Subscribed to PostgreSQL\"\n            maybe_log_info(socket, message)\n            push_system_message(\"postgres_changes\", socket, \"ok\", message, channel_name)\n            {:noreply, assign(socket, :pg_sub_ref, nil)}\n\n          {:error, {reason, error}} when reason in [:malformed_subscription_params, :subscription_insert_failed] ->\n            maybe_log_warning(socket, \"RealtimeDisabledForConfiguration\", error)\n            push_system_message(\"postgres_changes\", socket, \"error\", error, channel_name)\n            # No point in retrying if the params are invalid\n            {:noreply, assign(socket, :pg_sub_ref, nil)}\n\n          error ->\n            maybe_log_warning(socket, \"RealtimeDisabledForConfiguration\", error)\n\n            push_system_message(\"postgres_changes\", socket, \"error\", error, channel_name)\n            {:noreply, assign(socket, :pg_sub_ref, postgres_subscribe(5, 10))}\n        end\n\n      nil ->\n        maybe_log_warning(\n          socket,\n          \"ReconnectSubscribeToPostgres\",\n          \"Re-connecting to PostgreSQL with params: \" <> inspect(pg_change_params)\n        )\n\n        {:noreply, assign(socket, :pg_sub_ref, postgres_subscribe())}\n\n      error ->\n        maybe_log_error(socket, \"UnableToSubscribeToPostgres\", error)\n        push_system_message(\"postgres_changes\", socket, \"error\", error, channel_name)\n        {:noreply, assign(socket, :pg_sub_ref, postgres_subscribe(5, 10))}\n    end\n  rescue\n    error ->\n      log_warning(socket, \"UnableToSubscribeToPostgres\", error)\n      push_system_message(\"postgres_changes\", socket, \"error\", error, channel_name)\n      {:noreply, assign(socket, :pg_sub_ref, postgres_subscribe(5, 10))}\n  end\n\n  def handle_info(:confirm_token, %{assigns: %{pg_change_params: pg_change_params}} = socket) do\n    case confirm_token(socket) do\n      {:ok, claims, confirm_token_ref} ->\n        pg_change_params = Enum.map(pg_change_params, &Map.put(&1, :claims, claims))\n        {:noreply, assign(socket, %{confirm_token_ref: confirm_token_ref, pg_change_params: pg_change_params})}\n\n      {:error, :missing_claims} ->\n        shutdown_response(socket, \"Fields `role` and `exp` are required in JWT\")\n\n      {:error, :expired_token, msg} ->\n        shutdown_response(socket, msg)\n\n      {:error, error} ->\n        shutdown_response(socket, Realtime.Logs.to_log(error))\n    end\n  end\n\n  def handle_info(:disconnect, %{assigns: %{channel_name: channel_name}} = socket) do\n    Logger.info(\"Received operational call to disconnect channel\")\n    push_system_message(\"system\", socket, \"ok\", \"Server requested disconnect\", channel_name)\n    {:stop, :shutdown, socket}\n  end\n\n  def handle_info(:sync_presence, %{assigns: %{presence_enabled?: true}} = socket) do\n    case PresenceHandler.sync(socket) do\n      :ok ->\n        {:noreply, socket}\n\n      {:error, :rate_limit_exceeded} ->\n        shutdown_response(socket, \"Too many presence messages per second\")\n    end\n  end\n\n  def handle_info(:sync_presence, socket), do: {:noreply, socket}\n  def handle_info(_, socket), do: {:noreply, socket}\n\n  @impl true\n  def handle_in(\"broadcast\", payload, %{assigns: %{private?: true}} = socket) do\n    %{tenant: tenant_id} = socket.assigns\n\n    with {:ok, db_conn} <- Connect.lookup_or_start_connection(tenant_id) do\n      BroadcastHandler.handle(payload, db_conn, socket)\n    else\n      {:error, error} ->\n        log_error(socket, \"UnableToHandleBroadcast\", error)\n        {:noreply, socket}\n    end\n  end\n\n  def handle_in(\"broadcast\", payload, %{assigns: %{private?: false}} = socket) do\n    BroadcastHandler.handle(payload, socket)\n  end\n\n  def handle_in(\"presence\", payload, %{assigns: %{private?: true}} = socket) do\n    %{tenant: tenant_id} = socket.assigns\n\n    with {:ok, db_conn} <- Connect.lookup_or_start_connection(tenant_id),\n         {:ok, socket} <- PresenceHandler.handle(payload, db_conn, socket) do\n      {:reply, :ok, socket}\n    else\n      {:error, :client_rate_limit_exceeded} ->\n        log_error(socket, \"ClientPresenceRateLimitReached\", :client_rate_limit_exceeded)\n        shutdown_response(socket, \"Client presence rate limit exceeded\")\n\n      {:error, :rate_limit_exceeded} ->\n        shutdown_response(socket, \"Too many presence messages per second\")\n\n      {:error, :payload_size_exceeded} ->\n        shutdown_response(socket, \"Track message size exceeded\")\n\n      {:error, error} ->\n        log_error(socket, \"UnableToHandlePresence\", error)\n        {:reply, :error, socket}\n    end\n  end\n\n  def handle_in(\"presence\", payload, %{assigns: %{private?: false}} = socket) do\n    with {:ok, socket} <- PresenceHandler.handle(payload, nil, socket) do\n      {:reply, :ok, socket}\n    else\n      {:error, :client_rate_limit_exceeded} ->\n        log_error(socket, \"ClientPresenceRateLimitReached\", :client_rate_limit_exceeded)\n        shutdown_response(socket, \"Client presence rate limit exceeded\")\n\n      {:error, :rate_limit_exceeded} ->\n        shutdown_response(socket, \"Too many presence messages per second\")\n\n      {:error, :payload_size_exceeded} ->\n        shutdown_response(socket, \"Track message size exceeded\")\n\n      {:error, error} ->\n        log_error(socket, \"UnableToHandlePresence\", error)\n        {:reply, :error, socket}\n    end\n  end\n\n  def handle_in(\"access_token\", %{\"access_token\" => \"sb_\" <> _}, socket) do\n    {:noreply, socket}\n  end\n\n  def handle_in(\"access_token\", %{\"access_token\" => refresh_token}, %{assigns: %{access_token: access_token}} = socket)\n      when refresh_token == access_token do\n    {:noreply, socket}\n  end\n\n  def handle_in(\"access_token\", %{\"access_token\" => refresh_token}, %{assigns: %{access_token: _access_token}} = socket)\n      when is_nil(refresh_token) do\n    {:noreply, socket}\n  end\n\n  def handle_in(\"access_token\", %{\"access_token\" => refresh_token}, socket) when is_binary(refresh_token) do\n    %{\n      assigns: %{\n        tenant: tenant_id,\n        pg_sub_ref: pg_sub_ref,\n        channel_name: channel_name,\n        pg_change_params: pg_change_params\n      }\n    } = socket\n\n    # Update token and reset policies\n    socket = assign(socket, %{access_token: refresh_token, policies: nil})\n\n    with {:ok, claims, confirm_token_ref} <- confirm_token(socket),\n         socket = assign_authorization_context(socket, channel_name, claims),\n         {:ok, db_conn} <- Connect.lookup_or_start_connection(tenant_id),\n         {:ok, socket} <- maybe_assign_policies(channel_name, db_conn, socket) do\n      Helpers.cancel_timer(pg_sub_ref)\n      pg_change_params = Enum.map(pg_change_params, &Map.put(&1, :claims, claims))\n\n      pg_sub_ref =\n        case pg_change_params do\n          [_ | _] -> postgres_subscribe()\n          _ -> nil\n        end\n\n      assigns = %{\n        pg_sub_ref: pg_sub_ref,\n        confirm_token_ref: confirm_token_ref,\n        pg_change_params: pg_change_params\n      }\n\n      {:noreply, assign(socket, assigns)}\n    else\n      {:error, reason, msg} when reason in ~w(unauthorized expired_token token_malformed)a ->\n        shutdown_response(socket, msg)\n\n      {:error, :missing_claims} ->\n        shutdown_response(socket, \"Fields `role` and `exp` are required in JWT\")\n\n      {:error, :unable_to_set_policies, _msg} ->\n        shutdown_response(socket, \"Realtime was unable to connect to the project database\")\n\n      {:error, error} ->\n        shutdown_response(socket, inspect(error))\n\n      {:error, :rpc_error, :timeout} ->\n        shutdown_response(socket, \"Node request timeout\")\n\n      {:error, :rpc_error, reason} ->\n        shutdown_response(socket, \"RPC call error: \" <> inspect(reason))\n    end\n  end\n\n  def handle_in(type, payload, socket) do\n    count(socket)\n\n    # Log info here so that bad messages from clients won't flood Logflare\n    # Can subscribe to a Channel with `log_level` `info` to see these messages\n    message = \"Unexpected message from client of type `#{type}` with payload: #{inspect(payload)}\"\n    Logger.info(message)\n\n    {:noreply, socket}\n  end\n\n  @impl true\n  def terminate(reason, %{transport_pid: transport_pid}) do\n    Logger.debug(\"Channel terminated with reason: #{inspect(reason)}\")\n    :telemetry.execute([:prom_ex, :plugin, :realtime, :disconnected], %{})\n    Tracker.untrack(transport_pid)\n    :ok\n  end\n\n  defp postgres_subscribe(min \\\\ 1, max \\\\ 3) do\n    Process.send_after(self(), :postgres_subscribe, backoff(min, max))\n  end\n\n  defp backoff(min, max) do\n    {wait, _} = Backoff.backoff(%Backoff{type: :rand, min: min * 1000, max: max * 1000})\n    wait\n  end\n\n  def limit_joins(tenant, socket) do\n    rate_args = Tenants.joins_per_second_rate(tenant)\n\n    RateCounter.new(rate_args)\n\n    case RateCounter.get(rate_args) do\n      {:ok, %{limit: %{triggered: false}}} ->\n        GenCounter.add(rate_args.id)\n        :ok\n\n      {:ok, %{limit: %{triggered: true}}} ->\n        {:error, :too_many_joins}\n\n      error ->\n        log_error(socket, \"UnknownErrorOnCounter\", error)\n        {:error, error}\n    end\n  end\n\n  def limit_channels(tenant, %{transport_pid: pid}) do\n    key = Tenants.channels_per_client_key(tenant)\n\n    if Registry.count_match(Realtime.Registry, key, pid) + 1 > tenant.max_channels_per_client do\n      {:error, :too_many_channels}\n    else\n      Registry.register(Realtime.Registry, Tenants.channels_per_client_key(tenant), pid)\n      :ok\n    end\n  end\n\n  defp limit_max_users(tenant, transport_pid) do\n    if !UsersCounter.already_counted?(transport_pid, tenant.external_id) and\n         UsersCounter.tenant_users(tenant.external_id) >= tenant.max_concurrent_users do\n      {:error, :too_many_connections}\n    else\n      :ok\n    end\n  end\n\n  defp assign_counter(socket, tenant) do\n    rate_args = Tenants.events_per_second_rate(tenant)\n\n    RateCounter.new(rate_args)\n    assign(socket, :rate_counter, rate_args)\n  end\n\n  defp assign_presence_counter(socket, tenant) do\n    rate_args = Tenants.presence_events_per_second_rate(tenant)\n\n    RateCounter.new(rate_args)\n\n    assign(socket, :presence_rate_counter, rate_args)\n  end\n\n  defp assign_client_presence_rate_limit(socket, tenant) do\n    config = Application.get_env(:realtime, :client_presence_rate_limit, max_calls: 5, window_ms: 30_000)\n\n    max_calls =\n      case tenant.max_client_presence_events_per_window do\n        value when is_integer(value) and value > 0 -> value\n        _ -> config[:max_calls]\n      end\n\n    window_ms =\n      case tenant.client_presence_window_ms do\n        value when is_integer(value) and value > 0 -> value\n        _ -> config[:window_ms]\n      end\n\n    client_rate_limit = %{\n      max_calls: max_calls,\n      window_ms: window_ms,\n      counter: 0,\n      reset_at: nil\n    }\n\n    assign(socket, :presence_client_rate_limit, client_rate_limit)\n  end\n\n  defp count(%{assigns: %{rate_counter: counter}}), do: GenCounter.add(counter.id)\n\n  defp presence_key(params) do\n    case params[\"config\"][\"presence\"][\"key\"] do\n      key when is_binary(key) and key != \"\" -> key\n      _ -> UUID.uuid1()\n    end\n  end\n\n  defp assign_access_token(%{assigns: %{tenant_token: tenant_token}} = socket, params) do\n    access_token = Map.get(params, \"access_token\") || Map.get(params, \"user_token\")\n\n    case access_token do\n      \"sb_\" <> _ -> assign(socket, :access_token, tenant_token)\n      _ -> handle_access_token(socket, params)\n    end\n  end\n\n  defp handle_access_token(%{assigns: %{tenant_token: _tenant_token}} = socket, %{\"user_token\" => user_token})\n       when is_binary(user_token) do\n    assign(socket, :access_token, user_token)\n  end\n\n  defp handle_access_token(%{assigns: %{tenant_token: _tenant_token}} = socket, %{\"access_token\" => access_token})\n       when is_binary(access_token) do\n    assign(socket, :access_token, access_token)\n  end\n\n  defp handle_access_token(%{assigns: %{tenant_token: tenant_token}} = socket, _params) when is_binary(tenant_token) do\n    assign(socket, :access_token, tenant_token)\n  end\n\n  defp confirm_token(%{assigns: assigns}) do\n    %{jwt_secret: jwt_secret, access_token: access_token} = assigns\n\n    jwt_jwks = Map.get(assigns, :jwt_jwks)\n\n    with jwt_secret_dec <- Crypto.decrypt!(jwt_secret),\n         {:ok, %{\"exp\" => exp} = claims} when is_integer(exp) <-\n           ChannelsAuthorization.authorize_conn(access_token, jwt_secret_dec, jwt_jwks),\n         exp_diff when exp_diff > 0 <- exp - Joken.current_time() do\n      if ref = assigns[:confirm_token_ref], do: Helpers.cancel_timer(ref)\n\n      interval = min(@confirm_token_ms_interval, exp_diff * 1000)\n      ref = Process.send_after(self(), :confirm_token, interval)\n\n      {:ok, claims, ref}\n    else\n      {:error, :token_malformed} ->\n        {:error, :token_malformed, \"The token provided is not a valid JWT\"}\n\n      {:error, error} ->\n        {:error, error}\n\n      {:error, error, message} ->\n        {:error, error, message}\n\n      e ->\n        {:error, e}\n    end\n  end\n\n  defp shutdown_response(socket, message) when is_binary(message) do\n    %{assigns: %{channel_name: channel_name}} = socket\n    push_system_message(\"system\", socket, \"error\", message, channel_name)\n    maybe_log_warning(socket, \"ChannelShutdown\", message)\n    {:stop, :normal, socket}\n  end\n\n  defp push_system_message(extension, socket, status, error, channel_name)\n       when is_map(error) and is_map_key(error, :error_code) and is_map_key(error, :error_message) do\n    push(socket, \"system\", %{\n      extension: extension,\n      status: status,\n      message: \"#{error.error_code}: #{error.error_message}\",\n      channel: channel_name\n    })\n  end\n\n  defp push_system_message(extension, socket, status, message, channel_name)\n       when is_binary(message) do\n    push(socket, \"system\", %{\n      extension: extension,\n      status: status,\n      message: message,\n      channel: channel_name\n    })\n  end\n\n  defp push_system_message(extension, socket, status, message, channel_name) do\n    push(socket, \"system\", %{\n      extension: extension,\n      status: status,\n      message: inspect(message),\n      channel: channel_name\n    })\n  end\n\n  defp new_api?(%{\"config\" => _}), do: true\n  defp new_api?(_), do: false\n\n  defp pg_change_params(true, params, channel_pid, claims, _) do\n    case get_in(params, [\"config\", \"postgres_changes\"]) do\n      [_ | _] = params_list ->\n        params_list\n        |> Enum.reject(&is_nil/1)\n        |> Enum.map(fn params ->\n          %{\n            id: UUID.uuid1(),\n            channel_pid: channel_pid,\n            claims: claims,\n            params: params\n          }\n        end)\n\n      _ ->\n        []\n    end\n  end\n\n  defp pg_change_params(false, _, channel_pid, claims, sub_topic) do\n    params =\n      case String.split(sub_topic, \":\", parts: 3) do\n        [schema, table, filter] -> %{\"schema\" => schema, \"table\" => table, \"filter\" => filter}\n        [schema, table] -> %{\"schema\" => schema, \"table\" => table}\n        [schema] -> %{\"schema\" => schema}\n      end\n\n    [\n      %{\n        id: UUID.uuid1(),\n        channel_pid: channel_pid,\n        claims: claims,\n        params: params\n      }\n    ]\n  end\n\n  defp postgres_cdc_subscribe(_tenant, %{pg_change_params: []}), do: []\n\n  defp postgres_cdc_subscribe(tenant, opts) do\n    %{\n      is_new_api: is_new_api,\n      pg_change_params: pg_change_params,\n      transport_pid: transport_pid,\n      serializer: serializer,\n      topic: topic\n    } = opts\n\n    ids =\n      Enum.map(pg_change_params, fn %{id: id, params: params} ->\n        {UUID.string_to_binary!(id), :erlang.phash2(params)}\n      end)\n\n    subscription_metadata =\n      {:subscriber_fastlane, transport_pid, serializer, ids, topic, is_new_api}\n\n    metadata = [metadata: subscription_metadata]\n\n    {:ok, module} = PostgresCdc.driver(tenant.postgres_cdc_default)\n    PostgresCdc.subscribe(module, pg_change_params, tenant.external_id, metadata)\n\n    send(self(), :postgres_subscribe)\n\n    pg_change_params\n  end\n\n  defp add_id_to_postgres_changes(pg_change_params) do\n    Enum.map(pg_change_params, fn %{params: params} ->\n      id = :erlang.phash2(params)\n      Map.put(params, :id, id)\n    end)\n  end\n\n  defp assign_authorization_context(socket, topic, claims) do\n    authorization_context =\n      Authorization.build_authorization_params(%{\n        tenant_id: socket.assigns.tenant,\n        topic: topic,\n        headers: Map.get(socket.assigns, :headers, []),\n        claims: claims,\n        role: claims[\"role\"],\n        sub: claims[\"sub\"]\n      })\n\n    assign(socket, :authorization_context, authorization_context)\n  end\n\n  defp maybe_assign_policies(topic, db_conn, %{assigns: %{private?: true}} = socket)\n       when not is_nil(topic) do\n    authorization_context = socket.assigns.authorization_context\n    policies = socket.assigns.policies || %Policies{}\n    presence_enabled? = socket.assigns.presence_enabled?\n\n    with {:ok, policies} <-\n           Authorization.get_read_authorizations(policies, db_conn, authorization_context,\n             presence_enabled?: presence_enabled?\n           ) do\n      socket = assign(socket, :policies, policies)\n\n      if match?(%Policies{broadcast: %BroadcastPolicies{read: false}}, socket.assigns.policies),\n        do: {:error, :unauthorized, \"You do not have permissions to read from this Channel topic: #{topic}\"},\n        else: {:ok, socket}\n    else\n      {:error, :increase_connection_pool} ->\n        {:error, :increase_connection_pool}\n\n      {:error, :rls_policy_error, error} ->\n        log_error(socket, \"RlsPolicyError\", error)\n\n        {:error, :unauthorized, \"You do not have permissions to read from this Channel topic: #{topic}\"}\n\n      {:error, error} ->\n        {:error, :unable_to_set_policies, error}\n    end\n  end\n\n  defp maybe_assign_policies(_, _, socket), do: {:ok, assign(socket, policies: nil)}\n\n  defp only_private?(tenant, %{assigns: %{private?: private?}}) do\n    if tenant.private_only and !private? do\n      {:error, :private_only}\n    else\n      :ok\n    end\n  end\n\n  defp maybe_replay_messages(%{\"broadcast\" => %{\"replay\" => _}}, _sub_topic, _db_conn, _tenant_id, false = _private?) do\n    {:error, :invalid_replay_channel}\n  end\n\n  defp maybe_replay_messages(\n         %{\"broadcast\" => %{\"replay\" => replay_params}},\n         sub_topic,\n         db_conn,\n         tenant_id,\n         true = _private?\n       )\n       when is_map(replay_params) do\n    with {:ok, messages, message_ids} <-\n           Realtime.Messages.replay(\n             db_conn,\n             tenant_id,\n             sub_topic,\n             replay_params[\"since\"],\n             replay_params[\"limit\"] || 25\n           ) do\n      # Send to self because we can't write to the socket before finishing the join process\n      send(self(), {:replay, messages})\n      {:ok, message_ids}\n    end\n  end\n\n  defp maybe_replay_messages(_, _, _, _, _), do: {:ok, MapSet.new()}\n\n  defp presence_enabled?(client_enabled?, %Tenant{presence_enabled: tenant_enabled}) do\n    client_enabled? || tenant_enabled\n  end\n\n  defp max_heap_size(), do: Application.fetch_env!(:realtime, :websocket_max_heap_size)\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/socket_disconnect.ex",
    "content": "defmodule RealtimeWeb.SocketDisconnect do\n  @moduledoc \"\"\"\n  Handles the distributed disconnection of sockets for a given tenant. It also ensures that there are no repeated registrations of the same transport PID for a given tenant.\n  \"\"\"\n  use Realtime.Logs\n\n  alias Phoenix.Socket\n  alias Realtime.Api.Tenant\n  alias Realtime.Tenants\n\n  @doc \"\"\"\n  Adds a socket to the registry associated to a tenant.\n  It will register the transport PID and a list of channel PIDs associated with a given transport pid.\n  \"\"\"\n  @spec add(binary(), Socket.t()) :: :ok | {:error, term()}\n  def add(tenant_external_id, %Socket{transport_pid: transport_pid}) when is_binary(tenant_external_id) do\n    transport_pid_exists_match_spec = [\n      {\n        {tenant_external_id, :\"$1\", :\"$2\"},\n        [{:==, :\"$2\", transport_pid}],\n        [:\"$1\"]\n      }\n    ]\n\n    case Registry.select(__MODULE__.Registry, transport_pid_exists_match_spec) do\n      [] -> {:ok, _} = Registry.register(__MODULE__.Registry, tenant_external_id, transport_pid)\n      _ -> nil\n    end\n\n    :ok\n  end\n\n  @doc \"\"\"\n  Disconnects all sockets associated with a given tenant across all nodes in the cluster.\n  \"\"\"\n  @spec distributed_disconnect(Tenant.t() | binary()) :: list(:ok | :error)\n  def distributed_disconnect(%Tenant{external_id: external_id}), do: distributed_disconnect(external_id)\n\n  def distributed_disconnect(external_id) do\n    [Node.self() | Node.list()]\n    |> :erpc.multicall(__MODULE__, :disconnect, [external_id], 5000)\n    |> Enum.map(fn {res, _} -> res end)\n  end\n\n  @doc \"\"\"\n  Disconnects all sockets associated with a given tenant on the current node.\n  \"\"\"\n  @spec disconnect(binary()) :: :ok | :error\n  def disconnect(%Tenant{external_id: external_id}), do: disconnect(external_id)\n\n  def disconnect(tenant_external_id) do\n    Logger.metadata(external_id: tenant_external_id, project: tenant_external_id)\n    Logger.warning(\"Disconnecting all sockets for tenant #{tenant_external_id}\")\n    Tenants.broadcast_operation_event(:disconnect, tenant_external_id)\n\n    pids = Registry.lookup(__MODULE__.Registry, tenant_external_id)\n    for {_, pid} <- pids, Process.alive?(pid), do: Process.exit(pid, :shutdown)\n    Registry.unregister(__MODULE__.Registry, tenant_external_id)\n\n    :ok\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/tenant_rate_limiters.ex",
    "content": "defmodule RealtimeWeb.TenantRateLimiters do\n  @moduledoc \"\"\"\n  Rate limiters for tenants.\n  \"\"\"\n  require Logger\n  alias Realtime.UsersCounter\n  alias Realtime.Tenants\n  alias Realtime.RateCounter\n  alias Realtime.Api.Tenant\n\n  @spec check_tenant(Realtime.Api.Tenant.t()) :: :ok | {:error, :too_many_connections | :too_many_joins}\n  def check_tenant(tenant) do\n    with :ok <- max_concurrent_users_check(tenant) do\n      max_joins_per_second_check(tenant)\n    end\n  end\n\n  defp max_concurrent_users_check(%Tenant{max_concurrent_users: max_conn_users, external_id: external_id}) do\n    total_conn_users = UsersCounter.tenant_users(external_id)\n\n    if total_conn_users < max_conn_users,\n      do: :ok,\n      else: {:error, :too_many_connections}\n  end\n\n  defp max_joins_per_second_check(%Tenant{max_joins_per_second: max_joins_per_second} = tenant) do\n    rate_args = Tenants.joins_per_second_rate(tenant.external_id, max_joins_per_second)\n\n    RateCounter.new(rate_args)\n\n    case RateCounter.get(rate_args) do\n      {:ok, %{limit: %{triggered: false}}} ->\n        :ok\n\n      {:ok, %{limit: %{triggered: true}}} ->\n        {:error, :too_many_joins}\n\n      error ->\n        Logger.error(\"UnknownErrorOnCounter: #{inspect(error)}\")\n        {:error, error}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/channels/user_socket.ex",
    "content": "defmodule RealtimeWeb.UserSocket do\n  # This is defined up here before `use Phoenix.Socket` is called so that we can define `Phoenix.Socket.init/1`\n  # It has to be overridden because we need to set the `max_heap_size` flag from the transport process context\n  @impl Phoenix.Socket.Transport\n  def handle_in({payload, opts}, {_state, socket} = full_state) do\n    Phoenix.Socket.__in__({payload, opts}, full_state)\n  rescue\n    e in Phoenix.Socket.InvalidMessageError ->\n      RealtimeWeb.RealtimeChannel.Logging.log_error(socket, \"MalformedWebSocketMessage\", e.message)\n      {:ok, full_state}\n\n    e in Jason.DecodeError ->\n      RealtimeWeb.RealtimeChannel.Logging.log_error(socket, \"MalformedWebSocketMessage\", Jason.DecodeError.message(e))\n      {:ok, full_state}\n\n    e ->\n      RealtimeWeb.RealtimeChannel.Logging.log_error(socket, \"UnknownErrorOnWebSocketMessage\", Exception.message(e))\n      {:ok, full_state}\n  end\n\n  @impl true\n  def init(state) when is_tuple(state) do\n    Process.flag(:max_heap_size, max_heap_size())\n    Process.send_after(self(), {:measure_traffic, 0, 0}, measure_traffic_interval_in_ms())\n    Phoenix.Socket.__init__(state)\n  end\n\n  @impl true\n  def handle_info(\n        {:measure_traffic, previous_recv, previous_send},\n        {_, %{assigns: assigns, transport_pid: transport_pid}} = state\n      ) do\n    tenant_external_id = Map.get(assigns, :tenant)\n\n    %{latest_recv: latest_recv, latest_send: latest_send} =\n      collect_traffic_telemetry(transport_pid, tenant_external_id, previous_recv, previous_send)\n\n    Process.send_after(self(), {:measure_traffic, latest_recv, latest_send}, measure_traffic_interval_in_ms())\n\n    {:ok, state}\n  end\n\n  use Phoenix.Socket\n  use Realtime.Logs\n\n  alias Realtime.Api.Tenant\n  alias Realtime.Crypto\n  alias Realtime.Database\n  alias Realtime.Tenants\n\n  alias RealtimeWeb.TenantRateLimiters\n  alias RealtimeWeb.ChannelsAuthorization\n  alias RealtimeWeb.RealtimeChannel\n  alias RealtimeWeb.RealtimeChannel.Logging\n  ## Channels\n  channel \"realtime:*\", RealtimeChannel\n\n  @default_log_level :error\n\n  @impl true\n  def id(%{assigns: %{tenant: tenant}}), do: subscribers_id(tenant)\n\n  @spec subscribers_id(String.t()) :: String.t()\n  def subscribers_id(tenant), do: \"user_socket:\" <> tenant\n\n  @impl true\n  def connect(params, socket, opts) do\n    %{uri: %{host: host}, x_headers: headers} = opts\n\n    {:ok, external_id} = Database.get_external_id(host)\n    token = access_token(params, headers)\n    log_level = log_level(params)\n\n    Logger.metadata(external_id: external_id, project: external_id)\n    Logger.put_process_level(self(), log_level)\n\n    socket =\n      socket\n      |> assign(:tenant, external_id)\n      |> assign(:log_level, log_level)\n      |> assign(:access_token, token)\n\n    with %Tenant{\n           jwt_secret: jwt_secret,\n           jwt_jwks: jwt_jwks,\n           suspend: false\n         } = tenant <- Tenants.Cache.get_tenant_by_external_id(external_id),\n         token when is_binary(token) <- token,\n         jwt_secret_dec <- Crypto.decrypt!(jwt_secret),\n         {:ok, claims} <- ChannelsAuthorization.authorize_conn(token, jwt_secret_dec, jwt_jwks),\n         :ok <- TenantRateLimiters.check_tenant(tenant) do\n      assigns = %RealtimeChannel.Assigns{\n        claims: claims,\n        jwt_secret: jwt_secret,\n        jwt_jwks: jwt_jwks,\n        tenant: external_id,\n        log_level: log_level,\n        tenant_token: token,\n        headers: opts.x_headers\n      }\n\n      assigns = Map.from_struct(assigns)\n\n      {:ok, assign(socket, assigns)}\n    else\n      nil ->\n        log_error(\"TenantNotFound\", \"Tenant not found: #{external_id}\")\n        {:error, :tenant_not_found}\n\n      %Tenant{suspend: true} ->\n        Logging.log_error(socket, \"RealtimeDisabledForTenant\", \"Realtime disabled for this tenant\")\n        {:error, :tenant_suspended}\n\n      {:error, :expired_token, msg} ->\n        Logging.maybe_log_warning(socket, \"InvalidJWTToken\", msg)\n        {:error, :expired_token}\n\n      {:error, :missing_claims} ->\n        msg = \"Fields `role` and `exp` are required in JWT\"\n        Logging.maybe_log_warning(socket, \"InvalidJWTToken\", msg)\n        {:error, :missing_claims}\n\n      {:error, :token_malformed} ->\n        log_error(\"MalformedJWT\", \"The token provided is not a valid JWT\")\n        {:error, :token_malformed}\n\n      {:error, :too_many_connections} ->\n        msg = \"Too many connected users\"\n        Logging.log_error(socket, \"ConnectionRateLimitReached\", msg)\n        {:error, :too_many_connections}\n\n      {:error, :too_many_joins} ->\n        msg = \"Too many joins per second\"\n        Logging.log_error(socket, \"JoinsRateLimitReached\", msg)\n        {:error, :too_many_joins}\n\n      error ->\n        log_error(\"ErrorConnectingToWebsocket\", error)\n        {:error, error}\n    end\n  end\n\n  defp access_token(params, headers) do\n    case :proplists.lookup(\"x-api-key\", headers) do\n      :none -> Map.get(params, \"apikey\")\n      {\"x-api-key\", token} -> token\n    end\n  end\n\n  defp log_level(params) do\n    case Map.get(params, \"log_level\") do\n      level when level in [\"info\", \"warning\", \"error\"] -> String.to_existing_atom(level)\n      _ -> @default_log_level\n    end\n  end\n\n  defp max_heap_size(), do: Application.fetch_env!(:realtime, :websocket_max_heap_size)\n  defp measure_traffic_interval_in_ms(), do: Application.fetch_env!(:realtime, :measure_traffic_interval_in_ms)\n\n  defp collect_traffic_telemetry(nil, _tenant_external_id, previous_recv, previous_send),\n    do: %{latest_recv: previous_recv, latest_send: previous_send}\n\n  defp collect_traffic_telemetry(transport_pid, tenant_external_id, previous_recv, previous_send) do\n    %{send_oct: latest_send, recv_oct: latest_recv} =\n      transport_pid\n      |> Process.info(:links)\n      |> then(fn {:links, links} -> links end)\n      |> Enum.filter(&is_port/1)\n      |> Enum.reduce(%{send_oct: 0, recv_oct: 0}, fn link, acc ->\n        case :inet.getstat(link, [:send_oct, :recv_oct]) do\n          {:ok, stats} ->\n            send_oct = Keyword.get(stats, :send_oct, 0)\n            recv_oct = Keyword.get(stats, :recv_oct, 0)\n\n            %{\n              send_oct: acc.send_oct + send_oct,\n              recv_oct: acc.recv_oct + recv_oct\n            }\n\n          {:error, _} ->\n            acc\n        end\n      end)\n\n    send_delta = max(0, latest_send - previous_send)\n    recv_delta = max(0, latest_recv - previous_recv)\n\n    :telemetry.execute([:realtime, :channel, :output_bytes], %{size: send_delta}, %{tenant: tenant_external_id})\n    :telemetry.execute([:realtime, :channel, :input_bytes], %{size: recv_delta}, %{tenant: tenant_external_id})\n\n    %{latest_recv: latest_recv, latest_send: latest_send}\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/controllers/broadcast_controller.ex",
    "content": "defmodule RealtimeWeb.BroadcastController do\n  use RealtimeWeb, :controller\n  use OpenApiSpex.ControllerSpecs\n  require Logger\n\n  alias Realtime.Tenants.BatchBroadcast\n  alias RealtimeWeb.OpenApiSchemas.EmptyResponse\n  alias RealtimeWeb.OpenApiSchemas.TenantBatchParams\n  alias RealtimeWeb.OpenApiSchemas.TooManyRequestsResponse\n  alias RealtimeWeb.OpenApiSchemas.UnprocessableEntityResponse\n\n  action_fallback(RealtimeWeb.FallbackController)\n\n  operation(:broadcast,\n    summary: \"Broadcasts a batch of messages\",\n    parameters: [\n      token: [\n        in: :header,\n        name: \"Authorization\",\n        schema: %OpenApiSpex.Schema{type: :string},\n        required: true,\n        example:\n          \"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY\"\n      ]\n    ],\n    request_body: TenantBatchParams.params(),\n    responses: %{\n      202 => EmptyResponse.response(),\n      403 => EmptyResponse.response(),\n      422 => UnprocessableEntityResponse.response(),\n      429 => TooManyRequestsResponse.response()\n    }\n  )\n\n  def broadcast(%{assigns: %{tenant: tenant}} = conn, attrs) do\n    with :ok <- BatchBroadcast.broadcast(conn, tenant, attrs) do\n      send_resp(conn, :accepted, \"\")\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/controllers/fallback_controller.ex",
    "content": "defmodule RealtimeWeb.FallbackController do\n  @moduledoc \"\"\"\n  Translates controller action results into valid `Plug.Conn` responses.\n\n  See `Phoenix.Controller.action_fallback/1` for more details.\n  \"\"\"\n\n  use RealtimeWeb, :controller\n  use Realtime.Logs\n\n  import RealtimeWeb.ErrorHelpers\n\n  def call(conn, {:error, :not_found}) do\n    log_error(\"TenantNotFound\", \"Tenant not found\")\n\n    conn\n    |> put_status(:not_found)\n    |> put_view(RealtimeWeb.ErrorView)\n    |> render(\"error.json\", message: \"not found\")\n  end\n\n  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do\n    log_error(\n      \"UnprocessableEntity\",\n      Ecto.Changeset.traverse_errors(changeset, &translate_error/1)\n    )\n\n    conn\n    |> put_status(:unprocessable_entity)\n    |> put_view(RealtimeWeb.ChangesetView)\n    |> render(\"error.json\", changeset: changeset)\n  end\n\n  def call(conn, {:error, status, message}) when is_atom(status) and is_binary(message) do\n    log_error(\"UnprocessableEntity\", message)\n\n    conn\n    |> put_status(status)\n    |> put_view(RealtimeWeb.ErrorView)\n    |> render(\"error.json\", message: message)\n  end\n\n  def call(conn, {:error, %Ecto.Changeset{valid?: false} = changeset}) do\n    log_error(\n      \"UnprocessableEntity\",\n      Ecto.Changeset.traverse_errors(changeset, &translate_error/1)\n    )\n\n    conn\n    |> put_status(:unprocessable_entity)\n    |> put_view(RealtimeWeb.ChangesetView)\n    |> render(\"error.json\", changeset: changeset)\n  end\n\n  def call(conn, {:error, _}) do\n    conn\n    |> put_status(:unauthorized)\n    |> put_view(RealtimeWeb.ErrorView)\n    |> render(\"error.json\", message: \"Unauthorized\")\n  end\n\n  def call(conn, %Ecto.Changeset{valid?: false} = changeset) do\n    log_error(\n      \"UnprocessableEntity\",\n      Ecto.Changeset.traverse_errors(changeset, &translate_error/1)\n    )\n\n    conn\n    |> put_status(:unprocessable_entity)\n    |> put_view(RealtimeWeb.ChangesetView)\n    |> render(\"error.json\", changeset: changeset)\n  end\n\n  def call(conn, response) do\n    log_error(\"UnknownErrorOnController\", response)\n\n    conn\n    |> put_status(:unprocessable_entity)\n    |> put_view(RealtimeWeb.ErrorView)\n    |> render(\"error.json\", message: \"Unknown error\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/controllers/legacy_metrics_controller.ex",
    "content": "defmodule RealtimeWeb.LegacyMetricsController do\n  use RealtimeWeb, :controller\n  require Logger\n  alias Realtime.PromEx\n  alias Realtime.TenantPromEx\n  alias Realtime.GenRpc\n\n  def index(conn, _) do\n    serve_metrics(conn, [Node.self() | Node.list()], \"combined cluster\")\n  end\n\n  def region(conn, %{\"region\" => region}) do\n    serve_metrics(conn, Realtime.Nodes.region_nodes(region), \"combined region=#{region}\")\n  end\n\n  def get_combined_metrics do\n    bump_max_heap_size()\n    [PromEx.get_global_metrics(), TenantPromEx.get_metrics()]\n  end\n\n  defp serve_metrics(conn, nodes, label) do\n    conn =\n      conn\n      |> put_resp_content_type(\"text/plain\")\n      |> send_chunked(200)\n\n    {time, conn} = :timer.tc(fn -> collect_metrics(nodes, conn) end, :millisecond)\n    Logger.info(\"Collected #{label} metrics in #{time} milliseconds\")\n\n    conn\n  end\n\n  defp collect_metrics(nodes, conn) do\n    bump_max_heap_size()\n    timeout = Application.fetch_env!(:realtime, :metrics_rpc_timeout)\n\n    nodes\n    |> Task.async_stream(\n      fn node ->\n        {node, GenRpc.call(node, __MODULE__, :get_combined_metrics, [], timeout: timeout)}\n      end,\n      timeout: :infinity\n    )\n    |> Enum.reduce(conn, fn\n      {:ok, {node, {:error, :rpc_error, reason}}}, acc_conn ->\n        Logger.error(\"Cannot fetch metrics from the node #{inspect(node)} because #{inspect(reason)}\")\n        acc_conn\n\n      {:ok, {_node, metrics}}, acc_conn ->\n        case chunk(acc_conn, metrics) do\n          {:ok, acc_conn} ->\n            :erlang.garbage_collect()\n            acc_conn\n\n          {:error, reason} ->\n            Logger.error(\"Cannot stream metrics chunk because #{inspect(reason)}\")\n            acc_conn\n        end\n\n      {:exit, reason}, acc_conn ->\n        Logger.error(\"Metrics collection task exited: #{inspect(reason)}\")\n        acc_conn\n    end)\n  end\n\n  defp bump_max_heap_size do\n    system_max_heap_size = :erlang.system_info(:max_heap_size)[:size]\n\n    if is_integer(system_max_heap_size) and system_max_heap_size > 0 do\n      Process.flag(:max_heap_size, system_max_heap_size * 3)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/controllers/metrics_controller.ex",
    "content": "defmodule RealtimeWeb.MetricsController do\n  use RealtimeWeb, :controller\n  require Logger\n  alias Realtime.PromEx\n  alias Realtime.TenantPromEx\n  alias Realtime.GenRpc\n\n  def index(conn, _) do\n    serve_metrics(conn, [Node.self() | Node.list()], :get_global_metrics, \"global cluster\")\n  end\n\n  def tenant(conn, _) do\n    serve_metrics(conn, [Node.self() | Node.list()], :get_tenant_metrics, \"tenant cluster\")\n  end\n\n  def region(conn, %{\"region\" => region}) do\n    serve_metrics(conn, Realtime.Nodes.region_nodes(region), :get_global_metrics, \"global region=#{region}\")\n  end\n\n  def region_tenant(conn, %{\"region\" => region}) do\n    serve_metrics(conn, Realtime.Nodes.region_nodes(region), :get_tenant_metrics, \"tenant region=#{region}\")\n  end\n\n  defp serve_metrics(conn, nodes, metrics_fun, label) do\n    conn =\n      conn\n      |> put_resp_content_type(\"text/plain\")\n      |> send_chunked(200)\n\n    {time, conn} = :timer.tc(fn -> collect_metrics(nodes, metrics_fun, conn) end, :millisecond)\n    Logger.info(\"Collected #{label} metrics in #{time} milliseconds\")\n\n    conn\n  end\n\n  defp collect_metrics(nodes, metrics_fun, conn) do\n    bump_max_heap_size()\n    timeout = Application.fetch_env!(:realtime, :metrics_rpc_timeout)\n\n    nodes\n    |> Task.async_stream(\n      fn node ->\n        {node, GenRpc.call(node, __MODULE__, metrics_fun, [], timeout: timeout)}\n      end,\n      timeout: :infinity\n    )\n    |> Enum.reduce(conn, fn {_, {node, response}}, acc_conn ->\n      case response do\n        {:error, :rpc_error, reason} ->\n          Logger.error(\"Cannot fetch metrics from the node #{inspect(node)} because #{inspect(reason)}\")\n          acc_conn\n\n        metrics ->\n          {:ok, acc_conn} = chunk(acc_conn, metrics)\n          :erlang.garbage_collect()\n          acc_conn\n      end\n    end)\n  end\n\n  def get_global_metrics do\n    bump_max_heap_size()\n    PromEx.get_global_metrics()\n  end\n\n  def get_tenant_metrics do\n    bump_max_heap_size()\n    TenantPromEx.get_metrics()\n  end\n\n  @doc deprecated: \"Use get_global_metrics/0 instead\"\n  def get_metrics, do: get_global_metrics()\n\n  defp bump_max_heap_size do\n    system_max_heap_size = :erlang.system_info(:max_heap_size)[:size]\n\n    if is_integer(system_max_heap_size) and system_max_heap_size > 0 do\n      Process.flag(:max_heap_size, system_max_heap_size * 3)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/controllers/page_controller.ex",
    "content": "defmodule RealtimeWeb.PageController do\n  use RealtimeWeb, :controller\n\n  def index(conn, _params) do\n    render(conn, \"index.html\")\n  end\n\n  def healthcheck(conn, _params) do\n    conn\n    |> put_status(:ok)\n    |> text(\"ok\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/controllers/ping_controller.ex",
    "content": "defmodule RealtimeWeb.PingController do\n  use RealtimeWeb, :controller\n\n  def ping(conn, _params) do\n    json(conn, %{message: \"Success\"})\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/controllers/tenant_controller.ex",
    "content": "defmodule RealtimeWeb.TenantController do\n  use RealtimeWeb, :controller\n  use OpenApiSpex.ControllerSpecs\n\n  use Realtime.Logs\n\n  import Realtime.Logs\n\n  alias Realtime.Api\n  alias Realtime.Api.Tenant\n  alias Realtime.Database\n  alias Realtime.PostgresCdc\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Cache\n  alias Realtime.Tenants.Connect\n  alias Realtime.Tenants.Migrations\n  alias RealtimeWeb.OpenApiSchemas.EmptyResponse\n  alias RealtimeWeb.OpenApiSchemas.ErrorResponse\n  alias RealtimeWeb.OpenApiSchemas.NotFoundResponse\n  alias RealtimeWeb.OpenApiSchemas.TenantHealthResponse\n  alias RealtimeWeb.OpenApiSchemas.TenantParams\n  alias RealtimeWeb.OpenApiSchemas.TenantResponse\n  alias RealtimeWeb.OpenApiSchemas.TenantResponseList\n  alias RealtimeWeb.OpenApiSchemas.UnauthorizedResponse\n  alias RealtimeWeb.SocketDisconnect\n\n  @stop_timeout 10_000\n\n  action_fallback(RealtimeWeb.FallbackController)\n\n  plug :set_observability_attributes when action in [:show, :edit, :update, :delete, :reload, :shutdown, :health]\n\n  operation(:index,\n    summary: \"List tenants\",\n    parameters: [\n      authorization: [\n        in: :header,\n        name: \"Authorization\",\n        schema: %OpenApiSpex.Schema{type: :string},\n        required: true,\n        example:\n          \"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY\"\n      ]\n    ],\n    responses: %{\n      200 => TenantResponseList.response(),\n      403 => EmptyResponse.response()\n    }\n  )\n\n  def index(conn, _params) do\n    tenants = Api.list_tenants()\n    render(conn, \"index.json\", tenants: tenants)\n  end\n\n  operation(:show,\n    summary: \"Fetch tenant\",\n    parameters: [\n      token: [\n        in: :header,\n        name: \"Authorization\",\n        schema: %OpenApiSpex.Schema{type: :string},\n        required: true,\n        example:\n          \"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY\"\n      ],\n      tenant_id: [in: :path, description: \"Tenant ID\", type: :string]\n    ],\n    responses: %{\n      200 => TenantResponse.response(),\n      403 => EmptyResponse.response(),\n      404 => NotFoundResponse.response()\n    }\n  )\n\n  def show(conn, %{\"tenant_id\" => id}) do\n    tenant = Api.get_tenant_by_external_id(id)\n\n    case tenant do\n      %Tenant{} = tenant -> render(conn, \"show.json\", tenant: tenant)\n      nil -> {:error, :not_found}\n    end\n  end\n\n  operation(:create,\n    summary: \"Create or update tenant\",\n    parameters: [\n      token: [\n        in: :header,\n        name: \"Authorization\",\n        schema: %OpenApiSpex.Schema{type: :string},\n        required: true,\n        example:\n          \"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY\"\n      ]\n    ],\n    request_body: TenantParams.params(),\n    responses: %{\n      200 => TenantResponse.response(),\n      403 => EmptyResponse.response()\n    }\n  )\n\n  @spec create(any(), map()) :: any()\n  def create(conn, %{\"tenant\" => params}) do\n    external_id = Map.get(params, \"external_id\")\n\n    case Tenant.changeset(%Tenant{}, params) do\n      %{valid?: true} -> update(conn, %{\"tenant_id\" => external_id, \"tenant\" => params})\n      changeset -> changeset\n    end\n  end\n\n  operation(:update,\n    summary: \"Create or update tenant\",\n    parameters: [\n      token: [\n        in: :header,\n        name: \"Authorization\",\n        schema: %OpenApiSpex.Schema{type: :string},\n        required: true,\n        example:\n          \"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY\"\n      ],\n      tenant_id: [in: :path, description: \"Tenant ID\", type: :string]\n    ],\n    request_body: TenantParams.params(),\n    responses: %{\n      200 => TenantResponse.response(),\n      403 => EmptyResponse.response()\n    }\n  )\n\n  def update(conn, %{\"tenant_id\" => external_id, \"tenant\" => tenant_params}) do\n    tenant = Api.get_tenant_by_external_id(external_id, use_replica?: false)\n\n    case tenant do\n      nil ->\n        tenant_params = tenant_params |> Map.put(\"external_id\", external_id) |> Map.put(\"name\", external_id)\n\n        extensions =\n          Enum.reduce(tenant_params[\"extensions\"], [], fn\n            %{\"type\" => type, \"settings\" => settings}, acc -> [%{\"type\" => type, \"settings\" => settings} | acc]\n            _e, acc -> acc\n          end)\n\n        with {:ok, %Tenant{} = tenant} <- Api.create_tenant(%{tenant_params | \"extensions\" => extensions}),\n             res when res in [:ok, :noop] <- Migrations.run_migrations(tenant) do\n          Logger.metadata(external_id: tenant.external_id, project: tenant.external_id)\n\n          conn\n          |> put_status(:created)\n          |> put_resp_header(\"location\", Routes.tenant_path(conn, :show, tenant))\n          |> render(\"show.json\", tenant: tenant)\n        end\n\n      tenant ->\n        with {:ok, %Tenant{} = tenant} <- Api.update_tenant_by_external_id(tenant.external_id, tenant_params) do\n          conn\n          |> put_status(:ok)\n          |> put_resp_header(\"location\", Routes.tenant_path(conn, :show, tenant))\n          |> render(\"show.json\", tenant: tenant)\n        end\n    end\n  end\n\n  operation(:delete,\n    summary: \"Delete tenant\",\n    parameters: [\n      token: [\n        in: :header,\n        name: \"Authorization\",\n        schema: %OpenApiSpex.Schema{type: :string},\n        required: true,\n        example:\n          \"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY\"\n      ],\n      tenant_id: [in: :path, description: \"Tenant ID\", type: :string]\n    ],\n    responses: %{\n      204 => EmptyResponse.response(),\n      403 => UnauthorizedResponse.response(),\n      500 => ErrorResponse.response()\n    }\n  )\n\n  def delete(conn, %{\"tenant_id\" => tenant_id}) do\n    stop_all_timeout = Enum.count(PostgresCdc.available_drivers()) * 1_000\n\n    with %Tenant{} = tenant <- Api.get_tenant_by_external_id(tenant_id, use_replica: false),\n         _ <- Tenants.suspend_tenant_by_external_id(tenant_id),\n         true <- Api.delete_tenant_by_external_id(tenant_id),\n         :ok <- Cache.distributed_invalidate_tenant_cache(tenant_id),\n         :ok <- PostgresCdc.stop_all(tenant, stop_all_timeout),\n         :ok <- Database.replication_slot_teardown(tenant) do\n      send_resp(conn, 204, \"\")\n    else\n      nil ->\n        send_resp(conn, 204, \"\")\n\n      err ->\n        log_error(\"UnableToDeleteTenant\", err)\n        conn |> put_status(500) |> json(err) |> halt()\n    end\n  end\n\n  operation(:reload,\n    summary: \"Reload tenant\",\n    parameters: [\n      token: [\n        in: :header,\n        name: \"Authorization\",\n        schema: %OpenApiSpex.Schema{type: :string},\n        required: true,\n        example:\n          \"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY\"\n      ],\n      tenant_id: [in: :path, description: \"Tenant ID\", type: :string]\n    ],\n    responses: %{\n      204 => EmptyResponse.response(),\n      403 => EmptyResponse.response(),\n      404 => NotFoundResponse.response()\n    }\n  )\n\n  def reload(conn, %{\"tenant_id\" => tenant_id}) do\n    case Api.get_tenant_by_external_id(tenant_id, use_replica?: false) do\n      nil ->\n        {:error, :not_found}\n\n      tenant ->\n        PostgresCdc.stop_all(tenant, @stop_timeout)\n        Connect.shutdown(tenant.external_id)\n        SocketDisconnect.disconnect(tenant.external_id)\n        send_resp(conn, 204, \"\")\n    end\n  end\n\n  operation(:shutdown,\n    summary: \"Shutdowns the Connect module for a tenant\",\n    parameters: [\n      token: [\n        in: :header,\n        name: \"Authorization\",\n        schema: %OpenApiSpex.Schema{type: :string},\n        required: true,\n        example:\n          \"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY\"\n      ],\n      tenant_id: [in: :path, description: \"Tenant ID\", type: :string]\n    ],\n    responses: %{\n      204 => EmptyResponse.response(),\n      403 => EmptyResponse.response(),\n      404 => NotFoundResponse.response()\n    }\n  )\n\n  def shutdown(conn, %{\"tenant_id\" => tenant_id}) do\n    case Api.get_tenant_by_external_id(tenant_id, use_replica?: false) do\n      nil ->\n        {:error, :not_found}\n\n      tenant ->\n        Connect.shutdown(tenant.external_id)\n        send_resp(conn, 204, \"\")\n    end\n  end\n\n  operation(:health,\n    summary: \"Tenant health\",\n    parameters: [\n      token: [\n        in: :header,\n        name: \"Authorization\",\n        schema: %OpenApiSpex.Schema{type: :string},\n        required: true,\n        example:\n          \"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY\"\n      ],\n      tenant_id: [in: :path, description: \"Tenant ID\", type: :string]\n    ],\n    responses: %{\n      200 => TenantHealthResponse.response(),\n      403 => EmptyResponse.response(),\n      404 => NotFoundResponse.response()\n    }\n  )\n\n  def health(conn, %{\"tenant_id\" => tenant_id}) do\n    case Tenants.health_check(tenant_id) do\n      {:ok, response} -> json(conn, %{data: response})\n      {:error, %{healthy: false} = response} -> json(conn, %{data: response})\n      {:error, :tenant_not_found} -> {:error, :not_found}\n    end\n  end\n\n  defp set_observability_attributes(conn, _opts) do\n    tenant_id = conn.path_params[\"tenant_id\"]\n    OpenTelemetry.Tracer.set_attributes(external_id: tenant_id)\n    Logger.metadata(external_id: tenant_id, project: tenant_id)\n\n    conn\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/dashboard/process_dump.ex",
    "content": "defmodule Realtime.Dashboard.ProcessDump do\n  @moduledoc \"\"\"\n  Live Dashboard page to dump the current processes tree\n  \"\"\"\n  use Phoenix.LiveDashboard.PageBuilder\n\n  @impl true\n  def menu_link(_, _) do\n    {:ok, \"Process Dump\"}\n  end\n\n  @impl true\n  def mount(_, _, socket) do\n    ts = :os.system_time(:millisecond)\n    name = \"process_dump_#{ts}\"\n    content = dump_processes(name)\n    {:ok, socket |> assign(content: content) |> assign(name: name)}\n  end\n\n  @impl true\n  def render(assigns) do\n    ~H\"\"\"\n    <div class=\"prose\">\n      <h1>Process Dump</h1>\n      <a download={\"#{@name}.tar.gz\"} href={\"data:application/x-compressed;base64,#{@content}\"}>\n        Download\n      </a>\n      <br />After you untar the file, you can use `File.read!(\"filename\") |> :erlang.binary_to_term` to check the contents\n    </div>\n    \"\"\"\n  end\n\n  defp dump_processes(name) do\n    term = Process.list() |> Enum.map(&Process.info/1) |> :erlang.term_to_binary()\n    path = \"/tmp/#{name}\"\n    File.write!(path, term)\n    System.cmd(\"tar\", [\"-czf\", \"#{path}.tar.gz\", path])\n    \"#{path}.tar.gz\" |> File.read!() |> Base.encode64()\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/dashboard/tenant_info.ex",
    "content": "defmodule Realtime.Dashboard.TenantInfo do\n  @moduledoc \"\"\"\n  Live Dashboard page to inspect tenant and extension information by project ref.\n  Secrets (jwt_secret and encrypted extension fields) are never displayed.\n  \"\"\"\n  use Phoenix.LiveDashboard.PageBuilder\n\n  alias Realtime.Api\n  alias Realtime.Crypto\n\n  @impl true\n  def menu_link(_, _), do: {:ok, \"Tenant Info\"}\n\n  @impl true\n  def mount(_, _, socket) do\n    {:ok, assign(socket, project_ref: \"\", tenant: nil, error: nil)}\n  end\n\n  @impl true\n  def handle_event(\"lookup\", %{\"project_ref\" => ref}, socket) do\n    ref = String.trim(ref)\n\n    case Api.get_tenant_by_external_id(ref) do\n      nil -> {:noreply, assign(socket, project_ref: ref, tenant: nil, error: \"Tenant not found\")}\n      tenant -> {:noreply, assign(socket, project_ref: ref, tenant: prepare_tenant(tenant), error: nil)}\n    end\n  end\n\n  @impl true\n  def render(assigns) do\n    ~H\"\"\"\n    <div class=\"phx-dashboard-section\">\n      <h5 class=\"card-title\">Tenant Info</h5>\n\n      <form phx-submit=\"lookup\" class=\"mb-4 d-flex gap-2\">\n        <input\n          type=\"text\"\n          name=\"project_ref\"\n          value={@project_ref}\n          placeholder=\"Enter project ref\"\n          class=\"form-control w-auto\"\n          autocomplete=\"off\"\n        />\n        <button type=\"submit\" class=\"btn btn-primary\">Lookup</button>\n      </form>\n\n      <%= if @error do %>\n        <p class=\"text-danger\"><%= @error %></p>\n      <% end %>\n\n      <%= if @tenant do %>\n        <h6 class=\"mt-4\">Tenant</h6>\n        <table class=\"table table-hover\">\n          <tbody>\n            <tr><td>external_id</td><td><%= @tenant.external_id %></td></tr>\n            <tr><td>name</td><td><%= @tenant.name %></td></tr>\n            <tr><td>suspend</td><td><%= @tenant.suspend %></td></tr>\n            <tr><td>private_only</td><td><%= @tenant.private_only %></td></tr>\n            <tr><td>presence_enabled</td><td><%= @tenant.presence_enabled %></td></tr>\n            <tr><td>postgres_cdc_default</td><td><%= @tenant.postgres_cdc_default %></td></tr>\n            <tr><td>broadcast_adapter</td><td><%= @tenant.broadcast_adapter %></td></tr>\n            <tr><td>max_concurrent_users</td><td><%= @tenant.max_concurrent_users %></td></tr>\n            <tr><td>max_events_per_second</td><td><%= @tenant.max_events_per_second %></td></tr>\n            <tr><td>max_bytes_per_second</td><td><%= @tenant.max_bytes_per_second %></td></tr>\n            <tr><td>max_channels_per_client</td><td><%= @tenant.max_channels_per_client %></td></tr>\n            <tr><td>max_joins_per_second</td><td><%= @tenant.max_joins_per_second %></td></tr>\n            <tr><td>max_presence_events_per_second</td><td><%= @tenant.max_presence_events_per_second %></td></tr>\n            <tr><td>max_payload_size_in_kb</td><td><%= @tenant.max_payload_size_in_kb %></td></tr>\n            <tr><td>max_client_presence_events_per_window</td><td><%= @tenant.max_client_presence_events_per_window %></td></tr>\n            <tr><td>client_presence_window_ms</td><td><%= @tenant.client_presence_window_ms %></td></tr>\n            <tr><td>migrations_ran</td><td><%= @tenant.migrations_ran %></td></tr>\n            <tr><td>inserted_at</td><td><%= @tenant.inserted_at %></td></tr>\n            <tr><td>updated_at</td><td><%= @tenant.updated_at %></td></tr>\n          </tbody>\n        </table>\n\n        <%= for ext <- @tenant.extensions do %>\n          <h6 class=\"mt-4\">Extension: <%= ext.type %></h6>\n          <table class=\"table table-hover\">\n            <tbody>\n              <%= for {key, value} <- ext.settings do %>\n                <tr><td><%= key %></td><td><%= value %></td></tr>\n              <% end %>\n              <tr><td>inserted_at</td><td><%= ext.inserted_at %></td></tr>\n              <tr><td>updated_at</td><td><%= ext.updated_at %></td></tr>\n            </tbody>\n          </table>\n        <% end %>\n      <% end %>\n    </div>\n    \"\"\"\n  end\n\n  @secret_settings [\"db_password\"]\n  @encrypted_settings [\"db_host\", \"db_port\", \"db_name\", \"db_user\"]\n\n  defp prepare_tenant(tenant) do\n    %{tenant | extensions: Enum.map(tenant.extensions, &prepare_extension/1)}\n  end\n\n  defp prepare_extension(ext) do\n    settings =\n      ext.settings\n      |> Map.drop(@secret_settings)\n      |> Enum.map(fn\n        {key, value} when key in @encrypted_settings -> {key, Crypto.decrypt!(value)}\n        {key, value} -> {key, value}\n      end)\n      |> Enum.sort_by(&elem(&1, 0))\n\n    %{ext | settings: settings}\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/endpoint.ex",
    "content": "defmodule RealtimeWeb.Endpoint do\n  use Phoenix.Endpoint, otp_app: :realtime\n  alias RealtimeWeb.Plugs.BaggageRequestId\n\n  # The session will be stored in the cookie and signed,\n  # this means its contents can be read but not tampered with.\n  # Set :encryption_salt if you would also like to encrypt it.\n  @session_options [\n    store: :cookie,\n    key: \"_realtime_key\",\n    signing_salt: \"5OUq5X4H\"\n  ]\n\n  @fullsweep_after Application.compile_env!(:realtime, :websocket_fullsweep_after)\n\n  socket \"/socket\", RealtimeWeb.UserSocket,\n    websocket: [\n      connect_info: [:peer_data, :uri, :x_headers],\n      fullsweep_after: @fullsweep_after,\n      max_frame_size: 5_000_000,\n      # https://github.com/ninenines/cowboy/blob/24d32de931a0c985ff7939077463fc8be939f0e9/doc/src/manual/cowboy_websocket.asciidoc#L228\n      # active_n: The number of packets Cowboy will request from the socket at once.\n      # This can be used to tweak the performance of the server. Higher values reduce\n      # the number of times Cowboy need to request more packets from the port driver at\n      # the expense of potentially higher memory being used.\n      active_n: 100,\n      # Skip validating UTF8 for faster frame processing.\n      # Currently all text frames are handled only with JSON which already requires UTF-8\n      validate_utf8: false,\n      serializer: [\n        {Phoenix.Socket.V1.JSONSerializer, \"~> 1.0.0\"},\n        {RealtimeWeb.Socket.V2Serializer, \"~> 2.0.0\"}\n      ]\n    ],\n    longpoll: [\n      connect_info: [:peer_data, :uri, :x_headers],\n      serializer: [\n        {Phoenix.Socket.V1.JSONSerializer, \"~> 1.0.0\"},\n        {Phoenix.Socket.V2.JSONSerializer, \"~> 2.0.0\"}\n      ]\n    ]\n\n  socket \"/live\", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]\n\n  # Serve at \"/\" the static files from \"priv/static\" directory.\n  #\n  # You should set gzip to true if you are running phx.digest\n  # when deploying your static files in production.\n  plug Plug.Static,\n    at: \"/\",\n    from: :realtime,\n    gzip: false,\n    only: RealtimeWeb.static_paths()\n\n  # plug PromEx.Plug, path: \"/metrics\", prom_ex_module: Realtime.PromEx\n\n  # Code reloading can be explicitly enabled under the\n  # :code_reloader configuration of your endpoint.\n  if code_reloading? do\n    socket \"/phoenix/live_reload/socket\", Phoenix.LiveReloader.Socket\n    plug Phoenix.LiveReloader\n    plug Phoenix.CodeReloader\n  end\n\n  plug Phoenix.LiveDashboard.RequestLogger,\n    param_key: \"request_logger\",\n    cookie_key: \"request_logger\"\n\n  plug BaggageRequestId, baggage_key: BaggageRequestId.baggage_key()\n\n  plug Plug.Telemetry,\n    event_prefix: [:phoenix, :endpoint],\n    log: {__MODULE__, :log_level, []}\n\n  # Disables logging for routes /healthcheck and /api/tenants/:tenant_id/health when DISABLE_HEALTHCHECK_LOGGING=true\n  def log_level(%{path_info: [\"healthcheck\"]}) do\n    if Application.get_env(:realtime, :disable_healthcheck_logging, false), do: false, else: :info\n  end\n\n  def log_level(%{path_info: [\"api\", \"tenants\", _, \"health\"]}) do\n    if Application.get_env(:realtime, :disable_healthcheck_logging, false), do: false, else: :info\n  end\n\n  def log_level(_), do: :info\n\n  plug Plug.Parsers,\n    parsers: [:urlencoded, :multipart, :json],\n    pass: [\"*/*\"],\n    json_decoder: Phoenix.json_library()\n\n  plug Plug.MethodOverride\n  plug Plug.Head\n  plug Plug.Session, @session_options\n  plug RealtimeWeb.Router\nend\n"
  },
  {
    "path": "lib/realtime_web/gettext.ex",
    "content": "defmodule RealtimeWeb.Gettext do\n  @moduledoc \"\"\"\n  A module providing Internationalization with a gettext-based API.\n\n  By using [Gettext](https://hexdocs.pm/gettext),\n  your module gains a set of macros for translations, for example:\n\n      import RealtimeWeb.Gettext\n\n      # Simple translation\n      gettext(\"Here is the string to translate\")\n\n      # Plural translation\n      ngettext(\"Here is the string to translate\",\n               \"Here are the strings to translate\",\n               3)\n\n      # Domain-based translation\n      dgettext(\"errors\", \"Here is the error message to translate\")\n\n  See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.\n  \"\"\"\n  use Gettext.Backend, otp_app: :realtime\nend\n"
  },
  {
    "path": "lib/realtime_web/live/components.ex",
    "content": "defmodule RealtimeWeb.Components do\n  @moduledoc \"\"\"\n  Components for LiveView\n  \"\"\"\n\n  use Phoenix.Component\n  alias Phoenix.HTML.Form\n  alias Phoenix.LiveView.JS\n\n  @doc \"\"\"\n  Renders an h1 tag.\n  ## Examples\n      <.h1>My Header</.h1>\n  \"\"\"\n  slot(:inner_block, required: true)\n\n  def h1(assigns) do\n    ~H\"\"\"\n    <h1 class=\"mb-5 flex items-center text-2xl font-semibold leading-6 text-brand\">\n      <%= render_slot(@inner_block) %>\n    </h1>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders an h2 tag.\n  ## Examples\n      <.h2>My Header</.h2>\n  \"\"\"\n  slot(:inner_block, required: true)\n\n  def h2(assigns) do\n    ~H\"\"\"\n    <h2 class=\"mb-5 flex items-center text-lg font-semibold leading-6 text-brand\">\n      <%= render_slot(@inner_block) %>\n    </h2>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders an h3 tag.\n  ## Examples\n      <.h3>My Header</.h3>\n  \"\"\"\n  slot(:inner_block, required: true)\n\n  def h3(assigns) do\n    ~H\"\"\"\n    <h3 class=\"mb-5 flex items-center text-lg font-semibold leading-6 text-brand\">\n      <%= render_slot(@inner_block) %>\n    </h3>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders a button.\n  ## Examples\n      <.button>Send!</.button>\n      <.button phx-click=\"go\" class=\"ml-2\">Send!</.button>\n  \"\"\"\n  attr :type, :string, default: nil\n  attr :class, :string, default: nil\n  attr :rest, :global\n\n  slot(:inner_block, required: true)\n\n  def button(assigns) do\n    ~H\"\"\"\n    <button\n      type={@type}\n      class={[\n        \"bg-green-600 hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none\",\n        @class\n      ]}\n      {@rest}\n    >\n      <%= render_slot(@inner_block) %>\n    </button>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders a link as a button.\n  ## Examples\n      <.link_button>Send!</.link_button>\n  \"\"\"\n  attr :href, :string, default: \"#\"\n  attr :target, :string, default: \"\"\n  attr :rest, :global\n\n  slot(:inner_block, required: true)\n\n  def link_button(assigns) do\n    ~H\"\"\"\n    <.link\n      role=\"button\"\n      class=\"bg-green-600 hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none\"\n      href={@href}\n      target={@target}\n      {@rest}\n    >\n      <%= render_slot(@inner_block) %>\n    </.link>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders a link as a button.\n  ## Examples\n      <.link_button>Send!</.link_button>\n  \"\"\"\n  attr :href, :string, default: \"#\"\n  attr :target, :string, default: \"\"\n  attr :rest, :global\n\n  slot(:inner_block, required: true)\n\n  def gray_link_button(assigns) do\n    ~H\"\"\"\n    <.link\n      role=\"button\"\n      class=\"bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded focus:outline-none\"\n      href={@href}\n      target={@target}\n      {@rest}\n    >\n      <%= render_slot(@inner_block) %>\n    </.link>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders a link as a button, but optionally patches the browser history.\n  ## Examples\n      <.patch_button>Send!</.link_button>\n  \"\"\"\n  attr :patch, :string, default: \"#\"\n  attr :replace, :boolean, default: true\n  attr :target, :string, default: \"\"\n  attr :rest, :global\n\n  slot(:inner_block, required: true)\n\n  def patch_button(assigns) do\n    ~H\"\"\"\n    <.link\n      role=\"button\"\n      class=\"bg-green-600 hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none\"\n      patch={@patch}\n      replace={@replace}\n      target={@target}\n      {@rest}\n    >\n      <%= render_slot(@inner_block) %>\n    </.link>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders a modal.\n  ## Examples\n      <.modal id=\"confirm-modal\">\n        Are you sure?\n        <:confirm>OK</:confirm>\n        <:cancel>Cancel</:cancel>\n      </.modal>\n  JS commands may be passed to the `:on_cancel` and `on_confirm` attributes\n  for the caller to reactor to each button press, for example:\n      <.modal id=\"confirm\" on_confirm={JS.push(\"delete\")} on_cancel={JS.navigate(~p\"/posts\")}>\n        Are you sure you?\n        <:confirm>OK</:confirm>\n        <:cancel>Cancel</:cancel>\n      </.modal>\n  \"\"\"\n  attr :id, :string, required: true\n  attr :show, :boolean, default: false\n  attr :on_cancel, JS, default: %JS{}\n  attr :on_confirm, JS, default: %JS{}\n\n  slot(:inner_block, required: true)\n  slot(:title)\n  slot(:subtitle)\n  slot(:confirm)\n  slot(:cancel)\n\n  def modal(assigns) do\n    ~H\"\"\"\n    <div id={@id} phx-mounted={@show && show_modal(@id)} class=\"relative z-50 hidden\">\n      <div id={\"#{@id}-bg\"} class=\"fixed inset-0 bg-zinc-50/90 transition-opacity\" aria-hidden=\"true\" />\n      <div\n        class=\"fixed inset-0 overflow-y-auto\"\n        aria-labelledby={\"#{@id}-title\"}\n        aria-describedby={\"#{@id}-description\"}\n        role=\"dialog\"\n        aria-modal=\"true\"\n        tabindex=\"0\"\n      >\n        <div class=\"flex min-h-full items-center justify-center\">\n          <div class=\"w-full max-w-3xl p-4 sm:p-6 lg:py-8\">\n            <.focus_wrap\n              id={\"#{@id}-container\"}\n              phx-mounted={@show && show_modal(@id)}\n              phx-window-keydown={hide_modal(@on_cancel, @id)}\n              phx-key=\"escape\"\n              phx-click-away={hide_modal(@on_cancel, @id)}\n              class=\"hidden relative rounded-2xl bg-white p-14 shadow-lg shadow-zinc-700/10 ring-1 ring-gray-700/10 transition\"\n            >\n              <div class=\"absolute top-6 right-5\">\n                <button\n                  phx-click={hide_modal(@on_cancel, @id)}\n                  type=\"button\"\n                  class=\"-m-3 flex-none p-3 opacity-20 hover:opacity-40\"\n                  aria-label=\"Close\"\n                >\n                  x\n                </button>\n              </div>\n              <div id={\"#{@id}-content\"}>\n                <header :if={@title != []}>\n                  <h1 id={\"#{@id}-title\"} class=\"text-lg font-semibold leading-8 text-zinc-800\">\n                    <%= render_slot(@title) %>\n                  </h1>\n                  <p :if={@subtitle != []} class=\"mt-2 text-sm leading-6 text-zinc-600\">\n                    <%= render_slot(@subtitle) %>\n                  </p>\n                </header>\n                <%= render_slot(@inner_block) %>\n                <div :if={@confirm != [] or @cancel != []} class=\"ml-6 mb-4 flex items-center gap-5\">\n                  <.button\n                    :for={confirm <- @confirm}\n                    id={\"#{@id}-confirm\"}\n                    phx-click={@on_confirm}\n                    phx-disable-with\n                    class=\"py-2 px-3\"\n                  >\n                    <%= render_slot(confirm) %>\n                  </.button>\n                  <.link\n                    :for={cancel <- @cancel}\n                    phx-click={hide_modal(@on_cancel, @id)}\n                    class=\"text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700\"\n                  >\n                    <%= render_slot(cancel) %>\n                  </.link>\n                </div>\n              </div>\n            </.focus_wrap>\n          </div>\n        </div>\n      </div>\n    </div>\n    \"\"\"\n  end\n\n  attr :rest, :global\n\n  slot(:inner_block, required: true)\n\n  def badge(assigns) do\n    ~H\"\"\"\n    <div>\n      <span class=\"text-xs font-semibold inline-block uppercase py-[3px] px-[5px] rounded bg-gray-100\" {@rest}>\n        <%= render_slot(@inner_block) %>\n      </span>\n    </div>\n    \"\"\"\n  end\n\n  ## Forms\n\n  @doc \"\"\"\n  Renders a for field select dropdown.\n  ## Examples\n    <.select form={f} field={:order_by} list={@sort_fields} selected={:inserted_at}>\n  \"\"\"\n\n  attr :selected, :atom, required: true\n  attr :form, :atom, required: true\n  attr :field, :atom, required: true\n  attr :list, :list, required: true\n\n  slot(:inner_block, required: false)\n\n  def select(assigns) do\n    ~H\"\"\"\n    <%= Form.select(@form, @field, @list, selected: @selected, class: \"\n      my-1\n      block\n      w-full\n      rounded-md\n      border-gray-300\n      shadow-sm\n      focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n    \") %>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders a text_input for a form field\n  \"\"\"\n\n  attr :form, :atom, required: true\n  attr :field, :atom, required: true\n  attr :opts, :list, required: true\n\n  slot(:inner_block, required: false)\n\n  def text_input(assigns) do\n    class = ~s(\n      my-1\n      block\n      w-full\n      rounded-md\n      border-gray-300\n      shadow-sm\n      focus:border-indigo-300\n      focus:ring\n      focus:ring-indigo-200\n      focus:ring-opacity-50)\n\n    assigns = assign(assigns, :opts, assigns.opts ++ [class: class])\n\n    ~H\"\"\"\n    <%= Form.text_input(@form, @field, @opts) %>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders a label for a form field\n  \"\"\"\n\n  attr :form, :atom, required: true\n  attr :field, :atom, required: true\n\n  slot(:inner_block, required: false)\n\n  def label(assigns) do\n    ~H\"\"\"\n    <%= Form.label(@form, @field, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n    \"\"\"\n  end\n\n  ## JS Commands\n\n  def show(js \\\\ %JS{}, selector) do\n    JS.show(js,\n      to: selector,\n      time: 50,\n      transition:\n        {\"transition-all transform ease-out duration-300\", \"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\",\n         \"opacity-100 translate-y-0 sm:scale-100\"}\n    )\n  end\n\n  def hide(js \\\\ %JS{}, selector) do\n    JS.hide(js,\n      to: selector,\n      time: 50,\n      transition:\n        {\"transition-all transform ease-in duration-200\", \"opacity-100 translate-y-0 sm:scale-100\",\n         \"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\"}\n    )\n  end\n\n  def show_modal(js \\\\ %JS{}, id) when is_binary(id) do\n    js\n    |> JS.show(to: \"##{id}\")\n    |> JS.show(\n      to: \"##{id}-bg\",\n      transition: {\"transition-all transform ease-out duration-300\", \"opacity-0\", \"opacity-100\"}\n    )\n    |> show(\"##{id}-container\")\n    |> JS.focus_first(to: \"##{id}-content\")\n  end\n\n  def hide_modal(js \\\\ %JS{}, id) do\n    js\n    |> JS.hide(\n      to: \"##{id}-bg\",\n      transition: {\"transition-all transform ease-in duration-200\", \"opacity-100\", \"opacity-0\"}\n    )\n    |> hide(\"##{id}-container\")\n    |> JS.hide(to: \"##{id}\", transition: {\"block\", \"block\", \"hidden\"})\n    |> JS.pop_focus()\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/live/inspector_live/conn_component.ex",
    "content": "defmodule RealtimeWeb.InspectorLive.ConnComponent do\n  use RealtimeWeb, :live_component\n\n  defmodule Connection do\n    use Ecto.Schema\n    import Ecto.Changeset\n\n    schema \"f\" do\n      field(:log_level, :string, default: \"error\")\n      field(:token, :string)\n      field(:host, :string)\n      field(:project, :string)\n      field(:channel, :string, default: \"room_a\")\n      field(:schema, :string, default: \"public\")\n      field(:table, :string, default: \"*\")\n      field(:filter, :string)\n      field(:bearer, :string)\n      field(:enable_broadcast, :boolean, default: true)\n      field(:enable_presence, :boolean, default: false)\n      field(:enable_db_changes, :boolean, default: false)\n      field(:private_channel, :boolean, default: false)\n    end\n\n    def changeset(form, params \\\\ %{}) do\n      form\n      |> cast(params, [\n        :log_level,\n        :token,\n        :host,\n        :project,\n        :channel,\n        :schema,\n        :table,\n        :filter,\n        :bearer,\n        :enable_broadcast,\n        :enable_presence,\n        :enable_db_changes,\n        :private_channel\n      ])\n      |> validate_required([:channel])\n    end\n  end\n\n  @impl true\n  def mount(socket) do\n    changeset = Connection.changeset(%Connection{})\n\n    socket =\n      socket\n      |> assign(subscribed_state: \"Connect\")\n      |> assign(changeset: changeset)\n      |> assign(url_params: %{})\n\n    {:ok, socket}\n  end\n\n  @impl true\n  def update(assigns, socket) do\n    socket = assign(socket, assigns)\n\n    {:ok, socket}\n  end\n\n  @impl true\n  def handle_event(\n        \"validate\",\n        %{\"_target\" => [\"connection\", \"host\"], \"connection\" => conn},\n        socket\n      ) do\n    conn = Map.drop(conn, [\"project\"])\n\n    changeset = Connection.changeset(%Connection{}, conn)\n\n    socket =\n      socket\n      |> assign(changeset: changeset)\n      |> push_patch(\n        to: Routes.inspector_index_path(RealtimeWeb.Endpoint, :new, conn),\n        replace: true\n      )\n\n    {:noreply, socket}\n  end\n\n  def handle_event(\n        \"validate\",\n        %{\"_target\" => [\"connection\", \"project\"], \"connection\" => %{\"project\" => project} = conn},\n        socket\n      ) do\n    host = \"https://#{project}.supabase.co\"\n\n    conn = conn |> Map.put(\"host\", host) |> Map.put(\"project\", project)\n\n    changeset = Connection.changeset(%Connection{}, conn)\n\n    socket =\n      socket\n      |> assign(changeset: changeset)\n      |> push_patch(\n        to: Routes.inspector_index_path(RealtimeWeb.Endpoint, :new, conn),\n        replace: true\n      )\n\n    {:noreply, socket}\n  end\n\n  def handle_event(\"validate\", %{\"connection\" => conn}, socket) do\n    changeset = Connection.changeset(%Connection{}, conn)\n\n    socket =\n      socket\n      |> assign(changeset: changeset)\n      |> push_patch(\n        to: Routes.inspector_index_path(RealtimeWeb.Endpoint, :new, conn),\n        replace: true\n      )\n\n    {:noreply, socket}\n  end\n\n  def handle_event(\"connect\", %{\"connection\" => conn} = params, socket) do\n    send_share_url(conn)\n\n    socket =\n      socket\n      |> assign(subscribed_state: \"Connecting...\")\n      |> push_event(\"connect\", params)\n\n    {:noreply, socket}\n  end\n\n  def handle_event(\"disconnect\", _params, socket) do\n    socket =\n      socket\n      |> assign(subscribed_state: \"Connect\")\n      |> push_event(\"disconnect\", %{})\n\n    {:noreply, socket}\n  end\n\n  def handle_event(\"clear_local_storage\", _params, socket) do\n    socket =\n      socket\n      |> push_event(\"clear_local_storage\", %{})\n      |> push_patch(\n        to: Routes.inspector_index_path(RealtimeWeb.Endpoint, :new),\n        replace: true\n      )\n\n    {:noreply, socket}\n  end\n\n  def handle_event(\"local_storage\", _params, %{assigns: %{url_params: url_params}} = socket)\n      when url_params != %{} do\n    {:noreply, socket}\n  end\n\n  def handle_event(\n        \"local_storage\",\n        %{\n          \"channel\" => nil,\n          \"host\" => nil,\n          \"schema\" => nil,\n          \"table\" => nil,\n          \"token\" => nil,\n          \"filter\" => nil,\n          \"bearer\" => nil,\n          \"enable_presence\" => nil,\n          \"enable_db_changes\" => nil,\n          \"private_channel\" => nil\n        },\n        socket\n      ) do\n    {:noreply, socket}\n  end\n\n  def handle_event(\"local_storage\", %{\"log_level\" => nil} = params, socket) do\n    params = Map.drop(params, [\"log_level\"])\n    changeset = Connection.changeset(%Connection{}, params)\n\n    socket =\n      socket\n      |> assign(changeset: changeset)\n      |> push_patch(\n        to: Routes.inspector_index_path(RealtimeWeb.Endpoint, :new, params),\n        replace: true\n      )\n\n    {:noreply, socket}\n  end\n\n  def handle_event(\"local_storage\", params, socket) do\n    changeset = Connection.changeset(%Connection{}, params)\n\n    socket =\n      socket\n      |> assign(changeset: changeset)\n      |> push_patch(\n        to: Routes.inspector_index_path(RealtimeWeb.Endpoint, :new, params),\n        replace: true\n      )\n\n    {:noreply, socket}\n  end\n\n  def handle_event(\"cancel\", params, socket) do\n    changeset = Connection.changeset(%Connection{}, params)\n\n    {:noreply, assign(socket, changeset: changeset)}\n  end\n\n  defp send_share_url(conn) do\n    conn = Map.drop(conn, [\"token\"])\n    url = Routes.inspector_index_path(RealtimeWeb.Endpoint, :new, conn)\n    send(self(), {:share_url, url})\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/live/inspector_live/conn_component.html.heex",
    "content": "<div>\n  <.form\n    :let={f}\n    id=\"conn_form\"\n    for={@changeset}\n    phx-change=\"validate\"\n    class=\"bg-white rounded\"\n    phx-submit=\"connect\"\n    phx-target={@myself}\n  >\n    <div class=\"my-4\">\n      <%= label(f, :enable_broadcast, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= checkbox(f, :enable_broadcast, disabled: true, class: \"\n                    disabled:opacity-50\n                    my-1\n                    block\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :enable_broadcast) %>\n      <p class=\"text-gray-600 text-xs italic\">\n        Broadcast is always enabled when successfully connected to a Channel\n      </p>\n    </div>\n    <div class=\"mb-4\">\n      <%= label(f, :enable_presence, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= checkbox(f, :enable_presence, class: \"\n                    my-1\n                    block\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :enable_presence) %>\n      <p class=\"text-gray-600 text-xs italic\">Enable Presence on this Channel</p>\n    </div>\n    <div class=\"mb-4\">\n      <%= label(f, :enable_db_changes, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= checkbox(f, :enable_db_changes, class: \"\n                    my-1\n                    block\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :enable_db_changes) %>\n      <p class=\"text-gray-600 text-xs italic\">Enable Database Changes on this Channel</p>\n    </div>\n    <div class=\"mb-4\">\n      <%= label(f, :private_channel, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= checkbox(f, :private_channel, class: \"\n                    my-1\n                    block\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :private_channel) %>\n      <p class=\"text-gray-600 text-xs italic\">Is it using a channel?</p>\n    </div>\n    <div class=\"mb-4\">\n      <%= label(f, :project, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= text_input(f, :project, placeholder: \"project_ref\", class: \"\n                    my-1\n                    block\n                    w-full\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :project) %>\n      <p class=\"text-gray-600 text-xs italic\">Supabase platform project `Reference ID` or...</p>\n    </div>\n    <div class=\"mb-4\">\n      <%= label(f, :host, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= text_input(f, :host, placeholder: \"https://project_ref.supabase.co\", class: \"\n                    my-1\n                    block\n                    w-full\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :host) %>\n      <p class=\"text-gray-600 text-xs italic\">The host to connect to</p>\n    </div>\n    <div class=\"mb-4\">\n      <%= label(f, :channel, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= text_input(f, :channel, placeholder: \"room_a\", class: \"\n                    my-1\n                    block\n                    w-full\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :channel) %>\n      <p class=\"text-gray-600 text-xs italic\">The Channel to connect to</p>\n    </div>\n    <div class=\"mb-4\">\n      <%= label(f, :schema, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= text_input(f, :schema, placeholder: \"public\", class: \"\n                    my-1\n                    block\n                    w-full\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :schema) %>\n      <p class=\"text-gray-600 text-xs italic\">Listen to changes from tables in this schema</p>\n    </div>\n    <div class=\"mb-4\">\n      <%= label(f, :table, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= text_input(f, :table, placeholder: \"*\", class: \"\n                    my-1\n                    block\n                    w-full\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :table) %>\n      <p class=\"text-gray-600 text-xs italic\">Listen to changes from this table</p>\n    </div>\n    <div class=\"mb-4\">\n      <%= label(f, :filter, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= text_input(f, :filter, placeholder: \"body=eq.hey\", class: \"\n                    my-1\n                    block\n                    w-full\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :filter) %>\n      <p class=\"text-gray-600 text-xs italic\">Match records with a filter</p>\n    </div>\n    <div class=\"mb-4\">\n      <%= label(f, :log_level, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= select(f, :log_level, [\"debug\", \"info\", \"warning\", \"error\"], selected: \"info\", class: \"\n                    my-1\n                    block\n                    w-full\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :log_level) %>\n      <p class=\"text-gray-600 text-xs italic\">Set the backend log level for this connection</p>\n    </div>\n    <div class=\"mb-4\">\n      <%= label(f, :token, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= text_input(f, :token, value: Ecto.Changeset.get_field(@changeset, :token), class: \"\n                    my-1\n                    block\n                    w-full\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :token) %>\n      <p class=\"text-gray-600 text-xs italic\">Your Supabase `anon` or `service_role` key</p>\n    </div>\n    <div class=\"mb-4\">\n      <%= label(f, :bearer, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n      <%= text_input(f, :bearer, value: Ecto.Changeset.get_field(@changeset, :bearer), class: \"\n                    my-1\n                    block\n                    w-full\n                    rounded-md\n                    border-gray-300\n                    shadow-sm\n                    focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                  \") %>\n      <%= error_tag(f, :bearer) %>\n      <p class=\"text-gray-600 text-xs italic\">\n        An Auth user JWT optionally signed with custom claims\n      </p>\n    </div>\n    <div class=\"flex flex-row justify-between items-center py-6\">\n      <div>\n        <%= submit(@subscribed_state,\n          class: \"bg-green-600 hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none\"\n        ) %>\n      </div>\n      <div>\n        <.gray_link_button phx-click=\"clear_local_storage\" phx-target={@myself}>\n          Reset\n        </.gray_link_button>\n      </div>\n    </div>\n  </.form>\n</div>\n"
  },
  {
    "path": "lib/realtime_web/live/inspector_live/index.ex",
    "content": "defmodule RealtimeWeb.InspectorLive.Index do\n  use RealtimeWeb, :live_view\n\n  alias RealtimeWeb.InspectorLive.ConnComponent\n\n  defmodule Message do\n    use Ecto.Schema\n    import Ecto.Changeset\n\n    schema \"f\" do\n      field(:event, :string)\n      field(:payload, :string)\n    end\n\n    def changeset(form, params \\\\ %{}) do\n      cast(form, params, [:event, :payload])\n    end\n  end\n\n  @impl true\n  def mount(_params, _session, socket) do\n    changeset = Message.changeset(%Message{event: \"test\", payload: ~s({\"some\":\"data\"})})\n\n    socket =\n      socket\n      |> assign(changeset: changeset)\n      |> assign(page_title: \"Inspector - Supabase Realtime\")\n      |> assign(realtime_connected: false)\n      |> assign(connected_to: nil)\n      |> assign(postgres_subscribed: false)\n      |> assign(presence_subscribed: false)\n      |> assign(broadcast_subscribed: false)\n      |> assign(share_url: Routes.inspector_index_path(socket, :new))\n\n    {:ok, socket}\n  end\n\n  @impl true\n  def handle_params(params, _url, socket) do\n    changeset = ConnComponent.Connection.changeset(%ConnComponent.Connection{}, params)\n\n    send_update(\n      RealtimeWeb.InspectorLive.ConnComponent,\n      id: :new_conn,\n      changeset: changeset,\n      url_params: params\n    )\n\n    {:noreply, socket}\n  end\n\n  @impl true\n  def handle_event(\"send_message\", params, socket) do\n    {:noreply, push_event(socket, \"send_message\", params)}\n  end\n\n  def handle_event(\"postgres_subscribed\", _params, socket) do\n    socket = assign(socket, postgres_subscribed: true)\n\n    {:noreply, socket}\n  end\n\n  def handle_event(\"presence_subscribed\", _params, socket) do\n    socket = assign(socket, presence_subscribed: true)\n\n    {:noreply, socket}\n  end\n\n  def handle_event(\"broadcast_subscribed\", %{\"host\" => host}, socket) do\n    socket =\n      socket\n      |> assign(realtime_connected: true)\n      |> assign(connected_to: host)\n      |> assign(broadcast_subscribed: true)\n      |> push_patch(to: Routes.inspector_index_path(socket, :index))\n\n    {:noreply, socket}\n  end\n\n  @impl true\n  def handle_info({:share_url, url}, socket) do\n    {:noreply, assign(socket, share_url: url)}\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/live/inspector_live/index.html.heex",
    "content": "<div class=\"flex flex-row justify-between py-4\">\n  <div>\n    <.h1>Realtime Inspector</.h1>\n    <p>Use the Realtime Inspector to connect to Supabase Realtime and debug payloads</p>\n  </div>\n  <div>\n    <.link_button href={@share_url} target=\"_blank\">Share</.link_button>\n  </div>\n</div>\n<div class=\"py-4\">\n  <.h2>Connection</.h2>\n  <%= if @live_action == :new do %>\n    <.modal id=\"conn_modal\" show={true}>\n      <:title>New Connection</:title>\n      <.live_component\n        module={RealtimeWeb.InspectorLive.ConnComponent}\n        id={:new_conn}\n        title={@page_title}\n        action={@live_action}\n      />\n    </.modal>\n  <% end %>\n\n  <div id=\"conn_info\" class=\"mb-5\">\n    <%= if @broadcast_subscribed do %>\n      <p>Connected to <code><%= @connected_to %></code></p>\n    <% else %>\n      <p>Connect to a Realtime instance first!</p>\n    <% end %>\n  </div>\n  <.link_button href={Routes.inspector_index_path(@socket, :new)}>New connection</.link_button>\n</div>\n\n<div class=\"grid grid-cols-1 md:grid-cols-3 gap-12 py-4\">\n  <div>\n    <div class=\"flex flex-row flex-wrap justify-between\">\n      <.h3>Broadcast</.h3>\n      <.badge>\n        <%= if @broadcast_subscribed, do: \"💚 Subscribed\", else: \"⚠️ Not subscribed\" %>\n      </.badge>\n    </div>\n    <p>Broadcast an event and all subscribed clients will receive it.</p>\n  </div>\n  <div>\n    <div class=\"flex flex-row flex-wrap justify-between\">\n      <.h3>Presence</.h3>\n      <.badge>\n        <%= if @presence_subscribed, do: \"💚 Subscribed\", else: \"⚠️ Not subscribed\" %>\n      </.badge>\n    </div>\n    <p>\n      As clients join and leave a Channel all clients will by notified with a `join` or `leave` event.\n    </p>\n  </div>\n  <div>\n    <div class=\"flex flex-row flex-wrap justify-between\">\n      <.h3>Database</.h3>\n      <.badge>\n        <%= if @postgres_subscribed, do: \"💚 Subscribed\", else: \"⚠️ Not subscribed\" %>\n      </.badge>\n    </div>\n    <p>Database changes are also broadcast over your Channel.</p>\n  </div>\n</div>\n\n<div class=\"flex flex-row flex-wrap py-4\">\n  <aside class=\"w-full sm:w-1/3 md:w-1/4 pr-2 mb-8\">\n    <.h3>Broadcast an Event</.h3>\n    <div class=\"mb-6\">\n      <.form :let={m} id=\"message_form\" for={@changeset} class=\"bg-white rounded mt-4\" phx-submit=\"send_message\">\n        <div class=\"mb-4\">\n          <%= label(m, :event, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n          <%= text_input(m, :event, class: \"\n                        my-1\n                        block\n                        w-full\n                        rounded-md\n                        border-gray-300\n                        shadow-sm\n                        focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                      \") %>\n          <%= error_tag(m, :event) %>\n          <p class=\"text-gray-600 text-xs italic\">Event name</p>\n        </div>\n        <div class=\"mb-4\">\n          <%= label(m, :payload, class: \"block text-gray-700 text-sm font-bold mb-2\") %>\n          <%= text_input(m, :payload, class: \"\n                        my-1\n                        block\n                        w-full\n                        rounded-md\n                        border-gray-300\n                        shadow-sm\n                        focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50\n                      \") %>\n          <%= error_tag(m, :payload) %>\n          <p class=\"text-gray-600 text-xs italic\">Message payload</p>\n        </div>\n\n        <%= submit(\"Send\",\n          phx_disable_with: \"Sending...\",\n          class: \"bg-green-600 hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none\"\n        ) %>\n      </.form>\n    </div>\n    <div class=\"mb-6\"></div>\n    <div class=\"mb-6\"></div>\n  </aside>\n\n  <div class=\"w-full sm:w-2/3 md:w-3/4 pt-1 pl-2\">\n    <div id=\"payload\" phx-update=\"ignore\" phx-hook=\"payload\" class=\"overflow-x-auto relative rounded\">\n      <table class=\"table-fixed w-full text-md text-left text-gray-700\">\n        <thead class=\"text-xs text-gray-700 uppercase bg-gray-50\">\n          <tr>\n            <th scope=\"col\" class=\"w-32 py-3 px-6\">Extension</th>\n            <th scope=\"col\" class=\"w-64 py-3 px-6\">Timestamp</th>\n            <th scope=\"col\" class=\"py-3 px-6\">Payload</th>\n          </tr>\n        </thead>\n        <tbody id=\"plist\"></tbody>\n      </table>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/realtime_web/live/page_live/index.ex",
    "content": "defmodule RealtimeWeb.PageLive.Index do\n  use RealtimeWeb, :live_view\n\n  @impl true\n  def mount(_params, _session, socket) do\n    {:ok, socket}\n  end\n\n  @impl true\n  def handle_params(params, _url, socket) do\n    {:noreply, apply_action(socket, socket.assigns.live_action, params)}\n  end\n\n  defp apply_action(socket, :index, _params) do\n    socket\n    |> assign(:page_title, \"Home - Supabase Realtime\")\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/live/page_live/index.html.heex",
    "content": "<.h1>Supabase Realtime: Multiplayer Edition</.h1>\n<h2>Listen to PostgreSQL changes in real-time over WebSockets</h2>\n<h2>Presence and Broadcast for multiplayer features</h2>\n\n<div class=\"my-5\">\n  <h3 class=\"font-bold\">Tools</h3>\n  <ul>\n    <li><.link href={Routes.inspector_index_path(@socket, :new)}>🔎 Inspector</.link></li>\n    <li><.link href={Routes.status_index_path(@socket, :index)}>🟢 Status</.link></li>\n  </ul>\n</div>\n\n<div class=\"my-5\">\n  <h3 class=\"font-bold\">Links</h3>\n  <ul>\n    <li><.link href=\"https://supabase.com/docs/guides/realtime\">📗 Guides</.link></li>\n    <li><.link href=\"https://github.com/supabase/realtime\">💾 Github</.link></li>\n    <li><.link href=\"https://github.com/supabase/realtime-js\">💾 realtime-js</.link></li>\n    <li><.link href=\"https://multiplayer.dev\">👾 multiplayer.dev</.link></li>\n  </ul>\n</div>\n"
  },
  {
    "path": "lib/realtime_web/live/ping_live.ex",
    "content": "defmodule RealtimeWeb.PingLive do\n  use RealtimeWeb, :live_view\n\n  def mount(_params, _session, socket) do\n    ping()\n    {:ok, assign(socket, :ping, \"0.0 ms\")}\n  end\n\n  def render(assigns) do\n    ~H\"\"\"\n    <span class=\"font-mono\" id=\"latency\" phx-hook=\"latency\"><%= @ping %></span>\n    \"\"\"\n  end\n\n  def handle_info(:ping, socket) do\n    socket = socket |> push_event(\"ping\", %{ping: DateTime.utc_now() |> DateTime.to_iso8601()})\n\n    {:noreply, socket}\n  end\n\n  def handle_event(\"pong\", %{\"ping\" => ping}, socket) do\n    {:ok, datetime, 0} = DateTime.from_iso8601(ping)\n\n    pong =\n      (DateTime.diff(DateTime.utc_now(), datetime, :microsecond) / 1000)\n      |> Float.round(1)\n      |> Float.to_string()\n\n    ping()\n    {:noreply, assign(socket, :ping, pong <> \" ms\")}\n  end\n\n  defp ping do\n    timer = if Mix.env() == :dev, do: 60_000, else: 1_000\n    Process.send_after(self(), :ping, timer)\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/live/status_live/index.ex",
    "content": "defmodule RealtimeWeb.StatusLive.Index do\n  use RealtimeWeb, :live_view\n\n  alias Realtime.Latency.Payload\n  alias Realtime.Nodes\n  alias RealtimeWeb.Endpoint\n\n  @impl true\n  def mount(_params, _session, socket) do\n    if connected?(socket), do: Endpoint.subscribe(\"admin:cluster\")\n\n    socket =\n      socket\n      |> assign(nodes: Enum.count(all_nodes()))\n      |> stream(:pings, default_pings())\n\n    {:ok, socket}\n  end\n\n  @impl true\n  def handle_params(params, _url, socket) do\n    {:noreply, apply_action(socket, socket.assigns.live_action, params)}\n  end\n\n  @impl true\n  def handle_info(%Phoenix.Socket.Broadcast{payload: %Payload{} = payload}, socket) do\n    pair = pair_id(payload.from_node, payload.node)\n\n    {:noreply, stream(socket, :pings, [%{id: pair, payload: payload}])}\n  end\n\n  defp apply_action(socket, :index, _params) do\n    socket\n    |> assign(:page_title, \"Realtime Status\")\n  end\n\n  defp all_nodes do\n    [Node.self() | Node.list()] |> Enum.map(&Nodes.short_node_id_from_name/1)\n  end\n\n  defp default_pings do\n    for n <- all_nodes(), f <- all_nodes() do\n      pair = pair_id(f, n)\n\n      %{id: pair, payload: %Payload{from_node: f, latency: \"Loading...\", node: n, timestamp: \"Loading...\"}}\n    end\n  end\n\n  defp pair_id(from, to) do\n    from <> \"_\" <> to\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/live/status_live/index.html.heex",
    "content": "<.h1>Supabase Realtime: Multiplayer Edition</.h1>\n\n<.h2>Cluster Status</.h2>\n\n<p>Understand the latency between nodes across the Realtime cluster.</p>\n\n<div class=\"my-5\">\n  <div id=\"pings\" phx-update=\"stream\" class=\"grid grid-cols-4 gap-12 py-4\">\n    <div :for={{id, p} <- @streams.pings} id={id} class=\"p-4 border-2 whitespace-nowrap overflow-hidden\">\n      <div>From: <%= p.payload.from_region %> - <%= p.payload.from_node %></div>\n      <div>To: <%= p.payload.region %> - <%= p.payload.node %></div>\n      <div><%= p.payload.latency %> ms</div>\n      <div><%= p.payload.timestamp %></div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/realtime_web/live/tenants_live/index.ex",
    "content": "defmodule RealtimeWeb.TenantsLive.Index do\n  use RealtimeWeb, :live_view\n\n  alias Realtime.Api\n  alias Realtime.Api.Tenant\n\n  defmodule Socket do\n    defstruct [:tenants, :filter_changeset, :sort_fields]\n  end\n\n  defmodule Filter do\n    use Ecto.Schema\n    import Ecto.Changeset\n\n    schema \"f\" do\n      field(:order_by, :string, default: \"inserted_at\")\n      field(:search, :string, default: nil)\n      field(:limit, :integer, default: 10)\n      field(:order, :string, default: \"desc\")\n    end\n\n    def changeset(form, params \\\\ %{}) do\n      form\n      |> cast(params, [:order_by, :search, :limit, :order])\n    end\n\n    def apply_changes_form(changeset) do\n      apply_changes(changeset)\n    end\n  end\n\n  @impl true\n  def mount(_params, _session, socket) do\n    defaults =\n      %Socket{\n        tenants: [],\n        filter_changeset: Filter.changeset(%Filter{}, %{})\n      }\n      |> Map.from_struct()\n\n    sort_fields = %Tenant{} |> Map.keys() |> Enum.drop(2)\n\n    socket =\n      socket\n      |> assign(defaults)\n      |> assign(tenants: list_tenants(%Filter{}))\n      |> assign(sort_fields: sort_fields)\n\n    {:ok, socket}\n  end\n\n  @impl true\n  def handle_params(params, _url, socket) do\n    changeset = Filter.changeset(socket.assigns.filter_changeset, params)\n    form = Filter.apply_changes_form(changeset)\n\n    socket =\n      socket\n      |> assign(filter_changeset: changeset)\n      |> assign(\n        tenants:\n          Api.list_tenants(\n            search: form.search,\n            order_by: form.order_by,\n            limit: form.limit,\n            order: form.order\n          )\n      )\n\n    {:noreply, socket}\n  end\n\n  @impl true\n  def handle_event(\"validate\", %{\"filter\" => filter}, socket) do\n    changeset = Filter.changeset(socket.assigns.filter_changeset, filter)\n\n    socket =\n      socket\n      |> assign(filter_changeset: changeset)\n      |> push_patch(\n        to: Routes.tenants_index_path(RealtimeWeb.Endpoint, :index, changeset.changes),\n        replace: true\n      )\n\n    {:noreply, socket}\n  end\n\n  defp list_tenants(%Filter{} = filter) do\n    filter |> Map.from_struct() |> Enum.into([]) |> Api.list_tenants()\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/live/tenants_live/index.html.heex",
    "content": "<.h1>Supabase Realtime: Multiplayer Edition</.h1>\n<.h2>Tenants</.h2>\n<p>Listing all Supabase Realtime tenants.</p>\n\n<div class=\"my-5 flex flex-row flex-wrap\">\n  <div class=\"pr-4 w-full sm:w-full md:w-1/3 lg:w-1/4 xl:w-1/4\">\n    <.form :let={f} for={@filter_changeset} phx-change=\"validate\" phx-submit=\"filter_submit\">\n      <div class=\"my-4\">\n        <.select form={f} field={:order_by} list={@sort_fields} selected={:inserted_at} />\n      </div>\n      <div class=\"my-4\">\n        <.select form={f} field={:order} list={[:desc, :asc]} selected={:desc} />\n      </div>\n      <div class=\"my-4\">\n        <.text_input form={f} field={:search} opts={[phx_change: \"validate\", placeholder: \"tenant\"]} />\n      </div>\n      <div class=\"my-4\">\n        <.text_input form={f} field={:limit} opts={[phx_change: \"validate\", placeholder: \"limit\"]} />\n      </div>\n    </.form>\n  </div>\n\n  <div class=\"pl-4 w-full sm:w-full md:w-2/3 lg:w-3/4 xl:w-3/4\">\n    <ul>\n      <%= for t <- @tenants do %>\n        <li class=\"my-4\">\n          <span><%= t.external_id %></span>\n          <div class=\"flex flex-wrap\">\n            <%= for {k, v} <- Map.take(t, [:max_events_per_second, :max_concurrent_users, :max_bytes_per_second]) do %>\n              <div class=\"mr-4\"><%= k %>: <%= v %></div>\n            <% end %>\n          </div>\n        </li>\n      <% end %>\n    </ul>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/realtime_web/live/time_live.ex",
    "content": "defmodule RealtimeWeb.TimeLive do\n  use RealtimeWeb, :live_view\n\n  def mount(_params, _session, socket) do\n    {:ok, assign_time(socket)}\n  end\n\n  def render(assigns) do\n    ~H\"\"\"\n    <span class=\"font-mono\"><%= @server_time %></span>\n    \"\"\"\n  end\n\n  def handle_info(:time, socket) do\n    {:noreply, assign_time(socket)}\n  end\n\n  defp assign_time(socket) do\n    timer = if Mix.env() == :dev, do: 60_000, else: 100\n    Process.send_after(self(), :time, timer)\n    now = DateTime.utc_now() |> DateTime.to_string()\n\n    socket\n    |> assign(:server_time, now)\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/open_api_schemas.ex",
    "content": "defmodule RealtimeWeb.OpenApiSchemas do\n  @moduledoc \"\"\"\n  Provides schemas and response definitions for RealtimeWeb OpenAPI specification.\n  \"\"\"\n\n  alias OpenApiSpex.Schema\n\n  defmodule ChannelParams do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{\n        name: %Schema{\n          type: :string,\n          description: \"Channel Name\",\n          example: \"channel-1\"\n        }\n      }\n    })\n\n    def params, do: {\"Channel Params\", \"application/json\", __MODULE__}\n  end\n\n  defmodule TenantBatchParams do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{\n        messages: %Schema{\n          type: :array,\n          items: %Schema{\n            type: :object,\n            properties: %{\n              topic: %Schema{\n                type: :string,\n                description:\n                  \"Note: libraries will prepend the channel name with 'realtime:' so if you use this endpoint directly you'll need to also prepend 'realtime:' so it's captured by clients properly\"\n              },\n              payload: %Schema{type: :object},\n              event: %Schema{\n                type: :string,\n                description: \"Name of the event being broadcast\"\n              },\n              private: %Schema{type: :boolean}\n            }\n          }\n        }\n      }\n    })\n\n    def params, do: {\"Tenant Batch Params\", \"application/json\", __MODULE__}\n  end\n\n  defmodule TenantParams do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{\n        tenant: %Schema{\n          type: :object,\n          properties: %{\n            external_id: %Schema{type: :string, description: \"External ID\"},\n            name: %Schema{type: :string, description: \"Tenant name\"},\n            jwt_secret: %Schema{type: :string, description: \"JWT secret\"},\n            jwt_jwks: %Schema{\n              type: :object,\n              description: \"JWKS for verifying JWTs\",\n              properties: %{\n                keys: %Schema{\n                  type: :array,\n                  description: \"Set of JWKs\",\n                  items: %Schema{\n                    type: :object,\n                    description: \"JWK\"\n                  }\n                }\n              }\n            },\n            max_concurrent_users: %Schema{\n              type: :number,\n              description: \"Maximum connected concurrent clients\"\n            },\n            max_events_per_second: %Schema{\n              type: :number,\n              description: \"Maximum events, or messages, per second\"\n            },\n            postgres_cdc_default: %Schema{\n              type: :string,\n              description: \"Default Postgres CDC extension\"\n            },\n            max_bytes_per_second: %Schema{type: :number, description: \"Maximum bytes per second\"},\n            max_channels_per_client: %Schema{\n              type: :number,\n              description: \"Maximum channels per WebSocket connection\"\n            },\n            max_joins_per_second: %Schema{\n              type: :number,\n              description: \"Maximum channel joins per second\"\n            },\n            max_presence_events_per_second: %Schema{\n              type: :number,\n              description: \"Maximum presence events per second\"\n            },\n            max_payload_size_in_kb: %Schema{\n              type: :number,\n              description: \"Maximum payload size in KB\"\n            },\n            max_client_presence_events_per_window: %Schema{\n              type: :number,\n              description: \"Maximum client presence events (overrides environment default when set)\",\n              nullable: true\n            },\n            client_presence_window_ms: %Schema{\n              type: :number,\n              description: \"Client presence rate limit window in milliseconds (overrides environment default when set)\",\n              nullable: true\n            },\n            presence_enabled: %Schema{\n              type: :boolean,\n              description: \"When true, presence is enabled for clients that do not explicitly opt in\"\n            },\n            extensions: %Schema{\n              type: :array,\n              items: %Schema{\n                type: :object,\n                properties: %{\n                  type: %Schema{type: :string, description: \"Postgres CDC extension type\"},\n                  settings: %Schema{\n                    type: :object,\n                    description: \"Extension database configuration\"\n                  },\n                  tenant_external_id: %Schema{type: :string, description: \"Tenant external ID\"}\n                },\n                required: [\n                  :settings,\n                  :tenant_external_id\n                ]\n              }\n            }\n          },\n          required: [\n            :external_id,\n            :jwt_secret\n          ]\n        }\n      },\n      required: [:tenant],\n      example: %{\n        tenant: %{\n          external_id: \"tenant-1\",\n          name: \"First Tenant\",\n          jwt_secret: \"4a218613-b539-4c52-adaa-64b14b25ee88\",\n          max_concurrent_users: 1000,\n          max_events_per_second: 1000,\n          postgres_cdc_default: \"postgres_cdc_rls\",\n          max_bytes_per_second: 1000,\n          max_channels_per_client: 100,\n          max_joins_per_second: 1000,\n          extensions: [\n            %{\n              type: \"postgres_cdc_rls\",\n              settings: %{\n                \"region\" => \"us-west-1\",\n                \"db_host\" => \"db_host\",\n                \"db_name\" => \"postgres\",\n                \"db_port\" => \"5432\",\n                \"db_user\" => \"postgres\",\n                \"slot_name\" => \"supabase_realtime_replication_slot\",\n                \"db_password\" => \"password\",\n                \"publication\" => \"supabase_realtime\",\n                \"poll_interval_ms\" => 100,\n                \"poll_max_changes\" => 100,\n                \"poll_max_record_bytes\" => 1_048_576\n              },\n              tenant_external_id: \"tenant-1\"\n            }\n          ]\n        }\n      }\n    })\n\n    def params, do: {\"Tenant Params\", \"application/json\", __MODULE__}\n  end\n\n  defmodule TenantResponseValue do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{\n        id: %Schema{type: :string, description: \"UUID\"},\n        external_id: %Schema{type: :string, description: \"External ID\"},\n        name: %Schema{type: :string, description: \"Tenant name\"},\n        max_concurrent_users: %Schema{\n          type: :number,\n          description: \"Maximum connected concurrent clients\"\n        },\n        max_channels_per_client: %Schema{\n          type: :number,\n          description: \"Maximum channels per connected client\"\n        },\n        max_events_per_second: %Schema{\n          type: :number,\n          description: \"Maximum number of events, or messages, that all connected clients are permitted to send\"\n        },\n        max_joins_per_second: %Schema{\n          type: :number,\n          description: \"Maximum number of channel joins permitted for all connected clients\"\n        },\n        jwt_jwks: %Schema{\n          type: :object,\n          description: \"JWKS for verifying JWTs\",\n          properties: %{\n            keys: %Schema{\n              type: :array,\n              description: \"Set of JWKs\",\n              items: %Schema{\n                type: :object,\n                description: \"JWK\"\n              }\n            }\n          }\n        },\n        max_client_presence_events_per_window: %Schema{\n          type: :number,\n          description: \"Maximum client presence events (overrides environment default when set)\",\n          nullable: true\n        },\n        client_presence_window_ms: %Schema{\n          type: :number,\n          description: \"Client presence rate limit window in milliseconds (overrides environment default when set)\",\n          nullable: true\n        },\n        inserted_at: %Schema{type: :string, format: \"date-time\", description: \"Insert timestamp\"},\n        extensions: %Schema{\n          type: :array,\n          items: %Schema{\n            type: :object,\n            properties: %{\n              type: %Schema{type: :string, description: \"Postgres CDC extension type\"},\n              settings: %Schema{type: :object, description: \"Extension database configuration\"},\n              tenant_external_id: %Schema{type: :string, description: \"Tenant external ID\"},\n              inserted_at: %Schema{\n                type: :string,\n                format: \"date-time\",\n                description: \"Insert timestamp\"\n              },\n              updated_at: %Schema{\n                type: :string,\n                format: \"date-time\",\n                description: \"Update timestamp\"\n              }\n            },\n            required: [\n              :settings,\n              :tenant_external_id\n            ]\n          }\n        }\n      },\n      required: [\n        :external_id,\n        :jwt_secret\n      ],\n      example: %{\n        id: \"d4448862-666f-4af2-9966-78298e8af6bf\",\n        external_id: \"tenant-1\",\n        name: \"First Tenant\",\n        max_concurrent_users: 1000,\n        max_channels_per_client: 100,\n        max_events_per_second: 100,\n        max_joins_per_second: 100,\n        inserted_at: \"2023-01-01T00:00:00Z\",\n        extensions: [\n          %{\n            type: \"postgres_cdc_rls\",\n            settings: %{\n              \"region\" => \"us-west-1\",\n              \"db_host\" => \"db_host\",\n              \"db_name\" => \"postgres\",\n              \"db_port\" => \"5432\",\n              \"db_user\" => \"postgres\",\n              \"slot_name\" => \"supabase_realtime_replication_slot\",\n              \"db_password\" => \"password\",\n              \"publication\" => \"supabase_realtime\",\n              \"poll_interval_ms\" => 100,\n              \"poll_max_changes\" => 100,\n              \"poll_max_record_bytes\" => 1_048_576\n            },\n            tenant_external_id: \"tenant-1\",\n            inserted_at: \"2023-01-01T00:00:00Z\",\n            updated_at: \"2023-01-01T00:00:00Z\"\n          }\n        ]\n      }\n    })\n  end\n\n  defmodule ChannelResponseValue do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{\n        id: %Schema{type: :integer, description: \"Channel ID\"},\n        name: %Schema{type: :string, description: \"Channel Name\"},\n        inserted_at: %Schema{type: :string, format: \"date-time\", description: \"Insert timestamp\"},\n        updated_at: %Schema{type: :string, format: \"date-time\", description: \"Update timestamp\"}\n      },\n      required: [:id, :name, :inserted_at, :updated_at],\n      example: %{\n        id: 1,\n        name: \"channel-1\",\n        inserted_at: \"2023-01-01T00:00:00Z\",\n        updated_at: \"2023-01-01T00:00:00Z\"\n      }\n    })\n  end\n\n  defmodule TenantHealthResponseValue do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{\n        healthy: %Schema{type: :boolean, description: \"Tenant is healthy or not\"},\n        db_connected: %Schema{\n          type: :boolean,\n          description: \"Indicates if Realtime has an active connection to the tenant database\"\n        },\n        replication_connected: %Schema{\n          type: :boolean,\n          description: \"Indicates if Realtime has an active replication connection for broadcast changes\"\n        },\n        connected_cluster: %Schema{\n          type: :integer,\n          description: \"The count of currently connected clients for a tenant on the Realtime cluster\"\n        }\n      },\n      required: [\n        :healthy,\n        :db_connected,\n        :replication_connected,\n        :connected_cluster\n      ],\n      example: %{\n        healthy: true,\n        db_connected: true,\n        replication_connected: true,\n        connected_cluster: 10\n      }\n    })\n  end\n\n  defmodule TenantHealthResponse do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{data: TenantHealthResponseValue}\n    })\n\n    def response, do: {\"Tenant Response\", \"application/json\", __MODULE__}\n  end\n\n  defmodule TenantResponse do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{data: TenantResponseValue}\n    })\n\n    def response, do: {\"Tenant Response\", \"application/json\", __MODULE__}\n  end\n\n  defmodule TenantResponseList do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{data: %Schema{type: :array, items: TenantResponseValue}}\n    })\n\n    def response, do: {\"Tenant List Response\", \"application/json\", __MODULE__}\n  end\n\n  defmodule ChannelResponse do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{data: ChannelResponseValue}\n    })\n\n    def response, do: {\"Tenant Response\", \"application/json\", __MODULE__}\n  end\n\n  defmodule ChannelResponseList do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{data: %Schema{type: :array, items: ChannelResponseValue}}\n    })\n\n    def response, do: {\"Tenant List Response\", \"application/json\", __MODULE__}\n  end\n\n  defmodule EmptyResponse do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :string,\n      default: \"\"\n    })\n\n    def response, do: {\"Empty Response\", \"application/json\", __MODULE__}\n  end\n\n  defmodule NotFoundResponse do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{\n        error: %Schema{type: :string, default: \"not found\"}\n      }\n    })\n\n    def response, do: {\"Not Found\", \"application/json\", __MODULE__}\n  end\n\n  defmodule ErrorResponse do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{\n        error: %Schema{type: :string, default: \"error message\"}\n      }\n    })\n\n    def response, do: {\"Error\", \"application/json\", __MODULE__}\n  end\n\n  defmodule UnauthorizedResponse do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{\n        error: %Schema{type: :string, default: \"unauthorized\"}\n      }\n    })\n\n    def response, do: {\"Unauthorized\", \"application/json\", __MODULE__}\n  end\n\n  defmodule UnprocessableEntityResponse do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{\n        error: %Schema{\n          type: :object,\n          properties: %{\n            messages: %Schema{\n              type: :array,\n              items: %Schema{type: :object}\n            }\n          }\n        }\n      }\n    })\n\n    def response, do: {\"Unprocessable Entity\", \"application/json\", __MODULE__}\n  end\n\n  defmodule TooManyRequestsResponse do\n    @moduledoc false\n    require OpenApiSpex\n\n    OpenApiSpex.schema(%{\n      type: :object,\n      properties: %{\n        error: %Schema{\n          type: :object,\n          properties: %{\n            messages: %Schema{\n              type: :array,\n              items: %Schema{type: :object}\n            }\n          }\n        }\n      }\n    })\n\n    def response, do: {\"Too Many Requests\", \"application/json\", __MODULE__}\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/plugs/assign_tenant.ex",
    "content": "defmodule RealtimeWeb.Plugs.AssignTenant do\n  @moduledoc \"\"\"\n  Picks out the tenant from the request and assigns it in the conn.\n  \"\"\"\n  import Plug.Conn\n  import Phoenix.Controller, only: [json: 2]\n\n  require Logger\n\n  alias Realtime.Api\n  alias Realtime.Api.Tenant\n  alias Realtime.Database\n  alias Realtime.GenCounter\n  alias Realtime.RateCounter\n  alias Realtime.Tenants\n\n  def init(opts) do\n    opts\n  end\n\n  def call(%Plug.Conn{host: host} = conn, _opts) do\n    with {:ok, external_id} <- Database.get_external_id(host),\n         %Tenant{} = tenant <- Api.get_tenant_by_external_id(external_id, use_replica?: true) do\n      Logger.metadata(external_id: external_id, project: external_id)\n      OpenTelemetry.Tracer.set_attributes(external_id: external_id)\n\n      tenant =\n        tenant\n        |> tap(&initialize_counters/1)\n        |> tap(&GenCounter.add(Tenants.requests_per_second_key(&1)))\n        |> Api.preload_counters()\n\n      assign(conn, :tenant, tenant)\n    else\n      nil -> error_response(conn, \"Tenant not found in database\")\n    end\n  end\n\n  defp error_response(conn, message) do\n    conn\n    |> put_status(401)\n    |> json(%{message: message})\n    |> halt()\n  end\n\n  defp initialize_counters(tenant) do\n    RateCounter.new(Tenants.requests_per_second_rate(tenant))\n    RateCounter.new(Tenants.events_per_second_rate(tenant))\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/plugs/auth_tenant.ex",
    "content": "defmodule RealtimeWeb.AuthTenant do\n  @moduledoc \"\"\"\n  Authorization plug to ensure that only authorized clients can connect to the their tenant's endpoints.\n  \"\"\"\n  require Logger\n\n  import Plug.Conn\n  import Phoenix.Controller, only: [json: 2]\n\n  alias Realtime.Api.Tenant\n  alias Realtime.Crypto\n\n  alias RealtimeWeb.ChannelsAuthorization\n\n  def init(opts), do: opts\n\n  def call(%{assigns: %{tenant: tenant}} = conn, _opts) do\n    Logger.metadata(external_id: tenant.external_id, project: tenant.external_id)\n\n    with %Tenant{jwt_secret: jwt_secret, jwt_jwks: jwt_jwks} <- tenant,\n         token when is_binary(token) <- access_token(conn),\n         jwt_secret_dec <- Crypto.decrypt!(jwt_secret),\n         {:ok, claims} <- ChannelsAuthorization.authorize_conn(token, jwt_secret_dec, jwt_jwks) do\n      conn\n      |> assign(:claims, claims)\n      |> assign(:jwt, token)\n      |> assign(:role, claims[\"role\"])\n      |> assign(:sub, claims[\"sub\"])\n    else\n      _error -> unauthorized(conn)\n    end\n  end\n\n  def call(conn, _opts), do: unauthorized(conn)\n\n  defp access_token(conn) do\n    authorization = get_req_header(conn, \"authorization\")\n    apikey = get_req_header(conn, \"apikey\")\n\n    authorization =\n      case authorization do\n        [] ->\n          nil\n\n        [\"\"] ->\n          nil\n\n        [value | _] ->\n          [bearer, token] = value |> String.split(\" \")\n          bearer = String.downcase(bearer)\n          if bearer == \"bearer\", do: token\n      end\n\n    apikey =\n      case apikey do\n        [] -> nil\n        [value | _] -> value\n      end\n\n    cond do\n      authorization -> authorization\n      apikey -> apikey\n      true -> nil\n    end\n  end\n\n  defp unauthorized(conn),\n    do: conn |> put_status(401) |> json(%{message: \"Unauthorized\"}) |> halt()\nend\n"
  },
  {
    "path": "lib/realtime_web/plugs/baggage_request_id.ex",
    "content": "defmodule RealtimeWeb.Plugs.BaggageRequestId do\n  @moduledoc \"\"\"\n  Populates request ID based on trace baggage.\n  It looks for the specified `baggage_key` (default to 'request-id').\n\n  Otherwise generates a request ID using `Plug.RequestId`\n  \"\"\"\n\n  def baggage_key, do: Application.get_env(:realtime, :request_id_baggage_key, \"request-id\")\n\n  require Logger\n  alias Plug.Conn\n  @behaviour Plug\n\n  @impl true\n  @doc false\n  def init(opts) do\n    Keyword.get(opts, :baggage_key, \"request-id\")\n  end\n\n  @impl true\n  @doc false\n  @spec call(Conn.t(), String.t()) :: Conn.t()\n  def call(conn, baggage_key) do\n    :otel_propagator_text_map.extract(conn.req_headers)\n\n    with %{^baggage_key => {request_id, _}} <- :otel_baggage.get_all(),\n         true <- valid_request_id?(request_id) do\n      Logger.metadata(request_id: request_id)\n      Conn.put_resp_header(conn, \"x-request-id\", request_id)\n    else\n      _ ->\n        opts = Plug.RequestId.init([])\n        Plug.RequestId.call(conn, opts)\n    end\n  end\n\n  defp valid_request_id?(s), do: byte_size(s) in 10..200\nend\n"
  },
  {
    "path": "lib/realtime_web/plugs/metrics_mode.ex",
    "content": "defmodule RealtimeWeb.Plugs.MetricsMode do\n  @moduledoc \"\"\"\n  Plug to dispatch metrics requests to the appropriate controller based on the metrics mode.\n  \"\"\"\n  import Plug.Conn\n\n  def init(opts), do: opts\n\n  def call(conn, _opts) do\n    if Application.get_env(:realtime, :metrics_separation_enabled, false), do: conn, else: dispatch_legacy(conn)\n  end\n\n  defp dispatch_legacy(%{path_info: [\"tenant-metrics\" | _]} = conn) do\n    conn |> send_resp(404, \"\") |> halt()\n  end\n\n  defp dispatch_legacy(conn) do\n    action = if conn.path_params[\"region\"], do: :region, else: :index\n\n    conn\n    |> put_private(:phoenix_controller, RealtimeWeb.LegacyMetricsController)\n    |> put_private(:phoenix_action, action)\n    |> RealtimeWeb.LegacyMetricsController.call(action)\n    |> halt()\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/plugs/rate_limiter.ex",
    "content": "defmodule RealtimeWeb.Plugs.RateLimiter do\n  @moduledoc \"\"\"\n  Rate limits tenants.\n  \"\"\"\n  import Plug.Conn\n  import Phoenix.Controller, only: [json: 2]\n  require Logger\n\n  alias Realtime.Api.Tenant\n\n  def init(opts) do\n    opts\n  end\n\n  def call(\n        %{\n          assigns: %{\n            tenant: %Tenant{\n              events_per_second_rolling: avg,\n              events_per_second_now: _current,\n              max_events_per_second: max\n            }\n          }\n        } = conn,\n        _opts\n      ) do\n    avg = trunc(avg)\n\n    conn =\n      conn\n      |> put_resp_header(\"x-rate-rolling\", Integer.to_string(avg))\n      |> put_resp_header(\"x-rate-limit\", Integer.to_string(max))\n      |> put_resp_header(\"x-rate-limit-remaining\", Integer.to_string(max - avg))\n\n    if avg >= max do\n      conn\n      |> put_status(429)\n      |> json(%{message: \"Too many requests\"})\n      |> halt()\n    else\n      conn\n    end\n  end\n\n  def call(conn, _opts) do\n    conn\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/router.ex",
    "content": "defmodule RealtimeWeb.Router do\n  use RealtimeWeb, :router\n\n  require Logger\n  require OpenTelemetry.Tracer, as: Tracer\n\n  import RealtimeWeb.ChannelsAuthorization, only: [authorize: 3]\n\n  pipeline :browser do\n    plug(:accepts, [\"html\"])\n    plug(:fetch_session)\n    plug(:fetch_live_flash)\n    plug(:put_root_layout, {RealtimeWeb.LayoutView, :root})\n    plug(:protect_from_forgery)\n    plug(:put_secure_browser_headers)\n  end\n\n  pipeline :api do\n    plug(:accepts, [\"json\"])\n    plug(:check_auth, [:api_jwt_secret, :api_blocklist])\n    plug(:set_span_request_id)\n  end\n\n  pipeline :open_cors do\n    plug(Corsica, origins: \"*\")\n  end\n\n  pipeline :tenant_api do\n    plug(:accepts, [\"json\"])\n    plug(RealtimeWeb.Plugs.AssignTenant)\n    plug(RealtimeWeb.Plugs.RateLimiter)\n    plug(:set_span_request_id)\n  end\n\n  pipeline :secure_tenant_api do\n    plug(RealtimeWeb.AuthTenant)\n    plug(:set_span_request_id)\n  end\n\n  pipeline :dashboard_admin do\n    plug(:dashboard_auth)\n  end\n\n  pipeline :metrics do\n    plug(:check_auth, [:metrics_jwt_secret, :metrics_blocklist])\n    plug(RealtimeWeb.Plugs.MetricsMode)\n  end\n\n  pipeline :openapi do\n    plug(OpenApiSpex.Plug.PutApiSpec, module: RealtimeWeb.ApiSpec)\n  end\n\n  scope \"/\", RealtimeWeb do\n    get(\"/healthcheck\", PageController, :healthcheck)\n  end\n\n  scope \"/\", RealtimeWeb do\n    pipe_through(:browser)\n\n    live(\"/\", PageLive.Index, :index)\n    live(\"/inspector\", InspectorLive.Index, :index)\n    live(\"/inspector/new\", InspectorLive.Index, :new)\n    live(\"/status\", StatusLive.Index, :index)\n  end\n\n  scope \"/swaggerui\" do\n    pipe_through(:browser)\n    get(\"/\", OpenApiSpex.Plug.SwaggerUI, path: \"/api/openapi\")\n  end\n\n  scope \"/admin\", RealtimeWeb do\n    pipe_through [:browser, :dashboard_admin]\n    live(\"/tenants\", TenantsLive.Index, :index)\n  end\n\n  scope \"/metrics\", RealtimeWeb do\n    pipe_through(:metrics)\n\n    get(\"/\", MetricsController, :index)\n    get(\"/:region\", MetricsController, :region)\n  end\n\n  scope \"/tenant-metrics\", RealtimeWeb do\n    pipe_through(:metrics)\n\n    get(\"/\", MetricsController, :tenant)\n    get(\"/:region\", MetricsController, :region_tenant)\n  end\n\n  scope \"/api\" do\n    pipe_through(:openapi)\n\n    get(\"/openapi\", OpenApiSpex.Plug.RenderSpec, [])\n  end\n\n  scope \"/api\", RealtimeWeb do\n    pipe_through(:api)\n\n    resources(\"/tenants\", TenantController, param: \"tenant_id\", except: [:edit, :new])\n    post(\"/tenants/:tenant_id/reload\", TenantController, :reload)\n    post(\"/tenants/:tenant_id/shutdown\", TenantController, :shutdown)\n    get(\"/tenants/:tenant_id/health\", TenantController, :health)\n  end\n\n  scope \"/api\", RealtimeWeb do\n    pipe_through(:tenant_api)\n\n    get(\"/ping\", PingController, :ping)\n  end\n\n  scope \"/api\", RealtimeWeb do\n    pipe_through([:open_cors, :tenant_api, :secure_tenant_api])\n\n    post(\"/broadcast\", BroadcastController, :broadcast)\n  end\n\n  # Enables LiveDashboard only for development\n  #\n  # If you want to use the LiveDashboard in production, you should put\n  # it behind authentication and allow only admins to access it.\n  # If your application does not have an admins-only section yet,\n  # you can use Plug.BasicAuth to set up some basic authentication\n  # as long as you are also using SSL (which you should anyway).\n  scope \"/admin\" do\n    pipe_through [:browser, :dashboard_admin]\n\n    live_dashboard(\"/dashboard\",\n      ecto_repos: [\n        Realtime.Repo,\n        Realtime.Repo.Replica.FRA,\n        Realtime.Repo.Replica.IAD,\n        Realtime.Repo.Replica.SIN,\n        Realtime.Repo.Replica.SJC\n      ],\n      ecto_psql_extras_options: [long_running_queries: [threshold: \"200 milliseconds\"]],\n      metrics: RealtimeWeb.Telemetry,\n      additional_pages: [\n        route_name: Realtime.Dashboard.ProcessDump,\n        tenant_info: Realtime.Dashboard.TenantInfo\n      ]\n    )\n  end\n\n  defp check_auth(conn, [secret_key, blocklist_key]) do\n    secret = Application.fetch_env!(:realtime, secret_key)\n    blocklist = Application.get_env(:realtime, blocklist_key, [])\n\n    with [\"Bearer \" <> token] <- get_req_header(conn, \"authorization\"),\n         token <- Regex.replace(~r/\\s|\\n/, URI.decode(token), \"\"),\n         false <- token in blocklist,\n         {:ok, _claims} <- authorize(token, secret, nil) do\n      conn\n    else\n      _ ->\n        conn\n        |> send_resp(403, \"\")\n        |> halt()\n    end\n  end\n\n  defp dashboard_auth(conn, _opts) do\n    case Application.fetch_env!(:realtime, :dashboard_auth) do\n      :zta ->\n        {conn, user} = NimbleZTA.Cloudflare.authenticate(Realtime.ZTA, conn)\n        if user, do: conn, else: conn |> send_resp(403, \"\") |> halt()\n\n      :basic_auth ->\n        {user, password} = Application.fetch_env!(:realtime, :dashboard_credentials)\n        Plug.BasicAuth.basic_auth(conn, username: user, password: password)\n    end\n  catch\n    :exit, reason ->\n      Logger.error(\"ZTA authentication failed: #{inspect(reason)}\")\n      conn |> send_resp(503, \"\") |> halt()\n  end\n\n  defp set_span_request_id(conn, _) do\n    # Must have been set by BaggageRequestId\n    # We can't set the span attribute there because the phoenix span only starts after it reaches the Router\n    if request_id = Logger.metadata()[:request_id] do\n      Tracer.set_attribute(:request_id, request_id)\n    end\n\n    conn\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/socket/user_broadcast.ex",
    "content": "defmodule RealtimeWeb.Socket.UserBroadcast do\n  @moduledoc \"\"\"\n  Defines a message sent from pubsub to channels and vice-versa.\n\n  The message format requires the following keys:\n\n    * `:topic` - The string topic or topic:subtopic pair namespace, for example \"messages\", \"messages:123\"\n    * `:user_event`- The string user event name, for example \"my-event\"\n    * `:user_payload_encoding`- :json or :binary\n    * `:user_payload` - The actual message payload\n\n  Optionally metadata which is a map to be JSON encoded\n  \"\"\"\n\n  alias Phoenix.Socket.Broadcast\n\n  @type t :: %__MODULE__{}\n  defstruct topic: nil, user_event: nil, user_payload: nil, user_payload_encoding: nil, metadata: nil\n\n  @spec convert_to_json_broadcast(t) :: {:ok, Broadcast.t()} | {:error, String.t()}\n  def convert_to_json_broadcast(%__MODULE__{user_payload_encoding: :json} = user_broadcast) do\n    payload = %{\n      \"event\" => user_broadcast.user_event,\n      \"payload\" => Jason.Fragment.new(user_broadcast.user_payload),\n      \"type\" => \"broadcast\"\n    }\n\n    payload =\n      if user_broadcast.metadata do\n        Map.put(payload, \"meta\", user_broadcast.metadata)\n      else\n        payload\n      end\n\n    {:ok, %Broadcast{event: \"broadcast\", payload: payload, topic: user_broadcast.topic}}\n  end\n\n  def convert_to_json_broadcast(%__MODULE__{}), do: {:error, \"User payload encoding is not JSON\"}\nend\n"
  },
  {
    "path": "lib/realtime_web/socket/v2_serializer.ex",
    "content": "defmodule RealtimeWeb.Socket.V2Serializer do\n  @moduledoc \"\"\"\n  Custom serializer that is a superset of Phoenix's V2 JSONSerializer\n  that handles user broadcast and user broadcast push\n  \"\"\"\n\n  @behaviour Phoenix.Socket.Serializer\n\n  @push 0\n  @reply 1\n  @broadcast 2\n  @user_broadcast_push 3\n  @user_broadcast 4\n\n  alias Phoenix.Socket.{Message, Reply, Broadcast}\n  alias RealtimeWeb.Socket.UserBroadcast\n\n  @impl true\n  def fastlane!(%UserBroadcast{} = msg) do\n    metadata =\n      if msg.metadata do\n        Phoenix.json_library().encode!(msg.metadata)\n      else\n        msg.metadata\n      end\n\n    topic_size = byte_size!(msg.topic, :topic, 255)\n    user_event_size = byte_size!(msg.user_event, :user_event, 255)\n    metadata_size = byte_size!(metadata, :metadata, 255)\n    user_payload_encoding = if msg.user_payload_encoding == :json, do: 1, else: 0\n\n    bin = <<\n      @user_broadcast::size(8),\n      topic_size::size(8),\n      user_event_size::size(8),\n      metadata_size::size(8),\n      user_payload_encoding::size(8),\n      msg.topic::binary-size(topic_size),\n      msg.user_event::binary-size(user_event_size),\n      metadata || <<>>::binary-size(metadata_size),\n      msg.user_payload::binary\n    >>\n\n    {:socket_push, :binary, bin}\n  end\n\n  def fastlane!(%Broadcast{payload: {:binary, data}} = msg) do\n    topic_size = byte_size!(msg.topic, :topic, 255)\n    event_size = byte_size!(msg.event, :event, 255)\n\n    bin = <<\n      @broadcast::size(8),\n      topic_size::size(8),\n      event_size::size(8),\n      msg.topic::binary-size(topic_size),\n      msg.event::binary-size(event_size),\n      data::binary\n    >>\n\n    {:socket_push, :binary, bin}\n  end\n\n  def fastlane!(%Broadcast{payload: %{}} = msg) do\n    data = Phoenix.json_library().encode_to_iodata!([nil, nil, msg.topic, msg.event, msg.payload])\n    {:socket_push, :text, data}\n  end\n\n  def fastlane!(%Broadcast{payload: invalid}) do\n    raise ArgumentError, \"expected broadcasted payload to be a map, got: #{inspect(invalid)}\"\n  end\n\n  @impl true\n  def encode!(%Reply{payload: {:binary, data}} = reply) do\n    status = to_string(reply.status)\n    join_ref = to_string(reply.join_ref)\n    ref = to_string(reply.ref)\n    join_ref_size = byte_size!(join_ref, :join_ref, 255)\n    ref_size = byte_size!(ref, :ref, 255)\n    topic_size = byte_size!(reply.topic, :topic, 255)\n    status_size = byte_size!(status, :status, 255)\n\n    bin = <<\n      @reply::size(8),\n      join_ref_size::size(8),\n      ref_size::size(8),\n      topic_size::size(8),\n      status_size::size(8),\n      join_ref::binary-size(join_ref_size),\n      ref::binary-size(ref_size),\n      reply.topic::binary-size(topic_size),\n      status::binary-size(status_size),\n      data::binary\n    >>\n\n    {:socket_push, :binary, bin}\n  end\n\n  def encode!(%Reply{} = reply) do\n    data = [\n      reply.join_ref,\n      reply.ref,\n      reply.topic,\n      \"phx_reply\",\n      %{status: reply.status, response: reply.payload}\n    ]\n\n    {:socket_push, :text, Phoenix.json_library().encode_to_iodata!(data)}\n  end\n\n  def encode!(%Message{payload: {:binary, data}} = msg) do\n    join_ref = to_string(msg.join_ref)\n    join_ref_size = byte_size!(join_ref, :join_ref, 255)\n    topic_size = byte_size!(msg.topic, :topic, 255)\n    event_size = byte_size!(msg.event, :event, 255)\n\n    bin = <<\n      @push::size(8),\n      join_ref_size::size(8),\n      topic_size::size(8),\n      event_size::size(8),\n      join_ref::binary-size(join_ref_size),\n      msg.topic::binary-size(topic_size),\n      msg.event::binary-size(event_size),\n      data::binary\n    >>\n\n    {:socket_push, :binary, bin}\n  end\n\n  def encode!(%Message{payload: %{}} = msg) do\n    data = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]\n    {:socket_push, :text, Phoenix.json_library().encode_to_iodata!(data)}\n  end\n\n  def encode!(%Message{payload: invalid}) do\n    raise ArgumentError, \"expected payload to be a map, got: #{inspect(invalid)}\"\n  end\n\n  @impl true\n  def decode!(raw_message, opts) do\n    case Keyword.fetch(opts, :opcode) do\n      {:ok, :text} -> decode_text(raw_message)\n      {:ok, :binary} -> decode_binary(raw_message)\n    end\n  end\n\n  defp decode_text(raw_message) do\n    case Phoenix.json_library().decode!(raw_message) do\n      [join_ref, ref, topic, event, payload | _] ->\n        %Message{topic: topic, event: event, payload: payload, ref: ref, join_ref: join_ref}\n\n      other ->\n        raise Phoenix.Socket.InvalidMessageError,\n              \"expected V2 array, got: #{inspect(other, limit: 200, printable_limit: 200)}\"\n    end\n  end\n\n  defp decode_binary(<<\n         @push::size(8),\n         join_ref_size::size(8),\n         ref_size::size(8),\n         topic_size::size(8),\n         event_size::size(8),\n         join_ref::binary-size(join_ref_size),\n         ref::binary-size(ref_size),\n         topic::binary-size(topic_size),\n         event::binary-size(event_size),\n         data::binary\n       >>) do\n    %Message{\n      topic: topic,\n      event: event,\n      payload: {:binary, data},\n      ref: ref,\n      join_ref: join_ref\n    }\n  end\n\n  defp decode_binary(<<\n         @user_broadcast_push::size(8),\n         join_ref_size::size(8),\n         ref_size::size(8),\n         topic_size::size(8),\n         user_event_size::size(8),\n         metadata_size::size(8),\n         user_payload_encoding::size(8),\n         join_ref::binary-size(join_ref_size),\n         ref::binary-size(ref_size),\n         topic::binary-size(topic_size),\n         user_event::binary-size(user_event_size),\n         metadata::binary-size(metadata_size),\n         user_payload::binary\n       >>) do\n    user_payload_encoding = if user_payload_encoding == 0, do: :binary, else: :json\n\n    metadata =\n      if metadata_size > 0 do\n        Phoenix.json_library().decode!(metadata)\n      else\n        %{}\n      end\n\n    # Encoding as Message because that's how Phoenix Socket and Channel.Server expects things to show up\n    # Here we abuse the payload field to carry a tuple of (user_event, user payload encoding, user payload, metadata)\n    %Message{\n      topic: topic,\n      event: \"broadcast\",\n      payload: {user_event, user_payload_encoding, user_payload, metadata},\n      ref: ref,\n      join_ref: join_ref\n    }\n  end\n\n  defp byte_size!(nil, _kind, _max), do: 0\n\n  defp byte_size!(bin, kind, max) do\n    case byte_size(bin) do\n      size when size <= max ->\n        size\n\n      oversized ->\n        raise ArgumentError, \"\"\"\n        unable to convert #{kind} to binary.\n\n            #{inspect(bin)}\n\n        must be less than or equal to #{max} bytes, but is #{oversized} bytes.\n        \"\"\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/telemetry.ex",
    "content": "defmodule RealtimeWeb.Telemetry do\n  @moduledoc false\n\n  use Supervisor\n  import Telemetry.Metrics\n\n  def start_link(arg) do\n    Supervisor.start_link(__MODULE__, arg, name: __MODULE__)\n  end\n\n  @impl true\n  def init(_arg) do\n    children = [\n      # Telemetry poller will execute the given period measurements\n      # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics\n      {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}\n      # Add reporters as children of your supervision tree.\n      # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}\n    ]\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\n\n  def metrics do\n    [\n      # Phoenix Metrics\n      summary(\"phoenix.endpoint.stop.duration\",\n        unit: {:native, :millisecond}\n      ),\n      summary(\"phoenix.router_dispatch.stop.duration\",\n        tags: [:route],\n        unit: {:native, :millisecond}\n      ),\n\n      # Database Metrics\n      summary(\"realtime.repo.query.total_time\", unit: {:native, :millisecond}),\n      summary(\"realtime.repo.query.decode_time\", unit: {:native, :millisecond}),\n      summary(\"realtime.repo.query.query_time\", unit: {:native, :millisecond}),\n      summary(\"realtime.repo.query.queue_time\", unit: {:native, :millisecond}),\n      summary(\"realtime.repo.query.idle_time\", unit: {:native, :millisecond}),\n\n      # VM Metrics\n      summary(\"vm.memory.total\", unit: {:byte, :kilobyte}),\n      summary(\"vm.total_run_queue_lengths.total\"),\n      summary(\"vm.total_run_queue_lengths.cpu\"),\n      summary(\"vm.total_run_queue_lengths.io\")\n    ]\n  end\n\n  defp periodic_measurements do\n    [\n      # A module, function and arguments to be invoked periodically.\n      # This function must call :telemetry.execute/3 and a metric must be added above.\n      # {RealtimeWeb, :count_users, []}\n    ]\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/templates/layout/app.html.heex",
    "content": "<header class=\"px-4 sm:px-6 lg:px-8\">\n  <div class=\"flex items-center justify-between border-b border-zinc-100 py-3\">\n    <div class=\"flex items-center gap-4\">\n      <a href=\"/pages/home.html\">\n        <svg viewBox=\"0 0 71 48\" class=\"h-6\" aria-hidden=\"true\">\n          <path\n            d=\"m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z\"\n            fill=\"#FD4F00\"\n          />\n        </svg>\n      </a>\n      <p class=\"rounded-full bg-brand/5 px-2 text-[0.8125rem] font-medium leading-6 text-brand\">\n        Supabase Realtime\n      </p>\n    </div>\n  </div>\n</header>\n<main class=\"px-4 py-20 sm:px-6 lg:px-8\">\n  <div class=\"mx-auto max-w-5xl\">\n    <p class=\"alert alert-info\" role=\"alert\"><%= Phoenix.Flash.get(@flash, :info) %></p>\n    <p class=\"alert alert-danger\" role=\"alert\"><%= Phoenix.Flash.get(@flash, :error) %></p>\n\n    <%= @inner_content %>\n  </div>\n</main>\n"
  },
  {
    "path": "lib/realtime_web/templates/layout/live.html.heex",
    "content": "<header class=\"px-4 sm:px-6 lg:px-8\">\n  <div class=\"flex items-center justify-between border-b border-zinc-100 py-3\">\n    <div class=\"flex items-center gap-4\">\n      <a href=\"/\">\n        <svg height=\"25\" viewBox=\"0 0 581 113\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path\n            d=\"M151.397 66.7608C151.996 72.3621 157.091 81.9642 171.877 81.9642C184.764 81.9642 190.959 73.7624 190.959 65.7607C190.959 58.559 186.063 52.6577 176.373 50.6571L169.379 49.1569C166.682 48.6568 164.884 47.1565 164.884 44.7559C164.884 41.9552 167.681 39.8549 171.178 39.8549C176.772 39.8549 178.87 43.5556 179.27 46.4564L190.359 43.9558C189.76 38.6546 185.064 29.7527 171.078 29.7527C160.488 29.7527 152.696 37.0543 152.696 45.8561C152.696 52.7576 156.991 58.4591 166.482 60.5594L172.976 62.0598C176.772 62.8599 178.271 64.6605 178.271 66.8609C178.271 69.4615 176.173 71.762 171.777 71.762C165.983 71.762 163.085 68.1611 162.786 64.2602L151.397 66.7608Z\"\n            fill=\"#1F1F1F\"\n          />\n          <path\n            d=\"M233.421 80.4639H246.109C245.909 78.7635 245.609 75.3628 245.609 71.5618V31.2529H232.321V59.8592C232.321 65.5606 228.925 69.5614 223.031 69.5614C216.837 69.5614 214.039 65.1604 214.039 59.6592V31.2529H200.752V62.3599C200.752 73.0622 207.545 81.7642 219.434 81.7642C224.628 81.7642 230.325 79.7638 233.022 75.1627C233.022 77.1631 233.221 79.4636 233.421 80.4639Z\"\n            fill=\"#1F1F1F\"\n          />\n          <path\n            d=\"M273.076 99.4682V75.663C275.473 78.9636 280.469 81.6644 287.263 81.6644C301.149 81.6644 310.439 70.6617 310.439 55.7584C310.439 41.1553 302.148 30.1528 287.762 30.1528C280.37 30.1528 274.875 33.4534 272.677 37.2544V31.253H259.79V99.4682H273.076ZM297.352 55.8585C297.352 64.6606 291.958 69.7616 285.164 69.7616C278.372 69.7616 272.877 64.5605 272.877 55.8585C272.877 47.1566 278.372 42.0554 285.164 42.0554C291.958 42.0554 297.352 47.1566 297.352 55.8585Z\"\n            fill=\"#1F1F1F\"\n          />\n          <path\n            d=\"M317.964 67.0609C317.964 74.7627 324.357 81.8643 334.848 81.8643C342.139 81.8643 346.835 78.4634 349.332 74.5625C349.332 76.463 349.532 79.1635 349.832 80.4639H362.02C361.72 78.7635 361.422 75.2627 361.422 72.6622V48.4567C361.422 38.5545 355.627 29.7527 340.043 29.7527C326.855 29.7527 319.761 38.2544 318.963 45.9562L330.751 48.4567C331.151 44.1558 334.348 40.455 340.141 40.455C345.737 40.455 348.434 43.3556 348.434 46.8564C348.434 48.5568 347.536 49.9572 344.738 50.3572L332.65 52.1576C324.458 53.3579 317.964 58.2589 317.964 67.0609ZM337.644 71.962C333.349 71.962 331.25 69.1614 331.25 66.2608C331.25 62.4599 333.947 60.5594 337.345 60.0594L348.434 58.359V60.5594C348.434 69.2615 343.239 71.962 337.644 71.962Z\"\n            fill=\"#1F1F1F\"\n          />\n          <path\n            d=\"M387.703 80.4641V74.4627C390.299 78.6637 395.494 81.6644 402.288 81.6644C416.276 81.6644 425.467 70.5618 425.467 55.6585C425.467 41.0552 417.174 29.9528 402.788 29.9528C395.494 29.9528 390.1 33.1535 387.902 36.6541V8.04785H374.815V80.4641H387.703ZM412.178 55.7584C412.178 64.7605 406.784 69.7616 399.99 69.7616C393.297 69.7616 387.703 64.6606 387.703 55.7584C387.703 46.7564 393.297 41.8554 399.99 41.8554C406.784 41.8554 412.178 46.7564 412.178 55.7584Z\"\n            fill=\"#1F1F1F\"\n          />\n          <path\n            d=\"M432.99 67.0609C432.99 74.7627 439.383 81.8643 449.873 81.8643C457.165 81.8643 461.862 78.4634 464.358 74.5625C464.358 76.463 464.559 79.1635 464.858 80.4639H477.046C476.748 78.7635 476.448 75.2627 476.448 72.6622V48.4567C476.448 38.5545 470.653 29.7527 455.068 29.7527C441.881 29.7527 434.788 38.2544 433.989 45.9562L445.776 48.4567C446.177 44.1558 449.374 40.455 455.167 40.455C460.763 40.455 463.46 43.3556 463.46 46.8564C463.46 48.5568 462.561 49.9572 459.763 50.3572L447.676 52.1576C439.484 53.3579 432.99 58.2589 432.99 67.0609ZM452.671 71.962C448.375 71.962 446.276 69.1614 446.276 66.2608C446.276 62.4599 448.973 60.5594 452.371 60.0594L463.46 58.359V60.5594C463.46 69.2615 458.265 71.962 452.671 71.962Z\"\n            fill=\"#1F1F1F\"\n          />\n          <path\n            d=\"M485.645 66.7608C486.243 72.3621 491.339 81.9642 506.124 81.9642C519.012 81.9642 525.205 73.7624 525.205 65.7607C525.205 58.559 520.311 52.6577 510.62 50.6571L503.626 49.1569C500.929 48.6568 499.132 47.1565 499.132 44.7559C499.132 41.9552 501.928 39.8549 505.425 39.8549C511.021 39.8549 513.118 43.5556 513.519 46.4564L524.607 43.9558C524.007 38.6546 519.312 29.7527 505.326 29.7527C494.735 29.7527 486.944 37.0543 486.944 45.8561C486.944 52.7576 491.238 58.4591 500.73 60.5594L507.224 62.0598C511.021 62.8599 512.519 64.6605 512.519 66.8609C512.519 69.4615 510.421 71.762 506.025 71.762C500.23 71.762 497.334 68.1611 497.034 64.2602L485.645 66.7608Z\"\n            fill=\"#1F1F1F\"\n          />\n          <path\n            d=\"M545.385 50.2571C545.685 45.7562 549.482 40.5549 556.375 40.5549C563.967 40.5549 567.165 45.3561 567.365 50.2571H545.385ZM568.664 63.0601C567.065 67.4609 563.668 70.5617 557.474 70.5617C550.88 70.5617 545.385 65.8606 545.087 59.3593H580.252C580.252 59.159 580.451 57.1587 580.451 55.2582C580.451 39.4547 571.361 29.7527 556.175 29.7527C543.588 29.7527 531.998 39.9548 531.998 55.6584C531.998 72.262 543.886 81.9642 557.374 81.9642C569.462 81.9642 577.255 74.8626 579.753 66.3607L568.664 63.0601Z\"\n            fill=\"#1F1F1F\"\n          />\n          <path\n            d=\"M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z\"\n            fill=\"url(#paint0_linear)\"\n          />\n          <path\n            d=\"M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z\"\n            fill=\"url(#paint1_linear)\"\n            fill-opacity=\"0.2\"\n          />\n          <path\n            d=\"M45.317 2.07103C48.1765 -1.53037 53.9745 0.442937 54.0434 5.041L54.4849 72.2922H9.83113C1.64038 72.2922 -2.92775 62.8321 2.1655 56.4175L45.317 2.07103Z\"\n            fill=\"#3ECF8E\"\n          />\n          <defs>\n            <linearGradient\n              id=\"paint0_linear\"\n              x1=\"53.9738\"\n              y1=\"54.974\"\n              x2=\"94.1635\"\n              y2=\"71.8295\"\n              gradientUnits=\"userSpaceOnUse\"\n            >\n              <stop stop-color=\"#249361\" />\n              <stop offset=\"1\" stop-color=\"#3ECF8E\" />\n            </linearGradient>\n            <linearGradient\n              id=\"paint1_linear\"\n              x1=\"36.1558\"\n              y1=\"30.578\"\n              x2=\"54.4844\"\n              y2=\"65.0806\"\n              gradientUnits=\"userSpaceOnUse\"\n            >\n              <stop />\n              <stop offset=\"1\" stop-opacity=\"0\" />\n            </linearGradient>\n          </defs>\n        </svg>\n      </a>\n      <p class=\"rounded-full bg-brand/5 px-2 text-[0.8125rem] font-medium leading-6 text-gray-900\">\n        Realtime: Multiplayer Edition\n      </p>\n    </div>\n  </div>\n</header>\n<main class=\"px-4 py-24 sm:px-6 lg:px-8\">\n  <div class=\"mx-auto max-w-6xl\">\n    <p class=\"alert alert-info\" role=\"alert\"><%= Phoenix.Flash.get(@flash, :info) %></p>\n    <p class=\"alert alert-danger\" role=\"alert\"><%= Phoenix.Flash.get(@flash, :error) %></p>\n\n    <%= @inner_content %>\n  </div>\n</main>\n<footer class=\"fixed bottom-0 left-0 z-20 p-4 w-full bg-white border-t border-zinc-100 md:flex md:items-center md:justify-between md:p-6\">\n  <div>\n    <span class=\"font-mono mr-4 text-sm text-gray-900 sm:text-center\">\n      v<%= Application.get_env(:realtime, :version) %>\n    </span>\n    <span class=\"text-sm text-gray-900 sm:text-center\">\n      © <a href=\"https://supabase.com/\" class=\"hover:underline\">Supabase Inc</a>\n    </span>\n  </div>\n  <ul class=\"flex flex-wrap items-center mt-3 text-sm text-gray-900\">\n    <li><span class=\"mr-4\">Apache 2.0 License</span></li>\n    <li>\n      <span class=\"font-mono mr-4\"><%= Realtime.Nodes.short_node_id_from_name(node()) %></span>\n    </li>\n    <li><span class=\"font-mono mr-4\"><%= System.get_env(\"REGION\") %></span></li>\n    <li><%= live_render(@socket, RealtimeWeb.PingLive, id: \"ping\") %></li>\n  </ul>\n</footer>\n"
  },
  {
    "path": "lib/realtime_web/templates/layout/root.html.heex",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link rel=\"icon\" href=\"/favicon.svg\" />\n    <%= csrf_meta_tag() %>\n    <.live_title>\n      <%= assigns[:page_title] || \"Supabase Realtime\" %>\n    </.live_title>\n    <link phx-track-static rel=\"stylesheet\" href={Routes.static_path(@conn, \"/assets/app.css\")} />\n    <script defer phx-track-static type=\"text/javascript\" src={Routes.static_path(@conn, \"/assets/app.js\")}>\n    </script>\n  </head>\n  <body class=\"bg-white antialiased\">\n    <%= @inner_content %>\n  </body>\n</html>\n"
  },
  {
    "path": "lib/realtime_web/tenant_broadcaster.ex",
    "content": "defmodule RealtimeWeb.TenantBroadcaster do\n  @moduledoc \"\"\"\n  gen_rpc broadcaster\n  \"\"\"\n\n  alias Phoenix.PubSub\n\n  @type message_type :: :broadcast | :presence | :postgres_changes\n\n  @spec pubsub_direct_broadcast(\n          node :: node(),\n          tenant_id :: String.t(),\n          PubSub.topic(),\n          PubSub.message(),\n          PubSub.dispatcher(),\n          message_type\n        ) ::\n          :ok\n  def pubsub_direct_broadcast(node, tenant_id, topic, message, dispatcher, message_type) do\n    collect_payload_size(tenant_id, message, message_type)\n\n    do_direct_broadcast(node, topic, message, dispatcher)\n\n    :ok\n  end\n\n  # Remote\n  defp do_direct_broadcast(node, topic, message, dispatcher) when node != node() do\n    if pubsub_adapter() == :gen_rpc do\n      PubSub.direct_broadcast(node, Realtime.PubSub, topic, message, dispatcher)\n    else\n      Realtime.GenRpc.cast(node, PubSub, :local_broadcast, [Realtime.PubSub, topic, message, dispatcher], key: topic)\n    end\n  end\n\n  # Local\n  defp do_direct_broadcast(_node, topic, message, dispatcher) do\n    PubSub.local_broadcast(Realtime.PubSub, topic, message, dispatcher)\n  end\n\n  @spec pubsub_broadcast(tenant_id :: String.t(), PubSub.topic(), PubSub.message(), PubSub.dispatcher(), message_type) ::\n          :ok\n  def pubsub_broadcast(tenant_id, topic, message, dispatcher, message_type) do\n    collect_payload_size(tenant_id, message, message_type)\n\n    if pubsub_adapter() == :gen_rpc do\n      PubSub.broadcast(Realtime.PubSub, topic, message, dispatcher)\n    else\n      Realtime.GenRpc.multicast(PubSub, :local_broadcast, [Realtime.PubSub, topic, message, dispatcher], key: topic)\n    end\n\n    :ok\n  end\n\n  @spec pubsub_broadcast_from(\n          tenant_id :: String.t(),\n          from :: pid,\n          PubSub.topic(),\n          PubSub.message(),\n          PubSub.dispatcher(),\n          message_type\n        ) ::\n          :ok\n  def pubsub_broadcast_from(tenant_id, from, topic, message, dispatcher, message_type) do\n    collect_payload_size(tenant_id, message, message_type)\n\n    if pubsub_adapter() == :gen_rpc do\n      PubSub.broadcast_from(Realtime.PubSub, from, topic, message, dispatcher)\n    else\n      Realtime.GenRpc.multicast(\n        PubSub,\n        :local_broadcast_from,\n        [Realtime.PubSub, from, topic, message, dispatcher],\n        key: topic\n      )\n    end\n\n    :ok\n  end\n\n  @payload_size_event [:realtime, :tenants, :payload, :size]\n\n  @spec collect_payload_size(tenant_id :: String.t(), payload :: term, message_type :: message_type) :: :ok\n  def collect_payload_size(tenant_id, payload, message_type) when is_struct(payload) do\n    # Extracting from struct so the __struct__ bit is not calculated as part of the payload\n    collect_payload_size(tenant_id, Map.from_struct(payload), message_type)\n  end\n\n  def collect_payload_size(tenant_id, payload, message_type) do\n    :telemetry.execute(@payload_size_event, %{size: :erlang.external_size(payload)}, %{\n      tenant: tenant_id,\n      message_type: message_type\n    })\n  end\n\n  defp pubsub_adapter, do: Application.fetch_env!(:realtime, :pubsub_adapter)\nend\n"
  },
  {
    "path": "lib/realtime_web/views/changeset_view.ex",
    "content": "defmodule RealtimeWeb.ChangesetView do\n  use RealtimeWeb, :view\n\n  @doc \"\"\"\n  Traverses and translates changeset errors.\n\n  See `Ecto.Changeset.traverse_errors/2` and\n  `RealtimeWeb.ErrorHelpers.translate_error/1` for more details.\n  \"\"\"\n  def translate_errors(changeset) do\n    Ecto.Changeset.traverse_errors(changeset, &translate_error/1)\n  end\n\n  def render(\"error.json\", %{changeset: changeset}) do\n    %{errors: translate_errors(changeset)}\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/views/error_helpers.ex",
    "content": "defmodule RealtimeWeb.ErrorHelpers do\n  @moduledoc \"\"\"\n  Conveniences for translating and building error messages.\n  \"\"\"\n\n  use Phoenix.HTML\n\n  @doc \"\"\"\n  Generates tag for inlined form input errors.\n  \"\"\"\n  def error_tag(form, field) do\n    Enum.map(Keyword.get_values(form.errors, field), fn error ->\n      content_tag(:span, translate_error(error),\n        class: \"invalid-feedback\",\n        phx_feedback_for: input_id(form, field)\n      )\n    end)\n  end\n\n  @doc \"\"\"\n  Translates an error message using gettext.\n  \"\"\"\n  def translate_error({msg, opts}) do\n    # When using gettext, we typically pass the strings we want\n    # to translate as a static argument:\n    #\n    #     # Translate \"is invalid\" in the \"errors\" domain\n    #     dgettext(\"errors\", \"is invalid\")\n    #\n    #     # Translate the number of files with plural rules\n    #     dngettext(\"errors\", \"1 file\", \"%{count} files\", count)\n    #\n    # Because the error messages we show in our forms and APIs\n    # are defined inside Ecto, we need to translate them dynamically.\n    # This requires us to call the Gettext module passing our gettext\n    # backend as first argument.\n    #\n    # Note we use the \"errors\" domain, which means translations\n    # should be written to the errors.po file. The :count option is\n    # set by Ecto and indicates we should also apply plural rules.\n    if count = opts[:count] do\n      Gettext.dngettext(RealtimeWeb.Gettext, \"errors\", msg, msg, count, opts)\n    else\n      Gettext.dgettext(RealtimeWeb.Gettext, \"errors\", msg, opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web/views/error_view.ex",
    "content": "defmodule RealtimeWeb.ErrorView do\n  use RealtimeWeb, :view\n\n  def render(\"error.json\", %{conn: %{assigns: %{message: message}}}), do: %{message: message}\n\n  def template_not_found(template, _assigns), do: Phoenix.Controller.status_message_from_template(template)\nend\n"
  },
  {
    "path": "lib/realtime_web/views/layout_view.ex",
    "content": "defmodule RealtimeWeb.LayoutView do\n  use RealtimeWeb, :view\nend\n"
  },
  {
    "path": "lib/realtime_web/views/tenant_view.ex",
    "content": "defmodule RealtimeWeb.TenantView do\n  use RealtimeWeb, :view\n  alias RealtimeWeb.TenantView\n\n  def render(\"index.json\", %{tenants: tenants}) do\n    %{data: render_many(tenants, TenantView, \"tenant.json\")}\n  end\n\n  def render(\"show.json\", %{tenant: tenant}) do\n    %{data: render_one(tenant, TenantView, \"tenant.json\")}\n  end\n\n  def render(\"not_found.json\", %{tenant: nil}) do\n    %{error: \"not found\"}\n  end\n\n  def render(\"tenant.json\", %{tenant: tenant}) do\n    %{\n      id: tenant.id,\n      external_id: tenant.external_id,\n      name: tenant.name,\n      max_concurrent_users: tenant.max_concurrent_users,\n      max_channels_per_client: tenant.max_channels_per_client,\n      max_events_per_second: tenant.max_events_per_second,\n      max_joins_per_second: tenant.max_joins_per_second,\n      inserted_at: tenant.inserted_at,\n      extensions:\n        Enum.map(tenant.extensions, fn extension ->\n          Map.update(extension, :settings, %{}, fn settings ->\n            Map.drop(settings, [\"db_password\"])\n          end)\n        end),\n      private_only: tenant.private_only,\n      max_client_presence_events_per_window: tenant.max_client_presence_events_per_window,\n      client_presence_window_ms: tenant.client_presence_window_ms,\n      presence_enabled: tenant.presence_enabled\n    }\n  end\nend\n"
  },
  {
    "path": "lib/realtime_web.ex",
    "content": "defmodule RealtimeWeb do\n  @moduledoc \"\"\"\n  The entrypoint for defining your web interface, such\n  as controllers, views, channels and so on.\n\n  This can be used in your application as:\n\n      use RealtimeWeb, :controller\n      use RealtimeWeb, :view\n\n  The definitions below will be executed for every view,\n  controller, etc, so keep them short and clean, focused\n  on imports, uses and aliases.\n\n  Do NOT define functions inside the quoted expressions\n  below. Instead, define any helper function in modules\n  and import those modules here.\n  \"\"\"\n\n  def static_paths, do: ~w(assets fonts images favicon.svg robots.txt worker.js)\n\n  def controller do\n    quote do\n      use Phoenix.Controller, namespace: RealtimeWeb\n\n      import Plug.Conn\n      import RealtimeWeb.Gettext\n      alias RealtimeWeb.Router.Helpers, as: Routes\n\n      unquote(verified_routes())\n    end\n  end\n\n  def view do\n    quote do\n      use Phoenix.View,\n        root: \"lib/realtime_web/templates\",\n        namespace: RealtimeWeb\n\n      # Import convenience functions from controllers\n      import Phoenix.Controller,\n        only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]\n\n      # Include shared imports and aliases for views\n      unquote(view_helpers())\n    end\n  end\n\n  def live_view do\n    quote do\n      use Phoenix.LiveView,\n        layout: {RealtimeWeb.LayoutView, :live}\n\n      unquote(view_helpers())\n    end\n  end\n\n  def live_component do\n    quote do\n      use Phoenix.LiveComponent\n\n      unquote(view_helpers())\n    end\n  end\n\n  def component do\n    quote do\n      use Phoenix.Component\n\n      unquote(view_helpers())\n    end\n  end\n\n  def router do\n    quote do\n      use Phoenix.Router\n\n      import Plug.Conn\n      import Phoenix.Controller\n      import Phoenix.LiveView.Router\n      import Phoenix.LiveDashboard.Router\n    end\n  end\n\n  def channel do\n    quote do\n      use Phoenix.Channel, log_join: false, log_handle_in: false\n      import RealtimeWeb.Gettext\n    end\n  end\n\n  defp view_helpers do\n    quote do\n      # Use all HTML functionality (forms, tags, etc)\n      use Phoenix.HTML\n\n      import Phoenix.Component\n      import Phoenix.LiveComponent\n\n      import Phoenix.LiveView.Helpers\n      # Import basic rendering functionality (render, render_layout, etc)\n      import Phoenix.View\n\n      import RealtimeWeb.ErrorHelpers\n      import RealtimeWeb.Gettext\n      alias RealtimeWeb.Router.Helpers, as: Routes\n\n      import RealtimeWeb.Components\n\n      unquote(verified_routes())\n    end\n  end\n\n  @doc \"\"\"\n  When used, dispatch to the appropriate controller/view/etc.\n  \"\"\"\n  defmacro __using__(which) when is_atom(which) do\n    apply(__MODULE__, which, [])\n  end\n\n  def verified_routes do\n    quote do\n      use Phoenix.VerifiedRoutes,\n        endpoint: RealtimeWeb.Endpoint,\n        router: RealtimeWeb.Router,\n        statics: RealtimeWeb.static_paths()\n    end\n  end\nend\n"
  },
  {
    "path": "mix.exs",
    "content": "defmodule Realtime.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :realtime,\n      version: \"2.78.15\",\n      elixir: \"~> 1.18\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      aliases: aliases(),\n      deps: deps(),\n      dialyzer: dialyzer(),\n      test_coverage: [tool: ExCoveralls],\n      releases: [\n        realtime: [\n          # This will ensure that if opentelemetry terminates, even abnormally, our application will not be terminated.\n          applications: [\n            opentelemetry_exporter: :permanent,\n            opentelemetry: :temporary\n          ]\n        ]\n      ]\n    ]\n  end\n\n  defp dialyzer do\n    [\n      plt_add_apps: [:mix],\n      plt_core_path: \"priv/plts\",\n      plt_file: {:no_warn, \"priv/plts/dialyzer.plt\"},\n      # Warn if an ignore filter on dialyzer_ignore is not unused\n      list_unused_filters: true\n    ]\n  end\n\n  # Configuration for the OTP application.\n  #\n  # Type `mix help compile.app` for more information.\n  def application do\n    [\n      mod: {Realtime.Application, []},\n      extra_applications: [:logger, :runtime_tools, :prom_ex, :mix, :os_mon]\n    ]\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n  defp elixirc_paths(_), do: [\"lib\"]\n\n  # Specifies your project dependencies.\n  #\n  # Type `mix help deps` for examples and options.\n  defp deps do\n    [\n      {:phoenix, override: true, github: \"supabase/phoenix\", branch: \"feat/presence-custom-dispatcher-1.7.19\"},\n      {:phoenix_ecto, \"~> 4.4.0\"},\n      {:ecto_sql, \"~> 3.11\"},\n      {:ecto_psql_extras, \"~> 0.8\"},\n      {:postgrex, \"~> 0.20.0\"},\n      {:phoenix_html, \"~> 3.2\"},\n      {:phoenix_live_view, \"~> 0.18\"},\n      {:phoenix_live_reload, \"~> 1.2\", only: :dev},\n      {:phoenix_live_dashboard, \"~> 0.7\"},\n      {:phoenix_view, \"~> 2.0\"},\n      {:esbuild, \"~> 0.4\", runtime: Mix.env() == :dev},\n      {:tailwind, \"~> 0.1\", runtime: Mix.env() == :dev},\n      {:telemetry_metrics, \"~> 1.0\"},\n      {:telemetry_poller, \"~> 1.0\"},\n      {:gettext, \"~> 0.19\"},\n      {:jason, \"~> 1.3\"},\n      {:plug_cowboy, \"~> 2.6\"},\n      {:libcluster, \"~> 3.3\"},\n      {:libcluster_postgres, \"~> 0.2\"},\n      {:uuid, \"~> 1.1\"},\n      {:prom_ex, \"~> 1.10\"},\n      # prom_ex depends on peep ~> 3.0 but there is no issue using peep ~> 4.0\n      # https://github.com/akoutmos/prom_ex/pull/270\n      {:peep, \"~> 4.3\", override: true},\n      {:joken, \"~> 2.5.0\"},\n      {:nimble_zta, \"~> 0.1\"},\n      {:ex_json_schema, \"~> 0.7\"},\n      {:recon, \"~> 2.5\"},\n      {:mint, \"~> 1.4\"},\n      {:logflare_logger_backend, \"~> 0.11\"},\n      {:syn, \"~> 3.3\"},\n      {:beacon, path: \"./beacon\"},\n      {:cachex, \"~> 4.0\"},\n      {:open_api_spex, \"~> 3.16\"},\n      {:corsica, \"~> 2.0\"},\n      {:observer_cli, \"~> 1.7\"},\n      {:opentelemetry_exporter, \"~> 1.6\"},\n      {:opentelemetry, \"~> 1.3\"},\n      {:opentelemetry_api, \"~> 1.2\"},\n      {:opentelemetry_phoenix, \"~> 2.0\"},\n      {:opentelemetry_cowboy, \"~> 1.0\"},\n      {:opentelemetry_ecto, \"~> 1.2\"},\n      {:gen_rpc, git: \"https://github.com/supabase/gen_rpc.git\", ref: \"5382a0f2689a4cb8838873a2173928281dbe5002\"},\n      {:req, \"~> 0.5\"},\n      {:mimic, \"~> 1.0\", only: :test},\n      {:floki, \">= 0.30.0\", only: :test},\n      {:mint_web_socket, \"~> 1.0\", only: :test},\n      {:postgres_replication, git: \"https://github.com/filipecabaco/postgres_replication.git\", only: :test},\n      {:benchee, \"~> 1.1.0\", only: [:dev, :test]},\n      {:excoveralls, \"~> 0.18\", only: [:dev, :test], runtime: false},\n      {:sobelow, \"~> 0.13\", only: [:dev, :test], runtime: false},\n      {:mix_audit, \"~> 2.1\", only: [:dev, :test], runtime: false},\n      {:credo, \"~> 1.7\", only: [:dev, :test], runtime: false},\n      {:dialyxir, \"~> 1.4\", only: :dev, runtime: false},\n      {:poolboy, \"~> 1.5\", only: :test},\n      {:mix_test_watch, \"~> 1.0\", only: [:dev, :test], runtime: false}\n    ]\n  end\n\n  # Aliases are shortcuts or tasks specific to the current project.\n  # For example, to install project dependencies and perform other setup tasks, run:\n  #\n  #     $ mix setup\n  #\n  # See the documentation for `Mix` for more info on aliases.\n  defp aliases do\n    [\n      setup: [\"deps.get\", \"ecto.setup\", \"cmd npm install --prefix assets\"],\n      \"ecto.setup\": [\"ecto.create\", \"ecto.migrate\", \"run priv/repo/dev_seeds.exs\"],\n      \"ecto.reset\": [\"ecto.drop\", \"ecto.setup\"],\n      test: [\n        \"cmd epmd -daemon\",\n        \"ecto.create --quiet\",\n        \"ecto.migrate --migrations-path=priv/repo/migrations\",\n        \"test\"\n      ],\n      \"test.partitioned\": [\n        \"cmd epmd -daemon\",\n        \"ecto.create --quiet\",\n        \"ecto.migrate --migrations-path=priv/repo/migrations\",\n        \"test --partitions 4\"\n      ],\n      \"assets.deploy\": [\"esbuild default --minify\", \"tailwind default --minify\", \"phx.digest\"]\n    ]\n  end\nend\n"
  },
  {
    "path": "phx_join.schema.json",
    "content": "{\n    \"type\": \"object\",\n    \"description\": \"\",\n    \"title\": \"phx_join\",\n    \"required\": [\n        \"access_token\",\n        \"config\"\n    ],\n    \"properties\": {\n        \"config\": {\n            \"title\": \"config\",\n            \"$ref\": \"#/$defs/phx_join_config\"\n        },\n        \"access_token\": {\n            \"type\": \"string\",\n            \"description\": \"String, e.g. 'hello'\",\n            \"title\": \"access_token\"\n        }\n    },\n    \"additionalProperties\": false,\n    \"$defs\": {\n        \"phx_join_config\": {\n            \"type\": \"object\",\n            \"description\": \"\",\n            \"title\": \"phx_join_config\",\n            \"properties\": {\n                \"private\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Boolean, e.g. true\",\n                    \"title\": \"private\",\n                    \"default\": false\n                },\n                \"broadcast\": {\n                    \"title\": \"broadcast\",\n                    \"$ref\": \"#/$defs/phx_join_broadcast\",\n                    \"default\": {\n                        \"self\": false,\n                        \"ack\": false\n                    }\n                },\n                \"presence\": {\n                    \"title\": \"presence\",\n                    \"$ref\": \"#/$defs/phx_join_presence\",\n                    \"default\": {\n                        \"enabled\": false,\n                        \"key\": \"\"\n                    }\n                },\n                \"postgres_changes\": {\n                    \"type\": \"array\",\n                    \"title\": \"phx_join_postgres_changes\",\n                    \"items\": {\n                        \"$ref\": \"#/$defs/phx_join_postgres_changes\",\n                        \"default\": {\n                            \"table\": \"\",\n                            \"filter\": \"\",\n                            \"schema\": \"\",\n                            \"event\": \"\"\n                        }\n                    }\n                }\n            },\n            \"additionalProperties\": false\n        },\n        \"phx_join_broadcast\": {\n            \"type\": \"object\",\n            \"description\": \"\",\n            \"title\": \"phx_join_broadcast\",\n            \"properties\": {\n                \"self\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Boolean, e.g. true\",\n                    \"title\": \"self\",\n                    \"default\": false\n                },\n                \"ack\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Boolean, e.g. true\",\n                    \"title\": \"ack\",\n                    \"default\": false\n                }\n            },\n            \"additionalProperties\": false\n        },\n        \"phx_join_postgres_changes\": {\n            \"type\": \"object\",\n            \"description\": \"\",\n            \"title\": \"phx_join_postgres_changes\",\n            \"required\": [\n                \"event\",\n                \"filter\",\n                \"schema\",\n                \"table\"\n            ],\n            \"properties\": {\n                \"table\": {\n                    \"type\": \"string\",\n                    \"description\": \"String, e.g. 'hello'\",\n                    \"title\": \"table\"\n                },\n                \"filter\": {\n                    \"type\": \"string\",\n                    \"description\": \"String, e.g. 'hello'\",\n                    \"title\": \"filter\"\n                },\n                \"schema\": {\n                    \"type\": \"string\",\n                    \"description\": \"String, e.g. 'hello'\",\n                    \"title\": \"schema\"\n                },\n                \"event\": {\n                    \"type\": \"string\",\n                    \"description\": \"String, e.g. 'hello'\",\n                    \"title\": \"event\"\n                }\n            },\n            \"additionalProperties\": false\n        },\n        \"phx_join_presence\": {\n            \"type\": \"object\",\n            \"description\": \"\",\n            \"title\": \"phx_join_presence\",\n            \"properties\": {\n                \"enabled\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Boolean, e.g. true\",\n                    \"title\": \"enabled\",\n                    \"default\": false\n                },\n                \"key\": {\n                    \"type\": \"string\",\n                    \"description\": \"String, e.g. 'hello'\",\n                    \"title\": \"key\"\n                }\n            },\n            \"additionalProperties\": false\n        }\n    }\n}"
  },
  {
    "path": "priv/gettext/en/LC_MESSAGES/errors.po",
    "content": "## `msgid`s in this file come from POT (.pot) files.\n##\n## Do not add, change, or remove `msgid`s manually here as\n## they're tied to the ones in the corresponding POT file\n## (with the same domain).\n##\n## Use `mix gettext.extract --merge` or `mix gettext.merge`\n## to merge POT files into PO files.\nmsgid \"\"\nmsgstr \"\"\n\"Language: en\\n\"\n\n## From Ecto.Changeset.cast/4\nmsgid \"can't be blank\"\nmsgstr \"\"\n\n## From Ecto.Changeset.unique_constraint/3\nmsgid \"has already been taken\"\nmsgstr \"\"\n\n## From Ecto.Changeset.put_change/3\nmsgid \"is invalid\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_acceptance/3\nmsgid \"must be accepted\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_format/3\nmsgid \"has invalid format\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_subset/3\nmsgid \"has an invalid entry\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_exclusion/3\nmsgid \"is reserved\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_confirmation/3\nmsgid \"does not match confirmation\"\nmsgstr \"\"\n\n## From Ecto.Changeset.no_assoc_constraint/3\nmsgid \"is still associated with this entry\"\nmsgstr \"\"\n\nmsgid \"are still associated with this entry\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_length/3\nmsgid \"should be %{count} character(s)\"\nmsgid_plural \"should be %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have %{count} item(s)\"\nmsgid_plural \"should have %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at least %{count} character(s)\"\nmsgid_plural \"should be at least %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have at least %{count} item(s)\"\nmsgid_plural \"should have at least %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at most %{count} character(s)\"\nmsgid_plural \"should be at most %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have at most %{count} item(s)\"\nmsgid_plural \"should have at most %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n## From Ecto.Changeset.validate_number/3\nmsgid \"must be less than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be less than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be equal to %{number}\"\nmsgstr \"\"\n"
  },
  {
    "path": "priv/gettext/errors.pot",
    "content": "## This is a PO Template file.\n##\n## `msgid`s here are often extracted from source code.\n## Add new translations manually only if they're dynamic\n## translations that can't be statically extracted.\n##\n## Run `mix gettext.extract` to bring this file up to\n## date. Leave `msgstr`s empty as changing them here has no\n## effect: edit them in PO (`.po`) files instead.\n\n## From Ecto.Changeset.cast/4\nmsgid \"can't be blank\"\nmsgstr \"\"\n\n## From Ecto.Changeset.unique_constraint/3\nmsgid \"has already been taken\"\nmsgstr \"\"\n\n## From Ecto.Changeset.put_change/3\nmsgid \"is invalid\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_acceptance/3\nmsgid \"must be accepted\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_format/3\nmsgid \"has invalid format\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_subset/3\nmsgid \"has an invalid entry\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_exclusion/3\nmsgid \"is reserved\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_confirmation/3\nmsgid \"does not match confirmation\"\nmsgstr \"\"\n\n## From Ecto.Changeset.no_assoc_constraint/3\nmsgid \"is still associated with this entry\"\nmsgstr \"\"\n\nmsgid \"are still associated with this entry\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_length/3\nmsgid \"should be %{count} character(s)\"\nmsgid_plural \"should be %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have %{count} item(s)\"\nmsgid_plural \"should have %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at least %{count} character(s)\"\nmsgid_plural \"should be at least %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have at least %{count} item(s)\"\nmsgid_plural \"should have at least %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at most %{count} character(s)\"\nmsgid_plural \"should be at most %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have at most %{count} item(s)\"\nmsgid_plural \"should have at most %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n## From Ecto.Changeset.validate_number/3\nmsgid \"must be less than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be less than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be equal to %{number}\"\nmsgstr \"\"\n"
  },
  {
    "path": "priv/repo/dev_seeds.exs",
    "content": "alias Realtime.Api.Tenant\nalias Realtime.Database\nalias Realtime.Repo\nalias Realtime.Tenants\n\ntenant_name = \"realtime-dev\"\ndefault_db_host = \"127.0.0.1\"\n\n{:ok, tenant} =\n  Repo.transaction(fn ->\n    case Repo.get_by(Tenant, external_id: tenant_name) do\n      %Tenant{} = tenant -> Repo.delete!(tenant)\n      nil -> {:ok, nil}\n    end\n\n    tenant =\n      %Tenant{}\n      |> Tenant.changeset(%{\n        \"name\" => tenant_name,\n        \"external_id\" => tenant_name,\n        \"jwt_secret\" => System.get_env(\"API_JWT_SECRET\", \"super-secret-jwt-token-with-at-least-32-characters-long\"),\n        \"jwt_jwks\" => System.get_env(\"API_JWT_JWKS\") |> then(fn v -> if v, do: Jason.decode!(v) end),\n        \"extensions\" => [\n          %{\n            \"type\" => \"postgres_cdc_rls\",\n            \"settings\" => %{\n              \"db_name\" => System.get_env(\"DB_NAME\", \"postgres\"),\n              \"db_host\" => System.get_env(\"DB_HOST\", default_db_host),\n              \"db_user\" => System.get_env(\"DB_USER\", \"supabase_admin\"),\n              \"db_password\" => System.get_env(\"DB_PASSWORD\", \"postgres\"),\n              \"db_port\" => System.get_env(\"DB_PORT\", \"5433\"),\n              \"region\" => \"us-east-1\",\n              \"poll_interval_ms\" => 100,\n              \"poll_max_record_bytes\" => 1_048_576,\n              \"ssl_enforced\" => false\n            }\n          }\n        ]\n      })\n      |> Repo.insert!()\n\n    tenant\n  end)\n\n# Reset Tenant DB\nsettings = Database.from_tenant(tenant, \"realtime_migrations\", :stop)\nsettings = %{settings | max_restarts: 0, ssl: false}\n{:ok, tenant_conn} = Database.connect_db(settings)\npublication = \"supabase_realtime\"\n\nPostgrex.transaction(tenant_conn, fn db_conn ->\n  Postgrex.query!(db_conn, \"DROP SCHEMA IF EXISTS realtime CASCADE\", [])\n  Postgrex.query!(db_conn, \"CREATE SCHEMA IF NOT EXISTS realtime\", [])\n\n  [\n    \"drop publication if exists #{publication}\",\n    \"drop table if exists public.test_tenant;\",\n    \"create table public.test_tenant ( id SERIAL PRIMARY KEY, details text );\",\n    \"grant all on table public.test_tenant to anon;\",\n    \"grant all on table public.test_tenant to supabase_admin;\",\n    \"grant all on table public.test_tenant to authenticated;\",\n    \"create publication #{publication} for table public.test_tenant\"\n  ]\n  |> Enum.each(&Postgrex.query!(db_conn, &1))\nend)\n\ncase Tenants.Migrations.run_migrations(tenant) do\n  :ok -> :ok\n  :noop -> :ok\n  _ -> raise \"Running Migrations failed\"\nend\n\nTenants.Migrations.run_migrations(tenant)\n"
  },
  {
    "path": "priv/repo/migrations/.formatter.exs",
    "content": "[\n  import_deps: [:ecto_sql],\n  inputs: [\"*.exs\"]\n]\n"
  },
  {
    "path": "priv/repo/migrations/20210706140551_create_tenant.exs",
    "content": "defmodule Realtime.Repo.Migrations.CreateTenants do\n  use Ecto.Migration\n\n  def change do\n    create table(:tenants, primary_key: false) do\n      add(:id, :binary_id, primary_key: true)\n      add(:name, :string)\n      add(:external_id, :string)\n      add(:jwt_secret, :string, size: 500)\n      add(:max_concurrent_users, :integer, default: 10_000)\n      timestamps()\n    end\n\n    create(index(:tenants, [:external_id], unique: true))\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20220329161857_add_extensions_table.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddExtensionsTable do\n  use Ecto.Migration\n\n  def change do\n    create table(:extensions, primary_key: false) do\n      add(:id, :binary_id, primary_key: true)\n      add(:type, :string)\n      add(:settings, :map)\n\n      add(\n        :tenant_external_id,\n        references(:tenants, on_delete: :delete_all, type: :string, column: :external_id)\n      )\n\n      timestamps()\n    end\n\n    create(index(:extensions, [:tenant_external_id, :type], unique: true))\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20220410212326_add_tenant_max_eps.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddTenantMaxEps do\n  use Ecto.Migration\n\n  def up do\n    alter table(\"tenants\") do\n      add(:max_events_per_second, :integer, default: 10_000)\n    end\n  end\n\n  def down do\n    alter table(\"tenants\") do\n      remove(:max_events_per_second)\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20220506102948_rename_poll_interval_to_poll_interval_ms.exs",
    "content": "defmodule Realtime.Repo.Migrations.RenamePollIntervalToPollIntervalMs do\n  use Ecto.Migration\n  import Realtime.Api, only: [rename_settings_field: 2]\n\n  def up do\n    rename_settings_field(\"poll_interval\", \"poll_interval_ms\")\n  end\n\n  def down do\n    rename_settings_field(\"poll_interval_ms\", \"poll_interval\")\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20220527210857_add_external_id_uniq_index.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddExternalIdUniqIndex do\n  use Ecto.Migration\n\n  def change do\n    execute(\"alter table tenants add constraint uniq_external_id unique (external_id)\")\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20220815211129_new_max_events_per_second_default.exs",
    "content": "defmodule Realtime.Repo.Migrations.NewMaxEventsPerSecondDefault do\n  use Ecto.Migration\n\n  def change do\n    alter table(\"tenants\") do\n      modify(:max_events_per_second, :integer, null: false, default: 1_000)\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20220815215024_set_current_max_events_per_second.exs",
    "content": "defmodule Realtime.Repo.Migrations.SetCurrentMaxEventsPerSecond do\n  use Ecto.Migration\n\n  def change do\n    execute(\n      \"update tenants set max_events_per_second = 1000\",\n      \"update tenants set max_events_per_second = 10000\"\n    )\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20220818141501_change_limits_defaults.exs",
    "content": "defmodule Realtime.Repo.Migrations.ChangeLimitsDefaults do\n  use Ecto.Migration\n\n  def change do\n    alter table(\"tenants\") do\n      modify(:max_events_per_second, :integer,\n        null: false,\n        default: 100,\n        from: {:integer, null: false, default: 100}\n      )\n\n      modify(:max_concurrent_users, :integer,\n        null: false,\n        default: 200,\n        from: {:integer, null: false, default: 200}\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20221018173709_add_cdc_default.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddCdcDefault do\n  use Ecto.Migration\n\n  def up do\n    alter table(\"tenants\") do\n      add(:postgres_cdc_default, :string, default: \"postgres_cdc_rls\")\n    end\n  end\n\n  def down do\n    alter table(\"tenants\") do\n      remove(:postgres_cdc_default)\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20221102172703_rename_pg_type.exs",
    "content": "defmodule Realtime.Repo.Migrations.RenamePgType do\n  use Ecto.Migration\n\n  def up do\n    execute(\"update extensions set type = 'postgres_cdc_rls'\")\n  end\n\n  def down do\n    execute(\"update extensions set type = 'postgres'\")\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20221223010058_drop_tenants_uniq_external_id_index.exs",
    "content": "defmodule Realtime.Repo.Migrations.DropTenantsUniqExternalIdIndex do\n  use Ecto.Migration\n\n  def change do\n    execute(\"ALTER TABLE IF EXISTS tenants DROP CONSTRAINT IF EXISTS uniq_external_id\")\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20230110180046_add_limits_fields_to_tenants.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddLimitsFieldsToTenants do\n  use Ecto.Migration\n\n  def change do\n    alter table(\"tenants\") do\n      add(:max_bytes_per_second, :integer, default: 100_000, null: false)\n      add(:max_channels_per_client, :integer, default: 100, null: false)\n      add(:max_joins_per_second, :integer, default: 500, null: false)\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20230810220907_alter_tenants_table_columns_to_text.exs",
    "content": "defmodule Realtime.Repo.Migrations.AlterTenantsTableColumnsToText do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      modify :name, :text\n      modify :external_id, :text\n      modify :jwt_secret, :text\n      modify :postgres_cdc_default, :text, default: \"postgres_cdc_rls\"\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20230810220924_alter_extensions_table_columns_to_text.exs",
    "content": "defmodule Realtime.Repo.Migrations.AlterExtensionsTableColumnsToText do\n  use Ecto.Migration\n\n  def change do\n    alter table(:extensions) do\n      modify :type, :text\n      modify :tenant_external_id, :text\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20231024094642_add_tenant_suspend_flag.exs",
    "content": "defmodule :\"Elixir.Realtime.Repo.Migrations.Add-tenant-suspend-flag\" do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      add :suspend, :boolean, default: false\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20240306114423_add_tenant_jwt_jwks.exs",
    "content": "defmodule Realtime.Repo.Migrations.AdddTenantJwtJwksColumn do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      add :jwt_jwks, :map, default: nil\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20240418082835_add_authorization_flag.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddAuthorizationFlag do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      add :enable_authorization, :boolean, default: false\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20240625211759_remove_enable_authorization_flag.exs",
    "content": "defmodule Realtime.Repo.Migrations.RemoveEnableAuthorizationFlag do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      remove :enable_authorization\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20240704172020_add_notify_private_alpha.exs",
    "content": "defmodule :\"Elixir.Realtime.Repo.Migrations.Add-notify-private-alpha\" do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      add :notify_private_alpha, :boolean, default: false\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20240902173232_add_extension_external_id_index.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddExtensionExternalIdIndex do\n  use Ecto.Migration\n\n  def change do\n    create_if_not_exists index(\"extensions\", [:tenant_external_id])\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20241106103258_add_private_only_flag_column_to_tenant.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddPrivateOnlyFlagColumnToTenant do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      add(:private_only, :boolean, default: false, null: false)\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20250424203323_add_migrations_ran_to_tenant.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddMigrationsRanToTenant do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      add(:migrations_ran, :integer, default: 0)\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20250613072131_add_tenant_broadcast_adapter.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddTenantBroadcastAdapter do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      add :broadcast_adapter, :string, default: \"phoenix\"\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20250711044927_change_default_broadcast_adapter_to_gen_rpc.exs",
    "content": "defmodule Realtime.Repo.Migrations.ChangeDefaultBroadcastAdapterToGenRpc do\n  use Ecto.Migration\n\n  def change do\n    alter table(\"tenants\") do\n      modify :broadcast_adapter, :string, default: \"gen_rpc\", from: {:string, default: \"phoenix\"}\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20250811121559_add_max_presence_events_per_second.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddMaxPresenceEventsPerSecond do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      add :max_presence_events_per_second, :integer, default: 10000\n      add :max_payload_size_in_kb, :integer, default: 3000\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20250926223044_set_default_presence_value.exs",
    "content": "defmodule Realtime.Repo.Migrations.SetDefaultPresenceValue do\n  use Ecto.Migration\n  @disable_ddl_transaction true\n  @disable_migration_lock true\n  def change do\n    alter table(:tenants) do\n      modify :max_presence_events_per_second, :integer, default: 1000\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20251204170944_nullable_jwt_secrets.exs",
    "content": "defmodule Realtime.Repo.Migrations.NullableJwtSecrets do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      modify :jwt_secret, :text, null: true\n    end\n\n    create constraint(:tenants, :jwt_secret_or_jwt_jwks_required,\n             check: \"jwt_secret IS NOT NULL OR jwt_jwks IS NOT NULL\"\n           )\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20251218000543_ensure_jwt_secret_is_text.exs",
    "content": "defmodule Realtime.Repo.Migrations.EnsureJwtSecretIsText do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      modify :jwt_secret, :text, null: true\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20260209232800_add_max_client_presence_events_per_second.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddMaxClientPresenceEventsPerSecond do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      add :max_client_presence_events_per_window, :integer, null: true\n      add :client_presence_window_ms, :integer, null: true\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20260304000000_add_presence_enabled_to_tenants.exs",
    "content": "defmodule Realtime.Repo.Migrations.AddPresenceEnabledToTenants do\n  use Ecto.Migration\n\n  def change do\n    alter table(:tenants) do\n      add :presence_enabled, :boolean, default: false, null: false\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/seeds.exs",
    "content": "require Logger\n\nimport Ecto.Adapters.SQL, only: [query: 3]\n\nalias Realtime.Api.Tenant\nalias Realtime.Repo\nalias Realtime.Tenants\n\ntenant_name = System.get_env(\"SELF_HOST_TENANT_NAME\", \"realtime-dev\")\ndefault_db_host = \"host.docker.internal\"\n\n{:ok, tenant} =\n  Repo.transaction(fn ->\n    case Repo.get_by(Tenant, external_id: tenant_name) do\n      %Tenant{} = tenant -> Repo.delete!(tenant)\n      nil -> {:ok, nil}\n    end\n\n    %Tenant{}\n    |> Tenant.changeset(%{\n      \"name\" => tenant_name,\n      \"external_id\" => tenant_name,\n      \"jwt_secret\" => System.get_env(\"API_JWT_SECRET\", \"super-secret-jwt-token-with-at-least-32-characters-long\"),\n      \"jwt_jwks\" => System.get_env(\"API_JWT_JWKS\") |> then(fn v -> if v, do: Jason.decode!(v) end),\n      \"extensions\" => [\n        %{\n          \"type\" => \"postgres_cdc_rls\",\n          \"settings\" => %{\n            \"db_name\" => System.get_env(\"DB_NAME\", \"postgres\"),\n            \"db_host\" => System.get_env(\"DB_HOST\", default_db_host),\n            \"db_user\" => System.get_env(\"DB_USER\", \"supabase_admin\"),\n            \"db_password\" => System.get_env(\"DB_PASSWORD\", \"postgres\"),\n            \"db_port\" => System.get_env(\"DB_PORT\", \"5433\"),\n            \"region\" => \"us-east-1\",\n            \"poll_interval_ms\" => 100,\n            \"poll_max_record_bytes\" => 1_048_576,\n            \"ssl_enforced\" => false\n          }\n        }\n      ]\n    })\n    |> Repo.insert!()\n  end)\n\ntenant = Tenants.get_tenant_by_external_id(tenant_name)\n\nwith res when res in [:noop, :ok] <- Tenants.Migrations.run_migrations(tenant),\n     :ok <- Tenants.Janitor.MaintenanceTask.run(tenant.external_id) do\n  Logger.info(\"Tenant set-up successfully\")\nelse\n  error ->\n    Logger.info(\"Failed to set-up tenant: #{inspect(error)}\")\nend\n"
  },
  {
    "path": "priv/repo/seeds_before_migration.exs",
    "content": "import Ecto.Adapters.SQL, only: [query: 3]\n\n[\n  \"create schema if not exists realtime\"\n]\n|> Enum.each(&query(Realtime.Repo, &1, []))\n"
  },
  {
    "path": "priv/static/robots.txt",
    "content": "# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file\n#\n# To ban all spiders from the entire site uncomment the next two lines:\n# User-agent: *\n# Disallow: /\n"
  },
  {
    "path": "priv/static/worker.js",
    "content": "addEventListener(\"message\", (e) => {\n  if (e.data.event === \"start\") {\n    setInterval(() => postMessage({ event: \"keepAlive\" }), e.data.interval);\n  }\n});\n"
  },
  {
    "path": "rel/env.bat.eex",
    "content": "@echo off\nrem Set the release to work across nodes. If using the long name format like\nrem the one below (my_app@127.0.0.1), you need to also uncomment the\nrem RELEASE_DISTRIBUTION variable below. Must be \"sname\", \"name\" or \"none\".\nrem set RELEASE_DISTRIBUTION=name\nrem set RELEASE_NODE=<%= @release.name %>@127.0.0.1\n"
  },
  {
    "path": "rel/env.sh.eex",
    "content": "#!/bin/sh\n\n# Set the release to work across nodes. If using the long name format like\n# the one below (my_app@127.0.0.1), you need to also uncomment the\n# RELEASE_DISTRIBUTION variable below. Must be \"sname\", \"name\" or \"none\".\n\n# for Fly.io\nip=$(grep fly-local-6pn /etc/hosts | cut -f 1)\n\nif [ \"$AWS_EXECUTION_ENV\" = \"AWS_ECS_FARGATE\" ]; then\n  # for AWS ECS Fargate\n  ip=$(hostname -I | awk '{print $3}')\nelif [ -n \"${POD_IP}\" ]; then\n  # for kubernetes\n  ip=${POD_IP}\nfi\n\n# default to localhost\nif [ -z $ip ]; then\n  ip=127.0.0.1\nfi\n\n# assign the value of NODE_NAME if it exists, else assign the value of FLY_APP_NAME,\n# and if that doesn't exist either, assign \"realtime\" to node_name\nnode_name=\"${NODE_NAME:=${APP_NAME:=realtime}}\"\n\nexport RELEASE_DISTRIBUTION=name\nexport RELEASE_NODE=$node_name@$ip\n"
  },
  {
    "path": "rel/overlays/bin/migrate",
    "content": "#!/bin/sh\ncd -P -- \"$(dirname -- \"$0\")\"\nexec ./realtime eval Realtime.Release.migrate\n"
  },
  {
    "path": "rel/overlays/bin/migrate.bat",
    "content": "call \"%~dp0\\realtime\" eval Realtime.Release.migrate\n"
  },
  {
    "path": "rel/overlays/bin/server",
    "content": "#!/bin/sh\ncd -P -- \"$(dirname -- \"$0\")\"\nPHX_SERVER=true exec ./realtime start\n"
  },
  {
    "path": "rel/overlays/bin/server.bat",
    "content": "set PHX_SERVER=true\ncall \"%~dp0\\realtime\" start\n"
  },
  {
    "path": "rel/overlays/config.example.yml",
    "content": "endpoint_port: 4000\ndb_repo:\n  - hostname: \"127.0.0.1\"\n    username: \"postgres\"\n    password: \"postgres\"\n    database: \"postgres\"\n    pool_size: 3\n    port: 5432\ncluster:\n  - cookie: \"cookie_config\"\n    service: \"realtime-dns\"\n    application_name: \"realtime\"\n    polling_interval: 5000\n    # debug: false\n"
  },
  {
    "path": "rel/vm.args.eex",
    "content": "## Customize flags given to the VM: http://erlang.org/doc/man/erl.html\n## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here\n\n## Number of dirty schedulers doing IO work (file, sockets, and others)\n##+SDio 5\n\n## Increase number of concurrent ports/sockets\n##+Q 65536\n\n## Tweak GC to run more often\n##-env ERL_FULLSWEEP_AFTER 10\n\n## Limit process heap for all procs to 2500 MB. The number here is the number of words\n+hmax <%= div(2_500_000_000, :erlang.system_info(:wordsize)) %>\n\n## Set distribution buffer busy limit (default is 1024)\n+zdbbl 100000\n\n## Disable Busy Wait\n+sbwt none\n+sbwtdio none\n+sbwtdcpu none\n"
  },
  {
    "path": "run.sh",
    "content": "#!/bin/bash\nset -euo pipefail\nset -x\nulimit -n\n\nif [ ! -z \"${RLIMIT_NOFILE:-}\" ]; then\n    echo \"Setting RLIMIT_NOFILE to ${RLIMIT_NOFILE}\"\n    ulimit -Sn \"$RLIMIT_NOFILE\"\nfi\n\nexport ERL_CRASH_DUMP=/tmp/erl_crash.dump\n\nupload_crash_dump_to_s3() {\n    EXIT_CODE=${?:-0}\n    bucket=$ERL_CRASH_DUMP_S3_BUCKET\n    s3Host=$ERL_CRASH_DUMP_S3_HOST\n    s3Port=$ERL_CRASH_DUMP_S3_PORT\n\n    if [ \"${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI-}\" ]; then\n        response=$(curl -s http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI)\n        s3Key=$(echo \"$response\" | grep -o '\"AccessKeyId\": *\"[^\"]*\"' | grep -o '\"[^\"]*\"$' | tr -d '\"')\n        s3Secret=$(echo \"$response\" | grep -o '\"SecretAccessKey\": *\"[^\"]*\"' | grep -o '\"[^\"]*\"$' | tr -d '\"')\n    else\n        s3Key=$ERL_CRASH_DUMP_S3_KEY\n        s3Secret=$ERL_CRASH_DUMP_S3_SECRET\n    fi\n\n    filePath=${ERL_CRASH_DUMP_FOLDER:-tmp}/$(date +%s)_${ERL_CRASH_DUMP_FILE_NAME:-erl_crash.dump}\n\n    if [ -f \"${ERL_CRASH_DUMP_FOLDER:-tmp}/${ERL_CRASH_DUMP_FILE_NAME:-erl_crash.dump}\" ]; then\n        mv ${ERL_CRASH_DUMP_FOLDER:-tmp}/${ERL_CRASH_DUMP_FILE_NAME:-erl_crash.dump} $filePath\n\n        resource=\"/${bucket}/realtime/crash_dumps${filePath}\"\n\n        contentType=\"application/octet-stream\"\n        dateValue=$(date -R)\n        stringToSign=\"PUT\\n\\n${contentType}\\n${dateValue}\\n${resource}\"\n\n        signature=$(echo -en ${stringToSign} | openssl sha1 -hmac ${s3Secret} -binary | base64)\n\n        if [ \"${ERL_CRASH_DUMP_S3_SSL:-}\" = true ]; then\n            protocol=\"https\"\n        else\n            protocol=\"http\"\n        fi\n\n        curl -v -X PUT -T \"${filePath}\" \\\n            -H \"Host: ${s3Host}\" \\\n            -H \"Date: ${dateValue}\" \\\n            -H \"Content-Type: ${contentType}\" \\\n            -H \"Authorization: AWS ${s3Key}:${signature}\" \\\n            ${protocol}://${s3Host}:${s3Port}${resource}\n    fi\n\n    exit \"$EXIT_CODE\"\n}\n\ngenerate_certs() {\n    aws secretsmanager get-secret-value --secret-id \"${CLUSTER_SECRET_ID}\" --region \"${CLUSTER_SECRET_REGION}\" | jq -r '.SecretString' > cert_secrets\n    jq -r '.key' cert_secrets | base64 -d > ca.key\n    jq -r '.cert' cert_secrets | base64 -d > ca.cert\n    openssl req -new -nodes -out server.csr -keyout server.key -subj \"/C=US/ST=Delaware/L=New Castle/O=Supabase Inc/CN=$(hostname -f)\"\n    openssl x509 -req -in server.csr -days 90 -CA ca.cert -CAkey ca.key -out server.cert\n    rm -f ca.key\n    CWD=`pwd`\n    export GEN_RPC_CACERTFILE=\"$CWD/ca.cert\"\n    export GEN_RPC_KEYFILE=\"$CWD/server.key\"\n    export GEN_RPC_CERTFILE=\"$CWD/server.cert\"\n    chmod a+r \"$GEN_RPC_CACERTFILE\"\n    chmod a+r \"$GEN_RPC_KEYFILE\"\n    chmod a+r \"$GEN_RPC_CERTFILE\"\n    cat > inet_tls.conf <<EOF\n[\n  {server, [\n    {certfile, \"${GEN_RPC_CERTFILE}\"},\n    {keyfile, \"${GEN_RPC_KEYFILE}\"},\n    {secure_renegotiate, true}\n  ]},\n  {client, [\n    {cacertfile, \"${GEN_RPC_CACERTFILE}\"},\n    {verify, verify_none},\n    {secure_renegotiate, true}\n  ]}\n].\nEOF\n    export ERL_AFLAGS=\"${ERL_AFLAGS} -proto_dist inet_tls -ssl_dist_optfile ${CWD}/inet_tls.conf\"\n}\n\nif [ \"${ENABLE_ERL_CRASH_DUMP:-false}\" = true ]; then\n    trap upload_crash_dump_to_s3 INT TERM KILL EXIT\nfi\n\nif [[ -n \"${GENERATE_CLUSTER_CERTS:-}\" ]] ; then\n    generate_certs\nfi\n\necho \"Running migrations\"\nsudo -E -u nobody /app/bin/migrate\n\nif [ \"${SEED_SELF_HOST-}\" = true ]; then\n    echo \"Seeding selfhosted Realtime\"\n    sudo -E -u nobody /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)'\nfi\n\necho \"Starting Realtime\"\nulimit -n\nexec \"$@\"\n"
  },
  {
    "path": "test/api_jwt_secret_test.exs",
    "content": "defmodule RealtimeWeb.ApiJwtSecretTest do\n  use RealtimeWeb.ConnCase, async: false\n\n  test \"no api key\", %{conn: conn} do\n    previous = Application.get_env(:realtime, :api_jwt_secret)\n    Application.put_env(:realtime, :api_jwt_secret, nil)\n    on_exit(fn -> Application.put_env(:realtime, :api_jwt_secret, previous) end)\n\n    conn = get(conn, Routes.tenant_path(conn, :index))\n    assert conn.status == 403\n  end\n\n  test \"api key is right\", %{conn: conn} do\n    api_jwt_secret = Application.get_env(:realtime, :api_jwt_secret)\n    jwt = generate_jwt_token(api_jwt_secret)\n    conn = Plug.Conn.put_req_header(conn, \"authorization\", \"Bearer \" <> jwt)\n    conn = get(conn, Routes.tenant_path(conn, :index))\n    assert conn.status == 200\n  end\nend\n"
  },
  {
    "path": "test/e2e/.gitignore",
    "content": ".env\nrealtime-check\nresult\n"
  },
  {
    "path": "test/e2e/.template.env",
    "content": "PROJECT_URL=\nPROJECT_ANON_TOKEN=\nPROJECT_JWT_SECRET="
  },
  {
    "path": "test/e2e/.tool-versions",
    "content": "bun latest\n"
  },
  {
    "path": "test/e2e/README.md",
    "content": "# Realtime E2E tests\n\n| Option | Env var | Description |\n|---|---|---|\n| `--project` | | Supabase project ref (not needed for `--env local`) |\n| `--publishable-key` | `SUPABASE_ANON_KEY` | Project anon/public key |\n| `--secret-key` | `SUPABASE_SERVICE_ROLE_KEY` | Project service role key |\n| `--db-password` | `SUPABASE_DB_PASSWORD` | Database password (required for staging/prod) |\n| `--env` | | `local` \\| `staging` \\| `prod` (default: `prod`) |\n| `--domain` | | Email domain for the test user (default: `example.com`) |\n| `--port` | | Override URL port (useful for local) |\n| `--test` | | Comma-separated list of test categories to run (runs all if omitted) |\n| `--json` | | Output results as JSON to stdout (all other output goes to stderr) |\n\nSensitive credentials (`--secret-key`, `SUPABASE_DB_PASSWORD`) should be set as environment variables to avoid them appearing in shell history.\n\nA random test user is created at the start of each run and deleted automatically when it finishes.\n\n## Test categories\n\nPass any combination to `--test` as a comma-separated list:\n\n| Category | Description |\n|---|---|\n| `connection` | WebSocket connect latency and broadcast throughput |\n| `load` | Postgres changes and presence throughput (INSERT / UPDATE / DELETE) |\n| `broadcast` | Self-broadcast and REST broadcast API |\n| `presence` | Presence join on public and private channels |\n| `authorization` | Private channel allow/deny checks |\n| `postgres-changes` | Filtered INSERT, UPDATE, DELETE events and concurrent changes |\n| `broadcast-changes` | Database-triggered broadcast INSERT, UPDATE, DELETE events |\n\n```bash\n# Run only connection and broadcast tests\n./realtime-check --env local --publishable-key <key> --secret-key <key> --test connection,broadcast\n```\n\n## JSON output\n\nWhen `--json` is used, only the JSON is written to stdout — all progress and diagnostic output goes to stderr — making it safe to pipe directly to `jq`:\n\n```bash\n./realtime-check --json ... | jq '.slis'\n./realtime-check --json ... | jq '.suites[\"broadcast extension\"].tests'\n./realtime-check --json ... | jq 'select(.passed == false)'\n```\n\n## Using the binary\n\nThe pre-built binary requires no runtime — just run it directly.\n\n### Local project\n\n```bash\nsupabase start\nSUPABASE_SERVICE_ROLE_KEY=<service-role-key> \\\n  ./realtime-check --env local --publishable-key <anon-key>\n```\n\n### Remote project\n\n```bash\nSUPABASE_SERVICE_ROLE_KEY=<service-role-key> \\\nSUPABASE_DB_PASSWORD=<password> \\\n  ./realtime-check --project <project-ref> --publishable-key <anon-key>\n```\n\n## Using Bun\n\nRequires [Bun](https://bun.sh).\n\n### Run without building\n\n```bash\nbun install\nSUPABASE_SERVICE_ROLE_KEY=<key> SUPABASE_DB_PASSWORD=<pw> \\\n  bun run check -- --project <ref> --publishable-key <key>\n```\n\n### Build the binary\n\n```bash\nbun install\nbun run build\nSUPABASE_SERVICE_ROLE_KEY=<key> SUPABASE_DB_PASSWORD=<pw> \\\n  ./realtime-check --project <ref> --publishable-key <key>\n```\n\n## Using Nix\n\nRequires flakes support. Add this once to `/etc/nix/nix.conf`:\n\n```\nexperimental-features = nix-command flakes\n```\n\n### Build and run\n\n```bash\nbun run nix\nSUPABASE_SERVICE_ROLE_KEY=<key> SUPABASE_DB_PASSWORD=<pw> \\\n  ./result/bin/realtime-check --project <ref> --publishable-key <key>\n```\n\n> **Note:** The nix build locks the dependency hash in `flake.nix`. If you update `package.json` or `bun.lock`, run `nix build` once — it will fail with the new hash in the error output — then update `outputHash` in `flake.nix` accordingly.\n\n---\n\n## Deno tests (legacy)\n\nSee [legacy/README.md](./legacy/README.md).\n"
  },
  {
    "path": "test/e2e/flake.nix",
    "content": "{\n  description = \"realtime-check — Supabase Realtime end-to-end test CLI\";\n\n  inputs = {\n    nixpkgs.url = \"github:NixOS/nixpkgs/nixos-unstable\";\n    flake-utils.url = \"github:numtide/flake-utils\";\n  };\n\n  outputs = { self, nixpkgs, flake-utils }:\n    flake-utils.lib.eachDefaultSystem (system:\n      let\n        pkgs = nixpkgs.legacyPackages.${system};\n      in\n      let\n        src = pkgs.lib.cleanSourceWith {\n          src = ./.;\n          filter = path: type:\n            let baseName = baseNameOf path;\n            in baseName != \"node_modules\" && baseName != \"result\" && baseName != \"realtime-check\";\n        };\n\n        node_modules = pkgs.stdenv.mkDerivation {\n          name = \"realtime-check-node-modules\";\n          inherit src;\n          nativeBuildInputs = [ pkgs.bun ];\n          buildPhase = ''\n            export HOME=$TMPDIR\n            bun install --frozen-lockfile\n          '';\n          installPhase = \"cp -r node_modules $out\";\n          outputHashMode = \"recursive\";\n          outputHashAlgo = \"sha256\";\n          outputHash = \"sha256-MK55AYy2z5nY7B30o8vt34+wk+86Zruz/q2ZDmA951c=\";\n        };\n      in {\n        packages.default = pkgs.stdenv.mkDerivation {\n          pname = \"realtime-check\";\n          version = \"0.0.1\";\n          inherit src;\n          nativeBuildInputs = [ pkgs.bun ];\n          buildPhase = ''\n            export HOME=$TMPDIR\n            cp -r ${node_modules} node_modules\n            chmod -R u+w node_modules\n            bun build --compile --minify-syntax --minify-whitespace --minify-identifiers realtime-check.ts --outfile realtime-check\n          '';\n          installPhase = ''\n            install -Dm755 realtime-check $out/bin/realtime-check\n          '';\n        };\n\n        devShells.default = pkgs.mkShell {\n          buildInputs = [ pkgs.bun ];\n        };\n      }\n    );\n}\n"
  },
  {
    "path": "test/e2e/legacy/.tool-versions",
    "content": "deno latest\n"
  },
  {
    "path": "test/e2e/legacy/README.md",
    "content": "# Deno tests (legacy)\n\nThe original Deno-based tests require manual database setup before running.\n\n## Project environment\n\nRun the following SQL against your project before running the tests:\n\n```sql\nCREATE TABLE public.pg_changes (\n    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n    value text NOT NULL DEFAULT gen_random_uuid ()\n);\n\nCREATE TABLE public.dummy (\n    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n    value text NOT NULL DEFAULT gen_random_uuid ()\n);\n\nCREATE TABLE public.authorization (\n    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n    value text NOT NULL DEFAULT gen_random_uuid ()\n);\n\nCREATE TABLE public.broadcast_changes (\n    id text PRIMARY KEY,\n    value text NOT NULL\n);\n\nCREATE TABLE public.wallet (\n    id text PRIMARY KEY,\n    wallet_id text NOT NULL\n);\nINSERT INTO public.wallet (id, wallet_id) VALUES (1, 'wallet_1');\n\nALTER TABLE public.pg_changes ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.authorization ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.broadcast_changes ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.wallet ENABLE ROW LEVEL SECURITY;\n\nALTER PUBLICATION supabase_realtime\n    ADD TABLE public.pg_changes;\n\nALTER PUBLICATION supabase_realtime\n    ADD TABLE public.dummy;\n\nCREATE POLICY \"authenticated receive on topic\" ON \"realtime\".\"messages\" AS PERMISSIVE\n    FOR SELECT TO authenticated\n        USING ( realtime.topic() like 'topic:%');\n\nCREATE POLICY \"authenticated broadcast on topic\" ON \"realtime\".\"messages\" AS PERMISSIVE\n    FOR INSERT TO authenticated\n        WITH CHECK ( realtime.topic() like 'topic:%');\n\nCREATE POLICY \"authenticated jwt topic in wallet can receive\" ON \"realtime\".\"messages\" AS PERMISSIVE\n    FOR SELECT TO authenticated\n        USING ( realtime.topic() like 'jwt_topic:%' AND exists (select wallet_id from public.wallet where wallet_id = (auth.jwt() -> 'sub')::text));\n\nCREATE POLICY \"authenticated jwt topic in wallet can broadcast\" ON \"realtime\".\"messages\" AS PERMISSIVE\n    FOR INSERT TO authenticated\n        WITH CHECK ( realtime.topic() like 'jwt_topic:%' AND exists (select wallet_id from public.wallet where wallet_id = (auth.jwt() -> 'sub')::text));\n\nCREATE POLICY \"allow authenticated users all access\" ON \"public\".\"pg_changes\" AS PERMISSIVE\n    FOR ALL TO authenticated\n        USING (TRUE);\n\nCREATE POLICY \"authenticated have full access to read on broadcast_changes\" ON \"public\".\"broadcast_changes\" AS PERMISSIVE\n    FOR ALL TO authenticated\n        USING (TRUE);\n\nCREATE OR REPLACE FUNCTION broadcast_changes_for_table_trigger ()\n    RETURNS TRIGGER\n    AS $$\nDECLARE\n    topic text;\nBEGIN\n    topic = 'topic:test';\n    PERFORM\n        realtime.broadcast_changes (topic, TG_OP, TG_OP, TG_TABLE_NAME, TG_TABLE_SCHEMA, NEW, OLD, TG_LEVEL);\n    RETURN NULL;\nEND;\n$$\nLANGUAGE plpgsql;\n\nCREATE TRIGGER broadcast_changes_for_table_public_broadcast_changes_trigger\n    AFTER INSERT OR UPDATE OR DELETE ON broadcast_changes\n    FOR EACH ROW\n    EXECUTE FUNCTION broadcast_changes_for_table_trigger ();\n\nINSERT INTO \"auth\".\"users\" (\"instance_id\", \"id\", \"aud\", \"role\", \"email\", \"encrypted_password\", \"email_confirmed_at\", \"invited_at\", \"confirmation_token\", \"confirmation_sent_at\", \"recovery_token\", \"recovery_sent_at\", \"email_change_token_new\", \"email_change\", \"email_change_sent_at\", \"last_sign_in_at\", \"raw_app_meta_data\", \"raw_user_meta_data\", \"is_super_admin\", \"created_at\", \"updated_at\", \"phone\", \"phone_confirmed_at\", \"phone_change\", \"phone_change_token\", \"phone_change_sent_at\", \"email_change_token_current\", \"email_change_confirm_status\", \"banned_until\", \"reauthentication_token\", \"reauthentication_sent_at\", \"is_sso_user\", \"deleted_at\", \"is_anonymous\") VALUES ('00000000-0000-0000-0000-000000000000', '93c8bc43-c330-4702-aef2-4ba2c298950a', 'authenticated', 'authenticated', 'filipe@supabase.io', '$2a$10$WQ4tbkMVuS2OUmkX.LRC0uRwH6bU39CbI5bdHuLi82UXhUsjhrLP.', '2025-04-03 03:51:28.207805+00', null, '', '2025-04-03 03:50:59.085609+00', '', null, '', '', null, '2025-04-03 08:01:19.813327+00', '{\"provider\": \"email\", \"providers\": [\"email\"]}', '{\"sub\": \"92c8bc43-c330-4702-aef2-4ba2c298950a\", \"email\": \"filipe@supabase.io\", \"email_verified\": true, \"phone_verified\": false}', null, '2025-04-03 03:50:59.038087+00', '2025-04-03 22:09:10.979685+00', null, null, '', '', null, '', '0', null, '', null, 'false', null, 'false');\n```\n\n## Test environment\n\nCreate `.env` based on `.env.template` with:\n- `PROJECT_URL` — URL for the project\n- `PROJECT_ANON_TOKEN` — Anon authentication token for the project\n\n## Run\n\n```bash\ndeno test tests.ts --allow-read --allow-net --trace-leaks --allow-env=WS_NO_BUFFER_UTIL\n```\n"
  },
  {
    "path": "test/e2e/legacy/tests.ts",
    "content": "import { load } from \"https://deno.land/std@0.224.0/dotenv/mod.ts\";\nimport { createClient, SupabaseClient } from \"npm:@supabase/supabase-js@latest\";\nimport { assertEquals } from \"https://deno.land/std@0.224.0/assert/mod.ts\";\nimport {\n  describe,\n  it,\n  afterEach,\n} from \"https://deno.land/std@0.224.0/testing/bdd.ts\";\nimport { sleep } from \"https://deno.land/x/sleep/mod.ts\";\n\nimport { JWTPayload, SignJWT } from \"https://deno.land/x/jose@v5.9.4/index.ts\";\n\nconst env = await load();\n\nconst url = env[\"PROJECT_URL\"];\nconst token = env[\"PROJECT_ANON_TOKEN\"];\nconst jwtSecret = env[\"PROJECT_JWT_SECRET\"];\n\nconst realtime = { heartbeatIntervalMs: 5000, timeout: 5000 };\nconst config = { config: { broadcast: { self: true } } };\n\nlet supabase: SupabaseClient | null;\n\nafterEach(async () => {\n  if (supabase) await stopClient(supabase);\n  supabase = null;\n});\n\ndescribe(\"broadcast extension\", () => {\n  it(\"user is able to receive self broadcast\", async () => {\n    supabase = await createClient(url, token, { realtime });\n\n    let result = null;\n    let event = crypto.randomUUID();\n    let topic = \"topic:\" + crypto.randomUUID();\n    let expectedPayload = { message: crypto.randomUUID() };\n\n    const channel = supabase\n      .channel(topic, config)\n      .on(\"broadcast\", { event }, ({ payload }) => (result = payload))\n      .subscribe();\n\n    while (channel.state == \"joining\") await sleep(0.2);\n\n    await channel.send({\n      type: \"broadcast\",\n      event,\n      payload: expectedPayload,\n    });\n\n    while (result == null) await sleep(0.2);\n    assertEquals(result, expectedPayload);\n  });\n\n  it(\"user is able to use the endpoint to broadcast\", async () => {\n    supabase = await createClient(url, token, { realtime });\n\n    let result = null;\n    let event = crypto.randomUUID();\n    let topic = \"topic:\" + crypto.randomUUID();\n    let expectedPayload = { message: crypto.randomUUID() };\n    const activeChannel = supabase\n      .channel(topic, config)\n      .on(\"broadcast\", { event }, ({ payload }) => (result = payload))\n      .subscribe();\n    while (activeChannel.state == \"joining\") await sleep(0.2);\n\n    // Send from unsubscribed channel\n    supabase.channel(topic, config).httpSend(event, expectedPayload);\n\n    while (result == null) await sleep(0.2);\n\n    assertEquals(result, expectedPayload);\n  });\n});\n\ndescribe(\"presence extension\", () => {\n  it(\"user is able to receive presence updates\", async () => {\n    supabase = await createClient(url, token, { realtime });\n\n    let result: any = [];\n    let error = null;\n    let topic = \"topic:\" + crypto.randomUUID();\n    let message = crypto.randomUUID();\n    let key = crypto.randomUUID();\n    let expectedPayload = { message };\n\n    const config = { config: { broadcast: { self: true }, presence: { key } } };\n    const channel = supabase\n      .channel(topic, config)\n      .on(\"presence\", { event: \"join\" }, ({ key, newPresences }) =>\n        result.push({ key, newPresences })\n      )\n      .subscribe();\n\n    while (channel.state == \"joining\") await sleep(0.2);\n\n    const res = await channel.track(expectedPayload, { timeout: 5000 });\n    if (res == \"timed out\") error = res;\n\n    let presences = result[0].newPresences[0];\n    assertEquals(result[0].key, key);\n    assertEquals(presences.message, message);\n    assertEquals(error, null);\n  });\n\n  it(\"user is able to receive presence updates on private channels\", async () => {\n    supabase = await createClient(url, token, { realtime });\n    await signInUser(supabase, \"filipe@supabase.io\", \"test_test\");\n    await supabase.realtime.setAuth();\n\n    let result: any = [];\n    let error = null;\n    let topic = \"topic:\" + crypto.randomUUID();\n    let message = crypto.randomUUID();\n    let key = crypto.randomUUID();\n    let expectedPayload = { message };\n\n    const config = {\n      config: { private: true, broadcast: { self: true }, presence: { key } },\n    };\n    const channel = supabase\n      .channel(topic, config)\n      .on(\"presence\", { event: \"join\" }, ({ key, newPresences }) =>\n        result.push({ key, newPresences })\n      )\n      .subscribe();\n\n    while (channel.state == \"joining\") await sleep(0.2);\n\n    const res = await channel.track(expectedPayload, { timeout: 5000 });\n    if (res == \"timed out\") error = res;\n\n    let presences = result[0].newPresences[0];\n    assertEquals(result[0].key, key);\n    assertEquals(presences.message, message);\n    assertEquals(error, null);\n  });\n});\n\ndescribe(\"authorization check\", () => {\n  it(\"user using private channel cannot connect if does not have enough permissions\", async () => {\n    supabase = await createClient(url, token, { realtime });\n\n    let errMessage: any = null;\n    let topic = \"topic:\" + crypto.randomUUID();\n\n    const channel = supabase\n      .channel(topic, { config: { private: true } })\n      .subscribe((status: string, err: any) => {\n        if (status == \"CHANNEL_ERROR\") errMessage = err.message;\n      });\n\n    while (channel.state == \"joining\") await sleep(0.2);\n\n    assertEquals(\n      errMessage,\n      `\"Unauthorized: You do not have permissions to read from this Channel topic: ${topic}\"`\n    );\n  });\n\n  it(\"user using private channel can connect if they have enough permissions\", async () => {\n    supabase = await createClient(url, token, { realtime });\n    await signInUser(supabase, \"filipe@supabase.io\", \"test_test\");\n    await supabase.realtime.setAuth();\n\n    let topic = \"topic:\" + crypto.randomUUID();\n    let connected = false;\n\n    const channel = supabase\n      .channel(topic, { config: { private: true } })\n      .subscribe((status: string) => {\n        if (status == \"SUBSCRIBED\") connected = true;\n      });\n\n    while (channel.state == \"joining\") await sleep(0.2);\n\n    assertEquals(connected, true);\n  });\n\n  // it(\"user using private channel for jwt connections can connect if they have enough permissions based on claims\", async () => {\n  //   supabase = await createClient(url, token, { realtime });\n\n  //   let topic = \"jwt_topic:\" + crypto.randomUUID();\n  //   let connected = false;\n  //   let claims = { role: \"authenticated\", sub: \"wallet_1\" };\n  //   let jwt_token = await generateJwtToken(claims);\n\n  //   await supabase.realtime.setAuth(jwt_token);\n\n  //   const channel = supabase\n  //     .channel(topic, { config: { private: true } })\n  //     .subscribe((status: string) => {\n  //       if (status == \"SUBSCRIBED\") connected = true;\n  //     });\n\n  //   while (channel.state == \"joining\") await sleep(0.2);\n\n  //   assertEquals(connected, true);\n  // });\n});\n\ndescribe(\"broadcast changes\", () => {\n  it(\"authenticated user receives insert broadcast change from a specific topic based on id\", async () => {\n    supabase = await createClient(url, token, { realtime });\n    await signInUser(supabase, \"filipe@supabase.io\", \"test_test\");\n    await supabase.realtime.setAuth();\n\n    const table = \"broadcast_changes\";\n    const id = crypto.randomUUID();\n    const originalValue = crypto.randomUUID();\n    const updatedValue = crypto.randomUUID();\n\n    let insertResult: any, updateResult: any, deleteResult: any;\n\n    const channel = supabase\n      .channel(\"topic:test\", { config: { private: true } })\n      .on(\"broadcast\", { event: \"INSERT\" }, (res) => (insertResult = res))\n      .on(\"broadcast\", { event: \"DELETE\" }, (res) => (deleteResult = res))\n      .on(\"broadcast\", { event: \"UPDATE\" }, (res) => (updateResult = res))\n      .subscribe();\n\n    while (channel.state == \"joining\") await sleep(0.2);\n\n    // Test inserts\n    await supabase.from(table).insert({ value: originalValue, id });\n    while (!insertResult) await sleep(0.2);\n    assertEquals(insertResult.payload.record.id, id);\n    assertEquals(insertResult.payload.record.value, originalValue);\n    assertEquals(insertResult.payload.old_record, null);\n    assertEquals(insertResult.payload.operation, \"INSERT\");\n    assertEquals(insertResult.payload.schema, \"public\");\n    assertEquals(insertResult.payload.table, \"broadcast_changes\");\n\n    // Test updates\n    await supabase.from(table).update({ value: updatedValue }).eq(\"id\", id);\n    while (!updateResult) await sleep(0.2);\n    assertEquals(updateResult.payload.record.id, id);\n    assertEquals(updateResult.payload.record.value, updatedValue);\n    assertEquals(updateResult.payload.old_record.id, id);\n    assertEquals(updateResult.payload.old_record.value, originalValue);\n    assertEquals(updateResult.payload.operation, \"UPDATE\");\n    assertEquals(updateResult.payload.schema, \"public\");\n    assertEquals(updateResult.payload.table, \"broadcast_changes\");\n\n    // Test deletes\n    await supabase.from(table).delete().eq(\"id\", id);\n    while (!deleteResult) await sleep(0.2);\n    assertEquals(deleteResult.payload.record, null);\n    assertEquals(deleteResult.payload.old_record.id, id);\n    assertEquals(deleteResult.payload.old_record.value, updatedValue);\n    assertEquals(deleteResult.payload.operation, \"DELETE\");\n    assertEquals(deleteResult.payload.schema, \"public\");\n    assertEquals(deleteResult.payload.table, \"broadcast_changes\");\n  });\n});\n\ndescribe(\"postgres changes extension\", () => {\n  it(\"user is able to receive INSERT only events from a subscribed table with filter applied\", async () => {\n    supabase = await createClient(url, token, { realtime });\n    await signInUser(supabase, \"filipe@supabase.io\", \"test_test\");\n    await supabase.realtime.setAuth();\n\n    let subscribed = null;\n    let result: any = null;\n    let topic = \"topic:\" + crypto.randomUUID();\n\n    let previousId = await executeInsert(supabase, \"pg_changes\");\n    await executeInsert(supabase, \"dummy\");\n\n    const channel = supabase\n      .channel(topic, config)\n      .on(\n        \"postgres_changes\",\n        {\n          event: \"INSERT\",\n          schema: \"public\",\n          table: \"pg_changes\",\n          filter: `id=eq.${previousId + 1}`,\n        },\n        (payload) => (result = payload)\n      )\n      .on(\"system\", \"*\", ({ status }) => (subscribed = status))\n      .subscribe();\n\n    while (channel.state == \"joining\") await sleep(0.2);\n    while (subscribed != \"ok\") await sleep(0.2);\n\n    await executeInsert(supabase, \"pg_changes\");\n    await executeInsert(supabase, \"dummy\");\n\n    while (result == null) await sleep(0.2);\n\n    assertEquals(result.eventType, \"INSERT\");\n    assertEquals(result.new.id, previousId + 1);\n  });\n\n  it(\"user is able to receive UPDATE only events from a subscribed table with filter applied\", async () => {\n    supabase = await createClient(url, token, { realtime });\n    await signInUser(supabase, \"filipe@supabase.io\", \"test_test\");\n    await supabase.realtime.setAuth();\n\n    let result: any = null;\n    let subscribed = null;\n    let topic = \"topic:\" + crypto.randomUUID();\n\n    let mainId = await executeInsert(supabase, \"pg_changes\");\n    let fakeId = await executeInsert(supabase, \"pg_changes\");\n    let dummyId = await executeInsert(supabase, \"dummy\");\n\n    const channel = supabase\n      .channel(topic, config)\n      .on(\n        \"postgres_changes\",\n        {\n          event: \"UPDATE\",\n          schema: \"public\",\n          table: \"pg_changes\",\n          filter: `id=eq.${mainId}`,\n        },\n        (payload) => (result = payload)\n      )\n      .on(\"system\", \"*\", ({ status }) => (subscribed = status))\n      .subscribe();\n\n    while (channel.state == \"joining\") await sleep(0.2);\n    while (subscribed != \"ok\") await sleep(0.2);\n\n    executeUpdate(supabase, \"pg_changes\", mainId);\n    executeUpdate(supabase, \"pg_changes\", fakeId);\n    executeUpdate(supabase, \"dummy\", dummyId);\n\n    while (result == null) await sleep(0.2);\n\n    assertEquals(result.eventType, \"UPDATE\");\n    assertEquals(result.new.id, mainId);\n  });\n\n  it(\"user is able to receive DELETE only events from a subscribed table with filter applied\", async () => {\n    supabase = await createClient(url, token, { realtime });\n    await signInUser(supabase, \"filipe@supabase.io\", \"test_test\");\n    await supabase.realtime.setAuth();\n\n    let result: any = null;\n    let subscribed = null;\n    let topic = \"topic:\" + crypto.randomUUID();\n\n    let mainId = await executeInsert(supabase, \"pg_changes\");\n    let fakeId = await executeInsert(supabase, \"pg_changes\");\n    let dummyId = await executeInsert(supabase, \"dummy\");\n\n    const channel = supabase\n      .channel(topic, config)\n      .on(\n        \"postgres_changes\",\n        {\n          event: \"DELETE\",\n          schema: \"public\",\n          table: \"pg_changes\",\n          filter: `id=eq.${mainId}`,\n        },\n        (payload) => (result = payload)\n      )\n      .on(\"system\", \"*\", ({ status }) => (subscribed = status))\n      .subscribe();\n\n    while (channel.state == \"joining\") await sleep(0.2);\n    while (subscribed != \"ok\") await sleep(0.2);\n\n    executeDelete(supabase, \"pg_changes\", mainId);\n    executeDelete(supabase, \"pg_changes\", fakeId);\n    executeDelete(supabase, \"dummy\", dummyId);\n\n    while (result == null) await sleep(0.2);\n\n    assertEquals(result.eventType, \"DELETE\");\n    assertEquals(result.old.id, mainId);\n  });\n});\n\nasync function signInUser(\n  supabase: SupabaseClient,\n  email: string,\n  password: string\n) {\n  const { data, error } = await supabase.auth.signInWithPassword({\n    email,\n    password,\n  });\n  if (error) throw new Error(`Error signing in: ${error.message}`);\n  return data!.session!.access_token;\n}\n\nasync function stopClient(supabase: SupabaseClient) {\n  await supabase.removeAllChannels();\n  await supabase.auth.stopAutoRefresh();\n  await supabase.auth.signOut();\n}\n\nasync function executeInsert(\n  supabase: SupabaseClient,\n  table: string\n): Promise<number> {\n  const { data, error }: any = await supabase\n    .from(table)\n    .insert([{ value: crypto.randomUUID() }])\n    .select(\"id\");\n\n  if (error) throw new Error(`Error inserting data: ${error.message}`);\n  return data[0].id;\n}\n\nasync function executeUpdate(\n  supabase: SupabaseClient,\n  table: string,\n  id: number\n) {\n  const { data, error } = await supabase\n    .from(table)\n    .update({ value: crypto.randomUUID() })\n    .eq(\"id\", id);\n\n  if (error) throw new Error(`Error updating data: ${error.message}`);\n  return data;\n}\n\nasync function executeDelete(\n  supabase: SupabaseClient,\n  table: string,\n  id: number\n) {\n  const { data, error } = await supabase.from(table).delete().eq(\"id\", id);\n  if (error) {\n    throw new Error(`Error deleting data: ${error.message}`);\n  }\n  return data;\n}\n\nasync function generateJwtToken(payload: JWTPayload) {\n  const secret = new TextEncoder().encode(jwtSecret);\n  const jwt = await new SignJWT(payload)\n    .setProtectedHeader({ alg: \"HS256\", typ: \"JWT\" })\n    .setIssuedAt()\n    .setExpirationTime(\"1h\")\n    .sign(secret);\n\n  return jwt;\n}\n"
  },
  {
    "path": "test/e2e/package.json",
    "content": "{\n  \"name\": \"realtime-check\",\n  \"version\": \"0.0.1\",\n  \"dependencies\": {\n    \"@supabase/supabase-js\": \"latest\",\n    \"cli-table3\": \"^0.6.5\",\n    \"commander\": \"^12.1.0\",\n    \"kleur\": \"^4.1.5\"\n  },\n  \"scripts\": {\n    \"check\": \"bun run realtime-check.ts --\",\n    \"build\": \"bun build --compile --minify-syntax --minify-whitespace --minify-identifiers realtime-check.ts --outfile realtime-check\",\n    \"nix\": \"bun run build && nix build\"\n  }\n}\n"
  },
  {
    "path": "test/e2e/realtime-check.ts",
    "content": "#!/usr/bin/env bun\nimport assert from \"assert\";\nimport { createClient, SupabaseClient } from \"@supabase/supabase-js\";\nimport { Command } from \"commander\";\nimport kleur from \"kleur\";\nimport { SQL } from \"bun\";\nimport Table from \"cli-table3\";\n\nconst program = new Command()\n  .name(\"realtime-check\")\n  .description(\"End-to-end Realtime test suite against any Supabase project\")\n  .option(\"--project <ref>\", \"Supabase project ref (required for staging/prod)\")\n  .option(\"--publishable-key <key>\", \"Project publishable (anon) key\")\n  .option(\"--secret-key <key>\", \"Project secret (service role) key\")\n  .option(\"--db-password <password>\", \"Database password (required for staging/prod, or set SUPABASE_DB_PASSWORD)\")\n  .option(\"--env <env>\", \"Environment: local | staging | prod (default: prod)\", \"prod\")\n  .option(\"--domain <domain>\", \"Email domain for the test user\", \"example.com\")\n  .option(\"--port <port>\", \"Override URL port (useful for local)\")\n  .option(\"--json\", \"Output results as JSON to stdout\")\n  .option(\"--test <categories>\", \"Comma-separated list of test categories to run: functional,load,connection,load-postgres-changes,load-presence,load-broadcast,load-broadcast-from-db,load-broadcast-replay,broadcast,broadcast-replay,presence,authorization,postgres-changes,broadcast-changes\")\n  .parse();\n\nconst opts = program.opts();\nconst ANON_KEY: string = opts.publishableKey ?? process.env.SUPABASE_ANON_KEY;\nconst SERVICE_KEY: string = opts.secretKey ?? process.env.SUPABASE_SERVICE_ROLE_KEY;\nconst dbPassword: string = opts.dbPassword ?? process.env.SUPABASE_DB_PASSWORD ?? \"\";\nconst { project, env, domain: EMAIL_DOMAIN, port, json: JSON_OUTPUT, test: TEST_FILTER } = opts;\n\nconst TEST_CATEGORIES = TEST_FILTER\n  ? TEST_FILTER.split(\",\").map((s: string) => s.trim().toLowerCase())\n  : null;\n\nif (env !== \"local\" && !project) {\n  console.error(\"--project is required for staging and prod environments\");\n  process.exit(1);\n}\nif (env !== \"local\" && !dbPassword) {\n  console.error(\"SUPABASE_DB_PASSWORD env var is required for staging and prod environments\");\n  process.exit(1);\n}\nif (!ANON_KEY) {\n  console.error(\"--publishable-key is required\");\n  process.exit(1);\n}\nif (!SERVICE_KEY) {\n  console.error(\"--secret-key is required\");\n  process.exit(1);\n}\n\nconst PROJECT_URL = (() => {\n  if (env === \"local\") return `http://localhost:${port ?? 54321}`;\n  if (env === \"staging\") return `https://${project}.green.supabase.co`;\n  return `https://${project}.supabase.co`;\n})();\n\nconst DB_URL = (() => {\n  const pw = encodeURIComponent(dbPassword ?? \"postgres\");\n  if (env === \"local\") return `postgresql://postgres:${pw}@localhost:${port ?? 54322}/postgres`;\n  if (env === \"staging\") return `postgresql://postgres:${pw}@db.${project}.green.supabase.co:5432/postgres`;\n  return `postgresql://postgres:${pw}@db.${project}.supabase.co:5432/postgres`;\n})();\n\nconst DB_SSL = env !== \"local\" ? { rejectUnauthorized: false } : false;\n\nconst REALTIME_OPTS = { heartbeatIntervalMs: 5000, timeout: 5000 };\nconst BROADCAST_CONFIG = { config: { broadcast: { self: true } } };\nconst EVENT_TIMEOUT_MS = 8000;\nconst RATE_LIMIT_PAUSE_MS = 2000;\nconst BROADCAST_API_HEADERS = {\n  \"Content-Type\": \"application/json\",\n  \"Authorization\": `Bearer ${ANON_KEY}`,\n  \"apikey\": ANON_KEY,\n};\nconst LOAD_MESSAGES = 20;\nconst LOAD_SETTLE_MS = 5000;\nconst LOAD_DELIVERY_SLO = 99;\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\nconst log = (...args: unknown[]) => JSON_OUTPUT ? process.stderr.write(args.map(String).join(\" \") + \"\\n\") : console.log(...args);\nconst progress = (msg: string) => JSON_OUTPUT ? process.stderr.write(msg) : process.stdout.write(msg);\n\nfunction measureThroughput(latencies: number[], total: number, label: string, slo: number): Metric[] {\n  const delivered = latencies.length;\n  const deliveryRate = (delivered / total) * 100;\n  const sorted = latencies.slice().sort((a, b) => a - b);\n  if (delivered < total) log(`    ${kleur.yellow(`lost ${total - delivered}/${total} ${label}`)}`);\n  assert(deliveryRate >= slo, `Delivery rate ${deliveryRate.toFixed(1)}% below ${slo}% SLO`);\n  return [\n    { label: \"delivered\", value: deliveryRate, unit: \"%\" },\n    { label: \"p50\", value: sorted[Math.ceil(sorted.length * 0.5) - 1] ?? 0, unit: \"ms\" },\n    { label: \"p95\", value: sorted[Math.ceil(sorted.length * 0.95) - 1] ?? 0, unit: \"ms\" },\n    { label: \"p99\", value: sorted[Math.ceil(sorted.length * 0.99) - 1] ?? 0, unit: \"ms\" },\n  ];\n}\n\ntype Metric = { label: string; value: number; unit: string };\ntype TestResult = { suite: string; name: string; passed: boolean; durationMs: number; metrics: Metric[]; error?: string };\n\nlet currentSuite = \"\";\nconst results: TestResult[] = [];\n\nasync function test(name: string, fn: () => Promise<Metric[]>) {\n  progress(`  ${name} ... `);\n  const start = performance.now();\n  try {\n    const metrics = await fn();\n    const durationMs = performance.now() - start;\n    results.push({ suite: currentSuite, name, passed: true, durationMs, metrics });\n    const summary = metrics.map((m) => `${m.label}: ${kleur.cyan(`${m.value.toFixed(m.unit === \"%\" ? 1 : 0)}${m.unit}`)}`).join(\"  \");\n    log(`${kleur.green(\"PASS\")}  ${kleur.dim(`${durationMs.toFixed(0)}ms`)}${summary ? \"  \" + summary : \"\"}`);\n  } catch (e: any) {\n    const durationMs = performance.now() - start;\n    results.push({ suite: currentSuite, name, passed: false, durationMs, metrics: [], error: e?.message ?? String(e) });\n    log(`${kleur.red(\"FAIL\")}  ${kleur.dim(`${durationMs.toFixed(0)}ms`)}`);\n    log(`    ${kleur.red(e?.message ?? e)}`);\n  }\n}\n\nfunction suite(name: string) {\n  currentSuite = name;\n  log(`\\n${kleur.bold(name)}`);\n}\n\nasync function waitFor<T>(getter: () => T | null, label: string): Promise<{ value: T; latencyMs: number }> {\n  const start = performance.now();\n  const deadline = start + EVENT_TIMEOUT_MS;\n  let value: T | null;\n  while ((value = getter()) === null && performance.now() < deadline) await sleep(50);\n  if (value === null) throw new Error(`Timed out waiting for ${label}`);\n  return { value, latencyMs: performance.now() - start };\n}\n\nasync function stopClient(supabase: SupabaseClient) {\n  await Promise.all([supabase.removeAllChannels(), supabase.auth.stopAutoRefresh()]);\n  await supabase.auth.signOut();\n}\n\nasync function signInUser(supabase: SupabaseClient, email: string, password: string) {\n  const { data, error } = await supabase.auth.signInWithPassword({ email, password });\n  if (error) throw new Error(`Error signing in: ${error.message}`);\n  return data!.session!.access_token;\n}\n\nasync function waitForSubscribed(channel: ReturnType<SupabaseClient[\"channel\"]>): Promise<number> {\n  const start = performance.now();\n  const deadline = start + EVENT_TIMEOUT_MS;\n  while (channel.state === \"joining\" && performance.now() < deadline) await sleep(50);\n  if (channel.state !== \"joined\") throw new Error(`Channel failed to subscribe (state: ${channel.state})`);\n  return performance.now() - start;\n}\n\nasync function waitForPostgresChannel(channel: ReturnType<SupabaseClient[\"channel\"]>): Promise<{ subscribeMs: number; systemMs: number }> {\n  const start = performance.now();\n  let systemOk = false;\n  channel.on(\"system\", \"*\", ({ status }: { status: string }) => { if (status === \"ok\") systemOk = true; });\n  const subscribeMs = await waitForSubscribed(channel);\n  const { latencyMs: systemMs } = await waitFor(() => systemOk ? true : null, \"system ok\");\n  return { subscribeMs, systemMs: performance.now() - start };\n}\n\ntype TableName = \"pg_changes\" | \"dummy\" | \"authorization\" | \"broadcast_changes\" | \"wallet\" | \"replay_check\";\n\nasync function executeInsert(supabase: SupabaseClient, table: TableName): Promise<number> {\n  const { data, error } = await supabase.from(table).insert([{ value: crypto.randomUUID() }]).select(\"id\");\n  if (error) throw new Error(`Error inserting into ${table}: ${error.message}`);\n  return (data as { id: number }[])[0].id;\n}\n\nasync function executeUpdate(supabase: SupabaseClient, table: TableName, id: number) {\n  const { error } = await supabase.from(table).update({ value: crypto.randomUUID() }).eq(\"id\", id);\n  if (error) throw new Error(`Error updating ${table}: ${error.message}`);\n}\n\nasync function executeDelete(supabase: SupabaseClient, table: TableName, id: number) {\n  const { error } = await supabase.from(table).delete().eq(\"id\", id);\n  if (error) throw new Error(`Error deleting from ${table}: ${error.message}`);\n}\n\nasync function setup(): Promise<{ userId: string; testUser: { email: string; password: string } }> {\n  log(kleur.blue(\"Setting up database...\"));\n  const start = performance.now();\n\n  const email = `realtime-check-${crypto.randomUUID()}@${EMAIL_DOMAIN}`;\n  const password = crypto.randomUUID();\n  log(`  Test user: ${kleur.dim(email)}`);\n\n  const sql = new SQL(DB_URL, { tls: DB_SSL || undefined });\n  let userId: string;\n  try {\n    await Promise.all([\n      sql`CREATE TABLE IF NOT EXISTS public.pg_changes (\n            id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n            value text NOT NULL DEFAULT gen_random_uuid()\n          )`,\n      sql`CREATE TABLE IF NOT EXISTS public.dummy (\n            id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n            value text NOT NULL DEFAULT gen_random_uuid()\n          )`,\n      sql`CREATE TABLE IF NOT EXISTS public.authorization (\n            id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n            value text NOT NULL DEFAULT gen_random_uuid()\n          )`,\n      sql`CREATE TABLE IF NOT EXISTS public.broadcast_changes (id text PRIMARY KEY, value text NOT NULL)`,\n      sql`CREATE TABLE IF NOT EXISTS public.wallet (id text PRIMARY KEY, wallet_id text NOT NULL)`,\n      sql`CREATE TABLE IF NOT EXISTS public.replay_check (\n            id text PRIMARY KEY,\n            topic text NOT NULL,\n            event text NOT NULL,\n            payload jsonb NOT NULL DEFAULT '{}'\n          )`,\n    ]);\n\n    await Promise.all([\n      sql`INSERT INTO public.wallet (id, wallet_id) VALUES ('1', 'wallet_1') ON CONFLICT (id) DO NOTHING`,\n      sql`ALTER TABLE public.dummy DISABLE ROW LEVEL SECURITY`,\n      sql`ALTER TABLE public.pg_changes ENABLE ROW LEVEL SECURITY`,\n      sql`ALTER TABLE public.authorization ENABLE ROW LEVEL SECURITY`,\n      sql`ALTER TABLE public.broadcast_changes ENABLE ROW LEVEL SECURITY`,\n      sql`ALTER TABLE public.wallet ENABLE ROW LEVEL SECURITY`,\n      sql`ALTER TABLE public.replay_check ENABLE ROW LEVEL SECURITY`,\n      sql`ALTER PUBLICATION supabase_realtime ADD TABLE public.pg_changes`.catch(() => {}),\n      sql`ALTER PUBLICATION supabase_realtime ADD TABLE public.dummy`.catch(() => {}),\n    ]);\n\n    await Promise.all([\n      sql`DO $$ BEGIN\n            IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'authenticated receive on topic' AND tablename = 'messages' AND schemaname = 'realtime') THEN\n              CREATE POLICY \"authenticated receive on topic\" ON \"realtime\".\"messages\" AS PERMISSIVE\n                FOR SELECT TO authenticated USING (realtime.topic() like 'topic:%');\n            END IF;\n          END $$`,\n      sql`DO $$ BEGIN\n            IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'authenticated broadcast on topic' AND tablename = 'messages' AND schemaname = 'realtime') THEN\n              CREATE POLICY \"authenticated broadcast on topic\" ON \"realtime\".\"messages\" AS PERMISSIVE\n                FOR INSERT TO authenticated WITH CHECK (realtime.topic() like 'topic:%');\n            END IF;\n          END $$`,\n      sql`DO $$ BEGIN\n            IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'allow authenticated users all access' AND tablename = 'pg_changes' AND schemaname = 'public') THEN\n              CREATE POLICY \"allow authenticated users all access\" ON \"public\".\"pg_changes\" AS PERMISSIVE\n                FOR ALL TO authenticated USING (TRUE);\n            END IF;\n          END $$`,\n      sql`DO $$ BEGIN\n            IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'authenticated have full access to read on broadcast_changes' AND tablename = 'broadcast_changes' AND schemaname = 'public') THEN\n              CREATE POLICY \"authenticated have full access to read on broadcast_changes\" ON \"public\".\"broadcast_changes\" AS PERMISSIVE\n                FOR ALL TO authenticated USING (TRUE);\n            END IF;\n          END $$`,\n      sql`DO $$ BEGIN\n            IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'authenticated have full access to replay_check' AND tablename = 'replay_check' AND schemaname = 'public') THEN\n              CREATE POLICY \"authenticated have full access to replay_check\" ON \"public\".\"replay_check\" AS PERMISSIVE\n                FOR ALL TO authenticated USING (TRUE) WITH CHECK (TRUE);\n            END IF;\n          END $$`,\n    ]);\n\n    await sql`\n      CREATE OR REPLACE FUNCTION broadcast_changes_for_table_trigger() RETURNS TRIGGER AS $$\n      DECLARE topic text;\n      BEGIN\n        topic = 'topic:test';\n        PERFORM realtime.broadcast_changes(topic, TG_OP, TG_OP, TG_TABLE_NAME, TG_TABLE_SCHEMA, NEW, OLD, TG_LEVEL);\n        RETURN NULL;\n      END;\n      $$ LANGUAGE plpgsql\n    `;\n\n    await sql`\n      DO $$ BEGIN\n        IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'broadcast_changes_for_table_public_broadcast_changes_trigger') THEN\n          CREATE TRIGGER broadcast_changes_for_table_public_broadcast_changes_trigger\n            AFTER INSERT OR UPDATE OR DELETE ON broadcast_changes\n            FOR EACH ROW EXECUTE FUNCTION broadcast_changes_for_table_trigger();\n        END IF;\n      END $$\n    `;\n\n    await sql`\n      CREATE OR REPLACE FUNCTION replay_check_trigger() RETURNS TRIGGER AS $$\n      BEGIN\n        PERFORM realtime.send(NEW.payload, NEW.event, NEW.topic, true);\n        RETURN NULL;\n      END;\n      $$ LANGUAGE plpgsql\n    `;\n\n    await sql`\n      DO $$ BEGIN\n        IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'replay_check_send_trigger') THEN\n          CREATE TRIGGER replay_check_send_trigger\n            AFTER INSERT ON public.replay_check\n            FOR EACH ROW EXECUTE FUNCTION replay_check_trigger();\n        END IF;\n      END $$\n    `;\n\n    const admin = createClient(PROJECT_URL, SERVICE_KEY);\n    const { data, error } = await admin.auth.admin.createUser({ email, password, email_confirm: true });\n    if (error) throw new Error(`Failed to create test user: ${error.message}`);\n    userId = data.user.id;\n  } finally {\n    await sql.close();\n  }\n\n  log(`${kleur.green(\"Setup complete\")} ${kleur.dim(`(${(performance.now() - start).toFixed(0)}ms)`)}`);\n  return { userId: userId!, testUser: { email, password } };\n}\n\nasync function cleanup(userId: string) {\n  const sql = new SQL(DB_URL, { tls: DB_SSL || undefined });\n  try {\n    await sql`DELETE FROM auth.users WHERE id = ${userId}`;\n  } finally {\n    await sql.close();\n  }\n  log(kleur.dim(\"Test user cleaned up.\"));\n}\n\nasync function runConnectionTest() {\n  suite(\"connection\");\n\n  await test(\"first connect latency\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      const channel = supabase.channel(\"topic:\" + crypto.randomUUID()).subscribe();\n      const connectMs = await waitForSubscribed(channel);\n      return [{ label: \"connect\", value: connectMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await test(\"broadcast message throughput\", async () => {\n    const MESSAGES = 50;\n    const SETTLE_MS = 3000;\n    const DELIVERY_SLO = 99;\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      const topic = \"topic:\" + crypto.randomUUID();\n      const event = \"load\";\n      const sendTimes = new Map<number, number>();\n      const latencies: number[] = [];\n\n      const channel = supabase\n        .channel(topic, BROADCAST_CONFIG)\n        .on(\"broadcast\", { event }, ({ payload }) => {\n          const t = sendTimes.get(payload.seq);\n          if (t !== undefined) latencies.push(performance.now() - t);\n        })\n        .subscribe();\n\n      await waitForSubscribed(channel);\n\n      for (let i = 0; i < MESSAGES; i++) {\n        sendTimes.set(i, performance.now());\n        await channel.send({ type: \"broadcast\", event, payload: { seq: i } });\n      }\n\n      await sleep(SETTLE_MS);\n\n      return measureThroughput(latencies, MESSAGES, \"messages\", DELIVERY_SLO);\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n}\n\nasync function runLoadPostgresChangesTests(testUser: { email: string; password: string }) {\n  suite(\"load-postgres-changes\");\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"postgres changes system message latency\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      const channel = supabase\n        .channel(\"topic:\" + crypto.randomUUID(), BROADCAST_CONFIG)\n        .on(\"postgres_changes\", { event: \"INSERT\", schema: \"public\", table: \"pg_changes\" }, () => {})\n        .subscribe();\n      const { systemMs } = await waitForPostgresChannel(channel);\n      return [{ label: \"system\", value: systemMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"postgres changes INSERT throughput\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      const sendTimes = new Map<number, number>();\n      const latencies: number[] = [];\n\n      const channel = supabase\n        .channel(\"topic:\" + crypto.randomUUID(), BROADCAST_CONFIG)\n        .on(\"postgres_changes\", { event: \"INSERT\", schema: \"public\", table: \"pg_changes\" }, (p) => {\n          const t = sendTimes.get(p.new.id);\n          if (t !== undefined) latencies.push(performance.now() - t);\n        })\n        .subscribe();\n\n      await waitForPostgresChannel(channel);\n\n      for (let i = 0; i < LOAD_MESSAGES; i++) {\n        const t = performance.now();\n        const id = await executeInsert(supabase, \"pg_changes\");\n        sendTimes.set(id, t);\n      }\n\n      await sleep(LOAD_SETTLE_MS);\n\n      return measureThroughput(latencies, LOAD_MESSAGES, \"INSERT events\", LOAD_DELIVERY_SLO);\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"postgres changes UPDATE throughput\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      const ids = await Promise.all(Array.from({ length: LOAD_MESSAGES }, () => executeInsert(supabase, \"pg_changes\")));\n\n      const sendTimes = new Map<number, number>();\n      const latencies: number[] = [];\n\n      const channel = supabase\n        .channel(\"topic:\" + crypto.randomUUID(), BROADCAST_CONFIG)\n        .on(\"postgres_changes\", { event: \"UPDATE\", schema: \"public\", table: \"pg_changes\" }, (p) => {\n          const t = sendTimes.get(p.new.id);\n          if (t !== undefined) latencies.push(performance.now() - t);\n        })\n        .subscribe();\n\n      await waitForPostgresChannel(channel);\n\n      await Promise.all(ids.map((id) => {\n        sendTimes.set(id, performance.now());\n        return executeUpdate(supabase, \"pg_changes\", id);\n      }));\n\n      await sleep(LOAD_SETTLE_MS);\n\n      return measureThroughput(latencies, LOAD_MESSAGES, \"UPDATE events\", LOAD_DELIVERY_SLO);\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"postgres changes DELETE throughput\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      const ids = await Promise.all(Array.from({ length: LOAD_MESSAGES }, () => executeInsert(supabase, \"pg_changes\")));\n\n      const sendTimes = new Map<number, number>();\n      const latencies: number[] = [];\n\n      const channel = supabase\n        .channel(\"topic:\" + crypto.randomUUID(), BROADCAST_CONFIG)\n        .on(\"postgres_changes\", { event: \"DELETE\", schema: \"public\", table: \"pg_changes\" }, (p) => {\n          const t = sendTimes.get(p.old.id);\n          if (t !== undefined) latencies.push(performance.now() - t);\n        })\n        .subscribe();\n\n      await waitForPostgresChannel(channel);\n\n      await Promise.all(ids.map((id) => {\n        sendTimes.set(id, performance.now());\n        return executeDelete(supabase, \"pg_changes\", id);\n      }));\n\n      await sleep(LOAD_SETTLE_MS);\n\n      return measureThroughput(latencies, LOAD_MESSAGES, \"DELETE events\", LOAD_DELIVERY_SLO);\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n}\n\nasync function runLoadPresenceTests() {\n  suite(\"load-presence\");\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"presence join throughput\", async () => {\n    const CLIENTS = 10;\n    const observer = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    const senders: ReturnType<typeof createClient>[] = [];\n    try {\n      const topic = \"topic:\" + crypto.randomUUID();\n      const trackTimes = new Map<string, number>();\n      const latencies: number[] = [];\n\n      const observerChannel = observer\n        .channel(topic, { config: { broadcast: { self: true }, presence: { key: \"observer\" } } })\n        .on(\"presence\", { event: \"join\" }, (e) => {\n          if (e.key === \"observer\") return;\n          const t = trackTimes.get(e.key);\n          if (t !== undefined) latencies.push(performance.now() - t);\n        })\n        .subscribe();\n      await waitForSubscribed(observerChannel);\n\n      const clients = Array.from({ length: CLIENTS }, (_, i) => ({\n        client: createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS }),\n        key: `client-${i}`,\n      }));\n      senders.push(...clients.map((c) => c.client));\n\n      const channels = await Promise.all(clients.map(async ({ client, key }) => {\n        const ch = client.channel(topic, { config: { presence: { key } } }).subscribe();\n        await waitForSubscribed(ch);\n        return { ch, key };\n      }));\n\n      await Promise.all(channels.map(({ ch, key }) => {\n        trackTimes.set(key, performance.now());\n        return ch.track({ key });\n      }));\n\n      await sleep(LOAD_SETTLE_MS);\n\n      return measureThroughput(latencies, CLIENTS, \"presence joins\", LOAD_DELIVERY_SLO);\n    } finally {\n      await Promise.all(senders.map((c) => stopClient(c)));\n      await stopClient(observer);\n    }\n  });\n}\n\nasync function runLoadBroadcastFromDbTests(testUser: { email: string; password: string }) {\n  suite(\"load-broadcast-from-db\");\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"broadcast from database throughput\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      const sendTimes = new Map<string, number>();\n      const latencies: number[] = [];\n\n      const channel = supabase\n        .channel(\"topic:test\", { config: { private: true } })\n        .on(\"broadcast\", { event: \"INSERT\" }, (res) => {\n          const t = sendTimes.get(res.payload.record.id);\n          if (t !== undefined) latencies.push(performance.now() - t);\n        })\n        .subscribe();\n\n      await waitForSubscribed(channel);\n\n      await Promise.all(Array.from({ length: LOAD_MESSAGES }, async () => {\n        const id = crypto.randomUUID();\n        sendTimes.set(id, performance.now());\n        await supabase.from(\"broadcast_changes\").insert({ id, value: crypto.randomUUID() });\n      }));\n\n      await sleep(LOAD_SETTLE_MS);\n\n      await supabase.from(\"broadcast_changes\").delete().in(\"id\", [...sendTimes.keys()]);\n\n      return measureThroughput(latencies, LOAD_MESSAGES, \"broadcast-from-db events\", LOAD_DELIVERY_SLO);\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n}\n\nasync function runLoadBroadcastTests() {\n  suite(\"load-broadcast\");\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"broadcast self throughput\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      const event = \"load\";\n      const topic = \"topic:\" + crypto.randomUUID();\n      const sendTimes = new Map<number, number>();\n      const latencies: number[] = [];\n\n      const channel = supabase\n        .channel(topic, BROADCAST_CONFIG)\n        .on(\"broadcast\", { event }, ({ payload }) => {\n          const t = sendTimes.get(payload.seq);\n          if (t !== undefined) latencies.push(performance.now() - t);\n        })\n        .subscribe();\n\n      await waitForSubscribed(channel);\n\n      for (let i = 0; i < LOAD_MESSAGES; i++) {\n        sendTimes.set(i, performance.now());\n        await channel.send({ type: \"broadcast\", event, payload: { seq: i } });\n      }\n\n      await sleep(LOAD_SETTLE_MS);\n\n      return measureThroughput(latencies, LOAD_MESSAGES, \"broadcast events\", LOAD_DELIVERY_SLO);\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"broadcast API endpoint throughput\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      const event = \"load\";\n      const topic = \"topic:\" + crypto.randomUUID();\n      const sendTimes = new Map<number, number>();\n      const latencies: number[] = [];\n\n      const channel = supabase\n        .channel(topic, BROADCAST_CONFIG)\n        .on(\"broadcast\", { event }, ({ payload }) => {\n          const t = sendTimes.get(payload.seq);\n          if (t !== undefined) latencies.push(performance.now() - t);\n        })\n        .subscribe();\n\n      await waitForSubscribed(channel);\n\n      await Promise.all(Array.from({ length: LOAD_MESSAGES }, async (_, i) => {\n        sendTimes.set(i, performance.now());\n        const res = await fetch(`${PROJECT_URL}/realtime/v1/api/broadcast`, {\n          method: \"POST\",\n          headers: BROADCAST_API_HEADERS,\n          body: JSON.stringify({ messages: [{ topic, event, payload: { seq: i } }] }),\n        });\n        if (!res.ok) throw new Error(`Broadcast API returned ${res.status}`);\n      }));\n\n      await sleep(LOAD_SETTLE_MS);\n\n      return measureThroughput(latencies, LOAD_MESSAGES, \"broadcast API events\", LOAD_DELIVERY_SLO);\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n}\n\nasync function runLoadBroadcastReplayTests(testUser: { email: string; password: string }) {\n  suite(\"load-broadcast-replay\");\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"broadcast replay throughput\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      const event = crypto.randomUUID();\n      const topic = \"topic:\" + crypto.randomUUID();\n\n      const since = Date.now() - 1000;\n      await Promise.all(Array.from({ length: LOAD_MESSAGES }, (_, i) =>\n        supabase.from(\"replay_check\").insert({ id: crypto.randomUUID(), topic, event, payload: { seq: i } })\n      ));\n\n      await sleep(LOAD_SETTLE_MS);\n\n      const latencies: number[] = [];\n      const replayStart = performance.now();\n      const receiver = supabase.channel(topic, {\n        config: { private: true, broadcast: { replay: { since, limit: 25 } } },\n      }).on(\"broadcast\", { event }, () => {\n        latencies.push(performance.now() - replayStart);\n      }).subscribe();\n      await waitForSubscribed(receiver);\n\n      await sleep(LOAD_SETTLE_MS);\n\n      return measureThroughput(latencies, LOAD_MESSAGES, \"replayed broadcast events\", LOAD_DELIVERY_SLO);\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n}\n\n\nasync function runBroadcastTests() {\n  suite(\"broadcast extension\");\n\n  await test(\"user is able to receive self broadcast\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      let result: any = null;\n      const event = crypto.randomUUID();\n      const topic = \"topic:\" + crypto.randomUUID();\n      const expectedPayload = { message: crypto.randomUUID() };\n\n      const channel = supabase\n        .channel(topic, BROADCAST_CONFIG)\n        .on(\"broadcast\", { event }, ({ payload }) => (result = payload))\n        .subscribe();\n\n      const subscribeMs = await waitForSubscribed(channel);\n      await channel.send({ type: \"broadcast\", event, payload: expectedPayload });\n      const { latencyMs: eventMs } = await waitFor(() => result, \"broadcast event\");\n\n      assert.deepStrictEqual(result, expectedPayload);\n      return [{ label: \"subscribe\", value: subscribeMs, unit: \"ms\" }, { label: \"event\", value: eventMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await test(\"user is able to use the endpoint to broadcast\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      let result: any = null;\n      const event = crypto.randomUUID();\n      const topic = \"topic:\" + crypto.randomUUID();\n      const expectedPayload = { message: crypto.randomUUID() };\n\n      const channel = supabase\n        .channel(topic, BROADCAST_CONFIG)\n        .on(\"broadcast\", { event }, ({ payload }) => (result = payload))\n        .subscribe();\n\n      const subscribeMs = await waitForSubscribed(channel);\n\n      const res = await fetch(`${PROJECT_URL}/realtime/v1/api/broadcast`, {\n        method: \"POST\",\n        headers: BROADCAST_API_HEADERS,\n        body: JSON.stringify({ messages: [{ topic, event, payload: expectedPayload }] }),\n      });\n      if (!res.ok) throw new Error(`Broadcast API returned ${res.status}`);\n\n      const { latencyMs: eventMs } = await waitFor(() => result, \"broadcast event\");\n      assert.deepStrictEqual(result, expectedPayload);\n      return [{ label: \"subscribe\", value: subscribeMs, unit: \"ms\" }, { label: \"event\", value: eventMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n}\n\nasync function runPresenceTests(testUser: { email: string; password: string }) {\n  suite(\"presence extension\");\n\n  await test(\"user is able to receive presence updates\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      let joinEvent: any = null;\n      const topic = \"topic:\" + crypto.randomUUID();\n      const message = crypto.randomUUID();\n      const key = crypto.randomUUID();\n\n      const channel = supabase\n        .channel(topic, { config: { broadcast: { self: true }, presence: { key } } })\n        .on(\"presence\", { event: \"join\" }, (e) => (joinEvent = e))\n        .subscribe();\n\n      const subscribeMs = await waitForSubscribed(channel);\n      const trackStart = performance.now();\n      if (await channel.track({ message }, { timeout: 5000 }) === \"timed out\") throw new Error(\"track() timed out\");\n      const trackMs = performance.now() - trackStart;\n      const { latencyMs: eventMs } = await waitFor(() => joinEvent, \"presence join\");\n\n      assert.strictEqual(joinEvent.key, key);\n      assert.strictEqual(joinEvent.newPresences[0].message, message);\n      return [{ label: \"subscribe\", value: subscribeMs, unit: \"ms\" }, { label: \"track\", value: trackMs, unit: \"ms\" }, { label: \"event\", value: eventMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"user is able to receive presence updates on private channels\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n\n      let joinEvent: any = null;\n      const topic = \"topic:\" + crypto.randomUUID();\n      const message = crypto.randomUUID();\n      const key = crypto.randomUUID();\n\n      const channel = supabase\n        .channel(topic, { config: { private: true, broadcast: { self: true }, presence: { key } } })\n        .on(\"presence\", { event: \"join\" }, (e) => (joinEvent = e))\n        .subscribe();\n\n      const subscribeMs = await waitForSubscribed(channel);\n      const trackStart = performance.now();\n      if (await channel.track({ message }, { timeout: 5000 }) === \"timed out\") throw new Error(\"track() timed out\");\n      const trackMs = performance.now() - trackStart;\n      const { latencyMs: eventMs } = await waitFor(() => joinEvent, \"presence join\");\n\n      assert.strictEqual(joinEvent.key, key);\n      assert.strictEqual(joinEvent.newPresences[0].message, message);\n      return [{ label: \"subscribe\", value: subscribeMs, unit: \"ms\" }, { label: \"track\", value: trackMs, unit: \"ms\" }, { label: \"event\", value: eventMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n}\n\nasync function runAuthorizationTests(testUser: { email: string; password: string }) {\n  suite(\"authorization check\");\n\n  await test(\"user using private channel cannot connect without permissions\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      let errMessage: any = null;\n      const topic = \"topic:\" + crypto.randomUUID();\n\n      supabase\n        .channel(topic, { config: { private: true } })\n        .subscribe((status: string, err: any) => {\n          if (status === \"CHANNEL_ERROR\") errMessage = err.message;\n        });\n\n      const { latencyMs: rejectMs } = await waitFor(() => errMessage, \"CHANNEL_ERROR\");\n      assert.strictEqual(\n        errMessage,\n        `\"Unauthorized: You do not have permissions to read from this Channel topic: ${topic}\"`\n      );\n      return [{ label: \"rejection\", value: rejectMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"user using private channel can connect with enough permissions\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n\n      let connected = false;\n      const channel = supabase\n        .channel(\"topic:\" + crypto.randomUUID(), { config: { private: true } })\n        .subscribe((status: string) => { if (status === \"SUBSCRIBED\") connected = true; });\n\n      const subscribeMs = await waitForSubscribed(channel);\n      assert.strictEqual(connected, true);\n      return [{ label: \"subscribe\", value: subscribeMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n}\n\nasync function runBroadcastChangesTests(testUser: { email: string; password: string }) {\n  suite(\"broadcast changes\");\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"authenticated user receives INSERT broadcast change\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      const id = crypto.randomUUID();\n      const value = crypto.randomUUID();\n      let result: any = null;\n\n      const channel = supabase\n        .channel(\"topic:test\", { config: { private: true } })\n        .on(\"broadcast\", { event: \"INSERT\" }, (res) => (result = res))\n        .subscribe();\n\n      const subscribeMs = await waitForSubscribed(channel);\n      await supabase.from(\"broadcast_changes\").insert({ value, id });\n      const { latencyMs: eventMs } = await waitFor(() => result, \"INSERT event\");\n\n      assert.strictEqual(result.payload.record.id, id);\n      assert.strictEqual(result.payload.record.value, value);\n      assert.strictEqual(result.payload.old_record, null);\n      assert.strictEqual(result.payload.operation, \"INSERT\");\n      assert.strictEqual(result.payload.schema, \"public\");\n      assert.strictEqual(result.payload.table, \"broadcast_changes\");\n      return [{ label: \"subscribe\", value: subscribeMs, unit: \"ms\" }, { label: \"event\", value: eventMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"authenticated user receives UPDATE broadcast change\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      const id = crypto.randomUUID();\n      const originalValue = crypto.randomUUID();\n      const updatedValue = crypto.randomUUID();\n      await supabase.from(\"broadcast_changes\").insert({ value: originalValue, id });\n      let result: any = null;\n\n      const channel = supabase\n        .channel(\"topic:test\", { config: { private: true } })\n        .on(\"broadcast\", { event: \"UPDATE\" }, (res) => (result = res))\n        .subscribe();\n\n      const subscribeMs = await waitForSubscribed(channel);\n      await supabase.from(\"broadcast_changes\").update({ value: updatedValue }).eq(\"id\", id);\n      const { latencyMs: eventMs } = await waitFor(() => result, \"UPDATE event\");\n\n      assert.strictEqual(result.payload.record.id, id);\n      assert.strictEqual(result.payload.record.value, updatedValue);\n      assert.strictEqual(result.payload.old_record.id, id);\n      assert.strictEqual(result.payload.old_record.value, originalValue);\n      assert.strictEqual(result.payload.operation, \"UPDATE\");\n      assert.strictEqual(result.payload.schema, \"public\");\n      assert.strictEqual(result.payload.table, \"broadcast_changes\");\n      return [{ label: \"subscribe\", value: subscribeMs, unit: \"ms\" }, { label: \"event\", value: eventMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"authenticated user receives DELETE broadcast change\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      const id = crypto.randomUUID();\n      const value = crypto.randomUUID();\n      await supabase.from(\"broadcast_changes\").insert({ value, id });\n      let result: any = null;\n\n      const channel = supabase\n        .channel(\"topic:test\", { config: { private: true } })\n        .on(\"broadcast\", { event: \"DELETE\" }, (res) => (result = res))\n        .subscribe();\n\n      const subscribeMs = await waitForSubscribed(channel);\n      await supabase.from(\"broadcast_changes\").delete().eq(\"id\", id);\n      const { latencyMs: eventMs } = await waitFor(() => result, \"DELETE event\");\n\n      assert.strictEqual(result.payload.record, null);\n      assert.strictEqual(result.payload.old_record.id, id);\n      assert.strictEqual(result.payload.old_record.value, value);\n      assert.strictEqual(result.payload.operation, \"DELETE\");\n      assert.strictEqual(result.payload.schema, \"public\");\n      assert.strictEqual(result.payload.table, \"broadcast_changes\");\n      return [{ label: \"subscribe\", value: subscribeMs, unit: \"ms\" }, { label: \"event\", value: eventMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n}\n\nasync function runPostgresChangesTests(testUser: { email: string; password: string }) {\n  suite(\"postgres changes extension\");\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"user receives INSERT events with filter\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n\n      let result: unknown = null;\n      const previousId = await executeInsert(supabase, \"pg_changes\");\n      await executeInsert(supabase, \"dummy\");\n\n      const channel = supabase\n        .channel(\"topic:\" + crypto.randomUUID(), BROADCAST_CONFIG)\n        .on(\"postgres_changes\",\n          { event: \"INSERT\", schema: \"public\", table: \"pg_changes\", filter: `id=eq.${previousId + 1}` },\n          (payload) => (result = payload))\n        .subscribe();\n\n      const { subscribeMs } = await waitForPostgresChannel(channel);\n      await executeInsert(supabase, \"pg_changes\");\n      await executeInsert(supabase, \"dummy\");\n      const { latencyMs: eventMs } = await waitFor(() => result, \"INSERT event\");\n\n      assert.strictEqual(result.eventType, \"INSERT\");\n      assert.strictEqual(result.new.id, previousId + 1);\n      return [{ label: \"subscribe\", value: subscribeMs, unit: \"ms\" }, { label: \"event\", value: eventMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"user receives UPDATE events with filter\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n\n      let result: unknown = null;\n      const mainId = await executeInsert(supabase, \"pg_changes\");\n      const fakeId = await executeInsert(supabase, \"pg_changes\");\n      const dummyId = await executeInsert(supabase, \"dummy\");\n\n      const channel = supabase\n        .channel(\"topic:\" + crypto.randomUUID(), BROADCAST_CONFIG)\n        .on(\"postgres_changes\",\n          { event: \"UPDATE\", schema: \"public\", table: \"pg_changes\", filter: `id=eq.${mainId}` },\n          (payload) => (result = payload))\n        .subscribe();\n\n      const { subscribeMs } = await waitForPostgresChannel(channel);\n      await Promise.all([\n        executeUpdate(supabase, \"pg_changes\", mainId),\n        executeUpdate(supabase, \"pg_changes\", fakeId),\n        executeUpdate(supabase, \"dummy\", dummyId),\n      ]);\n      const { latencyMs: eventMs } = await waitFor(() => result, \"UPDATE event\");\n\n      assert.strictEqual(result.eventType, \"UPDATE\");\n      assert.strictEqual(result.new.id, mainId);\n      return [{ label: \"subscribe\", value: subscribeMs, unit: \"ms\" }, { label: \"event\", value: eventMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"user receives DELETE events with filter\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n\n      let result: unknown = null;\n      const mainId = await executeInsert(supabase, \"pg_changes\");\n      const fakeId = await executeInsert(supabase, \"pg_changes\");\n      const dummyId = await executeInsert(supabase, \"dummy\");\n\n      const channel = supabase\n        .channel(\"topic:\" + crypto.randomUUID(), BROADCAST_CONFIG)\n        .on(\"postgres_changes\",\n          { event: \"DELETE\", schema: \"public\", table: \"pg_changes\", filter: `id=eq.${mainId}` },\n          (payload) => (result = payload))\n        .subscribe();\n\n      const { subscribeMs } = await waitForPostgresChannel(channel);\n      await Promise.all([\n        executeDelete(supabase, \"pg_changes\", mainId),\n        executeDelete(supabase, \"pg_changes\", fakeId),\n        executeDelete(supabase, \"dummy\", dummyId),\n      ]);\n      const { latencyMs: eventMs } = await waitFor(() => result, \"DELETE event\");\n\n      assert.strictEqual(result.eventType, \"DELETE\");\n      assert.strictEqual(result.old.id, mainId);\n      return [{ label: \"subscribe\", value: subscribeMs, unit: \"ms\" }, { label: \"event\", value: eventMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await sleep(RATE_LIMIT_PAUSE_MS);\n  await test(\"user receives INSERT, UPDATE and DELETE concurrently\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      let insertResult: unknown = null, updateResult: unknown = null, deleteResult: unknown = null;\n\n      const insertId = await executeInsert(supabase, \"pg_changes\");\n      const updateId = await executeInsert(supabase, \"pg_changes\");\n      const deleteId = await executeInsert(supabase, \"pg_changes\");\n\n      const channel = supabase\n        .channel(\"topic:\" + crypto.randomUUID(), BROADCAST_CONFIG)\n        .on(\"postgres_changes\", { event: \"INSERT\", schema: \"public\", table: \"pg_changes\", filter: `id=eq.${insertId + 3}` }, (p) => (insertResult = p))\n        .on(\"postgres_changes\", { event: \"UPDATE\", schema: \"public\", table: \"pg_changes\", filter: `id=eq.${updateId}` }, (p) => (updateResult = p))\n        .on(\"postgres_changes\", { event: \"DELETE\", schema: \"public\", table: \"pg_changes\", filter: `id=eq.${deleteId}` }, (p) => (deleteResult = p))\n        .subscribe();\n\n      const { subscribeMs } = await waitForPostgresChannel(channel);\n\n      await Promise.all([\n        executeInsert(supabase, \"pg_changes\"),\n        executeUpdate(supabase, \"pg_changes\", updateId),\n        executeDelete(supabase, \"pg_changes\", deleteId),\n      ]);\n\n      const [{ latencyMs: insertMs }, { latencyMs: updateMs }, { latencyMs: deleteMs }] = await Promise.all([\n        waitFor(() => insertResult, \"INSERT event\"),\n        waitFor(() => updateResult, \"UPDATE event\"),\n        waitFor(() => deleteResult, \"DELETE event\"),\n      ]);\n\n      assert.strictEqual(insertResult.eventType, \"INSERT\");\n      assert.strictEqual(updateResult.eventType, \"UPDATE\");\n      assert.strictEqual(deleteResult.eventType, \"DELETE\");\n      return [\n        { label: \"subscribe\", value: subscribeMs, unit: \"ms\" },\n        { label: \"INSERT\", value: insertMs, unit: \"ms\" },\n        { label: \"UPDATE\", value: updateMs, unit: \"ms\" },\n        { label: \"DELETE\", value: deleteMs, unit: \"ms\" },\n      ];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n}\n\nasync function runBroadcastReplayTests(testUser: { email: string; password: string }) {\n  suite(\"broadcast replay\");\n\n  await test(\"replayed messages are delivered on join\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      const event = crypto.randomUUID();\n      const topic = \"topic:\" + crypto.randomUUID();\n      const payload = { message: crypto.randomUUID() };\n\n      const since = Date.now() - 1000;\n      await supabase.from(\"replay_check\").insert({ id: crypto.randomUUID(), topic, event, payload });\n\n      await sleep(500);\n\n      let result: any = null;\n      const receiver = supabase.channel(topic, {\n        config: { private: true, broadcast: { replay: { since, limit: 1 } } },\n      }).on(\"broadcast\", { event }, (msg) => (result = msg.payload)).subscribe();\n      const subscribeMs = await waitForSubscribed(receiver);\n\n      const { latencyMs: replayMs } = await waitFor(() => result, \"replayed broadcast event\");\n\n      assert.strictEqual(result.message, payload.message);\n      return [{ label: \"subscribe\", value: subscribeMs, unit: \"ms\" }, { label: \"replay\", value: replayMs, unit: \"ms\" }];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await test(\"replayed messages carry meta.replayed flag\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      const event = crypto.randomUUID();\n      const topic = \"topic:\" + crypto.randomUUID();\n\n      const since = Date.now() - 1000;\n      await supabase.from(\"replay_check\").insert({ id: crypto.randomUUID(), topic, event, payload: { value: 1 } });\n\n      await sleep(500);\n\n      let receivedMeta: any = null;\n      const receiver = supabase.channel(topic, {\n        config: { private: true, broadcast: { replay: { since, limit: 1 } } },\n      }).on(\"broadcast\", { event }, (msg) => (receivedMeta = msg.meta)).subscribe();\n      await waitForSubscribed(receiver);\n\n      await waitFor(() => receivedMeta, \"replayed broadcast meta\");\n\n      assert.strictEqual(receivedMeta?.replayed, true);\n      return [];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n\n  await test(\"messages before since are not replayed\", async () => {\n    const supabase = createClient(PROJECT_URL, ANON_KEY, { realtime: REALTIME_OPTS });\n    try {\n      await signInUser(supabase, testUser.email, testUser.password);\n      const event = crypto.randomUUID();\n      const topic = \"topic:\" + crypto.randomUUID();\n\n      await supabase.from(\"replay_check\").insert({ id: crypto.randomUUID(), topic, event, payload: { value: \"old\" } });\n\n      await sleep(1000);\n      const since = Date.now();\n\n      let result: any = null;\n      const receiver = supabase.channel(topic, {\n        config: { private: true, broadcast: { replay: { since, limit: 25 } } },\n      }).on(\"broadcast\", { event }, (msg) => (result = msg.payload)).subscribe();\n      await waitForSubscribed(receiver);\n\n      await sleep(2000);\n\n      assert.strictEqual(result, null);\n      return [];\n    } finally {\n      await stopClient(supabase);\n    }\n  });\n}\n\nfunction printSummary(totalMs: number) {\n  const passed = results.filter((r) => r.passed);\n  const failed = results.filter((r) => !r.passed);\n  const suites = [...new Set(results.map((r) => r.suite))];\n\n  if (JSON_OUTPUT) {\n    const slis: Record<string, Record<string, { value: number; unit: string }>> = {};\n    for (const r of passed) {\n      for (const m of r.metrics) {\n        const key = `${r.suite} / ${r.name}`;\n        slis[key] ??= {};\n        slis[key][m.label] = { value: m.value, unit: m.unit };\n      }\n    }\n    const output = {\n      passed: failed.length === 0,\n      durationMs: Math.round(totalMs),\n      summary: { total: results.length, passed: passed.length, failed: failed.length },\n      slis,\n      suites: Object.fromEntries(suites.map((suite) => {\n        const suiteResults = results.filter((r) => r.suite === suite);\n        return [suite, {\n          passed: suiteResults.every((r) => r.passed),\n          tests: suiteResults.map((r) => ({\n            name: r.name,\n            passed: r.passed,\n            durationMs: Math.round(r.durationMs),\n            ...(r.error ? { error: r.error } : {}),\n            slis: Object.fromEntries(r.metrics.map((m) => [m.label, { value: m.value, unit: m.unit }])),\n          })),\n        }];\n      })),\n    };\n    process.stdout.write(JSON.stringify(output, null, 2) + \"\\n\");\n    return;\n  }\n\n  for (const suite of suites) {\n    const suiteResults = results.filter((r) => r.suite === suite);\n    const suiteLabels = [...new Set(suiteResults.flatMap((r) => r.metrics.map((m) => m.label)))];\n\n    const table = new Table({\n      head: [kleur.bold(suite), kleur.dim(\"status\"), kleur.dim(\"total\"), ...suiteLabels.map(kleur.dim)],\n      style: { border: [\"dim\"], head: [] },\n    });\n\n    for (const r of suiteResults) {\n      table.push([\n        `  ${r.name}`,\n        r.passed ? kleur.green(\"PASS\") : kleur.red(\"FAIL\"),\n        kleur.dim(`${r.durationMs.toFixed(0)}ms`),\n        ...suiteLabels.map((label) => {\n          const m = r.metrics.find((x) => x.label === label);\n          return m ? kleur.cyan(`${m.value.toFixed(m.unit === \"%\" ? 1 : 0)}${m.unit}`) : kleur.dim(\"-\");\n        }),\n      ]);\n    }\n\n    log(`\n${table.toString()}`);\n  }\n\n  log(`\n${kleur.bold(`${passed.length} passed, ${failed.length} failed`)}  ${kleur.dim(`total ${(totalMs / 1000).toFixed(2)}s`)}`);\n\n  if (failed.length > 0) {\n    log(\"\\nFailed:\");\n    for (const r of failed) {\n      log(`  ${kleur.red(\"✗\")} ${r.suite} / ${r.name}`);\n      if (r.error) log(`    ${kleur.dim(r.error)}`);\n    }\n  }\n}\n\nconst SUITES: Record<string, (testUser: { email: string; password: string }) => Promise<void>> = {\n  \"connection\": () => runConnectionTest(),\n  \"load-postgres-changes\": (u) => runLoadPostgresChangesTests(u),\n  \"load-presence\": () => runLoadPresenceTests(),\n  \"load-broadcast\": () => runLoadBroadcastTests(),\n  \"load-broadcast-from-db\": (u) => runLoadBroadcastFromDbTests(u),\n  \"load-broadcast-replay\": (u) => runLoadBroadcastReplayTests(u),\n  \"broadcast\": () => runBroadcastTests(),\n  \"broadcast-replay\": (u) => runBroadcastReplayTests(u),\n  \"presence\": (u) => runPresenceTests(u),\n  \"authorization\": (u) => runAuthorizationTests(u),\n  \"postgres-changes\": (u) => runPostgresChangesTests(u),\n  \"broadcast-changes\": (u) => runBroadcastChangesTests(u),\n};\n\nconst LOAD_SUITES = Object.keys(SUITES).filter((k) => k.startsWith(\"load\"));\nconst FUNCTIONAL_SUITES = Object.keys(SUITES).filter((k) => !k.startsWith(\"load\"));\n\nasync function main() {\n  log(kleur.bold(\"Realtime Check\"));\n  log(`Project: ${PROJECT_URL}`);\n  log(`Env: ${env}  Email domain: ${EMAIL_DOMAIN}\\n`);\n\n  const activeCategories = TEST_CATEGORIES\n    ? TEST_CATEGORIES.flatMap((c: string) => {\n        if (c === \"functional\") return FUNCTIONAL_SUITES;\n        if (c === \"load\") return LOAD_SUITES;\n        return [c];\n      })\n    : null;\n\n  if (activeCategories) {\n    const unknown = activeCategories.filter((c: string) => !(c in SUITES));\n    if (unknown.length > 0) {\n      const valid = [\"functional\", \"load\", ...Object.keys(SUITES)].join(\", \");\n      log(`Unknown test categories: ${unknown.join(\", \")}\\nValid categories: ${valid}`);\n      process.exit(1);\n    }\n    log(`Running categories: ${activeCategories.join(\", \")}\\n`);\n  }\n\n  const suitesToRun = activeCategories\n    ? Object.entries(SUITES).filter(([key]) => activeCategories.includes(key))\n    : Object.entries(SUITES);\n\n  const { userId, testUser } = await setup();\n  const start = performance.now();\n  try {\n    for (const [, fn] of suitesToRun) await fn(testUser);\n  } finally {\n    await cleanup(userId);\n  }\n\n  printSummary(performance.now() - start);\n\n  if (results.some((r) => !r.passed)) process.exit(1);\n}\n\nmain().catch((e) => {\n  console.error(kleur.red(\"Fatal error:\"), e.message);\n  process.exit(1);\n});\n"
  },
  {
    "path": "test/e2e/supabase/.branches/_current_branch",
    "content": "main"
  },
  {
    "path": "test/e2e/supabase/.temp/cli-latest",
    "content": "v2.75.0"
  },
  {
    "path": "test/extensions/extensions_test.exs",
    "content": "defmodule Realtime.ExtensionsTest do\n  use ExUnit.Case, async: true\n\n  alias Realtime.Extensions\n\n  describe \"db_settings/1\" do\n    test \"returns default and required for postgres_cdc_rls\" do\n      result = Extensions.db_settings(\"postgres_cdc_rls\")\n\n      assert %{default: default, required: required} = result\n      assert is_map(default)\n      assert is_list(required)\n    end\n\n    test \"default contains expected keys\" do\n      %{default: default} = Extensions.db_settings(\"postgres_cdc_rls\")\n\n      assert Map.has_key?(default, \"poll_interval_ms\")\n      assert Map.has_key?(default, \"poll_max_changes\")\n      assert Map.has_key?(default, \"poll_max_record_bytes\")\n      assert Map.has_key?(default, \"publication\")\n      assert Map.has_key?(default, \"slot_name\")\n    end\n\n    test \"required contains expected fields\" do\n      %{required: required} = Extensions.db_settings(\"postgres_cdc_rls\")\n\n      field_names = Enum.map(required, fn {name, _validator, _required} -> name end)\n\n      assert \"db_host\" in field_names\n      assert \"db_name\" in field_names\n      assert \"db_user\" in field_names\n      assert \"db_port\" in field_names\n      assert \"db_password\" in field_names\n    end\n\n    test \"returns empty default for unknown extension type\" do\n      result = Extensions.db_settings(\"unknown_extension\")\n      assert %{default: %{}, required: []} = result\n    end\n  end\nend\n"
  },
  {
    "path": "test/extensions/postgres_cdc_rls/db_settings_test.exs",
    "content": "defmodule Extensions.PostgresCdcRls.DbSettingsTest do\n  use ExUnit.Case, async: true\n\n  alias Extensions.PostgresCdcRls.DbSettings\n\n  describe \"default/0\" do\n    test \"returns a map with expected keys and values\" do\n      default = DbSettings.default()\n\n      assert default[\"poll_interval_ms\"] == 100\n      assert default[\"poll_max_changes\"] == 100\n      assert default[\"poll_max_record_bytes\"] == 1_048_576\n      assert default[\"publication\"] == \"supabase_realtime\"\n      assert default[\"slot_name\"] == \"supabase_realtime_replication_slot\"\n    end\n  end\n\n  describe \"required/0\" do\n    test \"returns a list of tuples\" do\n      required = DbSettings.required()\n\n      assert is_list(required)\n      assert length(required) > 0\n\n      for {name, validator, required_flag} <- required do\n        assert is_binary(name)\n        assert is_function(validator, 1)\n        assert is_boolean(required_flag)\n      end\n    end\n\n    test \"db_host is required\" do\n      required = DbSettings.required()\n      assert {\"db_host\", _, true} = List.keyfind!(required, \"db_host\", 0)\n    end\n\n    test \"region is not required\" do\n      required = DbSettings.required()\n      assert {\"region\", _, false} = List.keyfind!(required, \"region\", 0)\n    end\n\n    test \"validators accept binary values\" do\n      required = DbSettings.required()\n\n      for {_name, validator, _required} <- required do\n        assert validator.(\"some_value\") == true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/extensions/postgres_cdc_rls/message_dispatcher_test.exs",
    "content": "defmodule Extensions.PostgresCdcRls.MessageDispatcherTest do\n  use ExUnit.Case, async: true\n\n  alias Extensions.PostgresCdcRls.MessageDispatcher\n  alias Phoenix.Socket.Broadcast\n\n  defmodule FakeSerializer do\n    def fastlane!(msg), do: {:encoded, msg}\n  end\n\n  describe \"dispatch/3\" do\n    test \"dispatches to fastlane subscribers with matching sub_ids using new api\" do\n      parent = self()\n\n      fastlane_pid =\n        spawn(fn ->\n          receive do\n            msg -> send(parent, {:received, msg})\n          end\n        end)\n\n      sub_ids = MapSet.new([\"sub_1\"])\n      ids = [{\"sub_1\", 1}]\n\n      subscriptions = [\n        {self(), {:subscriber_fastlane, fastlane_pid, FakeSerializer, ids, \"realtime:topic\", true}}\n      ]\n\n      payload = Jason.encode!(%{data: \"test\"})\n\n      assert :ok = MessageDispatcher.dispatch(subscriptions, self(), {\"INSERT\", payload, sub_ids})\n\n      assert_receive {:received, {:encoded, %Broadcast{topic: \"realtime:topic\", event: \"postgres_changes\"}}}\n    end\n\n    test \"dispatches to fastlane subscribers with matching sub_ids using old api\" do\n      parent = self()\n\n      fastlane_pid =\n        spawn(fn ->\n          receive do\n            msg -> send(parent, {:received, msg})\n          end\n        end)\n\n      sub_ids = MapSet.new([\"sub_1\"])\n      ids = [{\"sub_1\", 1}]\n\n      subscriptions = [\n        {self(), {:subscriber_fastlane, fastlane_pid, FakeSerializer, ids, \"realtime:topic\", false}}\n      ]\n\n      payload = Jason.encode!(%{data: \"test\"})\n\n      assert :ok = MessageDispatcher.dispatch(subscriptions, self(), {\"INSERT\", payload, sub_ids})\n\n      assert_receive {:received, {:encoded, %Broadcast{topic: \"realtime:topic\", event: \"INSERT\"}}}\n    end\n\n    test \"does not dispatch when sub_ids do not match\" do\n      parent = self()\n\n      fastlane_pid =\n        spawn(fn ->\n          receive do\n            msg -> send(parent, {:received, msg})\n          after\n            1000 -> :ok\n          end\n        end)\n\n      sub_ids = MapSet.new([\"sub_2\"])\n      ids = [{\"sub_1\", 1}]\n\n      subscriptions = [\n        {self(), {:subscriber_fastlane, fastlane_pid, FakeSerializer, ids, \"realtime:topic\", true}}\n      ]\n\n      assert :ok = MessageDispatcher.dispatch(subscriptions, self(), {\"INSERT\", \"payload\", sub_ids})\n\n      refute_receive {:received, _}\n    end\n\n    test \"caches encoded messages across multiple subscribers\" do\n      parent = self()\n\n      pids =\n        for _ <- 1..2 do\n          spawn(fn ->\n            receive do\n              msg -> send(parent, {:received, msg})\n            end\n          end)\n        end\n\n      sub_ids = MapSet.new([\"sub_1\"])\n      ids = [{\"sub_1\", 1}]\n\n      subscriptions =\n        Enum.map(pids, fn pid ->\n          {self(), {:subscriber_fastlane, pid, FakeSerializer, ids, \"realtime:topic\", true}}\n        end)\n\n      assert :ok = MessageDispatcher.dispatch(subscriptions, self(), {\"INSERT\", \"payload\", sub_ids})\n\n      assert_receive {:received, {:encoded, %Broadcast{}}}\n      assert_receive {:received, {:encoded, %Broadcast{}}}\n    end\n  end\nend\n"
  },
  {
    "path": "test/extensions/postgres_cdc_rls/replications_test.exs",
    "content": "defmodule Extensions.PostgresCdcRls.ReplicationsTest do\n  use Realtime.DataCase, async: false\n\n  alias Extensions.PostgresCdcRls.Replications\n  alias Realtime.Database\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    {:ok, conn} = Database.connect(tenant, \"realtime_rls\", :stop)\n    Integrations.setup_postgres_changes(conn)\n    %{conn: conn, tenant: tenant}\n  end\n\n  defp drop_slot_on_exit(tenant, slot_name) do\n    on_exit(fn ->\n      {:ok, conn} = Database.connect(tenant, \"realtime_rls\", :stop)\n      Postgrex.query(conn, \"select pg_drop_replication_slot($1)\", [slot_name])\n      GenServer.stop(conn)\n    end)\n  end\n\n  describe \"prepare_replication/2\" do\n    test \"creates a replication slot\", %{conn: conn, tenant: tenant} do\n      slot_name = \"test_slot_#{System.unique_integer([:positive])}\"\n      drop_slot_on_exit(tenant, slot_name)\n\n      assert {:ok, %Postgrex.Result{}} = Replications.prepare_replication(conn, slot_name)\n\n      assert {:ok, %Postgrex.Result{num_rows: 1}} =\n               Postgrex.query(conn, \"select 1 from pg_replication_slots where slot_name = $1\", [slot_name])\n    end\n\n    test \"is idempotent when slot already exists\", %{conn: conn, tenant: tenant} do\n      slot_name = \"test_slot_#{System.unique_integer([:positive])}\"\n      drop_slot_on_exit(tenant, slot_name)\n\n      assert {:ok, _} = Replications.prepare_replication(conn, slot_name)\n      assert {:ok, _} = Replications.prepare_replication(conn, slot_name)\n    end\n  end\n\n  describe \"terminate_backend/2\" do\n    test \"returns slot_not_found when slot does not exist\", %{conn: conn} do\n      assert {:error, :slot_not_found} = Replications.terminate_backend(conn, \"nonexistent_slot\")\n    end\n\n    test \"returns error when connection is in a failed transaction\", %{tenant: tenant} do\n      {:ok, bad_conn} = Realtime.Database.connect(tenant, \"realtime_rls\", :stop)\n\n      Postgrex.transaction(bad_conn, fn trans_conn ->\n        # Put the transaction in failed state\n        Postgrex.query(trans_conn, \"SELECT 1/0\", [])\n        # Subsequent queries return {:error, %Postgrex.Error{}} due to failed transaction\n        assert {:error, %Postgrex.Error{}} = Replications.terminate_backend(trans_conn, \"any_slot\")\n        # Return error to trigger rollback\n        {:error, :rollback}\n      end)\n\n      GenServer.stop(bad_conn)\n    end\n\n    test \"returns slot_not_found when slot exists but has no active backend\", %{conn: conn, tenant: tenant} do\n      slot_name = \"test_slot_#{System.unique_integer([:positive])}\"\n      drop_slot_on_exit(tenant, slot_name)\n\n      # Use a permanent (non-temporary) slot via a separate connection to avoid\n      # connection state issues that temporary slots cause on the same connection\n      {:ok, slot_conn} = Realtime.Database.connect(tenant, \"realtime_rls\", :stop)\n      Postgrex.query!(slot_conn, \"select pg_create_logical_replication_slot($1, 'pgoutput')\", [slot_name])\n      GenServer.stop(slot_conn)\n\n      assert {:error, :slot_not_found} = Replications.terminate_backend(conn, slot_name)\n    end\n  end\n\n  describe \"get_pg_stat_activity_diff/2\" do\n    test \"returns error when pid is not in pg_stat_activity\", %{conn: conn} do\n      assert {:error, :pid_not_found} = Replications.get_pg_stat_activity_diff(conn, 0)\n    end\n\n    test \"returns diff when pid is found in pg_stat_activity\", %{conn: conn} do\n      {:ok, %Postgrex.Result{rows: [[backend_pid]]}} = Postgrex.query(conn, \"SELECT pg_backend_pid()\", [])\n\n      result = Replications.get_pg_stat_activity_diff(conn, backend_pid)\n\n      assert {:ok, diff} = result\n      assert is_integer(diff)\n    end\n  end\n\n  describe \"list_changes/5\" do\n    test \"returns empty result when no changes\", %{conn: conn, tenant: tenant} do\n      slot_name = \"test_slot_#{System.unique_integer([:positive])}\"\n      drop_slot_on_exit(tenant, slot_name)\n\n      {:ok, _} = Replications.prepare_replication(conn, slot_name)\n\n      assert {:ok, %Postgrex.Result{}} =\n               Replications.list_changes(conn, slot_name, \"supabase_realtime_test\", 100, 1_048_576)\n    end\n  end\nend\n"
  },
  {
    "path": "test/extensions/postgres_cdc_rls/worker_supervisor_test.exs",
    "content": "defmodule Extensions.PostgresCdcRls.WorkerSupervisorTest do\n  use Realtime.DataCase, async: false\n\n  alias Extensions.PostgresCdcRls.WorkerSupervisor\n  alias Extensions.PostgresCdcRls.ReplicationPoller\n  alias Extensions.PostgresCdcRls.SubscriptionManager\n  alias Extensions.PostgresCdcRls.SubscriptionsChecker\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    extension = hd(tenant.extensions).settings\n\n    args =\n      extension\n      |> Map.put(\"id\", tenant.external_id)\n      |> Map.put(\"region\", extension[\"region\"])\n\n    %{args: args, tenant: tenant}\n  end\n\n  describe \"start_link/1\" do\n    test \"starts the supervisor with all children\", %{args: args} do\n      pid = start_link_supervised!({WorkerSupervisor, args})\n\n      assert Process.alive?(pid)\n\n      children = Supervisor.which_children(pid)\n      child_ids = Enum.map(children, fn {id, _pid, _type, _modules} -> id end)\n\n      assert ReplicationPoller in child_ids\n      assert SubscriptionManager in child_ids\n      assert SubscriptionsChecker in child_ids\n    end\n\n    test \"creates ETS tables for subscribers\", %{args: args} do\n      pid = start_link_supervised!({WorkerSupervisor, args})\n\n      children = Supervisor.which_children(pid)\n\n      replication_poller_pid =\n        Enum.find_value(children, fn\n          {ReplicationPoller, pid, _, _} when is_pid(pid) -> pid\n          _ -> nil\n        end)\n\n      assert replication_poller_pid != nil\n      assert Process.alive?(replication_poller_pid)\n    end\n\n    test \"raises exception when tenant is not in cache\" do\n      args = %{\n        \"id\" => \"nonexistent_tenant_#{System.unique_integer()}\",\n        \"region\" => \"us-east-1\",\n        \"db_host\" => \"localhost\",\n        \"db_name\" => \"realtime\",\n        \"db_user\" => \"user\",\n        \"db_password\" => \"pass\",\n        \"db_port\" => \"5432\"\n      }\n\n      {pid, ref} = spawn_monitor(fn -> WorkerSupervisor.start_link(args) end)\n      assert_receive {:DOWN, ^ref, :process, ^pid, {%Realtime.PostgresCdc.Exception{}, _}}\n    end\n  end\n\n  describe \"supervisor registration\" do\n    test \"registers in syn under the tenant scope\", %{args: args, tenant: tenant} do\n      start_link_supervised!({WorkerSupervisor, args})\n\n      scope = Realtime.Syn.PostgresCdc.scope(tenant.external_id)\n      assert {pid, _meta} = :syn.lookup(scope, tenant.external_id)\n      assert is_pid(pid)\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/distributed_realtime_channel_test.exs",
    "content": "defmodule Realtime.Integration.DistributedRealtimeChannelTest do\n  # Use of Clustered\n  use RealtimeWeb.ConnCase,\n    async: false,\n    parameterize: [%{serializer: Phoenix.Socket.V1.JSONSerializer}, %{serializer: RealtimeWeb.Socket.V2Serializer}]\n\n  alias Phoenix.Socket.Message\n\n  alias Realtime.Tenants.Connect\n  alias Realtime.Integration.WebsocketClient\n\n  setup do\n    tenant = Containers.checkout_tenant_unboxed(run_migrations: true)\n\n    {:ok, node} = Clustered.start()\n    region = Realtime.Tenants.region(tenant)\n    {:ok, db_conn} = :erpc.call(node, Connect, :connect, [tenant.external_id, region])\n    assert Connect.ready?(tenant.external_id)\n\n    assert node(db_conn) == node\n    %{tenant: tenant, topic: random_string()}\n  end\n\n  describe \"distributed broadcast\" do\n    @tag mode: :distributed\n    test \"it works\", %{tenant: tenant, topic: topic, serializer: serializer} do\n      {:ok, token} =\n        generate_token(tenant, %{exp: System.system_time(:second) + 1000, role: \"authenticated\", sub: random_string()})\n\n      {:ok, remote_socket} =\n        WebsocketClient.connect(self(), uri(tenant, serializer, 4012), serializer, [{\"x-api-key\", token}])\n\n      {:ok, socket} = WebsocketClient.connect(self(), uri(tenant, serializer), serializer, [{\"x-api-key\", token}])\n\n      config = %{broadcast: %{self: false}, private: false}\n      topic = \"realtime:#{topic}\"\n\n      :ok = WebsocketClient.join(remote_socket, topic, %{config: config})\n      :ok = WebsocketClient.join(socket, topic, %{config: config})\n\n      # Send through one socket and receive through the other (self: false)\n      payload = %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"}\n      :ok = WebsocketClient.send_event(remote_socket, topic, \"broadcast\", payload)\n\n      assert_receive %Message{event: \"broadcast\", payload: ^payload, topic: ^topic}, 2000\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/measure_traffic_test.exs",
    "content": "defmodule Realtime.Integration.MeasureTrafficTest do\n  use RealtimeWeb.ConnCase, async: false\n\n  alias Phoenix.Socket.Message\n  alias Realtime.Integration.WebsocketClient\n  alias Realtime.Tenants.ReplicationConnection\n\n  setup [:checkout_tenant_and_connect]\n\n  def handle_telemetry(event, measurements, metadata, name) do\n    tenant = metadata[:tenant]\n    [key] = Enum.take(event, -1)\n    value = Map.get(measurements, :sum) || Map.get(measurements, :value) || Map.get(measurements, :size) || 0\n\n    Agent.update(name, fn state ->\n      state =\n        Map.put_new(\n          state,\n          tenant,\n          %{\n            joins: 0,\n            events: 0,\n            db_events: 0,\n            presence_events: 0,\n            output_bytes: 0,\n            input_bytes: 0\n          }\n        )\n\n      update_in(state, [metadata[:tenant], key], fn v -> (v || 0) + value end)\n    end)\n  end\n\n  defp get_count(event, tenant) do\n    [key] = Enum.take(event, -1)\n\n    :\"TestCounter_#{tenant}\"\n    |> Agent.get(fn state -> get_in(state, [tenant, key]) || 0 end)\n  end\n\n  describe \"measure traffic\" do\n    setup %{tenant: tenant} do\n      events = [\n        [:realtime, :channel, :output_bytes],\n        [:realtime, :channel, :input_bytes]\n      ]\n\n      name = :\"TestCounter_#{tenant.external_id}\"\n\n      {:ok, _} =\n        start_supervised(%{\n          id: 1,\n          start: {Agent, :start_link, [fn -> %{} end, [name: name]]}\n        })\n\n      RateCounterHelper.stop(tenant.external_id)\n      on_exit(fn -> :telemetry.detach({__MODULE__, tenant.external_id}) end)\n      :telemetry.attach_many({__MODULE__, tenant.external_id}, events, &__MODULE__.handle_telemetry/4, name)\n\n      measure_traffic_interval_in_ms = Application.get_env(:realtime, :measure_traffic_interval_in_ms)\n      Application.put_env(:realtime, :measure_traffic_interval_in_ms, 10)\n      on_exit(fn -> Application.put_env(:realtime, :measure_traffic_interval_in_ms, measure_traffic_interval_in_ms) end)\n\n      :ok\n    end\n\n    test \"measure traffic for broadcast events\", %{tenant: tenant} do\n      {socket, _} = get_connection(tenant)\n      config = %{broadcast: %{self: true}}\n      topic = \"realtime:any\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      # Wait for join to complete\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 1000\n\n      for _ <- 1..5 do\n        WebsocketClient.send_event(socket, topic, \"broadcast\", %{\n          \"event\" => \"TEST\",\n          \"payload\" => %{\"msg\" => 1},\n          \"type\" => \"broadcast\"\n        })\n\n        assert_receive %Message{\n                         event: \"broadcast\",\n                         payload: %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"},\n                         topic: ^topic\n                       },\n                       500\n      end\n\n      # Wait for RateCounter to run\n      RateCounterHelper.tick_tenant_rate_counters!(tenant.external_id)\n      Process.sleep(100)\n\n      output_bytes = get_count([:realtime, :channel, :output_bytes], tenant.external_id)\n      input_bytes = get_count([:realtime, :channel, :input_bytes], tenant.external_id)\n\n      assert output_bytes > 0\n      assert input_bytes > 0\n    end\n\n    test \"measure traffic for presence events\", %{tenant: tenant} do\n      {socket, _} = get_connection(tenant)\n      config = %{broadcast: %{self: true}, presence: %{enabled: true}}\n      topic = \"realtime:any\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      # Wait for join to complete\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 1000\n\n      for _ <- 1..5 do\n        WebsocketClient.send_event(socket, topic, \"presence\", %{\n          \"event\" => \"TRACK\",\n          \"payload\" => %{name: \"realtime_presence_#{:rand.uniform(1000)}\", t: 1814.7000000029802},\n          \"type\" => \"presence\"\n        })\n      end\n\n      # Wait for RateCounter to run\n      RateCounterHelper.tick_tenant_rate_counters!(tenant.external_id)\n      Process.sleep(100)\n\n      output_bytes = get_count([:realtime, :channel, :output_bytes], tenant.external_id)\n      input_bytes = get_count([:realtime, :channel, :input_bytes], tenant.external_id)\n\n      assert output_bytes > 0, \"Expected output_bytes to be greater than 0, got #{output_bytes}\"\n      assert input_bytes > 0, \"Expected input_bytes to be greater than 0, got #{input_bytes}\"\n    end\n\n    test \"measure traffic for postgres changes events\", %{tenant: tenant, db_conn: db_conn} do\n      Integrations.setup_postgres_changes(db_conn)\n      {socket, _} = get_connection(tenant)\n      config = %{broadcast: %{self: true}, postgres_changes: [%{event: \"*\", schema: \"public\"}]}\n      topic = \"realtime:any\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      # Wait for join to complete\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 1000\n\n      # Wait for postgres_changes subscription to be ready\n      assert_receive %Message{\n                       event: \"system\",\n                       payload: %{\n                         \"channel\" => \"any\",\n                         \"extension\" => \"postgres_changes\",\n                         \"status\" => \"ok\"\n                       },\n                       topic: ^topic\n                     },\n                     8000\n\n      for _ <- 1..5 do\n        Postgrex.query!(db_conn, \"INSERT INTO test (details) VALUES ($1)\", [random_string()])\n      end\n\n      for _ <- 1..5 do\n        assert_receive %Message{\n                         event: \"postgres_changes\",\n                         payload: %{\"data\" => %{\"schema\" => \"public\", \"table\" => \"test\", \"type\" => \"INSERT\"}},\n                         topic: ^topic\n                       },\n                       500\n      end\n\n      # Wait for RateCounter to run\n      RateCounterHelper.tick_tenant_rate_counters!(tenant.external_id)\n      Process.sleep(100)\n\n      output_bytes = get_count([:realtime, :channel, :output_bytes], tenant.external_id)\n      input_bytes = get_count([:realtime, :channel, :input_bytes], tenant.external_id)\n\n      assert output_bytes > 0, \"Expected output_bytes to be greater than 0, got #{output_bytes}\"\n      assert input_bytes > 0, \"Expected input_bytes to be greater than 0, got #{input_bytes}\"\n    end\n\n    test \"measure traffic for db events\", %{tenant: tenant, db_conn: db_conn} do\n      {socket, _} = get_connection(tenant)\n      config = %{broadcast: %{self: true}, db: %{enabled: true}}\n      topic = \"realtime:any\"\n      channel_name = \"any\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      # Wait for join to complete\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 1000\n\n      Enum.reduce_while(1..30, nil, fn _, _ ->\n        if ReplicationConnection.whereis(tenant.external_id),\n          do: {:halt, :ok},\n          else:\n            (\n              Process.sleep(500)\n              {:cont, nil}\n            )\n      end)\n\n      for _ <- 1..5 do\n        event = random_string()\n        value = random_string()\n\n        Postgrex.query!(\n          db_conn,\n          \"SELECT realtime.send (json_build_object ('value', $1 :: text)::jsonb, $2 :: text, $3 :: text, FALSE::bool);\",\n          [value, event, channel_name]\n        )\n\n        assert_receive %Message{\n                         event: \"broadcast\",\n                         payload: %{\n                           \"event\" => ^event,\n                           \"payload\" => %{\"value\" => ^value},\n                           \"type\" => \"broadcast\"\n                         },\n                         topic: ^topic,\n                         join_ref: nil,\n                         ref: nil\n                       },\n                       2000\n      end\n\n      # Wait for RateCounter to run\n      RateCounterHelper.tick_tenant_rate_counters!(tenant.external_id)\n      Process.sleep(100)\n\n      output_bytes = get_count([:realtime, :channel, :output_bytes], tenant.external_id)\n      input_bytes = get_count([:realtime, :channel, :input_bytes], tenant.external_id)\n\n      assert output_bytes > 0, \"Expected output_bytes to be greater than 0, got #{output_bytes}\"\n      assert input_bytes > 0, \"Expected input_bytes to be greater than 0, got #{input_bytes}\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/region_aware_migrations_test.exs",
    "content": "defmodule Realtime.Integration.RegionAwareMigrationsTest do\n  use Realtime.DataCase, async: false\n  use Mimic\n\n  alias Containers\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Migrations\n\n  setup do\n    {:ok, port} = Containers.checkout()\n\n    settings = [\n      %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"db_host\" => \"127.0.0.1\",\n          \"db_name\" => \"postgres\",\n          \"db_user\" => \"supabase_admin\",\n          \"db_password\" => \"postgres\",\n          \"db_port\" => \"#{port}\",\n          \"poll_interval\" => 100,\n          \"poll_max_changes\" => 100,\n          \"poll_max_record_bytes\" => 1_048_576,\n          \"region\" => \"ap-southeast-2\",\n          \"publication\" => \"supabase_realtime_test\",\n          \"ssl_enforced\" => false\n        }\n      }\n    ]\n\n    tenant = tenant_fixture(%{extensions: settings})\n    region = Application.get_env(:realtime, :region)\n\n    {:ok, node} =\n      Clustered.start(nil,\n        extra_config: [\n          {:realtime, :region, Tenants.region(tenant)},\n          {:realtime, :master_region, region}\n        ]\n      )\n\n    Process.sleep(100)\n\n    %{tenant: tenant, node: node}\n  end\n\n  test \"run_migrations routes to node in tenant's region with expected arguments\", %{tenant: tenant, node: node} do\n    assert tenant.migrations_ran == 0\n\n    Realtime.GenRpc\n    |> Mimic.expect(:call, fn\n      called_node, Realtime.Nodes, func, args, opts ->\n        call_original(Realtime.GenRpc, :call, [called_node, Realtime.Nodes, func, args, opts])\n\n      called_node, Migrations, func, args, opts ->\n        assert called_node == node\n        assert func == :start_migration\n        assert opts[:tenant_id] == tenant.external_id\n\n        arg = hd(args)\n        assert arg.tenant_external_id == tenant.external_id\n        assert arg.migrations_ran == tenant.migrations_ran\n        assert arg.settings == hd(tenant.extensions).settings\n\n        assert opts[:timeout] == 50_000\n\n        call_original(Realtime.GenRpc, :call, [node, Migrations, func, args, opts])\n    end)\n\n    assert :ok = Migrations.run_migrations(tenant)\n    Process.sleep(1000)\n    tenant = Realtime.Repo.reload!(tenant)\n    refute tenant.migrations_ran == 0\n  end\nend\n"
  },
  {
    "path": "test/integration/region_aware_routing_test.exs",
    "content": "defmodule Realtime.Integration.RegionAwareRoutingTest do\n  use Realtime.DataCase, async: false\n  use Mimic\n\n  alias Realtime.Api\n  alias Realtime.Api.Tenant\n  alias Realtime.GenRpc\n  alias Realtime.Nodes\n\n  setup do\n    original_master_region = Application.get_env(:realtime, :master_region)\n\n    on_exit(fn ->\n      Application.put_env(:realtime, :master_region, original_master_region)\n    end)\n\n    Application.put_env(:realtime, :master_region, \"eu-west-2\")\n\n    {:ok, master_node} =\n      Clustered.start(nil,\n        extra_config: [\n          {:realtime, :region, \"eu-west-2\"},\n          {:realtime, :master_region, \"eu-west-2\"}\n        ]\n      )\n\n    Process.sleep(100)\n\n    %{master_node: master_node}\n  end\n\n  test \"create_tenant automatically routes to master region\", %{master_node: master_node} do\n    external_id = \"test_routing_#{System.unique_integer([:positive])}\"\n\n    attrs = %{\n      \"external_id\" => external_id,\n      \"name\" => external_id,\n      \"jwt_secret\" => \"secret\",\n      \"public_key\" => \"public\",\n      \"extensions\" => [],\n      \"postgres_cdc_default\" => \"postgres_cdc_rls\",\n      \"max_concurrent_users\" => 200,\n      \"max_events_per_second\" => 100\n    }\n\n    Mimic.expect(Realtime.GenRpc, :call, fn node, mod, func, args, opts ->\n      assert node == master_node\n      assert mod == Realtime.Api\n      assert func == :create_tenant\n      assert opts[:tenant_id] == external_id\n\n      call_original(GenRpc, :call, [node, mod, func, args, opts])\n    end)\n\n    result = Api.create_tenant(attrs)\n\n    assert {:ok, %Tenant{} = tenant} = result\n    assert tenant.external_id == external_id\n\n    assert Realtime.Repo.get_by(Tenant, external_id: external_id)\n  end\n\n  test \"update_tenant automatically routes to master region\", %{master_node: master_node} do\n    # Create tenant on master node first\n    tenant_attrs = %{\n      \"external_id\" => \"test_update_#{System.unique_integer([:positive])}\",\n      \"name\" => \"original\",\n      \"jwt_secret\" => \"secret\",\n      \"public_key\" => \"public\",\n      \"extensions\" => [],\n      \"postgres_cdc_default\" => \"postgres_cdc_rls\",\n      \"max_concurrent_users\" => 200,\n      \"max_events_per_second\" => 100\n    }\n\n    Realtime.GenRpc\n    |> Mimic.expect(:call, fn node, mod, func, args, opts ->\n      assert node == master_node\n      assert mod == Realtime.Api\n      assert func == :create_tenant\n      assert opts[:tenant_id] == tenant_attrs[\"external_id\"]\n\n      call_original(GenRpc, :call, [node, mod, func, args, opts])\n    end)\n    |> Mimic.expect(:call, fn node, mod, func, args, opts ->\n      assert node == master_node\n      assert mod == Realtime.Api\n      assert func == :update_tenant_by_external_id\n      assert opts[:tenant_id] == tenant_attrs[\"external_id\"]\n\n      call_original(GenRpc, :call, [node, mod, func, args, opts])\n    end)\n\n    tenant = tenant_fixture(tenant_attrs)\n\n    new_name = \"updated_via_routing\"\n    result = Api.update_tenant_by_external_id(tenant.external_id, %{name: new_name})\n\n    assert {:ok, %Tenant{} = updated} = result\n    assert updated.name == new_name\n\n    reloaded = Realtime.Repo.get(Tenant, tenant.id)\n    assert reloaded.name == new_name\n  end\n\n  test \"delete_tenant_by_external_id automatically routes to master region\", %{master_node: master_node} do\n    # Create tenant on master node first\n    tenant_attrs = %{\n      \"external_id\" => \"test_delete_#{System.unique_integer([:positive])}\",\n      \"name\" => \"to_delete\",\n      \"jwt_secret\" => \"secret\",\n      \"public_key\" => \"public\",\n      \"extensions\" => [],\n      \"postgres_cdc_default\" => \"postgres_cdc_rls\",\n      \"max_concurrent_users\" => 200,\n      \"max_events_per_second\" => 100\n    }\n\n    Realtime.GenRpc\n    |> Mimic.expect(:call, fn node, mod, func, args, opts ->\n      assert node == master_node\n      assert mod == Realtime.Api\n      assert func == :create_tenant\n      assert opts[:tenant_id] == tenant_attrs[\"external_id\"]\n\n      call_original(GenRpc, :call, [node, mod, func, args, opts])\n    end)\n    |> Mimic.expect(:call, fn node, mod, func, args, opts ->\n      assert node == master_node\n      assert mod == Realtime.Api\n      assert func == :delete_tenant_by_external_id\n      assert opts[:tenant_id] == tenant_attrs[\"external_id\"]\n\n      call_original(GenRpc, :call, [node, mod, func, args, opts])\n    end)\n\n    tenant = tenant_fixture(tenant_attrs)\n\n    result = Api.delete_tenant_by_external_id(tenant.external_id)\n\n    assert result == true\n\n    refute Realtime.Repo.get(Tenant, tenant.id)\n  end\n\n  test \"update_migrations_ran automatically routes to master region\", %{master_node: master_node} do\n    # Create tenant on master node first\n    tenant_attrs = %{\n      \"external_id\" => \"test_migrations_#{System.unique_integer([:positive])}\",\n      \"name\" => \"migrations_test\",\n      \"jwt_secret\" => \"secret\",\n      \"public_key\" => \"public\",\n      \"extensions\" => [],\n      \"postgres_cdc_default\" => \"postgres_cdc_rls\",\n      \"max_concurrent_users\" => 200,\n      \"max_events_per_second\" => 100,\n      \"migrations_ran\" => 0\n    }\n\n    Realtime.GenRpc\n    |> Mimic.expect(:call, fn node, mod, func, args, opts ->\n      assert node == master_node\n      assert mod == Realtime.Api\n      assert func == :create_tenant\n      assert opts[:tenant_id] == tenant_attrs[\"external_id\"]\n\n      call_original(GenRpc, :call, [node, mod, func, args, opts])\n    end)\n    |> Mimic.expect(:call, fn node, mod, func, args, opts ->\n      assert node == master_node\n      assert mod == Realtime.Api\n      assert func == :update_migrations_ran\n      assert opts[:tenant_id] == tenant_attrs[\"external_id\"]\n\n      call_original(GenRpc, :call, [node, mod, func, args, opts])\n    end)\n\n    tenant = tenant_fixture(tenant_attrs)\n\n    new_migrations_ran = 5\n    result = Api.update_migrations_ran(tenant.external_id, new_migrations_ran)\n\n    assert {:ok, updated} = result\n    assert updated.migrations_ran == new_migrations_ran\n\n    reloaded = Realtime.Repo.get(Tenant, tenant.id)\n    assert reloaded.migrations_ran == new_migrations_ran\n  end\n\n  test \"returns error when Nodes.node_from_region returns {:error, :not_available}\" do\n    external_id = \"test_error_node_unavailable_#{System.unique_integer([:positive])}\"\n\n    attrs = %{\n      \"external_id\" => external_id,\n      \"name\" => external_id,\n      \"jwt_secret\" => \"secret\",\n      \"public_key\" => \"public\",\n      \"extensions\" => [],\n      \"postgres_cdc_default\" => \"postgres_cdc_rls\",\n      \"max_concurrent_users\" => 200,\n      \"max_events_per_second\" => 100\n    }\n\n    Mimic.expect(Nodes, :node_from_region, fn _region, _key -> {:error, :not_available} end)\n    result = Api.create_tenant(attrs)\n    assert {:error, :not_available} = result\n  end\n\n  test \"returns error when GenRpc.call returns {:error, :rpc_error, reason}\" do\n    external_id = \"test_error_rpc_error_#{System.unique_integer([:positive])}\"\n    rpc_error_reason = :timeout\n\n    attrs = %{\n      \"external_id\" => external_id,\n      \"name\" => external_id,\n      \"jwt_secret\" => \"secret\",\n      \"public_key\" => \"public\",\n      \"extensions\" => [],\n      \"postgres_cdc_default\" => \"postgres_cdc_rls\",\n      \"max_concurrent_users\" => 200,\n      \"max_events_per_second\" => 100\n    }\n\n    Mimic.expect(GenRpc, :call, fn _node, _mod, _func, _args, _opts -> {:error, :rpc_error, rpc_error_reason} end)\n    result = Api.create_tenant(attrs)\n    assert {:error, ^rpc_error_reason} = result\n  end\nend\n"
  },
  {
    "path": "test/integration/rt_channel/authorization_test.exs",
    "content": "defmodule Realtime.Integration.RtChannel.AuthorizationTest do\n  use RealtimeWeb.ConnCase,\n    async: true,\n    parameterize: [\n      %{serializer: Phoenix.Socket.V1.JSONSerializer},\n      %{serializer: RealtimeWeb.Socket.V2Serializer}\n    ]\n\n  import ExUnit.CaptureLog\n  import Generators\n\n  alias Phoenix.Socket.Message\n  alias Realtime.Integration.WebsocketClient\n\n  @moduletag :capture_log\n\n  setup [:checkout_tenant_and_connect]\n\n  describe \"private only channels\" do\n    setup [:rls_context]\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"user with only private channels enabled will not be able to join public channels\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      change_tenant_configuration(tenant, :private_only, true)\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       payload: %{\n                         \"response\" => %{\n                           \"reason\" => \"PrivateOnly: This project only allows private channels\"\n                         },\n                         \"status\" => \"error\"\n                       }\n                     },\n                     500\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"user with only private channels enabled will be able to join private channels\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      change_tenant_configuration(tenant, :private_only, true)\n\n      Process.sleep(100)\n\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: true}\n      topic = \"realtime:#{topic}\"\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n    end\n  end\n\n  describe \"RLS policy enforcement\" do\n    setup [:rls_context]\n\n    @tag policies: [:read_matching_user_role, :write_matching_user_role], role: \"anon\"\n    test \"role policies are respected when accessing the channel\", %{tenant: tenant, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer, role: \"anon\")\n      config = %{broadcast: %{self: true}, private: true, presence: %{enabled: false}}\n      topic = random_string()\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^realtime_topic}, 500\n\n      {socket, _} = get_connection(tenant, serializer, role: \"potato\")\n      topic = random_string()\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config})\n      refute_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^realtime_topic}, 500\n    end\n\n    @tag policies: [:authenticated_read_matching_user_sub, :authenticated_write_matching_user_sub],\n         sub: Ecto.UUID.generate()\n    test \"sub policies are respected when accessing the channel\", %{tenant: tenant, sub: sub, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\", claims: %{sub: sub})\n      config = %{broadcast: %{self: true}, private: true, presence: %{enabled: false}}\n      topic = random_string()\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^realtime_topic}, 500\n\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\", claims: %{sub: Ecto.UUID.generate()})\n      topic = random_string()\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config})\n      refute_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^realtime_topic}, 500\n    end\n\n    @tag role: \"authenticated\", policies: [:broken_read_presence, :broken_write_presence]\n    test \"handle failing rls policy\", %{tenant: tenant, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: true}\n      topic = random_string()\n      realtime_topic = \"realtime:#{topic}\"\n\n      log =\n        capture_log(fn ->\n          WebsocketClient.join(socket, realtime_topic, %{config: config})\n\n          msg = \"Unauthorized: You do not have permissions to read from this Channel topic: #{topic}\"\n\n          assert_receive %Message{\n                           event: \"phx_reply\",\n                           payload: %{\n                             \"response\" => %{\n                               \"reason\" => ^msg\n                             },\n                             \"status\" => \"error\"\n                           }\n                         },\n                         500\n\n          refute_receive %Message{event: \"phx_reply\"}\n          refute_receive %Message{event: \"presence_state\"}\n        end)\n\n      assert log =~ \"RlsPolicyError\"\n    end\n  end\n\n  describe \"topic validation\" do\n    test \"handle empty topic by closing the socket\", %{tenant: tenant, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       payload: %{\n                         \"response\" => %{\n                           \"reason\" => \"TopicNameRequired: You must provide a topic name\"\n                         },\n                         \"status\" => \"error\"\n                       }\n                     },\n                     500\n\n      refute_receive %Message{event: \"phx_reply\"}\n      refute_receive %Message{event: \"presence_state\"}\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/rt_channel/billable_events_test.exs",
    "content": "defmodule Realtime.Integration.RtChannel.BillableEventsTest do\n  use RealtimeWeb.ConnCase,\n    async: true,\n    parameterize: [\n      %{serializer: Phoenix.Socket.V1.JSONSerializer},\n      %{serializer: RealtimeWeb.Socket.V2Serializer}\n    ]\n\n  import Generators\n\n  alias Phoenix.Socket.Message\n  alias Postgrex\n  alias Realtime.Database\n  alias Realtime.Integration.WebsocketClient\n  alias Realtime.Tenants\n\n  @moduletag :capture_log\n\n  setup [:checkout_tenant_connect_and_setup_postgres_changes]\n\n  setup %{tenant: tenant} do\n    events = [\n      [:realtime, :rate_counter, :channel, :joins],\n      [:realtime, :rate_counter, :channel, :events],\n      [:realtime, :rate_counter, :channel, :db_events],\n      [:realtime, :rate_counter, :channel, :presence_events]\n    ]\n\n    name = :\"TestCounter_#{tenant.external_id}\"\n\n    {:ok, _} =\n      start_supervised(%{\n        id: 1,\n        start: {Agent, :start_link, [fn -> %{} end, [name: name]]}\n      })\n\n    RateCounterHelper.stop(tenant.external_id)\n    on_exit(fn -> :telemetry.detach({__MODULE__, tenant.external_id}) end)\n    :telemetry.attach_many({__MODULE__, tenant.external_id}, events, &__MODULE__.handle_telemetry/4, name)\n\n    :ok\n  end\n\n  def handle_telemetry(event, measurements, metadata, name) do\n    tenant = metadata[:tenant]\n    [key] = Enum.take(event, -1)\n    value = Map.get(measurements, :sum) || Map.get(measurements, :value) || Map.get(measurements, :size) || 0\n\n    Agent.update(name, fn state ->\n      state =\n        Map.put_new(\n          state,\n          tenant,\n          %{\n            joins: 0,\n            events: 0,\n            db_events: 0,\n            presence_events: 0,\n            output_bytes: 0,\n            input_bytes: 0\n          }\n        )\n\n      update_in(state, [metadata[:tenant], key], fn v -> (v || 0) + value end)\n    end)\n  end\n\n  describe \"join events\" do\n    test \"join events\", %{tenant: tenant, serializer: serializer} do\n      external_id = tenant.external_id\n      {socket, _} = get_connection(tenant, serializer)\n      config = %{broadcast: %{self: true}, postgres_changes: [%{event: \"*\", schema: \"public\"}]}\n      topic = \"realtime:any\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      # Join events\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n      assert_receive %Message{topic: ^topic, event: \"system\"}, 5000\n\n      # Wait for RateCounter to run\n      RateCounterHelper.tick_tenant_rate_counters!(tenant.external_id)\n\n      # Expected billed\n      # 1 joins due to two sockets\n      # 0 presence events\n      # 0 db events as no postgres changes used\n      # 0 events broadcast is not used\n      assert 1 = get_count([:realtime, :rate_counter, :channel, :joins], external_id)\n      assert 0 = get_count([:realtime, :rate_counter, :channel, :presence_events], external_id)\n      assert 0 = get_count([:realtime, :rate_counter, :channel, :db_events], external_id)\n      assert 0 = get_count([:realtime, :rate_counter, :channel, :events], external_id)\n    end\n  end\n\n  describe \"broadcast events\" do\n    test \"broadcast events\", %{tenant: tenant, serializer: serializer} do\n      external_id = tenant.external_id\n      {socket1, _} = get_connection(tenant, serializer)\n      config = %{broadcast: %{self: true}}\n      topic = \"realtime:any\"\n\n      WebsocketClient.join(socket1, topic, %{config: config})\n\n      # Join events\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n      # Add second client so we can test the \"multiplication\" of billable events\n      {socket2, _} = get_connection(tenant, serializer)\n      WebsocketClient.join(socket2, topic, %{config: config})\n\n      # Join events\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n      # Broadcast event\n      payload = %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"}\n\n      for _ <- 1..5 do\n        WebsocketClient.send_event(socket1, topic, \"broadcast\", payload)\n        # both sockets\n        assert_receive %Message{topic: ^topic, event: \"broadcast\", payload: ^payload}\n        assert_receive %Message{topic: ^topic, event: \"broadcast\", payload: ^payload}\n      end\n\n      refute_receive _any\n\n      # Wait for RateCounter to run\n      RateCounterHelper.tick_tenant_rate_counters!(tenant.external_id)\n\n      # Expected billed\n      # 2 joins due to two sockets\n      # 0 presence events\n      # 0 db events as no postgres changes used\n      # 15 events as 5 events sent, 5 events received on client 1 and 5 events received on client 2\n      assert 2 = get_count([:realtime, :rate_counter, :channel, :joins], external_id)\n      assert 0 = get_count([:realtime, :rate_counter, :channel, :presence_events], external_id)\n      assert 0 = get_count([:realtime, :rate_counter, :channel, :db_events], external_id)\n      assert 15 = get_count([:realtime, :rate_counter, :channel, :events], external_id)\n    end\n  end\n\n  describe \"presence events\" do\n    test \"presence events\", %{tenant: tenant, serializer: serializer} do\n      external_id = tenant.external_id\n      {socket, _} = get_connection(tenant, serializer)\n      config = %{broadcast: %{self: true}, presence: %{enabled: true}}\n      topic = \"realtime:any\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      # Join events\n      assert_receive %Message{event: \"phx_reply\", topic: ^topic}, 1000\n\n      payload = %{\n        type: \"presence\",\n        event: \"TRACK\",\n        payload: %{name: \"realtime_presence_1\", t: 1814.7000000029802}\n      }\n\n      WebsocketClient.send_event(socket, topic, \"presence\", payload)\n      assert_receive %Message{event: \"presence_diff\", payload: %{\"joins\" => _, \"leaves\" => %{}}, topic: ^topic}\n\n      # Presence events\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n\n      payload = %{\n        type: \"presence\",\n        event: \"TRACK\",\n        payload: %{name: \"realtime_presence_2\", t: 1814.7000000029802}\n      }\n\n      WebsocketClient.send_event(socket, topic, \"presence\", payload)\n      assert_receive %Message{event: \"presence_diff\", payload: %{\"joins\" => _, \"leaves\" => %{}}, topic: ^topic}\n      assert_receive %Message{event: \"presence_diff\", payload: %{\"joins\" => _, \"leaves\" => %{}}, topic: ^topic}\n\n      # Wait for RateCounter to run\n      RateCounterHelper.tick_tenant_rate_counters!(tenant.external_id)\n\n      # Expected billed\n      # 2 joins due to two sockets\n      # 7 presence events\n      # 0 db events as no postgres changes used\n      # 0 events as no broadcast used\n      assert 2 = get_count([:realtime, :rate_counter, :channel, :joins], external_id)\n      assert 7 = get_count([:realtime, :rate_counter, :channel, :presence_events], external_id)\n      assert 0 = get_count([:realtime, :rate_counter, :channel, :db_events], external_id)\n      assert 0 = get_count([:realtime, :rate_counter, :channel, :events], external_id)\n    end\n  end\n\n  describe \"postgres changes events\" do\n    test \"postgres changes events\", %{tenant: tenant, serializer: serializer} do\n      external_id = tenant.external_id\n      {socket, _} = get_connection(tenant, serializer)\n      config = %{broadcast: %{self: true}, postgres_changes: [%{event: \"*\", schema: \"public\"}]}\n      topic = \"realtime:any\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      # Join events\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n      assert_receive %Message{topic: ^topic, event: \"system\"}, 5000\n\n      # Add second user to test the \"multiplication\" of billable events\n      {socket, _} = get_connection(tenant, serializer)\n      WebsocketClient.join(socket, topic, %{config: config})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n      assert_receive %Message{topic: ^topic, event: \"system\"}, 5000\n\n      tenant = Tenants.get_tenant_by_external_id(tenant.external_id)\n      {:ok, conn} = Database.connect(tenant, \"realtime_test\", :stop)\n\n      # Postgres Change events\n      for _ <- 1..5, do: Postgrex.query!(conn, \"insert into test (details) values ('test')\", [])\n\n      for _ <- 1..10 do\n        assert_receive %Message{\n                         topic: ^topic,\n                         event: \"postgres_changes\",\n                         payload: %{\"data\" => %{\"schema\" => \"public\", \"table\" => \"test\", \"type\" => \"INSERT\"}}\n                       },\n                       5000\n      end\n\n      # Wait for RateCounter to run\n      RateCounterHelper.tick_tenant_rate_counters!(tenant.external_id)\n\n      # Expected billed\n      # 2 joins due to two sockets\n      # 0 presence events due to two sockets\n      # 10 db events due to 5 inserts events sent to client 1 and 5 inserts events sent to client 2\n      # 0 events as no broadcast used\n      assert 2 = get_count([:realtime, :rate_counter, :channel, :joins], external_id)\n      assert 0 = get_count([:realtime, :rate_counter, :channel, :presence_events], external_id)\n      # (5 for each websocket)\n      assert 10 = get_count([:realtime, :rate_counter, :channel, :db_events], external_id)\n      assert 0 = get_count([:realtime, :rate_counter, :channel, :events], external_id)\n    end\n\n    test \"postgres changes error events\", %{tenant: tenant, serializer: serializer} do\n      external_id = tenant.external_id\n      {socket, _} = get_connection(tenant, serializer)\n      config = %{broadcast: %{self: true}, postgres_changes: [%{event: \"*\", schema: \"none\"}]}\n      topic = \"realtime:any\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      # Join events\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n      assert_receive %Message{topic: ^topic, event: \"system\"}, 5000\n\n      # Wait for RateCounter to run\n      RateCounterHelper.tick_tenant_rate_counters!(tenant.external_id)\n\n      # Expected billed\n      # 1 joins due to one socket\n      # 0 presence events due to one socket\n      # 0 db events\n      # 0 events as no broadcast used\n      assert 1 = get_count([:realtime, :rate_counter, :channel, :joins], external_id)\n      assert 0 = get_count([:realtime, :rate_counter, :channel, :presence_events], external_id)\n      assert 0 = get_count([:realtime, :rate_counter, :channel, :db_events], external_id)\n      assert 0 = get_count([:realtime, :rate_counter, :channel, :events], external_id)\n    end\n  end\n\n  defp get_count(event, tenant) do\n    [key] = Enum.take(event, -1)\n    Agent.get(:\"TestCounter_#{tenant}\", fn state -> get_in(state, [tenant, key]) || 0 end)\n  end\nend\n"
  },
  {
    "path": "test/integration/rt_channel/broadcast_test.exs",
    "content": "defmodule Realtime.Integration.RtChannel.BroadcastTest do\n  use RealtimeWeb.ConnCase,\n    async: true,\n    parameterize: [\n      %{serializer: Phoenix.Socket.V1.JSONSerializer},\n      %{serializer: RealtimeWeb.Socket.V2Serializer}\n    ]\n\n  import ExUnit.CaptureLog\n  import Generators\n\n  alias Phoenix.Socket.Message\n  alias Postgrex\n  alias Realtime.Database\n  alias Realtime.Integration.WebsocketClient\n  alias Realtime.Tenants.Connect\n\n  @moduletag :capture_log\n\n  setup [:checkout_tenant_and_connect]\n\n  describe \"public broadcast\" do\n    setup [:rls_context]\n\n    test \"public broadcast\", %{tenant: tenant, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer)\n      config = %{broadcast: %{self: true}, private: false}\n      topic = \"realtime:any\"\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n\n      payload = %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"}\n      WebsocketClient.send_event(socket, topic, \"broadcast\", payload)\n\n      assert_receive %Message{event: \"broadcast\", payload: ^payload, topic: ^topic}, 500\n    end\n\n    test \"broadcast to another tenant does not get mixed up\", %{tenant: tenant, serializer: serializer} do\n      other_tenant = Containers.checkout_tenant(run_migrations: true)\n\n      Realtime.Tenants.Cache.update_cache(other_tenant)\n\n      {socket, _} = get_connection(tenant, serializer)\n      config = %{broadcast: %{self: false}, private: false}\n      topic = \"realtime:any\"\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      {other_socket, _} = get_connection(other_tenant, serializer)\n      WebsocketClient.join(other_socket, topic, %{config: config})\n\n      # Both sockets joined\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n\n      payload = %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"}\n      WebsocketClient.send_event(socket, topic, \"broadcast\", payload)\n\n      # No message received\n      refute_receive %Message{event: \"broadcast\", payload: ^payload, topic: ^topic}, 500\n    end\n\n    @tag policies: []\n    test \"lack of connection to database error does not impact public channels\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      topic = \"realtime:#{topic}\"\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      WebsocketClient.join(socket, topic, %{config: %{broadcast: %{self: true}, private: false}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n\n      {service_role_socket, _} = get_connection(tenant, serializer, role: \"service_role\")\n      WebsocketClient.join(service_role_socket, topic, %{config: %{broadcast: %{self: false}, private: false}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n\n      log =\n        capture_log(fn ->\n          :syn.update_registry(Connect, tenant.external_id, fn _pid, meta -> %{meta | conn: nil} end)\n          payload = %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"}\n          WebsocketClient.send_event(service_role_socket, topic, \"broadcast\", payload)\n          assert_receive %Message{event: \"broadcast\", payload: ^payload, topic: ^topic}, 500\n        end)\n\n      refute log =~ \"UnableToHandleBroadcast\"\n    end\n  end\n\n  describe \"private broadcast\" do\n    setup [:rls_context]\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"private broadcast with valid channel with permissions sends message\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: true}\n      topic = \"realtime:#{topic}\"\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n\n      payload = %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"}\n      WebsocketClient.send_event(socket, topic, \"broadcast\", payload)\n\n      assert_receive %Message{event: \"broadcast\", payload: ^payload, topic: ^topic}\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence],\n         topic: \"topic\"\n    test \"private broadcast with valid channel a colon character sends message and won't intercept in public channels\",\n         %{topic: topic, tenant: tenant, serializer: serializer} do\n      {anon_socket, _} = get_connection(tenant, serializer, role: \"anon\")\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      valid_topic = \"realtime:#{topic}\"\n      malicious_topic = \"realtime:private:#{topic}\"\n\n      WebsocketClient.join(socket, valid_topic, %{config: %{broadcast: %{self: true}, private: true}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^valid_topic}, 300\n\n      WebsocketClient.join(anon_socket, malicious_topic, %{config: %{broadcast: %{self: true}, private: false}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^malicious_topic}, 300\n\n      payload = %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"}\n      WebsocketClient.send_event(socket, valid_topic, \"broadcast\", payload)\n\n      assert_receive %Message{event: \"broadcast\", payload: ^payload, topic: ^valid_topic}, 500\n      refute_receive %Message{event: \"broadcast\"}\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence]\n    test \"private broadcast with valid channel no write permissions won't send message but will receive message\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      config = %{broadcast: %{self: true}, private: true}\n      topic = \"realtime:#{topic}\"\n\n      {service_role_socket, _} = get_connection(tenant, serializer, role: \"service_role\")\n      WebsocketClient.join(service_role_socket, topic, %{config: config})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      WebsocketClient.join(socket, topic, %{config: config})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n\n      payload = %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"}\n\n      WebsocketClient.send_event(socket, topic, \"broadcast\", payload)\n      refute_receive %Message{event: \"broadcast\", payload: ^payload, topic: ^topic}, 500\n\n      WebsocketClient.send_event(service_role_socket, topic, \"broadcast\", payload)\n      assert_receive %Message{event: \"broadcast\", payload: ^payload, topic: ^topic}, 500\n      assert_receive %Message{event: \"broadcast\", payload: ^payload, topic: ^topic}, 500\n    end\n\n    @tag policies: []\n    test \"private broadcast with valid channel and no read permissions won't join\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      config = %{private: true}\n      expected = \"Unauthorized: You do not have permissions to read from this Channel topic: #{topic}\"\n\n      topic = \"realtime:#{topic}\"\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n\n      log =\n        capture_log(fn ->\n          WebsocketClient.join(socket, topic, %{config: config})\n\n          assert_receive %Message{\n                           topic: ^topic,\n                           event: \"phx_reply\",\n                           payload: %{\n                             \"response\" => %{\n                               \"reason\" => ^expected\n                             },\n                             \"status\" => \"error\"\n                           }\n                         },\n                         300\n\n          refute_receive %Message{event: \"phx_reply\", topic: ^topic}, 300\n        end)\n\n      assert log =~ expected\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence]\n    test \"handles lack of connection to database error on private channels\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      topic = \"realtime:#{topic}\"\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      WebsocketClient.join(socket, topic, %{config: %{broadcast: %{self: true}, private: true}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n\n      {service_role_socket, _} = get_connection(tenant, serializer, role: \"service_role\")\n      WebsocketClient.join(service_role_socket, topic, %{config: %{broadcast: %{self: false}, private: true}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n\n      log =\n        capture_log(fn ->\n          :syn.update_registry(Connect, tenant.external_id, fn _pid, meta -> %{meta | conn: nil} end)\n          payload = %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"}\n          WebsocketClient.send_event(service_role_socket, topic, \"broadcast\", payload)\n          # Waiting more than 15 seconds as this is the amount of time we will wait for the Connection to be ready\n          refute_receive %Message{event: \"broadcast\", payload: ^payload, topic: ^topic}, 16000\n        end)\n\n      assert log =~ \"UnableToHandleBroadcast\"\n    end\n  end\n\n  describe \"trigger-based broadcast changes\" do\n    setup [:rls_context, :setup_trigger]\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"broadcast insert event changes on insert in table with trigger\", %{\n      tenant: tenant,\n      topic: topic,\n      db_conn: db_conn,\n      table_name: table_name,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: true}\n      topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      value = random_string()\n      Postgrex.query!(db_conn, \"INSERT INTO #{table_name} (details) VALUES ($1)\", [value])\n\n      record = %{\"details\" => value, \"id\" => 1}\n\n      assert_receive %Message{\n                       event: \"broadcast\",\n                       payload: %{\n                         \"event\" => \"INSERT\",\n                         \"payload\" => %{\n                           \"old_record\" => nil,\n                           \"operation\" => \"INSERT\",\n                           \"record\" => ^record,\n                           \"schema\" => \"public\",\n                           \"table\" => ^table_name\n                         },\n                         \"type\" => \"broadcast\"\n                       },\n                       topic: ^topic\n                     },\n                     1000\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence],\n         requires_data: true\n    test \"broadcast update event changes on update in table with trigger\", %{\n      tenant: tenant,\n      topic: topic,\n      db_conn: db_conn,\n      table_name: table_name,\n      serializer: serializer\n    } do\n      value = random_string()\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: true}\n      topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      new_value = random_string()\n\n      Postgrex.query!(db_conn, \"INSERT INTO #{table_name} (details) VALUES ($1)\", [value])\n      Postgrex.query!(db_conn, \"UPDATE #{table_name} SET details = $1 WHERE details = $2\", [new_value, value])\n\n      old_record = %{\"details\" => value, \"id\" => 1}\n      record = %{\"details\" => new_value, \"id\" => 1}\n\n      assert_receive %Message{\n                       event: \"broadcast\",\n                       payload: %{\n                         \"event\" => \"UPDATE\",\n                         \"payload\" => %{\n                           \"old_record\" => ^old_record,\n                           \"operation\" => \"UPDATE\",\n                           \"record\" => ^record,\n                           \"schema\" => \"public\",\n                           \"table\" => ^table_name\n                         },\n                         \"type\" => \"broadcast\"\n                       },\n                       topic: ^topic\n                     },\n                     1000\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"broadcast delete event changes on delete in table with trigger\", %{\n      tenant: tenant,\n      topic: topic,\n      db_conn: db_conn,\n      table_name: table_name,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: true}\n      topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      value = random_string()\n\n      Postgrex.query!(db_conn, \"INSERT INTO #{table_name} (details) VALUES ($1)\", [value])\n      Postgrex.query!(db_conn, \"DELETE FROM #{table_name} WHERE details = $1\", [value])\n\n      record = %{\"details\" => value, \"id\" => 1}\n\n      assert_receive %Message{\n                       event: \"broadcast\",\n                       payload: %{\n                         \"event\" => \"DELETE\",\n                         \"payload\" => %{\n                           \"old_record\" => ^record,\n                           \"operation\" => \"DELETE\",\n                           \"record\" => nil,\n                           \"schema\" => \"public\",\n                           \"table\" => ^table_name\n                         },\n                         \"type\" => \"broadcast\"\n                       },\n                       topic: ^topic\n                     },\n                     1000\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"broadcast event when function 'send' is called with private topic\", %{\n      tenant: tenant,\n      topic: topic,\n      db_conn: db_conn,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: true}\n      full_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, full_topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      value = random_string()\n      event = random_string()\n\n      Postgrex.query!(\n        db_conn,\n        \"SELECT realtime.send (json_build_object ('value', $1 :: text)::jsonb, $2 :: text, $3 :: text, TRUE::bool);\",\n        [value, event, topic]\n      )\n\n      assert_receive %Message{\n                       event: \"broadcast\",\n                       payload: %{\n                         \"event\" => ^event,\n                         \"payload\" => %{\"value\" => ^value},\n                         \"type\" => \"broadcast\"\n                       },\n                       topic: ^full_topic,\n                       join_ref: nil,\n                       ref: nil\n                     },\n                     1000\n    end\n\n    test \"broadcast event when function 'send' is called with public topic\", %{\n      tenant: tenant,\n      topic: topic,\n      db_conn: db_conn,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      full_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, full_topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      value = random_string()\n      event = random_string()\n\n      Postgrex.query!(\n        db_conn,\n        \"SELECT realtime.send (json_build_object ('value', $1 :: text)::jsonb, $2 :: text, $3 :: text, FALSE::bool);\",\n        [value, event, topic]\n      )\n\n      assert_receive %Message{\n                       event: \"broadcast\",\n                       payload: %{\n                         \"event\" => ^event,\n                         \"payload\" => %{\"value\" => ^value},\n                         \"type\" => \"broadcast\"\n                       },\n                       topic: ^full_topic\n                     },\n                     1000\n    end\n  end\n\n  defp setup_trigger(%{tenant: tenant, topic: topic}) do\n    {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n    random_name = String.downcase(\"test_#{random_string()}\")\n    query = \"CREATE TABLE #{random_name} (id serial primary key, details text)\"\n    Postgrex.query!(db_conn, query, [])\n\n    query = \"\"\"\n    CREATE OR REPLACE FUNCTION broadcast_changes_for_table_#{random_name}_trigger ()\n    RETURNS TRIGGER\n    AS $$\n    DECLARE\n    topic text;\n    BEGIN\n    topic = '#{topic}';\n    PERFORM\n      realtime.broadcast_changes (topic, TG_OP, TG_OP, TG_TABLE_NAME, TG_TABLE_SCHEMA, NEW, OLD, TG_LEVEL);\n    RETURN NULL;\n    END;\n    $$\n    LANGUAGE plpgsql;\n    \"\"\"\n\n    Postgrex.query!(db_conn, query, [])\n\n    query = \"\"\"\n    CREATE TRIGGER broadcast_changes_for_#{random_name}_table\n    AFTER INSERT OR UPDATE OR DELETE ON #{random_name}\n    FOR EACH ROW\n    EXECUTE FUNCTION broadcast_changes_for_table_#{random_name}_trigger ();\n    \"\"\"\n\n    Postgrex.query!(db_conn, query, [])\n\n    on_exit(fn ->\n      {:ok, cleanup_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n      Postgrex.query!(cleanup_conn, \"DROP TABLE #{random_name} CASCADE\", [])\n      GenServer.stop(cleanup_conn)\n    end)\n\n    %{table_name: random_name, db_conn: db_conn}\n  end\nend\n"
  },
  {
    "path": "test/integration/rt_channel/connection_lifecycle_test.exs",
    "content": "defmodule Realtime.Integration.RtChannel.ConnectionLifecycleTest do\n  use RealtimeWeb.ConnCase,\n    async: true,\n    parameterize: [\n      %{serializer: Phoenix.Socket.V1.JSONSerializer},\n      %{serializer: RealtimeWeb.Socket.V2Serializer}\n    ]\n\n  import ExUnit.CaptureLog\n  import Generators\n\n  alias Phoenix.Socket.Message\n  alias Realtime.Integration.WebsocketClient\n  alias Realtime.Tenants\n  alias RealtimeWeb.SocketDisconnect\n\n  @moduletag :capture_log\n\n  setup [:checkout_tenant_and_connect]\n\n  describe \"socket disconnect - tenant suspension\" do\n    setup [:rls_context]\n\n    test \"tenant already suspended\", %{tenant: tenant, serializer: serializer} do\n      log =\n        capture_log(fn ->\n          change_tenant_configuration(tenant, :suspend, true)\n          {:error, %Mint.WebSocket.UpgradeFailureError{}} = get_connection(tenant, serializer, role: \"anon\")\n          refute_receive _any\n        end)\n\n      assert log =~ \"RealtimeDisabledForTenant\"\n    end\n  end\n\n  describe \"socket disconnect - configuration changes\" do\n    setup [:rls_context]\n\n    test \"on jwks the socket closes and sends a system message\", %{tenant: tenant, topic: topic, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{jwt_jwks: %{keys: [\"potato\"]}})\n      assert_process_down(socket)\n    end\n\n    test \"on jwt_secret the socket closes and sends a system message\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{jwt_secret: \"potato\"})\n      assert_process_down(socket)\n    end\n\n    test \"on private_only the socket closes and sends a system message\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{private_only: true})\n      assert_process_down(socket)\n    end\n\n    test \"on other param changes the socket won't close and no message is sent\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{max_concurrent_users: 100})\n\n      refute_receive %Message{\n                       topic: ^realtime_topic,\n                       event: \"system\",\n                       payload: %{\n                         \"extension\" => \"system\",\n                         \"message\" => \"Server requested disconnect\",\n                         \"status\" => \"ok\"\n                       }\n                     },\n                     500\n\n      Process.sleep(500)\n      assert :ok = WebsocketClient.send_heartbeat(socket)\n    end\n  end\n\n  describe \"socket disconnect - token expiry\" do\n    setup [:rls_context]\n\n    test \"invalid JWT with expired token\", %{tenant: tenant, serializer: serializer} do\n      log =\n        capture_log(fn ->\n          get_connection(tenant, serializer,\n            role: \"authenticated\",\n            claims: %{:exp => System.system_time(:second) - 1000},\n            params: %{log_level: :info}\n          )\n        end)\n\n      assert log =~ \"InvalidJWTToken: Token has expired\"\n    end\n  end\n\n  describe \"socket disconnect - distributed disconnect\" do\n    setup [:rls_context]\n\n    test \"check registry of SocketDisconnect and on distribution called, kill socket\", %{\n      tenant: tenant,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n\n      for _ <- 1..10 do\n        topic = \"realtime:#{random_string()}\"\n        WebsocketClient.join(socket, topic, %{config: config})\n\n        assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 500\n      end\n\n      assert :ok = WebsocketClient.send_heartbeat(socket)\n\n      SocketDisconnect.distributed_disconnect(tenant.external_id)\n\n      assert_process_down(socket, 5000)\n    end\n  end\n\n  describe \"rate limits - concurrent users\" do\n    setup [:rls_context]\n\n    test \"max_concurrent_users limit respected\", %{tenant: tenant, serializer: serializer} do\n      Tenants.get_tenant_by_external_id(tenant.external_id)\n      change_tenant_configuration(tenant, :max_concurrent_users, 1)\n\n      {socket1, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      {socket2, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      topic1 = \"realtime:#{random_string()}\"\n      topic2 = \"realtime:#{random_string()}\"\n      WebsocketClient.join(socket1, topic1, %{config: config})\n      WebsocketClient.join(socket1, topic2, %{config: config})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       topic: ^topic1,\n                       payload: %{\"response\" => %{\"postgres_changes\" => []}, \"status\" => \"ok\"}\n                     },\n                     500\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       topic: ^topic2,\n                       payload: %{\"response\" => %{\"postgres_changes\" => []}, \"status\" => \"ok\"}\n                     },\n                     500\n\n      topic3 = \"realtime:#{random_string()}\"\n      WebsocketClient.join(socket2, topic3, %{config: config})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       topic: ^topic3,\n                       payload: %{\n                         \"response\" => %{\n                           \"reason\" => \"ConnectionRateLimitReached: Too many connected users\"\n                         },\n                         \"status\" => \"error\"\n                       }\n                     },\n                     500\n\n      Realtime.Tenants.Cache.update_cache(%{tenant | max_concurrent_users: 2})\n\n      WebsocketClient.join(socket2, topic3, %{config: config})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       topic: ^topic3,\n                       payload: %{\"response\" => %{\"postgres_changes\" => []}, \"status\" => \"ok\"}\n                     },\n                     500\n    end\n  end\n\n  describe \"rate limits - events per second\" do\n    setup [:rls_context]\n\n    test \"max_events_per_second limit respected\", %{tenant: tenant, serializer: serializer} do\n      RateCounterHelper.stop(tenant.external_id)\n\n      log =\n        capture_log(fn ->\n          {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n          config = %{broadcast: %{self: true, ack: false}, private: false, presence: %{enabled: false}}\n          realtime_topic = \"realtime:#{random_string()}\"\n\n          WebsocketClient.join(socket, realtime_topic, %{config: config})\n          assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^realtime_topic}, 500\n\n          for _ <- 1..1000, Process.alive?(socket) do\n            WebsocketClient.send_event(socket, realtime_topic, \"broadcast\", %{})\n            assert_receive %Message{event: \"broadcast\", topic: ^realtime_topic}, 500\n          end\n\n          RateCounterHelper.tick_tenant_rate_counters!(tenant.external_id)\n\n          WebsocketClient.send_event(socket, realtime_topic, \"broadcast\", %{})\n\n          assert_receive %Message{event: \"phx_close\"}, 1000\n        end)\n\n      assert log =~ \"MessagePerSecondRateLimitReached\"\n    end\n  end\n\n  describe \"rate limits - channels per client\" do\n    setup [:rls_context]\n\n    test \"max_channels_per_client limit respected\", %{tenant: tenant, serializer: serializer} do\n      change_tenant_configuration(tenant, :max_channels_per_client, 1)\n\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic_1 = \"realtime:#{random_string()}\"\n      realtime_topic_2 = \"realtime:#{random_string()}\"\n\n      WebsocketClient.join(socket, realtime_topic_1, %{config: config})\n      WebsocketClient.join(socket, realtime_topic_2, %{config: config})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       payload: %{\"response\" => %{\"postgres_changes\" => []}, \"status\" => \"ok\"},\n                       topic: ^realtime_topic_1\n                     },\n                     500\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       payload: %{\n                         \"status\" => \"error\",\n                         \"response\" => %{\n                           \"reason\" => \"ChannelRateLimitReached: Too many channels\"\n                         }\n                       },\n                       topic: ^realtime_topic_2\n                     },\n                     500\n\n      refute_receive %Message{event: \"phx_reply\", topic: ^realtime_topic_2}, 500\n\n      Realtime.Tenants.Cache.update_cache(%{tenant | max_channels_per_client: 2})\n\n      WebsocketClient.join(socket, realtime_topic_2, %{config: config})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       payload: %{\"response\" => %{\"postgres_changes\" => []}, \"status\" => \"ok\"},\n                       topic: ^realtime_topic_2\n                     },\n                     500\n    end\n  end\n\n  describe \"rate limits - joins per second\" do\n    setup [:rls_context]\n\n    test \"max_joins_per_second limit respected\", %{tenant: tenant, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{random_string()}\"\n\n      log =\n        capture_log(fn ->\n          for _ <- 1..300 do\n            WebsocketClient.join(socket, realtime_topic, %{config: config})\n            assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^realtime_topic}, 500\n          end\n\n          RateCounterHelper.tick_tenant_rate_counters!(tenant.external_id)\n\n          WebsocketClient.join(socket, realtime_topic, %{config: config})\n          assert_process_down(socket)\n        end)\n\n      assert log =~\n               \"project=#{tenant.external_id} external_id=#{tenant.external_id} [critical] ClientJoinRateLimitReached: Too many joins per second\"\n\n      assert length(String.split(log, \"ClientJoinRateLimitReached\")) <= 3\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/rt_channel/postgres_changes_test.exs",
    "content": "defmodule Realtime.Integration.RtChannel.PostgresChangesTest do\n  use RealtimeWeb.ConnCase,\n    async: true,\n    parameterize: [\n      %{serializer: Phoenix.Socket.V1.JSONSerializer},\n      %{serializer: RealtimeWeb.Socket.V2Serializer}\n    ]\n\n  import ExUnit.CaptureLog\n  import Generators\n\n  alias Extensions.PostgresCdcRls\n  alias Phoenix.Socket.Message\n  alias Postgrex\n  alias Realtime.Database\n  alias Realtime.Integration.WebsocketClient\n\n  @moduletag :capture_log\n\n  setup [:checkout_tenant_connect_and_setup_postgres_changes]\n\n  describe \"insert\" do\n    test \"handle insert\", %{tenant: tenant, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer)\n      topic = \"realtime:any\"\n      config = %{postgres_changes: [%{event: \"INSERT\", schema: \"public\"}]}\n\n      WebsocketClient.join(socket, topic, %{config: config})\n      sub_id = :erlang.phash2(%{\"event\" => \"INSERT\", \"schema\" => \"public\"})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       payload: %{\n                         \"response\" => %{\n                           \"postgres_changes\" => [\n                             %{\"event\" => \"INSERT\", \"id\" => ^sub_id, \"schema\" => \"public\"}\n                           ]\n                         },\n                         \"status\" => \"ok\"\n                       },\n                       topic: ^topic\n                     },\n                     200\n\n      assert_receive %Message{\n                       event: \"system\",\n                       payload: %{\n                         \"channel\" => \"any\",\n                         \"extension\" => \"postgres_changes\",\n                         \"message\" => \"Subscribed to PostgreSQL\",\n                         \"status\" => \"ok\"\n                       },\n                       ref: nil,\n                       topic: ^topic\n                     },\n                     8000\n\n      {:ok, _, conn} = PostgresCdcRls.get_manager_conn(tenant.external_id)\n      %{rows: [[id]]} = Postgrex.query!(conn, \"insert into test (details) values ('test') returning id\", [])\n\n      assert_receive %Message{\n                       event: \"postgres_changes\",\n                       payload: %{\n                         \"data\" => %{\n                           \"columns\" => [\n                             %{\"name\" => \"id\", \"type\" => \"int4\"},\n                             %{\"name\" => \"details\", \"type\" => \"text\"},\n                             %{\"name\" => \"binary_data\", \"type\" => \"bytea\"}\n                           ],\n                           \"commit_timestamp\" => _ts,\n                           \"errors\" => nil,\n                           \"record\" => %{\"details\" => \"test\", \"id\" => ^id},\n                           \"schema\" => \"public\",\n                           \"table\" => \"test\",\n                           \"type\" => \"INSERT\"\n                         },\n                         \"ids\" => [^sub_id]\n                       },\n                       ref: nil,\n                       topic: \"realtime:any\"\n                     },\n                     500\n    end\n  end\n\n  describe \"bytea column\" do\n    test \"handle insert with bytea data without double-encoding\", %{tenant: tenant, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer)\n      topic = \"realtime:any\"\n      config = %{postgres_changes: [%{event: \"INSERT\", schema: \"public\"}]}\n\n      WebsocketClient.join(socket, topic, %{config: config})\n      sub_id = :erlang.phash2(%{\"event\" => \"INSERT\", \"schema\" => \"public\"})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       payload: %{\"status\" => \"ok\"},\n                       topic: ^topic\n                     },\n                     200\n\n      assert_receive %Message{\n                       event: \"system\",\n                       payload: %{\n                         \"channel\" => \"any\",\n                         \"extension\" => \"postgres_changes\",\n                         \"message\" => \"Subscribed to PostgreSQL\",\n                         \"status\" => \"ok\"\n                       },\n                       ref: nil,\n                       topic: ^topic\n                     },\n                     8000\n\n      {:ok, _, conn} = PostgresCdcRls.get_manager_conn(tenant.external_id)\n\n      binary_value = <<1, 2, 3, 4, 5>>\n\n      %{rows: [[_id]]} =\n        Postgrex.query!(conn, \"insert into test (details, binary_data) values ('test', $1) returning id\", [binary_value])\n\n      assert_receive %Message{\n                       event: \"postgres_changes\",\n                       payload: %{\n                         \"data\" => %{\n                           \"record\" => record,\n                           \"type\" => \"INSERT\"\n                         },\n                         \"ids\" => [^sub_id]\n                       },\n                       ref: nil,\n                       topic: \"realtime:any\"\n                     },\n                     500\n\n      # The bytea value should be the hex string as provided by wal2json\n      assert record[\"binary_data\"] == \"0102030405\"\n    end\n  end\n\n  describe \"update\" do\n    test \"handle update\", %{tenant: tenant, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer)\n      topic = \"realtime:any\"\n      config = %{postgres_changes: [%{event: \"UPDATE\", schema: \"public\"}]}\n\n      WebsocketClient.join(socket, topic, %{config: config})\n      sub_id = :erlang.phash2(%{\"event\" => \"UPDATE\", \"schema\" => \"public\"})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       payload: %{\n                         \"response\" => %{\n                           \"postgres_changes\" => [\n                             %{\"event\" => \"UPDATE\", \"id\" => ^sub_id, \"schema\" => \"public\"}\n                           ]\n                         },\n                         \"status\" => \"ok\"\n                       },\n                       ref: \"1\",\n                       topic: ^topic\n                     },\n                     200\n\n      assert_receive %Message{\n                       event: \"system\",\n                       payload: %{\n                         \"channel\" => \"any\",\n                         \"extension\" => \"postgres_changes\",\n                         \"message\" => \"Subscribed to PostgreSQL\",\n                         \"status\" => \"ok\"\n                       },\n                       ref: nil,\n                       topic: ^topic\n                     },\n                     8000\n\n      {:ok, _, conn} = PostgresCdcRls.get_manager_conn(tenant.external_id)\n      %{rows: [[id]]} = Postgrex.query!(conn, \"insert into test (details) values ('test') returning id\", [])\n\n      Postgrex.query!(conn, \"update test set details = 'test' where id = #{id}\", [])\n\n      assert_receive %Message{\n                       event: \"postgres_changes\",\n                       payload: %{\n                         \"data\" => %{\n                           \"columns\" => [\n                             %{\"name\" => \"id\", \"type\" => \"int4\"},\n                             %{\"name\" => \"details\", \"type\" => \"text\"},\n                             %{\"name\" => \"binary_data\", \"type\" => \"bytea\"}\n                           ],\n                           \"commit_timestamp\" => _ts,\n                           \"errors\" => nil,\n                           \"old_record\" => %{\"id\" => ^id},\n                           \"record\" => %{\"details\" => \"test\", \"id\" => ^id},\n                           \"schema\" => \"public\",\n                           \"table\" => \"test\",\n                           \"type\" => \"UPDATE\"\n                         },\n                         \"ids\" => [^sub_id]\n                       },\n                       ref: nil,\n                       topic: \"realtime:any\"\n                     },\n                     500\n    end\n  end\n\n  describe \"delete\" do\n    test \"handle delete\", %{tenant: tenant, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer)\n      topic = \"realtime:any\"\n      config = %{postgres_changes: [%{event: \"DELETE\", schema: \"public\"}]}\n\n      WebsocketClient.join(socket, topic, %{config: config})\n      sub_id = :erlang.phash2(%{\"event\" => \"DELETE\", \"schema\" => \"public\"})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       payload: %{\n                         \"response\" => %{\n                           \"postgres_changes\" => [\n                             %{\"event\" => \"DELETE\", \"id\" => ^sub_id, \"schema\" => \"public\"}\n                           ]\n                         },\n                         \"status\" => \"ok\"\n                       },\n                       ref: \"1\",\n                       topic: ^topic\n                     },\n                     200\n\n      assert_receive %Message{\n                       event: \"system\",\n                       payload: %{\n                         \"channel\" => \"any\",\n                         \"extension\" => \"postgres_changes\",\n                         \"message\" => \"Subscribed to PostgreSQL\",\n                         \"status\" => \"ok\"\n                       },\n                       ref: nil,\n                       topic: ^topic\n                     },\n                     8000\n\n      {:ok, _, conn} = PostgresCdcRls.get_manager_conn(tenant.external_id)\n      %{rows: [[id]]} = Postgrex.query!(conn, \"insert into test (details) values ('test') returning id\", [])\n      Postgrex.query!(conn, \"delete from test where id = #{id}\", [])\n\n      assert_receive %Message{\n                       event: \"postgres_changes\",\n                       payload: %{\n                         \"data\" => %{\n                           \"columns\" => [\n                             %{\"name\" => \"id\", \"type\" => \"int4\"},\n                             %{\"name\" => \"details\", \"type\" => \"text\"},\n                             %{\"name\" => \"binary_data\", \"type\" => \"bytea\"}\n                           ],\n                           \"commit_timestamp\" => _ts,\n                           \"errors\" => nil,\n                           \"old_record\" => %{\"id\" => ^id},\n                           \"schema\" => \"public\",\n                           \"table\" => \"test\",\n                           \"type\" => \"DELETE\"\n                         },\n                         \"ids\" => [^sub_id]\n                       },\n                       ref: nil,\n                       topic: \"realtime:any\"\n                     },\n                     500\n    end\n  end\n\n  describe \"wildcard\" do\n    test \"handle wildcard\", %{tenant: tenant, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer)\n      topic = \"realtime:any\"\n      config = %{postgres_changes: [%{event: \"*\", schema: \"public\"}]}\n\n      WebsocketClient.join(socket, topic, %{config: config})\n      sub_id = :erlang.phash2(%{\"event\" => \"*\", \"schema\" => \"public\"})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       payload: %{\n                         \"response\" => %{\n                           \"postgres_changes\" => [\n                             %{\"event\" => \"*\", \"id\" => ^sub_id, \"schema\" => \"public\"}\n                           ]\n                         },\n                         \"status\" => \"ok\"\n                       },\n                       ref: \"1\",\n                       topic: ^topic\n                     },\n                     200\n\n      assert_receive %Message{\n                       event: \"system\",\n                       payload: %{\n                         \"channel\" => \"any\",\n                         \"extension\" => \"postgres_changes\",\n                         \"message\" => \"Subscribed to PostgreSQL\",\n                         \"status\" => \"ok\"\n                       },\n                       ref: nil,\n                       topic: ^topic\n                     },\n                     8000\n\n      {:ok, _, conn} = PostgresCdcRls.get_manager_conn(tenant.external_id)\n      %{rows: [[id]]} = Postgrex.query!(conn, \"insert into test (details) values ('test') returning id\", [])\n\n      assert_receive %Message{\n                       event: \"postgres_changes\",\n                       payload: %{\n                         \"data\" => %{\n                           \"columns\" => [\n                             %{\"name\" => \"id\", \"type\" => \"int4\"},\n                             %{\"name\" => \"details\", \"type\" => \"text\"},\n                             %{\"name\" => \"binary_data\", \"type\" => \"bytea\"}\n                           ],\n                           \"commit_timestamp\" => _ts,\n                           \"errors\" => nil,\n                           \"record\" => %{\"id\" => ^id},\n                           \"schema\" => \"public\",\n                           \"table\" => \"test\",\n                           \"type\" => \"INSERT\"\n                         },\n                         \"ids\" => [^sub_id]\n                       },\n                       ref: nil,\n                       topic: \"realtime:any\"\n                     },\n                     500\n\n      Postgrex.query!(conn, \"update test set details = 'test' where id = #{id}\", [])\n\n      assert_receive %Message{\n                       event: \"postgres_changes\",\n                       payload: %{\n                         \"data\" => %{\n                           \"columns\" => [\n                             %{\"name\" => \"id\", \"type\" => \"int4\"},\n                             %{\"name\" => \"details\", \"type\" => \"text\"},\n                             %{\"name\" => \"binary_data\", \"type\" => \"bytea\"}\n                           ],\n                           \"commit_timestamp\" => _ts,\n                           \"errors\" => nil,\n                           \"old_record\" => %{\"id\" => ^id},\n                           \"record\" => %{\"details\" => \"test\", \"id\" => ^id},\n                           \"schema\" => \"public\",\n                           \"table\" => \"test\",\n                           \"type\" => \"UPDATE\"\n                         },\n                         \"ids\" => [^sub_id]\n                       },\n                       ref: nil,\n                       topic: \"realtime:any\"\n                     },\n                     500\n\n      Postgrex.query!(conn, \"delete from test where id = #{id}\", [])\n\n      assert_receive %Message{\n                       event: \"postgres_changes\",\n                       payload: %{\n                         \"data\" => %{\n                           \"columns\" => [\n                             %{\"name\" => \"id\", \"type\" => \"int4\"},\n                             %{\"name\" => \"details\", \"type\" => \"text\"},\n                             %{\"name\" => \"binary_data\", \"type\" => \"bytea\"}\n                           ],\n                           \"commit_timestamp\" => _ts,\n                           \"errors\" => nil,\n                           \"old_record\" => %{\"id\" => ^id},\n                           \"schema\" => \"public\",\n                           \"table\" => \"test\",\n                           \"type\" => \"DELETE\"\n                         },\n                         \"ids\" => [^sub_id]\n                       },\n                       ref: nil,\n                       topic: \"realtime:any\"\n                     },\n                     500\n    end\n  end\n\n  describe \"error handling\" do\n    test \"error subscribing\", %{tenant: tenant, serializer: serializer} do\n      {:ok, conn} = Database.connect(tenant, \"realtime_test\")\n\n      {:ok, _} =\n        Database.transaction(conn, fn db_conn ->\n          Postgrex.query!(db_conn, \"drop publication if exists supabase_realtime_test\")\n        end)\n\n      {socket, _} = get_connection(tenant, serializer)\n      topic = \"realtime:any\"\n      config = %{postgres_changes: [%{event: \"INSERT\", schema: \"public\"}]}\n\n      log =\n        capture_log(fn ->\n          WebsocketClient.join(socket, topic, %{config: config})\n\n          assert_receive %Message{\n                           event: \"system\",\n                           payload: %{\n                             \"channel\" => \"any\",\n                             \"extension\" => \"postgres_changes\",\n                             \"message\" =>\n                               \"Unable to subscribe to changes with given parameters. Please check Realtime is enabled for the given connect parameters: [event: INSERT, schema: public, table: *, filters: []]\",\n                             \"status\" => \"error\"\n                           },\n                           ref: nil,\n                           topic: ^topic\n                         },\n                         8000\n        end)\n\n      assert log =~ \"RealtimeDisabledForConfiguration\"\n      assert log =~ \"Unable to subscribe to changes with given parameters\"\n    end\n\n    test \"handle nil postgres changes params as empty param changes\", %{tenant: tenant, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer)\n      topic = \"realtime:any\"\n      config = %{postgres_changes: [nil]}\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 200\n\n      refute_receive %Message{\n                       event: \"system\",\n                       payload: %{\n                         \"channel\" => \"any\",\n                         \"extension\" => \"postgres_changes\",\n                         \"message\" => \"Subscribed to PostgreSQL\",\n                         \"status\" => \"ok\"\n                       },\n                       ref: nil,\n                       topic: ^topic\n                     },\n                     1000\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/rt_channel/presence_test.exs",
    "content": "defmodule Realtime.Integration.RtChannel.PresenceTest do\n  use RealtimeWeb.ConnCase,\n    async: true,\n    parameterize: [\n      %{serializer: Phoenix.Socket.V1.JSONSerializer},\n      %{serializer: RealtimeWeb.Socket.V2Serializer}\n    ]\n\n  import ExUnit.CaptureLog\n  import Generators\n\n  alias Phoenix.Socket.Message\n  alias Realtime.Integration.WebsocketClient\n  alias Realtime.Tenants.Connect\n\n  @moduletag :capture_log\n\n  setup [:checkout_tenant_and_connect]\n\n  describe \"public presence\" do\n    setup [:rls_context]\n\n    test \"public presence\", %{tenant: tenant, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer)\n      config = %{presence: %{key: \"\", enabled: true}, private: false}\n      topic = \"realtime:any\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n      assert_receive %Message{event: \"presence_state\", payload: %{}, topic: ^topic}, 500\n\n      payload = %{\n        type: \"presence\",\n        event: \"TRACK\",\n        payload: %{name: \"realtime_presence_96\", t: 1814.7000000029802}\n      }\n\n      WebsocketClient.send_event(socket, topic, \"presence\", payload)\n\n      assert_receive %Message{event: \"presence_diff\", payload: %{\"joins\" => joins, \"leaves\" => %{}}, topic: ^topic}\n\n      join_payload = joins |> Map.values() |> hd() |> get_in([\"metas\"]) |> hd()\n      assert get_in(join_payload, [\"name\"]) == payload.payload.name\n      assert get_in(join_payload, [\"t\"]) == payload.payload.t\n    end\n\n    test \"presence enabled if param enabled is set in configuration for public channels\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, topic, %{config: %{private: false, presence: %{enabled: true}}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n      assert_receive %Message{event: \"presence_state\"}, 500\n    end\n\n    test \"presence disabled if param 'enabled' is set to false in configuration for public channels\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, topic, %{config: %{private: false, presence: %{enabled: false}}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n      refute_receive %Message{event: \"presence_state\"}, 500\n    end\n\n    test \"presence automatically enabled when user sends track message for public channel\", %{\n      tenant: tenant,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer)\n      config = %{presence: %{key: \"\", enabled: false}, private: false}\n      topic = \"realtime:any\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n      refute_receive %Message{event: \"presence_state\"}, 500\n\n      payload = %{\n        type: \"presence\",\n        event: \"TRACK\",\n        payload: %{name: \"realtime_presence_96\", t: 1814.7000000029802}\n      }\n\n      WebsocketClient.send_event(socket, topic, \"presence\", payload)\n\n      assert_receive %Message{event: \"presence_diff\", payload: %{\"joins\" => joins, \"leaves\" => %{}}, topic: ^topic}\n\n      join_payload = joins |> Map.values() |> hd() |> get_in([\"metas\"]) |> hd()\n      assert get_in(join_payload, [\"name\"]) == payload.payload.name\n      assert get_in(join_payload, [\"t\"]) == payload.payload.t\n    end\n  end\n\n  describe \"private presence\" do\n    setup [:rls_context]\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"private presence with read and write permissions will be able to track and receive presence changes\",\n         %{tenant: tenant, topic: topic, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{presence: %{key: \"\", enabled: true}, private: true}\n      topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n      assert_receive %Message{event: \"presence_state\", payload: %{}, topic: ^topic}, 500\n\n      payload = %{\n        type: \"presence\",\n        event: \"TRACK\",\n        payload: %{name: \"realtime_presence_96\", t: 1814.7000000029802}\n      }\n\n      WebsocketClient.send_event(socket, topic, \"presence\", payload)\n      refute_receive %Message{event: \"phx_leave\", topic: ^topic}\n      assert_receive %Message{event: \"presence_diff\", payload: %{\"joins\" => joins, \"leaves\" => %{}}, topic: ^topic}, 500\n      join_payload = joins |> Map.values() |> hd() |> get_in([\"metas\"]) |> hd()\n      assert get_in(join_payload, [\"name\"]) == payload.payload.name\n      assert get_in(join_payload, [\"t\"]) == payload.payload.t\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence],\n         mode: :distributed\n    test \"private presence with read and write permissions will be able to track and receive presence changes using a remote node\",\n         %{tenant: tenant, topic: topic, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{presence: %{key: \"\", enabled: true}, private: true}\n      topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n      assert_receive %Message{event: \"presence_state\", payload: %{}, topic: ^topic}, 500\n\n      payload = %{\n        type: \"presence\",\n        event: \"TRACK\",\n        payload: %{name: \"realtime_presence_96\", t: 1814.7000000029802}\n      }\n\n      WebsocketClient.send_event(socket, topic, \"presence\", payload)\n      refute_receive %Message{event: \"phx_leave\", topic: ^topic}\n      assert_receive %Message{event: \"presence_diff\", payload: %{\"joins\" => joins, \"leaves\" => %{}}, topic: ^topic}, 500\n      join_payload = joins |> Map.values() |> hd() |> get_in([\"metas\"]) |> hd()\n      assert get_in(join_payload, [\"name\"]) == payload.payload.name\n      assert get_in(join_payload, [\"t\"]) == payload.payload.t\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence]\n    test \"private presence with read permissions will be able to receive presence changes but won't be able to track\",\n         %{tenant: tenant, topic: topic, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      {secondary_socket, _} = get_connection(tenant, serializer, role: \"service_role\")\n      config = fn key -> %{presence: %{key: key, enabled: true}, private: true} end\n      topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, topic, %{config: config.(\"authenticated\")})\n\n      payload = %{\n        type: \"presence\",\n        event: \"TRACK\",\n        payload: %{name: \"realtime_presence_96\", t: 1814.7000000029802}\n      }\n\n      # This will be ignored\n      WebsocketClient.send_event(socket, topic, \"presence\", payload)\n\n      assert_receive %Message{topic: ^topic, event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n      assert_receive %Message{event: \"presence_state\", payload: %{}, ref: nil, topic: ^topic}\n      refute_receive %Message{event: \"presence_diff\", payload: _, ref: _, topic: ^topic}\n\n      payload = %{\n        type: \"presence\",\n        event: \"TRACK\",\n        payload: %{name: \"realtime_presence_97\", t: 1814.7000000029802}\n      }\n\n      # This will be tracked\n      WebsocketClient.join(secondary_socket, topic, %{config: config.(\"service_role\")})\n      WebsocketClient.send_event(secondary_socket, topic, \"presence\", payload)\n\n      assert_receive %Message{topic: ^topic, event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n      assert_receive %Message{topic: ^topic, event: \"presence_diff\", payload: %{\"joins\" => joins, \"leaves\" => %{}}}\n      assert_receive %Message{event: \"presence_state\", payload: %{}, ref: nil, topic: ^topic}\n\n      join_payload = joins |> Map.values() |> hd() |> get_in([\"metas\"]) |> hd()\n      assert get_in(join_payload, [\"name\"]) == payload.payload.name\n      assert get_in(join_payload, [\"t\"]) == payload.payload.t\n\n      assert_receive %Message{topic: ^topic, event: \"presence_diff\"} = res\n\n      assert join_payload =\n               res\n               |> Map.from_struct()\n               |> get_in([:payload, \"joins\", \"service_role\", \"metas\"])\n               |> hd()\n\n      assert get_in(join_payload, [\"name\"]) == payload.payload.name\n      assert get_in(join_payload, [\"t\"]) == payload.payload.t\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"presence enabled if param enabled is set in configuration for private channels\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, topic, %{config: %{private: true, presence: %{enabled: true}}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n      assert_receive %Message{event: \"presence_state\"}, 500\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"presence disabled if param 'enabled' is set to false in configuration for private channels\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, topic, %{config: %{private: true, presence: %{enabled: false}}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n      refute_receive %Message{event: \"presence_state\"}, 500\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"presence automatically enabled when user sends track message for private channel\",\n         %{tenant: tenant, topic: topic, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{presence: %{key: \"\", enabled: false}, private: true}\n      topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, topic, %{config: config})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n      refute_receive %Message{event: \"presence_state\"}, 500\n\n      payload = %{\n        type: \"presence\",\n        event: \"TRACK\",\n        payload: %{name: \"realtime_presence_96\", t: 1814.7000000029802}\n      }\n\n      WebsocketClient.send_event(socket, topic, \"presence\", payload)\n\n      assert_receive %Message{event: \"presence_diff\", payload: %{\"joins\" => joins, \"leaves\" => %{}}, topic: ^topic}, 500\n      join_payload = joins |> Map.values() |> hd() |> get_in([\"metas\"]) |> hd()\n      assert get_in(join_payload, [\"name\"]) == payload.payload.name\n      assert get_in(join_payload, [\"t\"]) == payload.payload.t\n    end\n  end\n\n  describe \"database connection errors\" do\n    setup [:rls_context]\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"handles lack of connection to database error on private channels\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      topic = \"realtime:#{topic}\"\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      WebsocketClient.join(socket, topic, %{config: %{private: true, presence: %{enabled: true}}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n      assert_receive %Message{event: \"presence_state\"}\n\n      log =\n        capture_log(fn ->\n          :syn.update_registry(Connect, tenant.external_id, fn _pid, meta -> %{meta | conn: nil} end)\n          payload = %{type: \"presence\", event: \"TRACK\", payload: %{name: \"realtime_presence_96\", t: 1814.7000000029802}}\n          WebsocketClient.send_event(socket, topic, \"presence\", payload)\n\n          refute_receive %Message{event: \"presence_diff\"}, 500\n          # Waiting more than 5 seconds as this is the amount of time we will wait for the Connection to be ready\n          refute_receive %Message{event: \"phx_leave\", topic: ^topic}, 16000\n        end)\n\n      assert log =~ ~r/external_id=#{tenant.external_id}.*UnableToHandlePresence/\n    end\n\n    @tag policies: []\n    test \"lack of connection to database error does not impact public channels\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      topic = \"realtime:#{topic}\"\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      WebsocketClient.join(socket, topic, %{config: %{private: false, presence: %{enabled: true}}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^topic}, 300\n      assert_receive %Message{event: \"presence_state\"}\n\n      log =\n        capture_log(fn ->\n          :syn.update_registry(Connect, tenant.external_id, fn _pid, meta -> %{meta | conn: nil} end)\n          payload = %{type: \"presence\", event: \"TRACK\", payload: %{name: \"realtime_presence_96\", t: 1814.7000000029802}}\n          WebsocketClient.send_event(socket, topic, \"presence\", payload)\n\n          assert_receive %Message{event: \"presence_diff\"}, 500\n          refute_receive %Message{event: \"phx_leave\", topic: ^topic}\n        end)\n\n      refute log =~ ~r/external_id=#{tenant.external_id}.*UnableToHandlePresence/\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/rt_channel/token_handling_test.exs",
    "content": "defmodule Realtime.Integration.RtChannel.TokenHandlingTest do\n  use RealtimeWeb.ConnCase,\n    async: true,\n    parameterize: [%{serializer: Phoenix.Socket.V1.JSONSerializer}, %{serializer: RealtimeWeb.Socket.V2Serializer}]\n\n  import ExUnit.CaptureLog\n  import Generators\n\n  alias Phoenix.Socket.Message\n  alias Realtime.Database\n  alias Realtime.Integration.WebsocketClient\n\n  @moduletag :capture_log\n\n  setup [:checkout_tenant_and_connect]\n\n  describe \"token validation\" do\n    setup [:rls_context]\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"badly formatted jwt token\", %{tenant: tenant, serializer: serializer} do\n      log =\n        capture_log(fn ->\n          WebsocketClient.connect(self(), uri(tenant, serializer), serializer, [{\"x-api-key\", \"bad_token\"}])\n        end)\n\n      assert log =~ \"MalformedJWT: The token provided is not a valid JWT\"\n    end\n\n    test \"invalid JWT with expired token\", %{tenant: tenant, serializer: serializer} do\n      log =\n        capture_log(fn ->\n          get_connection(tenant, serializer,\n            role: \"authenticated\",\n            claims: %{:exp => System.system_time(:second) - 1000},\n            params: %{log_level: :info}\n          )\n        end)\n\n      assert log =~ \"InvalidJWTToken: Token has expired\"\n    end\n\n    test \"token required the role key\", %{tenant: tenant, serializer: serializer} do\n      {:ok, token} = token_no_role(tenant)\n\n      assert {:error, %{status_code: 403}} =\n               WebsocketClient.connect(self(), uri(tenant, serializer), serializer, [{\"x-api-key\", token}])\n    end\n\n    test \"handles connection with valid api-header but ignorable access_token payload\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      realtime_topic = \"realtime:#{topic}\"\n\n      log =\n        capture_log(fn ->\n          {:ok, token} =\n            generate_token(tenant, %{\n              exp: System.system_time(:second) + 1000,\n              role: \"authenticated\",\n              sub: random_string()\n            })\n\n          {:ok, socket} = WebsocketClient.connect(self(), uri(tenant, serializer), serializer, [{\"x-api-key\", token}])\n\n          WebsocketClient.join(socket, realtime_topic, %{\n            config: %{broadcast: %{self: true}, private: false},\n            access_token: \"sb_#{random_string()}\"\n          })\n\n          assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n        end)\n\n      refute log =~ \"MalformedJWT: The token provided is not a valid JWT\"\n    end\n\n    test \"missing claims close connection\", %{tenant: tenant, topic: topic, serializer: serializer} do\n      {socket, access_token} = get_connection(tenant, serializer, role: \"authenticated\")\n\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      {:ok, token} = generate_token(tenant, %{:exp => System.system_time(:second) + 2000})\n\n      # Update token to be a near expiring token\n      WebsocketClient.send_event(socket, realtime_topic, \"access_token\", %{\"access_token\" => token})\n\n      assert_receive %Message{\n                       event: \"system\",\n                       payload: %{\n                         \"extension\" => \"system\",\n                         \"message\" => \"Fields `role` and `exp` are required in JWT\",\n                         \"status\" => \"error\"\n                       }\n                     },\n                     500\n\n      assert_receive %Message{event: \"phx_close\"}\n    end\n  end\n\n  describe \"access token refresh\" do\n    setup [:rls_context]\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"on new access_token and channel is private policies are reevaluated for read policy\",\n         %{tenant: tenant, topic: topic, serializer: serializer} do\n      {socket, access_token} = get_connection(tenant, serializer, role: \"authenticated\")\n\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{\n        config: %{broadcast: %{self: true}, private: true},\n        access_token: access_token\n      })\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      {:ok, new_token} = token_valid(tenant, \"anon\")\n\n      WebsocketClient.send_event(socket, realtime_topic, \"access_token\", %{\"access_token\" => new_token})\n\n      error_message = \"You do not have permissions to read from this Channel topic: #{topic}\"\n\n      assert_receive %Message{\n        event: \"system\",\n        payload: %{\"channel\" => ^topic, \"extension\" => \"system\", \"message\" => ^error_message, \"status\" => \"error\"},\n        topic: ^realtime_topic\n      }\n\n      assert_receive %Message{event: \"phx_close\", topic: ^realtime_topic}\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"on new access_token and channel is private policies are reevaluated for write policy\", %{\n      topic: topic,\n      tenant: tenant,\n      serializer: serializer\n    } do\n      {socket, access_token} = get_connection(tenant, serializer, role: \"authenticated\")\n      realtime_topic = \"realtime:#{topic}\"\n      config = %{broadcast: %{self: true}, private: true}\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      # Checks first send which will set write policy to true\n      payload = %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"}\n      WebsocketClient.send_event(socket, realtime_topic, \"broadcast\", payload)\n\n      assert_receive %Message{event: \"broadcast\", payload: ^payload, topic: ^realtime_topic}, 500\n\n      # RLS policies changed to only allow read\n      {:ok, db_conn} = Database.connect(tenant, \"realtime_test\")\n      clean_table(db_conn, \"realtime\", \"messages\")\n      create_rls_policies(db_conn, [:authenticated_read_broadcast_and_presence], %{topic: topic})\n\n      # Set new token to recheck policies\n      {:ok, new_token} =\n        generate_token(tenant, %{exp: System.system_time(:second) + 1000, role: \"authenticated\", sub: random_string()})\n\n      WebsocketClient.send_event(socket, realtime_topic, \"access_token\", %{\"access_token\" => new_token})\n\n      # Send message to be ignored\n      payload = %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"}\n      WebsocketClient.send_event(socket, realtime_topic, \"broadcast\", payload)\n\n      refute_receive %Message{\n                       event: \"broadcast\",\n                       payload: ^payload,\n                       topic: ^realtime_topic\n                     },\n                     1500\n    end\n\n    test \"on new access_token and channel is public policies are not reevaluated\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      {socket, access_token} = get_connection(tenant, serializer, role: \"authenticated\")\n      {:ok, new_token} = token_valid(tenant, \"anon\")\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      WebsocketClient.send_event(socket, realtime_topic, \"access_token\", %{\"access_token\" => new_token})\n\n      refute_receive %Message{}\n    end\n\n    test \"on empty string access_token the socket sends an error message\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      {socket, access_token} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      WebsocketClient.send_event(socket, realtime_topic, \"access_token\", %{\"access_token\" => \"\"})\n\n      assert_receive %Message{\n        topic: ^realtime_topic,\n        event: \"system\",\n        payload: %{\n          \"extension\" => \"system\",\n          \"message\" => msg,\n          \"status\" => \"error\"\n        }\n      }\n\n      assert_receive %Message{event: \"phx_close\"}\n      assert msg =~ \"The token provided is not a valid JWT\"\n    end\n\n    test \"on expired access_token the socket sends an error message\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      sub = random_string()\n\n      {socket, access_token} = get_connection(tenant, serializer, role: \"authenticated\", claims: %{sub: sub})\n\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      {:ok, token} = generate_token(tenant, %{:exp => System.system_time(:second) - 1000, sub: sub})\n\n      log =\n        capture_log(fn ->\n          WebsocketClient.send_event(socket, realtime_topic, \"access_token\", %{\"access_token\" => token})\n\n          assert_receive %Message{\n            topic: ^realtime_topic,\n            event: \"system\",\n            payload: %{\"extension\" => \"system\", \"message\" => \"Token has expired \" <> _, \"status\" => \"error\"}\n          }\n\n          assert_receive %Message{event: \"phx_close\", topic: ^realtime_topic}\n        end)\n\n      assert log =~ \"ChannelShutdown: Token has expired\"\n    end\n\n    test \"ChannelShutdown include sub if available in jwt claims\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      exp = System.system_time(:second) + 10_000\n\n      {socket, access_token} =\n        get_connection(tenant, serializer, role: \"authenticated\", claims: %{exp: exp}, params: %{log_level: :warning})\n\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n      sub = random_string()\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}, topic: ^realtime_topic}, 500\n\n      {:ok, token} = generate_token(tenant, %{:exp => System.system_time(:second) - 1000, sub: sub})\n\n      log =\n        capture_log([level: :warning], fn ->\n          WebsocketClient.send_event(socket, realtime_topic, \"access_token\", %{\"access_token\" => token})\n\n          assert_receive %Message{event: \"system\"}, 1000\n          assert_receive %Message{event: \"phx_close\", topic: ^realtime_topic}\n        end)\n\n      assert log =~ \"ChannelShutdown\"\n      assert log =~ \"sub=#{sub}\"\n    end\n\n    test \"on sb prefixed access_token the socket ignores the message and respects JWT expiry time\", %{\n      tenant: tenant,\n      topic: topic,\n      serializer: serializer\n    } do\n      sub = random_string()\n\n      {socket, access_token} =\n        get_connection(tenant, serializer,\n          role: \"authenticated\",\n          claims: %{sub: sub, exp: System.system_time(:second) + 5}\n        )\n\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      WebsocketClient.send_event(socket, realtime_topic, \"access_token\", %{\n        \"access_token\" => \"sb_publishable_-fake_key\"\n      })\n\n      # Check if the new token does not trigger a shutdown\n      refute_receive %Message{event: \"system\", topic: ^realtime_topic}, 100\n\n      # Await to check if channel respects token expiry time\n      assert_receive %Message{\n                       event: \"system\",\n                       payload: %{\"extension\" => \"system\", \"message\" => msg, \"status\" => \"error\"},\n                       topic: ^realtime_topic\n                     },\n                     5000\n\n      assert_receive %Message{event: \"phx_close\", topic: ^realtime_topic}\n      assert msg =~ \"Token has expired\"\n    end\n  end\n\n  describe \"token expiry\" do\n    setup [:rls_context]\n\n    test \"checks token periodically\", %{tenant: tenant, topic: topic, serializer: serializer} do\n      {socket, access_token} = get_connection(tenant, serializer, role: \"authenticated\")\n\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      {:ok, token} =\n        generate_token(tenant, %{:exp => System.system_time(:second) + 2, role: \"authenticated\"})\n\n      # Update token to be a near expiring token\n      WebsocketClient.send_event(socket, realtime_topic, \"access_token\", %{\"access_token\" => token})\n\n      # Awaits to see if connection closes automatically\n      assert_receive %Message{\n                       event: \"system\",\n                       payload: %{\"extension\" => \"system\", \"message\" => msg, \"status\" => \"error\"}\n                     },\n                     3000\n\n      assert_receive %Message{event: \"phx_close\"}\n\n      assert msg =~ \"Token has expired\"\n    end\n\n    test \"token expires in between joins\", %{tenant: tenant, topic: topic, serializer: serializer} do\n      {socket, access_token} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      {:ok, access_token} =\n        generate_token(tenant, %{:exp => System.system_time(:second) + 1, role: \"authenticated\"})\n\n      # token expires in between joins so it needs to be handled by the channel and not the socket\n      Process.sleep(1000)\n      realtime_topic = \"realtime:#{topic}\"\n\n      log =\n        capture_log(fn ->\n          WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n          assert_receive %Message{\n                           event: \"phx_reply\",\n                           payload: %{\n                             \"status\" => \"error\",\n                             \"response\" => %{\"reason\" => reason}\n                           },\n                           topic: ^realtime_topic\n                         },\n                         500\n\n          assert reason =~ \"InvalidJWTToken: Token has expired\"\n        end)\n\n      assert_receive %Message{event: \"phx_close\"}\n      assert log =~ \"#{tenant.external_id}\"\n    end\n\n    test \"token loses claims in between joins\", %{tenant: tenant, topic: topic, serializer: serializer} do\n      {socket, access_token} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      {:ok, access_token} = generate_token(tenant, %{:exp => System.system_time(:second) + 10})\n\n      # token breaks claims in between joins so it needs to be handled by the channel and not the socket\n      realtime_topic = \"realtime:#{topic}\"\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       payload: %{\n                         \"status\" => \"error\",\n                         \"response\" => %{\n                           \"reason\" => \"InvalidJWTToken: Fields `role` and `exp` are required in JWT\"\n                         }\n                       },\n                       topic: ^realtime_topic\n                     },\n                     500\n\n      assert_receive %Message{event: \"phx_close\"}\n    end\n\n    test \"token is badly formatted in between joins\", %{tenant: tenant, topic: topic, serializer: serializer} do\n      {socket, access_token} = get_connection(tenant, serializer, role: \"authenticated\")\n      config = %{broadcast: %{self: true}, private: false}\n      realtime_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: access_token})\n\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n\n      # token becomes a string in between joins so it needs to be handled by the channel and not the socket\n      WebsocketClient.join(socket, realtime_topic, %{config: config, access_token: \"potato\"})\n\n      assert_receive %Message{\n                       event: \"phx_reply\",\n                       payload: %{\n                         \"status\" => \"error\",\n                         \"response\" => %{\n                           \"reason\" => \"MalformedJWT: The token provided is not a valid JWT\"\n                         }\n                       },\n                       topic: ^realtime_topic\n                     },\n                     500\n\n      assert_receive %Message{event: \"phx_close\"}\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/rt_channel/wal_bloat_test.exs",
    "content": "defmodule Realtime.Integration.RtChannel.WalBloatTest do\n  use RealtimeWeb.ConnCase,\n    async: false,\n    parameterize: [\n      %{serializer: Phoenix.Socket.V1.JSONSerializer},\n      %{serializer: RealtimeWeb.Socket.V2Serializer}\n    ]\n\n  import Generators\n\n  alias Phoenix.Socket.Message\n  alias Postgrex\n  alias Realtime.Database\n  alias Realtime.Integration.WebsocketClient\n  alias Realtime.Tenants.Connect\n  alias Realtime.Tenants.ReplicationConnection\n\n  @moduletag :capture_log\n\n  setup [:checkout_tenant_and_connect]\n\n  describe \"WAL bloat handling\" do\n    setup %{tenant: tenant} do\n      topic = random_string()\n      {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n\n      %{rows: [[max_wal_size]]} = Postgrex.query!(db_conn, \"SHOW max_wal_size\", [])\n      %{rows: [[wal_keep_size]]} = Postgrex.query!(db_conn, \"SHOW wal_keep_size\", [])\n      %{rows: [[max_slot_wal_keep_size]]} = Postgrex.query!(db_conn, \"SHOW max_slot_wal_keep_size\", [])\n\n      assert max_wal_size == \"32MB\"\n      assert wal_keep_size == \"32MB\"\n      assert max_slot_wal_keep_size == \"32MB\"\n\n      Postgrex.query!(db_conn, \"CREATE TABLE IF NOT EXISTS wal_test (id INT, data TEXT)\", [])\n\n      Postgrex.query!(\n        db_conn,\n        \"\"\"\n          CREATE OR REPLACE FUNCTION wal_test_trigger_func() RETURNS TRIGGER AS $$\n          BEGIN\n            PERFORM realtime.send(json_build_object ('value', 'test' :: text)::jsonb, 'test', '#{topic}', false);\n            RETURN NULL;\n          END;\n          $$ LANGUAGE plpgsql;\n        \"\"\",\n        []\n      )\n\n      Postgrex.query!(db_conn, \"DROP TRIGGER IF EXISTS wal_test_trigger ON wal_test\", [])\n\n      Postgrex.query!(\n        db_conn,\n        \"\"\"\n          CREATE TRIGGER wal_test_trigger\n          AFTER INSERT OR UPDATE OR DELETE ON wal_test\n          FOR EACH ROW\n          EXECUTE FUNCTION wal_test_trigger_func()\n        \"\"\",\n        []\n      )\n\n      GenServer.stop(db_conn)\n\n      on_exit(fn ->\n        {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n\n        Postgrex.query!(db_conn, \"DROP TABLE IF EXISTS wal_test CASCADE\", [])\n        GenServer.stop(db_conn)\n      end)\n\n      %{topic: topic}\n    end\n\n    @tag timeout: :timer.minutes(3)\n    test \"track PID changes during WAL bloat creation\", %{tenant: tenant, topic: topic, serializer: serializer} do\n      {socket, _} = get_connection(tenant, serializer, role: \"authenticated\")\n      full_topic = \"realtime:#{topic}\"\n\n      WebsocketClient.join(socket, full_topic, %{config: %{broadcast: %{self: true}, private: false}})\n      assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n      assert Connect.ready?(tenant.external_id)\n\n      {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      original_connect_pid = Connect.whereis(tenant.external_id)\n      original_replication_pid = ReplicationConnection.whereis(tenant.external_id)\n      await_replication_slot_active(db_conn, 30, 500)\n      original_db_pid = active_replication_slot_pid!(db_conn)\n\n      replication_ref = Process.monitor(original_replication_pid)\n\n      generate_wal_bloat(tenant)\n      terminate_bloat_connections(db_conn)\n\n      assert_receive {:DOWN, ^replication_ref, :process, ^original_replication_pid, _}, 60_000\n\n      assert Connect.ready?(tenant.external_id)\n      {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      new_db_pid = await_replication_slot_active(db_conn, 60, 1000)\n\n      assert new_db_pid != original_db_pid\n      assert ^original_connect_pid = Connect.whereis(tenant.external_id)\n      assert original_replication_pid != ReplicationConnection.whereis(tenant.external_id)\n\n      payload = %{\"event\" => \"TEST\", \"payload\" => %{\"msg\" => 1}, \"type\" => \"broadcast\"}\n      WebsocketClient.send_event(socket, full_topic, \"broadcast\", payload)\n      assert_receive %Message{event: \"broadcast\", payload: ^payload, topic: ^full_topic}, 500\n\n      Postgrex.query!(db_conn, \"INSERT INTO wal_test VALUES (1, 'test')\", [])\n\n      assert_receive %Message{\n                       event: \"broadcast\",\n                       payload: %{\n                         \"event\" => \"test\",\n                         \"payload\" => %{\"value\" => \"test\"},\n                         \"type\" => \"broadcast\"\n                       },\n                       join_ref: nil,\n                       ref: nil,\n                       topic: ^full_topic\n                     },\n                     5000\n    end\n  end\n\n  defp active_replication_slot_pid!(db_conn) do\n    %{rows: [[pid]]} =\n      Postgrex.query!(\n        db_conn,\n        \"SELECT active_pid FROM pg_replication_slots WHERE active_pid IS NOT NULL AND slot_name = 'supabase_realtime_messages_replication_slot_'\",\n        []\n      )\n\n    pid\n  end\n\n  defp await_replication_slot_active(db_conn, retries, interval_ms) do\n    Enum.reduce_while(1..retries, nil, fn _, _ ->\n      case Postgrex.query!(\n             db_conn,\n             \"SELECT active_pid FROM pg_replication_slots WHERE active_pid IS NOT NULL AND slot_name = 'supabase_realtime_messages_replication_slot_'\",\n             []\n           ) do\n        %{rows: [[pid]]} ->\n          {:halt, pid}\n\n        _ ->\n          Process.sleep(interval_ms)\n          {:cont, nil}\n      end\n    end)\n    |> then(fn\n      nil -> flunk(\"Replication slot did not become active within #{retries}s\")\n      pid -> pid\n    end)\n  end\n\n  defp generate_wal_bloat(tenant) do\n    1..5\n    |> Enum.map(fn _ ->\n      Task.async(fn ->\n        {:ok, conn} = Database.connect(tenant, \"realtime_bloat\", :stop)\n\n        Postgrex.transaction(conn, fn tx ->\n          Postgrex.query(tx, \"INSERT INTO wal_test SELECT generate_series(1, 100000), repeat('x', 2000)\", [])\n          {:error, \"test\"}\n        end)\n\n        Process.exit(conn, :normal)\n      end)\n    end)\n    |> Task.await_many(20_000)\n  end\n\n  defp terminate_bloat_connections(db_conn) do\n    Postgrex.query!(\n      db_conn,\n      \"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE application_name = 'realtime_bloat'\",\n      []\n    )\n  end\nend\n"
  },
  {
    "path": "test/integration/tests.ts",
    "content": "import { RealtimeClient } from \"npm:@supabase/supabase-js@latest\";\nimport { sleep } from \"https://deno.land/x/sleep/mod.ts\";\nimport { describe, it } from \"jsr:@std/testing/bdd\";\nimport { assertEquals } from \"jsr:@std/assert\";\nimport { deadline } from \"jsr:@std/async/deadline\";\n\nconst withDeadline = <Fn extends (...args: never[]) => Promise<unknown>>(fn: Fn, ms: number): Fn =>\n  ((...args) => deadline(fn(...args), ms)) as Fn;\n\nconst url = \"http://realtime-dev.localhost:4100/socket\";\nconst serviceRoleKey = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwNzU3NzYzODIsInJlZiI6IjEyNy4wLjAuMSIsInJvbGUiOiJzZXJ2aWNlX3JvbGUiLCJpYXQiOjE3NjA3NzYzODJ9.nupH8pnrOTgK9Xaq8-D4Ry-yQ-PnlXEagTVywQUJVIE\"\nconst apiKey = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwNzU2NjE3MjEsInJlZiI6IjEyNy4wLjAuMSIsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwiaWF0IjoxNzYwNjYxNzIxfQ.PxpBoelC9vWQ2OVhmwKBUDEIKgX7MpgSdsnmXw7UdYk\";\n\nconst realtimeV1 = { vsn: '1.0.0', params: { apikey: apiKey } , heartbeatIntervalMs: 5000, timeout: 5000 };\nconst realtimeV2 = { vsn: '2.0.0', params: { apikey: apiKey } , heartbeatIntervalMs: 5000, timeout: 5000 };\nconst realtimeServiceRole = { vsn: '2.0.0', logger: console.log, params: { apikey: serviceRoleKey } , heartbeatIntervalMs: 5000, timeout: 5000 };\n\nlet clientV1: RealtimeClient | null;\nlet clientV2: RealtimeClient | null;\n\ndescribe(\"broadcast extension\", { sanitizeOps: false, sanitizeResources: false }, () => {\n  it(\"users with different versions can receive self broadcast\", withDeadline(async () => {\n    clientV1 = new RealtimeClient(url, realtimeV1)\n    clientV2 = new RealtimeClient(url, realtimeV2)\n    let resultV1 = null;\n    let resultV2 = null;\n    let event = crypto.randomUUID();\n    let topic = \"topic:\" + crypto.randomUUID();\n    let expectedPayload = { message: crypto.randomUUID() };\n    const config = { config: { broadcast: { ack: true, self: true } } };\n\n    const channelV1 = clientV1\n      .channel(topic, config)\n      .on(\"broadcast\", { event }, ({ payload }) => (resultV1 = payload))\n      .subscribe();\n\n    const channelV2 = clientV2\n      .channel(topic, config)\n      .on(\"broadcast\", { event }, ({ payload }) => (resultV2 = payload))\n      .subscribe();\n\n    while (channelV1.state != \"joined\" || channelV2.state != \"joined\") await sleep(0.2);\n\n    // Send from V1 client - both should receive\n    await channelV1.send({\n      type: \"broadcast\",\n      event,\n      payload: expectedPayload,\n    });\n\n    while (resultV1 == null || resultV2 == null) await sleep(0.2);\n\n    assertEquals(resultV1, expectedPayload);\n    assertEquals(resultV2, expectedPayload);\n\n    // Reset results for second test\n    resultV1 = null;\n    resultV2 = null;\n    let expectedPayload2 = { message: crypto.randomUUID() };\n\n    // Send from V2 client - both should receive\n    await channelV2.send({\n      type: \"broadcast\",\n      event,\n      payload: expectedPayload2,\n    });\n\n    while (resultV1 == null || resultV2 == null) await sleep(0.2);\n\n    assertEquals(resultV1, expectedPayload2);\n    assertEquals(resultV2, expectedPayload2);\n\n    await channelV1.unsubscribe();\n    await channelV2.unsubscribe();\n\n    await stopClient(clientV1);\n    await stopClient(clientV2);\n    clientV1 = null;\n    clientV2 = null;\n  }, 5000));\n\n  it(\"v2 can send/receive binary payload\", withDeadline(async () => {\n    clientV2 = new RealtimeClient(url, realtimeV2)\n    let result = null;\n    let event = crypto.randomUUID();\n    let topic = \"topic:\" + crypto.randomUUID();\n    const expectedPayload = new ArrayBuffer(2);\n    const uint8 = new Uint8Array(expectedPayload); // View the buffer as unsigned 8-bit integers\n    uint8[0] = 125;\n    uint8[1] = 255;\n\n    const config = { config: { broadcast: { ack: true, self: true } } };\n\n    const channelV2 = clientV2\n      .channel(topic, config)\n      .on(\"broadcast\", { event }, ({ payload }) => (result = payload))\n      .subscribe();\n\n    while (channelV2.state != \"joined\") await sleep(0.2);\n\n    await channelV2.send({\n      type: \"broadcast\",\n      event,\n      payload: expectedPayload,\n    });\n\n    while (result == null) await sleep(0.2);\n\n    assertEquals(result, expectedPayload);\n\n    await channelV2.unsubscribe();\n\n    await stopClient(clientV2);\n    clientV2 = null;\n  }, 5000));\n\n  it(\"users with different versions can receive broadcasts from endpoint\", withDeadline(async () => {\n    clientV1 = new RealtimeClient(url, realtimeV1)\n    clientV2 = new RealtimeClient(url, realtimeV2)\n    let resultV1 = null;\n    let resultV2 = null;\n    let event = crypto.randomUUID();\n    let topic = \"topic:\" + crypto.randomUUID();\n    let expectedPayload = { message: crypto.randomUUID() };\n    const config = { config: { broadcast: { ack: true, self: true } } };\n\n    const channelV1 = clientV1\n      .channel(topic, config)\n      .on(\"broadcast\", { event }, ({ payload }) => (resultV1 = payload))\n      .subscribe();\n\n    const channelV2 = clientV2\n      .channel(topic, config)\n      .on(\"broadcast\", { event }, ({ payload }) => (resultV2 = payload))\n      .subscribe();\n\n    while (channelV1.state != \"joined\" || channelV2.state != \"joined\") await sleep(0.2);\n\n    // Send from unsubscribed channel - both should receive\n    new RealtimeClient(url, realtimeServiceRole).channel(topic, config).httpSend(event, expectedPayload);\n\n    while (resultV1 == null || resultV2 == null) await sleep(0.2);\n\n    assertEquals(resultV1, expectedPayload);\n    assertEquals(resultV2, expectedPayload);\n\n    await channelV1.unsubscribe();\n    await channelV2.unsubscribe();\n\n    await stopClient(clientV1);\n    await stopClient(clientV2);\n    clientV1 = null;\n    clientV2 = null;\n  }, 5000));\n});\n\n// describe(\"presence extension\", () => {\n//   it(\"user is able to receive presence updates\", async () => {\n//     let result: any = [];\n//     let error = null;\n//     let topic = \"topic:\" + crypto.randomUUID();\n//     let keyV1 = \"key V1\";\n//     let keyV2 = \"key V2\";\n//\n//     const configV1 = { config: { presence: { keyV1 } } };\n//     const configV2 = { config: { presence: { keyV1 } } };\n//\n//     const channelV1 = clientV1\n//       .channel(topic, configV1)\n//       .on(\"presence\", { event: \"join\" }, ({ key, newPresences }) =>\n//         result.push({ key, newPresences })\n//       )\n//       .subscribe();\n//\n//     const channelV2 = clientV2\n//       .channel(topic, configV2)\n//       .on(\"presence\", { event: \"join\" }, ({ key, newPresences }) =>\n//         result.push({ key, newPresences })\n//       )\n//       .subscribe();\n//\n//     while (channelV1.state != \"joined\" || channelV2.state != \"joined\") await sleep(0.2);\n//\n//     const resV1 = await channelV1.track({ key: keyV1 });\n//     const resV2 = await channelV2.track({ key: keyV2 });\n//\n//     if (resV1 == \"timed out\" || resV2 == \"timed out\") error = resV1 || resV2;\n//\n//     sleep(2.2);\n//\n//     // FIXME write assertions\n//     console.log(result)\n//     let presences = result[0].newPresences[0];\n//     assertEquals(result[0].key, keyV1);\n//     assertEquals(presences.message, message);\n//     assertEquals(error, null);\n//   });\n// });\n\nasync function stopClient(client: RealtimeClient | null) {\n  if (client) {\n    await client.removeAllChannels();\n  }\n}\n"
  },
  {
    "path": "test/integration/tracker_test.exs",
    "content": "defmodule Integration.TrackerTest do\n  # Changing the Tracker ETS table\n  use RealtimeWeb.ConnCase, async: false\n\n  alias RealtimeWeb.RealtimeChannel.Tracker\n  alias Phoenix.Socket.Message\n  alias Realtime.Tenants.Connect\n  alias Realtime.Integration.WebsocketClient\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    :ets.delete_all_objects(Tracker.table_name())\n\n    {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n    assert Connect.ready?(tenant.external_id)\n    %{db_conn: db_conn, tenant: tenant}\n  end\n\n  test \"tracks and untracks properly channels\", %{tenant: tenant} do\n    {socket, _} = get_connection(tenant)\n    config = %{broadcast: %{self: true}, private: false, presence: %{enabled: false}}\n\n    topics =\n      for _ <- 1..10 do\n        topic = \"realtime:#{random_string()}\"\n        :ok = WebsocketClient.join(socket, topic, %{config: config})\n        assert_receive %Message{topic: ^topic, event: \"phx_reply\"}, 500\n        topic\n      end\n\n    for topic <- topics do\n      :ok = WebsocketClient.leave(socket, topic, %{})\n      assert_receive %Message{topic: ^topic, event: \"phx_close\"}, 500\n    end\n\n    start_supervised!({Tracker, check_interval_in_ms: 100})\n    # wait to trigger tracker\n    assert_process_down(socket, 1000)\n  end\n\n  test \"failed connections are present in tracker with counter lower than 0 so they are actioned on by tracker\", %{\n    tenant: tenant\n  } do\n    assert [] = Tracker.list_pids()\n\n    {socket, _} = get_connection(tenant)\n    config = %{broadcast: %{self: true}, private: true, presence: %{enabled: false}}\n\n    for _ <- 1..10 do\n      topic = \"realtime:#{random_string()}\"\n      :ok = WebsocketClient.join(socket, topic, %{config: config})\n      assert_receive %Message{topic: ^topic, event: \"phx_reply\", payload: %{\"status\" => \"error\"}}, 500\n    end\n\n    assert [{_pid, count}] = Tracker.list_pids()\n    assert count == 0\n  end\n\n  test \"failed connections but one succeeds properly tracks\", %{tenant: tenant} do\n    assert [] = Tracker.list_pids()\n\n    {socket, _} = get_connection(tenant)\n    topic = \"realtime:#{random_string()}\"\n\n    :ok =\n      WebsocketClient.join(socket, topic, %{\n        config: %{broadcast: %{self: true}, private: false, presence: %{enabled: false}}\n      })\n\n    assert_receive %Message{topic: ^topic, event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n    assert [{_pid, count}] = Tracker.list_pids()\n    assert count == 1\n\n    for _ <- 1..10 do\n      topic = \"realtime:#{random_string()}\"\n\n      :ok =\n        WebsocketClient.join(socket, topic, %{\n          config: %{broadcast: %{self: true}, private: true, presence: %{enabled: false}}\n        })\n\n      assert_receive %Message{topic: ^topic, event: \"phx_reply\", payload: %{\"status\" => \"error\"}}, 500\n    end\n\n    topic = \"realtime:#{random_string()}\"\n\n    :ok =\n      WebsocketClient.join(socket, topic, %{\n        config: %{broadcast: %{self: true}, private: false, presence: %{enabled: false}}\n      })\n\n    assert_receive %Message{topic: ^topic, event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}, 500\n    assert [{_pid, count}] = Tracker.list_pids()\n    assert count == 2\n  end\nend\n"
  },
  {
    "path": "test/realtime/adapters/postgres/protocol_test.exs",
    "content": "defmodule Realtime.Adapters.Postgres.ProtocolTest do\n  use ExUnit.Case, async: true\n\n  alias Realtime.Adapters.Postgres.Protocol\n  alias Realtime.Adapters.Postgres.Protocol.Write\n  alias Realtime.Adapters.Postgres.Protocol.KeepAlive\n\n  test \"defguard is_write/1\" do\n    require Protocol\n    assert Protocol.is_write(\"w\")\n    refute Protocol.is_write(\"k\")\n  end\n\n  test \"defguard is_keep_alive/1\" do\n    require Protocol\n    assert Protocol.is_keep_alive(\"k\")\n    refute Protocol.is_keep_alive(\"w\")\n  end\n\n  describe \"parse/1\" do\n    test \"parses a write message\" do\n      wal_start = 100\n      wal_end = 200\n      clock = 300\n      message = \"some wal data\"\n\n      binary = <<?w, wal_start::64, wal_end::64, clock::64, message::binary>>\n\n      assert %Write{\n               server_wal_start: ^wal_start,\n               server_wal_end: ^wal_end,\n               server_system_clock: ^clock,\n               message: ^message\n             } = Protocol.parse(binary)\n    end\n\n    test \"parses a keep alive message with reply now\" do\n      wal_end = 500\n      clock = 600\n\n      binary = <<?k, wal_end::64, clock::64, 1::8>>\n\n      assert %KeepAlive{wal_end: ^wal_end, clock: ^clock, reply: :now} = Protocol.parse(binary)\n    end\n\n    test \"parses a keep alive message with reply later\" do\n      wal_end = 500\n      clock = 600\n\n      binary = <<?k, wal_end::64, clock::64, 0::8>>\n\n      assert %KeepAlive{wal_end: ^wal_end, clock: ^clock, reply: :later} = Protocol.parse(binary)\n    end\n  end\n\n  describe \"standby_status/5\" do\n    test \"returns binary message with reply now\" do\n      [message] = Protocol.standby_status(100, 200, 300, :now, 400)\n\n      assert <<?r, 100::64, 200::64, 300::64, 400::64, 1::8>> = message\n    end\n\n    test \"returns binary message with reply later\" do\n      [message] = Protocol.standby_status(100, 200, 300, :later, 400)\n\n      assert <<?r, 100::64, 200::64, 300::64, 400::64, 0::8>> = message\n    end\n\n    test \"uses current_time when clock is nil\" do\n      [message] = Protocol.standby_status(100, 200, 300, :now)\n\n      assert <<?r, 100::64, 200::64, 300::64, _clock::64, 1::8>> = message\n    end\n  end\n\n  test \"hold/0 returns empty list\" do\n    assert Protocol.hold() == []\n  end\n\n  test \"current_time/0 returns a positive integer\" do\n    time = Protocol.current_time()\n    assert is_integer(time)\n    assert time > 0\n  end\nend\n"
  },
  {
    "path": "test/realtime/api/extensions_test.exs",
    "content": "defmodule Realtime.Api.ExtensionsTest do\n  use ExUnit.Case, async: true\n\n  alias Realtime.Api.Extensions\n\n  describe \"changeset/2 with nil type\" do\n    test \"skips default settings merge\" do\n      changeset = Extensions.changeset(%Extensions{}, %{\"settings\" => %{\"foo\" => \"bar\"}})\n      assert changeset.changes[:settings] == %{\"foo\" => \"bar\"}\n    end\n\n    test \"validates required fields\" do\n      changeset = Extensions.changeset(%Extensions{}, %{})\n      refute changeset.valid?\n      assert {\"can't be blank\", _} = changeset.errors[:type]\n      assert {\"can't be blank\", _} = changeset.errors[:settings]\n    end\n  end\n\n  describe \"changeset/2 with type\" do\n    test \"merges default settings for postgres_cdc_rls\" do\n      attrs = %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"region\" => \"us-east-1\",\n          \"db_host\" => \"localhost\",\n          \"db_name\" => \"postgres\",\n          \"db_user\" => \"user\",\n          \"db_port\" => \"5432\",\n          \"db_password\" => \"pass\"\n        }\n      }\n\n      changeset = Extensions.changeset(%Extensions{}, attrs)\n      settings = changeset.changes[:settings]\n\n      assert settings[\"publication\"] == \"supabase_realtime\"\n      assert settings[\"slot_name\"] == \"supabase_realtime_replication_slot\"\n      assert settings[\"region\"] == \"us-east-1\"\n    end\n  end\n\n  describe \"validate_required_settings/2\" do\n    test \"adds error when required field is nil\" do\n      required = [{\"db_host\", &is_binary/1, false}]\n\n      changeset =\n        %Extensions{}\n        |> Ecto.Changeset.cast(%{type: \"test\", settings: %{}}, [:type, :settings])\n        |> Extensions.validate_required_settings(required)\n\n      refute changeset.valid?\n      assert {\"db_host can't be blank\", []} = changeset.errors[:settings]\n    end\n\n    test \"adds error when checker function fails\" do\n      required = [{\"db_port\", &is_binary/1, false}]\n\n      changeset =\n        %Extensions{}\n        |> Ecto.Changeset.cast(%{type: \"test\", settings: %{\"db_port\" => 5432}}, [:type, :settings])\n        |> Extensions.validate_required_settings(required)\n\n      refute changeset.valid?\n      assert {\"db_port is invalid\", []} = changeset.errors[:settings]\n    end\n\n    test \"passes when all required fields are valid\" do\n      required = [{\"db_host\", &is_binary/1, false}]\n\n      changeset =\n        %Extensions{}\n        |> Ecto.Changeset.cast(%{type: \"test\", settings: %{\"db_host\" => \"localhost\"}}, [:type, :settings])\n        |> Extensions.validate_required_settings(required)\n\n      assert changeset.valid?\n    end\n  end\n\n  describe \"encrypt_settings/2\" do\n    test \"encrypts fields marked for encryption\" do\n      required = [{\"db_password\", &is_binary/1, true}]\n\n      changeset =\n        %Extensions{}\n        |> Ecto.Changeset.cast(%{type: \"test\", settings: %{\"db_password\" => \"secret\"}}, [:type, :settings])\n        |> Extensions.encrypt_settings(required)\n\n      settings = Ecto.Changeset.get_change(changeset, :settings)\n      assert settings[\"db_password\"] != \"secret\"\n      assert Realtime.Crypto.decrypt!(settings[\"db_password\"]) == \"secret\"\n    end\n\n    test \"does not modify fields not marked for encryption\" do\n      required = [{\"region\", &is_binary/1, false}]\n\n      changeset =\n        %Extensions{}\n        |> Ecto.Changeset.cast(%{type: \"test\", settings: %{\"region\" => \"us-east-1\"}}, [:type, :settings])\n        |> Extensions.encrypt_settings(required)\n\n      settings = Ecto.Changeset.get_change(changeset, :settings)\n      assert settings[\"region\"] == \"us-east-1\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/api_test.exs",
    "content": "defmodule Realtime.ApiTest do\n  use Realtime.DataCase, async: true\n\n  use Mimic\n\n  alias Realtime.Api\n  alias Realtime.Api.Extensions, as: ApiExtensions\n  alias Realtime.Api.Tenant\n  alias Realtime.Crypto\n  alias Realtime.GenCounter\n  alias Realtime.RateCounter\n  alias Realtime.Tenants.Connect\n  alias Extensions.PostgresCdcRls\n\n  @db_conf Application.compile_env(:realtime, Realtime.Repo)\n\n  defp create_tenants(_) do\n    tenant1 = tenant_fixture(%{max_concurrent_users: 10_000_000})\n    tenant2 = tenant_fixture(%{max_concurrent_users: 20_000_000})\n    tenant3 = tenant_fixture(%{max_concurrent_users: 30_000_000})\n    %{tenants: [tenant1, tenant2, tenant3]}\n  end\n\n  describe \"list_tenants/0\" do\n    setup [:create_tenants]\n\n    test \"returns all tenants\", %{tenants: tenants} do\n      assert Enum.sort(Api.list_tenants()) == Enum.sort(tenants)\n    end\n  end\n\n  describe \"list_tenants/1\" do\n    setup [:create_tenants]\n\n    test \"list_tenants/1 returns filtered tenants\", %{tenants: tenants} do\n      assert hd(Api.list_tenants(search: hd(tenants).external_id)) == hd(tenants)\n\n      assert Api.list_tenants(order_by: \"max_concurrent_users\", order: \"desc\", limit: 2) ==\n               Enum.sort_by(tenants, & &1.max_concurrent_users, :desc) |> Enum.take(2)\n    end\n  end\n\n  describe \"get_tenant!/1\" do\n    setup [:create_tenants]\n\n    test \"returns the tenant with given id\", %{tenants: [tenant | _]} do\n      result = tenant.id |> Api.get_tenant!() |> Map.delete(:extensions)\n      expected = tenant |> Map.delete(:extensions)\n      assert result == expected\n    end\n  end\n\n  describe \"create_tenant/1\" do\n    test \"valid data creates a tenant\" do\n      port = Generators.port()\n\n      external_id = random_string()\n\n      expect(Realtime.Tenants.Cache, :global_cache_update, fn tenant ->\n        assert tenant.external_id == external_id\n      end)\n\n      valid_attrs = %{\n        external_id: external_id,\n        name: external_id,\n        extensions: [\n          %{\n            \"type\" => \"postgres_cdc_rls\",\n            \"settings\" => %{\n              \"db_host\" => @db_conf[:hostname],\n              \"db_name\" => @db_conf[:database],\n              \"db_user\" => @db_conf[:username],\n              \"db_password\" => @db_conf[:password],\n              \"db_port\" => \"#{port}\",\n              \"poll_interval\" => 100,\n              \"poll_max_changes\" => 100,\n              \"poll_max_record_bytes\" => 1_048_576,\n              \"region\" => \"us-east-1\"\n            }\n          }\n        ],\n        postgres_cdc_default: \"postgres_cdc_rls\",\n        jwt_secret: \"new secret\",\n        max_concurrent_users: 200,\n        max_events_per_second: 100\n      }\n\n      assert {:ok, %Tenant{} = tenant} = Api.create_tenant(valid_attrs)\n\n      assert tenant.external_id == external_id\n      assert tenant.jwt_secret == \"YIriPuuJO1uerq5hSZ1W5Q==\"\n      assert tenant.name == external_id\n      assert tenant.broadcast_adapter == :gen_rpc\n    end\n\n    test \"invalid data returns error changeset\" do\n      reject(&Realtime.Tenants.Cache.global_cache_update/1)\n      assert {:error, %Ecto.Changeset{}} = Api.create_tenant(%{external_id: nil, jwt_secret: nil, name: nil})\n    end\n  end\n\n  describe \"get_tenant_by_external_id/2\" do\n    setup [:create_tenants]\n\n    test \"fetch by external id\", %{tenants: [tenant | _]} do\n      %Tenant{extensions: [%ApiExtensions{} = extension]} =\n        Api.get_tenant_by_external_id(tenant.external_id)\n\n      assert Map.has_key?(extension.settings, \"db_password\")\n      password = extension.settings[\"db_password\"]\n      assert ^password = \"v1QVng3N+pZd/0AEObABwg==\"\n    end\n\n    test \"fetch by external id using replica\", %{tenants: [tenant | _]} do\n      %Tenant{extensions: [%ApiExtensions{} = extension]} =\n        Api.get_tenant_by_external_id(tenant.external_id, use_replica?: true)\n\n      assert Map.has_key?(extension.settings, \"db_password\")\n      password = extension.settings[\"db_password\"]\n      assert ^password = \"v1QVng3N+pZd/0AEObABwg==\"\n    end\n\n    test \"fetch by external id using no replica\", %{tenants: [tenant | _]} do\n      %Tenant{extensions: [%ApiExtensions{} = extension]} =\n        Api.get_tenant_by_external_id(tenant.external_id, use_replica?: false)\n\n      assert Map.has_key?(extension.settings, \"db_password\")\n      password = extension.settings[\"db_password\"]\n      assert ^password = \"v1QVng3N+pZd/0AEObABwg==\"\n    end\n  end\n\n  describe \"update_tenant_by_external_id/2\" do\n    setup [:create_tenants]\n\n    test \"valid data updates the tenant using external_id\", %{tenants: [tenant | _]} do\n      update_attrs = %{\n        external_id: tenant.external_id,\n        jwt_secret: \"some updated jwt_secret\",\n        name: \"some updated name\"\n      }\n\n      assert {:ok, %Tenant{} = tenant} = Api.update_tenant_by_external_id(tenant.external_id, update_attrs)\n      assert tenant.external_id == tenant.external_id\n\n      assert tenant.jwt_secret == Crypto.encrypt!(\"some updated jwt_secret\")\n      assert tenant.name == \"some updated name\"\n    end\n\n    test \"invalid data returns error changeset\", %{tenants: [tenant | _]} do\n      assert {:error, %Ecto.Changeset{}} =\n               Api.update_tenant_by_external_id(tenant.external_id, %{external_id: nil, jwt_secret: nil, name: nil})\n    end\n\n    test \"valid data and jwks change will send disconnect event\", %{tenants: [tenant | _]} do\n      :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, \"realtime:operations:\" <> tenant.external_id)\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{jwt_jwks: %{keys: [\"test\"]}})\n      assert_receive :disconnect, 500\n    end\n\n    test \"valid data and jwt_secret change will send disconnect event\", %{tenants: [tenant | _]} do\n      :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, \"realtime:operations:\" <> tenant.external_id)\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{jwt_secret: \"potato\"})\n      assert_receive :disconnect, 500\n    end\n\n    test \"valid data and suspend change will send disconnect event\", %{tenants: [tenant | _]} do\n      :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, \"realtime:operations:\" <> tenant.external_id)\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{suspend: true})\n      assert_receive :disconnect, 500\n    end\n\n    test \"valid data but not updating jwt_secret or jwt_jwks won't send event\", %{tenants: [tenant | _]} do\n      :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, \"realtime:operations:\" <> tenant.external_id)\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{max_events_per_second: 100})\n      refute_receive :disconnect, 500\n    end\n\n    test \"valid data and jwt_secret change will restart the database connection\", %{tenants: [tenant | _]} do\n      expect(Connect, :shutdown, fn external_id ->\n        assert external_id == tenant.external_id\n        :ok\n      end)\n\n      expect(PostgresCdcRls, :handle_stop, fn external_id, timeout ->\n        assert external_id == tenant.external_id\n        assert timeout == 5_000\n        :ok\n      end)\n\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{jwt_secret: \"potato\"})\n    end\n\n    test \"valid data and suspend change will restart the database connection\", %{tenants: [tenant | _]} do\n      expect(Connect, :shutdown, fn external_id ->\n        assert external_id == tenant.external_id\n        :ok\n      end)\n\n      expect(PostgresCdcRls, :handle_stop, fn external_id, timeout ->\n        assert external_id == tenant.external_id\n        assert timeout == 5_000\n        :ok\n      end)\n\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{suspend: true})\n    end\n\n    test \"valid data and tenant data change will not restart the database connection\", %{tenants: [tenant | _]} do\n      reject(&Connect.shutdown/1)\n      reject(&PostgresCdcRls.handle_stop/2)\n\n      expect(Realtime.Tenants.Cache, :global_cache_update, fn tenant ->\n        assert tenant.max_concurrent_users == 101\n      end)\n\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{max_concurrent_users: 101})\n    end\n\n    test \"valid data and extensions data change will restart the database connection\", %{tenants: [tenant | _]} do\n      extensions = [\n        %{\n          \"type\" => \"postgres_cdc_rls\",\n          \"settings\" => %{\n            \"db_host\" => \"127.0.0.1\",\n            \"db_name\" => \"postgres\",\n            \"db_user\" => \"supabase_admin\",\n            \"db_password\" => \"postgres\",\n            \"db_port\" => \"5432\",\n            \"poll_interval\" => 100,\n            \"poll_max_changes\" => 100,\n            \"poll_max_record_bytes\" => 1_048_576,\n            \"region\" => \"us-east-1\",\n            \"publication\" => \"supabase_realtime_test\",\n            \"ssl_enforced\" => false\n          }\n        }\n      ]\n\n      expect(Connect, :shutdown, fn external_id ->\n        assert external_id == tenant.external_id\n        :ok\n      end)\n\n      expect(PostgresCdcRls, :handle_stop, fn external_id, timeout ->\n        assert external_id == tenant.external_id\n        assert timeout == 5_000\n        :ok\n      end)\n\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{extensions: extensions})\n    end\n\n    test \"valid data and jwt_jwks change will restart the database connection\", %{tenants: [tenant | _]} do\n      expect(Connect, :shutdown, fn external_id ->\n        assert external_id == tenant.external_id\n        :ok\n      end)\n\n      expect(PostgresCdcRls, :handle_stop, fn external_id, timeout ->\n        assert external_id == tenant.external_id\n        assert timeout == 5_000\n        :ok\n      end)\n\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{jwt_jwks: %{keys: [\"test\"]}})\n    end\n\n    test \"valid data and jwt_secret change will restart DB connection even if handle_stop times out\", %{\n      tenants: [tenant | _]\n    } do\n      expect(Connect, :shutdown, fn external_id ->\n        assert external_id == tenant.external_id\n        :ok\n      end)\n\n      expect(PostgresCdcRls, :handle_stop, fn _external_id, _timeout ->\n        # Simulate timeout exit like DynamicSupervisor.stop/3 does\n        exit(:timeout)\n      end)\n\n      # Update should still succeed even if handle_stop times out\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{jwt_secret: \"potato\"})\n    end\n\n    test \"valid data and change to tenant data will refresh cache\", %{tenants: [tenant | _]} do\n      expect(Realtime.Tenants.Cache, :global_cache_update, fn tenant ->\n        assert tenant.name == \"new_name\"\n      end)\n\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{name: \"new_name\"})\n    end\n\n    test \"valid data and no changes to tenant will not refresh cache\", %{tenants: [tenant | _]} do\n      reject(&Realtime.Tenants.Cache.global_cache_update/1)\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{name: tenant.name})\n    end\n\n    test \"change to max_events_per_second publishes update to respective rate counters\", %{tenants: [tenant | _]} do\n      expect(RateCounter, :publish_update, fn key ->\n        assert key == Realtime.Tenants.events_per_second_key(tenant.external_id)\n      end)\n\n      expect(RateCounter, :publish_update, fn key ->\n        assert key == Realtime.Tenants.db_events_per_second_key(tenant.external_id)\n      end)\n\n      reject(&RateCounter.publish_update/1)\n\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{max_events_per_second: 123})\n    end\n\n    test \"change to max_joins_per_second publishes update to rate counters\", %{tenants: [tenant | _]} do\n      expect(RateCounter, :publish_update, fn key ->\n        assert key == Realtime.Tenants.joins_per_second_key(tenant.external_id)\n      end)\n\n      reject(&RateCounter.publish_update/1)\n\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{max_joins_per_second: 123})\n    end\n\n    test \"change to max_presence_events_per_second publishes update to rate counters\", %{tenants: [tenant | _]} do\n      expect(RateCounter, :publish_update, fn key ->\n        assert key == Realtime.Tenants.presence_events_per_second_key(tenant.external_id)\n      end)\n\n      reject(&RateCounter.publish_update/1)\n\n      assert {:ok, %Tenant{}} =\n               Api.update_tenant_by_external_id(tenant.external_id, %{max_presence_events_per_second: 123})\n    end\n\n    test \"change to extensions publishes update to rate counters\", %{tenants: [tenant | _]} do\n      extensions = [\n        %{\n          \"type\" => \"postgres_cdc_rls\",\n          \"settings\" => %{\n            \"db_host\" => \"127.0.0.1\",\n            \"db_name\" => \"postgres\",\n            \"db_user\" => \"supabase_admin\",\n            \"db_password\" => \"postgres\",\n            \"db_port\" => \"1234\",\n            \"poll_interval\" => 100,\n            \"poll_max_changes\" => 100,\n            \"poll_max_record_bytes\" => 1_048_576,\n            \"region\" => \"us-east-1\",\n            \"publication\" => \"supabase_realtime_test\",\n            \"ssl_enforced\" => false\n          }\n        }\n      ]\n\n      expect(RateCounter, :publish_update, fn key ->\n        assert key == Realtime.Tenants.connect_errors_per_second_key(tenant.external_id)\n      end)\n\n      expect(RateCounter, :publish_update, fn key ->\n        assert key == Realtime.Tenants.subscription_errors_per_second_key(tenant.external_id)\n      end)\n\n      expect(RateCounter, :publish_update, fn key ->\n        assert key == Realtime.Tenants.authorization_errors_per_second_key(tenant.external_id)\n      end)\n\n      reject(&RateCounter.publish_update/1)\n\n      assert {:ok, %Tenant{}} = Api.update_tenant_by_external_id(tenant.external_id, %{extensions: extensions})\n    end\n  end\n\n  describe \"delete_tenant_by_external_id/1\" do\n    test \"deletes the tenant\" do\n      tenant = tenant_fixture()\n      assert true == Api.delete_tenant_by_external_id(tenant.external_id)\n      assert false == Api.delete_tenant_by_external_id(\"undef_tenant\")\n      assert_raise Ecto.NoResultsError, fn -> Api.get_tenant!(tenant.id) end\n    end\n  end\n\n  describe \"preload_counters/1\" do\n    setup [:create_tenants]\n\n    test \"preloads counters for a given tenant \", %{tenants: [tenant | _]} do\n      tenant = Repo.reload!(tenant)\n      assert Api.preload_counters(nil) == nil\n\n      expect(GenCounter, :get, fn _ -> 1 end)\n      expect(RateCounter, :get, fn _ -> {:ok, %RateCounter{avg: 2}} end)\n      counters = Api.preload_counters(tenant)\n      assert counters.events_per_second_rolling == 2\n      assert counters.events_per_second_now == 1\n\n      assert Api.preload_counters(nil, :any) == nil\n    end\n  end\n\n  describe \"rename_settings_field/2\" do\n    @tag skip: \"** (Postgrex.Error) ERROR 0A000 (feature_not_supported) cached plan must not change result type\"\n    test \"renames setting fields\" do\n      tenant = tenant_fixture()\n      Api.rename_settings_field(\"poll_interval_ms\", \"poll_interval\")\n      assert %{extensions: [%{settings: %{\"poll_interval\" => _}}]} = tenant\n    end\n  end\n\n  describe \"requires_disconnect/1\" do\n    defmodule TestRequiresDisconnect do\n      import Api\n\n      def check(changeset) when requires_disconnect(changeset), do: true\n      def check(_changeset), do: false\n    end\n\n    test \"returns true if jwt_secret is changed\" do\n      changeset = %Ecto.Changeset{valid?: true, changes: %{jwt_secret: \"new_secret\"}}\n      assert TestRequiresDisconnect.check(changeset)\n    end\n\n    test \"returns true if jwt_jwks is changed\" do\n      changeset = %Ecto.Changeset{valid?: true, changes: %{jwt_jwks: %{keys: [\"test\"]}}}\n      assert TestRequiresDisconnect.check(changeset)\n    end\n\n    test \"returns true if private_only is changed\" do\n      changeset = %Ecto.Changeset{valid?: true, changes: %{private_only: true}}\n      assert TestRequiresDisconnect.check(changeset)\n    end\n\n    test \"returns true if suspend is changed\" do\n      changeset = %Ecto.Changeset{valid?: true, changes: %{suspend: true}}\n      assert TestRequiresDisconnect.check(changeset)\n    end\n\n    test \"returns false if valid? is false\" do\n      changeset = %Ecto.Changeset{valid?: false, changes: %{jwt_secret: \"new_secret\"}}\n      refute TestRequiresDisconnect.check(changeset)\n    end\n  end\n\n  describe \"requires_restarting_db_connection/1\" do\n    defmodule TestRequiresRestartingDbConnection do\n      import Api\n\n      def check(changeset) when requires_restarting_db_connection(changeset), do: true\n      def check(_changeset), do: false\n    end\n\n    test \"returns true if extensions is changed\" do\n      changeset = %Ecto.Changeset{valid?: true, changes: %{extensions: []}}\n      assert TestRequiresRestartingDbConnection.check(changeset)\n    end\n\n    test \"returns true if jwt_secret are changed\" do\n      changeset = %Ecto.Changeset{valid?: true, changes: %{jwt_secret: \"new_secret\"}}\n      assert TestRequiresRestartingDbConnection.check(changeset)\n    end\n\n    test \"returns true if jwt_jwks are changed\" do\n      changeset = %Ecto.Changeset{valid?: true, changes: %{jwt_jwks: %{keys: [\"test\"]}}}\n      assert TestRequiresRestartingDbConnection.check(changeset)\n    end\n\n    test \"returns true if suspend is changed\" do\n      changeset = %Ecto.Changeset{valid?: true, changes: %{suspend: true}}\n      assert TestRequiresRestartingDbConnection.check(changeset)\n    end\n\n    test \"returns true if multiple relevant fields are changed\" do\n      changeset = %Ecto.Changeset{valid?: true, changes: %{jwt_secret: \"new_secret\", jwt_jwks: %{keys: [\"test\"]}}}\n      assert TestRequiresRestartingDbConnection.check(changeset)\n    end\n\n    test \"returns false if no relevant fields are changed\" do\n      changeset = %Ecto.Changeset{valid?: true, changes: %{postgres_cdc_default: \"potato\"}}\n      refute TestRequiresRestartingDbConnection.check(changeset)\n    end\n\n    test \"returns false if valid? is false\" do\n      changeset = %Ecto.Changeset{valid?: false, changes: %{jwt_secret: \"new_secret\"}}\n      refute TestRequiresRestartingDbConnection.check(changeset)\n    end\n  end\n\n  describe \"update_migrations_ran/1\" do\n    test \"updates migrations_ran to the count of all migrations\" do\n      tenant = tenant_fixture(%{migrations_ran: 0})\n\n      expect(Realtime.Tenants.Cache, :global_cache_update, fn tenant ->\n        assert tenant.migrations_ran == 1\n        :ok\n      end)\n\n      assert {:ok, tenant} = Api.update_migrations_ran(tenant.external_id, 1)\n      assert tenant.migrations_ran == 1\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/database_distributed_test.exs",
    "content": "defmodule Realtime.DatabaseDistributedTest do\n  # async: false due to usage of Clustered\n  use Realtime.DataCase, async: false\n\n  import ExUnit.CaptureLog\n\n  alias Realtime.Database\n  alias Realtime.Rpc\n  alias Realtime.Tenants.Connect\n\n  doctest Realtime.Database\n  def handle_telemetry(event, metadata, content, pid: pid), do: send(pid, {event, metadata, content})\n\n  setup do\n    tenant = Containers.checkout_tenant()\n    :telemetry.attach(__MODULE__, [:realtime, :database, :transaction], &__MODULE__.handle_telemetry/4, pid: self())\n\n    on_exit(fn -> :telemetry.detach(__MODULE__) end)\n\n    %{tenant: tenant}\n  end\n\n  @aux_mod (quote do\n              defmodule DatabaseAux do\n                def checker(transaction_conn) do\n                  Postgrex.query!(transaction_conn, \"SELECT 1\", [])\n                end\n\n                def error(transaction_conn) do\n                  Postgrex.query!(transaction_conn, \"SELECT 1/0\", [])\n                end\n\n                def exception(_) do\n                  raise RuntimeError, \"💣\"\n                end\n              end\n            end)\n\n  Code.eval_quoted(@aux_mod)\n\n  describe \"transaction/1 in clustered mode\" do\n    setup do\n      tenant = Containers.checkout_tenant_unboxed(run_migrations: true)\n      %{distributed_tenant: tenant}\n    end\n\n    test \"success call returns output\", %{distributed_tenant: tenant} do\n      {:ok, node} = Clustered.start(@aux_mod)\n      {:ok, db_conn} = Rpc.call(node, Connect, :connect, [tenant.external_id, \"us-east-1\"])\n      assert node(db_conn) == node\n      assert {:ok, %Postgrex.Result{rows: [[1]]}} = Database.transaction(db_conn, &DatabaseAux.checker/1)\n    end\n\n    test \"handles database errors\", %{distributed_tenant: tenant} do\n      metadata = [external_id: \"123\", project: \"123\"]\n      {:ok, node} = Clustered.start(@aux_mod)\n      {:ok, db_conn} = Rpc.call(node, Connect, :connect, [tenant.external_id, \"us-east-1\"])\n      assert node(db_conn) == node\n\n      assert capture_log(fn ->\n               assert {:error, %Postgrex.Error{}} = Database.transaction(db_conn, &DatabaseAux.error/1, [], metadata)\n               # We have to wait for logs to be relayed to this node\n               Process.sleep(100)\n             end) =~ \"project=123 external_id=123 [error] ErrorExecutingTransaction:\"\n    end\n\n    test \"handles exception\", %{distributed_tenant: tenant} do\n      metadata = [external_id: \"123\", project: \"123\"]\n      {:ok, node} = Clustered.start(@aux_mod)\n      {:ok, db_conn} = Rpc.call(node, Connect, :connect, [tenant.external_id, \"us-east-1\"])\n      assert node(db_conn) == node\n\n      assert capture_log(fn ->\n               assert {:error, %RuntimeError{}} = Database.transaction(db_conn, &DatabaseAux.exception/1, [], metadata)\n               # We have to wait for logs to be relayed to this node\n               Process.sleep(100)\n             end) =~ \"project=123 external_id=123 [error] ErrorExecutingTransaction:\"\n    end\n\n    test \"db process is not alive anymore\" do\n      metadata = [external_id: \"123\", project: \"123\", tenant_id: \"123\"]\n      {:ok, node} = Clustered.start(@aux_mod)\n\n      pid = Rpc.call(node, :erlang, :self, [])\n      assert node(pid) == node\n\n      assert capture_log(fn ->\n               assert {:error, {:exit, {:noproc, {DBConnection.Holder, :checkout, [^pid, []]}}}} =\n                        Database.transaction(pid, &DatabaseAux.checker/1, [], metadata)\n\n               # We have to wait for logs to be relayed to this node\n               Process.sleep(100)\n             end) =~ \"project=123 external_id=123 [error] ErrorExecutingTransaction:\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/database_test.exs",
    "content": "defmodule Realtime.DatabaseTest do\n  use Realtime.DataCase, async: true\n\n  import ExUnit.CaptureLog\n\n  alias Realtime.Database\n\n  doctest Realtime.Database\n  def handle_telemetry(event, metadata, content, pid: pid), do: send(pid, {event, metadata, content})\n\n  setup do\n    tenant = Containers.checkout_tenant()\n    :telemetry.attach(__MODULE__, [:realtime, :database, :transaction], &__MODULE__.handle_telemetry/4, pid: self())\n\n    on_exit(fn -> :telemetry.detach(__MODULE__) end)\n\n    %{tenant: tenant}\n  end\n\n  describe \"check_tenant_connection/1\" do\n    setup context do\n      extension = %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"db_host\" => \"127.0.0.1\",\n          \"db_name\" => \"postgres\",\n          \"db_user\" => \"supabase_admin\",\n          \"db_password\" => \"postgres\",\n          \"region\" => \"us-east-1\",\n          \"ssl_enforced\" => false,\n          \"db_pool\" => Map.get(context, :db_pool),\n          \"subcriber_pool_size\" => Map.get(context, :subcriber_pool_size),\n          \"subs_pool_size\" => Map.get(context, :subs_pool_size)\n        }\n      }\n\n      {:ok, tenant} = update_extension(context.tenant, extension)\n\n      %{tenant: tenant}\n    end\n\n    test \"returns error when tenant is nil\" do\n      assert {:error, :tenant_not_found} = Database.check_tenant_connection(nil)\n    end\n\n    test \"connects to a tenant database\", %{tenant: tenant} do\n      assert {:ok, _conn, migrations_ran} = Database.check_tenant_connection(tenant)\n      assert is_integer(migrations_ran)\n      assert migrations_ran >= 0\n    end\n\n    test \"returns 0 migrations when realtime.schema_migrations does not exist\", %{tenant: tenant} do\n      # by default new containers do not have the schema_migrations table\n      assert {:ok, _conn, 0} = Database.check_tenant_connection(tenant)\n    end\n\n    test \"returns migration count when realtime.schema_migrations exists\", %{tenant: tenant} do\n      {:ok, conn} = Database.connect(tenant, \"realtime_test\", :stop)\n\n      Postgrex.query!(conn, \"CREATE TABLE IF NOT EXISTS realtime.schema_migrations (version bigint PRIMARY KEY)\", [])\n      Postgrex.query!(conn, \"INSERT INTO realtime.schema_migrations VALUES (1), (2), (3)\", [])\n\n      assert {:ok, check_conn, 3} = Database.check_tenant_connection(tenant)\n      GenServer.stop(check_conn)\n      GenServer.stop(conn)\n    end\n\n    # Connection limit for docker tenant db is 100\n    @tag db_pool: 50,\n         subs_pool_size: 73\n    test \"restricts connection if tenant database cannot receive more connections based on tenant pool\",\n         %{tenant: tenant} do\n      assert capture_log(fn ->\n               assert {:error, :tenant_db_too_many_connections} = Database.check_tenant_connection(tenant)\n             end) =~ ~r/Only \\d+ available connections\\. At least 126 connections are required/\n    end\n  end\n\n  describe \"replication_slot_teardown/1\" do\n    test \"removes replication slots with the realtime prefix\", %{tenant: tenant} do\n      {:ok, conn} = Database.connect(tenant, \"realtime_test\", :stop)\n\n      Postgrex.query!(\n        conn,\n        \"SELECT * FROM pg_create_logical_replication_slot('realtime_test_slot', 'pgoutput')\",\n        []\n      )\n\n      Database.replication_slot_teardown(tenant)\n      assert %{rows: []} = Postgrex.query!(conn, \"SELECT slot_name FROM pg_replication_slots\", [])\n    end\n  end\n\n  describe \"replication_slot_teardown/2\" do\n    test \"removes replication slots with a given name and existing connection\", %{tenant: tenant} do\n      name = String.downcase(\"slot_#{random_string()}\")\n      {:ok, conn} = Database.connect(tenant, \"realtime_test\", :stop)\n\n      Postgrex.query!(\n        conn,\n        \"SELECT * FROM pg_create_logical_replication_slot('#{name}', 'pgoutput')\",\n        []\n      )\n\n      Database.replication_slot_teardown(conn, name)\n      Process.sleep(1000)\n      assert %{rows: []} = Postgrex.query!(conn, \"SELECT slot_name FROM pg_replication_slots\", [])\n    end\n\n    test \"removes replication slots with a given name and a tenant\", %{tenant: tenant} do\n      name = String.downcase(\"slot_#{random_string()}\")\n      {:ok, conn} = Database.connect(tenant, \"realtime_test\", :stop)\n\n      Postgrex.query!(\n        conn,\n        \"SELECT * FROM pg_create_logical_replication_slot('#{name}', 'pgoutput')\",\n        []\n      )\n\n      Database.replication_slot_teardown(tenant, name)\n      assert %{rows: []} = Postgrex.query!(conn, \"SELECT slot_name FROM pg_replication_slots\", [])\n    end\n  end\n\n  describe \"transaction/1\" do\n    setup context do\n      extension = %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"db_host\" => \"127.0.0.1\",\n          \"db_name\" => \"postgres\",\n          \"db_user\" => \"supabase_admin\",\n          \"db_password\" => \"postgres\",\n          \"region\" => \"us-east-1\",\n          \"ssl_enforced\" => false,\n          \"db_pool\" => Map.get(context, :db_pool)\n        }\n      }\n\n      {:ok, tenant} = update_extension(context.tenant, extension)\n      {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n\n      %{db_conn: db_conn}\n    end\n\n    test \"handles transaction errors\", %{db_conn: db_conn} do\n      assert {:error, %DBConnection.ConnectionError{reason: :error}} =\n               Database.transaction(db_conn, fn conn ->\n                 Postgrex.query!(conn, \"select pg_terminate_backend(pg_backend_pid())\", [])\n               end)\n    end\n\n    @tag db_pool: 1\n    test \"on checkout error, handles raised exception as an error\", %{db_conn: db_conn} do\n      for _ <- 1..5 do\n        Task.start(fn ->\n          Database.transaction(\n            db_conn,\n            fn conn -> Postgrex.query!(conn, \"SELECT pg_sleep(20)\", []) end,\n            timeout: 20000\n          )\n        end)\n      end\n\n      Process.sleep(100)\n\n      log =\n        capture_log(fn ->\n          assert {:error, %DBConnection.ConnectionError{reason: :queue_timeout}} =\n                   Task.async(fn ->\n                     Database.transaction(\n                       db_conn,\n                       fn conn -> Postgrex.query!(conn, \"SELECT pg_sleep(11)\", []) end,\n                       [timeout: 15000],\n                       external_id: \"123\",\n                       project: \"123\"\n                     )\n                   end)\n                   |> Task.await(20000)\n        end)\n\n      assert log =~ \"project=123 external_id=123 [error] ErrorExecutingTransaction\"\n    end\n\n    test \"handles exit signals in transactions\", %{db_conn: db_conn} do\n      assert capture_log(fn ->\n               assert {:error, {:exit, _}} =\n                        Database.transaction(db_conn, fn _conn -> exit(:test_exit) end)\n             end) =~ \"ErrorExecutingTransaction\"\n    end\n\n    test \"run call using RPC\", %{db_conn: db_conn} do\n      assert {:ok, %{rows: [[1]]}} =\n               Realtime.Rpc.enhanced_call(\n                 node(db_conn),\n                 Database,\n                 :transaction,\n                 [\n                   db_conn,\n                   fn db_conn -> Postgrex.query!(db_conn, \"SELECT 1\", []) end,\n                   [backoff: :stop],\n                   [tenant_id: \"test\"]\n                 ]\n               )\n    end\n\n    test \"handles RPC error\", %{db_conn: db_conn} do\n      assert {:error, :rpc_error, :noconnection} =\n               Realtime.Rpc.enhanced_call(\n                 :potato@nohost,\n                 Database,\n                 :transaction,\n                 [\n                   db_conn,\n                   fn db_conn -> Postgrex.query!(db_conn, \"SELECT 1\", []) end,\n                   [backoff: :stop],\n                   [tenant_id: \"test\"]\n                 ]\n               )\n    end\n\n    test \"with telemetry event defined, emits telemetry event\", %{db_conn: db_conn} do\n      event = [:realtime, :database, :transaction]\n      opts = [telemetry: event]\n\n      Database.transaction(db_conn, fn conn -> Postgrex.query!(conn, \"SELECT pg_sleep(0.1)\", []) end, opts)\n      assert_receive {^event, %{latency: latency}, %{tenant: nil}}\n      assert latency > 100\n    end\n\n    test \"with telemetry event defined, emits telemetry event with tenant_id\", %{db_conn: db_conn} do\n      event = [:realtime, :database, :transaction]\n      tenant_id = random_string()\n      opts = [telemetry: event, tenant_id: tenant_id]\n\n      Database.transaction(db_conn, fn conn -> Postgrex.query!(conn, \"SELECT pg_sleep(0.1)\", []) end, opts)\n\n      assert_receive {^event, %{latency: latency}, %{tenant: ^tenant_id}}\n      assert latency > 100\n    end\n  end\n\n  describe \"pool_size_by_application_name/2\" do\n    test \"returns the number of connections per application name\" do\n      assert Database.pool_size_by_application_name(\"realtime_connect\", %{}) == 1\n      assert Database.pool_size_by_application_name(\"realtime_connect\", %{\"db_pool\" => 10}) == 10\n      assert Database.pool_size_by_application_name(\"realtime_potato\", %{}) == 1\n      assert Database.pool_size_by_application_name(\"realtime_rls\", %{\"db_pool\" => 10}) == 1\n\n      assert Database.pool_size_by_application_name(\"realtime_rls\", %{\"subs_pool_size\" => 10}) ==\n               1\n\n      assert Database.pool_size_by_application_name(\"realtime_rls\", %{\"subcriber_pool_size\" => 10}) ==\n               1\n\n      assert Database.pool_size_by_application_name(\"realtime_broadcast_changes\", %{\n               \"db_pool\" => 10\n             }) == 1\n\n      assert Database.pool_size_by_application_name(\"realtime_broadcast_changes\", %{\n               \"subs_pool_size\" => 10\n             }) == 1\n\n      assert Database.pool_size_by_application_name(\"realtime_broadcast_changes\", %{\n               \"subcriber_pool_size\" => 10\n             }) == 1\n\n      assert Database.pool_size_by_application_name(\"realtime_migrations\", %{\n               \"db_pool\" => 10\n             }) == 2\n\n      assert Database.pool_size_by_application_name(\"realtime_migrations\", %{\n               \"subs_pool_size\" => 10\n             }) == 2\n\n      assert Database.pool_size_by_application_name(\"realtime_migrations\", %{\n               \"subcriber_pool_size\" => 10\n             }) == 2\n    end\n  end\n\n  describe \"get_external_id/1\" do\n    test \"returns the external id for a given hostname\" do\n      assert Realtime.Database.get_external_id(\"tenant.realtime.supabase.co\") == {:ok, \"tenant\"}\n      assert Realtime.Database.get_external_id(\"tenant.supabase.co\") == {:ok, \"tenant\"}\n      assert Realtime.Database.get_external_id(\"localhost\") == {:ok, \"localhost\"}\n    end\n  end\n\n  describe \"detect_ip_version/1\" do\n    test \"detects appropriate IP version\" do\n      # Using ipv4.google.com\n      log =\n        capture_log(fn ->\n          assert Realtime.Database.detect_ip_version(\"ipv4.google.com\") == {:ok, :inet}\n        end)\n\n      assert log =~ \"IpV4Detected\"\n      assert log =~ ~r/resolved to \\d+\\.\\d+\\.\\d+\\.\\d+/\n\n      # Using ipv6.google.com\n      log =\n        capture_log(fn ->\n          assert Realtime.Database.detect_ip_version(\"ipv6.google.com\") == {:ok, :inet6}\n        end)\n\n      refute log =~ \"IpV4Detected\"\n\n      # Using 2001:0db8:85a3:0000:0000:8a2e:0370:7334\n      log =\n        capture_log(fn ->\n          assert Realtime.Database.detect_ip_version(\"2001:0db8:85a3:0000:0000:8a2e:0370:7334\") ==\n                   {:ok, :inet6}\n        end)\n\n      refute log =~ \"IpV4Detected\"\n\n      # Using 127.0.0.1\n      log =\n        capture_log(fn ->\n          assert Realtime.Database.detect_ip_version(\"127.0.0.1\") == {:ok, :inet}\n        end)\n\n      assert log =~ \"IpV4Detected\"\n      assert log =~ ~r/resolved to \\d+\\.\\d+\\.\\d+\\.\\d+/\n\n      # Using invalid domain\n      #       # Using 127.0.0.1\n      log =\n        capture_log(fn ->\n          assert Realtime.Database.detect_ip_version(\"potato\") == {:error, :nxdomain}\n        end)\n\n      refute log =~ \"IpV4Detected\"\n    end\n  end\n\n  describe \"from_tenant/3\" do\n    test \"uses default backoff when not provided\", %{tenant: tenant} do\n      settings = Database.from_tenant(tenant, \"realtime_test\")\n      assert settings.backoff_type == :rand_exp\n    end\n  end\n\n  describe \"from_settings/3\" do\n    test \"uses default backoff when not provided\", %{tenant: tenant} do\n      settings = Realtime.PostgresCdc.filter_settings(\"postgres_cdc_rls\", tenant.extensions)\n      result = Database.from_settings(settings, \"realtime_connect\")\n      assert result.backoff_type == :rand_exp\n    end\n\n    test \"returns struct with correct setup\", %{tenant: tenant} do\n      application_name = \"realtime_connect\"\n      backoff = :stop\n      {:ok, ip_version} = Database.detect_ip_version(\"127.0.0.1\")\n      socket_options = [ip_version]\n      settings = Realtime.PostgresCdc.filter_settings(\"postgres_cdc_rls\", tenant.extensions)\n      settings = Database.from_settings(settings, application_name, backoff)\n      port = settings.port\n\n      assert %Realtime.Database{\n               socket_options: ^socket_options,\n               application_name: ^application_name,\n               backoff_type: ^backoff,\n               hostname: \"127.0.0.1\",\n               port: ^port,\n               database: \"postgres\",\n               username: \"supabase_admin\",\n               password: \"postgres\",\n               pool_size: 1,\n               queue_target: 5000,\n               max_restarts: nil,\n               ssl: false\n             } = settings\n    end\n\n    test \"defaults ssl to true when ssl_enforced is not set\" do\n      assert Database.default_ssl_param(%{}) == true\n      assert Database.default_ssl_param(%{\"other\" => \"value\"}) == true\n    end\n\n    test \"handles SSL properties\", %{tenant: tenant} do\n      application_name = \"realtime_connect\"\n      backoff = :stop\n\n      settings = Realtime.PostgresCdc.filter_settings(\"postgres_cdc_rls\", tenant.extensions)\n      settings = Map.put(settings, \"ssl_enforced\", true)\n      settings = Database.from_settings(settings, application_name, backoff)\n      assert settings.ssl == [verify: :verify_none]\n\n      settings = Realtime.PostgresCdc.filter_settings(\"postgres_cdc_rls\", tenant.extensions)\n      settings = Map.put(settings, \"ssl_enforced\", false)\n      settings = Database.from_settings(settings, application_name, backoff)\n      assert settings.ssl == false\n    end\n  end\n\n  defp update_extension(tenant, extension) do\n    db_port = Realtime.Crypto.decrypt!(hd(tenant.extensions).settings[\"db_port\"])\n\n    extensions = [\n      put_in(extension, [\"settings\", \"db_port\"], db_port)\n    ]\n\n    Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{extensions: extensions})\n  end\nend\n"
  },
  {
    "path": "test/realtime/extensions/cdc_rls/cdc_rls_test.exs",
    "content": "defmodule Realtime.Extensions.CdcRlsTest do\n  # async: false due to global mimic mock\n  use Realtime.DataCase, async: false\n  use Mimic\n\n  import ExUnit.CaptureLog\n\n  setup :set_mimic_global\n\n  alias Extensions.PostgresCdcRls\n  alias Extensions.PostgresCdcRls.Subscriptions\n  alias PostgresCdcRls.SubscriptionManager\n  alias Postgrex\n  alias Realtime.Api.Tenant\n  alias Realtime.Database\n  alias Realtime.PostgresCdc\n  alias Realtime.RateCounter\n  alias Realtime.Tenants.Rebalancer\n\n  @cdc_module Extensions.PostgresCdcRls\n\n  describe \"Postgres extensions\" do\n    setup do\n      tenant = Containers.checkout_tenant(run_migrations: true)\n      {:ok, conn} = Database.connect(tenant, \"realtime_test\", :stop)\n      Integrations.setup_postgres_changes(conn)\n      GenServer.stop(conn)\n\n      %Tenant{extensions: extensions, external_id: external_id} = tenant\n      postgres_extension = PostgresCdc.filter_settings(\"postgres_cdc_rls\", extensions)\n      args = %{\"id\" => external_id, \"region\" => postgres_extension[\"region\"]}\n\n      pg_change_params = pubsub_subscribe(external_id)\n\n      RealtimeWeb.Endpoint.subscribe(Realtime.Syn.PostgresCdc.syn_topic(tenant.external_id))\n      # First time it will return nil\n      PostgresCdcRls.handle_connect(args)\n      # Wait for it to start\n      assert_receive %{event: \"ready\"}, 1000\n\n      on_exit(fn -> PostgresCdcRls.handle_stop(external_id, 10_000) end)\n      {:ok, response} = PostgresCdcRls.handle_connect(args)\n\n      # Now subscribe to the Postgres Changes\n      {:ok, _} = PostgresCdcRls.handle_after_connect(response, postgres_extension, pg_change_params, external_id)\n\n      RealtimeWeb.Endpoint.unsubscribe(Realtime.Syn.PostgresCdc.syn_topic(tenant.external_id))\n      %{tenant: tenant}\n    end\n\n    test \"supervisor crash must not respawn\", %{tenant: tenant} do\n      scope = Realtime.Syn.PostgresCdc.scope(tenant.external_id)\n\n      sup =\n        Enum.reduce_while(1..30, nil, fn _, acc ->\n          scope\n          |> :syn.lookup(tenant.external_id)\n          |> case do\n            :undefined ->\n              Process.sleep(500)\n              {:cont, acc}\n\n            {pid, _} when is_pid(pid) ->\n              {:halt, pid}\n          end\n        end)\n\n      assert Process.alive?(sup)\n      Process.monitor(sup)\n\n      RealtimeWeb.Endpoint.subscribe(Realtime.Syn.PostgresCdc.syn_topic(tenant.external_id))\n\n      Process.exit(sup, :kill)\n      scope_down = Atom.to_string(scope) <> \"_down\"\n\n      assert_receive {:DOWN, _, :process, ^sup, _reason}, 5000\n      assert_receive %{event: ^scope_down}\n      refute_receive %{event: \"ready\"}, 1000\n\n      :undefined = :syn.lookup(Realtime.Syn.PostgresCdc.scope(tenant.external_id), tenant.external_id)\n    end\n\n    test \"Subscription manager updates oids\", %{tenant: tenant} do\n      {subscriber_manager_pid, conn} =\n        Enum.reduce_while(1..25, nil, fn _, acc ->\n          case PostgresCdcRls.get_manager_conn(tenant.external_id) do\n            {:error, :wait} ->\n              Process.sleep(200)\n              {:cont, acc}\n\n            {:ok, pid, conn} ->\n              {:halt, {pid, conn}}\n          end\n        end)\n\n      %SubscriptionManager.State{oids: oids} = :sys.get_state(subscriber_manager_pid)\n\n      Postgrex.query!(conn, \"drop publication if exists supabase_realtime_test\", [])\n      send(subscriber_manager_pid, :check_oids)\n      %{oids: oids2} = :sys.get_state(subscriber_manager_pid)\n      assert !Map.equal?(oids, oids2)\n\n      Postgrex.query!(conn, \"create publication supabase_realtime_test for all tables\", [])\n      send(subscriber_manager_pid, :check_oids)\n      %{oids: oids3} = :sys.get_state(subscriber_manager_pid)\n      assert !Map.equal?(oids2, oids3)\n    end\n\n    test \"Stop tenant supervisor\", %{tenant: tenant} do\n      sup =\n        Enum.reduce_while(1..10, nil, fn _, acc ->\n          tenant.external_id\n          |> Realtime.Syn.PostgresCdc.scope()\n          |> :syn.lookup(tenant.external_id)\n          |> case do\n            :undefined ->\n              Process.sleep(500)\n              {:cont, acc}\n\n            {pid, _} ->\n              {:halt, pid}\n          end\n        end)\n\n      assert Process.alive?(sup)\n      PostgresCdc.stop(@cdc_module, tenant)\n      assert Process.alive?(sup) == false\n    end\n  end\n\n  describe \"handle_after_connect/4\" do\n    setup do\n      tenant = Containers.checkout_tenant(run_migrations: true)\n      %{tenant: tenant}\n    end\n\n    test \"rate counter raises exception returns error\", %{tenant: tenant} do\n      %Tenant{extensions: extensions, external_id: external_id} = tenant\n      postgres_extension = PostgresCdc.filter_settings(\"postgres_cdc_rls\", extensions)\n\n      stub(RateCounter, :get, fn _args -> raise \"unexpected RateCounter failure\" end)\n\n      import ExUnit.CaptureLog\n\n      log =\n        capture_log(fn ->\n          assert {:error, \"Too many database timeouts\"} =\n                   PostgresCdcRls.handle_after_connect({:manager_pid, self()}, postgres_extension, %{}, external_id)\n        end)\n\n      assert log =~ \"RateCounterError\"\n    end\n\n    test \"subscription error rate limit\", %{tenant: tenant} do\n      %Tenant{extensions: extensions, external_id: external_id} = tenant\n      postgres_extension = PostgresCdc.filter_settings(\"postgres_cdc_rls\", extensions)\n\n      stub(Subscriptions, :create, fn _conn, _publication, _subscription_list, _manager, _caller ->\n        {:error, %DBConnection.ConnectionError{}}\n      end)\n\n      # Now try to subscribe to the Postgres Changes\n      for _x <- 1..6 do\n        assert {:error, \"Too many database timeouts\"} =\n                 PostgresCdcRls.handle_after_connect({:manager_pid, self()}, postgres_extension, %{}, external_id)\n      end\n\n      rate = Realtime.Tenants.subscription_errors_per_second_rate(external_id, 4)\n\n      assert {:ok, %RateCounter{id: {:channel, :subscription_errors, ^external_id}, sum: 6, limit: %{triggered: true}}} =\n               RateCounterHelper.tick!(rate)\n\n      # It won't even be called now\n      reject(&Subscriptions.create/5)\n\n      assert {:error, \"Too many database timeouts\"} =\n               PostgresCdcRls.handle_after_connect({:manager_pid, self()}, postgres_extension, %{}, external_id)\n    end\n  end\n\n  describe \"Region rebalancing\" do\n    setup do\n      tenant = Containers.checkout_tenant(run_migrations: true)\n      %Tenant{extensions: extensions, external_id: external_id} = tenant\n      postgres_extension = PostgresCdc.filter_settings(\"postgres_cdc_rls\", extensions)\n\n      args = %{\"id\" => external_id, \"region\" => postgres_extension[\"region\"], check_region_interval: 100}\n\n      %{tenant_id: tenant.external_id, args: args}\n    end\n\n    test \"rebalancing needed process stops\", %{tenant_id: tenant_id, args: args} do\n      log =\n        capture_log(fn ->\n          expect(Rebalancer, :check, fn _, _, ^tenant_id -> {:error, :wrong_region} end)\n\n          {:ok, pid} = PostgresCdcRls.start(args)\n          ref = Process.monitor(pid)\n\n          assert_receive {:DOWN, ^ref, :process, ^pid, _reason}, 3000\n        end)\n\n      assert log =~ \"Rebalancing Postgres Changes replication for a closer region\"\n    end\n\n    test \"rebalancing not needed process stays up\", %{tenant_id: tenant_id, args: args} do\n      stub(Rebalancer, :check, fn _, _, ^tenant_id -> :ok end)\n\n      {:ok, pid} = PostgresCdcRls.start(args)\n      ref = Process.monitor(pid)\n\n      refute_receive {:DOWN, ^ref, :process, ^pid, _reason}, 1000\n    end\n  end\n\n  describe \"integration\" do\n    setup [:integration]\n\n    test \"subscribe inserts only\", %{tenant: tenant, conn: conn} do\n      on_exit(fn -> PostgresCdcRls.handle_stop(tenant.external_id, 10_000) end)\n\n      %Tenant{extensions: extensions, external_id: external_id} = tenant\n      postgres_extension = PostgresCdc.filter_settings(\"postgres_cdc_rls\", extensions)\n      args = %{\"id\" => external_id, \"region\" => postgres_extension[\"region\"]}\n\n      pg_change_params = pubsub_subscribe(external_id, \"INSERT\")\n\n      # First time it will return nil\n      PostgresCdcRls.handle_connect(args)\n      # Wait for it to start\n      assert_receive %{event: \"ready\"}, 3000\n      {:ok, response} = PostgresCdcRls.handle_connect(args)\n\n      assert_receive {\n        :telemetry,\n        [:realtime, :rpc],\n        %{latency: _},\n        %{\n          mechanism: :gen_rpc,\n          success: true\n        }\n      }\n\n      # Now subscribe to the Postgres Changes\n      Postgrex.query!(conn, \"delete from realtime.subscription\", [])\n      {:ok, _} = PostgresCdcRls.handle_after_connect(response, postgres_extension, pg_change_params, external_id)\n\n      assert %Postgrex.Result{num_rows: n} = Postgrex.query!(conn, \"select id from realtime.subscription\", [])\n      assert n >= 1\n\n      Process.sleep(500)\n\n      # Insert a record\n      %{rows: [[id]]} = Postgrex.query!(conn, \"insert into test (details) values ('test') returning id\", [])\n      # Delete the record\n      %{num_rows: 1} = Postgrex.query!(conn, \"delete from test\", [])\n\n      assert_receive {:socket_push, :text, data}, 5000\n      # No DELETE should be received\n      refute_receive {:socket_push, :text, _data}, 1000\n\n      assert %{\n               \"event\" => \"postgres_changes\",\n               \"payload\" => %{\n                 \"data\" => %{\n                   \"columns\" => [\n                     %{\"name\" => \"id\", \"type\" => \"int4\"},\n                     %{\"name\" => \"details\", \"type\" => \"text\"},\n                     %{\"name\" => \"binary_data\", \"type\" => \"bytea\"}\n                   ],\n                   \"commit_timestamp\" => _,\n                   \"errors\" => nil,\n                   \"record\" => %{\"details\" => \"test\", \"id\" => ^id, \"binary_data\" => nil},\n                   \"schema\" => \"public\",\n                   \"table\" => \"test\",\n                   \"type\" => \"INSERT\"\n                 },\n                 \"ids\" => _\n               },\n               \"ref\" => nil,\n               \"topic\" => \"realtime:test\"\n             } = Jason.decode!(data)\n\n      rate = Realtime.Tenants.db_events_per_second_rate(tenant)\n\n      assert {:ok, %RateCounter{id: {:channel, :db_events, ^external_id}, bucket: bucket}} =\n               RateCounterHelper.tick!(rate)\n\n      assert Enum.sum(bucket) == 1\n\n      assert_receive {\n        :telemetry,\n        [:realtime, :tenants, :payload, :size],\n        %{size: _},\n        %{tenant: ^external_id, message_type: :postgres_changes}\n      }\n    end\n\n    test \"db events rate limit works\", %{tenant: tenant, conn: conn} do\n      on_exit(fn -> PostgresCdcRls.handle_stop(tenant.external_id, 10_000) end)\n\n      %Tenant{extensions: extensions, external_id: external_id} = tenant\n      postgres_extension = PostgresCdc.filter_settings(\"postgres_cdc_rls\", extensions)\n      args = %{\"id\" => external_id, \"region\" => postgres_extension[\"region\"]}\n\n      pg_change_params = pubsub_subscribe(external_id)\n\n      # First time it will return nil\n      PostgresCdcRls.handle_connect(args)\n      # Wait for it to start\n      assert_receive %{event: \"ready\"}, 1000\n      {:ok, response} = PostgresCdcRls.handle_connect(args)\n\n      # Now subscribe to the Postgres Changes\n      Postgrex.query!(conn, \"delete from realtime.subscription\", [])\n      {:ok, _} = PostgresCdcRls.handle_after_connect(response, postgres_extension, pg_change_params, external_id)\n      assert %Postgrex.Result{rows: [[n]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n      assert n >= 1\n\n      rate = Realtime.Tenants.db_events_per_second_rate(tenant)\n\n      log =\n        capture_log(fn ->\n          # increment artifically the counter to  reach the limit\n          tenant.external_id\n          |> Realtime.Tenants.db_events_per_second_key()\n          |> Realtime.GenCounter.add(100_000_000)\n\n          RateCounterHelper.tick!(rate)\n        end)\n\n      assert log =~ \"MessagePerSecondRateLimitReached: Too many postgres changes messages per second\"\n\n      # Insert a record\n      %{rows: [[_id]]} = Postgrex.query!(conn, \"insert into test (details) values ('test') returning id\", [])\n\n      refute_receive {:socket_push, :text, _}, 5000\n\n      assert {:ok, %RateCounter{id: {:channel, :db_events, ^external_id}, bucket: bucket, limit: %{triggered: true}}} =\n               RateCounterHelper.tick!(rate)\n\n      # Nothing has changed\n      assert Enum.sum(bucket) == 100_000_000\n    end\n  end\n\n  @aux_mod (quote do\n              defmodule Subscriber do\n                # Start CDC remotely\n                def subscribe(tenant) do\n                  %Tenant{extensions: extensions, external_id: external_id} = tenant\n                  postgres_extension = PostgresCdc.filter_settings(\"postgres_cdc_rls\", extensions)\n                  args = %{\"id\" => external_id, \"region\" => postgres_extension[\"region\"]}\n\n                  RealtimeWeb.Endpoint.subscribe(Realtime.Syn.PostgresCdc.syn_topic(tenant.external_id))\n                  # First time it will return nil\n                  PostgresCdcRls.start(args)\n                  # Wait for it to start\n                  assert_receive %{event: \"ready\"}, 3000\n                  {:ok, manager, conn} = PostgresCdcRls.get_manager_conn(external_id)\n                  {:ok, {manager, conn}}\n                end\n              end\n            end)\n  describe \"distributed integration\" do\n    setup [:distributed_integration]\n\n    setup(%{tenant: tenant}) do\n      {:ok, node} = Clustered.start(@aux_mod)\n      {:ok, response} = :erpc.call(node, Subscriber, :subscribe, [tenant])\n\n      on_exit(fn ->\n        try do\n          PostgresCdcRls.handle_stop(tenant.external_id, 5_000)\n        catch\n          _, _ -> :ok\n        end\n      end)\n\n      %{node: node, response: response}\n    end\n\n    test \"subscribe distributed mode\", %{tenant: tenant, conn: conn, node: node, response: response} do\n      %Tenant{extensions: extensions, external_id: external_id} = tenant\n      postgres_extension = PostgresCdc.filter_settings(\"postgres_cdc_rls\", extensions)\n\n      pg_change_params = pubsub_subscribe(external_id)\n\n      Postgrex.query!(conn, \"delete from realtime.subscription\", [])\n      {:ok, _} = PostgresCdcRls.handle_after_connect(response, postgres_extension, pg_change_params, external_id)\n      assert %Postgrex.Result{rows: [[n]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n      assert n >= 1\n\n      # Wait for subscription to be executing\n      Process.sleep(200)\n\n      # Insert a record\n      %{rows: [[id]]} = Postgrex.query!(conn, \"insert into test (details) values ('test') returning id\", [])\n      # Delete the record\n      %{num_rows: 1} = Postgrex.query!(conn, \"delete from test\", [])\n\n      assert_receive {:socket_push, :text, data1}, 5000\n      assert_receive {:socket_push, :text, data2}, 5000\n\n      events = Enum.map([data1, data2], &Jason.decode!/1)\n\n      assert Enum.any?(events, fn event ->\n               match?(\n                 %{\n                   \"event\" => \"postgres_changes\",\n                   \"payload\" => %{\n                     \"data\" => %{\n                       \"errors\" => nil,\n                       \"record\" => %{\"details\" => \"test\", \"id\" => ^id, \"binary_data\" => nil},\n                       \"schema\" => \"public\",\n                       \"table\" => \"test\",\n                       \"type\" => \"INSERT\"\n                     }\n                   },\n                   \"ref\" => nil,\n                   \"topic\" => \"realtime:test\"\n                 },\n                 event\n               )\n             end)\n\n      assert Enum.any?(events, fn event ->\n               match?(\n                 %{\n                   \"event\" => \"postgres_changes\",\n                   \"payload\" => %{\n                     \"data\" => %{\n                       \"errors\" => nil,\n                       \"type\" => \"DELETE\",\n                       \"old_record\" => %{\"id\" => ^id},\n                       \"schema\" => \"public\",\n                       \"table\" => \"test\"\n                     }\n                   },\n                   \"ref\" => nil,\n                   \"topic\" => \"realtime:test\"\n                 },\n                 event\n               )\n             end)\n\n      assert_receive {\n        :telemetry,\n        [:realtime, :rpc],\n        %{latency: _},\n        %{\n          mechanism: :gen_rpc,\n          origin_node: _,\n          success: true,\n          target_node: ^node\n        }\n      }\n    end\n\n    test \"subscription error rate limit\", %{tenant: tenant, node: node} do\n      %Tenant{extensions: extensions, external_id: external_id} = tenant\n      postgres_extension = PostgresCdc.filter_settings(\"postgres_cdc_rls\", extensions)\n\n      pg_change_params = pubsub_subscribe(external_id)\n\n      # Grab a process that is not alive to cause subscriptions to error out\n      pid = :erpc.call(node, :erlang, :self, [])\n\n      # Now subscribe to the Postgres Changes multiple times to reach the rate limit\n      for _ <- 1..6 do\n        assert {:error, \"Too many database timeouts\"} =\n                 PostgresCdcRls.handle_after_connect({pid, pid}, postgres_extension, pg_change_params, external_id)\n      end\n\n      rate = Realtime.Tenants.subscription_errors_per_second_rate(external_id, 4)\n\n      assert {:ok, %RateCounter{id: {:channel, :subscription_errors, ^external_id}, limit: %{triggered: true}}} =\n               RateCounterHelper.tick!(rate)\n\n      # It won't even be called now\n      reject(&Realtime.GenRpc.call/5)\n\n      assert {:error, \"Too many database timeouts\"} =\n               PostgresCdcRls.handle_after_connect({pid, pid}, postgres_extension, pg_change_params, external_id)\n    end\n  end\n\n  defp integration(_) do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    {:ok, conn} = Database.connect(tenant, \"realtime_test\")\n    Integrations.setup_postgres_changes(conn)\n\n    on_exit(fn -> RateCounterHelper.stop(tenant.external_id) end)\n    on_exit(fn -> :telemetry.detach(__MODULE__) end)\n\n    :telemetry.attach_many(\n      __MODULE__,\n      [[:realtime, :tenants, :payload, :size], [:realtime, :rpc]],\n      &__MODULE__.handle_telemetry/4,\n      pid: self()\n    )\n\n    RealtimeWeb.Endpoint.subscribe(Realtime.Syn.PostgresCdc.syn_topic(tenant.external_id))\n\n    %{tenant: tenant, conn: conn}\n  end\n\n  defp distributed_integration(_) do\n    tenant = Containers.checkout_tenant_unboxed(run_migrations: true)\n    {:ok, conn} = Database.connect(tenant, \"realtime_test\")\n    Integrations.setup_postgres_changes(conn)\n\n    on_exit(fn -> RateCounterHelper.stop(tenant.external_id) end)\n    on_exit(fn -> :telemetry.detach(__MODULE__) end)\n\n    :telemetry.attach_many(\n      __MODULE__,\n      [[:realtime, :tenants, :payload, :size], [:realtime, :rpc]],\n      &__MODULE__.handle_telemetry/4,\n      pid: self()\n    )\n\n    RealtimeWeb.Endpoint.subscribe(Realtime.Syn.PostgresCdc.syn_topic(tenant.external_id))\n\n    %{tenant: tenant, conn: conn}\n  end\n\n  defp pubsub_subscribe(external_id, event \\\\ \"*\") do\n    pg_change_params = [\n      %{\n        id: UUID.uuid1(),\n        params: %{\"event\" => event, \"schema\" => \"public\"},\n        channel_pid: self(),\n        claims: %{\n          \"exp\" => System.system_time(:second) + 100_000,\n          \"iat\" => 0,\n          \"ref\" => \"127.0.0.1\",\n          \"role\" => \"anon\"\n        }\n      }\n    ]\n\n    topic = \"realtime:test\"\n    serializer = Phoenix.Socket.V1.JSONSerializer\n\n    ids =\n      Enum.map(pg_change_params, fn %{id: id, params: params} ->\n        {UUID.string_to_binary!(id), :erlang.phash2(params)}\n      end)\n\n    subscription_metadata = {:subscriber_fastlane, self(), serializer, ids, topic, true}\n    metadata = [metadata: subscription_metadata]\n    :ok = PostgresCdc.subscribe(PostgresCdcRls, pg_change_params, external_id, metadata)\n    pg_change_params\n  end\n\n  def handle_telemetry(event, measures, metadata, pid: pid), do: send(pid, {:telemetry, event, measures, metadata})\nend\n"
  },
  {
    "path": "test/realtime/extensions/cdc_rls/replication_poller_test.exs",
    "content": "defmodule Realtime.Extensions.PostgresCdcRls.ReplicationPollerTest do\n  # Tweaking application env\n  use Realtime.DataCase, async: false\n\n  use Mimic\n\n  alias Extensions.PostgresCdcRls.MessageDispatcher\n  alias Extensions.PostgresCdcRls.ReplicationPoller, as: Poller\n  alias Extensions.PostgresCdcRls.Replications\n\n  alias Realtime.Adapters.Changes.{\n    DeletedRecord,\n    NewRecord,\n    UpdatedRecord\n  }\n\n  alias Realtime.Database\n  alias Realtime.RateCounter\n\n  alias RealtimeWeb.TenantBroadcaster\n\n  import Poller, only: [generate_record: 1]\n\n  setup :set_mimic_global\n\n  @change_json ~s({\"table\":\"test\",\"type\":\"INSERT\",\"record\":{\"id\": 34, \"details\": \"test\"},\"columns\":[{\"name\": \"id\", \"type\": \"int4\"}, {\"name\": \"details\", \"type\": \"text\"}],\"errors\":null,\"schema\":\"public\",\"commit_timestamp\":\"2025-10-13T07:50:28.066Z\"})\n\n  describe \"poll\" do\n    setup do\n      :telemetry.attach(\n        __MODULE__,\n        [:realtime, :replication, :poller, :query, :stop],\n        &__MODULE__.handle_telemetry/4,\n        pid: self()\n      )\n\n      on_exit(fn -> :telemetry.detach(__MODULE__) end)\n\n      tenant = Containers.checkout_tenant(run_migrations: true)\n\n      {:ok, tenant} = Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{\"max_events_per_second\" => 123})\n\n      subscribers_pids_table = :ets.new(__MODULE__, [:public, :bag])\n      subscribers_nodes_table = :ets.new(__MODULE__, [:public, :set])\n\n      args =\n        hd(tenant.extensions).settings\n        |> Map.put(\"id\", tenant.external_id)\n        |> Map.put(\"subscribers_pids_table\", subscribers_pids_table)\n        |> Map.put(\"subscribers_nodes_table\", subscribers_nodes_table)\n\n      # unless specified it will return empty results\n      empty_results = {:ok, %Postgrex.Result{rows: [], num_rows: 0}}\n      stub(Replications, :list_changes, fn _, _, _, _, _ -> empty_results end)\n\n      %{args: args, tenant: tenant}\n    end\n\n    test \"handles slot in use error and retries\", %{args: args} do\n      tenant_id = args[\"id\"]\n\n      slot_in_use_error =\n        {:error,\n         %Postgrex.Error{\n           postgres: %{\n             code: :object_in_use,\n             message: \"replication slot is active for PID 12345\"\n           }\n         }}\n\n      stub(Replications, :get_pg_stat_activity_diff, fn _conn, _pid -> {:error, :pid_not_found} end)\n      stub(Replications, :terminate_backend, fn _conn, _slot -> {:error, :slot_not_found} end)\n\n      expect(Replications, :list_changes, fn _, _, _, _, _ -> slot_in_use_error end)\n\n      start_link_supervised!({Poller, args})\n\n      assert_receive {\n                       :telemetry,\n                       [:realtime, :replication, :poller, :query, :stop],\n                       %{duration: _},\n                       %{tenant: ^tenant_id}\n                     },\n                     1000\n    end\n\n    test \"handles slot in use error with pg_stat_activity returning diff\", %{args: args} do\n      tenant_id = args[\"id\"]\n\n      slot_in_use_error =\n        {:error,\n         %Postgrex.Error{\n           postgres: %{\n             code: :object_in_use,\n             message: \"replication slot is active for PID 12345\"\n           }\n         }}\n\n      stub(Replications, :get_pg_stat_activity_diff, fn _conn, _pid -> {:ok, 42} end)\n      stub(Replications, :terminate_backend, fn _conn, _slot -> {:error, :slot_not_found} end)\n\n      expect(Replications, :list_changes, fn _, _, _, _, _ -> slot_in_use_error end)\n\n      start_link_supervised!({Poller, args})\n\n      assert_receive {\n                       :telemetry,\n                       [:realtime, :replication, :poller, :query, :stop],\n                       %{duration: _},\n                       %{tenant: ^tenant_id}\n                     },\n                     1000\n    end\n\n    test \"handles prepare_replication failure and retries\", %{args: args} do\n      tenant_id = args[\"id\"]\n\n      stub(Replications, :prepare_replication, fn _, _ -> {:ok, %Postgrex.Result{}} end)\n      expect(Replications, :prepare_replication, fn _, _ -> {:error, \"prepare failed\"} end)\n\n      start_link_supervised!({Poller, args})\n\n      assert_receive {\n                       :telemetry,\n                       [:realtime, :replication, :poller, :query, :stop],\n                       %{duration: _},\n                       %{tenant: ^tenant_id}\n                     },\n                     2000\n    end\n\n    test \"terminates replication slot when retry count exceeds threshold\", %{args: args} do\n      tenant_id = args[\"id\"]\n\n      slot_in_use_error =\n        {:error,\n         %Postgrex.Error{\n           postgres: %{\n             code: :object_in_use,\n             message: \"replication slot is active for PID 12345\"\n           }\n         }}\n\n      stub(Replications, :get_pg_stat_activity_diff, fn _conn, _pid -> {:ok, 42} end)\n      stub(Replications, :list_changes, fn _, _, _, _, _ -> slot_in_use_error end)\n      stub(Replications, :terminate_backend, fn _conn, _slot -> {:ok, :terminated} end)\n\n      pid = start_link_supervised!({Poller, args})\n\n      # Wait for the first poll\n      assert_receive {:telemetry, [:realtime, :replication, :poller, :query, :stop], _, %{tenant: ^tenant_id}}, 1000\n\n      # Advance retry_count past threshold and send another poll\n      :sys.replace_state(pid, fn state -> %{state | retry_count: 4} end)\n      send(pid, :poll)\n\n      assert_receive {:telemetry, [:realtime, :replication, :poller, :query, :stop], _, %{tenant: ^tenant_id}}, 2000\n    end\n\n    test \"handles no new changes\", %{args: args, tenant: tenant} do\n      tenant_id = args[\"id\"]\n      reject(&TenantBroadcaster.pubsub_direct_broadcast/6)\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n      start_link_supervised!({Poller, args})\n\n      assert_receive {\n                       :telemetry,\n                       [:realtime, :replication, :poller, :query, :stop],\n                       %{duration: _},\n                       %{tenant: ^tenant_id}\n                     },\n                     500\n\n      rate = Realtime.Tenants.db_events_per_second_rate(tenant)\n\n      assert {:ok,\n              %RateCounter{\n                sum: sum,\n                limit: %{\n                  value: 123,\n                  measurement: :avg,\n                  triggered: false\n                }\n              }} = RateCounterHelper.tick!(rate)\n\n      assert sum == 0\n    end\n\n    test \"handles new changes with missing ets table\", %{args: args, tenant: tenant} do\n      tenant_id = args[\"id\"]\n\n      :ets.delete(args[\"subscribers_nodes_table\"])\n\n      results =\n        build_result([\n          <<71, 36, 83, 212, 168, 9, 17, 240, 165, 186, 118, 202, 193, 157, 232, 187>>,\n          <<251, 188, 190, 118, 168, 119, 17, 240, 188, 87, 118, 202, 193, 157, 232, 187>>\n        ])\n\n      expect(Replications, :list_changes, fn _, _, _, _, _ -> results end)\n      reject(&TenantBroadcaster.pubsub_direct_broadcast/6)\n\n      # Broadcast to the whole cluster due to missing node information\n      expect(TenantBroadcaster, :pubsub_broadcast, fn ^tenant_id,\n                                                      \"realtime:postgres:\" <> ^tenant_id,\n                                                      {\"INSERT\", change_json, _sub_ids},\n                                                      MessageDispatcher,\n                                                      :postgres_changes ->\n        assert Jason.decode!(change_json) == Jason.decode!(@change_json)\n        :ok\n      end)\n\n      start_link_supervised!({Poller, args})\n\n      # First poll with changes\n      assert_receive {\n                       :telemetry,\n                       [:realtime, :replication, :poller, :query, :stop],\n                       %{duration: _},\n                       %{tenant: ^tenant_id}\n                     },\n                     500\n\n      # Second poll without changes\n      assert_receive {\n                       :telemetry,\n                       [:realtime, :replication, :poller, :query, :stop],\n                       %{duration: _},\n                       %{tenant: ^tenant_id}\n                     },\n                     500\n\n      rate = Realtime.Tenants.db_events_per_second_rate(tenant)\n      assert {:ok, %RateCounter{sum: sum}} = RateCounterHelper.tick!(rate)\n      assert sum == 2\n    end\n\n    test \"handles new changes with no subscription nodes\", %{args: args, tenant: tenant} do\n      tenant_id = args[\"id\"]\n\n      results =\n        build_result([\n          <<71, 36, 83, 212, 168, 9, 17, 240, 165, 186, 118, 202, 193, 157, 232, 187>>,\n          <<251, 188, 190, 118, 168, 119, 17, 240, 188, 87, 118, 202, 193, 157, 232, 187>>\n        ])\n\n      expect(Replications, :list_changes, fn _, _, _, _, _ -> results end)\n      reject(&TenantBroadcaster.pubsub_direct_broadcast/6)\n\n      # Broadcast to the whole cluster due to missing node information\n      expect(TenantBroadcaster, :pubsub_broadcast, fn ^tenant_id,\n                                                      \"realtime:postgres:\" <> ^tenant_id,\n                                                      {\"INSERT\", change_json, _sub_ids},\n                                                      MessageDispatcher,\n                                                      :postgres_changes ->\n        assert Jason.decode!(change_json) == Jason.decode!(@change_json)\n        :ok\n      end)\n\n      start_link_supervised!({Poller, args})\n\n      # First poll with changes\n      assert_receive {\n                       :telemetry,\n                       [:realtime, :replication, :poller, :query, :stop],\n                       %{duration: _},\n                       %{tenant: ^tenant_id}\n                     },\n                     500\n\n      # Second poll without changes\n      assert_receive {\n                       :telemetry,\n                       [:realtime, :replication, :poller, :query, :stop],\n                       %{duration: _},\n                       %{tenant: ^tenant_id}\n                     },\n                     500\n\n      rate = Realtime.Tenants.db_events_per_second_rate(tenant)\n      assert {:ok, %RateCounter{sum: sum}} = RateCounterHelper.tick!(rate)\n      assert sum == 2\n    end\n\n    test \"handles new changes with missing subscription nodes\", %{args: args, tenant: tenant} do\n      tenant_id = args[\"id\"]\n\n      results =\n        build_result([\n          sub1 = <<71, 36, 83, 212, 168, 9, 17, 240, 165, 186, 118, 202, 193, 157, 232, 187>>,\n          <<251, 188, 190, 118, 168, 119, 17, 240, 188, 87, 118, 202, 193, 157, 232, 187>>\n        ])\n\n      # Only one subscription has node information\n      :ets.insert(args[\"subscribers_nodes_table\"], {sub1, node()})\n\n      expect(Replications, :list_changes, fn _, _, _, _, _ -> results end)\n      reject(&TenantBroadcaster.pubsub_direct_broadcast/6)\n\n      # Broadcast to the whole cluster due to missing node information\n      expect(TenantBroadcaster, :pubsub_broadcast, fn ^tenant_id,\n                                                      \"realtime:postgres:\" <> ^tenant_id,\n                                                      {\"INSERT\", change_json, _sub_ids},\n                                                      MessageDispatcher,\n                                                      :postgres_changes ->\n        assert Jason.decode!(change_json) == Jason.decode!(@change_json)\n        :ok\n      end)\n\n      start_link_supervised!({Poller, args})\n\n      # First poll with changes\n      assert_receive {\n                       :telemetry,\n                       [:realtime, :replication, :poller, :query, :stop],\n                       %{duration: _},\n                       %{tenant: ^tenant_id}\n                     },\n                     500\n\n      # Second poll without changes\n      assert_receive {\n                       :telemetry,\n                       [:realtime, :replication, :poller, :query, :stop],\n                       %{duration: _},\n                       %{tenant: ^tenant_id}\n                     },\n                     500\n\n      rate = Realtime.Tenants.db_events_per_second_rate(tenant)\n      assert {:ok, %RateCounter{sum: sum}} = RateCounterHelper.tick!(rate)\n      assert sum == 2\n    end\n\n    test \"handles new changes with subscription nodes information\", %{args: args, tenant: tenant} do\n      tenant_id = args[\"id\"]\n\n      results =\n        build_result([\n          sub1 = <<71, 36, 83, 212, 168, 9, 17, 240, 165, 186, 118, 202, 193, 157, 232, 187>>,\n          sub2 = <<251, 188, 190, 118, 168, 119, 17, 240, 188, 87, 118, 202, 193, 157, 232, 187>>,\n          sub3 = <<49, 59, 209, 112, 173, 77, 17, 240, 191, 41, 118, 202, 193, 157, 232, 187>>\n        ])\n\n      # All subscriptions have node information\n      :ets.insert(args[\"subscribers_nodes_table\"], {sub1, node()})\n      :ets.insert(args[\"subscribers_nodes_table\"], {sub2, :\"someothernode@127.0.0.1\"})\n      :ets.insert(args[\"subscribers_nodes_table\"], {sub3, node()})\n\n      expect(Replications, :list_changes, fn _, _, _, _, _ -> results end)\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n\n      topic = \"realtime:postgres:\" <> tenant_id\n\n      # # Broadcast to the exact nodes only\n      expect(TenantBroadcaster, :pubsub_direct_broadcast, 2, fn\n        _node, ^tenant_id, ^topic, {\"INSERT\", change_json, _sub_ids}, MessageDispatcher, :postgres_changes ->\n          assert Jason.decode!(change_json) == Jason.decode!(@change_json)\n          :ok\n      end)\n\n      start_link_supervised!({Poller, args})\n\n      # First poll with changes\n      assert_receive {\n                       :telemetry,\n                       [:realtime, :replication, :poller, :query, :stop],\n                       %{duration: _},\n                       %{tenant: ^tenant_id}\n                     },\n                     500\n\n      # Second poll without changes\n      assert_receive {\n                       :telemetry,\n                       [:realtime, :replication, :poller, :query, :stop],\n                       %{duration: _},\n                       %{tenant: ^tenant_id}\n                     },\n                     500\n\n      calls = calls(TenantBroadcaster, :pubsub_direct_broadcast, 6)\n\n      assert Enum.count(calls) == 2\n\n      node_subs = Enum.map(calls, fn [node, _, _, {\"INSERT\", _change_json, sub_ids}, _, _] -> {node, sub_ids} end)\n\n      assert {node(), MapSet.new([sub1, sub3])} in node_subs\n      assert {:\"someothernode@127.0.0.1\", MapSet.new([sub2])} in node_subs\n\n      rate = Realtime.Tenants.db_events_per_second_rate(tenant)\n      assert {:ok, %RateCounter{sum: sum}} = RateCounterHelper.tick!(rate)\n      assert sum == 3\n    end\n  end\n\n  @columns [\n    %{\"name\" => \"id\", \"type\" => \"int8\"},\n    %{\"name\" => \"details\", \"type\" => \"text\"},\n    %{\"name\" => \"user_id\", \"type\" => \"int8\"}\n  ]\n\n  @ts \"2021-11-05T17:20:51.52406+00:00\"\n\n  @subscription_id \"417e76fd-9bc5-4b3e-bd5d-a031389c4a6b\"\n  @subscription_ids MapSet.new([\"417e76fd-9bc5-4b3e-bd5d-a031389c4a6b\"])\n\n  @old_record %{\"id\" => 12}\n  @record %{\"details\" => \"test\", \"id\" => 12, \"user_id\" => 1}\n\n  describe \"generate_record/1\" do\n    test \"INSERT\" do\n      wal_record = [\n        {\"type\", \"INSERT\"},\n        {\"schema\", \"public\"},\n        {\"table\", \"todos\"},\n        {\"columns\", Jason.encode!(@columns)},\n        {\"record\", Jason.encode!(@record)},\n        {\"old_record\", nil},\n        {\"commit_timestamp\", @ts},\n        {\"subscription_ids\", [@subscription_id]},\n        {\"errors\", []}\n      ]\n\n      assert %NewRecord{\n               columns: columns,\n               commit_timestamp: @ts,\n               schema: \"public\",\n               table: \"todos\",\n               type: \"INSERT\",\n               subscription_ids: @subscription_ids,\n               record: record,\n               errors: nil\n             } = generate_record(wal_record)\n\n      # Encode then decode to get rid of the fragment\n      assert record |> Jason.encode!() |> Jason.decode!() == @record\n      assert columns |> Jason.encode!() |> Jason.decode!() == @columns\n    end\n\n    test \"UPDATE\" do\n      wal_record = [\n        {\"type\", \"UPDATE\"},\n        {\"schema\", \"public\"},\n        {\"table\", \"todos\"},\n        {\"columns\", Jason.encode!(@columns)},\n        {\"record\", Jason.encode!(@record)},\n        {\"old_record\", Jason.encode!(@old_record)},\n        {\"commit_timestamp\", @ts},\n        {\"subscription_ids\", [@subscription_id]},\n        {\"errors\", []}\n      ]\n\n      assert %UpdatedRecord{\n               columns: columns,\n               commit_timestamp: @ts,\n               schema: \"public\",\n               table: \"todos\",\n               type: \"UPDATE\",\n               subscription_ids: @subscription_ids,\n               record: record,\n               old_record: old_record,\n               errors: nil\n             } = generate_record(wal_record)\n\n      # Encode then decode to get rid of the fragment\n      assert record |> Jason.encode!() |> Jason.decode!() == @record\n      assert old_record |> Jason.encode!() |> Jason.decode!() == @old_record\n      assert columns |> Jason.encode!() |> Jason.decode!() == @columns\n    end\n\n    test \"DELETE\" do\n      wal_record = [\n        {\"type\", \"DELETE\"},\n        {\"schema\", \"public\"},\n        {\"table\", \"todos\"},\n        {\"columns\", Jason.encode!(@columns)},\n        {\"record\", nil},\n        {\"old_record\", Jason.encode!(@old_record)},\n        {\"commit_timestamp\", @ts},\n        {\"subscription_ids\", [@subscription_id]},\n        {\"errors\", []}\n      ]\n\n      assert %DeletedRecord{\n               columns: columns,\n               commit_timestamp: @ts,\n               schema: \"public\",\n               table: \"todos\",\n               type: \"DELETE\",\n               subscription_ids: @subscription_ids,\n               old_record: old_record,\n               errors: nil\n             } = generate_record(wal_record)\n\n      # Encode then decode to get rid of the fragment\n      assert old_record |> Jason.encode!() |> Jason.decode!() == @old_record\n      assert columns |> Jason.encode!() |> Jason.decode!() == @columns\n    end\n\n    test \"INSERT, large payload error present\" do\n      wal_record = [\n        {\"type\", \"INSERT\"},\n        {\"schema\", \"public\"},\n        {\"table\", \"todos\"},\n        {\"columns\", Jason.encode!(@columns)},\n        {\"record\", Jason.encode!(@record)},\n        {\"old_record\", nil},\n        {\"commit_timestamp\", @ts},\n        {\"subscription_ids\", [@subscription_id]},\n        {\"errors\", [\"Error 413: Payload Too Large\"]}\n      ]\n\n      assert %NewRecord{\n               columns: columns,\n               commit_timestamp: @ts,\n               schema: \"public\",\n               table: \"todos\",\n               type: \"INSERT\",\n               subscription_ids: @subscription_ids,\n               record: record,\n               errors: [\"Error 413: Payload Too Large\"]\n             } = generate_record(wal_record)\n\n      # Encode then decode to get rid of the fragment\n      assert record |> Jason.encode!() |> Jason.decode!() == @record\n      assert columns |> Jason.encode!() |> Jason.decode!() == @columns\n    end\n\n    test \"INSERT, other errors present\" do\n      wal_record = [\n        {\"type\", \"INSERT\"},\n        {\"schema\", \"public\"},\n        {\"table\", \"todos\"},\n        {\"columns\", Jason.encode!(@columns)},\n        {\"record\", Jason.encode!(@record)},\n        {\"old_record\", nil},\n        {\"commit_timestamp\", @ts},\n        {\"subscription_ids\", [@subscription_id]},\n        {\"errors\", [\"Error...\"]}\n      ]\n\n      assert %NewRecord{\n               columns: columns,\n               commit_timestamp: @ts,\n               schema: \"public\",\n               table: \"todos\",\n               type: \"INSERT\",\n               subscription_ids: @subscription_ids,\n               record: record,\n               errors: [\"Error...\"]\n             } = generate_record(wal_record)\n\n      # Encode then decode to get rid of the fragment\n      assert record |> Jason.encode!() |> Jason.decode!() == @record\n      assert columns |> Jason.encode!() |> Jason.decode!() == @columns\n    end\n\n    test \"UPDATE, large payload error present\" do\n      wal_record = [\n        {\"type\", \"UPDATE\"},\n        {\"schema\", \"public\"},\n        {\"table\", \"todos\"},\n        {\"columns\", Jason.encode!(@columns)},\n        {\"record\", Jason.encode!(@record)},\n        {\"old_record\", Jason.encode!(@old_record)},\n        {\"commit_timestamp\", @ts},\n        {\"subscription_ids\", [@subscription_id]},\n        {\"errors\", [\"Error 413: Payload Too Large\"]}\n      ]\n\n      assert %UpdatedRecord{\n               columns: columns,\n               commit_timestamp: @ts,\n               schema: \"public\",\n               table: \"todos\",\n               type: \"UPDATE\",\n               subscription_ids: @subscription_ids,\n               record: record,\n               old_record: old_record,\n               errors: [\"Error 413: Payload Too Large\"]\n             } = generate_record(wal_record)\n\n      # Encode then decode to get rid of the fragment\n      assert record |> Jason.encode!() |> Jason.decode!() == @record\n      assert old_record |> Jason.encode!() |> Jason.decode!() == @old_record\n      assert columns |> Jason.encode!() |> Jason.decode!() == @columns\n    end\n\n    test \"UPDATE, other errors present\" do\n      wal_record = [\n        {\"type\", \"UPDATE\"},\n        {\"schema\", \"public\"},\n        {\"table\", \"todos\"},\n        {\"columns\", Jason.encode!(@columns)},\n        {\"record\", Jason.encode!(@record)},\n        {\"old_record\", Jason.encode!(@old_record)},\n        {\"commit_timestamp\", @ts},\n        {\"subscription_ids\", [@subscription_id]},\n        {\"errors\", [\"Error...\"]}\n      ]\n\n      assert %UpdatedRecord{\n               columns: columns,\n               commit_timestamp: @ts,\n               schema: \"public\",\n               table: \"todos\",\n               type: \"UPDATE\",\n               subscription_ids: @subscription_ids,\n               record: record,\n               old_record: old_record,\n               errors: [\"Error...\"]\n             } = generate_record(wal_record)\n\n      # Encode then decode to get rid of the fragment\n      assert record |> Jason.encode!() |> Jason.decode!() == @record\n      assert old_record |> Jason.encode!() |> Jason.decode!() == @old_record\n      assert columns |> Jason.encode!() |> Jason.decode!() == @columns\n    end\n\n    test \"DELETE, large payload error present\" do\n      wal_record = [\n        {\"type\", \"DELETE\"},\n        {\"schema\", \"public\"},\n        {\"table\", \"todos\"},\n        {\"columns\", Jason.encode!(@columns)},\n        {\"record\", nil},\n        {\"old_record\", Jason.encode!(@old_record)},\n        {\"commit_timestamp\", @ts},\n        {\"subscription_ids\", [@subscription_id]},\n        {\"errors\", [\"Error 413: Payload Too Large\"]}\n      ]\n\n      assert %DeletedRecord{\n               columns: columns,\n               commit_timestamp: @ts,\n               schema: \"public\",\n               table: \"todos\",\n               type: \"DELETE\",\n               subscription_ids: @subscription_ids,\n               old_record: old_record,\n               errors: [\"Error 413: Payload Too Large\"]\n             } = generate_record(wal_record)\n\n      # Encode then decode to get rid of the fragment\n      assert old_record |> Jason.encode!() |> Jason.decode!() == @old_record\n      assert columns |> Jason.encode!() |> Jason.decode!() == @columns\n    end\n\n    test \"DELETE, other errors present\" do\n      wal_record = [\n        {\"type\", \"DELETE\"},\n        {\"schema\", \"public\"},\n        {\"table\", \"todos\"},\n        {\"columns\", Jason.encode!(@columns)},\n        {\"record\", nil},\n        {\"old_record\", Jason.encode!(@old_record)},\n        {\"commit_timestamp\", @ts},\n        {\"subscription_ids\", [@subscription_id]},\n        {\"errors\", [\"Error...\"]}\n      ]\n\n      assert %DeletedRecord{\n               columns: columns,\n               commit_timestamp: @ts,\n               schema: \"public\",\n               table: \"todos\",\n               type: \"DELETE\",\n               subscription_ids: @subscription_ids,\n               old_record: old_record,\n               errors: [\"Error...\"]\n             } = generate_record(wal_record)\n\n      # Encode then decode to get rid of the fragment\n      assert old_record |> Jason.encode!() |> Jason.decode!() == @old_record\n      assert columns |> Jason.encode!() |> Jason.decode!() == @columns\n    end\n  end\n\n  describe \"get_pg_stat_activity_diff/2\" do\n    setup do\n      tenant = Containers.checkout_tenant(run_migrations: true)\n      {:ok, conn} = Database.connect(tenant, \"realtime_rls\", :stop)\n      %{conn: conn}\n    end\n\n    test \"returns error when pid is not in pg_stat_activity\", %{conn: conn} do\n      assert {:error, :pid_not_found} = Replications.get_pg_stat_activity_diff(conn, 0)\n    end\n  end\n\n  describe \"error handling\" do\n    setup do\n      tenant = Containers.checkout_tenant(run_migrations: true)\n\n      args =\n        hd(tenant.extensions).settings\n        |> Map.put(\"id\", tenant.external_id)\n        |> Map.put(\"subscribers_pids_table\", :ets.new(__MODULE__, [:public, :bag]))\n        |> Map.put(\"subscribers_nodes_table\", :ets.new(__MODULE__, [:public, :set]))\n\n      %{args: args}\n    end\n\n    test \"stops cleanly when database connection fails\", %{args: args} do\n      stub(Realtime.Database, :connect_db, fn _settings -> {:error, :econnrefused} end)\n\n      pid = start_supervised!({Poller, args}, restart: :temporary)\n      ref = Process.monitor(pid)\n\n      assert_receive {:DOWN, ^ref, :process, ^pid, :econnrefused}, 1000\n    end\n  end\n\n  describe \"slot_name_suffix/0\" do\n    setup do\n      slot_name_suffix = Application.get_env(:realtime, :slot_name_suffix)\n\n      on_exit(fn -> Application.put_env(:realtime, :slot_name_suffix, slot_name_suffix) end)\n    end\n\n    test \"uses Application.get_env/2 with key :slot_name_suffix\" do\n      slot_name_suffix = Generators.random_string()\n      Application.put_env(:realtime, :slot_name_suffix, slot_name_suffix)\n      assert Poller.slot_name_suffix() == \"_\" <> slot_name_suffix\n    end\n\n    test \"defaults to no suffix\" do\n      assert Poller.slot_name_suffix() == \"\"\n    end\n  end\n\n  def handle_telemetry(event, measures, metadata, pid: pid), do: send(pid, {:telemetry, event, measures, metadata})\n\n  defp build_result(subscription_ids) do\n    {:ok,\n     %Postgrex.Result{\n       command: :select,\n       columns: [\n         \"type\",\n         \"schema\",\n         \"table\",\n         \"columns\",\n         \"record\",\n         \"old_record\",\n         \"commit_timestamp\",\n         \"subscription_ids\",\n         \"errors\"\n       ],\n       rows: [\n         [\n           \"INSERT\",\n           \"public\",\n           \"test\",\n           \"[{\\\"name\\\": \\\"id\\\", \\\"type\\\": \\\"int4\\\"}, {\\\"name\\\": \\\"details\\\", \\\"type\\\": \\\"text\\\"}]\",\n           \"{\\\"id\\\": 34, \\\"details\\\": \\\"test\\\"}\",\n           nil,\n           \"2025-10-13T07:50:28.066Z\",\n           subscription_ids,\n           []\n         ]\n       ],\n       num_rows: 1,\n       connection_id: 123,\n       messages: []\n     }}\n  end\nend\n"
  },
  {
    "path": "test/realtime/extensions/cdc_rls/replications_test.exs",
    "content": "defmodule Realtime.Extensions.PostgresCdcRls.ReplicationsTest do\n  use Realtime.DataCase, async: true\n\n  alias Extensions.PostgresCdcRls.Replications\n  alias Realtime.Database\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    {:ok, conn} = Database.connect(tenant, \"realtime_rls\", :stop)\n    %{conn: conn}\n  end\n\n  describe \"terminate_backend/2\" do\n    test \"returns slot_not_found when slot does not exist\", %{conn: conn} do\n      assert {:error, :slot_not_found} =\n               Replications.terminate_backend(conn, \"nonexistent_slot_#{:rand.uniform(999_999)}\")\n    end\n\n    test \"returns slot_not_found when slot exists but has no active backend\", %{conn: conn} do\n      slot_name = \"test_inactive_slot_#{:rand.uniform(999_999)}\"\n\n      Postgrex.query!(conn, \"SELECT pg_create_logical_replication_slot($1, 'wal2json')\", [slot_name])\n\n      try do\n        # No replication session is reading from it, so active_pid is nil\n        assert {:error, :slot_not_found} = Replications.terminate_backend(conn, slot_name)\n      after\n        Postgrex.query(conn, \"SELECT pg_drop_replication_slot($1)\", [slot_name])\n      end\n    end\n  end\n\n  describe \"get_pg_stat_activity_diff/2\" do\n    test \"returns error when pid is not in pg_stat_activity\", %{conn: conn} do\n      assert {:error, :pid_not_found} = Replications.get_pg_stat_activity_diff(conn, 0)\n    end\n\n    test \"returns diff when pid is found in pg_stat_activity\", %{conn: conn} do\n      # Get the PID of the current connection from pg_stat_activity\n      %{rows: [[db_pid]]} = Postgrex.query!(conn, \"SELECT pg_backend_pid()\", [])\n\n      # Update the application name so we can find this connection\n      Postgrex.query!(conn, \"SET application_name = 'realtime_rls'\", [])\n\n      assert {:ok, diff} = Replications.get_pg_stat_activity_diff(conn, db_pid)\n      assert is_integer(diff)\n    end\n  end\n\n  describe \"list_changes/5\" do\n    test \"returns rows from the publication slot\", %{conn: conn} do\n      slot_name = \"test_list_slot_#{:rand.uniform(999_999)}\"\n      publication = \"supabase_realtime_test\"\n\n      Postgrex.query!(conn, \"SELECT pg_create_logical_replication_slot($1, 'wal2json')\", [slot_name])\n\n      try do\n        assert {:ok, %Postgrex.Result{columns: columns}} =\n                 Replications.list_changes(conn, slot_name, publication, 100, 1_048_576)\n\n        assert \"type\" in columns\n      after\n        Postgrex.query(conn, \"SELECT pg_drop_replication_slot($1)\", [slot_name])\n      end\n    end\n  end\n\n  describe \"prepare_replication/2\" do\n    test \"creates a replication slot when it does not exist\", %{conn: conn} do\n      slot_name = \"test_prep_slot_#{:rand.uniform(999_999)}\"\n      assert {:ok, %Postgrex.Result{}} = Replications.prepare_replication(conn, slot_name)\n    end\n\n    test \"is idempotent when slot already exists\", %{conn: conn} do\n      slot_name = \"test_idempotent_slot_#{:rand.uniform(999_999)}\"\n      assert {:ok, _} = Replications.prepare_replication(conn, slot_name)\n      assert {:ok, _} = Replications.prepare_replication(conn, slot_name)\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/extensions/cdc_rls/subscription_manager_test.exs",
    "content": "defmodule Realtime.Extensions.CdcRls.SubscriptionManagerTest do\n  # async: false due to global Mimic stubs\n  use Realtime.DataCase, async: false\n  use Mimic\n\n  alias Extensions.PostgresCdcRls\n  alias Extensions.PostgresCdcRls.SubscriptionManager\n  alias Extensions.PostgresCdcRls.Subscriptions\n  alias Realtime.Database\n  alias Realtime.Tenants.Rebalancer\n\n  import ExUnit.CaptureLog\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    {:ok, db_conn} = Realtime.Database.connect(tenant, \"realtime_test\", :stop)\n    Integrations.setup_postgres_changes(db_conn)\n    GenServer.stop(db_conn)\n    Realtime.Tenants.Cache.update_cache(tenant)\n\n    subscribers_pids_table = :ets.new(__MODULE__, [:public, :bag])\n    subscribers_nodes_table = :ets.new(__MODULE__, [:public, :set])\n\n    args = %{\n      \"id\" => tenant.external_id,\n      \"subscribers_nodes_table\" => subscribers_nodes_table,\n      \"subscribers_pids_table\" => subscribers_pids_table\n    }\n\n    publication = \"supabase_realtime_test\"\n\n    # register this process with syn as if this was the WorkersSupervisor\n\n    scope = Realtime.Syn.PostgresCdc.scope(tenant.external_id)\n    :syn.register(scope, tenant.external_id, self(), %{region: \"us-east-1\", manager: nil, subs_pool: nil})\n\n    {:ok, pid} = SubscriptionManager.start_link(args)\n    # This serves so that we know that handle_continue has finished\n    :sys.get_state(pid)\n    %{args: args, pid: pid, publication: publication}\n  end\n\n  describe \"subscription\" do\n    test \"subscription\", %{pid: pid, args: args, publication: publication} do\n      {:ok, ^pid, conn} = PostgresCdcRls.get_manager_conn(args[\"id\"])\n      {uuid, bin_uuid, pg_change_params} = pg_change_params()\n\n      subscriber = self()\n\n      assert {:ok, [%Postgrex.Result{command: :insert}]} =\n               Subscriptions.create(conn, publication, [pg_change_params], pid, subscriber)\n\n      # Wait for subscription manager to process the :subscribed message\n      :sys.get_state(pid)\n\n      node = node()\n\n      assert [{^subscriber, ^uuid, _ref, ^node}] = :ets.tab2list(args[\"subscribers_pids_table\"])\n\n      assert :ets.tab2list(args[\"subscribers_nodes_table\"]) == [{bin_uuid, node}]\n    end\n\n    test \"subscriber died\", %{pid: pid, args: args, publication: publication} do\n      {:ok, ^pid, conn} = PostgresCdcRls.get_manager_conn(args[\"id\"])\n      self = self()\n\n      subscriber =\n        spawn(fn ->\n          receive do\n            :stop -> :ok\n          end\n        end)\n\n      {uuid1, bin_uuid1, pg_change_params1} = pg_change_params()\n      {uuid2, bin_uuid2, pg_change_params2} = pg_change_params()\n      {uuid3, bin_uuid3, pg_change_params3} = pg_change_params()\n\n      assert {:ok, _} =\n               Subscriptions.create(conn, publication, [pg_change_params1, pg_change_params2], pid, subscriber)\n\n      assert {:ok, _} = Subscriptions.create(conn, publication, [pg_change_params3], pid, self())\n\n      # Wait for subscription manager to process the :subscribed message\n      :sys.get_state(pid)\n\n      node = node()\n\n      assert :ets.info(args[\"subscribers_pids_table\"], :size) == 3\n\n      assert [{^subscriber, ^uuid1, _, ^node}, {^subscriber, ^uuid2, _, ^node}] =\n               :ets.lookup(args[\"subscribers_pids_table\"], subscriber)\n\n      assert [{^self, ^uuid3, _ref, ^node}] = :ets.lookup(args[\"subscribers_pids_table\"], self)\n\n      assert :ets.info(args[\"subscribers_nodes_table\"], :size) == 3\n      assert [{^bin_uuid1, ^node}] = :ets.lookup(args[\"subscribers_nodes_table\"], bin_uuid1)\n      assert [{^bin_uuid2, ^node}] = :ets.lookup(args[\"subscribers_nodes_table\"], bin_uuid2)\n      assert [{^bin_uuid3, ^node}] = :ets.lookup(args[\"subscribers_nodes_table\"], bin_uuid3)\n\n      send(subscriber, :stop)\n      # Wait for subscription manager to receive the :DOWN message\n      Process.sleep(200)\n\n      # Only the subscription we have not stopped should remain\n\n      assert [{^self, ^uuid3, _ref, ^node}] = :ets.tab2list(args[\"subscribers_pids_table\"])\n      assert [{^bin_uuid3, ^node}] = :ets.tab2list(args[\"subscribers_nodes_table\"])\n    end\n  end\n\n  describe \"subscription deletion\" do\n    test \"subscription is deleted when process goes away\", %{pid: pid, args: args, publication: publication} do\n      {:ok, ^pid, conn} = PostgresCdcRls.get_manager_conn(args[\"id\"])\n      {_uuid, _bin_uuid, pg_change_params} = pg_change_params()\n\n      subscriber =\n        spawn(fn ->\n          receive do\n            :stop -> :ok\n          end\n        end)\n\n      %Postgrex.Result{rows: [[baseline]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n\n      assert {:ok, [%Postgrex.Result{command: :insert}]} =\n               Subscriptions.create(conn, publication, [pg_change_params], pid, subscriber)\n\n      # Wait for subscription manager to process the :subscribed message\n      :sys.get_state(pid)\n\n      assert :ets.info(args[\"subscribers_pids_table\"], :size) == 1\n      assert :ets.info(args[\"subscribers_nodes_table\"], :size) == 1\n\n      %Postgrex.Result{rows: [[after_create]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n      assert after_create > baseline\n\n      send(subscriber, :stop)\n      # Wait for subscription manager to receive the :DOWN message\n      Process.sleep(200)\n\n      assert :ets.info(args[\"subscribers_pids_table\"], :size) == 0\n      assert :ets.info(args[\"subscribers_nodes_table\"], :size) == 0\n\n      # Force check delete queue on manager\n      send(pid, :check_delete_queue)\n      :sys.get_state(pid)\n\n      assert %Postgrex.Result{rows: [[^baseline]]} =\n               Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n    end\n  end\n\n  describe \"check no users\" do\n    test \"exit is sent to manager\", %{pid: pid} do\n      :sys.replace_state(pid, fn state -> %{state | no_users_ts: 0} end)\n\n      send(pid, :check_no_users)\n\n      assert_receive {:system, {^pid, _}, {:terminate, :shutdown}}\n    end\n  end\n\n  describe \"message handling\" do\n    setup :set_mimic_global\n\n    test \"re-subscribes all subscribers when publication oids change\", %{pid: pid, args: args} do\n      # Force state to have different oids so the new_oids branch is triggered when\n      # fetch_publication_tables returns the real oids from the database\n      :sys.replace_state(pid, fn state -> %{state | oids: %{fake: :oids_that_dont_match}} end)\n      :ets.insert(args[\"subscribers_pids_table\"], {self(), UUID.uuid1(), make_ref(), node()})\n\n      send(pid, :check_oids)\n\n      assert_receive :postgres_subscribe, 1000\n    end\n\n    test \"logs error when subscription deletion fails during check_delete_queue\", %{\n      pid: pid,\n      args: args,\n      publication: publication\n    } do\n      {:ok, ^pid, conn} = PostgresCdcRls.get_manager_conn(args[\"id\"])\n      {_uuid, _bin_uuid, pg_change_params} = pg_change_params()\n\n      subscriber = spawn(fn -> receive do: (:stop -> :ok) end)\n      Subscriptions.create(conn, publication, [pg_change_params], pid, subscriber)\n      :sys.get_state(pid)\n\n      stub(Subscriptions, :delete_multi, fn _conn, _ids -> {:error, :delete_failed} end)\n\n      send(subscriber, :stop)\n      Process.sleep(100)\n\n      log =\n        capture_log(fn ->\n          send(pid, :check_delete_queue)\n          :sys.get_state(pid)\n        end)\n\n      assert log =~ \"SubscriptionDeletionFailed\"\n    end\n\n    test \"schedules next region check when rebalancer returns ok\", %{pid: pid} do\n      # In a single-node test environment, nodes are equal → Rebalancer returns :ok\n      current_nodes = MapSet.new(Node.list())\n      send(pid, {:check_region, current_nodes})\n      :sys.get_state(pid)\n\n      assert Process.alive?(pid)\n    end\n\n    test \"calls handle_stop when wrong region detected\", %{pid: pid} do\n      stub(Rebalancer, :check, fn _prev, _curr, _id -> {:error, :wrong_region} end)\n      stub(PostgresCdcRls, :handle_stop, fn _id, _timeout -> :ok end)\n\n      send(pid, {:check_region, MapSet.new()})\n      :sys.get_state(pid)\n\n      assert Process.alive?(pid)\n    end\n  end\n\n  test \"handles empty delete queue without crashing\", %{pid: pid} do\n    send(pid, :check_delete_queue)\n    state = :sys.get_state(pid)\n    assert :queue.is_empty(state.delete_queue.queue)\n  end\n\n  test \"handles unhandled messages without crashing\", %{pid: pid} do\n    state_before = :sys.get_state(pid)\n    send(pid, :totally_unexpected_message)\n    state_after = :sys.get_state(pid)\n    assert state_before.id == state_after.id\n  end\n\n  describe \"error handling\" do\n    setup :set_mimic_global\n\n    test \"stops cleanly when database connection fails\", %{args: args} do\n      stub(Database, :connect_db, fn _settings -> {:error, :econnrefused} end)\n\n      pid = start_supervised!({SubscriptionManager, args}, restart: :temporary)\n      ref = Process.monitor(pid)\n\n      assert_receive {:DOWN, ^ref, :process, ^pid, :econnrefused}, 1000\n    end\n  end\n\n  defp pg_change_params do\n    uuid = UUID.uuid1()\n\n    pg_change_params = %{\n      id: uuid,\n      subscription_params: {\"*\", \"public\", \"*\", []},\n      claims: %{\n        \"exp\" => System.system_time(:second) + 100_000,\n        \"iat\" => 0,\n        \"role\" => \"anon\"\n      }\n    }\n\n    {uuid, UUID.string_to_binary!(uuid), pg_change_params}\n  end\nend\n"
  },
  {
    "path": "test/realtime/extensions/cdc_rls/subscriptions_checker_distributed_test.exs",
    "content": "defmodule Realtime.Extensions.CdcRls.SubscriptionsCheckerDistributedTest do\n  # Usage of Clustered\n  use ExUnit.Case, async: false\n  import ExUnit.CaptureLog\n\n  alias Extensions.PostgresCdcRls.SubscriptionsChecker, as: Checker\n\n  setup do\n    {:ok, peer, remote_node} = Clustered.start_disconnected()\n    true = Node.connect(remote_node)\n    {:ok, peer: peer, remote_node: remote_node}\n  end\n\n  describe \"not_alive_pids_dist/1\" do\n    test \"returns empty list for all alive PIDs\", %{remote_node: remote_node} do\n      assert Checker.not_alive_pids_dist(%{}) == []\n\n      pid1 = spawn(fn -> Process.sleep(5000) end)\n      pid2 = spawn(fn -> Process.sleep(5000) end)\n      pid3 = spawn(fn -> Process.sleep(5000) end)\n      pid4 = Node.spawn(remote_node, Process, :sleep, [5000])\n\n      assert Checker.not_alive_pids_dist(%{node() => MapSet.new([pid1, pid2, pid3]), remote_node => MapSet.new([pid4])}) ==\n               []\n    end\n\n    test \"returns list of dead PIDs\", %{remote_node: remote_node} do\n      pid1 = spawn(fn -> Process.sleep(5000) end)\n      pid2 = spawn(fn -> Process.sleep(5000) end)\n      pid3 = spawn(fn -> Process.sleep(5000) end)\n      pid4 = Node.spawn(remote_node, Process, :sleep, [5000])\n      pid5 = Node.spawn(remote_node, Process, :sleep, [5000])\n\n      Process.exit(pid2, :kill)\n      Process.exit(pid5, :kill)\n\n      assert Checker.not_alive_pids_dist(%{\n               node() => MapSet.new([pid1, pid2, pid3]),\n               remote_node => MapSet.new([pid4, pid5])\n             }) == [pid2, pid5]\n    end\n\n    test \"handles rpc error\", %{remote_node: remote_node, peer: peer} do\n      pid1 = spawn(fn -> Process.sleep(5000) end)\n      pid2 = spawn(fn -> Process.sleep(5000) end)\n      pid3 = spawn(fn -> Process.sleep(5000) end)\n      pid4 = Node.spawn(remote_node, Process, :sleep, [5000])\n      pid5 = Node.spawn(remote_node, Process, :sleep, [5000])\n\n      Process.exit(pid2, :kill)\n\n      # Stop the other node\n      :peer.stop(peer)\n\n      log =\n        capture_log(fn ->\n          assert Checker.not_alive_pids_dist(%{\n                   node() => MapSet.new([pid1, pid2, pid3]),\n                   remote_node => MapSet.new([pid4, pid5])\n                 }) == [pid2]\n        end)\n\n      assert log =~ \"UnableToCheckProcessesOnRemoteNode\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/extensions/cdc_rls/subscriptions_checker_test.exs",
    "content": "defmodule Realtime.Extensions.PostgresCdcRl.SubscriptionsCheckerTest do\n  use Realtime.DataCase, async: false\n  use Mimic\n\n  setup :set_mimic_global\n\n  alias Extensions.PostgresCdcRls.SubscriptionsChecker, as: Checker\n  alias Realtime.Database\n  alias Realtime.GenRpc\n  import ExUnit.CaptureLog\n  import UUID, only: [uuid1: 0, string_to_binary!: 1]\n\n  test \"subscribers_by_node/1\" do\n    subscribers_pids_table = :ets.new(:table, [:public, :bag])\n\n    test_data = [\n      {:pid1, \"id1\", :ref, :node1},\n      {:pid1, \"id1.2\", :ref, :node1},\n      {:pid2, \"id2\", :ref, :node2}\n    ]\n\n    :ets.insert(subscribers_pids_table, test_data)\n\n    assert Checker.subscribers_by_node(subscribers_pids_table) == %{\n             node1: MapSet.new([:pid1]),\n             node2: MapSet.new([:pid2])\n           }\n  end\n\n  describe \"not_alive_pids/1\" do\n    test \"returns empty list for empty input\" do\n      assert Checker.not_alive_pids(MapSet.new()) == []\n    end\n\n    test \"returns empty list for all alive PIDs\" do\n      pid1 = spawn(fn -> Process.sleep(5000) end)\n      pid2 = spawn(fn -> Process.sleep(5000) end)\n      pid3 = spawn(fn -> Process.sleep(5000) end)\n      assert Checker.not_alive_pids(MapSet.new([pid1, pid2, pid3])) == []\n    end\n\n    test \"returns list of dead PIDs\" do\n      pid1 = spawn(fn -> Process.sleep(5000) end)\n      pid2 = spawn(fn -> Process.sleep(5000) end)\n      pid3 = spawn(fn -> Process.sleep(5000) end)\n      Process.exit(pid2, :kill)\n      assert Checker.not_alive_pids(MapSet.new([pid1, pid2, pid3])) == [pid2]\n    end\n  end\n\n  describe \"handle_continue connection failure\" do\n    test \"stops GenServer when database connection fails\" do\n      tenant = Containers.checkout_tenant(run_migrations: true)\n\n      stub(Database, :connect_db, fn _settings -> {:error, :econnrefused} end)\n\n      args =\n        hd(tenant.extensions).settings\n        |> Map.put(\"id\", tenant.external_id)\n        |> Map.put(\"subscribers_pids_table\", :ets.new(:table, [:public, :bag]))\n        |> Map.put(\"subscribers_nodes_table\", :ets.new(:table, [:public, :set]))\n\n      pid = start_supervised!({Checker, args}, restart: :temporary)\n      ref = Process.monitor(pid)\n\n      assert_receive {:DOWN, ^ref, :process, ^pid, :econnrefused}, 2000\n    end\n  end\n\n  describe \"pop_not_alive_pids/4\" do\n    test \"one subscription per channel\" do\n      subscribers_pids_table = :ets.new(:table, [:public, :bag])\n      subscribers_nodes_table = :ets.new(:table, [:public, :set])\n\n      uuid1 = uuid1()\n      uuid2 = uuid1()\n      uuid3 = uuid1()\n\n      pids_test_data = [\n        {:pid1, uuid1, :ref, :node1},\n        {:pid1, uuid2, :ref, :node1},\n        {:pid2, uuid3, :ref, :node2}\n      ]\n\n      :ets.insert(subscribers_pids_table, pids_test_data)\n\n      nodes_test_data = [\n        {string_to_binary!(uuid1), :node1},\n        {string_to_binary!(uuid2), :node1},\n        {string_to_binary!(uuid3), :node2}\n      ]\n\n      :ets.insert(subscribers_nodes_table, nodes_test_data)\n\n      not_alive = Enum.sort(Checker.pop_not_alive_pids([:pid1], subscribers_pids_table, subscribers_nodes_table, \"id\"))\n      expected = Enum.sort([string_to_binary!(uuid1), string_to_binary!(uuid2)])\n      assert not_alive == expected\n\n      assert :ets.tab2list(subscribers_pids_table) == [{:pid2, uuid3, :ref, :node2}]\n      assert :ets.tab2list(subscribers_nodes_table) == [{string_to_binary!(uuid3), :node2}]\n    end\n\n    test \"two subscriptions per channel\" do\n      subscribers_pids_table = :ets.new(:table, [:public, :bag])\n      subscribers_nodes_table = :ets.new(:table, [:public, :set])\n\n      uuid1 = uuid1()\n      uuid2 = uuid1()\n\n      test_data = [\n        {:pid1, uuid1, :ref, :node1},\n        {:pid2, uuid2, :ref, :node2}\n      ]\n\n      :ets.insert(subscribers_pids_table, test_data)\n\n      nodes_test_data = [\n        {string_to_binary!(uuid1), :node1},\n        {string_to_binary!(uuid2), :node2}\n      ]\n\n      :ets.insert(subscribers_nodes_table, nodes_test_data)\n\n      assert Checker.pop_not_alive_pids([:pid1], subscribers_pids_table, subscribers_nodes_table, \"id\") == [\n               string_to_binary!(uuid1)\n             ]\n\n      assert :ets.tab2list(subscribers_pids_table) == [{:pid2, uuid2, :ref, :node2}]\n      assert :ets.tab2list(subscribers_nodes_table) == [{string_to_binary!(uuid2), :node2}]\n    end\n\n    test \"returns empty list when pid not found in table\" do\n      subscribers_pids_table = :ets.new(:table, [:public, :bag])\n      subscribers_nodes_table = :ets.new(:table, [:public, :set])\n\n      assert Checker.pop_not_alive_pids(\n               [:nonexistent_pid],\n               subscribers_pids_table,\n               subscribers_nodes_table,\n               \"tenant_id\"\n             ) == []\n    end\n  end\n\n  describe \"not_alive_pids_dist/1\" do\n    test \"handles remote node RPC error gracefully\" do\n      remote_node = :some_remote@node\n\n      stub(GenRpc, :call, fn ^remote_node, Checker, :not_alive_pids, _pids, _opts ->\n        {:error, :rpc_error, :timeout}\n      end)\n\n      log =\n        capture_log(fn ->\n          result = Checker.not_alive_pids_dist(%{remote_node => MapSet.new([self()])})\n          assert result == []\n        end)\n\n      assert log =~ \"UnableToCheckProcessesOnRemoteNode\"\n    end\n\n    test \"returns pids from remote node when RPC succeeds\" do\n      remote_node = :some_remote@node\n      dead_pid = self()\n\n      stub(GenRpc, :call, fn ^remote_node, Checker, :not_alive_pids, [pids_set], _opts ->\n        MapSet.to_list(pids_set)\n      end)\n\n      result = Checker.not_alive_pids_dist(%{remote_node => MapSet.new([dead_pid])})\n      assert dead_pid in result\n    end\n\n    test \"checks local pids directly without RPC\" do\n      dead_pid = spawn(fn -> :ok end)\n      ref = Process.monitor(dead_pid)\n      receive do: ({:DOWN, ^ref, :process, ^dead_pid, _} -> :ok)\n\n      result = Checker.not_alive_pids_dist(%{node() => MapSet.new([dead_pid])})\n      assert dead_pid in result\n    end\n  end\n\n  describe \"GenServer handle_info integration\" do\n    setup do\n      tenant = Containers.checkout_tenant(run_migrations: true)\n      Realtime.Tenants.Cache.update_cache(tenant)\n\n      subscribers_pids_table = :ets.new(:sub_pids, [:public, :bag])\n      subscribers_nodes_table = :ets.new(:sub_nodes, [:public, :set])\n\n      args = %{\n        \"id\" => tenant.external_id,\n        \"subscribers_pids_table\" => subscribers_pids_table,\n        \"subscribers_nodes_table\" => subscribers_nodes_table\n      }\n\n      pid = start_link_supervised!({Checker, args})\n      :sys.get_state(pid)\n\n      %{pid: pid, subscribers_pids_table: subscribers_pids_table, subscribers_nodes_table: subscribers_nodes_table}\n    end\n\n    test \"check_active_pids adds dead pids to delete queue\", %{\n      pid: pid,\n      subscribers_pids_table: subscribers_pids_table,\n      subscribers_nodes_table: subscribers_nodes_table\n    } do\n      dead_pid = spawn(fn -> :ok end)\n      ref = Process.monitor(dead_pid)\n      receive do: ({:DOWN, ^ref, :process, ^dead_pid, _} -> :ok)\n      u = uuid1()\n      bin_u = string_to_binary!(u)\n\n      :ets.insert(subscribers_pids_table, {dead_pid, u, make_ref(), node()})\n      :ets.insert(subscribers_nodes_table, {bin_u, node()})\n\n      send(pid, :check_active_pids)\n      :sys.get_state(pid)\n\n      state = :sys.get_state(pid)\n      assert not :queue.is_empty(state.delete_queue.queue)\n    end\n\n    test \"check_delete_queue processes items in queue\", %{pid: pid} do\n      bin_id = string_to_binary!(uuid1())\n\n      :sys.replace_state(pid, fn state ->\n        %{state | delete_queue: %{ref: nil, queue: :queue.in(bin_id, :queue.new())}}\n      end)\n\n      send(pid, :check_delete_queue)\n      state = :sys.get_state(pid)\n\n      assert :queue.is_empty(state.delete_queue.queue)\n    end\n\n    test \"check_delete_queue logs error when deletion fails\", %{pid: pid} do\n      stub(Extensions.PostgresCdcRls.Subscriptions, :delete_multi, fn _conn, _ids ->\n        {:error, :deletion_failed}\n      end)\n\n      bin_id = string_to_binary!(uuid1())\n\n      :sys.replace_state(pid, fn state ->\n        %{state | delete_queue: %{ref: nil, queue: :queue.in(bin_id, :queue.new())}}\n      end)\n\n      log =\n        capture_log(fn ->\n          send(pid, :check_delete_queue)\n          :sys.get_state(pid)\n        end)\n\n      assert log =~ \"UnableToDeletePhantomSubscriptions\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/extensions/cdc_rls/subscriptions_test.exs",
    "content": "defmodule Realtime.Extensions.PostgresCdcRls.SubscriptionsTest do\n  use RealtimeWeb.ChannelCase, async: true\n\n  doctest Extensions.PostgresCdcRls.Subscriptions, import: true\n\n  import ExUnit.CaptureLog\n\n  alias Extensions.PostgresCdcRls.Subscriptions\n  alias Realtime.Database\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n\n    {:ok, conn} =\n      tenant\n      |> Database.from_tenant(\"realtime_rls\")\n      |> Map.from_struct()\n      |> Keyword.new()\n      |> Postgrex.start_link()\n\n    Integrations.setup_postgres_changes(conn)\n    Subscriptions.delete_all(conn)\n    assert %Postgrex.Result{rows: [[0]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n\n    %{conn: conn, tenant: tenant}\n  end\n\n  describe \"create/5\" do\n    test \"create all tables & all events\", %{conn: conn} do\n      {:ok, subscription_params} = Subscriptions.parse_subscription_params(%{\"event\" => \"*\", \"schema\" => \"public\"})\n      params_list = [%{claims: %{\"role\" => \"anon\"}, id: UUID.uuid1(), subscription_params: subscription_params}]\n\n      assert {:ok, [%Postgrex.Result{}]} =\n               Subscriptions.create(conn, \"supabase_realtime_test\", params_list, self(), self())\n\n      %Postgrex.Result{rows: [[[], \"*\"]]} =\n        Postgrex.query!(conn, \"select filters, action_filter from realtime.subscription\", [])\n    end\n\n    test \"create all tables & all events on INSERT\", %{conn: conn} do\n      {:ok, subscription_params} = Subscriptions.parse_subscription_params(%{\"event\" => \"INSERT\", \"schema\" => \"public\"})\n      params_list = [%{claims: %{\"role\" => \"anon\"}, id: UUID.uuid1(), subscription_params: subscription_params}]\n\n      assert {:ok, [%Postgrex.Result{}]} =\n               Subscriptions.create(conn, \"supabase_realtime_test\", params_list, self(), self())\n\n      %Postgrex.Result{rows: [[[], \"INSERT\"]]} =\n        Postgrex.query!(conn, \"select filters, action_filter from realtime.subscription\", [])\n    end\n\n    test \"create specific table all events\", %{conn: conn} do\n      {:ok, subscription_params} = Subscriptions.parse_subscription_params(%{\"schema\" => \"public\", \"table\" => \"test\"})\n\n      subscription_list = [%{claims: %{\"role\" => \"anon\"}, id: UUID.uuid1(), subscription_params: subscription_params}]\n\n      assert {:ok, [%Postgrex.Result{}]} =\n               Subscriptions.create(conn, \"supabase_realtime_test\", subscription_list, self(), self())\n\n      %Postgrex.Result{rows: [[1]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n    end\n\n    test \"publication does not exist\", %{conn: conn} do\n      {:ok, subscription_params} = Subscriptions.parse_subscription_params(%{\"schema\" => \"public\", \"table\" => \"test\"})\n\n      subscription_list = [%{claims: %{\"role\" => \"anon\"}, id: UUID.uuid1(), subscription_params: subscription_params}]\n\n      Postgrex.query!(conn, \"drop publication if exists supabase_realtime_test\", [])\n\n      assert {:error,\n              {:subscription_insert_failed,\n               \"Unable to subscribe to changes with given parameters. Please check Realtime is enabled for the given connect parameters: [event: *, schema: public, table: test, filters: []]\"}} =\n               Subscriptions.create(conn, \"supabase_realtime_test\", subscription_list, self(), self())\n\n      %Postgrex.Result{rows: [[0]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n    end\n\n    test \"table does not exist\", %{conn: conn} do\n      {:ok, subscription_params} =\n        Subscriptions.parse_subscription_params(%{\"schema\" => \"public\", \"table\" => \"doesnotexist\"})\n\n      subscription_list = [%{claims: %{\"role\" => \"anon\"}, id: UUID.uuid1(), subscription_params: subscription_params}]\n\n      assert {:error,\n              {:subscription_insert_failed,\n               \"Unable to subscribe to changes with given parameters. Please check Realtime is enabled for the given connect parameters: [event: *, schema: public, table: doesnotexist, filters: []]\"}} =\n               Subscriptions.create(conn, \"supabase_realtime_test\", subscription_list, self(), self())\n\n      %Postgrex.Result{rows: [[0]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n    end\n\n    test \"column does not exist\", %{conn: conn} do\n      {:ok, subscription_params} =\n        Subscriptions.parse_subscription_params(%{\n          \"schema\" => \"public\",\n          \"table\" => \"test\",\n          \"filter\" => \"subject=eq.hey\"\n        })\n\n      subscription_list = [%{claims: %{\"role\" => \"anon\"}, id: UUID.uuid1(), subscription_params: subscription_params}]\n\n      assert {:error,\n              {:subscription_insert_failed,\n               \"Unable to subscribe to changes with given parameters. An exception happened so please check your connect parameters: [event: *, schema: public, table: test, filters: [{\\\"subject\\\", \\\"eq\\\", \\\"hey\\\"}]]. Exception: ERROR P0001 (raise_exception) invalid column for filter subject\"}} =\n               Subscriptions.create(conn, \"supabase_realtime_test\", subscription_list, self(), self())\n\n      %Postgrex.Result{rows: [[0]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n    end\n\n    test \"column type is wrong\", %{conn: conn} do\n      {:ok, subscription_params} =\n        Subscriptions.parse_subscription_params(%{\n          \"schema\" => \"public\",\n          \"table\" => \"test\",\n          \"filter\" => \"id=eq.hey\"\n        })\n\n      subscription_list = [%{claims: %{\"role\" => \"anon\"}, id: UUID.uuid1(), subscription_params: subscription_params}]\n\n      assert {:error,\n              {:subscription_insert_failed,\n               \"Unable to subscribe to changes with given parameters. An exception happened so please check your connect parameters: [event: *, schema: public, table: test, filters: [{\\\"id\\\", \\\"eq\\\", \\\"hey\\\"}]]. Exception: ERROR 22P02 (invalid_text_representation) invalid input syntax for type integer: \\\"hey\\\"\"}} =\n               Subscriptions.create(conn, \"supabase_realtime_test\", subscription_list, self(), self())\n\n      %Postgrex.Result{rows: [[0]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n    end\n\n    test \"connection error\" do\n      {:ok, subscription_params} =\n        Subscriptions.parse_subscription_params(%{\"schema\" => \"public\", \"table\" => \"test\"})\n\n      subscription_list = [%{claims: %{\"role\" => \"anon\"}, id: UUID.uuid1(), subscription_params: subscription_params}]\n      conn = spawn(fn -> :ok end)\n\n      assert {:error, {:exit, _}} =\n               Subscriptions.create(conn, \"supabase_realtime_test\", subscription_list, self(), self())\n    end\n\n    test \"timeout\", %{conn: conn} do\n      {:ok, subscription_params} = Subscriptions.parse_subscription_params(%{\"schema\" => \"public\", \"table\" => \"test\"})\n\n      Task.start(fn -> Postgrex.query!(conn, \"SELECT pg_sleep(20)\", []) end)\n\n      subscription_list = [%{claims: %{\"role\" => \"anon\"}, id: UUID.uuid1(), subscription_params: subscription_params}]\n\n      assert {:error, %DBConnection.ConnectionError{reason: :queue_timeout}} =\n               Subscriptions.create(conn, \"supabase_realtime_test\", subscription_list, self(), self())\n    end\n\n    test \"invalid table\" do\n      {:error,\n       \"No subscription params provided. Please provide at least a `schema` or `table` to subscribe to: %{\\\"schema\\\" => \\\"public\\\", \\\"table\\\" => %{\\\"actually a\\\" => \\\"map\\\"}}\"} =\n        Subscriptions.parse_subscription_params(%{\"schema\" => \"public\", \"table\" => %{\"actually a\" => \"map\"}})\n    end\n\n    test \"invalid schema\" do\n      {:error,\n       \"No subscription params provided. Please provide at least a `schema` or `table` to subscribe to: %{\\\"schema\\\" => %{\\\"actually a\\\" => \\\"map\\\"}, \\\"table\\\" => \\\"images\\\"}\"} =\n        Subscriptions.parse_subscription_params(%{\"table\" => \"images\", \"schema\" => %{\"actually a\" => \"map\"}})\n    end\n\n    test \"invalid filter\" do\n      {:error,\n       \"No subscription params provided. Please provide at least a `schema` or `table` to subscribe to: %{\\\"filter\\\" => ~c\\\"{\\\", \\\"schema\\\" => \\\"public\\\", \\\"table\\\" => \\\"images\\\"}\"} =\n        Subscriptions.parse_subscription_params(%{\"schema\" => \"public\", \"table\" => \"images\", \"filter\" => [123]})\n    end\n  end\n\n  describe \"delete_all/1\" do\n    test \"delete_all\", %{conn: conn} do\n      create_subscriptions(conn, 10)\n      assert :ok = Subscriptions.delete_all(conn)\n      assert %Postgrex.Result{rows: [[0]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n    end\n\n    test \"returns ok when connection is unavailable\" do\n      conn = spawn(fn -> :ok end)\n      assert :ok = Subscriptions.delete_all(conn)\n    end\n\n    test \"logs error when subscription table is dropped\", %{conn: conn} do\n      Postgrex.query!(conn, \"drop table if exists realtime.subscription cascade\", [])\n\n      log = capture_log(fn -> Subscriptions.delete_all(conn) end)\n      assert log =~ \"SubscriptionDeletionFailed\"\n    end\n  end\n\n  describe \"delete/2\" do\n    test \"returns error when subscription table is dropped\", %{conn: conn} do\n      Postgrex.query!(conn, \"drop table if exists realtime.subscription cascade\", [])\n\n      assert {:error, %Postgrex.Error{}} = Subscriptions.delete(conn, UUID.string_to_binary!(UUID.uuid1()))\n    end\n\n    test \"delete\", %{conn: conn} do\n      id = UUID.uuid1()\n      bin_id = UUID.string_to_binary!(id)\n\n      {:ok, subscription_params} =\n        Subscriptions.parse_subscription_params(%{\n          \"schema\" => \"public\",\n          \"table\" => \"test\",\n          \"filter\" => \"id=eq.hey\"\n        })\n\n      subscription_list = [%{claims: %{\"role\" => \"anon\"}, id: id, subscription_params: subscription_params}]\n      Subscriptions.create(conn, \"supabase_realtime_test\", subscription_list, self(), self())\n\n      assert {:ok, %Postgrex.Result{}} = Subscriptions.delete(conn, bin_id)\n      assert %Postgrex.Result{rows: [[0]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n    end\n\n    test \"returns error when connection is unavailable\" do\n      conn = spawn(fn -> :ok end)\n      assert {:error, _} = Subscriptions.delete(conn, UUID.uuid1())\n    end\n  end\n\n  describe \"delete_multi/2\" do\n    test \"delete_multi\", %{conn: conn} do\n      Subscriptions.delete_all(conn)\n      id1 = UUID.uuid1()\n      id2 = UUID.uuid1()\n\n      bin_id2 = UUID.string_to_binary!(id2)\n      bin_id1 = UUID.string_to_binary!(id1)\n\n      {:ok, subscription_params} =\n        Subscriptions.parse_subscription_params(%{\n          \"schema\" => \"public\",\n          \"table\" => \"test\",\n          \"filter\" => \"id=eq.123\"\n        })\n\n      subscription_list = [\n        %{claims: %{\"role\" => \"anon\"}, id: id1, subscription_params: subscription_params},\n        %{claims: %{\"role\" => \"anon\"}, id: id2, subscription_params: subscription_params}\n      ]\n\n      assert {:ok, _} = Subscriptions.create(conn, \"supabase_realtime_test\", subscription_list, self(), self())\n\n      assert %Postgrex.Result{rows: [[2]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n      assert {:ok, %Postgrex.Result{}} = Subscriptions.delete_multi(conn, [bin_id1, bin_id2])\n      assert %Postgrex.Result{rows: [[0]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n    end\n  end\n\n  describe \"delete_all_if_table_exists/1\" do\n    test \"delete_all_if_table_exists\", %{conn: conn} do\n      Subscriptions.delete_all(conn)\n      create_subscriptions(conn, 10)\n\n      assert :ok = Subscriptions.delete_all_if_table_exists(conn)\n      assert %Postgrex.Result{rows: [[0]]} = Postgrex.query!(conn, \"select count(*) from realtime.subscription\", [])\n    end\n\n    test \"logs error when trigger raises on delete\", %{conn: conn, tenant: tenant} do\n      create_subscriptions(conn, 3)\n\n      Postgrex.query!(\n        conn,\n        \"\"\"\n          create or replace function realtime.evil_delete_trigger()\n          returns trigger language plpgsql as $$\n          begin raise exception 'evil trigger'; end;\n          $$;\n        \"\"\",\n        []\n      )\n\n      Postgrex.query!(\n        conn,\n        \"\"\"\n          create trigger evil_delete_trigger\n          before delete on realtime.subscription\n          for each row execute function realtime.evil_delete_trigger();\n        \"\"\",\n        []\n      )\n\n      on_exit(fn ->\n        {:ok, cleanup_conn} =\n          tenant\n          |> Database.from_tenant(\"realtime_rls\")\n          |> Map.from_struct()\n          |> Keyword.new()\n          |> Postgrex.start_link()\n\n        Postgrex.query(cleanup_conn, \"drop trigger if exists evil_delete_trigger on realtime.subscription\", [])\n        Postgrex.query(cleanup_conn, \"drop function if exists realtime.evil_delete_trigger()\", [])\n        GenServer.stop(cleanup_conn)\n      end)\n\n      log = capture_log(fn -> Subscriptions.delete_all_if_table_exists(conn) end)\n      assert log =~ \"SubscriptionCleanupFailed\"\n    end\n\n    test \"logs error when connection is dead\" do\n      conn = spawn(fn -> :ok end)\n      log = capture_log(fn -> Subscriptions.delete_all_if_table_exists(conn) end)\n      assert log =~ \"SubscriptionCleanupFailed\"\n    end\n  end\n\n  describe \"fetch_publication_tables/2\" do\n    test \"fetch_publication_tables\", %{conn: conn} do\n      tables = Subscriptions.fetch_publication_tables(conn, \"supabase_realtime_test\")\n      assert tables[{\"*\"}] != nil\n    end\n  end\n\n  defp create_subscriptions(conn, num) do\n    params_list =\n      Enum.reduce(1..num, [], fn _i, acc ->\n        [\n          %{\n            claims: %{\n              \"exp\" => 1_974_176_791,\n              \"iat\" => 1_658_600_791,\n              \"iss\" => \"supabase\",\n              \"ref\" => \"127.0.0.1\",\n              \"role\" => \"anon\"\n            },\n            id: UUID.uuid1(),\n            subscription_params: {\"*\", \"public\", \"*\", []}\n          }\n          | acc\n        ]\n      end)\n\n    Subscriptions.create(conn, \"supabase_realtime_test\", params_list, self(), self())\n  end\nend\n"
  },
  {
    "path": "test/realtime/gen_counter/gen_counter_test.exs",
    "content": "defmodule Realtime.GenCounterTest do\n  use Realtime.DataCase, async: true\n\n  alias Realtime.GenCounter\n\n  describe \"add/1\" do\n    test \"increments a counter\" do\n      term = {:domain, :metric, Ecto.UUID.generate()}\n      assert GenCounter.add(term) == 1\n      assert GenCounter.add(term) == 2\n\n      assert GenCounter.get(term) == 2\n    end\n  end\n\n  describe \"add/2\" do\n    test \"increments a counter by `count`\" do\n      term = {:domain, :metric, Ecto.UUID.generate()}\n      assert GenCounter.add(term, 10) == 10\n      assert GenCounter.get(term) == 10\n    end\n  end\n\n  describe \"reset/2\" do\n    test \"reset a counter returning the previous value\" do\n      term = {:domain, :metric, Ecto.UUID.generate()}\n      GenCounter.add(term, 10)\n      assert 10 == GenCounter.reset(term)\n      assert GenCounter.get(term) == 0\n      assert :ets.lookup(:gen_counter, term) == [{term, 0}]\n    end\n\n    test \"reset a counter returning the previous value when counter doesn't exist\" do\n      term = {:domain, :metric, Ecto.UUID.generate()}\n      assert 0 == GenCounter.reset(term)\n      assert GenCounter.get(term) == 0\n      assert :ets.lookup(:gen_counter, term) == []\n    end\n\n    test \"reset a counter returning the previous value when counter is 0\" do\n      term = {:domain, :metric, Ecto.UUID.generate()}\n      GenCounter.add(term, 0)\n      assert 0 == GenCounter.reset(term)\n      assert 0 == GenCounter.reset(term)\n      assert GenCounter.get(term) == 0\n      assert :ets.lookup(:gen_counter, term) == [{term, 0}]\n    end\n  end\n\n  describe \"delete/1\" do\n    test \"stops the child process the counter is linked to\" do\n      term = {:domain, :metric, Ecto.UUID.generate()}\n      GenCounter.add(term, 10)\n\n      assert GenCounter.delete(term) == :ok\n      # When a counter doesn't exist it returns 0\n      assert GenCounter.get(term) == 0\n    end\n  end\n\n  describe \"get/1\" do\n    test \"gets the count of a counter\" do\n      term = {:domain, :metric, Ecto.UUID.generate()}\n      assert GenCounter.get(term) == 0\n\n      GenCounter.add(term)\n\n      assert GenCounter.get(term) == 1\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/gen_rpc_pub_sub/worker_test.exs",
    "content": "defmodule Realtime.GenRpcPubSub.WorkerTest do\n  use ExUnit.Case, async: true\n  alias Realtime.GenRpcPubSub.Worker\n  alias Realtime.GenRpc\n  alias Realtime.Nodes\n\n  use Mimic\n\n  @topic \"test_topic\"\n\n  setup do\n    worker = start_link_supervised!({Worker, {Realtime.PubSub, __MODULE__}})\n    %{worker: worker}\n  end\n\n  describe \"forward to local\" do\n    test \"local broadcast\", %{worker: worker} do\n      :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, @topic)\n      send(worker, Worker.forward_to_local(@topic, \"le message\", Phoenix.PubSub))\n\n      assert_receive \"le message\"\n      refute_receive _any\n    end\n  end\n\n  describe \"forward to region\" do\n    setup %{worker: worker} do\n      GenRpc\n      |> stub()\n      |> allow(self(), worker)\n\n      Nodes\n      |> stub()\n      |> allow(self(), worker)\n\n      :ok\n    end\n\n    test \"local broadcast + forward to other nodes\", %{worker: worker} do\n      parent = self()\n      expect(Nodes, :region_nodes, fn \"us-east-1\" -> [node(), :node_us_2, :node_us_3] end)\n\n      expect(GenRpc, :abcast, fn [:node_us_2, :node_us_3],\n                                 Realtime.GenRpcPubSub.WorkerTest,\n                                 {:ftl, \"test_topic\", \"le message\", Phoenix.PubSub},\n                                 [] ->\n        send(parent, :abcast_called)\n        :ok\n      end)\n\n      :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, @topic)\n      send(worker, Worker.forward_to_region(@topic, \"le message\", Phoenix.PubSub))\n\n      assert_receive \"le message\"\n      assert_receive :abcast_called\n      refute_receive _any\n    end\n\n    test \"local broadcast and no other nodes\", %{worker: worker} do\n      expect(Nodes, :region_nodes, fn \"us-east-1\" -> [node()] end)\n\n      reject(GenRpc, :abcast, 4)\n\n      :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, @topic)\n      send(worker, Worker.forward_to_region(@topic, \"le message\", Phoenix.PubSub))\n\n      assert_receive \"le message\"\n      refute_receive _any\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/gen_rpc_pub_sub_test.exs",
    "content": "Application.put_env(:phoenix_pubsub, :test_adapter, {Realtime.GenRpcPubSub, []})\nCode.require_file(\"../../deps/phoenix_pubsub/test/shared/pubsub_test.exs\", __DIR__)\n\ndefmodule Realtime.GenRpcPubSubTest do\n  # Application env being changed\n  use ExUnit.Case, async: false\n\n  test \"it sets off_heap message_queue_data flag on the workers\" do\n    assert Realtime.PubSubElixir.Realtime.PubSub.Adapter_1\n           |> Process.whereis()\n           |> Process.info(:message_queue_data) == {:message_queue_data, :off_heap}\n  end\n\n  test \"it sets fullsweep_after flag on the workers\" do\n    assert Realtime.PubSubElixir.Realtime.PubSub.Adapter_1\n           |> Process.whereis()\n           |> Process.info(:fullsweep_after) == {:fullsweep_after, 20}\n  end\n\n  @aux_mod (quote do\n              defmodule Subscriber do\n                # Relay messages to testing node\n                def subscribe(subscriber, topic) do\n                  spawn(fn ->\n                    RealtimeWeb.Endpoint.subscribe(topic)\n                    2 = length(Realtime.Nodes.region_nodes(\"us-east-1\"))\n                    2 = length(Realtime.Nodes.region_nodes(\"ap-southeast-2\"))\n                    send(subscriber, {:ready, Application.get_env(:realtime, :region)})\n\n                    loop = fn f ->\n                      receive do\n                        msg -> send(subscriber, {:relay, node(), msg})\n                      end\n\n                      f.(f)\n                    end\n\n                    loop.(loop)\n                  end)\n                end\n              end\n            end)\n\n  Code.eval_quoted(@aux_mod)\n\n  @topic \"gen-rpc-pub-sub-test-topic\"\n\n  for regional_broadcasting <- [true, false] do\n    describe \"regional balancing = #{regional_broadcasting}\" do\n      setup do\n        previous_region = Application.get_env(:realtime, :region)\n        Application.put_env(:realtime, :region, \"us-east-1\")\n        on_exit(fn -> Application.put_env(:realtime, :region, previous_region) end)\n\n        previous_regional_broadcast = Application.get_env(:realtime, :regional_broadcasting)\n        Application.put_env(:realtime, :regional_broadcasting, unquote(regional_broadcasting))\n        on_exit(fn -> Application.put_env(:realtime, :regional_broadcasting, previous_regional_broadcast) end)\n\n        :ok\n      end\n\n      @describetag regional_broadcasting: regional_broadcasting\n\n      test \"all messages are received\" do\n        # start 1 node in us-east-1 to test my region broadcasting\n        # start 2 nodes in ap-southeast-2 to test other region broadcasting\n\n        us_node = :us_node\n        ap2_nodeX = :ap2_nodeX\n        ap2_nodeY = :ap2_nodeY\n\n        # Avoid port collision\n        gen_rpc_port = Application.fetch_env!(:gen_rpc, :tcp_server_port)\n\n        client_config_per_node = %{\n          node() => gen_rpc_port,\n          :\"#{us_node}@127.0.0.1\" => 16970,\n          :\"#{ap2_nodeX}@127.0.0.1\" => 16971,\n          :\"#{ap2_nodeY}@127.0.0.1\" => 16972\n        }\n\n        extra_config = [{:gen_rpc, :client_config_per_node, {:internal, client_config_per_node}}]\n\n        on_exit(fn -> Application.put_env(:gen_rpc, :client_config_per_node, {:internal, %{}}) end)\n        Application.put_env(:gen_rpc, :client_config_per_node, {:internal, client_config_per_node})\n\n        us_extra_config =\n          [{:realtime, :region, \"us-east-1\"}, {:gen_rpc, :tcp_server_port, 16970}] ++ extra_config\n\n        {:ok, _} = Clustered.start(@aux_mod, name: us_node, extra_config: us_extra_config, phoenix_port: 4014)\n\n        ap2_nodeX_extra_config =\n          [{:realtime, :region, \"ap-southeast-2\"}, {:gen_rpc, :tcp_server_port, 16971}] ++ extra_config\n\n        {:ok, _} = Clustered.start(@aux_mod, name: ap2_nodeX, extra_config: ap2_nodeX_extra_config, phoenix_port: 4015)\n\n        ap2_nodeY_extra_config =\n          [{:realtime, :region, \"ap-southeast-2\"}, {:gen_rpc, :tcp_server_port, 16972}] ++ extra_config\n\n        {:ok, _} = Clustered.start(@aux_mod, name: ap2_nodeY, extra_config: ap2_nodeY_extra_config, phoenix_port: 4016)\n\n        # Ensuring that syn had enough time to propagate to all nodes the group information\n        Process.sleep(3000)\n\n        RealtimeWeb.Endpoint.subscribe(@topic)\n        :erpc.multicall(Node.list(), Subscriber, :subscribe, [self(), @topic])\n\n        assert length(Realtime.Nodes.region_nodes(\"us-east-1\")) == 2\n        assert length(Realtime.Nodes.region_nodes(\"ap-southeast-2\")) == 2\n\n        assert_receive {:ready, \"us-east-1\"}\n        assert_receive {:ready, \"ap-southeast-2\"}\n        assert_receive {:ready, \"ap-southeast-2\"}\n\n        message = %Phoenix.Socket.Broadcast{topic: @topic, event: \"an event\", payload: [\"a\", %{\"b\" => \"c\"}, 1, 23]}\n        Phoenix.PubSub.broadcast(Realtime.PubSub, @topic, message)\n\n        assert_receive ^message\n\n        # Remote nodes received the broadcast\n        assert_receive {:relay, :\"us_node@127.0.0.1\", ^message}, 5000\n        assert_receive {:relay, :\"ap2_nodeX@127.0.0.1\", ^message}, 1000\n        assert_receive {:relay, :\"ap2_nodeY@127.0.0.1\", ^message}, 1000\n        refute_receive _any\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/gen_rpc_test.exs",
    "content": "defmodule Realtime.GenRpcTest do\n  # Async false due to Clustered usage\n  use ExUnit.Case, async: false\n\n  import ExUnit.CaptureLog\n\n  alias Realtime.GenRpc\n\n  setup context do\n    {:ok, node} = Clustered.start(nil, extra_config: context[:extra_config] || [])\n\n    %{node: node}\n  end\n\n  describe \"call/5\" do\n    setup do\n      :telemetry.attach(__MODULE__, [:realtime, :rpc], &__MODULE__.handle_telemetry/4, pid: self())\n      on_exit(fn -> :telemetry.detach(__MODULE__) end)\n    end\n\n    test \"returns the result calling local node\" do\n      current_node = node()\n\n      assert GenRpc.call(current_node, Map, :fetch, [%{a: 1}, :a], tenant_id: \"123\") == {:ok, 1}\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^current_node,\n                        success: true,\n                        mechanism: :gen_rpc\n                      }}\n    end\n\n    test \"returns the result with an error tuple calling local node\" do\n      current_node = node()\n\n      assert GenRpc.call(current_node, File, :open, [\"/not-existing.file\"], tenant_id: \"123\") == {:error, :enoent}\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^current_node,\n                        success: false,\n                        mechanism: :gen_rpc\n                      }}\n    end\n\n    test \"returns the result calling remote node\", %{node: node} do\n      current_node = node()\n      assert GenRpc.call(node, Map, :fetch, [%{a: 1}, :a], tenant_id: \"123\") == {:ok, 1}\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^node,\n                        success: true,\n                        mechanism: :gen_rpc\n                      }}\n    end\n\n    test \"returns the result with an error tuple calling remote node\", %{node: node} do\n      current_node = node()\n\n      assert GenRpc.call(node, File, :open, [\"/not-existing.file\"], tenant_id: \"123\") == {:error, :enoent}\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^node,\n                        success: false,\n                        mechanism: :gen_rpc\n                      }}\n    end\n\n    test \"local node timeout error\" do\n      current_node = node()\n\n      log =\n        capture_log(fn ->\n          assert GenRpc.call(current_node, Process, :sleep, [500], timeout: 100, tenant_id: 123) ==\n                   {:error, :rpc_error, :timeout}\n        end)\n\n      assert log =~\n               \"project=123 external_id=123 [error] ErrorOnRpcCall: %{error: :timeout, mod: Process, func: :sleep, target: :\\\"#{current_node}\\\"}\"\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^current_node,\n                        success: false,\n                        mechanism: :gen_rpc\n                      }}\n    end\n\n    test \"remote node timeout error\", %{node: node} do\n      current_node = node()\n\n      log =\n        capture_log(fn ->\n          assert GenRpc.call(node, Process, :sleep, [500], timeout: 100, tenant_id: 123) ==\n                   {:error, :rpc_error, :timeout}\n        end)\n\n      assert log =~\n               ~r/project=123 external_id=123 \\[error\\] ErrorOnRpcCall: %{\\s+error: :timeout,\\s+mod: Process,\\s+func: :sleep,\\s+target:\\s+:\"#{node}\"/\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^node,\n                        success: false,\n                        mechanism: :gen_rpc\n                      }}\n    end\n\n    test \"local node exception\" do\n      current_node = node()\n\n      assert {:error, :rpc_error, _} = GenRpc.call(current_node, Map, :fetch!, [%{}, :a], tenant_id: \"123\")\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^current_node,\n                        success: false,\n                        mechanism: :gen_rpc\n                      }}\n    end\n\n    test \"remote node exception\", %{node: node} do\n      current_node = node()\n\n      assert {:error, :rpc_error, _} = GenRpc.call(node, Map, :fetch!, [%{}, :a], tenant_id: \"123\")\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^node,\n                        success: false,\n                        mechanism: :gen_rpc\n                      }}\n    end\n\n    @tag extra_config: [{:gen_rpc, :tcp_server_port, 9999}]\n    test \"bad tcp error\", %{node: node} do\n      current_node = node()\n\n      log =\n        capture_log(fn ->\n          assert GenRpc.call(node, Map, :fetch, [%{a: 1}, :a], tenant_id: 123) == {:error, :rpc_error, :econnrefused}\n        end)\n\n      assert log =~\n               ~r/project=123 external_id=123 \\[error\\] ErrorOnRpcCall: %{\\s+error: :econnrefused,\\s+mod: Map,\\s+func: :fetch,\\s+target:\\s+:\"#{node}\"/\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^node,\n                        success: false,\n                        mechanism: :gen_rpc\n                      }}\n    end\n\n    test \"bad node\" do\n      node = :\"unknown@1.1.1.1\"\n\n      log =\n        capture_log(fn ->\n          assert GenRpc.call(node, Map, :fetch, [%{a: 1}, :a], tenant_id: 123) == {:error, :rpc_error, :badnode}\n        end)\n\n      assert log =~\n               ~r/project=123 external_id=123 \\[error\\] ErrorOnRpcCall: %{+error: :badnode, mod: Map, func: :fetch, target: :\"#{node}\"/\n    end\n  end\n\n  describe \"abcast/4\" do\n    test \"abcast to registered process\", %{node: node} do\n      name =\n        System.unique_integer()\n        |> to_string()\n        |> String.to_atom()\n\n      :erlang.register(name, self())\n\n      # Use erpc to make the other node abcast to this one\n      :erpc.call(node, GenRpc, :abcast, [[node()], name, \"a message\", []])\n\n      assert_receive \"a message\"\n      refute_receive _any\n    end\n\n    @tag extra_config: [{:gen_rpc, :tcp_server_port, 9999}]\n    test \"tcp error\" do\n      Logger.put_process_level(self(), :debug)\n\n      log =\n        capture_log(fn ->\n          assert GenRpc.abcast(Node.list(), :some_process_name, \"a message\", []) == :ok\n          # We have to wait for gen_rpc logs to show up\n          Process.sleep(100)\n        end)\n\n      assert log =~ \"[error] event=connect_to_remote_server\"\n\n      refute_receive _any\n    end\n  end\n\n  describe \"cast/5\" do\n    test \"apply on a local node\" do\n      parent = self()\n\n      assert GenRpc.cast(node(), Kernel, :send, [parent, :sent]) == :ok\n\n      assert_receive :sent\n      refute_receive _any\n    end\n\n    test \"apply on a remote node\", %{node: node} do\n      parent = self()\n\n      assert GenRpc.cast(node, Kernel, :send, [parent, :sent]) == :ok\n\n      assert_receive :sent\n      refute_receive _any\n    end\n\n    test \"bad node does nothing\" do\n      node = :\"unknown@1.1.1.1\"\n\n      parent = self()\n\n      assert GenRpc.cast(node, Kernel, :send, [parent, :sent]) == :ok\n\n      refute_receive _any\n    end\n\n    @tag extra_config: [{:gen_rpc, :tcp_server_port, 9999}]\n    test \"tcp error\", %{node: node} do\n      parent = self()\n      Logger.put_process_level(self(), :debug)\n\n      log =\n        capture_log(fn ->\n          assert GenRpc.cast(node, Kernel, :send, [parent, :sent]) == :ok\n          # We have to wait for gen_rpc logs to show up\n          Process.sleep(100)\n        end)\n\n      assert log =~ \"[error] event=connect_to_remote_server\"\n\n      refute_receive _any\n    end\n  end\n\n  describe \"multicast/4\" do\n    test \"evals everywhere\" do\n      parent = self()\n\n      assert GenRpc.multicast(Kernel, :send, [parent, :sent]) == :ok\n\n      assert_receive :sent\n      assert_receive :sent\n      refute_receive _any\n    end\n\n    @tag extra_config: [{:gen_rpc, :tcp_server_port, 9999}]\n    test \"tcp error\" do\n      parent = self()\n      Logger.put_process_level(self(), :debug)\n\n      log =\n        capture_log(fn ->\n          assert GenRpc.multicast(Kernel, :send, [parent, :sent]) == :ok\n          # We have to wait for gen_rpc logs to show up\n          Process.sleep(100)\n        end)\n\n      assert log =~ \"[error] event=connect_to_remote_server\"\n\n      assert_receive :sent\n      refute_receive _any\n    end\n  end\n\n  describe \"multicall/4\" do\n    setup do\n      :telemetry.attach(__MODULE__, [:realtime, :rpc], &__MODULE__.handle_telemetry/4, pid: self())\n      on_exit(fn -> :telemetry.detach(__MODULE__) end)\n    end\n\n    test \"returns the result of the function call per node\", %{node: node} do\n      current_node = node()\n\n      assert GenRpc.multicall(Map, :fetch, [%{a: 1}, :a], tenant_id: \"123\") == [\n               {current_node, {:ok, 1}},\n               {node, {:ok, 1}}\n             ]\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^node,\n                        success: true,\n                        mechanism: :gen_rpc\n                      }}\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^current_node,\n                        success: true,\n                        mechanism: :gen_rpc\n                      }}\n    end\n\n    test \"timeout error\", %{node: node} do\n      current_node = node()\n\n      log =\n        capture_log(fn ->\n          assert GenRpc.multicall(Process, :sleep, [500], timeout: 100, tenant_id: 123) == [\n                   {current_node, {:error, :rpc_error, :timeout}},\n                   {node, {:error, :rpc_error, :timeout}}\n                 ]\n        end)\n\n      assert log =~\n               \"project=123 external_id=123 [error] ErrorOnRpcCall: %{error: :timeout, mod: Process, func: :sleep, target: :\\\"#{current_node}\\\"}\"\n\n      assert log =~\n               ~r/project=123 external_id=123 \\[error\\] ErrorOnRpcCall: %{\\s+error: :timeout,\\s+mod: Process,\\s+func: :sleep,\\s+target:\\s+:\"#{node}\"/\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^node,\n                        success: false,\n                        mechanism: :gen_rpc\n                      }}\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^current_node,\n                        success: false,\n                        mechanism: :gen_rpc\n                      }}\n    end\n\n    @tag extra_config: [{:gen_rpc, :tcp_server_port, 9999}]\n    test \"partial results with bad tcp error\", %{node: node} do\n      current_node = node()\n\n      log =\n        capture_log(fn ->\n          assert GenRpc.multicall(Map, :fetch, [%{a: 1}, :a], tenant_id: 123) == [\n                   {node(), {:ok, 1}},\n                   {node, {:error, :rpc_error, :econnrefused}}\n                 ]\n        end)\n\n      assert log =~\n               ~r/project=123 external_id=123 \\[error\\] ErrorOnRpcCall: %{\\s+error: :econnrefused,\\s+mod: Map,\\s+func: :fetch,\\s+target:\\s+:\"#{node}\"/\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^node,\n                        success: false,\n                        mechanism: :gen_rpc\n                      }}\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        origin_node: ^current_node,\n                        target_node: ^current_node,\n                        success: true,\n                        mechanism: :gen_rpc\n                      }}\n    end\n  end\n\n  def handle_telemetry(event, measurements, metadata, pid: pid), do: send(pid, {event, measurements, metadata})\nend\n"
  },
  {
    "path": "test/realtime/helpers_test.exs",
    "content": "defmodule Realtime.HelpersTest do\n  use Realtime.DataCase\n  doctest Realtime.Helpers\nend\n"
  },
  {
    "path": "test/realtime/log_filter_test.exs",
    "content": "defmodule Realtime.LogFilterTest do\n  use ExUnit.Case, async: true\n\n  alias Realtime.LogFilter\n\n  describe \"filter/2 - gen_statem crash reports\" do\n    test \"stops DBConnection.ConnectionError crashes\" do\n      event = gen_statem_event(%DBConnection.ConnectionError{message: \"tcp connect: connection refused\"})\n      assert :stop = LogFilter.filter(event, [])\n    end\n\n    test \"passes through gen_statem crashes for other reasons\" do\n      event = gen_statem_event(:some_other_reason)\n      assert ^event = LogFilter.filter(event, [])\n    end\n\n    test \"passes through non-gen_statem reports\" do\n      event = %{msg: {:report, %{label: {:supervisor, :child_terminated}}}, meta: %{}}\n      assert ^event = LogFilter.filter(event, [])\n    end\n  end\n\n  describe \"filter/2 - DBConnection.Connection log calls\" do\n    test \"stops messages from DBConnection.Connection\" do\n      event = db_connection_log_event(\"Postgrex.Protocol failed to connect: connection refused\")\n      assert :stop = LogFilter.filter(event, [])\n    end\n\n    test \"passes through messages from other modules\" do\n      event = %{msg: {:string, \"some log\"}, meta: %{mfa: {SomeOtherModule, :some_fun, 1}}}\n      assert ^event = LogFilter.filter(event, [])\n    end\n\n    test \"passes through messages with no mfa metadata\" do\n      event = %{msg: {:string, \"some log\"}, meta: %{}}\n      assert ^event = LogFilter.filter(event, [])\n    end\n  end\n\n  describe \"filter/2 - Ranch connection killed reports\" do\n    test \"stops Ranch reports when connection was killed\" do\n      event = ranch_event(RealtimeWeb.Endpoint.HTTP, :cowboy_clear, self(), :killed)\n      assert :stop = LogFilter.filter(event, [])\n    end\n\n    test \"passes through Ranch reports when connection exited for other reasons\" do\n      event = ranch_event(RealtimeWeb.Endpoint.HTTP, :cowboy_clear, self(), :some_error)\n      assert ^event = LogFilter.filter(event, [])\n    end\n  end\n\n  describe \"setup/0\" do\n    test \"installs the primary filter\" do\n      LogFilter.setup()\n      %{filters: filters} = :logger.get_primary_config()\n      assert List.keymember?(filters, :connection_noise, 0)\n    end\n\n    test \"is idempotent when called multiple times\" do\n      LogFilter.setup()\n      assert :ok = LogFilter.setup()\n    end\n  end\n\n  defp gen_statem_event(reason) do\n    %{\n      msg: {:report, %{label: {:gen_statem, :terminate}, name: self(), reason: {:error, reason, []}}},\n      meta: %{pid: self(), time: System.system_time()}\n    }\n  end\n\n  @ranch_format \"Ranch listener ~p had connection process started with ~p:start_link/3 at ~p exit with reason: ~0p~n\"\n\n  defp ranch_event(ref, protocol, pid, reason) do\n    %{msg: {:format, @ranch_format, [ref, protocol, pid, reason]}, meta: %{pid: self()}}\n  end\n\n  defp db_connection_log_event(message) do\n    %{\n      msg: {:string, message},\n      meta: %{mfa: {DBConnection.Connection, :handle_event, 4}, pid: self()}\n    }\n  end\nend\n"
  },
  {
    "path": "test/realtime/logs_test.exs",
    "content": "defmodule Realtime.LogsTest do\n  use ExUnit.Case\n\n  import ExUnit.CaptureLog\n\n  alias Realtime.Logs\n\n  describe \"to_log/1\" do\n    test \"returns binary as-is\" do\n      assert Logs.to_log(\"hello\") == \"hello\"\n    end\n\n    test \"inspects non-binary values\" do\n      assert Logs.to_log(%{key: \"value\"}) == inspect(%{key: \"value\"}, pretty: true)\n      assert Logs.to_log(123) == \"123\"\n      assert Logs.to_log([:a, :b]) == inspect([:a, :b], pretty: true)\n    end\n  end\n\n  describe \"log_error/2\" do\n    test \"logs error with code and message\" do\n      defmodule LogErrorTest do\n        use Realtime.Logs\n\n        def do_log do\n          log_error(\"TestCode\", \"something broke\")\n        end\n      end\n\n      log = capture_log(fn -> LogErrorTest.do_log() end)\n      assert log =~ \"TestCode: something broke\"\n    end\n  end\n\n  describe \"log_warning/2\" do\n    test \"logs warning with code and message\" do\n      defmodule LogWarningTest do\n        use Realtime.Logs\n\n        def do_log do\n          log_warning(\"WarnCode\", \"something suspicious\")\n        end\n      end\n\n      log = capture_log(fn -> LogWarningTest.do_log() end)\n      assert log =~ \"WarnCode: something suspicious\"\n    end\n  end\n\n  describe \"Jason.Encoder implementation\" do\n    test \"encodes DBConnection.ConnectionError\" do\n      error = %DBConnection.ConnectionError{\n        message: \"connection lost\",\n        reason: :timeout,\n        severity: :error\n      }\n\n      encoded = Jason.encode!(error)\n      assert encoded =~ \"message: \\\"connection lost\\\"\"\n      assert encoded =~ \"reason: :timeout\"\n      assert encoded =~ \"severity: :error\"\n    end\n\n    test \"encodes Postgrex.Error\" do\n      error = %Postgrex.Error{\n        message: \"relation not found\",\n        postgres: %{\n          code: \"42P01\",\n          schema: \"public\",\n          table: \"users\"\n        }\n      }\n\n      encoded = Jason.encode!(error)\n      assert encoded =~ \"message: \\\"relation not found\\\"\"\n      assert encoded =~ \"schema: \\\"public\\\"\"\n      assert encoded =~ \"table: \\\"users\\\"\"\n      assert encoded =~ \"code: \\\"42P01\\\"\"\n    end\n\n    test \"encodes Tuple with error logging\" do\n      log =\n        capture_log(fn ->\n          encoded = Jason.encode!({:error, \"test\"})\n          assert encoded =~ \"error: \\\"unable to parse response\\\"\"\n        end)\n\n      assert log =~ \"UnableToEncodeJson\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/messages_test.exs",
    "content": "defmodule Realtime.MessagesTest do\n  # usage of Clustered\n  use Realtime.DataCase, async: false\n\n  alias Realtime.Api.Message\n  alias Realtime.Database\n  alias Realtime.Messages\n  alias Realtime.Tenants.Repo\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    {:ok, conn} = Database.connect(tenant, \"realtime_test\", :stop)\n\n    date_start = Date.utc_today() |> Date.add(-10)\n    date_end = Date.utc_today()\n    create_messages_partitions(conn, date_start, date_end)\n\n    on_exit(fn -> :telemetry.detach(__MODULE__) end)\n\n    :telemetry.attach(\n      __MODULE__,\n      [:realtime, :tenants, :replay],\n      &__MODULE__.handle_telemetry/4,\n      pid: self()\n    )\n\n    %{conn: conn, tenant: tenant, date_start: date_start, date_end: date_end}\n  end\n\n  describe \"replay/5\" do\n    test \"invalid replay params\", %{tenant: tenant} do\n      assert Messages.replay(self(), tenant.external_id, \"a topic\", \"not a number\", 123) ==\n               {:error, :invalid_replay_params}\n\n      assert Messages.replay(self(), tenant.external_id, \"a topic\", 123, \"not a number\") ==\n               {:error, :invalid_replay_params}\n\n      assert Messages.replay(self(), tenant.external_id, \"a topic\", 253_402_300_800_000, 10) ==\n               {:error, :invalid_replay_params}\n    end\n\n    test \"empty replay\", %{conn: conn} do\n      assert Messages.replay(conn, \"tenant_id\", \"test\", 0, 10) == {:ok, [], MapSet.new()}\n    end\n\n    test \"replay respects limit\", %{conn: conn, tenant: tenant} do\n      external_id = tenant.external_id\n\n      m1 =\n        message_fixture(tenant, %{\n          \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-1, :minute),\n          \"event\" => \"new\",\n          \"extension\" => \"broadcast\",\n          \"topic\" => \"test\",\n          \"private\" => true,\n          \"payload\" => %{\"value\" => \"new\"}\n        })\n\n      message_fixture(tenant, %{\n        \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-2, :minute),\n        \"event\" => \"old\",\n        \"extension\" => \"broadcast\",\n        \"topic\" => \"test\",\n        \"private\" => true,\n        \"payload\" => %{\"value\" => \"old\"}\n      })\n\n      assert Messages.replay(conn, external_id, \"test\", 0, 1) == {:ok, [m1], MapSet.new([m1.id])}\n\n      assert_receive {\n        :telemetry,\n        [:realtime, :tenants, :replay],\n        %{latency: _},\n        %{tenant: ^external_id}\n      }\n    end\n\n    test \"replay private topic only\", %{conn: conn, tenant: tenant} do\n      privatem =\n        message_fixture(tenant, %{\n          \"private\" => true,\n          \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-1, :minute),\n          \"event\" => \"new\",\n          \"extension\" => \"broadcast\",\n          \"topic\" => \"test\",\n          \"payload\" => %{\"value\" => \"new\"}\n        })\n\n      message_fixture(tenant, %{\n        \"private\" => false,\n        \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-2, :minute),\n        \"event\" => \"old\",\n        \"extension\" => \"broadcast\",\n        \"topic\" => \"test\",\n        \"payload\" => %{\"value\" => \"old\"}\n      })\n\n      assert Messages.replay(conn, tenant.external_id, \"test\", 0, 10) == {:ok, [privatem], MapSet.new([privatem.id])}\n    end\n\n    test \"replay extension=broadcast\", %{conn: conn, tenant: tenant} do\n      privatem =\n        message_fixture(tenant, %{\n          \"private\" => true,\n          \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-1, :minute),\n          \"event\" => \"new\",\n          \"extension\" => \"broadcast\",\n          \"topic\" => \"test\",\n          \"payload\" => %{\"value\" => \"new\"}\n        })\n\n      message_fixture(tenant, %{\n        \"private\" => true,\n        \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-2, :minute),\n        \"event\" => \"old\",\n        \"extension\" => \"presence\",\n        \"topic\" => \"test\",\n        \"payload\" => %{\"value\" => \"old\"}\n      })\n\n      assert Messages.replay(conn, tenant.external_id, \"test\", 0, 10) == {:ok, [privatem], MapSet.new([privatem.id])}\n    end\n\n    test \"replay respects since\", %{conn: conn, tenant: tenant} do\n      m1 =\n        message_fixture(tenant, %{\n          \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-2, :minute),\n          \"event\" => \"first\",\n          \"extension\" => \"broadcast\",\n          \"topic\" => \"test\",\n          \"private\" => true,\n          \"payload\" => %{\"value\" => \"first\"}\n        })\n\n      m2 =\n        message_fixture(tenant, %{\n          \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-1, :minute),\n          \"event\" => \"second\",\n          \"extension\" => \"broadcast\",\n          \"topic\" => \"test\",\n          \"private\" => true,\n          \"payload\" => %{\"value\" => \"second\"}\n        })\n\n      message_fixture(tenant, %{\n        \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-10, :minute),\n        \"event\" => \"old\",\n        \"extension\" => \"broadcast\",\n        \"topic\" => \"test\",\n        \"private\" => true,\n        \"payload\" => %{\"value\" => \"old\"}\n      })\n\n      since = DateTime.utc_now() |> DateTime.add(-3, :minute) |> DateTime.to_unix(:millisecond)\n\n      assert Messages.replay(conn, tenant.external_id, \"test\", since, 10) == {:ok, [m1, m2], MapSet.new([m1.id, m2.id])}\n    end\n\n    test \"replay respects hard max limit of 25\", %{conn: conn, tenant: tenant} do\n      for _i <- 1..30 do\n        message_fixture(tenant, %{\n          \"inserted_at\" => NaiveDateTime.utc_now(),\n          \"event\" => \"event\",\n          \"extension\" => \"broadcast\",\n          \"topic\" => \"test\",\n          \"private\" => true,\n          \"payload\" => %{\"value\" => \"message\"}\n        })\n      end\n\n      assert {:ok, messages, set} = Messages.replay(conn, tenant.external_id, \"test\", 0, 30)\n      assert length(messages) == 25\n      assert MapSet.size(set) == 25\n    end\n\n    test \"replay respects hard min limit of 1\", %{conn: conn, tenant: tenant} do\n      message_fixture(tenant, %{\n        \"inserted_at\" => NaiveDateTime.utc_now(),\n        \"event\" => \"event\",\n        \"extension\" => \"broadcast\",\n        \"topic\" => \"test\",\n        \"private\" => true,\n        \"payload\" => %{\"value\" => \"message\"}\n      })\n\n      assert {:ok, messages, set} = Messages.replay(conn, tenant.external_id, \"test\", 0, 0)\n      assert length(messages) == 1\n      assert MapSet.size(set) == 1\n    end\n\n    test \"distributed replay\", %{conn: conn, tenant: tenant} do\n      m =\n        message_fixture(tenant, %{\n          \"inserted_at\" => NaiveDateTime.utc_now(),\n          \"event\" => \"event\",\n          \"extension\" => \"broadcast\",\n          \"topic\" => \"test\",\n          \"private\" => true,\n          \"payload\" => %{\"value\" => \"message\"}\n        })\n\n      {:ok, node} = Clustered.start()\n\n      # Call remote node passing the database connection that is local to this node\n      assert :erpc.call(node, Messages, :replay, [conn, tenant.external_id, \"test\", 0, 30]) ==\n               {:ok, [m], MapSet.new([m.id])}\n    end\n\n    test \"distributed replay error\", %{tenant: tenant} do\n      message_fixture(tenant, %{\n        \"inserted_at\" => NaiveDateTime.utc_now(),\n        \"event\" => \"event\",\n        \"extension\" => \"broadcast\",\n        \"topic\" => \"test\",\n        \"private\" => true,\n        \"payload\" => %{\"value\" => \"message\"}\n      })\n\n      {:ok, node} = Clustered.start()\n\n      # Call remote node passing the database connection that is local to this node\n      pid = spawn(fn -> :ok end)\n\n      assert :erpc.call(node, Messages, :replay, [pid, tenant.external_id, \"test\", 0, 30]) ==\n               {:error, :failed_to_replay_messages}\n    end\n  end\n\n  describe \"delete_old_messages/1\" do\n    test \"delete_old_messages/1 deletes messages older than 72 hours\", %{\n      conn: conn,\n      tenant: tenant,\n      date_start: date_start,\n      date_end: date_end\n    } do\n      utc_now = NaiveDateTime.utc_now()\n      limit = NaiveDateTime.add(utc_now, -72, :hour)\n\n      messages =\n        for date <- Date.range(date_start, date_end) do\n          inserted_at = date |> NaiveDateTime.new!(Time.new!(0, 0, 0))\n          message_fixture(tenant, %{inserted_at: inserted_at})\n        end\n\n      assert length(messages) == 11\n\n      to_keep =\n        Enum.reject(\n          messages,\n          &(NaiveDateTime.compare(NaiveDateTime.beginning_of_day(limit), &1.inserted_at) == :gt)\n        )\n\n      assert :ok = Messages.delete_old_messages(conn)\n      {:ok, current} = Repo.all(conn, from(m in Message), Message)\n\n      assert Enum.sort(current) == Enum.sort(to_keep)\n    end\n  end\n\n  def handle_telemetry(event, measures, metadata, pid: pid), do: send(pid, {:telemetry, event, measures, metadata})\nend\n"
  },
  {
    "path": "test/realtime/metrics_cleaner_test.exs",
    "content": "defmodule Realtime.MetricsCleanerTest do\n  use Realtime.DataCase, async: true\n\n  alias Realtime.MetricsCleaner\n  alias Realtime.Tenants.Connect\n\n  describe \"metrics cleanup - vacant websockets\" do\n    test \"cleans up metrics for users that have been disconnected\" do\n      :telemetry.execute(\n        [:realtime, :connections],\n        %{connected: 1, connected_cluster: 10, limit: 100},\n        %{tenant: \"occupied-tenant\"}\n      )\n\n      :telemetry.execute(\n        [:realtime, :connections],\n        %{connected: 0, connected_cluster: 20, limit: 100},\n        %{tenant: \"vacant-tenant1\"}\n      )\n\n      :telemetry.execute(\n        [:realtime, :connections],\n        %{connected: 0, connected_cluster: 20, limit: 100},\n        %{tenant: \"vacant-tenant2\"}\n      )\n\n      pid1 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n      pid3 = spawn_link(fn -> Process.sleep(:infinity) end)\n\n      Beacon.join(:users, \"occupied-tenant\", pid1)\n      Beacon.join(:users, \"vacant-tenant1\", pid2)\n      Beacon.join(:users, \"vacant-tenant2\", pid3)\n\n      metrics = Realtime.TenantPromEx.get_metrics() |> IO.iodata_to_binary()\n\n      assert String.contains?(metrics, \"tenant=\\\"occupied-tenant\\\"\")\n      assert String.contains?(metrics, \"tenant=\\\"vacant-tenant1\\\"\")\n      assert String.contains?(metrics, \"tenant=\\\"vacant-tenant2\\\"\")\n\n      start_supervised!(\n        {MetricsCleaner, [metrics_cleaner_schedule_timer_in_ms: 100, vacant_metric_threshold_in_seconds: 1]}\n      )\n\n      # Now let's disconnect vacant tenants\n      Beacon.leave(:users, \"vacant-tenant1\", pid2)\n      Beacon.leave(:users, \"vacant-tenant2\", pid3)\n\n      # Wait for clean up to run\n      Process.sleep(200)\n\n      # Nothing changes yet (threshold not reached)\n      metrics = Realtime.TenantPromEx.get_metrics() |> IO.iodata_to_binary()\n\n      assert String.contains?(metrics, \"tenant=\\\"occupied-tenant\\\"\")\n      assert String.contains?(metrics, \"tenant=\\\"vacant-tenant1\\\"\")\n      assert String.contains?(metrics, \"tenant=\\\"vacant-tenant2\\\"\")\n\n      # Wait for threshold to pass and cleanup to run\n      Process.sleep(2200)\n\n      # vacant tenant metrics are now gone\n      metrics = Realtime.TenantPromEx.get_metrics() |> IO.iodata_to_binary()\n\n      assert String.contains?(metrics, \"tenant=\\\"occupied-tenant\\\"\")\n      refute String.contains?(metrics, \"tenant=\\\"vacant-tenant1\\\"\")\n      refute String.contains?(metrics, \"tenant=\\\"vacant-tenant2\\\"\")\n    end\n\n    test \"does not clean up metrics if websockets reconnect before threshold\" do\n      :telemetry.execute(\n        [:realtime, :connections],\n        %{connected: 1, connected_cluster: 10, limit: 100},\n        %{tenant: \"reconnect-tenant\"}\n      )\n\n      pid = spawn_link(fn -> Process.sleep(:infinity) end)\n\n      Beacon.join(:users, \"reconnect-tenant\", pid)\n\n      metrics = Realtime.TenantPromEx.get_metrics() |> IO.iodata_to_binary()\n      assert String.contains?(metrics, \"tenant=\\\"reconnect-tenant\\\"\")\n\n      start_supervised!(\n        {MetricsCleaner, [metrics_cleaner_schedule_timer_in_ms: 100, vacant_metric_threshold_in_seconds: 1]}\n      )\n\n      # Disconnect\n      Beacon.leave(:users, \"reconnect-tenant\", pid)\n      Process.sleep(500)\n\n      # Reconnect before threshold\n      pid2 = spawn_link(fn -> Process.sleep(:infinity) end)\n      Beacon.join(:users, \"reconnect-tenant\", pid2)\n\n      # Wait for cleanup to run\n      Process.sleep(2200)\n\n      # Metrics should still be present\n      metrics = Realtime.TenantPromEx.get_metrics() |> IO.iodata_to_binary()\n      assert String.contains?(metrics, \"tenant=\\\"reconnect-tenant\\\"\")\n    end\n  end\n\n  describe \"metrics cleanup - disconnected tenants\" do\n    test \"cleans up metrics for tenants that have been unregistered\" do\n      :telemetry.execute(\n        [:realtime, :connections],\n        %{connected: 1, connected_cluster: 10, limit: 100},\n        %{tenant: \"connected-tenant\"}\n      )\n\n      :telemetry.execute(\n        [:realtime, :connections],\n        %{connected: 0, connected_cluster: 20, limit: 100},\n        %{tenant: \"disconnected-tenant1\"}\n      )\n\n      :telemetry.execute(\n        [:realtime, :connections],\n        %{connected: 0, connected_cluster: 20, limit: 100},\n        %{tenant: \"disconnected-tenant2\"}\n      )\n\n      metrics = Realtime.TenantPromEx.get_metrics() |> IO.iodata_to_binary()\n\n      assert String.contains?(metrics, \"tenant=\\\"connected-tenant\\\"\")\n      assert String.contains?(metrics, \"tenant=\\\"disconnected-tenant1\\\"\")\n      assert String.contains?(metrics, \"tenant=\\\"disconnected-tenant2\\\"\")\n\n      start_supervised!(\n        {MetricsCleaner, [metrics_cleaner_schedule_timer_in_ms: 100, vacant_metric_threshold_in_seconds: 1]}\n      )\n\n      # Simulate tenant registration (connected)\n      :telemetry.execute([:syn, Connect, :registered], %{}, %{name: \"connected-tenant\"})\n\n      # Simulate tenant unregistration (disconnected)\n      :telemetry.execute([:syn, Connect, :unregistered], %{}, %{name: \"disconnected-tenant1\"})\n      :telemetry.execute([:syn, Connect, :unregistered], %{}, %{name: \"disconnected-tenant2\"})\n\n      # Wait for clean up to run\n      Process.sleep(200)\n\n      # Nothing changes yet (threshold not reached)\n      metrics = Realtime.TenantPromEx.get_metrics() |> IO.iodata_to_binary()\n\n      assert String.contains?(metrics, \"tenant=\\\"connected-tenant\\\"\")\n      assert String.contains?(metrics, \"tenant=\\\"disconnected-tenant1\\\"\")\n      assert String.contains?(metrics, \"tenant=\\\"disconnected-tenant2\\\"\")\n\n      # Wait for threshold to pass and cleanup to run\n      Process.sleep(2200)\n\n      # disconnected tenant metrics are now gone\n      metrics = Realtime.TenantPromEx.get_metrics() |> IO.iodata_to_binary()\n\n      assert String.contains?(metrics, \"tenant=\\\"connected-tenant\\\"\")\n      refute String.contains?(metrics, \"tenant=\\\"disconnected-tenant1\\\"\")\n      refute String.contains?(metrics, \"tenant=\\\"disconnected-tenant2\\\"\")\n    end\n\n    test \"does not clean up metrics if tenant reconnects before threshold\" do\n      :telemetry.execute(\n        [:realtime, :connections],\n        %{connected: 1, connected_cluster: 10, limit: 100},\n        %{tenant: \"reconnect-tenant\"}\n      )\n\n      metrics = Realtime.TenantPromEx.get_metrics() |> IO.iodata_to_binary()\n      assert String.contains?(metrics, \"tenant=\\\"reconnect-tenant\\\"\")\n\n      start_supervised!(\n        {MetricsCleaner, [metrics_cleaner_schedule_timer_in_ms: 100, vacant_metric_threshold_in_seconds: 1]}\n      )\n\n      # Simulate tenant unregistration\n      :telemetry.execute([:syn, Connect, :unregistered], %{}, %{name: \"reconnect-tenant\"})\n      Process.sleep(500)\n\n      # Re-register before threshold\n      :telemetry.execute([:syn, Connect, :registered], %{}, %{name: \"reconnect-tenant\"})\n\n      # Wait for cleanup to run\n      Process.sleep(2200)\n\n      # Metrics should still be present\n      metrics = Realtime.TenantPromEx.get_metrics() |> IO.iodata_to_binary()\n      assert String.contains?(metrics, \"tenant=\\\"reconnect-tenant\\\"\")\n    end\n  end\n\n  describe \"handle_info/2 unexpected message\" do\n    test \"logs error for unexpected messages\" do\n      import ExUnit.CaptureLog\n\n      pid =\n        start_supervised!(\n          {MetricsCleaner, [metrics_cleaner_schedule_timer_in_ms: 60_000, vacant_metric_threshold_in_seconds: 600]}\n        )\n\n      log =\n        capture_log(fn ->\n          send(pid, :something_unexpected)\n          Process.sleep(100)\n        end)\n\n      assert log =~ \"Unexpected message\"\n      assert log =~ \"something_unexpected\"\n    end\n  end\n\n  describe \"handle_beacon_event/4\" do\n    test \"inserts and deletes from ETS table\" do\n      table = :ets.new(:test_beacon, [:set, :public])\n\n      MetricsCleaner.handle_beacon_event(\n        [:beacon, :users, :group, :vacant],\n        %{},\n        %{group: \"test-tenant\"},\n        table\n      )\n\n      assert [{\"test-tenant\", _timestamp}] = :ets.lookup(table, \"test-tenant\")\n\n      MetricsCleaner.handle_beacon_event(\n        [:beacon, :users, :group, :occupied],\n        %{},\n        %{group: \"test-tenant\"},\n        table\n      )\n\n      assert [] = :ets.lookup(table, \"test-tenant\")\n    end\n  end\n\n  describe \"handle_syn_event/4\" do\n    test \"inserts and deletes from ETS table\" do\n      table = :ets.new(:test_syn, [:set, :public])\n\n      MetricsCleaner.handle_syn_event(\n        [:syn, Connect, :unregistered],\n        %{},\n        %{name: \"test-tenant\"},\n        table\n      )\n\n      assert [{\"test-tenant\", _timestamp}] = :ets.lookup(table, \"test-tenant\")\n\n      MetricsCleaner.handle_syn_event(\n        [:syn, Connect, :registered],\n        %{},\n        %{name: \"test-tenant\"},\n        table\n      )\n\n      assert [] = :ets.lookup(table, \"test-tenant\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/metrics_pusher_test.exs",
    "content": "defmodule Realtime.MetricsPusherTest do\n  use Realtime.DataCase, async: true\n  import ExUnit.CaptureLog\n\n  alias Realtime.MetricsPusher\n  alias Plug.Conn\n\n  setup {Req.Test, :verify_on_exit!}\n\n  # Helper function to start MetricsPusher and allow it to use Req.Test\n  defp start_and_allow_pusher(opts) do\n    pid = start_supervised!({MetricsPusher, opts})\n    Req.Test.allow(MetricsPusher, self(), pid)\n    {:ok, pid}\n  end\n\n  describe \"start_link/1\" do\n    test \"does not start when URL is missing\" do\n      opts = [enabled: true]\n      assert :ignore = MetricsPusher.start_link(opts)\n    end\n\n    test \"sends request successfully\" do\n      opts = [\n        url: \"https://example.com:8428/api/v1/import/prometheus\",\n        user: \"realtime\",\n        auth: \"hunter2\",\n        compress: true,\n        interval: 10,\n        timeout: 5000\n      ]\n\n      parent = self()\n\n      Req.Test.expect(MetricsPusher, fn conn ->\n        body = Req.Test.raw_body(conn)\n        assert conn.method == \"POST\"\n        assert :zlib.gunzip(body) =~ \"# HELP beam_stats_run_queue_count\"\n        assert conn.scheme == :https\n        assert conn.host == \"example.com\"\n        assert conn.port == 8428\n        assert conn.request_path == \"/api/v1/import/prometheus\"\n        assert Conn.get_req_header(conn, \"authorization\") == [\"Basic #{Base.encode64(\"realtime:hunter2\")}\"]\n        assert Conn.get_req_header(conn, \"content-encoding\") == [\"gzip\"]\n        assert Conn.get_req_header(conn, \"content-type\") == [\"text/plain\"]\n\n        send(parent, :req_called)\n        Req.Test.text(conn, \"\")\n      end)\n\n      {:ok, _pid} = start_and_allow_pusher(opts)\n      assert_receive :req_called, 100\n    end\n\n    test \"sends request successfully without auth header\" do\n      opts = [\n        url: \"http://localhost:8428/api/v1/import/prometheus\",\n        compress: true,\n        interval: 10,\n        timeout: 5000\n      ]\n\n      parent = self()\n\n      Req.Test.expect(MetricsPusher, fn conn ->\n        body = Req.Test.raw_body(conn)\n        assert :zlib.gunzip(body) =~ \"# HELP beam_stats_run_queue_count\"\n        assert Conn.get_req_header(conn, \"authorization\") == []\n\n        send(parent, :req_called)\n        Req.Test.text(conn, \"\")\n      end)\n\n      {:ok, _pid} = start_and_allow_pusher(opts)\n      assert_receive :req_called, 100\n    end\n\n    test \"sends request body untouched when compress=false\" do\n      opts = [\n        url: \"http://localhost:8428/api/v1/import/prometheus\",\n        user: \"hunter2\",\n        auth: \"realtime\",\n        compress: false,\n        interval: 10,\n        timeout: 5000\n      ]\n\n      parent = self()\n\n      Req.Test.expect(MetricsPusher, fn conn ->\n        body = Req.Test.raw_body(conn)\n        assert body =~ \"# HELP beam_stats_run_queue_count\"\n        assert Conn.get_req_header(conn, \"content-encoding\") == []\n        assert Conn.get_req_header(conn, \"content-type\") == [\"text/plain\"]\n\n        send(parent, :req_called)\n        Req.Test.text(conn, \"\")\n      end)\n\n      {:ok, _pid} = start_and_allow_pusher(opts)\n      assert_receive :req_called, 100\n    end\n\n    test \"when request receives non 2XX response\" do\n      opts = [\n        url: \"https://example.com:8428/api/v1/import/prometheus\",\n        auth: \"hunter2\",\n        compress: true,\n        interval: 10,\n        timeout: 5000\n      ]\n\n      parent = self()\n\n      log =\n        capture_log(fn ->\n          Req.Test.expect(MetricsPusher, fn conn ->\n            send(parent, :req_called)\n            Conn.send_resp(conn, 500, \"\")\n          end)\n\n          {:ok, pid} = start_and_allow_pusher(opts)\n          assert_receive :req_called, 100\n          assert Process.alive?(pid)\n          # Wait enough for the log to be captured\n          Process.sleep(100)\n        end)\n\n      assert log =~ \"MetricsPusher: Failed to push metrics to\"\n    end\n\n    test \"when an error is raised\" do\n      opts = [\n        url: \"https://example.com:8428/api/v1/import/prometheus\",\n        interval: 10,\n        timeout: 5000\n      ]\n\n      parent = self()\n\n      log =\n        capture_log(fn ->\n          Req.Test.expect(MetricsPusher, fn _conn ->\n            send(parent, :req_called)\n            raise RuntimeError, \"unexpected error\"\n          end)\n\n          {:ok, pid} = start_and_allow_pusher(opts)\n          assert_receive :req_called, 100\n          assert Process.alive?(pid)\n          # Wait enough for the log to be captured\n          Process.sleep(100)\n        end)\n\n      assert log =~ \"MetricsPusher: Exception during push: %RuntimeError{message: \\\"unexpected error\\\"}\"\n    end\n\n    test \"logs unexpected messages and stays alive\" do\n      parent = self()\n\n      Req.Test.expect(MetricsPusher, fn conn ->\n        send(parent, :push_happened)\n        Req.Test.text(conn, \"\")\n      end)\n\n      {:ok, pid} =\n        start_and_allow_pusher(\n          url: \"http://localhost:8428/api/v1/import/prometheus\",\n          interval: 10,\n          timeout: 5000\n        )\n\n      assert_receive :push_happened, 500\n\n      log =\n        capture_log(fn ->\n          send(pid, :unexpected_message)\n          Process.sleep(50)\n          assert Process.alive?(pid)\n        end)\n\n      assert log =~ \"MetricsPusher received unexpected message: :unexpected_message\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/monitoring/distributed_metrics_test.exs",
    "content": "defmodule Realtime.DistributedMetricsTest do\n  # Async false due to Clustered usage\n  use ExUnit.Case, async: false\n\n  alias Realtime.DistributedMetrics\n\n  setup_all do\n    {:ok, node} = Clustered.start()\n    %{node: node}\n  end\n\n  describe \"info/0 while connected\" do\n    test \"per node metric\", %{node: node} do\n      assert %{\n               ^node => %{\n                 pid: _pid,\n                 port: _port,\n                 queue_size: {:ok, _},\n                 state: :up,\n                 inet_stats: [\n                   recv_oct: _,\n                   recv_cnt: _,\n                   recv_max: _,\n                   recv_avg: _,\n                   recv_dvi: _,\n                   send_oct: _,\n                   send_cnt: _,\n                   send_max: _,\n                   send_avg: _,\n                   send_pend: _\n                 ]\n               }\n             } = DistributedMetrics.info()\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/monitoring/erl_sys_mon_test.exs",
    "content": "defmodule Realtime.Monitoring.ErlSysMonTest do\n  use ExUnit.Case, async: true\n  import ExUnit.CaptureLog\n  alias Realtime.ErlSysMon\n\n  describe \"system monitoring\" do\n    test \"logs system monitor events\" do\n      start_supervised!({ErlSysMon, config: [{:long_message_queue, {1, 100}}]})\n\n      log =\n        capture_log(fn ->\n          Task.async(fn ->\n            Process.register(self(), TestProcess)\n            Enum.map(1..1000, &send(self(), &1))\n            # Wait  for ErlSysMon to notice\n            Process.sleep(4000)\n          end)\n          |> Task.await()\n        end)\n\n      assert log =~ \"Realtime.ErlSysMon message:\"\n      assert log =~ \"$initial_call\\\", {Realtime.Monitoring.ErlSysMonTest\"\n      assert log =~ \"ancestors\\\", [#{inspect(self())}]\"\n      assert log =~ \"registered_name: TestProcess\"\n      assert log =~ \"message_queue_len: \"\n      assert log =~ \"total_heap_size: \"\n    end\n\n    test \"logs non-pid monitor messages\" do\n      {:ok, pid} = ErlSysMon.start_link(config: [])\n\n      log =\n        capture_log(fn ->\n          send(pid, {:unexpected, \"message\"})\n          Process.sleep(100)\n        end)\n\n      assert log =~ \"Realtime.ErlSysMon message:\"\n      assert log =~ \"unexpected\"\n    end\n\n    test \"handles monitor event for dead process without crashing\" do\n      {:ok, pid} = ErlSysMon.start_link(config: [])\n\n      dead_pid = spawn(fn -> :ok end)\n      Process.sleep(50)\n\n      log =\n        capture_log(fn ->\n          send(pid, {:monitor, dead_pid, :long_gc, %{timeout: 500}})\n          Process.sleep(100)\n        end)\n\n      assert log =~ \"Realtime.ErlSysMon message:\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/monitoring/gen_rpc_metrics_test.exs",
    "content": "defmodule Realtime.GenRpcMetricsTest do\n  # Async false due to Clustered usage\n  use ExUnit.Case, async: false\n\n  alias Realtime.GenRpcMetrics\n\n  setup_all do\n    {:ok, node} = Clustered.start()\n    %{node: node}\n  end\n\n  describe \"info/0 while connected\" do\n    test \"per node metric\", %{node: node} do\n      # We need to generate some load on gen_rpc first\n      Realtime.GenRpc.call(node, String, :to_integer, [\"25\"], key: 1)\n      Realtime.GenRpc.call(node, String, :to_integer, [\"25\"], key: 2)\n\n      assert %{\n               ^node => %{\n                 connections: _,\n                 queue_size: _,\n                 inet_stats: %{\n                   recv_oct: _,\n                   recv_cnt: _,\n                   recv_max: _,\n                   recv_avg: _,\n                   recv_dvi: _,\n                   send_oct: _,\n                   send_cnt: _,\n                   send_max: _,\n                   send_avg: _,\n                   send_pend: _\n                 }\n               }\n             } = GenRpcMetrics.info()\n    end\n\n    test \"metric matches on both sides\", %{node: node} do\n      # We need to generate some load on gen_rpc first\n      Realtime.GenRpc.call(node, String, :to_integer, [\"25\"], key: 1)\n      Realtime.GenRpc.call(node, String, :to_integer, [\"25\"], key: 2)\n\n      :erpc.call(node, Realtime.GenRpc, :call, [node(), String, :to_integer, [\"25\"], [key: 1]])\n\n      local_metrics = GenRpcMetrics.info()[node][:inet_stats]\n      remote_metrics = :erpc.call(node, GenRpcMetrics, :info, [])[node()][:inet_stats]\n\n      assert Map.keys(local_metrics) == [\n               :send_pend,\n               :recv_avg,\n               :recv_cnt,\n               :recv_dvi,\n               :recv_max,\n               :recv_oct,\n               :send_avg,\n               :send_cnt,\n               :send_max,\n               :send_oct\n             ]\n\n      assert local_metrics[:connections] == remote_metrics[:connections]\n\n      assert_in_delta local_metrics[:send_avg], remote_metrics[:recv_avg], 200\n      assert_in_delta local_metrics[:recv_avg], remote_metrics[:send_avg], 200\n\n      assert local_metrics[:send_oct] == remote_metrics[:recv_oct]\n      assert local_metrics[:recv_oct] == remote_metrics[:send_oct]\n\n      assert local_metrics[:send_cnt] == remote_metrics[:recv_cnt]\n      assert local_metrics[:recv_cnt] == remote_metrics[:send_cnt]\n\n      assert local_metrics[:send_max] == remote_metrics[:recv_max]\n      assert local_metrics[:recv_max] == remote_metrics[:send_max]\n\n      assert local_metrics[:send_max] == remote_metrics[:recv_max]\n      assert local_metrics[:recv_max] == remote_metrics[:send_max]\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/monitoring/latency_test.exs",
    "content": "defmodule Realtime.LatencyTest do\n  # async: false due to the usage of Clustered mode that interacts with this tests and breaks their expectations\n  use Realtime.DataCase, async: false\n  alias Realtime.Latency\n\n  describe \"pong/0\" do\n    test \"returns pong with region\" do\n      assert {:ok, {:pong, region}} = Latency.pong()\n      assert is_binary(region)\n    end\n  end\n\n  describe \"pong/1\" do\n    test \"returns pong after sleeping for the given latency\" do\n      assert {:ok, {:pong, _region}} = Latency.pong(0)\n    end\n  end\n\n  describe \"handle_info/2\" do\n    test \"unexpected message does not crash the server\" do\n      pid = Process.whereis(Latency)\n      send(pid, :unexpected_message)\n      assert Process.alive?(pid)\n    end\n  end\n\n  describe \"handle_cast/2\" do\n    test \"ping cast triggers a ping and does not crash\" do\n      pid = Process.whereis(Latency)\n      GenServer.cast(pid, {:ping, 0, 5_000, 5_000})\n      assert Process.alive?(pid)\n    end\n  end\n\n  describe \"ping/3\" do\n    setup do\n      for node <- Node.list(), do: Node.disconnect(node)\n      :ok\n    end\n\n    test \"returns pong from healthy remote node\" do\n      {:ok, _node} = Clustered.start()\n      results = Latency.ping()\n      assert Enum.all?(results, fn {%Task{}, result} -> match?({:ok, %{response: {:ok, {:pong, _}}}}, result) end)\n    end\n\n    test \"returns pong from slow but healthy remote node\" do\n      {:ok, _node} = Clustered.start()\n      results = Latency.ping(100, 10_000, 30_000)\n      assert Enum.all?(results, fn {%Task{}, result} -> match?({:ok, %{response: {:ok, {:pong, _}}}}, result) end)\n    end\n\n    test \"returns error when remote node exceeds timer timeout\" do\n      assert [{%Task{}, {:ok, %{response: {:error, :rpc_error, _}}}}] = Latency.ping(500, 100)\n    end\n\n    test \"returns nil when task does not yield before yield timeout\" do\n      assert [{%Task{}, nil}] = Latency.ping(1_000, 500, 100)\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/monitoring/peep/partitioned_test.exs",
    "content": "Application.put_env(:peep, :test_storages, [\n  {Realtime.Monitoring.Peep.Partitioned, 3},\n  {Realtime.Monitoring.Peep.Partitioned, 1}\n])\n\nCode.require_file(\"../../../../deps/peep/test/shared/storage_test.exs\", __DIR__)\n"
  },
  {
    "path": "test/realtime/monitoring/prom_ex/plugins/distributed_test.exs",
    "content": "defmodule Realtime.PromEx.Plugins.DistributedTest do\n  # Async false due to Clustered usage\n  use ExUnit.Case, async: false\n  alias Realtime.PromEx.Plugins\n\n  defmodule MetricsTest do\n    use PromEx, otp_app: :metrics_test\n    @impl true\n    def plugins do\n      [{Plugins.Distributed, poll_rate: 100}]\n    end\n  end\n\n  setup_all do\n    {:ok, node} = Clustered.start()\n    start_supervised!(MetricsTest)\n    # Send some data back and forth\n    25 = :erpc.call(node, String, :to_integer, [\"25\"])\n    # Wait for MetricsTest to fetch metrics\n    Process.sleep(200)\n    %{node: node}\n  end\n\n  describe \"pooling metrics\" do\n    setup do\n      %{metrics: PromEx.get_metrics(MetricsTest)}\n    end\n\n    test \"send_pending_bytes\", %{metrics: metrics, node: node} do\n      assert metric_value(metrics, \"dist_send_pending_bytes\", origin_node: node(), target_node: node) == 0\n    end\n\n    test \"send_count\", %{metrics: metrics, node: node} do\n      value = metric_value(metrics, \"dist_send_count\", origin_node: node(), target_node: node)\n      assert is_integer(value)\n      assert value > 0\n    end\n\n    test \"send_bytes\", %{metrics: metrics, node: node} do\n      value = metric_value(metrics, \"dist_send_bytes\", origin_node: node(), target_node: node)\n      assert is_integer(value)\n      assert value > 0\n    end\n\n    test \"recv_count\", %{metrics: metrics, node: node} do\n      value = metric_value(metrics, \"dist_recv_count\", origin_node: node(), target_node: node)\n      assert is_integer(value)\n      assert value > 0\n    end\n\n    test \"recv_bytes\", %{metrics: metrics, node: node} do\n      value = metric_value(metrics, \"dist_recv_bytes\", origin_node: node(), target_node: node)\n      assert is_integer(value)\n      assert value > 0\n    end\n\n    test \"queue_size\", %{metrics: metrics, node: node} do\n      assert is_integer(metric_value(metrics, \"dist_queue_size\", origin_node: node(), target_node: node))\n    end\n  end\n\n  defp metric_value(metrics, metric, expected_tags), do: MetricsHelper.search(metrics, metric, expected_tags)\nend\n"
  },
  {
    "path": "test/realtime/monitoring/prom_ex/plugins/gen_rpc_test.exs",
    "content": "defmodule Realtime.PromEx.Plugins.GenRpcTest do\n  # Async false due to Clustered usage\n  use ExUnit.Case, async: false\n  alias Realtime.PromEx.Plugins\n\n  defmodule MetricsTest do\n    use PromEx, otp_app: :metrics_test\n    @impl true\n    def plugins do\n      [{Plugins.GenRpc, poll_rate: 100}]\n    end\n  end\n\n  setup_all do\n    {:ok, node} = Clustered.start()\n    start_supervised!(MetricsTest)\n    # Send some data back and forth\n    25 = :gen_rpc.call(node, String, :to_integer, [\"25\"])\n    # Wait for MetricsTest to fetch metrics\n    Process.sleep(200)\n    %{node: node}\n  end\n\n  describe \"pooling metrics\" do\n    setup do\n      %{metrics: PromEx.get_metrics(MetricsTest)}\n    end\n\n    test \"send_pending_bytes\", %{metrics: metrics, node: node} do\n      assert metric_value(metrics, \"gen_rpc_send_pending_bytes\", origin_node: node(), target_node: node) == 0\n    end\n\n    test \"send_count\", %{metrics: metrics, node: node} do\n      value = metric_value(metrics, \"gen_rpc_send_count\", origin_node: node(), target_node: node)\n      assert is_integer(value)\n      assert value > 0\n    end\n\n    test \"send_bytes\", %{metrics: metrics, node: node} do\n      value = metric_value(metrics, \"gen_rpc_send_bytes\", origin_node: node(), target_node: node)\n      assert is_integer(value)\n      assert value > 0\n    end\n\n    test \"recv_count\", %{metrics: metrics, node: node} do\n      value = metric_value(metrics, \"gen_rpc_recv_count\", origin_node: node(), target_node: node)\n      assert is_integer(value)\n      assert value > 0\n    end\n\n    test \"recv_bytes\", %{metrics: metrics, node: node} do\n      value = metric_value(metrics, \"gen_rpc_recv_bytes\", origin_node: node(), target_node: node)\n      assert is_integer(value)\n      assert value > 0\n    end\n\n    test \"queue_size\", %{metrics: metrics, node: node} do\n      value = metric_value(metrics, \"gen_rpc_queue_size_bytes\", origin_node: node(), target_node: node)\n      assert is_integer(value)\n    end\n  end\n\n  defp metric_value(metrics, metric, expected_tags), do: MetricsHelper.search(metrics, metric, expected_tags)\nend\n"
  },
  {
    "path": "test/realtime/monitoring/prom_ex/plugins/phoenix_test.exs",
    "content": "defmodule Realtime.PromEx.Plugins.PhoenixTest do\n  use Realtime.DataCase, async: false\n  alias Realtime.PromEx.Plugins\n  alias Realtime.Integration.WebsocketClient\n\n  defmodule MetricsTest do\n    use PromEx, otp_app: :realtime_test_phoenix\n    @impl true\n    def plugins do\n      [{Plugins.Phoenix, router: RealtimeWeb.Router, poll_rate: 100, metric_prefix: [:phoenix]}]\n    end\n  end\n\n  setup_all do\n    start_supervised!(MetricsTest)\n    :ok\n  end\n\n  setup do\n    %{tenant: Containers.checkout_tenant(run_migrations: true)}\n  end\n\n  describe \"pooling metrics\" do\n    test \"number of connections\", %{tenant: tenant} do\n      {:ok, token} = token_valid(tenant, \"anon\", %{})\n\n      {:ok, _} =\n        WebsocketClient.connect(\n          self(),\n          uri(tenant, Phoenix.Socket.V1.JSONSerializer),\n          Phoenix.Socket.V1.JSONSerializer,\n          [{\"x-api-key\", token}]\n        )\n\n      {:ok, _} =\n        WebsocketClient.connect(\n          self(),\n          uri(tenant, Phoenix.Socket.V1.JSONSerializer),\n          Phoenix.Socket.V1.JSONSerializer,\n          [{\"x-api-key\", token}]\n        )\n\n      Process.sleep(200)\n      assert metric_value(\"phoenix_connections_total\") >= 2\n    end\n  end\n\n  describe \"event metrics\" do\n    test \"socket connected\", %{tenant: tenant} do\n      {:ok, token} = token_valid(tenant, \"anon\", %{})\n\n      {:ok, _} =\n        WebsocketClient.connect(\n          self(),\n          uri(tenant, Phoenix.Socket.V1.JSONSerializer),\n          Phoenix.Socket.V1.JSONSerializer,\n          [{\"x-api-key\", token}]\n        )\n\n      {:ok, _} =\n        WebsocketClient.connect(\n          self(),\n          uri(tenant, RealtimeWeb.Socket.V2Serializer),\n          RealtimeWeb.Socket.V2Serializer,\n          [{\"x-api-key\", token}]\n        )\n\n      Process.sleep(200)\n\n      assert metric_value(\"phoenix_socket_connected_duration_milliseconds_count\",\n               endpoint: \"RealtimeWeb.Endpoint\",\n               result: \"ok\",\n               serializer: \"Elixir.Phoenix.Socket.V1.JSONSerializer\",\n               transport: \"websocket\"\n             ) >= 1\n\n      assert metric_value(\"phoenix_socket_connected_duration_milliseconds_count\",\n               endpoint: \"RealtimeWeb.Endpoint\",\n               result: \"ok\",\n               serializer: \"Elixir.RealtimeWeb.Socket.V2Serializer\",\n               transport: \"websocket\"\n             ) >= 1\n    end\n  end\n\n  defp metric_value(metric, expected_tags \\\\ nil) do\n    MetricsHelper.search(PromEx.get_metrics(MetricsTest), metric, expected_tags)\n  end\nend\n"
  },
  {
    "path": "test/realtime/monitoring/prom_ex/plugins/tenant_test.exs",
    "content": "defmodule Realtime.PromEx.Plugins.TenantTest do\n  use Realtime.DataCase, async: false\n\n  alias Realtime.PromEx.Plugins.Tenant\n  alias Realtime.PromEx.Plugins.TenantGlobal\n  alias Realtime.Rpc\n  alias Realtime.Tenants.Authorization\n  alias Realtime.Tenants.Authorization.Policies\n  alias Realtime.Tenants.Authorization.Policies\n  alias Realtime.RateCounter\n  alias Realtime.GenCounter\n\n  defmodule MetricsTest do\n    use PromEx, otp_app: :realtime_test_phoenix\n\n    @impl true\n    def plugins, do: [{Tenant, poll_rate: 50}, {TenantGlobal, poll_rate: 50}]\n  end\n\n  setup_all do\n    start_supervised!(MetricsTest)\n    :ok\n  end\n\n  def handle_telemetry(event, metadata, content, pid: pid), do: send(pid, {event, metadata, content})\n\n  @aux_mod (quote do\n              defmodule FakeUserCounter do\n                def fake_add(external_id) do\n                  pid = spawn(fn -> Process.sleep(2000) end)\n                  :ok = Beacon.join(:users, external_id, pid)\n                end\n\n                def fake_db_event(external_id) do\n                  rate = Realtime.Tenants.db_events_per_second_rate(external_id, 100)\n\n                  rate\n                  |> tap(&RateCounter.new(&1))\n                  |> tap(&GenCounter.add(&1.id))\n                  |> RateCounterHelper.tick!()\n                end\n\n                def fake_event(external_id) do\n                  rate = Realtime.Tenants.events_per_second_rate(external_id, 123)\n\n                  rate\n                  |> tap(&RateCounter.new(&1))\n                  |> tap(&GenCounter.add(&1.id))\n                  |> RateCounterHelper.tick!()\n                end\n\n                def fake_presence_event(external_id) do\n                  rate = Realtime.Tenants.presence_events_per_second_rate(external_id, 123)\n\n                  rate\n                  |> tap(&RateCounter.new(&1))\n                  |> tap(&GenCounter.add(&1.id))\n                  |> RateCounterHelper.tick!()\n                end\n\n                def fake_broadcast_from_database(external_id) do\n                  Realtime.Telemetry.execute(\n                    [:realtime, :tenants, :broadcast_from_database],\n                    %{\n                      # millisecond\n                      latency_committed_at: 9,\n                      # microsecond\n                      latency_inserted_at: 9000\n                    },\n                    %{tenant: external_id}\n                  )\n                end\n\n                def fake_input_bytes(external_id) do\n                  Realtime.Telemetry.execute([:realtime, :channel, :input_bytes], %{size: 10}, %{tenant: external_id})\n                end\n\n                def fake_output_bytes(external_id) do\n                  Realtime.Telemetry.execute([:realtime, :channel, :output_bytes], %{size: 10}, %{tenant: external_id})\n                end\n              end\n            end)\n\n  Code.eval_quoted(@aux_mod)\n\n  describe \"execute_tenant_metrics/0\" do\n    setup do\n      tenant = Containers.checkout_tenant()\n      :telemetry.attach(__MODULE__, [:realtime, :connections], &__MODULE__.handle_telemetry/4, pid: self())\n\n      on_exit(fn -> :telemetry.detach(__MODULE__) end)\n\n      {:ok, _} = Realtime.Tenants.Connect.lookup_or_start_connection(tenant.external_id)\n      {:ok, node} = Clustered.start(@aux_mod, extra_config: [{:realtime, :users_scope_broadcast_interval_in_ms, 50}])\n      %{tenant: tenant, node: node}\n    end\n\n    test \"returns a list of tenant metrics and handles bad tenant ids\", %{\n      tenant: %{external_id: external_id},\n      node: node\n    } do\n      :ok = Beacon.join(:users, external_id, self())\n      # Add bad tenant id\n      bad_tenant_id = random_string()\n      :ok = Beacon.join(:users, bad_tenant_id, self())\n\n      _ = Rpc.call(node, FakeUserCounter, :fake_add, [external_id])\n\n      Process.sleep(500)\n      Tenant.execute_tenant_metrics()\n\n      assert_receive {[:realtime, :connections], %{connected: 1, limit: 200, connected_cluster: 2},\n                      %{tenant: ^external_id}},\n                     500\n\n      refute_receive {[:realtime, :connections], %{connected: 1, limit: 200, connected_cluster: 2},\n                      %{tenant: ^bad_tenant_id}}\n    end\n  end\n\n  describe \"event_metrics/0\" do\n    setup do\n      tenant = Containers.checkout_tenant(run_migrations: true)\n      {:ok, db_conn} = Realtime.Database.connect(tenant, \"realtime_test\", :stop)\n\n      authorization_context =\n        Authorization.build_authorization_params(%{\n          tenant_id: tenant.external_id,\n          topic: \"test_topic\",\n          jwt: \"jwt\",\n          claims: [],\n          headers: [{\"header-1\", \"value-1\"}],\n          role: \"anon\"\n        })\n\n      %{authorization_context: authorization_context, db_conn: db_conn, tenant: tenant}\n    end\n\n    test \"event exists after counter added\", %{tenant: %{external_id: external_id}} do\n      metric_value = metric_value(\"realtime_channel_events\", tenant: external_id) || 0\n      FakeUserCounter.fake_event(external_id)\n\n      Process.sleep(100)\n      assert metric_value(\"realtime_channel_events\", tenant: external_id) == metric_value + 1\n    end\n\n    test \"global event exists after counter added\", %{tenant: %{external_id: external_id}} do\n      metric_value = metric_value(\"realtime_channel_global_events\") || 0\n\n      FakeUserCounter.fake_event(external_id)\n\n      Process.sleep(100)\n      assert metric_value(\"realtime_channel_global_events\") == metric_value + 1\n    end\n\n    test \"db_event exists after counter added\", %{tenant: %{external_id: external_id}} do\n      metric_value = metric_value(\"realtime_channel_db_events\", tenant: external_id) || 0\n      FakeUserCounter.fake_db_event(external_id)\n      Process.sleep(100)\n      assert metric_value(\"realtime_channel_db_events\", tenant: external_id) == metric_value + 1\n    end\n\n    test \"global db_event exists after counter added\", %{tenant: %{external_id: external_id}} do\n      metric_value = metric_value(\"realtime_channel_global_db_events\") || 0\n\n      FakeUserCounter.fake_db_event(external_id)\n      Process.sleep(100)\n      assert metric_value(\"realtime_channel_global_db_events\") == metric_value + 1\n    end\n\n    test \"presence_event exists after counter added\", %{tenant: %{external_id: external_id}} do\n      metric_value = metric_value(\"realtime_channel_presence_events\", tenant: external_id) || 0\n\n      FakeUserCounter.fake_presence_event(external_id)\n      Process.sleep(100)\n      assert metric_value(\"realtime_channel_presence_events\", tenant: external_id) == metric_value + 1\n    end\n\n    test \"global presence_event exists after counter added\", %{tenant: %{external_id: external_id}} do\n      metric_value = metric_value(\"realtime_channel_global_presence_events\") || 0\n      FakeUserCounter.fake_presence_event(external_id)\n      Process.sleep(100)\n      assert metric_value(\"realtime_channel_global_presence_events\") == metric_value + 1\n    end\n\n    test \"metric read_authorization_check exists after check\", context do\n      metric = \"realtime_tenants_read_authorization_check_count\"\n      metric_value = metric_value(metric, tenant: context.tenant.external_id) || 0\n\n      {:ok, _} =\n        Authorization.get_read_authorizations(\n          %Policies{},\n          context.db_conn,\n          context.authorization_context\n        )\n\n      Process.sleep(200)\n\n      assert metric_value(metric, tenant: context.tenant.external_id) == metric_value + 1\n\n      assert metric_value(\"realtime_tenants_read_authorization_check_bucket\",\n               tenant: context.tenant.external_id,\n               le: \"250.0\"\n             ) > 0\n    end\n\n    test \"metric write_authorization_check exists after check\", context do\n      metric = \"realtime_tenants_write_authorization_check_count\"\n      metric_value = metric_value(metric, tenant: context.tenant.external_id) || 0\n\n      {:ok, _} =\n        Authorization.get_write_authorizations(\n          %Policies{},\n          context.db_conn,\n          context.authorization_context\n        )\n\n      # Wait enough time for the poll rate to be triggered at least once\n      Process.sleep(200)\n\n      assert metric_value(metric, tenant: context.tenant.external_id) == metric_value + 1\n\n      assert metric_value(\"realtime_tenants_write_authorization_check_bucket\",\n               tenant: context.tenant.external_id,\n               le: \"250.0\"\n             ) > 0\n    end\n\n    test \"metric replay exists after check\", context do\n      external_id = context.tenant.external_id\n      metric = \"realtime_tenants_replay_count\"\n      metric_value = metric_value(metric, tenant: external_id) || 0\n\n      assert {:ok, _, _} = Realtime.Messages.replay(context.db_conn, external_id, \"test\", 0, 1)\n\n      # Wait enough time for the poll rate to be triggered at least once\n      Process.sleep(200)\n\n      assert metric_value(metric, tenant: external_id) == metric_value + 1\n\n      assert metric_value(\"realtime_tenants_replay_bucket\", tenant: external_id, le: \"250.0\") > 0\n    end\n\n    test \"metric realtime_tenants_broadcast_from_database_latency_committed_at exists after check\", context do\n      external_id = context.tenant.external_id\n      metric = \"realtime_tenants_broadcast_from_database_latency_committed_at_count\"\n      metric_value = metric_value(metric, tenant: external_id) || 0\n\n      FakeUserCounter.fake_broadcast_from_database(context.tenant.external_id)\n      Process.sleep(200)\n      assert metric_value(metric, tenant: external_id) == metric_value + 1\n\n      assert metric_value(\"realtime_tenants_broadcast_from_database_latency_committed_at_bucket\",\n               tenant: external_id,\n               le: \"10.0\"\n             ) > 0\n    end\n\n    test \"metric realtime_tenants_broadcast_from_database_latency_inserted_at exists after check\", context do\n      external_id = context.tenant.external_id\n      metric = \"realtime_tenants_broadcast_from_database_latency_inserted_at_count\"\n      metric_value = metric_value(metric, tenant: external_id) || 0\n\n      FakeUserCounter.fake_broadcast_from_database(context.tenant.external_id)\n      Process.sleep(200)\n      assert metric_value(metric, tenant: external_id) == metric_value + 1\n\n      assert metric_value(\"realtime_tenants_broadcast_from_database_latency_inserted_at_bucket\",\n               tenant: external_id,\n               le: \"10.0\"\n             ) > 0\n    end\n\n    test \"tenant metric payload size\", context do\n      external_id = context.tenant.external_id\n      metric = \"realtime_tenants_payload_size_count\"\n      metric_value = metric_value(metric, message_type: \"presence\", tenant: external_id) || 0\n\n      message = %{topic: \"a topic\", event: \"an event\", payload: [\"a\", %{\"b\" => \"c\"}, 1, 23]}\n      RealtimeWeb.TenantBroadcaster.pubsub_broadcast(external_id, \"a topic\", message, Phoenix.PubSub, :presence)\n\n      Process.sleep(200)\n      assert metric_value(metric, message_type: \"presence\", tenant: external_id) == metric_value + 1\n\n      assert metric_value(\"realtime_tenants_payload_size_bucket\", tenant: external_id, le: \"250\") > 0\n    end\n\n    test \"global metric payload size\", context do\n      external_id = context.tenant.external_id\n\n      metric = \"realtime_payload_size_count\"\n      metric_value = metric_value(metric, message_type: \"broadcast\") || 0\n\n      message = %{topic: \"a topic\", event: \"an event\", payload: [\"a\", %{\"b\" => \"c\"}, 1, 23]}\n      RealtimeWeb.TenantBroadcaster.pubsub_broadcast(external_id, \"a topic\", message, Phoenix.PubSub, :broadcast)\n\n      Process.sleep(200)\n      assert metric_value(metric, message_type: \"broadcast\") == metric_value + 1\n\n      assert metric_value(\"realtime_payload_size_bucket\", le: \"250.0\") > 0\n    end\n\n    test \"channel input bytes\", context do\n      external_id = context.tenant.external_id\n\n      FakeUserCounter.fake_input_bytes(external_id)\n      FakeUserCounter.fake_input_bytes(external_id)\n\n      Process.sleep(200)\n      assert metric_value(\"realtime_channel_input_bytes\", tenant: external_id) == 20\n    end\n\n    test \"channel output bytes\", context do\n      external_id = context.tenant.external_id\n\n      FakeUserCounter.fake_output_bytes(external_id)\n      FakeUserCounter.fake_output_bytes(external_id)\n\n      Process.sleep(200)\n      assert metric_value(\"realtime_channel_output_bytes\", tenant: external_id) == 20\n    end\n  end\n\n  describe \"execute_global_connection_metrics/0\" do\n    test \"emits global connection counts without a tenant tag\" do\n      pid = spawn_link(fn -> Process.sleep(:infinity) end)\n      :ok = Beacon.join(:users, \"global-test-tenant\", pid)\n\n      TenantGlobal.execute_global_connection_metrics()\n\n      Process.sleep(100)\n\n      assert metric_value(\"realtime_connections_global_connected\") >= 0\n      assert metric_value(\"realtime_connections_global_connected_cluster\") >= 0\n    end\n  end\n\n  defp metric_value(metric, expected_tags \\\\ nil) do\n    MetricsHelper.search(PromEx.get_metrics(MetricsTest), metric, expected_tags)\n  end\nend\n"
  },
  {
    "path": "test/realtime/monitoring/prom_ex/plugins/tenants_test.exs",
    "content": "defmodule Realtime.PromEx.Plugins.TenantsTest do\n  use Realtime.DataCase, async: false\n\n  alias Realtime.GenRpc\n  alias Realtime.PromEx.Plugins.Tenants\n  alias Realtime.Rpc\n  alias Realtime.Tenants.Connect\n\n  defmodule MetricsTest do\n    use PromEx, otp_app: :realtime_test_tenants\n    @impl true\n    def plugins do\n      [{Tenants, poll_rate: 50}]\n    end\n  end\n\n  defmodule Test do\n    def success, do: {:ok, \"success\"}\n    def failure, do: {:error, \"failure\"}\n    def exception, do: raise(RuntimeError)\n  end\n\n  setup_all do\n    start_supervised!(MetricsTest)\n    :ok\n  end\n\n  describe \"event_metrics erpc\" do\n    setup do\n      %{tenant: random_string()}\n    end\n\n    test \"global success\", %{tenant: tenant} do\n      metric = \"realtime_global_rpc_count\"\n      # Enough time for the poll rate to be triggered at least once\n      Process.sleep(200)\n      previous_value = metric_value(metric, mechanism: \"erpc\", success: true) || 0\n      assert {:ok, \"success\"} = Rpc.enhanced_call(node(), Test, :success, [], tenant_id: tenant)\n      Process.sleep(200)\n      assert metric_value(metric, mechanism: \"erpc\", success: true) == previous_value + 1\n    end\n\n    test \"global failure\", %{tenant: tenant} do\n      metric = \"realtime_global_rpc_count\"\n      # Enough time for the poll rate to be triggered at least once\n      Process.sleep(200)\n      previous_value = metric_value(metric, mechanism: \"erpc\", success: false) || 0\n      assert {:error, \"failure\"} = Rpc.enhanced_call(node(), Test, :failure, [], tenant_id: tenant)\n      Process.sleep(200)\n      assert metric_value(metric, mechanism: \"erpc\", success: false) == previous_value + 1\n    end\n\n    test \"global exception\", %{tenant: tenant} do\n      metric = \"realtime_global_rpc_count\"\n      # Enough time for the poll rate to be triggered at least once\n      Process.sleep(200)\n      previous_value = metric_value(metric, mechanism: \"erpc\", success: false) || 0\n\n      assert {:error, :rpc_error, %RuntimeError{message: \"runtime error\"}} =\n               Rpc.enhanced_call(node(), Test, :exception, [], tenant_id: tenant)\n\n      Process.sleep(200)\n      assert metric_value(metric, mechanism: \"erpc\", success: false) == previous_value + 1\n    end\n  end\n\n  describe \"event_metrics gen_rpc\" do\n    setup do\n      %{tenant: random_string()}\n    end\n\n    test \"global success\", %{tenant: tenant} do\n      metric = \"realtime_global_rpc_count\"\n      # Enough time for the poll rate to be triggered at least once\n      Process.sleep(200)\n      previous_value = metric_value(metric, mechanism: \"gen_rpc\", success: true) || 0\n      assert GenRpc.multicall(Test, :success, [], tenant_id: tenant) == [{node(), {:ok, \"success\"}}]\n      Process.sleep(200)\n      assert metric_value(metric, mechanism: \"gen_rpc\", success: true) == previous_value + 1\n    end\n\n    test \"global failure\", %{tenant: tenant} do\n      metric = \"realtime_global_rpc_count\"\n      # Enough time for the poll rate to be triggered at least once\n      Process.sleep(200)\n      previous_value = metric_value(metric, mechanism: \"gen_rpc\", success: false) || 0\n      assert GenRpc.multicall(Test, :failure, [], tenant_id: tenant) == [{node(), {:error, \"failure\"}}]\n      Process.sleep(200)\n      assert metric_value(metric, mechanism: \"gen_rpc\", success: false) == previous_value + 1\n    end\n\n    test \"global exception\", %{tenant: tenant} do\n      metric = \"realtime_global_rpc_count\"\n      # Enough time for the poll rate to be triggered at least once\n      Process.sleep(200)\n      previous_value = metric_value(metric, mechanism: \"gen_rpc\", success: false) || 0\n      node = node()\n\n      assert assert [{^node, {:error, :rpc_error, {:EXIT, {%RuntimeError{message: \"runtime error\"}, _stacktrace}}}}] =\n                      GenRpc.multicall(Test, :exception, [], tenant_id: tenant)\n\n      Process.sleep(200)\n      assert metric_value(metric, mechanism: \"gen_rpc\", success: false) == previous_value + 1\n    end\n  end\n\n  describe \"pooling metrics\" do\n    setup do\n      local_tenant = Containers.checkout_tenant(run_migrations: true)\n      {:ok, %{tenant: local_tenant}}\n    end\n\n    test \"conneted based on Connect module information for local node only\", %{tenant: tenant} do\n      # Enough time for the poll rate to be triggered at least once\n      Process.sleep(200)\n      previous_value = metric_value(\"realtime_tenants_connected\")\n      {:ok, _} = Connect.lookup_or_start_connection(tenant.external_id)\n      Process.sleep(200)\n      assert metric_value(\"realtime_tenants_connected\") == previous_value + 1\n    end\n  end\n\n  defp metric_value(metric, expected_tags \\\\ nil) do\n    MetricsHelper.search(PromEx.get_metrics(MetricsTest), metric, expected_tags)\n  end\nend\n"
  },
  {
    "path": "test/realtime/monitoring/prom_ex_test.exs",
    "content": "defmodule Realtime.PromExTest do\n  use ExUnit.Case, async: true\n  doctest Realtime.PromEx\n  alias Realtime.PromEx\n\n  describe \"get_metrics/0\" do\n    test \"builds metrics in prometheus format which includes host region and id\" do\n      metrics = PromEx.get_metrics() |> IO.iodata_to_binary()\n\n      assert String.contains?(\n               metrics,\n               \"# HELP beam_system_schedulers_online_info The number of scheduler threads that are online.\"\n             )\n\n      assert String.contains?(metrics, \"# TYPE beam_system_schedulers_online_info gauge\")\n\n      assert String.contains?(\n               metrics,\n               \"beam_system_schedulers_online_info{host=\\\"nohost\\\",id=\\\"nohost\\\",region=\\\"us-east-1\\\"}\"\n             )\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/monitoring/prometheus_test.exs",
    "content": "# Based on https://github.com/rkallos/peep/blob/708546ed069aebdf78ac1f581130332bd2e8b5b1/test/prometheus_test.exs\ndefmodule Realtime.Monitoring.PrometheusTest do\n  use ExUnit.Case, async: true\n\n  alias Realtime.Monitoring.Prometheus\n  alias Telemetry.Metrics\n\n  defmodule StorageCounter do\n    @moduledoc false\n    use Agent\n\n    def start() do\n      Agent.start(fn -> 0 end, name: __MODULE__)\n    end\n\n    def fresh_id() do\n      Agent.get_and_update(__MODULE__, fn i -> {:\"#{i}\", i + 1} end)\n    end\n  end\n\n  # Test struct that doesn't implement String.Chars\n  defmodule TestError do\n    defstruct [:reason, :code]\n  end\n\n  setup_all do\n    StorageCounter.start()\n    :ok\n  end\n\n  @impls [:default, {Realtime.Monitoring.Peep.Partitioned, 4}, :striped]\n\n  for impl <- @impls do\n    test \"#{inspect(impl)} - counter formatting\" do\n      counter = Metrics.counter(\"prometheus.test.counter\", description: \"a counter\")\n      name = StorageCounter.fresh_id()\n\n      opts = [\n        name: name,\n        metrics: [counter],\n        storage: unquote(impl)\n      ]\n\n      {:ok, _pid} = Peep.start_link(opts)\n\n      Peep.insert_metric(name, counter, 1, %{foo: :bar, baz: \"quux\"})\n\n      expected = [\n        \"# HELP prometheus_test_counter a counter\",\n        \"# TYPE prometheus_test_counter counter\",\n        ~s(prometheus_test_counter{baz=\"quux\",foo=\"bar\"} 1)\n      ]\n\n      assert export(name) == lines_to_string(expected)\n    end\n\n    describe \"#{inspect(impl)} - sum\" do\n      test \"sum formatting\" do\n        name = StorageCounter.fresh_id()\n        sum = Metrics.sum(\"prometheus.test.sum\", description: \"a sum\")\n\n        opts = [\n          name: name,\n          metrics: [sum],\n          storage: unquote(impl)\n        ]\n\n        {:ok, _pid} = Peep.start_link(opts)\n\n        Peep.insert_metric(name, sum, 5, %{foo: :bar, baz: \"quux\"})\n        Peep.insert_metric(name, sum, 3, %{foo: :bar, baz: \"quux\"})\n\n        expected = [\n          \"# HELP prometheus_test_sum a sum\",\n          \"# TYPE prometheus_test_sum counter\",\n          ~s(prometheus_test_sum{baz=\"quux\",foo=\"bar\"} 8)\n        ]\n\n        assert export(name) == lines_to_string(expected)\n      end\n\n      test \"custom type\" do\n        name = StorageCounter.fresh_id()\n\n        sum =\n          Metrics.sum(\"prometheus.test.sum\",\n            description: \"a sum\",\n            reporter_options: [prometheus_type: \"gauge\"]\n          )\n\n        opts = [\n          name: name,\n          metrics: [sum],\n          storage: unquote(impl)\n        ]\n\n        {:ok, _pid} = Peep.start_link(opts)\n\n        Peep.insert_metric(name, sum, 5, %{foo: :bar, baz: \"quux\"})\n        Peep.insert_metric(name, sum, 3, %{foo: :bar, baz: \"quux\"})\n\n        expected = [\n          \"# HELP prometheus_test_sum a sum\",\n          \"# TYPE prometheus_test_sum gauge\",\n          ~s(prometheus_test_sum{baz=\"quux\",foo=\"bar\"} 8)\n        ]\n\n        assert export(name) == lines_to_string(expected)\n      end\n    end\n\n    describe \"#{inspect(impl)} - last_value\" do\n      test \"formatting\" do\n        name = StorageCounter.fresh_id()\n        last_value = Metrics.last_value(\"prometheus.test.gauge\", description: \"a last_value\")\n\n        opts = [\n          name: name,\n          metrics: [last_value],\n          storage: unquote(impl)\n        ]\n\n        {:ok, _pid} = Peep.start_link(opts)\n\n        Peep.insert_metric(name, last_value, 5, %{blee: :bloo, flee: \"floo\"})\n\n        expected = [\n          \"# HELP prometheus_test_gauge a last_value\",\n          \"# TYPE prometheus_test_gauge gauge\",\n          ~s(prometheus_test_gauge{blee=\"bloo\",flee=\"floo\"} 5)\n        ]\n\n        assert export(name) == lines_to_string(expected)\n      end\n\n      test \"custom type\" do\n        name = StorageCounter.fresh_id()\n\n        last_value =\n          Metrics.last_value(\"prometheus.test.gauge\",\n            description: \"a last_value\",\n            reporter_options: [prometheus_type: :sum]\n          )\n\n        opts = [\n          name: name,\n          metrics: [last_value],\n          storage: unquote(impl)\n        ]\n\n        {:ok, _pid} = Peep.start_link(opts)\n\n        Peep.insert_metric(name, last_value, 5, %{blee: :bloo, flee: \"floo\"})\n\n        expected = [\n          \"# HELP prometheus_test_gauge a last_value\",\n          \"# TYPE prometheus_test_gauge sum\",\n          ~s(prometheus_test_gauge{blee=\"bloo\",flee=\"floo\"} 5)\n        ]\n\n        assert export(name) == lines_to_string(expected)\n      end\n    end\n\n    test \"#{inspect(impl)} - dist formatting\" do\n      name = StorageCounter.fresh_id()\n\n      dist =\n        Metrics.distribution(\"prometheus.test.distribution\",\n          description: \"a distribution\",\n          reporter_options: [max_value: 1000]\n        )\n\n      opts = [\n        name: name,\n        metrics: [dist],\n        storage: unquote(impl)\n      ]\n\n      {:ok, _pid} = Peep.start_link(opts)\n\n      expected = []\n      assert export(name) == lines_to_string(expected)\n\n      Peep.insert_metric(name, dist, 1, %{glee: :gloo})\n\n      expected = [\n        \"# HELP prometheus_test_distribution a distribution\",\n        \"# TYPE prometheus_test_distribution histogram\",\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.222222\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.493827\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.825789\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"2.23152\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"2.727413\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"3.333505\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"4.074283\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"4.97968\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"6.086275\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"7.438781\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"9.091843\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"11.112253\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"13.581642\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"16.599785\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"20.288626\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"24.79721\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"30.307701\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"37.042745\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"45.274466\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"55.335459\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"67.632227\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"82.661611\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"101.030858\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"123.48216\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"150.92264\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"184.461004\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"225.452339\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"275.552858\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"336.786827\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"411.628344\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"503.101309\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"614.9016\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"751.5464\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"918.556711\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1122.680424\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"+Inf\"} 1),\n        ~s(prometheus_test_distribution_sum{glee=\"gloo\"} 1),\n        ~s(prometheus_test_distribution_count{glee=\"gloo\"} 1)\n      ]\n\n      assert export(name) == lines_to_string(expected)\n\n      for i <- 2..2000 do\n        Peep.insert_metric(name, dist, i, %{glee: :gloo})\n      end\n\n      expected = [\n        \"# HELP prometheus_test_distribution a distribution\",\n        \"# TYPE prometheus_test_distribution histogram\",\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.222222\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.493827\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.825789\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"2.23152\"} 2),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"2.727413\"} 2),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"3.333505\"} 3),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"4.074283\"} 4),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"4.97968\"} 4),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"6.086275\"} 6),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"7.438781\"} 7),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"9.091843\"} 9),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"11.112253\"} 11),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"13.581642\"} 13),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"16.599785\"} 16),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"20.288626\"} 20),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"24.79721\"} 24),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"30.307701\"} 30),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"37.042745\"} 37),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"45.274466\"} 45),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"55.335459\"} 55),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"67.632227\"} 67),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"82.661611\"} 82),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"101.030858\"} 101),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"123.48216\"} 123),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"150.92264\"} 150),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"184.461004\"} 184),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"225.452339\"} 225),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"275.552858\"} 275),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"336.786827\"} 336),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"411.628344\"} 411),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"503.101309\"} 503),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"614.9016\"} 614),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"751.5464\"} 751),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"918.556711\"} 918),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1122.680424\"} 1122),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"+Inf\"} 2000),\n        ~s(prometheus_test_distribution_sum{glee=\"gloo\"} 2001000),\n        ~s(prometheus_test_distribution_count{glee=\"gloo\"} 2000)\n      ]\n\n      assert export(name) == lines_to_string(expected)\n    end\n\n    test \"#{inspect(impl)} - dist formatting pow10\" do\n      name = StorageCounter.fresh_id()\n\n      dist =\n        Metrics.distribution(\"prometheus.test.distribution\",\n          description: \"a distribution\",\n          reporter_options: [\n            max_value: 1000,\n            peep_bucket_calculator: Peep.Buckets.PowersOfTen\n          ]\n        )\n\n      opts = [\n        name: name,\n        metrics: [dist],\n        storage: unquote(impl)\n      ]\n\n      {:ok, _pid} = Peep.start_link(opts)\n\n      expected = []\n      assert export(name) == lines_to_string(expected)\n\n      Peep.insert_metric(name, dist, 1, %{glee: :gloo})\n\n      expected = [\n        \"# HELP prometheus_test_distribution a distribution\",\n        \"# TYPE prometheus_test_distribution histogram\",\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"10.0\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"100.0\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e3\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e4\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e5\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e6\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e7\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e8\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e9\"} 1),\n        ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"+Inf\"} 1),\n        ~s(prometheus_test_distribution_sum{glee=\"gloo\"} 1),\n        ~s(prometheus_test_distribution_count{glee=\"gloo\"} 1)\n      ]\n\n      assert export(name) == lines_to_string(expected)\n\n      f = fn ->\n        for i <- 1..2000 do\n          Peep.insert_metric(name, dist, i, %{glee: :gloo})\n        end\n      end\n\n      1..20 |> Enum.map(fn _ -> Task.async(f) end) |> Task.await_many()\n\n      expected =\n        [\n          \"# HELP prometheus_test_distribution a distribution\",\n          \"# TYPE prometheus_test_distribution histogram\",\n          ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"10.0\"} 181),\n          ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"100.0\"} 1981),\n          ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e3\"} 19981),\n          ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e4\"} 40001),\n          ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e5\"} 40001),\n          ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e6\"} 40001),\n          ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e7\"} 40001),\n          ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e8\"} 40001),\n          ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"1.0e9\"} 40001),\n          ~s(prometheus_test_distribution_bucket{glee=\"gloo\",le=\"+Inf\"} 40001),\n          ~s(prometheus_test_distribution_sum{glee=\"gloo\"} 40020001),\n          ~s(prometheus_test_distribution_count{glee=\"gloo\"} 40001)\n        ]\n\n      assert export(name) == lines_to_string(expected)\n    end\n\n    test \"#{inspect(impl)} - regression: label escaping\" do\n      name = StorageCounter.fresh_id()\n\n      counter =\n        Metrics.counter(\n          \"prometheus.test.counter\",\n          description: \"a counter\"\n        )\n\n      opts = [\n        name: name,\n        metrics: [counter],\n        storage: unquote(impl)\n      ]\n\n      {:ok, _pid} = Peep.start_link(opts)\n\n      Peep.insert_metric(name, counter, 1, %{atom: \"\\\"string\\\"\"})\n      Peep.insert_metric(name, counter, 1, %{\"\\\"string\\\"\" => :atom})\n      Peep.insert_metric(name, counter, 1, %{\"\\\"string\\\"\" => \"\\\"string\\\"\"})\n      Peep.insert_metric(name, counter, 1, %{\"string\" => \"string\\n\"})\n\n      expected = [\n        \"# HELP prometheus_test_counter a counter\",\n        \"# TYPE prometheus_test_counter counter\",\n        ~s(prometheus_test_counter{atom=\"\\\\\\\"string\\\\\\\"\"} 1),\n        ~s(prometheus_test_counter{\\\"string\\\"=\"atom\"} 1),\n        ~s(prometheus_test_counter{\\\"string\\\"=\"\\\\\\\"string\\\\\\\"\"} 1),\n        ~s(prometheus_test_counter{string=\"string\\\\n\"} 1)\n      ]\n\n      assert export(name) == lines_to_string(expected)\n    end\n\n    test \"#{inspect(impl)} - regression: handle structs without String.Chars\" do\n      name = StorageCounter.fresh_id()\n\n      counter =\n        Metrics.counter(\n          \"prometheus.test.counter\",\n          description: \"a counter\"\n        )\n\n      opts = [\n        name: name,\n        metrics: [counter],\n        storage: unquote(impl)\n      ]\n\n      {:ok, _pid} = Peep.start_link(opts)\n\n      # Create a struct that doesn't implement String.Chars\n      error_struct = %TestError{reason: :tcp_closed, code: 1001}\n\n      Peep.insert_metric(name, counter, 1, %{error: error_struct})\n\n      result = export(name)\n\n      # Should not crash and should contain the inspected struct representation\n      assert result =~ \"prometheus_test_counter\"\n      assert result =~ \"TestError\"\n      assert result =~ \"tcp_closed\"\n    end\n  end\n\n  defp export(name) do\n    Peep.get_all_metrics(name)\n    |> Prometheus.export()\n    |> IO.iodata_to_binary()\n  end\n\n  defp lines_to_string(lines) do\n    lines\n    |> Enum.map(&[&1, ?\\n])\n    |> Enum.concat([\"# EOF\\n\"])\n    |> IO.iodata_to_binary()\n  end\nend\n"
  },
  {
    "path": "test/realtime/nodes_test.exs",
    "content": "defmodule Realtime.NodesTest do\n  use Realtime.DataCase, async: false\n  use Mimic\n  alias Realtime.Nodes\n  alias Realtime.Tenants\n\n  defp spawn_fake_node(region, node) do\n    parent = self()\n\n    fun = fn ->\n      :syn.join(RegionNodes, region, self(), node: node)\n      send(parent, :joined)\n\n      receive do\n        :ok -> :ok\n      end\n    end\n\n    {:ok, _pid} = start_supervised({Task, fun}, id: {region, node})\n    assert_receive :joined\n  end\n\n  describe \"all_node_regions/0\" do\n    test \"returns all regions with nodes\" do\n      spawn_fake_node(\"us-east-1\", :node_1)\n      spawn_fake_node(\"ap-2\", :node_2)\n      spawn_fake_node(\"ap-2\", :node_3)\n\n      assert Nodes.all_node_regions() |> Enum.sort() == [\"ap-2\", \"us-east-1\"]\n    end\n\n    test \"with no other nodes, returns my region only\" do\n      assert Nodes.all_node_regions() == [\"us-east-1\"]\n    end\n  end\n\n  describe \"region_nodes/1\" do\n    test \"nil region returns empty list\" do\n      assert Nodes.region_nodes(nil) == []\n    end\n\n    test \"returns nodes from region\" do\n      region = \"ap-southeast-2\"\n      spawn_fake_node(region, :node_1)\n      spawn_fake_node(region, :node_2)\n\n      spawn_fake_node(\"eu-west-2\", :node_3)\n\n      assert Nodes.region_nodes(region) == [:node_1, :node_2]\n      assert Nodes.region_nodes(\"eu-west-2\") == [:node_3]\n    end\n\n    test \"on non-existing region, returns empty list\" do\n      assert Nodes.region_nodes(\"non-existing-region\") == []\n    end\n  end\n\n  describe \"node_from_region/2\" do\n    test \"nil region returns error\" do\n      assert {:error, :not_available} = Nodes.node_from_region(nil, :any_key)\n    end\n\n    test \"empty region returns error\" do\n      assert {:error, :not_available} = Nodes.node_from_region(\"empty-region\", :any_key)\n    end\n\n    test \"returns the same node given the same key\" do\n      region = \"ap-southeast-3\"\n      spawn_fake_node(region, :node_1)\n      spawn_fake_node(region, :node_2)\n\n      spawn_fake_node(\"eu-west-3\", :node_3)\n\n      assert {:ok, :node_2} = Nodes.node_from_region(region, :key1)\n      assert {:ok, :node_2} = Nodes.node_from_region(region, :key1)\n    end\n  end\n\n  describe \"get_node_for_tenant/1\" do\n    setup do\n      tenant = Containers.checkout_tenant()\n      region = tenant.extensions |> hd() |> get_in([Access.key!(:settings), \"region\"])\n      %{tenant: tenant, region: region}\n    end\n\n    test \"nil call returns error\" do\n      assert {:error, :tenant_not_found} = Nodes.get_node_for_tenant(nil)\n      reject(&:syn.members/2)\n    end\n\n    test \"on existing tenant id, returns a node from the region using load-aware picking\", %{\n      tenant: tenant,\n      region: region\n    } do\n      expected_nodes = [:tenant@closest1, :tenant@closest2]\n\n      expect(:syn, :members, fn RegionNodes, ^region ->\n        [\n          {self(), [node: Enum.at(expected_nodes, 0)]},\n          {self(), [node: Enum.at(expected_nodes, 1)]}\n        ]\n      end)\n\n      expected_region = Tenants.region(tenant)\n\n      assert {:ok, node, region} = Nodes.get_node_for_tenant(tenant)\n      assert region == expected_region\n      assert node in expected_nodes\n    end\n\n    test \"on existing tenant id, and a single node for a given region, returns single node\", %{\n      tenant: tenant,\n      region: region\n    } do\n      expect(:syn, :members, fn RegionNodes, ^region -> [{self(), [node: :tenant@closest1]}] end)\n\n      assert {:ok, node, region} = Nodes.get_node_for_tenant(tenant)\n\n      expected_region = Tenants.region(tenant)\n\n      assert node == :tenant@closest1\n      assert region == expected_region\n    end\n\n    test \"on existing tenant id, returns default node for regions not registered in syn\", %{tenant: tenant} do\n      expect(:syn, :members, fn RegionNodes, _ -> [] end)\n      assert {:ok, node, region} = Nodes.get_node_for_tenant(tenant)\n\n      expected_region = Tenants.region(tenant)\n\n      assert node == node()\n      assert region == expected_region\n    end\n  end\n\n  describe \"platform_region_translator/1\" do\n    test \"returns nil for nil input\" do\n      assert Nodes.platform_region_translator(nil) == nil\n    end\n\n    test \"uses default mapping when no custom mapping configured\" do\n      original = Application.get_env(:realtime, :region_mapping)\n      on_exit(fn -> Application.put_env(:realtime, :region_mapping, original) end)\n\n      Application.put_env(:realtime, :region_mapping, nil)\n\n      assert Nodes.platform_region_translator(\"eu-north-1\") == \"eu-west-2\"\n      assert Nodes.platform_region_translator(\"us-west-2\") == \"us-west-1\"\n      assert Nodes.platform_region_translator(\"unknown-region\") == nil\n    end\n\n    test \"uses custom mapping when configured without falling back to defaults\" do\n      original = Application.get_env(:realtime, :region_mapping)\n      on_exit(fn -> Application.put_env(:realtime, :region_mapping, original) end)\n\n      custom_mapping = %{\n        \"custom-region-1\" => \"us-east-1\",\n        \"eu-north-1\" => \"custom-target\"\n      }\n\n      Application.put_env(:realtime, :region_mapping, custom_mapping)\n\n      # Custom mappings work\n      assert Nodes.platform_region_translator(\"custom-region-1\") == \"us-east-1\"\n      assert Nodes.platform_region_translator(\"eu-north-1\") == \"custom-target\"\n\n      # Unmapped regions return nil (no fallback to defaults)\n      assert Nodes.platform_region_translator(\"us-west-2\") == nil\n    end\n  end\n\n  describe \"node_load/1\" do\n    test \"returns {:error, :not_enough_data} for local node with insufficient uptime\" do\n      assert {:error, :not_enough_data} = Nodes.node_load(node())\n    end\n  end\n\n  describe \"node_load/1 with sufficient uptime\" do\n    setup do\n      Application.put_env(:realtime, :node_balance_uptime_threshold_in_ms, 0)\n\n      on_exit(fn ->\n        Application.put_env(:realtime, :node_balance_uptime_threshold_in_ms, 999_999_999_999)\n      end)\n    end\n\n    test \"returns cpu load for local node\" do\n      load = Nodes.node_load(node())\n\n      assert is_integer(load)\n      assert load >= 0\n    end\n\n    test \"returns cpu load for remote node\" do\n      {:ok, remote_node} = Clustered.start()\n\n      load = Nodes.node_load(remote_node)\n\n      assert is_integer(load)\n      assert load >= 0\n    end\n\n    test \"remote node can also get its own load\" do\n      {:ok, remote_node} = Clustered.start()\n\n      load = :rpc.call(remote_node, Nodes, :node_load, [remote_node])\n\n      assert is_integer(load)\n      assert load >= 0\n    end\n  end\n\n  describe \"launch_node/3 load-aware but not enough uptime\" do\n    test \"returns the one node from the region when one node is available\" do\n      region = \"clustered-test-region\"\n      spawn_fake_node(region, :remote_node)\n\n      result = Nodes.launch_node(region, node(), \"test-tenant-123\")\n\n      assert result == :remote_node\n    end\n\n    test \"returns default node when no region nodes available\" do\n      result = Nodes.launch_node(\"empty-region\", node(), \"test-tenant-123\")\n\n      assert result == node()\n    end\n\n    test \"same tenant_id picks same nodes\" do\n      region = \"deterministic-region\"\n      spawn_fake_node(region, :node_a)\n      spawn_fake_node(region, :node_b)\n      spawn_fake_node(region, :node_c)\n\n      tenant_id = \"test-tenant-456\"\n\n      # Call 10 times, should always return same node with hashed tenant ID\n      results = for _ <- 1..10, do: Nodes.launch_node(region, node(), tenant_id)\n\n      assert length(Enum.uniq(results)) == 1\n    end\n\n    test \"different tenant_ids distribute across nodes\" do\n      region = \"distribution-region\"\n      spawn_fake_node(region, :node_a)\n      spawn_fake_node(region, :node_b)\n      spawn_fake_node(region, :node_c)\n\n      # Generate 30 different tenant IDs\n      tenant_ids = for i <- 1..30, do: \"tenant-#{i}\"\n\n      results =\n        Enum.map(tenant_ids, fn id ->\n          Nodes.launch_node(region, node(), id)\n        end)\n\n      # Should distribute across multiple nodes (at least 2) using the hashed tenant IDs\n      assert length(Enum.uniq(results)) >= 2\n    end\n  end\n\n  describe \"launch_node/3 with load-aware node picking enabled\" do\n    setup do\n      Application.put_env(:realtime, :node_balance_uptime_threshold_in_ms, 0)\n\n      on_exit(fn ->\n        Application.put_env(:realtime, :node_balance_uptime_threshold_in_ms, 999_999_999_999)\n      end)\n    end\n\n    test \"picks deterministic node when one node has insufficient data\" do\n      region = \"uptime-test-region\"\n      spawn_fake_node(region, :node_a)\n      spawn_fake_node(region, :node_b)\n\n      stub(Nodes, :node_load, fn\n        :node_a -> {:error, :not_enough_data}\n        :node_b -> 100\n      end)\n\n      results = for _ <- 1..10, do: Nodes.launch_node(region, node(), \"test-tenant-123\")\n\n      # Deterministic with hashed tenant ID\n      assert length(Enum.uniq(results)) == 1\n      assert Enum.uniq(results) == [:node_b]\n    end\n\n    test \"compares load between nodes and picks the least loaded deterministically\" do\n      {:ok, remote_node} = Clustered.start(nil, [{:realtime, :node_balance_uptime_threshold_in_ms, 0}])\n\n      region = \"load-test-region\"\n      spawn_fake_node(region, node())\n      spawn_fake_node(region, remote_node)\n\n      local_load = Nodes.node_load(node())\n      remote_load = Nodes.node_load(remote_node)\n\n      assert is_integer(local_load) and local_load >= 0\n      assert is_integer(remote_load) and remote_load >= 0\n\n      results = for _ <- 1..10, do: Nodes.launch_node(region, node(), \"test-tenant-789\")\n\n      # Should be deterministic - all same node within time bucket\n      assert length(Enum.uniq(results)) == 1\n      assert Enum.all?(results, &(&1 in [node(), remote_node]))\n    end\n  end\n\n  describe \"short_node_id_from_name/1\" do\n    test \"extracts short ID from fly.io-style IPv6 node name\" do\n      assert Nodes.short_node_id_from_name(:\"realtime-prod@fdaa:0:cc:a7b:b385:83c3:cfe3:2\") ==\n               \"83c3cfe3\"\n    end\n\n    test \"returns full name for localhost node\" do\n      assert Nodes.short_node_id_from_name(:\"pink@127.0.0.1\") == \"pink@127.0.0.1\"\n    end\n\n    test \"returns host for standard domain-style node names\" do\n      assert Nodes.short_node_id_from_name(:\"realtime@host.name.internal\") == \"host.name.internal\"\n    end\n\n    test \"returns host for simple IP node\" do\n      assert Nodes.short_node_id_from_name(:\"pink@10.0.1.1\") == \"10.0.1.1\"\n    end\n\n    test \"returns host for nonode@nohost\" do\n      assert Nodes.short_node_id_from_name(:nonode@nohost) == \"nohost\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/oid_test.exs",
    "content": "defmodule Realtime.OidTest do\n  use ExUnit.Case, async: true\n  import Realtime.Adapters.Postgres.OidDatabase\n  doctest Realtime.Adapters.Postgres.OidDatabase\nend\n"
  },
  {
    "path": "test/realtime/postgres_decoder_test.exs",
    "content": "defmodule Realtime.PostgresDecoderTest do\n  use ExUnit.Case, async: true\n  alias Realtime.Adapters.Postgres.Decoder\n\n  alias Decoder.Messages.Begin\n  alias Decoder.Messages.Commit\n  alias Decoder.Messages.Insert\n  alias Decoder.Messages.Origin\n  alias Decoder.Messages.Relation\n  alias Decoder.Messages.Relation.Column\n  alias Decoder.Messages.Type\n  alias Decoder.Messages.Unsupported\n\n  test \"decodes begin messages\" do\n    {:ok, expected_dt_no_microseconds, 0} = DateTime.from_iso8601(\"2019-07-18T17:02:35Z\")\n    expected_dt = DateTime.add(expected_dt_no_microseconds, 726_322, :microsecond)\n\n    assert Decoder.decode_message(\n             <<66, 0, 0, 0, 2, 167, 244, 168, 128, 0, 2, 48, 246, 88, 88, 213, 242, 0, 0, 2, 107>>,\n             %{}\n           ) ==\n             %Begin{commit_timestamp: expected_dt, final_lsn: {2, 2_817_828_992}, xid: 619}\n  end\n\n  test \"decodes commit messages\" do\n    {:ok, expected_dt_no_microseconds, 0} = DateTime.from_iso8601(\"2019-07-18T17:02:35Z\")\n    expected_dt = DateTime.add(expected_dt_no_microseconds, 726_322, :microsecond)\n\n    assert Decoder.decode_message(\n             <<67, 0, 0, 0, 0, 2, 167, 244, 168, 128, 0, 0, 0, 2, 167, 244, 168, 176, 0, 2, 48, 246, 88, 88, 213, 242>>,\n             %{}\n           ) == %Commit{\n             flags: [],\n             lsn: {2, 2_817_828_992},\n             end_lsn: {2, 2_817_829_040},\n             commit_timestamp: expected_dt\n           }\n  end\n\n  test \"decodes origin messages\" do\n    assert Decoder.decode_message(<<79, 0, 0, 0, 2, 167, 244, 168, 128>> <> \"Elmer Fud\", %{}) ==\n             %Origin{\n               origin_commit_lsn: {2, 2_817_828_992},\n               name: \"Elmer Fud\"\n             }\n  end\n\n  test \"decodes relation messages\" do\n    assert Decoder.decode_message(\n             <<82, 0, 0, 96, 0, 112, 117, 98, 108, 105, 99, 0, 102, 111, 111, 0, 100, 0, 2, 0, 98, 97, 114, 0, 0, 0, 0,\n               25, 255, 255, 255, 255, 1, 105, 100, 0, 0, 0, 0, 23, 255, 255, 255, 255>>,\n             %{}\n           ) == %Relation{\n             id: 24_576,\n             namespace: \"public\",\n             name: \"foo\",\n             replica_identity: :default,\n             columns: [\n               %Column{\n                 flags: [],\n                 name: \"bar\",\n                 type: \"text\",\n                 type_modifier: 4_294_967_295\n               },\n               %Column{\n                 flags: [:key],\n                 name: \"id\",\n                 type: \"int4\",\n                 type_modifier: 4_294_967_295\n               }\n             ]\n           }\n  end\n\n  test \"decodes type messages\" do\n    assert Decoder.decode_message(\n             <<89, 0, 0, 128, 52, 112, 117, 98, 108, 105, 99, 0, 101, 120, 97, 109, 112, 108, 101, 95, 116, 121, 112,\n               101, 0>>,\n             %{}\n           ) ==\n             %Type{\n               id: 32_820,\n               namespace: \"public\",\n               name: \"example_type\"\n             }\n  end\n\n  describe \"data message (TupleData) decoder\" do\n    setup do\n      relation = %{\n        id: 24_576,\n        namespace: \"public\",\n        name: \"foo\",\n        columns: [\n          %Column{name: \"id\", type: \"uuid\"},\n          %Column{name: \"bar\", type: \"text\"}\n        ]\n      }\n\n      %{relation: relation}\n    end\n\n    test \"decodes insert messages\", %{relation: relation} do\n      uuid = UUID.uuid4()\n      string = Generators.random_string()\n\n      data =\n        \"I\" <>\n          <<relation.id::integer-32>> <>\n          \"N\" <>\n          <<2::integer-16>> <>\n          \"b\" <>\n          <<16::integer-32>> <>\n          UUID.string_to_binary!(uuid) <>\n          \"b\" <>\n          <<byte_size(string)::integer-32>> <>\n          string\n\n      assert Decoder.decode_message(\n               data,\n               %{relation.id => relation}\n             ) == %Insert{\n               relation_id: relation.id,\n               tuple_data: {uuid, string}\n             }\n    end\n\n    test \"ignores unknown relations\", %{relation: relation} do\n      uuid = UUID.uuid4()\n      string = Generators.random_string()\n\n      data =\n        \"I\" <>\n          <<679::integer-32>> <>\n          \"N\" <>\n          <<2::integer-16>> <>\n          \"b\" <>\n          <<16::integer-32>> <>\n          UUID.string_to_binary!(uuid) <>\n          \"b\" <>\n          <<byte_size(string)::integer-32>> <>\n          string\n\n      assert Decoder.decode_message(\n               data,\n               %{relation.id => relation}\n             ) == %Unsupported{}\n    end\n\n    test \"decodes insert messages with null values\", %{relation: relation} do\n      string = Generators.random_string()\n\n      data =\n        \"I\" <>\n          <<relation.id::integer-32>> <>\n          \"N\" <>\n          <<2::integer-16>> <>\n          \"n\" <>\n          \"b\" <>\n          <<byte_size(string)::integer-32>> <>\n          string\n\n      assert Decoder.decode_message(data, %{relation.id => relation}) == %Insert{\n               relation_id: relation.id,\n               tuple_data: {nil, string}\n             }\n    end\n\n    test \"decodes insert messages with unchanged toasted values\", %{relation: relation} do\n      string = Generators.random_string()\n\n      data =\n        \"I\" <>\n          <<relation.id::integer-32>> <>\n          \"N\" <>\n          <<2::integer-16>> <>\n          \"u\" <>\n          \"b\" <>\n          <<byte_size(string)::integer-32>> <>\n          string\n\n      assert Decoder.decode_message(data, %{relation.id => relation}) == %Insert{\n               relation_id: relation.id,\n               tuple_data: {:unchanged_toast, string}\n             }\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/rate_counter/rate_counter_test.exs",
    "content": "defmodule Realtime.RateCounterTest do\n  use Realtime.DataCase, async: true\n\n  require Logger\n\n  alias Realtime.RateCounter\n  alias Realtime.RateCounter.Args\n  alias Realtime.GenCounter\n\n  import ExUnit.CaptureLog\n\n  describe \"new/2\" do\n    test \"starts a new rate counter without telemetry\" do\n      id = {:domain, :metric, Ecto.UUID.generate()}\n      args = %Args{id: id, opts: []}\n      assert {:ok, pid} = RateCounter.new(args)\n\n      assert %Realtime.RateCounter{\n               id: ^id,\n               avg: +0.0,\n               bucket: _,\n               max_bucket_len: 60,\n               tick: 1000,\n               tick_ref: _,\n               idle_shutdown: 600_000,\n               idle_shutdown_ref: _,\n               telemetry: %{emit: false},\n               limit: %{log: false}\n             } = :sys.get_state(pid)\n    end\n\n    test \"starts a new rate counter with telemetry\" do\n      :telemetry.detach(__MODULE__)\n\n      :telemetry.attach(\n        __MODULE__,\n        [:realtime, :rate_counter, :custom, :new_event],\n        &__MODULE__.handle_telemetry/4,\n        pid: self()\n      )\n\n      id = {:domain, :metric, Ecto.UUID.generate()}\n\n      args = %Args{\n        id: id,\n        opts: [\n          tick: 10,\n          telemetry: %{\n            event_name: [:custom, :new_event],\n            measurements: %{limit: 123},\n            metadata: %{tenant: \"abc\"}\n          }\n        ]\n      }\n\n      assert {:ok, pid} = RateCounter.new(args)\n\n      assert %Realtime.RateCounter{\n               id: ^id,\n               avg: +0.0,\n               bucket: _,\n               max_bucket_len: 60,\n               tick: 10,\n               tick_ref: _,\n               idle_shutdown: 600_000,\n               idle_shutdown_ref: _,\n               telemetry: %{\n                 emit: true,\n                 event_name: [:realtime, :rate_counter, :custom, :new_event],\n                 measurements: %{sum: 0, limit: 123},\n                 metadata: %{id: ^id, tenant: \"abc\"}\n               }\n             } = :sys.get_state(pid)\n\n      GenCounter.add(args.id, 10)\n\n      assert_receive {\n        [:realtime, :rate_counter, :custom, :new_event],\n        %{sum: 10, limit: 123},\n        %{id: ^id, tenant: \"abc\"}\n      }\n    end\n\n    test \"raise error when limit is specified without measurement\" do\n      id = {:domain, :metric, Ecto.UUID.generate()}\n\n      args = %Args{\n        id: id,\n        opts: [\n          tick: 100,\n          max_bucket_len: 10,\n          limit: [\n            value: 10,\n            log_fn: fn ->\n              Logger.error(\"ErrorMessage: Reason\", external_id: \"tenant123\", project: \"tenant123\")\n            end\n          ]\n        ]\n      }\n\n      assert {:error, {%KeyError{key: :measurement, term: _}, _}} = RateCounter.new(args)\n    end\n\n    test \"raise error when limit is specified without value\" do\n      id = {:domain, :metric, Ecto.UUID.generate()}\n\n      args = %Args{\n        id: id,\n        opts: [\n          tick: 100,\n          max_bucket_len: 10,\n          limit: [\n            measurement: :avg,\n            log_fn: fn ->\n              Logger.error(\"ErrorMessage: Reason\", external_id: \"tenant123\", project: \"tenant123\")\n            end\n          ]\n        ]\n      }\n\n      assert {:error, {%KeyError{key: :value, term: _}, _}} = RateCounter.new(args)\n    end\n\n    test \"raise error when limit is specified without log_fn\" do\n      id = {:domain, :metric, Ecto.UUID.generate()}\n\n      args = %Args{\n        id: id,\n        opts: [\n          tick: 100,\n          max_bucket_len: 10,\n          limit: [\n            measurement: :avg,\n            value: 100\n          ]\n        ]\n      }\n\n      assert {:error, {%KeyError{key: :log_fn, term: _}, _}} = RateCounter.new(args)\n    end\n\n    test \"starts a new rate counter with avg limit to log\" do\n      id = {:domain, :metric, Ecto.UUID.generate()}\n\n      args = %Args{\n        id: id,\n        opts: [\n          tick: 100,\n          max_bucket_len: 10,\n          limit: [\n            value: 10,\n            measurement: :avg,\n            log_fn: fn ->\n              Logger.error(\"ErrorMessage: Reason\", external_id: \"tenant123\", project: \"tenant123\")\n            end\n          ]\n        ]\n      }\n\n      assert {:ok, pid} = RateCounter.new(args)\n\n      assert %RateCounter{\n               id: ^id,\n               avg: +0.0,\n               bucket: _,\n               max_bucket_len: 10,\n               telemetry: %{emit: false},\n               limit: %{\n                 log: true,\n                 value: 10,\n                 triggered: false\n               }\n             } = :sys.get_state(pid)\n\n      log =\n        capture_log(fn ->\n          GenCounter.add(args.id, 50)\n          Process.sleep(300)\n        end)\n\n      assert {:ok, %RateCounter{limit: %{triggered: true}}} = RateCounter.get(args)\n      assert log =~ \"project=tenant123 external_id=tenant123 [error] ErrorMessage: Reason\"\n\n      # Only one log message should be emitted\n      # Splitting by the error message returns the error message and the rest of the log only\n      assert length(String.split(log, \"ErrorMessage: Reason\")) == 2\n\n      Process.sleep(300)\n\n      assert {:ok, %RateCounter{limit: %{triggered: false}}} = RateCounter.get(args)\n    end\n\n    test \"starts a new rate counter with sum limit to log\" do\n      id = {:domain, :metric, Ecto.UUID.generate()}\n\n      args = %Args{\n        id: id,\n        opts: [\n          tick: 100,\n          max_bucket_len: 5,\n          limit: [\n            value: 49,\n            measurement: :sum,\n            log_fn: fn ->\n              Logger.error(\"ErrorMessage: Reason\", external_id: \"tenant123\", project: \"tenant123\")\n            end\n          ]\n        ]\n      }\n\n      assert {:ok, pid} = RateCounter.new(args)\n\n      assert %RateCounter{\n               id: ^id,\n               avg: +0.0,\n               sum: 0,\n               bucket: _,\n               max_bucket_len: 5,\n               telemetry: %{emit: false},\n               limit: %{\n                 log: true,\n                 value: 49,\n                 measurement: :sum,\n                 triggered: false\n               }\n             } = :sys.get_state(pid)\n\n      log =\n        capture_log(fn ->\n          GenCounter.add(args.id, 100)\n          Process.sleep(120)\n        end)\n\n      assert {:ok, %RateCounter{sum: sum, limit: %{triggered: true}}} = RateCounter.get(args)\n      assert sum > 49\n      assert log =~ \"project=tenant123 external_id=tenant123 [error] ErrorMessage: Reason\"\n\n      # Only one log message should be emitted\n      # Splitting by the error message returns the error message and the rest of the log only\n      assert length(String.split(log, \"ErrorMessage: Reason\")) == 2\n\n      Process.sleep(600)\n\n      assert {:ok, %RateCounter{sum: 0, limit: %{triggered: false}}} = RateCounter.get(args)\n    end\n\n    test \"reset counter if GenCounter already had something\" do\n      args = %Args{id: {:domain, :metric, Ecto.UUID.generate()}}\n      GenCounter.add(args.id, 100)\n      assert {:ok, _} = RateCounter.new(args)\n      assert GenCounter.get(args.id) == 0\n    end\n\n    test \"rate counters are unique for an Erlang term\" do\n      args = %Args{id: {:domain, :metric, Ecto.UUID.generate()}}\n      {:ok, pid} = RateCounter.new(args)\n\n      assert {:error, {:already_started, ^pid}} = RateCounter.new(args)\n    end\n\n    test \"rate counters shut themselves down when no activity occurs on the GenCounter\" do\n      args = %Args{id: {:domain, :metric, Ecto.UUID.generate()}}\n      {:ok, pid} = RateCounter.new(args, idle_shutdown: 100)\n\n      Process.monitor(pid)\n      assert_receive {:DOWN, _ref, :process, ^pid, :normal}, 200\n      # Cache has not expired yet\n      assert {:ok, %RateCounter{}} = Cachex.get(RateCounter, args.id)\n      Process.sleep(2000)\n      assert {:ok, nil} = Cachex.get(RateCounter, args.id)\n\n      # Ok new RateCounter automatically started now\n      assert {:ok, %RateCounter{}} = RateCounter.get(args)\n\n      [{new_pid, _}] = Registry.lookup(Realtime.Registry.Unique, {RateCounter, :rate_counter, args.id})\n      assert new_pid != pid\n    end\n\n    test \"rate counters reset GenCounter to zero after one tick and average the bucket\" do\n      args = %Args{id: {:domain, :metric, Ecto.UUID.generate()}}\n      {:ok, _pid} = RateCounter.new(args, tick: 5)\n      assert GenCounter.add(args.id) == 1\n      Process.sleep(10)\n\n      assert {:ok,\n              %RateCounter{\n                avg: avg,\n                bucket: bucket,\n                id: _id,\n                idle_shutdown: _,\n                idle_shutdown_ref: _ref,\n                max_bucket_len: 60,\n                tick: 5,\n                tick_ref: _ref2\n              }} = RateCounter.get(args)\n\n      assert 1 in bucket\n      assert avg > 0.0\n\n      assert GenCounter.get(args.id) == 0\n    end\n  end\n\n  describe \"publish_update/1\" do\n    test \"cause shutdown with update message from update topic\" do\n      args = %Args{id: {:domain, :metric, Ecto.UUID.generate()}}\n      {:ok, pid} = RateCounter.new(args)\n\n      Process.monitor(pid)\n      RateCounter.publish_update(args.id)\n\n      assert_receive {:DOWN, _ref, :process, ^pid, :normal}\n    end\n  end\n\n  describe \"get/1\" do\n    test \"gets the state of a rate counter\" do\n      args = %Args{id: {:domain, :metric, Ecto.UUID.generate()}}\n      {:ok, _} = RateCounter.new(args)\n\n      assert {:ok, %RateCounter{}} = RateCounter.get(args)\n    end\n\n    test \"creates counter if not started yet\" do\n      args = %Args{id: {:domain, :metric, Ecto.UUID.generate()}}\n\n      assert {:ok, %RateCounter{}} = RateCounter.get(args)\n    end\n  end\n\n  def handle_telemetry(event, measures, metadata, pid: pid), do: send(pid, {event, measures, metadata})\nend\n"
  },
  {
    "path": "test/realtime/repo_replica_test.exs",
    "content": "defmodule Realtime.Repo.ReplicaTest do\n  # application env being changed\n  use ExUnit.Case, async: false\n  alias Realtime.Repo.Replica\n\n  setup do\n    previous_platform = Application.get_env(:realtime, :platform)\n    previous_region = Application.get_env(:realtime, :region)\n    previous_master_region = Application.get_env(:realtime, :master_region)\n    previous_main_replica = Application.get_env(:realtime, Replica)\n\n    on_exit(fn ->\n      Application.put_env(:realtime, :platform, previous_platform)\n      Application.put_env(:realtime, :region, previous_region)\n      Application.put_env(:realtime, :master_region, previous_master_region)\n      Application.delete_env(:realtime, Replica)\n\n      if previous_main_replica do\n        Application.put_env(:realtime, Replica, previous_main_replica)\n      end\n    end)\n  end\n\n  describe \"handle aws platform\" do\n    for {region, mod} <- Replica.replicas_aws() do\n      setup do\n        Application.put_env(:realtime, :platform, :aws)\n        Application.put_env(:realtime, :master_region, \"special-region\")\n        :ok\n      end\n\n      test \"handles #{region} region\" do\n        Application.put_env(:realtime, :region, unquote(region))\n        replica_asserts(unquote(mod), Replica.replica())\n      end\n\n      test \"defaults to Realtime.Repo if region is equal to master region on #{region}\" do\n        Application.put_env(:realtime, :region, unquote(region))\n        Application.put_env(:realtime, :master_region, unquote(region))\n        replica_asserts(Realtime.Repo, Replica.replica())\n      end\n    end\n\n    test \"defaults to Realtime.Repo if region is not configured\" do\n      Application.put_env(:realtime, :region, \"unknown\")\n      replica_asserts(Realtime.Repo, Replica.replica())\n    end\n  end\n\n  describe \"handle fly platform\" do\n    for {region, mod} <- Replica.replicas_fly() do\n      setup do\n        Application.put_env(:realtime, :platform, :fly)\n        Application.put_env(:realtime, :master_region, \"special-region\")\n        :ok\n      end\n\n      test \"handles #{region} region\" do\n        Application.put_env(:realtime, :region, unquote(region))\n        replica_asserts(unquote(mod), Replica.replica())\n      end\n    end\n\n    test \"defaults to Realtime.Repo if region is not configured\" do\n      Application.put_env(:realtime, :region, \"unknown\")\n      replica_asserts(Realtime.Repo, Replica.replica())\n    end\n  end\n\n  describe \"main replica module configuration\" do\n    setup do\n      Application.put_env(:realtime, Replica, hostname: \"test-replica-host\")\n      :ok\n    end\n\n    test \"uses main replica module when configured on AWS platform\" do\n      Application.put_env(:realtime, :platform, :aws)\n      Application.put_env(:realtime, :region, \"us-west-1\")\n      Application.put_env(:realtime, :master_region, \"us-east-1\")\n\n      replica_asserts(Replica, Replica.replica())\n    end\n\n    test \"uses main replica module when configured on Fly platform\" do\n      Application.put_env(:realtime, :platform, :fly)\n      Application.put_env(:realtime, :region, \"sea\")\n      Application.put_env(:realtime, :master_region, \"sjc\")\n\n      replica_asserts(Replica, Replica.replica())\n    end\n\n    test \"still defaults to Realtime.Repo when region matches master region\" do\n      Application.put_env(:realtime, :platform, :aws)\n      Application.put_env(:realtime, :region, \"us-west-1\")\n      Application.put_env(:realtime, :master_region, \"us-west-1\")\n\n      replica_asserts(Realtime.Repo, Replica.replica())\n    end\n\n    test \"uses main replica module when region is unknown\" do\n      Application.put_env(:realtime, :platform, :aws)\n      Application.put_env(:realtime, :region, \"unknown-region\")\n      Application.put_env(:realtime, :master_region, \"us-east-1\")\n\n      replica_asserts(Replica, Replica.replica())\n    end\n\n    test \"uses main replica module without platform configuration\" do\n      Application.delete_env(:realtime, :platform)\n      Application.put_env(:realtime, :region, \"us-west-1\")\n      Application.put_env(:realtime, :master_region, \"us-east-1\")\n\n      replica_asserts(Replica, Replica.replica())\n    end\n  end\n\n  defp replica_asserts(mod, replica) do\n    assert mod == replica\n    assert [Ecto.Repo] == replica.__info__(:attributes) |> Keyword.get(:behaviour)\n  end\nend\n"
  },
  {
    "path": "test/realtime/rpc_test.exs",
    "content": "defmodule Realtime.RpcTest do\n  use ExUnit.Case\n\n  import ExUnit.CaptureLog\n\n  alias Realtime.Rpc\n\n  @aux_mod (quote do\n              defmodule TestRpc do\n                def test_raise, do: raise(\"test\")\n                def test_timeout, do: Process.sleep(200)\n                def test_success, do: {:ok, \"success\"}\n              end\n            end)\n\n  Code.eval_quoted(@aux_mod)\n\n  def handle_telemetry(event, measurements, metadata, pid: pid), do: send(pid, {event, measurements, metadata})\n\n  setup do\n    {:ok, node} = Clustered.start(@aux_mod)\n    :telemetry.attach(__MODULE__, [:realtime, :rpc], &__MODULE__.handle_telemetry/4, pid: self())\n    on_exit(fn -> :telemetry.detach(__MODULE__) end)\n\n    %{node: node}\n  end\n\n  describe \"call/5\" do\n    test \"successful RPC call returns exactly what the original function returns\", %{node: node} do\n      assert {:ok, \"success\"} = Rpc.call(node, TestRpc, :test_success, [])\n      origin_node = node()\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        mod: TestRpc,\n                        func: :test_success,\n                        origin_node: ^origin_node,\n                        target_node: ^node\n                      }}\n    end\n\n    test \"raised exceptions are properly caught and logged\", %{node: node} do\n      assert {:badrpc, {:EXIT, {%RuntimeError{message: \"test\"}, [{TestRpc, :test_raise, 0, _}]}}} =\n               Rpc.call(node, TestRpc, :test_raise, [])\n\n      origin_node = node()\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        mod: TestRpc,\n                        func: :test_raise,\n                        origin_node: ^origin_node,\n                        target_node: ^node\n                      }}\n    end\n\n    test \"timeouts are properly caught and logged\", %{node: node} do\n      assert {:badrpc, :timeout} =\n               Rpc.call(node, TestRpc, :test_timeout, [], timeout: 100)\n\n      origin_node = node()\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        mod: TestRpc,\n                        func: :test_timeout,\n                        origin_node: ^origin_node,\n                        target_node: ^node\n                      }}\n    end\n  end\n\n  describe \"enhanced_call/5\" do\n    test \"successful RPC call returns exactly what the original function returns\", %{node: node} do\n      assert {:ok, \"success\"} = Rpc.enhanced_call(node, TestRpc, :test_success, [], tenant_id: \"123\")\n      origin_node = node()\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        mod: TestRpc,\n                        func: :test_success,\n                        origin_node: ^origin_node,\n                        target_node: ^node,\n                        success: true\n                      }}\n    end\n\n    test \"raised exceptions are properly caught and logged\", %{node: node} do\n      assert capture_log(fn ->\n               assert {:error, :rpc_error, %RuntimeError{message: \"test\"}} =\n                        Rpc.enhanced_call(node, TestRpc, :test_raise, [], tenant_id: \"123\")\n             end) =~ \"project=123 external_id=123 [error] ErrorOnRpcCall\"\n\n      origin_node = node()\n\n      assert_receive {[:realtime, :rpc], %{latency: _},\n                      %{\n                        mod: TestRpc,\n                        func: :test_raise,\n                        origin_node: ^origin_node,\n                        target_node: ^node,\n                        success: false\n                      }}\n    end\n\n    test \"timeouts are properly caught and logged\", %{node: node} do\n      assert capture_log(fn ->\n               assert {:error, :rpc_error, :timeout} =\n                        Rpc.enhanced_call(node, TestRpc, :test_timeout, [], timeout: 100)\n             end) =~ \"ErrorOnRpcCall\"\n\n      origin_node = node()\n\n      assert_receive {[:realtime, :rpc], %{latency: 0},\n                      %{\n                        mod: TestRpc,\n                        func: :test_timeout,\n                        origin_node: ^origin_node,\n                        target_node: ^node\n                      }}\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/signal_handler_test.exs",
    "content": "defmodule Realtime.SignalHandlerTest do\n  use ExUnit.Case\n  import ExUnit.CaptureLog\n  alias Realtime.SignalHandler\n\n  defmodule FakeHandler do\n    def handle_event(signal, _state), do: send(self(), signal)\n  end\n\n  setup do\n    on_exit(fn ->\n      Application.put_env(:realtime, :shutdown_in_progress, false)\n    end)\n  end\n\n  describe \"signal handling\" do\n    test \"sends signal to handler_mod\" do\n      {:ok, state} = SignalHandler.init({%{handler_mod: FakeHandler}, :ok})\n\n      assert capture_log(fn -> SignalHandler.handle_event(:sigterm, state) end) =~\n               \"SignalHandler: :sigterm received\"\n\n      assert_receive :sigterm\n    end\n\n    test \"sets shutdown_in_progress on sigterm\" do\n      {:ok, state} = SignalHandler.init({%{handler_mod: FakeHandler}, :ok})\n\n      capture_log(fn -> SignalHandler.handle_event(:sigterm, state) end)\n\n      assert Application.get_env(:realtime, :shutdown_in_progress) == true\n    end\n\n    test \"does not set shutdown_in_progress on non-sigterm signals\" do\n      Application.put_env(:realtime, :shutdown_in_progress, false)\n      {:ok, state} = SignalHandler.init({%{handler_mod: FakeHandler}, :ok})\n\n      capture_log(fn -> SignalHandler.handle_event(:sigusr1, state) end)\n\n      refute Application.get_env(:realtime, :shutdown_in_progress)\n    end\n  end\n\n  describe \"gen_event callbacks\" do\n    test \"handle_info delegates to erl_signal_handler\" do\n      {:ok, state} = SignalHandler.init({%{handler_mod: FakeHandler}, :ok})\n      assert {:ok, _state} = SignalHandler.handle_info(:some_info, state)\n    end\n\n    test \"handle_call delegates to erl_signal_handler\" do\n      {:ok, state} = SignalHandler.init({%{handler_mod: FakeHandler}, :ok})\n      assert {:ok, _reply, _state} = SignalHandler.handle_call(:some_call, state)\n    end\n  end\n\n  describe \"shutdown_in_progress?/1\" do\n    test \"shutdown_in_progress? returns error when shutdown is in progress\" do\n      Application.put_env(:realtime, :shutdown_in_progress, true)\n      assert SignalHandler.shutdown_in_progress?() == {:error, :shutdown_in_progress}\n    end\n\n    test \"shutdown_in_progress? returns ok when no shutdown in progress\" do\n      Application.put_env(:realtime, :shutdown_in_progress, false)\n      assert SignalHandler.shutdown_in_progress?() == :ok\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/syn_handler_test.exs",
    "content": "defmodule Realtime.SynHandlerTest do\n  use Realtime.DataCase, async: false\n  import ExUnit.CaptureLog\n  alias Realtime.SynHandler\n  alias Realtime.Tenants.Connect\n  alias RealtimeWeb.Endpoint\n\n  @mod SynHandler\n  @name \"test\"\n  @topic \"syn_handler\"\n\n  @aux_mod (quote do\n              defmodule FakeConnect do\n                use GenServer\n\n                def start_link([tenant_id, region, opts]) do\n                  name = {Connect, tenant_id, %{conn: nil, region: region}}\n                  gen_opts = [name: {:via, :syn, name}]\n                  GenServer.start_link(FakeConnect, [tenant_id, opts], gen_opts)\n                end\n\n                def init([tenant_id, opts]) do\n                  conn = Keyword.get(opts, :conn, \"remote_conn\")\n                  :syn.update_registry(Connect, tenant_id, fn _pid, meta -> %{meta | conn: conn} end)\n\n                  if opts[:trap_exit], do: Process.flag(:trap_exit, true)\n\n                  {:ok, nil}\n                end\n\n                def handle_info(:shutdown_connect, state), do: {:stop, :normal, state}\n                def handle_info(_, state), do: {:noreply, state}\n              end\n            end)\n\n  Code.eval_quoted(@aux_mod)\n\n  # > :\"main@127.0.0.11\" < :\"atest@127.0.0.1\"\n  # false\n  # iex(2)> :erlang.phash2(\"tenant123\", 2)\n  # 0\n  # iex(3)> :erlang.phash2(\"tenant999\", 2)\n  # 1\n  describe \"integration test with a Connect conflict name=atest\" do\n    setup do\n      {:ok, pid, node} =\n        Clustered.start_disconnected(@aux_mod, name: :atest, extra_config: [{:realtime, :region, \"ap-southeast-2\"}])\n\n      %{peer_pid: pid, node: node}\n    end\n\n    @tag tenant_id: \"tenant999\"\n    test \"tenant hash = 1\", %{node: node, peer_pid: peer_pid, tenant_id: tenant_id} do\n      assert :erlang.phash2(tenant_id, 2) == 1\n      local_pid = start_supervised!({FakeConnect, [tenant_id, \"us-east-1\", [conn: \"local_conn\"]]})\n      {:ok, remote_pid} = :peer.call(peer_pid, FakeConnect, :start_link, [[tenant_id, \"ap-southeast-2\", []]])\n      on_exit(fn -> Process.exit(remote_pid, :brutal_kill) end)\n\n      log =\n        capture_log(fn ->\n          # Connect to peer node to cause a conflict on syn\n          true = Node.connect(node)\n          # Give some time for the conflict resolution to happen on the other node\n          Process.sleep(500)\n\n          # Both nodes agree\n          assert {^remote_pid, %{region: \"ap-southeast-2\", conn: \"remote_conn\"}} =\n                   :peer.call(peer_pid, :syn, :lookup, [Connect, tenant_id])\n\n          assert {^remote_pid, %{region: \"ap-southeast-2\", conn: \"remote_conn\"}} = :syn.lookup(Connect, tenant_id)\n\n          assert :peer.call(peer_pid, Process, :alive?, [remote_pid])\n\n          refute Process.alive?(local_pid)\n        end)\n\n      assert log =~ \"stop local process: #{inspect(local_pid)}\"\n      assert log =~ \"Successfully stopped #{inspect(local_pid)}\"\n\n      assert log =~\n               \"Elixir.Realtime.Tenants.Connect terminated due to syn conflict resolution: \\\"#{tenant_id}\\\" #{inspect(local_pid)}\"\n    end\n\n    @tag tenant_id: \"tenant123\"\n    test \"tenant hash = 0\", %{node: node, peer_pid: peer_pid, tenant_id: tenant_id} do\n      assert :erlang.phash2(tenant_id, 2) == 0\n      {:ok, remote_pid} = :peer.call(peer_pid, FakeConnect, :start_link, [[tenant_id, \"ap-southeast-2\", []]])\n      local_pid = start_supervised!({FakeConnect, [tenant_id, \"us-east-1\", [conn: \"local_conn\"]]})\n      on_exit(fn -> Process.exit(remote_pid, :kill) end)\n\n      log =\n        capture_log(fn ->\n          # Connect to peer node to cause a conflict on syn\n          true = Node.connect(node)\n          # Give some time for the conflict resolution to happen on the other node\n          Process.sleep(500)\n\n          # Both nodes agree\n          assert {^local_pid, %{region: \"us-east-1\", conn: \"local_conn\"}} = :syn.lookup(Connect, tenant_id)\n\n          assert {^local_pid, %{region: \"us-east-1\", conn: \"local_conn\"}} =\n                   :peer.call(peer_pid, :syn, :lookup, [Connect, tenant_id])\n\n          refute :peer.call(peer_pid, Process, :alive?, [remote_pid])\n\n          assert Process.alive?(local_pid)\n        end)\n\n      assert log =~ \"remote process will be stopped: #{inspect(remote_pid)}\"\n    end\n  end\n\n  # > :\"main@127.0.0.11\" < :\"test@127.0.0.1\"\n  # true\n  # iex(2)> :erlang.phash2(\"tenant123\", 2)\n  # 0\n  # iex(3)> :erlang.phash2(\"tenant999\", 2)\n  # 1\n  describe \"integration test with a Connect conflict name=test\" do\n    setup do\n      {:ok, pid, node} =\n        Clustered.start_disconnected(@aux_mod, name: :test, extra_config: [{:realtime, :region, \"ap-southeast-2\"}])\n\n      %{peer_pid: pid, node: node}\n    end\n\n    @tag tenant_id: \"tenant999\"\n    test \"tenant hash = 1\", %{node: node, peer_pid: peer_pid, tenant_id: tenant_id} do\n      assert :erlang.phash2(tenant_id, 2) == 1\n      Endpoint.subscribe(\"connect:#{tenant_id}\")\n      local_pid = start_supervised!({FakeConnect, [tenant_id, \"us-east-1\", [conn: \"local_conn\"]]})\n\n      {:ok, remote_pid} = :peer.call(peer_pid, FakeConnect, :start_link, [[tenant_id, \"ap-southeast-2\", []]])\n\n      on_exit(fn -> Process.exit(remote_pid, :brutal_kill) end)\n\n      log =\n        capture_log(fn ->\n          # Connect to peer node to cause a conflict on syn\n          true = Node.connect(node)\n          # Give some time for the conflict resolution to happen on the other node\n          Process.sleep(500)\n\n          # Both nodes agree\n          assert {^local_pid, %{region: \"us-east-1\", conn: \"local_conn\"}} = :syn.lookup(Connect, tenant_id)\n\n          assert {^local_pid, %{region: \"us-east-1\", conn: \"local_conn\"}} =\n                   :peer.call(peer_pid, :syn, :lookup, [Connect, tenant_id])\n\n          refute :peer.call(peer_pid, Process, :alive?, [remote_pid])\n\n          assert Process.alive?(local_pid)\n        end)\n\n      assert log =~ \"remote process will be stopped: #{inspect(remote_pid)}\"\n    end\n\n    @tag tenant_id: \"tenant123\"\n    test \"tenant hash = 0\", %{node: node, peer_pid: peer_pid, tenant_id: tenant_id} do\n      assert :erlang.phash2(tenant_id, 2) == 0\n      # Start remote process first\n      {:ok, remote_pid} = :peer.call(peer_pid, FakeConnect, :start_link, [[tenant_id, \"ap-southeast-2\", []]])\n\n      on_exit(fn -> Process.exit(remote_pid, :kill) end)\n\n      # start connect locally later\n      local_pid = start_supervised!({FakeConnect, [tenant_id, \"us-east-1\", [conn: \"local_conn\"]]})\n\n      log =\n        capture_log(fn ->\n          # Connect to peer node to cause a conflict on syn\n          true = Node.connect(node)\n          # Give some time for the conflict resolution to happen on the other node\n          Process.sleep(500)\n\n          # Both nodes agree\n          assert {^remote_pid, %{region: \"ap-southeast-2\", conn: \"remote_conn\"}} =\n                   :peer.call(peer_pid, :syn, :lookup, [Connect, tenant_id])\n\n          assert {^remote_pid, %{region: \"ap-southeast-2\", conn: \"remote_conn\"}} = :syn.lookup(Connect, tenant_id)\n\n          assert :peer.call(peer_pid, Process, :alive?, [remote_pid])\n\n          refute Process.alive?(local_pid)\n        end)\n\n      assert log =~ \"stop local process: #{inspect(local_pid)}\"\n      assert log =~ \"Successfully stopped #{inspect(local_pid)}\"\n\n      assert log =~\n               \"Elixir.Realtime.Tenants.Connect terminated due to syn conflict resolution: \\\"#{tenant_id}\\\" #{inspect(local_pid)}\"\n    end\n\n    @tag tenant_id: \"tenant123\"\n    test \"tenant hash = 0 but timed out stopping\", %{node: node, peer_pid: peer_pid, tenant_id: tenant_id} do\n      assert :erlang.phash2(tenant_id, 2) == 0\n      # Start remote process first\n      {:ok, remote_pid} = :peer.call(peer_pid, FakeConnect, :start_link, [[tenant_id, \"ap-southeast-2\", []]])\n\n      on_exit(fn -> Process.exit(remote_pid, :kill) end)\n\n      # start connect locally later\n      local_pid = start_supervised!({FakeConnect, [tenant_id, \"us-east-1\", [conn: \"local_conn\", trap_exit: true]]})\n\n      log =\n        capture_log(fn ->\n          # Connect to peer node to cause a conflict on syn\n          true = Node.connect(node)\n          assert_process_down(local_pid, :killed, 6000)\n\n          # Both nodes agree\n          assert {^remote_pid, %{region: \"ap-southeast-2\", conn: \"remote_conn\"}} =\n                   :peer.call(peer_pid, :syn, :lookup, [Connect, tenant_id])\n\n          assert {^remote_pid, %{region: \"ap-southeast-2\", conn: \"remote_conn\"}} = :syn.lookup(Connect, tenant_id)\n\n          assert :peer.call(peer_pid, Process, :alive?, [remote_pid])\n\n          refute Process.alive?(local_pid)\n        end)\n\n      assert log =~ \"stop local process: #{inspect(local_pid)}\"\n      assert log =~ \"Timed out while waiting for process #{inspect(local_pid)} to stop. Sending kill exit signal\"\n\n      assert log =~\n               \"Elixir.Realtime.Tenants.Connect terminated due to syn conflict resolution: \\\"#{tenant_id}\\\" #{inspect(local_pid)}\"\n    end\n  end\n\n  describe \"on_process_registered/5\" do\n    test \"emits telemetry event for process registration\" do\n      pid = self()\n      meta = %{some: :meta}\n      reason = :normal\n\n      # Attach a test handler to capture the telemetry event\n      test_pid = self()\n      handler_id = [:test, :syn_handler, :registered]\n\n      :telemetry.attach(\n        handler_id,\n        [:syn, @mod, :registered],\n        fn event, measurements, metadata, _config ->\n          send(test_pid, {:telemetry_event, event, measurements, metadata})\n        end,\n        nil\n      )\n\n      on_exit(fn -> :telemetry.detach(handler_id) end)\n\n      assert SynHandler.on_process_registered(@mod, @name, pid, meta, reason) == :ok\n\n      assert_receive {:telemetry_event, [:syn, @mod, :registered], %{}, %{name: @name}}\n    end\n  end\n\n  describe \"on_process_unregistered/5\" do\n    setup do\n      RealtimeWeb.Endpoint.subscribe(\"#{@topic}:#{@name}\")\n    end\n\n    test \"emits telemetry event for process unregistration\" do\n      reason = :normal\n      pid = self()\n\n      # Attach a test handler to capture the telemetry event\n      test_pid = self()\n      handler_id = [:test, :syn_handler, :unregistered]\n\n      :telemetry.attach(\n        handler_id,\n        [:syn, @mod, :unregistered],\n        fn event, measurements, metadata, _config ->\n          send(test_pid, {:telemetry_event, event, measurements, metadata})\n        end,\n        nil\n      )\n\n      on_exit(fn -> :telemetry.detach(handler_id) end)\n\n      capture_log(fn ->\n        assert SynHandler.on_process_unregistered(@mod, @name, pid, %{}, reason) == :ok\n      end)\n\n      assert_receive {:telemetry_event, [:syn, @mod, :unregistered], %{}, %{name: @name}}\n\n      topic = \"#{@topic}:#{@name}\"\n      event = \"#{@topic}_down\"\n      assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: ^event, payload: %{reason: ^reason, pid: ^pid}}\n    end\n\n    test \"it handles :syn_conflict_resolution reason\" do\n      reason = :syn_conflict_resolution\n      pid = self()\n\n      log =\n        capture_log(fn ->\n          assert SynHandler.on_process_unregistered(@mod, @name, pid, %{}, reason) == :ok\n        end)\n\n      topic = \"#{@topic}:#{@name}\"\n      event = \"#{@topic}_down\"\n\n      assert log =~ \"#{@mod} terminated due to syn conflict resolution: #{inspect(@name)} #{inspect(self())}\"\n      assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: ^event, payload: %{reason: ^reason, pid: ^pid}}\n    end\n\n    test \"it handles other reasons\" do\n      reason = :other_reason\n      pid = self()\n\n      log =\n        capture_log(fn ->\n          assert SynHandler.on_process_unregistered(@mod, @name, pid, %{}, reason) == :ok\n        end)\n\n      topic = \"#{@topic}:#{@name}\"\n      event = \"#{@topic}_down\"\n\n      refute log =~ \"#{@mod} terminated: #{inspect(@name)} #{node()}\"\n\n      assert_receive %Phoenix.Socket.Broadcast{\n                       topic: ^topic,\n                       event: ^event,\n                       payload: %{reason: ^reason, pid: ^pid}\n                     },\n                     500\n    end\n  end\n\n  defp assert_process_down(pid, reason, timeout) do\n    ref = Process.monitor(pid)\n\n    if reason do\n      assert_receive {:DOWN, ^ref, :process, ^pid, ^reason}, timeout\n    else\n      assert_receive {:DOWN, ^ref, :process, ^pid, _reason}, timeout\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/telemetry/logger_test.exs",
    "content": "defmodule Realtime.Telemetry.LoggerTest do\n  use ExUnit.Case\n  import ExUnit.CaptureLog\n  alias Realtime.Telemetry.Logger, as: TelemetryLogger\n\n  setup do\n    level = Logger.level()\n    Logger.configure(level: :info)\n    on_exit(fn -> Logger.configure(level: level) end)\n  end\n\n  describe \"logger backend initialization\" do\n    test \"logs on telemetry event\" do\n      start_link_supervised!({TelemetryLogger, handler_id: \"telemetry-logger-test\"})\n\n      assert capture_log(fn ->\n               :telemetry.execute([:realtime, :connections], %{count: 1}, %{tenant: \"tenant\"})\n             end) =~ \"Billing metrics: [:realtime, :connections]\"\n    end\n\n    test \"ignores events without tenant\" do\n      start_link_supervised!({TelemetryLogger, handler_id: \"telemetry-logger-test\"})\n\n      refute capture_log(fn ->\n               :telemetry.execute([:realtime, :connections], %{count: 1}, %{})\n             end) =~ \"Billing metrics: [:realtime, :connections]\"\n    end\n  end\n\n  describe \"handle_info/2\" do\n    test \"ignores unexpected messages\" do\n      assert {:noreply, []} = TelemetryLogger.handle_info(:unexpected, [])\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/authorization_remote_test.exs",
    "content": "defmodule Realtime.Tenants.AuthorizationRemoteTest do\n  # async: false due to usage of Clustered\n  use RealtimeWeb.ConnCase, async: false\n  use Mimic\n\n  import ExUnit.CaptureLog\n\n  require Phoenix.ChannelTest\n\n  alias Realtime.Database\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Authorization\n  alias Realtime.Tenants.Authorization.Policies\n  alias Realtime.Tenants.Authorization.Policies.BroadcastPolicies\n  alias Realtime.Tenants.Authorization.Policies.PresencePolicies\n  alias Realtime.Tenants.Connect\n\n  setup [:remote_rls_context]\n\n  describe \"get_authorizations\" do\n    @tag role: \"authenticated\",\n         policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"authenticated user has expected policies\", context do\n      {:ok, policies} =\n        Authorization.get_read_authorizations(\n          %Policies{},\n          context.db_conn,\n          context.authorization_context\n        )\n\n      assert %Policies{\n               broadcast: %BroadcastPolicies{read: true, write: nil},\n               presence: %PresencePolicies{read: true, write: nil}\n             } == policies\n\n      {:ok, policies} =\n        Authorization.get_write_authorizations(\n          policies,\n          context.db_conn,\n          context.authorization_context\n        )\n\n      assert %Policies{\n               broadcast: %BroadcastPolicies{read: true, write: true},\n               presence: %PresencePolicies{read: true, write: true}\n             } == policies\n    end\n\n    @tag role: \"anon\",\n         policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"anon user has no policies\", context do\n      {:ok, policies} =\n        Authorization.get_read_authorizations(\n          %Policies{},\n          context.db_conn,\n          context.authorization_context\n        )\n\n      assert %Policies{\n               broadcast: %BroadcastPolicies{read: false, write: nil},\n               presence: %PresencePolicies{read: false, write: nil}\n             } == policies\n\n      {:ok, policies} =\n        Authorization.get_write_authorizations(\n          policies,\n          context.db_conn,\n          context.authorization_context\n        )\n\n      assert %Policies{\n               broadcast: %BroadcastPolicies{read: false, write: false},\n               presence: %PresencePolicies{read: false, write: false}\n             } == policies\n    end\n\n    @tag role: \"anon\",\n         policies: []\n    test \"db process is down\", context do\n      db_conn = :erpc.call(context.node, :erlang, :self, [])\n\n      {:error, :increase_connection_pool} =\n        Authorization.get_read_authorizations(%Policies{}, db_conn, context.authorization_context)\n\n      {:error, :increase_connection_pool} =\n        Authorization.get_write_authorizations(%Policies{}, db_conn, context.authorization_context)\n    end\n\n    @tag role: \"anon\", policies: []\n    test \"get_read_authorizations rate limit when db has many connection errors\", context do\n      pid = :erpc.call(context.node, :erlang, :self, [])\n\n      log =\n        capture_log(fn ->\n          for _ <- 1..6 do\n            {:error, :increase_connection_pool} =\n              Authorization.get_read_authorizations(%Policies{}, pid, context.authorization_context)\n          end\n\n          rate_counter = Realtime.Tenants.authorization_errors_per_second_rate(context.tenant)\n          RateCounterHelper.tick!(rate_counter)\n\n          for _ <- 1..10 do\n            {:error, :increase_connection_pool} =\n              Authorization.get_read_authorizations(%Policies{}, pid, context.authorization_context)\n          end\n        end)\n\n      assert log =~ \"IncreaseConnectionPool: Too many database timeouts\"\n      assert length(String.split(log, \"IncreaseConnectionPool: Too many database timeouts\")) == 2\n    end\n\n    @tag role: \"anon\", policies: []\n    test \"get_write_authorizations rate limit when db has many connection errors\", context do\n      pid = spawn(fn -> :ok end)\n\n      log =\n        capture_log(fn ->\n          for _ <- 1..6 do\n            {:error, :increase_connection_pool} =\n              Authorization.get_write_authorizations(%Policies{}, pid, context.authorization_context)\n          end\n\n          rate_counter = Realtime.Tenants.authorization_errors_per_second_rate(context.tenant)\n          RateCounterHelper.tick!(rate_counter)\n\n          for _ <- 1..10 do\n            {:error, :increase_connection_pool} =\n              Authorization.get_write_authorizations(%Policies{}, pid, context.authorization_context)\n          end\n        end)\n\n      assert log =~ \"IncreaseConnectionPool: Too many database timeouts\"\n      assert length(String.split(log, \"IncreaseConnectionPool: Too many database timeouts\")) == 2\n    end\n  end\n\n  describe \"database error\" do\n    @tag role: \"authenticated\",\n         policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence],\n         timeout: :timer.minutes(1)\n    test \"handles small pool size\", context do\n      task =\n        Task.async(fn ->\n          :erpc.call(node(context.db_conn), Postgrex, :query!, [\n            context.db_conn,\n            \"SELECT pg_sleep(19)\",\n            [],\n            [timeout: :timer.seconds(20)]\n          ])\n        end)\n\n      Process.sleep(100)\n\n      log =\n        capture_log(fn ->\n          t1 =\n            Task.async(fn ->\n              assert {:error, :increase_connection_pool} =\n                       Authorization.get_read_authorizations(\n                         %Policies{},\n                         context.db_conn,\n                         context.authorization_context\n                       )\n            end)\n\n          t2 =\n            Task.async(fn ->\n              assert {:error, :increase_connection_pool} =\n                       Authorization.get_write_authorizations(\n                         %Policies{},\n                         context.db_conn,\n                         context.authorization_context\n                       )\n            end)\n\n          Task.await_many([t1, t2], 20_000)\n          rate_counter = Realtime.Tenants.authorization_errors_per_second_rate(context.tenant)\n          RateCounterHelper.tick!(rate_counter)\n        end)\n\n      external_id = context.tenant.external_id\n\n      assert log =~\n               \"project=#{external_id} external_id=#{external_id} [critical] IncreaseConnectionPool: Too many database timeouts\"\n\n      Task.await(task, :timer.seconds(30))\n    end\n\n    @tag role: \"authenticated\",\n         policies: [:broken_read_presence, :broken_write_presence]\n    test \"broken RLS policy returns error\", context do\n      assert {:error, :rls_policy_error, %Postgrex.Error{}} =\n               Authorization.get_read_authorizations(\n                 %Policies{},\n                 context.db_conn,\n                 context.authorization_context\n               )\n\n      assert {:error, :rls_policy_error, %Postgrex.Error{}} =\n               Authorization.get_write_authorizations(\n                 %Policies{},\n                 context.db_conn,\n                 context.authorization_context\n               )\n\n      assert {:error, :rls_policy_error, %Postgrex.Error{}} =\n               Authorization.get_read_authorizations(\n                 %Policies{},\n                 context.db_conn,\n                 context.authorization_context\n               )\n\n      assert {:error, :rls_policy_error, %Postgrex.Error{}} =\n               Authorization.get_write_authorizations(\n                 %Policies{},\n                 context.db_conn,\n                 context.authorization_context\n               )\n\n      assert {:error, :rls_policy_error, %Postgrex.Error{}} =\n               Authorization.get_write_authorizations(\n                 %Policies{},\n                 context.db_conn,\n                 context.authorization_context\n               )\n    end\n  end\n\n  defp remote_rls_context(context) do\n    tenant = Containers.checkout_tenant_unboxed(run_migrations: true)\n\n    {:ok, local_db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n    topic = random_string()\n\n    clean_table(local_db_conn, \"realtime\", \"messages\")\n\n    claims = %{sub: random_string(), role: context.role, exp: Joken.current_time() + 1_000}\n\n    authorization_context =\n      Authorization.build_authorization_params(%{\n        tenant_id: tenant.external_id,\n        topic: topic,\n        claims: claims,\n        headers: [{\"header-1\", \"value-1\"}],\n        role: claims.role\n      })\n\n    Realtime.Tenants.Migrations.create_partitions(local_db_conn)\n    create_rls_policies(local_db_conn, context.policies, %{topic: topic})\n\n    {:ok, node} = Clustered.start()\n    region = Tenants.region(tenant)\n    {:ok, db_conn} = :erpc.call(node, Connect, :connect, [tenant.external_id, region])\n\n    assert node(db_conn) == node\n\n    %{\n      tenant: tenant,\n      topic: topic,\n      db_conn: db_conn,\n      node: node,\n      authorization_context: authorization_context\n    }\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/authorization_test.exs",
    "content": "defmodule Realtime.Tenants.AuthorizationTest do\n  use RealtimeWeb.ConnCase, async: true\n  use Mimic\n\n  require Phoenix.ChannelTest\n\n  import ExUnit.CaptureLog\n\n  alias Realtime.Api.Message\n  alias Realtime.Database\n  alias Realtime.Tenants.Repo\n  alias Realtime.Tenants.Authorization\n  alias Realtime.Tenants.Authorization.Policies\n  alias Realtime.Tenants.Authorization.Policies.BroadcastPolicies\n  alias Realtime.Tenants.Authorization.Policies.PresencePolicies\n\n  setup [:checkout_tenant_and_connect, :rls_context]\n\n  describe \"get_authorizations/3\" do\n    @tag role: \"authenticated\",\n         policies: [\n           :authenticated_read_broadcast_and_presence,\n           :authenticated_write_broadcast_and_presence\n         ]\n    test \"authenticated user has expected policies\", context do\n      {:ok, policies} =\n        Authorization.get_read_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n\n      {:ok, policies} =\n        Authorization.get_write_authorizations(policies, context.db_conn, context.authorization_context)\n\n      assert %Policies{\n               broadcast: %BroadcastPolicies{read: true, write: true},\n               presence: %PresencePolicies{read: true, write: true}\n             } == policies\n    end\n\n    @tag role: \"authenticated\",\n         policies: [:authenticated_read_matching_user_sub],\n         sub: \"ccbdfd51-c5aa-4d61-8c17-647664466a26\"\n    test \"authenticated user sub is available\", context do\n      assert {:ok, %Policies{broadcast: %BroadcastPolicies{read: true, write: nil}}} =\n               Authorization.get_read_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n\n      authorization_context = %{context.authorization_context | sub: \"135f6d25-5840-4266-a8ca-b9a45960e424\"}\n\n      assert {:ok, %Policies{broadcast: %BroadcastPolicies{read: false, write: nil}}} =\n               Authorization.get_read_authorizations(%Policies{}, context.db_conn, authorization_context)\n    end\n\n    @tag role: \"authenticated\",\n         policies: [:read_matching_user_role]\n    test \"user role is exposed\", context do\n      assert {:ok, %Policies{broadcast: %BroadcastPolicies{read: true, write: nil}}} =\n               Authorization.get_read_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n\n      authorization_context = %{context.authorization_context | role: \"anon\"}\n\n      assert {:ok, %Policies{broadcast: %BroadcastPolicies{read: false, write: nil}}} =\n               Authorization.get_read_authorizations(%Policies{}, context.db_conn, authorization_context)\n    end\n\n    @tag role: \"authenticated\",\n         policies: [:authenticated_read_broadcast, :authenticated_write_broadcast]\n    test \"skips presence RLS check when presence is disabled\", context do\n      {:ok, policies} =\n        Authorization.get_read_authorizations(%Policies{}, context.db_conn, context.authorization_context,\n          presence_enabled?: false\n        )\n\n      {:ok, policies} =\n        Authorization.get_write_authorizations(policies, context.db_conn, context.authorization_context,\n          presence_enabled?: false\n        )\n\n      assert %Policies{\n               broadcast: %BroadcastPolicies{read: true, write: true},\n               presence: %PresencePolicies{read: false, write: false}\n             } == policies\n    end\n\n    @tag role: \"anon\",\n         policies: [\n           :authenticated_read_broadcast_and_presence,\n           :authenticated_write_broadcast_and_presence\n         ]\n    test \"anon user has no policies\", context do\n      {:ok, policies} =\n        Authorization.get_read_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n\n      {:ok, policies} =\n        Authorization.get_write_authorizations(policies, context.db_conn, context.authorization_context)\n\n      assert %Policies{\n               broadcast: %BroadcastPolicies{read: false, write: false},\n               presence: %PresencePolicies{read: false, write: false}\n             } == policies\n    end\n\n    @tag role: \"anon\", policies: []\n    test \"db process is down\", context do\n      pid = spawn(fn -> :ok end)\n\n      {:error, :increase_connection_pool} =\n        Authorization.get_read_authorizations(%Policies{}, pid, context.authorization_context)\n\n      {:error, :increase_connection_pool} =\n        Authorization.get_write_authorizations(%Policies{}, pid, context.authorization_context)\n    end\n\n    @tag role: \"anon\", policies: []\n    test \"get_read_authorizations rate limit when db has many connection errors\", context do\n      update_db_pool_size(context.tenant, 5)\n      pid = spawn(fn -> :ok end)\n\n      log =\n        capture_log(fn ->\n          for _ <- 1..6 do\n            {:error, :increase_connection_pool} =\n              Authorization.get_read_authorizations(%Policies{}, pid, context.authorization_context)\n          end\n\n          rate_counter = Realtime.Tenants.authorization_errors_per_second_rate(context.tenant)\n          RateCounterHelper.tick!(rate_counter)\n          reject(&Database.transaction/4)\n\n          for _ <- 1..10 do\n            {:error, :increase_connection_pool} =\n              Authorization.get_read_authorizations(%Policies{}, pid, context.authorization_context)\n          end\n        end)\n\n      assert log =~ \"IncreaseConnectionPool: Too many database timeouts\"\n      assert length(String.split(log, \"IncreaseConnectionPool: Too many database timeouts\")) <= 3\n    end\n\n    @tag role: \"anon\", policies: []\n    test \"get_write_authorizations rate limit when db has many connection errors\", context do\n      update_db_pool_size(context.tenant, 5)\n      pid = spawn(fn -> :ok end)\n\n      log =\n        capture_log(fn ->\n          for _ <- 1..6 do\n            {:error, :increase_connection_pool} =\n              Authorization.get_write_authorizations(%Policies{}, pid, context.authorization_context)\n          end\n\n          rate_counter = Realtime.Tenants.authorization_errors_per_second_rate(context.tenant)\n          RateCounterHelper.tick!(rate_counter)\n          reject(&Database.transaction/4)\n\n          for _ <- 1..10 do\n            {:error, :increase_connection_pool} =\n              Authorization.get_write_authorizations(%Policies{}, pid, context.authorization_context)\n          end\n        end)\n\n      assert log =~ \"IncreaseConnectionPool: Too many database timeouts\"\n      assert length(String.split(log, \"IncreaseConnectionPool: Too many database timeouts\")) == 2\n    end\n  end\n\n  describe \"database error\" do\n    @tag role: \"authenticated\",\n         policies: [\n           :authenticated_read_broadcast_and_presence,\n           :authenticated_write_broadcast_and_presence\n         ],\n         timeout: :timer.minutes(1)\n    test \"handles small pool size\", context do\n      task =\n        Task.async(fn ->\n          Postgrex.query!(context.db_conn, \"SELECT pg_sleep(19)\", [], timeout: :timer.seconds(20))\n        end)\n\n      Process.sleep(100)\n\n      log =\n        capture_log(fn ->\n          t1 =\n            Task.async(fn ->\n              assert {:error, :increase_connection_pool} =\n                       Authorization.get_read_authorizations(\n                         %Policies{},\n                         context.db_conn,\n                         context.authorization_context\n                       )\n            end)\n\n          t2 =\n            Task.async(fn ->\n              assert {:error, :increase_connection_pool} =\n                       Authorization.get_write_authorizations(\n                         %Policies{},\n                         context.db_conn,\n                         context.authorization_context\n                       )\n            end)\n\n          Task.await_many([t1, t2], 20_000)\n          rate_counter = Realtime.Tenants.authorization_errors_per_second_rate(context.tenant)\n          RateCounterHelper.tick!(rate_counter)\n        end)\n\n      external_id = context.tenant.external_id\n      assert log =~ \"project=#{external_id} external_id=#{external_id} [error] ErrorExecutingTransaction\"\n\n      assert log =~\n               \"project=#{external_id} external_id=#{external_id} [critical] IncreaseConnectionPool: Too many database timeouts\"\n\n      Task.await(task, :timer.seconds(30))\n    end\n\n    @tag role: \"authenticated\",\n         policies: [:broken_read_presence, :broken_write_presence]\n    test \"broken RLS policy sets policies to false and shows error to user\", context do\n      assert {:error, :rls_policy_error, %Postgrex.Error{}} =\n               Authorization.get_read_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n\n      assert {:error, :rls_policy_error, %Postgrex.Error{}} =\n               Authorization.get_write_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n\n      assert {:error, :rls_policy_error, %Postgrex.Error{}} =\n               Authorization.get_read_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n\n      assert {:error, :rls_policy_error, %Postgrex.Error{}} =\n               Authorization.get_write_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n\n      assert {:error, :rls_policy_error, %Postgrex.Error{}} =\n               Authorization.get_write_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n    end\n  end\n\n  describe \"ensure database stays clean\" do\n    @tag role: \"authenticated\",\n         policies: [\n           :authenticated_read_broadcast_and_presence,\n           :authenticated_write_broadcast_and_presence\n         ]\n    test \"authenticated user has expected policies\", context do\n      {:ok, _} = Authorization.get_read_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n      {:ok, _} = Authorization.get_write_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n\n      {:ok, db_conn} = Database.connect(context.tenant, \"realtime_test\")\n      assert {:ok, []} = Repo.all(db_conn, Message, Message)\n    end\n  end\n\n  describe \"telemetry\" do\n    @tag role: \"authenticated\",\n         policies: [\n           :authenticated_read_broadcast_and_presence,\n           :authenticated_write_broadcast_and_presence\n         ]\n\n    test \"sends telemetry event\", context do\n      on_exit(fn -> :telemetry.detach(__MODULE__) end)\n\n      events = [\n        [:realtime, :tenants, :write_authorization_check],\n        [:realtime, :tenants, :read_authorization_check]\n      ]\n\n      :telemetry.attach_many(\n        __MODULE__,\n        events,\n        fn event, measurements, metadata, _config ->\n          send(self(), {:telemetry_event, event, measurements, metadata})\n        end,\n        %{}\n      )\n\n      {:ok, _} = Authorization.get_read_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n      {:ok, _} = Authorization.get_write_authorizations(%Policies{}, context.db_conn, context.authorization_context)\n\n      external_id = context.authorization_context.tenant_id\n\n      assert_receive {:telemetry_event, [:realtime, :tenants, :read_authorization_check], %{latency: _},\n                      %{tenant: ^external_id}}\n\n      assert_receive {:telemetry_event, [:realtime, :tenants, :write_authorization_check], %{latency: _},\n                      %{tenant: ^external_id}}\n    end\n  end\n\n  defp update_db_pool_size(tenant, db_pool) do\n    extension = hd(tenant.extensions)\n\n    settings = Map.put(extension.settings, \"db_pool\", db_pool)\n\n    extensions = [Map.from_struct(%{extension | :settings => settings})]\n\n    {:ok, tenant} = Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{extensions: extensions})\n\n    Realtime.Tenants.Cache.update_cache(tenant)\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/batch_broadcast_test.exs",
    "content": "defmodule Realtime.Tenants.BatchBroadcastTest do\n  use RealtimeWeb.ConnCase, async: true\n  use Mimic\n\n  alias Realtime.Database\n  alias Realtime.GenCounter\n  alias Realtime.RateCounter\n  alias Realtime.Tenants\n  alias Realtime.Tenants.BatchBroadcast\n  alias Realtime.Tenants.Authorization\n  alias Realtime.Tenants.Authorization.Policies\n  alias Realtime.Tenants.Authorization.Policies.BroadcastPolicies\n  alias Realtime.Tenants.Connect\n\n  alias RealtimeWeb.TenantBroadcaster\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    Realtime.Tenants.Cache.update_cache(tenant)\n    {:ok, tenant: tenant}\n  end\n\n  describe \"public message broadcasting\" do\n    test \"broadcasts multiple public messages successfully\", %{tenant: tenant} do\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n      topic1 = random_string()\n      topic2 = random_string()\n\n      messages = %{\n        messages: [\n          %{topic: topic1, payload: %{\"data\" => \"test1\"}, event: \"event1\"},\n          %{topic: topic2, payload: %{\"data\" => \"test2\"}, event: \"event2\"},\n          %{topic: topic1, payload: %{\"data\" => \"test3\"}, event: \"event3\"}\n        ]\n      }\n\n      expect(GenCounter, :add, 3, fn ^broadcast_events_key -> :ok end)\n      expect(TenantBroadcaster, :pubsub_broadcast, 3, fn _, _, _, _, _ -> :ok end)\n\n      assert :ok = BatchBroadcast.broadcast(nil, tenant, messages, false)\n    end\n\n    test \"public messages do not have private prefix in topic\", %{tenant: tenant} do\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n      topic = random_string()\n\n      messages = %{\n        messages: [%{topic: topic, payload: %{\"data\" => \"test\"}, event: \"event1\"}]\n      }\n\n      expect(GenCounter, :add, fn ^broadcast_events_key -> :ok end)\n\n      expect(TenantBroadcaster, :pubsub_broadcast, fn _, topic, _, _, _ ->\n        refute String.contains?(topic, \"-private\")\n      end)\n\n      assert :ok = BatchBroadcast.broadcast(nil, tenant, messages, false)\n    end\n  end\n\n  describe \"message ID metadata\" do\n    test \"includes message ID in metadata when provided\", %{tenant: tenant} do\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n      topic = random_string()\n\n      messages = %{\n        messages: [%{id: \"msg-123\", topic: topic, payload: %{\"data\" => \"test\"}, event: \"event1\"}]\n      }\n\n      expect(GenCounter, :add, fn ^broadcast_events_key -> :ok end)\n\n      expect(TenantBroadcaster, :pubsub_broadcast, fn _, _, broadcast, _, _ ->\n        assert %Phoenix.Socket.Broadcast{\n                 payload: %{\n                   \"payload\" => %{\"data\" => \"test\"},\n                   \"event\" => \"event1\",\n                   \"type\" => \"broadcast\",\n                   \"meta\" => %{\"id\" => \"msg-123\"}\n                 }\n               } = broadcast\n      end)\n\n      assert :ok = BatchBroadcast.broadcast(nil, tenant, messages, false)\n    end\n  end\n\n  describe \"super user broadcasting\" do\n    test \"bypasses authorization for private messages with super_user flag\", %{tenant: tenant} do\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n      topic1 = random_string()\n      topic2 = random_string()\n\n      messages = %{\n        messages: [\n          %{topic: topic1, payload: %{\"data\" => \"test1\"}, event: \"event1\", private: true},\n          %{topic: topic2, payload: %{\"data\" => \"test2\"}, event: \"event2\", private: true}\n        ]\n      }\n\n      expect(GenCounter, :add, 2, fn ^broadcast_events_key -> :ok end)\n      expect(TenantBroadcaster, :pubsub_broadcast, 2, fn _, _, _, _, _ -> :ok end)\n\n      assert :ok = BatchBroadcast.broadcast(nil, tenant, messages, true)\n    end\n\n    test \"private messages have private prefix in topic\", %{tenant: tenant} do\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n      topic = random_string()\n\n      messages = %{\n        messages: [%{topic: topic, payload: %{\"data\" => \"test\"}, event: \"event1\", private: true}]\n      }\n\n      expect(GenCounter, :add, fn ^broadcast_events_key -> :ok end)\n\n      expect(TenantBroadcaster, :pubsub_broadcast, fn _, topic, _, _, _ ->\n        assert String.contains?(topic, \"-private\")\n      end)\n\n      assert :ok = BatchBroadcast.broadcast(nil, tenant, messages, true)\n    end\n  end\n\n  describe \"private message authorization\" do\n    test \"broadcasts private messages with valid authorization\", %{tenant: tenant} do\n      topic = random_string()\n      sub = random_string()\n      role = \"authenticated\"\n\n      auth_params = %{\n        tenant_id: tenant.external_id,\n        topic: topic,\n        headers: [{\"header-1\", \"value-1\"}],\n        claims: %{\"sub\" => sub, \"role\" => role, \"exp\" => Joken.current_time() + 1_000},\n        role: role,\n        sub: sub\n      }\n\n      messages = %{messages: [%{topic: topic, payload: %{\"data\" => \"test\"}, event: \"event1\", private: true}]}\n\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n\n      expect(GenCounter, :add, 1, fn ^broadcast_events_key -> :ok end)\n\n      Authorization\n      |> expect(:build_authorization_params, fn params -> params end)\n      |> expect(:get_write_authorizations, fn _, _ -> {:ok, %Policies{broadcast: %BroadcastPolicies{write: true}}} end)\n\n      expect(TenantBroadcaster, :pubsub_broadcast, 1, fn _, _, _, _, _ -> :ok end)\n\n      assert :ok = BatchBroadcast.broadcast(auth_params, tenant, messages, false)\n    end\n\n    test \"skips private messages without authorization\", %{tenant: tenant} do\n      topic = random_string()\n      sub = random_string()\n      role = \"anon\"\n\n      auth_params = %{\n        tenant_id: tenant.external_id,\n        topic: topic,\n        headers: [{\"header-1\", \"value-1\"}],\n        claims: %{\"sub\" => sub, \"role\" => role, \"exp\" => Joken.current_time() + 1_000},\n        role: role,\n        sub: sub\n      }\n\n      Authorization\n      |> expect(:build_authorization_params, 1, fn params -> params end)\n      |> expect(:get_write_authorizations, 1, fn _, _ ->\n        {:ok, %Policies{broadcast: %BroadcastPolicies{write: false}}}\n      end)\n\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n\n      messages = %{\n        messages: [%{topic: topic, payload: %{\"data\" => \"test\"}, event: \"event1\", private: true}]\n      }\n\n      assert :ok = BatchBroadcast.broadcast(auth_params, tenant, messages, false)\n\n      assert calls(&TenantBroadcaster.pubsub_broadcast/5) == []\n    end\n\n    test \"broadcasts only authorized topics in mixed authorization batch\", %{tenant: tenant} do\n      topic = random_string()\n      sub = random_string()\n      role = \"authenticated\"\n\n      auth_params = %{\n        tenant_id: tenant.external_id,\n        headers: [{\"header-1\", \"value-1\"}],\n        claims: %{\"sub\" => sub, \"role\" => role, \"exp\" => Joken.current_time() + 1_000},\n        role: role,\n        sub: sub\n      }\n\n      messages = %{\n        messages: [\n          %{topic: topic, payload: %{\"data\" => \"test1\"}, event: \"event1\", private: true},\n          %{topic: random_string(), payload: %{\"data\" => \"test2\"}, event: \"event2\", private: true}\n        ]\n      }\n\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n\n      expect(GenCounter, :add, fn ^broadcast_events_key -> :ok end)\n\n      Authorization\n      |> expect(:build_authorization_params, 2, fn params -> params end)\n      |> expect(:get_write_authorizations, 2, fn\n        _, %{topic: ^topic} -> %Policies{broadcast: %BroadcastPolicies{write: true}}\n        _, _ -> %Policies{broadcast: %BroadcastPolicies{write: false}}\n      end)\n\n      # Only one topic will actually be broadcasted\n      expect(TenantBroadcaster, :pubsub_broadcast, 1, fn _, _, %Phoenix.Socket.Broadcast{topic: ^topic}, _, _ ->\n        :ok\n      end)\n\n      assert :ok = BatchBroadcast.broadcast(auth_params, tenant, messages, false)\n    end\n\n    test \"groups messages by topic and checks authorization once per topic\", %{tenant: tenant} do\n      topic_1 = random_string()\n      topic_2 = random_string()\n      sub = random_string()\n      role = \"authenticated\"\n\n      auth_params = %{\n        tenant_id: tenant.external_id,\n        headers: [{\"header-1\", \"value-1\"}],\n        claims: %{\"sub\" => sub, \"role\" => role, \"exp\" => Joken.current_time() + 1_000},\n        role: role,\n        sub: sub\n      }\n\n      messages = %{\n        messages: [\n          %{topic: topic_1, payload: %{\"data\" => \"test1\"}, event: \"event1\", private: true},\n          %{topic: topic_2, payload: %{\"data\" => \"test2\"}, event: \"event2\", private: true},\n          %{topic: topic_1, payload: %{\"data\" => \"test3\"}, event: \"event3\", private: true}\n        ]\n      }\n\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n\n      expect(GenCounter, :add, 3, fn ^broadcast_events_key -> :ok end)\n\n      Authorization\n      |> expect(:build_authorization_params, 2, fn params -> params end)\n      |> expect(:get_write_authorizations, 2, fn _, _ ->\n        {:ok, %Policies{broadcast: %BroadcastPolicies{write: true}}}\n      end)\n\n      expect(TenantBroadcaster, :pubsub_broadcast, 3, fn _, _, _, _, _ -> :ok end)\n\n      assert :ok = BatchBroadcast.broadcast(auth_params, tenant, messages, false)\n    end\n\n    test \"handles missing auth params for private messages\", %{tenant: tenant} do\n      events_per_second_rate = Tenants.events_per_second_rate(tenant)\n\n      RateCounter\n      |> stub(:new, fn _ -> {:ok, nil} end)\n      |> stub(:get, fn ^events_per_second_rate -> {:ok, %RateCounter{avg: 0}} end)\n\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n      reject(&Connect.lookup_or_start_connection/1)\n\n      messages = %{\n        messages: [%{topic: \"topic1\", payload: %{\"data\" => \"test\"}, event: \"event1\", private: true}]\n      }\n\n      assert :ok = BatchBroadcast.broadcast(nil, tenant, messages, false)\n\n      assert calls(&TenantBroadcaster.pubsub_broadcast/5) == []\n    end\n  end\n\n  describe \"mixed public and private messages\" do\n    setup %{tenant: tenant} do\n      {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n      %{db_conn: db_conn}\n    end\n\n    test \"broadcasts both public and private messages together\", %{tenant: tenant, db_conn: db_conn} do\n      topic = random_string()\n      sub = random_string()\n      role = \"authenticated\"\n\n      create_rls_policies(db_conn, [:authenticated_write_broadcast], %{topic: topic})\n\n      auth_params = %{\n        tenant_id: tenant.external_id,\n        topic: topic,\n        headers: [{\"header-1\", \"value-1\"}],\n        claims: %{\"sub\" => sub, \"role\" => role, \"exp\" => Joken.current_time() + 1_000},\n        role: role,\n        sub: sub\n      }\n\n      events_per_second_rate = Tenants.events_per_second_rate(tenant)\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n\n      RateCounter\n      |> stub(:new, fn _ -> {:ok, nil} end)\n      |> stub(:get, fn\n        ^events_per_second_rate ->\n          {:ok, %RateCounter{avg: 0}}\n\n        _ ->\n          {:ok,\n           %RateCounter{\n             avg: 0,\n             limit: %{log: true, value: 10, measurement: :sum, triggered: false, log_fn: fn -> :ok end}\n           }}\n      end)\n\n      expect(GenCounter, :add, 3, fn ^broadcast_events_key -> :ok end)\n      expect(Connect, :lookup_or_start_connection, fn _ -> {:ok, db_conn} end)\n\n      Authorization\n      |> expect(:build_authorization_params, fn params -> params end)\n      |> expect(:get_write_authorizations, fn _, _ ->\n        {:ok, %Policies{broadcast: %BroadcastPolicies{write: true}}}\n      end)\n\n      expect(TenantBroadcaster, :pubsub_broadcast, 3, fn _, _, _, _, _ -> :ok end)\n\n      messages = %{\n        messages: [\n          %{topic: \"public1\", payload: %{\"data\" => \"public\"}, event: \"event1\", private: false},\n          %{topic: topic, payload: %{\"data\" => \"private\"}, event: \"event2\", private: true},\n          %{topic: \"public2\", payload: %{\"data\" => \"public2\"}, event: \"event3\"}\n        ]\n      }\n\n      assert :ok = BatchBroadcast.broadcast(auth_params, tenant, messages, false)\n\n      broadcast_calls = calls(&TenantBroadcaster.pubsub_broadcast/5)\n      assert length(broadcast_calls) == 3\n    end\n  end\n\n  describe \"Plug.Conn integration\" do\n    test \"accepts and converts Plug.Conn to auth params\", %{tenant: tenant} do\n      topic = random_string()\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n      messages = %{messages: [%{topic: topic, payload: %{\"data\" => \"test\"}, event: \"event1\"}]}\n\n      expect(GenCounter, :add, fn ^broadcast_events_key -> :ok end)\n      expect(TenantBroadcaster, :pubsub_broadcast, 1, fn _, _, _, _, _ -> :ok end)\n\n      conn =\n        build_conn()\n        |> Map.put(:assigns, %{\n          claims: %{\"sub\" => \"user123\", \"role\" => \"authenticated\"},\n          role: \"authenticated\",\n          sub: \"user123\"\n        })\n        |> Map.put(:req_headers, [{\"authorization\", \"Bearer token\"}])\n\n      assert :ok = BatchBroadcast.broadcast(conn, tenant, messages, false)\n    end\n  end\n\n  describe \"message validation\" do\n    test \"returns changeset error when topic is missing\", %{tenant: tenant} do\n      messages = %{messages: [%{payload: %{\"data\" => \"test\"}, event: \"event1\"}]}\n\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n\n      result = BatchBroadcast.broadcast(nil, tenant, messages, false)\n      assert {:error, %Ecto.Changeset{valid?: false}} = result\n    end\n\n    test \"returns changeset error when payload is missing\", %{tenant: tenant} do\n      topic = random_string()\n      messages = %{messages: [%{topic: topic, event: \"event1\"}]}\n\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n\n      result = BatchBroadcast.broadcast(nil, tenant, messages, false)\n      assert {:error, %Ecto.Changeset{valid?: false}} = result\n    end\n\n    test \"returns changeset error when event is missing\", %{tenant: tenant} do\n      topic = random_string()\n      messages = %{messages: [%{topic: topic, payload: %{\"data\" => \"test\"}}]}\n\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n      result = BatchBroadcast.broadcast(nil, tenant, messages, false)\n      assert {:error, %Ecto.Changeset{valid?: false}} = result\n    end\n\n    test \"returns changeset error when messages array is empty\", %{tenant: tenant} do\n      messages = %{messages: []}\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n      result = BatchBroadcast.broadcast(nil, tenant, messages, false)\n      assert {:error, %Ecto.Changeset{valid?: false}} = result\n    end\n  end\n\n  describe \"rate limiting\" do\n    test \"rejects broadcast when rate limit is exceeded\", %{tenant: tenant} do\n      events_per_second_rate = Tenants.events_per_second_rate(tenant)\n      topic = random_string()\n      messages = %{messages: [%{topic: topic, payload: %{\"data\" => \"test\"}, event: \"event1\"}]}\n\n      RateCounter\n      |> stub(:new, fn _ -> {:ok, nil} end)\n      |> stub(:get, fn ^events_per_second_rate -> {:ok, %RateCounter{avg: tenant.max_events_per_second + 1}} end)\n\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n\n      result = BatchBroadcast.broadcast(nil, tenant, messages, false)\n      assert {:error, :too_many_requests, \"You have exceeded your rate limit\"} = result\n    end\n\n    test \"rejects broadcast when batch would exceed rate limit\", %{tenant: tenant} do\n      events_per_second_rate = Tenants.events_per_second_rate(tenant)\n\n      messages = %{\n        messages:\n          Enum.map(1..10, fn _ ->\n            %{topic: random_string(), payload: %{\"data\" => \"test\"}, event: random_string()}\n          end)\n      }\n\n      RateCounter\n      |> stub(:new, fn _ -> {:ok, nil} end)\n      |> stub(:get, fn ^events_per_second_rate ->\n        {:ok, %RateCounter{avg: tenant.max_events_per_second - 5}}\n      end)\n\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n\n      result = BatchBroadcast.broadcast(nil, tenant, messages, false)\n\n      assert {:error, :too_many_requests, \"Too many messages to broadcast, please reduce the batch size\"} = result\n    end\n\n    test \"allows broadcast at rate limit boundary\", %{tenant: tenant} do\n      events_per_second_rate = Tenants.events_per_second_rate(tenant)\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n      current_rate = tenant.max_events_per_second - 2\n\n      messages = %{\n        messages: [\n          %{topic: random_string(), payload: %{\"data\" => \"test1\"}, event: \"event1\"},\n          %{topic: random_string(), payload: %{\"data\" => \"test2\"}, event: \"event2\"}\n        ]\n      }\n\n      RateCounter\n      |> stub(:new, fn _ -> {:ok, nil} end)\n      |> stub(:get, fn ^events_per_second_rate ->\n        {:ok, %RateCounter{avg: current_rate}}\n      end)\n\n      expect(GenCounter, :add, 2, fn ^broadcast_events_key -> :ok end)\n      expect(TenantBroadcaster, :pubsub_broadcast, 2, fn _, _, _, _, _ -> :ok end)\n\n      assert :ok = BatchBroadcast.broadcast(nil, tenant, messages, false)\n    end\n\n    test \"rejects broadcast when payload size exceeds tenant limit\", %{tenant: tenant} do\n      messages = %{\n        messages: [\n          %{\n            topic: random_string(),\n            payload: %{\"data\" => random_string(tenant.max_payload_size_in_kb * 1000 + 1)},\n            event: \"event1\"\n          }\n        ]\n      }\n\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n\n      result = BatchBroadcast.broadcast(nil, tenant, messages, false)\n\n      assert {:error,\n              %Ecto.Changeset{\n                valid?: false,\n                changes: %{messages: [%{errors: [payload: {\"Payload size exceeds tenant limit\", []}]}]}\n              }} = result\n    end\n  end\n\n  describe \"error handling\" do\n    test \"returns error when tenant is nil\" do\n      messages = %{messages: [%{topic: \"topic1\", payload: %{\"data\" => \"test\"}, event: \"event1\"}]}\n      assert {:error, :tenant_not_found} = BatchBroadcast.broadcast(nil, nil, messages, false)\n    end\n\n    test \"gracefully handles database connection errors for private messages\", %{tenant: tenant} do\n      topic = random_string()\n      sub = random_string()\n      role = \"authenticated\"\n\n      auth_params = %{\n        tenant_id: tenant.external_id,\n        headers: [{\"header-1\", \"value-1\"}],\n        claims: %{\"sub\" => sub, \"role\" => role, \"exp\" => Joken.current_time() + 1_000},\n        role: role,\n        sub: sub\n      }\n\n      events_per_second_rate = Tenants.events_per_second_rate(tenant)\n\n      RateCounter\n      |> stub(:new, fn _ -> {:ok, nil} end)\n      |> stub(:get, fn ^events_per_second_rate -> {:ok, %RateCounter{avg: 0}} end)\n\n      expect(Connect, :lookup_or_start_connection, fn _ -> {:error, :connection_failed} end)\n\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n\n      messages = %{\n        messages: [%{topic: topic, payload: %{\"data\" => \"test\"}, event: \"event1\", private: true}]\n      }\n\n      assert :ok = BatchBroadcast.broadcast(auth_params, tenant, messages, false)\n\n      assert calls(&TenantBroadcaster.pubsub_broadcast/5) == []\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/cache_test.exs",
    "content": "defmodule Realtime.Tenants.CacheTest do\n  use Realtime.DataCase, async: false\n\n  alias Realtime.Api\n  alias Realtime.Rpc\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Cache\n\n  setup do\n    {:ok, tenant: tenant_fixture()}\n  end\n\n  describe \"get_tenant_by_external_id/1\" do\n    test \"tenants cache returns a cached result\", %{tenant: tenant} do\n      external_id = tenant.external_id\n      assert %Api.Tenant{name: \"tenant\"} = Cache.get_tenant_by_external_id(external_id)\n\n      changeset = Api.Tenant.changeset(tenant, %{name: \"new name\"})\n      Repo.update!(changeset)\n      assert %Api.Tenant{name: \"new name\"} = Tenants.get_tenant_by_external_id(external_id)\n      assert %Api.Tenant{name: \"tenant\"} = Cache.get_tenant_by_external_id(external_id)\n    end\n\n    test \"does not cache when tenant is not found\" do\n      assert Cache.get_tenant_by_external_id(\"not found\") == nil\n\n      assert Cachex.exists?(Cache, {:get_tenant_by_external_id, \"not found\"}) == {:ok, false}\n    end\n  end\n\n  describe \"invalidate_tenant_cache/1\" do\n    test \"invalidates the cache given a tenant_id\", %{tenant: tenant} do\n      external_id = tenant.external_id\n      assert %Api.Tenant{suspend: false} = Cache.get_tenant_by_external_id(external_id)\n\n      # Update a tenant\n      tenant |> Realtime.Api.Tenant.changeset(%{suspend: true}) |> Realtime.Repo.update!()\n\n      # Cache showing old value\n      assert %Api.Tenant{suspend: false} = Cache.get_tenant_by_external_id(external_id)\n\n      # Invalidate cache\n      Cache.invalidate_tenant_cache(external_id)\n      assert %Api.Tenant{suspend: true} = Cache.get_tenant_by_external_id(external_id)\n    end\n  end\n\n  describe \"update_cache/1\" do\n    test \"updates the cache given a tenant\", %{tenant: tenant} do\n      external_id = tenant.external_id\n      assert %Api.Tenant{name: \"tenant\"} = Cache.get_tenant_by_external_id(external_id)\n      # Update a tenant\n      updated_tenant = %{tenant | name: \"updated name\"}\n      # Update cache\n      Cache.update_cache(updated_tenant)\n      assert %Api.Tenant{name: \"updated name\"} = Cache.get_tenant_by_external_id(external_id)\n    end\n  end\n\n  describe \"distributed_invalidate_tenant_cache/1\" do\n    setup do\n      {:ok, node} = Clustered.start()\n\n      tenant =\n        Ecto.Adapters.SQL.Sandbox.unboxed_run(Realtime.Repo, fn ->\n          tenant_fixture()\n        end)\n\n      on_exit(fn ->\n        Ecto.Adapters.SQL.Sandbox.unboxed_run(Realtime.Repo, fn ->\n          Realtime.Api.delete_tenant_by_external_id(tenant.external_id)\n        end)\n      end)\n\n      %{node: node, tenant: tenant}\n    end\n\n    test \"invalidates the cache given a tenant_id\", %{node: node, tenant: tenant} do\n      external_id = tenant.external_id\n      expected_name = tenant.name\n      dummy_name = random_string()\n      dummy_tenant = %{tenant | name: dummy_name}\n\n      assert {:ok, true} = Cache.update_cache(dummy_tenant)\n\n      assert {:ok, %Api.Tenant{name: ^dummy_name}} =\n               Cachex.get(Cache, {:get_tenant_by_external_id, external_id})\n\n      seed_remote_cache(node, external_id, dummy_tenant)\n\n      assert :ok = Cache.distributed_invalidate_tenant_cache(external_id)\n\n      assert_eventually(fn ->\n        %Api.Tenant{name: ^expected_name} = Cache.get_tenant_by_external_id(external_id)\n\n        %Api.Tenant{name: ^expected_name} =\n          Rpc.enhanced_call(node, Cache, :get_tenant_by_external_id, [external_id])\n      end)\n    end\n  end\n\n  describe \"global_cache_update/1\" do\n    setup do\n      {:ok, node} = Clustered.start()\n\n      tenant =\n        Ecto.Adapters.SQL.Sandbox.unboxed_run(Realtime.Repo, fn ->\n          tenant_fixture()\n        end)\n\n      on_exit(fn ->\n        Ecto.Adapters.SQL.Sandbox.unboxed_run(Realtime.Repo, fn ->\n          Realtime.Api.delete_tenant_by_external_id(tenant.external_id)\n        end)\n      end)\n\n      %{node: node, tenant: tenant}\n    end\n\n    test \"update the cache given a tenant_id\", %{node: node, tenant: tenant} do\n      external_id = tenant.external_id\n      expected_name = tenant.name\n      dummy_name = random_string()\n      dummy_tenant = %{tenant | name: dummy_name}\n\n      assert {:ok, true} = Cache.update_cache(dummy_tenant)\n\n      assert {:ok, %Api.Tenant{name: ^dummy_name}} =\n               Cachex.get(Cache, {:get_tenant_by_external_id, external_id})\n\n      seed_remote_cache(node, external_id, dummy_tenant)\n\n      assert :ok = Cache.global_cache_update(tenant)\n\n      assert_eventually(fn ->\n        {:ok, %Api.Tenant{name: ^expected_name}} =\n          Cachex.get(Cache, {:get_tenant_by_external_id, external_id})\n\n        {:ok, %Api.Tenant{name: ^expected_name}} =\n          Rpc.enhanced_call(node, Cachex, :get, [Cache, {:get_tenant_by_external_id, external_id}])\n      end)\n    end\n  end\n\n  defp seed_remote_cache(node, external_id, tenant, attempts \\\\ 20) do\n    Rpc.enhanced_call(node, Cache, :update_cache, [tenant])\n\n    case Rpc.enhanced_call(node, Cachex, :get, [Cache, {:get_tenant_by_external_id, external_id}]) do\n      {:ok, %Api.Tenant{external_id: ^external_id, name: name}} when name == tenant.name ->\n        :ok\n\n      _other when attempts > 0 ->\n        Process.sleep(50)\n        seed_remote_cache(node, external_id, tenant, attempts - 1)\n\n      other ->\n        flunk(\"Failed to seed remote cache after retries, last result: #{inspect(other)}\")\n    end\n  end\n\n  defp assert_eventually(fun, attempts \\\\ 50, interval \\\\ 100)\n\n  defp assert_eventually(fun, 0, _interval) do\n    fun.()\n  end\n\n  defp assert_eventually(fun, attempts, interval) do\n    fun.()\n  rescue\n    _ ->\n      Process.sleep(interval)\n      assert_eventually(fun, attempts - 1, interval)\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/connect/get_tenant_test.exs",
    "content": "defmodule Realtime.Tenants.Connect.GetTenantTest do\n  use Realtime.DataCase, async: true\n\n  alias Realtime.Tenants.Connect.GetTenant\n\n  describe \"run/1\" do\n    test \"returns tenant when found\" do\n      tenant = Containers.checkout_tenant()\n      assert {:ok, %{tenant: %Realtime.Api.Tenant{}}} = GetTenant.run(%{tenant_id: tenant.external_id})\n    end\n\n    test \"returns error when tenant not found\" do\n      assert {:error, :tenant_not_found} = GetTenant.run(%{tenant_id: \"nonexistent_tenant_id\"})\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/connect/piper_test.exs",
    "content": "defmodule Realtime.Tenants.Connect.PiperTest do\n  use ExUnit.Case, async: true\n\n  import ExUnit.CaptureLog\n\n  alias Realtime.Tenants.Connect.Piper\n\n  defmodule Piper1 do\n    @behaviour Piper\n    def run(acc), do: {:ok, Map.put(acc, :piper1, \"Piper1\")}\n  end\n\n  defmodule Piper2 do\n    @behaviour Piper\n    def run(acc), do: Map.get(acc, :piper1) && {:ok, Map.put(acc, :piper2, \"Piper2\")}\n  end\n\n  defmodule Piper3 do\n    @behaviour Piper\n    def run(acc), do: Map.get(acc, :piper2) && {:ok, Map.put(acc, :piper3, \"Piper3\")}\n  end\n\n  defmodule PiperErr do\n    @behaviour Piper\n    def run(_acc), do: {:error, \"PiperErr\"}\n  end\n\n  defmodule PiperBadReturn do\n    @behaviour Piper\n    def run(_acc), do: nil\n  end\n\n  defmodule PiperException do\n    @behaviour Piper\n    def run(_acc), do: raise(\"PiperException\")\n  end\n\n  @pipeline [__MODULE__.Piper1, __MODULE__.Piper2, __MODULE__.Piper3]\n  test \"runs pipeline as expected and accumlates outputs\" do\n    assert {:ok,\n            %{\n              piper1: \"Piper1\",\n              piper2: \"Piper2\",\n              piper3: \"Piper3\",\n              initial: \"state\"\n            }} = Piper.run(@pipeline, %{initial: \"state\"})\n  end\n\n  test \"runs pipeline and handles error\" do\n    assert {:error, \"PiperErr\"} =\n             Piper.run(@pipeline ++ [__MODULE__.PiperErr], %{\n               initial: \"state\"\n             })\n  end\n\n  test \"runs pipeline and handles bad return with raise\" do\n    assert_raise ArgumentError, fn ->\n      Piper.run(@pipeline ++ [__MODULE__.PiperBadReturn], %{})\n    end\n  end\n\n  test \"on pipeline job function, raises exception\" do\n    assert_raise RuntimeError, fn ->\n      Piper.run(@pipeline ++ [__MODULE__.PiperException], %{})\n    end\n  end\n\n  test \"logs pipe execution times\" do\n    assert capture_log(fn ->\n             assert {:error, \"PiperErr\"} =\n                      Piper.run([__MODULE__.PiperErr], %{initial: \"state\"})\n           end) =~ \"Realtime.Tenants.Connect.PiperTest.PiperErr failed in \"\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/connect/reconcile_migrations_test.exs",
    "content": "defmodule Realtime.Tenants.Connect.ReconcileMigrationsTest do\n  use Realtime.DataCase, async: true\n\n  alias Realtime.Tenants.Connect.ReconcileMigrations\n  alias Realtime.Tenants.Migrations\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    %{tenant: tenant}\n  end\n\n  describe \"run/1\" do\n    test \"does nothing when migrations_ran matches database count\", %{tenant: tenant} do\n      acc = %{tenant: tenant, migrations_ran_on_database: tenant.migrations_ran}\n\n      assert {:ok, %{tenant: returned_tenant}} = ReconcileMigrations.run(acc)\n      assert returned_tenant.migrations_ran == tenant.migrations_ran\n    end\n\n    test \"updates tenant when database has fewer migrations than cached count\", %{tenant: tenant} do\n      stale_count = tenant.migrations_ran - 5\n      acc = %{tenant: tenant, migrations_ran_on_database: stale_count}\n\n      assert {:ok, %{tenant: updated_tenant}} = ReconcileMigrations.run(acc)\n      assert updated_tenant.migrations_ran == stale_count\n    end\n\n    test \"updates tenant when database has more migrations than cached count\", %{tenant: tenant} do\n      {:ok, tenant} =\n        Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{migrations_ran: 0})\n\n      total = Enum.count(Migrations.migrations())\n      acc = %{tenant: tenant, migrations_ran_on_database: total}\n\n      assert {:ok, %{tenant: updated_tenant}} = ReconcileMigrations.run(acc)\n      assert updated_tenant.migrations_ran == total\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/connect/register_process_test.exs",
    "content": "defmodule Realtime.Tenants.Connect.RegisterProcessTest do\n  use Realtime.DataCase, async: true\n  alias Realtime.Tenants.Connect.RegisterProcess\n  alias Realtime.Database\n\n  describe \"run/1\" do\n    setup do\n      tenant = Containers.checkout_tenant(run_migrations: true)\n      # Warm cache to avoid Cachex and Ecto.Sandbox ownership issues\n      Realtime.Tenants.Cache.update_cache(tenant)\n      {:ok, conn} = Database.connect(tenant, \"realtime_test\")\n      %{tenant_id: tenant.external_id, db_conn_pid: conn}\n    end\n\n    test \"registers the process in syn and Registry and updates metadata\", %{tenant_id: tenant_id, db_conn_pid: conn} do\n      # Fake the process registration in :syn\n      :syn.register(Realtime.Tenants.Connect, tenant_id, self(), %{conn: nil})\n      assert {:ok, _} = RegisterProcess.run(%{tenant_id: tenant_id, db_conn_pid: conn})\n      assert {pid, %{conn: ^conn}} = :syn.lookup(Realtime.Tenants.Connect, tenant_id)\n      assert [{^pid, %{}}] = Registry.lookup(Realtime.Tenants.Connect.Registry, tenant_id)\n    end\n\n    test \"fails to register the process in syn and Registry and updates metadata\", %{\n      tenant_id: tenant_id,\n      db_conn_pid: conn\n    } do\n      # Fake the process registration in :syn\n      :syn.register(Realtime.Tenants.Connect, tenant_id, self(), %{conn: nil})\n\n      # Register normally\n      assert {:ok, _} = RegisterProcess.run(%{tenant_id: tenant_id, db_conn_pid: conn})\n      assert {pid, %{conn: ^conn}} = :syn.lookup(Realtime.Tenants.Connect, tenant_id)\n      assert [{^pid, %{}}] = Registry.lookup(Realtime.Tenants.Connect.Registry, tenant_id)\n\n      # Check failure\n      assert {:error, :already_registered} = RegisterProcess.run(%{tenant_id: tenant_id, db_conn_pid: conn})\n    end\n\n    test \"handles undefined process error\" do\n      assert {:error, :process_not_found} =\n               RegisterProcess.run(%{tenant_id: Generators.random_string(), db_conn_pid: nil})\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/connect_test.exs",
    "content": "defmodule Realtime.Tenants.ConnectTest do\n  # Async false due to Mimic running as global because we are spawning Connect processes\n  use Realtime.DataCase, async: false\n  use Mimic\n\n  setup :set_mimic_global\n\n  import ExUnit.CaptureLog\n\n  alias Realtime.Database\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Connect\n  alias Realtime.Tenants.Rebalancer\n  alias Realtime.Tenants.ReplicationConnection\n  alias Realtime.UsersCounter\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n\n    %{tenant: tenant}\n  end\n\n  defp assert_process_down(pid, timeout \\\\ 100, reason \\\\ nil) do\n    ref = Process.monitor(pid)\n\n    if reason do\n      assert_receive {:DOWN, ^ref, :process, ^pid, ^reason}, timeout\n    else\n      assert_receive {:DOWN, ^ref, :process, ^pid, _reason}, timeout\n    end\n  end\n\n  defp refute_process_down(pid, timeout \\\\ 500) do\n    ref = Process.monitor(pid)\n    refute_receive {:DOWN, ^ref, :process, ^pid, _reason}, timeout\n  end\n\n  describe \"temporary process\" do\n    test \"starts a temporary process\", %{tenant: tenant} do\n      assert {:ok, _} = Connect.lookup_or_start_connection(tenant.external_id)\n      pid = Connect.whereis(tenant.external_id)\n      # Brutally kill the process\n      Process.exit(pid, :kill)\n      assert_process_down(pid)\n      # Wait to ensure that the process has not restarted\n      Process.sleep(1000)\n\n      # Temporary process should not be registered in syn\n      refute Connect.whereis(tenant.external_id)\n    end\n  end\n\n  describe \"list_tenants/0\" do\n    test \"lists all tenants with active connections\", %{tenant: tenant1} do\n      tenant2 = Containers.checkout_tenant(run_migrations: true)\n      assert {:ok, _} = Connect.lookup_or_start_connection(tenant1.external_id)\n      assert {:ok, _} = Connect.lookup_or_start_connection(tenant2.external_id)\n\n      list_tenants = Connect.list_tenants() |> MapSet.new()\n      tenants = MapSet.new([tenant1.external_id, tenant2.external_id])\n\n      assert MapSet.subset?(tenants, list_tenants)\n    end\n  end\n\n  describe \"handle cold start\" do\n    test \"multiple processes connecting calling Connect.connect\", %{tenant: tenant} do\n      parent = self()\n\n      # Let's slow down Connect.connect so that multiple RPC calls are executed\n      stub(Connect, :connect, fn x, y, z ->\n        :timer.sleep(1000)\n        call_original(Connect, :connect, [x, y, z])\n      end)\n\n      connect = fn -> send(parent, Connect.lookup_or_start_connection(tenant.external_id)) end\n      # Let's call enough times to potentially trigger the Connect RateCounter\n\n      for _ <- 1..50, do: spawn(connect)\n\n      assert_receive({:ok, pid}, 2000)\n\n      for _ <- 1..49, do: assert_receive({:ok, ^pid})\n\n      # Does not trigger rate limit as connections eventually succeeded\n\n      {:ok, rate_counter} =\n        tenant.external_id\n        |> Tenants.connect_errors_per_second_rate()\n        |> Realtime.RateCounter.get()\n\n      assert rate_counter.sum == 0\n      assert rate_counter.avg == 0.0\n      assert rate_counter.limit.triggered == false\n    end\n\n    test \"multiple proccesses succeed together\", %{tenant: tenant} do\n      parent = self()\n\n      # Let's slow down Connect starting\n      expect(Database, :check_tenant_connection, fn t ->\n        :timer.sleep(1000)\n        call_original(Database, :check_tenant_connection, [t])\n      end)\n\n      connect = fn -> send(parent, Connect.lookup_or_start_connection(tenant.external_id)) end\n\n      # Start an early connect\n      spawn(connect)\n      :timer.sleep(100)\n\n      # Start others\n      spawn(connect)\n      spawn(connect)\n\n      # This one should block and wait for the first Connect\n      {:ok, pid} = Connect.lookup_or_start_connection(tenant.external_id)\n\n      assert_receive {:ok, ^pid}\n      assert_receive {:ok, ^pid}\n      assert_receive {:ok, ^pid}\n    end\n\n    test \"more than 15 seconds passed error out\", %{tenant: tenant} do\n      parent = self()\n\n      # Let's slow down Connect starting\n      expect(Database, :check_tenant_connection, fn t ->\n        Process.sleep(15500)\n        call_original(Database, :check_tenant_connection, [t])\n      end)\n\n      connect = fn -> send(parent, Connect.lookup_or_start_connection(tenant.external_id)) end\n\n      spawn(connect)\n      spawn(connect)\n\n      {:error, :initializing} = Connect.lookup_or_start_connection(tenant.external_id)\n      # The above call waited 15 seconds\n      assert_receive {:error, :initializing}\n      assert_receive {:error, :initializing}\n\n      # This one will succeed\n      {:ok, _pid} = Connect.lookup_or_start_connection(tenant.external_id)\n    end\n\n    test \"too many db connections\", %{tenant: tenant} do\n      extension = %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"db_host\" => \"127.0.0.1\",\n          \"db_name\" => \"postgres\",\n          \"db_user\" => \"supabase_admin\",\n          \"db_password\" => \"postgres\",\n          \"poll_interval\" => 100,\n          \"poll_max_changes\" => 100,\n          \"poll_max_record_bytes\" => 1_048_576,\n          \"region\" => \"us-east-1\",\n          \"ssl_enforced\" => false,\n          \"db_pool\" => 100,\n          \"subcriber_pool_size\" => 100,\n          \"subs_pool_size\" => 100\n        }\n      }\n\n      {:ok, tenant} = update_extension(tenant, extension)\n\n      parent = self()\n\n      # Let's slow down Connect starting\n      expect(Database, :check_tenant_connection, fn t ->\n        :timer.sleep(1000)\n        call_original(Database, :check_tenant_connection, [t])\n      end)\n\n      connect = fn -> send(parent, Connect.lookup_or_start_connection(tenant.external_id)) end\n\n      # Start an early connect\n      spawn(connect)\n      :timer.sleep(100)\n\n      # Start others\n      spawn(connect)\n      spawn(connect)\n\n      # This one should block and wait for the first Connect\n      {:error, :tenant_db_too_many_connections} = Connect.lookup_or_start_connection(tenant.external_id)\n\n      assert_receive {:error, :tenant_db_too_many_connections}\n      assert_receive {:error, :tenant_db_too_many_connections}\n      assert_receive {:error, :tenant_db_too_many_connections}\n      refute_receive _any\n    end\n  end\n\n  describe \"region rebalancing\" do\n    test \"rebalancing needed process stops\", %{tenant: tenant} do\n      external_id = tenant.external_id\n\n      log =\n        capture_log(fn ->\n          assert {:ok, db_conn} = Connect.lookup_or_start_connection(external_id, check_connect_region_interval: 100)\n          expect(Rebalancer, :check, 1, fn _, _, ^external_id -> {:error, :wrong_region} end)\n          reject(&Rebalancer.check/3)\n          assert_process_down(db_conn, 1000, {:shutdown, :rebalancing})\n        end)\n\n      assert log =~ \"Rebalancing Tenant database connection\"\n    end\n\n    test \"rebalancing not needed process stays up\", %{tenant: tenant} do\n      external_id = tenant.external_id\n      assert {:ok, db_conn} = Connect.lookup_or_start_connection(external_id, check_connect_region_interval: 100)\n\n      stub(Rebalancer, :check, fn _, _, ^external_id -> :ok end)\n\n      refute_process_down(db_conn)\n    end\n  end\n\n  describe \"lookup_or_start_connection/1\" do\n    test \"if tenant exists and connected, returns the db connection and tracks it in ets\", %{tenant: tenant} do\n      assert {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert is_pid(db_conn)\n      assert Connect.shutdown(tenant.external_id) == :ok\n    end\n\n    test \"tracks multiple users that connect and disconnect\", %{tenant: tenant1} do\n      tenant2 = Containers.checkout_tenant(run_migrations: true)\n      tenants = [tenant1, tenant2]\n\n      for tenant <- tenants do\n        assert {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n\n        assert is_pid(db_conn)\n        Connect.shutdown(tenant.external_id)\n        assert_process_down(db_conn)\n\n        tenant.external_id\n      end\n\n      result = :ets.select(Connect, [{{:\"$1\"}, [], [:\"$1\"]}]) |> Enum.sort()\n      assert tenant1.external_id in result\n      assert tenant2.external_id in result\n    end\n\n    test \"on database disconnect, returns new connection\", %{tenant: tenant} do\n      assert {:ok, old_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      Connect.shutdown(tenant.external_id)\n      assert_process_down(old_conn)\n      # Sleeping here so that syn has enough time to unregister\n      # This could be avoided if we called :syn.unregister/2 on shutdown\n      Process.sleep(100)\n\n      assert {:ok, new_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n\n      on_exit(fn -> Process.exit(new_conn, :shutdown) end)\n\n      assert new_conn != old_conn\n      Connect.shutdown(tenant.external_id)\n    end\n\n    test \"if tenant exists but unable to connect, returns error\" do\n      port = Generators.port()\n\n      extensions = [\n        %{\n          \"type\" => \"postgres_cdc_rls\",\n          \"settings\" => %{\n            \"db_host\" => \"127.0.0.1\",\n            \"db_name\" => \"postgres\",\n            \"db_user\" => \"postgres\",\n            \"db_password\" => \"postgres\",\n            \"db_port\" => \"#{port}\",\n            \"poll_interval\" => 100,\n            \"poll_max_changes\" => 100,\n            \"poll_max_record_bytes\" => 1_048_576,\n            \"region\" => \"us-east-1\",\n            \"ssl_enforced\" => true\n          }\n        }\n      ]\n\n      tenant = tenant_fixture(%{extensions: extensions})\n      external_id = tenant.external_id\n\n      assert capture_log(fn ->\n               assert {:error, :tenant_database_unavailable} =\n                        Connect.lookup_or_start_connection(tenant.external_id)\n             end) =~ \"project=#{external_id} external_id=#{external_id} [error] UnableToConnectToTenantDatabase\"\n    end\n\n    test \"if tenant does not exist, returns error\" do\n      assert {:error, :tenant_not_found} = Connect.lookup_or_start_connection(\"none\")\n    end\n\n    test \"if no users are connected to a tenant channel, stop the connection\", %{\n      tenant: %{external_id: tenant_id} = tenant\n    } do\n      {:ok, db_conn} = Connect.lookup_or_start_connection(tenant_id, check_connected_user_interval: 100)\n\n      # Not enough time has passed, connection still alive\n      Process.sleep(400)\n      region = Tenants.region(tenant)\n      assert {_, %{conn: _, region: ^region}} = :syn.lookup(Connect, tenant_id)\n\n      assert_process_down(db_conn, 1000)\n      # Enough time has passed, syn has cleaned up\n      Process.sleep(100)\n      assert :undefined = :syn.lookup(Connect, tenant_id)\n      refute Process.alive?(db_conn)\n      Connect.shutdown(tenant_id)\n    end\n\n    test \"if users are connected to a tenant channel, keep the connection\", %{\n      tenant: %{external_id: tenant_id} = tenant\n    } do\n      UsersCounter.add(self(), tenant_id)\n\n      {:ok, db_conn} = Connect.lookup_or_start_connection(tenant_id, check_connected_user_interval: 10)\n\n      # Emulate connected user\n      UsersCounter.add(self(), tenant_id)\n      region = Tenants.region(tenant)\n      assert {pid, %{conn: conn_pid, region: ^region}} = :syn.lookup(Connect, tenant_id)\n      Process.sleep(300)\n      assert {^pid, %{conn: ^conn_pid, region: ^region}} = :syn.lookup(Connect, tenant_id)\n      assert Process.alive?(db_conn)\n\n      Connect.shutdown(tenant_id)\n    end\n\n    test \"connection is killed after user leaving\", %{tenant: tenant} do\n      external_id = tenant.external_id\n\n      UsersCounter.add(self(), external_id)\n\n      {:ok, db_conn} = Connect.lookup_or_start_connection(external_id, check_connected_user_interval: 10)\n      region = Tenants.region(tenant)\n      assert {_pid, %{conn: ^db_conn, region: ^region}} = :syn.lookup(Connect, external_id)\n      Beacon.leave(:users, external_id, self())\n      Process.sleep(1000)\n      refute Beacon.local_member?(:users, external_id, self())\n      refute Process.alive?(db_conn)\n      Connect.shutdown(external_id)\n    end\n\n    test \"error if tenant is suspended\" do\n      tenant = tenant_fixture(suspend: true)\n\n      assert {:error, :tenant_suspended} = Connect.lookup_or_start_connection(tenant.external_id)\n    end\n\n    test \"tenant not able to connect if database has not enough connections\", %{\n      tenant: tenant\n    } do\n      extension = %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"db_host\" => \"127.0.0.1\",\n          \"db_name\" => \"postgres\",\n          \"db_user\" => \"supabase_admin\",\n          \"db_password\" => \"postgres\",\n          \"poll_interval\" => 100,\n          \"poll_max_changes\" => 100,\n          \"poll_max_record_bytes\" => 1_048_576,\n          \"region\" => \"us-east-1\",\n          \"ssl_enforced\" => false,\n          \"db_pool\" => 100,\n          \"subcriber_pool_size\" => 100,\n          \"subs_pool_size\" => 100\n        }\n      }\n\n      {:ok, tenant} = update_extension(tenant, extension)\n\n      assert capture_log(fn ->\n               assert {:error, :tenant_db_too_many_connections} = Connect.lookup_or_start_connection(tenant.external_id)\n             end) =~ ~r/Only \\d+ available connections\\. At least \\d+ connections are required/\n    end\n\n    test \"handles tenant suspension and unsuspension in a reactive way\", %{tenant: tenant} do\n      assert {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert Connect.ready?(tenant.external_id)\n\n      Realtime.Tenants.suspend_tenant_by_external_id(tenant.external_id)\n      assert_process_down(db_conn)\n      # Wait for syn to unregister and Cachex to be invalided\n      Process.sleep(500)\n\n      assert {:error, :tenant_suspended} = Connect.lookup_or_start_connection(tenant.external_id)\n      refute Process.alive?(db_conn)\n\n      Realtime.Tenants.unsuspend_tenant_by_external_id(tenant.external_id)\n      Process.sleep(50)\n      assert {:ok, _} = Connect.lookup_or_start_connection(tenant.external_id)\n      Connect.shutdown(tenant.external_id)\n    end\n\n    test \"handles tenant suspension only on targetted suspended user\", %{tenant: tenant1} do\n      tenant2 = Containers.checkout_tenant(run_migrations: true)\n\n      assert {:ok, db_conn} = Connect.lookup_or_start_connection(tenant1.external_id)\n\n      log =\n        capture_log(fn ->\n          Realtime.Tenants.suspend_tenant_by_external_id(tenant2.external_id)\n          Process.sleep(50)\n        end)\n\n      refute log =~ \"Tenant was suspended\"\n      assert Process.alive?(db_conn)\n    end\n\n    test \"properly handles of failing calls by avoid creating too many connections\", %{tenant: tenant} do\n      extension = %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"db_host\" => \"127.0.0.1\",\n          \"db_name\" => \"postgres\",\n          \"db_user\" => \"supabase_admin\",\n          \"db_password\" => \"postgres\",\n          \"poll_interval\" => 100,\n          \"poll_max_changes\" => 100,\n          \"poll_max_record_bytes\" => 1_048_576,\n          \"region\" => \"us-east-1\",\n          \"ssl_enforced\" => true\n        }\n      }\n\n      {:ok, tenant} = update_extension(tenant, extension)\n\n      Enum.each(1..10, fn _ ->\n        Task.start(fn ->\n          Connect.lookup_or_start_connection(tenant.external_id)\n        end)\n      end)\n\n      send(check_db_connections_created(self(), tenant.external_id), :check)\n      Process.sleep(5000)\n      refute_receive :too_many_connections\n    end\n\n    test \"on migrations failure, stop the process\" do\n      tenant = Containers.checkout_tenant(run_migrations: false)\n      expect(Realtime.Tenants.Migrations, :run_migrations, fn ^tenant -> raise \"error\" end)\n\n      assert {:ok, pid} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert_process_down(pid)\n      refute Process.alive?(pid)\n    end\n\n    test \"reconciles migrations_ran when database count differs from cached value\", %{tenant: tenant} do\n      total_migrations = Enum.count(Realtime.Tenants.Migrations.migrations())\n      stale_count = tenant.migrations_ran - 5\n      parent = self()\n\n      expect(Database, :check_tenant_connection, fn t ->\n        {:ok, conn, _actual_count} = call_original(Database, :check_tenant_connection, [t])\n        {:ok, conn, stale_count}\n      end)\n\n      expect(Realtime.Tenants.Migrations, :run_migrations, fn tenant ->\n        send(parent, {:migrations_ran_at_run, tenant.migrations_ran})\n        call_original(Realtime.Tenants.Migrations, :run_migrations, [tenant])\n      end)\n\n      assert {:ok, _db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert Connect.ready?(tenant.external_id)\n\n      assert_receive {:migrations_ran_at_run, ^stale_count}\n\n      updated_tenant = Tenants.get_tenant_by_external_id(tenant.external_id)\n      assert updated_tenant.migrations_ran == total_migrations\n    end\n\n    test \"starts broadcast handler and does not fail on existing connection\", %{tenant: tenant} do\n      assert {:ok, _db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert Connect.ready?(tenant.external_id)\n\n      replication_connection_before = ReplicationConnection.whereis(tenant.external_id)\n      assert Process.alive?(replication_connection_before)\n\n      assert {:ok, replication_conn_pid_before} = Connect.replication_status(tenant.external_id)\n\n      assert {:ok, _db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n\n      replication_connection_after = ReplicationConnection.whereis(tenant.external_id)\n      assert Process.alive?(replication_connection_after)\n      assert replication_connection_before == replication_connection_after\n\n      assert {:ok, replication_conn_pid_after} = Connect.replication_status(tenant.external_id)\n      assert replication_conn_pid_before == replication_conn_pid_after\n    end\n\n    test \"on replication connection postgres pid being stopped, Connect module recovers it\", %{tenant: tenant} do\n      assert {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert Connect.ready?(tenant.external_id)\n\n      replication_connection_pid = ReplicationConnection.whereis(tenant.external_id)\n      Process.monitor(replication_connection_pid)\n\n      assert Process.alive?(replication_connection_pid)\n      pid = Connect.whereis(tenant.external_id)\n\n      assert {:ok, replication_conn_before} = Connect.replication_status(tenant.external_id)\n\n      Postgrex.query!(\n        db_conn,\n        \"SELECT pg_terminate_backend(pid) from pg_stat_activity where application_name='realtime_replication_connection'\",\n        []\n      )\n\n      assert_receive {:DOWN, _, :process, ^replication_connection_pid, _}\n\n      Process.sleep(100)\n      assert {:error, :not_connected} = Connect.replication_status(tenant.external_id)\n\n      new_replication_connection_pid = assert_pid(fn -> ReplicationConnection.whereis(tenant.external_id) end)\n\n      assert replication_connection_pid != new_replication_connection_pid\n      assert Process.alive?(new_replication_connection_pid)\n      assert Process.alive?(pid)\n\n      assert {:ok, replication_conn_after} = assert_replication_status(tenant.external_id)\n      assert replication_conn_before != replication_conn_after\n    end\n\n    test \"on replication connection exit, Connect module recovers it\", %{tenant: tenant} do\n      assert {:ok, _db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert Connect.ready?(tenant.external_id)\n\n      replication_connection_pid = ReplicationConnection.whereis(tenant.external_id)\n      Process.monitor(replication_connection_pid)\n      assert Process.alive?(replication_connection_pid)\n      pid = Connect.whereis(tenant.external_id)\n\n      assert {:ok, replication_conn_before} = Connect.replication_status(tenant.external_id)\n\n      Process.exit(replication_connection_pid, :kill)\n      assert_receive {:DOWN, _, :process, ^replication_connection_pid, _}\n\n      Process.sleep(100)\n      assert {:error, :not_connected} = Connect.replication_status(tenant.external_id)\n\n      new_replication_connection_pid = assert_pid(fn -> ReplicationConnection.whereis(tenant.external_id) end)\n\n      assert replication_connection_pid != new_replication_connection_pid\n      assert Process.alive?(new_replication_connection_pid)\n      assert Process.alive?(pid)\n\n      assert {:ok, replication_conn_after} = assert_replication_status(tenant.external_id)\n      assert replication_conn_before != replication_conn_after\n    end\n\n    test \"handles replication connection timeout by logging and shutting down\", %{tenant: tenant} do\n      expect(ReplicationConnection, :start, fn _tenant, _pid ->\n        {:error, :replication_connection_timeout}\n      end)\n\n      log =\n        capture_log(fn ->\n          assert {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n          assert_process_down(db_conn)\n        end)\n\n      assert log =~ \"ReplicationConnectionTimeout\"\n      assert log =~ \"Replication connection timed out during initialization\"\n    end\n\n    test \"handles max_wal_senders by logging the correct operational code\", %{tenant: tenant} do\n      opts = tenant |> Database.from_tenant(\"realtime_test\", :stop) |> Database.opts()\n      parent = self()\n\n      pids =\n        for i <- 0..4 do\n          replication_slot_opts =\n            %PostgresReplication{\n              connection_opts: opts,\n              table: :all,\n              output_plugin: \"pgoutput\",\n              output_plugin_options: [proto_version: \"1\", publication_names: \"test_#{i}_publication\"],\n              handler_module: Replication.TestHandler,\n              publication_name: \"test_#{i}_publication\",\n              replication_slot_name: \"test_#{i}_slot\"\n            }\n\n          spawn(fn ->\n            {:ok, pid} = PostgresReplication.start_link(replication_slot_opts)\n            send(parent, {:replication_ready, i})\n\n            receive do\n              :stop -> Process.exit(pid, :kill)\n            end\n          end)\n        end\n\n      for i <- 0..4, do: assert_receive({:replication_ready, ^i}, 5000)\n\n      on_exit(fn ->\n        Enum.each(pids, &send(&1, :stop))\n        Process.sleep(2000)\n      end)\n\n      log =\n        capture_log(fn ->\n          assert {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n          assert_process_down(db_conn)\n        end)\n\n      assert log =~ \"ReplicationMaxWalSendersReached\"\n    end\n\n    test \"handle rpc errors gracefully\" do\n      expect(Realtime.Nodes, :get_node_for_tenant, fn _ -> {:ok, :potato@nohost, \"us-east-1\"} end)\n\n      assert capture_log(fn -> assert {:error, :rpc_error, _} = Connect.lookup_or_start_connection(\"tenant\") end) =~\n               \"project=tenant external_id=tenant [error] ErrorOnRpcCall\"\n    end\n\n    test \"rate limit connect when too many connections against bad database\", %{tenant: tenant} do\n      extension = %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"db_host\" => \"127.0.0.1\",\n          \"db_name\" => \"postgres\",\n          \"db_user\" => \"supabase_admin\",\n          \"db_password\" => \"postgres\",\n          \"poll_interval\" => 100,\n          \"poll_max_changes\" => 100,\n          \"poll_max_record_bytes\" => 1_048_576,\n          \"region\" => \"us-east-1\",\n          \"ssl_enforced\" => true\n        }\n      }\n\n      {:ok, tenant} = update_extension(tenant, extension)\n\n      log =\n        capture_log(fn ->\n          res =\n            for _ <- 1..10 do\n              Process.sleep(250)\n              Connect.lookup_or_start_connection(tenant.external_id)\n            end\n\n          assert Enum.any?(res, fn {_, res} -> res == :connect_rate_limit_reached end)\n        end)\n\n      assert log =~ \"DatabaseConnectionRateLimitReached: Too many connection attempts against the tenant database\"\n    end\n\n    test \"rate limit connect will not trigger if connection is successful\", %{tenant: tenant} do\n      log =\n        capture_log(fn ->\n          res =\n            for _ <- 1..20 do\n              Process.sleep(500)\n              Connect.lookup_or_start_connection(tenant.external_id)\n            end\n\n          refute Enum.any?(res, fn {_, res} -> res == :tenant_db_too_many_connections end)\n        end)\n\n      refute log =~ \"DatabaseConnectionRateLimitReached: Too many connection attempts against the tenant database\"\n    end\n  end\n\n  describe \"shutdown/1\" do\n    test \"shutdowns all associated connections\", %{tenant: tenant} do\n      assert {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert Process.alive?(db_conn)\n      assert Connect.ready?(tenant.external_id)\n      connect_pid = Connect.whereis(tenant.external_id)\n      replication_connection_pid = ReplicationConnection.whereis(tenant.external_id)\n      assert Process.alive?(connect_pid)\n      assert Process.alive?(replication_connection_pid)\n\n      assert {_, %{conn: ^db_conn}} = :syn.lookup(Connect, tenant.external_id)\n      assert {:ok, _replication_conn_pid} = Connect.replication_status(tenant.external_id)\n\n      Connect.shutdown(tenant.external_id)\n      assert_process_down(connect_pid)\n      assert_process_down(replication_connection_pid)\n    end\n\n    test \"if tenant does not exist, does nothing\" do\n      assert :ok = Connect.shutdown(\"none\")\n    end\n  end\n\n  describe \"backoff configuration\" do\n    test \"backoff is configured with correct min/max/type values\", %{tenant: tenant} do\n      assert {:ok, _db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      pid = Connect.whereis(tenant.external_id)\n      state = :sys.get_state(pid)\n      assert state.backoff.min == :timer.seconds(1)\n      assert state.backoff.max == :timer.seconds(15)\n      assert state.backoff.type == :rand_exp\n    end\n  end\n\n  describe \"replication recovery\" do\n    test \"recovery reschedules without stopping when pg_stat_activity shows existing walsender\", %{tenant: tenant} do\n      assert {:ok, _db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert Connect.ready?(tenant.external_id)\n\n      pid = Connect.whereis(tenant.external_id)\n\n      # The real replication connection is active, so pg_stat_activity returns num_rows: 1 naturally\n      send(pid, :recover_replication_connection)\n      Process.sleep(100)\n\n      assert Process.alive?(pid)\n    end\n\n    test \"recovery stops when elapsed time exceeds 2-hour window\", %{tenant: tenant} do\n      assert {:ok, _db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert Connect.ready?(tenant.external_id)\n\n      pid = Connect.whereis(tenant.external_id)\n      ref = Process.monitor(pid)\n\n      past_ts = System.monotonic_time(:millisecond) - :timer.hours(3)\n      :sys.replace_state(pid, fn state -> %{state | replication_recovery_started_at: past_ts} end)\n\n      log =\n        capture_log(fn ->\n          send(pid, :recover_replication_connection)\n          assert_receive {:DOWN, ^ref, :process, ^pid, _reason}, 1000\n        end)\n\n      assert log =~ \"Replication recovery window exceeded\"\n    end\n\n    test \"recovery preserves replication_recovery_started_at across multiple crashes\", %{tenant: tenant} do\n      assert {:ok, _db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert Connect.ready?(tenant.external_id)\n\n      pid = Connect.whereis(tenant.external_id)\n      original_ts = System.monotonic_time(:millisecond) - 1000\n\n      ref = make_ref()\n\n      :sys.replace_state(pid, fn state ->\n        %{\n          state\n          | replication_connection_reference: ref,\n            replication_connection_pid: self(),\n            replication_recovery_started_at: original_ts\n        }\n      end)\n\n      send(pid, {:DOWN, ref, :process, self(), :simulated_crash})\n      Process.sleep(100)\n\n      state = :sys.get_state(pid)\n      assert state.replication_recovery_started_at == original_ts\n\n      Connect.shutdown(tenant.external_id)\n    end\n\n    test \"recovery resets replication_recovery_started_at on successful reconnection\", %{tenant: tenant} do\n      assert {:ok, _db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert Connect.ready?(tenant.external_id)\n\n      pid = Connect.whereis(tenant.external_id)\n\n      replication_pid = ReplicationConnection.whereis(tenant.external_id)\n      Process.monitor(replication_pid)\n      Process.exit(replication_pid, :kill)\n      assert_receive {:DOWN, _, :process, ^replication_pid, _}, 1000\n\n      Process.sleep(100)\n      assert {:error, :not_connected} = Connect.replication_status(tenant.external_id)\n\n      assert {:ok, _} = assert_replication_status(tenant.external_id)\n\n      state = :sys.get_state(pid)\n      assert state.replication_recovery_started_at == nil\n      assert Process.alive?(pid)\n\n      Connect.shutdown(tenant.external_id)\n    end\n  end\n\n  describe \"get_status/1 degraded state\" do\n    test \"returns {:ok, conn} when replication_conn is nil in syn\", %{tenant: tenant} do\n      assert {:ok, _db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      assert Connect.ready?(tenant.external_id)\n\n      tenant_id = tenant.external_id\n\n      :syn.update_registry(Connect, tenant_id, fn _pid, meta -> %{meta | replication_conn: nil} end)\n\n      assert {:ok, conn} = Connect.get_status(tenant_id)\n      assert is_pid(conn)\n\n      Connect.shutdown(tenant_id)\n    end\n  end\n\n  describe \"registers into local registry\" do\n    test \"successfully registers a process\", %{tenant: %{external_id: external_id}} do\n      assert {:ok, _db_conn} = Connect.lookup_or_start_connection(external_id)\n      assert Registry.whereis_name({Realtime.Tenants.Connect.Registry, external_id})\n    end\n\n    test \"successfully unregisters a process\", %{tenant: %{external_id: external_id}} do\n      assert {:ok, _db_conn} = Connect.lookup_or_start_connection(external_id)\n      assert Registry.whereis_name({Realtime.Tenants.Connect.Registry, external_id})\n      Connect.shutdown(external_id)\n      Process.sleep(100)\n      assert :undefined = Registry.whereis_name({Realtime.Tenants.Connect.Registry, external_id})\n    end\n  end\n\n  defp check_db_connections_created(test_pid, tenant_id) do\n    spawn(fn ->\n      receive do\n        :check ->\n          processes =\n            for pid <- Process.list(),\n                info = Process.info(pid),\n                dict = Keyword.get(info, :dictionary, []),\n                match?({DBConnection.Connection, :init, 1}, dict[:\"$initial_call\"]),\n                Keyword.get(dict, :\"$logger_metadata$\")[:external_id] == tenant_id do\n              pid\n            end\n\n          Process.send_after(check_db_connections_created(test_pid, tenant_id), :check, 500)\n\n          if length(processes) > 1 do\n            send(test_pid, :too_many_connections)\n          end\n      end\n    end)\n  end\n\n  defp update_extension(tenant, extension) do\n    db_port = Realtime.Crypto.decrypt!(hd(tenant.extensions).settings[\"db_port\"])\n\n    extensions = [\n      put_in(extension, [\"settings\", \"db_port\"], db_port)\n    ]\n\n    Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{extensions: extensions})\n  end\n\n  defp assert_pid(call, attempts \\\\ 10)\n\n  defp assert_pid(_call, 0) do\n    raise \"Timeout waiting for pid\"\n  end\n\n  defp assert_pid(call, attempts) do\n    case call.() do\n      pid when is_pid(pid) ->\n        pid\n\n      _ ->\n        Process.sleep(500)\n        assert_pid(call, attempts - 1)\n    end\n  end\n\n  defp assert_replication_status(tenant_id, attempts \\\\ 20)\n\n  defp assert_replication_status(tenant_id, 0) do\n    Connect.replication_status(tenant_id)\n  end\n\n  defp assert_replication_status(tenant_id, attempts) do\n    case Connect.replication_status(tenant_id) do\n      {:ok, _} = result -> result\n      _ -> Process.sleep(500) && assert_replication_status(tenant_id, attempts - 1)\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/janitor/maintenance_task_test.exs",
    "content": "defmodule Realtime.Tenants.Janitor.MaintenanceTaskTest do\n  use Realtime.DataCase, async: true\n\n  alias Realtime.Tenants.Janitor.MaintenanceTask\n  alias Realtime.Api.Message\n  alias Realtime.Database\n  alias Realtime.Tenants.Repo\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    # Warm cache to avoid Cachex and Ecto.Sandbox ownership issues\n    Realtime.Tenants.Cache.update_cache(tenant)\n\n    %{tenant: tenant}\n  end\n\n  test \"cleans messages older than 72 hours and creates partitions\", %{tenant: tenant} do\n    {:ok, conn} = Database.connect(tenant, \"realtime_test\", :stop)\n\n    utc_now = NaiveDateTime.utc_now()\n    limit = NaiveDateTime.add(utc_now, -72, :hour)\n\n    date_start = Date.utc_today() |> Date.add(-10)\n    date_end = Date.utc_today()\n    create_messages_partitions(conn, date_start, date_end)\n\n    messages =\n      for days <- -5..0 do\n        inserted_at = NaiveDateTime.add(utc_now, days, :day)\n        message_fixture(tenant, %{inserted_at: inserted_at})\n      end\n      |> MapSet.new()\n\n    to_keep =\n      messages\n      |> Enum.reject(&(NaiveDateTime.compare(NaiveDateTime.beginning_of_day(limit), &1.inserted_at) == :gt))\n      |> MapSet.new()\n\n    assert MaintenanceTask.run(tenant.external_id) == :ok\n\n    {:ok, res} = Repo.all(conn, from(m in Message), Message)\n\n    verify_partitions(conn)\n\n    current = MapSet.new(res)\n\n    assert MapSet.difference(current, to_keep) |> MapSet.size() == 0\n  end\n\n  test \"exits if fails to remove old messages\" do\n    extensions = [\n      %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"db_host\" => \"127.0.0.1\",\n          \"db_name\" => \"postgres\",\n          \"db_user\" => \"supabase_admin\",\n          \"db_password\" => \"postgres\",\n          \"db_port\" => \"11111\",\n          \"poll_interval\" => 100,\n          \"poll_max_changes\" => 100,\n          \"poll_max_record_bytes\" => 1_048_576,\n          \"region\" => \"us-east-1\",\n          \"ssl_enforced\" => false\n        }\n      }\n    ]\n\n    tenant = tenant_fixture(%{extensions: extensions})\n    # Warm cache to avoid Cachex and Ecto.Sandbox ownership issues\n    Realtime.Tenants.Cache.update_cache(tenant)\n\n    Process.flag(:trap_exit, true)\n\n    t =\n      Task.async(fn ->\n        MaintenanceTask.run(tenant.external_id)\n      end)\n\n    pid = t.pid\n    ref = t.ref\n    assert_receive {:EXIT, ^pid, :killed}\n    assert_receive {:DOWN, ^ref, :process, ^pid, :killed}\n  end\n\n  defp verify_partitions(conn) do\n    today = Date.utc_today()\n    yesterday = Date.add(today, -3)\n    future = Date.add(today, 3)\n    dates = Date.range(yesterday, future)\n\n    %{rows: rows} =\n      Postgrex.query!(\n        conn,\n        \"SELECT tablename from pg_catalog.pg_tables where schemaname = 'realtime' and tablename like 'messages_%'\",\n        []\n      )\n\n    partitions = MapSet.new(rows, fn [name] -> name end)\n\n    expected_names =\n      MapSet.new(dates, fn date -> \"messages_#{date |> Date.to_iso8601() |> String.replace(\"-\", \"_\")}\" end)\n\n    assert MapSet.equal?(partitions, expected_names)\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/janitor_test.exs",
    "content": "defmodule Realtime.Tenants.JanitorTest do\n  # async: false due to the fact that we're checking ets tables that can be modified by other tests\n  use Realtime.DataCase, async: false\n\n  import ExUnit.CaptureLog\n\n  alias Realtime.Api.Message\n  alias Realtime.Database\n  alias Realtime.Tenants.Janitor\n  alias Realtime.Tenants.Connect\n  alias Realtime.Tenants.Repo\n\n  setup do\n    :ets.delete_all_objects(Connect)\n    timer = Application.get_env(:realtime, :janitor_schedule_timer)\n\n    Application.put_env(:realtime, :janitor_schedule_timer, 200)\n    Application.put_env(:realtime, :janitor_schedule_randomize, false)\n    Application.put_env(:realtime, :janitor_chunk_size, 2)\n    tenant1 = Containers.checkout_tenant(run_migrations: true)\n    tenant2 = Containers.checkout_tenant(run_migrations: true)\n\n    tenants =\n      Enum.map(\n        [tenant1, tenant2],\n        fn tenant ->\n          tenant = Realtime.Repo.preload(tenant, :extensions)\n          Connect.lookup_or_start_connection(tenant.external_id)\n          Process.sleep(500)\n          tenant\n        end\n      )\n\n    date_start = Date.utc_today() |> Date.add(-10)\n    date_end = Date.utc_today()\n\n    Enum.map(tenants, fn tenant ->\n      {:ok, conn} = Database.connect(tenant, \"realtime_test\", :stop)\n      create_messages_partitions(conn, date_start, date_end)\n    end)\n\n    start_supervised!(\n      {Task.Supervisor,\n       name: Realtime.Tenants.Janitor.TaskSupervisor, max_children: 5, max_seconds: 500, max_restarts: 1}\n    )\n\n    on_exit(fn ->\n      Enum.each(tenants, &Connect.shutdown(&1.external_id))\n      Process.sleep(10)\n      Application.put_env(:realtime, :janitor_schedule_timer, timer)\n    end)\n\n    %{tenants: tenants}\n  end\n\n  test \"cleans messages older than 72 hours and creates partitions from tenants that were active and untracks the user and test tenant is connected\",\n       %{\n         tenants: tenants\n       } do\n    utc_now = NaiveDateTime.utc_now()\n    limit = NaiveDateTime.add(utc_now, -72, :hour)\n\n    messages =\n      for days <- -5..0 do\n        inserted_at = NaiveDateTime.add(utc_now, days, :day)\n        Enum.map(tenants, &message_fixture(&1, %{inserted_at: inserted_at}))\n      end\n      |> List.flatten()\n      |> MapSet.new()\n\n    to_keep =\n      messages\n      |> Enum.reject(&(NaiveDateTime.compare(NaiveDateTime.beginning_of_day(limit), &1.inserted_at) == :gt))\n      |> MapSet.new()\n\n    start_supervised!(Janitor)\n    Process.sleep(500)\n\n    current =\n      Enum.map(tenants, fn tenant ->\n        {:ok, conn} = Database.connect(tenant, \"realtime_test\", :stop)\n        {:ok, res} = Repo.all(conn, from(m in Message), Message)\n\n        verify_partitions(conn)\n\n        res\n      end)\n      |> List.flatten()\n      |> MapSet.new()\n\n    assert MapSet.difference(current, to_keep) |> MapSet.size() == 0\n\n    assert :ets.tab2list(Connect) == []\n  end\n\n  test \"cleans messages older than 72 hours and creates partitions from tenants that were active and untracks the user and test tenant has disconnected\",\n       %{\n         tenants: tenants\n       } do\n    Connect.shutdown(hd(tenants).external_id)\n    Process.sleep(100)\n\n    utc_now = NaiveDateTime.utc_now()\n    limit = NaiveDateTime.add(utc_now, -72, :hour)\n\n    messages =\n      for days <- -5..0 do\n        inserted_at = NaiveDateTime.add(utc_now, days, :day)\n        Enum.map(tenants, &message_fixture(&1, %{inserted_at: inserted_at}))\n      end\n      |> List.flatten()\n      |> MapSet.new()\n\n    to_keep =\n      messages\n      |> Enum.reject(&(NaiveDateTime.compare(NaiveDateTime.beginning_of_day(limit), &1.inserted_at) == :gt))\n      |> MapSet.new()\n\n    start_supervised!(Janitor)\n    Process.sleep(500)\n\n    current =\n      Enum.map(tenants, fn tenant ->\n        {:ok, conn} = Database.connect(tenant, \"realtime_test\", :stop)\n        {:ok, res} = Repo.all(conn, from(m in Message), Message)\n\n        verify_partitions(conn)\n\n        res\n      end)\n      |> List.flatten()\n      |> MapSet.new()\n\n    assert MapSet.difference(current, to_keep) |> MapSet.size() == 0\n    assert :ets.tab2list(Connect) == []\n  end\n\n  test \"logs error if fails to connect to tenant\" do\n    extensions = [\n      %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"db_host\" => \"127.0.0.1\",\n          \"db_name\" => \"postgres\",\n          \"db_user\" => \"supabase_admin\",\n          \"db_password\" => \"postgres\",\n          \"db_port\" => \"1111\",\n          \"poll_interval\" => 100,\n          \"poll_max_changes\" => 100,\n          \"poll_max_record_bytes\" => 1_048_576,\n          \"region\" => \"us-east-1\",\n          \"ssl_enforced\" => false\n        }\n      }\n    ]\n\n    tenant = tenant_fixture(%{extensions: extensions})\n    # Force add a bad tenant\n    :ets.insert(Connect, {tenant.external_id})\n\n    Process.sleep(250)\n\n    assert capture_log(fn ->\n             start_supervised!(Janitor)\n             Process.sleep(1000)\n           end) =~ \"JanitorFailedToDeleteOldMessages\"\n\n    assert :ets.tab2list(Connect) == []\n  end\n\n  defp verify_partitions(conn) do\n    today = Date.utc_today()\n    yesterday = Date.add(today, -3)\n    future = Date.add(today, 3)\n    dates = Date.range(yesterday, future)\n\n    %{rows: rows} =\n      Postgrex.query!(\n        conn,\n        \"SELECT tablename from pg_catalog.pg_tables where schemaname = 'realtime' and tablename like 'messages_%'\",\n        []\n      )\n\n    partitions = MapSet.new(rows, fn [name] -> name end)\n\n    expected_names =\n      MapSet.new(dates, fn date -> \"messages_#{date |> Date.to_iso8601() |> String.replace(\"-\", \"_\")}\" end)\n\n    assert MapSet.equal?(partitions, expected_names)\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/migrations_test.exs",
    "content": "defmodule Realtime.Tenants.MigrationsTest do\n  alias Realtime.Tenants.Cache\n  # Can't use async: true because Cachex does not work well with Ecto Sandbox\n  use Realtime.DataCase, async: false\n\n  alias Realtime.Tenants.Migrations\n\n  describe \"run_migrations/1\" do\n    test \"migrations for a given tenant only run once\" do\n      tenant = Containers.checkout_tenant()\n\n      res =\n        for _ <- 0..10 do\n          Task.async(fn -> Migrations.run_migrations(tenant) end)\n        end\n        |> Task.await_many()\n        |> Enum.uniq()\n\n      assert [:ok] = res\n    end\n\n    test \"migrations run if tenant has migrations_ran set to 0\" do\n      tenant = Containers.checkout_tenant()\n\n      assert Migrations.run_migrations(tenant) == :ok\n      # Sleeping waiting for Cache to be invalided\n      Process.sleep(100)\n      assert Cache.get_tenant_by_external_id(tenant.external_id).migrations_ran == Enum.count(Migrations.migrations())\n    end\n\n    test \"migrations do not run if tenant has migrations_ran at the count of all migrations\" do\n      tenant = tenant_fixture(%{migrations_ran: Enum.count(Migrations.migrations())})\n      assert Migrations.run_migrations(tenant) == :noop\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/rebalancer_test.exs",
    "content": "defmodule Realtime.Tenants.RebalancerTest do\n  use Realtime.DataCase, async: true\n\n  alias Realtime.Tenants.Rebalancer\n  alias Realtime.Nodes\n\n  use Mimic\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    # Warm cache to avoid Cachex and Ecto.Sandbox ownership issues\n    Realtime.Tenants.Cache.update_cache(tenant)\n    %{tenant: tenant}\n  end\n\n  describe \"check/3\" do\n    test \"different node set returns :ok\", %{tenant: tenant} do\n      external_id = tenant.external_id\n\n      # Don't even try to look for the region\n      reject(&Nodes.get_node_for_tenant/1)\n\n      assert Rebalancer.check(MapSet.new([node()]), MapSet.new([node(), :other_node]), external_id) == :ok\n    end\n\n    test \"same node set correct region set returns :ok\", %{tenant: tenant} do\n      external_id = tenant.external_id\n      current_region = Application.fetch_env!(:realtime, :region)\n\n      expect(Nodes, :get_node_for_tenant, fn ^tenant -> {:ok, :some_node, current_region} end)\n      reject(&Nodes.get_node_for_tenant/1)\n\n      assert Rebalancer.check(MapSet.new([node(), :some_node]), MapSet.new([node(), :some_node]), external_id) == :ok\n    end\n\n    test \"same node set different region set returns :ok\", %{tenant: tenant} do\n      external_id = tenant.external_id\n\n      expect(Nodes, :get_node_for_tenant, fn ^tenant -> {:ok, :some_node, \"ap-southeast-1\"} end)\n      reject(&Nodes.get_node_for_tenant/1)\n\n      assert Rebalancer.check(MapSet.new([node(), :some_node]), MapSet.new([node(), :some_node]), external_id) ==\n               {:error, :wrong_region}\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/replication_connection/watchdog_test.exs",
    "content": "defmodule Realtime.Tenants.ReplicationConnection.WatchdogTest do\n  use ExUnit.Case, async: true\n\n  import ExUnit.CaptureLog\n\n  alias Realtime.Tenants.ReplicationConnection.Watchdog\n\n  defmodule FakeReplicationConnection do\n    def child_spec(opts) do\n      %{id: __MODULE__, start: {__MODULE__, :start_link, [opts]}, type: :worker, restart: :temporary, shutdown: 500}\n    end\n\n    def start_link(opts \\\\ []), do: :gen_statem.start_link(__MODULE__, opts, [])\n\n    def callback_mode, do: :state_functions\n\n    def init(opts) do\n      respond_to_health_checks = Keyword.get(opts, :respond_to_health_checks, true)\n      delay_ms = Keyword.get(opts, :delay_ms, 0)\n\n      data = %{\n        respond_to_health_checks: respond_to_health_checks,\n        delay_ms: delay_ms,\n        health_check_count: 0\n      }\n\n      {:ok, :idle, data}\n    end\n\n    def idle({:call, from}, :health_check, %{respond_to_health_checks: true, delay_ms: delay_ms} = data) do\n      if delay_ms > 0 do\n        Process.sleep(delay_ms)\n      end\n\n      :gen_statem.reply(from, :ok)\n      {:keep_state, %{data | health_check_count: data.health_check_count + 1}}\n    end\n\n    def idle({:call, _from}, :health_check, %{respond_to_health_checks: false} = data) do\n      # Don't reply - this will cause a timeout\n      {:keep_state, %{data | health_check_count: data.health_check_count + 1}}\n    end\n\n    def idle({:call, from}, :get_health_check_count, data) do\n      :gen_statem.reply(from, data.health_check_count)\n      {:keep_state, data}\n    end\n\n    def idle({:call, from}, :set_no_respond, data) do\n      :gen_statem.reply(from, :ok)\n      {:keep_state, %{data | respond_to_health_checks: false}}\n    end\n\n    def get_health_check_count(pid), do: :gen_statem.call(pid, :get_health_check_count)\n\n    def set_no_respond(pid), do: :gen_statem.call(pid, :set_no_respond)\n  end\n\n  test \"performs periodic health checks successfully\" do\n    fake_pid = start_link_supervised!(FakeReplicationConnection)\n\n    watchdog_pid =\n      start_supervised!(\n        {Watchdog, parent_pid: fake_pid, tenant_id: \"test-tenant\", watchdog_interval: 50, watchdog_timeout: 100}\n      )\n\n    # Wait for at least 2 health check cycles\n    Process.sleep(150)\n\n    assert Process.alive?(watchdog_pid)\n    assert Process.alive?(fake_pid)\n\n    # Verify health checks were performed\n    count = FakeReplicationConnection.get_health_check_count(fake_pid)\n    assert count >= 2\n  end\n\n  describe \"timeout handling\" do\n    test \"stops when health check times out\" do\n      # Create a fake process that doesn't respond to health checks\n      fake_pid = start_supervised!({FakeReplicationConnection, respond_to_health_checks: false})\n\n      logs =\n        capture_log(fn ->\n          watchdog_pid =\n            start_supervised!(\n              {Watchdog, parent_pid: fake_pid, tenant_id: \"test-tenant\", watchdog_interval: 50, watchdog_timeout: 100}\n            )\n\n          ref = Process.monitor(watchdog_pid)\n\n          # Wait for the first health check to timeout\n          assert_receive {:DOWN, ^ref, :process, ^watchdog_pid, :watchdog_timeout}, 500\n          refute Process.alive?(watchdog_pid)\n        end)\n\n      assert logs =~ \"ReplicationConnectionWatchdogTimeout\"\n      assert logs =~ \"ReplicationConnection is not responding\"\n    end\n\n    test \"stops immediately if health check takes longer than timeout\" do\n      # Create a fake process with a 200ms delay\n      fake_pid = start_supervised!({FakeReplicationConnection, delay_ms: 200})\n\n      logs =\n        capture_log(fn ->\n          watchdog_pid =\n            start_supervised!(\n              {Watchdog, parent_pid: fake_pid, tenant_id: \"timeout-test\", watchdog_interval: 50, watchdog_timeout: 100}\n            )\n\n          ref = Process.monitor(watchdog_pid)\n\n          # Should timeout because delay (200ms) > timeout (100ms)\n          assert_receive {:DOWN, ^ref, :process, ^watchdog_pid, :watchdog_timeout}, 500\n        end)\n\n      assert logs =~ \"ReplicationConnectionWatchdogTimeout\"\n    end\n  end\n\n  describe \"dynamic behavior changes\" do\n    test \"handles transition from healthy to timeout\" do\n      # Start with responding, then stop responding\n      fake_pid = start_supervised!(FakeReplicationConnection)\n\n      watchdog_pid =\n        start_supervised!(\n          {Watchdog, parent_pid: fake_pid, tenant_id: \"test-tenant\", watchdog_interval: 50, watchdog_timeout: 100}\n        )\n\n      # Wait for first successful health check\n      Process.sleep(80)\n      assert Process.alive?(watchdog_pid)\n\n      ref = Process.monitor(watchdog_pid)\n      # Now make the fake process stop responding\n      FakeReplicationConnection.set_no_respond(fake_pid)\n\n      logs =\n        capture_log(fn ->\n          # Should timeout on next health check\n          assert_receive {:DOWN, ^ref, :process, ^watchdog_pid, :watchdog_timeout}, 500\n        end)\n\n      assert logs =~ \"ReplicationConnectionWatchdogTimeout\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/replication_connection_test.exs",
    "content": "defmodule Realtime.Tenants.ReplicationConnectionTest do\n  # Async false due to tweaking application env\n  use Realtime.DataCase, async: false\n  use Mimic\n  setup :set_mimic_global\n\n  import ExUnit.CaptureLog\n\n  alias Realtime.Api.Message\n  alias Realtime.Database\n  alias Realtime.Tenants\n  alias Realtime.Tenants.ReplicationConnection\n  alias RealtimeWeb.Endpoint\n  alias Realtime.Tenants.Repo\n\n  @replication_slot_name \"supabase_realtime_messages_replication_slot_test\"\n\n  setup do\n    slot = Application.get_env(:realtime, :slot_name_suffix)\n    on_exit(fn -> Application.put_env(:realtime, :slot_name_suffix, slot) end)\n    Application.put_env(:realtime, :slot_name_suffix, \"test\")\n\n    tenant = Containers.checkout_tenant(run_migrations: true)\n\n    {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n    Integrations.setup_postgres_changes(db_conn)\n    Postgrex.query(db_conn, \"SELECT pg_drop_replication_slot($1)\", [@replication_slot_name])\n\n    %{tenant: tenant, db_conn: db_conn}\n  end\n\n  describe \"temporary process\" do\n    test \"starts a temporary process\", %{tenant: tenant} do\n      assert {:ok, pid} = ReplicationConnection.start(tenant, self())\n      assert conn = ReplicationConnection.whereis(tenant.external_id)\n\n      # Brutally kill the process\n      Process.exit(pid, :kill)\n      assert_process_down(pid)\n      assert_process_down(conn)\n      # Wait to ensure that the process has not restarted\n      Process.sleep(1000)\n\n      # Temporary process should not be registered\n      refute ReplicationConnection.whereis(tenant.external_id)\n    end\n  end\n\n  describe \"replication\" do\n    test \"fails if tenant connection is invalid\" do\n      tenant =\n        tenant_fixture(%{\n          \"extensions\" => [\n            %{\n              \"type\" => \"postgres_cdc_rls\",\n              \"settings\" => %{\n                \"db_host\" => \"127.0.0.1\",\n                \"db_name\" => \"postgres\",\n                \"db_user\" => \"supabase_admin\",\n                \"db_password\" => \"postgres\",\n                \"db_port\" => \"9001\",\n                \"poll_interval\" => 100,\n                \"poll_max_changes\" => 100,\n                \"poll_max_record_bytes\" => 1_048_576,\n                \"region\" => \"us-east-1\",\n                \"ssl_enforced\" => true\n              }\n            }\n          ]\n        })\n\n      assert {:error, _} = ReplicationConnection.start(tenant, self())\n    end\n\n    test \"starts a handler for the tenant and broadcasts\", %{tenant: tenant, db_conn: db_conn} do\n      start_link_supervised!(\n        {ReplicationConnection, %ReplicationConnection{tenant_id: tenant.external_id, monitored_pid: self()}},\n        restart: :transient\n      )\n\n      topic = random_string()\n      tenant_topic = Tenants.tenant_topic(tenant.external_id, topic, false)\n      subscribe(tenant_topic, topic)\n\n      total_messages = 5\n      # Works with one insert per transaction\n      for _ <- 1..total_messages do\n        value = random_string()\n\n        row =\n          message_fixture(tenant, %{\n            \"topic\" => topic,\n            \"private\" => true,\n            \"event\" => \"INSERT\",\n            \"payload\" => %{\"value\" => value}\n          })\n\n        assert_receive {:socket_push, :text, data}\n        message = data |> IO.iodata_to_binary() |> Jason.decode!()\n\n        payload = %{\n          \"event\" => \"INSERT\",\n          \"meta\" => %{\"id\" => row.id},\n          \"payload\" => %{\n            \"value\" => value\n          },\n          \"type\" => \"broadcast\"\n        }\n\n        assert message == %{\"event\" => \"broadcast\", \"payload\" => payload, \"ref\" => nil, \"topic\" => topic}\n      end\n\n      Process.sleep(500)\n      # Works with batch inserts\n      messages =\n        for _ <- 1..total_messages do\n          Message.changeset(%Message{}, %{\n            \"topic\" => topic,\n            \"private\" => true,\n            \"event\" => \"INSERT\",\n            \"extension\" => \"broadcast\",\n            \"payload\" => %{\"value\" => random_string()}\n          })\n        end\n\n      {:ok, _} = Repo.insert_all_entries(db_conn, messages, Message)\n\n      messages_received =\n        for _ <- 1..total_messages, into: [] do\n          assert_receive {:socket_push, :text, data}\n          data |> IO.iodata_to_binary() |> Jason.decode!()\n        end\n\n      for row <- messages do\n        assert Enum.count(messages_received, fn message_received ->\n                 value = row |> Map.from_struct() |> get_in([:changes, :payload, \"value\"])\n\n                 match?(\n                   %{\n                     \"event\" => \"broadcast\",\n                     \"payload\" => %{\n                       \"event\" => \"INSERT\",\n                       \"meta\" => %{\"id\" => _id},\n                       \"payload\" => %{\n                         \"value\" => ^value\n                       }\n                     },\n                     \"ref\" => nil,\n                     \"topic\" => ^topic\n                   },\n                   message_received\n                 )\n               end) == 1\n      end\n    end\n\n    test \"starts a handler for the tenant and broadcasts to public channel\", %{tenant: tenant, db_conn: db_conn} do\n      start_link_supervised!(\n        {ReplicationConnection, %ReplicationConnection{tenant_id: tenant.external_id, monitored_pid: self()}},\n        restart: :transient\n      )\n\n      topic = random_string()\n      tenant_topic = Tenants.tenant_topic(tenant.external_id, topic, true)\n      subscribe(tenant_topic, topic)\n\n      total_messages = 5\n      # Works with one insert per transaction\n      for _ <- 1..total_messages do\n        value = random_string()\n\n        row =\n          message_fixture(tenant, %{\n            \"topic\" => topic,\n            \"private\" => false,\n            \"event\" => \"INSERT\",\n            \"payload\" => %{\"value\" => value}\n          })\n\n        assert_receive {:socket_push, :text, data}\n        message = data |> IO.iodata_to_binary() |> Jason.decode!()\n\n        payload = %{\n          \"event\" => \"INSERT\",\n          \"meta\" => %{\"id\" => row.id},\n          \"payload\" => %{\n            \"value\" => value\n          },\n          \"type\" => \"broadcast\"\n        }\n\n        assert message == %{\"event\" => \"broadcast\", \"payload\" => payload, \"ref\" => nil, \"topic\" => topic}\n      end\n\n      Process.sleep(500)\n      # Works with batch inserts\n      messages =\n        for _ <- 1..total_messages do\n          Message.changeset(%Message{}, %{\n            \"topic\" => topic,\n            \"private\" => false,\n            \"event\" => \"INSERT\",\n            \"extension\" => \"broadcast\",\n            \"payload\" => %{\"value\" => random_string()}\n          })\n        end\n\n      {:ok, _} = Repo.insert_all_entries(db_conn, messages, Message)\n\n      messages_received =\n        for _ <- 1..total_messages, into: [] do\n          assert_receive {:socket_push, :text, data}\n          data |> IO.iodata_to_binary() |> Jason.decode!()\n        end\n\n      for row <- messages do\n        assert Enum.count(messages_received, fn message_received ->\n                 value = row |> Map.from_struct() |> get_in([:changes, :payload, \"value\"])\n\n                 match?(\n                   %{\n                     \"event\" => \"broadcast\",\n                     \"payload\" => %{\n                       \"event\" => \"INSERT\",\n                       \"meta\" => %{\"id\" => _id},\n                       \"payload\" => %{\n                         \"value\" => ^value\n                       }\n                     },\n                     \"ref\" => nil,\n                     \"topic\" => ^topic\n                   },\n                   message_received\n                 )\n               end) == 1\n      end\n    end\n\n    test \"replicates binary with exactly 16 bytes to test UUID conversion error\", %{tenant: tenant} do\n      start_link_supervised!(\n        {ReplicationConnection, %ReplicationConnection{tenant_id: tenant.external_id, monitored_pid: self()}},\n        restart: :transient\n      )\n\n      topic = \"db:job_scheduler\"\n      tenant_topic = Tenants.tenant_topic(tenant.external_id, topic, false)\n      subscribe(tenant_topic, topic)\n      payload = %{\"value\" => random_string()}\n\n      row =\n        message_fixture(tenant, %{\n          \"topic\" => topic,\n          \"private\" => true,\n          \"event\" => \"UPDATE\",\n          \"extension\" => \"broadcast\",\n          \"payload\" => payload\n        })\n\n      row_id = row.id\n\n      assert_receive {:socket_push, :text, data}, 2000\n      message = data |> IO.iodata_to_binary() |> Jason.decode!()\n\n      assert %{\n               \"event\" => \"broadcast\",\n               \"payload\" => %{\n                 \"event\" => \"UPDATE\",\n                 \"meta\" => %{\"id\" => ^row_id},\n                 \"payload\" => received_payload,\n                 \"type\" => \"broadcast\"\n               },\n               \"ref\" => nil,\n               \"topic\" => ^topic\n             } = message\n\n      assert received_payload == payload\n    end\n\n    test \"should not process unsupported relations\", %{tenant: tenant, db_conn: db_conn} do\n      # update\n      queries = [\n        \"DROP TABLE IF EXISTS public.test\",\n        \"\"\"\n        CREATE TABLE \"public\".\"test\" (\n        \"id\" int4 NOT NULL default nextval('test_id_seq'::regclass),\n        \"details\" text,\n        PRIMARY KEY (\"id\"));\n        \"\"\"\n      ]\n\n      Postgrex.transaction(db_conn, fn conn ->\n        Enum.each(queries, &Postgrex.query!(conn, &1, []))\n      end)\n\n      logs =\n        capture_log(fn ->\n          start_link_supervised!(\n            {ReplicationConnection, %ReplicationConnection{tenant_id: tenant.external_id, monitored_pid: self()}},\n            restart: :transient\n          )\n\n          assert_replication_started(db_conn, @replication_slot_name)\n          assert_publication_contains_only_messages(db_conn, \"supabase_realtime_messages_publication\")\n\n          # Add table to publication to test the error handling\n          Postgrex.query!(db_conn, \"ALTER PUBLICATION supabase_realtime_messages_publication ADD TABLE public.test\", [])\n          %{rows: [[_id]]} = Postgrex.query!(db_conn, \"insert into test (details) values ('test') returning id\", [])\n\n          topic = \"db:job_scheduler\"\n          tenant_topic = Tenants.tenant_topic(tenant.external_id, topic, false)\n          subscribe(tenant_topic, topic)\n          payload = %{\"value\" => random_string()}\n\n          row =\n            message_fixture(tenant, %{\n              \"topic\" => topic,\n              \"private\" => true,\n              \"event\" => \"UPDATE\",\n              \"extension\" => \"broadcast\",\n              \"payload\" => payload\n            })\n\n          row_id = row.id\n\n          assert_receive {:socket_push, :text, data}, 2000\n          message = data |> IO.iodata_to_binary() |> Jason.decode!()\n\n          assert %{\n                   \"event\" => \"broadcast\",\n                   \"payload\" => %{\n                     \"event\" => \"UPDATE\",\n                     \"meta\" => %{\"id\" => ^row_id},\n                     \"payload\" => received_payload,\n                     \"type\" => \"broadcast\"\n                   },\n                   \"ref\" => nil,\n                   \"topic\" => ^topic\n                 } = message\n\n          assert received_payload == payload\n        end)\n\n      assert logs =~ \"Unexpected relation on schema 'public' and table 'test'\"\n    end\n\n    test \"monitored pid stopping brings down ReplicationConnection \", %{tenant: tenant} do\n      monitored_pid =\n        spawn(fn ->\n          receive do\n            :stop -> :ok\n          end\n        end)\n\n      logs =\n        capture_log(fn ->\n          pid =\n            start_supervised!(\n              {ReplicationConnection,\n               %ReplicationConnection{tenant_id: tenant.external_id, monitored_pid: monitored_pid}},\n              restart: :transient\n            )\n\n          send(monitored_pid, :stop)\n\n          ref = Process.monitor(pid)\n          assert_receive {:DOWN, ^ref, :process, ^pid, _reason}, 100\n          refute Process.alive?(pid)\n        end)\n\n      assert logs =~ \"Disconnecting broadcast changes handler in the step\"\n    end\n\n    test \"message without event logs error\", %{tenant: tenant} do\n      logs =\n        capture_log(fn ->\n          start_supervised!(\n            {ReplicationConnection, %ReplicationConnection{tenant_id: tenant.external_id, monitored_pid: self()}},\n            restart: :transient\n          )\n\n          topic = random_string()\n          tenant_topic = Tenants.tenant_topic(tenant.external_id, topic, false)\n          assert :ok = Endpoint.subscribe(tenant_topic)\n\n          message_fixture(tenant, %{\n            \"topic\" => \"some_topic\",\n            \"private\" => true,\n            \"payload\" => %{\"value\" => \"something\"}\n          })\n\n          refute_receive %Phoenix.Socket.Broadcast{}, 500\n        end)\n\n      assert logs =~ \"UnableToBroadcastChanges\"\n    end\n\n    test \"message that exceeds payload size logs error\", %{tenant: tenant} do\n      logs =\n        capture_log(fn ->\n          start_supervised!(\n            {ReplicationConnection, %ReplicationConnection{tenant_id: tenant.external_id, monitored_pid: self()}},\n            restart: :transient\n          )\n\n          topic = random_string()\n          tenant_topic = Tenants.tenant_topic(tenant.external_id, topic, false)\n          assert :ok = Endpoint.subscribe(tenant_topic)\n\n          message_fixture(tenant, %{\n            \"event\" => random_string(),\n            \"topic\" => random_string(),\n            \"private\" => true,\n            \"payload\" => %{\"data\" => random_string(tenant.max_payload_size_in_kb * 1000 + 1)}\n          })\n\n          refute_receive %Phoenix.Socket.Broadcast{}, 500\n        end)\n\n      assert logs =~ \"UnableToBroadcastChanges: %{messages: [%{payload: [\\\"Payload size exceeds tenant limit\\\"]}]}\"\n    end\n\n    test \"payload without id\", %{tenant: tenant, db_conn: db_conn} do\n      start_link_supervised!(\n        {ReplicationConnection, %ReplicationConnection{tenant_id: tenant.external_id, monitored_pid: self()}},\n        restart: :transient\n      )\n\n      topic = random_string()\n      tenant_topic = Tenants.tenant_topic(tenant.external_id, topic, false)\n      subscribe(tenant_topic, topic)\n\n      value = \"something\"\n      event = \"INSERT\"\n\n      Postgrex.query!(\n        db_conn,\n        \"SELECT realtime.send (json_build_object ('value', $1 :: text)::jsonb, $2 :: text, $3 :: text, TRUE::bool);\",\n        [value, event, topic]\n      )\n\n      {:ok, [%{id: id}]} = Repo.all(db_conn, from(m in Message), Message)\n\n      assert_receive {:socket_push, :text, data}, 500\n      message = data |> IO.iodata_to_binary() |> Jason.decode!()\n\n      assert %{\n               \"event\" => \"broadcast\",\n               \"payload\" => %{\n                 \"event\" => \"INSERT\",\n                 \"meta\" => %{\"id\" => ^id},\n                 \"payload\" => payload,\n                 \"type\" => \"broadcast\"\n               },\n               \"ref\" => nil,\n               \"topic\" => ^topic\n             } = message\n\n      assert payload == %{\n               \"value\" => \"something\",\n               \"id\" => id\n             }\n    end\n\n    test \"payload including id\", %{tenant: tenant, db_conn: db_conn} do\n      start_link_supervised!(\n        {ReplicationConnection, %ReplicationConnection{tenant_id: tenant.external_id, monitored_pid: self()}},\n        restart: :transient\n      )\n\n      topic = random_string()\n      tenant_topic = Tenants.tenant_topic(tenant.external_id, topic, false)\n      subscribe(tenant_topic, topic)\n\n      id = \"123456\"\n      value = \"something\"\n      event = \"INSERT\"\n\n      Postgrex.query!(\n        db_conn,\n        \"SELECT realtime.send (json_build_object ('value', $1 :: text, 'id', $2 :: text)::jsonb, $3 :: text, $4 :: text, TRUE::bool);\",\n        [value, id, event, topic]\n      )\n\n      {:ok, [%{id: message_id}]} = Repo.all(db_conn, from(m in Message), Message)\n\n      assert_receive {:socket_push, :text, data}, 500\n      message = data |> IO.iodata_to_binary() |> Jason.decode!()\n\n      assert %{\n               \"event\" => \"broadcast\",\n               \"payload\" => %{\n                 \"meta\" => %{\"id\" => ^message_id},\n                 \"event\" => \"INSERT\",\n                 \"payload\" => %{\"value\" => \"something\", \"id\" => ^id},\n                 \"type\" => \"broadcast\"\n               },\n               \"ref\" => nil,\n               \"topic\" => ^topic\n             } = message\n    end\n\n    test \"fails on existing replication slot\", %{tenant: tenant} do\n      {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n      name = @replication_slot_name\n\n      Postgrex.query!(db_conn, \"SELECT pg_create_logical_replication_slot($1, 'test_decoding')\", [name])\n\n      assert {:error, {:shutdown, :replication_slot_in_use}} =\n               ReplicationConnection.start(tenant, self())\n\n      Postgrex.query!(db_conn, \"SELECT pg_drop_replication_slot($1)\", [name])\n    end\n\n    test \"times out when init takes too long\", %{tenant: tenant} do\n      expect(ReplicationConnection, :init, 1, fn arg ->\n        :timer.sleep(1000)\n        call_original(ReplicationConnection, :init, [arg])\n      end)\n\n      assert {:error, :replication_connection_timeout} = ReplicationConnection.start(tenant, self(), 100)\n    end\n\n    test \"handle standby connections exceeds max_wal_senders\", %{tenant: tenant} do\n      opts = Database.from_tenant(tenant, \"realtime_test\", :stop) |> Database.opts()\n      parent = self()\n\n      # This creates a loop of errors that occupies all WAL senders and lets us test the error handling\n      pids =\n        for i <- 0..5 do\n          replication_slot_opts =\n            %PostgresReplication{\n              connection_opts: opts,\n              table: :all,\n              output_plugin: \"pgoutput\",\n              output_plugin_options: [proto_version: \"1\", publication_names: \"test_#{i}_publication\"],\n              handler_module: Replication.TestHandler,\n              publication_name: \"test_#{i}_publication\",\n              replication_slot_name: \"test_#{i}_slot\"\n            }\n\n          spawn(fn ->\n            {:ok, pid} = PostgresReplication.start_link(replication_slot_opts)\n            send(parent, :ready)\n\n            receive do\n              :stop -> Process.exit(pid, :kill)\n            end\n          end)\n        end\n\n      on_exit(fn ->\n        Enum.each(pids, &send(&1, :stop))\n        Process.sleep(2000)\n      end)\n\n      assert_receive :ready, 5000\n      assert_receive :ready, 5000\n      assert_receive :ready, 5000\n      assert_receive :ready, 5000\n\n      assert {:error, :max_wal_senders_reached} = ReplicationConnection.start(tenant, self())\n    end\n\n    test \"handles WAL pressure gracefully\", %{tenant: tenant} do\n      {:ok, replication_pid} = ReplicationConnection.start(tenant, self())\n\n      {:ok, conn} = Database.connect(tenant, \"realtime_test\", :stop)\n      on_exit(fn -> Process.exit(conn, :normal) end)\n\n      large_payload = String.duplicate(\"x\", 10 * 1024 * 1024)\n\n      for i <- 1..5 do\n        message_fixture_with_conn(tenant, conn, %{\n          \"topic\" => \"stress_#{i}\",\n          \"private\" => true,\n          \"event\" => \"INSERT\",\n          \"payload\" => %{\"data\" => large_payload}\n        })\n      end\n\n      assert Process.alive?(replication_pid)\n    end\n  end\n\n  describe \"publication validation steps\" do\n    test \"if proper tables are included, starts replication\", %{tenant: tenant, db_conn: db_conn} do\n      publication_name = \"supabase_realtime_messages_publication\"\n\n      Postgrex.query!(db_conn, \"DROP PUBLICATION IF EXISTS #{publication_name}\", [])\n      Postgrex.query!(db_conn, \"CREATE PUBLICATION #{publication_name} FOR TABLE realtime.messages\", [])\n\n      logs =\n        capture_log(fn ->\n          {:ok, pid} = ReplicationConnection.start(tenant, self())\n\n          assert_replication_started(db_conn, @replication_slot_name)\n          assert Process.alive?(pid)\n          assert_publication_contains_only_messages(db_conn, publication_name)\n\n          Process.exit(pid, :shutdown)\n        end)\n\n      refute logs =~ \"Recreating\"\n    end\n\n    test \"if includes unexpected tables, recreates publication\", %{tenant: tenant, db_conn: db_conn} do\n      publication_name = \"supabase_realtime_messages_publication\"\n\n      Postgrex.query!(db_conn, \"DROP PUBLICATION IF EXISTS #{publication_name}\", [])\n      Postgrex.query!(db_conn, \"CREATE TABLE IF NOT EXISTS public.wrong_table (id int)\", [])\n      Postgrex.query!(db_conn, \"CREATE PUBLICATION #{publication_name} FOR TABLE public.wrong_table\", [])\n\n      logs =\n        capture_log(fn ->\n          {:ok, pid} = ReplicationConnection.start(tenant, self())\n\n          assert_replication_started(db_conn, @replication_slot_name)\n          assert Process.alive?(pid)\n          assert_publication_contains_only_messages(db_conn, publication_name)\n\n          Process.exit(pid, :shutdown)\n        end)\n\n      assert logs =~ \"Recreating\"\n    end\n\n    test \"recreates publication if it has no tables\", %{tenant: tenant, db_conn: db_conn} do\n      publication_name = \"supabase_realtime_messages_publication\"\n\n      Postgrex.query!(db_conn, \"DROP PUBLICATION IF EXISTS #{publication_name}\", [])\n      Postgrex.query!(db_conn, \"CREATE PUBLICATION #{publication_name}\", [])\n\n      logs =\n        capture_log(fn ->\n          {:ok, pid} = ReplicationConnection.start(tenant, self())\n\n          assert_replication_started(db_conn, @replication_slot_name)\n          assert Process.alive?(pid)\n          assert_publication_contains_only_messages(db_conn, publication_name)\n\n          Process.exit(pid, :shutdown)\n        end)\n\n      assert logs =~ \"Recreating\"\n    end\n\n    test \"recreates publication if it has expected tables and unexpected tables under same publication\", %{\n      tenant: tenant,\n      db_conn: db_conn\n    } do\n      publication_name = \"supabase_realtime_messages_publication\"\n\n      Postgrex.query!(db_conn, \"DROP PUBLICATION IF EXISTS #{publication_name}\", [])\n      Postgrex.query!(db_conn, \"CREATE TABLE IF NOT EXISTS public.extra_table (id int)\", [])\n\n      Postgrex.query!(\n        db_conn,\n        \"CREATE PUBLICATION #{publication_name} FOR TABLE realtime.messages, public.extra_table\",\n        []\n      )\n\n      logs =\n        capture_log(fn ->\n          {:ok, pid} = ReplicationConnection.start(tenant, self())\n\n          assert_replication_started(db_conn, @replication_slot_name)\n          assert Process.alive?(pid)\n          assert_publication_contains_only_messages(db_conn, publication_name)\n\n          Process.exit(pid, :shutdown)\n        end)\n\n      assert logs =~ \"Recreating\"\n    end\n  end\n\n  describe \"handle_result/2 for step :start_replication_slot\" do\n    test \"returns disconnect when error has postgres map with message\" do\n      error = %Postgrex.Error{\n        postgres: %{\n          code: :undefined_table,\n          message: \"relation \\\"realtime.messages\\\" does not exist\"\n        }\n      }\n\n      state = %ReplicationConnection{step: :start_replication_slot}\n\n      assert {:disconnect, \"Error starting replication: relation \\\"realtime.messages\\\" does not exist\"} =\n               ReplicationConnection.handle_result(error, state)\n    end\n\n    test \"returns disconnect when error has top-level message and no postgres map\" do\n      error = %Postgrex.Error{message: \"connection closed\"}\n      state = %ReplicationConnection{step: :start_replication_slot}\n\n      assert {:disconnect, \"Error starting replication: connection closed\"} =\n               ReplicationConnection.handle_result(error, state)\n    end\n\n    test \"returns disconnect when results list contains a Postgrex.Error\" do\n      error = %Postgrex.Error{message: \"something went wrong\"}\n      state = %ReplicationConnection{step: :start_replication_slot}\n\n      assert {:disconnect, \"Error starting replication: something went wrong\"} =\n               ReplicationConnection.handle_result([error], state)\n    end\n  end\n\n  describe \"whereis/1\" do\n    @tag skip:\n           \"We are using a GenServer wrapper so the pid returned is not the same as the ReplicationConnection for now\"\n    test \"returns pid if exists\", %{tenant: tenant} do\n      {:ok, pid} = ReplicationConnection.start(tenant, self())\n      assert ReplicationConnection.whereis(tenant.external_id) == pid\n      Process.exit(pid, :shutdown)\n    end\n\n    test \"returns nil if not exists\" do\n      assert ReplicationConnection.whereis(random_string()) == nil\n    end\n  end\n\n  def handle_telemetry(event, measures, metadata, pid: pid), do: send(pid, {event, measures, metadata})\n\n  describe \"telemetry events\" do\n    setup do\n      :telemetry.detach(__MODULE__)\n\n      :telemetry.attach(\n        __MODULE__,\n        [:realtime, :tenants, :broadcast_from_database],\n        &__MODULE__.handle_telemetry/4,\n        pid: self()\n      )\n    end\n\n    test \"receives telemetry event\", %{tenant: %{external_id: external_id} = tenant} do\n      start_link_supervised!(\n        {ReplicationConnection, %ReplicationConnection{tenant_id: external_id, monitored_pid: self()}},\n        restart: :transient\n      )\n\n      topic = random_string()\n      tenant_topic = Tenants.tenant_topic(external_id, topic, false)\n      subscribe(tenant_topic, topic)\n\n      message_fixture(tenant, %{\n        \"topic\" => topic,\n        \"private\" => true,\n        \"event\" => \"INSERT\",\n        \"payload\" => %{\"value\" => random_string()}\n      })\n\n      assert_receive {:socket_push, :text, data}, 500\n      message = data |> IO.iodata_to_binary() |> Jason.decode!()\n\n      assert %{\"event\" => \"broadcast\", \"payload\" => _, \"ref\" => nil, \"topic\" => ^topic} = message\n\n      assert_receive {[:realtime, :tenants, :broadcast_from_database],\n                      %{latency_committed_at: latency_committed_at, latency_inserted_at: latency_inserted_at},\n                      %{tenant: ^external_id}}\n\n      assert latency_committed_at\n      assert latency_inserted_at\n    end\n  end\n\n  defp subscribe(tenant_topic, topic) do\n    fastlane =\n      RealtimeWeb.RealtimeChannel.MessageDispatcher.fastlane_metadata(\n        self(),\n        Phoenix.Socket.V1.JSONSerializer,\n        topic,\n        :warning,\n        \"tenant_id\"\n      )\n\n    Endpoint.subscribe(tenant_topic, metadata: fastlane)\n  end\n\n  defp assert_process_down(pid, timeout \\\\ 100) do\n    ref = Process.monitor(pid)\n    assert_receive {:DOWN, ^ref, :process, ^pid, _reason}, timeout\n  end\n\n  defp message_fixture_with_conn(_tenant, conn, override) do\n    create_attrs = %{\n      \"topic\" => random_string(),\n      \"extension\" => \"broadcast\"\n    }\n\n    override = override |> Enum.map(fn {k, v} -> {\"#{k}\", v} end) |> Map.new()\n\n    {:ok, message} =\n      create_attrs\n      |> Map.merge(override)\n      |> TenantConnection.create_message(conn)\n\n    message\n  end\n\n  defp assert_publication_contains_only_messages(db_conn, publication_name) do\n    %{rows: rows} =\n      Postgrex.query!(\n        db_conn,\n        \"SELECT schemaname, tablename FROM pg_publication_tables WHERE pubname = $1\",\n        [publication_name]\n      )\n\n    valid_tables =\n      Enum.all?(rows, fn [schema, table] ->\n        schema == \"realtime\" and (table == \"messages\" or String.starts_with?(table, \"messages_\"))\n      end)\n\n    assert valid_tables, \"Expected only realtime.messages or its partitions, got: #{inspect(rows)}\"\n  end\n\n  defp assert_replication_started(db_conn, slot_name, retries \\\\ 10, interval_ms \\\\ 10) do\n    case check_replication_status(db_conn, slot_name, retries, interval_ms) do\n      :ok -> :ok\n      :error -> flunk(\"Replication slot #{slot_name} did not become active\")\n    end\n  end\n\n  defp check_replication_status(_db_conn, _slot_name, 0, _interval_ms), do: :error\n\n  defp check_replication_status(db_conn, slot_name, retries_remaining, interval_ms) do\n    %{rows: rows} =\n      Postgrex.query!(db_conn, \"SELECT active FROM pg_replication_slots WHERE slot_name = $1\", [slot_name])\n\n    case rows do\n      [[true]] ->\n        :ok\n\n      _ ->\n        Process.sleep(interval_ms)\n        check_replication_status(db_conn, slot_name, retries_remaining - 1, interval_ms)\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants/repo_test.exs",
    "content": "defmodule Realtime.Tenants.RepoTest do\n  use Realtime.DataCase, async: true\n\n  import Ecto.Query\n\n  alias Realtime.Api.Message\n  alias Realtime.Tenants.Repo\n  alias Realtime.Database\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n    %{tenant: tenant, db_conn: db_conn}\n  end\n\n  describe \"all/3\" do\n    test \"fetches multiple entries and loads a given struct\", %{db_conn: db_conn, tenant: tenant} do\n      message_1 = message_fixture(tenant)\n      message_2 = message_fixture(tenant)\n\n      assert {:ok, res} = Repo.all(db_conn, Message, Message)\n      assert Enum.sort([message_1, message_2]) == Enum.sort(res)\n      assert Enum.all?(res, &(Ecto.get_meta(&1, :state) == :loaded))\n    end\n\n    test \"handles exceptions\", %{db_conn: db_conn} do\n      Process.unlink(db_conn)\n      Process.exit(db_conn, :kill)\n\n      assert {:error, :postgrex_exception} = Repo.all(db_conn, from(c in Message), Message)\n    end\n  end\n\n  describe \"one/3\" do\n    test \"fetches one entry and loads a given struct\", %{db_conn: db_conn, tenant: tenant} do\n      message_1 = message_fixture(tenant)\n      _message_2 = message_fixture(tenant)\n      query = from(c in Message, where: c.id == ^message_1.id)\n      assert {:ok, ^message_1} = Repo.one(db_conn, query, Message)\n      assert Ecto.get_meta(message_1, :state) == :loaded\n    end\n\n    test \"raises exception on multiple results\", %{db_conn: db_conn, tenant: tenant} do\n      _message_1 = message_fixture(tenant)\n      _message_2 = message_fixture(tenant)\n\n      assert_raise RuntimeError, \"expected at most one result but got 2 in result\", fn ->\n        Repo.one(db_conn, Message, Message)\n      end\n    end\n\n    test \"if not found, returns not found error\", %{db_conn: db_conn} do\n      query = from(c in Message, where: c.topic == \"potato\")\n      assert {:error, :not_found} = Repo.one(db_conn, query, Message)\n    end\n\n    test \"handles exceptions\", %{db_conn: db_conn} do\n      Process.unlink(db_conn)\n      Process.exit(db_conn, :kill)\n      query = from(c in Message, where: c.topic == \"potato\")\n      assert {:error, :postgrex_exception} = Repo.one(db_conn, query, Message)\n    end\n  end\n\n  describe \"insert/3\" do\n    test \"inserts a new entry with a given changeset and returns struct\", %{db_conn: db_conn} do\n      changeset = Message.changeset(%Message{}, %{topic: \"foo\", extension: :presence})\n\n      assert {:ok, %Message{}} = Repo.insert(db_conn, changeset, Message)\n    end\n\n    test \"returns changeset if changeset is invalid\", %{db_conn: db_conn} do\n      changeset = Message.changeset(%Message{}, %{})\n      res = Repo.insert(db_conn, changeset, Message)\n      assert {:error, %Ecto.Changeset{valid?: false}} = res\n    end\n\n    test \"returns a Changeset on Changeset error\", %{db_conn: db_conn} do\n      changeset = Message.changeset(%Message{}, %{})\n\n      assert {:error,\n              %Ecto.Changeset{\n                valid?: false,\n                errors: [\n                  topic: {\"can't be blank\", [validation: :required]},\n                  extension: {\"can't be blank\", [validation: :required]}\n                ]\n              }} =\n               Repo.insert(db_conn, changeset, Message)\n    end\n\n    test \"handles exceptions\", %{db_conn: db_conn} do\n      Process.unlink(db_conn)\n      Process.exit(db_conn, :kill)\n\n      changeset = Message.changeset(%Message{}, %{topic: \"foo\", extension: :presence})\n\n      assert {:error, :postgrex_exception} = Repo.insert(db_conn, changeset, Message)\n    end\n  end\n\n  describe \"insert_all_entries/3\" do\n    test \"inserts a new entries with a given changeset and returns struct\", %{db_conn: db_conn} do\n      changeset = [\n        Message.changeset(%Message{}, %{topic: random_string(), extension: :presence}),\n        Message.changeset(%Message{}, %{topic: random_string(), extension: :broadcast}),\n        Message.changeset(%Message{}, %{topic: random_string(), extension: :presence}),\n        Message.changeset(%Message{}, %{topic: random_string(), extension: :broadcast})\n      ]\n\n      assert {:ok, results} = Repo.insert_all_entries(db_conn, changeset, Message)\n      assert Enum.all?(results, fn result -> is_map(result) end)\n    end\n\n    test \"returns changeset if changeset is invalid\", %{db_conn: db_conn} do\n      changeset = [Message.changeset(%Message{}, %{})]\n      res = Repo.insert_all_entries(db_conn, changeset, Message)\n      assert {:error, [%Ecto.Changeset{valid?: false}]} = res\n    end\n\n    test \"returns a Changeset on Changeset error\", %{db_conn: db_conn} do\n      changeset = [Message.changeset(%Message{}, %{})]\n\n      assert {:error,\n              [\n                %Ecto.Changeset{\n                  valid?: false,\n                  errors: [\n                    topic: {\"can't be blank\", [validation: :required]},\n                    extension: {\"can't be blank\", [validation: :required]}\n                  ]\n                }\n              ]} =\n               Repo.insert_all_entries(db_conn, changeset, Message)\n    end\n\n    test \"handles exceptions\", %{db_conn: db_conn} do\n      Process.unlink(db_conn)\n      Process.exit(db_conn, :kill)\n\n      changeset = [Message.changeset(%Message{}, %{topic: \"foo\", extension: :presence})]\n\n      assert {:error, :postgrex_exception} = Repo.insert_all_entries(db_conn, changeset, Message)\n    end\n  end\n\n  describe \"del/3\" do\n    test \"deletes all from query entry\", %{db_conn: db_conn, tenant: tenant} do\n      Stream.repeatedly(fn -> message_fixture(tenant) end) |> Enum.take(3)\n      assert {:ok, 3} = Repo.del(db_conn, Message)\n    end\n\n    test \"raises error on bad queries\", %{db_conn: db_conn} do\n      # wrong id type\n      query = from(c in Message, where: c.id == \"potato\")\n\n      assert_raise Ecto.QueryError, fn ->\n        Repo.del(db_conn, query)\n      end\n    end\n\n    test \"handles exceptions\", %{db_conn: db_conn} do\n      Process.unlink(db_conn)\n      Process.exit(db_conn, :kill)\n\n      assert {:error, :postgrex_exception} = Repo.del(db_conn, Message)\n    end\n  end\n\n  describe \"update/3\" do\n    test \"updates a new entry with a given changeset and returns struct\", %{\n      db_conn: db_conn,\n      tenant: tenant\n    } do\n      message = message_fixture(tenant)\n      changeset = Message.changeset(message, %{topic: \"foo\"})\n      assert {:ok, %Message{}} = Repo.update(db_conn, changeset, Message)\n    end\n\n    test \"returns changeset if changeset is invalid\", %{db_conn: db_conn, tenant: tenant} do\n      message = message_fixture(tenant)\n      changeset = Message.changeset(message, %{topic: 0})\n      res = Repo.update(db_conn, changeset, Message)\n      assert {:error, %Ecto.Changeset{valid?: false}} = res\n    end\n\n    test \"returns an Changeset on Changeset error\", %{db_conn: db_conn, tenant: tenant} do\n      message_to_update = message_fixture(tenant)\n\n      changeset = Message.changeset(message_to_update, %{topic: nil})\n\n      assert {:error,\n              %Ecto.Changeset{\n                valid?: false,\n                errors: [topic: {\"can't be blank\", [validation: :required]}]\n              }} = Repo.update(db_conn, changeset, Message)\n    end\n\n    test \"handles exceptions\", %{tenant: tenant, db_conn: db_conn} do\n      changeset = Message.changeset(message_fixture(tenant), %{topic: \"foo\"})\n\n      Process.unlink(db_conn)\n      Process.exit(db_conn, :kill)\n\n      assert {:error, :postgrex_exception} = Repo.update(db_conn, changeset, Message)\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/tenants_test.exs",
    "content": "defmodule Realtime.TenantsTest do\n  # async: false due to cache usage\n  alias Realtime.Tenants.Migrations\n  use Realtime.DataCase, async: false\n\n  alias Realtime.GenCounter\n  alias Realtime.Tenants\n  doctest Realtime.Tenants\n\n  describe \"tenants\" do\n    test \"get_tenant_limits/1\" do\n      tenant = tenant_fixture()\n      keys = Tenants.limiter_keys(tenant)\n\n      for key <- keys do\n        GenCounter.add(key, 9)\n      end\n\n      limits = Tenants.get_tenant_limits(tenant, keys)\n\n      [all] = Enum.filter(limits, fn e -> e.limiter == Tenants.requests_per_second_key(tenant) end)\n      assert all.counter == 9\n\n      [user_channels] = Enum.filter(limits, fn e -> e.limiter == Tenants.channels_per_client_key(tenant) end)\n      assert user_channels.counter == 9\n\n      [channel_joins] = Enum.filter(limits, fn e -> e.limiter == Tenants.joins_per_second_key(tenant) end)\n      assert channel_joins.counter == 9\n\n      [tenant_events] = Enum.filter(limits, fn e -> e.limiter == Tenants.events_per_second_key(tenant) end)\n      assert tenant_events.counter == 9\n    end\n  end\n\n  describe \"suspend_tenant_by_external_id/1\" do\n    setup do\n      tenant = tenant_fixture()\n      :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, \"realtime:operations:\" <> tenant.external_id)\n      %{tenant: tenant}\n    end\n\n    test \"sets suspend flag to true and publishes message\", %{tenant: %{external_id: external_id}} do\n      {:ok, tenant} = Tenants.suspend_tenant_by_external_id(external_id)\n      assert tenant.suspend == true\n      assert_receive :suspend_tenant, 500\n    end\n\n    test \"does not publish message if if not targetted tenant\", %{tenant: tenant} do\n      Tenants.suspend_tenant_by_external_id(tenant_fixture().external_id)\n      tenant = Repo.reload!(tenant)\n      assert tenant.suspend == false\n      refute_receive :suspend_tenant, 500\n    end\n  end\n\n  describe \"unsuspend_tenant_by_external_id/1\" do\n    setup do\n      tenant = tenant_fixture(%{suspend: true})\n      :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, \"realtime:operations:\" <> tenant.external_id)\n      %{tenant: tenant}\n    end\n\n    test \"sets suspend flag to false and publishes message\", %{tenant: tenant} do\n      {:ok, tenant} = Tenants.unsuspend_tenant_by_external_id(tenant.external_id)\n      assert tenant.suspend == false\n      assert_receive :unsuspend_tenant, 500\n    end\n\n    test \"does not publish message if not targetted tenant\", %{tenant: tenant} do\n      Tenants.unsuspend_tenant_by_external_id(tenant_fixture().external_id)\n      tenant = Repo.reload!(tenant)\n      assert tenant.suspend == true\n      refute_receive :unsuspend_tenant, 500\n    end\n  end\n\n  describe \"run_migrations?/1\" do\n    test \"returns true if migrations_ran is lower than existing migrations\" do\n      tenant = tenant_fixture(%{migrations_ran: 0})\n      assert Tenants.run_migrations?(tenant)\n\n      tenant = tenant_fixture(%{migrations_ran: Enum.count(Migrations.migrations()) - 1})\n      assert Tenants.run_migrations?(tenant)\n    end\n\n    test \"returns false if migrations_ran is count of all migrations\" do\n      tenant = tenant_fixture(%{migrations_ran: Enum.count(Migrations.migrations())})\n      refute Tenants.run_migrations?(tenant)\n    end\n  end\n\n  describe \"broadcast_operation_event/2\" do\n    setup do\n      tenant = tenant_fixture()\n      :ok = Phoenix.PubSub.subscribe(Realtime.PubSub, \"realtime:operations:\" <> tenant.external_id)\n      %{tenant: tenant}\n    end\n\n    test \"broadcasts events to the targetted tenant\", %{tenant: tenant} do\n      events = [\n        :suspend_tenant,\n        :unsuspend_tenant,\n        :disconnect\n      ]\n\n      for event <- events do\n        Tenants.broadcast_operation_event(event, tenant.external_id)\n        assert_receive ^event\n      end\n    end\n  end\n\n  describe \"region/1\" do\n    test \"returns the region of the tenant\" do\n      attrs = %{\n        \"external_id\" => random_string(),\n        \"name\" => \"tenant\",\n        \"extensions\" => [\n          %{\n            \"type\" => \"postgres_cdc_rls\",\n            \"settings\" => %{\n              \"db_host\" => \"127.0.0.1\",\n              \"db_name\" => \"postgres\",\n              \"db_user\" => \"supabase_admin\",\n              \"db_password\" => \"postgres\",\n              \"db_port\" => \"#{port()}\",\n              \"poll_interval\" => 100,\n              \"poll_max_changes\" => 100,\n              \"poll_max_record_bytes\" => 1_048_576,\n              \"region\" => \"us-east-1\",\n              \"publication\" => \"supabase_realtime_test\",\n              \"ssl_enforced\" => false\n            }\n          }\n        ],\n        \"postgres_cdc_default\" => \"postgres_cdc_rls\",\n        \"jwt_secret\" => \"new secret\",\n        \"jwt_jwks\" => nil\n      }\n\n      {:ok, tenant} = Realtime.Api.create_tenant(attrs)\n      assert Tenants.region(tenant) == \"us-east-1\"\n    end\n\n    test \"returns nil if no extension is set\" do\n      attrs = %{\n        \"external_id\" => random_string(),\n        \"name\" => \"tenant\",\n        \"extensions\" => [],\n        \"postgres_cdc_default\" => \"postgres_cdc_rls\",\n        \"jwt_secret\" => \"new secret\",\n        \"jwt_jwks\" => nil\n      }\n\n      {:ok, tenant} = Realtime.Api.create_tenant(attrs)\n      assert Tenants.region(tenant) == nil\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime/users_counter_test.exs",
    "content": "defmodule Realtime.UsersCounterTest do\n  use Realtime.DataCase, async: false\n  alias Realtime.UsersCounter\n  alias Realtime.Rpc\n\n  setup_all do\n    tenant_id = random_string()\n    count = generate_load(tenant_id)\n\n    %{tenant_id: tenant_id, count: count, nodes: Node.list()}\n  end\n\n  describe \"already_counted?/2\" do\n    test \"returns true if pid already counted for tenant\", %{tenant_id: tenant_id} do\n      pid = self()\n      assert UsersCounter.add(pid, tenant_id) == :ok\n      assert UsersCounter.already_counted?(pid, tenant_id) == true\n    end\n\n    test \"returns false if pid not counted for tenant\" do\n      assert UsersCounter.already_counted?(self(), random_string()) == false\n    end\n  end\n\n  describe \"add/1\" do\n    test \"starts counter for tenant\" do\n      assert UsersCounter.add(self(), random_string()) == :ok\n    end\n  end\n\n  describe \"local_tenants/0\" do\n    test \"returns list of tenant ids with local connections\" do\n      tenant_id = random_string()\n      assert UsersCounter.add(self(), tenant_id) == :ok\n\n      tenants = UsersCounter.local_tenants()\n      assert is_list(tenants)\n      assert tenant_id in tenants\n    end\n  end\n\n  @aux_mod (quote do\n              defmodule Aux do\n                def ping() do\n                  spawn(fn -> Process.sleep(:infinity) end)\n                end\n\n                def join(pid, group) do\n                  UsersCounter.add(pid, group)\n                end\n              end\n            end)\n\n  Code.eval_quoted(@aux_mod)\n\n  describe \"tenant_counts/0\" do\n    test \"map of tenant and number of users\", %{tenant_id: tenant_id, count: expected} do\n      assert UsersCounter.add(self(), tenant_id) == :ok\n      Process.sleep(1000)\n      counts = UsersCounter.tenant_counts()\n\n      assert counts[tenant_id] == expected + 1\n      assert map_size(counts) >= 61\n\n      counts = Beacon.local_member_counts(:users)\n\n      assert counts[tenant_id] == 1\n      assert map_size(counts) >= 1\n\n      counts = Beacon.member_counts(:users)\n\n      assert counts[tenant_id] == expected + 1\n      assert map_size(counts) >= 61\n    end\n  end\n\n  describe \"local_tenant_counts/0\" do\n    test \"map of tenant and number of users for local node only\", %{tenant_id: tenant_id} do\n      assert UsersCounter.add(self(), tenant_id) == :ok\n\n      my_counts = UsersCounter.local_tenant_counts()\n      # Only one connection from this test process on this node\n      assert my_counts == %{tenant_id => 1}\n    end\n  end\n\n  describe \"tenant_users/1\" do\n    test \"returns count of connected clients for tenant on cluster node\", %{tenant_id: tenant_id, count: expected} do\n      Process.sleep(1000)\n      assert UsersCounter.tenant_users(tenant_id) == expected\n    end\n  end\n\n  defp generate_load(tenant_id) do\n    processes = 2\n\n    gen_rpc_port = Application.fetch_env!(:gen_rpc, :tcp_server_port)\n\n    nodes = %{\n      node() => gen_rpc_port,\n      :\"us_node@127.0.0.1\" => 16980,\n      :\"ap2_nodeX@127.0.0.1\" => 16981,\n      :\"ap2_nodeY@127.0.0.1\" => 16982\n    }\n\n    regions = %{\n      :\"us_node@127.0.0.1\" => \"us-east-1\",\n      :\"ap2_nodeX@127.0.0.1\" => \"ap-southeast-2\",\n      :\"ap2_nodeY@127.0.0.1\" => \"ap-southeast-2\"\n    }\n\n    on_exit(fn -> Application.put_env(:gen_rpc, :client_config_per_node, {:internal, %{}}) end)\n    Application.put_env(:gen_rpc, :client_config_per_node, {:internal, nodes})\n\n    nodes\n    |> Enum.filter(fn {node, _port} -> node != Node.self() end)\n    |> Enum.with_index(1)\n    |> Enum.each(fn {{node, gen_rpc_port}, i} ->\n      # Avoid port collision\n      extra_config = [\n        {:gen_rpc, :tcp_server_port, gen_rpc_port},\n        {:gen_rpc, :client_config_per_node, {:internal, nodes}},\n        {:realtime, :users_scope_broadcast_interval_in_ms, 100},\n        {:realtime, :region, regions[node]}\n      ]\n\n      node_name =\n        node\n        |> to_string()\n        |> String.split(\"@\")\n        |> hd()\n        |> String.to_atom()\n\n      {:ok, node} = Clustered.start(@aux_mod, name: node_name, extra_config: extra_config, phoenix_port: 4012 + i)\n\n      for _ <- 1..processes do\n        pid = Rpc.call(node, Aux, :ping, [])\n\n        for _ <- 1..10 do\n          # replicate same pid added multiple times concurrently\n          Task.start(fn ->\n            Rpc.call(node, Aux, :join, [pid, tenant_id])\n          end)\n\n          # noisy neighbors to test handling of bigger loads on concurrent calls\n          Task.start(fn ->\n            Rpc.call(node, Aux, :join, [pid, random_string()])\n          end)\n        end\n      end\n    end)\n\n    3 * processes\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/channels/auth/channels_authorization_test.exs",
    "content": "defmodule RealtimeWeb.ChannelsAuthorizationTest do\n  use ExUnit.Case, async: true\n\n  use Mimic\n\n  import Generators\n\n  alias RealtimeWeb.ChannelsAuthorization\n  alias RealtimeWeb.JwtVerification\n\n  @secret \"\"\n  describe \"authorize_conn/3\" do\n    test \"when token is authorized\" do\n      input_token = \"\\n token %20 1 %20 2 %20 3   \"\n      expected_token = \"token123\"\n\n      expect(JwtVerification, :verify, 1, fn token, @secret, _jwks ->\n        assert token == expected_token\n        {:ok, %{}}\n      end)\n\n      assert {:ok, %{}} = ChannelsAuthorization.authorize(input_token, @secret, nil)\n    end\n\n    test \"when token is unauthorized\" do\n      expect(JwtVerification, :verify, 1, fn _token, @secret, _jwks -> :error end)\n      assert :error = ChannelsAuthorization.authorize(\"bad_token\", @secret, nil)\n    end\n\n    test \"when token is not a jwt token\" do\n      assert {:error, :token_malformed} = ChannelsAuthorization.authorize(\"bad_token\", @secret, nil)\n    end\n\n    test \"when token is not a string\" do\n      assert {:error, :invalid_token} = ChannelsAuthorization.authorize([], @secret, nil)\n    end\n\n    test \"authorize_conn/3 fails when has missing headers\" do\n      jwt = generate_jwt_token(@secret, %{})\n\n      assert {:error, :missing_claims} =\n               ChannelsAuthorization.authorize_conn(jwt, @secret, nil)\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/channels/auth/jwt_verification_test.exs",
    "content": "defmodule RealtimeWeb.JwtVerificationTest do\n  # async: false due to mock usage and changing application env\n  use Realtime.DataCase, async: false\n\n  alias RealtimeWeb.JwtVerification\n  alias RealtimeWeb.Joken.CurrentTime.Mock\n\n  @jwt_secret \"secret\"\n  @alg \"HS256\"\n\n  setup_all do\n    Application.put_env(:realtime, :jwt_secret, @jwt_secret)\n    Application.put_env(:realtime, :jwt_claim_validators, %{})\n    :ok\n  end\n\n  setup do\n    start_supervised(Mock)\n    on_exit(fn -> Application.put_env(:realtime, :jwt_claim_validators, %{}) end)\n    :ok\n  end\n\n  describe \"verify/3\" do\n    test \"when token is not a string\" do\n      assert {:error, :not_a_string} = JwtVerification.verify([], @jwt_secret, nil)\n    end\n\n    test \"when token is a badly formatted string fails\" do\n      invalid_token = \"bad_token\"\n\n      assert {:error, :token_malformed} =\n               JwtVerification.verify(invalid_token, @jwt_secret, nil)\n    end\n\n    test \"when token has invalid format fails\" do\n      invalid_token = Base.encode64(\"{}\")\n\n      assert {:error, :token_malformed} =\n               JwtVerification.verify(invalid_token, @jwt_secret, nil)\n    end\n\n    test \"when token header is not a map\" do\n      invalid_token =\n        Base.encode64(\"[]\") <> \".\" <> Base.encode64(\"{}\") <> \".\" <> Base.encode64(\"<<\\\"sig\\\">>\")\n\n      assert {:error, _reason} = JwtVerification.verify(invalid_token, @jwt_secret, nil)\n    end\n\n    test \"when token claims is not a map\" do\n      invalid_token =\n        Base.encode64(\"{}\") <> \".\" <> Base.encode64(\"[]\") <> \".\" <> Base.encode64(\"<<\\\"sig\\\">>\")\n\n      assert {:error, _reason} = JwtVerification.verify(invalid_token, @jwt_secret, nil)\n    end\n\n    test \"when token header does not have kid or alg\" do\n      invalid_token =\n        Base.encode64(~s[{\"kid\": \"mykid123\"}]) <>\n          \".\" <> Base.encode64(\"{}\") <> \".\" <> Base.encode64(~s[<<\"sig\">>])\n\n      assert {:error, _reason} = JwtVerification.verify(invalid_token, @jwt_secret, nil)\n\n      invalid_token =\n        Base.encode64(~s[{\"alg\": \"HS256\"}]) <>\n          \".\" <> Base.encode64(\"{}\") <> \".\" <> Base.encode64(~s[<<\"sig\">>])\n\n      assert {:error, _reason} = JwtVerification.verify(invalid_token, @jwt_secret, nil)\n    end\n\n    test \"when token header alg is not allowed\" do\n      invalid_token =\n        Base.encode64(~s[{\"alg\": \"ZZ999\"}]) <>\n          \".\" <> Base.encode64(\"{}\") <> \".\" <> Base.encode64(~s[<<\"sig\">>])\n\n      assert {:error, _reason} = JwtVerification.verify(invalid_token, @jwt_secret, nil)\n    end\n\n    test \"when token is valid and alg is HS256\" do\n      signer = Joken.Signer.create(\"HS256\", @jwt_secret)\n\n      token = Joken.generate_and_sign!(%{}, %{}, signer)\n\n      assert {:ok, _claims} = JwtVerification.verify(token, @jwt_secret, nil)\n    end\n\n    test \"when token is valid and alg is HS384\" do\n      signer = Joken.Signer.create(\"HS384\", @jwt_secret)\n\n      token = Joken.generate_and_sign!(%{}, %{}, signer)\n\n      assert {:ok, _claims} = JwtVerification.verify(token, @jwt_secret, nil)\n    end\n\n    test \"when token is valid and alg is HS512\" do\n      signer = Joken.Signer.create(\"HS512\", @jwt_secret)\n\n      token = Joken.generate_and_sign!(%{}, %{}, signer)\n\n      assert {:ok, _claims} = JwtVerification.verify(token, @jwt_secret, nil)\n    end\n\n    test \"when token has expired we return current time as the message so we can use it in expiration calculations\" do\n      signer = Joken.Signer.create(@alg, @jwt_secret)\n\n      current_time = 1_610_086_801\n      Mock.freeze(current_time)\n\n      token =\n        Joken.generate_and_sign!(\n          %{\"exp\" => %Joken.Claim{generate: fn -> current_time end}},\n          %{},\n          signer\n        )\n\n      assert {:error, [message: current_time, claim: \"exp\", claim_val: 1_610_086_801]} =\n               JwtVerification.verify(token, @jwt_secret, nil)\n\n      assert is_integer(current_time)\n\n      token =\n        Joken.generate_and_sign!(\n          %{\"exp\" => %Joken.Claim{generate: fn -> current_time - 1 end}},\n          %{},\n          signer\n        )\n\n      assert {:error, [message: current_time, claim: \"exp\", claim_val: _]} =\n               JwtVerification.verify(token, @jwt_secret, nil)\n\n      assert is_integer(current_time)\n    end\n\n    test \"when token has not expired\" do\n      signer = Joken.Signer.create(@alg, @jwt_secret)\n\n      Mock.freeze()\n      current_time = Mock.current_time()\n\n      token =\n        Joken.generate_and_sign!(\n          %{\n            \"exp\" => %Joken.Claim{generate: fn -> current_time + 1 end}\n          },\n          %{},\n          signer\n        )\n\n      assert {:ok, _claims} = JwtVerification.verify(token, @jwt_secret, nil)\n    end\n\n    test \"when token claims match expected claims from :jwt_claim_validators config\" do\n      Application.put_env(:realtime, :jwt_claim_validators, %{\n        \"iss\" => \"Tester\",\n        \"aud\" => \"www.test.com\"\n      })\n\n      signer = Joken.Signer.create(@alg, @jwt_secret)\n\n      Mock.freeze()\n      current_time = Mock.current_time()\n\n      token =\n        Joken.generate_and_sign!(\n          %{\n            \"exp\" => %Joken.Claim{generate: fn -> current_time + 1 end},\n            \"iss\" => %Joken.Claim{generate: fn -> \"Tester\" end},\n            \"aud\" => %Joken.Claim{generate: fn -> \"www.test.com\" end},\n            \"sub\" => %Joken.Claim{generate: fn -> \"tester@test.com\" end}\n          },\n          %{},\n          signer\n        )\n\n      assert {:ok, _claims} = JwtVerification.verify(token, @jwt_secret, nil)\n    end\n\n    test \"when token claims do not match expected claims from :jwt_claim_validators config\" do\n      Application.put_env(:realtime, :jwt_claim_validators, %{\n        \"iss\" => \"Issuer\",\n        \"aud\" => \"www.test.com\"\n      })\n\n      signer = Joken.Signer.create(@alg, @jwt_secret)\n\n      Mock.freeze()\n      current_time = Mock.current_time()\n\n      token =\n        Joken.generate_and_sign!(\n          %{\n            \"exp\" => %Joken.Claim{generate: fn -> current_time + 1 end},\n            \"iss\" => %Joken.Claim{generate: fn -> \"Tester\" end},\n            \"aud\" => %Joken.Claim{generate: fn -> \"www.test.com\" end},\n            \"sub\" => %Joken.Claim{generate: fn -> \"tester@test.com\" end}\n          },\n          %{},\n          signer\n        )\n\n      assert {:error, [message: \"Invalid token\", claim: \"iss\", claim_val: \"Tester\"]} =\n               JwtVerification.verify(token, @jwt_secret, nil)\n    end\n\n    test \"using RS256 JWK\" do\n      jwks = %{\n        \"keys\" => [\n          %{\n            \"kty\" => \"RSA\",\n            \"n\" =>\n              \"6r1mKwCalvJ0NyThyQkBr5huFILwwhXcxtsdlw-WybNz4avzODQwLFkA-b2fnnfdFgualV2NdcvoJSo1bzVGCWWqwWKWdTQKFjtcjAIC4FnhOv5ynNF9Ub-11ORDd1aiq_4XKNA4GaS1HqBekVDAAvJYy99Jz0CkLx4NU_VrS0U9sOQzUAhy2MwZCx2kZ3SWKEMjjEIkbvIb22IdRTyuFsAndKGpyzhw-MalnU5P2hOig-QApNBc0WJtTHTAa4PLQ6v_5jNc5PzCwP8jGK9SlrSF-GOnx9BVBX9t-AIDp-BviKbtY7y-pku6-f7HSiS2T3iAJkHXPm9E_NwwhWzMJQ\",\n            \"e\" => \"AQAB\",\n            \"kid\" => \"key-id-1\"\n          }\n        ]\n      }\n\n      token =\n        \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDc1NjUsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MTE2NX0.zUeoZrWK1efAc4q9y978_9qkhdXktdjf5H8O9Rw0SHcPaXW8OBcuNR2huRrgORvqFx6_sHn6nCJaWkZGzO-f8wskMD7Z4INq2JUypr6nASie3Qu2lLyeY3WTInaXNAKH-oqlfTLRskbz8zkIxOj2bBJiN9ceQLkJU-c92ndiuiG5D1jyQrGsvRdFem_cemp0yOoEaC0XWdjeV6C_UD-34GIyv3o8H4HZg1GcCiyNnAfDmLAcTOQPmqkwsRDQb-pm5O3HwpQt9WHOB6i1vzf-nmIGyCRA7STPdALK16-aiAyT4SJRxM5WN3iK8yitH7g4JETb9WocBbwIM_zfNnUI5w\"\n\n      # Check that the signature is valid even though time may be off.\n      assert JwtVerification.verify(token, @jwt_secret, jwks) != {:error, :signature_error}\n    end\n\n    test \"using RS256 JWK but wrong signature\" do\n      jwks = %{\n        \"keys\" => [\n          %{\n            \"kty\" => \"RSA\",\n            \"n\" =>\n              \"6r1mKwCalvJ0NyThyQkBr5huFILwwhXcxtsdlw-WybNz4avzODQwLFkA-b2fnnfdFgualV2NdcvoJSo1bzVGCWWqwWKWdTQKFjtcjAIC4FnhOv5ynNF9Ub-11ORDd1aiq_4XKNA4GaS1HqBekVDAAvJYy99Jz0CkLx4NU_VrS0U9sOQzUAhy2MwZCx2kZ3SWKEMjjEIkbvIb22IdRTyuFsAndKGpyzhw-MalnU5P2hOig-QApNBc0WJtTHTAa4PLQ6v_5jNc5PzCwP8jGK9SlrSF-GOnx9BVBX9t-AIDp-BviKbtY7y-pku6-f7HSiS2T3iAJkHXPm9E_NwwhWzMJQ\",\n            \"e\" => \"AQAB\",\n            \"kid\" => \"key-id-1\"\n          }\n        ]\n      }\n\n      token =\n        \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDc1NjUsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MTE2NX0.zUeoZrWK1efAc4q9y978_9qkhdXktdjf5H8O9Rw0SHcPaXW8OBcuNR2huRrgORvqFx6_sHn6nCJaWkZGzO-f8wskMD7Z4INq2JUypr6nASie3Qu2lLyeY3WTInaXNAKH-oqlfTLRskbz8zkIxOj2bBJiN9ceQLkJU-c92ndiuiG5D1jyQrGsvRdFem_cemp0yOoEaC0XWdjeV6C_UD-34GIyv3o8H4HZg1GcCiyNnAfDmLAcTOQPmqkwsRDQb-pm5O3HwpQt9WHOB6i1vzf-nmIGyCRA7STPdALK16-aiAyT4SJRxM5WN3iK8yitH7g4JETb9WocBbwIM_zfnnUI5w\"\n\n      assert JwtVerification.verify(token, @jwt_secret, jwks) == {:error, :signature_error}\n    end\n\n    test \"using ES256 JWK\" do\n      jwks = %{\n        \"keys\" => [\n          %{\n            \"kty\" => \"EC\",\n            \"x\" => \"iX_niXPSL2nW-9IyCELzyceAtuE3B98pWML5tQGACD4\",\n            \"y\" => \"kT02DoLhXx6gtpkbrN8XwQ2wtzE6cDBaqlWgVXIeqV0\",\n            \"crv\" => \"P-256\",\n            \"d\" => \"FBVYnsYA2C3FTggEwV8kCRMo4FLl220_cWY2RdXyb_8\",\n            \"kid\" => \"key-id-1\"\n          }\n        ]\n      }\n\n      token =\n        \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDk2NTcsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MzI1N30.IIQBuEiSnZacGMqiqsrLAeRGOjIaB4F3x1gnLN5zvhFryJ-6tdgu96lFv5HUF13IL2UfHWad0OuvoDt4DEHRxw\"\n\n      # Check that the signature is valid even though time may be off.\n      assert {:error, [message: _, claim: \"exp\", claim_val: _]} = JwtVerification.verify(token, @jwt_secret, jwks)\n    end\n\n    test \"using ES256 JWK with wrong signature\" do\n      jwks = %{\n        \"keys\" => [\n          %{\n            \"kty\" => \"EC\",\n            \"x\" => \"iX_niXPSL2nW-9IyCELzyceAtuE3B98pWML5tQGACD4\",\n            \"y\" => \"kT02DoLhXx6gtpkbrN8XwQ2wtzE6cDBaqlWgVXIeqV0\",\n            \"crv\" => \"P-256\",\n            \"d\" => \"FBVYnsYA2C3FTggEwV8kCRMo4FLl220_cWY2RdXyb_8\",\n            \"kid\" => \"key-id-1\"\n          }\n        ]\n      }\n\n      token =\n        \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDk2NTcsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MzI1N30.IIQBuEiSnZacGMqiqsrLAeRGOjIaB4F3x1gnLN5zvhFryJ-6tdgu96lFv5HUF13IL2UfHWad0OuvoDt4DEHrxw\"\n\n      assert JwtVerification.verify(token, @jwt_secret, jwks) == {:error, :signature_error}\n    end\n\n    test \"using HS256 JWK\" do\n      jwks = %{\n        \"keys\" => [\n          %{\n            \"alg\" => \"HS256\",\n            \"k\" =>\n              \"WWpiUEVXK2I4dVM1djkzMS9TWTNmb2RtcUtiZVh3NnBHS0JaS1JDMGpaODdhVHpaZ3N0Ly9yMG0wU1M4Z1U4OFE0aGdwclBMMzVRRU5ya253TWxhUlE9PQ\",\n            \"key_ops\" => [\"verify\"],\n            \"kid\" => \"4FcGwlBxkBV1bSZw\",\n            \"kty\" => \"oct\"\n          }\n        ]\n      }\n\n      token =\n        \"eyJhbGciOiJIUzI1NiIsImtpZCI6IjRGY0d3bEJ4a0JWMWJTWnciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2hqbmRnYWdpZGlwY3RxdXFxeXloLnN1cGFiYXNlLmNvL2F1dGgvdjEiLCJzdWIiOiJmZjA0NjVlMy1lZjk3LTRkYjItOWE1Zi0zZDI4Y2YxODE0MmYiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzUyODA4NjE4LCJpYXQiOjE3NTI4MDUwMTgsImVtYWlsIjoiY2hhdEBlZHVhcmRvLmd1cmdlbC5tZSIsInBob25lIjoiIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwiLCJwcm92aWRlcnMiOlsiZW1haWwiXX0sInVzZXJfbWV0YWRhdGEiOnsiZW1haWwiOiJjaGF0QGVkdWFyZG8uZ3VyZ2VsLm1lIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInBob25lX3ZlcmlmaWVkIjpmYWxzZSwic3ViIjoiZmYwNDY1ZTMtZWY5Ny00ZGIyLTlhNWYtM2QyOGNmMTgxNDJmIn0sInJvbGUiOiJhdXRoZW50aWNhdGVkIiwiYWFsIjoiYWFsMSIsImFtciI6W3sibWV0aG9kIjoicGFzc3dvcmQiLCJ0aW1lc3RhbXAiOjE3NTI4MDUwMTh9XSwic2Vzc2lvbl9pZCI6IjA2MDJkYWM0LWMwMjctNGIwNi1hZDM5LTMzN2ViMTZlODdlNSIsImlzX2Fub255bW91cyI6ZmFsc2V9.SnGzRjLfHPtT64kXYEQVBLKizCl76LqEPILyAPxoDwk\"\n\n      # Check that the signature is valid even though time may be off.\n      assert {:error, [message: _, claim: \"exp\", claim_val: _]} = JwtVerification.verify(token, @jwt_secret, jwks)\n    end\n\n    test \"using JWT without typ header parameter\" do\n      jwks = %{\n        \"keys\" => [\n          %{\n            \"alg\" => \"RS256\",\n            \"e\" => \"AQAB\",\n            \"kid\" => \"sso_oidc_key_pair_01K20X7J43ZV31EKZMG7X3KW7K\",\n            \"kty\" => \"RSA\",\n            \"n\" =>\n              \"uyc8QVrMjJTZEZoGNIJrugvf6j3iZ2Uz2IEgFuHCeC5AbM4BwZ4V1JshcW2CRs8uAmEdnH_-jiibayGmoND7OLziZJZsLir_8_kZSc5jjJ-IVFMzB1XXKze44HB3IOpcaPW1IOx0NfSI6-xOFinX7yApTZxXxmLFMEXtOWVsY_MPMrjThfvCSqesDGLRGKCn_DA4Hhaixf4NG-etGUOiavHNpqfREM7td-9caWpIyo7acWcgfYyhkxZbuYRINje1V66HWIdzzZ8uhfLXm-85LZdaWn9J83OHh3TEQW2SIAfHSNZkwXu6h5ndd_ciWxTWuvU3yjWkS-k4C8dXB--bZQ\",\n            \"use\" => \"sig\",\n            \"x5t#S256\" => \"HmlJQWnNuujjOdcRTCu5amk1pttdFRMBR7Rl4zplobA\"\n          }\n        ]\n      }\n\n      token =\n        \"eyJhbGciOiJSUzI1NiIsImtpZCI6InNzb19vaWRjX2tleV9wYWlyXzAxSzIwWDdKNDNaVjMxRUtaTUc3WDNLVzdLIn0.eyJyb2xlIjoiYXV0aGVudGljYXRlZCIsImlzcyI6Imh0dHBzOi8vYXBpLndvcmtvcy5jb20vdXNlcl9tYW5hZ2VtZW50L2NsaWVudF8wMUsyMFg3SkNROVg1NDVXUzgwUVpQQTNGUSIsInN1YiI6InVzZXJfMDFLMjBYR1g3SkU1M1JNVEIzRjE0SEFUNTkiLCJzaWQiOiJzZXNzaW9uXzAxSzIwWEdYQVhINjhLMkI0VjcwWko3VzlZIiwianRpIjoiMDFLMjEyUzNLMEpXQ0U3Wk1URVk0RVM4UTYiLCJleHAiOjE3NTQ1MzA5MTQsImlhdCI6MTc1NDUzMDYxNH0.kt-zlPxaLVDMU9iAWCYvi3dtm9HzYponaX5E4lsS2k5F469obf4i9FyLDeXsaPyAN9V3DaAjUwys9lDyjiPsE7y1s2tFE1g9LU4goJbwinkoklUYrTmVVZcY4lkVEQSZZGwmkwgIsqKsdwTlYMzVjejsAk25Foq-FhGdsyoju21sU5S4fZPC-n8Rl8wBwD5rOPFWVHxk3z4z37585LDHhADyq1S17WxLJobfEgK_TeAlwXa2bK14vutVPpuL-qAn8nX8b6aqBoHtvRbfqsi2s72bA1S31rTgT2hgD5hqbn_g0rVmMJXt2u1zl4XDBzUj_c3lxNncUf1MgsVOHrbYRw\"\n\n      assert {:error, [message: _, claim: \"exp\", claim_val: _]} = JwtVerification.verify(token, @jwt_secret, jwks)\n    end\n\n    test \"using HS256 JWK with wrong signature\" do\n      jwks = %{\n        \"keys\" => [\n          %{\n            \"alg\" => \"HS256\",\n            \"k\" =>\n              \"WWpiUEVXK2I4dVM1djkzMS9TWTNmb2RtcUtiZVh3NnBHS0JaS1JDMGpaODdhVHpaZ3N0Ly9yMG0wU1M4Z1U4OFE0aGdwclBMMzVRRU5ya253TWxhUlE9PQ\",\n            \"key_ops\" => [\"verify\"],\n            \"kid\" => \"4FcGwlBxkBV1bSZw\",\n            \"kty\" => \"oct\"\n          }\n        ]\n      }\n\n      token =\n        \"eyJhbGciOiJIUzI1NiIsImtpZCI6IjRGY0d3bEJ4a0JWMWJTWnciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2hqbmRnYWdpZGlwY3RxdXFxeXloLnN1cGFiYXNlLmNvL2F1dGgvdjEiLCJzdWIiOiJmZjA0NjVlMy1lZjk3LTRkYjItOWE1Zi0zZDI4Y2YxODE0MmYiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzUyODA4NjE4LCJpYXQiOjE3NTI4MDUwMTgsImVtYWlsIjoiY2hhdEBlZHVhcmRvLmd1cmdlbC5tZSIsInBob25lIjoiIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwiLCJwcm92aWRlcnMiOlsiZW1haWwiXX0sInVzZXJfbWV0YWRhdGEiOnsiZW1haWwiOiJjaGF0QGVkdWFyZG8uZ3VyZ2VsLm1lIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInBob25lX3ZlcmlmaWVkIjpmYWxzZSwic3ViIjoiZmYwNDY1ZTMtZWY5Ny00ZGIyLTlhNWYtM2QyOGNmMTgxNDJmIn0sInJvbGUiOiJhdXRoZW50aWNhdGVkIiwiYWFsIjoiYWFsMSIsImFtciI6W3sibWV0aG9kIjoicGFzc3dvcmQiLCJ0aW1lc3RhbXAiOjE3NTI4MDUwMTh9XSwic2Vzc2lvbl9pZCI6IjA2MDJkYWM0LWMwMjctNGIwNi1hZDM5LTMzN2ViMTZlODdlNSIsImlzX2Fub255bW91cyI6ZmFsc2V9.SnGzRjLfHPtT64kXYEQVBLKizCl76LqEPILyApxoDwk\"\n\n      assert JwtVerification.verify(token, @jwt_secret, jwks) == {:error, :signature_error}\n    end\n\n    test \"returns error when no matching JWK is found for RSA algorithm\" do\n      # Replace with a valid JWT structure\n      token =\n        \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDc1NjUsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MTE2NX0.zUeoZrWK1efAc4q9y978_9qkhdXktdjf5H8O9Rw0SHcPaXW8OBcuNR2huRrgORvqFx6_sHn6nCJaWkZGzO-f8wskMD7Z4INq2JUypr6nASie3Qu2lLyeY3WTInaXNAKH-oqlfTLRskbz8zkIxOj2bBJiN9ceQLkJU-c92ndiuiG5D1jyQrGsvRdFem_cemp0yOoEaC0XWdjeV6C_UD-34GIyv3o8H4HZg1GcCiyNnAfDmLAcTOQPmqkwsRDQb-pm5O3HwpQt9WHOB6i1vzf-nmIGyCRA7STPdALK16-aiAyT4SJRxM5WN3iK8yitH7g4JETb9WocBbwIM_zfNnUI5w\"\n\n      jwt_secret = \"secret\"\n      jwks = %{\"keys\" => [%{\"kty\" => \"RSA\", \"kid\" => \"some_other_kid\"}]}\n\n      assert {:error, :error_generating_signer} = JwtVerification.verify(token, jwt_secret, jwks)\n    end\n\n    test \"returns error when no matching JWK is found for EC algorithm\" do\n      # Replace with a valid JWT structure\n      token =\n        \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDc1NjUsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MTE2NX0.zUeoZrWK1efAc4q9y978_9qkhdXktdjf5H8O9Rw0SHcPaXW8OBcuNR2huRrgORvqFx6_sHn6nCJaWkZGzO-f8wskMD7Z4INq2JUypr6nASie3Qu2lLyeY3WTInaXNAKH-oqlfTLRskbz8zkIxOj2bBJiN9ceQLkJU-c92ndiuiG5D1jyQrGsvRdFem_cemp0yOoEaC0XWdjeV6C_UD-34GIyv3o8H4HZg1GcCiyNnAfDmLAcTOQPmqkwsRDQb-pm5O3HwpQt9WHOB6i1vzf-nmIGyCRA7STPdALK16-aiAyT4SJRxM5WN3iK8yitH7g4JETb9WocBbwIM_zfNnUI5w\"\n\n      jwt_secret = \"secret\"\n      jwks = %{\"keys\" => [%{\"kty\" => \"EC\", \"kid\" => \"some_other_kid\"}]}\n\n      assert {:error, :error_generating_signer} = JwtVerification.verify(token, jwt_secret, jwks)\n    end\n\n    test \"returns error when no matching JWK is found for OKP algorithm\" do\n      # Replace with a valid JWT structure\n      token =\n        \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDc1NjUsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MTE2NX0.zUeoZrWK1efAc4q9y978_9qkhdXktdjf5H8O9Rw0SHcPaXW8OBcuNR2huRrgORvqFx6_sHn6nCJaWkZGzO-f8wskMD7Z4INq2JUypr6nASie3Qu2lLyeY3WTInaXNAKH-oqlfTLRskbz8zkIxOj2bBJiN9ceQLkJU-c92ndiuiG5D1jyQrGsvRdFem_cemp0yOoEaC0XWdjeV6C_UD-34GIyv3o8H4HZg1GcCiyNnAfDmLAcTOQPmqkwsRDQb-pm5O3HwpQt9WHOB6i1vzf-nmIGyCRA7STPdALK16-aiAyT4SJRxM5WN3iK8yitH7g4JETb9WocBbwIM_zfNnUI5w\"\n\n      jwt_secret = \"secret\"\n      jwks = %{\"keys\" => [%{\"kty\" => \"OKP\", \"kid\" => \"some_other_kid\"}]}\n\n      assert {:error, :error_generating_signer} = JwtVerification.verify(token, jwt_secret, jwks)\n    end\n\n    test \"using Ed25519 JWK\" do\n      # Generate Ed25519 key pair\n      {pub, priv} = :crypto.generate_key(:eddsa, :ed25519)\n\n      jwk = %{\n        \"kty\" => \"OKP\",\n        \"crv\" => \"Ed25519\",\n        \"x\" => Base.url_encode64(pub, padding: false),\n        \"d\" => Base.url_encode64(priv, padding: false),\n        \"kid\" => \"ed-key-1\"\n      }\n\n      jwks = %{\"keys\" => [jwk]}\n\n      signer = Joken.Signer.create(\"Ed25519\", jwk, %{\"kid\" => \"ed-key-1\"})\n\n      Mock.freeze()\n      current_time = Mock.current_time()\n\n      token =\n        Joken.generate_and_sign!(\n          %{\"exp\" => %Joken.Claim{generate: fn -> current_time + 100 end}},\n          %{},\n          signer\n        )\n\n      assert {:ok, _claims} = JwtVerification.verify(token, @jwt_secret, jwks)\n    end\n\n    test \"returns error for unsupported algorithm with kid and jwks\" do\n      header = Base.url_encode64(Jason.encode!(%{\"alg\" => \"PS256\", \"kid\" => \"key-1\"}), padding: false)\n      claims = Base.url_encode64(Jason.encode!(%{\"exp\" => 9_999_999_999}), padding: false)\n      token = \"#{header}.#{claims}.signature\"\n\n      jwks = %{\"keys\" => [%{\"kty\" => \"RSA\", \"kid\" => \"key-1\"}]}\n\n      assert {:error, _} = JwtVerification.verify(token, @jwt_secret, jwks)\n    end\n\n    test \"falls back to jwt_secret when HS256 kid has no matching JWK\" do\n      Mock.freeze()\n      current_time = Mock.current_time()\n\n      signer = Joken.Signer.create(\"HS256\", @jwt_secret)\n\n      token =\n        Joken.generate_and_sign!(\n          %{\"exp\" => %Joken.Claim{generate: fn -> current_time + 100 end}},\n          %{},\n          signer\n        )\n\n      jwks = %{\"keys\" => [%{\"kty\" => \"oct\", \"kid\" => \"wrong-kid\"}]}\n\n      assert {:ok, _claims} = JwtVerification.verify(token, @jwt_secret, jwks)\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/channels/payloads/flexible_boolean_test.exs",
    "content": "defmodule RealtimeWeb.Channels.Payloads.FlexibleBooleanTest do\n  use ExUnit.Case, async: true\n\n  alias RealtimeWeb.Channels.Payloads.FlexibleBoolean\n\n  describe \"type/0\" do\n    test \"returns :boolean\" do\n      assert FlexibleBoolean.type() == :boolean\n    end\n  end\n\n  describe \"cast/1\" do\n    test \"casts boolean true as-is\" do\n      assert FlexibleBoolean.cast(true) == {:ok, true}\n    end\n\n    test \"casts boolean false as-is\" do\n      assert FlexibleBoolean.cast(false) == {:ok, false}\n    end\n\n    test \"casts string 'true' in any case to boolean true\" do\n      assert FlexibleBoolean.cast(\"true\") == {:ok, true}\n      assert FlexibleBoolean.cast(\"True\") == {:ok, true}\n      assert FlexibleBoolean.cast(\"TRUE\") == {:ok, true}\n      assert FlexibleBoolean.cast(\"tRuE\") == {:ok, true}\n    end\n\n    test \"casts string 'false' in any case to boolean false\" do\n      assert FlexibleBoolean.cast(\"false\") == {:ok, false}\n      assert FlexibleBoolean.cast(\"False\") == {:ok, false}\n      assert FlexibleBoolean.cast(\"FALSE\") == {:ok, false}\n      assert FlexibleBoolean.cast(\"fAlSe\") == {:ok, false}\n    end\n\n    test \"returns error for invalid string values\" do\n      assert FlexibleBoolean.cast(\"test\") == :error\n      assert FlexibleBoolean.cast(\"yes\") == :error\n      assert FlexibleBoolean.cast(\"no\") == :error\n      assert FlexibleBoolean.cast(\"1\") == :error\n      assert FlexibleBoolean.cast(\"0\") == :error\n      assert FlexibleBoolean.cast(\"\") == :error\n    end\n\n    test \"returns error for non-boolean, non-string values\" do\n      assert FlexibleBoolean.cast(1) == :error\n      assert FlexibleBoolean.cast(0) == :error\n      assert FlexibleBoolean.cast(nil) == :error\n      assert FlexibleBoolean.cast(%{}) == :error\n      assert FlexibleBoolean.cast([]) == :error\n    end\n  end\n\n  describe \"load/1\" do\n    test \"loads boolean values\" do\n      assert FlexibleBoolean.load(true) == {:ok, true}\n      assert FlexibleBoolean.load(false) == {:ok, false}\n    end\n  end\n\n  describe \"dump/1\" do\n    test \"dumps boolean values\" do\n      assert FlexibleBoolean.dump(true) == {:ok, true}\n      assert FlexibleBoolean.dump(false) == {:ok, false}\n    end\n\n    test \"returns error for non-boolean values\" do\n      assert FlexibleBoolean.dump(\"true\") == :error\n      assert FlexibleBoolean.dump(1) == :error\n      assert FlexibleBoolean.dump(nil) == :error\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/channels/payloads/join_test.exs",
    "content": "defmodule RealtimeWeb.Channels.Payloads.JoinTest do\n  use ExUnit.Case, async: true\n\n  import Generators\n\n  alias RealtimeWeb.Channels.Payloads.Join\n  alias RealtimeWeb.Channels.Payloads.Config\n  alias RealtimeWeb.Channels.Payloads.Broadcast\n  alias RealtimeWeb.Channels.Payloads.Broadcast.Replay\n  alias RealtimeWeb.Channels.Payloads.Presence\n  alias RealtimeWeb.Channels.Payloads.PostgresChange\n\n  describe \"validate/1\" do\n    test \"valid payload allows join\" do\n      key = random_string()\n      access_token = random_string()\n\n      config = %{\n        \"config\" => %{\n          \"private\" => false,\n          \"broadcast\" => %{\"ack\" => false, \"self\" => false, \"replay\" => %{\"since\" => 1, \"limit\" => 10}},\n          \"presence\" => %{\"enabled\" => true, \"key\" => key},\n          \"postgres_changes\" => [\n            %{\"event\" => \"INSERT\", \"schema\" => \"public\", \"table\" => \"users\", \"filter\" => \"id=eq.1\"},\n            %{\"event\" => \"DELETE\", \"schema\" => \"public\", \"table\" => \"users\", \"filter\" => \"id=eq.2\"},\n            %{\"event\" => \"UPDATE\", \"schema\" => \"public\", \"table\" => \"users\", \"filter\" => \"id=eq.3\"}\n          ]\n        },\n        \"access_token\" => access_token\n      }\n\n      assert {:ok, %Join{config: config, access_token: ^access_token}} = Join.validate(config)\n\n      assert %Config{\n               private: false,\n               broadcast: broadcast,\n               presence: presence,\n               postgres_changes: postgres_changes\n             } = config\n\n      assert %Broadcast{ack: false, self: false, replay: replay} = broadcast\n      assert %Presence{enabled: true, key: ^key} = presence\n      assert %Replay{since: 1, limit: 10} = replay\n\n      assert [\n               %PostgresChange{event: \"INSERT\", schema: \"public\", table: \"users\", filter: \"id=eq.1\"},\n               %PostgresChange{event: \"DELETE\", schema: \"public\", table: \"users\", filter: \"id=eq.2\"},\n               %PostgresChange{event: \"UPDATE\", schema: \"public\", table: \"users\", filter: \"id=eq.3\"}\n             ] = postgres_changes\n    end\n\n    test \"presence key as default\" do\n      config = %{\"config\" => %{\"presence\" => %{\"enabled\" => true}}}\n\n      assert {:ok, %Join{config: %Config{presence: %Presence{key: key}}}} = Join.validate(config)\n\n      assert key != \"\"\n      assert is_binary(key)\n    end\n\n    test \"presence key can be number\" do\n      config = %{\"config\" => %{\"presence\" => %{\"enabled\" => true, \"key\" => 123}}}\n\n      assert {:ok, %Join{config: %Config{presence: %Presence{key: key}}}} = Join.validate(config)\n\n      assert key == 123\n    end\n\n    test \"invalid replay\" do\n      config = %{\"config\" => %{\"broadcast\" => %{\"replay\" => 123}}}\n\n      assert {\n               :error,\n               :invalid_join_payload,\n               %{config: %{broadcast: %{replay: [\"unable to parse, expected a map\"]}}}\n             } =\n               Join.validate(config)\n    end\n\n    test \"missing enabled presence defaults to true\" do\n      config = %{\"config\" => %{\"presence\" => %{}}}\n\n      assert {:ok, %Join{config: %Config{presence: %Presence{enabled: true}}}} = Join.validate(config)\n    end\n\n    test \"invalid payload returns errors\" do\n      config = %{\"config\" => [\"test\"]}\n\n      assert {:error, :invalid_join_payload, %{config: error}} = Join.validate(config)\n      assert error == [\"unable to parse, expected a map\"]\n    end\n\n    test \"invalid nested configurations returns errors\" do\n      config = %{\n        \"config\" => %{\n          \"broadcast\" => %{\"ack\" => \"test\"},\n          \"presence\" => %{\"enabled\" => \"test\"},\n          \"postgres_changes\" => %{\"event\" => \"test\"}\n        },\n        \"access_token\" => true,\n        \"user_token\" => true\n      }\n\n      assert {:error, :invalid_join_payload, errors} = Join.validate(config)\n\n      assert errors == %{\n               config: %{\n                 broadcast: %{ack: [\"unable to parse, expected boolean\"]},\n                 presence: %{enabled: [\"unable to parse, expected boolean\"]},\n                 postgres_changes: [\"unable to parse, expected an array of maps\"]\n               },\n               access_token: [\"unable to parse, expected string\"],\n               user_token: [\"unable to parse, expected string\"]\n             }\n    end\n\n    test \"handles postgres changes with nil value in array as empty array\" do\n      config = %{\"config\" => %{\"postgres_changes\" => [nil]}}\n\n      assert {:ok, %Join{config: %Config{postgres_changes: []}}} = Join.validate(config)\n    end\n\n    test \"handles postgres changes as nil as empty array\" do\n      config = %{\"config\" => %{\"postgres_changes\" => nil}}\n\n      assert {:ok, %Join{config: %Config{postgres_changes: []}}} = Join.validate(config)\n    end\n\n    test \"accepts string 'true' for boolean fields\" do\n      config = %{\n        \"config\" => %{\n          \"private\" => \"true\",\n          \"broadcast\" => %{\"ack\" => \"true\", \"self\" => \"true\"},\n          \"presence\" => %{\"enabled\" => \"true\"}\n        }\n      }\n\n      assert {:ok, %Join{config: config_result}} = Join.validate(config)\n\n      assert %Config{\n               private: true,\n               broadcast: %Broadcast{ack: true, self: true},\n               presence: %Presence{enabled: true}\n             } = config_result\n    end\n\n    test \"accepts string 'True' for boolean fields\" do\n      config = %{\n        \"config\" => %{\n          \"private\" => \"True\",\n          \"broadcast\" => %{\"ack\" => \"True\", \"self\" => \"True\"},\n          \"presence\" => %{\"enabled\" => \"True\"}\n        }\n      }\n\n      assert {:ok, %Join{config: config_result}} = Join.validate(config)\n\n      assert %Config{\n               private: true,\n               broadcast: %Broadcast{ack: true, self: true},\n               presence: %Presence{enabled: true}\n             } = config_result\n    end\n\n    test \"accepts string 'false' for boolean fields\" do\n      config = %{\n        \"config\" => %{\n          \"private\" => \"false\",\n          \"broadcast\" => %{\"ack\" => \"false\", \"self\" => \"false\"},\n          \"presence\" => %{\"enabled\" => \"false\"}\n        }\n      }\n\n      assert {:ok, %Join{config: config_result}} = Join.validate(config)\n\n      assert %Config{\n               private: false,\n               broadcast: %Broadcast{ack: false, self: false},\n               presence: %Presence{enabled: false}\n             } = config_result\n    end\n\n    test \"accepts string 'False' for boolean fields\" do\n      config = %{\n        \"config\" => %{\n          \"private\" => \"False\",\n          \"broadcast\" => %{\"ack\" => \"False\", \"self\" => \"False\"},\n          \"presence\" => %{\"enabled\" => \"False\"}\n        }\n      }\n\n      assert {:ok, %Join{config: config_result}} = Join.validate(config)\n\n      assert %Config{\n               private: false,\n               broadcast: %Broadcast{ack: false, self: false},\n               presence: %Presence{enabled: false}\n             } = config_result\n    end\n\n    test \"rejects invalid boolean strings\" do\n      config = %{\n        \"config\" => %{\n          \"private\" => \"yes\",\n          \"broadcast\" => %{\"ack\" => \"a\", \"self\" => \"b\"},\n          \"presence\" => %{\"enabled\" => \"no\"}\n        }\n      }\n\n      assert {:error, :invalid_join_payload, errors} = Join.validate(config)\n\n      assert errors == %{\n               config: %{\n                 private: [\"unable to parse, expected boolean\"],\n                 broadcast: %{\n                   ack: [\"unable to parse, expected boolean\"],\n                   self: [\"unable to parse, expected boolean\"]\n                 },\n                 presence: %{enabled: [\"unable to parse, expected boolean\"]}\n               }\n             }\n    end\n  end\n\n  describe \"presence_enabled?/1\" do\n    test \"returns enabled value from config\" do\n      join = %Join{config: %Config{presence: %Presence{enabled: false}}}\n      refute Join.presence_enabled?(join)\n\n      join = %Join{config: %Config{presence: %Presence{enabled: true}}}\n      assert Join.presence_enabled?(join)\n    end\n\n    test \"defaults to true when config is nil\" do\n      assert Join.presence_enabled?(%Join{config: nil})\n    end\n\n    test \"defaults to true for non-Join struct\" do\n      assert Join.presence_enabled?(nil)\n    end\n  end\n\n  describe \"presence_key/1\" do\n    test \"returns UUID when key is empty string\" do\n      join = %Join{config: %Config{presence: %Presence{key: \"\"}}}\n      key = Join.presence_key(join)\n      assert is_binary(key)\n      assert key != \"\"\n    end\n\n    test \"returns the configured key\" do\n      join = %Join{config: %Config{presence: %Presence{key: \"my_key\"}}}\n      assert Join.presence_key(join) == \"my_key\"\n    end\n\n    test \"returns UUID for non-matching struct\" do\n      key = Join.presence_key(%Join{config: nil})\n      assert is_binary(key)\n      assert key != \"\"\n    end\n  end\n\n  describe \"ack_broadcast?/1\" do\n    test \"returns ack value from config\" do\n      join = %Join{config: %Config{broadcast: %Broadcast{ack: true}}}\n      assert Join.ack_broadcast?(join)\n\n      join = %Join{config: %Config{broadcast: %Broadcast{ack: false}}}\n      refute Join.ack_broadcast?(join)\n    end\n\n    test \"defaults to false when config is nil\" do\n      refute Join.ack_broadcast?(%Join{config: nil})\n    end\n  end\n\n  describe \"self_broadcast?/1\" do\n    test \"returns self value from config\" do\n      join = %Join{config: %Config{broadcast: %Broadcast{self: true}}}\n      assert Join.self_broadcast?(join)\n\n      join = %Join{config: %Config{broadcast: %Broadcast{self: false}}}\n      refute Join.self_broadcast?(join)\n    end\n\n    test \"defaults to false when config is nil\" do\n      refute Join.self_broadcast?(%Join{config: nil})\n    end\n  end\n\n  describe \"private?/1\" do\n    test \"returns private value from config\" do\n      join = %Join{config: %Config{private: true}}\n      assert Join.private?(join)\n\n      join = %Join{config: %Config{private: false}}\n      refute Join.private?(join)\n    end\n\n    test \"defaults to false when config is nil\" do\n      refute Join.private?(%Join{config: nil})\n    end\n  end\n\n  describe \"error_message/2\" do\n    test \"returns message with type when type is present\" do\n      assert Join.error_message(:field, type: :string) == \"unable to parse, expected string\"\n    end\n\n    test \"returns generic message when type is not present\" do\n      assert Join.error_message(:field, []) == \"unable to parse\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/channels/realtime_channel/broadcast_handler_test.exs",
    "content": "defmodule RealtimeWeb.RealtimeChannel.BroadcastHandlerTest do\n  use Realtime.DataCase,\n    async: true,\n    parameterize: [%{serializer: Phoenix.Socket.V1.JSONSerializer}, %{serializer: RealtimeWeb.Socket.V2Serializer}]\n\n  use Mimic\n\n  import Generators\n  import ExUnit.CaptureLog\n\n  alias Ecto.UUID\n  alias Realtime.RateCounter\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Authorization\n  alias Realtime.Tenants.Authorization.Policies\n  alias Realtime.Tenants.Authorization.Policies.BroadcastPolicies\n  alias Realtime.Tenants.Connect\n  alias RealtimeWeb.Endpoint\n  alias RealtimeWeb.RealtimeChannel.BroadcastHandler\n\n  setup [:initiate_tenant]\n\n  @payload %{\"a\" => \"b\"}\n\n  describe \"handle/3\" do\n    test \"with write true policy, user is able to send message\",\n         %{topic: topic, tenant: tenant, db_conn: db_conn, serializer: serializer} do\n      socket = socket_fixture(tenant, topic, policies: %Policies{broadcast: %BroadcastPolicies{write: true}})\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:reply, :ok, socket} = BroadcastHandler.handle(@payload, db_conn, socket)\n          socket\n      end\n\n      for _ <- 1..100 do\n        topic = \"realtime:#{topic}\"\n        assert_receive {:socket_push, :text, data}\n\n        assert Jason.decode!(data) == message(serializer, topic, @payload)\n      end\n\n      {:ok, %{avg: avg, bucket: buckets}} = RateCounterHelper.tick!(Tenants.events_per_second_rate(tenant))\n      assert Enum.sum(buckets) == 100\n      assert avg > 0\n    end\n\n    test \"with write false policy, user is not able to send message\", %{topic: topic, tenant: tenant, db_conn: db_conn} do\n      socket = socket_fixture(tenant, topic, policies: %Policies{broadcast: %BroadcastPolicies{write: false}})\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:noreply, socket} = BroadcastHandler.handle(%{}, db_conn, socket)\n          socket\n      end\n\n      refute_received _any\n\n      {:ok, %{avg: avg}} = RateCounterHelper.tick!(Tenants.events_per_second_rate(tenant))\n      assert avg == 0.0\n    end\n\n    @tag policies: [:authenticated_read_broadcast, :authenticated_write_broadcast]\n    test \"with nil policy but valid user, is able to send message\",\n         %{topic: topic, tenant: tenant, db_conn: db_conn, serializer: serializer} do\n      socket = socket_fixture(tenant, topic)\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:reply, :ok, socket} = BroadcastHandler.handle(@payload, db_conn, socket)\n          socket\n      end\n\n      for _ <- 1..100 do\n        topic = \"realtime:#{topic}\"\n        assert_received {:socket_push, :text, data}\n        assert Jason.decode!(data) == message(serializer, topic, @payload)\n      end\n\n      {:ok, %{avg: avg, bucket: buckets}} = RateCounterHelper.tick!(Tenants.events_per_second_rate(tenant))\n      assert Enum.sum(buckets) == 100\n      assert avg > 0.0\n    end\n\n    @tag policies: [:authenticated_read_matching_user_sub, :authenticated_write_matching_user_sub], sub: UUID.generate()\n    test \"with valid sub, is able to send message\",\n         %{topic: topic, tenant: tenant, db_conn: db_conn, sub: sub, serializer: serializer} do\n      socket =\n        socket_fixture(tenant, topic,\n          policies: %Policies{broadcast: %BroadcastPolicies{write: nil, read: true}},\n          claims: %{sub: sub}\n        )\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:reply, :ok, socket} = BroadcastHandler.handle(@payload, db_conn, socket)\n          socket\n      end\n\n      for _ <- 1..100 do\n        topic = \"realtime:#{topic}\"\n        assert_received {:socket_push, :text, data}\n        assert Jason.decode!(data) == message(serializer, topic, @payload)\n      end\n    end\n\n    @tag policies: [:authenticated_read_matching_user_sub, :authenticated_write_matching_user_sub], sub: UUID.generate()\n    test \"with invalid sub, is not able to send message\", %{topic: topic, tenant: tenant, db_conn: db_conn} do\n      socket =\n        socket_fixture(tenant, topic,\n          policies: %Policies{broadcast: %BroadcastPolicies{write: nil, read: true}},\n          claims: %{sub: UUID.generate()}\n        )\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:noreply, socket} = BroadcastHandler.handle(%{\"a\" => \"b\"}, db_conn, socket)\n          socket\n      end\n\n      refute_receive {:socket_push, :text, _}, 120\n    end\n\n    @tag policies: [:read_matching_user_role, :write_matching_user_role], role: \"anon\"\n    test \"with valid role, is able to send message\",\n         %{topic: topic, tenant: tenant, db_conn: db_conn, serializer: serializer} do\n      socket =\n        socket_fixture(tenant, topic,\n          policies: %Policies{broadcast: %BroadcastPolicies{write: nil, read: true}},\n          claims: %{role: \"anon\"}\n        )\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:reply, :ok, socket} = BroadcastHandler.handle(@payload, db_conn, socket)\n          socket\n      end\n\n      for _ <- 1..100 do\n        topic = \"realtime:#{topic}\"\n        assert_received {:socket_push, :text, data}\n        assert Jason.decode!(data) == message(serializer, topic, @payload)\n      end\n    end\n\n    @tag policies: [:read_matching_user_role, :write_matching_user_role], role: \"anon\"\n    test \"with invalid role, is not able to send message\", %{topic: topic, tenant: tenant, db_conn: db_conn} do\n      socket =\n        socket_fixture(tenant, topic,\n          policies: %Policies{broadcast: %BroadcastPolicies{write: nil, read: true}},\n          claims: %{role: \"potato\"}\n        )\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:noreply, socket} = BroadcastHandler.handle(%{\"a\" => \"b\"}, db_conn, socket)\n          socket\n      end\n\n      refute_receive {:socket_push, :text, _}, 120\n    end\n\n    test \"with nil policy and invalid user, won't send message\", %{topic: topic, tenant: tenant, db_conn: db_conn} do\n      socket = socket_fixture(tenant, topic)\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:noreply, socket} = BroadcastHandler.handle(%{}, db_conn, socket)\n          socket\n      end\n\n      refute_received _any\n\n      {:ok, %{avg: avg}} = RateCounterHelper.tick!(Tenants.events_per_second_rate(tenant))\n      assert avg == 0.0\n    end\n\n    @tag policies: [:authenticated_read_broadcast, :authenticated_write_broadcast]\n    test \"validation only runs once on nil and valid policies\",\n         %{topic: topic, tenant: tenant, db_conn: db_conn, serializer: serializer} do\n      socket = socket_fixture(tenant, topic)\n\n      expect(Authorization, :get_write_authorizations, 1, fn conn, db_conn, auth_context ->\n        call_original(Authorization, :get_write_authorizations, [conn, db_conn, auth_context])\n      end)\n\n      reject(&Authorization.get_write_authorizations/3)\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:reply, :ok, socket} = BroadcastHandler.handle(@payload, db_conn, socket)\n          socket\n      end\n\n      for _ <- 1..100 do\n        topic = \"realtime:#{topic}\"\n        assert_receive {:socket_push, :text, data}\n        assert Jason.decode!(data) == message(serializer, topic, @payload)\n      end\n    end\n\n    test \"validation only runs once on nil and blocking policies\", %{topic: topic, tenant: tenant, db_conn: db_conn} do\n      socket = socket_fixture(tenant, topic)\n\n      expect(Authorization, :get_write_authorizations, 1, fn conn, db_conn, auth_context ->\n        call_original(Authorization, :get_write_authorizations, [conn, db_conn, auth_context])\n      end)\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:noreply, socket} = BroadcastHandler.handle(%{}, db_conn, socket)\n          socket\n      end\n\n      refute_receive _, 100\n    end\n\n    test \"no ack still sends message\", %{topic: topic, tenant: tenant, db_conn: db_conn, serializer: serializer} do\n      socket =\n        socket_fixture(tenant, topic,\n          policies: %Policies{broadcast: %BroadcastPolicies{write: true}},\n          ack_broadcast: false\n        )\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:noreply, socket} = BroadcastHandler.handle(@payload, db_conn, socket)\n          socket\n      end\n\n      Process.sleep(100)\n\n      for _ <- 1..100 do\n        topic = \"realtime:#{topic}\"\n        assert_received {:socket_push, :text, data}\n        assert Jason.decode!(data) == message(serializer, topic, @payload)\n      end\n    end\n\n    test \"public channels are able to send messages\",\n         %{topic: topic, tenant: tenant, db_conn: db_conn, serializer: serializer} do\n      socket = socket_fixture(tenant, topic, private?: false, policies: nil)\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:reply, :ok, socket} = BroadcastHandler.handle(@payload, db_conn, socket)\n          socket\n      end\n\n      for _ <- 1..100 do\n        topic = \"realtime:#{topic}\"\n        assert_received {:socket_push, :text, data}\n        assert Jason.decode!(data) == message(serializer, topic, @payload)\n      end\n\n      {:ok, %{avg: avg, bucket: buckets}} = RateCounterHelper.tick!(Tenants.events_per_second_rate(tenant))\n      assert Enum.sum(buckets) == 100\n      assert avg > 0.0\n    end\n\n    test \"public channels are able to send messages and ack\",\n         %{topic: topic, tenant: tenant, db_conn: db_conn, serializer: serializer} do\n      socket = socket_fixture(tenant, topic, private?: false, policies: nil)\n\n      for _ <- 1..100, reduce: socket do\n        socket ->\n          {:reply, :ok, socket} = BroadcastHandler.handle(@payload, db_conn, socket)\n          socket\n      end\n\n      for _ <- 1..100 do\n        topic = \"realtime:#{topic}\"\n        assert_receive {:socket_push, :text, data}\n        assert Jason.decode!(data) == message(serializer, topic, @payload)\n      end\n\n      {:ok, %{avg: avg, bucket: buckets}} = RateCounterHelper.tick!(Tenants.events_per_second_rate(tenant))\n      assert Enum.sum(buckets) == 100\n      assert avg > 0.0\n    end\n\n    test \"V2 json UserBroadcastPush\", %{topic: topic, tenant: tenant, db_conn: db_conn, serializer: serializer} do\n      socket = socket_fixture(tenant, topic, private?: false, policies: nil)\n\n      user_broadcast_payload = %{\"a\" => \"b\"}\n      json_encoded_user_broadcast_payload = Jason.encode!(user_broadcast_payload)\n\n      {:reply, :ok, _socket} =\n        BroadcastHandler.handle({\"event123\", :json, json_encoded_user_broadcast_payload, %{}}, db_conn, socket)\n\n      topic = \"realtime:#{topic}\"\n      assert_receive {:socket_push, code, data}\n\n      if serializer == RealtimeWeb.Socket.V2Serializer do\n        assert code == :binary\n\n        assert data ==\n                 <<\n                   # user broadcast = 4\n                   4::size(8),\n                   # topic_size\n                   byte_size(topic),\n                   # user_event_size\n                   byte_size(\"event123\"),\n                   # metadata_size\n                   0,\n                   # json encoding\n                   1::size(8),\n                   topic::binary,\n                   \"event123\"\n                 >> <> json_encoded_user_broadcast_payload\n      else\n        assert code == :text\n\n        assert Jason.decode!(data) ==\n                 message(serializer, topic, %{\n                   \"event\" => \"event123\",\n                   \"payload\" => user_broadcast_payload,\n                   \"type\" => \"broadcast\"\n                 })\n      end\n    end\n\n    test \"V2 binary UserBroadcastPush\", %{topic: topic, tenant: tenant, db_conn: db_conn, serializer: serializer} do\n      socket = socket_fixture(tenant, topic, private?: false, policies: nil)\n\n      user_broadcast_payload = <<123, 456, 789>>\n\n      {:reply, :ok, _socket} =\n        BroadcastHandler.handle({\"event123\", :binary, user_broadcast_payload, %{}}, db_conn, socket)\n\n      topic = \"realtime:#{topic}\"\n\n      if serializer == RealtimeWeb.Socket.V2Serializer do\n        assert_receive {:socket_push, :binary, data}\n\n        assert data ==\n                 <<\n                   # user broadcast = 4\n                   4::size(8),\n                   # topic_size\n                   byte_size(topic),\n                   # user_event_size\n                   byte_size(\"event123\"),\n                   # metadata_size\n                   0,\n                   # binary encoding\n                   0::size(8),\n                   topic::binary,\n                   \"event123\"\n                 >> <> user_broadcast_payload\n      else\n        # Can't receive binary payloads on V1 serializer\n        refute_receive {:socket_push, _code, _data}\n      end\n    end\n\n    @tag policies: [:broken_write_presence]\n    test \"handle failing rls policy\", %{topic: topic, tenant: tenant, db_conn: db_conn} do\n      socket = socket_fixture(tenant, topic)\n\n      log =\n        capture_log(fn ->\n          {:noreply, _socket} = BroadcastHandler.handle(%{}, db_conn, socket)\n\n          {:ok, %{avg: avg}} = RateCounterHelper.tick!(Tenants.events_per_second_rate(tenant))\n          assert avg == 0.0\n\n          refute_receive _, 200\n        end)\n\n      assert log =~ \"RlsPolicyError\"\n    end\n\n    test \"handle payload size excedding limits in private channels\", %{topic: topic, tenant: tenant, db_conn: db_conn} do\n      socket =\n        socket_fixture(tenant, topic,\n          policies: %Policies{broadcast: %BroadcastPolicies{write: true}},\n          ack_broadcast: false\n        )\n\n      assert {:noreply, _} =\n               BroadcastHandler.handle(\n                 %{\"data\" => random_string(tenant.max_payload_size_in_kb * 1000 + 1)},\n                 db_conn,\n                 socket\n               )\n\n      refute_receive {:socket_push, :text, _}, 120\n    end\n\n    test \"handle payload size excedding limits in public channels\", %{topic: topic, tenant: tenant, db_conn: db_conn} do\n      socket = socket_fixture(tenant, topic, ack_broadcast: false, private?: false)\n\n      assert {:noreply, _} =\n               BroadcastHandler.handle(\n                 %{\"data\" => random_string(tenant.max_payload_size_in_kb * 1000 + 1)},\n                 db_conn,\n                 socket\n               )\n\n      refute_receive {:socket_push, :text, _}, 120\n    end\n\n    test \"handle payload size excedding limits in private channel and if ack it will receive error\", %{\n      topic: topic,\n      tenant: tenant,\n      db_conn: db_conn\n    } do\n      socket =\n        socket_fixture(tenant, topic,\n          policies: %Policies{broadcast: %BroadcastPolicies{write: true}},\n          ack_broadcast: true\n        )\n\n      assert {:reply, {:error, :payload_size_exceeded}, _} =\n               BroadcastHandler.handle(\n                 %{\"data\" => random_string(tenant.max_payload_size_in_kb * 1000 + 1)},\n                 db_conn,\n                 socket\n               )\n\n      refute_receive {:socket_push, :text, _}, 120\n    end\n\n    test \"handle payload size excedding limits in public channels and if ack it will receive error\", %{\n      topic: topic,\n      tenant: tenant,\n      db_conn: db_conn\n    } do\n      socket = socket_fixture(tenant, topic, ack_broadcast: true, private?: false)\n\n      assert {:reply, {:error, :payload_size_exceeded}, _} =\n               BroadcastHandler.handle(\n                 %{\"data\" => random_string(tenant.max_payload_size_in_kb * 1000 + 1)},\n                 db_conn,\n                 socket\n               )\n\n      refute_receive {:socket_push, :text, _}, 120\n    end\n  end\n\n  defp initiate_tenant(context) do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n\n    # Warm cache to avoid Cachex and Ecto.Sandbox ownership issues\n    Realtime.Tenants.Cache.update_cache(tenant)\n\n    rate = Tenants.events_per_second_rate(tenant)\n    RateCounter.new(rate, tick: 100)\n\n    {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n    assert Connect.ready?(tenant.external_id)\n\n    topic = random_string()\n    # Simulate fastlane\n    fastlane =\n      RealtimeWeb.RealtimeChannel.MessageDispatcher.fastlane_metadata(\n        self(),\n        context.serializer,\n        \"realtime:#{topic}\",\n        :warning,\n        \"tenant_id\"\n      )\n\n    Endpoint.subscribe(\"realtime:#{topic}\", metadata: fastlane)\n\n    if policies = context[:policies] do\n      sub = context[:sub]\n      role = context[:role]\n      create_rls_policies(db_conn, policies, %{topic: topic, sub: sub, role: role})\n    end\n\n    %{tenant: tenant, topic: topic, db_conn: db_conn}\n  end\n\n  defp socket_fixture(tenant, topic, opts \\\\ []) do\n    policies = Keyword.get(opts, :policies, %Policies{broadcast: %BroadcastPolicies{write: nil, read: true}})\n    ack_broadcast = Keyword.get(opts, :ack_broadcast, true)\n    private? = Keyword.get(opts, :private?, true)\n\n    default_claims = %{sub: UUID.generate(), role: \"authenticated\", exp: Joken.current_time() + 1_000}\n    claims = Keyword.get(opts, :claims, %{})\n    claims = Map.merge(default_claims, claims)\n\n    signer = Joken.Signer.create(\"HS256\", \"secret\")\n    jwt = Joken.generate_and_sign!(%{}, claims, signer)\n\n    authorization_context =\n      Authorization.build_authorization_params(%{\n        tenant_id: tenant.external_id,\n        topic: topic,\n        jwt: jwt,\n        claims: claims,\n        headers: [{\"header-1\", \"value-1\"}],\n        role: claims.role,\n        sub: claims.sub\n      })\n\n    rate_counter = Tenants.events_per_second_rate(tenant)\n\n    tenant_topic = \"realtime:#{topic}\"\n    self_broadcast = true\n\n    %Phoenix.Socket{\n      assigns: %{\n        tenant_topic: tenant_topic,\n        ack_broadcast: ack_broadcast,\n        self_broadcast: self_broadcast,\n        policies: policies,\n        authorization_context: authorization_context,\n        rate_counter: rate_counter,\n        private?: private?,\n        tenant: tenant.external_id\n      }\n    }\n  end\n\n  defp message(RealtimeWeb.Socket.V2Serializer, topic, payload), do: [nil, nil, topic, \"broadcast\", payload]\n\n  defp message(Phoenix.Socket.V1.JSONSerializer, topic, payload) do\n    %{\"event\" => \"broadcast\", \"payload\" => payload, \"ref\" => nil, \"topic\" => topic}\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/channels/realtime_channel/logging_test.exs",
    "content": "defmodule RealtimeWeb.RealtimeChannel.LoggingTest do\n  # async: false due to changes in Logger levels\n  use Realtime.DataCase, async: false\n  import ExUnit.CaptureLog\n  alias RealtimeWeb.RealtimeChannel.Logging\n\n  def handle_telemetry(event, measures, metadata, pid: pid), do: send(pid, {event, measures, metadata})\n\n  setup do\n    :telemetry.attach(__MODULE__, [:realtime, :channel, :error], &__MODULE__.handle_telemetry/4, pid: self())\n    level = Logger.level()\n    Logger.configure(level: :info)\n    tenant = tenant_fixture()\n\n    on_exit(fn ->\n      :telemetry.detach(__MODULE__)\n      Logger.configure(level: level)\n    end)\n\n    %{tenant: tenant}\n  end\n\n  describe \"log_error/3\" do\n    test \"logs error message with JWT claims in metadata\", %{tenant: tenant} do\n      sub = random_string()\n      exp = System.system_time(:second) + 1000\n      iss = \"https://#{random_string()}.com\"\n      token = generate_jwt_token(tenant, %{sub: sub, exp: exp, iss: iss})\n      socket = %{assigns: %{log_level: :error, tenant: tenant.external_id, access_token: token}}\n\n      log =\n        capture_log(fn ->\n          {:error, %{reason: \"TestError: test error\"}} = Logging.log_error(socket, \"TestError\", \"test error\")\n        end)\n\n      assert log =~ \"TestError: test error\"\n      assert log =~ \"sub=#{sub}\"\n      assert log =~ \"exp=#{exp}\"\n      assert log =~ \"iss=#{iss}\"\n      assert log =~ \"error_code=TestError\"\n    end\n  end\n\n  describe \"log_warning/3\" do\n    test \"logs warning message with JWT claims in metadata\", %{tenant: tenant} do\n      sub = random_string()\n      exp = System.system_time(:second) + 1000\n      iss = \"https://#{random_string()}.com\"\n      token = generate_jwt_token(tenant, %{sub: sub, exp: exp, iss: iss})\n      socket = %{assigns: %{log_level: :warning, tenant: tenant.external_id, access_token: token}}\n\n      log =\n        capture_log(fn ->\n          {:error, %{reason: \"TestWarning: test warning\"}} = Logging.log_warning(socket, \"TestWarning\", \"test warning\")\n        end)\n\n      assert log =~ \"TestWarning: test warning\"\n      assert log =~ \"sub=#{sub}\"\n      assert log =~ \"exp=#{exp}\"\n      assert log =~ \"iss=#{iss}\"\n      assert log =~ \"error_code=TestWarning\"\n    end\n  end\n\n  describe \"maybe_log_error/3\" do\n    test \"logs error message when log_level is less or equal to error\" do\n      log_levels = [:debug, :info, :warning, :error]\n\n      for log_level <- log_levels do\n        socket = %{assigns: %{log_level: log_level, tenant: random_string(), access_token: \"test_token\"}}\n\n        log =\n          capture_log(fn ->\n            assert Logging.maybe_log_error(socket, \"TestCode\", \"test message\") ==\n                     {:error, %{reason: \"TestCode: test message\"}}\n          end)\n\n        assert log =~ \"TestCode: test message\"\n        assert log =~ \"error_code=TestCode\"\n\n        assert capture_log(fn ->\n                 assert Logging.maybe_log_error(socket, \"TestCode\", %{a: \"b\"}) ==\n                          {:error, %{reason: \"TestCode: %{a: \\\"b\\\"}\"}}\n               end) =~ \"TestCode: %{a: \\\"b\\\"}\"\n      end\n    end\n\n    test \"does not log error message when log_level is higher than error\" do\n      socket = %{assigns: %{log_level: :critical, tenant: random_string(), access_token: \"test_token\"}}\n\n      assert capture_log(fn ->\n               assert Logging.maybe_log_error(socket, \"TestCode\", \"test message\") ==\n                        {:error, %{reason: \"TestCode: test message\"}}\n             end) == \"\"\n    end\n\n    test \"also returns {:error, %{reason: msg}} when log_level is error\" do\n      socket = %{assigns: %{log_level: :error, tenant: random_string(), access_token: \"test_token\"}}\n\n      assert Logging.maybe_log_error(socket, \"TestCode\", \"test message\") ==\n               {:error, %{reason: \"TestCode: test message\"}}\n    end\n  end\n\n  describe \"maybe_log_warning/3\" do\n    test \"logs error message when log_level is less or equal to warning\" do\n      log_levels = [:debug, :info, :warning]\n\n      for log_level <- log_levels do\n        socket = %{assigns: %{log_level: log_level, tenant: random_string(), access_token: \"test_token\"}}\n\n        log =\n          capture_log(fn ->\n            assert Logging.maybe_log_warning(socket, \"TestCode\", \"test message\") ==\n                     {:error, %{reason: \"TestCode: test message\"}}\n          end)\n\n        assert log =~ \"TestCode: test message\"\n        assert log =~ \"error_code=TestCode\"\n\n        assert capture_log(fn ->\n                 assert Logging.maybe_log_warning(socket, \"TestCode\", %{a: \"b\"}) ==\n                          {:error, %{reason: \"TestCode: %{a: \\\"b\\\"}\"}}\n               end) =~ \"TestCode: %{a: \\\"b\\\"}\"\n      end\n    end\n\n    test \"does not log error message when log_level is higher than warning\" do\n      socket = %{assigns: %{log_level: :error, tenant: random_string(), access_token: \"test_token\"}}\n\n      assert capture_log(fn ->\n               assert Logging.maybe_log_warning(socket, \"TestCode\", \"test message\") ==\n                        {:error, %{reason: \"TestCode: test message\"}}\n             end) == \"\"\n    end\n\n    test \"also returns {:error, %{reason: msg}} when log_level is warning\" do\n      socket = %{assigns: %{log_level: :warning, tenant: random_string(), access_token: \"test_token\"}}\n\n      assert Logging.maybe_log_warning(socket, \"TestCode\", \"test message\") ==\n               {:error, %{reason: \"TestCode: test message\"}}\n    end\n  end\n\n  describe \"maybe_log_info/3\" do\n    test \"logs error message when log_level is less or equal to info\" do\n      log_levels = [:debug, :info]\n\n      for log_level <- log_levels do\n        socket = %{assigns: %{log_level: log_level, tenant: random_string(), access_token: \"test_token\"}}\n\n        assert capture_log(fn -> :ok = Logging.maybe_log_info(socket, \"test message\") end) =~ \"test message\"\n        assert capture_log(fn -> :ok = Logging.maybe_log_info(socket, %{a: \"b\"}) end) =~ \"%{a: \\\"b\\\"}\"\n      end\n    end\n\n    test \"does not log error message when log_level is higher than info\" do\n      socket = %{assigns: %{log_level: :warning, tenant: random_string(), access_token: \"test_token\"}}\n      assert capture_log(fn -> :ok = Logging.maybe_log_info(socket, \"test message\") end) == \"\"\n    end\n  end\n\n  test \"emits telemetry for system errors\" do\n    socket = %{assigns: %{log_level: :error, tenant: random_string(), access_token: \"test_token\"}}\n\n    for error <- Logging.system_errors() do\n      assert Logging.maybe_log_error(socket, error, \"test error\") ==\n               {:error, %{reason: \"#{error}: test error\"}}\n\n      assert_receive {[:realtime, :channel, :error], %{code: ^error}, %{code: ^error}}\n    end\n\n    assert Logging.maybe_log_error(socket, \"TestError\", \"test error\") ==\n             {:error, %{reason: \"TestError: test error\"}}\n\n    refute_receive {[:realtime, :channel, :error], :_, :_}\n  end\n\n  test \"logs include JWT claims in metadata\", %{tenant: tenant} do\n    sub = random_string()\n    exp = System.system_time(:second) + 1000\n    iss = \"https://#{random_string()}.com\"\n    token = generate_jwt_token(tenant, %{sub: sub, exp: exp, iss: iss})\n    socket = %{assigns: %{log_level: :error, tenant: tenant.external_id, access_token: token}}\n    log = capture_log(fn -> Logging.maybe_log_error(socket, \"TestError\", \"test error\") end)\n    assert log =~ \"sub=#{sub}\"\n    assert log =~ \"exp=#{exp}\"\n    assert log =~ \"iss=#{iss}\"\n  end\n\n  test \"logs include project metadata\" do\n    tenant_id = random_string()\n    socket = %{assigns: %{log_level: :error, tenant: tenant_id, access_token: \"test_token\"}}\n\n    log = capture_log(fn -> Logging.maybe_log_error(socket, \"TestError\", \"test error\") end)\n    assert log =~ tenant_id\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/channels/realtime_channel/message_dispatcher_test.exs",
    "content": "defmodule RealtimeWeb.RealtimeChannel.MessageDispatcherTest do\n  use ExUnit.Case, async: true\n\n  import ExUnit.CaptureLog\n\n  alias Phoenix.Socket.Broadcast\n  alias Phoenix.Socket.V1\n  alias RealtimeWeb.RealtimeChannel.MessageDispatcher\n  alias RealtimeWeb.Socket.UserBroadcast\n  alias RealtimeWeb.Socket.V2Serializer\n\n  defmodule TestSerializer do\n    def fastlane!(msg) do\n      Agent.update(TestSerializer, fn count -> count + 1 end)\n      {:encoded, msg}\n    end\n  end\n\n  describe \"fastlane_metadata/5\" do\n    test \"info level\" do\n      assert MessageDispatcher.fastlane_metadata(self(), Serializer, \"realtime:topic\", :info, \"tenant_id\") ==\n               {:rc_fastlane, self(), Serializer, \"realtime:topic\", :info, \"tenant_id\", MapSet.new()}\n    end\n\n    test \"non-info level\" do\n      assert MessageDispatcher.fastlane_metadata(self(), Serializer, \"realtime:topic\", :warning, \"tenant_id\") ==\n               {:rc_fastlane, self(), Serializer, \"realtime:topic\", :warning, \"tenant_id\", MapSet.new()}\n    end\n\n    test \"replayed message ids\" do\n      assert MessageDispatcher.fastlane_metadata(\n               self(),\n               Serializer,\n               \"realtime:topic\",\n               :warning,\n               \"tenant_id\",\n               MapSet.new([1])\n             ) ==\n               {:rc_fastlane, self(), Serializer, \"realtime:topic\", :warning, \"tenant_id\", MapSet.new([1])}\n    end\n  end\n\n  describe \"dispatch/3\" do\n    setup do\n      {:ok, _pid} =\n        start_supervised(%{\n          id: TestSerializer,\n          start: {Agent, :start_link, [fn -> 0 end, [name: TestSerializer]]}\n        })\n\n      :ok\n    end\n\n    test \"dispatches messages to fastlane subscribers\" do\n      parent = self()\n\n      subscriber_pid =\n        spawn(fn ->\n          loop = fn loop ->\n            receive do\n              msg ->\n                send(parent, {:subscriber, msg})\n                loop.(loop)\n            end\n          end\n\n          loop.(loop)\n        end)\n\n      from_pid = :erlang.list_to_pid(~c'<0.2.1>')\n\n      subscribers = [\n        {subscriber_pid, {:rc_fastlane, self(), TestSerializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}},\n        {subscriber_pid, {:rc_fastlane, self(), TestSerializer, \"realtime:topic\", :warning, \"tenant123\", MapSet.new()}}\n      ]\n\n      msg = %Broadcast{topic: \"some:other:topic\", event: \"event\", payload: %{data: \"test\"}}\n\n      log =\n        capture_log(fn ->\n          assert MessageDispatcher.dispatch(subscribers, from_pid, msg) == :ok\n        end)\n\n      assert log =~ \"Received message on realtime:topic with payload: #{inspect(msg, pretty: true)}\"\n\n      assert_receive {:encoded, %Broadcast{event: \"event\", payload: %{data: \"test\"}, topic: \"realtime:topic\"}}\n      assert_receive {:encoded, %Broadcast{event: \"event\", payload: %{data: \"test\"}, topic: \"realtime:topic\"}}\n\n      assert Agent.get(TestSerializer, & &1) == 1\n\n      assert_receive {:subscriber, :update_rate_counter}\n      assert_receive {:subscriber, :update_rate_counter}\n\n      refute_receive _any\n    end\n\n    test \"dispatches 'presence_diff' messages to fastlane subscribers\" do\n      parent = self()\n\n      subscriber_pid =\n        spawn(fn ->\n          loop = fn loop ->\n            receive do\n              msg ->\n                send(parent, {:subscriber, msg})\n                loop.(loop)\n            end\n          end\n\n          loop.(loop)\n        end)\n\n      from_pid = :erlang.list_to_pid(~c'<0.2.1>')\n\n      subscribers = [\n        {subscriber_pid, {:rc_fastlane, self(), TestSerializer, \"realtime:topic\", :info, \"tenant456\", MapSet.new()}},\n        {subscriber_pid, {:rc_fastlane, self(), TestSerializer, \"realtime:topic\", :warning, \"tenant456\", MapSet.new()}}\n      ]\n\n      msg = %Broadcast{topic: \"some:other:topic\", event: \"presence_diff\", payload: %{data: \"test\"}}\n\n      log =\n        capture_log(fn ->\n          assert MessageDispatcher.dispatch(subscribers, from_pid, msg) == :ok\n        end)\n\n      assert log =~ \"Received message on realtime:topic with payload: #{inspect(msg, pretty: true)}\"\n\n      assert_receive {:encoded, %Broadcast{event: \"presence_diff\", payload: %{data: \"test\"}, topic: \"realtime:topic\"}}\n      assert_receive {:encoded, %Broadcast{event: \"presence_diff\", payload: %{data: \"test\"}, topic: \"realtime:topic\"}}\n\n      assert Agent.get(TestSerializer, & &1) == 1\n\n      assert Realtime.GenCounter.get(Realtime.Tenants.presence_events_per_second_key(\"tenant456\")) == 2\n\n      refute_receive _any\n    end\n\n    test \"does not dispatch messages to fastlane subscribers if they already replayed it\" do\n      parent = self()\n\n      subscriber_pid =\n        spawn(fn ->\n          loop = fn loop ->\n            receive do\n              msg ->\n                send(parent, {:subscriber, msg})\n                loop.(loop)\n            end\n          end\n\n          loop.(loop)\n        end)\n\n      from_pid = :erlang.list_to_pid(~c'<0.2.1>')\n      replaeyd_message_ids = MapSet.new([\"123\"])\n\n      subscribers = [\n        {subscriber_pid,\n         {:rc_fastlane, self(), TestSerializer, \"realtime:topic\", :info, \"tenant123\", replaeyd_message_ids}},\n        {subscriber_pid,\n         {:rc_fastlane, self(), TestSerializer, \"realtime:topic\", :warning, \"tenant123\", replaeyd_message_ids}}\n      ]\n\n      msg = %Broadcast{\n        topic: \"some:other:topic\",\n        event: \"event\",\n        payload: %{\"data\" => \"test\", \"meta\" => %{\"id\" => \"123\"}}\n      }\n\n      assert MessageDispatcher.dispatch(subscribers, from_pid, msg) == :ok\n\n      assert Agent.get(TestSerializer, & &1) == 0\n\n      refute_receive _any\n    end\n\n    test \"payload is not a map\" do\n      parent = self()\n\n      subscriber_pid =\n        spawn(fn ->\n          loop = fn loop ->\n            receive do\n              msg ->\n                send(parent, {:subscriber, msg})\n                loop.(loop)\n            end\n          end\n\n          loop.(loop)\n        end)\n\n      from_pid = :erlang.list_to_pid(~c'<0.2.1>')\n\n      subscribers = [\n        {subscriber_pid, {:rc_fastlane, self(), TestSerializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}},\n        {subscriber_pid, {:rc_fastlane, self(), TestSerializer, \"realtime:topic\", :warning, \"tenant123\", MapSet.new()}}\n      ]\n\n      msg = %Broadcast{topic: \"some:other:topic\", event: \"event\", payload: \"not a map\"}\n\n      log =\n        capture_log(fn ->\n          assert MessageDispatcher.dispatch(subscribers, from_pid, msg) == :ok\n        end)\n\n      assert log =~ \"Received message on realtime:topic with payload: #{inspect(msg, pretty: true)}\"\n\n      assert_receive {:encoded, %Broadcast{event: \"event\", payload: \"not a map\", topic: \"realtime:topic\"}}\n      assert_receive {:encoded, %Broadcast{event: \"event\", payload: \"not a map\", topic: \"realtime:topic\"}}\n\n      assert Agent.get(TestSerializer, & &1) == 1\n\n      assert_receive {:subscriber, :update_rate_counter}\n      assert_receive {:subscriber, :update_rate_counter}\n\n      refute_receive _any\n    end\n\n    test \"dispatches messages to non fastlane subscribers\" do\n      from_pid = :erlang.list_to_pid(~c'<0.2.1>')\n\n      subscribers = [\n        {self(), :not_fastlane},\n        {self(), :not_fastlane}\n      ]\n\n      msg = %Broadcast{topic: \"some:other:topic\", event: \"event\", payload: %{data: \"test\"}}\n\n      assert MessageDispatcher.dispatch(subscribers, from_pid, msg) == :ok\n\n      assert_receive %Phoenix.Socket.Broadcast{topic: \"some:other:topic\", event: \"event\", payload: %{data: \"test\"}}\n      assert_receive %Phoenix.Socket.Broadcast{topic: \"some:other:topic\", event: \"event\", payload: %{data: \"test\"}}\n\n      # TestSerializer is not called\n      assert Agent.get(TestSerializer, & &1) == 0\n    end\n\n    test \"dispatches Broadcast to V1 & V2 Serializers\" do\n      parent = self()\n\n      subscriber_pid =\n        spawn(fn ->\n          loop = fn loop ->\n            receive do\n              msg ->\n                send(parent, {:subscriber, msg})\n                loop.(loop)\n            end\n          end\n\n          loop.(loop)\n        end)\n\n      from_pid = :erlang.list_to_pid(~c'<0.2.1>')\n\n      subscribers = [\n        {subscriber_pid, {:rc_fastlane, self(), V1.JSONSerializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}},\n        {subscriber_pid, {:rc_fastlane, self(), V1.JSONSerializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}},\n        {subscriber_pid, {:rc_fastlane, self(), V2Serializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}},\n        {subscriber_pid, {:rc_fastlane, self(), V2Serializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}}\n      ]\n\n      msg = %Broadcast{topic: \"some:other:topic\", event: \"event\", payload: %{data: \"test\"}}\n\n      log =\n        capture_log(fn ->\n          assert MessageDispatcher.dispatch(subscribers, from_pid, msg) == :ok\n        end)\n\n      assert log =~ \"Received message on realtime:topic with payload: #{inspect(msg, pretty: true)}\"\n\n      # Receive 2 messages using V1\n      assert_receive {:socket_push, :text, message_v1}\n      assert_receive {:socket_push, :text, ^message_v1}\n\n      assert Jason.decode!(message_v1) == %{\n               \"event\" => \"event\",\n               \"payload\" => %{\"data\" => \"test\"},\n               \"ref\" => nil,\n               \"topic\" => \"realtime:topic\"\n             }\n\n      # Receive 2 messages using V2\n      assert_receive {:socket_push, :text, message_v2}\n      assert_receive {:socket_push, :text, ^message_v2}\n\n      # V2 is an array format\n      assert Jason.decode!(message_v2) == [nil, nil, \"realtime:topic\", \"event\", %{\"data\" => \"test\"}]\n\n      assert_receive {:subscriber, :update_rate_counter}\n      assert_receive {:subscriber, :update_rate_counter}\n      assert_receive {:subscriber, :update_rate_counter}\n      assert_receive {:subscriber, :update_rate_counter}\n\n      refute_receive _any\n    end\n\n    test \"dispatches json UserBroadcast to V1 & V2 Serializers\" do\n      parent = self()\n\n      subscriber_pid =\n        spawn(fn ->\n          loop = fn loop ->\n            receive do\n              msg ->\n                send(parent, {:subscriber, msg})\n                loop.(loop)\n            end\n          end\n\n          loop.(loop)\n        end)\n\n      from_pid = :erlang.list_to_pid(~c'<0.2.1>')\n\n      subscribers = [\n        {subscriber_pid, {:rc_fastlane, self(), V1.JSONSerializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}},\n        {subscriber_pid, {:rc_fastlane, self(), V1.JSONSerializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}},\n        {subscriber_pid, {:rc_fastlane, self(), V2Serializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}},\n        {subscriber_pid, {:rc_fastlane, self(), V2Serializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}}\n      ]\n\n      user_payload = Jason.encode!(%{data: \"test\"})\n\n      msg = %UserBroadcast{\n        topic: \"some:other:topic\",\n        user_event: \"event123\",\n        user_payload: user_payload,\n        user_payload_encoding: :json,\n        metadata: %{\"id\" => \"123\", \"replayed\" => true}\n      }\n\n      log =\n        capture_log(fn ->\n          assert MessageDispatcher.dispatch(subscribers, from_pid, msg) == :ok\n        end)\n\n      assert log =~ \"Received message on realtime:topic with payload: #{inspect(msg, pretty: true)}\"\n\n      # Receive 2 messages using V1\n      assert_receive {:socket_push, :text, message_v1}\n      assert_receive {:socket_push, :text, ^message_v1}\n\n      assert Jason.decode!(message_v1) == %{\n               \"event\" => \"broadcast\",\n               \"payload\" => %{\n                 \"event\" => \"event123\",\n                 \"meta\" => %{\"id\" => \"123\", \"replayed\" => true},\n                 \"payload\" => %{\"data\" => \"test\"},\n                 \"type\" => \"broadcast\"\n               },\n               \"ref\" => nil,\n               \"topic\" => \"realtime:topic\"\n             }\n\n      # Receive 2 messages using V2\n      assert_receive {:socket_push, :binary, message_v2}\n      assert_receive {:socket_push, :binary, ^message_v2}\n\n      encoded_metadata = Jason.encode!(%{\"id\" => \"123\", \"replayed\" => true})\n      metadata_size = byte_size(encoded_metadata)\n\n      # binary payload structure\n      assert message_v2 ==\n               <<\n                 # user broadcast = 4\n                 4::size(8),\n                 # topic_size\n                 14,\n                 # user_event_size\n                 8,\n                 # metadata_size\n                 metadata_size,\n                 # json encoding\n                 1::size(8),\n                 \"realtime:topic\",\n                 \"event123\"\n               >> <> encoded_metadata <> user_payload\n\n      assert_receive {:subscriber, :update_rate_counter}\n      assert_receive {:subscriber, :update_rate_counter}\n      assert_receive {:subscriber, :update_rate_counter}\n      assert_receive {:subscriber, :update_rate_counter}\n\n      refute_receive _any\n    end\n\n    test \"dispatches binary UserBroadcast to V1 & V2 Serializers\" do\n      parent = self()\n\n      subscriber_pid =\n        spawn(fn ->\n          loop = fn loop ->\n            receive do\n              msg ->\n                send(parent, {:subscriber, msg})\n                loop.(loop)\n            end\n          end\n\n          loop.(loop)\n        end)\n\n      from_pid = :erlang.list_to_pid(~c'<0.2.1>')\n\n      subscribers = [\n        {subscriber_pid, {:rc_fastlane, self(), V1.JSONSerializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}},\n        {subscriber_pid, {:rc_fastlane, self(), V1.JSONSerializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}},\n        {subscriber_pid, {:rc_fastlane, self(), V2Serializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}},\n        {subscriber_pid, {:rc_fastlane, self(), V2Serializer, \"realtime:topic\", :info, \"tenant123\", MapSet.new()}}\n      ]\n\n      user_payload = <<123, 456, 789>>\n\n      msg = %UserBroadcast{\n        topic: \"some:other:topic\",\n        user_event: \"event123\",\n        user_payload: user_payload,\n        user_payload_encoding: :binary,\n        metadata: %{\"id\" => \"123\", \"replayed\" => true}\n      }\n\n      log =\n        capture_log(fn ->\n          assert MessageDispatcher.dispatch(subscribers, from_pid, msg) == :ok\n        end)\n\n      assert log =~ \"Received message on realtime:topic with payload: #{inspect(msg, pretty: true)}\"\n      assert log =~ \"User payload encoding is not JSON\"\n\n      # Only prints once\n      assert String.split(log, \"User payload encoding is not JSON\") |> length() == 2\n\n      # No V1 message received as binary payloads are not supported\n      refute_receive {:socket_push, :text, _message_v1}\n\n      # Receive 2 messages using V2\n      assert_receive {:socket_push, :binary, message_v2}\n      assert_receive {:socket_push, :binary, ^message_v2}\n\n      encoded_metadata = Jason.encode!(%{\"id\" => \"123\", \"replayed\" => true})\n      metadata_size = byte_size(encoded_metadata)\n\n      # binary payload structure\n      assert message_v2 ==\n               <<\n                 # user broadcast = 4\n                 4::size(8),\n                 # topic_size\n                 14,\n                 # user_event_size\n                 8,\n                 # metadata_size\n                 metadata_size,\n                 # binary encoding\n                 0::size(8),\n                 \"realtime:topic\",\n                 \"event123\"\n               >> <> encoded_metadata <> user_payload\n\n      assert_receive {:subscriber, :update_rate_counter}\n      assert_receive {:subscriber, :update_rate_counter}\n      assert_receive {:subscriber, :update_rate_counter}\n      assert_receive {:subscriber, :update_rate_counter}\n\n      refute_receive _any\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/channels/realtime_channel/presence_handler_test.exs",
    "content": "defmodule RealtimeWeb.RealtimeChannel.PresenceHandlerTest do\n  use Realtime.DataCase, async: true\n  use Mimic\n\n  import ExUnit.CaptureLog\n  import Generators\n\n  alias Phoenix.Socket.Broadcast\n  alias Realtime.RateCounter\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Authorization\n  alias Realtime.Tenants.Authorization.Policies\n  alias Realtime.Tenants.Authorization.Policies.BroadcastPolicies\n  alias Realtime.Tenants.Authorization.Policies.PresencePolicies\n  alias Realtime.Tenants.Connect\n  alias RealtimeWeb.Endpoint\n  alias RealtimeWeb.RealtimeChannel.PresenceHandler\n\n  setup [:initiate_tenant]\n\n  describe \"is_private?/1\" do\n    defmodule TestIsPrivate do\n      import RealtimeWeb.RealtimeChannel.PresenceHandler\n\n      def check(socket) when is_private?(socket), do: true\n      def check(_socket), do: false\n    end\n\n    test \"returns true if the socket is a private channel\", %{tenant: tenant} do\n      socket = socket_fixture(tenant, random_string(), random_string(), private?: true)\n      assert TestIsPrivate.check(socket)\n    end\n\n    test \"returns false if the socket is a public channel\", %{tenant: tenant} do\n      socket = socket_fixture(tenant, random_string(), random_string(), private?: false)\n      refute TestIsPrivate.check(socket)\n    end\n  end\n\n  describe \"can_read_presence?/1\" do\n    defmodule TestCanReadPresence do\n      import RealtimeWeb.RealtimeChannel.PresenceHandler\n\n      def check(socket) when can_read_presence?(socket), do: true\n      def check(_socket), do: false\n    end\n\n    test \"returns true if the socket is a private channel and the presence read policy is true\", %{tenant: tenant} do\n      policies = %Policies{presence: %PresencePolicies{read: true}}\n      socket = socket_fixture(tenant, random_string(), random_string(), policies: policies, private?: true)\n      assert TestCanReadPresence.check(socket)\n    end\n\n    test \"returns false if the socket is a private channel and the presence read policy is false\", %{tenant: tenant} do\n      policies = %Policies{presence: %PresencePolicies{read: false}}\n      socket = socket_fixture(tenant, random_string(), random_string(), policies: policies, private?: true)\n      refute TestCanReadPresence.check(socket)\n    end\n\n    test \"returns false if the socket is a public channel \", %{tenant: tenant} do\n      policies = %Policies{presence: %PresencePolicies{read: true}}\n      socket = socket_fixture(tenant, random_string(), random_string(), policies: policies, private?: false)\n      refute TestCanReadPresence.check(socket)\n\n      policies = %Policies{presence: %PresencePolicies{read: false}}\n      socket = socket_fixture(tenant, random_string(), random_string(), policies: policies, private?: false)\n      refute TestCanReadPresence.check(socket)\n    end\n  end\n\n  describe \"can_write_presence?/1\" do\n    defmodule TestCanWritePresence do\n      import RealtimeWeb.RealtimeChannel.PresenceHandler\n\n      def check(socket) when can_write_presence?(socket), do: true\n      def check(_socket), do: false\n    end\n\n    test \"returns true if the socket is a private channel and the presence write policy is true\", %{tenant: tenant} do\n      policies = %Policies{presence: %PresencePolicies{write: true}}\n      socket = socket_fixture(tenant, random_string(), random_string(), policies: policies, private?: true)\n      assert TestCanWritePresence.check(socket)\n    end\n\n    test \"returns false if the socket is a private channel and the presence write policy is false\", %{tenant: tenant} do\n      policies = %Policies{presence: %PresencePolicies{write: false}}\n      socket = socket_fixture(tenant, random_string(), random_string(), policies: policies, private?: true)\n      refute TestCanWritePresence.check(socket)\n    end\n\n    test \"returns false if the socket is a public channel and the presence write does not matter\", %{tenant: tenant} do\n      policies = %Policies{presence: %PresencePolicies{write: true}}\n      socket = socket_fixture(tenant, random_string(), random_string(), policies: policies, private?: false)\n      refute TestCanWritePresence.check(socket)\n\n      policies = %Policies{presence: %PresencePolicies{write: false}}\n      socket = socket_fixture(tenant, random_string(), random_string(), policies: policies, private?: false)\n      refute TestCanWritePresence.check(socket)\n    end\n  end\n\n  describe \"handle/3\" do\n    setup %{tenant: tenant} do\n      on_exit(fn -> :telemetry.detach(__MODULE__) end)\n\n      :telemetry.attach(\n        __MODULE__,\n        [:realtime, :tenants, :payload, :size],\n        &__MODULE__.handle_telemetry/4,\n        %{pid: self(), tenant: tenant}\n      )\n    end\n\n    test \"with true policy and is private, user can track their presence and changes\", %{\n      tenant: tenant,\n      topic: topic,\n      db_conn: db_conn\n    } do\n      external_id = tenant.external_id\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n\n      socket =\n        socket_fixture(tenant, topic, key, policies: policies)\n\n      PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"A\" => \"b\", \"c\" => \"b\"}}, db_conn, socket)\n      topic = socket.assigns.tenant_topic\n\n      assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\", payload: %{joins: joins, leaves: %{}}}\n      assert Map.has_key?(joins, key)\n\n      assert_receive {:telemetry, [:realtime, :tenants, :payload, :size], %{size: 30},\n                      %{tenant: ^external_id, message_type: :presence}}\n    end\n\n    test \"when tracking already existing user, metadata updated\", %{tenant: tenant, topic: topic, db_conn: db_conn} do\n      external_id = tenant.external_id\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies)\n\n      assert {:ok, socket} = PresenceHandler.handle(%{\"event\" => \"track\"}, db_conn, socket)\n\n      topic = socket.assigns.tenant_topic\n      assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\", payload: %{joins: joins, leaves: %{}}}\n      assert Map.has_key?(joins, key)\n\n      payload = %{\"event\" => \"track\", \"payload\" => %{\"content\" => random_string()}}\n      assert {:ok, _socket} = PresenceHandler.handle(payload, db_conn, socket)\n\n      assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\", payload: %{joins: joins, leaves: %{}}}\n      assert Map.has_key?(joins, key)\n\n      assert_receive {:telemetry, [:realtime, :tenants, :payload, :size], %{size: 6},\n                      %{tenant: ^external_id, message_type: :presence}}\n\n      assert_receive {:telemetry, [:realtime, :tenants, :payload, :size], %{size: 55},\n                      %{tenant: ^external_id, message_type: :presence}}\n\n      refute_receive _\n    end\n\n    test \"tracking the same payload does nothing\", %{tenant: tenant, topic: topic, db_conn: db_conn} do\n      external_id = tenant.external_id\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies)\n\n      assert {:ok, socket} = PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"a\" => \"b\"}}, db_conn, socket)\n\n      assert_receive {:telemetry, [:realtime, :tenants, :payload, :size], %{size: 18},\n                      %{tenant: ^external_id, message_type: :presence}}\n\n      topic = socket.assigns.tenant_topic\n      assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\", payload: %{joins: joins, leaves: %{}}}\n      assert Map.has_key?(joins, key)\n\n      assert {:ok, _socket} =\n               PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"a\" => \"b\"}}, db_conn, socket)\n\n      refute_receive _\n    end\n\n    test \"tracking, untracking and then tracking the same payload emit events\", context do\n      %{tenant: tenant, topic: topic, db_conn: db_conn} = context\n      external_id = tenant.external_id\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies)\n\n      assert {:ok, socket} = PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"a\" => \"b\"}}, db_conn, socket)\n      assert socket.assigns.presence_track_payload == %{\"a\" => \"b\"}\n\n      assert_receive {:telemetry, [:realtime, :tenants, :payload, :size], %{size: 18},\n                      %{tenant: ^external_id, message_type: :presence}}\n\n      topic = socket.assigns.tenant_topic\n      assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\", payload: %{joins: joins, leaves: %{}}}\n      assert %{^key => %{metas: [%{:phx_ref => _, \"a\" => \"b\"}]}} = joins\n\n      assert {:ok, socket} = PresenceHandler.handle(%{\"event\" => \"untrack\"}, db_conn, socket)\n      assert socket.assigns.presence_track_payload == nil\n\n      assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\", payload: %{joins: %{}, leaves: leaves}}\n      assert %{^key => %{metas: [%{:phx_ref => _, \"a\" => \"b\"}]}} = leaves\n\n      assert {:ok, socket} = PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"a\" => \"b\"}}, db_conn, socket)\n\n      assert socket.assigns.presence_track_payload == %{\"a\" => \"b\"}\n\n      assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\", payload: %{joins: joins, leaves: %{}}}\n      assert %{^key => %{metas: [%{:phx_ref => _, \"a\" => \"b\"}]}} = joins\n\n      assert_receive {:telemetry, [:realtime, :tenants, :payload, :size], %{size: 18},\n                      %{tenant: ^external_id, message_type: :presence}}\n\n      refute_receive _\n    end\n\n    test \"with false policy and is public, user can track their presence and changes\", %{tenant: tenant, topic: topic} do\n      external_id = tenant.external_id\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: false, write: false}}\n      socket = socket_fixture(tenant, topic, key, policies: policies, private?: false)\n\n      assert {:ok, _socket} = PresenceHandler.handle(%{\"event\" => \"track\"}, nil, socket)\n\n      topic = socket.assigns.tenant_topic\n      assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\", payload: %{joins: joins, leaves: %{}}}\n      assert Map.has_key?(joins, key)\n\n      assert_receive {:telemetry, [:realtime, :tenants, :payload, :size], %{size: 6},\n                      %{tenant: ^external_id, message_type: :presence}}\n    end\n\n    test \"user can untrack when they want\", %{tenant: tenant, topic: topic, db_conn: db_conn} do\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies)\n\n      assert {:ok, socket} = PresenceHandler.handle(%{\"event\" => \"track\"}, db_conn, socket)\n\n      topic = socket.assigns.tenant_topic\n      assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\", payload: %{joins: joins, leaves: %{}}}\n      assert Map.has_key?(joins, key)\n\n      assert {:ok, _socket} = PresenceHandler.handle(%{\"event\" => \"untrack\"}, db_conn, socket)\n      assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\", payload: %{joins: %{}, leaves: leaves}}\n      assert Map.has_key?(leaves, key)\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"only checks write policies once on private channels\", %{tenant: tenant, topic: topic, db_conn: db_conn} do\n      expect(Authorization, :get_write_authorizations, 1, fn conn, db_conn, auth_context ->\n        call_original(Authorization, :get_write_authorizations, [conn, db_conn, auth_context])\n      end)\n\n      reject(&Authorization.get_write_authorizations/3)\n\n      key = random_string()\n      # Use high client rate limit to test tenant-level rate limiting\n      client_rate_limit = %{max_calls: 1000, window_ms: 60_000, counter: 0, reset_at: nil}\n      socket = socket_fixture(tenant, topic, key, client_rate_limit: client_rate_limit)\n      topic = socket.assigns.tenant_topic\n\n      for _ <- 1..300, reduce: socket do\n        socket ->\n          assert {:ok, socket} =\n                   PresenceHandler.handle(\n                     %{\"event\" => \"track\", \"payload\" => %{\"metadata\" => random_string()}},\n                     db_conn,\n                     socket\n                   )\n\n          assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\"}\n          socket\n      end\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :broken_write_presence]\n    test \"handle failing rls policy\", %{tenant: tenant, topic: topic, db_conn: db_conn} do\n      expect(Authorization, :get_write_authorizations, 1, fn conn, db_conn, auth_context ->\n        call_original(Authorization, :get_write_authorizations, [conn, db_conn, auth_context])\n      end)\n\n      key = random_string()\n      socket = socket_fixture(tenant, topic, key)\n      topic = socket.assigns.tenant_topic\n\n      log =\n        capture_log(fn ->\n          assert {:error, :rls_policy_error} =\n                   PresenceHandler.handle(\n                     %{\"event\" => \"track\", \"payload\" => %{\"metadata\" => random_string()}},\n                     db_conn,\n                     socket\n                   )\n\n          refute_receive %Broadcast{topic: ^topic, event: \"presence_diff\"}, 1000\n        end)\n\n      assert log =~ \"RlsPolicyError\"\n    end\n\n    test \"does not check write policies once on public channels\", %{tenant: tenant, topic: topic} do\n      reject(&Authorization.get_write_authorizations/3)\n\n      key = random_string()\n      policies = %Policies{broadcast: %BroadcastPolicies{read: false}}\n      # Use high client rate limit to test tenant-level rate limiting\n      client_rate_limit = %{max_calls: 1000, window_ms: 60_000, counter: 0, reset_at: nil}\n\n      socket =\n        socket_fixture(tenant, topic, key, policies: policies, private?: false, client_rate_limit: client_rate_limit)\n\n      topic = socket.assigns.tenant_topic\n\n      for _ <- 1..300, reduce: socket do\n        socket ->\n          assert {:ok, socket} =\n                   PresenceHandler.handle(\n                     %{\"event\" => \"track\", \"payload\" => %{\"metadata\" => random_string()}},\n                     nil,\n                     socket\n                   )\n\n          assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\"}\n          socket\n      end\n    end\n\n    test \"logs out non recognized events\" do\n      tenant = tenant_fixture()\n\n      socket =\n        socket_fixture(tenant, \"topic\", \"presence_key\",\n          private?: false,\n          client_rate_limit: %{max_calls: 1000, window_ms: 60_000, counter: 0, reset_at: nil}\n        )\n\n      log =\n        capture_log(fn ->\n          assert {:error, :unknown_presence_event} = PresenceHandler.handle(%{\"event\" => \"unknown\"}, nil, socket)\n        end)\n\n      assert log =~ \"UnknownPresenceEvent\"\n    end\n\n    test \"socket with presence enabled false will ignore non-track presence events in public channel\", %{\n      tenant: tenant,\n      topic: topic\n    } do\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies, private?: false, enabled?: false)\n\n      assert {:ok, _socket} = PresenceHandler.handle(%{\"event\" => \"untrack\"}, nil, socket)\n      topic = socket.assigns.tenant_topic\n      refute_receive %Broadcast{topic: ^topic, event: \"presence_diff\"}\n    end\n\n    test \"socket with presence enabled false will ignore non-track presence events in private channel\", %{\n      tenant: tenant,\n      topic: topic,\n      db_conn: db_conn\n    } do\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies, private?: false, enabled?: false)\n\n      assert {:ok, _socket} = PresenceHandler.handle(%{\"event\" => \"untrack\"}, db_conn, socket)\n      topic = socket.assigns.tenant_topic\n      refute_receive %Broadcast{topic: ^topic, event: \"presence_diff\"}\n    end\n\n    test \"socket with presence disabled will enable presence on track message for public channel\", %{\n      tenant: tenant,\n      topic: topic\n    } do\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies, private?: false, enabled?: false)\n\n      refute socket.assigns.presence_enabled?\n\n      assert {:ok, updated_socket} = PresenceHandler.handle(%{\"event\" => \"track\"}, nil, socket)\n\n      assert updated_socket.assigns.presence_enabled?\n      topic = socket.assigns.tenant_topic\n      assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\", payload: %{joins: joins, leaves: %{}}}\n      assert Map.has_key?(joins, key)\n    end\n\n    test \"socket with presence disabled will enable presence on track message for private channel\", %{\n      tenant: tenant,\n      topic: topic,\n      db_conn: db_conn\n    } do\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies, private?: true, enabled?: false)\n\n      refute socket.assigns.presence_enabled?\n\n      assert {:ok, updated_socket} = PresenceHandler.handle(%{\"event\" => \"track\"}, db_conn, socket)\n\n      assert updated_socket.assigns.presence_enabled?\n      topic = socket.assigns.tenant_topic\n      assert_receive %Broadcast{topic: ^topic, event: \"presence_diff\", payload: %{joins: joins, leaves: %{}}}\n      assert Map.has_key?(joins, key)\n    end\n\n    test \"socket with presence disabled will not enable presence on untrack message\", %{\n      tenant: tenant,\n      topic: topic,\n      db_conn: db_conn\n    } do\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies, enabled?: false)\n\n      refute socket.assigns.presence_enabled?\n\n      assert {:ok, updated_socket} = PresenceHandler.handle(%{\"event\" => \"untrack\"}, db_conn, socket)\n\n      refute updated_socket.assigns.presence_enabled?\n      topic = socket.assigns.tenant_topic\n      refute_receive %Broadcast{topic: ^topic, event: \"presence_diff\"}\n    end\n\n    test \"socket with presence disabled will not enable presence on unknown event\", %{\n      tenant: tenant,\n      topic: topic,\n      db_conn: db_conn\n    } do\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies, enabled?: false)\n\n      refute socket.assigns.presence_enabled?\n\n      assert {:error, :unknown_presence_event} = PresenceHandler.handle(%{\"event\" => \"unknown\"}, db_conn, socket)\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"rate limit is checked on private channel\", %{tenant: tenant, topic: topic, db_conn: db_conn} do\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies, private?: true)\n\n      log =\n        capture_log(fn ->\n          for _ <- 1..300, do: PresenceHandler.handle(%{\"event\" => \"track\"}, db_conn, socket)\n\n          {:ok, _} = RateCounterHelper.tick!(Tenants.presence_events_per_second_rate(tenant))\n\n          assert {:error, :rate_limit_exceeded} = PresenceHandler.handle(%{\"event\" => \"track\"}, db_conn, socket)\n        end)\n\n      assert log =~ \"PresenceRateLimitReached\"\n    end\n\n    test \"rate limit is checked on public channel\", %{tenant: tenant, topic: topic, db_conn: db_conn} do\n      key = random_string()\n      socket = socket_fixture(tenant, topic, key, private?: false)\n\n      log =\n        capture_log(fn ->\n          for _ <- 1..300, do: PresenceHandler.handle(%{\"event\" => \"track\"}, db_conn, socket)\n\n          {:ok, _} = RateCounterHelper.tick!(Tenants.presence_events_per_second_rate(tenant))\n\n          assert {:error, :rate_limit_exceeded} = PresenceHandler.handle(%{\"event\" => \"track\"}, db_conn, socket)\n        end)\n\n      assert log =~ \"PresenceRateLimitReached\"\n    end\n\n    test \"fails on high payload size\", %{tenant: tenant, topic: topic, db_conn: db_conn} do\n      key = random_string()\n      socket = socket_fixture(tenant, topic, key, private?: false)\n      payload_size = tenant.max_payload_size_in_kb * 1000\n\n      payload = %{content: random_string(payload_size)}\n\n      assert {:error, :payload_size_exceeded} =\n               PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => payload}, db_conn, socket)\n    end\n  end\n\n  describe \"sync/1\" do\n    test \"syncs presence state for public channels\", %{tenant: tenant, topic: topic} do\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: false, write: false}}\n      socket = socket_fixture(tenant, topic, key, policies: policies, private?: false)\n\n      assert :ok = PresenceHandler.sync(socket)\n      assert_receive {_, :text, msg}\n      msg = Jason.decode!(msg)\n      assert msg[\"event\"] == \"presence_state\"\n    end\n\n    test \"syncs presence state for private channels with read policy true\", %{tenant: tenant, topic: topic} do\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies, private?: true)\n\n      assert :ok = PresenceHandler.sync(socket)\n      assert_receive {_, :text, msg}\n      msg = Jason.decode!(msg)\n      assert msg[\"event\"] == \"presence_state\"\n    end\n\n    test \"ignores sync for private channels with read policy false\", %{tenant: tenant, topic: topic} do\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: false, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies, private?: true)\n\n      assert :ok = PresenceHandler.sync(socket)\n      refute_receive {_, :text, _}\n    end\n\n    test \"ignores sync when presence is disabled\", %{tenant: tenant, topic: topic} do\n      key = random_string()\n      policies = %Policies{presence: %PresencePolicies{read: true, write: true}}\n      socket = socket_fixture(tenant, topic, key, policies: policies, private?: true, enabled?: false)\n\n      assert :ok = PresenceHandler.sync(socket)\n      refute_receive {_, :text, _}\n    end\n\n    test \"respects rate limits on public channels\", %{tenant: tenant, topic: topic, db_conn: db_conn} do\n      key = random_string()\n      socket = socket_fixture(tenant, topic, key, private?: false)\n\n      log =\n        capture_log(fn ->\n          for _ <- 1..300, do: PresenceHandler.handle(%{\"event\" => \"track\"}, db_conn, socket)\n\n          {:ok, _} = RateCounterHelper.tick!(Tenants.presence_events_per_second_rate(tenant))\n\n          assert {:error, :rate_limit_exceeded} = PresenceHandler.handle(%{\"event\" => \"track\"}, db_conn, socket)\n        end)\n\n      assert log =~ \"PresenceRateLimitReached\"\n    end\n\n    @tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]\n    test \"respects rate limits on private channels\", %{tenant: tenant, topic: topic, db_conn: db_conn} do\n      key = random_string()\n      socket = socket_fixture(tenant, topic, key, private?: true)\n\n      log =\n        capture_log(fn ->\n          for _ <- 1..300, do: PresenceHandler.handle(%{\"event\" => \"track\"}, db_conn, socket)\n\n          {:ok, _} = RateCounterHelper.tick!(Tenants.presence_events_per_second_rate(tenant))\n\n          assert {:error, :rate_limit_exceeded} = PresenceHandler.handle(%{\"event\" => \"track\"}, db_conn, socket)\n        end)\n\n      assert log =~ \"PresenceRateLimitReached\"\n    end\n  end\n\n  describe \"per-client rate limiting\" do\n    test \"allows calls under the limit\", %{tenant: tenant, topic: topic} do\n      client_rate_limit = %{max_calls: 10, window_ms: 60_000, counter: 0, reset_at: nil}\n      socket = socket_fixture(tenant, topic, random_string(), private?: false, client_rate_limit: client_rate_limit)\n\n      # Make 9 calls (under limit of 10)\n      socket =\n        Enum.reduce(1..9, socket, fn _, acc_socket ->\n          {:ok, updated_socket} =\n            PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, acc_socket)\n\n          updated_socket\n        end)\n\n      assert %{counter: 9, max_calls: 10, window_ms: 60000, reset_at: _} = socket.assigns.presence_client_rate_limit\n\n      # 10th call should still work\n      assert {:ok, socket} =\n               PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, socket)\n\n      assert %{counter: 10, max_calls: 10, window_ms: 60000, reset_at: _} = socket.assigns.presence_client_rate_limit\n    end\n\n    test \"blocks calls over the limit\", %{tenant: tenant, topic: topic} do\n      client_rate_limit = %{max_calls: 10, window_ms: 60_000, counter: 0, reset_at: nil}\n      socket = socket_fixture(tenant, topic, random_string(), private?: false, client_rate_limit: client_rate_limit)\n\n      # Make 10 calls (at limit)\n      socket =\n        Enum.reduce(1..10, socket, fn _, acc_socket ->\n          {:ok, updated_socket} =\n            PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, acc_socket)\n\n          updated_socket\n        end)\n\n      # 11th call should fail\n      assert {:error, :client_rate_limit_exceeded} =\n               PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, socket)\n\n      assert %{counter: 10, max_calls: 10, window_ms: 60000, reset_at: _} = socket.assigns.presence_client_rate_limit\n    end\n\n    test \"rate limits work independently per socket\", %{tenant: tenant, topic: topic} do\n      client_rate_limit = %{max_calls: 10, window_ms: 60_000, counter: 0, reset_at: nil}\n      socket1 = socket_fixture(tenant, topic, random_string(), private?: false, client_rate_limit: client_rate_limit)\n      socket2 = socket_fixture(tenant, topic, random_string(), private?: false, client_rate_limit: client_rate_limit)\n\n      socket1 =\n        Enum.reduce(1..10, socket1, fn _, acc_socket ->\n          {:ok, updated_socket} =\n            PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, acc_socket)\n\n          updated_socket\n        end)\n\n      assert {:error, :client_rate_limit_exceeded} =\n               PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, socket1)\n\n      # socket2 should still work (independent limit)\n      assert {:ok, _socket} =\n               PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, socket2)\n    end\n\n    test \"tenant override for max_client_presence_events_per_window is applied\", %{tenant: tenant, topic: topic} do\n      {:ok, updated_tenant} =\n        Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{max_client_presence_events_per_window: 3})\n\n      Realtime.Tenants.Cache.update_cache(updated_tenant)\n\n      socket = socket_fixture(updated_tenant, topic, random_string(), private?: false)\n\n      assert %{max_calls: 3} = socket.assigns.presence_client_rate_limit\n\n      socket =\n        Enum.reduce(1..3, socket, fn _, acc_socket ->\n          {:ok, updated_socket} =\n            PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, acc_socket)\n\n          updated_socket\n        end)\n\n      assert {:error, :client_rate_limit_exceeded} =\n               PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, socket)\n    end\n\n    test \"falls back to env config when tenant override is nil\", %{tenant: tenant, topic: topic} do\n      assert is_nil(tenant.max_client_presence_events_per_window)\n      assert is_nil(tenant.client_presence_window_ms)\n\n      config = Application.get_env(:realtime, :client_presence_rate_limit)\n      expected_max_calls = config[:max_calls]\n      expected_window_ms = config[:window_ms]\n      socket = socket_fixture(tenant, topic, random_string(), private?: false)\n\n      assert %{max_calls: ^expected_max_calls, window_ms: ^expected_window_ms} =\n               socket.assigns.presence_client_rate_limit\n    end\n\n    test \"tenant override for client_presence_window_ms is applied\", %{tenant: tenant, topic: topic} do\n      {:ok, updated_tenant} =\n        Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{client_presence_window_ms: 5_000})\n\n      Realtime.Tenants.Cache.update_cache(updated_tenant)\n\n      socket = socket_fixture(updated_tenant, topic, random_string(), private?: false)\n\n      assert %{window_ms: 5_000} = socket.assigns.presence_client_rate_limit\n    end\n\n    test \"tenant override for client_presence_window_ms respects the window\", %{tenant: tenant, topic: topic} do\n      {:ok, updated_tenant} =\n        Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{\n          max_client_presence_events_per_window: 3,\n          client_presence_window_ms: 100\n        })\n\n      Realtime.Tenants.Cache.update_cache(updated_tenant)\n\n      socket = socket_fixture(updated_tenant, topic, random_string(), private?: false)\n\n      assert %{max_calls: 3, window_ms: 100} = socket.assigns.presence_client_rate_limit\n\n      socket =\n        Enum.reduce(1..3, socket, fn _, acc_socket ->\n          {:ok, updated_socket} =\n            PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, acc_socket)\n\n          updated_socket\n        end)\n\n      assert {:error, :client_rate_limit_exceeded} =\n               PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, socket)\n\n      Process.sleep(101)\n\n      assert {:ok, _socket} =\n               PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, socket)\n    end\n\n    test \"rate limit resets after window expires\", %{tenant: tenant, topic: topic} do\n      # Create socket with a very short window (100ms)\n      socket = socket_fixture(tenant, topic, random_string(), private?: false)\n\n      # Override the window to be very short for testing\n      short_window_config = %{\n        max_calls: 3,\n        window_ms: 100,\n        counter: 0,\n        reset_at: nil\n      }\n\n      socket = %{socket | assigns: Map.put(socket.assigns, :presence_client_rate_limit, short_window_config)}\n\n      # Make 3 calls (at limit)\n      socket =\n        Enum.reduce(1..3, socket, fn _, acc_socket ->\n          {:ok, updated_socket} =\n            PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, acc_socket)\n\n          updated_socket\n        end)\n\n      # 4th call should fail\n      assert {:error, :client_rate_limit_exceeded} =\n               PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, socket)\n\n      # Wait for window to expire\n      Process.sleep(101)\n\n      # Should be able to call again after window reset\n      assert {:ok, _socket} =\n               PresenceHandler.handle(%{\"event\" => \"track\", \"payload\" => %{\"call\" => random_string()}}, nil, socket)\n    end\n  end\n\n  defp initiate_tenant(context) do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    # Warm cache to avoid Cachex and Ecto.Sandbox ownership issues\n    Realtime.Tenants.Cache.update_cache(tenant)\n\n    {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n    assert Connect.ready?(tenant.external_id)\n\n    topic = random_string()\n    if policies = context[:policies], do: create_rls_policies(db_conn, policies, %{topic: topic})\n\n    {:ok, tenant: tenant, db_conn: db_conn, topic: topic}\n  end\n\n  defp socket_fixture(tenant, topic, presence_key, opts \\\\ []) do\n    policies =\n      Keyword.get(opts, :policies, %Policies{\n        broadcast: %BroadcastPolicies{read: true},\n        presence: %PresencePolicies{read: true, write: nil}\n      })\n\n    private? = Keyword.get(opts, :private?, true)\n    enabled? = Keyword.get(opts, :enabled?, true)\n    log_level = Keyword.get(opts, :log_level, :error)\n\n    claims = %{sub: random_string(), role: \"authenticated\", exp: Joken.current_time() + 1_000}\n    signer = Joken.Signer.create(\"HS256\", \"secret\")\n    jwt = Joken.generate_and_sign!(%{}, claims, signer)\n\n    authorization_context =\n      Authorization.build_authorization_params(%{\n        tenant_id: tenant.external_id,\n        topic: topic,\n        jwt: jwt,\n        claims: claims,\n        headers: [{\"header-1\", \"value-1\"}],\n        role: claims.role\n      })\n\n    tenant_topic = Tenants.tenant_topic(tenant.external_id, topic)\n    Endpoint.subscribe(tenant_topic)\n\n    rate = Tenants.presence_events_per_second_rate(tenant)\n\n    RateCounter.new(rate)\n\n    client_rate_limit_override = Keyword.get(opts, :client_rate_limit)\n\n    client_rate_limit =\n      if client_rate_limit_override do\n        client_rate_limit_override\n      else\n        config = Application.get_env(:realtime, :client_presence_rate_limit, max_calls: 10, window_ms: 60_000)\n\n        max_calls =\n          case tenant.max_client_presence_events_per_window do\n            value when is_integer(value) and value > 0 -> value\n            _ -> config[:max_calls]\n          end\n\n        window_ms =\n          case tenant.client_presence_window_ms do\n            value when is_integer(value) and value > 0 -> value\n            _ -> config[:window_ms]\n          end\n\n        %{\n          max_calls: max_calls,\n          window_ms: window_ms,\n          counter: 0,\n          reset_at: nil\n        }\n      end\n\n    %Phoenix.Socket{\n      joined: true,\n      topic: \"realtime:#{topic}\",\n      transport_pid: self(),\n      serializer: Phoenix.Socket.V1.JSONSerializer,\n      assigns: %{\n        tenant_topic: tenant_topic,\n        self_broadcast: true,\n        policies: policies,\n        authorization_context: authorization_context,\n        presence_rate_counter: rate,\n        presence_client_rate_limit: client_rate_limit,\n        private?: private?,\n        presence_key: presence_key,\n        presence_enabled?: enabled?,\n        log_level: log_level,\n        channel_name: topic,\n        tenant: tenant.external_id\n      }\n    }\n  end\n\n  def handle_telemetry(event, measures, metadata, %{pid: pid, tenant: tenant}) do\n    if metadata[:tenant] == tenant.external_id do\n      send(pid, {:telemetry, event, measures, metadata})\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/channels/realtime_channel/tracker_test.exs",
    "content": "defmodule RealtimeWeb.RealtimeChannel.TrackerTest do\n  # It kills websockets when no channels are open\n  # It can affect other tests\n  use Realtime.DataCase, async: false\n  alias RealtimeWeb.RealtimeChannel.Tracker\n\n  setup do\n    start_supervised!({Tracker, check_interval_in_ms: 100})\n    :ets.delete_all_objects(Tracker.table_name())\n    :ok\n  end\n\n  describe \"track/2\" do\n    test \"is able to track channels per transport pid\" do\n      pid = self()\n      Tracker.track(pid)\n\n      assert Tracker.count(pid) == 1\n    end\n\n    test \"is able to track multiple channels per transport pid\" do\n      pid = self()\n      Tracker.track(pid)\n      Tracker.track(pid)\n\n      assert Tracker.count(pid) == 2\n    end\n  end\n\n  describe \"untrack/1\" do\n    test \"is able to untrack a transport pid\" do\n      pid = self()\n      Tracker.track(pid)\n      Tracker.untrack(pid)\n\n      assert Tracker.count(pid) == 0\n    end\n  end\n\n  describe \"count/1\" do\n    test \"is able to count the number of channels per transport pid\" do\n      pid = self()\n      Tracker.track(pid)\n      Tracker.track(pid)\n\n      assert Tracker.count(pid) == 2\n    end\n  end\n\n  describe \"list_pids/0\" do\n    test \"is able to list all pids in the table and their count\" do\n      pid = self()\n      Tracker.track(pid)\n      Tracker.track(pid)\n\n      assert Tracker.list_pids() == [{pid, 2}]\n    end\n  end\n\n  test \"kills tracked pid when no channels are open\" do\n    assert Tracker.table_name() |> :ets.tab2list() |> length() == 0\n\n    pids =\n      for _ <- 1..10_500 do\n        pid = spawn(fn -> :timer.sleep(:infinity) end)\n\n        Tracker.track(pid)\n        Tracker.untrack(pid)\n\n        # Check random negative numbers\n        Enum.random([true, false]) && Tracker.untrack(pid)\n        pid\n      end\n\n    Process.sleep(150)\n\n    for pid <- pids, do: refute(Process.alive?(pid))\n    assert Tracker.table_name() |> :ets.tab2list() |> length() == 0\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/channels/realtime_channel_test.exs",
    "content": "defmodule RealtimeWeb.RealtimeChannelTest do\n  use RealtimeWeb.ChannelCase, async: true\n  use Mimic\n\n  import ExUnit.CaptureLog\n\n  alias Phoenix.Channel.Server\n  alias Phoenix.Socket\n\n  alias Realtime.Tenants.Authorization\n  alias Realtime.Tenants.Connect\n  alias Realtime.RateCounter\n  alias RealtimeWeb.UserSocket\n\n  setup do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    {:ok, db_conn} = Realtime.Database.connect(tenant, \"realtime_test\", :stop)\n    Integrations.setup_postgres_changes(db_conn)\n    GenServer.stop(db_conn)\n    Realtime.Tenants.Cache.update_cache(tenant)\n    {:ok, tenant: tenant}\n  end\n\n  setup :rls_context\n\n  describe \"process flags\" do\n    test \"max heap size is set for both transport and channel processes\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      assert Process.info(socket.transport_pid, :max_heap_size) ==\n               {:max_heap_size, %{error_logger: true, include_shared_binaries: false, kill: true, size: 6_250_000}}\n\n      assert {:ok, _, socket} = subscribe_and_join(socket, \"realtime:test\", %{})\n\n      assert Process.info(socket.channel_pid, :max_heap_size) ==\n               {:max_heap_size, %{error_logger: true, include_shared_binaries: false, kill: true, size: 6_250_000}}\n    end\n\n    # We don't test the socket because on unit tests Phoenix is not setting the fullsweep_after config\n    test \"fullsweep_after is set on channel process\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      assert {:ok, _, socket} = subscribe_and_join(socket, \"realtime:test\", %{})\n\n      assert Process.info(socket.channel_pid, :fullsweep_after) == {:fullsweep_after, 20}\n    end\n  end\n\n  describe \"postgres changes\" do\n    test \"subscribes to inserts\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      config = %{\n        \"postgres_changes\" => [%{\"event\" => \"INSERT\", \"schema\" => \"public\", \"table\" => \"test\"}]\n      }\n\n      assert {:ok, reply, _socket} = subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n\n      assert %{postgres_changes: [%{:id => sub_id, \"event\" => \"INSERT\", \"schema\" => \"public\", \"table\" => \"test\"}]} =\n               reply\n\n      assert_push \"system\",\n                  %{message: \"Subscribed to PostgreSQL\", status: \"ok\", extension: \"postgres_changes\", channel: \"test\"},\n                  5000\n\n      {:ok, conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      %{rows: [[id]]} = Postgrex.query!(conn, \"insert into test (details) values ('test') returning id\", [])\n\n      assert_push \"postgres_changes\", %{data: data, ids: [^sub_id]}, 500\n\n      # we encode and decode because the data is a Jason.Fragment\n      assert %{\n               \"table\" => \"test\",\n               \"type\" => \"INSERT\",\n               \"record\" => %{\"details\" => \"test\", \"id\" => ^id, \"binary_data\" => nil},\n               \"columns\" => [\n                 %{\"name\" => \"id\", \"type\" => \"int4\"},\n                 %{\"name\" => \"details\", \"type\" => \"text\"},\n                 %{\"name\" => \"binary_data\", \"type\" => \"bytea\"}\n               ],\n               \"errors\" => nil,\n               \"schema\" => \"public\",\n               \"commit_timestamp\" => _\n             } = Jason.encode!(data) |> Jason.decode!()\n\n      refute_receive %Socket.Message{}\n      refute_receive %Socket.Reply{}\n    end\n\n    test \"multiple subscriptions\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      config = %{\n        \"postgres_changes\" => [\n          %{\"event\" => \"INSERT\", \"schema\" => \"public\", \"table\" => \"test\"},\n          %{\"event\" => \"DELETE\", \"schema\" => \"public\", \"table\" => \"test\"}\n        ]\n      }\n\n      assert {:ok, reply, _socket} = subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n\n      assert %{\n               postgres_changes: [\n                 %{:id => insert_sub_id, \"event\" => \"INSERT\", \"schema\" => \"public\", \"table\" => \"test\"},\n                 %{\n                   :id => delete_sub_id,\n                   \"event\" => \"DELETE\",\n                   \"schema\" => \"public\",\n                   \"table\" => \"test\"\n                 }\n               ]\n             } =\n               reply\n\n      assert_push \"system\",\n                  %{message: \"Subscribed to PostgreSQL\", status: \"ok\", extension: \"postgres_changes\", channel: \"test\"},\n                  5000\n\n      {:ok, conn} = Connect.lookup_or_start_connection(tenant.external_id)\n      # Insert, update and delete but update should not be received\n      %{rows: [[id]]} = Postgrex.query!(conn, \"insert into test (details) values ('test') returning id\", [])\n      Postgrex.query!(conn, \"update test set details = 'test' where id = $1\", [id])\n      Postgrex.query!(conn, \"delete from test where id = $1\", [id])\n\n      assert_push \"postgres_changes\", %{data: data, ids: [^insert_sub_id]}, 500\n\n      # we encode and decode because the data is a Jason.Fragment\n      assert %{\n               \"table\" => \"test\",\n               \"type\" => \"INSERT\",\n               \"record\" => %{\"details\" => \"test\", \"id\" => ^id},\n               \"columns\" => [\n                 %{\"name\" => \"id\", \"type\" => \"int4\"},\n                 %{\"name\" => \"details\", \"type\" => \"text\"},\n                 %{\"name\" => \"binary_data\", \"type\" => \"bytea\"}\n               ],\n               \"errors\" => nil,\n               \"schema\" => \"public\",\n               \"commit_timestamp\" => _\n             } = Jason.encode!(data) |> Jason.decode!()\n\n      assert_push \"postgres_changes\", %{data: data, ids: [^delete_sub_id]}, 500\n\n      # we encode and decode because the data is a Jason.Fragment\n      assert %{\n               \"table\" => \"test\",\n               \"type\" => \"DELETE\",\n               \"old_record\" => %{\"id\" => ^id},\n               \"columns\" => [\n                 %{\"name\" => \"id\", \"type\" => \"int4\"},\n                 %{\"name\" => \"details\", \"type\" => \"text\"},\n                 %{\"name\" => \"binary_data\", \"type\" => \"bytea\"}\n               ],\n               \"errors\" => nil,\n               \"schema\" => \"public\",\n               \"commit_timestamp\" => _\n             } = Jason.encode!(data) |> Jason.decode!()\n\n      refute_receive _any\n    end\n\n    test \"malformed subscription params\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      config = %{\n        \"postgres_changes\" => [%{\"event\" => \"*\", \"schema\" => \"public\", \"table\" => \"test\", \"filter\" => \"wrong\"}]\n      }\n\n      assert {:ok, reply, socket} = subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n\n      assert %{postgres_changes: [%{\"event\" => \"*\", \"schema\" => \"public\", \"table\" => \"test\"}]} = reply\n\n      assert_push \"system\",\n                  %{\n                    message: \"Error parsing `filter` params: [\\\"wrong\\\"]\",\n                    status: \"error\",\n                    extension: \"postgres_changes\",\n                    channel: \"test\"\n                  },\n                  3000\n\n      socket = Server.socket(socket.channel_pid)\n\n      # It won't re-subscribe\n      assert socket.assigns.pg_sub_ref == nil\n    end\n\n    test \"invalid subscription table does not exist\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      config = %{\n        \"postgres_changes\" => [%{\"event\" => \"*\", \"schema\" => \"public\", \"table\" => \"doesnotexist\"}]\n      }\n\n      assert {:ok, reply, socket} = subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n\n      assert %{postgres_changes: [%{\"event\" => \"*\", \"schema\" => \"public\", \"table\" => \"doesnotexist\"}]} = reply\n\n      assert_push \"system\",\n                  %{\n                    message:\n                      \"Unable to subscribe to changes with given parameters. Please check Realtime is enabled for the given connect parameters: [event: *, schema: public, table: doesnotexist, filters: []]\",\n                    status: \"error\",\n                    extension: \"postgres_changes\",\n                    channel: \"test\"\n                  },\n                  5000\n\n      socket = Server.socket(socket.channel_pid)\n\n      # It won't re-subscribe\n      assert socket.assigns.pg_sub_ref == nil\n    end\n\n    test \"invalid subscription column does not exist\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      config = %{\n        \"postgres_changes\" => [\n          %{\"event\" => \"*\", \"schema\" => \"public\", \"table\" => \"test\", \"filter\" => \"notacolumn=eq.123\"}\n        ]\n      }\n\n      assert {:ok, reply, socket} = subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n\n      assert %{postgres_changes: [%{\"event\" => \"*\", \"schema\" => \"public\", \"table\" => \"test\"}]} = reply\n\n      assert_push \"system\",\n                  %{\n                    message:\n                      \"Unable to subscribe to changes with given parameters. An exception happened so please check your connect parameters: [event: *, schema: public, table: test, filters: [{\\\"notacolumn\\\", \\\"eq\\\", \\\"123\\\"}]]. Exception: ERROR P0001 (raise_exception) invalid column for filter notacolumn\",\n                    status: \"error\",\n                    extension: \"postgres_changes\",\n                    channel: \"test\"\n                  },\n                  5000\n\n      socket = Server.socket(socket.channel_pid)\n\n      # It won't re-subscribe\n      assert socket.assigns.pg_sub_ref == nil\n    end\n\n    test \"connection error\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      config = %{\n        \"postgres_changes\" => [%{\"event\" => \"*\", \"schema\" => \"public\", \"table\" => \"test\"}]\n      }\n\n      conn = spawn(fn -> :ok end)\n      # Let's set the subscription manager conn to be a pid that is no more\n\n      assert {:ok, reply, socket} = subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n\n      assert %{postgres_changes: [%{\"event\" => \"*\", \"schema\" => \"public\", \"table\" => \"test\"}]} = reply\n\n      assert_push \"system\",\n                  %{\n                    message: \"Subscribed to PostgreSQL\",\n                    status: \"ok\",\n                    extension: \"postgres_changes\",\n                    channel: \"test\"\n                  },\n                  5000\n\n      {:ok, manager_pid, _conn} = Extensions.PostgresCdcRls.get_manager_conn(tenant.external_id)\n      Extensions.PostgresCdcRls.update_meta(tenant.external_id, manager_pid, conn)\n\n      assert {:ok, _reply, socket} = subscribe_and_join(socket, \"realtime:test_fail\", %{\"config\" => config})\n\n      assert_push \"system\",\n                  %{message: message, status: \"error\", extension: \"postgres_changes\", channel: \"test_fail\"},\n                  5000\n\n      assert message =~ \"{:error, \\\"Too many database timeouts\\\"}\"\n      socket = Server.socket(socket.channel_pid)\n\n      # It will try again in the future\n      assert socket.assigns.pg_sub_ref != nil\n    end\n  end\n\n  describe \"broadcast\" do\n    @describetag policies: [:authenticated_all_topic_read]\n\n    test \"broadcast map payload\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      config = %{\n        \"broadcast\" => %{\"self\" => true}\n      }\n\n      assert {:ok, _, socket} = subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n\n      push(socket, \"broadcast\", %{\"event\" => \"my_event\", \"payload\" => %{\"hello\" => \"world\"}})\n\n      assert_receive %Phoenix.Socket.Message{\n        topic: \"realtime:test\",\n        event: \"broadcast\",\n        payload: %{\"event\" => \"my_event\", \"payload\" => %{\"hello\" => \"world\"}}\n      }\n    end\n\n    test \"broadcast non-map payload\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      config = %{\n        \"broadcast\" => %{\"self\" => true}\n      }\n\n      assert {:ok, _, socket} = subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n\n      push(socket, \"broadcast\", \"not a map\")\n\n      assert_receive %Phoenix.Socket.Message{\n        topic: \"realtime:test\",\n        event: \"broadcast\",\n        payload: \"not a map\"\n      }\n    end\n\n    test \"wrong replay params\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      config = %{\n        \"private\" => true,\n        \"broadcast\" => %{\n          \"replay\" => %{\"limit\" => \"not a number\", \"since\" => :erlang.system_time(:millisecond) - 5 * 60000}\n        }\n      }\n\n      assert {:error, %{reason: \"UnableToReplayMessages: Replay params are not valid\"}} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n\n      config = %{\n        \"private\" => true,\n        \"broadcast\" => %{\n          \"replay\" => %{\"limit\" => 1, \"since\" => \"not a number\"}\n        }\n      }\n\n      assert {:error, %{reason: \"UnableToReplayMessages: Replay params are not valid\"}} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n\n      config = %{\n        \"private\" => true,\n        \"broadcast\" => %{\n          \"replay\" => %{}\n        }\n      }\n\n      assert {:error, %{reason: \"UnableToReplayMessages: Replay params are not valid\"}} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n    end\n\n    test \"failure to replay\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      config = %{\n        \"private\" => true,\n        \"broadcast\" => %{\n          \"replay\" => %{\"limit\" => 12, \"since\" => :erlang.system_time(:millisecond) - 5 * 60000}\n        }\n      }\n\n      Authorization\n      |> expect(:get_read_authorizations, fn _, _, _, _ ->\n        {:ok,\n         %Authorization.Policies{\n           broadcast: %Authorization.Policies.BroadcastPolicies{read: true, write: nil}\n         }}\n      end)\n\n      # Broken database connection\n      conn = spawn(fn -> :ok end)\n      Connect.lookup_or_start_connection(tenant.external_id)\n      {:ok, _} = :syn.update_registry(Connect, tenant.external_id, fn _pid, meta -> %{meta | conn: conn} end)\n\n      assert {:error, %{reason: \"UnableToReplayMessages: Realtime was unable to replay messages\"}} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n    end\n\n    test \"replay messages on public topic not allowed\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      config = %{\n        \"broadcast\" => %{\"replay\" => %{\"limit\" => 2, \"since\" => :erlang.system_time(:millisecond) - 5 * 60000}}\n      }\n\n      assert {\n               :error,\n               %{reason: \"UnableToReplayMessages: Replay is not allowed for public channels\"}\n             } = subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n\n      refute_receive %Socket.Message{}\n      refute_receive %Socket.Reply{}\n    end\n\n    @tag policies: [:authenticated_all_topic_read]\n    test \"replay messages on private topic\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      # Old message\n      message_fixture(tenant, %{\n        \"private\" => true,\n        \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-1, :day),\n        \"event\" => \"old\",\n        \"extension\" => \"broadcast\",\n        \"topic\" => \"test\",\n        \"payload\" => %{\"value\" => \"old\"}\n      })\n\n      %{id: message1_id} =\n        message_fixture(tenant, %{\n          \"private\" => true,\n          \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-1, :minute),\n          \"event\" => \"first\",\n          \"extension\" => \"broadcast\",\n          \"topic\" => \"test\",\n          \"payload\" => %{\"value\" => \"first\"}\n        })\n\n      %{id: message2_id} =\n        message_fixture(tenant, %{\n          \"private\" => true,\n          \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-2, :minute),\n          \"event\" => \"second\",\n          \"extension\" => \"broadcast\",\n          \"topic\" => \"test\",\n          \"payload\" => %{\"value\" => \"second\"}\n        })\n\n      # This one should not be received because of the limit\n      message_fixture(tenant, %{\n        \"private\" => true,\n        \"inserted_at\" => NaiveDateTime.utc_now() |> NaiveDateTime.add(-3, :minute),\n        \"event\" => \"third\",\n        \"extension\" => \"broadcast\",\n        \"topic\" => \"test\",\n        \"payload\" => %{\"value\" => \"third\"}\n      })\n\n      config = %{\n        \"private\" => true,\n        \"broadcast\" => %{\"replay\" => %{\"limit\" => 2, \"since\" => :erlang.system_time(:millisecond) - 5 * 60000}}\n      }\n\n      assert {:ok, _, %Socket{}} = subscribe_and_join(socket, \"realtime:test\", %{\"config\" => config})\n\n      assert_receive %Socket.Message{\n        topic: \"realtime:test\",\n        event: \"broadcast\",\n        payload: %{\n          \"event\" => \"first\",\n          \"meta\" => %{\"id\" => ^message1_id, \"replayed\" => true},\n          \"payload\" => %{\"value\" => \"first\"},\n          \"type\" => \"broadcast\"\n        }\n      }\n\n      assert_receive %Socket.Message{\n        topic: \"realtime:test\",\n        event: \"broadcast\",\n        payload: %{\n          \"event\" => \"second\",\n          \"meta\" => %{\"id\" => ^message2_id, \"replayed\" => true},\n          \"payload\" => %{\"value\" => \"second\"},\n          \"type\" => \"broadcast\"\n        }\n      }\n\n      refute_receive %Socket.Message{}\n    end\n  end\n\n  describe \"presence\" do\n    test \"presence state event is counted\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert {:ok, _, %Socket{} = socket} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"config\" => %{\"presence\" => %{\"enabled\" => true}}})\n\n      assert_receive %Socket.Message{topic: \"realtime:test\", event: \"presence_state\", payload: %{}}\n\n      tenant_id = tenant.external_id\n\n      assert {:ok, %RateCounter{id: {:channel, :presence_events, ^tenant_id}, bucket: bucket}} =\n               RateCounterHelper.tick!(socket.assigns.presence_rate_counter)\n\n      # presence_state\n      assert Enum.sum(bucket) == 1\n    end\n\n    test \"client rate limit blocks calls over the limit and shuts down channel\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      config = %{\"config\" => %{\"presence\" => %{\"enabled\" => true, \"key\" => \"user_id\"}}}\n      assert {:ok, _, %Socket{channel_pid: channel_pid} = socket} = subscribe_and_join(socket, \"realtime:test\", config)\n\n      assert_receive %Socket.Message{topic: \"realtime:test\", event: \"presence_state\", payload: %{}}\n\n      # Make 5 presence calls (at the default limit)\n      for i <- 1..5 do\n        ref = push(socket, \"presence\", %{\"type\" => \"presence\", \"event\" => \"TRACK\", \"payload\" => %{\"call\" => i}})\n        assert_receive %Socket.Reply{ref: ^ref, status: :ok}, 500\n      end\n\n      assert capture_log(fn ->\n               # 6th call should cause channel shutdown\n               push(socket, \"presence\", %{\"type\" => \"presence\", \"event\" => \"TRACK\", \"payload\" => %{\"call\" => 6}})\n\n               assert_receive %Socket.Message{\n                                topic: \"realtime:test\",\n                                event: \"system\",\n                                payload: %{\n                                  message: \"Client presence rate limit exceeded\",\n                                  status: \"error\",\n                                  extension: \"system\",\n                                  channel: \"test\"\n                                }\n                              },\n                              500\n             end) =~ \"ClientPresenceRateLimitReached\"\n\n      assert_process_down(channel_pid)\n    end\n\n    test \"client rate limits are independent per connection\", %{tenant: tenant} do\n      jwt1 = Generators.generate_jwt_token(tenant)\n      jwt2 = Generators.generate_jwt_token(tenant)\n\n      {:ok, %Socket{} = socket1} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt1))\n      {:ok, %Socket{} = socket2} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt2))\n\n      config = %{\"config\" => %{\"presence\" => %{\"key\" => \"user_id\"}}}\n\n      assert {:ok, _, %Socket{channel_pid: channel_pid1} = socket1} =\n               subscribe_and_join(socket1, \"realtime:test1\", config)\n\n      assert {:ok, _, %Socket{} = socket2} = subscribe_and_join(socket2, \"realtime:test2\", config)\n\n      # Exhaust rate limit for socket1\n      for i <- 1..5 do\n        ref = push(socket1, \"presence\", %{\"type\" => \"presence\", \"event\" => \"TRACK\", \"payload\" => %{\"call\" => i}})\n        assert_receive %Socket.Reply{ref: ^ref, status: :ok}, 500\n      end\n\n      # socket1's 6th call should cause shutdown\n      push(socket1, \"presence\", %{\"type\" => \"presence\", \"event\" => \"TRACK\", \"payload\" => %{\"call\" => 6}})\n\n      assert_receive %Socket.Message{\n                       topic: \"realtime:test1\",\n                       event: \"system\",\n                       payload: %{\n                         message: \"Client presence rate limit exceeded\",\n                         status: \"error\",\n                         extension: \"system\",\n                         channel: \"test1\"\n                       }\n                     },\n                     500\n\n      assert_process_down(channel_pid1)\n\n      # socket2 should still work (independent rate limit)\n      ref = push(socket2, \"presence\", %{\"type\" => \"presence\", \"event\" => \"TRACK\", \"payload\" => %{\"call\" => 1}})\n      assert_receive %Socket.Reply{ref: ^ref, status: :ok}, 500\n    end\n\n    test \"presence track closes on high payload size\", %{tenant: tenant} do\n      topic = \"realtime:test\"\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert {:ok, _, %Socket{} = socket} =\n               subscribe_and_join(socket, topic, %{\"config\" => %{\"presence\" => %{\"enabled\" => true}}})\n\n      assert_receive %Phoenix.Socket.Message{topic: \"realtime:test\", event: \"presence_state\"}, 500\n\n      payload = %{\n        type: \"presence\",\n        event: \"TRACK\",\n        payload: %{name: \"realtime_presence_96\", t: 1814.7000000029802, content: String.duplicate(\"a\", 3_500_000)}\n      }\n\n      push(socket, \"presence\", payload)\n\n      assert_receive %Phoenix.Socket.Message{\n                       event: \"system\",\n                       payload: %{\n                         extension: \"system\",\n                         message: \"Track message size exceeded\",\n                         status: \"error\"\n                       },\n                       topic: ^topic\n                     },\n                     500\n    end\n\n    test \"presence track with same payload does nothing\", %{tenant: tenant} do\n      topic = \"realtime:test\"\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert {:ok, _, %Socket{} = socket} =\n               subscribe_and_join(socket, topic, %{config: %{presence: %{enabled: true, key: \"my_key\"}}})\n\n      assert_receive %Phoenix.Socket.Message{topic: \"realtime:test\", event: \"presence_state\"}, 500\n\n      payload = %{type: \"presence\", event: \"TRACK\", payload: %{\"hello\" => \"world\"}}\n\n      push(socket, \"presence\", payload)\n\n      assert_receive %Socket.Reply{payload: %{}, topic: \"realtime:test\", status: :ok}, 500\n\n      assert_receive %Socket.Message{\n                       payload: %{\n                         joins: %{\"my_key\" => %{metas: [%{:phx_ref => _, \"hello\" => \"world\"}]}},\n                         leaves: %{}\n                       },\n                       topic: \"realtime:test\",\n                       event: \"presence_diff\"\n                     },\n                     500\n\n      push(socket, \"presence\", payload)\n\n      assert_receive %Socket.Reply{payload: %{}, topic: \"realtime:test\", status: :ok}, 500\n      # no presence_diff this time\n\n      refute_receive %Socket.Message{}\n      refute_receive %Socket.Reply{}\n    end\n\n    test \"presence is disabled when tenant has presence_enabled false and client does not override\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      assert {:ok, _, %Socket{} = socket} = subscribe_and_join(socket, \"realtime:test\", %{})\n\n      refute_receive %Socket.Message{event: \"presence_state\"}, 200\n      assert socket.assigns.presence_enabled? == false\n    end\n\n    test \"presence is enabled when client explicitly enables it even if tenant flag is false\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      config = %{\"config\" => %{\"presence\" => %{\"enabled\" => true}}}\n\n      assert {:ok, _, %Socket{} = socket} = subscribe_and_join(socket, \"realtime:test\", config)\n\n      assert_receive %Socket.Message{event: \"presence_state\"}, 500\n      assert socket.assigns.presence_enabled? == true\n    end\n\n    test \"presence defaults to tenant flag when client does not specify\", %{tenant: tenant} do\n      {:ok, tenant} =\n        Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{\"presence_enabled\" => true})\n\n      Realtime.Tenants.Cache.update_cache(tenant)\n\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      assert {:ok, _, %Socket{} = socket} = subscribe_and_join(socket, \"realtime:test\", %{})\n\n      assert_receive %Socket.Message{event: \"presence_state\"}, 500\n      assert socket.assigns.presence_enabled? == true\n    end\n  end\n\n  describe \"unexpected errors\" do\n    test \"unexpected error on Connect\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      expect(Connect, :lookup_or_start_connection, fn _ ->\n        {:error, \"Realtime was unable to connect to the project database\"}\n      end)\n\n      assert capture_log(fn ->\n               assert {:error, %{reason: \"Unknown Error on Channel\"}} =\n                        subscribe_and_join(socket, \"realtime:test\", %{})\n             end) =~ \"UnknownErrorOnChannel: Realtime was unable to connect to the project database\"\n    end\n\n    test \"unexpected error while setting policies\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{}, conn_opts(tenant, jwt))\n\n      expect(Authorization, :get_read_authorizations, fn _, _, _, _ ->\n        {:error, \"Realtime was unable to connect to the project database\"}\n      end)\n\n      assert capture_log(fn ->\n               assert {:error, %{reason: \"Realtime was unable to connect to the project database\"}} =\n                        subscribe_and_join(socket, \"realtime:test\", %{\"config\" => %{\"private\" => true}})\n             end) =~ \"UnableToSetPolicies\"\n    end\n  end\n\n  describe \"maximum number of connected clients per tenant\" do\n    test \"not reached\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      Realtime.Tenants.Cache.update_cache(%{tenant | max_concurrent_users: 1})\n\n      assert {:ok, _, %Socket{}} = subscribe_and_join(socket, \"realtime:test\", %{})\n    end\n\n    test \"reached after connecting\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      Realtime.Tenants.Cache.update_cache(%{tenant | max_concurrent_users: 1})\n\n      pid = spawn_link(fn -> Process.sleep(:infinity) end)\n      Realtime.UsersCounter.add(pid, tenant.external_id)\n\n      assert {:error, %{reason: \"ConnectionRateLimitReached: Too many connected users\"}} =\n               subscribe_and_join(socket, \"realtime:test\", %{})\n\n      pid = spawn_link(fn -> Process.sleep(:infinity) end)\n      Realtime.UsersCounter.add(pid, tenant.external_id)\n\n      assert {:error, %{reason: \"ConnectionRateLimitReached: Too many connected users\"}} =\n               subscribe_and_join(socket, \"realtime:test\", %{})\n    end\n\n    test \"reached before connecting\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n\n      Realtime.Tenants.Cache.update_cache(%{tenant | max_concurrent_users: 1})\n\n      Realtime.UsersCounter.add(self(), tenant.external_id)\n\n      {:error, :too_many_connections} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n    end\n  end\n\n  describe \"access_token\" do\n    @tag policies: [:authenticated_all_topic_read]\n    test \"new valid access_token\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert socket =\n               subscribe_and_join!(socket, \"realtime:test\", %{\n                 \"config\" => %{\"private\" => true, \"presence\" => %{\"enabled\" => true}}\n               })\n\n      old_confirm_ref = socket.assigns.confirm_token_ref\n\n      assert socket.assigns.policies == %Realtime.Tenants.Authorization.Policies{\n               broadcast: %Realtime.Tenants.Authorization.Policies.BroadcastPolicies{read: true, write: nil},\n               presence: %Realtime.Tenants.Authorization.Policies.PresencePolicies{read: true, write: nil}\n             }\n\n      new_token =\n        Generators.generate_jwt_token(tenant, %{\n          exp: System.system_time(:second) + 10_000,\n          role: \"authenticated\",\n          sub: \"123\"\n        })\n\n      assert new_token != jwt\n\n      push(socket, \"access_token\", %{\"access_token\" => new_token})\n\n      socket = Server.socket(socket.channel_pid)\n\n      assert socket.assigns.access_token == new_token\n      assert socket.assigns.confirm_token_ref != old_confirm_ref\n    end\n\n    @tag policies: [:authenticated_all_topic_read]\n    test \"new valid access_token and policy has changed\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert socket =\n               subscribe_and_join!(socket, \"realtime:test\", %{\n                 \"config\" => %{\"private\" => true, \"presence\" => %{\"enabled\" => true}}\n               })\n\n      assert socket.assigns.policies == %Realtime.Tenants.Authorization.Policies{\n               broadcast: %Realtime.Tenants.Authorization.Policies.BroadcastPolicies{read: true, write: nil},\n               presence: %Realtime.Tenants.Authorization.Policies.PresencePolicies{read: true, write: nil}\n             }\n\n      new_token =\n        Generators.generate_jwt_token(tenant, %{\n          exp: System.system_time(:second) + 10_000,\n          role: \"authenticated\",\n          sub: \"123\"\n        })\n\n      assert new_token != jwt\n\n      # RLS policies removed so it should now fail\n      {:ok, db_conn} = Realtime.Database.connect(tenant, \"realtime_test\")\n      clean_table(db_conn, \"realtime\", \"messages\")\n\n      push(socket, \"access_token\", %{\"access_token\" => new_token})\n\n      # Channel closes\n      assert_process_down(socket.channel_pid)\n    end\n\n    test \"new valid access_token but Connect timed out\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert %Socket{channel_pid: channel_pid} = socket = subscribe_and_join!(socket, \"realtime:test\", %{})\n\n      new_token =\n        Generators.generate_jwt_token(tenant, %{\n          exp: System.system_time(:second) + 10_000,\n          role: \"authenticated\",\n          sub: \"123\"\n        })\n\n      assert new_token != jwt\n\n      log =\n        capture_log(fn ->\n          expect(Connect, :lookup_or_start_connection, fn _ -> {:error, :rpc_error, :timeout} end)\n          allow(Connect, self(), channel_pid)\n\n          push(socket, \"access_token\", %{\"access_token\" => new_token})\n\n          # Channel closes\n          assert_process_down(channel_pid)\n        end)\n\n      assert log =~ \"Node request timeout\"\n    end\n\n    test \"new valid access_token but Connect had an error\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert %Socket{channel_pid: channel_pid} = socket = subscribe_and_join!(socket, \"realtime:test\", %{})\n\n      new_token =\n        Generators.generate_jwt_token(tenant, %{\n          exp: System.system_time(:second) + 10_000,\n          role: \"authenticated\",\n          sub: \"123\"\n        })\n\n      assert new_token != jwt\n\n      log =\n        capture_log(fn ->\n          expect(Connect, :lookup_or_start_connection, fn _ -> {:error, :rpc_error, {:EXIT, :actual_error}} end)\n          allow(Connect, self(), channel_pid)\n\n          push(socket, \"access_token\", %{\"access_token\" => new_token})\n\n          # Channel closes\n          assert_process_down(channel_pid)\n        end)\n\n      assert log =~ \"RPC call error: {:EXIT, :actual_error}\"\n    end\n\n    test \"new broken access_token\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert %Socket{channel_pid: channel_pid} = socket = subscribe_and_join!(socket, \"realtime:test\", %{})\n\n      new_token = \"not even a JWT\"\n\n      push(socket, \"access_token\", %{\"access_token\" => new_token})\n\n      # Channel closes\n      assert_process_down(channel_pid)\n\n      assert_receive %Socket.Message{\n        topic: \"realtime:test\",\n        event: \"system\",\n        payload: %{\n          message: \"The token provided is not a valid JWT\",\n          status: \"error\",\n          extension: \"system\",\n          channel: \"test\"\n        }\n      }\n\n      # Socket also closes...\n      assert_receive {:socket_close, ^channel_pid, :normal}\n    end\n\n    test \"new JWT missing role claim\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert %Socket{channel_pid: channel_pid} = socket = subscribe_and_join!(socket, \"realtime:test\", %{})\n\n      new_token = Generators.generate_jwt_token(tenant, %{exp: System.system_time(:second) + 10_000})\n\n      push(socket, \"access_token\", %{\"access_token\" => new_token})\n\n      # Channel closes\n      assert_process_down(channel_pid)\n\n      assert_receive %Socket.Message{\n        topic: \"realtime:test\",\n        event: \"system\",\n        payload: %{\n          message: \"Fields `role` and `exp` are required in JWT\",\n          status: \"error\",\n          extension: \"system\",\n          channel: \"test\"\n        }\n      }\n\n      # Socket also closes...\n      assert_receive {:socket_close, ^channel_pid, :normal}\n    end\n\n    test \"new JWT missing exp claim\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert %Socket{channel_pid: channel_pid} = socket = subscribe_and_join!(socket, \"realtime:test\", %{})\n\n      new_token = Generators.generate_jwt_token(tenant, %{role: \"authenticated\"})\n\n      push(socket, \"access_token\", %{\"access_token\" => new_token})\n\n      # Channel closes\n      assert_process_down(channel_pid)\n\n      assert_receive %Socket.Message{\n        topic: \"realtime:test\",\n        event: \"system\",\n        payload: %{\n          message: \"Fields `role` and `exp` are required in JWT\",\n          status: \"error\",\n          extension: \"system\",\n          channel: \"test\"\n        }\n      }\n\n      # Socket also closes...\n      assert_receive {:socket_close, ^channel_pid, :normal}\n    end\n\n    test \"new expired JWT\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert %Socket{channel_pid: channel_pid} = socket = subscribe_and_join!(socket, \"realtime:test\", %{})\n\n      new_token =\n        Generators.generate_jwt_token(tenant, %{role: \"authenticated\", exp: System.system_time(:second) - 1000})\n\n      push(socket, \"access_token\", %{\"access_token\" => new_token})\n\n      # Channel closes\n      assert_process_down(channel_pid)\n\n      assert_receive %Socket.Message{\n        topic: \"realtime:test\",\n        event: \"system\",\n        payload: %{\n          message: message,\n          status: \"error\",\n          extension: \"system\",\n          channel: \"test\"\n        }\n      }\n\n      assert message =~ ~r{Token has expired \\d+ seconds ago}\n\n      # Socket also closes...\n      assert_receive {:socket_close, ^channel_pid, :normal}\n    end\n  end\n\n  describe \"confirm token\" do\n    test \"token has expired\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant, %{role: \"authenticated\", exp: System.system_time(:second) + 2})\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert %Socket{channel_pid: channel_pid} = subscribe_and_join!(socket, \"realtime:test\", %{})\n\n      Process.sleep(2000)\n      send(channel_pid, :confirm_token)\n\n      # Channel closes\n      assert_process_down(channel_pid)\n\n      assert_receive %Socket.Message{\n        topic: \"realtime:test\",\n        event: \"system\",\n        payload: %{\n          message: \"Token has expired 0 seconds ago\",\n          status: \"error\",\n          extension: \"system\",\n          channel: \"test\"\n        }\n      }\n    end\n  end\n\n  describe \"access_token validations\" do\n    test \"access_token has expired\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant)\n      jwt = Generators.generate_jwt_token(tenant, %{role: \"authenticated\", exp: System.system_time(:second) - 1})\n\n      assert {:ok, socket} = connect(UserSocket, %{}, conn_opts(tenant, api_key))\n\n      assert {:error, %{reason: \"InvalidJWTToken: Token has expired \" <> _}} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"access_token\" => jwt})\n    end\n\n    test \"access_token has expired log_level=warning\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant)\n      jwt = Generators.generate_jwt_token(tenant, %{role: \"authenticated\", exp: System.system_time(:second) - 1})\n\n      assert {:ok, socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n      assert {:error, %{reason: \"InvalidJWTToken: Token has expired \" <> _}} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"access_token\" => jwt})\n    end\n\n    test \"access_token missing exp claim on join\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant)\n      jwt = Generators.generate_jwt_token(tenant, %{role: \"authenticated\"})\n\n      assert {:ok, socket} = connect(UserSocket, %{}, conn_opts(tenant, api_key))\n\n      assert {:error, %{reason: \"InvalidJWTToken: Fields `role` and `exp` are required in JWT\"}} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"access_token\" => jwt})\n    end\n\n    test \"access_token missing exp claim on join log_level=warning\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant)\n      jwt = Generators.generate_jwt_token(tenant, %{role: \"authenticated\"})\n\n      assert {:ok, socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n      assert {:error, %{reason: \"InvalidJWTToken: Fields `role` and `exp` are required in JWT\"}} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"access_token\" => jwt})\n    end\n\n    test \"access_token missing role claim on join\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant)\n      jwt = Generators.generate_jwt_token(tenant, %{exp: System.system_time(:second) + 1000})\n\n      assert {:ok, socket} = connect(UserSocket, %{}, conn_opts(tenant, api_key))\n\n      assert {:error, %{reason: \"InvalidJWTToken: Fields `role` and `exp` are required in JWT\"}} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"access_token\" => jwt})\n    end\n\n    test \"access_token missing role claim on join log_level=warning\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant)\n      jwt = Generators.generate_jwt_token(tenant, %{exp: System.system_time(:second) + 1000})\n\n      assert {:ok, socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n      assert {:error, %{reason: \"InvalidJWTToken: Fields `role` and `exp` are required in JWT\"}} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"access_token\" => jwt})\n    end\n\n    test \"missing claims returns error no logs\", %{tenant: tenant} do\n      sub = random_string()\n      iss = \"https://#{random_string()}.com\"\n      exp = System.system_time(:second) + 10_000\n\n      api_key = Generators.generate_jwt_token(tenant)\n      jwt = Generators.generate_jwt_token(tenant, %{exp: exp, sub: sub, iss: iss})\n\n      assert {:ok, socket} = connect(UserSocket, %{}, conn_opts(tenant, api_key))\n\n      log =\n        capture_log(fn ->\n          assert {:error, %{reason: \"InvalidJWTToken: Fields `role` and `exp` are required in JWT\"}} =\n                   subscribe_and_join(socket, \"realtime:test\", %{\"access_token\" => jwt})\n        end)\n\n      refute log =~ \"InvalidJWTToken: Fields `role` and `exp` are required in JWT\"\n    end\n\n    test \"missing claims returns a error with token exp, iss and sub in metadata if available log_level=warning\", %{\n      tenant: tenant\n    } do\n      sub = random_string()\n      iss = \"https://#{random_string()}.com\"\n      exp = System.system_time(:second) + 10_000\n\n      api_key = Generators.generate_jwt_token(tenant)\n      jwt = Generators.generate_jwt_token(tenant, %{exp: exp, sub: sub, iss: iss})\n\n      assert {:ok, socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n      log =\n        capture_log(fn ->\n          assert {:error, %{reason: \"InvalidJWTToken: Fields `role` and `exp` are required in JWT\"}} =\n                   subscribe_and_join(socket, \"realtime:test\", %{\"access_token\" => jwt})\n        end)\n\n      assert log =~ \"InvalidJWTToken: Fields `role` and `exp` are required in JWT\"\n      assert log =~ \"sub=#{sub}\"\n      assert log =~ \"iss=#{iss}\"\n      assert log =~ \"exp=#{exp}\"\n    end\n\n    test \"expired jwt returns error no logs\", %{tenant: tenant} do\n      sub = random_string()\n\n      api_key = Generators.generate_jwt_token(tenant)\n\n      jwt =\n        Generators.generate_jwt_token(tenant, %{role: \"authenticated\", exp: System.system_time(:second) - 1, sub: sub})\n\n      assert {:ok, socket} = connect(UserSocket, %{}, conn_opts(tenant, api_key))\n\n      log =\n        capture_log(fn ->\n          assert {:error, %{reason: \"InvalidJWTToken: Token has expired \" <> _}} =\n                   subscribe_and_join(socket, \"realtime:test\", %{\"access_token\" => jwt})\n        end)\n\n      refute log =~ \"InvalidJWTToken: Token has expired\"\n    end\n\n    test \"expired jwt returns a error with sub data if available log_level=warning\", %{tenant: tenant} do\n      sub = random_string()\n\n      api_key = Generators.generate_jwt_token(tenant)\n\n      jwt =\n        Generators.generate_jwt_token(tenant, %{role: \"authenticated\", exp: System.system_time(:second) - 1, sub: sub})\n\n      assert {:ok, socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n      log =\n        capture_log(fn ->\n          assert {:error, %{reason: \"InvalidJWTToken: Token has expired \" <> _}} =\n                   subscribe_and_join(socket, \"realtime:test\", %{\"access_token\" => jwt})\n        end)\n\n      assert log =~ \"InvalidJWTToken: Token has expired\"\n      assert log =~ \"sub=#{sub}\"\n    end\n  end\n\n  describe \"API Key validations\" do\n    test \"x-api-key header has not expired\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n      assert {:ok, _, %Socket{}} = subscribe_and_join(socket, \"realtime:test\", %{})\n    end\n\n    test \"apikey param has not expired\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant)\n\n      conn_opts = [\n        connect_info: %{\n          uri: URI.parse(\"https://#{tenant.external_id}.localhost:4000/socket/websocket\"),\n          x_headers: []\n        }\n      ]\n\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\", \"apikey\" => api_key}, conn_opts)\n\n      assert {:ok, _, %Socket{} = socket} = subscribe_and_join(socket, \"realtime:test\", %{})\n      assert socket.assigns.access_token == api_key\n    end\n\n    test \"join with access_token starting with sb_\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n      assert {:ok, _, %Socket{} = socket} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"access_token\" => \"sb_something\"})\n\n      assert socket.assigns.access_token == api_key\n    end\n\n    test \"join with user_token starting with sb_\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n      assert {:ok, _, %Socket{} = socket} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"user_token\" => \"sb_something\"})\n\n      assert socket.assigns.access_token == api_key\n    end\n\n    test \"join with access_token\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant)\n      access_token = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n      assert {:ok, _, %Socket{} = socket} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"access_token\" => access_token})\n\n      assert socket.assigns.access_token == access_token\n    end\n\n    test \"join with user_token\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant)\n      user_token = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n      assert {:ok, _, %Socket{} = socket} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"user_token\" => user_token})\n\n      assert socket.assigns.access_token == user_token\n    end\n\n    test \"api_key has expired\", %{tenant: tenant} do\n      assert capture_log(fn ->\n               api_key =\n                 Generators.generate_jwt_token(tenant, %{role: \"authenticated\", exp: System.system_time(:second)})\n\n               assert {:error, :expired_token} =\n                        connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n               Process.sleep(300)\n             end) =~ \"InvalidJWTToken: Token has expired\"\n\n      api_key = Generators.generate_jwt_token(tenant, %{role: \"authenticated\", exp: System.system_time(:second) - 1})\n\n      assert capture_log(fn ->\n               assert {:error, :expired_token} =\n                        connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n             end) =~ \"InvalidJWTToken: Token has expired\"\n    end\n\n    test \"missing role claims returns a error\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant, %{exp: System.system_time(:second) + 1000})\n\n      log =\n        capture_log(fn ->\n          assert {:error, :missing_claims} =\n                   connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n        end)\n\n      assert log =~ \"InvalidJWTToken: Fields `role` and `exp` are required in JWT\"\n    end\n\n    test \"missing exp claims returns a error\", %{tenant: tenant} do\n      api_key = Generators.generate_jwt_token(tenant, %{role: \"authenticated\"})\n\n      log =\n        capture_log(fn ->\n          assert {:error, :missing_claims} =\n                   connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n        end)\n\n      assert log =~ \"InvalidJWTToken: Fields `role` and `exp` are required in JWT\"\n    end\n\n    test \"missing claims returns a error with token exp, iss and sub in metadata if available\", %{tenant: tenant} do\n      sub = random_string()\n      iss = \"https://#{random_string()}.com\"\n      exp = System.system_time(:second) + 10_000\n\n      api_key = Generators.generate_jwt_token(tenant, %{exp: exp, sub: sub, iss: iss})\n\n      log =\n        capture_log(fn ->\n          assert {:error, :missing_claims} =\n                   connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n          Process.sleep(300)\n        end)\n\n      assert log =~ \"InvalidJWTToken: Fields `role` and `exp` are required in JWT\"\n      assert log =~ \"sub=#{sub}\"\n      assert log =~ \"iss=#{iss}\"\n      assert log =~ \"exp=#{exp}\"\n    end\n\n    test \"expired api_key returns a error with sub data if available\", %{tenant: tenant} do\n      sub = random_string()\n\n      api_key =\n        Generators.generate_jwt_token(tenant, %{role: \"authenticated\", exp: System.system_time(:second) - 1, sub: sub})\n\n      log =\n        capture_log(fn ->\n          assert {:error, :expired_token} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, api_key))\n\n          Process.sleep(300)\n        end)\n\n      assert log =~ \"InvalidJWTToken: Token has expired\"\n      assert log =~ \"sub=#{sub}\"\n    end\n  end\n\n  describe \"checks tenant db connectivity\" do\n    test \"successful connection proceeds with join\", %{tenant: tenant} do\n      jwt = Generators.generate_jwt_token(tenant)\n\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n      assert {:ok, _, %Socket{}} = subscribe_and_join(socket, \"realtime:test\", %{})\n    end\n\n    test \"unsuccessful connection halts join\", %{tenant: tenant} do\n      extension = %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"db_host\" => \"127.0.0.1\",\n          \"db_name\" => \"false\",\n          \"db_user\" => \"false\",\n          \"db_password\" => \"false\",\n          \"poll_interval\" => 100,\n          \"poll_max_changes\" => 100,\n          \"poll_max_record_bytes\" => 1_048_576,\n          \"region\" => \"us-east-1\",\n          \"ssl_enforced\" => false\n        }\n      }\n\n      {:ok, tenant} = update_extension(tenant, extension)\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert {:error, %{reason: \"UnableToConnectToProject: Realtime was unable to connect to the project database\"}} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"config\" => %{\"private\" => true}})\n    end\n\n    test \"lack of connections halts join\", %{tenant: tenant} do\n      extension =\n        %{\n          \"type\" => \"postgres_cdc_rls\",\n          \"settings\" => %{\n            \"db_host\" => \"127.0.0.1\",\n            \"db_name\" => \"postgres\",\n            \"db_user\" => \"supabase_admin\",\n            \"db_password\" => \"postgres\",\n            \"poll_interval\" => 100,\n            \"poll_max_changes\" => 100,\n            \"poll_max_record_bytes\" => 1_048_576,\n            \"region\" => \"us-east-1\",\n            \"ssl_enforced\" => false,\n            \"db_pool\" => 100,\n            \"subcriber_pool_size\" => 100,\n            \"subs_pool_size\" => 100\n          }\n        }\n\n      {:ok, tenant} = update_extension(tenant, extension)\n\n      jwt = Generators.generate_jwt_token(tenant)\n      {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n      assert {:error,\n              %{reason: \"DatabaseLackOfConnections: Database can't accept more connections, Realtime won't connect\"}} =\n               subscribe_and_join(socket, \"realtime:test\", %{\"config\" => %{\"private\" => true}})\n    end\n  end\n\n  test \"registers transport pid and channel pid per tenant\", %{tenant: tenant} do\n    jwt = Generators.generate_jwt_token(tenant)\n    {:ok, %Socket{} = socket} = connect(UserSocket, %{\"log_level\" => \"warning\"}, conn_opts(tenant, jwt))\n\n    assert {:ok, _, %Socket{transport_pid: transport_pid_1} = socket} =\n             subscribe_and_join(socket, \"realtime:#{random_string()}\", %{})\n\n    assert {:ok, _, %Socket{transport_pid: ^transport_pid_1}} =\n             subscribe_and_join(socket, \"realtime:#{random_string()}\", %{})\n\n    assert [{_, ^transport_pid_1}] = Registry.lookup(RealtimeWeb.SocketDisconnect.Registry, tenant.external_id)\n  end\n\n  defp conn_opts(tenant, token) do\n    [\n      connect_info: %{\n        uri: URI.parse(\"https://#{tenant.external_id}.localhost:4000/socket/websocket\"),\n        x_headers: [{\"x-api-key\", token}]\n      }\n    ]\n  end\n\n  defp update_extension(tenant, extension) do\n    db_port = Realtime.Crypto.decrypt!(hd(tenant.extensions).settings[\"db_port\"])\n\n    extensions = [\n      put_in(extension, [\"settings\", \"db_port\"], db_port)\n    ]\n\n    with {:ok, tenant} <- Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{extensions: extensions}) do\n      Realtime.Tenants.Cache.update_cache(tenant)\n      {:ok, tenant}\n    end\n  end\n\n  defp assert_process_down(pid) do\n    ref = Process.monitor(pid)\n    assert_receive {:DOWN, ^ref, :process, ^pid, _reason}\n  end\n\n  defp rls_context(%{tenant: tenant, policies: policies}) do\n    {:ok, conn} = Realtime.Database.connect(tenant, \"realtime_test\", :stop)\n    create_rls_policies(conn, policies, %{topic: \"realtime:test\"})\n    :ok\n  end\n\n  defp rls_context(_), do: :ok\nend\n"
  },
  {
    "path": "test/realtime_web/channels/socket_disconnect_test.exs",
    "content": "defmodule RealtimeWeb.SocketDisconnectTest do\n  use ExUnit.Case\n  import ExUnit.CaptureLog\n  import Generators\n\n  alias Phoenix.PubSub\n  alias RealtimeWeb.SocketDisconnect\n\n  @aux_mod (quote do\n              defmodule DisconnectTestAux do\n                alias RealtimeWeb.SocketDisconnect\n\n                def generate_tenant_processes(tenant_external_id) do\n                  tenant_pids =\n                    for _ <- 1..10 do\n                      pid = spawn(fn -> Process.sleep(:infinity) end)\n                      SocketDisconnect.add(tenant_external_id, %Phoenix.Socket{transport_pid: pid})\n                      pid\n                    end\n\n                  other_tenant_pids =\n                    for _ <- 1..10 do\n                      pid = spawn(fn -> Process.sleep(:infinity) end)\n                      SocketDisconnect.add(Generators.random_string(), %Phoenix.Socket{transport_pid: pid})\n                      pid\n                    end\n\n                  %{tenant: tenant_pids, other: other_tenant_pids}\n                end\n              end\n            end)\n\n  Code.eval_quoted(@aux_mod)\n\n  describe \"add/2\" do\n    test \"successfully registers a socket with the tenant's external_id\" do\n      tenant_external_id = random_string()\n      pid = spawn(fn -> Process.sleep(:infinity) end)\n      socket = %Phoenix.Socket{transport_pid: pid}\n\n      assert :ok = SocketDisconnect.add(tenant_external_id, socket)\n      # Verify that the socket is registered in the registry\n\n      assert [{_, ^pid}] = Registry.lookup(RealtimeWeb.SocketDisconnect.Registry, tenant_external_id)\n    end\n\n    test \"successfully registers multiple entries repeatedly without collision\" do\n      tenant_external_id = random_string()\n\n      transport_pid = spawn(fn -> Process.sleep(:infinity) end)\n      socket = %Phoenix.Socket{transport_pid: transport_pid}\n\n      assert :ok = SocketDisconnect.add(tenant_external_id, socket)\n      assert :ok = SocketDisconnect.add(tenant_external_id, socket)\n      assert :ok = SocketDisconnect.add(tenant_external_id, socket)\n\n      assert result = Registry.lookup(RealtimeWeb.SocketDisconnect.Registry, tenant_external_id)\n      assert length(result) == 1\n      assert [{_, ^transport_pid}] = result\n      assert Process.alive?(transport_pid)\n    end\n\n    test \"successfully registers multiple entries from different pids without collision\" do\n      tenant_external_id = random_string()\n\n      for _ <- 1..10 do\n        pid = spawn(fn -> Process.sleep(:infinity) end)\n        socket = %Phoenix.Socket{transport_pid: pid}\n        assert :ok = SocketDisconnect.add(tenant_external_id, socket)\n        assert :ok = SocketDisconnect.add(tenant_external_id, socket)\n        assert :ok = SocketDisconnect.add(tenant_external_id, socket)\n\n        pid\n      end\n\n      # Verify that only one entry is registered\n      result = Registry.lookup(RealtimeWeb.SocketDisconnect.Registry, tenant_external_id)\n      assert length(result) == 10\n      for {_, pid} <- result, do: assert(Process.alive?(pid))\n    end\n  end\n\n  describe \"disconnect/1\" do\n    test \"successfully disconnects all sockets associated with a given tenant on the current node\" do\n      tenant_external_id = random_string()\n      %{tenant: tenant_pids, other: other_pids} = DisconnectTestAux.generate_tenant_processes(tenant_external_id)\n\n      # Ensure all processes are alive before disconnecting\n      for pid <- tenant_pids, do: assert(Process.alive?(pid))\n      for pid <- other_pids, do: assert(Process.alive?(pid))\n\n      # Perform the disconnect\n      assert :ok = SocketDisconnect.disconnect(tenant_external_id)\n\n      # Verify that tenant processes are killed and other processes remain alive\n      for pid <- tenant_pids, do: refute(Process.alive?(pid))\n      for pid <- other_pids, do: assert(Process.alive?(pid))\n    end\n\n    test \"after disconnect, pid is unregistered\" do\n      tenant_external_id = random_string()\n      PubSub.subscribe(Realtime.PubSub, \"realtime:operations:\" <> tenant_external_id)\n      %{tenant: tenant_pids, other: other_pids} = DisconnectTestAux.generate_tenant_processes(tenant_external_id)\n\n      # Ensure all processes are alive before disconnecting\n      for pid <- tenant_pids, do: assert(Process.alive?(pid))\n      for pid <- other_pids, do: assert(Process.alive?(pid))\n\n      # Perform the disconnect\n      log =\n        capture_log(fn ->\n          assert :ok = SocketDisconnect.disconnect(tenant_external_id)\n        end)\n\n      assert_received :disconnect\n      Process.sleep(200)\n      assert [] = Registry.lookup(RealtimeWeb.SocketDisconnect.Registry, tenant_external_id)\n      assert log =~ \"Disconnecting all sockets for tenant #{tenant_external_id}\"\n    end\n  end\n\n  describe \"distributed_disconnect/1\" do\n    setup do\n      {:ok, node} = Clustered.start(@aux_mod)\n      %{node: node}\n    end\n\n    test \"successfully kills all processes associated with a given tenant and non others\" do\n      tenant_external_id = random_string()\n      # Generate fake processes for the tenant and other tenants\n      %{tenant: tenant_pids, other: other_pids} = DisconnectTestAux.generate_tenant_processes(tenant_external_id)\n\n      %{tenant: remote_tenant_pids, other: remote_other_pids} =\n        :erpc.call(Node.self(), DisconnectTestAux, :generate_tenant_processes, [tenant_external_id])\n\n      # Ensure all processes are alive before disconnecting\n      for pid <- tenant_pids ++ remote_tenant_pids, do: assert(Process.alive?(pid))\n      for pid <- other_pids ++ remote_other_pids, do: assert(Process.alive?(pid))\n\n      # Perform the distributed disconnect\n      assert [:ok, :ok] = SocketDisconnect.distributed_disconnect(tenant_external_id)\n\n      # Verify that tenant processes are killed and other processes remain alive\n      for pid <- tenant_pids ++ remote_tenant_pids, do: refute(Process.alive?(pid))\n      for pid <- other_pids ++ remote_other_pids, do: assert(Process.alive?(pid))\n    end\n  end\n\n  test \"on registered pid dead, Registry cleans up\" do\n    tenant_external_id = random_string()\n\n    pid =\n      spawn(fn ->\n        pid = spawn(fn -> Process.sleep(:infinity) end)\n        socket = %Phoenix.Socket{transport_pid: pid}\n        assert :ok = SocketDisconnect.add(tenant_external_id, socket)\n\n        assert [^pid] = Registry.lookup(RealtimeWeb.SocketDisconnect.Registry, tenant_external_id)\n      end)\n\n    Process.sleep(100)\n    refute Process.alive?(pid)\n    assert [] = Registry.lookup(RealtimeWeb.SocketDisconnect.Registry, tenant_external_id)\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/channels/tenant_rate_limiters_test.exs",
    "content": "defmodule RealtimeWeb.TenantRateLimitersTest do\n  use Realtime.DataCase, async: true\n\n  use Mimic\n  alias RealtimeWeb.TenantRateLimiters\n  alias Realtime.Api.Tenant\n\n  setup do\n    tenant = %Tenant{external_id: random_string(), max_concurrent_users: 1, max_joins_per_second: 1}\n\n    %{tenant: tenant}\n  end\n\n  describe \"check_tenant/1\" do\n    test \"rate is not exceeded\", %{tenant: tenant} do\n      assert TenantRateLimiters.check_tenant(tenant) == :ok\n    end\n\n    test \"max concurrent users is exceeded\", %{tenant: tenant} do\n      Realtime.UsersCounter.add(self(), tenant.external_id)\n\n      assert TenantRateLimiters.check_tenant(tenant) == {:error, :too_many_connections}\n    end\n\n    test \"max joins is exceeded\", %{tenant: tenant} do\n      expect(Realtime.RateCounter, :get, fn _ -> {:ok, %{limit: %{triggered: true}}} end)\n\n      assert TenantRateLimiters.check_tenant(tenant) == {:error, :too_many_joins}\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/channels/user_socket_test.exs",
    "content": "defmodule RealtimeWeb.UserSocketTest do\n  use ExUnit.Case, async: true\n  import ExUnit.CaptureLog\n\n  alias RealtimeWeb.Socket.V2Serializer\n  alias RealtimeWeb.UserSocket\n\n  @socket %Phoenix.Socket{\n    serializer: V2Serializer,\n    assigns: %{tenant: \"test-tenant\", access_token: \"test-token\", log_level: :error}\n  }\n  @state {%{channels: %{}, channels_inverse: %{}}, @socket}\n\n  describe \"handle_in/2 with invalid messages\" do\n    test \"does not crash and logs when message is an array with not enough items\" do\n      raw = Jason.encode!([\"join_ref\", \"ref\", \"topic\"])\n\n      log =\n        capture_log(fn ->\n          assert {:ok, @state} = UserSocket.handle_in({raw, [opcode: :text]}, @state)\n        end)\n\n      assert log =~ \"MalformedWebSocketMessage\"\n    end\n\n    test \"does not crash and logs when message is a map\" do\n      raw = Jason.encode!(%{\"topic\" => \"t\", \"event\" => \"e\", \"payload\" => %{}})\n\n      log =\n        capture_log(fn ->\n          assert {:ok, @state} = UserSocket.handle_in({raw, [opcode: :text]}, @state)\n        end)\n\n      assert log =~ \"MalformedWebSocketMessage\"\n    end\n\n    test \"does not crash and logs when message is empty string\" do\n      log =\n        capture_log(fn ->\n          assert {:ok, @state} = UserSocket.handle_in({\"\", [opcode: :text]}, @state)\n        end)\n\n      assert log =~ \"MalformedWebSocketMessage\"\n    end\n\n    test \"does not crash and logs when message is invalid JSON\" do\n      log =\n        capture_log(fn ->\n          assert {:ok, @state} = UserSocket.handle_in({\"not json\", [opcode: :text]}, @state)\n        end)\n\n      assert log =~ \"MalformedWebSocketMessage\"\n    end\n\n    test \"does not crash and logs on unexpected errors\" do\n      log =\n        capture_log(fn ->\n          assert {:ok, @state} = UserSocket.handle_in({:not_a_binary, [opcode: :text]}, @state)\n        end)\n\n      assert log =~ \"UnknownErrorOnWebSocketMessage\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/controllers/broadcast_controller_test.exs",
    "content": "defmodule RealtimeWeb.BroadcastControllerTest do\n  use RealtimeWeb.ConnCase, async: true\n  use Mimic\n\n  alias Realtime.Crypto\n  alias Realtime.GenCounter\n  alias Realtime.RateCounter\n  alias Realtime.Tenants\n  alias Realtime.Database\n\n  alias RealtimeWeb.RealtimeChannel\n  alias RealtimeWeb.Endpoint\n  alias RealtimeWeb.TenantBroadcaster\n\n  @token \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjIsInJvbGUiOiJmb28iLCJleHAiOiJiYXIifQ.Ret2CevUozCsPhpgW2FMeFL7RooLgoOvfQzNpLBj5ak\"\n  @expired_token \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjEwNzMyOTAsImlhdCI6MTYyNzg4NjQ0MCwicm9sZSI6ImFub24ifQ.AHmuaydSU3XAxwoIFhd3gwGwjnBIKsjFil0JQEOLtRw\"\n\n  setup %{conn: conn} do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    # Warm cache to avoid Cachex and Ecto.Sandbox ownership issues\n    Realtime.Tenants.Cache.update_cache(tenant)\n\n    conn = generate_conn(conn, tenant)\n\n    {:ok, conn: conn, tenant: tenant}\n  end\n\n  defp subscribe(tenant_topic, topic) do\n    fastlane =\n      RealtimeChannel.MessageDispatcher.fastlane_metadata(\n        self(),\n        Phoenix.Socket.V1.JSONSerializer,\n        topic,\n        :error,\n        \"tenant_id\"\n      )\n\n    Endpoint.subscribe(tenant_topic, metadata: fastlane)\n  end\n\n  defp assert_receive_messages(count) do\n    Enum.map(1..count, fn _ ->\n      assert_receive {:socket_push, :text, data}\n\n      data\n      |> IO.iodata_to_binary()\n      |> Jason.decode!()\n    end)\n  end\n\n  describe \"broadcast\" do\n    test \"returns 202 when batch of messages is broadcasted\", %{conn: conn, tenant: tenant} do\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n      request_events_key = Tenants.requests_per_second_key(tenant)\n\n      GenCounter\n      |> expect(:add, fn ^request_events_key -> :ok end)\n      |> expect(:add, 2, fn ^broadcast_events_key -> :ok end)\n\n      sub_topic_1 = \"sub_topic_1\"\n      sub_topic_2 = \"sub_topic_2\"\n      topic_1 = Tenants.tenant_topic(tenant, sub_topic_1)\n      topic_2 = Tenants.tenant_topic(tenant, sub_topic_2)\n\n      payload_1 = %{\"data\" => \"data\"}\n      payload_2 = %{\"data\" => \"data\"}\n      event_1 = \"event_1\"\n      event_2 = \"event_2\"\n\n      payload_topic_1 = %{\"payload\" => payload_1, \"event\" => event_1, \"type\" => \"broadcast\"}\n      payload_topic_2 = %{\"payload\" => payload_2, \"event\" => event_2, \"type\" => \"broadcast\"}\n\n      subscribe(topic_1, sub_topic_1)\n      subscribe(topic_2, sub_topic_2)\n\n      conn =\n        post(conn, Routes.broadcast_path(conn, :broadcast), %{\n          \"messages\" => [\n            %{\"topic\" => sub_topic_1, \"payload\" => payload_1, \"event\" => event_1},\n            %{\"topic\" => sub_topic_1, \"payload\" => payload_1, \"event\" => event_1},\n            %{\"topic\" => sub_topic_2, \"payload\" => payload_2, \"event\" => event_2}\n          ]\n        })\n\n      assert conn.status == 202\n\n      messages = assert_receive_messages(3)\n\n      assert Enum.count(messages, fn message ->\n               message == %{\n                 \"event\" => \"broadcast\",\n                 \"payload\" => payload_topic_1,\n                 \"ref\" => nil,\n                 \"topic\" => sub_topic_1\n               }\n             end) == 2\n\n      assert Enum.count(messages, fn message ->\n               message == %{\n                 \"event\" => \"broadcast\",\n                 \"payload\" => payload_topic_2,\n                 \"ref\" => nil,\n                 \"topic\" => sub_topic_2\n               }\n             end) == 1\n\n      refute_receive {:socket_push, _, _}\n    end\n\n    test \"returns 422 when batch of messages includes badly formed messages\", %{conn: conn, tenant: tenant} do\n      tenant_topic = Tenants.tenant_topic(tenant, \"topic\")\n\n      subscribe(tenant_topic, \"topic\")\n\n      conn =\n        post(conn, Routes.broadcast_path(conn, :broadcast), %{\n          \"messages\" => [\n            %{\n              \"topic\" => \"topic\"\n            },\n            %{\n              \"payload\" => %{\"data\" => \"data\"}\n            },\n            %{\n              \"topic\" => \"topic\",\n              \"payload\" => %{\"data\" => \"data\"},\n              \"event\" => \"event\"\n            }\n          ]\n        })\n\n      assert Jason.decode!(conn.resp_body) == %{\n               \"errors\" => %{\n                 \"messages\" => [\n                   %{\"payload\" => [\"can't be blank\"], \"event\" => [\"can't be blank\"]},\n                   %{\"topic\" => [\"can't be blank\"], \"event\" => [\"can't be blank\"]},\n                   %{}\n                 ]\n               }\n             }\n\n      assert conn.status == 422\n\n      {:ok, rate_counter} = RateCounterHelper.tick!(Tenants.requests_per_second_rate(tenant))\n      assert rate_counter.avg != 0.0\n\n      {:ok, rate_counter} = RateCounterHelper.tick!(Tenants.events_per_second_rate(tenant))\n      assert rate_counter.avg == 0.0\n\n      refute_receive {:socket_push, _, _}\n    end\n\n    test \"returns 422 when batch of messages includes a message that exceeds the tenant payload size\", %{\n      conn: conn,\n      tenant: tenant\n    } do\n      sub_topic_1 = \"sub_topic_1\"\n      sub_topic_2 = \"sub_topic_2\"\n\n      payload_1 = %{\"data\" => \"data\"}\n      payload_2 = %{\"data\" => random_string(tenant.max_payload_size_in_kb * 1000 + 100)}\n      event_1 = \"event_1\"\n      event_2 = \"event_2\"\n\n      conn =\n        post(conn, Routes.broadcast_path(conn, :broadcast), %{\n          \"messages\" => [\n            %{\"topic\" => sub_topic_1, \"payload\" => payload_1, \"event\" => event_1},\n            %{\"topic\" => sub_topic_1, \"payload\" => payload_1, \"event\" => event_1},\n            %{\"topic\" => sub_topic_2, \"payload\" => payload_2, \"event\" => event_2}\n          ]\n        })\n\n      assert conn.status == 422\n    end\n  end\n\n  describe \"too many requests\" do\n    test \"batch will exceed rate limit\", %{conn: conn, tenant: tenant} do\n      requests_rate = Tenants.requests_per_second_rate(tenant)\n      events_rate = Tenants.events_per_second_rate(tenant)\n\n      RateCounter\n      |> stub(:new, fn _ -> {:ok, nil} end)\n      |> stub(:get, fn\n        ^requests_rate -> {:ok, %RateCounter{avg: 0}}\n        ^events_rate -> {:ok, %RateCounter{avg: 10}}\n      end)\n\n      conn =\n        post(conn, Routes.broadcast_path(conn, :broadcast), %{\n          \"messages\" =>\n            Stream.repeatedly(fn ->\n              %{\n                \"topic\" => Tenants.tenant_topic(tenant, \"sub_topic\"),\n                \"payload\" => %{\"data\" => \"data\"},\n                \"event\" => \"event\"\n              }\n            end)\n            |> Enum.take(1000)\n        })\n\n      assert conn.status == 429\n\n      assert conn.resp_body ==\n               Jason.encode!(%{\n                 message: \"Too many messages to broadcast, please reduce the batch size\"\n               })\n    end\n\n    test \"user has hit the rate limit\", %{conn: conn, tenant: tenant} do\n      requests_rate = Tenants.requests_per_second_rate(tenant)\n      events_rate = Tenants.events_per_second_rate(tenant)\n\n      RateCounter\n      |> stub(:new, fn _ -> {:ok, nil} end)\n      |> stub(:get, fn\n        ^requests_rate -> {:ok, %RateCounter{avg: 0}}\n        ^events_rate -> {:ok, %RateCounter{avg: 1000}}\n      end)\n\n      messages = [\n        %{\"topic\" => Tenants.tenant_topic(tenant, \"sub_topic\"), \"payload\" => %{\"data\" => \"data\"}, \"event\" => \"event\"}\n      ]\n\n      conn = generate_conn(conn, tenant)\n      conn = post(conn, Routes.broadcast_path(conn, :broadcast), %{\"messages\" => messages})\n      assert conn.status == 429\n      assert conn.resp_body == Jason.encode!(%{message: \"You have exceeded your rate limit\"})\n    end\n  end\n\n  describe \"unauthorized\" do\n    test \"invalid token returns 401\", %{conn: conn, tenant: tenant} do\n      conn =\n        conn\n        |> delete_req_header(\"authorization\")\n        |> put_req_header(\"accept\", \"application/json\")\n        |> put_req_header(\"x-api-key\", \"potato\")\n        |> then(&%{&1 | host: \"#{tenant.external_id}.supabase.com\"})\n\n      conn = post(conn, Routes.broadcast_path(conn, :broadcast), %{})\n      assert conn.status == 401\n    end\n\n    test \"expired token returns 401\", %{conn: conn, tenant: tenant} do\n      conn =\n        conn\n        |> delete_req_header(\"authorization\")\n        |> put_req_header(\"accept\", \"application/json\")\n        |> put_req_header(\"x-api-key\", @expired_token)\n        |> then(&%{&1 | host: \"#{tenant.external_id}.supabase.com\"})\n\n      conn = post(conn, Routes.broadcast_path(conn, :broadcast), %{})\n      assert conn.status == 401\n    end\n\n    test \"invalid tenant returns 401\", %{conn: conn} do\n      conn =\n        conn\n        |> put_req_header(\"accept\", \"application/json\")\n        |> put_req_header(\"x-api-key\", \"potato\")\n        |> then(&%{&1 | host: \"potato.supabase.com\"})\n\n      conn = post(conn, Routes.broadcast_path(conn, :broadcast), %{})\n      assert conn.status == 401\n    end\n  end\n\n  describe \"authorization for broadcast\" do\n    setup %{conn: conn, tenant: tenant} = context do\n      jwt_secret = Crypto.decrypt!(tenant.jwt_secret)\n\n      {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n      clean_table(db_conn, \"realtime\", \"messages\")\n\n      claims = %{sub: random_string(), role: context.role, exp: Joken.current_time() + 1_000}\n      signer = Joken.Signer.create(\"HS256\", jwt_secret)\n\n      jwt = Joken.generate_and_sign!(%{}, claims, signer)\n\n      conn =\n        conn\n        |> put_req_header(\"accept\", \"application/json\")\n        |> put_req_header(\"authorization\", \"Bearer #{jwt}\")\n        |> then(&%{&1 | host: \"#{tenant.external_id}.supabase.com\"})\n\n      {:ok, conn: conn, db_conn: db_conn, tenant: tenant}\n    end\n\n    @tag role: \"authenticated\"\n    test \"user with permission to read all channels and write to them is able to broadcast\", %{\n      conn: conn,\n      db_conn: db_conn,\n      tenant: tenant\n    } do\n      request_events_key = Tenants.requests_per_second_key(tenant)\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n      expect(TenantBroadcaster, :pubsub_broadcast, 5, fn _, _, _, _, _ -> :ok end)\n\n      messages_to_send =\n        Stream.repeatedly(fn -> generate_message_with_policies(db_conn, tenant) end)\n        |> Enum.take(5)\n\n      messages =\n        Enum.map(messages_to_send, fn %{topic: topic} ->\n          %{\n            \"topic\" => topic,\n            \"payload\" => %{\"content\" => \"payload\" <> topic},\n            \"event\" => \"event\" <> topic,\n            \"private\" => true\n          }\n        end)\n\n      GenCounter\n      |> expect(:add, fn ^request_events_key -> :ok end)\n      |> expect(:add, length(messages), fn ^broadcast_events_key -> :ok end)\n\n      conn = post(conn, Routes.broadcast_path(conn, :broadcast), %{\"messages\" => messages})\n\n      broadcast_calls = calls(&TenantBroadcaster.pubsub_broadcast/5)\n\n      Enum.each(messages_to_send, fn %{topic: topic} ->\n        broadcast_topic = Tenants.tenant_topic(tenant, topic, false)\n\n        message = %Phoenix.Socket.Broadcast{\n          topic: topic,\n          event: \"broadcast\",\n          payload: %{\n            \"payload\" => %{\"content\" => \"payload\" <> topic},\n            \"event\" => \"event\" <> topic,\n            \"type\" => \"broadcast\"\n          }\n        }\n\n        assert Enum.any?(broadcast_calls, fn\n                 [_, ^broadcast_topic, ^message, RealtimeChannel.MessageDispatcher, :broadcast] -> true\n                 _ -> false\n               end)\n      end)\n\n      assert conn.status == 202\n    end\n\n    @tag role: \"authenticated\"\n    test \"user with permission is also able to broadcast to open channel\", %{\n      conn: conn,\n      db_conn: db_conn,\n      tenant: tenant\n    } do\n      request_events_key = Tenants.requests_per_second_key(tenant)\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n      expect(TenantBroadcaster, :pubsub_broadcast, 6, fn _, _, _, _, _ -> :ok end)\n\n      channels =\n        Stream.repeatedly(fn -> generate_message_with_policies(db_conn, tenant) end)\n        |> Enum.take(5)\n\n      messages =\n        Enum.map(channels, fn %{topic: topic} ->\n          %{\n            \"topic\" => topic,\n            \"payload\" => %{\"content\" => \"payload\" <> topic},\n            \"event\" => \"event\" <> topic,\n            \"private\" => true\n          }\n        end)\n\n      messages =\n        messages ++\n          [\n            %{\n              \"topic\" => \"open_channel\",\n              \"payload\" => %{\"content\" => \"content_open_channel\"},\n              \"event\" => \"event_open_channel\"\n            }\n          ]\n\n      GenCounter\n      |> expect(:add, fn ^request_events_key -> :ok end)\n      |> expect(:add, length(messages), fn ^broadcast_events_key -> :ok end)\n\n      conn = post(conn, Routes.broadcast_path(conn, :broadcast), %{\"messages\" => messages})\n\n      broadcast_calls = calls(&TenantBroadcaster.pubsub_broadcast/5)\n\n      Enum.each(channels, fn %{topic: topic} ->\n        broadcast_topic = Tenants.tenant_topic(tenant, topic, false)\n\n        message = %Phoenix.Socket.Broadcast{\n          topic: topic,\n          event: \"broadcast\",\n          payload: %{\n            \"payload\" => %{\"content\" => \"payload\" <> topic},\n            \"event\" => \"event\" <> topic,\n            \"type\" => \"broadcast\"\n          }\n        }\n\n        assert Enum.count(broadcast_calls, fn\n                 [_, ^broadcast_topic, ^message, RealtimeChannel.MessageDispatcher, :broadcast] -> true\n                 _ -> false\n               end) == 1\n      end)\n\n      # Check open channel\n      message = %Phoenix.Socket.Broadcast{\n        topic: \"open_channel\",\n        event: \"broadcast\",\n        payload: %{\n          \"payload\" => %{\"content\" => \"content_open_channel\"},\n          \"event\" => \"event_open_channel\",\n          \"type\" => \"broadcast\"\n        }\n      }\n\n      open_channel_topic = Tenants.tenant_topic(tenant, \"open_channel\", true)\n\n      assert Enum.count(broadcast_calls, fn\n               [_, ^open_channel_topic, ^message, RealtimeChannel.MessageDispatcher, :broadcast] -> true\n               _ -> false\n             end) == 1\n\n      assert conn.status == 202\n    end\n\n    @tag role: \"authenticated\"\n    test \"user with permission to write a limited set is only able to broadcast to said set\", %{\n      conn: conn,\n      db_conn: db_conn,\n      tenant: tenant\n    } do\n      request_events_key = Tenants.requests_per_second_key(tenant)\n      broadcast_events_key = Tenants.events_per_second_key(tenant)\n      expect(TenantBroadcaster, :pubsub_broadcast, 5, fn _, _, _, _, _ -> :ok end)\n\n      messages_to_send =\n        Stream.repeatedly(fn -> generate_message_with_policies(db_conn, tenant) end)\n        |> Enum.take(5)\n\n      no_auth_channel = message_fixture(tenant)\n\n      messages =\n        Enum.map(messages_to_send ++ [no_auth_channel], fn %{topic: topic} ->\n          %{\n            \"topic\" => topic,\n            \"payload\" => %{\"content\" => \"payload\" <> topic},\n            \"event\" => \"event\" <> topic,\n            \"private\" => true\n          }\n        end)\n\n      GenCounter\n      |> expect(:add, fn ^request_events_key -> :ok end)\n      # remove the one message that won't be broadcasted for this user\n      |> expect(:add, length(messages) - 1, fn ^broadcast_events_key -> :ok end)\n\n      conn = post(conn, Routes.broadcast_path(conn, :broadcast), %{\"messages\" => messages})\n\n      broadcast_calls = calls(&TenantBroadcaster.pubsub_broadcast/5)\n\n      Enum.each(messages_to_send, fn %{topic: topic} ->\n        broadcast_topic = Tenants.tenant_topic(tenant, topic, false)\n\n        message = %Phoenix.Socket.Broadcast{\n          topic: topic,\n          event: \"broadcast\",\n          payload: %{\n            \"payload\" => %{\"content\" => \"payload\" <> topic},\n            \"event\" => \"event\" <> topic,\n            \"type\" => \"broadcast\"\n          }\n        }\n\n        assert Enum.count(broadcast_calls, fn\n                 [_, ^broadcast_topic, ^message, RealtimeChannel.MessageDispatcher, :broadcast] -> true\n                 _ -> false\n               end) == 1\n      end)\n\n      assert length(broadcast_calls) == length(messages_to_send)\n\n      assert conn.status == 202\n    end\n\n    @tag role: \"anon\"\n    test \"user without permission won't broadcast\", %{conn: conn, db_conn: db_conn, tenant: tenant} do\n      request_events_key = Tenants.requests_per_second_key(tenant)\n      reject(&TenantBroadcaster.pubsub_broadcast/5)\n\n      messages =\n        Stream.repeatedly(fn -> generate_message_with_policies(db_conn, tenant) end)\n        |> Enum.take(5)\n\n      # Duplicate messages to ensure same topics emit twice\n      messages = messages ++ messages\n\n      messages =\n        Enum.map(messages, fn %{topic: topic} ->\n          %{\n            \"topic\" => topic,\n            \"payload\" => %{\"content\" => random_string()},\n            \"event\" => random_string(),\n            \"private\" => true\n          }\n        end)\n\n      GenCounter\n      |> expect(:add, fn ^request_events_key -> 1 end)\n\n      conn = post(conn, Routes.broadcast_path(conn, :broadcast), %{\"messages\" => messages})\n\n      assert conn.status == 202\n    end\n  end\n\n  defp generate_message_with_policies(db_conn, tenant) do\n    message = message_fixture(tenant)\n    create_rls_policies(db_conn, [:authenticated_read_broadcast, :authenticated_write_broadcast], message)\n    message\n  end\n\n  defp generate_conn(conn, tenant) do\n    conn\n    |> put_req_header(\"accept\", \"application/json\")\n    |> put_req_header(\"authorization\", \"Bearer #{@token}\")\n    |> then(&%{&1 | host: \"#{tenant.external_id}.supabase.com\"})\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/controllers/fallback_controller_test.exs",
    "content": "defmodule RealtimeWeb.FallbackControllerTest do\n  use RealtimeWeb.ConnCase, async: true\n\n  alias RealtimeWeb.FallbackController\n\n  describe \"call/2\" do\n    test \"returns 404 with not found message\", %{conn: conn} do\n      conn = FallbackController.call(conn, {:error, :not_found})\n\n      assert json_response(conn, 404) == %{\"message\" => \"not found\"}\n    end\n\n    test \"returns 422 with changeset errors\", %{conn: conn} do\n      changeset =\n        {%{}, %{name: :string}}\n        |> Ecto.Changeset.cast(%{name: 123}, [:name])\n\n      conn = FallbackController.call(conn, {:error, changeset})\n\n      assert %{\"errors\" => _} = json_response(conn, 422)\n    end\n\n    test \"returns custom status with message\", %{conn: conn} do\n      conn = FallbackController.call(conn, {:error, :bad_request, \"invalid input\"})\n\n      assert json_response(conn, 400) == %{\"message\" => \"invalid input\"}\n    end\n\n    test \"returns 401 for generic error tuple\", %{conn: conn} do\n      conn = FallbackController.call(conn, {:error, \"something went wrong\"})\n\n      assert json_response(conn, 401) == %{\"message\" => \"Unauthorized\"}\n    end\n\n    test \"returns 422 for bare invalid changeset\", %{conn: conn} do\n      changeset =\n        {%{}, %{name: :string}}\n        |> Ecto.Changeset.cast(%{name: 123}, [:name])\n        |> Map.put(:valid?, false)\n\n      conn = FallbackController.call(conn, changeset)\n\n      assert %{\"errors\" => _} = json_response(conn, 422)\n    end\n\n    test \"returns 422 for unknown error format\", %{conn: conn} do\n      conn = FallbackController.call(conn, :unexpected_value)\n\n      assert json_response(conn, 422) == %{\"message\" => \"Unknown error\"}\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/controllers/legacy_metrics_controller_test.exs",
    "content": "defmodule RealtimeWeb.LegacyMetricsControllerTest do\n  use RealtimeWeb.ConnCase, async: false\n  alias Realtime.GenRpc\n\n  import ExUnit.CaptureLog\n  use Mimic\n\n  @global_metrics [\n    \"beam_system_schedulers_online_info\",\n    \"osmon_ram_usage\",\n    \"phoenix_channel_joined_total\",\n    \"phoenix_channel_handled_in_duration_milliseconds\",\n    \"phoenix_socket_connected_duration_milliseconds\",\n    \"phoenix_connections_active\",\n    \"phoenix_connections_max\",\n    \"realtime_global_rpc\",\n    \"realtime_channel_global_events\",\n    \"realtime_channel_global_presence_events\",\n    \"realtime_channel_global_db_events\",\n    \"realtime_channel_global_joins\",\n    \"realtime_channel_global_input_bytes\",\n    \"realtime_channel_global_output_bytes\",\n    \"realtime_channel_global_error\",\n    \"realtime_payload_size\"\n  ]\n\n  @tenant_metrics [\n    \"realtime_channel_events\",\n    \"realtime_channel_presence_events\",\n    \"realtime_channel_db_events\",\n    \"realtime_channel_joins\",\n    \"realtime_channel_input_bytes\",\n    \"realtime_channel_output_bytes\",\n    \"realtime_tenants_payload_size\",\n    \"realtime_replication_poller_query_duration\",\n    \"realtime_tenants_read_authorization_check\",\n    \"realtime_tenants_write_authorization_check\",\n    \"realtime_tenants_broadcast_from_database_latency_committed_at\",\n    \"realtime_tenants_broadcast_from_database_latency_inserted_at\",\n    \"realtime_tenants_replay\",\n    \"realtime_channel_error\"\n  ]\n\n  defp fire_all_tenant_events do\n    tenant_meta = %{tenant: \"test_tenant\"}\n\n    :telemetry.execute([:realtime, :channel, :error], %{code: 1}, %{code: 404})\n    :telemetry.execute([:realtime, :rate_counter, :channel, :events], %{sum: 5}, tenant_meta)\n    :telemetry.execute([:realtime, :rate_counter, :channel, :presence_events], %{sum: 3}, tenant_meta)\n    :telemetry.execute([:realtime, :rate_counter, :channel, :db_events], %{sum: 2}, tenant_meta)\n    :telemetry.execute([:realtime, :rate_counter, :channel, :joins], %{sum: 1}, tenant_meta)\n    :telemetry.execute([:realtime, :channel, :input_bytes], %{size: 1024}, tenant_meta)\n    :telemetry.execute([:realtime, :channel, :output_bytes], %{size: 2048}, tenant_meta)\n\n    :telemetry.execute(\n      [:realtime, :tenants, :payload, :size],\n      %{size: 512},\n      Map.put(tenant_meta, :message_type, \"broadcast\")\n    )\n\n    :telemetry.execute([:realtime, :replication, :poller, :query, :stop], %{duration: 100}, tenant_meta)\n    :telemetry.execute([:realtime, :tenants, :read_authorization_check], %{latency: 10}, tenant_meta)\n    :telemetry.execute([:realtime, :tenants, :write_authorization_check], %{latency: 15}, tenant_meta)\n\n    :telemetry.execute(\n      [:realtime, :tenants, :broadcast_from_database],\n      %{latency_committed_at: 50, latency_inserted_at: 40},\n      tenant_meta\n    )\n\n    :telemetry.execute([:realtime, :tenants, :replay], %{latency: 20}, tenant_meta)\n    :telemetry.execute([:realtime, :rpc], %{latency: 5}, %{success: true, mechanism: :erpc})\n\n    :telemetry.execute([:phoenix, :channel_joined], %{}, %{\n      result: :ok,\n      socket: %Phoenix.Socket{transport: :websocket, endpoint: RealtimeWeb.Endpoint}\n    })\n\n    :telemetry.execute([:phoenix, :channel_handled_in], %{duration: 500_000}, %{\n      socket: %Phoenix.Socket{endpoint: RealtimeWeb.Endpoint}\n    })\n\n    :telemetry.execute([:phoenix, :socket_connected], %{duration: 200_000}, %{\n      result: :ok,\n      endpoint: RealtimeWeb.Endpoint,\n      transport: :websocket,\n      serializer: Phoenix.Socket.V2.JSONSerializer\n    })\n  end\n\n  setup_all do\n    metrics_tags = %{\n      region: \"ap-southeast-2\",\n      host: \"anothernode@something.com\",\n      id: \"someid\"\n    }\n\n    {:ok, _} =\n      Clustered.start(nil,\n        extra_config: [{:realtime, :region, \"ap-southeast-2\"}, {:realtime, :metrics_tags, metrics_tags}]\n      )\n\n    :ok\n  end\n\n  setup %{conn: conn} do\n    previous = Application.get_env(:realtime, :metrics_separation_enabled)\n    Application.put_env(:realtime, :metrics_separation_enabled, false)\n    on_exit(fn -> Application.put_env(:realtime, :metrics_separation_enabled, previous) end)\n\n    jwt_secret = Application.fetch_env!(:realtime, :metrics_jwt_secret)\n    token = generate_jwt_token(jwt_secret, %{})\n\n    {:ok, conn: put_req_header(conn, \"authorization\", \"Bearer #{token}\")}\n  end\n\n  describe \"GET /metrics (legacy combined)\" do\n    test \"contains both global and tenant metrics\", %{conn: conn} do\n      fire_all_tenant_events()\n\n      response =\n        conn\n        |> get(~p\"/metrics\")\n        |> text_response(200)\n\n      for metric <- @global_metrics do\n        assert response =~ \"# HELP #{metric}\", \"expected global metric #{metric} to be present\"\n      end\n\n      for metric <- @tenant_metrics do\n        assert response =~ \"# HELP #{metric}\", \"expected tenant metric #{metric} to be present\"\n      end\n    end\n\n    test \"returns 200 and logs error on node timeout\", %{conn: conn} do\n      Mimic.stub(GenRpc, :call, fn node, mod, func, args, opts ->\n        if node != node() do\n          {:error, :rpc_error, :timeout}\n        else\n          call_original(GenRpc, :call, [node, mod, func, args, opts])\n        end\n      end)\n\n      log =\n        capture_log(fn ->\n          response =\n            conn\n            |> get(~p\"/metrics\")\n            |> text_response(200)\n\n          refute response =~ \"region=\\\"ap-southeast-2\\\"\"\n          assert response =~ \"region=\\\"us-east-1\\\"\"\n        end)\n\n      assert log =~ \"Cannot fetch metrics from the node\"\n    end\n\n    test \"returns 403 when authorization header is missing\", %{conn: conn} do\n      conn\n      |> delete_req_header(\"authorization\")\n      |> get(~p\"/metrics\")\n      |> response(403)\n    end\n\n    test \"returns 403 when authorization header is wrong\", %{conn: conn} do\n      conn\n      |> put_req_header(\"authorization\", \"Bearer #{generate_jwt_token(\"bad_secret\", %{})}\")\n      |> get(~p\"/metrics\")\n      |> response(403)\n    end\n  end\n\n  describe \"GET /metrics/:region (legacy combined)\" do\n    test \"contains both global and tenant metrics for region\", %{conn: conn} do\n      fire_all_tenant_events()\n\n      response =\n        conn\n        |> get(~p\"/metrics/us-east-1\")\n        |> text_response(200)\n\n      for metric <- @global_metrics do\n        assert response =~ \"# HELP #{metric}\", \"expected global metric #{metric} to be present\"\n      end\n\n      for metric <- @tenant_metrics do\n        assert response =~ \"# HELP #{metric}\", \"expected tenant metric #{metric} to be present\"\n      end\n    end\n\n    test \"returns 200 and logs error on node timeout\", %{conn: conn} do\n      Mimic.stub(GenRpc, :call, fn _node, _mod, _func, _args, _opts ->\n        {:error, :rpc_error, :timeout}\n      end)\n\n      log =\n        capture_log(fn ->\n          assert conn |> get(~p\"/metrics/ap-southeast-2\") |> text_response(200) == \"\"\n        end)\n\n      assert log =~ \"Cannot fetch metrics from the node\"\n    end\n\n    test \"returns 403 when authorization header is missing\", %{conn: conn} do\n      conn\n      |> delete_req_header(\"authorization\")\n      |> get(~p\"/metrics/ap-southeast-2\")\n      |> response(403)\n    end\n\n    test \"returns 403 when authorization header is wrong\", %{conn: conn} do\n      conn\n      |> put_req_header(\"authorization\", \"Bearer #{generate_jwt_token(\"bad_secret\", %{})}\")\n      |> get(~p\"/metrics/ap-southeast-2\")\n      |> response(403)\n    end\n  end\n\n  describe \"GET /tenant-metrics (legacy mode)\" do\n    test \"returns 404\", %{conn: conn} do\n      conn\n      |> get(~p\"/tenant-metrics\")\n      |> response(404)\n    end\n  end\n\n  describe \"GET /tenant-metrics/:region (legacy mode)\" do\n    test \"returns 404\", %{conn: conn} do\n      conn\n      |> get(~p\"/tenant-metrics/ap-southeast-2\")\n      |> response(404)\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/controllers/live_dasboard_test.exs",
    "content": "defmodule RealtimeWeb.LiveDashboardTest do\n  use RealtimeWeb.ConnCase\n  import Generators\n  import Mimic\n\n  describe \"live_dashboard with basic_auth\" do\n    setup do\n      user = random_string()\n      password = random_string()\n\n      Application.put_env(:realtime, :dashboard_auth, :basic_auth)\n      Application.put_env(:realtime, :dashboard_credentials, {user, password})\n\n      on_exit(fn ->\n        Application.delete_env(:realtime, :dashboard_auth)\n        Application.delete_env(:realtime, :dashboard_credentials)\n      end)\n\n      %{user: user, password: password}\n    end\n\n    test \"with credentials renders view\", %{conn: conn, user: user, password: password} do\n      path =\n        conn\n        |> using_basic_auth(user, password)\n        |> get(\"/admin/dashboard\")\n        |> redirected_to(302)\n\n      conn = conn |> recycle() |> using_basic_auth(user, password) |> get(path)\n\n      assert html_response(conn, 200) =~ \"Dashboard\"\n    end\n\n    test \"without credentials returns 401\", %{conn: conn} do\n      assert conn |> get(\"/admin/dashboard\") |> response(401)\n    end\n\n    test \"with wrong credentials returns 401\", %{conn: conn} do\n      assert conn |> using_basic_auth(\"wrong\", \"wrong\") |> get(\"/admin/dashboard\") |> response(401)\n    end\n  end\n\n  describe \"live_dashboard with zta\" do\n    setup do\n      Application.put_env(:realtime, :dashboard_auth, :zta)\n\n      on_exit(fn -> Application.delete_env(:realtime, :dashboard_auth) end)\n    end\n\n    test \"with valid cf token renders view\", %{conn: conn} do\n      stub(NimbleZTA.Cloudflare, :authenticate, fn _name, conn -> {conn, %{email: \"user@example.com\"}} end)\n\n      path = conn |> get(\"/admin/dashboard\") |> redirected_to(302)\n      conn = conn |> recycle() |> get(path)\n\n      assert html_response(conn, 200) =~ \"Dashboard\"\n    end\n\n    test \"without cf token returns 403\", %{conn: conn} do\n      stub(NimbleZTA.Cloudflare, :authenticate, fn _name, conn -> {conn, nil} end)\n\n      assert conn |> get(\"/admin/dashboard\") |> response(403)\n    end\n\n    test \"when zta service is unavailable returns 503\", %{conn: conn} do\n      stub(NimbleZTA.Cloudflare, :authenticate, fn _name, _conn -> exit(:noproc) end)\n\n      assert conn |> get(\"/admin/dashboard\") |> response(503)\n    end\n  end\n\n  defp using_basic_auth(conn, username, password) do\n    header_content = \"Basic \" <> Base.encode64(\"#{username}:#{password}\")\n    put_req_header(conn, \"authorization\", header_content)\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/controllers/metrics_controller_test.exs",
    "content": "defmodule RealtimeWeb.MetricsControllerTest do\n  # Usage of Clustered\n  # Also changing Application env\n  use RealtimeWeb.ConnCase, async: false\n  alias Realtime.GenRpc\n\n  import ExUnit.CaptureLog\n  use Mimic\n\n  # Metrics that must appear on GET /metrics (global endpoint)\n  @global_metrics [\n    # BEAM / OS\n    \"beam_system_schedulers_online_info\",\n    \"osmon_ram_usage\",\n    # Phoenix WebSocket channel, socket, and connection capacity\n    \"phoenix_channel_joined_total\",\n    \"phoenix_channel_handled_in_duration_milliseconds\",\n    \"phoenix_socket_connected_duration_milliseconds\",\n    \"phoenix_connections_active\",\n    \"phoenix_connections_max\",\n    # GenRPC call latency\n    \"realtime_global_rpc\",\n    # Global aggregates of tenant activity (no tenant label)\n    \"realtime_channel_global_events\",\n    \"realtime_channel_global_presence_events\",\n    \"realtime_channel_global_db_events\",\n    \"realtime_channel_global_joins\",\n    \"realtime_channel_global_input_bytes\",\n    \"realtime_channel_global_output_bytes\",\n    \"realtime_channel_global_error\",\n    \"realtime_payload_size\"\n  ]\n\n  # Metrics that must appear on GET /tenant-metrics (tenant endpoint)\n  @tenant_metrics [\n    # Per-tenant channel events\n    \"realtime_channel_events\",\n    \"realtime_channel_presence_events\",\n    \"realtime_channel_db_events\",\n    \"realtime_channel_joins\",\n    \"realtime_channel_input_bytes\",\n    \"realtime_channel_output_bytes\",\n    # Per-tenant payload size\n    \"realtime_tenants_payload_size\",\n    # Per-tenant latency / replication\n    \"realtime_replication_poller_query_duration\",\n    \"realtime_tenants_read_authorization_check\",\n    \"realtime_tenants_write_authorization_check\",\n    \"realtime_tenants_broadcast_from_database_latency_committed_at\",\n    \"realtime_tenants_broadcast_from_database_latency_inserted_at\",\n    \"realtime_tenants_replay\",\n    # Per-tenant errors\n    \"realtime_channel_error\"\n  ]\n\n  # Fires every telemetry event needed to populate all event-based metrics\n  defp fire_all_tenant_events do\n    tenant_meta = %{tenant: \"test_tenant\"}\n\n    :telemetry.execute([:realtime, :channel, :error], %{code: 1}, %{code: 404})\n    :telemetry.execute([:realtime, :rate_counter, :channel, :events], %{sum: 5}, tenant_meta)\n    :telemetry.execute([:realtime, :rate_counter, :channel, :presence_events], %{sum: 3}, tenant_meta)\n    :telemetry.execute([:realtime, :rate_counter, :channel, :db_events], %{sum: 2}, tenant_meta)\n    :telemetry.execute([:realtime, :rate_counter, :channel, :joins], %{sum: 1}, tenant_meta)\n    :telemetry.execute([:realtime, :channel, :input_bytes], %{size: 1024}, tenant_meta)\n    :telemetry.execute([:realtime, :channel, :output_bytes], %{size: 2048}, tenant_meta)\n\n    :telemetry.execute(\n      [:realtime, :tenants, :payload, :size],\n      %{size: 512},\n      Map.put(tenant_meta, :message_type, \"broadcast\")\n    )\n\n    :telemetry.execute([:realtime, :replication, :poller, :query, :stop], %{duration: 100}, tenant_meta)\n    :telemetry.execute([:realtime, :tenants, :read_authorization_check], %{latency: 10}, tenant_meta)\n    :telemetry.execute([:realtime, :tenants, :write_authorization_check], %{latency: 15}, tenant_meta)\n\n    :telemetry.execute(\n      [:realtime, :tenants, :broadcast_from_database],\n      %{latency_committed_at: 50, latency_inserted_at: 40},\n      tenant_meta\n    )\n\n    :telemetry.execute([:realtime, :tenants, :replay], %{latency: 20}, tenant_meta)\n    :telemetry.execute([:realtime, :rpc], %{latency: 5}, %{success: true, mechanism: :erpc})\n\n    :telemetry.execute([:phoenix, :channel_joined], %{}, %{\n      result: :ok,\n      socket: %Phoenix.Socket{transport: :websocket, endpoint: RealtimeWeb.Endpoint}\n    })\n\n    :telemetry.execute([:phoenix, :channel_handled_in], %{duration: 500_000}, %{\n      socket: %Phoenix.Socket{endpoint: RealtimeWeb.Endpoint}\n    })\n\n    :telemetry.execute([:phoenix, :socket_connected], %{duration: 200_000}, %{\n      result: :ok,\n      endpoint: RealtimeWeb.Endpoint,\n      transport: :websocket,\n      serializer: Phoenix.Socket.V2.JSONSerializer\n    })\n  end\n\n  setup_all do\n    metrics_tags = %{\n      region: \"ap-southeast-2\",\n      host: \"anothernode@something.com\",\n      id: \"someid\"\n    }\n\n    {:ok, _} =\n      Clustered.start(nil,\n        extra_config: [{:realtime, :region, \"ap-southeast-2\"}, {:realtime, :metrics_tags, metrics_tags}]\n      )\n\n    :ok\n  end\n\n  setup %{conn: conn} do\n    previous = Application.get_env(:realtime, :metrics_separation_enabled)\n    Application.put_env(:realtime, :metrics_separation_enabled, true)\n    on_exit(fn -> Application.put_env(:realtime, :metrics_separation_enabled, previous) end)\n\n    jwt_secret = Application.fetch_env!(:realtime, :metrics_jwt_secret)\n    token = generate_jwt_token(jwt_secret, %{})\n\n    {:ok, conn: put_req_header(conn, \"authorization\", \"Bearer #{token}\")}\n  end\n\n  describe \"GET /metrics\" do\n    test \"contains all expected global metrics\", %{conn: conn} do\n      fire_all_tenant_events()\n\n      response =\n        conn\n        |> get(~p\"/metrics\")\n        |> text_response(200)\n\n      for metric <- @global_metrics do\n        assert response =~ \"# HELP #{metric}\", \"expected global metric #{metric} to be present\"\n      end\n    end\n\n    test \"does not contain per-tenant labeled metrics\", %{conn: conn} do\n      fire_all_tenant_events()\n\n      response =\n        conn\n        |> get(~p\"/metrics\")\n        |> text_response(200)\n\n      for metric <- @tenant_metrics do\n        refute response =~ \"# HELP #{metric}\\n\", \"expected tenant metric #{metric} to be absent from global endpoint\"\n      end\n    end\n\n    test \"includes region tags from all nodes\", %{conn: conn} do\n      response =\n        conn\n        |> get(~p\"/metrics\")\n        |> text_response(200)\n\n      assert response =~ \"region=\\\"ap-southeast-2\\\"\"\n      assert response =~ \"region=\\\"us-east-1\\\"\"\n    end\n\n    test \"returns 200 and logs error on node timeout\", %{conn: conn} do\n      Mimic.stub(GenRpc, :call, fn node, mod, func, args, opts ->\n        if node != node() do\n          {:error, :rpc_error, :timeout}\n        else\n          call_original(GenRpc, :call, [node, mod, func, args, opts])\n        end\n      end)\n\n      log =\n        capture_log(fn ->\n          response =\n            conn\n            |> get(~p\"/metrics\")\n            |> text_response(200)\n\n          refute response =~ \"region=\\\"ap-southeast-2\\\"\"\n          assert response =~ \"region=\\\"us-east-1\\\"\"\n        end)\n\n      assert log =~ \"Cannot fetch metrics from the node\"\n    end\n\n    test \"returns 403 when authorization header is missing\", %{conn: conn} do\n      conn\n      |> delete_req_header(\"authorization\")\n      |> get(~p\"/metrics\")\n      |> response(403)\n    end\n\n    test \"returns 403 when authorization header is wrong\", %{conn: conn} do\n      conn\n      |> put_req_header(\"authorization\", \"Bearer #{generate_jwt_token(\"bad_secret\", %{})}\")\n      |> get(~p\"/metrics\")\n      |> response(403)\n    end\n  end\n\n  describe \"GET /tenant-metrics\" do\n    test \"contains all expected tenant metrics\", %{conn: conn} do\n      fire_all_tenant_events()\n\n      response =\n        conn\n        |> get(~p\"/tenant-metrics\")\n        |> text_response(200)\n\n      for metric <- @tenant_metrics do\n        assert response =~ \"# HELP #{metric}\", \"expected tenant metric #{metric} to be present\"\n      end\n    end\n\n    test \"does not contain global aggregated or BEAM metrics\", %{conn: conn} do\n      fire_all_tenant_events()\n\n      response =\n        conn\n        |> get(~p\"/tenant-metrics\")\n        |> text_response(200)\n\n      for metric <- @global_metrics do\n        refute response =~ \"# HELP #{metric}\\n\", \"expected global metric #{metric} to be absent from tenant endpoint\"\n      end\n    end\n\n    test \"returns 200 and logs error on node timeout\", %{conn: conn} do\n      Mimic.stub(GenRpc, :call, fn _node, _mod, _func, _args, _opts ->\n        {:error, :rpc_error, :timeout}\n      end)\n\n      log =\n        capture_log(fn ->\n          assert conn |> get(~p\"/tenant-metrics\") |> text_response(200) == \"\"\n        end)\n\n      assert log =~ \"Cannot fetch metrics from the node\"\n    end\n\n    test \"returns 403 when authorization header is missing\", %{conn: conn} do\n      conn\n      |> delete_req_header(\"authorization\")\n      |> get(~p\"/tenant-metrics\")\n      |> response(403)\n    end\n\n    test \"returns 403 when authorization header is wrong\", %{conn: conn} do\n      conn\n      |> put_req_header(\"authorization\", \"Bearer #{generate_jwt_token(\"bad_secret\", %{})}\")\n      |> get(~p\"/tenant-metrics\")\n      |> response(403)\n    end\n  end\n\n  describe \"GET /metrics/:region\" do\n    test \"returns global metrics scoped to the given region\", %{conn: conn} do\n      response =\n        conn\n        |> get(~p\"/metrics/ap-southeast-2\")\n        |> text_response(200)\n\n      assert response =~ \"# HELP beam_system_schedulers_online_info\"\n      assert response =~ \"region=\\\"ap-southeast-2\\\"\"\n      refute response =~ \"region=\\\"us-east-1\\\"\"\n    end\n\n    test \"does not contain per-tenant labeled metrics\", %{conn: conn} do\n      fire_all_tenant_events()\n\n      response =\n        conn\n        |> get(~p\"/metrics/us-east-1\")\n        |> text_response(200)\n\n      for metric <- @tenant_metrics do\n        refute response =~ \"# HELP #{metric}\\n\",\n               \"expected tenant metric #{metric} to be absent from region global endpoint\"\n      end\n    end\n\n    test \"returns 200 and logs error on node timeout\", %{conn: conn} do\n      Mimic.stub(GenRpc, :call, fn _node, _mod, _func, _args, _opts ->\n        {:error, :rpc_error, :timeout}\n      end)\n\n      log =\n        capture_log(fn ->\n          assert conn |> get(~p\"/metrics/ap-southeast-2\") |> text_response(200) == \"\"\n        end)\n\n      assert log =~ \"Cannot fetch metrics from the node\"\n    end\n\n    test \"returns 403 when authorization header is missing\", %{conn: conn} do\n      conn\n      |> delete_req_header(\"authorization\")\n      |> get(~p\"/metrics/ap-southeast-2\")\n      |> response(403)\n    end\n\n    test \"returns 403 when authorization header is wrong\", %{conn: conn} do\n      conn\n      |> put_req_header(\"authorization\", \"Bearer #{generate_jwt_token(\"bad_secret\", %{})}\")\n      |> get(~p\"/metrics/ap-southeast-2\")\n      |> response(403)\n    end\n  end\n\n  describe \"GET /tenant-metrics/:region\" do\n    test \"returns tenant metrics scoped to the given region\", %{conn: conn} do\n      fire_all_tenant_events()\n\n      response =\n        conn\n        |> get(~p\"/tenant-metrics/us-east-1\")\n        |> text_response(200)\n\n      for metric <- @tenant_metrics do\n        assert response =~ \"# HELP #{metric}\", \"expected tenant metric #{metric} to be present\"\n      end\n    end\n\n    test \"does not contain global aggregated or BEAM metrics\", %{conn: conn} do\n      fire_all_tenant_events()\n\n      response =\n        conn\n        |> get(~p\"/tenant-metrics/ap-southeast-2\")\n        |> text_response(200)\n\n      for metric <- @global_metrics do\n        refute response =~ \"# HELP #{metric}\\n\",\n               \"expected global metric #{metric} to be absent from region tenant endpoint\"\n      end\n    end\n\n    test \"returns 200 and logs error on node timeout\", %{conn: conn} do\n      Mimic.stub(GenRpc, :call, fn _node, _mod, _func, _args, _opts ->\n        {:error, :rpc_error, :timeout}\n      end)\n\n      log =\n        capture_log(fn ->\n          assert conn |> get(~p\"/tenant-metrics/ap-southeast-2\") |> text_response(200) == \"\"\n        end)\n\n      assert log =~ \"Cannot fetch metrics from the node\"\n    end\n\n    test \"returns 403 when authorization header is missing\", %{conn: conn} do\n      conn\n      |> delete_req_header(\"authorization\")\n      |> get(~p\"/tenant-metrics/ap-southeast-2\")\n      |> response(403)\n    end\n\n    test \"returns 403 when authorization header is wrong\", %{conn: conn} do\n      conn\n      |> put_req_header(\"authorization\", \"Bearer #{generate_jwt_token(\"bad_secret\", %{})}\")\n      |> get(~p\"/tenant-metrics/ap-southeast-2\")\n      |> response(403)\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/controllers/openapi_controller_test.exs",
    "content": "defmodule RealtimeWeb.Controllers.OpenapiControllerTest do\n  use RealtimeWeb.ConnCase\n\n  describe \"openapi\" do\n    test \"returns the openapi spec\", %{conn: conn} do\n      conn = get(conn, ~p\"/api/openapi\")\n      assert json_response(conn, 200)\n    end\n  end\n\n  describe \"swaggerui\" do\n    test \"returns the swaggerui\", %{conn: conn} do\n      conn = get(conn, ~p\"/swaggerui\")\n      assert html_response(conn, 200) =~ \"Swagger UI\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/controllers/page_controller_test.exs",
    "content": "defmodule RealtimeWeb.PageControllerTest do\n  use RealtimeWeb.ConnCase, async: false\n\n  import ExUnit.CaptureLog\n\n  test \"GET / renders index page\", %{conn: conn} do\n    conn = get(conn, \"/\")\n    assert html_response(conn, 200) =~ \" Supabase Realtime: Multiplayer Edition\"\n  end\n\n  test \"GET /healthcheck returns ok status\", %{conn: conn} do\n    conn = get(conn, \"/healthcheck\")\n    assert text_response(conn, 200) == \"ok\"\n  end\n\n  describe \"GET /healthcheck logging behavior\" do\n    setup do\n      original_value = Application.get_env(:realtime, :disable_healthcheck_logging, false)\n      on_exit(fn -> Application.put_env(:realtime, :disable_healthcheck_logging, original_value) end)\n      :ok\n    end\n\n    test \"logs request when DISABLE_HEALTHCHECK_LOGGING is false\", %{conn: conn} do\n      Application.put_env(:realtime, :disable_healthcheck_logging, false)\n\n      log =\n        capture_log(fn ->\n          conn = get(conn, \"/healthcheck\")\n          assert text_response(conn, 200) == \"ok\"\n        end)\n\n      assert log =~ \"GET /healthcheck\"\n    end\n\n    test \"does not log request when DISABLE_HEALTHCHECK_LOGGING is true\", %{conn: conn} do\n      Application.put_env(:realtime, :disable_healthcheck_logging, true)\n\n      log =\n        capture_log(fn ->\n          conn = get(conn, \"/healthcheck\")\n          assert text_response(conn, 200) == \"ok\"\n        end)\n\n      refute log =~ \"GET /healthcheck\"\n    end\n\n    test \"logs request when DISABLE_HEALTHCHECK_LOGGING is not set (default)\", %{conn: conn} do\n      Application.delete_env(:realtime, :disable_healthcheck_logging)\n\n      log =\n        capture_log(fn ->\n          conn = get(conn, \"/healthcheck\")\n          assert text_response(conn, 200) == \"ok\"\n        end)\n\n      assert log =~ \"GET /healthcheck\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/controllers/tenant_controller_test.exs",
    "content": "defmodule RealtimeWeb.TenantControllerTest do\n  # Can't run async true because under the hood Cachex is used and it doesn't see Ecto.Sandbox\n  # Also using global otel_simple_processor\n  use RealtimeWeb.ConnCase, async: false\n\n  import ExUnit.CaptureLog\n  require OpenTelemetry.Tracer, as: Tracer\n\n  alias Realtime.Api.Tenant\n  alias Realtime.Crypto\n  alias Realtime.Database\n  alias Realtime.PromEx.Plugins.Tenants\n  alias Realtime.Tenants\n  alias Realtime.Tenants.Cache\n  alias Realtime.Tenants.Connect\n  alias Realtime.UsersCounter\n\n  @invalid_attrs %{external_id: nil, jwt_secret: nil, extensions: [], name: nil}\n\n  setup context do\n    %{conn: conn} = context\n    key = Application.get_env(:realtime, :api_jwt_secret)\n    jwt = generate_jwt_token(key)\n\n    conn =\n      conn\n      |> put_req_header(\"accept\", \"application/json\")\n      |> put_req_header(\"authorization\", \"Bearer #{jwt}\")\n\n    :otel_simple_processor.set_exporter(:otel_exporter_pid, self())\n\n    {:ok, conn: conn}\n  end\n\n  defp with_tenant(_context) do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    %{tenant: tenant}\n  end\n\n  describe \"show tenant\" do\n    setup [:with_tenant]\n\n    test \"removes db_password\", %{conn: conn, tenant: tenant} do\n      conn = get(conn, ~p\"/api/tenants/#{tenant.external_id}\")\n      response = json_response(conn, 200)\n      refute get_in(response, [\"data\", \"extensions\", Access.at(0), \"settings\", \"db_password\"])\n    end\n\n    test \"returns not found on non existing tenant\", %{conn: conn} do\n      conn = get(conn, ~p\"/api/tenants/no\")\n      response = json_response(conn, 404)\n      assert response == %{\"message\" => \"not found\"}\n    end\n\n    test \"sets appropriate observability metadata\", %{conn: conn, tenant: tenant} do\n      external_id = tenant.external_id\n\n      # opentelemetry_phoenix expects to be a child of the originating cowboy process hence the Task here :shrug:\n      Tracer.with_span \"test\" do\n        Task.async(fn ->\n          get(conn, ~p\"/api/tenants/#{external_id}\")\n\n          assert Logger.metadata()[:external_id] == external_id\n          assert Logger.metadata()[:project] == external_id\n        end)\n        |> Task.await()\n      end\n\n      assert_receive {:span, span(name: \"GET /api/tenants/:tenant_id\", attributes: attributes)}\n\n      assert attributes(map: %{external_id: ^external_id}) = attributes\n    end\n  end\n\n  describe \"create tenant with post\" do\n    test \"run migrations on creation and encrypts credentials\", %{conn: conn} do\n      external_id = random_string()\n      {:ok, port} = Containers.checkout()\n\n      assert nil == Tenants.get_tenant_by_external_id(external_id)\n\n      attrs = default_tenant_attrs(port)\n      attrs = Map.put(attrs, \"external_id\", external_id)\n\n      conn = post(conn, ~p\"/api/tenants\", tenant: attrs)\n\n      assert %{\"id\" => _id, \"external_id\" => ^external_id} = json_response(conn, 201)[\"data\"]\n\n      [%{\"settings\" => settings}] = json_response(conn, 201)[\"data\"][\"extensions\"]\n\n      assert Crypto.encrypt!(\"127.0.0.1\") == settings[\"db_host\"]\n      assert Crypto.encrypt!(\"postgres\") == settings[\"db_name\"]\n      assert Crypto.encrypt!(\"supabase_admin\") == settings[\"db_user\"]\n      refute settings[\"db_password\"]\n      Process.sleep(100)\n\n      %{broadcast_adapter: :gen_rpc, extensions: [%{settings: settings}]} =\n        tenant = Tenants.get_tenant_by_external_id(external_id)\n\n      assert Crypto.encrypt!(\"postgres\") == settings[\"db_password\"]\n\n      assert tenant.migrations_ran > 0\n    end\n  end\n\n  describe \"create tenant with put\" do\n    test \"run migrations on creation and encrypts credentials\", %{conn: conn} do\n      external_id = random_string()\n      {:ok, port} = Containers.checkout()\n\n      assert nil == Tenants.get_tenant_by_external_id(external_id)\n\n      attrs = default_tenant_attrs(port)\n\n      conn = put(conn, ~p\"/api/tenants/#{external_id}\", tenant: attrs)\n\n      assert %{\"id\" => _id, \"external_id\" => ^external_id} = json_response(conn, 201)[\"data\"]\n      [%{\"settings\" => settings}] = json_response(conn, 201)[\"data\"][\"extensions\"]\n\n      assert Crypto.encrypt!(\"127.0.0.1\") == settings[\"db_host\"]\n      assert Crypto.encrypt!(\"postgres\") == settings[\"db_name\"]\n      assert Crypto.encrypt!(\"supabase_admin\") == settings[\"db_user\"]\n      refute settings[\"db_password\"]\n      Process.sleep(100)\n      %{extensions: [%{settings: settings}]} = tenant = Tenants.get_tenant_by_external_id(external_id)\n\n      assert Crypto.encrypt!(\"postgres\") == settings[\"db_password\"]\n      assert tenant.migrations_ran > 0\n    end\n  end\n\n  describe \"upsert with post\" do\n    setup [:with_tenant]\n\n    test \"renders tenant when data is valid\", %{conn: conn, tenant: tenant} do\n      external_id = tenant.external_id\n      port = Database.from_tenant(tenant, \"realtime_test\", :stop).port\n      attrs = default_tenant_attrs(port)\n      attrs = Map.put(attrs, \"external_id\", external_id)\n      conn = post(conn, ~p\"/api/tenants\", tenant: attrs)\n      assert %{\"id\" => _id, \"external_id\" => ^external_id} = json_response(conn, 200)[\"data\"]\n\n      conn = get(conn, Routes.tenant_path(conn, :show, external_id))\n      assert ^external_id = json_response(conn, 200)[\"data\"][\"external_id\"]\n      assert 200 = json_response(conn, 200)[\"data\"][\"max_concurrent_users\"]\n      assert 100 = json_response(conn, 200)[\"data\"][\"max_channels_per_client\"]\n      assert 100 = json_response(conn, 200)[\"data\"][\"max_events_per_second\"]\n      assert 100 = json_response(conn, 200)[\"data\"][\"max_joins_per_second\"]\n    end\n\n    test \"can set max_client_presence_events_per_window\", %{conn: conn, tenant: tenant} do\n      external_id = tenant.external_id\n      port = Database.from_tenant(tenant, \"realtime_test\", :stop).port\n      attrs = default_tenant_attrs(port) |> Map.put(\"max_client_presence_events_per_window\", 42)\n      attrs = Map.put(attrs, \"external_id\", external_id)\n\n      conn = post(conn, ~p\"/api/tenants\", tenant: attrs)\n      assert %{\"max_client_presence_events_per_window\" => 42} = json_response(conn, 200)[\"data\"]\n\n      conn = get(conn, Routes.tenant_path(conn, :show, external_id))\n      assert 42 = json_response(conn, 200)[\"data\"][\"max_client_presence_events_per_window\"]\n    end\n\n    test \"max_client_presence_events_per_window defaults to nil\", %{conn: conn, tenant: tenant} do\n      external_id = tenant.external_id\n\n      conn = get(conn, Routes.tenant_path(conn, :show, external_id))\n      assert is_nil(json_response(conn, 200)[\"data\"][\"max_client_presence_events_per_window\"])\n    end\n\n    test \"can set client_presence_window_ms\", %{conn: conn, tenant: tenant} do\n      external_id = tenant.external_id\n      port = Database.from_tenant(tenant, \"realtime_test\", :stop).port\n      attrs = default_tenant_attrs(port) |> Map.put(\"client_presence_window_ms\", 5_000)\n      attrs = Map.put(attrs, \"external_id\", external_id)\n\n      conn = post(conn, ~p\"/api/tenants\", tenant: attrs)\n      assert %{\"client_presence_window_ms\" => 5_000} = json_response(conn, 200)[\"data\"]\n\n      conn = get(conn, Routes.tenant_path(conn, :show, external_id))\n      assert 5_000 = json_response(conn, 200)[\"data\"][\"client_presence_window_ms\"]\n    end\n\n    test \"client_presence_window_ms defaults to nil\", %{conn: conn, tenant: tenant} do\n      external_id = tenant.external_id\n\n      conn = get(conn, Routes.tenant_path(conn, :show, external_id))\n      assert is_nil(json_response(conn, 200)[\"data\"][\"client_presence_window_ms\"])\n    end\n\n    test \"renders errors when data is invalid\", %{conn: conn} do\n      conn = post(conn, ~p\"/api/tenants\", tenant: @invalid_attrs)\n      assert json_response(conn, 422)[\"errors\"] != %{}\n    end\n\n    test \"returns 403 when jwt is invalid\", %{conn: conn} do\n      conn = put_req_header(conn, \"authorization\", \"Bearer potato\")\n      conn = post(conn, ~p\"/api/tenants\", tenant: default_tenant_attrs(5000))\n      assert response(conn, 403)\n    end\n  end\n\n  describe \"upsert with put\" do\n    setup [:with_tenant]\n\n    test \"renders tenant when data is valid\", %{tenant: tenant, conn: conn} do\n      external_id = tenant.external_id\n      port = Database.from_tenant(tenant, \"realtime_test\", :stop).port\n      attrs = default_tenant_attrs(port)\n\n      conn = put(conn, ~p\"/api/tenants/#{external_id}\", tenant: attrs)\n      assert %{\"id\" => _id, \"external_id\" => ^external_id} = json_response(conn, 200)[\"data\"]\n\n      conn = get(conn, Routes.tenant_path(conn, :show, external_id))\n      assert ^external_id = json_response(conn, 200)[\"data\"][\"external_id\"]\n      assert 200 = json_response(conn, 200)[\"data\"][\"max_concurrent_users\"]\n      assert 100 = json_response(conn, 200)[\"data\"][\"max_channels_per_client\"]\n      assert 100 = json_response(conn, 200)[\"data\"][\"max_events_per_second\"]\n      assert 100 = json_response(conn, 200)[\"data\"][\"max_joins_per_second\"]\n    end\n\n    test \"can update max_client_presence_events_per_window\", %{tenant: tenant, conn: conn} do\n      external_id = tenant.external_id\n      port = Database.from_tenant(tenant, \"realtime_test\", :stop).port\n      attrs = default_tenant_attrs(port) |> Map.put(\"max_client_presence_events_per_window\", 99)\n\n      conn = put(conn, ~p\"/api/tenants/#{external_id}\", tenant: attrs)\n      assert %{\"max_client_presence_events_per_window\" => 99} = json_response(conn, 200)[\"data\"]\n    end\n\n    test \"can update client_presence_window_ms\", %{tenant: tenant, conn: conn} do\n      external_id = tenant.external_id\n      port = Database.from_tenant(tenant, \"realtime_test\", :stop).port\n      attrs = default_tenant_attrs(port) |> Map.put(\"client_presence_window_ms\", 10_000)\n\n      conn = put(conn, ~p\"/api/tenants/#{external_id}\", tenant: attrs)\n      assert %{\"client_presence_window_ms\" => 10_000} = json_response(conn, 200)[\"data\"]\n    end\n\n    test \"can update presence_enabled\", %{tenant: tenant, conn: conn} do\n      external_id = tenant.external_id\n      port = Database.from_tenant(tenant, \"realtime_test\", :stop).port\n\n      assert tenant.presence_enabled == false\n\n      attrs = default_tenant_attrs(port) |> Map.put(\"presence_enabled\", true)\n      conn = put(conn, ~p\"/api/tenants/#{external_id}\", tenant: attrs)\n      assert %{\"presence_enabled\" => true} = json_response(conn, 200)[\"data\"]\n\n      updated_tenant = Realtime.Api.get_tenant_by_external_id(external_id, use_replica?: false)\n      assert updated_tenant.presence_enabled == true\n    end\n\n    test \"renders errors when data is invalid\", %{conn: conn} do\n      conn = put(conn, ~p\"/api/tenants/#{random_string()}\", tenant: @invalid_attrs)\n      assert json_response(conn, 422)[\"errors\"] != %{}\n    end\n\n    test \"returns 403 when jwt is invalid\", %{conn: conn} do\n      conn = put_req_header(conn, \"authorization\", \"Bearer potato\")\n      conn = put(conn, ~p\"/api/tenants/external_id\", tenant: default_tenant_attrs(5000))\n      assert response(conn, 403)\n    end\n\n    test \"sets appropriate observability metadata\", %{conn: conn, tenant: tenant} do\n      external_id = tenant.external_id\n      port = Database.from_tenant(tenant, \"realtime_test\", :stop).port\n      attrs = default_tenant_attrs(port)\n\n      # opentelemetry_phoenix expects to be a child of the originating cowboy process hence the Task here :shrug:\n      Tracer.with_span \"test\" do\n        Task.async(fn ->\n          put(conn, ~p\"/api/tenants/#{external_id}\", tenant: attrs)\n\n          assert Logger.metadata()[:external_id] == external_id\n          assert Logger.metadata()[:project] == external_id\n        end)\n        |> Task.await()\n      end\n\n      assert_receive {:span, span(name: \"PUT /api/tenants/:tenant_id\", attributes: attributes)}\n\n      assert attributes(map: %{external_id: ^external_id}) = attributes\n    end\n  end\n\n  describe \"delete tenant\" do\n    setup [:with_tenant]\n\n    test \"deletes chosen tenant\", %{conn: conn, tenant: tenant} do\n      {:ok, _pid} = Connect.lookup_or_start_connection(tenant.external_id)\n\n      assert Connect.ready?(tenant.external_id)\n\n      assert Cache.get_tenant_by_external_id(tenant.external_id)\n      {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n\n      %{rows: [rows]} =\n        Postgrex.query!(db_conn, \"SELECT slot_name FROM pg_replication_slots\", [])\n\n      assert rows > 0\n      conn = delete(conn, ~p\"/api/tenants/#{tenant.external_id}\")\n      assert response(conn, 204)\n\n      refute Cache.get_tenant_by_external_id(tenant.external_id)\n      refute Tenants.get_tenant_by_external_id(tenant.external_id)\n      Process.sleep(500)\n\n      assert {:ok, %{rows: []}} =\n               Postgrex.query(db_conn, \"SELECT slot_name FROM pg_replication_slots\", [])\n    end\n\n    test \"tenant doesn't exist\", %{conn: conn} do\n      conn = delete(conn, ~p\"/api/tenants/nope\")\n      assert response(conn, 204)\n    end\n\n    test \"returns 403 when jwt is invalid\", %{conn: conn, tenant: tenant} do\n      conn = put_req_header(conn, \"authorization\", \"Bearer potato\")\n      conn = delete(conn, ~p\"/api/tenants/#{tenant.external_id}\")\n      assert response(conn, 403) == \"\"\n    end\n\n    test \"sets appropriate observability metadata\", %{conn: conn, tenant: tenant} do\n      external_id = tenant.external_id\n\n      # opentelemetry_phoenix expects to be a child of the originating cowboy process hence the Task here :shrug:\n      Tracer.with_span \"test\" do\n        Task.async(fn ->\n          delete(conn, ~p\"/api/tenants/#{external_id}\")\n\n          assert Logger.metadata()[:external_id] == external_id\n          assert Logger.metadata()[:project] == external_id\n        end)\n        |> Task.await()\n      end\n\n      assert_receive {:span, span(name: \"DELETE /api/tenants/:tenant_id\", attributes: attributes)}\n\n      assert attributes(map: %{external_id: ^external_id}) = attributes\n    end\n  end\n\n  describe \"reload tenant\" do\n    setup [:with_tenant]\n\n    test \"reload when tenant does exist\", %{conn: conn, tenant: %{external_id: external_id} = tenant} do\n      Phoenix.PubSub.subscribe(Realtime.PubSub, \"realtime:operations:\" <> external_id)\n\n      [%{settings: settings}] = tenant.extensions\n      settings = Map.put(settings, \"id\", external_id)\n      {:ok, _} = Extensions.PostgresCdcRls.start(settings)\n      wait_on_postgres_cdc_rls(external_id)\n\n      {:ok, manager_pid, _} = Extensions.PostgresCdcRls.get_manager_conn(external_id)\n      {:ok, connect_pid} = Connect.lookup_or_start_connection(external_id)\n      Process.monitor(manager_pid)\n      Process.monitor(connect_pid)\n\n      assert Process.alive?(manager_pid)\n      assert Process.alive?(connect_pid)\n\n      %{status: status} = post(conn, ~p\"/api/tenants/#{external_id}/reload\")\n\n      assert status == 204\n\n      assert_receive :disconnect\n      assert_receive {:DOWN, _, :process, ^manager_pid, _}\n      assert_receive {:DOWN, _, :process, ^connect_pid, _}\n\n      refute Process.alive?(manager_pid)\n      refute Process.alive?(connect_pid)\n    end\n\n    test \"reload when tenant does not exist\", %{conn: conn} do\n      %{status: status} = post(conn, ~p\"/api/tenants/nope/reload\")\n      assert status == 404\n    end\n\n    test \"returns 403 when jwt is invalid\", %{conn: conn, tenant: tenant} do\n      conn = put_req_header(conn, \"authorization\", \"Bearer potato\")\n      conn = post(conn, ~p\"/api/tenants/#{tenant.external_id}/reload\")\n      assert response(conn, 403) == \"\"\n    end\n\n    test \"sets appropriate observability metadata\", %{conn: conn, tenant: tenant} do\n      external_id = tenant.external_id\n\n      # opentelemetry_phoenix expects to be a child of the originating cowboy process hence the Task here :shrug:\n      Tracer.with_span \"test\" do\n        Task.async(fn ->\n          post(conn, ~p\"/api/tenants/#{tenant.external_id}/reload\")\n\n          assert Logger.metadata()[:external_id] == external_id\n          assert Logger.metadata()[:project] == external_id\n        end)\n        |> Task.await()\n      end\n\n      assert_receive {:span, span(name: \"POST /api/tenants/:tenant_id/reload\", attributes: attributes)}\n\n      assert attributes(map: %{external_id: ^external_id}) = attributes\n    end\n  end\n\n  describe \"shutdown Connect module for tenant\" do\n    setup [:with_tenant]\n\n    test \"shuts down Connect process when tenant exists\", %{conn: conn, tenant: %{external_id: external_id}} do\n      Phoenix.PubSub.subscribe(Realtime.PubSub, \"realtime:operations:\" <> external_id)\n\n      {:ok, connect_pid} = Connect.lookup_or_start_connection(external_id)\n      Process.monitor(connect_pid)\n\n      assert Process.alive?(connect_pid)\n\n      %{status: status} = post(conn, ~p\"/api/tenants/#{external_id}/shutdown\")\n\n      assert status == 204\n      assert_receive {:DOWN, _, :process, ^connect_pid, _}\n      refute Process.alive?(connect_pid)\n    end\n\n    test \"returns 204 when tenant exists but Connect is not running\", %{conn: conn, tenant: %{external_id: external_id}} do\n      %{status: status} = post(conn, ~p\"/api/tenants/#{external_id}/shutdown\")\n      assert status == 204\n    end\n\n    test \"returns 404 when tenant does not exist\", %{conn: conn} do\n      %{status: status} = post(conn, ~p\"/api/tenants/nope/shutdown\")\n      assert status == 404\n    end\n\n    test \"returns 403 when jwt is invalid\", %{conn: conn, tenant: tenant} do\n      conn = put_req_header(conn, \"authorization\", \"Bearer potato\")\n      conn = post(conn, ~p\"/api/tenants/#{tenant.external_id}/shutdown\")\n      assert response(conn, 403) == \"\"\n    end\n\n    test \"sets appropriate observability metadata\", %{conn: conn, tenant: tenant} do\n      external_id = tenant.external_id\n\n      Tracer.with_span \"test\" do\n        Task.async(fn ->\n          post(conn, ~p\"/api/tenants/#{tenant.external_id}/shutdown\")\n\n          assert Logger.metadata()[:external_id] == external_id\n          assert Logger.metadata()[:project] == external_id\n        end)\n        |> Task.await()\n      end\n\n      assert_receive {:span, span(name: \"POST /api/tenants/:tenant_id/shutdown\", attributes: attributes)}\n\n      assert attributes(map: %{external_id: ^external_id}) = attributes\n    end\n  end\n\n  describe \"health check tenant\" do\n    setup [:with_tenant]\n\n    setup do\n      previous_region = Application.get_env(:realtime, :region)\n      Application.put_env(:realtime, :region, \"us-east-1\")\n      on_exit(fn -> Application.put_env(:realtime, :region, previous_region) end)\n    end\n\n    test \"health check when tenant does not exist\", %{conn: conn} do\n      %{status: status} = get(conn, ~p\"/api/tenants/nope/health\")\n      assert status == 404\n    end\n\n    test \"healthy tenant with 0 client connections\", %{\n      conn: conn,\n      tenant: %Tenant{external_id: external_id}\n    } do\n      conn = get(conn, ~p\"/api/tenants/#{external_id}/health\")\n      data = json_response(conn, 200)[\"data\"]\n      Connect.shutdown(external_id)\n\n      assert %{\n               \"healthy\" => true,\n               \"db_connected\" => false,\n               \"replication_connected\" => false,\n               \"connected_cluster\" => 0,\n               \"region\" => \"us-east-1\",\n               \"node\" => \"#{node()}\"\n             } == data\n    end\n\n    test \"unhealthy tenant with 1 client connections and no db connection\", %{\n      conn: conn,\n      tenant: %Tenant{external_id: ext_id}\n    } do\n      # Fake adding a connected client here\n      # No connection to the tenant database\n      UsersCounter.add(self(), ext_id)\n\n      conn = get(conn, ~p\"/api/tenants/#{ext_id}/health\")\n      data = json_response(conn, 200)[\"data\"]\n\n      assert %{\n               \"healthy\" => false,\n               \"db_connected\" => false,\n               \"replication_connected\" => false,\n               \"connected_cluster\" => 1,\n               \"region\" => \"us-east-1\",\n               \"node\" => \"#{node()}\"\n             } == data\n    end\n\n    test \"healthy tenant with db connection but no replication connection\", %{\n      conn: conn,\n      tenant: %Tenant{external_id: ext_id}\n    } do\n      {:ok, db_conn} = Connect.lookup_or_start_connection(ext_id)\n      # Fake adding a connected client here\n      UsersCounter.add(self(), ext_id)\n\n      # Fake a db connection without replication (replication_conn: nil)\n      :syn.register(Realtime.Tenants.Connect, ext_id, self(), %{conn: nil, region: \"us-east-1\", replication_conn: nil})\n\n      :syn.update_registry(Realtime.Tenants.Connect, ext_id, fn _pid, meta ->\n        %{meta | conn: db_conn}\n      end)\n\n      conn = get(conn, ~p\"/api/tenants/#{ext_id}/health\")\n      data = json_response(conn, 200)[\"data\"]\n\n      assert %{\n               \"healthy\" => true,\n               \"db_connected\" => true,\n               \"replication_connected\" => false,\n               \"connected_cluster\" => 1,\n               \"region\" => \"us-east-1\",\n               \"node\" => \"#{node()}\"\n             } == data\n    end\n\n    test \"healthy tenant with db and replication connection\", %{conn: conn, tenant: %Tenant{external_id: ext_id}} do\n      {:ok, db_conn} = Connect.lookup_or_start_connection(ext_id)\n      # Fake adding a connected client here\n      UsersCounter.add(self(), ext_id)\n\n      # Fake a db connection with replication_conn in syn metadata\n      :syn.register(Realtime.Tenants.Connect, ext_id, self(), %{conn: nil, region: \"us-east-1\", replication_conn: nil})\n\n      :syn.update_registry(Realtime.Tenants.Connect, ext_id, fn _pid, meta ->\n        %{meta | conn: db_conn, replication_conn: self()}\n      end)\n\n      conn = get(conn, ~p\"/api/tenants/#{ext_id}/health\")\n      data = json_response(conn, 200)[\"data\"]\n\n      assert %{\n               \"healthy\" => true,\n               \"db_connected\" => true,\n               \"replication_connected\" => true,\n               \"connected_cluster\" => 1,\n               \"region\" => \"us-east-1\",\n               \"node\" => \"#{node()}\"\n             } == data\n    end\n\n    test \"returns 403 when jwt is invalid\", %{conn: conn, tenant: tenant} do\n      conn = put_req_header(conn, \"authorization\", \"Bearer potato\")\n      conn = get(conn, ~p\"/api/tenants/#{tenant.external_id}/health\")\n      assert response(conn, 403) == \"\"\n    end\n\n    test \"runs migrations\", %{conn: conn} do\n      tenant = Containers.checkout_tenant(run_migrations: false)\n\n      {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n      assert {:error, _} = Postgrex.query(db_conn, \"SELECT * FROM realtime.messages\", [])\n\n      conn = get(conn, ~p\"/api/tenants/#{tenant.external_id}/health\")\n      data = json_response(conn, 200)[\"data\"]\n      Process.sleep(1000)\n\n      assert {:ok, %{rows: []}} = Postgrex.query(db_conn, \"SELECT * FROM realtime.messages\", [])\n\n      assert %{\"healthy\" => true, \"db_connected\" => false, \"replication_connected\" => false, \"connected_cluster\" => 0} =\n               data\n    end\n\n    test \"sets appropriate observability metadata\", %{conn: conn, tenant: tenant} do\n      external_id = tenant.external_id\n      # opentelemetry_phoenix expects to be a child of the originating cowboy process hence the Task here :shrug:\n      Tracer.with_span \"test\" do\n        Task.async(fn ->\n          get(conn, ~p\"/api/tenants/#{tenant.external_id}/health\")\n\n          assert Logger.metadata()[:external_id] == external_id\n          assert Logger.metadata()[:project] == external_id\n        end)\n        |> Task.await()\n      end\n\n      assert_receive {:span, span(name: \"GET /api/tenants/:tenant_id/health\", attributes: attributes)}\n\n      assert attributes(map: %{external_id: ^external_id}) = attributes\n    end\n\n    test \"logs request when DISABLE_HEALTHCHECK_LOGGING is false\", %{conn: conn, tenant: tenant} do\n      original_value = Application.get_env(:realtime, :disable_healthcheck_logging, false)\n      Application.put_env(:realtime, :disable_healthcheck_logging, false)\n      on_exit(fn -> Application.put_env(:realtime, :disable_healthcheck_logging, original_value) end)\n\n      log =\n        capture_log(fn ->\n          conn = get(conn, ~p\"/api/tenants/#{tenant.external_id}/health\")\n          assert json_response(conn, 200)\n        end)\n\n      assert log =~ \"GET /api/tenants\"\n      assert log =~ \"/health\"\n    end\n\n    test \"does not log request when DISABLE_HEALTHCHECK_LOGGING is true\", %{conn: conn, tenant: tenant} do\n      original_value = Application.get_env(:realtime, :disable_healthcheck_logging, false)\n      Application.put_env(:realtime, :disable_healthcheck_logging, true)\n      on_exit(fn -> Application.put_env(:realtime, :disable_healthcheck_logging, original_value) end)\n\n      log =\n        capture_log(fn ->\n          conn = get(conn, ~p\"/api/tenants/#{tenant.external_id}/health\")\n          assert json_response(conn, 200)\n        end)\n\n      refute log =~ \"GET /api/tenants\"\n      refute log =~ \"/health\"\n    end\n\n    test \"logs request when DISABLE_HEALTHCHECK_LOGGING is not set (default)\", %{conn: conn, tenant: tenant} do\n      original_value = Application.get_env(:realtime, :disable_healthcheck_logging, false)\n      Application.delete_env(:realtime, :disable_healthcheck_logging)\n      on_exit(fn -> Application.put_env(:realtime, :disable_healthcheck_logging, original_value) end)\n\n      log =\n        capture_log(fn ->\n          conn = get(conn, ~p\"/api/tenants/#{tenant.external_id}/health\")\n          assert json_response(conn, 200)\n        end)\n\n      assert log =~ \"GET /api/tenants\"\n      assert log =~ \"/health\"\n    end\n  end\n\n  defp default_tenant_attrs(port) do\n    %{\n      \"extensions\" => [\n        %{\n          \"type\" => \"postgres_cdc_rls\",\n          \"settings\" => %{\n            \"db_host\" => \"127.0.0.1\",\n            \"db_name\" => \"postgres\",\n            \"db_user\" => \"supabase_admin\",\n            \"db_password\" => \"postgres\",\n            \"db_port\" => \"#{port}\",\n            \"poll_interval\" => 100,\n            \"poll_max_changes\" => 100,\n            \"poll_max_record_bytes\" => 1_048_576,\n            \"region\" => \"us-east-1\",\n            \"ssl_enforced\" => false\n          }\n        }\n      ],\n      \"postgres_cdc_default\" => \"postgres_cdc_rls\",\n      \"jwt_secret\" => \"new secret\"\n    }\n  end\n\n  defp wait_on_postgres_cdc_rls(external_id, attempt \\\\ 10)\n\n  defp wait_on_postgres_cdc_rls(external_id, 0) do\n    raise \"Postgres CDC RLS manager connection not established for #{external_id} after multiple attempts\"\n  end\n\n  defp wait_on_postgres_cdc_rls(external_id, attempt) do\n    case Extensions.PostgresCdcRls.get_manager_conn(external_id) do\n      {:ok, _, _} ->\n        :ok\n\n      {:error, _} ->\n        Process.sleep(100)\n        wait_on_postgres_cdc_rls(external_id, attempt - 1)\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/dashboard/tenant_info_test.exs",
    "content": "defmodule Realtime.Dashboard.TenantInfoTest do\n  use RealtimeWeb.ConnCase\n  import Phoenix.LiveViewTest\n  import Generators\n\n  setup do\n    Application.put_env(:realtime, :dashboard_auth, :basic_auth)\n    Application.put_env(:realtime, :dashboard_credentials, {\"user\", \"pass\"})\n\n    on_exit(fn ->\n      Application.delete_env(:realtime, :dashboard_auth)\n      Application.delete_env(:realtime, :dashboard_credentials)\n    end)\n\n    tenant = tenant_fixture()\n    conn = using_basic_auth(build_conn(), \"user\", \"pass\")\n\n    %{tenant: tenant, conn: conn}\n  end\n\n  test \"renders lookup form\", %{conn: conn} do\n    {:ok, _view, html} = live(conn, \"/admin/dashboard/tenant_info\")\n\n    assert html =~ \"Tenant Info\"\n    assert html =~ \"project_ref\"\n  end\n\n  test \"shows tenant info for valid project ref\", %{conn: conn, tenant: tenant} do\n    {:ok, view, _html} = live(conn, \"/admin/dashboard/tenant_info\")\n\n    html = view |> element(\"form[phx-submit='lookup']\") |> render_submit(%{project_ref: tenant.external_id})\n\n    assert html =~ tenant.external_id\n    assert html =~ tenant.name\n    assert html =~ \"postgres_cdc_rls\"\n  end\n\n  test \"shows error for unknown project ref\", %{conn: conn} do\n    {:ok, view, _html} = live(conn, \"/admin/dashboard/tenant_info\")\n\n    html = view |> element(\"form[phx-submit='lookup']\") |> render_submit(%{project_ref: \"nonexistent\"})\n\n    assert html =~ \"Tenant not found\"\n  end\n\n  test \"does not show db_password\", %{conn: conn, tenant: tenant} do\n    {:ok, view, _html} = live(conn, \"/admin/dashboard/tenant_info\")\n\n    html = view |> element(\"form[phx-submit='lookup']\") |> render_submit(%{project_ref: tenant.external_id})\n\n    refute html =~ \"db_password\"\n  end\n\n  test \"shows decrypted db_host\", %{conn: conn, tenant: tenant} do\n    {:ok, view, _html} = live(conn, \"/admin/dashboard/tenant_info\")\n\n    html = view |> element(\"form[phx-submit='lookup']\") |> render_submit(%{project_ref: tenant.external_id})\n\n    assert html =~ \"127.0.0.1\"\n  end\n\n  defp using_basic_auth(conn, username, password) do\n    header_content = \"Basic \" <> Base.encode64(\"#{username}:#{password}\")\n    put_req_header(conn, \"authorization\", header_content)\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/integration/tracing_test.exs",
    "content": "defmodule Realtime.Integration.TracingTest do\n  # Async due to usage of global otel_simple_processor\n  use RealtimeWeb.ConnCase, async: false\n\n  @parent_id \"b7ad6b7169203331\"\n  @traceparent \"00-0af7651916cd43dd8448eb211c80319c-#{@parent_id}-01\"\n  @span_parent_id Integer.parse(@parent_id, 16) |> elem(0)\n\n  # This is doing a blackbox approach because tracing is not captured with normal Phoenix controller tests\n  # We need cowboy, endpoint and router to trigger their telemetry events\n\n  test \"traces basic HTTP request with phoenix and cowboy information\" do\n    :otel_simple_processor.set_exporter(:otel_exporter_pid, self())\n    url = RealtimeWeb.Endpoint.url() <> \"/healthcheck\"\n\n    baggage_request_id = UUID.uuid4()\n\n    response =\n      Req.get!(url, headers: [{\"traceparent\", @traceparent}, {\"baggage\", \"sb-request-id=#{baggage_request_id}\"}])\n\n    assert_receive {:span, span(name: \"GET /healthcheck\", attributes: attributes, parent_span_id: @span_parent_id)}\n\n    assert attributes(\n             map: %{\n               \"http.request.method\": :GET,\n               \"http.response.status_code\": 200,\n               \"http.route\": \"/healthcheck\",\n               \"phoenix.action\": :healthcheck,\n               \"phoenix.plug\": RealtimeWeb.PageController,\n               \"url.path\": \"/healthcheck\",\n               \"url.scheme\": :http\n             }\n           ) = attributes\n\n    assert %{\"x-request-id\" => [^baggage_request_id]} = response.headers\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/live/inspector_live/index_test.exs",
    "content": "defmodule RealtimeWeb.InspectorLive.IndexTest do\n  use RealtimeWeb.ConnCase\n  import Phoenix.LiveViewTest\n\n  describe \"Inspector LiveView\" do\n    test \"renders inspector page\", %{conn: conn} do\n      {:ok, _view, html} = live(conn, ~p\"/inspector\")\n\n      assert html =~ \"Realtime Inspector\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/live/page_live/index_test.exs",
    "content": "defmodule RealtimeWeb.PageLive.IndexTest do\n  use RealtimeWeb.ConnCase\n  import Phoenix.LiveViewTest\n\n  describe \"Index LiveView\" do\n    test \"renders page successfully\", %{conn: conn} do\n      {:ok, _view, html} = live(conn, \"/\")\n\n      assert html =~ \"Supabase Realtime: Multiplayer Edition\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/live/status_live/index_test.exs",
    "content": "defmodule RealtimeWeb.StatusLive.IndexTest do\n  use RealtimeWeb.ConnCase\n  import Phoenix.LiveViewTest\n\n  alias Realtime.Latency.Payload\n  alias Realtime.Nodes\n  alias RealtimeWeb.Endpoint\n\n  describe \"Status LiveView\" do\n    test \"renders status page\", %{conn: conn} do\n      {:ok, _view, html} = live(conn, ~p\"/status\")\n\n      assert html =~ \"Realtime Status\"\n    end\n\n    test \"receives broadcast from PubSub\", %{conn: conn} do\n      {:ok, view, _html} = live(conn, ~p\"/status\")\n\n      payload = %Payload{\n        from_node: Nodes.short_node_id_from_name(:\"pink@127.0.0.1\"),\n        node: Nodes.short_node_id_from_name(:\"orange@127.0.0.1\"),\n        latency: \"42ms\",\n        timestamp: DateTime.utc_now()\n      }\n\n      Endpoint.broadcast(\"admin:cluster\", \"ping\", payload)\n\n      html = render(view)\n      assert html =~ \"42ms\"\n      assert html =~ \"pink@127.0.0.1_orange@127.0.0.1\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/live/tenants_live/index_test.exs",
    "content": "defmodule RealtimeWeb.TenantsLive.IndexTest do\n  use RealtimeWeb.ConnCase\n  import Phoenix.LiveViewTest\n  import Generators\n  import Mimic\n\n  describe \"TenantsLive Index with basic_auth\" do\n    setup do\n      user = random_string()\n      password = random_string()\n\n      Application.put_env(:realtime, :dashboard_auth, :basic_auth)\n      Application.put_env(:realtime, :dashboard_credentials, {user, password})\n\n      on_exit(fn ->\n        Application.delete_env(:realtime, :dashboard_auth)\n        Application.delete_env(:realtime, :dashboard_credentials)\n      end)\n\n      %{user: user, password: password}\n    end\n\n    test \"renders tenant view\", %{conn: conn, user: user, password: password} do\n      {:ok, _view, html} =\n        conn |> using_basic_auth(user, password) |> live(~p\"/admin/tenants\")\n\n      assert html =~ \"Listing all Supabase Realtime tenants.\"\n    end\n\n    test \"returns 401 if no credentials\", %{conn: conn} do\n      assert conn |> get(~p\"/admin/tenants\") |> response(401)\n    end\n\n    test \"returns 401 with wrong credentials\", %{conn: conn} do\n      assert conn |> using_basic_auth(\"wrong\", \"wrong\") |> get(~p\"/admin/tenants\") |> response(401)\n    end\n  end\n\n  describe \"TenantsLive Index with zta\" do\n    setup do\n      Application.put_env(:realtime, :dashboard_auth, :zta)\n\n      on_exit(fn -> Application.delete_env(:realtime, :dashboard_auth) end)\n    end\n\n    test \"renders tenant view with valid cf token\", %{conn: conn} do\n      stub(NimbleZTA.Cloudflare, :authenticate, fn _name, conn -> {conn, %{email: \"user@example.com\"}} end)\n\n      {:ok, _view, html} = live(conn, ~p\"/admin/tenants\")\n\n      assert html =~ \"Listing all Supabase Realtime tenants.\"\n    end\n\n    test \"returns 403 without cf token\", %{conn: conn} do\n      stub(NimbleZTA.Cloudflare, :authenticate, fn _name, conn -> {conn, nil} end)\n\n      assert conn |> get(~p\"/admin/tenants\") |> response(403)\n    end\n\n    test \"returns 503 when zta service is unavailable\", %{conn: conn} do\n      stub(NimbleZTA.Cloudflare, :authenticate, fn _name, _conn -> exit(:noproc) end)\n\n      assert conn |> get(~p\"/admin/tenants\") |> response(503)\n    end\n  end\n\n  defp using_basic_auth(conn, username, password) do\n    header_content = \"Basic \" <> Base.encode64(\"#{username}:#{password}\")\n    put_req_header(conn, \"authorization\", header_content)\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/plugs/assign_tenant_test.exs",
    "content": "defmodule RealtimeWeb.Plugs.AssignTenantTest do\n  # Use of global otel_simple_processor\n  use RealtimeWeb.ConnCase, async: false\n\n  require OpenTelemetry.Tracer, as: Tracer\n\n  alias Realtime.Api\n\n  @tenant %{\n    \"external_id\" => \"external_id\",\n    \"name\" => \"external_id\",\n    \"extensions\" => [\n      %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"db_host\" => \"127.0.0.1\",\n          \"db_name\" => \"postgres\",\n          \"db_user\" => \"supabase_admin\",\n          \"db_password\" => \"postgres\",\n          \"db_port\" => \"6432\",\n          \"poll_interval\" => 100,\n          \"poll_max_changes\" => 100,\n          \"poll_max_record_bytes\" => 1_048_576,\n          \"region\" => \"us-east-1\"\n        }\n      }\n    ],\n    \"postgres_cdc_default\" => \"postgres_cdc_rls\",\n    \"jwt_secret\" => \"new secret\"\n  }\n\n  setup %{conn: conn} do\n    conn =\n      conn\n      |> put_req_header(\"accept\", \"application/json\")\n\n    {:ok, _tenant} = Api.create_tenant(@tenant)\n\n    {:ok, conn: conn}\n  end\n\n  test \"serve a 401 unauthorized when we can't find a tenant\", %{conn: conn} do\n    conn =\n      conn\n      |> Map.put(:host, \"not-found-tenant.localhost.com\")\n      |> get(Routes.ping_path(conn, :ping))\n\n    assert conn.status == 401\n  end\n\n  @tag :failing\n  test \"serve a 401 unauthorized when we have a bad request\", %{conn: conn} do\n    conn =\n      conn\n      |> Map.put(:host, \"localhost.com\")\n      |> get(Routes.ping_path(conn, :ping))\n\n    assert conn.status == 401\n  end\n\n  test \"assigns a tenant\", %{conn: conn} do\n    tenant_fixture(%{external_id: \"localhost\"})\n\n    conn =\n      conn\n      |> Map.put(:host, \"localhost.localhost.com\")\n      |> get(Routes.ping_path(conn, :ping))\n\n    assert conn.status == 200\n  end\n\n  test \"assigns a tenant even with lots of subdomains\", %{conn: conn} do\n    tenant_fixture(%{external_id: \"localhost\"})\n\n    conn =\n      conn\n      |> Map.put(:host, \"localhost.realtime.localhost.com\")\n      |> get(Routes.ping_path(conn, :ping))\n\n    assert conn.status == 200\n  end\n\n  test \"sets appropriate observability metadata\", %{conn: conn} do\n    external_id = \"localhost\"\n    tenant_fixture(%{external_id: external_id})\n\n    :otel_simple_processor.set_exporter(:otel_exporter_pid, self())\n\n    # opentelemetry_phoenix expects to be a child of the originating cowboy process hence the Task here :shrug:\n    Tracer.with_span \"test\" do\n      Task.async(fn ->\n        conn\n        |> Map.put(:host, \"localhost.localhost.com\")\n        |> get(Routes.ping_path(conn, :ping))\n\n        assert Logger.metadata()[:external_id] == external_id\n        assert Logger.metadata()[:project] == external_id\n      end)\n      |> Task.await()\n    end\n\n    assert_receive {:span, span(name: \"GET /api/ping\", attributes: attributes)}\n\n    assert attributes(map: %{external_id: ^external_id}) = attributes\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/plugs/auth_tenant_test.exs",
    "content": "defmodule RealtimeWeb.AuthTenantTest do\n  use RealtimeWeb.ConnCase, async: true\n\n  import Plug.Conn\n\n  alias RealtimeWeb.AuthTenant\n\n  @token \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjIsInJvbGUiOiJmb28iLCJleHAiOiJiYXIifQ.Ret2CevUozCsPhpgW2FMeFL7RooLgoOvfQzNpLBj5ak\"\n  describe \"without tenant\" do\n    test \"returns 401\", %{conn: conn} do\n      conn = AuthTenant.call(conn, %{})\n      assert conn.status == 401\n      assert conn.halted\n    end\n  end\n\n  describe \"with tenant\" do\n    setup %{conn: conn} = context do\n      api_key = Map.get(context, :api_key)\n      header = Map.get(context, :header)\n\n      conn = if api_key, do: put_req_header(conn, header, api_key), else: conn\n\n      conn = assign(conn, :tenant, tenant_fixture())\n      %{conn: conn}\n    end\n\n    test \"returns 401 if token isn't present in header\", %{conn: conn} do\n      conn = AuthTenant.call(conn, %{})\n      assert conn.status == 401\n      assert conn.halted\n    end\n\n    @tag api_key: \"Bearer invalid\", header: \"authorization\"\n    test \"returns 401 if token in authorization header isn't valid\", %{conn: conn} do\n      conn = AuthTenant.call(conn, %{})\n      assert conn.status == 401\n      assert conn.halted\n    end\n\n    @tag api_key: \"Bearer #{@token}\", header: \"authorization\"\n    test \"returns non halted and null status if token in authorization header is valid\", %{\n      conn: conn\n    } do\n      conn = AuthTenant.call(conn, %{})\n      refute conn.status\n      refute conn.halted\n    end\n\n    @tag api_key: \"bearer #{@token}\", header: \"authorization\"\n    test \"returns non halted and null status if token in authorization header is valid and case insensitive\",\n         %{\n           conn: conn\n         } do\n      conn = AuthTenant.call(conn, %{})\n      refute conn.status\n      refute conn.halted\n    end\n\n    @tag api_key: \"earer #{@token}\", header: \"authorization\"\n    test \"returns halted and unauthorized if token is badly formatted\", %{\n      conn: conn\n    } do\n      conn = AuthTenant.call(conn, %{})\n      assert conn.status == 401\n      assert conn.halted\n    end\n\n    @tag api_key: \"invalid\", header: \"apikey\"\n    test \"returns 401 if token in apikey header isn't valid\", %{conn: conn} do\n      conn = AuthTenant.call(conn, %{})\n      assert conn.status == 401\n      assert conn.halted\n    end\n\n    @tag api_key: @token, header: \"apikey\"\n    test \"returns non halted and null status if token in apikey header is valid\", %{\n      conn: conn\n    } do\n      conn = AuthTenant.call(conn, %{})\n      refute conn.status\n      refute conn.halted\n    end\n\n    @tag api_key: \"Bearer #{@token}\", header: \"authorization\"\n    test \"assigns jwt information on success\", %{\n      conn: conn\n    } do\n      conn = AuthTenant.call(conn, %{})\n      assert conn.assigns.jwt == @token\n      assert conn.assigns.claims == %{\"exp\" => \"bar\", \"iat\" => 1_516_239_022, \"role\" => \"foo\"}\n      assert conn.assigns.role == \"foo\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/plugs/baggage_request_id_test.exs",
    "content": "defmodule RealtimeWeb.Plugs.BaggageRequestIdTest do\n  use ExUnit.Case, async: true\n  import Plug.Test\n  import Plug.Conn\n\n  alias RealtimeWeb.Plugs.BaggageRequestId\n\n  defp call(conn, opts) do\n    BaggageRequestId.call(conn, BaggageRequestId.init(opts))\n  end\n\n  test \"uses request id from baggage if valid\" do\n    conn =\n      conn(:get, \"/\")\n      |> put_req_header(\"baggage\", \"request-id=1234567890\")\n      |> call([])\n\n    assert [\"1234567890\"] = get_resp_header(conn, \"x-request-id\")\n    assert Logger.metadata()[:request_id] == \"1234567890\"\n  end\n\n  test \"uses configured request id key from baggage\" do\n    conn =\n      conn(:get, \"/\")\n      |> put_req_header(\"baggage\", \"my-request-id=1234567890\")\n      |> call(baggage_key: \"my-request-id\")\n\n    assert [\"1234567890\"] = get_resp_header(conn, \"x-request-id\")\n    assert Logger.metadata()[:request_id] == \"1234567890\"\n  end\n\n  test \"generates new request id if not valid from baggage: min size\" do\n    conn =\n      conn(:get, \"/\")\n      # too short\n      |> put_req_header(\"baggage\", \"request-id=123\")\n      |> call([])\n\n    [res_request_id] = get_resp_header(conn, \"x-request-id\")\n    assert ^res_request_id = Logger.metadata()[:request_id]\n    assert generated_request_id?(res_request_id)\n    assert res_request_id != \"123\"\n  end\n\n  test \"generates new request id if not valid from baggage: max size\" do\n    request_id = String.duplicate(\"0\", 201)\n\n    conn =\n      conn(:get, \"/\")\n      # too long\n      |> put_req_header(\"baggage\", \"request-id=#{request_id}\")\n      |> call([])\n\n    [res_request_id] = get_resp_header(conn, \"x-request-id\")\n    assert ^res_request_id = Logger.metadata()[:request_id]\n    assert generated_request_id?(res_request_id)\n    assert res_request_id != request_id\n  end\n\n  test \"generates new request id if there is no bahhage\" do\n    conn =\n      conn(:get, \"/\")\n      |> call([])\n\n    [res_request_id] = get_resp_header(conn, \"x-request-id\")\n    assert ^res_request_id = Logger.metadata()[:request_id]\n    assert generated_request_id?(res_request_id)\n  end\n\n  test \"generates new request id if not include inside baggage\" do\n    conn =\n      conn(:get, \"/\")\n      |> put_req_header(\"baggage\", \"something-else=123\")\n      |> call([])\n\n    [res_request_id] = get_resp_header(conn, \"x-request-id\")\n    assert ^res_request_id = Logger.metadata()[:request_id]\n    assert generated_request_id?(res_request_id)\n  end\n\n  defp generated_request_id?(request_id) do\n    Regex.match?(~r/\\A[A-Za-z0-9-_]+\\z/, request_id)\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/plugs/metrics_mode_test.exs",
    "content": "defmodule RealtimeWeb.Plugs.MetricsModeTest do\n  use RealtimeWeb.ConnCase, async: true\n\n  alias RealtimeWeb.Plugs.MetricsMode\n\n  setup do\n    original = Application.get_env(:realtime, :metrics_separation_enabled)\n    on_exit(fn -> Application.put_env(:realtime, :metrics_separation_enabled, original) end)\n    :ok\n  end\n\n  describe \"call/2\" do\n    test \"dispatches tenant-metrics path to 404 in legacy mode\", %{conn: conn} do\n      Application.put_env(:realtime, :metrics_separation_enabled, false)\n      conn = %{conn | path_info: [\"tenant-metrics\", \"some_tenant\"]}\n      conn = MetricsMode.call(conn, [])\n      assert conn.status == 404\n      assert conn.halted\n    end\n\n    test \"passes through unchanged when metrics_separation_enabled is true\", %{conn: conn} do\n      Application.put_env(:realtime, :metrics_separation_enabled, true)\n      result = MetricsMode.call(conn, [])\n      refute result.halted\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/plugs/rate_limiter_test.exs",
    "content": "defmodule RealtimeWeb.Plugs.RateLimiterTest do\n  use RealtimeWeb.ConnCase\n\n  alias Realtime.Api\n\n  @tenant %{\n    \"external_id\" => \"localhost\",\n    \"name\" => \"localhost\",\n    \"max_events_per_second\" => 0,\n    \"extensions\" => [\n      %{\n        \"type\" => \"postgres_cdc_rls\",\n        \"settings\" => %{\n          \"db_host\" => \"127.0.0.1\",\n          \"db_name\" => \"postgres\",\n          \"db_user\" => \"supabase_admin\",\n          \"db_password\" => \"postgres\",\n          \"db_port\" => \"6432\",\n          \"poll_interval\" => 100,\n          \"poll_max_changes\" => 100,\n          \"poll_max_record_bytes\" => 1_048_576,\n          \"region\" => \"us-east-1\"\n        }\n      }\n    ],\n    \"postgres_cdc_default\" => \"postgres_cdc_rls\",\n    \"jwt_secret\" => \"new secret\"\n  }\n\n  setup %{conn: conn} do\n    conn =\n      conn\n      |> put_req_header(\"accept\", \"application/json\")\n\n    {:ok, _tenant} = Api.create_tenant(@tenant)\n\n    {:ok, conn: conn}\n  end\n\n  test \"serve a 429 when rate limit is set to 0\", %{conn: conn} do\n    conn =\n      conn\n      |> Map.put(:host, \"localhost.localhost.com\")\n      |> get(Routes.ping_path(conn, :ping))\n\n    assert conn.status == 429\n  end\n\n  test \"serve a 200 when rate limit is set to 100\", %{conn: conn} do\n    {:ok, _tenant} = Api.update_tenant_by_external_id(@tenant[\"external_id\"], %{\"max_events_per_second\" => 100})\n\n    conn =\n      conn\n      |> Map.put(:host, \"localhost.localhost.com\")\n      |> get(Routes.ping_path(conn, :ping))\n\n    assert conn.status == 200\n  end\n\n  test \"passes through when tenant is not in assigns\", %{conn: conn} do\n    alias RealtimeWeb.Plugs.RateLimiter\n\n    result = RateLimiter.call(conn, [])\n\n    refute result.halted\n  end\n\n  test \"sets rate limit headers on 429 response\", %{conn: conn} do\n    conn =\n      conn\n      |> Map.put(:host, \"localhost.localhost.com\")\n      |> get(Routes.ping_path(conn, :ping))\n\n    assert conn.status == 429\n    assert get_resp_header(conn, \"x-rate-limit\") == [\"0\"]\n    assert get_resp_header(conn, \"x-rate-rolling\") != []\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/socket/v2_serializer_test.exs",
    "content": "defmodule RealtimeWeb.Socket.V2SerializerTest do\n  use ExUnit.Case, async: true\n\n  alias Phoenix.Socket.{Broadcast, Message, Reply}\n  alias RealtimeWeb.Socket.UserBroadcast\n  alias RealtimeWeb.Socket.V2Serializer\n\n  @serializer V2Serializer\n  @v2_fastlane_json \"[null,null,\\\"t\\\",\\\"e\\\",{\\\"m\\\":1}]\"\n  @v2_msg_json \"[null,null,\\\"t\\\",\\\"e\\\",{\\\"m\\\":1}]\"\n\n  @client_push <<\n    # push\n    0::size(8),\n    # join_ref_size\n    2,\n    # ref_size\n    3,\n    # topic_size\n    5,\n    # event_size\n    5,\n    \"12\",\n    \"123\",\n    \"topic\",\n    \"event\",\n    101,\n    102,\n    103\n  >>\n\n  @client_binary_user_broadcast_push <<\n    # user broadcast push\n    3::size(8),\n    # join_ref_size\n    2,\n    # ref_size\n    3,\n    # topic_size\n    5,\n    # user_event_size\n    10,\n    # metadata_size\n    0,\n    # binary encoding\n    0::size(8),\n    \"12\",\n    \"123\",\n    \"topic\",\n    \"user_event\",\n    101,\n    102,\n    103\n  >>\n\n  @client_json_user_broadcast_push <<\n    # user broadcast push\n    3::size(8),\n    # join_ref_size\n    2,\n    # ref_size\n    3,\n    # topic_size\n    5,\n    # user_event_size\n    10,\n    # metadata_size\n    0,\n    # json encoding\n    1::size(8),\n    \"12\",\n    \"123\",\n    \"topic\",\n    \"user_event\",\n    123,\n    34,\n    97,\n    34,\n    58,\n    34,\n    98,\n    34,\n    125\n  >>\n\n  @client_binary_user_broadcast_push_with_metadata <<\n    # user broadcast push\n    3::size(8),\n    # join_ref_size\n    2,\n    # ref_size\n    3,\n    # topic_size\n    5,\n    # user_event_size\n    10,\n    # metadata_size\n    14,\n    # binary encoding\n    0::size(8),\n    \"12\",\n    \"123\",\n    \"topic\",\n    \"user_event\",\n    ~s<{\"store\":true}>,\n    101,\n    102,\n    103\n  >>\n\n  @reply <<\n    # reply\n    1::size(8),\n    # join_ref_size\n    2,\n    # ref_size\n    3,\n    # topic_size\n    5,\n    # status_size\n    2,\n    \"12\",\n    \"123\",\n    \"topic\",\n    \"ok\",\n    101,\n    102,\n    103\n  >>\n\n  @broadcast <<\n    # broadcast\n    2::size(8),\n    # topic_size\n    5,\n    # event_size\n    5,\n    \"topic\",\n    \"event\",\n    101,\n    102,\n    103\n  >>\n\n  @binary_user_broadcast <<\n    # user broadcast\n    4::size(8),\n    # topic_size\n    5,\n    # user_event_size\n    10,\n    # metadata_size\n    17,\n    # binary encoding\n    0::size(8),\n    \"topic\",\n    \"user_event\",\n    # metadata\n    123,\n    34,\n    114,\n    101,\n    112,\n    108,\n    97,\n    121,\n    101,\n    100,\n    34,\n    58,\n    116,\n    114,\n    117,\n    101,\n    125,\n    # payload\n    101,\n    102,\n    103\n  >>\n\n  @binary_user_broadcast_no_metadata <<\n    # user broadcast\n    4::size(8),\n    # topic_size\n    5,\n    # user_event_size\n    10,\n    # metadata_size\n    0,\n    # binary encoding\n    0::size(8),\n    \"topic\",\n    \"user_event\",\n    # metadata\n    # payload\n    101,\n    102,\n    103\n  >>\n\n  @json_user_broadcast <<\n    # user broadcast\n    4::size(8),\n    # topic_size\n    5,\n    # user_event_size\n    10,\n    # metadata_size\n    17,\n    # json encoding\n    1::size(8),\n    \"topic\",\n    \"user_event\",\n    # metadata\n    123,\n    34,\n    114,\n    101,\n    112,\n    108,\n    97,\n    121,\n    101,\n    100,\n    34,\n    58,\n    116,\n    114,\n    117,\n    101,\n    125,\n    # payload\n    123,\n    34,\n    97,\n    34,\n    58,\n    34,\n    98,\n    34,\n    125\n  >>\n\n  @json_user_broadcast_no_metadata <<\n    # broadcast\n    4::size(8),\n    # topic_size\n    5,\n    # user_event_size\n    10,\n    # metadata_size\n    0,\n    # json encoding\n    1::size(8),\n    \"topic\",\n    \"user_event\",\n    # metadata\n    # payload\n    123,\n    34,\n    97,\n    34,\n    58,\n    34,\n    98,\n    34,\n    125\n  >>\n\n  defp encode!(serializer, msg) do\n    case serializer.encode!(msg) do\n      {:socket_push, :text, encoded} ->\n        assert is_list(encoded)\n        IO.iodata_to_binary(encoded)\n\n      {:socket_push, :binary, encoded} ->\n        assert is_binary(encoded)\n        encoded\n    end\n  end\n\n  defp decode!(serializer, msg, opts), do: serializer.decode!(msg, opts)\n\n  defp fastlane!(serializer, msg) do\n    case serializer.fastlane!(msg) do\n      {:socket_push, :text, encoded} ->\n        assert is_list(encoded)\n        IO.iodata_to_binary(encoded)\n\n      {:socket_push, :binary, encoded} ->\n        assert is_binary(encoded)\n        encoded\n    end\n  end\n\n  test \"encode!/1 encodes `Phoenix.Socket.Message` as JSON\" do\n    msg = %Message{topic: \"t\", event: \"e\", payload: %{m: 1}}\n    assert encode!(@serializer, msg) == @v2_msg_json\n  end\n\n  test \"encode!/1 raises when payload is not a map\" do\n    msg = %Message{topic: \"t\", event: \"e\", payload: \"invalid\"}\n    assert_raise ArgumentError, fn -> encode!(@serializer, msg) end\n  end\n\n  test \"encode!/1 encodes `Phoenix.Socket.Reply` as JSON\" do\n    msg = %Reply{topic: \"t\", payload: %{m: 1}}\n    encoded = encode!(@serializer, msg)\n\n    assert Jason.decode!(encoded) == [\n             nil,\n             nil,\n             \"t\",\n             \"phx_reply\",\n             %{\"response\" => %{\"m\" => 1}, \"status\" => nil}\n           ]\n  end\n\n  test \"decode!/2 decodes `Phoenix.Socket.Message` from JSON\" do\n    assert %Message{topic: \"t\", event: \"e\", payload: %{\"m\" => 1}} ==\n             decode!(@serializer, @v2_msg_json, opcode: :text)\n  end\n\n  test \"fastlane!/1 encodes a broadcast into a message as JSON\" do\n    msg = %Broadcast{topic: \"t\", event: \"e\", payload: %{m: 1}}\n    assert fastlane!(@serializer, msg) == @v2_fastlane_json\n  end\n\n  test \"fastlane!/1 raises when payload is not a map\" do\n    msg = %Broadcast{topic: \"t\", event: \"e\", payload: \"invalid\"}\n    assert_raise ArgumentError, fn -> fastlane!(@serializer, msg) end\n  end\n\n  describe \"binary encode\" do\n    test \"general pushed message\" do\n      push = <<\n        # push\n        0::size(8),\n        # join_ref_size\n        2,\n        # topic_size\n        5,\n        # event_size\n        5,\n        \"12\",\n        \"topic\",\n        \"event\",\n        101,\n        102,\n        103\n      >>\n\n      assert encode!(@serializer, %Phoenix.Socket.Message{\n               join_ref: \"12\",\n               ref: nil,\n               topic: \"topic\",\n               event: \"event\",\n               payload: {:binary, <<101, 102, 103>>}\n             }) == push\n    end\n\n    test \"encode with oversized headers\" do\n      assert_raise ArgumentError, ~r/unable to convert topic to binary/, fn ->\n        encode!(@serializer, %Phoenix.Socket.Message{\n          join_ref: \"12\",\n          ref: nil,\n          topic: String.duplicate(\"t\", 256),\n          event: \"event\",\n          payload: {:binary, <<101, 102, 103>>}\n        })\n      end\n\n      assert_raise ArgumentError, ~r/unable to convert event to binary/, fn ->\n        encode!(@serializer, %Phoenix.Socket.Message{\n          join_ref: \"12\",\n          ref: nil,\n          topic: \"topic\",\n          event: String.duplicate(\"e\", 256),\n          payload: {:binary, <<101, 102, 103>>}\n        })\n      end\n\n      assert_raise ArgumentError, ~r/unable to convert join_ref to binary/, fn ->\n        encode!(@serializer, %Phoenix.Socket.Message{\n          join_ref: String.duplicate(\"j\", 256),\n          ref: nil,\n          topic: \"topic\",\n          event: \"event\",\n          payload: {:binary, <<101, 102, 103>>}\n        })\n      end\n    end\n\n    test \"reply\" do\n      assert encode!(@serializer, %Phoenix.Socket.Reply{\n               join_ref: \"12\",\n               ref: \"123\",\n               topic: \"topic\",\n               status: :ok,\n               payload: {:binary, <<101, 102, 103>>}\n             }) == @reply\n    end\n\n    test \"reply with oversized headers\" do\n      assert_raise ArgumentError, ~r/unable to convert ref to binary/, fn ->\n        encode!(@serializer, %Phoenix.Socket.Reply{\n          join_ref: \"12\",\n          ref: String.duplicate(\"r\", 256),\n          topic: \"topic\",\n          status: :ok,\n          payload: {:binary, <<101, 102, 103>>}\n        })\n      end\n    end\n\n    test \"fastlane binary Broadcast\" do\n      assert fastlane!(@serializer, %Broadcast{\n               topic: \"topic\",\n               event: \"event\",\n               payload: {:binary, <<101, 102, 103>>}\n             }) == @broadcast\n    end\n\n    test \"fastlane binary UserBroadcast\" do\n      assert fastlane!(@serializer, %UserBroadcast{\n               topic: \"topic\",\n               user_event: \"user_event\",\n               metadata: %{\"replayed\" => true},\n               user_payload_encoding: :binary,\n               user_payload: <<101, 102, 103>>\n             }) == @binary_user_broadcast\n    end\n\n    test \"fastlane binary UserBroadcast no metadata\" do\n      assert fastlane!(@serializer, %UserBroadcast{\n               topic: \"topic\",\n               user_event: \"user_event\",\n               metadata: nil,\n               user_payload_encoding: :binary,\n               user_payload: <<101, 102, 103>>\n             }) == @binary_user_broadcast_no_metadata\n    end\n\n    test \"fastlane json UserBroadcast\" do\n      assert fastlane!(@serializer, %UserBroadcast{\n               topic: \"topic\",\n               user_event: \"user_event\",\n               metadata: %{\"replayed\" => true},\n               user_payload_encoding: :json,\n               user_payload: \"{\\\"a\\\":\\\"b\\\"}\"\n             }) == @json_user_broadcast\n    end\n\n    test \"fastlane json UserBroadcast no metadata\" do\n      assert fastlane!(@serializer, %UserBroadcast{\n               topic: \"topic\",\n               user_event: \"user_event\",\n               user_payload_encoding: :json,\n               user_payload: \"{\\\"a\\\":\\\"b\\\"}\"\n             }) == @json_user_broadcast_no_metadata\n    end\n\n    test \"fastlane with oversized headers\" do\n      assert_raise ArgumentError, ~r/unable to convert topic to binary/, fn ->\n        fastlane!(@serializer, %Broadcast{\n          topic: String.duplicate(\"t\", 256),\n          event: \"event\",\n          payload: {:binary, <<101, 102, 103>>}\n        })\n      end\n\n      assert_raise ArgumentError, ~r/unable to convert event to binary/, fn ->\n        fastlane!(@serializer, %Broadcast{\n          topic: \"topic\",\n          event: String.duplicate(\"e\", 256),\n          payload: {:binary, <<101, 102, 103>>}\n        })\n      end\n\n      assert_raise ArgumentError, ~r/unable to convert topic to binary/, fn ->\n        fastlane!(@serializer, %UserBroadcast{\n          topic: String.duplicate(\"t\", 256),\n          user_event: \"user_event\",\n          user_payload_encoding: :json,\n          user_payload: \"{\\\"a\\\":\\\"b\\\"}\"\n        })\n      end\n\n      assert_raise ArgumentError, ~r/unable to convert user_event to binary/, fn ->\n        fastlane!(@serializer, %UserBroadcast{\n          topic: \"topic\",\n          user_event: String.duplicate(\"e\", 256),\n          user_payload_encoding: :json,\n          user_payload: \"{\\\"a\\\":\\\"b\\\"}\"\n        })\n      end\n\n      assert_raise ArgumentError, ~r/unable to convert metadata to binary/, fn ->\n        fastlane!(@serializer, %UserBroadcast{\n          topic: \"topic\",\n          user_event: \"user_event\",\n          metadata: %{k: String.duplicate(\"e\", 256)},\n          user_payload_encoding: :json,\n          user_payload: \"{\\\"a\\\":\\\"b\\\"}\"\n        })\n      end\n    end\n  end\n\n  describe \"decode!/2 invalid text formats\" do\n    test \"raises on a bare JSON string\" do\n      raw = Jason.encode!(\"just a string\")\n\n      assert_raise Phoenix.Socket.InvalidMessageError, fn ->\n        decode!(@serializer, raw, opcode: :text)\n      end\n    end\n\n    test \"raises on a V1 map\" do\n      raw = Jason.encode!(%{\"topic\" => \"t\", \"event\" => \"e\", \"payload\" => %{\"m\" => 1}, \"ref\" => \"1\", \"join_ref\" => \"11\"})\n\n      assert_raise Phoenix.Socket.InvalidMessageError, fn ->\n        decode!(@serializer, raw, opcode: :text)\n      end\n    end\n  end\n\n  describe \"binary decode\" do\n    test \"pushed message\" do\n      assert decode!(@serializer, @client_push, opcode: :binary) == %Phoenix.Socket.Message{\n               join_ref: \"12\",\n               ref: \"123\",\n               topic: \"topic\",\n               event: \"event\",\n               payload: {:binary, <<101, 102, 103>>}\n             }\n    end\n\n    test \"binary user pushed message with metadata\" do\n      assert decode!(@serializer, @client_binary_user_broadcast_push_with_metadata, opcode: :binary) ==\n               %Phoenix.Socket.Message{\n                 join_ref: \"12\",\n                 ref: \"123\",\n                 topic: \"topic\",\n                 event: \"broadcast\",\n                 payload: {\"user_event\", :binary, <<101, 102, 103>>, %{\"store\" => true}}\n               }\n    end\n\n    test \"binary user pushed message\" do\n      assert decode!(@serializer, @client_binary_user_broadcast_push, opcode: :binary) == %Phoenix.Socket.Message{\n               join_ref: \"12\",\n               ref: \"123\",\n               topic: \"topic\",\n               event: \"broadcast\",\n               payload: {\"user_event\", :binary, <<101, 102, 103>>, %{}}\n             }\n    end\n\n    test \"json binary user pushed message\" do\n      assert decode!(@serializer, @client_json_user_broadcast_push, opcode: :binary) == %Phoenix.Socket.Message{\n               join_ref: \"12\",\n               ref: \"123\",\n               topic: \"topic\",\n               event: \"broadcast\",\n               payload: {\"user_event\", :json, \"{\\\"a\\\":\\\"b\\\"}\", %{}}\n             }\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/tenant_broadcaster_test.exs",
    "content": "defmodule RealtimeWeb.TenantBroadcasterTest do\n  # Usage of Clustered and changing Application env\n  use Realtime.DataCase, async: false\n\n  alias Phoenix.Socket.Broadcast\n\n  alias RealtimeWeb.Endpoint\n  alias RealtimeWeb.TenantBroadcaster\n\n  @topic \"test-topic\" <> to_string(__MODULE__)\n\n  @aux_mod (quote do\n              defmodule Subscriber do\n                # Relay messages to testing node\n                def subscribe(subscriber, topic) do\n                  spawn(fn ->\n                    RealtimeWeb.Endpoint.subscribe(topic)\n                    send(subscriber, :ready)\n\n                    receive do\n                      msg ->\n                        send(subscriber, {:relay, node(), msg})\n                    end\n                  end)\n                end\n              end\n            end)\n\n  setup_all do\n    {:ok, node} = Clustered.start(@aux_mod)\n\n    %{node: node}\n  end\n\n  setup context do\n    tenant_id = random_string()\n    Endpoint.subscribe(@topic)\n\n    :erpc.call(context.node, Subscriber, :subscribe, [self(), @topic])\n    assert_receive :ready\n\n    on_exit(fn -> :telemetry.detach(__MODULE__) end)\n\n    :telemetry.attach(\n      __MODULE__,\n      [:realtime, :tenants, :payload, :size],\n      &__MODULE__.handle_telemetry/4,\n      %{pid: self(), tenant: tenant_id}\n    )\n\n    original = Application.fetch_env!(:realtime, :pubsub_adapter)\n    on_exit(fn -> Application.put_env(:realtime, :pubsub_adapter, original) end)\n    Application.put_env(:realtime, :pubsub_adapter, context.pubsub_adapter)\n\n    {:ok, tenant_id: tenant_id}\n  end\n\n  for pubsub_adapter <- [:gen_rpc, :pg2] do\n    describe \"pubsub_broadcast/5 #{pubsub_adapter}\" do\n      @describetag pubsub_adapter: pubsub_adapter\n\n      test \"pubsub_broadcast\", %{node: node, tenant_id: tenant_id} do\n        message = %Broadcast{topic: @topic, event: \"an event\", payload: %{\"a\" => \"b\"}}\n        TenantBroadcaster.pubsub_broadcast(tenant_id, @topic, message, Phoenix.PubSub, :broadcast)\n\n        assert_receive ^message\n\n        # Remote node received the broadcast\n        assert_receive {:relay, ^node, ^message}\n\n        assert_receive {\n          :telemetry,\n          [:realtime, :tenants, :payload, :size],\n          %{size: 114},\n          %{tenant: ^tenant_id, message_type: :broadcast}\n        }\n      end\n\n      test \"pubsub_broadcast list payload\", %{node: node, tenant_id: tenant_id} do\n        message = %Broadcast{topic: @topic, event: \"an event\", payload: [\"a\", %{\"b\" => \"c\"}, 1, 23]}\n        TenantBroadcaster.pubsub_broadcast(tenant_id, @topic, message, Phoenix.PubSub, :broadcast)\n\n        assert_receive ^message\n\n        # Remote node received the broadcast\n        assert_receive {:relay, ^node, ^message}\n\n        assert_receive {\n          :telemetry,\n          [:realtime, :tenants, :payload, :size],\n          %{size: 130},\n          %{tenant: ^tenant_id, message_type: :broadcast}\n        }\n      end\n\n      test \"pubsub_broadcast string payload\", %{node: node, tenant_id: tenant_id} do\n        message = %Broadcast{topic: @topic, event: \"an event\", payload: \"some text payload\"}\n        TenantBroadcaster.pubsub_broadcast(tenant_id, @topic, message, Phoenix.PubSub, :broadcast)\n\n        assert_receive ^message\n\n        # Remote node received the broadcast\n        assert_receive {:relay, ^node, ^message}\n\n        assert_receive {\n          :telemetry,\n          [:realtime, :tenants, :payload, :size],\n          %{size: 119},\n          %{tenant: ^tenant_id, message_type: :broadcast}\n        }\n      end\n    end\n\n    describe \"pubsub_broadcast_from/6 #{pubsub_adapter}\" do\n      @describetag pubsub_adapter: pubsub_adapter\n\n      test \"pubsub_broadcast_from\", %{node: node, tenant_id: tenant_id} do\n        parent = self()\n\n        spawn_link(fn ->\n          Endpoint.subscribe(@topic)\n          send(parent, :ready)\n\n          receive do\n            msg -> send(parent, {:other_process, msg})\n          end\n        end)\n\n        assert_receive :ready\n\n        message = %Broadcast{topic: @topic, event: \"an event\", payload: %{\"a\" => \"b\"}}\n\n        TenantBroadcaster.pubsub_broadcast_from(tenant_id, self(), @topic, message, Phoenix.PubSub, :broadcast)\n\n        assert_receive {:other_process, ^message}\n\n        # Remote node received the broadcast\n        assert_receive {:relay, ^node, ^message}\n\n        assert_receive {\n          :telemetry,\n          [:realtime, :tenants, :payload, :size],\n          %{size: 114},\n          %{tenant: ^tenant_id, message_type: :broadcast}\n        }\n\n        # This process does not receive the message\n        refute_receive _any\n      end\n    end\n\n    describe \"pubsub_direct_broadcast/6 #{pubsub_adapter}\" do\n      @describetag pubsub_adapter: pubsub_adapter\n\n      test \"pubsub_direct_broadcast\", %{node: node, tenant_id: tenant_id} do\n        message = %Broadcast{topic: @topic, event: \"an event\", payload: %{\"a\" => \"b\"}}\n\n        TenantBroadcaster.pubsub_direct_broadcast(node(), tenant_id, @topic, message, Phoenix.PubSub, :broadcast)\n        TenantBroadcaster.pubsub_direct_broadcast(node, tenant_id, @topic, message, Phoenix.PubSub, :broadcast)\n\n        assert_receive ^message\n\n        # Remote node received the broadcast\n        assert_receive {:relay, ^node, ^message}\n\n        assert_receive {\n          :telemetry,\n          [:realtime, :tenants, :payload, :size],\n          %{size: 114},\n          %{tenant: ^tenant_id, message_type: :broadcast}\n        }\n      end\n\n      test \"pubsub_direct_broadcast list payload\", %{node: node, tenant_id: tenant_id} do\n        message = %Broadcast{topic: @topic, event: \"an event\", payload: [\"a\", %{\"b\" => \"c\"}, 1, 23]}\n\n        TenantBroadcaster.pubsub_direct_broadcast(node(), tenant_id, @topic, message, Phoenix.PubSub, :broadcast)\n        TenantBroadcaster.pubsub_direct_broadcast(node, tenant_id, @topic, message, Phoenix.PubSub, :broadcast)\n\n        assert_receive ^message\n\n        # Remote node received the broadcast\n        assert_receive {:relay, ^node, ^message}\n\n        assert_receive {\n          :telemetry,\n          [:realtime, :tenants, :payload, :size],\n          %{size: 130},\n          %{tenant: ^tenant_id, message_type: :broadcast}\n        }\n      end\n\n      test \"pubsub_direct_broadcast string payload\", %{node: node, tenant_id: tenant_id} do\n        message = %Broadcast{topic: @topic, event: \"an event\", payload: \"some text payload\"}\n\n        TenantBroadcaster.pubsub_direct_broadcast(node(), tenant_id, @topic, message, Phoenix.PubSub, :broadcast)\n        TenantBroadcaster.pubsub_direct_broadcast(node, tenant_id, @topic, message, Phoenix.PubSub, :broadcast)\n\n        assert_receive ^message\n\n        # Remote node received the broadcast\n        assert_receive {:relay, ^node, ^message}\n\n        assert_receive {\n          :telemetry,\n          [:realtime, :tenants, :payload, :size],\n          %{size: 119},\n          %{tenant: ^tenant_id, message_type: :broadcast}\n        }\n      end\n    end\n  end\n\n  describe \"collect_payload_size/3\" do\n    @describetag pubsub_adapter: :gen_rpc\n\n    test \"emit telemetry for struct\", %{tenant_id: tenant_id} do\n      TenantBroadcaster.collect_payload_size(\n        tenant_id,\n        %Phoenix.Socket.Broadcast{event: \"broadcast\", payload: %{\"a\" => \"b\"}},\n        :broadcast\n      )\n\n      assert_receive {:telemetry, [:realtime, :tenants, :payload, :size], %{size: 65},\n                      %{tenant: ^tenant_id, message_type: :broadcast}}\n    end\n\n    test \"emit telemetry for map\", %{tenant_id: tenant_id} do\n      TenantBroadcaster.collect_payload_size(\n        tenant_id,\n        %{event: \"broadcast\", payload: %{\"a\" => \"b\"}},\n        :postgres_changes\n      )\n\n      assert_receive {:telemetry, [:realtime, :tenants, :payload, :size], %{size: 53},\n                      %{tenant: ^tenant_id, message_type: :postgres_changes}}\n    end\n\n    test \"emit telemetry for non-map\", %{tenant_id: tenant_id} do\n      TenantBroadcaster.collect_payload_size(tenant_id, \"some blob\", :presence)\n\n      assert_receive {:telemetry, [:realtime, :tenants, :payload, :size], %{size: 15},\n                      %{tenant: ^tenant_id, message_type: :presence}}\n    end\n  end\n\n  def handle_telemetry(event, measures, metadata, %{pid: pid, tenant: tenant}) do\n    if metadata[:tenant] == tenant do\n      send(pid, {:telemetry, event, measures, metadata})\n    end\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/views/error_view_test.exs",
    "content": "defmodule RealtimeWeb.ErrorViewTest do\n  use RealtimeWeb.ConnCase, async: true\n\n  # Bring render/3 and render_to_string/3 for testing custom views\n  import Phoenix.View\n\n  test \"renders 404.html\" do\n    assert render_to_string(RealtimeWeb.ErrorView, \"404.html\", []) == \"Not Found\"\n  end\n\n  test \"renders 500.html\" do\n    assert render_to_string(RealtimeWeb.ErrorView, \"500.html\", []) == \"Internal Server Error\"\n  end\nend\n"
  },
  {
    "path": "test/realtime_web/views/layout_view_test.exs",
    "content": "defmodule RealtimeWeb.LayoutViewTest do\n  use RealtimeWeb.ConnCase\n\n  # When testing helpers, you may want to import Phoenix.HTML and\n  # use functions such as safe_to_string() to convert the helper\n  # result into an HTML string.\n  # import Phoenix.HTML\nend\n"
  },
  {
    "path": "test/realtime_web/views/page_view_test.exs",
    "content": "defmodule RealtimeWeb.PageViewTest do\n  use RealtimeWeb.ConnCase\nend\n"
  },
  {
    "path": "test/support/channel_case.ex",
    "content": "defmodule RealtimeWeb.ChannelCase do\n  @moduledoc \"\"\"\n  This module defines the test case to be used by\n  channel tests.\n\n  Such tests rely on `Phoenix.ChannelTest` and also\n  import other functionality to make it easier\n  to build common data structures and query the data layer.\n\n  Finally, if the test case interacts with the database,\n  we enable the SQL sandbox, so changes done to the database\n  are reverted at the end of every test. If you are using\n  PostgreSQL, you can even run database tests asynchronously\n  by setting `use RealtimeWeb.ChannelCase, async: true`, although\n  this option is not recommended for other databases.\n  \"\"\"\n\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      # Import conveniences for testing with channels\n      import Phoenix.ChannelTest\n      import Generators\n      import TenantConnection\n      # The default endpoint for testing\n      @endpoint RealtimeWeb.Endpoint\n    end\n  end\n\n  setup tags do\n    Realtime.DataCase.setup_sandbox(tags)\n  end\nend\n"
  },
  {
    "path": "test/support/cleanup.ex",
    "content": "defmodule Cleanup do\n  alias Realtime.Tenants.Connect\n  def ensure_no_replication_slot(attempts \\\\ 5)\n  def ensure_no_replication_slot(0), do: raise(\"Replication slot teardown failed\")\n  @table_name :\"syn_registry_by_name_Elixir.Realtime.Tenants.Connect\"\n\n  def ensure_no_replication_slot(attempts) do\n    {:ok, conn} =\n      Postgrex.start_link(\n        hostname: \"localhost\",\n        port: 5433,\n        database: \"postgres\",\n        username: \"supabase_admin\",\n        password: \"postgres\"\n      )\n\n    # Stop lingering connections\n    Enum.each(:ets.tab2list(@table_name), fn {tenant_id, _, _, _, _, _} ->\n      Connect.shutdown(tenant_id)\n    end)\n\n    # Ensure no replication slots are active\n    case Postgrex.query(conn, \"SELECT active_pid, slot_name FROM pg_replication_slots\", []) do\n      {:ok, %{rows: []}} ->\n        :ok\n\n      {:ok, %{rows: rows}} ->\n        Enum.each(rows, fn [pid, slot_name] ->\n          Postgrex.query(conn, \"select pg_terminate_backend($1) \", [pid])\n          Postgrex.query(conn, \"select pg_drop_replication_slot($1)\", [slot_name])\n        end)\n\n      {:error, _} ->\n        Process.sleep(1000)\n        ensure_no_replication_slot(attempts - 1)\n    end\n  end\nend\n"
  },
  {
    "path": "test/support/clustered.ex",
    "content": "defmodule Clustered do\n  @moduledoc \"\"\"\n  Uses the gist https://gist.github.com/ityonemo/177cbc96f8c8722bfc4d127ff9baec62 to start a node for testing\n  \"\"\"\n\n  @doc \"\"\"\n  Starts a node for testing.\n\n  Can receive an auxiliary module to be evaluated in the node so you are able to setup functions within the test context and outside of the normal code context\n\n  e.g.\n  ```\n  @aux_mod (quote do\n              defmodule Aux do\n                def checker(res), do: res\n              end\n            end)\n\n  Code.eval_quoted(@aux_mod)\n  test \"clustered call\" do\n    {:ok, node} = Clustered.start(@aux_mod)\n    assert ok = :rpc.call(node, Aux, :checker, [:ok])\n  end\n  ```\n  \"\"\"\n  @spec start(any(), keyword()) :: {:ok, node}\n  def start(aux_mod \\\\ nil, opts \\\\ []) do\n    {:ok, pid, node} = start_disconnected(aux_mod, opts)\n\n    :ok = wait_for_gen_rpc(pid)\n\n    true = Node.connect(node)\n\n    max_clients = Application.get_env(:realtime, :max_gen_rpc_clients, 5)\n\n    for key <- 1..max_clients do\n      _ = :gen_rpc.call({node, key}, :erlang, :node, [], 5_000)\n    end\n\n    {:ok, node}\n  end\n\n  @doc \"\"\"\n  Similar to `start/2` but the node is not connected automatically\n  \"\"\"\n  @spec start_disconnected(any(), keyword()) :: {:ok, :peer.server_ref(), node}\n  def start_disconnected(aux_mod \\\\ nil, opts \\\\ []) do\n    extra_config = Keyword.get(opts, :extra_config, [])\n    phoenix_port = Keyword.get(opts, :phoenix_port, 4012)\n    name = Keyword.get(opts, :name, :peer.random_name())\n\n    partition = System.get_env(\"MIX_TEST_PARTITION\")\n    node_name = if partition, do: :\"main#{partition}@127.0.0.1\", else: :\"main@127.0.0.1\"\n\n    :ok =\n      case :net_kernel.start([node_name]) do\n        {:ok, _} ->\n          :ok\n\n        {:error, {:already_started, _}} ->\n          :ok\n\n        {:error, reason} ->\n          raise \"Failed to start node: #{inspect(reason)}\"\n      end\n\n    true = :erlang.set_cookie(:cookie)\n\n    {:ok, pid, node} =\n      ExUnit.Callbacks.start_supervised(%{\n        id: {:peer, name},\n        start:\n          {:peer, :start_link,\n           [\n             %{\n               name: name,\n               host: ~c\"127.0.0.1\",\n               longnames: true,\n               connection: :standard_io\n             }\n           ]}\n      })\n\n    :peer.call(pid, :erlang, :set_cookie, [:cookie])\n\n    :ok = :peer.call(pid, :code, :add_paths, [:code.get_path()])\n\n    # We need to load the app first as it has default app env that we want to override\n    :ok = :peer.call(pid, Application, :ensure_loaded, [:gen_rpc])\n\n    for {app_name, _, _} <- Application.loaded_applications(),\n        {key, value} <- Application.get_all_env(app_name) do\n      :ok = :peer.call(pid, Application, :put_env, [app_name, key, value])\n    end\n\n    endpoint = Application.get_env(:realtime, RealtimeWeb.Endpoint)\n\n    :ok =\n      :peer.call(pid, Application, :put_env, [\n        :realtime,\n        RealtimeWeb.Endpoint,\n        Keyword.put(endpoint, :http, port: phoenix_port)\n      ])\n\n    # Configure gen_rpc swapping port definitons\n    gen_rpc_tcp_server_port = Application.fetch_env!(:gen_rpc, :tcp_server_port)\n    gen_rpc_tcp_client_port = Application.fetch_env!(:gen_rpc, :tcp_client_port)\n\n    :ok = :peer.call(pid, Application, :put_env, [:gen_rpc, :tcp_server_port, gen_rpc_tcp_client_port])\n    :ok = :peer.call(pid, Application, :put_env, [:gen_rpc, :tcp_client_port, gen_rpc_tcp_server_port])\n\n    # We need to override this value as the current implementation overrides the string with a map leading to errors\n    :ok = :peer.call(pid, Application, :put_env, [:realtime, :jwt_claim_validators, \"{}\"])\n\n    # Override with extra config\n    for {app_name, key, value} <- extra_config do\n      :ok = :peer.call(pid, Application, :put_env, [app_name, key, value])\n    end\n\n    wait_for_port_free(gen_rpc_tcp_client_port)\n    {:ok, _} = :peer.call(pid, Application, :ensure_all_started, [:gen_rpc])\n    {:ok, _} = :peer.call(pid, Application, :ensure_all_started, [:mix])\n    :ok = :peer.call(pid, Mix, :env, [Mix.env()])\n\n    Enum.each(\n      [:logger, :runtime_tools, :prom_ex, :mix, :os_mon, :realtime],\n      fn app -> {:ok, _} = :peer.call(pid, Application, :ensure_all_started, [app]) end\n    )\n\n    if aux_mod do\n      {{:module, _, _, _}, []} = :peer.call(pid, Code, :eval_quoted, [aux_mod])\n    end\n\n    {:ok, pid, node}\n  end\n\n  defp wait_for_gen_rpc(pid) do\n    port = :peer.call(pid, Application, :get_env, [:gen_rpc, :tcp_server_port])\n\n    case port do\n      port when is_integer(port) and port > 0 -> wait_for_port({127, 0, 0, 1}, port, 50, 100)\n      _ -> raise \"gen_rpc tcp_server_port is not configured: #{inspect(port)}\"\n    end\n  end\n\n  defp wait_for_port_free(port, attempts \\\\ 50, delay_ms \\\\ 100)\n  defp wait_for_port_free(_port, 0, _delay_ms), do: :ok\n\n  defp wait_for_port_free(port, attempts, delay_ms) do\n    case :gen_tcp.connect({127, 0, 0, 1}, port, [:binary, active: false], 100) do\n      {:ok, socket} ->\n        :gen_tcp.close(socket)\n        Process.sleep(delay_ms)\n        wait_for_port_free(port, attempts - 1, delay_ms)\n\n      {:error, _} ->\n        :ok\n    end\n  end\n\n  defp wait_for_port(_host, _port, 0, _delay_ms), do: raise(\"gen_rpc tcp server did not start in time\")\n\n  defp wait_for_port(host, port, attempts, delay_ms) do\n    case :gen_tcp.connect(host, port, [:binary, active: false], 200) do\n      {:ok, socket} ->\n        :ok = :gen_tcp.close(socket)\n        :ok\n\n      {:error, _reason} ->\n        Process.sleep(delay_ms)\n        wait_for_port(host, port, attempts - 1, delay_ms)\n    end\n  end\nend\n"
  },
  {
    "path": "test/support/conn_case.ex",
    "content": "defmodule RealtimeWeb.ConnCase do\n  @moduledoc \"\"\"\n  This module defines the test case to be used by\n  tests that require setting up a connection.\n\n  Such tests rely on `Phoenix.ConnTest` and also\n  import other functionality to make it easier\n  to build common data structures and query the data layer.\n\n  Finally, if the test case interacts with the database,\n  we enable the SQL sandbox, so changes done to the database\n  are reverted at the end of every test. If you are using\n  PostgreSQL, you can even run database tests asynchronously\n  by setting `use RealtimeWeb.ConnCase, async: true`, although\n  this option is not recommended for other databases.\n  \"\"\"\n\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      # Import conveniences for testing with connections\n      import Generators\n      import Integrations\n      import TenantConnection\n      import Phoenix.ConnTest\n      import Plug.Conn\n      import Realtime.DataCase\n\n      alias RealtimeWeb.Router.Helpers, as: Routes\n\n      use RealtimeWeb, :verified_routes\n      use Realtime.Tracing\n\n      # The default endpoint for testing\n      @endpoint RealtimeWeb.Endpoint\n    end\n  end\n\n  setup tags do\n    Realtime.DataCase.setup_sandbox(tags)\n    {:ok, conn: Phoenix.ConnTest.build_conn()}\n  end\nend\n"
  },
  {
    "path": "test/support/containers/container.ex",
    "content": "defmodule Containers.Container do\n  use GenServer\n\n  def start_link(args \\\\ [], opts \\\\ []) do\n    GenServer.start_link(__MODULE__, args, opts)\n  end\n\n  def port(pid), do: GenServer.call(pid, :port, 15_000)\n  def name(pid), do: GenServer.call(pid, :name, 15_000)\n\n  @impl true\n  def handle_call(:port, _from, state) do\n    {:reply, state[:port], state}\n  end\n\n  @impl true\n  def handle_call(:name, _from, state) do\n    {:reply, state[:name], state}\n  end\n\n  @impl true\n  def init(_args) do\n    {:ok, %{}, {:continue, :start_container}}\n  end\n\n  @impl true\n  def handle_continue(:start_container, _state) do\n    {:ok, name, port} = Containers.start_container()\n\n    {:noreply, %{name: name, port: port}, {:continue, :check_container_ready}}\n  end\n\n  @impl true\n  def handle_continue(:check_container_ready, state) do\n    check_container_ready(state[:name])\n    {:noreply, state}\n  end\n\n  defp check_container_ready(name, attempts \\\\ 100)\n  defp check_container_ready(name, 0), do: raise(\"Container #{name} is not ready\")\n\n  defp check_container_ready(name, attempts) do\n    case System.cmd(\"docker\", [\"exec\", name, \"pg_isready\", \"-p\", \"5432\", \"-h\", \"localhost\"]) do\n      {_, 0} ->\n        :ok\n\n      {_, _} ->\n        Process.sleep(250)\n        check_container_ready(name, attempts - 1)\n    end\n  end\nend\n"
  },
  {
    "path": "test/support/containers.ex",
    "content": "defmodule Containers do\n  alias Extensions.PostgresCdcRls\n  alias Realtime.Tenants.Connect\n  alias Containers.Container\n  alias Realtime.Database\n  alias Realtime.Tenants.Migrations\n\n  use GenServer\n\n  @image \"supabase/postgres:17.6.1.074\"\n  # Pull image if not available\n  def pull do\n    case System.cmd(\"docker\", [\"image\", \"inspect\", @image]) do\n      {_, 0} ->\n        :ok\n\n      _ ->\n        IO.puts(\"Pulling image #{@image}. This might take a while...\")\n        {_, 0} = System.cmd(\"docker\", [\"pull\", @image])\n    end\n  end\n\n  def start_container(), do: GenServer.call(__MODULE__, :start_container, 10_000)\n  def port(), do: GenServer.call(__MODULE__, :port, 10_000)\n\n  def start_link(max_cases), do: GenServer.start_link(__MODULE__, max_cases, name: __MODULE__)\n\n  def init(max_cases) do\n    existing_containers = existing_containers(\"realtime-test-*\")\n    ports = for {_, port} <- existing_containers, do: port\n    available_ports = Enum.shuffle(5501..9000) -- ports\n\n    {:ok, %{existing_containers: existing_containers, ports: available_ports}, {:continue, {:pool, max_cases}}}\n  end\n\n  def handle_continue({:pool, max_cases}, state) do\n    {:ok, _pid} =\n      :poolboy.start_link(\n        [\n          strategy: :fifo,\n          name: {:local, Containers.Pool},\n          size: max_cases + 2,\n          max_overflow: 0,\n          worker_module: Containers.Container\n        ],\n        []\n      )\n\n    {:noreply, state}\n  end\n\n  def handle_call(:port, _from, state) do\n    [port | ports] = state.ports\n    {:reply, port, %{state | ports: ports}}\n  end\n\n  def handle_call(:start_container, _from, state) do\n    case state.existing_containers do\n      [{name, port} | rest] ->\n        {:reply, {:ok, name, port}, %{state | existing_containers: rest}}\n\n      [] ->\n        [port | ports] = state.ports\n        name = \"realtime-test-#{random_string(12)}\"\n\n        docker_run!(name, port)\n\n        {:reply, {:ok, name, port}, %{state | ports: ports}}\n    end\n  end\n\n  defp random_string(length) do\n    :crypto.strong_rand_bytes(length)\n    |> Base.url_encode64()\n    |> binary_part(0, length)\n  end\n\n  def initialize(external_id) do\n    name = \"realtime-tenant-test-#{external_id}\"\n\n    port =\n      case existing_containers(name) do\n        [{^name, port}] ->\n          port\n\n        [] ->\n          port = 5500\n          docker_run!(name, port)\n          port\n      end\n\n    check_container_ready(name)\n\n    opts = %{external_id: external_id, name: external_id, port: port, jwt_secret: \"secure_jwt_secret\"}\n    tenant = Generators.tenant_fixture(opts)\n\n    Migrations.run_migrations(tenant)\n    {:ok, pid} = Database.connect(tenant, \"realtime_test\", :stop)\n    :ok = Migrations.create_partitions(pid)\n    Process.exit(pid, :normal)\n\n    tenant\n  end\n\n  @doc \"Return port for a container that can be used\"\n  def checkout() do\n    with container when is_pid(container) <- :poolboy.checkout(Containers.Pool, true, 5_000),\n         port <- Container.port(container) do\n      # Automatically checkin the container at the end of the test\n      ExUnit.Callbacks.on_exit(fn -> :poolboy.checkin(Containers.Pool, container) end)\n\n      {:ok, port}\n    else\n      _ -> {:error, \"failed to checkout a container\"}\n    end\n  end\n\n  defp storage_up!(tenant) do\n    settings =\n      Database.from_tenant(tenant, \"realtime_test\", :stop)\n      |> Map.from_struct()\n      |> Keyword.new()\n\n    case Ecto.Adapters.Postgres.storage_up(settings) do\n      :ok -> :ok\n      {:error, :already_up} -> :ok\n      _ -> raise \"Failed to create database\"\n    end\n  end\n\n  def checkout_tenant(opts \\\\ []), do: do_checkout_tenant(opts, :sandbox)\n  def checkout_tenant_unboxed(opts \\\\ []), do: do_checkout_tenant(opts, :unboxed)\n\n  defp do_checkout_tenant(opts, mode) do\n    with container when is_pid(container) <- :poolboy.checkout(Containers.Pool, true, 5_000),\n         port <- Container.port(container) do\n      tenant = repo_run(mode, fn -> Generators.tenant_fixture(%{port: port, migrations_ran: 0}) end)\n\n      run_migrations? = Keyword.get(opts, :run_migrations, false)\n\n      settings = Database.from_tenant(tenant, \"realtime_test\", :stop)\n      settings = %{settings | max_restarts: 0, ssl: false}\n      {:ok, conn} = Database.connect_db(settings)\n\n      try do\n        Postgrex.transaction(conn, fn db_conn ->\n          Postgrex.query!(db_conn, \"DROP SCHEMA IF EXISTS realtime CASCADE\", [])\n          Postgrex.query!(db_conn, \"CREATE SCHEMA IF NOT EXISTS realtime\", [])\n        end)\n\n        storage_up!(tenant)\n\n        RateCounterHelper.stop(tenant.external_id)\n\n        ExUnit.Callbacks.on_exit(fn ->\n          if connect_pid = Connect.whereis(tenant.external_id) do\n            supervisor = {:via, PartitionSupervisor, {Realtime.Tenants.Connect.DynamicSupervisor, tenant.external_id}}\n\n            DynamicSupervisor.terminate_child(supervisor, connect_pid)\n          end\n\n          try do\n            PostgresCdcRls.handle_stop(tenant.external_id, 5_000)\n          catch\n            _, _ -> :ok\n          end\n\n          if mode == :unboxed do\n            repo_run(:unboxed, fn -> Realtime.Api.delete_tenant_by_external_id(tenant.external_id) end)\n          end\n\n          :poolboy.checkin(Containers.Pool, container)\n        end)\n\n        if run_migrations? do\n          case run_migrations(tenant) do\n            {:ok, count} ->\n              :ok = Migrations.create_partitions(conn)\n\n              {:ok, tenant} =\n                repo_run(mode, fn ->\n                  Realtime.Api.update_tenant_by_external_id(tenant.external_id, %{migrations_ran: count})\n                end)\n\n              if mode == :sandbox, do: Realtime.Tenants.Cache.invalidate_tenant_cache(tenant.external_id)\n\n              tenant\n\n            error ->\n              raise \"Failed to run migrations: #{inspect(error)}\"\n          end\n        else\n          tenant\n        end\n      after\n        GenServer.stop(conn)\n      end\n    else\n      _ -> {:error, \"failed to checkout a container\"}\n    end\n  end\n\n  defp repo_run(:unboxed, fun), do: Ecto.Adapters.SQL.Sandbox.unboxed_run(Realtime.Repo, fun)\n  defp repo_run(:sandbox, fun), do: fun.()\n\n  def stop_containers() do\n    {list, 0} = System.cmd(\"docker\", [\"ps\", \"-a\", \"--format\", \"{{.Names}}\", \"--filter\", \"name=realtime-test-*\"])\n    names = list |> String.trim() |> String.split(\"\\n\")\n\n    for name <- names do\n      System.cmd(\"docker\", [\"rm\", \"-f\", name])\n    end\n  end\n\n  def stop_container(external_id) do\n    name = \"realtime-tenant-test-#{external_id}\"\n    System.cmd(\"docker\", [\"rm\", \"-f\", name])\n  end\n\n  defp existing_containers(pattern) do\n    {containers, 0} = System.cmd(\"docker\", [\"ps\", \"--format\", \"{{json .}}\", \"--filter\", \"name=#{pattern}\"])\n\n    containers\n    |> String.split(\"\\n\", trim: true)\n    |> Enum.map(fn container ->\n      container = Jason.decode!(container)\n      # Ports\" => \"0.0.0.0:6445->5432/tcp, [::]:6445->5432/tcp\"\n      regex = ~r/(?<=:)\\d+(?=->)/\n\n      [port] =\n        Regex.scan(regex, container[\"Ports\"])\n        |> List.flatten()\n        |> Enum.uniq()\n\n      {container[\"Names\"], String.to_integer(port)}\n    end)\n  end\n\n  defp check_container_ready(name, attempts \\\\ 50)\n  defp check_container_ready(name, 0), do: raise(\"Container #{name} is not ready\")\n\n  defp check_container_ready(name, attempts) do\n    case System.cmd(\"docker\", [\"exec\", name, \"pg_isready\", \"-p\", \"5432\", \"-h\", \"localhost\"]) do\n      {_, 0} ->\n        :ok\n\n      {_, _} ->\n        Process.sleep(500)\n        check_container_ready(name, attempts - 1)\n    end\n  end\n\n  # This exists so we avoid using an external process on Realtime.Tenants.Migrations\n  defp run_migrations(tenant) do\n    %{extensions: [%{settings: settings} | _]} = tenant\n    settings = Database.from_settings(settings, \"realtime_migrations\", :stop)\n\n    [\n      hostname: settings.hostname,\n      port: settings.port,\n      database: settings.database,\n      password: settings.password,\n      username: settings.username,\n      pool_size: settings.pool_size,\n      backoff_type: settings.backoff_type,\n      socket_options: settings.socket_options,\n      parameters: [application_name: settings.application_name],\n      ssl: settings.ssl\n    ]\n    |> Realtime.Repo.with_dynamic_repo(fn repo ->\n      try do\n        opts = [all: true, prefix: \"realtime\", dynamic_repo: repo, log: false]\n        migrations = Realtime.Tenants.Migrations.migrations()\n        Ecto.Migrator.run(Realtime.Repo, migrations, :up, opts)\n\n        {:ok, length(migrations)}\n      rescue\n        error ->\n          {:error, error}\n      end\n    end)\n  end\n\n  defp docker_run!(name, port) do\n    {_, 0} =\n      System.cmd(\"docker\", [\n        \"run\",\n        \"-d\",\n        \"--rm\",\n        \"--name\",\n        name,\n        \"-e\",\n        \"POSTGRES_HOST=/var/run/postgresql\",\n        \"-e\",\n        \"POSTGRES_PASSWORD=postgres\",\n        \"-p\",\n        \"#{port}:5432\",\n        @image,\n        \"postgres\",\n        \"-c\",\n        \"config_file=/etc/postgresql/postgresql.conf\",\n        \"-c\",\n        \"wal_keep_size=32MB\",\n        \"-c\",\n        \"max_wal_size=32MB\",\n        \"-c\",\n        \"max_slot_wal_keep_size=32MB\"\n      ])\n  end\nend\n"
  },
  {
    "path": "test/support/data_case.ex",
    "content": "defmodule Realtime.DataCase do\n  @moduledoc \"\"\"\n  This module defines the setup for tests requiring\n  access to the application's data layer.\n  You may define functions here to be used as helpers in\n  your tests.\n  Finally, if the test case interacts with the database,\n  we enable the SQL sandbox, so changes done to the database\n  are reverted at the end of every test. If you are using\n  PostgreSQL, you can even run database tests asynchronously\n  by setting `use Realtime.DataCase, async: true`, although\n  this option is not recommended for other databases.\n  \"\"\"\n\n  use ExUnit.CaseTemplate\n  alias Ecto.Adapters.SQL.Sandbox\n\n  using do\n    quote do\n      alias Realtime.Repo\n\n      import Ecto\n      import Ecto.Changeset\n      import Ecto.Query\n      import Realtime.DataCase\n      import Generators\n      import TenantConnection\n    end\n  end\n\n  def setup_sandbox(tags) do\n    pid = Sandbox.start_owner!(Realtime.Repo, shared: not tags[:async])\n    on_exit(fn -> Sandbox.stop_owner(pid) end)\n    :ok\n  end\n\n  setup tags do\n    setup_sandbox(tags)\n    {:ok, conn: Phoenix.ConnTest.build_conn()}\n  end\n\n  @doc \"\"\"\n  A helper that transforms changeset errors into a map of messages.\n      assert {:error, changeset} = Accounts.create_user(%{password: \"short\"})\n      assert \"password is too short\" in errors_on(changeset).password\n      assert %{password: [\"password is too short\"]} = errors_on(changeset)\n  \"\"\"\n  def errors_on(changeset) do\n    Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->\n      Regex.replace(~r\"%{(\\w+)}\", message, fn _, key ->\n        opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()\n      end)\n    end)\n  end\nend\n"
  },
  {
    "path": "test/support/generators.ex",
    "content": "defmodule Generators do\n  @moduledoc \"\"\"\n  Data genarators for tests.\n  \"\"\"\n\n  alias Realtime.Api.Tenant\n  alias Realtime.Crypto\n  alias Realtime.Database\n  alias Realtime.Integration.WebsocketClient\n  def port(), do: Containers.port()\n\n  @spec tenant_fixture(map()) :: Realtime.Api.Tenant.t()\n  def tenant_fixture(override \\\\ %{}) do\n    create_attrs = %{\n      \"external_id\" => random_string(),\n      \"name\" => \"tenant\",\n      \"extensions\" => [\n        %{\n          \"type\" => \"postgres_cdc_rls\",\n          \"settings\" => %{\n            \"db_host\" => \"127.0.0.1\",\n            \"db_name\" => \"postgres\",\n            \"db_user\" => \"supabase_admin\",\n            \"db_password\" => \"postgres\",\n            \"db_port\" => \"#{override[:port] || port()}\",\n            \"poll_interval\" => 100,\n            \"poll_max_changes\" => 100,\n            \"poll_max_record_bytes\" => 1_048_576,\n            \"region\" => \"us-east-1\",\n            \"publication\" => \"supabase_realtime_test\",\n            \"ssl_enforced\" => false\n          }\n        }\n      ],\n      \"postgres_cdc_default\" => \"postgres_cdc_rls\",\n      \"jwt_secret\" => \"new secret\",\n      \"jwt_jwks\" => nil,\n      \"max_presence_events_per_second\" => 50,\n      \"max_payload_size_in_kb\" => 3000\n    }\n\n    override = override |> Enum.map(fn {k, v} -> {\"#{k}\", v} end) |> Map.new()\n    {:ok, tenant} = create_attrs |> Map.merge(override) |> Realtime.Api.create_tenant()\n\n    tenant\n  end\n\n  @spec message_fixture(Realtime.Api.Tenant.t()) :: any()\n  def message_fixture(tenant, override \\\\ %{}) do\n    {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n    Realtime.Tenants.Migrations.create_partitions(db_conn)\n\n    create_attrs = %{\n      \"topic\" => random_string(),\n      \"extension\" => Enum.random([:presence, :broadcast])\n    }\n\n    override = override |> Enum.map(fn {k, v} -> {\"#{k}\", v} end) |> Map.new()\n\n    {:ok, channel} =\n      create_attrs\n      |> Map.merge(override)\n      |> TenantConnection.create_message(db_conn)\n\n    Process.exit(db_conn, :normal)\n    channel\n  end\n\n  def random_string(length \\\\ 20) do\n    length |> :crypto.strong_rand_bytes() |> Base.encode32() |> String.downcase()\n  end\n\n  def clean_table(db_conn, schema, table) do\n    Database.transaction(db_conn, fn transaction_conn ->\n      %{rows: rows} =\n        Postgrex.query!(\n          transaction_conn,\n          \"SELECT policyname FROM pg_policies WHERE schemaname = '#{schema}' and tablename = '#{table}'\",\n          []\n        )\n\n      rows\n      |> List.flatten()\n      |> Enum.each(fn name ->\n        Postgrex.query!(\n          transaction_conn,\n          \"DROP POLICY IF EXISTS \\\"#{name}\\\" ON #{schema}.#{table}\",\n          []\n        )\n      end)\n\n      Postgrex.query!(transaction_conn, \"TRUNCATE TABLE #{schema}.#{table} CASCADE\", [])\n    end)\n  end\n\n  def create_messages_partitions(db_conn, start_date, end_date) do\n    Enum.each(Date.range(start_date, end_date), fn date ->\n      partition_name = \"messages_#{date |> Date.to_iso8601() |> String.replace(\"-\", \"_\")}\"\n      start_timestamp = Date.to_string(date)\n      end_timestamp = Date.to_string(Date.add(date, 1))\n\n      Postgrex.query!(\n        db_conn,\n        \"\"\"\n        CREATE TABLE IF NOT EXISTS realtime.#{partition_name}\n        PARTITION OF realtime.messages\n        FOR VALUES FROM ('#{start_timestamp}') TO ('#{end_timestamp}');\n        \"\"\",\n        []\n      )\n    end)\n  end\n\n  @doc \"\"\"\n  Creates support RLS policies given a name and params to be used by the policies\n  Supported:\n  * read_all_topic - Sets read all topic policy for authenticated role\n  * write_all_topic - Sets write all topic policy for authenticated role\n  * read_topic - Sets read channel policy for authenticated role\n  * write_topic - Sets write channel policy for authenticated role\n  * read_broadcast - Sets read broadcast policy for authenticated role\n  * write_broadcast - Sets write broadcast policy for authenticated role\n  \"\"\"\n  def create_rls_policies(conn, policies, params) do\n    query = \"\"\"\n    CREATE OR REPLACE FUNCTION test_log_error() RETURNS boolean AS $$\n    BEGIN\n    RAISE EXCEPTION 'test error';\n    RETURN TRUE;\n    END$$ LANGUAGE plpgsql;\n    \"\"\"\n\n    Postgrex.query!(conn, query, [])\n\n    Enum.each(policies, fn policy ->\n      query = policy_query(policy, params)\n      Postgrex.query!(conn, query, [])\n    end)\n  end\n\n  def policy_query(query, params \\\\ nil)\n\n  def policy_query(:authenticated_all_topic_read, _) do\n    \"\"\"\n    CREATE POLICY \"authenticated_all_topic_read\"\n    ON realtime.messages FOR SELECT\n    TO authenticated\n    USING ( true );\n    \"\"\"\n  end\n\n  def policy_query(:authenticated_all_topic_insert, _) do\n    \"\"\"\n    CREATE POLICY \"authenticated_all_topic_write\"\n    ON realtime.messages FOR INSERT\n    TO authenticated\n    WITH CHECK ( true );\n    \"\"\"\n  end\n\n  def policy_query(:authenticated_read_matching_user_sub, %{sub: sub}) do\n    \"\"\"\n    CREATE POLICY \"authenticated_read_topic_has_user_sub\"\n    ON realtime.messages FOR SELECT\n    TO authenticated\n    USING (((SELECT auth.uid() AS uid)::text) = '#{sub}')\n    \"\"\"\n  end\n\n  def policy_query(:read_matching_user_role, %{role: role}) do\n    \"\"\"\n    CREATE POLICY \"authenticated_read_topic_has_user_role\"\n    ON realtime.messages FOR SELECT\n    USING (((SELECT auth.role())::text) = '#{role}')\n    \"\"\"\n  end\n\n  def policy_query(:authenticated_write_matching_user_sub, %{sub: sub}) do\n    \"\"\"\n    CREATE POLICY \"authenticated_write_topic_has_user_sub\"\n    ON realtime.messages FOR INSERT\n    TO authenticated\n    WITH CHECK (((SELECT auth.uid() AS uid)::text) = '#{sub}')\n    \"\"\"\n  end\n\n  def policy_query(:write_matching_user_role, %{role: role}) do\n    \"\"\"\n    CREATE POLICY \"authenticated_write_topic_has_user_role\"\n    ON realtime.messages FOR INSERT\n    WITH CHECK (((SELECT auth.role())::text) = '#{role}')\n    \"\"\"\n  end\n\n  def policy_query(:authenticated_read_broadcast, %{topic: name}) do\n    \"\"\"\n    CREATE POLICY \"authenticated_read_broadcast_#{name}\"\n    ON realtime.messages FOR SELECT\n    TO authenticated\n    USING ( realtime.topic() = '#{name}' AND realtime.messages.extension = 'broadcast' );\n    \"\"\"\n  end\n\n  def policy_query(:authenticated_write_broadcast, %{topic: name}) do\n    \"\"\"\n    CREATE POLICY \"authenticated_write_broadcast_#{name}\"\n    ON realtime.messages FOR INSERT\n    TO authenticated\n    WITH CHECK ( realtime.topic() = '#{name}' AND realtime.messages.extension = 'broadcast');\n    \"\"\"\n  end\n\n  def policy_query(:authenticated_read_presence, %{topic: name}) do\n    \"\"\"\n    CREATE POLICY \"authenticated_read_presence_#{name}\"\n    ON realtime.messages FOR SELECT\n    TO authenticated\n    USING ( realtime.topic() = '#{name}' AND realtime.messages.extension = 'presence' );\n    \"\"\"\n  end\n\n  def policy_query(:authenticated_write_presence, %{topic: name}) do\n    \"\"\"\n    CREATE POLICY \"authenticated_write_presence_#{name}\"\n    ON realtime.messages FOR INSERT\n    TO authenticated\n    WITH CHECK ( realtime.topic() = '#{name}' AND realtime.messages.extension = 'presence' );\n    \"\"\"\n  end\n\n  def policy_query(:authenticated_read_broadcast_and_presence, %{topic: name}) do\n    \"\"\"\n    CREATE POLICY \"authenticated_read_presence_#{name}\"\n    ON realtime.messages FOR SELECT\n    TO authenticated\n    USING ( realtime.topic() = '#{name}' AND realtime.messages.extension IN ('presence', 'broadcast') );\n    \"\"\"\n  end\n\n  def policy_query(:authenticated_write_broadcast_and_presence, %{topic: name}) do\n    \"\"\"\n    CREATE POLICY \"authenticated_write_presence_#{name}\"\n    ON realtime.messages FOR INSERT\n    TO authenticated\n    WITH CHECK ( realtime.topic() = '#{name}' AND realtime.messages.extension IN ('presence', 'broadcast') );\n    \"\"\"\n  end\n\n  def policy_query(:broken_read_presence, _) do\n    \"\"\"\n    CREATE POLICY \"authenticated_read_presence\"\n    ON realtime.messages FOR SELECT\n    TO authenticated\n    USING (  (SELECT test_log_error())  );\n    \"\"\"\n  end\n\n  def policy_query(:broken_write_presence, _) do\n    \"\"\"\n    CREATE POLICY \"authenticated_write_presence\"\n    ON realtime.messages FOR INSERT\n    TO authenticated\n    WITH CHECK ( (SELECT test_log_error()) );\n    \"\"\"\n  end\n\n  @spec generate_jwt_token(Tenant.t() | binary()) :: Joken.sign_result()\n  def generate_jwt_token(secret_or_tenant) do\n    claims = %{role: \"authenticated\", exp: System.system_time(:second) + 100_000}\n    generate_jwt_token(secret_or_tenant, claims)\n  end\n\n  @spec generate_jwt_token(Tenant.t() | binary(), map()) :: Joken.sign_result()\n  def generate_jwt_token(%Tenant{} = tenant, claims) do\n    secret = Crypto.decrypt!(tenant.jwt_secret)\n    generate_jwt_token(secret, claims)\n  end\n\n  def generate_jwt_token(secret, claims) when is_binary(secret) do\n    signer = Joken.Signer.create(\"HS256\", secret)\n    {:ok, claims} = Joken.generate_claims(%{}, claims)\n    {:ok, jwt, _} = Joken.encode_and_sign(claims, signer)\n    jwt\n  end\n\n  defp test_port do\n    :realtime\n    |> Application.get_env(RealtimeWeb.Endpoint, %{})\n    |> get_in([:http, :port]) || 4002\n  end\n\n  def get_connection(tenant, serializer \\\\ Phoenix.Socket.V1.JSONSerializer, opts \\\\ []) do\n    params = Keyword.get(opts, :params, %{log_level: :warning})\n    claims = Keyword.get(opts, :claims, %{})\n    role = Keyword.get(opts, :role, \"anon\")\n\n    params = Enum.reduce(params, \"\", fn {k, v}, acc -> \"#{acc}&#{k}=#{v}\" end)\n    uri = \"#{uri(tenant, serializer)}&#{params}\"\n\n    with {:ok, token} <- token_valid(tenant, role, claims),\n         {:ok, socket} <- WebsocketClient.connect(self(), uri, serializer, [{\"x-api-key\", token}]) do\n      {socket, token}\n    end\n  end\n\n  def uri(tenant, serializer, port \\\\ nil),\n    do: \"ws://#{tenant.external_id}.localhost:#{port || test_port()}/socket/websocket?vsn=#{vsn(serializer)}\"\n\n  defp vsn(Phoenix.Socket.V1.JSONSerializer), do: \"1.0.0\"\n  defp vsn(RealtimeWeb.Socket.V2Serializer), do: \"2.0.0\"\n\n  @spec token_valid(Tenant.t(), binary(), map()) :: {:ok, binary()}\n  def token_valid(tenant, role, claims \\\\ %{}), do: generate_token(tenant, Map.put(claims, :role, role))\n  @spec token_no_role(Tenant.t()) :: {:ok, binary()}\n  def token_no_role(tenant), do: generate_token(tenant)\n\n  @spec generate_token(Tenant.t() | binary(), map()) :: {:ok, binary()}\n  def generate_token(tenant, claims \\\\ %{}) do\n    claims =\n      Map.merge(\n        %{ref: \"127.0.0.1\", iat: System.system_time(:second), exp: System.system_time(:second) + 604_800},\n        claims\n      )\n\n    {:ok, generate_jwt_token(tenant, claims)}\n  end\nend\n"
  },
  {
    "path": "test/support/integrations.ex",
    "content": "defmodule Integrations do\n  import ExUnit.Assertions\n  import Generators\n\n  alias Realtime.Api.Tenant\n  alias Realtime.Database\n  alias Realtime.Tenants.Authorization\n  alias Realtime.Tenants.Connect\n\n  def checkout_tenant_and_connect(_context \\\\ %{}) do\n    tenant = Containers.checkout_tenant(run_migrations: true)\n    {:ok, db_conn} = Connect.lookup_or_start_connection(tenant.external_id)\n    assert Connect.ready?(tenant.external_id)\n    %{db_conn: db_conn, tenant: tenant}\n  end\n\n  def rls_context(%{tenant: tenant} = context) do\n    {:ok, db_conn} = Database.connect(tenant, \"realtime_test\", :stop)\n    clean_table(db_conn, \"realtime\", \"messages\")\n    topic = Map.get(context, :topic, random_string())\n    policies = Map.get(context, :policies, nil)\n    role = Map.get(context, :role, nil)\n    sub = Map.get(context, :sub, nil)\n\n    if policies, do: create_rls_policies(db_conn, policies, %{topic: topic, role: role, sub: sub})\n\n    authorization_context =\n      Authorization.build_authorization_params(%{\n        tenant_id: tenant.external_id,\n        topic: topic,\n        headers: [{\"header-1\", \"value-1\"}],\n        claims: %{sub: sub, role: role},\n        role: role,\n        sub: sub\n      })\n\n    ExUnit.Callbacks.on_exit(fn ->\n      if Process.alive?(db_conn) do\n        try do\n          GenServer.stop(db_conn, :normal, 1_000)\n        catch\n          :exit, _ -> :ok\n        end\n      end\n    end)\n\n    %{topic: topic, role: role, sub: sub, db_conn: db_conn, authorization_context: authorization_context}\n  end\n\n  def change_tenant_configuration(%Tenant{external_id: external_id}, limit, value) do\n    tenant =\n      external_id\n      |> Realtime.Tenants.get_tenant_by_external_id()\n      |> Tenant.changeset(%{limit => value})\n      |> Realtime.Repo.update!()\n\n    Realtime.Tenants.Cache.update_cache(tenant)\n  end\n\n  def checkout_tenant_connect_and_setup_postgres_changes(_context \\\\ %{}) do\n    %{db_conn: db_conn} = result = checkout_tenant_and_connect()\n    setup_postgres_changes(db_conn)\n    result\n  end\n\n  def setup_postgres_changes(conn) do\n    publication = \"supabase_realtime_test\"\n\n    Postgrex.transaction(conn, fn db_conn ->\n      queries = [\n        \"DROP TABLE IF EXISTS public.test\",\n        \"DROP PUBLICATION IF EXISTS #{publication}\",\n        \"create sequence if not exists test_id_seq;\",\n        \"\"\"\n        create table \"public\".\"test\" (\n        \"id\" int4 not null default nextval('test_id_seq'::regclass),\n        \"details\" text,\n        \"binary_data\" bytea,\n        primary key (\"id\"));\n        \"\"\",\n        \"grant all on table public.test to anon;\",\n        \"grant all on table public.test to supabase_admin;\",\n        \"grant all on table public.test to authenticated;\",\n        \"create publication #{publication} for all tables\",\n        \"\"\"\n        DO $$\n        DECLARE\n        r RECORD;\n        BEGIN\n        FOR r IN\n          SELECT slot_name, active_pid\n          FROM pg_replication_slots\n          WHERE slot_name LIKE 'supabase_realtime%'\n        LOOP\n          IF r.active_pid IS NOT NULL THEN\n            BEGIN\n              SELECT pg_terminate_backend(r.active_pid);\n              PERFORM pg_sleep(0.5);\n            EXCEPTION WHEN OTHERS THEN\n              NULL;\n            END;\n          END IF;\n\n          BEGIN\n            IF EXISTS (SELECT 1 FROM pg_replication_slots WHERE slot_name = r.slot_name) THEN\n              PERFORM pg_drop_replication_slot(r.slot_name);\n            END IF;\n          EXCEPTION WHEN OTHERS THEN\n            NULL;\n          END;\n        END LOOP;\n        END$$;\n        \"\"\"\n      ]\n\n      Enum.each(queries, &Postgrex.query!(db_conn, &1, []))\n    end)\n  end\n\n  def assert_process_down(pid, timeout \\\\ 1000) do\n    ref = Process.monitor(pid)\n    assert_receive {:DOWN, ^ref, :process, ^pid, _reason}, timeout\n  end\nend\n"
  },
  {
    "path": "test/support/joken_current_time_mock.ex",
    "content": "defmodule RealtimeWeb.Joken.CurrentTime.Mock do\n  @moduledoc \"\"\"\n\n  Mock implementation of Joken current time with time freezing.\n\n  This is a copy of Joken.CurrentTime.Mock.\n\n  \"\"\"\n\n  use Agent\n\n  def start_link do\n    Agent.start_link(\n      fn ->\n        %{is_frozen: false, frozen_value: nil}\n      end,\n      name: Joken\n    )\n  end\n\n  def child_spec(_args) do\n    %{\n      id: __MODULE__,\n      start: {__MODULE__, :start_link, []}\n    }\n  end\n\n  def current_time do\n    state = Agent.get(Joken, fn state -> state end)\n\n    if state[:is_frozen] do\n      state[:frozen_value]\n    else\n      :os.system_time(:second)\n    end\n  end\n\n  def freeze do\n    freeze(:os.system_time(:second))\n  end\n\n  def freeze(timestamp) do\n    Agent.update(Joken, fn _state ->\n      %{is_frozen: true, frozen_value: timestamp}\n    end)\n  end\n\n  def unique_name_per_process do\n    binary_pid =\n      self()\n      |> :erlang.pid_to_list()\n      |> :erlang.iolist_to_binary()\n\n    \"{__MODULE__}_#{binary_pid}\" |> String.to_atom()\n  end\nend\n"
  },
  {
    "path": "test/support/metrics_helper.ex",
    "content": "defmodule MetricsHelper do\n  @spec search(String.t(), String.t(), map() | keyword() | nil) ::\n          {:ok, String.t(), map(), String.t()} | {:error, String.t()}\n  def search(prometheus_metrics, metric_name, expected_tags \\\\ nil) do\n    # Escape the metric_name to handle any special regex characters\n    escaped_name = Regex.escape(metric_name)\n    regex = ~r/^(?<name>#{escaped_name})\\{(?<tags>[^}]+)\\}\\s+(?<value>\\d+(?:\\.\\d+)?)$/\n\n    prometheus_metrics\n    |> IO.iodata_to_binary()\n    |> String.split(\"\\n\", trim: true)\n    |> Enum.find_value(\n      nil,\n      fn item ->\n        case parse(item, regex, expected_tags) do\n          {:ok, value} -> value\n          {:error, _reason} -> false\n        end\n      end\n    )\n    |> case do\n      nil -> nil\n      number -> String.to_integer(number)\n    end\n  end\n\n  defp parse(metric_string, regex, expected_tags) do\n    case Regex.named_captures(regex, metric_string) do\n      %{\"name\" => _name, \"tags\" => tags_string, \"value\" => value} ->\n        tags = parse_tags(tags_string)\n\n        if expected_tags && !matching_tags(tags, expected_tags) do\n          {:error, \"Tags do not match expected tags\"}\n        else\n          {:ok, value}\n        end\n\n      nil ->\n        {:error, \"Invalid metric format or metric name mismatch\"}\n    end\n  end\n\n  defp parse_tags(tags_string) do\n    ~r/(?<key>[a-zA-Z_][a-zA-Z0-9_]*)=\"(?<value>[^\"]*)\"/\n    |> Regex.scan(tags_string, capture: :all_names)\n    |> Enum.map(fn [key, value] -> {key, value} end)\n    |> Map.new()\n  end\n\n  defp matching_tags(tags, expected_tags) do\n    Enum.all?(expected_tags, fn {k, v} -> Map.get(tags, to_string(k)) == to_string(v) end)\n  end\nend\n"
  },
  {
    "path": "test/support/prometheus_fixtures.ex",
    "content": ""
  },
  {
    "path": "test/support/rate_counter_helper.ex",
    "content": "defmodule RateCounterHelper do\n  alias Realtime.RateCounter\n\n  @spec stop(term()) :: :ok\n  def stop(tenant_id) do\n    keys =\n      Registry.select(Realtime.Registry.Unique, [\n        {{{:\"$1\", :_, {:_, :_, :\"$2\"}}, :\"$3\", :_}, [{:==, :\"$1\", RateCounter}, {:==, :\"$2\", tenant_id}], [:\"$_\"]}\n      ])\n\n    Enum.each(keys, fn {{_, _, key}, {pid, _}} ->\n      if Process.alive?(pid), do: GenServer.stop(pid)\n      Realtime.GenCounter.delete(key)\n      Cachex.del!(RateCounter, key)\n    end)\n\n    :ok\n  end\n\n  @spec tick!(RateCounter.Args.t()) :: RateCounter.t()\n  def tick!(args) do\n    [{pid, _}] = Registry.lookup(Realtime.Registry.Unique, {RateCounter, :rate_counter, args.id})\n    send(pid, :tick)\n    {:ok, :sys.get_state(pid)}\n  end\n\n  def tick_tenant_rate_counters!(tenant_id) do\n    keys =\n      Registry.select(Realtime.Registry.Unique, [\n        {{{:\"$1\", :_, {:_, :_, :\"$2\"}}, :\"$3\", :_}, [{:==, :\"$1\", RateCounter}, {:==, :\"$2\", tenant_id}], [:\"$_\"]}\n      ])\n\n    Enum.each(keys, fn {{_, _, _key}, {pid, _}} ->\n      send(pid, :tick)\n      # do a get_state to wait for the tick to be processed\n      :sys.get_state(pid)\n    end)\n\n    :ok\n  end\nend\n"
  },
  {
    "path": "test/support/replication_test_handler.ex",
    "content": "defmodule Replication.TestHandler do\n  @behaviour PostgresReplication.Handler\n  import PostgresReplication.Protocol\n  alias PostgresReplication.Protocol.KeepAlive\n\n  @impl true\n  def call(message, _metadata) when is_write(message) do\n    :noreply\n  end\n\n  def call(message, _metadata) when is_keep_alive(message) do\n    reply =\n      case parse(message) do\n        %KeepAlive{reply: :now, wal_end: wal_end} ->\n          wal_end = wal_end + 1\n          standby(wal_end, wal_end, wal_end, :now)\n\n        _ ->\n          hold()\n      end\n\n    {:reply, reply}\n  end\n\n  def call(_, _), do: :noreply\nend\n"
  },
  {
    "path": "test/support/tenant_connection.ex",
    "content": "defmodule TenantConnection do\n  @moduledoc \"\"\"\n  Boilerplate code to handle Realtime.Tenants.Connect during tests\n  \"\"\"\n  alias Realtime.Api.Message\n  alias Realtime.Database\n  alias Realtime.Tenants.Repo\n  alias Realtime.Tenants.Connect\n  alias RealtimeWeb.Endpoint\n\n  def create_message(attrs, conn, opts \\\\ [mode: :savepoint]) do\n    message = Message.changeset(%Message{}, attrs)\n\n    {:ok, result} =\n      Database.transaction(conn, fn transaction_conn ->\n        with {:ok, %Message{} = message} <- Repo.insert(transaction_conn, message, Message, opts) do\n          message\n        end\n      end)\n\n    case result do\n      %Ecto.Changeset{valid?: false} = error -> {:error, error}\n      {:error, error} -> {:error, error}\n      result -> {:ok, result}\n    end\n  end\n\n  def ensure_connect_down(tenant_id) do\n    # Using syn and not a normal Process.monitor because we want to ensure\n    # that the process is down AND that the registry has been updated accordingly\n    Endpoint.subscribe(\"connect:#{tenant_id}\")\n\n    if Connect.whereis(tenant_id) do\n      Connect.shutdown(tenant_id)\n\n      receive do\n        %{event: \"connect_down\"} -> :ok\n      after\n        5000 ->\n          if Connect.whereis(tenant_id) do\n            raise \"Connect process for tenant #{tenant_id} did not shut down in time\"\n          end\n      end\n    end\n  after\n    Endpoint.unsubscribe(\"connect:#{tenant_id}\")\n  end\nend\n"
  },
  {
    "path": "test/support/tracing.ex",
    "content": "defmodule Realtime.Tracing do\n  defmacro __using__(_opts) do\n    quote do\n      # Use Record module to extract fields of the Span record from the opentelemetry dependency.\n      require Record\n      @span_fields Record.extract(:span, from: \"deps/opentelemetry/include/otel_span.hrl\")\n      @status_fields Record.extract(:status, from: \"deps/opentelemetry_api/include/opentelemetry.hrl\")\n      @attributes_fields Record.extract(:attributes, from: \"deps/opentelemetry_api/src/otel_attributes.erl\")\n      # Define macros for otel stuff\n      Record.defrecordp(:span, @span_fields)\n      Record.defrecordp(:status, @status_fields)\n      Record.defrecordp(:attributes, @attributes_fields)\n    end\n  end\nend\n"
  },
  {
    "path": "test/support/websocket_client.ex",
    "content": "# This file draws heavily from https://github.com/phoenixframework/phoenix/blob/22ef871312bd06fa591c2da04d32949ebc77f750/test/support/websocket_client.exs\n# License: https://github.com/phoenixframework/phoenix/blob/22ef871312bd06fa591c2da04d32949ebc77f750/LICENSE.md\n\ndefmodule Realtime.Integration.WebsocketClient do\n  @moduledoc \"\"\"\n  A WebSocket client used to test Phoenix.Channel\n  \"\"\"\n\n  use GenServer\n  import Kernel, except: [send: 2]\n\n  defstruct [\n    :conn,\n    :request_ref,\n    :websocket,\n    :caller,\n    :status,\n    :resp_headers,\n    :sender,\n    :serializer,\n    closing?: false,\n    topics: %{},\n    # Use different initial join_ref from ref to\n    # make sure the server is not coupling them.\n    join_ref: 11,\n    ref: 1\n  ]\n\n  alias Phoenix.Socket.Message\n\n  @doc \"\"\"\n  Starts the WebSocket client for given ws URL. `Phoenix.Socket.Message`s\n  received from the server are forwarded to the sender pid.\n  \"\"\"\n  def connect(sender, url, serializer, headers \\\\ []) do\n    with {:ok, socket} <- GenServer.start_link(__MODULE__, {sender, serializer}),\n         {:ok, :connected} <- GenServer.call(socket, {:connect, url, headers}) do\n      {:ok, socket}\n    end\n  end\n\n  @doc \"\"\"\n  Closes the socket\n  \"\"\"\n  def close(socket), do: GenServer.cast(socket, :close)\n\n  @doc \"\"\"\n  Sends an event to the WebSocket server per the message protocol.\n  \"\"\"\n  def send_event(socket, topic, event, msg) do\n    GenServer.call(socket, {:send, %Message{topic: topic, event: event, payload: msg}})\n  end\n\n  @doc \"\"\"\n  Sends a low-level text message to the client.\n  \"\"\"\n  def send(socket, msg), do: GenServer.call(socket, {:send, msg})\n\n  @doc \"\"\"\n  Sends a heartbeat event\n  \"\"\"\n  def send_heartbeat(socket), do: send_event(socket, \"phoenix\", \"heartbeat\", %{})\n\n  @doc \"\"\"\n  Sends join event to the WebSocket server per the Message protocol\n  \"\"\"\n  def join(socket, topic, msg), do: send_event(socket, topic, \"phx_join\", msg)\n\n  @doc \"\"\"\n  Sends leave event to the WebSocket server per the Message protocol\n  \"\"\"\n  def leave(socket, topic, msg), do: send_event(socket, topic, \"phx_leave\", msg)\n\n  ## GenServer implementation\n\n  @doc false\n  def init({sender, serializer}) do\n    state = %__MODULE__{sender: sender, serializer: serializer}\n    {:ok, state}\n  end\n\n  @doc false\n  def handle_call({:connect, url, headers}, from, state) do\n    uri = URI.parse(url)\n\n    http_scheme =\n      case uri.scheme do\n        \"ws\" -> :http\n        \"wss\" -> :https\n      end\n\n    ws_scheme =\n      case uri.scheme do\n        \"ws\" -> :ws\n        \"wss\" -> :wss\n      end\n\n    path =\n      case uri.query do\n        nil -> uri.path\n        query -> uri.path <> \"?\" <> query\n      end\n\n    [subdomain, host] = String.split(uri.host, \".\")\n\n    with {:ok, conn} <- Mint.HTTP.connect(http_scheme, host, uri.port, hostname: subdomain),\n         {:ok, conn, ref} <- Mint.WebSocket.upgrade(ws_scheme, conn, path, headers) do\n      state = %{state | conn: conn, request_ref: ref, caller: from}\n      {:noreply, state}\n    else\n      {:error, reason} -> {:reply, {:error, reason}, state}\n      {:error, conn, reason} -> {:reply, {:error, reason}, put_in(state.conn, conn)}\n    end\n  end\n\n  def handle_call({:send, msg}, _from, state) do\n    {frame, state} = serialize_msg(msg, state)\n\n    case stream_frame(state, frame) do\n      {:ok, state} -> {:reply, :ok, state}\n      {:error, state, reason} -> {:reply, {:error, reason}, state}\n    end\n  end\n\n  @doc false\n  def handle_cast(:close, state), do: do_close(state)\n\n  defp do_close(state) do\n    # Streaming a close frame may fail if the server has already closed\n    # for writing.\n    _ = stream_frame(state, :close)\n    Mint.HTTP.close(state.conn)\n    {:stop, :normal, state}\n  end\n\n  @doc false\n  def handle_info(message, state) do\n    case Mint.WebSocket.stream(state.conn, message) do\n      {:ok, conn, responses} ->\n        state = state.conn |> put_in(conn) |> handle_responses(responses)\n        if state.closing?, do: do_close(state), else: {:noreply, state}\n\n      {:error, _conn, %Mint.TransportError{reason: :closed}, _} ->\n        {:stop, :normal, state}\n\n      {:error, conn, reason, _responses} ->\n        state = state.conn |> put_in(conn) |> reply({:error, reason})\n        {:noreply, state}\n\n      :unknown ->\n        {:noreply, state}\n    end\n  end\n\n  defp handle_responses(state, responses)\n\n  defp handle_responses(%{request_ref: ref} = state, [{:status, ref, status} | rest]) do\n    state.status\n    |> put_in(status)\n    |> handle_responses(rest)\n  end\n\n  defp handle_responses(%{request_ref: ref} = state, [{:headers, ref, resp_headers} | rest]) do\n    state.resp_headers\n    |> put_in(resp_headers)\n    |> handle_responses(rest)\n  end\n\n  defp handle_responses(%{request_ref: ref} = state, [{:done, ref} | rest]) do\n    case Mint.WebSocket.new(state.conn, ref, state.status, state.resp_headers) do\n      {:ok, conn, websocket} ->\n        %{state | conn: conn, websocket: websocket, status: nil, resp_headers: nil}\n        |> reply({:ok, :connected})\n        |> handle_responses(rest)\n\n      {:error, conn, reason} ->\n        state.conn\n        |> put_in(conn)\n        |> reply({:error, reason})\n    end\n  end\n\n  defp handle_responses(%{request_ref: ref, websocket: websocket} = state, [{:data, ref, data} | rest])\n       when websocket != nil do\n    case Mint.WebSocket.decode(websocket, data) do\n      {:ok, websocket, frames} ->\n        state.websocket\n        |> put_in(websocket)\n        |> handle_frames(frames)\n        |> handle_responses(rest)\n\n      {:error, websocket, reason} ->\n        state.websocket\n        |> put_in(websocket)\n        |> reply({:error, reason})\n    end\n  end\n\n  defp handle_responses(state, [_response | rest]), do: handle_responses(state, rest)\n\n  defp handle_responses(state, []), do: state\n\n  defp handle_frames(state, frames) do\n    {frames, state} =\n      Enum.flat_map_reduce(frames, state, fn\n        # reply to ping with pong\n        {:ping, data} = frame, state ->\n          {:ok, state} = stream_frame(state, {:pong, data})\n\n          {[frame], state}\n\n        # deserialize text and binary frames\n        {:text, text}, state ->\n          frame =\n            case state.serializer do\n              :noop -> {:text, text}\n              serializer -> serializer.decode!(text, opcode: :text)\n            end\n\n          {[frame], state}\n\n        {:binary, data}, state ->\n          {[binary_decode(data)], state}\n\n        # prepare to close the connection when a close frame is received\n        {:close, _code, _data}, state ->\n          {[], put_in(state.closing?, true)}\n\n        frame, state ->\n          {[frame], state}\n      end)\n\n    Enum.each(frames, &Kernel.send(state.sender, &1))\n\n    state\n  end\n\n  # Encodes a frame as a binary and sends it along the wire, keeping `conn`\n  # and `websocket` up to date in `state`.\n  defp stream_frame(state, frame) do\n    with {:ok, websocket, data} <- Mint.WebSocket.encode(state.websocket, frame),\n         state = put_in(state.websocket, websocket),\n         {:ok, conn} <- Mint.WebSocket.stream_request_body(state.conn, state.request_ref, data) do\n      {:ok, put_in(state.conn, conn)}\n    else\n      {:error, %Mint.WebSocket{} = websocket, reason} ->\n        {:error, put_in(state.websocket, websocket), reason}\n\n      {:error, conn, reason} ->\n        {:error, put_in(state.conn, conn), reason}\n    end\n  end\n\n  # reply to an open GenServer call request if there is one\n  defp reply(state, response) do\n    if state.caller, do: GenServer.reply(state.caller, response)\n    put_in(state.caller, nil)\n  end\n\n  defp serialize_msg(msg, %{serializer: :noop} = state), do: {msg, state}\n\n  defp serialize_msg(%Message{payload: {:binary, _}} = msg, %{ref: ref} = state) do\n    {join_ref, state} = join_ref_for(msg, state)\n    msg = Map.merge(msg, %{ref: to_string(ref), join_ref: to_string(join_ref)})\n    {{:binary, binary_encode_push!(msg)}, put_in(state.ref, ref + 1)}\n  end\n\n  defp serialize_msg(%Message{} = msg, %{ref: ref} = state) do\n    {join_ref, state} = join_ref_for(msg, state)\n    msg = Map.merge(msg, %{ref: to_string(ref), join_ref: to_string(join_ref)})\n    {{:text, encode!(msg, state)}, put_in(state.ref, ref + 1)}\n  end\n\n  defp serialize_msg(msg, state), do: {msg, state}\n\n  defp join_ref_for(\n         %{topic: topic, event: \"phx_join\"},\n         %{topics: topics, join_ref: join_ref} = state\n       ) do\n    topics = Map.put(topics, topic, join_ref)\n    {join_ref, %{state | topics: topics, join_ref: join_ref + 1}}\n  end\n\n  defp join_ref_for(%{topic: topic}, %{topics: topics} = state) do\n    {Map.get(topics, topic), state}\n  end\n\n  defp encode!(map, state) do\n    {:socket_push, :text, chardata} = state.serializer.encode!(map)\n    IO.chardata_to_string(chardata)\n  end\n\n  defp binary_encode_push!(%Message{payload: {:binary, data}} = msg) do\n    ref = to_string(msg.ref)\n    join_ref = to_string(msg.join_ref)\n    join_ref_size = byte_size(join_ref)\n    ref_size = byte_size(ref)\n    topic_size = byte_size(msg.topic)\n    event_size = byte_size(msg.event)\n\n    <<\n      0::size(8),\n      join_ref_size::size(8),\n      ref_size::size(8),\n      topic_size::size(8),\n      event_size::size(8),\n      join_ref::binary-size(join_ref_size),\n      ref::binary-size(ref_size),\n      msg.topic::binary-size(topic_size),\n      msg.event::binary-size(event_size),\n      data::binary\n    >>\n  end\n\n  # push\n  defp binary_decode(<<\n         0::size(8),\n         join_ref_size::size(8),\n         topic_size::size(8),\n         event_size::size(8),\n         join_ref::binary-size(join_ref_size),\n         topic::binary-size(topic_size),\n         event::binary-size(event_size),\n         data::binary\n       >>) do\n    %Message{join_ref: join_ref, topic: topic, event: event, payload: {:binary, data}}\n  end\n\n  # reply\n  defp binary_decode(<<\n         1::size(8),\n         join_ref_size::size(8),\n         ref_size::size(8),\n         topic_size::size(8),\n         status_size::size(8),\n         join_ref::binary-size(join_ref_size),\n         ref::binary-size(ref_size),\n         topic::binary-size(topic_size),\n         status::binary-size(status_size),\n         data::binary\n       >>) do\n    payload = %{\"status\" => status, \"response\" => {:binary, data}}\n    %Message{join_ref: join_ref, ref: ref, topic: topic, event: \"phx_reply\", payload: payload}\n  end\nend\n"
  },
  {
    "path": "test/test_helper.exs",
    "content": "start_time = :os.system_time(:millisecond)\n\nalias Realtime.Api\nmax_cases = String.to_integer(System.get_env(\"MAX_CASES\", \"4\"))\nExUnit.start(exclude: [:failing], max_cases: max_cases, capture_log: true)\n\nmax_cases = ExUnit.configuration()[:max_cases]\n\nContainers.pull()\n\nif System.get_env(\"REUSE_CONTAINERS\") != \"true\" do\n  Containers.stop_containers()\nend\n\n{:ok, _pid} = Containers.start_link(max_cases)\n\nfor tenant <- Api.list_tenants(), do: Api.delete_tenant_by_external_id(tenant.external_id)\n\nEcto.Adapters.SQL.Sandbox.mode(Realtime.Repo, :manual)\n\nMimic.copy(:syn)\nMimic.copy(Extensions.PostgresCdcRls)\nMimic.copy(Extensions.PostgresCdcRls.Replications)\nMimic.copy(Extensions.PostgresCdcRls.Subscriptions)\nMimic.copy(Realtime.Database)\nMimic.copy(Realtime.GenCounter)\nMimic.copy(Realtime.GenRpc)\nMimic.copy(Realtime.Nodes)\nMimic.copy(Realtime.Repo.Replica)\nMimic.copy(Realtime.RateCounter)\nMimic.copy(Realtime.Tenants.Authorization)\nMimic.copy(Realtime.Tenants.Cache)\nMimic.copy(Realtime.Tenants.Connect)\nMimic.copy(Realtime.Tenants.Migrations)\nMimic.copy(Realtime.Tenants.Rebalancer)\nMimic.copy(Realtime.Tenants.ReplicationConnection)\nMimic.copy(RealtimeWeb.ChannelsAuthorization)\nMimic.copy(RealtimeWeb.Endpoint)\nMimic.copy(RealtimeWeb.JwtVerification)\nMimic.copy(RealtimeWeb.TenantBroadcaster)\nMimic.copy(NimbleZTA.Cloudflare)\n\npartition = System.get_env(\"MIX_TEST_PARTITION\")\nnode_name = if partition, do: :\"main#{partition}@127.0.0.1\", else: :\"main@127.0.0.1\"\n:net_kernel.start([node_name])\nregion = Application.get_env(:realtime, :region)\n[{pid, _}] = :syn.members(RegionNodes, region)\n:syn.update_member(RegionNodes, region, pid, fn _ -> [node: node()] end)\n\nend_time = :os.system_time(:millisecond)\nIO.puts(\"[test_helper.exs] Time to start tests: #{end_time - start_time} ms\")\n"
  }
]