functionalscript 0.38.0 → 0.40.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 (137) hide show
  1. package/README.md +1 -1
  2. package/fjs/bnf/descent/module.f.d.ts +43 -2
  3. package/fjs/bnf/descent/module.f.js +37 -12
  4. package/fjs/bnf/descent/proof.f.d.ts +1 -0
  5. package/fjs/bnf/descent/proof.f.js +73 -32
  6. package/fjs/bnf/ll1/module.f.js +2 -2
  7. package/fjs/bnf/token_symbol/module.f.d.ts +38 -0
  8. package/fjs/bnf/token_symbol/module.f.js +48 -0
  9. package/fjs/bnf/token_symbol/proof.f.d.ts +10 -0
  10. package/fjs/bnf/token_symbol/proof.f.js +37 -0
  11. package/fjs/cas/cli/module.f.js +6 -9
  12. package/fjs/cas/evo/module.f.d.ts +77 -13
  13. package/fjs/cas/evo/module.f.js +130 -18
  14. package/fjs/cas/evo/proof.f.d.ts +7 -0
  15. package/fjs/cas/evo/proof.f.js +120 -1
  16. package/fjs/cas/module.f.js +64 -37
  17. package/fjs/cas/proof.f.d.ts +3 -9
  18. package/fjs/cas/proof.f.js +94 -74
  19. package/fjs/ci/bun/module.f.js +3 -3
  20. package/fjs/ci/common/module.f.d.ts +1 -2
  21. package/fjs/ci/common/module.f.js +1 -5
  22. package/fjs/ci/config/module.f.d.ts +11 -8
  23. package/fjs/ci/config/module.f.js +24 -9
  24. package/fjs/ci/deno/module.f.js +3 -3
  25. package/fjs/ci/module.f.js +13 -7
  26. package/fjs/ci/nix/module.f.d.ts +61 -0
  27. package/fjs/ci/nix/module.f.js +92 -0
  28. package/fjs/ci/nix/proof.f.d.ts +23 -0
  29. package/fjs/ci/nix/proof.f.js +109 -0
  30. package/fjs/ci/node/module.f.d.ts +23 -1
  31. package/fjs/ci/node/module.f.js +58 -12
  32. package/fjs/ci/proof.f.d.ts +2 -0
  33. package/fjs/ci/proof.f.js +49 -9
  34. package/fjs/cli/module.f.js +2 -2
  35. package/fjs/common/monoid/module.f.d.ts +46 -2
  36. package/fjs/common/monoid/module.f.js +44 -0
  37. package/fjs/common/monoid/proof.f.d.ts +5 -0
  38. package/fjs/common/monoid/proof.f.js +27 -1
  39. package/fjs/dev/module.f.d.ts +1 -0
  40. package/fjs/dev/module.f.js +38 -27
  41. package/fjs/dev/update/module.f.d.ts +11 -0
  42. package/fjs/dev/update/module.f.js +20 -0
  43. package/fjs/dev/update/proof.f.d.ts +7 -0
  44. package/fjs/dev/update/proof.f.js +35 -0
  45. package/fjs/djs/ast/module.f.d.ts +47 -0
  46. package/fjs/djs/ast/module.f.js +9 -0
  47. package/fjs/djs/module.f.js +5 -8
  48. package/fjs/djs/parser/module.f.d.ts +3 -0
  49. package/fjs/djs/parser/module.f.js +17 -0
  50. package/fjs/djs/tokenizer/module.f.js +2 -2
  51. package/fjs/djs/tokenizer/proof.f.d.ts +1 -0
  52. package/fjs/djs/tokenizer/proof.f.js +70 -16
  53. package/fjs/djs/transpiler/module.f.js +6 -6
  54. package/fjs/effects/eff/module.f.d.ts +72 -0
  55. package/fjs/effects/eff/module.f.js +46 -0
  56. package/fjs/effects/eff/proof.f.d.ts +11 -0
  57. package/fjs/effects/eff/proof.f.js +58 -0
  58. package/fjs/effects/list/module.f.d.ts +11 -5
  59. package/fjs/effects/list/module.f.js +2 -1
  60. package/fjs/effects/memory/proof.f.js +7 -8
  61. package/fjs/effects/module.f.d.ts +334 -52
  62. package/fjs/effects/module.f.js +253 -45
  63. package/fjs/effects/node/memory/proof.js +3 -2
  64. package/fjs/effects/node/module.d.ts +3 -3
  65. package/fjs/effects/node/module.f.d.ts +18 -8
  66. package/fjs/effects/node/module.f.js +31 -11
  67. package/fjs/effects/node/module.js +12 -12
  68. package/fjs/effects/node/proof.f.d.ts +1 -0
  69. package/fjs/effects/node/proof.f.js +32 -11
  70. package/fjs/effects/node/virtual/module.f.js +1 -1
  71. package/fjs/effects/proof.f.d.ts +32 -6
  72. package/fjs/effects/proof.f.js +131 -39
  73. package/fjs/emergent_testing/all.test.js +2 -1
  74. package/fjs/emergent_testing/module.f.d.ts +3 -3
  75. package/fjs/emergent_testing/module.f.js +63 -49
  76. package/fjs/emergent_testing/proof.f.d.ts +4 -0
  77. package/fjs/emergent_testing/proof.f.js +54 -21
  78. package/fjs/emergent_testing/scenarios/thenable.pass.js +1 -1
  79. package/fjs/fsc/module.f.js +4 -4
  80. package/fjs/fsm/module.f.js +1 -1
  81. package/fjs/js/tokenizer/module.f.d.ts +1 -0
  82. package/fjs/js/tokenizer/module.f.js +13 -6
  83. package/fjs/{cas/mcp → mcp/cas}/module.f.d.ts +3 -25
  84. package/fjs/{cas/mcp → mcp/cas}/module.f.js +26 -64
  85. package/fjs/mcp/evo/module.f.d.ts +27 -0
  86. package/fjs/mcp/evo/module.f.js +94 -0
  87. package/fjs/{cas/evo/mcp → mcp/evo}/proof.f.d.ts +2 -0
  88. package/fjs/{cas/evo/mcp → mcp/evo}/proof.f.js +40 -9
  89. package/fjs/mcp/module.f.d.ts +54 -237
  90. package/fjs/mcp/module.f.js +55 -258
  91. package/fjs/mcp/proof.f.d.ts +43 -32
  92. package/fjs/mcp/proof.f.js +509 -200
  93. package/fjs/media/nix/module.f.d.ts +30 -0
  94. package/fjs/media/nix/module.f.js +166 -0
  95. package/fjs/media/nix/proof.f.d.ts +32 -0
  96. package/fjs/media/nix/proof.f.js +127 -0
  97. package/fjs/media/type/module.f.js +2 -2
  98. package/fjs/media/type/proof.f.js +7 -10
  99. package/fjs/module.f.js +5 -9
  100. package/fjs/protocol/json_rpc/module.f.d.ts +114 -0
  101. package/fjs/{media/json/rpc → protocol/json_rpc}/module.f.js +3 -3
  102. package/fjs/{media/json/rpc → protocol/json_rpc}/proof.f.js +3 -3
  103. package/fjs/protocol/mcp/module.f.d.ts +239 -0
  104. package/fjs/protocol/mcp/module.f.js +272 -0
  105. package/fjs/protocol/mcp/proof.f.d.ts +34 -0
  106. package/fjs/protocol/mcp/proof.f.js +208 -0
  107. package/fjs/{mcp → protocol/mcp}/stdio/module.f.d.ts +7 -7
  108. package/fjs/{mcp → protocol/mcp}/stdio/module.f.js +22 -22
  109. package/fjs/{mcp → protocol/mcp}/stdio/proof.f.js +9 -9
  110. package/fjs/types/bigint/module.f.js +4 -3
  111. package/fjs/types/bit_vec/module.f.js +5 -15
  112. package/fjs/types/btree/remove/module.f.d.ts +5 -0
  113. package/fjs/types/btree/remove/module.f.js +12 -0
  114. package/fjs/types/list/module.f.d.ts +21 -0
  115. package/fjs/types/list/module.f.js +17 -0
  116. package/fjs/types/list/proof.f.d.ts +1 -0
  117. package/fjs/types/list/proof.f.js +24 -1
  118. package/fjs/types/number/module.f.js +2 -1
  119. package/fjs/types/range_map/module.f.d.ts +14 -13
  120. package/fjs/types/range_map/module.f.js +18 -13
  121. package/fjs/types/range_map/proof.f.js +26 -39
  122. package/fjs/types/range_set/module.f.d.ts +5 -0
  123. package/fjs/types/range_set/module.f.js +16 -0
  124. package/fjs/types/range_set/proof.f.d.ts +1 -0
  125. package/fjs/types/range_set/proof.f.js +19 -0
  126. package/fjs/types/string/module.f.js +10 -2
  127. package/fjs/website/module.f.js +2 -3
  128. package/package.json +5 -5
  129. package/fjs/cas/evo/mcp/module.f.d.ts +0 -19
  130. package/fjs/cas/evo/mcp/module.f.js +0 -56
  131. package/fjs/cas/mcp/proof.f.d.ts +0 -45
  132. package/fjs/cas/mcp/proof.f.js +0 -545
  133. package/fjs/ci/playwright/module.f.d.ts +0 -2
  134. package/fjs/ci/playwright/module.f.js +0 -25
  135. package/fjs/media/json/rpc/module.f.d.ts +0 -114
  136. /package/fjs/{media/json/rpc → protocol/json_rpc}/proof.f.d.ts +0 -0
  137. /package/fjs/{mcp → protocol/mcp}/stdio/proof.f.d.ts +0 -0
