@kairos-es/read 0.0.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.
Files changed (82) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +538 -0
  3. package/dist/cjs/EventLogDurability.js +184 -0
  4. package/dist/cjs/EventLogDurability.js.map +1 -0
  5. package/dist/cjs/ProjectionRunner.js +478 -0
  6. package/dist/cjs/ProjectionRunner.js.map +1 -0
  7. package/dist/cjs/ProjectionStore.js +233 -0
  8. package/dist/cjs/ProjectionStore.js.map +1 -0
  9. package/dist/cjs/foldIntoRef.js +36 -0
  10. package/dist/cjs/foldIntoRef.js.map +1 -0
  11. package/dist/cjs/inMemoryProjectionStore.js +138 -0
  12. package/dist/cjs/inMemoryProjectionStore.js.map +1 -0
  13. package/dist/cjs/index.js +128 -0
  14. package/dist/cjs/index.js.map +1 -0
  15. package/dist/cjs/projectionWiringFault.js +532 -0
  16. package/dist/cjs/projectionWiringFault.js.map +1 -0
  17. package/dist/cjs/runProjection.js +117 -0
  18. package/dist/cjs/runProjection.js.map +1 -0
  19. package/dist/cjs/runProjections.js +144 -0
  20. package/dist/cjs/runProjections.js.map +1 -0
  21. package/dist/cjs/superviseOnProgress.js +580 -0
  22. package/dist/cjs/superviseOnProgress.js.map +1 -0
  23. package/dist/cjs/testing.js +143 -0
  24. package/dist/cjs/testing.js.map +1 -0
  25. package/dist/dts/EventLogDurability.d.ts +182 -0
  26. package/dist/dts/EventLogDurability.d.ts.map +1 -0
  27. package/dist/dts/ProjectionRunner.d.ts +557 -0
  28. package/dist/dts/ProjectionRunner.d.ts.map +1 -0
  29. package/dist/dts/ProjectionStore.d.ts +475 -0
  30. package/dist/dts/ProjectionStore.d.ts.map +1 -0
  31. package/dist/dts/foldIntoRef.d.ts +39 -0
  32. package/dist/dts/foldIntoRef.d.ts.map +1 -0
  33. package/dist/dts/inMemoryProjectionStore.d.ts +11 -0
  34. package/dist/dts/inMemoryProjectionStore.d.ts.map +1 -0
  35. package/dist/dts/index.d.ts +185 -0
  36. package/dist/dts/index.d.ts.map +1 -0
  37. package/dist/dts/projectionWiringFault.d.ts +260 -0
  38. package/dist/dts/projectionWiringFault.d.ts.map +1 -0
  39. package/dist/dts/runProjection.d.ts +185 -0
  40. package/dist/dts/runProjection.d.ts.map +1 -0
  41. package/dist/dts/runProjections.d.ts +480 -0
  42. package/dist/dts/runProjections.d.ts.map +1 -0
  43. package/dist/dts/superviseOnProgress.d.ts +587 -0
  44. package/dist/dts/superviseOnProgress.d.ts.map +1 -0
  45. package/dist/dts/testing.d.ts +207 -0
  46. package/dist/dts/testing.d.ts.map +1 -0
  47. package/dist/esm/EventLogDurability.js +175 -0
  48. package/dist/esm/EventLogDurability.js.map +1 -0
  49. package/dist/esm/ProjectionRunner.js +468 -0
  50. package/dist/esm/ProjectionRunner.js.map +1 -0
  51. package/dist/esm/ProjectionStore.js +223 -0
  52. package/dist/esm/ProjectionStore.js.map +1 -0
  53. package/dist/esm/foldIntoRef.js +29 -0
  54. package/dist/esm/foldIntoRef.js.map +1 -0
  55. package/dist/esm/inMemoryProjectionStore.js +131 -0
  56. package/dist/esm/inMemoryProjectionStore.js.map +1 -0
  57. package/dist/esm/index.js +185 -0
  58. package/dist/esm/index.js.map +1 -0
  59. package/dist/esm/package.json +4 -0
  60. package/dist/esm/projectionWiringFault.js +524 -0
  61. package/dist/esm/projectionWiringFault.js.map +1 -0
  62. package/dist/esm/runProjection.js +109 -0
  63. package/dist/esm/runProjection.js.map +1 -0
  64. package/dist/esm/runProjections.js +137 -0
  65. package/dist/esm/runProjections.js.map +1 -0
  66. package/dist/esm/superviseOnProgress.js +571 -0
  67. package/dist/esm/superviseOnProgress.js.map +1 -0
  68. package/dist/esm/testing.js +133 -0
  69. package/dist/esm/testing.js.map +1 -0
  70. package/package.json +41 -0
  71. package/src/EventLogDurability.ts +201 -0
  72. package/src/ProjectionRunner.ts +923 -0
  73. package/src/ProjectionStore.ts +528 -0
  74. package/src/foldIntoRef.ts +63 -0
  75. package/src/inMemoryProjectionStore.ts +163 -0
  76. package/src/index.ts +218 -0
  77. package/src/projectionWiringFault.ts +694 -0
  78. package/src/runProjection.ts +270 -0
  79. package/src/runProjections.ts +623 -0
  80. package/src/superviseOnProgress.ts +897 -0
  81. package/src/testing.ts +290 -0
  82. package/testing/package.json +6 -0
