@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,233 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.forKey = exports.checkpointKey = exports.ProjectionStoreError = exports.ProjectionId = exports.PartitionId = exports.DEFAULT_PARTITION = exports.CheckpointSuperseded = void 0;
7
+ var _effect = require("effect");
8
+ /**
9
+ * The name of a read model, as a branded non-empty string.
10
+ *
11
+ * Branded rather than a bare `string` for the same reason every other kairos-es
12
+ * identifier is (`Position`, `Tag`, `EventType`): the projection name and the
13
+ * partition name are both non-empty strings with identical shape, so an
14
+ * unbranded pair is trivially transposable at a call site and the mistake would
15
+ * surface only as a projection silently reading somebody else's checkpoint.
16
+ * Construct with `ProjectionId.make(...)`.
17
+ */
18
+ const ProjectionId = exports.ProjectionId = /*#__PURE__*/_effect.Schema.String.pipe(/*#__PURE__*/_effect.Schema.nonEmptyString(), /*#__PURE__*/_effect.Schema.brand('ProjectionId'));
19
+ /**
20
+ * The partition of a read model's checkpoint, as a branded non-empty string.
21
+ *
22
+ * `DEFAULT_PARTITION` is the single value the RUNNER uses, because stream
23
+ * sharding is deliberately not offered: DCB events carry a tag matrix, so two
24
+ * shards can own rows for the same tag while each holds its own checkpoint —
25
+ * both guarded compare-and-sets then succeed and the lost update goes undetected
26
+ * (the guard arbitrates cursor progress, not row ownership). Nothing on this port
27
+ * restricts the value, and a `ProjectionStore` must keep every distinct
28
+ * `(projection, partition)` pair independent — the shared contract suite commits
29
+ * to a second partition precisely to prove that. The dimension is in the KEY so
30
+ * that a restricted, opt-in sharding capability — for read models that provably
31
+ * need no cross-tag ordering and whose every row is owned by one partition value
32
+ * — could be added later with no migration of the checkpoint's shape or storage.
33
+ */
34
+ const PartitionId = exports.PartitionId = /*#__PURE__*/_effect.Schema.String.pipe(/*#__PURE__*/_effect.Schema.nonEmptyString(), /*#__PURE__*/_effect.Schema.brand('PartitionId'));
35
+ /** The partition value every runner uses while sharding is cut (ADR-0007). */
36
+ const DEFAULT_PARTITION = exports.DEFAULT_PARTITION = /*#__PURE__*/PartitionId.make('default');
37
+ /**
38
+ * Build a `CheckpointKey`, defaulting the partition — the constructor nearly
39
+ * every call site wants, so that the sharding dimension stays in the key shape
40
+ * without appearing in ordinary wiring code.
41
+ */
42
+ const checkpointKey = (projection, partition = DEFAULT_PARTITION) => ({
43
+ projection,
44
+ partition
45
+ });
46
+ /**
47
+ * An infrastructure fault from a view store — a connection dropped, a statement
48
+ * failed, the checkpoint table is missing.
49
+ *
50
+ * This is a value on the ERROR channel rather than a defect, which inverts the
51
+ * store contract's rule (`DcbEventStore` dies on infrastructure faults, ADR-0002)
52
+ * and does so on purpose: the read side has a supervisor above it, and the
53
+ * supervisor's whole job is to tell "retry the subscription with backoff" from
54
+ * "give up and report a stalled projection". A defect carries no such
55
+ * distinction. `cause` is exact-optional — only set when there is one.
56
+ */
57
+ exports.checkpointKey = checkpointKey;
58
+ class ProjectionStoreError extends /*#__PURE__*/_effect.Data.TaggedError('ProjectionStoreError') {
59
+ /**
60
+ * `reason` again, as the `Error` field that says why — so the fault renders
61
+ * ITSELF.
62
+ *
63
+ * `Data.TaggedError` sets `name` to the tag and leaves `message` empty, so
64
+ * without this a `ProjectionStoreError` stringifies to `'ProjectionStoreError'`
65
+ * and the sentence explaining the fault reaches a log line only where something
66
+ * OUTSIDE the class knows to go and fetch `reason`. With it,
67
+ * `Error.prototype.toString` yields `'ProjectionStoreError: <reason>'`, and
68
+ * `String(fault)`, `Cause.pretty` (which reads `message` off anything `instanceof
69
+ * Error`) and the projection supervisor's `reason` annotation all take it from the
70
+ * one place it is declared. That matters most for exactly the audience this error
71
+ * exists for: the supervisor above the read side, whose log line is what an
72
+ * operator sees when a view store starts refusing to answer.
73
+ *
74
+ * ## What filling `message` reaches — and what it deliberately leaves alone
75
+ *
76
+ * Not the supervisor's two log lines: `message` is the field `Cause.pretty` reads
77
+ * off anything `instanceof Error`, so this changes how a `ProjectionStoreError`
78
+ * prints EVERYWHERE it is reported — a failed fibre's `cause` annotation on all
79
+ * three shipped loggers, `Effect.logError(message, cause)`, an unhandled
80
+ * failure's report, and any reporter or test runner that reads `.message`. That
81
+ * blast radius is the point of doing it on the class instead of at one call site:
82
+ * one declaration improves every reader at once, and none of them has to know
83
+ * this class's field names.
84
+ *
85
+ * What it does NOT change is the STRUCTURED shape. `Data.Error` overrides
86
+ * `YieldableError`'s `toJSON` with `{ ...plainArgs, ...this }` — own enumerables
87
+ * plus the constructor args, and a prototype accessor is neither — so
88
+ * `JSON.stringify(fault)` and an `Effect.logError(fault)` that JSON-encodes its
89
+ * object message emit exactly the `reason`/`_tag` they always did. The humane
90
+ * rendering gains a sentence; the machine-parsed one is untouched.
91
+ *
92
+ * A GETTER rather than a second field, so `reason` stays the single declaration
93
+ * and the two cannot disagree. Safe as a prototype accessor because
94
+ * `Data.TaggedError`'s constructor is `super(args?.message, …)` followed by
95
+ * `Object.assign(this, args)` (verified against `effect@3.22`'s `Data.ts`) and
96
+ * `message` is not among the fields above, so nothing shadows it with an own
97
+ * property.
98
+ *
99
+ * Both mechanics — that accessor safety and the `toJSON` shape above it — are
100
+ * argued HERE and nowhere else, for every read-side fault that fills the field:
101
+ * `CheckpointSuperseded` below, `PipelineDied` in `ProjectionRunner.ts` and
102
+ * `ProjectionStalled` in `superviseOnProgress.ts` each point back rather than
103
+ * re-derive, and none of the four declares a `message` field for that
104
+ * `Object.assign` to shadow its accessor with. One version pin to re-verify on an
105
+ * `effect` bump, not four.
106
+ */
107
+ get message() {
108
+ return this.reason;
109
+ }
110
+ }
111
+ /**
112
+ * The guarded compare-and-set lost: the stored checkpoint no longer equals
113
+ * `expected`, so somebody else advanced this key.
114
+ *
115
+ * A tagged ERROR, never a returned outcome value. A returned
116
+ * `Advanced | Superseded` only rolls the view writes back if the caller remembers
117
+ * to convert it into a failure, and forgetting is silent corruption — the view
118
+ * commits while the checkpoint stays put, and the next pass double-applies the
119
+ * batch. On the error channel the abort is automatic and cannot be ignored.
120
+ *
121
+ * It carries the key and the expectation but deliberately NOT the stored
122
+ * position, mirroring `AppendConditionFailed` (ADR-0002) for the same reason: the
123
+ * recovery path re-reads the checkpoint, so a carried position would already be
124
+ * stale by the time anything acted on it, and carrying one would invite exactly
125
+ * the resume-from-the-conflict-point bug that omitting it prevents.
126
+ *
127
+ * Like its sibling `ProjectionStoreError` it fills `message`, from the expectation
128
+ * it already carries. The getter below says what the field reaches, and also
129
+ * records the reasoning it REPLACED — this class once left `message` empty on a
130
+ * ground that could not be true, and that is worth being able to recognise again.
131
+ */
132
+ exports.ProjectionStoreError = ProjectionStoreError;
133
+ class CheckpointSuperseded extends /*#__PURE__*/_effect.Data.TaggedError('CheckpointSuperseded') {
134
+ /**
135
+ * The sentence this class's own first paragraph writes, plus the expectation the
136
+ * guard turned on — as the `Error` field that says why, so the fault renders
137
+ * ITSELF.
138
+ *
139
+ * `Data.TaggedError` sets `name` to the tag and leaves `message` empty, and
140
+ * `Cause.pretty` substitutes its own `'An error has occurred'` for an empty one,
141
+ * so unfilled this fault printed through a `Cause` reads
142
+ * `'CheckpointSuperseded: An error has occurred'` — a line naming neither a guard,
143
+ * nor a checkpoint, nor an expectation. Filled, `Error.prototype.toString` yields
144
+ * the tag followed by the sentence, everywhere the fault is reported. How far that
145
+ * reaches and what it deliberately leaves alone is set out on
146
+ * `ProjectionStoreError` above; the argument is identical for every fault that
147
+ * fills the field, so it is made once, on the one an operator meets most.
148
+ *
149
+ * ## Why this field was once left EMPTY, and why that reason was wrong
150
+ *
151
+ * Recorded rather than quietly deleted, because a rationale that cannot be true is
152
+ * worse than a missing one and the shape of this mistake is easy to repeat. The
153
+ * argument ran: `expected` is a `bigint`; a `bigint` is not JSON-serialisable, and
154
+ * `effect@3.22`'s `Logger.json` throws outright on one nested in an annotation
155
+ * value and loses the whole line with it; therefore filling `message` would push
156
+ * that value class into the field every renderer reaches for first.
157
+ *
158
+ * The measurement is true and stays load-bearing. The inference from it is a
159
+ * category error. `message` is typed `string`, and TEMPLATE INTERPOLATION of a
160
+ * `bigint` yields its decimal digits, so nothing downstream of this getter can see
161
+ * a `bigint` through it. The hazard is about the values handed to a LOGGER as
162
+ * ANNOTATIONS — which is exactly where the projection supervisor applies it, in
163
+ * `shouldRestart`, whose `position` annotation is `String(stored)` — and it says
164
+ * nothing whatever about a string rendered from one. Nor was the substitute the
165
+ * old argument offered a substitute: the supervisor's `position` annotation is the
166
+ * progress signal it RE-READ after the failure, not this expectation. Meanwhile
167
+ * the cost of the omission was real and was being paid on every `Cause.pretty` of
168
+ * a superseded commit.
169
+ *
170
+ * What the correction does NOT touch is the other ground this class states, which
171
+ * stands unchanged: no STORED position is carried, because the recovery path
172
+ * re-reads and a carried one would already be stale. That decision is about a
173
+ * RECOVERY INPUT — what a caller might wrongly resume from. This getter is a
174
+ * human-readable label over a field the class already carries, and `expected` is
175
+ * exactly as stale in the label as it is in the payload: a reader is being told
176
+ * what the guard expected, never what to resume from.
177
+ *
178
+ * A GETTER rather than a `message` field, so `expected` stays the single
179
+ * declaration. Why a prototype accessor is safe here, and why a structured log of
180
+ * this fault still emits the same `key`/`expected`/`_tag` it always did, are set
181
+ * out once for all four of these getters on `ProjectionStoreError` above.
182
+ */
183
+ get message() {
184
+ return `the stored checkpoint no longer equals the expectation (${this.expected})`;
185
+ }
186
+ }
187
+ /**
188
+ * Bind a `ProjectionStore` to one `CheckpointKey`, producing the surface a read
189
+ * model is actually given.
190
+ *
191
+ * A FREE FUNCTION rather than a method on `ProjectionStore`, decided that way for
192
+ * two reasons beyond the obvious one (a single implementation, which every present
193
+ * and future backend then gets for nothing rather than re-deriving — and could
194
+ * re-derive WRONGLY, since a `forKey` that closed over the wrong key would be
195
+ * invisible until two read models started trading cursors).
196
+ *
197
+ * The sharper reason is DECORATORS. Wrapping a store is an established move here —
198
+ * a test's never-committing view store, a durability override — and every one of
199
+ * them is written `{ ...inner, commit: … }`. Were `forKey` a method, that spread
200
+ * would copy the INNER store's own `forKey`, whose closure still points at the
201
+ * inner store, so `forKey(decorated, key)` would silently route straight past the
202
+ * decoration. As a free function it reads `store.commit` off the value it is
203
+ * handed, so a decorated store decorates.
204
+ *
205
+ * The second reason is the port's implementation burden: `ProjectionStore` stays
206
+ * three methods, which is what a backend has to get right, and no implementation
207
+ * can drift on a member that is pure derivation.
208
+ *
209
+ * `Effect.suspend` on the two no-argument members preserves the port's per-call
210
+ * construction timing exactly: an implementation is entitled to build its effect
211
+ * when its method is called (the SQL store binds parameters there), so suspending
212
+ * keeps `yield* view.readCheckpoint` indistinguishable from
213
+ * `yield* store.readCheckpoint(key)` rather than freezing one construction for the
214
+ * store's lifetime.
215
+ */
216
+ exports.CheckpointSuperseded = CheckpointSuperseded;
217
+ const forKey = (store, key) => ({
218
+ key,
219
+ durability: store.durability,
220
+ readCheckpoint: _effect.Effect.suspend(() => store.readCheckpoint(key)),
221
+ // Spelled out field by field rather than `{ key, ...args }`: the bound key is
222
+ // then the ONLY key in the call, which is the whole claim this façade makes, and
223
+ // no spread order or stray property can put a different one there.
224
+ commit: args => store.commit({
225
+ key,
226
+ expected: args.expected,
227
+ next: args.next,
228
+ viewWrites: args.viewWrites
229
+ }),
230
+ resetCheckpoint: _effect.Effect.suspend(() => store.resetCheckpoint(key))
231
+ });
232
+ exports.forKey = forKey;
233
+ //# sourceMappingURL=ProjectionStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectionStore.js","names":["_effect","require","ProjectionId","exports","Schema","String","pipe","nonEmptyString","brand","PartitionId","DEFAULT_PARTITION","make","checkpointKey","projection","partition","ProjectionStoreError","Data","TaggedError","message","reason","CheckpointSuperseded","expected","forKey","store","key","durability","readCheckpoint","Effect","suspend","commit","args","next","viewWrites","resetCheckpoint"],"sources":["../../src/ProjectionStore.ts"],"sourcesContent":[null],"mappings":";;;;;;AAsCA,IAAAA,OAAA,GAAAC,OAAA;AAEA;;;;;;;;;;AAUO,MAAMC,YAAY,GAAAC,OAAA,CAAAD,YAAA,gBAAGE,cAAM,CAACC,MAAM,CAACC,IAAI,cAC5CF,cAAM,CAACG,cAAc,EAAE,eACvBH,cAAM,CAACI,KAAK,CAAC,cAAc,CAAC,CAC7B;AAGD;;;;;;;;;;;;;;;AAeO,MAAMC,WAAW,GAAAN,OAAA,CAAAM,WAAA,gBAAGL,cAAM,CAACC,MAAM,CAACC,IAAI,cAC3CF,cAAM,CAACG,cAAc,EAAE,eACvBH,cAAM,CAACI,KAAK,CAAC,aAAa,CAAC,CAC5B;AAGD;AACO,MAAME,iBAAiB,GAAAP,OAAA,CAAAO,iBAAA,gBAAgBD,WAAW,CAACE,IAAI,CAAC,SAAS,CAAC;AA8BzE;;;;;AAKO,MAAMC,aAAa,GAAGA,CAC3BC,UAAwB,EACxBC,SAAA,GAAyBJ,iBAAiB,MACvB;EAAEG,UAAU;EAAEC;AAAS,CAAE,CAAC;AAc/C;;;;;;;;;;;AAAAX,OAAA,CAAAS,aAAA,GAAAA,aAAA;AAWM,MAAOG,oBAAqB,sBAAQC,YAAI,CAACC,WAAW,CACxD,sBAAsB,CAItB;EACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgDA,IAAaC,OAAOA,CAAA;IAClB,OAAO,IAAI,CAACC,MAAM;EACpB;;AAGF;;;;;;;;;;;;;;;;;;;;;AAAAhB,OAAA,CAAAY,oBAAA,GAAAA,oBAAA;AAqBM,MAAOK,oBAAqB,sBAAQJ,YAAI,CAACC,WAAW,CACxD,sBAAsB,CAItB;EACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDA,IAAaC,OAAOA,CAAA;IAClB,OAAO,2DAA2D,IAAI,CAACG,QAAQ,GAAG;EACpF;;AAsMF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAAlB,OAAA,CAAAiB,oBAAA,GAAAA,oBAAA;AA6BO,MAAME,MAAM,GAAGA,CACpBC,KAAsB,EACtBC,GAAkB,MACQ;EAC1BA,GAAG;EACHC,UAAU,EAAEF,KAAK,CAACE,UAAU;EAC5BC,cAAc,EAAEC,cAAM,CAACC,OAAO,CAAC,MAAML,KAAK,CAACG,cAAc,CAACF,GAAG,CAAC,CAAC;EAC/D;EACA;EACA;EACAK,MAAM,EAASC,IAId,IACCP,KAAK,CAACM,MAAM,CAAC;IACXL,GAAG;IACHH,QAAQ,EAAES,IAAI,CAACT,QAAQ;IACvBU,IAAI,EAAED,IAAI,CAACC,IAAI;IACfC,UAAU,EAAEF,IAAI,CAACE;GAClB,CAAC;EACJC,eAAe,EAAEN,cAAM,CAACC,OAAO,CAAC,MAAML,KAAK,CAACU,eAAe,CAACT,GAAG,CAAC;CACjE,CAAC;AAAArB,OAAA,CAAAmB,MAAA,GAAAA,MAAA","ignoreList":[]}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.foldIntoRef = void 0;
7
+ var _core = require("@kairos-es/core");
8
+ var _effect = require("effect");
9
+ /**
10
+ * Build the per-batch `apply` for a read model whose whole view is `view`.
11
+ *
12
+ * `slices` must be the SAME record the read model's subscription query is derived
13
+ * from, so the events the runner delivers are exactly the events these folds
14
+ * expect; `composeProjections` gates each slice on its own query before
15
+ * dispatching, so a slice never sees an event it did not ask for.
16
+ *
17
+ * The returned effect is infallible (`E = never`) and requirement-free
18
+ * (`R = never`), which is the whole point: handed to `ProjectionStore.commit` as
19
+ * `viewWrites`, it adds nothing to the commit's error or requirement channels, so
20
+ * the ONLY way that commit can fail is the checkpoint guard.
21
+ */
22
+ const foldIntoRef = (slices, view) => {
23
+ // Composed ONCE, at wiring time, not per batch: the composition is pure and
24
+ // derives the merged query and the dispatch table, so rebuilding it on every
25
+ // batch would only burn allocations.
26
+ const composite = (0, _core.composeProjections)(slices);
27
+ return batch =>
28
+ // One `Ref.update`, folding the whole batch inside the update function. Not a
29
+ // fold of `Ref.update`s: N updates are N observable states, so an interrupt or
30
+ // a concurrent reader could see the view half-way through a batch, and on a
31
+ // lost checkpoint guard a partly-applied batch would already be visible. One
32
+ // update makes the batch atomic in the same sense the checkpoint advance is.
33
+ _effect.Ref.update(view, state => batch.reduce((acc, decoded) => composite.evolve(acc, decoded), state));
34
+ };
35
+ exports.foldIntoRef = foldIntoRef;
36
+ //# sourceMappingURL=foldIntoRef.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foldIntoRef.js","names":["_core","require","_effect","foldIntoRef","slices","view","composite","composeProjections","batch","Ref","update","state","reduce","acc","decoded","evolve","exports"],"sources":["../../src/foldIntoRef.ts"],"sourcesContent":[null],"mappings":";;;;;;AAqBA,IAAAA,KAAA,GAAAC,OAAA;AAMA,IAAAC,OAAA,GAAAD,OAAA;AAEA;;;;;;;;;;;;;AAaO,MAAME,WAAW,GAAGA,CACzBC,MAAS,EACTC,IAAgC,KAGN;EAC1B;EACA;EACA;EACA,MAAMC,SAAS,GAAG,IAAAC,wBAAkB,EAACH,MAAM,CAAC;EAE5C,OAAQI,KAAK;EACX;EACA;EACA;EACA;EACA;EACAC,WAAG,CAACC,MAAM,CAACL,IAAI,EAAGM,KAAK,IACrBH,KAAK,CAACI,MAAM,CAAC,CAACC,GAAG,EAAEC,OAAO,KAAKR,SAAS,CAACS,MAAM,CAACF,GAAG,EAAEC,OAAO,CAAC,EAAEH,KAAK,CAAC,CACtE;AACL,CAAC;AAAAK,OAAA,CAAAb,WAAA,GAAAA,WAAA","ignoreList":[]}
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.makeInMemoryProjectionStore = void 0;
7
+ var _core = require("@kairos-es/core");
8
+ var _effect = require("effect");
9
+ var _ProjectionStore = require("./ProjectionStore.js");
10
+ /**
11
+ * The in-memory `ProjectionStore`: a `Ref` of checkpoints plus the exclusive
12
+ * critical section that makes the guarded advance atomic.
13
+ *
14
+ * This is NOT merely a test double. It is load-bearing for the in-process
15
+ * supervision restart, where the subscription dies (the event store's poll retry
16
+ * exhausted) but the process and its in-memory view survive: the restart must
17
+ * resume from the checkpoint rather than replay from `ORIGIN` into an
18
+ * already-populated view, and an ephemeral checkpoint stored beside an ephemeral
19
+ * view is exactly what makes that resume correct. It is also the whole
20
+ * all-in-memory permutation — tests, prototypes, client-side use — and the
21
+ * durable-log-with-in-memory-view mode's view store (dev, admin and in-flux
22
+ * slices, rebuilt from `ORIGIN` each boot).
23
+ *
24
+ * A factory `Effect`, deliberately not a `Layer`: the view store is selected PER
25
+ * MATERIALISATION — so a slice may start in memory and GRADUATE to Postgres, which
26
+ * `ReadModel.store` owns the claim about — and it is therefore a value handed to a
27
+ * read model, not a context tag that one store could claim process-wide.
28
+ *
29
+ * That selection is a FLOOR rather than a ceiling, and this store is on the common
30
+ * side of it: the two variants above are also available AT ONCE, an in-memory
31
+ * materialisation of a slice record beside a durable one from the SAME record —
32
+ * a Postgres table somebody queries with SQL, and an in-process view of the same
33
+ * slices holding one aggregate's hot figures with no round trip to reach them. That
34
+ * is `runProjections`, and every call of this factory is an independent store, so
35
+ * two materialisations wired that way share nothing but their slices. What they do
36
+ * not share is their FOLDS: each `apply` re-expresses one, so agreement between
37
+ * their figures is something a test demonstrates rather than something the wiring
38
+ * gives.
39
+ *
40
+ * The critical-section design mirrors `core`'s in-memory event store: one
41
+ * `Effect.makeSemaphore(1)` serialises the guarded advance, exactly as that store
42
+ * serialises check-then-append, and for the same reason — the check and the write
43
+ * must not be separable, and that atomicity IS the concurrency guarantee.
44
+ */
45
+
46
+ /**
47
+ * Flatten a `CheckpointKey` into a map key.
48
+ *
49
+ * The separator is NUL. A `ProjectionId`/`PartitionId` is any non-empty string,
50
+ * so a printable separator (`:`, `/`, `|`) could be forged inside either half and
51
+ * two distinct keys would collide onto one checkpoint — one read model silently
52
+ * reading and advancing another's cursor. NUL cannot appear in a name any human
53
+ * or config file produces, so the flattening stays injective in practice without
54
+ * narrowing the id grammar.
55
+ *
56
+ * The other route to that same failure is a WIRING rather than a forged id: two
57
+ * materialisations handed this one store under equal keys, which would be two
58
+ * runners over one cursor. That one is caught before anything is forked, by the
59
+ * set-level collision rung of the construction gate in `projectionWiringFault.ts`,
60
+ * and its sentence names the two fixes. Nothing here can catch it — a store sees
61
+ * keys, never who is holding it.
62
+ */
63
+ const mapKey = key => `${key.projection}\u0000${key.partition}`;
64
+ /**
65
+ * Build an in-memory `ProjectionStore`.
66
+ *
67
+ * Every call is an independent store with its own checkpoints and its own
68
+ * semaphore, so two read models wired to two calls of this factory cannot
69
+ * interfere — and a test needing a fresh store just calls it again.
70
+ */
71
+ const makeInMemoryProjectionStore = exports.makeInMemoryProjectionStore = /*#__PURE__*/_effect.Effect.gen(function* () {
72
+ const checkpoints = yield* _effect.Ref.make(new Map());
73
+ const mutex = yield* _effect.Effect.makeSemaphore(1);
74
+ /** The stored position, or `ORIGIN` for a key never committed. */
75
+ const positionOf = (stored, key) => stored.get(mapKey(key)) ?? _core.ORIGIN;
76
+ const readCheckpoint = key =>
77
+ // No lock needed: `Ref.get` is a single atomic read, and the guard that
78
+ // actually protects a commit is re-checked INSIDE the critical section
79
+ // below — a value read here is only ever an expectation to be guarded on,
80
+ // never a licence to write.
81
+ _effect.Effect.map(_effect.Ref.get(checkpoints), stored => positionOf(stored, key));
82
+ const commit = args =>
83
+ // The permit is acquired INTERRUPTIBLY (a fibre waiting its turn can still
84
+ // be torn down), and only the section itself is uninterruptible.
85
+ mutex.withPermits(1)(_effect.Effect.uninterruptible(_effect.Effect.gen(function* () {
86
+ const stored = positionOf(yield* _effect.Ref.get(checkpoints), args.key);
87
+ // The guard is checked FIRST, before the view writes run. There is no
88
+ // transaction here to roll anything back, so the ONLY way a lost
89
+ // guard can leave the view untouched is to lose before touching it.
90
+ // Check-then-act is safe despite `viewWrites` suspending in between,
91
+ // because the section is exclusive: no other fibre can observe or
92
+ // advance this checkpoint until the permit is released, so the value
93
+ // read here cannot go stale within the section.
94
+ //
95
+ // On a lost guard `viewWrites` was NEVER RUN, which is observably
96
+ // identical to the SQL store's rollback — the port's abort contract
97
+ // is met by omission rather than by undo. (The one case omission
98
+ // cannot cover is a `viewWrites` that fails part-way, which is why
99
+ // the port requires a non-transactional store's per-batch write to be
100
+ // a single atomic effect; see `ProjectionStore` and `foldIntoRef`.)
101
+ if (stored !== args.expected) {
102
+ return yield* new _ProjectionStore.CheckpointSuperseded({
103
+ key: args.key,
104
+ expected: args.expected
105
+ });
106
+ }
107
+ yield* args.viewWrites;
108
+ // Uninterruptibility earns its keep here: an interrupt landing
109
+ // between the view writes and this advance would commit the batch's
110
+ // effects with the cursor left behind, and the next run would apply
111
+ // the same batch a second time. Copy-on-write so a concurrent reader
112
+ // holding the previous map sees a consistent snapshot.
113
+ yield* _effect.Ref.update(checkpoints, current => new Map(current).set(mapKey(args.key), args.next));
114
+ })));
115
+ const resetCheckpoint = key =>
116
+ // Under the same permit as `commit`, so a reset can never interleave with a
117
+ // guarded advance and leave the map half-updated relative to the view.
118
+ // Deleting rather than storing `ORIGIN` keeps "never committed" and "reset"
119
+ // one state, which is what the port promises the read-back is.
120
+ mutex.withPermits(1)(_effect.Ref.update(checkpoints, current => {
121
+ const next = new Map(current);
122
+ next.delete(mapKey(key));
123
+ return next;
124
+ }));
125
+ // Annotated at the definition site rather than only through the exported
126
+ // signature, per the rule on `ProjectionStore` itself.
127
+ const store = {
128
+ // Dies with the process, which is the R2 input: an ephemeral view is legal
129
+ // over either an ephemeral or a durable log (it just re-catches-up), so
130
+ // this value never trips the durability-ordering check.
131
+ durability: 'ephemeral',
132
+ readCheckpoint,
133
+ commit,
134
+ resetCheckpoint
135
+ };
136
+ return store;
137
+ });
138
+ //# sourceMappingURL=inMemoryProjectionStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inMemoryProjectionStore.js","names":["_core","require","_effect","_ProjectionStore","mapKey","key","projection","partition","makeInMemoryProjectionStore","exports","Effect","gen","checkpoints","Ref","make","Map","mutex","makeSemaphore","positionOf","stored","get","ORIGIN","readCheckpoint","map","commit","args","withPermits","uninterruptible","expected","CheckpointSuperseded","viewWrites","update","current","set","next","resetCheckpoint","delete","store","durability"],"sources":["../../src/inMemoryProjectionStore.ts"],"sourcesContent":[null],"mappings":";;;;;;AAmCA,IAAAA,KAAA,GAAAC,OAAA;AACA,IAAAC,OAAA,GAAAD,OAAA;AAEA,IAAAE,gBAAA,GAAAF,OAAA;AAtCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA;;;;;;;;;;;;;;;;;AAiBA,MAAMG,MAAM,GAAIC,GAAkB,IAChC,GAAGA,GAAG,CAACC,UAAU,SAASD,GAAG,CAACE,SAAS,EAAE;AAE3C;;;;;;;AAOO,MAAMC,2BAA2B,GAAAC,OAAA,CAAAD,2BAAA,gBACtCE,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB,MAAMC,WAAW,GAAG,OAAOC,WAAG,CAACC,IAAI,CACjC,IAAIC,GAAG,EAAE,CACV;EACD,MAAMC,KAAK,GAAG,OAAON,cAAM,CAACO,aAAa,CAAC,CAAC,CAAC;EAE5C;EACA,MAAMC,UAAU,GAAGA,CACjBC,MAAqC,EACrCd,GAAkB,KACLc,MAAM,CAACC,GAAG,CAAChB,MAAM,CAACC,GAAG,CAAC,CAAC,IAAIgB,YAAM;EAEhD,MAAMC,cAAc,GAClBjB,GAAkB;EAElB;EACA;EACA;EACA;EACAK,cAAM,CAACa,GAAG,CAACV,WAAG,CAACO,GAAG,CAACR,WAAW,CAAC,EAAGO,MAAM,IAAKD,UAAU,CAACC,MAAM,EAAEd,GAAG,CAAC,CAAC;EAEvE,MAAMmB,MAAM,GAAUC,IAKrB;EACC;EACA;EACAT,KAAK,CAACU,WAAW,CAAC,CAAC,CAAC,CAClBhB,cAAM,CAACiB,eAAe,CACpBjB,cAAM,CAACC,GAAG,CAAC,aAAS;IAClB,MAAMQ,MAAM,GAAGD,UAAU,CAAC,OAAOL,WAAG,CAACO,GAAG,CAACR,WAAW,CAAC,EAAEa,IAAI,CAACpB,GAAG,CAAC;IAEhE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,IAAIc,MAAM,KAAKM,IAAI,CAACG,QAAQ,EAAE;MAC5B,OAAO,OAAO,IAAIC,qCAAoB,CAAC;QACrCxB,GAAG,EAAEoB,IAAI,CAACpB,GAAG;QACbuB,QAAQ,EAAEH,IAAI,CAACG;OAChB,CAAC;IACJ;IAEA,OAAOH,IAAI,CAACK,UAAU;IAEtB;IACA;IACA;IACA;IACA;IACA,OAAOjB,WAAG,CAACkB,MAAM,CAACnB,WAAW,EAAGoB,OAAO,IACrC,IAAIjB,GAAG,CAACiB,OAAO,CAAC,CAACC,GAAG,CAAC7B,MAAM,CAACqB,IAAI,CAACpB,GAAG,CAAC,EAAEoB,IAAI,CAACS,IAAI,CAAC,CAClD;EACH,CAAC,CAAC,CACH,CACF;EAEH,MAAMC,eAAe,GAAI9B,GAAkB;EACzC;EACA;EACA;EACA;EACAW,KAAK,CAACU,WAAW,CAAC,CAAC,CAAC,CAClBb,WAAG,CAACkB,MAAM,CAACnB,WAAW,EAAGoB,OAAO,IAAI;IAClC,MAAME,IAAI,GAAG,IAAInB,GAAG,CAACiB,OAAO,CAAC;IAC7BE,IAAI,CAACE,MAAM,CAAChC,MAAM,CAACC,GAAG,CAAC,CAAC;IACxB,OAAO6B,IAAI;EACb,CAAC,CAAC,CACH;EAEH;EACA;EACA,MAAMG,KAAK,GAAoB;IAC7B;IACA;IACA;IACAC,UAAU,EAAE,WAAW;IACvBhB,cAAc;IACdE,MAAM;IACNW;GACD;EACD,OAAOE,KAAK;AACd,CAAC,CAAC","ignoreList":[]}
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "BatchSize", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _ProjectionRunner.BatchSize;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "CheckpointSuperseded", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _ProjectionStore.CheckpointSuperseded;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "DEFAULT_PARTITION", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _ProjectionStore.DEFAULT_PARTITION;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "DurableEventLog", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _EventLogDurability.DurableEventLog;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "EphemeralEventLog", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _EventLogDurability.EphemeralEventLog;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "EventLogDurability", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _EventLogDurability.EventLogDurability;
40
+ }
41
+ });
42
+ Object.defineProperty(exports, "MaxNoProgressRestarts", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _superviseOnProgress.MaxNoProgressRestarts;
46
+ }
47
+ });
48
+ Object.defineProperty(exports, "PartitionId", {
49
+ enumerable: true,
50
+ get: function () {
51
+ return _ProjectionStore.PartitionId;
52
+ }
53
+ });
54
+ Object.defineProperty(exports, "PipelineDied", {
55
+ enumerable: true,
56
+ get: function () {
57
+ return _ProjectionRunner.PipelineDied;
58
+ }
59
+ });
60
+ Object.defineProperty(exports, "ProjectionId", {
61
+ enumerable: true,
62
+ get: function () {
63
+ return _ProjectionStore.ProjectionId;
64
+ }
65
+ });
66
+ Object.defineProperty(exports, "ProjectionStalled", {
67
+ enumerable: true,
68
+ get: function () {
69
+ return _superviseOnProgress.ProjectionStalled;
70
+ }
71
+ });
72
+ Object.defineProperty(exports, "ProjectionStoreError", {
73
+ enumerable: true,
74
+ get: function () {
75
+ return _ProjectionStore.ProjectionStoreError;
76
+ }
77
+ });
78
+ Object.defineProperty(exports, "checkpointKey", {
79
+ enumerable: true,
80
+ get: function () {
81
+ return _ProjectionStore.checkpointKey;
82
+ }
83
+ });
84
+ Object.defineProperty(exports, "foldIntoRef", {
85
+ enumerable: true,
86
+ get: function () {
87
+ return _foldIntoRef.foldIntoRef;
88
+ }
89
+ });
90
+ Object.defineProperty(exports, "forKey", {
91
+ enumerable: true,
92
+ get: function () {
93
+ return _ProjectionStore.forKey;
94
+ }
95
+ });
96
+ Object.defineProperty(exports, "makeInMemoryProjectionStore", {
97
+ enumerable: true,
98
+ get: function () {
99
+ return _inMemoryProjectionStore.makeInMemoryProjectionStore;
100
+ }
101
+ });
102
+ Object.defineProperty(exports, "projectionLayer", {
103
+ enumerable: true,
104
+ get: function () {
105
+ return _runProjection.projectionLayer;
106
+ }
107
+ });
108
+ Object.defineProperty(exports, "runProjection", {
109
+ enumerable: true,
110
+ get: function () {
111
+ return _runProjection.runProjection;
112
+ }
113
+ });
114
+ Object.defineProperty(exports, "runProjections", {
115
+ enumerable: true,
116
+ get: function () {
117
+ return _runProjections.runProjections;
118
+ }
119
+ });
120
+ var _EventLogDurability = require("./EventLogDurability.js");
121
+ var _foldIntoRef = require("./foldIntoRef.js");
122
+ var _inMemoryProjectionStore = require("./inMemoryProjectionStore.js");
123
+ var _ProjectionRunner = require("./ProjectionRunner.js");
124
+ var _ProjectionStore = require("./ProjectionStore.js");
125
+ var _runProjection = require("./runProjection.js");
126
+ var _runProjections = require("./runProjections.js");
127
+ var _superviseOnProgress = require("./superviseOnProgress.js");
128
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["_EventLogDurability","require","_foldIntoRef","_inMemoryProjectionStore","_ProjectionRunner","_ProjectionStore","_runProjection","_runProjections","_superviseOnProgress"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgLA,IAAAA,mBAAA,GAAAC,OAAA;AAKA,IAAAC,YAAA,GAAAD,OAAA;AACA,IAAAE,wBAAA,GAAAF,OAAA;AACA,IAAAG,iBAAA,GAAAH,OAAA;AAOA,IAAAI,gBAAA,GAAAJ,OAAA;AAaA,IAAAK,cAAA,GAAAL,OAAA;AAKA,IAAAM,eAAA,GAAAN,OAAA;AAKA,IAAAO,oBAAA,GAAAP,OAAA","ignoreList":[]}