@@ -0,0 +1,208 @@
1
+ import { assert, assertEq } from '../../asserts/module.f.js';
2
+ import { pure, step } from '../../effects/module.f.js';
3
+ import { eff } from '../../effects/eff/module.f.js';
4
+ import { run } from '../../effects/mock/module.f.js';
5
+ import { asBase, asNominal, create, read } from '../../effects/memory/module.f.js';
6
+ import { uninitializedState, mcpStep, notInitialized, } from './module.f.js';
7
+ const initial = { next: 0, values: {} };
8
+ const mock = {
9
+ memCreate: value => state => {
10
+ const id = `k${state.next}`;
11
+ const key = asNominal(id);
12
+ return [{ next: state.next + 1, values: { ...state.values, [id]: value } }, key];
13
+ },
14
+ memRead: key => state => [state, state.values[asBase(key)]],
15
+ memWrite: (key, value) => state => {
16
+ const id = asBase(key);
17
+ return [{ ...state, values: { ...state.values, [id]: value } }, undefined];
18
+ },
19
+ };
20
+ // ── Fixtures ──────────────────────────────────────────────────────────────────
21
+ const config = {
22
+ serverInfo: { name: 'test-server', version: '0.1.0' },
23
+ capabilities: { tools: {} },
24
+ protocolVersion: '2024-11-05',
25
+ };
26
+ const configNoTools = { ...config, capabilities: {} };
27
+ const handlers = {
28
+ // Echoes a received cursor as `nextCursor` so tests can observe pagination params.
29
+ toolsList: (p) => pure(p.cursor === undefined
30
+ ? { tools: [{ name: 'greet', inputSchema: {} }] }
31
+ : { tools: [], nextCursor: p.cursor }),
32
+ toolsCall: (_p) => pure({ content: [{ type: 'text', text: 'hello' }] }),
33
+ };
34
+ // Run a memory effect against the mock, return the result.
35
+ const runMem = (effect) => run(mock)(initial)(effect)[1];
36
+ // TypeScript infers O = Operation (the upper bound) rather than O = never when
37
+ // O flows through McpHandlers<never>, so we cast the widened type down to MemOp.
38
+ const asMemEffect = (e) => e;
39
+ // Run one step from uninitializedState, return [response, newState].
40
+ const step1 = (cfg) => (msg) => runMem(asMemEffect(step(create(uninitializedState), key => step(mcpStep(cfg)(handlers)(key)(msg), resp => step(read(key), state => pure([resp, state]))))));
41
+ // Run initialize then a second step, return [response, newState] of the second.
42
+ const step2 = (cfg) => (msg1) => (msg2) => runMem(asMemEffect(eff(create(uninitializedState)).step(key => eff(mcpStep(cfg)(handlers)(key)(msg1)).step(() => eff(mcpStep(cfg)(handlers)(key)(msg2)).step(resp => step(read(key), state => pure([resp, state]))).value).value).value));
43
+ // Run initialize, notifications/initialized, then a third step; return [response, newState] of the third.
44
+ const step3 = (cfg) => (msg1) => (msg2) => (msg3) => runMem(asMemEffect(eff(create(uninitializedState)).step(key => eff(mcpStep(cfg)(handlers)(key)(msg1)).step(() => eff(mcpStep(cfg)(handlers)(key)(msg2)).step(() => eff(mcpStep(cfg)(handlers)(key)(msg3)).step(resp => eff(read(key)).step(state => pure([resp, state])).value).value).value).value).value));
45
+ // ── Test messages ─────────────────────────────────────────────────────────────
46
+ const initMsg = { jsonrpc: '2.0', method: 'initialize', id: 1,
47
+ params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'client', version: '0.0.1' } } };
48
+ const initNotif = { jsonrpc: '2.0', method: 'notifications/initialized' };
49
+ // ── Tests ─────────────────────────────────────────────────────────────────────
50
+ export const proof = {
51
+ lifecycle: {
52
+ initialStateIsUninitialized: () => {
53
+ assert(uninitializedState[0] === 'uninitialized');
54
+ },
55
+ initializeTransitionsToInitializing: () => {
56
+ const [, newState] = step1(config)(initMsg);
57
+ assert(newState[0] === 'initializing');
58
+ },
59
+ initializedNotificationTransitionsToInitialized: () => {
60
+ const [resp, newState] = step2(config)(initMsg)(initNotif);
61
+ assertEq(resp, null);
62
+ assert(newState[0] === 'initialized');
63
+ },
64
+ initializeReturnsResult: () => {
65
+ const [resp] = step1(config)(initMsg);
66
+ assert(resp !== null && typeof resp === 'object' && 'result' in resp);
67
+ const r = resp.result;
68
+ assertEq(r.protocolVersion, '2024-11-05');
69
+ },
70
+ initializeWithBadParamsReturnsInvalidParams: () => {
71
+ const bad = { jsonrpc: '2.0', method: 'initialize', id: 2, params: { wrong: true } };
72
+ const [resp, newState] = step1(config)(bad);
73
+ assert(newState[0] === 'uninitialized');
74
+ assertEq(resp.error.code, -32602);
75
+ },
76
+ notificationBeforeInitReturnNull: () => {
77
+ const [resp, newState] = step1(config)(initNotif);
78
+ assert(newState[0] === 'uninitialized');
79
+ assertEq(resp, null);
80
+ },
81
+ unknownNotificationReturnNull: () => {
82
+ const notif = { jsonrpc: '2.0', method: 'notifications/unknown' };
83
+ const [resp] = step2(config)(initMsg)(notif);
84
+ assertEq(resp, null);
85
+ },
86
+ doubleInitializeReturnsInvalidRequest: () => {
87
+ const [resp, newState] = step2(config)(initMsg)(initMsg);
88
+ assert(newState[0] === 'initializing');
89
+ assertEq(resp.error.code, -32600);
90
+ },
91
+ pingBeforeInitSucceeds: () => {
92
+ const msg = { jsonrpc: '2.0', method: 'ping', id: 11 };
93
+ const [resp, newState] = step1(config)(msg);
94
+ assert(newState[0] === 'uninitialized');
95
+ assert(!('error' in resp));
96
+ },
97
+ pingDuringInitializingSucceeds: () => {
98
+ const msg = { jsonrpc: '2.0', method: 'ping', id: 12 };
99
+ const [resp, newState] = step2(config)(initMsg)(msg);
100
+ assert(newState[0] === 'initializing');
101
+ assert(!('error' in resp));
102
+ },
103
+ pingAfterInitSucceeds: () => {
104
+ const msg = { jsonrpc: '2.0', method: 'ping', id: 15 };
105
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
106
+ assert(!('error' in resp));
107
+ },
108
+ pingWithObjectParamsSucceeds: () => {
109
+ const msg = { jsonrpc: '2.0', method: 'ping', id: 19, params: {} };
110
+ const [resp] = step1(config)(msg);
111
+ assert(!('error' in resp));
112
+ },
113
+ pingInvalidParamsReturnsInvalidParams: () => {
114
+ const msg = { jsonrpc: '2.0', method: 'ping', id: 20, params: 1 };
115
+ const [resp] = step1(config)(msg);
116
+ assertEq(resp.error.code, -32602);
117
+ },
118
+ initializedNotificationObjectParamsTransitions: () => {
119
+ const notif = { jsonrpc: '2.0', method: 'notifications/initialized', params: {} };
120
+ const [resp, newState] = step2(config)(initMsg)(notif);
121
+ assertEq(resp, null);
122
+ assert(newState[0] === 'initialized');
123
+ },
124
+ initializedNotificationBadParamsIgnored: () => {
125
+ const notif = { jsonrpc: '2.0', method: 'notifications/initialized', params: 1 };
126
+ const [resp, newState] = step2(config)(initMsg)(notif);
127
+ assertEq(resp, null);
128
+ assert(newState[0] === 'initializing');
129
+ },
130
+ methodBeforeInitReturnsNotInitialized: () => {
131
+ const msg = { jsonrpc: '2.0', method: 'tools/list', id: 3 };
132
+ const [resp, newState] = step1(config)(msg);
133
+ assert(newState[0] === 'uninitialized');
134
+ assertEq(resp.error.code, notInitialized.code);
135
+ },
136
+ methodDuringInitializingReturnsNotInitialized: () => {
137
+ const msg = { jsonrpc: '2.0', method: 'tools/list', id: 16 };
138
+ const [resp, newState] = step2(config)(initMsg)(msg);
139
+ assert(newState[0] === 'initializing');
140
+ assertEq(resp.error.code, notInitialized.code);
141
+ },
142
+ invalidEnvelopeReturnsInvalidRequest: () => {
143
+ const bad = { jsonrpc: '1.0', method: 'ping', id: 4 };
144
+ const [resp] = step1(config)(bad);
145
+ assertEq(resp.error.code, -32600);
146
+ assertEq(resp.id, null);
147
+ },
148
+ },
149
+ tools: {
150
+ toolsListSucceeds: () => {
151
+ const msg = { jsonrpc: '2.0', method: 'tools/list', id: 5 };
152
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
153
+ assertEq(resp.result.tools.length, 1);
154
+ assertEq(resp.result.tools[0].name, 'greet');
155
+ },
156
+ toolsListPassesCursorToHandler: () => {
157
+ const msg = { jsonrpc: '2.0', method: 'tools/list', id: 17,
158
+ params: { cursor: 'page-2' } };
159
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
160
+ assertEq(resp.result.nextCursor, 'page-2');
161
+ },
162
+ toolsListInvalidCursorReturnsInvalidParams: () => {
163
+ const msg = { jsonrpc: '2.0', method: 'tools/list', id: 18,
164
+ params: { cursor: 42 } };
165
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
166
+ assertEq(resp.error.code, -32602);
167
+ },
168
+ toolsCallSucceeds: () => {
169
+ const msg = { jsonrpc: '2.0', method: 'tools/call', id: 6,
170
+ params: { name: 'greet', arguments: {} } };
171
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
172
+ assertEq(resp.result.content[0].text, 'hello');
173
+ },
174
+ toolsCallBadParamsReturnsInvalidParams: () => {
175
+ const msg = { jsonrpc: '2.0', method: 'tools/call', id: 7, params: { missing: true } };
176
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
177
+ assertEq(resp.error.code, -32602);
178
+ },
179
+ toolsCallAbsentArgumentsSucceeds: () => {
180
+ const msg = { jsonrpc: '2.0', method: 'tools/call', id: 13,
181
+ params: { name: 'greet' } };
182
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
183
+ assertEq(resp.result.content[0].text, 'hello');
184
+ },
185
+ toolsCallNullArgumentsReturnsInvalidParams: () => {
186
+ const msg = { jsonrpc: '2.0', method: 'tools/call', id: 14,
187
+ params: { name: 'greet', arguments: null } };
188
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
189
+ assertEq(resp.error.code, -32602);
190
+ },
191
+ toolsListWithoutCapabilityReturnsMethodNotFound: () => {
192
+ const msg = { jsonrpc: '2.0', method: 'tools/list', id: 8 };
193
+ const [resp] = step3(configNoTools)(initMsg)(initNotif)(msg);
194
+ assertEq(resp.error.code, -32601);
195
+ },
196
+ toolsCallWithoutCapabilityReturnsMethodNotFound: () => {
197
+ const msg = { jsonrpc: '2.0', method: 'tools/call', id: 9,
198
+ params: { name: 'greet', arguments: {} } };
199
+ const [resp] = step3(configNoTools)(initMsg)(initNotif)(msg);
200
+ assertEq(resp.error.code, -32601);
201
+ },
202
+ unknownMethodReturnsMethodNotFound: () => {
203
+ const msg = { jsonrpc: '2.0', method: 'resources/list', id: 10 };
204
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
205
+ assertEq(resp.error.code, -32601);
206
+ },
207
+ },
208
+ };
@@ -2,7 +2,7 @@
2
2
  * stdio transport for JSON-RPC / MCP servers.
