sendscript 2.4.3 → 2.5.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 +7 -0
- package/README.md +41 -43
- package/README.mz +38 -40
- package/package.json +1 -1
- package/references.mjs +10 -6
- package/references.test.mjs +54 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,8 +4,15 @@ 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.5.0](https://github.com/bas080/sendscript/compare/v2.4.3...v2.5.0)
|
|
8
|
+
|
|
9
|
+
- Clarify native await behavior in README [`a26f1b3`](https://github.com/bas080/sendscript/commit/a26f1b3c204390690eda7950c967d31ee55aa353)
|
|
10
|
+
- Allow passing onAwait callback to references make function [`8fdb1c4`](https://github.com/bas080/sendscript/commit/8fdb1c4bfa0682fabdd9d27ca075cd1e0cfaedf7)
|
|
11
|
+
|
|
7
12
|
#### [v2.4.3](https://github.com/bas080/sendscript/compare/v2.4.2...v2.4.3)
|
|
8
13
|
|
|
14
|
+
> 17 August 2026
|
|
15
|
+
|
|
9
16
|
- Improve TypeScript section with comprehensive examples and explanations [`a3c5867`](https://github.com/bas080/sendscript/commit/a3c5867f835bbcde1e802e75c80ca6f02dd4f0e8)
|
|
10
17
|
|
|
11
18
|
#### [v2.4.2](https://github.com/bas080/sendscript/compare/v2.4.1...v2.4.2)
|
package/README.md
CHANGED
|
@@ -306,27 +306,40 @@ behavior for the sendscript DSL and parser.
|
|
|
306
306
|
|
|
307
307
|
### await
|
|
308
308
|
|
|
309
|
-
|
|
310
|
-
the
|
|
311
|
-
|
|
309
|
+
By default, `await api.someMethod(...)` still creates a SendScript await stub
|
|
310
|
+
and keeps the result serializable with `stringify()`. It does not automatically
|
|
311
|
+
send anything over the network.
|
|
312
312
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
313
|
+
```js
|
|
314
|
+
const api = references(['add'])
|
|
315
|
+
const program = api.add(1, 2)
|
|
316
|
+
|
|
317
|
+
Stringify()(program)
|
|
318
|
+
// => "[\"call\",[\"ref\",\"add\"],[1,2]]"
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
If you want native `await` to cross the transport boundary, pass an `onAwait`
|
|
322
|
+
handler when creating the references:
|
|
316
323
|
|
|
317
324
|
```js
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
unread: await fetchUnreadMessages(userId),
|
|
321
|
-
emptyTrash: await emptyTrash(userId),
|
|
322
|
-
archived: await archiveMessages(selectMessages({ old: true })),
|
|
323
|
-
}
|
|
325
|
+
import Stringify from 'sendscript/stringify.mjs'
|
|
326
|
+
import references from 'sendscript/references.mjs'
|
|
324
327
|
|
|
325
|
-
const
|
|
328
|
+
const stringify = Stringify()
|
|
329
|
+
|
|
330
|
+
const api = references(['add'], (program) => {
|
|
331
|
+
return fetch('/api', {
|
|
332
|
+
method: 'POST',
|
|
333
|
+
headers: { 'content-type': 'application/json' },
|
|
334
|
+
body: stringify(program),
|
|
335
|
+
}).then((response) => response.json())
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
const result = await api.add(1, 2)
|
|
326
339
|
```
|
|
327
340
|
|
|
328
|
-
|
|
329
|
-
|
|
341
|
+
The callback receives the generated SendScript program, and you decide how it is
|
|
342
|
+
transported or executed.
|
|
330
343
|
|
|
331
344
|
## TypeScript
|
|
332
345
|
|
|
@@ -601,36 +614,21 @@ code to create the AST.
|
|
|
601
614
|
|
|
602
615
|
### Callbacks
|
|
603
616
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
as a templating tool to make sendscript programs; just like one would use
|
|
607
|
-
JavaScript with react templates. `items.map(deleteItem)` would return an array
|
|
608
|
-
of sendscript function calls which can be given to sendscript's parse.
|
|
609
|
-
|
|
610
|
-
Client functions cannot be called by sendscript functions (as of yet) since we
|
|
611
|
-
cannot serialize client functions. No work has been done to have the server send
|
|
612
|
-
back intermediate values to perform client function calls or by performing
|
|
613
|
-
smaller sendscript program payloads that are passed to the client. Very
|
|
614
|
-
interesting stuff to look into. You can achieve this now but it looks less clean
|
|
615
|
-
because you have to do `send` calls which is a bit manual.
|
|
617
|
+
SendScript supports basic callback-style usage, such as passing a function as an
|
|
618
|
+
argument or using a callback to flip arguments into the generated program.
|
|
616
619
|
|
|
617
620
|
```js
|
|
618
|
-
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
It might be interesting to allow configuration to create references that will
|
|
622
|
-
trigger a send whenever await is called. That would remove the ability to create
|
|
623
|
-
a single payload whenever using await. You can then write the above in the
|
|
624
|
-
following manner.
|
|
621
|
+
const api = references(['map', 'add'])
|
|
625
622
|
|
|
626
|
-
|
|
627
|
-
await updateUser(id, merge(await getUser(id), { ...newValues }))
|
|
623
|
+
const result = api.map((value) => api.add(value, 1))([1, 2, 3])
|
|
628
624
|
```
|
|
629
625
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
626
|
+
This is meant for composing SendScript programs, not for executing arbitrary
|
|
627
|
+
client-side logic at runtime.
|
|
628
|
+
|
|
629
|
+
> [!WARNING] Mixing client and server functions can be confusing, because the
|
|
630
|
+
> callback may be used to build a program rather than execute immediately. Keep
|
|
631
|
+
> callback logic simple and deterministic.
|
|
634
632
|
|
|
635
633
|
### Error handling
|
|
636
634
|
|
|
@@ -651,10 +649,10 @@ npm t -- report text-summary
|
|
|
651
649
|
```
|
|
652
650
|
|
|
653
651
|
=============================== Coverage summary ===============================
|
|
654
|
-
Statements : 100% (
|
|
655
|
-
Branches : 100% (
|
|
652
|
+
Statements : 100% ( 516/516 )
|
|
653
|
+
Branches : 100% ( 157/157 )
|
|
656
654
|
Functions : 100% ( 23/23 )
|
|
657
|
-
Lines : 100% (
|
|
655
|
+
Lines : 100% ( 516/516 )
|
|
658
656
|
================================================================================
|
|
659
657
|
```
|
|
660
658
|
|
package/README.mz
CHANGED
|
@@ -261,27 +261,40 @@ behavior for the sendscript DSL and parser.
|
|
|
261
261
|
|
|
262
262
|
### await
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
the
|
|
266
|
-
|
|
264
|
+
By default, `await api.someMethod(...)` still creates a SendScript await stub
|
|
265
|
+
and keeps the result serializable with `stringify()`. It does not automatically
|
|
266
|
+
send anything over the network.
|
|
267
267
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
268
|
+
```js
|
|
269
|
+
const api = references(['add'])
|
|
270
|
+
const program = api.add(1, 2)
|
|
271
|
+
|
|
272
|
+
Stringify()(program)
|
|
273
|
+
// => "[\"call\",[\"ref\",\"add\"],[1,2]]"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
If you want native `await` to cross the transport boundary, pass an `onAwait`
|
|
277
|
+
handler when creating the references:
|
|
271
278
|
|
|
272
279
|
```js
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
unread: await fetchUnreadMessages(userId),
|
|
276
|
-
emptyTrash: await emptyTrash(userId),
|
|
277
|
-
archived: await archiveMessages(selectMessages({ old: true })),
|
|
278
|
-
}
|
|
280
|
+
import Stringify from 'sendscript/stringify.mjs'
|
|
281
|
+
import references from 'sendscript/references.mjs'
|
|
279
282
|
|
|
280
|
-
const
|
|
283
|
+
const stringify = Stringify()
|
|
284
|
+
|
|
285
|
+
const api = references(['add'], (program) => {
|
|
286
|
+
return fetch('/api', {
|
|
287
|
+
method: 'POST',
|
|
288
|
+
headers: { 'content-type': 'application/json' },
|
|
289
|
+
body: stringify(program),
|
|
290
|
+
}).then((response) => response.json())
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
const result = await api.add(1, 2)
|
|
281
294
|
```
|
|
282
295
|
|
|
283
|
-
|
|
284
|
-
|
|
296
|
+
The callback receives the generated SendScript program, and you decide how it is
|
|
297
|
+
transported or executed.
|
|
285
298
|
|
|
286
299
|
## TypeScript
|
|
287
300
|
|
|
@@ -496,36 +509,21 @@ code to create the AST.
|
|
|
496
509
|
|
|
497
510
|
### Callbacks
|
|
498
511
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
as a templating tool to make sendscript programs; just like one would use
|
|
502
|
-
JavaScript with react templates. `items.map(deleteItem)` would return an array
|
|
503
|
-
of sendscript function calls which can be given to sendscript's parse.
|
|
504
|
-
|
|
505
|
-
Client functions cannot be called by sendscript functions (as of yet) since we
|
|
506
|
-
cannot serialize client functions. No work has been done to have the server send
|
|
507
|
-
back intermediate values to perform client function calls or by performing
|
|
508
|
-
smaller sendscript program payloads that are passed to the client. Very
|
|
509
|
-
interesting stuff to look into. You can achieve this now but it looks less clean
|
|
510
|
-
because you have to do `send` calls which is a bit manual.
|
|
512
|
+
SendScript supports basic callback-style usage, such as passing a function as an
|
|
513
|
+
argument or using a callback to flip arguments into the generated program.
|
|
511
514
|
|
|
512
515
|
```js
|
|
513
|
-
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
It might be interesting to allow configuration to create references that will
|
|
517
|
-
trigger a send whenever await is called. That would remove the ability to create
|
|
518
|
-
a single payload whenever using await. You can then write the above in the
|
|
519
|
-
following manner.
|
|
516
|
+
const api = references(['map', 'add'])
|
|
520
517
|
|
|
521
|
-
|
|
522
|
-
await updateUser(id, merge(await getUser(id), { ...newValues }))
|
|
518
|
+
const result = api.map((value) => api.add(value, 1))([1, 2, 3])
|
|
523
519
|
```
|
|
524
520
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
521
|
+
This is meant for composing SendScript programs, not for executing arbitrary
|
|
522
|
+
client-side logic at runtime.
|
|
523
|
+
|
|
524
|
+
> [!WARNING] Mixing client and server functions can be confusing, because the
|
|
525
|
+
> callback may be used to build a program rather than execute immediately. Keep
|
|
526
|
+
> callback logic simple and deterministic.
|
|
529
527
|
|
|
530
528
|
### Error handling
|
|
531
529
|
|
package/package.json
CHANGED
package/references.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { awaitSymbol, call, ref, then, referenceSymbol } from './symbol.mjs'
|
|
|
7
7
|
* @param {Array<string>} path - Path representing the function location in schema.
|
|
8
8
|
* @returns {Function} Reference function with attached control methods (.then, .catch, toJSON).
|
|
9
9
|
*/
|
|
10
|
-
function instrument (path) {
|
|
10
|
+
function instrument (path, onAwait = null) {
|
|
11
11
|
/**
|
|
12
12
|
* Creates a callable reference invocation.
|
|
13
13
|
*
|
|
@@ -15,7 +15,7 @@ function instrument (path) {
|
|
|
15
15
|
* @returns {Function} New instrumented reference node.
|
|
16
16
|
*/
|
|
17
17
|
function reference (...args) {
|
|
18
|
-
const called = instrument(path)
|
|
18
|
+
const called = instrument(path, onAwait)
|
|
19
19
|
|
|
20
20
|
called.toJSON = () => ({
|
|
21
21
|
[call]: call,
|
|
@@ -71,7 +71,7 @@ function instrument (path) {
|
|
|
71
71
|
return dotThen(resolve, reject)
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const awaited = instrument(path)
|
|
74
|
+
const awaited = instrument(path, onAwait)
|
|
75
75
|
delete awaited.then
|
|
76
76
|
|
|
77
77
|
awaited.toJSON = () => ({
|
|
@@ -79,6 +79,10 @@ function instrument (path) {
|
|
|
79
79
|
ref: reference
|
|
80
80
|
})
|
|
81
81
|
|
|
82
|
+
if (typeof onAwait === 'function') {
|
|
83
|
+
return resolve(onAwait(awaited))
|
|
84
|
+
}
|
|
85
|
+
|
|
82
86
|
return resolve(awaited)
|
|
83
87
|
}
|
|
84
88
|
|
|
@@ -106,15 +110,15 @@ function instrument (path) {
|
|
|
106
110
|
* @throws {Error} If schema format is invalid
|
|
107
111
|
* @public
|
|
108
112
|
*/
|
|
109
|
-
export default function References (schema, parentPath = []) {
|
|
113
|
+
export default function References (schema, onAwait = null, parentPath = []) {
|
|
110
114
|
return schema.reduce((acc, item) => {
|
|
111
115
|
if (typeof item === 'string') {
|
|
112
|
-
acc[item] = instrument([...parentPath, item])
|
|
116
|
+
acc[item] = instrument([...parentPath, item], onAwait)
|
|
113
117
|
} else if (Array.isArray(item)) {
|
|
114
118
|
const [name, children] = item
|
|
115
119
|
|
|
116
120
|
if (Array.isArray(children)) {
|
|
117
|
-
acc[name] = References(children, [...parentPath, name])
|
|
121
|
+
acc[name] = References(children, onAwait, [...parentPath, name])
|
|
118
122
|
} else {
|
|
119
123
|
throw new Error(`Expected children array for namespace "${name}"`)
|
|
120
124
|
}
|
package/references.test.mjs
CHANGED
|
@@ -1,8 +1,62 @@
|
|
|
1
1
|
import { test } from 'tap'
|
|
2
2
|
import references from './references.mjs'
|
|
3
|
+
import Stringify from './stringify.mjs'
|
|
3
4
|
|
|
4
5
|
test('invalid uses of references', t => {
|
|
5
6
|
t.throws(() => references([['a']]))
|
|
6
7
|
t.throws(() => references([{}]))
|
|
7
8
|
t.end()
|
|
8
9
|
})
|
|
10
|
+
|
|
11
|
+
test('native await hook is optional and backwards compatible', async t => {
|
|
12
|
+
const schema = ['add', 'square', 'identity', ['nested', ['value']]]
|
|
13
|
+
const stringify = Stringify()
|
|
14
|
+
|
|
15
|
+
const defaultApi = references(schema)
|
|
16
|
+
const defaultProgram = defaultApi.add(1, 2)
|
|
17
|
+
|
|
18
|
+
t.doesNotThrow(() => stringify(defaultProgram))
|
|
19
|
+
t.same(JSON.parse(stringify(defaultProgram))[0], 'call')
|
|
20
|
+
|
|
21
|
+
const calls = []
|
|
22
|
+
const api = references(schema, (program) => {
|
|
23
|
+
calls.push(program)
|
|
24
|
+
return 42
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
t.equal(await api.add(1, 2), 42)
|
|
28
|
+
t.equal(calls.length, 1)
|
|
29
|
+
t.equal(typeof calls[0], 'function')
|
|
30
|
+
|
|
31
|
+
const asyncApi = references(schema, async () => {
|
|
32
|
+
return await Promise.resolve(17)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
t.equal(await asyncApi.identity(9), 17)
|
|
36
|
+
|
|
37
|
+
const rejectedApi = references(schema, async () => {
|
|
38
|
+
throw new Error('boom')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
await t.rejects((async () => {
|
|
42
|
+
await rejectedApi.identity(9)
|
|
43
|
+
})(), { message: 'boom' })
|
|
44
|
+
|
|
45
|
+
const thenApi = references(schema)
|
|
46
|
+
const thenProgram = thenApi.add(1, 2).then(thenApi.square)
|
|
47
|
+
t.same(JSON.parse(stringify(thenProgram))[0], 'then')
|
|
48
|
+
|
|
49
|
+
const nestedApi = references([['outer', ['inner']]], (program) => program)
|
|
50
|
+
t.equal(typeof nestedApi.outer.inner, 'function')
|
|
51
|
+
t.same(JSON.parse(stringify(nestedApi.outer.inner(1)))[0], 'call')
|
|
52
|
+
|
|
53
|
+
const onAwaitApi = references(['add'], (program) => {
|
|
54
|
+
const serialized = stringify(program)
|
|
55
|
+
t.same(JSON.parse(serialized), ['await', ['call', ['ref', 'add'], [['leaf', '1'], ['leaf', '2']]]])
|
|
56
|
+
return 99
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
t.equal(await onAwaitApi.add(1, 2), 99)
|
|
60
|
+
|
|
61
|
+
t.end()
|
|
62
|
+
})
|