async-matrix 1.2.1 → 2.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/async/discord/api/path_tree.rb +2 -5
  3. data/lib/async/discord/api.rb +1 -6
  4. data/lib/async/discord/client.rb +1 -4
  5. data/lib/async/discord/error.rb +1 -5
  6. data/lib/async/discord/gateway.rb +1 -4
  7. data/lib/async/discord.rb +0 -1
  8. data/lib/async/matrix/api/chain.rb +1 -4
  9. data/lib/async/matrix/api/concat.rb +1 -5
  10. data/lib/async/matrix/api/path_tree.rb +1 -4
  11. data/lib/async/matrix/api.rb +1 -5
  12. data/lib/async/matrix/application_service/bot.rb +4 -7
  13. data/lib/async/matrix/application_service/config/vivify.rb +1 -5
  14. data/lib/async/matrix/application_service/config.rb +1 -4
  15. data/lib/async/matrix/application_service/dispatcher.rb +65 -9
  16. data/lib/async/matrix/application_service/error_response.rb +1 -5
  17. data/lib/async/matrix/application_service/event.rb +1 -5
  18. data/lib/async/matrix/application_service/server.rb +339 -211
  19. data/lib/async/matrix/application_service/transaction.rb +1 -5
  20. data/lib/async/matrix/application_service/transaction_handler.rb +185 -0
  21. data/lib/async/matrix/application_service/transaction_store.rb +1 -5
  22. data/lib/async/matrix/bridge/discord/db/connection.rb +1 -3
  23. data/lib/async/matrix/bridge/discord/db/file.rb +2 -4
  24. data/lib/async/matrix/bridge/discord/db/guild.rb +2 -4
  25. data/lib/async/matrix/bridge/discord/db/message.rb +2 -4
  26. data/lib/async/matrix/bridge/discord/db/portal.rb +2 -4
  27. data/lib/async/matrix/bridge/discord/db/puppet.rb +2 -4
  28. data/lib/async/matrix/bridge/discord/db/reaction.rb +2 -4
  29. data/lib/async/matrix/bridge/discord/db/schema.rb +18 -0
  30. data/lib/async/matrix/bridge/discord/db/user.rb +2 -4
  31. data/lib/async/matrix/bridge/discord/db.rb +14 -16
  32. data/lib/async/matrix/client.rb +1 -4
  33. data/lib/async/matrix/connection.rb +1 -4
  34. data/lib/async/matrix/double_puppet_client.rb +2 -4
  35. data/lib/async/matrix/e2ee.rb +1 -2
  36. data/lib/async/matrix/endpoint.rb +1 -4
  37. data/lib/async/matrix/error.rb +1 -4
  38. data/lib/async/matrix/media_client.rb +1 -4
  39. data/lib/async/matrix/notifier.rb +1 -4
  40. data/lib/async/matrix/schema/registry.rb +1 -2
  41. data/lib/async/matrix/schema/validation_error.rb +1 -2
  42. data/lib/async/matrix/schema.rb +1 -5
  43. data/lib/async/matrix/server.rb +1 -4
  44. data/lib/async/matrix/stream.rb +1 -4
  45. data/lib/async/matrix/version.rb +1 -1
  46. data/lib/async/matrix.rb +4 -1
  47. metadata +23 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a0c3eee197effa06e2abe173a328734d5c0e2db5c033c26a24030813d7ad69b
4
- data.tar.gz: 597a3fdae49b1fdf17584bdb181067c27f3fc6e2877e56ec4607f60bff242c06
3
+ metadata.gz: 2b22a524df6b1dabf1a9b39a5d483f42185ffa33dea24ec714da460dee7d4350
4
+ data.tar.gz: 79307bffc1d8461fc9e31c8395545d5ec43181b9298f21c3a5e23179af1a66ee
5
5
  SHA512:
