singulus 0.1.0 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac98626fb3bc3db2c925dc5a31d25655501aa36544907ee9ba95d87b3e7e3917
4
- data.tar.gz: 371565dd472005e3a4038a0fd7ad1d9fa2679fe237b610072101c5a541b7f821
3
+ metadata.gz: 2d1789b73a6f295eb931426f74275d5103ee163683754c29d8566c58f5054bea
4
+ data.tar.gz: af0fdfb83a78959d15d1d866c195926e3c99407ee5e90629ef2e04904ee83294
5
5
  SHA512:
6
- metadata.gz: 5a6826300e20a88e8ae1e811cd1525c7f3cd38dcb6628d22608c92e2fc9ebc7029774288b2b990742783222f98f9054d188e5d892852a3bc7da8cf74181f71cc
7
- data.tar.gz: 8d7b79fbcaac93d423b22c82856539e57c6ed3f718cbed8a22a1ca2f6760c45d2e47c42c1db3a11bfb1bda1033cf6800cfcf3029474693add6f54e8fb876855f
6
+ metadata.gz: fbe8c5420941bedf97c2bf995b3131afa7e2d5777a8c6a46c9851965012acf250a7372b79d8a6721b8a8a4f3dc5f9edd5c919d2654ae14098d14be749e8608ed
7
+ data.tar.gz: 6ae106c3ebbd2efc6cde98511b7ce47691b3c9949707b6f478e2dc2ffe0db98e429bcc644c735b587820bc77419b989d319e02ed88f808a14512d0c62c196c9c
data/.yardopts ADDED
@@ -0,0 +1,11 @@
1
+ --markup markdown
2
+ --main README.md
3
+ --title "Singulus API Reference"
4
+ --no-private
5
+ --query '!object.path.start_with?("Singulus::Internal")'
6
+ --fail-on-warning
7
+ lib/**/*.rb
8
+ -
9
+ README.md
10
+ CHANGELOG.md
11
+ LICENSE.txt
data/CHANGELOG.md CHANGED
@@ -6,6 +6,18 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.1] - 2026-09-08
10
+
11
+ ### Changed
12
+
13
+ - Organized specs by configuration, Singleton, Multiton lifecycle, retention, and runtime security responsibilities.
14
+ - Fixed the Multiton test helper to apply the requested mode without requiring a retention strategy.
15
+
16
+ ### Added
17
+
18
+ - YARD reference for configuration, Singleton and Multiton APIs, with types, errors, lifecycle semantics, and examples.
19
+ - Local documentation task and CI generation with warnings treated as failures.
20
+
9
21
  ### Fixed
10
22
 
11
23
  - Completed the final RuboCop cleanup across Multiton and specs.
@@ -73,5 +85,6 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
73
85
  - Protect common reflective constructor access paths.
74
86
  - Protect captured `Method` and `UnboundMethod` paths in `:runtime` mode.
75
87
 
76
- [Unreleased]: https://github.com/Rubcraft/singulus/compare/v0.1.0...HEAD
88
+ [Unreleased]: https://github.com/Rubcraft/singulus/compare/v0.1.1...HEAD
89
+ [0.1.1]: https://github.com/Rubcraft/singulus/compare/v0.1.0...v0.1.1
77
90
  [0.1.0]: https://github.com/Rubcraft/singulus/releases/tag/v0.1.0
data/README.md CHANGED
@@ -241,6 +241,26 @@ bundle exec rake build
241
241
 
242
242
  SimpleCov enforces line and branch coverage, while CI tests supported Ruby versions independently from RuboCop.
243
243
 
