@kairos-es/codec 1.0.0-alpha.4 → 1.0.0-alpha.5
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/README.md +2 -2
- package/dist/cjs/MetadataSource.js +1 -1
- package/dist/cjs/MetadataSource.js.map +1 -1
- package/dist/cjs/Serializer.js +3 -3
- package/dist/cjs/Serializer.js.map +1 -1
- package/dist/cjs/Uuid.js +6 -3
- package/dist/cjs/Uuid.js.map +1 -1
- package/dist/cjs/decider.js +1 -1
- package/dist/cjs/decisionModel.js +1 -1
- package/dist/cjs/decisionModel.js.map +1 -1
- package/dist/cjs/decode.js +5 -5
- package/dist/cjs/decode.js.map +1 -1
- package/dist/cjs/deserialiseThenDecode.js +1 -1
- package/dist/cjs/deserialiseThenDecode.js.map +1 -1
- package/dist/cjs/errors.js +6 -4
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/event.js.map +1 -1
- package/dist/cjs/precondition.js +2 -2
- package/dist/cjs/precondition.js.map +1 -1
- package/dist/cjs/projection.js +9 -10
- package/dist/cjs/projection.js.map +1 -1
- package/dist/cjs/readLedger.js +3 -3
- package/dist/cjs/readLedger.js.map +1 -1
- package/dist/cjs/registry.js.map +1 -1
- package/dist/cjs/toEvent.js +4 -4
- package/dist/cjs/toEvent.js.map +1 -1
- package/dist/dts-cjs/MetadataSource.d.ts +8 -0
- package/dist/{dts → dts-cjs}/MetadataSource.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/Serializer.d.ts +3 -3
- package/dist/{dts → dts-cjs}/Serializer.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/Uuid.d.ts +6 -3
- package/dist/{dts → dts-cjs}/Uuid.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/decider.d.ts +7 -7
- package/dist/{dts → dts-cjs}/decider.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/decision.d.ts +2 -2
- package/dist/{dts → dts-cjs}/decode.d.ts +3 -3
- package/dist/{dts → dts-cjs}/decode.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/deserialiseThenDecode.d.ts +3 -3
- package/dist/{dts → dts-cjs}/deserialiseThenDecode.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/errors.d.ts +4 -3
- package/dist/dts-cjs/errors.d.ts.map +1 -0
- package/dist/{dts → dts-cjs}/event.d.ts +18 -8
- package/dist/dts-cjs/event.d.ts.map +1 -0
- package/dist/dts-cjs/package.json +3 -0
- package/dist/{dts → dts-cjs}/precondition.d.ts +2 -2
- package/dist/{dts → dts-cjs}/projection.d.ts +2 -2
- package/dist/dts-cjs/projection.d.ts.map +1 -0
- package/dist/{dts → dts-cjs}/readLedger.d.ts +4 -4
- package/dist/{dts → dts-cjs}/readLedger.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/toEvent.d.ts +2 -2
- package/dist/dts-esm/MetadataSource.d.ts +8 -0
- package/dist/dts-esm/MetadataSource.d.ts.map +1 -0
- package/dist/dts-esm/Serializer.d.ts +27 -0
- package/dist/dts-esm/Serializer.d.ts.map +1 -0
- package/dist/dts-esm/Uuid.d.ts +22 -0
- package/dist/dts-esm/Uuid.d.ts.map +1 -0
- package/dist/dts-esm/decider.d.ts +206 -0
- package/dist/dts-esm/decider.d.ts.map +1 -0
- package/dist/dts-esm/decision.d.ts +161 -0
- package/dist/dts-esm/decision.d.ts.map +1 -0
- package/dist/dts-esm/decisionModel.d.ts +149 -0
- package/dist/dts-esm/decisionModel.d.ts.map +1 -0
- package/dist/dts-esm/decode.d.ts +92 -0
- package/dist/dts-esm/decode.d.ts.map +1 -0
- package/dist/dts-esm/deserialiseThenDecode.d.ts +62 -0
- package/dist/dts-esm/deserialiseThenDecode.d.ts.map +1 -0
- package/dist/dts-esm/errors.d.ts +80 -0
- package/dist/dts-esm/errors.d.ts.map +1 -0
- package/dist/dts-esm/event.d.ts +81 -0
- package/dist/dts-esm/event.d.ts.map +1 -0
- package/dist/dts-esm/index.d.ts +134 -0
- package/dist/dts-esm/index.d.ts.map +1 -0
- package/dist/dts-esm/package.json +3 -0
- package/dist/dts-esm/precondition.d.ts +244 -0
- package/dist/dts-esm/precondition.d.ts.map +1 -0
- package/dist/dts-esm/projection.d.ts +56 -0
- package/dist/dts-esm/projection.d.ts.map +1 -0
- package/dist/dts-esm/readLedger.d.ts +263 -0
- package/dist/dts-esm/readLedger.d.ts.map +1 -0
- package/dist/dts-esm/registry.d.ts +46 -0
- package/dist/dts-esm/registry.d.ts.map +1 -0
- package/dist/dts-esm/toEvent.d.ts +45 -0
- package/dist/dts-esm/toEvent.d.ts.map +1 -0
- package/dist/esm/MetadataSource.js +1 -1
- package/dist/esm/MetadataSource.js.map +1 -1
- package/dist/esm/Serializer.js +3 -3
- package/dist/esm/Serializer.js.map +1 -1
- package/dist/esm/Uuid.js +6 -3
- package/dist/esm/Uuid.js.map +1 -1
- package/dist/esm/decider.js +1 -1
- package/dist/esm/decisionModel.js +1 -1
- package/dist/esm/decisionModel.js.map +1 -1
- package/dist/esm/decode.js +5 -5
- package/dist/esm/decode.js.map +1 -1
- package/dist/esm/deserialiseThenDecode.js +1 -1
- package/dist/esm/deserialiseThenDecode.js.map +1 -1
- package/dist/esm/errors.js +6 -4
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/event.js.map +1 -1
- package/dist/esm/precondition.js +2 -2
- package/dist/esm/precondition.js.map +1 -1
- package/dist/esm/projection.js +9 -10
- package/dist/esm/projection.js.map +1 -1
- package/dist/esm/readLedger.js +3 -3
- package/dist/esm/readLedger.js.map +1 -1
- package/dist/esm/registry.js.map +1 -1
- package/dist/esm/toEvent.js +4 -4
- package/dist/esm/toEvent.js.map +1 -1
- package/package.json +12 -7
- package/src/MetadataSource.ts +2 -2
- package/src/Serializer.ts +4 -4
- package/src/Uuid.ts +7 -4
- package/src/decider.ts +9 -9
- package/src/decision.ts +1 -1
- package/src/decisionModel.ts +1 -1
- package/src/decode.ts +12 -18
- package/src/deserialiseThenDecode.ts +5 -5
- package/src/errors.ts +6 -4
- package/src/event.ts +19 -9
- package/src/precondition.ts +3 -3
- package/src/projection.ts +15 -20
- package/src/readLedger.ts +7 -7
- package/src/registry.ts +1 -1
- package/src/toEvent.ts +4 -4
- package/dist/dts/MetadataSource.d.ts +0 -8
- package/dist/dts/errors.d.ts.map +0 -1
- package/dist/dts/event.d.ts.map +0 -1
- package/dist/dts/projection.d.ts.map +0 -1
- /package/dist/{dts → dts-cjs}/decision.d.ts.map +0 -0
- /package/dist/{dts → dts-cjs}/decisionModel.d.ts +0 -0
- /package/dist/{dts → dts-cjs}/decisionModel.d.ts.map +0 -0
- /package/dist/{dts → dts-cjs}/index.d.ts +0 -0
- /package/dist/{dts → dts-cjs}/index.d.ts.map +0 -0
- /package/dist/{dts → dts-cjs}/precondition.d.ts.map +0 -0
- /package/dist/{dts → dts-cjs}/registry.d.ts +0 -0
- /package/dist/{dts → dts-cjs}/registry.d.ts.map +0 -0
- /package/dist/{dts → dts-cjs}/toEvent.d.ts.map +0 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Unchanged`: a decision's answer when there is nothing to do because the work
|
|
3
|
+
* is already done, and the two types that let a decision say so without costing
|
|
4
|
+
* any existing caller its `Position`.
|
|
5
|
+
*
|
|
6
|
+
* ## Why a decision needs a third answer
|
|
7
|
+
*
|
|
8
|
+
* A decision had two answers: a non-empty batch of events to append, or a
|
|
9
|
+
* failure. The idempotent case fitted neither. A webhook redelivered after its
|
|
10
|
+
* first delivery was recorded, or a recorder re-running after a conflict whose
|
|
11
|
+
* winning write already closed the item, has nothing to append — and nothing
|
|
12
|
+
* WRONG has happened, so a failure is the wrong channel: a caller would have to
|
|
13
|
+
* tell "already done" from a genuine refusal by matching a tag, and a retry
|
|
14
|
+
* policy keyed on the error channel would retry a success. An empty batch is not
|
|
15
|
+
* the answer either. `append` refuses one as a defect, and the `Decider`'s
|
|
16
|
+
* `NonEmptyReadonlyArray` bound is what makes that refusal unreachable from a
|
|
17
|
+
* decision, a guarantee `decider.ts` calls load-bearing. So "already done" gets a
|
|
18
|
+
* value of its own, and it is a SUCCESS: an idempotent skip is never retried.
|
|
19
|
+
*
|
|
20
|
+
* ## Why it is recognised by a type id, and never by `instanceof` or a `_tag`
|
|
21
|
+
*
|
|
22
|
+
* This package ships dual ESM/CJS, so one consumer's graph can hold two copies of
|
|
23
|
+
* this module and therefore two distinct `Unchanged` classes. Under `instanceof`,
|
|
24
|
+
* a value built by one copy fails the other's check, and the runner would treat
|
|
25
|
+
* "already done" as a batch and hand it to `append` — the same single point of
|
|
26
|
+
* silent failure `@kairos-es/core` argues against, in its contract store, for
|
|
27
|
+
* `AppendConditionFailed`. That error falls back on its `_tag`, and a tag is
|
|
28
|
+
* enough there because the error channel is the library's own. A DECISION's
|
|
29
|
+
* success channel is not: an application's own union may well carry a member
|
|
30
|
+
* tagged `'Unchanged'`, and a runner that skipped on the bare tag would swallow
|
|
31
|
+
* that value and append nothing, silently. `Symbol.for` gives an identity that is
|
|
32
|
+
* shared across both builds (the global symbol registry is per runtime, not per
|
|
33
|
+
* module copy) and that no application value carries by accident. `isUnchanged`
|
|
34
|
+
* is the one test, and every runner in this package uses it.
|
|
35
|
+
*
|
|
36
|
+
* ## Why the widening is a defaulted type parameter, not a second entry point
|
|
37
|
+
*
|
|
38
|
+
* `Decider` gains a fifth type parameter `O`, defaulting to the non-empty batch.
|
|
39
|
+
* A second entry point would have meant a second `Decider`, a second runner, a
|
|
40
|
+
* second `transact` and a second `decideOnly` — against a surface whose whole
|
|
41
|
+
* argument is that `runDecider` and `decideOnly` are ONE pair.
|
|
42
|
+
*
|
|
43
|
+
* ## Why `DecisionResult` is shaped as it is, and why `decider` is overloaded
|
|
44
|
+
*
|
|
45
|
+
* The promise is that a decider or body that cannot answer `Unchanged` keeps
|
|
46
|
+
* exactly the `Position` result it had. Two kinds of success make that harder
|
|
47
|
+
* than it looks, and both were measured against call shapes that compiled
|
|
48
|
+
* before the widening.
|
|
49
|
+
*
|
|
50
|
+
* - An UNTYPED success. `any` satisfies every question a conditional can ask of
|
|
51
|
+
* it: `Unchanged extends any` holds, and `any extends Unchanged` takes BOTH
|
|
52
|
+
* branches. So a decision nobody typed would read as one that can answer
|
|
53
|
+
* `Unchanged`, and its caller's `(position: Position) => …` would stop
|
|
54
|
+
* compiling. `DecisionResult` therefore checks for `any` FIRST, and answers
|
|
55
|
+
* `Position`.
|
|
56
|
+
* - A GENERIC success. In a wrapper generic in the success type
|
|
57
|
+
* (`<A extends NonEmptyReadonlyArray<DcbEvent>, …>`), a conditional over `A`
|
|
58
|
+
* stays DEFERRED, and a deferred conditional is `Position` only if the checker
|
|
59
|
+
* can see that it must be. It can for one shape and not for the other:
|
|
60
|
+
* `Unchanged extends O ? …` is related through the union of its two branches,
|
|
61
|
+
* which admits `Unchanged`; `O extends Unchanged ? …`, DISTRIBUTIVE over `O`,
|
|
62
|
+
* is related through itself instantiated at `O`'s bound — and at a bound of
|
|
63
|
+
* the non-empty batch that is `Position`. So `DecisionResult` is written the
|
|
64
|
+
* second way, and `runDecider` and `transact` keep ONE signature each, which
|
|
65
|
+
* is what lets `body.pipe(transact(p))` still infer its error and requirement
|
|
66
|
+
* channels; an overloaded step would lose both to `unknown`.
|
|
67
|
+
*
|
|
68
|
+
* `decider(…)` cannot be served by a conditional at all. Its `O` is derived from
|
|
69
|
+
* the SUCCESS of an effect type variable, and `Effect.Success` of a type
|
|
70
|
+
* variable is itself deferred, so a wrapper that copies `decider`'s own generics
|
|
71
|
+
* would get a `Decider` whose `O` nothing can resolve, and a runner result that
|
|
72
|
+
* is not `Position`. So `decider` is declared TWICE, which asks the question by
|
|
73
|
+
* ASSIGNABILITY instead: FIRST for an effect whose success is the non-empty
|
|
74
|
+
* batch, returning a `Decider` with the default `O`, and only then for any
|
|
75
|
+
* `DecisionOutput`, returning one whose `O` is `DecisionOutput`. A type
|
|
76
|
+
* variable bounded by the batch fits the first, and so does `any`; a `decide`
|
|
77
|
+
* with a branch answering `Unchanged` does not, and falls through to the second.
|
|
78
|
+
* Keep that ORDER: the checker takes the first declaration that fits, so
|
|
79
|
+
* swapped, every decider would be widened.
|
|
80
|
+
*
|
|
81
|
+
* Neither reaches a type QUERY. `ReturnType<typeof runDecider>` reads `O` at its
|
|
82
|
+
* bound, which admits `Unchanged`, so its success is widened; instantiated
|
|
83
|
+
* (`typeof runDecider<P, Command, E, R>`), `O` takes its default and the answer
|
|
84
|
+
* is `Position` again. `ReturnType<typeof decider<…>>` reads the LAST
|
|
85
|
+
* declaration, so it is the widened `Decider` whatever effect type it is given;
|
|
86
|
+
* a decider VALUE is typed by the declaration that fitted it.
|
|
87
|
+
*
|
|
88
|
+
* `DecisionOutput` still refuses a possibly-empty array — a `DcbEvent[]` from a
|
|
89
|
+
* `filter`, say — at compile time, because a plain array is neither non-empty nor
|
|
90
|
+
* `Unchanged`. The widening adds an answer; it removes no guarantee.
|
|
91
|
+
*/
|
|
92
|
+
import type { DcbEvent, Position } from '@kairos-es/core';
|
|
93
|
+
import { type Array as Arr } from 'effect';
|
|
94
|
+
/**
|
|
95
|
+
* The type id `isUnchanged` recognises. `Symbol.for`, so both halves of the dual
|
|
96
|
+
* build — and any two copies of this package in one graph — agree on it.
|
|
97
|
+
*/
|
|
98
|
+
export declare const UnchangedTypeId: unique symbol;
|
|
99
|
+
/** The type of `UnchangedTypeId`. */
|
|
100
|
+
export type UnchangedTypeId = typeof UnchangedTypeId;
|
|
101
|
+
declare const Unchanged_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]; }>) => Readonly<A> & {
|
|
102
|
+
readonly _tag: "Unchanged";
|
|
103
|
+
} & import("effect/Pipeable").Pipeable;
|
|
104
|
+
/**
|
|
105
|
+
* "Nothing to do: this work is already done." A runner that receives it appends
|
|
106
|
+
* nothing and returns it in place of a `Position`.
|
|
107
|
+
*
|
|
108
|
+
* `reason` is for a human reading a trace or a test failure, and nothing in the
|
|
109
|
+
* library reads it. A `Data.TaggedClass`, so two values with the same reason are
|
|
110
|
+
* `Equal` and a test can compare them structurally.
|
|
111
|
+
*
|
|
112
|
+
* ⚠ Test for it with `isUnchanged`, never with `instanceof` or `_tag` — the
|
|
113
|
+
* module documentation says why each of those is wrong.
|
|
114
|
+
*/
|
|
115
|
+
export declare class Unchanged extends Unchanged_base<{
|
|
116
|
+
readonly reason?: string;
|
|
117
|
+
}> {
|
|
118
|
+
readonly [UnchangedTypeId]: UnchangedTypeId;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Whether `u` is an `Unchanged`, by its type id. Total over `unknown`, and
|
|
122
|
+
* realm-safe across the dual ESM/CJS build. An application value that merely
|
|
123
|
+
* carries `_tag: 'Unchanged'` answers `false`, deliberately.
|
|
124
|
+
*/
|
|
125
|
+
export declare const isUnchanged: (u: unknown) => u is Unchanged;
|
|
126
|
+
/**
|
|
127
|
+
* The constructors a `decide` step reaches for. One member today; it is a
|
|
128
|
+
* namespace object rather than a bare function so the call site reads as what it
|
|
129
|
+
* is, `Decision.unchanged('already dispatched')`.
|
|
130
|
+
*/
|
|
131
|
+
export declare const Decision: {
|
|
132
|
+
readonly unchanged: (reason?: string) => Unchanged;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Everything a decision may succeed with: a non-empty batch of events to
|
|
136
|
+
* append, or `Unchanged`. A plain, possibly-empty array is NOT a member, which is
|
|
137
|
+
* what keeps an empty batch unreachable from a decision.
|
|
138
|
+
*/
|
|
139
|
+
export type DecisionOutput = Arr.NonEmptyReadonlyArray<DcbEvent> | Unchanged;
|
|
140
|
+
/**
|
|
141
|
+
* What a runner returns for a decision that succeeds with `O`: `Position` unless
|
|
142
|
+
* `O` admits `Unchanged`, and `Position | Unchanged` when it does.
|
|
143
|
+
*
|
|
144
|
+
* Asked of each member of `O` — DISTRIBUTIVE — so a union `O` answers
|
|
145
|
+
* `Position | Unchanged` exactly when one of its members is `Unchanged`, a batch
|
|
146
|
+
* answers `Position` with no `| never` residue, and an `O` that is a type
|
|
147
|
+
* variable bounded by the batch is seen to be `Position`, which is what a
|
|
148
|
+
* wrapper generic in its success type needs. `O` that can ONLY be `Unchanged`
|
|
149
|
+
* answers `Unchanged`, the precise result and assignable wherever
|
|
150
|
+
* `Position | Unchanged` is expected.
|
|
151
|
+
*
|
|
152
|
+
* Two members are checked BEFORE the distribution, and both answer `Position`,
|
|
153
|
+
* the result each had before `Unchanged` existed. `any`, because a distributive
|
|
154
|
+
* conditional over `any` takes both branches and would widen every untyped
|
|
155
|
+
* decision. And `never` — a body that only ever fails — because distributing
|
|
156
|
+
* over `never` answers `never`, which a variable once typed with a `Position`
|
|
157
|
+
* result would no longer accept. The module documentation owns the rest.
|
|
158
|
+
*/
|
|
159
|
+
export type DecisionResult<O> = 0 extends 1 & O ? Position : [O] extends [never] ? Position : O extends Unchanged ? Unchanged : Position;
|
|
160
|
+
export {};
|
|
161
|
+
//# sourceMappingURL=decision.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../../src/decision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,KAAK,KAAK,IAAI,GAAG,EAAmB,MAAM,QAAQ,CAAA;AAE3D;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,OAAO,MAEpC,CAAA;AAED,qCAAqC;AACrC,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAA;;;;AAEpD;;;;;;;;;;GAUG;AACH,qBAAa,SAAU,SAAQ,eAA8B;IAC3D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB,CAAC;IACA,QAAQ,CAAC,CAAC,eAAe,CAAC,EAAE,eAAe,CAAkB;CAC9D;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,SACH,CAAA;AAE3C;;;;GAIG;AACH,eAAO,MAAM,QAAQ,EAAE;IACrB,QAAQ,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,SAAS,CAAA;CAGnD,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,qBAAqB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAA;AAE5E;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAC3C,QAAQ,GACR,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GACjB,QAAQ,GACR,CAAC,SAAS,SAAS,GACjB,SAAS,GACT,QAAQ,CAAA"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-and-fold over a slice record: read the composed query, decode each event
|
|
3
|
+
* through the matching def's `Schema` in an effectful `Stream.mapEffect` stage,
|
|
4
|
+
* then fold *purely* through core's `composeProjections`.
|
|
5
|
+
*
|
|
6
|
+
* TWO exports sit over that ONE body, the second expressed over the first.
|
|
7
|
+
* `modelAtHead` stops where the fold does, at `{ state, head }` — a
|
|
8
|
+
* materialisation of the record with no table, no checkpoint and no runner.
|
|
9
|
+
* `buildDecisionModel` adds the only part that is genuinely decision-specific:
|
|
10
|
+
* the `AppendCondition` (the DCB optimistic-concurrency contract). It is core's
|
|
11
|
+
* old `buildDecisionModel(Record<string, AnyProjection>)` surface, moved here
|
|
12
|
+
* because decode needs `Schema` + the `Serializer`.
|
|
13
|
+
*
|
|
14
|
+
* BOTH exports also record into the ambient read ledger when one is in context,
|
|
15
|
+
* which is what makes a decision that walks a chain of identifiers guard on every
|
|
16
|
+
* hop rather than on its last. Recording is `modelAtHead`'s, so it happens
|
|
17
|
+
* whatever symbol performed the read; outside a bracket there is no ledger,
|
|
18
|
+
* nothing is recorded and no behaviour changes. `readLedger.ts` owns the whole
|
|
19
|
+
* argument, including why the recorder is this module rather than a
|
|
20
|
+
* layered-only entry point of its own.
|
|
21
|
+
*
|
|
22
|
+
* The fold stays pure and in core; only the decode stage is effectful, and that
|
|
23
|
+
* stage lives in `decode.ts` — `decodeSlices` — so the read side folds a live
|
|
24
|
+
* `subscribe` stream through the *same* decode this module folds a bounded
|
|
25
|
+
* `read` stream through.
|
|
26
|
+
*
|
|
27
|
+
* The result requirement is exactly `DcbEventStore | Serializer | R`, where `R`
|
|
28
|
+
* is the union of the record's slices' requirements — inferred with no cast,
|
|
29
|
+
* `R` being carried covariantly by each slice's `schemas` map value type.
|
|
30
|
+
* `Serializer` is not yielded here: `decodeSlices` reads it from context itself,
|
|
31
|
+
* so the requirement arrives through the decode stage rather than through a
|
|
32
|
+
* binding this module owns (see `decode.ts` for why).
|
|
33
|
+
*/
|
|
34
|
+
import { type AppendCondition, type CompositeState, DcbEventStore, type Position } from '@kairos-es/core';
|
|
35
|
+
import { Effect } from 'effect';
|
|
36
|
+
import type { CodecError } from './errors.js';
|
|
37
|
+
import type { SliceProjection } from './projection.js';
|
|
38
|
+
import { type TaglessSliceInLedger } from './readLedger.js';
|
|
39
|
+
import type { Serializer } from './Serializer.js';
|
|
40
|
+
/** The union of requirements `R` carried by a record of `SliceProjection`s. */
|
|
41
|
+
export type RequirementOf<T extends Record<string, SliceProjection>> = T[keyof T] extends SliceProjection<infer _S, infer R> ? R : never;
|
|
42
|
+
/**
|
|
43
|
+
* Read the events for the composed slices' query, decode each through its def's
|
|
44
|
+
* `Schema`, fold into per-key state, and return `{ state, head }`: one slice
|
|
45
|
+
* record materialised at read time, with no table, no checkpoint and no runner.
|
|
46
|
+
*
|
|
47
|
+
* ## Why `head` comes back, and not bare `state`
|
|
48
|
+
*
|
|
49
|
+
* `head` is the consistency statement, and without it the state is a figure with
|
|
50
|
+
* no as-of — which is why the as-of is in the name. The fold stops at the
|
|
51
|
+
* position the read returned and is maintained no further, so `head` is the
|
|
52
|
+
* whole of what a caller can say about this state's currency: it is what lets
|
|
53
|
+
* the write path turn it into an `AppendCondition` (see `buildDecisionModel`),
|
|
54
|
+
* and what lets a read-time caller report, cache or compare a state against a
|
|
55
|
+
* position rather than against a wall clock.
|
|
56
|
+
*
|
|
57
|
+
* It is also the inversion worth naming out loud, because intuition runs the
|
|
58
|
+
* other way. A MAINTAINED view is consistent as of its CHECKPOINT, which trails
|
|
59
|
+
* the log's head by however far behind its runner is; this fold is consistent as
|
|
60
|
+
* of the head itself. So of two materialisations of one slice record, the one
|
|
61
|
+
* with no table, no checkpoint and no runner is the MOST current, and the table
|
|
62
|
+
* beside it is the stale one. Reach for a maintained view for the queries a
|
|
63
|
+
* single fold cannot serve — across many identities, ordered, joined, paginated
|
|
64
|
+
* — and never because it is fresher, because it is not.
|
|
65
|
+
*
|
|
66
|
+
* ⚠ AND INSIDE A BRACKET IT IS NOT THE FLOOR THE APPEND WILL USE. Everything
|
|
67
|
+
* above is a statement about THIS read, and it stays true; what changes is that a
|
|
68
|
+
* bracket guards on the EARLIEST head across every hop it walked, which on any hop
|
|
69
|
+
* after the first is a strictly lower number than the one returned here. So a
|
|
70
|
+
* caller who reads `head` inside a bracket and reasons about what the append will
|
|
71
|
+
* refuse is reasoning from the wrong figure — the guard is wider than this head
|
|
72
|
+
* says. Nothing here is wrong; the trap is assuming the two are the same number,
|
|
73
|
+
* and it is named because it is exactly the assumption that holds outside a
|
|
74
|
+
* bracket and quietly stops holding inside one. `readLedger.ts` owns the floor.
|
|
75
|
+
*
|
|
76
|
+
* `head` is LOG-GLOBAL: it is the head the store's `read` returned, so it moves
|
|
77
|
+
* for an event no slice here selects. `test/decisionModel.test.ts` pins that
|
|
78
|
+
* behaviour EXPLICITLY, over four appended events one of which the composed query
|
|
79
|
+
* excludes by tag; `test/modelAtHead.test.ts` pins it incidentally, its own head
|
|
80
|
+
* assertion landing on that same excluded event's position. Two guards, one of them
|
|
81
|
+
* deliberate — reach for the explicit one when the property itself is what changes.
|
|
82
|
+
* Log-global neither tightens nor loosens the derived guard — there can be no
|
|
83
|
+
* event matching the query between the query's last match and the log head, so a
|
|
84
|
+
* query-filtered head would refuse exactly the same appends — which is precisely
|
|
85
|
+
* why the store's own figure is the one to carry: one fact about the log, not a
|
|
86
|
+
* per-query derivation, and the honest as-of for a state folded from everything
|
|
87
|
+
* the query selected up to it.
|
|
88
|
+
*
|
|
89
|
+
* ## Why this lives in the codec and not in `@kairos-es/read`
|
|
90
|
+
*
|
|
91
|
+
* In `@kairos-es/read` it would be the only export touching none of that
|
|
92
|
+
* package's machinery: no checkpoint, no `EventLogDurability` (R2 is vacuous
|
|
93
|
+
* where there is no checkpoint to outlive the log), no rung of that package's
|
|
94
|
+
* construction gate, and none of its test apparatus. The vocabulary is already
|
|
95
|
+
* this package's — ADR-0004 names the typed slice path's clients as "a decision
|
|
96
|
+
* model / materialisation / command" — and the body is `buildDecisionModel`'s,
|
|
97
|
+
* which is here for the reason the module doc gives.
|
|
98
|
+
*/
|
|
99
|
+
export declare const modelAtHead: <T extends Record<string, SliceProjection>>(projections: T) => Effect.Effect<{
|
|
100
|
+
readonly state: CompositeState<T>;
|
|
101
|
+
readonly head: Position;
|
|
102
|
+
}, CodecError | TaglessSliceInLedger, DcbEventStore | Serializer | RequirementOf<T>>;
|
|
103
|
+
/**
|
|
104
|
+
* The write side's decision model: `modelAtHead` plus the `AppendCondition` that
|
|
105
|
+
* reuses the read query as `failIfEventsMatch` and the read head as `after` (the
|
|
106
|
+
* DCB optimistic-concurrency contract).
|
|
107
|
+
*
|
|
108
|
+
* It is expressed OVER `modelAtHead` rather than beside it, and that is the point
|
|
109
|
+
* of the pair. Two functions reading, decoding and folding a slice record almost
|
|
110
|
+
* identically would need two sets of tests and would be free to drift on the
|
|
111
|
+
* decode stage, on the cast recovering `CompositeState<T>`, or on which record
|
|
112
|
+
* the query is composed from. Re-expressing makes this a decomposition instead
|
|
113
|
+
* of a near-duplicate: `modelAtHead` is the shared part, this function is the
|
|
114
|
+
* condition and nothing else, and each is shorter than the one function was.
|
|
115
|
+
*
|
|
116
|
+
* Outside a bracket `composeProjections` therefore runs twice for one command —
|
|
117
|
+
* once inside `modelAtHead` for the read, once here for `failIfEventsMatch` — and
|
|
118
|
+
* that is deliberate rather than waste. It is a pure derivation of the record, so
|
|
119
|
+
* the two queries cannot disagree however either call site changes. The
|
|
120
|
+
* alternative, returning the query from `modelAtHead`, would put a third field on
|
|
121
|
+
* a READ-TIME API that no read-time caller wants, to save one pure merge over a
|
|
122
|
+
* handful of slices; a surface paying for a body's convenience is the worse
|
|
123
|
+
* trade. Inside a bracket the second merge is not reached at all: the ledger's
|
|
124
|
+
* own derivation composes every recorded hop instead.
|
|
125
|
+
*
|
|
126
|
+
* ## How to attribute the read, the decode and the fold — the rule, stated once
|
|
127
|
+
*
|
|
128
|
+
* Because this function is expressed OVER `modelAtHead`, prose about the codec
|
|
129
|
+
* splits into two kinds and only one of them may name this symbol:
|
|
130
|
+
*
|
|
131
|
+
* - Naming this function as the ENTRY POINT a caller invokes, or as the path
|
|
132
|
+
* something is reached THROUGH, is correct — "the `buildDecisionModel` fold", "the
|
|
133
|
+
* write path reaches decode through `buildDecisionModel`". Transitive reach is real
|
|
134
|
+
* reach, and this is the symbol a command actually calls.
|
|
135
|
+
* - Naming it as the function that CONTAINS the read, the decode or the fold is
|
|
136
|
+
* FALSE. All three are `modelAtHead`'s; this body adds an `AppendCondition` and
|
|
137
|
+
* nothing else.
|
|
138
|
+
*
|
|
139
|
+
* Worth writing down because the distinction is invisible from a call site and the
|
|
140
|
+
* mistake is systematic rather than careless: every such claim was TRUE before
|
|
141
|
+
* `modelAtHead` was extracted from here, so a doc written against the older shape
|
|
142
|
+
* reads perfectly and is wrong. The correction is to name `modelAtHead`, never to
|
|
143
|
+
* soften the sentence.
|
|
144
|
+
*/
|
|
145
|
+
export declare const buildDecisionModel: <T extends Record<string, SliceProjection>>(projections: T) => Effect.Effect<{
|
|
146
|
+
readonly state: CompositeState<T>;
|
|
147
|
+
readonly appendCondition: AppendCondition;
|
|
148
|
+
}, CodecError | TaglessSliceInLedger, DcbEventStore | Serializer | RequirementOf<T>>;
|
|
149
|
+
//# sourceMappingURL=decisionModel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decisionModel.d.ts","sourceRoot":"","sources":["../../src/decisionModel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,aAAa,EACb,KAAK,QAAQ,EACd,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,MAAM,EAAkB,MAAM,QAAQ,CAAA;AAE/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,+EAA+E;AAC/E,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,IACjE,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,eAAe,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACnE,aAAa,CAAC,KACb,MAAM,CAAC,MAAM,CACd;IAAE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,EAC9D,UAAU,GAAG,oBAAoB,EACjC,aAAa,GAAG,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,CA6C3C,CAAA;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EAC1E,aAAa,CAAC,KACb,MAAM,CAAC,MAAM,CACd;IACE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAA;IACjC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAA;CAC1C,EACD,UAAU,GAAG,oBAAoB,EACjC,aAAa,GAAG,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,CA6B3C,CAAA"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared decode stage: the wire-bytes -> typed-payload half of the codec
|
|
3
|
+
* seam, lifted out of `buildDecisionModel` into a `Stream` combinator so every
|
|
4
|
+
* consumer of a slice record decodes through *one* implementation.
|
|
5
|
+
*
|
|
6
|
+
* Two consumers, one stage. `modelAtHead` folds a bounded `read` stream into
|
|
7
|
+
* decision state, which is how `buildDecisionModel` gets one; the read side folds
|
|
8
|
+
* an unbounded `subscribe` stream into a materialised view. They differ in
|
|
9
|
+
* everything *downstream* of decode (bounded vs live, a pure fold vs a
|
|
10
|
+
* transactional commit) and in nothing upstream of it — same slice record, same `type -> Schema` lookup, same
|
|
11
|
+
* `Serializer`, same failure taxonomy — so decode is exactly where the seam
|
|
12
|
+
* belongs. Duplicating it would let the two paths drift: a read side with its
|
|
13
|
+
* own copy could silently accept an event the decision path rejects, or reword
|
|
14
|
+
* a `CodecError` reason, and nothing would catch it.
|
|
15
|
+
*
|
|
16
|
+
* Why a `Stream` combinator rather than an `Effect` over a materialised array:
|
|
17
|
+
* decode must stay back-pressured. `Stream.mapEffect` with no `concurrency`
|
|
18
|
+
* option decodes one event at a time, pulling only as fast as the consumer
|
|
19
|
+
* folds, and that is what lets a projection catch up over a backlog far larger
|
|
20
|
+
* than memory.
|
|
21
|
+
*
|
|
22
|
+
* Be precise about what adding `concurrency` here would and would not cost,
|
|
23
|
+
* because the obvious reason is the wrong one. Effect's `mapEffect` is
|
|
24
|
+
* order-PRESERVING even when concurrent — reordering needs the separate
|
|
25
|
+
* `unordered: true` option (verified against `effect@4`'s `Channel.mapEffect`,
|
|
26
|
+
* which takes the ordered path unless that option is set) — so a bare
|
|
27
|
+
* `concurrency` would NOT hand the fold its events out of position order. What
|
|
28
|
+
* it would actually cost is the back-pressure above, and it would run
|
|
29
|
+
* application-authored `Schema` transforms (an ADR-0004 decryption hook, say)
|
|
30
|
+
* concurrently, which their authors have no reason to expect. `unordered: true`
|
|
31
|
+
* is the option that would be an outright correctness bug against an
|
|
32
|
+
* order-sensitive fold. Sequential is the right default for both reasons; the
|
|
33
|
+
* order argument alone does not carry it.
|
|
34
|
+
*
|
|
35
|
+
* Why these failures are values and not defects: corrupt bytes, an absent
|
|
36
|
+
* schema and a `Schema` mismatch are *data* problems an application can
|
|
37
|
+
* legitimately handle (quarantine the event, fail the command, alert), so they
|
|
38
|
+
* belong on the error channel as `CodecError` — see `errors.ts`. The three
|
|
39
|
+
* `reason` strings are observable surface; treat them as fixed, not cosmetic.
|
|
40
|
+
*
|
|
41
|
+
* Two of those three are no longer this module's to reword. `deserialise` and
|
|
42
|
+
* `decode` are fixed in `deserialiseThenDecode`, the two-step this stage shares
|
|
43
|
+
* with the general (registry) path, precisely so that a claim about surface has
|
|
44
|
+
* ONE implementation behind it rather than a copy per path. The absent-schema
|
|
45
|
+
* reason stays here, because the two paths deliberately disagree about what a
|
|
46
|
+
* lookup miss means — `UnknownEventType` there, ADR-0005 — and that helper's doc
|
|
47
|
+
* carries the argument.
|
|
48
|
+
*/
|
|
49
|
+
import type { DecodedEvent, SequencedEvent } from '@kairos-es/core';
|
|
50
|
+
import { Stream } from 'effect';
|
|
51
|
+
import type { RequirementOf } from './decisionModel.js';
|
|
52
|
+
import { CodecError } from './errors.js';
|
|
53
|
+
import type { SliceProjection } from './projection.js';
|
|
54
|
+
import { Serializer } from './Serializer.js';
|
|
55
|
+
/**
|
|
56
|
+
* The public decode stage: give it the slice record, get back a `Stream`
|
|
57
|
+
* combinator that decodes every event those slices can handle.
|
|
58
|
+
*
|
|
59
|
+
* **Why one call rather than two.** The lookup and the stage were briefly two
|
|
60
|
+
* public symbols — merge the record, then hand the map to the stage — and every
|
|
61
|
+
* consumer in the repo composed them in exactly that order and did nothing else
|
|
62
|
+
* with the intermediate map. A two-step protocol with one legal composition is
|
|
63
|
+
* not flexibility, it is a way to get it wrong: a caller can merge the wrong
|
|
64
|
+
* record, or a record that is not the one its `Query` was composed from, and
|
|
65
|
+
* that second mistake is silent. In the read side it would decode a live
|
|
66
|
+
* subscription against schemas the subscription never selects — every event
|
|
67
|
+
* arriving would fail `no schema for event type`, framed as a data problem when
|
|
68
|
+
* it is a wiring one. Taking the slices directly removes the intermediate value
|
|
69
|
+
* a caller could substitute, so the schemas provably come from the same record
|
|
70
|
+
* the query does.
|
|
71
|
+
*
|
|
72
|
+
* **Still curried, deliberately.** The merge is a pure derivation of the slice
|
|
73
|
+
* record, so both consumers build the stage ONCE — per decision model, per read
|
|
74
|
+
* model — and reuse it across every stream they drive. Rebuilding the table per
|
|
75
|
+
* stream (let alone per restart) would only burn allocations, and the currying
|
|
76
|
+
* is what makes "built once" the natural way to call this rather than a
|
|
77
|
+
* discipline.
|
|
78
|
+
*
|
|
79
|
+
* **It can REFUSE the record**, and does so the moment it is called rather than
|
|
80
|
+
* once events are flowing: a type reachable from two slices under two DIFFERENT
|
|
81
|
+
* `Schema`s throws, in the same words `makeRegistry` uses. See
|
|
82
|
+
* `mergeSliceSchemas` for the rule, why reference equality is its discriminator
|
|
83
|
+
* and why a throw is the right mechanism. Both in-repo consumers call this
|
|
84
|
+
* before there is a stream to fail, so inside their `Effect.gen`s the refusal is
|
|
85
|
+
* a defect at construction.
|
|
86
|
+
*
|
|
87
|
+
* `RequirementOf<T>` surfaces in the result's `R`, so a slice whose `Schema`
|
|
88
|
+
* carries a requirement (an application-authored decryption transform, ADR-0004)
|
|
89
|
+
* propagates it to the caller with no cast at the call site.
|
|
90
|
+
*/
|
|
91
|
+
export declare const decodeSlices: <T extends Record<string, SliceProjection>>(slices: T) => (<E, R>(events: Stream.Stream<SequencedEvent, E, R>) => Stream.Stream<DecodedEvent, E | CodecError, R | RequirementOf<T> | Serializer>);
|
|
92
|
+
//# sourceMappingURL=decode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../src/decode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACnE,OAAO,EAAuB,MAAM,EAAE,MAAM,QAAQ,CAAA;AAIpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAoJzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACpE,QAAQ,CAAC,KACR,CAAC,CAAC,CAAC,EAAE,CAAC,EACP,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,EAAE,CAAC,CAAC,KACxC,MAAM,CAAC,MAAM,CAChB,YAAY,EACZ,CAAC,GAAG,UAAU,EACd,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,UAAU,CAClC,CAA4C,CAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two-step every decode in this package performs, in one implementation:
|
|
3
|
+
* deserialise the wire bytes through the `Serializer` its caller already holds,
|
|
4
|
+
* then `Schema.decodeUnknownEffect` the result, each half's failure wrapped as the
|
|
5
|
+
* `CodecError` whose `reason` says WHICH half failed.
|
|
6
|
+
*
|
|
7
|
+
* **What it is the single home of.** The two `reason` strings `'deserialise'`
|
|
8
|
+
* and `'decode'`, and their ORDER. Why those strings are observable surface —
|
|
9
|
+
* fixed rather than cosmetic, because an application quarantines an event,
|
|
10
|
+
* alerts or fails a command off them — is argued in `decode.ts`'s module doc and
|
|
11
|
+
* is not restated here. What this module adds is that the argument had TWO
|
|
12
|
+
* implementations standing behind it: the slice stage (`decodeEvents`) and the
|
|
13
|
+
* general/registry path (`makeRegistry`'s `decode`) each wrote the pair out, in
|
|
14
|
+
* one package, and only the slice copy was pinned by a test. A reworded `reason`
|
|
15
|
+
* on the registry path would have been caught by nothing. Sharing the step
|
|
16
|
+
* DELETES that drift channel rather than alarming it, which is why this exists
|
|
17
|
+
* as a function and not as a second test.
|
|
18
|
+
*
|
|
19
|
+
* **The order is contract, not accident.** Deserialise has to run first because
|
|
20
|
+
* `Schema.decodeUnknownEffect` has nothing to read until it has, and the observable
|
|
21
|
+
* consequence is that the two reasons PARTITION the causes an operator acts on:
|
|
22
|
+
* rotten bytes are always `'deserialise'` and never `'decode'`, so a storage or
|
|
23
|
+
* producer problem never arrives dressed as schema drift. Swapping the labels
|
|
24
|
+
* would not break a decode; it would send every incident to the wrong team.
|
|
25
|
+
*
|
|
26
|
+
* **Why a `Serializer` VALUE rather than reading the tag here.** The two callers
|
|
27
|
+
* acquire it at deliberately different moments and this step must not flatten
|
|
28
|
+
* that. `decodeEvents` reads it ONCE when the stream starts (`Stream.unwrap`), so
|
|
29
|
+
* a backlog of a million events does not re-read context per event; the
|
|
30
|
+
* registry's `decode` is a per-event `Effect` and yields it inside its own
|
|
31
|
+
* `Effect.gen`. Taking the resolved service preserves both, and keeps
|
|
32
|
+
* `Serializer` off this signature's `R`, so the requirement stays declared where
|
|
33
|
+
* the acquisition actually happens — which is where a reader goes looking for it.
|
|
34
|
+
*
|
|
35
|
+
* **Why the LOOKUP is not folded in**, though both callers do one immediately
|
|
36
|
+
* before calling this: the two paths disagree about what a miss MEANS, and the
|
|
37
|
+
* disagreement is deliberate. The slice path fails `CodecError` with
|
|
38
|
+
* `no schema for event type '<type>'` — a caller named the type and the record it
|
|
39
|
+
* supplied has no entry for it, so the wiring is wrong. The registry path fails
|
|
40
|
+
* `UnknownEventType` — the type arrived off the WIRE and the registry does not
|
|
41
|
+
* know it, which is ADR-0005 decode-totality and the one fault a rolling
|
|
42
|
+
* deployment is EXPECTED to meet. `errors.ts` argues why those two sentences stay
|
|
43
|
+
* greppable apart. Folding the lookup in would mean a per-caller miss
|
|
44
|
+
* constructor: a second seam, bought to share the one thing the two paths
|
|
45
|
+
* genuinely do not have in common.
|
|
46
|
+
*
|
|
47
|
+
* `R` is the SCHEMA's requirement, propagated untouched — an ADR-0004 decryption
|
|
48
|
+
* transform reaches the caller's `R` through here with no cast. The three
|
|
49
|
+
* parameters are positional because their types are mutually unassignable, so a
|
|
50
|
+
* transposed call is a compile error rather than a silent inversion.
|
|
51
|
+
*
|
|
52
|
+
* PACKAGE-INTERNAL: exported to its two callers and no further, deliberately
|
|
53
|
+
* absent from `index.ts`. It is a shared step, not a capability on offer — a
|
|
54
|
+
* consumer holding bytes and a `Schema` wants `decodeSlices` or a `Registry`,
|
|
55
|
+
* both of which do the lookup this one refuses to.
|
|
56
|
+
*/
|
|
57
|
+
import type { Context } from 'effect';
|
|
58
|
+
import { Effect, Schema } from 'effect';
|
|
59
|
+
import { CodecError } from './errors.js';
|
|
60
|
+
import type { Serializer } from './Serializer.js';
|
|
61
|
+
export declare const deserialiseThenDecode: <R>(serializer: Context.Service.Shape<typeof Serializer>, schema: Schema.Decoder<unknown, R>, bytes: Uint8Array) => Effect.Effect<unknown, CodecError, R>;
|
|
62
|
+
//# sourceMappingURL=deserialiseThenDecode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deserialiseThenDecode.d.ts","sourceRoot":"","sources":["../../src/deserialiseThenDecode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,eAAO,MAAM,qBAAqB,GAAI,CAAC,EACrC,YAAY,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,EACpD,QAAQ,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAClC,OAAO,UAAU,KAChB,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC,CAUpC,CAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
declare const CodecError_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 & {
|
|
2
|
+
readonly _tag: "CodecError";
|
|
3
|
+
} & Readonly<A>;
|
|
4
|
+
/** A serialisation or `Schema` decode/encode failure on the codec path. */
|
|
5
|
+
export declare class CodecError extends CodecError_base<{
|
|
6
|
+
readonly reason: string;
|
|
7
|
+
readonly cause?: unknown;
|
|
8
|
+
}> {
|
|
9
|
+
/**
|
|
10
|
+
* `reason` again, as the `Error` field that says why — so the fault renders
|
|
11
|
+
* ITSELF.
|
|
12
|
+
*
|
|
13
|
+
* `Data.TaggedError` sets `name` to the tag and leaves `message` empty, so
|
|
14
|
+
* without this a `CodecError` stringifies to `'CodecError'`, and the sentence
|
|
15
|
+
* naming the schema, the event type or the missing key that failed reaches a log
|
|
16
|
+
* line only where the code doing the logging knows to reach in for `reason`.
|
|
17
|
+
* Filled, `Error.prototype.toString` yields `'CodecError: <reason>'`, which is
|
|
18
|
+
* what `Cause.pretty` (it reads `message` off anything `instanceof Error`) and the
|
|
19
|
+
* read side's projection supervisor both report — neither of them holding a table
|
|
20
|
+
* of this class's fields to keep in step with it.
|
|
21
|
+
*
|
|
22
|
+
* This class is on the package's PUBLISHED surface, so be exact about what the
|
|
23
|
+
* field moves. Everything that renders the error for a HUMAN: `String(err)`,
|
|
24
|
+
* `Cause.pretty` and therefore a failed fibre's `cause` annotation on every
|
|
25
|
+
* shipped logger, and an unhandled failure's report. Nothing that renders it for
|
|
26
|
+
* a MACHINE: `Data.Error` overrides `YieldableError`'s `toJSON` with
|
|
27
|
+
* `{ ...plainArgs, ...this }` — own enumerables plus the constructor args, and a
|
|
28
|
+
* prototype accessor is neither — so `JSON.stringify(err)` still emits the same
|
|
29
|
+
* `reason`/`_tag` and a structured log of a raw `CodecError` keeps its shape.
|
|
30
|
+
*
|
|
31
|
+
* A GETTER rather than a second field, so `reason` stays the single declaration.
|
|
32
|
+
* It is visible because `Data.TaggedError`'s constructor is
|
|
33
|
+
* `super(args?.message, …)` followed by copying the args' own enumerable keys
|
|
34
|
+
* onto the instance (verified against `effect@4`'s `internal/core.ts`): the
|
|
35
|
+
* args above carry no `message`, so nothing shadows this prototype accessor
|
|
36
|
+
* with an own property.
|
|
37
|
+
*/
|
|
38
|
+
get message(): string;
|
|
39
|
+
}
|
|
40
|
+
declare const UnknownEventType_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 & {
|
|
41
|
+
readonly _tag: "UnknownEventType";
|
|
42
|
+
} & Readonly<A>;
|
|
43
|
+
/** A wire event whose runtime type string is outside the registry's known set. */
|
|
44
|
+
export declare class UnknownEventType extends UnknownEventType_base<{
|
|
45
|
+
readonly type: string;
|
|
46
|
+
}> {
|
|
47
|
+
/**
|
|
48
|
+
* The offending type, in a sentence saying what the registry did with it — so the
|
|
49
|
+
* fault renders ITSELF, exactly as `CodecError` above does.
|
|
50
|
+
*
|
|
51
|
+
* `Data.TaggedError` sets `name` to the tag and leaves `message` empty, so without
|
|
52
|
+
* this a decode that met an unregistered type stringifies to `'UnknownEventType'`
|
|
53
|
+
* — the one datum that makes the failure actionable, the type string itself,
|
|
54
|
+
* reaching a log line only where the logging code knows to reach in for `type`.
|
|
55
|
+
* Filled, `Error.prototype.toString` yields the tag followed by
|
|
56
|
+
* `event type 'nope.v1' is not in the registry`, which is what `Cause.pretty`
|
|
57
|
+
* prints and what an operator greps for. It matters here in particular because
|
|
58
|
+
* this class is the home of ADR-0005 decode-totality: a wire event outside the
|
|
59
|
+
* registered set is the expected consequence of a deployment reading a log a newer
|
|
60
|
+
* writer has extended, and the first question is always WHICH type — which the tag
|
|
61
|
+
* alone cannot answer.
|
|
62
|
+
*
|
|
63
|
+
* The wording deliberately differs from `CodecError`'s. That one says there is no
|
|
64
|
+
* schema for a type, and is raised on the TYPED path, where a caller named the
|
|
65
|
+
* type and the schema map it supplied has no entry for it. This one is the general
|
|
66
|
+
* (registry) path, where the type arrived off the WIRE and the registry is what
|
|
67
|
+
* does not know it. Distinct sentences keep the two greppable apart, which a
|
|
68
|
+
* shared phrasing would cost for nothing.
|
|
69
|
+
*
|
|
70
|
+
* A GETTER rather than a second field, so `type` stays the single declaration.
|
|
71
|
+
* What filling this field reaches, what it deliberately leaves untouched, and why
|
|
72
|
+
* a prototype accessor is visible at all are argued once on `CodecError` above and
|
|
73
|
+
* not restated here — it is the same package, the same published surface and the
|
|
74
|
+
* same mechanism, and two copies of that paragraph would be two things to keep in
|
|
75
|
+
* step. The read side's four getters point at one home for the identical reason.
|
|
76
|
+
*/
|
|
77
|
+
get message(): string;
|
|
78
|
+
}
|
|
79
|
+
export {};
|
|
80
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":";;;AAgBA,2EAA2E;AAC3E,qBAAa,UAAW,SAAQ,gBAA+B;IAC7D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CACzB,CAAC;IACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;;;;AAED,kFAAkF;AAClF,qBAAa,gBAAiB,SAAQ,sBAAqC;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB,CAAC;IACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF"}
|