3
3
  *
4
4
  * `stdioTransport` wraps a step function — the `mcpStep`-shaped
5
- * `(value) => Effect<O, Response | null>` from `fjs/mcp/module.f.ts` — in the
5
+ * `(value) => Effect<O, Response | null>` from `fjs/protocol/mcp/module.f.ts` — in the
6
6
  * canonical read → parse → dispatch → write loop, expressed as a recursive
7
7
  * effect so it stays in the pure effect model and is fully testable against a
8
8
  * mock stdin / stdout (see `fjs/effects/node/virtual`) with no real process.
@@ -29,19 +29,19 @@
29
29
  *
30
30
  * @module
31
31
  */
32
- import { type Effect, type Operation } from '../../effects/module.f.ts';
33
- import { type Read, type Write } from '../../effects/node/module.f.ts';
34
- import { type Unknown } from '../../media/json/module.f.ts';
35
- import { type Response } from '../../media/json/rpc/module.f.ts';
32
+ import { type Effect, type Operation } from '../../../effects/module.f.ts';
33
+ import { type Read, type Write } from '../../../effects/node/module.f.ts';
34
+ import { type Unknown } from '../../../media/json/module.f.ts';
35
+ import { type Response } from '../../json_rpc/module.f.ts';
36
36
  /**
37
37
  * A transport step: maps one parsed JSON-RPC message to a response, or `null`
38
38
  * for a notification that needs no reply. The shape of `mcpStep(config)(handlers)(key)`.
39
39
  */