244
+ ## API documentation
245
+
246
+ Generate the YARD reference locally:
247
+
248
+ ```bash
249
+ bundle install
250
+ bundle exec rake yard
251
+ ```
252
+
253
+ Open `doc/index.html` in a browser. The reference includes configuration,
254
+ include-time options, errors, and the class methods installed by Singleton
255
+ and Multiton. Installed methods appear on their pattern module's page; call
256
+ these on your including class, while calling `.with` on the pattern module.
257
+ The configuration object's reader/writer contract is documented under
258
+ `Singulus.configuration`; its concrete class remains private.
259
+
260
+ `.yardopts` excludes private implementation details. Generated HTML and the
261
+ YARD cache are ignored by Git. Documentation generation fails on YARD warnings
262
+ and runs in the CI quality job and `bundle exec rake ci`.
263
+
244
264
  ## Releasing
245
265
 
246
266
  Repository initialization, version-control operations, and release-tag creation are managed by the Rubcraft Toolkit. Singulus itself does not prescribe or duplicate those commands.
@@ -2,15 +2,24 @@
2
2
 
3
3
  module Singulus
4
4
  module Internal
5
+ # Mutable defaults exposed through Singulus.configuration, not a public type.
6
+ # @private
5
7
  class Configuration
6
8
  MODES = %i[standard strict runtime].freeze
7
9
 
10
+ # Returns the mode applied to subsequently installed classes.
11
+ # @return [Symbol] :standard, :strict, or :runtime
8
12
  attr_reader :default_mode
9
13
 
14
+ # Initializes defaults in strict mode.
10
15
  def initialize
11
16
  self.default_mode = :strict
12
17
  end
13
18
 
19
+ # Sets the default for subsequently installed classes.
20
+ # @param mode [Symbol, String] standard, strict, or runtime
21
+ # @return [Symbol] normalized mode
22
+ # @raise [Singulus::Error] if the mode is unsupported
14
23
  def default_mode=(mode)
15
24
  mode = normalize_mode(mode)
16
25
 
@@ -1,6 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Singulus
4
+ # Public base exception for Singulus policy and configuration failures.
5
+ #
6
+ # Rescue this class for invalid modes/retention, locked registry settings,
7
+ # recursive Multiton initialization, and hardening violations. Specialized
8
+ # subclasses are private implementation details. Ruby and application errors
9
+ # (such as ArgumentError or initializer failures) are not wrapped.
10
+ #
11
+ # @example Handle a rejected configuration
12
+ # begin
13
+ # Singulus.configure { |config| config.default_mode = :unknown }
14
+ # rescue Singulus::Error => error
15
+ # warn error.message
16
+ # end
4
17
  class Error < StandardError; end
5
18
 
6
19
  module Internal
@@ -3,6 +3,7 @@
3
3
  module Singulus
4
4
  module Internal
5
5
  module SingletonClassMethods
6
+ # See {Singulus::Singleton.instance} for the public contract.
6
7
  def instance
7
8
  return super unless Internal.locally_hardened?(self)
8
9
 
@@ -11,11 +12,13 @@ module Singulus
11
12
  instance
12
13
  end
13
14
 
15
+ # See {Singulus::Singleton.singulus} for the public contract.
14
16
  def singulus(mode:)
15
17
  Internal.set_mode!(self, mode)
16
18
  self
17
19
  end
18
20
 
21
+ # See {Singulus::Singleton.singulus_mode} for the public contract.
19
22
  def singulus_mode
20
23
  Internal.mode_for(self)
21
24
  end
@@ -1,10 +1,153 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Singulus
4
+ # Provides a synchronized registry of instances indexed by normalized keys.
5
+ #
6
+ # Include this module and call `.instance_for` on the including class.
7
+ # Uniqueness lasts only while an entry is retained: deletion, expiration,
8
+ # eviction, or weak-reference collection allows a new object for the same key,
9
+ # even if application code still holds a previously released instance.
10
+ # Strict/runtime modes reject duplication and subclassing with {Error}.
11
+ # Standard-mode subclasses start with independent, default-configured registries.
12
+ #
13
+ # Methods documented below as class methods are installed on the including
14
+ # class, except {.with}, which is called on this module. Configure classes
15
+ # at boot before using them concurrently.
16
+ #
17
+ # @example Reuse a tenant-specific service
18
+ # class TenantService
19
+ # include Singulus::Multiton
20
+ # def initialize(tenant_id)
21
+ # @tenant_id = tenant_id
22
+ # end
23
+ # end
24
+ # TenantService.instance_for(10).equal?(TenantService.instance_for(10)) # => true
4
25
  module Multiton
