@godspeedai/cognate-projections 0.1.0 → 0.1.2

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.
Files changed (2) hide show
  1. package/README.md +70 -19
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,21 +1,72 @@
1
1
  # @godspeedai/cognate-projections
2
2
 
3
- Checkpointed, idempotent, rebuildable projections over the durable event log (spec §11).
4
-
5
- `ProjectionRunner` reads committed events in `position` order in batches, applies a
6
- `Projection`, and commits each batch's derived writes together with its checkpoint via
7
- one `store.commitProjection` (CAS on `fromPosition`). On an apply failure it commits the
8
- successful prefix, then records the error at the failing event via `failProjection` and
9
- returns `status: "error"` — the failing event is never skipped.
10
-
11
- - `catchUp()` — process new events; returns the stored error, unmodified, while a prior
12
- error is unresolved.
13
- - `retry()` — clears an error by reattempting from the checkpoint.
14
- - `rebuild()` — resets derived state and the checkpoint, then replays from position 0.
15
- - A stored checkpoint version that differs from the projection's own version yields
16
- `rebuild_required` and is never reinterpreted.
17
- - `start()` / `stop()` — live mode, following `store.onCommitted`; runs are serialized.
18
- - `waitFor(position)` — resolves once the checkpoint reaches `position`.
19
-
20
- Concurrent runners racing on the same projection resolve via `CheckpointConflictError`:
21
- the loser re-reads the checkpoint and continues without double-applying.
3
+ Checkpointed, idempotent, rebuildable projections over the Cognate durable event log. A projection is a deterministic derivation from events; `ProjectionRunner` keeps its derived state and its checkpoint in sync, batch by batch, and never skips a failing event.
4
+
5
+ **When to use this package:** you want a read model or index derived from committed events — counts, listings, per-tenant views — that survives restarts, can be rebuilt from scratch, and stays consistent under concurrent writers. The
6
+ [`@godspeedai/cognate-runtime-bun`](https://www.npmjs.com/package/@godspeedai/cognate-runtime-bun)
7
+ runtime uses this package to keep registered projections current; you can also run a `ProjectionRunner` directly over any
8
+ [`@godspeedai/cognate-events`](https://www.npmjs.com/package/@godspeedai/cognate-events) store.
9
+
10
+ ## Installation
11
+
12
+ ```sh
13
+ bun add @godspeedai/cognate-projections
14
+ ```
15
+
16
+ Requires [Bun](https://bun.sh) >= 1.4.0. Needs a store implementing the
17
+ [`@godspeedai/cognate-events`](https://www.npmjs.com/package/@godspeedai/cognate-events) `DurableStore`
18
+ contract — for example [`@godspeedai/cognate-store-sqlite`](https://www.npmjs.com/package/@godspeedai/cognate-store-sqlite)
19
+ or [`@godspeedai/cognate-store-memory`](https://www.npmjs.com/package/@godspeedai/cognate-store-memory).
20
+
21
+ ## Quick start
22
+
23
+ ```ts
24
+ import { ProjectionRunner } from "@godspeedai/cognate-projections";
25
+ import type { Projection } from "@godspeedai/cognate-projections";
26
+
27
+ const ledger: Projection = {
28
+ name: "ledger",
29
+ version: "1", // bump to force a rebuild rather than reinterpret old state
30
+ apply(event, state) {
31
+ const count = (state.get(`count:${event.eventType}`) as number | undefined) ?? 0;
32
+ const total = (state.get("total") as number | undefined) ?? 0;
33
+ const amount = (event.payload as { amount?: number }).amount ?? 0;
34
+ return [
35
+ { key: `count:${event.eventType}`, value: count + 1 },
36
+ { key: "total", value: total + amount },
37
+ ];
38
+ },
39
+ };
40
+
41
+ const runner = new ProjectionRunner(store, ledger);
42
+ const result = await runner.catchUp();
43
+ // { status: "ok", position: <last applied store position>, applied: 3 }
44
+
45
+ const total = await store.readProjection("ledger", "total");
46
+ ```
47
+
48
+ `apply` returns the keys it wants to write (`value: null` deletes a key) and may read state written earlier in the same batch through `state.get`.
49
+
50
+ ## How the runner behaves
51
+
52
+ - **Checkpointed and idempotent.** Each batch's writes and its checkpoint commit atomically through one `commitProjection` call, guarded by the previous position. Running `catchUp()` twice applies each event exactly once.
53
+ - **Failures stop the world — correctly.** On an apply error the runner commits the successful prefix, records the error at the failing event, and returns `{ status: "error", ... }`. `catchUp()` keeps returning that stored error until you clear it with `retry()`, which reattempts from the checkpoint. The failing event is never skipped.
54
+ - **Version changes require rebuilds.** A stored checkpoint whose version differs from the projection's own yields `{ status: "rebuild_required" }`; `rebuild()` deletes the derived state and replays from position 0.
55
+ - **Live mode.** `start()` follows committed events as they happen; `stop()` unsubscribes after the in-flight run finishes. All runs — live, `catchUp`, `retry`, `rebuild` — are serialized so they never overlap. `waitFor(position)` resolves once the checkpoint reaches a given store position.
56
+ - **Concurrent runners are safe.** Two runners racing on one projection resolve through the store's checkpoint conflict: the loser re-reads the checkpoint and continues from it without double-applying.
57
+
58
+ ## Limitations
59
+
60
+ - Projections are derived state: nothing here is authoritative, and `rebuild()` will happily delete and recompute everything. Never store the only copy of data in a projection.
61
+ - `apply` must be deterministic given the event and the state — no clock, randomness, or network. Record nondeterministic inputs as events first.
62
+
63
+ ## Related packages
64
+
65
+ - [`@godspeedai/cognate-events`](https://www.npmjs.com/package/@godspeedai/cognate-events) — the event log and checkpoint API this package builds on.
66
+ - [`@godspeedai/cognate-store-sqlite`](https://www.npmjs.com/package/@godspeedai/cognate-store-sqlite) — the durable store to project over.
67
+ - [`@godspeedai/cognate-runtime-bun`](https://www.npmjs.com/package/@godspeedai/cognate-runtime-bun) — keeps registered projections current automatically; public ones are readable through the service and the client.
68
+ - [`@godspeedai/cognate`](https://www.npmjs.com/package/@godspeedai/cognate) — the SDK umbrella.
69
+
70
+ ## License
71
+
72
+ Apache-2.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@godspeedai/cognate-projections",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Checkpointed, idempotent, rebuildable projections over the durable event log (spec §11).",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -22,7 +22,7 @@
22
22
  ".": "./src/index.ts"
23
23
  },
24
24
  "dependencies": {
25
- "@godspeedai/cognate-events": "^0.1.0"
25
+ "@godspeedai/cognate-events": "^0.1.2"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@godspeedai/cognate-store-memory": "workspace:*",