acta 0.2.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/.tool-versions +1 -0
- data/CHANGELOG.md +210 -0
- data/LICENSE +21 -0
- data/PLAN.md +158 -0
- data/README.md +559 -0
- data/Rakefile +12 -0
- data/app/controllers/acta/web/application_controller.rb +10 -0
- data/app/controllers/acta/web/events_controller.rb +37 -0
- data/app/helpers/acta/web/application_helper.rb +106 -0
- data/app/views/acta/web/events/index.html.erb +312 -0
- data/app/views/acta/web/events/show.html.erb +72 -0
- data/app/views/layouts/acta/web/application.html.erb +594 -0
- data/config/routes.rb +4 -0
- data/lib/acta/actor.rb +34 -0
- data/lib/acta/adapters/base.rb +59 -0
- data/lib/acta/adapters/postgres.rb +73 -0
- data/lib/acta/adapters/sqlite.rb +58 -0
- data/lib/acta/adapters.rb +19 -0
- data/lib/acta/array_type.rb +30 -0
- data/lib/acta/command.rb +48 -0
- data/lib/acta/current.rb +10 -0
- data/lib/acta/errors.rb +102 -0
- data/lib/acta/event.rb +80 -0
- data/lib/acta/events_query.rb +73 -0
- data/lib/acta/handler.rb +9 -0
- data/lib/acta/model.rb +58 -0
- data/lib/acta/model_type.rb +32 -0
- data/lib/acta/projection.rb +64 -0
- data/lib/acta/projection_managed.rb +108 -0
- data/lib/acta/railtie.rb +65 -0
- data/lib/acta/reactor.rb +15 -0
- data/lib/acta/reactor_job.rb +19 -0
- data/lib/acta/record.rb +10 -0
- data/lib/acta/schema.rb +12 -0
- data/lib/acta/serializable.rb +48 -0
- data/lib/acta/testing/dsl.rb +90 -0
- data/lib/acta/testing/matchers.rb +77 -0
- data/lib/acta/testing.rb +50 -0
- data/lib/acta/types/encrypted_string.rb +63 -0
- data/lib/acta/version.rb +5 -0
- data/lib/acta/web/engine.rb +13 -0
- data/lib/acta/web/events_query.rb +81 -0
- data/lib/acta/web.rb +45 -0
- data/lib/acta.rb +296 -0
- data/lib/generators/acta/install/install_generator.rb +23 -0
- data/lib/generators/acta/install/templates/create_acta_events.rb.tt +9 -0
- data/sig/acta.rbs +4 -0
- metadata +152 -0
data/lib/acta/web.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Acta
|
|
4
|
+
module Web
|
|
5
|
+
# Raised when the engine is mounted without `base_controller_class`
|
|
6
|
+
# being set. Without a configured parent, the engine's controllers
|
|
7
|
+
# would inherit ActionController::Base directly — meaning the event
|
|
8
|
+
# log is publicly accessible. Fail loudly at request time so the
|
|
9
|
+
# mistake surfaces in development before reaching production.
|
|
10
|
+
class ConfigurationError < StandardError; end
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
# The host-app controller class (as a String) that engine controllers
|
|
14
|
+
# should inherit from. Set this to your `ApplicationController` (or any
|
|
15
|
+
# base controller that enforces authentication) before mounting:
|
|
16
|
+
#
|
|
17
|
+
# # config/initializers/acta_web.rb
|
|
18
|
+
# Acta::Web.base_controller_class = "ApplicationController"
|
|
19
|
+
#
|
|
20
|
+
# No default is provided: a misconfigured mount would expose the
|
|
21
|
+
# entire event log without authentication.
|
|
22
|
+
def base_controller_class
|
|
23
|
+
@base_controller_class || raise(
|
|
24
|
+
ConfigurationError,
|
|
25
|
+
"Acta::Web.base_controller_class is not set. Configure it before " \
|
|
26
|
+
"mounting the engine, e.g. in config/initializers/acta_web.rb:\n\n" \
|
|
27
|
+
" Acta::Web.base_controller_class = \"ApplicationController\"\n\n" \
|
|
28
|
+
"Set it to a controller class that enforces authentication so the " \
|
|
29
|
+
"event log isn't publicly accessible."
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def base_controller_class=(klass)
|
|
34
|
+
@base_controller_class = klass
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Test/reset hook — clears the configured controller class. Mainly for specs.
|
|
38
|
+
def reset_configuration!
|
|
39
|
+
@base_controller_class = nil
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
require_relative "web/engine"
|
data/lib/acta.rb
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "acta/version"
|
|
4
|
+
require_relative "acta/errors"
|
|
5
|
+
require_relative "acta/actor"
|
|
6
|
+
require_relative "acta/current"
|
|
7
|
+
require_relative "acta/types/encrypted_string"
|
|
8
|
+
require_relative "acta/model"
|
|
9
|
+
require_relative "acta/serializable"
|
|
10
|
+
require_relative "acta/event"
|
|
11
|
+
require_relative "acta/schema"
|
|
12
|
+
require_relative "acta/record"
|
|
13
|
+
require_relative "acta/adapters"
|
|
14
|
+
require_relative "acta/events_query"
|
|
15
|
+
require_relative "acta/handler"
|
|
16
|
+
require_relative "acta/projection"
|
|
17
|
+
require_relative "acta/reactor"
|
|
18
|
+
require_relative "acta/reactor_job"
|
|
19
|
+
require_relative "acta/command"
|
|
20
|
+
require_relative "acta/projection_managed"
|
|
21
|
+
require_relative "acta/railtie" if defined?(::Rails::Railtie)
|
|
22
|
+
|
|
23
|
+
require "active_support/lazy_load_hooks"
|
|
24
|
+
ActiveSupport.on_load(:active_record) do
|
|
25
|
+
include Acta::ProjectionManaged
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module Acta
|
|
29
|
+
def self.adapter
|
|
30
|
+
@adapter ||= Adapters.for(Record.connection)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.reset_adapter!
|
|
34
|
+
@adapter = nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.emit(event, actor: nil, if_version: nil)
|
|
38
|
+
event.actor = actor if actor
|
|
39
|
+
raise MissingActor, "No actor for emit of #{event.event_type} (set Acta::Current.actor or pass actor:)" if event.actor.nil?
|
|
40
|
+
|
|
41
|
+
assert_version!(event, if_version) unless if_version.nil?
|
|
42
|
+
|
|
43
|
+
ActiveSupport::Notifications.instrument("acta.event_emitted", event:, event_type: event.event_type) do
|
|
44
|
+
Record.transaction(requires_new: true) do
|
|
45
|
+
record = adapter.insert_event(record_attributes_for(event))
|
|
46
|
+
event.recorded_at = record.recorded_at
|
|
47
|
+
dispatch(event, kind: :projection)
|
|
48
|
+
end
|
|
49
|
+
dispatch(event, kind: :handler)
|
|
50
|
+
dispatch(event, kind: :reactor)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
event
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.subscribe(event_class, handler_class, &block)
|
|
57
|
+
handlers[event_class] << { handler_class:, block:, kind: handler_kind(handler_class) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.handlers
|
|
61
|
+
@handlers ||= Hash.new { |h, k| h[k] = [] }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.dispatch(event, kind: nil)
|
|
65
|
+
handlers.each do |event_class, registrations|
|
|
66
|
+
next unless event.is_a?(event_class)
|
|
67
|
+
|
|
68
|
+
registrations.each do |registration|
|
|
69
|
+
next if kind && registration[:kind] != kind
|
|
70
|
+
|
|
71
|
+
invoke(event, registration)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.invoke(event, registration)
|
|
77
|
+
case registration[:kind]
|
|
78
|
+
when :projection then run_projection(event, registration)
|
|
79
|
+
when :reactor then run_reactor(event, registration)
|
|
80
|
+
else registration[:block].call(event)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
private_class_method :invoke
|
|
84
|
+
|
|
85
|
+
def self.run_projection(event, registration)
|
|
86
|
+
ActiveSupport::Notifications.instrument(
|
|
87
|
+
"acta.projection_applied",
|
|
88
|
+
event:,
|
|
89
|
+
projection_class: registration[:handler_class]
|
|
90
|
+
) do
|
|
91
|
+
Projection.applying! { registration[:block].call(event) }
|
|
92
|
+
end
|
|
93
|
+
rescue ProjectionError
|
|
94
|
+
raise
|
|
95
|
+
rescue StandardError => e
|
|
96
|
+
raise ProjectionError.new(
|
|
97
|
+
event:,
|
|
98
|
+
projection_class: registration[:handler_class],
|
|
99
|
+
original: e
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
private_class_method :run_projection
|
|
103
|
+
|
|
104
|
+
def self.run_reactor(event, registration)
|
|
105
|
+
if registration[:handler_class].sync?
|
|
106
|
+
ActiveSupport::Notifications.instrument(
|
|
107
|
+
"acta.reactor_invoked",
|
|
108
|
+
event:,
|
|
109
|
+
reactor_class: registration[:handler_class],
|
|
110
|
+
sync: true
|
|
111
|
+
) do
|
|
112
|
+
registration[:block].call(event)
|
|
113
|
+
end
|
|
114
|
+
else
|
|
115
|
+
ActiveSupport::Notifications.instrument(
|
|
116
|
+
"acta.reactor_enqueued",
|
|
117
|
+
event:,
|
|
118
|
+
reactor_class: registration[:handler_class]
|
|
119
|
+
) do
|
|
120
|
+
ReactorJob.perform_later(
|
|
121
|
+
event_uuid: event.uuid,
|
|
122
|
+
reactor_class: registration[:handler_class].name,
|
|
123
|
+
event_class: event.class.name
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
private_class_method :run_reactor
|
|
129
|
+
|
|
130
|
+
def self.reset_handlers!
|
|
131
|
+
@handlers = Hash.new { |h, k| h[k] = [] }
|
|
132
|
+
@projection_classes = []
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def self.projection_classes
|
|
136
|
+
@projection_classes ||= []
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.register_projection(klass)
|
|
140
|
+
projection_classes << klass unless projection_classes.include?(klass)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def self.rebuild!
|
|
144
|
+
Projection.applying! { truncate_projections! }
|
|
145
|
+
Record.order(:id).find_each do |record|
|
|
146
|
+
event = events.find_by_uuid(record.uuid)
|
|
147
|
+
dispatch(event, kind: :projection)
|
|
148
|
+
rescue ProjectionError
|
|
149
|
+
raise
|
|
150
|
+
rescue StandardError => e
|
|
151
|
+
raise ReplayError.new(record:, original: e)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Truncate all projections in FK-safe order. Wrapped in `Projection.applying!`
|
|
156
|
+
# by `rebuild!` so projection-managed AR models (`acta_managed!`) accept the
|
|
157
|
+
# delete_all calls instead of raising `ProjectionWriteError`.
|
|
158
|
+
def self.truncate_projections!
|
|
159
|
+
legacy, declared = projection_classes.partition { |p| p.truncated_classes.empty? }
|
|
160
|
+
|
|
161
|
+
legacy.each(&:truncate!)
|
|
162
|
+
truncate_order(declared).each(&:truncate!)
|
|
163
|
+
end
|
|
164
|
+
private_class_method :truncate_projections!
|
|
165
|
+
|
|
166
|
+
# Order projections so that, for every belongs_to A → B where A and B are
|
|
167
|
+
# owned by different projections, the projection owning A truncates first.
|
|
168
|
+
# This deletes children before parents and keeps Acta.rebuild! safe under
|
|
169
|
+
# FK constraints regardless of registration order.
|
|
170
|
+
def self.truncate_order(projections)
|
|
171
|
+
return projections if projections.length < 2
|
|
172
|
+
|
|
173
|
+
owner_of = projections.each_with_object({}) do |projection, acc|
|
|
174
|
+
projection.truncated_classes.each { |klass| acc[klass] = projection }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# `before[parent] = [children]`: every child projection must run before
|
|
178
|
+
# the parent so the FK-bearing rows are gone by the time the parent
|
|
179
|
+
# tries to delete the rows they reference.
|
|
180
|
+
before = Hash.new { |h, k| h[k] = [] }
|
|
181
|
+
projections.each do |child_projection|
|
|
182
|
+
child_projection.truncated_classes.each do |child_class|
|
|
183
|
+
child_class.reflect_on_all_associations(:belongs_to).each do |reflection|
|
|
184
|
+
next if reflection.polymorphic?
|
|
185
|
+
|
|
186
|
+
parent_class = begin
|
|
187
|
+
reflection.klass
|
|
188
|
+
rescue StandardError
|
|
189
|
+
next
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
parent_owner = owner_of[parent_class]
|
|
193
|
+
next if parent_owner.nil? || parent_owner == child_projection
|
|
194
|
+
|
|
195
|
+
before[parent_owner] << child_projection unless before[parent_owner].include?(child_projection)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
sorted = topological_sort(projections, before)
|
|
201
|
+
sorted || raise(TruncateOrderError.new(projections))
|
|
202
|
+
end
|
|
203
|
+
private_class_method :truncate_order
|
|
204
|
+
|
|
205
|
+
# Given `before[node] = [predecessors]`, returns nodes ordered so each
|
|
206
|
+
# predecessor appears before the node it constrains, or nil if the graph
|
|
207
|
+
# has a cycle. Stable: preserves input order among nodes that don't
|
|
208
|
+
# constrain each other.
|
|
209
|
+
def self.topological_sort(nodes, before)
|
|
210
|
+
visited = {}
|
|
211
|
+
result = []
|
|
212
|
+
|
|
213
|
+
visit = lambda do |node|
|
|
214
|
+
case visited[node]
|
|
215
|
+
when :done then return true
|
|
216
|
+
when :visiting then return false
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
visited[node] = :visiting
|
|
220
|
+
before[node].each { |predecessor| return false unless visit.call(predecessor) }
|
|
221
|
+
visited[node] = :done
|
|
222
|
+
result << node
|
|
223
|
+
true
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
nodes.each { |node| return nil unless visit.call(node) }
|
|
227
|
+
|
|
228
|
+
result
|
|
229
|
+
end
|
|
230
|
+
private_class_method :topological_sort
|
|
231
|
+
|
|
232
|
+
def self.handler_kind(handler_class)
|
|
233
|
+
if handler_class <= Projection
|
|
234
|
+
:projection
|
|
235
|
+
elsif handler_class <= Reactor
|
|
236
|
+
:reactor
|
|
237
|
+
else
|
|
238
|
+
:handler
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
private_class_method :handler_kind
|
|
242
|
+
|
|
243
|
+
# Public: read the current high-water mark for a stream. Returns 0 for
|
|
244
|
+
# streams that have never been emitted to. Use the result with
|
|
245
|
+
# `Acta.emit(..., if_version: version)` for optimistic locking.
|
|
246
|
+
def self.version_of(stream_type:, stream_key:)
|
|
247
|
+
Record
|
|
248
|
+
.where(stream_type: stream_type.to_s, stream_key: stream_key)
|
|
249
|
+
.maximum(:stream_sequence) || 0
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def self.assert_version!(event, expected)
|
|
253
|
+
if event.stream_type.nil? || event.stream_key.nil?
|
|
254
|
+
raise ArgumentError, "if_version requires the event to declare a stream"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
actual = version_of(stream_type: event.stream_type, stream_key: event.stream_key)
|
|
258
|
+
return if actual == expected
|
|
259
|
+
|
|
260
|
+
raise VersionConflict.new(
|
|
261
|
+
stream_type: event.stream_type,
|
|
262
|
+
stream_key: event.stream_key,
|
|
263
|
+
expected_version: expected,
|
|
264
|
+
actual_version: actual
|
|
265
|
+
)
|
|
266
|
+
end
|
|
267
|
+
private_class_method :assert_version!
|
|
268
|
+
|
|
269
|
+
def self.events
|
|
270
|
+
EventsQuery.new(adapter.fetch_records)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def self.record_attributes_for(event)
|
|
274
|
+
actor = event.actor
|
|
275
|
+
{
|
|
276
|
+
uuid: event.uuid,
|
|
277
|
+
event_type: event.event_type,
|
|
278
|
+
event_version: event.event_version,
|
|
279
|
+
stream_type: event.stream_type,
|
|
280
|
+
stream_key: event.stream_key,
|
|
281
|
+
payload: event.payload_hash,
|
|
282
|
+
actor_type: actor.type,
|
|
283
|
+
actor_id: actor.id,
|
|
284
|
+
source: actor.source,
|
|
285
|
+
metadata: (actor.metadata.empty? ? nil : actor.metadata),
|
|
286
|
+
occurred_at: event.occurred_at,
|
|
287
|
+
recorded_at: Time.current
|
|
288
|
+
}
|
|
289
|
+
end
|
|
290
|
+
private_class_method :record_attributes_for
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# The web admin engine is opt-in: required only when the host runs Rails.
|
|
294
|
+
# Loading it unconditionally would pull in ActionController etc. for
|
|
295
|
+
# non-Rails consumers (background jobs, scripts).
|
|
296
|
+
require_relative "acta/web" if defined?(::Rails)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/migration"
|
|
5
|
+
require "rails/generators/active_record"
|
|
6
|
+
|
|
7
|
+
module Acta
|
|
8
|
+
module Generators
|
|
9
|
+
class InstallGenerator < Rails::Generators::Base
|
|
10
|
+
include Rails::Generators::Migration
|
|
11
|
+
|
|
12
|
+
source_root File.expand_path("templates", __dir__)
|
|
13
|
+
|
|
14
|
+
def self.next_migration_number(path)
|
|
15
|
+
ActiveRecord::Generators::Base.next_migration_number(path)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create_migration_file
|
|
19
|
+
migration_template "create_acta_events.rb.tt", "db/migrate/create_acta_events.rb"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/sig/acta.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: acta
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tom Gladhill
|
|
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: activejob
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '8.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '8.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: activemodel
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '8.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '8.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: activerecord
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '8.1'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '8.1'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: activesupport
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '8.1'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '8.1'
|
|
68
|
+
description: |-
|
|
69
|
+
Acta ships a small, opinionated set of primitives for event-driven and
|
|
70
|
+
event-sourced Rails applications: events, handlers, projections, reactors,
|
|
71
|
+
and commands. Projections run synchronously inside the emit transaction;
|
|
72
|
+
reactors fan out via ActiveJob. ActiveModel-backed payloads with support
|
|
73
|
+
for nested models and ActiveRecord piggyback. SQLite and Postgres adapters.
|
|
74
|
+
email:
|
|
75
|
+
- tom@gladhill.ca
|
|
76
|
+
executables: []
|
|
77
|
+
extensions: []
|
|
78
|
+
extra_rdoc_files: []
|
|
79
|
+
files:
|
|
80
|
+
- ".tool-versions"
|
|
81
|
+
- CHANGELOG.md
|
|
82
|
+
- LICENSE
|
|
83
|
+
- PLAN.md
|
|
84
|
+
- README.md
|
|
85
|
+
- Rakefile
|
|
86
|
+
- app/controllers/acta/web/application_controller.rb
|
|
87
|
+
- app/controllers/acta/web/events_controller.rb
|
|
88
|
+
- app/helpers/acta/web/application_helper.rb
|
|
89
|
+
- app/views/acta/web/events/index.html.erb
|
|
90
|
+
- app/views/acta/web/events/show.html.erb
|
|
91
|
+
- app/views/layouts/acta/web/application.html.erb
|
|
92
|
+
- config/routes.rb
|
|
93
|
+
- lib/acta.rb
|
|
94
|
+
- lib/acta/actor.rb
|
|
95
|
+
- lib/acta/adapters.rb
|
|
96
|
+
- lib/acta/adapters/base.rb
|
|
97
|
+
- lib/acta/adapters/postgres.rb
|
|
98
|
+
- lib/acta/adapters/sqlite.rb
|
|
99
|
+
- lib/acta/array_type.rb
|
|
100
|
+
- lib/acta/command.rb
|
|
101
|
+
- lib/acta/current.rb
|
|
102
|
+
- lib/acta/errors.rb
|
|
103
|
+
- lib/acta/event.rb
|
|
104
|
+
- lib/acta/events_query.rb
|
|
105
|
+
- lib/acta/handler.rb
|
|
106
|
+
- lib/acta/model.rb
|
|
107
|
+
- lib/acta/model_type.rb
|
|
108
|
+
- lib/acta/projection.rb
|
|
109
|
+
- lib/acta/projection_managed.rb
|
|
110
|
+
- lib/acta/railtie.rb
|
|
111
|
+
- lib/acta/reactor.rb
|
|
112
|
+
- lib/acta/reactor_job.rb
|
|
113
|
+
- lib/acta/record.rb
|
|
114
|
+
- lib/acta/schema.rb
|
|
115
|
+
- lib/acta/serializable.rb
|
|
116
|
+
- lib/acta/testing.rb
|
|
117
|
+
- lib/acta/testing/dsl.rb
|
|
118
|
+
- lib/acta/testing/matchers.rb
|
|
119
|
+
- lib/acta/types/encrypted_string.rb
|
|
120
|
+
- lib/acta/version.rb
|
|
121
|
+
- lib/acta/web.rb
|
|
122
|
+
- lib/acta/web/engine.rb
|
|
123
|
+
- lib/acta/web/events_query.rb
|
|
124
|
+
- lib/generators/acta/install/install_generator.rb
|
|
125
|
+
- lib/generators/acta/install/templates/create_acta_events.rb.tt
|
|
126
|
+
- sig/acta.rbs
|
|
127
|
+
homepage: https://github.com/whoojemaflip/acta
|
|
128
|
+
licenses:
|
|
129
|
+
- MIT
|
|
130
|
+
metadata:
|
|
131
|
+
homepage_uri: https://github.com/whoojemaflip/acta
|
|
132
|
+
source_code_uri: https://github.com/whoojemaflip/acta
|
|
133
|
+
changelog_uri: https://github.com/whoojemaflip/acta/blob/main/CHANGELOG.md
|
|
134
|
+
bug_tracker_uri: https://github.com/whoojemaflip/acta/issues
|
|
135
|
+
rdoc_options: []
|
|
136
|
+
require_paths:
|
|
137
|
+
- lib
|
|
138
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
|
+
requirements:
|
|
140
|
+
- - ">="
|
|
141
|
+
- !ruby/object:Gem::Version
|
|
142
|
+
version: '3.4'
|
|
143
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
144
|
+
requirements:
|
|
145
|
+
- - ">="
|
|
146
|
+
- !ruby/object:Gem::Version
|
|
147
|
+
version: '0'
|
|
148
|
+
requirements: []
|
|
149
|
+
rubygems_version: 3.7.2
|
|
150
|
+
specification_version: 4
|
|
151
|
+
summary: Lightweight event-driven and event-sourced primitives for Rails.
|
|
152
|
+
test_files: []
|