Full Code of rails/solid_cable for AI

main 92818a0fb781 cached
168 files
154.5 KB
55.6k tokens
134 symbols
1 requests
Download .txt
Repository: rails/solid_cable
Branch: main
Commit: 92818a0fb781
Files: 168
Total size: 154.5 KB

Directory structure:
gitextract_sjfrw0w9/

├── .github/
│   └── workflows/
│       ├── lint.yml
│       └── test.yml
├── .gitignore
├── .rubocop.yml
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app/
│   ├── jobs/
│   │   └── solid_cable/
│   │       └── trim_job.rb
│   └── models/
│       └── solid_cable/
│           ├── message.rb
│           └── record.rb
├── bench/
│   ├── .dockerignore
│   ├── .ruby-version
│   ├── Dockerfile
│   ├── Gemfile
│   ├── Rakefile
│   ├── app/
│   │   ├── assets/
│   │   │   ├── builds/
│   │   │   │   └── .keep
│   │   │   ├── images/
│   │   │   │   └── .keep
│   │   │   └── stylesheets/
│   │   │       └── application.tailwind.css
│   │   ├── channels/
│   │   │   ├── application_cable/
│   │   │   │   ├── channel.rb
│   │   │   │   └── connection.rb
│   │   │   └── broadcast_channel.rb
│   │   ├── controllers/
│   │   │   ├── application_controller.rb
│   │   │   ├── concerns/
│   │   │   │   └── .keep
│   │   │   ├── rooms_controller.rb
│   │   │   └── ws_debugger_controller.rb
│   │   ├── javascript/
│   │   │   ├── application.js
│   │   │   ├── channels/
│   │   │   │   ├── broadcast_channel.js
│   │   │   │   ├── consumer.js
│   │   │   │   └── index.js
│   │   │   └── controllers/
│   │   │       ├── application.js
│   │   │       └── index.js
│   │   ├── jobs/
│   │   │   └── application_job.rb
│   │   ├── mailers/
│   │   │   └── application_mailer.rb
│   │   ├── models/
│   │   │   ├── application_record.rb
│   │   │   ├── concerns/
│   │   │   │   └── .keep
│   │   │   └── room.rb
│   │   └── views/
│   │       ├── layouts/
│   │       │   ├── application.html.erb
│   │       │   ├── mailer.html.erb
│   │       │   └── mailer.text.erb
│   │       ├── rooms/
│   │       │   ├── _form.html.erb
│   │       │   ├── _room.html.erb
│   │       │   ├── edit.html.erb
│   │       │   ├── index.html.erb
│   │       │   ├── new.html.erb
│   │       │   └── show.html.erb
│   │       └── ws_debugger/
│   │           └── show.html.erb
│   ├── bin/
│   │   ├── bundle
│   │   ├── docker-entrypoint
│   │   ├── importmap
│   │   ├── kamal
│   │   ├── rails
│   │   ├── rake
│   │   ├── rubocop
│   │   └── setup
│   ├── config/
│   │   ├── application.rb
│   │   ├── boot.rb
│   │   ├── cable.yml
│   │   ├── credentials.yml.enc
│   │   ├── database.yml
│   │   ├── deploy.yml
│   │   ├── environment.rb
│   │   ├── environments/
│   │   │   ├── development.rb
│   │   │   ├── production.rb
│   │   │   └── test.rb
│   │   ├── importmap.rb
│   │   ├── init.sql
│   │   ├── initializers/
│   │   │   ├── assets.rb
│   │   │   ├── content_security_policy.rb
│   │   │   ├── filter_parameter_logging.rb
│   │   │   ├── inflections.rb
│   │   │   └── permissions_policy.rb
│   │   ├── locales/
│   │   │   └── en.yml
│   │   ├── puma.rb
│   │   ├── routes.rb
│   │   ├── storage.yml
│   │   └── tailwind.config.js
│   ├── config.ru
│   ├── db/
│   │   ├── migrate/
│   │   │   ├── 20240529231225_create_rooms.rb
│   │   │   ├── 20240530031126_create_solid_cable_message.solid_cable.rb
│   │   │   ├── 20240607184931_index_channels.solid_cable.rb
│   │   │   ├── 20240609023040_create_active_error_faults.active_error.rb
│   │   │   ├── 20240609023041_create_active_error_instances.active_error.rb
│   │   │   └── 20240912235943_create_compact_channel.rb
│   │   ├── schema.rb
│   │   └── seeds.rb
│   ├── loadtest.js
│   ├── log/
│   │   └── .keep
│   ├── public/
│   │   ├── 404.html
│   │   ├── 406-unsupported-browser.html
│   │   ├── 422.html
│   │   ├── 500.html
│   │   └── robots.txt
│   ├── test/
│   │   ├── channels/
│   │   │   ├── application_cable/
│   │   │   │   └── connection_test.rb
│   │   │   └── broadcast_channel_test.rb
│   │   ├── controllers/
│   │   │   └── .keep
│   │   ├── fixtures/
│   │   │   ├── files/
│   │   │   │   └── .keep
│   │   │   └── rooms.yml
│   │   ├── models/
│   │   │   ├── .keep
│   │   │   └── room_test.rb
│   │   └── test_helper.rb
│   └── vendor/
│       ├── .keep
│       └── javascript/
│           └── .keep
├── bin/
│   ├── rails
│   ├── release
│   └── test
├── lib/
│   ├── action_cable/
│   │   └── subscription_adapter/
│   │       └── solid_cable.rb
│   ├── generators/
│   │   └── solid_cable/
│   │       ├── install/
│   │       │   ├── USAGE
│   │       │   ├── install_generator.rb
│   │       │   └── templates/
│   │       │       ├── config/
│   │       │       │   └── cable.yml
│   │       │       └── db/
│   │       │           └── cable_schema.rb
│   │       └── update/
│   │           ├── USAGE
│   │           ├── templates/
│   │           │   └── db/
│   │           │       └── migrate/
│   │           │           └── create_compact_channel.rb
│   │           └── update_generator.rb
│   ├── solid_cable/
│   │   ├── engine.rb
│   │   └── version.rb
│   ├── solid_cable.rb
│   └── tasks/
│       └── solid_cable_tasks.rake
├── solid_cable.gemspec
└── test/
    ├── config_stubs.rb
    ├── dummy/
    │   ├── Rakefile
    │   ├── app/
    │   │   ├── controllers/
    │   │   │   ├── application_controller.rb
    │   │   │   └── concerns/
    │   │   │       └── .keep
    │   │   ├── helpers/
    │   │   │   └── application_helper.rb
    │   │   ├── jobs/
    │   │   │   └── application_job.rb
    │   │   ├── models/
    │   │   │   ├── application_record.rb
    │   │   │   └── concerns/
    │   │   │       └── .keep
    │   │   └── views/
    │   │       ├── layouts/
    │   │       │   ├── application.html.erb
    │   │       │   ├── mailer.html.erb
    │   │       │   └── mailer.text.erb
    │   │       └── pwa/
    │   │           ├── manifest.json.erb
    │   │           └── service-worker.js
    │   ├── bin/
    │   │   ├── ci
    │   │   ├── dev
    │   │   ├── rails
    │   │   ├── rake
    │   │   └── setup
    │   ├── config/
    │   │   ├── application.rb
    │   │   ├── boot.rb
    │   │   ├── cable.yml
    │   │   ├── ci.rb
    │   │   ├── database.yml
    │   │   ├── environment.rb
    │   │   ├── environments/
    │   │   │   ├── development.rb
    │   │   │   ├── production.rb
    │   │   │   └── test.rb
    │   │   ├── initializers/
    │   │   │   ├── assets.rb
    │   │   │   ├── content_security_policy.rb
    │   │   │   ├── filter_parameter_logging.rb
    │   │   │   └── inflections.rb
    │   │   ├── locales/
    │   │   │   └── en.yml
    │   │   ├── puma.rb
    │   │   ├── routes.rb
    │   │   └── storage.yml
    │   ├── config.ru
    │   ├── db/
    │   │   └── schema.rb
    │   ├── log/
    │   │   └── .keep
    │   └── public/
    │       ├── 400.html
    │       ├── 404.html
    │       ├── 406-unsupported-browser.html
    │       ├── 422.html
    │       └── 500.html
    ├── jobs/
    │   └── trim_job_test.rb
    ├── lib/
    │   ├── action_cable/
    │   │   └── subscription_adapter/
    │   │       └── solid_cable_test.rb
    │   └── generators/
    │       └── solid_cable/
    │           ├── install/
    │           │   └── install_generator_test.rb
    │           └── update/
    │               └── update_generator_test.rb
    ├── solid_cable_test.rb
    └── test_helper.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/lint.yml
================================================
name: lint

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    name: Linting
    steps:
    - uses: actions/checkout@v6
    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        ruby-version: '4.0'
    - name: Install dependencies
      run: bundle install
    - name: Run tests
      run: bundle exec rubocop --config=./.rubocop.yml --parallel


================================================
FILE: .github/workflows/test.yml
================================================
name: tests

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  build:
    name: Tests
    runs-on: ubuntu-latest
    timeout-minutes: 10
    strategy:
      fail-fast: false
      matrix:
        database: [mysql, postgres, sqlite]
        ruby-version:
          - 3.2
          - 3.3
          - 3.4
          - 4.0
    services:
      mysql:
        image: mysql:8.0.31
        env:
          MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
        ports:
          - 33060:3306
        options: --health-cmd "mysql -h localhost -e \"select now()\"" --health-interval 1s --health-timeout 5s --health-retries 30
      postgres:
        image: postgres:15.1
        env:
          POSTGRES_HOST_AUTH_METHOD: "trust"
        ports:
          - 55432:5432
    env:
      TARGET_DB: ${{ matrix.database }}
    steps:
    - uses: actions/checkout@v6
    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        ruby-version: ${{ matrix.ruby-version }}
    - name: Install dependencies
      run: bundle install
    - name: Setup db
      run: |
        bin/rails db:setup
    - name: Run tests
      run: bin/test


================================================
FILE: .gitignore
================================================
/.bundle/
/doc/
/log/*.log
/pkg/
/tmp/
/test/dummy/db/*.sqlite3
/test/dummy/db/*.sqlite3-*
/test/dummy/log/*.log
/test/dummy/storage/
/test/dummy/tmp/
Gemfile.lock
/bench/db/*.sqlite3
/bench/db/*.sqlite3-*
/bench/log/*.log
/bench/storage/
/bench/tmp/
/bench/app/assets/builds/*
!/bench/app/assets/builds/.keep
/bench/config/master.key
/bench/storage/*
!/bench/storage/.keep
/bench/tmp/storage/*
!/bench/tmp/storage/
!/bench/tmp/storage/.keep
/bench/public/assets
/bench/.env*
!/bench/.env*.erb
/bench/k6
test/dummy/solid_cable_test


================================================
FILE: .rubocop.yml
================================================
inherit_gem:
  rubocop-rails-omakase: rubocop.yml

AllCops:
  NewCops: enable
  SuggestExtensions: false
  Exclude:
    - vendor/**/*
    - Gemfile.lock
    - db/*_schema.rb
    - db/schema.rb
    - tmp/**/*
    - bin/**/*
    - test/dummy/**/*
    - bench/**/*
    - test/lib/action_cable/subscription_adapter/solid_cable_test.rb
    - lib/generators/**/*_schema.rb
  TargetRubyVersion: 3.3


================================================
FILE: Gemfile
================================================
# frozen_string_literal: true

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

# Specify your gem's dependencies in solid_cable.gemspec.
gemspec

gem "puma"

gem "pg"
gem "sqlite3"
gem "trilogy"

gem "rubocop-rails-omakase"


================================================
FILE: MIT-LICENSE
================================================
Copyright Nick Pezza

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# Solid Cable

Solid Cable is a database-backed Action Cable adapter that keeps messages in a table and continuously polls for updates. This makes it possible to drop the common dependency on Redis, if it isn't needed for any other purpose. Despite polling, the performance of Solid Cable is comparable to Redis in most situations. And in all circumstances, it makes it easier to deploy Rails when Redis is no longer a required dependency for Action Cable functionality.

> [!NOTE]
> Solid Cable is tested to work with MySQL, SQLite, and PostgreSQL.
>
> Action Cable already has a [dedicated PostgreSQL adapter](https://guides.rubyonrails.org/action_cable_overview.html#postgresql-adapter),
> which utilizes the builtin `NOTIFY` command for better performance. However, that
> adapter has an 8kb limit on its payload. Solid Cable is a great alternative if you find yourself
> broadcasting large payloads, or prefer not to use the `NOTIFY` command.

## Installation

Solid Cable is configured by default in new Rails 8 applications. But if you're running an earlier version, you can add it manually following these steps:

1. `bundle add solid_cable`
2. `bin/rails solid_cable:install`

This will configure Solid Cable as the production cable adapter by overwritting `config/cable.yml` and create `db/cable_schema.rb`.

You will then have to add the configuration for the cable database in `config/database.yml`. If you're using SQLite, it'll look like this:

```yaml
production:
  primary:
    <<: *default
    database: storage/production.sqlite3
  cable:
    <<: *default
    database: storage/production_cable.sqlite3
    migrations_paths: db/cable_migrate
```

...or if you're using MySQL/PostgreSQL/Trilogy:

```yaml
production:
  primary: &primary_production
    <<: *default
    database: app_production
    username: app
    password: <%= ENV["APP_DATABASE_PASSWORD"] %>
  cable:
    <<: *primary_production
    database: app_production_cable
    migrations_paths: db/cable_migrate
```

> [!NOTE]
> Calling `bin/rails solid_cable:install` will automatically setup `config/cable.yml`, so no additional configuration is needed there (although you must make sure that you use the `cable` name in `database.yml` for this to match!). But if you want to use Solid Cable in a different environment (like staging or even development), you'll have to manually add that `connects_to` block to the respective environment in the `config/cable.yml` file. And, as always, make sure that the name you're using for the database in `config/cable.yml` matches the name you define in `config/database.yml`.

Then run `db:prepare` in production to ensure the database is created and the schema is loaded.

### Single database configuration

Running Solid Cable in a separate database is recommended, but it's also possible to use a single database for both the app and Action Cable.

1. Copy the contents of `db/cable_schema.rb` into a normal migration and delete `db/cable_schema.rb`
2. Remove `connects_to` from `config/cable.yml`
3. `bin/rails db:migrate`

You won't have multiple databases, so `database.yml` doesn't need to have primary and cable database.

## Configuration

All configuration is managed via the `config/cable.yml` file. By default, it'll be configured like this:

```yaml
production:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable
  polling_interval: 0.1.seconds
  message_retention: 1.day
```

The options are:

- `connects_to` - set the Active Record database configuration for the Solid Cable models. All options available in Active Record can be used here.
- `polling_interval` - sets the frequency of the polling interval. (Defaults to
  0.1.seconds)
- `message_retention` - sets the retention time for messages kept in the database. Used as the cut-off when trimming is performed. (Defaults to 1.day)
- `autotrim` - sets wether you want Solid Cable to handle autotrimming messages. (Defaults to true)
- `silence_polling` - whether to silence Active Record logs emitted when polling (Defaults to true)
- `use_skip_locked` - whether to use `FOR UPDATE SKIP LOCKED` when performing trimming. This will be automatically detected in the future, and for now, you'd only need to set this to `false` if your database doesn't support it. For MySQL, that'd be versions < 8, and for PostgreSQL, versions < 9.5. If you use SQLite, this has no effect, as writes are sequential. (Defaults to true)
- `trim_batch_size` - the batch size to use when deleting old records (default: `100`)
- `reconnect_attempts` - Supports a number of connection attempts or an array of
  durations to wait between attempts. (Defaults to 1 retry attempt)


## Trimming

Messages are autotrimmed based upon the `message_retention` setting to determine how long messages are to be kept around. If no `message_retention` is given or parsing fails, it defaults to `1.day`. Messages are trimmed when a messsage is broadcast.

Autotrimming can negatively impact performance slightly depending on your workload because it is potentially doing a delete on broadcast. If
you would prefer, you can disable autotrimming by setting `autotrim: false` and you can manually enqueue the job later, `SolidCable::TrimJob.perform_later`, or run it on a recurring interval out of band.


## Upgrading

If you have already installed Solid Cable < 3 and are upgrading to version 3,
run `solid_cable:update` to install a new migration.


## Benchmarks

Inside the `bench` directory there is a minimal Rails app that is used to benchmark.
You are welcome to update the config/deploy.yml file to point to your own server
if you want to deploy the app to your own server and run benchmarks.

To benchmark we use [k6](https://k6.io). Most of the setup was gotten from this
[article](https://evilmartians.com/chronicles/real-time-stress-anycable-k6-websockets-and-yabeda).
1. Install k6
1. Install xk6-cable by running `xk6 build --with
   github.com/anycable/xk6-cable`. This will output a custom k6 binary.
1. Run the load test with `./k6 run loadtest.js`
    - This script takes a variety of ENV variables:
        - WS_URL: The url to send websocket connections
        - MAX: The number of virtual users to hit the server with
        - TIME: The duration of the load test
        - MESSAGES_NUM: The number of messages each VU will send to the server


#### Results

Our loadtest is run on a Hetzner CCX13, with a MESSAGES_NUM of 5, and a TIME of 90.

##### SQLite

With a polling interval of 0.1 seconds and autotrimming enabled.

100 VUs
```
rtt..................: avg=135.82ms min=50ms     med=138ms    max=357ms    p(90)=174ms    p(95)=195ms
ws_connecting........: avg=205.81ms min=149.35ms med=199.01ms max=509.48ms p(90)=254.04ms p(95)=261.77ms
```
250 VUs
```
rtt..................: avg=146.24ms min=50ms     med=144ms    max=435ms p(90)=209ms   p(95)=234.04ms
ws_connecting........: avg=222.15ms min=146.47ms med=208.57ms max=1.3s  p(90)=263.6ms p(95)=284.18ms
```
500 VUs
```
rtt..................: avg=271.79ms min=48ms     med=205ms    max=1.15s p(90)=558ms    p(95)=660ms
ws_connecting........: avg=248.81ms min=145.89ms med=221.89ms max=1.38s p(90)=290.41ms p(95)=322.2ms
```
750 VUs
```
rtt..................: avg=548.27ms min=51ms     med=438ms    max=5.19s  p(90)=1.18s  p(95)=1.29s
ws_connecting........: avg=266.37ms min=144.06ms med=224.93ms max=2.33s  p(90)=298ms  p(95)=342.87ms
```

With trimming disabled

250 VUs
```
rtt..................: avg=139.47ms min=48ms     med=142ms    max=807ms p(90)=189ms    p(95)=214ms
ws_connecting........: avg=212.58ms min=146.19ms med=196.25ms max=1.25s p(90)=255.74ms p(95)=272.44ms
```

With a polling interval of 0.01 seconds it becomes comparable to Redis

250 VUs
```
rtt..................: avg=84.22ms  min=43ms     med=69ms     max=416ms p(90)=137ms    p(95)=150ms
ws_connecting........: avg=219.37ms min=144.71ms med=200.77ms max=2.17s p(90)=265.23ms p(95)=290.83ms
```

##### Redis

This instance was hosted on the same machine.

100 VUs
```
rtt..................: avg=68.95ms  min=41ms     med=56ms     max=6.23s  p(90)=114ms   p(95)=129ms
ws_connecting........: avg=211.09ms min=153.23ms med=195.69ms max=1.44s  p(90)=258.1ms p(95)=272.23ms
```
250 VUs
```
rtt..................: avg=69.32ms  min=40ms     med=56ms     max=645ms p(90)=119ms    p(95)=135ms
ws_connecting........: avg=212.95ms min=142.92ms med=196.31ms max=1.25s p(90)=260.25ms p(95)=273.49ms
```
500 VUs
```
rtt..................: avg=87.5ms   min=40ms     med=67ms     max=839ms p(90)=149ms    p(95)=176ms
ws_connecting........: avg=242.62ms min=142.03ms med=213.76ms max=2.34s p(90)=291.25ms p(95)=324.04ms
```
750 VUs
```
rtt..................: avg=162.54ms min=39ms  med=123ms    max=2.26s p(90)=343.1ms  p(95)=438ms
ws_connecting........: avg=353.08ms min=143ms med=264.15ms max=2.73s p(90)=541.36ms p(95)=1.15s
```


##### MySQL

With a polling interval of 0.1 seconds and autotrimming enabled. This instance
was also hosted on the same machine.

100 VUs
```
rtt..................: avg=136.02ms min=51ms     med=137ms    max=877ms p(90)=168.1ms  p(95)=198ms
ws_connecting........: avg=207.76ms min=151.93ms med=196.74ms max=1.21s p(90)=249.91ms p(95)=260.37ms
```
250 VUs
```
rtt..................: avg=159.33ms min=51ms    med=149ms    max=559ms p(90)=236ms    p(95)=263ms
ws_connecting........: avg=232.38ms min=151.6ms med=218.09ms max=1.38s p(90)=287.99ms p(95)=324.6ms
```
500 VUs
```
rtt..................: avg=441.07ms min=51ms     med=312ms    max=2.29s  p(90)=931ms    p(95)=1.07s
ws_connecting........: avg=256.73ms min=152.23ms med=231.02ms max=2.31s  p(90)=305.69ms p(95)=340.83ms
```
750 VUs
```
rtt..................: avg=822.08ms min=51ms     med=732ms    max=5.05s  p(90)=1.76s    p(95)=1.97s
ws_connecting........: avg=278.08ms min=146.66ms med=236.35ms max=2.37s  p(90)=318.17ms p(95)=374.98ms
```


##### PostgreSQL with Solid Cable

With a polling interval of 0.1 seconds and autotrimming enabled. This instance
was also hosted on the same machine.

100 VUs
```
rtt..................: avg=137.45ms min=48ms     med=139ms    max=439ms    p(90)=179.1ms  p(95)=204ms
ws_connecting........: avg=207.13ms min=150.29ms med=197.76ms max=443.67ms p(90)=254.44ms p(95)=263.29ms
```
250 VUs
```
rtt..................: avg=151.63ms min=49ms     med=146ms    max=538ms p(90)=222ms    p(95)=248.04ms
ws_connecting........: avg=245.89ms min=147.18ms med=205.57ms max=30s   p(90)=265.08ms p(95)=281.15ms
```
500 VUs
```
rtt..................: avg=362.79ms min=50ms     med=249ms    max=1.21s p(90)=757ms    p(95)=844ms
ws_connecting........: avg=257.02ms min=146.13ms med=227.65ms max=2.39s p(90)=303.22ms p(95)=344.39ms
```


##### PostgreSQL with dedicated adapter

100 VUs
```
rtt..................: avg=69.76ms  min=41ms     med=57ms     max=622ms p(90)=116ms    p(95)=133ms
ws_connecting........: avg=210.97ms min=149.68ms med=196.06ms max=1.27s p(90)=259.67ms p(95)=273.17ms
```
250 VUs
```
rtt..................: avg=73.43ms  min=40ms     med=58ms     max=698ms p(90)=126ms    p(95)=141ms
ws_connecting........: avg=210.83ms min=143.01ms med=195.22ms max=1.27s p(90)=259.27ms p(95)=272.6ms
```

## License

The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).


================================================
FILE: Rakefile
================================================
require "bundler/setup"

APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
load "rails/tasks/engine.rake"

require "bundler/gem_tasks"


================================================
FILE: app/jobs/solid_cable/trim_job.rb
================================================
# frozen_string_literal: true

module SolidCable
  class TrimJob < ActiveJob::Base
    def perform
      return unless trim?

      ::SolidCable::Message.transaction do
        ids = ::SolidCable::Message.trimmable.non_blocking_lock.
              limit(trim_batch_size).pluck(:id)
        ::SolidCable::Message.where(id: ids).delete_all
      end
    end

    private
      def trim_batch_size
        ::SolidCable.trim_batch_size
      end

      def trim?
        expires_per_write = (1 / trim_batch_size.to_f) * ::SolidCable.trim_chance

        !::SolidCable.autotrim? ||
          rand < (expires_per_write - expires_per_write.floor)
      end
  end
end


================================================
FILE: app/models/solid_cable/message.rb
================================================
# frozen_string_literal: true

module SolidCable
  class Message < SolidCable::Record
    scope :trimmable, lambda {
      where(created_at: ...::SolidCable.message_retention.ago)
    }
    scope :broadcastable, lambda { |channels, last_id|
      where(channel_hash: channel_hashes_for(channels)).
        where(id: (last_id.to_i + 1)..).order(:id)
    }

    class << self
      def broadcast(channel, payload)
        insert({ created_at: Time.current, channel:, payload:,
          channel_hash: channel_hash_for(channel) })
      end

      def channel_hashes_for(channels)
        channels.map { |channel| channel_hash_for(channel) }
      end

      # Need to unpack this as a signed integer since Postgresql and SQLite
      # don't support unsigned integers
      def channel_hash_for(channel)
        Digest::SHA256.digest(channel.to_s).unpack1("q>")
      end
    end
  end
end


================================================
FILE: app/models/solid_cable/record.rb
================================================
# frozen_string_literal: true

module SolidCable
  class Record < ActiveRecord::Base
    self.abstract_class = true

    connects_to(**SolidCable.connects_to) if SolidCable.connects_to.present?

    def self.non_blocking_lock
      if SolidCable.use_skip_locked
        lock(Arel.sql("FOR UPDATE SKIP LOCKED"))
      else
        lock
      end
    end
  end
end


================================================
FILE: bench/.dockerignore
================================================
# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.

# Ignore git directory.
/.git/

# Ignore bundler config.
/.bundle

# Ignore all default key files.
/config/master.key
/config/credentials/*.key

# Ignore all environment files.
/.env*
!/.env.example

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
tmp/*
log/*
tmp/cache/assets/*

# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/
!/tmp/pids/.keep

# Ignore storage (uploaded files in development and any SQLite databases).
/storage/*
!/storage/.keep
/tmp/storage/*
/tmp/cache/*
tmp/storage/*
tmp/cache/*
/coverage/*
coverage/*
!/tmp/storage/.keep

# Ignore assets.
/node_modules/
/app/assets/builds/*
!/app/assets/builds/.keep
/public/assets
/vendor/bundle
test/*
/test/*
tags
/tags
k6


================================================
FILE: bench/.ruby-version
================================================
3.3.5


================================================
FILE: bench/Dockerfile
================================================
# syntax = docker/dockerfile:1

# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t my-app .
# docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY=<value from config/master.key> my-app

# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.3.5
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base

# Rails app lives here
WORKDIR /rails

# Install base packages
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libjemalloc2 postgresql-client && \
    apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Set production environment
ENV RAILS_ENV="production" \
    BUNDLE_WITHOUT="development:test:linters:deploy" \
    BUNDLE_DEPLOYMENT="1" \
    BUNDLE_PATH="/usr/local/bundle" \
    BUNDLE_WITHOUT="development"

# Throw-away build stage to reduce size of final image
FROM base AS build

# Install packages needed to build gems
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential git pkg-config libpq-dev && \
    apt-get clean && rm -rf /var/cache/apt/archives /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
    rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
    bundle exec bootsnap precompile --gemfile

# Copy application code
COPY . .

RUN bundle exec bootsnap precompile app/ lib/ && \
    SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile && \
    rm -rf vendor/ruby/3.3.0/cache

# Final stage for app image
FROM base

# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails

# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
    useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
    mkdir /data && \
    chown -R rails:rails db log storage tmp /data
USER 1000:1000

# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]


================================================
FILE: bench/Gemfile
================================================
source "https://rubygems.org"

gem "rails", github: "rails/rails", branch: "main"
gem "activeerror"
gem "propshaft"
gem "solid_cable", github: "rails/solid_cable", branch: "main"
gem "sqlite3"
gem "trilogy"
gem "pg"
gem "puma"
gem "importmap-rails"
gem "redis"
gem "bootsnap", require: false
gem "kamal", require: false
gem "tailwindcss-rails"


================================================
FILE: bench/Rakefile
================================================
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

require_relative "config/application"

Rails.application.load_tasks


================================================
FILE: bench/app/assets/builds/.keep
================================================


================================================
FILE: bench/app/assets/images/.keep
================================================


================================================
FILE: bench/app/assets/stylesheets/application.tailwind.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;

/*

@layer components {
  .btn-primary {
    @apply py-2 px-4 bg-blue-200;
  }
}

*/


================================================
FILE: bench/app/channels/application_cable/channel.rb
================================================
# :markup: markdown

module ApplicationCable
  class Channel < ActionCable::Channel::Base
  end
end


================================================
FILE: bench/app/channels/application_cable/connection.rb
================================================
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    rescue_from Exception do |error|
      Rails.error.report(e, handled: false,
                      source: "application.action_cable")
    end

    identified_by :id

    def connect
      self.id = SecureRandom.uuid
    end
  end
end


================================================
FILE: bench/app/channels/broadcast_channel.rb
================================================
class BroadcastChannel < ApplicationCable::Channel
  def subscribed
    Rails.logger.info "a client subscribed: #{id}"
    stream_from "broadcast:#{id}"
  end

  def unsubscribed
    Rails.logger.info "unsubscribed: #{id}"
    stop_all_streams
  end

  def ping(data)
    broadcast_to id, { message: "pong #{data.with_indifferent_access[:message]}" }
  end
end


================================================
FILE: bench/app/controllers/application_controller.rb
================================================
class ApplicationController < ActionController::Base
end


================================================
FILE: bench/app/controllers/concerns/.keep
================================================


================================================
FILE: bench/app/controllers/rooms_controller.rb
================================================
class RoomsController < ApplicationController
  before_action :set_room, only: %i[ show edit update destroy ]

  # GET /rooms
  def index
    @rooms = Room.all
  end

  # GET /rooms/1
  def show
  end

  # GET /rooms/new
  def new
    @room = Room.new
  end

  # GET /rooms/1/edit
  def edit
  end

  # POST /rooms
  def create
    @room = Room.new(room_params)

    if @room.save
      redirect_to @room, notice: "Room was successfully created."
    else
      render :new, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /rooms/1
  def update
    if @room.update(room_params)
      redirect_to @room, notice: "Room was successfully updated.", status: :see_other
    else
      render :edit, status: :unprocessable_entity
    end
  end

  # DELETE /rooms/1
  def destroy
    @room.destroy!
    redirect_to rooms_url, notice: "Room was successfully destroyed.", status: :see_other
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_room
      @room = Room.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def room_params
      params.require(:room).permit(:name)
    end
end


================================================
FILE: bench/app/controllers/ws_debugger_controller.rb
================================================
class WsDebuggerController < ApplicationController
end


================================================
FILE: bench/app/javascript/application.js
================================================
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "channels"


================================================
FILE: bench/app/javascript/channels/broadcast_channel.js
================================================
import consumer from "channels/consumer"

consumer.subscriptions.create("BroadcastChannel", {
  connected() {
    // Called when the subscription is ready for use on the server
  },

  disconnected() {
    // Called when the subscription has been terminated by the server
  },

  received(data) {
    // Called when there's incoming data on the websocket for this channel
  }
});


================================================
FILE: bench/app/javascript/channels/consumer.js
================================================
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.

import { createConsumer } from "@rails/actioncable"

export default createConsumer()


================================================
FILE: bench/app/javascript/channels/index.js
================================================
// Import all the channels to be used by Action Cable
import "channels/broadcast_channel"


================================================
FILE: bench/app/javascript/controllers/application.js
================================================
import { Application } from "@hotwired/stimulus"

const application = Application.start()

// Configure Stimulus development experience
application.debug = false
window.Stimulus   = application

export { application }


================================================
FILE: bench/app/javascript/controllers/index.js
================================================
// Import and register all your controllers from the importmap under controllers/*

import { application } from "controllers/application"

// Eager load all controllers defined in the import map under controllers/**/*_controller
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
eagerLoadControllersFrom("controllers", application)

// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
// lazyLoadControllersFrom("controllers", application)


================================================
FILE: bench/app/jobs/application_job.rb
================================================
class ApplicationJob < ActiveJob::Base
  # Automatically retry jobs that encountered a deadlock
  # retry_on ActiveRecord::Deadlocked

  # Most jobs are safe to ignore if the underlying records are no longer available
  # discard_on ActiveJob::DeserializationError
end


================================================
FILE: bench/app/mailers/application_mailer.rb
================================================
class ApplicationMailer < ActionMailer::Base
  default from: "from@example.com"
  layout "mailer"
end


================================================
FILE: bench/app/models/application_record.rb
================================================
class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class
end


================================================
FILE: bench/app/models/concerns/.keep
================================================


================================================
FILE: bench/app/models/room.rb
================================================
class Room < ApplicationRecord
end


================================================
FILE: bench/app/views/layouts/application.html.erb
================================================
<!DOCTYPE html>
<html>
  <head>
    <title><%= content_for(:title) || "Cablebench" %></title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= yield :head %>

    <link rel="icon" href="/icon.png" type="image/png">
    <link rel="icon" href="/icon.svg" type="image/svg+xml">
    <link rel="apple-touch-icon" href="/icon.png">
    <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>

    <%= stylesheet_link_tag :all, "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
  </head>

  <body>
    <main class="container mx-auto mt-28 px-5 flex">
      <%= yield %>
    </main>
  </body>
</html>


================================================
FILE: bench/app/views/layouts/mailer.html.erb
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <style>
      /* Email styles need to be inline */
    </style>
  </head>

  <body>
    <%= yield %>
  </body>
</html>


================================================
FILE: bench/app/views/layouts/mailer.text.erb
================================================
<%= yield %>


================================================
FILE: bench/app/views/rooms/_form.html.erb
================================================
<%= form_with(model: room, class: "contents") do |form| %>
  <% if room.errors.any? %>
    <div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
      <h2><%= pluralize(room.errors.count, "error") %> prohibited this room from being saved:</h2>

      <ul>
        <% room.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="my-5">
    <%= form.label :name %>
    <%= form.text_field :name, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" %>
  </div>

  <div class="inline">
    <%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
  </div>
<% end %>


================================================
FILE: bench/app/views/rooms/_room.html.erb
================================================
<div id="<%= dom_id room %>">
  <p class="my-5">
    <strong class="block font-medium mb-1">Name:</strong>
    <%= room.name %>
  </p>

</div>


================================================
FILE: bench/app/views/rooms/edit.html.erb
================================================
<div class="mx-auto md:w-2/3 w-full">
  <h1 class="font-bold text-4xl">Editing room</h1>

  <%= render "form", room: @room %>

  <%= link_to "Show this room", @room, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
  <%= link_to "Back to rooms", rooms_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
</div>


================================================
FILE: bench/app/views/rooms/index.html.erb
================================================
<div class="w-full">
  <% if notice.present? %>
    <p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
  <% end %>

  <% content_for :title, "Rooms" %>

  <div class="flex justify-between items-center">
    <h1 class="font-bold text-4xl">Rooms</h1>
    <%= link_to "New room", new_room_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
  </div>

  <div id="rooms" class="min-w-full">
    <% @rooms.each do |room| %>
      <%= render room %>
      <p>
        <%= link_to "Show this room", room, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
      </p>
    <% end %>
  </div>
</div>


================================================
FILE: bench/app/views/rooms/new.html.erb
================================================
<div class="mx-auto md:w-2/3 w-full">
  <h1 class="font-bold text-4xl">New room</h1>

  <%= render "form", room: @room %>

  <%= link_to "Back to rooms", rooms_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
</div>


================================================
FILE: bench/app/views/rooms/show.html.erb
================================================
<div class="mx-auto md:w-2/3 w-full flex">
  <div class="mx-auto">
    <% if notice.present? %>
      <p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
    <% end %>

    <%= render @room %>

    <%= link_to "Edit this room", edit_room_path(@room), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
    <%= link_to "Back to rooms", rooms_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
    <div class="inline-block ml-2">
      <%= button_to "Destroy this room", @room, method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
    </div>
  </div>
</div>


================================================
FILE: bench/app/views/ws_debugger/show.html.erb
================================================
<p>hello world</p>


================================================
FILE: bench/bin/bundle
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true

#
# This file was generated by Bundler.
#
# The application 'bundle' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require "rubygems"

m = Module.new do
  module_function

  def invoked_as_script?
    File.expand_path($0) == File.expand_path(__FILE__)
  end

  def env_var_version
    ENV["BUNDLER_VERSION"]
  end

  def cli_arg_version
    return unless invoked_as_script? # don't want to hijack other binstubs
    return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
    bundler_version = nil
    update_index = nil
    ARGV.each_with_index do |a, i|
      if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN)
        bundler_version = a
      end
      next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
      bundler_version = $1
      update_index = i
    end
    bundler_version
  end

  def gemfile
    gemfile = ENV["BUNDLE_GEMFILE"]
    return gemfile if gemfile && !gemfile.empty?

    File.expand_path("../Gemfile", __dir__)
  end

  def lockfile
    lockfile =
      case File.basename(gemfile)
      when "gems.rb" then gemfile.sub(/\.rb$/, ".locked")
      else "#{gemfile}.lock"
      end
    File.expand_path(lockfile)
  end

  def lockfile_version
    return unless File.file?(lockfile)
    lockfile_contents = File.read(lockfile)
    return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
    Regexp.last_match(1)
  end

  def bundler_requirement
    @bundler_requirement ||=
      env_var_version ||
      cli_arg_version ||
      bundler_requirement_for(lockfile_version)
  end

  def bundler_requirement_for(version)
    return "#{Gem::Requirement.default}.a" unless version

    bundler_gem_version = Gem::Version.new(version)

    bundler_gem_version.approximate_recommendation
  end

  def load_bundler!
    ENV["BUNDLE_GEMFILE"] ||= gemfile

    activate_bundler
  end

  def activate_bundler
    gem_error = activation_error_handling do
      gem "bundler", bundler_requirement
    end
    return if gem_error.nil?
    require_error = activation_error_handling do
      require "bundler/version"
    end
    return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
    warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
    exit 42
  end

  def activation_error_handling
    yield
    nil
  rescue StandardError, LoadError => e
    e
  end
end

m.load_bundler!

if m.invoked_as_script?
  load Gem.bin_path("bundler", "bundle")
end


================================================
FILE: bench/bin/docker-entrypoint
================================================
#!/bin/bash -e

# Enable jemalloc for reduced memory usage and latency.
if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then
  export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)"
