@kairos-es/codec 1.0.0-alpha.0 → 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.
Files changed (44) hide show
  1. package/README.md +75 -3
  2. package/dist/cjs/decider.js +44 -69
  3. package/dist/cjs/decider.js.map +1 -1
  4. package/dist/cjs/decision.js +45 -0
  5. package/dist/cjs/decision.js.map +1 -0
  6. package/dist/cjs/decisionModel.js +76 -20
  7. package/dist/cjs/decisionModel.js.map +1 -1
  8. package/dist/cjs/index.js +63 -0
  9. package/dist/cjs/index.js.map +1 -1
  10. package/dist/cjs/precondition.js +191 -0
  11. package/dist/cjs/precondition.js.map +1 -0
  12. package/dist/cjs/readLedger.js +224 -0
  13. package/dist/cjs/readLedger.js.map +1 -0
  14. package/dist/dts/decider.d.ts +57 -8
  15. package/dist/dts/decider.d.ts.map +1 -1
  16. package/dist/dts/decision.d.ts +161 -0
  17. package/dist/dts/decision.d.ts.map +1 -0
  18. package/dist/dts/decisionModel.d.ts +33 -13
  19. package/dist/dts/decisionModel.d.ts.map +1 -1
  20. package/dist/dts/index.d.ts +69 -0
  21. package/dist/dts/index.d.ts.map +1 -1
  22. package/dist/dts/precondition.d.ts +244 -0
  23. package/dist/dts/precondition.d.ts.map +1 -0
  24. package/dist/dts/readLedger.d.ts +263 -0
  25. package/dist/dts/readLedger.d.ts.map +1 -0
  26. package/dist/esm/decider.js +42 -67
  27. package/dist/esm/decider.js.map +1 -1
  28. package/dist/esm/decision.js +37 -0
  29. package/dist/esm/decision.js.map +1 -0
  30. package/dist/esm/decisionModel.js +77 -21
  31. package/dist/esm/decisionModel.js.map +1 -1
  32. package/dist/esm/index.js +69 -0
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/precondition.js +182 -0
  35. package/dist/esm/precondition.js.map +1 -0
  36. package/dist/esm/readLedger.js +212 -0
  37. package/dist/esm/readLedger.js.map +1 -0
  38. package/package.json +2 -2
  39. package/src/decider.ts +98 -19
  40. package/src/decision.ts +174 -0
  41. package/src/decisionModel.ts +87 -23
  42. package/src/index.ts +87 -0
  43. package/src/precondition.ts +371 -0
  44. package/src/readLedger.ts +394 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * `Unchanged`: a decision's answer when there is nothing to do because the work
