Skip to content

Commit 4b033eb

Browse files
committed
Add ruby_event_store-browser-swimlane extension gem
A browser extension comparing several streams side by side: their events merged into one newest-first timeline table, one column per stream, with infinite scroll and a created-at/valid-at sort toggle. An event linked into several compared streams renders once, as a link in each matching column. Each page is composed from existing single-stream reads - one time ordered read per compared stream, k-way merged by event time. Count is the read depth per stream, not the page size: a page emits everything above the completeness horizon (the highest chunk end among the streams whose read was cut), so nothing provably complete is thrown away. The shared cursor is that horizon timestamp and the next page asks for strictly older events - nothing is dropped or repeated. Built entirely on the browser extension API: routes and json responses via register_routes, an entry link via stream_links, its own stimulus controllers via scripts.
1 parent 600dccd commit 4b033eb

19 files changed

Lines changed: 1221 additions & 0 deletions
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: ruby_event_store-browser-swimlane_coverage
2+
on:
3+
workflow_dispatch:
4+
repository_dispatch:
5+
types:
6+
- script
7+
push:
8+
branches:
9+
- master
10+
paths:
11+
- contrib/ruby_event_store-browser-swimlane/Gemfile.lock
12+
- ".github/workflows/ruby_event_store-browser-swimlane_coverage.yml"
13+
- support/**
14+
- "!support/bundler/**"
15+
- "!support/ci/**"
16+
pull_request:
17+
paths:
18+
- contrib/ruby_event_store-browser-swimlane/Gemfile.lock
19+
- ".github/workflows/ruby_event_store-browser-swimlane_coverage.yml"
20+
- support/**
21+
- "!support/bundler/**"
22+
- "!support/ci/**"
23+
schedule:
24+
- cron: 0 17 * * *
25+
jobs:
26+
coverage:
27+
runs-on: macos-14
28+
timeout-minutes: 120
29+
env:
30+
WORKING_DIRECTORY: contrib/ruby_event_store-browser-swimlane
31+
RUBY_VERSION: "${{ matrix.ruby_version }}"
32+
BUNDLE_GEMFILE: "${{ matrix.bundle_gemfile }}"
33+
BUNDLE_WITHOUT: database
34+
strategy:
35+
fail-fast: false
36+
matrix:
37+
include:
38+
- ruby_version: ruby-4.0
39+
bundle_gemfile: Gemfile
40+
steps:
41+
- uses: actions/checkout@v4
42+
with:
43+
fetch-depth: 1
44+
- run: test -e ${{ env.BUNDLE_GEMFILE }}.lock
45+
working-directory: "${{ env.WORKING_DIRECTORY }}"
46+
- uses: ruby/setup-ruby@v1
47+
with:
48+
ruby-version: "${{ env.RUBY_VERSION }}"
49+
bundler-cache: true
50+
working-directory: "${{ env.WORKING_DIRECTORY }}"
51+
- run: make mutate
52+
working-directory: "${{ env.WORKING_DIRECTORY }}"
53+
env:
54+
RUBYOPT: "--enable-frozen-string-literal"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: ruby_event_store-browser-swimlane_mutate
2+
on:
3+
workflow_dispatch:
4+
repository_dispatch:
5+
types:
6+
- script
7+
push:
8+
branches:
9+
- master
10+
paths:
11+
- contrib/ruby_event_store-browser-swimlane/**
12+
- ".github/workflows/ruby_event_store-browser-swimlane_mutate.yml"
13+
- support/**
14+
- "!support/bundler/**"
15+
- "!support/ci/**"
16+
pull_request:
17+
paths:
18+
- contrib/ruby_event_store-browser-swimlane/**
19+
- ".github/workflows/ruby_event_store-browser-swimlane_mutate.yml"
20+
- support/**
21+
- "!support/bundler/**"
22+
- "!support/ci/**"
23+
jobs:
24+
mutate:
25+
runs-on: macos-14
26+
timeout-minutes: 120
27+
env:
28+
WORKING_DIRECTORY: contrib/ruby_event_store-browser-swimlane
29+
RUBY_VERSION: "${{ matrix.ruby_version }}"
30+
BUNDLE_GEMFILE: "${{ matrix.bundle_gemfile }}"
31+
BUNDLE_WITHOUT: database
32+
SINCE_SHA: "${{ github.event.pull_request.base.sha || 'HEAD~1' }}"
33+
strategy:
34+
fail-fast: false
35+
matrix:
36+
include:
37+
- ruby_version: ruby-4.0
38+
bundle_gemfile: Gemfile
39+
steps:
40+
- uses: actions/checkout@v4
41+
with:
42+
fetch-depth: 0
43+
- run: test -e ${{ env.BUNDLE_GEMFILE }}.lock
44+
working-directory: "${{ env.WORKING_DIRECTORY }}"
45+
- uses: ruby/setup-ruby@v1
46+
with:
47+
ruby-version: "${{ env.RUBY_VERSION }}"
48+
bundler-cache: true
49+
working-directory: "${{ env.WORKING_DIRECTORY }}"
50+
- run: make mutate-changes
51+
working-directory: "${{ env.WORKING_DIRECTORY }}"
52+
env:
53+
RUBYOPT: "--enable-frozen-string-literal"
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: ruby_event_store-browser-swimlane_test
2+
on:
3+
workflow_dispatch:
4+
repository_dispatch:
5+
types:
6+
- script
7+
push:
8+
branches:
9+
- master
10+
paths:
11+
- contrib/ruby_event_store-browser-swimlane/**
12+
- ".github/workflows/ruby_event_store-browser-swimlane_test.yml"
13+
- support/**
14+
- "!support/bundler/**"
15+
- "!support/ci/**"
16+
pull_request:
17+
paths:
18+
- contrib/ruby_event_store-browser-swimlane/**
19+
- ".github/workflows/ruby_event_store-browser-swimlane_test.yml"
20+
- support/**
21+
- "!support/bundler/**"
22+
- "!support/ci/**"
23+
jobs:
24+
test:
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 120
27+
env:
28+
WORKING_DIRECTORY: contrib/ruby_event_store-browser-swimlane
29+
RUBY_VERSION: "${{ matrix.ruby_version }}"
30+
BUNDLE_GEMFILE: "${{ matrix.bundle_gemfile }}"
31+
strategy:
32+
fail-fast: false
33+
matrix:
34+
include:
35+
- ruby_version: ruby-4.0
36+
bundle_gemfile: Gemfile
37+
- ruby_version: ruby-3.4
38+
bundle_gemfile: Gemfile
39+
- ruby_version: ruby-3.3
40+
bundle_gemfile: Gemfile
41+
steps:
42+
- uses: actions/checkout@v4
43+
with:
44+
fetch-depth: 1
45+
- run: test -e ${{ env.BUNDLE_GEMFILE }}.lock
46+
working-directory: "${{ env.WORKING_DIRECTORY }}"
47+
- uses: ruby/setup-ruby@v1
48+
with:
49+
ruby-version: "${{ env.RUBY_VERSION }}"
50+
bundler-cache: true
51+
working-directory: "${{ env.WORKING_DIRECTORY }}"
52+
- run: make test
53+
working-directory: "${{ env.WORKING_DIRECTORY }}"
54+
env:
55+
RUBYOPT: "--enable-frozen-string-literal"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# https://github.com/mbj/mutant/blob/master/docs/configuration.md
2+
3+
usage: opensource
4+
requires:
5+
- ruby_event_store/browser/app
6+
- ruby_event_store/browser/swimlane
7+
includes:
8+
- lib
9+
integration:
10+
name: rspec
11+
mutation:
12+
operators: light
13+
coverage_criteria:
14+
process_abort: true
15+
matcher:
16+
subjects:
17+
- RubyEventStore::Browser::Swimlane*
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
gemspec
5+
6+
eval_gemfile "../../support/bundler/Gemfile.shared"
7+
gem "ruby_event_store", path: "../.."
8+
gem "ruby_event_store-browser", path: "../.."
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
PATH
2+
remote: ../..
3+
specs:
4+
ruby_event_store (3.0.0)
5+
concurrent-ruby (~> 1.0, >= 1.1.6)
6+
ruby_event_store-browser (3.0.0)
7+
rack
8+
ruby_event_store (= 3.0.0)
9+
10+
PATH
11+
remote: .
12+
specs:
13+
ruby_event_store-browser-swimlane (0.1.0)
14+
ruby_event_store (>= 2.0.0)
15+
ruby_event_store-browser (>= 3.0.0)
16+
17+
GEM
18+
remote: https://rubygems.org/
19+
specs:
20+
ast (2.4.3)
21+
concurrent-ruby (1.3.7)
22+
diff-lcs (1.6.2)
23+
drb (2.2.3)
24+
erb (6.0.5)
25+
io-console (0.8.2)
26+
irb (1.18.0)
27+
pp (>= 0.6.0)
28+
prism (>= 1.3.0)
29+
rdoc (>= 4.0.0)
30+
reline (>= 0.4.2)
31+
logger (1.7.0)
32+
minitest (6.0.6)
33+
drb (~> 2.0)
34+
prism (~> 1.5)
35+
mutant (0.16.3)
36+
diff-lcs (>= 1.6, < 3)
37+
irb (~> 1.15)
38+
parser (~> 3.3.10)
39+
regexp_parser (~> 2.10)
40+
securerandom (>= 0.3)
41+
sorbet-runtime (~> 0.6.0)
42+
unparser (>= 0.8.2, < 0.10)
43+
mutant-minitest (0.16.3)
44+
minitest (>= 5.11, < 7)
45+
mutant (= 0.16.3)
46+
mutex_m (~> 0.2)
47+
mutant-rspec (0.16.3)
48+
mutant (= 0.16.3)
49+
rspec-core (>= 3.8.0, < 5.0.0)
50+
mutex_m (0.3.0)
51+
parser (3.3.12.0)
52+
ast (~> 2.4.1)
53+
racc
54+
pp (0.6.4)
55+
prettyprint
56+
prettyprint (0.2.0)
57+
prism (1.9.0)
58+
racc (1.8.1)
59+
rack (3.2.6)
60+
rake (13.4.2)
61+
rbs (4.0.3)
62+
logger
63+
prism (>= 1.6.0)
64+
tsort
65+
rdoc (8.0.0)
66+
erb
67+
prism (>= 1.6.0)
68+
rbs (>= 4.0.0)
69+
tsort
70+
regexp_parser (2.12.0)
71+
reline (0.6.3)
72+
io-console (~> 0.5)
73+
rspec (3.13.2)
74+
rspec-core (~> 3.13.0)
75+
rspec-expectations (~> 3.13.0)
76+
rspec-mocks (~> 3.13.0)
77+
rspec-core (3.13.6)
78+
rspec-support (~> 3.13.0)
79+
rspec-expectations (3.13.5)
80+
diff-lcs (>= 1.2.0, < 2.0)
81+
rspec-support (~> 3.13.0)
82+
rspec-mocks (3.13.8)
83+
diff-lcs (>= 1.2.0, < 2.0)
84+
rspec-support (~> 3.13.0)
85+
rspec-support (3.13.7)
86+
securerandom (0.4.1)
87+
sorbet-runtime (0.6.13342)
88+
tsort (0.2.0)
89+
unparser (0.9.0)
90+
diff-lcs (>= 1.6, < 3)
91+
parser (>= 3.3.0)
92+
prism (>= 1.5.1)
93+
94+
PLATFORMS
95+
arm64-darwin-24
96+
ruby
97+
98+
DEPENDENCIES
99+
irb
100+
mutant
101+
mutant-minitest
102+
mutant-rspec
103+
rake (>= 10.0)
104+
rspec
105+
ruby_event_store!
106+
ruby_event_store-browser!
107+
ruby_event_store-browser-swimlane!
108+
109+
CHECKSUMS
110+
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
111+
concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0
112+
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
113+
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
114+
erb (6.0.5) sha256=858e63488cb796c9daba8b6e9ff4b3879c395022049be9a66a8e00980e612eac
115+
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
116+
irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
117+
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
118+
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
119+
mutant (0.16.3) sha256=7e10fb09b80a78d82f8e38283fc1e4d562cbb1392451e2a21611ea2bc44b5023
120+
mutant-minitest (0.16.3) sha256=504d110c1bd503528a7e57a4f062960e571bba4e3939e4a66ddc0f75d2071d90
121+
mutant-rspec (0.16.3) sha256=006ee9640800f205278604bb2160ca4b1d0013464fb216a48b7c09571a1fc547
122+
mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751
123+
parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
124+
pp (0.6.4) sha256=dfcb0fce700c41456265922884f9fe195d7fbb0674a3578e6c0f69588e82b570
125+
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
126+
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
127+
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
128+
rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2
129+
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
130+
rbs (4.0.3) sha256=5a7bf70e2628549d9a1f44eae447b2cfe55968a9c60cfff52693a4bdcc020e14
131+
rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469
132+
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
133+
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
134+
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
135+
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
136+
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
137+
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
138+
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
139+
ruby_event_store (3.0.0)
140+
ruby_event_store-browser (3.0.0)
141+
ruby_event_store-browser-swimlane (0.1.0)
142+
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
143+
sorbet-runtime (0.6.13342) sha256=dbeb9892597f593ed2b29e38018912787f6c4e0c4b736d8af0d6bd3c63bde3c3
144+
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
145+
unparser (0.9.0) sha256=4331f174a73a23b69250b13d47da3794ed1449711ee0f9ed8947dc020ba76067
146+
147+
BUNDLED WITH
148+
4.0.10
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
GEM_VERSION = $(shell cat lib/ruby_event_store/browser/swimlane/version.rb | grep VERSION | egrep -o '[0-9]+\.[0-9]+\.[0-9]+')
2+
GEM_NAME = ruby_event_store-browser-swimlane
3+
4+
include ../../support/make/install.mk
5+
include ../../support/make/test.mk
6+
include ../../support/make/mutant.mk
7+
include ../../support/make/gem.mk
8+
include ../../support/make/help.mk
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# RubyEventStore::Browser::Swimlane
2+
3+
A [RubyEventStore browser](https://railseventstore.org) extension comparing several streams side by side: their events merged into one newest-first timeline table, one column per stream, with infinite scroll. An event linked into several compared streams renders once, as a link in each matching column. The timeline can be sorted along either axis of the bi-temporal model — created-at (default) or valid-at.
4+
5+
## Usage
6+
7+
```ruby
8+
mount RubyEventStore::Browser::App.for(
9+
event_store_locator: -> { Rails.configuration.event_store },
10+
extensions: [RubyEventStore::Browser::Swimlane.new],
11+
) => "/res"
12+
```
13+
14+
A `Compare` link shows up on every stream page; the comparison itself lives under `/compare?streams[]=first&streams[]=second`.

0 commit comments

Comments
 (0)