safe_memoize 0.7.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41e3b8e3232ef52d536166c4e701a99a8d7a1e86e2c6d07493d1838d904dbef6
4
- data.tar.gz: dac922ad348d3ccf14d8946035b5a0e7709cacc899dcc6e0713da77c980ac6ca
3
+ metadata.gz: 0b47b6031a8f395991376eec0407b93d1cf02caecad23f4d77e4d68234ef87b5
4
+ data.tar.gz: 7542678912a0a39425a75e3addfc718f2abb35068e28e0cdc0444c294dd4c4c1
5
5
  SHA512:
6
- metadata.gz: a7f263ac2b16ff248ccf6afa158aaaae5a7fd69cadef68ff6926c3d96257378df8d4295f2eac8278a68de12492128282c1ffea42da95c178de7e5619981c8634
7
- data.tar.gz: b50610d8ac36d0c7ff4fe9ed174a7f7864654f4178bc5778a9555eda1d934320b7ee0bcf4de53919e9dfacc35d06d8320f0b9dc8e8a25d6fab6808dfb0c0aab4
6
+ metadata.gz: a304040bf86b1bc359c91f3d687a9f45e020bdaddded53b82a87e473553491aa9daba1ac45f8abadffb8ede8af270479f73762ab47357486aef9b46043afacde
7
+ data.tar.gz: 79d6a552f98f9f2ef1482832c211cc7b0a58f6481c989320c19db63b870a1723b482304955a133c661a0082a07c17de539258a9f869da0b5cbd0aaa2acacd96a
@@ -10,7 +10,27 @@ on:
10
10
  permissions:
11
11
  contents: read
12
12
 
13
+ env:
14
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
15
+
13
16
  jobs:
17
+ lint:
18
+ name: Lint
19
+ runs-on: ubuntu-latest
20
+
21
+ steps:
22
+ - name: Check out repository
23
+ uses: actions/checkout@v5
24
+
25
+ - name: Set up Ruby
26
+ uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: "3.4"
29
+ bundler-cache: true
30
+
31
+ - name: Run StandardRB
32
+ run: bundle exec standardrb
33
+
14
34
  test:
15
35
  name: Ruby ${{ matrix.ruby }}
16
36
  runs-on: ubuntu-latest
@@ -32,6 +52,13 @@ jobs:
32
52
  ruby-version: ${{ matrix.ruby }}
33
53
  bundler-cache: true
34
54
 
35
- - name: Run test and lint suite
36
- run: bundle exec rake
55
+ - name: Run test suite
56
+ run: bundle exec rspec
57
+
58
+ - name: Upload coverage to Codecov
59
+ uses: codecov/codecov-action@v5
60
+ if: matrix.ruby == '3.4'
61
+ with:
62
+ token: ${{ secrets.CODECOV_TOKEN }}
63
+ files: coverage/.resultset.json
37
64
 
@@ -7,6 +7,7 @@ on:
7
7
 
8
8
  permissions:
9
9
  contents: write
10
+ id-token: write
10
11
 
11
12
  jobs:
12
13
  release:
@@ -66,10 +67,13 @@ jobs:
66
67
  RELEASE_TAG: ${{ github.ref_name }}
67
68
  run: echo "RubyGems already has version ${RELEASE_TAG#v}; skipping gem push."
68
69
 
70
+ - name: Configure RubyGems credentials (trusted publishing)
71
+ if: steps.rubygems.outputs.already_published != 'true'
72
+ uses: rubygems/configure-rubygems-credentials@v1.0.0
73
+
69
74
  - name: Publish gem to RubyGems
70
75
  if: steps.rubygems.outputs.already_published != 'true'
71
76
  env:
72
- GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
73
77
  RELEASE_TAG: ${{ github.ref_name }}
74
78
  run: |
75
79
  version="${RELEASE_TAG#v}"
data/CHANGELOG.md CHANGED
@@ -1,139 +1,158 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
7
+ from v1.0.0 onwards. Prior 0.x releases may include breaking changes between minor versions.
8
+
1
9
  ## [Unreleased]
2
10
 
