functionalscript 0.30.0 → 0.31.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.
Files changed (181) hide show
  1. package/fs/asn.1/module.f.js +18 -22
  2. package/fs/asn.1/proof.f.d.ts +5 -0
  3. package/fs/asn.1/proof.f.js +38 -2
  4. package/fs/base64/module.f.d.ts +9 -0
  5. package/fs/base64/module.f.js +60 -0
  6. package/fs/base64/proof.f.d.ts +11 -0
  7. package/fs/base64/proof.f.js +117 -0
  8. package/fs/base_n/module.f.d.ts +44 -0
  9. package/fs/base_n/module.f.js +54 -0
  10. package/fs/base_n/proof.f.d.ts +10 -0
  11. package/fs/base_n/proof.f.js +86 -0
  12. package/fs/bnf/data/module.f.d.ts +4 -9
  13. package/fs/bnf/data/module.f.js +4 -3
  14. package/fs/bnf/module.f.d.ts +3 -4
  15. package/fs/bnf/module.f.js +3 -2
  16. package/fs/bnf/proof.f.d.ts +9 -0
  17. package/fs/bnf/proof.f.js +98 -1
  18. package/fs/cas/mcp/module.f.d.ts +40 -0
  19. package/fs/cas/mcp/module.f.js +223 -0
  20. package/fs/cas/mcp/proof.f.d.ts +30 -0
  21. package/fs/cas/mcp/proof.f.js +310 -0
  22. package/fs/cas/module.f.d.ts +9 -3
  23. package/fs/cas/module.f.js +104 -69
  24. package/fs/cas/proof.f.d.ts +2 -0
  25. package/fs/cas/proof.f.js +30 -1
  26. package/fs/cbase32/module.f.d.ts +1 -1
  27. package/fs/cbase32/module.f.js +19 -40
  28. package/fs/cbase32/proof.f.d.ts +1 -0
  29. package/fs/cbase32/proof.f.js +15 -0
  30. package/fs/ci/config/module.f.d.ts +6 -6
  31. package/fs/ci/config/module.f.js +6 -6
  32. package/fs/ci/deno/module.f.js +2 -2
  33. package/fs/ci/module.f.d.ts +5 -0
  34. package/fs/ci/module.f.js +2 -3
  35. package/fs/ci/proof.f.d.ts +1 -0
  36. package/fs/ci/proof.f.js +9 -3
  37. package/fs/cli/module.f.d.ts +3 -1
  38. package/fs/cli/module.f.js +22 -12
  39. package/fs/cli/proof.f.d.ts +5 -0
  40. package/fs/cli/proof.f.js +76 -0
  41. package/fs/crypto/hmac/proof.f.d.ts +1 -0
  42. package/fs/crypto/hmac/proof.f.js +8 -0
  43. package/fs/crypto/secp/proof.f.d.ts +1 -0
  44. package/fs/crypto/secp/proof.f.js +10 -0
  45. package/fs/crypto/sign/module.f.js +1 -4
  46. package/fs/dev/module.f.d.ts +5 -3
  47. package/fs/dev/module.f.js +13 -2
  48. package/fs/dev/package_json/proof.f.d.ts +1 -0
  49. package/fs/dev/package_json/proof.f.js +5 -0
  50. package/fs/dev/proof.f.d.ts +1 -0
  51. package/fs/dev/proof.f.js +12 -1
  52. package/fs/djs/ast/module.f.d.ts +1 -1
  53. package/fs/djs/ast/proof.f.d.ts +5 -0
  54. package/fs/djs/ast/proof.f.js +17 -0
  55. package/fs/djs/module.f.d.ts +1 -1
  56. package/fs/djs/module.f.js +2 -3
  57. package/fs/djs/serializer/module.f.js +24 -11
  58. package/fs/djs/transpiler/module.f.js +3 -4
  59. package/fs/effects/memory/module.f.d.ts +37 -0
  60. package/fs/effects/memory/module.f.js +24 -0
  61. package/fs/effects/memory/proof.f.d.ts +6 -0
  62. package/fs/effects/memory/proof.f.js +64 -0
  63. package/fs/effects/mock/module.f.d.ts +7 -2
  64. package/fs/effects/mock/module.f.js +12 -8
  65. package/fs/effects/module.d.ts +1 -1
  66. package/fs/effects/module.f.d.ts +41 -0
  67. package/fs/effects/module.f.js +25 -0
  68. package/fs/effects/module.js +6 -7
  69. package/fs/effects/node/memory/module.d.ts +20 -0
  70. package/fs/effects/node/memory/module.js +44 -0
  71. package/fs/effects/node/memory/proof.d.ts +10 -0
  72. package/fs/effects/node/memory/proof.js +35 -0
  73. package/fs/effects/node/module.d.ts +1 -1
  74. package/fs/effects/node/module.f.d.ts +54 -8
  75. package/fs/effects/node/module.f.js +55 -4
  76. package/fs/effects/node/module.js +167 -137
  77. package/fs/effects/node/proof.f.d.ts +10 -0
  78. package/fs/effects/node/proof.f.js +88 -21
  79. package/fs/effects/node/virtual/module.f.d.ts +6 -0
  80. package/fs/effects/node/virtual/module.f.js +48 -16
  81. package/fs/effects/node/virtual/proof.f.d.ts +17 -0
  82. package/fs/effects/node/virtual/proof.f.js +79 -0
  83. package/fs/effects/proof.f.d.ts +5 -0
  84. package/fs/effects/proof.f.js +54 -34
  85. package/fs/emergent_testing/module.f.d.ts +6 -1
  86. package/fs/emergent_testing/module.f.js +8 -3
  87. package/fs/emergent_testing/proof.f.d.ts +11 -0
  88. package/fs/emergent_testing/proof.f.js +52 -16
  89. package/fs/fjs/module.f.js +2 -2
  90. package/fs/fjs/proof.f.d.ts +8 -0
  91. package/fs/fjs/proof.f.js +39 -0
  92. package/fs/fsc/module.f.d.ts +5 -0
  93. package/fs/fsc/module.f.js +17 -27
  94. package/fs/fsc/proof.f.d.ts +8 -0
  95. package/fs/fsc/proof.f.js +48 -2
  96. package/fs/fsm/module.f.d.ts +2 -3
  97. package/fs/fsm/module.f.js +1 -1
  98. package/fs/html/module.f.d.ts +2 -3
  99. package/fs/html/module.f.js +7 -6
  100. package/fs/html/proof.f.d.ts +1 -0
  101. package/fs/html/proof.f.js +7 -0
  102. package/fs/js/tokenizer/module.f.js +1 -1
  103. package/fs/js/tokenizer/proof.f.js +7 -0
  104. package/fs/json/module.f.d.ts +9 -2
  105. package/fs/json/module.f.js +9 -7
  106. package/fs/json/parser/module.f.js +4 -6
  107. package/fs/json/parser/proof.f.js +19 -10
  108. package/fs/json/proof.f.d.ts +2 -1
  109. package/fs/json/proof.f.js +33 -6
  110. package/fs/json/rpc/module.f.d.ts +5 -0
  111. package/fs/json/rpc/module.f.js +16 -11
  112. package/fs/json/schema/module.f.d.ts +15 -14
  113. package/fs/json/schema/proof.f.d.ts +3 -0
  114. package/fs/json/schema/proof.f.js +16 -1
  115. package/fs/mcp/module.f.d.ts +103 -5
  116. package/fs/mcp/module.f.js +139 -2
  117. package/fs/mcp/proof.f.d.ts +34 -0
  118. package/fs/mcp/proof.f.js +207 -0
  119. package/fs/mcp/stdio/module.f.d.ts +39 -0
  120. package/fs/mcp/stdio/module.f.js +54 -0
  121. package/fs/mcp/stdio/proof.f.d.ts +10 -0
  122. package/fs/mcp/stdio/proof.f.js +92 -0
  123. package/fs/mime/module.f.d.ts +39 -0
  124. package/fs/mime/module.f.js +78 -0
  125. package/fs/mime/proof.f.d.ts +15 -0
  126. package/fs/mime/proof.f.js +30 -0
  127. package/fs/path/module.f.d.ts +8 -0
  128. package/fs/path/module.f.js +10 -3
  129. package/fs/path/proof.f.d.ts +1 -0
  130. package/fs/path/proof.f.js +40 -2
  131. package/fs/text/sgr/proof.f.js +30 -2
  132. package/fs/text/utf8/module.f.d.ts +7 -0
  133. package/fs/text/utf8/module.f.js +85 -31
  134. package/fs/text/utf8/proof.f.d.ts +1 -0
  135. package/fs/text/utf8/proof.f.js +60 -1
  136. package/fs/types/bigfloat/module.f.d.ts +3 -0
  137. package/fs/types/bigfloat/module.f.js +30 -31
  138. package/fs/types/bigint/module.f.d.ts +24 -2
  139. package/fs/types/bigint/module.f.js +30 -2
  140. package/fs/types/bigint/proof.f.d.ts +7 -0
  141. package/fs/types/bigint/proof.f.js +47 -16
  142. package/fs/types/bit_vec/module.f.d.ts +18 -0
  143. package/fs/types/bit_vec/module.f.js +18 -0
  144. package/fs/types/bit_vec/proof.f.d.ts +1 -0
  145. package/fs/types/bit_vec/proof.f.js +13 -1
  146. package/fs/types/function/operator/proof.f.d.ts +2 -0
  147. package/fs/types/function/operator/proof.f.js +20 -2
  148. package/fs/types/list/module.f.js +1 -4
  149. package/fs/types/list/proof.f.d.ts +1 -1
  150. package/fs/types/list/proof.f.js +14 -6
  151. package/fs/types/map/proof.f.d.ts +1 -0
  152. package/fs/types/map/proof.f.js +10 -1
  153. package/fs/types/nibble_set/module.f.d.ts +13 -0
  154. package/fs/types/nibble_set/module.f.js +16 -0
  155. package/fs/types/object/module.f.d.ts +12 -8
  156. package/fs/types/object/module.f.js +9 -2
  157. package/fs/types/object/proof.f.d.ts +14 -0
  158. package/fs/types/object/proof.f.js +4 -0
  159. package/fs/types/ordered_map/proof.f.d.ts +1 -0
  160. package/fs/types/ordered_map/proof.f.js +39 -79
  161. package/fs/types/phantom/module.f.d.ts +22 -0
  162. package/fs/types/phantom/module.f.js +11 -0
  163. package/fs/types/prime_field/proof.f.d.ts +7 -0
  164. package/fs/types/prime_field/proof.f.js +63 -122
  165. package/fs/types/rtti/common/module.f.d.ts +4 -4
  166. package/fs/types/rtti/common/module.f.js +1 -1
  167. package/fs/types/rtti/module.f.d.ts +2 -3
  168. package/fs/types/rtti/parse/module.f.js +0 -1
  169. package/fs/types/rtti/ts/module.f.d.ts +26 -32
  170. package/fs/types/rtti/ts/module.f.js +2 -2
  171. package/fs/types/rtti/ts/proof.f.js +3 -3
  172. package/fs/types/rtti/validate/module.f.js +0 -1
  173. package/fs/types/sorted_list/module.f.d.ts +6 -0
  174. package/fs/types/sorted_list/module.f.js +11 -0
  175. package/fs/types/sorted_set/module.f.js +2 -8
  176. package/fs/types/ts/module.f.d.ts +1 -1
  177. package/fs/types/ts/module.f.js +3 -1
  178. package/fs/types/ts/proof.f.js +3 -2
  179. package/fs/website/proof.f.d.ts +1 -0
  180. package/fs/website/proof.f.js +8 -1
  181. package/package.json +3 -3
