functionalscript 0.14.0 → 0.14.2
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/config/module.f.d.ts +3 -3
- package/fs/ci/config/module.f.js +3 -3
- package/fs/ci/module.f.d.ts +1 -1
- package/fs/ci/module.f.js +22 -19
- package/fs/ci/rust/module.f.d.ts +1 -1
- package/fs/ci/rust/module.f.js +1 -1
- package/fs/ci/test.f.d.ts +5 -0
- package/fs/ci/test.f.js +40 -0
- package/fs/dev/tf/all.test.js +1 -52
- package/fs/dev/tf/module.d.ts +1 -0
- package/fs/dev/tf/module.js +52 -0
- package/fs/types/rtti/module.f.d.ts +12 -1
- package/fs/types/rtti/module.f.js +75 -2
- package/fs/types/rtti/parse/test.f.d.ts +12 -0
- package/fs/types/rtti/parse/test.f.js +29 -0
- package/fs/types/rtti/test.f.d.ts +28 -0
- package/fs/types/rtti/test.f.js +107 -6
- package/fs/types/rtti/validate/module.f.d.ts +7 -1
- package/fs/types/rtti/validate/module.f.js +8 -2
- package/fs/types/rtti/validate/test.f.d.ts +12 -0
- package/fs/types/rtti/validate/test.f.js +29 -0
- package/package.json +1 -1
|
@@ -12,9 +12,9 @@ export declare const images: {
|
|
|
12
12
|
readonly arm: "windows-11-arm";
|
|
13
13
|
};
|
|
14
14
|
};
|
|
15
|
-
export declare const bun = "1.3.
|
|
15
|
+
export declare const bun = "1.3.14";
|
|
16
16
|
export declare const deno = "2.7.14";
|
|
17
|
-
export declare const playwright = "1.
|
|
17
|
+
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";
|
|
@@ -22,4 +22,4 @@ export declare const node: {
|
|
|
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.
|
|
25
|
+
export declare const tsgo = "7.0.0-dev.20260512.1";
|
package/fs/ci/config/module.f.js
CHANGED
|
@@ -14,11 +14,11 @@ export const images = {
|
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
16
|
// https://bun.sh/
|
|
17
|
-
export const bun = '1.3.
|
|
17
|
+
export const bun = '1.3.14';
|
|
18
18
|
// https://deno.com/
|
|
19
19
|
export const deno = '2.7.14';
|
|
20
20
|
// https://www.npmjs.com/package/playwright
|
|
21
|
-
export const playwright = '1.
|
|
21
|
+
export const playwright = '1.60.0';
|
|
22
22
|
// https://rust-lang.org/
|
|
23
23
|
export const rust = '1.95.0';
|
|
24
24
|
// https://nodejs.org/en/download
|
|
@@ -31,4 +31,4 @@ 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.
|
|
34
|
+
export const tsgo = '7.0.0-dev.20260512.1';
|
package/fs/ci/module.f.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Effect } from '../types/effects/module.f.ts';
|
|
2
2
|
import { type NodeOp } from '../types/effects/node/module.f.ts';
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const ci: (rust: boolean) => Effect<NodeOp, number>;
|
|
4
4
|
declare const _default: () => Effect<NodeOp, number>;
|
|
5
5
|
export default _default;
|
package/fs/ci/module.f.js
CHANGED
|
@@ -13,28 +13,31 @@ import { nodeMainSteps, nodeVersions } from "./node/module.f.js";
|
|
|
13
13
|
import { playwrightJob } from "./playwright/module.f.js";
|
|
14
14
|
import { bunSteps } from "./bun/module.f.js";
|
|
15
15
|
import { denoSteps } from "./deno/module.f.js";
|
|
16
|
-
const job = (
|
|
17
|
-
const id = `${
|
|
18
|
-
const image = images[
|
|
16
|
+
const job = (rust) => (o) => (a) => {
|
|
17
|
+
const id = `${o}-${a}`;
|
|
18
|
+
const image = images[o][a];
|
|
19
19
|
const result = [
|
|
20
|
-
...rustSteps(
|
|
21
|
-
...nodeMainSteps(
|
|
20
|
+
...(rust ? rustSteps(o, a) : []),
|
|
21
|
+
...nodeMainSteps(o),
|
|
22
22
|
...denoSteps,
|
|
23
|
-
...bunSteps(
|
|
23
|
+
...bunSteps(o, a),
|
|
24
24
|
];
|
|
25
25
|
return [id, { 'runs-on': image, steps: toSteps(result) }];
|
|
26
26
|
};
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
export const ci = (rust) => {
|
|
28
|
+
const jobs = {
|
|
29
|
+
...Object.fromEntries(os.flatMap(o => architecture.map(job(rust)(o)))),
|
|
30
|
+
...nodeVersions,
|
|
31
|
+
playwright: playwrightJob,
|
|
32
|
+
};
|
|
33
|
+
const gha = {
|
|
34
|
+
name: 'CI',
|
|
35
|
+
on: { pull_request: {} },
|
|
36
|
+
jobs,
|
|
37
|
+
};
|
|
38
|
+
return begin
|
|
39
|
+
.step(() => writeFile('.github/workflows/ci.yml', utf8(JSON.stringify(gha, null, ' '))))
|
|
40
|
+
.step(() => pure(0));
|
|
31
41
|
};
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
on: { pull_request: {} },
|
|
35
|
-
jobs,
|
|
36
|
-
};
|
|
37
|
-
export const effect = begin
|
|
38
|
-
.step(() => writeFile('.github/workflows/ci.yml', utf8(JSON.stringify(gha, null, ' '))))
|
|
39
|
-
.step(() => pure(0));
|
|
40
|
-
export default () => effect;
|
|
42
|
+
const defaultEffect = ci(true);
|
|
43
|
+
export default () => defaultEffect;
|
package/fs/ci/rust/module.f.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type Architecture, type MetaStep, type Os } from '../common/module.f.ts';
|
|
2
|
-
export declare const rustSteps: (
|
|
2
|
+
export declare const rustSteps: (v: Os, a: Architecture) => readonly MetaStep[];
|
package/fs/ci/rust/module.f.js
CHANGED
package/fs/ci/test.f.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ci } from "./module.f.js";
|
|
2
|
+
import { utf8ToString } from "../text/module.f.js";
|
|
3
|
+
import { isVec } from "../types/bit_vec/module.f.js";
|
|
4
|
+
import { emptyState, virtual } from "../types/effects/node/virtual/module.f.js";
|
|
5
|
+
const hasCargo = (gha) => Object.values(gha.jobs).some(job => job.steps.some(step => step.run?.includes('cargo')));
|
|
6
|
+
const githubState = {
|
|
7
|
+
...emptyState,
|
|
8
|
+
root: { '.github': { workflows: {} } },
|
|
9
|
+
};
|
|
10
|
+
const run = (rust) => {
|
|
11
|
+
const [state, result] = virtual(githubState)(ci(rust));
|
|
12
|
+
if (result !== 0) {
|
|
13
|
+
throw result;
|
|
14
|
+
}
|
|
15
|
+
const dotGithub = state.root['.github'];
|
|
16
|
+
if (dotGithub === undefined || isVec(dotGithub)) {
|
|
17
|
+
throw dotGithub;
|
|
18
|
+
}
|
|
19
|
+
const workflows = dotGithub['workflows'];
|
|
20
|
+
if (workflows === undefined || isVec(workflows)) {
|
|
21
|
+
throw workflows;
|
|
22
|
+
}
|
|
23
|
+
const file = workflows['ci.yml'];
|
|
24
|
+
if (!isVec(file)) {
|
|
25
|
+
throw file;
|
|
26
|
+
}
|
|
27
|
+
return JSON.parse(utf8ToString(file));
|
|
28
|
+
};
|
|
29
|
+
export default {
|
|
30
|
+
rust: () => {
|
|
31
|
+
if (!hasCargo(run(true))) {
|
|
32
|
+
throw 'expected Rust steps';
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
noRust: () => {
|
|
36
|
+
if (hasCargo(run(false))) {
|
|
37
|
+
throw 'unexpected Rust steps';
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
package/fs/dev/tf/all.test.js
CHANGED
|
@@ -1,54 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { loadModuleMap } from "../module.f.js";
|
|
3
|
-
import { isTest, parseTestSet } from "./module.f.js";
|
|
4
|
-
import * as nodeTest from 'node:test';
|
|
5
|
-
const isBun = typeof Bun !== 'undefined';
|
|
6
|
-
const isPlaywright = typeof process !== 'undefined' && process?.env?.PLAYWRIGHT_TEST !== undefined;
|
|
7
|
-
const createFramework = (fw) => (prefix, f) => fw.test(prefix, t => f((name, v) => t.test(name, v)));
|
|
8
|
-
// Bun doesn't support nested tests yet.
|
|
9
|
-
const createBunFramework = (fw) => (prefix, f) => f((name, v) => fw.test(`${prefix}: ${name}`, v));
|
|
10
|
-
const createPlaywrightFramework = async () => {
|
|
11
|
-
const pwTest = (await import('@playwright/test')).test;
|
|
12
|
-
return (prefix, f) => f((name, v) => pwTest(`${prefix}: ${name}`, v));
|
|
13
|
-
};
|
|
14
|
-
const framework = isPlaywright ? await createPlaywrightFramework() :
|
|
15
|
-
isBun ? createBunFramework(nodeTest) :
|
|
16
|
-
createFramework(nodeTest);
|
|
17
|
-
const parse = parseTestSet(io.tryCatch);
|
|
18
|
-
const scanModule = (x) => async (subTestRunner) => {
|
|
19
|
-
let subTests = [x];
|
|
20
|
-
while (true) {
|
|
21
|
-
const [first, ...rest] = subTests;
|
|
22
|
-
if (first === undefined) {
|
|
23
|
-
break;
|
|
24
|
-
}
|
|
25
|
-
subTests = rest;
|
|
26
|
-
//
|
|
27
|
-
const [name, value, throws] = first;
|
|
28
|
-
const set = parse(throws)(value);
|
|
29
|
-
if (typeof set === 'function') {
|
|
30
|
-
await subTestRunner(name, () => {
|
|
31
|
-
const r = set();
|
|
32
|
-
// The result of a function is walked as a fresh sub-tree;
|
|
33
|
-
// the parent's `throws` flag does not propagate into it.
|
|
34
|
-
subTests = [...subTests, [`${name}()`, r, false]];
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
for (const [j, y] of set) {
|
|
39
|
-
const pr = `${name}/${j}`;
|
|
40
|
-
subTests = [...subTests, [pr, y, throws || j === 'throw']];
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
const run = async () => {
|
|
46
|
-
const x = await loadModuleMap(io);
|
|
47
|
-
for (const [i, v] of Object.entries(x)) {
|
|
48
|
-
if (isTest(i)) {
|
|
49
|
-
framework(i, scanModule(['', v.default, false]));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
};
|
|
1
|
+
import { run } from "./module.js";
|
|
53
2
|
// we need `await` for Playwright.
|
|
54
3
|
await run();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const run: () => Promise<void>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { io } from "../../io/module.js";
|
|
2
|
+
import { loadModuleMap } from "../module.f.js";
|
|
3
|
+
import { isTest, parseTestSet } from "./module.f.js";
|
|
4
|
+
import * as nodeTest from 'node:test';
|
|
5
|
+
const isBun = typeof Bun !== 'undefined';
|
|
6
|
+
const isPlaywright = typeof process !== 'undefined' && process?.env?.PLAYWRIGHT_TEST !== undefined;
|
|
7
|
+
const createFramework = (fw) => (prefix, f) => fw.test(prefix, t => f((name, v) => t.test(name, v)));
|
|
8
|
+
// Bun doesn't support nested tests yet.
|
|
9
|
+
const createBunFramework = (fw) => (prefix, f) => f((name, v) => fw.test(`${prefix}: ${name}`, v));
|
|
10
|
+
const createPlaywrightFramework = async () => {
|
|
11
|
+
const pwTest = (await import('@playwright/test')).test;
|
|
12
|
+
return (prefix, f) => f((name, v) => pwTest(`${prefix}: ${name}`, v));
|
|
13
|
+
};
|
|
14
|
+
const framework = isPlaywright ? await createPlaywrightFramework() :
|
|
15
|
+
isBun ? createBunFramework(nodeTest) :
|
|
16
|
+
createFramework(nodeTest);
|
|
17
|
+
const parse = parseTestSet(io.tryCatch);
|
|
18
|
+
const scanModule = (x) => async (subTestRunner) => {
|
|
19
|
+
let subTests = [x];
|
|
20
|
+
while (true) {
|
|
21
|
+
const [first, ...rest] = subTests;
|
|
22
|
+
if (first === undefined) {
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
subTests = rest;
|
|
26
|
+
//
|
|
27
|
+
const [name, value, throws] = first;
|
|
28
|
+
const set = parse(throws)(value);
|
|
29
|
+
if (typeof set === 'function') {
|
|
30
|
+
await subTestRunner(name, () => {
|
|
31
|
+
const r = set();
|
|
32
|
+
// The result of a function is walked as a fresh sub-tree;
|
|
33
|
+
// the parent's `throws` flag does not propagate into it.
|
|
34
|
+
subTests = [...subTests, [`${name}()`, r, false]];
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
for (const [j, y] of set) {
|
|
39
|
+
const pr = `${name}/${j}`;
|
|
40
|
+
subTests = [...subTests, [pr, y, throws || j === 'throw']];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
export const run = async () => {
|
|
46
|
+
const x = await loadModuleMap(io);
|
|
47
|
+
for (const [i, v] of Object.entries(x)) {
|
|
48
|
+
if (isTest(i)) {
|
|
49
|
+
framework(i, scanModule(['', v.default, false]));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -60,7 +60,18 @@ export type Record<T extends Type> = Type1<'record', T>;
|
|
|
60
60
|
export declare const record: MakeType1<'record'>;
|
|
61
61
|
/** Schema type for a union of types `T`. */
|
|
62
62
|
export type Or<T extends readonly Type[]> = () => readonly ['or', ...T];
|
|
63
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Constructs a schema that validates a value matching any of the given schemas.
|
|
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`.
|
|
74
|
+
*/
|
|
64
75
|
export declare const or: <T extends readonly Type[]>(...types: T) => Or<T>;
|
|
65
76
|
/** Constructs a schema that validates a value matching `T` or `undefined`. */
|
|
66
77
|
export declare const option: <T extends Type>(t: T) => Or<readonly [T, undefined]>;
|
|
@@ -19,8 +19,81 @@ 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
|
-
/**
|
|
23
|
-
|
|
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
|
+
/**
|
|
82
|
+
* Constructs a schema that validates a value matching any of the given schemas.
|
|
83
|
+
*
|
|
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`.
|
|
92
|
+
*/
|
|
93
|
+
export const or = (...types) => {
|
|
94
|
+
const reduced = reduceOr(types);
|
|
95
|
+
return (() => ['or', ...reduced]);
|
|
96
|
+
};
|
|
24
97
|
/** Constructs a schema that validates a value matching `T` or `undefined`. */
|
|
25
98
|
export const option = (t) => or(t, undefined);
|
|
26
99
|
/** Schema that never matches any value — the empty union, corresponding to TypeScript's `never`. */
|
|
@@ -31,6 +31,18 @@ declare const _default: {
|
|
|
31
31
|
ok: () => void;
|
|
32
32
|
error: () => void;
|
|
33
33
|
};
|
|
34
|
+
nan: {
|
|
35
|
+
ok: () => void;
|
|
36
|
+
error: () => void;
|
|
37
|
+
};
|
|
38
|
+
infinity: {
|
|
39
|
+
ok: () => void;
|
|
40
|
+
error: () => void;
|
|
41
|
+
};
|
|
42
|
+
signedZero: {
|
|
43
|
+
distinct: () => void;
|
|
44
|
+
self: () => void;
|
|
45
|
+
};
|
|
34
46
|
string: {
|
|
35
47
|
ok: () => void;
|
|
36
48
|
error: () => void;
|
|
@@ -120,6 +120,35 @@ export default {
|
|
|
120
120
|
ok: () => assertOk(parse(42)(42)),
|
|
121
121
|
error: () => assertError(parse(42)(43)),
|
|
122
122
|
},
|
|
123
|
+
nan: {
|
|
124
|
+
ok: () => assertOk(parse(NaN)(NaN)),
|
|
125
|
+
error: () => {
|
|
126
|
+
assertError(parse(NaN)(0));
|
|
127
|
+
assertError(parse(0)(NaN));
|
|
128
|
+
assertError(parse(42)(NaN));
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
infinity: {
|
|
132
|
+
ok: () => {
|
|
133
|
+
assertOk(parse(Infinity)(Infinity));
|
|
134
|
+
assertOk(parse(-Infinity)(-Infinity));
|
|
135
|
+
},
|
|
136
|
+
error: () => {
|
|
137
|
+
assertError(parse(Infinity)(-Infinity));
|
|
138
|
+
assertError(parse(Infinity)(0));
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
signedZero: {
|
|
142
|
+
// `Object.is` distinguishes +0 and -0; `===` treats them equal.
|
|
143
|
+
distinct: () => {
|
|
144
|
+
assertError(parse(0)(-0));
|
|
145
|
+
assertError(parse(-0)(0));
|
|
146
|
+
},
|
|
147
|
+
self: () => {
|
|
148
|
+
assertOk(parse(0)(0));
|
|
149
|
+
assertOk(parse(-0)(-0));
|
|
150
|
+
},
|
|
151
|
+
},
|
|
123
152
|
string: {
|
|
124
153
|
ok: () => assertOk(parse('hello')('hello')),
|
|
125
154
|
error: () => assertError(parse('hello')('world')),
|
|
@@ -2,5 +2,33 @@ 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
|
+
};
|
|
5
33
|
};
|
|
6
34
|
export default _default;
|
package/fs/types/rtti/test.f.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { boolean, number, string, bigint, unknown, or, never } from "./module.f.js";
|
|
2
|
+
import { assertEq } from "../../dev/module.f.js";
|
|
1
3
|
const tests = {
|
|
2
4
|
undefined: [undefined],
|
|
3
5
|
boolean: [true, false],
|
|
@@ -7,16 +9,115 @@ const tests = {
|
|
|
7
9
|
object: [null, {}, []],
|
|
8
10
|
function: [() => undefined]
|
|
9
11
|
};
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
+
};
|
|
16
22
|
export default {
|
|
17
23
|
typeof: Object.fromEntries(Object.entries(tests).map(([k, a]) => [k, a.map(v => () => {
|
|
18
24
|
if (typeof v !== k) {
|
|
19
25
|
throw `typeof ${v} !== ${k}`;
|
|
20
26
|
}
|
|
21
27
|
})])),
|
|
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
|
+
},
|
|
22
123
|
};
|
|
@@ -53,7 +53,13 @@ export declare const verror: (message: string) => Error<ValidationError>;
|
|
|
53
53
|
export declare const prependPath: (key: string, r: Error<ValidationError>) => Error<ValidationError>;
|
|
54
54
|
/** Validates a `Tag0` primitive schema using `typeof`. */
|
|
55
55
|
export declare const primitive0Validate: <K extends Primitive0, T extends Info0<K>>(tag: K) => Validate<T>;
|
|
56
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Validates a primitive `Const` schema using `Object.is` (SameValue).
|
|
58
|
+
*
|
|
59
|
+
* `Object.is` is used instead of `===` so that:
|
|
60
|
+
* - `NaN` const schemas match `NaN` values (`===` would always fail because `NaN !== NaN`).
|
|
61
|
+
* - `+0` and `-0` are treated as distinct const values.
|
|
62
|
+
*/
|
|
57
63
|
export declare const constPrimitiveValidate: <T extends Primitive>(rtti: T) => Validate<T>;
|
|
58
64
|
/**
|
|
59
65
|
* Creates a validator function for the given RTTI schema.
|
|
@@ -60,8 +60,14 @@ const structValidate = constContainerValidate(isObject, (value, k) => value[k]);
|
|
|
60
60
|
const constObjectValidate = (rtti) => commonIsArray(rtti)
|
|
61
61
|
? tupleValidate(rtti)
|
|
62
62
|
: structValidate(rtti);
|
|
63
|
-
/**
|
|
64
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Validates a primitive `Const` schema using `Object.is` (SameValue).
|
|
65
|
+
*
|
|
66
|
+
* `Object.is` is used instead of `===` so that:
|
|
67
|
+
* - `NaN` const schemas match `NaN` values (`===` would always fail because `NaN !== NaN`).
|
|
68
|
+
* - `+0` and `-0` are treated as distinct const values.
|
|
69
|
+
*/
|
|
70
|
+
export const constPrimitiveValidate = (rtti) => value => Object.is(rtti, value)
|
|
65
71
|
? ok(value)
|
|
66
72
|
: verror('unexpected value');
|
|
67
73
|
const constValidate = (rtti) => typeof rtti === 'object' && rtti !== null
|
|
@@ -31,6 +31,18 @@ declare const _default: {
|
|
|
31
31
|
ok: () => void;
|
|
32
32
|
error: () => void;
|
|
33
33
|
};
|
|
34
|
+
nan: {
|
|
35
|
+
ok: () => void;
|
|
36
|
+
error: () => void;
|
|
37
|
+
};
|
|
38
|
+
infinity: {
|
|
39
|
+
ok: () => void;
|
|
40
|
+
error: () => void;
|
|
41
|
+
};
|
|
42
|
+
signedZero: {
|
|
43
|
+
distinct: () => void;
|
|
44
|
+
self: () => void;
|
|
45
|
+
};
|
|
34
46
|
string: {
|
|
35
47
|
ok: () => void;
|
|
36
48
|
error: () => void;
|
|
@@ -91,6 +91,35 @@ export default {
|
|
|
91
91
|
},
|
|
92
92
|
error: () => assertError(validate(42)(43)),
|
|
93
93
|
},
|
|
94
|
+
nan: {
|
|
95
|
+
ok: () => assertOk(validate(NaN)(NaN)),
|
|
96
|
+
error: () => {
|
|
97
|
+
assertError(validate(NaN)(0));
|
|
98
|
+
assertError(validate(0)(NaN));
|
|
99
|
+
assertError(validate(42)(NaN));
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
infinity: {
|
|
103
|
+
ok: () => {
|
|
104
|
+
assertOk(validate(Infinity)(Infinity));
|
|
105
|
+
assertOk(validate(-Infinity)(-Infinity));
|
|
106
|
+
},
|
|
107
|
+
error: () => {
|
|
108
|
+
assertError(validate(Infinity)(-Infinity));
|
|
109
|
+
assertError(validate(Infinity)(0));
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
signedZero: {
|
|
113
|
+
// `Object.is` distinguishes +0 and -0; `===` treats them equal.
|
|
114
|
+
distinct: () => {
|
|
115
|
+
assertError(validate(0)(-0));
|
|
116
|
+
assertError(validate(-0)(0));
|
|
117
|
+
},
|
|
118
|
+
self: () => {
|
|
119
|
+
assertOk(validate(0)(0));
|
|
120
|
+
assertOk(validate(-0)(-0));
|
|
121
|
+
},
|
|
122
|
+
},
|
|
94
123
|
string: {
|
|
95
124
|
ok: () => {
|
|
96
125
|
assertOk(validate('hello')('hello'));
|