sendscript 2.4.0 → 2.4.2
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 +16 -0
- package/CONTRIBUTING.md +12 -2
- package/README.md +184 -187
- package/README.mz +142 -33
- package/example/typescript/docs/functions/add.md +1 -1
- package/example/typescript/docs/functions/square.md +1 -1
- package/package.json +2 -2
- package/curry.mjs +0 -11
- package/curry.test.mjs +0 -35
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.2](https://github.com/bas080/sendscript/compare/v2.4.1...v2.4.2)
|
|
8
|
+
|
|
9
|
+
- Update tap and typedoc [`46421de`](https://github.com/bas080/sendscript/commit/46421de16533066ee13c91ea75505b1ecdd548f0)
|
|
10
|
+
- Remove the unused curry helper [`9dcbb48`](https://github.com/bas080/sendscript/commit/9dcbb485b455937070c65359ecde8236c128bee8)
|
|
11
|
+
- Remove duplicate Promises section with outdated async/await information [`5f43ddb`](https://github.com/bas080/sendscript/commit/5f43ddb91ddb2d77227ddd6a570807f874230ea1)
|
|
12
|
+
|
|
13
|
+
#### [v2.4.1](https://github.com/bas080/sendscript/compare/v2.4.0...v2.4.1)
|
|
14
|
+
|
|
15
|
+
> 29 June 2026
|
|
16
|
+
|
|
17
|
+
- Update tap and typedoc version [`3f10410`](https://github.com/bas080/sendscript/commit/3f1041020e00331ff30ffb5f972738dbaf7a50f3)
|
|
18
|
+
- Update tap to 21.7.1 [`d1b5bbd`](https://github.com/bas080/sendscript/commit/d1b5bbd4cb7012e6606973d3d23d08df56cadca2)
|
|
19
|
+
- Document limitations of sendscript [`f577499`](https://github.com/bas080/sendscript/commit/f577499279b85e04ea589a3bcb1866d145216f00)
|
|
20
|
+
|
|
7
21
|
#### [v2.4.0](https://github.com/bas080/sendscript/compare/v2.3.9...v2.4.0)
|
|
8
22
|
|
|
23
|
+
> 16 April 2026
|
|
24
|
+
|
|
9
25
|
- Update tap to 4.5.4 [`5249a85`](https://github.com/bas080/sendscript/commit/5249a85a08b10485f0d64058fd2c8bcaae84edda)
|
|
10
26
|
- Add fn and args support [`6110654`](https://github.com/bas080/sendscript/commit/6110654fa039d17cd9ba0e15f749756b615b5d09)
|
|
11
27
|
- Move transport agnostic section to socket example [`3a92257`](https://github.com/bas080/sendscript/commit/3a9225787df16a0fd83293bfa14d91b74a46d070)
|
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
|
|
@@ -40,8 +52,6 @@ Generate the README from the mz file.
|
|
|
40
52
|
```bash bash
|
|
41
53
|
markatzea ./README.mz | tee ./README.md
|
|
42
54
|
|
|
43
|
-
npx documentation readme schema.mjs references.mjs stringify.mjs parse.mjs -s Reference --github --a public --markdown-toc false
|
|
44
|
-
|
|
45
55
|
npx markdown-toc --maxdepth 3 -i README.md
|
|
46
56
|
|
|
47
57
|
git add *.md ./example
|
package/README.md
CHANGED
|
@@ -1,31 +1,37 @@
|
|
|
1
1
|
# SendScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Serialize and execute composable JavaScript function calls with JSON.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/sendscript)
|
|
6
6
|
[](#tests)
|
|
7
7
|
[](https://standardjs.com)
|
|
8
|
-
[](./LICENSE.txt)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Composable function calls** - Combine multiple functions into a single
|
|
13
|
+
request
|
|
14
|
+
- **Write ordinary JavaScript** - Programs serialize to JSON and execute as
|
|
15
|
+
written
|
|
16
|
+
- **Type-safe** - Works great with TypeScript for client-side type checking
|
|
17
|
+
- **Zero dependencies** - Lightweight core library
|
|
18
|
+
- **Async/await support** - Seamless async operations within a single payload
|
|
19
|
+
- **Custom serializers** - Support for Date, Map, Set, BigInt, and more
|
|
20
|
+
- **Transport agnostic** - Use HTTP, WebSockets, or any other communication
|
|
21
|
+
method
|
|
9
22
|
|
|
10
23
|
<!-- toc -->
|
|
11
24
|
|
|
25
|
+
- [Installation](#installation)
|
|
26
|
+
- [Quick Start](#quick-start)
|
|
12
27
|
- [Introduction](#introduction)
|
|
13
|
-
- [
|
|
14
|
-
|
|
15
|
-
* [Parse](#parse)
|
|
16
|
-
* [parse](#parse)
|
|
17
|
-
* [References](#references)
|
|
18
|
-
* [SchemaNode](#schemanode)
|
|
19
|
-
* [Schema](#schema)
|
|
20
|
-
* [defaultLeafStringify](#defaultleafstringify)
|
|
21
|
-
* [Stringify](#stringify)
|
|
22
|
-
* [stringify](#stringify)
|
|
28
|
+
- [Why SendScript?](#why-sendscript)
|
|
29
|
+
- [How It Works](#how-it-works)
|
|
23
30
|
- [Socket example](#socket-example)
|
|
24
31
|
* [Module](#module)
|
|
25
32
|
* [Server](#server)
|
|
26
33
|
* [Client](#client)
|
|
27
34
|
- [Repl](#repl)
|
|
28
|
-
- [Functions](#functions)
|
|
29
35
|
- [Promises](#promises)
|
|
30
36
|
* [.then / .catch](#then--catch)
|
|
31
37
|
* [await](#await)
|
|
@@ -36,23 +42,75 @@ Write JS code that you can run on servers, browsers or other clients.
|
|
|
36
42
|
* [Validating structured input](#validating-structured-input)
|
|
37
43
|
- [Leaf Serializer](#leaf-serializer)
|
|
38
44
|
* [Example with superjson](#example-with-superjson)
|
|
45
|
+
- [Limitations](#limitations)
|
|
46
|
+
* [Chaining APIs](#chaining-apis)
|
|
47
|
+
* [Callbacks](#callbacks)
|
|
48
|
+
* [Error handling](#error-handling)
|
|
49
|
+
- [Tests](#tests)
|
|
50
|
+
- [Formatting](#formatting)
|
|
39
51
|
- [Changelog](#changelog)
|
|
40
52
|
- [License](#license)
|
|
41
53
|
- [Issues](#issues)
|
|
42
54
|
|
|
43
55
|
<!-- tocstop -->
|
|
44
56
|
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
Install SendScript from npm:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install sendscript
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Quick Start
|
|
66
|
+
|
|
67
|
+
Here's the simplest example to get started with SendScript:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
import Stringify from 'sendscript/stringify.mjs'
|
|
71
|
+
import Parse from 'sendscript/parse.mjs'
|
|
72
|
+
import references from 'sendscript/references.mjs'
|
|
73
|
+
|
|
74
|
+
// Define your functions
|
|
75
|
+
const functions = {
|
|
76
|
+
greet: (name) => `Hello, ${name}!`,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Create client-side stubs
|
|
80
|
+
const { greet } = references(['greet'])
|
|
81
|
+
|
|
82
|
+
// Serialize a program on the client
|
|
83
|
+
const stringify = Stringify()
|
|
84
|
+
const program = stringify(greet('World'))
|
|
85
|
+
|
|
86
|
+
// Parse and execute on the server
|
|
87
|
+
const parse = Parse(['greet'], functions)
|
|
88
|
+
const result = parse(program) // "Hello, World!"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This demonstrates the core concept: write a program on the client, serialize it
|
|
92
|
+
to JSON, send it to the server, and execute it exactly as written.
|
|
93
|
+
|
|
45
94
|
## Introduction
|
|
46
95
|
|
|
47
96
|
There has been interest in improving APIs by allowing aggregations in a single
|
|
48
97
|
request. Examples include
|
|
49
98
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
99
|
+
- [JSON-RPC](https://json-rpc.dev/) which allows you to do multiple requests but
|
|
100
|
+
it does not allow you to compose the return value of one endpoint to be the
|
|
101
|
+
input/arguments of another.
|
|
102
|
+
|
|
103
|
+
- [GraphQL](https://graphql.org/) is very cool but also introduces a new
|
|
104
|
+
language and the tooling that is required to wield it.
|
|
105
|
+
|
|
106
|
+
## Why SendScript?
|
|
107
|
+
|
|
108
|
+
Unlike JSON-RPC (which doesn't support function composition) or GraphQL (which
|
|
109
|
+
requires learning a new language), SendScript lets you write ordinary
|
|
110
|
+
JavaScript/TypeScript that gets serialized and executed on the server exactly as
|
|
111
|
+
written.
|
|
53
112
|
|
|
54
|
-
|
|
55
|
-
languages and the tooling that is required to wield it.
|
|
113
|
+
## How It Works
|
|
56
114
|
|
|
57
115
|
What SendScript attempts is to allow for very expressive queries and mutations
|
|
58
116
|
to be performed that read and write like ordinary JS. That means that the
|
|
@@ -73,7 +131,6 @@ const stringify = Stringify()
|
|
|
73
131
|
|
|
74
132
|
console.log(stringify(add(1,2)))
|
|
75
133
|
```
|
|
76
|
-
|
|
77
134
|
```json
|
|
78
135
|
["call",["ref","add"],[["leaf","1"],["leaf","2"]]]
|
|
79
136
|
```
|
|
@@ -95,7 +152,6 @@ const program = '["call",["ref","add"],[1,2]]'
|
|
|
95
152
|
|
|
96
153
|
console.log(parse(program))
|
|
97
154
|
```
|
|
98
|
-
|
|
99
155
|
```json
|
|
100
156
|
3
|
|
101
157
|
```
|
|
@@ -105,131 +161,18 @@ composition and even await.
|
|
|
105
161
|
|
|
106
162
|
This package is nothing more than the absolute core of sendscript. It includes:
|
|
107
163
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
The naming could use more love and there are many things to solve either in the
|
|
114
|
-
core or around it. Things like supporting more complex (de)serializers, errors
|
|
115
|
-
and maybe mixing client functions with sendscript programs. Contact me if I have
|
|
116
|
-
piqued your interest.
|
|
117
|
-
|
|
118
|
-
## Reference
|
|
119
|
-
|
|
120
|
-
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
|
121
|
-
|
|
122
|
-
### defaultLeafParse
|
|
123
|
-
|
|
124
|
-
[parse.mjs:49-49](https://github.com/bas080/sendscript/blob/5249a85a08b10485f0d64058fd2c8bcaae84edda/parse.mjs#L49-L49 "Source code on GitHub")
|
|
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)** 
|
|
131
|
-
|
|
132
|
-
Returns **any** 
|
|
133
|
-
|
|
134
|
-
### Parse
|
|
135
|
-
|
|
136
|
-
[parse.mjs:68-215](https://github.com/bas080/sendscript/blob/5249a85a08b10485f0d64058fd2c8bcaae84edda/parse.mjs#L68-L215 "Source code on GitHub")
|
|
137
|
-
|
|
138
|
-
#### Parameters
|
|
139
|
-
|
|
140
|
-
* `schema` **[Schema](#schema)** 
|
|
141
|
-
* `env` **Env** runtime environment for refs
|
|
142
|
-
* `leafParse` (optional, default `defaultLeafParse`)
|
|
143
|
-
|
|
144
|
-
Returns **[parse](#parse)** 
|
|
145
|
-
|
|
146
|
-
### parse
|
|
147
|
-
|
|
148
|
-
[parse.mjs:78-214](https://github.com/bas080/sendscript/blob/5249a85a08b10485f0d64058fd2c8bcaae84edda/parse.mjs#L78-L214 "Source code on GitHub")
|
|
149
|
-
|
|
150
|
-
Parses and executes a serialized program.
|
|
151
|
-
|
|
152
|
-
#### Parameters
|
|
153
|
-
|
|
154
|
-
* `program` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** JSON encoded program
|
|
155
|
-
|
|
156
|
-
Returns **(any | [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<any>)** 
|
|
157
|
-
|
|
158
|
-
### References
|
|
159
|
-
|
|
160
|
-
[references.mjs:109-127](https://github.com/bas080/sendscript/blob/5249a85a08b10485f0d64058fd2c8bcaae84edda/references.mjs#L109-L127 "Source code on GitHub")
|
|
161
|
-
|
|
162
|
-
Builds a nested API structure from a schema definition.
|
|
163
|
-
|
|
164
|
-
#### Parameters
|
|
165
|
-
|
|
166
|
-
* `schema` **[Schema](#schema)** 
|
|
167
|
-
* `parentPath` (optional, default `[]`)
|
|
168
|
-
|
|
169
|
-
<!---->
|
|
170
|
-
|
|
171
|
-
* Throws **[Error](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)** If schema format is invalid
|
|
172
|
-
|
|
173
|
-
Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Nested instrumented API object
|
|
174
|
-
|
|
175
|
-
### SchemaNode
|
|
176
|
-
|
|
177
|
-
[schema.mjs:1-19](https://github.com/bas080/sendscript/blob/5249a85a08b10485f0d64058fd2c8bcaae84edda/schema.mjs#L1-L6 "Source code on GitHub")
|
|
178
|
-
|
|
179
|
-
A single schema node.
|
|
180
|
-
|
|
181
|
-
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)])
|
|
182
|
-
|
|
183
|
-
### Schema
|
|
184
|
-
|
|
185
|
-
[schema.mjs:1-19](https://github.com/bas080/sendscript/blob/5249a85a08b10485f0d64058fd2c8bcaae84edda/schema.mjs#L8-L18 "Source code on GitHub")
|
|
186
|
-
|
|
187
|
-
A schema defines the structure of the runtime API tree.
|
|
188
|
-
|
|
189
|
-
* string → leaf node
|
|
190
|
-
* \[name, children] → namespace node
|
|
191
|
-
|
|
192
|
-
Schema is recursive: nodes can contain nested schemas.
|
|
193
|
-
|
|
194
|
-
Type: [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[SchemaNode](#schemanode)>
|
|
195
|
-
|
|
196
|
-
### defaultLeafStringify
|
|
164
|
+
- The `references` function to create stubs to write the programs.
|
|
165
|
+
- `stringify` which takes the program and returns a JSON string.
|
|
166
|
+
- `parse` which takes the `stringify` JSON string and a real module and returns
|
|
167
|
+
the result.
|
|
197
168
|
|
|
198
|
-
|
|
169
|
+
> **Note:** SendScript works in Node.js and browsers. The core library is
|
|
170
|
+
> framework-agnostic and transport-agnostic — use HTTP, WebSockets, or any other
|
|
171
|
+
> communication method that suits your needs.
|
|
199
172
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
#### Parameters
|
|
205
|
-
|
|
206
|
-
* `x` **any** 
|
|
207
|
-
|
|
208
|
-
Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 
|
|
209
|
-
|
|
210
|
-
### Stringify
|
|
211
|
-
|
|
212
|
-
[stringify.mjs:56-157](https://github.com/bas080/sendscript/blob/5249a85a08b10485f0d64058fd2c8bcaae84edda/stringify.mjs#L56-L157 "Source code on GitHub")
|
|
213
|
-
|
|
214
|
-
Creates a stringify function for SendScript AST structures.
|
|
215
|
-
|
|
216
|
-
#### Parameters
|
|
217
|
-
|
|
218
|
-
* `leafStringify` (optional, default `defaultLeafStringify`)
|
|
219
|
-
|
|
220
|
-
Returns **[stringify](#stringify)** 
|
|
221
|
-
|
|
222
|
-
### stringify
|
|
223
|
-
|
|
224
|
-
[stringify.mjs:64-156](https://github.com/bas080/sendscript/blob/5249a85a08b10485f0d64058fd2c8bcaae84edda/stringify.mjs#L64-L156 "Source code on GitHub")
|
|
225
|
-
|
|
226
|
-
Serializes a program into a JSON string representation.
|
|
227
|
-
|
|
228
|
-
#### Parameters
|
|
229
|
-
|
|
230
|
-
* `program` **any** 
|
|
231
|
-
|
|
232
|
-
Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 
|
|
173
|
+
Future enhancements may include support for more complex (de)serializers,
|
|
174
|
+
improved error handling, and deeper integration of client functions with
|
|
175
|
+
SendScript programs. Contributions and feedback are welcome.
|
|
233
176
|
|
|
234
177
|
## Socket example
|
|
235
178
|
|
|
@@ -251,7 +194,7 @@ export const square = (a) => a * a
|
|
|
251
194
|
|
|
252
195
|
### Server
|
|
253
196
|
|
|
254
|
-
Here a socket.io server that runs SendScript programs.
|
|
197
|
+
Here's a socket.io server that runs SendScript programs.
|
|
255
198
|
|
|
256
199
|
```js
|
|
257
200
|
// ./example/server.socket.io.mjs
|
|
@@ -331,40 +274,23 @@ node ./example/client.socket.io.mjs
|
|
|
331
274
|
|
|
332
275
|
pkill sendscript
|
|
333
276
|
```
|
|
334
|
-
|
|
335
|
-
|
|
277
|
+
```
|
|
278
|
+
Result: 100
|
|
279
|
+
```
|
|
336
280
|
|
|
337
281
|
## Repl
|
|
338
282
|
|
|
339
|
-
Sendscript ships with a
|
|
283
|
+
Sendscript ships with a bare-bones (no-dependencies) node-repl script. One can
|
|
340
284
|
run it by simply typing `sendscript` in their console.
|
|
341
285
|
|
|
342
|
-
> Use the `DEBUG='*'` to enable all logs or `DEBUG='sendscript:*'` for
|
|
343
|
-
>
|
|
344
|
-
|
|
345
|
-
## Functions
|
|
346
|
-
|
|
347
|
-
SendScript as of `v2.4` supports functions to do basic templating. It does not
|
|
348
|
-
support async functions and will throw an error when you define one.
|
|
349
|
-
|
|
350
|
-
Under the hood the function is called when it is being parsed. Make sure you
|
|
351
|
-
understand what you are doing when mixing client and server functions.
|
|
352
|
-
|
|
353
|
-
```js
|
|
354
|
-
map((a) => add(a, 1))([1, 2, 3]))
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
It also supports nested functions.
|
|
358
|
-
|
|
359
|
-
```js
|
|
360
|
-
map(call)(map((a) => () => add(a, 1))([1, 2, 3]))
|
|
361
|
-
```
|
|
286
|
+
> Use the `DEBUG='*'` to enable all logs or `DEBUG='sendscript:*'` for printing
|
|
287
|
+
> only sendscript logs.
|
|
362
288
|
|
|
363
289
|
## Promises
|
|
364
290
|
|
|
365
291
|
### .then / .catch
|
|
366
292
|
|
|
367
|
-
Supported since
|
|
293
|
+
Supported since `v2.3`.
|
|
368
294
|
|
|
369
295
|
```js
|
|
370
296
|
const getOrCreatePost = send(createPost(title).catch(createPost(title)))
|
|
@@ -402,19 +328,18 @@ defined properties and returned values.
|
|
|
402
328
|
|
|
403
329
|
There is a good use-case to write a module in TypeScript.
|
|
404
330
|
|
|
405
|
-
1.
|
|
406
|
-
|
|
407
|
-
2.
|
|
408
|
-
|
|
409
|
-
3.
|
|
410
|
-
|
|
331
|
+
1. Obviously the module would have the benefits that TypeScript offers when
|
|
332
|
+
coding.
|
|
333
|
+
2. You can use tools like [typedoc][typedoc] to generate docs from your types to
|
|
334
|
+
share with consumers of your API.
|
|
335
|
+
3. You can use the types of the module to coerce your client to adopt the
|
|
336
|
+
module's type.
|
|
411
337
|
|
|
412
338
|
Let's say we have this module which we use on the server.
|
|
413
339
|
|
|
414
340
|
```bash
|
|
415
341
|
cat ./example/typescript/math.ts
|
|
416
342
|
```
|
|
417
|
-
|
|
418
343
|
```ts
|
|
419
344
|
export const add = (a: number, b: number) => a + b
|
|
420
345
|
export const square = (a: number) => a * a
|
|
@@ -425,7 +350,6 @@ We can then coerce the types of the instrumented stubs.
|
|
|
425
350
|
```bash
|
|
426
351
|
cat ./example/typescript/client.ts
|
|
427
352
|
```
|
|
428
|
-
|
|
429
353
|
```ts
|
|
430
354
|
import math from './math.client.ts'
|
|
431
355
|
import Stringify from 'sendscript/stringify.mjs'
|
|
@@ -451,7 +375,7 @@ npx typedoc --plugin typedoc-plugin-markdown --out ./example/typescript/docs ./e
|
|
|
451
375
|
|
|
452
376
|
You can see the docs [here](./example/typescript/docs/globals.md)
|
|
453
377
|
|
|
454
|
-
>
|
|
378
|
+
> [!NOTE] Although type coercion on the client side can improve the development
|
|
455
379
|
> experience, it does not represent the actual type. Values are subject to
|
|
456
380
|
> serialization and deserialization.
|
|
457
381
|
|
|
@@ -518,9 +442,9 @@ export function createUser(user) {
|
|
|
518
442
|
|
|
519
443
|
**Benefits**:
|
|
520
444
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
445
|
+
- Ensures arguments match expected types and shapes.
|
|
446
|
+
- Throws structured errors that can be propagated to clients.
|
|
447
|
+
- Works with TypeScript for automatic type inference.
|
|
524
448
|
|
|
525
449
|
## Leaf Serializer
|
|
526
450
|
|
|
@@ -584,7 +508,7 @@ const env = {
|
|
|
584
508
|
}
|
|
585
509
|
|
|
586
510
|
// Parse with custom leaf deserializer
|
|
587
|
-
const parse = Parse(schema, env,
|
|
511
|
+
const parse = Parse(schema, env, leafDeserializer)
|
|
588
512
|
|
|
589
513
|
const result = parse(json)
|
|
590
514
|
```
|
|
@@ -592,6 +516,84 @@ const result = parse(json)
|
|
|
592
516
|
The leaf wrapper format is `['leaf', serializedPayload]`, making it unambiguous
|
|
593
517
|
and safe from colliding with SendScript operators.
|
|
594
518
|
|
|
519
|
+
## Limitations
|
|
520
|
+
|
|
521
|
+
The stubs the `references` function returns are limited in the way they can be
|
|
522
|
+
used. This has to do with the fact that they are to be serialized.
|
|
523
|
+
|
|
524
|
+
### Chaining APIs
|
|
525
|
+
|
|
526
|
+
Currently you cannot do chaining APIs. `lib().doThis().doThat()`. It likely is
|
|
527
|
+
possible to implement but will require more extensive schemas and obviously more
|
|
528
|
+
code to create the AST.
|
|
529
|
+
|
|
530
|
+
### Callbacks
|
|
531
|
+
|
|
532
|
+
Although it is possible to mix client and server functions, it works very
|
|
533
|
+
different to ordinary functions. Client functions can be used but should be seen
|
|
534
|
+
as a templating tool to make sendscript programs; just like one would use
|
|
535
|
+
JavaScript with react templates. `items.map(deleteItem)` would return an array
|
|
536
|
+
of sendscript function calls which can be given to sendscript's parse.
|
|
537
|
+
|
|
538
|
+
Client functions cannot be called by sendscript functions (as of yet) since we
|
|
539
|
+
cannot serialize client functions. No work has been done to have the server send
|
|
540
|
+
back intermediate values to perform client function calls or by performing
|
|
541
|
+
smaller sendscript program payloads that are passed to the client. Very
|
|
542
|
+
interesting stuff to look into. You can achieve this now but it looks less clean
|
|
543
|
+
because you have to do `send` calls which is a bit manual.
|
|
544
|
+
|
|
545
|
+
```js
|
|
546
|
+
await send(updateUser(id, merge(await send(getUser(id)), { ...newValues })))
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
It might be interesting to allow configuration to create references that will
|
|
550
|
+
trigger a send whenever await is called. That would remove the ability to create
|
|
551
|
+
a single payload whenever using await. You can then write the above in the
|
|
552
|
+
following manner.
|
|
553
|
+
|
|
554
|
+
```js
|
|
555
|
+
await updateUser(id, merge(await getUser(id), { ...newValues }))
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Possible footgun is that updateUser is only performed when awaited (.then is
|
|
559
|
+
called). This lazy behavior can trip users. This footgun could be resolved by
|
|
560
|
+
checking if any outstanding work exists at the end of a step or the beginning of
|
|
561
|
+
a new step.
|
|
562
|
+
|
|
563
|
+
### Error handling
|
|
564
|
+
|
|
565
|
+
Sendscript does not have builtin tools to handle errors. You can write your own
|
|
566
|
+
utilities for that or use things like [Ramda's tryCatch][tryCatch]. This will
|
|
567
|
+
likely never be supported as JS try catch does not have a return value. It might
|
|
568
|
+
make sense to wrap everything that can throw in promises which is easy to do
|
|
569
|
+
with async functions.
|
|
570
|
+
|
|
571
|
+
## Tests
|
|
572
|
+
|
|
573
|
+
Tests with 100% code coverage.
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
npm t -- -R silent
|
|
577
|
+
npm t -- report text-summary
|
|
578
|
+
```
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
=============================== Coverage summary ===============================
|
|
582
|
+
Statements : 100% ( 512/512 )
|
|
583
|
+
Branches : 100% ( 147/147 )
|
|
584
|
+
Functions : 100% ( 23/23 )
|
|
585
|
+
Lines : 100% ( 512/512 )
|
|
586
|
+
================================================================================
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
## Formatting
|
|
590
|
+
|
|
591
|
+
Standard because no config.
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
npx standard
|
|
595
|
+
```
|
|
596
|
+
|
|
595
597
|
## Changelog
|
|
596
598
|
|
|
597
599
|
The [changelog][changelog] is generated using the useful
|
|
@@ -610,13 +612,8 @@ See the [LICENSE.txt][license] file for details.
|
|
|
610
612
|
See [issues][issues] for roadmap and known bugs.
|
|
611
613
|
|
|
612
614
|
[license]: ./LICENSE.txt
|
|
613
|
-
|
|
614
615
|
[socket.io]: https://socket.io/
|
|
615
|
-
|
|
616
616
|
[changelog]: ./CHANGELOG.md
|
|
617
|
-
|
|
618
617
|
[auto-changelog]: https://www.npmjs.com/package/auto-changelog
|
|
619
|
-
|
|
620
618
|
[typedoc]: https://github.com/TypeStrong/typedoc
|
|
621
|
-
|
|
622
619
|
[issues]: https://github.com/bas080/sendscript/issues
|
package/README.mz
CHANGED
|
@@ -1,14 +1,64 @@
|
|
|
1
1
|
# SendScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Serialize and execute composable JavaScript function calls with JSON.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/sendscript)
|
|
6
6
|
[](#tests)
|
|
7
7
|
[](https://standardjs.com)
|
|
8
8
|
[](./LICENSE.txt)
|
|
9
9
|
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Composable function calls** - Combine multiple functions into a single
|
|
13
|
+
request
|
|
14
|
+
- **Write ordinary JavaScript** - Programs serialize to JSON and execute as
|
|
15
|
+
written
|
|
16
|
+
- **Type-safe** - Works great with TypeScript for client-side type checking
|
|
17
|
+
- **Zero dependencies** - Lightweight core library
|
|
18
|
+
- **Async/await support** - Seamless async operations within a single payload
|
|
19
|
+
- **Custom serializers** - Support for Date, Map, Set, BigInt, and more
|
|
20
|
+
- **Transport agnostic** - Use HTTP, WebSockets, or any other communication
|
|
21
|
+
method
|
|
22
|
+
|
|
10
23
|
<!-- toc -->
|
|
11
24
|
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Install SendScript from npm:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install sendscript
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
Here's the simplest example to get started with SendScript:
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
import Stringify from 'sendscript/stringify.mjs'
|
|
39
|
+
import Parse from 'sendscript/parse.mjs'
|
|
40
|
+
import references from 'sendscript/references.mjs'
|
|
41
|
+
|
|
42
|
+
// Define your functions
|
|
43
|
+
const functions = {
|
|
44
|
+
greet: (name) => `Hello, ${name}!`,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Create client-side stubs
|
|
48
|
+
const { greet } = references(['greet'])
|
|
49
|
+
|
|
50
|
+
// Serialize a program on the client
|
|
51
|
+
const stringify = Stringify()
|
|
52
|
+
const program = stringify(greet('World'))
|
|
53
|
+
|
|
54
|
+
// Parse and execute on the server
|
|
55
|
+
const parse = Parse(['greet'], functions)
|
|
56
|
+
const result = parse(program) // "Hello, World!"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This demonstrates the core concept: write a program on the client, serialize it
|
|
60
|
+
to JSON, send it to the server, and execute it exactly as written.
|
|
61
|
+
|
|
12
62
|
## Introduction
|
|
13
63
|
|
|
14
64
|
There has been interest in improving APIs by allowing aggregations in a single
|
|
@@ -19,7 +69,16 @@ request. Examples include
|
|
|
19
69
|
input/arguments of another.
|
|
20
70
|
|
|
21
71
|
- [GraphQL](https://graphql.org/) is very cool but also introduces a new
|
|
22
|
-
|
|
72
|
+
language and the tooling that is required to wield it.
|
|
73
|
+
|
|
74
|
+
## Why SendScript?
|
|
75
|
+
|
|
76
|
+
Unlike JSON-RPC (which doesn't support function composition) or GraphQL (which
|
|
77
|
+
requires learning a new language), SendScript lets you write ordinary
|
|
78
|
+
JavaScript/TypeScript that gets serialized and executed on the server exactly as
|
|
79
|
+
written.
|
|
80
|
+
|
|
81
|
+
## How It Works
|
|
23
82
|
|
|
24
83
|
What SendScript attempts is to allow for very expressive queries and mutations
|
|
25
84
|
to be performed that read and write like ordinary JS. That means that the
|
|
@@ -69,14 +128,13 @@ This package is nothing more than the absolute core of sendscript. It includes:
|
|
|
69
128
|
- `parse` which takes the `stringify` JSON string and a real module and returns
|
|
70
129
|
the result.
|
|
71
130
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
piqued your interest.
|
|
131
|
+
> **Note:** SendScript works in Node.js and browsers. The core library is
|
|
132
|
+
> framework-agnostic and transport-agnostic — use HTTP, WebSockets, or any other
|
|
133
|
+
> communication method that suits your needs.
|
|
76
134
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
135
|
+
Future enhancements may include support for more complex (de)serializers,
|
|
136
|
+
improved error handling, and deeper integration of client functions with
|
|
137
|
+
SendScript programs. Contributions and feedback are welcome.
|
|
80
138
|
|
|
81
139
|
## Socket example
|
|
82
140
|
|
|
@@ -98,7 +156,7 @@ export const square = (a) => a * a
|
|
|
98
156
|
|
|
99
157
|
### Server
|
|
100
158
|
|
|
101
|
-
Here a socket.io server that runs SendScript programs.
|
|
159
|
+
Here's a socket.io server that runs SendScript programs.
|
|
102
160
|
|
|
103
161
|
```js cat - > ./example/server.socket.io.mjs
|
|
104
162
|
// ./example/server.socket.io.mjs
|
|
@@ -181,35 +239,17 @@ pkill sendscript
|
|
|
181
239
|
|
|
182
240
|
## Repl
|
|
183
241
|
|
|
184
|
-
Sendscript ships with a
|
|
242
|
+
Sendscript ships with a bare-bones (no-dependencies) node-repl script. One can
|
|
185
243
|
run it by simply typing `sendscript` in their console.
|
|
186
244
|
|
|
187
|
-
> Use the `DEBUG='*'` to enable all logs or `DEBUG='sendscript:*'` for
|
|
188
|
-
>
|
|
189
|
-
|
|
190
|
-
## Functions
|
|
191
|
-
|
|
192
|
-
SendScript as of `v2.4` supports functions to do basic templating. It does not
|
|
193
|
-
support async functions and will throw an error when you define one.
|
|
194
|
-
|
|
195
|
-
Under the hood the function is called when it is being parsed. Make sure you
|
|
196
|
-
understand what you are doing when mixing client and server functions.
|
|
197
|
-
|
|
198
|
-
```js
|
|
199
|
-
map((a) => add(a, 1))([1, 2, 3]))
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
It also supports nested functions.
|
|
203
|
-
|
|
204
|
-
```js
|
|
205
|
-
map(call)(map((a) => () => add(a, 1))([1, 2, 3]))
|
|
206
|
-
```
|
|
245
|
+
> Use the `DEBUG='*'` to enable all logs or `DEBUG='sendscript:*'` for printing
|
|
246
|
+
> only sendscript logs.
|
|
207
247
|
|
|
208
248
|
## Promises
|
|
209
249
|
|
|
210
250
|
### .then / .catch
|
|
211
251
|
|
|
212
|
-
Supported since
|
|
252
|
+
Supported since `v2.3`.
|
|
213
253
|
|
|
214
254
|
```js
|
|
215
255
|
const getOrCreatePost = send(createPost(title).catch(createPost(title)))
|
|
@@ -407,7 +447,7 @@ const env = {
|
|
|
407
447
|
}
|
|
408
448
|
|
|
409
449
|
// Parse with custom leaf deserializer
|
|
410
|
-
const parse = Parse(schema, env,
|
|
450
|
+
const parse = Parse(schema, env, leafDeserializer)
|
|
411
451
|
|
|
412
452
|
const result = parse(json)
|
|
413
453
|
```
|
|
@@ -415,6 +455,75 @@ const result = parse(json)
|
|
|
415
455
|
The leaf wrapper format is `['leaf', serializedPayload]`, making it unambiguous
|
|
416
456
|
and safe from colliding with SendScript operators.
|
|
417
457
|
|
|
458
|
+
## Limitations
|
|
459
|
+
|
|
460
|
+
The stubs the `references` function returns are limited in the way they can be
|
|
461
|
+
used. This has to do with the fact that they are to be serialized.
|
|
462
|
+
|
|
463
|
+
### Chaining APIs
|
|
464
|
+
|
|
465
|
+
Currently you cannot do chaining APIs. `lib().doThis().doThat()`. It likely is
|
|
466
|
+
possible to implement but will require more extensive schemas and obviously more
|
|
467
|
+
code to create the AST.
|
|
468
|
+
|
|
469
|
+
### Callbacks
|
|
470
|
+
|
|
471
|
+
Although it is possible to mix client and server functions, it works very
|
|
472
|
+
different to ordinary functions. Client functions can be used but should be seen
|
|
473
|
+
as a templating tool to make sendscript programs; just like one would use
|
|
474
|
+
JavaScript with react templates. `items.map(deleteItem)` would return an array
|
|
475
|
+
of sendscript function calls which can be given to sendscript's parse.
|
|
476
|
+
|
|
477
|
+
Client functions cannot be called by sendscript functions (as of yet) since we
|
|
478
|
+
cannot serialize client functions. No work has been done to have the server send
|
|
479
|
+
back intermediate values to perform client function calls or by performing
|
|
480
|
+
smaller sendscript program payloads that are passed to the client. Very
|
|
481
|
+
interesting stuff to look into. You can achieve this now but it looks less clean
|
|
482
|
+
because you have to do `send` calls which is a bit manual.
|
|
483
|
+
|
|
484
|
+
```js
|
|
485
|
+
await send(updateUser(id, merge(await send(getUser(id)), { ...newValues })))
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
It might be interesting to allow configuration to create references that will
|
|
489
|
+
trigger a send whenever await is called. That would remove the ability to create
|
|
490
|
+
a single payload whenever using await. You can then write the above in the
|
|
491
|
+
following manner.
|
|
492
|
+
|
|
493
|
+
```js
|
|
494
|
+
await updateUser(id, merge(await getUser(id), { ...newValues }))
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Possible footgun is that updateUser is only performed when awaited (.then is
|
|
498
|
+
called). This lazy behavior can trip users. This footgun could be resolved by
|
|
499
|
+
checking if any outstanding work exists at the end of a step or the beginning of
|
|
500
|
+
a new step.
|
|
501
|
+
|
|
502
|
+
### Error handling
|
|
503
|
+
|
|
504
|
+
Sendscript does not have builtin tools to handle errors. You can write your own
|
|
505
|
+
utilities for that or use things like [Ramda's tryCatch][tryCatch]. This will
|
|
506
|
+
likely never be supported as JS try catch does not have a return value. It might
|
|
507
|
+
make sense to wrap everything that can throw in promises which is easy to do
|
|
508
|
+
with async functions.
|
|
509
|
+
|
|
510
|
+
## Tests
|
|
511
|
+
|
|
512
|
+
Tests with 100% code coverage.
|
|
513
|
+
|
|
514
|
+
```bash bash
|
|
515
|
+
npm t -- -R silent
|
|
516
|
+
npm t -- report text-summary
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## Formatting
|
|
520
|
+
|
|
521
|
+
Standard because no config.
|
|
522
|
+
|
|
523
|
+
```bash bash
|
|
524
|
+
npx standard
|
|
525
|
+
```
|
|
526
|
+
|
|
418
527
|
## Changelog
|
|
419
528
|
|
|
420
529
|
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/
|
|
11
|
+
Defined in: [math.ts:1](https://github.com/bas080/sendscript/blob/46421de16533066ee13c91ea75505b1ecdd548f0/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/46421de16533066ee13c91ea75505b1ecdd548f0/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.4.
|
|
3
|
+
"version": "2.4.2",
|
|
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.
|
|
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/curry.mjs
DELETED
package/curry.test.mjs
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { test } from 'tap'
|
|
2
|
-
import curry from './curry.mjs'
|
|
3
|
-
|
|
4
|
-
test('curry function', (t) => {
|
|
5
|
-
t.test('curry function returns a function', (assert) => {
|
|
6
|
-
const curried = curry((a, b, c) => a + b + c)
|
|
7
|
-
assert.type(curried, 'function', 'returns a function')
|
|
8
|
-
assert.end()
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
t.test('curried function returns correct result', (assert) => {
|
|
12
|
-
const curried = curry((a, b, c) => a + b + c)
|
|
13
|
-
const result = curried(1)(2)(3)
|
|
14
|
-
assert.equal(result, 6, 'returns correct result')
|
|
15
|
-
assert.end()
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
t.test('curried function handles partial application', (assert) => {
|
|
19
|
-
const curried = curry((a, b, c) => a + b + c)
|
|
20
|
-
const partial = curried(1, 2)
|
|
21
|
-
const result = partial(3)
|
|
22
|
-
assert.equal(result, 6, 'handles partial application')
|
|
23
|
-
assert.end()
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
t.test('curried function handles multiple arguments', (assert) => {
|
|
27
|
-
const curried = curry((a, b, c, d) => a + b + c + d)
|
|
28
|
-
const partial = curried(1)
|
|
29
|
-
const result = partial(2)(3, 4)
|
|
30
|
-
assert.equal(result, 10, 'handles multiple arguments')
|
|
31
|
-
assert.end()
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
t.end()
|
|
35
|
-
})
|