3
+ * is already done, and the two types that let a decision say so without costing
4
+ * any existing caller its `Position`.
5
+ *
6
+ * ## Why a decision needs a third answer
7
+ *
8
+ * A decision had two answers: a non-empty batch of events to append, or a
9
+ * failure. The idempotent case fitted neither. A webhook redelivered after its
10
+ * first delivery was recorded, or a recorder re-running after a conflict whose
11
+ * winning write already closed the item, has nothing to append — and nothing
12
+ * WRONG has happened, so a failure is the wrong channel: a caller would have to
13
+ * tell "already done" from a genuine refusal by matching a tag, and a retry
14
+ * policy keyed on the error channel would retry a success. An empty batch is not
15
+ * the answer either. `append` refuses one as a defect, and the `Decider`'s
16
+ * `NonEmptyReadonlyArray` bound is what makes that refusal unreachable from a
17
+ * decision, a guarantee `decider.ts` calls load-bearing. So "already done" gets a
18
+ * value of its own, and it is a SUCCESS: an idempotent skip is never retried.
19
+ *
20
+ * ## Why it is recognised by a type id, and never by `instanceof` or a `_tag`
21
+ *
22
+ * This package ships dual ESM/CJS, so one consumer's graph can hold two copies of
23
+ * this module and therefore two distinct `Unchanged` classes. Under `instanceof`,
24
+ * a value built by one copy fails the other's check, and the runner would treat
25
+ * "already done" as a batch and hand it to `append` — the same single point of
26
+ * silent failure `@kairos-es/core` argues against, in its contract store, for
27
+ * `AppendConditionFailed`. That error falls back on its `_tag`, and a tag is
28
+ * enough there because the error channel is the library's own. A DECISION's
29
+ * success channel is not: an application's own union may well carry a member
30
+ * tagged `'Unchanged'`, and a runner that skipped on the bare tag would swallow
31
+ * that value and append nothing, silently. `Symbol.for` gives an identity that is
32
+ * shared across both builds (the global symbol registry is per runtime, not per
33
+ * module copy) and that no application value carries by accident. `isUnchanged`
34
+ * is the one test, and every runner in this package uses it.
35
+ *
36
+ * ## Why the widening is a defaulted type parameter, not a second entry point
37
+ *
38
+ * `Decider` gains a fifth type parameter `O`, defaulting to the non-empty batch.
39
+ * A second entry point would have meant a second `Decider`, a second runner, a
40
+ * second `transact` and a second `decideOnly` — against a surface whose whole
41
+ * argument is that `runDecider` and `decideOnly` are ONE pair.
42
+ *
43
+ * ## Why `DecisionResult` is shaped as it is, and why `decider` is overloaded
44
+ *
45
+ * The promise is that a decider or body that cannot answer `Unchanged` keeps
46
+ * exactly the `Position` result it had. Two kinds of success make that harder
47
+ * than it looks, and both were measured against call shapes that compiled
48
+ * before the widening.
49
+ *
50
+ * - An UNTYPED success. `any` satisfies every question a conditional can ask of
51
+ * it: `Unchanged extends any` holds, and `any extends Unchanged` takes BOTH
52
+ * branches. So a decision nobody typed would read as one that can answer
53
+ * `Unchanged`, and its caller's `(position: Position) => …` would stop
54
+ * compiling. `DecisionResult` therefore checks for `any` FIRST, and answers
55
+ * `Position`.
56
+ * - A GENERIC success. In a wrapper generic in the success type
57
+ * (`<A extends NonEmptyReadonlyArray<DcbEvent>, …>`), a conditional over `A`
58
+ * stays DEFERRED, and a deferred conditional is `Position` only if the checker
59
+ * can see that it must be. It can for one shape and not for the other:
60
+ * `Unchanged extends O ? …` is related through the union of its two branches,
61
+ * which admits `Unchanged`; `O extends Unchanged ? …`, DISTRIBUTIVE over `O`,
62
+ * is related through itself instantiated at `O`'s bound — and at a bound of
63
+ * the non-empty batch that is `Position`. So `DecisionResult` is written the
64
+ * second way, and `runDecider` and `transact` keep ONE signature each, which
65
+ * is what lets `body.pipe(transact(p))` still infer its error and requirement
66
+ * channels; an overloaded step would lose both to `unknown`.
67
+ *
68
+ * `decider(…)` cannot be served by a conditional at all. Its `O` is derived from
69
+ * the SUCCESS of an effect type variable, and `Effect.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"}
@@ -7,11 +7,18 @@
7
7
  * `modelAtHead` stops where the fold does, at `{ state, head }` — a
8
8
  * materialisation of the record with no table, no checkpoint and no runner.
9
9
  * `buildDecisionModel` adds the only part that is genuinely decision-specific:
10
- * the `AppendCondition` derived from the same query and that same head (the DCB
11
- * optimistic-concurrency contract). It is core's old
12
- * `buildDecisionModel(Record<string, AnyProjection>)` surface, moved here
10
+ * the `AppendCondition` (the DCB optimistic-concurrency contract). It is core's
11
+ * old `buildDecisionModel(Record<string, AnyProjection>)` surface, moved here
13
12
  * because decode needs `Schema` + the `Serializer`.
14
13
  *
14
+ * BOTH exports also record into the ambient read ledger when one is in context,
15
+ * which is what makes a decision that walks a chain of identifiers guard on every
16
+ * hop rather than on its last. Recording is `modelAtHead`'s, so it happens
17
+ * whatever symbol performed the read; outside a bracket there is no ledger,
18
+ * nothing is recorded and no behaviour changes. `readLedger.ts` owns the whole
19
+ * argument, including why the recorder is this module rather than a
20
+ * layered-only entry point of its own.
21
+ *
15
22
  * The fold stays pure and in core; only the decode stage is effectful, and that
16
23
  * stage lives in `decode.ts` — `decodeSlices` — so the read side folds a live
17
24
  * `subscribe` stream through the *same* decode this module folds a bounded
@@ -28,6 +35,7 @@ import { type AppendCondition, type CompositeState, DcbEventStore, type Position
28
35
  import { Effect } from 'effect';
29
36
  import type { CodecError } from './errors.js';
30
37
  import type { SliceProjection } from './projection.js';
38
+ import { type TaglessSliceInLedger } from './readLedger.js';
31
39
  import type { Serializer } from './Serializer.js';
32
40
  /** The union of requirements `R` carried by a record of `SliceProjection`s. */
33
41
  export type RequirementOf<T extends Record<string, SliceProjection>> = T[keyof T] extends SliceProjection<infer _S, infer R> ? R : never;
@@ -55,6 +63,16 @@ export type RequirementOf<T extends Record<string, SliceProjection>> = T[keyof T
55
63
  * single fold cannot serve — across many identities, ordered, joined, paginated
56
64
  * — and never because it is fresher, because it is not.
57
65
  *
66
+ * ⚠ AND INSIDE A BRACKET IT IS NOT THE FLOOR THE APPEND WILL USE. Everything
67
+ * above is a statement about THIS read, and it stays true; what changes is that a
68
+ * bracket guards on the EARLIEST head across every hop it walked, which on any hop
69
+ * after the first is a strictly lower number than the one returned here. So a
70
+ * caller who reads `head` inside a bracket and reasons about what the append will
71
+ * refuse is reasoning from the wrong figure — the guard is wider than this head
72
+ * says. Nothing here is wrong; the trap is assuming the two are the same number,
73
+ * and it is named because it is exactly the assumption that holds outside a
74
+ * bracket and quietly stops holding inside one. `readLedger.ts` owns the floor.
75
+ *
58
76
  * `head` is LOG-GLOBAL: it is the head the store's `read` returned, so it moves
59
77
  * for an event no slice here selects. `test/decisionModel.test.ts` pins that
60
78
  * behaviour EXPLICITLY, over four appended events one of which the composed query
@@ -81,7 +99,7 @@ export type RequirementOf<T extends Record<string, SliceProjection>> = T[keyof T
81
99
  export declare const modelAtHead: <T extends Record<string, SliceProjection>>(projections: T) => Effect.Effect<{
82
100
  readonly state: CompositeState<T>;
83
101
  readonly head: Position;
84
- }, CodecError, DcbEventStore | Serializer | RequirementOf<T>>;
102
+ }, CodecError | TaglessSliceInLedger, DcbEventStore | Serializer | RequirementOf<T>>;
85
103
  /**
86
104
  * The write side's decision model: `modelAtHead` plus the `AppendCondition` that
87
105
  * reuses the read query as `failIfEventsMatch` and the read head as `after` (the
@@ -95,17 +113,19 @@ export declare const modelAtHead: <T extends Record<string, SliceProjection>>(pr
95
113
  * of a near-duplicate: `modelAtHead` is the shared part, this function is the
96
114
  * condition and nothing else, and each is shorter than the one function was.
97
115
  *
98
- * `composeProjections` therefore runs TWICE for one command — once inside
99
- * `modelAtHead` for the read, once here for `failIfEventsMatch` — and that is
100
- * deliberate rather than waste. It is a pure derivation of the record, so the two
101
- * queries cannot disagree however either call site changes. The alternative,
102
- * returning the query from `modelAtHead`, would put a third field on a READ-TIME
103
- * API that no read-time caller wants, to save one pure merge over a handful of
104
- * slices; a surface paying for a body's convenience is the worse trade.
116
+ * Outside a bracket `composeProjections` therefore runs twice for one command —
117
+ * once inside `modelAtHead` for the read, once here for `failIfEventsMatch` — and
118
+ * that is deliberate rather than waste. It is a pure derivation of the record, so
119
+ * the two queries cannot disagree however either call site changes. The
120
+ * alternative, returning the query from `modelAtHead`, would put a third field on
121
+ * a READ-TIME API that no read-time caller wants, to save one pure merge over a
122
+ * handful of slices; a surface paying for a body's convenience is the worse
123
+ * trade. Inside a bracket the second merge is not reached at all: the ledger's
124
+ * own derivation composes every recorded hop instead.
105
125
  *
106
126
  * ## How to attribute the read, the decode and the fold — the rule, stated once
107
127
  *
108
- * Because this function is `Effect.map` over `modelAtHead`, prose about the codec
128
+ * Because this function is expressed OVER `modelAtHead`, prose about the codec
109
129
  * splits into two kinds and only one of them may name this symbol:
110
130
  *
111
131
  * - Naming this function as the ENTRY POINT a caller invokes, or as the path
@@ -125,5 +145,5 @@ export declare const modelAtHead: <T extends Record<string, SliceProjection>>(pr
125
145
  export declare const buildDecisionModel: <T extends Record<string, SliceProjection>>(projections: T) => Effect.Effect<{
126
146
  readonly state: CompositeState<T>;
127
147
  readonly appendCondition: AppendCondition;
128
- }, CodecError, DcbEventStore | Serializer | RequirementOf<T>>;
148
+ }, CodecError | TaglessSliceInLedger, DcbEventStore | Serializer | RequirementOf<T>>;
129
149
  //# sourceMappingURL=decisionModel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"decisionModel.d.ts","sourceRoot":"","sources":["../../src/decisionModel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,aAAa,EACb,KAAK,QAAQ,EACd,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAA;AAEvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,+EAA+E;AAC/E,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,IACjE,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,eAAe,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACnE,aAAa,CAAC,KACb,MAAM,CAAC,MAAM,CACd;IAAE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,EAC9D,UAAU,EACV,aAAa,GAAG,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,CAyB3C,CAAA;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EAC1E,aAAa,CAAC,KACb,MAAM,CAAC,MAAM,CACd;IACE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAA;IACjC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAA;CAC1C,EACD,UAAU,EACV,aAAa,GAAG,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,CAU1C,CAAA"}
1
+ {"version":3,"file":"decisionModel.d.ts","sourceRoot":"","sources":["../../src/decisionModel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,aAAa,EACb,KAAK,QAAQ,EACd,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,MAAM,EAAkB,MAAM,QAAQ,CAAA;AAE/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,+EAA+E;AAC/E,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,IACjE,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,eAAe,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACnE,aAAa,CAAC,KACb,MAAM,CAAC,MAAM,CACd;IAAE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,EAC9D,UAAU,GAAG,oBAAoB,EACjC,aAAa,GAAG,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,CA6C3C,CAAA;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EAC1E,aAAa,CAAC,KACb,MAAM,CAAC,MAAM,CACd;IACE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAA;IACjC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAA;CAC1C,EACD,UAAU,GAAG,oBAAoB,EACjC,aAAa,GAAG,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,CA6B3C,CAAA"}
@@ -34,6 +34,72 @@
34
34
  * error union are written once, in the two function bodies, rather than restated
35
35
  * as an annotation above them.
36
36
  *
37
+ * A decision may also answer `Unchanged` — "already done, nothing to append" —
38
+ * built with `Decision.unchanged(reason?)` and recognised ONLY by
39
+ * `isUnchanged`, by its `UnchangedTypeId`. It is a success, not an error and not
40
+ * an empty batch: every runner returns it in place of a `Position` and appends
41
+ * nothing. `Decider`'s fifth type parameter carries the widening and defaults to
42
+ * the non-empty batch, and each runner's result is `DecisionResult<O>`, so a
43
+ * decider or body that cannot answer `Unchanged` keeps a plain `Position` — an
44
+ * untyped one and one generic in its success type included — and its caller is
45
+ * never asked to handle it. `decision.ts` owns the argument, and the one type
46
+ * query that does read differently.
47
+ *
48
+ * `transact(precondition)(body)` is the layered write path, for a decision whose
49
+ * second query cannot be written until an earlier read has been folded. It opens
50
+ * an ambient read ledger, evaluates the precondition inside it, hands that
51
+ * precondition's output to the body as a service, and appends what the body
52
+ * returned under the union of every query either of them read, at the earliest
53
+ * head observed. No condition reaches the caller. `unsafeAppend(events, reason)`
54
+ * is the one sanctioned way out. `precondition.ts` owns the argument for the
55
+ * bracket and everything on it, and `readLedger.ts` for the ledger the seam rests
56
+ * on — including why the derivation itself is deliberately not on this surface.
57
+ *
58
+ * ## ⚠ `runDecider` and `decideOnly` are TWO DISPOSITIONS OF ONE CYCLE
59
+ *
60
+ * They are read as a pair or not at all, because each refuses exactly where the
61
+ * other is the answer:
62
+ *
63
+ * | | outside a bracket | inside a bracket |
64
+ * | ------------ | ---------------------------------------- | ----------------------------------- |
65
+ * | `runDecider` | ✅ the ordinary single-hop case | ⛔ a DEFECT, refused at the store |
66
+ * | `decideOnly` | ⛔ unrunnable, and `tsc` says so | ✅ the layered case |
67
+ *
68
+ * Both carry `Unchanged` the same way, which is what keeps them one pair:
69
+ * `runDecider` returns it having appended nothing, and `decideOnly` passes it to
70
+ * the body unaltered, where `transact` returns it having appended nothing —
71
+ * and, having no write to guard, without refusing an empty bracket as
72
+ * `NoReadsRecorded`.
73
+ *
74
+ * ⚠ *Unrunnable* rather than *unwritable*, and the difference is measured. A bare
75
+ * `decideOnly(d, cmd)` outside a bracket COMPILES, and so does an exported helper
76
+ * over it: its `ReadLedger` requirement simply propagates, as any unmet requirement
77
+ * does. The refusal lands where the context is DISCHARGED — an `Effect.provide`, a
78
+ * `runPromise`, an annotation asking for `R = never` — which may be another file.
79
+ * So `pnpm check` catches it without anything running, which is the whole gain over
80
+ * the runtime defect this replaced; what it does not do is underline the call.
81
+ *
82
+ * Neither is expressed over the other, and that is forced rather than chosen: a
83
+ * definition of `runDecider` as *"`decideOnly`, then append"* would have to call
84
+ * `decideOnly` outside a bracket, which does not typecheck, and the append it then
85
+ * made inside one would be refused. The two refusals are the same fact seen from
86
+ * either side — an append inside a bracket belongs to the bracket — so any
87
+ * expression of one over the other has to defeat one of them.
88
+ *
89
+ * ⚠ The two refusals arrive at DIFFERENT MOMENTS, which is the part a caller acts
90
+ * on. `runDecider` inside a bracket is refused when it RUNS, at core's shared append
91
+ * decorator — so the same refusal also meets a raw `store.append` and a runner one
92
+ * call away in a helper. `decideOnly` outside a bracket is refused by `tsc`, at the
93
+ * discharge point above, by naming `ReadLedger` in its requirement channel. Why each
94
+ * was given the instrument it was is a DECISION and belongs to the record that took
95
+ * it: ADR-0014's `Status`. A lint rule reaches the first case earlier still, while it
96
+ * is being written; `biome-plugins/README.md` in the source repository owns what a
97
+ * rule there can and cannot see.
98
+ *
99
+ * The remedy is one identifier away in each direction, which is the whole reason a
100
+ * refusal is affordable at all. `decider.ts` and `precondition.ts` carry the two
101
+ * halves.
102
+ *
37
103
  * `modelAtHead(slices)` is that same read-decode-fold WITHOUT the
38
104
  * `AppendCondition`: `{ state, head }`, one slice record materialised at read
39
105
  * time with no table, no checkpoint and no runner, current as of the `head` its
@@ -51,11 +117,14 @@
51
117
  * record other than the one the query came from (see `decode.ts`).
52
118
  */
53
119
  export { type Decider, decider, runDecider } from './decider.js';
120
+ export { Decision, type DecisionOutput, type DecisionResult, isUnchanged, Unchanged, UnchangedTypeId, } from './decision.js';
54
121
  export { buildDecisionModel, modelAtHead, type RequirementOf, } from './decisionModel.js';
55
122
  export { decodeSlices } from './decode.js';
56
123
  export { CodecError, UnknownEventType } from './errors.js';
57
124
  export { type EventDef, type EventDefAny, event, type TagsFor } from './event.js';
125
+ export { decideOnly, type Precondition, transact, unsafeAppend, } from './precondition.js';
58
126
  export { type ProjectionBuilder, projection, type SliceProjection, } from './projection.js';
127
+ export { type CloseHop, type Hop, NoReadsRecorded, ReadLedger, TaglessSliceInLedger, } from './readLedger.js';
59
128
  export { makeRegistry, type Registry } from './registry.js';
60
129
  export { Serializer, SerializerDefault } from './Serializer.js';
61
130
  export { toEvent } from './toEvent.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,KAAK,aAAa,GACnB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AACvD,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAA;AAC9E,OAAO,EACL,KAAK,iBAAiB,EACtB,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,YAAY,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqHG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EACL,QAAQ,EACR,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,WAAW,EACX,SAAS,EACT,eAAe,GAChB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,KAAK,aAAa,GACnB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AACvD,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAA;AAC9E,OAAO,EACL,UAAU,EACV,KAAK,YAAY,EACjB,QAAQ,EACR,YAAY,GACb,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,KAAK,iBAAiB,EACtB,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,GAAG,EACR,eAAe,EACf,UAAU,EACV,oBAAoB,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,YAAY,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA"}
@@ -0,0 +1,244 @@
1
+ /**
2
+ * The bracket that owns the append, the precondition value it takes, and the two
3
+ * functions that make a decision inside it writable.
4
+ *
5
+ * ## What the bracket is for, and why no condition reaches the caller
6
+ *
7
+ * `transact(precondition)(body)` evaluates a guard, hands its output to the body
8
+ * as a service, and appends whatever the body produced under the union of every
9
+ * read EITHER of them performed. A caller writes no condition, no union, no floor
10
+ * and no `min`.
11
+ *
12
+ * That is not an ergonomic preference. Owning the append does not make a wrong
13
+ * condition a type error — it makes one UNWRITABLE, which is the stronger
14
+ * guarantee: a returned condition is a condition a caller can decline to use, and
15
+ * a decision that looks guarded and is not is the exact failure this seam exists
16
+ * to close. It is what Axon Framework 5 does, where the condition is married to
17
+ * the events at prepare-commit and the caller never holds one.
18
+ *
19
+ * One thing is deliberately NOT borrowed from that prior art. There, an append
20
+ * with no prior read leaves the condition unset and the append unguarded, so the
21
+ * method name reads as unconditional while the behaviour is not. `NoReadsRecorded`
22
+ * closes that door here and must not be lost.
23
+ *
24
+ * ⚠ The name says *declare, then do*. It is NOT a database transaction: an append
25
+ * under a condition is not two-phase, and nothing rolls back.
26
+ *
27
+ * ## A body may answer `Unchanged`, and then there is nothing to guard
28
+ *
29
+ * The body's success is a `DecisionOutput`: a non-empty batch, or `Unchanged` —
30
+ * "already done". On `Unchanged` the bracket appends nothing, derives no
31
+ * condition and returns the `Unchanged`, and the result type is
32
+ * `DecisionResult<O>`, so a body that cannot answer it keeps a plain `Position`.
33
+ * `decideOnly` passes a decider's `Unchanged` through to the body unaltered.
34
+ *
35
+ * It is NOT refused as `NoReadsRecorded`, even from a bracket that read nothing,
36
+ * and that is not a hole in the refusal. `NoReadsRecorded` exists because an
37
+ * append with no prior read would go out UNGUARDED while looking guarded. An
38
+ * `Unchanged` appends nothing, so there is no write for a condition to guard and
39
+ * nothing for the refusal to protect. The same empty bracket returning EVENTS is
40
+ * still refused, exactly as before. `decision.ts` owns what `Unchanged` is.
41
+ *
42
+ * ## Order is the design, and it is the one line here that must not move
43
+ *
44
+ * The precondition is evaluated INSIDE the bracket and never before it. Evaluated
45
+ * before, its reads are not part of what it protects — the ledger does not exist
46
+ * yet, so its hops are lost and the write is guarded only by the business read.
47
+ * The check would look exactly as it does now and would guard nothing.
48
+ *
49
+ * ## Why the precondition's output crosses as a SERVICE
50
+ *
51
+ * A body that needs authorisation names the tag in its requirement channel, so
52
+ * `transact` must provide it, so the body CANNOT BE RUN without a precondition.
53
+ * That is a PRESENCE check, and presence is the one direction the type system can
54
+ * decide here — `Exclude<R, Id>` in the return type is where it is spent. The
55
+ * alternative, a curried parameter, buys nothing equivalent: a body is free to
56
+ * ignore a parameter.
57
+ *
58
+ * ## Composition is a combinator, not a list
59
+ *
60
+ * `transact` takes exactly ONE precondition. A variadic argument asserts
61
+ * INDEPENDENCE, and the interesting case is DEPENDENT — *may act for this
62
+ * organisation*, then *may sign in that organisation* — which a flat list cannot
63
+ * express and which is the very shape this change exists for. A dependent pair
64
+ * needs nothing anyway: a precondition is an ordinary `Effect` evaluated inside
65
+ * the open ledger, so two compose with `Effect.gen` and both sets of reads land in
66
+ * the same condition. An INDEPENDENT set wants a combinator over a RECORD,
67
+ * evaluated SEQUENTIALLY — fail-fast is the point, and concurrent reads are
68
+ * precisely the case where the earliest-head reduction stops being redundant. That
69
+ * combinator is purely additive and is not shipped here.
70
+ *
71
+ * ## Conflict retry goes OUTSIDE the bracket, never inside
72
+ *
73
+ * `retryOnConflict(transact(p)(body))`, never a retry within the body. The bracket
74
+ * takes an opaque `Effect`, so no type can express that rule and the lint gate is
75
+ * what enforces it: in the source repository, `biome-plugins/noRetryInBracket.grit`
76
+ * owns why and `test/retryInBracket.test.ts` executes it.
77
+ *
78
+ * ## ⚠⚠ THE ONE MISTAKE NOTHING CATCHES: a view read inside a bracket
79
+ *
80
+ * **A projection read, a raw SQL read, or any read that does not fold through
81
+ * `modelAtHead`, performed inside a bracket, contributes NO HOP.** Its query is
82
+ * absent from the derived union and its head is absent from the floor, so the
83
+ * decision rests on data the condition does not guard — and the bracket looks
84
+ * guarded, which is the exact failure this seam exists to close, wearing the fix's
85
+ * own name.
86
+ *
87
+ * **Nothing will tell you — not here and not in your own tree.** Not the type
88
+ * system: a requirement channel is ADDITIVE, so it can demand a capability's
89
+ * presence and never its absence. Not `NoReadsRecorded`: that fires on an EMPTY
90
+ * bracket and never on an under-populated one, so a bracket holding one genuine fold
91
+ * beside a view read passes it. And not a lint rule — one was written for exactly
92
+ * this, kept for a while and deleted, because a syntactic gate has no call graph and
93
+ * no types, so it could only name a receiver by convention, and because a lint
94
+ * plugin runs only where its own config is and so never travels with the library.
95
+ * `biome-plugins/README.md` in the source repository records the test that rule
96
+ * failed.
97
+ *
98
+ * So this is a rule a reader keeps, and knowing it is the guarantee: **inside a
99
+ * bracket, read through `modelAtHead`. If a read must
100
+ * consult a materialised view, do it OUTSIDE the bracket** — where it is ordinary
101
+ * code, because its answer is then not part of what the append is guarded on.
102
+ * ADR-0014's *decision read* rule is the test for which one a given read is.
103
+ */
104
+ import type { AppendConditionFailed, DcbEvent, Position } from '@kairos-es/core';
105
+ import { AppendIsOwned, DcbEventStore } from '@kairos-es/core';
106
+ import type { Array as Arr } from 'effect';
107
+ import { Context, Effect } from 'effect';
108
+ import type { Decider } from './decider.js';
109
+ import { type DecisionOutput, type DecisionResult } from './decision.js';
110
+ import { type RequirementOf } from './decisionModel.js';
111
+ import type { CodecError } from './errors.js';
112
+ import type { SliceProjection } from './projection.js';
113
+ import { NoReadsRecorded, ReadLedger, type TaglessSliceInLedger } from './readLedger.js';
114
+ import type { Serializer } from './Serializer.js';
115
+ import type { Uuid } from './Uuid.js';
116
+ /**
117
+ * A named, reusable guard plus the tag its output is handed across.
118
+ *
119
+ * A VALUE and not a function of the command, deliberately: the point of the seam
120
+ * is that a guard is written once, centrally, and referenced by however many
121
+ * commands need it. A command supplies its own arguments when it BUILDS the value
122
+ * (`mayActFor(userId, orgId, verb)`), and what `transact` receives is the built
123
+ * thing.
124
+ *
125
+ * `Service` is fully generic and the library never reads it. Every consumer-side
126
+ * authorisation shape — a plain resolved identifier, a branded proof, a gate with
127
+ * a private minting closure — is application code this genericity accommodates
128
+ * without the library knowing anything about it. That is a requirement rather than
129
+ * an incidental property: hard-coding it would foreclose a consumer's later
130
+ * choice, and it costs nothing to keep.
131
+ *
132
+ * ⚠ `evaluate` is an `Effect`, so it is a FIELD OF AN OBJECT LITERAL and its
133
+ * expression is evaluated when the `Precondition` VALUE is built — before the
134
+ * bracket opens and before any cheap check inside it runs. Anything a builder does
135
+ * eagerly there (parsing an argument, resolving an identifier) happens outside the
136
+ * ordering guarantee above, and a throw there is an uncatchable defect on the very
137
+ * path meant to deny the input cheaply. Wrap such work in `Effect.suspend`.
138
+ */
139
+ export interface Precondition<Id, Service, E, R> {
140
+ readonly tag: Context.Tag<Id, Service>;
141
+ readonly evaluate: Effect.Effect<Service, E, R>;
142
+ }
143
+ /**
144
+ * The DCB command cycle MINUS the append: read the decision model, decode, fold,
145
+ * decide, and RETURN the events for the bracket to append — or the decider's
146
+ * `Unchanged`, passed through as it came, for the bracket to append nothing.
147
+ *
148
+ * Bracket-only, and the refusal is STATIC — see the section below for the mechanism
149
+ * and, more importantly, for where the error actually lands, which is not where a
150
+ * reader first expects. Forced through anyway, being outside a bracket is a DEFECT
151
+ * rather than an error-channel value: it is not a data-dependent outcome a caller
152
+ * could handle, and the events produced would be dropped with nothing to append them.
153
+ *
154
+ * It is also what makes the nesting refusal liveable. A command runner inside a
155
+ * bracket is refused at the store, and this is the function a builder reaches for
156
+ * instead — one identifier away. A refusal is only affordable when the fix is that
157
+ * close.
158
+ *
159
+ * `modelAtHead` and NOT `buildDecisionModel`: this function must not derive a
160
+ * condition at all. The bracket owns that, and owning it in ONE place is what
161
+ * stops a caller appending under the last hop's condition alone.
162
+ *
163
+ * ## Bracket-only is a REQUIREMENT, so `tsc` refuses it rather than a run
164
+ *
165
+ * `ReadLedger` is named in the requirement channel and `transact` is the only thing
166
+ * that provides it, so a program that calls this outside a bracket cannot be RUN,
167
+ * and `tsc` is what says so — nothing has to execute for a caller to be told.
168
+ *
169
+ * ⚠ Measured, because the obvious stronger reading is wrong: the call site itself
170
+ * COMPILES. A bare `decideOnly(d, cmd)` outside a bracket is fine, and so is an
171
+ * exported helper over it; the requirement just propagates, as any unmet requirement
172
+ * does. What fails is the DISCHARGE — `Effect.provide`, `runPromise`, an annotation
173
+ * asking for `R = never` — possibly in another file. That is still a static refusal
174
+ * and it is the whole gain over the runtime defect this replaced. It is not a
175
+ * squiggle under the mistake.
176
+ *
177
+ * That is a PRESENCE check, and presence is the direction the type system can
178
+ * decide here — the same direction `transact`'s `Exclude<R, Id>` already spends on
179
+ * the precondition's output. ADR-0014's 2026-08-29 amendment carries the rule that
180
+ * follows: look for a presence check before reaching for a defect. This function
181
+ * shipped a defect first, and the check was available all along.
182
+ *
183
+ * ⚠ Two lines carry it and their JOBS differ. The RETURN ANNOTATION is the surface a
184
+ * consumer is judged against, and what the discharge above meets. The
185
+ * `yield* ReadLedger` keeps the requirement in the INFERRED type, so it survives the
186
+ * annotation being edited, and it dies at run time for the audience no type reaches
187
+ * — a JavaScript consumer runs no compile step — rather than handing back events
188
+ * with no bracket to append them. Nothing here READS the ledger; `modelAtHead` does
189
+ * the recording, ambiently, through `Effect.serviceOption`.
190
+ *
191
+ * ⚠ Do not try to write down which check catches which deletion. It was tried twice
192
+ * and was wrong twice, and `test/appendOwnership.test.ts` in the source repository
193
+ * says why the second failure means there should be no third attempt. Plant the
194
+ * mutation instead.
195
+ */
196
+ export declare const decideOnly: <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<O, E | CodecError | TaglessSliceInLedger, ReadLedger | DcbEventStore | Serializer | Uuid | R | RequirementOf<P>>;
197
+ /**
198
+ * The sanctioned exception to the nesting refusal: append these events under NO
199
+ * condition, and say in the identifier and in a required `reason` why that is
200
+ * deliberate.
201
+ *
202
+ * It covers three cases and no others: deliberate narrowing, a genuinely
203
+ * unconditional creation, and a second append inside one bracket. The `reason` is
204
+ * ceremony on purpose, and ADR-0014 decision 6 owns why a hatch has to cost more
205
+ * than the safe path — that argument is a DECISION and belongs to the record that
206
+ * took it, so it is not restated here. How biome turns a required argument into a
207
+ * review signal is the source repository's `biome-plugins/unsafeAppendReported.grit`.
208
+ *
209
+ * ⚠ It BYPASSES the derived condition rather than composing with it. A caller who
210
+ * genuinely needs *the union, minus one event type* must restate the whole
211
+ * condition here. If that turns up in practice, a composing override is the
212
+ * additive answer and this hatch is not it.
213
+ *
214
+ * `Effect.mapInputContext` over
215
+ * `Context.omit(AppendIsOwned)` is what lets it through the store's ownership
216
+ * refusal — the one route, and `AppendIsOwned.ts` owns why removing the marker is
217
+ * the shape rather than a second tag or a sanctioning flag.
218
+ */
219
+ export declare const unsafeAppend: (events: Arr.NonEmptyReadonlyArray<DcbEvent>, reason: string) => Effect.Effect<Position, AppendConditionFailed, DcbEventStore>;
220
+ /**
221
+ * Open a bracket over ONE decision: evaluate the precondition inside it, hand its
222
+ * output to the body under the precondition's tag, and append what the body
223
+ * produced under the union of every read either of them performed.
224
+ *
225
+ * Curried so the precondition reads as the thing the command DECLARES and the body
226
+ * reads as the thing it DOES.
227
+ *
228
+ * The ownership marker is provided ALONGSIDE the ledger and to the body only, so
229
+ * any append inside the body — a nested bracket, a command runner, a raw
230
+ * `store.append` — is refused at the store as a defect, while the bracket's own
231
+ * closing append below runs outside that scope and is unaffected. That is why the
232
+ * refusal is on APPENDING rather than on opening a bracket: a command runner
233
+ * appends without opening one, and its events usually match the union the bracket
234
+ * is about to guard on, so the bracket's own close would be refused by a condition
235
+ * its own body invalidated.
236
+ *
237
+ * The body may succeed with `Unchanged` (see the module documentation), and the
238
+ * result is then `DecisionResult<O>`: `Position` for a body that cannot answer
239
+ * it, `Position | Unchanged` for one that can. `O` is last and defaulted, so an
240
+ * explicit `<E, R>` written against the narrower signature still means what it
241
+ * did.
242
+ */
243
+ export declare const transact: <Id, Service, PE, PR>(precondition: Precondition<Id, Service, PE, PR>) => <E, R, O extends DecisionOutput = Arr.NonEmptyReadonlyArray<DcbEvent>>(body: Effect.Effect<O, E, R>) => Effect.Effect<DecisionResult<O>, E | PE | NoReadsRecorded | AppendConditionFailed, Exclude<Exclude<R, Id>, ReadLedger | AppendIsOwned> | Exclude<PR, ReadLedger | AppendIsOwned> | DcbEventStore>;
244
+ //# sourceMappingURL=precondition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"precondition.d.ts","sourceRoot":"","sources":["../../src/precondition.ts"],"names":[],"mappings":"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"}