chewy 7.6.0 → 8.0.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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -1
  3. data/.github/dependabot.yml +2 -2
  4. data/.github/workflows/ruby.yml +11 -10
  5. data/.rubocop.yml +1 -1
  6. data/.rubocop_todo.yml +132 -39
  7. data/CHANGELOG.md +18 -1
  8. data/CONTRIBUTING.md +1 -1
  9. data/LICENSE.txt +1 -1
  10. data/README.md +50 -1125
  11. data/chewy.gemspec +3 -2
  12. data/docker-compose.yml +14 -0
  13. data/docs/README.md +16 -0
  14. data/docs/configuration.md +440 -0
  15. data/docs/import.md +122 -0
  16. data/docs/indexing.md +329 -0
  17. data/docs/querying.md +72 -0
  18. data/docs/rake_tasks.md +108 -0
  19. data/docs/testing.md +41 -0
  20. data/docs/troubleshooting.md +101 -0
  21. data/gemfiles/base.gemfile +3 -3
  22. data/gemfiles/{rails.6.1.activerecord.gemfile → rails.7.2.activerecord.gemfile} +3 -3
  23. data/gemfiles/{rails.7.0.activerecord.gemfile → rails.8.0.activerecord.gemfile} +3 -3
  24. data/lib/chewy/config.rb +2 -2
  25. data/lib/chewy/errors.rb +3 -0
  26. data/lib/chewy/fields/root.rb +1 -1
  27. data/lib/chewy/index/actions.rb +5 -5
  28. data/lib/chewy/index/aliases.rb +1 -1
  29. data/lib/chewy/index/syncer.rb +5 -5
  30. data/lib/chewy/minitest/helpers.rb +1 -1
  31. data/lib/chewy/search/request.rb +4 -4
  32. data/lib/chewy/search/response.rb +7 -0
  33. data/lib/chewy/search/scrolling.rb +2 -1
  34. data/lib/chewy/strategy/delayed_sidekiq/worker.rb +1 -1
  35. data/lib/chewy/version.rb +1 -1
  36. data/lib/chewy.rb +4 -0
  37. data/migration_guide.md +1 -1
  38. data/spec/chewy/config_spec.rb +13 -14
  39. data/spec/chewy/elastic_client_spec.rb +1 -1
  40. data/spec/chewy/fields/base_spec.rb +2 -2
  41. data/spec/chewy/fields/time_fields_spec.rb +1 -1
  42. data/spec/chewy/index/actions_spec.rb +9 -70
  43. data/spec/chewy/index/aliases_spec.rb +1 -1
  44. data/spec/chewy/index/import/bulk_builder_spec.rb +2 -2
  45. data/spec/chewy/index/import/bulk_request_spec.rb +1 -1
  46. data/spec/chewy/index/import/routine_spec.rb +1 -1
  47. data/spec/chewy/index/import_spec.rb +15 -15
  48. data/spec/chewy/index/observe/callback_spec.rb +1 -1
  49. data/spec/chewy/index/specification_spec.rb +1 -4
  50. data/spec/chewy/index/syncer_spec.rb +1 -1
  51. data/spec/chewy/index_spec.rb +1 -1
  52. data/spec/chewy/journal_spec.rb +2 -2
  53. data/spec/chewy/minitest/helpers_spec.rb +2 -6
  54. data/spec/chewy/multi_search_spec.rb +1 -1
  55. data/spec/chewy/rake_helper_spec.rb +1 -1
  56. data/spec/chewy/repository_spec.rb +4 -4
  57. data/spec/chewy/rspec/update_index_spec.rb +2 -2
  58. data/spec/chewy/runtime_spec.rb +2 -2
  59. data/spec/chewy/search/loader_spec.rb +1 -1
  60. data/spec/chewy/search/pagination/kaminari_examples.rb +1 -1
  61. data/spec/chewy/search/query_proxy_spec.rb +0 -24
  62. data/spec/chewy/search/request_spec.rb +7 -3
  63. data/spec/chewy/search/response_spec.rb +2 -24
  64. data/spec/chewy/search/scrolling_spec.rb +1 -1
  65. data/spec/chewy/search_spec.rb +1 -1
  66. data/spec/chewy/stash_spec.rb +1 -1
  67. data/spec/chewy/strategy/delayed_sidekiq_spec.rb +27 -10
  68. data/spec/chewy/strategy_spec.rb +1 -1
  69. data/spec/chewy_spec.rb +5 -22
  70. data/spec/spec_helper.rb +26 -0
  71. data/spec/support/active_record.rb +35 -4
  72. metadata +22 -17
  73. data/gemfiles/rails.7.1.activerecord.gemfile +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a15165f889275fecc6a0d590c3339ce0ac9b7944f916306fd6883e7c2be67747