fi

# If running the rails server then create or migrate existing database
if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
  ./bin/rails db:prepare
fi

exec "${@}"


================================================
FILE: bench/bin/importmap
================================================
#!/usr/bin/env ruby

require_relative "../config/application"
require "importmap/commands"


================================================
FILE: bench/bin/kamal
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true

#
# This file was generated by Bundler.
#
# The application 'kamal' is installed as part of a gem, and
# this file is here to facilitate running it.
#

ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)

bundle_binstub = File.expand_path("bundle", __dir__)

if File.file?(bundle_binstub)
  if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
    load(bundle_binstub)
  else
    abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
  end
end

require "rubygems"
require "bundler/setup"

load Gem.bin_path("kamal", "kamal")


================================================
FILE: bench/bin/rails
================================================
#!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"


================================================
FILE: bench/bin/rake
================================================
#!/usr/bin/env ruby
require_relative "../config/boot"
require "rake"
Rake.application.run


================================================
FILE: bench/bin/rubocop
================================================
#!/usr/bin/env ruby
require "rubygems"
require "bundler/setup"

# explicit rubocop config increases performance slightly while avoiding config confusion.
ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__))

load Gem.bin_path("rubocop", "rubocop")


================================================
FILE: bench/bin/setup
================================================
#!/usr/bin/env ruby
require "fileutils"

APP_ROOT = File.expand_path("..", __dir__)
APP_NAME = "cablebench"

def system!(*args)
  system(*args, exception: true)
end

FileUtils.chdir APP_ROOT do
  # This script is a way to set up or update your development environment automatically.
  # This script is idempotent, so that you can run it at any time and get an expectable outcome.
  # Add necessary setup steps to this file.

  puts "== Installing dependencies =="
  system! "gem install bundler --conservative"
  system("bundle check") || system!("bundle install")

  # puts "\n== Copying sample files =="
  # unless File.exist?("config/database.yml")
  #   FileUtils.cp "config/database.yml.sample", "config/database.yml"
  # end

  puts "\n== Preparing database =="
  system! "bin/rails db:prepare"

  puts "\n== Removing old logs and tempfiles =="
  system! "bin/rails log:clear tmp:clear"

  puts "\n== Restarting application server =="
  system! "bin/rails restart"

  # puts "\n== Configuring puma-dev =="
  # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}"
  # system "curl -Is https://#{APP_NAME}.test/up | head -n 1"
end


================================================
FILE: bench/config/application.rb
================================================
require_relative "boot"

require "rails/all"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Cablebench
  class Application < Rails::Application
    config.action_cable.mount_path = '/cable'
    config.action_cable.disable_request_forgery_protection = true

    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 8.0

    # Please, add to the `ignore` list any other `lib` subdirectories that do
    # not contain `.rb` files, or that should not be reloaded or eager loaded.
    # Common ones are `templates`, `generators`, or `middleware`, for example.
    config.autoload_lib(ignore: %w[assets tasks])

    # Configuration for the application, engines, and railties goes here.
    #
    # These settings can be overridden in specific environments using the files
    # in config/environments, which are processed later.
    #
    # config.time_zone = "Central Time (US & Canada)"
    # config.eager_load_paths << Rails.root.join("extras")
  end
end


================================================
FILE: bench/config/boot.rb
================================================
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)

require "bundler/setup" # Set up gems listed in the Gemfile.
require "bootsnap/setup" # Speed up boot time by caching expensive operations.


================================================
FILE: bench/config/cable.yml
================================================
development:
  # adapter: redis
  # url: redis://localhost:6379/1
  adapter: solid_cable
  message_retention: 15.minutes
  polling_interval: 0.1.seconds

test:
  adapter: test

production:
  # adapter: redis
  # url: <%= ENV.fetch("REDIS_URL") { "redis://#{ENV["HOST"]}:6379/1" } %>
  # channel_prefix: cablebench_production

  # adapter: postgresql
  # database: cablebench
  # user: cablebench
  # password: <%= ENV["POSTGRES_PASSWORD"] %>
  # host: <%= ENV["HOST"] %>

  adapter: solid_cable
  message_retention: 1.day
  polling_interval: 0.1.seconds


================================================
FILE: bench/config/credentials.yml.enc
================================================
7lxKveoOh/YSo6BSUdrNHtg29iVtfI9AinJgpSiiQkSWpoVPHluT1PNVQpleQhs3LqM0WP4aMBX12xzUP843RI2LYQmtUmbjkJtfkMs/dCijfslkwfrwSI8ApQINOTwVG3fbYdFKe5N8C2Qhqbch+vMDktSL/eTXw7x1dSaUUjRF1zfEzi+xtn8D4nbDSv3m5tbUAzG2D3NU2bYIBaMtSNlnu0orY6RFPi8HRnGArsoMd41cBxUUJP6a23jatCGTl7BRrwRncBpoMKPbimiqE2YpE1XD+A25qcjA/1kxneSVvRl+0S0lUqVbUO9L84tsj9prt8YWKTeDWP9MWu9wGvyVoUb9eDMf15k3soBrKvG2++oZ6t/2S7pIg9gGEMBcRZwq9nHRnaS5SEhUFBmb4yr40/qWdpKGFKGAA/9al3sD3ChAlYXc5zQ81V4+o3hCxapvWbUr--2KQ9UYDapBokyl5i--Me6kHSK8rQVQyCeZM2w1lw==

================================================
FILE: bench/config/database.yml
================================================
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: storage/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: storage/test.sqlite3


# Store production database in the storage/ directory, which by default
# is mounted as a persistent Docker volume in config/deploy.yml.
production:
  # <<: *default
  # adapter: sqlite3
  # pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  # timeout: 5000
  # database: /data/production.sqlite3

  # adapter: trilogy
  # pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 50 } %>
  # timeout: 5000
  # database: solid_cable
  # user: root
  # password: <%= ENV["MYSQL_ROOT_PASSWORD"] %>
  # host: <%= ENV["HOST"] %>
  # ssl: true

  adapter: postgresql
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 50 } %>
  timeout: 5000
  database: cablebench
  user: cablebench
  password: <%= ENV["POSTGRES_PASSWORD"] %>
  host: <%= ENV["HOST"] %>


================================================
FILE: bench/config/deploy.yml
================================================
service: solidcable
image: npezza/solid_cable
asset_path: /rails/public/assets
servers:
  web:
    - <%= ENV["HOST"] %>
registry:
  username: npezza
  password:
    - KAMAL_REGISTRY_PASSWORD

env:
  secret:
    - RAILS_MASTER_KEY
    - MYSQL_ROOT_PASSWORD
    - HOST
    - POSTGRES_PASSWORD

volumes:
  - "solidcable:/data"
builder:
  context: "."
  local:
    arch: amd64

accessories:
  mysql:
    image: mysql:8.3
    host: "<%= ENV['HOST'] %>"
    port: 3306
    env:
      clear:
        MYSQL_ROOT_HOST: '%'
      secret:
        - MYSQL_ROOT_PASSWORD
    files:
      - config/init.sql:/docker-entrypoint-initdb.d/setup.sql
    directories:
      - data:/var/lib/mysql
  redis:
    image: redis:latest
    host: "<%= ENV['HOST'] %>"
    port: 6379
    cmd: "redis-server"
    volumes:
      - /var/lib/redis:/data
  postgres:
    image: postgres:16
    host: "<%= ENV['HOST'] %>"
    port: 5432
    env:
      clear:
        POSTGRES_USER: "cablebench"
        POSTGRES_DB: "cablebench"
      secret:
        - POSTGRES_PASSWORD
    files:
      - config/init.sql:/docker-entrypoint-initdb.d/setup.sql
    directories:
      - data:/var/lib/postgresql/data


================================================
FILE: bench/config/environment.rb
================================================
# Load the Rails application.
require_relative "application"

# Initialize the Rails application.
Rails.application.initialize!


================================================
FILE: bench/config/environments/development.rb
================================================
require "active_support/core_ext/integer/time"

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # In the development environment your application's code is reloaded any time
  # it changes. This slows down response time but is perfect for development
  # since you don't have to restart the web server when you make code changes.
  config.enable_reloading = true

  # Do not eager load code on boot.
  config.eager_load = false

  # Show full error reports.
  config.consider_all_requests_local = true

  # Enable server timing.
  config.server_timing = true

  # Enable/disable caching. By default caching is disabled.
  # Run rails dev:cache to toggle caching.
  if Rails.root.join("tmp/caching-dev.txt").exist?
    config.action_controller.perform_caching = true
    config.action_controller.enable_fragment_cache_logging = true

    config.cache_store = :memory_store
    config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{2.days.to_i}" }
  else
    config.action_controller.perform_caching = false

    config.cache_store = :null_store
  end

  # Store uploaded files on the local file system (see config/storage.yml for options).
  config.active_storage.service = :local

  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false

  config.action_mailer.perform_caching = false

  config.action_mailer.default_url_options = { host: "localhost", port: 3000 }

  # Print deprecation notices to the Rails logger.
  config.active_support.deprecation = :log

  # Raise an error on page load if there are pending migrations.
  config.active_record.migration_error = :page_load

  # Highlight code that triggered database queries in logs.
  config.active_record.verbose_query_logs = true

  # Highlight code that enqueued background job in logs.
  config.active_job.verbose_enqueue_logs = true


  # Raises error for missing translations.
  # config.i18n.raise_on_missing_translations = true

  # Annotate rendered view with file names.
  config.action_view.annotate_rendered_view_with_filenames = true

  # Uncomment if you wish to allow Action Cable access from any origin.
  # config.action_cable.disable_request_forgery_protection = true

  # Raise error when a before_action's only/except options reference missing actions.
  config.action_controller.raise_on_missing_callback_actions = true

  # Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
  # config.generators.apply_rubocop_autocorrect_after_generate!
end


================================================
FILE: bench/config/environments/production.rb
================================================
require "active_support/core_ext/integer/time"

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # Code is not reloaded between requests.
  config.enable_reloading = false

  # Eager load code on boot. This eager loads most of Rails and
  # your application in memory, allowing both threaded web servers
  # and those relying on copy on write to perform better.
  # Rake tasks automatically ignore this option for performance.
  config.eager_load = true

  # Full error reports are disabled and caching is turned on.
  config.consider_all_requests_local = false
  config.action_controller.perform_caching = true

  # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment
  # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files).
  # config.require_master_key = true

  # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead.
  # config.public_file_server.enabled = false

  # Enable serving of images, stylesheets, and JavaScripts from an asset server.
  # config.asset_host = "http://assets.example.com"

  # Specifies the header that your server uses for sending files.
  # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
  # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX

  # Store uploaded files on the local file system (see config/storage.yml for options).
  config.active_storage.service = :local

  # Mount Action Cable outside main process or domain.
  # config.action_cable.mount_path = nil
  # config.action_cable.url = "wss://example.com/cable"
  # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
  config.action_cable.worker_pool_size = 10

  # Assume all access to the app is happening through a SSL-terminating reverse proxy.
  # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.
  config.assume_ssl = true

  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true

  # Skip http-to-https redirect for the default health check endpoint.
  # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }

  # Log to STDOUT by default
  config.logger = ActiveSupport::Logger.new(STDOUT)
    .tap  { |logger| logger.formatter = ::Logger::Formatter.new }
    .then { |logger| ActiveSupport::TaggedLogging.new(logger) }

  # Prepend all log lines with the following tags.
  config.log_tags = [ :request_id ]

  # "info" includes generic and useful information about system operation, but avoids logging too much
  # information to avoid inadvertent exposure of personally identifiable information (PII). If you
  # want to log everything, set the level to "debug".
  config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "debug")

  # Use a different cache store in production.
  # config.cache_store = :mem_cache_store

  # Use a real queuing backend for Active Job (and separate queues per environment).
  # config.active_job.queue_adapter = :resque
  # config.active_job.queue_name_prefix = "cablebench_production"

  config.action_mailer.perform_caching = false

  # Ignore bad email addresses and do not raise email delivery errors.
  # Set this to true and configure the email server for immediate delivery to raise delivery errors.
  # config.action_mailer.raise_delivery_errors = false

  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
  # the I18n.default_locale when a translation cannot be found).
  config.i18n.fallbacks = true

  # Don't log any deprecations.
  config.active_support.report_deprecations = false

  # Do not dump schema after migrations.
  config.active_record.dump_schema_after_migration = false

  # Enable DNS rebinding protection and other `Host` header attacks.
  # config.hosts = [
  #   "example.com",     # Allow requests from example.com
  #   /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
  # ]
  # Skip DNS rebinding protection for the default health check endpoint.
  # config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
