@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,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ProjectionStore` — the read side's single port: a scalar checkpoint plus the
|
|
3
|
+
* atomic bracket a read model's own view writes are committed INSIDE (ADR-0007).
|
|
4
|
+
*
|
|
5
|
+
* It is deliberately one port rather than two (a checkpoint store and a view
|
|
6
|
+
* store), because the whole exactly-once property of a projection is that the
|
|
7
|
+
* view write and the checkpoint advance land together or not at all. Splitting
|
|
8
|
+
* them would put the bracket in the caller's hands, and a forgotten bracket is
|
|
9
|
+
* silent: the view would commit without the checkpoint advancing and the next
|
|
10
|
+
* pass would apply the same batch again. So `commit` takes `viewWrites` as an
|
|
11
|
+
* ARGUMENT — the caller hands its writes in, the port owns the transaction — and
|
|
12
|
+
* there is no operation on this interface that advances a checkpoint outside it.
|
|
13
|
+
*
|
|
14
|
+
* The checkpoint is a SCALAR `Position`, not a set of processed positions and not
|
|
15
|
+
* a high-water mark with gap tracking. That is licensed by the store's
|
|
16
|
+
* exclusive-lock append (ADR-0002): id-allocation order equals commit order, so
|
|
17
|
+
* once a position is visible every lower position is either already visible or
|
|
18
|
+
* permanently absent. A reader may therefore process strictly ascending and
|
|
19
|
+
* treat an absent position as absent forever. Positions are compared, never
|
|
20
|
+
* arithmetic'd, and never assumed contiguous.
|
|
21
|
+
*
|
|
22
|
+
* The port is MULTI-KEY — every operation takes a `CheckpointKey` — because one
|
|
23
|
+
* `SqlClient` legitimately serves many read models, and a store must keep every
|
|
24
|
+
* key independent. One MATERIALISATION of a read model, however, has exactly ONE
|
|
25
|
+
* key, so it is handed the `KeyedProjectionStore` façade at the bottom of this
|
|
26
|
+
* module rather than the port itself: `forKey` binds a store to a key once, and from
|
|
27
|
+
* there no operation has a key to get wrong. Both surfaces are real and neither
|
|
28
|
+
* replaces the other. A read model may have SEVERAL materialisations, one per store;
|
|
29
|
+
* the `CheckpointKey` doc below says what that does and does not do to key
|
|
30
|
+
* uniqueness.
|
|
31
|
+
*
|
|
32
|
+
* This module is dependency-free by design — branded ids, two tagged errors, two
|
|
33
|
+
* interfaces and the one-line façade that relates them. `@kairos-es/read` ships the
|
|
34
|
+
* in-memory implementation (the rule being that the package defining a port ships
|
|
35
|
+
* that port's dependency-free implementation, as `core` does for the in-memory
|
|
36
|
+
* event store); every dependency-bearing backend is its own package.
|
|
37
|
+
*/
|
|
38
|
+
import type { Position } from '@kairos-es/core';
|
|
39
|
+
import { Effect, Schema } from 'effect';
|
|
40
|
+
/**
|
|
41
|
+
* The name of a read model, as a branded non-empty string.
|
|
42
|
+
*
|
|
43
|
+
* Branded rather than a bare `string` for the same reason every other kairos-es
|
|
44
|
+
* identifier is (`Position`, `Tag`, `EventType`): the projection name and the
|
|
45
|
+
* partition name are both non-empty strings with identical shape, so an
|
|
46
|
+
* unbranded pair is trivially transposable at a call site and the mistake would
|
|
47
|
+
* surface only as a projection silently reading somebody else's checkpoint.
|
|
48
|
+
* Construct with `ProjectionId.make(...)`.
|
|
49
|
+
*/
|
|
50
|
+
export declare const ProjectionId: Schema.brand<Schema.filter<typeof Schema.String>, "ProjectionId">;
|
|
51
|
+
export type ProjectionId = typeof ProjectionId.Type;
|
|
52
|
+
/**
|
|
53
|
+
* The partition of a read model's checkpoint, as a branded non-empty string.
|
|
54
|
+
*
|
|
55
|
+
* `DEFAULT_PARTITION` is the single value the RUNNER uses, because stream
|
|
56
|
+
* sharding is deliberately not offered: DCB events carry a tag matrix, so two
|
|
57
|
+
* shards can own rows for the same tag while each holds its own checkpoint —
|
|
58
|
+
* both guarded compare-and-sets then succeed and the lost update goes undetected
|
|
59
|
+
* (the guard arbitrates cursor progress, not row ownership). Nothing on this port
|
|
60
|
+
* restricts the value, and a `ProjectionStore` must keep every distinct
|
|
61
|
+
* `(projection, partition)` pair independent — the shared contract suite commits
|
|
62
|
+
* to a second partition precisely to prove that. The dimension is in the KEY so
|
|
63
|
+
* that a restricted, opt-in sharding capability — for read models that provably
|
|
64
|
+
* need no cross-tag ordering and whose every row is owned by one partition value
|
|
65
|
+
* — could be added later with no migration of the checkpoint's shape or storage.
|
|
66
|
+
*/
|
|
67
|
+
export declare const PartitionId: Schema.brand<Schema.filter<typeof Schema.String>, "PartitionId">;
|
|
68
|
+
export type PartitionId = typeof PartitionId.Type;
|
|
69
|
+
/** The partition value every runner uses while sharding is cut (ADR-0007). */
|
|
70
|
+
export declare const DEFAULT_PARTITION: PartitionId;
|
|
71
|
+
/**
|
|
72
|
+
* What a checkpoint is stored under: the read model plus its partition.
|
|
73
|
+
*
|
|
74
|
+
* **A key is unique WITHIN ONE STORE**, and stating the scope is load-bearing
|
|
75
|
+
* rather than pedantic: without it, the uniqueness rule below points a reader at the
|
|
76
|
+
* opposite of the right answer for a read model materialised more than once. The
|
|
77
|
+
* scope is a consequence of R1 rather than a policy — the checkpoint is co-located
|
|
78
|
+
* with its view so the pair commits atomically, so a key is only ever resolved
|
|
79
|
+
* against the store holding it, and there is no global namespace to collide in.
|
|
80
|
+
*
|
|
81
|
+
* Two DIFFERENT read models never share a key in one store, and by R3 read models
|
|
82
|
+
* that share one subscription share one cursor and therefore ONE key — a composed
|
|
83
|
+
* bundle checkpoints as a unit and rebuilds as a unit. But ONE read model
|
|
84
|
+
* materialised in several stores SHARES one `ProjectionId` across them, which is
|
|
85
|
+
* what its single identity means, and which
|
|
86
|
+
* `@kairos-es/read-postgres`'s `test/courseRosterGraduation.test.ts` asserts as its
|
|
87
|
+
* acceptance criterion. Distinctness is forced only where two materialisations of
|
|
88
|
+
* one read model COHABIT a store, which is what `PartitionId` is for: two of them
|
|
89
|
+
* in one store under one key would be two runners over one cursor, each advancing
|
|
90
|
+
* the other's, and the read side's construction gate refuses that pairing rather
|
|
91
|
+
* than leaving it to be discovered — a rung only a multi-materialisation call can
|
|
92
|
+
* reach, a lone materialisation having no sibling to collide with.
|
|
93
|
+
*/
|
|
94
|
+
export interface CheckpointKey {
|
|
95
|
+
readonly projection: ProjectionId;
|
|
96
|
+
readonly partition: PartitionId;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Build a `CheckpointKey`, defaulting the partition — the constructor nearly
|
|
100
|
+
* every call site wants, so that the sharding dimension stays in the key shape
|
|
101
|
+
* without appearing in ordinary wiring code.
|
|
102
|
+
*/
|
|
103
|
+
export declare const checkpointKey: (projection: ProjectionId, partition?: PartitionId) => CheckpointKey;
|
|
104
|
+
/**
|
|
105
|
+
* How far a store's state survives: `'ephemeral'` dies with the process,
|
|
106
|
+
* `'durable'` outlives it.
|
|
107
|
+
*
|
|
108
|
+
* Declared rather than inferred so that R2 — checkpoint durability must not
|
|
109
|
+
* EXCEED event-log durability — is checkable at construction instead of
|
|
110
|
+
* invisible at runtime. This type is the VIEW's half of that comparison;
|
|
111
|
+
* `EventLogDurability` is the log's, and `EventLogDurability.ts` is where R2's
|
|
112
|
+
* argument and the silent failure it prevents are written.
|
|
113
|
+
*/
|
|
114
|
+
export type Durability = 'ephemeral' | 'durable';
|
|
115
|
+
declare const ProjectionStoreError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
116
|
+
readonly _tag: "ProjectionStoreError";
|
|
117
|
+
} & Readonly<A>;
|
|
118
|
+
/**
|
|
119
|
+
* An infrastructure fault from a view store — a connection dropped, a statement
|
|
120
|
+
* failed, the checkpoint table is missing.
|
|
121
|
+
*
|
|
122
|
+
* This is a value on the ERROR channel rather than a defect, which inverts the
|
|
123
|
+
* store contract's rule (`DcbEventStore` dies on infrastructure faults, ADR-0002)
|
|
124
|
+
* and does so on purpose: the read side has a supervisor above it, and the
|
|
125
|
+
* supervisor's whole job is to tell "retry the subscription with backoff" from
|
|
126
|
+
* "give up and report a stalled projection". A defect carries no such
|
|
127
|
+
* distinction. `cause` is exact-optional — only set when there is one.
|
|
128
|
+
*/
|
|
129
|
+
export declare class ProjectionStoreError extends ProjectionStoreError_base<{
|
|
130
|
+
readonly reason: string;
|
|
131
|
+
readonly cause?: unknown;
|
|
132
|
+
}> {
|
|
133
|
+
/**
|
|
134
|
+
* `reason` again, as the `Error` field that says why — so the fault renders
|
|
135
|
+
* ITSELF.
|
|
136
|
+
*
|
|
137
|
+
* `Data.TaggedError` sets `name` to the tag and leaves `message` empty, so
|
|
138
|
+
* without this a `ProjectionStoreError` stringifies to `'ProjectionStoreError'`
|
|
139
|
+
* and the sentence explaining the fault reaches a log line only where something
|
|
140
|
+
* OUTSIDE the class knows to go and fetch `reason`. With it,
|
|
141
|
+
* `Error.prototype.toString` yields `'ProjectionStoreError: <reason>'`, and
|
|
142
|
+
* `String(fault)`, `Cause.pretty` (which reads `message` off anything `instanceof
|
|
143
|
+
* Error`) and the projection supervisor's `reason` annotation all take it from the
|
|
144
|
+
* one place it is declared. That matters most for exactly the audience this error
|
|
145
|
+
* exists for: the supervisor above the read side, whose log line is what an
|
|
146
|
+
* operator sees when a view store starts refusing to answer.
|
|
147
|
+
*
|
|
148
|
+
* ## What filling `message` reaches — and what it deliberately leaves alone
|
|
149
|
+
*
|
|
150
|
+
* Not the supervisor's two log lines: `message` is the field `Cause.pretty` reads
|
|
151
|
+
* off anything `instanceof Error`, so this changes how a `ProjectionStoreError`
|
|
152
|
+
* prints EVERYWHERE it is reported — a failed fibre's `cause` annotation on all
|
|
153
|
+
* three shipped loggers, `Effect.logError(message, cause)`, an unhandled
|
|
154
|
+
* failure's report, and any reporter or test runner that reads `.message`. That
|
|
155
|
+
* blast radius is the point of doing it on the class instead of at one call site:
|
|
156
|
+
* one declaration improves every reader at once, and none of them has to know
|
|
157
|
+
* this class's field names.
|
|
158
|
+
*
|
|
159
|
+
* What it does NOT change is the STRUCTURED shape. `Data.Error` overrides
|
|
160
|
+
* `YieldableError`'s `toJSON` with `{ ...plainArgs, ...this }` — own enumerables
|
|
161
|
+
* plus the constructor args, and a prototype accessor is neither — so
|
|
162
|
+
* `JSON.stringify(fault)` and an `Effect.logError(fault)` that JSON-encodes its
|
|
163
|
+
* object message emit exactly the `reason`/`_tag` they always did. The humane
|
|
164
|
+
* rendering gains a sentence; the machine-parsed one is untouched.
|
|
165
|
+
*
|
|
166
|
+
* A GETTER rather than a second field, so `reason` stays the single declaration
|
|
167
|
+
* and the two cannot disagree. Safe as a prototype accessor because
|
|
168
|
+
* `Data.TaggedError`'s constructor is `super(args?.message, …)` followed by
|
|
169
|
+
* `Object.assign(this, args)` (verified against `effect@3.22`'s `Data.ts`) and
|
|
170
|
+
* `message` is not among the fields above, so nothing shadows it with an own
|
|
171
|
+
* property.
|
|
172
|
+
*
|
|
173
|
+
* Both mechanics — that accessor safety and the `toJSON` shape above it — are
|
|
174
|
+
* argued HERE and nowhere else, for every read-side fault that fills the field:
|
|
175
|
+
* `CheckpointSuperseded` below, `PipelineDied` in `ProjectionRunner.ts` and
|
|
176
|
+
* `ProjectionStalled` in `superviseOnProgress.ts` each point back rather than
|
|
177
|
+
* re-derive, and none of the four declares a `message` field for that
|
|
178
|
+
* `Object.assign` to shadow its accessor with. One version pin to re-verify on an
|
|
179
|
+
* `effect` bump, not four.
|
|
180
|
+
*/
|
|
181
|
+
get message(): string;
|
|
182
|
+
}
|
|
183
|
+
declare const CheckpointSuperseded_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
184
|
+
readonly _tag: "CheckpointSuperseded";
|
|
185
|
+
} & Readonly<A>;
|
|
186
|
+
/**
|
|
187
|
+
* The guarded compare-and-set lost: the stored checkpoint no longer equals
|
|
188
|
+
* `expected`, so somebody else advanced this key.
|
|
189
|
+
*
|
|
190
|
+
* A tagged ERROR, never a returned outcome value. A returned
|
|
191
|
+
* `Advanced | Superseded` only rolls the view writes back if the caller remembers
|
|
192
|
+
* to convert it into a failure, and forgetting is silent corruption — the view
|
|
193
|
+
* commits while the checkpoint stays put, and the next pass double-applies the
|
|
194
|
+
* batch. On the error channel the abort is automatic and cannot be ignored.
|
|
195
|
+
*
|
|
196
|
+
* It carries the key and the expectation but deliberately NOT the stored
|
|
197
|
+
* position, mirroring `AppendConditionFailed` (ADR-0002) for the same reason: the
|
|
198
|
+
* recovery path re-reads the checkpoint, so a carried position would already be
|
|
199
|
+
* stale by the time anything acted on it, and carrying one would invite exactly
|
|
200
|
+
* the resume-from-the-conflict-point bug that omitting it prevents.
|
|
201
|
+
*
|
|
202
|
+
* Like its sibling `ProjectionStoreError` it fills `message`, from the expectation
|
|
203
|
+
* it already carries. The getter below says what the field reaches, and also
|
|
204
|
+
* records the reasoning it REPLACED — this class once left `message` empty on a
|
|
205
|
+
* ground that could not be true, and that is worth being able to recognise again.
|
|
206
|
+
*/
|
|
207
|
+
export declare class CheckpointSuperseded extends CheckpointSuperseded_base<{
|
|
208
|
+
readonly key: CheckpointKey;
|
|
209
|
+
readonly expected: Position;
|
|
210
|
+
}> {
|
|
211
|
+
/**
|
|
212
|
+
* The sentence this class's own first paragraph writes, plus the expectation the
|
|
213
|
+
* guard turned on — as the `Error` field that says why, so the fault renders
|
|
214
|
+
* ITSELF.
|
|
215
|
+
*
|
|
216
|
+
* `Data.TaggedError` sets `name` to the tag and leaves `message` empty, and
|
|
217
|
+
* `Cause.pretty` substitutes its own `'An error has occurred'` for an empty one,
|
|
218
|
+
* so unfilled this fault printed through a `Cause` reads
|
|
219
|
+
* `'CheckpointSuperseded: An error has occurred'` — a line naming neither a guard,
|
|
220
|
+
* nor a checkpoint, nor an expectation. Filled, `Error.prototype.toString` yields
|
|
221
|
+
* the tag followed by the sentence, everywhere the fault is reported. How far that
|
|
222
|
+
* reaches and what it deliberately leaves alone is set out on
|
|
223
|
+
* `ProjectionStoreError` above; the argument is identical for every fault that
|
|
224
|
+
* fills the field, so it is made once, on the one an operator meets most.
|
|
225
|
+
*
|
|
226
|
+
* ## Why this field was once left EMPTY, and why that reason was wrong
|
|
227
|
+
*
|
|
228
|
+
* Recorded rather than quietly deleted, because a rationale that cannot be true is
|
|
229
|
+
* worse than a missing one and the shape of this mistake is easy to repeat. The
|
|
230
|
+
* argument ran: `expected` is a `bigint`; a `bigint` is not JSON-serialisable, and
|
|
231
|
+
* `effect@3.22`'s `Logger.json` throws outright on one nested in an annotation
|
|
232
|
+
* value and loses the whole line with it; therefore filling `message` would push
|
|
233
|
+
* that value class into the field every renderer reaches for first.
|
|
234
|
+
*
|
|
235
|
+
* The measurement is true and stays load-bearing. The inference from it is a
|
|
236
|
+
* category error. `message` is typed `string`, and TEMPLATE INTERPOLATION of a
|
|
237
|
+
* `bigint` yields its decimal digits, so nothing downstream of this getter can see
|
|
238
|
+
* a `bigint` through it. The hazard is about the values handed to a LOGGER as
|
|
239
|
+
* ANNOTATIONS — which is exactly where the projection supervisor applies it, in
|
|
240
|
+
* `shouldRestart`, whose `position` annotation is `String(stored)` — and it says
|
|
241
|
+
* nothing whatever about a string rendered from one. Nor was the substitute the
|
|
242
|
+
* old argument offered a substitute: the supervisor's `position` annotation is the
|
|
243
|
+
* progress signal it RE-READ after the failure, not this expectation. Meanwhile
|
|
244
|
+
* the cost of the omission was real and was being paid on every `Cause.pretty` of
|
|
245
|
+
* a superseded commit.
|
|
246
|
+
*
|
|
247
|
+
* What the correction does NOT touch is the other ground this class states, which
|
|
248
|
+
* stands unchanged: no STORED position is carried, because the recovery path
|
|
249
|
+
* re-reads and a carried one would already be stale. That decision is about a
|
|
250
|
+
* RECOVERY INPUT — what a caller might wrongly resume from. This getter is a
|
|
251
|
+
* human-readable label over a field the class already carries, and `expected` is
|
|
252
|
+
* exactly as stale in the label as it is in the payload: a reader is being told
|
|
253
|
+
* what the guard expected, never what to resume from.
|
|
254
|
+
*
|
|
255
|
+
* A GETTER rather than a `message` field, so `expected` stays the single
|
|
256
|
+
* declaration. Why a prototype accessor is safe here, and why a structured log of
|
|
257
|
+
* this fault still emits the same `key`/`expected`/`_tag` it always did, are set
|
|
258
|
+
* out once for all four of these getters on `ProjectionStoreError` above.
|
|
259
|
+
*/
|
|
260
|
+
get message(): string;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Where read models materialise: a checkpoint and the view it tracks, in one store
|
|
264
|
+
* so each pair can be committed atomically.
|
|
265
|
+
*
|
|
266
|
+
* Each such pair is ONE MATERIALISATION of a read model, and a read model may have
|
|
267
|
+
* several across several stores — which is why the `CheckpointKey` doc above scopes
|
|
268
|
+
* uniqueness to one store rather than globally, and why R1 is what forces that
|
|
269
|
+
* scope.
|
|
270
|
+
*
|
|
271
|
+
* **Multi-key on purpose, and NOT the surface a read model is given.** Every
|
|
272
|
+
* operation takes a `CheckpointKey`, because one store instance — one `SqlClient`,
|
|
273
|
+
* one connection pool — serves however many read models an application runs, and
|
|
274
|
+
* keeping their keys independent is a contract obligation the shared suite proves.
|
|
275
|
+
* One materialisation has exactly one key, though, so it is handed
|
|
276
|
+
* `forKey(store, key)` instead: see `KeyedProjectionStore` below for why the
|
|
277
|
+
* difference is structural rather than stylistic. Implement THIS interface; hand out
|
|
278
|
+
* THAT one.
|
|
279
|
+
*
|
|
280
|
+
* **Annotate an implementation at its DEFINITION site** — `const store:
|
|
281
|
+
* ProjectionStore = { … }`, rather than leaning on the factory's return type alone
|
|
282
|
+
* — so a drift from this interface is a compile error THERE, on the offending
|
|
283
|
+
* member, instead of an opaque mismatch at the export. Both shipped
|
|
284
|
+
* implementations do it and say so in one line; a new backend should too.
|
|
285
|
+
*
|
|
286
|
+
* **The contract a NON-TRANSACTIONAL implementation must honour.** A SQL store
|
|
287
|
+
* runs `commit` in a transaction, so a `viewWrites` that fails PART-WAY is rolled
|
|
288
|
+
* back whole. An in-memory store has no transaction and cannot undo the writes
|
|
289
|
+
* that already landed before the failure. That single divergence is discharged by
|
|
290
|
+
* a requirement on the CALLER of a non-transactional store rather than by
|
|
291
|
+
* capability tiers in the shared contract suite: the per-batch view write MUST be
|
|
292
|
+
* a single atomic effect — fold the batch purely, then perform ONE write (see
|
|
293
|
+
* `foldIntoRef`, which is exactly that shape). Under that requirement a part-way
|
|
294
|
+
* failure is unreachable, every implementation's observable behaviour is
|
|
295
|
+
* identical, and one suite tests them all with no per-backend exemptions. State
|
|
296
|
+
* it loudly at any site that hands a multi-write `apply` to an in-memory store:
|
|
297
|
+
* that, not the store, is the bug.
|
|
298
|
+
*
|
|
299
|
+
* R1 (atomicity) is what this interface IS. The atomic unit is the VIEW's own
|
|
300
|
+
* transaction and never the event log's, so a view in a DIFFERENT database from the
|
|
301
|
+
* log is still exactly-once: the checkpoint travels with the view, and re-reading
|
|
302
|
+
* the log from a position needs no transaction at all, being deterministic and
|
|
303
|
+
* side-effect-free.
|
|
304
|
+
*
|
|
305
|
+
* A view store offering no transaction at all — a search index, an object store, an
|
|
306
|
+
* HTTP API — cannot implement this interface honestly: there is nothing for the
|
|
307
|
+
* checkpoint advance to ride along inside, so such a view is at-least-once and its
|
|
308
|
+
* writes must be IDEMPOTENT. That is a different design and not this port, but it
|
|
309
|
+
* has a recommended shape, so it is written down rather than left to be
|
|
310
|
+
* rediscovered: make every write POSITION-KEYED so re-applying an event no-ops —
|
|
311
|
+
* in SQL terms an `INSERT … ON CONFLICT DO NOTHING` keyed by the event's position,
|
|
312
|
+
* and in a search index or an object store the same idea, the document or object
|
|
313
|
+
* key derived from the position so a replay overwrites itself instead of
|
|
314
|
+
* accumulating. The guarantee then degrades from exactly-once to "at least once,
|
|
315
|
+
* and twice is indistinguishable from once".
|
|
316
|
+
*
|
|
317
|
+
* It is RECOMMENDED rather than mandated (and no helper is shipped for it)
|
|
318
|
+
* because the library cannot force it: the position-keying lives in the read
|
|
319
|
+
* model's own rows, whose shape this port knows nothing about. Nothing here can
|
|
320
|
+
* check it, so the honest move is to document the pattern and say plainly that
|
|
321
|
+
* the obligation is the reader's.
|
|
322
|
+
*/
|
|
323
|
+
export interface ProjectionStore {
|
|
324
|
+
/**
|
|
325
|
+
* How far this store's state survives — the R2 input. Checked against the
|
|
326
|
+
* event log's durability at runner construction.
|
|
327
|
+
*/
|
|
328
|
+
readonly durability: Durability;
|
|
329
|
+
/**
|
|
330
|
+
* The stored position for `key`. A key that has never been committed reads
|
|
331
|
+
* back as `ORIGIN` (`0n`), so a fresh read model and a reset read model are
|
|
332
|
+
* indistinguishable and both simply catch up from the start of the log.
|
|
333
|
+
*/
|
|
334
|
+
readonly readCheckpoint: (key: CheckpointKey) => Effect.Effect<Position, ProjectionStoreError>;
|
|
335
|
+
/**
|
|
336
|
+
* Apply `viewWrites` and advance the checkpoint from `expected` to `next`
|
|
337
|
+
* ATOMICALLY: both land or neither does.
|
|
338
|
+
*
|
|
339
|
+
* The view writes are passed IN rather than the caller opening its own
|
|
340
|
+
* transaction, so the bracket cannot be forgotten and no path exists to a
|
|
341
|
+
* checkpoint advance outside it. `viewWrites` is an arbitrary `Effect`, so
|
|
342
|
+
* nothing that belongs in the same transaction is excluded — the caller's own
|
|
343
|
+
* statements join it through the same client — and its `E`/`R` pass straight
|
|
344
|
+
* through this signature untouched, so a projection keeps its own error and
|
|
345
|
+
* requirement types.
|
|
346
|
+
*
|
|
347
|
+
* Fails `CheckpointSuperseded` when the stored position no longer equals
|
|
348
|
+
* `expected`, and that failure ABORTS the whole commit: the view writes must
|
|
349
|
+
* have no observable effect afterwards. That abort is the exactly-once
|
|
350
|
+
* mechanism, so it is the one behaviour the shared contract suite proves
|
|
351
|
+
* against a known-broken store rather than assuming.
|
|
352
|
+
*
|
|
353
|
+
* `next` is used AS IS. It is the last processed event's position, never
|
|
354
|
+
* `expected + 1` or any other arithmetic: positions are strictly increasing but
|
|
355
|
+
* not gapless, so a jump is normal and an increment would be wrong.
|
|
356
|
+
*/
|
|
357
|
+
readonly commit: <E, R>(args: {
|
|
358
|
+
readonly key: CheckpointKey;
|
|
359
|
+
readonly expected: Position;
|
|
360
|
+
readonly next: Position;
|
|
361
|
+
readonly viewWrites: Effect.Effect<void, E, R>;
|
|
362
|
+
}) => Effect.Effect<void, E | CheckpointSuperseded | ProjectionStoreError, R>;
|
|
363
|
+
/**
|
|
364
|
+
* Return `key` to `ORIGIN`, so the next run catches up from the start of the
|
|
365
|
+
* log — the rebuild path.
|
|
366
|
+
*
|
|
367
|
+
* It resets the CHECKPOINT only, never the view: what a rebuild must do with
|
|
368
|
+
* existing rows (truncate them, or write into a fresh table and swap) is the
|
|
369
|
+
* read model's business, and the port has no idea what its rows are.
|
|
370
|
+
*/
|
|
371
|
+
readonly resetCheckpoint: (key: CheckpointKey) => Effect.Effect<void, ProjectionStoreError>;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* A `ProjectionStore` BOUND to one `CheckpointKey`: the same three operations,
|
|
375
|
+
* with no key to pass and therefore none to get wrong.
|
|
376
|
+
*
|
|
377
|
+
* **Why this exists: an invariant that was convention is now structure.** "One
|
|
378
|
+
* materialisation, one key, one runner" (ADR-0007) was true of every wiring in this
|
|
379
|
+
* repo and of every wiring a reader would write, but nothing enforced it. A
|
|
380
|
+
* `ReadModel` carrying `key` and `store` as INDEPENDENT fields admits two DIFFERENT
|
|
381
|
+
* read models authored against the same key in one store, or one read model whose
|
|
382
|
+
* `apply` reads a neighbour's checkpoint, and neither is a type error — the mistake
|
|
383
|
+
* would surface as one projection silently advancing another's cursor, which is
|
|
384
|
+
* exactly the failure mode `ProjectionId` is branded to prevent at the level below.
|
|
385
|
+
* Binding the key to the store ONCE, at the wiring site, makes the pairing the only
|
|
386
|
+
* thing there is: `commit` no longer has a key argument, so there is no key to
|
|
387
|
+
* transpose, omit, or refresh from the wrong variable.
|
|
388
|
+
*
|
|
389
|
+
* **`runProjections` is the same move one level up, and the collision gate is what
|
|
390
|
+
* binding alone cannot reach.** Where this façade binds a key to a store, that
|
|
391
|
+
* function binds ONE slice record and ONE `ProjectionId` across N materialisations
|
|
392
|
+
* and does the `forKey` call per entry itself, so "one read model, N
|
|
393
|
+
* materialisations, one identity" stops being a convention too. What binding cannot
|
|
394
|
+
* catch is the one incoherence that is invisible AFTER it: two entries bound to the
|
|
395
|
+
* same store under equal keys are each a perfectly well-formed
|
|
396
|
+
* `KeyedProjectionStore`, and `forKey` deliberately does not expose the store it
|
|
397
|
+
* closed over, so nothing downstream can ask whether two of them are the same. That
|
|
398
|
+
* question is only answerable at the UNBOUND port, which is where
|
|
399
|
+
* the read side's construction gate asks it.
|
|
400
|
+
*
|
|
401
|
+
* It is a FAÇADE and not a replacement. The multi-key port underneath is a real
|
|
402
|
+
* requirement — one client serves many read models, and per-key independence is
|
|
403
|
+
* contract behaviour the shared suite proves against every backend — so `forKey`
|
|
404
|
+
* narrows a store for one consumer rather than narrowing the port for everyone.
|
|
405
|
+
*
|
|
406
|
+
* `readCheckpoint` and `resetCheckpoint` are Effect VALUES rather than
|
|
407
|
+
* zero-argument functions. An `Effect` is a lazy description, so a value is
|
|
408
|
+
* re-runnable and a no-argument call has nothing left to express; `yield*
|
|
409
|
+
* view.readCheckpoint` is then the whole operation, with no vestigial `()` to
|
|
410
|
+
* suggest an argument might one day return.
|
|
411
|
+
*/
|
|
412
|
+
export interface KeyedProjectionStore {
|
|
413
|
+
/**
|
|
414
|
+
* The key this store is bound to.
|
|
415
|
+
*
|
|
416
|
+
* Carried rather than dropped because the RUNNER still needs it: its log
|
|
417
|
+
* annotations and its `PipelineDied`/`ProjectionStalled` payloads name BOTH halves —
|
|
418
|
+
* an operator greps on `projection` and `partition` together — and its
|
|
419
|
+
* construction-time defect messages name at least the projection. That is the whole
|
|
420
|
+
* reason the pair travels as one value: an operator's first question about a stalled
|
|
421
|
+
* projection is which one, and in a store where two materialisations cohabit the
|
|
422
|
+
* partition is the half that answers it. It
|
|
423
|
+
* is exposed for READING; nothing here takes it as an argument, which is the
|
|
424
|
+
* point.
|
|
425
|
+
*/
|
|
426
|
+
readonly key: CheckpointKey;
|
|
427
|
+
/** The underlying store's durability — the R2 input, passed straight through. */
|
|
428
|
+
readonly durability: Durability;
|
|
429
|
+
/** The stored position for this store's key. `ORIGIN` when never committed. */
|
|
430
|
+
readonly readCheckpoint: Effect.Effect<Position, ProjectionStoreError>;
|
|
431
|
+
/**
|
|
432
|
+
* Apply `viewWrites` and advance this key's checkpoint from `expected` to
|
|
433
|
+
* `next` atomically — `ProjectionStore.commit` with the one argument a caller
|
|
434
|
+
* could get wrong already supplied.
|
|
435
|
+
*/
|
|
436
|
+
readonly commit: <E, R>(args: {
|
|
437
|
+
readonly expected: Position;
|
|
438
|
+
readonly next: Position;
|
|
439
|
+
readonly viewWrites: Effect.Effect<void, E, R>;
|
|
440
|
+
}) => Effect.Effect<void, E | CheckpointSuperseded | ProjectionStoreError, R>;
|
|
441
|
+
/** Return this key to `ORIGIN` — the rebuild path, checkpoint only. */
|
|
442
|
+
readonly resetCheckpoint: Effect.Effect<void, ProjectionStoreError>;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Bind a `ProjectionStore` to one `CheckpointKey`, producing the surface a read
|
|
446
|
+
* model is actually given.
|
|
447
|
+
*
|
|
448
|
+
* A FREE FUNCTION rather than a method on `ProjectionStore`, decided that way for
|
|
449
|
+
* two reasons beyond the obvious one (a single implementation, which every present
|
|
450
|
+
* and future backend then gets for nothing rather than re-deriving — and could
|
|
451
|
+
* re-derive WRONGLY, since a `forKey` that closed over the wrong key would be
|
|
452
|
+
* invisible until two read models started trading cursors).
|
|
453
|
+
*
|
|
454
|
+
* The sharper reason is DECORATORS. Wrapping a store is an established move here —
|
|
455
|
+
* a test's never-committing view store, a durability override — and every one of
|
|
456
|
+
* them is written `{ ...inner, commit: … }`. Were `forKey` a method, that spread
|
|
457
|
+
* would copy the INNER store's own `forKey`, whose closure still points at the
|
|
458
|
+
* inner store, so `forKey(decorated, key)` would silently route straight past the
|
|
459
|
+
* decoration. As a free function it reads `store.commit` off the value it is
|
|
460
|
+
* handed, so a decorated store decorates.
|
|
461
|
+
*
|
|
462
|
+
* The second reason is the port's implementation burden: `ProjectionStore` stays
|
|
463
|
+
* three methods, which is what a backend has to get right, and no implementation
|
|
464
|
+
* can drift on a member that is pure derivation.
|
|
465
|
+
*
|
|
466
|
+
* `Effect.suspend` on the two no-argument members preserves the port's per-call
|
|
467
|
+
* construction timing exactly: an implementation is entitled to build its effect
|
|
468
|
+
* when its method is called (the SQL store binds parameters there), so suspending
|
|
469
|
+
* keeps `yield* view.readCheckpoint` indistinguishable from
|
|
470
|
+
* `yield* store.readCheckpoint(key)` rather than freezing one construction for the
|
|
471
|
+
* store's lifetime.
|
|
472
|
+
*/
|
|
473
|
+
export declare const forKey: (store: ProjectionStore, key: CheckpointKey) => KeyedProjectionStore;
|
|
474
|
+
export {};
|
|
475
|
+
//# sourceMappingURL=ProjectionStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectionStore.d.ts","sourceRoot":"","sources":["../../src/ProjectionStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAQ,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE7C;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,mEAGxB,CAAA;AACD,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,kEAGvB,CAAA;AACD,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAA;AAEjD,8EAA8E;AAC9E,eAAO,MAAM,iBAAiB,EAAE,WAAyC,CAAA;AAEzE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAA;IACjC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAA;CAChC;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,YAAY,YAAY,EACxB,YAAW,WAA+B,KACzC,aAA4C,CAAA;AAE/C;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,SAAS,CAAA;;;;AAEhD;;;;;;;;;;GAUG;AACH,qBAAa,oBAAqB,SAAQ,0BAExC;IACA,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CACzB,CAAC;IACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+CG;IACH,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;;;;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,oBAAqB,SAAQ,0BAExC;IACA,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;CAC5B,CAAC;IACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgDG;IACH,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAA;IAE/B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,CACvB,GAAG,EAAE,aAAa,KACf,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAA;IAElD;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE;QAC5B,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAA;QAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;QAC3B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;QACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;KAC/C,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,oBAAoB,GAAG,oBAAoB,EAAE,CAAC,CAAC,CAAA;IAE7E;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,EAAE,CACxB,GAAG,EAAE,aAAa,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAA;CAC/C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAA;IAE3B,iFAAiF;IACjF,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAA;IAE/B,+EAA+E;IAC/E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAA;IAEtE;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE;QAC5B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;QAC3B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;QACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;KAC/C,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,oBAAoB,GAAG,oBAAoB,EAAE,CAAC,CAAC,CAAA;IAE7E,uEAAuE;IACvE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAA;CACpE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,MAAM,GACjB,OAAO,eAAe,EACtB,KAAK,aAAa,KACjB,oBAmBD,CAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `foldIntoRef` — the folded-state read model's `apply`: fold a batch PURELY
|
|
3
|
+
* through `composeProjections`, then perform exactly ONE write.
|
|
4
|
+
*
|
|
5
|
+
* This is the shape the `ProjectionStore` port's single-atomic-effect requirement
|
|
6
|
+
* asks for, and it is why that requirement costs nothing. A non-transactional view
|
|
7
|
+
* store cannot roll back a `viewWrites` that fails part-way; a fold followed by a
|
|
8
|
+
* single `Ref.update` has no part-way to fail at — the fold is pure (it cannot
|
|
9
|
+
* fail, and it touches nothing observable) and the write is a single atomic step.
|
|
10
|
+
* So the one divergence between a transactional and a non-transactional
|
|
11
|
+
* `ProjectionStore` is unreachable for a read model written this way, which is
|
|
12
|
+
* what keeps the shared contract suite free of capability tiers.
|
|
13
|
+
*
|
|
14
|
+
* It is deliberately a thin helper over primitives that already exist rather than
|
|
15
|
+
* a new fold abstraction: the composition is `core`'s `composeProjections`, the
|
|
16
|
+
* slices are the codec's `projection().on().build()`, and this module only wires
|
|
17
|
+
* the two to a `Ref`. Nothing here is required — a read model with its own
|
|
18
|
+
* multi-statement SQL `apply` ignores it entirely and relies on the transaction
|
|
19
|
+
* instead. It is the RECOMMENDED `apply` for an in-memory view specifically.
|
|
20
|
+
*/
|
|
21
|
+
import type { SliceProjection } from '@kairos-es/codec';
|
|
22
|
+
import { type CompositeState, type DecodedEvent } from '@kairos-es/core';
|
|
23
|
+
import type { Array as Arr } from 'effect';
|
|
24
|
+
import { type Effect, Ref } from 'effect';
|
|
25
|
+
/**
|
|
26
|
+
* Build the per-batch `apply` for a read model whose whole view is `view`.
|
|
27
|
+
*
|
|
28
|
+
* `slices` must be the SAME record the read model's subscription query is derived
|
|
29
|
+
* from, so the events the runner delivers are exactly the events these folds
|
|
30
|
+
* expect; `composeProjections` gates each slice on its own query before
|
|
31
|
+
* dispatching, so a slice never sees an event it did not ask for.
|
|
32
|
+
*
|
|
33
|
+
* The returned effect is infallible (`E = never`) and requirement-free
|
|
34
|
+
* (`R = never`), which is the whole point: handed to `ProjectionStore.commit` as
|
|
35
|
+
* `viewWrites`, it adds nothing to the commit's error or requirement channels, so
|
|
36
|
+
* the ONLY way that commit can fail is the checkpoint guard.
|
|
37
|
+
*/
|
|
38
|
+
export declare const foldIntoRef: <T extends Record<string, SliceProjection>>(slices: T, view: Ref.Ref<CompositeState<T>>) => ((batch: Arr.NonEmptyReadonlyArray<DecodedEvent>) => Effect.Effect<void>);
|
|
39
|
+
//# sourceMappingURL=foldIntoRef.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foldIntoRef.d.ts","sourceRoot":"","sources":["../../src/foldIntoRef.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EACL,KAAK,cAAc,EAEnB,KAAK,YAAY,EAClB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,KAAK,IAAI,GAAG,EAAE,MAAM,QAAQ,CAAA;AAC1C,OAAO,EAAE,KAAK,MAAM,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAA;AAEzC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACnE,QAAQ,CAAC,EACT,MAAM,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,KAC/B,CAAC,CACF,KAAK,EAAE,GAAG,CAAC,qBAAqB,CAAC,YAAY,CAAC,KAC3C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAevB,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import type { ProjectionStore } from './ProjectionStore.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build an in-memory `ProjectionStore`.
|
|
5
|
+
*
|
|
6
|
+
* Every call is an independent store with its own checkpoints and its own
|
|
7
|
+
* semaphore, so two read models wired to two calls of this factory cannot
|
|
8
|
+
* interfere — and a test needing a fresh store just calls it again.
|
|
9
|
+
*/
|
|
10
|
+
export declare const makeInMemoryProjectionStore: Effect.Effect<ProjectionStore>;
|
|
11
|
+
//# sourceMappingURL=inMemoryProjectionStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inMemoryProjectionStore.d.ts","sourceRoot":"","sources":["../../src/inMemoryProjectionStore.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAE,MAAM,EAAO,MAAM,QAAQ,CAAA;AACpC,OAAO,KAAK,EAAiB,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAuBvE;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CA+FnE,CAAA"}
|