4
- data.tar.gz: 8efc6201add68bf1c378f598934b6c52c74bd9a62056e7aafdd357358d5cd2b0
3
+ metadata.gz: b5ed854e820d95af1d41232ffab69a142b8cb45591ba01f8b316349120ec49b5
4
+ data.tar.gz: b2ea3e975b6fcc5af8e3a993e731e1bb5a1e64ffab9edb1b26687e91e4c05763
5
5
  SHA512:
6
- metadata.gz: cfc7f1297fc72fcbdfdbedbcd82c6afe12dd9e15583b4e3467a8a77da89cd1aa8f20b9d0c0a8dd1841275e2916715030e28e9ed10cf510d6985904be9574890e
7
- data.tar.gz: 6917027945ce94dab50d2f6d679570be58e4bb86472f1d591805d915ca425ecbd24e4cc13e30a459fe2dae4d572002735b69cef138a5c6038c52803a251b8a54
6
+ metadata.gz: 92b482a9606d2e0e2dfcf8560d70679a1542f6e89012c012f7d46f4844dd568c04feca8a949b9c100444de73819d821edcb07cf9bbbb997a10f8cbcf93b033eb
7
+ data.tar.gz: c0595cbc913c89c295f653b124c545153f2775cdb64050bee435067020700093a69b137e132ec743feb4a5f477efd8c4a278eb3dce569c86f67cce1188c4e9da
data/.github/CODEOWNERS CHANGED
@@ -1 +1 @@
1
- .github/workflows @toptal/platform-sre
1
+ * @toptal/platform-foundation
@@ -20,7 +20,7 @@ updates:
20
20
  - "ruby"
21
21
  - "dependencies"
22
22
  reviewers:
23
- - "toptal/devx"
23
+ - "toptal/sre"
24
24
  registries:
25
25
  - toptal-github
26
26
  insecure-external-code-execution: allow
@@ -38,5 +38,5 @@ updates:
38
38
  - "dependencies"
39
39
  - "gha"
40
40
  reviewers:
41
- - "toptal/devx"
41
+ - "toptal/sre"
42
42
  open-pull-requests-limit: 3
@@ -3,6 +3,7 @@ name: CI
3
3
  on:
4
4
  push:
5
5
  branches: [master]
6
+ workflow_dispatch:
6
7
  pull_request:
