skyfall 0.2.0 → 0.2.1

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: ce06b8a7ef3783cd255621d6868a737735b782999a8cab76b70cf1f869e2cd1d
4
- data.tar.gz: 9edea42903a17cb83ee717ea92c81e9a853b530a24f0bc803f70835b3bed1ba2
3
+ metadata.gz: 9a508caa504715c30f9d9ff68dbf419c8b88ad00e5fbc6315ae6a3abb624e167
4
+ data.tar.gz: 9010ece05b7f1cce0a2573ecfa75965d1dba2b77cd7926eaa8fc510f8e2ae7e2
5
5
  SHA512:
6
- metadata.gz: 1b04bb91b734fe84d984af3c6a8c78959230d5ef865109882d86c5f0b2537f0a8b25678dcace7f5d8c26e560f871edba4de07f38273e2e7cf17088fd125a08e2
7
- data.tar.gz: 5dffcf8a308789995d16293a9b8caba848374ab80c147a0ef87f440cb2c98887b4d81f3eb447d74c2bfc608ae448b54d9ada90556eb6e55cf59cc1df37e15390
6
+ metadata.gz: b9fc0370411496e76a4caaa7b2795e999b2fbf3445e21a02e3b9135e4c7fc5e3766d7ea3a94e145becf3ab96fd4d494b0f7d8a04a09eee03698a97125d44238d
7
+ data.tar.gz: b302a5f7eefc30509a6b721809105bd9439f6919b31ab73878d132fa8a67062d0d77c17d375d3d433cb77bbba63127a823d1817f5d1a71f16c2b6601dca7d493
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [0.2.1] - 2023-08-19
2
+
3
+ - optimized `WebsocketMessage` parsing performance - lazy parsing of most properties (message decoding should be over 50% faster on average)
4
+ - added separate subclasses of `WebsocketMessage` for different message types
5
+ - added support for `#handle`, `#info` and `#tombstone` message types
6
+ - `UnknownMessage` is returned for unrecognized message types
7
+
1
8
  ## [0.2.0] - 2023-07-24
2
9
 
3
10
  - switched the websocket library from `websocket-client-simple` to `faye-websocket`, which should make event parsing up to ~30× faster (!)
@@ -31,6 +31,9 @@ module Skyfall
31
31
  read_section(buffer) until buffer.eof?
32
32
  end
33
33
 
34
+ def section_with_cid(cid)
35
+ @sections.detect { |s| s.cid == cid }&.body
36
+ end
34
37
 
35
38
  private
36
39
 