end


================================================
FILE: bench/config/environments/test.rb
================================================
require "active_support/core_ext/integer/time"

# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # While tests run files are not watched, reloading is not necessary.
  config.enable_reloading = false

  # Eager loading loads your entire application. When running a single test locally,
  # this is usually not necessary, and can slow down your test suite. However, it's
  # recommended that you enable it in continuous integration systems to ensure eager
  # loading is working properly before deploying your code.
  config.eager_load = ENV["CI"].present?

  # Configure public file server for tests with Cache-Control for performance.
  config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{1.hour.to_i}" }

  # Show full error reports and disable caching.
  config.consider_all_requests_local = true
  config.action_controller.perform_caching = false
  config.cache_store = :null_store

  # Render exception templates for rescuable exceptions and raise for other exceptions.
  config.action_dispatch.show_exceptions = :rescuable

  # Disable request forgery protection in test environment.
  config.action_controller.allow_forgery_protection = false

  # Store uploaded files on the local file system in a temporary directory.
  config.active_storage.service = :test

  config.action_mailer.perform_caching = false

  # Tell Action Mailer not to deliver emails to the real world.
  # The :test delivery method accumulates sent emails in the
  # ActionMailer::Base.deliveries array.
  config.action_mailer.delivery_method = :test

  # Unlike controllers, the mailer instance doesn't have any context about the
  # incoming request so you'll need to provide the :host parameter yourself.
  config.action_mailer.default_url_options = { host: "www.example.com" }

  # Print deprecation notices to the stderr.
  config.active_support.deprecation = :stderr

  # Raises error for missing translations.
  # config.i18n.raise_on_missing_translations = true

  # Annotate rendered view with file names.
  # config.action_view.annotate_rendered_view_with_filenames = true

  # Raise error when a before_action's only/except options reference missing actions.
  config.action_controller.raise_on_missing_callback_actions = true
end


================================================
FILE: bench/config/importmap.rb
================================================
# Pin npm packages by running ./bin/importmap

pin "application"
pin "@rails/actioncable", to: "actioncable.esm.js"
pin_all_from "app/javascript/channels", under: "channels"


================================================
FILE: bench/config/init.sql
================================================
CREATE DATABASE cablebench;


================================================
FILE: bench/config/initializers/assets.rb
================================================
# Be sure to restart your server when you modify this file.

# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = "1.0"

# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path


================================================
FILE: bench/config/initializers/content_security_policy.rb
================================================
# Be sure to restart your server when you modify this file.

# Define an application-wide content security policy.
# See the Securing Rails Applications Guide for more information:
# https://guides.rubyonrails.org/security.html#content-security-policy-header

# Rails.application.configure do
#   config.content_security_policy do |policy|
#     policy.default_src :self, :https
#     policy.font_src    :self, :https, :data
#     policy.img_src     :self, :https, :data
#     policy.object_src  :none
#     policy.script_src  :self, :https
#     policy.style_src   :self, :https
#     # Specify URI for violation reports
#     # policy.report_uri "/csp-violation-report-endpoint"
#   end
#
#   # Generate session nonces for permitted importmap, inline scripts, and inline styles.
#   config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
#   config.content_security_policy_nonce_directives = %w(script-src style-src)
#
#   # Report violations without enforcing the policy.
#   # config.content_security_policy_report_only = true
# end


================================================
FILE: bench/config/initializers/filter_parameter_logging.rb
================================================
# Be sure to restart your server when you modify this file.

# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.
# Use this to limit dissemination of sensitive information.
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
Rails.application.config.filter_parameters += [
  :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
]


================================================
FILE: bench/config/initializers/inflections.rb
================================================
# Be sure to restart your server when you modify this file.

# Add new inflection rules using the following format. Inflections
# are locale specific, and you may define rules for as many different
# locales as you wish. All of these examples are active by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
#   inflect.plural /^(ox)$/i, "\\1en"
#   inflect.singular /^(ox)en/i, "\\1"
#   inflect.irregular "person", "people"
#   inflect.uncountable %w( fish sheep )
# end

# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
#   inflect.acronym "RESTful"
# end


================================================
FILE: bench/config/initializers/permissions_policy.rb
================================================
# Be sure to restart your server when you modify this file.

# Define an application-wide HTTP permissions policy. For further
# information see: https://developers.google.com/web/updates/2018/06/feature-policy

# Rails.application.config.permissions_policy do |policy|
#   policy.camera      :none
#   policy.gyroscope   :none
#   policy.microphone  :none
#   policy.usb         :none
#   policy.fullscreen  :self
#   policy.payment     :self, "https://secure.example.com"
# end


================================================
FILE: bench/config/locales/en.yml
================================================
# Files in the config/locales directory are used for internationalization and
# are automatically loaded by Rails. If you want to use locales other than
# English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
#     I18n.t "hello"
#
# In views, this is aliased to just `t`:
#
#     <%= t("hello") %>
#
# To use a different locale, set it with `I18n.locale`:
#
#     I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# To learn more about the API, please read the Rails Internationalization guide
# at https://guides.rubyonrails.org/i18n.html.
#
# Be aware that YAML interprets the following case-insensitive strings as
# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings
# must be quoted to be interpreted as strings. For example:
#
#     en:
#       "yes": yup
#       enabled: "ON"

en:
  hello: "Hello world"


================================================
FILE: bench/config/puma.rb
================================================
# This configuration file will be evaluated by Puma. The top-level methods that
# are invoked here are part of Puma's configuration DSL. For more information
# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.

# Puma starts a configurable number of processes (workers) and each process
# serves each request in a thread from an internal thread pool.
#
# The ideal number of threads per worker depends both on how much time the
# application spends waiting for IO operations and on how much you wish to
# prioritize throughput over latency.
#
# As a rule of thumb, increasing the number of threads will increase how much
# traffic a given process can handle (throughput), but due to CRuby's
# Global VM Lock (GVL) it has diminishing returns and will degrade the
# response time (latency) of the application.
#
# The default is set to 3 threads as it's deemed a decent compromise between
# throughput and latency for the average Rails application.
#
# Any libraries that use a connection pool or another resource pool should
# be configured to provide at least as many connections as the number of
# threads. This includes Active Record's `pool` parameter in `database.yml`.
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count

# Specifies the `environment` that Puma will run in.
rails_env = ENV.fetch("RAILS_ENV", "development")
environment rails_env

case rails_env
when "production"
  # If you are running more than 1 thread per process, the workers count
  # should be equal to the number of processors (CPU cores) in production.
  #
  # Automatically detect the number of available processors in production.
  require "concurrent-ruby"
  workers_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.available_processor_count })
  workers workers_count if workers_count > 1
  worker_timeout 240

  preload_app!
when "development"
  # Specifies a very generous `worker_timeout` so that the worker
  # isn't killed by Puma when suspended by a debugger.
  worker_timeout 3600
end

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
port ENV.fetch("PORT", 3000)

# Allow puma to be restarted by `bin/rails restart` command.
plugin :tmp_restart

# Only use a pidfile when requested
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]


================================================
FILE: bench/config/routes.rb
================================================
Rails.application.routes.draw do
  mount ActiveError::Engine => "/errors"
  mount ActionCable.server => "/cable"

  get "/ws" => "ws_debugger#show"
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
  # Can be used by load balancers and uptime monitors to verify that the app is live.
  get "up" => "rails/health#show", as: :rails_health_check

  resources :rooms
  root "rooms#index"
end


================================================
FILE: bench/config/storage.yml
================================================
test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
# amazon:
#   service: S3
#   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
#   region: us-east-1
#   bucket: your_own_bucket-<%= Rails.env %>

# Remember not to checkin your GCS keyfile to a repository
# google:
#   service: GCS
#   project: your_project
#   credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
#   bucket: your_own_bucket-<%= Rails.env %>

# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
# microsoft:
#   service: AzureStorage
#   storage_account_name: your_account_name
#   storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
#   container: your_container_name-<%= Rails.env %>

# mirror:
#   service: Mirror
#   primary: local
#   mirrors: [ amazon, google, microsoft ]


================================================
FILE: bench/config/tailwind.config.js
================================================
const defaultTheme = require('tailwindcss/defaultTheme')

module.exports = {
  content: [
    './public/*.html',
    './app/helpers/**/*.rb',
    './app/javascript/**/*.js',
    './app/views/**/*.{erb,haml,html,slim}'
  ],
  theme: {
    extend: {
      fontFamily: {
        sans: ['Inter var', ...defaultTheme.fontFamily.sans],
      },
    },
  },
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
    require('@tailwindcss/container-queries'),
  ]
}


================================================
FILE: bench/config.ru
================================================
# This file is used by Rack-based servers to start the application.

require_relative "config/environment"

run Rails.application
Rails.application.load_server


================================================
FILE: bench/db/migrate/20240529231225_create_rooms.rb
================================================
class CreateRooms < ActiveRecord::Migration[8.0]
  def change
    create_table :rooms do |t|
      t.string :name

      t.timestamps
    end
  end
end


================================================
FILE: bench/db/migrate/20240530031126_create_solid_cable_message.solid_cable.rb
================================================
# frozen_string_literal: true

# This migration comes from solid_cable (originally 20240103034713)
class CreateSolidCableMessage < ActiveRecord::Migration[7.1]
  def change
    create_table :solid_cable_messages, if_not_exists: true do |t|
      t.text :channel
      t.text :payload

      t.timestamps

      t.index :created_at
    end
  end
end


================================================
FILE: bench/db/migrate/20240607184931_index_channels.solid_cable.rb
================================================
# This migration comes from solid_cable (originally 20240607184711)
class IndexChannels < ActiveRecord::Migration[7.1]
  def change
    add_index :solid_cable_messages, :channel, length: 500
  end
end


================================================
FILE: bench/db/migrate/20240609023040_create_active_error_faults.active_error.rb
================================================
# frozen_string_literal: true

# This migration comes from active_error (originally 20200727220359)
class CreateActiveErrorFaults < ActiveRecord::Migration[7.1]
  def change # rubocop:disable Metrics/AbcSize
    create_table :active_error_faults do |t|
      t.belongs_to :cause
      t.binary :backtrace, limit: 512.megabytes
      t.binary :backtrace_locations, limit: 512.megabytes
      t.string :klass
      t.text :message
      t.string :controller
      t.string :action
      t.integer :instances_count
      t.text :blamed_files, limit: 512.megabytes
      t.text :options

      t.timestamps
    end
  end
end


================================================
FILE: bench/db/migrate/20240609023041_create_active_error_instances.active_error.rb
================================================
# frozen_string_literal: true

# This migration comes from active_error (originally 20200727225318)
class CreateActiveErrorInstances < ActiveRecord::Migration[7.1]
  def change
    create_table :active_error_instances do |t|
      t.belongs_to :fault
      t.text :url
      t.binary :headers, limit: 512.megabytes
      t.binary :parameters, limit: 512.megabytes

      t.timestamps
    end
  end
end


================================================
FILE: bench/db/migrate/20240912235943_create_compact_channel.rb
================================================
# frozen_string_literal: true

class CreateCompactChannel < ActiveRecord::Migration[7.2]
  def up
    change_column :solid_cable_messages, :channel, :binary, limit: 1024, null: false
    add_column :solid_cable_messages, :channel_hash, :integer, limit: 8, if_not_exists: true
    add_index :solid_cable_messages, :channel_hash, if_not_exists: true
    change_column :solid_cable_messages, :payload, :binary, limit: 536_870_912, null: false

    SolidCable::Message.reset_column_information
    SolidCable::Message.find_each do |msg|
      msg.update(channel_hash: SolidCable::Message.channel_hash_for(msg.channel))
    end
  end

  def down
    change_column :solid_cable_messages, :channel, :text
    remove_column :solid_cable_messages, :channel_hash, if_exists: true
    change_column :solid_cable_messages, :payload, :text
  end
end


================================================
FILE: bench/db/schema.rb
================================================
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[8.0].define(version: 2024_09_12_235943) do
  create_table "active_error_faults", force: :cascade do |t|
    t.integer "cause_id"
    t.binary "backtrace", limit: 536870912
    t.binary "backtrace_locations", limit: 536870912
    t.string "klass"
    t.text "message"
    t.string "controller"
    t.string "action"
    t.integer "instances_count"
    t.text "blamed_files", limit: 536870912
    t.text "options"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["cause_id"], name: "index_active_error_faults_on_cause_id"
  end

  create_table "active_error_instances", force: :cascade do |t|
    t.integer "fault_id"
    t.text "url"
    t.binary "headers", limit: 536870912
    t.binary "parameters", limit: 536870912
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["fault_id"], name: "index_active_error_instances_on_fault_id"
  end

  create_table "rooms", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "solid_cable_messages", force: :cascade do |t|
    t.binary "channel", limit: 1024, null: false
    t.binary "payload", limit: 536870912, null: false
    t.datetime "created_at", null: false
    t.integer "channel_hash", limit: 8, null: false
    t.index ["channel"], name: "index_solid_cable_messages_on_channel"
    t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash"
    t.index ["created_at"], name: "index_solid_cable_messages_on_created_at"
  end
end


================================================
FILE: bench/db/seeds.rb
================================================
# This file should ensure the existence of records required to run the application in every environment (production,
# development, test). The code here should be idempotent so that it can be executed at any point in every environment.
# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
#
# Example:
#
#   ["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
#     MovieGenre.find_or_create_by!(name: genre_name)
#   end


================================================
FILE: bench/loadtest.js
================================================
import { check, sleep, fail } from "k6";
import cable from "k6/x/cable";
import { randomIntBetween } from "https://jslib.k6.io/k6-utils/1.1.0/index.js";
import { Trend } from "k6/metrics";

let rttTrend = new Trend("rtt", true);

const WS_URL = __ENV.WS_URL || "wss://solid-cable.dev/cable";
const WS_COOKIE = __ENV.WS_COOKIE; // we need a valid cookie to authorize request
const MAX = parseInt(__ENV.MAX || "20");
// Total test duration
const TIME = parseInt(__ENV.TIME || "90");
const MESSAGES_NUM = parseInt(__ENV.NUM || "5");

export let options = {
  thresholds: {
    checks: ["rate>0.9"],
  },
  scenarios: {
    ping: {
      // We use ramping executor to slowly increase the number of users during a test
      executor: "ramping-vus",
      startVUs: (MAX / 10 || 1) | 0,
      stages: [
        { duration: `${TIME / 3}s`, target: (MAX / 4) | 0 },
        { duration: `${(7 * TIME) / 12}s`, target: MAX },
        { duration: `${TIME / 12}s`, target: 0 },
      ],
    },
  },
};


export default function () {
  const client = cable.connect(WS_URL, { cookies: WS_COOKIE, receiveTimeoutMS: 60000 });

   if (!check(client, { "successful connection": (obj) => obj })) {
    fail("connection failed");
  }

  const channel = client.subscribe("BroadcastChannel", {});
  if (!check(channel, { "successful subscription": (obj) => obj })) {
    fail("failed to subscribe");
  }

  for (let i = 0; i < MESSAGES_NUM; i++) {
    let startMessage = Date.now();
    channel.perform("ping", { message: `Hello ${i}` });

    let message = channel.receive();
    if (!check(message, { "received res": (obj) => obj.message === `pong Hello ${i}` })) {
      fail("expected message hasn't been received");
    }

    let endMessage = Date.now();
    rttTrend.add(endMessage - startMessage);

    sleep(randomIntBetween(5, 10) / 10);
  }

  // Terminate the WS connection
  client.disconnect();
}


================================================
FILE: bench/log/.keep
================================================


================================================
FILE: bench/public/404.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>The page you were looking for doesn't exist (404)</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <style>
  .rails-default-error-page {
    background-color: #EFEFEF;
    color: #2E2F30;
    text-align: center;
    font-family: arial, sans-serif;
    margin: 0;
  }

  .rails-default-error-page div.dialog {
    width: 95%;
    max-width: 33em;
    margin: 4em auto 0;
  }

  .rails-default-error-page div.dialog > div {
    border: 1px solid #CCC;
    border-right-color: #999;
    border-left-color: #999;
    border-bottom-color: #BBB;
    border-top: #B00100 solid 4px;
    border-top-left-radius: 9px;
    border-top-right-radius: 9px;
    background-color: white;
    padding: 7px 12% 0;
    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
  }

  .rails-default-error-page h1 {
    font-size: 100%;
    color: #730E15;
    line-height: 1.5em;
  }

  .rails-default-error-page div.dialog > p {
    margin: 0 0 1em;
    padding: 1em;
    background-color: #F7F7F7;
    border: 1px solid #CCC;
    border-right-color: #999;
    border-left-color: #999;
    border-bottom-color: #999;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    border-top-color: #DADADA;
    color: #666;
    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
  }
  </style>
</head>

<body class="rails-default-error-page">
  <!-- This file lives in public/404.html -->
  <div class="dialog">
    <div>
      <h1>The page you were looking for doesn't exist.</h1>
      <p>You may have mistyped the address or the page may have moved.</p>
    </div>
    <p>If you are the application owner check the logs for more information.</p>
  </div>
</body>
</html>


================================================
FILE: bench/public/406-unsupported-browser.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>Your browser is not supported (406)</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <style>
  .rails-default-error-page {
    background-color: #EFEFEF;
    color: #2E2F30;
    text-align: center;
    font-family: arial, sans-serif;
    margin: 0;
  }

  .rails-default-error-page div.dialog {
    width: 95%;
    max-width: 33em;
    margin: 4em auto 0;
  }

  .rails-default-error-page div.dialog > div {
    border: 1px solid #CCC;
    border-right-color: #999;
    border-left-color: #999;
    border-bottom-color: #BBB;
    border-top: #B00100 solid 4px;
    border-top-left-radius: 9px;
    border-top-right-radius: 9px;
    background-color: white;
    padding: 7px 12% 0;
    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
  }

  .rails-default-error-page h1 {
    font-size: 100%;
    color: #730E15;
    line-height: 1.5em;
  }

  .rails-default-error-page div.dialog > p {
    margin: 0 0 1em;
    padding: 1em;
    background-color: #F7F7F7;
    border: 1px solid #CCC;
    border-right-color: #999;
    border-left-color: #999;
    border-bottom-color: #999;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    border-top-color: #DADADA;
    color: #666;
    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
  }
  </style>
</head>

<body class="rails-default-error-page">
  <!-- This file lives in public/406-unsupported-browser.html -->
  <div class="dialog">
    <div>
      <h1>Your browser is not supported.</h1>
      <p>Please upgrade your browser to continue.</p>
    </div>
  </div>
</body>
</html>


================================================
FILE: bench/public/422.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>The change you wanted was rejected (422)</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <style>
  .rails-default-error-page {
    background-color: #EFEFEF;
    color: #2E2F30;
    text-align: center;
    font-family: arial, sans-serif;
    margin: 0;
  }

  .rails-default-error-page div.dialog {
    width: 95%;
    max-width: 33em;
    margin: 4em auto 0;
  }

  .rails-default-error-page div.dialog > div {
    border: 1px solid #CCC;
    border-right-color: #999;
    border-left-color: #999;
    border-bottom-color: #BBB;
    border-top: #B00100 solid 4px;
    border-top-left-radius: 9px;
    border-top-right-radius: 9px;
    background-color: white;
    padding: 7px 12% 0;
    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
  }

  .rails-default-error-page h1 {
    font-size: 100%;
    color: #730E15;
    line-height: 1.5em;
  }

  .rails-default-error-page div.dialog > p {
    margin: 0 0 1em;
    padding: 1em;
    background-color: #F7F7F7;
    border: 1px solid #CCC;
    border-right-color: #999;
    border-left-color: #999;
    border-bottom-color: #999;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    border-top-color: #DADADA;
    color: #666;
    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
  }
  </style>
</head>

<body class="rails-default-error-page">
  <!-- This file lives in public/422.html -->
  <div class="dialog">
    <div>
      <h1>The change you wanted was rejected.</h1>
      <p>Maybe you tried to change something you didn't have access to.</p>
    </div>
    <p>If you are the application owner check the logs for more information.</p>
  </div>
</body>
</html>


================================================
FILE: bench/public/500.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>We're sorry, but something went wrong (500)</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <style>
  .rails-default-error-page {
    background-color: #EFEFEF;
    color: #2E2F30;
    text-align: center;
    font-family: arial, sans-serif;
    margin: 0;
  }

  .rails-default-error-page div.dialog {
    width: 95%;
    max-width: 33em;
    margin: 4em auto 0;
  }

  .rails-default-error-page div.dialog > div {
    border: 1px solid #CCC;
    border-right-color: #999;
    border-left-color: #999;
    border-bottom-color: #BBB;
    border-top: #B00100 solid 4px;
    border-top-left-radius: 9px;
    border-top-right-radius: 9px;
    background-color: white;
    padding: 7px 12% 0;
    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
  }

  .rails-default-error-page h1 {
    font-size: 100%;
    color: #730E15;
    line-height: 1.5em;
  }

  .rails-default-error-page div.dialog > p {
    margin: 0 0 1em;
    padding: 1em;
    background-color: #F7F7F7;
    border: 1px solid #CCC;
    border-right-color: #999;
    border-left-color: #999;
    border-bottom-color: #999;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    border-top-color: #DADADA;
    color: #666;
    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
  }
  </style>
</head>

<body class="rails-default-error-page">
  <!-- This file lives in public/500.html -->
  <div class="dialog">
    <div>
      <h1>We're sorry, but something went wrong.</h1>
    </div>
    <p>If you are the application owner check the logs for more information.</p>
  </div>
</body>
</html>


================================================
FILE: bench/public/robots.txt
================================================
# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file


================================================
FILE: bench/test/channels/application_cable/connection_test.rb
================================================
require "test_helper"

module ApplicationCable
  class ConnectionTest < ActionCable::Connection::TestCase
    # test "connects with cookies" do
    #   cookies.signed[:user_id] = 42
    #
    #   connect
    #
    #   assert_equal connection.user_id, "42"
    # end
  end
end


================================================
FILE: bench/test/channels/broadcast_channel_test.rb
================================================
require "test_helper"

class BroadcastChannelTest < ActionCable::Channel::TestCase
  # test "subscribes" do
  #   subscribe
  #   assert subscription.confirmed?
  # end
end


================================================
FILE: bench/test/controllers/.keep
================================================


================================================
FILE: bench/test/fixtures/files/.keep
================================================


================================================
FILE: bench/test/fixtures/rooms.yml
================================================
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

one:
  name: MyString

two:
  name: MyString


================================================
FILE: bench/test/models/.keep
================================================


================================================
FILE: bench/test/models/room_test.rb
================================================
require "test_helper"

class RoomTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end


================================================
FILE: bench/test/test_helper.rb
================================================
ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"

module ActiveSupport
  class TestCase
    # Run tests in parallel with specified workers
    parallelize(workers: :number_of_processors)

    # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
    fixtures :all

    # Add more helper methods to be used by all tests here...
  end
end


================================================
FILE: bench/vendor/.keep
================================================


================================================
FILE: bench/vendor/javascript/.keep
================================================


================================================
FILE: bin/rails
================================================
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails gems
# installed from the root of your application.

ENGINE_ROOT = File.expand_path("..", __dir__)
ENGINE_PATH = File.expand_path("../lib/solid_cable/engine", __dir__)
APP_PATH = File.expand_path("../test/dummy/config/application", __dir__)

# Set up gems listed in the Gemfile.
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])

require "rails/all"
require "rails/engine/commands"


================================================
FILE: bin/release
================================================
#!/usr/bin/env bash

VERSION=$1

if [ -z "$VERSION" ]
then
  echo "Usage: bin/release <version>"
  exit 1
fi

printf "# frozen_string_literal: true\n\nmodule SolidCable\n  VERSION = \"$VERSION\"\nend\n" > ./lib/solid_cable/version.rb
bundle
git add lib/solid_cable/version.rb
git commit -m "Bump version for $VERSION"
rake release


================================================
FILE: bin/test
================================================
#!/usr/bin/env ruby
$: << File.expand_path("../test", __dir__)

require "bundler/setup"
require "rails/plugin/test"


================================================
FILE: lib/action_cable/subscription_adapter/solid_cable.rb
================================================
# frozen_string_literal: true

require "action_cable/subscription_adapter/base"
require "action_cable/subscription_adapter/channel_prefix"
require "action_cable/subscription_adapter/subscriber_map"
require "concurrent/atomic/semaphore"