26
+ # @!method self.instance_for(identifier, *args, **kwargs, &block)
27
+ # Returns the retained instance for a key, or constructs and stores one.
28
+ #
29
+ # The normalizer determines the registry key, but the original identifier,
30
+ # additional arguments, keywords, and block are forwarded to `initialize` only
31
+ # on a cache miss. A hit ignores the additional initialization inputs.
32
+ # Construction and registry access are synchronized per class. Expired and dead
33
+ # weak entries are purged first; a hit refreshes LRU order, but never the TTL.
34
+ # Initializer and key-normalizer exceptions propagate. Failed initialization
35
+ # is not cached, so a later call may retry.
36
+ #
37
+ # @param identifier [Object] original identifier passed first to initialize
38
+ # @param args [Array<Object>] remaining positional initializer arguments
39
+ # @param kwargs [Hash] initializer keyword arguments
40
+ # @param block [Proc, nil] block forwarded to initialize on a miss
41
+ # @return [Object] instance of the receiving class
42
+ # @raise [Error] if initialization recursively requests the same normalized key
43
+ # @see multiton_key
44
+ # @see multiton_retention
45
+
46
+ # @!method self.instance?(identifier)
47
+ # Checks whether a live entry exists without constructing an instance.
48
+ #
49
+ # Purges expired/dead entries and does not refresh LRU order or TTL.
50
+ # Key-normalizer exceptions propagate.
51
+ #
52
+ # @param identifier [Object] identifier to normalize and look up
53
+ # @return [Boolean] whether the normalized key is registered
54
+
55
+ # @!method self.delete_instance(identifier)
56
+ # Removes registry ownership of a live entry and returns its instance.
57
+ #
58
+ # Purges expired/dead entries first. Existing external references stay valid;
59
+ # a later lookup can construct another instance. Normalizer exceptions propagate.
60
+ #
61
+ # @param identifier [Object] identifier to normalize and remove
62
+ # @return [Object, nil] removed instance, or nil if no live entry exists
63
+
64
+ # @!method self.clear_instances
65
+ # Releases all registry entries after purging expired/dead entries.
66
+ #
67
+ # External references remain valid. The empty registry allows changes to its
68
+ # key normalizer and retention policy.
69
+ #
70
+ # @return [Integer] number of live entries removed
71
+
72
+ # @!method self.instance_count
73
+ # Counts registered entries after purging expired/dead entries.
74
+ # @return [Integer] current registry size
75
+
76
+ # @!method self.instance_keys
77
+ # Returns a frozen snapshot of keys after purging expired/dead entries.
78
+ #
79
+ # Keys are normalized keys, not necessarily the original identifiers. Strings,
80
+ # arrays, and hashes are recursively copied/frozen when registered; other key
81
+ # objects are not copied. This operation does not refresh LRU order or TTL.
82
+ #
83
+ # @return [Array<Object>] frozen array of currently registered keys
84
+
85
+ # @!method self.singulus(mode: nil, retention: nil, ttl: nil, max_size: nil)
86
+ # Updates the class mode and/or retention policy, returning the class.
87
+ #
88
+ # Mode is applied first; a subsequent retention error does not roll it back.
89
+ # TTL/capacity settings are ignored unless retention is provided.
90
+ #
91
+ # @param mode [Symbol, String, nil] hardening mode; nil leaves it unchanged
92
+ # @param retention [Symbol, String, nil] strategy; nil leaves it unchanged
93
+ # @param ttl [Numeric, nil] positive lifetime in seconds for ttl/bounded
94
+ # @param max_size [Integer, nil] positive capacity for lru/bounded
95
+ # @return [Class] receiving class
96
+ # @raise [Error] for invalid settings or retention changes on a nonempty registry
97
+ # @see multiton_retention
98
+
99
+ # @!method self.singulus_mode
100
+ # Returns this class's current hardening mode.
101
+ # @return [Symbol] :standard, :strict, or :runtime
102
+
103
+ # @!method self.multiton_key(&block)
104
+ # Reads or replaces the key normalizer used by identifier-based operations.
105
+ #
106
+ # The block receives the original identifier. Its result becomes the registry
107
+ # key, with strings, arrays, and hashes recursively copied/frozen. Other objects
108
+ # must keep stable `hash` and `eql?` behavior while registered. No block means
109
+ # read the current normalizer; nil means identifiers are used directly.
110
+ # Clear the registry before changing normalization. Block exceptions propagate
111
+ # from subsequent identifier-based operations.
112
+ #
113
+ # @param block [Proc, nil] normalizer to install, or nil to read
114
+ # @yield [identifier] computes a registry key
115
+ # @yieldparam identifier [Object] original identifier
116
+ # @yieldreturn [Object] stable key
117
+ # @return [Class, Proc, nil] class when setting; current normalizer when reading
118
+ # @raise [Error] if setting a normalizer while live entries remain
119
+ # @example Normalize case before creating instances
120
+ # TenantService.multiton_key { |id| id.to_s.downcase }
121
+
122
+ # @!method self.multiton_retention(strategy = nil, ttl: nil, max_size: nil)
123
+ # Reads or replaces the registry retention policy (initially `:forever`).
124
+ #
125
+ # `forever` holds entries until explicit release; `weak` holds weak references.
126
+ # Neither accepts TTL or capacity. `lru` requires only max_size and evicts the
127
+ # least recently accessed entry. `ttl` requires only ttl and expires entries
128
+ # from their creation time using the monotonic clock. `bounded` requires both.
129
+ # Expiration is lazy, checked during registry operations, not by a background
130
+ # worker. TTL never slides on access. All policies can allow new instances
131
+ # after an entry is released. Settings may change only on an empty registry.
132
+ #
133
+ # @param strategy [Symbol, String, nil] forever, weak, lru, ttl, or bounded;
134
+ # nil reads the current strategy and ignores other arguments
135
+ # @param ttl [Numeric, nil] positive seconds (converted with to_f)
136
+ # @param max_size [Integer, nil] positive capacity (converted with to_i)
137
+ # @return [Class, Symbol] class when setting; strategy when reading
138
+ # @raise [Error] for an unknown strategy, incompatible/missing/nonpositive
139
+ # options, or a nonempty registry
140
+ # @example Combine time and capacity bounds
141
+ # TenantService.clear_instances
142
+ # TenantService.multiton_retention(:bounded, ttl: 300, max_size: 100)
143
+
144
+ # Supported internal retention names.
145
+ # @private
5
146
  RETENTIONS = %i[forever lru ttl bounded weak].freeze
