@semiont/event-sourcing 0.5.4 → 0.5.5
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 +645 -20
- package/package.json +4 -2
- package/dist/event-log.d.ts +0 -51
- package/dist/event-log.d.ts.map +0 -1
- package/dist/event-store-factory.d.ts +0 -19
- package/dist/event-store-factory.d.ts.map +0 -1
- package/dist/event-store.d.ts +0 -40
- package/dist/event-store.d.ts.map +0 -1
- package/dist/identifier-utils.d.ts +0 -10
- package/dist/identifier-utils.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/query/event-query.d.ts +0 -48
- package/dist/query/event-query.d.ts.map +0 -1
- package/dist/storage/event-storage.d.ts +0 -111
- package/dist/storage/event-storage.d.ts.map +0 -1
- package/dist/storage/shard-utils.d.ts +0 -56
- package/dist/storage/shard-utils.d.ts.map +0 -1
- package/dist/storage/storage-uri-index.d.ts +0 -60
- package/dist/storage/storage-uri-index.d.ts.map +0 -1
- package/dist/storage/view-storage.d.ts +0 -33
- package/dist/storage/view-storage.d.ts.map +0 -1
- package/dist/view-manager.d.ts +0 -84
- package/dist/view-manager.d.ts.map +0 -1
- package/dist/views/projection-reducers.d.ts +0 -73
- package/dist/views/projection-reducers.d.ts.map +0 -1
- package/dist/views/view-materializer.d.ts +0 -92
- package/dist/views/view-materializer.d.ts.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,25 +1,650 @@
|
|
|
1
|
+
import * as _semiont_core from '@semiont/core';
|
|
2
|
+
import { ResourceId, ResourceDescriptor, ResourceAnnotations, Logger, EventInput, StoredEvent, EventQuery as EventQuery$1, PersistedEvent, EventBus, TagSchema } from '@semiont/core';
|
|
3
|
+
import { SemiontProject } from '@semiont/core/node';
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
|
-
*
|
|
6
|
+
* View Storage - Materialized Views
|
|
3
7
|
*
|
|
4
|
-
*
|
|
8
|
+
* Stores materialized views of resource state and annotations
|
|
9
|
+
* Built from event streams, can be rebuilt at any time
|
|
5
10
|
*
|
|
6
|
-
*
|
|
7
|
-
|
|
11
|
+
* Stores both ResourceDescriptor metadata and ResourceAnnotations, but keeps them logically separate
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface ResourceView {
|
|
15
|
+
resource: ResourceDescriptor;
|
|
16
|
+
annotations: ResourceAnnotations;
|
|
17
|
+
}
|
|
18
|
+
interface ViewStorage {
|
|
19
|
+
save(resourceId: ResourceId, view: ResourceView): Promise<void>;
|
|
20
|
+
get(resourceId: ResourceId): Promise<ResourceView | null>;
|
|
21
|
+
delete(resourceId: ResourceId): Promise<void>;
|
|
22
|
+
exists(resourceId: ResourceId): Promise<boolean>;
|
|
23
|
+
getAll(): Promise<ResourceView[]>;
|
|
24
|
+
}
|
|
25
|
+
declare class FilesystemViewStorage implements ViewStorage {
|
|
26
|
+
private basePath;
|
|
27
|
+
private logger?;
|
|
28
|
+
constructor(project: SemiontProject, logger?: Logger);
|
|
29
|
+
private getProjectionPath;
|
|
30
|
+
save(resourceId: ResourceId, projection: ResourceView): Promise<void>;
|
|
31
|
+
get(resourceId: ResourceId): Promise<ResourceView | null>;
|
|
32
|
+
delete(resourceId: ResourceId): Promise<void>;
|
|
33
|
+
exists(resourceId: ResourceId): Promise<boolean>;
|
|
34
|
+
getAll(): Promise<ResourceView[]>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Event Storage - Physical Storage Layer
|
|
39
|
+
*
|
|
40
|
+
* Handles file I/O operations for event storage:
|
|
41
|
+
* - JSONL file writing/reading
|
|
42
|
+
* - 4-hex sharding (65,536 shards)
|
|
43
|
+
* - File rotation
|
|
44
|
+
* - Event stream initialization
|
|
45
|
+
*
|
|
46
|
+
* @see docs/EVENT-STORE.md#eventstorage for architecture details
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
interface EventStorageConfig {
|
|
50
|
+
maxEventsPerFile?: number;
|
|
51
|
+
enableSharding?: boolean;
|
|
52
|
+
numShards?: number;
|
|
53
|
+
enableCompression?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* EventStorage handles physical storage of events
|
|
57
|
+
* Owns: file I/O, sharding, AND sequence/hash tracking
|
|
58
|
+
*/
|
|
59
|
+
declare class EventStorage {
|
|
60
|
+
private config;
|
|
61
|
+
private project;
|
|
62
|
+
private logger?;
|
|
63
|
+
private resourceSequences;
|
|
64
|
+
private currentFiles;
|
|
65
|
+
constructor(project: SemiontProject, config?: EventStorageConfig, logger?: Logger);
|
|
66
|
+
/**
|
|
67
|
+
* Calculate shard path for a resource ID
|
|
68
|
+
* Uses jump consistent hash for uniform distribution
|
|
69
|
+
* Special case: __system__ events bypass sharding
|
|
70
|
+
*/
|
|
71
|
+
getShardPath(resourceId: ResourceId): string;
|
|
72
|
+
/**
|
|
73
|
+
* Get full path to resource's event directory
|
|
74
|
+
*/
|
|
75
|
+
getResourcePath(resourceId: ResourceId): string;
|
|
76
|
+
/**
|
|
77
|
+
* Initialize directory structure for a resource's event stream
|
|
78
|
+
* Also loads sequence number and last hash if stream exists
|
|
79
|
+
*/
|
|
80
|
+
initializeResourceStream(resourceId: ResourceId): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Append an event - handles EVERYTHING for event creation
|
|
83
|
+
* Creates ID, timestamp, metadata, sequence tracking, and writes to disk.
|
|
84
|
+
*
|
|
85
|
+
* Integrity is provided by git at the commit level (when gitSync is enabled),
|
|
86
|
+
* not by per-event chaining metadata. Per-event signatures (the unused
|
|
87
|
+
* `EventSignature` field on StoredEvent) are the planned mechanism for
|
|
88
|
+
* cross-KB authorship binding when federation becomes a real requirement.
|
|
89
|
+
*
|
|
90
|
+
* @param options.correlationId - Optional id propagated from a command. Stored
|
|
91
|
+
* on the event's metadata so subscribers (notably the events-stream → frontend
|
|
92
|
+
* path) can match command-result events back to the POST that initiated them.
|
|
93
|
+
*/
|
|
94
|
+
appendEvent(event: EventInput, resourceId: ResourceId, options?: {
|
|
95
|
+
correlationId?: string;
|
|
96
|
+
}): Promise<StoredEvent>;
|
|
97
|
+
/**
|
|
98
|
+
* Write an event to storage (append to JSONL)
|
|
99
|
+
* Internal method - use appendEvent() instead
|
|
100
|
+
*
|
|
101
|
+
* Uses currentFiles cache to avoid fs.readdir() + countEventsInFile() on every append.
|
|
102
|
+
* Cache is populated on first append (cold start) and updated on rotation.
|
|
103
|
+
*/
|
|
104
|
+
private writeEvent;
|
|
105
|
+
/**
|
|
106
|
+
* Count events in a specific file
|
|
107
|
+
*/
|
|
108
|
+
countEventsInFile(resourceId: ResourceId, filename: string): Promise<number>;
|
|
109
|
+
/**
|
|
110
|
+
* Read all events from a specific file
|
|
111
|
+
*/
|
|
112
|
+
readEventsFromFile(resourceId: ResourceId, filename: string): Promise<StoredEvent[]>;
|
|
113
|
+
/**
|
|
114
|
+
* Get list of event files for a resource (sorted by sequence)
|
|
115
|
+
*/
|
|
116
|
+
getEventFiles(resourceId: ResourceId): Promise<string[]>;
|
|
117
|
+
/**
|
|
118
|
+
* Create a new event file for rotation
|
|
119
|
+
*/
|
|
120
|
+
createNewEventFile(resourceId: ResourceId): Promise<string>;
|
|
121
|
+
/**
|
|
122
|
+
* Get the last event from a specific file
|
|
123
|
+
*/
|
|
124
|
+
getLastEvent(resourceId: ResourceId, filename: string): Promise<StoredEvent | null>;
|
|
125
|
+
/**
|
|
126
|
+
* Get all events for a resource across all files
|
|
127
|
+
*/
|
|
128
|
+
getAllEvents(resourceId: ResourceId): Promise<StoredEvent[]>;
|
|
129
|
+
/**
|
|
130
|
+
* Get all resource IDs by scanning shard directories
|
|
131
|
+
*/
|
|
132
|
+
getAllResourceIds(): Promise<ResourceId[]>;
|
|
133
|
+
/**
|
|
134
|
+
* Create filename for event file
|
|
135
|
+
*/
|
|
136
|
+
private createEventFilename;
|
|
137
|
+
/**
|
|
138
|
+
* Get current sequence number for a resource
|
|
139
|
+
*/
|
|
140
|
+
getSequenceNumber(resourceId: ResourceId): number;
|
|
141
|
+
/**
|
|
142
|
+
* Increment and return next sequence number for a resource
|
|
143
|
+
*/
|
|
144
|
+
getNextSequenceNumber(resourceId: ResourceId): number;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* EventLog - Event Persistence Layer
|
|
149
|
+
*
|
|
150
|
+
* Single Responsibility: Event persistence only
|
|
151
|
+
* - Appends events to storage (JSONL files)
|
|
152
|
+
* - Retrieves events by resource
|
|
153
|
+
* - Queries events with filters
|
|
154
|
+
*
|
|
155
|
+
* Does NOT handle:
|
|
156
|
+
* - Pub/sub notifications (see EventBus)
|
|
157
|
+
* - View updates (see ViewManager)
|
|
158
|
+
*/
|
|
159
|
+
|
|
160
|
+
interface EventLogConfig {
|
|
161
|
+
project: SemiontProject;
|
|
162
|
+
enableSharding?: boolean;
|
|
163
|
+
maxEventsPerFile?: number;
|
|
164
|
+
}
|
|
165
|
+
declare class EventLog {
|
|
166
|
+
readonly storage: EventStorage;
|
|
167
|
+
constructor(config: EventLogConfig, logger?: Logger);
|
|
168
|
+
/**
|
|
169
|
+
* Append event to log
|
|
170
|
+
* @param event - Resource event (from @semiont/core)
|
|
171
|
+
* @param resourceId - Branded ResourceId (from @semiont/core)
|
|
172
|
+
* @param options.correlationId - Optional command correlation id (stored on metadata)
|
|
173
|
+
* @returns Stored event with metadata (sequence number, timestamp, checksum)
|
|
174
|
+
*/
|
|
175
|
+
append(event: EventInput, resourceId: ResourceId, options?: {
|
|
176
|
+
correlationId?: string;
|
|
177
|
+
}): Promise<StoredEvent>;
|
|
178
|
+
/**
|
|
179
|
+
* Get all events for a resource
|
|
180
|
+
* @param resourceId - Branded ResourceId (from @semiont/core)
|
|
181
|
+
*/
|
|
182
|
+
getEvents(resourceId: ResourceId): Promise<StoredEvent[]>;
|
|
183
|
+
/**
|
|
184
|
+
* Get all resource IDs
|
|
185
|
+
* @returns Array of branded ResourceId types
|
|
186
|
+
*/
|
|
187
|
+
getAllResourceIds(): Promise<ResourceId[]>;
|
|
188
|
+
/**
|
|
189
|
+
* Query events with filter
|
|
190
|
+
* @param resourceId - Branded ResourceId (from @semiont/core)
|
|
191
|
+
* @param filter - Optional event filter
|
|
192
|
+
*/
|
|
193
|
+
queryEvents(resourceId: ResourceId, filter?: EventQuery$1): Promise<StoredEvent[]>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Minimal structural type for the event log dependency of `rebuildAll`.
|
|
198
|
+
* Avoids importing the concrete EventLog class from a sibling directory and
|
|
199
|
+
* keeps the materializer independent of the event-log implementation.
|
|
200
|
+
*/
|
|
201
|
+
interface RebuildEventSource {
|
|
202
|
+
getEvents(resourceId: ResourceId): Promise<StoredEvent[]>;
|
|
203
|
+
getAllResourceIds(): Promise<ResourceId[]>;
|
|
204
|
+
}
|
|
205
|
+
interface ViewMaterializerConfig {
|
|
206
|
+
basePath: string;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* ViewMaterializer builds and maintains materialized views from events
|
|
210
|
+
*/
|
|
211
|
+
declare class ViewMaterializer {
|
|
212
|
+
private viewStorage;
|
|
213
|
+
private config;
|
|
214
|
+
private logger?;
|
|
215
|
+
constructor(viewStorage: ViewStorage, config: ViewMaterializerConfig, logger?: Logger);
|
|
216
|
+
/**
|
|
217
|
+
* Materialize resource view from events
|
|
218
|
+
* Loads existing view if cached, otherwise rebuilds from events
|
|
219
|
+
*/
|
|
220
|
+
materialize(events: StoredEvent[], resourceId: ResourceId): Promise<ResourceView | null>;
|
|
221
|
+
/**
|
|
222
|
+
* Materialize view incrementally with a single event
|
|
223
|
+
* Falls back to full rebuild if view doesn't exist
|
|
224
|
+
*/
|
|
225
|
+
materializeIncremental(resourceId: ResourceId, event: PersistedEvent, getAllEvents: () => Promise<StoredEvent[]>): Promise<void>;
|
|
226
|
+
/**
|
|
227
|
+
* Update the storage-uri index in response to an event.
|
|
228
|
+
*
|
|
229
|
+
* Only yield:created (with storageUri), yield:moved, need index changes.
|
|
230
|
+
* resource.archived / resource.unarchived do NOT modify the index.
|
|
231
|
+
*/
|
|
232
|
+
private materializeStorageUriIndex;
|
|
233
|
+
/**
|
|
234
|
+
* Materialize view from event list (full rebuild)
|
|
235
|
+
*/
|
|
236
|
+
private materializeFromEvents;
|
|
237
|
+
/**
|
|
238
|
+
* Apply an event to ResourceDescriptor state (metadata only)
|
|
239
|
+
*/
|
|
240
|
+
private applyEventToResource;
|
|
241
|
+
/**
|
|
242
|
+
* Apply an event to ResourceAnnotations (annotation collections only)
|
|
243
|
+
*/
|
|
244
|
+
private applyEventToAnnotations;
|
|
245
|
+
/**
|
|
246
|
+
* Walk every event stream in the event log and materialize the corresponding
|
|
247
|
+
* view from scratch. Idempotent: existing view files are overwritten.
|
|
248
|
+
*
|
|
249
|
+
* Mirrors GraphDBConsumer.rebuildAll() and Smelter.rebuildAll() — this is the
|
|
250
|
+
* recovery path that makes the ephemeral stateDir safe to wipe. The live
|
|
251
|
+
* append path (EventStore.appendEvent → materializeIncremental /
|
|
252
|
+
* materializeEntityTypes / materializeTagSchemas) is unchanged and runs in
|
|
253
|
+
* addition.
|
|
254
|
+
*/
|
|
255
|
+
rebuildAll(eventLog: RebuildEventSource): Promise<void>;
|
|
256
|
+
/**
|
|
257
|
+
* Materialize entity types view — System-level view.
|
|
258
|
+
*
|
|
259
|
+
* I/O shell around the pure {@link applyEntityTypeAdded} reducer:
|
|
260
|
+
* read JSON file → reduce → write JSON file. The reducer owns the
|
|
261
|
+
* dedup + sort semantics; the shell owns the disk I/O.
|
|
262
|
+
*/
|
|
263
|
+
materializeEntityTypes(entityType: string): Promise<void>;
|
|
264
|
+
/**
|
|
265
|
+
* Materialize tag schemas view — System-level view.
|
|
266
|
+
*
|
|
267
|
+
* I/O shell around the pure {@link applyTagSchemaAdded} reducer.
|
|
268
|
+
* The reducer owns the most-recent-wins semantics + the
|
|
269
|
+
* differing-content-overwrite-warning; the shell forwards the
|
|
270
|
+
* warning (when present) to this materializer's logger and writes
|
|
271
|
+
* the resulting state to disk.
|
|
272
|
+
*/
|
|
273
|
+
materializeTagSchemas(schema: _semiont_core.TagSchema): Promise<void>;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* ViewManager - Materialized View Management Layer
|
|
278
|
+
*
|
|
279
|
+
* Single Responsibility: View updates only
|
|
280
|
+
* - Updates resource views from events
|
|
281
|
+
* - Updates system views (entity types)
|
|
282
|
+
* - Rebuilds views when needed
|
|
283
|
+
*
|
|
284
|
+
* Does NOT handle:
|
|
285
|
+
* - Event persistence (see EventLog)
|
|
286
|
+
* - Pub/sub notifications (see EventBus)
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
interface ViewManagerConfig {
|
|
290
|
+
basePath: string;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* ViewManager wraps ViewMaterializer with a clean API
|
|
294
|
+
* Handles both resource and system-level views
|
|
295
|
+
*
|
|
296
|
+
* ## Per-resource serialization
|
|
297
|
+
*
|
|
298
|
+
* `materializeResource` runs read-modify-write cycles on the view file for
|
|
299
|
+
* a given resource (load JSON → apply event → save JSON). When multiple
|
|
300
|
+
* events arrive for the same resource in rapid succession — the canonical
|
|
301
|
+
* example is the reference-detection worker emitting `mark:added` +
|
|
302
|
+
* `job:progress` + `job:completed` within a few milliseconds — concurrent
|
|
303
|
+
* RMW cycles will clobber each other, losing events and occasionally
|
|
304
|
+
* corrupting the view file entirely.
|
|
305
|
+
*
|
|
306
|
+
* ViewManager serializes these via `serializePerKey` from `@semiont/core`:
|
|
307
|
+
* each incoming `materializeResource` call chains onto the previous one
|
|
308
|
+
* for the same `resourceId`, so the work runs strictly sequentially per
|
|
309
|
+
* resource while still parallelizing across different resources. System
|
|
310
|
+
* events go through their own shared chain (keyed by a sentinel).
|
|
311
|
+
*
|
|
312
|
+
* Why this shape and not RxJS `groupBy + concatMap`:
|
|
313
|
+
* ViewManager is called **synchronously** by `EventStore.appendEvent` — it
|
|
314
|
+
* must block the caller until the view is written, so SSE subscribers
|
|
315
|
+
* that see the subsequently-published event get the up-to-date view (a
|
|
316
|
+
* read-your-writes guarantee). The RxJS stream-consumer pattern used by
|
|
317
|
+
* `Smelter`, `GraphDBConsumer`, and `Gatherer` can't provide that
|
|
318
|
+
* guarantee because it's fire-and-forget from the publisher's perspective.
|
|
319
|
+
* Both patterns solve "serialize work per resource" — see also
|
|
320
|
+
* `packages/core/src/serialize-per-key.ts` for the shared primitive.
|
|
321
|
+
*/
|
|
322
|
+
declare class ViewManager {
|
|
323
|
+
readonly materializer: ViewMaterializer;
|
|
324
|
+
private resourceChains;
|
|
325
|
+
private systemChains;
|
|
326
|
+
private static readonly SYSTEM_KEY;
|
|
327
|
+
constructor(viewStorage: ViewStorage, config: ViewManagerConfig, logger?: Logger);
|
|
328
|
+
/**
|
|
329
|
+
* Update resource view with a new event.
|
|
330
|
+
* Serialized per resource — see class doc.
|
|
331
|
+
*
|
|
332
|
+
* @param resourceId - Branded ResourceId (from @semiont/core)
|
|
333
|
+
* @param event - Resource event (from @semiont/core)
|
|
334
|
+
* @param getAllEvents - Function to retrieve all events for rebuild if needed
|
|
335
|
+
*/
|
|
336
|
+
materializeResource(resourceId: ResourceId, event: PersistedEvent, getAllEvents: () => Promise<StoredEvent[]>): Promise<void>;
|
|
337
|
+
/**
|
|
338
|
+
* Update system-level view (entity types + tag schemas today).
|
|
339
|
+
* Serialized through a shared chain — see class doc.
|
|
340
|
+
*/
|
|
341
|
+
materializeSystem(eventType: string, payload: any): Promise<void>;
|
|
342
|
+
/**
|
|
343
|
+
* Rebuild all materialized views from the event log on startup.
|
|
344
|
+
* Mirrors GraphDBConsumer.rebuildAll() — call this once during
|
|
345
|
+
* createKnowledgeBase before the HTTP server begins accepting requests.
|
|
346
|
+
* Idempotent: existing view files are overwritten.
|
|
347
|
+
*/
|
|
348
|
+
rebuildAll(eventLog: RebuildEventSource): Promise<void>;
|
|
349
|
+
/**
|
|
350
|
+
* Get resource view (builds from events if needed)
|
|
351
|
+
* @param resourceId - Branded ResourceId (from @semiont/core)
|
|
352
|
+
* @param events - Stored events for the resource (from @semiont/core)
|
|
353
|
+
* @returns Resource view or null if no events
|
|
354
|
+
*/
|
|
355
|
+
getOrMaterialize(resourceId: ResourceId, events: StoredEvent[]): Promise<ResourceView | null>;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* EventStore - Orchestration Layer
|
|
360
|
+
*
|
|
361
|
+
* Coordinates event sourcing operations:
|
|
8
362
|
* - EventLog: Event persistence (append, retrieve, query)
|
|
9
363
|
* - ViewManager: View materialization (resource and system)
|
|
10
|
-
* -
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
364
|
+
* - Core EventBus: Publishes StoredEvent to typed channels after persistence
|
|
365
|
+
*
|
|
366
|
+
* appendEvent() is the single write path:
|
|
367
|
+
* 1. Persist to EventLog
|
|
368
|
+
* 2. Materialize views
|
|
369
|
+
* 3. Enrich (optional callback — attach post-materialization data)
|
|
370
|
+
* 4. Publish StoredEvent to global and resource-scoped typed channels
|
|
371
|
+
*/
|
|
372
|
+
|
|
373
|
+
type EnrichEvent = (event: StoredEvent, resourceId: ResourceId) => Promise<StoredEvent>;
|
|
374
|
+
declare class EventStore {
|
|
375
|
+
readonly log: EventLog;
|
|
376
|
+
readonly views: ViewManager;
|
|
377
|
+
readonly viewStorage: ViewStorage;
|
|
378
|
+
readonly coreEventBus: EventBus;
|
|
379
|
+
private enrichEvent;
|
|
380
|
+
constructor(project: SemiontProject, stateDir: string, viewStorage: ViewStorage, coreEventBus: EventBus, logger?: Logger);
|
|
381
|
+
setEnrichEvent(fn: EnrichEvent): void;
|
|
382
|
+
/**
|
|
383
|
+
* Append an event to the store
|
|
384
|
+
* Coordinates: persistence → view → enrich → notification
|
|
385
|
+
*
|
|
386
|
+
* @param options.correlationId - Optional id propagated from a command.
|
|
387
|
+
*/
|
|
388
|
+
appendEvent(event: EventInput, options?: {
|
|
389
|
+
correlationId?: string;
|
|
390
|
+
}): Promise<StoredEvent>;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Event Store Factory
|
|
395
|
+
*
|
|
396
|
+
* Factory function for creating EventStore instances with standard configuration.
|
|
397
|
+
* This is the canonical way to instantiate an EventStore.
|
|
398
|
+
*/
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Create and initialize an EventStore instance
|
|
402
|
+
*
|
|
403
|
+
* @param project - SemiontProject instance
|
|
404
|
+
* @param eventBus - @semiont/core EventBus for publishing domain events
|
|
405
|
+
* @param logger - Optional logger for structured logging
|
|
406
|
+
* @returns Configured EventStore instance ready for use
|
|
407
|
+
*/
|
|
408
|
+
declare function createEventStore(project: SemiontProject, eventBus: EventBus, logger?: Logger): EventStore;
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Sharding Utilities
|
|
412
|
+
*
|
|
413
|
+
* Shared utilities for consistent sharding across all storage layers
|
|
414
|
+
* Uses Google's Jump Consistent Hash algorithm for even distribution
|
|
415
|
+
*/
|
|
416
|
+
/**
|
|
417
|
+
* TEMPORARY: Simple modulo-based hash sharding
|
|
418
|
+
*
|
|
419
|
+
* ⚠️ TODO: Replace with proper Jump Consistent Hash implementation
|
|
420
|
+
*
|
|
421
|
+
* This is a TEMPORARY implementation using simple modulo. It works and provides
|
|
422
|
+
* good distribution, but does NOT provide the minimal reshuffling property of
|
|
423
|
+
* Jump Consistent Hash when changing bucket counts.
|
|
424
|
+
*
|
|
425
|
+
* The proper implementation should use Google's Jump Consistent Hash algorithm:
|
|
426
|
+
* Reference: "A Fast, Minimal Memory, Consistent Hash Algorithm" by Lamping & Veach (2014)
|
|
427
|
+
* https://arxiv.org/abs/1406.2294
|
|
428
|
+
*
|
|
429
|
+
* Working implementations exist in npm packages like:
|
|
430
|
+
* - jumphash (https://www.npmjs.com/package/jumphash)
|
|
431
|
+
* - jump-gouache (https://github.com/bhoudu/jump-gouache)
|
|
432
|
+
*
|
|
433
|
+
* The algorithm requires proper 64-bit integer handling with BigInt to avoid
|
|
434
|
+
* precision loss in JavaScript. The previous attempt failed due to incorrect
|
|
435
|
+
* BigInt arithmetic in the while loop condition.
|
|
436
|
+
*
|
|
437
|
+
* Until replaced, this modulo approach will cause ALL data to be reshuffled
|
|
438
|
+
* if bucket count changes, rather than the optimal O(n/k) reshuffling that
|
|
439
|
+
* Jump Consistent Hash provides.
|
|
440
|
+
*
|
|
441
|
+
* @param key - The key to hash (typically a resource ID)
|
|
442
|
+
* @param numBuckets - Number of shards/buckets (default: 65536 for 4-hex sharding)
|
|
443
|
+
* @returns Shard number (0 to numBuckets-1)
|
|
444
|
+
*/
|
|
445
|
+
declare function jumpConsistentHash(key: string, numBuckets?: number): number;
|
|
446
|
+
/**
|
|
447
|
+
* Get 4-hex shard path for a key
|
|
448
|
+
*
|
|
449
|
+
* @param key - The key to hash (typically a resource ID)
|
|
450
|
+
* @param numBuckets - Number of shards (default: 65536)
|
|
451
|
+
* @returns Path segments like ['ab', 'cd']
|
|
452
|
+
*/
|
|
453
|
+
declare function getShardPath(key: string, numBuckets?: number): [string, string];
|
|
454
|
+
/**
|
|
455
|
+
* Calculate SHA-256 hash of data
|
|
456
|
+
*/
|
|
457
|
+
declare function sha256(data: string | object): string;
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Storage URI Index
|
|
461
|
+
*
|
|
462
|
+
* Projection that maps file:// URIs → resourceIds.
|
|
463
|
+
* Sharded at:
|
|
464
|
+
* {projectionsDir}/storage-uri/{ab}/{cd}/{uri-hash}.json
|
|
465
|
+
*
|
|
466
|
+
* where {ab}/{cd} comes from jumpConsistentHash(uri) and
|
|
467
|
+
* {uri-hash} is the SHA-256 of the URI string.
|
|
468
|
+
*
|
|
469
|
+
* Each file contains: { uri: string; resourceId: string }
|
|
470
|
+
*
|
|
471
|
+
* This index is maintained by ViewMaterializer (the single owner).
|
|
472
|
+
* It is never modified by Stower or other actors.
|
|
473
|
+
*
|
|
474
|
+
* Archive/unarchive do NOT remove entries from this index.
|
|
475
|
+
* Archived resources are marked in the ResourceView (archived: true)
|
|
476
|
+
* but remain findable by URI.
|
|
477
|
+
*/
|
|
478
|
+
interface StorageUriEntry {
|
|
479
|
+
uri: string;
|
|
480
|
+
resourceId: string;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Thrown when a URI is not found in the storage-uri index.
|
|
484
|
+
*/
|
|
485
|
+
declare class ResourceNotFoundError extends Error {
|
|
486
|
+
readonly uri: string;
|
|
487
|
+
constructor(uri: string);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Resolve a file:// URI to a resourceId using the storage-uri index.
|
|
491
|
+
*
|
|
492
|
+
* @param projectionsDir - Path to the projections directory
|
|
493
|
+
* @param uri - file:// URI (e.g. "file://docs/overview.md")
|
|
494
|
+
* @returns resourceId
|
|
495
|
+
* @throws ResourceNotFoundError if URI is not in the index
|
|
496
|
+
*/
|
|
497
|
+
declare function resolveStorageUri(projectionsDir: string, uri: string): Promise<string>;
|
|
498
|
+
/**
|
|
499
|
+
* Write a URI → resourceId mapping to the index.
|
|
500
|
+
*
|
|
501
|
+
* Called by ViewMaterializer when handling resource.created, resource.moved.
|
|
502
|
+
*
|
|
503
|
+
* @param projectionsDir - Path to the projections directory
|
|
504
|
+
* @param uri - file:// URI
|
|
505
|
+
* @param resourceId - resourceId to map to
|
|
506
|
+
*/
|
|
507
|
+
declare function writeStorageUriEntry(projectionsDir: string, uri: string, resourceId: string): Promise<void>;
|
|
508
|
+
/**
|
|
509
|
+
* Remove a URI entry from the index.
|
|
510
|
+
*
|
|
511
|
+
* Called by ViewMaterializer when handling resource.moved (old URI only).
|
|
512
|
+
* NOT called on resource.archived — archived resources retain their index entry.
|
|
513
|
+
*
|
|
514
|
+
* @param projectionsDir - Path to the projections directory
|
|
515
|
+
* @param uri - file:// URI to remove
|
|
516
|
+
*/
|
|
517
|
+
declare function removeStorageUriEntry(projectionsDir: string, uri: string): Promise<void>;
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Event Query - Read Operations
|
|
521
|
+
*
|
|
522
|
+
* Handles querying and reading events from storage:
|
|
523
|
+
* - Query events with filters (type, user, timestamp, sequence)
|
|
524
|
+
* - Get all events for a resource
|
|
525
|
+
* - Get last event from a file
|
|
526
|
+
* - Efficient streaming reads from JSONL files
|
|
527
|
+
*
|
|
528
|
+
* @see docs/EVENT-STORE.md#eventquery for architecture details
|
|
529
|
+
*/
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* EventQuery handles all read operations for events
|
|
533
|
+
* Uses EventStorage for file access, adds query filtering
|
|
534
|
+
*/
|
|
535
|
+
declare class EventQuery {
|
|
536
|
+
private eventStorage;
|
|
537
|
+
constructor(eventStorage: EventStorage);
|
|
538
|
+
/**
|
|
539
|
+
* Query events with filters
|
|
540
|
+
* Supports filtering by: userId, eventTypes, timestamps, sequence number, limit
|
|
541
|
+
*/
|
|
542
|
+
queryEvents(query: EventQuery$1): Promise<StoredEvent[]>;
|
|
543
|
+
/**
|
|
544
|
+
* Get all events for a specific resource (no filters)
|
|
545
|
+
*/
|
|
546
|
+
getResourceEvents(resourceId: ResourceId): Promise<StoredEvent[]>;
|
|
547
|
+
/**
|
|
548
|
+
* Get the last event from a specific file
|
|
549
|
+
* Useful for initializing sequence numbers and last hashes
|
|
550
|
+
*/
|
|
551
|
+
getLastEvent(resourceId: ResourceId, filename: string): Promise<StoredEvent | null>;
|
|
552
|
+
/**
|
|
553
|
+
* Get the latest event for a resource across all files
|
|
554
|
+
*/
|
|
555
|
+
getLatestEvent(resourceId: ResourceId): Promise<StoredEvent | null>;
|
|
556
|
+
/**
|
|
557
|
+
* Get event count for a resource
|
|
558
|
+
*/
|
|
559
|
+
getEventCount(resourceId: ResourceId): Promise<number>;
|
|
560
|
+
/**
|
|
561
|
+
* Check if a resource has any events
|
|
562
|
+
*/
|
|
563
|
+
hasEvents(resourceId: ResourceId): Promise<boolean>;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Projection reducers — the pure core of the system-level projection
|
|
568
|
+
* materializers in `view-materializer.ts`.
|
|
569
|
+
*
|
|
570
|
+
* Each reducer takes the current state of a `__system__` projection and
|
|
571
|
+
* a single command payload, and returns the next state plus any
|
|
572
|
+
* side-effect signals (currently just optional warnings). The
|
|
573
|
+
* surrounding I/O — read JSON file → reduce → write JSON file — lives
|
|
574
|
+
* in the `ViewMaterializer` shell.
|
|
575
|
+
*
|
|
576
|
+
* Why this split: tests for the projection-update semantics shouldn't
|
|
577
|
+
* need the filesystem or an Apple-container event store to assert
|
|
578
|
+
* "registering identical content twice is a no-op." The shell is
|
|
579
|
+
* already covered by integration tests at `tag-schemas-reader.test.ts`
|
|
580
|
+
* (Stower → materializer → reader round-trip) and
|
|
581
|
+
* `local-transport.test.ts` (real client → bus → cache invalidation).
|
|
582
|
+
*
|
|
583
|
+
* The reducers also become the natural home for the deferred schema-
|
|
584
|
+
* evolution work in `.plans/EVOLVE-TAG-SCHEMA.md` — migration
|
|
585
|
+
* commands (rename/remove a category, version-bump a schema id) are
|
|
586
|
+
* additional pure functions on the same view shapes.
|
|
587
|
+
*
|
|
588
|
+
* Load-bearing properties (sortedness, uniqueness, idempotence,
|
|
589
|
+
* most-recent-wins, no-mutation) are pinned by axiom-style fast-check
|
|
590
|
+
* tests in `__tests__/views/projection-reducers.test.ts`. See
|
|
591
|
+
* `docs/system/PROJECTION-PATTERN.md` for the full axiom catalog and
|
|
592
|
+
* the architectural narrative.
|
|
593
|
+
*/
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Apply a `frame:entity-type-added` event to the entity-types
|
|
597
|
+
* projection. Idempotent — dedup via a `Set` + locale-aware sort.
|
|
598
|
+
*
|
|
599
|
+
* Sort uses `localeCompare` to match {@link applyTagSchemaAdded}'s
|
|
600
|
+
* id-sort and to give a sensible ordering across mixed-case + symbol
|
|
601
|
+
* tags. (The pre-refactor materializer used `Array.sort()` with no
|
|
602
|
+
* comparator, which is codepoint order — `_x` would sort *after* `A`
|
|
603
|
+
* because `_` (0x5F) > `A` (0x41). Surfaced by an axiom test in
|
|
604
|
+
* `projection-reducers.test.ts`.)
|
|
605
|
+
*/
|
|
606
|
+
declare function applyEntityTypeAdded(current: readonly string[], add: string): string[];
|
|
607
|
+
/**
|
|
608
|
+
* Result of {@link applyTagSchemaAdded}.
|
|
609
|
+
*
|
|
610
|
+
* The reducer is pure — it doesn't log warnings itself. Instead it
|
|
611
|
+
* returns the would-be warning as data; the I/O shell decides whether
|
|
612
|
+
* to forward it to the logger. This keeps the function trivially
|
|
613
|
+
* testable and lets callers compose multiple reductions without
|
|
614
|
+
* threading a logger through.
|
|
615
|
+
*/
|
|
616
|
+
interface ApplyTagSchemaAddedResult {
|
|
617
|
+
/** The next state of the tagSchemas list, sorted by id. */
|
|
618
|
+
next: TagSchema[];
|
|
619
|
+
/**
|
|
620
|
+
* Set when an existing schema with the same id was overwritten with
|
|
621
|
+
* differing content (deep-not-equal). Identical re-registrations
|
|
622
|
+
* return `undefined` here — the projection silently no-ops.
|
|
623
|
+
*/
|
|
624
|
+
warning?: {
|
|
625
|
+
schemaId: string;
|
|
626
|
+
message: string;
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Apply a `frame:tag-schema-added` event to the tag-schemas projection.
|
|
631
|
+
*
|
|
632
|
+
* Most-recent-wins by `schema.id`:
|
|
633
|
+
* - new id → append + sort
|
|
634
|
+
* - existing id with identical content → no-op (warning undefined)
|
|
635
|
+
* - existing id with differing content → replace + warning emitted
|
|
636
|
+
*/
|
|
637
|
+
declare function applyTagSchemaAdded(current: readonly TagSchema[], add: TagSchema): ApplyTagSchemaAddedResult;
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Identifier utilities for event sourcing
|
|
641
|
+
*/
|
|
642
|
+
/**
|
|
643
|
+
* Generate a unique annotation ID (bare nanoid)
|
|
644
|
+
*
|
|
645
|
+
* @returns A bare annotation ID (e.g., "V1StGXR8_Z5jdHi6B-myT")
|
|
646
|
+
*/
|
|
647
|
+
declare function generateAnnotationId(): string;
|
|
648
|
+
|
|
649
|
+
export { EventLog, EventQuery, EventStorage, EventStore, FilesystemViewStorage, ResourceNotFoundError, ViewManager, ViewMaterializer, applyEntityTypeAdded, applyTagSchemaAdded, createEventStore, generateAnnotationId, getShardPath, jumpConsistentHash, removeStorageUriEntry, resolveStorageUri, sha256, writeStorageUriEntry };
|
|
650
|
+
export type { ApplyTagSchemaAddedResult, EnrichEvent, EventLogConfig, EventStorageConfig, ResourceView, StorageUriEntry, ViewManagerConfig, ViewStorage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@semiont/event-sourcing",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Event sourcing infrastructure for Semiont - EventLog, EventBus, and ViewManager",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"typecheck": "tsc --noEmit",
|
|
32
|
-
"build": "npm run typecheck && tsup && tsc -p tsconfig.build.json",
|
|
32
|
+
"build": "npm run typecheck && tsup && tsc -p tsconfig.build.json && rollup -c rollup.dts.config.mjs && rm -rf dist-types",
|
|
33
33
|
"watch": "tsup --watch",
|
|
34
34
|
"clean": "rm -rf dist *.tsbuildinfo",
|
|
35
35
|
"test": "vitest run",
|
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
"@types/uuid": "^10.0.0",
|
|
52
52
|
"@vitest/coverage-v8": "^4.1.0",
|
|
53
53
|
"fast-check": "^4.3.0",
|
|
54
|
+
"rollup": "^4.60.3",
|
|
55
|
+
"rollup-plugin-dts": "^6.4.1",
|
|
54
56
|
"tsup": "^8.5.1",
|
|
55
57
|
"typescript": "^6.0.2"
|
|
56
58
|
},
|