@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 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
- * @semiont/event-sourcing
6
+ * View Storage - Materialized Views
3
7
  *
4
- * Event sourcing infrastructure for Semiont
8
+ * Stores materialized views of resource state and annotations
9
+ * Built from event streams, can be rebuilt at any time
5
10
  *
6
- * Provides:
7
- * - EventStore: Orchestration layer for event sourcing
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
- * - ViewStorage: Interface and filesystem implementation for materialized views
11
- */
12
- export { EventStore, type EnrichEvent } from './event-store';
13
- export { createEventStore } from './event-store-factory';
14
- export { EventLog, type EventLogConfig } from './event-log';
15
- export { ViewManager, type ViewManagerConfig } from './view-manager';
16
- export { type EventStorageConfig } from './storage/event-storage';
17
- export { EventStorage } from './storage/event-storage';
18
- export { type ViewStorage, type ResourceView, FilesystemViewStorage, } from './storage/view-storage';
19
- export { getShardPath, sha256, jumpConsistentHash } from './storage/shard-utils';
20
- export { resolveStorageUri, writeStorageUriEntry, removeStorageUriEntry, ResourceNotFoundError, type StorageUriEntry, } from './storage/storage-uri-index';
21
- export { EventQuery } from './query/event-query';
22
- export { ViewMaterializer } from './views/view-materializer';
23
- export { applyEntityTypeAdded, applyTagSchemaAdded, type ApplyTagSchemaAddedResult, } from './views/projection-reducers';
24
- export { generateAnnotationId } from './identifier-utils';
25
- //# sourceMappingURL=index.d.ts.map
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.4",
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
  },