salesforce_streamer 2.0.0.rc2 → 2.3.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: 62cda30f1dc4379de105485088cce3cb77a874c6b336465b05ebdc0976c46e60
4
- data.tar.gz: 8c470f3301875a1926dfc68e8b40cc3862d8478bbd95c97fe4cdd154c0bd3c05
3
+ metadata.gz: 76475a30dc6515e8071504a9383c2811fa339f0885be703929008aaf2abbed8d
4
+ data.tar.gz: 927f5ff453fc8e51adaeae3915ee4ace4b0bcf11e355fc66c5aa32dbb5eec3aa
5
5
  SHA512:
6
- metadata.gz: c8a7aee18710329e556852c68aeb626e587108f788acf880aa0810671b9c536517ee34798eb1e9a8fd03a67ff2e8171f59ddf1ab8da9b4f836f57b5cf3746015
7
- data.tar.gz: 4c91a95c717ece055e22e05b487994293c2015f04633e0d46676f2a740986345762c8d263f40f57b981ebc18ab76553dbc7252ad2ebe95ea415ff6b533eba7f6
6
+ metadata.gz: cf695ae8cf24912b2ccb403b46b3151cc9ec988202ed9dd04857dab27cbef4d9afec3425898384649bc1c5bc84e5a68d515237b20f0b0a249ebe92251d51cebf
7
+ data.tar.gz: 0b36d256fdc82499c701aa619205c0dddf3d8837200c1e1575f144fd87caed132658e3137d3c3bc9d655157086a5e6fa2b805561729b7cef50e19a526265ef1d
@@ -1,7 +1,7 @@
1
- # automatically approve PRs submitted by Dependabot
1
+ # automatically approve PRs submitted by Dependabot or Renofidev
2
2
  # this will allow Dependabot to automatically merge dependency update PRs where CI passes
3
3
  # from: https://github.com/hmarr/auto-approve-action
4
- name: Auto approve Dependabot PRs
4
+ name: Auto approve dependency upgrades PRs
5
5
 
6
6
  on:
7
7
  pull_request
@@ -11,6 +11,6 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
13
  - uses: hmarr/auto-approve-action@v2.0.0
14
- if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]'
14
+ if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]' || github.actor == 'renofidev'
15
15
  with:
16
16
  github-token: "${{ secrets.GITHUB_TOKEN }}"
@@ -0,0 +1,19 @@
1
+ name: automerge
2
+ on:
3
+ pull_request_review:
4
+ types:
5
+ - submitted
6
+ check_suite:
7
+ types:
8
+ - completed
9
+ status: {}
10
+ jobs:
11
+ automerge:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - name: automerge
15
+ uses: "pascalgn/automerge-action@v0.11.0"
16
+ env:
17
+ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
18
+ MERGE_METHOD: squash
19
+ MERGE_DELETE_BRANCH: true
@@ -3,6 +3,38 @@
3
3
  Sorted so that the most recent change logs are near the top. Only significant
4
4
  changes are logged in the change log.
5
5
 
6
+ ## 2020-08-17 Scott Serok [scott@renofi.com](mailto:scott@renofi.com)
7
+
8
+ v2.1 changes the expected interface of `Configuration#replay_adapter`.
9
+
10
+ Normally this breaking change would require a major version bump, but since the
11
+ functionality today is quiet broken we can call this a major bug fix.
12
+
13
+ The `config.replay_adapter` should be an object that has an interface like Hash.
14
+ It must respond to `[]` and `[]=`. By default the adapter is an empty hash. If
15
+ you want your push topic replayId to persist between restarts, then you should
16
+ implement a class with an appropriate interface.
17
+
18
+ ```ruby
19
+ class MyReplayAdapter
20
+ def [](channel)
21
+ MyPersistence.get(channel)
22
+ end
23
+
24
+ def []=(channel, replay_id)
25
+ MyPersistence.set(channel, replay_id)
26
+ end
27
+ end
28
+ ```
29
+
30
+ This change was sparked by a misunderstanding of the
31
+ `Restforce::Concerns::Streaming::ReplayExtension` replay handlers.
32
+ SalesforceStreamer can eliminate some complexity and fix a bug by delegating the
33
+ responsibility of maintaining the current replayId to that ReplayExtension. The
34
+ object will be used on each request/response cycle to record and read the latest
35
+ replayId as long as the object assigned to `config.replay_adapter` responds to
36
+ `[]` and `[]=`.
37
+
6
38
  ## 2020-08-04 Scott Serok [scott@renofi.com](mailto:scott@renofi.com)
