@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.
@@ -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.