functionalscript 0.17.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 (82) 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/common/module.f.d.ts +4 -5
  4. package/fs/ci/common/module.f.js +4 -4
  5. package/fs/ci/config/module.f.d.ts +5 -5
  6. package/fs/ci/config/module.f.js +5 -5
  7. package/fs/ci/test.f.js +2 -4
  8. package/fs/crypto/sign/module.f.js +3 -3
  9. package/fs/dev/module.f.d.ts +3 -3
  10. package/fs/dev/module.f.js +12 -9
  11. package/fs/dev/tf/module.d.ts +1 -1
  12. package/fs/dev/tf/module.f.d.ts +76 -7
  13. package/fs/dev/tf/module.f.js +166 -87
  14. package/fs/dev/tf/module.js +3 -53
  15. package/fs/dev/tf/scenarios/all.d.ts +1 -0
  16. package/fs/dev/tf/scenarios/all.js +3 -0
  17. package/fs/dev/tf/scenarios/fail.fail.f.d.ts +1 -0
  18. package/fs/dev/tf/scenarios/fail.fail.f.js +1 -0
  19. package/fs/dev/tf/scenarios/return-value.pass.f.d.ts +1 -0
  20. package/fs/dev/tf/scenarios/return-value.pass.f.js +2 -0
  21. package/fs/dev/tf/scenarios/throw.pass.f.d.ts +6 -0
  22. package/fs/dev/tf/scenarios/throw.pass.f.js +3 -0
  23. package/fs/dev/tf/test.f.d.ts +21 -20
  24. package/fs/dev/tf/test.f.js +254 -31
  25. package/fs/djs/module.f.d.ts +2 -2
  26. package/fs/djs/module.f.js +8 -5
  27. package/fs/djs/test.f.js +5 -6
  28. package/fs/djs/tokenizer-new/test.f.js +126 -78
  29. package/fs/djs/transpiler/module.f.js +2 -2
  30. package/fs/djs/transpiler/test.f.js +11 -12
  31. package/fs/fjs/module.f.d.ts +2 -7
  32. package/fs/fjs/module.f.js +16 -22
  33. package/fs/fjs/module.js +2 -2
  34. package/fs/io/module.d.ts +3 -3
  35. package/fs/io/module.f.d.ts +9 -2
  36. package/fs/io/module.f.js +13 -3
  37. package/fs/io/module.js +68 -17
  38. package/fs/path/module.f.d.ts +6 -0
  39. package/fs/path/module.f.js +6 -0
  40. package/fs/path/test.f.d.ts +3 -5
  41. package/fs/path/test.f.js +67 -49
  42. package/fs/sul/id/module.f.js +3 -4
  43. package/fs/sul/level/literal/module.f.js +3 -3
  44. package/fs/text/sgr/module.f.d.ts +9 -1
  45. package/fs/text/sgr/module.f.js +16 -5
  46. package/fs/text/sgr/test.f.js +16 -1
  47. package/fs/types/bit_vec/module.f.d.ts +9 -4
  48. package/fs/types/bit_vec/module.f.js +7 -9
  49. package/fs/types/btree/remove/module.f.d.ts +1 -1
  50. package/fs/types/btree/remove/module.f.js +7 -2
  51. package/fs/types/btree/set/module.f.d.ts +1 -1
  52. package/fs/types/btree/set/module.f.js +7 -2
  53. package/fs/types/btree/types/module.f.d.ts +1 -0
  54. package/fs/types/btree/types/module.f.js +1 -1
  55. package/fs/types/effects/node/module.f.d.ts +30 -17
  56. package/fs/types/effects/node/module.f.js +21 -2
  57. package/fs/types/effects/node/test.f.js +8 -5
  58. package/fs/types/effects/node/virtual/module.f.d.ts +12 -2
  59. package/fs/types/effects/node/virtual/module.f.js +31 -17
  60. package/fs/types/function/compare/module.f.d.ts +12 -0
  61. package/fs/types/function/compare/module.f.js +33 -0
  62. package/fs/types/function/operator/test.f.d.ts +10 -0
  63. package/fs/types/function/operator/test.f.js +81 -0
  64. package/fs/types/nullable/test.f.js +10 -1
  65. package/fs/types/range_map/module.f.js +3 -18
  66. package/fs/types/result/module.f.d.ts +4 -0
  67. package/fs/types/result/module.f.js +4 -0
  68. package/fs/types/result/test.f.d.ts +2 -4
  69. package/fs/types/result/test.f.js +24 -16
  70. package/fs/types/rtti/common/module.f.d.ts +10 -1
  71. package/fs/types/rtti/common/module.f.js +7 -2
  72. package/fs/types/rtti/parse/module.f.js +35 -46
  73. package/fs/types/rtti/validate/module.f.js +9 -12
  74. package/fs/types/sorted_list/module.f.d.ts +1 -2
  75. package/fs/types/sorted_list/module.f.js +8 -21
  76. package/fs/types/sorted_set/module.f.d.ts +1 -3
  77. package/fs/types/ts/test.f.d.ts +18 -0
  78. package/fs/types/ts/test.f.js +111 -0
  79. package/fs/types/uint8array/module.f.js +7 -1
  80. package/fs/types/uint8array/test.f.d.ts +1 -0
  81. package/fs/types/uint8array/test.f.js +5 -1
  82. package/package.json +4 -4