module ActionCable
  module SubscriptionAdapter
    class SolidCable < ::ActionCable::SubscriptionAdapter::Base
      prepend ::ActionCable::SubscriptionAdapter::ChannelPrefix

      def initialize(*)
        super
        @listener = nil
      end

      def broadcast(channel, payload)
        ::SolidCable::Message.broadcast(channel, payload)

        ::SolidCable::TrimJob.perform_now if ::SolidCable.autotrim?
      end

      def subscribe(channel, callback, success_callback = nil)
        listener.add_subscriber(channel, callback, success_callback)
      end

      def unsubscribe(channel, callback)
        listener.remove_subscriber(channel, callback)
      end

      delegate :shutdown, to: :listener

      private
        def listener
          @listener || @server.mutex.synchronize do
            @listener ||= Listener.new(@server.event_loop)
          end
        end

        class Listener < ::ActionCable::SubscriptionAdapter::SubscriberMap
          CONNECTION_ERRORS = [ ActiveRecord::ConnectionFailed ]
          Stop = Class.new(Exception)

          def initialize(event_loop)
            super()

            @event_loop = event_loop

            # Critical section begins with 0 permits. It can be understood as
            # being "normally held" by the listener thread. It is released
            # for specific sections of code, rather than acquired.
            @critical = Concurrent::Semaphore.new(0)

            @reconnect_attempt = 0
            @last_id = last_message_id

            @thread = Thread.new do
              Thread.current.name = "solid_cable_listener"
              Thread.current.report_on_exception = true

              begin
                listen
              rescue *CONNECTION_ERRORS
                retry if retry_connecting?
              end
            end
          end

          def listen
            loop do
              begin
                instance = interruptible { Rails.application.executor.run! }
                with_polling_volume { broadcast_messages }
              ensure
                instance.complete! if instance
              end

              interruptible { sleep ::SolidCable.polling_interval }
            end
          rescue Stop
          ensure
            @critical.release
          end

          def interruptible
            @critical.release
            yield
          ensure
            @critical.acquire
          end

          def shutdown
            @critical.acquire
            # We have the critical permit, and so the listen thread must be
            # safe to interrupt.
            thread.raise(Stop)
            @critical.release
            thread.join
          end

          def add_channel(channel, on_success)
            channels[channel] = last_message_id
            event_loop.post(&on_success) if on_success
          end

          def remove_channel(channel)
            channels.delete(channel)
          end

          def invoke_callback(*)
            event_loop.post { super }
          end

          private
            attr_reader :event_loop, :thread
            attr_accessor :last_id, :reconnect_attempt

            def last_message_id
              ::SolidCable::Message.maximum(:id) || 0
            end

            def channels
              @channels ||= Concurrent::Map.new
            end

            def broadcast_messages
              current_channels = channels.dup

              ::SolidCable::Message.
                broadcastable(current_channels.keys, last_id).
                each do |message|
                  should_broadcast_message = false
                  channels.compute_if_present(message.channel) do |channel_last_id|
                    break if channel_last_id >= message.id

                    should_broadcast_message = true
                    message.id
                  end

                  broadcast(message.channel, message.payload) if should_broadcast_message
                  self.last_id = message.id
                end
            end

            def with_polling_volume
              if ::SolidCable.silence_polling? && ActiveRecord::Base.logger
                ActiveRecord::Base.logger.silence { yield }
              else
                yield
              end
            end

            def reconnect_attempts
              @reconnect_attempts ||= ::SolidCable.reconnect_attempts
            end

            def retry_connecting?
              self.reconnect_attempt += 1

              return false if reconnect_attempt > reconnect_attempts.size

              sleep_t = reconnect_attempts[reconnect_attempt - 1]

              sleep(sleep_t) if sleep_t > 0

              true
            end
        end
    end
  end
end


================================================
FILE: lib/generators/solid_cable/install/USAGE
================================================
Description:
    Installs solid_cable as the Action Cable adapter

Example:
    bin/rails generate solid_cable:install

    This will perform the following:
        Installs solid_cable migrations


================================================
FILE: lib/generators/solid_cable/install/install_generator.rb
================================================
# frozen_string_literal: true

class SolidCable::InstallGenerator < Rails::Generators::Base
  source_root File.expand_path("templates", __dir__)

  def copy_files
    template "db/cable_schema.rb"
    template "config/cable.yml", force: true
  end
end


================================================
FILE: lib/generators/solid_cable/install/templates/config/cable.yml
================================================
# Async adapter only works within the same process, so for manually triggering cable updates from a console,
# and seeing results in the browser, you must do so from the web console (running inside the dev process),
# not a terminal started via bin/rails console! Add "console" to any action or any ERB template view
# to make the web console appear.
development:
  adapter: async

test:
  adapter: test

production:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable
  polling_interval: 0.1.seconds
  message_retention: 1.day


================================================
FILE: lib/generators/solid_cable/install/templates/db/cable_schema.rb
================================================
ActiveRecord::Schema[7.1].define(version: 1) do
  create_table "solid_cable_messages", force: :cascade do |t|
    t.binary "channel", limit: 1024, null: false
    t.binary "payload", limit: 536870912, null: false
    t.datetime "created_at", null: false
    t.integer "channel_hash", limit: 8, null: false
    t.index ["channel"], name: "index_solid_cable_messages_on_channel"
    t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash"
    t.index ["created_at"], name: "index_solid_cable_messages_on_created_at"
  end
end


================================================
FILE: lib/generators/solid_cable/update/USAGE
================================================
Description:
    Updates Solid Cable migrations

Example:
    bin/rails generate solid_cable:update

    This will perform the following:
        Installs new Solid Cable migrations


================================================
FILE: lib/generators/solid_cable/update/templates/db/migrate/create_compact_channel.rb
================================================
# frozen_string_literal: true

class CreateCompactChannel < ActiveRecord::Migration[7.2]
  def up
    change_column :solid_cable_messages, :channel, :binary, limit: 1024, null: false
    add_column :solid_cable_messages, :channel_hash, :integer, limit: 8, if_not_exists: true
    add_index :solid_cable_messages, :channel_hash, if_not_exists: true
    change_column :solid_cable_messages, :payload, :binary, limit: 536_870_912, null: false

    SolidCable::Message.find_each do |msg|
      msg.update(channel_hash: SolidCable::Message.channel_hash_for(msg.channel))
    end
  end

  def down
    change_column :solid_cable_messages, :channel, :text
    remove_column :solid_cable_messages, :channel_hash, if_exists: true
    change_column :solid_cable_messages, :payload, :text
  end
end


================================================
FILE: lib/generators/solid_cable/update/update_generator.rb
================================================
# frozen_string_literal: true

require "rails/generators"
require "rails/generators/active_record"

class SolidCable::UpdateGenerator < Rails::Generators::Base
  include ActiveRecord::Generators::Migration

  source_root File.expand_path("templates", __dir__)

  def copy_files
    migration_template "db/migrate/create_compact_channel.rb",
                       "db/cable_migrate/create_compact_channel.rb"
  end
end


================================================
FILE: lib/solid_cable/engine.rb
================================================
# frozen_string_literal: true

module SolidCable
  class Engine < ::Rails::Engine
    isolate_namespace SolidCable
  end
end


================================================
FILE: lib/solid_cable/version.rb
================================================
# frozen_string_literal: true

module SolidCable
  VERSION = "3.0.12"
end


================================================
FILE: lib/solid_cable.rb
================================================
# frozen_string_literal: true

require "solid_cable/version"
require "solid_cable/engine"
require "action_cable/subscription_adapter/solid_cable"

module SolidCable
  class << self
    def connects_to
      cable_config.connects_to.to_h.deep_transform_values(&:to_sym)
    end

    def silence_polling?
      cable_config.silence_polling != false
    end

    def polling_interval
      parse_duration(cable_config.polling_interval, default: 0.1.seconds)
    end

    def message_retention
      parse_duration(cable_config.message_retention, default: 1.day)
    end

    def autotrim?
      cable_config.autotrim != false
    end

    def trim_batch_size
      if (size = cable_config.trim_batch_size.to_i) < 2
        100
      else
        size
      end
    end

    def use_skip_locked
      cable_config.use_skip_locked != false
    end

    # For every write that we do, we attempt to delete trim_chance times as
    # many records. This ensures there is downward pressure on the cache size
    # while there is valid data to delete. Read this as 'every time the trim job
    # runs theres a trim_multiplier chance this trims'. Adjust number to make it
    # more or less likely to trim. Only works like this if trim_batch_size is
    # 100
    def trim_chance
      2
    end

    def reconnect_attempts
      attempts = cable_config.fetch(:reconnect_attempts, 1)
      attempts = Array.new(attempts, 0) if attempts.is_a?(Integer)
      attempts
    end

    private
      def cable_config
        Rails.application.config_for("cable")
      end

      def parse_duration(duration, default:)
        if duration.present?
          *amount, units = duration.to_s.split(".")
          amount.join(".").to_f.public_send(units)
        else
          default
        end
      end
  end
end


================================================
FILE: lib/tasks/solid_cable_tasks.rake
================================================
# frozen_string_literal: true

desc "Copy over the schema and set cable adapter for Solid Cable"
namespace :solid_cable do
  task :install do
    Rails::Command.invoke :generate, [ "solid_cable:install" ]
  end

  task :update do
    Rails::Command.invoke :generate, [ "solid_cable:update" ]
  end
end


================================================
FILE: solid_cable.gemspec
================================================
# frozen_string_literal: true

require_relative "lib/solid_cable/version"

Gem::Specification.new do |spec|
  spec.name        = "solid_cable"
  spec.version     = SolidCable::VERSION
  spec.authors     = [ "Nick Pezza" ]
  spec.email       = [ "pezza@hey.com" ]
  spec.homepage    = "https://github.com/rails/solid_cable"
  spec.summary     = "Database-backed Action Cable backend."
  spec.description = "Database-backed Action Cable backend."
  spec.license     = "MIT"

  spec.metadata["homepage_uri"] = spec.homepage
  spec.metadata["source_code_uri"] = spec.homepage
  spec.metadata["rubygems_mfa_required"] = "true"

  spec.files = Dir.chdir(File.expand_path(__dir__)) do
    Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
  end

  rails_version = ">= 7.2"
  spec.required_ruby_version = ">= 3.2.0"
  spec.add_dependency "activerecord", rails_version
  spec.add_dependency "activejob", rails_version
  spec.add_dependency "actioncable", rails_version
  spec.add_dependency "railties", rails_version

  spec.add_development_dependency "minitest", "~> 5.0"
end


================================================
FILE: test/config_stubs.rb
================================================
# frozen_string_literal: true

module ConfigStubs
  extend ActiveSupport::Concern

  class ConfigStub
    def initialize(**)
      @config = ActiveSupport::OrderedOptions.new.
                update({ adapter: :test }.merge(**))
    end

    def config_for(_file)
      @config
    end

    def executor
      @executor ||= ExectorStub.new
    end

    class ExectorStub
      def run!
      end
    end
  end

  def with_cable_config(**)
    Rails.stub(:application, ConfigStub.new(**)) { yield }
  end
end


================================================
FILE: test/dummy/Rakefile
================================================
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

require_relative "config/application"

Rails.application.load_tasks


================================================
FILE: test/dummy/app/controllers/application_controller.rb
================================================
class ApplicationController < ActionController::Base
  # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
  allow_browser versions: :modern
end


================================================
FILE: test/dummy/app/controllers/concerns/.keep
================================================


================================================
FILE: test/dummy/app/helpers/application_helper.rb
================================================
module ApplicationHelper
end


================================================
FILE: test/dummy/app/jobs/application_job.rb
================================================
class ApplicationJob < ActiveJob::Base
  # Automatically retry jobs that encountered a deadlock
  # retry_on ActiveRecord::Deadlocked

  # Most jobs are safe to ignore if the underlying records are no longer available
  # discard_on ActiveJob::DeserializationError
end


================================================
FILE: test/dummy/app/models/application_record.rb
================================================
class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class
end


================================================
FILE: test/dummy/app/models/concerns/.keep
================================================


================================================
FILE: test/dummy/app/views/layouts/application.html.erb
================================================
<!DOCTYPE html>
<html>
  <head>
    <title><%= content_for(:title) || "Dummy" %></title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="application-name" content="Dummy">
    <meta name="mobile-web-app-capable" content="yes">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= yield :head %>

    <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
    <%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>

    <link rel="icon" href="/icon.png" type="image/png">
    <link rel="icon" href="/icon.svg" type="image/svg+xml">
    <link rel="apple-touch-icon" href="/icon.png">

    <%# Includes all stylesheet files in app/assets/stylesheets %>
    <%= stylesheet_link_tag :app %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>


================================================
FILE: test/dummy/app/views/layouts/mailer.html.erb
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <style>
      /* Email styles need to be inline */
    </style>
  </head>

  <body>
    <%= yield %>
  </body>
</html>


================================================
FILE: test/dummy/app/views/layouts/mailer.text.erb
================================================
<%= yield %>


================================================
FILE: test/dummy/app/views/pwa/manifest.json.erb
================================================
{
  "name": "Dummy",
  "icons": [
    {
      "src": "/icon.png",
      "type": "image/png",
      "sizes": "512x512"
    },
    {
      "src": "/icon.png",
      "type": "image/png",
      "sizes": "512x512",
      "purpose": "maskable"
    }
  ],
  "start_url": "/",
  "display": "standalone",
  "scope": "/",
  "description": "Dummy.",
  "theme_color": "red",
  "background_color": "red"
}


================================================
FILE: test/dummy/app/views/pwa/service-worker.js
================================================
// Add a service worker for processing Web Push notifications:
//
// self.addEventListener("push", async (event) => {
//   const { title, options } = await event.data.json()
//   event.waitUntil(self.registration.showNotification(title, options))
// })
//
// self.addEventListener("notificationclick", function(event) {
//   event.notification.close()
//   event.waitUntil(
//     clients.matchAll({ type: "window" }).then((clientList) => {
//       for (let i = 0; i < clientList.length; i++) {
//         let client = clientList[i]
//         let clientPath = (new URL(client.url)).pathname
//
//         if (clientPath == event.notification.data.path && "focus" in client) {
//           return client.focus()
//         }
//       }
//
//       if (clients.openWindow) {
//         return clients.openWindow(event.notification.data.path)
//       }
//     })
//   )
// })


================================================
FILE: test/dummy/bin/ci
================================================
#!/usr/bin/env ruby
require_relative "../config/boot"
require "active_support/continuous_integration"

CI = ActiveSupport::ContinuousIntegration
require_relative "../config/ci.rb"


================================================
FILE: test/dummy/bin/dev
================================================
#!/usr/bin/env ruby
exec "./bin/rails", "server", *ARGV


================================================
FILE: test/dummy/bin/rails
================================================
#!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"


================================================
FILE: test/dummy/bin/rake
================================================
#!/usr/bin/env ruby
require_relative "../config/boot"
require "rake"
Rake.application.run


================================================
FILE: test/dummy/bin/setup
================================================
#!/usr/bin/env ruby
require "fileutils"

APP_ROOT = File.expand_path("..", __dir__)

def system!(*args)
  system(*args, exception: true)
end

FileUtils.chdir APP_ROOT do
  # This script is a way to set up or update your development environment automatically.
  # This script is idempotent, so that you can run it at any time and get an expectable outcome.
  # Add necessary setup steps to this file.

  puts "== Installing dependencies =="
  system("bundle check") || system!("bundle install")

  # puts "\n== Copying sample files =="
  # unless File.exist?("config/database.yml")
  #   FileUtils.cp "config/database.yml.sample", "config/database.yml"
  # end

  puts "\n== Preparing database =="
  system! "bin/rails db:prepare"
  system! "bin/rails db:reset" if ARGV.include?("--reset")

  puts "\n== Removing old logs and tempfiles =="
  system! "bin/rails log:clear tmp:clear"

  unless ARGV.include?("--skip-server")
    puts "\n== Starting development server =="
    STDOUT.flush # flush the output before exec(2) so that it displays
    exec "bin/dev"
  end
end


================================================
FILE: test/dummy/config/application.rb
================================================
require_relative "boot"

require "rails/all"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Dummy
  class Application < Rails::Application
    config.load_defaults Rails::VERSION::STRING.to_f

    # For compatibility with applications that use this config
    config.action_controller.include_all_helpers = false

    # Please, add to the `ignore` list any other `lib` subdirectories that do
    # not contain `.rb` files, or that should not be reloaded or eager loaded.
    # Common ones are `templates`, `generators`, or `middleware`, for example.
    config.autoload_lib(ignore: %w[assets tasks])

    # Configuration for the application, engines, and railties goes here.
    #
    # These settings can be overridden in specific environments using the files
    # in config/environments, which are processed later.
    #
    # config.time_zone = "Central Time (US & Canada)"
    # config.eager_load_paths << Rails.root.join("extras")
  end
end


================================================
FILE: test/dummy/config/boot.rb
================================================
# Set up gems listed in the Gemfile.
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__)

require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
$LOAD_PATH.unshift File.expand_path("../../../lib", __dir__)


================================================
FILE: test/dummy/config/cable.yml
================================================
development:
  adapter: async

test:
  adapter: test

production:
  adapter: redis
  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
  channel_prefix: dummy_production


================================================
FILE: test/dummy/config/ci.rb
================================================
# Run using bin/ci

CI.run do
  step "Setup", "bin/setup --skip-server"


  step "Tests: Rails", "bin/rails test"
  step "Tests: System", "bin/rails test:system"
  step "Tests: Seeds", "env RAILS_ENV=test bin/rails db:seed:replant"

  # Optional: set a green GitHub commit status to unblock PR merge.
  # Requires the `gh` CLI and `gh extension install basecamp/gh-signoff`.
  # if success?
  #   step "Signoff: All systems go. Ready for merge and deploy.", "gh signoff"
  # else
  #   failure "Signoff: CI failed. Do not merge or deploy.", "Fix the issues and try again."
  # end
end


================================================
FILE: test/dummy/config/database.yml
================================================
<% def database_name_from(name); ["mysql", "postgres"].exclude?(ENV["TARGET_DB"]) ? "db/#{name}.sqlite3" : name; end %>

<% if ENV["TARGET_DB"] == "mysql" %>
default: &default
  adapter: trilogy
  username: root
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: "127.0.0.1"
  port: 33060
<% elsif ENV["TARGET_DB"] == "postgres" %>
default: &default
  adapter: postgresql
  encoding: unicode
  username: postgres
  pool: 5
  host: "127.0.0.1"
  port: 55432
  gssencmode: disable # https://github.com/ged/ruby-pg/issues/311
<% else %>
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 50 } %>
  timeout: 100
<% end %>

development:
  <<: *default
  database: <%= database_name_from("solid_cable_development") %>

test:
  <<: *default
  pool: 20
  database: <%= database_name_from("solid_cable_test") %>


================================================
FILE: test/dummy/config/environment.rb
================================================
# Load the Rails application.
require_relative "application"

# Initialize the Rails application.
Rails.application.initialize!


================================================
FILE: test/dummy/config/environments/development.rb
================================================
require "active_support/core_ext/integer/time"

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # Make code changes take effect immediately without server restart.
  config.enable_reloading = true

  # Do not eager load code on boot.
  config.eager_load = false

  # Show full error reports.
  config.consider_all_requests_local = true

  # Enable server timing.
  config.server_timing = true

  # Enable/disable Action Controller caching. By default Action Controller caching is disabled.
  # Run rails dev:cache to toggle Action Controller caching.
  if Rails.root.join("tmp/caching-dev.txt").exist?
    config.action_controller.perform_caching = true
    config.action_controller.enable_fragment_cache_logging = true
    config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" }
  else
    config.action_controller.perform_caching = false
  end

  # Change to :null_store to avoid any caching.
  config.cache_store = :memory_store

  # Store uploaded files on the local file system (see config/storage.yml for options).
  # config.active_storage.service = :local

  # Don't care if the mailer can't send.
  # config.action_mailer.raise_delivery_errors = false

  # Make template changes take effect immediately.
  # config.action_mailer.perform_caching = false

  # Set localhost to be used by links generated in mailer templates.
  # config.action_mailer.default_url_options = { host: "localhost", port: 3000 }

  # Print deprecation notices to the Rails logger.
  config.active_support.deprecation = :log

  # Raise an error on page load if there are pending migrations.
  config.active_record.migration_error = :page_load

  # Highlight code that triggered database queries in logs.
  config.active_record.verbose_query_logs = true

  # Append comments with runtime information tags to SQL queries in logs.
  config.active_record.query_log_tags_enabled = true

  # Highlight code that enqueued background job in logs.
  config.active_job.verbose_enqueue_logs = true

  # Highlight code that triggered redirect in logs.
  config.action_dispatch.verbose_redirect_logs = true

  # Suppress logger output for asset requests.
  # config.assets.quiet = true

  # Raises error for missing translations.
  # config.i18n.raise_on_missing_translations = true

  # Annotate rendered view with file names.
  config.action_view.annotate_rendered_view_with_filenames = true

  # Uncomment if you wish to allow Action Cable access from any origin.
  # config.action_cable.disable_request_forgery_protection = true

  # Raise error when a before_action's only/except options reference missing actions.
  config.action_controller.raise_on_missing_callback_actions = true
end


================================================
FILE: test/dummy/config/environments/production.rb
================================================
require "active_support/core_ext/integer/time"

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # Code is not reloaded between requests.
  config.enable_reloading = false

  # Eager load code on boot for better performance and memory savings (ignored by Rake tasks).
  config.eager_load = true

  # Full error reports are disabled.
  config.consider_all_requests_local = false

  # Turn on fragment caching in view templates.
  config.action_controller.perform_caching = true

  # Cache assets for far-future expiry since they are all digest stamped.
  config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" }

  # Enable serving of images, stylesheets, and JavaScripts from an asset server.
  # config.asset_host = "http://assets.example.com"

  # Store uploaded files on the local file system (see config/storage.yml for options).
  # config.active_storage.service = :local

  # Assume all access to the app is happening through a SSL-terminating reverse proxy.
  config.assume_ssl = true

  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true

  # Skip http-to-https redirect for the default health check endpoint.
  # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }

  # Log to STDOUT with the current request id as a default log tag.
  config.log_tags = [ :request_id ]
  config.logger   = ActiveSupport::TaggedLogging.logger(STDOUT)

  # Change to "debug" to log everything (including potentially personally-identifiable information!).
  config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info")

  # Prevent health checks from clogging up the logs.
  config.silence_healthcheck_path = "/up"

  # Don't log any deprecations.
  config.active_support.report_deprecations = false

  # Replace the default in-process memory cache store with a durable alternative.
  # config.cache_store = :mem_cache_store

  # Replace the default in-process and non-durable queuing backend for Active Job.
  # config.active_job.queue_adapter = :resque

  # Ignore bad email addresses and do not raise email delivery errors.
  # Set this to true and configure the email server for immediate delivery to raise delivery errors.
  # config.action_mailer.raise_delivery_errors = false

  # Set host to be used by links generated in mailer templates.
  config.action_mailer.default_url_options = { host: "example.com" }

  # Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.
  # config.action_mailer.smtp_settings = {
  #   user_name: Rails.application.credentials.dig(:smtp, :user_name),
  #   password: Rails.application.credentials.dig(:smtp, :password),
  #   address: "smtp.example.com",
  #   port: 587,
  #   authentication: :plain
  # }

  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
  # the I18n.default_locale when a translation cannot be found).
  config.i18n.fallbacks = true

  # Do not dump schema after migrations.
  config.active_record.dump_schema_after_migration = false

  # Only use :id for inspections in production.
  config.active_record.attributes_for_inspect = [ :id ]

  # Enable DNS rebinding protection and other `Host` header attacks.
  # config.hosts = [
  #   "example.com",     # Allow requests from example.com
  #   /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
  # ]
  #
  # Skip DNS rebinding protection for the default health check endpoint.
  # config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
end


================================================
FILE: test/dummy/config/environments/test.rb
================================================
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # While tests run files are not watched, reloading is not necessary.
  config.enable_reloading = false

  # Eager loading loads your entire application. When running a single test locally,
  # this is usually not necessary, and can slow down your test suite. However, it's
  # recommended that you enable it in continuous integration systems to ensure eager
  # loading is working properly before deploying your code.
  config.eager_load = ENV["CI"].present?

  # Configure public file server for tests with cache-control for performance.
  config.public_file_server.headers = { "cache-control" => "public, max-age=3600" }

  # Show full error reports.
  config.consider_all_requests_local = true
  config.cache_store = :null_store

  # Render exception templates for rescuable exceptions and raise for other exceptions.
  config.action_dispatch.show_exceptions = :rescuable

  # Disable request forgery protection in test environment.
  config.action_controller.allow_forgery_protection = false

  # Store uploaded files on the local file system in a temporary directory.
  # config.active_storage.service = :test

  # Tell Action Mailer not to deliver emails to the real world.
  # The :test delivery method accumulates sent emails in the
  # ActionMailer::Base.deliveries array.
  # config.action_mailer.delivery_method = :test

  # Set host to be used by links generated in mailer templates.
  # config.action_mailer.default_url_options = { host: "example.com" }

  # Print deprecation notices to the stderr.
  config.active_support.deprecation = :stderr

  # Raises error for missing translations.
  # config.i18n.raise_on_missing_translations = true

  # Annotate rendered view with file names.
  # config.action_view.annotate_rendered_view_with_filenames = true

  # Raise error when a before_action's only/except options reference missing actions.
  config.action_controller.raise_on_missing_callback_actions = true
end


================================================
FILE: test/dummy/config/initializers/assets.rb
================================================
# Be sure to restart your server when you modify this file.

# Version of your assets, change this if you want to expire all your assets.
# Rails.application.config.assets.version = "1.0"

# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path


================================================
FILE: test/dummy/config/initializers/content_security_policy.rb
================================================
# Be sure to restart your server when you modify this file.