11
+ ## [0.9.0] - 2026-05-22
12
+
13
+ ### Added
14
+
15
+ - `ActiveSupport::Notifications` integration — opt-in via `SafeMemoize.configure { |c| c.active_support_notifications = true }`; emits `cache_hit.safe_memoize`, `cache_miss.safe_memoize`, `cache_evict.safe_memoize`, `cache_expire.safe_memoize`, and `cache_store.safe_memoize` events; each payload includes `:method`, `:key`, and `:class`; zero overhead when ActiveSupport is not loaded
16
+ - `SafeMemoize::Adapters::StatsD` — thin optional adapter that routes lifecycle events to any StatsD client via `SafeMemoize.configure { |c| c.statsd_client = my_client }`; emits `safe_memoize.hit`, `safe_memoize.miss`, `safe_memoize.evict`, `safe_memoize.expire`, and `safe_memoize.store` with `method:` and `class:` tags; client errors are rescued and warned rather than raised
17
+ - Formal benchmark suite (`benchmarks/benchmark.rb`) — six scenarios covering zero-arg cache hit/miss, with-argument hit, fast vs locked path, shared vs instance cache, and concurrent throughput under 8-thread contention; optional comparisons against `memery` and `memo_wise`; run with `bundle exec ruby benchmarks/benchmark.rb`
18
+ - Concurrency stress test suite (`spec/concurrency_spec.rb`) — 18 barrier-synchronized examples hammering the fast path, locked path, and shared cache under 30 concurrent threads; covers exactly-once computation, LRU size invariant, hook count integrity, metric accuracy, TTL pruning, and deadlock detection (10-second timeout per run)
19
+ - `SafeMemoize::Adapters::OpenTelemetry` — optional adapter that wraps each cache-miss computation in an OpenTelemetry span; configure via `SafeMemoize.configure { |c| c.opentelemetry_tracer = OpenTelemetry.tracer_provider.tracer("safe_memoize") }`; span name is `"safe_memoize.compute"` with attributes `safe_memoize.method`, `safe_memoize.class`, and `safe_memoize.cache_hit`; falls back to untraced execution when the tracer is absent or does not respond to `in_span`
20
+ - `SafeMemoize::Rails` — opt-in request-scope helpers (`require "safe_memoize/rails"`): `SafeMemoize::Rails::RequestScoped` concern auto-registers `after_action :reset_all_memos` in controllers and exposes `reset_request_memos` elsewhere; `SafeMemoize::Rails::Middleware` Rack middleware resets all thread-tracked instances (`SafeMemoize::Rails.track(self)`) at the end of each request even on error
21
+
22
+ ## [0.8.0] - 2026-05-21
23
+
24
+ ### Added
25
+
26
+ - Raise `ArgumentError` at definition time when `memoize` is called on a method that does not exist on the class — previously the error only surfaced at runtime when `super` had nothing to call
27
+ - Key serialization safety: argument arrays, hashes, and strings are deep-frozen into an independent copy when the cache key is built, so callers that mutate their arguments after a call can no longer corrupt or miss the cached entry
28
+ - `memo_inspect` — single-entry deep-inspection helper returning all metadata for one cached call in one mutex-held read: `cached`, `value`, `hits`, `misses`, `ttl_remaining`, `age`, `custom_key`, and `lru_position`; returns `nil` when the entry is not cached
29
+ - Deprecation infrastructure: `SafeMemoize.deprecate(subject, message:, horizon:)` emits a structured `[SafeMemoize]` warning to stderr by default; configurable via `SafeMemoize.configure { |c| c.on_deprecation = ->(msg) { ... } }` to raise, log, or collect warnings
30
+ - `memoize_all only:` — symmetric counterpart to `except:`; explicitly lists the methods to memoize and skips all others; raises `ArgumentError` when both `only:` and `except:` are given
31
+ - Hook error isolation: exceptions raised inside lifecycle hooks no longer propagate to the caller; by default a `[SafeMemoize] Hook error in <type>: <message>` warning is emitted to stderr; configurable via `SafeMemoize.configure { |c| c.on_hook_error = ->(error, hook_type, cache_key) { ... } }` to raise, log, or silence
32
+
3
33
  ## [0.7.0] - 2026-05-18
4
34
 
