natswork-client 0.0.1

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.
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/named_base'
5
+
6
+ module NatsWork
7
+ module Generators
8
+ class JobGenerator < Rails::Generators::NamedBase
9
+ desc 'Creates a new NatsWork job class'
10
+
11
+ source_root File.expand_path('templates', __dir__)
12
+
13
+ argument :methods, type: :array, default: [], banner: 'method method'
14
+
15
+ class_option :queue, type: :string, default: 'default',
16
+ desc: 'Queue name for this job'
17
+
18
+ class_option :retries, type: :numeric, default: 3,
19
+ desc: 'Number of retries for this job'
20
+
21
+ class_option :timeout, type: :numeric, default: 30,
22
+ desc: 'Timeout in seconds for this job'
23
+
24
+ def create_job_file
25
+ template 'job.rb.erb', File.join('app/jobs', class_path, "#{file_name}_job.rb")
26
+ end
27
+
28
+ def create_test_file
29
+ if defined?(RSpec)
30
+ template 'job_spec.rb.erb',
31
+ File.join('spec/jobs', class_path, "#{file_name}_job_spec.rb")
32
+ else
33
+ template 'job_test.rb.erb',
34
+ File.join('test/jobs', class_path, "#{file_name}_job_test.rb")
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% module_namespacing do -%>
4
+ class <%= class_name %>Job < NatsWork::Job
5
+ queue '<%= options[:queue] %>'
6
+ retries <%= options[:retries] %>
7
+ timeout <%= options[:timeout] %>
8
+
9
+ def perform(<%= methods.empty? ? '*args' : methods.map { |m| m.underscore }.join(', ') %>)
10
+ <% if methods.any? -%>
11
+ <% methods.each do |method| -%>
12
+ # Process <%= method.underscore %>
13
+ <% end -%>
14
+ <% else -%>
15
+ # Implement job logic here
16
+ <% end -%>
17
+ end
18
+ end
19
+ <% end -%>
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ <% module_namespacing do -%>
6
+ RSpec.describe <%= class_name %>Job, type: :job do
7
+ describe '#perform' do
8
+ it 'executes the job successfully' do
9
+ job = described_class.new
10
+ <% if methods.any? -%>
11
+ expect { job.perform(<%= methods.map { |m| ":#{m.underscore}" }.join(', ') %>) }.not_to raise_error
12
+ <% else -%>
13
+ expect { job.perform('test') }.not_to raise_error
14
+ <% end -%>
15
+ end
16
+
17
+ it 'is registered in the job registry' do
18
+ expect(NatsWork::Registry.instance.registered?('<%= class_name %>Job')).to be true
19
+ end
20
+
21
+ it 'uses the correct queue' do
22
+ metadata = NatsWork::Registry.instance.metadata_for('<%= class_name %>Job')
23
+ expect(metadata[:queue]).to eq('<%= options[:queue] %>')
24
+ end
25
+ end
26
+ end
27
+ <% end -%>
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ <% module_namespacing do -%>
6
+ class <%= class_name %>JobTest < ActiveSupport::TestCase
7
+ def setup
8
+ @job = <%= class_name %>Job.new
9
+ end
10
+
11
+ test 'performs job successfully' do
12
+ <% if methods.any? -%>
13
+ assert_nothing_raised { @job.perform(<%= methods.map { |m| ":#{m.underscore}" }.join(', ') %>) }
14
+ <% else -%>
15
+ assert_nothing_raised { @job.perform('test') }
16
+ <% end -%>
17
+ end
18
+
19
+ test 'is registered in the job registry' do
20
+ assert NatsWork::Registry.instance.registered?('<%= class_name %>Job')
21
+ end
22
+
23
+ test 'uses the correct queue' do
24
+ metadata = NatsWork::Registry.instance.metadata_for('<%= class_name %>Job')
25
+ assert_equal '<%= options[:queue] %>', metadata[:queue]
26
+ end
27
+ end
28
+ <% end -%>
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/railtie'
4
+
5
+ module NatsWork
6
+ class Railtie < ::Rails::Railtie
7
+ initializer 'natswork.setup' do
8
+ ActiveSupport.on_load(:after_initialize) do
9
+ NatsWork::Railtie.load_jobs
10
+ end
11
+ end
12
+
13
+ config.to_prepare do
14
+ NatsWork::Railtie.load_jobs if Rails.application.config.cache_classes == false
15
+ end
16
+
17
+ def self.load_jobs
18
+ return unless Rails.application
19
+
20
+ jobs_path = Rails.root.join('app', 'jobs')
21
+ return unless jobs_path.exist?
22
+
23
+ NatsWork::Registry.instance.scan_directory(jobs_path.to_s)
24
+
25
+ Rails.logger.info "[NatsWork] Loaded #{NatsWork::Registry.instance.size} job(s)"
26
+ end
27
+
28
+ console do
29
+ # Load console helpers when Rails console starts
30
+ require 'natswork/rails/console_helpers'
31
+ end
32
+
33
+ generators do
34
+ require 'natswork/rails/generators/job_generator'
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'concurrent'
5
+ require 'json'
6
+ require 'natswork/errors'
7
+
8
+ module NatsWork
9
+ class Registry
10
+ include Singleton
11
+
12
+ def initialize
13
+ @mutex = Mutex.new
14
+ @jobs = Concurrent::Hash.new
15
+ @metadata = Concurrent::Hash.new
16
+ end
17
+
18
+ def register(name, job_class, options = {})
19
+ name = name.to_s
20
+ @mutex.synchronize do
21
+ @jobs[name] = job_class
22
+ @metadata[name] = build_metadata(job_class, options)
23
+ end
24
+ end
25
+
26
+ def lookup(name)
27
+ @jobs[name.to_s]
28
+ end
29
+
30
+ def lookup!(name)
31
+ job_class = lookup(name)
32
+ raise UnknownJobError, "Unknown job: #{name}" unless job_class
33
+
34
+ job_class
35
+ end
36
+
37
+ def registered?(name)
38
+ @jobs.key?(name.to_s)
39
+ end
40
+
41
+ def jobs_for_queue(queue_name)
42
+ @jobs.select do |name, _|
43
+ metadata = @metadata[name]
44
+ metadata && metadata[:queue] == queue_name
45
+ end.to_a
46
+ end
47
+
48
+ def queues
49
+ @metadata.values.map { |m| m[:queue] }.compact.uniq
50
+ end
51
+
52
+ def all
53
+ @jobs.to_a
54
+ end
55
+
56
+ def size
57
+ @jobs.size
58
+ end
59
+
60
+ def clear!
61
+ @mutex.synchronize do
62
+ @jobs.clear
63
+ @metadata.clear
64
+ end
65
+ end
66
+
67
+ def metadata_for(name)
68
+ @metadata[name.to_s]
69
+ end
70
+
71
+ def alias(alias_name, source_name)
72
+ source_class = lookup!(source_name)
73
+ source_metadata = @metadata[source_name.to_s]
74
+
75
+ @mutex.synchronize do
76
+ @jobs[alias_name.to_s] = source_class
77
+ @metadata[alias_name.to_s] = source_metadata.dup if source_metadata
78
+ end
79
+ end
80
+
81
+ def to_h
82
+ @mutex.synchronize do
83
+ @jobs.each_with_object({}) do |(name, klass), hash|
84
+ metadata = @metadata[name] || {}
85
+ hash[name] = metadata.merge(class: klass.name)
86
+ end
87
+ end
88
+ end
89
+
90
+ def to_json(*args)
91
+ to_h.transform_values do |meta|
92
+ meta.except(:class).merge('class' => meta[:class])
93
+ end.to_json(*args)
94
+ end
95
+
96
+ def load_from_hash(config)
97
+ @mutex.synchronize do
98
+ config.each do |name, settings|
99
+ class_name = settings['class'] || settings[:class]
100
+ job_class = Object.const_get(class_name)
101
+
102
+ options = settings.dup
103
+ options.delete('class')
104
+ options.delete(:class)
105
+
106
+ @jobs[name] = job_class
107
+ @metadata[name] = build_metadata(job_class, symbolize_keys(options))
108
+ end
109
+ end
110
+ end
111
+
112
+ def scan_directory(path)
113
+ Dir.glob(File.join(path, '**', '*.rb')).sort.each do |file|
114
+ require file
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def build_metadata(job_class, options)
121
+ {
122
+ class: job_class,
123
+ queue: options[:queue] || 'default',
124
+ retries: options[:retries],
125
+ timeout: options[:timeout]
126
+ }.compact
127
+ end
128
+
129
+ def symbolize_keys(hash)
130
+ hash.transform_keys(&:to_sym)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'time'
5
+ require 'date'
6
+
7
+ module NatsWork
8
+ class SerializationError < Error; end
9
+
10
+ module Serializer
11
+ module_function
12
+
13
+ def dump(object)
14
+ JSON.generate(prepare_for_json(object))
15
+ rescue StandardError => e
16
+ raise SerializationError, "Cannot serialize object: #{e.message}"
17
+ end
18
+
19
+ def load(json_string, symbolize_keys: true)
20
+ JSON.parse(json_string.to_s, symbolize_names: symbolize_keys)
21
+ rescue JSON::ParserError => e
22
+ raise SerializationError, "Failed to parse JSON: #{e.message}"
23
+ end
24
+
25
+ def safe_dump(object)
26
+ dump(object)
27
+ rescue SerializationError
28
+ nil
29
+ end
30
+
31
+ def safe_load(json_string, symbolize_keys: true)
32
+ load(json_string, symbolize_keys: symbolize_keys)
33
+ rescue SerializationError
34
+ nil
35
+ end
36
+
37
+ private
38
+
39
+ def prepare_for_json(object)
40
+ case object
41
+ when Hash
42
+ object.transform_keys(&:to_s).transform_values { |v| prepare_for_json(v) }
43
+ when Array
44
+ object.map { |item| prepare_for_json(item) }
45
+ when Symbol
46
+ object.to_s
47
+ when Time
48
+ object.utc.iso8601
49
+ when Date
50
+ object.iso8601
51
+ when DateTime
52
+ object.to_time.utc.iso8601
53
+ when String, Numeric, TrueClass, FalseClass, NilClass
54
+ object
55
+ else
56
+ if object.respond_to?(:to_h)
57
+ prepare_for_json(object.to_h)
58
+ elsif object.respond_to?(:as_json)
59
+ prepare_for_json(object.as_json)
60
+ else
61
+ raise SerializationError, "Cannot serialize object of type #{object.class}"
62
+ end
63
+ end
64
+ end
65
+
66
+ module_function :prepare_for_json
67
+ end
68
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NatsWork
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Main entry point for natswork-client gem
4
+ require 'natswork'
data/lib/natswork.rb ADDED
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'json'
5
+ require 'securerandom'
6
+ require 'time'
7
+
8
+ # Load core components first
9
+ require_relative 'natswork/version'
10
+ require_relative 'natswork/errors'
11
+ require_relative 'natswork/configuration'
12
+
13
+ # Core NatsWork module
14
+ module NatsWork
15
+ class << self
16
+ def config
17
+ @config ||= Configuration.instance
18
+ end
19
+
20
+ def configure
21
+ yield(config) if block_given?
22
+ config
23
+ end
24
+
25
+ def logger
26
+ config.logger || Logger.new($stdout)
27
+ end
28
+ end
29
+ end
30
+
31
+ # Initialize default configuration
32
+ NatsWork.configure
33
+
34
+ # Load remaining components
35
+ require_relative 'natswork/serializer'
36
+ require_relative 'natswork/logging'
37
+ require_relative 'natswork/message'
38
+ require_relative 'natswork/connection'
39
+ require_relative 'natswork/connection_pool'
40
+ require_relative 'natswork/jetstream_manager'
41
+ require_relative 'natswork/registry'
42
+ require_relative 'natswork/job'
43
+ require_relative 'natswork/client'
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: natswork-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - NatsWork Contributors
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-09-30 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: concurrent-ruby
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: multi_json
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.15'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.15'
40
+ - !ruby/object:Gem::Dependency
41
+ name: nats-pure
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.4'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.4'
54
+ - !ruby/object:Gem::Dependency
55
+ name: bundler
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '13.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '13.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rspec
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.12'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.12'
96
+ description: Dispatch jobs to NatsWork workers via NATS messaging
97
+ email:
98
+ - natswork@tesote.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - CHANGELOG.md
104
+ - LICENSE
105
+ - README.md
106
+ - lib/active_job/queue_adapters/natswork_adapter.rb
107
+ - lib/generators/natswork/install_generator.rb
108
+ - lib/generators/natswork/job_generator.rb
109
+ - lib/generators/natswork/templates/job.rb.erb
110
+ - lib/generators/natswork/templates/job_spec.rb.erb
111
+ - lib/generators/natswork/templates/natswork.rb.erb
112
+ - lib/natswork-client.rb
113
+ - lib/natswork.rb
114
+ - lib/natswork/circuit_breaker.rb
115
+ - lib/natswork/client.rb
116
+ - lib/natswork/client/version.rb
117
+ - lib/natswork/compression.rb
118
+ - lib/natswork/configuration.rb
119
+ - lib/natswork/connection.rb
120
+ - lib/natswork/connection_pool.rb
121
+ - lib/natswork/errors.rb
122
+ - lib/natswork/jetstream_manager.rb
123
+ - lib/natswork/job.rb
124
+ - lib/natswork/logging.rb
125
+ - lib/natswork/message.rb
126
+ - lib/natswork/rails/console_helpers.rb
127
+ - lib/natswork/rails/generators/job_generator.rb
128
+ - lib/natswork/rails/generators/templates/job.rb.erb
129
+ - lib/natswork/rails/generators/templates/job_spec.rb.erb
130
+ - lib/natswork/rails/generators/templates/job_test.rb.erb
131
+ - lib/natswork/railtie.rb
132
+ - lib/natswork/registry.rb
133
+ - lib/natswork/serializer.rb
134
+ - lib/natswork/version.rb
135
+ homepage: https://github.com/tesote/nats-work
136
+ licenses:
137
+ - MIT
138
+ metadata:
139
+ homepage_uri: https://github.com/tesote/nats-work
140
+ source_code_uri: https://github.com/tesote/nats-work
141
+ changelog_uri: https://github.com/tesote/nats-work/blob/main/CHANGELOG.md
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: 2.7.0
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubygems_version: 3.6.2
157
+ specification_version: 4
158
+ summary: Client gem for NatsWork job processing system
159
+ test_files: []