@kairos-es/codec 1.0.0-alpha.1 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/cjs/decider.js +35 -67
- package/dist/cjs/decider.js.map +1 -1
- package/dist/cjs/decision.js +45 -0
- package/dist/cjs/decision.js.map +1 -0
- package/dist/cjs/index.js +25 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/precondition.js +20 -3
- package/dist/cjs/precondition.js.map +1 -1
- package/dist/dts/decider.d.ts +47 -6
- package/dist/dts/decider.d.ts.map +1 -1
- package/dist/dts/decision.d.ts +161 -0
- package/dist/dts/decision.d.ts.map +1 -0
- package/dist/dts/index.d.ts +18 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/precondition.d.ts +26 -3
- package/dist/dts/precondition.d.ts.map +1 -1
- package/dist/esm/decider.js +33 -65
- package/dist/esm/decider.js.map +1 -1
- package/dist/esm/decision.js +37 -0
- package/dist/esm/decision.js.map +1 -0
- package/dist/esm/index.js +18 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/precondition.js +20 -3
- package/dist/esm/precondition.js.map +1 -1
- package/package.json +2 -2
- package/src/decider.ts +87 -16
- package/src/decision.ts +174 -0
- package/src/index.ts +25 -0
- package/src/precondition.ts +46 -8
package/src/decision.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
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, Data, Predicate } from 'effect'
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The type id `isUnchanged` recognises. `Symbol.for`, so both halves of the dual
|
|
97
|
+
* build — and any two copies of this package in one graph — agree on it.
|
|
98
|
+
*/
|
|
99
|
+
export const UnchangedTypeId: unique symbol = Symbol.for(
|
|
100
|
+
'@kairos-es/codec/Unchanged',
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
/** The type of `UnchangedTypeId`. */
|
|
104
|
+
export type UnchangedTypeId = typeof UnchangedTypeId
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* "Nothing to do: this work is already done." A runner that receives it appends
|
|
108
|
+
* nothing and returns it in place of a `Position`.
|
|
109
|
+
*
|
|
110
|
+
* `reason` is for a human reading a trace or a test failure, and nothing in the
|
|
111
|
+
* library reads it. A `Data.TaggedClass`, so two values with the same reason are
|
|
112
|
+
* `Equal` and a test can compare them structurally.
|
|
113
|
+
*
|
|
114
|
+
* ⚠ Test for it with `isUnchanged`, never with `instanceof` or `_tag` — the
|
|
115
|
+
* module documentation says why each of those is wrong.
|
|
116
|
+
*/
|
|
117
|
+
export class Unchanged extends Data.TaggedClass('Unchanged')<{
|
|
118
|
+
readonly reason?: string
|
|
119
|
+
}> {
|
|
120
|
+
readonly [UnchangedTypeId]: UnchangedTypeId = UnchangedTypeId
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Whether `u` is an `Unchanged`, by its type id. Total over `unknown`, and
|
|
125
|
+
* realm-safe across the dual ESM/CJS build. An application value that merely
|
|
126
|
+
* carries `_tag: 'Unchanged'` answers `false`, deliberately.
|
|
127
|
+
*/
|
|
128
|
+
export const isUnchanged = (u: unknown): u is Unchanged =>
|
|
129
|
+
Predicate.hasProperty(u, UnchangedTypeId)
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The constructors a `decide` step reaches for. One member today; it is a
|
|
133
|
+
* namespace object rather than a bare function so the call site reads as what it
|
|
134
|
+
* is, `Decision.unchanged('already dispatched')`.
|
|
135
|
+
*/
|
|
136
|
+
export const Decision: {
|
|
137
|
+
readonly unchanged: (reason?: string) => Unchanged
|
|
138
|
+
} = {
|
|
139
|
+
unchanged: (reason) => new Unchanged(reason === undefined ? {} : { reason }),
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Everything a decision may succeed with: a non-empty batch of events to
|
|
144
|
+
* append, or `Unchanged`. A plain, possibly-empty array is NOT a member, which is
|
|
145
|
+
* what keeps an empty batch unreachable from a decision.
|
|
146
|
+
*/
|
|
147
|
+
export type DecisionOutput = Arr.NonEmptyReadonlyArray<DcbEvent> | Unchanged
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* What a runner returns for a decision that succeeds with `O`: `Position` unless
|
|
151
|
+
* `O` admits `Unchanged`, and `Position | Unchanged` when it does.
|
|
152
|
+
*
|
|
153
|
+
* Asked of each member of `O` — DISTRIBUTIVE — so a union `O` answers
|
|
154
|
+
* `Position | Unchanged` exactly when one of its members is `Unchanged`, a batch
|
|
155
|
+
* answers `Position` with no `| never` residue, and an `O` that is a type
|
|
156
|
+
* variable bounded by the batch is seen to be `Position`, which is what a
|
|
157
|
+
* wrapper generic in its success type needs. `O` that can ONLY be `Unchanged`
|
|
158
|
+
* answers `Unchanged`, the precise result and assignable wherever
|
|
159
|
+
* `Position | Unchanged` is expected.
|
|
160
|
+
*
|
|
161
|
+
* Two members are checked BEFORE the distribution, and both answer `Position`,
|
|
162
|
+
* the result each had before `Unchanged` existed. `any`, because a distributive
|
|
163
|
+
* conditional over `any` takes both branches and would widen every untyped
|
|
164
|
+
* decision. And `never` — a body that only ever fails — because distributing
|
|
165
|
+
* over `never` answers `never`, which a variable once typed with a `Position`
|
|
166
|
+
* result would no longer accept. The module documentation owns the rest.
|
|
167
|
+
*/
|
|
168
|
+
export type DecisionResult<O> = 0 extends 1 & O
|
|
169
|
+
? Position
|
|
170
|
+
: [O] extends [never]
|
|
171
|
+
? Position
|
|
172
|
+
: O extends Unchanged
|
|
173
|
+
? Unchanged
|
|
174
|
+
: Position
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,17 @@
|
|
|
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
|
+
*
|
|
37
48
|
* `transact(precondition)(body)` is the layered write path, for a decision whose
|
|
38
49
|
* second query cannot be written until an earlier read has been folded. It opens
|
|
39
50
|
* an ambient read ledger, evaluates the precondition inside it, hands that
|
|
@@ -54,6 +65,12 @@
|
|
|
54
65
|
* | `runDecider` | ✅ the ordinary single-hop case | ⛔ a DEFECT, refused at the store |
|
|
55
66
|
* | `decideOnly` | ⛔ unrunnable, and `tsc` says so | ✅ the layered case |
|
|
56
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
|
+
*
|
|
57
74
|
* ⚠ *Unrunnable* rather than *unwritable*, and the difference is measured. A bare
|
|
58
75
|
* `decideOnly(d, cmd)` outside a bracket COMPILES, and so does an exported helper
|
|
59
76
|
* over it: its `ReadLedger` requirement simply propagates, as any unmet requirement
|
|
@@ -101,6 +118,14 @@
|
|
|
101
118
|
*/
|
|
102
119
|
|
|
103
120
|
export { type Decider, decider, runDecider } from './decider'
|
|
121
|
+
export {
|
|
122
|
+
Decision,
|
|
123
|
+
type DecisionOutput,
|
|
124
|
+
type DecisionResult,
|
|
125
|
+
isUnchanged,
|
|
126
|
+
Unchanged,
|
|
127
|
+
UnchangedTypeId,
|
|
128
|
+
} from './decision'
|
|
104
129
|
export {
|
|
105
130
|
buildDecisionModel,
|
|
106
131
|
modelAtHead,
|
package/src/precondition.ts
CHANGED
|
@@ -24,6 +24,21 @@
|
|
|
24
24
|
* ⚠ The name says *declare, then do*. It is NOT a database transaction: an append
|
|
25
25
|
* under a condition is not two-phase, and nothing rolls back.
|
|
26
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
|
+
*
|
|
27
42
|
* ## Order is the design, and it is the one line here that must not move
|
|
28
43
|
*
|
|
29
44
|
* The precondition is evaluated INSIDE the bracket and never before it. Evaluated
|
|
@@ -91,6 +106,11 @@ import { AppendIsOwned, DcbEventStore } from '@kairos-es/core'
|
|
|
91
106
|
import type { Array as Arr } from 'effect'
|
|
92
107
|
import { Context, Effect, Layer } from 'effect'
|
|
93
108
|
import type { Decider } from './decider'
|
|
109
|
+
import {
|
|
110
|
+
type DecisionOutput,
|
|
111
|
+
type DecisionResult,
|
|
112
|
+
isUnchanged,
|
|
113
|
+
} from './decision'
|
|
94
114
|
import { modelAtHead, type RequirementOf } from './decisionModel'
|
|
95
115
|
import type { CodecError } from './errors'
|
|
96
116
|
import type { SliceProjection } from './projection'
|
|
@@ -134,7 +154,8 @@ export interface Precondition<Id, Service, E, R> {
|
|
|
134
154
|
|
|
135
155
|
/**
|
|
136
156
|
* The DCB command cycle MINUS the append: read the decision model, decode, fold,
|
|
137
|
-
* decide, and RETURN the events for the bracket to append
|
|
157
|
+
* decide, and RETURN the events for the bracket to append — or the decider's
|
|
158
|
+
* `Unchanged`, passed through as it came, for the bracket to append nothing.
|
|
138
159
|
*
|
|
139
160
|
* Bracket-only, and the refusal is STATIC — see the section below for the mechanism
|
|
140
161
|
* and, more importantly, for where the error actually lands, which is not where a
|
|
@@ -189,11 +210,12 @@ export const decideOnly = <
|
|
|
189
210
|
Command,
|
|
190
211
|
E,
|
|
191
212
|
R,
|
|
213
|
+
O extends DecisionOutput = Arr.NonEmptyReadonlyArray<DcbEvent>,
|
|
192
214
|
>(
|
|
193
|
-
decider: Decider<P, Command, E, R>,
|
|
215
|
+
decider: Decider<P, Command, E, R, O>,
|
|
194
216
|
command: Command,
|
|
195
217
|
): Effect.Effect<
|
|
196
|
-
|
|
218
|
+
O,
|
|
197
219
|
E | CodecError | TaglessSliceInLedger,
|
|
198
220
|
ReadLedger | DcbEventStore | Serializer | Uuid | R | RequirementOf<P>
|
|
199
221
|
> =>
|
|
@@ -275,13 +297,19 @@ export const unsafeAppend = (
|
|
|
275
297
|
* appends without opening one, and its events usually match the union the bracket
|
|
276
298
|
* is about to guard on, so the bracket's own close would be refused by a condition
|
|
277
299
|
* its own body invalidated.
|
|
300
|
+
*
|
|
301
|
+
* The body may succeed with `Unchanged` (see the module documentation), and the
|
|
302
|
+
* result is then `DecisionResult<O>`: `Position` for a body that cannot answer
|
|
303
|
+
* it, `Position | Unchanged` for one that can. `O` is last and defaulted, so an
|
|
304
|
+
* explicit `<E, R>` written against the narrower signature still means what it
|
|
305
|
+
* did.
|
|
278
306
|
*/
|
|
279
307
|
export const transact =
|
|
280
308
|
<Id, Service, PE, PR>(precondition: Precondition<Id, Service, PE, PR>) =>
|
|
281
|
-
<E, R
|
|
282
|
-
body: Effect.Effect<
|
|
309
|
+
<E, R, O extends DecisionOutput = Arr.NonEmptyReadonlyArray<DcbEvent>>(
|
|
310
|
+
body: Effect.Effect<O, E, R>,
|
|
283
311
|
): Effect.Effect<
|
|
284
|
-
|
|
312
|
+
DecisionResult<O>,
|
|
285
313
|
E | PE | NoReadsRecorded | AppendConditionFailed,
|
|
286
314
|
| Exclude<Exclude<R, Id>, ReadLedger | AppendIsOwned>
|
|
287
315
|
| Exclude<PR, ReadLedger | AppendIsOwned>
|
|
@@ -291,7 +319,7 @@ export const transact =
|
|
|
291
319
|
const store = yield* DcbEventStore
|
|
292
320
|
const ledger = yield* makeReadLedger
|
|
293
321
|
|
|
294
|
-
const
|
|
322
|
+
const output: DecisionOutput = yield* Effect.gen(function* () {
|
|
295
323
|
// ORDER IS THE DESIGN. The precondition runs first and runs HERE, with the
|
|
296
324
|
// ledger already in context, so its reads are hops in the very condition
|
|
297
325
|
// that will guard the body's write.
|
|
@@ -324,10 +352,20 @@ export const transact =
|
|
|
324
352
|
),
|
|
325
353
|
)
|
|
326
354
|
|
|
355
|
+
// "Already done": nothing to append, so no condition to derive and nothing
|
|
356
|
+
// for `NoReadsRecorded` to protect — that refusal guards a WRITE that would
|
|
357
|
+
// go out unguarded, and there is none. It must stay BEFORE the derivation,
|
|
358
|
+
// or an empty bracket answering `Unchanged` would be refused for a write it
|
|
359
|
+
// never asked for. The casts restate `DecisionResult<O>`, which the checker
|
|
360
|
+
// cannot relate to either branch while `O` is still a type variable.
|
|
361
|
+
if (isUnchanged(output)) {
|
|
362
|
+
return output as DecisionResult<O>
|
|
363
|
+
}
|
|
364
|
+
|
|
327
365
|
const condition = deriveLayeredCondition(yield* ledger.hops)
|
|
328
366
|
if (condition === undefined) {
|
|
329
367
|
return yield* new NoReadsRecorded()
|
|
330
368
|
}
|
|
331
369
|
|
|
332
|
-
return yield* store.append(
|
|
370
|
+
return (yield* store.append(output, condition)) as DecisionResult<O>
|
|
333
371
|
})
|