@@ -0,0 +1,23 @@
1
+ require_relative '../car_archive'
2
+ require_relative '../cid'
3
+ require_relative '../operation'
4
+
5
+ module Skyfall
6
+ class CommitMessage < WebsocketMessage
7
+ def commit
8
+ @commit ||= @data_object['commit'] && CID.from_cbor_tag(@data_object['commit'])
9
+ end
10
+
11
+ def prev
12
+ @prev ||= @data_object['prev'] && CID.from_cbor_tag(@data_object['prev'])
13
+ end
14
+
15
+ def blocks
16
+ @blocks ||= CarArchive.new(@data_object['blocks'])
17
+ end
18
+
19
+ def operations
20
+ @operations ||= @data_object['ops'].map { |op| Operation.new(self, op) }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ module Skyfall
2
+ class HandleMessage < WebsocketMessage
3
+ def handle
4
+ @data_object['handle']
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ module Skyfall
2
+ class InfoMessage < WebsocketMessage
3
+ attr_reader :name, :message
4
+
5
+ OUTDATED_CURSOR = "OutdatedCursor"
6
+
7
+ def initialize(type_object, data_object)
8
+ super
9
+
10
+ @name = @data_object['name']
11
+ @message = @data_object['message']
12
+ end
13
+
14
+ def to_s
15
+ (@name || "InfoMessage") + (@message ? ": #{@message}" : "")
16
+ end
17
+
18
+ def inspectable_variables
19
+ super - [:@did, :@seq]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ module Skyfall
2
+ class TombstoneMessage < WebsocketMessage
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Skyfall
2
+ class UnknownMessage < WebsocketMessage
3
+ end
4
+ end
@@ -0,0 +1,90 @@
1
+ require_relative '../errors'
2
+ require_relative '../extensions'
3
+
4
+ require 'cbor'
5
+ require 'time'
6
+
7
+ module Skyfall
8
+ class WebsocketMessage
9
+ using Skyfall::Extensions
10
+
11
+ require_relative 'commit_message'
12
+ require_relative 'handle_message'
13
+ require_relative 'info_message'
14
+ require_relative 'tombstone_message'
15
+ require_relative 'unknown_message'
16
+
17
+ attr_reader :type_object, :data_object
18
+ attr_reader :type, :did, :seq
19
+
20
+ alias repo did
21
+
22
+ def self.new(data)
23
+ type_object, data_object = decode_cbor_objects(data)
24
+
25
+ message_class = case type_object['t']
26
+ when '#commit' then CommitMessage
27
+ when '#handle' then HandleMessage
28
+ when '#info' then InfoMessage
29
+ when '#tombstone' then TombstoneMessage
30
+ else UnknownMessage
31
+ end
32
+
33
+ message = message_class.allocate
34
+ message.send(:initialize, type_object, data_object)
35
+ message
36
+ end
37
+
38
+ def initialize(type_object, data_object)
39
+ @type_object = type_object
40
+ @data_object = data_object
41
+
42
+ @type = @type_object['t'][1..-1].to_sym
43
+ @did = @data_object['repo'] || @data_object['did']
44
+ @seq = @data_object['seq']
45
+ end
46
+
47
+ def operations
48
+ []
49
+ end
50
+
51
+ def time
52
+ @time ||= @data_object['time'] && Time.parse(@data_object['time'])
53
+ end
54
+
55
+ def inspectable_variables
56
+ instance_variables - [:@type_object, :@data_object, :@blocks]
57
+ end
58
+
59
+ def inspect
60
+ vars = inspectable_variables.map { |v| "#{v}=#{instance_variable_get(v).inspect}" }.join(", ")
61
+ "#<#{self.class}:0x#{object_id} #{vars}>"
62
+ end
63
+
64
+ private
65
+
66
+ def self.decode_cbor_objects(data)
67
+ objects = CBOR.decode_sequence(data)
68
+
69
+ if objects.length < 2
70
+ raise DecodeError.new("Malformed message: #{objects.inspect}")
71
+ elsif objects.length > 2
72
+ raise DecodeError.new("Invalid number of objects: #{objects.length}")
73
+ end
74
+
75
+ type, data = objects
76
+
77
+ if data['error']
78
+ raise SubscriptionError.new(data['error'], data['message'])
79
+ end
80
+
81
+ raise DecodeError.new("Invalid object type: #{type}") unless type.is_a?(Hash)
82
+ raise UnsupportedError.new("Unexpected CBOR object: #{type}") unless type['op'] == 1
83
+ raise DecodeError.new("Missing data: #{type} #{objects.inspect}") unless type['op'] && type['t']
84
+ raise DecodeError.new("Invalid message type: #{type['t']}") unless type['t'].start_with?('#')
85
+ raise DecodeError.new("Invalid object type: #{data}") unless data.is_a?(Hash)
86
+
87
+ [type, data]
88
+ end
89
+ end
90
+ end
@@ -2,30 +2,41 @@ require_relative 'collection'
2
2
 
3
3
  module Skyfall
4
4
  class Operation
5
- attr_reader :repo, :path, :action, :cid
5
+ def initialize(message, json)
6
+ @message = message
7
+ @json = json
8
+ end
6
9
 
7
- def initialize(repo, path, action, cid, record)
8
- @repo = repo
9
- @path = path
10
- @action = action.to_sym
11
- @cid = cid
12
- @record = record
10
+ def repo
11
+ @message.repo
13
12
  end
14
13
 
15
- def raw_record
16
- @record
14
+ def path
15
+ @json['path']
17
16
  end
18
17
 
19
- def uri
20
- "at://#{repo}/#{path}"
18
+ def action
19
+ @json['action'].to_sym
21
20
  end
22
21
 
23
22
  def collection
24
- path.split('/')[0]
23
+ @json['path'].split('/')[0]
25
24
  end
26
25
 
27
26
  def rkey
28
- path.split('/')[1]
27
+ @json['path'].split('/')[1]
28
+ end
29
+
30
+ def uri
31
+ "at://#{repo}/#{path}"
32
+ end
33
+
34
+ def cid
35
+ @cid ||= @json['cid'] && CID.from_cbor_tag(@json['cid'])
36
+ end
37
+
38
+ def raw_record
39
+ @raw_record ||= cid && @message.blocks.section_with_cid(cid)
29
40
  end
30
41
 
31
42
  def type
