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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +355 -2
  3. package/dist/evidence.d.ts +45 -0
  4. package/dist/evidence.d.ts.map +1 -0
  5. package/dist/evidence.js +22 -0
  6. package/dist/evidence.js.map +1 -0
  7. package/dist/identity.d.ts +52 -0
  8. package/dist/identity.d.ts.map +1 -0
  9. package/dist/identity.js +22 -0
  10. package/dist/identity.js.map +1 -0
  11. package/dist/index.d.ts +35 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +18 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/internal/callback.d.ts +13 -0
  16. package/dist/internal/callback.d.ts.map +1 -0
  17. package/dist/internal/callback.js +2 -0
  18. package/dist/internal/callback.js.map +1 -0
  19. package/dist/internal/promise-like.d.ts +8 -0
  20. package/dist/internal/promise-like.d.ts.map +1 -0
  21. package/dist/internal/promise-like.js +12 -0
  22. package/dist/internal/promise-like.js.map +1 -0
  23. package/dist/internal/scalar.d.ts +18 -0
  24. package/dist/internal/scalar.d.ts.map +1 -0
  25. package/dist/internal/scalar.js +6 -0
  26. package/dist/internal/scalar.js.map +1 -0
  27. package/dist/option.d.ts +90 -0
  28. package/dist/option.d.ts.map +1 -0
  29. package/dist/option.js +96 -0
  30. package/dist/option.js.map +1 -0
  31. package/dist/protocol.d.ts +42 -0
  32. package/dist/protocol.d.ts.map +1 -0
  33. package/dist/protocol.js +67 -0
  34. package/dist/protocol.js.map +1 -0
  35. package/dist/result/capture.d.ts +42 -0
  36. package/dist/result/capture.d.ts.map +1 -0
  37. package/dist/result/capture.js +41 -0
  38. package/dist/result/capture.js.map +1 -0
  39. package/dist/result/core.d.ts +98 -0
  40. package/dist/result/core.d.ts.map +1 -0
  41. package/dist/result/core.js +103 -0
  42. package/dist/result/core.js.map +1 -0
  43. package/dist/result/index.d.ts +4 -0
  44. package/dist/result/index.d.ts.map +1 -0
  45. package/dist/result/index.js +4 -0
  46. package/dist/result/index.js.map +1 -0
  47. package/dist/result/throw.d.ts +4 -0
  48. package/dist/result/throw.d.ts.map +1 -0
  49. package/dist/result/throw.js +8 -0
  50. package/dist/result/throw.js.map +1 -0
  51. package/dist/validation.d.ts +126 -0
  52. package/dist/validation.d.ts.map +1 -0
  53. package/dist/validation.js +240 -0
  54. package/dist/validation.js.map +1 -0
  55. package/dist/variant.d.ts +97 -0
  56. package/dist/variant.d.ts.map +1 -0
  57. package/dist/variant.js +102 -0
  58. package/dist/variant.js.map +1 -0
  59. package/docs/API.md +543 -0
  60. package/docs/GUIDE.md +739 -0
  61. package/docs/SEMANTICS.md +319 -0
  62. package/docs/laws/evidence.md +113 -0
  63. package/docs/laws/identity.md +126 -0
  64. package/docs/laws/match.md +163 -0
  65. package/docs/laws/option.md +100 -0
  66. package/docs/laws/protocol.md +152 -0
  67. package/docs/laws/result.md +124 -0
  68. package/docs/laws/validation.md +111 -0
  69. package/docs/laws/variant.md +251 -0
  70. package/package.json +87 -3
  71. package/src/evidence.ts +120 -0
  72. package/src/identity.ts +129 -0
  73. package/src/index.ts +54 -0
  74. package/src/internal/callback.ts +54 -0
  75. package/src/internal/promise-like.ts +32 -0
  76. package/src/internal/scalar.ts +43 -0
  77. package/src/option.ts +214 -0
  78. package/src/protocol.ts +174 -0
  79. package/src/result/capture.ts +209 -0
  80. package/src/result/core.ts +229 -0
  81. package/src/result/index.ts +3 -0
  82. package/src/result/throw.ts +13 -0
  83. package/src/validation.ts +491 -0
  84. 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.