smplkit 1.0.14 → 1.0.15

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1457724d2c923a56cfc88a4cfbe43c53e27c908944b737a0ffb4b658f0035fed
4
- data.tar.gz: c04ecdb00222ed550f975f3dbbee7b39db29e7ca48b945b81bbca7f8a72f318d
3
+ metadata.gz: d5d5ed1af296c517f09ddd0a76660b157184f3bb763a61ae0fcf727c1a3f5a87
4
+ data.tar.gz: 487bcd0200af912fcbb1acdf52967e87bf485c1b1ced27fb564cfc96ac4ee41b
5
5
  SHA512:
6
- metadata.gz: 86481042695d43a1ac553e3fc6acd2727bebda917aaf2b9fc63970ad9488b3119320fdad4435090bce9d185f1c55b48d4ac086af5a6f4a9b32008699f06a6213
7
- data.tar.gz: 3322a551e81299287f3b51bd9537868689365c14a03d9f2ec0366d4d2b9fb5e3ea8b7033abe93d2f8d6f1613fd4c559b6f2c69a62a672bf697e460a6576fc042
6
+ metadata.gz: 78bd324f3ab4c18732cf2a0a4bbef41f761ebf347501d721b941083ee4a06657bbd5f3c48e0113e63522515ed5b15566b47ecc727613925b8df866b4459d4f6e
7
+ data.tar.gz: f66aa64fdd45800335739c0d5a8fda29e4b8c51d70c94f5412ee1bdc44f09d161ded9eefd5c9252f438796c2063d53dcdad870dd949b4d2872b9751b6c54d974
data/README.md CHANGED
@@ -100,12 +100,23 @@ This creates `config/initializers/smplkit.rb` with documented examples for the p
100
100
 
101
101
  ```bash
102
102
  bundle install
103
- bundle exec rspec # unit tests
103
+ bundle exec rspec # unit tests — wrapper layer is held to 100% line coverage in CI
104
104
  bundle exec rubocop # lint
105
- make generate # regenerate clients (requires Node.js + npx)
105
+ make generate # regenerate clients
106
106
  ```
107
107
 
108
- The repository follows the standard smplkit "every commit lands on `main`" workflow — see CLAUDE.md.
108
+ `make generate` shells out to `npx --yes @openapitools/openapi-generator-cli`,
109
+ so it requires:
110
+
111
+ - Node.js 18+ (provides `npx`)
112
+ - A JRE 11+ (the openapi-generator JAR runs under Java)
113
+
114
+ Both are pre-installed on the CI matrix runners. On a developer machine,
115
+ install via Homebrew (`brew install node openjdk`) or your distribution's
116
+ package manager.
117
+
118
+ The repository follows the standard smplkit "every commit lands on `main`"
119
+ workflow — see CLAUDE.md.
109
120
 
110
121
  ## License
111
122
 
@@ -31,8 +31,8 @@ module Smplkit
31
31
  attr_reader :manage, :config, :flags, :logging
32
32
 
33
33
  # Construct, yield to the block, and close on exit.
34
- def self.open(**)
35
- client = new(**)
34
+ def self.open(**kwargs)
35
+ client = new(**kwargs)
36
36
  begin
37
37
  yield client
38
38
  ensure
@@ -185,19 +185,23 @@ module Smplkit
185
185
 
186
186
  def schedule_periodic_flush
187
187
  @flush_timer = Concurrent::TimerTask.new(execution_interval: PERIODIC_FLUSH_INTERVAL) do
188
- next if @closed
189
-
190
- begin
191
- @manage.contexts.flush
192
- @manage.flags.flush
193
- @manage.loggers.flush
194
- rescue StandardError => e
195
- Smplkit.debug("registration", "periodic flush failed: #{e.class}: #{e.message}")
196
- end
188
+ run_periodic_flush
197
189
  end
198
190
  @flush_timer.execute
199
191
  end
200
192
 
193
+ # Extracted as a private method so the timer body is reachable from
194
+ # tests without poking into Concurrent::TimerTask internals.
195
+ def run_periodic_flush
196
+ return if @closed
197
+
198
+ @manage.contexts.flush
199
+ @manage.flags.flush
200
+ @manage.loggers.flush
201
+ rescue StandardError => e
202
+ Smplkit.debug("registration", "periodic flush failed: #{e.class}: #{e.message}")
203
+ end
204
+
201
205
  def final_flush
202
206
  [@manage.contexts, @manage.flags, @manage.loggers].each do |ns|
203
207
  ns.flush