40
40
  export type Step<O extends Operation> = (value: Unknown) => Effect<O, Response | null>;
41
41
  /**
42
- * Drives the read-parse-dispatch-write loop for `step` over stdin/stdout.
42
+ * Drives the read-parse-dispatch-write loop for `handler` over stdin/stdout.
43
43
  *
44
44
  * Recurses after each handled line; terminates (resolving to `void`) when
45
45
  * `readLine` reports EOF.
46
46
  */
47
- export declare const stdioTransport: <O extends Operation>(step: Step<O>) => Effect<Read | Write | O, void>;
47
+ export declare const stdioTransport: <O extends Operation>(handler: Step<O>) => Effect<Read | Write | O, void>;
@@ -2,7 +2,7 @@
2
2
  * stdio transport for JSON-RPC / MCP servers.
3
3
  *
4
4
  * `stdioTransport` wraps a step function — the `mcpStep`-shaped
5
- * `(value) => Effect<O, Response | null>` from `fjs/mcp/module.f.ts` — in the
5
+ * `(value) => Effect<O, Response | null>` from `fjs/protocol/mcp/module.f.ts` — in the
6
6
  * canonical read → parse → dispatch → write loop, expressed as a recursive
7
7
  * effect so it stays in the pure effect model and is fully testable against a
