@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,184 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.durabilityOrderingFault = exports.EventLogDurability = exports.EphemeralEventLog = exports.DurableEventLog = void 0;
7
+ var _effect = require("effect");
8
+ /**
9
+ * `EventLogDurability` — the R2 input the EVENT LOG contributes: how far the log a
10
+ * projection subscribes to survives a restart, declared by the application that
11
+ * chose the store's `Layer` and read from context in ONE place — the
12
+ * `prepareProjection` phase both runner entry points share, which is also the gate's
13
+ * only caller.
14
+ *
15
+ * ## The rule: R2, durability ordering
16
+ *
17
+ * This is R2's home, and every other mention in the read side points here rather
18
+ * than restating it.
19
+ *
20
+ * Checkpoint durability must not EXCEED event-log durability. The comparison has
21
+ * two halves, each declared where it is known: the VIEW store carries its own
22
+ * class on the `ProjectionStore` it implements, and the LOG's class is this tag. A
23
+ * DURABLE view over an EPHEMERAL log is the one illegal pairing of the four.
24
+ *
25
+ * It is illegal because the failure is SILENT. The checkpoint survives a restart
26
+ * the log does not, so on the next boot `subscribe(query, checkpoint)` returns
27
+ * nothing against a re-emptied log: no fibre dies, no error reaches any channel,
28
+ * no log line appears, and the view simply sits frozen and permanently stale while
29
+ * continuing to serve reads. Nothing at runtime is placed to notice it, which is
30
+ * why the two declarations are compared BEFORE anything is forked.
31
+ *
32
+ * `durabilityOrderingFault` at the foot of this module is that comparison — the
33
+ * CHECK beside the argument for it, so a reader who comes here for the rule finds
34
+ * the code that enforces it and the sentence an author meets when they break it,
35
+ * rather than a restatement of the rule four hundred lines away in the pipeline
36
+ * module. It is the first PER-ENTRY rung of the five rules `projectionWiringFault`
37
+ * chains, behind only the set-level collision rung, and is judged for every
38
+ * materialisation in the call; that module owns the argument for the whole order.
39
+ *
40
+ * The asymmetry is deliberate: getting the declaration wrong in the SAFE direction
41
+ * — saying `'ephemeral'` over a genuinely durable log — costs nothing but a durable
42
+ * view, so nothing checks it. Only the unsafe direction is rejected.
43
+ *
44
+ * ## Why it is DECLARED at all
45
+ *
46
+ * `DcbEventStore` deliberately exposes no durability, and that opacity is a
47
+ * feature rather than an omission: a runner never learns which engine it is
48
+ * running against, which is exactly what lets one projection sit over the
49
+ * in-memory log in a test and over Postgres in production with nothing in between
50
+ * changing. The store contract is untouched by the read side. But R2 has to know
51
+ * the log's class, so the only honest source is the application that picked the
52
+ * layer.
53
+ *
54
+ * ## Why a `Context.Tag` rather than a field on `ReadModel`
55
+ *
56
+ * ONE log, ONE declaration. The fact being declared is a property of the EVENT
57
+ * LOG, and every read model in a deployment reads the same log, so authoring it
58
+ * per read model states one fact N times at N sites — and N copies can DISAGREE.
59
+ * One read model declaring `'durable'` beside a neighbour declaring `'ephemeral'`
60
+ * over the identical `DcbEventStore` is not a type error, and nothing anywhere
61
+ * could notice: `runProjection` only ever sees the one pair it was handed, so
62
+ * there is no vantage point from which the contradiction is even visible. The read
63
+ * side's own scaling story is one runner per materialisation — horizontal scale by
64
+ * functional decomposition, never by sharding one read model's stream (ADR-0007) —
65
+ * so N > 1 is the EXPECTED case, and the unsafe direction is then reachable by a
66
+ * copy-paste that gets one of N wrong. N materialisations of ONE read model
67
+ * (`runProjections`) only sharpen that: their whole point is that the view stores
68
+ * differ, so their R2 verdicts differ too and each is judged separately against this
69
+ * one declaration — which is precisely why the LOG's half must not be authored
70
+ * alongside them.
71
+ *
72
+ * Provided beside the store layer, the declaration sits with the choice it
73
+ * describes: `Layer.merge(DcbEventStoreInMemory, EphemeralEventLog)` names the log
74
+ * and its durability class in one expression, and there is exactly one of it
75
+ * however many runners the graph goes on to carry.
76
+ *
77
+ * The cost is real and taken deliberately: this is a REQUIRED service in the `R` of
78
+ * `runProjection`, `projectionLayer` and `runProjections` alike, which every consumer
79
+ * must provide — and most costly to forget at `runProjections`, where one missing
80
+ * layer refuses N materialisations at once. Defaulting it would
81
+ * defeat the point in both directions — defaulting to `'ephemeral'` would make the
82
+ * PRODUCTION wiring the one you have to remember, and defaulting to `'durable'`
83
+ * would turn a forgotten declaration into the silently frozen view R2 exists to
84
+ * prevent.
85
+ *
86
+ * ## Why the store layers do not provide it themselves
87
+ *
88
+ * `DcbEventStoreInMemory` could plausibly provide `'ephemeral'` on its own and make
89
+ * the all-in-memory case correct by default. It deliberately does not, for three
90
+ * reasons that outweigh the ergonomics.
91
+ *
92
+ * This is a READ-SIDE tag, and `@kairos-es/core` knows nothing of the read side —
93
+ * `read` peers `core`, not the other way round — so core would have to DEFINE the
94
+ * tag for its layer to provide it. That puts a read-side rule inside the store
95
+ * contract's own package, which is precisely the coupling the contract's
96
+ * durability-opacity exists to avoid.
97
+ *
98
+ * It would also move the declaration from the APPLICATION to the BACKEND. Then a
99
+ * backend that got its own class wrong (a log that persists only on a flag, say)
100
+ * would be wrong for every read model at once with no wiring site left to correct
101
+ * it, and the whole reason R2 keys on a declaration rather than on the store is
102
+ * that only the application knows.
103
+ *
104
+ * And two providers of one tag is not an error in a layer graph — it is resolved
105
+ * by merge order. An application that declared `DurableEventLog` beside a store
106
+ * layer declaring `'ephemeral'` would get whichever merge happened to win, silently,
107
+ * which is a worse failure than the one being fixed. The single token
108
+ * `EphemeralEventLog` costs is not worth any of that.
109
+ */
110
+
111
+ /**
112
+ * The event log's durability class, as the application declares it.
113
+ *
114
+ * The service value is the bare `Durability` rather than a record wrapping it:
115
+ * the tag's name says exactly what it holds, so `yield* EventLogDurability` is the
116
+ * whole read, and `Layer.succeed(EventLogDurability, 'durable')` is the whole
117
+ * declaration for anyone not reaching for the two layers below.
118
+ */
119
+ class EventLogDurability extends /*#__PURE__*/_effect.Context.Tag('@kairos-es/read/EventLogDurability')() {}
120
+ /**
121
+ * Declare that the event log OUTLIVES the process — the production wiring, beside
122
+ * a durable `DcbEventStore` layer such as `@kairos-es/store-postgres`'s.
123
+ *
124
+ * Shipped as a `Layer` rather than left to `Layer.succeed` at each wiring site for
125
+ * the same reason `SerializerDefault` is: it names the declaration, so the two
126
+ * legal values cannot be typo'd, and it composes into a store layer's own merge
127
+ * without a second import from `effect`.
128
+ */
129
+ exports.EventLogDurability = EventLogDurability;
130
+ const DurableEventLog = exports.DurableEventLog = /*#__PURE__*/_effect.Layer.succeed(EventLogDurability, 'durable');
131
+ /**
132
+ * Declare that the event log DIES WITH THE PROCESS — `core`'s
133
+ * `DcbEventStoreInMemory`, and any other log whose contents do not survive a
134
+ * restart.
135
+ *
136
+ * Under this declaration a DURABLE `ProjectionStore` is rejected by R2, because
137
+ * its checkpoint would outlive the log it points into. It is also the honest
138
+ * conservative choice over a log whose durability is genuinely unknown: it forgoes
139
+ * a durable view and nothing else.
140
+ */
141
+ const EphemeralEventLog = exports.EphemeralEventLog = /*#__PURE__*/_effect.Layer.succeed(EventLogDurability, 'ephemeral');
142
+ /**
143
+ * R2 itself: `undefined` when the two declarations are legally ordered, otherwise
144
+ * the sentence an author meets when they are not.
145
+ *
146
+ * The one illegal pairing of the four is a DURABLE view over an EPHEMERAL log. The
147
+ * asymmetry is deliberate and the module doc argues it: declaring `'ephemeral'`
148
+ * over a genuinely durable log costs nothing but a durable view, so only the unsafe
149
+ * direction is rejected.
150
+ *
151
+ * ## Why NAMED FIELDS rather than two positional arguments
152
+ *
153
+ * The two operands are the same unbranded `Durability` union, so a positional pair
154
+ * would typecheck transposed — and a transposed R2 check is not a broken check, it
155
+ * is an INVERTED one: it would accept the one pairing that silently freezes a view
156
+ * and reject the three that are fine, which is worse than having no check at all.
157
+ * Named fields make that transposition unwritable without visibly naming the wrong
158
+ * field, which is as much as a call-site convention can be asked to carry.
159
+ *
160
+ * It is as much as is WANTED here, too. Branding the two halves so the type system
161
+ * separated them was considered and rejected: `Durability` is a two-value union
162
+ * read straight off a `Layer` and a `ProjectionStore`, both of them public surfaces
163
+ * a caller writes by hand, so branding it would put a constructor between an
164
+ * application and `Layer.succeed(EventLogDurability, 'durable')` for a mistake the
165
+ * R2 case and its non-vacuity control already catch — that control provides
166
+ * `DurableEventLog` over the SAME durable view store and asserts the runner starts,
167
+ * which no mis-comparison in here can satisfy while still failing the rejection
168
+ * half.
169
+ *
170
+ * The sentence begins with `R2 violated` and states the rule, the silent failure it
171
+ * prevents, and BOTH fixes, because it is written for whoever meets it in a log with
172
+ * no file open. It names `EventLogDurability` in particular: the log's half of the
173
+ * comparison is this service, provided beside the `DcbEventStore` layer, so that is
174
+ * where the reader has to go — a read model carries no field to correct. It does not
175
+ * name the projection; the caller's preamble carries that, for every rule at once.
176
+ * Whether it also names a PARTITION is the caller's to decide: `runProjection` writes
177
+ * one, and `runProjections` deliberately writes none — not because its entries share a
178
+ * partition (each resolves its own, and a distinct one is the escape hatch for two
179
+ * materialisations cohabiting a store) but because a fault there is already attributed
180
+ * by entry INDEX, which discriminates where a partition may not.
181
+ */
182
+ const durabilityOrderingFault = declarations => declarations.eventLogDurability === 'ephemeral' && declarations.viewDurability === 'durable' ? 'R2 violated — a DURABLE ProjectionStore over an EPHEMERAL event log. ' + 'The checkpoint would survive a restart the log does not, so on the next ' + 'boot subscribe(query, checkpoint) would return nothing against a ' + 're-emptied log and the view would sit FROZEN and permanently stale ' + 'without ever erroring. Pair an ephemeral log with an ephemeral view ' + "store, or make the log durable. The log's half of this comparison is " + 'the EventLogDurability service provided beside your DcbEventStore layer ' + '(EphemeralEventLog here); the view store declares the other half itself.' : undefined;
183
+ exports.durabilityOrderingFault = durabilityOrderingFault;
184
+ //# sourceMappingURL=EventLogDurability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventLogDurability.js","names":["_effect","require","EventLogDurability","Context","Tag","exports","DurableEventLog","Layer","succeed","EphemeralEventLog","durabilityOrderingFault","declarations","eventLogDurability","viewDurability","undefined"],"sources":["../../src/EventLogDurability.ts"],"sourcesContent":[null],"mappings":";;;;;;AAsGA,IAAAA,OAAA,GAAAC,OAAA;AAtGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyGA;;;;;;;;AAQM,MAAOC,kBAAmB,sBAAQC,eAAO,CAACC,GAAG,CACjD,oCAAoC,CACrC,EAAkC;AAEnC;;;;;;;;;AAAAC,OAAA,CAAAH,kBAAA,GAAAA,kBAAA;AASO,MAAMI,eAAe,GAAAD,OAAA,CAAAC,eAAA,gBAAoCC,aAAK,CAACC,OAAO,CAC3EN,kBAAkB,EAClB,SAAS,CACV;AAED;;;;;;;;;;AAUO,MAAMO,iBAAiB,GAAAJ,OAAA,CAAAI,iBAAA,gBAAoCF,aAAK,CAACC,OAAO,CAC7EN,kBAAkB,EAClB,WAAW,CACZ;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCO,MAAMQ,uBAAuB,GAAIC,YAGvC,IACCA,YAAY,CAACC,kBAAkB,KAAK,WAAW,IAC/CD,YAAY,CAACE,cAAc,KAAK,SAAS,GACrC,uEAAuE,GACvE,0EAA0E,GAC1E,mEAAmE,GACnE,qEAAqE,GACrE,sEAAsE,GACtE,uEAAuE,GACvE,0EAA0E,GAC1E,0EAA0E,GAC1EC,SAAS;AAAAT,OAAA,CAAAK,uBAAA,GAAAA,uBAAA","ignoreList":[]}