5
- - Add `memo_preload` to batch-warm multiple cache entries in one call
6
- - `obj.memo_preload(:find, [1], [2], [3])` calls the memoized method for each arg set and caches all results
7
- - Returns an array of results in the same order as the input arg sets
8
- - Computes each entry only once subsequent calls return from cache
9
- - Add `on_memo_store` hook that fires whenever a value is written to the cache
10
- - Fires on every cache miss (fast path and LRU path)
11
- - Also fires when entries are written via `warm_memo` or `load_memo`
12
- - Does not fire on cache hits or when a conditional `:if`/`:unless` prevents storing
13
- - Fires on the calling instance for `shared: true` misses
14
- - Completes the full lifecycle hook set: `on_store`, `on_hit`, `on_miss`, `on_expire`, `on_evict`
15
- - Add per-method `cache_metrics_reset(:method)` to clear stats for a single method without wiping the rest
16
- - `cache_metrics_reset` (no args) still clears all metrics as before
17
- - Add `SafeMemoize.configure` for global default options
18
- - `SafeMemoize.configure { |c| c.default_ttl = 60 }` applies a TTL to all subsequently memoized methods
19
- - `SafeMemoize.configure { |c| c.default_max_size = 100 }` sets a global LRU size limit
20
- - Per-call options (`ttl:`, `max_size:`) override the global defaults
21
- - `SafeMemoize.reset_configuration!` restores defaults to `nil`
22
- - Add `memo_touch` to reset the expiry clock on a cached entry without recomputing
23
- - `memo_touch(:method, *args)` extends the entry's TTL from now using the original TTL window
24
- - `memo_touch(:method, *args, ttl: 30)` sets a new TTL explicitly
25
- - Returns `true` on success, `false` if the entry is not cached or already expired
26
- - Add `shared_memo_age` class method to inspect how long ago a shared entry was cached
27
- - Add `shared_memo_stale?` class method to check whether a shared entry's TTL has elapsed
28
- - Update RBS type signatures for all new methods and the `Configuration` class
29
- - Add `key:` option to `memoize` for class-level cache key generation
30
- - `memoize :method, key: ->(a, b) { a }` defines a key generator at the class level — calls whose key block returns the same value share one cache entry
31
- - Instance-level `memoize_with_custom_key` still takes priority over `key:`
32
- - Composes with all existing options (`ttl:`, `max_size:`, `shared:`, `if:`, etc.)
33
- - Raises `ArgumentError` if `key:` is not callable
34
- - Add `shared:` support to `memoize_all` (was already functional via `**options` passthrough; now tested and documented)
35
- - Add `memo_refresh` to force-recompute a cached entry and store the new value in one call
36
- - Add `memo_age` to return how many seconds ago an entry was cached (`nil` if not cached or expired)
37
- - Add `memo_stale?` to check whether a cached entry exists but its TTL has elapsed
35
+ ### Added
36
+
37
+ - `memo_preload` to batch-warm multiple cache entries in one call `obj.memo_preload(:find, [1], [2], [3])` calls the memoized method for each arg set, caches all results, and returns them in input order
38
+ - `on_memo_store` hook that fires whenever a value is written to the cache (miss, `warm_memo`, or `load_memo`); completes the full lifecycle hook set alongside `on_hit`, `on_miss`, `on_expire`, and `on_evict`
39
+ - `SafeMemoize.configure` for global default options `default_ttl` and `default_max_size` apply to all subsequently memoized methods; per-call options override the global defaults
40
+ - `SafeMemoize.reset_configuration!` to restore all global defaults to `nil`
41
+ - `memo_touch` to reset the expiry clock on a cached entry without recomputing — accepts an optional `ttl:` override; returns `true` on success, `false` if the entry is not cached or already expired
42
+ - `shared_memo_age` class method to inspect how long ago a shared entry was cached
43
+ - `shared_memo_stale?` class method to check whether a shared entry's TTL has elapsed
44
+ - `key:` option on `memoize` for class-level cache key generation — calls whose key block returns the same value share one cache entry; instance-level `memoize_with_custom_key` still takes priority
45
+ - `memo_refresh` to force-recompute a cached entry and store the new value in one call
46
+ - `memo_age` to return how many seconds ago an entry was cached (`nil` if not cached or expired)
47
+ - `memo_stale?` to check whether a cached entry exists but its TTL has elapsed
48
+
49
+ ### Changed
50
+
51
+ - `cache_metrics_reset` now accepts an optional method name to clear stats for a single method only; calling without arguments still clears all metrics
52
+ - `shared:` support in `memoize_all` is now tested and documented (was already functional via `**options` passthrough)
53
+ - RBS type signatures updated for all new methods and the `Configuration` class
38
54
 