6
- metadata.gz: d596428929e71d223463ac9bd28f78941cd9ced8e6dde5ccb9450f9c5fd7e0e527ae2953c5592a6d3ffa93b7a5557f74935a58fddee6de687d466d7540c7b426
7
- data.tar.gz: 87faa2828aba3103c88feb145a0b53bb1bbfb2f05adaa795e05f7020c6cc15aa1bc81b51a260e5485b986eb2d4196b18d82e954309cdbbae2b56a99a73c1bd12
6
+ metadata.gz: fae7b428ac3c1f896dc01684710dd41f115551ec0bdc31719110ca0be0a352e9ce6a656b69f3805db1a5949eb1c675e1d1c2b8703ffdbedd295aea79b95d21c0
7
+ data.tar.gz: 959d7af1a0c7b2adab25f608e8834ff8493b7055a4bf9f4a0ba8673c6e5ab62f0320d78a6d559aa6e71c62157fae32b9db23bb18242fdebc42599925922cdbf2
@@ -3,12 +3,10 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
6
  require "json"
8
7
  require "pathname"
9
8
  require "uri"
10
- require "async/discord"
11
- require "async/matrix"
9
+ require_relative "../../matrix/api/path_tree"
12
10
 
13
11
  module Async
14
12
  module Discord
@@ -75,7 +73,7 @@ module Async
75
73
  end
76
74
  end
77
75
 
78
- test do
76
+ __END__
79
77
  describe "Async::Discord::Api::PathTree" do
80
78
  it "loads from the Discord OpenAPI spec" do
81
79
  tree = Async::Discord::Api::PathTree.load
@@ -127,4 +125,3 @@ test do
127
125
  result[:operation_id].should == "testAction"
128
126
  end
129
127
  end
130
- end
@@ -3,10 +3,6 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
- require "async/discord"
8
- require "async/matrix"
9
-
10
6
  module Async
11
7
  module Discord
12
8
  # Runtime-generated Discord HTTP API built from the official OpenAPI spec.
@@ -72,7 +68,7 @@ module Async
72
68
  end
73
69
  end
74
70
 
75
- test do
71
+ __END__
76
72
  describe "Async::Discord::Api::Gateway" do
77
73
  # Stub client that records calls instead of making HTTP requests.
78
74
  StubDiscordClient = Struct.new(:calls) do
@@ -153,4 +149,3 @@ test do
153
149
  lambda { gw.totally.bogus.endpoint.get }.should.raise Async::Matrix::InvalidEndpointError
154
150
  end
155
151
  end
156
- end
@@ -3,9 +3,7 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
6
  require "async/http/internet"
8
- require "async/discord"
9
7
  require "json"
10
8
  require "erb"
11
9
  require "console"
@@ -223,7 +221,7 @@ module Async
223
221
  end
224
222
  end
225
223
 
226
- test do
224
+ __END__
227
225
  describe "Async::Discord::Client" do
228
226
  it "sets authorization header with Bot prefix" do
229
227
  client = Async::Discord::Client.new(token: "test_token_123")
@@ -283,4 +281,3 @@ test do
283
281
  lambda { client.close }.should.not.raise
284
282
  end
285
283
  end
286
- end
@@ -3,9 +3,6 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
- require "async/discord"
8
-
9
6
  module Async
10
7
  module Discord
11
8
  # Base error for all Discord API errors.
@@ -39,7 +36,7 @@ module Async
39
36
  end
40
37
  end
41
38
 
42
- test do
39
+ __END__
43
40
  describe "Async::Discord::Error" do
44
41
  it "stores code and message" do
45
42
  err = Async::Discord::Error.new("DISCORD_ERROR", "something broke")
@@ -85,4 +82,3 @@ test do
85
82
  it "GatewayError inherits from Error" do
86
83
  Async::Discord::GatewayError.new("GW", "disconnected").should.be.kind_of Async::Discord::Error
87
84
  end
88
- end
@@ -3,13 +3,11 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
6
  require "async"