@@ -40,32 +40,6 @@ module Smplkit
40
40
  )
41
41
  end
42
42
 
43
- def build_config_request_body(config)
44
- items = {}
45
- config.items.each do |item|
46
- items[item.name] = {
47
- "value" => item.value,
48
- "type" => item.type,
49
- "description" => item.description
50
- }.compact
51
- end
52
-
53
- environments = config.environments.each_with_object({}) do |(env, env_obj), out|
54
- out[env] = { "values" => env_obj.values_raw }
55
- end
56
-
57
- # The Config schema (per the OpenAPI spec) does not include +key+ in
58
- # attributes — the resource +id+ carries the customer-facing key.
59
- attributes = {
60
- "name" => config.name,
61
- "description" => config.description,
62
- "parent" => config.parent_id,
63
- "items" => items,
64
- "environments" => environments
65
- }.compact
66
- { "data" => { "type" => "config", "id" => config.key, "attributes" => attributes } }
67
- end
68
-
69
43
  # Deep-merge two Hashes, with +override+ winning. Mirrors the Python
70
44
  # +deep_merge+ helper used by the resolver.
71
45
  def deep_merge(base, override)
@@ -28,12 +28,14 @@ module Smplkit
28
28
  "telemetry" => true
29
29
  }.freeze
30
30
 
31
- ResolvedConfig = Data.define(
32
- :api_key, :base_domain, :scheme, :environment, :service, :debug, :telemetry
31
+ ResolvedConfig = Struct.new(
32
+ :api_key, :base_domain, :scheme, :environment, :service, :debug, :telemetry,
33
+ keyword_init: true
33
34
  )
34
35
 
35
- ResolvedManagementConfig = Data.define(
36
- :api_key, :base_domain, :scheme, :debug
36
+ ResolvedManagementConfig = Struct.new(
37
+ :api_key, :base_domain, :scheme, :debug,
38
+ keyword_init: true
37
39
  )
38
40
 
39
41
  module_function
@@ -23,8 +23,8 @@ module Smplkit
23
23
  h
24
24
  end
25
25
 
26
- def to_json(*)
27
- JSON.generate(to_h, *)
26
+ def to_json(*args)
27
+ JSON.generate(to_h, *args)
28
28
  end
29
29
  end
30
30
 
@@ -47,30 +47,6 @@ module Smplkit
47
47
  rules: rules
48
48
  )
49
49
  end
50
-
51
- # Build the JSON body for a Flag create/update request.
52
- def build_flag_request_body(flag)
53
- environments = flag.environments.each_with_object({}) do |(env_key, env), out|
54
- out[env_key] = {
55
- "enabled" => env.enabled,
56
- "default" => env.default,
57
- "rules" => env.rules.map { |r| { "logic" => r.logic, "value" => r.value, "description" => r.description } }
58
- }
59
- end
60
-
61
- attributes = {
62
- "id" => flag.id,
63
- "name" => flag.name,
64
- "type" => flag.type,
65
- "default" => flag.default,
66
- "description" => flag.description,
67
- "environments" => environments
68
- }
69
- values = flag.values
70
- attributes["values"] = values.map { |v| { "name" => v.name, "value" => v.value } } if values
71
-
72
- { "data" => { "type" => "flag", "id" => flag.id, "attributes" => attributes.compact } }
73
- end
74
50
  end
75
51
  end
76
52
  end
@@ -6,7 +6,7 @@ module Smplkit
6
6
  #
7
7
  # Lives in +Flag#values+. Frozen — author values via +Flag#add_value+ /
8
8
  # +Flag#remove_value+ / +Flag#clear_values+.
9
- FlagValue = Data.define(:name, :value)
9
+ FlagValue = Struct.new(:name, :value, keyword_init: true)
10
10
 
11
11
  # A single targeting rule on a +Flag+.
12
12
  #
@@ -102,7 +102,11 @@ module Smplkit
102
102
 
103
103
  return unless @adapters.empty?
104
104
 
105
+ # :nocov: defensive log — unreachable in practice because stdlib
106
+ # +Logger+ is always present, so +StdlibLoggerAdapter+ is always
107
+ # constructible.
105
108
  Smplkit.debug("registration", "no logging adapters loaded; runtime features disabled")
109
+ # :nocov:
106
110
  end
107
111
 
108
112
  def observe_logger(_adapter, raw_name, level)
@@ -38,32 +38,6 @@ module Smplkit
38
38
  updated_at: attrs["updated_at"]
39
39
  )
