graphql-anycable 1.3.3 → 1.3.4

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: ba22f385767198acc338809a6b60237248dfc15ecdd2f279a31628e6dad013e8
4
- data.tar.gz: 6e59e6c8119969ee8358219b080abecb05df38d6ffe5549adbc58869f214872d
3
+ metadata.gz: 8d1012a281016a5fdd02aa9a1decc2d3935f545fef5899c15505dfeef64c9628
4
+ data.tar.gz: ae8f4ff4cb25da208b31b8a257f60307bf66fe626362741ea29705c97bcab8e1
5
5
  SHA512:
6
- metadata.gz: 3479edd8cf0548d649ca3a0aaaeea728a8030c22342b50cb41beacca14b2ab7391bc9fc1cd2783ca861a07c8049b4ef1256e735fc97468469372213a822c9201
7
- data.tar.gz: f6595e5f63dd71d375ec8d90816ae102839a8fda29bfc3f3400af10dedbcbf0cd4fb587d1462233b1a1b733801dfa5af8f172fc1f0906c4b4ba4db6e5250eebf
6
+ metadata.gz: 3c37a74df3ffc723b7b2fcf7bbfc009b3d67df79275d1c027b22bec3c9f45d6b3a5896b270582c50fc0deb78e38c1f7bc4f38a03288aa941ac20ed584538a3c8
7
+ data.tar.gz: 7dff7ea7638ee30a09bbc889648db4e0ebf4a12ba2798f215b013f29bb92e146241b0a9e4cad167c4691fbe15141ef085966f4799d708352ed1729628c97d634
@@ -19,13 +19,17 @@ jobs:
19
19
  matrix:
20
20
  include:
21
21
  - ruby: "4.0"
22
- graphql: '~> 2.3'
22
+ graphql: '~> 2.6'
23
23
  anycable: '~> 1.6'
24
24
  redis_version: latest
25
+ - ruby: "3.4"
26
+ graphql: '~> 2.5.0'
27
+ anycable: '~> 1.6'
28
+ redis_version: '7.4'
25
29
  - ruby: "3.3"
26
- graphql: '~> 2.3'
30
+ graphql: '~> 2.3.0'
27
31
  anycable: '~> 1.5'
28
- redis_version: latest
32
+ redis_version: '7.4'
29
33
  - ruby: "3.2"
30
34
  graphql: '~> 2.2.0'
31
35
  anycable: '~> 1.5.0'