39
55
  ## [0.6.3] - 2026-05-18
40
56
 
57
+ ### Changed
58
+
41
59
  - Upgrade `softprops/action-gh-release` from v2 to v3 to resolve Node.js 20 deprecation warning in release workflow
42
60
 
43
61
  ## [0.6.2] - 2026-05-18
44
62
 
45
- - Achieve 100% line coverage across all lib files
46
- - Add SimpleCov filter to exclude `/spec` from coverage reporting
47
- - Add tests for `memo_ttl` in `CacheRecordMethods` covering nil, valid numeric, negative, and non-numeric inputs
48
- - Add tests for private `memo_cache_read` in `CacheStoreMethods` covering nil cache, live hit, and expired entry
49
- - Add tests for `memo_keys` / `memo_values` with custom-key entries, covering the `custom_key:` projection branch in `InspectionMethods`
50
- - Add missing error-case tests for `ReleaseTooling.update_version_file` (no VERSION constant) and `finalize_changelog` (no Unreleased heading)
63
+ ### Added
64
+
65
+ - 100% line coverage across all lib files — added tests for edge cases in `CacheRecordMethods`, `CacheStoreMethods`, `InspectionMethods`, and `ReleaseTooling`; added SimpleCov filter to exclude `/spec` from coverage reporting
51
66
 
52
67
  ## [0.6.1] - 2026-05-17
53
68
 
54
- - Fix `memo_keys` and `memo_values` showing `args: custom_key, kwargs: nil` for methods using `memoize_with_custom_key` — now surfaces as `custom_key:`
55
- - Refactor `cache_stats` / `cache_stats_for` to share aggregation logic via private helpers
69
+ ### Changed
70
+
71
+ - Refactored `cache_stats` / `cache_stats_for` to share aggregation logic via private helpers
72
+
73
+ ### Fixed
74
+
75
+ - `memo_keys` and `memo_values` showed `args: custom_key, kwargs: nil` for methods using `memoize_with_custom_key` — now correctly surfaces as `custom_key:`
56
76
 
57
77
  ## [0.6.0] - 2026-05-17
58
78
 
59
- - Fix TTL clock starting at `memoize` definition time instead of first method call
60
- - Fix metrics key silently dropping kwargs, causing methods that differ only in kwargs to share a metrics bucket
61
- - Fix stale LRU references remaining after expired entries are pruned
62
- - Add `ttl:` option to `warm_memo` so warmed entries can be given an expiry
63
- - Add `max_size:` support for `shared: true` memoization (class-level LRU eviction)
64
- - Add `ttl_refresh: true` option on `memoize` for sliding window TTL — resets expiry on every cache hit
65
- - Add `include_protected:` and `include_private:` options to `memoize_all`
66
- - Add `memo_ttl_remaining` for TTL introspection — returns seconds until expiry, `nil` for no TTL, `0` for uncached/expired
79
+ ### Added
80
+
81
+ - `ttl:` option on `warm_memo` so warmed entries can be given an expiry
82
+ - `max_size:` support for `shared: true` memoization (class-level LRU eviction)
83
+ - `ttl_refresh: true` option on `memoize` for sliding window TTL — resets the expiry clock on every cache hit so the entry only expires after a full TTL of inactivity
84
+ - `include_protected:` and `include_private:` options on `memoize_all`
85
+ - `memo_ttl_remaining` for TTL introspection — returns seconds until expiry, `nil` for no TTL, `0` for uncached or expired
86
+
87
+ ### Fixed
88
+
89
+ - TTL clock started at `memoize` definition time instead of at first method call
90
+ - Metrics key silently dropped kwargs, causing methods that differ only in kwargs to share a metrics bucket
91
+ - Stale LRU references remained in the order list after expired entries were pruned
67
92
 
68
93
  ## [0.5.0] - 2026-05-17
69
94
 
