@systemfsoftware/effect-cell-types 3.0.0 → 5.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 +81 -0
- package/README.md +38 -27
- package/dist/index.d.ts +103 -56
- package/dist/index.mjs +63 -67
- package/package.json +7 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,86 @@
|
|
|
1
1
|
# @systemfsoftware/effect-cell-types
|
|
2
2
|
|
|
3
|
+
## 5.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- A phase can no longer require a service. `Phases` drops `readContext` and
|
|
8
|
+
`writeContext`, and a read or write phase must return an effect whose context is
|
|
9
|
+
`never`.
|
|
10
|
+
|
|
11
|
+
Those two members were a claim about what a phase needed that nothing
|
|
12
|
+
recomputed. Whenever the surrounding stage was generic over `Phases`, the
|
|
13
|
+
compiler could not see through the type parameter to what the phase body
|
|
14
|
+
actually reached for, so declaring `never` for a body requiring four services
|
|
15
|
+
was accepted. The description then compiled clean and the missing service
|
|
16
|
+
surfaced only wherever it was finally applied, or nowhere at all.
|
|
17
|
+
|
|
18
|
+
Services now arrive the same way a phase's other inputs already do: as
|
|
19
|
+
parameters. Resolve them where you build the description, take them as an
|
|
20
|
+
argument, and provide them to the phase's own effect with
|
|
21
|
+
`Effect.provideContext`. Both mistakes that used to pass now fail — claiming
|
|
22
|
+
less than the body needs leaves the phase's context wider than `never`, which
|
|
23
|
+
the phase type rejects, and claiming more than it needs widens the requirement
|
|
24
|
+
of whoever builds the description, which surfaces where that builder is run.
|
|
25
|
+
|
|
26
|
+
`apply` therefore derives a context of `never` for every description, and a
|
|
27
|
+
caller can no longer be handed a requirement it never agreed to.
|
|
28
|
+
|
|
29
|
+
- The wire surface is now the seven symbols it always claimed to be: the mark,
|
|
30
|
+
the minted types, `mint`, `Fields` and `wire`. Fifteen convenience wrappers
|
|
31
|
+
around the schema library are gone. A member you built with a wrapper decodes
|
|
32
|
+
to exactly what it decoded to before; only the spelling changed.
|
|
33
|
+
|
|
34
|
+
To migrate, wrap the schema library member in `mint` where you used a wrapper:
|
|
35
|
+
|
|
36
|
+
- `string`, `number`, `boolean`, `integer` → `mint(S.String)`,
|
|
37
|
+
`mint(S.Finite)`, `mint(S.Boolean)`, `mint(S.Int)`
|
|
38
|
+
- `literal(...v)` → `mint(S.Literals([...v]))`; `union(...m)` →
|
|
39
|
+
`mint(S.Union([...m]))`; `tuple(...t)` → `mint(S.Tuple([...t]))`
|
|
40
|
+
- `nullOr(m)` / `undefinedOr(m)` / `nullishOr(m)` / `array(m)` /
|
|
41
|
+
`optional(m)` / `record(k, v)` / `suspend(t)` → the same letter under
|
|
42
|
+
`mint`, e.g. `mint(S.NullOr(m))`, `mint(S.Array(m))`, `mint(S.Record(k, v))`
|
|
43
|
+
- `refine(m, predicate)` → `mint(S.refine(predicate)(m))`
|
|
44
|
+
|
|
45
|
+
### Minor Changes
|
|
46
|
+
|
|
47
|
+
- A write phase now receives what its own layer's read gathered, as a second
|
|
48
|
+
argument after the encoded output.
|
|
49
|
+
|
|
50
|
+
This is for the common shape where a write persists or reports on what the read
|
|
51
|
+
found while the decision in between narrowed to what it needed. Until now such a
|
|
52
|
+
write had no channel for that value, so the layer had to keep it in a mutable
|
|
53
|
+
binding beside the description — assigned during the read, read back during the
|
|
54
|
+
write — and then guard at runtime against a value that was in fact always there.
|
|
55
|
+
The argument replaces that binding and the guard with it.
|
|
56
|
+
|
|
57
|
+
Writes that do not want the value are unchanged: a write declaring a single
|
|
58
|
+
parameter still satisfies the phase type, so nothing you have already written
|
|
59
|
+
needs to move.
|
|
60
|
+
|
|
61
|
+
## 4.0.0
|
|
62
|
+
|
|
63
|
+
### Major Changes
|
|
64
|
+
|
|
65
|
+
- `Workflow.make` now takes two arguments: the command's schema class first, the decider second.
|
|
66
|
+
|
|
67
|
+
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.
|
|
68
|
+
|
|
69
|
+
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.
|
|
70
|
+
|
|
71
|
+
- `Tagged` no longer ships in the `Workflow` namespace.
|
|
72
|
+
|
|
73
|
+
The requirement it expressed is unchanged: a decision error must still carry a tag the consumer can
|
|
74
|
+
dispatch on, and `UntaggedError` still names the failure when one does not. Replace an annotation
|
|
75
|
+
that referred to `Workflow.Tagged` with the concrete error type, or with a `Schema.TaggedError`
|
|
76
|
+
class, which satisfies the channel directly.
|
|
77
|
+
|
|
78
|
+
### Patch Changes
|
|
79
|
+
|
|
80
|
+
- 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.
|
|
81
|
+
|
|
82
|
+
Installing alongside a newer release candidate no longer reports an unmet peer dependency or resolves a second copy of `effect` into the dependency tree.
|
|
83
|
+
|
|
3
84
|
## 3.0.0
|
|
4
85
|
|
|
5
86
|
### 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
|
|
5
|
+
import * as Schema$1 from "effect/Schema";
|
|
6
|
+
import { Schema } 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.
|
|
116
|
+
*/
|
|
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.
|
|
86
131
|
*/
|
|
87
|
-
declare
|
|
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
|
}
|
|
@@ -107,16 +152,23 @@ interface Phases {
|
|
|
107
152
|
readonly decodeError: unknown;
|
|
108
153
|
readonly readError: unknown;
|
|
109
154
|
readonly writeError: unknown;
|
|
110
|
-
readonly readContext: unknown;
|
|
111
|
-
readonly writeContext: unknown;
|
|
112
155
|
}
|
|
113
156
|
/**
|
|
114
157
|
* A read gathers what the decision needs, and may gather a product across its interior;
|
|
115
158
|
* that interior is not type-visible, so no I/O count is claimed or enforced here. A step that
|
|
116
159
|
* mutates in order to report — bumping a counter and returning the resulting rate — is one
|
|
117
160
|
* such product, and belongs here rather than in a layer of its own.
|
|
161
|
+
*
|
|
162
|
+
* The context channel is pinned `never`: a phase requires nothing. Services are resolved by
|
|
163
|
+
* whoever builds the description and handed to the phase as ordinary parameters, which is
|
|
164
|
+
* the same edge that already gathers the read's inputs. The alternative — a `readContext`
|
|
165
|
+
* member on the bag — let an author write `never` for a body that reaches for a service, and
|
|
166
|
+
* nothing checked the claim: under a stage generic over `Phases` the compiler cannot see the
|
|
167
|
+
* lambda's requirement at all, so the description compiled and the missing service surfaced
|
|
168
|
+
* only where it was finally applied, or nowhere. Pinning it makes the lie unrepresentable
|
|
169
|
+
* instead of merely discouraged, and leaves `apply`'s derived `R` honestly `never`.
|
|
118
170
|
*/
|
|
119
|
-
type ReadPhase<P extends Phases> = (command: P['command']) => Effect$1.Effect<P['raw'], P['readError'],
|
|
171
|
+
type ReadPhase<P extends Phases> = (command: P['command']) => Effect$1.Effect<P['raw'], P['readError'], never>;
|
|
120
172
|
/** Validation. Its `Left` is fatal: it reaches the derived error channel and no write runs. */
|
|
121
173
|
type DecodePhase<P extends Phases> = (raw: P['raw']) => Result$1.Result<P['decoded'], P['decodeError']>;
|
|
122
174
|
/**
|
|
@@ -131,7 +183,19 @@ type DecodePhase<P extends Phases> = (raw: P['raw']) => Result$1.Result<P['decod
|
|
|
131
183
|
type DecidePhase<P extends Phases> = ((decoded: P['decoded']) => Result$1.Result<P['decision'], P['decisionError']>) & WorkflowBrand;
|
|
132
184
|
/** Shapes what the write consumes. Total, so it receives both branches of the decision. */
|
|
133
185
|
type EncodePhase<P extends Phases> = (outcome: Result$1.Result<P['decision'], P['decisionError']>) => P['output'];
|
|
134
|
-
|
|
186
|
+
/**
|
|
187
|
+
* The write. It receives the encoded `output` and, as a second argument, the `raw` its own
|
|
188
|
+
* layer's read gathered.
|
|
189
|
+
*
|
|
190
|
+
* `raw` is there because a write is frequently the point that persists or reports what the
|
|
191
|
+
* read found, while the decision in between deliberately narrows to what it needed. Without
|
|
192
|
+
* this argument such a write has no channel for it and the layer smuggles the value through
|
|
193
|
+
* a closure — a `let` beside the description, assigned in the read and consulted in the
|
|
194
|
+
* write, which then needs a runtime guard for a value the fold has already produced. The
|
|
195
|
+
* argument is second, and a write that does not want it declares one parameter: a unary
|
|
196
|
+
* function satisfies this type, so every write written before it existed is unchanged.
|
|
197
|
+
*/
|
|
198
|
+
type WritePhase<P extends Phases> = (output: P['output'], raw: P['raw']) => Effect$1.Effect<P['response'], P['writeError'], never>;
|
|
135
199
|
/**
|
|
136
200
|
* The invocation shape a folding consumer must use to call a phase's `run`:
|
|
137
201
|
* - `'effect'` — `run` returns an `Effect`; yield it. (read, write)
|
|
@@ -305,7 +369,7 @@ declare const write: {
|
|
|
305
369
|
* description's response is the last layer's. No scope is opened and interruptibility is
|
|
306
370
|
* untouched, so a `Scope.Scope` a phase requires reaches the caller as part of the derived `R`.
|
|
307
371
|
*/
|
|
308
|
-
declare const apply: <P extends Phases>(description: WriteDone<P>, command: P['command']) => Effect$1.Effect<P["response"], P["decodeError"] | P["readError"] | P["writeError"],
|
|
372
|
+
declare const apply: <P extends Phases>(description: WriteDone<P>, command: P['command']) => Effect$1.Effect<P["response"], P["decodeError"] | P["readError"] | P["writeError"], never>;
|
|
309
373
|
/** One phase's vocabulary entry: what it is called, its purity, its invocation shape. */
|
|
310
374
|
interface PhaseFact {
|
|
311
375
|
readonly name: Phase<Phases>['name'];
|
|
@@ -333,6 +397,16 @@ interface Vocabulary {
|
|
|
333
397
|
*/
|
|
334
398
|
readonly applier: 'apply';
|
|
335
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* The bag the canonical description is built with. It is `Phases` with one member
|
|
402
|
+
* pinned: `decoded` is the canonical command class, because `canonicalDecide` is a
|
|
403
|
+
* decider over that class and a decider's parameter is contravariant — a phase typed
|
|
404
|
+
* `(decoded: unknown) => …` would demand that `unknown` be assignable to the command,
|
|
405
|
+
* which it is not. Every other member stays `unknown`, so nothing else narrows.
|
|
406
|
+
*/
|
|
407
|
+
interface CanonicalPhases extends Phases {
|
|
408
|
+
readonly decoded: CanonicalCommand;
|
|
409
|
+
}
|
|
336
410
|
/**
|
|
337
411
|
* A canonical description, built through the public constructors with phases that do
|
|
338
412
|
* nothing. It is exported so a consumer — a generator, a lint rule, a documenter — can
|
|
@@ -345,20 +419,20 @@ interface Vocabulary {
|
|
|
345
419
|
* any other sequence fails to typecheck here — which is what keeps the derived order
|
|
346
420
|
* non-circular: it is read off a value, and the value's shape is enforced by the types.
|
|
347
421
|
*/
|
|
348
|
-
declare const canonical: WriteDone<
|
|
422
|
+
declare const canonical: WriteDone<CanonicalPhases>;
|
|
349
423
|
declare const vocabulary: Vocabulary;
|
|
350
424
|
declare namespace Policy_d_exports {
|
|
351
425
|
export { Policy };
|
|
352
426
|
}
|
|
353
427
|
type Policy<A, E, R> = (self: Effect<A, E, R>) => Effect<A, E, R>;
|
|
354
428
|
declare namespace Wire_d_exports {
|
|
355
|
-
export { AnyMinted, Fields, Mark, Minted, MintedField,
|
|
429
|
+
export { AnyMinted, Fields, Mark, Minted, MintedField, mint, wire };
|
|
356
430
|
}
|
|
357
431
|
/**
|
|
358
432
|
* Marker a wire member carries once this workspace declares its type. The property type is the
|
|
359
433
|
* fix, so the compiler diagnostic names it.
|
|
360
434
|
*
|
|
361
|
-
* It sits on the schema, never on the decoded value: `Schema.Type<typeof Wire.
|
|
435
|
+
* It sits on the schema, never on the decoded value: `Schema.Type<typeof Wire.mint(S.String)>` is `string`.
|
|
362
436
|
*/
|
|
363
437
|
interface Mark {
|
|
364
438
|
readonly __WIRE_MEMBER_IS_NOT_BUILT_FROM_THE_ALPHABET__: 'this member names a type this workspace does not declare; build it from Wire, or admit a foreign schema deliberately with Wire.mint';
|
|
@@ -372,8 +446,7 @@ type Minted<A, I = A> = Schema.Codec<A, I> & Mark;
|
|
|
372
446
|
/** Any marked schema, for constraint positions. */
|
|
373
447
|
type AnyMinted = Schema.Top & Mark;
|
|
374
448
|
/**
|
|
375
|
-
* Any marked struct member — a schema, or a property signature from
|
|
376
|
-
*
|
|
449
|
+
* Any marked struct member — a schema, or a property signature from `S.optional`.
|
|
377
450
|
* The permissive `Constraint` arm rather than a narrower schema type, whose `never` variants
|
|
378
451
|
* would fail the assignability check before the marker is reached, leaving the diagnostic to
|
|
379
452
|
* report an unrelated type error.
|
|
@@ -382,37 +455,11 @@ type MintedField = Schema.Constraint & Mark;
|
|
|
382
455
|
/**
|
|
383
456
|
* Mark a member whose type this workspace declares.
|
|
384
457
|
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
458
|
+
* The value returned is the one passed in, and its concrete type is preserved, so a marked
|
|
459
|
+
* struct stays a struct and Effect's inference keeps working. Reach for it to admit anything
|
|
460
|
+
* the schema library can express, including a vendor's own schema — deliberately.
|
|
388
461
|
*/
|
|
389
462
|
declare const mint: <Field extends Schema.Constraint>(field: Field) => Field & Mark;
|
|
390
|
-
declare const string: Schema.String & Mark;
|
|
391
|
-
declare const number: Schema.Finite & Mark;
|
|
392
|
-
declare const boolean: Schema.Boolean & Mark;
|
|
393
|
-
declare const integer: Schema.Int & Mark;
|
|
394
|
-
declare const literal: <const Literals extends ReadonlyArray<SchemaAST.LiteralValue>>(...literals: Literals) => Schema.Literals<Literals> & Mark;
|
|
395
|
-
declare const nullOr: <A, I>(member: Minted<A, I>) => Minted<A | null, I | null>;
|
|
396
|
-
declare const undefinedOr: <A, I>(member: Minted<A, I>) => Minted<A | undefined, I | undefined>;
|
|
397
|
-
declare const nullishOr: <A, I>(member: Minted<A, I>) => Minted<A | null | undefined, I | null | undefined>;
|
|
398
|
-
declare const array: <A, I>(member: Minted<A, I>) => Minted<ReadonlyArray<A>, ReadonlyArray<I>>;
|
|
399
|
-
/** A field that may be absent from the payload entirely, as distinct from present and `undefined`. */
|
|
400
|
-
declare const optional: <Member extends AnyMinted>(member: Member) => Schema.optional<Member> & Mark;
|
|
401
|
-
declare const record: <K extends Schema.Record.Key & Mark, V extends AnyMinted>(key: K, value: V) => Schema.$Record<K, V> & Mark;
|
|
402
|
-
declare const union: <Members extends readonly [AnyMinted, AnyMinted, ...Array<AnyMinted>]>(...members: Members) => Schema.Union<Members> & Mark;
|
|
403
|
-
declare const tuple: <Elements extends ReadonlyArray<AnyMinted>>(...elements: Elements) => Schema.Tuple<Elements> & Mark;
|
|
404
|
-
declare const suspend: <A, I>(thunk: () => Minted<A, I>) => Minted<A, I>;
|
|
405
|
-
/**
|
|
406
|
-
* Constrain a member's values. Effect's own `member.pipe(Schema.check(Schema.isMinLength(1)))`
|
|
407
|
-
* preserves the mark, but returns the schema with the concrete decode/encode pair fused; this
|
|
408
|
-
* combinator is the alphabet's own shape for the same operation, so a refined member keeps the
|
|
409
|
-
* declaration this workspace makes of its type.
|
|
410
|
-
*
|
|
411
|
-
* `S.refine` needs a type-guard as its predicate argument; the workspace's predicate is a plain
|
|
412
|
-
* `(a: A) => boolean` that narrows nothing, so a truth-preserving `value is A` guard is written
|
|
413
|
-
* at this one call site rather than widening the public signature.
|
|
414
|
-
*/
|
|
415
|
-
declare const refine: <A, I>(member: Minted<A, I>, predicate: (a: A) => boolean, annotations?: Schema.Annotations.Filter) => Minted<A, I>;
|
|
416
463
|
type Fields = Record<string, MintedField>;
|
|
417
464
|
declare const wire: <F extends Fields>(fields: F) => Schema.Struct<F> & Mark;
|
|
418
465
|
//#endregion
|
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({
|
|
@@ -185,9 +218,15 @@ const runLayer = (layer, command) => Effect$1.gen(function* () {
|
|
|
185
218
|
const last = phases[phases.length - 1];
|
|
186
219
|
if (!last || last.name !== "write") return yield* Effect$1.die(/* @__PURE__ */ new Error("effect-cell-types: a layer reached the interpreter without a write phase closing it"));
|
|
187
220
|
let value = command;
|
|
221
|
+
let raw = command;
|
|
188
222
|
for (const phase of phases.slice(0, -1)) switch (phase.convention) {
|
|
189
223
|
case "effect":
|
|
190
|
-
|
|
224
|
+
if (phase.name === "read") {
|
|
225
|
+
value = yield* phase.run(value);
|
|
226
|
+
raw = value;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
value = yield* phase.run(value, raw);
|
|
191
230
|
break;
|
|
192
231
|
case "either-fail":
|
|
193
232
|
value = yield* Result$1.match(phase.run(value), {
|
|
@@ -207,7 +246,7 @@ const runLayer = (layer, command) => Effect$1.gen(function* () {
|
|
|
207
246
|
return yield* Effect$1.die(/* @__PURE__ */ new Error(`effect-cell-types: unknown phase convention ${String(unreachable)}`));
|
|
208
247
|
}
|
|
209
248
|
}
|
|
210
|
-
return yield* last.run(value);
|
|
249
|
+
return yield* last.run(value, raw);
|
|
211
250
|
});
|
|
212
251
|
/**
|
|
213
252
|
* Applies a description. The return type is deliberately not annotated: `gen` accumulates
|
|
@@ -246,7 +285,7 @@ const apply = (description, command) => Effect$1.gen(function* () {
|
|
|
246
285
|
* any other sequence fails to typecheck here — which is what keeps the derived order
|
|
247
286
|
* non-circular: it is read off a value, and the value's shape is enforced by the types.
|
|
248
287
|
*/
|
|
249
|
-
const canonical = write(encode(decide(decode(read(() => Effect$1.void), () => Result$1.succeed(
|
|
288
|
+
const canonical = write(encode(decide(decode(read(() => Effect$1.void), () => Result$1.succeed(CanonicalCommand.make({}))), canonicalDecide), () => void 0), () => Effect$1.void);
|
|
250
289
|
/**
|
|
251
290
|
* The phase vocabulary, obtained by walking `canonical`. A consumer that needs the phase
|
|
252
291
|
* names, their purity, or their order — a lint rule, a generator, a document — reads them
|
|
@@ -279,81 +318,38 @@ var Policy_exports = /* @__PURE__ */ __exportAll({});
|
|
|
279
318
|
*
|
|
280
319
|
* ```ts
|
|
281
320
|
* import { Wire } from '@systemfsoftware/effect-cell-types'
|
|
282
|
-
* import { Schema } from 'effect'
|
|
321
|
+
* import { Effect, Schema as S } from 'effect'
|
|
283
322
|
*
|
|
284
323
|
* const Invoice = Wire.wire({
|
|
285
|
-
* id: Wire.
|
|
286
|
-
* amountDue: Wire.
|
|
287
|
-
* status: Wire.
|
|
288
|
-
* lineItems: Wire.
|
|
289
|
-
* metadata: Wire.
|
|
290
|
-
* deleted: Wire.optional(Wire.
|
|
324
|
+
* id: Wire.mint(S.String),
|
|
325
|
+
* amountDue: Wire.mint(S.NullOr(Wire.mint(S.Finite))),
|
|
326
|
+
* status: Wire.mint(S.Literals(['draft', 'open', 'paid'])),
|
|
327
|
+
* lineItems: Wire.mint(S.Array(Wire.mint(S.String))),
|
|
328
|
+
* metadata: Wire.mint(S.Record(Wire.mint(S.String), Wire.mint(S.String))),
|
|
329
|
+
* deleted: Wire.mint(S.optional(Wire.mint(S.Boolean))),
|
|
291
330
|
* })
|
|
292
331
|
*
|
|
293
332
|
* // Decodes to your own type — `{ id: string; amountDue: number | null; ... }`.
|
|
294
|
-
* const invoice = Schema.
|
|
333
|
+
* const invoice = yield* Schema.decode(Invoice)(payload)
|
|
295
334
|
* ```
|
|
296
335
|
*
|
|
297
|
-
* Only declarations that
|
|
298
|
-
* schema deliberately. It is a guardrail, not a security boundary.
|
|
336
|
+
* Only declarations that name members through {@link mint} are constrained, and mint admits a
|
|
337
|
+
* foreign schema deliberately. It is a guardrail, not a security boundary.
|
|
299
338
|
*/
|
|
300
339
|
var Wire_exports = /* @__PURE__ */ __exportAll({
|
|
301
|
-
array: () => array,
|
|
302
|
-
boolean: () => boolean,
|
|
303
|
-
integer: () => integer,
|
|
304
|
-
literal: () => literal,
|
|
305
340
|
mint: () => mint,
|
|
306
|
-
nullOr: () => nullOr,
|
|
307
|
-
nullishOr: () => nullishOr,
|
|
308
|
-
number: () => number,
|
|
309
|
-
optional: () => optional,
|
|
310
|
-
record: () => record,
|
|
311
|
-
refine: () => refine,
|
|
312
|
-
string: () => string,
|
|
313
|
-
suspend: () => suspend,
|
|
314
|
-
tuple: () => tuple,
|
|
315
|
-
undefinedOr: () => undefinedOr,
|
|
316
|
-
union: () => union,
|
|
317
341
|
wire: () => wire
|
|
318
342
|
});
|
|
319
343
|
/**
|
|
320
344
|
* Mark a member whose type this workspace declares.
|
|
321
345
|
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
346
|
+
* The value returned is the one passed in, and its concrete type is preserved, so a marked
|
|
347
|
+
* struct stays a struct and Effect's inference keeps working. Reach for it to admit anything
|
|
348
|
+
* the schema library can express, including a vendor's own schema — deliberately.
|
|
325
349
|
*/
|
|
326
350
|
const mint = (field) => {
|
|
327
|
-
assertMinted(field);
|
|
328
351
|
return field;
|
|
329
352
|
};
|
|
330
|
-
function assertMinted(_field) {}
|
|
331
|
-
const string = mint(Schema.String);
|
|
332
|
-
const number = mint(Schema.Finite);
|
|
333
|
-
const boolean = mint(Schema.Boolean);
|
|
334
|
-
const integer = mint(Schema.Int);
|
|
335
|
-
const literal = (...literals) => mint(Schema.Literals(literals));
|
|
336
|
-
const nullOr = (member) => mint(Schema.NullOr(member));
|
|
337
|
-
const undefinedOr = (member) => mint(Schema.UndefinedOr(member));
|
|
338
|
-
const nullishOr = (member) => mint(Schema.NullishOr(member));
|
|
339
|
-
const array = (member) => mint(Schema.Array(member));
|
|
340
|
-
/** A field that may be absent from the payload entirely, as distinct from present and `undefined`. */
|
|
341
|
-
const optional = (member) => mint(Schema.optional(member));
|
|
342
|
-
const record = (key, value) => mint(Schema.Record(key, value));
|
|
343
|
-
const union = (...members) => mint(Schema.Union(members));
|
|
344
|
-
const tuple = (...elements) => mint(Schema.Tuple(elements));
|
|
345
|
-
const suspend = (thunk) => mint(Schema.suspend(thunk));
|
|
346
|
-
/**
|
|
347
|
-
* Constrain a member's values. Effect's own `member.pipe(Schema.check(Schema.isMinLength(1)))`
|
|
348
|
-
* preserves the mark, but returns the schema with the concrete decode/encode pair fused; this
|
|
349
|
-
* combinator is the alphabet's own shape for the same operation, so a refined member keeps the
|
|
350
|
-
* declaration this workspace makes of its type.
|
|
351
|
-
*
|
|
352
|
-
* `S.refine` needs a type-guard as its predicate argument; the workspace's predicate is a plain
|
|
353
|
-
* `(a: A) => boolean` that narrows nothing, so a truth-preserving `value is A` guard is written
|
|
354
|
-
* at this one call site rather than widening the public signature.
|
|
355
|
-
*/
|
|
356
|
-
const refine = (member, predicate, annotations) => mint(Schema.refine((value) => predicate(value), annotations)(member));
|
|
357
353
|
const wire = (fields) => mint(Schema.Struct(fields));
|
|
358
354
|
//#endregion
|
|
359
355
|
export { Cell_exports as Cell, Policy_exports as Policy, Wire_exports as Wire, 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": "
|
|
4
|
+
"version": "5.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.112",
|
|
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.112",
|
|
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.
|
|
46
|
-
"@systemfsoftware/vitest-config": "^0.1.0",
|
|
45
|
+
"@systemfsoftware/effect-gherkin-spec": "2.0.1",
|
|
47
46
|
"@systemfsoftware/tsconfig": "^1.3.3",
|
|
48
|
-
"@systemfsoftware/oxlint-config": "^0.1.0"
|
|
47
|
+
"@systemfsoftware/oxlint-config": "^0.1.0",
|
|
48
|
+
"@systemfsoftware/vitest-config": "^0.1.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"effect": "4.0.0-rc.
|
|
51
|
+
"effect": "^4.0.0-rc.112"
|
|
52
52
|
},
|
|
53
53
|
"publishConfig": {
|
|
54
54
|
"provenance": true
|
|
@@ -58,7 +58,6 @@
|
|
|
58
58
|
"build": "tsdown && pnpm api:check",
|
|
59
59
|
"typecheck": "tsc --noEmit --incremental",
|
|
60
60
|
"test": "vitest run --passWithNoTests",
|
|
61
|
-
"test:run": "vitest run --passWithNoTests",
|
|
62
61
|
"test:types": "TSTYCHE_TYPESCRIPT_MODULE=tstyche-typescript tstyche",
|
|
63
62
|
"api:check": "api-extractor run",
|
|
64
63
|
"api:update": "api-extractor run --local",
|