@@ -0,0 +1,163 @@
1
+ /**
2
+ * The in-memory `ProjectionStore`: a `Ref` of checkpoints plus the exclusive
3
+ * critical section that makes the guarded advance atomic.
4
+ *
5
+ * This is NOT merely a test double. It is load-bearing for the in-process
6
+ * supervision restart, where the subscription dies (the event store's poll retry
7
+ * exhausted) but the process and its in-memory view survive: the restart must
8
+ * resume from the checkpoint rather than replay from `ORIGIN` into an
9
+ * already-populated view, and an ephemeral checkpoint stored beside an ephemeral
10
+ * view is exactly what makes that resume correct. It is also the whole
11
+ * all-in-memory permutation — tests, prototypes, client-side use — and the
12
+ * durable-log-with-in-memory-view mode's view store (dev, admin and in-flux
13
+ * slices, rebuilt from `ORIGIN` each boot).
14
+ *
15
+ * A factory `Effect`, deliberately not a `Layer`: the view store is selected PER
16
+ * MATERIALISATION — so a slice may start in memory and GRADUATE to Postgres, which
17
+ * `ReadModel.store` owns the claim about — and it is therefore a value handed to a
18
+ * read model, not a context tag that one store could claim process-wide.
19
+ *
20
+ * That selection is a FLOOR rather than a ceiling, and this store is on the common
21
+ * side of it: the two variants above are also available AT ONCE, an in-memory
22
+ * materialisation of a slice record beside a durable one from the SAME record —
23
+ * a Postgres table somebody queries with SQL, and an in-process view of the same
24
+ * slices holding one aggregate's hot figures with no round trip to reach them. That
25
+ * is `runProjections`, and every call of this factory is an independent store, so
26
+ * two materialisations wired that way share nothing but their slices. What they do
27
+ * not share is their FOLDS: each `apply` re-expresses one, so agreement between
28
+ * their figures is something a test demonstrates rather than something the wiring
29
+ * gives.
30
+ *
31
+ * The critical-section design mirrors `core`'s in-memory event store: one
32
+ * `Effect.makeSemaphore(1)` serialises the guarded advance, exactly as that store
33
+ * serialises check-then-append, and for the same reason — the check and the write
34
+ * must not be separable, and that atomicity IS the concurrency guarantee.
35
+ */
36
+ import { ORIGIN, type Position } from '@kairos-es/core'
37
+ import { Effect, Ref } from 'effect'
38
+ import type { CheckpointKey, ProjectionStore } from './ProjectionStore'
39
+ import { CheckpointSuperseded } from './ProjectionStore'
40
+
41
+ /**
42
+ * Flatten a `CheckpointKey` into a map key.
43
+ *
44
+ * The separator is NUL. A `ProjectionId`/`PartitionId` is any non-empty string,
45
+ * so a printable separator (`:`, `/`, `|`) could be forged inside either half and
46
+ * two distinct keys would collide onto one checkpoint — one read model silently
47
+ * reading and advancing another's cursor. NUL cannot appear in a name any human
48
+ * or config file produces, so the flattening stays injective in practice without
49
+ * narrowing the id grammar.
50
+ *
51
+ * The other route to that same failure is a WIRING rather than a forged id: two
52
+ * materialisations handed this one store under equal keys, which would be two
53
+ * runners over one cursor. That one is caught before anything is forked, by the
54
+ * set-level collision rung of the construction gate in `projectionWiringFault.ts`,
55
+ * and its sentence names the two fixes. Nothing here can catch it — a store sees
56
+ * keys, never who is holding it.
57
+ */
58
+ const mapKey = (key: CheckpointKey): string =>
59
+ `${key.projection}\u0000${key.partition}`
60
+
61
+ /**
62
+ * Build an in-memory `ProjectionStore`.
63
+ *
64
+ * Every call is an independent store with its own checkpoints and its own
65
+ * semaphore, so two read models wired to two calls of this factory cannot
66
+ * interfere — and a test needing a fresh store just calls it again.
67
+ */
68
+ export const makeInMemoryProjectionStore: Effect.Effect<ProjectionStore> =
69
+ Effect.gen(function* () {
70
+ const checkpoints = yield* Ref.make<ReadonlyMap<string, Position>>(
71
+ new Map(),
72
+ )
73
+ const mutex = yield* Effect.makeSemaphore(1)
74
+
75
+ /** The stored position, or `ORIGIN` for a key never committed. */
76
+ const positionOf = (
77
+ stored: ReadonlyMap<string, Position>,
78
+ key: CheckpointKey,
79
+ ): Position => stored.get(mapKey(key)) ?? ORIGIN
80
+
81
+ const readCheckpoint = (
82
+ key: CheckpointKey,
83
+ ): Effect.Effect<Position, never> =>
84
+ // No lock needed: `Ref.get` is a single atomic read, and the guard that
85
+ // actually protects a commit is re-checked INSIDE the critical section
86
+ // below — a value read here is only ever an expectation to be guarded on,
87
+ // never a licence to write.
88
+ Effect.map(Ref.get(checkpoints), (stored) => positionOf(stored, key))
89
+
90
+ const commit = <E, R>(args: {
91
+ readonly key: CheckpointKey
92
+ readonly expected: Position
93
+ readonly next: Position
94
+ readonly viewWrites: Effect.Effect<void, E, R>
95
+ }): Effect.Effect<void, E | CheckpointSuperseded, R> =>
96
+ // The permit is acquired INTERRUPTIBLY (a fibre waiting its turn can still
97
+ // be torn down), and only the section itself is uninterruptible.
98
+ mutex.withPermits(1)(
99
+ Effect.uninterruptible(
100
+ Effect.gen(function* () {
101
+ const stored = positionOf(yield* Ref.get(checkpoints), args.key)
102
+
103
+ // The guard is checked FIRST, before the view writes run. There is no
104
+ // transaction here to roll anything back, so the ONLY way a lost
105
+ // guard can leave the view untouched is to lose before touching it.
106
+ // Check-then-act is safe despite `viewWrites` suspending in between,
107
+ // because the section is exclusive: no other fibre can observe or
108
+ // advance this checkpoint until the permit is released, so the value
109
+ // read here cannot go stale within the section.
110
+ //
111
+ // On a lost guard `viewWrites` was NEVER RUN, which is observably
112
+ // identical to the SQL store's rollback — the port's abort contract
113
+ // is met by omission rather than by undo. (The one case omission
114
+ // cannot cover is a `viewWrites` that fails part-way, which is why
115
+ // the port requires a non-transactional store's per-batch write to be
116
+ // a single atomic effect; see `ProjectionStore` and `foldIntoRef`.)
117
+ if (stored !== args.expected) {
118
+ return yield* new CheckpointSuperseded({
119
+ key: args.key,
120
+ expected: args.expected,
121
+ })
122
+ }
123
+
124
+ yield* args.viewWrites
125
+
126
+ // Uninterruptibility earns its keep here: an interrupt landing
127
+ // between the view writes and this advance would commit the batch's
128
+ // effects with the cursor left behind, and the next run would apply
129
+ // the same batch a second time. Copy-on-write so a concurrent reader
130
+ // holding the previous map sees a consistent snapshot.
131
+ yield* Ref.update(checkpoints, (current) =>
132
+ new Map(current).set(mapKey(args.key), args.next),
133
+ )
134
+ }),
135
+ ),
136
+ )
137
+
138
+ const resetCheckpoint = (key: CheckpointKey): Effect.Effect<void> =>
139
+ // Under the same permit as `commit`, so a reset can never interleave with a
140
+ // guarded advance and leave the map half-updated relative to the view.
141
+ // Deleting rather than storing `ORIGIN` keeps "never committed" and "reset"
142
+ // one state, which is what the port promises the read-back is.
143
+ mutex.withPermits(1)(
144
+ Ref.update(checkpoints, (current) => {
145
+ const next = new Map(current)
146
+ next.delete(mapKey(key))
147
+ return next
148
+ }),
149
+ )
150
+
151
+ // Annotated at the definition site rather than only through the exported
152
+ // signature, per the rule on `ProjectionStore` itself.
153
+ const store: ProjectionStore = {
154
+ // Dies with the process, which is the R2 input: an ephemeral view is legal
155
+ // over either an ephemeral or a durable log (it just re-catches-up), so
156
+ // this value never trips the durability-ordering check.
157
+ durability: 'ephemeral',
158
+ readCheckpoint,
159
+ commit,
160
+ resetCheckpoint,
161
+ }
162
+ return store
163
+ })
package/src/index.ts ADDED
@@ -0,0 +1,218 @@
1
+ /**
2
+ * @kairos-es/read — the read side: a query-driven projection runner over the
3
+ * store's `subscribe` contract, and a scalar checkpoint committed atomically with
4
+ * the read model's own writes (ADR-0007).
5
+ *
6
+ * Platform-neutral by design: peers are `effect` plus `@kairos-es/core` and
7
+ * `@kairos-es/codec` only — the same peer shape as the codec — so the
8
+ * all-in-memory permutation, a property of one MATERIALISATION's (log, view)
9
+ * pairing and including client-side use, needs no database
10
+ * dependency. Every dependency-bearing backend is its own package, which is why
11
+ * the SQL `ProjectionStore` lives in `@kairos-es/read-postgres` rather than behind
12
+ * a sub-entry here: a sub-entry shares one dependency surface, whereas a separate
13
+ * package isolates a new one (`@effect/sql`).
14
+ *
15
+ * ## Where each rationale lives
16
+ *
17
+ * This module re-exports; it does not argue. Every argument the read side rests
18
+ * on is written ONCE, at the definition site a reader would reach for anyway,
19
+ * because a rationale kept in N places is a rationale a correction has to find N
20
+ * times. What follows is the index, R1 and R2 included: each of those two has a
21
+ * module that owns it, so each gets a pointer here like everything else. R3 is the
22
+ * one exception, and the last section says why.
23
+ *
24
+ * - **The log seam is `DcbEventStore.subscribe`, and it belongs entirely to the
25
+ * event store.** `store-postgres` owns LISTEN/NOTIFY as a wake source over
26
+ * `core`'s shared poll machine, `store-sqlite` owns no wake source and rides
27
+ * that poll alone, and `core`'s in-memory layer owns its append `PubSub` as a
28
+ * wake source over that same machine; all three sit behind the
29
+ * identical `subscribe(query, after)` contract, so a projection never learns
30
+ * which it is running against. Nothing in this package adds to that contract —
31
+ * the one claim below that no other module is placed to make.
32
+ * - **The view seam is `ProjectionStore`** — one port rather than two, `commit`
33
+ * taking the view writes as an ARGUMENT, `CheckpointSuperseded` as an error
34
+ * rather than a returned outcome, and `ProjectionStoreError` inverting the store
35
+ * contract's defect rule. `ProjectionStore.ts`'s module doc. The multi-key port
36
+ * against the keyed façade a read model is handed is on `KeyedProjectionStore`
37
+ * and on `forKey` in the same module; the single-atomic-effect requirement a
38
+ * NON-TRANSACTIONAL store's caller must honour is on the `ProjectionStore`
39
+ * interface itself, and `foldIntoRef.ts` is that shape.
40
+ * - **R1, atomicity** — a read model's checkpoint lives WITH its view, in a store
41
+ * where the two can be committed atomically, which is what `commit` is. The
42
+ * argument is on the `ProjectionStore` interface in `ProjectionStore.ts` (R1 is
43
+ * what that interface IS), together with what a view offering no transaction at
44
+ * all falls back to: at-least-once, with POSITION-KEYED idempotent writes, a
45
+ * recommendation because the port cannot check it.
46
+ * - **R2, durability ordering** — checkpoint durability must not EXCEED event-log
47
+ * durability, so a DURABLE view over an EPHEMERAL log is a defect rejected at
48
+ * construction rather than a view that silently freezes. The view store declares
49
+ * its half on the `ProjectionStore` it implements; the log's half, the silent
50
+ * failure the rule prevents, and why the log's class is ONE context tag beside
51
+ * the store layer rather than a field per read model are all in
52
+ * `EventLogDurability.ts`.
53
+ * - **How these errors RENDER themselves** — why `ProjectionStoreError` derives its
54
+ * `message` from `reason` and `CheckpointSuperseded` derives one from `expected`
55
+ * (including the `bigint` argument that once kept the field empty and why it was a
56
+ * category error), how far filling that field reaches (every reader of the error,
57
+ * not one log line) and what it leaves untouched: the two classes in
58
+ * `ProjectionStore.ts`. `PipelineDied` in `ProjectionRunner.ts` and
59
+ * `ProjectionStalled` in `superviseOnProgress.ts` are the other two and point
60
+ * there.
61
+ * - **Why the view store is a VALUE and not a `Context.Tag`**, and what a slice's
62
+ * graduation from memory to Postgres does and does not change: `ReadModel.store`
63
+ * in `runProjection.ts`. It is demonstrated rather than asserted, by
64
+ * `examples/course-subscriptions`'s `src/courseRosterSql.ts` and by
65
+ * `@kairos-es/read-postgres`'s `test/courseRosterGraduation.test.ts`.
66
+ * - **The in-memory implementation, and why it is not merely a test double:**
67
+ * `inMemoryProjectionStore.ts`.
68
+ * - **The runner** — the pipeline stage by stage, and why supervision lives next
69
+ * door: `ProjectionRunner.ts`, which holds the SUBSTRATE both entry points sit on
70
+ * and no entry point of its own. Its two option sets are documented on
71
+ * `ProjectionPipelineOptions` there and on `ProjectionSupervisionOptions` in
72
+ * `superviseOnProgress.ts`; `ProjectionRunnerOptions` says why their union is
73
+ * FLAT and why every figure is bounded rather than merely documented.
74
+ * - **ONE materialisation of one read model** — `runProjection` and the
75
+ * `projectionLayer` over it: `runProjection.ts`, which also says why the singular
76
+ * and the plural entry points are two modules over one substrate and why neither
77
+ * calls the other.
78
+ * - **N MATERIALISATIONS of one read model** — one slice record and one
79
+ * `ProjectionId` for the call, one view store and one runner each, which is what
80
+ * `runProjections` is: `runProjections.ts`. It carries the guarantee at exactly
81
+ * its strength (one query, one decode, N applies — never "they cannot drift",
82
+ * since a SQL `apply` re-implements the fold and agreement between materialised
83
+ * figures is empirical), why the guarantee is construction-scoped, why each entry
84
+ * hands over an UNBOUND `ProjectionStore` while the identity is named once for the
85
+ * call, why the tuning and the two supervision hooks are PER ENTRY, and why there
86
+ * is no `projectionsLayer`. The wiring that is its OWN — two materialisations
87
+ * sharing one store under one resolved `CheckpointKey` — is the set-level rung of
88
+ * the construction gate below, with the two bounds it deliberately does not reach;
89
+ * the gate's other four run in the same pass — two per entry, two once over the
90
+ * shared query and slice record — so no refused call forks anything, whichever
91
+ * rung and whichever entry it was.
92
+ * - **The supervisor** — retry while an externally observed signal moves, give up
93
+ * when it stops; why a `CheckpointSuperseded` loser resynchronises instead of
94
+ * tripping the breaker; why the two hooks are the primary observability channel
95
+ * and the log lines a thin lossy default; why it holds no rendering of a
96
+ * caller's `E` at all, the label being the fault's own `message`; and the
97
+ * wall-clock arithmetic behind the no-progress budget: `superviseOnProgress.ts`.
98
+ * `superviseOnProgress` ITSELF is package-internal and deliberately not exported
99
+ * below — it is the projection runner's supervisor, not a general combinator, and
100
+ * that module records both why and what generalising it would cost. Three
101
+ * symbols from it are exported, each because the PUBLIC surface names it:
102
+ * `ProjectionStalled` is `ProjectionRunner.fibre`'s error channel,
103
+ * `ProjectionRunnerOptions` extends `ProjectionSupervisionOptions`, and tuning
104
+ * the no-progress budget means calling `MaxNoProgressRestarts.make(n)`.
105
+ * - **One writer per MATERIALISATION, and why one read model's stream is never
106
+ * sharded:** ADR-0007, with the partition dimension's own half on `PartitionId`.
107
+ * Per materialisation rather than per read model, because a read model may have
108
+ * several — N of them are N single writers, never shards of one stream.
109
+ * Where apply THROUGHPUT rather than isolation is the constraint, ADR-0007 also
110
+ * carries the escape hatch — coalesce a batch per target row inside the one
111
+ * commit — and there is no library symbol for it, because it is a fold the read
112
+ * model performs over its own batch.
113
+ * - **The tagless-slice rule** — every read-model slice carries at least one tag
114
+ * (a `system:…` tag for a genuinely global slice), enforced at construction by
115
+ * two checks that cannot stand in for each other, since a lone tagless slice
116
+ * composes to a query the store WOULD serve: both are rungs of the wiring gate
117
+ * `projectionWiringFault.ts`, which argues the rules and the order they are judged
118
+ * in, and which is consulted from the one `prepareProjection` phase both runner
119
+ * entry points run before either forks anything.
120
+ * - **Running a read model in a test** — the `@kairos-es/read/testing` subpath
121
+ * (`testing.ts`), which also states its boundary against
122
+ * `@kairos-es/projection-store-contract-tests`.
123
+ * - **The narrative version of all of it** is this package's `README.md`; the
124
+ * decision it implements is ADR-0007.
125
+ *
126
+ * ## R3, the legal permutations, and several materialisations of one read model
127
+ *
128
+ * The one thing this file ARGUES rather than indexes. R1 and R2 each have a module
129
+ * that owns them, pointed at above; R3 has none, because it is a property of how
130
+ * read models are composed into a SUBSCRIPTION and no single module holds that.
131
+ *
132
+ * **R3, composition bound.** Read models sharing one subscription share one cursor
133
+ * and one checkpoint, hence by R1 one view store and one durability class. So
134
+ * per-read-model view selection means one runner per view-store GROUP, not one per
135
+ * read model — and rebuilding one member of a composed bundle means rebuilding the
136
+ * bundle (or running a transient dedicated runner from `ORIGIN`). Composing is
137
+ * still the default, because the NOTIFY channel is per store, not per query.
138
+ *
139
+ * **What R3 does NOT say.** It does not bound how many times ONE read model may be
140
+ * materialised. R3 is about sharing a SUBSCRIPTION, and several materialisations of
141
+ * one read model deliberately do not share one: they pay N subscriptions, N cursors
142
+ * and N checkpoints precisely BECAUSE R1 and R2 forbid the sharing — one atomic
143
+ * `commit` cannot span two stores, and one checkpoint cannot carry two durability
144
+ * classes. So per-read-model view-store selection is a FLOOR rather than a ceiling.
145
+ * One slice record may feed a durable Postgres materialisation and an ephemeral
146
+ * in-memory one AT ONCE, wired by `runProjections`, which holds one `slices` value
147
+ * and one `ProjectionId` across them so their query and their decode cannot diverge.
148
+ * What it does not hold is the FOLDS — each `apply` re-expresses the fold against
149
+ * its own store, and a SQL one writing rows is not `foldIntoRef` — so agreement
150
+ * between two materialised figures is EMPIRICAL, something a test demonstrates, and
151
+ * never something the wiring gives you. The runners do not contend either: each
152
+ * owns its own checkpoint in its own store, so the guarded compare-and-set
153
+ * arbitrates with no lease, no lock and no coordinator.
154
+ *
155
+ * That leaves exactly THREE legal (log, view) permutations: all-in-memory (tests,
156
+ * prototypes, client-side); a durable log with an in-memory view (dev, low-traffic
157
+ * and admin views, in-flux slices — rebuilt from `ORIGIN` each boot, which is
158
+ * affordable at the event counts that are its remit and a reason to graduate a
159
+ * slice once it bakes); and a durable log with a durable view, the production
160
+ * default. Each is a property of ONE MATERIALISATION's pairing. Mixing is a
161
+ * DEPLOYMENT property rather than a fourth permutation: one deployment can run the
162
+ * second and third side by side against the same log — for two different read
163
+ * models, which is the case R3 bounds when they try to share a subscription, or for
164
+ * two materialisations of ONE read model, which R3 never reaches because they never
165
+ * share one.
166
+ *
167
+ * **A third KIND of materialisation sits outside all of this.** A read-time
168
+ * materialisation — `modelAtHead`, in `@kairos-es/codec` — folds the log per query
169
+ * and has no table, no checkpoint, no runner and no view store, so no permutation,
170
+ * no rung of the construction gate and no durability rule applies to it, R2 being
171
+ * VACUOUS where there is no checkpoint whose durability could outlive a log. It is
172
+ * consistent as of the `head` its read returned, which trails nothing, so it is the
173
+ * MOST current view of a slice record and the maintained table beside it is the
174
+ * stale one. Where a read model is small enough to fold at query time, that is a
175
+ * reason not to maintain it at all.
176
+ */
177
+ export {
178
+ DurableEventLog,
179
+ EphemeralEventLog,
180
+ EventLogDurability,
181
+ } from './EventLogDurability'
182
+ export { foldIntoRef } from './foldIntoRef'
183
+ export { makeInMemoryProjectionStore } from './inMemoryProjectionStore'
184
+ export {
185
+ BatchSize,
186
+ PipelineDied,
187
+ type ProjectionPipelineOptions,
188
+ type ProjectionRunner,
189
+ type ProjectionRunnerOptions,
190
+ } from './ProjectionRunner'
191
+ export {
192
+ type CheckpointKey,
193
+ CheckpointSuperseded,
194
+ checkpointKey,
195
+ DEFAULT_PARTITION,
196
+ type Durability,
197
+ forKey,
198
+ type KeyedProjectionStore,
199
+ PartitionId,
200
+ ProjectionId,
201
+ type ProjectionStore,
202
+ ProjectionStoreError,
203
+ } from './ProjectionStore'
204
+ export {
205
+ projectionLayer,
206
+ type ReadModel,
207
+ runProjection,
208
+ } from './runProjection'
209
+ export {
210
+ type Materialisation,
211
+ type MaterialisedProjection,
212
+ runProjections,
213
+ } from './runProjections'
214
+ export {
215
+ MaxNoProgressRestarts,
216
+ ProjectionStalled,
217
+ type ProjectionSupervisionOptions,
218
+ } from './superviseOnProgress'