70
- - Drop support for Ruby 3.2 (EOL); minimum required version is now Ruby 3.3
95
+ ### Removed
96
+
97
+ - Support for Ruby 3.2 (EOL); minimum required version is now Ruby 3.3
71
98
 
72
99
  ## [0.4.0] - 2026-05-17
73
100
 
74
- - Add `warm_memo`, `dump_memo`, and `load_memo` for cache warm-up and persistence
75
- - `warm_memo(:method, *args, **kwargs) { value }` — pre-populates a cache entry via block without calling the method
76
- - `dump_memo` / `dump_memo(:method)` — exports live cached entries as a plain `{[method, args, kwargs] => value}` hash
77
- - `load_memo(snapshot)` merges a snapshot into the cache; loaded entries have no TTL
78
- - Expired entries are excluded from `dump_memo` output
79
- - Add `shared: true` option on `memoize` to store results on the class instead of per-instance
80
- - All instances share one cache; the method is computed only once regardless of how many objects exist
81
- - Class-level invalidation: `reset_shared_memo`, `reset_all_shared_memos`
82
- - Class-level inspection: `shared_memoized?`, `shared_memo_count`
83
- - Supports `ttl:`, `if:`, and `unless:` options
84
- - Instance hooks (`on_memo_hit`, `on_memo_miss`, `on_memo_expire`) fire on the calling instance
85
- - Add `memoize_all` to memoize every public method defined on the class in one call
86
- - Accepts all options supported by `memoize` (`ttl:`, `max_size:`, `if:`, `unless:`)
87
- - `except:` option to skip specific methods by name
88
- - Only affects public methods defined directly on the class
89
- - Add `on_memo_miss` hook that fires on every cache miss, completing the full lifecycle hook set alongside `on_memo_hit`, `on_memo_evict`, and `on_memo_expire`
101
+ ### Added
102
+
103
+ - `warm_memo`, `dump_memo`, and `load_memo` for cache warm-up and persistence pre-populate entries without calling the method, export live entries as a plain hash, and restore from a snapshot
104
+ - `shared: true` option on `memoize` to store results on the class instead of per-instance includes `reset_shared_memo`, `reset_all_shared_memos`, `shared_memoized?`, and `shared_memo_count`; supports `ttl:`, `if:`, and `unless:`
105
+ - `memoize_all` to memoize every public method defined on the class in one call — accepts all `memoize` options plus `except:` to skip specific methods
106
+ - `on_memo_miss` hook that fires on every cache miss, completing the full lifecycle hook set
90
107
 
91
108
  ## [0.3.0] - 2026-05-15
92
109
 
93
- - Add `on_memo_hit` hook that fires on every cache hit, completing the lifecycle API alongside `on_memo_expire` and `on_memo_evict`
94
- - Add conditional memoization via `if:` and `unless:` options on `memoize`
95
- - `if: ->(result) { ... }` only caches when the lambda returns truthy
96
- - `unless: ->(result) { ... }` — skips caching when the lambda returns truthy
97
- - Uncached calls recompute on every invocation until the condition is met
98
- - Compatible with `ttl:`, `max_size:`, hooks, and all inspection APIs
99
- - Add LRU cache size limit via `max_size:` option on `memoize`
100
- - Evicts the least-recently-used entry per method when the limit is reached
101
- - Cache hits promote entries to most-recently-used, preventing premature eviction
102
- - Fires the existing `on_evict` hook for LRU-evicted entries
103
- - Self-healing: stale LRU references left by `reset_memo` are pruned automatically
104
- - Compatible with `ttl:` option and all existing inspection/reset APIs
105
- - Thread-safe under concurrent access
110
+ ### Added
111
+
112
+ - `on_memo_hit` hook that fires on every cache hit
113
+ - Conditional memoization via `if:` and `unless:` predicates on `memoize`uncached calls recompute on every invocation until the condition is satisfied; composes with `ttl:`, `max_size:`, and hooks
114
+ - LRU cache size limit via `max_size:` on `memoize` evicts the least-recently-used entry when the limit is reached; cache hits promote entries; fires `on_evict`; thread-safe
106
115
 
107
116
  ## [0.2.0] - 2026-05-14
108
117
 
