functionalscript 0.14.8 → 0.15.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.
@@ -18,8 +18,8 @@ export declare const playwright = "1.60.0";
18
18
  export declare const rust = "1.95.0";
19
19
  export declare const node: {
20
20
  readonly default: "26.1.0";
21
- readonly others: readonly ["22.22.2", "24.15.0"];
21
+ readonly others: readonly ["22.22.3", "24.15.0"];
22
22
  };
23
23
  export declare const wasmtime = "44.0.1";
24
24
  export declare const wasmer = "7.1.0";
25
- export declare const tsgo = "7.0.0-dev.20260512.1";
25
+ export declare const tsgo = "7.0.0-dev.20260515.1";
@@ -24,11 +24,11 @@ export const rust = '1.95.0';
24
24
  // https://nodejs.org/en/download
25
25
  export const node = {
26
26
  default: '26.1.0',
27
- others: ['22.22.2', '24.15.0'],
27
+ others: ['22.22.3', '24.15.0'],
28
28
  };
29
29
  // https://github.com/bytecodealliance/wasmtime/releases
30
30
  export const wasmtime = '44.0.1';
31
31
  // https://github.com/wasmerio/wasmer/releases
32
32
  export const wasmer = '7.1.0';
33
33
  // https://www.npmjs.com/package/@typescript/native-preview?activeTab=versions
34
- export const tsgo = '7.0.0-dev.20260512.1';
34
+ export const tsgo = '7.0.0-dev.20260515.1';
@@ -11,6 +11,7 @@ export declare const assert: (v: boolean, msg?: unknown) => asserts v;
11
11
  export declare const assertEq: <T>(a: T, b: T) => void;
12
12
  export type Module = {
13
13
  readonly default?: unknown;
14
+ readonly [k: string]: unknown;
14
15
  };
15
16
  export type ModuleMap = {
16
17
  readonly [k in string]: Module;
@@ -93,6 +93,16 @@ export const test = (input) => {
93
93
  if (isTest(k)) {
94
94
  log(`testing ${k}`);
95
95
  [ts, state] = test('| ')(false)(v.default)([ts, state]);
96
+ // Non-default exports are walked as a sibling test group so
97
+ // a test file can spread its tests across multiple named
98
+ // exports (see issue 27 in `issues/README.md`). Skip exports
99
+ // that parseTestSet would treat as empty (constants, types,
100
+ // non-test helpers) to avoid noisy empty entries in output.
101
+ const others = Object.fromEntries(Object.entries(v).filter(([key, val]) => key !== 'default' && ((typeof val === 'function' && val.length === 0) ||
102
+ (typeof val === 'object' && val !== null))));
103
+ if (Object.keys(others).length !== 0) {
104
+ [ts, state] = test('| ')(false)(others)([ts, state]);
105
+ }
96
106
  }
97
107
  return [ts, state];
98
108
  };
@@ -14,3 +14,8 @@ declare const _default: {
14
14
  };
15
15
  };
16
16
  export default _default;
17
+ export declare const namedExportTest: () => void;
18
+ export declare const namedExportGroup: {
19
+ nested: () => void;
20
+ throw: () => never;
21
+ };
@@ -20,3 +20,17 @@ export default {
20
20
  },
21
21
  },
22
22
  };
23
+ // Non-default exports are walked as a sibling test group (see issue 27).
24
+ export const namedExportTest = () => {
25
+ if (2 + 2 !== 4) {
26
+ throw 'arithmetic broken';
27
+ }
28
+ };
29
+ export const namedExportGroup = {
30
+ nested: () => {
31
+ if ('a' + 'b' !== 'ab') {
32
+ throw 'string concat broken';
33
+ }
34
+ },
35
+ throw: () => { throw 'expected to throw'; },
36
+ };
package/fs/io/module.f.js CHANGED
@@ -37,7 +37,7 @@ const collect = async (v) => {
37
37
  };
