x-streams 1.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.
- checksums.yaml +7 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +99 -0
- data/LICENSE.txt +21 -0
- data/README.md +61 -0
- data/lib/x/streams/api.rb +41 -0
- data/lib/x/streams/callback_error.rb +53 -0
- data/lib/x/streams/error.rb +34 -0
- data/lib/x/streams/reconnect_handler.rb +322 -0
- data/lib/x/streams/rules_rejected.rb +84 -0
- data/lib/x/streams/stopper.rb +366 -0
- data/lib/x/streams/stream_error.rb +96 -0
- data/lib/x/streams/stream_parser.rb +182 -0
- data/lib/x/streams/stream_rule.rb +182 -0
- data/lib/x/streams/stream_rules.rb +153 -0
- data/lib/x/streams/streaming_client.rb +544 -0
- data/lib/x/streams/validator.rb +119 -0
- data/lib/x/streams/version.rb +24 -0
- data/lib/x/streams.rb +9 -0
- data/sig/manifest.yaml +6 -0
- data/sig/x-streams.rbs +87 -0
- metadata +88 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module X
|
|
4
|
+
module Streams
|
|
5
|
+
# Checks the settings of a streaming client and the parsing classes of a stream before a stream is opened, and the
|
|
6
|
+
# identifiers of rules
|
|
7
|
+
#
|
|
8
|
+
# Internal to x-streams: StreamingClient and ReconnectHandler check what they are given with it, with the
|
|
9
|
+
# messages X::Client checks its own settings with, and StreamRule and StreamRules read identifiers with it, as
|
|
10
|
+
# x-resources reads them.
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
module Validator
|
|
14
|
+
extend self
|
|
15
|
+
|
|
16
|
+
# The message of the error raised for a count that is neither an Integer of at least 0 nor Float::INFINITY
|
|
17
|
+
INVALID_COUNT_OR_INFINITY = "%s must be an Integer of at least 0, or Float::INFINITY for no limit, not %s"
|
|
18
|
+
# The fewest seconds a stream may wait for a read: the interval of the keep-alive X sends a quiet stream, and five
|
|
19
|
+
# seconds more, so that a keep-alive that arrives a little late does not time the read out
|
|
20
|
+
MINIMUM_READ_TIMEOUT = 25 # seconds
|
|
21
|
+
# The message of the error raised for a read timeout that is neither a finite number of seconds of at least
|
|
22
|
+
# MINIMUM_READ_TIMEOUT nor nil
|
|
23
|
+
INVALID_READ_TIMEOUT = "%s must be a finite number of seconds of at least #{MINIMUM_READ_TIMEOUT}, five more than the " \
|
|
24
|
+
"20-second interval of the keep-alive X sends a quiet stream, or nil for no timeout, not %s"
|
|
25
|
+
# The message of the error raised for an array_class that is not a Class
|
|
26
|
+
INVALID_ARRAY_CLASS = "%s must be a Class that JSON.parse builds each array into, such as Array, not %s"
|
|
27
|
+
# The message of the error raised for an object_class that is neither a Class nor responds to from_response
|
|
28
|
+
INVALID_OBJECT_CLASS = "%s must be a Class that JSON.parse builds each object into, such as Hash, or respond to " \
|
|
29
|
+
"from_response, as the resource classes of x-resources do, not %s"
|
|
30
|
+
# The message of the error raised for a callback that neither responds to call nor is nil
|
|
31
|
+
INVALID_CALLABLE = "%s must respond to call, as a Proc or a lambda does, or be nil, not %s %s"
|
|
32
|
+
# The pattern of the name of a class that an takes the place of a before, as that of an Integer
|
|
33
|
+
VOWEL = /\A[AEIOU]/
|
|
34
|
+
# The pattern of an identifier given as a String: digits alone, with no sign, underscore, or whitespace
|
|
35
|
+
IDENTIFIER = /\A\d+\z/
|
|
36
|
+
# The message of the error raised for an identifier that is neither an Integer that is not negative nor a String
|
|
37
|
+
# of digits, as Integer() words it
|
|
38
|
+
INVALID_IDENTIFIER = "invalid value for Integer(): %s"
|
|
39
|
+
private_constant :MINIMUM_READ_TIMEOUT, :INVALID_COUNT_OR_INFINITY, :INVALID_READ_TIMEOUT, :INVALID_ARRAY_CLASS, :INVALID_OBJECT_CLASS,
|
|
40
|
+
:INVALID_CALLABLE, :VOWEL, :IDENTIFIER, :INVALID_IDENTIFIER
|
|
41
|
+
|
|
42
|
+
# Check that a count is an Integer of at least 0, or Float::INFINITY for no limit
|
|
43
|
+
#
|
|
44
|
+
# @api private
|
|
45
|
+
# @param name [Symbol] the name of the setting, which the error names
|
|
46
|
+
# @param value [Object] the value of the setting
|
|
47
|
+
# @return [Integer, Float] the value
|
|
48
|
+
# @raise [ArgumentError] if the value is neither an Integer of at least 0 nor Float::INFINITY
|
|
49
|
+
def count_or_infinity!(name, value)
|
|
50
|
+
return value if (value.instance_of?(Integer) && !value.negative?) || Float::INFINITY.eql?(value)
|
|
51
|
+
|
|
52
|
+
raise ArgumentError, format(INVALID_COUNT_OR_INFINITY, name, value.inspect)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Check that a read timeout is a finite number of seconds of at least 25, or nil
|
|
56
|
+
#
|
|
57
|
+
# X sends a quiet stream a keep-alive every 20 seconds, so a read timeout no longer than that drops a stream that
|
|
58
|
+
# is quiet but connected whenever a keep-alive arrives a little late, and one shorter drops it every time, and
|
|
59
|
+
# reconnects it without end, delivering nothing, where the read timeout of a request may be as short as 0.
|
|
60
|
+
#
|
|
61
|
+
# @api private
|
|
62
|
+
# @param name [Symbol] the name of the setting, which the error names
|
|
63
|
+
# @param value [Object] the value of the setting
|
|
64
|
+
# @return [Integer, Float, nil] the value
|
|
65
|
+
# @raise [ArgumentError] if the value is neither a finite real number of at least MINIMUM_READ_TIMEOUT nor nil
|
|
66
|
+
def read_timeout!(name, value)
|
|
67
|
+
return value if value.nil? || (value.is_a?(Numeric) && value.real? && value.finite? && value >= MINIMUM_READ_TIMEOUT)
|
|
68
|
+
|
|
69
|
+
raise ArgumentError, format(INVALID_READ_TIMEOUT, name, value.inspect)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check that a callback responds to call, or is nil
|
|
73
|
+
#
|
|
74
|
+
# @api private
|
|
75
|
+
# @param name [Symbol] the name of the setting, which the error names
|
|
76
|
+
# @param value [Object] the value of the setting
|
|
77
|
+
# @return [#call, nil] the value
|
|
78
|
+
# @raise [ArgumentError] if the value neither responds to call nor is nil
|
|
79
|
+
def callable!(name, value)
|
|
80
|
+
return value if value.nil? || value.respond_to?(:call)
|
|
81
|
+
|
|
82
|
+
raise ArgumentError, format(INVALID_CALLABLE, name, value.class.to_s.match?(VOWEL) ? "an" : "a", value.class)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Read the identifier of a rule as an Integer
|
|
86
|
+
#
|
|
87
|
+
# It is read as strictly as x-resources reads an identifier: an Integer that is not negative, or a String of digits
|
|
88
|
+
# alone, as the API sends one, with no sign, underscore, or whitespace, which Integer() would take, so that
|
|
89
|
+
# " 1_0 " is not read as 10, nor "-1" as -1.
|
|
90
|
+
#
|
|
91
|
+
# @api private
|
|
92
|
+
# @param value [Object] the identifier
|
|
93
|
+
# @return [Integer] the identifier
|
|
94
|
+
# @raise [ArgumentError] if the identifier is neither an Integer that is not negative nor a String of digits
|
|
95
|
+
def identifier!(value)
|
|
96
|
+
return value if value.instance_of?(Integer) && !value.negative?
|
|
97
|
+
raise ArgumentError, format(INVALID_IDENTIFIER, value.to_s.inspect) unless value.instance_of?(String) && IDENTIFIER.match?(value)
|
|
98
|
+
|
|
99
|
+
Integer(value, 10)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Check the classes a stream parses its objects into
|
|
103
|
+
#
|
|
104
|
+
# @api private
|
|
105
|
+
# @param array_class [Object] the class for parsing JSON arrays
|
|
106
|
+
# @param object_class [Object] the class for parsing JSON objects, or one that responds to from_response
|
|
107
|
+
# @return [void]
|
|
108
|
+
# @raise [ArgumentError] if array_class is not a Class, or object_class is neither a Class nor responds to
|
|
109
|
+
# from_response
|
|
110
|
+
def parsing_classes!(array_class:, object_class:)
|
|
111
|
+
raise ArgumentError, format(INVALID_ARRAY_CLASS, :array_class, array_class.inspect) unless array_class.instance_of?(Class)
|
|
112
|
+
return if object_class.instance_of?(Class) || object_class.respond_to?(:from_response)
|
|
113
|
+
|
|
114
|
+
raise ArgumentError, format(INVALID_OBJECT_CLASS, :object_class, object_class.inspect)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
private_constant :Validator
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubygems/version"
|
|
4
|
+
|
|
5
|
+
module X
|
|
6
|
+
# Streams from the X API, and the rules of its filtered stream
|
|
7
|
+
# @api public
|
|
8
|
+
module Streams
|
|
9
|
+
# The current version of the x-streams gem
|
|
10
|
+
VERSION = "1.0.0"
|
|
11
|
+
|
|
12
|
+
# The version as a Gem::Version, which compares one release with another
|
|
13
|
+
#
|
|
14
|
+
# VERSION is a String, as a version constant is throughout Ruby, so that what reads it can split it, match it,
|
|
15
|
+
# or send it wherever a String belongs. This builds the Gem::Version that compares it with another version,
|
|
16
|
+
# which a String compares by character rather than by segment.
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
# @return [Gem::Version] the version
|
|
20
|
+
# @example Take a path that a later release opened
|
|
21
|
+
# X::Streams.gem_version >= Gem::Version.new("1.1")
|
|
22
|
+
def self.gem_version = Gem::Version.new(VERSION)
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/x/streams.rb
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "streams/version"
|
|
4
|
+
require_relative "streams/api"
|
|
5
|
+
require_relative "streams/error"
|
|
6
|
+
require_relative "streams/rules_rejected"
|
|
7
|
+
require_relative "streams/stream_error"
|
|
8
|
+
require_relative "streams/stream_rule"
|
|
9
|
+
require_relative "streams/streaming_client"
|
data/sig/manifest.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# The standard libraries the signatures of x-streams refer to, which rbs collection loads for code that depends on it
|
|
2
|
+
#
|
|
3
|
+
# rbs collection reads every library named here as a standard library, so the gems x-streams depends on are left to
|
|
4
|
+
# its gemspec, from which it installs their signatures.
|
|
5
|
+
dependencies:
|
|
6
|
+
- name: uri
|
data/sig/x-streams.rbs
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Signatures of the public API of x-streams, which the gem ships; sig/internal declares its internals for Steep alone
|
|
2
|
+
module X
|
|
3
|
+
module Streams
|
|
4
|
+
VERSION: String
|
|
5
|
+
|
|
6
|
+
def self.gem_version: () -> Gem::Version
|
|
7
|
+
|
|
8
|
+
class Error < X::Error
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# The streaming method mixed into a client
|
|
12
|
+
module API
|
|
13
|
+
def streaming: (?read_timeout: Numeric?, ?max_reconnects: Float | Integer, ?on_reconnect: StreamingClient::_ReconnectHook?) -> StreamingClient
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class RulesRejected < Streams::Error
|
|
18
|
+
attr_reader problems: Array[Problem]
|
|
19
|
+
attr_reader added: Array[StreamRule]?
|
|
20
|
+
attr_reader deleted_count: Integer?
|
|
21
|
+
|
|
22
|
+
def initialize: (?String? message, ?problems: Array[Problem], ?added: Array[StreamRule]?, ?deleted_count: Integer?) -> void
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class StreamError < Streams::Error
|
|
26
|
+
attr_reader problems: Array[Problem]
|
|
27
|
+
attr_reader http_method: Symbol?
|
|
28
|
+
attr_reader uri: URI::Generic?
|
|
29
|
+
|
|
30
|
+
def initialize: (?String? message, ?problems: Array[Problem], ?http_method: (Symbol | String)?, ?uri: URI::Generic?) -> void
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class StreamRule
|
|
34
|
+
attr_reader id: Integer?
|
|
35
|
+
attr_reader value: String
|
|
36
|
+
attr_reader tag: String?
|
|
37
|
+
def initialize: (value: String, ?tag: String?, ?id: (Integer | String)?) -> void
|
|
38
|
+
def to_h: () -> {id: Integer?, value: String, tag: String?}
|
|
39
|
+
def deconstruct_keys: (Array[Symbol]? _keys) -> {id: Integer?, value: String, tag: String?}
|
|
40
|
+
def ==: (untyped other) -> bool
|
|
41
|
+
def eql?: (untyped other) -> bool
|
|
42
|
+
def hash: () -> Integer
|
|
43
|
+
def inspect: () -> String
|
|
44
|
+
def marshal_dump: () -> untyped
|
|
45
|
+
def marshal_load: (untyped state) -> void
|
|
46
|
+
def encode_with: (untyped coder) -> void
|
|
47
|
+
def init_with: (untyped coder) -> void
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class StreamingClient
|
|
51
|
+
DEFAULT_READ_TIMEOUT: Float | Integer
|
|
52
|
+
DEFAULT_MAX_RECONNECTS: Float | Integer
|
|
53
|
+
|
|
54
|
+
type addable_rule = StreamRule | Hash[untyped, untyped] | String
|
|
55
|
+
type rule = addable_rule | Integer | _MatchingRule
|
|
56
|
+
|
|
57
|
+
# The X::MatchingRule of x-resources, which x-streams does not depend on, and which names a rule by its identifier;
|
|
58
|
+
# anything else with an id raises ArgumentError
|
|
59
|
+
interface _MatchingRule
|
|
60
|
+
def id: () -> Integer
|
|
61
|
+
def tag: () -> String?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The callable passed the error that dropped a stream and its wait to reconnect, before each wait; every release
|
|
65
|
+
# of 1.x passes these two arguments and no others
|
|
66
|
+
interface _ReconnectHook
|
|
67
|
+
def call: (StandardError error, Integer | Float wait) -> untyped
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
attr_reader client: Client
|
|
71
|
+
attr_reader on_reconnect: _ReconnectHook?
|
|
72
|
+
def initialize: (Client client, ?read_timeout: Numeric?, ?max_reconnects: Integer | Float, ?on_reconnect: _ReconnectHook?) -> void
|
|
73
|
+
def inspect: () -> String
|
|
74
|
+
def stream: (String endpoint, ?params: params?, ?headers: headers, ?array_class: Class, ?object_class: object_class) { (untyped) -> void } -> untyped
|
|
75
|
+
def stop: () -> nil
|
|
76
|
+
def stopped?: () -> bool
|
|
77
|
+
def read_timeout: () -> Numeric?
|
|
78
|
+
def max_reconnects: () -> (Integer | Float)
|
|
79
|
+
def rules: (?params: params?) -> Array[StreamRule]
|
|
80
|
+
def add_rules: (Array[addable_rule] | addable_rule rules, ?dry_run: bool) ?{ (Problem problem) -> void } -> Array[StreamRule]
|
|
81
|
+
def delete_rules: (Array[rule] | rule rules, ?dry_run: bool) ?{ (Problem problem) -> void } -> Integer
|
|
82
|
+
def marshal_dump: () -> bot
|
|
83
|
+
def encode_with: (untyped coder) -> bot
|
|
84
|
+
def as_json: (*untyped) -> bot
|
|
85
|
+
def to_json: (?JSON::State? state) -> bot
|
|
86
|
+
end
|
|
87
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: x-streams
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Erik Berlin
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: x-core
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 1.0.0
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '2'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: 1.0.0
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '2'
|
|
32
|
+
email:
|
|
33
|
+
- sferik@gmail.com
|
|
34
|
+
executables: []
|
|
35
|
+
extensions: []
|
|
36
|
+
extra_rdoc_files: []
|
|
37
|
+
files:
|
|
38
|
+
- ".yardopts"
|
|
39
|
+
- CHANGELOG.md
|
|
40
|
+
- LICENSE.txt
|
|
41
|
+
- README.md
|
|
42
|
+
- lib/x/streams.rb
|
|
43
|
+
- lib/x/streams/api.rb
|
|
44
|
+
- lib/x/streams/callback_error.rb
|
|
45
|
+
- lib/x/streams/error.rb
|
|
46
|
+
- lib/x/streams/reconnect_handler.rb
|
|
47
|
+
- lib/x/streams/rules_rejected.rb
|
|
48
|
+
- lib/x/streams/stopper.rb
|
|
49
|
+
- lib/x/streams/stream_error.rb
|
|
50
|
+
- lib/x/streams/stream_parser.rb
|
|
51
|
+
- lib/x/streams/stream_rule.rb
|
|
52
|
+
- lib/x/streams/stream_rules.rb
|
|
53
|
+
- lib/x/streams/streaming_client.rb
|
|
54
|
+
- lib/x/streams/validator.rb
|
|
55
|
+
- lib/x/streams/version.rb
|
|
56
|
+
- sig/manifest.yaml
|
|
57
|
+
- sig/x-streams.rbs
|
|
58
|
+
homepage: https://sferik.github.io/x-ruby
|
|
59
|
+
licenses:
|
|
60
|
+
- MIT
|
|
61
|
+
metadata:
|
|
62
|
+
allowed_push_host: https://rubygems.org
|
|
63
|
+
bug_tracker_uri: https://github.com/sferik/x-ruby/issues
|
|
64
|
+
changelog_uri: https://github.com/sferik/x-ruby/blob/main/x-streams/CHANGELOG.md
|
|
65
|
+
documentation_uri: https://rubydoc.info/gems/x-streams/
|
|
66
|
+
funding_uri: https://github.com/sponsors/sferik/
|
|
67
|
+
homepage_uri: https://sferik.github.io/x-ruby
|
|
68
|
+
rubygems_mfa_required: 'true'
|
|
69
|
+
source_code_uri: https://github.com/sferik/x-ruby/tree/main/x-streams
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '3.4'
|
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
requirements: []
|
|
84
|
+
rubygems_version: 4.0.21
|
|
85
|
+
specification_version: 4
|
|
86
|
+
summary: 'Streaming for the X gem: the sample and filtered streams, reconnected as
|
|
87
|
+
X recommends, and the rules of the filtered stream.'
|
|
88
|
+
test_files: []
|