nats_worker 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 93b89424e0d096a2be58cd427a15fada391ab6666f21a628203e18c5af882cc0
4
+ data.tar.gz: d6252f84c3191eb33dc4f88d8423b5d272c7254d2585d6d7e61bb8875e0febe6
5
+ SHA512:
6
+ metadata.gz: 75ab5a6630f6954ab17d333abb0c936359762646076047cb8435cbbe5e31ab593aeac500ec712fcc80a10ecc9ea55e087c9bff431bc13b427e8faa3807b93a7c
7
+ data.tar.gz: 7ef758ee93578d624bcc67ba7826383d4f00b740f9070ce2f7bc9bb179da65acaf36e60c4e312264dd03820d6bad37bb1fae3e994a6ab924dc41f3494f4f3a54
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexandr Prokopenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # nats_worker
2
+
3
+ Minimal Sneakers-like worker framework for **NATS JetStream**, powered by
4
+ [`nats-pure`](https://github.com/nats-io/nats-pure.rb). Designed to drop into
5
+ a Rails app: put workers in `app/workers`, run them via `bin/nats_worker` or
6
+ a rake task.
7
+
8
+ ## Installation
9
+
10
+ Add to your `Gemfile`:
11
+
12
+ ```ruby
13
+ gem "nats_worker"
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ ```ruby
19
+ # app/workers/order_worker.rb
20
+ class OrderWorker
21
+ include NatsWorker::Worker
22
+
23
+ from_stream "ORDERS",
24
+ subject: "orders.>",
25
+ durable: "order_worker",
26
+ threads: 2,
27
+ fetch: 10,
28
+ ack_wait: 30
29
+
30
+ def work(msg)
31
+ payload = JSON.parse(msg.data)
32
+ Order.create!(payload)
33
+ # Auto-ack on success, auto-nak on raised exception.
34
+ # You can still call `ack!(msg)`, `nack!(msg)`, `term!(msg)` manually.
35
+ end
36
+ end
37
+ ```
38
+
39
+ ### Configuration
40
+
41
+ ```ruby
42
+ # config/initializers/nats_worker.rb
43
+ NatsWorker.configure do |c|
44
+ c.servers = ENV.fetch("NATS_URL", "nats://127.0.0.1:4222").split(",")
45
+ c.nats_options = { name: "my-app", reconnect_time_wait: 1 }
46
+ c.logger = Rails.logger
47
+ c.default_threads = 1
48
+ c.default_fetch_size = 10
49
+ c.default_fetch_timeout = 5
50
+ c.default_ack_wait = 30
51
+ c.shutdown_timeout = 25
52
+ end
53
+ ```
54
+
55
+ ### Running
56
+
57
+ Run all registered workers:
58
+
59
+ ```sh
60
+ bundle exec nats_worker
61
+ # or
62
+ bundle exec rake nats_worker:work
63
+ ```
64
+
65
+ Run a subset (works with both):
66
+
67
+ ```sh
68
+ WORKERS=OrderWorker,PaymentWorker bundle exec nats_worker
69
+ WORKERS=OrderWorker bundle exec rake nats_worker:work
70
+ ```
71
+
72
+ Outside of Rails, point at a custom boot file:
73
+
74
+ ```sh
75
+ bundle exec nats_worker -r ./my_app.rb -w OrderWorker
76
+ ```
77
+
78
+ ### Ack semantics
79
+
80
+ * `work` returns normally → message is **acked**.
81
+ * `work` raises → message is **nak'd** (re-delivered subject to `ack_wait`).
82
+ * You may call `ack!(msg)` / `nack!(msg)` / `term!(msg)` explicitly; double-ack
83
+ is harmless.
84
+
85
+ ### Signals
86
+
87
+ `SIGINT` / `SIGTERM` trigger graceful shutdown: the runner stops fetching new
88
+ batches, lets in-flight `work` calls finish (up to `shutdown_timeout` seconds),
89
+ drains the NATS connection and exits.
90
+
91
+ ## License
92
+
93
+ MIT
94
+
95
+ ## Tests
96
+
97
+ Unit tests:
98
+
99
+ ```sh
100
+ bundle exec rspec spec/worker_spec.rb
101
+ ```
102
+
103
+ End-to-end tests run against a live NATS server with JetStream enabled. The
104
+ provided devcontainer (`.devcontainer/`) brings one up alongside the app
105
+ container, exposing it as `nats:4222`. To run them:
106
+
107
+ ```sh
108
+ # from the host:
109
+ cd .devcontainer && docker compose -p nats_worker up -d
110
+ docker exec -e NATS_URL=nats://nats:4222 nats_worker-app-1 \
111
+ bash -c "cd /workspaces/nats-worker && bundle exec rspec"
112
+ ```
113
+
114
+ If `NATS_URL` does not point at a reachable server, e2e specs are auto-skipped
115
+ (the `:integration` tag is filtered out).
116
+
data/bin/nats_worker ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "nats_worker"
5
+ require "nats_worker/cli"
6
+
7
+ NatsWorker::CLI.start(ARGV)
@@ -0,0 +1,69 @@
1
+ require "optparse"
2
+
3
+ module NatsWorker
4
+ # Parses ARGV for `bin/nats_worker` and the rake task, loads the Rails
5
+ # environment when present, resolves which worker classes to run and
6
+ # hands them off to NatsWorker::Runner.
7
+ class CLI
8
+ def self.start(argv = ARGV)
9
+ new(argv).run
10
+ end
11
+
12
+ def initialize(argv)
13
+ @argv = argv.dup
14
+ @options = { workers: nil, require: nil }
15
+ end
16
+
17
+ def run
18
+ parse!
19
+ boot_app
20
+ runner_classes = resolve_workers
21
+ Runner.new(runner_classes).run
22
+ end
23
+
24
+ private
25
+
26
+ def parse!
27
+ OptionParser.new do |o|
28
+ o.banner = "Usage: nats_worker [options]"
29
+ o.on("-w", "--workers W1,W2", Array, "Worker class names to run") { |v| @options[:workers] = v }
30
+ o.on("-r", "--require PATH", String, "Require a file before starting") { |v| @options[:require] = v }
31
+ o.on("-h", "--help") { puts o; exit 0 }
32
+ o.on("-v", "--version") { puts "nats_worker #{NatsWorker::VERSION}"; exit 0 }
33
+ end.parse!(@argv)
34
+
35
+ env_workers = ENV["WORKERS"]
36
+ @options[:workers] ||= env_workers.split(",").map(&:strip).reject(&:empty?) if env_workers && !env_workers.empty?
37
+ end
38
+
39
+ def boot_app
40
+ if @options[:require]
41
+ require File.expand_path(@options[:require])
42
+ return
43
+ end
44
+
45
+ env_path = File.expand_path("config/environment.rb")
46
+ if File.exist?(env_path)
47
+ ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
48
+ require env_path
49
+ Rails.application.eager_load! if defined?(Rails) && Rails.respond_to?(:application)
50
+ else
51
+ require "nats_worker"
52
+ end
53
+ end
54
+
55
+ def resolve_workers
56
+ if @options[:workers]
57
+ @options[:workers].map { |name| constantize(name) }
58
+ else
59
+ NatsWorker.workers
60
+ end
61
+ end
62
+
63
+ def constantize(name)
64
+ Object.const_get(name)
65
+ rescue NameError => e
66
+ raise NatsWorker::Error, "Unknown worker class #{name.inspect}: #{e.message}"
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,22 @@
1
+ require "logger"
2
+
3
+ module NatsWorker
4
+ class Configuration
5
+ attr_accessor :servers, :nats_options, :logger,
6
+ :default_fetch_size, :default_fetch_timeout,
7
+ :default_threads, :default_ack_wait,
8
+ :shutdown_timeout
9
+
10
+ def initialize
11
+ @servers = ENV.fetch("NATS_URL", "nats://127.0.0.1:4222").split(",")
12
+ @nats_options = {}
13
+ @logger = Logger.new($stdout)
14
+ @logger.level = Logger::INFO
15
+ @default_fetch_size = 10
16
+ @default_fetch_timeout = 5
17
+ @default_threads = 1
18
+ @default_ack_wait = 30
19
+ @shutdown_timeout = 25
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ require "rails/railtie"
2
+
3
+ module NatsWorker
4
+ class Railtie < ::Rails::Railtie
5
+ railtie_name :nats_worker
6
+
7
+ rake_tasks do
8
+ load File.expand_path("tasks.rake", __dir__)
9
+ end
10
+
11
+ # Make sure app/workers is autoloaded by Rails (it usually is via the
12
+ # default app autoload paths, but we add it explicitly to be safe).
13
+ initializer "nats_worker.autoload" do |app|
14
+ workers_path = app.root.join("app", "workers")
15
+ app.config.autoload_paths << workers_path.to_s if workers_path.exist?
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,145 @@
1
+ require "nats/client"
2
+
3
+ module NatsWorker
4
+ # Runs one or more worker classes: opens a NATS connection, creates
5
+ # JetStream pull subscriptions and dispatches incoming messages to
6
+ # each worker's #work method.
7
+ class Runner
8
+ def initialize(worker_classes, config: NatsWorker.configuration)
9
+ @worker_classes = Array(worker_classes)
10
+ @config = config
11
+ @logger = config.logger
12
+ @threads = []
13
+ @stopping = false
14
+ @nats = nil
15
+ end
16
+
17
+ def run
18
+ raise NatsWorker::Error, "no workers to run" if @worker_classes.empty?
19
+
20
+ install_signal_handlers
21
+ connect!
22
+
23
+ @worker_classes.each { |klass| start_worker(klass) }
24
+
25
+ @logger.info("[nats_worker] running #{@worker_classes.size} worker(s); pid=#{Process.pid}")
26
+ wait_for_shutdown
27
+ end
28
+
29
+ def stop
30
+ return if @stopping
31
+
32
+ @stopping = true
33
+ @logger.info("[nats_worker] shutting down...")
34
+ end
35
+
36
+ private
37
+
38
+ def install_signal_handlers
39
+ %w[INT TERM].each do |sig|
40
+ trap(sig) { stop }
41
+ end
42
+ end
43
+
44
+ def connect!
45
+ opts = { servers: @config.servers }.merge(@config.nats_options || {})
46
+ @nats = NATS.connect(**opts)
47
+ @logger.info("[nats_worker] connected to NATS: #{@config.servers.join(',')}")
48
+ end
49
+
50
+ def start_worker(klass)
51
+ sub_cfg = klass.nats_subscription
52
+ threads = sub_cfg[:threads] || @config.default_threads
53
+ instance = klass.new
54
+
55
+ threads.times do |i|
56
+ @threads << Thread.new do
57
+ Thread.current.name = "#{klass.name}##{i}" rescue nil
58
+ run_pull_loop(instance, klass, sub_cfg)
59
+ end
60
+ end
61
+ end
62
+
63
+ def run_pull_loop(instance, klass, sub_cfg)
64
+ js = @nats.jetstream
65
+ sub = subscribe(js, sub_cfg)
66
+
67
+ fetch_size = sub_cfg[:fetch] || @config.default_fetch_size
68
+ fetch_timeout = sub_cfg[:fetch_timeout] || @config.default_fetch_timeout
69
+
70
+ until @stopping
71
+ begin
72
+ msgs = sub.fetch(fetch_size, timeout: fetch_timeout)
73
+ rescue NATS::IO::Timeout
74
+ next
75
+ rescue StandardError => e
76
+ @logger.error("[#{klass.name}] fetch error: #{e.class}: #{e.message}")
77
+ sleep 1
78
+ next
79
+ end
80
+
81
+ msgs.each do |msg|
82
+ break if @stopping
83
+ dispatch(instance, klass, msg)
84
+ end
85
+ end
86
+ rescue StandardError => e
87
+ @logger.error("[#{klass.name}] fatal: #{e.class}: #{e.message}\n#{e.backtrace.first(10).join("\n")}")
88
+ end
89
+
90
+ def subscribe(js, sub_cfg)
91
+ subject = sub_cfg[:subject] || ">"
92
+ ensure_consumer(js, sub_cfg, subject)
93
+ js.pull_subscribe(subject, sub_cfg[:durable], stream: sub_cfg[:stream])
94
+ end
95
+
96
+ def ensure_consumer(js, sub_cfg, subject)
97
+ js.consumer_info(sub_cfg[:stream], sub_cfg[:durable])
98
+ rescue NATS::JetStream::Error::NotFound
99
+ ack_wait = sub_cfg[:ack_wait] || @config.default_ack_wait
100
+ cfg = {
101
+ durable_name: sub_cfg[:durable],
102
+ ack_policy: "explicit",
103
+ ack_wait: ack_wait * 1_000_000_000,
104
+ filter_subject: subject
105
+ }.merge(sub_cfg[:consumer_config] || {})
106
+ js.add_consumer(sub_cfg[:stream], NATS::JetStream::API::ConsumerConfig.new(**cfg))
107
+ end
108
+
109
+ def dispatch(instance, klass, msg)
110
+ instance.work(msg)
111
+ safe_ack(msg)
112
+ rescue StandardError => e
113
+ @logger.error("[#{klass.name}] work error: #{e.class}: #{e.message}")
114
+ begin
115
+ msg.nak
116
+ rescue StandardError => nak_err
117
+ @logger.error("[#{klass.name}] nak failed: #{nak_err.class}: #{nak_err.message}")
118
+ end
119
+ end
120
+
121
+ def safe_ack(msg)
122
+ msg.ack
123
+ rescue NATS::JetStream::Error::MsgAlreadyAckd
124
+ # User already acked manually — fine.
125
+ rescue StandardError => e
126
+ @logger.warn("[nats_worker] ack failed: #{e.class}: #{e.message}")
127
+ end
128
+
129
+ def wait_for_shutdown
130
+ sleep 0.5 until @stopping
131
+ deadline = Time.now + @config.shutdown_timeout
132
+ @threads.each do |t|
133
+ remaining = deadline - Time.now
134
+ t.join(remaining.positive? ? remaining : 0)
135
+ end
136
+ @threads.select(&:alive?).each(&:kill)
137
+ @nats&.drain
138
+ rescue StandardError => e
139
+ @logger.error("[nats_worker] shutdown error: #{e.class}: #{e.message}")
140
+ ensure
141
+ @nats&.close rescue nil
142
+ @logger.info("[nats_worker] stopped")
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,11 @@
1
+ require "nats_worker/cli"
2
+
3
+ namespace :nats_worker do
4
+ desc "Run NATS JetStream workers (use WORKERS=Foo,Bar to limit)"
5
+ task work: :environment do
6
+ Rails.application.eager_load! if defined?(Rails)
7
+ argv = []
8
+ argv += ["--workers", ENV["WORKERS"]] if ENV["WORKERS"] && !ENV["WORKERS"].empty?
9
+ NatsWorker::CLI.start(argv)
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module NatsWorker
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -0,0 +1,78 @@
1
+ module NatsWorker
2
+ # Mix in to make a class a NATS JetStream worker.
3
+ #
4
+ # Example:
5
+ # class OrderWorker
6
+ # include NatsWorker::Worker
7
+ #
8
+ # from_stream "ORDERS",
9
+ # subject: "orders.>",
10
+ # durable: "order_worker",
11
+ # threads: 2,
12
+ # fetch: 10,
13
+ # ack_wait: 30
14
+ #
15
+ # def work(msg)
16
+ # payload = JSON.parse(msg.data)
17
+ # # ... do something ...
18
+ # end
19
+ # end
20
+ module Worker
21
+ def self.included(base)
22
+ base.extend(ClassMethods)
23
+ NatsWorker.register(base)
24
+ end
25
+
26
+ # The framework auto-acks on success and nacks on exception, but workers
27
+ # may call these explicitly if they want finer control.
28
+ def ack!(msg); msg.ack; end
29
+ def nack!(msg); msg.nak; end
30
+ def term!(msg); msg.term; end
31
+
32
+ def logger
33
+ NatsWorker.logger
34
+ end
35
+
36
+ module ClassMethods
37
+ # Subscription configuration for this worker.
38
+ #
39
+ # @param stream [String] JetStream stream name (required)
40
+ # @param subject [String] subject filter (defaults to ">" inside the stream)
41
+ # @param durable [String] durable consumer name (defaults to class name underscored)
42
+ # @param threads [Integer] number of pull-loop threads
43
+ # @param fetch [Integer] batch size for each pull
44
+ # @param fetch_timeout [Integer] seconds to wait for a batch
45
+ # @param ack_wait [Integer] consumer ack_wait in seconds
46
+ # @param consumer_config [Hash] extra fields merged into the consumer config
47
+ def from_stream(stream, subject: nil, durable: nil, threads: nil,
48
+ fetch: nil, fetch_timeout: nil, ack_wait: nil,
49
+ consumer_config: {})
50
+ @nats_subscription = {
51
+ stream: stream,
52
+ subject: subject,
53
+ durable: durable || default_durable_name,
54
+ threads: threads,
55
+ fetch: fetch,
56
+ fetch_timeout: fetch_timeout,
57
+ ack_wait: ack_wait,
58
+ consumer_config: consumer_config
59
+ }
60
+ end
61
+
62
+ def nats_subscription
63
+ @nats_subscription or raise NatsWorker::Error,
64
+ "#{name} did not declare from_stream(...)"
65
+ end
66
+
67
+ private
68
+
69
+ def default_durable_name
70
+ n = name.to_s.gsub("::", "/")
71
+ n.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
72
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
73
+ .tr("/", "_")
74
+ .downcase
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,35 @@
1
+ require "logger"
2
+
3
+ require "nats_worker/version"
4
+ require "nats_worker/configuration"
5
+ require "nats_worker/worker"
6
+ require "nats_worker/runner"
7
+
8
+ module NatsWorker
9
+ class Error < StandardError; end
10
+
11
+ class << self
12
+ def configure
13
+ yield configuration
14
+ end
15
+
16
+ def configuration
17
+ @configuration ||= Configuration.new
18
+ end
19
+
20
+ def logger
21
+ configuration.logger
22
+ end
23
+
24
+ # Registry of worker classes that included NatsWorker::Worker.
25
+ def workers
26
+ @workers ||= []
27
+ end
28
+
29
+ def register(worker_class)
30
+ workers << worker_class unless workers.include?(worker_class)
31
+ end
32
+ end
33
+ end
34
+
35
+ require "nats_worker/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,21 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "nats_worker"
3
+ s.version = "0.1.0"
4
+ s.summary = "Minimal worker framework for NATS JetStream"
5
+ s.description = "Drop-in workers for Rails (app/workers) that consume NATS JetStream " \
6
+ "streams via pull-based subscriptions, using nats-pure."
7
+ s.authors = ["nats_worker contributors"]
8
+ s.license = "MIT"
9
+ s.required_ruby_version = ">= 2.7"
10
+
11
+ s.files = Dir["lib/**/*", "bin/*", "README.md", "LICENSE", "nats_worker.gemspec"]
12
+ s.bindir = "bin"
13
+ s.executables = ["nats_worker"]
14
+ s.require_paths = ["lib"]
15
+
16
+ s.add_dependency "nats-pure", ">= 2.3"
17
+ s.add_dependency "concurrent-ruby", ">= 1.1"
18
+
19
+ s.add_development_dependency "rspec", "~> 3.12"
20
+ s.add_development_dependency "rake", "~> 13.0"
21
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nats_worker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - nats_worker contributors
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-05-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nats-pure
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '2.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.12'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.12'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ description: Drop-in workers for Rails (app/workers) that consume NATS JetStream streams
70
+ via pull-based subscriptions, using nats-pure.
71
+ email:
72
+ executables:
73
+ - nats_worker
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - LICENSE
78
+ - README.md
79
+ - bin/nats_worker
80
+ - lib/nats_worker.rb
81
+ - lib/nats_worker/cli.rb
82
+ - lib/nats_worker/configuration.rb
83
+ - lib/nats_worker/railtie.rb
84
+ - lib/nats_worker/runner.rb
85
+ - lib/nats_worker/tasks.rake
86
+ - lib/nats_worker/version.rb
87
+ - lib/nats_worker/worker.rb
88
+ - nats_worker.gemspec
89
+ homepage:
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '2.7'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubygems_version: 3.5.22
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Minimal worker framework for NATS JetStream
112
+ test_files: []