sendscript 2.3.2 → 2.3.4

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.
package/CHANGELOG.md CHANGED
@@ -4,8 +4,22 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [v2.3.4](https://github.com/bas080/sendscript/compare/v2.3.3...v2.3.4)
8
+
9
+ - Improve naming in JSDocs [`669a40e`](https://github.com/bas080/sendscript/commit/669a40e28a5c77963d855dd4e2122d0ba2c074cd)
10
+
11
+ #### [v2.3.3](https://github.com/bas080/sendscript/compare/v2.3.2...v2.3.3)
12
+
13
+ > 14 April 2026
14
+
15
+ - Add JSDoc to relevant function for docs [`d1d3709`](https://github.com/bas080/sendscript/commit/d1d3709088a51b71651170ef642b013eedc9ca3a)
16
+ - Remove tests section from README [`5da7bb3`](https://github.com/bas080/sendscript/commit/5da7bb3f3b1dc8078683e076fe2b520bc6d28896)
17
+ - Add generated from JSDoc reference section [`08eb266`](https://github.com/bas080/sendscript/commit/08eb266b06d8998e6849dbc5c9738fbe782ada18)
18
+
7
19
  #### [v2.3.2](https://github.com/bas080/sendscript/compare/v2.3.1...v2.3.2)
8
20
 
21
+ > 14 April 2026
22
+
9
23
  - Format the README.mz file [`df43dbb`](https://github.com/bas080/sendscript/commit/df43dbb8b790cfc612536f49571f9fe4aad03837)
10
24
  - Move some commands from README to CONTRIBUTING [`45659c5`](https://github.com/bas080/sendscript/commit/45659c51354ac3bbe3bed5ef3826785d8a1a4a32)
11
25
  - Add format markdown check in contributing md [`e68d4d6`](https://github.com/bas080/sendscript/commit/e68d4d6aea26a7a54c2f933ad98a8e363d7f3357)
package/CONTRIBUTING.md CHANGED
@@ -40,7 +40,9 @@ Generate the README from the mz file.
40
40
  ```bash bash
41
41
  markatzea ./README.mz | tee ./README.md
42
42
 
43
- npx markdown-toc -i README.md
43
+ npx documentation readme references.mjs stringify.mjs parse.mjs -s Reference --github --a public --markdown-toc false
44
+
45
+ npx markdown-toc --maxdepth 3 -i README.md
44
46
 
45
47
  git add *.md ./example
46
48
  ```
package/README.md CHANGED
@@ -2,14 +2,22 @@
2
2
 
3
3
  Write JS code that you can run on servers, browsers or other clients.
4
4
 
5
- [![NPM](https://img.shields.io/npm/v/sendscript?color=blue&style=flat-square)](https://www.npmjs.com/package/sendscript)
5
+ [![NPM](https://img.shields.io/npm/v/sendscript?color=blue\&style=flat-square)](https://www.npmjs.com/package/sendscript)
6
6
  [![100% Code Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen?style=flat-square)](#tests)
7
7
  [![Standard Code Style](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](https://standardjs.com)
8
- [![License](https://img.shields.io/npm/l/sendscript?color=brightgreen&style=flat-square)](./LICENSE.txt)
8
+ [![License](https://img.shields.io/npm/l/sendscript?color=brightgreen\&style=flat-square)](./LICENSE.txt)
9
9
 
10
10
  <!-- toc -->
11
11
 
12
12
  - [Introduction](#introduction)
13
+ - [Reference](#reference)
14
+ * [defaultLeafParse](#defaultleafparse)
15
+ * [Parse](#parse)
16
+ * [parse](#parse)
17
+ * [References](#references)
18
+ * [defaultLeafStringify](#defaultleafstringify)
19
+ * [Stringify](#stringify)
20
+ * [stringify](#stringify)
13
21
  - [Socket example](#socket-example)
14
22
  * [Module](#module)
15
23
  * [Server](#server)
@@ -25,10 +33,9 @@ Write JS code that you can run on servers, browsers or other clients.
25
33
  * [Validating structured input](#validating-structured-input)
26
34
  - [Leaf Serializer](#leaf-serializer)
27
35
  * [Example with superjson](#example-with-superjson)
28
- - [Tests](#tests)
29
36
  - [Changelog](#changelog)
30
37
  - [License](#license)
31
- - [Roadmap](#roadmap)
38
+ - [Issues](#issues)
32
39
 
33
40
  <!-- tocstop -->
34
41
 
@@ -37,12 +44,12 @@ Write JS code that you can run on servers, browsers or other clients.
37
44
  There has been interest in improving APIs by allowing aggregations in a single
38
45
  request. Examples include
39
46
 
40
- - [JSON-RPC](https://json-rpc.dev/) which allows you to do multiple requests but
41
- it does not allow you to compose the return value of one endpoint to be the
42
- input/arguments of another.
47
+ * [JSON-RPC](https://json-rpc.dev/) which allows you to do multiple requests but
48
+ it does not allow you to compose the return value of one endpoint to be the
49
+ input/arguments of another.
43
50
 
44
- - [GraphQL](https://graphql.org/) is very cool but also introduces a new
45
- languages and the tooling that is required to wield it.
51
+ * [GraphQL](https://graphql.org/) is very cool but also introduces a new
52
+ languages and the tooling that is required to wield it.
46
53
 
47
54
  What SendScript attempts is to allow for very expressive queries and mutations
48
55
  to be performed that read and write like ordinary JS. That means that the
@@ -63,6 +70,7 @@ const stringify = Stringify()
63
70
 
64
71
  console.log(stringify(add(1,2)))
65
72
  ```
73
+
66
74
  ```json
67
75
  ["call",["ref","add"],[["leaf","1"],["leaf","2"]]]
68
76
  ```
@@ -84,6 +92,7 @@ const program = '["call",["ref","add"],[1,2]]'
84
92
 
85
93
  console.log(parse(program))
86
94
  ```
95
+
87
96
  ```json
88
97
  3
89
98
  ```
@@ -93,21 +102,105 @@ composition and even await.
93
102
 
94
103
  This package is nothing more than the absolute core of sendscript. It includes:
95
104
 
96
- - The `references` function to create stubs to write the programs.
97
- - `stringify` which takes the program and returns a JSON string.
98
- - `parse` which takes the `stringify` JSON string and a real module and returns
99
- the result.
105
+ * The `references` function to create stubs to write the programs.
106
+ * `stringify` which takes the program and returns a JSON string.
107
+ * `parse` which takes the `stringify` JSON string and a real module and returns
108
+ the result.
100
109
 
101
110
  The naming could use more love and there are many things to solve either in the
102
111
  core or around it. Things like supporting more complex (de)serializers, errors
103
112
  and maybe mixing client functions with sendscript programs. Contact me if I have
104
113
  piqued your interest.
105
114
 
106
- ---
115
+ ***
107
116
 
108
117
  SendScript leaves it up to you to choose HTTP, web-sockets or any other method
109
118
  of communication between servers and clients that best fits your needs.
110
119
 
120
+ ## Reference
121
+
122
+ <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
123
+
124
+ ### defaultLeafParse
125
+
126
+ Default deserializer for leaf nodes.
127
+
128
+ #### Parameters
129
+
130
+ * `text` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**&#x20;
131
+
132
+ Returns **any**&#x20;
133
+
134
+ ### Parse
135
+
136
+ #### Parameters
137
+
138
+ * `schemaArg` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) | \[[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)])>**&#x20;
139
+ * `env` **Env** runtime environment for refs
140
+ * `leafParse` (optional, default `defaultLeafParse`)
141
+
142
+ Returns **[parse](#parse)**&#x20;
143
+
144
+ ### parse
145
+
146
+ Parses and executes a serialized program.
147
+
148
+ #### Parameters
149
+
150
+ * `program` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** JSON encoded program
151
+
152
+ Returns **(any | [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<any>)**&#x20;
153
+
154
+ ### References
155
+
156
+ Builds a nested API structure from a schema definition.
157
+
158
+ Schema supports:
159
+
160
+ * string => leaf function node
161
+ * \[name, children] => namespace with nested schema
162
+
163
+ #### Parameters
164
+
165
+ * `schema` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) | \[[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)])>**&#x20;
166
+ * `parentPath` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>** (optional, default `[]`)
167
+
168
+ <!---->
169
+
170
+ * Throws **[Error](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)** If schema format is invalid
171
+
172
+ Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Nested instrumented API object
173
+
174
+ ### defaultLeafStringify
175
+
176
+ Default strict serializer for leaf values.
177
+
178
+ Rejects non-JSON-safe values.
179
+
180
+ #### Parameters
181
+
182
+ * `x` **any**&#x20;
183
+
184
+ Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**&#x20;
185
+
186
+ ### Stringify
187
+
188
+ Creates a stringify function for SendScript AST structures.
189
+
190
+ #### Parameters
191
+
192
+ * `leafStringify` (optional, default `defaultLeafStringify`)
193
+
194
+ ### stringify
195
+
196
+ Serializes a program into a JSON string representation.
197
+
198
+ #### Parameters
199
+
200
+ * `program` **any**&#x20;
201
+
202
+ Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**&#x20;
203
+
111
204
  ## Socket example
112
205
 
113
206
  For this example we'll use [socket.io][socket.io].
@@ -205,9 +298,8 @@ node ./example/client.socket.io.mjs
205
298
 
206
299
  pkill sendscript
207
300
  ```
208
- ```
209
- Result: 100
210
- ```
301
+
302
+ Result: 100
211
303
 
212
304
  ## Repl
213
305
 
@@ -259,18 +351,19 @@ defined properties and returned values.
259
351
 
260
352
  There is a good use-case to write a module in TypeScript.
261
353
 
262
- 1. Obviously the module would have the benefits that TypeScript offers when
263
- coding.
264
- 2. You can use tools like [typedoc][typedoc] to generate docs from your types to
265
- share with consumers of your API.
266
- 3. You can use the types of the module to coerce your client to adopt the
267
- module's type.
354
+ 1. Obviously the module would have the benefits that TypeScript offers when
355
+ coding.
356
+ 2. You can use tools like [typedoc][typedoc] to generate docs from your types to
357
+ share with consumers of your API.
358
+ 3. You can use the types of the module to coerce your client to adopt the
359
+ module's type.
268
360
 
269
361
  Let's say we have this module which we use on the server.
270
362
 
271
363
  ```bash
272
364
  cat ./example/typescript/math.ts
273
365
  ```
366
+
274
367
  ```ts
275
368
  export const add = (a: number, b: number) => a + b
276
369
  export const square = (a: number) => a * a
@@ -281,6 +374,7 @@ We can then coerce the types of the instrumented stubs.
281
374
  ```bash
282
375
  cat ./example/typescript/client.ts
283
376
  ```
377
+
284
378
  ```ts
285
379
  import math from './math.client.ts'
286
380
  import Stringify from 'sendscript/stringify.mjs'
@@ -306,7 +400,7 @@ npx typedoc --plugin typedoc-plugin-markdown --out ./example/typescript/docs ./e
306
400
 
307
401
  You can see the docs [here](./example/typescript/docs/globals.md)
308
402
 
309
- > [!NOTE] Although type coercion on the client side can improve the development
403
+ > \[!NOTE] Although type coercion on the client side can improve the development
310
404
  > experience, it does not represent the actual type. Values are subject to
311
405
  > serialization and deserialization.
312
406
 
@@ -373,9 +467,9 @@ export function createUser(user) {
373
467
 
374
468
  **Benefits**:
375
469
 
376
- - Ensures arguments match expected types and shapes.
377
- - Throws structured errors that can be propagated to clients.
378
- - Works with TypeScript for automatic type inference.
470
+ * Ensures arguments match expected types and shapes.
471
+ * Throws structured errors that can be propagated to clients.
472
+ * Works with TypeScript for automatic type inference.
379
473
 
380
474
  ## Leaf Serializer
381
475
 
@@ -447,32 +541,6 @@ const result = parse(json)
447
541
  The leaf wrapper format is `['leaf', serializedPayload]`, making it unambiguous
448
542
  and safe from colliding with SendScript operators.
449
543
 
450
- ## Tests
451
-
452
- Tests with 100% code coverage.
453
-
454
- ```bash
455
- npm t -- -R silent
456
- npm t -- report text-summary
457
- ```
458
- ```
459
-
460
- > sendscript@2.3.2 test
461
- > tap -R silent
462
-
463
-
464
- > sendscript@2.3.2 test
465
- > tap report text-summary
466
-
467
-
468
- =============================== Coverage summary ===============================
469
- Statements : 100% ( 372/372 )
470
- Branches : 100% ( 137/137 )
471
- Functions : 100% ( 24/24 )
472
- Lines : 100% ( 372/372 )
473
- ================================================================================
474
- ```
475
-
476
544
  ## Changelog
477
545
 
478
546
  The [changelog][changelog] is generated using the useful
@@ -486,12 +554,18 @@ npx auto-changelog -p
486
554
 
487
555
  See the [LICENSE.txt][license] file for details.
488
556
 
489
- ## Roadmap
557
+ ## Issues
490
558
 
491
- - [ ] Support for simple lambdas to compose functions more easily.
559
+ See [issues][issues] for roadmap and known bugs.
492
560
 
493
561
  [license]: ./LICENSE.txt
562
+
494
563
  [socket.io]: https://socket.io/
564
+
495
565
  [changelog]: ./CHANGELOG.md
566
+
496
567
  [auto-changelog]: https://www.npmjs.com/package/auto-changelog
568
+
497
569
  [typedoc]: https://github.com/TypeStrong/typedoc
570
+
571
+ [issues]: https://github.com/bas080/sendscript/issues
package/README.mz CHANGED
@@ -79,6 +79,10 @@ piqued your interest.
79
79
  SendScript leaves it up to you to choose HTTP, web-sockets or any other method
80
80
  of communication between servers and clients that best fits your needs.
81
81
 
82
+ ## Reference
83
+
84
+ <!-- Reference -->
85
+
82
86
  ## Socket example
83
87
 
84
88
  For this example we'll use [socket.io][socket.io].
@@ -395,15 +399,6 @@ const result = parse(json)
395
399
  The leaf wrapper format is `['leaf', serializedPayload]`, making it unambiguous
396
400
  and safe from colliding with SendScript operators.
397
401
 
398
- ## Tests
399
-
400
- Tests with 100% code coverage.
401
-
402
- ```bash bash
403
- npm t -- -R silent
404
- npm t -- report text-summary
405
- ```
406
-
407
402
  ## Changelog
408
403
 
409
404
  The [changelog][changelog] is generated using the useful
@@ -417,12 +412,13 @@ npx auto-changelog -p
417
412
 
418
413
  See the [LICENSE.txt][license] file for details.
419
414
 
420
- ## Roadmap
415
+ ## Issues
421
416
 
422
- - [ ] Support for simple lambdas to compose functions more easily.
417
+ See [issues][issues] for roadmap and known bugs.
423
418
 
424
419
  [license]: ./LICENSE.txt
425
420
  [socket.io]: https://socket.io/
426
421
  [changelog]: ./CHANGELOG.md
427
422
  [auto-changelog]: https://www.npmjs.com/package/auto-changelog
428
423
  [typedoc]: https://github.com/TypeStrong/typedoc
424
+ [issues]: https://github.com/bas080/sendscript/issues
@@ -8,7 +8,7 @@
8
8
 
9
9
  > **add**(`a`, `b`): `number`
10
10
 
11
- Defined in: [math.ts:1](https://github.com/bas080/sendscript/blob/45659c51354ac3bbe3bed5ef3826785d8a1a4a32/example/typescript/math.ts#L1)
11
+ Defined in: [math.ts:1](https://github.com/bas080/sendscript/blob/669a40e28a5c77963d855dd4e2122d0ba2c074cd/example/typescript/math.ts#L1)
12
12
 
13
13
  ## Parameters
14
14
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  > **square**(`a`): `number`
10
10
 
11
- Defined in: [math.ts:2](https://github.com/bas080/sendscript/blob/45659c51354ac3bbe3bed5ef3826785d8a1a4a32/example/typescript/math.ts#L2)
11
+ Defined in: [math.ts:2](https://github.com/bas080/sendscript/blob/669a40e28a5c77963d855dd4e2122d0ba2c074cd/example/typescript/math.ts#L2)
12
12
 
13
13
  ## Parameters
14
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sendscript",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "Blur the line between server and client code.",
5
5
  "module": true,
6
6
  "main": "index.mjs",
package/parse.mjs CHANGED
@@ -6,13 +6,14 @@ function flattenSchema (schema) {
6
6
 
7
7
  for (const item of schema) {
8
8
  if (typeof item === 'string') {
9
- // leaf function
10
9
  obj[item] = true
11
10
  } else if (Array.isArray(item)) {
12
11
  const [name, children] = item
12
+
13
13
  if (!Array.isArray(children)) {
14
14
  throw new Error(`Expected children array for namespace "${name}"`)
15
15
  }
16
+
16
17
  obj[name] = flattenSchema(children)
17
18
  } else {
18
19
  throw new Error('Schema items must be strings or [name, children] arrays')
@@ -38,7 +39,6 @@ const spy = (type, fn) => (...args) => {
38
39
  return value
39
40
  }
40
41
 
41
- // Recursively resolve awaited values in a parsed tree
42
42
  const evaluate = spy('eval', (value, awaits = []) => {
43
43
  if (value === undefinedSentinel) return undefined
44
44
 
@@ -47,49 +47,41 @@ const evaluate = spy('eval', (value, awaits = []) => {
47
47
 
48
48
  if (operator === 'await') {
49
49
  const [index] = rest
50
- // No need to check index. It is closely tied to the program.
51
- // if (typeof index !== 'number' || index < 0 || index >= awaitsResolved.length) {
52
- // throw new Error(`Invalid await index: ${index}`);
53
- // }
54
50
  return awaits[index]
55
51
  }
56
52
 
57
53
  if (operator === 'then') {
58
54
  const [v, onResolve, onReject] = rest
59
- return evaluate(v, awaits).then(evaluate(onResolve, awaits), evaluate(onReject, awaits))
55
+ return evaluate(v, awaits).then(
56
+ evaluate(onResolve, awaits),
57
+ evaluate(onReject, awaits)
58
+ )
60
59
  }
61
60
 
62
61
  if (operator === 'call') {
63
- // Step 1: evaluate the function itself
64
62
  let [fn, args] = rest
65
63
  fn = evaluate(fn, awaits)
66
64
 
67
- // Step 2: evaluate each argument AFTER fn is ready
68
65
  for (let i = 0; i < args.length; i++) {
69
66
  args[i] = evaluate(args[i], awaits)
70
67
  }
71
68
 
72
- // Step 3: call the function
73
69
  return fn(...args)
74
70
  }
75
71
 
76
72
  if (operator === 'quote') {
77
73
  const [quoted] = rest
78
- return quoted // return as-is without evaluating
74
+ return quoted
79
75
  }
80
76
 
81
- // fallback: evaluate each element
82
- // re-uses the array again.
83
-
84
- for (let index = 0; index < value.length; index++) {
85
- const item = value[index]
86
- value[index] = evaluate(item, awaits)
77
+ for (let i = 0; i < value.length; i++) {
78
+ value[i] = evaluate(value[i], awaits)
87
79
  }
80
+
88
81
  return value
89
82
  }
90
83
 
91
84
  if (isPlainObject(value)) {
92
- // We muatate the object itself. No need to make a new one.
93
85
  for (const key of Object.keys(value)) {
94
86
  value[key] = evaluate(value[key], awaits)
95
87
  }
@@ -99,34 +91,69 @@ const evaluate = spy('eval', (value, awaits = []) => {
99
91
  return value
100
92
  })
101
93
 
102
- const defaultLeafDeserializer = (text) => JSON.parse(text)
103
-
104
- export default (schemaArg, env, deserialize = defaultLeafDeserializer) => {
94
+ /**
95
+ * Default deserializer for leaf nodes.
96
+ *
97
+ * @param {string} text
98
+ * @returns {any}
99
+ * @public
100
+ */
101
+ const defaultLeafParse = (text) => JSON.parse(text)
102
+
103
+ /**
104
+ * @template Env
105
+ * @typedef {(program: string) => any | Promise<any>} parse
106
+ *
107
+ * Creates a program parser for a given schema and environment.
108
+ *
109
+ * The parser:
110
+ * - resolves references into runtime functions
111
+ * - deserializes leaf nodes
112
+ * - collects and executes async awaits
113
+ * - evaluates AST-like JSON programs
114
+ *
115
+ * @param {Array<string | [string, Array]>} schemaArg
116
+ * @param {Env} env - runtime environment for refs
117
+ * @param {(text: string) => any} [leafParse=defaultLeafParse]
118
+ * @returns {parse}
119
+ * @public
120
+ */
121
+ export default function Parse (schemaArg, env, leafParse = defaultLeafParse) {
105
122
  const schema = flattenSchema(schemaArg)
106
123
 
124
+ /**
125
+ * Parses and executes a serialized program.
126
+ *
127
+ * @param {string} program - JSON encoded program
128
+ * @returns {any|Promise<any>}
129
+ * @public
130
+ */
107
131
  return function parse (program) {
108
132
  debug('program', program)
109
133
  const awaits = []
110
134
 
111
- // Creates the list of awaits that will resolve in order
112
- // and also deserializes the leaves.
135
+ /**
136
+ * JSON reviver used during parsing.
137
+ * Converts encoded operators into runtime structures.
138
+ *
139
+ * @param {string} key
140
+ * @param {any} value
141
+ * @returns {any}
142
+ */
113
143
  const reviver = spy('revive', (key, value) => {
114
144
  if (value === null) return value
115
145
 
116
- if (!Array.isArray(value)) {
117
- return value
118
- }
146
+ if (!Array.isArray(value)) return value
119
147
 
120
148
  const [operator, ...rest] = value
121
149
 
122
150
  if (operator === 'leaf') {
123
- const leafValue = deserialize(rest[0])
151
+ const leafValue = leafParse(rest[0])
124
152
  return leafValue === undefined ? undefinedSentinel : leafValue
125
153
  }
126
154
 
127
155
  if (operator === 'await') {
128
156
  const [program] = rest
129
-
130
157
  return ['await', awaits.push(program) - 1]
131
158
  }
132
159
 
@@ -151,19 +178,17 @@ export default (schemaArg, env, deserialize = defaultLeafDeserializer) => {
151
178
  })
152
179
 
153
180
  const parsed = JSON.parse(program, reviver)
154
-
155
181
  debug('parsed', parsed)
156
182
 
157
183
  if (awaits.length) {
158
184
  debug('awaits', awaits)
159
185
 
160
186
  return (async function () {
161
- for (let index = 0; index < awaits.length; index++) {
162
- awaits[index] = await evaluate(awaits[index], awaits)
187
+ for (let i = 0; i < awaits.length; i++) {
188
+ awaits[i] = await evaluate(awaits[i], awaits)
163
189
  }
164
190
 
165
191
  debug('awaits(awaited)', awaits)
166
-
167
192
  return evaluate(parsed, awaits)
168
193
  })()
169
194
  }
package/references.mjs CHANGED
@@ -1,6 +1,19 @@
1
1
  import { awaitSymbol, call, ref, then, referenceSymbol } from './symbol.mjs'
2
2
 
3
+ /**
4
+ * Creates a callable reference node bound to a path.
5
+ * Used to build a lazy/instrumented execution structure.
6
+ *
7
+ * @param {Array<string>} path - Path representing the function location in schema.
8
+ * @returns {Function} Reference function with attached control methods (.then, .catch, toJSON).
9
+ */
3
10
  function instrument (path) {
11
+ /**
12
+ * Creates a callable reference invocation.
13
+ *
14
+ * @param {...any} args - Arguments passed to the call.
15
+ * @returns {Function} New instrumented reference node.
16
+ */
4
17
  function reference (...args) {
5
18
  const called = instrument(path)
6
19
 
@@ -13,6 +26,13 @@ function instrument (path) {
13
26
  return called
14
27
  }
15
28
 
29
+ /**
30
+ * Internal helper for building promise-like continuation nodes.
31
+ *
32
+ * @param {Function|null} resolve
33
+ * @param {Function|null} reject
34
+ * @returns {Function} Instrumented continuation node.
35
+ */
16
36
  function dotThen (resolve, reject) {
17
37
  const node = instrument(path)
18
38
 
@@ -26,18 +46,32 @@ function instrument (path) {
26
46
  return node
27
47
  }
28
48
 
49
+ /**
50
+ * Registers rejection handler (promise-style).
51
+ *
52
+ * @param {Function} reject
53
+ * @returns {Function}
54
+ */
29
55
  reference.catch = (reject) => {
30
56
  return dotThen(null, reject)
31
57
  }
32
58
 
59
+ /**
60
+ * Handles async chaining or awaiting logic.
61
+ *
62
+ * If resolve/reject contain a reference marker, it behaves like a .then chain.
63
+ * Otherwise it behaves like an await wrapper.
64
+ *
65
+ * @param {Function} resolve
66
+ * @param {Function} reject
67
+ * @returns {Function|any}
68
+ */
33
69
  reference.then = (resolve, reject) => {
34
- // That is how we know if it is an await or a .then call.
35
70
  if (resolve?.[referenceSymbol] || reject?.[referenceSymbol]) {
36
71
  return dotThen(resolve, reject)
37
72
  }
38
73
 
39
74
  const awaited = instrument(path)
40
- // Prevent infinite recur
41
75
  delete awaited.then
42
76
 
43
77
  awaited.toJSON = () => ({
@@ -48,6 +82,11 @@ function instrument (path) {
48
82
  return resolve(awaited)
49
83
  }
50
84
 
85
+ /**
86
+ * JSON representation of the reference path node.
87
+ *
88
+ * @returns {{ref: symbol, path: Array<string>}}
89
+ */
51
90
  reference.toJSON = () => ({
52
91
  [ref]: ref,
53
92
  path
@@ -58,17 +97,29 @@ function instrument (path) {
58
97
  return reference
59
98
  }
60
99
 
61
- export default function module (schema, parentPath = []) {
100
+ /**
101
+ * Builds a nested API structure from a schema definition.
102
+ *
103
+ * Schema supports:
104
+ * - string => leaf function node
105
+ * - [name, children] => namespace with nested schema
106
+ *
107
+ * @param {Array<string | [string, Array]>} schema
108
+ * @param {Array<string>} [parentPath=[]]
109
+ * @returns {Object} Nested instrumented API object
110
+ *
111
+ * @throws {Error} If schema format is invalid
112
+ * @public
113
+ */
114
+ export default function References (schema, parentPath = []) {
62
115
  return schema.reduce((acc, item) => {
63
116
  if (typeof item === 'string') {
64
- // leaf function
65
117
  acc[item] = instrument([...parentPath, item])
66
118
  } else if (Array.isArray(item)) {
67
119
  const [name, children] = item
68
120
 
69
121
  if (Array.isArray(children)) {
70
- // recurse: children can be strings or [name, children] arrays
71
- acc[name] = module(children, [...parentPath, name])
122
+ acc[name] = References(children, [...parentPath, name])
72
123
  } else {
73
124
  throw new Error(`Expected children array for namespace "${name}"`)
74
125
  }
package/stringify.mjs CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  const debug = Debug.extend('stringify')
11
11
 
12
12
  const keywords = ['ref', 'call', 'quote', 'await', 'leaf']
13
+
13
14
  const isKeyword = (v) => keywords.includes(v)
14
15
 
15
16
  const isPlainObject = (value) => {
@@ -18,26 +19,26 @@ const isPlainObject = (value) => {
18
19
  return proto === Object.prototype || proto === null
19
20
  }
20
21
 
21
- // Recursively transform a program tree, encoding SendScript operators and leaf values
22
22
  function transformValue (value, leafSerializer) {
23
23
  debug(value)
24
24
 
25
- if (value === null) {
26
- return null
27
- }
25
+ if (value === null) return null
28
26
 
29
- // Normalize SendScript wrapper functions (ref, call, await)
27
+ // unwrap function wrappers (instrumented nodes)
30
28
  if (typeof value === 'function' && typeof value.toJSON === 'function') {
31
29
  return transformValue(value.toJSON(), leafSerializer)
32
30
  }
33
31
 
34
- // Encode SendScript operators
35
32
  if (value && value[ref]) {
36
33
  return ['ref', ...value.path]
37
34
  }
38
35
 
39
36
  if (value && value[call]) {
40
- return ['call', transformValue(value.ref, leafSerializer), transformValue(value.args, leafSerializer)]
37
+ return [
38
+ 'call',
39
+ transformValue(value.ref, leafSerializer),
40
+ transformValue(value.args, leafSerializer)
41
+ ]
41
42
  }
42
43
 
43
44
  if (value && value[awaitSymbol]) {
@@ -45,22 +46,27 @@ function transformValue (value, leafSerializer) {
45
46
  }
46
47
 
47
48
  if (value && value[then]) {
48
- return ['then', transformValue(value.ref, leafSerializer), transformValue(value.resolve || null, leafSerializer), transformValue(value.reject || null, leafSerializer)]
49
+ return [
50
+ 'then',
51
+ transformValue(value.ref, leafSerializer),
52
+ transformValue(value.resolve || null, leafSerializer),
53
+ transformValue(value.reject || null, leafSerializer)
54
+ ]
49
55
  }
50
56
 
51
- // Handle arrays: quote keyword operators, transform other arrays recursively
52
57
  if (Array.isArray(value)) {
53
58
  const [operator, ...rest] = value
54
59
 
55
60
  if (isKeyword(operator)) {
56
- // Quote reserved keyword strings to preserve them as data
57
- return [['quote', operator], ...rest.map((item) => transformValue(item, leafSerializer))]
61
+ return [
62
+ ['quote', operator],
63
+ ...rest.map((item) => transformValue(item, leafSerializer))
64
+ ]
58
65
  }
59
66
 
60
67
  return value.map((item) => transformValue(item, leafSerializer))
61
68
  }
62
69
 
63
- // Recurse into plain objects
64
70
  if (isPlainObject(value)) {
65
71
  const result = {}
66
72
 
@@ -71,24 +77,46 @@ function transformValue (value, leafSerializer) {
71
77
  return result
72
78
  }
73
79
 
74
- // Encode non-JSON leaf values (Date, RegExp, BigInt, etc.)
75
80
  return ['leaf', leafSerializer(value)]
76
81
  }
77
82
 
78
- function strictStringify (x) {
83
+ /**
84
+ * Default strict serializer for leaf values.
85
+ *
86
+ * Rejects non-JSON-safe values.
87
+ *
88
+ * @param {any} x
89
+ * @returns {string}
90
+ * @public
91
+ */
92
+ function defaultLeafStringify (x) {
79
93
  const typeOf = typeof x
80
94
 
81
95
  if (typeOf === 'object' || typeOf === 'function' || x === undefined) {
82
- throw new SendScriptSerializationError(`Cannot and should not attempt to serialize ${x}`)
96
+ throw new SendScriptSerializationError(
97
+ `Cannot and should not attempt to serialize ${x}`
98
+ )
83
99
  }
84
100
 
85
101
  return JSON.stringify(x)
86
102
  }
87
103
 
88
- export default function stringify (leafSerializer = strictStringify) {
89
- function stringify (program) {
90
- return JSON.stringify(transformValue(program, leafSerializer))
104
+ /**
105
+ * Creates a stringify function for SendScript AST structures.
106
+ *
107
+ * @param {(value: any) => string} [leafSerializer=strictStringify]
108
+ * @returns {(program: any) => string}
109
+ * @public
110
+ */
111
+ export default function Stringify (leafStringify = defaultLeafStringify) {
112
+ /**
113
+ * Serializes a program into a JSON string representation.
114
+ *
115
+ * @param {any} program
116
+ * @returns {string}
117
+ * @public
118
+ */
119
+ return function stringify (program) {
120
+ return JSON.stringify(transformValue(program, leafStringify))
91
121
  }
92
-
93
- return stringify
94
122
  }