package/fs/io/module.js CHANGED
@@ -10,12 +10,72 @@ import http from 'node:http';
10
10
  import childProcess from 'node:child_process';
11
11
  import fs from 'node:fs';
12
12
  import process from 'node:process';
13
- import { fromIo, run } from "./module.f.js";
14
13
  import { concat } from "../path/module.f.js";
14
+ import { once } from 'node:events';
15
+ import { fromIo, runProgram } from "./module.f.js";
15
16
  import { error, ok } from "../types/result/module.f.js";
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
+ };
16
44
  const prefix = 'file:///';
17
45
  const { now } = Date;
46
+ /** Maps `WriteConsoles` names to the corresponding Node.js writable streams. */
47
+ const streams = {
48
+ stdout: process.stdout,
49
+ stderr: process.stderr,
50
+ };
51
+ /**
52
+ * Writes `data` to `stream` respecting Node.js backpressure.
53
+ *
54
+ * `stream.write()` returns `false` when the internal buffer is full; the data
55
+ * is already buffered at that point (no retry needed) but the caller must not
56
+ * issue more writes until the `'drain'` event fires. Waiting here throttles the
57
+ * producer to the speed of the OS consumer, preventing unbounded memory growth
58
+ * when many large messages arrive faster than they can be flushed.
59
+ *
60
+ * When the buffer is not full `write()` returns `true` and we return
61
+ * immediately, so large computations with occasional prints never stall.
62
+ *
63
+ * @see {@link https://nodejs.org/api/stream.html#writablewritechunk}
64
+ */
65
+ const writeAll = async (stream, data) => {
66
+ if (!stream.write(data)) {
67
+ await once(stream, 'drain');
68
+ }
69
+ };
18
70
  export const asyncImport = (v) => import(__rewriteRelativeImportExtension(v));