7
39
 
8
40
  v2.0 is released as a major simplification of this library. There are 2
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- salesforce_streamer (2.0.0.rc2)
4
+ salesforce_streamer (2.3.0)
5
5
  dry-initializer (~> 3.0)
6
6
  faye (~> 1.4)
7
7
  restforce (>= 4.2, < 6.0)
@@ -13,16 +13,14 @@ GEM
13
13
  public_suffix (>= 2.0.2, < 5.0)
14
14
  ast (2.4.1)
15
15
  byebug (11.1.3)
16
- codecov (0.2.3)
17
- colorize
16
+ codecov (0.2.12)
18
17
  json
19
18
  simplecov
20
- colorize (0.8.1)
21
19
  cookiejar (0.3.3)
22
20
  diff-lcs (1.4.4)
23
21
  docile (1.3.2)
24
- dry-initializer (3.0.3)
25
- em-http-request (1.1.6)
22
+ dry-initializer (3.0.4)
23
+ em-http-request (1.1.7)
26
24
  addressable (>= 2.3.4)
27
25
  cookiejar (!= 0.3.1)
28
26
  em-socksify (>= 0.3)
@@ -31,8 +29,9 @@ GEM
31
29
  em-socksify (0.3.2)
32
30
  eventmachine (>= 1.0.0.beta.4)
33
31
  eventmachine (1.2.7)
34
- faraday (1.0.1)
32
+ faraday (1.1.0)
35
33
  multipart-post (>= 1.2, < 3)
34
+ ruby2_keywords
36
35
  faraday_middleware (1.0.0)
37
36
  faraday (~> 1.0)
38
37
  faye (1.4.0)
@@ -49,56 +48,59 @@ GEM
49
48
  hashie (4.1.0)
50
49
  http_parser.rb (0.6.0)
51
50
  json (2.3.1)
52
- jwt (2.2.1)
51
+ jwt (2.2.2)
53
52
  multi_json (1.15.0)
54
53
  multipart-post (2.1.1)
55
- parallel (1.19.2)
56
- parser (2.7.1.4)
54
+ parallel (1.20.0)
55
+ parser (2.7.2.0)
57
56
  ast (~> 2.4.1)
58
- public_suffix (4.0.5)
57
+ public_suffix (4.0.6)
59
58
  rack (2.2.3)
60
59
  rainbow (3.0.0)
61
60
  rake (13.0.1)
62
- regexp_parser (1.7.1)
63
- restforce (5.0.0)
61
+ regexp_parser (1.8.2)
62
+ restforce (5.0.3)
64
63
  faraday (>= 0.9.0, <= 2.0)
65
64
  faraday_middleware (>= 0.8.8, <= 2.0)
66
65
  hashie (>= 1.2.0, < 5.0)
67
66
  jwt (>= 1.5.6)
68
67
  rexml (3.2.4)
69
- rspec (3.9.0)
70
- rspec-core (~> 3.9.0)
71
- rspec-expectations (~> 3.9.0)
72
- rspec-mocks (~> 3.9.0)
73
- rspec-core (3.9.2)
74
- rspec-support (~> 3.9.3)
75
- rspec-expectations (3.9.2)
68
+ rspec (3.10.0)
69
+ rspec-core (~> 3.10.0)
70
+ rspec-expectations (~> 3.10.0)
71
+ rspec-mocks (~> 3.10.0)
72
+ rspec-core (3.10.0)
73
+ rspec-support (~> 3.10.0)
74
+ rspec-expectations (3.10.0)
76
75
  diff-lcs (>= 1.2.0, < 2.0)
