@systemfsoftware/effect-cell-types 3.0.0 → 4.0.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 +23 -0
- package/README.md +38 -27
- package/dist/index.d.ts +72 -17
- package/dist/index.mjs +42 -11
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @systemfsoftware/effect-cell-types
|
|
2
2
|
|
|
3
|
+
## 4.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- `Workflow.make` now takes two arguments: the command's schema class first, the decider second.
|
|
8
|
+
|
|
9
|
+
To migrate, declare the command as a `Schema.Class` or `Schema.TaggedClass` and pass it first. The decider's parameter type is inferred from that class, so its annotation can be dropped. A plain interface, a type alias, an object literal, a `Schema.Struct` and a primitive are all refused at the command position — an interface produces no value to pass, and the others are not schema classes.
|
|
10
|
+
|
|
11
|
+
The decider's own contract is unchanged: it still returns a `Result`, and a decision channel of `never`, an error channel of `never`, or an error channel carrying no tag are refused as before.
|
|
12
|
+
|
|
13
|
+
- `Tagged` no longer ships in the `Workflow` namespace.
|
|
14
|
+
|
|
15
|
+
The requirement it expressed is unchanged: a decision error must still carry a tag the consumer can
|
|
16
|
+
dispatch on, and `UntaggedError` still names the failure when one does not. Replace an annotation
|
|
17
|
+
that referred to `Workflow.Tagged` with the concrete error type, or with a `Schema.TaggedError`
|
|
18
|
+
class, which satisfies the channel directly.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- The peer requirements for `effect` and for the Effect test-runner integration now accept any compatible `4.0.0-rc` release, instead of demanding one exact release candidate.
|
|
23
|
+
|
|
24
|
+
Installing alongside a newer release candidate no longer reports an unmet peer dependency or resolves a second copy of `effect` into the dependency tree.
|
|
25
|
+
|
|
3
26
|
## 3.0.0
|
|
4
27
|
|
|
5
28
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -23,22 +23,28 @@ can satisfy, so the mistake is a compile error with the remediation attached (be
|
|
|
23
23
|
|
|
24
24
|
## The constructor
|
|
25
25
|
|
|
26
|
-
Executors build a workflow from a
|
|
26
|
+
Executors build a workflow from the command's schema class and a decider over that class — runtime identity, one assertion across the branded return:
|
|
27
27
|
|
|
28
28
|
```ts
|
|
29
29
|
import { make } from '@systemfsoftware/effect-cell-types'
|
|
30
30
|
import { Result } from 'effect'
|
|
31
|
+
import * as S from 'effect/Schema'
|
|
32
|
+
|
|
33
|
+
export class DecideInput extends S.Class<DecideInput>('DecideInput')({
|
|
34
|
+
exitSuccess: S.Boolean,
|
|
35
|
+
}) {}
|
|
31
36
|
|
|
32
|
-
export const decide = make
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
) => (input.exitSuccess
|
|
37
|
+
export const decide = make(
|
|
38
|
+
DecideInput,
|
|
39
|
+
(input) => (input.exitSuccess
|
|
36
40
|
? Result.succeed(new RestartDecisionContinue())
|
|
37
41
|
: Result.fail(new RestartDecisionExhausted())),
|
|
38
42
|
)
|
|
39
43
|
```
|
|
40
44
|
|
|
41
|
-
The
|
|
45
|
+
The command is constrained on the **value**, not on a type parameter inferred from the decider's parameter, and that is the whole mechanism. A constraint on such a parameter is a structural predicate, and TypeScript cannot express "this type came from a class declaration" — so a marker placed there is just a property, and `interface Fake extends Marker {}` satisfies it. A declared type produces no value, so it cannot reach an argument position at all: an interface at the command position is refused with "only refers to a type, but is being used as a value here". `Schema.Class` and `Schema.TaggedClass` are both accepted; a `Schema.Struct`, a plain class, an object literal and a primitive are each refused.
|
|
46
|
+
|
|
47
|
+
No type argument needs writing: the command type comes from the class, so the decider's parameter needs no annotation. The `never`-channel conditional still lives on the **return** type, so a total decision (`Result<Decision, never>`) resolves to `UninhabitedError` and the call site fails with "This expression is not callable", while a `Promise`- or bare-value-returning decider is rejected at the argument. `make` is a runtime value, so consumers need it as an ordinary import only where they construct workflows; everywhere else `import type` still erases at compile time.
|
|
42
48
|
|
|
43
49
|
## Worked example
|
|
44
50
|
|
|
@@ -92,7 +98,8 @@ const restartIndicesFor = (
|
|
|
92
98
|
)
|
|
93
99
|
|
|
94
100
|
export const decideRestart = Workflow.make(
|
|
95
|
-
|
|
101
|
+
DecideInput,
|
|
102
|
+
(input): Result.Result<
|
|
96
103
|
RestartDecisionContinue | RestartDecisionRestart,
|
|
97
104
|
RestartDecisionExhausted
|
|
98
105
|
> =>
|
|
@@ -113,25 +120,28 @@ export const decideRestart = Workflow.make(
|
|
|
113
120
|
)
|
|
114
121
|
```
|
|
115
122
|
|
|
116
|
-
The shape to copy: one exported decision built by `Workflow.make`,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
The shape to copy: one exported decision built by `Workflow.make`, taking the command's
|
|
124
|
+
schema class and a decider over that class, whose body returns `Result` values via
|
|
125
|
+
`Result.succeed` and `Result.fail`. The command channel comes from the class, the decision
|
|
126
|
+
and error channels are inferred from the annotated return, and `make` is the only door to
|
|
127
|
+
the `WorkflowBrand` conjunct — annotating a function `Workflow<…>` directly is still refused
|
|
128
|
+
wherever the brand is demanded, because a workflow that never passed through `make` is not a
|
|
129
|
+
decision anything may run. The error channel is a real variant (`RestartDecisionExhausted`) —
|
|
130
|
+
giving up is a decision the caller must branch on, so declaring the error channel `never` is
|
|
131
|
+
rejected, not allowed.
|
|
123
132
|
|
|
124
133
|
## What it rejects at compile time
|
|
125
134
|
|
|
126
|
-
All
|
|
135
|
+
All six violations fail `tsc`; the messages below are what `tsc` reports (verified against this package and `effect@4.0.0-rc.108`).
|
|
127
136
|
|
|
128
|
-
| Violation
|
|
129
|
-
|
|
|
130
|
-
| A `Promise` return
|
|
131
|
-
| An `Effect` return
|
|
132
|
-
| `never` decision channel
|
|
133
|
-
| `never` error channel
|
|
134
|
-
| A bare decider handed to `Cell.decide`
|
|
137
|
+
| Violation | `tsc` reports | Why it is rejected |
|
|
138
|
+
| ----------------------------------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
139
|
+
| 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 |
|
|
140
|
+
| 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 |
|
|
141
|
+
| `never` decision channel | `Type '...' is not assignable to type 'UninhabitedDecision'` | a workflow that can never produce a decision can never succeed |
|
|
142
|
+
| `never` error channel | `Type '...' is not assignable to type 'UninhabitedError'` | a workflow that cannot fail decides nothing; move it to a `*.kernel.ts` |
|
|
143
|
+
| A bare decider handed to `Cell.decide` | `Type '(command: Cmd) => Result<Dec, Err>' is not assignable to type 'WorkflowBrand'` | only a `Workflow.make` value satisfies `DecidePhase`; a lambda that skipped `make` is not a decision a description may run |
|
|
144
|
+
| 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 |
|
|
135
145
|
|
|
136
146
|
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:
|
|
137
147
|
|
|
@@ -159,17 +169,17 @@ the outcome once:
|
|
|
159
169
|
import { Workflow } from '@systemfsoftware/effect-cell-types'
|
|
160
170
|
import { Result } from 'effect'
|
|
161
171
|
import * as Match from 'effect/Match'
|
|
172
|
+
import * as S from 'effect/Schema'
|
|
162
173
|
|
|
163
174
|
class Decision {}
|
|
164
175
|
class Err {
|
|
165
176
|
constructor(readonly reason: string) {}
|
|
166
177
|
}
|
|
167
|
-
|
|
168
|
-
readonly valid: boolean
|
|
169
|
-
}
|
|
178
|
+
class Input extends S.Class<Input>('Input')({ valid: S.Boolean }) {}
|
|
170
179
|
|
|
171
180
|
const decide = Workflow.make(
|
|
172
|
-
|
|
181
|
+
Input,
|
|
182
|
+
(input): Result.Result<Decision, Err> =>
|
|
173
183
|
Result.gen(function*() {
|
|
174
184
|
const outcome = Match.value(input).pipe(
|
|
175
185
|
Match.when({ valid: false }, () => Result.fail(new Err('invalid input'))),
|
|
@@ -192,7 +202,8 @@ type — so an unreachable error channel is rejected rather than silently allowe
|
|
|
192
202
|
|
|
193
203
|
```ts
|
|
194
204
|
const decide = Workflow.make(
|
|
195
|
-
|
|
205
|
+
Input,
|
|
206
|
+
(input): Result.Result<Decision, Err> =>
|
|
196
207
|
Result.gen(function*() {
|
|
197
208
|
return new Decision()
|
|
198
209
|
}),
|
package/dist/index.d.ts
CHANGED
|
@@ -2,9 +2,10 @@ import * as Effect$1 from "effect/Effect";
|
|
|
2
2
|
import { Effect } from "effect/Effect";
|
|
3
3
|
import * as Result$1 from "effect/Result";
|
|
4
4
|
import { Result } from "effect/Result";
|
|
5
|
+
import * as Schema$1 from "effect/Schema";
|
|
5
6
|
import { Schema, SchemaAST } from "effect";
|
|
6
7
|
declare namespace Workflow_d_exports {
|
|
7
|
-
export { Inhabited,
|
|
8
|
+
export { Inhabited, UninhabitedDecision, UninhabitedError, UntaggedError, Workflow, WorkflowBrand, make };
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
11
|
* The nominal brand a workflow carries. `Workflow.make` is the only door that applies it,
|
|
@@ -40,10 +41,6 @@ interface UninhabitedError {
|
|
|
40
41
|
interface UntaggedError {
|
|
41
42
|
readonly __WORKFLOW_ERROR_CHANNEL_CARRIES_NO_TAG__: 'this error carries no _tag the consumer can dispatch on; declare it as an S.TaggedError';
|
|
42
43
|
}
|
|
43
|
-
/** The shape an error channel must have: a tag the consumer dispatches on. */
|
|
44
|
-
interface Tagged {
|
|
45
|
-
readonly _tag: string;
|
|
46
|
-
}
|
|
47
44
|
/**
|
|
48
45
|
* A decider whose channels are both inhabited, or the marker naming which channel is not.
|
|
49
46
|
*
|
|
@@ -56,15 +53,48 @@ interface Tagged {
|
|
|
56
53
|
*/
|
|
57
54
|
type Workflow<Command, Decision, DecisionError> = [Decision] extends [never] ? UninhabitedDecision : [DecisionError] extends [never] ? UninhabitedError : ((command: Command) => Result<Decision, DecisionError>) & WorkflowBrand;
|
|
58
55
|
/**
|
|
59
|
-
* `unknown` when
|
|
60
|
-
*
|
|
61
|
-
*
|
|
56
|
+
* `unknown` when the error channel carries a tag a consumer can dispatch on, the {@link UntaggedError}
|
|
57
|
+
* marker otherwise. Two steps, both load-bearing: `'_tag' extends keyof E` asks whether the key is
|
|
58
|
+
* there, and `[E['_tag']] extends [string]` asks whether what it holds is dispatchable. Key presence
|
|
59
|
+
* alone admits `_tag: number`, `_tag?: string` and a `_tag()` method — none of which `Match.tag` can
|
|
60
|
+
* dispatch on, which is exactly what the marker claims to refuse.
|
|
61
|
+
*
|
|
62
|
+
* Both steps read the tag through `keyof` and an indexed access rather than declaring a `_tag`
|
|
63
|
+
* member, which is why the erased `Tagged` interface cannot come back: stating the requirement as a
|
|
64
|
+
* shape would write the very member this repo forbids.
|
|
65
|
+
*/
|
|
66
|
+
type DispatchableTag<E> = '_tag' extends keyof E ? [E['_tag']] extends [string] ? unknown : UntaggedError : UntaggedError;
|
|
67
|
+
/**
|
|
68
|
+
* `unknown` when both channels are inhabited and the error carries a dispatchable tag, so the
|
|
69
|
+
* intersection in {@link make} collapses to the plain `Result` and neither inference nor the
|
|
70
|
+
* authoring surface changes. Otherwise the marker the author must satisfy, which they cannot, which
|
|
71
|
+
* is the point.
|
|
62
72
|
*/
|
|
63
|
-
type Inhabited<Decision, DecisionError> = [Decision] extends [never] ? UninhabitedDecision : [DecisionError] extends [never] ? UninhabitedError :
|
|
73
|
+
type Inhabited<Decision, DecisionError> = [Decision] extends [never] ? UninhabitedDecision : [DecisionError] extends [never] ? UninhabitedError : DispatchableTag<DecisionError>;
|
|
64
74
|
/**
|
|
65
|
-
* Builds a workflow
|
|
75
|
+
* Builds a workflow from the command's schema class and a decider over that class's
|
|
76
|
+
* instance type, refusing an uninhabited or untagged channel at this call rather than at
|
|
66
77
|
* whoever first calls the result — which for a workflow nothing calls yet is never.
|
|
67
78
|
*
|
|
79
|
+
* The command is constrained on the **value**, not on a type parameter inferred from the
|
|
80
|
+
* decider's parameter. That is the whole mechanism. Any constraint on such a parameter is a
|
|
81
|
+
* structural predicate, and TypeScript cannot say "this type came from a class declaration"
|
|
82
|
+
* — so a marker placed there is a property, every property is declarable, and
|
|
83
|
+
* `interface Fake extends Marker {}` satisfies it. A declared type produces no value, so it
|
|
84
|
+
* cannot reach an argument position at all: there is no marker to smuggle because there is
|
|
85
|
+
* no marker.
|
|
86
|
+
*
|
|
87
|
+
* The three parameters mirror `Schema.Class`'s own bound exactly, and that is load-bearing.
|
|
88
|
+
* `Class<Self, S, Inherited>` places `S` in both covariant (`S["Type"]`) and contravariant
|
|
89
|
+
* (`S["fields"]`) positions, so it is invariant in `S`: every *fixed* spelling —
|
|
90
|
+
* `Class<unknown, Struct<Struct.Fields>, unknown>` and its variants — rejects real command
|
|
91
|
+
* classes. Generic over `S` accepts them and still refuses a `Struct`, which lacks
|
|
92
|
+
* `identifier` and `extend`. `Class<any, any, any>` also works and is banned here.
|
|
93
|
+
*
|
|
94
|
+
* `Schema.TaggedClass` returns this same `Class` interface, so one constraint covers both
|
|
95
|
+
* factories with no union. The import is type-only: this package gains no runtime dependency
|
|
96
|
+
* on Effect Schema, and `make` stays the identity function it always was.
|
|
97
|
+
*
|
|
68
98
|
* The markers ride the parameter function's return type, not the parameter as `Workflow<C, D, E>`:
|
|
69
99
|
* a conditional type in parameter position resolves `D` and `E` to `unknown` and the markers become
|
|
70
100
|
* unreachable. On the return type both still infer from the `Result` conjunct while the marker
|
|
@@ -78,13 +108,28 @@ type Inhabited<Decision, DecisionError> = [Decision] extends [never] ? Uninhabit
|
|
|
78
108
|
* The narrowing goes through an assertion signature rather than an `as` cast: every narrowing
|
|
79
109
|
* assertion trips `typescript(no-unsafe-type-assertion)`, and a suppression comment would hide the
|
|
80
110
|
* one place this file could lie. It is sound rather than merely permitted — with both channels
|
|
81
|
-
* inhabited `Workflow<
|
|
82
|
-
* conjunct, and otherwise the return type is a marker with no call
|
|
83
|
-
* back is unobservable through it. The brand is applied here and
|
|
84
|
-
*
|
|
85
|
-
*
|
|
111
|
+
* inhabited `Workflow<Self, D, E>` is `(command: Self) => Result<D, E>` carrying the
|
|
112
|
+
* {@link WorkflowBrand} conjunct, and otherwise the return type is a marker with no call
|
|
113
|
+
* signature, so the value handed back is unobservable through it. The brand is applied here and
|
|
114
|
+
* nowhere else: the assertion adds no runtime property, yet a value that did not pass through this
|
|
115
|
+
* door fails the conjunct wherever a decision is run.
|
|
86
116
|
*/
|
|
87
|
-
declare const make: <
|
|
117
|
+
declare const make: <Self, S extends Schema$1.Constraint & {
|
|
118
|
+
readonly fields: Schema$1.Struct.Fields;
|
|
119
|
+
}, Inherited, D, E>(_command: Schema$1.Class<Self, S, Inherited>, decide: (command: Self) => Result<D, E> & Inhabited<D, E>) => Workflow<Self, D, E>;
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/CanonicalDecide.workflow.d.ts
|
|
122
|
+
declare const CanonicalCommand_base: Schema$1.Class<CanonicalCommand, Schema$1.TaggedStruct<"CanonicalCommand", {}>, {}>;
|
|
123
|
+
/**
|
|
124
|
+
* The canonical command. `Workflow.make` constrains its first argument to a real
|
|
125
|
+
* schema class, so the canonical description needs one too — it carries no fields
|
|
126
|
+
* because the canonical's phases do nothing, and its only job is to be a genuine
|
|
127
|
+
* command value rather than a shape asserted into place.
|
|
128
|
+
*
|
|
129
|
+
* It is declared here rather than in a `*.schema.ts` because this is the owning
|
|
130
|
+
* single-segment `<stem>.workflow.ts`, which `schema-declaration-location` admits.
|
|
131
|
+
*/
|
|
132
|
+
declare class CanonicalCommand extends CanonicalCommand_base {}
|
|
88
133
|
declare namespace Cell_d_exports {
|
|
89
134
|
export { Convention, DESCRIPTION_MODULE, DecideDone, DecideNode, DecidePhase, DecodeDone, DecodeNode, DecodePhase, Description, EncodeDone, EncodeNode, EncodePhase, IO_CELLS, IoCellClassification, Layer, Phase, PhaseFact, Phases, ReadDone, ReadNode, ReadPhase, Vocabulary, WriteDone, WriteNode, WritePhase, apply, canonical, decide, decode, encode, read, vocabulary, write };
|
|
90
135
|
}
|
|
@@ -333,6 +378,16 @@ interface Vocabulary {
|
|
|
333
378
|
*/
|
|
334
379
|
readonly applier: 'apply';
|
|
335
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* The bag the canonical description is built with. It is `Phases` with one member
|
|
383
|
+
* pinned: `decoded` is the canonical command class, because `canonicalDecide` is a
|
|
384
|
+
* decider over that class and a decider's parameter is contravariant — a phase typed
|
|
385
|
+
* `(decoded: unknown) => …` would demand that `unknown` be assignable to the command,
|
|
386
|
+
* which it is not. Every other member stays `unknown`, so nothing else narrows.
|
|
387
|
+
*/
|
|
388
|
+
interface CanonicalPhases extends Phases {
|
|
389
|
+
readonly decoded: CanonicalCommand;
|
|
390
|
+
}
|
|
336
391
|
/**
|
|
337
392
|
* A canonical description, built through the public constructors with phases that do
|
|
338
393
|
* nothing. It is exported so a consumer — a generator, a lint rule, a documenter — can
|
|
@@ -345,7 +400,7 @@ interface Vocabulary {
|
|
|
345
400
|
* any other sequence fails to typecheck here — which is what keeps the derived order
|
|
346
401
|
* non-circular: it is read off a value, and the value's shape is enforced by the types.
|
|
347
402
|
*/
|
|
348
|
-
declare const canonical: WriteDone<
|
|
403
|
+
declare const canonical: WriteDone<CanonicalPhases>;
|
|
349
404
|
declare const vocabulary: Vocabulary;
|
|
350
405
|
declare namespace Policy_d_exports {
|
|
351
406
|
export { Policy };
|
package/dist/index.mjs
CHANGED
|
@@ -4,13 +4,36 @@ import * as Effect$1 from "effect/Effect";
|
|
|
4
4
|
import { dual } from "effect/Function";
|
|
5
5
|
import * as Option from "effect/Option";
|
|
6
6
|
import * as Result$1 from "effect/Result";
|
|
7
|
+
import * as S from "effect/Schema";
|
|
7
8
|
import { Schema } from "effect";
|
|
9
|
+
S.TaggedError()("CanonicalDecideError", {});
|
|
10
|
+
//#endregion
|
|
8
11
|
//#region src/Workflow.ts
|
|
9
12
|
var Workflow_exports = /* @__PURE__ */ __exportAll({ make: () => make });
|
|
10
13
|
/**
|
|
11
|
-
* Builds a workflow
|
|
14
|
+
* Builds a workflow from the command's schema class and a decider over that class's
|
|
15
|
+
* instance type, refusing an uninhabited or untagged channel at this call rather than at
|
|
12
16
|
* whoever first calls the result — which for a workflow nothing calls yet is never.
|
|
13
17
|
*
|
|
18
|
+
* The command is constrained on the **value**, not on a type parameter inferred from the
|
|
19
|
+
* decider's parameter. That is the whole mechanism. Any constraint on such a parameter is a
|
|
20
|
+
* structural predicate, and TypeScript cannot say "this type came from a class declaration"
|
|
21
|
+
* — so a marker placed there is a property, every property is declarable, and
|
|
22
|
+
* `interface Fake extends Marker {}` satisfies it. A declared type produces no value, so it
|
|
23
|
+
* cannot reach an argument position at all: there is no marker to smuggle because there is
|
|
24
|
+
* no marker.
|
|
25
|
+
*
|
|
26
|
+
* The three parameters mirror `Schema.Class`'s own bound exactly, and that is load-bearing.
|
|
27
|
+
* `Class<Self, S, Inherited>` places `S` in both covariant (`S["Type"]`) and contravariant
|
|
28
|
+
* (`S["fields"]`) positions, so it is invariant in `S`: every *fixed* spelling —
|
|
29
|
+
* `Class<unknown, Struct<Struct.Fields>, unknown>` and its variants — rejects real command
|
|
30
|
+
* classes. Generic over `S` accepts them and still refuses a `Struct`, which lacks
|
|
31
|
+
* `identifier` and `extend`. `Class<any, any, any>` also works and is banned here.
|
|
32
|
+
*
|
|
33
|
+
* `Schema.TaggedClass` returns this same `Class` interface, so one constraint covers both
|
|
34
|
+
* factories with no union. The import is type-only: this package gains no runtime dependency
|
|
35
|
+
* on Effect Schema, and `make` stays the identity function it always was.
|
|
36
|
+
*
|
|
14
37
|
* The markers ride the parameter function's return type, not the parameter as `Workflow<C, D, E>`:
|
|
15
38
|
* a conditional type in parameter position resolves `D` and `E` to `unknown` and the markers become
|
|
16
39
|
* unreachable. On the return type both still infer from the `Result` conjunct while the marker
|
|
@@ -24,24 +47,34 @@ var Workflow_exports = /* @__PURE__ */ __exportAll({ make: () => make });
|
|
|
24
47
|
* The narrowing goes through an assertion signature rather than an `as` cast: every narrowing
|
|
25
48
|
* assertion trips `typescript(no-unsafe-type-assertion)`, and a suppression comment would hide the
|
|
26
49
|
* one place this file could lie. It is sound rather than merely permitted — with both channels
|
|
27
|
-
* inhabited `Workflow<
|
|
28
|
-
* conjunct, and otherwise the return type is a marker with no call
|
|
29
|
-
* back is unobservable through it. The brand is applied here and
|
|
30
|
-
*
|
|
31
|
-
*
|
|
50
|
+
* inhabited `Workflow<Self, D, E>` is `(command: Self) => Result<D, E>` carrying the
|
|
51
|
+
* {@link WorkflowBrand} conjunct, and otherwise the return type is a marker with no call
|
|
52
|
+
* signature, so the value handed back is unobservable through it. The brand is applied here and
|
|
53
|
+
* nowhere else: the assertion adds no runtime property, yet a value that did not pass through this
|
|
54
|
+
* door fails the conjunct wherever a decision is run.
|
|
32
55
|
*/
|
|
33
|
-
const make = (decide) => {
|
|
56
|
+
const make = (_command, decide) => {
|
|
34
57
|
return decide;
|
|
35
58
|
};
|
|
36
59
|
//#endregion
|
|
37
60
|
//#region src/CanonicalDecide.workflow.ts
|
|
38
61
|
/**
|
|
62
|
+
* The canonical command. `Workflow.make` constrains its first argument to a real
|
|
63
|
+
* schema class, so the canonical description needs one too — it carries no fields
|
|
64
|
+
* because the canonical's phases do nothing, and its only job is to be a genuine
|
|
65
|
+
* command value rather than a shape asserted into place.
|
|
66
|
+
*
|
|
67
|
+
* It is declared here rather than in a `*.schema.ts` because this is the owning
|
|
68
|
+
* single-segment `<stem>.workflow.ts`, which `schema-declaration-location` admits.
|
|
69
|
+
*/
|
|
70
|
+
var CanonicalCommand = class extends S.TaggedClass()("CanonicalCommand", {}) {};
|
|
71
|
+
/**
|
|
39
72
|
* The canonical decider. Extracted so `make-file-location` only sees it inside a
|
|
40
73
|
* single-segment `.workflow.ts` file, satisfying the restored taxonomy while
|
|
41
74
|
* preserving the exact phantom-channel contract the `DecidePhase` brand and
|
|
42
75
|
* the interpreter rely on.
|
|
43
76
|
*/
|
|
44
|
-
const canonicalDecide = make((
|
|
77
|
+
const canonicalDecide = make(CanonicalCommand, (_command) => Result$1.succeed(void 0));
|
|
45
78
|
//#endregion
|
|
46
79
|
//#region src/Cell.ts
|
|
47
80
|
var Cell_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -246,7 +279,7 @@ const apply = (description, command) => Effect$1.gen(function* () {
|
|
|
246
279
|
* any other sequence fails to typecheck here — which is what keeps the derived order
|
|
247
280
|
* non-circular: it is read off a value, and the value's shape is enforced by the types.
|
|
248
281
|
*/
|
|
249
|
-
const canonical = write(encode(decide(decode(read(() => Effect$1.void), () => Result$1.succeed(
|
|
282
|
+
const canonical = write(encode(decide(decode(read(() => Effect$1.void), () => Result$1.succeed(CanonicalCommand.make({}))), canonicalDecide), () => void 0), () => Effect$1.void);
|
|
250
283
|
/**
|
|
251
284
|
* The phase vocabulary, obtained by walking `canonical`. A consumer that needs the phase
|
|
252
285
|
* names, their purity, or their order — a lint rule, a generator, a document — reads them
|
|
@@ -324,10 +357,8 @@ var Wire_exports = /* @__PURE__ */ __exportAll({
|
|
|
324
357
|
* preserved, so a marked struct stays a struct and Effect's inference keeps working.
|
|
325
358
|
*/
|
|
326
359
|
const mint = (field) => {
|
|
327
|
-
assertMinted(field);
|
|
328
360
|
return field;
|
|
329
361
|
};
|
|
330
|
-
function assertMinted(_field) {}
|
|
331
362
|
const string = mint(Schema.String);
|
|
332
363
|
const number = mint(Schema.Finite);
|
|
333
364
|
const boolean = mint(Schema.Boolean);
|
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": "
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"author": "Ryan Lee <drdgvhbh@gmail.com>",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -32,23 +32,23 @@
|
|
|
32
32
|
"dist"
|
|
33
33
|
],
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@effect/vitest": "4.0.0-rc.
|
|
35
|
+
"@effect/vitest": "4.0.0-rc.111",
|
|
36
36
|
"@microsoft/api-extractor": "^7.58.7",
|
|
37
37
|
"@systemfsoftware/arethetypeswrong-cli": "^1.1.1",
|
|
38
38
|
"@types/node": "^24",
|
|
39
|
-
"effect": "4.0.0-rc.
|
|
39
|
+
"effect": "4.0.0-rc.111",
|
|
40
40
|
"oxlint": "^1.77.0",
|
|
41
41
|
"rimraf": "^6.1.3",
|
|
42
42
|
"tsdown": "^0.22.14",
|
|
43
43
|
"tstyche": "^7.1.0",
|
|
44
44
|
"vitest": "^4.1.10",
|
|
45
|
-
"@systemfsoftware/effect-gherkin-spec": "2.0.
|
|
45
|
+
"@systemfsoftware/effect-gherkin-spec": "2.0.1",
|
|
46
|
+
"@systemfsoftware/oxlint-config": "^0.1.0",
|
|
46
47
|
"@systemfsoftware/vitest-config": "^0.1.0",
|
|
47
|
-
"@systemfsoftware/tsconfig": "^1.3.3"
|
|
48
|
-
"@systemfsoftware/oxlint-config": "^0.1.0"
|
|
48
|
+
"@systemfsoftware/tsconfig": "^1.3.3"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"effect": "4.0.0-rc.
|
|
51
|
+
"effect": "^4.0.0-rc.111"
|
|
52
52
|
},
|
|
53
53
|
"publishConfig": {
|
|
54
54
|
"provenance": true
|