40
40
  end
41
-
42
- def build_logger_body(logger)
43
- attributes = {
44
- "name" => logger.name,
45
- "resolved_level" => logger.resolved_level&.to_s,
46
- "level" => logger.level&.to_s,
47
- "service" => logger.service,
48
- "environment" => logger.environment,
49
- "log_group_id" => logger.log_group_id,
50
- "managed" => logger.managed,
51
- "description" => logger.description
52
- }.compact
53
- { "data" => { "type" => "logger", "id" => logger.id, "attributes" => attributes } }
54
- end
55
-
56
- def build_log_group_body(group)
57
- attributes = {
58
- "key" => group.key,
59
- "name" => group.name,
60
- "level" => group.level&.to_s,
61
- "description" => group.description,
62
- "parent_id" => group.parent_id,
63
- "environments" => group.environments
64
- }.compact
65
- { "data" => { "type" => "log_group", "id" => group.key, "attributes" => attributes } }
66
- end
67
41
  end
68
42
  end
69
43
  end
data/lib/smplkit/ws.rb CHANGED
@@ -90,14 +90,16 @@ module Smplkit
90
90
  def stop
91
91
  Smplkit.debug("websocket", "stopping shared WebSocket")
92
92
  @closed = true
93
- @connection_status = "disconnected"
94
93
  close_active_connection
95
94
  thread = @ws_thread
96
95
  @ws_thread = nil
97
- return unless thread
98
-
99
- thread.join(2.0)
100
- thread.kill if thread.alive?
96
+ if thread
97
+ thread.join(2.0)
98
+ thread.kill if thread.alive?
99
+ end
100
+ # Set authoritatively after the thread is dead so a racing connect
101
+ # call (which also sets "connecting") cannot clobber this value.
102
+ @connection_status = "disconnected"
101
103
  end
102
104
 
103
105
  # ----- URL builder ----------------------------------------------
@@ -169,6 +171,8 @@ module Smplkit
169
171
  end
170
172
 
171
173
  def connect(task)
174
+ return if @closed
175
+
172
176
  url = build_ws_url
173
177
  @connection_status = "connecting"
174
178
  Smplkit.debug("websocket", "connecting to #{safe_url}")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smplkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.14
4
+ version: 1.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Smpl Solutions LLC
@@ -13,44 +13,62 @@ dependencies:
13
13
  name: async
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - "~>"
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '2.6'
19
+ - - "<"
17
20
  - !ruby/object:Gem::Version
18
- version: '2.39'
21
+ version: '3'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
- - - "~>"
26
+ - - ">="
24
27
  - !ruby/object:Gem::Version
25
- version: '2.39'
28
+ version: '2.6'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '3'
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: async-http
28
34
  requirement: !ruby/object:Gem::Requirement
29
35
  requirements:
30
- - - "~>"
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0.79'
39
+ - - "<"
31
40
  - !ruby/object:Gem::Version
32
- version: '0.95'
41
+ version: '1'
33
42
  type: :runtime
34
43
  prerelease: false
35
44
  version_requirements: !ruby/object:Gem::Requirement
36
45
  requirements:
37
- - - "~>"
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0.79'
49
+ - - "<"
38
50
  - !ruby/object:Gem::Version
39
- version: '0.95'
51
+ version: '1'
40
52
  - !ruby/object:Gem::Dependency
41
53
  name: async-websocket
42
54
  requirement: !ruby/object:Gem::Requirement
43
55
  requirements:
44
- - - "~>"
56
+ - - ">="
45
57
  - !ruby/object:Gem::Version
46
- version: '0.30'
58
+ version: '0.26'
59
+ - - "<"
60
+ - !ruby/object:Gem::Version
61
+ version: '1'
47
62
  type: :runtime
48
63
  prerelease: false
49
64
  version_requirements: !ruby/object:Gem::Requirement
50
65
  requirements:
51
- - - "~>"
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0.26'
69
+ - - "<"
52
70
  - !ruby/object:Gem::Version
53
- version: '0.30'
71
+ version: '1'
54
72
  - !ruby/object:Gem::Dependency
55
73
  name: concurrent-ruby
56
74
  requirement: !ruby/object:Gem::Requirement
@@ -570,7 +588,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
570
588
  requirements:
571
589
  - - ">="
572
590
  - !ruby/object:Gem::Version
573
- version: '3.3'
591
+ version: '3.1'
574
592
  required_rubygems_version: !ruby/object:Gem::Requirement
575
593
  requirements:
576
594
  - - ">="