sendscript 2.3.9 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,8 +4,24 @@ 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.4.1](https://github.com/bas080/sendscript/compare/v2.4.0...v2.4.1)
8
+
9
+ - Update tap and typedoc version [`3f10410`](https://github.com/bas080/sendscript/commit/3f1041020e00331ff30ffb5f972738dbaf7a50f3)
10
+ - Update tap to 21.7.1 [`d1b5bbd`](https://github.com/bas080/sendscript/commit/d1b5bbd4cb7012e6606973d3d23d08df56cadca2)
11
+ - Document limitations of sendscript [`f577499`](https://github.com/bas080/sendscript/commit/f577499279b85e04ea589a3bcb1866d145216f00)
12
+
13
+ #### [v2.4.0](https://github.com/bas080/sendscript/compare/v2.3.9...v2.4.0)
14
+
15
+ > 16 April 2026
16
+
17
+ - Update tap to 4.5.4 [`5249a85`](https://github.com/bas080/sendscript/commit/5249a85a08b10485f0d64058fd2c8bcaae84edda)
18
+ - Add fn and args support [`6110654`](https://github.com/bas080/sendscript/commit/6110654fa039d17cd9ba0e15f749756b615b5d09)
19
+ - Move transport agnostic section to socket example [`3a92257`](https://github.com/bas080/sendscript/commit/3a9225787df16a0fd83293bfa14d91b74a46d070)
20
+
7
21
  #### [v2.3.9](https://github.com/bas080/sendscript/compare/v2.3.8...v2.3.9)
8
22
 
23
+ > 14 April 2026
24
+
9
25
  - Remove schema description from reference and schema shape [`7126829`](https://github.com/bas080/sendscript/commit/7126829b5968588d7c9377ee643b88465f3fe16e)
10
26
 
11
27
  #### [v2.3.8](https://github.com/bas080/sendscript/compare/v2.3.7...v2.3.8)
package/CONTRIBUTING.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Contributing
2
2
 
3
+ The pre-push git hook.
4
+
5
+ ```bash cat - > .git/hooks/pre-push
6
+ #!/usr/bin/env bash
7
+
8
+ set -xeuo pipefail
9
+
10
+ npm t
11
+ npx standard
12
+ npx prettier -c --parser markdown *.mz
13
+ ```
14
+
3
15
  For the README.mz examples to work we need sendscript to be linked.
4
16
 
5
17
  ```bash bash > /dev/null
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SendScript
2
2
 
3
- Write JS code that you can run on servers, browsers or other clients.
3
+ RPC and no-build with composable function calls in a single payload.
4
4
 
5
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)
@@ -28,6 +28,9 @@ Write JS code that you can run on servers, browsers or other clients.
28
28
  - [Promises](#promises)
29
29
  * [.then / .catch](#then--catch)
30
30
  * [await](#await)
31
+ - [Promises](#promises-1)
32
+ * [.then / .catch](#then--catch-1)
33
+ * [await](#await-1)
31
34
  - [TypeScript](#typescript)
32
35
  - [Schema and Nested Modules](#schema-and-nested-modules)
33
36
  * [Defining a Nested Module](#defining-a-nested-module)
@@ -35,6 +38,12 @@ Write JS code that you can run on servers, browsers or other clients.
35
38
  * [Validating structured input](#validating-structured-input)
36
39
  - [Leaf Serializer](#leaf-serializer)
37
40
  * [Example with superjson](#example-with-superjson)
41
+ - [Limitations](#limitations)
42
+ * [Chaining APIs](#chaining-apis)
43
+ * [Callbacks](#callbacks)
44
+ * [Error handling](#error-handling)
45
+ - [Tests](#tests)
46
+ - [Formatting](#formatting)
38
47
  - [Changelog](#changelog)
39
48
  - [License](#license)
40
49
  - [Issues](#issues)
@@ -114,17 +123,14 @@ core or around it. Things like supporting more complex (de)serializers, errors
114
123
  and maybe mixing client functions with sendscript programs. Contact me if I have
115
124
  piqued your interest.
116
125
 
117
- ***
118
-
119
- SendScript leaves it up to you to choose HTTP, web-sockets or any other method
120
- of communication between servers and clients that best fits your needs.
121
-
122
126
  ## Reference
123
127
 
124
128
  <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
125
129
 
126
130
  ### defaultLeafParse
127
131
 
132
+ [parse.mjs:49-49](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/parse.mjs#L49-L49 "Source code on GitHub")
133
+
128
134
  Default deserializer for leaf nodes.
129
135
 
130
136
  #### Parameters
@@ -135,6 +141,8 @@ Returns **any**&#x20;
135
141
 
136
142
  ### Parse
137
143
 
144
+ [parse.mjs:68-215](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/parse.mjs#L68-L215 "Source code on GitHub")
145
+
138
146
  #### Parameters
139
147
 
140
148
  * `schema` **[Schema](#schema)**&#x20;
@@ -145,6 +153,8 @@ Returns **[parse](#parse)**&#x20;
145
153
 
146
154
  ### parse
147
155
 
156
+ [parse.mjs:78-214](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/parse.mjs#L78-L214 "Source code on GitHub")
157
+
148
158
  Parses and executes a serialized program.
149
159
 
150
160
  #### Parameters
@@ -155,6 +165,8 @@ Returns **(any | [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Ref
155
165
 
156
166
  ### References
157
167
 
168
+ [references.mjs:109-127](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/references.mjs#L109-L127 "Source code on GitHub")
169
+
158
170
  Builds a nested API structure from a schema definition.
159
171
 
160
172
  #### Parameters
@@ -170,12 +182,16 @@ Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/G
170
182
 
171
183
  ### SchemaNode
172
184
 
185
+ [schema.mjs:1-19](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/schema.mjs#L1-L6 "Source code on GitHub")
186
+
173
187
  A single schema node.
174
188
 
175
189
  Type: ([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) | \[[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), [Schema](#schema)])
176
190
 
177
191
  ### Schema
178
192
 
193
+ [schema.mjs:1-19](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/schema.mjs#L8-L18 "Source code on GitHub")
194
+
179
195
  A schema defines the structure of the runtime API tree.
180
196
 
181
197
  * string → leaf node
@@ -187,6 +203,8 @@ Type: [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global
187
203
 
188
204
  ### defaultLeafStringify
189
205
 
206
+ [stringify.mjs:37-47](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/stringify.mjs#L37-L47 "Source code on GitHub")
207
+
190
208
  Default strict serializer for leaf values.
191
209
 
192
210
  Rejects non-JSON-safe values.
@@ -199,6 +217,8 @@ Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/G
199
217
 
200
218
  ### Stringify
201
219
 
220
+ [stringify.mjs:56-157](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/stringify.mjs#L56-L157 "Source code on GitHub")
221
+
202
222
  Creates a stringify function for SendScript AST structures.
203
223
 
204
224
  #### Parameters
@@ -209,6 +229,8 @@ Returns **[stringify](#stringify)**&#x20;
209
229
 
210
230
  ### stringify
211
231
 
232
+ [stringify.mjs:64-156](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/stringify.mjs#L64-L156 "Source code on GitHub")
233
+
212
234
  Serializes a program into a JSON string representation.
213
235
 
214
236
  #### Parameters
@@ -219,6 +241,9 @@ Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/G
219
241
 
220
242
  ## Socket example
221
243
 
244
+ SendScript leaves it up to you to choose HTTP, web-sockets or any other method
245
+ of communication between servers and clients that best fits your needs.
246
+
222
247
  For this example we'll use [socket.io][socket.io].
223
248
 
224
249
  ### Module
@@ -319,11 +344,43 @@ pkill sendscript
319
344
 
320
345
  ## Repl
321
346
 
322
- Sendscript ships with a barebones (no-dependencies) node-repl script. One can
347
+ Sendscript ships with a bare-bones (no-dependencies) node-repl script. One can
323
348
  run it by simply typing `sendscript` in their console.
324
349
 
325
- > Use the `DEBUG='*'` to enable all logs or `DEBUG='sendscript:*'` for
326
- > printingonly sendscript logs.
350
+ > Use the `DEBUG='*'` to enable all logs or `DEBUG='sendscript:*'` for printing
351
+ > only sendscript logs.
352
+
353
+ ## Promises
354
+
355
+ ### .then / .catch
356
+
357
+ Supported since vs `v2.3`.
358
+
359
+ ```js
360
+ const getOrCreatePost = send(createPost(title).catch(createPost(title)))
361
+ ```
362
+
363
+ You will likely need to define better helpers that makes it safer to handle
364
+ rejections and work with promises. It is however sensible to have this basic
365
+ behavior for the sendscript DSL and parser.
366
+
367
+ ### await
368
+
369
+ SendScript as of `v2.4` supports functions to do basic templating. It does not
370
+ support async functions and will throw an error when you define one.
371
+
372
+ Under the hood the function is called when it is being parsed. Make sure you
373
+ understand what you are doing when mixing client and server functions.
374
+
375
+ ```js
376
+ map((a) => add(a, 1))([1, 2, 3]))
377
+ ```
378
+
379
+ It also supports nested functions.
380
+
381
+ ```js
382
+ map(call)(map((a) => () => add(a, 1))([1, 2, 3]))
383
+ ```
327
384
 
328
385
  ## Promises
329
386
 
@@ -549,7 +606,7 @@ const env = {
549
606
  }
550
607
 
551
608
  // Parse with custom leaf deserializer
552
- const parse = Parse(schema, env, leadDeserializer)
609
+ const parse = Parse(schema, env, leafDeserializer)
553
610
 
554
611
  const result = parse(json)
555
612
  ```
@@ -557,6 +614,93 @@ const result = parse(json)
557
614
  The leaf wrapper format is `['leaf', serializedPayload]`, making it unambiguous
558
615
  and safe from colliding with SendScript operators.
559
616
 
617
+ ## Limitations
618
+
619
+ The stubs the `references` function returns are limited in the way they can be
620
+ used. This has to do with the fact that they are to be serialized.
621
+
622
+ ### Chaining APIs
623
+
624
+ Currently you cannot do chaining APIs. `lib().doThis().doThat()`. It likely is
625
+ possible to implement but will require more extensive schemas and obviously more
626
+ code to create the AST.
627
+
628
+ ### Callbacks
629
+
630
+ Although it is possible to mix client and server functions, it works very
631
+ different to ordinary functions. Client functions can be used but should be seen
632
+ as a templating tool to make sendscript programs; just like one would use
633
+ JavaScript with react templates. `items.map(deleteItem)` would return an array
634
+ of sendscript function calls which can be given to sendscript's parse.
635
+
636
+ Client functions cannot be called by sendscript functions (as of yet) since we
637
+ cannot serialize client functions. No work has been done to have the server send
638
+ back intermediate values to perform client function calls or by performing
639
+ smaller sendscript program payloads that are passed to the client. Very
640
+ interesting stuff to look into. You can achieve this now but it looks less clean
641
+ because you have to do `send` calls which is a bit manual.
642
+
643
+ ```js
644
+ await send(updateUser(id, merge(await send(getUser(id)), { ...newValues })))
645
+ ```
646
+
647
+ It might be interesting to allow configuration to create references that will
648
+ trigger a send whenever await is called. That would remove the ability to create
649
+ a single payload whenever using await. You can then write the above in the
650
+ following manner.
651
+
652
+ ```js
653
+ await updateUser(id, merge(await getUser(id), { ...newValues }))
654
+ ```
655
+
656
+ Possible footgun is that updateUser is only performed when awaited (.then is
657
+ called). This lazy behavior can trip users. This footgun could be resolved by
658
+ checking if any outstanding work exists at the end of a step or the beginning of
659
+ a new step.
660
+
661
+ ### Error handling
662
+
663
+ Sendscript does not have builtin tools to handle errors. You can write your own
664
+ utilities for that or use things like \[Ramda's tryCatch]\[tryCatch]. This will
665
+ likely never be supported as JS try catch does not have a return value. It might
666
+ make sense to wrap everything that can throw in promises which is easy to do
667
+ with async functions.
668
+
669
+ ## Tests
670
+
671
+ Tests with 100% code coverage.
672
+
673
+ ```bash
674
+ npm t -- -R silent
675
+ npm t -- report text-summary
676
+ ```
677
+
678
+ ```
679
+
680
+ > sendscript@2.4.1 test
681
+ > tap -R silent
682
+
683
+
684
+ > sendscript@2.4.1 test
685
+ > tap report text-summary
686
+
687
+
688
+ =============================== Coverage summary ===============================
689
+ Statements : 100% ( 523/523 )
690
+ Branches : 100% ( 153/153 )
691
+ Functions : 100% ( 25/25 )
692
+ Lines : 100% ( 523/523 )
693
+ ================================================================================
694
+ ```
695
+
696
+ ## Formatting
697
+
698
+ Standard because no config.
699
+
700
+ ```bash
701
+ npx standard
702
+ ```
703
+
560
704
  ## Changelog
561
705
 
562
706
  The [changelog][changelog] is generated using the useful
package/README.mz CHANGED
@@ -1,6 +1,6 @@
1
1
  # SendScript
2
2
 
3
- Write JS code that you can run on servers, browsers or other clients.
3
+ RPC and no-build with composable function calls in a single payload.
4
4
 
5
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)
@@ -74,17 +74,15 @@ core or around it. Things like supporting more complex (de)serializers, errors
74
74
  and maybe mixing client functions with sendscript programs. Contact me if I have
75
75
  piqued your interest.
76
76
 
77
- ---
78
-
79
- SendScript leaves it up to you to choose HTTP, web-sockets or any other method
80
- of communication between servers and clients that best fits your needs.
81
-
82
77
  ## Reference
83
78
 
84
79
  <!-- Reference -->
85
80
 
86
81
  ## Socket example
87
82
 
83
+ SendScript leaves it up to you to choose HTTP, web-sockets or any other method
84
+ of communication between servers and clients that best fits your needs.
85
+
88
86
  For this example we'll use [socket.io][socket.io].
89
87
 
90
88
  ### Module
@@ -183,11 +181,43 @@ pkill sendscript
183
181
 
184
182
  ## Repl
185
183
 
186
- Sendscript ships with a barebones (no-dependencies) node-repl script. One can
184
+ Sendscript ships with a bare-bones (no-dependencies) node-repl script. One can
187
185
  run it by simply typing `sendscript` in their console.
188
186
 
189
- > Use the `DEBUG='*'` to enable all logs or `DEBUG='sendscript:*'` for
190
- > printingonly sendscript logs.
187
+ > Use the `DEBUG='*'` to enable all logs or `DEBUG='sendscript:*'` for printing
188
+ > only sendscript logs.
189
+
190
+ ## Promises
191
+
192
+ ### .then / .catch
193
+
194
+ Supported since vs `v2.3`.
195
+
196
+ ```js
197
+ const getOrCreatePost = send(createPost(title).catch(createPost(title)))
198
+ ```
199
+
200
+ You will likely need to define better helpers that makes it safer to handle
201
+ rejections and work with promises. It is however sensible to have this basic
202
+ behavior for the sendscript DSL and parser.
203
+
204
+ ### await
205
+
206
+ SendScript as of `v2.4` supports functions to do basic templating. It does not
207
+ support async functions and will throw an error when you define one.
208
+
209
+ Under the hood the function is called when it is being parsed. Make sure you
210
+ understand what you are doing when mixing client and server functions.
211
+
212
+ ```js
213
+ map((a) => add(a, 1))([1, 2, 3]))
214
+ ```
215
+
216
+ It also supports nested functions.
217
+
218
+ ```js
219
+ map(call)(map((a) => () => add(a, 1))([1, 2, 3]))
220
+ ```
191
221
 
192
222
  ## Promises
193
223
 
@@ -391,7 +421,7 @@ const env = {
391
421
  }
392
422
 
393
423
  // Parse with custom leaf deserializer
394
- const parse = Parse(schema, env, leadDeserializer)
424
+ const parse = Parse(schema, env, leafDeserializer)
395
425
 
396
426
  const result = parse(json)
397
427
  ```
@@ -399,6 +429,75 @@ const result = parse(json)
399
429
  The leaf wrapper format is `['leaf', serializedPayload]`, making it unambiguous
400
430
  and safe from colliding with SendScript operators.
401
431
 
432
+ ## Limitations
433
+
434
+ The stubs the `references` function returns are limited in the way they can be
435
+ used. This has to do with the fact that they are to be serialized.
436
+
437
+ ### Chaining APIs
438
+
439
+ Currently you cannot do chaining APIs. `lib().doThis().doThat()`. It likely is
440
+ possible to implement but will require more extensive schemas and obviously more
441
+ code to create the AST.
442
+
443
+ ### Callbacks
444
+
445
+ Although it is possible to mix client and server functions, it works very
446
+ different to ordinary functions. Client functions can be used but should be seen
447
+ as a templating tool to make sendscript programs; just like one would use
448
+ JavaScript with react templates. `items.map(deleteItem)` would return an array
449
+ of sendscript function calls which can be given to sendscript's parse.
450
+
451
+ Client functions cannot be called by sendscript functions (as of yet) since we
452
+ cannot serialize client functions. No work has been done to have the server send
453
+ back intermediate values to perform client function calls or by performing
454
+ smaller sendscript program payloads that are passed to the client. Very
455
+ interesting stuff to look into. You can achieve this now but it looks less clean
456
+ because you have to do `send` calls which is a bit manual.
457
+
458
+ ```js
459
+ await send(updateUser(id, merge(await send(getUser(id)), { ...newValues })))
460
+ ```
461
+
462
+ It might be interesting to allow configuration to create references that will
463
+ trigger a send whenever await is called. That would remove the ability to create
464
+ a single payload whenever using await. You can then write the above in the
465
+ following manner.
466
+
467
+ ```js
468
+ await updateUser(id, merge(await getUser(id), { ...newValues }))
469
+ ```
470
+
471
+ Possible footgun is that updateUser is only performed when awaited (.then is
472
+ called). This lazy behavior can trip users. This footgun could be resolved by
473
+ checking if any outstanding work exists at the end of a step or the beginning of
474
+ a new step.
475
+
476
+ ### Error handling
477
+
478
+ Sendscript does not have builtin tools to handle errors. You can write your own
479
+ utilities for that or use things like [Ramda's tryCatch][tryCatch]. This will
480
+ likely never be supported as JS try catch does not have a return value. It might
481
+ make sense to wrap everything that can throw in promises which is easy to do
482
+ with async functions.
483
+
484
+ ## Tests
485
+
486
+ Tests with 100% code coverage.
487
+
488
+ ```bash bash
489
+ npm t -- -R silent
490
+ npm t -- report text-summary
491
+ ```
492
+
493
+ ## Formatting
494
+
495
+ Standard because no config.
496
+
497
+ ```bash bash
498
+ npx standard
499
+ ```
500
+
402
501
  ## Changelog
403
502
 
404
503
  The [changelog][changelog] is generated using the useful
@@ -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/7126829b5968588d7c9377ee643b88465f3fe16e/example/typescript/math.ts#L1)
11
+ Defined in: [math.ts:1](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/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/7126829b5968588d7c9377ee643b88465f3fe16e/example/typescript/math.ts#L2)
11
+ Defined in: [math.ts:2](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/example/typescript/math.ts#L2)
12
12
 
13
13
  ## Parameters
14
14
 
package/index.test.mjs CHANGED
@@ -34,6 +34,7 @@ const myModuleOrig = {
34
34
  hello: 'world',
35
35
  noop: () => {},
36
36
  resolve: (x) => Promise.resolve(x),
37
+ call: (fn, ...args) => fn(...args),
37
38
  reject: x => Promise.reject(x),
38
39
  asyncFn: async () => 'my-async-function',
39
40
  instanceOf: (x, t) => x instanceof t,
@@ -85,15 +86,59 @@ test('should evaluate basic expressions correctly', async (t) => {
85
86
  always,
86
87
  multiply3,
87
88
  sayHello,
89
+ call,
88
90
  nested
89
91
  } = api
90
92
 
93
+ t.test('throws when async fn is provided', async t => {
94
+ t.throws(() => run(identity(async () => {})))
95
+ t.end()
96
+ })
97
+
98
+ t.test('supports fns with arguments', async t => {
99
+ t.same(run(map((a) => add(a, 1))([1, 2, 3])), [2, 3, 4])
100
+ t.end()
101
+ })
102
+
103
+ t.test('nested fns with arguments', async t => {
104
+ t.same(run(
105
+ map(call)(
106
+ map((a) => () => add(a, 1))([1, 2, 3])
107
+ )), [2, 3, 4])
108
+ t.end()
109
+ })
110
+
111
+ t.test('supports fns without arguments', async t => {
112
+ t.equal(run(() => add(1, 2))(), 3)
113
+ t.equal(run(always(() => 42)()()), 42)
114
+ t.equal(run(always(x => add(x, 42))()(2)), 44)
115
+ t.end()
116
+ })
117
+
118
+ t.test('supports binding reference functions on client', async t => {
119
+ const plusOne = add.bind(null, 1)
120
+
121
+ t.equal(run(plusOne(1)), 2)
122
+ t.equal(await run(resolve(1).then(plusOne)), 2)
123
+
124
+ // Even this works.
125
+ t.equal(await run(resolve(1).then(v => plusOne(v))), 2)
126
+
127
+ t.end()
128
+ })
129
+
91
130
  t.test('supports .then', async t => {
131
+ const something = 30
132
+
92
133
  t.rejects(run(reject('error')))
93
134
 
94
135
  t.equal(await run(reject('error').catch(sayHello)), 'hello error')
95
136
  t.equal(await run(reject('error').then(null, sayHello)), 'hello error')
96
137
 
138
+ // .then with fns just works and I do not know why.
139
+ t.equal(await run(resolve(32).then(() => add(something, 1))), 31)
140
+ t.equal(await run(resolve(32).then(value => add(something, value))), 62)
141
+
97
142
  t.equal(await run(resolve('value').then(sayHello)), 'hello value')
98
143
  t.equal(await run(resolve('value').then(sayHello, noop)), 'hello value')
99
144
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sendscript",
3
- "version": "2.3.9",
3
+ "version": "2.4.1",
4
4
  "description": "Blur the line between server and client code.",
5
5
  "module": true,
6
6
  "main": "index.mjs",
@@ -25,7 +25,7 @@
25
25
  "license": "MIT",
26
26
  "devDependencies": {
27
27
  "superjson": "^2.2.6",
28
- "tap": "^21.6.3",
28
+ "tap": "^21.7.1",
29
29
  "tape-check": "^1.0.0-rc.0",
30
30
  "typedoc": "^0.28.18",
31
31
  "typedoc-plugin-markdown": "^4.11.0"
package/parse.mjs CHANGED
@@ -39,58 +39,6 @@ const spy = (type, fn) => (...args) => {
39
39
  return value
40
40
  }
41
41
 
42
- const evaluate = spy('eval', (value, awaits = []) => {
43
- if (value === undefinedSentinel) return undefined
44
-
45
- if (Array.isArray(value)) {
46
- const [operator, ...rest] = value
47
-
48
- if (operator === 'await') {
49
- const [index] = rest
50
- return awaits[index]
51
- }
52
-
53
- if (operator === 'then') {
54
- const [v, onResolve, onReject] = rest
55
- return evaluate(v, awaits).then(
56
- evaluate(onResolve, awaits),
57
- evaluate(onReject, awaits)
58
- )
59
- }
60
-
61
- if (operator === 'call') {
62
- let [fn, args] = rest
63
- fn = evaluate(fn, awaits)
64
-
65
- for (let i = 0; i < args.length; i++) {
66
- args[i] = evaluate(args[i], awaits)
67
- }
68
-
69
- return fn(...args)
70
- }
71
-
72
- if (operator === 'quote') {
73
- const [quoted] = rest
74
- return quoted
75
- }
76
-
77
- for (let i = 0; i < value.length; i++) {
78
- value[i] = evaluate(value[i], awaits)
79
- }
80
-
81
- return value
82
- }
83
-
84
- if (isPlainObject(value)) {
85
- for (const key of Object.keys(value)) {
86
- value[key] = evaluate(value[key], awaits)
87
- }
88
- return value
89
- }
90
-
91
- return value
92
- })
93
-
94
42
  /**
95
43
  * Default deserializer for leaf nodes.
96
44
  *
@@ -131,6 +79,76 @@ export default function Parse (schema, env, leafParse = defaultLeafParse) {
131
79
  debug('program', program)
132
80
  const awaits = []
133
81
 
82
+ const evaluateOuter = spy('eval', (env, value) => {
83
+ const evaluate = evaluateOuter.bind(null, env)
84
+
85
+ if (value === undefinedSentinel) return undefined
86
+
87
+ if (Array.isArray(value)) {
88
+ const [operator, ...rest] = value
89
+
90
+ if (operator === 'await') {
91
+ const [index] = rest
92
+ return awaits[index]
93
+ }
94
+
95
+ if (operator === 'then') {
96
+ const [v, onResolve, onReject] = rest
97
+ return evaluate(v).then(
98
+ evaluate(onResolve),
99
+ evaluate(onReject)
100
+ )
101
+ }
102
+
103
+ if (operator === 'fn') {
104
+ const [argIds, body] = [...rest]
105
+
106
+ return (...args) => {
107
+ const newEnv = Object.create(env)
108
+
109
+ argIds.forEach((id, index) => {
110
+ newEnv[id] = args[index]
111
+ })
112
+
113
+ return evaluateOuter(newEnv, body)
114
+ }
115
+ }
116
+
117
+ if (operator === 'arg') {
118
+ const [id] = rest
119
+
120
+ return env[id]
121
+ }
122
+
123
+ if (operator === 'call') {
124
+ let [fn, args] = rest
125
+ fn = evaluate(fn)
126
+
127
+ return fn(...args.map(evaluate))
128
+ }
129
+
130
+ if (operator === 'quote') {
131
+ const [quoted] = rest
132
+ return quoted
133
+ }
134
+
135
+ for (let i = 0; i < value.length; i++) {
136
+ value[i] = evaluate(value[i])
137
+ }
138
+
139
+ return value
140
+ }
141
+
142
+ if (isPlainObject(value)) {
143
+ for (const key of Object.keys(value)) {
144
+ value[key] = evaluate(value[key])
145
+ }
146
+ return value
147
+ }
148
+
149
+ return value
150
+ })
151
+
134
152
  /**
135
153
  * JSON reviver used during parsing.
136
154
  * Converts encoded operators into runtime structures.
@@ -184,14 +202,14 @@ export default function Parse (schema, env, leafParse = defaultLeafParse) {
184
202
 
185
203
  return (async function () {
186
204
  for (let i = 0; i < awaits.length; i++) {
187
- awaits[i] = await evaluate(awaits[i], awaits)
205
+ awaits[i] = await evaluateOuter({}, awaits[i])
188
206
  }
189
207
 
190
208
  debug('awaits(awaited)', awaits)
191
- return evaluate(parsed, awaits)
209
+ return evaluateOuter({}, parsed)
192
210
  })()
193
211
  }
194
212
 
195
- return evaluate(parsed)
213
+ return evaluateOuter({}, parsed)
196
214
  }
197
215
  }
package/stringify.mjs CHANGED
@@ -2,14 +2,20 @@ import Debug from './debug.mjs'
2
2
  import { SendScriptSerializationError } from './error.mjs'
3
3
  import {
4
4
  awaitSymbol,
5
+ referenceSymbol,
5
6
  then,
6
7
  call,
7
8
  ref
8
9
  } from './symbol.mjs'
9
10
 
11
+ const asyncFunction = async function () {}
12
+ asyncFunction()
13
+ const AsyncFunction = (asyncFunction).constructor
14
+
10
15
  const debug = Debug.extend('stringify')
11
16
 
12
- const keywords = ['ref', 'call', 'quote', 'await', 'leaf']
17
+ const keywords = ['ref', 'call', 'quote', 'await', 'leaf', 'arg', 'fn']
18
+ const argSymbol = Symbol('arg')
13
19
 
14
20
  const isKeyword = (v) => keywords.includes(v)
15
21
 
@@ -19,67 +25,6 @@ const isPlainObject = (value) => {
19
25
  return proto === Object.prototype || proto === null
20
26
  }
21
27
 
22
- function transformValue (value, leafSerializer) {
23
- debug(value)
24
-
25
- if (value === null) return null
26
-
27
- // unwrap function wrappers (instrumented nodes)
28
- if (typeof value === 'function' && typeof value.toJSON === 'function') {
29
- return transformValue(value.toJSON(), leafSerializer)
30
- }
31
-
32
- if (value && value[ref]) {
33
- return ['ref', ...value.path]
34
- }
35
-
36
- if (value && value[call]) {
37
- return [
38
- 'call',
39
- transformValue(value.ref, leafSerializer),
40
- transformValue(value.args, leafSerializer)
41
- ]
42
- }
43
-
44
- if (value && value[awaitSymbol]) {
45
- return ['await', transformValue(value.ref, leafSerializer)]
46
- }
47
-
48
- if (value && value[then]) {
49
- return [
50
- 'then',
51
- transformValue(value.ref, leafSerializer),
52
- transformValue(value.resolve || null, leafSerializer),
53
- transformValue(value.reject || null, leafSerializer)
54
- ]
55
- }
56
-
57
- if (Array.isArray(value)) {
58
- const [operator, ...rest] = value
59
-
60
- if (isKeyword(operator)) {
61
- return [
62
- ['quote', operator],
63
- ...rest.map((item) => transformValue(item, leafSerializer))
64
- ]
65
- }
66
-
67
- return value.map((item) => transformValue(item, leafSerializer))
68
- }
69
-
70
- if (isPlainObject(value)) {
71
- const result = {}
72
-
73
- for (const key of Object.keys(value)) {
74
- result[key] = transformValue(value[key], leafSerializer)
75
- }
76
-
77
- return result
78
- }
79
-
80
- return ['leaf', leafSerializer(value)]
81
- }
82
-
83
28
  /**
84
29
  * Default strict serializer for leaf values.
85
30
  *
@@ -117,6 +62,96 @@ export default function Stringify (leafStringify = defaultLeafStringify) {
117
62
  * @public
118
63
  */
119
64
  return function stringify (program) {
120
- return JSON.stringify(transformValue(program, leafStringify))
65
+ // Reset the argId
66
+ let argId = 0
67
+
68
+ function transformValue (value) {
69
+ debug(value)
70
+
71
+ if (value === null) return null
72
+
73
+ // unwrap function wrappers (instrumented nodes)
74
+ if (value && value[referenceSymbol]) {
75
+ return transformValue(value.toJSON())
76
+ }
77
+
78
+ // Is a simple function. We call it to get the template.
79
+ if (typeof value === 'function') {
80
+ if (value instanceof AsyncFunction) {
81
+ throw new SendScriptSerializationError('Sendscript does not support async functions as of yet.')
82
+ }
83
+
84
+ const args = []
85
+ const argIds = []
86
+
87
+ for (let i = 0; i < value.length; i++) {
88
+ const arg = argId++
89
+ const placeholder = ['arg', arg]
90
+
91
+ placeholder[argSymbol] = argSymbol
92
+
93
+ args.push(placeholder)
94
+ argIds.push(arg)
95
+ }
96
+
97
+ return ['fn', argIds, transformValue(value(...args))]
98
+ }
99
+
100
+ if (value && value[argSymbol]) {
101
+ return value
102
+ }
103
+
104
+ if (value && value[ref]) {
105
+ return ['ref', ...value.path]
106
+ }
107
+
108
+ if (value && value[call]) {
109
+ return [
110
+ 'call',
111
+ transformValue(value.ref),
112
+ transformValue(value.args)
113
+ ]
114
+ }
115
+
116
+ if (value && value[awaitSymbol]) {
117
+ return ['await', transformValue(value.ref)]
118
+ }
119
+
120
+ if (value && value[then]) {
121
+ return [
122
+ 'then',
123
+ transformValue(value.ref),
124
+ transformValue(value.resolve || null),
125
+ transformValue(value.reject || null)
126
+ ]
127
+ }
128
+
129
+ if (Array.isArray(value)) {
130
+ const [operator, ...rest] = value
131
+
132
+ if (isKeyword(operator)) {
133
+ return [
134
+ ['quote', operator],
135
+ ...rest.map((item) => transformValue(item))
136
+ ]
137
+ }
138
+
139
+ return value.map((item) => transformValue(item))
140
+ }
141
+
142
+ if (isPlainObject(value)) {
143
+ const result = {}
144
+
145
+ for (const key of Object.keys(value)) {
146
+ result[key] = transformValue(value[key])
147
+ }
148
+
149
+ return result
150
+ }
151
+
152
+ return ['leaf', leafStringify(value)]
153
+ }
154
+
155
+ return JSON.stringify(transformValue(program))
121
156
  }
122
157
  }