# Define an application-wide content security policy.
# See the Securing Rails Applications Guide for more information:
# https://guides.rubyonrails.org/security.html#content-security-policy-header

# Rails.application.configure do
#   config.content_security_policy do |policy|
#     policy.default_src :self, :https
#     policy.font_src    :self, :https, :data
#     policy.img_src     :self, :https, :data
#     policy.object_src  :none
#     policy.script_src  :self, :https
#     policy.style_src   :self, :https
#     # Specify URI for violation reports
#     # policy.report_uri "/csp-violation-report-endpoint"
#   end
#
#   # Generate session nonces for permitted importmap, inline scripts, and inline styles.
#   config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
#   config.content_security_policy_nonce_directives = %w(script-src style-src)
#
#   # Automatically add `nonce` to `javascript_tag`, `javascript_include_tag`, and `stylesheet_link_tag`
#   # if the corresponding directives are specified in `content_security_policy_nonce_directives`.
#   # config.content_security_policy_nonce_auto = true
#
#   # Report violations without enforcing the policy.
#   # config.content_security_policy_report_only = true
# end


================================================
FILE: test/dummy/config/initializers/filter_parameter_logging.rb
================================================
# Be sure to restart your server when you modify this file.

# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.
# Use this to limit dissemination of sensitive information.
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
Rails.application.config.filter_parameters += [
  :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc
]


================================================
FILE: test/dummy/config/initializers/inflections.rb
================================================
# Be sure to restart your server when you modify this file.

# Add new inflection rules using the following format. Inflections
# are locale specific, and you may define rules for as many different
# locales as you wish. All of these examples are active by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
#   inflect.plural /^(ox)$/i, "\\1en"
#   inflect.singular /^(ox)en/i, "\\1"
#   inflect.irregular "person", "people"
#   inflect.uncountable %w( fish sheep )
# end

# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
#   inflect.acronym "RESTful"
# end


================================================
FILE: test/dummy/config/locales/en.yml
================================================
# Files in the config/locales directory are used for internationalization and
# are automatically loaded by Rails. If you want to use locales other than
# English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
#     I18n.t "hello"
#
# In views, this is aliased to just `t`:
#
#     <%= t("hello") %>
#
# To use a different locale, set it with `I18n.locale`:
#
#     I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# To learn more about the API, please read the Rails Internationalization guide
# at https://guides.rubyonrails.org/i18n.html.
#
# Be aware that YAML interprets the following case-insensitive strings as
# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings
# must be quoted to be interpreted as strings. For example:
#
#     en:
#       "yes": yup
#       enabled: "ON"

en:
  hello: "Hello world"


================================================
FILE: test/dummy/config/puma.rb
================================================
# This configuration file will be evaluated by Puma. The top-level methods that
# are invoked here are part of Puma's configuration DSL. For more information
# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.
#
# Puma starts a configurable number of processes (workers) and each process
# serves each request in a thread from an internal thread pool.
#
# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You
# should only set this value when you want to run 2 or more workers. The
# default is already 1. You can set it to `auto` to automatically start a worker
# for each available processor.
#
# The ideal number of threads per worker depends both on how much time the
# application spends waiting for IO operations and on how much you wish to
# prioritize throughput over latency.
#
# As a rule of thumb, increasing the number of threads will increase how much
# traffic a given process can handle (throughput), but due to CRuby's
# Global VM Lock (GVL) it has diminishing returns and will degrade the
# response time (latency) of the application.
#
# The default is set to 3 threads as it's deemed a decent compromise between
# throughput and latency for the average Rails application.
#
# Any libraries that use a connection pool or another resource pool should
# be configured to provide at least as many connections as the number of
# threads. This includes Active Record's `pool` parameter in `database.yml`.
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
port ENV.fetch("PORT", 3000)

# Allow puma to be restarted by `bin/rails restart` command.
plugin :tmp_restart

# Specify the PID file. Defaults to tmp/pids/server.pid in development.
# In other environments, only set the PID file if requested.
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]


================================================
FILE: test/dummy/config/routes.rb
================================================
# frozen_string_literal: true

Rails.application.routes.draw do
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
  # Can be used by load balancers and uptime monitors to verify that the app is live.
  get "up" => "rails/health#show", as: :rails_health_check

  # Defines the root path route ("/")
  # root "posts#index"
end


================================================
FILE: test/dummy/config/storage.yml
================================================
test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
# amazon:
#   service: S3
#   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
#   region: us-east-1
#   bucket: your_own_bucket-<%= Rails.env %>

# Remember not to checkin your GCS keyfile to a repository
# google:
#   service: GCS
#   project: your_project
#   credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
#   bucket: your_own_bucket-<%= Rails.env %>

# mirror:
#   service: Mirror
#   primary: local
#   mirrors: [ amazon, google, microsoft ]


================================================
FILE: test/dummy/config.ru
================================================
# This file is used by Rack-based servers to start the application.

require_relative "config/environment"

run Rails.application
Rails.application.load_server


================================================
FILE: test/dummy/db/schema.rb
================================================
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.2].define(version: 2024_09_12_130854) do
  create_table "solid_cable_messages", force: :cascade do |t|
    t.binary "channel", limit: 1024, null: false
    t.binary "payload", limit: 536870912, null: false
    t.datetime "created_at", null: false
    t.integer "channel_hash", limit: 8, null: false
    t.index ["channel"], name: "index_solid_cable_messages_on_channel"
    t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash"
    t.index ["created_at"], name: "index_solid_cable_messages_on_created_at"
  end
end


================================================
FILE: test/dummy/log/.keep
================================================


================================================
FILE: test/dummy/public/400.html
================================================
<!doctype html>

<html lang="en">

  <head>

    <title>The server cannot process the request due to a client error (400 Bad Request)</title>

    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, width=device-width">
    <meta name="robots" content="noindex, nofollow">

    <style>

      *, *::before, *::after {
        box-sizing: border-box;
      }

      * {
        margin: 0;
      }

      html {
        font-size: 16px;
      }

      body {
        background: #FFF;
        color: #261B23;
        display: grid;
        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
        font-size: clamp(1rem, 2.5vw, 2rem);
        -webkit-font-smoothing: antialiased;
        font-style: normal;
        font-weight: 400;
        letter-spacing: -0.0025em;
        line-height: 1.4;
        min-height: 100dvh;
        place-items: center;
        text-rendering: optimizeLegibility;
        -webkit-text-size-adjust: 100%;
      }

      #error-description {
        fill: #d30001;
      }

      #error-id {
        fill: #f0eff0;
      }

      @media (prefers-color-scheme: dark) {
        body {
          background: #101010;
          color: #e0e0e0;
        }

        #error-description {
          fill: #FF6161;
        }

        #error-id {
          fill: #2c2c2c;
        }
      }

      a {
        color: inherit;
        font-weight: 700;
        text-decoration: underline;
        text-underline-offset: 0.0925em;
      }

      b, strong {
        font-weight: 700;
      }

      i, em {
        font-style: italic;
      }

      main {
        display: grid;
        gap: 1em;
        padding: 2em;
        place-items: center;
        text-align: center;
      }

      main header {
        width: min(100%, 12em);
      }

      main header svg {
        height: auto;
        max-width: 100%;
        width: 100%;
      }

      main article {
        width: min(100%, 30em);
      }

      main article p {
        font-size: 75%;
      }

      main article br {
        display: none;

        @media(min-width: 48em) {
          display: inline;
        }
      }

    </style>

  </head>

  <body>

    <!-- This file lives in public/400.html -->

    <main>
      <header>
        <svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm140.456 133.2831c-40.823 0-64.884-35.146-64.884-85.7015 0-50.5554 24.061-85.700907 64.884-85.700907 40.822 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.062 85.7015-64.884 85.7015zm0-133.2831c-17.573 0-22.71 21.8984-22.71 47.5816 0 25.6835 5.137 47.5815 22.71 47.5815 17.302 0 22.709-21.898 22.709-47.5815 0-25.6832-5.407-47.5816-22.709-47.5816z" id="error-id"/><path d="m123.606 85.4445c3.212 1.0523 5.538 4.2089 5.538 8.0301 0 6.1472-4.209 9.5254-11.298 9.5254h-15.617v-34.0033h14.565c7.089 0 11.353 3.1566 11.353 9.2484 0 3.6551-2.049 6.3134-4.541 7.1994zm-12.904-2.9905h5.095c2.603 0 3.988-.9968 3.988-3.1013 0-2.1044-1.385-3.0459-3.988-3.0459h-5.095zm0 6.6456v6.5902h5.981c2.492 0 3.877-1.3291 3.877-3.2674 0-2.049-1.385-3.3228-3.877-3.3228zm43.786 13.9004h-8.362v-1.274c-.831.831-3.323 1.717-5.981 1.717-4.929 0-9.083-2.769-9.083-8.0301 0-4.818 4.154-7.9193 9.581-7.9193 2.049 0 4.486.6646 5.483 1.3845v-1.606c0-1.606-.942-2.9905-3.046-2.9905-1.606 0-2.548.7199-2.935 1.8275h-8.197c.72-4.8181 4.985-8.6393 11.409-8.6393 7.088 0 11.131 3.7659 11.131 10.2453zm-8.362-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.434.7199-3.434 2.3813 0 1.7168 1.717 2.4367 3.434 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm27.996 6.9779v-1.994c-1.163 1.329-3.599 2.548-6.147 2.548-7.199 0-11.131-5.8151-11.131-13.0145s3.932-13.0143 11.131-13.0143c2.548 0 4.984 1.2184 6.147 2.5475v-13.0697h8.695v35.997zm0-9.1931v-6.5902c-.664-1.3291-2.159-2.326-3.821-2.326-2.99 0-4.763 2.4368-4.763 5.6488s1.773 5.5934 4.763 5.5934c1.717 0 3.157-.9415 3.821-2.326zm35.471-2.049h-3.101v11.2421h-8.806v-34.0033h15.285c7.31 0 12.35 4.1535 12.35 11.5744 0 5.1503-2.603 8.6947-6.757 10.2453l7.975 12.1836h-9.858zm-3.101-15.2849v8.1962h5.538c3.156 0 4.596-1.606 4.596-4.0981s-1.44-4.0981-4.596-4.0981zm36.957 17.8323h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm30.98 27.5234v-10.799c-1.163 1.329-3.6 2.548-6.147 2.548-7.2 0-11.132-5.9259-11.132-13.0145 0-7.144 3.932-13.0143 11.132-13.0143 2.547 0 4.984 1.2184 6.147 2.5475v-1.9937h8.695v33.726zm0-17.9981v-6.5902c-.665-1.3291-2.105-2.326-3.821-2.326-2.991 0-4.763 2.4368-4.763 5.6488s1.772 5.5934 4.763 5.5934c1.661 0 3.156-.9415 3.821-2.326zm36.789-15.7279v24.921h-8.695v-2.16c-1.329 1.551-3.821 2.714-6.646 2.714-5.482 0-8.75-3.5999-8.75-9.1379v-16.3371h8.64v14.288c0 2.1045.996 3.5997 3.212 3.5997 1.606 0 3.101-1.0522 3.544-2.769v-15.1187zm19.084 16.2263h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.963 5.095 11.963 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm13.428 11.0206h8.474c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.939-1.5506l-4.873-.9969c-4.154-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.762-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.741-8.2518zm27.538-.8861v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.993-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.871 0-9.193-2.769-9.193-9.0819z" id="error-description"/></svg>
      </header>
      <article>
        <p><strong>The server cannot process the request due to a client error.</strong> Please check the request and try again. If you're the application owner check the logs for more information.</p>
      </article>
    </main>

  </body>

</html>


================================================
FILE: test/dummy/public/404.html
================================================
<!doctype html>

<html lang="en">

  <head>

    <title>The page you were looking for doesn't exist (404 Not found)</title>

    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, width=device-width">
    <meta name="robots" content="noindex, nofollow">

    <style>

      *, *::before, *::after {
        box-sizing: border-box;
      }

      * {
        margin: 0;
      }

      html {
        font-size: 16px;
      }

      body {
        background: #FFF;
        color: #261B23;
        display: grid;
        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
        font-size: clamp(1rem, 2.5vw, 2rem);
        -webkit-font-smoothing: antialiased;
        font-style: normal;
        font-weight: 400;
        letter-spacing: -0.0025em;
        line-height: 1.4;
        min-height: 100dvh;
        place-items: center;
        text-rendering: optimizeLegibility;
        -webkit-text-size-adjust: 100%;
      }

      #error-description {
        fill: #d30001;
      }

      #error-id {
        fill: #f0eff0;
      }

      @media (prefers-color-scheme: dark) {
        body {
          background: #101010;
          color: #e0e0e0;
        }

        #error-description {
          fill: #FF6161;
        }

        #error-id {
          fill: #2c2c2c;
        }
      }

      a {
        color: inherit;
        font-weight: 700;
        text-decoration: underline;
        text-underline-offset: 0.0925em;
      }

      b, strong {
        font-weight: 700;
      }

      i, em {
        font-style: italic;
      }

      main {
        display: grid;
        gap: 1em;
        padding: 2em;
        place-items: center;
        text-align: center;
      }

      main header {
        width: min(100%, 12em);
      }

      main header svg {
        height: auto;
        max-width: 100%;
        width: 100%;
      }

      main article {
        width: min(100%, 30em);
      }

      main article p {
        font-size: 75%;
      }

      main article br {
        display: none;

        @media(min-width: 48em) {
          display: inline;
        }
      }

    </style>

  </head>

  <body>

    <!-- This file lives in public/404.html -->

    <main>
      <header>
        <svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm165.328-35.41581-45.689 100.02991h26.224v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.184v-31.901l50.285-103.27391z" id="error-id"/><path d="m157.758 68.9967v34.0033h-7.199l-14.233-19.8814v19.8814h-8.584v-34.0033h8.307l13.125 18.7184v-18.7184zm28.454 21.5428c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.528 0c0-3.4336-1.496-5.8703-4.209-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.209-2.3813 4.209-5.8149zm13.184 3.8766v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm37.027 8.5839h-8.806v-34.0033h23.924v7.6978h-15.118v6.7564h13.9v7.5316h-13.9zm41.876-12.4605c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm35.337-12.4605v24.921h-8.695v-2.16c-1.329 1.551-3.821 2.714-6.646 2.714-5.482 0-8.75-3.5999-8.75-9.1379v-16.3371h8.64v14.288c0 2.1045.997 3.5997 3.212 3.5997 1.606 0 3.101-1.0522 3.544-2.769v-15.1187zm4.076 24.921v-24.921h8.694v2.1598c1.385-1.5506 3.822-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.639v-14.2327c0-2.049-1.053-3.5443-3.268-3.5443-1.717 0-3.156.9969-3.6 2.7136v15.0634zm44.113 0v-1.994c-1.163 1.329-3.6 2.548-6.147 2.548-7.2 0-11.132-5.8151-11.132-13.0145s3.932-13.0143 11.132-13.0143c2.547 0 4.984 1.2184 6.147 2.5475v-13.0697h8.695v35.997zm0-9.1931v-6.5902c-.665-1.3291-2.16-2.326-3.821-2.326-2.991 0-4.763 2.4368-4.763 5.6488s1.772 5.5934 4.763 5.5934c1.717 0 3.156-.9415 3.821-2.326z" id="error-description"/></svg>
      </header>
      <article>
        <p><strong>The page you were looking for doesn't exist.</strong> You may have mistyped the address or the page may have moved. If you're the application owner check the logs for more information.</p>
      </article>
    </main>

  </body>

</html>


================================================
FILE: test/dummy/public/406-unsupported-browser.html
================================================
<!doctype html>

<html lang="en">

  <head>

    <title>Your browser is not supported (406 Not Acceptable)</title>

    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, width=device-width">
    <meta name="robots" content="noindex, nofollow">

    <style>

      *, *::before, *::after {
        box-sizing: border-box;
      }

      * {
        margin: 0;
      }

      html {
        font-size: 16px;
      }

      body {
        background: #FFF;
        color: #261B23;
        display: grid;
        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
        font-size: clamp(1rem, 2.5vw, 2rem);
        -webkit-font-smoothing: antialiased;
        font-style: normal;
        font-weight: 400;
        letter-spacing: -0.0025em;
        line-height: 1.4;
        min-height: 100dvh;
        place-items: center;
        text-rendering: optimizeLegibility;
        -webkit-text-size-adjust: 100%;
      }

      #error-description {
        fill: #d30001;
      }

      #error-id {
        fill: #f0eff0;
      }

      @media (prefers-color-scheme: dark) {
        body {
          background: #101010;
          color: #e0e0e0;
        }

        #error-description {
          fill: #FF6161;
        }

        #error-id {
          fill: #2c2c2c;
        }
      }

      a {
        color: inherit;
        font-weight: 700;
        text-decoration: underline;
        text-underline-offset: 0.0925em;
      }

      b, strong {
        font-weight: 700;
      }

      i, em {
        font-style: italic;
      }

      main {
        display: grid;
        gap: 1em;
        padding: 2em;
        place-items: center;
        text-align: center;
      }

      main header {
        width: min(100%, 12em);
      }

      main header svg {
        height: auto;
        max-width: 100%;
        width: 100%;
      }

      main article {
        width: min(100%, 30em);
      }

      main article p {
        font-size: 75%;
      }

      main article br {
        display: none;

        @media(min-width: 48em) {
          display: inline;
        }
      }

    </style>

  </head>

  <body>

    <!-- This file lives in public/406-unsupported-browser.html -->

    <main>
      <header>
        <svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm202.906 9.7326h-41.093c-2.433-7.2994-7.84-12.4361-17.302-12.4361-16.221 0-25.413 17.5728-25.954 34.8752v1.3517c5.137-7.0291 16.221-12.4361 30.82-12.4361 33.524 0 54.881 24.0612 54.881 53.7998 0 33.253-23.791 58.396-61.64 58.396-21.628 0-39.741-10.003-50.825-27.576-9.733-14.599-13.788-32.442-13.788-54.3406 0-51.9072 24.331-89.485807 66.236-89.485807 32.712 0 53.258 18.654107 58.665 47.851907zm-82.727 66.2355c0 13.247 9.463 22.439 22.71 22.439 12.977 0 22.439-9.192 22.439-22.439 0-13.517-9.462-22.7091-22.439-22.7091-13.247 0-22.71 9.1921-22.71 22.7091z" id="error-id"/><path d="m100.761 68.9967v34.0033h-7.1991l-14.2326-19.8814v19.8814h-8.5839v-34.0033h8.307l13.125 18.7184v-18.7184zm28.454 21.5428c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm13.185 3.8766v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm39.02-25.4194h9.083l12.958 34.0033h-9.027l-2.436-6.5902h-12.35l-2.381 6.5902h-8.806zm4.431 10.5222-3.489 9.5807h6.978zm17.44 11.0206c0-7.6978 5.095-13.0143 12.572-13.0143 6.701 0 10.854 3.9874 11.574 9.8023h-8.418c-.221-1.4953-1.384-2.6029-3.156-2.6029-2.437 0-3.988 2.2706-3.988 5.8149s1.551 5.7595 3.988 5.7595c1.772 0 2.935-1.0522 3.156-2.5475h8.418c-.72 5.7596-4.873 9.8025-11.574 9.8025-7.477 0-12.572-5.3167-12.572-13.0145zm25.676 0c0-7.6978 5.095-13.0143 12.572-13.0143 6.701 0 10.854 3.9874 11.574 9.8023h-8.418c-.221-1.4953-1.384-2.6029-3.156-2.6029-2.437 0-3.988 2.2706-3.988 5.8149s1.551 5.7595 3.988 5.7595c1.772 0 2.935-1.0522 3.156-2.5475h8.418c-.72 5.7596-4.873 9.8025-11.574 9.8025-7.477 0-12.572-5.3167-12.572-13.0145zm42.013 3.7658h8.031c-.887 5.7597-5.206 9.2487-11.686 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.317-13.0143 12.516-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.319 4.5965 1.773 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm23.4 16.7244v10.799h-8.694v-33.726h8.694v1.9937c1.163-1.3291 3.6-2.5475 6.148-2.5475 7.199 0 11.131 5.8703 11.131 13.0143 0 7.0886-3.932 13.0145-11.131 13.0145-2.548 0-4.985-1.219-6.148-2.548zm0-13.7893v6.5902c.665 1.3845 2.16 2.326 3.822 2.326 2.99 0 4.762-2.3814 4.762-5.5934s-1.772-5.6488-4.762-5.6488c-1.717 0-3.157.9969-3.822 2.326zm21.892 7.1994v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.942 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm39.458 8.5839h-8.363v-1.274c-.83.831-3.322 1.717-5.981 1.717-4.928 0-9.082-2.769-9.082-8.0301 0-4.818 4.154-7.9193 9.581-7.9193 2.049 0 4.486.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.045-2.9905-1.606 0-2.548.7199-2.936 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.553-1.0522-2.049-1.7167-3.655-1.7167-1.716 0-3.433.7199-3.433 2.3813 0 1.7168 1.717 2.4367 3.433 2.4367 1.606 0 3.102-.6645 3.655-1.6614zm20.742 4.9839v1.994h-8.694v-35.997h8.694v13.0697c1.163-1.3291 3.6-2.5475 6.148-2.5475 7.199 0 11.131 5.8149 11.131 13.0143s-3.932 13.0145-11.131 13.0145c-2.548 0-4.985-1.219-6.148-2.548zm0-13.7893v6.5902c.665 1.3845 2.105 2.326 3.822 2.326 2.99 0 4.762-2.3814 4.762-5.5934s-1.772-5.6488-4.762-5.6488c-1.662 0-3.157.9969-3.822 2.326zm28.759-20.2137v35.997h-8.695v-35.997zm19.172 27.3023h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.384-3.5997-3.544-3.5997z" id="error-description"/></svg>
      </header>
      <article>
        <p><strong>Your browser is not supported.</strong><br> Please upgrade your browser to continue.</p>
      </article>
    </main>

  </body>

</html>


================================================
FILE: test/dummy/public/422.html
================================================
<!doctype html>

