@structure-ai/eventsourcing-nisshi 0.0.15 → 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/README.md +1 -1
- package/package.json +3 -3
- package/src/EventStore.ts +16 -2
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
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
|
32
|
-
"@structure-ai/eventsourcing": "0.0
|
|
31
|
+
"@structure-ai/domain": "0.1.0",
|
|
32
|
+
"@structure-ai/eventsourcing": "0.1.0"
|
|
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
|
-
|
|
316
|
-
|
|
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) {
|