6
147
  private_constant :RETENTIONS
7
148
 
149
+ # Internal registry entry, optionally holding a weak reference.
150
+ # @private
8
151
  Entry = Struct.new(:value, :expires_at, keyword_init: true) do
9
152
  def instance
10
153
  value.is_a?(WeakRef) ? value.__getobj__ : value
@@ -19,10 +162,35 @@ module Singulus
19
162
  private_constant :Entry
20
163
 
21
164
  class << self
165
+ # Installs the pattern when Ruby evaluates `include`.
166
+ # @private
22
167
  def included(base)
23
168
  install(base)
24
169
  end
25
170
 
171
+ # Builds an independent module with mode and optional retention settings.
172
+ #
173
+ # Defaults are captured now; mode and retention validation happens on inclusion.
174
+ # `ttl` and `max_size` are applied only when `retention` is supplied.
175
+ # See {.multiton_retention} for supported combinations.
176
+ #
177
+ # @param mode [Symbol, String, nil] :standard, :strict, or :runtime;
178
+ # nil uses the configured default
179
+ # @param options [Hash] mode and registry settings
180
+ # @option options [Symbol, String] :mode alternative to positional mode
181
+ # @option options [Symbol, String] :retention registry retention strategy
182
+ # @option options [Numeric] :ttl positive lifetime in seconds
183
+ # @option options [Integer] :max_size positive registry capacity
184
+ # @return [Module] configured module to include in a class
185
+ # @raise [ArgumentError] if both mode forms or unknown options are supplied
186
+ # @raise [Error] on inclusion for invalid mode or retention settings
187
+ # @example Limit registry capacity
188
+ # class TenantService
189
+ # include Singulus::Multiton.with(:strict, retention: :lru, max_size: 100)
190
+ # def initialize(tenant_id)
191
+ # @tenant_id = tenant_id
192
+ # end
193
+ # end
26
194
  def with(mode = nil, **options)
