tether 0.1.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/LICENSE.txt +21 -0
- data/README.md +5 -0
- data/Rakefile +17 -0
- data/lib/tether/client.rb +298 -0
- data/lib/tether/deadline.rb +127 -0
- data/lib/tether/dispatcher.rb +109 -0
- data/lib/tether/errors.rb +204 -0
- data/lib/tether/headers.rb +256 -0
- data/lib/tether/inbox_mux.rb +113 -0
- data/lib/tether/msg.rb +90 -0
- data/lib/tether/nuid.rb +68 -0
- data/lib/tether/protocol/encoder.rb +125 -0
- data/lib/tether/protocol/parser.rb +336 -0
- data/lib/tether/protocol.rb +18 -0
- data/lib/tether/subscription.rb +123 -0
- data/lib/tether/transport.rb +114 -0
- data/lib/tether/version.rb +5 -0
- data/lib/tether.rb +28 -0
- data/sig/tether/client.rbs +45 -0
- data/sig/tether/deadline.rbs +21 -0
- data/sig/tether/dispatcher.rbs +19 -0
- data/sig/tether/errors.rbs +110 -0
- data/sig/tether/headers.rbs +50 -0
- data/sig/tether/inbox_mux.rbs +20 -0
- data/sig/tether/msg.rbs +16 -0
- data/sig/tether/nuid.rbs +21 -0
- data/sig/tether/protocol/encoder.rbs +13 -0
- data/sig/tether/protocol/parser.rbs +43 -0
- data/sig/tether/protocol.rbs +22 -0
- data/sig/tether/subscription.rbs +24 -0
- data/sig/tether/transport.rbs +22 -0
- data/sig/tether/version.rbs +3 -0
- metadata +77 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module Tether
|
|
7
|
+
# The socket a connection reads and writes.
|
|
8
|
+
#
|
|
9
|
+
# Deadlines are applied with `IO#timeout=`, which raises `IO::TimeoutError` at
|
|
10
|
+
# the point the operating system reports the socket is not ready, rather than
|
|
11
|
+
# at an arbitrary bytecode boundary the way `Timeout.timeout` would.
|
|
12
|
+
class Transport
|
|
13
|
+
DEFAULT_PORT = 4222
|
|
14
|
+
READ_SIZE = 64 * 1024
|
|
15
|
+
|
|
16
|
+
# @return [URI::Generic] the server this transport is connected to
|
|
17
|
+
attr_reader :uri
|
|
18
|
+
|
|
19
|
+
# Opens a TCP connection.
|
|
20
|
+
#
|
|
21
|
+
# @param uri [String, URI::Generic]
|
|
22
|
+
# @param deadline [Deadline] bounds the connect and every later read
|
|
23
|
+
# @return [Transport]
|
|
24
|
+
# @raise [ConnectionError] if the server cannot be reached
|
|
25
|
+
def self.connect(uri, deadline: Deadline.infinite)
|
|
26
|
+
new(uri).tap { _1.open(deadline) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Prepares a transport without opening it.
|
|
30
|
+
#
|
|
31
|
+
# @param uri [String, URI::Generic] a bare host:port is treated as nats://
|
|
32
|
+
def initialize(uri)
|
|
33
|
+
@uri = normalize(uri)
|
|
34
|
+
@lock = Mutex.new
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Opens the socket with TCP_NODELAY set, since the protocol is latency
|
|
38
|
+
# sensitive and frames are already written whole.
|
|
39
|
+
#
|
|
40
|
+
# @param deadline [Deadline]
|
|
41
|
+
# @return [self]
|
|
42
|
+
# @raise [ConnectionError]
|
|
43
|
+
def open(deadline = Deadline.infinite)
|
|
44
|
+
@socket = Socket.tcp(@uri.host, @uri.port, connect_timeout: deadline.remaining)
|
|
45
|
+
@socket.sync = true
|
|
46
|
+
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
|
|
47
|
+
self
|
|
48
|
+
rescue SystemCallError, IO::TimeoutError => e
|
|
49
|
+
raise ConnectionError, "could not connect to #{@uri}: #{e.message}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Reads whatever is available.
|
|
53
|
+
#
|
|
54
|
+
# @param deadline [Deadline]
|
|
55
|
+
# @return [String, nil] bytes, or nil at end of stream
|
|
56
|
+
# @raise [TimeoutError] if the deadline passes with nothing readable
|
|
57
|
+
def read(deadline = Deadline.infinite)
|
|
58
|
+
socket = @socket or raise ConnectionClosedError, "transport is closed"
|
|
59
|
+
socket.timeout = deadline.remaining
|
|
60
|
+
socket.readpartial(READ_SIZE)
|
|
61
|
+
rescue EOFError
|
|
62
|
+
nil
|
|
63
|
+
rescue IO::TimeoutError
|
|
64
|
+
raise TimeoutError, "timed out reading from #{@uri}"
|
|
65
|
+
rescue IOError, SystemCallError => e
|
|
66
|
+
raise ConnectionClosedError, "read failed on #{@uri}: #{e.message}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Writes one complete frame.
|
|
70
|
+
#
|
|
71
|
+
# The lock spans the whole frame rather than each write, so frames from
|
|
72
|
+
# different threads cannot interleave on the wire.
|
|
73
|
+
#
|
|
74
|
+
# @param frame [String] a complete, already encoded frame
|
|
75
|
+
# @return [Integer] bytes written
|
|
76
|
+
def write(frame)
|
|
77
|
+
@lock.synchronize do
|
|
78
|
+
socket = @socket or raise ConnectionClosedError, "transport is closed"
|
|
79
|
+
socket.write(frame)
|
|
80
|
+
end
|
|
81
|
+
rescue IOError, SystemCallError => e
|
|
82
|
+
raise ConnectionClosedError, "write failed on #{@uri}: #{e.message}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Closes the socket, which also unblocks a read waiting on it.
|
|
86
|
+
#
|
|
87
|
+
# @return [void]
|
|
88
|
+
def close
|
|
89
|
+
@lock.synchronize do
|
|
90
|
+
@socket&.close
|
|
91
|
+
@socket = nil
|
|
92
|
+
end
|
|
93
|
+
rescue IOError, SystemCallError
|
|
94
|
+
@socket = nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @return [Boolean] whether the socket is gone; every read and write raises after this
|
|
98
|
+
def closed?
|
|
99
|
+
@socket.nil? || @socket.closed?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def normalize(uri)
|
|
105
|
+
parsed = uri.is_a?(URI::Generic) ? uri : URI.parse(coerce_scheme(uri.to_s))
|
|
106
|
+
parsed.port ||= DEFAULT_PORT
|
|
107
|
+
parsed
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def coerce_scheme(value)
|
|
111
|
+
value.include?("://") ? value : "nats://#{value}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
data/lib/tether.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tether/version"
|
|
4
|
+
require_relative "tether/errors"
|
|
5
|
+
require_relative "tether/deadline"
|
|
6
|
+
require_relative "tether/nuid"
|
|
7
|
+
require_relative "tether/headers"
|
|
8
|
+
require_relative "tether/protocol"
|
|
9
|
+
require_relative "tether/protocol/encoder"
|
|
10
|
+
require_relative "tether/protocol/parser"
|
|
11
|
+
require_relative "tether/msg"
|
|
12
|
+
require_relative "tether/transport"
|
|
13
|
+
require_relative "tether/subscription"
|
|
14
|
+
require_relative "tether/dispatcher"
|
|
15
|
+
require_relative "tether/inbox_mux"
|
|
16
|
+
require_relative "tether/client"
|
|
17
|
+
|
|
18
|
+
# A NATS client for Ruby.
|
|
19
|
+
module Tether
|
|
20
|
+
# Connects to a NATS server.
|
|
21
|
+
#
|
|
22
|
+
# @param uri [String, nil] defaults to NATS_URL, then nats://127.0.0.1:4222
|
|
23
|
+
# @param options [Hash{Symbol => Object}] CONNECT overrides plus :connect_timeout
|
|
24
|
+
# @return [Client, Object] the client, or the block's value when a block is given
|
|
25
|
+
def self.connect(uri = nil, **options, &)
|
|
26
|
+
Client.connect(uri, **options, &)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
def self.connect: (?String? uri, **untyped options) ?{ (Client) -> untyped } -> untyped
|
|
3
|
+
|
|
4
|
+
class Client
|
|
5
|
+
DEFAULT_URI: String
|
|
6
|
+
DEFAULT_CONNECT_TIMEOUT: Integer
|
|
7
|
+
DEFAULT_FLUSH_TIMEOUT: Integer
|
|
8
|
+
DEFAULT_REQUEST_TIMEOUT: Integer
|
|
9
|
+
|
|
10
|
+
attr_reader server_info: Hash[String, untyped]?
|
|
11
|
+
attr_reader status: Symbol
|
|
12
|
+
attr_reader last_error: Exception?
|
|
13
|
+
|
|
14
|
+
def self.connect: (?String? uri, **untyped options) ?{ (Client) -> untyped } -> untyped
|
|
15
|
+
|
|
16
|
+
def initialize: (?String? uri, **untyped options) -> void
|
|
17
|
+
def connect: (?timeout: Numeric?) -> self
|
|
18
|
+
def publish: (String subject, ?String? payload, ?reply_to: String?, ?headers: Headers?) -> void
|
|
19
|
+
def subscribe: (String subject, ?queue: String?, ?max_pending: Integer) ?{ (Msg) -> void } -> Subscription
|
|
20
|
+
def unsubscribe: (Subscription subscription, ?Integer? max_msgs) -> void
|
|
21
|
+
def request: (String subject, ?String? payload, ?timeout: Numeric?, ?headers: Headers?) -> Msg
|
|
22
|
+
def new_inbox: () -> String
|
|
23
|
+
def flush: (?timeout: Numeric?) -> bool
|
|
24
|
+
def close: () -> void
|
|
25
|
+
def connected?: () -> bool
|
|
26
|
+
def closed?: () -> bool
|
|
27
|
+
def on_error: () { (Exception) -> void } -> void
|
|
28
|
+
def subscription_count: () -> Integer
|
|
29
|
+
def max_payload: () -> Integer?
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def inbox_mux: () -> InboxMux
|
|
34
|
+
def build_dispatcher: () -> Dispatcher
|
|
35
|
+
def read_server_info: (Deadline deadline) -> void
|
|
36
|
+
def connect_options: () -> Hash[Symbol, untyped]
|
|
37
|
+
def start_reader: () -> Thread
|
|
38
|
+
def read_loop: () -> void
|
|
39
|
+
def next_sid: () -> String
|
|
40
|
+
def check_payload_size!: (String subject, String frame) -> void
|
|
41
|
+
def report_slow_consumer: (Subscription subscription) -> void
|
|
42
|
+
def record_error: (Exception error) -> void
|
|
43
|
+
def ensure_connected!: () -> void
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
class Deadline
|
|
3
|
+
NONE: nil
|
|
4
|
+
|
|
5
|
+
attr_reader at: Float?
|
|
6
|
+
|
|
7
|
+
def self.after: (Numeric? seconds) -> Deadline
|
|
8
|
+
def self.infinite: () -> Deadline
|
|
9
|
+
def self.coerce: (Deadline | Numeric | nil value) -> Deadline
|
|
10
|
+
def self.now: () -> Float
|
|
11
|
+
|
|
12
|
+
def initialize: (Float? at) -> void
|
|
13
|
+
def infinite?: () -> bool
|
|
14
|
+
def remaining: () -> Float?
|
|
15
|
+
def expired?: () -> bool
|
|
16
|
+
def check!: (?String? subject) -> void
|
|
17
|
+
def with_at_most: (Numeric? seconds) -> Deadline
|
|
18
|
+
def earliest: (Deadline other) -> Deadline
|
|
19
|
+
def inspect: () -> String
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
class Dispatcher
|
|
3
|
+
def initialize: (**^(*untyped) -> void handlers) -> void
|
|
4
|
+
def add: (Subscription subscription) -> Subscription
|
|
5
|
+
def remove: (String sid) -> Subscription?
|
|
6
|
+
def []: (String sid) -> Subscription?
|
|
7
|
+
def subscriptions: () -> Array[Subscription]
|
|
8
|
+
def size: () -> Integer
|
|
9
|
+
def clear: () -> void
|
|
10
|
+
|
|
11
|
+
def on_msg: (String subject, String sid, String? reply_to, String? header_bytes, String payload) -> void
|
|
12
|
+
def on_ping: () -> void
|
|
13
|
+
def on_pong: () -> void
|
|
14
|
+
def on_ok: () -> void
|
|
15
|
+
def on_info: (String json) -> void
|
|
16
|
+
def on_error: (String description) -> void
|
|
17
|
+
def on_protocol_error: (String message) -> void
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
class Error < StandardError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
class ProtocolError < Error
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class TimeoutError < Error
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class NoRespondersError < Error
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class MaxPayloadError < Error
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class SlowConsumerError < Error
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class InvalidSubjectError < Error
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class ConnectionError < Error
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class ConnectionClosedError < ConnectionError
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class ConnectionDrainingError < ConnectionError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class ConnectionReconnectingError < ConnectionError
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class NoServersError < ConnectionError
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class ServerError < Error
|
|
39
|
+
MAPPING: Hash[String, Symbol]
|
|
40
|
+
|
|
41
|
+
def self.for: (String? description) -> ServerError
|
|
42
|
+
def self.fatal?: () -> bool
|
|
43
|
+
def fatal?: () -> bool
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class AuthorizationError < ServerError
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class AuthenticationExpiredError < ServerError
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class AuthenticationRevokedError < ServerError
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class AuthenticationTimeoutError < ServerError
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class StaleConnectionError < ServerError
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class MaxConnectionsError < ServerError
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class MaxAccountConnectionsError < ServerError
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class MaxPayloadViolationError < ServerError
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class MaxControlLineError < ServerError
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class NoRespondersRequiresHeadersError < ServerError
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class ConnectionThrottledError < ServerError
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class TLSRequiredError < ServerError
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class InvalidClientProtocolError < ServerError
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class UnknownProtocolError < ServerError
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class WrongPortError < ServerError
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class ServerInvalidSubjectError < ServerError
|
|
92
|
+
def self.fatal?: () -> bool
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class InvalidPublishSubjectError < ServerError
|
|
96
|
+
def self.fatal?: () -> bool
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class FailedAccountRegistrationError < ServerError
|
|
100
|
+
def self.fatal?: () -> bool
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
class MaxSubscriptionsError < ServerError
|
|
104
|
+
def self.fatal?: () -> bool
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class PermissionsViolationError < ServerError
|
|
108
|
+
def self.fatal?: () -> bool
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
class Headers
|
|
3
|
+
type entries = Hash[String, String | Array[String]]
|
|
4
|
+
|
|
5
|
+
VERSION: String
|
|
6
|
+
CONTROL: Integer
|
|
7
|
+
NO_CONTENT: Integer
|
|
8
|
+
BAD_REQUEST: Integer
|
|
9
|
+
NOT_FOUND: Integer
|
|
10
|
+
REQUEST_TIMEOUT: Integer
|
|
11
|
+
CONFLICT: Integer
|
|
12
|
+
REQUIRED_API_LEVEL: Integer
|
|
13
|
+
TOO_MANY_RESULTS: Integer
|
|
14
|
+
PIN_ID_MISMATCH: Integer
|
|
15
|
+
TOO_MANY_REQUESTS: Integer
|
|
16
|
+
INTERNAL_ERROR: Integer
|
|
17
|
+
NO_RESPONDERS: Integer
|
|
18
|
+
|
|
19
|
+
attr_reader status: Integer?
|
|
20
|
+
attr_reader description: String?
|
|
21
|
+
|
|
22
|
+
def self.parse: (String? bytes) -> Headers
|
|
23
|
+
def self.[]: (entries) -> Headers
|
|
24
|
+
|
|
25
|
+
def initialize: (?entries: entries, ?status: Integer?, ?description: String?) -> void
|
|
26
|
+
def []: (String key) -> String?
|
|
27
|
+
def []=: (String key, String value) -> String
|
|
28
|
+
def add: (String key, String value) -> self
|
|
29
|
+
def values: (String key) -> Array[String]
|
|
30
|
+
def delete: (String key) -> Array[String]?
|
|
31
|
+
def key?: (String key) -> bool
|
|
32
|
+
def each: () { (String, String) -> void } -> self
|
|
33
|
+
| () -> Enumerator[[String, String], self]
|
|
34
|
+
def keys: () -> Array[String]
|
|
35
|
+
def empty?: () -> bool
|
|
36
|
+
def no_responders?: () -> bool
|
|
37
|
+
def control?: () -> bool
|
|
38
|
+
def idle_heartbeat?: () -> bool
|
|
39
|
+
def flow_control?: () -> bool
|
|
40
|
+
def to_h: () -> Hash[String, Array[String]]
|
|
41
|
+
def to_wire: () -> String
|
|
42
|
+
def inspect: () -> String
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def validate_key: (untyped key) -> String
|
|
47
|
+
def validate_value: (untyped value) -> String
|
|
48
|
+
def status_suffix: () -> String
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
class InboxMux
|
|
3
|
+
INBOX_PREFIX: String
|
|
4
|
+
|
|
5
|
+
attr_reader prefix: String
|
|
6
|
+
|
|
7
|
+
def initialize: (Client client, ?prefix: String?, ?max_pending: Integer) -> void
|
|
8
|
+
def request: (String subject, ?String? payload, ?deadline: Deadline, ?headers: Headers?) -> Msg
|
|
9
|
+
def start: () -> Subscription
|
|
10
|
+
def started?: () -> bool
|
|
11
|
+
def pending: () -> Integer
|
|
12
|
+
def close: () -> void
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def await: (Thread::Queue waiter, String subject, Deadline deadline) -> Msg
|
|
17
|
+
def resolve: (Msg msg) -> void
|
|
18
|
+
def next_token: () -> String
|
|
19
|
+
end
|
|
20
|
+
end
|
data/sig/tether/msg.rbs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
class Msg
|
|
3
|
+
attr_reader subject: String
|
|
4
|
+
attr_reader data: String
|
|
5
|
+
attr_reader reply_to: String?
|
|
6
|
+
attr_reader subscription: Subscription?
|
|
7
|
+
|
|
8
|
+
def initialize: (subject: String, data: String, ?reply_to: String?, ?header_bytes: String?, ?subscription: Subscription?) -> void
|
|
9
|
+
def headers: () -> Headers?
|
|
10
|
+
def status: () -> Integer?
|
|
11
|
+
def no_responders?: () -> bool
|
|
12
|
+
def respond: (?String? payload, ?headers: Headers?) -> void
|
|
13
|
+
def empty?: () -> bool
|
|
14
|
+
def inspect: () -> String
|
|
15
|
+
end
|
|
16
|
+
end
|
data/sig/tether/nuid.rbs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
class NUID
|
|
3
|
+
DIGITS: String
|
|
4
|
+
BASE: Integer
|
|
5
|
+
PREFIX_LENGTH: Integer
|
|
6
|
+
SEQUENCE_LENGTH: Integer
|
|
7
|
+
TOTAL_LENGTH: Integer
|
|
8
|
+
MAX_SEQUENCE: Integer
|
|
9
|
+
MIN_INCREMENT: Integer
|
|
10
|
+
MAX_INCREMENT: Integer
|
|
11
|
+
|
|
12
|
+
def self.next: () -> String
|
|
13
|
+
|
|
14
|
+
def initialize: () -> void
|
|
15
|
+
def next: () -> String
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def reseed!: () -> void
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
module Protocol
|
|
3
|
+
module Encoder
|
|
4
|
+
def self.pub: (String subject, ?String? payload, ?String? reply_to) -> String
|
|
5
|
+
def self.hpub: (String subject, String? payload, Headers | String headers, ?String? reply_to) -> String
|
|
6
|
+
def self.sub: (String subject, Integer | String sid, ?queue: String?) -> String
|
|
7
|
+
def self.unsub: (Integer | String sid, ?Integer? max_msgs) -> String
|
|
8
|
+
DEFAULT_CONNECT: Hash[Symbol, untyped]
|
|
9
|
+
|
|
10
|
+
def self.connect: (?Hash[Symbol | String, untyped] options) -> String
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
module Protocol
|
|
3
|
+
class Parser
|
|
4
|
+
COMPACT_THRESHOLD: Integer
|
|
5
|
+
SNIPPET_LIMIT: Integer
|
|
6
|
+
AWAITING_CONTROL_LINE: Symbol
|
|
7
|
+
AWAITING_PAYLOAD: Symbol
|
|
8
|
+
|
|
9
|
+
attr_reader state: Symbol
|
|
10
|
+
attr_accessor max_payload: Integer?
|
|
11
|
+
|
|
12
|
+
def initialize: (_ParserSink sink, ?max_control_line: Integer, ?max_payload: Integer?) -> void
|
|
13
|
+
def <<: (String? data) -> self
|
|
14
|
+
alias parse <<
|
|
15
|
+
def reset!: () -> void
|
|
16
|
+
def broken?: () -> bool
|
|
17
|
+
def pending_bytes: () -> Integer
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def run: () -> void
|
|
22
|
+
def consume_control_line: () -> bool
|
|
23
|
+
def match_message_line: () -> bool?
|
|
24
|
+
def start_payload: (MatchData match, bool headers) -> bool
|
|
25
|
+
def dispatch: (Integer start, Integer line_end) -> bool
|
|
26
|
+
def dispatch_ping_pong: (Integer start, Integer line_end) -> bool
|
|
27
|
+
def dispatch_ok: (Integer start, Integer line_end) -> bool
|
|
28
|
+
def dispatch_err: (Integer start, Integer line_end) -> bool
|
|
29
|
+
def dispatch_info: (Integer start, Integer line_end) -> bool
|
|
30
|
+
def consume_payload: () -> bool
|
|
31
|
+
def payload_terminated?: () -> bool
|
|
32
|
+
def emit_message: () -> void
|
|
33
|
+
def verb?: (Integer start, Integer limit, String verb) -> bool
|
|
34
|
+
def trimmed: (Integer from, Integer to) -> String
|
|
35
|
+
def snippet: (Integer start, Integer line_end) -> String
|
|
36
|
+
def clear_frame: () -> void
|
|
37
|
+
def compact: () -> void
|
|
38
|
+
def malformed: (Integer start, Integer line_end) -> bool
|
|
39
|
+
def unknown_operation: (Integer start, Integer line_end) -> bool
|
|
40
|
+
def fail_protocol!: (String message) -> bool
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
module Protocol
|
|
3
|
+
CRLF: String
|
|
4
|
+
CRLF_SIZE: Integer
|
|
5
|
+
EMPTY_PAYLOAD: String
|
|
6
|
+
PING: String
|
|
7
|
+
PONG: String
|
|
8
|
+
DEFAULT_MAX_CONTROL_LINE: Integer
|
|
9
|
+
BINARY: Encoding
|
|
10
|
+
|
|
11
|
+
# The contract {Parser} delivers frames to. Implemented by {Dispatcher}.
|
|
12
|
+
interface _ParserSink
|
|
13
|
+
def on_msg: (String subject, String sid, String? reply_to, String? headers, String payload) -> void
|
|
14
|
+
def on_ping: () -> void
|
|
15
|
+
def on_pong: () -> void
|
|
16
|
+
def on_ok: () -> void
|
|
17
|
+
def on_info: (String json) -> void
|
|
18
|
+
def on_error: (String description) -> void
|
|
19
|
+
def on_protocol_error: (String message) -> void
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
class Subscription
|
|
3
|
+
DEFAULT_MAX_PENDING: Integer
|
|
4
|
+
|
|
5
|
+
attr_reader sid: String
|
|
6
|
+
attr_reader subject: String
|
|
7
|
+
attr_reader queue_group: String?
|
|
8
|
+
attr_reader delivered: Integer
|
|
9
|
+
attr_reader dropped: Integer
|
|
10
|
+
attr_reader responder: Client?
|
|
11
|
+
|
|
12
|
+
def initialize: (sid: String, subject: String, ?queue_group: String?, ?max_pending: Integer, ?on_slow_consumer: (^(Subscription) -> void)?, ?responder: Client?) ?{ (Msg) -> void } -> void
|
|
13
|
+
def deliver: (Msg msg) -> bool
|
|
14
|
+
def next_message: (?Deadline deadline) -> Msg
|
|
15
|
+
def pending: () -> Integer
|
|
16
|
+
def closed?: () -> bool
|
|
17
|
+
def close: () -> void
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def start_worker: () -> Thread
|
|
22
|
+
def invoke: (Msg msg) -> void
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Tether
|
|
2
|
+
class Transport
|
|
3
|
+
DEFAULT_PORT: Integer
|
|
4
|
+
READ_SIZE: Integer
|
|
5
|
+
|
|
6
|
+
attr_reader uri: URI::Generic
|
|
7
|
+
|
|
8
|
+
def self.connect: (String | URI::Generic uri, ?deadline: Deadline) -> Transport
|
|
9
|
+
|
|
10
|
+
def initialize: (String | URI::Generic uri) -> void
|
|
11
|
+
def open: (?Deadline deadline) -> self
|
|
12
|
+
def read: (?Deadline deadline) -> String?
|
|
13
|
+
def write: (String frame) -> Integer
|
|
14
|
+
def close: () -> void
|
|
15
|
+
def closed?: () -> bool
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def normalize: (String | URI::Generic uri) -> URI::Generic
|
|
20
|
+
def coerce_scheme: (String value) -> String
|
|
21
|
+
end
|
|
22
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tether
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dmitry Vorotilin
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: A NATS and JetStream client for Ruby, built around explicit deadlines
|
|
13
|
+
and per-subscription dispatch so concurrent use of one connection is safe by design.
|
|
14
|
+
email:
|
|
15
|
+
- d.vorotilin@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- LICENSE.txt
|
|
21
|
+
- README.md
|
|
22
|
+
- Rakefile
|
|
23
|
+
- lib/tether.rb
|
|
24
|
+
- lib/tether/client.rb
|
|
25
|
+
- lib/tether/deadline.rb
|
|
26
|
+
- lib/tether/dispatcher.rb
|
|
27
|
+
- lib/tether/errors.rb
|
|
28
|
+
- lib/tether/headers.rb
|
|
29
|
+
- lib/tether/inbox_mux.rb
|
|
30
|
+
- lib/tether/msg.rb
|
|
31
|
+
- lib/tether/nuid.rb
|
|
32
|
+
- lib/tether/protocol.rb
|
|
33
|
+
- lib/tether/protocol/encoder.rb
|
|
34
|
+
- lib/tether/protocol/parser.rb
|
|
35
|
+
- lib/tether/subscription.rb
|
|
36
|
+
- lib/tether/transport.rb
|
|
37
|
+
- lib/tether/version.rb
|
|
38
|
+
- sig/tether/client.rbs
|
|
39
|
+
- sig/tether/deadline.rbs
|
|
40
|
+
- sig/tether/dispatcher.rbs
|
|
41
|
+
- sig/tether/errors.rbs
|
|
42
|
+
- sig/tether/headers.rbs
|
|
43
|
+
- sig/tether/inbox_mux.rbs
|
|
44
|
+
- sig/tether/msg.rbs
|
|
45
|
+
- sig/tether/nuid.rbs
|
|
46
|
+
- sig/tether/protocol.rbs
|
|
47
|
+
- sig/tether/protocol/encoder.rbs
|
|
48
|
+
- sig/tether/protocol/parser.rbs
|
|
49
|
+
- sig/tether/subscription.rbs
|
|
50
|
+
- sig/tether/transport.rbs
|
|
51
|
+
- sig/tether/version.rbs
|
|
52
|
+
homepage: https://github.com/bitsbeam/tether
|
|
53
|
+
licenses:
|
|
54
|
+
- MIT
|
|
55
|
+
metadata:
|
|
56
|
+
homepage_uri: https://github.com/bitsbeam/tether
|
|
57
|
+
source_code_uri: https://github.com/bitsbeam/tether
|
|
58
|
+
changelog_uri: https://github.com/bitsbeam/tether/blob/main/CHANGELOG.md
|
|
59
|
+
rubygems_mfa_required: 'true'
|
|
60
|
+
rdoc_options: []
|
|
61
|
+
require_paths:
|
|
62
|
+
- lib
|
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 3.2.0
|
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
requirements: []
|
|
74
|
+
rubygems_version: 4.0.12
|
|
75
|
+
specification_version: 4
|
|
76
|
+
summary: A NATS client for Ruby
|
|
77
|
+
test_files: []
|