<html lang="en">

  <head>

    <title>The change you wanted was rejected (422 Unprocessable Entity)</title>

    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, width=device-width">
    <meta name="robots" content="noindex, nofollow">

    <style>

      *, *::before, *::after {
        box-sizing: border-box;
      }

      * {
        margin: 0;
      }

      html {
        font-size: 16px;
      }

      body {
        background: #FFF;
        color: #261B23;
        display: grid;
        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
        font-size: clamp(1rem, 2.5vw, 2rem);
        -webkit-font-smoothing: antialiased;
        font-style: normal;
        font-weight: 400;
        letter-spacing: -0.0025em;
        line-height: 1.4;
        min-height: 100dvh;
        place-items: center;
        text-rendering: optimizeLegibility;
        -webkit-text-size-adjust: 100%;
      }

      #error-description {
        fill: #d30001;
      }

      #error-id {
        fill: #f0eff0;
      }

      @media (prefers-color-scheme: dark) {
        body {
          background: #101010;
          color: #e0e0e0;
        }

        #error-description {
          fill: #FF6161;
        }

        #error-id {
          fill: #2c2c2c;
        }
      }

      a {
        color: inherit;
        font-weight: 700;
        text-decoration: underline;
        text-underline-offset: 0.0925em;
      }

      b, strong {
        font-weight: 700;
      }

      i, em {
        font-style: italic;
      }

      main {
        display: grid;
        gap: 1em;
        padding: 2em;
        place-items: center;
        text-align: center;
      }

      main header {
        width: min(100%, 12em);
      }

      main header svg {
        height: auto;
        max-width: 100%;
        width: 100%;
      }

      main article {
        width: min(100%, 30em);
      }

      main article p {
        font-size: 75%;
      }

      main article br {
        display: none;

        @media(min-width: 48em) {
          display: inline;
        }
      }

    </style>

  </head>

  <body>

    <!-- This file lives in public/422.html -->

    <main>
      <header>
        <svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm130.453 51.63681c0-8.9215-6.218-15.4099-15.681-15.4099-10.273 0-15.95 7.5698-16.491 16.4913h-44.608c3.244-30.8199 25.683-55.421707 61.099-55.421707 36.498 0 59.477 20.816907 59.477 51.636807 0 21.3577-14.869 36.7676-31.901 52.7186l-27.305 27.035h59.747v37.308h-120.306v-27.846l57.044-56.7736c11.084-11.8954 18.925-20.0059 18.925-29.7385zm140.455 0c0-8.9215-6.218-15.4099-15.68-15.4099-10.274 0-15.951 7.5698-16.492 16.4913h-44.608c3.245-30.8199 25.684-55.421707 61.1-55.421707 36.497 0 59.477 20.816907 59.477 51.636807 0 21.3577-14.87 36.7676-31.902 52.7186l-27.305 27.035h59.747v37.308h-120.305v-27.846l57.043-56.7736c11.085-11.8954 18.925-20.0059 18.925-29.7385z" id="error-id"/><path d="m19.3936 103.554c-8.9715 0-14.84183-5.0952-14.84183-14.4544v-20.1029h8.86083v19.3276c0 4.8181 2.2706 7.3102 5.981 7.3102 3.6551 0 5.9257-2.4921 5.9257-7.3102v-19.3276h8.8608v20.1583c0 9.3038-5.8149 14.399-14.7865 14.399zm18.734-.554v-24.921h8.6947v2.1598c1.3845-1.5506 3.8212-2.7136 6.701-2.7136 5.538 0 8.8054 3.5997 8.8054 9.1377v16.3371h-8.6393v-14.2327c0-2.049-1.0522-3.5443-3.2674-3.5443-1.7168 0-3.1567.9969-3.5997 2.7136v15.0634zm36.8584-1.994v10.799h-8.6946v-33.726h8.6946v1.9937c1.163-1.3291 3.5997-2.5475 6.1472-2.5475 7.1994 0 11.1314 5.8703 11.1314 13.0143 0 7.0886-3.932 13.0145-11.1314 13.0145-2.5475 0-4.9842-1.219-6.1472-2.548zm0-13.7893v6.5902c.6646 1.3845 2.1599 2.326 3.8213 2.326 2.9905 0 4.7626-2.3814 4.7626-5.5934s-1.7721-5.6488-4.7626-5.6488c-1.7168 0-3.1567.9969-3.8213 2.326zm36.789-9.2485v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.6949v-24.921h8.6949v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm26.769 12.5713c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.528 0c0-3.4336-1.496-5.8703-4.209-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.209-2.3813 4.209-5.8149zm10.352 0c0-7.6978 5.095-13.0143 12.571-13.0143 6.701 0 10.855 3.9874 11.574 9.8023h-8.417c-.222-1.4953-1.385-2.6029-3.157-2.6029-2.437 0-3.987 2.2706-3.987 5.8149s1.55 5.7595 3.987 5.7595c1.772 0 2.935-1.0522 3.157-2.5475h8.417c-.719 5.7596-4.873 9.8025-11.574 9.8025-7.476 0-12.571-5.3167-12.571-13.0145zm42.013 3.7658h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.544-3.5997zm13.428 11.0206h8.473c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.938-1.5506l-4.874-.9969c-4.153-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.763-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.74-8.2518zm24.269 0h8.474c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.939-1.5506l-4.873-.9969c-4.154-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.763-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.741-8.2518zm47.918 7.6978h-8.363v-1.274c-.831.831-3.323 1.717-5.981 1.717-4.929 0-9.082-2.769-9.082-8.0301 0-4.818 4.153-7.9193 9.581-7.9193 2.049 0 4.485.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.046-2.9905-1.606 0-2.547.7199-2.935 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.434.7199-3.434 2.3813 0 1.7168 1.717 2.4367 3.434 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm20.742 4.9839v1.994h-8.695v-35.997h8.695v13.0697c1.163-1.3291 3.6-2.5475 6.147-2.5475 7.2 0 11.132 5.8149 11.132 13.0143s-3.932 13.0145-11.132 13.0145c-2.547 0-4.984-1.219-6.147-2.548zm0-13.7893v6.5902c.665 1.3845 2.105 2.326 3.821 2.326 2.991 0 4.763-2.3814 4.763-5.5934s-1.772-5.6488-4.763-5.6488c-1.661 0-3.156.9969-3.821 2.326zm28.759-20.2137v35.997h-8.695v-35.997zm19.172 27.3023h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm25.461-15.2849h24.311v7.6424h-15.561v5.3165h14.232v7.4763h-14.232v5.8703h15.561v7.6978h-24.311zm27.942 34.0033v-24.921h8.694v2.1598c1.385-1.5506 3.822-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.639v-14.2327c0-2.049-1.053-3.5443-3.268-3.5443-1.717 0-3.157.9969-3.6 2.7136v15.0634zm29.991-8.5839v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.942 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm26.161-16.3371v24.921h-8.694v-24.921zm.61-6.7564c0 2.8244-2.271 4.652-4.929 4.652s-4.929-1.8276-4.929-4.652c0-2.8797 2.271-4.7073 4.929-4.7073s4.929 1.8276 4.929 4.7073zm5.382 23.0935v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm29.22 17.3889h-8.584l3.655-9.414-9.303-24.312h9.026l4.763 14.1773 4.652-14.1773h8.639z" id="error-description"/></svg>
      </header>
      <article>
        <p><strong>The change you wanted was rejected.</strong> Maybe you tried to change something you didn't have access to. If you're the application owner check the logs for more information.</p>
      </article>
    </main>

  </body>

</html>


================================================
FILE: test/dummy/public/500.html
================================================
<!doctype html>

<html lang="en">

  <head>

    <title>We're sorry, but something went wrong (500 Internal Server Error)</title>

    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, width=device-width">
    <meta name="robots" content="noindex, nofollow">

    <style>

      *, *::before, *::after {
        box-sizing: border-box;
      }

      * {
        margin: 0;
      }

      html {
        font-size: 16px;
      }

      body {
        background: #FFF;
        color: #261B23;
        display: grid;
        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
        font-size: clamp(1rem, 2.5vw, 2rem);
        -webkit-font-smoothing: antialiased;
        font-style: normal;
        font-weight: 400;
        letter-spacing: -0.0025em;
        line-height: 1.4;
        min-height: 100dvh;
        place-items: center;
        text-rendering: optimizeLegibility;
        -webkit-text-size-adjust: 100%;
      }

      #error-description {
        fill: #d30001;
      }

      #error-id {
        fill: #f0eff0;
      }

      @media (prefers-color-scheme: dark) {
        body {
          background: #101010;
          color: #e0e0e0;
        }

        #error-description {
          fill: #FF6161;
        }

        #error-id {
          fill: #2c2c2c;
        }
      }

      a {
        color: inherit;
        font-weight: 700;
        text-decoration: underline;
        text-underline-offset: 0.0925em;
      }

      b, strong {
        font-weight: 700;
      }

      i, em {
        font-style: italic;
      }

      main {
        display: grid;
        gap: 1em;
        padding: 2em;
        place-items: center;
        text-align: center;
      }

      main header {
        width: min(100%, 12em);
      }

      main header svg {
        height: auto;
        max-width: 100%;
        width: 100%;
      }

      main article {
        width: min(100%, 30em);
      }

      main article p {
        font-size: 75%;
      }

      main article br {
        display: none;

        @media(min-width: 48em) {
          display: inline;
        }
      }

    </style>

  </head>

  <body>

    <!-- This file lives in public/500.html -->

    <main>
      <header>
        <svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m101.23 93.8427c-8.1103 0-15.4098 3.7849-19.7354 8.3813h-36.2269v-99.21891h103.8143v37.03791h-68.3984v24.8722c5.1366-2.7035 15.1396-5.9477 24.6014-5.9477 35.146 0 56.233 22.7094 56.233 55.4215 0 34.605-23.791 57.315-60.558 57.315-37.8492 0-61.64-22.169-63.8028-55.963h42.9857c1.0814 10.814 9.1919 19.195 21.6281 19.195 11.355 0 19.465-8.381 19.465-20.547 0-11.625-7.299-20.5463-20.006-20.5463zm138.833 77.8613c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm140.456 133.2831c-40.823 0-64.884-35.146-64.884-85.7015 0-50.5554 24.061-85.700907 64.884-85.700907 40.822 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.062 85.7015-64.884 85.7015zm0-133.2831c-17.573 0-22.71 21.8984-22.71 47.5816 0 25.6835 5.137 47.5815 22.71 47.5815 17.302 0 22.709-21.898 22.709-47.5815 0-25.6832-5.407-47.5816-22.709-47.5816z" id="error-id"/><path d="m23.1377 68.9967v34.0033h-8.9162v-34.0033zm4.3157 34.0033v-24.921h8.6947v2.1598c1.3845-1.5506 3.8212-2.7136 6.701-2.7136 5.538 0 8.8054 3.5997 8.8054 9.1377v16.3371h-8.6393v-14.2327c0-2.049-1.0522-3.5443-3.2674-3.5443-1.7168 0-3.1567.9969-3.5997 2.7136v15.0634zm29.9913-8.5839v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.5839v6.8671h5.2058v6.7564h-5.2058v8.307c0 1.9383.9415 2.769 2.6583 2.769.9414 0 1.9937-.2216 2.769-.5538v7.3654c-.9969.443-2.8798.775-4.8181.775-5.8703 0-9.1931-2.769-9.1931-9.0819zm32.3666-.1108h8.0301c-.8861 5.7597-5.2057 9.2487-11.6852 9.2487-7.6424 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.3165-13.0143 12.5159-13.0143 7.6424 0 11.9621 5.095 11.9621 12.5159v2.1598h-16.1156c.2769 2.9905 1.8275 4.5965 4.3196 4.5965 1.7722 0 3.1567-.7753 3.6551-2.4921zm-3.8212-10.0237c-2.0491 0-3.4336 1.2737-3.9874 3.5997h7.5317c-.1107-2.0491-1.3845-3.5997-3.5443-3.5997zm31.4299-6.3134v8.3624c-1.052-.5538-2.215-.7753-3.599-.7753-2.382 0-3.988 1.0522-4.431 2.8244v14.6203h-8.694v-24.921h8.694v2.2152c1.219-1.6614 3.157-2.769 5.649-2.769 1.108 0 1.994.2215 2.381.443zm2.949 25.0318v-24.921h8.694v2.1598c1.385-1.5506 3.821-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.64v-14.2327c0-2.049-1.052-3.5443-3.267-3.5443-1.717 0-3.157.9969-3.6 2.7136v15.0634zm50.371 0h-8.363v-1.274c-.83.831-3.323 1.717-5.981 1.717-4.929 0-9.082-2.769-9.082-8.0301 0-4.818 4.153-7.9193 9.581-7.9193 2.049 0 4.485.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.046-2.9905-1.606 0-2.547.7199-2.935 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.433.7199-3.433 2.3813 0 1.7168 1.716 2.4367 3.433 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm20.742-29.0191v35.997h-8.694v-35.997zm13.036 25.9178h9.248c.72 2.326 2.714 3.489 5.483 3.489 2.713 0 4.596-1.163 4.596-3.2674 0-1.6061-1.052-2.326-3.212-2.8244l-6.534-1.3845c-4.985-1.1076-8.751-3.7105-8.751-9.47 0-6.6456 5.538-11.0206 13.07-11.0206 8.307 0 13.014 4.5411 13.956 10.4114h-8.695c-.72-1.8829-2.27-3.3228-5.205-3.3228-2.548 0-4.265 1.1076-4.265 2.9905 0 1.4953 1.052 2.326 2.825 2.7137l6.645 1.5506c5.815 1.3845 9.027 4.5412 9.027 9.8023 0 6.9778-5.87 10.9654-13.291 10.9654-8.141 0-13.679-3.9322-14.897-10.6332zm46.509 1.3845h8.031c-.887 5.7597-5.206 9.2487-11.686 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.317-13.0143 12.516-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.319 4.5965 1.773 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm31.431-6.3134v8.3624c-1.053-.5538-2.216-.7753-3.6-.7753-2.381 0-3.988 1.0522-4.431 2.8244v14.6203h-8.694v-24.921h8.694v2.2152c1.219-1.6614 3.157-2.769 5.649-2.769 1.108 0 1.994.2215 2.382.443zm18.288 25.0318h-7.809l-9.47-24.921h8.861l4.763 14.288 4.652-14.288h8.528zm25.614-8.6947h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.384-3.5997-3.544-3.5997zm31.43-6.3134v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.157-2.769 5.649-2.769 1.107 0 1.993.2215 2.381.443zm13.703-8.9715h24.312v7.6424h-15.562v5.3165h14.232v7.4763h-14.232v5.8703h15.562v7.6978h-24.312zm44.667 8.9715v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm19.673 0v8.3624c-1.053-.5538-2.216-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm26.769 12.5713c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm28.082-12.5713v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.157-2.769 5.649-2.769 1.107 0 1.993.2215 2.381.443z" id="error-description"/></svg>
      </header>
      <article>
        <p><strong>We're sorry, but something went wrong.</strong><br> If you're the application owner check the logs for more information.</p>
      </article>
    </main>

  </body>

</html>


================================================
FILE: test/jobs/trim_job_test.rb
================================================
# frozen_string_literal: true

require "test_helper"
require "config_stubs"

class TrimJobTest < ActiveJob::TestCase
  include ConfigStubs

  test "trims a limited number of messages" do
    SolidCable.stub(:trim_chance, 99.999) do
      with_cable_config trim_batch_size: 2, message_rention: "1.second" do
        4.times do
          SolidCable::Message.broadcast("foo", "bar")
          SolidCable::Message.update_all(created_at: 2.days.ago)
        end

        assert_difference -> { SolidCable::Message.count }, -2 do
          SolidCable::TrimJob.perform_now
        end
      end
    end
  end

  test "trims when out of band with autotrim disabled" do
    SolidCable.stub(:trim_chance, 0) do
      with_cable_config autotrim: false, trim_batch_size: 2, message_rention: "1.second" do
        4.times do
          SolidCable::Message.broadcast("foo", "bar")
          SolidCable::Message.update_all(created_at: 2.days.ago)
        end

        assert_difference -> { SolidCable::Message.count }, -2 do
          SolidCable::TrimJob.perform_now
        end
      end
    end
  end
end


================================================
FILE: test/lib/action_cable/subscription_adapter/solid_cable_test.rb
================================================
# frozen_string_literal: true

require "test_helper"
require "concurrent"

require "active_support/core_ext/hash/indifferent_access"
require "pathname"
require "config_stubs"

class ActionCable::SubscriptionAdapter::SolidCableTest < ActionCable::TestCase
  include ConfigStubs

  WAIT_WHEN_EXPECTING_EVENT = 1
  WAIT_WHEN_NOT_EXPECTING_EVENT = 0.2

  setup do
    server = ActionCable::Server::Base.new
    server.config.cable = cable_config.with_indifferent_access
    server.config.logger = Logger.new(StringIO.new).tap do |l|
      l.level = Logger::UNKNOWN
    end

    adapter_klass = server.config.pubsub_adapter

    @rx_adapter = adapter_klass.new(server)
    @tx_adapter = adapter_klass.new(server)

    @tx_adapter.shutdown
    @tx_adapter = @rx_adapter
  end

  teardown do
    [@rx_adapter, @tx_adapter].uniq.compact.each(&:shutdown)
  end

  test "subscribe_and_unsubscribe" do
    subscribe_as_queue("channel") do |queue|
    end
  end

  test "basic_broadcast" do
    subscribe_as_queue("channel") do |queue|
      @tx_adapter.broadcast("channel", "hello world")

      assert_equal "hello world", next_message_in_queue(queue)
    end
  end

  test "broadcast_after_unsubscribe" do
    keep_queue = nil
    subscribe_as_queue("channel") do |queue|
      keep_queue = queue

      @tx_adapter.broadcast("channel", "hello world")

      assert_equal "hello world", next_message_in_queue(queue)
    end

    @tx_adapter.broadcast("channel", "hello void")

    sleep WAIT_WHEN_NOT_EXPECTING_EVENT
    assert_empty keep_queue
  end

  test "trims_after_unsubscribe" do
    SolidCable.stub(:trim_chance, 99.999999) do
      with_cable_config message_retention: "2.seconds", trim_batch_size: 2 do
        subscribe_as_queue("channel") do |queue|
          4.times do
            @tx_adapter.broadcast("channel", "hello world")
            sleep 3
          end

          queue.clear
        end
        assert_equal 1, SolidCable::Message.where(channel: "channel").count
      end
    end
  end

  test "multiple_broadcast" do
    subscribe_as_queue("channel") do |queue|
      @tx_adapter.broadcast("channel", "bananas")
      @tx_adapter.broadcast("channel", "apples")

      received = []
      2.times { received << next_message_in_queue(queue) }
      assert_equal %w(apples bananas), received.sort
    end
  end

  test "identical_subscriptions" do
    subscribe_as_queue("channel") do |queue|
      subscribe_as_queue("channel") do |queue_2|
        @tx_adapter.broadcast("channel", "hello")

        assert_equal "hello", next_message_in_queue(queue_2)
      end

      assert_equal "hello", next_message_in_queue(queue)
    end
  end

  test "simultaneous_subscriptions" do
    subscribe_as_queue("channel") do |queue|
      subscribe_as_queue("other channel") do |queue_2|
        @tx_adapter.broadcast("channel", "apples")
        @tx_adapter.broadcast("other channel", "oranges")

        assert_equal "apples", next_message_in_queue(queue)
        assert_equal "oranges", next_message_in_queue(queue_2)
      end
    end
  end

  test "channel_filtered_broadcast" do
    subscribe_as_queue("channel") do |queue|
      @tx_adapter.broadcast("other channel", "one")
      @tx_adapter.broadcast("channel", "two")

      assert_equal "two", next_message_in_queue(queue)
    end
  end

  test "long_identifiers" do
    channel_1 = "#{'a' * 100}1"
    channel_2 = "#{'a' * 100}2"
    subscribe_as_queue(channel_1) do |queue|
      subscribe_as_queue(channel_2) do |queue_2|
        @tx_adapter.broadcast(channel_1, "apples")
        @tx_adapter.broadcast(channel_2, "oranges")

        assert_equal "apples", next_message_in_queue(queue)
        assert_equal "oranges", next_message_in_queue(queue_2)
      end
    end
  end

  test "does not raise error when polling with no Active Record logger" do
    with_active_record_logger(nil) do
      assert_nothing_raised do
        subscribe_as_queue("channel") do |queue|
          @tx_adapter.broadcast("channel", "hello world")

          assert_equal "hello world", next_message_in_queue(queue)
        end
      end
    end
  end

  test "does not send old messages" do
    @tx_adapter.broadcast("channel", "channel1")
    @tx_adapter.broadcast("channel", "channel2")

    subscribe_as_queue("channel") do |queue|
      assert_empty queue

      @tx_adapter.broadcast("channel", "channel3")
      @tx_adapter.broadcast("channel", "channel4")
      @tx_adapter.broadcast("other", "other1")
      @tx_adapter.broadcast("other", "other2")

      subscribe_as_queue("other") do |other_queue|
        assert_empty other_queue
      end
      assert_equal "channel3", next_message_in_queue(queue)
      assert_equal "channel4", next_message_in_queue(queue)
    end

    @tx_adapter.broadcast("channel", "channel5")
    @tx_adapter.broadcast("channel", "channel6")

    subscribe_as_queue("channel") do |queue|
      assert_empty queue
    end
  end

  test "retries after a connection failure and keeps listening" do
    with_cable_config reconnect_attempts: [0] do
      raised = false
      original = SolidCable::Message.method(:broadcastable)

      SolidCable::Message.stub(:broadcastable, lambda { |channels, last_id|
        if raised
          original.call(channels, last_id)
        else
          raised = true
          raise ActiveRecord::ConnectionFailed, "boom"
        end
      }) do
        subscribe_as_queue("reconnect-channel") do |queue|
          @tx_adapter.broadcast("reconnect-channel", "hello")

          assert_equal "hello", next_message_in_queue(queue)
        end
      end

      assert raised
    end
  end

  private
    def cable_config
      { adapter: "solid_cable", message_retention: "1.second",
        polling_interval: "0.01.seconds" }
    end

    def subscribe_as_queue(channel, adapter = @rx_adapter)
      queue = Queue.new

      callback = ->(data) { queue << data }
      subscribed = Concurrent::Event.new
      adapter.subscribe(channel, callback, proc { subscribed.set })
      subscribed.wait(WAIT_WHEN_EXPECTING_EVENT)
      assert_predicate subscribed, :set?

      yield queue

      sleep WAIT_WHEN_NOT_EXPECTING_EVENT
      assert_empty queue
    ensure
      adapter.unsubscribe(channel, callback) if subscribed.set?
    end

    def with_active_record_logger(logger)
      old_logger = ActiveRecord::Base.logger
      ActiveRecord::Base.logger = logger
      yield
    ensure
      ActiveRecord::Base.logger = old_logger
    end

    def next_message_in_queue(queue)
      Timeout.timeout(5, nil, "Failed to get next item in queue") { queue.pop }
    end
end


================================================
FILE: test/lib/generators/solid_cable/install/install_generator_test.rb
================================================
require "test_helper"
require_relative "../../../../../lib/generators/solid_cable/install/install_generator"

class SolidCable::InstallGeneratorTest < Rails::Generators::TestCase
  tests SolidCable::InstallGenerator
  destination File.expand_path("../../../../../tmp", __dir__)

  setup :prepare_destination
  setup :run_generator

  test "cable_schema exists" do
    assert_file "db/cable_schema.rb"
  end

  test "cable.yml exists" do
    assert_file "config/cable.yml"
  end
end


================================================
FILE: test/lib/generators/solid_cable/update/update_generator_test.rb
================================================
require "test_helper"
require_relative "../../../../../lib/generators/solid_cable/update/update_generator"

class SolidCable::UpdateGeneratorTest < Rails::Generators::TestCase
  tests SolidCable::UpdateGenerator
  destination File.expand_path("../../../../../tmp", __dir__)

  setup :prepare_destination
  setup :run_generator

  test "cable_schema exists" do
    assert_migration "db/cable_migrate/create_compact_channel.rb"
  end
end


================================================
FILE: test/solid_cable_test.rb
================================================
# frozen_string_literal: true

require "test_helper"
require "config_stubs"

class SolidCableTest < ActiveSupport::TestCase
  include ConfigStubs

  test "it has a version number" do
    assert SolidCable::VERSION
  end

  test "autotrimming when nothing is set" do
    assert_not Rails.application.config_for("cable").key?(:autotrim)
    assert SolidCable.autotrim?
  end

  test "autotrimming when set to false" do
    with_cable_config autotrim: false do
      assert_not SolidCable.autotrim?
    end
  end

  test "autotrimming when set to true" do
    with_cable_config autotrim: true do
      assert SolidCable.autotrim?
    end
  end

  test "default trim_batch_size is 100" do
    assert_equal 100, SolidCable.trim_batch_size
  end

  test "trim_batch_size when set badly" do
    with_cable_config trim_batch_size: "weird" do
      assert_equal 100, SolidCable.trim_batch_size
    end

    with_cable_config trim_batch_size: "0" do
      assert_equal 100, SolidCable.trim_batch_size
    end
  end

  test "trim_batch_size when set" do
    with_cable_config trim_batch_size: 42 do
      assert_equal 42, SolidCable.trim_batch_size
    end
  end

  test "reconnect_attempts defaults to a single zero" do
    assert_equal [ 0 ], SolidCable.reconnect_attempts
  end

  test "reconnect_attempts accepts an integer" do
    with_cable_config reconnect_attempts: 3 do
      assert_equal [ 0, 0, 0 ], SolidCable.reconnect_attempts
    end
  end

  test "reconnect_attempts accepts an array" do
    with_cable_config reconnect_attempts: [ 0, 1, 2 ] do
      assert_equal [ 0, 1, 2 ], SolidCable.reconnect_attempts
    end
  end
end


================================================
FILE: test/test_helper.rb
================================================
# frozen_string_literal: true

# Configure Rails Environment
ENV["RAILS_ENV"] = "test"

require_relative "../test/dummy/config/environment"
ActiveRecord::Migrator.migrations_paths = [ File.expand_path(
"../test/dummy/db/migrate", __dir__
) ]
require "rails/test_help"
require "minitest/autorun"

# Load fixtures from the engine
if ActiveSupport::TestCase.respond_to?(:fixture_paths=)
  ActiveSupport::TestCase.fixture_paths =
    [ File.expand_path("fixtures", __dir__) ]
  ActionDispatch::IntegrationTest.fixture_paths =
    ActiveSupport::TestCase.fixture_paths
  ActiveSupport::TestCase.file_fixture_path =
    "#{File.expand_path('fixtures', __dir__)}/files"
  ActiveSupport::TestCase.fixtures :all
end
Download .txt
gitextract_sjfrw0w9/

