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
@@ -2,8 +2,7 @@ import { transpile } from "./transpiler/module.f.js";
2
2
  import { stringify, stringifyAsTree } from "./serializer/module.f.js";
3
3
  import { sort } from "../types/object/module.f.js";
4
4
  import { pure } from "../effects/module.f.js";
5
- import { writeFile, error, } from "../effects/node/module.f.js";
6
- import { utf8 } from "../text/module.f.js";
5
+ import { writeUtf8File, error, } from "../effects/node/module.f.js";
7
6
  export const compile = args => {
8
7
  if (args.length < 2) {
9
8
  return error('Error: Requires 2 or more arguments')
@@ -20,7 +19,7 @@ export const compile = args => {
20
19
  const content = outputFileName.endsWith('.json')
21
20
  ? stringifyAsTree(sort)(result[1])
22
21
  : stringify(sort)(result[1]);
23
- return writeFile(outputFileName, utf8(content))
22
+ return writeUtf8File(outputFileName, content)
24
23
  .step(() => pure(0));
25
24
  });
26
25
  };
@@ -7,10 +7,20 @@ import { serialize as bigintSerialize } from "../../types/bigint/module.f.js";
7
7
  import { objectWrap, arrayWrap, stringSerialize, numberSerialize, nullSerialize, boolSerialize } from "../../json/serializer/module.f.js";
8
8
  const colon = [':'];
9
9
  export const undefinedSerialize = ['undefined'];