71
+ export const tryCatch = f => {
72
+ try {
73
+ return ok(f());
74
+ }
75
+ catch (e) {
76
+ return error(e);
77
+ }
78
+ };
19
79
  export const io = {
20
80
  console,
21
81
  fs,
@@ -27,14 +87,7 @@ export const io = {
27
87
  },
28
88
  performance,
29
89
  fetch,
30
- tryCatch: f => {
31
- try {
32
- return ok(f());
33
- }
34
- catch (e) {
35
- return error(e);
36
- }
37
- },
90
+ tryCatch,
38
91
  asyncTryCatch: async (f) => {
39
92
  try {
40
93
  return ok(await f());
@@ -61,13 +114,11 @@ export const io = {
61
114
  }
62
115
  return { result, duration: after - before };
63
116
  },
117
+ write: (stream, data) => writeAll(streams[stream], fromVec(data)),
118
+ testContext,
119
+ bunTestContext,
120
+ playwrightTestContext,
121
+ engine: isPlaywright ? 'playwright' : 'Bun' in globalThis ? 'bun' : 'node',
64
122
  };
65
- export const legacyRun = run(io);
66
- export const ioRun = (io) => {
67
- const r = fromIo(io);
68
- const { argv, env } = io.process;
69
- const options = { args: argv.slice(2), env };
70
- return p => r(p(options));
71
- };
72
- const effectRun = ioRun(io);
123
+ const effectRun = runProgram(io)(io.process.argv.slice(2));
73
124
  export default effectRun;
@@ -20,3 +20,9 @@ export declare const normalize: Unary<string, string>;
20
20
  * Concatenates two path fragments and returns a normalized path.
21
21
  */
22
22
  export declare const concat: Reduce<string>;
23
+ /**
24
+ * Returns `path` relative to `base` with a `./` prefix, or `path` unchanged
25
+ * if it does not start with `base` or `base` is empty.
26
+ * E.g. `relativize('/repo', '/repo/fs/a.ts')` → `'./fs/a.ts'`.
27
+ */
28
+ export declare const relativize: (base: string, path: string) => string;
@@ -46,3 +46,9 @@ export const concat = a => b => {
46
46
  const s = stringConcat([a, '/', b]);
47
47
  return normalize(s);
48
48
  };
49
+ /**
50
+ * Returns `path` relative to `base` with a `./` prefix, or `path` unchanged
51
+ * if it does not start with `base` or `base` is empty.
52
+ * E.g. `relativize('/repo', '/repo/fs/a.ts')` → `'./fs/a.ts'`.
53
+ */
54
+ export const relativize = (base, path) => base !== '' && path.startsWith(base) ? `.${path.slice(base.length)}` : path;
@@ -1,5 +1,3 @@
1
- declare const _default: {
2
- normalize: (() => void)[];
3
- concat: (() => void)[];
4
- };
5
- export default _default;
1
+ export declare const normalizeTest: (() => void)[];
2
+ export declare const concatTest: (() => void)[];
3
+ export declare const relativizeTest: (() => void)[];
package/fs/path/test.f.js CHANGED
@@ -1,49 +1,67 @@
1
- import { concat, normalize } from "./module.f.js";
2
- export default {
3
- normalize: [
4
- () => {
5
- const norm = normalize("dir/file.json");
6
- if (norm !== "dir/file.json") {
7
- throw norm;
8
- }
9
- },
10
- () => {
11
- const norm = normalize("dir//file.json");
12
- if (norm !== "dir/file.json") {
13
- throw norm;
14
- }
15
- },
16
- () => {
17
- const norm = normalize("../../dir/file.json");
18
- if (norm !== "../../dir/file.json") {
19
- throw norm;
20
- }
21
- },
22
- () => {
23
- const norm = normalize("../../dir/../file.json");
24
- if (norm !== "../../file.json") {
25
- throw norm;
26
- }
27
- },
28
- ],
29
- concat: [
30
- () => {
31
- const c = concat("a")("b");
32
- if (c !== "a/b") {
33
- throw c;
34
- }
35
- },
36
- () => {
37
- const c = concat("a///b/")("c");
38
- if (c !== "a/b/c") {
39
- throw c;
40
- }
41
- },
42
- () => {
43
- const c = concat("a/../b/..")("c");
44
- if (c !== "c") {
45
- throw c;
46
- }
47
- },
48
- ]
49
- };
1
+ import { concat, normalize, relativize } from "./module.f.js";
2
+ export const normalizeTest = [
3
+ () => {
4
+ const norm = normalize("dir/file.json");
5
+ if (norm !== "dir/file.json") {
6
+ throw norm;
7
+ }
8
+ },
9
+ () => {
10
+ const norm = normalize("dir//file.json");
11
+ if (norm !== "dir/file.json") {
12
+ throw norm;
13
+ }
14
+ },
15
+ () => {
16
+ const norm = normalize("../../dir/file.json");
17
+ if (norm !== "../../dir/file.json") {
18
+ throw norm;
19
+ }
20
+ },
21
+ () => {
22
+ const norm = normalize("../../dir/../file.json");
23
+ if (norm !== "../../file.json") {
24
+ throw norm;
25
+ }
26
+ },
27
+ ];
28
+ export const concatTest = [
29
+ () => {
30
+ const c = concat("a")("b");
31
+ if (c !== "a/b") {
32
+ throw c;
33
+ }
34
+ },
35
+ () => {
36
+ const c = concat("a///b/")("c");
37
+ if (c !== "a/b/c") {
38
+ throw c;
39
+ }
40
+ },
41
+ () => {
42
+ const c = concat("a/../b/..")("c");
43
+ if (c !== "c") {
44
+ throw c;
45
+ }
46
+ },
47
+ ];
48
+ export const relativizeTest = [
49
+ () => {
50
+ const r = relativize('/repo', '/repo/fs/a.ts');
51
+ if (r !== './fs/a.ts') {
52
+ throw r;
53
+ }
54
+ },
55
+ () => {
56
+ const r = relativize('/repo', '/other/a.ts');
57
+ if (r !== '/other/a.ts') {
58
+ throw r;
59
+ }
60
+ },
61
+ () => {
62
+ const r = relativize('', './fs/a.ts');
63
+ if (r !== './fs/a.ts') {
64
+ throw r;
65
+ }
66
+ },
67
+ ];
@@ -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);
@@ -5,6 +5,8 @@
5
5
  * @module