27
195
  resolved_mode = options.delete(:mode)
28
196
 
@@ -43,6 +211,8 @@ module Singulus
43
211
  end
44
212
  end
45
213
 
214
+ # Installs guards and a fresh registry on a class.
215
+ # @private
46
216
  def install(base, mode: Singulus.configuration.default_mode)
47
217
  Internal.initialize_managed_class!(base, kind: :multiton, mode: mode)
48
218
 
@@ -71,7 +241,10 @@ module Singulus
71
241
  end
72
242
  end
73
243
 
244
+ # Implementation of methods installed on each Multiton class.
245
+ # @private
74
246
  module ClassMethods
247
+ # See {Singulus::Multiton.instance_for} for the public contract.
75
248
  def instance_for(identifier, ...)
76
249
  key = singulus_multiton_key_for(identifier)
77
250
 
@@ -102,6 +275,7 @@ module Singulus
102
275
  end
103
276
  end
104
277
 
278
+ # See {Singulus::Multiton.instance?} for the public contract.
105
279
  def instance?(identifier)
106
280
  key = singulus_multiton_key_for(identifier)
107
281
 
@@ -111,6 +285,7 @@ module Singulus
111
285
  end
112
286
  end
113
287
 
288
+ # See {Singulus::Multiton.delete_instance} for the public contract.
114
289
  def delete_instance(identifier)
115
290
  key = singulus_multiton_key_for(identifier)
116
291
 
@@ -120,6 +295,7 @@ module Singulus
120
295
  end
121
296
  end
122
297
 
298
+ # See {Singulus::Multiton.clear_instances} for the public contract.
123
299
  def clear_instances
124
300
  singulus_multiton_mutex.synchronize do
125
301
  singulus_multiton_purge_expired!
@@ -129,6 +305,7 @@ module Singulus
129
305
  end
130
306
  end
131
307
 
308
+ # See {Singulus::Multiton.instance_count} for the public contract.
132
309
  def instance_count
133
310
  singulus_multiton_mutex.synchronize do
134
311
  singulus_multiton_purge_expired!
@@ -136,6 +313,7 @@ module Singulus
136
313
  end
137
314
  end
138
315
 
316
+ # See {Singulus::Multiton.instance_keys} for the public contract.
139
317
  def instance_keys
140
318
  singulus_multiton_mutex.synchronize do
141
319
  singulus_multiton_purge_expired!
@@ -143,16 +321,19 @@ module Singulus
143
321
  end
144
322
  end
145
323
 
324
+ # See {Singulus::Multiton.singulus} for the public contract.
146
325
  def singulus(mode: nil, retention: nil, ttl: nil, max_size: nil)
147
326
  Internal.set_mode!(self, mode) if mode
148
327
  multiton_retention(retention, ttl: ttl, max_size: max_size) if retention
149
328
  self
150
329
  end
151
330
 
