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.
Files changed (38) hide show
  1. package/fs/ci/common/module.f.d.ts +50 -16
  2. package/fs/ci/common/module.f.js +19 -0
  3. package/fs/ci/config/module.f.d.ts +2 -2
  4. package/fs/ci/config/module.f.js +2 -2
  5. package/fs/ci/module.f.js +1 -1
  6. package/fs/ci/test.f.js +6 -2
  7. package/fs/dev/module.f.d.ts +2 -1
  8. package/fs/dev/module.f.js +25 -16
  9. package/fs/dev/tf/module.f.js +10 -0
  10. package/fs/dev/tf/test.f.d.ts +5 -0
  11. package/fs/dev/tf/test.f.js +14 -0
  12. package/fs/fjs/module.f.js +5 -3
  13. package/fs/io/module.d.ts +2 -2
  14. package/fs/io/module.f.d.ts +3 -15
  15. package/fs/io/module.f.js +14 -8
  16. package/fs/io/module.js +2 -2
  17. package/fs/io/virtual/module.f.js +2 -2
  18. package/fs/types/effects/mock/module.f.d.ts +1 -1
  19. package/fs/types/effects/mock/module.f.js +1 -1
  20. package/fs/types/effects/module.f.d.ts +5 -7
  21. package/fs/types/effects/module.f.js +1 -2
  22. package/fs/types/effects/module.js +1 -1
  23. package/fs/types/effects/node/module.f.d.ts +34 -25
  24. package/fs/types/effects/node/module.f.js +9 -7
  25. package/fs/types/effects/node/test.f.js +1 -1
  26. package/fs/types/effects/node/virtual/module.f.js +16 -5
  27. package/fs/types/rtti/common/module.f.d.ts +77 -0
  28. package/fs/types/rtti/common/module.f.js +45 -0
  29. package/fs/types/rtti/module.f.d.ts +4 -8
  30. package/fs/types/rtti/module.f.js +5 -71
  31. package/fs/types/rtti/parse/module.f.d.ts +3 -27
  32. package/fs/types/rtti/parse/module.f.js +12 -20
  33. package/fs/types/rtti/test.f.d.ts +0 -28
  34. package/fs/types/rtti/test.f.js +0 -107
  35. package/fs/types/rtti/ts/module.f.d.ts +9 -6
  36. package/fs/types/rtti/validate/module.f.d.ts +3 -79
  37. package/fs/types/rtti/validate/module.f.js +13 -36
  38. 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 RestFunc, type ToAsyncOperationMap } from '../module.f.ts';
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>(_: readonly Effect<never, T>[]) => readonly 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', (_: string) => IoResult<Vec>];
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 MkdirParam = readonly [string, MakeDirectoryOptions?];
22
- export type Mkdir = readonly ['mkdir', (_: MkdirParam) => IoResult<void>];
23
- export declare const mkdir: RestFunc<Mkdir>;
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 ReaddirParam = readonly [string, ReaddirOptions];
39
- export type Readdir = readonly ['readdir', (_: ReaddirParam) => IoResult<readonly Dirent[]>];
40
- export declare const readdir: RestFunc<Readdir>;
41
- export type WriteFileParam = readonly [string, Vec];
42
- export type WriteFile = readonly ['writeFile', (_: WriteFileParam) => IoResult<void>];
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 ExecParam = readonly [string, string?];
51
- export type Exec = readonly ['exec', (_: ExecParam) => IoResult<ExecResult>];
52
- export declare const exec: RestFunc<Exec>;
53
- export type Fs = Mkdir | ReadFile | Readdir | WriteFile | Rm | Exec;
54
- export type Error = ['error', (_: string) => void];
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', (_: string) => void];
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', (_: RequestListener<Operation>) => Server];
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', (_: readonly [Server, number]) => void];
78
- export declare const listen: RestFunc<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 NodeOp = All | Fetch | Console | Fs | Http | Forever;
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
- export type NodeProgram = (argv: readonly string[]) => Effect<NodeOp, number>;
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 { doRest, do_ } from "../module.f.js";
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 = doRest('mkdir');
13
+ export const mkdir = do_('mkdir');
14
14
  export const readFile = do_('readFile');
15
- export const readdir = doRest('readdir');
16
- export const writeFile = doRest('writeFile');
15
+ export const readdir = do_('readdir');
16
+ export const writeFile = do_('writeFile');
17
17
  export const rm = do_('rm');
18
- export const exec = doRest('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 = doRest('listen');
23
+ export const listen = do_('listen');
23
24
  export const forever = do_('forever');
25
+ export const import_ = do_('import');
@@ -24,7 +24,7 @@ export default {
24
24
  if (cmd !== 'readFile') {
25
25
  throw cmd;
26
26
  }
27
- if (p !== 'hello') {
27
+ if (p[0] !== 'hello') {
28
28
  throw p;
29
29
  }
30
30
  e = cont(['ok', vec8(0x15n)]);
@@ -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, [path, p]) => mkdir(p !== undefined)(state, path),
138
+ mkdir: (state, path, p) => mkdir(p !== undefined)(state, path),
130
139
  readFile,
131
- readdir: (state, [path, { recursive }]) => readdir(path, recursive === true)(state, path),
132
- writeFile: (state, [path, payload]) => writeFile(payload)(state, path),
133
- rm: (state, path) => rm(state, path),
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
- * The resulting `or` is normalized at construction time:
67
- * - nested `or` thunks are flattened into the outer union,
68
- * - any `unknown` variant collapses the whole union to `unknown`,
69
- * - primitive consts subsumed by a matching primitive thunk are dropped
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
- * The resulting `or` is normalized at construction time:
85
- * - nested `or` thunks are flattened into the outer union,
86
- * - any `unknown` variant collapses the whole union to `unknown`,
87
- * - primitive consts subsumed by a matching primitive thunk are dropped
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 ValidateResult, type Validate } from '../validate/module.f.ts';
3
- export type { Path, ValidationError } from '../validate/module.f.ts';
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> = ValidateResult<T>;
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
- export const parse = (rtti) => {
101
- if (typeof rtti === 'function') {
102
- const [tag, ...value] = rtti();
103
- switch (tag) {
104
- case 'const': return constParse(value[0]);
105
- case 'array': return arrayParse(value[0]);
106
- case 'record': return recordParse(value[0]);
107
- case 'unknown': return ok;
108
- case 'or': return orParse(value);
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;