@kairos-es/codec 1.0.0-alpha.1 → 1.0.0-alpha.2
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 +16 -0
- package/dist/cjs/decider.js +35 -67
- package/dist/cjs/decider.js.map +1 -1
- package/dist/cjs/decision.js +45 -0
- package/dist/cjs/decision.js.map +1 -0
- package/dist/cjs/index.js +25 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/precondition.js +20 -3
- package/dist/cjs/precondition.js.map +1 -1
- package/dist/dts/decider.d.ts +47 -6
- package/dist/dts/decider.d.ts.map +1 -1
- package/dist/dts/decision.d.ts +161 -0
- package/dist/dts/decision.d.ts.map +1 -0
- package/dist/dts/index.d.ts +18 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/precondition.d.ts +26 -3
- package/dist/dts/precondition.d.ts.map +1 -1
- package/dist/esm/decider.js +33 -65
- package/dist/esm/decider.js.map +1 -1
- package/dist/esm/decision.js +37 -0
- package/dist/esm/decision.js.map +1 -0
- package/dist/esm/index.js +18 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/precondition.js +20 -3
- package/dist/esm/precondition.js.map +1 -1
- package/package.json +2 -2
- package/src/decider.ts +87 -16
- package/src/decision.ts +174 -0
- package/src/index.ts +25 -0
- package/src/precondition.ts +46 -8
package/README.md
CHANGED
|
@@ -76,6 +76,22 @@ type as a typed `UnknownEventType` (ADR-0005).
|
|
|
76
76
|
`Decider` and `runDecider(decider, command)` close a SINGLE-HOP command cycle over
|
|
77
77
|
that fold: build the decision model, decide, append under the read's own condition.
|
|
78
78
|
|
|
79
|
+
A decision may also answer **`Unchanged`** — "already done, nothing to append" —
|
|
80
|
+
with `Decision.unchanged(reason?)`. It is a success, not an error and not an empty
|
|
81
|
+
batch: `runDecider` returns it in place of a `Position` and appends nothing, and
|
|
82
|
+
`transact` does the same for a body that returns it (without refusing an empty
|
|
83
|
+
bracket as `NoReadsRecorded`, since there is no write to guard). Test for it with
|
|
84
|
+
`isUnchanged`, never with `instanceof` or its `_tag`: the first breaks across the
|
|
85
|
+
dual ESM/CJS build, and the second would match your own union's member of the same
|
|
86
|
+
name. A decider that cannot answer `Unchanged` keeps a plain `Position` result —
|
|
87
|
+
an untyped (`any`) one, and one built in a wrapper generic in its success type,
|
|
88
|
+
included — so no call site changes for code that never uses it. The one thing that
|
|
89
|
+
does is a type query over a signature whose `O` is still open, such as
|
|
90
|
+
`ReturnType<typeof runDecider>`: it now reads `Position | Unchanged`, and
|
|
91
|
+
`typeof runDecider<P, Command, E, R>` gives `Position` back. `decider(...)` infers
|
|
92
|
+
the widening from a branch returning `Decision.unchanged(...)`, and a hand-written
|
|
93
|
+
`Decider` opts in with its fifth type argument, `DecisionOutput`.
|
|
94
|
+
|
|
79
95
|
⚠ **`runDecider` and `decideOnly` are two dispositions of one cycle**, and each
|
|
80
96
|
refuses exactly where the other is the answer:
|
|
81
97
|
|
package/dist/cjs/decider.js
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
6
|
+
exports.decider = decider;
|
|
7
|
+
exports.runDecider = void 0;
|
|
7
8
|
var _core = require("@kairos-es/core");
|
|
8
9
|
var _effect = require("effect");
|
|
10
|
+
var _decision = require("./decision.js");
|
|
9
11
|
var _decisionModel = require("./decisionModel.js");
|
|
10
12
|
/**
|
|
11
13
|
* A `Decider`: the write side's unit of decision. It pairs the projections a
|
|
@@ -61,74 +63,34 @@ var _decisionModel = require("./decisionModel.js");
|
|
|
61
63
|
* boundary, and decode may fail with `CodecError`. All surface on the error
|
|
62
64
|
* channel, so a caller handles all three the same way — and
|
|
63
65
|
* `@kairos-es/testing`'s `.thenError(tag)` can assert any of them.
|
|
64
|
-
*/
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Build a `Decider` with all four of its type arguments INFERRED: the slice
|
|
68
|
-
* record `P` from what `projections` returns, the error union `E` and the extra
|
|
69
|
-
* requirement `R` from what `decide` returns. The command type is the one thing
|
|
70
|
-
* a caller still writes, once, as the annotation on the `projections` parameter
|
|
71
|
-
* — `Command` occurs only in parameter positions, so nothing else could supply
|
|
72
|
-
* it, and it is domain information (the command this decision is parametrised
|
|
73
|
-
* by) rather than plumbing.
|
|
74
|
-
*
|
|
75
|
-
* It adds no capability: the interface stays directly inhabitable and this
|
|
76
|
-
* returns exactly the object literal a caller would have written. What it
|
|
77
|
-
* changes is what an author must SPELL. An object literal annotated
|
|
78
|
-
* `Decider<P, Command, E>` has to state the whole slice record — a
|
|
79
|
-
* `ReturnType<typeof someProjection>` per key, a slice's state type being
|
|
80
|
-
* inferred by `projection().on().build()` and so having no name to write — plus
|
|
81
|
-
* the union of every error its decide branches can fail with. Dropping the
|
|
82
|
-
* annotation instead is not open to the author: without it `decide`'s `state`
|
|
83
|
-
* and `command` parameters have no contextual type and are implicitly `any`. So
|
|
84
|
-
* the cost landed hardest on the worked example, where some thirty lines of type
|
|
85
|
-
* plumbing sat between the reader and the three decisions that file exists to
|
|
86
|
-
* teach.
|
|
87
|
-
*
|
|
88
|
-
* `Eff` captures decide's WHOLE return type as one type parameter instead of
|
|
89
|
-
* inferring `E` and `R` from an `Effect<…, E, Serializer | Uuid | R>` shaped
|
|
90
|
-
* target, and that is the part that had to be got right rather than merely
|
|
91
|
-
* written down. A real `decide` returns a UNION of effects — one `Effect.fail`
|
|
92
|
-
* per rule it enforces, plus the produce path — and TypeScript infers a type
|
|
93
|
-
* parameter from a union source by choosing one constituent's candidate, not by
|
|
94
|
-
* unioning them. Against the shaped target `E` therefore came out as whichever
|
|
95
|
-
* branch won, and every other branch was then rejected against it. Inference to
|
|
96
|
-
* a NAKED type variable takes the union whole, which is why `Eff` is the
|
|
97
|
-
* parameter and `Effect.Effect.Error` / `Effect.Effect.Context` recover the two
|
|
98
|
-
* channels afterwards: those do distribute across a union of effects.
|
|
99
66
|
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* Effect.Error<Eff>, …>`. The value is the two functions it was handed, so
|
|
111
|
-
* there is nothing for the cast to be wrong about.
|
|
112
|
-
*
|
|
113
|
-
* `R` excludes `Serializer | Uuid` because that is what `R` MEANS on `Decider`:
|
|
114
|
-
* the interface writes decide's requirement as `Serializer | Uuid | R`, so `R`
|
|
115
|
-
* is what the produce side needs BEYOND the codec's own two services. Leaving
|
|
116
|
-
* them in would state the same requirement twice and make an ordinary decider
|
|
117
|
-
* read as though it carried an extra one, though the set `runDecider` ends up
|
|
118
|
-
* asking for is identical either way. The exclusion cannot swallow a caller's
|
|
119
|
-
* own service by accident: a `Context.Tag`'s requirement type carries its key as
|
|
120
|
-
* a literal `Id` field, so only a tag declared under `@kairos-es/codec/Uuid` or
|
|
121
|
-
* `@kairos-es/codec/Serializer` matches — and a tag under that key IS that
|
|
122
|
-
* service, context being keyed by the same string at runtime.
|
|
67
|
+
* `decide` may also answer `Unchanged` — "already done, nothing to append" — but
|
|
68
|
+
* only where its type says so: `Decider`'s fifth type parameter `O` defaults to
|
|
69
|
+
* the non-empty batch, and `runDecider` returns `DecisionResult<O>`, which is
|
|
70
|
+
* plain `Position` unless `O` admits `Unchanged`. So a decider that cannot say
|
|
71
|
+
* "already done" keeps exactly the result type it always had — an untyped one
|
|
72
|
+
* and one built in a wrapper generic in its success type included — and one
|
|
73
|
+
* that can makes its caller handle `Position | Unchanged`. `runDecider` returns an
|
|
74
|
+
* `Unchanged` without calling `append`, so `append`'s refusal of an empty batch
|
|
75
|
+
* is untouched and never met. `decision.ts` owns why it is a success rather than
|
|
76
|
+
* an error or an empty batch, and why it is recognised by a type id.
|
|
123
77
|
*/
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
78
|
+
|
|
79
|
+
function decider(projections, decide) {
|
|
80
|
+
return {
|
|
81
|
+
projections,
|
|
82
|
+
decide
|
|
83
|
+
};
|
|
84
|
+
}
|
|
128
85
|
/**
|
|
129
86
|
* Run the full DCB command cycle for a decider against the ambient
|
|
130
87
|
* `DcbEventStore`: build the decision model (real read + decode + fold), decide,
|
|
131
|
-
* and append under the read's append condition. Returns the last new position
|
|
88
|
+
* and append under the read's append condition. Returns the last new position —
|
|
89
|
+
* or, when `decide` answered `Unchanged`, that `Unchanged`, having appended
|
|
90
|
+
* nothing. The result type is `DecisionResult<O>`, so it is plain `Position` for
|
|
91
|
+
* every decider that cannot answer `Unchanged`, including one whose `O` is still
|
|
92
|
+
* a type variable bounded by the batch. ONE signature, deliberately:
|
|
93
|
+
* `decision.ts` says why that alias, and not an overload, is what carries it.
|
|
132
94
|
*
|
|
133
95
|
* The requirement is `DcbEventStore | Serializer | Uuid | R |
|
|
134
96
|
* RequirementOf<P>`: `Serializer` enters through both the decode
|
|
@@ -145,15 +107,21 @@ const decider = (projections, decide) => ({
|
|
|
145
107
|
* answer, and why neither can be expressed over the other. Read it before
|
|
146
108
|
* changing either signature; this file does not restate the argument.
|
|
147
109
|
*/
|
|
148
|
-
exports.decider = decider;
|
|
149
110
|
const runDecider = (decider, command) => _effect.Effect.gen(function* () {
|
|
150
111
|
const store = yield* _core.DcbEventStore;
|
|
151
112
|
const {
|
|
152
113
|
state,
|
|
153
114
|
appendCondition
|
|
154
115
|
} = yield* (0, _decisionModel.buildDecisionModel)(decider.projections(command));
|
|
155
|
-
const
|
|
156
|
-
|
|
116
|
+
const output = yield* decider.decide(state, command);
|
|
117
|
+
// "Already done" never reaches `append`: an empty batch stays a defect there,
|
|
118
|
+
// and this is not one. The two casts restate `DecisionResult<O>`, which the
|
|
119
|
+
// checker cannot relate to either branch while `O` is still a type variable;
|
|
120
|
+
// the branch taken is exactly the one the conditional selects.
|
|
121
|
+
if ((0, _decision.isUnchanged)(output)) {
|
|
122
|
+
return output;
|
|
123
|
+
}
|
|
124
|
+
return yield* store.append(output, appendCondition);
|
|
157
125
|
});
|
|
158
126
|
exports.runDecider = runDecider;
|
|
159
127
|
//# sourceMappingURL=decider.js.map
|
package/dist/cjs/decider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decider.js","names":["_core","require","_effect","_decisionModel","decider","projections","decide","
|
|
1
|
+
{"version":3,"file":"decider.js","names":["_core","require","_effect","_decision","_decisionModel","decider","projections","decide","runDecider","command","Effect","gen","store","DcbEventStore","state","appendCondition","buildDecisionModel","output","isUnchanged","append","exports"],"sources":["../../src/decider.ts"],"sourcesContent":[null],"mappings":";;;;;;;AAwEA,IAAAA,KAAA,GAAAC,OAAA;AAEA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,SAAA,GAAAF,OAAA;AAKA,IAAAG,cAAA,GAAAH,OAAA;AAhFA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoOM,SAAUI,OAAOA,CACrBC,WAAoC,EACpCC,MAGoD;EAEpD,OAAO;IAAED,WAAW;IAAEC;EAAM,CAM3B;AACH;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBO,MAAMC,UAAU,GAAGA,CAOxBH,OAAqC,EACrCI,OAAgB,KAMhBC,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB,MAAMC,KAAK,GAAG,OAAOC,mBAAa;EAClC,MAAM;IAAEC,KAAK;IAAEC;EAAe,CAAE,GAAG,OAAO,IAAAC,iCAAkB,EAC1DX,OAAO,CAACC,WAAW,CAACG,OAAO,CAAC,CAC7B;EACD,MAAMQ,MAAM,GAAmB,OAAOZ,OAAO,CAACE,MAAM,CAACO,KAAK,EAAEL,OAAO,CAAC;EACpE;EACA;EACA;EACA;EACA,IAAI,IAAAS,qBAAW,EAACD,MAAM,CAAC,EAAE;IACvB,OAAOA,MAA2B;EACpC;EACA,OAAQ,OAAOL,KAAK,CAACO,MAAM,CAACF,MAAM,EAAEF,eAAe,CAAC;AACtD,CAAC,CAAC;AAAAK,OAAA,CAAAZ,UAAA,GAAAA,UAAA","ignoreList":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.isUnchanged = exports.UnchangedTypeId = exports.Unchanged = exports.Decision = void 0;
|
|
7
|
+
var _effect = require("effect");
|
|
8
|
+
/**
|
|
9
|
+
* The type id `isUnchanged` recognises. `Symbol.for`, so both halves of the dual
|
|
10
|
+
* build — and any two copies of this package in one graph — agree on it.
|
|
11
|
+
*/
|
|
12
|
+
const UnchangedTypeId = exports.UnchangedTypeId = /*#__PURE__*/Symbol.for('@kairos-es/codec/Unchanged');
|
|
13
|
+
/**
|
|
14
|
+
* "Nothing to do: this work is already done." A runner that receives it appends
|
|
15
|
+
* nothing and returns it in place of a `Position`.
|
|
16
|
+
*
|
|
17
|
+
* `reason` is for a human reading a trace or a test failure, and nothing in the
|
|
18
|
+
* library reads it. A `Data.TaggedClass`, so two values with the same reason are
|
|
19
|
+
* `Equal` and a test can compare them structurally.
|
|
20
|
+
*
|
|
21
|
+
* ⚠ Test for it with `isUnchanged`, never with `instanceof` or `_tag` — the
|
|
22
|
+
* module documentation says why each of those is wrong.
|
|
23
|
+
*/
|
|
24
|
+
class Unchanged extends /*#__PURE__*/_effect.Data.TaggedClass('Unchanged') {
|
|
25
|
+
[UnchangedTypeId] = UnchangedTypeId;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether `u` is an `Unchanged`, by its type id. Total over `unknown`, and
|
|
29
|
+
* realm-safe across the dual ESM/CJS build. An application value that merely
|
|
30
|
+
* carries `_tag: 'Unchanged'` answers `false`, deliberately.
|
|
31
|
+
*/
|
|
32
|
+
exports.Unchanged = Unchanged;
|
|
33
|
+
const isUnchanged = u => _effect.Predicate.hasProperty(u, UnchangedTypeId);
|
|
34
|
+
/**
|
|
35
|
+
* The constructors a `decide` step reaches for. One member today; it is a
|
|
36
|
+
* namespace object rather than a bare function so the call site reads as what it
|
|
37
|
+
* is, `Decision.unchanged('already dispatched')`.
|
|
38
|
+
*/
|
|
39
|
+
exports.isUnchanged = isUnchanged;
|
|
40
|
+
const Decision = exports.Decision = {
|
|
41
|
+
unchanged: reason => new Unchanged(reason === undefined ? {} : {
|
|
42
|
+
reason
|
|
43
|
+
})
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=decision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision.js","names":["_effect","require","UnchangedTypeId","exports","Symbol","for","Unchanged","Data","TaggedClass","isUnchanged","u","Predicate","hasProperty","Decision","unchanged","reason","undefined"],"sources":["../../src/decision.ts"],"sourcesContent":[null],"mappings":";;;;;;AA4FA,IAAAA,OAAA,GAAAC,OAAA;AAEA;;;;AAIO,MAAMC,eAAe,GAAAC,OAAA,CAAAD,eAAA,gBAAkBE,MAAM,CAACC,GAAG,CACtD,4BAA4B,CAC7B;AAKD;;;;;;;;;;;AAWM,MAAOC,SAAU,sBAAQC,YAAI,CAACC,WAAW,CAAC,WAAW,CAEzD;EACS,CAACN,eAAe,IAAqBA,eAAe;;AAG/D;;;;;AAAAC,OAAA,CAAAG,SAAA,GAAAA,SAAA;AAKO,MAAMG,WAAW,GAAIC,CAAU,IACpCC,iBAAS,CAACC,WAAW,CAACF,CAAC,EAAER,eAAe,CAAC;AAE3C;;;;;AAAAC,OAAA,CAAAM,WAAA,GAAAA,WAAA;AAKO,MAAMI,QAAQ,GAAAV,OAAA,CAAAU,QAAA,GAEjB;EACFC,SAAS,EAAGC,MAAM,IAAK,IAAIT,SAAS,CAACS,MAAM,KAAKC,SAAS,GAAG,EAAE,GAAG;IAAED;EAAM,CAAE;CAC5E","ignoreList":[]}
|
package/dist/cjs/index.js
CHANGED
|
@@ -9,6 +9,12 @@ Object.defineProperty(exports, "CodecError", {
|
|
|
9
9
|
return _errors.CodecError;
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
|
+
Object.defineProperty(exports, "Decision", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _decision.Decision;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
12
18
|
Object.defineProperty(exports, "NoReadsRecorded", {
|
|
13
19
|
enumerable: true,
|
|
14
20
|
get: function () {
|
|
@@ -39,6 +45,18 @@ Object.defineProperty(exports, "TaglessSliceInLedger", {
|
|
|
39
45
|
return _readLedger.TaglessSliceInLedger;
|
|
40
46
|
}
|
|
41
47
|
});
|
|
48
|
+
Object.defineProperty(exports, "Unchanged", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
get: function () {
|
|
51
|
+
return _decision.Unchanged;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
Object.defineProperty(exports, "UnchangedTypeId", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
get: function () {
|
|
57
|
+
return _decision.UnchangedTypeId;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
42
60
|
Object.defineProperty(exports, "UnknownEventType", {
|
|
43
61
|
enumerable: true,
|
|
44
62
|
get: function () {
|
|
@@ -93,6 +111,12 @@ Object.defineProperty(exports, "event", {
|
|
|
93
111
|
return _event.event;
|
|
94
112
|
}
|
|
95
113
|
});
|
|
114
|
+
Object.defineProperty(exports, "isUnchanged", {
|
|
115
|
+
enumerable: true,
|
|
116
|
+
get: function () {
|
|
117
|
+
return _decision.isUnchanged;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
96
120
|
Object.defineProperty(exports, "makeRegistry", {
|
|
97
121
|
enumerable: true,
|
|
98
122
|
get: function () {
|
|
@@ -136,6 +160,7 @@ Object.defineProperty(exports, "unsafeAppend", {
|
|
|
136
160
|
}
|
|
137
161
|
});
|
|
138
162
|
var _decider = require("./decider.js");
|
|
163
|
+
var _decision = require("./decision.js");
|
|
139
164
|
var _decisionModel = require("./decisionModel.js");
|
|
140
165
|
var _decode = require("./decode.js");
|
|
141
166
|
var _errors = require("./errors.js");
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["_decider","require","_decisionModel","_decode","_errors","_event","_precondition","_projection","_readLedger","_registry","_Serializer","_toEvent","_Uuid"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":["_decider","require","_decision","_decisionModel","_decode","_errors","_event","_precondition","_projection","_readLedger","_registry","_Serializer","_toEvent","_Uuid"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuHA,IAAAA,QAAA,GAAAC,OAAA;AACA,IAAAC,SAAA,GAAAD,OAAA;AAQA,IAAAE,cAAA,GAAAF,OAAA;AAKA,IAAAG,OAAA,GAAAH,OAAA;AACA,IAAAI,OAAA,GAAAJ,OAAA;AACA,IAAAK,MAAA,GAAAL,OAAA;AACA,IAAAM,aAAA,GAAAN,OAAA;AAMA,IAAAO,WAAA,GAAAP,OAAA;AAKA,IAAAQ,WAAA,GAAAR,OAAA;AAOA,IAAAS,SAAA,GAAAT,OAAA;AACA,IAAAU,WAAA,GAAAV,OAAA;AACA,IAAAW,QAAA,GAAAX,OAAA;AACA,IAAAY,KAAA,GAAAZ,OAAA","ignoreList":[]}
|
package/dist/cjs/precondition.js
CHANGED
|
@@ -6,11 +6,13 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.unsafeAppend = exports.transact = exports.decideOnly = void 0;
|
|
7
7
|
var _core = require("@kairos-es/core");
|
|
8
8
|
var _effect = require("effect");
|
|
9
|
+
var _decision = require("./decision.js");
|
|
9
10
|
var _decisionModel = require("./decisionModel.js");
|
|
10
11
|
var _readLedger = require("./readLedger.js");
|
|
11
12
|
/**
|
|
12
13
|
* The DCB command cycle MINUS the append: read the decision model, decode, fold,
|
|
13
|
-
* decide, and RETURN the events for the bracket to append
|
|
14
|
+
* decide, and RETURN the events for the bracket to append — or the decider's
|
|
15
|
+
* `Unchanged`, passed through as it came, for the bracket to append nothing.
|
|
14
16
|
*
|
|
15
17
|
* Bracket-only, and the refusal is STATIC — see the section below for the mechanism
|
|
16
18
|
* and, more importantly, for where the error actually lands, which is not where a
|
|
@@ -131,12 +133,18 @@ _effect.Effect.mapInputContext(store.append(events), context => _effect.Context.
|
|
|
131
133
|
* appends without opening one, and its events usually match the union the bracket
|
|
132
134
|
* is about to guard on, so the bracket's own close would be refused by a condition
|
|
133
135
|
* its own body invalidated.
|
|
136
|
+
*
|
|
137
|
+
* The body may succeed with `Unchanged` (see the module documentation), and the
|
|
138
|
+
* result is then `DecisionResult<O>`: `Position` for a body that cannot answer
|
|
139
|
+
* it, `Position | Unchanged` for one that can. `O` is last and defaulted, so an
|
|
140
|
+
* explicit `<E, R>` written against the narrower signature still means what it
|
|
141
|
+
* did.
|
|
134
142
|
*/
|
|
135
143
|
exports.unsafeAppend = unsafeAppend;
|
|
136
144
|
const transact = precondition => body => _effect.Effect.gen(function* () {
|
|
137
145
|
const store = yield* _core.DcbEventStore;
|
|
138
146
|
const ledger = yield* _readLedger.makeReadLedger;
|
|
139
|
-
const
|
|
147
|
+
const output = yield* _effect.Effect.gen(function* () {
|
|
140
148
|
// ORDER IS THE DESIGN. The precondition runs first and runs HERE, with the
|
|
141
149
|
// ledger already in context, so its reads are hops in the very condition
|
|
142
150
|
// that will guard the body's write.
|
|
@@ -164,11 +172,20 @@ const transact = precondition => body => _effect.Effect.gen(function* () {
|
|
|
164
172
|
_effect.Effect.provide(_effect.Layer.merge(ledger.layer, _effect.Layer.succeed(_core.AppendIsOwned, {
|
|
165
173
|
by: 'transact'
|
|
166
174
|
}))));
|
|
175
|
+
// "Already done": nothing to append, so no condition to derive and nothing
|
|
176
|
+
// for `NoReadsRecorded` to protect — that refusal guards a WRITE that would
|
|
177
|
+
// go out unguarded, and there is none. It must stay BEFORE the derivation,
|
|
178
|
+
// or an empty bracket answering `Unchanged` would be refused for a write it
|
|
179
|
+
// never asked for. The casts restate `DecisionResult<O>`, which the checker
|
|
180
|
+
// cannot relate to either branch while `O` is still a type variable.
|
|
181
|
+
if ((0, _decision.isUnchanged)(output)) {
|
|
182
|
+
return output;
|
|
183
|
+
}
|
|
167
184
|
const condition = (0, _readLedger.deriveLayeredCondition)(yield* ledger.hops);
|
|
168
185
|
if (condition === undefined) {
|
|
169
186
|
return yield* new _readLedger.NoReadsRecorded();
|
|
170
187
|
}
|
|
171
|
-
return yield* store.append(
|
|
188
|
+
return yield* store.append(output, condition);
|
|
172
189
|
});
|
|
173
190
|
exports.transact = transact;
|
|
174
191
|
//# sourceMappingURL=precondition.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"precondition.js","names":["_core","require","_effect","_decisionModel","_readLedger","decideOnly","decider","command","Effect","gen","ReadLedger","state","modelAtHead","projections","decide","exports","unsafeAppend","events","reason","flatMap","DcbEventStore","store","mapInputContext","append","context","Context","omit","AppendIsOwned","transact","precondition","body","ledger","makeReadLedger","proof","evaluate","pipe","provideService","tag","provide","Layer","merge","layer","succeed","by","condition","deriveLayeredCondition","hops","undefined","NoReadsRecorded"],"sources":["../../src/precondition.ts"],"sourcesContent":[null],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"precondition.js","names":["_core","require","_effect","_decision","_decisionModel","_readLedger","decideOnly","decider","command","Effect","gen","ReadLedger","state","modelAtHead","projections","decide","exports","unsafeAppend","events","reason","flatMap","DcbEventStore","store","mapInputContext","append","context","Context","omit","AppendIsOwned","transact","precondition","body","ledger","makeReadLedger","output","proof","evaluate","pipe","provideService","tag","provide","Layer","merge","layer","succeed","by","isUnchanged","condition","deriveLayeredCondition","hops","undefined","NoReadsRecorded"],"sources":["../../src/precondition.ts"],"sourcesContent":[null],"mappings":";;;;;;AAwGA,IAAAA,KAAA,GAAAC,OAAA;AAEA,IAAAC,OAAA,GAAAD,OAAA;AAEA,IAAAE,SAAA,GAAAF,OAAA;AAKA,IAAAG,cAAA,GAAAH,OAAA;AAGA,IAAAI,WAAA,GAAAJ,OAAA;AAsCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDO,MAAMK,UAAU,GAAGA,CAOxBC,OAAqC,EACrCC,OAAgB,KAMhBC,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB,OAAOC,sBAAU;EAEjB,MAAM;IAAEC;EAAK,CAAE,GAAG,OAAO,IAAAC,0BAAW,EAACN,OAAO,CAACO,WAAW,CAACN,OAAO,CAAC,CAAC;EAClE,OAAO,OAAOD,OAAO,CAACQ,MAAM,CAACH,KAAK,EAAEJ,OAAO,CAAC;AAC9C,CAAC,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;AAAAQ,OAAA,CAAAV,UAAA,GAAAA,UAAA;AAsBO,MAAMW,YAAY,GAAGA,CAC1BC,MAA2C;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACAC,MAAc,KAEdV,cAAM,CAACW,OAAO,CAACC,mBAAa,EAAGC,KAAK;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACAb,cAAM,CAACc,eAAe,CACpBD,KAAK,CAACE,MAAM,CAACN,MAAM,CAAC,EACnBO,OAA+B,IAAKC,eAAO,CAACC,IAAI,CAACC,mBAAa,CAAC,CAACH,OAAO,CAAC,CAC1E,CACF;AAEH;;;;;;;;;;;;;;;;;;;;;;;AAAAT,OAAA,CAAAC,YAAA,GAAAA,YAAA;AAuBO,MAAMY,QAAQ,GACGC,YAA+C,IAEnEC,IAA4B,IAQ5BtB,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB,MAAMY,KAAK,GAAG,OAAOD,mBAAa;EAClC,MAAMW,MAAM,GAAG,OAAOC,0BAAc;EAEpC,MAAMC,MAAM,GAAmB,OAAOzB,cAAM,CAACC,GAAG,CAAC,aAAS;IACxD;IACA;IACA;IACA,MAAMyB,KAAK,GAAG,OAAOL,YAAY,CAACM,QAAQ;IAC1C,OAAO,OAAOL,IAAI,CAACM,IAAI,CAAC5B,cAAM,CAAC6B,cAAc,CAACR,YAAY,CAACS,GAAG,EAAEJ,KAAK,CAAC,CAAC;EACzE,CAAC,CAAC,CAACE,IAAI;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA5B,cAAM,CAAC+B,OAAO,CACZC,aAAK,CAACC,KAAK,CACTV,MAAM,CAACW,KAAK,EACZF,aAAK,CAACG,OAAO,CAAChB,mBAAa,EAAE;IAAEiB,EAAE,EAAE;EAAU,CAAE,CAAC,CACjD,CACF,CACF;EAED;EACA;EACA;EACA;EACA;EACA;EACA,IAAI,IAAAC,qBAAW,EAACZ,MAAM,CAAC,EAAE;IACvB,OAAOA,MAA2B;EACpC;EAEA,MAAMa,SAAS,GAAG,IAAAC,kCAAsB,EAAC,OAAOhB,MAAM,CAACiB,IAAI,CAAC;EAC5D,IAAIF,SAAS,KAAKG,SAAS,EAAE;IAC3B,OAAO,OAAO,IAAIC,2BAAe,EAAE;EACrC;EAEA,OAAQ,OAAO7B,KAAK,CAACE,MAAM,CAACU,MAAM,EAAEa,SAAS,CAAC;AAChD,CAAC,CAAC;AAAA/B,OAAA,CAAAa,QAAA,GAAAA,QAAA","ignoreList":[]}
|
package/dist/dts/decider.d.ts
CHANGED
|
@@ -52,11 +52,23 @@
|
|
|
52
52
|
* boundary, and decode may fail with `CodecError`. All surface on the error
|
|
53
53
|
* channel, so a caller handles all three the same way — and
|
|
54
54
|
* `@kairos-es/testing`'s `.thenError(tag)` can assert any of them.
|
|
55
|
+
*
|
|
56
|
+
* `decide` may also answer `Unchanged` — "already done, nothing to append" — but
|
|
57
|
+
* only where its type says so: `Decider`'s fifth type parameter `O` defaults to
|
|
58
|
+
* the non-empty batch, and `runDecider` returns `DecisionResult<O>`, which is
|
|
59
|
+
* plain `Position` unless `O` admits `Unchanged`. So a decider that cannot say
|
|
60
|
+
* "already done" keeps exactly the result type it always had — an untyped one
|
|
61
|
+
* and one built in a wrapper generic in its success type included — and one
|
|
62
|
+
* that can makes its caller handle `Position | Unchanged`. `runDecider` returns an
|
|
63
|
+
* `Unchanged` without calling `append`, so `append`'s refusal of an empty batch
|
|
64
|
+
* is untouched and never met. `decision.ts` owns why it is a success rather than
|
|
65
|
+
* an error or an empty batch, and why it is recognised by a type id.
|
|
55
66
|
*/
|
|
56
|
-
import type { AppendConditionFailed, CompositeState, DcbEvent
|
|
67
|
+
import type { AppendConditionFailed, CompositeState, DcbEvent } from '@kairos-es/core';
|
|
57
68
|
import { DcbEventStore } from '@kairos-es/core';
|
|
58
69
|
import type { Array as Arr } from 'effect';
|
|
59
70
|
import { Effect } from 'effect';
|
|
71
|
+
import { type DecisionOutput, type DecisionResult } from './decision.js';
|
|
60
72
|
import { type RequirementOf } from './decisionModel.js';
|
|
61
73
|
import type { CodecError } from './errors.js';
|
|
62
74
|
import type { SliceProjection } from './projection.js';
|
|
@@ -77,10 +89,17 @@ import type { Uuid } from './Uuid.js';
|
|
|
77
89
|
* schema-backed, state-erased bound for a heterogeneous record); the
|
|
78
90
|
* precise per-key state is recovered by core's shared `CompositeState`. The
|
|
79
91
|
* requirement `R` is the union carried by the record's slices' schemas.
|
|
92
|
+
*
|
|
93
|
+
* `O` is what `decide` may succeed with. It defaults to the non-empty batch, and
|
|
94
|
+
* a decider that can answer "already done" widens it to `DecisionOutput`
|
|
95
|
+
* (`NonEmptyReadonlyArray<DcbEvent> | Unchanged`). It is LAST and DEFAULTED so
|
|
96
|
+
* that every `Decider<P, Command, E, R>` already written means exactly what it
|
|
97
|
+
* meant. Its bound is `DecisionOutput`, which still excludes a possibly-empty
|
|
98
|
+
* array — the widening adds an answer and removes no guarantee.
|
|
80
99
|
*/
|
|
81
|
-
export interface Decider<P extends Record<string, SliceProjection>, Command, E = never, R = never
|
|
100
|
+
export interface Decider<P extends Record<string, SliceProjection>, Command, E = never, R = never, O extends DecisionOutput = Arr.NonEmptyReadonlyArray<DcbEvent>> {
|
|
82
101
|
readonly projections: (command: Command) => P;
|
|
83
|
-
readonly decide: (state: CompositeState<P>, command: Command) => Effect.Effect<
|
|
102
|
+
readonly decide: (state: CompositeState<P>, command: Command) => Effect.Effect<O, E, Serializer | Uuid | R>;
|
|
84
103
|
}
|
|
85
104
|
/**
|
|
86
105
|
* Build a `Decider` with all four of its type arguments INFERRED: the slice
|
|
@@ -129,6 +148,22 @@ export interface Decider<P extends Record<string, SliceProjection>, Command, E =
|
|
|
129
148
|
* Effect.Error<Eff>, …>`. The value is the two functions it was handed, so
|
|
130
149
|
* there is nothing for the cast to be wrong about.
|
|
131
150
|
*
|
|
151
|
+
* The success channel is how a decider opts into `Unchanged` without writing a
|
|
152
|
+
* fifth type argument, and it is why this function is declared TWICE. The first
|
|
153
|
+
* declaration bounds `Eff` by the non-empty batch and returns a `Decider` with
|
|
154
|
+
* the DEFAULT `O`; the second bounds it by `DecisionOutput` and returns one whose
|
|
155
|
+
* `O` is `DecisionOutput`. A `decide` with a branch returning
|
|
156
|
+
* `Decision.unchanged(…)` fails the first bound and lands on the second, and one
|
|
157
|
+
* with no such branch keeps the default. Either way `O` is one of the two a
|
|
158
|
+
* caller ever writes, never the inferred success type passed through: inference
|
|
159
|
+
* is more precise than anyone writes — the produce path's one-element tuple, say
|
|
160
|
+
* — and a `Decider<…, readonly [DcbEvent]>` would not be the
|
|
161
|
+
* `Decider<P, Command, E, R>` its caller spells, though it behaves identically.
|
|
162
|
+
* Asking by ASSIGNABILITY rather than by a conditional over `Eff`'s success is
|
|
163
|
+
* what keeps an untyped (`any`) success, and a success that is a type variable
|
|
164
|
+
* bounded by the batch, on the default; `decision.ts` owns that argument, and
|
|
165
|
+
* the one type query the pair cannot answer as before.
|
|
166
|
+
*
|
|
132
167
|
* `R` excludes `Serializer | Uuid` because that is what `R` MEANS on `Decider`:
|
|
133
168
|
* the interface writes decide's requirement as `Serializer | Uuid | R`, so `R`
|
|
134
169
|
* is what the produce side needs BEYOND the codec's own two services. Leaving
|
|
@@ -140,11 +175,17 @@ export interface Decider<P extends Record<string, SliceProjection>, Command, E =
|
|
|
140
175
|
* `@kairos-es/codec/Serializer` matches — and a tag under that key IS that
|
|
141
176
|
* service, context being keyed by the same string at runtime.
|
|
142
177
|
*/
|
|
143
|
-
export declare
|
|
178
|
+
export declare function decider<P extends Record<string, SliceProjection>, Command, Eff extends Effect.Effect<Arr.NonEmptyReadonlyArray<DcbEvent>, unknown, unknown>>(projections: (command: Command) => P, decide: (state: CompositeState<P>, command: Command) => Eff): Decider<P, Command, Effect.Effect.Error<Eff>, Exclude<Effect.Effect.Context<Eff>, Serializer | Uuid>>;
|
|
179
|
+
export declare function decider<P extends Record<string, SliceProjection>, Command, Eff extends Effect.Effect<DecisionOutput, unknown, unknown>>(projections: (command: Command) => P, decide: (state: CompositeState<P>, command: Command) => Eff): Decider<P, Command, Effect.Effect.Error<Eff>, Exclude<Effect.Effect.Context<Eff>, Serializer | Uuid>, DecisionOutput>;
|
|
144
180
|
/**
|
|
145
181
|
* Run the full DCB command cycle for a decider against the ambient
|
|
146
182
|
* `DcbEventStore`: build the decision model (real read + decode + fold), decide,
|
|
147
|
-
* and append under the read's append condition. Returns the last new position
|
|
183
|
+
* and append under the read's append condition. Returns the last new position —
|
|
184
|
+
* or, when `decide` answered `Unchanged`, that `Unchanged`, having appended
|
|
185
|
+
* nothing. The result type is `DecisionResult<O>`, so it is plain `Position` for
|
|
186
|
+
* every decider that cannot answer `Unchanged`, including one whose `O` is still
|
|
187
|
+
* a type variable bounded by the batch. ONE signature, deliberately:
|
|
188
|
+
* `decision.ts` says why that alias, and not an overload, is what carries it.
|
|
148
189
|
*
|
|
149
190
|
* The requirement is `DcbEventStore | Serializer | Uuid | R |
|
|
150
191
|
* RequirementOf<P>`: `Serializer` enters through both the decode
|
|
@@ -161,5 +202,5 @@ export declare const decider: <P extends Record<string, SliceProjection>, Comman
|
|
|
161
202
|
* answer, and why neither can be expressed over the other. Read it before
|
|
162
203
|
* changing either signature; this file does not restate the argument.
|
|
163
204
|
*/
|
|
164
|
-
export declare const runDecider: <P extends Record<string, SliceProjection>, Command, E, R
|
|
205
|
+
export declare const runDecider: <P extends Record<string, SliceProjection>, Command, E, R, O extends DecisionOutput = Arr.NonEmptyReadonlyArray<DcbEvent>>(decider: Decider<P, Command, E, R, O>, command: Command) => Effect.Effect<DecisionResult<O>, E | CodecError | AppendConditionFailed | TaglessSliceInLedger, DcbEventStore | Serializer | Uuid | R | RequirementOf<P>>;
|
|
165
206
|
//# sourceMappingURL=decider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decider.d.ts","sourceRoot":"","sources":["../../src/decider.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"decider.d.ts","sourceRoot":"","sources":["../../src/decider.ts"],"names":[],"mappings":"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"}
|
|
@@ -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.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
|
+
};
|
|
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"}
|