functionalscript 0.18.0 → 0.19.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/asn.1/module.f.js +7 -8
  2. package/fs/asn.1/test.f.js +11 -12
  3. package/fs/ci/config/module.f.d.ts +4 -4
  4. package/fs/ci/config/module.f.js +4 -4
  5. package/fs/crypto/sign/module.f.js +3 -3
  6. package/fs/dev/tf/module.d.ts +1 -2
  7. package/fs/dev/tf/module.f.d.ts +10 -5
  8. package/fs/dev/tf/module.f.js +53 -22
  9. package/fs/dev/tf/module.js +4 -103
  10. package/fs/dev/tf/scenarios/all.d.ts +1 -0
  11. package/fs/dev/tf/scenarios/all.js +3 -0
  12. package/fs/dev/tf/scenarios/fail.fail.f.d.ts +1 -0
  13. package/fs/dev/tf/scenarios/fail.fail.f.js +1 -0
  14. package/fs/dev/tf/scenarios/return-value.pass.f.d.ts +1 -0
  15. package/fs/dev/tf/scenarios/return-value.pass.f.js +2 -0
  16. package/fs/dev/tf/scenarios/throw.pass.f.d.ts +6 -0
  17. package/fs/dev/tf/scenarios/throw.pass.f.js +3 -0
  18. package/fs/dev/tf/test.f.js +9 -4
  19. package/fs/io/module.f.d.ts +5 -1
  20. package/fs/io/module.f.js +8 -9
  21. package/fs/io/module.js +30 -0
  22. package/fs/sul/id/module.f.js +3 -4
  23. package/fs/sul/level/literal/module.f.js +3 -3
  24. package/fs/text/sgr/test.f.js +16 -1
  25. package/fs/types/bit_vec/module.f.d.ts +9 -4
  26. package/fs/types/bit_vec/module.f.js +7 -9
  27. package/fs/types/btree/remove/module.f.d.ts +1 -1
  28. package/fs/types/btree/remove/module.f.js +7 -2
  29. package/fs/types/btree/set/module.f.d.ts +1 -1
  30. package/fs/types/btree/set/module.f.js +7 -2
  31. package/fs/types/btree/types/module.f.d.ts +1 -0
  32. package/fs/types/btree/types/module.f.js +1 -1
  33. package/fs/types/effects/node/module.f.d.ts +14 -1
  34. package/fs/types/effects/node/module.f.js +1 -0
  35. package/fs/types/effects/node/virtual/module.f.d.ts +2 -1
  36. package/fs/types/effects/node/virtual/module.f.js +1 -0
  37. package/fs/types/nullable/test.f.js +10 -1
  38. package/package.json +4 -4
@@ -5,19 +5,18 @@
5
5
  * @module
6
6
  */
7
7
  import { bitLength, max } from "../types/bigint/module.f.js";
8
- import { empty, isVec, length, listToVec, msb, uint, unpack, vec, vec8 } from "../types/bit_vec/module.f.js";
8
+ import { empty, isVec, length, msb, uint, unpack, vec, vec8 } from "../types/bit_vec/module.f.js";
9
9
  import { identity } from "../types/function/module.f.js";
10
10
  import { encode as b128encode, decode as b128decode } from "../base128/module.f.js";
11
- const pop = msb.popFront;
11
+ const { popFront: pop, listToVec } = msb;
12
12
  const pop8 = pop(8n);
13
- const concat = listToVec(msb);
14
13
  const classPcMask = 224n;
15
14
  const tagNumberMask = 31n;
16
15
  const parsedTagEncode = ([classPc, number]) => {
17
16
  const [firstByteNumber, rest] = number < tagNumberMask
18
17
  ? [number, empty]
19
18
  : [tagNumberMask, b128encode(number)];
20
- return concat([vec8(classPc | firstByteNumber), rest]);
19
+ return listToVec([vec8(classPc | firstByteNumber), rest]);
21
20
  };
22
21
  const parsedTagDecode = (v) => {
23
22
  const [firstByte, rest] = pop8(v);
@@ -86,7 +85,7 @@ const lenEncode = (uint) => {
86
85
  return vec8(uint);
87
86
  }
88
87
  const { byteLen, v } = round8({ length: bitLength(uint), uint });
89
- return concat([vec8(0x80n | byteLen), v]);
88
+ return listToVec([vec8(0x80n | byteLen), v]);
90
89
  };
91
90
  /**
92
91
  * Decodes the length field of an ASN.1 TLV and returns the length in bits and the remaining input.
@@ -104,7 +103,7 @@ const lenDecode = (v) => {
104
103
  export const encodeRaw = ([tag, value]) => {
105
104
  const tagVec = tagEncode(tag);
106
105
  const { byteLen, v } = round8(unpack(value));
107
- return concat([tagVec, lenEncode(byteLen), v]);
106
+ return listToVec([tagVec, lenEncode(byteLen), v]);
108
107
  };
109
108
  /** Decodes a raw ASN.1 TLV tuple and returns the remaining input. */