@@ -4,17 +4,21 @@
4
4
  * @module
5
5
  */
6
6
  import { todo } from "../../../asserts/module.f.js";
7
- import { parse } from "../../../path/module.f.js";
7
+ import { join, parse } from "../../../path/module.f.js";
8
8
  import { utf8ToString } from "../../../text/module.f.js";
9
9
  import { isVec } from "../../../types/bit_vec/module.f.js";
10
10
  import { error, ok } from "../../../types/result/module.f.js";
11
11
  import { run } from "../../mock/module.f.js";
12
+ import { asBase, asNominal } from "../../memory/module.f.js";
12
13
  export const emptyState = {
13
14
  stdout: '',
14
15
  stderr: '',
16
+ stdin: [],
15
17
  root: {},
16
18
  internet: {},
17
19
  epochNs: 0,
20
+ memoryNext: 0,
21
+ memoryValues: {},
18
22
  };
19
23
  const operation = (op) => {
20
24
  const f = (dir, path) => {
@@ -29,7 +33,7 @@ const operation = (op) => {
29
33
  const [newSubDir, r] = f(subDir, rest);
30
34
  return [{ ...dir, [first]: newSubDir }, r];
31
35
  };
32
- return (state, path) => {
36
+ return (path) => (state) => {
33
37
  const [root, result] = f(state.root, parse(path));
34
38
  return [{ ...state, root }, result];
35
39
  };
@@ -49,15 +53,20 @@ const mkdir = (recursive) => operation((dir, path) => {
49
53
  dir = { ...dir, ...d };
50
54
  return [dir, okVoid];
51
55
  });
52
- const readFileError = error('no such file');
56
+ /** Absent-path error mirroring Node's `ENOENT`, so `isNotFound` recognizes it. */
57
+ const enoent = error({ code: 'ENOENT' });
53
58
  const readFile = readOperation((dir, path) => {
54
59
  if (path.length !== 1) {
55
- return readFileError;
60
+ return enoent;
56
61
  }
57
62
  const file = dir[path[0]];
58
63
  if (typeof file === 'function') {
59
64
  throw new Error(`'${path[0]}' is a JsModule; readFile not supported`);
60
65
  }
66
+ if (file === undefined) {
67
+ return enoent;
68
+ }
69
+ // exists but is a directory — a real error, not a missing path
61
70
  if (!isVec(file)) {
62
71
  return error(`'${path[0]}' is not a file`);
63
72
  }
@@ -102,7 +111,7 @@ const readdir = (base, recursive) => readOperation((dir, path) => {
102
111
  const isFile = typeof content !== 'object';
103
112
  result = [...result, { name, parentPath, isFile }];
104
113
  if (!isFile && recursive) {
105
- result = [...result, ...f(`${parentPath}/${name}`, content)];
114
+ result = [...result, ...f(join(parentPath, name), content)];
106
115
  }
107
116
  }
108
117
  return result;
@@ -114,9 +123,9 @@ const access = readOperation((dir, path) => {
114
123
  return okVoid;
115
124
  }
116
125
  if (path.length !== 1) {
117
- return error('no such file or directory');
126
+ return enoent;
118
127
  }
119
- return dir[path[0]] !== undefined ? okVoid : error('no such file or directory');
128
+ return dir[path[0]] !== undefined ? okVoid : enoent;
120
129
  });
121
130
  const rm = operation((dir, path) => {
122
131
  if (path.length !== 1) {
@@ -134,7 +143,7 @@ const rm = operation((dir, path) => {
134
143
  return [rest, okVoid];
135
144
  });
136
145
  const map = {
137
- all: (state, ...a) => {
146
+ all: (...a) => state => {
138
147
  let e = [];
139
148
  for (const i of a) {
140
149
  const [ns, ei] = virtual(state)(i);
@@ -143,14 +152,31 @@ const map = {
143
152
  }
144
153
  return [state, e];
145
154
  },
146
- fetch: (state, url) => {
155
+ memCreate: value => state => {
156
+ const id = `mem${state.memoryNext}`;
157
+ const key = asNominal(id);
158
+ return [{
159
+ ...state,
160
+ memoryNext: state.memoryNext + 1,
161
+ memoryValues: { ...state.memoryValues, [id]: value },
162
+ }, key];
163
+ },
164
+ memRead: key => state => [state, state.memoryValues[asBase(key)]],
165
+ memWrite: (key, value) => state => {
166
+ const id = asBase(key);
167
+ return [{
168
+ ...state,
169
+ memoryValues: { ...state.memoryValues, [id]: value },
170
+ }, undefined];
171
+ },
172
+ fetch: url => state => {
147
173
  const result = state.internet[url];
148
174
  return result === undefined ? [state, error('not found')] : [state, ok(result)];
149
175
  },
150
- mkdir: (state, path, p) => mkdir(p !== undefined)(state, path),
176
+ mkdir: (path, p) => mkdir(p !== undefined)(path),
151
177
  readFile,
152
- readdir: (state, path, { recursive }) => readdir(path, recursive === true)(state, path),
153
- writeFile: (state, path, payload) => writeFile(payload)(state, path),
178
+ readdir: (path, { recursive }) => readdir(path, recursive === true)(path),
179
+ writeFile: (path, payload) => writeFile(payload)(path),
154
180
  access,
155
181
  import: import_,
156
182
  rm,
@@ -158,7 +184,7 @@ const map = {
158
184
  createServer: todo,
159
185
  listen: todo,
160
186
  forever: todo,
161
- now: (state) => [state, state.epochNs],
187
+ now: () => state => [state, state.epochNs],
162
188
  // Virtual sandbox is a pass-through: the fixture's test function is
163
189
  // expected to return a `SandboxResult` directly (encoding pass/fail and a
164
190
  // chosen duration), so the handler invokes it without try/catch or clock
@@ -166,12 +192,18 @@ const map = {
166
192
  // result instead of the runner measuring real execution. A genuine
167
193
  // exception in a fixture propagates loudly as a bug in the fixture.
168
194
  // See: issues/156-tf-virtual-tests.md
169
- sandbox: (state, f) => [state, f()],
170
- await: (state, p) => [state, [p]],
195
+ sandbox: f => state => [state, f()],
196
+ await: p => state => [state, [p]],
171
197
  test: todo,
172
- write: (state, stream, data) => {
198
+ write: (stream, data) => state => {
173
199
  const s = utf8ToString(data);
174
200
  return [{ ...state, [stream]: `${state[stream]}${s}` }, undefined];
175
201
  },
202
+ read: () => state => {
203
+ const [first, ...rest] = state.stdin;
204
+ return state.stdin.length === 0
205
+ ? [state, null]
206
+ : [{ ...state, stdin: rest }, first];
207
+ },
176
208
  };
177
209
  export const virtual = run(map);
@@ -0,0 +1,17 @@
1
+ export declare const proof: {
2
+ rm: {
3
+ success: () => void;
4
+ notFound: () => void;
5
+ isDirectory: () => void;
6
+ };
7
+ writeFileOnDirectory: () => void;
8
+ readdirRecursive: () => void;
9
+ readFileIntoDir: () => void;
10
+ awaitNonPromise: () => void;
11
+ fetchNotFound: () => void;
12
+ importNestedPath: () => void;
13
+ importNonModule: () => void;
14
+ throw: {
15
+ readFileOnJsModule: () => void;
16
+ };
17
+ };
@@ -0,0 +1,79 @@
1
+ import { assertEq } from "../../../asserts/module.f.js";
2
+ import { awaitIfPromise, fetch, rm, writeFile, readFile, readdir, import_ } from "../module.f.js";
3
+ import { vec8 } from "../../../types/bit_vec/module.f.js";
4
+ import { emptyState, virtual } from "./module.f.js";
5
+ export const proof = {
6
+ rm: {
7
+ success: () => {
8
+ const root = { 'a.txt': vec8(0x42n) };
9
+ const [, result] = virtual({ ...emptyState, root })(rm('a.txt'));
10
+ assertEq(result[0], 'ok');
11
+ },
12
+ notFound: () => {
13
+ const [, result] = virtual(emptyState)(rm('notexist.txt'));
14
+ assertEq(result[0], 'error');
15
+ },
16
+ isDirectory: () => {
17
+ const inner = {};
18
+ const root = { 'mydir': inner };
19
+ const [, result] = virtual({ ...emptyState, root })(rm('mydir'));
20
+ assertEq(result[0], 'error');
21
+ },
22
+ },
23
+ writeFileOnDirectory: () => {
24
+ const inner = {};
25
+ const root = { 'mydir': inner };
26
+ const [, result] = virtual({ ...emptyState, root })(writeFile('mydir', vec8(0x42n)));
27
+ assertEq(result[0], 'error');
28
+ },
29
+ readdirRecursive: () => {
30
+ const file = vec8(0x42n);
31
+ const sub = { 'file.txt': file };
32
+ const outer = { 'sub': sub };
33
+ const root = { 'mydir': outer };
34
+ const [, result] = virtual({ ...emptyState, root })(readdir('mydir', { recursive: true }));
35
+ assertEq(result[0], 'ok');
36
+ if (result[0] !== 'ok') {
37
+ throw result;
38
+ }
39
+ assertEq(result[1].length, 2);
40
+ },
41
+ readFileIntoDir: () => {
42
+ // 'a/b' where both 'a' and 'b' are directories
43
+ // hits path.length === 0 in operation's f and path.length !== 1 in readFile op
44
+ const inner = {};
45
+ const outer = { 'b': inner };
46
+ const root = { 'a': outer };
47
+ const [, result] = virtual({ ...emptyState, root })(readFile('a/b'));
48
+ assertEq(result[0], 'error');
49
+ },
50
+ awaitNonPromise: () => {
51
+ // a non-promise value passes through the virtual `await` handler as-is
52
+ const [, result] = virtual(emptyState)(awaitIfPromise(42));
53
+ assertEq(result, 42);
54
+ },
55
+ fetchNotFound: () => {
56
+ // covers the `result === undefined` branch of the `fetch` handler
57
+ const [, result] = virtual(emptyState)(fetch('https://example.com/missing'));
58
+ assertEq(result[0], 'error');
59
+ },
60
+ importNestedPath: () => {
61
+ // import_ on a path whose parent does not exist covers the
62
+ // path.length !== 1 branch of the import_ op
63
+ const [, result] = virtual(emptyState)(import_('a/b'));
64
+ assertEq(result[0], 'error');
65
+ },
66
+ importNonModule: () => {
67
+ // import_ on a Vec (not a JsModule) covers typeof entry !== 'function' branch
68
+ const root = { 'module.f.ts': vec8(0x42n) };
69
+ const [, result] = virtual({ ...emptyState, root })(import_('module.f.ts'));
70
+ assertEq(result[0], 'error');
71
+ },
72
+ throw: {
73
+ readFileOnJsModule: () => {
74
+ // readFile on a JsModule path covers typeof file === 'function' branch
75
+ const root = { 'a.f.ts': (() => ({})) };
76
+ virtual({ ...emptyState, root })(readFile('a.f.ts'));
77
+ },
78
+ },
79
+ };
@@ -8,4 +8,9 @@ export declare const proof: {
8
8
  empty: () => void;
9
9
  runs: () => void;
10
10
  };
11
+ decode: () => void;
12
+ match: {
13
+ done: () => void;
14
+ cont: () => void;
15
+ };
11
16
  };
@@ -1,56 +1,76 @@
1
- import { foldStep, forEachStep, pure } from "./module.f.js";
1
+ import { decode, do_, foldStep, forEachStep, match, pure } from "./module.f.js";
2
+ const assertPure = (e, expected) => {
3
+ const d = decode(e);
4
+ if (!d.done) {
5
+ throw e.value;
6
+ }
7
+ if (d.result !== expected) {
8
+ throw d.result;
9
+ }
10
+ };
11
+ const next = match({ add: (a, b) => a + b });
2
12
  export const proof = {
3
13
  foldStep: {
4
14
  empty: () => {
5
15
  const e = foldStep((x) => (s) => pure(s + x))(10)([]);
6
- const { value } = e;
7
- if (value.length !== 1) {
8
- throw value;
9
- }
10
- if (value[0] !== 10) {
11
- throw value[0];
12
- }
16
+ assertPure(e, 10);
13
17
  },
14
18
  threadsState: () => {
15
19
  const e = foldStep((x) => (s) => pure(s + x))(0)([1, 2, 3, 4]);
16
- const { value } = e;
17
- if (value.length !== 1) {
18
- throw value;
19
- }
20
- if (value[0] !== 10) {
21
- throw value[0];
22
- }
20
+ assertPure(e, 10);
23
21
  },
24
22
  order: () => {
25
23
  const e = foldStep((x) => (s) => pure(s + x))('')(['a', 'b', 'c']);
26
- const { value } = e;
27
- if (value.length !== 1) {
28
- throw value;
29
- }
30
- if (value[0] !== 'abc') {
31
- throw value[0];
32
- }
24
+ assertPure(e, 'abc');
33
25
  },
34
26
  },
35
27
  forEachStep: {
36
28
  empty: () => {
37
29
  const e = forEachStep(() => pure(undefined))([]);
38
- const { value } = e;
39
- if (value.length !== 1) {
40
- throw value;
41
- }
42
- if (value[0] !== undefined) {
43
- throw value[0];
44
- }
30
+ assertPure(e, undefined);
45
31
  },
46
32
  runs: () => {
47
33
  const e = forEachStep(() => pure(undefined))([1, 2, 3]);
48
- const { value } = e;
49
- if (value.length !== 1) {
50
- throw value;
34
+ assertPure(e, undefined);
35
+ },
36
+ },
37
+ decode: () => {
38
+ const d = decode(do_('add')(2, 3));
39
+ if (d.done) {
40
+ throw d;
41
+ }
42
+ if (d.command !== 'add') {
43
+ throw d.command;
44
+ }
45
+ if (d.payload[0] !== 2 || d.payload[1] !== 3) {
46
+ throw d.payload;
47
+ }
48
+ assertPure(d.continuation(5), 5);
49
+ },
50
+ match: {
51
+ done: () => {
52
+ const r = next(pure(7));
53
+ if (r[0] !== 'done') {
54
+ throw r;
55
+ }
56
+ if (r[1] !== 7) {
57
+ throw r[1];
58
+ }
59
+ },
60
+ cont: () => {
61
+ const r = next(do_('add')(2, 3));
62
+ if (r[0] !== 'cont') {
63
+ throw r;
64
+ }
65
+ if (r[1] !== 5) {
66
+ throw r[1];
67
+ }
68
+ const r2 = next(r[2](r[1]));
69
+ if (r2[0] !== 'done') {
70
+ throw r2;
51
71
  }
52
- if (value[0] !== undefined) {
53
- throw value[0];
72
+ if (r2[1] !== 5) {
73
+ throw r2[1];
54
74
  }
55
75
  },
56
76
  },
@@ -24,7 +24,12 @@ export type TestSet = TestEntry | readonly (readonly [string, unknown])[];
24
24
  * Converts an arbitrary JS value into a `TestSet`.
25
25
  *
26
26
  * - Zero-argument functions become a `TestEntry`; the `throws` flag is set if
27
- * `throws` is already `true` or the function's `.name === 'throw'`.
27
+ * `throws` is already `true` (i.e. a `throw` key appears in the ancestor path).
28
+ * The canonical way to declare a throw-test is structural: nest it under a
29
+ * `throw` property key. The secondary `fn.name === 'throw'` check is a legacy
30
+ * path that only fires when a function is extracted from a `throw` key and
31
+ * placed under a different key; that pattern is engine-dependent (unreliable on
32
+ * Bun) and is not a supported authoring style.
28
33
  * - Non-null objects become an array of `[key, value]` pairs to recurse into.
29
34
  * - All other values (including functions with parameters) produce an empty array.
30
35
  */
@@ -15,6 +15,7 @@ import { all, awaitIfPromise, sandbox, test } from "../effects/node/module.f.js"
15
15
  import { pure } from "../effects/module.f.js";
16
16
  import { loadModuleMap, shouldLoad } from "../dev/module.f.js";
17
17
  import { invert } from "../types/result/module.f.js";
18
+ import { definedEntries } from "../types/object/module.f.js";
18
19
  const addPass = (delta) => (ts) => ({ ...ts, time: ts.time + delta, pass: ts.pass + 1 });
19
20
  const addFail = (delta) => (ts) => ({ ...ts, time: ts.time + delta, fail: ts.fail + 1 });
20
21
  const timeFormat = (a) => {
@@ -30,7 +31,12 @@ const timeFormat = (a) => {
30
31
  * Converts an arbitrary JS value into a `TestSet`.
31
32
  *
32
33
  * - Zero-argument functions become a `TestEntry`; the `throws` flag is set if
33
- * `throws` is already `true` or the function's `.name === 'throw'`.
34
+ * `throws` is already `true` (i.e. a `throw` key appears in the ancestor path).
35
+ * The canonical way to declare a throw-test is structural: nest it under a
36
+ * `throw` property key. The secondary `fn.name === 'throw'` check is a legacy
37
+ * path that only fires when a function is extracted from a `throw` key and
38
+ * placed under a different key; that pattern is engine-dependent (unreliable on
39
+ * Bun) and is not a supported authoring style.
34
40
  * - Non-null objects become an array of `[key, value]` pairs to recurse into.
35
41
  * - All other values (including functions with parameters) produce an empty array.
36
42
  */
@@ -129,8 +135,7 @@ const runModule = ({ result, test }) => (k, v) => (ts) => {
129
135
  return walk([], false, v)
130
136
  .step(delta => pure(mergeState(ts, delta)));
131
137
  };
132
- const { entries } = Object;
133
- const proofEntries = (moduleMap) => entries(moduleMap)
138
+ const proofEntries = (moduleMap) => definedEntries(moduleMap)
134
139
  .flatMap(([k, v]) => v.proof !== undefined ? [[k, v.proof]] : []);
135
140
  /**
136
141
  * Runs all test modules in `moduleMap` whose names pass `isTest`, accumulates
@@ -10,6 +10,7 @@ export declare const multipleFiles: () => void;
10
10
  export declare const throwByFunctionName: () => void;
11
11
  export declare const namedExports: () => void;
12
12
  export declare const defaultReporterOutput: () => void;
13
+ export declare const defaultReporterOutputLargeDuration: () => void;
13
14
  export declare const defaultReporterFailOutput: () => void;
14
15
  export declare const githubReporterOutput: () => void;
15
16
  export declare const registerSuffixes: () => void;
@@ -21,6 +22,10 @@ export declare const helpers: {
21
22
  fmtPath: () => void;
22
23
  fmtTerm: () => void;
23
24
  ghEscape: () => void;
25
+ parseTestSet: {
26
+ nullReturnsEmpty: () => void;
27
+ functionWithParamsReturnsEmpty: () => void;
28
+ };
24
29
  };
25
30
  export declare const proof: {
26
31
  flat: () => void;
@@ -35,9 +40,11 @@ export declare const proof: {
35
40
  throwByFunctionName: () => void;
36
41
  namedExports: () => void;
37
42
  defaultReporterOutput: () => void;
43
+ defaultReporterOutputLargeDuration: () => void;
38
44
  defaultReporterFailOutput: () => void;
39
45
  githubReporterOutput: () => void;
40
46
  registerSuffixes: () => void;
47
+ defaultReporterExpectedToThrow: () => void;
41
48
  helpers: {
42
49
  isInteger: () => void;
43
50
  isIdentifier: () => void;
@@ -46,5 +53,9 @@ export declare const proof: {
46
53
  fmtPath: () => void;
47
54
  fmtTerm: () => void;
48
55
  ghEscape: () => void;
56
+ parseTestSet: {
57
+ nullReturnsEmpty: () => void;
58
+ functionWithParamsReturnsEmpty: () => void;
59
+ };
49
60
  };
50
61
  };
@@ -1,23 +1,23 @@
1
1
  import { pure } from "../effects/module.f.js";
2
+ import { log } from "../effects/node/module.f.js";
2
3
  import { emptyState } from "../effects/node/virtual/module.f.js";
3
4
  import { virtual } from "../effects/node/virtual/module.f.js";
4
5
  import { assert, assertEq, todo } from "../asserts/module.f.js";
5
- import { testAll, defaultReporter, fmtPath, fmtTerm, fmtImport, ghEscape, isInteger, isIdentifier, registerModule, defaultTest, } from "./module.f.js";
6
+ import { testAll, defaultReporter, fmtPath, fmtTerm, fmtImport, ghEscape, isInteger, isIdentifier, registerModule, parseTestSet, defaultTest, } from "./module.f.js";
6
7
  import { run as mockRun } from "../effects/mock/module.f.js";
7
8
  import { shouldLoad } from "../dev/module.f.js";
8
- const makeReporter = () => {
9
- const events = [];
10
- const reporter = {
11
- result: (file, path, r, _throws) => { events.push(['result', file, [...path], r]); return pure(undefined); },
12
- summary: (pass, fail, time) => { events.push(['summary', pass, fail, time]); return pure(undefined); },
13
- test: defaultTest,
14
- };
15
- return [reporter, () => events];
16
- };
9
+ const writeEvent = (event) => log(JSON.stringify(event));
10
+ const parseEvents = (stdout) => stdout === '' ? [] : stdout.trimEnd().split('\n').map(line => JSON.parse(line));
11
+ const makeReporter = () => ({
12
+ result: (file, path, r, _throws) => writeEvent(['result', file, [...path], r]),
13
+ summary: (pass, fail, time) => writeEvent(['summary', pass, fail, time]),
14
+ test: defaultTest,
15
+ });
17
16
  const noopTestContext = { test: todo };
18
17
  const options = (initCwd, github = false) => ({
19
18
  args: [],
20
19
  env: { INIT_CWD: initCwd, ...(github ? { GITHUB_ACTIONS: 'true' } : {}) },
20
+ home: '.',
21
21
  std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
22
22
  testContext: noopTestContext,
23
23
  bunTestContext: noopTestContext,
@@ -28,10 +28,10 @@ const ok0 = () => ({ result: ['ok', undefined], duration: 0 });
28
28
  const fail0 = () => ({ result: ['error', 'oops'], duration: 0 });
29
29
  const ok1 = () => ({ result: ['ok', undefined], duration: 1 });
30
30
  const run = (dir, initCwd = '.') => {
31
- const [reporter, getEvents] = makeReporter();
31
+ const reporter = makeReporter();
32
32
  const state = { ...emptyState, root: dir };
33
- const [, exitCode] = virtual(state)(testAll(reporter)(options(initCwd)));
34
- return [getEvents(), exitCode];
33
+ const [finalState, exitCode] = virtual(state)(testAll(reporter)(options(initCwd)));
34
+ return [parseEvents(finalState.stdout), exitCode];
35
35
  };
36
36
  // Runs the real `defaultReporter` and returns its captured stdout/stderr so the
37
37
  // terminal and GitHub output formats can be asserted directly.
@@ -199,6 +199,16 @@ export const defaultReporterOutput = () => {
199
199
  + 'Number of tests: pass: 1, fail: 0, total: 1\n'
200
200
  + 'Time: 0.0000 ms\n');
201
201
  };
202
+ // timeFormat with duration >= 1ms covers the `yl <= 0` branch (no leading zeros needed)
203
+ export const defaultReporterOutputLargeDuration = () => {
204
+ const [stdout, , exit] = runMain({
205
+ 'a.proof.f.ts': () => ({ proof: { x: ok1 } }),
206
+ });
207
+ assertEq(exit, 0);
208
+ assertEq(stdout, 'import("./a.proof.f.ts").proof.x(): ok, 1.0000 ms\n'
209
+ + 'Number of tests: pass: 1, fail: 0, total: 1\n'
210
+ + 'Time: 1.0000 ms\n');
211
+ };
202
212
  // a failure on the non-GitHub reporter writes the error to stderr, not stdout
203
213
  export const defaultReporterFailOutput = () => {
204
214
  const [, stderr, exit] = runMain({
@@ -222,8 +232,8 @@ export const registerSuffixes = () => {
222
232
  let runner;
223
233
  const noopCtx = { test: (_n, _o, _f) => Promise.resolve() };
224
234
  const makeRunner = () => mockRun({
225
- test: (s, _ctx, name, _xf, _fn) => [[...s, name], undefined],
226
- all: (s, ...effects) => {
235
+ test: (_ctx, name, _xf, _fn) => (s) => [[...s, name], undefined],
236
+ all: (...effects) => (s) => {
227
237
  let st = s;
228
238
  const rs = [];
229
239
  for (const e of effects) {
@@ -233,7 +243,7 @@ export const registerSuffixes = () => {
233
243
  }
234
244
  return [st, rs];
235
245
  },
236
- await: (s, p) => [s, [p]],
246
+ await: p => (s) => [s, [p]],
237
247
  });
238
248
  runner = makeRunner();
239
249
  const proof = {
@@ -309,6 +319,8 @@ export const helpers = {
309
319
  assertEq(fmtTerm(['math', 'add']), '| | add');
310
320
  assertEq(fmtTerm(['a', '0']), '| | 0');
311
321
  assertEq(fmtTerm(['x', 'hello world']), '| | "hello world"');
322
+ // null marks a function-call boundary; fmtTerm filters it out
323
+ assertEq(fmtTerm(['outer', null, 'inner']), '| | inner');
312
324
  },
313
325
  ghEscape: () => {
314
326
  assertEq(ghEscape('a%b'), 'a%25b');
@@ -317,6 +329,28 @@ export const helpers = {
317
329
  assertEq(ghEscape('a\r\nb'), 'a%0D%0Ab');
318
330
  assertEq(ghEscape('a%b:c,d'), 'a%25b%3Ac%2Cd');
319
331
  },
332
+ parseTestSet: {
333
+ nullReturnsEmpty: () => {
334
+ const result = parseTestSet(false, null);
335
+ assertEq(Array.isArray(result), true);
336
+ assertEq(result.length, 0);
337
+ },
338
+ functionWithParamsReturnsEmpty: () => {
339
+ const result = parseTestSet(false, (_x) => _x);
340
+ assertEq(Array.isArray(result), true);
341
+ assertEq(result.length, 0);
342
+ },
343
+ },
344
+ };
345
+ // a passing throw-test emits '# EXPECTED TO THROW' in its output line
346
+ const defaultReporterExpectedToThrow = () => {
347
+ // fail0 returns a SandboxResult indicating an error; in a throw context
348
+ // defaultTest inverts it to ok, so defaultReporter.result sees s==='ok' and throws===true
349
+ const [stdout, , exit] = runMain({
350
+ 'a.proof.f.ts': () => ({ proof: { throw: { x: fail0 } } }),
351
+ });
352
+ assertEq(exit, 0);
353
+ assert(stdout.includes('# EXPECTED TO THROW'), stdout);
320
354
  };
321
355
  export const proof = {
322
356
  flat,
@@ -331,8 +365,10 @@ export const proof = {
331
365
  throwByFunctionName,
332
366
  namedExports,
333
367
  defaultReporterOutput,
368
+ defaultReporterOutputLargeDuration,
334
369
  defaultReporterFailOutput,
335
370
  githubReporterOutput,
336
371
  registerSuffixes,
372
+ defaultReporterExpectedToThrow,
337
373
  helpers
338
374
  };
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { compile } from "../djs/module.f.js";
7
7
  import { main as testMain } from "../emergent_testing/module.f.js";
8
- import { main as casMain } from "../cas/module.f.js";
8
+ import { commands as casCommands } from "../cas/module.f.js";
9
9
  import { main as ciMain } from "../ci/module.f.js";
10
10
  import { import_ } from "../effects/node/module.f.js";
11
11
  import { dispatch } from "../cli/module.f.js";
@@ -23,7 +23,7 @@ const commands = [
23
23
  {
24
24
  names: ['cas', 's'],
25
25
  description: 'Content-addressable storage operations',
26
- handler: casMain,
26
+ handler: casCommands,
27
27
  },
28
28
  {
29
29
  names: ['ci', 'i'],
@@ -0,0 +1,8 @@
1
+ export declare const proof: {
2
+ help: () => void;
3
+ compileRequiresArgs: () => void;
4
+ runModule: () => void;
5
+ throw: {
6
+ runImportError: () => void;
7
+ };
8
+ };
@@ -0,0 +1,39 @@
1
+ import { assert, assertEq } from "../asserts/module.f.js";
2
+ import { pure } from "../effects/module.f.js";
3
+ import { emptyState, virtual } from "../effects/node/virtual/module.f.js";
4
+ import { main } from "./module.f.js";
5
+ const makeOptions = (args) => ({
6
+ args,
7
+ env: {},
8
+ home: '.',
9
+ std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
10
+ testContext: { test: async () => { } },
11
+ bunTestContext: { test: async () => { } },
12
+ playwrightTestContext: { test: async () => { } },
13
+ engine: 'node',
14
+ });
15
+ const run = (root) => (args) => virtual({ ...emptyState, root })(main(makeOptions(args)));
16
+ const appMain = ({ args }) => pure(args.length);
17
+ export const proof = {
18
+ help: () => {
19
+ const [state, code] = run({})(['help']);
20
+ assertEq(code, 0);
21
+ assert(state.stdout.includes('compile'), 'expected command list in stdout');
22
+ },
23
+ compileRequiresArgs: () => {
24
+ const [state, code] = run({})(['compile']);
25
+ assertEq(code, 1);
26
+ assert(state.stderr.length !== 0, 'expected error in stderr');
27
+ },
28
+ runModule: () => {
29
+ const root = { 'app.f.ts': () => ({ main: appMain }) };
30
+ const [, code] = run(root)(['run', 'app.f.ts', 'x', 'y']);
31
+ // `run` strips the command and file name, so `main` sees two arguments
32
+ assertEq(code, 2);
33
+ },
34
+ throw: {
35
+ runImportError: () => {
36
+ run({})(['run', 'missing.f.ts']);
37
+ },
38
+ },
39
+ };
@@ -2,4 +2,9 @@ type Result = readonly [readonly string[], ToResult];
2
2
  type ToResult = (codePoint: number) => Result;
3
3
  export declare const terminal = -1;
4
4
  export declare const init: ToResult;
5
+ export declare const proof: {
6
+ throw: {
7
+ unionConflict: () => void;
8
+ };
9
+ };
5
10
  export {};