@systemfsoftware/effect-cell-types 8.0.0 → 8.2.0
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/CHANGELOG.md +16 -0
- package/README.md +72 -14
- package/dist/index.d.ts +105 -134
- package/dist/index.mjs +124 -94
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @systemfsoftware/effect-cell-types
|
|
2
2
|
|
|
3
|
+
## 8.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Build a cell as a typed continuation chain: `Sandwich.read` takes the read effect and each step exposes only the phase that may follow, so a misordered chain fails to compile with a missing-method error naming the lawful next steps. Pure fillings (`decode`, `encode`) accept only `Sandwich.pure` thunks, and every finished cell carries a recorded `phases` tuple naming the steps it runs. The record-spec authoring surface is retired: build chains with `Sandwich.read` instead.
|
|
8
|
+
|
|
9
|
+
## 8.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- `collectAll` now declares an error channel of `never`: the composed cell cannot fail, because every per-item refusal is delivered to the fold as a `Result`. Composed cells no longer carry the item error channel in their type.
|
|
14
|
+
|
|
15
|
+
`Workflow.andThen` now composes two total workflows (components whose error channel is `never`) into a total workflow. Error-carrying chains are unchanged.
|
|
16
|
+
|
|
17
|
+
`Workflow.make` and `Workflow.total` now refuse decision unions whose variants declare the family brand as a narrow unique-symbol type instead of the documented class-field idiom. Declare the brand as a `readonly [T] = T` field on each `S.TaggedClass` variant: the field initializer widens the slot to the general `symbol` type, and the predicate keys on that widened slot. A `typeof`-annotated slot stays narrow and is refused with `UnsharedTypeId` — including the idiom hand-written as an interface. An interface annotating the slot as plain `symbol` is structurally identical to the class idiom and is accepted.
|
|
18
|
+
|
|
3
19
|
## 8.0.0
|
|
4
20
|
|
|
5
21
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -15,8 +15,8 @@ When both channels are inhabited, `Workflow<Command, Decision, Error>` is the fu
|
|
|
15
15
|
`(command: Command) => Result<Decision, Error>` carrying the nominal `WorkflowBrand`
|
|
16
16
|
conjunct — a phantom readonly TypeId-keyed field that no runtime property backs. The brand
|
|
17
17
|
is what makes the workbook nominal: `Workflow.make` is the only constructor that applies
|
|
18
|
-
it, and every surface that runs a decision — the `decide`
|
|
19
|
-
|
|
18
|
+
it, and every surface that runs a decision — the chain's `decide` slot demands it —
|
|
19
|
+
requires it, so a decision that skipped `make` is a compile error at the call site that would have run it, with the brand named in the diagnostic. A `never` channel does
|
|
20
20
|
not silently collapse to that function: it resolves to a marker interface that no function
|
|
21
21
|
can satisfy, so the mistake is a compile error with the remediation attached (below). The
|
|
22
22
|
success channel is shaped the same way: `Workflow.make` refuses a decision channel that is
|
|
@@ -133,22 +133,80 @@ decision anything may run. The error channel is a real variant (`RestartDecision
|
|
|
133
133
|
giving up is a decision the caller must branch on, so declaring the error channel `never` is
|
|
134
134
|
rejected, not allowed.
|
|
135
135
|
|
|
136
|
+
## Building a cell: the `Sandwich` chain
|
|
137
|
+
|
|
138
|
+
A cell is authored as a typed continuation chain, not a record of phases. `Sandwich.read` takes the impure read effect and returns only the lawful next steps; each step composes its `run` at construction and exposes only what may follow, so a misordered chain fails to compile with a missing-method error whose displayed type names the lawful next steps. There is no interpreter: composition happens step by step inside the constructors. Every finished cell carries a recorded `phases` tuple — a type-level literal plus a matching runtime array, both produced by the constructors.
|
|
139
|
+
|
|
140
|
+
| Step | Exposes next | Channel law |
|
|
141
|
+
| -------- | ----------------------------------------------- | ---------------------------------------------------------------- |
|
|
142
|
+
| `read` | `decode`, `decide` | `I` consumed; `Raw` produced; `E`/`R` from the `Effect` |
|
|
143
|
+
| `decode` | `decide` | a `Sandwich.pure` phase; its refusal fails the cell |
|
|
144
|
+
| `decide` | `encode` (decoded chain) or `write` (raw chain) | a `Workflow.make` value; the outcome is a value, never a failure |
|
|
145
|
+
| `encode` | `write` | a `Sandwich.pure` phase shaping the outcome `Result` |
|
|
146
|
+
| `write` | — | `Out` plus `Raw` in (or unary `Out` alone); `Resp`/`E`/`R` out |
|
|
147
|
+
|
|
148
|
+
A short chain skips the filling's middle steps — `read → decide → write` — and records exactly those three phases:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { Sandwich, type Workflow } from '@systemfsoftware/effect-cell-types'
|
|
152
|
+
import { Effect, Result } from 'effect'
|
|
153
|
+
|
|
154
|
+
// The reader's own domain: a command, its raw reading, and a decide outcome.
|
|
155
|
+
interface Command {
|
|
156
|
+
readonly id: string
|
|
157
|
+
}
|
|
158
|
+
interface Raw {
|
|
159
|
+
readonly bytes: string
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// `admit` is a `Workflow.make` value over the decoded form, built as in
|
|
163
|
+
// "The constructor" above; `render` turns its outcome into a string.
|
|
164
|
+
declare const admit: Workflow<Decoded, Admitted, Malformed>
|
|
165
|
+
declare const render: (outcome: Result.Result<Admitted, Malformed>) => string
|
|
166
|
+
|
|
167
|
+
const cell = Sandwich.read((command: Command) => Effect.succeed(new Decoded({ length: command.id.length }))).decide(
|
|
168
|
+
admit,
|
|
169
|
+
).write(
|
|
170
|
+
(outcome: Result.Result<Admitted, Malformed>) => Effect.sync(() => render(outcome)),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
cell.phases // ['read', 'decide', 'write']
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
A full chain fills `decode` and `encode` with `Sandwich.pure` phases — synchronous `Result`-returning thunks, the only values the slots accept, so no `Effect` can be evaluated inside them:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const full = Sandwich.read((command: Command) => Effect.succeed({ bytes: command.id })).decode(
|
|
180
|
+
Sandwich.pure((raw: Raw): Result.Result<Decoded, Malformed> =>
|
|
181
|
+
Result.succeed(new Decoded({ length: raw.bytes.length }))
|
|
182
|
+
),
|
|
183
|
+
).decide(admit).encode(
|
|
184
|
+
Sandwich.pure((outcome: Result.Result<Admitted, Malformed>): Result.Result<string, never> =>
|
|
185
|
+
Result.succeed(render(outcome))
|
|
186
|
+
),
|
|
187
|
+
).write((line: string, raw: Raw) => Effect.succeed(`${line}<-${raw.bytes}`))
|
|
188
|
+
|
|
189
|
+
full.phases // ['read', 'decode', 'decide', 'encode', 'write']
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The `decide` refusal is an outcome, not a failure: it travels to `encode` and `write` as a `Result` value. A `decode` refusal fails the cell and the run stops there.
|
|
193
|
+
|
|
136
194
|
## What it rejects at compile time
|
|
137
195
|
|
|
138
196
|
All six violations fail `tsc`; the messages below are what `tsc` reports (verified against this package and `effect@4.0.0-rc.108`).
|
|
139
197
|
|
|
140
|
-
| Violation | `tsc` reports | Why it is rejected
|
|
141
|
-
| ----------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
142
|
-
| A `Promise` return | `Type 'Promise<Decision>' is not assignable to type 'Result<Decision, Err>'` | a workflow is a synchronous pure decision; async work belongs in the executor shell around it
|
|
143
|
-
| An `Effect` return | `Type 'Effect<Decision, never, never>' is not assignable to type 'Result<Decision, Err>'` | the workflow returns a value, not an effect handle; the executor runs effects and hands the workflow its input
|
|
144
|
-
| `never` decision channel | `Type '...' is not assignable to type 'UninhabitedDecision'` | a workflow that can never produce a decision can never succeed
|
|
145
|
-
| `never` error channel | `Type '...' is not assignable to type 'UninhabitedError'` | a workflow that cannot fail decides nothing; fold the function into its owning module
|
|
146
|
-
| An untagged error variant | `Type '...' is not assignable to type 'UntaggedError'` | an error variant needs a `_tag` a consumer can dispatch on; declare the errors as `S.TaggedError` instances
|
|
147
|
-
| A single-variant decision channel | `Type '...' is not assignable to type 'SingleVariantDecision'` | a decision chooses between at least two distinguishable outcomes; one variant is a calculation wearing a decision's shape
|
|
148
|
-
| An untagged decision variant | `Type '...' is not assignable to type 'UntaggedDecision'` | a decision variant needs a `_tag` a consumer can dispatch on; declare the variants as `S.TaggedClass` instances
|
|
149
|
-
| Decision variants with no shared TypeId | `Type '...' is not assignable to type 'UnsharedTypeId'` | one decision family carries one TypeId — a `Symbol.for` brand on every variant class
|
|
150
|
-
| A bare decider in a `
|
|
151
|
-
| A plain interface at the command position | `'Cmd' only refers to a type, but is being used as a value here` | the command is constrained on the value, and a declared type produces none — so there is no marker to smuggle
|
|
198
|
+
| Violation | `tsc` reports | Why it is rejected |
|
|
199
|
+
| ----------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
200
|
+
| A `Promise` return | `Type 'Promise<Decision>' is not assignable to type 'Result<Decision, Err>'` | a workflow is a synchronous pure decision; async work belongs in the executor shell around it |
|
|
201
|
+
| An `Effect` return | `Type 'Effect<Decision, never, never>' is not assignable to type 'Result<Decision, Err>'` | the workflow returns a value, not an effect handle; the executor runs effects and hands the workflow its input |
|
|
202
|
+
| `never` decision channel | `Type '...' is not assignable to type 'UninhabitedDecision'` | a workflow that can never produce a decision can never succeed |
|
|
203
|
+
| `never` error channel | `Type '...' is not assignable to type 'UninhabitedError'` | a workflow that cannot fail decides nothing; fold the function into its owning module |
|
|
204
|
+
| An untagged error variant | `Type '...' is not assignable to type 'UntaggedError'` | an error variant needs a `_tag` a consumer can dispatch on; declare the errors as `S.TaggedError` instances |
|
|
205
|
+
| A single-variant decision channel | `Type '...' is not assignable to type 'SingleVariantDecision'` | a decision chooses between at least two distinguishable outcomes; one variant is a calculation wearing a decision's shape |
|
|
206
|
+
| An untagged decision variant | `Type '...' is not assignable to type 'UntaggedDecision'` | a decision variant needs a `_tag` a consumer can dispatch on; declare the variants as `S.TaggedClass` instances |
|
|
207
|
+
| Decision variants with no shared TypeId | `Type '...' is not assignable to type 'UnsharedTypeId'` | one decision family carries one TypeId — a `Symbol.for` brand on every variant class |
|
|
208
|
+
| A bare decider in a `decide` slot | `Type '(command: Cmd) => Result<Dec, Err>' is not assignable to type 'WorkflowBrand'` | only a `Workflow.make` value satisfies the `decide` slot; a lambda that skipped `make` is not a decision a chain may run |
|
|
209
|
+
| A plain interface at the command position | `'Cmd' only refers to a type, but is being used as a value here` | the command is constrained on the value, and a declared type produces none — so there is no marker to smuggle |
|
|
152
210
|
|
|
153
211
|
The two `never` cases are where the content-vs-filename distinction pays off. `Workflow<C, never, E>` resolves to `UninhabitedDecision` and `Workflow<C, D, never>` to `UninhabitedError` — interfaces whose only property is required and whose _type_ is the remediation, so the compile error points at the fix:
|
|
154
212
|
|
package/dist/index.d.ts
CHANGED
|
@@ -5,89 +5,11 @@ import { Result } from "effect/Result";
|
|
|
5
5
|
import { Kind, TypeLambda } from "effect/HKT";
|
|
6
6
|
import { Layer } from "effect/Layer";
|
|
7
7
|
import * as Schema from "effect/Schema";
|
|
8
|
-
//#region src/Facts.d.ts
|
|
9
|
-
declare const DESCRIPTION_MODULE: '@systemfsoftware/effect-cell-types';
|
|
10
|
-
declare const IO_CELLS: {
|
|
11
|
-
readonly cells: readonly ['store', 'adapter'];
|
|
12
|
-
readonly sources: readonly ['effect/Clock', 'effect/System'];
|
|
13
|
-
};
|
|
14
|
-
type IoCellClassification = typeof IO_CELLS;
|
|
15
|
-
type PhaseName = 'read' | 'decode' | 'decide' | 'encode' | 'write';
|
|
16
|
-
declare namespace Workflow_d_exports {
|
|
17
|
-
export { Inhabited, SingleVariantDecision, UninhabitedDecision, UninhabitedError, UnsharedTypeId, UntaggedDecision, UntaggedError, Workflow, WorkflowBrand, andThen$1 as andThen, make, total };
|
|
18
|
-
}
|
|
19
|
-
declare const WorkflowTypeId: unique symbol;
|
|
20
|
-
type WorkflowTypeId = typeof WorkflowTypeId;
|
|
21
|
-
interface WorkflowBrand {
|
|
22
|
-
readonly [WorkflowTypeId]: WorkflowTypeId;
|
|
23
|
-
}
|
|
24
|
-
interface UninhabitedDecision {
|
|
25
|
-
readonly __WORKFLOW_DECISION_CHANNEL_IS_NEVER__: 'this workflow can never succeed; give it a decision variant it can return';
|
|
26
|
-
}
|
|
27
|
-
interface UninhabitedError {
|
|
28
|
-
readonly __WORKFLOW_ERROR_CHANNEL_IS_NEVER__: 'this workflow cannot fail, so it decides nothing; give it an error variant or fold the function into its owning module';
|
|
29
|
-
}
|
|
30
|
-
interface UntaggedError {
|
|
31
|
-
readonly __WORKFLOW_ERROR_CHANNEL_CARRIES_NO_TAG__: 'this error carries no _tag the consumer can dispatch on; declare it as an S.TaggedError';
|
|
32
|
-
}
|
|
33
|
-
interface SingleVariantDecision {
|
|
34
|
-
readonly __WORKFLOW_DECISION_CHANNEL_HAS_ONE_VARIANT__: 'this workflow decides one outcome, which is not a decision; add the variant it chooses between, or fold the function into its owning module';
|
|
35
|
-
}
|
|
36
|
-
interface UntaggedDecision {
|
|
37
|
-
readonly __WORKFLOW_DECISION_CHANNEL_CARRIES_NO_TAG__: 'a decision variant carries no _tag the consumer can dispatch on; declare the variants as S.TaggedClass instances';
|
|
38
|
-
}
|
|
39
|
-
interface UnsharedTypeId {
|
|
40
|
-
readonly __WORKFLOW_DECISION_VARIANTS_DO_NOT_SHARE_A_TYPE_ID__: 'the decision variants must share one TypeId — a Symbol.for family brand on each variant class';
|
|
41
|
-
}
|
|
42
|
-
type AtLeastTwoDistinct<T, U = T> = U extends unknown ? [T] extends [U] ? false : true : never;
|
|
43
|
-
type TaggedMembers<D> = D extends unknown ? '_tag' extends keyof D ? [D['_tag']] extends [string] ? true : false : false : never;
|
|
44
|
-
type MutuallyAssignable<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
|
|
45
|
-
type BrandSlotIsTheGeneralSymbol<D, K extends PropertyKey> = D extends unknown ? K extends keyof D ? MutuallyAssignable<D[K], symbol> : false : never;
|
|
46
|
-
type SharedTypeId<D> = [{ [K in keyof D]: [K] extends [symbol] ? ([BrandSlotIsTheGeneralSymbol<D, K>] extends [true] ? K : never) : never; }[keyof D]] extends [never] ? UnsharedTypeId : unknown;
|
|
47
|
-
type DecisionShape<D> = [unknown] extends [D] ? unknown : AtLeastTwoDistinct<D> extends false ? SingleVariantDecision : boolean extends TaggedMembers<D> ? UntaggedDecision : SharedTypeId<D>;
|
|
48
|
-
type Workflow<Command, Decision, DecisionError> = [Decision] extends [never] ? UninhabitedDecision : [DecisionError] extends [never] ? UninhabitedError : ((command: Command) => Result<Decision, DecisionError>) & WorkflowBrand;
|
|
49
|
-
type DispatchableTag<E> = '_tag' extends keyof E ? [E['_tag']] extends [string] ? unknown : UntaggedError : UntaggedError;
|
|
50
|
-
type Inhabited<Decision, DecisionError> = [Decision] extends [never] ? UninhabitedDecision : [DecisionError] extends [never] ? UninhabitedError : DecisionShape<Decision> & DispatchableTag<DecisionError>;
|
|
51
|
-
declare const make: <Self, S extends Schema.Constraint & {
|
|
52
|
-
readonly fields: Schema.Struct.Fields;
|
|
53
|
-
}, Inherited, D, E>(_command: Schema.Class<Self, S, Inherited>, decide: (command: Self) => Result<D, E> & Inhabited<D, E>) => Workflow<Self, D, E>;
|
|
54
|
-
/**
|
|
55
|
-
* Brands a decision that cannot fail. `make` refuses a `never` error channel outright
|
|
56
|
-
* (`UninhabitedError`); this is the door for the decider that genuinely decides everything.
|
|
57
|
-
* The decision must still choose between at least two tagged variants sharing one TypeId, so
|
|
58
|
-
* `SingleVariantDecision`, `UntaggedDecision`, and `UnsharedTypeId` still fire.
|
|
59
|
-
*
|
|
60
|
-
* The command schema class comes first, exactly as in {@link make}, so the command channel
|
|
61
|
-
* stays pinned to the class rather than an inferred annotation. The decider's return carries
|
|
62
|
-
* `DecisionShape` written out: the `Workflow` alias is a deferred conditional, and in
|
|
63
|
-
* parameter position it collapses the whole parameter to `unknown` while the decision
|
|
64
|
-
* channel is still generic.
|
|
65
|
-
*/
|
|
66
|
-
declare const total: <Self, S extends Schema.Constraint & {
|
|
67
|
-
readonly fields: Schema.Struct.Fields;
|
|
68
|
-
}, Inherited, D>(_command: Schema.Class<Self, S, Inherited>, decide: (command: Self) => Result<D, never> & DecisionShape<D>) => ((command: Self) => Result<D, never>) & WorkflowBrand;
|
|
69
|
-
/**
|
|
70
|
-
* Composes two workflows: what the upstream decides becomes the command the downstream decides
|
|
71
|
-
* on, and a refusal short-circuits the pair. The return type dispatches on the component error
|
|
72
|
-
* union — two components that cannot fail publish the total form, because the `Workflow` alias
|
|
73
|
-
* refuses a `never` channel; a carried error publishes the union as before.
|
|
74
|
-
*/
|
|
75
|
-
declare const andThen$1: <Ctx, SelfA, SA extends Schema.Constraint & {
|
|
76
|
-
readonly fields: Schema.Struct.Fields;
|
|
77
|
-
}, InheritedA, D1, E1, SelfB extends {
|
|
78
|
-
readonly decision: D1;
|
|
79
|
-
readonly ctx: Ctx;
|
|
80
|
-
}, D2, E2>(_commandA: Schema.Class<SelfA, SA, InheritedA>, upstream: ((command: SelfA) => Result<D1, E1>) & WorkflowBrand, commandB: {
|
|
81
|
-
new (props: {
|
|
82
|
-
readonly decision: D1;
|
|
83
|
-
readonly ctx: Ctx;
|
|
84
|
-
}): SelfB;
|
|
85
|
-
}, ctx: NoInfer<Ctx>, downstream: ((command: SelfB) => Result<D2, E2>) & WorkflowBrand) => [E1 | E2] extends [never] ? ((command: SelfA) => Result<D2, never>) & WorkflowBrand : Workflow<SelfA, D2, E1 | E2>;
|
|
86
8
|
declare namespace Cell_d_exports {
|
|
87
|
-
export { Cell, CellTypeId,
|
|
9
|
+
export { Cell, CellTypeId, Kind$1 as Kind, Run, TypeLambda$1 as TypeLambda, andThen$1 as andThen, collect, collectAll, gate, map, mapInput, provide, zip };
|
|
88
10
|
}
|
|
89
11
|
/**
|
|
90
|
-
* The nominal brand every `Cell` carries. `
|
|
12
|
+
* The nominal brand every `Cell` carries. The `Sandwich` chain's `write` is the only door that applies it.
|
|
91
13
|
*/
|
|
92
14
|
declare const CellTypeId: unique symbol;
|
|
93
15
|
/**
|
|
@@ -120,48 +42,6 @@ type Kind$1<I, E, R, A> = Kind<TypeLambda$1, I, E, R, A>;
|
|
|
120
42
|
* `Run<I, A, E, R>` is `Cell<I, A, E, R>['run']`.
|
|
121
43
|
*/
|
|
122
44
|
type Run<I, A, E, R> = Cell<I, A, E, R>['run'];
|
|
123
|
-
interface LayerCore<I, Raw, RE, RR, Dec, DE, Resp, WE, WR> {
|
|
124
|
-
readonly read: (command: I) => Effect.Effect<Raw, RE, RR>;
|
|
125
|
-
readonly decide: ((decoded: Raw) => Result$1.Result<Dec, DE>) & WorkflowBrand;
|
|
126
|
-
readonly write: (output: Result$1.Result<Dec, DE>, raw: Raw) => Effect.Effect<Resp, WE, WR>;
|
|
127
|
-
}
|
|
128
|
-
interface LayerShortSpec<I, Raw, RE, RR, Dec, DE, Resp, WE, WR> extends LayerCore<I, Raw, RE, RR, Dec, DE, Resp, WE, WR> {
|
|
129
|
-
readonly decode?: never;
|
|
130
|
-
readonly encode?: never;
|
|
131
|
-
}
|
|
132
|
-
interface LayerLongSpec<I, Raw, RE, RR, Dcd, DecE, Dec, DE, Out, Resp, WE, WR> extends Omit<LayerCore<I, Raw, RE, RR, Dec, DE, Resp, WE, WR>, 'decide' | 'write'> {
|
|
133
|
-
readonly decode: (raw: Raw) => Result$1.Result<Dcd, DecE>;
|
|
134
|
-
readonly decide: ((decoded: Dcd) => Result$1.Result<Dec, DE>) & WorkflowBrand;
|
|
135
|
-
readonly encode: (outcome: Result$1.Result<Dec, DE>) => Out;
|
|
136
|
-
readonly write: (output: Out, raw: Raw) => Effect.Effect<Resp, WE, WR>;
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Builds a Cell from one sandwich.
|
|
140
|
-
*
|
|
141
|
-
* Short form — `read` produces the value `decide` rules on, and the decide outcome is what
|
|
142
|
-
* `write` receives:
|
|
143
|
-
*
|
|
144
|
-
* ```ts
|
|
145
|
-
* import { Cell, Workflow } from '@systemfsoftware/effect-cell-types'
|
|
146
|
-
* import { Effect, Result } from 'effect'
|
|
147
|
-
*
|
|
148
|
-
* declare const decideAdmission: Workflow<CliArgs, Verdict, Refusal>
|
|
149
|
-
* declare class CliArgs { readonly target: string }
|
|
150
|
-
* declare class Verdict { readonly ok: boolean }
|
|
151
|
-
* declare class Refusal { readonly _tag: 'Refused' }
|
|
152
|
-
*
|
|
153
|
-
* const cell = Cell.layer({
|
|
154
|
-
* read: (args: CliArgs) => Effect.succeed(args),
|
|
155
|
-
* decide: decideAdmission,
|
|
156
|
-
* write: (outcome: Result.Result<Verdict, Refusal>, raw: CliArgs) => Effect.void,
|
|
157
|
-
* })
|
|
158
|
-
* ```
|
|
159
|
-
*
|
|
160
|
-
* Long form — `decode` and `encode` adapt each side of `decide`; both are required together,
|
|
161
|
-
* and a spec carrying one without the other fails inference.
|
|
162
|
-
*/
|
|
163
|
-
declare function layer<I, Raw, RE, RR, Dec, DE, Resp, WE, WR>(spec: LayerShortSpec<I, Raw, RE, RR, Dec, DE, Resp, WE, WR>): Cell<I, Resp, RE | WE, RR | WR>;
|
|
164
|
-
declare function layer<I, Raw, RE, RR, Dcd, DecE, Dec, DE, Out, Resp, WE, WR>(spec: LayerLongSpec<I, Raw, RE, RR, Dcd, DecE, Dec, DE, Out, Resp, WE, WR>): Cell<I, Resp, RE | DecE | WE, RR | WR>;
|
|
165
45
|
/**
|
|
166
46
|
* Transforms the Cell's response.
|
|
167
47
|
*/
|
|
@@ -180,7 +60,7 @@ declare const mapInput: {
|
|
|
180
60
|
* Feeds this Cell's response to the next Cell as its input. The error and service channels
|
|
181
61
|
* union.
|
|
182
62
|
*/
|
|
183
|
-
declare const andThen: {
|
|
63
|
+
declare const andThen$1: {
|
|
184
64
|
<B, E2, R2>(that: Cell<never, B, E2, R2>): <I, A, E, R>(self: Cell<I, A, E, R>) => Cell<I, B, E | E2, R | R2>;
|
|
185
65
|
<I, A, E, R, B, E2, R2>(self: Cell<I, A, E, R>, that: Cell<A, B, E2, R2>): Cell<I, B, E | E2, R | R2>;
|
|
186
66
|
};
|
|
@@ -226,19 +106,110 @@ declare const provide: {
|
|
|
226
106
|
<RIn, LE, ROut>(layer: Layer<ROut, LE, RIn>): <I, A, E, R>(self: Cell<I, A, E, R>) => Cell<I, A, E | LE, RIn | Exclude<R, ROut>>;
|
|
227
107
|
<I, A, E, R, RIn, LE, ROut>(self: Cell<I, A, E, R>, layer: Layer<ROut, LE, RIn>): Cell<I, A, E | LE, RIn | Exclude<R, ROut>>;
|
|
228
108
|
};
|
|
109
|
+
declare namespace Workflow_d_exports {
|
|
110
|
+
export { Inhabited, SingleVariantDecision, UninhabitedDecision, UninhabitedError, UnsharedTypeId, UntaggedDecision, UntaggedError, Workflow, WorkflowBrand, andThen, make, total };
|
|
111
|
+
}
|
|
112
|
+
declare const WorkflowTypeId: unique symbol;
|
|
113
|
+
type WorkflowTypeId = typeof WorkflowTypeId;
|
|
114
|
+
interface WorkflowBrand {
|
|
115
|
+
readonly [WorkflowTypeId]: WorkflowTypeId;
|
|
116
|
+
}
|
|
117
|
+
interface UninhabitedDecision {
|
|
118
|
+
readonly __WORKFLOW_DECISION_CHANNEL_IS_NEVER__: 'this workflow can never succeed; give it a decision variant it can return';
|
|
119
|
+
}
|
|
120
|
+
interface UninhabitedError {
|
|
121
|
+
readonly __WORKFLOW_ERROR_CHANNEL_IS_NEVER__: 'this workflow cannot fail, so it decides nothing; give it an error variant or fold the function into its owning module';
|
|
122
|
+
}
|
|
123
|
+
interface UntaggedError {
|
|
124
|
+
readonly __WORKFLOW_ERROR_CHANNEL_CARRIES_NO_TAG__: 'this error carries no _tag the consumer can dispatch on; declare it as an S.TaggedError';
|
|
125
|
+
}
|
|
126
|
+
interface SingleVariantDecision {
|
|
127
|
+
readonly __WORKFLOW_DECISION_CHANNEL_HAS_ONE_VARIANT__: 'this workflow decides one outcome, which is not a decision; add the variant it chooses between, or fold the function into its owning module';
|
|
128
|
+
}
|
|
129
|
+
interface UntaggedDecision {
|
|
130
|
+
readonly __WORKFLOW_DECISION_CHANNEL_CARRIES_NO_TAG__: 'a decision variant carries no _tag the consumer can dispatch on; declare the variants as S.TaggedClass instances';
|
|
131
|
+
}
|
|
132
|
+
interface UnsharedTypeId {
|
|
133
|
+
readonly __WORKFLOW_DECISION_VARIANTS_DO_NOT_SHARE_A_TYPE_ID__: 'the decision variants must share one TypeId — a Symbol.for family brand on each variant class';
|
|
134
|
+
}
|
|
135
|
+
type AtLeastTwoDistinct<T, U = T> = U extends unknown ? [T] extends [U] ? false : true : never;
|
|
136
|
+
type TaggedMembers<D> = D extends unknown ? '_tag' extends keyof D ? [D['_tag']] extends [string] ? true : false : false : never;
|
|
137
|
+
type MutuallyAssignable<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
|
|
138
|
+
type BrandSlotIsTheGeneralSymbol<D, K extends PropertyKey> = D extends unknown ? K extends keyof D ? MutuallyAssignable<D[K], symbol> : false : never;
|
|
139
|
+
type SharedTypeId<D> = [{ [K in keyof D]: [K] extends [symbol] ? ([BrandSlotIsTheGeneralSymbol<D, K>] extends [true] ? K : never) : never; }[keyof D]] extends [never] ? UnsharedTypeId : unknown;
|
|
140
|
+
type DecisionShape<D> = [unknown] extends [D] ? unknown : AtLeastTwoDistinct<D> extends false ? SingleVariantDecision : boolean extends TaggedMembers<D> ? UntaggedDecision : SharedTypeId<D>;
|
|
141
|
+
type Workflow<Command, Decision, DecisionError> = [Decision] extends [never] ? UninhabitedDecision : [DecisionError] extends [never] ? UninhabitedError : ((command: Command) => Result<Decision, DecisionError>) & WorkflowBrand;
|
|
142
|
+
type DispatchableTag<E> = '_tag' extends keyof E ? [E['_tag']] extends [string] ? unknown : UntaggedError : UntaggedError;
|
|
143
|
+
type Inhabited<Decision, DecisionError> = [Decision] extends [never] ? UninhabitedDecision : [DecisionError] extends [never] ? UninhabitedError : DecisionShape<Decision> & DispatchableTag<DecisionError>;
|
|
144
|
+
declare const make: <Self, S extends Schema.Constraint & {
|
|
145
|
+
readonly fields: Schema.Struct.Fields;
|
|
146
|
+
}, Inherited, D, E>(_command: Schema.Class<Self, S, Inherited>, decide: (command: Self) => Result<D, E> & Inhabited<D, E>) => Workflow<Self, D, E>;
|
|
229
147
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
148
|
+
* Brands a decision that cannot fail. `make` refuses a `never` error channel outright
|
|
149
|
+
* (`UninhabitedError`); this is the door for the decider that genuinely decides everything.
|
|
150
|
+
* The decision must still choose between at least two tagged variants sharing one TypeId, so
|
|
151
|
+
* `SingleVariantDecision`, `UntaggedDecision`, and `UnsharedTypeId` still fire.
|
|
152
|
+
*
|
|
153
|
+
* The command schema class comes first, exactly as in {@link make}, so the command channel
|
|
154
|
+
* stays pinned to the class rather than an inferred annotation. The decider's return carries
|
|
155
|
+
* `DecisionShape` written out: the `Workflow` alias is a deferred conditional, and in
|
|
156
|
+
* parameter position it collapses the whole parameter to `unknown` while the decision
|
|
157
|
+
* channel is still generic.
|
|
158
|
+
*/
|
|
159
|
+
declare const total: <Self, S extends Schema.Constraint & {
|
|
160
|
+
readonly fields: Schema.Struct.Fields;
|
|
161
|
+
}, Inherited, D>(_command: Schema.Class<Self, S, Inherited>, decide: (command: Self) => Result<D, never> & DecisionShape<D>) => ((command: Self) => Result<D, never>) & WorkflowBrand;
|
|
162
|
+
/**
|
|
163
|
+
* Composes two workflows: what the upstream decides becomes the command the downstream decides
|
|
164
|
+
* on, and a refusal short-circuits the pair. The return type dispatches on the component error
|
|
165
|
+
* union — two components that cannot fail publish the total form, because the `Workflow` alias
|
|
166
|
+
* refuses a `never` channel; a carried error publishes the union as before.
|
|
233
167
|
*/
|
|
234
|
-
|
|
235
|
-
readonly
|
|
236
|
-
|
|
237
|
-
readonly
|
|
238
|
-
|
|
168
|
+
declare const andThen: <Ctx, SelfA, SA extends Schema.Constraint & {
|
|
169
|
+
readonly fields: Schema.Struct.Fields;
|
|
170
|
+
}, InheritedA, D1, E1, SelfB extends {
|
|
171
|
+
readonly decision: D1;
|
|
172
|
+
readonly ctx: Ctx;
|
|
173
|
+
}, D2, E2>(_commandA: Schema.Class<SelfA, SA, InheritedA>, upstream: ((command: SelfA) => Result<D1, E1>) & WorkflowBrand, commandB: {
|
|
174
|
+
new (props: {
|
|
175
|
+
readonly decision: D1;
|
|
176
|
+
readonly ctx: Ctx;
|
|
177
|
+
}): SelfB;
|
|
178
|
+
}, ctx: NoInfer<Ctx>, downstream: ((command: SelfB) => Result<D2, E2>) & WorkflowBrand) => [E1 | E2] extends [never] ? ((command: SelfA) => Result<D2, never>) & WorkflowBrand : Workflow<SelfA, D2, E1 | E2>;
|
|
179
|
+
declare namespace Sandwich_d_exports {
|
|
180
|
+
export { DecodedChain, DecodedDecidedChain, EncodedChain, PurePhase, RawDecidedChain, ReadChain, pure, read };
|
|
181
|
+
}
|
|
182
|
+
declare const PurePhaseBrand: unique symbol;
|
|
183
|
+
type PurePhaseBrand = typeof PurePhaseBrand;
|
|
184
|
+
type PurePhase<In, Out, E = never> = ((input: In) => Result$1.Result<Out, E>) & {
|
|
185
|
+
readonly [PurePhaseBrand]: true;
|
|
186
|
+
};
|
|
187
|
+
declare const pure: <In, Out, E = never>(fn: (input: In) => Result$1.Result<Out, E>) => PurePhase<In, Out, E>;
|
|
188
|
+
interface ReadChain<I, Raw, RE, RR> {
|
|
189
|
+
readonly 'sentence: must decode or decide after read': true;
|
|
190
|
+
decode<Dcd, DecE>(phase: PurePhase<Raw, Dcd, DecE>): DecodedChain<I, Raw, Dcd, RE, DecE, RR>;
|
|
191
|
+
decide<Dec, DE>(workflow: ((decoded: Raw) => Result$1.Result<Dec, DE>) & WorkflowBrand): RawDecidedChain<I, Raw, Dec, DE, RE, RR>;
|
|
192
|
+
}
|
|
193
|
+
interface DecodedChain<I, Raw, Dcd, RE, DecE, RR> {
|
|
194
|
+
readonly 'sentence: must decide after decode': true;
|
|
195
|
+
decide<Dec, DE>(workflow: ((decoded: Dcd) => Result$1.Result<Dec, DE>) & WorkflowBrand): DecodedDecidedChain<I, Raw, Dec, DE, RE, DecE, RR>;
|
|
196
|
+
}
|
|
197
|
+
interface RawDecidedChain<I, Raw, Dec, DE, RE, RR> {
|
|
198
|
+
readonly 'sentence: must write after decide on raw chain': true;
|
|
199
|
+
write<Resp, WE, WR>(run: (output: Result$1.Result<Dec, DE>, raw: Raw) => Effect.Effect<Resp, WE, WR>): Cell<I, Resp, RE | WE, RR | WR> & {
|
|
200
|
+
readonly phases: readonly ['read', 'decide', 'write'];
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
interface DecodedDecidedChain<I, Raw, Dec, DE, RE, DecE, RR> {
|
|
204
|
+
readonly 'sentence: must encode after decide on decoded chain': true;
|
|
205
|
+
encode<Out>(phase: PurePhase<Result$1.Result<Dec, DE>, Out, never>): EncodedChain<I, Raw, Out, RE, DecE, RR>;
|
|
206
|
+
}
|
|
207
|
+
interface EncodedChain<I, Raw, Out, RE, DecE, RR> {
|
|
208
|
+
readonly 'sentence: must write after encode': true;
|
|
209
|
+
write<Resp, WE, WR>(run: (output: Out, raw: Raw) => Effect.Effect<Resp, WE, WR>): Cell<I, Resp, RE | DecE | WE, RR | WR> & {
|
|
210
|
+
readonly phases: readonly ['read', 'decode', 'decide', 'encode', 'write'];
|
|
239
211
|
};
|
|
240
|
-
readonly composer: 'layer';
|
|
241
212
|
}
|
|
242
|
-
declare const
|
|
213
|
+
declare const read: <I, Raw, RE, RR>(run: (command: I) => Effect.Effect<Raw, RE, RR>) => ReadChain<I, Raw, RE, RR>;
|
|
243
214
|
//#endregion
|
|
244
|
-
export { Cell_d_exports as Cell, Workflow_d_exports as Workflow };
|
|
215
|
+
export { Cell_d_exports as Cell, Sandwich_d_exports as Sandwich, Workflow_d_exports as Workflow };
|
package/dist/index.mjs
CHANGED
|
@@ -4,122 +4,49 @@ import { dual } from "effect/Function";
|
|
|
4
4
|
import * as Option from "effect/Option";
|
|
5
5
|
import * as Result$1 from "effect/Result";
|
|
6
6
|
import { flatMap } from "effect/Result";
|
|
7
|
-
//#region src/Facts.ts
|
|
8
|
-
const DESCRIPTION_MODULE = "@systemfsoftware/effect-cell-types";
|
|
9
|
-
const IO_CELLS = {
|
|
10
|
-
cells: ["store", "adapter"],
|
|
11
|
-
sources: ["effect/Clock", "effect/System"]
|
|
12
|
-
};
|
|
13
|
-
//#endregion
|
|
14
|
-
//#region src/Workflow.ts
|
|
15
|
-
var Workflow_exports = /* @__PURE__ */ __exportAll({
|
|
16
|
-
andThen: () => andThen$1,
|
|
17
|
-
make: () => make$1,
|
|
18
|
-
total: () => total
|
|
19
|
-
});
|
|
20
|
-
const make$1 = (_command, decide) => {
|
|
21
|
-
return decide;
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Brands a decision that cannot fail. `make` refuses a `never` error channel outright
|
|
25
|
-
* (`UninhabitedError`); this is the door for the decider that genuinely decides everything.
|
|
26
|
-
* The decision must still choose between at least two tagged variants sharing one TypeId, so
|
|
27
|
-
* `SingleVariantDecision`, `UntaggedDecision`, and `UnsharedTypeId` still fire.
|
|
28
|
-
*
|
|
29
|
-
* The command schema class comes first, exactly as in {@link make}, so the command channel
|
|
30
|
-
* stays pinned to the class rather than an inferred annotation. The decider's return carries
|
|
31
|
-
* `DecisionShape` written out: the `Workflow` alias is a deferred conditional, and in
|
|
32
|
-
* parameter position it collapses the whole parameter to `unknown` while the decision
|
|
33
|
-
* channel is still generic.
|
|
34
|
-
*/
|
|
35
|
-
const total = (_command, decide) => {
|
|
36
|
-
return decide;
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* Composes two workflows: what the upstream decides becomes the command the downstream decides
|
|
40
|
-
* on, and a refusal short-circuits the pair. The return type dispatches on the component error
|
|
41
|
-
* union — two components that cannot fail publish the total form, because the `Workflow` alias
|
|
42
|
-
* refuses a `never` channel; a carried error publishes the union as before.
|
|
43
|
-
*/
|
|
44
|
-
const andThen$1 = (_commandA, upstream, commandB, ctx, downstream) => {
|
|
45
|
-
const composed = (command) => flatMap(upstream(command), (decision) => downstream(new commandB({
|
|
46
|
-
decision,
|
|
47
|
-
ctx
|
|
48
|
-
})));
|
|
49
|
-
return composed;
|
|
50
|
-
};
|
|
51
|
-
//#endregion
|
|
52
7
|
//#region src/Cell.ts
|
|
53
8
|
var Cell_exports = /* @__PURE__ */ __exportAll({
|
|
54
9
|
CellTypeId: () => CellTypeId,
|
|
55
|
-
|
|
56
|
-
IO_CELLS: () => IO_CELLS,
|
|
57
|
-
andThen: () => andThen,
|
|
10
|
+
andThen: () => andThen$1,
|
|
58
11
|
collect: () => collect,
|
|
59
12
|
collectAll: () => collectAll,
|
|
60
13
|
gate: () => gate,
|
|
61
|
-
layer: () => layer,
|
|
62
14
|
map: () => map,
|
|
63
15
|
mapInput: () => mapInput,
|
|
64
16
|
provide: () => provide,
|
|
65
|
-
vocabulary: () => vocabulary,
|
|
66
17
|
zip: () => zip
|
|
67
18
|
});
|
|
68
19
|
/**
|
|
69
|
-
* The nominal brand every `Cell` carries. `
|
|
20
|
+
* The nominal brand every `Cell` carries. The `Sandwich` chain's `write` is the only door that applies it.
|
|
70
21
|
*/
|
|
71
22
|
const CellTypeId = Symbol.for("@systemfsoftware/effect-cell-types/Cell");
|
|
72
|
-
const make = (run) => ({
|
|
23
|
+
const make$1 = (run) => ({
|
|
73
24
|
[CellTypeId]: CellTypeId,
|
|
74
25
|
run
|
|
75
26
|
});
|
|
76
27
|
/**
|
|
77
|
-
* The interpreter. Order is the text: read, then decode, then decide, then encode, then
|
|
78
|
-
* write. The `E` channel is the sandwich's truth — read, decode, and write failures;
|
|
79
|
-
* a decide refusal is the outcome the encode and write receive, not a failure.
|
|
80
|
-
*/
|
|
81
|
-
const layerRunner = (spec) => {
|
|
82
|
-
if ("decode" in spec && "encode" in spec) return (input) => Effect.gen(function* () {
|
|
83
|
-
const raw = yield* spec.read(input);
|
|
84
|
-
const decoded = yield* Result$1.match(spec.decode(raw), {
|
|
85
|
-
onFailure: Effect.fail,
|
|
86
|
-
onSuccess: Effect.succeed
|
|
87
|
-
});
|
|
88
|
-
const outcome = spec.decide(decoded);
|
|
89
|
-
return yield* spec.write(spec.encode(outcome), raw);
|
|
90
|
-
});
|
|
91
|
-
return (input) => Effect.gen(function* () {
|
|
92
|
-
const raw = yield* spec.read(input);
|
|
93
|
-
const outcome = spec.decide(raw);
|
|
94
|
-
return yield* spec.write(outcome, raw);
|
|
95
|
-
});
|
|
96
|
-
};
|
|
97
|
-
function layer(spec) {
|
|
98
|
-
return make(layerRunner(spec));
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
28
|
* Transforms the Cell's response.
|
|
102
29
|
*/
|
|
103
|
-
const map = dual(2, (self, f) => make((input) => Effect.map(self.run(input), f)));
|
|
30
|
+
const map = dual(2, (self, f) => make$1((input) => Effect.map(self.run(input), f)));
|
|
104
31
|
/**
|
|
105
32
|
* Transforms the Cell's input.
|
|
106
33
|
*/
|
|
107
|
-
const mapInput = dual(2, (self, f) => make((input) => self.run(f(input))));
|
|
34
|
+
const mapInput = dual(2, (self, f) => make$1((input) => self.run(f(input))));
|
|
108
35
|
/**
|
|
109
36
|
* Feeds this Cell's response to the next Cell as its input. The error and service channels
|
|
110
37
|
* union.
|
|
111
38
|
*/
|
|
112
|
-
const andThen = dual(2, (self, that) => make((input) => Effect.flatMap(self.run(input), (response) => that.run(response))));
|
|
39
|
+
const andThen$1 = dual(2, (self, that) => make$1((input) => Effect.flatMap(self.run(input), (response) => that.run(response))));
|
|
113
40
|
/**
|
|
114
41
|
* Runs both Cells against the same input and tuples the responses. Fails fast: when one
|
|
115
42
|
* side refuses, the other's write never runs.
|
|
116
43
|
*/
|
|
117
|
-
const zip = dual(2, (self, that) => make((input) => Effect.zipWith(self.run(input), that.run(input), (a, b) => [a, b])));
|
|
44
|
+
const zip = dual(2, (self, that) => make$1((input) => Effect.zipWith(self.run(input), that.run(input), (a, b) => [a, b])));
|
|
118
45
|
/**
|
|
119
46
|
* Runs the inner Cell on the value this Cell read, yielding `Option.none` when it read none.
|
|
120
47
|
* A skip is an absence, never a refusal; the error and service channels union.
|
|
121
48
|
*/
|
|
122
|
-
const gate = dual(2, (self, inner) => make((input) => Effect.flatMap(self.run(input), (read) => Option.match(read, {
|
|
49
|
+
const gate = dual(2, (self, inner) => make$1((input) => Effect.flatMap(self.run(input), (read) => Option.match(read, {
|
|
123
50
|
onNone: () => Effect.succeed(Option.none()),
|
|
124
51
|
onSome: (raw) => Effect.map(inner.run(raw), Option.some)
|
|
125
52
|
}))));
|
|
@@ -127,28 +54,131 @@ const gate = dual(2, (self, inner) => make((input) => Effect.flatMap(self.run(in
|
|
|
127
54
|
* Runs the Cell once per item, in order, and folds the responses into one value. The error
|
|
128
55
|
* and service channels are unchanged; the first refusal ends the run.
|
|
129
56
|
*/
|
|
130
|
-
const collect = dual(2, (self, fold) => make((items) => Effect.map(Effect.forEach(items, (item) => self.run(item)), (responses) => fold(responses))));
|
|
57
|
+
const collect = dual(2, (self, fold) => make$1((items) => Effect.map(Effect.forEach(items, (item) => self.run(item)), (responses) => fold(responses))));
|
|
131
58
|
/**
|
|
132
59
|
* Runs the Cell once per item, in order, and folds every outcome — each a `Result.Result` —
|
|
133
60
|
* into one value. Unlike {@link collect}, no refusal ends the run: every item is attempted
|
|
134
61
|
* and its failure travels to the fold.
|
|
135
62
|
*/
|
|
136
|
-
const collectAll = dual(2, (self, fold) => make((items) => Effect.map(Effect.forEach(items, (item) => Effect.result(self.run(item))), (results) => fold(results))));
|
|
63
|
+
const collectAll = dual(2, (self, fold) => make$1((items) => Effect.map(Effect.forEach(items, (item) => Effect.result(self.run(item))), (results) => fold(results))));
|
|
137
64
|
/**
|
|
138
65
|
* Provides a Layer to the Cell, eliminating the services the layer builds from `R`. This is
|
|
139
66
|
* the one composition-root elimination; the resulting Cell still demands the layer's input
|
|
140
67
|
* services. A missing provide is a compile error at the run site.
|
|
141
68
|
*/
|
|
142
|
-
const provide = dual(2, (self, layer) => make((input) => Effect.provide(self.run(input), layer)));
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
69
|
+
const provide = dual(2, (self, layer) => make$1((input) => Effect.provide(self.run(input), layer)));
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/Workflow.ts
|
|
72
|
+
var Workflow_exports = /* @__PURE__ */ __exportAll({
|
|
73
|
+
andThen: () => andThen,
|
|
74
|
+
make: () => make,
|
|
75
|
+
total: () => total
|
|
76
|
+
});
|
|
77
|
+
const make = (_command, decide) => {
|
|
78
|
+
return decide;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Brands a decision that cannot fail. `make` refuses a `never` error channel outright
|
|
82
|
+
* (`UninhabitedError`); this is the door for the decider that genuinely decides everything.
|
|
83
|
+
* The decision must still choose between at least two tagged variants sharing one TypeId, so
|
|
84
|
+
* `SingleVariantDecision`, `UntaggedDecision`, and `UnsharedTypeId` still fire.
|
|
85
|
+
*
|
|
86
|
+
* The command schema class comes first, exactly as in {@link make}, so the command channel
|
|
87
|
+
* stays pinned to the class rather than an inferred annotation. The decider's return carries
|
|
88
|
+
* `DecisionShape` written out: the `Workflow` alias is a deferred conditional, and in
|
|
89
|
+
* parameter position it collapses the whole parameter to `unknown` while the decision
|
|
90
|
+
* channel is still generic.
|
|
91
|
+
*/
|
|
92
|
+
const total = (_command, decide) => {
|
|
93
|
+
return decide;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Composes two workflows: what the upstream decides becomes the command the downstream decides
|
|
97
|
+
* on, and a refusal short-circuits the pair. The return type dispatches on the component error
|
|
98
|
+
* union — two components that cannot fail publish the total form, because the `Workflow` alias
|
|
99
|
+
* refuses a `never` channel; a carried error publishes the union as before.
|
|
100
|
+
*/
|
|
101
|
+
const andThen = (_commandA, upstream, commandB, ctx, downstream) => {
|
|
102
|
+
const composed = (command) => flatMap(upstream(command), (decision) => downstream(new commandB({
|
|
103
|
+
decision,
|
|
104
|
+
ctx
|
|
105
|
+
})));
|
|
106
|
+
return composed;
|
|
107
|
+
};
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/Sandwich.ts
|
|
110
|
+
var Sandwich_exports = /* @__PURE__ */ __exportAll({
|
|
111
|
+
pure: () => pure,
|
|
112
|
+
read: () => read
|
|
113
|
+
});
|
|
114
|
+
const PurePhaseBrand = Symbol.for("@systemfsoftware/effect-cell-types/PurePhase");
|
|
115
|
+
const pure = (fn) => Object.assign(fn, { [PurePhaseBrand]: true });
|
|
116
|
+
const read = (run) => {
|
|
117
|
+
const decode = (phase) => {
|
|
118
|
+
const decide = (workflow) => {
|
|
119
|
+
const encode = (encodePhase) => {
|
|
120
|
+
const write = (writeRun) => {
|
|
121
|
+
const composed = (input) => Effect.gen(function* () {
|
|
122
|
+
const raw = yield* run(input);
|
|
123
|
+
const outcome = workflow(yield* Result$1.match(phase(raw), {
|
|
124
|
+
onFailure: Effect.fail,
|
|
125
|
+
onSuccess: Effect.succeed
|
|
126
|
+
}));
|
|
127
|
+
return yield* writeRun(Result$1.getOrThrow(encodePhase(outcome)), raw);
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
[CellTypeId]: CellTypeId,
|
|
131
|
+
run: composed,
|
|
132
|
+
phases: [
|
|
133
|
+
"read",
|
|
134
|
+
"decode",
|
|
135
|
+
"decide",
|
|
136
|
+
"encode",
|
|
137
|
+
"write"
|
|
138
|
+
]
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
"sentence: must write after encode": true,
|
|
143
|
+
write
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
"sentence: must encode after decide on decoded chain": true,
|
|
148
|
+
encode
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
"sentence: must decide after decode": true,
|
|
153
|
+
decide
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
const decide = (workflow) => {
|
|
157
|
+
const write = (writeRun) => {
|
|
158
|
+
const composed = (input) => Effect.gen(function* () {
|
|
159
|
+
const raw = yield* run(input);
|
|
160
|
+
return yield* writeRun(workflow(raw), raw);
|
|
161
|
+
});
|
|
162
|
+
return {
|
|
163
|
+
[CellTypeId]: CellTypeId,
|
|
164
|
+
run: composed,
|
|
165
|
+
phases: [
|
|
166
|
+
"read",
|
|
167
|
+
"decide",
|
|
168
|
+
"write"
|
|
169
|
+
]
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
return {
|
|
173
|
+
"sentence: must write after decide on raw chain": true,
|
|
174
|
+
write
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
"sentence: must decode or decide after read": true,
|
|
179
|
+
decode,
|
|
180
|
+
decide
|
|
181
|
+
};
|
|
152
182
|
};
|
|
153
183
|
//#endregion
|
|
154
|
-
export { Cell_exports as Cell, Workflow_exports as Workflow };
|
|
184
|
+
export { Cell_exports as Cell, Sandwich_exports as Sandwich, Workflow_exports as Workflow };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/effect-cell-types",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.2.0",
|
|
5
5
|
"author": "Ryan Lee <drdgvhbh@gmail.com>",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@effect/vitest": "4.0.0-rc.112",
|
|
36
36
|
"@microsoft/api-extractor": "^7.58.7",
|
|
37
|
-
"@systemfsoftware/arethetypeswrong-cli": "^4.
|
|
37
|
+
"@systemfsoftware/arethetypeswrong-cli": "^4.2.0",
|
|
38
38
|
"@types/node": "^24",
|
|
39
39
|
"effect": "4.0.0-rc.112",
|
|
40
40
|
"oxlint": "^1.77.0",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"tsdown": "^0.22.14",
|
|
43
43
|
"tstyche": "^7.1.0",
|
|
44
44
|
"vitest": "^4",
|
|
45
|
-
"@systemfsoftware/effect-gherkin-spec": "4.0.2",
|
|
46
45
|
"@systemfsoftware/oxlint-config": "^0.1.0",
|
|
46
|
+
"@systemfsoftware/effect-gherkin-spec": "4.0.2",
|
|
47
47
|
"@systemfsoftware/tsconfig": "^1.3.4",
|
|
48
48
|
"@systemfsoftware/vitest-config": "^0.1.0"
|
|
49
49
|
},
|