77
- rspec-support (~> 3.9.0)
78
- rspec-mocks (3.9.1)
76
+ rspec-support (~> 3.10.0)
77
+ rspec-mocks (3.10.0)
79
78
  diff-lcs (>= 1.2.0, < 2.0)
80
- rspec-support (~> 3.9.0)
81
- rspec-support (3.9.3)
82
- rubocop (0.88.0)
79
+ rspec-support (~> 3.10.0)
80
+ rspec-support (3.10.0)
81
+ rubocop (1.2.0)
83
82
  parallel (~> 1.10)
84
- parser (>= 2.7.1.1)
83
+ parser (>= 2.7.1.5)
85
84
  rainbow (>= 2.2.2, < 4.0)
86
- regexp_parser (>= 1.7)
85
+ regexp_parser (>= 1.8)
87
86
  rexml
88
- rubocop-ast (>= 0.1.0, < 1.0)
87
+ rubocop-ast (>= 1.0.1)
89
88
  ruby-progressbar (~> 1.7)
90
89
  unicode-display_width (>= 1.4.0, < 2.0)
91
- rubocop-ast (0.3.0)
92
- parser (>= 2.7.1.4)
93
- rubocop-performance (1.7.1)
94
- rubocop (>= 0.82.0)
95
- rubocop-rspec (1.42.0)
90
+ rubocop-ast (1.1.1)
91
+ parser (>= 2.7.1.5)
92
+ rubocop-performance (1.8.1)
96
93
  rubocop (>= 0.87.0)
94
+ rubocop-ast (>= 0.4.0)
95
+ rubocop-rspec (2.0.0)
96
+ rubocop (~> 1.0)
97
+ rubocop-ast (>= 1.1.0)
97
98
  ruby-progressbar (1.10.1)
98
- simplecov (0.18.5)
99
+ ruby2_keywords (0.0.2)
100
+ simplecov (0.19.1)
99
101
  docile (~> 1.1)
100
102
  simplecov-html (~> 0.11)
101
- simplecov-html (0.12.2)
103
+ simplecov-html (0.12.3)
102
104
  unicode-display_width (1.7.0)
103
105
  websocket-driver (0.7.3)
104
106
  websocket-extensions (>= 0.1.0)
data/README.md CHANGED
@@ -20,33 +20,11 @@ And then execute:
20
20
 
21
21
  ## Usage
22
22
 
23
- ### Middleware
24
-
25
- You can initialize the streamer server with any number of middleware classes.
26
- When a message is received by a PushTopic subscription, the chain of middleware
27
- classes are executed before the message handler is called.
28
-
29
- ```ruby
30
- # config/initializers/streamer.rb
31
- class MySimpleMiddleware
32
- def initialize(handler)
33
- @handler = handler
34
- end
35
- def call(message)
36
- @handler.call(message)
37
- end
38
- end
39
-
40
- SalesforceStreamer.config.use_middleware MySimpleMiddleware
41
- ```
42
-
43
23
  ### Configure Push Topics
44
24
 
45
- Create a YAML file to configure your server subscriptions. The configuration
46
- for each subscription must have a nested `salesforce:` key. These settings will
47
- be synced to your Salesforce instance when the `-x` flag is set on the command
48
- line. For more information about the `replay:` and `notify_fields_for` options
49
- please see the Salesforce Streaming API reference documentation.
25
+ Create a YAML file to configure your PushTopic subscriptions. When streamer
26
+ starts up it will check for any differences between Salesforce PushTopics and
27
+ this yaml and update any differences when `config.manage_topics = true`.
50
28
 
51
29
  ```yaml
52
30
  # config/streamer.yml
@@ -71,14 +49,6 @@ production:
71
49
  <<: *DEFAULT
72
50
  ```
73
51
 