8
7
  require "async/http/endpoint"
9
8
  require "async/websocket/client"
10
9
  require "json"
11
10
  require "console"
12
- require "async/discord"
13
11
 
14
12
  module Async
15
13
  module Discord
@@ -269,7 +267,7 @@ module Async
269
267
  end
270
268
  end
271
269
 
272
- test do
270
+ __END__
273
271
  describe "Async::Discord::Gateway" do
274
272
  it "initializes with token and intents" do
275
273
  gw = Async::Discord::Gateway.new(token: "test_token", intents: 0x30001)
@@ -359,4 +357,3 @@ test do
359
357
  gw.instance_variable_get(:@gateway_url).should == "wss://custom.gateway.example.com"
360
358
  end
361
359
  end
362
- end
data/lib/async/discord.rb CHANGED
@@ -4,7 +4,6 @@
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
6
  require "async/http"
7
- require "scampi"
8
7
 
9
8
  module Async
10
9
  module Discord
@@ -3,8 +3,6 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
- require "async/matrix"
8
6
  require "string_builder"
9
7
  require "erb"
10
8
 
@@ -231,7 +229,7 @@ module Async
231
229
  end
232
230
  end
233
231
 
234
- test do
232
+ __END__
235
233
  describe "Async::Matrix::Api::Chain" do
236
234
  def make_tree
237
235
  tree = Async::Matrix::Api::PathTree.new
@@ -578,4 +576,3 @@ test do
578
576
  client.calls.last.last[:max_retries].should.be.nil
579
577
  end
580
578
  end
581
- end
@@ -3,9 +3,6 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
- require "async/matrix"
8
-
9
6
  module Async
10
7
  module Matrix
11
8
  module Api
@@ -63,7 +60,7 @@ module Async
63
60
  end
64
61
  end
65
62
 
66
- test do
63
+ __END__
67
64
  require "string_builder"
68
65
 
69
66
  describe "Async::Matrix::Api::Concat" do
@@ -102,4 +99,3 @@ test do
102
99
  segments.should == %w[account whoami]
103
100
  end
104
101
  end
105
- end
@@ -3,8 +3,6 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
- require "async/matrix"
8
6
  require "yaml"
9
7
  require "pathname"
10
8
 
@@ -130,7 +128,7 @@ module Async
130
128
  end
131
129
  end
132
130
 
133
- test do
131
+ __END__
134
132
  describe "Async::Matrix::Api::PathTree" do
135
133
  def make_tree
136
134
  tree = Async::Matrix::Api::PathTree.new
@@ -205,4 +203,3 @@ test do
205
203
  result[:valid].should == false
206
204
  end
207
205
  end
208
- end
@@ -3,9 +3,6 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
- require "async/matrix"
8
-
9
6
  module Async
10
7
  module Matrix
11
8
  # Runtime-generated Matrix Client-Server API built from official OpenAPI schemas.
@@ -74,7 +71,7 @@ module Async
74
71
  end
75
72
  end
76
73
 
77
- test do
74
+ __END__
78
75
  describe "Async::Matrix::Api::Gateway" do
79
76
  # Stub client that records calls instead of making HTTP requests.
80
77
  StubClient = Struct.new(:calls) do
@@ -201,4 +198,3 @@ test do
201
198
  client.calls[1][1].should == "/_matrix/client/v3/createRoom"
202
199
  end
203
200
  end
204
- end
@@ -3,18 +3,16 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
6
  require "console"
8
- require "async/matrix"
9
7
 
10
8
  module Async
11
9
  module Matrix
12
10
  module ApplicationService
13
11
  # DSL wrapper that pairs a Client with event handlers.
14
12
  #
15
- # A Bot produces handler objects that conform to the Dispatcher's
13
+ # A Bot produces handler objects that conform to the TransactionHandler's
16
14
  # duck-type contract (#event_types, #call). Register a bot on a
