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,251 @@
|
|
|
1
|
+
# Variant law
|
|
2
|
+
|
|
3
|
+
Variant represents one exact closed family of labeled alternatives.
|
|
4
|
+
|
|
5
|
+
For one family declaration with case names `K` and payload assignment `P`:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Variant(F) = sum over k in K of P(k)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
A unit case has one nullary alternative. A payload case carries one value of its
|
|
12
|
+
declared payload type.
|
|
13
|
+
|
|
14
|
+
Use Variant when a domain owns a finite alternative vocabulary whose case name
|
|
15
|
+
and payload type must stay correlated.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
const Message = Variant.define("Message", [
|
|
19
|
+
["quit", Variant.unit],
|
|
20
|
+
["write", Variant.payload<string>()],
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
type Message =
|
|
24
|
+
Variant.Value<typeof Message>;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 1. Closed family
|
|
28
|
+
|
|
29
|
+
One `Variant.define` call owns one finite readonly tuple of
|
|
30
|
+
`[caseName, caseSpec]` entries. That tuple is the complete case universe for
|
|
31
|
+
the family.
|
|
32
|
+
|
|
33
|
+
The outer tuple and every case-entry pair must be readonly, its length must be
|
|
34
|
+
statically concrete, and every case name must be one concrete string literal.
|
|
35
|
+
Mutable tuples, broad arrays, optional/union entry sets, duplicate names, and
|
|
36
|
+
patterned/broad string names do not define one exact closed family. Empty
|
|
37
|
+
declarations are valid and produce an uninhabited Variant value type.
|
|
38
|
+
|
|
39
|
+
Declaration order carries no meaning.
|
|
40
|
+
|
|
41
|
+
The finite readonly tuple is part of the typed boundary. Ordinary record width
|
|
42
|
+
subtyping can hide runtime properties behind a narrower static record type, so
|
|
43
|
+
Variant derives the family from an exact tuple rather than record keys. Directly
|
|
44
|
+
mutable declaration tuples are rejected. A readonly view over separately
|
|
45
|
+
mutable backing data can still be invalidated through TypeScript's unsound
|
|
46
|
+
aliasing and is governed by the trust model in
|
|
47
|
+
[SEMANTICS.md](../SEMANTICS.md).
|
|
48
|
+
|
|
49
|
+
Adding or removing a case changes the family declaration and therefore the
|
|
50
|
+
family-level exhaustive handling obligation.
|
|
51
|
+
|
|
52
|
+
## 2. Family identity
|
|
53
|
+
|
|
54
|
+
A string family name gives package-copy-stable named identity. A bound
|
|
55
|
+
`Symbol` gives declaration-owned identity.
|
|
56
|
+
|
|
57
|
+
Named family values use the structural phantom key:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
~selaws.variant:<Name>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Declaration-owned families use the caller-owned symbol as the phantom property
|
|
64
|
+
key.
|
|
65
|
+
|
|
66
|
+
The phantom marker includes the Variant category, closed case-name universe,
|
|
67
|
+
and case signatures. Case-name universes are exact; payload positions retain
|
|
68
|
+
ordinary TypeScript structural variance.
|
|
69
|
+
|
|
70
|
+
Duplicate compatible Selaws installations therefore agree on an identically
|
|
71
|
+
declared named family, while distinct names and distinct caller-owned symbols
|
|
72
|
+
remain separate families.
|
|
73
|
+
|
|
74
|
+
## 3. Case formation
|
|
75
|
+
|
|
76
|
+
`Variant.unit` declares a nullary case.
|
|
77
|
+
|
|
78
|
+
`Variant.payload<T>()` declares a case carrying one `T`.
|
|
79
|
+
|
|
80
|
+
These declaration markers are opaque, package-copy-stable sentinels; their
|
|
81
|
+
runtime representation carries no domain payload.
|
|
82
|
+
|
|
83
|
+
`Variant.define` produces one `make` constructor for every declared case.
|
|
84
|
+
Formation preserves the selected case name and payload:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
Message.make.quit();
|
|
88
|
+
// { tag: "quit" }
|
|
89
|
+
|
|
90
|
+
Message.make.write("hello");
|
|
91
|
+
// { tag: "write", value: "hello" }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A unit case and a payload case remain distinct even when the payload type is
|
|
95
|
+
`undefined` or `never`.
|
|
96
|
+
|
|
97
|
+
## 4. Representation
|
|
98
|
+
|
|
99
|
+
Variant values are ordinary transparent JavaScript objects.
|
|
100
|
+
|
|
101
|
+
A unit case has the representation:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
{ tag: caseName }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
A payload case has the representation:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
{ tag: caseName, value: payload }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The family phantom identity is static TypeScript information. Formation adds no
|
|
114
|
+
runtime brand to the value.
|
|
115
|
+
|
|
116
|
+
Constructed values are ordinary structural data. The family declaration and
|
|
117
|
+
its `make` namespace are immutable; payload values themselves are not
|
|
118
|
+
deep-frozen by Variant.
|
|
119
|
+
|
|
120
|
+
## 5. Exhaustive elimination
|
|
121
|
+
|
|
122
|
+
Variant family `match` conforms to the shared [Match law](./match.md). The
|
|
123
|
+
Variant family owns the branch universe: one branch for every declared case,
|
|
124
|
+
with the payload assignment defined by that family.
|
|
125
|
+
|
|
126
|
+
Typed family `match` requires one handler for every declared case. The
|
|
127
|
+
complete family declaration, rather than the current narrowing of the input
|
|
128
|
+
value, determines the required handler object.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const length = Message.match(
|
|
132
|
+
Message.make.write("hello"),
|
|
133
|
+
{
|
|
134
|
+
quit: () => 0,
|
|
135
|
+
write: (text) => text.length,
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Exactly the selected own data-property handler runs once and without a
|
|
141
|
+
library-defined `this` receiver. Inherited properties and accessors do not
|
|
142
|
+
satisfy handler availability. Unit handlers receive zero arguments. Payload
|
|
143
|
+
handlers receive exactly the stored payload. Unselected handler properties are
|
|
144
|
+
not read by Match execution.
|
|
145
|
+
|
|
146
|
+
At runtime, `match` requires the Variant tag to be an own data property.
|
|
147
|
+
Payload cases also require an own data `value` property. Inherited properties
|
|
148
|
+
and accessors therefore cannot supply either part of the Variant
|
|
149
|
+
representation. `match` then validates that the selected tag belongs to the
|
|
150
|
+
family and that the selected handler is an own function property. TypeScript
|
|
151
|
+
owns whole-handler exhaustiveness for honestly typed calls.
|
|
152
|
+
|
|
153
|
+
Handler return and abrupt completion follow ordinary JavaScript semantics.
|
|
154
|
+
Promise values remain native Promise values.
|
|
155
|
+
|
|
156
|
+
Normal TypeScript discriminant narrowing remains available when local control
|
|
157
|
+
flow owns the branch:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
if (message.tag === "write") {
|
|
161
|
+
message.value;
|
|
162
|
+
// string
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 6. Generic and recursive families
|
|
167
|
+
|
|
168
|
+
A generic family can be an ordinary factory:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const Remote = <T>() =>
|
|
172
|
+
Variant.define("Remote", [
|
|
173
|
+
["idle", Variant.unit],
|
|
174
|
+
["success", Variant.payload<T>()],
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
type Remote<T> =
|
|
178
|
+
Variant.Value<ReturnType<typeof Remote<T>>>;
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Recursive payloads can refer back to the Variant value through an ordinary
|
|
182
|
+
object or interface boundary:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
interface AddPayload {
|
|
186
|
+
readonly left: Expr;
|
|
187
|
+
readonly right: Expr;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const Expr = Variant.define("Expr", [
|
|
191
|
+
["literal", Variant.payload<number>()],
|
|
192
|
+
["add", Variant.payload<AddPayload>()],
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
type Expr =
|
|
196
|
+
Variant.Value<typeof Expr>;
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Variant does not introduce a separate recursive-data runtime.
|
|
200
|
+
|
|
201
|
+
## 7. Snapshot stability
|
|
202
|
+
|
|
203
|
+
`Variant.define` snapshots its case-entry tuple into constructor and
|
|
204
|
+
elimination behavior. Later mutation of caller-owned JavaScript arrays cannot
|
|
205
|
+
change the already-defined family. Typed declarations reject directly mutable outer and inner tuples. A readonly
|
|
206
|
+
view whose backing array is mutated through another alias before definition is
|
|
207
|
+
a TypeScript trust-model escape rather than a guarantee Variant can reify at
|
|
208
|
+
runtime.
|
|
209
|
+
|
|
210
|
+
The returned family and its `make` namespace are immutable runtime
|
|
211
|
+
declarations. Constructed Variant values remain ordinary structural data.
|
|
212
|
+
|
|
213
|
+
## 8. Typed boundary
|
|
214
|
+
|
|
215
|
+
Variant formation consumes already-typed payloads.
|
|
216
|
+
|
|
217
|
+
Runtime schema decoding, normalization, authorization, mutable freshness, and
|
|
218
|
+
version negotiation belong to application owners that can establish those
|
|
219
|
+
facts.
|
|
220
|
+
|
|
221
|
+
The runtime declaration boundary recognizes its finite case grammar. `match`
|
|
222
|
+
checks selected case membership and selected handler availability. Payload type
|
|
223
|
+
validity remains a TypeScript guarantee inside the ordinary Selaws trust model.
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
const UserEvent = Variant.define("UserEvent", [
|
|
227
|
+
["loaded", Variant.payload<User>()],
|
|
228
|
+
]);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
This declaration does not validate unknown JSON as `User`; the decoding
|
|
232
|
+
boundary must do that before constructing `loaded`.
|
|
233
|
+
|
|
234
|
+
## 9. Owner independence
|
|
235
|
+
|
|
236
|
+
Variant owns labeled alternative identity, correlated payload formation, and
|
|
237
|
+
the complete case universe used for elimination. Its family `match` conforms
|
|
238
|
+
to the shared Match law; Match does not become a Variant-owned or independent
|
|
239
|
+
carrier.
|
|
240
|
+
|
|
241
|
+
Protocol owns admissible labeled transitions between application-owned state
|
|
242
|
+
identifiers.
|
|
243
|
+
|
|
244
|
+
Option owns presence.
|
|
245
|
+
|
|
246
|
+
Result owns recoverable success or failure.
|
|
247
|
+
|
|
248
|
+
Validation owns deterministic accumulation of independent issues.
|
|
249
|
+
|
|
250
|
+
Those owners may use sum-shaped structural data or compose values without
|
|
251
|
+
transferring their domain laws to Variant.
|
package/package.json
CHANGED
|
@@ -1,6 +1,90 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "selaws",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Independent semantic primitives for TypeScript with explicit laws.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"typescript",
|
|
8
|
+
"semantic",
|
|
9
|
+
"result",
|
|
10
|
+
"option",
|
|
11
|
+
"validation",
|
|
12
|
+
"nominal-typing",
|
|
13
|
+
"refinement-types",
|
|
14
|
+
"domain-modeling"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"src",
|
|
22
|
+
"docs",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/amatzk/selaws.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/amatzk/selaws",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/amatzk/selaws/issues"
|
|
33
|
+
},
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/index.js",
|
|
38
|
+
"default": "./dist/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./identity": {
|
|
41
|
+
"types": "./dist/identity.d.ts",
|
|
42
|
+
"import": "./dist/identity.js",
|
|
43
|
+
"default": "./dist/identity.js"
|
|
44
|
+
},
|
|
45
|
+
"./evidence": {
|
|
46
|
+
"types": "./dist/evidence.d.ts",
|
|
47
|
+
"import": "./dist/evidence.js",
|
|
48
|
+
"default": "./dist/evidence.js"
|
|
49
|
+
},
|
|
50
|
+
"./protocol": {
|
|
51
|
+
"types": "./dist/protocol.d.ts",
|
|
52
|
+
"import": "./dist/protocol.js",
|
|
53
|
+
"default": "./dist/protocol.js"
|
|
54
|
+
},
|
|
55
|
+
"./variant": {
|
|
56
|
+
"types": "./dist/variant.d.ts",
|
|
57
|
+
"import": "./dist/variant.js",
|
|
58
|
+
"default": "./dist/variant.js"
|
|
59
|
+
},
|
|
60
|
+
"./result": {
|
|
61
|
+
"types": "./dist/result/index.d.ts",
|
|
62
|
+
"import": "./dist/result/index.js",
|
|
63
|
+
"default": "./dist/result/index.js"
|
|
64
|
+
},
|
|
65
|
+
"./option": {
|
|
66
|
+
"types": "./dist/option.d.ts",
|
|
67
|
+
"import": "./dist/option.js",
|
|
68
|
+
"default": "./dist/option.js"
|
|
69
|
+
},
|
|
70
|
+
"./validation": {
|
|
71
|
+
"types": "./dist/validation.d.ts",
|
|
72
|
+
"import": "./dist/validation.js",
|
|
73
|
+
"default": "./dist/validation.js"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@biomejs/biome": "2.5.15",
|
|
78
|
+
"typescript": "7.0.2"
|
|
79
|
+
},
|
|
80
|
+
"scripts": {
|
|
81
|
+
"build": "node scripts/clean-dist.mjs && tsc -p tsconfig.build.json",
|
|
82
|
+
"format": "biome format --write .",
|
|
83
|
+
"lint": "biome lint .",
|
|
84
|
+
"fix": "biome check --write .",
|
|
85
|
+
"test:types": "tsc -p tsconfig.test.json",
|
|
86
|
+
"test:runtime": "node --test test/*.test.mjs",
|
|
87
|
+
"test": "pnpm run build && pnpm run test:types && pnpm run test:runtime",
|
|
88
|
+
"check": "biome ci . && pnpm run test"
|
|
89
|
+
}
|
|
6
90
|
}
|
package/src/evidence.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBigint,
|
|
3
|
+
isBoolean,
|
|
4
|
+
isNumber,
|
|
5
|
+
isString,
|
|
6
|
+
isSymbol,
|
|
7
|
+
type NarrowSymbolSet,
|
|
8
|
+
type Scalar,
|
|
9
|
+
type SingleName,
|
|
10
|
+
type SingleSymbol,
|
|
11
|
+
type Token,
|
|
12
|
+
} from "./internal/scalar.js";
|
|
13
|
+
|
|
14
|
+
export type { Scalar } from "./internal/scalar.js";
|
|
15
|
+
|
|
16
|
+
type EvidenceMarker = {
|
|
17
|
+
readonly "~selaws.evidence": true;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A stable fact established about a scalar value.
|
|
22
|
+
*
|
|
23
|
+
* Independent fact tokens occupy independent phantom properties, so evidence
|
|
24
|
+
* composes while preserving existing identity and evidence on the same scalar.
|
|
25
|
+
*/
|
|
26
|
+
export type Evidence<T extends Scalar, Fact extends symbol> = [Fact] extends [never]
|
|
27
|
+
? never
|
|
28
|
+
: [Fact] extends [NarrowSymbolSet<Fact>]
|
|
29
|
+
? T & {
|
|
30
|
+
readonly [Key in Fact]: EvidenceMarker;
|
|
31
|
+
}
|
|
32
|
+
: never;
|
|
33
|
+
|
|
34
|
+
type Establish<T extends Scalar, Fact extends symbol> = <Value extends T>(
|
|
35
|
+
value: Value,
|
|
36
|
+
) => Evidence<Value, Fact>;
|
|
37
|
+
|
|
38
|
+
/** Defines an owner-scoped formation boundary for one stable fact. */
|
|
39
|
+
export function defineFact<T extends Scalar>() {
|
|
40
|
+
return function define<const Fact extends symbol, Api>(
|
|
41
|
+
token: Fact & SingleSymbol<Fact>,
|
|
42
|
+
build: (establish: Establish<T, Fact>) => Api,
|
|
43
|
+
): Api {
|
|
44
|
+
void token;
|
|
45
|
+
|
|
46
|
+
const establish = <Value extends T>(value: Value): Evidence<Value, Fact> =>
|
|
47
|
+
value as Evidence<Value, Fact>;
|
|
48
|
+
|
|
49
|
+
return build(establish);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type NamedEvidence<T extends Scalar, Name extends string> = T & {
|
|
54
|
+
readonly [Key in `~selaws.evidence:${Name}`]: true;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type FactEvidence<
|
|
58
|
+
T extends Scalar,
|
|
59
|
+
TokenValue extends Token,
|
|
60
|
+
> = TokenValue extends string
|
|
61
|
+
? NamedEvidence<T, TokenValue>
|
|
62
|
+
: TokenValue extends symbol
|
|
63
|
+
? Evidence<T, TokenValue>
|
|
64
|
+
: never;
|
|
65
|
+
|
|
66
|
+
type Predicate<T> = (value: T) => boolean;
|
|
67
|
+
|
|
68
|
+
interface Fact<T extends Scalar, TokenValue extends Token> {
|
|
69
|
+
<Value extends T>(value: Value): FactEvidence<Value, TokenValue> | undefined;
|
|
70
|
+
(value: unknown): FactEvidence<T, TokenValue> | undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface FactFactory<T extends Scalar> {
|
|
74
|
+
<const Name extends string>(
|
|
75
|
+
name: Name & SingleName<Name>,
|
|
76
|
+
predicate: Predicate<T>,
|
|
77
|
+
): Fact<T, Name>;
|
|
78
|
+
|
|
79
|
+
<const Key extends symbol>(
|
|
80
|
+
key: Key & SingleSymbol<Key>,
|
|
81
|
+
predicate: Predicate<T>,
|
|
82
|
+
): Fact<T, Key>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const factFactory = <T extends Scalar>(
|
|
86
|
+
isCarrier: (value: unknown) => value is T,
|
|
87
|
+
): FactFactory<T> =>
|
|
88
|
+
((_token: Token, predicate: Predicate<T>) => (value: unknown) =>
|
|
89
|
+
isCarrier(value) && predicate(value)
|
|
90
|
+
? value
|
|
91
|
+
: undefined) as unknown as FactFactory<T>;
|
|
92
|
+
|
|
93
|
+
type EvidenceFacade = Readonly<{
|
|
94
|
+
bigint: FactFactory<bigint>;
|
|
95
|
+
boolean: FactFactory<boolean>;
|
|
96
|
+
define: typeof defineFact;
|
|
97
|
+
number: FactFactory<number>;
|
|
98
|
+
string: FactFactory<string>;
|
|
99
|
+
symbol: FactFactory<symbol>;
|
|
100
|
+
}>;
|
|
101
|
+
|
|
102
|
+
/** Stable scalar facts with named or declaration-owned evidence. */
|
|
103
|
+
export const evidence: EvidenceFacade = {
|
|
104
|
+
bigint: factFactory(isBigint),
|
|
105
|
+
boolean: factFactory(isBoolean),
|
|
106
|
+
define: defineFact,
|
|
107
|
+
number: factFactory(isNumber),
|
|
108
|
+
string: factFactory(isString),
|
|
109
|
+
symbol: factFactory(isSymbol),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export namespace evidence {
|
|
113
|
+
/** Applies an evidence declaration's fact type to an existing scalar value. */
|
|
114
|
+
export type Proven<F, Value extends Scalar> =
|
|
115
|
+
F extends Fact<infer T, infer TokenValue>
|
|
116
|
+
? Value extends T
|
|
117
|
+
? FactEvidence<Value, TokenValue>
|
|
118
|
+
: never
|
|
119
|
+
: never;
|
|
120
|
+
}
|
package/src/identity.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBigint,
|
|
3
|
+
isBoolean,
|
|
4
|
+
isNumber,
|
|
5
|
+
isString,
|
|
6
|
+
isSymbol,
|
|
7
|
+
type Scalar,
|
|
8
|
+
type SingleName,
|
|
9
|
+
type SingleSymbol,
|
|
10
|
+
type Token,
|
|
11
|
+
} from "./internal/scalar.js";
|
|
12
|
+
|
|
13
|
+
export type { Scalar } from "./internal/scalar.js";
|
|
14
|
+
|
|
15
|
+
type IdentityMarker = {
|
|
16
|
+
readonly "~selaws.identity": true;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A scalar carrying one declaration-owned nominal identity.
|
|
21
|
+
*
|
|
22
|
+
* The domain token is the phantom property key itself, so identity belongs to
|
|
23
|
+
* the declaration that owns the token and remains stable across duplicate
|
|
24
|
+
* Selaws installations.
|
|
25
|
+
*/
|
|
26
|
+
export type Identity<T extends Scalar, Id extends symbol> = [Id] extends [never]
|
|
27
|
+
? never
|
|
28
|
+
: [Id] extends [SingleSymbol<Id>]
|
|
29
|
+
? T & {
|
|
30
|
+
readonly [Key in Id]: IdentityMarker;
|
|
31
|
+
}
|
|
32
|
+
: never;
|
|
33
|
+
|
|
34
|
+
type Mint<T extends Scalar, Id extends symbol> = <Value extends T>(
|
|
35
|
+
value: Value,
|
|
36
|
+
) => Identity<Value, Id>;
|
|
37
|
+
|
|
38
|
+
/** Defines an owner-scoped formation boundary for one nominal identity. */
|
|
39
|
+
export function defineIdentity<T extends Scalar>() {
|
|
40
|
+
return function define<const Id extends symbol, Api>(
|
|
41
|
+
token: Id & SingleSymbol<Id>,
|
|
42
|
+
build: (mint: Mint<T, Id>) => Api,
|
|
43
|
+
): Api {
|
|
44
|
+
void token;
|
|
45
|
+
|
|
46
|
+
const mint = <Value extends T>(value: Value): Identity<Value, Id> =>
|
|
47
|
+
value as Identity<Value, Id>;
|
|
48
|
+
|
|
49
|
+
return build(mint);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type NamedIdentity<T extends Scalar, Name extends string> = T & {
|
|
54
|
+
readonly [Key in `~selaws.identity:${Name}`]: true;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type DomainIdentity<
|
|
58
|
+
T extends Scalar,
|
|
59
|
+
TokenValue extends Token,
|
|
60
|
+
> = TokenValue extends string
|
|
61
|
+
? NamedIdentity<T, TokenValue>
|
|
62
|
+
: TokenValue extends symbol
|
|
63
|
+
? Identity<T, TokenValue>
|
|
64
|
+
: never;
|
|
65
|
+
|
|
66
|
+
type Predicate<T> = (value: T) => boolean;
|
|
67
|
+
|
|
68
|
+
interface OpenIdentity<T extends Scalar, TokenValue extends Token> {
|
|
69
|
+
<Value extends T>(value: Value): DomainIdentity<Value, TokenValue>;
|
|
70
|
+
(value: unknown): DomainIdentity<T, TokenValue> | undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface CheckedIdentity<T extends Scalar, TokenValue extends Token> {
|
|
74
|
+
<Value extends T>(value: Value): DomainIdentity<Value, TokenValue> | undefined;
|
|
75
|
+
(value: unknown): DomainIdentity<T, TokenValue> | undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface IdentityFactory<T extends Scalar> {
|
|
79
|
+
<const Name extends string>(name: Name & SingleName<Name>): OpenIdentity<T, Name>;
|
|
80
|
+
|
|
81
|
+
<const Name extends string>(
|
|
82
|
+
name: Name & SingleName<Name>,
|
|
83
|
+
predicate: Predicate<T>,
|
|
84
|
+
): CheckedIdentity<T, Name>;
|
|
85
|
+
|
|
86
|
+
<const Key extends symbol>(key: Key & SingleSymbol<Key>): OpenIdentity<T, Key>;
|
|
87
|
+
|
|
88
|
+
<const Key extends symbol>(
|
|
89
|
+
key: Key & SingleSymbol<Key>,
|
|
90
|
+
predicate: Predicate<T>,
|
|
91
|
+
): CheckedIdentity<T, Key>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const identityFactory = <T extends Scalar>(
|
|
95
|
+
isCarrier: (value: unknown) => value is T,
|
|
96
|
+
): IdentityFactory<T> =>
|
|
97
|
+
((_token: Token, predicate?: Predicate<T>) => (value: unknown) =>
|
|
98
|
+
isCarrier(value) && (predicate === undefined || predicate(value))
|
|
99
|
+
? value
|
|
100
|
+
: undefined) as unknown as IdentityFactory<T>;
|
|
101
|
+
|
|
102
|
+
type IdentityFacade = Readonly<{
|
|
103
|
+
bigint: IdentityFactory<bigint>;
|
|
104
|
+
boolean: IdentityFactory<boolean>;
|
|
105
|
+
define: typeof defineIdentity;
|
|
106
|
+
number: IdentityFactory<number>;
|
|
107
|
+
string: IdentityFactory<string>;
|
|
108
|
+
symbol: IdentityFactory<symbol>;
|
|
109
|
+
}>;
|
|
110
|
+
|
|
111
|
+
/** Scalar identity formation with named or declaration-owned identity. */
|
|
112
|
+
export const identity: IdentityFacade = {
|
|
113
|
+
bigint: identityFactory(isBigint),
|
|
114
|
+
boolean: identityFactory(isBoolean),
|
|
115
|
+
define: defineIdentity,
|
|
116
|
+
number: identityFactory(isNumber),
|
|
117
|
+
string: identityFactory(isString),
|
|
118
|
+
symbol: identityFactory(isSymbol),
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export namespace identity {
|
|
122
|
+
/** Extracts the scalar identity value type produced by an identity declaration. */
|
|
123
|
+
export type Value<Domain> =
|
|
124
|
+
Domain extends OpenIdentity<infer T, infer TokenValue>
|
|
125
|
+
? DomainIdentity<T, TokenValue>
|
|
126
|
+
: Domain extends CheckedIdentity<infer T, infer TokenValue>
|
|
127
|
+
? DomainIdentity<T, TokenValue>
|
|
128
|
+
: never;
|
|
129
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Option as OptionFacade } from "./option.js";
|
|
2
|
+
import { Protocol as ProtocolFacade } from "./protocol.js";
|
|
3
|
+
import { Result as ResultFacade } from "./result/core.js";
|
|
4
|
+
import { Validation as ValidationFacade } from "./validation.js";
|
|
5
|
+
import { Variant as VariantFacade } from "./variant.js";
|
|
6
|
+
|
|
7
|
+
export { type Evidence, evidence } from "./evidence.js";
|
|
8
|
+
export { type Identity, identity, type Scalar } from "./identity.js";
|
|
9
|
+
|
|
10
|
+
/** Root-entry Protocol carrier paired with the Protocol facade value. */
|
|
11
|
+
export type Protocol<
|
|
12
|
+
Transitions extends readonly import("./protocol.js").Transition[],
|
|
13
|
+
> = import("./protocol.js").Protocol<Transitions>;
|
|
14
|
+
/** Root-entry Protocol facade. */
|
|
15
|
+
export const Protocol: typeof ProtocolFacade = ProtocolFacade;
|
|
16
|
+
|
|
17
|
+
/** Root-entry Variant carrier paired with the Variant facade value. */
|
|
18
|
+
export type Variant<Family> = import("./variant.js").Variant<Family>;
|
|
19
|
+
/** Root-entry Variant facade. */
|
|
20
|
+
export const Variant: typeof VariantFacade = VariantFacade;
|
|
21
|
+
export namespace Variant {
|
|
22
|
+
/** Extracts the closed value union produced by one Variant family. */
|
|
23
|
+
export type Value<Family> = import("./variant.js").Variant<Family>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type { None, OptionValue, Some } from "./option.js";
|
|
27
|
+
/** Root-entry Option carrier paired with the Option facade value. */
|
|
28
|
+
export type Option<T> = import("./option.js").Option<T>;
|
|
29
|
+
/** Root-entry Option facade. */
|
|
30
|
+
export const Option: typeof OptionFacade = OptionFacade;
|
|
31
|
+
|
|
32
|
+
export type {
|
|
33
|
+
Invalid,
|
|
34
|
+
Valid,
|
|
35
|
+
ValidationError,
|
|
36
|
+
ValidationIssues,
|
|
37
|
+
ValidationValue,
|
|
38
|
+
} from "./validation.js";
|
|
39
|
+
/** Root-entry Validation carrier paired with the Validation facade value. */
|
|
40
|
+
export type Validation<T, E> = import("./validation.js").Validation<T, E>;
|
|
41
|
+
/** Root-entry Validation facade. */
|
|
42
|
+
export const Validation: typeof ValidationFacade = ValidationFacade;
|
|
43
|
+
|
|
44
|
+
export type {
|
|
45
|
+
AsyncResult,
|
|
46
|
+
Err,
|
|
47
|
+
Ok,
|
|
48
|
+
ResultError,
|
|
49
|
+
ResultValue,
|
|
50
|
+
} from "./result/core.js";
|
|
51
|
+
/** Root-entry Result carrier paired with the Result facade value. */
|
|
52
|
+
export type Result<T, E> = import("./result/core.js").Result<T, E>;
|
|
53
|
+
/** Root-entry Result facade. */
|
|
54
|
+
export const Result: typeof ResultFacade = ResultFacade;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
type Same<A, B> =
|
|
2
|
+
(<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
|
|
3
|
+
? (<T>() => T extends B ? 1 : 2) extends <T>() => T extends A ? 1 : 2
|
|
4
|
+
? true
|
|
5
|
+
: false
|
|
6
|
+
: false;
|
|
7
|
+
|
|
8
|
+
type UnknownCallback = (this: void, ...args: never[]) => unknown;
|
|
9
|
+
|
|
10
|
+
type OverloadUnion<
|
|
11
|
+
Callback,
|
|
12
|
+
Partial = unknown,
|
|
13
|
+
Previous = never,
|
|
14
|
+
Repeated extends boolean = false,
|
|
15
|
+
Accumulated = never,
|
|
16
|
+
> = Callback extends (this: infer This, ...args: infer Args) => infer Result
|
|
17
|
+
? ((this: This, ...args: Args) => Result) extends infer Current
|
|
18
|
+
? Same<Current, Previous> extends true
|
|
19
|
+
? Repeated extends true
|
|
20
|
+
? Accumulated | UnknownCallback
|
|
21
|
+
: Partial extends Callback
|
|
22
|
+
? Accumulated
|
|
23
|
+
: OverloadUnion<
|
|
24
|
+
Partial & Callback,
|
|
25
|
+
Partial & Current,
|
|
26
|
+
Current,
|
|
27
|
+
true,
|
|
28
|
+
Accumulated | Current
|
|
29
|
+
>
|
|
30
|
+
: Partial extends Callback
|
|
31
|
+
? Accumulated
|
|
32
|
+
: OverloadUnion<
|
|
33
|
+
Partial & Callback,
|
|
34
|
+
Partial & Current,
|
|
35
|
+
Current,
|
|
36
|
+
false,
|
|
37
|
+
Accumulated | Current
|
|
38
|
+
>
|
|
39
|
+
: Accumulated
|
|
40
|
+
: Accumulated;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extracts a conservative union of callable return types.
|
|
44
|
+
*
|
|
45
|
+
* TypeScript exposes overloads as intersections. Generic callable reflection
|
|
46
|
+
* can repeat one instantiated signature indefinitely; repeated reflection
|
|
47
|
+
* widens to unknown instead of exhausting compiler instantiation depth.
|
|
48
|
+
*/
|
|
49
|
+
export type CallbackResult<Callback> =
|
|
50
|
+
OverloadUnion<Callback> extends infer One
|
|
51
|
+
? One extends (...args: never[]) => infer Result
|
|
52
|
+
? Result
|
|
53
|
+
: never
|
|
54
|
+
: never;
|