74
- It's important to note that the way push topics are managed is by the Salesforce
75
- name attribute. This should uniquely identify each push topic. It is not
76
- recommended to change the name of your push topic definitions; otherwise, the
77
- push topic manager will not find a push topic in Salesforce resulting in the
78
- creation of a brand new push topic. If the push topic manager identifies a
79
- difference in any of the other Salesforce attributes, then it will update the
80
- push topic in Salesforce before starting the streaming server.
81
-
82
52
  ### Define Message Handlers
83
53
 
84
54
  Define your handlers somewhere in your project. They must respond to either
@@ -88,14 +58,17 @@ Define your handlers somewhere in your project. They must respond to either
88
58
  # lib/account_change_handler.rb
89
59
  # Handle account changes inline
90
60
  class AccountChangeHandler
91
- def self.call(message)
92
- puts message
61
+ class << self
62
+ def call(message)
63
+ puts message
64
+ end
93
65
  end
94
66
  end
95
67
 
96
68
  # Handle account changes asynchronously
97
69
  class AccountChangeHandler
98
70
  include Sidekiq::Worker
71
+
99
72
  def perform(message)
100
73
  puts message
101
74
  end
@@ -116,10 +89,9 @@ Configure the `SalesforceStreamer` module.
116
89
  SalesforceStreamer.configure do |config|
117
90
  config.logger = Logger.new(STDERR, level: 'INFO')
118
91
  config.exception_adapter = proc { |e| puts e }
119
- config.replay_adapter = proc { |topic|
120
- (Store.get(topic.name) || topic.replay).to_i
121
- }
92
+ config.replay_adapter = MyReplayAdapter
122
93
  config.use_middleware AfterMessageReceived
94
+ config.use_faye_extension ErrorLoggingExtension.new
123
95
  config.manage_topics = true
124
96
  end
