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,32 @@
|
|
|
1
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
2
|
+
|
|
3
|
+
type BroadFunction = FunctionConstructor["prototype"];
|
|
4
|
+
|
|
5
|
+
type HasCallableThen<T> = T extends object
|
|
6
|
+
? "then" extends keyof T
|
|
7
|
+
? Extract<T["then"], BroadFunction> extends never
|
|
8
|
+
? false
|
|
9
|
+
: true
|
|
10
|
+
: false
|
|
11
|
+
: false;
|
|
12
|
+
|
|
13
|
+
export type NotPromiseLike<T> =
|
|
14
|
+
IsAny<T> extends true
|
|
15
|
+
? T
|
|
16
|
+
: true extends (T extends unknown ? HasCallableThen<T> : never)
|
|
17
|
+
? never
|
|
18
|
+
: T;
|
|
19
|
+
|
|
20
|
+
export const isPromiseLike = (value: unknown): value is PromiseLike<unknown> => {
|
|
21
|
+
if ((typeof value !== "object" && typeof value !== "function") || value === null) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return typeof (value as { readonly then?: unknown }).then === "function";
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const assertNotPromiseLike = (value: unknown, message: string): void => {
|
|
29
|
+
if (isPromiseLike(value)) {
|
|
30
|
+
throw new TypeError(message);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Immutable scalar carriers that can hold Selaws phantom meaning. */
|
|
2
|
+
export type Scalar = string | number | bigint | boolean | symbol;
|
|
3
|
+
|
|
4
|
+
export type Token = string | symbol;
|
|
5
|
+
|
|
6
|
+
type IsUnion<T, Whole = T> = T extends Whole
|
|
7
|
+
? [Whole] extends [T]
|
|
8
|
+
? false
|
|
9
|
+
: true
|
|
10
|
+
: never;
|
|
11
|
+
|
|
12
|
+
// An empty record is assignable to broad, patterned, and branded key spaces,
|
|
13
|
+
// but not to one concrete string or unique-symbol key.
|
|
14
|
+
type SingleToken<Key extends Token> =
|
|
15
|
+
true extends IsUnion<Key>
|
|
16
|
+
? never
|
|
17
|
+
: Record<never, never> extends Record<Key, never>
|
|
18
|
+
? never
|
|
19
|
+
: Key;
|
|
20
|
+
|
|
21
|
+
type SingletonSymbolMembers<Key extends symbol> = Key extends unknown
|
|
22
|
+
? SingleToken<Key>
|
|
23
|
+
: never;
|
|
24
|
+
|
|
25
|
+
export type NarrowSymbolSet<Key extends symbol> = [Key] extends [
|
|
26
|
+
SingletonSymbolMembers<Key>,
|
|
27
|
+
]
|
|
28
|
+
? Key
|
|
29
|
+
: never;
|
|
30
|
+
|
|
31
|
+
export type SingleSymbol<Key extends symbol> = SingleToken<Key>;
|
|
32
|
+
export type SingleName<Name extends string> = SingleToken<Name>;
|
|
33
|
+
|
|
34
|
+
export const isString = (value: unknown): value is string => typeof value === "string";
|
|
35
|
+
|
|
36
|
+
export const isNumber = (value: unknown): value is number => typeof value === "number";
|
|
37
|
+
|
|
38
|
+
export const isBigint = (value: unknown): value is bigint => typeof value === "bigint";
|
|
39
|
+
|
|
40
|
+
export const isBoolean = (value: unknown): value is boolean =>
|
|
41
|
+
typeof value === "boolean";
|
|
42
|
+
|
|
43
|
+
export const isSymbol = (value: unknown): value is symbol => typeof value === "symbol";
|
package/src/option.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { CallbackResult } from "./internal/callback.js";
|
|
2
|
+
import { assertNotPromiseLike, type NotPromiseLike } from "./internal/promise-like.js";
|
|
3
|
+
|
|
4
|
+
/** A present Option value. */
|
|
5
|
+
export type Some<T> = Readonly<{
|
|
6
|
+
some: true;
|
|
7
|
+
value: T;
|
|
8
|
+
}>;
|
|
9
|
+
|
|
10
|
+
/** Reasonless absence. */
|
|
11
|
+
export type None = Readonly<{
|
|
12
|
+
some: false;
|
|
13
|
+
}>;
|
|
14
|
+
|
|
15
|
+
/** Explicit presence or absence. */
|
|
16
|
+
export type Option<T> = Some<T> | None;
|
|
17
|
+
|
|
18
|
+
/** Extracts the present value type from an Option union. */
|
|
19
|
+
export type OptionValue<O> = O extends Some<infer T> ? T : never;
|
|
20
|
+
|
|
21
|
+
type OptionValues<O extends readonly Option<unknown>[]> = {
|
|
22
|
+
[K in keyof O]: OptionValue<O[K]>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type SynchronousReturn<T> = [T] extends [NotPromiseLike<T>] ? unknown : never;
|
|
26
|
+
|
|
27
|
+
type IsMutableArray<Value> = Value extends unknown[] ? true : false;
|
|
28
|
+
|
|
29
|
+
type StableArrayInput<Value extends readonly unknown[]> =
|
|
30
|
+
true extends IsMutableArray<Value> ? never : unknown;
|
|
31
|
+
|
|
32
|
+
/** Constructs a present Option value. */
|
|
33
|
+
export const some = <T>(value: T): Option<T> => ({
|
|
34
|
+
some: true,
|
|
35
|
+
value,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/** Constructs reasonless absence. */
|
|
39
|
+
export const none = (): Option<never> => ({
|
|
40
|
+
some: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/** Narrows an Option to Some. */
|
|
44
|
+
export const isSome = <T>(option: Option<T>): option is Some<T> => option.some;
|
|
45
|
+
|
|
46
|
+
/** Narrows an Option to None. */
|
|
47
|
+
export const isNone = <T>(option: Option<T>): option is None => !option.some;
|
|
48
|
+
|
|
49
|
+
/** Eliminates an Option under the shared Match law. */
|
|
50
|
+
export const match = <
|
|
51
|
+
T,
|
|
52
|
+
Some extends (this: void, value: NoInfer<T>) => unknown,
|
|
53
|
+
None extends (this: void) => unknown,
|
|
54
|
+
>(
|
|
55
|
+
option: Option<T>,
|
|
56
|
+
arms: Readonly<{
|
|
57
|
+
some: Some;
|
|
58
|
+
none: None;
|
|
59
|
+
}>,
|
|
60
|
+
): CallbackResult<Some> | CallbackResult<None> => {
|
|
61
|
+
if (option.some) {
|
|
62
|
+
const selected = arms.some;
|
|
63
|
+
return selected(option.value) as CallbackResult<Some>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const selected = arms.none;
|
|
67
|
+
return selected() as CallbackResult<None>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** Transforms the Some value and preserves None. */
|
|
71
|
+
export const map = <T, U>(option: Option<T>, transform: (value: T) => U): Option<U> =>
|
|
72
|
+
option.some ? some(transform(option.value)) : option;
|
|
73
|
+
|
|
74
|
+
/** Sequences an Option-producing computation when a value is present. */
|
|
75
|
+
export const andThen = <T, U>(
|
|
76
|
+
option: Option<T>,
|
|
77
|
+
next: (value: T) => Option<U>,
|
|
78
|
+
): Option<U> => (option.some ? next(option.value) : option);
|
|
79
|
+
|
|
80
|
+
/** Lazily provides another Option when the input is None. */
|
|
81
|
+
export const orElse = <T, U>(
|
|
82
|
+
option: Option<T>,
|
|
83
|
+
fallback: () => Option<U>,
|
|
84
|
+
): Option<T | U> => (option.some ? option : fallback());
|
|
85
|
+
|
|
86
|
+
/** Removes one explicit nested Option layer. */
|
|
87
|
+
export const flatten = <T>(option: Option<Option<T>>): Option<T> =>
|
|
88
|
+
option.some ? option.value : option;
|
|
89
|
+
|
|
90
|
+
/** Retains Some when a type-guard predicate accepts its value. */
|
|
91
|
+
export function filter<T, U extends T>(
|
|
92
|
+
option: Option<T>,
|
|
93
|
+
predicate: (value: T) => value is U,
|
|
94
|
+
): Option<U>;
|
|
95
|
+
|
|
96
|
+
/** Retains Some when the predicate accepts its value. */
|
|
97
|
+
export function filter<T>(
|
|
98
|
+
option: Option<T>,
|
|
99
|
+
predicate: (value: T) => boolean,
|
|
100
|
+
): Option<T>;
|
|
101
|
+
|
|
102
|
+
export function filter<T>(
|
|
103
|
+
option: Option<T>,
|
|
104
|
+
predicate: (value: T) => boolean,
|
|
105
|
+
): Option<T> {
|
|
106
|
+
if (!option.some) {
|
|
107
|
+
return option;
|
|
108
|
+
}
|
|
109
|
+
return predicate(option.value) ? option : none();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Synchronously observes Some and returns the original Option. */
|
|
113
|
+
export const inspect = <T, R>(
|
|
114
|
+
option: Option<T>,
|
|
115
|
+
observe: ((value: T) => R) & SynchronousReturn<R>,
|
|
116
|
+
): Option<T> => {
|
|
117
|
+
if (option.some) {
|
|
118
|
+
const completion = observe(option.value);
|
|
119
|
+
assertNotPromiseLike(
|
|
120
|
+
completion,
|
|
121
|
+
"Option.inspect() expects a synchronous observer.",
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return option;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/** Returns the Some value or an eager fallback. */
|
|
128
|
+
export const unwrapOr = <T, U>(option: Option<T>, fallback: U): T | U =>
|
|
129
|
+
option.some ? option.value : fallback;
|
|
130
|
+
|
|
131
|
+
/** Returns the Some value or evaluates a lazy fallback. */
|
|
132
|
+
export const unwrapOrElse = <T, U>(option: Option<T>, fallback: () => U): T | U =>
|
|
133
|
+
option.some ? option.value : fallback();
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Combines already-materialized Options.
|
|
137
|
+
*
|
|
138
|
+
* All Some values produce a position-preserving tuple; any None produces None.
|
|
139
|
+
*/
|
|
140
|
+
export const all = <const O extends readonly Option<unknown>[]>(
|
|
141
|
+
options: O & StableArrayInput<O>,
|
|
142
|
+
): Option<OptionValues<O>> => {
|
|
143
|
+
const values: unknown[] = [];
|
|
144
|
+
const length = options.length;
|
|
145
|
+
|
|
146
|
+
for (let index = 0; index < length; index += 1) {
|
|
147
|
+
const option = options[index] as Option<unknown>;
|
|
148
|
+
if (!option.some) {
|
|
149
|
+
return none();
|
|
150
|
+
}
|
|
151
|
+
values.push(option.value);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return some(values as OptionValues<O>);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/** Maps exactly undefined to None and every other value to Some. */
|
|
158
|
+
export const fromUndefined = <T>(value: T): Option<Exclude<T, undefined>> =>
|
|
159
|
+
value === undefined ? none() : some(value as Exclude<T, undefined>);
|
|
160
|
+
|
|
161
|
+
/** Maps null or undefined to None and every other value to Some. */
|
|
162
|
+
export const fromNullable = <T>(value: T): Option<NonNullable<T>> =>
|
|
163
|
+
value === undefined || value === null ? none() : some(value as NonNullable<T>);
|
|
164
|
+
|
|
165
|
+
/** Maps None to undefined and returns the Some value unchanged. */
|
|
166
|
+
export const toUndefined = <T>(option: Option<T>): T | undefined =>
|
|
167
|
+
option.some ? option.value : undefined;
|
|
168
|
+
|
|
169
|
+
/** Maps None to null and returns the Some value unchanged. */
|
|
170
|
+
export const toNullable = <T>(option: Option<T>): T | null =>
|
|
171
|
+
option.some ? option.value : null;
|
|
172
|
+
|
|
173
|
+
type OptionFacade = Readonly<{
|
|
174
|
+
all: typeof all;
|
|
175
|
+
andThen: typeof andThen;
|
|
176
|
+
filter: typeof filter;
|
|
177
|
+
flatten: typeof flatten;
|
|
178
|
+
fromNullable: typeof fromNullable;
|
|
179
|
+
fromUndefined: typeof fromUndefined;
|
|
180
|
+
inspect: typeof inspect;
|
|
181
|
+
isNone: typeof isNone;
|
|
182
|
+
isSome: typeof isSome;
|
|
183
|
+
map: typeof map;
|
|
184
|
+
match: typeof match;
|
|
185
|
+
none: typeof none;
|
|
186
|
+
orElse: typeof orElse;
|
|
187
|
+
some: typeof some;
|
|
188
|
+
toNullable: typeof toNullable;
|
|
189
|
+
toUndefined: typeof toUndefined;
|
|
190
|
+
unwrapOr: typeof unwrapOr;
|
|
191
|
+
unwrapOrElse: typeof unwrapOrElse;
|
|
192
|
+
}>;
|
|
193
|
+
|
|
194
|
+
/** Facade for Option construction, transformation, elimination, and collection. */
|
|
195
|
+
export const Option: OptionFacade = {
|
|
196
|
+
all,
|
|
197
|
+
andThen,
|
|
198
|
+
filter,
|
|
199
|
+
flatten,
|
|
200
|
+
fromNullable,
|
|
201
|
+
fromUndefined,
|
|
202
|
+
inspect,
|
|
203
|
+
isNone,
|
|
204
|
+
isSome,
|
|
205
|
+
map,
|
|
206
|
+
match,
|
|
207
|
+
none,
|
|
208
|
+
orElse,
|
|
209
|
+
some,
|
|
210
|
+
toNullable,
|
|
211
|
+
toUndefined,
|
|
212
|
+
unwrapOr,
|
|
213
|
+
unwrapOrElse,
|
|
214
|
+
};
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { Scalar } from "./internal/scalar.js";
|
|
2
|
+
|
|
3
|
+
/** Scalar identifiers used as Protocol states. */
|
|
4
|
+
export type State = Scalar;
|
|
5
|
+
|
|
6
|
+
/** Scalar identifiers used to distinguish transitions. */
|
|
7
|
+
export type Label = Scalar;
|
|
8
|
+
|
|
9
|
+
/** One admissible labeled transition. */
|
|
10
|
+
export type Transition<
|
|
11
|
+
From extends State = State,
|
|
12
|
+
Via extends Label = Label,
|
|
13
|
+
To extends State = State,
|
|
14
|
+
> = readonly [from: From, label: Via, to: To];
|
|
15
|
+
|
|
16
|
+
type TransitionUnion<Transitions extends readonly Transition[]> = Transitions[number];
|
|
17
|
+
|
|
18
|
+
type SourceOf<One> = One extends readonly [infer From extends State, Label, State]
|
|
19
|
+
? From
|
|
20
|
+
: never;
|
|
21
|
+
|
|
22
|
+
type LabelOf<One> = One extends readonly [State, infer Via extends Label, State]
|
|
23
|
+
? Via
|
|
24
|
+
: never;
|
|
25
|
+
|
|
26
|
+
type TargetOf<One> = One extends readonly [State, Label, infer To extends State]
|
|
27
|
+
? To
|
|
28
|
+
: never;
|
|
29
|
+
|
|
30
|
+
/** All state identifiers that occur as a source or target. */
|
|
31
|
+
export type States<Transitions extends readonly Transition[]> =
|
|
32
|
+
| SourceOf<TransitionUnion<Transitions>>
|
|
33
|
+
| TargetOf<TransitionUnion<Transitions>>;
|
|
34
|
+
|
|
35
|
+
/** All transition labels that occur in a declaration. */
|
|
36
|
+
export type Labels<Transitions extends readonly Transition[]> = LabelOf<
|
|
37
|
+
TransitionUnion<Transitions>
|
|
38
|
+
>;
|
|
39
|
+
|
|
40
|
+
type NextFrom<One, From extends State, Via extends Label> = One extends readonly [
|
|
41
|
+
infer Source extends State,
|
|
42
|
+
infer EdgeLabel extends Label,
|
|
43
|
+
infer To extends State,
|
|
44
|
+
]
|
|
45
|
+
? [Source & From] extends [never]
|
|
46
|
+
? never
|
|
47
|
+
: [EdgeLabel & Via] extends [never]
|
|
48
|
+
? never
|
|
49
|
+
: To
|
|
50
|
+
: never;
|
|
51
|
+
|
|
52
|
+
/** The statically admissible targets for one source state and transition label. */
|
|
53
|
+
export type Next<
|
|
54
|
+
Transitions extends readonly Transition[],
|
|
55
|
+
From extends States<Transitions>,
|
|
56
|
+
Via extends Labels<Transitions>,
|
|
57
|
+
> = NextFrom<TransitionUnion<Transitions>, From, Via>;
|
|
58
|
+
|
|
59
|
+
type IsMutableArray<Value> = Value extends unknown[] ? true : false;
|
|
60
|
+
|
|
61
|
+
type ClosedTransitions<Transitions extends readonly Transition[]> =
|
|
62
|
+
true extends IsMutableArray<Transitions>
|
|
63
|
+
? never
|
|
64
|
+
: true extends IsMutableArray<Transitions[number]>
|
|
65
|
+
? never
|
|
66
|
+
: Transitions;
|
|
67
|
+
|
|
68
|
+
/** Runtime membership for one immutable labeled transition relation. */
|
|
69
|
+
export type Protocol<Transitions extends readonly Transition[]> = Readonly<{
|
|
70
|
+
allows<
|
|
71
|
+
From extends States<Transitions>,
|
|
72
|
+
Via extends Labels<Transitions>,
|
|
73
|
+
To extends States<Transitions>,
|
|
74
|
+
>(from: From, label: Via, to: To): boolean;
|
|
75
|
+
}>;
|
|
76
|
+
|
|
77
|
+
const isScalarIdentifier = (value: unknown): value is Scalar => {
|
|
78
|
+
switch (typeof value) {
|
|
79
|
+
case "string":
|
|
80
|
+
case "number":
|
|
81
|
+
case "bigint":
|
|
82
|
+
case "boolean":
|
|
83
|
+
case "symbol":
|
|
84
|
+
return true;
|
|
85
|
+
default:
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Defines one immutable admissible labeled transition relation.
|
|
92
|
+
*
|
|
93
|
+
* The runtime relation snapshots the supplied triples and uses SameValueZero
|
|
94
|
+
* equality through Map and Set.
|
|
95
|
+
*/
|
|
96
|
+
export const define = <const Transitions extends readonly Transition[]>(
|
|
97
|
+
transitions: Transitions &
|
|
98
|
+
(ClosedTransitions<Transitions> extends never ? never : unknown),
|
|
99
|
+
): Protocol<Transitions> => {
|
|
100
|
+
if (!Array.isArray(transitions)) {
|
|
101
|
+
throw new TypeError("Protocol declarations must be arrays.");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const relation = new Map<State, Map<Label, Set<State>>>();
|
|
105
|
+
|
|
106
|
+
const length = transitions.length;
|
|
107
|
+
|
|
108
|
+
for (let index = 0; index < length; index += 1) {
|
|
109
|
+
if (!Object.hasOwn(transitions, index)) {
|
|
110
|
+
throw new TypeError("Protocol declarations must contain own transition entries.");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const transition = transitions[index] as unknown;
|
|
114
|
+
|
|
115
|
+
if (
|
|
116
|
+
!Array.isArray(transition) ||
|
|
117
|
+
transition.length !== 3 ||
|
|
118
|
+
!Object.hasOwn(transition, 0) ||
|
|
119
|
+
!Object.hasOwn(transition, 1) ||
|
|
120
|
+
!Object.hasOwn(transition, 2)
|
|
121
|
+
) {
|
|
122
|
+
throw new TypeError(
|
|
123
|
+
"Protocol transitions must be own [from, label, to] triples.",
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const from = transition[0];
|
|
128
|
+
const label = transition[1];
|
|
129
|
+
const to = transition[2];
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
!isScalarIdentifier(from) ||
|
|
133
|
+
!isScalarIdentifier(label) ||
|
|
134
|
+
!isScalarIdentifier(to)
|
|
135
|
+
) {
|
|
136
|
+
throw new TypeError("Protocol states and labels must be scalar identifiers.");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let labels = relation.get(from);
|
|
140
|
+
|
|
141
|
+
if (labels === undefined) {
|
|
142
|
+
labels = new Map<Label, Set<State>>();
|
|
143
|
+
relation.set(from, labels);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let targets = labels.get(label);
|
|
147
|
+
|
|
148
|
+
if (targets === undefined) {
|
|
149
|
+
targets = new Set<State>();
|
|
150
|
+
labels.set(label, targets);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
targets.add(to);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return Object.freeze({
|
|
157
|
+
allows<
|
|
158
|
+
From extends States<Transitions>,
|
|
159
|
+
Via extends Labels<Transitions>,
|
|
160
|
+
To extends States<Transitions>,
|
|
161
|
+
>(from: From, label: Via, to: To): boolean {
|
|
162
|
+
return relation.get(from)?.get(label)?.has(to) ?? false;
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
type ProtocolFacade = Readonly<{
|
|
168
|
+
define: typeof define;
|
|
169
|
+
}>;
|
|
170
|
+
|
|
171
|
+
/** Admissible labeled transition relations. */
|
|
172
|
+
export const Protocol: ProtocolFacade = {
|
|
173
|
+
define,
|
|
174
|
+
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { assertNotPromiseLike, type NotPromiseLike } from "../internal/promise-like.js";
|
|
2
|
+
import { err, ok, type Result } from "./core.js";
|
|
3
|
+
|
|
4
|
+
type SyncPrimitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
5
|
+
|
|
6
|
+
type NonThenableObject = object & {
|
|
7
|
+
readonly then?: never;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type OverloadUnion<Callable, Partial = unknown> = Callable extends (
|
|
11
|
+
this: infer This,
|
|
12
|
+
...args: infer Args
|
|
13
|
+
) => infer Return
|
|
14
|
+
? Partial extends Callable
|
|
15
|
+
? never
|
|
16
|
+
:
|
|
17
|
+
| OverloadUnion<
|
|
18
|
+
Partial & Callable,
|
|
19
|
+
Partial & ((this: This, ...args: Args) => Return)
|
|
20
|
+
>
|
|
21
|
+
| ((this: This, ...args: Args) => Return)
|
|
22
|
+
: never;
|
|
23
|
+
|
|
24
|
+
type IsUnion<T, Whole = T> = T extends unknown
|
|
25
|
+
? [Whole] extends [T]
|
|
26
|
+
? false
|
|
27
|
+
: true
|
|
28
|
+
: never;
|
|
29
|
+
|
|
30
|
+
type UnionToIntersection<T> = (T extends unknown ? (value: T) => void : never) extends (
|
|
31
|
+
value: infer Intersection,
|
|
32
|
+
) => void
|
|
33
|
+
? Intersection
|
|
34
|
+
: never;
|
|
35
|
+
|
|
36
|
+
type HasAsyncOverload<Callable> = true extends (
|
|
37
|
+
OverloadUnion<Callable> extends infer One
|
|
38
|
+
? One extends (...args: never[]) => infer Return
|
|
39
|
+
? [Return] extends [NotPromiseLike<Return>]
|
|
40
|
+
? false
|
|
41
|
+
: true
|
|
42
|
+
: never
|
|
43
|
+
: never
|
|
44
|
+
)
|
|
45
|
+
? true
|
|
46
|
+
: false;
|
|
47
|
+
|
|
48
|
+
type HasNonPromiseOverload<Callable> = true extends (
|
|
49
|
+
OverloadUnion<Callable> extends infer One
|
|
50
|
+
? One extends (...args: never[]) => infer Return
|
|
51
|
+
? [Return] extends [PromiseLike<unknown>]
|
|
52
|
+
? false
|
|
53
|
+
: true
|
|
54
|
+
: never
|
|
55
|
+
: never
|
|
56
|
+
)
|
|
57
|
+
? true
|
|
58
|
+
: false;
|
|
59
|
+
|
|
60
|
+
type SyncWrappedOne<Callable, E> = Callable extends (
|
|
61
|
+
this: infer This,
|
|
62
|
+
...args: infer Args
|
|
63
|
+
) => infer Return
|
|
64
|
+
? (this: This, ...args: Args) => Result<Return, E>
|
|
65
|
+
: never;
|
|
66
|
+
|
|
67
|
+
type SyncWrappedOverloads<Callable, E> = UnionToIntersection<
|
|
68
|
+
OverloadUnion<Callable> extends infer One
|
|
69
|
+
? One extends unknown
|
|
70
|
+
? SyncWrappedOne<One, E>
|
|
71
|
+
: never
|
|
72
|
+
: never
|
|
73
|
+
>;
|
|
74
|
+
|
|
75
|
+
type AsyncWrappedOne<Callable, E> = Callable extends (
|
|
76
|
+
this: infer This,
|
|
77
|
+
...args: infer Args
|
|
78
|
+
) => infer Return
|
|
79
|
+
? (this: This, ...args: Args) => Promise<Result<Awaited<Return>, E>>
|
|
80
|
+
: never;
|
|
81
|
+
|
|
82
|
+
type AsyncWrappedOverloads<Callable, E> = UnionToIntersection<
|
|
83
|
+
OverloadUnion<Callable> extends infer One
|
|
84
|
+
? One extends unknown
|
|
85
|
+
? AsyncWrappedOne<One, E>
|
|
86
|
+
: never
|
|
87
|
+
: never
|
|
88
|
+
>;
|
|
89
|
+
|
|
90
|
+
const attemptRuntime = <T, E>(
|
|
91
|
+
read: () => T,
|
|
92
|
+
mapThrown: (caught: unknown) => E,
|
|
93
|
+
): Result<T, E> => {
|
|
94
|
+
let value: T;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
value = read();
|
|
98
|
+
} catch (caught) {
|
|
99
|
+
return err(mapThrown(caught));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
assertNotPromiseLike(value, "attempt() expects a synchronous thunk.");
|
|
103
|
+
return ok(value);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Captures one synchronous invocation boundary as Result.
|
|
108
|
+
*
|
|
109
|
+
* Thrown values are mapped from unknown into the caller's recoverable error
|
|
110
|
+
* type. Promise-like returns are rejected as a sync-boundary contract error.
|
|
111
|
+
* A thrown mapper remains an abrupt JavaScript completion.
|
|
112
|
+
*/
|
|
113
|
+
export function attempt<T extends SyncPrimitive, E>(
|
|
114
|
+
read: () => T,
|
|
115
|
+
mapThrown: (caught: unknown) => E,
|
|
116
|
+
): Result<T, E>;
|
|
117
|
+
|
|
118
|
+
export function attempt<T extends NonThenableObject, E>(
|
|
119
|
+
read: () => T,
|
|
120
|
+
mapThrown: (caught: unknown) => E,
|
|
121
|
+
): Result<T, E>;
|
|
122
|
+
|
|
123
|
+
export function attempt<T, E>(
|
|
124
|
+
read: () => NotPromiseLike<T>,
|
|
125
|
+
mapThrown: (caught: unknown) => E,
|
|
126
|
+
): Result<T, E>;
|
|
127
|
+
|
|
128
|
+
export function attempt<T, E>(
|
|
129
|
+
read: () => T,
|
|
130
|
+
mapThrown: (caught: unknown) => E,
|
|
131
|
+
): Result<T, E> {
|
|
132
|
+
return attemptRuntime(read, mapThrown);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Captures synchronous invocation plus Promise rejection as Result.
|
|
137
|
+
*
|
|
138
|
+
* The returned Promise resolves to Result; the mapper defines the recoverable
|
|
139
|
+
* error vocabulary for both abrupt paths.
|
|
140
|
+
*/
|
|
141
|
+
export const attemptAsync = async <T, E>(
|
|
142
|
+
read: () => PromiseLike<T>,
|
|
143
|
+
mapThrown: (caught: unknown) => E,
|
|
144
|
+
): Promise<Result<Awaited<T>, E>> => {
|
|
145
|
+
try {
|
|
146
|
+
return ok(await read());
|
|
147
|
+
} catch (caught) {
|
|
148
|
+
return err(mapThrown(caught));
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/** Wraps a synchronous function with the attempt boundary while preserving this and arguments. */
|
|
153
|
+
export function wrap<Callable extends (...args: never[]) => unknown, E>(
|
|
154
|
+
read: Callable &
|
|
155
|
+
(true extends IsUnion<Callable> ? never : unknown) &
|
|
156
|
+
(true extends IsUnion<OverloadUnion<Callable>> ? unknown : never) &
|
|
157
|
+
(HasAsyncOverload<Callable> extends false ? unknown : never),
|
|
158
|
+
mapThrown: (caught: unknown) => E,
|
|
159
|
+
): SyncWrappedOverloads<Callable, E>;
|
|
160
|
+
|
|
161
|
+
export function wrap<This, Args extends unknown[], T extends SyncPrimitive, E>(
|
|
162
|
+
read: (this: This, ...args: Args) => T,
|
|
163
|
+
mapThrown: (caught: unknown) => E,
|
|
164
|
+
): (this: This, ...args: Args) => Result<T, E>;
|
|
165
|
+
|
|
166
|
+
export function wrap<This, Args extends unknown[], T extends NonThenableObject, E>(
|
|
167
|
+
read: (this: This, ...args: Args) => T,
|
|
168
|
+
mapThrown: (caught: unknown) => E,
|
|
169
|
+
): (this: This, ...args: Args) => Result<T, E>;
|
|
170
|
+
|
|
171
|
+
export function wrap<This, Args extends unknown[], T, E>(
|
|
172
|
+
read: (this: This, ...args: Args) => NotPromiseLike<T>,
|
|
173
|
+
mapThrown: (caught: unknown) => E,
|
|
174
|
+
): (this: This, ...args: Args) => Result<T, E>;
|
|
175
|
+
|
|
176
|
+
export function wrap<This, Args extends unknown[], T, E>(
|
|
177
|
+
read: (this: This, ...args: Args) => T,
|
|
178
|
+
mapThrown: (caught: unknown) => E,
|
|
179
|
+
): (this: This, ...args: Args) => Result<T, E> {
|
|
180
|
+
return function wrapped(this: This, ...args: Args): Result<T, E> {
|
|
181
|
+
return attemptRuntime(() => Reflect.apply(read, this, args), mapThrown);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Wraps an async function with the attemptAsync boundary while preserving this and arguments. */
|
|
186
|
+
export function wrapAsync<Callable extends (...args: never[]) => unknown, E>(
|
|
187
|
+
read: Callable &
|
|
188
|
+
(true extends IsUnion<Callable> ? never : unknown) &
|
|
189
|
+
(true extends IsUnion<OverloadUnion<Callable>> ? unknown : never) &
|
|
190
|
+
(HasNonPromiseOverload<Callable> extends false ? unknown : never),
|
|
191
|
+
mapThrown: (caught: unknown) => E,
|
|
192
|
+
): AsyncWrappedOverloads<Callable, E>;
|
|
193
|
+
|
|
194
|
+
export function wrapAsync<This, Args extends unknown[], T, E>(
|
|
195
|
+
read: (this: This, ...args: Args) => PromiseLike<T>,
|
|
196
|
+
mapThrown: (caught: unknown) => E,
|
|
197
|
+
): (this: This, ...args: Args) => Promise<Result<Awaited<T>, E>>;
|
|
198
|
+
|
|
199
|
+
export function wrapAsync<This, Args extends unknown[], T, E>(
|
|
200
|
+
read: (this: This, ...args: Args) => PromiseLike<T>,
|
|
201
|
+
mapThrown: (caught: unknown) => E,
|
|
202
|
+
): (this: This, ...args: Args) => Promise<Result<Awaited<T>, E>> {
|
|
203
|
+
return function wrappedAsync(
|
|
204
|
+
this: This,
|
|
205
|
+
...args: Args
|
|
206
|
+
): Promise<Result<Awaited<T>, E>> {
|
|
207
|
+
return attemptAsync(() => Reflect.apply(read, this, args), mapThrown);
|
|
208
|
+
};
|
|
209
|
+
}
|