7
8
  types: [
8
9
  synchronize, # PR was updated
@@ -16,8 +17,8 @@ jobs:
16
17
  strategy:
17
18
  fail-fast: false
18
19
  matrix:
19
- ruby: [ '3.0', '3.1', '3.2' ]
20
- gemfile: [rails.6.1.activerecord, rails.7.0.activerecord, rails.7.1.activerecord]
20
+ ruby: [ '3.2', '3.3', '3.4' ]
21
+ gemfile: [rails.7.2.activerecord, rails.8.0.activerecord]
21
22
  name: ${{ matrix.ruby }}-${{ matrix.gemfile }}
22
23
 
23
24
  env:
@@ -36,25 +37,25 @@ jobs:
36
37
  --health-timeout 5s
37
38
  --health-retries 5
38
39
  steps:
39
- - uses: actions/checkout@v4
40
+ - uses: actions/checkout@v6
40
41
  - uses: ruby/setup-ruby@v1
41
42
  with:
42
43
  ruby-version: ${{ matrix.ruby }}
43
44
  bundler-cache: true
44
- - name: Run Elasticsearch
45
- uses: elastic/elastic-github-actions/elasticsearch@9de0f78f306e4ebc0838f057e6b754364685e759
46
- with:
47
- stack-version: 7.10.1
48
- port: 9250
45
+ - name: Start containers
46
+ run: |
47
+ docker compose up elasticsearch_test -d
48
+ sleep 15
49
+
49
50
  - name: Tests
50
51
  run: bundle exec rspec
51
52
 
52
53
  rubocop:
53
54
  runs-on: ubuntu-latest
54
55
  steps:
55
- - uses: actions/checkout@v4
56
+ - uses: actions/checkout@v6
56
57
  - uses: ruby/setup-ruby@v1
57
58
  with:
58
- ruby-version: 3.0
59
+ ruby-version: 3.4
59
60
  bundler-cache: true
60
61
  - run: bundle exec rubocop --format simple
data/.rubocop.yml CHANGED
@@ -2,7 +2,7 @@ inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
4
  NewCops: enable
5
- TargetRubyVersion: 3.0
5
+ TargetRubyVersion: 3.2
6
6
 
7
7
  Layout/AccessModifierIndentation:
8
8
  EnforcedStyle: outdent
data/.rubocop_todo.yml CHANGED
@@ -1,17 +1,36 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2021-04-02 12:44:05 UTC using RuboCop version 1.11.0.
3
+ # on 2026-02-25 10:21:58 UTC using RuboCop version 1.84.2.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ # Offense count: 5
10
+ # This cop supports safe autocorrection (--autocorrect).
11
+ Layout/EmptyLinesAfterModuleInclusion:
12
+ Exclude:
13
+ - 'lib/chewy/rspec/helpers.rb'
14
+ - 'lib/chewy/runtime/version.rb'
15
+ - 'lib/chewy/search/parameters/limit.rb'
16
+ - 'lib/chewy/search/parameters/offset.rb'
17
+ - 'lib/chewy/search/request.rb'
18
+
9
19
  # Offense count: 1
10
- # Configuration parameters: Include.
11
- # Include: **/*.gemspec
12
- Gemspec/RequiredRubyVersion:
20
+ # This cop supports safe autocorrection (--autocorrect).
21
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
22
+ # SupportedStyles: aligned, indented, indented_relative_to_receiver
23
+ Layout/MultilineMethodCallIndentation:
24
+ Exclude:
25
+ - 'spec/chewy/rake_helper_spec.rb'
26
+
27
+ # Offense count: 1
28
+ # This cop supports safe autocorrection (--autocorrect).
29
+ # Configuration parameters: EnforcedStyle.
30
+ # SupportedStyles: final_newline, final_blank_line
31
+ Layout/TrailingEmptyLines:
13
32
  Exclude:
14
- - 'chewy.gemspec'
33
+ - 'gemfiles/rails.8.0.activerecord.gemfile'
15
34
 
16
35
  # Offense count: 2
17
36
  # Configuration parameters: AllowedMethods.
@@ -21,112 +40,186 @@ Lint/ConstantDefinitionInBlock:
21
40
  - 'lib/chewy/rspec/update_index.rb'
22
41
  - 'spec/chewy/config_spec.rb'
23
42
 
24
- # Offense count: 10
43
+ # Offense count: 11
25
44
  # Configuration parameters: AllowComments, AllowEmptyLambdas.
26
45
  Lint/EmptyBlock:
27
46
  Exclude:
47
+ - 'spec/chewy/index/import/bulk_request_spec.rb'
48
+ - 'spec/chewy/index/witchcraft_spec.rb'
28
49
  - 'spec/chewy/minitest/helpers_spec.rb'
29
50
  - 'spec/chewy/rspec/update_index_spec.rb'
30
51
  - 'spec/chewy/search/scrolling_spec.rb'
31
52
  - 'spec/chewy/strategy/atomic_spec.rb'
32
53
  - 'spec/chewy/strategy_spec.rb'
33
- - 'spec/chewy/index/import/bulk_request_spec.rb'
34
- - 'spec/chewy/index/witchcraft_spec.rb'
35
- - 'spec/chewy_spec.rb'
36
54
 
37
55
  # Offense count: 3
56
+ # Configuration parameters: AllowedParentClasses.
38
57
  Lint/MissingSuper:
39
58
  Exclude:
40
- - 'lib/chewy/strategy/atomic.rb'
41
59
  - 'lib/chewy/index/adapter/object.rb'
42
60
  - 'lib/chewy/index/adapter/orm.rb'
61
+ - 'lib/chewy/strategy/atomic.rb'
43
62
 
44
- # Offense count: 35
45
- # Configuration parameters: IgnoredMethods, CountRepeatedAttributes.
63
+ # Offense count: 41
64
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
46
65
  Metrics/AbcSize:
47
66
  Max: 41
48
67
 
49
- # Offense count: 4
68
+ # Offense count: 7
50
69
  # Configuration parameters: CountComments, CountAsOne.
51
70
  Metrics/ClassLength:
52
71
  Max: 267
53
72
 
54
- # Offense count: 13
55
- # Configuration parameters: IgnoredMethods.
73
+ # Offense count: 15
74
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
56
75
  Metrics/CyclomaticComplexity:
57
76
  Max: 12
58
77
 
59
- # Offense count: 43
60
- # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
78
+ # Offense count: 53
79
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
61
80
  Metrics/MethodLength:
62
81
  Max: 29
63
82
 
64
83
  # Offense count: 2
65
84
  # Configuration parameters: CountComments, CountAsOne.
66
85
  Metrics/ModuleLength:
67
- Max: 158
86
+ Max: 123
68
87
 
69
- # Offense count: 18
70
- # Configuration parameters: IgnoredMethods.
88
+ # Offense count: 19
89
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
71
90
  Metrics/PerceivedComplexity:
72
91
  Max: 13
73
92
 
93
+ # Offense count: 33
94
+ # This cop supports safe autocorrection (--autocorrect).
95
+ # Configuration parameters: EnforcedStyle, BlockForwardingName.
96
+ # SupportedStyles: anonymous, explicit
97
+ Naming/BlockForwarding:
98
+ Exclude:
99
+ - 'lib/chewy/elastic_client.rb'
100
+ - 'lib/chewy/index.rb'
101
+ - 'lib/chewy/index/adapter/base.rb'
102
+ - 'lib/chewy/index/adapter/object.rb'
103
+ - 'lib/chewy/index/adapter/orm.rb'
104
+ - 'lib/chewy/index/mapping.rb'
105
+ - 'lib/chewy/index/wrapper.rb'
106
+ - 'lib/chewy/minitest/helpers.rb'
107
+ - 'lib/chewy/search.rb'
108
+ - 'spec/chewy/index/adapter/active_record_spec.rb'
109
+ - 'spec/support/active_record.rb'
110
+ - 'spec/support/class_helpers.rb'
111
+
112
+ # Offense count: 10
113
+ # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
114
+ # AllowedMethods: call
115
+ # WaywardPredicates: infinite?, nonzero?
116
+ Naming/PredicateMethod:
117
+ Exclude:
118
+ - 'lib/chewy/index/adapter/object.rb'
119
+ - 'lib/chewy/index/import.rb'
120
+ - 'lib/chewy/index/import/routine.rb'
121
+ - 'lib/chewy/index/observe.rb'
122
+ - 'lib/chewy/index/syncer.rb'
123
+ - 'lib/chewy/rspec/update_index.rb'
124
+ - 'lib/chewy/search/parameters.rb'
125
+ - 'lib/chewy/search/parameters/concerns/bool_storage.rb'
126
+ - 'lib/chewy/search/request.rb'
127
+
74
128
  # Offense count: 11
75
- # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers.
129
+ # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
76
130
  # SupportedStyles: snake_case, normalcase, non_integer
77
- # AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339
131
+ # AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
78
132
  Naming/VariableNumber:
79
133
  Exclude:
80
134
  - 'spec/chewy/fields/root_spec.rb'
81
135
 
82
- # Offense count: 5
136
+ # Offense count: 3
83
137
  Style/DocumentDynamicEvalDefinition:
84
138
  Exclude:
85
- - 'lib/chewy/index/actions.rb'
139
+ - 'lib/chewy/index/witchcraft.rb'
86
140
  - 'lib/chewy/repository.rb'
87
141
  - 'lib/chewy/search/pagination/kaminari.rb'
88
- - 'lib/chewy/index/crutch.rb'
89
- - 'lib/chewy/index/witchcraft.rb'
90
142
 
91
- # Offense count: 58
143
+ # Offense count: 56
144
+ # Configuration parameters: AllowedConstants.
92
145
  Style/Documentation:
93
146
  Enabled: false
94
147
 
95
148
  # Offense count: 2
96
- # Cop supports --auto-correct.
149
+ # This cop supports safe autocorrection (--autocorrect).
97
150
  Style/EvalWithLocation:
98
151
  Exclude:
99
152
  - 'spec/chewy/index_spec.rb'
100
153
 
101
- # Offense count: 191
102
- # Cop supports --auto-correct.
154
+ # Offense count: 4
155
+ # This cop supports unsafe autocorrection (--autocorrect-all).
156
+ Style/FileNull:
157
+ Exclude:
158
+ - 'spec/chewy/config_spec.rb'
159
+ - 'spec/chewy/index/adapter/active_record_spec.rb'
160
+ - 'spec/support/active_record.rb'
161
+
162
+ # Offense count: 208
163
+ # This cop supports unsafe autocorrection (--autocorrect-all).
103
164
  # Configuration parameters: EnforcedStyle.
104
165
  # SupportedStyles: always, always_true, never
105
166
  Style/FrozenStringLiteralComment:
106
167
  Enabled: false
107
168
 
108
169
  # Offense count: 2
109
- # Configuration parameters: MinBodyLength.
170
+ # This cop supports safe autocorrection (--autocorrect).
171
+ # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals.
110
172
  Style/GuardClause:
111
173
  Exclude:
112
174
  - 'lib/chewy.rb'
113
175
  - 'spec/support/active_record.rb'
114
176
 
115
- # Offense count: 10
116
- # Cop supports --auto-correct.
177
+ # Offense count: 8
178
+ # This cop supports safe autocorrection (--autocorrect).
179
+ # Configuration parameters: EnforcedStyle.
180
+ # SupportedStyles: braces, no_braces
181
+ Style/HashAsLastArrayItem:
182
+ Exclude:
183
+ - 'lib/chewy/index/specification.rb'
184
+ - 'spec/chewy/search/parameters/query_storage_examples.rb'
185
+ - 'spec/chewy/search/request_spec.rb'
186
+ - 'spec/chewy/stash_spec.rb'
187
+
188
+ # Offense count: 1
189
+ # This cop supports unsafe autocorrection (--autocorrect-all).
190
+ Style/HashSlice:
191
+ Exclude:
192
+ - 'lib/chewy/search/parameters.rb'
193
+
194
+ # Offense count: 7
195
+ # This cop supports safe autocorrection (--autocorrect).
117
196
  Style/IfUnlessModifier:
118
197
  Exclude:
119
198
  - 'lib/chewy.rb'
120
199
  - 'lib/chewy/railtie.rb'
121
200
  - 'lib/chewy/rspec/update_index.rb'
122
201
  - 'lib/chewy/search/query_proxy.rb'
123
- - 'lib/chewy/index/import.rb'
124
- - 'lib/chewy/index/witchcraft.rb'
125
202
  - 'spec/support/active_record.rb'
126
203
 
127
- # Offense count: 53
128
- # Cop supports --auto-correct.
129
- # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
204
+ # Offense count: 5
205
+ # This cop supports safe autocorrection (--autocorrect).
206
+ Style/KeywordArgumentsMerging:
207
+ Exclude:
208
+ - 'lib/chewy/index/actions.rb'
209
+ - 'lib/chewy/index/import/bulk_request.rb'
210
+ - 'lib/chewy/index/mapping.rb'
211
+ - 'lib/chewy/search/loader.rb'
212
+
213
+ # Offense count: 5
214
+ # This cop supports safe autocorrection (--autocorrect).
215
+ Style/RedundantParentheses:
216
+ Exclude:
217
+ - 'lib/chewy/rake_helper.rb'
218
+ - 'lib/chewy/rspec/update_index.rb'
219
+
220
+ # Offense count: 120
221
+ # This cop supports safe autocorrection (--autocorrect).
222
+ # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
130
223
  # URISchemes: http, https
131
224
  Layout/LineLength:
132
- Max: 191
225
+ Max: 190
data/CHANGELOG.md CHANGED
@@ -1,11 +1,28 @@
1
1
  # Changelog
2
2
 
3
- ## master (unreleased)
3
+ ## 8.0.0 (2026-02-25)
4
4
 
5
5
  ### New Features
6
6
 
7
7
  ### Changes
8
8
 
9
+ * **(Breaking)** Drop support for Ruby < 3.2 and Rails < 7.2. Chewy now requires Ruby ~> 3.2 and ActiveSupport >= 7.2. ([@bbatsov][])
10
+
11
+ ### Bugs Fixed
12
+
13
+ * [#964](https://github.com/toptal/chewy/pull/964): Fix `delayed_sidekiq` worker to handle UUID primary keys correctly.
14
+ * Fix `FrozenError` in `Syncer` when ActiveRecord returns frozen arrays from `pluck`. ([@bbatsov][])
15
+
16
+ ## 8.0.0-beta (2024-08-27)
17
+
18
+ ### New Features
19
+
20
+ * [#962](https://github.com/toptal/chewy/pull/962): ElasticSearch v.8 support added
21
+
22
+ * `delete_all_enabled` setting introduced to align Chewy.massacre with wildcard indices deletion disabled in ES 8 by default
23
+
24
+ ### Changes
25
+
9
26
  ### Bugs Fixed
10
27
 
11
28
  ## 7.6.0 (2024-05-03)
data/CONTRIBUTING.md CHANGED
@@ -50,7 +50,7 @@ Here are a few examples:
50
50
  * Mark it up in [Markdown syntax][6].
51
51
  * The entry line should start with `* ` (an asterisk and a space).
52
52
  * If the change has a related GitHub issue (e.g. a bug fix for a reported issue), put a link to the issue as `[#123](https://github.com/toptal/chewy/issues/123): `.
53
- * Describe the brief of the change. The sentence should end with a punctuation.
53
+ * Describe the change briefly. The sentence should end with a punctuation.
54
54
  * If this is a breaking change, mark it with `**(Breaking)**`.
55
55
  * At the end of the entry, add an implicit link to your GitHub user page as `([@username][])`.
56
56
  * If this is your first contribution to the project, add a link definition for the implicit link to the bottom of the changelog as `[@username]: https://github.com/username`.
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013-2021 Toptal, LLC
1
+ Copyright (c) 2013-2025 Toptal, LLC
2
2
 
3
3
  MIT License
4
4