functionalscript 0.14.0 → 0.14.1

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.
@@ -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.13";
15
+ export declare const bun = "1.3.14";
16
16
  export declare const deno = "2.7.14";
17
- export declare const playwright = "1.59.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.20260508.1";
25
+ export declare const tsgo = "7.0.0-dev.20260512.1";
@@ -14,11 +14,11 @@ export const images = {
14
14
  }
15
15
  };
16
16
  // https://bun.sh/
17
- export const bun = '1.3.13';
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.59.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.20260508.1';
34
+ export const tsgo = '7.0.0-dev.20260512.1';
@@ -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 effect: Effect<NodeOp, number>;
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 = (v) => (a) => {
17
- const id = `${v}-${a}`;
18
- const image = images[v][a];
16
+ const job = (rust) => (o) => (a) => {
17
+ const id = `${o}-${a}`;
18
+ const image = images[o][a];
19
19
  const result = [
20
- ...rustSteps(a, v),
21
- ...nodeMainSteps(v),
20
+ ...(rust ? rustSteps(o, a) : []),
21
+ ...nodeMainSteps(o),
22
22
  ...denoSteps,
23
- ...bunSteps(v, a),
23
+ ...bunSteps(o, a),
24
24
  ];
25
25
  return [id, { 'runs-on': image, steps: toSteps(result) }];
26
26
  };
27
- const jobs = {
28
- ...Object.fromEntries(os.flatMap(v => architecture.map(job(v)))),
29
- ...nodeVersions,
30
- playwright: playwrightJob,
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 gha = {
33
- name: 'CI',
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;
@@ -1,2 +1,2 @@
1
1
  import { type Architecture, type MetaStep, type Os } from '../common/module.f.ts';
2
- export declare const rustSteps: (a: Architecture, v: Os) => readonly MetaStep[];
2
+ export declare const rustSteps: (v: Os, a: Architecture) => readonly MetaStep[];
@@ -29,7 +29,7 @@ const i686 = (a, v) => {
29
29
  }
30
30
  return [];
31
31
  };
32
- export const rustSteps = (a, v) => [
32
+ export const rustSteps = (v, a) => [
33
33
  test({ run: 'cargo fmt -- --check' }),
34
34
  test({ run: 'cargo clippy -- -D warnings' }),
35
35
  ...cargoTest(),
@@ -0,0 +1,5 @@
1
+ declare const _default: {
2
+ rust: () => void;
3
+ noRust: () => void;
4
+ };
5
+ export default _default;
@@ -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
+ };
@@ -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
- /** Constructs a schema that validates a value matching any of the given schemas. */
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
- /** Constructs a schema that validates a value matching any of the given schemas. */
23
- export const or = (...types) => () => ['or', ...types];
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;
@@ -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 assertOk = ([k]) => { if (k !== 'ok') {
11
- throw 'expected ok';
12
- } };
13
- const assertError = ([k]) => { if (k !== 'error') {
14
- throw 'expected error';
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
- /** Validates a primitive `Const` schema using strict equality (`===`). */
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
- /** Validates a primitive `Const` schema using strict equality (`===`). */
64
- export const constPrimitiveValidate = (rtti) => value => rtti === value
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'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "**/*.js",