@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 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
 
@@ -3,9 +3,11 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.runDecider = exports.decider = void 0;
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
- * The constraint on `Eff` is load-bearing twice over. It is what still refuses a
101
- * `decide` that could produce an EMPTY batch, the one guarantee the shaped
102
- * target used to give and the one `append` relies on. It is also what
103
- * contextually types the produce path's `(event) => [event]` as a one-element
104
- * TUPLE; unconstrained, that widens to `DcbEvent[]`, which is not a
105
- * `NonEmptyReadonlyArray` and would fail at the call site.
106
- *
107
- * The single `as` is narrow and unavoidable: the declared return is a
108
- * conditional over an unresolved `Eff`, so while `Eff` is still a type variable
109
- * the checker cannot relate `(…) => Eff` to `(…) => Effect<…,
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
- const decider = (projections, decide) => ({
125
- projections,
126
- decide
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 newEvents = yield* decider.decide(state, command);
156
- return yield* store.append(newEvents, appendCondition);
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
@@ -1 +1 @@
1
- {"version":3,"file":"decider.js","names":["_core","require","_effect","_decisionModel","decider","projections","decide","exports","runDecider","command","Effect","gen","store","DcbEventStore","state","appendCondition","buildDecisionModel","newEvents","append"],"sources":["../../src/decider.ts"],"sourcesContent":[null],"mappings":";;;;;;AA8DA,IAAAA,KAAA,GAAAC,OAAA;AAEA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,cAAA,GAAAF,OAAA;AAjEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DO,MAAMG,OAAO,GAAGA,CASrBC,WAAoC,EACpCC,MAA2D,MAO1D;EAAED,WAAW;EAAEC;AAAM,CAAE,CAKvB;AAEH;;;;;;;;;;;;;;;;;;;;AAAAC,OAAA,CAAAH,OAAA,GAAAA,OAAA;AAoBO,MAAMI,UAAU,GAAGA,CAMxBJ,OAAkC,EAClCK,OAAgB,KAMhBC,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB,MAAMC,KAAK,GAAG,OAAOC,mBAAa;EAClC,MAAM;IAAEC,KAAK;IAAEC;EAAe,CAAE,GAAG,OAAO,IAAAC,iCAAkB,EAC1DZ,OAAO,CAACC,WAAW,CAACI,OAAO,CAAC,CAC7B;EACD,MAAMQ,SAAS,GAAG,OAAOb,OAAO,CAACE,MAAM,CAACQ,KAAK,EAAEL,OAAO,CAAC;EACvD,OAAO,OAAOG,KAAK,CAACM,MAAM,CAACD,SAAS,EAAEF,eAAe,CAAC;AACxD,CAAC,CAAC;AAAAR,OAAA,CAAAC,UAAA,GAAAA,UAAA","ignoreList":[]}
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");
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsGA,IAAAA,QAAA,GAAAC,OAAA;AACA,IAAAC,cAAA,GAAAD,OAAA;AAKA,IAAAE,OAAA,GAAAF,OAAA;AACA,IAAAG,OAAA,GAAAH,OAAA;AACA,IAAAI,MAAA,GAAAJ,OAAA;AACA,IAAAK,aAAA,GAAAL,OAAA;AAMA,IAAAM,WAAA,GAAAN,OAAA;AAKA,IAAAO,WAAA,GAAAP,OAAA;AAOA,IAAAQ,SAAA,GAAAR,OAAA;AACA,IAAAS,WAAA,GAAAT,OAAA;AACA,IAAAU,QAAA,GAAAV,OAAA;AACA,IAAAW,KAAA,GAAAX,OAAA","ignoreList":[]}
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":[]}
@@ -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 events = yield* _effect.Effect.gen(function* () {
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(events, condition);
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":";;;;;;AAyFA,IAAAA,KAAA,GAAAC,OAAA;AAEA,IAAAC,OAAA,GAAAD,OAAA;AAEA,IAAAE,cAAA,GAAAF,OAAA;AAGA,IAAAG,WAAA,GAAAH,OAAA;AAsCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDO,MAAMI,UAAU,GAAGA,CAMxBC,OAAkC,EAClCC,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;AAiBO,MAAMY,QAAQ,GACGC,YAA+C,IAEnEC,IAA8D,IAQ9DtB,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB,MAAMY,KAAK,GAAG,OAAOD,mBAAa;EAClC,MAAMW,MAAM,GAAG,OAAOC,0BAAc;EAEpC,MAAMf,MAAM,GAAG,OAAOT,cAAM,CAACC,GAAG,CAAC,aAAS;IACxC;IACA;IACA;IACA,MAAMwB,KAAK,GAAG,OAAOJ,YAAY,CAACK,QAAQ;IAC1C,OAAO,OAAOJ,IAAI,CAACK,IAAI,CAAC3B,cAAM,CAAC4B,cAAc,CAACP,YAAY,CAACQ,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;EACA3B,cAAM,CAAC8B,OAAO,CACZC,aAAK,CAACC,KAAK,CACTT,MAAM,CAACU,KAAK,EACZF,aAAK,CAACG,OAAO,CAACf,mBAAa,EAAE;IAAEgB,EAAE,EAAE;EAAU,CAAE,CAAC,CACjD,CACF,CACF;EAED,MAAMC,SAAS,GAAG,IAAAC,kCAAsB,EAAC,OAAOd,MAAM,CAACe,IAAI,CAAC;EAC5D,IAAIF,SAAS,KAAKG,SAAS,EAAE;IAC3B,OAAO,OAAO,IAAIC,2BAAe,EAAE;EACrC;EAEA,OAAO,OAAO3B,KAAK,CAACE,MAAM,CAACN,MAAM,EAAE2B,SAAS,CAAC;AAC/C,CAAC,CAAC;AAAA7B,OAAA,CAAAa,QAAA,GAAAA,QAAA","ignoreList":[]}
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":[]}
@@ -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, Position } from '@kairos-es/core';
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<Arr.NonEmptyReadonlyArray<DcbEvent>, E, Serializer | Uuid | R>;
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 const 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>>;
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>(decider: Decider<P, Command, E, R>, command: Command) => Effect.Effect<Position, E | CodecError | AppendConditionFailed | TaglessSliceInLedger, DcbEventStore | Serializer | Uuid | R | RequirementOf<P>>;
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,cAAc,EACd,QAAQ,EACR,QAAQ,EACT,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,KAAK,EAAE,KAAK,IAAI,GAAG,EAAE,MAAM,QAAQ,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAElC;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,OAAO,CACtB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACzC,OAAO,EACP,CAAC,GAAG,KAAK,EACT,CAAC,GAAG,KAAK;IAET,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,CAAC,CAAA;IAC7C,QAAQ,CAAC,MAAM,EAAE,CACf,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,EACxB,OAAO,EAAE,OAAO,KACb,MAAM,CAAC,MAAM,CAChB,GAAG,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EACnC,CAAC,EACD,UAAU,GAAG,IAAI,GAAG,CAAC,CACtB,CAAA;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,eAAO,MAAM,OAAO,GAClB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACzC,OAAO,EACP,GAAG,SAAS,MAAM,CAAC,MAAM,CACvB,GAAG,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EACnC,OAAO,EACP,OAAO,CACR,EAED,aAAa,CAAC,OAAO,EAAE,OAAO,KAAK,CAAC,EACpC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,KAAK,GAAG,KAC1D,OAAO,CACR,CAAC,EACD,OAAO,EACP,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EACxB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC,CAOrD,CAAA;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,UAAU,GACrB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACzC,OAAO,EACP,CAAC,EACD,CAAC,EAED,SAAS,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,EAClC,SAAS,OAAO,KACf,MAAM,CAAC,MAAM,CACd,QAAQ,EACR,CAAC,GAAG,UAAU,GAAG,qBAAqB,GAAG,oBAAoB,EAC7D,aAAa,GAAG,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAStD,CAAA"}
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"}