data/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 1.3.4 - 2026-08-21
11
+
12
+ ### Fixed
13
+
14
+ - Return `nil` from `read_subscription` if subscription data has gone from Redis, That was resulted in a "No query string was present" errors received on graphql subscription clients receiving a hash with an `nil` `query_string` instead of receiving `nil` directly. [@thlacroix] ([#51](https://github.com/anycable/graphql-anycable/pull/51))
15
+
16
+ - Subscription events are no longer dropped when another subscription with the same fingerprint expires during execution. [@prog-supdex] ([#53](https://github.com/anycable/graphql-anycable/pull/53))
17
+
18
+ - Deleting a subscription no longer goes through the deprecated `GraphQL::AnyCable.redis`; this avoids both the deprecation warning and reusing a connection after it has been returned to the pool. [@Envek] ([#53](https://github.com/anycable/graphql-anycable/pull/53))
19
+
20
+ ### Changed
21
+
22
+ - `GraphQL::Subscriptions::AnyCableSubscriptions#read_subscription` now raises `GraphQL::AnyCable::SubscriptionExpiredError` when the subscription is no longer stored in Redis, instead of returning `nil`. [@Envek] ([#53](https://github.com/anycable/graphql-anycable/pull/53))
23
+
10
24
  ## 1.3.3 - 2026-08-10
11
25
 
12
26
  ### Fixed
@@ -231,6 +245,7 @@ Technical release to test publishing via GitHub Actions.
231
245
 
232
246
  Initial version: store subscriptions on redis, re-execute queries in sync. [@Envek]
233
247
 
248
+ [@thlacroix]: https://github.com/thlacroix "Thomas Lacroix"
234
249
  [@jjb]: https://github.com/jjb "John Bachir"
235
250
  [@prog-supdex]: https://github.com/prog-supdex "Igor Platonov"
236
251
  [@ilyasgaraev]: https://github.com/ilyasgaraev "Ilyas Garaev"
@@ -16,5 +16,9 @@ module GraphQL
16
16
  DEFAULT_MESSAGE
17
17
  end
18
18
  end
19
+
20
+ # This error is raised when a subscription is gone from Redis by the time it is read,
21
+ # so that the caller can tell it from an update GraphQL has deliberately skipped.
22
+ class SubscriptionExpiredError < ::RuntimeError; end
19
23
  end
20
24
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module AnyCable
5
- VERSION = "1.3.3"
5
+ VERSION = "1.3.4"
6
6
  end
7
7
  end
@@ -95,10 +95,20 @@ module GraphQL
95
95
  def execute_grouped(fingerprint, subscription_ids, event, object)
96
96
  return if subscription_ids.empty?
97
97
 
98
- subscription_id = with_redis { |redis| subscription_ids.find { |sid| redis.exists?(redis_key(SUBSCRIPTION_PREFIX) + sid) } }
99
- return unless subscription_id # All subscriptions has expired but haven't cleaned up yet
98
+ result = nil
99
+
100
+ subscription_ids.each do |subscription_id|
101
+ result = execute_update(subscription_id, event, object)
102
+
103
+ # Whatever GraphQL has decided here — including a nil result for NO_UPDATE or
104
+ # for #unsubscribe without a final update — applies to the whole group, as all
105
+ # of its subscriptions share the same fingerprint. So, ask only one of them.
106
+ break
107
+ rescue GraphQL::AnyCable::SubscriptionExpiredError
108
+ # This one is gone, but the rest of the group is still waiting for the update
109
+ next
110
+ end
100
111
 
101
- result = execute_update(subscription_id, event, object)
102
112
  return unless result
103
113
 
104
114
  # Having calculated the result _once_, send the same payload to all subscribers
@@ -162,19 +172,27 @@ module GraphQL
162
172
  end
163
173
 
164
174
  # Return the query from "storage" (in redis)
175
+ # @raise [GraphQL::AnyCable::SubscriptionExpiredError] if it is not stored anymore
165
176
  def read_subscription(subscription_id)
166
- with_redis do |redis|
177
+ subscription = with_redis do |redis|
167
178
  redis.mapped_hmget(
168
179
  "#{redis_key(SUBSCRIPTION_PREFIX)}#{subscription_id}",
169
180
  :query_string, :variables, :context, :operation_name
170
- ).tap do |subscription|
171
- next if subscription.values.all?(&:nil?) # Redis returns hash with all nils for missing key
172
-
173
- subscription[:context] = @serializer.load(subscription[:context])
174
- subscription[:variables] = JSON.parse(subscription[:variables])
175
- subscription[:operation_name] = nil if subscription[:operation_name].strip == ""
176
- end
181
+ )
177
182
  end
183
+
184
+ # Give the connection back before raising or deserializing: a connector may be
185
+ # backed by a pool, and neither of these needs Redis anymore.
186
+ #
187
+ # Redis returns a hash with nil values for a missing key. A subscription without
188
+ # a query string is unusable anyway, so treat a half-written one as missing, too.
189
+ raise GraphQL::AnyCable::SubscriptionExpiredError, subscription_id if subscription[:query_string].nil?
190
+
191
+ subscription[:context] = @serializer.load(subscription[:context])
192
+ subscription[:variables] = JSON.parse(subscription[:variables])
193
+ subscription[:operation_name] = nil if subscription[:operation_name].to_s.strip == ""
194
+
195
+ subscription
178
196
  end
179
197
 
180
198
  # The channel was closed, forget about it and its subscriptions
@@ -194,7 +212,9 @@ module GraphQL
194
212
  end
195
213
  end
196
214
 
197
- def delete_subscription(subscription_id, redis: AnyCable.redis)
215
+ def delete_subscription(subscription_id, redis: nil)
216
+ return with_redis { |connection| delete_subscription(subscription_id, redis: connection) } unless redis
217
+
198
218
  events = redis.hget(redis_key(SUBSCRIPTION_PREFIX) + subscription_id, :events)
199
219
  events = events ? JSON.parse(events) : {}
200
220
  fingerprint_subscriptions = {}
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-anycable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.3
4
+ version: 1.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Novikov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-10 00:00:00.000000000 Z
11
+ date: 2026-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: anycable-core