├── .github/
│   └── workflows/
│       ├── lint.yml
│       └── test.yml
├── .gitignore
├── .rubocop.yml
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app/
│   ├── jobs/
│   │   └── solid_cable/
│   │       └── trim_job.rb
│   └── models/
│       └── solid_cable/
│           ├── message.rb
│           └── record.rb
├── bench/
│   ├── .dockerignore
│   ├── .ruby-version
│   ├── Dockerfile
│   ├── Gemfile
│   ├── Rakefile
│   ├── app/
│   │   ├── assets/
│   │   │   ├── builds/
│   │   │   │   └── .keep
│   │   │   ├── images/
│   │   │   │   └── .keep
│   │   │   └── stylesheets/
│   │   │       └── application.tailwind.css
│   │   ├── channels/
│   │   │   ├── application_cable/
│   │   │   │   ├── channel.rb
│   │   │   │   └── connection.rb
│   │   │   └── broadcast_channel.rb
│   │   ├── controllers/
│   │   │   ├── application_controller.rb
│   │   │   ├── concerns/
│   │   │   │   └── .keep
│   │   │   ├── rooms_controller.rb
│   │   │   └── ws_debugger_controller.rb
│   │   ├── javascript/
│   │   │   ├── application.js
│   │   │   ├── channels/
│   │   │   │   ├── broadcast_channel.js
│   │   │   │   ├── consumer.js
│   │   │   │   └── index.js
│   │   │   └── controllers/
│   │   │       ├── application.js
│   │   │       └── index.js
│   │   ├── jobs/
│   │   │   └── application_job.rb
│   │   ├── mailers/
│   │   │   └── application_mailer.rb
│   │   ├── models/
│   │   │   ├── application_record.rb
│   │   │   ├── concerns/
│   │   │   │   └── .keep
│   │   │   └── room.rb
│   │   └── views/
│   │       ├── layouts/
│   │       │   ├── application.html.erb
│   │       │   ├── mailer.html.erb
│   │       │   └── mailer.text.erb
│   │       ├── rooms/
│   │       │   ├── _form.html.erb
│   │       │   ├── _room.html.erb
│   │       │   ├── edit.html.erb
│   │       │   ├── index.html.erb
│   │       │   ├── new.html.erb
│   │       │   └── show.html.erb
│   │       └── ws_debugger/
│   │           └── show.html.erb
│   ├── bin/
│   │   ├── bundle
│   │   ├── docker-entrypoint
│   │   ├── importmap
│   │   ├── kamal
│   │   ├── rails
│   │   ├── rake
│   │   ├── rubocop
│   │   └── setup
│   ├── config/
│   │   ├── application.rb
│   │   ├── boot.rb
│   │   ├── cable.yml
│   │   ├── credentials.yml.enc
│   │   ├── database.yml
│   │   ├── deploy.yml
│   │   ├── environment.rb
│   │   ├── environments/
│   │   │   ├── development.rb
│   │   │   ├── production.rb
│   │   │   └── test.rb
│   │   ├── importmap.rb
│   │   ├── init.sql
│   │   ├── initializers/
│   │   │   ├── assets.rb
│   │   │   ├── content_security_policy.rb
│   │   │   ├── filter_parameter_logging.rb
│   │   │   ├── inflections.rb
│   │   │   └── permissions_policy.rb
│   │   ├── locales/
│   │   │   └── en.yml
│   │   ├── puma.rb
│   │   ├── routes.rb
│   │   ├── storage.yml
│   │   └── tailwind.config.js
│   ├── config.ru
│   ├── db/
│   │   ├── migrate/
│   │   │   ├── 20240529231225_create_rooms.rb
│   │   │   ├── 20240530031126_create_solid_cable_message.solid_cable.rb
│   │   │   ├── 20240607184931_index_channels.solid_cable.rb
│   │   │   ├── 20240609023040_create_active_error_faults.active_error.rb
│   │   │   ├── 20240609023041_create_active_error_instances.active_error.rb
│   │   │   └── 20240912235943_create_compact_channel.rb
│   │   ├── schema.rb
│   │   └── seeds.rb
│   ├── loadtest.js
│   ├── log/
│   │   └── .keep
│   ├── public/
│   │   ├── 404.html
│   │   ├── 406-unsupported-browser.html
│   │   ├── 422.html
│   │   ├── 500.html
│   │   └── robots.txt
│   ├── test/
│   │   ├── channels/
│   │   │   ├── application_cable/
│   │   │   │   └── connection_test.rb
│   │   │   └── broadcast_channel_test.rb
│   │   ├── controllers/
│   │   │   └── .keep
│   │   ├── fixtures/
│   │   │   ├── files/
│   │   │   │   └── .keep
│   │   │   └── rooms.yml
│   │   ├── models/
│   │   │   ├── .keep
│   │   │   └── room_test.rb
│   │   └── test_helper.rb
│   └── vendor/
│       ├── .keep
│       └── javascript/
│           └── .keep
├── bin/
│   ├── rails
│   ├── release
│   └── test
├── lib/
│   ├── action_cable/
│   │   └── subscription_adapter/
│   │       └── solid_cable.rb
│   ├── generators/
│   │   └── solid_cable/
│   │       ├── install/
│   │       │   ├── USAGE
│   │       │   ├── install_generator.rb
│   │       │   └── templates/
│   │       │       ├── config/
│   │       │       │   └── cable.yml
│   │       │       └── db/
│   │       │           └── cable_schema.rb
│   │       └── update/
│   │           ├── USAGE
│   │           ├── templates/
│   │           │   └── db/
│   │           │       └── migrate/
│   │           │           └── create_compact_channel.rb
│   │           └── update_generator.rb
│   ├── solid_cable/
│   │   ├── engine.rb
│   │   └── version.rb
│   ├── solid_cable.rb
│   └── tasks/
│       └── solid_cable_tasks.rake
├── solid_cable.gemspec
└── test/
    ├── config_stubs.rb
    ├── dummy/
    │   ├── Rakefile
    │   ├── app/
    │   │   ├── controllers/
    │   │   │   ├── application_controller.rb
    │   │   │   └── concerns/
    │   │   │       └── .keep
    │   │   ├── helpers/
    │   │   │   └── application_helper.rb
    │   │   ├── jobs/
    │   │   │   └── application_job.rb
    │   │   ├── models/
    │   │   │   ├── application_record.rb
    │   │   │   └── concerns/
    │   │   │       └── .keep
    │   │   └── views/
    │   │       ├── layouts/
    │   │       │   ├── application.html.erb
    │   │       │   ├── mailer.html.erb
    │   │       │   └── mailer.text.erb
    │   │       └── pwa/
    │   │           ├── manifest.json.erb
    │   │           └── service-worker.js
    │   ├── bin/
    │   │   ├── ci
    │   │   ├── dev
    │   │   ├── rails
    │   │   ├── rake
    │   │   └── setup
    │   ├── config/
    │   │   ├── application.rb
    │   │   ├── boot.rb
    │   │   ├── cable.yml
    │   │   ├── ci.rb
    │   │   ├── database.yml
    │   │   ├── environment.rb
    │   │   ├── environments/
    │   │   │   ├── development.rb
    │   │   │   ├── production.rb
    │   │   │   └── test.rb
    │   │   ├── initializers/
    │   │   │   ├── assets.rb
    │   │   │   ├── content_security_policy.rb
    │   │   │   ├── filter_parameter_logging.rb
    │   │   │   └── inflections.rb
    │   │   ├── locales/
    │   │   │   └── en.yml
    │   │   ├── puma.rb
    │   │   ├── routes.rb
    │   │   └── storage.yml
    │   ├── config.ru
    │   ├── db/
    │   │   └── schema.rb
    │   ├── log/
    │   │   └── .keep
    │   └── public/
    │       ├── 400.html
    │       ├── 404.html
    │       ├── 406-unsupported-browser.html
    │       ├── 422.html
    │       └── 500.html
    ├── jobs/
    │   └── trim_job_test.rb
    ├── lib/
    │   ├── action_cable/
    │   │   └── subscription_adapter/
    │   │       └── solid_cable_test.rb
    │   └── generators/
    │       └── solid_cable/
    │           ├── install/
    │           │   └── install_generator_test.rb
    │           └── update/
    │               └── update_generator_test.rb
    ├── solid_cable_test.rb
    └── test_helper.rb
Download .txt
SYMBOL INDEX (134 symbols across 44 files)

FILE: app/jobs/solid_cable/trim_job.rb
  type SolidCable (line 3) | module SolidCable
    class TrimJob (line 4) | class TrimJob < ActiveJob::Base
      method perform (line 5) | def perform
      method trim_batch_size (line 16) | def trim_batch_size
      method trim? (line 20) | def trim?

FILE: app/models/solid_cable/message.rb
  type SolidCable (line 3) | module SolidCable
    class Message (line 4) | class Message < SolidCable::Record
      method broadcast (line 14) | def broadcast(channel, payload)
      method channel_hashes_for (line 19) | def channel_hashes_for(channels)
      method channel_hash_for (line 25) | def channel_hash_for(channel)

FILE: app/models/solid_cable/record.rb
  type SolidCable (line 3) | module SolidCable
    class Record (line 4) | class Record < ActiveRecord::Base
      method non_blocking_lock (line 9) | def self.non_blocking_lock

FILE: bench/app/channels/application_cable/channel.rb
  type ApplicationCable (line 3) | module ApplicationCable
    class Channel (line 4) | class Channel < ActionCable::Channel::Base

FILE: bench/app/channels/application_cable/connection.rb
  type ApplicationCable (line 1) | module ApplicationCable
    class Connection (line 2) | class Connection < ActionCable::Connection::Base
      method connect (line 10) | def connect

FILE: bench/app/channels/broadcast_channel.rb
  class BroadcastChannel (line 1) | class BroadcastChannel < ApplicationCable::Channel
    method subscribed (line 2) | def subscribed
    method unsubscribed (line 7) | def unsubscribed
    method ping (line 12) | def ping(data)

FILE: bench/app/controllers/application_controller.rb
  class ApplicationController (line 1) | class ApplicationController < ActionController::Base

FILE: bench/app/controllers/rooms_controller.rb
  class RoomsController (line 1) | class RoomsController < ApplicationController
    method index (line 5) | def index
    method show (line 10) | def show
    method new (line 14) | def new
    method edit (line 19) | def edit
    method create (line 23) | def create
    method update (line 34) | def update
    method destroy (line 43) | def destroy
    method set_room (line 50) | def set_room
    method room_params (line 55) | def room_params

FILE: bench/app/controllers/ws_debugger_controller.rb
  class WsDebuggerController (line 1) | class WsDebuggerController < ApplicationController

