@structure-ai/eventsourcing-nisshi 0.0.15 → 0.1.1

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/README.md CHANGED
@@ -7,7 +7,7 @@ Implements **four** ports: `EventStore`, `SnapshotStore`, `CheckpointStore`, `In
7
7
  ## How it works
8
8
 
9
9
  - **Events** live in one single-partition topic (default `events`), key = stream name, value = a JSON envelope `{type, schemaVersion, version, payload, metadata}`. Infinite retention, no compaction — the topic *is* the raw history.
10
- - **Positions** are Kafka offsets + 1 — a true global total order (single partition).
10
+ - **Positions** are Kafka offsets + 1 — a true global total order (single Kafka partition; unrelated to the envelope's optional `partition` key, which `readAll({ partition })` filters on record by record, since the topic has no index on it).
11
11
  - **Optimistic concurrency** via the sidecar ledger: `append(stream, expectedVersion, events)` reserves `expectedVersion+1..n` in one SQL transaction (conditional UPDATE / unique INSERT), produces with `acks=all`, then confirms. A lost race fails with `ConcurrencyConflict` before anything is written. A crash between reservation and produce leaves pending rows that `drainPending` re-produces (at-least-once; readers dedupe by `(stream, version)`).
12
12
  - **Snapshots / checkpoints / inbox** are sidecar tables (pure cache and dedupe state; losing them costs performance, never correctness).
13
13
  - **Protocol**: an in-package minimal Kafka wire client (no runtime dependencies) pinned to non-flexible API versions — `Produce` v3, `Fetch` v4, `Metadata` v0, `CreateTopics` v4 — negotiated and verified at connect. No consumer groups, no transactions: reads are positioned fetches, progress tracking is the sidecar checkpoint.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@structure-ai/eventsourcing-nisshi",
3
- "version": "0.0.15",
3
+ "version": "0.1.1",
4
4
  "description": "Nisshi (Kafka-API broker) event store with a SQL sidecar for optimistic concurrency, snapshots, checkpoints, and inbox dedupe.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -28,8 +28,8 @@
28
28
  "@effect/sql": "^0.52.1",
29
29
  "@effect/sql-sqlite-bun": "^0.53.0",
30
30
  "@effect/sql-pg": "^0.53.0",
31
- "@structure-ai/domain": "0.0.15",
32
- "@structure-ai/eventsourcing": "0.0.15"
31
+ "@structure-ai/domain": "0.1.1",
32
+ "@structure-ai/eventsourcing": "0.1.1"
33
33
  },
34
34
  "devDependencies": {
35
35
  "typescript": "^5.9.2",
package/src/EventStore.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  type AppendResult,
7
7
  EventStore,
8
8
  type EventStoreService,
9
+ readAllPartitions,
9
10
  type StoredEvent,
10
11
  type StoredEventMetadata,
11
12
  } from "@structure-ai/eventsourcing";
@@ -302,6 +303,14 @@ const make = (
302
303
  const events: StoredEvent[] = [];
303
304
  let offset = (readOptions?.fromPosition ?? 1n) - 1n;
304
305
  const limit = readOptions?.batchSize;
306
+ // The topic has no index on the envelope: the filter runs on
307
+ // every fetched record, and paging keeps fetching until
308
+ // `limit` MATCHING events were collected or the topic ends.
309
+ const partitions = readAllPartitions(readOptions?.partition);
310
+ const keep = (event: StoredEvent): boolean =>
311
+ partitions === undefined ||
312
+ (event.metadata.partition !== undefined &&
313
+ partitions.includes(event.metadata.partition));
305
314
  for (;;) {
306
315
  if (limit !== undefined && events.length >= limit) {
307
316
  break;
@@ -312,9 +321,14 @@ const make = (
312
321
  }
313
322
  for (const record of page.records) {
314
323
  const streamName = record.key === null ? "" : new TextDecoder().decode(record.key);
315
- events.push(
316
- storedEvent(record.offset, streamName, decodeWireEvent(record.value, streamName)),
324
+ const event = storedEvent(
325
+ record.offset,
326
+ streamName,
327
+ decodeWireEvent(record.value, streamName),
317
328
  );
329
+ if (keep(event)) {
330
+ events.push(event);
331
+ }
318
332
  }
319
333
  const last = page.records[page.records.length - 1];
320
334
  if (last === undefined || last.offset + 1n >= page.highWatermark) {