functionalscript 0.14.8 → 0.16.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/fs/ci/common/module.f.d.ts +50 -16
- package/fs/ci/common/module.f.js +19 -0
- package/fs/ci/config/module.f.d.ts +2 -2
- package/fs/ci/config/module.f.js +2 -2
- package/fs/ci/module.f.js +1 -1
- package/fs/ci/test.f.js +6 -2
- package/fs/dev/module.f.d.ts +2 -1
- package/fs/dev/module.f.js +25 -16
- package/fs/dev/tf/module.f.js +10 -0
- package/fs/dev/tf/test.f.d.ts +5 -0
- package/fs/dev/tf/test.f.js +14 -0
- package/fs/fjs/module.f.js +5 -3
- package/fs/io/module.d.ts +2 -2
- package/fs/io/module.f.d.ts +3 -15
- package/fs/io/module.f.js +14 -8
- package/fs/io/module.js +2 -2
- package/fs/io/virtual/module.f.js +2 -2
- package/fs/types/effects/mock/module.f.d.ts +1 -1
- package/fs/types/effects/mock/module.f.js +1 -1
- package/fs/types/effects/module.f.d.ts +5 -7
- package/fs/types/effects/module.f.js +1 -2
- package/fs/types/effects/module.js +1 -1
- package/fs/types/effects/node/module.f.d.ts +34 -25
- package/fs/types/effects/node/module.f.js +9 -7
- package/fs/types/effects/node/test.f.js +1 -1
- package/fs/types/effects/node/virtual/module.f.js +16 -5
- package/fs/types/rtti/common/module.f.d.ts +77 -0
- package/fs/types/rtti/common/module.f.js +45 -0
- package/fs/types/rtti/module.f.d.ts +4 -8
- package/fs/types/rtti/module.f.js +5 -71
- package/fs/types/rtti/parse/module.f.d.ts +3 -27
- package/fs/types/rtti/parse/module.f.js +12 -20
- package/fs/types/rtti/test.f.d.ts +0 -28
- package/fs/types/rtti/test.f.js +0 -107
- package/fs/types/rtti/ts/module.f.d.ts +9 -6
- package/fs/types/rtti/validate/module.f.d.ts +3 -79
- package/fs/types/rtti/validate/module.f.js +13 -36
- package/package.json +2 -2
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Vec } from '../../bit_vec/module.f.ts';
|
|
2
2
|
import type { Nominal } from '../../nominal/module.f.ts';
|
|
3
3
|
import type { Result } from '../../result/module.f.ts';
|
|
4
|
-
import { type Effect, type Func, type Operation, type
|
|
4
|
+
import { type Effect, type Func, type Operation, type ToAsyncOperationMap } from '../module.f.ts';
|
|
5
5
|
export type IoResult<T> = Result<T, unknown>;
|
|
6
|
-
export type All = ['all', <T>(
|
|
6
|
+
export type All = ['all', <T>(...effects: Effect<never, T>[]) => readonly T[]];
|
|
7
7
|
/**
|
|
8
8
|
* To run the operation `O` should be known by the runner/engine.
|
|
9
9
|
* This is the reason why we merge `O` with `All` in the resulted `Effect`.
|
|
@@ -13,15 +13,14 @@ export type All = ['all', <T>(_: readonly Effect<never, T>[]) => readonly T[]];
|
|
|
13
13
|
*/
|
|
14
14
|
export declare const all: <O extends Operation, T>(...a: readonly Effect<O, T>[]) => Effect<O | All, readonly T[]>;
|
|
15
15
|
export declare const both: <O0 extends Operation, T0>(a: Effect<O0, T0>) => <O1 extends Operation, T1>(b: Effect<O1, T1>) => Effect<O0 | O1 | All, readonly [T0, T1]>;
|
|
16
|
-
export type Fetch = ['fetch', (
|
|
16
|
+
export type Fetch = ['fetch', (url: string) => IoResult<Vec>];
|
|
17
17
|
export declare const fetch: Func<Fetch>;
|
|
18
18
|
export type MakeDirectoryOptions = {
|
|
19
19
|
readonly recursive: true;
|
|
20
20
|
};
|
|
21
|
-
export type
|
|
22
|
-
export
|
|
23
|
-
export
|
|
24
|
-
export type ReadFile = readonly ['readFile', (_: string) => IoResult<Vec>];
|
|
21
|
+
export type Mkdir = readonly ['mkdir', (path: string, options?: MakeDirectoryOptions) => IoResult<void>];
|
|
22
|
+
export declare const mkdir: Func<Mkdir>;
|
|
23
|
+
export type ReadFile = readonly ['readFile', (path: string) => IoResult<Vec>];
|
|
25
24
|
export declare const readFile: Func<ReadFile>;
|
|
26
25
|
/**
|
|
27
26
|
* Represents a directory entry (file or directory) in the filesystem
|
|
@@ -35,25 +34,24 @@ export type Dirent = {
|
|
|
35
34
|
export type ReaddirOptions = {
|
|
36
35
|
readonly recursive?: true;
|
|
37
36
|
};
|
|
38
|
-
export type
|
|
39
|
-
export
|
|
40
|
-
export
|
|
41
|
-
export
|
|
42
|
-
export type
|
|
43
|
-
export declare const writeFile: RestFunc<WriteFile>;
|
|
44
|
-
export type Rm = readonly ['rm', (_: string) => IoResult<void>];
|
|
37
|
+
export type Readdir = readonly ['readdir', (path: string, options: ReaddirOptions) => IoResult<readonly Dirent[]>];
|
|
38
|
+
export declare const readdir: Func<Readdir>;
|
|
39
|
+
export type WriteFile = readonly ['writeFile', (path: string, data: Vec) => IoResult<void>];
|
|
40
|
+
export declare const writeFile: Func<WriteFile>;
|
|
41
|
+
export type Rm = readonly ['rm', (path: string) => IoResult<void>];
|
|
45
42
|
export declare const rm: Func<Rm>;
|
|
46
43
|
export type ExecResult = {
|
|
47
44
|
readonly stdout: string;
|
|
48
45
|
readonly stderr: string;
|
|
49
46
|
};
|
|
50
|
-
export type
|
|
51
|
-
export
|
|
52
|
-
export
|
|
53
|
-
export
|
|
54
|
-
export type
|
|
47
|
+
export type Exec = readonly ['exec', (command: string, stdin?: string) => IoResult<ExecResult>];
|
|
48
|
+
export declare const exec: Func<Exec>;
|
|
49
|
+
export type Access = readonly ['access', (path: string) => IoResult<void>];
|
|
50
|
+
export declare const access: Func<Access>;
|
|
51
|
+
export type Fs = Mkdir | ReadFile | Readdir | WriteFile | Rm | Exec | Access;
|
|
52
|
+
export type Error = ['error', (message: string) => void];
|
|
55
53
|
export declare const error: Func<Error>;
|
|
56
|
-
export type Log = ['log', (
|
|
54
|
+
export type Log = ['log', (message: string) => void];
|
|
57
55
|
export declare const log: Func<Log>;
|
|
58
56
|
export type Console = Log | Error;
|
|
59
57
|
export type Server = Nominal<'server', `160855c4f69310fece3273c1853ac32de43dee1eb41bf59d821917f8eebe9272`, unknown>;
|
|
@@ -72,14 +70,25 @@ export type ServerResponse = {
|
|
|
72
70
|
readonly body: Vec;
|
|
73
71
|
};
|
|
74
72
|
export type RequestListener<O extends Operation> = (_: IncomingMessage) => Effect<O, ServerResponse>;
|
|
75
|
-
export type CreateServer = ['createServer', (
|
|
73
|
+
export type CreateServer = ['createServer', (listener: RequestListener<Operation>) => Server];
|
|
76
74
|
export declare const createServer: <O extends Operation>(listener: RequestListener<O>) => Effect<O | CreateServer, Server>;
|
|
77
|
-
export type Listen = ['listen', (
|
|
78
|
-
export declare const listen:
|
|
75
|
+
export type Listen = ['listen', (server: Server, port: number) => void];
|
|
76
|
+
export declare const listen: Func<Listen>;
|
|
79
77
|
export type Http = CreateServer | Listen;
|
|
80
78
|
export type Forever = ['forever', () => never];
|
|
81
79
|
export declare const forever: Func<Forever>;
|
|
82
|
-
export type
|
|
80
|
+
export type Module = {
|
|
81
|
+
readonly default: unknown;
|
|
82
|
+
};
|
|
83
|
+
export type Import = ['import', (path: string) => IoResult<Module>];
|
|
84
|
+
export declare const import_: Func<Import>;
|
|
85
|
+
export type NodeOp = All | Fetch | Console | Fs | Http | Forever | Import;
|
|
83
86
|
export type NodeEffect<T> = Effect<NodeOp, T>;
|
|
84
87
|
export type NodeOperationMap = ToAsyncOperationMap<NodeOp>;
|
|
85
|
-
|
|
88
|
+
/**
|
|
89
|
+
* The environment variables.
|
|
90
|
+
*/
|
|
91
|
+
export type Env = {
|
|
92
|
+
readonly [k: string]: string | undefined;
|
|
93
|
+
};
|
|
94
|
+
export type NodeProgram = (argv: readonly string[], env: Env) => Effect<NodeOp, number>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { do_ } from "../module.f.js";
|
|
2
2
|
const doAll = do_('all');
|
|
3
3
|
/**
|
|
4
4
|
* To run the operation `O` should be known by the runner/engine.
|
|
@@ -7,17 +7,19 @@ const doAll = do_('all');
|
|
|
7
7
|
* @param a
|
|
8
8
|
* @returns
|
|
9
9
|
*/
|
|
10
|
-
export const all = (...a) => doAll(a);
|
|
10
|
+
export const all = (...a) => doAll(...a);
|
|
11
11
|
export const both = (a) => (b) => all(a, b);
|
|
12
12
|
export const fetch = do_('fetch');
|
|
13
|
-
export const mkdir =
|
|
13
|
+
export const mkdir = do_('mkdir');
|
|
14
14
|
export const readFile = do_('readFile');
|
|
15
|
-
export const readdir =
|
|
16
|
-
export const writeFile =
|
|
15
|
+
export const readdir = do_('readdir');
|
|
16
|
+
export const writeFile = do_('writeFile');
|
|
17
17
|
export const rm = do_('rm');
|
|
18
|
-
export const exec =
|
|
18
|
+
export const exec = do_('exec');
|
|
19
|
+
export const access = do_('access');
|
|
19
20
|
export const error = do_('error');
|
|
20
21
|
export const log = do_('log');
|
|
21
22
|
export const createServer = do_('createServer');
|
|
22
|
-
export const listen =
|
|
23
|
+
export const listen = do_('listen');
|
|
23
24
|
export const forever = do_('forever');
|
|
25
|
+
export const import_ = do_('import');
|
|
@@ -94,6 +94,15 @@ const readdir = (base, recursive) => readOperation((dir, path) => {
|
|
|
94
94
|
};
|
|
95
95
|
return ok(f(base, dir));
|
|
96
96
|
});
|
|
97
|
+
const access = readOperation((dir, path) => {
|
|
98
|
+
if (path.length === 0) {
|
|
99
|
+
return okVoid;
|
|
100
|
+
}
|
|
101
|
+
if (path.length !== 1) {
|
|
102
|
+
return error('no such file or directory');
|
|
103
|
+
}
|
|
104
|
+
return dir[path[0]] !== undefined ? okVoid : error('no such file or directory');
|
|
105
|
+
});
|
|
97
106
|
const rm = operation((dir, path) => {
|
|
98
107
|
if (path.length !== 1) {
|
|
99
108
|
return [dir, error('invalid path')];
|
|
@@ -111,7 +120,7 @@ const rm = operation((dir, path) => {
|
|
|
111
120
|
});
|
|
112
121
|
const console = (name) => (state, payload) => [{ ...state, [name]: `${state[name]}${payload}\n` }, undefined];
|
|
113
122
|
const map = {
|
|
114
|
-
all: (state, a) => {
|
|
123
|
+
all: (state, ...a) => {
|
|
115
124
|
let e = [];
|
|
116
125
|
for (const i of a) {
|
|
117
126
|
const [ns, ei] = virtual(state)(i);
|
|
@@ -126,11 +135,13 @@ const map = {
|
|
|
126
135
|
const result = state.internet[url];
|
|
127
136
|
return result === undefined ? [state, error('not found')] : [state, ok(result)];
|
|
128
137
|
},
|
|
129
|
-
mkdir: (state,
|
|
138
|
+
mkdir: (state, path, p) => mkdir(p !== undefined)(state, path),
|
|
130
139
|
readFile,
|
|
131
|
-
readdir: (state,
|
|
132
|
-
writeFile: (state,
|
|
133
|
-
|
|
140
|
+
readdir: (state, path, { recursive }) => readdir(path, recursive === true)(state, path),
|
|
141
|
+
writeFile: (state, path, payload) => writeFile(payload)(state, path),
|
|
142
|
+
access,
|
|
143
|
+
import: todo,
|
|
144
|
+
rm,
|
|
134
145
|
exec: todo,
|
|
135
146
|
createServer: todo,
|
|
136
147
|
listen: todo,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared kernel for RTTI consumers (`validate`, `parse`).
|
|
3
|
+
*
|
|
4
|
+
* Both consumers traverse the same schema shape and produce the same
|
|
5
|
+
* `Result<T, ValidationError>` outcome. Only the per-variant handling differs
|
|
6
|
+
* — `validate` keeps the original value, `parse` constructs a fresh one.
|
|
7
|
+
*
|
|
8
|
+
* This module hosts the parts that do not differ:
|
|
9
|
+
*
|
|
10
|
+
* - The error shape (`ValidationError`, `Path`) and path bookkeeping
|
|
11
|
+
* (`verror`, `prependPath`).
|
|
12
|
+
* - Primitive checks (`primitive0Validate`, `constPrimitiveValidate`).
|
|
13
|
+
* - The `Validate<T>`/`Result<T>` signatures — `parse` uses the same shape.
|
|
14
|
+
* - `visit`: a visitor over the `Type` ADT. Callers supply a `Visitor<R>`
|
|
15
|
+
* with one handler per variant; `visit(v)(rtti)` recognizes `rtti` and
|
|
16
|
+
* calls the matching handler. Both consumers compose their top-level
|
|
17
|
+
* function from a visitor.
|
|
18
|
+
*
|
|
19
|
+
* Keeping the kernel here also removes `parse`'s incidental dependency on
|
|
20
|
+
* `validate` and gives future schema-driven consumers (e.g. the data form
|
|
21
|
+
* sketched in [i143](../../../../issues/README.md)) a stable shared base.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
import type { Primitive, Unknown } from '../../../djs/module.f.ts';
|
|
26
|
+
import { type Info0, type Primitive0, type Struct, type Tuple, type Type } from '../module.f.ts';
|
|
27
|
+
import { type Error, type Result as CommonResult } from '../../result/module.f.ts';
|
|
28
|
+
import type { Ts } from '../ts/module.f.ts';
|
|
29
|
+
/** A path to a sub-value within the validated structure. Each step is an object key or stringified array index. */
|
|
30
|
+
export type Path = readonly string[];
|
|
31
|
+
/** Detailed validation failure: the offending `path` plus a short `message`. */
|
|
32
|
+
export type ValidationError = {
|
|
33
|
+
readonly path: Path;
|
|
34
|
+
readonly message: string;
|
|
35
|
+
};
|
|
36
|
+
/** Validation/parse result: either the typed value or a `ValidationError`. */
|
|
37
|
+
export type Result<T extends Type> = CommonResult<Ts<T>, ValidationError>;
|
|
38
|
+
/** A function that checks an unknown value against schema `T`. Shared by `validate` and `parse`. */
|
|
39
|
+
export type Validate<T extends Type> = (value: Unknown) => Result<T>;
|
|
40
|
+
/** Builds an error result with empty path and the given message. */
|
|
41
|
+
export declare const verror: (message: string) => Error<ValidationError>;
|
|
42
|
+
/** Prepends `key` to the error's path, used to build the path bottom-up. */
|
|
43
|
+
export declare const prependPath: (key: string, r: Error<ValidationError>) => Error<ValidationError>;
|
|
44
|
+
/** Validates a `Tag0` primitive schema using `typeof`. */
|
|
45
|
+
export declare const primitive0Validate: <K extends Primitive0, T extends Info0<K>>(tag: K) => Validate<T>;
|
|
46
|
+
/**
|
|
47
|
+
* Validates a primitive `Const` schema using `Object.is` (SameValue).
|
|
48
|
+
*
|
|
49
|
+
* `Object.is` is used instead of `===` so that:
|
|
50
|
+
* - `NaN` const schemas match `NaN` values (`===` would always fail because `NaN !== NaN`).
|
|
51
|
+
* - `+0` and `-0` are treated as distinct const values.
|
|
52
|
+
*/
|
|
53
|
+
export declare const constPrimitiveValidate: <T extends Primitive>(rtti: T) => Validate<T>;
|
|
54
|
+
/**
|
|
55
|
+
* One handler per `Type` variant. Both `validate` and `parse` provide a
|
|
56
|
+
* `Visitor<R>` where `R` is the per-variant output (e.g. a `Validate` function).
|
|
57
|
+
*/
|
|
58
|
+
export type Visitor<R> = {
|
|
59
|
+
readonly tuple: (rtti: Tuple) => R;
|
|
60
|
+
readonly struct: (rtti: Struct) => R;
|
|
61
|
+
readonly array: (item: Type) => R;
|
|
62
|
+
readonly record: (item: Type) => R;
|
|
63
|
+
readonly or: (variants: readonly Type[]) => R;
|
|
64
|
+
readonly constPrimitive: (p: Primitive) => R;
|
|
65
|
+
readonly primitive0: (tag: Primitive0) => R;
|
|
66
|
+
readonly unknown: () => R;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Visits a schema `Type` by dispatching to the matching handler in `v`.
|
|
70
|
+
*
|
|
71
|
+
* - `Thunk` schemas are evaluated once to read the `Info` descriptor, then
|
|
72
|
+
* routed by tag (`'const'`, `'array'`, `'record'`, `'unknown'`, `'or'`,
|
|
73
|
+
* or a `Tag0` primitive).
|
|
74
|
+
* - `Const` schemas (primitives, tuples, structs) are routed directly to
|
|
75
|
+
* `tuple`, `struct`, or `constPrimitive`.
|
|
76
|
+
*/
|
|
77
|
+
export declare const visit: <R>(v: Visitor<R>) => (rtti: Type) => R;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {} from "../module.f.js";
|
|
2
|
+
import { error, ok } from "../../result/module.f.js";
|
|
3
|
+
import { isArray } from "../../array/module.f.js";
|
|
4
|
+
/** Builds an error result with empty path and the given message. */
|
|
5
|
+
export const verror = (message) => error({ path: [], message });
|
|
6
|
+
/** Prepends `key` to the error's path, used to build the path bottom-up. */
|
|
7
|
+
export const prependPath = (key, r) => error({ path: [key, ...r[1].path], message: r[1].message });
|
|
8
|
+
/** Validates a `Tag0` primitive schema using `typeof`. */
|
|
9
|
+
export const primitive0Validate = (tag) => value => typeof value === tag ? ok(value) : verror('unexpected value');
|
|
10
|
+
/**
|
|
11
|
+
* Validates a primitive `Const` schema using `Object.is` (SameValue).
|
|
12
|
+
*
|
|
13
|
+
* `Object.is` is used instead of `===` so that:
|
|
14
|
+
* - `NaN` const schemas match `NaN` values (`===` would always fail because `NaN !== NaN`).
|
|
15
|
+
* - `+0` and `-0` are treated as distinct const values.
|
|
16
|
+
*/
|
|
17
|
+
export const constPrimitiveValidate = (rtti) => value => Object.is(rtti, value)
|
|
18
|
+
? ok(value)
|
|
19
|
+
: verror('unexpected value');
|
|
20
|
+
const visitConst = (v) => (c) => typeof c === 'object' && c !== null
|
|
21
|
+
? (isArray(c) ? v.tuple(c) : v.struct(c))
|
|
22
|
+
: v.constPrimitive(c);
|
|
23
|
+
/**
|
|
24
|
+
* Visits a schema `Type` by dispatching to the matching handler in `v`.
|
|
25
|
+
*
|
|
26
|
+
* - `Thunk` schemas are evaluated once to read the `Info` descriptor, then
|
|
27
|
+
* routed by tag (`'const'`, `'array'`, `'record'`, `'unknown'`, `'or'`,
|
|
28
|
+
* or a `Tag0` primitive).
|
|
29
|
+
* - `Const` schemas (primitives, tuples, structs) are routed directly to
|
|
30
|
+
* `tuple`, `struct`, or `constPrimitive`.
|
|
31
|
+
*/
|
|
32
|
+
export const visit = (v) => (rtti) => {
|
|
33
|
+
if (typeof rtti === 'function') {
|
|
34
|
+
const [tag, ...value] = rtti();
|
|
35
|
+
switch (tag) {
|
|
36
|
+
case 'const': return visitConst(v)(value[0]);
|
|
37
|
+
case 'array': return v.array(value[0]);
|
|
38
|
+
case 'record': return v.record(value[0]);
|
|
39
|
+
case 'unknown': return v.unknown();
|
|
40
|
+
case 'or': return v.or(value);
|
|
41
|
+
}
|
|
42
|
+
return v.primitive0(tag);
|
|
43
|
+
}
|
|
44
|
+
return visitConst(v)(rtti);
|
|
45
|
+
};
|
|
@@ -63,14 +63,10 @@ export type Or<T extends readonly Type[]> = () => readonly ['or', ...T];
|
|
|
63
63
|
/**
|
|
64
64
|
* Constructs a schema that validates a value matching any of the given schemas.
|
|
65
65
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* -
|
|
69
|
-
*
|
|
70
|
-
* (e.g. `or(42, number)` → `or(number)`),
|
|
71
|
-
* - duplicate variants are deduplicated via `Object.is`.
|
|
72
|
-
*
|
|
73
|
-
* See `issues/130-or-optimization.md`.
|
|
66
|
+
* `or` is intentionally a lazy, allocation-free constructor: it captures its
|
|
67
|
+
* arguments in a thunk and does no flattening, deduplication, subset analysis,
|
|
68
|
+
* or canonical-form work. All such algebra lives on the serializable data form
|
|
69
|
+
* — see `issues/143-rtti-data.md`.
|
|
74
70
|
*/
|
|
75
71
|
export declare const or: <T extends readonly Type[]>(...types: T) => Or<T>;
|
|
76
72
|
/** Constructs a schema that validates a value matching `T` or `undefined`. */
|
|
@@ -19,81 +19,15 @@ const type1 = (key) => t => () => [key, t];
|
|
|
19
19
|
export const array = type1('array');
|
|
20
20
|
/** Constructs a schema that validates `{ readonly[K in string]: Ts<T> }`. */
|
|
21
21
|
export const record = type1('record');
|
|
22
|
-
/** Reads the tag of a thunk variant, or returns `null` for a `Const`. */
|
|
23
|
-
const variantTag = (t) => typeof t === 'function' ? t()[0] : null;
|
|
24
|
-
const isPrim0 = includes(primitive0List);
|
|
25
|
-
const flattenStep = ([visited, out], t) => {
|
|
26
|
-
if (typeof t === 'function' && !visited.has(t)) {
|
|
27
|
-
const nextVisited = new Set([...visited, t]);
|
|
28
|
-
const info = t();
|
|
29
|
-
if (info[0] === 'or') {
|
|
30
|
-
const [v, inner] = info.slice(1)
|
|
31
|
-
.reduce(flattenStep, [nextVisited, []]);
|
|
32
|
-
return [v, [...out, ...inner]];
|
|
33
|
-
}
|
|
34
|
-
return [nextVisited, [...out, t]];
|
|
35
|
-
}
|
|
36
|
-
return [visited, [...out, t]];
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* Walks `types` and produces a flat list of `or` variants:
|
|
40
|
-
*
|
|
41
|
-
* - Variants whose thunk resolves to `['or', ...]` are inlined.
|
|
42
|
-
* - Each thunk is resolved at most once; thunks reached a second time are
|
|
43
|
-
* kept as-is, so self-referential `or` schemas terminate.
|
|
44
|
-
*/
|
|
45
|
-
const flattenOr = (types) => types.reduce(flattenStep, [new Set(), []])[1];
|
|
46
|
-
const collectStep = ([u, p], t) => {
|
|
47
|
-
if (u) {
|
|
48
|
-
return [u, p];
|
|
49
|
-
}
|
|
50
|
-
const tag = variantTag(t);
|
|
51
|
-
if (tag === 'unknown') {
|
|
52
|
-
return [true, p];
|
|
53
|
-
}
|
|
54
|
-
return tag !== null && isPrim0(tag) ? [u, new Set([...p, tag])] : [u, p];
|
|
55
|
-
};
|
|
56
|
-
const dedupStep = ([primThunks, acc], t) => primThunks.has(typeof t) || acc.some(r => Object.is(r, t))
|
|
57
|
-
? [primThunks, acc]
|
|
58
|
-
: [primThunks, [...acc, t]];
|
|
59
|
-
/**
|
|
60
|
-
* Drops variants that are trivially subsumed by another variant.
|
|
61
|
-
*
|
|
62
|
-
* Trivial subset rules handled here:
|
|
63
|
-
* - any variant ⊆ `unknown` — if `unknown` is present, the entire union is
|
|
64
|
-
* `unknown`.
|
|
65
|
-
* - a primitive const ⊆ its primitive type thunk — `42 ⊆ number`,
|
|
66
|
-
* `'hi' ⊆ string`, `true ⊆ boolean`, `7n ⊆ bigint`.
|
|
67
|
-
*
|
|
68
|
-
* `Object.is` is used for deduplication, so `NaN` collapses with itself and
|
|
69
|
-
* `+0` and `-0` stay distinct — matching `constPrimitiveValidate`.
|
|
70
|
-
*
|
|
71
|
-
* Full structural subset (tuples/structs/`or`/recursive schemas) is left to
|
|
72
|
-
* a future change — see goals 1 and 3 of issue 130.
|
|
73
|
-
*/
|
|
74
|
-
const reduceOr = (types) => {
|
|
75
|
-
const flat = flattenOr(types);
|
|
76
|
-
const [hasUnknown, primThunks] = flat.reduce(collectStep, [false, new Set()]);
|
|
77
|
-
return hasUnknown
|
|
78
|
-
? [unknown]
|
|
79
|
-
: flat.reduce(dedupStep, [primThunks, []])[1];
|
|
80
|
-
};
|
|
81
22
|
/**
|
|
82
23
|
* Constructs a schema that validates a value matching any of the given schemas.
|
|
83
24
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* -
|
|
87
|
-
*
|
|
88
|
-
* (e.g. `or(42, number)` → `or(number)`),
|
|
89
|
-
* - duplicate variants are deduplicated via `Object.is`.
|
|
90
|
-
*
|
|
91
|
-
* See `issues/130-or-optimization.md`.
|
|
25
|
+
* `or` is intentionally a lazy, allocation-free constructor: it captures its
|
|
26
|
+
* arguments in a thunk and does no flattening, deduplication, subset analysis,
|
|
27
|
+
* or canonical-form work. All such algebra lives on the serializable data form
|
|
28
|
+
* — see `issues/143-rtti-data.md`.
|
|
92
29
|
*/
|
|
93
|
-
export const or = (...types) =>
|
|
94
|
-
const reduced = reduceOr(types);
|
|
95
|
-
return (() => ['or', ...reduced]);
|
|
96
|
-
};
|
|
30
|
+
export const or = (...types) => () => ['or', ...types];
|
|
97
31
|
/** Constructs a schema that validates a value matching `T` or `undefined`. */
|
|
98
32
|
export const option = (t) => or(t, undefined);
|
|
99
33
|
/** Schema that never matches any value — the empty union, corresponding to TypeScript's `never`. */
|
|
@@ -1,32 +1,8 @@
|
|
|
1
1
|
import { type Type } from '../module.f.ts';
|
|
2
|
-
import { type Result as
|
|
3
|
-
export type
|
|
2
|
+
import { type Result as CommonValidateResult, type Validate } from '../common/module.f.ts';
|
|
3
|
+
export { type Path, type ValidationError } from '../common/module.f.ts';
|
|
4
4
|
/** Parse result: either the freshly constructed typed value or a `ValidationError`. */
|
|
5
|
-
export type Result<T extends Type> =
|
|
5
|
+
export type Result<T extends Type> = CommonValidateResult<T>;
|
|
6
6
|
/** A function that parses an unknown value into the schema `T`. */
|
|
7
7
|
export type Parse<T extends Type> = Validate<T>;
|
|
8
|
-
/**
|
|
9
|
-
* Creates a parser function for the given RTTI schema.
|
|
10
|
-
*
|
|
11
|
-
* The returned function takes an unknown value and returns either
|
|
12
|
-
* `['ok', newValue]` containing a freshly constructed value matching the schema,
|
|
13
|
-
* or `['error', { path, message }]` describing the failure location.
|
|
14
|
-
*
|
|
15
|
-
* @param rtti - A schema `Type`: a `Thunk` for tag-based schemas, or a `Const`
|
|
16
|
-
* (primitive literal, tuple, or struct) for exact-value schemas.
|
|
17
|
-
* @returns A `Parse<T>` function.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```ts
|
|
21
|
-
* const p = parse(array(number))
|
|
22
|
-
* p([1, 2, 3]) // ['ok', [1, 2, 3]] (a new array)
|
|
23
|
-
* p([1, 'two']) // ['error', { path: ['1'], message: 'unexpected value' }]
|
|
24
|
-
*
|
|
25
|
-
* // tuples are closed: extra elements are dropped
|
|
26
|
-
* parse([number, number] as const)([1, 2, 3]) // ['ok', [1, 2]]
|
|
27
|
-
*
|
|
28
|
-
* // structs drop undeclared keys
|
|
29
|
-
* parse({ a: number } as const)({ a: 1, b: 2 }) // ['ok', { a: 1 }]
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
8
|
export declare const parse: <T extends Type>(rtti: T) => Parse<T>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {} from "../module.f.js";
|
|
2
2
|
import { ok } from "../../result/module.f.js";
|
|
3
|
-
import { constPrimitiveValidate, prependPath, primitive0Validate, verror, } from "../validate/module.f.js";
|
|
4
3
|
import { isArray as commonIsArray } from "../../array/module.f.js";
|
|
5
4
|
import { isObject as commonIsObject } from "../../object/module.f.js";
|
|
6
5
|
import { find, map as listMap } from "../../list/module.f.js";
|
|
6
|
+
import { constPrimitiveValidate, prependPath, primitive0Validate, verror, visit, } from "../common/module.f.js";
|
|
7
|
+
export {} from "../common/module.f.js";
|
|
7
8
|
const indexedFirstError = (results) => {
|
|
8
9
|
// TODO: findIndex breaks type inference,
|
|
9
10
|
// we should replace it with something else.
|
|
@@ -65,12 +66,6 @@ const structParse = (rtti) => value => {
|
|
|
65
66
|
? ok(Object.fromEntries(results.map(([k, r]) => [k, r[1]])))
|
|
66
67
|
: prependPath(err[0], err[1]));
|
|
67
68
|
};
|
|
68
|
-
const constObjectParse = (rtti) => commonIsArray(rtti)
|
|
69
|
-
? tupleParse(rtti)
|
|
70
|
-
: structParse(rtti);
|
|
71
|
-
const constParse = (rtti) => typeof rtti === 'object' && rtti !== null
|
|
72
|
-
? constObjectParse(rtti)
|
|
73
|
-
: constPrimitiveValidate(rtti);
|
|
74
69
|
const findFirst = find(verror('no match'))((k) => k[0] === 'ok');
|
|
75
70
|
const orParse = (rtti) => value => findFirst(listMap(t => parse(t)(value))(rtti));
|
|
76
71
|
/**
|
|
@@ -97,17 +92,14 @@ const orParse = (rtti) => value => findFirst(listMap(t => parse(t)(value))(rtti)
|
|
|
97
92
|
* parse({ a: number } as const)({ a: 1, b: 2 }) // ['ok', { a: 1 }]
|
|
98
93
|
* ```
|
|
99
94
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
return primitive0Validate(tag);
|
|
111
|
-
}
|
|
112
|
-
return constParse(rtti);
|
|
95
|
+
const parseVisitor = {
|
|
96
|
+
tuple: tupleParse,
|
|
97
|
+
struct: structParse,
|
|
98
|
+
array: arrayParse,
|
|
99
|
+
record: recordParse,
|
|
100
|
+
or: orParse,
|
|
101
|
+
constPrimitive: constPrimitiveValidate,
|
|
102
|
+
primitive0: primitive0Validate,
|
|
103
|
+
unknown: () => ok,
|
|
113
104
|
};
|
|
105
|
+
export const parse = (rtti) => visit(parseVisitor)(rtti);
|
|
@@ -2,33 +2,5 @@ declare const _default: {
|
|
|
2
2
|
typeof: {
|
|
3
3
|
[k: string]: (() => void)[];
|
|
4
4
|
};
|
|
5
|
-
or: {
|
|
6
|
-
flatten: {
|
|
7
|
-
shallow: () => void;
|
|
8
|
-
deep: () => void;
|
|
9
|
-
manualOrThunk: () => void;
|
|
10
|
-
selfReferential: () => void;
|
|
11
|
-
};
|
|
12
|
-
unknownCollapse: {
|
|
13
|
-
withConst: () => void;
|
|
14
|
-
withThunk: () => void;
|
|
15
|
-
nested: () => void;
|
|
16
|
-
};
|
|
17
|
-
dropPrimitiveSubset: {
|
|
18
|
-
number: () => void;
|
|
19
|
-
boolean: () => void;
|
|
20
|
-
string: () => void;
|
|
21
|
-
bigint: () => void;
|
|
22
|
-
keepIfThunkAbsent: () => void;
|
|
23
|
-
mixed: () => void;
|
|
24
|
-
};
|
|
25
|
-
dedup: {
|
|
26
|
-
sameThunkReference: () => void;
|
|
27
|
-
samePrimitive: () => void;
|
|
28
|
-
nanCollapses: () => void;
|
|
29
|
-
signedZeroDistinct: () => void;
|
|
30
|
-
};
|
|
31
|
-
emptyStillNever: () => void;
|
|
32
|
-
};
|
|
33
5
|
};
|
|
34
6
|
export default _default;
|