@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,185 @@
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 { DurableEventLog, EphemeralEventLog, EventLogDurability, } from './EventLogDurability.js';
178
+ export { foldIntoRef } from './foldIntoRef.js';
179
+ export { makeInMemoryProjectionStore } from './inMemoryProjectionStore.js';
180
+ export { BatchSize, PipelineDied, type ProjectionPipelineOptions, type ProjectionRunner, type ProjectionRunnerOptions, } from './ProjectionRunner.js';
181
+ export { type CheckpointKey, CheckpointSuperseded, checkpointKey, DEFAULT_PARTITION, type Durability, forKey, type KeyedProjectionStore, PartitionId, ProjectionId, type ProjectionStore, ProjectionStoreError, } from './ProjectionStore.js';
182
+ export { projectionLayer, type ReadModel, runProjection, } from './runProjection.js';
183
+ export { type Materialisation, type MaterialisedProjection, runProjections, } from './runProjections.js';
184
+ export { MaxNoProgressRestarts, ProjectionStalled, type ProjectionSupervisionOptions, } from './superviseOnProgress.js';
185
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+KG;AACH,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,EACL,SAAS,EACT,YAAY,EACZ,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,KAAK,aAAa,EAClB,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EACjB,KAAK,UAAU,EACf,MAAM,EACN,KAAK,oBAAoB,EACzB,WAAW,EACX,YAAY,EACZ,KAAK,eAAe,EACpB,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,eAAe,EACf,KAAK,SAAS,EACd,aAAa,GACd,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,cAAc,GACf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,KAAK,4BAA4B,GAClC,MAAM,uBAAuB,CAAA"}
@@ -0,0 +1,260 @@
1
+ /**
2
+ * The read side's CONSTRUCTION GATE: every wiring the read side refuses ON ITS OWN
3
+ * RULES, judged in one place, in one declared order, each verdict one
4
+ * `string | undefined`.
5
+ *
6
+ * ## ONE entry point over five rules, at three arities
7
+ *
8
+ * `projectionWiringFault` judges ONE CALL: the log the whole graph subscribes to,
9
+ * the one composed query and the one slice record every materialisation shares, and
10
+ * a LIST of the resolved per-entry wirings. `runProjection` hands it a singleton and
11
+ * `runProjections` hands it N, and both do so before either forks anything.
12
+ *
13
+ * The rules divide by what they are a function of, not by who called:
14
+ *
15
+ * - the COLLISION rung is a function of the whole list, pairwise;
16
+ * - R2 and the tuning figures are functions of ONE entry, judged for every entry in
17
+ * turn;
18
+ * - the servable grammar and the per-slice tag rule are functions of the SHARED
19
+ * query and the SHARED slice record, so they are judged once for the call.
20
+ *
21
+ * One record with the shared fields stated once and the entries as a list is what
22
+ * lets all three arities compose in one `??` chain. What the gate is stays what it
23
+ * was: one home, one return type, one declared order (below), and a caller that
24
+ * classifies the verdict under a preamble it was handed.
25
+ *
26
+ * ## The ONE refusal that is deliberately not here
27
+ *
28
+ * Naming it is what keeps "one place" honest. A slice record holding two DISTINCT
29
+ * `Schema`s under one event type is refused by `@kairos-es/codec`'s
30
+ * `decodeSlices`, which `prepareProjection` calls a few lines BELOW this gate on
31
+ * behalf of both entry points — so a rejected record still dies as a defect with
32
+ * nothing forked, exactly as a rejected wiring does, but the sentence a reader
33
+ * meets is the codec's. That rule
34
+ * belongs to the codec because BOTH of its consumers need it and only one is on
35
+ * the read side: the write path reaches the same merge through
36
+ * `buildDecisionModel` and never comes near this module, so a rung here
37
+ * would be a second home for one rule, covering half the hazard. `decode.ts` owns
38
+ * the argument; this is a pointer, not a restatement.
39
+ *
40
+ * Which of the two a record wrong in BOTH ways meets FIRST is not this module's to
41
+ * state either, and it has exactly one home: `prepareProjection` in
42
+ * `ProjectionRunner.ts`, the single body in which the gate call sits above the
43
+ * `decodeSlices` call, for both entry points at once. It was a claim two function
44
+ * bodies each made in prose until that phase was extracted, and pinned for only one
45
+ * of them.
46
+ *
47
+ * ## Why this is a module and not a paragraph of `runProjection`
48
+ *
49
+ * `runProjection` was doing two unrelated jobs. One is to refuse a wiring that can
50
+ * only ever be wrong — five rules, none of which mentions a stream, a scope or a
51
+ * fibre, and only one of which so much as compares two stores for identity. The
52
+ * other is to build and fork a pipeline. The refusals had
53
+ * grown to roughly two fifths of that function's body and most of its doc, and
54
+ * three of them wrote their own `Effect.die` with their own copy of the preamble
55
+ * naming the projection.
56
+ *
57
+ * The precedent is this package's own: `superviseOnProgress` was a deep module
58
+ * trapped inside the same function, and pulling it out is what made a supervision
59
+ * quantity pinnable against a stub instead of through the whole pipeline. This is
60
+ * the other half of that function, and it is deeper still by the measure that
61
+ * matters here — its interface is a record in and a sentence out, while everything
62
+ * behind it is five unrelated rules over four vocabularies (a layer graph, a
63
+ * tuning literal, a query grammar, a set of view stores).
64
+ *
65
+ * The fifth rule arriving from a DIFFERENT caller is what the module having been
66
+ * extracted bought. `runProjections` needed a construction-time refusal of its own,
67
+ * and there was already a place for it — with a settled return type, a settled
68
+ * division of labour with its caller, and a suite that states a rule against a
69
+ * record literal. Inline, it would have been a sixth thing inside a second pipeline
70
+ * function, in a second style, with a second preamble. It landed as a RUNG of the
71
+ * one chain rather than as a second entry point, which is what keeps the declared
72
+ * order below a property of this file rather than of which caller a reader opened.
73
+ *
74
+ * ## Why `string | undefined`, and why the shared record takes no `CheckpointKey`
75
+ *
76
+ * `undefined` for a sound wiring, otherwise the SPECIFIC sentence: what is wrong,
77
+ * what it would do, and how to fix it. It does not throw, does not call `Effect.die`
78
+ * and does not return an `Either` — ONE exit route and one return type, which is
79
+ * what lets five rules over four vocabularies compose with `??` and be read as five
80
+ * lines.
81
+ *
82
+ * The shared half of the record names no projection and no partition. The per-entry
83
+ * list does carry a resolved `CheckpointKey` each, but for the collision rung to
84
+ * COMPARE rather than for anything to report: no rule interpolates a `key` into its
85
+ * sentence except the collision rung, which names the partition the two offenders
86
+ * share.
87
+ *
88
+ * ATTRIBUTION and CLASSIFICATION both sit outside. This gate's ONE caller,
89
+ * `prepareProjection`, owns the single `Effect.die`; the PREAMBLE naming the
90
+ * projection (and, where there is one to name, the partition) comes from one level
91
+ * further out still, from whichever entry point was called. So the preamble is
92
+ * written once per ENTRY POINT rather than once per rule — which is exactly what
93
+ * collapsed three `Effect.die` sites to one. The gate
94
+ * writes the third party to that sentence: where there is more than one entry, the
95
+ * per-entry rungs prefix their verdict with the offending INDEX, in the same
96
+ * `materialisations[i]` vocabulary the collision rung already uses. Over a singleton
97
+ * there is no index to name and none is written, so a single wiring's message is
98
+ * what it always was. The same arrangement repeats one level DOWN, inside this file:
99
+ * `positiveFiniteDuration` owns the `Duration` bound and each of the four tuning
100
+ * call sites supplies the `role` clause, because the predicate knows the rule and
101
+ * its caller knows what breaks.
102
+ *
103
+ * The classification half of that is worth spelling out. That a mis-wiring is a
104
+ * DEFECT rather
105
+ * than a channel value is a claim about `runProjection`'s and `runProjections`'
106
+ * signatures — both error channels are `never` because everything that can go wrong
107
+ * once a projection is RUNNING belongs to the supervisor — and a gate returning a
108
+ * string makes no such claim, so it can be called from a plain function, a test, or
109
+ * an `Effect.gen` without three different failure conventions.
110
+ *
111
+ * ## The ORDER is a decision, and this is the only place it is stated
112
+ *
113
+ * The rules run OUTWARD-IN, from the widest wiring decision to the narrowest.
114
+ *
115
+ * The order is over RULES and not over ENTRIES. Entry 1's layer-graph mistake is
116
+ * still wider than entry 0's options-literal mistake, so rung 2 is judged for every
117
+ * entry before rung 3 is judged for any. An entry-major loop would instead let ARRAY
118
+ * POSITION decide which fault an author sees, and position carries no meaning here:
119
+ * the entries are a set, and are a tuple only because inference needs one.
120
+ *
121
+ * 1. **The collision rung**, once over the whole SET. Two materialisations sharing
122
+ * one store under one key is a property of the WHOLE CALL rather than of any one
123
+ * wiring in it, so it is judged before any single wiring is looked at — the same
124
+ * outward-in principle, one level up. And a colliding pair is worth hearing about
125
+ * BEFORE a mis-tuned figure in one of them, because fixing the tuning would leave
126
+ * the two runners racing one cursor. It cannot fire below N = 2, so it is vacuous
127
+ * for the singleton `runProjection` passes.
128
+ * 2. **R2, durability ordering**, PER ENTRY in entry order. A property of the LAYER
129
+ * GRAPH — which log, which view store — decided before any read model existed. If
130
+ * that pairing is illegal then nothing else about the runner matters, because the
131
+ * view it maintains cannot be true however well tuned the daemon is.
132
+ * 3. **The tuning figures**, PER ENTRY in entry order. What the caller wrote in the
133
+ * options literal, a smaller and more local mistake than picking the wrong pair
134
+ * of layers.
135
+ * 4. **The store's own servable grammar**, once over the SHARED composed query,
136
+ * reported in the STORE's vocabulary by `core`'s own `assertServableQuery`.
137
+ * 5. **The read side's stricter per-slice tag rule**, once over the SHARED slice
138
+ * record, and it MUST stay after (4): it catches only what that grammar admits,
139
+ * since a lone type-only item is servable. Were it first, a tagless-plus-tagged
140
+ * union — a violation of the shared grammar — would be reported as a read-side
141
+ * slice convention instead of in the vocabulary every other caller of that
142
+ * grammar meets it in.
143
+ *
144
+ * Hoisting the two SHARED rungs ahead of the two per-entry ones, which the
145
+ * shared-versus-per-entry axis would suggest, was considered and rejected: it
146
+ * changes the message a single wiring wrong in two ways gets, which is a fixed
147
+ * constraint. (2)-(3) ahead of (4)-(5) also restores the relative order that held
148
+ * before the supervisor was extracted, when its three restart durations were checked
149
+ * with `batchWindow` rather than a module away. But the point of writing the order
150
+ * down is not the restoration: it is that the order is now FIVE LINES a reader can
151
+ * read, rather than an emergent property of which of two modules happens to look at
152
+ * a field first.
153
+ *
154
+ * ## Why the input is a record of ALREADY-RESOLVED values
155
+ *
156
+ * Every field is a value, not an option: the caller has already applied its
157
+ * defaults — through `resolveProjection`, the one resolution site both callers
158
+ * share — so the gate judges exactly the figures that will run and cannot be handed
159
+ * one default while the pipeline uses another. That also holds the defaults
160
+ * themselves to the rules they document, for one decode per runner. The keys are
161
+ * RESOLVED `CheckpointKey`s for the same reason and one more: a partition a caller
162
+ * omitted is `DEFAULT_PARTITION`, so a rung judging the raw options would miss the
163
+ * commonest form of the collision, which is two entries that name no partition at
164
+ * all.
165
+ *
166
+ * The two INTEGER options are absent from the record on purpose. `BatchSize` and
167
+ * `MaxNoProgressRestarts` are branded, so their boundary is the caller's own
168
+ * `.make(...)` — a bad value is refused a stack frame away from where it was
169
+ * written and never reaches here at all.
170
+ *
171
+ * `slices` is a SHARED field and is typed as narrowly as the check needs — a query
172
+ * per slice, nothing else — which keeps this module free of a `@kairos-es/codec`
173
+ * import for a rule that has no opinion about schemas, payloads or folds.
174
+ * `Record<string, SliceProjection>` satisfies it structurally, so the record an entry
175
+ * point was handed reaches this rung unconverted.
176
+ *
177
+ * An entry's `cursorIdentity` is typed `object` and never called: it is an identity
178
+ * token the collision rung compares by REFERENCE. `object` rather than `ProjectionStore`
179
+ * because a `KeyedProjectionStore` is not structurally one (`readCheckpoint` is an
180
+ * `Effect` on the façade and a function on the port), so a caller holding only a
181
+ * bound view could not fill the field; and because `object` forbids `undefined`,
182
+ * which would otherwise make two unidentified entries compare equal.
183
+ *
184
+ * It is INTERNAL: nothing here is re-exported from the package index. The gate is
185
+ * how the two runner entry points refuse a wiring, not a capability the library
186
+ * offers — a caller with a wiring to validate has `runProjection` or
187
+ * `runProjections` itself. BOTH judge all five rules before either forks anything.
188
+ */
189
+ import { type Query } from '@kairos-es/core';
190
+ import { Duration } from 'effect';
191
+ import type { CheckpointKey, Durability } from './ProjectionStore.js';
192
+ /**
193
+ * ONE materialisation as the gate sees it: an identity token, its resolved key, its
194
+ * half of R2 and the four `Duration`s that tune it.
195
+ *
196
+ * `ResolvedProjection` in `ProjectionRunner.ts` satisfies all of it but
197
+ * `cursorIdentity`, which is why both entry points can build an entry by spreading a
198
+ * resolved wiring beside the identity they hold.
199
+ *
200
+ * Exported to the PACKAGE and no further, which is one more symbol than the gate
201
+ * used to offer and still nothing a consumer can reach: `prepareProjection` — the
202
+ * phase that calls this gate on behalf of both entry points — names the entry LIST
203
+ * in its own signature, and a structural re-declaration there would be a second
204
+ * statement of what the gate judges, free to drift from this one.
205
+ *
206
+ * The field is NOT called `store`, and that is the point rather than taste. The two
207
+ * entry points put DIFFERENT objects in it — `runProjections` the UNBOUND port, which is
208
+ * the last surface at which sameness is askable since `forKey` deliberately does not
209
+ * expose what it closed over; `runProjection` the BOUND view it was handed, where a
210
+ * singleton makes the rung vacuous anyway — and each is right for its own arity.
211
+ * Under the name `store` those two meanings sat behind one word in a record built by
212
+ * SPREADING a `Materialisation`, whose own `store` field means only the first of
213
+ * them. Nothing would have complained if a future field on either side shadowed the
214
+ * other, the type being `object`. What the rung needs is not "a store" but "the value
215
+ * whose IDENTITY separates one cursor from another", which is what this name says.
216
+ *
217
+ * Typed `object` because the rung COMPARES it and never calls it (a
218
+ * `KeyedProjectionStore` is not structurally a `ProjectionStore`), and because
219
+ * `object` also refuses `undefined` — which would otherwise make two unidentified
220
+ * entries compare equal and manufacture a collision out of nothing.
221
+ *
222
+ * `batchSize` is deliberately absent along with `maxNoProgressRestarts`: both are
223
+ * branded, so both were bounded at the caller's own `.make(...)`.
224
+ */
225
+ export type MaterialisationWiring = {
226
+ readonly cursorIdentity: object;
227
+ readonly key: CheckpointKey;
228
+ readonly viewDurability: Durability;
229
+ readonly batchWindow: Duration.DurationInput;
230
+ readonly restartMinDelay: Duration.DurationInput;
231
+ readonly restartMaxDelay: Duration.DurationInput;
232
+ readonly restartResetAfter: Duration.DurationInput;
233
+ };
234
+ /**
235
+ * Judge ONE CALL's whole wiring: `undefined` when it is sound, otherwise the
236
+ * sentence describing the FIRST rule it breaks.
237
+ *
238
+ * Five rules, `??`-chained in the outward-in order the module doc argues for and
239
+ * fixes. `??` rather than an array of predicates or a collected list of every
240
+ * violation, for two reasons: the first fault is the one to fix — the later rules
241
+ * judge a wiring whose wider decisions are already known to be wrong, so their
242
+ * verdicts would be noise — and a chain of five named calls is the whole
243
+ * implementation, which is what makes the declared order checkable against the code
244
+ * rather than merely asserted about it.
245
+ *
246
+ * The two PER-ENTRY rungs go through `perEntry`, which is what keeps the chain
247
+ * rule-major: every entry is judged by rung 2 before any entry is judged by rung 3.
248
+ *
249
+ * Every field is a RESOLVED value: see the module doc for why (and for why the two
250
+ * integer options are not here at all).
251
+ */
252
+ export declare const projectionWiringFault: (wiring: {
253
+ readonly eventLogDurability: Durability;
254
+ readonly query: Query;
255
+ readonly slices: Record<string, {
256
+ readonly query: Query;
257
+ }>;
258
+ readonly materialisations: ReadonlyArray<MaterialisationWiring>;
259
+ }) => string | undefined;
260
+ //# sourceMappingURL=projectionWiringFault.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectionWiringFault.d.ts","sourceRoot":"","sources":["../../src/projectionWiringFault.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2LG;AACH,OAAO,EAAuB,KAAK,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACjE,OAAO,EAAE,QAAQ,EAAkB,MAAM,QAAQ,CAAA;AAEjD,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAyRlE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAA;IAC3B,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAA;IACnC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,aAAa,CAAA;IAC5C,QAAQ,CAAC,eAAe,EAAE,QAAQ,CAAC,aAAa,CAAA;IAChD,QAAQ,CAAC,eAAe,EAAE,QAAQ,CAAC,aAAa,CAAA;IAChD,QAAQ,CAAC,iBAAiB,EAAE,QAAQ,CAAC,aAAa,CAAA;CACnD,CAAA;AAgCD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ;IAC5C,QAAQ,CAAC,kBAAkB,EAAE,UAAU,CAAA;IACvC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAA;IAC1D,QAAQ,CAAC,gBAAgB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAA;CAChE,KAAG,MAAM,GAAG,SAUc,CAAA"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * `runProjection` — the read side's SINGULAR entry point: one read model, one view
3
+ * store, one supervised daemon forked into the caller's `Scope`; plus
4
+ * `projectionLayer`, the same call shaped as a `Layer` so an application's graph owns
5
+ * that daemon's lifetime.
6
+ *
7
+ * ## Two entry points, one substrate
8
+ *
9
+ * This module and `runProjections.ts` are the read side's TWO entry points, and they
10
+ * are deliberately symmetrical one arity apart: N = 1 here, N >= 1 there. Neither
11
+ * builds a pipeline of its own and neither carries a rule of its own. Both run the
12
+ * three phases in `ProjectionRunner.ts` — `resolveProjection`, `prepareProjection`
13
+ * (which is where the construction gate `projectionWiringFault.ts` is consulted, for
14
+ * both of them, before either forks anything) and `buildAndFork` — so what differs
15
+ * between them is the shape of their INPUT and nothing else.
16
+ *
17
+ * Neither calls the other, and that follows from those input shapes rather than being
18
+ * an omission: a `ReadModel` carries a BOUND `KeyedProjectionStore`, a
19
+ * `Materialisation` an UNBOUND `ProjectionStore` plus the one `ProjectionId` the call
20
+ * names, and `forKey` deliberately does not expose what it closed over — so there is
21
+ * no route back from the first shape to the second. `runProjections.ts` owns that
22
+ * argument in full; this is the pointer to it, not a second home for it.
23
+ *
24
+ * ## Why a module of its own rather than a section of the substrate
25
+ *
26
+ * Because a file declaring the `ProjectionRunner` handle, the two option sets, the
27
+ * three shared phases AND one of the two entry points reads as though that entry
28
+ * point were the privileged one. It is not: it is the N = 1 case, and a read model
29
+ * that grows a second materialisation moves to the sibling without changing anything
30
+ * about how it is run. Keeping the substrate under the name of the type it declares,
31
+ * and each entry point in a file named for the function it exports, is what makes
32
+ * that symmetry visible from the directory listing.
33
+ */
34
+ import type { RequirementOf, Serializer, SliceProjection } from '@kairos-es/codec';
35
+ import type { DcbEventStore, DecodedEvent } from '@kairos-es/core';
36
+ import { type Array as Arr, Effect, Layer, type Scope } from 'effect';
37
+ import type { EventLogDurability } from './EventLogDurability.js';
38
+ import { type ProjectionRunner, type ProjectionRunnerOptions } from './ProjectionRunner.js';
39
+ import type { KeyedProjectionStore } from './ProjectionStore.js';
40
+ /**
41
+ * One read model: where it materialises (under which key), which slices drive it,
42
+ * and how a batch is applied.
43
+ *
44
+ * The slices are load-bearing twice over: their composed query is the
45
+ * subscription's query (so the runner reads exactly the events the folds want,
46
+ * with no separate query to keep in step), and their merged `type -> Schema` map
47
+ * is the decode lookup. There is no way to declare one without the other.
48
+ *
49
+ * The other half of R2 — the EVENT LOG's durability — is deliberately NOT a field
50
+ * here: it is declared once beside the store layer as the `EventLogDurability`
51
+ * service and read from context. `EventLogDurability.ts` argues why, and states
52
+ * the rule both halves feed.
53
+ *
54
+ * By R3, read models sharing one subscription share one cursor and therefore one
55
+ * `CheckpointKey`, one view store and one durability class — which is why a
56
+ * composed bundle is expressed as ONE `ReadModel` over several slices rather than
57
+ * several `ReadModel`s over one subscription.
58
+ *
59
+ * That bound is about sharing one SUBSCRIPTION, and the boundary is worth stating
60
+ * because the sentence above otherwise reads as one materialisation per read model.
61
+ * It says nothing about materialising ONE read model more than once: several
62
+ * materialisations of one read model deliberately take N subscriptions, N cursors
63
+ * and N checkpoints, exactly because R1 and R2 forbid them to share any of the
64
+ * three. Wire that with `runProjections`, which holds one `slices` value and one
65
+ * `ProjectionId` across N materialisations so their query and their decode cannot
66
+ * diverge, while each keeps its own store, its own `apply`, its own checkpoint and
67
+ * its own supervision hooks. What it does NOT hold is the folds, so agreement
68
+ * between the figures two materialisations arrive at stays empirical.
69
+ */
70
+ export interface ReadModel<T extends Record<string, SliceProjection>, E = never, R = never> {
71
+ /**
72
+ * The view store this read model materialises into, ALREADY BOUND to the
73
+ * checkpoint it advances (`forKey(store, key)`).
74
+ *
75
+ * A VALUE on the read model rather than a context tag, because the view store
76
+ * is selected per MATERIALISATION: one deployment can run an in-memory admin view
77
+ * and a durable production view against the same event log, and a slice can
78
+ * graduate from the first to the second with its `slices`, its composed query,
79
+ * its `CheckpointKey` and this runner untouched. What necessarily changes is
80
+ * this field and `apply` below — the two that name the view store — and nothing
81
+ * else.
82
+ *
83
+ * Graduation is SEQUENTIAL, memory and then Postgres, and it is not the only
84
+ * shape the selection permits: the same slice record can feed both AT ONCE, which
85
+ * is what `runProjections` wires. This field stays SINGULAR either way — one
86
+ * materialisation, one store, one key, one runner — and N materialisations are N
87
+ * of these values over one `slices` value, never one value holding a list. A list
88
+ * would imply one atomic `commit` across two stores, which R1 says is unsound;
89
+ * the demonstration of the simultaneous shape is
90
+ * `@kairos-es/read-postgres`'s `test/courseRosterGraduation.test.ts`, and the
91
+ * worked production wiring is `examples/course-subscriptions`'s
92
+ * `src/courseRosterMaterialisations.ts`.
93
+ *
94
+ * KEYED rather than a `(key, store)` pair, for the reason `KeyedProjectionStore`
95
+ * in `ProjectionStore.ts` argues — it owns why binding a key to a store turns
96
+ * "one materialisation, one key, one runner" from convention into structure. What
97
+ * is local to this field is that the key stays readable as `store.key`, which is
98
+ * where the runner's log annotations and failure payloads get it.
99
+ */
100
+ readonly store: KeyedProjectionStore;
101
+ /** The slices whose composed query drives the subscription and whose schemas decode. */
102
+ readonly slices: T;
103
+ /**
104
+ * The per-batch view writes, handed to `ProjectionStore.commit` so they land
105
+ * inside the same transaction as the checkpoint advance.
106
+ *
107
+ * The batch is non-empty by construction, so `apply` needs no empty case. If
108
+ * the store is NON-transactional this must be a SINGLE atomic effect (fold the
109
+ * batch purely, then one write — see `foldIntoRef`): the port cannot roll back
110
+ * a multi-write `apply` that fails part-way, and that requirement, not the
111
+ * store, is where the obligation sits.
112
+ */
113
+ readonly apply: (batch: Arr.NonEmptyReadonlyArray<DecodedEvent>) => Effect.Effect<void, E, R>;
114
+ }
115
+ /**
116
+ * Start the daemon maintaining `readModel`, forked into the caller's `Scope`.
117
+ *
118
+ * The returned effect is INFALLIBLE (`E = never`): starting a projection cannot
119
+ * fail, because everything that can go wrong once it is running is the
120
+ * supervisor's business and lives on the fibre's error channel.
121
+ *
122
+ * ## What can go wrong BEFORE the fork is a programming error, and those are defects
123
+ *
124
+ * Which wirings are refused on the READ SIDE'S rules, why each one is a wiring
125
+ * nothing at runtime would ever notice, and the order they are judged in are all
126
+ * `projectionWiringFault`'s — one gate over five rules, whose only relation to this
127
+ * function is that it is the one place they can be applied before anything exists to
128
+ * be wrong. The gate judges a SET of materialisations, and this function supplies a
129
+ * SINGLETON: the two PER-ENTRY rungs judge that one wiring, the two that are
130
+ * functions of the shared query and slice record judge it once and would do so
131
+ * whatever N was, and the collision rung is a property of the set and is vacuous over
132
+ * one entry, having no sibling to be paired with. That is also why nothing here carries
133
+ * an entry index — the gate names one only where there is a set to index into.
134
+ * A further refusal is the CODEC's and fires just below the gate; `prepareProjection`
135
+ * owns that ordering for both entry points, and the gate's doc says why the rule
136
+ * stays in the codec. This function keeps the two claims that are its own.
137
+ *
138
+ * The verdict is a DEFECT rather than a value on the error channel, because no
139
+ * application can handle its own mis-wiring: the only repair is to change the
140
+ * wiring, and a channel value would ask every caller to write a handler for a
141
+ * mistake that has already been made by the time the program runs. `Effect.die`
142
+ * with an `Error` is the same classification the store gives a mis-built query
143
+ * through `assertServableQuery`, which is one of the rules.
144
+ *
145
+ * And it lands on THIS effect before the `forkScoped` at the foot of `buildAndFork`:
146
+ * the gate is consulted while the daemon is still being
147
+ * assembled, so a rejected wiring has read no checkpoint, opened no subscription
148
+ * and left no half-started fibre behind. That is what makes the defect safe to
149
+ * raise rather than merely early, and every construction case in this package
150
+ * asserts the stored checkpoint is still `ORIGIN` afterwards to keep it true.
151
+ *
152
+ * The preamble on the message is written HERE, once, naming the projection and the
153
+ * partition — the gate returns the sentence about the RULE, and this function knows
154
+ * WHO broke it. The single `Effect.die` that joins them is `prepareProjection`'s,
155
+ * which is why there is one for both entry points rather than one per rule.
156
+ */
157
+ export declare const runProjection: <T extends Record<string, SliceProjection>, E, R>(readModel: ReadModel<T, E, R>, options?: ProjectionRunnerOptions) => Effect.Effect<ProjectionRunner, never, Scope.Scope | DcbEventStore | EventLogDurability | Serializer | RequirementOf<T> | R>;
158
+ /**
159
+ * `runProjection` as a `Layer` whose own scope owns the daemon.
160
+ *
161
+ * It provides NOTHING (`ROut = never`): a projection is a background process, not
162
+ * a service, and nothing should be able to depend on it as one — a read model is
163
+ * queried through its view store, never through the runner. What the `Layer` buys
164
+ * is lifecycle: dropped into an application's layer graph, the daemon starts when
165
+ * the graph is built and is interrupted when it is released, alongside the store
166
+ * layers it reads from, with no bespoke startup or shutdown code.
167
+ *
168
+ * Discarding the runner discards its fibre too, which is correct and has one
169
+ * consequence worth wiring for: under this `Layer` — the recommended shape — a
170
+ * `ProjectionStalled` has no fibre to be awaited on, so it reaches the outside
171
+ * world only through the supervisor's `Effect.logError` and through `onStalled`.
172
+ * That hook is the reason a stall need not be a log line somebody happens to grep
173
+ * for; pass one in `options` if a stalled projection should fail a health check,
174
+ * exit non-zero, or page.
175
+ *
176
+ * `Layer.scopedDiscard` discharges the `Scope` the fork needs; the store, its
177
+ * `EventLogDurability` declaration, the `Serializer`, the slices' requirements and
178
+ * the read model's own `R` stay on the layer's inputs, so they are satisfied the
179
+ * same way every other layer's are. `EventLogDurability` being an INPUT is what
180
+ * makes the wiring read correctly: the declaration is provided once, alongside the
181
+ * `DcbEventStore` layer it describes, and every projection layer in the graph is
182
+ * then satisfied from that one declaration.
183
+ */
184
+ export declare const projectionLayer: <T extends Record<string, SliceProjection>, E, R>(readModel: ReadModel<T, E, R>, options?: ProjectionRunnerOptions) => Layer.Layer<never, never, DcbEventStore | EventLogDurability | Serializer | RequirementOf<T> | R>;
185
+ //# sourceMappingURL=runProjection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runProjection.d.ts","sourceRoot":"","sources":["../../src/runProjection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,eAAe,EAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,EAAE,KAAK,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAA;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC9D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAG7B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,WAAW,SAAS,CACxB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACzC,CAAC,GAAG,KAAK,EACT,CAAC,GAAG,KAAK;IAET;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAA;IAEpC,wFAAwF;IACxF,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAElB;;;;;;;;;OASG;IACH,QAAQ,CAAC,KAAK,EAAE,CACd,KAAK,EAAE,GAAG,CAAC,qBAAqB,CAAC,YAAY,CAAC,KAC3C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,CAAC,EAAE,CAAC,EAC3E,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC7B,UAAU,uBAAuB,KAChC,MAAM,CAAC,MAAM,CACd,gBAAgB,EAChB,KAAK,EACH,KAAK,CAAC,KAAK,GACX,aAAa,GACb,kBAAkB,GAClB,UAAU,GACV,aAAa,CAAC,CAAC,CAAC,GAChB,CAAC,CA2CD,CAAA;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,eAAe,GAC1B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACzC,CAAC,EACD,CAAC,EAED,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC7B,UAAU,uBAAuB,KAChC,KAAK,CAAC,KAAK,CACZ,KAAK,EACL,KAAK,EACL,aAAa,GAAG,kBAAkB,GAAG,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CACb,CAAA"}