38
38
  export const fromIo = ({ console: { error, log }, fs: { promises: { mkdir, readFile, readdir, writeFile, rm } }, fetch, http: { createServer }, childProcess, }) => {
39
39
  const result = asyncRun({
40
- all: async (effects) => await Promise.all(effects.map(result)),
40
+ all: async (...effects) => await Promise.all(effects.map(result)),
41
41
  error: async (message) => error(message),
42
42
  log: async (message) => log(message),
43
43
  fetch: async (url) => tc(async () => {
@@ -47,13 +47,13 @@ export const fromIo = ({ console: { error, log }, fs: { promises: { mkdir, readF
47
47
  }
48
48
  return toVec(new Uint8Array(await response.arrayBuffer()));
49
49
  }),
50
- mkdir: param => tc(async () => { await mkdir(...param); }),
50
+ mkdir: (...p) => tc(async () => { await mkdir(...p); }),
51
51
  readFile: path => tc(async () => toVec(await readFile(path))),
52
- readdir: ([path, r]) => tc(async () => (await readdir(path, { ...r, withFileTypes: true }))
52
+ readdir: (path, r) => tc(async () => (await readdir(path, { ...r, withFileTypes: true }))
53
53
  .map(v => ({ name: v.name, parentPath: normalize(v.parentPath), isFile: v.isFile() }))),
54
- writeFile: ([path, data]) => tc(() => writeFile(path, fromVec(data))),
54
+ writeFile: (path, data) => tc(() => writeFile(path, fromVec(data))),
55
55
  rm: path => tc(() => rm(path)),
56
- exec: ([command, stdin]) => new Promise(resolve => {
56
+ exec: (command, stdin) => new Promise(resolve => {
57
57
  const child = childProcess.exec(command, (e, stdout, stderr) => resolve(e !== null ? ['error', e] : ok({ stdout, stderr })));
58
58
  child.stdin?.end(stdin);
59
59
  }),
@@ -75,7 +75,7 @@ export const fromIo = ({ console: { error, log }, fs: { promises: { mkdir, readF
75
75
  const server = asNominal(createServer(nodeRl));
76
76
  return server;
77
77
  },
78
- listen: async ([server, port]) => {
78
+ listen: async (server, port) => {
79
79
  const s = asBase(server);
80
80
  s.listen(port);
81
81
  },
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import type { Effect, Operation, Pr } from "../module.f.ts";
7
7
  export type MemOperationMap<O extends Operation, S> = {
8
- readonly [K in O[0]]: (state: S, payload: Pr<O, K>[0]) => readonly [S, Pr<O, K>[1]];
8
+ readonly [K in O[0]]: (state: S, ...payload: Pr<O, K>[0]) => readonly [S, Pr<O, K>[1]];
9
9
  };
10
10
  export type RunInstance<O extends Operation, S> = (state: S) => <O1 extends O, T>(effect: Effect<O1, T>) => readonly [S, T];
11
11
  export declare const run: <O extends Operation, S>(o: MemOperationMap<O, S>) => RunInstance<O, S>;
@@ -9,7 +9,7 @@ export const run = (o) => state => effect => {
9
9
  }
10
10
  const [cmd, payload, cont] = value;
11
11
  const operation = o[cmd];
12
- const [ns, m] = operation(s, payload);
12
+ const [ns, m] = operation(s, ...payload);
13
13
  s = ns;
14
14
  e = cont(m);
15
15
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- export type Operation = readonly [string, (_: never) => unknown];
6
+ export type Operation = readonly [string, (..._: readonly never[]) => unknown];
7
7
  export type Effect<O extends Operation, T> = {
8
8
  value: Value<O, T>;
9
9
  step: <Q extends Operation, R>(f: (p: T) => Effect<Q, R>) => Effect<O | Q, R>;
@@ -11,20 +11,18 @@ export type Effect<O extends Operation, T> = {
11
11
  export type Value<O extends Operation, T> = Pure<T> | Do<O, T>;
12
12
  export type Pure<T> = readonly [T];
13
13
  export type DoKPR<O extends Operation, T, K extends string, PR extends readonly [unknown, unknown]> = readonly [K, PR[0], (_: PR[1]) => Effect<O, T>];
14
- export type Pr<O extends Operation, K extends O[0]> = O extends readonly [K, (_: infer P) => infer R] ? readonly [P, R] : never;
14
+ export type Pr<O extends Operation, K extends O[0]> = O extends readonly [K, (...args: infer P) => infer R] ? readonly [P, R] : never;
15
15
  export type DoK<O extends Operation, T, K extends O[0]> = DoKPR<O, T, K, Pr<O, K>>;
16
16
  export type Do<O extends Operation, T> = DoK<O, T, O[0]>;
17
17
  export declare const pure: <T>(v: T) => Effect<never, T>;
18
18
  export declare const doFull: <O extends Operation, T, K extends O[0]>(cmd: K, param: Pr<O, K>[0], cont: (input: Pr<O, K>[1]) => Effect<O, T>) => Effect<O, T>;
19
19
  export type Param<O extends Operation> = F<O>[0];
20
20
  export type Return<O extends Operation> = F<O>[1];
21
- export declare const do_: <O extends Operation>(cmd: O[0]) => (param: Param<O>) => Effect<O, Return<O>>;
22
- export declare const doRest: <O extends Operation>(cmd: O[0]) => (...param: Param<O>) => Effect<O, Return<O>>;
21
+ export declare const do_: <O extends Operation>(cmd: O[0]) => (...param: Param<O>) => Effect<O, Return<O>>;
23
22
  export declare const begin: Effect<never, void>;
24
23
  export type ToAsyncOperationMap<O extends Operation> = {
25
- readonly [K in O[0]]: (payload: Pr<O, K>[0]) => Promise<Pr<O, K>[1]>;
24
+ readonly [K in O[0]]: (...payload: Pr<O, K>[0]) => Promise<Pr<O, K>[1]>;
26
25
  };
27
26
  export type F<O extends Operation> = Pr<O, O[0]>;
28
- export type Func<O extends Operation> = (_: Param<O>) => Effect<O, Return<O>>;
29
- export type RestFunc<O extends Operation> = (..._: Param<O>) => Effect<O, Return<O>>;
27
+ export type Func<O extends Operation> = (..._: Param<O>) => Effect<O, Return<O>>;
30
28
  export type ListEffect<O extends Operation, T> = Effect<O, readonly [T, ListEffect<O, T>] | undefined>;
@@ -11,6 +11,5 @@ export const doFull = (cmd, param, cont) => ({
11
11
  value: [cmd, param, cont],
12
12
  step: (f) => doFull(cmd, param, x => cont(x).step(f)),
13
13
  });
14
- export const do_ = (cmd) => (param) => doFull(cmd, param, pure);
15
- export const doRest = (cmd) => (...param) => do_(cmd)(param);
14
+ export const do_ = (cmd) => (...param) => doFull(cmd, param, pure);
16
15
  export const begin = pure(undefined);
@@ -6,7 +6,7 @@ export const asyncRun = (map) => async (effect) => {
6
6
  }
7
7
  const [command, payload, continuation] = value;
8
8
  const operation = map[command];
9
- const result = await operation(payload);
9
+ const result = await operation(...payload);
10
10
  effect = continuation(result);
11
11
  }
12
12
  };
@@ -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,22 @@ 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>;
47
+ export type Exec = readonly ['exec', (command: string, stdin?: string) => IoResult<ExecResult>];
48
+ export declare const exec: Func<Exec>;
53
49
  export type Fs = Mkdir | ReadFile | Readdir | WriteFile | Rm | Exec;
54
- export type Error = ['error', (_: string) => void];
50
+ export type Error = ['error', (message: string) => void];
55
51
  export declare const error: Func<Error>;
56
- export type Log = ['log', (_: string) => void];
52
+ export type Log = ['log', (message: string) => void];
57
53
  export declare const log: Func<Log>;
58
54
  export type Console = Log | Error;
59
55
  export type Server = Nominal<'server', `160855c4f69310fece3273c1853ac32de43dee1eb41bf59d821917f8eebe9272`, unknown>;
@@ -72,10 +68,10 @@ export type ServerResponse = {
72
68
  readonly body: Vec;
73
69
  };
74
70
  export type RequestListener<O extends Operation> = (_: IncomingMessage) => Effect<O, ServerResponse>;
75
- export type CreateServer = ['createServer', (_: RequestListener<Operation>) => Server];
71
+ export type CreateServer = ['createServer', (listener: RequestListener<Operation>) => Server];
76
72
  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>;
73
+ export type Listen = ['listen', (server: Server, port: number) => void];
74
+ export declare const listen: Func<Listen>;
79
75
  export type Http = CreateServer | Listen;
80
76
  export type Forever = ['forever', () => never];
81
77
  export declare const forever: Func<Forever>;
@@ -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,17 @@ 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
19
  export const error = do_('error');
20
20
  export const log = do_('log');
21
21
  export const createServer = do_('createServer');
22
- export const listen = doRest('listen');
22
+ export const listen = do_('listen');
23
23
  export const forever = do_('forever');
@@ -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)]);
@@ -111,7 +111,7 @@ const rm = operation((dir, path) => {
111
111
  });
112
112
  const console = (name) => (state, payload) => [{ ...state, [name]: `${state[name]}${payload}\n` }, undefined];
113
113
  const map = {
114
- all: (state, a) => {
114
+ all: (state, ...a) => {
115
115
  let e = [];
116
116
  for (const i of a) {
117
117
  const [ns, ei] = virtual(state)(i);
@@ -126,11 +126,11 @@ const map = {
126
126
  const result = state.internet[url];
127
127
  return result === undefined ? [state, error('not found')] : [state, ok(result)];
128
128
  },
129
- mkdir: (state, [path, p]) => mkdir(p !== undefined)(state, path),
129
+ mkdir: (state, path, p) => mkdir(p !== undefined)(state, path),
130
130
  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),
131
+ readdir: (state, path, { recursive }) => readdir(path, recursive === true)(state, path),
132
+ writeFile: (state, path, payload) => writeFile(payload)(state, path),
133
+ rm,
134
134
  exec: todo,
135
135
  createServer: todo,
136
136
  listen: todo,
@@ -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`. */
@@ -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;
@@ -1,5 +1,3 @@
1
- import { boolean, number, string, bigint, unknown, or, never } from "./module.f.js";
2
- import { assertEq } from "../../dev/module.f.js";
3
1
  const tests = {
4
2
  undefined: [undefined],
5
3
  boolean: [true, false],
@@ -9,115 +7,10 @@ const tests = {
9
7
  object: [null, {}, []],
10
8
  function: [() => undefined]
11
9
  };
12
- const variantsOf = (t) => {
13
- if (typeof t !== 'function') {
14
- throw 'expected an or thunk';
15
- }
16
- const info = t();
17
- if (info[0] !== 'or') {
18
- throw `expected an or thunk, got ${info[0]}`;
19
- }
20
- return info.slice(1);
21
- };
22
10
  export default {
23
11
  typeof: Object.fromEntries(Object.entries(tests).map(([k, a]) => [k, a.map(v => () => {
24
12
  if (typeof v !== k) {
25
13
  throw `typeof ${v} !== ${k}`;
26
14
  }
27
15
  })])),
28
- or: {
29
- flatten: {
30
- shallow: () => {
31
- const v = variantsOf(or(or(boolean, number), string));
32
- assertEq(v.length, 3);
33
- assertEq(v[0], boolean);
34
- assertEq(v[1], number);
35
- assertEq(v[2], string);
36
- },
37
- deep: () => {
38
- assertEq(variantsOf(or(or(or(boolean, number), string), bigint)).length, 4);
39
- },
40
- manualOrThunk: () => {
41
- // Manually-constructed `() => ['or', ...]` thunks should also flatten.
42
- const inner = () => ['or', boolean, number];
43
- assertEq(variantsOf(or(inner, string)).length, 3);
44
- },
45
- selfReferential: () => {
46
- // A self-referential `or` thunk terminates: it is expanded once,
47
- // then kept as-is on the second encounter (via the visited set).
48
- const t = (() => ['or', boolean, t]);
49
- // expansion: t -> [boolean, t]; t (second) kept as-is; number kept.
50
- assertEq(variantsOf(or(t, number)).length, 3);
51
- },
52
- },
53
- unknownCollapse: {
54
- withConst: () => {
55
- const v = variantsOf(or(unknown, 42));
56
- assertEq(v.length, 1);
57
- assertEq(v[0], unknown);
58
- },
59
- withThunk: () => {
60
- const v = variantsOf(or(number, unknown, string));
61
- assertEq(v.length, 1);
62
- assertEq(v[0], unknown);
63
- },
64
- nested: () => {
65
- // nested `or` whose flattening surfaces an `unknown`
66
- const v = variantsOf(or(or(boolean, unknown), 42));
67
- assertEq(v.length, 1);
68
- assertEq(v[0], unknown);
69
- },
70
- },
71
- dropPrimitiveSubset: {
72
- number: () => {
73
- const v = variantsOf(or(42, number));
74
- assertEq(v.length, 1);
75
- assertEq(v[0], number);
76
- },
77
- boolean: () => {
78
- const v = variantsOf(or(true, false, boolean));
79
- assertEq(v.length, 1);
80
- assertEq(v[0], boolean);
81
- },
82
- string: () => {
83
- const v = variantsOf(or('hi', 'bye', string));
84
- assertEq(v.length, 1);
85
- assertEq(v[0], string);
86
- },
87
- bigint: () => {
88
- const v = variantsOf(or(7n, bigint));
89
- assertEq(v.length, 1);
90
- assertEq(v[0], bigint);
91
- },
92
- keepIfThunkAbsent: () => {
93
- // Without a matching primitive thunk, consts are kept.
94
- assertEq(variantsOf(or(42, 'hello')).length, 2);
95
- },
96
- mixed: () => {
97
- // `null`/`undefined` consts have no matching primitive thunk and remain.
98
- assertEq(variantsOf(or(null, undefined, 42, number)).length, 3);
99
- },
100
- },
101
- dedup: {
102
- sameThunkReference: () => {
103
- assertEq(variantsOf(or(boolean, boolean)).length, 1);
104
- },
105
- samePrimitive: () => {
106
- assertEq(variantsOf(or(42, 42)).length, 1);
107
- },
108
- nanCollapses: () => {
109
- // `Object.is(NaN, NaN)` is true, so duplicate `NaN` variants
110
- // collapse — matching `constPrimitiveValidate` semantics.
111
- assertEq(variantsOf(or(NaN, NaN)).length, 1);
112
- },
113
- signedZeroDistinct: () => {
114
- // `Object.is(+0, -0)` is false, so they remain distinct.
115
- assertEq(variantsOf(or(0, -0)).length, 2);
116
- },
117
- },
118
- emptyStillNever: () => {
119
- assertEq(variantsOf(or()).length, 0);
120
- assertEq(variantsOf(never).length, 0);
121
- },
122
- },
123
16
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.14.8",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "**/*.js",
@@ -18,7 +18,7 @@
18
18
  "website": "node --experimental-strip-types ./fs/fjs/module.ts r ./fs/website/module.f.ts"
19
19
  },
20
20
  "engines": {
21
- "node": ">=20"
21
+ "node": ">=22"
22
22
  },
23
23
  "bin": {
24
24
  "fjs": "fs/fjs/module.js"