FILE: bench/app/javascript/channels/broadcast_channel.js
  method connected (line 4) | connected() {
  method disconnected (line 8) | disconnected() {
  method received (line 12) | received(data) {

FILE: bench/app/jobs/application_job.rb
  class ApplicationJob (line 1) | class ApplicationJob < ActiveJob::Base

FILE: bench/app/mailers/application_mailer.rb
  class ApplicationMailer (line 1) | class ApplicationMailer < ActionMailer::Base

FILE: bench/app/models/application_record.rb
  class ApplicationRecord (line 1) | class ApplicationRecord < ActiveRecord::Base

FILE: bench/app/models/room.rb
  class Room (line 1) | class Room < ApplicationRecord

FILE: bench/config/application.rb
  type Cablebench (line 9) | module Cablebench
    class Application (line 10) | class Application < Rails::Application

FILE: bench/db/migrate/20240529231225_create_rooms.rb
  class CreateRooms (line 1) | class CreateRooms < ActiveRecord::Migration[8.0]
    method change (line 2) | def change

FILE: bench/db/migrate/20240530031126_create_solid_cable_message.solid_cable.rb
  class CreateSolidCableMessage (line 4) | class CreateSolidCableMessage < ActiveRecord::Migration[7.1]
    method change (line 5) | def change

FILE: bench/db/migrate/20240607184931_index_channels.solid_cable.rb
  class IndexChannels (line 2) | class IndexChannels < ActiveRecord::Migration[7.1]
    method change (line 3) | def change

FILE: bench/db/migrate/20240609023040_create_active_error_faults.active_error.rb
  class CreateActiveErrorFaults (line 4) | class CreateActiveErrorFaults < ActiveRecord::Migration[7.1]
    method change (line 5) | def change # rubocop:disable Metrics/AbcSize

FILE: bench/db/migrate/20240609023041_create_active_error_instances.active_error.rb
  class CreateActiveErrorInstances (line 4) | class CreateActiveErrorInstances < ActiveRecord::Migration[7.1]
    method change (line 5) | def change

FILE: bench/db/migrate/20240912235943_create_compact_channel.rb
  class CreateCompactChannel (line 3) | class CreateCompactChannel < ActiveRecord::Migration[7.2]
    method up (line 4) | def up
    method down (line 16) | def down

FILE: bench/loadtest.js
  constant WS_URL (line 8) | const WS_URL = __ENV.WS_URL || "wss://solid-cable.dev/cable";
  constant WS_COOKIE (line 9) | const WS_COOKIE = __ENV.WS_COOKIE;
  constant MAX (line 10) | const MAX = parseInt(__ENV.MAX || "20");
  constant TIME (line 12) | const TIME = parseInt(__ENV.TIME || "90");
  constant MESSAGES_NUM (line 13) | const MESSAGES_NUM = parseInt(__ENV.NUM || "5");

FILE: bench/test/channels/application_cable/connection_test.rb
  type ApplicationCable (line 3) | module ApplicationCable
    class ConnectionTest (line 4) | class ConnectionTest < ActionCable::Connection::TestCase

FILE: bench/test/channels/broadcast_channel_test.rb
  class BroadcastChannelTest (line 3) | class BroadcastChannelTest < ActionCable::Channel::TestCase

FILE: bench/test/models/room_test.rb
  class RoomTest (line 3) | class RoomTest < ActiveSupport::TestCase

FILE: bench/test/test_helper.rb
  type ActiveSupport (line 5) | module ActiveSupport
    class TestCase (line 6) | class TestCase

FILE: lib/action_cable/subscription_adapter/solid_cable.rb
  type ActionCable (line 8) | module ActionCable
    type SubscriptionAdapter (line 9) | module SubscriptionAdapter
      class SolidCable (line 10) | class SolidCable < ::ActionCable::SubscriptionAdapter::Base
        method initialize (line 13) | def initialize(*)
        method broadcast (line 18) | def broadcast(channel, payload)
        method subscribe (line 24) | def subscribe(channel, callback, success_callback = nil)
        method unsubscribe (line 28) | def unsubscribe(channel, callback)
        method listener (line 35) | def listener
        class Listener (line 41) | class Listener < ::ActionCable::SubscriptionAdapter::SubscriberMap
          method initialize (line 45) | def initialize(event_loop)
          method listen (line 70) | def listen
          method interruptible (line 86) | def interruptible
          method shutdown (line 93) | def shutdown
          method add_channel (line 102) | def add_channel(channel, on_success)
          method remove_channel (line 107) | def remove_channel(channel)
          method invoke_callback (line 111) | def invoke_callback(*)
          method last_message_id (line 119) | def last_message_id
          method channels (line 123) | def channels
          method broadcast_messages (line 127) | def broadcast_messages
          method with_polling_volume (line 146) | def with_polling_volume
          method reconnect_attempts (line 154) | def reconnect_attempts
          method retry_connecting? (line 158) | def retry_connecting?

FILE: lib/generators/solid_cable/install/install_generator.rb
  class SolidCable::InstallGenerator (line 3) | class SolidCable::InstallGenerator < Rails::Generators::Base
    method copy_files (line 6) | def copy_files

FILE: lib/generators/solid_cable/update/templates/db/migrate/create_compact_channel.rb
  class CreateCompactChannel (line 3) | class CreateCompactChannel < ActiveRecord::Migration[7.2]
    method up (line 4) | def up
    method down (line 15) | def down

FILE: lib/generators/solid_cable/update/update_generator.rb
  class SolidCable::UpdateGenerator (line 6) | class SolidCable::UpdateGenerator < Rails::Generators::Base
    method copy_files (line 11) | def copy_files

FILE: lib/solid_cable.rb
  type SolidCable (line 7) | module SolidCable
    function connects_to (line 9) | def connects_to
    function silence_polling? (line 13) | def silence_polling?
    function polling_interval (line 17) | def polling_interval
    function message_retention (line 21) | def message_retention
    function autotrim? (line 25) | def autotrim?
    function trim_batch_size (line 29) | def trim_batch_size
    function use_skip_locked (line 37) | def use_skip_locked
    function trim_chance (line 47) | def trim_chance
    function reconnect_attempts (line 51) | def reconnect_attempts
    function cable_config (line 58) | def cable_config
    function parse_duration (line 62) | def parse_duration(duration, default:)

FILE: lib/solid_cable/engine.rb
  type SolidCable (line 3) | module SolidCable
    class Engine (line 4) | class Engine < ::Rails::Engine

FILE: lib/solid_cable/version.rb
  type SolidCable (line 3) | module SolidCable

FILE: test/config_stubs.rb
  type ConfigStubs (line 3) | module ConfigStubs
    class ConfigStub (line 6) | class ConfigStub
      method initialize (line 7) | def initialize(**)
      method config_for (line 12) | def config_for(_file)
      method executor (line 16) | def executor
      class ExectorStub (line 20) | class ExectorStub
        method run! (line 21) | def run!
    function with_cable_config (line 26) | def with_cable_config(**)

FILE: test/dummy/app/controllers/application_controller.rb
  class ApplicationController (line 1) | class ApplicationController < ActionController::Base

FILE: test/dummy/app/helpers/application_helper.rb
  type ApplicationHelper (line 1) | module ApplicationHelper

FILE: test/dummy/app/jobs/application_job.rb
  class ApplicationJob (line 1) | class ApplicationJob < ActiveJob::Base

FILE: test/dummy/app/models/application_record.rb
  class ApplicationRecord (line 1) | class ApplicationRecord < ActiveRecord::Base

FILE: test/dummy/config/application.rb
  type Dummy (line 9) | module Dummy
    class Application (line 10) | class Application < Rails::Application

FILE: test/jobs/trim_job_test.rb
  class TrimJobTest (line 6) | class TrimJobTest < ActiveJob::TestCase

FILE: test/lib/action_cable/subscription_adapter/solid_cable_test.rb
  class ActionCable::SubscriptionAdapter::SolidCableTest (line 10) | class ActionCable::SubscriptionAdapter::SolidCableTest < ActionCable::Te...
    method cable_config (line 203) | def cable_config
    method subscribe_as_queue (line 208) | def subscribe_as_queue(channel, adapter = @rx_adapter)
    method with_active_record_logger (line 225) | def with_active_record_logger(logger)
    method next_message_in_queue (line 233) | def next_message_in_queue(queue)

FILE: test/lib/generators/solid_cable/install/install_generator_test.rb
  class SolidCable::InstallGeneratorTest (line 4) | class SolidCable::InstallGeneratorTest < Rails::Generators::TestCase

FILE: test/lib/generators/solid_cable/update/update_generator_test.rb
  class SolidCable::UpdateGeneratorTest (line 4) | class SolidCable::UpdateGeneratorTest < Rails::Generators::TestCase

FILE: test/solid_cable_test.rb
  class SolidCableTest (line 6) | class SolidCableTest < ActiveSupport::TestCase
Condensed preview — 168 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (175K chars).
[
  {
    "path": ".github/workflows/lint.yml",
    "chars": 413,
    "preview": "name: lint\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    name: "
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1125,
    "preview": "name: tests\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  build:\n    name: Tests\n    runs-on: ubuntu-"
  },
  {
    "path": ".gitignore",
    "chars": 532,
    "preview": "/.bundle/\n/doc/\n/log/*.log\n/pkg/\n/tmp/\n/test/dummy/db/*.sqlite3\n/test/dummy/db/*.sqlite3-*\n/test/dummy/log/*.log\n/test/d"
  },
  {
    "path": ".rubocop.yml",
    "chars": 392,
    "preview": "inherit_gem:\n  rubocop-rails-omakase: rubocop.yml\n\nAllCops:\n  NewCops: enable\n  SuggestExtensions: false\n  Exclude:\n    "
  },
  {
    "path": "Gemfile",
    "chars": 271,
    "preview": "# frozen_string_literal: true\n\nsource \"https://rubygems.org\"\ngit_source(:github) { |repo| \"https://github.com/#{repo}.gi"
  },
  {
    "path": "MIT-LICENSE",
    "chars": 1045,
    "preview": "Copyright Nick Pezza\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and "
  },
  {
    "path": "README.md",
    "chars": 11257,
    "preview": "# Solid Cable\n\nSolid Cable is a database-backed Action Cable adapter that keeps messages in a table and continuously pol"
  },
  {
    "path": "Rakefile",
    "chars": 149,
    "preview": "require \"bundler/setup\"\n\nAPP_RAKEFILE = File.expand_path(\"test/dummy/Rakefile\", __dir__)\nload \"rails/tasks/engine.rake\"\n"
  },
  {
    "path": "app/jobs/solid_cable/trim_job.rb",
    "chars": 660,
    "preview": "# frozen_string_literal: true\n\nmodule SolidCable\n  class TrimJob < ActiveJob::Base\n    def perform\n      return unless t"
  },
  {
    "path": "app/models/solid_cable/message.rb",
    "chars": 888,
    "preview": "# frozen_string_literal: true\n\nmodule SolidCable\n  class Message < SolidCable::Record\n    scope :trimmable, lambda {\n   "
  },
  {
    "path": "app/models/solid_cable/record.rb",
    "chars": 363,
    "preview": "# frozen_string_literal: true\n\nmodule SolidCable\n  class Record < ActiveRecord::Base\n    self.abstract_class = true\n\n   "
  },
  {
    "path": "bench/.dockerignore",
    "chars": 836,
    "preview": "# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.\n\n# Ignore git d"
  },
  {
    "path": "bench/.ruby-version",
    "chars": 6,
    "preview": "3.3.5\n"
  },
  {
    "path": "bench/Dockerfile",
    "chars": 2378,
    "preview": "# syntax = docker/dockerfile:1\n\n# This Dockerfile is designed for production, not development. Use with Kamal or build'n"
  },
  {
    "path": "bench/Gemfile",
    "chars": 344,
    "preview": "source \"https://rubygems.org\"\n\ngem \"rails\", github: \"rails/rails\", branch: \"main\"\ngem \"activeerror\"\ngem \"propshaft\"\ngem "
  },
  {
    "path": "bench/Rakefile",
    "chars": 227,
    "preview": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they wil"
  },
  {
    "path": "bench/app/assets/builds/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bench/app/assets/images/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bench/app/assets/stylesheets/application.tailwind.css",
    "chars": 145,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n/*\n\n@layer components {\n  .btn-primary {\n    @apply py-2 px-"
  },
  {
    "path": "bench/app/channels/application_cable/channel.rb",
    "chars": 100,
    "preview": "# :markup: markdown\n\nmodule ApplicationCable\n  class Channel < ActionCable::Channel::Base\n  end\nend\n"
  },
  {
    "path": "bench/app/channels/application_cable/connection.rb",
    "chars": 314,
    "preview": "module ApplicationCable\n  class Connection < ActionCable::Connection::Base\n    rescue_from Exception do |error|\n      Ra"
  },
  {
    "path": "bench/app/channels/broadcast_channel.rb",
    "chars": 361,
    "preview": "class BroadcastChannel < ApplicationCable::Channel\n  def subscribed\n    Rails.logger.info \"a client subscribed: #{id}\"\n "
  },
  {
    "path": "bench/app/controllers/application_controller.rb",
    "chars": 57,
    "preview": "class ApplicationController < ActionController::Base\nend\n"
  },
  {
    "path": "bench/app/controllers/concerns/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bench/app/controllers/rooms_controller.rb",
    "chars": 1175,
    "preview": "class RoomsController < ApplicationController\n  before_action :set_room, only: %i[ show edit update destroy ]\n\n  # GET /"
  },
  {
    "path": "bench/app/controllers/ws_debugger_controller.rb",
    "chars": 55,
    "preview": "class WsDebuggerController < ApplicationController\nend\n"
  },
  {
    "path": "bench/app/javascript/application.js",
    "chars": 123,
    "preview": "// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails\nimport \"channel"
  },
  {
    "path": "bench/app/javascript/channels/broadcast_channel.js",
    "chars": 380,
    "preview": "import consumer from \"channels/consumer\"\n\nconsumer.subscriptions.create(\"BroadcastChannel\", {\n  connected() {\n    // Cal"
  },
  {
    "path": "bench/app/javascript/channels/consumer.js",
    "chars": 270,
    "preview": "// Action Cable provides the framework to deal with WebSockets in Rails.\n// You can generate new channels where WebSocke"
  },
  {
    "path": "bench/app/javascript/channels/index.js",
    "chars": 90,
    "preview": "// Import all the channels to be used by Action Cable\nimport \"channels/broadcast_channel\"\n"
  },
  {
    "path": "bench/app/javascript/controllers/application.js",
    "chars": 218,
    "preview": "import { Application } from \"@hotwired/stimulus\"\n\nconst application = Application.start()\n\n// Configure Stimulus develop"
  },
  {
    "path": "bench/app/javascript/controllers/index.js",
    "chars": 584,
    "preview": "// Import and register all your controllers from the importmap under controllers/*\n\nimport { application } from \"control"
  },
  {
    "path": "bench/app/jobs/application_job.rb",
    "chars": 269,
    "preview": "class ApplicationJob < ActiveJob::Base\n  # Automatically retry jobs that encountered a deadlock\n  # retry_on ActiveRecor"
  },
  {
    "path": "bench/app/mailers/application_mailer.rb",
    "chars": 102,
    "preview": "class ApplicationMailer < ActionMailer::Base\n  default from: \"from@example.com\"\n  layout \"mailer\"\nend\n"
  },
  {
    "path": "bench/app/models/application_record.rb",
    "chars": 74,
    "preview": "class ApplicationRecord < ActiveRecord::Base\n  primary_abstract_class\nend\n"
  },
  {
    "path": "bench/app/models/concerns/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bench/app/models/room.rb",
    "chars": 35,
    "preview": "class Room < ApplicationRecord\nend\n"
  },
  {
    "path": "bench/app/views/layouts/application.html.erb",
    "chars": 781,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title><%= content_for(:title) || \"Cablebench\" %></title>\n    <meta name=\"viewport\" "
  },
  {
    "path": "bench/app/views/layouts/mailer.html.erb",
    "chars": 227,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n    <style>\n    "
  },
  {
    "path": "bench/app/views/layouts/mailer.text.erb",
    "chars": 13,
    "preview": "<%= yield %>\n"
  },
  {
    "path": "bench/app/views/rooms/_form.html.erb",
    "chars": 783,
    "preview": "<%= form_with(model: room, class: \"contents\") do |form| %>\n  <% if room.errors.any? %>\n    <div id=\"error_explanation\" c"
  },
  {
    "path": "bench/app/views/rooms/_room.html.erb",
    "chars": 143,
    "preview": "<div id=\"<%= dom_id room %>\">\n  <p class=\"my-5\">\n    <strong class=\"block font-medium mb-1\">Name:</strong>\n    <%= room."
  },
  {
    "path": "bench/app/views/rooms/edit.html.erb",
    "chars": 366,
    "preview": "<div class=\"mx-auto md:w-2/3 w-full\">\n  <h1 class=\"font-bold text-4xl\">Editing room</h1>\n\n  <%= render \"form\", room: @ro"
  },
  {
    "path": "bench/app/views/rooms/index.html.erb",
    "chars": 709,
    "preview": "<div class=\"w-full\">\n  <% if notice.present? %>\n    <p class=\"py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium roun"
  },
  {
    "path": "bench/app/views/rooms/new.html.erb",
    "chars": 248,
    "preview": "<div class=\"mx-auto md:w-2/3 w-full\">\n  <h1 class=\"font-bold text-4xl\">New room</h1>\n\n  <%= render \"form\", room: @room %"
  },
  {
    "path": "bench/app/views/rooms/show.html.erb",
    "chars": 703,
    "preview": "<div class=\"mx-auto md:w-2/3 w-full flex\">\n  <div class=\"mx-auto\">\n    <% if notice.present? %>\n      <p class=\"py-2 px-"
  },
  {
    "path": "bench/app/views/ws_debugger/show.html.erb",
    "chars": 19,
    "preview": "<p>hello world</p>\n"
  },
  {
    "path": "bench/bin/bundle",
    "chars": 2801,
    "preview": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n#\n# This file was generated by Bundler.\n#\n# The application 'bundle' "
  },
  {
    "path": "bench/bin/docker-entrypoint",
    "chars": 381,
    "preview": "#!/bin/bash -e\n\n# Enable jemalloc for reduced memory usage and latency.\nif [ -z \"${LD_PRELOAD+x}\" ] && [ -f /usr/lib/*/l"
  },
  {
    "path": "bench/bin/importmap",
    "chars": 91,
    "preview": "#!/usr/bin/env ruby\n\nrequire_relative \"../config/application\"\nrequire \"importmap/commands\"\n"
  },
  {
    "path": "bench/bin/kamal",
    "chars": 745,
    "preview": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n#\n# This file was generated by Bundler.\n#\n# The application 'kamal' i"
  },
  {
    "path": "bench/bin/rails",
    "chars": 141,
    "preview": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path(\"../config/application\", __dir__)\nrequire_relative \"../config/boot\"\nrequ"
  },
  {
    "path": "bench/bin/rake",
    "chars": 90,
    "preview": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"rake\"\nRake.application.run\n"
  },
  {
    "path": "bench/bin/rubocop",
    "chars": 266,
    "preview": "#!/usr/bin/env ruby\nrequire \"rubygems\"\nrequire \"bundler/setup\"\n\n# explicit rubocop config increases performance slightly"
  },
  {
    "path": "bench/bin/setup",
    "chars": 1135,
    "preview": "#!/usr/bin/env ruby\nrequire \"fileutils\"\n\nAPP_ROOT = File.expand_path(\"..\", __dir__)\nAPP_NAME = \"cablebench\"\n\ndef system!"
  },
  {
    "path": "bench/config/application.rb",
    "chars": 1107,
    "preview": "require_relative \"boot\"\n\nrequire \"rails/all\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited "
  },
  {
    "path": "bench/config/boot.rb",
    "chars": 207,
    "preview": "ENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../Gemfile\", __dir__)\n\nrequire \"bundler/setup\" # Set up gems listed in the G"
  },
  {
    "path": "bench/config/cable.yml",
    "chars": 554,
    "preview": "development:\n  # adapter: redis\n  # url: redis://localhost:6379/1\n  adapter: solid_cable\n  message_retention: 15.minutes"
  },
  {
    "path": "bench/config/credentials.yml.enc",
    "chars": 500,
    "preview": "7lxKveoOh/YSo6BSUdrNHtg29iVtfI9AinJgpSiiQkSWpoVPHluT1PNVQpleQhs3LqM0WP4aMBX12xzUP843RI2LYQmtUmbjkJtfkMs/dCijfslkwfrwSI8A"
  },
  {
    "path": "bench/config/database.yml",
    "chars": 1152,
    "preview": "default: &default\n  adapter: sqlite3\n  pool: <%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %>\n  timeout: 5000\n\ndevelopment:\n "
  },
  {
    "path": "bench/config/deploy.yml",
    "chars": 1164,
    "preview": "service: solidcable\nimage: npezza/solid_cable\nasset_path: /rails/public/assets\nservers:\n  web:\n    - <%= ENV[\"HOST\"] %>\n"
  },
  {
    "path": "bench/config/environment.rb",
    "chars": 128,
    "preview": "# Load the Rails application.\nrequire_relative \"application\"\n\n# Initialize the Rails application.\nRails.application.init"
  },
  {
    "path": "bench/config/environments/development.rb",
    "chars": 2578,
    "preview": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take pre"
  },
  {
    "path": "bench/config/environments/production.rb",
    "chars": 4312,
    "preview": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take pre"
  },
  {
    "path": "bench/config/environments/test.rb",
    "chars": 2592,
    "preview": "require \"active_support/core_ext/integer/time\"\n\n# The test environment is used exclusively to run your application's\n# t"
  },
  {
    "path": "bench/config/importmap.rb",
    "chars": 174,
    "preview": "# Pin npm packages by running ./bin/importmap\n\npin \"application\"\npin \"@rails/actioncable\", to: \"actioncable.esm.js\"\npin_"
  },
  {
    "path": "bench/config/init.sql",
    "chars": 28,
    "preview": "CREATE DATABASE cablebench;\n"
  },
  {
    "path": "bench/config/initializers/assets.rb",
    "chars": 296,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Version of your assets, change this if you want to expire"
  },
  {
    "path": "bench/config/initializers/content_security_policy.rb",
    "chars": 1071,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy.\n# See"
  },
  {
    "path": "bench/config/initializers/filter_parameter_logging.rb",
    "chars": 456,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Configure parameters to be partially matched (e.g. passw "
  },
  {
    "path": "bench/config/initializers/inflections.rb",
    "chars": 649,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Infl"
  },
  {
    "path": "bench/config/initializers/permissions_policy.rb",
    "chars": 480,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide HTTP permissions policy. For f"
  },
  {
    "path": "bench/config/locales/en.yml",
    "chars": 908,
    "preview": "# Files in the config/locales directory are used for internationalization and\n# are automatically loaded by Rails. If yo"
  },
  {
    "path": "bench/config/puma.rb",
    "chars": 2322,
    "preview": "# This configuration file will be evaluated by Puma. The top-level methods that\n# are invoked here are part of Puma's co"
  },
  {
    "path": "bench/config/routes.rb",
    "chars": 534,
    "preview": "Rails.application.routes.draw do\n  mount ActiveError::Engine => \"/errors\"\n  mount ActionCable.server => \"/cable\"\n\n  get "
  },
  {
    "path": "bench/config/storage.yml",
    "chars": 1152,
    "preview": "test:\n  service: Disk\n  root: <%= Rails.root.join(\"tmp/storage\") %>\n\nlocal:\n  service: Disk\n  root: <%= Rails.root.join("
  },
  {
    "path": "bench/config/tailwind.config.js",
    "chars": 492,
    "preview": "const defaultTheme = require('tailwindcss/defaultTheme')\n\nmodule.exports = {\n  content: [\n    './public/*.html',\n    './"
  },
  {
    "path": "bench/config.ru",
    "chars": 160,
    "preview": "# This file is used by Rack-based servers to start the application.\n\nrequire_relative \"config/environment\"\n\nrun Rails.ap"
  },
  {
    "path": "bench/db/migrate/20240529231225_create_rooms.rb",
    "chars": 152,
    "preview": "class CreateRooms < ActiveRecord::Migration[8.0]\n  def change\n    create_table :rooms do |t|\n      t.string :name\n\n     "
  },
  {
    "path": "bench/db/migrate/20240530031126_create_solid_cable_message.solid_cable.rb",
    "chars": 349,
    "preview": "# frozen_string_literal: true\n\n# This migration comes from solid_cable (originally 20240103034713)\nclass CreateSolidCabl"
  },
  {
    "path": "bench/db/migrate/20240607184931_index_channels.solid_cable.rb",
    "chars": 201,
    "preview": "# This migration comes from solid_cable (originally 20240607184711)\nclass IndexChannels < ActiveRecord::Migration[7.1]\n "
  },
  {
    "path": "bench/db/migrate/20240609023040_create_active_error_faults.active_error.rb",
    "chars": 621,
    "preview": "# frozen_string_literal: true\n\n# This migration comes from active_error (originally 20200727220359)\nclass CreateActiveEr"
  },
  {
    "path": "bench/db/migrate/20240609023041_create_active_error_instances.active_error.rb",
    "chars": 402,
    "preview": "# frozen_string_literal: true\n\n# This migration comes from active_error (originally 20200727225318)\nclass CreateActiveEr"
  },
  {
    "path": "bench/db/migrate/20240912235943_create_compact_channel.rb",
    "chars": 837,
    "preview": "# frozen_string_literal: true\n\nclass CreateCompactChannel < ActiveRecord::Migration[7.2]\n  def up\n    change_column :sol"
  },
  {
    "path": "bench/db/schema.rb",
    "chars": 2312,
    "preview": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the mig"
  },
  {
    "path": "bench/db/seeds.rb",
    "chars": 494,
    "preview": "# This file should ensure the existence of records required to run the application in every environment (production,\n# d"
  },
  {
    "path": "bench/loadtest.js",
    "chars": 1890,
    "preview": "import { check, sleep, fail } from \"k6\";\nimport cable from \"k6/x/cable\";\nimport { randomIntBetween } from \"https://jslib"
  },
  {
    "path": "bench/log/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bench/public/404.html",
    "chars": 1722,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The page you were looking for doesn't exist (404)</title>\n  <meta name=\"viewport\""
  },
  {
    "path": "bench/public/406-unsupported-browser.html",
    "chars": 1612,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Your browser is not supported (406)</title>\n  <meta name=\"viewport\" content=\"widt"
  },
  {
    "path": "bench/public/422.html",
    "chars": 1705,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The change you wanted was rejected (422)</title>\n  <meta name=\"viewport\" content="
  },
  {
    "path": "bench/public/500.html",
    "chars": 1635,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>We're sorry, but something went wrong (500)</title>\n  <meta name=\"viewport\" conte"
  },
  {
    "path": "bench/public/robots.txt",
    "chars": 99,
    "preview": "# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file\n"
  },
  {
    "path": "bench/test/channels/application_cable/connection_test.rb",
    "chars": 276,
    "preview": "require \"test_helper\"\n\nmodule ApplicationCable\n  class ConnectionTest < ActionCable::Connection::TestCase\n    # test \"co"
  },
  {
    "path": "bench/test/channels/broadcast_channel_test.rb",
    "chars": 173,
    "preview": "require \"test_helper\"\n\nclass BroadcastChannelTest < ActionCable::Channel::TestCase\n  # test \"subscribes\" do\n  #   subscr"
  },
  {
    "path": "bench/test/controllers/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bench/test/fixtures/files/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bench/test/fixtures/rooms.yml",
    "chars": 136,
    "preview": "# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html\n\none:\n  name: MyString\n\ntwo:\n "
  },
  {
    "path": "bench/test/models/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bench/test/models/room_test.rb",
    "chars": 118,
    "preview": "require \"test_helper\"\n\nclass RoomTest < ActiveSupport::TestCase\n  # test \"the truth\" do\n  #   assert true\n  # end\nend\n"
  },
  {
    "path": "bench/test/test_helper.rb",
    "chars": 410,
    "preview": "ENV[\"RAILS_ENV\"] ||= \"test\"\nrequire_relative \"../config/environment\"\nrequire \"rails/test_help\"\n\nmodule ActiveSupport\n  c"
  },
  {
    "path": "bench/vendor/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bench/vendor/javascript/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bin/rails",
    "chars": 553,
    "preview": "#!/usr/bin/env ruby\n# This command will automatically be run when you run \"rails\" with Rails gems\n# installed from the r"
  },
  {
    "path": "bin/release",
    "chars": 331,
    "preview": "#!/usr/bin/env bash\n\nVERSION=$1\n\nif [ -z \"$VERSION\" ]\nthen\n  echo \"Usage: bin/release <version>\"\n  exit 1\nfi\n\nprintf \"# "
  },
  {
    "path": "bin/test",
    "chars": 116,
    "preview": "#!/usr/bin/env ruby\n$: << File.expand_path(\"../test\", __dir__)\n\nrequire \"bundler/setup\"\nrequire \"rails/plugin/test\"\n"
  },
  {
    "path": "lib/action_cable/subscription_adapter/solid_cable.rb",
    "chars": 4969,
    "preview": "# frozen_string_literal: true\n\nrequire \"action_cable/subscription_adapter/base\"\nrequire \"action_cable/subscription_adapt"
  },
  {
    "path": "lib/generators/solid_cable/install/USAGE",
    "chars": 197,
    "preview": "Description:\n    Installs solid_cable as the Action Cable adapter\n\nExample:\n    bin/rails generate solid_cable:install\n\n"
  },
  {
    "path": "lib/generators/solid_cable/install/install_generator.rb",
    "chars": 252,
    "preview": "# frozen_string_literal: true\n\nclass SolidCable::InstallGenerator < Rails::Generators::Base\n  source_root File.expand_pa"
  },
  {
    "path": "lib/generators/solid_cable/install/templates/config/cable.yml",
    "chars": 549,
    "preview": "# Async adapter only works within the same process, so for manually triggering cable updates from a console,\n# and seein"
  },
  {
    "path": "lib/generators/solid_cable/install/templates/db/cable_schema.rb",
    "chars": 545,
    "preview": "ActiveRecord::Schema[7.1].define(version: 1) do\n  create_table \"solid_cable_messages\", force: :cascade do |t|\n    t.bina"
  },
  {
    "path": "lib/generators/solid_cable/update/USAGE",
    "chars": 182,
    "preview": "Description:\n    Updates Solid Cable migrations\n\nExample:\n    bin/rails generate solid_cable:update\n\n    This will perfo"
  },
  {
    "path": "lib/generators/solid_cable/update/templates/db/migrate/create_compact_channel.rb",
    "chars": 788,
    "preview": "# frozen_string_literal: true\n\nclass CreateCompactChannel < ActiveRecord::Migration[7.2]\n  def up\n    change_column :sol"
  },
  {
    "path": "lib/generators/solid_cable/update/update_generator.rb",
    "chars": 419,
    "preview": "# frozen_string_literal: true\n\nrequire \"rails/generators\"\nrequire \"rails/generators/active_record\"\n\nclass SolidCable::Up"
  },
  {
    "path": "lib/solid_cable/engine.rb",
    "chars": 125,
    "preview": "# frozen_string_literal: true\n\nmodule SolidCable\n  class Engine < ::Rails::Engine\n    isolate_namespace SolidCable\n  end"
  },
  {
    "path": "lib/solid_cable/version.rb",
    "chars": 74,
    "preview": "# frozen_string_literal: true\n\nmodule SolidCable\n  VERSION = \"3.0.12\"\nend\n"
  },
  {
    "path": "lib/solid_cable.rb",
    "chars": 1795,
    "preview": "# frozen_string_literal: true\n\nrequire \"solid_cable/version\"\nrequire \"solid_cable/engine\"\nrequire \"action_cable/subscrip"
  },
  {
    "path": "lib/tasks/solid_cable_tasks.rake",
    "chars": 302,
    "preview": "# frozen_string_literal: true\n\ndesc \"Copy over the schema and set cable adapter for Solid Cable\"\nnamespace :solid_cable "
  },
  {
    "path": "solid_cable.gemspec",
    "chars": 1090,
    "preview": "# frozen_string_literal: true\n\nrequire_relative \"lib/solid_cable/version\"\n\nGem::Specification.new do |spec|\n  spec.name "
  },
  {
    "path": "test/config_stubs.rb",
    "chars": 508,
    "preview": "# frozen_string_literal: true\n\nmodule ConfigStubs\n  extend ActiveSupport::Concern\n\n  class ConfigStub\n    def initialize"
  },
  {
    "path": "test/dummy/Rakefile",
    "chars": 227,
    "preview": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they wil"
  },
  {
    "path": "test/dummy/app/controllers/application_controller.rb",
    "chars": 204,
    "preview": "class ApplicationController < ActionController::Base\n  # Only allow modern browsers supporting webp images, web push, ba"
  },
  {
    "path": "test/dummy/app/controllers/concerns/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test/dummy/app/helpers/application_helper.rb",
    "chars": 29,
    "preview": "module ApplicationHelper\nend\n"
  },
  {
    "path": "test/dummy/app/jobs/application_job.rb",
    "chars": 269,
    "preview": "class ApplicationJob < ActiveJob::Base\n  # Automatically retry jobs that encountered a deadlock\n  # retry_on ActiveRecor"
  },
  {
    "path": "test/dummy/app/models/application_record.rb",
    "chars": 74,
    "preview": "class ApplicationRecord < ActiveRecord::Base\n  primary_abstract_class\nend\n"
  },
  {
    "path": "test/dummy/app/models/concerns/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test/dummy/app/views/layouts/application.html.erb",
    "chars": 906,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title><%= content_for(:title) || \"Dummy\" %></title>\n    <meta name=\"viewport\" conte"
  },
  {
    "path": "test/dummy/app/views/layouts/mailer.html.erb",
    "chars": 227,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n    <style>\n    "
  },
  {
    "path": "test/dummy/app/views/layouts/mailer.text.erb",
    "chars": 13,
    "preview": "<%= yield %>\n"
  },
  {
    "path": "test/dummy/app/views/pwa/manifest.json.erb",
    "chars": 393,
    "preview": "{\n  \"name\": \"Dummy\",\n  \"icons\": [\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n  "
  },
  {
    "path": "test/dummy/app/views/pwa/service-worker.js",
    "chars": 876,
    "preview": "// Add a service worker for processing Web Push notifications:\n//\n// self.addEventListener(\"push\", async (event) => {\n//"
  },
  {
    "path": "test/dummy/bin/ci",
    "chars": 180,
    "preview": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"active_support/continuous_integration\"\n\nCI = ActiveSuppor"
  },
  {
    "path": "test/dummy/bin/dev",
    "chars": 56,
    "preview": "#!/usr/bin/env ruby\nexec \"./bin/rails\", \"server\", *ARGV\n"
  },
  {
    "path": "test/dummy/bin/rails",
    "chars": 141,
    "preview": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path(\"../config/application\", __dir__)\nrequire_relative \"../config/boot\"\nrequ"
  },
  {
    "path": "test/dummy/bin/rake",
    "chars": 90,
    "preview": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"rake\"\nRake.application.run\n"
  },
  {
    "path": "test/dummy/bin/setup",
    "chars": 1069,
    "preview": "#!/usr/bin/env ruby\nrequire \"fileutils\"\n\nAPP_ROOT = File.expand_path(\"..\", __dir__)\n\ndef system!(*args)\n  system(*args, "
  },
  {
    "path": "test/dummy/config/application.rb",
    "chars": 1054,
    "preview": "require_relative \"boot\"\n\nrequire \"rails/all\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited "
  },
  {
    "path": "test/dummy/config/boot.rb",
    "chars": 233,
    "preview": "# Set up gems listed in the Gemfile.\nENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../../../Gemfile\", __dir__)\n\nrequire \"b"
  },
  {
    "path": "test/dummy/config/cable.yml",
    "chars": 186,
    "preview": "development:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: redis\n  url: <%= ENV.fetch(\"REDIS_URL\") { \""
  },
  {
    "path": "test/dummy/config/ci.rb",
    "chars": 585,
    "preview": "# Run using bin/ci\n\nCI.run do\n  step \"Setup\", \"bin/setup --skip-server\"\n\n\n  step \"Tests: Rails\", \"bin/rails test\"\n  step"
  },
  {
    "path": "test/dummy/config/database.yml",
    "chars": 843,
    "preview": "<% def database_name_from(name); [\"mysql\", \"postgres\"].exclude?(ENV[\"TARGET_DB\"]) ? \"db/#{name}.sqlite3\" : name; end %>\n"
  },
  {
    "path": "test/dummy/config/environment.rb",
    "chars": 128,
    "preview": "# Load the Rails application.\nrequire_relative \"application\"\n\n# Initialize the Rails application.\nRails.application.init"
  },
  {
    "path": "test/dummy/config/environments/development.rb",
    "chars": 2770,
    "preview": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take pre"
  },
  {
    "path": "test/dummy/config/environments/production.rb",
    "chars": 3668,
    "preview": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take pre"
  },
  {
    "path": "test/dummy/config/environments/test.rb",
    "chars": 2325,
    "preview": "# The test environment is used exclusively to run your application's\n# test suite. You never need to work with it otherw"
  },
  {
    "path": "test/dummy/config/initializers/assets.rb",
    "chars": 298,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Version of your assets, change this if you want to expire"
  },
  {
    "path": "test/dummy/config/initializers/content_security_policy.rb",
    "chars": 1332,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy.\n# See"
  },
  {
    "path": "test/dummy/config/initializers/filter_parameter_logging.rb",
    "chars": 468,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Configure parameters to be partially matched (e.g. passw "
  },
  {
    "path": "test/dummy/config/initializers/inflections.rb",
    "chars": 649,
    "preview": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Infl"
  },
  {
    "path": "test/dummy/config/locales/en.yml",
    "chars": 908,
    "preview": "# Files in the config/locales directory are used for internationalization and\n# are automatically loaded by Rails. If yo"
  },
  {
    "path": "test/dummy/config/puma.rb",
    "chars": 1922,
    "preview": "# This configuration file will be evaluated by Puma. The top-level methods that\n# are invoked here are part of Puma's co"
  },
  {
    "path": "test/dummy/config/routes.rb",
    "chars": 471,
    "preview": "# frozen_string_literal: true\n\nRails.application.routes.draw do\n  # Define your application routes per the DSL in https:"
  },
  {
    "path": "test/dummy/config/storage.yml",
    "chars": 811,
    "preview": "test:\n  service: Disk\n  root: <%= Rails.root.join(\"tmp/storage\") %>\n\nlocal:\n  service: Disk\n  root: <%= Rails.root.join("
  },
  {
    "path": "test/dummy/config.ru",
    "chars": 160,
    "preview": "# This file is used by Rack-based servers to start the application.\n\nrequire_relative \"config/environment\"\n\nrun Rails.ap"
  },
  {
    "path": "test/dummy/db/schema.rb",
    "chars": 1271,
    "preview": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the mig"
  },
  {
    "path": "test/dummy/log/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test/dummy/public/400.html",
    "chars": 7054,
    "preview": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>The server cannot process the request due to a client error (400"
  },
  {
    "path": "test/dummy/public/404.html",
    "chars": 5201,
    "preview": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>The page you were looking for doesn't exist (404 Not found)</tit"
  },
  {
    "path": "test/dummy/public/406-unsupported-browser.html",
    "chars": 7283,
    "preview": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>Your browser is not supported (406 Not Acceptable)</title>\n\n    "
  },
  {
    "path": "test/dummy/public/422.html",
    "chars": 8747,
    "preview": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>The change you wanted was rejected (422 Unprocessable Entity)</t"
  },
  {
    "path": "test/dummy/public/500.html",
    "chars": 8283,
    "preview": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>We're sorry, but something went wrong (500 Internal Server Error"
  },
  {
    "path": "test/jobs/trim_job_test.rb",
    "chars": 1092,
    "preview": "# frozen_string_literal: true\n\nrequire \"test_helper\"\nrequire \"config_stubs\"\n\nclass TrimJobTest < ActiveJob::TestCase\n  i"
  },
  {
    "path": "test/lib/action_cable/subscription_adapter/solid_cable_test.rb",
    "chars": 6581,
    "preview": "# frozen_string_literal: true\n\nrequire \"test_helper\"\nrequire \"concurrent\"\n\nrequire \"active_support/core_ext/hash/indiffe"
  },
  {
    "path": "test/lib/generators/solid_cable/install/install_generator_test.rb",
    "chars": 482,
    "preview": "require \"test_helper\"\nrequire_relative \"../../../../../lib/generators/solid_cable/install/install_generator\"\n\nclass Soli"
  },
  {
    "path": "test/lib/generators/solid_cable/update/update_generator_test.rb",
    "chars": 436,
    "preview": "require \"test_helper\"\nrequire_relative \"../../../../../lib/generators/solid_cable/update/update_generator\"\n\nclass SolidC"
  },
  {
    "path": "test/solid_cable_test.rb",
    "chars": 1630,
    "preview": "# frozen_string_literal: true\n\nrequire \"test_helper\"\nrequire \"config_stubs\"\n\nclass SolidCableTest < ActiveSupport::TestC"
  },
  {
    "path": "test/test_helper.rb",
    "chars": 707,
    "preview": "# frozen_string_literal: true\n\n# Configure Rails Environment\nENV[\"RAILS_ENV\"] = \"test\"\n\nrequire_relative \"../test/dummy/"
  }
]

About this extraction

This page contains the full source code of the rails/solid_cable GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 168 files (154.5 KB), approximately 55.6k tokens, and a symbol index with 134 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!