6
6
  */
7
7
  import type { Io, Writable } from "../../io/module.f.ts";
8
+ import { type Write, type WriteConsoles, type NodeProgramOptions } from '../../types/effects/node/module.f.ts';
9
+ import { type Effect } from '../../types/effects/module.f.ts';
8
10
  export declare const backspace: string;
9
11
  type End = 'm';
10
12
  type Csi = (code: number | string) => string;
@@ -44,7 +46,7 @@ export type WriteText = (text: string) => WriteText;
44
46
  export declare const createConsoleText: (stdout: Stdout) => WriteText;
45
47
  export type CsiConsole = (s: string) => void;
46
48
  /**
47
- * Creates a TTY-aware console function.
49
+ * Creates a TTY-aware console function. Appends `\n` to each string before writing.
48
50
  *
49
51
  * For TTY destinations, ANSI SGR sequences are preserved.
50
52
  * For non-TTY destinations, ANSI SGR sequences are stripped.
@@ -57,4 +59,10 @@ export declare const console: ({ fs: { writeSync } }: Io) => (w: Writable) => Cs
57
59
  export declare const stdio: (io: Io) => CsiConsole;
58
60
  /** Writes to process stderr using a TTY-aware CSI console. */
59
61
  export declare const stderr: (io: Io) => CsiConsole;
62
+ /**
63
+ * Effect-based TTY-aware write. Strips ANSI SGR sequences when the target
64
+ * stream is not a TTY, then encodes to UTF-8 and emits a `Write` effect.
65
+ * Does NOT append `\n` — callers are responsible for line termination.
66
+ */
67
+ export declare const csiWrite: ({ std }: NodeProgramOptions) => (stream: WriteConsoles) => (s: string) => Effect<Write, void>;
60
68
  export {};
@@ -4,6 +4,9 @@
4
4
  *
5
5
  * @module
6
6
  */
7
+ import { write } from "../../types/effects/node/module.f.js";
8
+ import {} from "../../types/effects/module.f.js";
9
+ import { utf8 } from "../module.f.js";
7
10
  export const backspace = '\x08';
8
11
  const begin = '\x1b[';