109
- - Add optional TTL expiration support for memoized entries
110
- - Add cache invalidation/expiration hooks for custom handlers
111
- - `on_memo_expire` hook fires when TTL entries expire
112
- - `on_memo_evict` hook fires when manually resetting cache entries
113
- - `clear_memo_hooks` to remove registered hooks
114
- - Add cache statistics and monitoring capabilities
115
- - `cache_stats` for comprehensive cache metrics
116
- - `cache_stats_for(method_name)` for per-method statistics
117
- - `cache_hit_rate` and `cache_miss_rate` for performance analysis
118
- - `cache_metrics_reset` to clear collected metrics
119
- - Add manual cache key generation support
120
- - `memoize_with_custom_key` to define custom cache key logic
121
- - `clear_custom_keys` to remove custom key generators
122
- - Support for complex and computed keys based on arguments
118
+ ### Added
119
+
120
+ - Optional TTL expiration for memoized entries
121
+ - `on_memo_expire` and `on_memo_evict` lifecycle hooks; `clear_memo_hooks` to remove registered hooks
122
+ - Cache metrics: `cache_stats`, `cache_stats_for`, `cache_hit_rate`, `cache_miss_rate`, and `cache_metrics_reset`
123
+ - Custom cache key generation via `memoize_with_custom_key` and `clear_custom_keys`
123
124
 
124
125
  ## [0.1.2] - 2026-05-13
125
126
 
126
- - Preserve public, protected, and private visibility for memoized methods
127
- - Allow reset_memo to clear one cached argument combination or all entries for a method
128
- - Add a memoized? helper for checking whether a method call is already cached
129
- - Add a memo_count helper for inspecting cache size per instance or method
130
- - Add a memo_keys helper for inspecting cached argument signatures
131
- - Add a memo_values helper for inspecting cached signatures and their values
127
+ ### Added
128
+
129
+ - Method visibility preservation (public, protected, private) for memoized methods
130
+ - Targeted `reset_memo` clear one cached argument combination or all entries for a method
131
+ - `memoized?` helper to check whether a specific call is cached
132
+ - `memo_count`, `memo_keys`, and `memo_values` helpers for cache introspection
132
133
 
133
134
  ## [0.1.1] - 2026-05-13
134
135
 
135
- - Add automated release tooling plus a GitHub Actions workflow for RubyGems publishing and GitHub releases
136
+ ### Added
137
+
138
+ - Automated release tooling (`bin/release`) and GitHub Actions workflow for RubyGems publishing and GitHub releases
136
139
 
137
140
  ## [0.1.0] - 2026-02-26
138
141
 
142
+ ### Added
143
+
139
144
  - Initial release
145
+
146
+ [Unreleased]: https://github.com/eclectic-coding/safe_memoize/compare/v0.7.0...HEAD
147
+ [0.7.0]: https://github.com/eclectic-coding/safe_memoize/compare/v0.6.3...v0.7.0
148
+ [0.6.3]: https://github.com/eclectic-coding/safe_memoize/compare/v0.6.2...v0.6.3
149
+ [0.6.2]: https://github.com/eclectic-coding/safe_memoize/compare/v0.6.1...v0.6.2
150
+ [0.6.1]: https://github.com/eclectic-coding/safe_memoize/compare/v0.6.0...v0.6.1
151
+ [0.6.0]: https://github.com/eclectic-coding/safe_memoize/compare/v0.5.0...v0.6.0
152
+ [0.5.0]: https://github.com/eclectic-coding/safe_memoize/compare/v0.4.0...v0.5.0
153
+ [0.4.0]: https://github.com/eclectic-coding/safe_memoize/compare/v0.3.0...v0.4.0
154
+ [0.3.0]: https://github.com/eclectic-coding/safe_memoize/compare/v0.2.0...v0.3.0
155
+ [0.2.0]: https://github.com/eclectic-coding/safe_memoize/compare/v0.1.2...v0.2.0
156
+ [0.1.2]: https://github.com/eclectic-coding/safe_memoize/compare/v0.1.1...v0.1.2
157
+ [0.1.1]: https://github.com/eclectic-coding/safe_memoize/compare/v0.1.0...v0.1.1
158
+ [0.1.0]: https://github.com/eclectic-coding/safe_memoize/releases/tag/v0.1.0