125
97
  ```
@@ -160,6 +132,74 @@ By default, the executable will load the YAML based on the `RACK_ENV` environmen
160
132
  variable, or default to `:development` if not set. You can override this by
161
133
  setting the `config.environment = :integration`
162
134
 
135
+ ### Message Handling Middleware
136
+
137
+ You can initialize the streamer server with any number of middleware classes.
138
+ When a message is received by a PushTopic subscription, the chain of middleware
139
+ classes are executed before the message handler is called.
140
+
141
+ ```ruby
142
+ # config/initializers/streamer.rb
143
+ class MySimpleMiddleware
144
+ def initialize(handler)
145
+ @handler = handler
146
+ end
147
+
148
+ def call(message)
149
+ @handler.call(message)
150
+ end
151
+ end
152
+
153
+ SalesforceStreamer.config.use_middleware MySimpleMiddleware
154
+ ```
155
+
156
+ ### ReplayAdapter
157
+
158
+ The `config.replay_adapter` should be an object that has an interface like Hash.
159
+ It must respond to `[]` and `[]=`. By default the adapter is an empty hash. If
160
+ you want your push topic replayId to persist between restarts, then you should
161
+ implement a class with an appropriate interface.
162
+
163
+ ```ruby
164
+ class MyReplayAdapter
165
+ def [](channel)
166
+ Persistence.get(channel)
167
+ end
168
+
169
+ def []=(channel, replay_id)
170
+ Persistence.set(channel, replay_id)
171
+ end
172
+ end
173
+ ```
174
+
175
+ This adapter will be used directly by `Restforce::ReplayExtension`.
176
+
177
+ ### Use Faye Extension
178
+
179
+ The `config.use_faye_extension` should be given an object that responds to
180
+ `.incoming(message, callback)` or `.outgoing(message, callback)` or both. Find
181
+ out more about extensions from
182
+ [Faye](https://github.com/faye/faye/blob/master/spec/ruby/server/extensions_spec.rb)
183
+ specs.
184
+
185
+ Any configured extensions are added to the Faye client used by the Restforce
186
+ client when starting up the server. If the extension responds to `.server=` then
187
+ the instance of `SalesforceStreamer::Server` is assigned. This may be convenient
188
+ to restart the server subscriptions if an error requires a reset.
189
+
190
+ ```ruby
191
+ class MyRestartFayeExtension
192
+ attr_accessor :server
193
+
194
+ def incoming(message, callback)
195
+ callback.call(message).tap |message|
196
+ server.restart if message['error'] == 'tragic'
197
+ end
198
+ end
199
+ end
200
+
201
+ SalesforceStreamer.config.use_faye_extension MyRestartFayeExtension.new
202
+ ```
163
203
  ## Development
164
204
 
165
205
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -32,7 +32,7 @@ module SalesforceStreamer
32
32
  end
33
33
 
34
34
  o.on '-v', '--verbose LEVEL', 'Set the log level (default no logging)' do |arg|
35
- @config.logger = Logger.new(STDERR, level: arg)
35
+ @config.logger = Logger.new($stderr, level: arg)
36
36
  end
37
37
 
38
38
  o.on '-V', '--version', 'Print the version information' do
@@ -3,7 +3,7 @@ module SalesforceStreamer
3
3
  class Configuration
4
4
  attr_accessor :environment, :logger, :require_path, :config_file,
5
5
  :manage_topics, :exception_adapter, :replay_adapter
6
- attr_reader :middleware
6
+ attr_reader :middleware, :faye_extensions
7
7
 
8
8
  class << self
9
9
  attr_writer :instance
@@ -21,11 +21,12 @@ module SalesforceStreamer
21
21
  @environment = ENV['RACK_ENV'] || :development
22
22
  @logger = Logger.new(IO::NULL)
23
23
  @exception_adapter = proc { |exc| fail(exc) }
24
- @replay_adapter = proc { |topic| topic.id || topic.replay }
24
+ @replay_adapter = Hash.new { |hash, key| hash[key] = -1 }
25
25
  @manage_topics = false
26
26
  @config_file = './config/streamer.yml'
27
27
  @require_path = './config/environment'
28
28
  @middleware = []
29
+ @faye_extensions = [ReplayIdErrorExtension.new]
29
30
  end
30
31
 
31
32
  def manage_topics?
@@ -37,6 +38,11 @@ module SalesforceStreamer
37
38
  @middleware << [klass, args, block]
38
39
  end
39
40
 
41
+ # adds a Faye extension
42
+ def use_faye_extension(extension)
43
+ @faye_extensions << extension
44
+ end
45
+
40
46
  # returns a ready to use chain of middleware
41
47
  def middleware_runner(last_handler)
42
48
  @middleware.reduce(last_handler) do |next_handler, current_handler|
@@ -34,7 +34,7 @@ module SalesforceStreamer
34
34
  @handler = Object.const_get(@handler)
35
35
  true
36
36
  rescue NameError, TypeError => e
37
- message = 'handler=' + @handler.to_s + ' exception=' + e.to_s
37
+ message = "handler=#{@handler} exception=#{e}"
38
38
  raise(PushTopicHandlerMissingError, message)
39
39
  end
40
40
 
@@ -8,10 +8,8 @@ module SalesforceStreamer
8
8
  @client.authenticate!
9
9
  end
10
10
 
11
- def subscribe(*args)
12
- @client.subscribe(args) do
13
- yield
14
- end
11
+ def subscribe(*args, &block)
12
+ @client.subscribe(args, &block)
15
13
  end
16
14
 
17
15
  # Returns nil or an instance of Restforce::SObject
@@ -1,15 +1,23 @@
1
1
  module SalesforceStreamer
2
2
  class Server
3
3
  attr_writer :push_topics
4
+ attr_reader :client
4
5
 
5
6
  def initialize(push_topics: [])
6
7
  @push_topics = push_topics
7
8
  end
8
9
 
9
10
  def run
10
- Log.info 'Starting Server'
11
+ Log.info 'Starting server'
11
12
  catch_signals
12
- start_em
13
+ reset_client
14
+ EM.run { subscribe }
15
+ end
16
+
17
+ def restart
18
+ Log.info 'Restarting server'
19
+ reset_client
20
+ EM.next_tick { subscribe }
13
21
  end
14
22
 
15
23
  private
@@ -23,24 +31,21 @@ module SalesforceStreamer
23
31
  end
24
32
  end
25
33
 
26
- def client
27
- return @client if @client
34
+ def reset_client
28
35
  @client = Restforce.new
29
- @client.authenticate!
30
- @client.faye.add_extension ReplayIdErrorExtension.new
31
- @client
36
+ client.authenticate!
37
+ Configuration.instance.faye_extensions.each do |extension|
38
+ Log.debug %(adding Faye extension #{extension})
39
+ extension.server = self if extension.respond_to?(:server=)
40
+ client.faye.add_extension extension
41
+ end
32
42
  end
33
43
 
34
- def start_em
35
- EM.run do
36
- @push_topics.map do |topic|
37
- replay_id = Configuration.instance.replay_adapter.call(topic)
38
- client.subscribe topic.name, replay: replay_id.to_i do |message|
39
- replay_id = message.dig('event', 'replayId')
40
- Log.info "Message #{replay_id} received from topic #{topic.name}"
41
- topic.handle message
42
- topic.id = replay_id
43
- end
44
+ def subscribe
45
+ @push_topics.each do |topic|
46
+ client.subscribe topic.name, replay: Configuration.instance.replay_adapter do |message|
47
+ Log.info "Message #{message.dig('event', 'replayId')} received from topic #{topic.name}"
48
+ topic.handle message
44
49
  end
45
50
  end
46
51
  end
@@ -1,3 +1,3 @@
1
1
  module SalesforceStreamer
2
- VERSION = '2.0.0.rc2'.freeze
2
+ VERSION = '2.3.0'.freeze
3
3
  end
@@ -14,6 +14,7 @@ Gem::Specification.new do |spec|
14
14
 
15
15
  spec.metadata['homepage_uri'] = spec.homepage
16
16
  spec.metadata['source_code_uri'] = spec.homepage
17
+ spec.metadata['documentation_uri'] = 'https://www.rubydoc.info/gems/salesforce_streamer'
17
18
 
18
19
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
20
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(bin/|spec/|\.rub)}) }
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: salesforce_streamer
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.rc2
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Serok
8
8
  - RenoFi Engineering Team
9
- autorequire:
9
+ autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2020-08-04 00:00:00.000000000 Z
12
+ date: 2020-11-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: dry-initializer
@@ -157,7 +157,7 @@ dependencies:
157
157
  - - ">="
158
158
  - !ruby/object:Gem::Version
159
159
  version: '0'
160
- description:
160
+ description:
161
161
  email:
162
162
  - scott@renofi.com
163
163
  - engineering@renofi.com
@@ -172,6 +172,7 @@ files:
172
172
  - ".github/ISSUE_TEMPLATE/story.md"
173
173
  - ".github/PULL_REQUEST_TEMPLATE.md"
174
174
  - ".github/workflows/auto-approve.yml"
175
+ - ".github/workflows/auto-merge.yml"
175
176
  - ".gitignore"
176
177
  - ".rspec"
177
178
  - ".travis.yml"
@@ -202,7 +203,8 @@ licenses:
202
203
  metadata:
203
204
  homepage_uri: https://github.com/renofi/salesforce_streamer
204
205
  source_code_uri: https://github.com/renofi/salesforce_streamer
205
- post_install_message:
206
+ documentation_uri: https://www.rubydoc.info/gems/salesforce_streamer
207
+ post_install_message:
206
208
  rdoc_options: []
207
209
  require_paths:
208
210
  - lib
@@ -213,12 +215,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
213
215
  version: '2.6'
214
216
  required_rubygems_version: !ruby/object:Gem::Requirement
215
217
  requirements:
216
- - - ">"
218
+ - - ">="
217
219
  - !ruby/object:Gem::Version
218
- version: 1.3.1
220
+ version: '0'
219
221
  requirements: []
220
- rubygems_version: 3.1.2
221
- signing_key:
222
+ rubygems_version: 3.1.4
223
+ signing_key:
222
224
  specification_version: 4
223
225
  summary: A wrapper around the Restforce Streaming API with a built-in PushTopic manager.
224
226
  test_files: []