331
+ # See {Singulus::Multiton.singulus_mode} for the public contract.
152
332
  def singulus_mode
153
333
  Internal.mode_for(self)
154
334
  end
155
335
 
336
+ # See {Singulus::Multiton.multiton_key} for the public contract.
156
337
  def multiton_key(&block)
157
338
  return @__singulus_multiton_key_normalizer__ unless block
158
339
 
@@ -161,6 +342,7 @@ module Singulus
161
342
  self
162
343
  end
163
344
 
345
+ # See {Singulus::Multiton.multiton_retention} for the public contract.
164
346
  def multiton_retention(strategy = nil, ttl: nil, max_size: nil)
165
347
  return @__singulus_multiton_retention__ unless strategy
166
348
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Singulus
4
- VERSION = "0.1.0"
4
+ # Current gem version in semantic versioning format.
5
+ # @return [String] version used by the gem specification
6
+ VERSION = "0.1.1"
5
7
  end
data/lib/singulus.rb CHANGED
@@ -15,7 +15,19 @@ require_relative "singulus/internal/singleton_class_methods"
15
15
  require_relative "singulus/internal/runtime_hardening"
16
16
  require_relative "singulus/multiton"
17
17
 
18
+ # Configurable Singleton and keyed Multiton patterns for a Ruby process.
19
+ #
20
+ # Require `singulus`, then include {Singleton} or {Multiton} in a class.
21
+ # The default mode is `:strict`; `:standard` relaxes local guards and
22
+ # `:runtime` adds process-wide Method/UnboundMethod guards for managed classes.
23
+ # Runtime hardening is not a sandbox for code running in the same process.
24
+ #
25
+ # @see Singleton
26
+ # @see Multiton
27
+ # @see Error
18
28
  module Singulus
29
+ # Implementation details; not a supported public namespace.
30
+ # @private
19
31
  module Internal
20
32
  CONSTRUCTORS = %i[new allocate].freeze
21
33
 
@@ -137,10 +149,37 @@ module Singulus
137
149
  private_constant :Internal
138
150
 
139
151
  class << self
152
+ # Returns the mutable process-wide defaults for subsequently installed classes.
153
+ #
154
+ # Use the returned object's `default_mode` reader and `default_mode=` writer;
155
+ # its concrete class is private. The reader returns a Symbol. The writer accepts
156
+ # `:standard`, `:strict`, `:runtime`, or their String forms, and raises
157
+ # {Error} for invalid modes. Existing managed classes keep their mode.
158
+ # Setting the writer directly does not install runtime guards until a runtime
159
+ # class is installed; use {configure} to enable them immediately.
160
+ #
161
+ # @return [#default_mode, #default_mode=] shared configuration object
162
+ # @example Read the default
163
+ # Singulus.configuration.default_mode # => :strict
140
164
  def configuration
141
165
  @configuration ||= Internal::Configuration.new
142
166
  end
143
167
 
168
+ # Yields the shared configuration and enables runtime guards when requested.
169
+ #
170
+ # Configure during application boot, before defining managed classes or
171
+ # capturing constructor references. Runtime patches are installed once and
172
+ # remain installed for the life of the process. Exceptions from the block
173
+ # propagate; changes already made to the configuration are not rolled back.
174
+ #
175
+ # @yield [config] changes defaults for future class installations
176
+ # @yieldparam config [#default_mode, #default_mode=] mutable configuration
177
+ # @yieldreturn [Object] ignored
178
+ # @return [#default_mode, #default_mode=] shared configuration after the block
179
+ # @raise [ArgumentError] if no block is given
180
+ # @raise [Error] if the block assigns an unsupported default mode
181
+ # @example Enable runtime hardening during boot
182
+ # Singulus.configure { |config| config.default_mode = :runtime }
144
183
  def configure
145
184
  raise ArgumentError, "a block is required" unless block_given?
146
185
 
@@ -149,17 +188,82 @@ module Singulus
149
188
  configuration