17
- # Server (or Dispatcher) the same way you would register a plain handler.
15
+ # Server (or TransactionHandler) the same way you would register a plain handler.
18
16
  #
19
17
  # bot = Bot.new(client) do
20
18
  # on "m.room.member" do |event|
@@ -61,7 +59,7 @@ module Async
61
59
  end
62
60
 
63
61
  # Internal handler object produced by the #on DSL method.
64
- # Conforms to the Dispatcher duck-type: #event_types, #call.
62
+ # Conforms to the TransactionHandler duck-type: #event_types, #call.
65
63
  class Handler
66
64
  attr_reader :event_types
67
65
 
@@ -128,7 +126,7 @@ module Async
128
126
  end
129
127
  end
130
128
 
131
- test do
129
+ __END__
132
130
  describe "Async::Matrix::ApplicationService::Bot" do
133
131
  def make_config
134
132
  Async::Matrix::ApplicationService::Config.new({
@@ -232,4 +230,3 @@ test do
232
230
  }.should.raise(ArgumentError)
233
231
  end
234
232
  end
235
- end
@@ -3,9 +3,6 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
- require "async/matrix"
8
-
9
6
  module Async
10
7
  module Matrix
11
8
  module ApplicationService
@@ -60,7 +57,7 @@ module Async
60
57
  end
61
58
  end
62
59
 
63
- test do
60
+ __END__
64
61
  V = Async::Matrix::ApplicationService::Config::Vivify
65
62
 
66
63
  describe "Vivify" do
@@ -110,4 +107,3 @@ test do
110
107
  result.b.should == true
111
108
  end
112
109
  end
113
- end
@@ -3,12 +3,10 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
6
  require "forwardable"
8
7
  require "json_schemer"
9
8
  require "pathname"
10
9
  require "yaml"
11
- require "async/matrix"
12
10
  require_relative "config/vivify"
13
11
 
14
12
  module Async
@@ -89,7 +87,7 @@ module Async
89
87
  end
90
88
  end
91
89
 
92
- test do
90
+ __END__
93
91
  require "tempfile"
94
92
 
95
93
  describe "Async::Matrix::ApplicationService::Config" do
@@ -225,4 +223,3 @@ test do
225
223
  config.bridge.permissions[:"@admin:localhost"].should == "admin"
226
224
  end
227
225
  end
228
- end
@@ -3,9 +3,7 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
6
  require "console"
8
- require "async/matrix"
9
7
 
10
8
  module Async
11
9
  module Matrix
@@ -16,14 +14,38 @@ module Async
16
14
  # When an event arrives, the dispatcher finds all matching handlers
17
15
  # and calls them. Errors in one handler do not prevent others from running.
18
16
  class Dispatcher
19
- def initialize
20
- @handlers = Hash.new { |h, k| h[k] = [] }
17
+ def initialize(txn_store: TransactionStore.new)
18
+ @handlers = Hash.new { |h, k| h[k] = [] }
19
+ @txn_store = txn_store
21
20
  end
22
21
 
22
+ # Register a Bot (responds to #handlers) or a plain handler (responds
23
+ # to #event_types and #call). Bots are expanded into their handlers.
23
24
  def register(handler)
24
- handler.event_types.each do |type|
25
- @handlers[type] << handler
26
- Console.info(self) { "Registered #{handler.class.name} for #{type}" }
25
+ if handler.respond_to?(:handlers)
26
+ handler.handlers.each { |h| register(h) }
27
+ else
28
+ handler.event_types.each do |type|
29
+ @handlers[type] << handler
30
+ Console.info(self) { "Registered #{handler.class.name} for #{type}" }
31
+ end
32
+ end
33
+ end
34
+
35
+ # Idempotently process a homeserver transaction. Duplicate transaction
36
+ # IDs (already seen) are skipped. Returns :processed or :duplicate.
37
+ #
38
+ # The idempotency store lives here rather than in the HTTP layer because
39
+ # the Grape server is stateless across requests — the dispatcher is the
40
+ # stable, long-lived object.
41
+ def receive_transaction(txn_id, body)
42
+ if @txn_store.seen?(txn_id)
43
+ Console.debug(self) { "Duplicate transaction #{txn_id} — skipping" }
44
+ :duplicate
45
+ else
46
+ dispatch_transaction(body)
47
+ @txn_store.mark_seen(txn_id)
48
+ :processed
27
49
  end
28
50
  end
29
51
 
@@ -59,7 +81,7 @@ module Async
59
81
  end
60
82
  end
61
83
 
62
- test do
84
+ __END__
63
85
  describe "Async::Matrix::ApplicationService::Dispatcher" do
64
86
  it "registers and dispatches to handlers" do
65
87
  received = []
@@ -125,5 +147,39 @@ test do
125
147
  })