8
8
  * mock stdin / stdout (see `fjs/effects/node/virtual`) with no real process.
@@ -29,16 +29,16 @@
29
29
  *
30
30
  * @module
31
31
  */
32
- import { pure } from '../../effects/module.f.js';
33
- import { readLine, write } from '../../effects/node/module.f.js';
34
- import { tryUtf8 } from '../../text/module.f.js';
35
- import { stringToList } from '../../text/utf16/module.f.js';
36
- import { stringify } from '../../media/json/module.f.js';
37
- import { tokenize } from '../../media/json/tokenizer/module.f.js';
38
- import { parse } from '../../media/json/parser/module.f.js';
39
- import { sort } from '../../types/object/module.f.js';
40
- import { internalError, jsonrpc, parseError } from '../../media/json/rpc/module.f.js';
41
- import { error, ok } from '../../types/result/module.f.js';
32
+ import { pure, step } from '../../../effects/module.f.js';
33
+ import { readLine, write } from '../../../effects/node/module.f.js';
34
+ import { tryUtf8 } from '../../../text/module.f.js';
35
+ import { stringToList } from '../../../text/utf16/module.f.js';
36
+ import { stringify } from '../../../media/json/module.f.js';
37
+ import { tokenize } from '../../../media/json/tokenizer/module.f.js';
38
+ import { parse } from '../../../media/json/parser/module.f.js';
39
+ import { sort } from '../../../types/object/module.f.js';
40
+ import { internalError, jsonrpc, parseError } from '../../json_rpc/module.f.js';
41
+ import { error, ok } from '../../../types/result/module.f.js';
42
42
  const stringifyJson = stringify(sort);
43
43
  /** The parse-error response (`-32700`, `id: null`) for a malformed input line. */
44
44
  const parseErrorResponse = { jsonrpc, error: parseError, id: null };
@@ -49,24 +49,24 @@ const writeResponse = (resp) => {
49
49
  const v = tryUtf8(stringifyJson(resp) + '\n');
50
50
  return v === null
51
51
  ? pure(error(undefined))
52
- : write('stdout', v).step(() => pure(ok(undefined)));
52
+ : step(write('stdout', v), () => pure(ok(undefined)));
53
53
  };
54
54
  /**
55
- * Drives the read-parse-dispatch-write loop for `step` over stdin/stdout.
55
+ * Drives the read-parse-dispatch-write loop for `handler` over stdin/stdout.
56
56
  *
57
57
  * Recurses after each handled line; terminates (resolving to `void`) when
58
58
  * `readLine` reports EOF.
59
59
  */
