pgbus 0.8.4 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/app/assets/javascripts/pgbus/stream_source_element.js +150 -5
- data/app/controllers/pgbus/batches_controller.rb +15 -0
- data/app/helpers/pgbus/application_helper.rb +12 -0
- data/app/views/layouts/pgbus/application.html.erb +2 -0
- data/app/views/pgbus/batches/_batches_table.html.erb +54 -0
- data/app/views/pgbus/batches/index.html.erb +8 -0
- data/app/views/pgbus/batches/show.html.erb +90 -0
- data/config/locales/da.yml +34 -0
- data/config/locales/de.yml +34 -0
- data/config/locales/en.yml +34 -0
- data/config/locales/es.yml +34 -0
- data/config/locales/fi.yml +34 -0
- data/config/locales/fr.yml +34 -0
- data/config/locales/it.yml +34 -0
- data/config/locales/ja.yml +34 -0
- data/config/locales/nb.yml +34 -0
- data/config/locales/nl.yml +34 -0
- data/config/locales/pt.yml +34 -0
- data/config/locales/sv.yml +34 -0
- data/config/routes.rb +1 -0
- data/lib/pgbus/client.rb +102 -4
- data/lib/pgbus/configuration.rb +84 -0
- data/lib/pgbus/execution_pools/async_pool.rb +44 -6
- data/lib/pgbus/process/supervisor.rb +2 -1
- data/lib/pgbus/process/worker.rb +38 -1
- data/lib/pgbus/serializer.rb +1 -1
- data/lib/pgbus/streams/coalescer.rb +88 -0
- data/lib/pgbus/streams/envelope.rb +20 -1
- data/lib/pgbus/streams/key.rb +35 -2
- data/lib/pgbus/streams/renderer.rb +67 -0
- data/lib/pgbus/streams.rb +150 -1
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source.rb +96 -0
- data/lib/pgbus/web/stream_app.rb +8 -4
- data/lib/pgbus/web/streamer/connection.rb +15 -1
- data/lib/pgbus/web/streamer/falcon_connection.rb +9 -1
- data/lib/pgbus/web/streamer/heartbeat.rb +23 -1
- data/lib/pgbus/web/streamer/stream_event_dispatcher.rb +129 -14
- data/lib/pgbus.rb +11 -0
- data/lib/tasks/pgbus_queues.rake +54 -0
- metadata +10 -3
|
@@ -32,18 +32,34 @@ module Pgbus
|
|
|
32
32
|
# disambiguate from Pgbus::Process::Dispatcher, which is an
|
|
33
33
|
# unrelated worker-side pool coordinator. See issue #98 item 8.
|
|
34
34
|
class StreamEventDispatcher
|
|
35
|
-
WakeMessage
|
|
36
|
-
ConnectMessage
|
|
37
|
-
DisconnectMessage
|
|
35
|
+
WakeMessage = Listener::WakeMessage
|
|
36
|
+
ConnectMessage = Data.define(:connection)
|
|
37
|
+
DisconnectMessage = Data.define(:connection)
|
|
38
|
+
# Posted by the Heartbeat once per tick with the current presence
|
|
39
|
+
# connections, so the touch (a last_seen_at refresh) runs on the
|
|
40
|
+
# dispatcher thread where AR connections are released each pass.
|
|
41
|
+
PresenceTouchMessage = Data.define(:connections)
|
|
38
42
|
|
|
39
43
|
# An unwrapped stream broadcast. Similar shape to
|
|
40
44
|
# Pgbus::Client::ReadAfter::Envelope (msg_id + payload) so
|
|
41
45
|
# Connection#enqueue can consume either type via duck typing,
|
|
42
|
-
# but adds
|
|
43
|
-
# Pgbus::Streams::Stream#broadcast
|
|
44
|
-
# visible_to
|
|
45
|
-
#
|
|
46
|
-
|
|
46
|
+
# but adds two delivery-control fields carried through from
|
|
47
|
+
# Pgbus::Streams::Stream#broadcast:
|
|
48
|
+
# - `visible_to` — audience filter label (evaluated per-connection)
|
|
49
|
+
# - `exclude` — a connection id to skip (actor-echo suppression:
|
|
50
|
+
# the broadcaster's own SSE connection does not
|
|
51
|
+
# receive the echo of its own broadcast)
|
|
52
|
+
# The Dispatcher uses both to decide per-connection delivery;
|
|
53
|
+
# Connection never sees either field.
|
|
54
|
+
# - `event` — the SSE `event:` name for the delivered frame.
|
|
55
|
+
# nil means the default (turbo-stream); a typed
|
|
56
|
+
# name (e.g. "presence", "reactive") lets clients
|
|
57
|
+
# route without sniffing the HTML (issue #170).
|
|
58
|
+
StreamEnvelope = Data.define(:msg_id, :enqueued_at, :payload, :source, :visible_to, :exclude, :event) do
|
|
59
|
+
def initialize(msg_id:, enqueued_at:, payload:, source:, visible_to: nil, exclude: nil, event: nil)
|
|
60
|
+
super
|
|
61
|
+
end
|
|
62
|
+
end
|
|
47
63
|
|
|
48
64
|
DEFAULT_READ_LIMIT = 500
|
|
49
65
|
|
|
@@ -51,13 +67,18 @@ module Pgbus
|
|
|
51
67
|
|
|
52
68
|
def initialize(client:, registry:, listener:, dispatch_queue:,
|
|
53
69
|
logger: Pgbus.logger, read_limit: DEFAULT_READ_LIMIT,
|
|
54
|
-
filters: nil, config: nil, stream_counter: nil
|
|
70
|
+
filters: nil, config: nil, stream_counter: nil,
|
|
71
|
+
presence_provider: nil)
|
|
55
72
|
@client = client
|
|
56
73
|
@registry = registry
|
|
57
74
|
@listener = listener
|
|
58
75
|
@queue = dispatch_queue
|
|
59
76
|
@logger = logger
|
|
60
77
|
@read_limit = read_limit
|
|
78
|
+
# Vends a presence handle for a logical stream name. Injected so
|
|
79
|
+
# tests can record join/leave/touch without a DB. Production
|
|
80
|
+
# defaults to the real per-stream Presence via Pgbus.stream.
|
|
81
|
+
@presence_provider = presence_provider || ->(name) { Pgbus.stream(name).presence }
|
|
61
82
|
# Filters default to the process-wide registry so production
|
|
62
83
|
# code picks up whatever was registered at boot. Tests inject
|
|
63
84
|
# a fresh Filters instance to avoid cross-test pollution.
|
|
@@ -175,9 +196,10 @@ module Pgbus
|
|
|
175
196
|
|
|
176
197
|
def handle(msg)
|
|
177
198
|
case msg
|
|
178
|
-
when WakeMessage
|
|
179
|
-
when ConnectMessage
|
|
180
|
-
when DisconnectMessage
|
|
199
|
+
when WakeMessage then handle_wake(msg)
|
|
200
|
+
when ConnectMessage then handle_connect(msg)
|
|
201
|
+
when DisconnectMessage then handle_disconnect(msg)
|
|
202
|
+
when PresenceTouchMessage then handle_presence_touch(msg)
|
|
181
203
|
else
|
|
182
204
|
@logger.warn { "[Pgbus::Streamer::StreamEventDispatcher] unknown message: #{msg.class}" }
|
|
183
205
|
end
|
|
@@ -239,6 +261,7 @@ module Pgbus
|
|
|
239
261
|
|
|
240
262
|
visible_to = parsed["visible_to"]
|
|
241
263
|
visible_to = visible_to.to_sym if visible_to.is_a?(String)
|
|
264
|
+
exclude = parsed["exclude"]
|
|
242
265
|
|
|
243
266
|
@ephemeral_seq += 1
|
|
244
267
|
envelope = StreamEnvelope.new(
|
|
@@ -246,7 +269,9 @@ module Pgbus
|
|
|
246
269
|
enqueued_at: Time.now.utc.iso8601(6),
|
|
247
270
|
payload: html,
|
|
248
271
|
source: "ephemeral",
|
|
249
|
-
visible_to: visible_to
|
|
272
|
+
visible_to: visible_to,
|
|
273
|
+
exclude: exclude,
|
|
274
|
+
event: normalize_sse_event(parsed["event"])
|
|
250
275
|
)
|
|
251
276
|
|
|
252
277
|
registered.each do |conn|
|
|
@@ -320,6 +345,7 @@ module Pgbus
|
|
|
320
345
|
else
|
|
321
346
|
@stream_counter.increment_connections(stream)
|
|
322
347
|
@registry.register(connection)
|
|
348
|
+
presence_join(connection, stream)
|
|
323
349
|
end
|
|
324
350
|
|
|
325
351
|
record_stat(
|
|
@@ -346,6 +372,7 @@ module Pgbus
|
|
|
346
372
|
removed = @registry.unregister(connection)
|
|
347
373
|
@scanned_cursor.delete(connection)
|
|
348
374
|
@stream_counter.decrement_connections(stream) if removed
|
|
375
|
+
presence_leave(connection, stream)
|
|
349
376
|
cleanup_stream_if_unused(stream)
|
|
350
377
|
|
|
351
378
|
record_stat(
|
|
@@ -355,6 +382,21 @@ module Pgbus
|
|
|
355
382
|
)
|
|
356
383
|
end
|
|
357
384
|
|
|
385
|
+
# Touches (refreshes last_seen_at for) the presence members on the
|
|
386
|
+
# given connections. Posted by the Heartbeat each tick so idle but
|
|
387
|
+
# still-connected members don't get swept. Connections without a
|
|
388
|
+
# presence member (non-presence streams, anonymous) are skipped.
|
|
389
|
+
def handle_presence_touch(msg)
|
|
390
|
+
msg.connections.each do |connection|
|
|
391
|
+
member_id = presence_member_of(connection)
|
|
392
|
+
next unless member_id
|
|
393
|
+
|
|
394
|
+
@presence_provider.call(connection.stream_name).touch(member_id: member_id)
|
|
395
|
+
rescue StandardError => e
|
|
396
|
+
@logger.error { "[Pgbus::Streamer::StreamEventDispatcher] presence touch failed: #{e.class}: #{e.message}" }
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
358
400
|
# If this stream has no remaining subscribers (registered or
|
|
359
401
|
# in-flight), release all per-stream state so long-running
|
|
360
402
|
# processes don't leak memory proportional to unique stream
|
|
@@ -426,6 +468,22 @@ module Pgbus
|
|
|
426
468
|
@client.config.queue_name(stream_name)
|
|
427
469
|
end
|
|
428
470
|
|
|
471
|
+
# Sanitizes a typed SSE event name from an untrusted broadcast
|
|
472
|
+
# payload before it reaches the SSE `event:` line. Returns nil
|
|
473
|
+
# (→ the default turbo-stream event) for non-strings, blanks, or
|
|
474
|
+
# any value containing CR/LF — a crafted event with a newline could
|
|
475
|
+
# otherwise inject extra SSE fields (a forged id:/data:) into the
|
|
476
|
+
# frame and corrupt cursor/event routing. Defense in depth with
|
|
477
|
+
# Envelope.message, which also strips newlines.
|
|
478
|
+
def normalize_sse_event(value)
|
|
479
|
+
return nil unless value.is_a?(String)
|
|
480
|
+
|
|
481
|
+
event = value.strip
|
|
482
|
+
return nil if event.empty? || event.match?(/[\r\n]/)
|
|
483
|
+
|
|
484
|
+
event
|
|
485
|
+
end
|
|
486
|
+
|
|
429
487
|
# Pgbus::Streams::Stream#broadcast wraps HTML payloads as
|
|
430
488
|
# {"html": "..."} so PGMQ's JSONB column accepts them. Here we
|
|
431
489
|
# unwrap the html field and return a new envelope whose payload
|
|
@@ -442,13 +500,16 @@ module Pgbus
|
|
|
442
500
|
|
|
443
501
|
visible_to = parsed["visible_to"]
|
|
444
502
|
visible_to = visible_to.to_sym if visible_to.is_a?(String)
|
|
503
|
+
exclude = parsed["exclude"]
|
|
445
504
|
|
|
446
505
|
StreamEnvelope.new(
|
|
447
506
|
msg_id: envelope.msg_id,
|
|
448
507
|
enqueued_at: envelope.enqueued_at,
|
|
449
508
|
payload: html,
|
|
450
509
|
source: envelope.source,
|
|
451
|
-
visible_to: visible_to
|
|
510
|
+
visible_to: visible_to,
|
|
511
|
+
exclude: exclude,
|
|
512
|
+
event: normalize_sse_event(parsed["event"])
|
|
452
513
|
)
|
|
453
514
|
rescue JSON::ParserError
|
|
454
515
|
envelope
|
|
@@ -460,13 +521,67 @@ module Pgbus
|
|
|
460
521
|
# Filters registry. Envelopes that predate the StreamEnvelope
|
|
461
522
|
# refactor (plain ReadAfter::Envelope with no visible_to) also
|
|
462
523
|
# pass through.
|
|
524
|
+
#
|
|
525
|
+
# Actor-echo suppression: an envelope carrying `exclude:` (a
|
|
526
|
+
# connection id) is dropped for the connection whose id matches.
|
|
527
|
+
# This lets the broadcaster's own SSE connection skip the echo of
|
|
528
|
+
# its own broadcast — the actor already applied the change via its
|
|
529
|
+
# action's HTTP response, so re-applying the SSE echo would
|
|
530
|
+
# double-apply (re-run animations, clobber optimistic edits). The
|
|
531
|
+
# exclude check runs *before* the audience filter so an excluded
|
|
532
|
+
# actor is skipped even when it would otherwise match visible_to.
|
|
463
533
|
def visible_envelopes_for(envelopes, connection)
|
|
464
534
|
envelopes.select do |envelope|
|
|
535
|
+
next false if excluded?(envelope, connection)
|
|
536
|
+
|
|
465
537
|
label = envelope.respond_to?(:visible_to) ? envelope.visible_to : nil
|
|
466
538
|
@filters.visible?(label, connection.context)
|
|
467
539
|
end
|
|
468
540
|
end
|
|
469
541
|
|
|
542
|
+
# True when the envelope names this connection in its `exclude`
|
|
543
|
+
# field. Guarded by respond_to? so plain ReadAfter::Envelopes
|
|
544
|
+
# (no exclude field) and connections without an id never match.
|
|
545
|
+
def excluded?(envelope, connection)
|
|
546
|
+
return false unless envelope.respond_to?(:exclude)
|
|
547
|
+
|
|
548
|
+
exclude = envelope.exclude
|
|
549
|
+
return false if exclude.nil? || exclude.to_s.empty?
|
|
550
|
+
|
|
551
|
+
connection.respond_to?(:id) && connection.id.to_s == exclude.to_s
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# Connection-driven presence (issue #169). Auto-joins a member when
|
|
555
|
+
# the stream is configured for presence and the connection's
|
|
556
|
+
# authorize-context yields a member id. Stores the member id on the
|
|
557
|
+
# connection so handle_disconnect/handle_presence_touch can act on
|
|
558
|
+
# it. Failures are logged and swallowed: a presence DB hiccup must
|
|
559
|
+
# not knock a live SSE connection out of the registry.
|
|
560
|
+
def presence_join(connection, stream)
|
|
561
|
+
return unless @config&.stream_presence?(stream)
|
|
562
|
+
|
|
563
|
+
member = @config.presence_member_for(connection.context)
|
|
564
|
+
return unless member
|
|
565
|
+
|
|
566
|
+
@presence_provider.call(stream).join(member_id: member[:id], metadata: member[:metadata] || {})
|
|
567
|
+
connection.presence_member = member[:id] if connection.respond_to?(:presence_member=)
|
|
568
|
+
rescue StandardError => e
|
|
569
|
+
@logger.error { "[Pgbus::Streamer::StreamEventDispatcher] presence join failed: #{e.class}: #{e.message}" }
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def presence_leave(connection, stream)
|
|
573
|
+
member_id = presence_member_of(connection)
|
|
574
|
+
return unless member_id
|
|
575
|
+
|
|
576
|
+
@presence_provider.call(stream).leave(member_id: member_id)
|
|
577
|
+
rescue StandardError => e
|
|
578
|
+
@logger.error { "[Pgbus::Streamer::StreamEventDispatcher] presence leave failed: #{e.class}: #{e.message}" }
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
def presence_member_of(connection)
|
|
582
|
+
connection.respond_to?(:presence_member) ? connection.presence_member : nil
|
|
583
|
+
end
|
|
584
|
+
|
|
470
585
|
def monotonic_ms
|
|
471
586
|
::Process.clock_gettime(::Process::CLOCK_MONOTONIC) * 1000.0
|
|
472
587
|
end
|
data/lib/pgbus.rb
CHANGED
|
@@ -130,6 +130,17 @@ module Pgbus
|
|
|
130
130
|
Streams::Key.stream_key(*parts, **)
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
+
# Accepts an already-built stream key verbatim (no re-keying), only
|
|
134
|
+
# enforcing the queue-name budget. Use when you hold a key string and
|
|
135
|
+
# want to pass it to both `turbo_stream_from` and a broadcaster
|
|
136
|
+
# without the colon-separator guard raising on the second call.
|
|
137
|
+
#
|
|
138
|
+
# key = Pgbus.stream_key(chat, :messages)
|
|
139
|
+
# Pgbus.stream(Pgbus.stream_key!(key)).broadcast(html)
|
|
140
|
+
def stream_key!(key)
|
|
141
|
+
Streams::Key.stream_key!(key)
|
|
142
|
+
end
|
|
143
|
+
|
|
133
144
|
def reset!
|
|
134
145
|
@client&.close
|
|
135
146
|
@client = nil
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :pgbus do
|
|
4
|
+
namespace :queues do
|
|
5
|
+
desc "Create FIFO indexes on all pgbus queues (required for grouped reads)"
|
|
6
|
+
task fifo_indexes: :environment do
|
|
7
|
+
puts "Creating FIFO indexes on all pgbus queues..."
|
|
8
|
+
Pgbus.client.create_fifo_indexes_all
|
|
9
|
+
puts "Done. FIFO indexes created on all queues."
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
desc "Create FIFO index on a specific queue (QUEUE=name)"
|
|
13
|
+
task fifo_index: :environment do
|
|
14
|
+
queue = ENV.fetch("QUEUE") do
|
|
15
|
+
abort "Usage: rake pgbus:queues:fifo_index QUEUE=<queue_name>"
|
|
16
|
+
end
|
|
17
|
+
puts "Creating FIFO index on queue '#{queue}'..."
|
|
18
|
+
Pgbus.client.create_fifo_index(queue)
|
|
19
|
+
puts "Done."
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
namespace :archives do
|
|
24
|
+
desc "Convert a queue's archive table to pg_partman-managed partitions (QUEUE=name)"
|
|
25
|
+
task partition: :environment do
|
|
26
|
+
queue = ENV.fetch("QUEUE") do
|
|
27
|
+
abort "Usage: rake pgbus:archives:partition QUEUE=<queue_name> " \
|
|
28
|
+
"[INTERVAL=10000] [RETENTION=100000] [LEADING_PARTITION=10]"
|
|
29
|
+
end
|
|
30
|
+
interval = ENV.fetch("INTERVAL", "10000")
|
|
31
|
+
retention = ENV.fetch("RETENTION", "100000")
|
|
32
|
+
leading_raw = ENV.fetch("LEADING_PARTITION", "10")
|
|
33
|
+
leading = begin
|
|
34
|
+
Integer(leading_raw, 10)
|
|
35
|
+
rescue ArgumentError, TypeError
|
|
36
|
+
abort "LEADING_PARTITION must be a positive integer, got #{leading_raw.inspect}"
|
|
37
|
+
end
|
|
38
|
+
abort "LEADING_PARTITION must be a positive integer" if leading <= 0
|
|
39
|
+
|
|
40
|
+
puts "Converting archive table for queue '#{queue}' to partitioned..."
|
|
41
|
+
puts " Partition interval: #{interval}"
|
|
42
|
+
puts " Retention interval: #{retention}"
|
|
43
|
+
puts " Leading partitions: #{leading}"
|
|
44
|
+
|
|
45
|
+
Pgbus.client.convert_archive_partitioned(
|
|
46
|
+
queue,
|
|
47
|
+
partition_interval: interval,
|
|
48
|
+
retention_interval: retention,
|
|
49
|
+
leading_partition: leading
|
|
50
|
+
)
|
|
51
|
+
puts "Done. Archive table is now partitioned."
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pgbus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -63,14 +63,14 @@ dependencies:
|
|
|
63
63
|
requirements:
|
|
64
64
|
- - "~>"
|
|
65
65
|
- !ruby/object:Gem::Version
|
|
66
|
-
version:
|
|
66
|
+
version: 0.7.0
|
|
67
67
|
type: :runtime
|
|
68
68
|
prerelease: false
|
|
69
69
|
version_requirements: !ruby/object:Gem::Requirement
|
|
70
70
|
requirements:
|
|
71
71
|
- - "~>"
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
|
-
version:
|
|
73
|
+
version: 0.7.0
|
|
74
74
|
- !ruby/object:Gem::Dependency
|
|
75
75
|
name: railties
|
|
76
76
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -126,6 +126,7 @@ files:
|
|
|
126
126
|
- app/controllers/pgbus/api/metrics_controller.rb
|
|
127
127
|
- app/controllers/pgbus/api/stats_controller.rb
|
|
128
128
|
- app/controllers/pgbus/application_controller.rb
|
|
129
|
+
- app/controllers/pgbus/batches_controller.rb
|
|
129
130
|
- app/controllers/pgbus/dashboard_controller.rb
|
|
130
131
|
- app/controllers/pgbus/dead_letter_controller.rb
|
|
131
132
|
- app/controllers/pgbus/events_controller.rb
|
|
@@ -161,6 +162,9 @@ files:
|
|
|
161
162
|
- app/models/pgbus/stream_stat.rb
|
|
162
163
|
- app/models/pgbus/uniqueness_key.rb
|
|
163
164
|
- app/views/layouts/pgbus/application.html.erb
|
|
165
|
+
- app/views/pgbus/batches/_batches_table.html.erb
|
|
166
|
+
- app/views/pgbus/batches/index.html.erb
|
|
167
|
+
- app/views/pgbus/batches/show.html.erb
|
|
164
168
|
- app/views/pgbus/dashboard/_processes_table.html.erb
|
|
165
169
|
- app/views/pgbus/dashboard/_queue_health.html.erb
|
|
166
170
|
- app/views/pgbus/dashboard/_queues_table.html.erb
|
|
@@ -309,11 +313,13 @@ files:
|
|
|
309
313
|
- lib/pgbus/stat_buffer.rb
|
|
310
314
|
- lib/pgbus/streams.rb
|
|
311
315
|
- lib/pgbus/streams/broadcastable_override.rb
|
|
316
|
+
- lib/pgbus/streams/coalescer.rb
|
|
312
317
|
- lib/pgbus/streams/cursor.rb
|
|
313
318
|
- lib/pgbus/streams/envelope.rb
|
|
314
319
|
- lib/pgbus/streams/filters.rb
|
|
315
320
|
- lib/pgbus/streams/key.rb
|
|
316
321
|
- lib/pgbus/streams/presence.rb
|
|
322
|
+
- lib/pgbus/streams/renderer.rb
|
|
317
323
|
- lib/pgbus/streams/signed_name.rb
|
|
318
324
|
- lib/pgbus/streams/streamable.rb
|
|
319
325
|
- lib/pgbus/streams/turbo_broadcastable.rb
|
|
@@ -343,6 +349,7 @@ files:
|
|
|
343
349
|
- lib/puma/plugin/pgbus_streams.rb
|
|
344
350
|
- lib/tasks/pgbus_autovacuum.rake
|
|
345
351
|
- lib/tasks/pgbus_pgmq.rake
|
|
352
|
+
- lib/tasks/pgbus_queues.rake
|
|
346
353
|
- lib/tasks/pgbus_streams.rake
|
|
347
354
|
homepage: https://github.com/mhenrixon/pgbus
|
|
348
355
|
licenses:
|