126
148
  received.length.should == 2
127
149
  end
150
+
151
+ it "registers a bot (object responding to #handlers)" do
152
+ received = []
153
+ handler = Object.new
154
+ handler.define_singleton_method(:event_types) { ["m.room.message"] }
155
+ handler.define_singleton_method(:call) { |e| received << e }
156
+
157
+ bot = Object.new
158
+ bot.define_singleton_method(:handlers) { [handler] }
159
+
160
+ dispatcher = Async::Matrix::ApplicationService::Dispatcher.new
161
+ dispatcher.register(bot)
162
+ dispatcher.handler_count.should == 1
163
+
164
+ dispatcher.dispatch_transaction({
165
+ "events" => [{"type" => "m.room.message", "content" => {"body" => "hi"}}]
166
+ })
167
+ received.length.should == 1
168
+ end
169
+
170
+ it "processes a transaction idempotently via receive_transaction" do
171
+ received = []
172
+ handler = Object.new
173
+ handler.define_singleton_method(:event_types) { ["m.room.message"] }
174
+ handler.define_singleton_method(:call) { |e| received << e }
175
+
176
+ dispatcher = Async::Matrix::ApplicationService::Dispatcher.new
177
+ dispatcher.register(handler)
178
+
179
+ body = {"events" => [{"type" => "m.room.message", "content" => {"body" => "hi"}}]}
180
+
181
+ dispatcher.receive_transaction("txn1", body).should == :processed
182
+ dispatcher.receive_transaction("txn1", body).should == :duplicate
183
+ received.length.should == 1
184
+ end
128
185
  end
129
- end
@@ -3,9 +3,6 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
- require "async/matrix"
8
-
9
6
  module Async
10
7
  module Matrix
11
8
  module ApplicationService
@@ -21,7 +18,7 @@ module Async
21
18
  end
22
19
  end
23
20
 
24
- test do
21
+ __END__
25
22
  describe "Async::Matrix::ApplicationService::ErrorResponse" do
26
23
  it "parses errcode and error" do
27
24
  resp = Async::Matrix::ApplicationService::ErrorResponse.new({
@@ -38,4 +35,3 @@ test do
38
35
  resp.error.should.be.nil
39
36
  end
40
37
  end
41
- end
@@ -3,9 +3,6 @@
3
3
  # Released under the Apache License, Version 2.0.
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
- require "bundler/setup"
7
- require "async/matrix"
8
-
9
6
  module Async
10
7
  module Matrix
11
8
  module ApplicationService
@@ -115,7 +112,7 @@ module Async
115
112
  end
116
113
  end
117
114
 
118
- test do
115
+ __END__
119
116
  describe "Async::Matrix::ApplicationService::Content" do
120
117
  it "parses msgtype, body, and membership" do
121
118
  content = Async::Matrix::ApplicationService::Content.new({
@@ -286,4 +283,3 @@ test do
286
283
  props.should.include "membership"
287
284
  end
288
285
  end
289
- end