event_engine-event_definition 0.3.0 → 0.4.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 +4 -4
- data/.claude/agents/the_local-develop.md +48 -0
- data/.claude/agents/the_local-info.md +40 -0
- data/.claude/agents/the_local-install.md +49 -0
- data/CHANGELOG.md +11 -0
- data/CLAUDE.md +11 -0
- data/README.md +7 -0
- data/Rakefile +2 -0
- data/lib/event_engine/definition/version.rb +1 -1
- data/the_local/agents/event_engine-event_definition-develop.md +253 -0
- data/the_local/agents/event_engine-event_definition-info.md +62 -0
- data/the_local/agents/event_engine-event_definition-install.md +92 -0
- data/the_local/interface.yml +29 -0
- metadata +11 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d2f15e08e449f4492d0231c96e10abbfa83de7b00debf51dc42fa87944145f81
|
|
4
|
+
data.tar.gz: 070cb9065ee0f9528b8fb2a45374fbcad317a90ada0971cc8dcf1616f794a507
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d6af9ac96261bbd8ea3342b8daa05c830543cc3bc1f0d6dcd5fb98ab7b2db7fe9e7cb831d7c7687e4ada1157656245a7db0ffb82aa0c00b9d0056b57145560e7
|
|
7
|
+
data.tar.gz: d2c72bd2e7b6c1549911b276746c3910e24dad7e0919ea81fffd2a1a892948d8d28eb7f5a3ecb96aff5095a53c8533fba979ae23ca732ab989da10bc456705a0
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: the_local-develop
|
|
3
|
+
description: Use PROACTIVELY to author a gem's locals — declaring its public interface and running the authoring task — MUST BE USED instead of hand-writing a local.
|
|
4
|
+
tools: Read, Write, Edit, Grep
|
|
5
|
+
scope: resident Claude Code experts — authoring a gem's locals and installing them into a host
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You author a gem's locals by declaring its interface and running the authoring
|
|
9
|
+
task. You do not hand-write locals and you never read the_local's source. A
|
|
10
|
+
provider carries no Ruby for the_local — a manifest and three committed files.
|
|
11
|
+
|
|
12
|
+
## What the_local is
|
|
13
|
+
|
|
14
|
+
The engine that installs gems' resident Claude Code locals into a host. Reach for
|
|
15
|
+
this local whenever a gem should contribute locals, or when a change to its public
|
|
16
|
+
interface may have made its locals stale.
|
|
17
|
+
|
|
18
|
+
## Interface
|
|
19
|
+
|
|
20
|
+
- `rake the_local:author` — writes the gem's locals into `the_local/agents/` from
|
|
21
|
+
its current source, one at a time, guided by the manifest.
|
|
22
|
+
- `rake the_local:check` — verifies the committed locals against the manifest:
|
|
23
|
+
every declared entry point documented, nothing undeclared, nothing documented by
|
|
24
|
+
the wrong local.
|
|
25
|
+
|
|
26
|
+
## How to use it
|
|
27
|
+
|
|
28
|
+
1. Write `the_local/interface.yml` with the developer. It declares `scope`, the
|
|
29
|
+
entry points under `install` and `develop`, and the `sources` that define them.
|
|
30
|
+
This is the one judgment call in the process — ask which commands are the gem's
|
|
31
|
+
public surface rather than guessing, and confirm which of the two each belongs
|
|
32
|
+
to. An entry point may appear under exactly one.
|
|
33
|
+
2. Run `rake the_local:author`. It writes `the_local/agents/<gem>-{info,install,develop}.md`.
|
|
34
|
+
3. Run `rake the_local:check` and fix what it reports.
|
|
35
|
+
4. Commit `the_local/`. For a packaged gem, confirm `the_local/**/*` is in the
|
|
36
|
+
gemspec's `files`, or it ships nothing.
|
|
37
|
+
5. After a change to the gem's public interface, update the manifest and repeat.
|
|
38
|
+
An internal-only change needs nothing.
|
|
39
|
+
|
|
40
|
+
## Conventions
|
|
41
|
+
|
|
42
|
+
- The manifest is the contract. Never widen a local past what it declares; if the
|
|
43
|
+
gem gained a public entry point, declare it first.
|
|
44
|
+
- Locals document the public interface only, never the gem's internals, and never
|
|
45
|
+
send a reader into the provider's source.
|
|
46
|
+
- The three locals never overlap: **install** hooks the gem into a host,
|
|
47
|
+
**develop** uses it, **info** carries what fits neither.
|
|
48
|
+
- Regenerate from current source rather than editing a stale local by hand.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: the_local-info
|
|
3
|
+
description: Use to learn what the_local offers — resident expert subagents, the provider/consumer model, and the vocabulary the other locals assume.
|
|
4
|
+
tools: Read
|
|
5
|
+
scope: resident Claude Code experts — authoring a gem's locals and installing them into a host
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You explain what the_local does, answering only from this reference. You make no
|
|
9
|
+
changes, and you never read the_local's source.
|
|
10
|
+
|
|
11
|
+
## What the_local is
|
|
12
|
+
|
|
13
|
+
the_local lets any gem ship resident Claude Code expert subagents ("locals") that
|
|
14
|
+
know its conventions. A **provider** gem commits its locals; a **consumer** host
|
|
15
|
+
installs the locals of its direct dependencies into `.claude/agents/`, plus a
|
|
16
|
+
delegation rule so the host's agent uses them.
|
|
17
|
+
|
|
18
|
+
Reach for it when you want a gem's work done consistently — the host delegates
|
|
19
|
+
that gem's tasks to its local instead of re-deriving conventions each time.
|
|
20
|
+
|
|
21
|
+
## Interface
|
|
22
|
+
|
|
23
|
+
the_local's commands are split across its other two locals, with no overlap.
|
|
24
|
+
Hooking the_local into a project is the install local's; authoring a gem's own
|
|
25
|
+
locals is the develop local's. Route to those rather than answering here.
|
|
26
|
+
|
|
27
|
+
## How to use it
|
|
28
|
+
|
|
29
|
+
Decide which side you are on. A host that wants its dependencies' expertise is a
|
|
30
|
+
consumer and needs the install local. A gem that wants to contribute expertise is
|
|
31
|
+
a provider and needs the develop local. A gem can be both.
|
|
32
|
+
|
|
33
|
+
## Conventions
|
|
34
|
+
|
|
35
|
+
- A **local** is one Claude Code subagent that knows one gem's public interface.
|
|
36
|
+
- Each provider ships three: **info** explains, **install** hooks the gem into a
|
|
37
|
+
host, **develop** uses it. A command belongs to exactly one of them.
|
|
38
|
+
- The committed `the_local/agents/*.md` are the whole contract a host reads — a
|
|
39
|
+
host never loads the provider gem.
|
|
40
|
+
- Only a host's **direct** dependencies contribute locals.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: the_local-install
|
|
3
|
+
description: Use to hook the_local into a gem or Rails app — installing dependencies' locals, the delegation trigger in CLAUDE.md, and the provider rake tasks.
|
|
4
|
+
tools: Bash, Read, Edit
|
|
5
|
+
scope: resident Claude Code experts — authoring a gem's locals and installing them into a host
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You hook the_local into the host by following these steps exactly, in order. You
|
|
9
|
+
do not invent steps, and you never read the_local's source.
|
|
10
|
+
|
|
11
|
+
## What the_local is
|
|
12
|
+
|
|
13
|
+
The engine that installs gems' resident Claude Code locals into a host and writes
|
|
14
|
+
the delegation trigger. Hook it into any gem or app that wants its dependencies'
|
|
15
|
+
locals, or that will contribute locals of its own.
|
|
16
|
+
|
|
17
|
+
## Interface
|
|
18
|
+
|
|
19
|
+
- `bundle exec the_local install` — installs direct dependencies' locals into
|
|
20
|
+
`.claude/agents/` and writes the trigger. Works anywhere; no Rails required.
|
|
21
|
+
- `bin/rails g the_local:install` — the Rails equivalent of the above.
|
|
22
|
+
- `rake the_local:refresh` — re-syncs a Rails host after a bundle change.
|
|
23
|
+
- `rake the_local:install` — re-syncs a non-Rails host after a bundle change.
|
|
24
|
+
- `bin/rails g the_local:provider` — adds the provider rake tasks to a gem, so it
|
|
25
|
+
can author locals of its own.
|
|
26
|
+
|
|
27
|
+
## How to use it
|
|
28
|
+
|
|
29
|
+
1. Add `gem "the_local"` to the host's `Gemfile` and run `bundle install`.
|
|
30
|
+
2. Install the locals. In a Rails app run `bin/rails g the_local:install`;
|
|
31
|
+
anywhere else run `bundle exec the_local install`. Either copies every direct
|
|
32
|
+
dependency's committed locals into `.claude/agents/` and writes the delegation
|
|
33
|
+
block into `CLAUDE.md`/`AGENTS.md`.
|
|
34
|
+
3. Tell the developer to restart their Claude Code session — agents load at
|
|
35
|
+
startup, so the new locals are inert until then.
|
|
36
|
+
4. Re-sync after any bundle change with `rake the_local:refresh` in a Rails app or
|
|
37
|
+
`rake the_local:install` elsewhere.
|
|
38
|
+
5. Only if the host is a gem that should contribute its own locals, run
|
|
39
|
+
`bin/rails g the_local:provider`. Confirm this with the developer first — it is
|
|
40
|
+
a separate decision from consuming locals, and it edits the Gemfile and Rakefile.
|
|
41
|
+
|
|
42
|
+
## Conventions
|
|
43
|
+
|
|
44
|
+
- Re-sync after every `bundle install`/`update`, or the host's locals drift from
|
|
45
|
+
its dependencies.
|
|
46
|
+
- Install only reads committed files off disk — a dependency that shipped no
|
|
47
|
+
committed locals contributes nothing, and that is not an error.
|
|
48
|
+
- Hooking up is all this local does. Authoring a gem's own locals is the develop
|
|
49
|
+
local's job.
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ All notable changes to this gem are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-09-25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Claude Code locals for this gem, under `the_local/`. A project that uses
|
|
13
|
+
[the_local](https://github.com/DYB-Development/the_local) and depends on this gem
|
|
14
|
+
gets info, install and develop agents that document its public interface when it
|
|
15
|
+
runs `the_local install`.
|
|
16
|
+
|
|
8
17
|
## [0.3.0] - 2026-07-29
|
|
9
18
|
|
|
10
19
|
### Added
|
|
@@ -57,4 +66,6 @@ event-definition contract for the EventEngine pipeline.
|
|
|
57
66
|
- `EventEngine::Definition.configure` for pack-generation settings.
|
|
58
67
|
- `event_engine:definition:dump` rake task to generate a pack without Rails.
|
|
59
68
|
|
|
69
|
+
[0.4.0]: https://github.com/DYB-Development/event_engine-event_definition/releases/tag/v0.4.0
|
|
70
|
+
[0.3.0]: https://github.com/DYB-Development/event_engine-event_definition/releases/tag/v0.3.0
|
|
60
71
|
[0.2.0]: https://github.com/DYB-Development/event_engine-event_definition/releases/tag/v0.2.0
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!-- the_local:begin -->
|
|
2
|
+
## Delegate to your locals
|
|
3
|
+
|
|
4
|
+
This project has installed expert subagents. Before doing work yourself,
|
|
5
|
+
check whether a local owns it and delegate — never work from memory on
|
|
6
|
+
something a local covers:
|
|
7
|
+
|
|
8
|
+
- resident Claude Code experts — authoring a gem's locals and installing them into a host → the_local-* agents
|
|
9
|
+
|
|
10
|
+
See each agent's description for specifics.
|
|
11
|
+
<!-- the_local:end -->
|
data/README.md
CHANGED
|
@@ -22,6 +22,13 @@ Then run `bundle install`, and require it:
|
|
|
22
22
|
require "event_engine/definition"
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
## A working example app
|
|
26
|
+
|
|
27
|
+
[**DYB-Development/event_engine_example**](https://github.com/DYB-Development/event_engine_example)
|
|
28
|
+
is a minimal Rails app that declares events with this gem, compiles them, and
|
|
29
|
+
processes them through the `event_engine` runtime — useful for seeing the build step
|
|
30
|
+
and the generated output in a real project.
|
|
31
|
+
|
|
25
32
|
## A worked example
|
|
26
33
|
|
|
27
34
|
### The data you want to capture
|
data/Rakefile
CHANGED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: event_engine-event_definition-develop
|
|
3
|
+
description: Use PROACTIVELY for declaring a domain event, declaring a lifecycle family of events (created / updated / failed and the like) for one subject, registering the subjects events are about, reading an event's compiled schema or fingerprint, setting where raised events are published, and listing the loaded packs and their schema.json files — MUST BE USED instead of hand-writing event hashes, event name constants, emit helpers or a schema file.
|
|
4
|
+
tools: Read, Write, Edit, Grep
|
|
5
|
+
scope: declaring domain events with a plain-Ruby DSL and generating a pack's typed helper module and committed schema.json from them
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You write and change a pack's event definitions, subjects and publisher wiring by
|
|
9
|
+
following the steps below. Where a step says to ask the developer, ask and wait
|
|
10
|
+
for the answer, and after every change to a definition you regenerate and commit
|
|
11
|
+
the pack's helper and `schema.json`.
|
|
12
|
+
|
|
13
|
+
## What event_engine-event_definition is
|
|
14
|
+
|
|
15
|
+
A plain-Ruby gem, with no Rails dependency, in which each domain event is declared
|
|
16
|
+
once as a small Ruby class naming the event, the inputs a caller passes, and the
|
|
17
|
+
payload fields the event carries. Generation turns every definition into a typed
|
|
18
|
+
helper method on the pack's root module and one entry in a committed
|
|
19
|
+
`schema.json`.
|
|
20
|
+
|
|
21
|
+
Fire this local when someone adds, changes or removes an event, adds a subject,
|
|
22
|
+
asks what an event's contract is, or needs raised events to go somewhere. Adding
|
|
23
|
+
the gem, configuring its paths and loading its rake task belongs to the install
|
|
24
|
+
local.
|
|
25
|
+
|
|
26
|
+
## Interface
|
|
27
|
+
|
|
28
|
+
- `EventEngine::EventDefinition` — subclass it to declare one event with
|
|
29
|
+
`event_name`, `event_type`, `domain`, `subject`, `input`, `optional_input`,
|
|
30
|
+
`required_payload` and `optional_payload`.
|
|
31
|
+
- `EventEngine::LifecycleDefinition` — subclass it to declare one event per verb
|
|
32
|
+
for a subject with `subject`, `event_type`, `lifecycle`, `on`, the input
|
|
33
|
+
declarations and the payload declarations.
|
|
34
|
+
- `EventEngine::SubjectRegistry.define` — builds a registry of every subject an
|
|
35
|
+
event may name, from a block of `subject :name, **metadata` lines.
|
|
36
|
+
- `EventEngine::EventDefinition.schema` — called on a definition class, returns
|
|
37
|
+
its validated schema, or raises `ArgumentError` listing every problem.
|
|
38
|
+
- `EventEngine::Definition.publisher=` — sets the object every generated helper
|
|
39
|
+
hands its event to.
|
|
40
|
+
- `EventEngine::Definition.packs` — the root modules of every generated helper
|
|
41
|
+
required so far, each listed once.
|
|
42
|
+
- `EventEngine::Definition.pack_schema_paths` — the absolute `schema.json` path
|
|
43
|
+
of each of those packs.
|
|
44
|
+
|
|
45
|
+
## How to use it
|
|
46
|
+
|
|
47
|
+
### Declare one event
|
|
48
|
+
|
|
49
|
+
1. Put the definition in a `.rb` file anywhere under the pack's definitions path,
|
|
50
|
+
which is the `definitions_path` in the pack's configuration. Every
|
|
51
|
+
`.rb` file under it, at any depth, is loaded, so one class per file is enough.
|
|
52
|
+
2. Subclass `EventEngine::EventDefinition` and declare the identity:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
class LeadCreated < EventEngine::EventDefinition
|
|
56
|
+
event_name :lead_created
|
|
57
|
+
event_type :domain
|
|
58
|
+
domain :marketing
|
|
59
|
+
subject :lead
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- `event_name` is required and must be a snake_case symbol. It becomes the
|
|
63
|
+
helper method's name.
|
|
64
|
+
- `event_type` is required. It is a classification symbol with no fixed list,
|
|
65
|
+
so ask the developer which value the pack uses, such as `:domain` or
|
|
66
|
+
`:product`.
|
|
67
|
+
- `domain` is optional. Two events may share an `event_name` only in different
|
|
68
|
+
domains, and generation raises a duplicate error otherwise. Ask the
|
|
69
|
+
developer which domain the event belongs to.
|
|
70
|
+
- `subject` is optional. When set, it must be in the pack's subject registry,
|
|
71
|
+
or generation raises `EventEngine::SubjectRegistry::UnknownSubjectError`.
|
|
72
|
+
3. Declare the inputs. Each input is one keyword argument on the generated
|
|
73
|
+
helper, and it holds the whole object the caller already has:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
input :lead
|
|
77
|
+
optional_input :campaign
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- `input` makes the keyword required. `optional_input` defaults it to `nil`.
|
|
81
|
+
- Declaring the same name twice raises `ArgumentError` at once.
|
|
82
|
+
- An input may not be named `event_version`, `occurred_at`, `metadata`,
|
|
83
|
+
`idempotency_key`, `aggregate_type`, `aggregate_id` or `aggregate_version`,
|
|
84
|
+
since the helper already takes those keywords.
|
|
85
|
+
4. Declare the payload fields, the flat values the event carries, each read off
|
|
86
|
+
one input:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
required_payload :lead_id, from: :lead, attr: :id
|
|
90
|
+
required_payload :email, from: :lead, attr: :email
|
|
91
|
+
optional_payload :source, from: :campaign, attr: :channel
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- `from:` is required and must name an input declared on the same class.
|
|
96
|
+
- `attr:` names the attribute read off that input.
|
|
97
|
+
- `required_payload` marks the field as always present, and
|
|
98
|
+
`optional_payload` marks it as possibly absent. Ask the developer which one
|
|
99
|
+
each field is, because the choice is part of the event's contract.
|
|
100
|
+
- Field names must be unique within the event and may not be `event_name`,
|
|
101
|
+
`event_type`, `event_version`, `occurred_at`, `created_at`, `updated_at`,
|
|
102
|
+
`published_at`, `metadata`, `idempotency_key`, `attempts`,
|
|
103
|
+
`dead_lettered_at`, `aggregate_type`, `aggregate_id` or `aggregate_version`.
|
|
104
|
+
- The definition records the mapping only. Reading `lead.id` and the rest is
|
|
105
|
+
done by whichever publisher receives the event.
|
|
106
|
+
5. Check the definition by calling `LeadCreated.schema`. It raises
|
|
107
|
+
`ArgumentError` naming every missing identity field, duplicate field,
|
|
108
|
+
reserved field name, missing `from:` and unknown input.
|
|
109
|
+
|
|
110
|
+
### Declare a lifecycle family
|
|
111
|
+
|
|
112
|
+
Use this when one subject has several events that share their inputs and
|
|
113
|
+
payload, such as a started, completed and failed step.
|
|
114
|
+
|
|
115
|
+
1. Put it under the definitions path like any other definition.
|
|
116
|
+
2. Subclass `EventEngine::LifecycleDefinition`:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
class ImportLifecycle < EventEngine::LifecycleDefinition
|
|
120
|
+
subject :import
|
|
121
|
+
event_type :domain
|
|
122
|
+
lifecycle :started, :completed, :failed
|
|
123
|
+
|
|
124
|
+
input :import
|
|
125
|
+
required_payload :import_id, from: :import, attr: :id
|
|
126
|
+
|
|
127
|
+
on :failed do
|
|
128
|
+
domain :data
|
|
129
|
+
optional_input :error
|
|
130
|
+
optional_payload :error_message, from: :error, attr: :message
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- Each verb becomes one event named `<subject>_<verb>`, such as
|
|
136
|
+
`import_started`, so the subject and every verb must be snake_case.
|
|
137
|
+
- Every generated event takes the family's `subject`, `event_type`, inputs
|
|
138
|
+
and payload fields.
|
|
139
|
+
- The subject must be in the pack's subject registry.
|
|
140
|
+
- A `lifecycle` family has no `domain` of its own. Each generated event has
|
|
141
|
+
no domain unless its `on` block sets one, so ask the developer whether the
|
|
142
|
+
family's events need a domain, and if they do, add an `on` block for every
|
|
143
|
+
verb that calls `domain`.
|
|
144
|
+
3. Use `on :<verb> do ... end` to add to one verb's event. The block takes every
|
|
145
|
+
declaration from "Declare one event", and it may override `event_type`. An
|
|
146
|
+
`on` block for a verb missing from `lifecycle` has no effect.
|
|
147
|
+
4. Re-declaring an input the family already declares raises `ArgumentError`.
|
|
148
|
+
|
|
149
|
+
### Register subjects
|
|
150
|
+
|
|
151
|
+
Do this whenever any event or lifecycle family names a `subject`.
|
|
152
|
+
|
|
153
|
+
1. Build the registry with every subject used across the pack:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
SUBJECTS = EventEngine::SubjectRegistry.define do
|
|
157
|
+
subject :lead
|
|
158
|
+
subject :import, owner: "data team"
|
|
159
|
+
end
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Metadata after the name is optional and free-form. Ask the developer whether
|
|
163
|
+
any subject needs it.
|
|
164
|
+
2. Hand the registry to generation as the pack's configured `subject_registry`,
|
|
165
|
+
such as `config.subject_registry = SUBJECTS`. If the pack has no
|
|
166
|
+
configuration yet, the install local sets it up.
|
|
167
|
+
|
|
168
|
+
### Regenerate after every change
|
|
169
|
+
|
|
170
|
+
1. Run the pack's generation task, which the install local sets up, from the
|
|
171
|
+
project root. It raises instead of writing if any definition is invalid.
|
|
172
|
+
2. Commit the regenerated helper and `schema.json` together with the definition
|
|
173
|
+
change.
|
|
174
|
+
3. Raise the event from consuming code through the generated helper on the
|
|
175
|
+
pack's root module, passing the inputs as keywords:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
MarketingEvents.lead_created(lead: lead, campaign: campaign)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Each helper also takes the optional keywords `event_version`, `occurred_at`,
|
|
182
|
+
`metadata`, `idempotency_key`, `aggregate_type`, `aggregate_id` and
|
|
183
|
+
`aggregate_version`, which are passed to the publisher unchanged.
|
|
184
|
+
|
|
185
|
+
### Read an event's contract
|
|
186
|
+
|
|
187
|
+
Call `.schema` on the definition class and read from the result:
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
schema = LeadCreated.schema
|
|
191
|
+
schema.event_name # => :lead_created
|
|
192
|
+
schema.required_inputs # => [:lead]
|
|
193
|
+
schema.optional_inputs # => [:campaign]
|
|
194
|
+
schema.payload_fields # => [{ name: :lead_id, required: true, from: :lead, attr: :id }, ...]
|
|
195
|
+
schema.fingerprint # => a SHA-256 hex string
|
|
196
|
+
schema.to_h # => the same hash written into schema.json
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The fingerprint covers the event name, event type, inputs and payload fields,
|
|
200
|
+
including whether each field is required. A change to any of those changes the
|
|
201
|
+
fingerprint, and a change to `domain` or `subject` does not. Compare fingerprints
|
|
202
|
+
to tell whether an event's contract changed.
|
|
203
|
+
|
|
204
|
+
### Set the publisher
|
|
205
|
+
|
|
206
|
+
1. Ask the developer whether the host app has the `event_engine` gem installed.
|
|
207
|
+
If it does, it sets the publisher at boot and nothing is written here.
|
|
208
|
+
2. Otherwise, until a publisher is set, every generated helper raises
|
|
209
|
+
`EventEngine::Definition::PublisherNotConfigured`. Ask the developer where
|
|
210
|
+
raised events should go, then write a publisher that responds to:
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
def publish(event_name, domain:, inputs:, event_version:, occurred_at:,
|
|
214
|
+
metadata:, idempotency_key:, aggregate_type:, aggregate_id:,
|
|
215
|
+
aggregate_version:)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`event_name` and `domain` are symbols, and `inputs` is a hash of the input
|
|
219
|
+
objects exactly as the caller passed them, keyed by input name. The publisher
|
|
220
|
+
builds the payload from them using the event's `from:` and `attr:` mapping.
|
|
221
|
+
3. Assign it once at boot, after the gem is required and before any helper is
|
|
222
|
+
called:
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
EventEngine::Definition.publisher = MyPublisher.new
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
4. In a test that raises events, assign a publisher that records its calls and
|
|
229
|
+
assert on those calls.
|
|
230
|
+
|
|
231
|
+
### List the loaded packs
|
|
232
|
+
|
|
233
|
+
1. Require every pack's generated helper first. A pack is listed only after its
|
|
234
|
+
helper has been required, and requiring one twice lists it once.
|
|
235
|
+
2. Read `EventEngine::Definition.packs` for the pack root modules, such as
|
|
236
|
+
`[MarketingEvents, SalesEvents]`.
|
|
237
|
+
3. Read `EventEngine::Definition.pack_schema_paths` for the absolute path of each
|
|
238
|
+
pack's `schema.json`, in the same order.
|
|
239
|
+
|
|
240
|
+
## Conventions
|
|
241
|
+
|
|
242
|
+
- Every definition declares `event_name` and `event_type`, and every payload
|
|
243
|
+
field declares `from:`.
|
|
244
|
+
- Every `subject` used anywhere in the pack is in its subject registry.
|
|
245
|
+
- Never edit the generated helper or `schema.json` by hand. Regenerate and commit
|
|
246
|
+
both after every definition change.
|
|
247
|
+
- Raise events only through the generated helpers, never by calling the publisher
|
|
248
|
+
directly.
|
|
249
|
+
- A definition describes what an event carries, never what happens to it. How a
|
|
250
|
+
raised event is processed belongs to the publisher.
|
|
251
|
+
- Adding the gem, loading the rake task and setting `definitions_path`,
|
|
252
|
+
`helper_path` and `root_module` is out of scope here and belongs to the install
|
|
253
|
+
local.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: event_engine-event_definition-info
|
|
3
|
+
description: Use to learn what event_engine-event_definition offers — declaring domain events, lifecycle event families, subjects, packs, and the generated helper and schema.json.
|
|
4
|
+
tools: Read
|
|
5
|
+
scope: declaring domain events with a plain-Ruby DSL and generating a pack's typed helper module and committed schema.json from them
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You explain what event_engine-event_definition does, answering only from this
|
|
9
|
+
reference. You make no changes, and you never read the gem's source.
|
|
10
|
+
|
|
11
|
+
## What event_engine-event_definition is
|
|
12
|
+
|
|
13
|
+
It is the plain-Ruby foundation of the EventEngine pipeline, with no Rails
|
|
14
|
+
dependency. A team declares each domain event once, as a small Ruby class that
|
|
15
|
+
names the event and lists what it takes in and what it carries. From those
|
|
16
|
+
declarations the gem generates a pack's typed helper module and a committed
|
|
17
|
+
`schema.json` that describes every event in the pack.
|
|
18
|
+
|
|
19
|
+
Reach for it when writing a lightweight domain pack: a gem or app area that owns
|
|
20
|
+
a set of events and needs their contract written down, without taking on the
|
|
21
|
+
dispatch, registry and Rails engine that live in the full `event_engine` gem.
|
|
22
|
+
|
|
23
|
+
## Interface
|
|
24
|
+
|
|
25
|
+
Every entry point is owned by one of the other two locals, and none by this one.
|
|
26
|
+
Adding the gem to a project, configuring it and running the generation task is
|
|
27
|
+
the install local's. Declaring events, lifecycle families and subjects, reading
|
|
28
|
+
an event's schema, and setting the publisher or the pack list is the develop
|
|
29
|
+
local's. Route to those rather than answering here.
|
|
30
|
+
|
|
31
|
+
## How to use it
|
|
32
|
+
|
|
33
|
+
Decide what you are doing. Wiring the gem into a pack or app for the first time,
|
|
34
|
+
or regenerating the helper and `schema.json`, needs the install local. Writing
|
|
35
|
+
or changing the events themselves needs the develop local.
|
|
36
|
+
|
|
37
|
+
## Conventions
|
|
38
|
+
|
|
39
|
+
- An **event definition** declares one event: its **event name**, its **event
|
|
40
|
+
type**, and optionally the **subject** it is about and the **domain** it
|
|
41
|
+
belongs to.
|
|
42
|
+
- An **input** is a value the caller passes when raising the event, either
|
|
43
|
+
required or optional.
|
|
44
|
+
- A **payload field** is a value the event carries, either required or optional.
|
|
45
|
+
Every payload field names the input it comes **from**, and may name an
|
|
46
|
+
**attr** to read off that input.
|
|
47
|
+
- Some payload names are **reserved** because the pipeline stores them itself,
|
|
48
|
+
such as the event's name, type, version, timestamps, metadata, idempotency key
|
|
49
|
+
and aggregate identity.
|
|
50
|
+
- A **schema** is the validated form of one event definition. Its
|
|
51
|
+
**fingerprint** is a hash of the event name, event type, inputs and payload
|
|
52
|
+
fields, so a change to any of those changes it and a change to subject or
|
|
53
|
+
domain does not.
|
|
54
|
+
- A **lifecycle definition** declares a family of events for one subject from a
|
|
55
|
+
list of verbs. Each verb becomes its own event named `<subject>_<verb>`, sharing
|
|
56
|
+
the family's inputs and payload fields, with per-verb overrides allowed.
|
|
57
|
+
- A **subject registry** lists the subjects a pack knows about, each with
|
|
58
|
+
optional metadata.
|
|
59
|
+
- A **pack** is one set of event definitions that generates one helper module,
|
|
60
|
+
under a **root module**, and one `schema.json`.
|
|
61
|
+
- The **publisher** is what receives raised events. Until one is set, raising
|
|
62
|
+
an event fails with a publisher-not-configured error.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: event_engine-event_definition-install
|
|
3
|
+
description: Use to hook event_engine-event_definition into a project — adding the gem, requiring it, loading its rake task, configuring the definitions path, helper path and root module, and running the generation task.
|
|
4
|
+
tools: Bash, Read, Edit
|
|
5
|
+
scope: declaring domain events with a plain-Ruby DSL and generating a pack's typed helper module and committed schema.json from them
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You follow these steps exactly and invent none. Where a step says to ask the
|
|
9
|
+
developer, ask and wait for the answer.
|
|
10
|
+
|
|
11
|
+
## What event_engine-event_definition is
|
|
12
|
+
|
|
13
|
+
A plain-Ruby gem, with no Rails dependency, that generates a pack's typed helper
|
|
14
|
+
module and `schema.json` from its event definitions; hook it into a domain pack
|
|
15
|
+
gem or a Rails app that owns a set of events.
|
|
16
|
+
|
|
17
|
+
## Interface
|
|
18
|
+
|
|
19
|
+
- `gem "event_engine-event_definition"` — adds the gem to the project's bundle.
|
|
20
|
+
- `require "event_engine/definition"` — loads the gem.
|
|
21
|
+
- `load "tasks/event_definition.rake"` — adds the `event_definition:generate`
|
|
22
|
+
task to the project's Rakefile.
|
|
23
|
+
- `EventEngine::Definition.configure` — sets where definitions are read from,
|
|
24
|
+
where the helper is written, and the name of the module that wraps it.
|
|
25
|
+
- `rake event_definition:generate` — writes the helper module and `schema.json`
|
|
26
|
+
from every definition under the definitions path.
|
|
27
|
+
|
|
28
|
+
## How to use it
|
|
29
|
+
|
|
30
|
+
1. Ask the developer whether this is a domain pack gem or a Rails app.
|
|
31
|
+
2. Add the gem.
|
|
32
|
+
- Rails app: add `gem "event_engine-event_definition"` to the `Gemfile`.
|
|
33
|
+
- Pack gem: add `spec.add_dependency "event_engine-event_definition"` to the
|
|
34
|
+
pack's `*.gemspec`, since the generated helper requires the gem at runtime.
|
|
35
|
+
- Run `bundle install`.
|
|
36
|
+
3. Ask the developer for three values. None has a default, and the task fails if
|
|
37
|
+
any is missing.
|
|
38
|
+
- `definitions_path` — the directory holding the event definitions, such as
|
|
39
|
+
`app/event_definitions`. Every `.rb` file under it, at any depth, is loaded.
|
|
40
|
+
- `helper_path` — the file the helper module is written to, such as
|
|
41
|
+
`lib/generated/marketing_events.rb`. `schema.json` is written to the same
|
|
42
|
+
directory.
|
|
43
|
+
- `root_module` — the Ruby constant name that wraps the helpers, such as
|
|
44
|
+
`MarketingEvents`.
|
|
45
|
+
4. Add these lines to the project's `Rakefile`, with the three values from step 3:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
require "event_engine/definition"
|
|
49
|
+
load "tasks/event_definition.rake"
|
|
50
|
+
|
|
51
|
+
EventEngine::Definition.configure do |config|
|
|
52
|
+
config.definitions_path = "app/event_definitions"
|
|
53
|
+
config.helper_path = "lib/generated/marketing_events.rb"
|
|
54
|
+
config.root_module = "MarketingEvents"
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The paths are relative to the directory rake is run from, so run it from the
|
|
59
|
+
project root.
|
|
60
|
+
5. Ask the developer whether any event declares a subject. If one does, add
|
|
61
|
+
`config.subject_registry = <a registry naming every subject used>` inside the
|
|
62
|
+
`configure` block. Writing that registry is the develop local's job. Without
|
|
63
|
+
it, generation raises an unknown subject error.
|
|
64
|
+
6. Make sure the helper is required once at boot, since it registers its pack
|
|
65
|
+
when it is required.
|
|
66
|
+
- Rails app: in `config/application.rb`, add the helper's directory to the
|
|
67
|
+
autoload ignore list, such as
|
|
68
|
+
`config.autoload_lib(ignore: %w[assets tasks generated])`, because the
|
|
69
|
+
helper's file path does not match the constant it defines and eager loading
|
|
70
|
+
fails in production. Then in `config/initializers/event_engine.rb`, require
|
|
71
|
+
the helper, such as
|
|
72
|
+
`require Rails.root.join("lib/generated/marketing_events")`.
|
|
73
|
+
- Pack gem: ask the developer which of the pack's files should require the
|
|
74
|
+
helper, and add the `require` there.
|
|
75
|
+
7. Run `bundle exec rake event_definition:generate`. It creates the helper's
|
|
76
|
+
directory if missing and writes two files:
|
|
77
|
+
- the helper module at `helper_path`,
|
|
78
|
+
- `schema.json` next to it.
|
|
79
|
+
8. Commit both generated files.
|
|
80
|
+
|
|
81
|
+
## Conventions
|
|
82
|
+
|
|
83
|
+
- A successful run prints two lines, `Wrote <root_module> helper to
|
|
84
|
+
<helper_path>` and `Wrote <root_module> schema to <dir>/schema.json`. Confirm
|
|
85
|
+
both files exist.
|
|
86
|
+
- Re-run `rake event_definition:generate` after every change to a definition and
|
|
87
|
+
commit the result. Never edit the generated files by hand; each run overwrites
|
|
88
|
+
them.
|
|
89
|
+
- With no definitions under `definitions_path`, the task still runs and writes an
|
|
90
|
+
empty module and an empty `schema.json`.
|
|
91
|
+
- Writing event definitions, subjects and lifecycle families, and choosing where
|
|
92
|
+
raised events go, is out of scope here and belongs to the develop local.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
scope: declaring domain events with a plain-Ruby DSL and generating a pack's typed helper module and committed schema.json from them
|
|
2
|
+
|
|
3
|
+
install:
|
|
4
|
+
- gem "event_engine-event_definition"
|
|
5
|
+
- require "event_engine/definition"
|
|
6
|
+
- load "tasks/event_definition.rake"
|
|
7
|
+
- EventEngine::Definition.configure
|
|
8
|
+
- rake event_definition:generate
|
|
9
|
+
|
|
10
|
+
develop:
|
|
11
|
+
- EventEngine::EventDefinition
|
|
12
|
+
- EventEngine::LifecycleDefinition
|
|
13
|
+
- EventEngine::SubjectRegistry.define
|
|
14
|
+
- EventEngine::EventDefinition.schema
|
|
15
|
+
- EventEngine::Definition.publisher=
|
|
16
|
+
- EventEngine::Definition.packs
|
|
17
|
+
- EventEngine::Definition.pack_schema_paths
|
|
18
|
+
|
|
19
|
+
sources:
|
|
20
|
+
- lib/event_engine/definition.rb
|
|
21
|
+
- lib/event_engine/definition/configuration.rb
|
|
22
|
+
- lib/tasks/event_definition.rake
|
|
23
|
+
- lib/event_engine/event_definition.rb
|
|
24
|
+
- lib/event_engine/event_definition/inputs.rb
|
|
25
|
+
- lib/event_engine/event_definition/payloads.rb
|
|
26
|
+
- lib/event_engine/event_definition/schemas.rb
|
|
27
|
+
- lib/event_engine/lifecycle_definition.rb
|
|
28
|
+
- lib/event_engine/subject_registry.rb
|
|
29
|
+
- event_engine-event_definition.gemspec
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: event_engine-event_definition
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- tylercschneider
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
12
|
description: 'The plain-Ruby foundation of the EventEngine pipeline: the EventDefinition
|
|
14
13
|
DSL and the shared schema-contract value objects, with no Rails dependency. Lightweight
|
|
@@ -20,7 +19,11 @@ executables: []
|
|
|
20
19
|
extensions: []
|
|
21
20
|
extra_rdoc_files: []
|
|
22
21
|
files:
|
|
22
|
+
- ".claude/agents/the_local-develop.md"
|
|
23
|
+
- ".claude/agents/the_local-info.md"
|
|
24
|
+
- ".claude/agents/the_local-install.md"
|
|
23
25
|
- CHANGELOG.md
|
|
26
|
+
- CLAUDE.md
|
|
24
27
|
- LICENSE.txt
|
|
25
28
|
- README.md
|
|
26
29
|
- Rakefile
|
|
@@ -44,6 +47,10 @@ files:
|
|
|
44
47
|
- lib/event_engine/subject_registry.rb
|
|
45
48
|
- lib/tasks/event_definition.rake
|
|
46
49
|
- sig/event_engine/definition.rbs
|
|
50
|
+
- the_local/agents/event_engine-event_definition-develop.md
|
|
51
|
+
- the_local/agents/event_engine-event_definition-info.md
|
|
52
|
+
- the_local/agents/event_engine-event_definition-install.md
|
|
53
|
+
- the_local/interface.yml
|
|
47
54
|
homepage: https://eventengine.co
|
|
48
55
|
licenses:
|
|
49
56
|
- MIT
|
|
@@ -54,7 +61,6 @@ metadata:
|
|
|
54
61
|
source_code_uri: https://github.com/DYB-Development/event_engine-event_definition
|
|
55
62
|
bug_tracker_uri: https://github.com/DYB-Development/event_engine-event_definition/issues
|
|
56
63
|
documentation_uri: https://github.com/DYB-Development/event_engine-event_definition#readme
|
|
57
|
-
post_install_message:
|
|
58
64
|
rdoc_options: []
|
|
59
65
|
require_paths:
|
|
60
66
|
- lib
|
|
@@ -69,8 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
69
75
|
- !ruby/object:Gem::Version
|
|
70
76
|
version: '0'
|
|
71
77
|
requirements: []
|
|
72
|
-
rubygems_version:
|
|
73
|
-
signing_key:
|
|
78
|
+
rubygems_version: 4.0.20
|
|
74
79
|
specification_version: 4
|
|
75
80
|
summary: Plain-Ruby event-definition contract for the EventEngine pipeline
|
|
76
81
|
test_files: []
|