@@ -1,4 +1,4 @@
1
- require_relative 'websocket_message'
1
+ require_relative 'messages/websocket_message'
2
2
 
3
3
  require 'eventmachine'
4
4
  require 'faye/websocket'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Skyfall
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/skyfall.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'skyfall/stream'
4
- require_relative 'skyfall/websocket_message'
4
+ require_relative 'skyfall/messages/websocket_message'
5
5
  require_relative 'skyfall/version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skyfall
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kuba Suder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-24 00:00:00.000000000 Z
11
+ date: 2023-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base32
@@ -86,10 +86,15 @@ files:
86
86
  - lib/skyfall/collection.rb
87
87
  - lib/skyfall/errors.rb
88
88
  - lib/skyfall/extensions.rb
89
+ - lib/skyfall/messages/commit_message.rb
90
+ - lib/skyfall/messages/handle_message.rb
91
+ - lib/skyfall/messages/info_message.rb
92
+ - lib/skyfall/messages/tombstone_message.rb
93
+ - lib/skyfall/messages/unknown_message.rb
94
+ - lib/skyfall/messages/websocket_message.rb
89
95
  - lib/skyfall/operation.rb
90
96
  - lib/skyfall/stream.rb
91
97
  - lib/skyfall/version.rb
92
- - lib/skyfall/websocket_message.rb
93
98
  - sig/skyfall.rbs
94
99
  homepage: https://github.com/mackuba/skyfall
95
100
  licenses:
@@ -1,77 +0,0 @@
1
- require_relative 'car_archive'
2
- require_relative 'cid'
3
- require_relative 'errors'
4
- require_relative 'extensions'
5
- require_relative 'operation'
6
-
7
- require 'cbor'
8
- require 'time'
9
-
10
- module Skyfall
11
- class WebsocketMessage
12
- using Skyfall::Extensions
13
-
14
- attr_reader :type_object, :data_object
15
- attr_reader :type, :repo, :time, :seq, :commit, :prev, :blocks, :operations
16
-
17
- def initialize(data)
18
- @type_object, @data_object = decode_cbor_objects(data)
19
-
20
- @type = @type_object['t'][1..-1].to_sym
21
- @operations = []
22
-
23
- @repo = @data_object['repo']
24
- @time = Time.parse(@data_object['time'])
25
- @seq = @data_object['seq']
26
-
27
- return unless @type == :commit
28
-
29
- @commit = @data_object['commit'] && CID.from_cbor_tag(@data_object['commit'])
30
- @prev = @data_object['prev'] && CID.from_cbor_tag(@data_object['prev'])
31
-
32
- @blocks = CarArchive.new(@data_object['blocks'])
33
-
34
- @operations = @data_object['ops'].map { |op|
35
- cid = op['cid'] && CID.from_cbor_tag(op['cid'])
36
- path = op['path']
37
- action = op['action']
38
- record = cid && @blocks.sections.detect { |s| s.cid == cid }.body
39
-
40
- Operation.new(@repo, path, action, cid, record)
41
- }
42
- end
43
-
44
- def inspect
45
- keys = instance_variables - [:@type_object, :@data_object, :@blocks]
46
- vars = keys.map { |v| "#{v}=#{instance_variable_get(v).inspect}" }.join(", ")
47
- "#<#{self.class}:0x#{object_id} #{vars}>"
48
- end
49
-
50
- private
51
-
52
- def decode_cbor_objects(data)
53
- objects = CBOR.decode_sequence(data)
54
-
55
- if objects.length < 2
56
- raise DecodeError.new("Malformed message: #{objects.inspect}")
57
- elsif objects.length > 2
58
- raise DecodeError.new("Invalid number of objects: #{objects.length}")
59
- end
60
-
61
- type_object, data_object = objects
62
-
63
- if data_object['error']
64
- raise SubscriptionError.new(data_object['error'], data_object['message'])
65
- end
66
-
67
- raise DecodeError.new("Invalid object type: #{type_object}") unless type_object.is_a?(Hash)
68
- raise UnsupportedError.new("Unexpected CBOR object: #{type_object}") unless type_object['op'] == 1
69
- raise DecodeError.new("Missing data: #{type_object} #{objects.inspect}") unless type_object['op'] && type_object['t']
70
- raise DecodeError.new("Invalid message type: #{type_object['t']}") unless type_object['t'].start_with?('#')
71
-
72
- raise DecodeError.new("Invalid object type: #{data_object}") unless data_object.is_a?(Hash)
73
-
74
- [type_object, data_object]
75
- end
76
- end
77
- end