selaws 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +355 -2
- package/dist/evidence.d.ts +45 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +22 -0
- package/dist/evidence.js.map +1 -0
- package/dist/identity.d.ts +52 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +22 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/callback.d.ts +13 -0
- package/dist/internal/callback.d.ts.map +1 -0
- package/dist/internal/callback.js +2 -0
- package/dist/internal/callback.js.map +1 -0
- package/dist/internal/promise-like.d.ts +8 -0
- package/dist/internal/promise-like.d.ts.map +1 -0
- package/dist/internal/promise-like.js +12 -0
- package/dist/internal/promise-like.js.map +1 -0
- package/dist/internal/scalar.d.ts +18 -0
- package/dist/internal/scalar.d.ts.map +1 -0
- package/dist/internal/scalar.js +6 -0
- package/dist/internal/scalar.js.map +1 -0
- package/dist/option.d.ts +90 -0
- package/dist/option.d.ts.map +1 -0
- package/dist/option.js +96 -0
- package/dist/option.js.map +1 -0
- package/dist/protocol.d.ts +42 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +67 -0
- package/dist/protocol.js.map +1 -0
- package/dist/result/capture.d.ts +42 -0
- package/dist/result/capture.d.ts.map +1 -0
- package/dist/result/capture.js +41 -0
- package/dist/result/capture.js.map +1 -0
- package/dist/result/core.d.ts +98 -0
- package/dist/result/core.d.ts.map +1 -0
- package/dist/result/core.js +103 -0
- package/dist/result/core.js.map +1 -0
- package/dist/result/index.d.ts +4 -0
- package/dist/result/index.d.ts.map +1 -0
- package/dist/result/index.js +4 -0
- package/dist/result/index.js.map +1 -0
- package/dist/result/throw.d.ts +4 -0
- package/dist/result/throw.d.ts.map +1 -0
- package/dist/result/throw.js +8 -0
- package/dist/result/throw.js.map +1 -0
- package/dist/validation.d.ts +126 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +240 -0
- package/dist/validation.js.map +1 -0
- package/dist/variant.d.ts +97 -0
- package/dist/variant.d.ts.map +1 -0
- package/dist/variant.js +102 -0
- package/dist/variant.js.map +1 -0
- package/docs/API.md +543 -0
- package/docs/GUIDE.md +739 -0
- package/docs/SEMANTICS.md +319 -0
- package/docs/laws/evidence.md +113 -0
- package/docs/laws/identity.md +126 -0
- package/docs/laws/match.md +163 -0
- package/docs/laws/option.md +100 -0
- package/docs/laws/protocol.md +152 -0
- package/docs/laws/result.md +124 -0
- package/docs/laws/validation.md +111 -0
- package/docs/laws/variant.md +251 -0
- package/package.json +87 -3
- package/src/evidence.ts +120 -0
- package/src/identity.ts +129 -0
- package/src/index.ts +54 -0
- package/src/internal/callback.ts +54 -0
- package/src/internal/promise-like.ts +32 -0
- package/src/internal/scalar.ts +43 -0
- package/src/option.ts +214 -0
- package/src/protocol.ts +174 -0
- package/src/result/capture.ts +209 -0
- package/src/result/core.ts +229 -0
- package/src/result/index.ts +3 -0
- package/src/result/throw.ts +13 -0
- package/src/validation.ts +491 -0
- package/src/variant.ts +363 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Match law
|
|
2
|
+
|
|
3
|
+
Match is Selaws' shared elimination law for sum-like semantic owners.
|
|
4
|
+
|
|
5
|
+
It applies to:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Option Some / None
|
|
9
|
+
Result Ok / Err
|
|
10
|
+
Validation Valid / Invalid
|
|
11
|
+
Variant one declared family case
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Match is not an eighth semantic owner. The owning carrier still defines which
|
|
15
|
+
branches exist, what each branch means, and which payload belongs to each
|
|
16
|
+
branch.
|
|
17
|
+
|
|
18
|
+
There is no root `Match` value, `Match` type, or `selaws/match` entry point.
|
|
19
|
+
The public realizations stay on their owners:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
Option.match
|
|
23
|
+
Result.match
|
|
24
|
+
Validation.match
|
|
25
|
+
VariantFamily.match
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 1. Owner-defined branch universe
|
|
29
|
+
|
|
30
|
+
For one owner with finite branch universe `B` and branch payload assignment
|
|
31
|
+
`P`, Match eliminates one value from:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
sum over b in B of P(b)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
through one handler per branch.
|
|
38
|
+
|
|
39
|
+
The owner supplies `B` and `P`.
|
|
40
|
+
|
|
41
|
+
Match does not reinterpret Option as Result, Result as Validation, or one
|
|
42
|
+
Variant family as another. Runtime structural similarity does not identify the
|
|
43
|
+
semantic owner.
|
|
44
|
+
|
|
45
|
+
## 2. Typed totality
|
|
46
|
+
|
|
47
|
+
A typed Match call provides a handler for every branch in the owning semantic
|
|
48
|
+
universe.
|
|
49
|
+
|
|
50
|
+
This requirement is determined by the owner, not by the current narrowing of
|
|
51
|
+
the input value.
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
Option requires Some and None
|
|
57
|
+
Result requires Ok and Err
|
|
58
|
+
Validation requires Valid and Invalid
|
|
59
|
+
Variant requires every case in the declared family
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A narrowed Some, Ok, Valid, or Variant case does not reduce the family-level
|
|
63
|
+
handler obligation.
|
|
64
|
+
|
|
65
|
+
## 3. Unique selection
|
|
66
|
+
|
|
67
|
+
The owning carrier determines exactly one selected branch.
|
|
68
|
+
|
|
69
|
+
Match resolves only that branch's handler according to the owner's handler
|
|
70
|
+
boundary. Unselected handler properties are not read or invoked by Selaws
|
|
71
|
+
Match execution.
|
|
72
|
+
|
|
73
|
+
When selected-handler resolution completes with a callable handler, that
|
|
74
|
+
handler is invoked exactly once. Any abrupt completion while resolving the
|
|
75
|
+
selected handler remains ordinary JavaScript abrupt completion.
|
|
76
|
+
|
|
77
|
+
## 4. Payload correlation and arity
|
|
78
|
+
|
|
79
|
+
The selected handler receives exactly the payload owned by the selected branch.
|
|
80
|
+
|
|
81
|
+
Nullary branches receive zero arguments.
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
Option Some -> present value
|
|
87
|
+
Option None -> zero arguments
|
|
88
|
+
|
|
89
|
+
Result Ok -> success value
|
|
90
|
+
Result Err -> error value
|
|
91
|
+
|
|
92
|
+
Validation Valid -> valid value
|
|
93
|
+
Validation Invalid-> complete non-empty issue collection
|
|
94
|
+
|
|
95
|
+
Variant unit case -> zero arguments
|
|
96
|
+
Variant payload -> stored case payload
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Match does not flatten, convert, accumulate, or otherwise reinterpret a branch
|
|
100
|
+
payload.
|
|
101
|
+
|
|
102
|
+
## 5. Receiver neutrality
|
|
103
|
+
|
|
104
|
+
Selaws does not supply the handler object as a callback receiver.
|
|
105
|
+
|
|
106
|
+
The selected handler is invoked as an ordinary callback without a
|
|
107
|
+
library-defined `this` value. A function that carries its own explicit JavaScript
|
|
108
|
+
binding, such as a bound function, retains that ordinary language behavior.
|
|
109
|
+
|
|
110
|
+
Receiver semantics are therefore not a hidden communication channel between a
|
|
111
|
+
semantic owner and its Match handler. This rule governs callback invocation;
|
|
112
|
+
it does not rewrite ordinary JavaScript property-access semantics used by an
|
|
113
|
+
owner to resolve the selected handler.
|
|
114
|
+
|
|
115
|
+
## 6. Completion transparency
|
|
116
|
+
|
|
117
|
+
After selected-handler resolution, the selected handler's completion is the
|
|
118
|
+
Match completion.
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
ordinary return -> ordinary Match return
|
|
122
|
+
throw -> throw remains abrupt
|
|
123
|
+
Promise return -> native Promise value remains native
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Match does not capture exceptions, await Promises, introduce asynchronous
|
|
127
|
+
carriers, or normalize handler return values.
|
|
128
|
+
|
|
129
|
+
The static Match result is a conservative union of the return types exposed by
|
|
130
|
+
the participating handlers. Finite overloaded handlers therefore preserve
|
|
131
|
+
their exposed return alternatives rather than collapsing to one overload.
|
|
132
|
+
|
|
133
|
+
TypeScript can reflect some generic callable signatures as the same instantiated
|
|
134
|
+
signature repeatedly. When that reflection would cycle, Selaws widens the
|
|
135
|
+
affected callback result to `unknown` instead of imposing an arbitrary overload
|
|
136
|
+
count or exhausting compiler instantiation depth.
|
|
137
|
+
|
|
138
|
+
## 7. Runtime boundary ownership
|
|
139
|
+
|
|
140
|
+
The shared Match law does not require one universal runtime validator.
|
|
141
|
+
|
|
142
|
+
Fixed structural owners rely on their ordinary typed carrier boundary and
|
|
143
|
+
ordinary JavaScript property lookup for the selected handler.
|
|
144
|
+
|
|
145
|
+
Variant additionally owns runtime family checks because its family declaration
|
|
146
|
+
exists at runtime. Variant Match therefore validates its own tagged
|
|
147
|
+
representation, declared case membership, payload representation, and selected
|
|
148
|
+
own handler availability.
|
|
149
|
+
|
|
150
|
+
Those Variant checks are owner-specific enforcement of Variant meaning, not
|
|
151
|
+
additional shared Match meaning.
|
|
152
|
+
|
|
153
|
+
## 8. Implementation independence
|
|
154
|
+
|
|
155
|
+
A shared law does not require one shared production helper.
|
|
156
|
+
|
|
157
|
+
Each owner may keep a local implementation when that preserves clearer
|
|
158
|
+
ownership, inference, and runtime boundaries. Shared conformance tests establish
|
|
159
|
+
the cross-owner law.
|
|
160
|
+
|
|
161
|
+
A future Match-capable owner must define its branch universe and payload
|
|
162
|
+
correlation, conform to this shared law, and keep any additional runtime
|
|
163
|
+
validation with the owner that can establish it.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Option law
|
|
2
|
+
|
|
3
|
+
Option represents explicit presence or reasonless absence.
|
|
4
|
+
|
|
5
|
+
Use Option when callers only need to distinguish “a value exists” from “no
|
|
6
|
+
value exists”.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
type Some<T> = Readonly<{
|
|
10
|
+
some: true;
|
|
11
|
+
value: T;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
type None = Readonly<{
|
|
15
|
+
some: false;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
type Option<T> = Some<T> | None;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
If the absent branch needs a diagnostic reason, use Result or a domain-specific
|
|
22
|
+
Variant instead of placing hidden meaning behind None.
|
|
23
|
+
|
|
24
|
+
## Presence
|
|
25
|
+
|
|
26
|
+
`Some(value)` means a value is present, including `undefined` or `null`
|
|
27
|
+
when explicitly wrapped. `None` carries no diagnostic reason.
|
|
28
|
+
|
|
29
|
+
Constructors preserve this distinction:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
some(undefined) => Some(undefined)
|
|
33
|
+
fromUndefined(undefined) => None
|
|
34
|
+
fromUndefined(null) => Some(null)
|
|
35
|
+
fromNullable(undefined) => None
|
|
36
|
+
fromNullable(null) => None
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Falsy values such as `0`, `false`, and `""` remain present.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
const maybeName =
|
|
43
|
+
Option.fromUndefined(row.name);
|
|
44
|
+
|
|
45
|
+
const label = Option.match(maybeName, {
|
|
46
|
+
some: (name) => name,
|
|
47
|
+
none: () => "Anonymous",
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Match elimination
|
|
52
|
+
|
|
53
|
+
Option's Match branch universe is exactly Some and None.
|
|
54
|
+
|
|
55
|
+
`Option.match` conforms to the shared [Match law](./match.md). Typed
|
|
56
|
+
elimination requires both branches even when the current input is narrowed.
|
|
57
|
+
Some passes its present value to the selected handler. None is nullary and
|
|
58
|
+
passes zero arguments.
|
|
59
|
+
|
|
60
|
+
Option owns the presence/absence meaning. Match owns the shared elimination
|
|
61
|
+
behavior: exactly one selected receiver-neutral callback runs, unselected
|
|
62
|
+
handlers are untouched, and the selected callback's ordinary JavaScript
|
|
63
|
+
completion is preserved.
|
|
64
|
+
|
|
65
|
+
## Composition
|
|
66
|
+
|
|
67
|
+
`map` transforms Some.
|
|
68
|
+
|
|
69
|
+
`andThen` sequences presence-dependent work.
|
|
70
|
+
|
|
71
|
+
`orElse` evaluates fallback only for None.
|
|
72
|
+
|
|
73
|
+
`flatten` removes one explicit nested Option layer.
|
|
74
|
+
|
|
75
|
+
`filter` keeps a present value only when its predicate accepts it.
|
|
76
|
+
|
|
77
|
+
`all` combines already-materialized Options. It preserves tuple position when
|
|
78
|
+
every input is Some; any None produces None. Typed input arrays must be readonly so array covariance cannot change element
|
|
79
|
+
meaning behind the static type before the call. Inline array literals infer
|
|
80
|
+
readonly tuples; broad `readonly Option<T>[]` inputs remain valid.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const pair = Option.all([
|
|
84
|
+
maybeFirst,
|
|
85
|
+
maybeLast,
|
|
86
|
+
] as const);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Observation and projection
|
|
90
|
+
|
|
91
|
+
`inspect` synchronously observes Some and returns the original Option.
|
|
92
|
+
Promise-like observer completion is outside that synchronous contract.
|
|
93
|
+
|
|
94
|
+
`unwrapOr` and `unwrapOrElse` leave Option by choosing a fallback value.
|
|
95
|
+
|
|
96
|
+
`toUndefined` and `toNullable` leave Option by projecting None to a
|
|
97
|
+
conventional JavaScript sentinel.
|
|
98
|
+
|
|
99
|
+
These projections do not change the law of the Option value before the
|
|
100
|
+
boundary.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Protocol laws
|
|
2
|
+
|
|
3
|
+
Protocol owns one admissible labeled transition relation over
|
|
4
|
+
application-owned scalar state and label identifiers.
|
|
5
|
+
|
|
6
|
+
For a declaration `R`:
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
R subset State x Label x State
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`allows(from, label, to)` is true exactly when `(from, label, to)` is in that
|
|
13
|
+
relation.
|
|
14
|
+
|
|
15
|
+
Use Protocol when the application must state which labeled transitions are
|
|
16
|
+
admissible without also introducing a state-machine runtime.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
const transitions = [
|
|
20
|
+
["pending", "pay", "paid"],
|
|
21
|
+
["pending", "cancel", "cancelled"],
|
|
22
|
+
["paid", "ship", "shipped"],
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
const OrderProtocol =
|
|
26
|
+
Protocol.define(transitions);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 1. Application-owned identifiers
|
|
30
|
+
|
|
31
|
+
The application owns the meaning of state identifiers and transition labels.
|
|
32
|
+
Protocol receives scalar identifiers and does not construct domain states,
|
|
33
|
+
commands, events, or operations.
|
|
34
|
+
|
|
35
|
+
A Protocol declaration therefore does not define a complete state universe.
|
|
36
|
+
`States<Transitions>` contains states that occur in declared source or target
|
|
37
|
+
positions. A state with no declared edge is outside that projection unless the
|
|
38
|
+
application represents it elsewhere.
|
|
39
|
+
|
|
40
|
+
## 2. Label preservation
|
|
41
|
+
|
|
42
|
+
Labels are part of relation identity. Two declared transitions with the same
|
|
43
|
+
source and target but different labels remain distinct transitions.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
Protocol.define([
|
|
47
|
+
["ready", "retry", "ready"],
|
|
48
|
+
["ready", "refresh", "ready"],
|
|
49
|
+
] as const);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`retry` and `refresh` remain separate even though their source and target are
|
|
53
|
+
the same.
|
|
54
|
+
|
|
55
|
+
## 3. Single relation
|
|
56
|
+
|
|
57
|
+
The same readonly transition declaration determines both the TypeScript
|
|
58
|
+
`Next<Transitions, From, Label>` projection and runtime
|
|
59
|
+
`allows(from, label, to)` membership. The typed declaration requires a readonly outer relation and readonly
|
|
60
|
+
transition triples, rejecting mutation through the declaration type itself.
|
|
61
|
+
A readonly view over separately mutable backing data remains subject to the
|
|
62
|
+
TypeScript trust model in [SEMANTICS.md](../SEMANTICS.md).
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
type AfterPay =
|
|
66
|
+
Next<
|
|
67
|
+
typeof transitions,
|
|
68
|
+
"pending",
|
|
69
|
+
"pay"
|
|
70
|
+
>;
|
|
71
|
+
// "paid"
|
|
72
|
+
|
|
73
|
+
OrderProtocol.allows(
|
|
74
|
+
"pending",
|
|
75
|
+
"pay",
|
|
76
|
+
"paid",
|
|
77
|
+
);
|
|
78
|
+
// true
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`Next` is the static target projection.
|
|
82
|
+
|
|
83
|
+
`allows` returns the runtime membership boolean. It does not assert a
|
|
84
|
+
target-narrowing type predicate because broad or union source and label values
|
|
85
|
+
do not preserve the correlation required for that narrowing.
|
|
86
|
+
|
|
87
|
+
## 4. Snapshot stability
|
|
88
|
+
|
|
89
|
+
`Protocol.define` snapshots declaration values. Typed declarations use readonly
|
|
90
|
+
arrays and readonly triples. Runtime JavaScript callers may still pass ordinary
|
|
91
|
+
arrays; later mutation of those caller-owned arrays does not change the already
|
|
92
|
+
defined runtime relation.
|
|
93
|
+
|
|
94
|
+
## 5. Set semantics
|
|
95
|
+
|
|
96
|
+
Duplicate triples do not change admissibility. Declaration order is not
|
|
97
|
+
semantic.
|
|
98
|
+
|
|
99
|
+
## 6. Equality
|
|
100
|
+
|
|
101
|
+
Runtime state and label identity follows JavaScript SameValueZero, matching
|
|
102
|
+
`Map` and `Set` key semantics.
|
|
103
|
+
|
|
104
|
+
## 7. Relational semantics
|
|
105
|
+
|
|
106
|
+
Protocol does not require determinism. The same `(from, label)` pair may admit
|
|
107
|
+
multiple target states.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const Routing = Protocol.define([
|
|
111
|
+
["open", "advance", "left"],
|
|
112
|
+
["open", "advance", "right"],
|
|
113
|
+
] as const);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Both targets are admissible. Protocol does not choose one.
|
|
117
|
+
|
|
118
|
+
Protocol also does not require totality. A source/label pair may have no
|
|
119
|
+
declared target.
|
|
120
|
+
|
|
121
|
+
## 8. Execution independence
|
|
122
|
+
|
|
123
|
+
Protocol describes admissibility. It does not choose a target, store current
|
|
124
|
+
state, dispatch events, execute effects, schedule timers, persist state, retry,
|
|
125
|
+
or orchestrate a workflow.
|
|
126
|
+
|
|
127
|
+
Variant may independently own an event vocabulary whose `tag` is used as a
|
|
128
|
+
Protocol label. The Variant payload and the Protocol relation remain separate
|
|
129
|
+
meanings.
|
|
130
|
+
|
|
131
|
+
## 9. Freshness independence
|
|
132
|
+
|
|
133
|
+
An admissible triple does not prove that a persisted or concurrently observed
|
|
134
|
+
source state is still current.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
if (
|
|
138
|
+
OrderProtocol.allows(
|
|
139
|
+
observedState,
|
|
140
|
+
"pay",
|
|
141
|
+
"paid",
|
|
142
|
+
)
|
|
143
|
+
) {
|
|
144
|
+
// A storage owner must still establish
|
|
145
|
+
// that observedState is current when writing.
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Atomic mutation and concurrency checks remain application-owned.
|
|
150
|
+
|
|
151
|
+
A labeled trace is protocol-valid exactly when every
|
|
152
|
+
`(state[i], label[i], state[i + 1])` triple is admissible.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Result law
|
|
2
|
+
|
|
3
|
+
Result represents recoverable success or error with fail-fast composition.
|
|
4
|
+
|
|
5
|
+
Use Result when later work depends on earlier success or when one recoverable
|
|
6
|
+
error value should stop the current dependent path.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
type Result<T, E> =
|
|
10
|
+
| Readonly<{ ok: true; value: T }>
|
|
11
|
+
| Readonly<{ ok: false; error: E }>;
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Constructors preserve the impossible generic axis as `never`.
|
|
15
|
+
|
|
16
|
+
## Data algebra
|
|
17
|
+
|
|
18
|
+
`map` transforms Ok.
|
|
19
|
+
|
|
20
|
+
`mapError` transforms Err.
|
|
21
|
+
|
|
22
|
+
`andThen` sequences a dependent Result-producing step.
|
|
23
|
+
|
|
24
|
+
`orElse` recovers from Err.
|
|
25
|
+
|
|
26
|
+
`flatten` removes one explicit nested Result layer.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const user = await loadUser(userId);
|
|
30
|
+
|
|
31
|
+
if (!user.ok) {
|
|
32
|
+
return user;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return loadAccount(
|
|
36
|
+
user.value.accountId,
|
|
37
|
+
);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Ordinary JavaScript branching is a first-class way to compose Result values.
|
|
41
|
+
|
|
42
|
+
`Result.all` inspects already-materialized inputs in order and returns the
|
|
43
|
+
first Err object itself. Complete success preserves tuple positions. Typed
|
|
44
|
+
input arrays must be readonly so array covariance cannot replace a Result with
|
|
45
|
+
one carrying a different success or error type before the call.
|
|
46
|
+
|
|
47
|
+
## Match elimination
|
|
48
|
+
|
|
49
|
+
Result's Match branch universe is exactly Ok and Err.
|
|
50
|
+
|
|
51
|
+
`Result.match` conforms to the shared [Match law](./match.md). Typed
|
|
52
|
+
elimination requires both branches even when the input is narrowed. Ok passes
|
|
53
|
+
the success value; Err passes the recoverable error value.
|
|
54
|
+
|
|
55
|
+
Result owns recoverable success/failure meaning. Match owns the shared
|
|
56
|
+
elimination behavior and does not capture a handler throw, await a handler
|
|
57
|
+
Promise, or provide the handler object as `this`.
|
|
58
|
+
|
|
59
|
+
## Target-owned conversions
|
|
60
|
+
|
|
61
|
+
`Result.fromOption(option, onNone)` evaluates `onNone` only for None.
|
|
62
|
+
|
|
63
|
+
`Result.fromValidation(validation)` carries Invalid's complete non-empty issue
|
|
64
|
+
collection as one Result error value and preserves that collection object.
|
|
65
|
+
|
|
66
|
+
A Result error may itself be a domain-specific Variant. Result owns
|
|
67
|
+
success/error; Variant owns the closed error vocabulary.
|
|
68
|
+
|
|
69
|
+
## Abrupt capture
|
|
70
|
+
|
|
71
|
+
`attempt` and `wrap` capture one synchronous invocation boundary.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const parsed = attempt(
|
|
75
|
+
() => JSON.parse(text) as unknown,
|
|
76
|
+
(cause) => ({
|
|
77
|
+
kind: "invalid-json" as const,
|
|
78
|
+
cause,
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The caller supplies an `unknown -> E` mapper. A returned Result or other
|
|
84
|
+
ordinary value is success data; capture does not flatten it.
|
|
85
|
+
|
|
86
|
+
A returned Promise-like value, meaning a non-null object or function with a
|
|
87
|
+
callable `then`, violates the synchronous boundary and raises `TypeError`.
|
|
88
|
+
That contract error is outside the user's thrown-value mapper.
|
|
89
|
+
|
|
90
|
+
`wrap` preserves the wrapped function's arguments and `this`.
|
|
91
|
+
|
|
92
|
+
`attemptAsync` and `wrapAsync` own invocation throws plus rejection from the
|
|
93
|
+
returned Promise-like value. Their successful payload follows native
|
|
94
|
+
`Awaited` semantics.
|
|
95
|
+
|
|
96
|
+
Mapper throws and ordinary Result callback throws remain abrupt.
|
|
97
|
+
|
|
98
|
+
## Composition boundary
|
|
99
|
+
|
|
100
|
+
Dependent Result composition uses ordinary JavaScript control flow or the
|
|
101
|
+
data-level `andThen` combinator.
|
|
102
|
+
|
|
103
|
+
Result does not define a generator protocol, asynchronous control runtime,
|
|
104
|
+
implicit early-return syntax, scheduler, retry policy, or cancellation model.
|
|
105
|
+
|
|
106
|
+
For asynchronous work, Promise remains the scheduling and awaiting owner:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const user = await loadUser(userId);
|
|
110
|
+
|
|
111
|
+
if (!user.ok) {
|
|
112
|
+
return user;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return loadAccount(user.value.accountId);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Recoverable-to-abrupt boundary
|
|
119
|
+
|
|
120
|
+
`orThrow(result, mapErrorToThrowable)` returns Ok and explicitly maps Err to a
|
|
121
|
+
thrown JavaScript value.
|
|
122
|
+
|
|
123
|
+
This boundary is intentional and visible; an Err does not throw merely because
|
|
124
|
+
it exists.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Validation law
|
|
2
|
+
|
|
3
|
+
Validation represents valid data or a non-empty ordered issue collection.
|
|
4
|
+
|
|
5
|
+
Use Validation when multiple checks can be evaluated independently from values
|
|
6
|
+
that are already available and callers should receive every issue found in that
|
|
7
|
+
pass.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
type ValidationIssues<E> =
|
|
11
|
+
readonly [E, ...E[]];
|
|
12
|
+
|
|
13
|
+
type Validation<T, E> =
|
|
14
|
+
| Readonly<{ valid: true; value: T }>
|
|
15
|
+
| Readonly<{
|
|
16
|
+
valid: false;
|
|
17
|
+
errors: ValidationIssues<E>;
|
|
18
|
+
}>;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Non-empty invalidity
|
|
22
|
+
|
|
23
|
+
Every Invalid contains at least one issue. The constructor therefore requires
|
|
24
|
+
one first issue.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
Validation.invalid(
|
|
28
|
+
"name-required",
|
|
29
|
+
"email-invalid",
|
|
30
|
+
);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
There is no empty Invalid state.
|
|
34
|
+
|
|
35
|
+
## Independent accumulation
|
|
36
|
+
|
|
37
|
+
Validation accumulates issues only across inputs that are already independently
|
|
38
|
+
available.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const form = Validation.struct([
|
|
42
|
+
["name", validateName(raw.name)],
|
|
43
|
+
["email", validateEmail(raw.email)],
|
|
44
|
+
]);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`Validation.all` traverses tuple positions in order. Complete success
|
|
48
|
+
preserves the tuple shape. Failure concatenates every issue collection in input
|
|
49
|
+
order while retaining each collection's internal order and duplicates. Typed
|
|
50
|
+
input arrays must be readonly so array covariance cannot replace a Validation
|
|
51
|
+
with one carrying a different value or issue type before the call.
|
|
52
|
+
|
|
53
|
+
`Validation.struct` performs the analogous product for one finite readonly tuple
|
|
54
|
+
of readonly `[key, Validation]` entries. Keys are concrete string or symbol
|
|
55
|
+
identities. Complete success produces one ordinary data property per declared
|
|
56
|
+
key. Failure concatenates every issue collection in entry order while retaining
|
|
57
|
+
internal order and duplicates.
|
|
58
|
+
|
|
59
|
+
The typed grammar rejects directly mutable outer or inner tuples, duplicate
|
|
60
|
+
keys, broad string/symbol key spaces, and broad-length or union declarations
|
|
61
|
+
because those do not identify one exact stable keyed product. A readonly view
|
|
62
|
+
over separately mutable backing data remains subject to the TypeScript trust
|
|
63
|
+
model in [SEMANTICS.md](../SEMANTICS.md). Runtime JavaScript callers must supply one array of exact
|
|
64
|
+
pairs, unique string/symbol keys, and structural Validation values with own
|
|
65
|
+
branch fields. Large issue collections are accumulated iteratively.
|
|
66
|
+
|
|
67
|
+
Validation does not schedule checks and does not run dependent work whose input
|
|
68
|
+
does not yet exist.
|
|
69
|
+
|
|
70
|
+
## Match elimination
|
|
71
|
+
|
|
72
|
+
Validation's Match branch universe is exactly Valid and Invalid.
|
|
73
|
+
|
|
74
|
+
`Validation.match` conforms to the shared [Match law](./match.md). Valid
|
|
75
|
+
passes its value. Invalid passes the complete non-empty issue collection as one
|
|
76
|
+
handler payload; Match does not flatten or iterate that collection.
|
|
77
|
+
|
|
78
|
+
Typed elimination requires both branches even when the input is narrowed. The
|
|
79
|
+
selected handler is invoked once without a library-defined receiver, and its
|
|
80
|
+
ordinary return, throw, or Promise completion is preserved.
|
|
81
|
+
|
|
82
|
+
## Mapping and observation
|
|
83
|
+
|
|
84
|
+
`map` transforms Valid.
|
|
85
|
+
|
|
86
|
+
`mapError` transforms each issue exactly once in order.
|
|
87
|
+
|
|
88
|
+
`inspect` synchronously observes Valid and returns the original Validation.
|
|
89
|
+
|
|
90
|
+
`inspectErrors` synchronously observes the complete non-empty issue collection
|
|
91
|
+
as one value and returns the original Validation.
|
|
92
|
+
|
|
93
|
+
`unwrapOr` and `unwrapOrElse` leave Validation through an explicit fallback.
|
|
94
|
+
|
|
95
|
+
## Target-owned conversions
|
|
96
|
+
|
|
97
|
+
`Validation.fromOption(option, onNone)` evaluates `onNone` only for None and
|
|
98
|
+
creates one issue.
|
|
99
|
+
|
|
100
|
+
`Validation.fromResult(result)` turns Err into exactly one issue. An array or
|
|
101
|
+
other aggregate Err value remains one issue value rather than being spread.
|
|
102
|
+
|
|
103
|
+
The reverse boundary is owned by Result:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const ready =
|
|
107
|
+
Result.fromValidation(form);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
That conversion carries Invalid's complete non-empty issue collection as one
|
|
111
|
+
Result error value.
|