150
189
  end
151
190
 
191
+ # Replaces the shared configuration with a new object in `:strict` mode.
192
+ #
193
+ # Does not change existing classes, clear instances, or uninstall runtime
194
+ # patches. Previously returned configuration objects are no longer shared.
195
+ #
196
+ # @return [#default_mode, #default_mode=] new configuration object
152
197
  def reset_configuration!
153
198
  @configuration = Internal::Configuration.new
154
199
  end
155
200
  end
156
201
 
202
+ # Provides one lazily initialized, thread-safe instance per class and process.
203
+ #
204
+ # Include this module and call `.instance` on the including class. Construction
205
+ # uses a zero-argument initializer. In strict/runtime modes constructors are
206
+ # sealed after the first successful access, and duplication and inheritance
207
+ # raise {Error}. Standard mode delegates singleton semantics to Ruby Singleton,
208
+ # which rejects instance duplication with TypeError.
209
+ #
210
+ # Methods documented below as class methods are installed on the including
211
+ # class, except {.with}, which is called on this module.
212
+ #
213
+ # @example Share one service
214
+ # class Settings
215
+ # include Singulus::Singleton
216
+ # end
217
+ # Settings.instance.equal?(Settings.instance) # => true
157
218
  module Singleton
219
+ # @!method self.instance
220
+ # Returns the class's shared instance, constructing it once if needed.
221
+ #
222
+ # Initialization is synchronized by Ruby Singleton. Initializer exceptions
223
+ # propagate and construction can be retried. Strict/runtime modes seal
224
+ # constructors after successful access. Recursive `.instance` calls from the
225
+ # initializer are unsupported by Ruby Singleton.
226
+ #
227
+ # @return [Object] instance of the receiving class
228
+
229
+ # @!method self.singulus(mode:)
230
+ # Changes this class's hardening mode.
231
+ #
232
+ # Switching to runtime installs process-wide guards. Switching away does not
233
+ # uninstall them. A sealed Singleton cannot downgrade to standard mode.
234
+ #
235
+ # @param mode [Symbol, String] :standard, :strict, or :runtime
236
+ # @return [Class] receiving class for chaining
237
+ # @raise [Error] for an invalid mode or a downgrade after constructor sealing
238
+
239
+ # @!method self.singulus_mode
240
+ # Returns this class's current hardening mode.
241
+ # @return [Symbol] :standard, :strict, or :runtime
242
+
243
+ # Inclusion helpers for the public mixin.
158
244
  class << self
245
+ # Installs the pattern when Ruby evaluates `include`.
246
+ # @private
159
247
  def included(base)
160
248
  Internal.install_singleton!(base)
161
249
  end
162
250
 
251
+ # Builds an independent module with an include-time hardening mode.
252
+ #
253
+ # The default is captured when this method is called. Mode validation occurs
254
+ # when the returned module is included in a class.
255
+ #
256
+ # @param mode [Symbol, String, nil] :standard, :strict, or :runtime;
257
+ # nil uses the configured default
258
+ # @param options [Hash] keyword form of the mode
259
+ # @option options [Symbol, String] :mode alternative to positional mode
260
+ # @return [Module] module to include in the managed class
261
+ # @raise [ArgumentError] if both mode forms or unknown options are supplied
262
+ # @raise [Error] on inclusion if the resolved mode is unsupported
263
+ # @example Select a mode for one class
264
+ # class Settings
265
+ # include Singulus::Singleton.with(mode: :runtime)
266
+ # end
163
267
  def with(mode = nil, **options)
164
268
  resolved_mode = options.delete(:mode)
165
269
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: singulus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Furattini
@@ -18,6 +18,7 @@ executables: []
18
18
  extensions: []
19
19
  extra_rdoc_files: []
20
20
  files:
21
+ - ".yardopts"
21
22
  - CHANGELOG.md
22
23
  - LICENSE.txt
23
24
  - README.md