sendscript 2.3.2 → 2.3.3

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,16 @@ 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.3](https://github.com/bas080/sendscript/compare/v2.3.2...v2.3.3)
8
+
9
+ - Add JSDoc to relevant function for docs [`d1d3709`](https://github.com/bas080/sendscript/commit/d1d3709088a51b71651170ef642b013eedc9ca3a)
10
+ - Remove tests section from README [`5da7bb3`](https://github.com/bas080/sendscript/commit/5da7bb3f3b1dc8078683e076fe2b520bc6d28896)
11
+ - Add generated from JSDoc reference section [`08eb266`](https://github.com/bas080/sendscript/commit/08eb266b06d8998e6849dbc5c9738fbe782ada18)
12
+
7
13
  #### [v2.3.2](https://github.com/bas080/sendscript/compare/v2.3.1...v2.3.2)
8
14
 
15
+ > 14 April 2026
16
+
9
17
  - Format the README.mz file [`df43dbb`](https://github.com/bas080/sendscript/commit/df43dbb8b790cfc612536f49571f9fe4aad03837)
10
18
  - Move some commands from README to CONTRIBUTING [`45659c5`](https://github.com/bas080/sendscript/commit/45659c51354ac3bbe3bed5ef3826785d8a1a4a32)
11
19
  - 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 parse.mjs stringify.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
+ * [defaultLeafDeserializer](#defaultleafdeserializer)
15
+ * [Parse](#parse)
16
+ * [parse](#parse)
17
+ * [References](#references)
18
+ * [strictStringify](#strictstringify)
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,112 @@ 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
+ ### defaultLeafDeserializer
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
+ Creates a program parser for a given schema and environment.
137
+
138
+ The parser:
139
+
140
+ * resolves references into runtime functions
141
+ * deserializes leaf nodes
142
+ * collects and executes async awaits
143
+ * evaluates AST-like JSON programs
144
+
145
+ #### Parameters
146
+
147
+ * `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;
148
+ * `env` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** runtime environment for refs
149
+ * `deserialize` (optional, default `defaultLeafDeserializer`)
150
+
151
+ ### parse
152
+
153
+ Parses and executes a serialized program.
154
+
155
+ #### Parameters
156
+
157
+ * `program` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** JSON encoded program
158
+
159
+ Returns **(any | [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<any>)**&#x20;
160
+
161
+ ### References
162
+
163
+ Builds a nested API structure from a schema definition.
164
+
165
+ Schema supports:
166
+
167
+ * string => leaf function node
168
+ * \[name, children] => namespace with nested schema
169
+
170
+ #### Parameters
171
+
172
+ * `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;
173
+ * `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 `[]`)
174
+
175
+ <!---->
176
+
177
+ * Throws **[Error](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)** If schema format is invalid
178
+
179
+ Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Nested instrumented API object
180
+
181
+ ### strictStringify
182
+
183
+ Default strict serializer for leaf values.
184
+
185
+ Rejects non-JSON-safe values.
186
+
187
+ #### Parameters
188
+
189
+ * `x` **any**&#x20;
190
+
191
+ Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**&#x20;
192
+
193
+ ### Stringify
194
+
195
+ Creates a stringify function for SendScript AST structures.
196
+
197
+ #### Parameters
198
+
199
+ * `leafSerializer` (optional, default `strictStringify`)
200
+
201
+ ### stringify
202
+
203
+ Serializes a program into a JSON string representation.
204
+
205
+ #### Parameters
206
+
207
+ * `program` **any**&#x20;
208
+
209
+ Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**&#x20;
210
+
111
211
  ## Socket example
112
212
 
113
213
  For this example we'll use [socket.io][socket.io].
@@ -205,9 +305,8 @@ node ./example/client.socket.io.mjs
205
305
 
206
306
  pkill sendscript
207
307
  ```
208
- ```
209
- Result: 100
210
- ```
308
+
309
+ Result: 100
211
310
 
212
311
  ## Repl
213
312
 
@@ -259,18 +358,19 @@ defined properties and returned values.
259
358
 
260
359
  There is a good use-case to write a module in TypeScript.
261
360
 
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.
361
+ 1. Obviously the module would have the benefits that TypeScript offers when
362
+ coding.
363
+ 2. You can use tools like [typedoc][typedoc] to generate docs from your types to
364
+ share with consumers of your API.
365
+ 3. You can use the types of the module to coerce your client to adopt the
366
+ module's type.
268
367
 
269
368
  Let's say we have this module which we use on the server.
270
369
 
271
370
  ```bash
272
371
  cat ./example/typescript/math.ts
273
372
  ```
373
+
274
374
  ```ts
275
375
  export const add = (a: number, b: number) => a + b
276
376
  export const square = (a: number) => a * a
@@ -281,6 +381,7 @@ We can then coerce the types of the instrumented stubs.
281
381
  ```bash
282
382
  cat ./example/typescript/client.ts
283
383
  ```
384
+
284
385
  ```ts
285
386
  import math from './math.client.ts'
286
387
  import Stringify from 'sendscript/stringify.mjs'
@@ -306,7 +407,7 @@ npx typedoc --plugin typedoc-plugin-markdown --out ./example/typescript/docs ./e
306
407
 
307
408
  You can see the docs [here](./example/typescript/docs/globals.md)
308
409
 
309
- > [!NOTE] Although type coercion on the client side can improve the development
410
+ > \[!NOTE] Although type coercion on the client side can improve the development
310
411
  > experience, it does not represent the actual type. Values are subject to
311
412
  > serialization and deserialization.
312
413
 
@@ -373,9 +474,9 @@ export function createUser(user) {
373
474
 
374
475
  **Benefits**:
375
476
 
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.
477
+ * Ensures arguments match expected types and shapes.
478
+ * Throws structured errors that can be propagated to clients.
479
+ * Works with TypeScript for automatic type inference.
379
480
 
380
481
  ## Leaf Serializer
381
482
 
@@ -447,32 +548,6 @@ const result = parse(json)
447
548
  The leaf wrapper format is `['leaf', serializedPayload]`, making it unambiguous
448
549
  and safe from colliding with SendScript operators.
449
550
 
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
551
  ## Changelog
477
552
 
478
553
  The [changelog][changelog] is generated using the useful
@@ -486,12 +561,18 @@ npx auto-changelog -p
486
561
 
487
562
  See the [LICENSE.txt][license] file for details.
488
563
 
489
- ## Roadmap
564
+ ## Issues
490
565
 
491
- - [ ] Support for simple lambdas to compose functions more easily.
566
+ See [issues][issues] for roadmap and known bugs.
492
567
 
493
568
  [license]: ./LICENSE.txt
569
+
494
570
  [socket.io]: https://socket.io/
571
+
495
572
  [changelog]: ./CHANGELOG.md
573
+
496
574
  [auto-changelog]: https://www.npmjs.com/package/auto-changelog
575
+
497
576
  [typedoc]: https://github.com/TypeStrong/typedoc
577
+
578
+ [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/08eb266b06d8998e6849dbc5c9738fbe782ada18/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/08eb266b06d8998e6849dbc5c9738fbe782ada18/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.3",
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,23 +91,56 @@ const evaluate = spy('eval', (value, awaits = []) => {
99
91
  return value
100
92
  })
101
93
 
94
+ /**
95
+ * Default deserializer for leaf nodes.
96
+ *
97
+ * @param {string} text
98
+ * @returns {any}
99
+ * @public
100
+ */
102
101
  const defaultLeafDeserializer = (text) => JSON.parse(text)
103
102
 
104
- export default (schemaArg, env, deserialize = defaultLeafDeserializer) => {
103
+ /**
104
+ * Creates a program parser for a given schema and environment.
105
+ *
106
+ * The parser:
107
+ * - resolves references into runtime functions
108
+ * - deserializes leaf nodes
109
+ * - collects and executes async awaits
110
+ * - evaluates AST-like JSON programs
111
+ *
112
+ * @param {Array<string | [string, Array]>} schemaArg
113
+ * @param {Object} env - runtime environment for refs
114
+ * @param {(text: string) => any} [deserialize=defaultLeafDeserializer]
115
+ * @returns {(program: string) => any|Promise<any>}
116
+ * @public
117
+ */
118
+ export default function Parse (schemaArg, env, deserialize = defaultLeafDeserializer) {
105
119
  const schema = flattenSchema(schemaArg)
106
120
 
121
+ /**
122
+ * Parses and executes a serialized program.
123
+ *
124
+ * @param {string} program - JSON encoded program
125
+ * @returns {any|Promise<any>}
126
+ * @public
127
+ */
107
128
  return function parse (program) {
108
129
  debug('program', program)
109
130
  const awaits = []
110
131
 
111
- // Creates the list of awaits that will resolve in order
112
- // and also deserializes the leaves.
132
+ /**
133
+ * JSON reviver used during parsing.
134
+ * Converts encoded operators into runtime structures.
135
+ *
136
+ * @param {string} key
137
+ * @param {any} value
138
+ * @returns {any}
139
+ */
113
140
  const reviver = spy('revive', (key, value) => {
114
141
  if (value === null) return value
115
142
 
116
- if (!Array.isArray(value)) {
117
- return value
118
- }
143
+ if (!Array.isArray(value)) return value
119
144
 
120
145
  const [operator, ...rest] = value
121
146
 
@@ -126,7 +151,6 @@ export default (schemaArg, env, deserialize = defaultLeafDeserializer) => {
126
151
 
127
152
  if (operator === 'await') {
128
153
  const [program] = rest
129
-
130
154
  return ['await', awaits.push(program) - 1]
131
155
  }
132
156
 
@@ -151,19 +175,17 @@ export default (schemaArg, env, deserialize = defaultLeafDeserializer) => {
151
175
  })
152
176
 
153
177
  const parsed = JSON.parse(program, reviver)
154
-
155
178
  debug('parsed', parsed)
156
179
 
157
180
  if (awaits.length) {
158
181
  debug('awaits', awaits)
159
182
 
160
183
  return (async function () {
161
- for (let index = 0; index < awaits.length; index++) {
162
- awaits[index] = await evaluate(awaits[index], awaits)
184
+ for (let i = 0; i < awaits.length; i++) {
185
+ awaits[i] = await evaluate(awaits[i], awaits)
163
186
  }
164
187
 
165
188
  debug('awaits(awaited)', awaits)
166
-
167
189
  return evaluate(parsed, awaits)
168
190
  })()
169
191
  }
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,21 +77,45 @@ 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
 
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
+ */
78
92
  function strictStringify (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) {
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 (leafSerializer = strictStringify) {
112
+ /**
113
+ * Serializes a program into a JSON string representation.
114
+ *
115
+ * @param {any} program
116
+ * @returns {string}
117
+ * @public
118
+ */
89
119
  function stringify (program) {
90
120
  return JSON.stringify(transformValue(program, leafSerializer))
91
121
  }