10
+ /**
11
+ * Returns the value's `RefCounter` only if it is *shared* (referenced more
12
+ * than once) — otherwise `undefined`. Names the single predicate that drives
13
+ * const hoisting in both `getConstants` (decide which values become consts)
14
+ * and `serializeWithConst` (emit a `c<N>` reference to one).
15
+ */
16
+ const sharedRef = (refs) => (v) => {
17
+ const rc = refs.get(v);
18
+ return rc !== undefined && rc[1] > 1 ? rc : undefined;
19
+ };
10
20
  const getConstants = refs => {
21
+ const shared = sharedRef(refs);
11
22
  const checkSelf = djs => state => {
12
- const refCounter = refs.get(djs);
13
- if (refCounter !== undefined && refCounter[1] > 1 && !state.added.has(djs)) {
23
+ if (shared(djs) !== undefined && !state.added.has(djs)) {
14
24
  return {
15
25
  added: new Set([...state.added, djs]),
16
26
  consts: { head: state.consts, tail: [djs] }
@@ -95,16 +105,19 @@ const buildSerialize = refLookup => sort => {
95
105
  return f;
96
106
  };
97
107
  export const serializeWithoutConst = buildSerialize(noRef);
98
- const serializeWithConst = sort => refs => root => buildSerialize(value => {
99
- if (value === root) {
108
+ const serializeWithConst = sort => refs => {
109
+ const shared = sharedRef(refs);
110
+ return root => buildSerialize(value => {
111
+ if (value === root) {
112
+ return null;
113
+ }
114
+ const rc = shared(value);
115
+ if (rc !== undefined) {
116
+ return [`c${rc[0]}`];
117
+ }
100
118
  return null;
101
- }
102
- const refCounter = refs.get(value);
103
- if (refCounter !== undefined && refCounter[1] > 1) {
104
- return [`c${refCounter[0]}`];
105
- }
106
- return null;
107
- })(sort);
119
+ })(sort);
120
+ };
108
121
  const countRefsOp = djs => refs => {
109
122
  switch (typeof djs) {
110
123
  case 'boolean':
@@ -13,8 +13,7 @@ import { concat as pathConcat } from "../../path/module.f.js";
13
13
  import { parseFromTokens } from "../parser/module.f.js";
14
14
  import { run } from "../ast/module.f.js";
15
15
  import { foldStep, pure } from "../../effects/module.f.js";
16
- import { readFile } from "../../effects/node/module.f.js";
17
- import { utf8ToString } from "../../text/module.f.js";
16
+ import { readUtf8File } from "../../effects/node/module.f.js";
18
17
  const mapDjs = context => path => {
19
18
  const res = at(path)(context.complete);
20
19
  if (res === null) {
@@ -22,11 +21,11 @@ const mapDjs = context => path => {
22
21
  }
23
22
  return res.djs;
24
23
  };
25
- const parseModule = path => readFile(path).step(result => {
24
+ const parseModule = path => readUtf8File(path).step(result => {
26
25
  if (result[0] === 'error') {
27
26
  return pure(error({ message: 'file not found', metadata: null }));
28
27
  }
29
- const tokens = tokenize(stringToList(utf8ToString(result[1])))(path);
28
+ const tokens = tokenize(stringToList(result[1]))(path);
30
29
  return pure(parseFromTokens(tokens));
31
30
  });
32
31
  const transpileWithImports = path => parseModuleResult => context => {
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Typed key-value memory effects for state that persists across effect steps.
3
+ *
4
+ * A memory key is opaque at runtime and carries the value type at compile time,
5
+ * so a key returned by {@link create} can only be read and written with values
6
+ * of the same type. The concrete interpreter owns the actual storage and key
7
+ * generation strategy.
8
+ *
9
+ * Memory effects compose with other effects by unioning operation types, for
10
+ * example `Effect<IoOp | MemOp, T>` for a program that performs both I/O and
11
+ * memory operations.
12
+ *
13
+ * @module
14
+ */
15
+ import type { Phantom } from '../../types/phantom/module.f.ts';
16
+ import { type Nominal } from '../../types/nominal/module.f.ts';
17
+ import { type Effect } from '../module.f.ts';
18
+ /** Nominal brand version for memory keys. */
19
+ type MemKeyHash = '3f114fa6036a8da026b827f0c3e6d901f5e81ad9a320e431ccce31451892d286';
20
+ /** Opaque handle for a value stored by the memory interpreter. */
21
+ export type Key<T> = Phantom<Nominal<'MemKey', MemKeyHash, string>, T>;
22
+ export declare const asBase: (n: Nominal<"MemKey", "3f114fa6036a8da026b827f0c3e6d901f5e81ad9a320e431ccce31451892d286", string>) => string;
23
+ export declare const asNominal: (b: string) => Nominal<"MemKey", "3f114fa6036a8da026b827f0c3e6d901f5e81ad9a320e431ccce31451892d286", string>;
24
+ /** Allocates a fresh memory slot and initializes it with `value`. */
25
+ export type MemCreate = readonly ['memCreate', <T>(value: T) => Key<T>];
26
+ /** Reads the current value stored at `key`. */
27
+ export type MemRead = readonly ['memRead', <T>(key: Key<T>) => T];
28
+ /** Replaces the current value stored at `key`. */
29
+ export type MemWrite = readonly ['memWrite', <T>(key: Key<T>, value: T) => void];
30
+ export type MemOp = MemCreate | MemRead | MemWrite;
31
+ /** Creates a new typed memory slot with `value` as its initial contents. */
32
+ export declare const create: <T>(value: T) => Effect<MemCreate, Key<T>>;
33
+ /** Reads the current contents of a typed memory slot. */
34
+ export declare const read: <T>(ket: Key<T>) => Effect<MemRead, T>;
35
+ /** Replaces the current contents of a typed memory slot. */
36
+ export declare const write: <T>(key: Key<T>, value: T) => Effect<MemWrite, void>;
37
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Typed key-value memory effects for state that persists across effect steps.
3
+ *
4
+ * A memory key is opaque at runtime and carries the value type at compile time,
5
+ * so a key returned by {@link create} can only be read and written with values
6
+ * of the same type. The concrete interpreter owns the actual storage and key
7
+ * generation strategy.
8
+ *
9
+ * Memory effects compose with other effects by unioning operation types, for
10
+ * example `Effect<IoOp | MemOp, T>` for a program that performs both I/O and
11
+ * memory operations.
12
+ *
13
+ * @module
14
+ */
15
+ import { asBase as nominalAsBase, asNominal as nominalAsNominal } from "../../types/nominal/module.f.js";
16
+ import { do_ } from "../module.f.js";
17
+ export const asBase = (nominalAsBase);
18
+ export const asNominal = (nominalAsNominal);
19
+ /** Creates a new typed memory slot with `value` as its initial contents. */
20
+ export const create = do_('memCreate');
21
+ /** Reads the current contents of a typed memory slot. */
22
+ export const read = do_('memRead');
23
+ /** Replaces the current contents of a typed memory slot. */
24
+ export const write = do_('memWrite');
@@ -0,0 +1,6 @@
1
+ export declare const proof: {
2
+ roundTrip: () => void;
3
+ allocatesFreshKeys: () => void;
4
+ typeTest: () => void;
5
+ throw: () => void;
6
+ };
@@ -0,0 +1,64 @@
1
+ import { run } from "../mock/module.f.js";
2
+ import { pure } from "../module.f.js";
3
+ import { asBase, asNominal, create, read, write, } from "./module.f.js";
4
+ const initial = { next: 0, values: {} };
5
+ const mock = {
6
+ memCreate: value => state => {
7
+ const id = `k${state.next}`;
8
+ const key = asNominal(id);
9
+ return [{
10
+ next: state.next + 1,
11
+ values: { ...state.values, [id]: value },
12
+ }, key];
13
+ },
14
+ memRead: key => state => [state, state.values[asBase(key)]],
15
+ memWrite: (key, value) => state => {
16
+ const id = asBase(key);
17
+ if (!(id in state.values)) {
18
+ throw id;
19
+ }
20
+ return [{
21
+ ...state,
22
+ values: { ...state.values, [id]: value },
23
+ }, undefined];
24
+ },
25
+ };
26
+ const program = create(1).step(key => read(key).step(value => write(key, value + 41).step(() => read(key))));
27
+ export const proof = {
28
+ roundTrip: () => {
29
+ const [state, result] = run(mock)(initial)(program);
30
+ if (result !== 42) {
31
+ throw result;
32
+ }
33
+ if (state.values.k0 !== 42) {
34
+ throw state;
35
+ }
36
+ },
37
+ allocatesFreshKeys: () => {
38
+ const effect = create('a').step(a => create('b').step(b => pure([
39
+ asBase(a),
40
+ asBase(b),
41
+ ])));
42
+ const [state, [a, b]] = run(mock)(initial)(effect);
43
+ if (a !== 'k0') {
44
+ throw a;
45
+ }
46
+ if (b !== 'k1') {
47
+ throw b;
48
+ }
49
+ if (state.values.k0 !== 'a') {
50
+ throw state;
51
+ }
52
+ if (state.values.k1 !== 'b') {
53
+ throw state;
54
+ }
55
+ },
56
+ typeTest: () => {
57
+ // const e = create(1).step(k => write(k, 'bad').step(() => read(k)))
58
+ create(1).step(k => write(k, 5).step(() => read(k)));
59
+ },
60
+ throw: () => {
61
+ const key = asNominal('missing');
62
+ run(mock)(initial)(write(key, 1));
63
+ },
64
+ };
@@ -3,9 +3,14 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import type { Effect, Operation, Pr } from "../module.f.ts";
6
+ import { type Effect, type Operation, type Pr } from "../module.f.ts";
7
+ /**
8
+ * A synchronous, state-threading operation map. An entry takes the command's
9
+ * payload (fixed when the command is issued) and returns a state transition —
10
+ * the curried `state` parameter is data the runner supplies on each step.
11
+ */
7
12
  export type MemOperationMap<O extends Operation, S> = {
8
- readonly [K in O[0]]: (state: S, ...payload: Pr<O, K>[0]) => readonly [S, Pr<O, K>[1]];
13
+ readonly [K in O[0]]: (...payload: Pr<O, K>[0]) => (state: S) => readonly [S, Pr<O, K>[1]];
9
14
  };
10
15
  export type RunInstance<O extends Operation, S> = (state: S) => <O1 extends O, T>(effect: Effect<O1, T>) => readonly [S, T];
11
16
  export declare const run: <O extends Operation, S>(o: MemOperationMap<O, S>) => RunInstance<O, S>;
@@ -1,16 +1,20 @@
1
+ /**
2
+ * Mock effect runtimes for testing effectful programs.
3
+ *
4
+ * @module
5
+ */
6
+ import { match } from "../module.f.js";
1
7
  export const run = (o) => state => effect => {
8
+ const next = match(o);
2
9
  let s = state;
3
10
  let e = effect;
4
11
  while (true) {
5
- const { value } = e;
6
- if (value.length === 1) {
7
- const [v] = value;
8
- return [s, v];
12
+ const r = next(e);
13
+ if (r[0] === 'done') {
14
+ return [s, r[1]];
9
15
  }
10
- const [cmd, payload, cont] = value;
11
- const operation = o[cmd];
12
- const [ns, m] = operation(s, ...payload);
16
+ const [ns, m] = r[1](s);
13
17
  s = ns;
14
- e = cont(m);
18
+ e = r[2](m);
15
19
  }
16
20
  };
@@ -1,2 +1,2 @@
1
- import type { Effect, Operation, ToAsyncOperationMap } from "./module.f.ts";
1
+ import { type Effect, type Operation, type ToAsyncOperationMap } from "./module.f.ts";
2
2
  export declare const asyncRun: <O extends Operation>(map: ToAsyncOperationMap<O>) => <T, E extends Effect<O, T>>(effect: Effect<O, T>) => Promise<T>;
@@ -36,6 +36,47 @@ export declare const foldStep: <O extends Operation, T, S>(f: (item: T) => (stat
36
36
  * results. The `void` accumulator sibling of `foldStep`.
37
37
  */
38
38
  export declare const forEachStep: <O extends Operation, T>(f: (item: T) => Effect<O, void>) => (items: List<T>) => Effect<O, void>;
39
+ /**
40
+ * The decoded form of an effect's next step: either a final `result`, or a
41
+ * `command` to perform with its `payload` and the `continuation` to resume
42
+ * with the command's output.
43
+ */
44
+ export type Decoded<O extends Operation, T> = {
45
+ readonly done: true;
46
+ readonly result: T;
47
+ } | {
48
+ readonly done: false;
49
+ readonly command: Do<O, T>[0];
50
+ readonly payload: Do<O, T>[1];
51
+ readonly continuation: Do<O, T>[2];
52
+ };
53
+ /**
54
+ * Decodes an effect's next step: a pure result, or a command to perform.
55
+ *
56
+ * This is the only function that knows how `Value` is laid out (a length-1
57
+ * tuple for `Pure`, a `[command, payload, continuation]` tuple for `Do`).
58
+ * Interpreters and proofs must go through `decode` (or `match`) instead of
59
+ * inspecting the tuple, so the representation can change without touching
60
+ * them.
61
+ */
62
+ export declare const decode: <O extends Operation, T>({ value }: Effect<O, T>) => Decoded<O, T>;
63
+ /**
64
+ * An operation map whose entries take a command's payload and return some
65
+ * output `R`. Generalizes `ToAsyncOperationMap` (`R = Promise<…>`) and the
66
+ * curried `MemOperationMap` (`R = (state) => [state, …]`).
67
+ */
68
+ export type OperationMap<O extends Operation, R> = {
69
+ readonly [K in O[0]]: (...payload: Pr<O, K>[0]) => R;
70
+ };
71
+ export type MatchResult<O extends Operation, T, R> = readonly ['done', T] | readonly ['cont', R, Do<O, T>[2]];
72
+ /**
73
+ * Decodes an effect's next step and dispatches its command to `map`,
74
+ * returning either the final result or the operation's output `R` paired
75
+ * with the continuation. The one world-specific step — `await` for async
76
+ * runners, state threading for sync ones — is left to the caller, so every
77
+ * interpreter loop is this skeleton plus a single eliminator line.
78
+ */
79
+ export declare const match: <O extends Operation, R>(map: OperationMap<O, R>) => <O1 extends O, T>(effect: Effect<O1, T>) => MatchResult<O1, T, R>;
39
80
  export type ToAsyncOperationMap<O extends Operation> = {
40
81
  readonly [K in O[0]]: (...payload: Pr<O, K>[0]) => Promise<Pr<O, K>[1]>;
41
82
  };
@@ -29,3 +29,28 @@ export const foldStep = (f) => (init) => (items) => fold(item => acc => acc.step
29
29
  * results. The `void` accumulator sibling of `foldStep`.
30
30
  */
31
31
  export const forEachStep = (f) => (items) => foldStep((item) => () => f(item))(undefined)(items);
32
+ /**
33
+ * Decodes an effect's next step: a pure result, or a command to perform.
34
+ *
35
+ * This is the only function that knows how `Value` is laid out (a length-1
36
+ * tuple for `Pure`, a `[command, payload, continuation]` tuple for `Do`).
37
+ * Interpreters and proofs must go through `decode` (or `match`) instead of
38
+ * inspecting the tuple, so the representation can change without touching
39
+ * them.
40
+ */
41
+ export const decode = ({ value }) => value.length === 1
42
+ ? { done: true, result: value[0] }
43
+ : { done: false, command: value[0], payload: value[1], continuation: value[2] };
44
+ /**
45
+ * Decodes an effect's next step and dispatches its command to `map`,
46
+ * returning either the final result or the operation's output `R` paired
47
+ * with the continuation. The one world-specific step — `await` for async
48
+ * runners, state threading for sync ones — is left to the caller, so every
49
+ * interpreter loop is this skeleton plus a single eliminator line.
50
+ */
51
+ export const match = (map) => (effect) => {
52
+ const d = decode(effect);
53
+ return d.done
54
+ ? ['done', d.result]
55
+ : ['cont', map[d.command](...d.payload), d.continuation];
56
+ };
@@ -1,12 +1,11 @@
1
+ import { match } from "./module.f.js";
1
2
  export const asyncRun = (map) => async (effect) => {
3
+ const next = match(map);
2
4
  while (true) {
3
- const { value } = effect;
4
- if (value.length === 1) {
5
- return value[0];
5
+ const r = next(effect);
6
+ if (r[0] === 'done') {
7
+ return r[1];
6
8
  }
7
- const [command, payload, continuation] = value;
8
- const operation = map[command];
9
- const result = await operation(...payload);
10
- effect = continuation(result);
9
+ effect = r[2](await r[1]);
11
10
  }
12
11
  };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Node.js interpreter helpers for memory effects.
3
+ *
4
+ * @module
5
+ */
6
+ import type { Effect, ToAsyncOperationMap } from '../../module.f.ts';
7
+ import { type MemOp } from '../../memory/module.f.ts';
8
+ export type MemoryOperationMap = ToAsyncOperationMap<MemOp>;
9
+ export type Uuid = () => string;
10
+ /**
11
+ * Creates a stateful operation map backed by an immutable `Map` snapshot.
12
+ *
13
+ * Each returned operation map owns its own store. Reusing the same map across
14
+ * multiple `asyncRun` calls preserves memory across those calls; creating a new
15
+ * map starts with an empty store. Keys are generated with `crypto.randomUUID()`
16
+ * by default.
17
+ */
18
+ export declare const memoryOperationMap: (uuid?: Uuid) => MemoryOperationMap;
19
+ /** Runs a memory-only effect using a fresh memory store. */
20
+ export declare const run: <T>(effect: Effect<MemOp, T>) => Promise<T>;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Node.js interpreter helpers for memory effects.
3
+ *
4
+ * @module
5
+ */
6
+ import { randomUUID } from 'node:crypto';
7
+ import { asyncRun } from "../../module.js";
8
+ import { asBase, asNominal } from "../../memory/module.f.js";
9
+ const missingKey = (id) => new Error(`memory key not found: ${id}`);
10
+ /**
11
+ * Creates a stateful operation map backed by an immutable `Map` snapshot.
12
+ *
13
+ * Each returned operation map owns its own store. Reusing the same map across
14
+ * multiple `asyncRun` calls preserves memory across those calls; creating a new
15
+ * map starts with an empty store. Keys are generated with `crypto.randomUUID()`
16
+ * by default.
17
+ */
18
+ export const memoryOperationMap = (uuid = randomUUID) => {
19
+ const store = new Map();
20
+ return {
21
+ memCreate: async (value) => {
22
+ const id = uuid();
23
+ const key = asNominal(id);
24
+ store.set(id, value);
25
+ return key;
26
+ },
27
+ memRead: async (key) => {
28
+ const id = asBase(key);
29
+ if (!store.has(id)) {
30
+ throw missingKey(id);
31
+ }
32
+ return store.get(id);
33
+ },
34
+ memWrite: async (key, value) => {
35
+ const id = asBase(key);
36
+ if (!store.has(id)) {
37
+ throw missingKey(id);
38
+ }
39
+ store.set(id, value);
40
+ },
41
+ };
42
+ };
43
+ /** Runs a memory-only effect using a fresh memory store. */
44
+ export const run = (effect) => asyncRun(memoryOperationMap())(effect);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Node.js interpreter proofs for memory effects.
3
+ *
4
+ * @module
5
+ */
6
+ export declare const proof: {
7
+ nodeInterpreter: () => Promise<void>;
8
+ reusedOperationMapPersists: () => Promise<void>;
9
+ missingKeyThrows: () => Promise<void>;
10
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Node.js interpreter proofs for memory effects.
3
+ *
4
+ * @module
5
+ */
6
+ import { asyncRun } from "../../module.js";
7
+ import { asNominal, create, read, write, } from "../../memory/module.f.js";
8
+ import { memoryOperationMap, run } from "./module.js";
9
+ export const proof = {
10
+ nodeInterpreter: async () => {
11
+ const result = await run(create(1).step(key => write(key, 2).step(() => read(key))));
12
+ if (result !== 2) {
13
+ throw result;
14
+ }
15
+ },
16
+ reusedOperationMapPersists: async () => {
17
+ const runner = asyncRun(memoryOperationMap());
18
+ const key = await runner(create(1));
19
+ await runner(write(key, 2));
20
+ const result = await runner(read(key));
21
+ if (result !== 2) {
22
+ throw result;
23
+ }
24
+ },
25
+ missingKeyThrows: async () => {
26
+ const key = asNominal('missing');
27
+ const result = await run(read(key)).then(() => undefined, error => error);
28
+ if (!(result instanceof Error)) {
29
+ throw result;
30
+ }
31
+ if (result.message !== 'memory key not found: missing') {
32
+ throw result;
33
+ }
34
+ },
35
+ };
@@ -1,6 +1,6 @@
1
1
  import { type NodeProgram } from './module.f.ts';
2
2
  /**
3
- * Runs a `NodeProgram` against the real Node `io` and process arguments,
3
+ * Runs a `NodeProgram` against the real Node globals and process arguments,
4
4
  * resolving to its exit code **without** terminating the process.
5
5
  *
6
6
  * Use this when the caller must stay alive afterwards — e.g. when proofs are
@@ -1,8 +1,19 @@
1
1
  import type { Vec } from '../../types/bit_vec/module.f.ts';
2
+ import type { MemOp } from '../memory/module.f.ts';
2
3
  import type { Nominal } from '../../types/nominal/module.f.ts';
3
- import type { Result } from '../../types/result/module.f.ts';
4
+ import { type Result } from '../../types/result/module.f.ts';
5
+ import type { StringMap } from '../../types/object/module.f.ts';
4
6
  import { type Effect, type Func, type Operation, type ToAsyncOperationMap } from '../module.f.ts';
5
7
  export type IoResult<T> = Result<T, unknown>;
8
+ /**
9
+ * True if `e` is a "file or directory does not exist" (`ENOENT`) error.
10
+ *
11
+ * Node's filesystem rejections are `Error`s carrying `code: 'ENOENT'`; the
12
+ * virtual interpreter mirrors that shape for absent paths. Lets callers swallow
13
+ * only the missing-path case (e.g. a fresh store) while propagating genuine
14
+ * failures (permissions, corruption) rather than masking them.
15
+ */
16
+ export declare const isNotFound: (e: unknown) => boolean;
6
17
  export type All = ['all', <T>(...effects: Effect<never, T>[]) => readonly T[]];
7
18
  /**
8
19
  * To run the operation `O` should be known by the runner/engine.
@@ -22,6 +33,14 @@ export type Mkdir = readonly ['mkdir', (path: string, options?: MakeDirectoryOpt
22
33
  export declare const mkdir: Func<Mkdir>;
23
34
  export type ReadFile = readonly ['readFile', (path: string) => IoResult<Vec>];
24
35
  export declare const readFile: Func<ReadFile>;
36
+ /**
37
+ * Reads a file as UTF-8 text.
38
+ *
39
+ * Preserves the `IoResult` instead of unwrapping so callers can pattern-match
40
+ * on errors (e.g. convert them into domain-specific errors) or `unwrap` at the
41
+ * call site.
42
+ */
43
+ export declare const readUtf8File: (path: string) => Effect<ReadFile, IoResult<string>>;
25
44
  /**
26
45
  * Represents a directory entry (file or directory) in the filesystem
27
46
  * @see https://nodejs.org/api/fs.html#class-fsdirent
@@ -38,6 +57,8 @@ export type Readdir = readonly ['readdir', (path: string, options: ReaddirOption
38
57
  export declare const readdir: Func<Readdir>;
39
58
  export type WriteFile = readonly ['writeFile', (path: string, data: Vec) => IoResult<void>];
40
59
  export declare const writeFile: Func<WriteFile>;
60
+ /** Writes a string to `path` as UTF-8 bytes. */
61
+ export declare const writeUtf8File: (path: string, content: string) => Effect<WriteFile, IoResult<void>>;
41
62
  export type Rm = readonly ['rm', (path: string) => IoResult<void>];
42
63
  export declare const rm: Func<Rm>;
43
64
  export type ExecResult = {
@@ -50,9 +71,7 @@ export type Access = readonly ['access', (path: string) => IoResult<void>];
50
71
  export declare const access: Func<Access>;
51
72
  export type Fs = Mkdir | ReadFile | Readdir | WriteFile | Rm | Exec | Access;
52
73
  export type Server = Nominal<'server', `160855c4f69310fece3273c1853ac32de43dee1eb41bf59d821917f8eebe9272`, unknown>;
53
- export type Headers = {
54
- readonly [k in string]: string;
55
- };
74
+ export type Headers = StringMap<string, string>;
56
75
  export type IncomingMessage = {
57
76
  readonly method: string;
58
77
  readonly url: string;
@@ -72,9 +91,7 @@ export declare const listen: Func<Listen>;
72
91
  export type Http = CreateServer | Listen;
73
92
  export type Forever = ['forever', () => never];
74
93
  export declare const forever: Func<Forever>;
75
- export type Module = {
76
- readonly [k in string]: unknown;
77
- };
94
+ export type Module = StringMap<string, unknown>;
78
95
  export type Import = ['import', (path: string) => IoResult<Module>];
79
96
  export declare const import_: Func<Import>;
80
97
  /** Named output streams accepted by the `Write` effect. */
@@ -92,6 +109,34 @@ export type Console = (s: string) => Effect<Write, void>;
92
109
  export declare const log: Console;
93
110
  /** Writes a line to `stderr`. Replaces the retired `Error` effect. */
94
111
  export declare const error: Console;
112
+ /** Named input streams accepted by the `Read` effect. */
113
+ export type ReadConsoles = 'stdin';
114
+ /**
115
+ * Reads a single byte from a named input stream — the byte-granular dual of
116
+ * {@link write}. Resolves to the byte value (`0`–`255`) or `null` at end of
117
+ * input (EOF). One byte at a time: the effect carries no buffering or line
118
+ * policy, so higher-level framing (see {@link readLine}) lives in pure code
119
+ * rather than the interpreter. Back-pressure is naturally sequential — the next
120
+ * `read` is only issued once the previous byte is consumed.
121
+ */
122
+ export type Read = readonly ['read', (stream: ReadConsoles) => number | null];
123
+ /** Emits a `Read` effect, yielding the next input byte or `null` at EOF. */
124
+ export declare const read: Func<Read>;
125
+ /**
126
+ * Reads one line from `stream` as a pure combinator over the byte-level
127
+ * {@link read}: accumulates bytes until a `\n` terminator or EOF, then
128
+ * UTF-8-decodes them. The terminator is consumed but excluded from the result.
129
+ *
130
+ * Reading a single byte per step means a line never over-reads past its
131
+ * terminator, so no leftover-byte buffer has to survive between calls — each
132
+ * `readLine` is self-contained. Yields `null` only at EOF with nothing
133
+ * buffered; a final line lacking a trailing newline is returned in full.
134
+ *
135
+ * Bytes accumulate into a cons-list by prepending (O(1) per byte) and are
136
+ * reversed and decoded once at the terminator, so a large line costs O(n)
137
+ * rather than the O(n²) of copying a growing array on every byte.
138
+ */
139
+ export declare const readLine: (stream: ReadConsoles) => Effect<Read, string | null>;
95
140
  export type Now = readonly ['now', () => number];
96
141
  export declare const now: Func<Now>;
97
142
  /**
@@ -159,7 +204,7 @@ export type TestContext = {
159
204
  /** Effect operation that registers a named test with the active `TestContext`. */
160
205
  export type Test = readonly ['test', (ctx: TestContext, name: string, expectFailure: boolean, test: (t: TestContext) => Effect<Test | All | Await, void>) => void];
161
206
  export declare const test: Func<Test>;
162
- export type NodeOp = All | Await | Fetch | Fs | Http | Forever | Import | Now | Sandbox | Write | Test;
207
+ export type NodeOp = All | Await | Fetch | Fs | Http | Forever | Import | MemOp | Now | Read | Sandbox | Write | Test;
163
208
  export type NodeEffect<T> = Effect<NodeOp, T>;
164
209
  /**
165
210
  * Writes an error line to `stderr` and yields exit code `1`. The canonical
@@ -192,6 +237,7 @@ export type Engine = 'node' | 'bun' | 'playwright';
192
237
  export type NodeProgramOptions = {
193
238
  readonly args: readonly string[];
194
239
  readonly env: Env;
240
+ readonly home: string;
195
241
  readonly std: {
196
242
  readonly [k in WriteConsoles]: {
197
243
  readonly isTTY: boolean;