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,319 @@
|
|
|
1
|
+
# Selaws semantics
|
|
2
|
+
|
|
3
|
+
Selaws is one distribution package containing independent semantic owners.
|
|
4
|
+
Package co-location does not merge their laws.
|
|
5
|
+
|
|
6
|
+
| Owner | Question |
|
|
7
|
+
| --- | --- |
|
|
8
|
+
| Identity | What scalar value is this? |
|
|
9
|
+
| Evidence | What stable fact is established about this scalar? |
|
|
10
|
+
| Protocol | Which labeled state transitions are admissible? |
|
|
11
|
+
| Variant | Which closed labeled alternative is this, and what payload does it carry? |
|
|
12
|
+
| Option | Is a value present? |
|
|
13
|
+
| Validation | Which independently available checks have issues? |
|
|
14
|
+
| Result | Did a recoverable computation succeed? |
|
|
15
|
+
|
|
16
|
+
The detailed owner contracts are:
|
|
17
|
+
|
|
18
|
+
- [Identity](./laws/identity.md)
|
|
19
|
+
- [Evidence](./laws/evidence.md)
|
|
20
|
+
- [Protocol](./laws/protocol.md)
|
|
21
|
+
- [Variant](./laws/variant.md)
|
|
22
|
+
- [Option](./laws/option.md)
|
|
23
|
+
- [Validation](./laws/validation.md)
|
|
24
|
+
- [Result](./laws/result.md)
|
|
25
|
+
|
|
26
|
+
Cross-owner semantics can also be first-class shared laws without becoming new
|
|
27
|
+
owners. The shared Match contract is [laws/match.md](./laws/match.md).
|
|
28
|
+
|
|
29
|
+
The [Guide](./GUIDE.md) shows application patterns. This file defines the
|
|
30
|
+
shared package laws those patterns must preserve.
|
|
31
|
+
|
|
32
|
+
## 1. Semantic selection law
|
|
33
|
+
|
|
34
|
+
Choose an owner by the meaning the application needs, not by implementation
|
|
35
|
+
shape.
|
|
36
|
+
|
|
37
|
+
A tagged union does not automatically imply Variant. Option, Validation, and
|
|
38
|
+
Result are also sum-shaped, but their branches carry different laws:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
Variant closed alternative identity + correlated payload
|
|
42
|
+
Option presence / reasonless absence
|
|
43
|
+
Validation independent issue accumulation
|
|
44
|
+
Result recoverable success / fail-fast error
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Likewise, a list of state-like strings does not automatically imply Protocol.
|
|
48
|
+
Protocol begins only when the application needs an admissible labeled relation
|
|
49
|
+
between scalar identifiers.
|
|
50
|
+
|
|
51
|
+
Using one owner as a carrier for another owner's meaning does not transfer the
|
|
52
|
+
second owner's laws automatically.
|
|
53
|
+
|
|
54
|
+
## 2. Ownership law
|
|
55
|
+
|
|
56
|
+
Each primitive owns one dominant meaning. Similar implementation shapes do not
|
|
57
|
+
create a shared semantic owner.
|
|
58
|
+
|
|
59
|
+
Cross-owner operations are named on the target owner because the conversion
|
|
60
|
+
introduces the target meaning:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
Result.fromOption
|
|
64
|
+
Result.fromValidation
|
|
65
|
+
|
|
66
|
+
Validation.fromOption
|
|
67
|
+
Validation.fromResult
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The source information preserved by each boundary is part of the target
|
|
71
|
+
owner's law.
|
|
72
|
+
|
|
73
|
+
Protocol is independent of every other semantic owner. It consumes
|
|
74
|
+
application-owned scalar identifiers without importing Identity, Evidence, or
|
|
75
|
+
Variant meaning.
|
|
76
|
+
|
|
77
|
+
Variant owns exact closed labeled alternatives and correlated payload
|
|
78
|
+
formation. Option, Validation, and Result retain their presence, accumulation,
|
|
79
|
+
and recoverable-failure meanings even when their carriers are also tagged
|
|
80
|
+
unions.
|
|
81
|
+
|
|
82
|
+
Identity and Evidence remain independent scalar meanings. Identity says which
|
|
83
|
+
domain scalar this is. Evidence says which stable fact has been established
|
|
84
|
+
about that same scalar.
|
|
85
|
+
|
|
86
|
+
Application code composes owners through ordinary typed values.
|
|
87
|
+
|
|
88
|
+
## 3. Composition law
|
|
89
|
+
|
|
90
|
+
Owner independence does not prohibit useful composition. It determines which
|
|
91
|
+
owner is responsible for each statement.
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
Identity + Evidence
|
|
97
|
+
UserId carrying a separately established NonEmpty fact
|
|
98
|
+
|
|
99
|
+
Variant inside Result
|
|
100
|
+
Result<User, LoadUserError>
|
|
101
|
+
where LoadUserError is a closed Variant family
|
|
102
|
+
|
|
103
|
+
Validation -> Result
|
|
104
|
+
independent field issues accumulate first,
|
|
105
|
+
then the complete issue collection becomes one Result error
|
|
106
|
+
|
|
107
|
+
Variant label + Protocol
|
|
108
|
+
Variant owns event payload/case identity;
|
|
109
|
+
Protocol can use the scalar event tag as a transition label
|
|
110
|
+
|
|
111
|
+
Promise<Result<T,E>>
|
|
112
|
+
Promise owns scheduling/awaiting;
|
|
113
|
+
Result owns recoverable success/error data
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Composition must preserve those boundaries. A convenience helper that makes one
|
|
117
|
+
owner silently perform another owner's job changes the semantic surface and
|
|
118
|
+
requires its own owner justification.
|
|
119
|
+
|
|
120
|
+
## 4. Match law
|
|
121
|
+
|
|
122
|
+
Match is the shared elimination law for Option, Result, Validation, and Variant.
|
|
123
|
+
It is not another semantic owner and introduces no root value, type, dispatcher,
|
|
124
|
+
or package subpath.
|
|
125
|
+
|
|
126
|
+
The owning carrier determines the complete branch universe and the payload
|
|
127
|
+
associated with each branch. Typed Match requires a handler for every branch in
|
|
128
|
+
that universe even when the current value is already narrowed.
|
|
129
|
+
|
|
130
|
+
For the selected branch, Match:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
invokes exactly one handler exactly once
|
|
134
|
+
does not read or invoke unselected handler properties
|
|
135
|
+
preserves the owner-defined branch payload and arity
|
|
136
|
+
supplies no library-defined this receiver
|
|
137
|
+
returns the selected handler completion unchanged
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Therefore a returned Promise remains a native Promise and a thrown handler
|
|
141
|
+
remains abrupt. Match does not capture, await, wrap, or normalize completion.
|
|
142
|
+
|
|
143
|
+
Runtime validation beyond those shared elimination laws remains owner-specific.
|
|
144
|
+
Variant can validate its runtime family representation because Variant owns a
|
|
145
|
+
runtime case declaration. Fixed structural owners retain their own typed
|
|
146
|
+
boundaries.
|
|
147
|
+
|
|
148
|
+
The public grammar remains owner-scoped:
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
Option.match
|
|
152
|
+
Result.match
|
|
153
|
+
Validation.match
|
|
154
|
+
VariantFamily.match
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
A centralized `Match(value, handlers)` could not recover semantic ownership
|
|
158
|
+
from transparent structural data, while `Match(owner, value, handlers)` would
|
|
159
|
+
duplicate the owner and weaken TypeScript inference. Shared law therefore does
|
|
160
|
+
not imply shared dispatch.
|
|
161
|
+
|
|
162
|
+
## 5. Representation law
|
|
163
|
+
|
|
164
|
+
Identity and Evidence enrich immutable JavaScript scalar values:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
string | number | bigint | boolean | symbol
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Successful formation returns that same primitive representation.
|
|
171
|
+
|
|
172
|
+
Protocol uses those JavaScript scalar kinds as application-owned state and
|
|
173
|
+
label identifiers. `Protocol.define` snapshots readonly
|
|
174
|
+
`[from, label, to]` declarations into a private membership relation. The
|
|
175
|
+
runtime relation is not application state.
|
|
176
|
+
|
|
177
|
+
Variant snapshots a finite case declaration into immutable family constructors
|
|
178
|
+
and elimination behavior. Its values remain transparent tagged JavaScript
|
|
179
|
+
objects:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
{ tag: caseName }
|
|
183
|
+
{ tag: caseName, value: payload }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Variant phantom family identity adds no runtime brand.
|
|
187
|
+
|
|
188
|
+
Option, Validation, and Result also use ordinary structural object and array
|
|
189
|
+
data. Their TypeScript `Readonly` contracts describe typed use; runtime values
|
|
190
|
+
remain transparent JavaScript data.
|
|
191
|
+
|
|
192
|
+
Transparent representation is not permission to bypass each owner's formation
|
|
193
|
+
and boundary laws in typed application code.
|
|
194
|
+
|
|
195
|
+
## 6. Phantom identity law
|
|
196
|
+
|
|
197
|
+
Named identity, evidence, and Variant families use Selaws-owned,
|
|
198
|
+
package-copy-stable structural keys:
|
|
199
|
+
|
|
200
|
+
```text
|
|
201
|
+
~selaws.identity:<Name>
|
|
202
|
+
~selaws.evidence:<Name>
|
|
203
|
+
~selaws.variant:<Name>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
A concrete string name therefore defines a shared structural identity contract.
|
|
207
|
+
|
|
208
|
+
Declaration-owned identity, evidence, and Variant families use a caller-owned
|
|
209
|
+
symbol as the phantom property key. The bound symbol determines declaration
|
|
210
|
+
identity.
|
|
211
|
+
|
|
212
|
+
The category payloads remain distinct. Reusing one symbol for independent
|
|
213
|
+
owners does not make Identity imply Evidence or make either owner become
|
|
214
|
+
Variant.
|
|
215
|
+
|
|
216
|
+
Variant family markers additionally retain the closed case-name universe and
|
|
217
|
+
case signatures needed for typed compatibility. Case-name universes are exact;
|
|
218
|
+
payload positions retain ordinary TypeScript structural variance.
|
|
219
|
+
|
|
220
|
+
These encodings are compatibility law: duplicate compatible Selaws
|
|
221
|
+
installations can agree on the same declared named meaning without sharing a
|
|
222
|
+
package-local unique-symbol brand.
|
|
223
|
+
|
|
224
|
+
## 7. Completion law
|
|
225
|
+
|
|
226
|
+
Ordinary transformation, recovery, fallback, observation, predicate,
|
|
227
|
+
conversion, and Variant elimination callbacks follow JavaScript completion
|
|
228
|
+
semantics. A thrown callback remains abrupt unless an explicit Result capture
|
|
229
|
+
boundary owns the conversion.
|
|
230
|
+
|
|
231
|
+
Synchronous observation helpers require synchronous completion. Promise-like
|
|
232
|
+
completion is rejected rather than silently discarded. At runtime,
|
|
233
|
+
Promise-like means a non-null object or function with a callable `then`.
|
|
234
|
+
|
|
235
|
+
Result capture helpers are the explicit boundary that maps thrown or rejected
|
|
236
|
+
JavaScript completion into recoverable Result error data.
|
|
237
|
+
|
|
238
|
+
## 8. Async law
|
|
239
|
+
|
|
240
|
+
Native Promise composition owns scheduling, awaiting, and rejection.
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
Promise<Result<T, E>>
|
|
244
|
+
Promise<Option<T>>
|
|
245
|
+
Promise<Validation<T, E>>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Selaws does not introduce an asynchronous carrier wrapper.
|
|
249
|
+
|
|
250
|
+
Result does not introduce a generator or asynchronous control-flow runtime.
|
|
251
|
+
Dependent async composition remains ordinary JavaScript control flow over
|
|
252
|
+
`Promise<Result<T, E>>` values.
|
|
253
|
+
|
|
254
|
+
Independent asynchronous work may be scheduled with Promise first and then
|
|
255
|
+
combined by the relevant Selaws data owner.
|
|
256
|
+
|
|
257
|
+
## 9. Information law
|
|
258
|
+
|
|
259
|
+
Owner boundaries preserve information according to the target contract.
|
|
260
|
+
|
|
261
|
+
- Option None gains an error only when converted to Result.
|
|
262
|
+
- Option None gains an issue only when converted to Validation.
|
|
263
|
+
- Invalid's complete non-empty issue collection becomes one Result error value.
|
|
264
|
+
- One Result Err value becomes one Validation issue, even when that value is
|
|
265
|
+
itself an array.
|
|
266
|
+
- Variant payloads remain attached to their declared case; conversion through
|
|
267
|
+
another owner must not erase that correlation unless that boundary explicitly
|
|
268
|
+
defines such a projection.
|
|
269
|
+
- Protocol labels remain part of relation identity even when source and target
|
|
270
|
+
states are otherwise equal.
|
|
271
|
+
|
|
272
|
+
Applications can state a different projection explicitly before or after an
|
|
273
|
+
owner boundary.
|
|
274
|
+
|
|
275
|
+
## 10. Snapshot law
|
|
276
|
+
|
|
277
|
+
Protocol and Variant both define immutable runtime declarations from
|
|
278
|
+
caller-provided declaration data.
|
|
279
|
+
|
|
280
|
+
`Protocol.define` snapshots transition triples.
|
|
281
|
+
|
|
282
|
+
`Variant.define` snapshots case names and case kinds.
|
|
283
|
+
|
|
284
|
+
Protocol and Variant typed declarations require readonly finite tuples and
|
|
285
|
+
reject directly mutable declaration arrays. This prevents mutation through the
|
|
286
|
+
declaration type itself. TypeScript can still create a readonly view over a
|
|
287
|
+
separately mutable alias and mutate the same backing array before definition;
|
|
288
|
+
that language-level unsound aliasing is governed by the trust model below.
|
|
289
|
+
|
|
290
|
+
At runtime, both owners snapshot caller-provided declaration data. Later
|
|
291
|
+
mutation of caller-owned JavaScript arrays does not change the already-defined
|
|
292
|
+
Protocol or Variant family.
|
|
293
|
+
|
|
294
|
+
Snapshotting the declaration does not freeze application payloads, application
|
|
295
|
+
state, or external storage.
|
|
296
|
+
|
|
297
|
+
## 11. TypeScript trust model
|
|
298
|
+
|
|
299
|
+
Selaws expresses semantic guarantees for values whose runtime state is still
|
|
300
|
+
described by their ordinary TypeScript type. Formation APIs centralize honest
|
|
301
|
+
introduction of phantom meaning. Assertions, `any`, and TypeScript's unsound mutable aliasing can break that
|
|
302
|
+
relationship; Selaws does not reify erased static types at runtime. A readonly
|
|
303
|
+
view is therefore trusted to describe the current backing value when it crosses
|
|
304
|
+
a Selaws typed boundary.
|
|
305
|
+
|
|
306
|
+
Protocol runtime membership establishes only whether one concrete triple was
|
|
307
|
+
declared. It does not establish authorization, current-state freshness, or an
|
|
308
|
+
atomic state mutation.
|
|
309
|
+
|
|
310
|
+
Variant runtime formation establishes the selected declared case and preserves
|
|
311
|
+
the supplied payload. Runtime payload schema validity still belongs to the
|
|
312
|
+
application boundary that decoded or produced that value.
|
|
313
|
+
|
|
314
|
+
Identity and Evidence predicates can validate their own scalar formation or
|
|
315
|
+
fact condition. They are not general object-schema decoders.
|
|
316
|
+
|
|
317
|
+
Runtime authorization, mutable freshness, revocation, schema decoding,
|
|
318
|
+
normalization, persistence, version negotiation, and resource enforcement
|
|
319
|
+
remain owned by application layers that can establish those facts at runtime.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Evidence law
|
|
2
|
+
|
|
3
|
+
Evidence answers:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
What stable fact is established about this scalar value?
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Use Evidence when the application already has the right scalar value and must
|
|
10
|
+
record that a separate predicate has been established without replacing the
|
|
11
|
+
value's identity.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const NonEmpty = evidence.string(
|
|
15
|
+
"NonEmpty",
|
|
16
|
+
(value) => value.length > 0,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const checked = NonEmpty(userId);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Evidence applies to immutable scalar carriers, so aliasing cannot mutate the
|
|
23
|
+
underlying value after the fact is established.
|
|
24
|
+
|
|
25
|
+
## Named evidence
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { evidence } from "selaws/evidence";
|
|
29
|
+
|
|
30
|
+
const NonEmpty = evidence.string(
|
|
31
|
+
"NonEmpty",
|
|
32
|
+
(value) => value.length > 0,
|
|
33
|
+
);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
A concrete literal name maps to
|
|
37
|
+
`~selaws.evidence:<Name>`. This Selaws-owned key keeps compatible producers
|
|
38
|
+
and duplicate Selaws installations structurally compatible.
|
|
39
|
+
|
|
40
|
+
Every Evidence factory has a predicate because evidence is introduced only
|
|
41
|
+
after the fact is established.
|
|
42
|
+
|
|
43
|
+
## Declaration-owned evidence
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import {
|
|
47
|
+
defineFact,
|
|
48
|
+
type Evidence,
|
|
49
|
+
} from "selaws/evidence";
|
|
50
|
+
|
|
51
|
+
const nonEmptyKey: unique symbol =
|
|
52
|
+
Symbol("NonEmpty");
|
|
53
|
+
|
|
54
|
+
type NonEmpty<T extends string> =
|
|
55
|
+
Evidence<T, typeof nonEmptyKey>;
|
|
56
|
+
|
|
57
|
+
const NonEmpty = defineFact<string>()(
|
|
58
|
+
nonEmptyKey,
|
|
59
|
+
(establish) => ({
|
|
60
|
+
check<T extends string>(value: T) {
|
|
61
|
+
return value.length > 0
|
|
62
|
+
? establish(value)
|
|
63
|
+
: undefined;
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
A non-empty finite set of narrow symbol fact keys represents accumulated
|
|
70
|
+
declaration-owned evidence.
|
|
71
|
+
|
|
72
|
+
`defineFact` keeps the establishment operation private to the declaration
|
|
73
|
+
callback so the declaring module owns how the fact becomes available.
|
|
74
|
+
|
|
75
|
+
## Composition
|
|
76
|
+
|
|
77
|
+
Establishing evidence preserves identity and earlier evidence on the same
|
|
78
|
+
scalar.
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
UserId
|
|
82
|
+
+ NonEmpty
|
|
83
|
+
+ Ascii
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Identity and Evidence use distinct phantom categories. Reusing one symbol for
|
|
87
|
+
both categories does not make identity imply evidence.
|
|
88
|
+
|
|
89
|
+
`evidence.Proven<typeof Fact, Value>` expresses a named or declaration-owned
|
|
90
|
+
fact on an existing compatible scalar type when inference alone is not
|
|
91
|
+
sufficient.
|
|
92
|
+
|
|
93
|
+
## Transformation
|
|
94
|
+
|
|
95
|
+
Evidence applies to the scalar value that was established.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const checked = NonEmpty(userId);
|
|
99
|
+
|
|
100
|
+
if (checked !== undefined) {
|
|
101
|
+
const trimmed = checked.trim();
|
|
102
|
+
|
|
103
|
+
const checkedTrimmed =
|
|
104
|
+
NonEmpty(trimmed);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
An operation such as `.trim()` produces another scalar value. Relevant
|
|
109
|
+
evidence is established again on that new value.
|
|
110
|
+
|
|
111
|
+
Evidence does not claim mutable freshness, authorization, external provenance,
|
|
112
|
+
or facts about aggregate objects. Those meanings remain with owners that can
|
|
113
|
+
establish them.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Identity law
|
|
2
|
+
|
|
3
|
+
Identity answers:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
What scalar value is this?
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Use Identity when two values can have the same JavaScript scalar
|
|
10
|
+
representation but belong to different application domains.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
const UserId = identity.string("UserId");
|
|
14
|
+
const OrderId = identity.string("OrderId");
|
|
15
|
+
|
|
16
|
+
type UserId = identity.Value<typeof UserId>;
|
|
17
|
+
type OrderId = identity.Value<typeof OrderId>;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
A `UserId` and an `OrderId` may both be strings at runtime while remaining
|
|
21
|
+
distinct typed meanings.
|
|
22
|
+
|
|
23
|
+
## Carrier
|
|
24
|
+
|
|
25
|
+
Identity applies to immutable scalar carriers:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
string | number | bigint | boolean | symbol
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Formation preserves the original runtime primitive. Identity does not wrap the
|
|
32
|
+
value in an object.
|
|
33
|
+
|
|
34
|
+
## Named identity
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { identity } from "selaws/identity";
|
|
38
|
+
|
|
39
|
+
const UserId = identity.string("UserId");
|
|
40
|
+
type UserId = identity.Value<typeof UserId>;
|
|
41
|
+
|
|
42
|
+
const id = UserId("u_1");
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A concrete literal name maps to the Selaws-owned structural phantom key
|
|
46
|
+
`~selaws.identity:<Name>`.
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
same carrier + same literal name => same named identity
|
|
50
|
+
different literal names => different named identities
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The name must denote one concrete identity. Broad strings, unions, patterns,
|
|
54
|
+
and branded string spaces do not satisfy that requirement.
|
|
55
|
+
|
|
56
|
+
Named identity is appropriate when compatible producers intentionally share the
|
|
57
|
+
same structural identity contract.
|
|
58
|
+
|
|
59
|
+
## Declaration-owned identity
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import {
|
|
63
|
+
defineIdentity,
|
|
64
|
+
type Identity,
|
|
65
|
+
} from "selaws/identity";
|
|
66
|
+
|
|
67
|
+
const userIdKey: unique symbol =
|
|
68
|
+
Symbol("UserId");
|
|
69
|
+
|
|
70
|
+
type UserId =
|
|
71
|
+
Identity<string, typeof userIdKey>;
|
|
72
|
+
|
|
73
|
+
const UserId = defineIdentity<string>()(
|
|
74
|
+
userIdKey,
|
|
75
|
+
(mint) => ({
|
|
76
|
+
fromString(value: string): UserId {
|
|
77
|
+
return mint(value);
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The token itself is the phantom property key. One identity requires one narrow
|
|
84
|
+
symbol token. Separate symbols denote separate identities.
|
|
85
|
+
|
|
86
|
+
`defineIdentity` supplies `mint` only inside the definition callback. The
|
|
87
|
+
declaring module chooses which public formation operations can introduce the
|
|
88
|
+
identity.
|
|
89
|
+
|
|
90
|
+
## Checked local formation
|
|
91
|
+
|
|
92
|
+
A predicate can own a local scalar formation condition:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const Port = identity.number(
|
|
96
|
+
"Port",
|
|
97
|
+
(value) =>
|
|
98
|
+
Number.isInteger(value) &&
|
|
99
|
+
value >= 0 &&
|
|
100
|
+
value <= 65_535,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const port = Port(input);
|
|
104
|
+
// Port | undefined
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Accepted scalar input returns the identity-bearing value. Rejected or
|
|
108
|
+
wrong-carrier input returns `undefined`.
|
|
109
|
+
|
|
110
|
+
The predicate establishes only this owner's scalar condition. Identity does not
|
|
111
|
+
become an object-schema decoder, normalizer, authorization system, or external
|
|
112
|
+
data validator.
|
|
113
|
+
|
|
114
|
+
## Composition
|
|
115
|
+
|
|
116
|
+
Forming a new identity on a scalar preserves existing intersections carried by
|
|
117
|
+
that same value. Independent identities can therefore coexist when explicitly
|
|
118
|
+
formed.
|
|
119
|
+
|
|
120
|
+
Named identity keeps its structural key spelling unchanged, so compatible
|
|
121
|
+
producers and duplicate Selaws installations preserve the same identity.
|
|
122
|
+
Declaration-owned identity remains keyed by the caller-owned symbol rather than
|
|
123
|
+
a package-owned unique symbol.
|
|
124
|
+
|
|
125
|
+
Identity and Evidence are independent. A value's identity does not imply that a
|
|
126
|
+
fact has been established about it.
|