@eventengine/store 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.
- package/dist/index.d.ts +32 -0
- package/dist/index.js +46 -0
- package/package.json +41 -0
- package/the-local/agents/store-develop.md +50 -0
- package/the-local/agents/store-info.md +50 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AppendOnlyStore } from '@eventengine/ports';
|
|
2
|
+
import { Level, Handler } from '@eventengine/core';
|
|
3
|
+
|
|
4
|
+
interface StoredEvent {
|
|
5
|
+
name: string;
|
|
6
|
+
occurredAt: string;
|
|
7
|
+
payload: unknown;
|
|
8
|
+
type?: string;
|
|
9
|
+
level?: Level;
|
|
10
|
+
version?: number;
|
|
11
|
+
metadata?: Record<string, unknown>;
|
|
12
|
+
idempotencyKey?: string;
|
|
13
|
+
aggregateType?: string;
|
|
14
|
+
aggregateId?: string;
|
|
15
|
+
aggregateVersion?: number;
|
|
16
|
+
}
|
|
17
|
+
type Projection = (event: StoredEvent) => void | Promise<void>;
|
|
18
|
+
type ProjectionErrorHandler = (error: unknown, event: StoredEvent) => void;
|
|
19
|
+
declare class EventStore {
|
|
20
|
+
private readonly log;
|
|
21
|
+
private readonly onProjectionError;
|
|
22
|
+
private readonly projections;
|
|
23
|
+
constructor(log: AppendOnlyStore<StoredEvent>, onProjectionError?: ProjectionErrorHandler);
|
|
24
|
+
subscribe(projection: Projection): void;
|
|
25
|
+
append(event: StoredEvent): Promise<void>;
|
|
26
|
+
recorder(): Handler;
|
|
27
|
+
projectionDispatcher(): Handler;
|
|
28
|
+
replay(projection: Projection): Promise<void>;
|
|
29
|
+
all(): Promise<StoredEvent[]>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { EventStore, type Projection, type ProjectionErrorHandler, type StoredEvent };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/event-store.ts
|
|
2
|
+
var EventStore = class {
|
|
3
|
+
constructor(log, onProjectionError = () => void 0) {
|
|
4
|
+
this.log = log;
|
|
5
|
+
this.onProjectionError = onProjectionError;
|
|
6
|
+
}
|
|
7
|
+
log;
|
|
8
|
+
onProjectionError;
|
|
9
|
+
projections = [];
|
|
10
|
+
subscribe(projection) {
|
|
11
|
+
this.projections.push(projection);
|
|
12
|
+
}
|
|
13
|
+
async append(event) {
|
|
14
|
+
await this.log.append(event);
|
|
15
|
+
}
|
|
16
|
+
recorder() {
|
|
17
|
+
return (event) => this.append(event);
|
|
18
|
+
}
|
|
19
|
+
projectionDispatcher() {
|
|
20
|
+
return async (event) => {
|
|
21
|
+
for (const projection of this.projections) {
|
|
22
|
+
try {
|
|
23
|
+
await projection(event);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
this.onProjectionError(error, event);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async replay(projection) {
|
|
31
|
+
for (const event of await this.all()) await projection(event);
|
|
32
|
+
}
|
|
33
|
+
async all() {
|
|
34
|
+
const events = [];
|
|
35
|
+
let cursor = null;
|
|
36
|
+
do {
|
|
37
|
+
const page = await this.log.readFrom(cursor, 100);
|
|
38
|
+
events.push(...page.rows);
|
|
39
|
+
cursor = page.next;
|
|
40
|
+
} while (cursor !== null);
|
|
41
|
+
return events;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
export {
|
|
45
|
+
EventStore
|
|
46
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eventengine/store",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "event-engine store: the permanent event record, projections, and replay built on @eventengine/core.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/DYB-Development/event-engine.git",
|
|
9
|
+
"directory": "store"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/DYB-Development/event-engine/tree/main/store#readme",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"the-local/agents"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@eventengine/core": "0.1.0",
|
|
24
|
+
"@eventengine/ports": "0.1.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"zod": "^3.24.1"
|
|
28
|
+
},
|
|
29
|
+
"the-local": {
|
|
30
|
+
"prefix": "store",
|
|
31
|
+
"scope": "the permanent event record — recording, projections, and replay on core",
|
|
32
|
+
"agentsDir": "the-local/agents"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
37
|
+
"build:locals": "the-local build ."
|
|
38
|
+
},
|
|
39
|
+
"main": "dist/index.js",
|
|
40
|
+
"types": "dist/index.d.ts"
|
|
41
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: store-develop
|
|
3
|
+
description: Use PROACTIVELY for work involving @eventengine/store — recording, projections, or replay. MUST BE USED instead of guessing store's API.
|
|
4
|
+
tools: Read, Write, Edit, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You do @eventengine/store work following the reference exactly: wire the recorder and projectionDispatcher into core's EventEngine, record via emit (not by hand), register live projections with subscribe, and rebuild state with replay. You know store builds on @eventengine/core and defer event definition to it. You do not invent API the reference does not list.
|
|
8
|
+
|
|
9
|
+
## @eventengine/store
|
|
10
|
+
|
|
11
|
+
The permanent, queryable event record — plus the projections and replay built on
|
|
12
|
+
it. It is **built on @eventengine/core**: it plugs into core's `EventEngine` as
|
|
13
|
+
two handlers (mirroring the Ruby gem's `Recorder` + `ProjectionDispatcher`) and
|
|
14
|
+
records the same Event envelope core defines. It also uses an append-only store
|
|
15
|
+
from @eventengine/ports.
|
|
16
|
+
|
|
17
|
+
### Wire it in
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const store = new EventStore(new InMemoryAppendOnlyStore(), onProjectionError);
|
|
21
|
+
engine.registerHandler(store.recorder(), "all"); // records every dispatched event
|
|
22
|
+
engine.registerHandler(store.projectionDispatcher(), "all"); // runs projections
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Recording happens **via dispatch**, not a manual call — `emit` an event and the
|
|
26
|
+
recorder appends it. The recorder runs before the projection dispatcher, so an
|
|
27
|
+
event is durable before any projection sees it.
|
|
28
|
+
|
|
29
|
+
### API
|
|
30
|
+
|
|
31
|
+
- `recorder()` → a `Handler` that appends the event to the log.
|
|
32
|
+
- `projectionDispatcher()` → a `Handler` that runs every subscribed projection,
|
|
33
|
+
**isolated**: if one throws, the rest still run and the error goes to the
|
|
34
|
+
constructor's `onProjectionError`. The append is never undone.
|
|
35
|
+
- `subscribe(projection)` — register a projection, any
|
|
36
|
+
`(event) => void | Promise<void>`.
|
|
37
|
+
- `append(event)` — record directly (used by the recorder; handy in tests).
|
|
38
|
+
- `all()` — every recorded event, paged through the cursor.
|
|
39
|
+
- `replay(projection)` — walk the whole log through a projection to rebuild
|
|
40
|
+
state (event sourcing). Unlike `subscribe`, replay **fails fast** — a rebuild
|
|
41
|
+
that hits a bad event should surface, not skip.
|
|
42
|
+
|
|
43
|
+
### Conventions
|
|
44
|
+
|
|
45
|
+
- Record through the engine: register `recorder()` and `projectionDispatcher()`
|
|
46
|
+
and `emit`; don't append by hand outside tests.
|
|
47
|
+
- Use `subscribe` for live projections (isolated) and `replay` for rebuilds
|
|
48
|
+
(fail-fast).
|
|
49
|
+
- The store holds core's Event envelope verbatim; it doesn't redefine events —
|
|
50
|
+
that's @eventengine/core's job.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: store-info
|
|
3
|
+
description: Use to learn @eventengine/store — the event record, recorder and projection-dispatcher handlers, subscribe/replay, and how it builds on core.
|
|
4
|
+
tools: Read
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You explain @eventengine/store, answering only from the reference: EventStore plugs into core's EventEngine as a recorder and a projection-dispatcher, records the Event envelope, runs isolated live projections via subscribe, and rebuilds state via fail-fast replay. You make no changes.
|
|
8
|
+
|
|
9
|
+
## @eventengine/store
|
|
10
|
+
|
|
11
|
+
The permanent, queryable event record — plus the projections and replay built on
|
|
12
|
+
it. It is **built on @eventengine/core**: it plugs into core's `EventEngine` as
|
|
13
|
+
two handlers (mirroring the Ruby gem's `Recorder` + `ProjectionDispatcher`) and
|
|
14
|
+
records the same Event envelope core defines. It also uses an append-only store
|
|
15
|
+
from @eventengine/ports.
|
|
16
|
+
|
|
17
|
+
### Wire it in
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const store = new EventStore(new InMemoryAppendOnlyStore(), onProjectionError);
|
|
21
|
+
engine.registerHandler(store.recorder(), "all"); // records every dispatched event
|
|
22
|
+
engine.registerHandler(store.projectionDispatcher(), "all"); // runs projections
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Recording happens **via dispatch**, not a manual call — `emit` an event and the
|
|
26
|
+
recorder appends it. The recorder runs before the projection dispatcher, so an
|
|
27
|
+
event is durable before any projection sees it.
|
|
28
|
+
|
|
29
|
+
### API
|
|
30
|
+
|
|
31
|
+
- `recorder()` → a `Handler` that appends the event to the log.
|
|
32
|
+
- `projectionDispatcher()` → a `Handler` that runs every subscribed projection,
|
|
33
|
+
**isolated**: if one throws, the rest still run and the error goes to the
|
|
34
|
+
constructor's `onProjectionError`. The append is never undone.
|
|
35
|
+
- `subscribe(projection)` — register a projection, any
|
|
36
|
+
`(event) => void | Promise<void>`.
|
|
37
|
+
- `append(event)` — record directly (used by the recorder; handy in tests).
|
|
38
|
+
- `all()` — every recorded event, paged through the cursor.
|
|
39
|
+
- `replay(projection)` — walk the whole log through a projection to rebuild
|
|
40
|
+
state (event sourcing). Unlike `subscribe`, replay **fails fast** — a rebuild
|
|
41
|
+
that hits a bad event should surface, not skip.
|
|
42
|
+
|
|
43
|
+
### Conventions
|
|
44
|
+
|
|
45
|
+
- Record through the engine: register `recorder()` and `projectionDispatcher()`
|
|
46
|
+
and `emit`; don't append by hand outside tests.
|
|
47
|
+
- Use `subscribe` for live projections (isolated) and `replay` for rebuilds
|
|
48
|
+
(fail-fast).
|
|
49
|
+
- The store holds core's Event envelope verbatim; it doesn't redefine events —
|
|
50
|
+
that's @eventengine/core's job.
|