sendscript 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +25 -25
- package/README.mz +14 -1
- package/error.mjs +1 -0
- package/example/typescript/docs/functions/add.md +1 -1
- package/example/typescript/docs/functions/square.md +1 -1
- package/index.test.mjs +46 -3
- package/package.json +1 -1
- package/parse.mjs +14 -9
- package/references.mjs +27 -5
- package/stringify.mjs +17 -1
- package/symbol.mjs +2 -0
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.0](https://github.com/bas080/sendscript/compare/v2.2.0...v2.3.0)
|
|
8
|
+
|
|
9
|
+
- Add support for then and catch [`c0bbea8`](https://github.com/bas080/sendscript/commit/c0bbea8efcc6b262c091d9043e96ac8c3f7606e1)
|
|
10
|
+
- Document .catch and .then support [`b35009a`](https://github.com/bas080/sendscript/commit/b35009ace9761d32c0c6ef1d1d045bed2ecfe74e)
|
|
11
|
+
|
|
12
|
+
#### [v2.2.0](https://github.com/bas080/sendscript/compare/v2.1.0...v2.2.0)
|
|
13
|
+
|
|
14
|
+
> 10 April 2026
|
|
15
|
+
|
|
16
|
+
- Have default leaf serializer throw if value has no JSON equivalant [`6752f2f`](https://github.com/bas080/sendscript/commit/6752f2f3ed51e252ab93a31b632fe8fbae51d7cb)
|
|
17
|
+
- Test that prototype chain works when parsing [`2cf0e7e`](https://github.com/bas080/sendscript/commit/2cf0e7e7ced32258102382e7ff1e3f8e91a2cc77)
|
|
18
|
+
|
|
7
19
|
#### [v2.1.0](https://github.com/bas080/sendscript/compare/v2.0.1...v2.1.0)
|
|
8
20
|
|
|
21
|
+
> 9 April 2026
|
|
22
|
+
|
|
9
23
|
- Make eval order when using await inline with JS [`00db8aa`](https://github.com/bas080/sendscript/commit/00db8aaa563378db814035e3e550269593ed82e2)
|
|
10
24
|
- Merge release workflow into publish [`6a5db12`](https://github.com/bas080/sendscript/commit/6a5db128b659dd295390505b2e470c5913617abf)
|
|
11
25
|
- Fix the cli repl [`3cf8b0d`](https://github.com/bas080/sendscript/commit/3cf8b0d9b4531989fb01a53fa9ab4cb6a011c183)
|
package/README.md
CHANGED
|
@@ -15,7 +15,9 @@ Write JS code that you can run on servers, browsers or other clients.
|
|
|
15
15
|
* [Server](#server)
|
|
16
16
|
* [Client](#client)
|
|
17
17
|
- [Repl](#repl)
|
|
18
|
-
- [
|
|
18
|
+
- [Promises](#promises)
|
|
19
|
+
* [.then / .catch](#then--catch)
|
|
20
|
+
* [await](#await)
|
|
19
21
|
- [TypeScript](#typescript)
|
|
20
22
|
- [Schema and Nested Modules](#schema-and-nested-modules)
|
|
21
23
|
* [Defining a Nested Module](#defining-a-nested-module)
|
|
@@ -27,8 +29,6 @@ Write JS code that you can run on servers, browsers or other clients.
|
|
|
27
29
|
- [Formatting](#formatting)
|
|
28
30
|
- [Changelog](#changelog)
|
|
29
31
|
- [Dependencies](#dependencies)
|
|
30
|
-
- [License](#license)
|
|
31
|
-
- [Roadmap](#roadmap)
|
|
32
32
|
|
|
33
33
|
<!-- tocstop -->
|
|
34
34
|
|
|
@@ -215,7 +215,20 @@ Sendscript ships with a barebones (no-dependencies) node-repl script. One can ru
|
|
|
215
215
|
|
|
216
216
|
> Use the `DEBUG='*'` to enable all logs or `DEBUG='sendscript:*'` for printingonly sendscript logs.
|
|
217
217
|
|
|
218
|
-
##
|
|
218
|
+
## Promises
|
|
219
|
+
|
|
220
|
+
### .then / .catch
|
|
221
|
+
|
|
222
|
+
Supported since vs `v2.3`.
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
const getOrCreatePost = send(createPost(title).catch(createPost(title)))
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
You will likely need to define better helpers that makes it safer to handle rejections and work with promises. It is however sensible to have
|
|
229
|
+
this basic behavior for the sendscript DSL and parser.
|
|
230
|
+
|
|
231
|
+
### await
|
|
219
232
|
|
|
220
233
|
SendScript supports async/await seamlessly within a single request. This avoids the performance pitfalls of waterfall-style messaging, which can be especially slow on high-latency networks.
|
|
221
234
|
|
|
@@ -429,19 +442,19 @@ npm t -- report text-summary
|
|
|
429
442
|
```
|
|
430
443
|
```
|
|
431
444
|
|
|
432
|
-
> sendscript@2.
|
|
445
|
+
> sendscript@2.3.0 test
|
|
433
446
|
> tap -R silent
|
|
434
447
|
|
|
435
448
|
|
|
436
|
-
> sendscript@2.
|
|
449
|
+
> sendscript@2.3.0 test
|
|
437
450
|
> tap report text-summary
|
|
438
451
|
|
|
439
452
|
|
|
440
453
|
=============================== Coverage summary ===============================
|
|
441
|
-
Statements : 100% (
|
|
442
|
-
Branches : 100% (
|
|
443
|
-
Functions : 100% (
|
|
444
|
-
Lines : 100% (
|
|
454
|
+
Statements : 100% ( 372/372 )
|
|
455
|
+
Branches : 100% ( 137/137 )
|
|
456
|
+
Functions : 100% ( 24/24 )
|
|
457
|
+
Lines : 100% ( 372/372 )
|
|
445
458
|
================================================================================
|
|
446
459
|
```
|
|
447
460
|
|
|
@@ -470,19 +483,6 @@ Check if packages are up to date on release.
|
|
|
470
483
|
npm outdated && echo 'No outdated packages found'
|
|
471
484
|
```
|
|
472
485
|
```
|
|
473
|
-
|
|
486
|
+
Package Current Wanted Latest Location Depended by
|
|
487
|
+
typedoc 0.28.18 0.28.19 0.28.19 node_modules/typedoc master
|
|
474
488
|
```
|
|
475
|
-
|
|
476
|
-
## License
|
|
477
|
-
|
|
478
|
-
See the [LICENSE.txt][license] file for details.
|
|
479
|
-
|
|
480
|
-
## Roadmap
|
|
481
|
-
|
|
482
|
-
- [ ] Support for simple lambdas to compose functions more easily.
|
|
483
|
-
|
|
484
|
-
[license]:./LICENSE.txt
|
|
485
|
-
[socket.io]:https://socket.io/
|
|
486
|
-
[changelog]:./CHANGELOG.md
|
|
487
|
-
[auto-changelog]:https://www.npmjs.com/package/auto-changelog
|
|
488
|
-
[typedoc]:https://github.com/TypeStrong/typedoc
|
package/README.mz
CHANGED
|
@@ -183,7 +183,20 @@ Sendscript ships with a barebones (no-dependencies) node-repl script. One can ru
|
|
|
183
183
|
|
|
184
184
|
> Use the `DEBUG='*'` to enable all logs or `DEBUG='sendscript:*'` for printingonly sendscript logs.
|
|
185
185
|
|
|
186
|
-
##
|
|
186
|
+
## Promises
|
|
187
|
+
|
|
188
|
+
### .then / .catch
|
|
189
|
+
|
|
190
|
+
Supported since vs `v2.3`.
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
const getOrCreatePost = send(createPost(title).catch(createPost(title)))
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
You will likely need to define better helpers that makes it safer to handle rejections and work with promises. It is however sensible to have
|
|
197
|
+
this basic behavior for the sendscript DSL and parser.
|
|
198
|
+
|
|
199
|
+
### await
|
|
187
200
|
|
|
188
201
|
SendScript supports async/await seamlessly within a single request. This avoids the performance pitfalls of waterfall-style messaging, which can be especially slow on high-latency networks.
|
|
189
202
|
|
package/error.mjs
CHANGED
|
@@ -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/
|
|
11
|
+
Defined in: [math.ts:1](https://github.com/bas080/sendscript/blob/b35009ace9761d32c0c6ef1d1d045bed2ecfe74e/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/
|
|
11
|
+
Defined in: [math.ts:2](https://github.com/bas080/sendscript/blob/b35009ace9761d32c0c6ef1d1d045bed2ecfe74e/example/typescript/math.ts#L2)
|
|
12
12
|
|
|
13
13
|
## Parameters
|
|
14
14
|
|
package/index.test.mjs
CHANGED
|
@@ -2,10 +2,11 @@ import { test } from 'tap'
|
|
|
2
2
|
import Stringify from './stringify.mjs'
|
|
3
3
|
import references from './references.mjs'
|
|
4
4
|
import Parse from './parse.mjs'
|
|
5
|
+
import { SendScriptSerializationError } from './error.mjs'
|
|
5
6
|
|
|
6
7
|
const order = []
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
+
const myModuleOrig = {
|
|
9
10
|
nested: {
|
|
10
11
|
again: {
|
|
11
12
|
T: () => true
|
|
@@ -27,11 +28,13 @@ const myModule = {
|
|
|
27
28
|
toArray: (...array) => array,
|
|
28
29
|
always: (x) => () => x,
|
|
29
30
|
multiply3: (a) => (b) => (c) => a * b * c,
|
|
31
|
+
sayHello: x => `hello ${x}`,
|
|
30
32
|
map: (fn) => (array) => array.map(fn),
|
|
31
33
|
filter: (pred) => (array) => array.filter(pred),
|
|
32
34
|
hello: 'world',
|
|
33
35
|
noop: () => {},
|
|
34
36
|
resolve: (x) => Promise.resolve(x),
|
|
37
|
+
reject: x => Promise.reject(x),
|
|
35
38
|
asyncFn: async () => 'my-async-function',
|
|
36
39
|
instanceOf: (x, t) => x instanceof t,
|
|
37
40
|
asyncAdd: async (a, b) => a + b,
|
|
@@ -45,7 +48,10 @@ const myModule = {
|
|
|
45
48
|
Promise
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
// We want to support prototype chain when parsing.
|
|
52
|
+
const myModule = Object.create(myModuleOrig)
|
|
53
|
+
const schema = Object.keys(myModuleOrig)
|
|
54
|
+
|
|
49
55
|
schema.push(['nested', [
|
|
50
56
|
['again', ['T']]
|
|
51
57
|
]])
|
|
@@ -63,6 +69,7 @@ test('should evaluate basic expressions correctly', async (t) => {
|
|
|
63
69
|
aPromise,
|
|
64
70
|
asyncAdd,
|
|
65
71
|
resolve,
|
|
72
|
+
reject,
|
|
66
73
|
delay,
|
|
67
74
|
delayedIdentity,
|
|
68
75
|
noop,
|
|
@@ -77,9 +84,27 @@ test('should evaluate basic expressions correctly', async (t) => {
|
|
|
77
84
|
identity,
|
|
78
85
|
always,
|
|
79
86
|
multiply3,
|
|
87
|
+
sayHello,
|
|
80
88
|
nested
|
|
81
89
|
} = api
|
|
82
90
|
|
|
91
|
+
t.test('supports .then', async t => {
|
|
92
|
+
t.rejects(run(reject('error')))
|
|
93
|
+
|
|
94
|
+
t.equal(await run(reject('error').catch(sayHello)), 'hello error')
|
|
95
|
+
t.equal(await run(reject('error').then(null, sayHello)), 'hello error')
|
|
96
|
+
|
|
97
|
+
t.equal(await run(resolve('value').then(sayHello)), 'hello value')
|
|
98
|
+
t.equal(await run(resolve('value').then(sayHello, noop)), 'hello value')
|
|
99
|
+
|
|
100
|
+
t.end()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
t.test('resolve and reject callback works for .then', async t => {
|
|
104
|
+
t.equal(await run(resolve(42).then(multiply3(1)(2))), 84)
|
|
105
|
+
t.end()
|
|
106
|
+
})
|
|
107
|
+
|
|
83
108
|
t.test('await evaluation order matches JS semantics', async (t) => {
|
|
84
109
|
const lorder = []
|
|
85
110
|
|
|
@@ -225,12 +250,30 @@ test('should evaluate basic expressions correctly', async (t) => {
|
|
|
225
250
|
t.end()
|
|
226
251
|
})
|
|
227
252
|
|
|
253
|
+
t.test('throws if stringify returns undefined', t => {
|
|
254
|
+
// This is to prevent accidentally sending payloads that make little sense.
|
|
255
|
+
|
|
256
|
+
// Check if it throw the expected error type.
|
|
257
|
+
try {
|
|
258
|
+
run(undefined)
|
|
259
|
+
} catch (error) {
|
|
260
|
+
t.ok(error instanceof SendScriptSerializationError)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
t.throws(() => run(undefined))
|
|
264
|
+
t.throws(() => run(new Set()))
|
|
265
|
+
t.throws(() => run(identity(undefined)))
|
|
266
|
+
t.throws(() => run(() => {}))
|
|
267
|
+
|
|
268
|
+
t.end()
|
|
269
|
+
})
|
|
270
|
+
|
|
228
271
|
t.test('basic types and identity', (t) => {
|
|
229
272
|
t.equal(run(identity(null)), null)
|
|
230
|
-
t.equal(run(identity(undefined)), null)
|
|
231
273
|
t.equal(run(noop()), undefined)
|
|
232
274
|
t.equal(run(identity(1)), 1)
|
|
233
275
|
t.strictSame(run(identity([])), [])
|
|
276
|
+
t.strictSame(run(identity({})), {})
|
|
234
277
|
t.strictSame(run(identity([identity(1), 2, 3])), [1, 2, 3])
|
|
235
278
|
t.strictSame(run(always('hello')()), 'hello')
|
|
236
279
|
t.end()
|
package/package.json
CHANGED
package/parse.mjs
CHANGED
|
@@ -32,8 +32,14 @@ const isPlainObject = (value) => {
|
|
|
32
32
|
return proto === Object.prototype || proto === null
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
const spy = (type, fn) => (...args) => {
|
|
36
|
+
const value = fn(...args)
|
|
37
|
+
debug(type, args, ' => ', value)
|
|
38
|
+
return value
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
// Recursively resolve awaited values in a parsed tree
|
|
36
|
-
const evaluate = (value, awaits = []) => {
|
|
42
|
+
const evaluate = spy('eval', (value, awaits = []) => {
|
|
37
43
|
if (value === undefinedSentinel) return undefined
|
|
38
44
|
|
|
39
45
|
if (Array.isArray(value)) {
|
|
@@ -48,6 +54,11 @@ const evaluate = (value, awaits = []) => {
|
|
|
48
54
|
return awaits[index]
|
|
49
55
|
}
|
|
50
56
|
|
|
57
|
+
if (operator === 'then') {
|
|
58
|
+
const [v, onResolve, onReject] = rest
|
|
59
|
+
return evaluate(v, awaits).then(evaluate(onResolve, awaits), evaluate(onReject, awaits))
|
|
60
|
+
}
|
|
61
|
+
|
|
51
62
|
if (operator === 'call') {
|
|
52
63
|
// Step 1: evaluate the function itself
|
|
53
64
|
let [fn, args] = rest
|
|
@@ -86,13 +97,7 @@ const evaluate = (value, awaits = []) => {
|
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
return value
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const spy = (fn) => (...args) => {
|
|
92
|
-
const value = fn(...args)
|
|
93
|
-
debug(args, ' => ', value)
|
|
94
|
-
return value
|
|
95
|
-
}
|
|
100
|
+
})
|
|
96
101
|
|
|
97
102
|
const defaultLeafDeserializer = (text) => JSON.parse(text)
|
|
98
103
|
|
|
@@ -105,7 +110,7 @@ export default (schemaArg, env, deserialize = defaultLeafDeserializer) => {
|
|
|
105
110
|
|
|
106
111
|
// Creates the list of awaits that will resolve in order
|
|
107
112
|
// and also deserializes the leaves.
|
|
108
|
-
const reviver = spy((key, value) => {
|
|
113
|
+
const reviver = spy('revive', (key, value) => {
|
|
109
114
|
if (value === null) return value
|
|
110
115
|
|
|
111
116
|
if (!Array.isArray(value)) {
|
package/references.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { awaitSymbol, call, ref } from './symbol.mjs'
|
|
1
|
+
import { awaitSymbol, call, ref, then, referenceSymbol } from './symbol.mjs'
|
|
2
2
|
|
|
3
3
|
function instrument (path) {
|
|
4
4
|
function reference (...args) {
|
|
@@ -6,7 +6,6 @@ function instrument (path) {
|
|
|
6
6
|
|
|
7
7
|
called.toJSON = () => ({
|
|
8
8
|
[call]: call,
|
|
9
|
-
call: true,
|
|
10
9
|
ref: reference,
|
|
11
10
|
args
|
|
12
11
|
})
|
|
@@ -14,13 +13,35 @@ function instrument (path) {
|
|
|
14
13
|
return called
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
function dotThen (resolve, reject) {
|
|
17
|
+
const node = instrument(path)
|
|
18
|
+
|
|
19
|
+
node.toJSON = () => ({
|
|
20
|
+
[then]: then,
|
|
21
|
+
ref: reference,
|
|
22
|
+
resolve,
|
|
23
|
+
reject
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return node
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
reference.catch = (reject) => {
|
|
30
|
+
return dotThen(null, reject)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
reference.then = (resolve, reject) => {
|
|
34
|
+
// That is how we know if it is an await or a .then call.
|
|
35
|
+
if (resolve?.[referenceSymbol] || reject?.[referenceSymbol]) {
|
|
36
|
+
return dotThen(resolve, reject)
|
|
37
|
+
}
|
|
38
|
+
|
|
18
39
|
const awaited = instrument(path)
|
|
40
|
+
// Prevent infinite recur
|
|
19
41
|
delete awaited.then
|
|
20
42
|
|
|
21
43
|
awaited.toJSON = () => ({
|
|
22
44
|
[awaitSymbol]: awaitSymbol,
|
|
23
|
-
await: true,
|
|
24
45
|
ref: reference
|
|
25
46
|
})
|
|
26
47
|
|
|
@@ -29,10 +50,11 @@ function instrument (path) {
|
|
|
29
50
|
|
|
30
51
|
reference.toJSON = () => ({
|
|
31
52
|
[ref]: ref,
|
|
32
|
-
reference: true,
|
|
33
53
|
path
|
|
34
54
|
})
|
|
35
55
|
|
|
56
|
+
reference[referenceSymbol] = referenceSymbol
|
|
57
|
+
|
|
36
58
|
return reference
|
|
37
59
|
}
|
|
38
60
|
|
package/stringify.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import Debug from './debug.mjs'
|
|
2
|
+
import { SendScriptSerializationError } from './error.mjs'
|
|
2
3
|
import {
|
|
3
4
|
awaitSymbol,
|
|
5
|
+
then,
|
|
4
6
|
call,
|
|
5
7
|
ref
|
|
6
8
|
} from './symbol.mjs'
|
|
@@ -42,6 +44,10 @@ function transformValue (value, leafSerializer) {
|
|
|
42
44
|
return ['await', transformValue(value.ref, leafSerializer)]
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
if (value && value[then]) {
|
|
48
|
+
return ['then', transformValue(value.ref, leafSerializer), transformValue(value.resolve || null, leafSerializer), transformValue(value.reject || null, leafSerializer)]
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
// Handle arrays: quote keyword operators, transform other arrays recursively
|
|
46
52
|
if (Array.isArray(value)) {
|
|
47
53
|
const [operator, ...rest] = value
|
|
@@ -69,7 +75,17 @@ function transformValue (value, leafSerializer) {
|
|
|
69
75
|
return ['leaf', leafSerializer(value)]
|
|
70
76
|
}
|
|
71
77
|
|
|
72
|
-
|
|
78
|
+
function strictStringify (x) {
|
|
79
|
+
const typeOf = typeof x
|
|
80
|
+
|
|
81
|
+
if (typeOf === 'object' || typeOf === 'function' || x === undefined) {
|
|
82
|
+
throw new SendScriptSerializationError(`Cannot and should not attempt to serialize ${x}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return JSON.stringify(x)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default function stringify (leafSerializer = strictStringify) {
|
|
73
89
|
function stringify (program) {
|
|
74
90
|
return JSON.stringify(transformValue(program, leafSerializer))
|
|
75
91
|
}
|