9
12
  /**
@@ -46,8 +49,9 @@ export const createConsoleText = (stdout) => {
46
49
  };
47
50
  return f('');
48
51
  };
52
+ const str = (isTTY) => (s) => isTTY ? s : s.replace(/\x1b\[[0-9;]*m/g, '');
49
53
  /**
50
- * Creates a TTY-aware console function.
54
+ * Creates a TTY-aware console function. Appends `\n` to each string before writing.
51
55
  *
52
56
  * For TTY destinations, ANSI SGR sequences are preserved.
53
57
  * For non-TTY destinations, ANSI SGR sequences are stripped.
@@ -56,12 +60,19 @@ export const createConsoleText = (stdout) => {
56
60
  * @returns A function that targets a writable stream.
57
61
  */
58
62
  export const console = ({ fs: { writeSync } }) => (w) => {
59
- const { isTTY } = w;
60
- return isTTY
61
- ? (s) => writeSync(w.fd, s + '\n')
62
- : (s) => writeSync(w.fd, s.replace(/\x1b\[[0-9;]*m/g, '') + '\n');
63
+ const toStr = str(w.isTTY);
64
+ return (s) => writeSync(w.fd, toStr(s) + '\n');
63
65
  };
64
66
  /** Writes to process stdout using a TTY-aware CSI console. */
65
67
  export const stdio = (io) => console(io)(io.process.stdout);
66
68
  /** Writes to process stderr using a TTY-aware CSI console. */
67
69
  export const stderr = (io) => console(io)(io.process.stderr);
70
+ /**
71
+ * Effect-based TTY-aware write. Strips ANSI SGR sequences when the target
72
+ * stream is not a TTY, then encodes to UTF-8 and emits a `Write` effect.
73
+ * Does NOT append `\n` — callers are responsible for line termination.
74
+ */
75
+ export const csiWrite = ({ std }) => (stream) => {
76
+ const toStr = str(std[stream].isTTY);
77
+ return (s) => write(stream, utf8(toStr(s)));
78
+ };
@@ -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;
@@ -1,12 +1,3 @@
1
- /**
2
- * Node.js effect operations: filesystem (`mkdir`, `readFile`, `readdir`,
3
- * `writeFile`, `rm`, `access`), networking (`fetch`, `createServer`, `listen`),
4
- * subprocess `exec`, console (`log`, `error`), `import_`, `now`, `sandbox`, `forever`,
5
- * and `all`/`both` parallelism; defines the `NodeOp`/`NodeProgram` types used
6
- * by the Node runner.
7
- *
8
- * @module
9
- */
10
1
  import type { Vec } from '../../bit_vec/module.f.ts';
11
2
  import type { Nominal } from '../../nominal/module.f.ts';
12
3
  import type { Result } from '../../result/module.f.ts';
@@ -58,11 +49,6 @@ export declare const exec: Func<Exec>;
58
49
  export type Access = readonly ['access', (path: string) => IoResult<void>];
59
50
  export declare const access: Func<Access>;
60
51
  export type Fs = Mkdir | ReadFile | Readdir | WriteFile | Rm | Exec | Access;
61
- export type Error = ['error', (message: string) => void];
62
- export declare const error: Func<Error>;
63
- export type Log = ['log', (message: string) => void];
64
- export declare const log: Func<Log>;
65
- export type Console = Log | Error;
66
52
  export type Server = Nominal<'server', `160855c4f69310fece3273c1853ac32de43dee1eb41bf59d821917f8eebe9272`, unknown>;
67
53
  export type Headers = {
68
54
  readonly [k in string]: string;
@@ -87,10 +73,18 @@ export type Http = CreateServer | Listen;
87
73
  export type Forever = ['forever', () => never];
88
74
  export declare const forever: Func<Forever>;
89
75
  export type Module = {
90
- readonly default: unknown;
76
+ readonly [k in string]: unknown;
91
77
  };
92
78
  export type Import = ['import', (path: string) => IoResult<Module>];
93
79
  export declare const import_: Func<Import>;
80
+ export type WriteConsoles = 'stdout' | 'stderr';
81
+ export type Write = readonly ['write', (stream: WriteConsoles, data: Vec) => void];
82
+ export declare const write: Func<Write>;
83
+ export type Console = (s: string) => Effect<Write, void>;
84
+ /** Writes a line to `stdout`. Replaces the retired `Log` effect. */
85
+ export declare const log: Console;
86
+ /** Writes a line to `stderr`. Replaces the retired `Error` effect. */
87
+ export declare const error: Console;
94
88
  export type Now = readonly ['now', () => number];
95
89
  export declare const now: Func<Now>;
96
90
  export type SandboxResult<T> = {
@@ -122,7 +116,15 @@ export type Sandbox = readonly ['sandbox', <T>(f: () => T) => SandboxResult<T>];
122
116
  * @see {@link SandboxResult}
123
117
  */
124
118
  export declare const sandbox: Func<Sandbox>;
125
- export type NodeOp = All | Fetch | Console | Fs | Http | Forever | Import | Now | Sandbox;
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;
126
128
  export type NodeEffect<T> = Effect<NodeOp, T>;
127
129
  export type NodeOperationMap = ToAsyncOperationMap<NodeOp>;
128
130
  /**
@@ -131,8 +133,19 @@ export type NodeOperationMap = ToAsyncOperationMap<NodeOp>;
131
133
  export type Env = {
132
134
  readonly [k: string]: string | undefined;
133
135
  };
136
+ export type Engine = 'node' | 'bun' | 'playwright';
134
137
  export type NodeProgramOptions = {
135
138
  readonly args: readonly string[];
136
139
  readonly env: Env;
140
+ readonly std: {
141
+ readonly [k in WriteConsoles]: {
142
+ readonly isTTY: boolean;
143
+ };
144
+ };
145
+ readonly testContext: TestContext;
146
+ readonly bunTestContext: TestContext;
147
+ readonly playwrightTestContext: TestContext;
148
+ readonly engine: Engine;
137
149
  };
138
- export type NodeProgram = (options: NodeProgramOptions) => Effect<NodeOp, number>;
150
+ export type Program<O extends Operation> = (options: NodeProgramOptions) => Effect<O, number>;
151
+ export type NodeProgram = Program<NodeOp>;
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Node.js effect operations: filesystem (`mkdir`, `readFile`, `readdir`,
3
+ * `writeFile`, `rm`, `access`), networking (`fetch`, `createServer`, `listen`),
4
+ * subprocess `exec`, `log`/`error` (wrappers over `write`), `import_`, `now`,
5
+ * `sandbox`, `forever`, and `all`/`both` parallelism; defines the
6
+ * `NodeOp`/`NodeProgram` types used by the Node runner.
7
+ *
8
+ * @module
9
+ */
10
+ import { utf8 } from "../../../text/module.f.js";
1
11
  import { do_ } from "../module.f.js";
2
12
  const doAll = do_('all');
3
13
  /**
@@ -17,12 +27,20 @@ export const writeFile = do_('writeFile');
17
27
  export const rm = do_('rm');
18
28
  export const exec = do_('exec');
19
29
  export const access = do_('access');
20
- export const error = do_('error');
21
- export const log = do_('log');
22
30
  export const createServer = do_('createServer');
23
31
  export const listen = do_('listen');
24
32
  export const forever = do_('forever');
25
33
  export const import_ = do_('import');
34
+ export const write = do_('write');
35
+ /**
36
+ * Encodes `s + '\n'` as UTF-8 and emits a `Write` effect to `stream`.
37
+ * Shared implementation for `log` and `error`.
38
+ */
39
+ const writeString = (stream) => (s) => write(stream, utf8(s + '\n'));
40
+ /** Writes a line to `stdout`. Replaces the retired `Log` effect. */
41
+ export const log = writeString('stdout');
42
+ /** Writes a line to `stderr`. Replaces the retired `Error` effect. */
43
+ export const error = writeString('stderr');
26
44
  export const now = do_('now');
27
45
  /**
28
46
  * Runs a plain synchronous function in an isolated, measured environment.
@@ -43,3 +61,4 @@ export const now = do_('now');
43
61
  * @see {@link SandboxResult}
44
62
  */
45
63
  export const sandbox = do_('sandbox');
64
+ export const test = do_('test');