@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.
- package/LICENSE +28 -0
- package/README.md +538 -0
- package/dist/cjs/EventLogDurability.js +184 -0
- package/dist/cjs/EventLogDurability.js.map +1 -0
- package/dist/cjs/ProjectionRunner.js +478 -0
- package/dist/cjs/ProjectionRunner.js.map +1 -0
- package/dist/cjs/ProjectionStore.js +233 -0
- package/dist/cjs/ProjectionStore.js.map +1 -0
- package/dist/cjs/foldIntoRef.js +36 -0
- package/dist/cjs/foldIntoRef.js.map +1 -0
- package/dist/cjs/inMemoryProjectionStore.js +138 -0
- package/dist/cjs/inMemoryProjectionStore.js.map +1 -0
- package/dist/cjs/index.js +128 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/projectionWiringFault.js +532 -0
- package/dist/cjs/projectionWiringFault.js.map +1 -0
- package/dist/cjs/runProjection.js +117 -0
- package/dist/cjs/runProjection.js.map +1 -0
- package/dist/cjs/runProjections.js +144 -0
- package/dist/cjs/runProjections.js.map +1 -0
- package/dist/cjs/superviseOnProgress.js +580 -0
- package/dist/cjs/superviseOnProgress.js.map +1 -0
- package/dist/cjs/testing.js +143 -0
- package/dist/cjs/testing.js.map +1 -0
- package/dist/dts/EventLogDurability.d.ts +182 -0
- package/dist/dts/EventLogDurability.d.ts.map +1 -0
- package/dist/dts/ProjectionRunner.d.ts +557 -0
- package/dist/dts/ProjectionRunner.d.ts.map +1 -0
- package/dist/dts/ProjectionStore.d.ts +475 -0
- package/dist/dts/ProjectionStore.d.ts.map +1 -0
- package/dist/dts/foldIntoRef.d.ts +39 -0
- package/dist/dts/foldIntoRef.d.ts.map +1 -0
- package/dist/dts/inMemoryProjectionStore.d.ts +11 -0
- package/dist/dts/inMemoryProjectionStore.d.ts.map +1 -0
- package/dist/dts/index.d.ts +185 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/projectionWiringFault.d.ts +260 -0
- package/dist/dts/projectionWiringFault.d.ts.map +1 -0
- package/dist/dts/runProjection.d.ts +185 -0
- package/dist/dts/runProjection.d.ts.map +1 -0
- package/dist/dts/runProjections.d.ts +480 -0
- package/dist/dts/runProjections.d.ts.map +1 -0
- package/dist/dts/superviseOnProgress.d.ts +587 -0
- package/dist/dts/superviseOnProgress.d.ts.map +1 -0
- package/dist/dts/testing.d.ts +207 -0
- package/dist/dts/testing.d.ts.map +1 -0
- package/dist/esm/EventLogDurability.js +175 -0
- package/dist/esm/EventLogDurability.js.map +1 -0
- package/dist/esm/ProjectionRunner.js +468 -0
- package/dist/esm/ProjectionRunner.js.map +1 -0
- package/dist/esm/ProjectionStore.js +223 -0
- package/dist/esm/ProjectionStore.js.map +1 -0
- package/dist/esm/foldIntoRef.js +29 -0
- package/dist/esm/foldIntoRef.js.map +1 -0
- package/dist/esm/inMemoryProjectionStore.js +131 -0
- package/dist/esm/inMemoryProjectionStore.js.map +1 -0
- package/dist/esm/index.js +185 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/dist/esm/projectionWiringFault.js +524 -0
- package/dist/esm/projectionWiringFault.js.map +1 -0
- package/dist/esm/runProjection.js +109 -0
- package/dist/esm/runProjection.js.map +1 -0
- package/dist/esm/runProjections.js +137 -0
- package/dist/esm/runProjections.js.map +1 -0
- package/dist/esm/superviseOnProgress.js +571 -0
- package/dist/esm/superviseOnProgress.js.map +1 -0
- package/dist/esm/testing.js +133 -0
- package/dist/esm/testing.js.map +1 -0
- package/package.json +41 -0
- package/src/EventLogDurability.ts +201 -0
- package/src/ProjectionRunner.ts +923 -0
- package/src/ProjectionStore.ts +528 -0
- package/src/foldIntoRef.ts +63 -0
- package/src/inMemoryProjectionStore.ts +163 -0
- package/src/index.ts +218 -0
- package/src/projectionWiringFault.ts +694 -0
- package/src/runProjection.ts +270 -0
- package/src/runProjections.ts +623 -0
- package/src/superviseOnProgress.ts +897 -0
- package/src/testing.ts +290 -0
- package/testing/package.json +6 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.projectionWiringFault = void 0;
|
|
7
|
+
var _core = require("@kairos-es/core");
|
|
8
|
+
var _effect = require("effect");
|
|
9
|
+
var _EventLogDurability = require("./EventLogDurability.js");
|
|
10
|
+
/**
|
|
11
|
+
* The read side's CONSTRUCTION GATE: every wiring the read side refuses ON ITS OWN
|
|
12
|
+
* RULES, judged in one place, in one declared order, each verdict one
|
|
13
|
+
* `string | undefined`.
|
|
14
|
+
*
|
|
15
|
+
* ## ONE entry point over five rules, at three arities
|
|
16
|
+
*
|
|
17
|
+
* `projectionWiringFault` judges ONE CALL: the log the whole graph subscribes to,
|
|
18
|
+
* the one composed query and the one slice record every materialisation shares, and
|
|
19
|
+
* a LIST of the resolved per-entry wirings. `runProjection` hands it a singleton and
|
|
20
|
+
* `runProjections` hands it N, and both do so before either forks anything.
|
|
21
|
+
*
|
|
22
|
+
* The rules divide by what they are a function of, not by who called:
|
|
23
|
+
*
|
|
24
|
+
* - the COLLISION rung is a function of the whole list, pairwise;
|
|
25
|
+
* - R2 and the tuning figures are functions of ONE entry, judged for every entry in
|
|
26
|
+
* turn;
|
|
27
|
+
* - the servable grammar and the per-slice tag rule are functions of the SHARED
|
|
28
|
+
* query and the SHARED slice record, so they are judged once for the call.
|
|
29
|
+
*
|
|
30
|
+
* One record with the shared fields stated once and the entries as a list is what
|
|
31
|
+
* lets all three arities compose in one `??` chain. What the gate is stays what it
|
|
32
|
+
* was: one home, one return type, one declared order (below), and a caller that
|
|
33
|
+
* classifies the verdict under a preamble it was handed.
|
|
34
|
+
*
|
|
35
|
+
* ## The ONE refusal that is deliberately not here
|
|
36
|
+
*
|
|
37
|
+
* Naming it is what keeps "one place" honest. A slice record holding two DISTINCT
|
|
38
|
+
* `Schema`s under one event type is refused by `@kairos-es/codec`'s
|
|
39
|
+
* `decodeSlices`, which `prepareProjection` calls a few lines BELOW this gate on
|
|
40
|
+
* behalf of both entry points — so a rejected record still dies as a defect with
|
|
41
|
+
* nothing forked, exactly as a rejected wiring does, but the sentence a reader
|
|
42
|
+
* meets is the codec's. That rule
|
|
43
|
+
* belongs to the codec because BOTH of its consumers need it and only one is on
|
|
44
|
+
* the read side: the write path reaches the same merge through
|
|
45
|
+
* `buildDecisionModel` and never comes near this module, so a rung here
|
|
46
|
+
* would be a second home for one rule, covering half the hazard. `decode.ts` owns
|
|
47
|
+
* the argument; this is a pointer, not a restatement.
|
|
48
|
+
*
|
|
49
|
+
* Which of the two a record wrong in BOTH ways meets FIRST is not this module's to
|
|
50
|
+
* state either, and it has exactly one home: `prepareProjection` in
|
|
51
|
+
* `ProjectionRunner.ts`, the single body in which the gate call sits above the
|
|
52
|
+
* `decodeSlices` call, for both entry points at once. It was a claim two function
|
|
53
|
+
* bodies each made in prose until that phase was extracted, and pinned for only one
|
|
54
|
+
* of them.
|
|
55
|
+
*
|
|
56
|
+
* ## Why this is a module and not a paragraph of `runProjection`
|
|
57
|
+
*
|
|
58
|
+
* `runProjection` was doing two unrelated jobs. One is to refuse a wiring that can
|
|
59
|
+
* only ever be wrong — five rules, none of which mentions a stream, a scope or a
|
|
60
|
+
* fibre, and only one of which so much as compares two stores for identity. The
|
|
61
|
+
* other is to build and fork a pipeline. The refusals had
|
|
62
|
+
* grown to roughly two fifths of that function's body and most of its doc, and
|
|
63
|
+
* three of them wrote their own `Effect.die` with their own copy of the preamble
|
|
64
|
+
* naming the projection.
|
|
65
|
+
*
|
|
66
|
+
* The precedent is this package's own: `superviseOnProgress` was a deep module
|
|
67
|
+
* trapped inside the same function, and pulling it out is what made a supervision
|
|
68
|
+
* quantity pinnable against a stub instead of through the whole pipeline. This is
|
|
69
|
+
* the other half of that function, and it is deeper still by the measure that
|
|
70
|
+
* matters here — its interface is a record in and a sentence out, while everything
|
|
71
|
+
* behind it is five unrelated rules over four vocabularies (a layer graph, a
|
|
72
|
+
* tuning literal, a query grammar, a set of view stores).
|
|
73
|
+
*
|
|
74
|
+
* The fifth rule arriving from a DIFFERENT caller is what the module having been
|
|
75
|
+
* extracted bought. `runProjections` needed a construction-time refusal of its own,
|
|
76
|
+
* and there was already a place for it — with a settled return type, a settled
|
|
77
|
+
* division of labour with its caller, and a suite that states a rule against a
|
|
78
|
+
* record literal. Inline, it would have been a sixth thing inside a second pipeline
|
|
79
|
+
* function, in a second style, with a second preamble. It landed as a RUNG of the
|
|
80
|
+
* one chain rather than as a second entry point, which is what keeps the declared
|
|
81
|
+
* order below a property of this file rather than of which caller a reader opened.
|
|
82
|
+
*
|
|
83
|
+
* ## Why `string | undefined`, and why the shared record takes no `CheckpointKey`
|
|
84
|
+
*
|
|
85
|
+
* `undefined` for a sound wiring, otherwise the SPECIFIC sentence: what is wrong,
|
|
86
|
+
* what it would do, and how to fix it. It does not throw, does not call `Effect.die`
|
|
87
|
+
* and does not return an `Either` — ONE exit route and one return type, which is
|
|
88
|
+
* what lets five rules over four vocabularies compose with `??` and be read as five
|
|
89
|
+
* lines.
|
|
90
|
+
*
|
|
91
|
+
* The shared half of the record names no projection and no partition. The per-entry
|
|
92
|
+
* list does carry a resolved `CheckpointKey` each, but for the collision rung to
|
|
93
|
+
* COMPARE rather than for anything to report: no rule interpolates a `key` into its
|
|
94
|
+
* sentence except the collision rung, which names the partition the two offenders
|
|
95
|
+
* share.
|
|
96
|
+
*
|
|
97
|
+
* ATTRIBUTION and CLASSIFICATION both sit outside. This gate's ONE caller,
|
|
98
|
+
* `prepareProjection`, owns the single `Effect.die`; the PREAMBLE naming the
|
|
99
|
+
* projection (and, where there is one to name, the partition) comes from one level
|
|
100
|
+
* further out still, from whichever entry point was called. So the preamble is
|
|
101
|
+
* written once per ENTRY POINT rather than once per rule — which is exactly what
|
|
102
|
+
* collapsed three `Effect.die` sites to one. The gate
|
|
103
|
+
* writes the third party to that sentence: where there is more than one entry, the
|
|
104
|
+
* per-entry rungs prefix their verdict with the offending INDEX, in the same
|
|
105
|
+
* `materialisations[i]` vocabulary the collision rung already uses. Over a singleton
|
|
106
|
+
* there is no index to name and none is written, so a single wiring's message is
|
|
107
|
+
* what it always was. The same arrangement repeats one level DOWN, inside this file:
|
|
108
|
+
* `positiveFiniteDuration` owns the `Duration` bound and each of the four tuning
|
|
109
|
+
* call sites supplies the `role` clause, because the predicate knows the rule and
|
|
110
|
+
* its caller knows what breaks.
|
|
111
|
+
*
|
|
112
|
+
* The classification half of that is worth spelling out. That a mis-wiring is a
|
|
113
|
+
* DEFECT rather
|
|
114
|
+
* than a channel value is a claim about `runProjection`'s and `runProjections`'
|
|
115
|
+
* signatures — both error channels are `never` because everything that can go wrong
|
|
116
|
+
* once a projection is RUNNING belongs to the supervisor — and a gate returning a
|
|
117
|
+
* string makes no such claim, so it can be called from a plain function, a test, or
|
|
118
|
+
* an `Effect.gen` without three different failure conventions.
|
|
119
|
+
*
|
|
120
|
+
* ## The ORDER is a decision, and this is the only place it is stated
|
|
121
|
+
*
|
|
122
|
+
* The rules run OUTWARD-IN, from the widest wiring decision to the narrowest.
|
|
123
|
+
*
|
|
124
|
+
* The order is over RULES and not over ENTRIES. Entry 1's layer-graph mistake is
|
|
125
|
+
* still wider than entry 0's options-literal mistake, so rung 2 is judged for every
|
|
126
|
+
* entry before rung 3 is judged for any. An entry-major loop would instead let ARRAY
|
|
127
|
+
* POSITION decide which fault an author sees, and position carries no meaning here:
|
|
128
|
+
* the entries are a set, and are a tuple only because inference needs one.
|
|
129
|
+
*
|
|
130
|
+
* 1. **The collision rung**, once over the whole SET. Two materialisations sharing
|
|
131
|
+
* one store under one key is a property of the WHOLE CALL rather than of any one
|
|
132
|
+
* wiring in it, so it is judged before any single wiring is looked at — the same
|
|
133
|
+
* outward-in principle, one level up. And a colliding pair is worth hearing about
|
|
134
|
+
* BEFORE a mis-tuned figure in one of them, because fixing the tuning would leave
|
|
135
|
+
* the two runners racing one cursor. It cannot fire below N = 2, so it is vacuous
|
|
136
|
+
* for the singleton `runProjection` passes.
|
|
137
|
+
* 2. **R2, durability ordering**, PER ENTRY in entry order. A property of the LAYER
|
|
138
|
+
* GRAPH — which log, which view store — decided before any read model existed. If
|
|
139
|
+
* that pairing is illegal then nothing else about the runner matters, because the
|
|
140
|
+
* view it maintains cannot be true however well tuned the daemon is.
|
|
141
|
+
* 3. **The tuning figures**, PER ENTRY in entry order. What the caller wrote in the
|
|
142
|
+
* options literal, a smaller and more local mistake than picking the wrong pair
|
|
143
|
+
* of layers.
|
|
144
|
+
* 4. **The store's own servable grammar**, once over the SHARED composed query,
|
|
145
|
+
* reported in the STORE's vocabulary by `core`'s own `assertServableQuery`.
|
|
146
|
+
* 5. **The read side's stricter per-slice tag rule**, once over the SHARED slice
|
|
147
|
+
* record, and it MUST stay after (4): it catches only what that grammar admits,
|
|
148
|
+
* since a lone type-only item is servable. Were it first, a tagless-plus-tagged
|
|
149
|
+
* union — a violation of the shared grammar — would be reported as a read-side
|
|
150
|
+
* slice convention instead of in the vocabulary every other caller of that
|
|
151
|
+
* grammar meets it in.
|
|
152
|
+
*
|
|
153
|
+
* Hoisting the two SHARED rungs ahead of the two per-entry ones, which the
|
|
154
|
+
* shared-versus-per-entry axis would suggest, was considered and rejected: it
|
|
155
|
+
* changes the message a single wiring wrong in two ways gets, which is a fixed
|
|
156
|
+
* constraint. (2)-(3) ahead of (4)-(5) also restores the relative order that held
|
|
157
|
+
* before the supervisor was extracted, when its three restart durations were checked
|
|
158
|
+
* with `batchWindow` rather than a module away. But the point of writing the order
|
|
159
|
+
* down is not the restoration: it is that the order is now FIVE LINES a reader can
|
|
160
|
+
* read, rather than an emergent property of which of two modules happens to look at
|
|
161
|
+
* a field first.
|
|
162
|
+
*
|
|
163
|
+
* ## Why the input is a record of ALREADY-RESOLVED values
|
|
164
|
+
*
|
|
165
|
+
* Every field is a value, not an option: the caller has already applied its
|
|
166
|
+
* defaults — through `resolveProjection`, the one resolution site both callers
|
|
167
|
+
* share — so the gate judges exactly the figures that will run and cannot be handed
|
|
168
|
+
* one default while the pipeline uses another. That also holds the defaults
|
|
169
|
+
* themselves to the rules they document, for one decode per runner. The keys are
|
|
170
|
+
* RESOLVED `CheckpointKey`s for the same reason and one more: a partition a caller
|
|
171
|
+
* omitted is `DEFAULT_PARTITION`, so a rung judging the raw options would miss the
|
|
172
|
+
* commonest form of the collision, which is two entries that name no partition at
|
|
173
|
+
* all.
|
|
174
|
+
*
|
|
175
|
+
* The two INTEGER options are absent from the record on purpose. `BatchSize` and
|
|
176
|
+
* `MaxNoProgressRestarts` are branded, so their boundary is the caller's own
|
|
177
|
+
* `.make(...)` — a bad value is refused a stack frame away from where it was
|
|
178
|
+
* written and never reaches here at all.
|
|
179
|
+
*
|
|
180
|
+
* `slices` is a SHARED field and is typed as narrowly as the check needs — a query
|
|
181
|
+
* per slice, nothing else — which keeps this module free of a `@kairos-es/codec`
|
|
182
|
+
* import for a rule that has no opinion about schemas, payloads or folds.
|
|
183
|
+
* `Record<string, SliceProjection>` satisfies it structurally, so the record an entry
|
|
184
|
+
* point was handed reaches this rung unconverted.
|
|
185
|
+
*
|
|
186
|
+
* An entry's `cursorIdentity` is typed `object` and never called: it is an identity
|
|
187
|
+
* token the collision rung compares by REFERENCE. `object` rather than `ProjectionStore`
|
|
188
|
+
* because a `KeyedProjectionStore` is not structurally one (`readCheckpoint` is an
|
|
189
|
+
* `Effect` on the façade and a function on the port), so a caller holding only a
|
|
190
|
+
* bound view could not fill the field; and because `object` forbids `undefined`,
|
|
191
|
+
* which would otherwise make two unidentified entries compare equal.
|
|
192
|
+
*
|
|
193
|
+
* It is INTERNAL: nothing here is re-exported from the package index. The gate is
|
|
194
|
+
* how the two runner entry points refuse a wiring, not a capability the library
|
|
195
|
+
* offers — a caller with a wiring to validate has `runProjection` or
|
|
196
|
+
* `runProjections` itself. BOTH judge all five rules before either forks anything.
|
|
197
|
+
*/
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* One `Duration.DurationInput` option, decoded and bounded: the `Duration` if it
|
|
201
|
+
* is positive and finite, otherwise the sentence explaining what it is and what
|
|
202
|
+
* that would do.
|
|
203
|
+
*
|
|
204
|
+
* FILE-PRIVATE, and applied four times, all of them by `tuningFault` below. It owns
|
|
205
|
+
* the BOUND and the shape of the sentence; each call site owns `role`, the clause
|
|
206
|
+
* saying what THIS option does and therefore what breaks at each degenerate end,
|
|
207
|
+
* because a generic "must be positive and finite" tells an author the rule but not
|
|
208
|
+
* the consequence, and the consequence is what makes the fix obvious. So the four
|
|
209
|
+
* call sites differ by exactly the thing that differs between them, and one bound is
|
|
210
|
+
* not stated four ways — the module doc's division, nested one level down.
|
|
211
|
+
*
|
|
212
|
+
* Three things about the checks are worth knowing rather than rediscovering:
|
|
213
|
+
*
|
|
214
|
+
* - **`Duration.decodeUnknown`, not `Duration.decode`.** The latter THROWS
|
|
215
|
+
* `Error('Invalid DurationInput')` on an unparseable string, which inside
|
|
216
|
+
* `Effect.gen` would already become a defect — but one whose message names
|
|
217
|
+
* neither the option nor the value. The `Option`-returning form (`decodeUnknown`
|
|
218
|
+
* is `Option.liftThrowable(decode)`) lets the fault carry both.
|
|
219
|
+
* - **`Duration.isZero` covers NEGATIVES too.** Every `Duration` constructor routes
|
|
220
|
+
* through one internal `make` that maps a non-positive `number` or `bigint` to the
|
|
221
|
+
* zero value, and the `[seconds, nanos]` form maps a `-Infinity` component to zero
|
|
222
|
+
* outright (verified against `effect@3.22`'s `Duration.ts`), so `-5`,
|
|
223
|
+
* `'-5 millis'` and `0` are indistinguishable by the time they are a `Duration`.
|
|
224
|
+
* There is therefore no separate "negative" case to test for — and the zero
|
|
225
|
+
* message has to SAY that, because a reader told "restartMinDelay is zero" while
|
|
226
|
+
* looking at `'-5 millis'` in their own wiring would otherwise conclude the check
|
|
227
|
+
* was reading the wrong field.
|
|
228
|
+
* - **Infinity is rejected everywhere, including `restartResetAfter`.** An infinite
|
|
229
|
+
* figure in any of these positions stops a TIMING mechanism from being one, and
|
|
230
|
+
* every claim the runner's docs make — a latency ceiling, a bounded backoff, a
|
|
231
|
+
* five-minute budget — stops being true. "Effectively never" remains expressible
|
|
232
|
+
* as a large finite figure, so a uniform rule costs a caller nothing and is one
|
|
233
|
+
* predicate rather than four. It needs its own check: `Duration.isZero` answers
|
|
234
|
+
* `false` for the infinite `Duration`, so the two bounds are independent.
|
|
235
|
+
*
|
|
236
|
+
* `String(input)`, never `JSON.stringify`, for the same reason `superviseOnProgress`
|
|
237
|
+
* reduces its log annotations with `String`: `DurationInput` admits a `bigint` of
|
|
238
|
+
* nanoseconds, and `JSON.stringify` THROWS on one — inside the very code path whose
|
|
239
|
+
* job is to explain a mistake clearly.
|
|
240
|
+
*/
|
|
241
|
+
const positiveFiniteDuration = (name, input, role) => {
|
|
242
|
+
const decoded = _effect.Duration.decodeUnknown(input);
|
|
243
|
+
if (_effect.Option.isNone(decoded)) {
|
|
244
|
+
return _effect.Either.left(`${name} is not a Duration.DurationInput (got ${String(input)}). Pass a ` + "string like '50 millis', a Duration, a number of milliseconds, a " + 'bigint of nanoseconds, or a [seconds, nanos] tuple.');
|
|
245
|
+
}
|
|
246
|
+
if (!_effect.Duration.isFinite(decoded.value)) {
|
|
247
|
+
return _effect.Either.left(`${name} is infinite (got ${String(input)}). ${role}`);
|
|
248
|
+
}
|
|
249
|
+
if (_effect.Duration.isZero(decoded.value)) {
|
|
250
|
+
return _effect.Either.left(`${name} is zero (got ${String(input)}; Duration clamps any negative ` + `input to zero, so a negative arrives here as zero too). ${role}`);
|
|
251
|
+
}
|
|
252
|
+
return _effect.Either.right(decoded.value);
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* The runner's four `Duration`s, decoded and bounded: `undefined` when the tuning
|
|
256
|
+
* is sound, otherwise the sentence naming the FIRST violation.
|
|
257
|
+
*
|
|
258
|
+
* ## WHY these are checked at all when both integer options are branded
|
|
259
|
+
*
|
|
260
|
+
* `Duration.DurationInput` is a UNION — a `Duration`, millis as a `number`, nanos
|
|
261
|
+
* as a `bigint`, a `[seconds, nanos]` tuple, or a `'50 millis'` string — so there
|
|
262
|
+
* is no single primitive for `Schema.Int`-plus-`Schema.brand` to refine, and
|
|
263
|
+
* validity is only knowable after `Duration.decode` runs. Branding one anyway would
|
|
264
|
+
* mean one of two losses: a constructor every call site must thread a raw input
|
|
265
|
+
* through, or a field narrowed to `Duration` alone, which makes
|
|
266
|
+
* `batchWindow: '50 millis'` — the ergonomic point of `DurationInput`, and what
|
|
267
|
+
* every test and example in this repo writes — unwritable. So the check happens at
|
|
268
|
+
* CONSTRUCTION instead, where the caller turns it into a defect. Same discipline as
|
|
269
|
+
* `BatchSize` and `MaxNoProgressRestarts` (fail at the boundary, report where the
|
|
270
|
+
* author can act), different mechanism, because the type is a different shape.
|
|
271
|
+
*
|
|
272
|
+
* ## Why all four together, when they tune two different machines
|
|
273
|
+
*
|
|
274
|
+
* `batchWindow` paces the PIPELINE and the three restart figures pace the
|
|
275
|
+
* SUPERVISOR, and those two modules deliberately know nothing of each other. But
|
|
276
|
+
* the mistake is one mistake — a degenerate figure whose failure mode is a hot loop
|
|
277
|
+
* or a permanently frozen view with nothing on any error channel — and an author
|
|
278
|
+
* who wrote `restartMinDelay: 0` beside `batchWindow: 0` should not have to fix one
|
|
279
|
+
* and re-run to be told about the other by a differently-worded message from a
|
|
280
|
+
* different module. `Either.all` short-circuits on the first violation, so one
|
|
281
|
+
* degenerate figure is one sentence naming one option; what the single home buys is
|
|
282
|
+
* that the sentence is built the same way whichever field it is about.
|
|
283
|
+
*
|
|
284
|
+
* ## The one CROSS-FIELD rule, and the one deliberately absent
|
|
285
|
+
*
|
|
286
|
+
* `restartMinDelay <= restartMaxDelay` is checked, in the spirit of
|
|
287
|
+
* `ReadPostgresConfig`'s own cross-field filter: the names assert an ordering, and
|
|
288
|
+
* inverting it is silently absorbed rather than rejected. `Schedule.union` takes the
|
|
289
|
+
* SHORTER of its two arms, so a min above the max deletes the exponential
|
|
290
|
+
* entirely — every restart sleeps exactly `restartMaxDelay`, the documented sawtooth
|
|
291
|
+
* never happens, and the no-progress budget is off by however far apart the two
|
|
292
|
+
* figures are. Equality is allowed: min == max is a deliberate flat pacing, not a
|
|
293
|
+
* contradiction.
|
|
294
|
+
*
|
|
295
|
+
* `restartResetAfter` is deliberately NOT cross-checked against the delays,
|
|
296
|
+
* although a reset threshold below `restartMinDelay` does flatten the backoff (the
|
|
297
|
+
* schedule resets before it can grow). The difference is that no ordering is implied
|
|
298
|
+
* there — the two figures measure different things, elapsed retrying time against
|
|
299
|
+
* one sleep — so a short reset is a tuning choice expressible another way
|
|
300
|
+
* (min == max), whereas min above max is a statement that contradicts itself.
|
|
301
|
+
* Rejecting the former would be inventing a policy rather than enforcing a rule.
|
|
302
|
+
*
|
|
303
|
+
* The words `invalid tuning` are this sentence's own, not the caller's preamble's:
|
|
304
|
+
* the preamble says which projection is mis-WIRED, and this says the wiring fault is
|
|
305
|
+
* a mis-set figure rather than a bad pair of layers or a mis-built slice. They are a
|
|
306
|
+
* CONSTANT rather than a literal at each of the two return sites below, so the phrase
|
|
307
|
+
* an operator greps for is written once even though the rule is one function.
|
|
308
|
+
*/
|
|
309
|
+
const MIS_TUNED = 'invalid tuning —';
|
|
310
|
+
const tuningFault = tuning => {
|
|
311
|
+
const checked = _effect.Either.all({
|
|
312
|
+
batchWindow: positiveFiniteDuration('batchWindow', tuning.batchWindow, 'batchWindow is how long a PARTIAL batch waits before committing anyway: ' + 'at zero Stream.groupedWithin spins, emitting empty chunks without ever ' + 'pulling the subscription; infinite means a partial batch never flushes, ' + 'so a lone live event waits for batchSize-1 more events that may never ' + 'come.'),
|
|
313
|
+
restartMinDelay: positiveFiniteDuration('restartMinDelay', tuning.restartMinDelay, 'restartMinDelay is the BASE Schedule.exponential multiplies: at zero ' + 'every delay in the series is zero and the supervisor restarts in a hot ' + 'loop; infinite means the first restart never arrives.'),
|
|
314
|
+
restartMaxDelay: positiveFiniteDuration('restartMaxDelay', tuning.restartMaxDelay, 'restartMaxDelay is the backoff CEILING, unioned with the exponential: ' + 'Schedule.union takes the shorter arm, so at zero it caps every sleep to ' + 'nothing and the supervisor restarts in a hot loop; infinite removes the ' + 'cap and lets the exponential grow without bound.'),
|
|
315
|
+
restartResetAfter: positiveFiniteDuration('restartResetAfter', tuning.restartResetAfter, 'restartResetAfter is the elapsed-retrying threshold at which the backoff ' + 'starts over: at zero it resets on every decision so the backoff never ' + 'grows, and infinite means it never resets — either way the sawtooth is ' + "gone, and the no-progress budget's default is sized in WALL CLOCK " + 'against that sawtooth, so it would no longer buy the minutes it ' + 'documents.')
|
|
316
|
+
});
|
|
317
|
+
if (_effect.Either.isLeft(checked)) {
|
|
318
|
+
return `${MIS_TUNED} ${checked.left}`;
|
|
319
|
+
}
|
|
320
|
+
if (_effect.Duration.greaterThan(checked.right.restartMinDelay, checked.right.restartMaxDelay)) {
|
|
321
|
+
return `${MIS_TUNED} restartMinDelay ` + `(${_effect.Duration.format(checked.right.restartMinDelay)}) is greater than ` + `restartMaxDelay (${_effect.Duration.format(checked.right.restartMaxDelay)}). ` + 'Schedule.union takes the SHORTER of its two arms, so an inverted pair ' + 'silences the exponential entirely: every restart would sleep exactly ' + 'restartMaxDelay, the documented sawtooth would not happen, and the ' + 'no-progress budget would be wrong by however far apart the two figures ' + 'are. Swap them, or raise the ceiling.';
|
|
322
|
+
}
|
|
323
|
+
return undefined;
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* The composed subscription query against the STORE's servable grammar:
|
|
327
|
+
* `undefined` when the store would serve it, otherwise `core`'s own sentence about
|
|
328
|
+
* why it would not.
|
|
329
|
+
*
|
|
330
|
+
* ## Why `core`'s `assertServableQuery` and not a predicate of our own
|
|
331
|
+
*
|
|
332
|
+
* It is the very function every engine applies inside `read`/`subscribe`/`append`,
|
|
333
|
+
* so calling it is what makes ONE grammar rather than two that agree until one is
|
|
334
|
+
* edited. A local re-implementation would be a second definition of servability
|
|
335
|
+
* living in a package that does not own the concept, free to drift in either
|
|
336
|
+
* direction: too strict and it refuses a query the store would happily serve, too
|
|
337
|
+
* lax and the violation resurfaces at the first stream pull, inside the forked
|
|
338
|
+
* fibre, as a `PipelineDied` that only becomes visible as a `ProjectionStalled`
|
|
339
|
+
* once the breaker trips on a checkpoint that never moved — several backoff sleeps
|
|
340
|
+
* later, describing a stall rather than the mis-built slice that caused it.
|
|
341
|
+
*
|
|
342
|
+
* ## Why the THROW is converted rather than propagated
|
|
343
|
+
*
|
|
344
|
+
* `assertServableQuery` throws, because its own callers are store methods where a
|
|
345
|
+
* throw inside an `Effect` is already the defect it should be. Here it is one of
|
|
346
|
+
* five rules whose results compose, so letting it throw would give this gate two
|
|
347
|
+
* exit routes — a returned sentence for four rules and an exception for the
|
|
348
|
+
* fifth — and every caller would then need both a `??` chain and a `try`. Catching
|
|
349
|
+
* it keeps ONE exit route, and the caller keeps one `Effect.die`.
|
|
350
|
+
*
|
|
351
|
+
* The message is passed through VERBATIM, which is the point of the conversion
|
|
352
|
+
* rather than a detail of it: the violation goes on being reported in the store's
|
|
353
|
+
* vocabulary, in the same words the same rule produces when a store rejects the
|
|
354
|
+
* same query, so an author who meets it twice meets it once. The `instanceof`
|
|
355
|
+
* fallback is for the shape of the contract rather than for anything observed —
|
|
356
|
+
* `throw` admits any value, and `String(error)` is the honest reading of one this
|
|
357
|
+
* module cannot inspect.
|
|
358
|
+
*/
|
|
359
|
+
const servableQueryFault = wiring => {
|
|
360
|
+
try {
|
|
361
|
+
(0, _core.assertServableQuery)(wiring.query);
|
|
362
|
+
return undefined;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
return error instanceof Error ? error.message : String(error);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* The read side's OWN per-slice rule: `undefined` when every slice carries at least
|
|
369
|
+
* one tag, otherwise the sentence naming the first slice that does not.
|
|
370
|
+
*
|
|
371
|
+
* Checked slice by slice because the composed grammar above cannot see it: a SINGLE
|
|
372
|
+
* tagless slice composes to one type-only item, which IS servable, so the store
|
|
373
|
+
* would run that subscription happily. It is still a programming error, for two
|
|
374
|
+
* reasons. It does not COMPOSE — adding a second, tagged slice later turns today's
|
|
375
|
+
* accepted query into a tagless-plus-tagged union the store rejects, so the mistake
|
|
376
|
+
* would surface as a break in an unrelated change rather than at the slice that
|
|
377
|
+
* caused it. And a read model that genuinely is global should SAY so with an
|
|
378
|
+
* explicit `system:…` tag, which is the convention every other boundary in the repo
|
|
379
|
+
* already follows (CONTEXT); saying it by omission reads as a forgotten tag.
|
|
380
|
+
*
|
|
381
|
+
* The sentence names the offending SLICE, the rule and the fix, and deliberately
|
|
382
|
+
* not the projection: the caller's preamble carries that, for every rule at once,
|
|
383
|
+
* together with the partition where the caller has one worth naming.
|
|
384
|
+
*/
|
|
385
|
+
const taglessSliceFault = wiring => {
|
|
386
|
+
for (const [name, slice] of Object.entries(wiring.slices)) {
|
|
387
|
+
const untagged = slice.query.items.length === 0 || slice.query.items.some(item => item.tags.length === 0);
|
|
388
|
+
if (untagged) {
|
|
389
|
+
return `read-model slice '${name}' carries NO tags. Every read-model slice ` + 'must carry at least one tag: build it with .build({ tags: [...] }), ' + 'and give a genuinely GLOBAL slice an explicit system tag ' + "(tagIdentity('system').make('…')) rather than no tag at all. A " + 'tagless slice is servable on its own — it is the single type-only fast ' + 'path — which is exactly why it is rejected here rather than by the ' + 'store: composing it with any tagged slice later would produce an ' + 'unservable tagless-plus-tagged union query, so the mistake would ' + 'surface far from its cause.';
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return undefined;
|
|
393
|
+
};
|
|
394
|
+
/**
|
|
395
|
+
* Apply a PER-ENTRY rule to every entry in turn, returning the first sentence one of
|
|
396
|
+
* them yields — prefixed with the offending index where, and only where, there is
|
|
397
|
+
* more than one entry to index into.
|
|
398
|
+
*
|
|
399
|
+
* The condition is what keeps a single wiring's message byte-identical to what it
|
|
400
|
+
* was before the two callers shared this chain, and it keeps the module internally
|
|
401
|
+
* consistent: an index appears exactly where there is a set. The collision rung
|
|
402
|
+
* needs no such condition because it cannot fire below N = 2, so its indices are
|
|
403
|
+
* always meaningful.
|
|
404
|
+
*
|
|
405
|
+
* The prefix copies the collision rung's own vocabulary — `materialisations[i]` —
|
|
406
|
+
* rather than inventing a second way to name an entry, so an author who meets both
|
|
407
|
+
* meets one.
|
|
408
|
+
*/
|
|
409
|
+
const perEntry = (entries, rule) => {
|
|
410
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
411
|
+
const fault = rule(entries[index]);
|
|
412
|
+
if (fault !== undefined) {
|
|
413
|
+
return entries.length > 1 ? `materialisations[${index}] — ${fault}` : fault;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return undefined;
|
|
417
|
+
};
|
|
418
|
+
/**
|
|
419
|
+
* Judge ONE CALL's whole wiring: `undefined` when it is sound, otherwise the
|
|
420
|
+
* sentence describing the FIRST rule it breaks.
|
|
421
|
+
*
|
|
422
|
+
* Five rules, `??`-chained in the outward-in order the module doc argues for and
|
|
423
|
+
* fixes. `??` rather than an array of predicates or a collected list of every
|
|
424
|
+
* violation, for two reasons: the first fault is the one to fix — the later rules
|
|
425
|
+
* judge a wiring whose wider decisions are already known to be wrong, so their
|
|
426
|
+
* verdicts would be noise — and a chain of five named calls is the whole
|
|
427
|
+
* implementation, which is what makes the declared order checkable against the code
|
|
428
|
+
* rather than merely asserted about it.
|
|
429
|
+
*
|
|
430
|
+
* The two PER-ENTRY rungs go through `perEntry`, which is what keeps the chain
|
|
431
|
+
* rule-major: every entry is judged by rung 2 before any entry is judged by rung 3.
|
|
432
|
+
*
|
|
433
|
+
* Every field is a RESOLVED value: see the module doc for why (and for why the two
|
|
434
|
+
* integer options are not here at all).
|
|
435
|
+
*/
|
|
436
|
+
const projectionWiringFault = wiring => materialisationCollisionFault(wiring) ?? perEntry(wiring.materialisations, entry => (0, _EventLogDurability.durabilityOrderingFault)({
|
|
437
|
+
eventLogDurability: wiring.eventLogDurability,
|
|
438
|
+
viewDurability: entry.viewDurability
|
|
439
|
+
})) ?? perEntry(wiring.materialisations, tuningFault) ?? servableQueryFault(wiring) ?? taglessSliceFault(wiring);
|
|
440
|
+
/**
|
|
441
|
+
* The SET-LEVEL rung: `undefined` when no two materialisations of one read model
|
|
442
|
+
* would fight over one cursor, otherwise the sentence naming the offending pair and
|
|
443
|
+
* the fix.
|
|
444
|
+
*
|
|
445
|
+
* It is the FIRST rung in the declared order despite being the last symbol in this
|
|
446
|
+
* file, which is layout rather than precedence: the four per-wiring predicates sit
|
|
447
|
+
* above the chain that composes them, and this one — the newest and by far the
|
|
448
|
+
* longest — sits below it rather than pushing them apart. The module doc argues the
|
|
449
|
+
* order.
|
|
450
|
+
*
|
|
451
|
+
* FILE-PRIVATE, like every other rung: the chain is the only entry point, so a
|
|
452
|
+
* caller cannot reach one rule without the four that are declared to precede or
|
|
453
|
+
* follow it. It keeps its NAME because ADR-0007 cites the predicate by name.
|
|
454
|
+
*
|
|
455
|
+
* ## The rule
|
|
456
|
+
*
|
|
457
|
+
* A fault when two entries share the SAME store REFERENCE — compared by identity,
|
|
458
|
+
* never called, which is why the field is typed `object` — and their resolved
|
|
459
|
+
* `CheckpointKey`s are equal in both halves. Both conditions are needed and
|
|
460
|
+
* neither is sufficient, which is the whole content of the rule and the reason it
|
|
461
|
+
* takes resolved keys rather than a caller's raw options.
|
|
462
|
+
*
|
|
463
|
+
* Why the pairing is a defect: a `CheckpointKey` addresses ONE cursor within ONE
|
|
464
|
+
* store, so two runners over that pair are two writers over one scalar guarded
|
|
465
|
+
* compare-and-set. Every batch races it; one wins, the loser's `commit` fails
|
|
466
|
+
* `CheckpointSuperseded` and its own view writes are aborted with it; the loser then
|
|
467
|
+
* resynchronises from the winner's position and does it again. Nothing errors out to
|
|
468
|
+
* an operator — the supervisor's breaker deliberately does NOT trip, because the
|
|
469
|
+
* shared signal keeps moving, which is exactly the competing-writer case
|
|
470
|
+
* `superviseOnProgress` is built to absorb — so the observable result is two views
|
|
471
|
+
* each holding an arbitrary subset of the events, indefinitely, at the schedule's
|
|
472
|
+
* rate. It is the "one MATERIALISATION, one runner" invariant of ADR-0007 — which
|
|
473
|
+
* that record's own amendment is careful to word that way rather than per READ
|
|
474
|
+
* MODEL, a read model being materialisable N ways on purpose — broken in the one way
|
|
475
|
+
* a single `runProjection` call cannot break it.
|
|
476
|
+
*
|
|
477
|
+
* ## PAIRWISE, deliberately
|
|
478
|
+
*
|
|
479
|
+
* Two nested loops over N, where N is the number of ways one read model is
|
|
480
|
+
* materialised — two or three in every shape this exists for, and bounded by how many
|
|
481
|
+
* view stores an application has. A map keyed on the store plus the flattened key
|
|
482
|
+
* would be asymptotically better and worse in every way that matters here: it would
|
|
483
|
+
* need a key flattening (with the separator-injection question `mapKey` in
|
|
484
|
+
* `inMemoryProjectionStore.ts` had to settle) and a composite map key that cannot
|
|
485
|
+
* hold an object identity, to save microseconds on a list of three at construction
|
|
486
|
+
* time. Pairwise says the rule in the shape the rule is written in.
|
|
487
|
+
*
|
|
488
|
+
* ## The two bounds, which are DECISIONS and not oversights
|
|
489
|
+
*
|
|
490
|
+
* It does not reject equal keys across DIFFERENT stores. That is the sound and
|
|
491
|
+
* intended shape, and the reason this rung needs the store identity at all: one read
|
|
492
|
+
* model realised N ways shares ONE `ProjectionId`, because a `CheckpointKey` is a
|
|
493
|
+
* lookup identifier within one store rather than a global name. Two stores holding
|
|
494
|
+
* the same key are two materialisations of one read model, each with its own cursor,
|
|
495
|
+
* which is precisely what `runProjections` is for —
|
|
496
|
+
* `packages/read-postgres/test/courseRosterGraduation.test.ts` asserts that key
|
|
497
|
+
* equality across an in-memory and a SQL store as its acceptance criterion.
|
|
498
|
+
*
|
|
499
|
+
* And it cannot detect two SEPARATELY CONSTRUCTED stores pointed at one physical
|
|
500
|
+
* namespace — two `makeSqlProjectionStore` calls on the same schema and table
|
|
501
|
+
* prefix, say. Reference equality is blind to that by construction, and the
|
|
502
|
+
* same class of limit is already recorded for the SQL backend's own schema isolation
|
|
503
|
+
* in `packages/read-postgres/src/internal/ddl.ts`, which can reject an identifier it
|
|
504
|
+
* would truncate but cannot know what another config points at. What reference
|
|
505
|
+
* equality does catch is the realistic mistake: ONE store value passed twice, which
|
|
506
|
+
* is what the ergonomic shape of the call invites — a caller who has one store to
|
|
507
|
+
* hand writes it into both entries.
|
|
508
|
+
*
|
|
509
|
+
* The sentence names the two entries by INDEX and the partition they share, states
|
|
510
|
+
* what the pairing would do, and gives both fixes — the one that is almost always
|
|
511
|
+
* wanted (one materialisation whose single `apply` writes both views inside the one
|
|
512
|
+
* `commit`) and the escape hatch that makes genuine cohabitation legal (a distinct
|
|
513
|
+
* `partition`). It names no projection: the caller's preamble carries that, for every
|
|
514
|
+
* rule at once.
|
|
515
|
+
*/
|
|
516
|
+
exports.projectionWiringFault = projectionWiringFault;
|
|
517
|
+
const materialisationCollisionFault = wiring => {
|
|
518
|
+
const {
|
|
519
|
+
materialisations
|
|
520
|
+
} = wiring;
|
|
521
|
+
for (let left = 0; left < materialisations.length; left += 1) {
|
|
522
|
+
for (let right = left + 1; right < materialisations.length; right += 1) {
|
|
523
|
+
const one = materialisations[left];
|
|
524
|
+
const other = materialisations[right];
|
|
525
|
+
if (one.cursorIdentity === other.cursorIdentity && one.key.projection === other.key.projection && one.key.partition === other.key.partition) {
|
|
526
|
+
return `colliding materialisations — materialisations[${left}] and ` + `materialisations[${right}] share ONE ProjectionStore under the SAME ` + `CheckpointKey (partition '${one.key.partition}'), so they would be two ` + 'runners over one cursor. Every batch would race the same guarded ' + "advance: one wins, the loser's commit fails CheckpointSuperseded and " + 'its own view writes are aborted with it, and because the shared ' + 'checkpoint keeps moving the supervisor never gives up — so the two ' + 'views would each hold an arbitrary subset of the events, ' + 'indefinitely, with nothing on any error channel. SEVERAL VIEWS IN ONE ' + 'STORE is a supported shape and this is not how to reach it: it wants ' + 'ONE materialisation whose single apply writes both views inside the ' + 'one commit, under one checkpoint. Where two materialisations genuinely ' + 'must cohabit one store, give one of them its own partition — a ' + 'distinct PartitionId is a distinct CheckpointKey and therefore a ' + 'cursor of its own.';
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return undefined;
|
|
531
|
+
};
|
|
532
|
+
//# sourceMappingURL=projectionWiringFault.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectionWiringFault.js","names":["_core","require","_effect","_EventLogDurability","positiveFiniteDuration","name","input","role","decoded","Duration","decodeUnknown","Option","isNone","Either","left","String","isFinite","value","isZero","right","MIS_TUNED","tuningFault","tuning","checked","all","batchWindow","restartMinDelay","restartMaxDelay","restartResetAfter","isLeft","greaterThan","format","undefined","servableQueryFault","wiring","assertServableQuery","query","error","Error","message","taglessSliceFault","slice","Object","entries","slices","untagged","items","length","some","item","tags","perEntry","rule","index","fault","projectionWiringFault","materialisationCollisionFault","materialisations","entry","durabilityOrderingFault","eventLogDurability","viewDurability","exports","one","other","cursorIdentity","key","projection","partition"],"sources":["../../src/projectionWiringFault.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
|