110
109
  export const decodeRaw = (v) => {
@@ -139,7 +138,7 @@ export const decodeOctetString = (v) => v;
139
138
  export const encodeObjectIdentifier = (oid) => {
140
139
  const [first, second, ...rest] = oid;
141
140
  const firstByte = first * 40n + second;
142
- return concat([vec8(firstByte), ...rest.map(b128encode)]);
141
+ return listToVec([vec8(firstByte), ...rest.map(b128encode)]);
143
142
  };
144
143
  /** Decodes an OBJECT IDENTIFIER value. */
145
144
  export const decodeObjectIdentifier = (v) => {
@@ -155,7 +154,7 @@ export const decodeObjectIdentifier = (v) => {
155
154
  }
156
155
  return result;
157
156
  };
158
- const genericEncodeSequence = (map) => (...records) => concat(map(records.map(encode)));
157
+ const genericEncodeSequence = (map) => (...records) => listToVec(map(records.map(encode)));
159
158
  /** Encodes a SEQUENCE payload from ordered records. */
160
159
  export const encodeSequence = genericEncodeSequence(identity);
161
160
  /** Decodes a SEQUENCE payload into records. */
@@ -1,8 +1,7 @@
1
- import { empty, length, listToVec, msb, uint, unpack, vec, vec8 } from "../types/bit_vec/module.f.js";
1
+ import { empty, length, msb, uint, unpack, vec, vec8 } from "../types/bit_vec/module.f.js";
2
2
  import { asBase } from "../types/nominal/module.f.js";
3
3
  import { decodeRaw, decodeInteger, encodeRaw, encodeInteger, integer, encode, decode, constructedSequence, octetString, boolean, constructedSet } from "./module.f.js";
4
- const { concat, popFront: pop } = msb;
5
- const cat = listToVec(msb);
4
+ const { concat, popFront: pop, listToVec } = msb;
6
5
  const pop8 = pop(8n);
7
6
  const check = (tag, v, rest) => {
8
7
  const s = encodeRaw([tag, v]);
@@ -244,19 +243,19 @@ export default {
244
243
  },
245
244
  encodeDecode: {
246
245
  integer: () => {
247
- ch([integer, 0n], cat([vec8(BigInt(integer)), vec8(1n), vec8(0n)]));
248
- ch([integer, 1n], cat([vec8(BigInt(integer)), vec8(1n), vec8(1n)]));
246
+ ch([integer, 0n], listToVec([vec8(BigInt(integer)), vec8(1n), vec8(0n)]));
247
+ ch([integer, 1n], listToVec([vec8(BigInt(integer)), vec8(1n), vec8(1n)]));
249
248
  },
250
249
  sequence: () => {
251
- ch([constructedSequence, []], cat([vec8(BigInt(constructedSequence)), vec8(0n)]));
252
- ch([constructedSequence, [[integer, 0n]]], cat([vec8(BigInt(constructedSequence)), vec8(3n), encode([integer, 0n])]));
253
- ch([constructedSequence, [[integer, 1n], [integer, 2n]]], cat([
250
+ ch([constructedSequence, []], listToVec([vec8(BigInt(constructedSequence)), vec8(0n)]));
251
+ ch([constructedSequence, [[integer, 0n]]], listToVec([vec8(BigInt(constructedSequence)), vec8(3n), encode([integer, 0n])]));
252
+ ch([constructedSequence, [[integer, 1n], [integer, 2n]]], listToVec([
254
253
  vec8(BigInt(constructedSequence)),
255
254
  vec8(6n),
256
255
  encode([integer, 1n]),
257
256
  encode([integer, 2n])
258
257
  ]));
259
- ch([constructedSequence, [[octetString, vec8(0x23n)], [boolean, true], [boolean, false]]], cat([
258
+ ch([constructedSequence, [[octetString, vec8(0x23n)], [boolean, true], [boolean, false]]], listToVec([
260
259
  vec8(BigInt(constructedSequence)),
261
260
  vec8(9n),
262
261
  encode([octetString, vec8(0x23n)]),
@@ -265,7 +264,7 @@ export default {
265
264
  ]));
266
265
  },
267
266
  set: () => {
268
- ch([constructedSet, [[integer, 2n], [integer, 1n]]], cat([
267
+ ch([constructedSet, [[integer, 2n], [integer, 1n]]], listToVec([
269
268
  vec8(BigInt(constructedSet)),
270
269
  vec8(6n),
271
270
  encode([integer, 1n]),
@@ -276,7 +275,7 @@ export default {
276
275
  raw: [
277
276
  () => {
278
277
  const e = encodeRaw([0x00n, vec8(0x23n)]);
279
- if (e !== cat([vec8(0x00n), vec8(1n), vec8(0x23n)])) {
278
+ if (e !== listToVec([vec8(0x00n), vec8(1n), vec8(0x23n)])) {
280
279
  throw `encode: ${length(e)}: ${uint(e).toString(2)}`;
281
280
  }
282
281
  const [[tag, value], rest] = decodeRaw(e);
@@ -292,7 +291,7 @@ export default {
292
291
  },
293
292
  () => {
294
293
  const e = encodeRaw([0x1f20n, vec(16n)(0x1234n)]);
295
- if (e !== cat([vec8(0x1fn), vec8(0x20n), vec8(2n), vec(16n)(0x1234n)])) {
294
+ if (e !== listToVec([vec8(0x1fn), vec8(0x20n), vec8(2n), vec(16n)(0x1234n)])) {
296
295
  const l = length(e);
297
296
  const u = uint(e);
298
297
  throw `encode: ${l}: ${u.toString(16)}`;
@@ -20,13 +20,13 @@ export declare const images: {
20
20
  };
21
21
  };
22
22
  export declare const bun = "1.3.14";
23
- export declare const deno = "2.8.0";
23
+ export declare const deno = "2.8.1";
24
24
  export declare const playwright = "1.60.0";
25
25
  export declare const rust = "1.95.0";
26
26
  export declare const node: {
27
27
  readonly default: "26.2.0";
28
- readonly others: readonly ["22.22.3", "24.16.0"];
28
+ readonly others: readonly ["24.16.0"];
29
29
  };
30
- export declare const wasmtime = "44.0.1";
30
+ export declare const wasmtime = "45.0.0";
31
31
  export declare const wasmer = "7.1.0";
32
- export declare const tsgo = "7.0.0-dev.20260526.1";
32
+ export declare const tsgo = "7.0.0-dev.20260527.1";
@@ -23,7 +23,7 @@ export const images = {
23
23
  // https://bun.sh/
24
24
  export const bun = '1.3.14';
25
25
  // https://deno.com/
26
- export const deno = '2.8.0';
26
+ export const deno = '2.8.1';
27
27
  // https://www.npmjs.com/package/playwright
28
28
  export const playwright = '1.60.0';
29
29
  // https://rust-lang.org/
@@ -31,11 +31,11 @@ export const rust = '1.95.0';
31
31
  // https://nodejs.org/en/download
32
32
  export const node = {
33
33
  default: '26.2.0',
34
- others: ['22.22.3', '24.16.0'],
34
+ others: ['24.16.0'],
35
35
  };
36
36
  // https://github.com/bytecodealliance/wasmtime/releases
37
- export const wasmtime = '44.0.1';
37
+ export const wasmtime = '45.0.0';
38
38
  // https://github.com/wasmerio/wasmer/releases
39
39
  export const wasmer = '7.1.0';
40
40
  // https://www.npmjs.com/package/@typescript/native-preview?activeTab=versions
41
- export const tsgo = '7.0.0-dev.20260526.1';
41
+ export const tsgo = '7.0.0-dev.20260527.1';
@@ -1,5 +1,5 @@
1
1
  import { bitLength, divUp, roundUp } from "../../types/bigint/module.f.js";
2
- import { empty, length, listToVec, msb, repeat, unpack, vec, vec8 } from "../../types/bit_vec/module.f.js";
2
+ import { empty, length, msb, repeat, unpack, vec, vec8 } from "../../types/bit_vec/module.f.js";
3
3
  import { hmac } from "../hmac/module.f.js";
4
4
  import { computeSync } from "../sha2/module.f.js";
5
5
  // qlen to rlen
@@ -34,8 +34,8 @@ export const all = (q) => {
34
34
  export const fromCurve = (c) => all(c.nf.p);
35
35
  const x01 = vec8(0x01n);
36
36
  const x00 = vec8(0x00n);
37
- const ltov = listToVec(msb);
38
- export const concat = (...x) => ltov(x);
37
+ const { listToVec } = msb;
38
+ export const concat = (...x) => listToVec(x);
39
39
  /**
40
40
  * Computes deterministic ECDSA nonce `k` as described by RFC6979.
41
41
  */
@@ -1,2 +1 @@
1
- export declare const run3: () => Promise<void>;
2
- export declare const run: () => Promise<void>;
1
+ export declare const run: () => Promise<number>;
@@ -1,21 +1,22 @@
1
- import { type All, type NodeProgram, type NodeProgramOptions, type Program, type Sandbox, type SandboxResult, type Write } from '../../types/effects/node/module.f.ts';
1
+ import { type All, type NodeProgram, type NodeProgramOptions, type Program, type Sandbox, type SandboxResult, type Test, type TestContext, type Write } from '../../types/effects/node/module.f.ts';
2
2
  import { type Effect, type Operation } from '../../types/effects/module.f.ts';
3
3
  import { type LoadModuleOperations, type ModuleMap } from '../module.f.ts';
4
4
  export declare const isTest: (s: string) => boolean;
5
- export type Test = () => unknown;
5
+ export type TestFn = () => unknown;
6
6
  export type TestEntry = {
7
- readonly fn: Test;
7
+ readonly fn: TestFn;
8
8
  readonly throws: boolean;
9
9
  };
10
10
  export type TestSet = TestEntry | readonly (readonly [string, unknown])[];
11
11
  export declare const parseTestSet: (throws: boolean, x: unknown) => TestSet;
12
+ type TestAndPath = readonly [Path, TestEntry];
12
13
  /**
13
14
  * Recursively collects all leaf tests reachable from `v` as `[path, entry]`
14
15
  * pairs, without running anything. Return-value sub-trees are not walked
15
16
  * (that requires execution); only the static object/array/function structure
16
17
  * is traversed.
17
18
  */
18
- export declare const collectTests: (path: Path, throws: boolean, v: unknown) => readonly (readonly [Path, TestEntry])[];
19
+ export declare const collectTests: (path: Path, throws: boolean, v: unknown) => readonly TestAndPath[];
19
20
  /**
20
21
  * Receives semantic test-run events. Each method is the runner's notification
21
22
  * of an event; the reporter decides how to render it (terminal, GitHub
@@ -29,8 +30,10 @@ export type Reporter<O extends Operation> = {
29
30
  readonly summary: (pass: number, fail: number, time: number) => Effect<O, void>;
30
31
  readonly test: (file: string, path: Path, set: TestEntry) => Effect<O, SandboxResult<unknown>>;
31
32
  };
33
+ export declare const registerModule: (ctx: TestContext, k: string, v: unknown) => Effect<Test | All, void>;
32
34
  export declare const runModuleMap: <O extends Operation>(reporter: Reporter<O>) => (moduleMap: ModuleMap) => Effect<O | All, number>;
33
- export declare const test: <O extends Operation>(reporter: Reporter<O>) => Program<O | All | LoadModuleOperations>;
35
+ export declare const testAll: <O extends Operation>(reporter: Reporter<O>) => Program<O | All | LoadModuleOperations>;
36
+ export declare const registerModuleMap: (ctx: TestContext, moduleMap: ModuleMap) => Effect<Test | All, void>;
34
37
  export type Path = readonly (string | null)[];
35
38
  export declare const isInteger: (s: string) => boolean;
36
39
  export declare const isIdentifier: (s: string) => boolean;
@@ -70,3 +73,5 @@ export declare const defaultTest: (file: string, path: Path, { fn, throws }: Tes
70
73
  */
71
74
  export declare const defaultReporter: (options: NodeProgramOptions) => Reporter<Write | Sandbox>;
72
75
  export declare const main: NodeProgram;
76
+ export declare const register: NodeProgram;
77
+ export {};
@@ -4,8 +4,8 @@
4
4
  * @module
5
5
  */
6
6
  import { reset, fgGreen, fgRed, bold, csiWrite } from "../../text/sgr/module.f.js";
7
- import { all, sandbox } from "../../types/effects/node/module.f.js";
8
- import { pure } from "../../types/effects/module.f.js";
7
+ import { all, sandbox, test } from "../../types/effects/node/module.f.js";
8
+ import { pure, do_ } from "../../types/effects/module.f.js";
9
9
  import { loadModuleMap } from "../module.f.js";
10
10
  import { invert } from "../../types/result/module.f.js";
11
11
  export const isTest = (s) => s.endsWith('test.f.js') || s.endsWith('test.f.ts');
@@ -51,28 +51,47 @@ export const collectTests = (path, throws, v) => {
51
51
  }
52
52
  return [[path, set]];
53
53
  };
54
+ export const registerModule = (ctx, k, v) => {
55
+ const registerOne = (ctx, [path, { fn, throws }]) => test(ctx, fmtImport(k, path), throws, (t) => {
56
+ if (throws) {
57
+ fn();
58
+ return pure(undefined);
59
+ }
60
+ const r = fn();
61
+ const sub = collectTests([...path, null], false, r);
62
+ if (sub.length === 0) {
63
+ return pure(undefined);
64
+ }
65
+ return all(...sub.map(e => registerOne(t, e))).step(() => pure(undefined));
66
+ });
67
+ const tests = collectTests([], false, v);
68
+ if (tests.length === 0) {
69
+ return pure(undefined);
70
+ }
71
+ return all(...tests.map(e => registerOne(ctx, e))).step(() => pure(undefined));
72
+ };
54
73
  const mergeState = (a, b) => ({ time: a.time + b.time, pass: a.pass + b.pass, fail: a.fail + b.fail });
55
74
  const zero = { time: 0, pass: 0, fail: 0 };
56
75
  const runModule = ({ result, test }) => (k, v) => (ts) => {
57
- const walk = (path, throws, v) => {
58
- const effects = collectTests(path, throws, v)
59
- .map(([testPath, set]) => test(k, testPath, set)
60
- .step(sr => {
61
- const { result: [s, r], duration } = sr;
62
- return result(k, testPath, sr)
63
- .step(() => {
64
- if (s === 'ok') {
65
- if (set.throws) {
66
- return pure(addPass(duration)(zero));
67
- }
68
- // Walk return-value sub-tree; null marks the call boundary so
69
- // paths render as e.g. `outer().inner`. throws resets to false.
70
- return walk([...testPath, null], false, r)
71
- .step(sub => pure(mergeState(addPass(duration)(zero), sub)));
76
+ const one = ([testPath, set]) => test(k, testPath, set)
77
+ .step(sr => {
78
+ const { result: [s, r], duration } = sr;
79
+ return result(k, testPath, sr)
80
+ .step(() => {
81
+ if (s === 'ok') {
82
+ if (set.throws) {
83
+ return pure(addPass(duration)(zero));
72
84
  }
73
- return pure(addFail(duration)(zero));
74
- });
75
- }));
85
+ // Walk return-value sub-tree; null marks the call boundary so
86
+ // paths render as e.g. `outer().inner`. throws resets to false.
87
+ return walk([...testPath, null], false, r)
88
+ .step(sub => pure(mergeState(addPass(duration)(zero), sub)));
89
+ }
90
+ return pure(addFail(duration)(zero));
91
+ });
92
+ });
93
+ const walk = (path, throws, v) => {
94
+ const effects = collectTests(path, throws, v).map(one);
76
95
  return all(...effects)
77
96
  .step(states => pure(states.reduce(mergeState, zero)));
78
97
  };
@@ -87,7 +106,14 @@ export const runModuleMap = (reporter) => (moduleMap) => {
87
106
  .step(ts => summary(ts.pass, ts.fail, ts.time)
88
107
  .step(() => pure(ts.fail !== 0 ? 1 : 0)));
89
108
  };
90
- export const test = (reporter) => options => loadModuleMap(options.env).step(runModuleMap(reporter));
109
+ export const testAll = (reporter) => options => loadModuleMap(options.env).step(runModuleMap(reporter));
110
+ export const registerModuleMap = (ctx, moduleMap) => {
111
+ const modules = entries(moduleMap).filter(([k]) => isTest(k));
112
+ if (modules.length === 0) {
113
+ return pure(undefined);
114
+ }
115
+ return all(...modules.map(([k, v]) => registerModule(ctx, k, v))).step(() => pure(undefined));
116
+ };
91
117
  const isAlpha = (c) => (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c === '_' || c === '$';
92
118
  const isDigit = (c) => c >= '0' && c <= '9';
93
119
  export const isInteger = (s) => s.length > 0 && [...s].every(isDigit) && (s === '0' || s[0] !== '0');
@@ -169,4 +195,9 @@ export const defaultReporter = (options) => {
169
195
  test: defaultTest,
170
196
  };
171
197
  };
172
- export const main = options => test(defaultReporter(options))(options);
198
+ export const main = options => testAll(defaultReporter(options))(options);
199
+ export const register = o => loadModuleMap(o.env)
200
+ .step(m => registerModuleMap(o.engine === 'bun' ? o.bunTestContext :
201
+ o.engine === 'playwright' ? o.playwrightTestContext :
202
+ o.testContext, m))
203
+ .step(() => pure(0));
@@ -1,103 +1,4 @@
1
- import { io, tryCatch } from "../../io/module.js";
2
- import { loadModuleMap } from "../module.f.js";
3
- import { fmtImport, isTest, parseTestSet, runModuleMap } from "./module.f.js";
4
- import * as nodeTest from 'node:test';
5
- import { asyncImport } from "../../io/module.js";
6
- import { fromIo } from "../../io/module.f.js";
7
- import { pure } from "../../types/effects/module.f.js";
8
- import { asyncRun } from "../../types/effects/module.js";
9
- import {} from "../../types/effects/node/module.f.js";
10
- const isBun = typeof Bun !== 'undefined';
11
- const isPlaywright = typeof process !== 'undefined' && process?.env?.PLAYWRIGHT_TEST !== undefined;
12
- const createFramework = (fw) => (prefix, f) => fw.test(prefix, t => f((name, v) => t.test(name, v)));
13
- // Bun doesn't support nested tests yet.
14
- const createBunFramework = (fw) => (prefix, f) => f((name, v) => fw.test(`${prefix}: ${name}`, v));
15
- const createPlaywrightFramework = async () => {
16
- const pwTest = (await asyncImport('@playwright/test')).test;
17
- return (prefix, f) => f((name, v) => pwTest(`${prefix}: ${name}`, v));
18
- };
19
- const framework = isPlaywright ? await createPlaywrightFramework() :
20
- isBun ? createBunFramework(nodeTest) :
21
- createFramework(nodeTest);
22
- const scanModule = (x) => async (subTestRunner) => {
23
- let subTests = [x];
24
- while (true) {
25
- const [first, ...rest] = subTests;
26
- if (first === undefined) {
27
- break;
28
- }
29
- subTests = rest;
30
- //
31
- const [name, value, throws] = first;
32
- const set = parseTestSet(throws, value);
33
- if (set instanceof Array) {
34
- for (const [j, y] of set) {
35
- const pr = `${name}/${j}`;
36
- subTests = [...subTests, [pr, y, throws || j === 'throw']];
37
- }
38
- }
39
- else {
40
- await subTestRunner(name, () => {
41
- if (set.throws) {
42
- let threw = false;
43
- try {
44
- set.fn();
45
- }
46
- catch (_) {
47
- threw = true;
48
- }
49
- if (!threw) {
50
- throw new Error(`${name}() expected to throw`);
51
- }
52
- }
53
- else {
54
- const r = set.fn();
55
- // The result of a function is walked as a fresh sub-tree;
56
- // the parent's `throws` flag does not propagate into it.
57
- subTests = [...subTests, [`${name}()`, r, false]];
58
- }
59
- });
60
- }
61
- }
62
- };
63
- const noOp = () => pure(undefined);
64
- const reporter = {
65
- result: noOp,
66
- summary: noOp,
67
- test: (file, path, { throws, fn }) => {
68
- nodeTest.test(fmtImport(file, path), async (_t) => {
69
- const [s, r] = tryCatch(fn);
70
- if (throws === (s === 'ok')) {
71
- throw r;
72
- }
73
- if (!throws) {
74
- // TODO: add subtests
75
- }
76
- });
77
- return pure({
78
- result: ['ok', undefined],
79
- duration: 0,
80
- });
81
- }
82
- };
83
- const map = {
84
- // TODO: we use the same algorithm twice. Refactor by creating a `createAll(map)`
85
- // helper that takes a `map` and returns an `all` function that runs effects
86
- // according to the map. There could be a problem with circular dependencies,
87
- // but we can use a lazy function `() => ToAsyncOperationMap<All>` instead od `map`.
88
- all: async (...effects) => Promise.all(effects.map(asyncRun(map))),
89
- };
90
- export const run3 = async () => {
91
- const fio = fromIo(io);
92
- const moduleMap = await fio(loadModuleMap(io.process.env));
93
- const runner = runModuleMap(reporter)(moduleMap);
94
- await asyncRun(map)(runner);
95
- };
96
- export const run = async () => {
97
- const moduleMap = await fromIo(io)(loadModuleMap(io.process.env));
98
- for (const [i, v] of Object.entries(moduleMap)) {
99
- if (isTest(i)) {
100
- framework(i, scanModule(['', v, false]));
101
- }
102
- }
103
- };
1
+ import { io } from "../../io/module.js";
2
+ import { register } from "./module.f.js";
3
+ import { runProgram } from "../../io/module.f.js";
4
+ export const run = () => runProgram(io)([])(register);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { run } from "../module.js";
2
+ // we need `await` for Playwright.
3
+ await run();
@@ -0,0 +1 @@
1
+ export declare const failing: () => never;
@@ -0,0 +1 @@
1
+ export const failing = () => { throw 'intentional failure'; };
@@ -0,0 +1 @@
1
+ export declare const outer: () => unknown;
@@ -0,0 +1,2 @@
1
+ const inner = () => { };
2
+ export const outer = () => ({ inner });
@@ -0,0 +1,6 @@
1
+ declare const _default: {
2
+ throw: {
3
+ a: () => never;
4
+ };
5
+ };
6
+ export default _default;
@@ -0,0 +1,3 @@
1
+ export default {
2
+ throw: { a: () => { throw 'expected'; } }
3
+ };
@@ -1,8 +1,8 @@
1
1
  import { pure } from "../../types/effects/module.f.js";
2
2
  import { emptyState } from "../../types/effects/node/virtual/module.f.js";
3
3
  import { virtual } from "../../types/effects/node/virtual/module.f.js";
4
- import { assert, assertEq } from "../module.f.js";
5
- import { test, defaultReporter, fmtPath, fmtTerm, fmtImport, ghEscape, isInteger, isIdentifier, defaultTest, } from "./module.f.js";
4
+ import { assert, assertEq, todo } from "../module.f.js";
5
+ import { testAll, defaultReporter, fmtPath, fmtTerm, fmtImport, ghEscape, isInteger, isIdentifier, defaultTest, } from "./module.f.js";
6
6
  const makeReporter = () => {
7
7
  const events = [];
8
8
  const reporter = {
@@ -12,10 +12,15 @@ const makeReporter = () => {
12
12
  };
13
13
  return [reporter, () => events];
14
14
  };
15
+ const noopTestContext = { test: todo };
15
16
  const options = (initCwd, github = false) => ({
16
17
  args: [],
17
18
  env: { INIT_CWD: initCwd, ...(github ? { GITHUB_ACTION: 'true' } : {}) },
18
19
  std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
20
+ testContext: noopTestContext,
21
+ bunTestContext: noopTestContext,
22
+ playwrightTestContext: noopTestContext,
23
+ engine: 'node',
19
24
  });
20
25
  const ok0 = () => ({ result: ['ok', undefined], duration: 0 });
21
26
  const fail0 = () => ({ result: ['error', 'oops'], duration: 0 });
@@ -23,7 +28,7 @@ const ok1 = () => ({ result: ['ok', undefined], duration: 1 });
23
28
  const run = (dir, initCwd = '.') => {
24
29
  const [reporter, getEvents] = makeReporter();
25
30
  const state = { ...emptyState, root: dir };
26
- const [, exitCode] = virtual(state)(test(reporter)(options(initCwd)));
31
+ const [, exitCode] = virtual(state)(testAll(reporter)(options(initCwd)));
27
32
  return [getEvents(), exitCode];
28
33
  };
29
34
  // Runs the real `defaultReporter` and returns its captured stdout/stderr so the
@@ -31,7 +36,7 @@ const run = (dir, initCwd = '.') => {
31
36
  const runMain = (dir, github = false) => {
32
37
  const state = { ...emptyState, root: dir };
33
38
  const opts = options('.', github);
34
- const [finalState, exitCode] = virtual(state)(test(defaultReporter(opts))(opts));
39
+ const [finalState, exitCode] = virtual(state)(testAll(defaultReporter(opts))(opts));
35
40
  return [finalState.stdout, finalState.stderr, exitCode];
36
41
  };
37
42
  // flat object: two passing tests
@@ -1,5 +1,5 @@
1
1
  import { type Effect } from '../types/effects/module.f.ts';
2
- import type { Headers, Module, NodeOp, Env, SandboxResult, NodeProgram, WriteConsoles } from '../types/effects/node/module.f.ts';
2
+ import { type Headers, type Module, type NodeOp, type Env, type SandboxResult, type NodeProgram, type WriteConsoles, type TestContext, type Engine } from '../types/effects/node/module.f.ts';
3
3
  import type { Vec } from '../types/bit_vec/module.f.ts';
4
4
  import { type Result } from '../types/result/module.f.ts';
5
5
  /**
@@ -116,6 +116,10 @@ export type Io = {
116
116
  readonly now: () => number;
117
117
  readonly sandbox: Sandbox;
118
118
  readonly write: (stream: WriteConsoles, data: Vec) => Promise<void>;
119
+ readonly testContext: TestContext;
120
+ readonly bunTestContext: TestContext;
121
+ readonly playwrightTestContext: TestContext;
122
+ readonly engine: Engine;
119
123
  };
120
124
  export type App = (io: Io) => Promise<number>;
121
125
  export type Run = (f: App) => Promise<never>;
package/fs/io/module.f.js CHANGED
@@ -10,6 +10,7 @@
10
10
  import { normalize } from "../path/module.f.js";
11
11
  import {} from "../types/effects/module.f.js";
12
12
  import { asyncRun } from "../types/effects/module.js";
13
+ import {} from "../types/effects/node/module.f.js";
13
14
  import { asBase, asNominal } from "../types/nominal/module.f.js";
14
15
  import { error, ok } from "../types/result/module.f.js";
15
16
  import { fromVec, listToVec, toVec } from "../types/uint8array/module.f.js";
@@ -96,18 +97,16 @@ export const fromIo = ({ fs: { promises: { mkdir, readFile, readdir, writeFile,
96
97
  now: async () => ioNow(),
97
98
  sandbox: async (f) => ioSandbox(f),
98
99
  write: ioWrite,
100
+ test: async (ctx, name, expectFailure, test) => ctx.test(name, { expectFailure }, async (t) => result(test(t))),
99
101
  });
100
102
  return result;
101
103
  };
102
104
  export const runProgram = (io) => {
103
- const { process: { env, stdout, stderr } } = io;
105
+ const { process: { env, stdout, stderr }, testContext, bunTestContext, playwrightTestContext, engine } = io;
106
+ const std = { stdout, stderr };
104
107
  const f = fromIo(io);
105
- return (args) => (program) => f(program({
106
- args,
107
- env,
108
- std: {
109
- stdout: { isTTY: stdout.isTTY },
110
- stderr: { isTTY: stderr.isTTY },
111
- },
112
- }));
108
+ return args => {
109
+ const options = { args, env, std, testContext, bunTestContext, playwrightTestContext, engine };
110
+ return program => f(program(options));
111
+ };
113
112
  };
package/fs/io/module.js CHANGED
@@ -15,6 +15,32 @@ import { once } from 'node:events';
15
15
  import { fromIo, runProgram } from "./module.f.js";
16
16
  import { error, ok } from "../types/result/module.f.js";
17
17
  import { fromVec } from "../types/uint8array/module.f.js";
18
+ import * as testContext from 'node:test';
19
+ const isPlaywright = 'PLAYWRIGHT_TEST' in (process?.env ?? {});
20
+ const pwTest = isPlaywright
21
+ ? (await import('@playwright/test')).test
22
+ : undefined;
23
+ const inlineTest = async (name, { expectFailure }, fn) => {
24
+ if (expectFailure) {
25
+ try {
26
+ await fn(inlineContext);
27
+ }
28
+ catch {
29
+ return;
30
+ }
31
+ throw new Error(`expected to throw: ${name}`);
32
+ }
33
+ else {
34
+ await fn(inlineContext);
35
+ }
36
+ };
37
+ const inlineContext = { test: inlineTest };
38
+ const bunTestContext = {
39
+ test: (name, opts, fn) => testContext.test(name, () => inlineTest(name, opts, fn))
40
+ };
41
+ const playwrightTestContext = {
42
+ test: (name, opts, fn) => pwTest(name, () => inlineTest(name, opts, fn))
43
+ };
18
44
  const prefix = 'file:///';
19
45
  const { now } = Date;
20
46
  /** Maps `WriteConsoles` names to the corresponding Node.js writable streams. */
@@ -89,6 +115,10 @@ export const io = {
89
115
  return { result, duration: after - before };
90
116
  },
91
117
  write: (stream, data) => writeAll(streams[stream], fromVec(data)),
118
+ testContext,
119
+ bunTestContext,
120
+ playwrightTestContext,
121
+ engine: isPlaywright ? 'playwright' : 'Bun' in globalThis ? 'bun' : 'node',
92
122
  };
93
123
  const effectRun = runProgram(io)(io.process.argv.slice(2));
94
124
  export default effectRun;
@@ -6,7 +6,7 @@
6
6
  * @module
7
7
  */
8
8
  import { toArray } from "../../types/list/module.f.js";
9
- import { length, listToVec, msb, uint, uintChunkList, unpack, vec } from "../../types/bit_vec/module.f.js";
9
+ import { length, msb, uint, uintChunkList, unpack, vec } from "../../types/bit_vec/module.f.js";
10
10
  import { assertEq } from "../../dev/module.f.js";
11
11
  import { utf8 } from "../../text/module.f.js";
12
12
  import { secp256r1 } from "../../crypto/secp/module.f.js";
@@ -77,9 +77,8 @@ export const isHash = (v) => asBase(v) >> hashPrefixOffset === 1n;
77
77
  export const hashId = (hash) => asNominal(hashPrefix | hash);
78
78
  const hash2 = base32.compress(iv);
79
79
  const vecX20 = vec(0x20n);
80
- const ltv = listToVec(msb);
81
- const hashMerge = (a, b) => hashId(uint(ltv(hash2((asBase(a) << 0x100n) | asBase(b)).map(vecX20))));
82
- const { concat } = msb;
80
+ const { concat, listToVec } = msb;
81
+ const hashMerge = (a, b) => hashId(uint(listToVec(hash2((asBase(a) << 0x100n) | asBase(b)).map(vecX20))));
83
82
  export const compress = (a, b) => {
84
83
  if (isHash(a) || isHash(b)) {
85
84
  return hashMerge(a, b);
@@ -5,7 +5,7 @@
5
5
  * @module
6
6
  */
7
7
  import { log2 } from "../../../types/bigint/module.f.js";
8
- import { listToVec, msb, vec } from "../../../types/bit_vec/module.f.js";
8
+ import { msb, vec } from "../../../types/bit_vec/module.f.js";
9
9
  import { strictEqual } from "../../../types/function/operator/module.f.js";
10
10
  import { equal, map } from "../../../types/list/module.f.js";
11
11
  import { join } from "../../../types/string/module.f.js";
@@ -73,12 +73,12 @@ export const pipelineStep = (bit, [l1s, l2s, l3s]) => {
73
73
  const [l3Out, newL3s] = l3.encode(l2Out, l3s);
74
74
  return [l3Out, [newL1s, newL2s, newL3s]];
75
75
  };
76
- const concat = listToVec(msb);
77
76
  const vec1 = vec(1n);
77
+ const { listToVec } = msb;
78
78
  const literalToVec = (prior, e) => {
79
79
  const m = map(prior);
80
80
  const { decode } = level(e);
81
- return literal => concat(m(decode(literal)));
81
+ return literal => listToVec(m(decode(literal)));
82
82
  };
83
83
  /** Decodes a level-1 symbol to its canonical MSB bit vector. */
84
84
  export const literal1ToVec = literalToVec(vec1, 0n);
@@ -1,8 +1,23 @@
1
- import { fgRed } from "./module.f.js";
1
+ import { fgRed, createConsoleText, backspace } from "./module.f.js";
2
2
  export default [
3
3
  () => {
4
4
  if (fgRed !== '\x1b[31m') {
5
5
  throw new Error('Test failed: sgr(0)');
6
6
  }
7
+ },
8
+ () => {
9
+ const output = [];
10
+ const stdout = { write: (s) => { output.push(s); } };
11
+ const writer1 = createConsoleText(stdout);
12
+ const writer2 = writer1('hello');
13
+ if (output[0] !== 'hello') {
14
+ throw output[0];
15
+ }
16
+ // replacing 'hello' (len=5) with 'hi' (len=2): suffixLength=3
17
+ writer2('hi');
18
+ const expected = backspace.repeat(5) + 'hi' + ' '.repeat(3) + backspace.repeat(3);
19
+ if (output[1] !== expected) {
20
+ throw output[1];
21
+ }
7
22
  }
8
23
  ];
@@ -146,6 +146,15 @@ export type BitOrder = {
146
146
  * ```
147
147
  */
148
148
  readonly concat: Reduce;
149
+ /**
150
+ * Folds a list of vectors into a single vector in this bit order.
151
+ *
152
+ * Unlike `concat`, which joins exactly two vectors, this joins a whole list.
153
+ *
154
+ * @returns The concatenation of every vector in the list, or `empty` when the
155
+ * list is empty.
156
+ */
157
+ readonly listToVec: (list: List<Vec>) => Vec;
149
158
  /**
150
159
  * Computes the bitwise exclusive OR of two vectors after normalizing their lengths.
151
160
  *
@@ -219,10 +228,6 @@ export declare const chunkList: (bo: BitOrder) => (n: bigint) => (v: Vec) => Thu
219
228
  * @returns A thunk that produces a list of unsigned 8-bit integers.
220
229
  */
221
230
  export declare const u8List: (bo: BitOrder) => (v: Vec) => Thunk<number>;
222
- /**
223
- * Concatenates a list of vectors using the provided bit order.
224
- */
225
- export declare const listToVec: ({ concat }: BitOrder) => (list: List<Vec>) => Vec;
226
231
  /**
227
232
  * Repeats a vector to create a padded block of the desired length.
228
233
  */
@@ -127,14 +127,16 @@ const bo = ({ front, removeFront, norm, uintCmp, unpackSplit, unpackConcatUint }
127
127
  return [uint, pack(u)];
128
128
  };
129
129
  };
130
+ const concat = a => b => {
131
+ const au = unpack(a);
132
+ const bu = unpack(b);
133
+ return pack(unpackConcat(au)(bu));
134
+ };
130
135
  return {
131
136
  front,
132
137
  removeFront,
133
- concat: a => b => {
134
- const au = unpack(a);
135
- const bu = unpack(b);
136
- return pack(unpackConcat(au)(bu));
137
- },
138
+ concat,
139
+ listToVec: fold(flip(concat))(empty),
138
140
  xor: op(norm)(xor),
139
141
  unpackPopFront,
140
142
  popFront,
@@ -301,10 +303,6 @@ const vecToU8 = ({ unpackSplit }) => {
301
303
  * @returns A thunk that produces a list of unsigned 8-bit integers.
302
304
  */
303
305
  export const u8List = (bo) => (v) => map(vecToU8(bo))(chunkList(bo)(8n)(v));
304
- /**
305
- * Concatenates a list of vectors using the provided bit order.
306
- */
307
- export const listToVec = ({ concat }) => fold(flip(concat))(empty);
308
306
  /**
309
307
  * Repeats a vector to create a padded block of the desired length.
310
308
  */
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import type { TNode, Tree } from '../types/module.f.ts';
6
+ import { type TNode, type Tree } from '../types/module.f.ts';
7
7
  import type { Compare } from '../../function/compare/module.f.ts';
8
8
  export declare const nodeRemove: <T>(c: Compare<T>) => (node: TNode<T>) => Tree<T>;
9
9
  export declare const remove: <T>(c: Compare<T>) => (tree: Tree<T>) => Tree<T>;
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Removal operations for persistent B-tree structures.
3
+ *
4
+ * @module
5
+ */
6
+ import { collapseRoot } from "../types/module.f.js";
1
7
  import { find } from "../find/module.f.js";
2
8
  import { fold, concat, next } from "../../list/module.f.js";
3
9
  import { map } from "../../nullable/module.f.js";
@@ -163,7 +169,6 @@ export const nodeRemove = (c) => (node) => {
163
169
  return first;
164
170
  }
165
171
  const { first: tf, tail: tt } = tailR;
166
- const result = reduce(initReduce(tf)(first))(tt);
167
- return result.length === 1 ? result[0] : result;
172
+ return collapseRoot(reduce(initReduce(tf)(first))(tt));
168
173
  };
169
174
  export const remove = c => map(nodeRemove(c));
@@ -3,6 +3,6 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import type { TNode, Tree } from '../types/module.f.ts';
6
+ import { type TNode, type Tree } from '../types/module.f.ts';
7
7
  import type { Compare } from '../../function/compare/module.f.ts';
8
8
  export declare const set: <T>(c: Compare<T>) => (f: (value: T | null) => T) => (tree: Tree<T>) => TNode<T>;
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Insertion and update operations for persistent B-tree structures.
3
+ *
4
+ * @module
5
+ */
6
+ import { collapseRoot } from "../types/module.f.js";
1
7
  import { find } from "../find/module.f.js";
2
8
  import { fold } from "../../list/module.f.js";
3
9
  const b57 = b => b.length === 5 ? [b] : [[b[0], b[1], b[2]], b[3], [b[4], b[5], b[6]]];
@@ -114,7 +120,6 @@ const nodeSet = (c) => (g) => (node) => {
114
120
  }
115
121
  }
116
122
  };
117
- const r = reduceBranch(f())(tail);
118
- return r.length === 1 ? r[0] : r;
123
+ return collapseRoot(reduceBranch(f())(tail));
119
124
  };
120
125
  export const set = c => f => tree => tree === null ? [f(null)] : nodeSet(c)(f)(tree);
@@ -12,3 +12,4 @@ export type TNode<T> = Leaf1<T> | Leaf2<T> | Branch3<T> | Branch5<T>;
12
12
  export type Tree<T> = TNode<T> | null;
13
13
  export type Branch1<T> = readonly [TNode<T>];
14
14
  export type Branch7<T> = readonly [...Branch5<T>, T, TNode<T>];
15
+ export declare const collapseRoot: <T>(b: Branch1<T> | Branch3<T> | Branch5<T>) => TNode<T>;
@@ -1 +1 @@
1
- export {};
1
+ export const collapseRoot = b => b.length === 1 ? b[0] : b;
@@ -116,7 +116,15 @@ export type Sandbox = readonly ['sandbox', <T>(f: () => T) => SandboxResult<T>];
116
116
  * @see {@link SandboxResult}
117
117
  */
118
118
  export declare const sandbox: Func<Sandbox>;
119
- export type NodeOp = All | Fetch | Fs | Http | Forever | Import | Now | Sandbox | Write;
119
+ export type TestFn = (name: string, options: {
120
+ readonly expectFailure: boolean;
121
+ }, fn: (t: TestContext) => Promise<void>) => Promise<void>;
122
+ export type TestContext = {
123
+ readonly test: TestFn;
124
+ };
125
+ export type Test = readonly ['test', (ctx: TestContext, name: string, expectFailure: boolean, test: (t: TestContext) => Effect<Test | All, void>) => void];
126
+ export declare const test: Func<Test>;
127
+ export type NodeOp = All | Fetch | Fs | Http | Forever | Import | Now | Sandbox | Write | Test;
120
128
  export type NodeEffect<T> = Effect<NodeOp, T>;
121
129
  export type NodeOperationMap = ToAsyncOperationMap<NodeOp>;
122
130
  /**
@@ -125,6 +133,7 @@ export type NodeOperationMap = ToAsyncOperationMap<NodeOp>;
125
133
  export type Env = {
126
134
  readonly [k: string]: string | undefined;
127
135
  };
136
+ export type Engine = 'node' | 'bun' | 'playwright';
128
137
  export type NodeProgramOptions = {
129
138
  readonly args: readonly string[];
130
139
  readonly env: Env;
@@ -133,6 +142,10 @@ export type NodeProgramOptions = {
133
142
  readonly isTTY: boolean;
134
143
  };
135
144
  };
145
+ readonly testContext: TestContext;
146
+ readonly bunTestContext: TestContext;
147
+ readonly playwrightTestContext: TestContext;
148
+ readonly engine: Engine;
136
149
  };
137
150
  export type Program<O extends Operation> = (options: NodeProgramOptions) => Effect<O, number>;
138
151
  export type NodeProgram = Program<NodeOp>;
@@ -61,3 +61,4 @@ export const now = do_('now');
61
61
  * @see {@link SandboxResult}
62
62
  */
63
63
  export const sandbox = do_('sandbox');
64
+ export const test = do_('test');
@@ -10,8 +10,9 @@ import type { Module, NodeOp } from '../module.f.ts';
10
10
  * each import for closures/state.
11
11
  */
12
12
  export type JsModule = () => Module;
13
+ export type Entity = Vec | Dir | JsModule;
13
14
  export type Dir = {
14
- readonly [name in string]?: Dir | Vec | JsModule;
15
+ readonly [name in string]?: Entity;
15
16
  };
16
17
  export type State = {
17
18
  stdout: string;
@@ -167,6 +167,7 @@ const map = {
167
167
  // exception in a fixture propagates loudly as a bug in the fixture.
168
168
  // See: issues/156-tf-virtual-tests.md
169
169
  sandbox: (state, f) => [state, f()],
170
+ test: todo,
170
171
  write: (state, stream, data) => {
171
172
  const s = utf8ToString(data);
172
173
  return [{ ...state, [stream]: `${state[stream]}${s}` }, undefined];
@@ -1,4 +1,4 @@
1
- import { map, toOption } from "./module.f.js";
1
+ import { map, match, toOption } from "./module.f.js";
2
2
  export default [
3
3
  () => {
4
4
  const optionSq = map((v) => v * v);
@@ -20,5 +20,14 @@ export default [
20
20
  if (opt2.length !== 0) {
21
21
  throw opt2;
22
22
  }
23
+ },
24
+ () => {
25
+ const double = match((v) => v * 2)(() => -1);
26
+ if (double(3) !== 6) {
27
+ throw double(3);
28
+ }
29
+ if (double(null) !== -1) {
30
+ throw double(null);
31
+ }
23
32
  }
24
33
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "**/*.js",
@@ -9,17 +9,17 @@
9
9
  "description": "FunctionalScript is a purely functional subset of JavaScript",
10
10
  "scripts": {
11
11
  "prepack": "tsc --NoEmit false",
12
- "test": "tsc && node --test --experimental-strip-types --experimental-test-coverage --test-coverage-include=**/module.f.ts",
12
+ "test": "tsc && node --test --experimental-test-coverage --test-coverage-include=**/module.f.ts",
13
13
  "index": "node ./fs/fjs/module.ts r ./fs/dev/index/module.f.ts",
14
14
  "fst": "node ./fs/fjs/module.ts t",
15
15
  "start": "node ./fs/fjs/module.ts",
16
16
  "ci-update": "node ./fs/fjs/module.ts r ./fs/ci/module.f.ts",
17
17
  "update": "npm install && npm run index && npm run ci-update",
18
- "index-html": "node --experimental-strip-types ./fs/fjs/module.ts r ./fs/website/module.f.ts",
18
+ "index-html": "node ./fs/fjs/module.ts r ./fs/website/module.f.ts",
19
19
  "website": "npm run prepack &&npm run index-html"
20
20
  },
21
21
  "engines": {
22
- "node": ">=22"
22
+ "node": ">=24"
23
23
  },
24
24
  "bin": {
25
25
  "fjs": "fs/fjs/module.js"