60
- export const stdioTransport = (step) => readLine('stdin').step(line => line === null
60
+ export const stdioTransport = (handler) => step(readLine('stdin'), line => line === null
61
61
  ? pure(undefined)
62
- : handleLine(step)(line));
63
- const handleLine = (step) => (line) => {
62
+ : handleLine(handler)(line));
63
+ const handleLine = (handler) => (line) => {
64
64
  const [t, value] = parse(tokenize(stringToList(line)));
65
- return (t === 'error'
65
+ return step(t === 'error'
66
66
  ? writeResponse(parseErrorResponse)
67
- : step(value).step(resp => resp === null
67
+ : step(handler(value), resp => resp === null
68
68
  ? pure(undefined)
69
- : writeResponse(resp).step(([t2]) => t2 === 'error'
69
+ : step(writeResponse(resp), ([t2]) => t2 === 'error'
70
70
  // The real response didn't fit. Retry with a fixed, small
71
71
  // internal-error body carrying `resp.id` — but a
72
72
  // caller-controlled `id` (e.g. a very large string) can
@@ -74,8 +74,8 @@ const handleLine = (step) => (line) => {
74
74
  // that retry is bounded by one more: an `id: null`
75
75
  // internal-error, whose fully-constant shape is the only
76
76
  // line in this transport guaranteed to always encode.
77
- ? writeResponse(internalErrorResponse(resp.id)).step(([t3]) => t3 === 'error'
78
- ? writeResponse(internalErrorResponse(null)).step(() => pure(undefined))
77
+ ? step(writeResponse(internalErrorResponse(resp.id)), ([t3]) => t3 === 'error'
78
+ ? step(writeResponse(internalErrorResponse(null)), () => pure(undefined))
79
79
  : pure(undefined))
80
- : pure(undefined)))).step(() => stdioTransport(step));
80
+ : pure(undefined))), () => stdioTransport(handler));
81
81
  };
@@ -1,12 +1,12 @@
1
- import { 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 { stringify } from '../../media/json/module.f.js';
5
- import { utf8 } from '../../text/module.f.js';
6
- import { fromVec } from '../../types/uint8array/module.f.js';
7
- import { maxLengthBytes } from '../../types/bit_vec/module.f.js';
8
- import { sort } from '../../types/object/module.f.js';
9
- import { internalError, jsonrpc, parseError } from '../../media/json/rpc/module.f.js';
1
+ import { 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 { stringify } from '../../../media/json/module.f.js';
5
+ import { utf8 } from '../../../text/module.f.js';
6
+ import { fromVec } from '../../../types/uint8array/module.f.js';
7
+ import { maxLengthBytes } from '../../../types/bit_vec/module.f.js';
8
+ import { sort } from '../../../types/object/module.f.js';
9
+ import { internalError, jsonrpc, parseError } from '../../json_rpc/module.f.js';
10
10
  import { stdioTransport } from './module.f.js';
11
11
  const stringifyJson = stringify(sort);
12
12
  // Extracts the request `id` (a request has one; a notification does not).
@@ -17,7 +17,8 @@
17
17
  * ```
18
18
  */
19
19
  import { cmp } from '../function/compare/module.f.js';
20
- import { reduce } from '../list/module.f.js';
20
+ import {} from '../list/module.f.js';
21
+ import { fold } from '../../common/monoid/module.f.js';
21
22
  /**
22
23
  * Adds two `bigint` values.
23
24
  *
@@ -31,7 +32,7 @@ export const addition = a => b => a + b;
31
32
  * @param input - A list of bigint values.
32
33
  * @returns The sum of all values in the list.
33
34
  */
34
- export const sum = reduce(addition)(0n);
35
+ export const sum = fold({ identity: 0n, operation: addition });
35
36
  /**
36
37
  * Multiplies two `bigint` values.
37
38
  *
@@ -45,7 +46,7 @@ export const multiple = a => b => a * b;
45
46
  * @param input - A list of bigint values.
46
47
  * @returns The product of all values in the list.
47
48
  */
48
- export const product = reduce(multiple)(1n);
49
+ export const product = fold({ identity: 1n, operation: multiple });
49
50
  /**
50
51
  * Calculates the absolute value of a `bigint`.
51
52
  *
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import { bitLength, divUp, mask, maxLength, xor } from '../bigint/module.f.js';
25
25
  import { flip, identity } from '../function/module.f.js';
26
- import { iterable, map } from '../list/module.f.js';
26
+ import { map, tryFold } from '../list/module.f.js';
27
27
  import { asBase, asNominal } from '../nominal/module.f.js';
28
28
  import { repeat as mRepeat } from '../../common/monoid/module.f.js';
29
29
  import { cmp, max, min } from '../function/compare/module.f.js';
@@ -174,24 +174,14 @@ const listToVecOp = (unpackConcat) => ({
174
174
  * the final reduce prepends higher (earlier) slots in front of accumulated
175
175
  * later runs. An empty list yields `unpackEmpty`.
176
176
  *
177
+ * Returns `null` as soon as the accumulated length exceeds `maxLength`, which
178
+ * `tryFold` propagates by abandoning the rest of the list.
179
+ *
177
180
  * This is the bit-vector analogue of a builder that accumulates appended pieces
178
181
  * and materializes the combined result on demand, such as `StringBuilder`
179
182
  * (Java, C#) or `strings.Builder` (Go).
180
183
  */
181
- const unpackListToVec = (unpackConcat) => {
182
- const { init, update, end } = listToVecOp(unpackConcat);
183
- return (list) => {
184
- let result = init;
185
- for (const e of iterable(list)) {
186
- const candidate = update(e, result);
187
- if (candidate === null) {
188
- return null;
189
- }
190
- result = candidate;
191
- }
192
- return end(result);
193
- };
194
- };
184
+ const unpackListToVec = (unpackConcat) => tryFold(listToVecOp(unpackConcat));
195
185
  const bo = ({ front, removeFront, norm, uintCmp, unpackSplit, unpackConcatUint }) => {
196
186
  const unpackPopFront = (len) => {
197
187
  const m = mask(len);
@@ -7,3 +7,8 @@ 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>;
10
+ export declare const proof: {
11
+ throw: {
12
+ reduceValue0DefaultBranch: () => void;
13
+ };
14
+ };
@@ -172,3 +172,15 @@ export const nodeRemove = (c) => (node) => {
172
172
  return collapseRoot(reduce(initReduce(tf)(first))(tt));
173
173
  };
174
174
  export const remove = c => map(nodeRemove(c));
175
+ // `reduceValue0`/`reduceValue2`/`initValue0`/`initValue1` merge a Branch1's lone
176
+ // child into its Branch3 sibling. The sibling is always a Branch (length 3 or 5)
177
+ // in every reachable tree shape, so the `default` arm guarding a mismatched
178
+ // (leaf) sibling length can't be hit through the public `remove` API. Exercise
179
+ // it directly here so the invariant guard itself stays covered.
180
+ export const proof = {
181
+ throw: {
182
+ reduceValue0DefaultBranch: () => {
183
+ reduceValue0([['leaf']])([['x'], 's', ['y']]);
184
+ },
185
+ },
186
+ };
@@ -1,3 +1,4 @@
1
+ import type { Nullable } from '../nullable/module.f.ts';
1
2
  import { type Scan, type StateScan, type Fold, type Reduce, type Equal } from '../function/operator/module.f.ts';
2
3
  export type List<T> = NotLazy<T> | Thunk<T>;
3
4
  type NotLazy<T> = Result<T> | Concat<T> | readonly T[];
@@ -44,6 +45,26 @@ export declare const stateScan: <I, S, O>(op: StateScan<I, S, O>) => (init: S) =
44
45
  export declare const foldScan: <I, O>(op: Fold<I, O>) => (init: O) => (input: List<I>) => Thunk<O>;
45
46
  export declare const fold: <I, O>(op: Fold<I, O>) => (init: O) => (input: List<I>) => O;
46
47
  export declare const reduce: <T>(op: Reduce<T>) => <D>(def: D) => (input: List<T>) => D | T;
48
+ /**
49
+ * A fold that can bail out early, packaged as plain data.
50
+ *
51
+ * `init` is the starting state, `update` advances the state by one item and
52
+ * returns `null` to abort the whole fold, and `end` finalizes the surviving
53
+ * state into a result. Keeping the three parts together lets `tryFold` drive
54
+ * any short-circuiting accumulation without knowing its domain.
55
+ */
56
+ export type Accumulator<I, T, R> = {
57
+ readonly init: T;
58
+ readonly update: (i: I, state: T) => Nullable<T>;
59
+ readonly end: (state: T) => R;
60
+ };
61
+ /**
62
+ * Folds `input` with an {@link Accumulator}, short-circuiting to `null` the
63
+ * moment `update` rejects an item; otherwise finalizes with `end`. This is the
64
+ * list-level `try*` sibling of {@link fold} for accumulations that can fail
65
+ * partway through (see the `try*`/`Nullable` convention in `AGENTS.md`).
66
+ */
67
+ export declare const tryFold: <I, T, R>({ init, update, end }: Accumulator<I, T, R>) => (input: List<I>) => Nullable<R>;
47
68
  export declare const length: <T>(input: List<T>) => number;
48
69
  export type Entry<T> = readonly [number, T];
49
70
  export declare const entries: <T>(input: List<T>) => Thunk<Entry<T>>;
@@ -127,6 +127,23 @@ export const stateScan = op => compose(stateScanToScan(op))(scan);
127
127
  export const foldScan = op => compose(foldToScan(op))(scan);
128
128
  export const fold = op => init => compose(foldScan(op)(init))(last(init));
129
129
  export const reduce = op => def => compose(scan(reduceToScan(op)))(last(def));
130
+ /**
131
+ * Folds `input` with an {@link Accumulator}, short-circuiting to `null` the
132
+ * moment `update` rejects an item; otherwise finalizes with `end`. This is the
133
+ * list-level `try*` sibling of {@link fold} for accumulations that can fail
134
+ * partway through (see the `try*`/`Nullable` convention in `AGENTS.md`).
135
+ */
136
+ export const tryFold = ({ init, update, end }) => (input) => {
137
+ let state = init;
138
+ for (const i of iterable(input)) {
139
+ const candidate = update(i, state);
140
+ if (candidate === null) {
141
+ return null;
142
+ }
143
+ state = candidate;
144
+ }
145
+ return end(state);
146
+ };
130
147
  const lengthList = list => () => {
131
148
  const notLazy = trampoline(list);
132
149
  if (notLazy === null) {
@@ -19,6 +19,7 @@ export declare const proof: {
19
19
  drop: (() => void)[];
20
20
  additionTests: (() => void)[];
21
21
  entries: (() => void)[];
22
+ tryFold: readonly [() => void, () => void, () => void];
22
23
  reverse: (() => void)[];
23
24
  zip: (() => void)[];
24
25
  logic: typeof logic;
@@ -1,4 +1,4 @@
1
- import { length, concat, countdown, cycle, drop, dropWhile, entries, every, filter, find, flat, flatMap, map, next, reduce, reverse, scan, some, take, takeWhile, toArray, zip, first, filterMap, isEmpty, equal } from './module.f.js';
1
+ import { length, concat, countdown, cycle, drop, dropWhile, entries, every, filter, find, flat, flatMap, map, next, reduce, reverse, scan, some, take, takeWhile, toArray, zip, first, filterMap, isEmpty, equal, tryFold } from './module.f.js';
2
2
  import { stringify } from '../../media/json/module.f.js';
3
3
  import { sort } from '../object/module.f.js';
4
4
  import { addition, strictEqual, reduceToScan } from '../function/operator/module.f.js';
@@ -112,6 +112,28 @@ const entriesTest = [
112
112
  assertEq(result, '[[0,"hello"],[1,"world"]]');
113
113
  }
114
114
  ];
115
+ const sumUpTo = (limit) => ({
116
+ init: 0,
117
+ update: (i, state) => {
118
+ const next = state + i;
119
+ return next > limit ? null : next;
120
+ },
121
+ end: (state) => state * 10,
122
+ });
123
+ const tryFoldTest = [
124
+ () => {
125
+ const result = tryFold(sumUpTo(100))([1, 2, 3]);
126
+ assertEq(result, 60);
127
+ },
128
+ () => {
129
+ const result = tryFold(sumUpTo(4))([1, 2, 3]);
130
+ assertEq(result, null);
131
+ },
132
+ () => {
133
+ const result = tryFold(sumUpTo(100))([]);
134
+ assertEq(result, 0);
135
+ },
136
+ ];
115
137
  const reverseTest = [
116
138
  () => {
117
139
  const result = str(reverse([]));
@@ -257,6 +279,7 @@ export const proof = {
257
279
  drop: dropTest,
258
280
  additionTests,
259
281
  entries: entriesTest,
282
+ tryFold: tryFoldTest,
260
283
  reverse: reverseTest,
261
284
  zip: zipTest,
262
285
  logic,
@@ -7,7 +7,8 @@
7
7
  import { reduce } from '../list/module.f.js';
8
8
  import { addition } from '../function/operator/module.f.js';
9
9
  import { cmp as uCmp, min as uMin, max as uMax } from '../function/compare/module.f.js';
10
- export const sum = reduce(addition)(0);
10
+ import { fold } from '../../common/monoid/module.f.js';
11
+ export const sum = fold({ identity: 0, operation: addition });
11
12
  export const min = reduce(uMin)(null);
12
13
  export const max = reduce(uMax)(null);
13
14
  export const cmp = uCmp;
@@ -13,25 +13,26 @@
13
13
  * })
14
14
  *
15
15
  * // Create range maps
16
- * const range1 = rmOps.fromRange([0, 10])(2)
17
- * const range2 = rmOps.fromRange([5, 15])(5)
16
+ * const range1 = rmOps.fromRange(2)([0, 10])
17
+ * const range2 = rmOps.fromRange(5)([5, 15])
18
18
  *
19
19
  * // Merge range maps
20
20
  * const merged = toArray(rmOps.merge(range1)(range2))
21
21
  *
22
22
  * // Retrieve values from the merged range map
23
+ * const get = rmOps.get(merged)
23
24
  * //
24
- * if (rmOps.get(-1)(merged) !== 0) { throw 'error' }
25
+ * if (get(-1) !== 0) { throw 'error' }
25
26
  * //
26
- * if (rmOps.get(0)(merged) !== 2) { throw 'error' }
27
- * if (rmOps.get(2)(merged) !== 2) { throw 'error' }
27
+ * if (get(0) !== 2) { throw 'error' }
28
+ * if (get(2) !== 2) { throw 'error' }
28
29
  * // overlapped: 2 | 5 = 7
29
- * if (rmOps.get(7)(merged) !== 7) { throw 'error' }
30
+ * if (get(7) !== 7) { throw 'error' }
30
31
  * //
31
- * if (rmOps.get(12)(merged) !== 5) { throw 'error' }
32
- * if (rmOps.get(15)(merged) !== 5) { throw 'error' }
32
+ * if (get(12) !== 5) { throw 'error' }
33
+ * if (get(15) !== 5) { throw 'error' }
33
34
  * //
34
- * if (rmOps.get(16)(merged) !== 0) { throw 'error' }
35
+ * if (get(16) !== 0) { throw 'error' }
35
36
  * ```
36
37
  */
37
38
  import { type SortedList } from '../sorted_list/module.f.ts';
@@ -63,8 +64,8 @@ export type Properties<T> = {
63
64
  };
64
65
  export type RangeMerge<T> = Reduce<RangeMap<T>>;
65
66
  export declare const merge: <T>(op: Properties<T>) => RangeMerge<T>;
66
- export declare const get: <T>(def: T) => (value: number) => (rm: RangeMapArray<T>) => T;
67
- export declare const fromRange: <T>(def: T) => (r: Range) => (value: T) => RangeMapArray<T>;
67
+ export declare const get: <T>(def: T) => (rm: RangeMapArray<T>) => (value: number) => T;
68
+ export declare const fromRange: <T>(def: T) => (value: T) => (r: Range) => RangeMapArray<T>;
68
69
  /**
69
70
  * Represents a set of operations for managing range maps.
70
71
  */
@@ -76,11 +77,11 @@ export type RangeMapOp<T> = {
76
77
  /**
77
78
  * Retrieves the value associated with a given numeric range.
78
79
  */
79
- readonly get: (value: number) => (rm: RangeMapArray<T>) => T;
80
+ readonly get: (rm: RangeMapArray<T>) => (value: number) => T;
80
81
  /**
81
82
  * Constructs a range map for a single numeric range and value.
82
83
  */
83
- readonly fromRange: (r: Range) => (value: T) => RangeMapArray<T>;
84
+ readonly fromRange: (value: T) => (r: Range) => RangeMapArray<T>;
84
85
  };
85
86
  /**
86
87
  * Creates a set of operations for managing range maps using the specified properties.