sendscript 2.4.1 → 2.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +0 -2
- package/README.md +185 -225
- package/README.mz +114 -60
- package/example/typescript/client.ts +16 -3
- package/example/typescript/docs/functions/add.md +1 -1
- package/example/typescript/docs/functions/square.md +1 -1
- package/example/typescript/math.client.ts +10 -1
- package/example/typescript/math.ts +9 -2
- package/package.json +1 -1
- package/curry.mjs +0 -11
- package/curry.test.mjs +0 -35
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.4.3](https://github.com/bas080/sendscript/compare/v2.4.2...v2.4.3)
|
|
8
|
+
|
|
9
|
+
- Improve TypeScript section with comprehensive examples and explanations [`a3c5867`](https://github.com/bas080/sendscript/commit/a3c5867f835bbcde1e802e75c80ca6f02dd4f0e8)
|
|
10
|
+
|
|
11
|
+
#### [v2.4.2](https://github.com/bas080/sendscript/compare/v2.4.1...v2.4.2)
|
|
12
|
+
|
|
13
|
+
> 17 August 2026
|
|
14
|
+
|
|
15
|
+
- Update tap and typedoc [`46421de`](https://github.com/bas080/sendscript/commit/46421de16533066ee13c91ea75505b1ecdd548f0)
|
|
16
|
+
- Remove the unused curry helper [`9dcbb48`](https://github.com/bas080/sendscript/commit/9dcbb485b455937070c65359ecde8236c128bee8)
|
|
17
|
+
- Remove duplicate Promises section with outdated async/await information [`5f43ddb`](https://github.com/bas080/sendscript/commit/5f43ddb91ddb2d77227ddd6a570807f874230ea1)
|
|
18
|
+
|
|
7
19
|
#### [v2.4.1](https://github.com/bas080/sendscript/compare/v2.4.0...v2.4.1)
|
|
8
20
|
|
|
21
|
+
> 29 June 2026
|
|
22
|
+
|
|
9
23
|
- Update tap and typedoc version [`3f10410`](https://github.com/bas080/sendscript/commit/3f1041020e00331ff30ffb5f972738dbaf7a50f3)
|
|
10
24
|
- Update tap to 21.7.1 [`d1b5bbd`](https://github.com/bas080/sendscript/commit/d1b5bbd4cb7012e6606973d3d23d08df56cadca2)
|
|
11
25
|
- Document limitations of sendscript [`f577499`](https://github.com/bas080/sendscript/commit/f577499279b85e04ea589a3bcb1866d145216f00)
|
package/CONTRIBUTING.md
CHANGED
|
@@ -52,8 +52,6 @@ Generate the README from the mz file.
|
|
|
52
52
|
```bash bash
|
|
53
53
|
markatzea ./README.mz | tee ./README.md
|
|
54
54
|
|
|
55
|
-
npx documentation readme schema.mjs references.mjs stringify.mjs parse.mjs -s Reference --github --a public --markdown-toc false
|
|
56
|
-
|
|
57
55
|
npx markdown-toc --maxdepth 3 -i README.md
|
|
58
56
|
|
|
59
57
|
git add *.md ./example
|
package/README.md
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
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)
|
|
@@ -28,10 +35,11 @@ RPC and no-build with composable function calls in a single payload.
|
|
|
28
35
|
- [Promises](#promises)
|
|
29
36
|
* [.then / .catch](#then--catch)
|
|
30
37
|
* [await](#await)
|
|
31
|
-
- [Promises](#promises-1)
|
|
32
|
-
* [.then / .catch](#then--catch-1)
|
|
33
|
-
* [await](#await-1)
|
|
34
38
|
- [TypeScript](#typescript)
|
|
39
|
+
* [Server-Side Module](#server-side-module)
|
|
40
|
+
* [Client-Side Type Stub](#client-side-type-stub)
|
|
41
|
+
* [Using Typed References](#using-typed-references)
|
|
42
|
+
* [Generating API Documentation](#generating-api-documentation)
|
|
35
43
|
- [Schema and Nested Modules](#schema-and-nested-modules)
|
|
36
44
|
* [Defining a Nested Module](#defining-a-nested-module)
|
|
37
45
|
- [Validation (using Zod)](#validation-using-zod)
|
|
@@ -50,17 +58,63 @@ RPC and no-build with composable function calls in a single payload.
|
|
|
50
58
|
|
|
51
59
|
<!-- tocstop -->
|
|
52
60
|
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
Install SendScript from npm:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install sendscript
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Quick Start
|
|
70
|
+
|
|
71
|
+
Here's the simplest example to get started with SendScript:
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
import Stringify from 'sendscript/stringify.mjs'
|
|
75
|
+
import Parse from 'sendscript/parse.mjs'
|
|
76
|
+
import references from 'sendscript/references.mjs'
|
|
77
|
+
|
|
78
|
+
// Define your functions
|
|
79
|
+
const functions = {
|
|
80
|
+
greet: (name) => `Hello, ${name}!`,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Create client-side stubs
|
|
84
|
+
const { greet } = references(['greet'])
|
|
85
|
+
|
|
86
|
+
// Serialize a program on the client
|
|
87
|
+
const stringify = Stringify()
|
|
88
|
+
const program = stringify(greet('World'))
|
|
89
|
+
|
|
90
|
+
// Parse and execute on the server
|
|
91
|
+
const parse = Parse(['greet'], functions)
|
|
92
|
+
const result = parse(program) // "Hello, World!"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This demonstrates the core concept: write a program on the client, serialize it
|
|
96
|
+
to JSON, send it to the server, and execute it exactly as written.
|
|
97
|
+
|
|
53
98
|
## Introduction
|
|
54
99
|
|
|
55
100
|
There has been interest in improving APIs by allowing aggregations in a single
|
|
56
101
|
request. Examples include
|
|
57
102
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
103
|
+
- [JSON-RPC](https://json-rpc.dev/) which allows you to do multiple requests but
|
|
104
|
+
it does not allow you to compose the return value of one endpoint to be the
|
|
105
|
+
input/arguments of another.
|
|
106
|
+
|
|
107
|
+
- [GraphQL](https://graphql.org/) is very cool but also introduces a new
|
|
108
|
+
language and the tooling that is required to wield it.
|
|
61
109
|
|
|
62
|
-
|
|
63
|
-
|
|
110
|
+
## Why SendScript?
|
|
111
|
+
|
|
112
|
+
Unlike JSON-RPC (which doesn't support function composition) or GraphQL (which
|
|
113
|
+
requires learning a new language), SendScript lets you write ordinary
|
|
114
|
+
JavaScript/TypeScript that gets serialized and executed on the server exactly as
|
|
115
|
+
written.
|
|
116
|
+
|
|
117
|
+
## How It Works
|
|
64
118
|
|
|
65
119
|
What SendScript attempts is to allow for very expressive queries and mutations
|
|
66
120
|
to be performed that read and write like ordinary JS. That means that the
|
|
@@ -81,7 +135,6 @@ const stringify = Stringify()
|
|
|
81
135
|
|
|
82
136
|
console.log(stringify(add(1,2)))
|
|
83
137
|
```
|
|
84
|
-
|
|
85
138
|
```json
|
|
86
139
|
["call",["ref","add"],[["leaf","1"],["leaf","2"]]]
|
|
87
140
|
```
|
|
@@ -103,7 +156,6 @@ const program = '["call",["ref","add"],[1,2]]'
|
|
|
103
156
|
|
|
104
157
|
console.log(parse(program))
|
|
105
158
|
```
|
|
106
|
-
|
|
107
159
|
```json
|
|
108
160
|
3
|
|
109
161
|
```
|
|
@@ -113,131 +165,18 @@ composition and even await.
|
|
|
113
165
|
|
|
114
166
|
This package is nothing more than the absolute core of sendscript. It includes:
|
|
115
167
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
The naming could use more love and there are many things to solve either in the
|
|
122
|
-
core or around it. Things like supporting more complex (de)serializers, errors
|
|
123
|
-
and maybe mixing client functions with sendscript programs. Contact me if I have
|
|
124
|
-
piqued your interest.
|
|
125
|
-
|
|
126
|
-
## Reference
|
|
127
|
-
|
|
128
|
-
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
|
129
|
-
|
|
130
|
-
### defaultLeafParse
|
|
131
|
-
|
|
132
|
-
[parse.mjs:49-49](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/parse.mjs#L49-L49 "Source code on GitHub")
|
|
133
|
-
|
|
134
|
-
Default deserializer for leaf nodes.
|
|
135
|
-
|
|
136
|
-
#### Parameters
|
|
137
|
-
|
|
138
|
-
* `text` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 
|
|
139
|
-
|
|
140
|
-
Returns **any** 
|
|
141
|
-
|
|
142
|
-
### Parse
|
|
143
|
-
|
|
144
|
-
[parse.mjs:68-215](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/parse.mjs#L68-L215 "Source code on GitHub")
|
|
145
|
-
|
|
146
|
-
#### Parameters
|
|
147
|
-
|
|
148
|
-
* `schema` **[Schema](#schema)** 
|
|
149
|
-
* `env` **Env** runtime environment for refs
|
|
150
|
-
* `leafParse` (optional, default `defaultLeafParse`)
|
|
151
|
-
|
|
152
|
-
Returns **[parse](#parse)** 
|
|
153
|
-
|
|
154
|
-
### parse
|
|
155
|
-
|
|
156
|
-
[parse.mjs:78-214](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/parse.mjs#L78-L214 "Source code on GitHub")
|
|
157
|
-
|
|
158
|
-
Parses and executes a serialized program.
|
|
159
|
-
|
|
160
|
-
#### Parameters
|
|
161
|
-
|
|
162
|
-
* `program` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** JSON encoded program
|
|
163
|
-
|
|
164
|
-
Returns **(any | [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<any>)** 
|
|
165
|
-
|
|
166
|
-
### References
|
|
167
|
-
|
|
168
|
-
[references.mjs:109-127](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/references.mjs#L109-L127 "Source code on GitHub")
|
|
169
|
-
|
|
170
|
-
Builds a nested API structure from a schema definition.
|
|
171
|
-
|
|
172
|
-
#### Parameters
|
|
173
|
-
|
|
174
|
-
* `schema` **[Schema](#schema)** 
|
|
175
|
-
* `parentPath` (optional, default `[]`)
|
|
176
|
-
|
|
177
|
-
<!---->
|
|
178
|
-
|
|
179
|
-
* Throws **[Error](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)** If schema format is invalid
|
|
180
|
-
|
|
181
|
-
Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Nested instrumented API object
|
|
182
|
-
|
|
183
|
-
### SchemaNode
|
|
184
|
-
|
|
185
|
-
[schema.mjs:1-19](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/schema.mjs#L1-L6 "Source code on GitHub")
|
|
186
|
-
|
|
187
|
-
A single schema node.
|
|
188
|
-
|
|
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)])
|
|
190
|
-
|
|
191
|
-
### Schema
|
|
192
|
-
|
|
193
|
-
[schema.mjs:1-19](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/schema.mjs#L8-L18 "Source code on GitHub")
|
|
194
|
-
|
|
195
|
-
A schema defines the structure of the runtime API tree.
|
|
196
|
-
|
|
197
|
-
* string → leaf node
|
|
198
|
-
* \[name, children] → namespace node
|
|
199
|
-
|
|
200
|
-
Schema is recursive: nodes can contain nested schemas.
|
|
201
|
-
|
|
202
|
-
Type: [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[SchemaNode](#schemanode)>
|
|
203
|
-
|
|
204
|
-
### defaultLeafStringify
|
|
205
|
-
|
|
206
|
-
[stringify.mjs:37-47](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/stringify.mjs#L37-L47 "Source code on GitHub")
|
|
207
|
-
|
|
208
|
-
Default strict serializer for leaf values.
|
|
209
|
-
|
|
210
|
-
Rejects non-JSON-safe values.
|
|
211
|
-
|
|
212
|
-
#### Parameters
|
|
213
|
-
|
|
214
|
-
* `x` **any** 
|
|
168
|
+
- The `references` function to create stubs to write the programs.
|
|
169
|
+
- `stringify` which takes the program and returns a JSON string.
|
|
170
|
+
- `parse` which takes the `stringify` JSON string and a real module and returns
|
|
171
|
+
the result.
|
|
215
172
|
|
|
216
|
-
|
|
173
|
+
> **Note:** SendScript works in Node.js and browsers. The core library is
|
|
174
|
+
> framework-agnostic and transport-agnostic — use HTTP, WebSockets, or any other
|
|
175
|
+
> communication method that suits your needs.
|
|
217
176
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
Creates a stringify function for SendScript AST structures.
|
|
223
|
-
|
|
224
|
-
#### Parameters
|
|
225
|
-
|
|
226
|
-
* `leafStringify` (optional, default `defaultLeafStringify`)
|
|
227
|
-
|
|
228
|
-
Returns **[stringify](#stringify)** 
|
|
229
|
-
|
|
230
|
-
### stringify
|
|
231
|
-
|
|
232
|
-
[stringify.mjs:64-156](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/stringify.mjs#L64-L156 "Source code on GitHub")
|
|
233
|
-
|
|
234
|
-
Serializes a program into a JSON string representation.
|
|
235
|
-
|
|
236
|
-
#### Parameters
|
|
237
|
-
|
|
238
|
-
* `program` **any** 
|
|
239
|
-
|
|
240
|
-
Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 
|
|
177
|
+
Future enhancements may include support for more complex (de)serializers,
|
|
178
|
+
improved error handling, and deeper integration of client functions with
|
|
179
|
+
SendScript programs. Contributions and feedback are welcome.
|
|
241
180
|
|
|
242
181
|
## Socket example
|
|
243
182
|
|
|
@@ -259,7 +198,7 @@ export const square = (a) => a * a
|
|
|
259
198
|
|
|
260
199
|
### Server
|
|
261
200
|
|
|
262
|
-
Here a socket.io server that runs SendScript programs.
|
|
201
|
+
Here's a socket.io server that runs SendScript programs.
|
|
263
202
|
|
|
264
203
|
```js
|
|
265
204
|
// ./example/server.socket.io.mjs
|
|
@@ -339,8 +278,9 @@ node ./example/client.socket.io.mjs
|
|
|
339
278
|
|
|
340
279
|
pkill sendscript
|
|
341
280
|
```
|
|
342
|
-
|
|
343
|
-
|
|
281
|
+
```
|
|
282
|
+
Result: 100
|
|
283
|
+
```
|
|
344
284
|
|
|
345
285
|
## Repl
|
|
346
286
|
|
|
@@ -354,39 +294,7 @@ run it by simply typing `sendscript` in their console.
|
|
|
354
294
|
|
|
355
295
|
### .then / .catch
|
|
356
296
|
|
|
357
|
-
Supported since
|
|
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
|
-
```
|
|
384
|
-
|
|
385
|
-
## Promises
|
|
386
|
-
|
|
387
|
-
### .then / .catch
|
|
388
|
-
|
|
389
|
-
Supported since vs `v2.3`.
|
|
297
|
+
Supported since `v2.3`.
|
|
390
298
|
|
|
391
299
|
```js
|
|
392
300
|
const getOrCreatePost = send(createPost(title).catch(createPost(title)))
|
|
@@ -422,39 +330,84 @@ defined properties and returned values.
|
|
|
422
330
|
|
|
423
331
|
## TypeScript
|
|
424
332
|
|
|
425
|
-
|
|
333
|
+
Using SendScript with TypeScript enables **type-safe client-side code**. Your
|
|
334
|
+
client can have full IDE autocomplete and compile-time type checking when
|
|
335
|
+
calling server functions.
|
|
426
336
|
|
|
427
|
-
|
|
428
|
-
coding.
|
|
429
|
-
2. You can use tools like [typedoc][typedoc] to generate docs from your types to
|
|
430
|
-
share with consumers of your API.
|
|
431
|
-
3. You can use the types of the module to coerce your client to adopt the
|
|
432
|
-
module's type.
|
|
337
|
+
### Server-Side Module
|
|
433
338
|
|
|
434
|
-
|
|
339
|
+
Define your API as a TypeScript module on the server:
|
|
435
340
|
|
|
436
341
|
```bash
|
|
437
342
|
cat ./example/typescript/math.ts
|
|
438
343
|
```
|
|
344
|
+
```ts
|
|
345
|
+
/**
|
|
346
|
+
* Server-side math module with typed functions
|
|
347
|
+
* These functions will be called from the client through SendScript
|
|
348
|
+
*/
|
|
349
|
+
|
|
350
|
+
export const add = (a: number, b: number): number => a + b
|
|
351
|
+
|
|
352
|
+
export const square = (a: number): number => a * a
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Client-Side Type Stub
|
|
439
357
|
|
|
358
|
+
Create a client-side file that mirrors your server types using the `as typeof`
|
|
359
|
+
casting pattern:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
cat ./example/typescript/math.client.ts
|
|
363
|
+
```
|
|
440
364
|
```ts
|
|
441
|
-
|
|
442
|
-
|
|
365
|
+
/**
|
|
366
|
+
* Client-side type-safe stubs for the math API
|
|
367
|
+
*
|
|
368
|
+
* This file creates typed references that mirror the server's functions.
|
|
369
|
+
* The 'as typeof mathTypes' cast gives us full TypeScript support and IDE autocomplete.
|
|
370
|
+
*/
|
|
371
|
+
|
|
372
|
+
import type * as mathTypes from './math.ts'
|
|
373
|
+
import references from 'sendscript/references.mjs'
|
|
374
|
+
|
|
375
|
+
// Create type-safe stubs - this tells TypeScript that 'add' and 'square'
|
|
376
|
+
// have the same signatures as the server functions
|
|
377
|
+
export default references(['add', 'square']) as typeof mathTypes
|
|
378
|
+
|
|
443
379
|
```
|
|
444
380
|
|
|
445
|
-
|
|
381
|
+
The `as typeof mathTypes` type assertion gives your client-side references the
|
|
382
|
+
exact same types as your server module. This means:
|
|
383
|
+
|
|
384
|
+
- Full IDE autocomplete for function names and parameters
|
|
385
|
+
- Compile-time type checking - catch errors before runtime
|
|
386
|
+
- Your client code looks identical to regular JavaScript calls
|
|
387
|
+
|
|
388
|
+
### Using Typed References
|
|
389
|
+
|
|
390
|
+
Now on your client, you have complete type safety:
|
|
446
391
|
|
|
447
392
|
```bash
|
|
448
393
|
cat ./example/typescript/client.ts
|
|
449
394
|
```
|
|
450
|
-
|
|
451
395
|
```ts
|
|
452
|
-
|
|
396
|
+
/**
|
|
397
|
+
* Client-side usage with type-safe SendScript
|
|
398
|
+
*/
|
|
399
|
+
|
|
400
|
+
import math from './math.client.ts'
|
|
453
401
|
import Stringify from 'sendscript/stringify.mjs'
|
|
454
402
|
|
|
455
403
|
const stringify = Stringify()
|
|
456
404
|
|
|
457
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Send a SendScript program to the server
|
|
407
|
+
*
|
|
408
|
+
* TypeScript knows that the return type matches the program's return type.
|
|
409
|
+
* In this case, square(add(1, 2)) returns a number, so T is number.
|
|
410
|
+
*/
|
|
458
411
|
async function send<T>(program: T): Promise<T> {
|
|
459
412
|
return (await fetch('/api', {
|
|
460
413
|
method: 'POST',
|
|
@@ -462,20 +415,41 @@ async function send<T>(program: T): Promise<T> {
|
|
|
462
415
|
})).json()
|
|
463
416
|
}
|
|
464
417
|
|
|
465
|
-
|
|
418
|
+
// TypeScript provides full autocomplete for math.add and math.square
|
|
419
|
+
// It knows they take numbers and return numbers
|
|
420
|
+
const result = await send(math.square(math.add(1, 2)))
|
|
421
|
+
console.log(result) // 9
|
|
422
|
+
|
|
466
423
|
```
|
|
467
424
|
|
|
468
|
-
|
|
425
|
+
TypeScript knows the exact parameter types and return types for every function
|
|
426
|
+
call.
|
|
427
|
+
|
|
428
|
+
### Generating API Documentation
|
|
429
|
+
|
|
430
|
+
You can use [typedoc][typedoc] to automatically generate documentation from your
|
|
431
|
+
TypeScript types:
|
|
469
432
|
|
|
470
433
|
```bash
|
|
471
|
-
npx typedoc --plugin typedoc-plugin-markdown --out ./
|
|
434
|
+
npx typedoc --plugin typedoc-plugin-markdown --out ./docs ./example/typescript/math.ts
|
|
435
|
+
```
|
|
436
|
+
```
|
|
437
|
+
[96m[info][0m Loaded plugin typedoc-plugin-markdown
|
|
438
|
+
[96m[info][0m markdown generated at ./docs
|
|
472
439
|
```
|
|
473
440
|
|
|
474
|
-
|
|
441
|
+
This generates markdown docs that can be shared with API consumers. See the
|
|
442
|
+
[generated docs](./example/typescript/docs/globals.md).
|
|
475
443
|
|
|
476
|
-
>
|
|
477
|
-
>
|
|
478
|
-
>
|
|
444
|
+
> [!IMPORTANT] **Type vs. Runtime Values**
|
|
445
|
+
>
|
|
446
|
+
> Type casting on the client side improves the development experience, but
|
|
447
|
+
> remember:
|
|
448
|
+
>
|
|
449
|
+
> - The actual serialized JSON may differ from the static types
|
|
450
|
+
> - Runtime values depend on serialization/deserialization
|
|
451
|
+
> - Always validate user input on the server using schema validation (e.g., Zod)
|
|
452
|
+
> - Use the types as a contract, not a guarantee
|
|
479
453
|
|
|
480
454
|
## Schema and Nested Modules
|
|
481
455
|
|
|
@@ -540,9 +514,9 @@ export function createUser(user) {
|
|
|
540
514
|
|
|
541
515
|
**Benefits**:
|
|
542
516
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
517
|
+
- Ensures arguments match expected types and shapes.
|
|
518
|
+
- Throws structured errors that can be propagated to clients.
|
|
519
|
+
- Works with TypeScript for automatic type inference.
|
|
546
520
|
|
|
547
521
|
## Leaf Serializer
|
|
548
522
|
|
|
@@ -661,7 +635,7 @@ a new step.
|
|
|
661
635
|
### Error handling
|
|
662
636
|
|
|
663
637
|
Sendscript does not have builtin tools to handle errors. You can write your own
|
|
664
|
-
utilities for that or use things like
|
|
638
|
+
utilities for that or use things like [Ramda's tryCatch][tryCatch]. This will
|
|
665
639
|
likely never be supported as JS try catch does not have a return value. It might
|
|
666
640
|
make sense to wrap everything that can throw in promises which is easy to do
|
|
667
641
|
with async functions.
|
|
@@ -674,22 +648,13 @@ Tests with 100% code coverage.
|
|
|
674
648
|
npm t -- -R silent
|
|
675
649
|
npm t -- report text-summary
|
|
676
650
|
```
|
|
677
|
-
|
|
678
651
|
```
|
|
679
652
|
|
|
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
653
|
=============================== Coverage summary ===============================
|
|
689
|
-
Statements : 100% (
|
|
690
|
-
Branches : 100% (
|
|
691
|
-
Functions : 100% (
|
|
692
|
-
Lines : 100% (
|
|
654
|
+
Statements : 100% ( 512/512 )
|
|
655
|
+
Branches : 100% ( 147/147 )
|
|
656
|
+
Functions : 100% ( 23/23 )
|
|
657
|
+
Lines : 100% ( 512/512 )
|
|
693
658
|
================================================================================
|
|
694
659
|
```
|
|
695
660
|
|
|
@@ -719,13 +684,8 @@ See the [LICENSE.txt][license] file for details.
|
|
|
719
684
|
See [issues][issues] for roadmap and known bugs.
|
|
720
685
|
|
|
721
686
|
[license]: ./LICENSE.txt
|
|
722
|
-
|
|
723
687
|
[socket.io]: https://socket.io/
|
|
724
|
-
|
|
725
688
|
[changelog]: ./CHANGELOG.md
|
|
726
|
-
|
|
727
689
|
[auto-changelog]: https://www.npmjs.com/package/auto-changelog
|
|
728
|
-
|
|
729
690
|
[typedoc]: https://github.com/TypeStrong/typedoc
|
|
730
|
-
|
|
731
691
|
[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
|
|
@@ -191,39 +249,7 @@ run it by simply typing `sendscript` in their console.
|
|
|
191
249
|
|
|
192
250
|
### .then / .catch
|
|
193
251
|
|
|
194
|
-
Supported since
|
|
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
|
-
```
|
|
221
|
-
|
|
222
|
-
## Promises
|
|
223
|
-
|
|
224
|
-
### .then / .catch
|
|
225
|
-
|
|
226
|
-
Supported since vs `v2.3`.
|
|
252
|
+
Supported since `v2.3`.
|
|
227
253
|
|
|
228
254
|
```js
|
|
229
255
|
const getOrCreatePost = send(createPost(title).catch(createPost(title)))
|
|
@@ -259,38 +285,66 @@ defined properties and returned values.
|
|
|
259
285
|
|
|
260
286
|
## TypeScript
|
|
261
287
|
|
|
262
|
-
|
|
288
|
+
Using SendScript with TypeScript enables **type-safe client-side code**. Your
|
|
289
|
+
client can have full IDE autocomplete and compile-time type checking when
|
|
290
|
+
calling server functions.
|
|
263
291
|
|
|
264
|
-
|
|
265
|
-
coding.
|
|
266
|
-
2. You can use tools like [typedoc][typedoc] to generate docs from your types to
|
|
267
|
-
share with consumers of your API.
|
|
268
|
-
3. You can use the types of the module to coerce your client to adopt the
|
|
269
|
-
module's type.
|
|
292
|
+
### Server-Side Module
|
|
270
293
|
|
|
271
|
-
|
|
294
|
+
Define your API as a TypeScript module on the server:
|
|
272
295
|
|
|
273
296
|
```bash|ts bash
|
|
274
297
|
cat ./example/typescript/math.ts
|
|
275
298
|
```
|
|
276
299
|
|
|
277
|
-
|
|
300
|
+
### Client-Side Type Stub
|
|
301
|
+
|
|
302
|
+
Create a client-side file that mirrors your server types using the `as typeof`
|
|
303
|
+
casting pattern:
|
|
278
304
|
|
|
279
305
|
```bash|ts bash
|
|
280
|
-
cat ./example/typescript/client.ts
|
|
306
|
+
cat ./example/typescript/math.client.ts
|
|
281
307
|
```
|
|
282
308
|
|
|
283
|
-
|
|
309
|
+
The `as typeof mathTypes` type assertion gives your client-side references the
|
|
310
|
+
exact same types as your server module. This means:
|
|
311
|
+
|
|
312
|
+
- Full IDE autocomplete for function names and parameters
|
|
313
|
+
- Compile-time type checking - catch errors before runtime
|
|
314
|
+
- Your client code looks identical to regular JavaScript calls
|
|
315
|
+
|
|
316
|
+
### Using Typed References
|
|
317
|
+
|
|
318
|
+
Now on your client, you have complete type safety:
|
|
284
319
|
|
|
285
|
-
```bash bash
|
|
286
|
-
|
|
320
|
+
```bash|ts bash
|
|
321
|
+
cat ./example/typescript/client.ts
|
|
287
322
|
```
|
|
288
323
|
|
|
289
|
-
|
|
324
|
+
TypeScript knows the exact parameter types and return types for every function
|
|
325
|
+
call.
|
|
326
|
+
|
|
327
|
+
### Generating API Documentation
|
|
328
|
+
|
|
329
|
+
You can use [typedoc][typedoc] to automatically generate documentation from your
|
|
330
|
+
TypeScript types:
|
|
331
|
+
|
|
332
|
+
```bash bash
|
|
333
|
+
npx typedoc --plugin typedoc-plugin-markdown --out ./docs ./example/typescript/math.ts
|
|
334
|
+
```
|
|
290
335
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
336
|
+
This generates markdown docs that can be shared with API consumers. See the
|
|
337
|
+
[generated docs](./example/typescript/docs/globals.md).
|
|
338
|
+
|
|
339
|
+
> [!IMPORTANT] **Type vs. Runtime Values**
|
|
340
|
+
>
|
|
341
|
+
> Type casting on the client side improves the development experience, but
|
|
342
|
+
> remember:
|
|
343
|
+
>
|
|
344
|
+
> - The actual serialized JSON may differ from the static types
|
|
345
|
+
> - Runtime values depend on serialization/deserialization
|
|
346
|
+
> - Always validate user input on the server using schema validation (e.g., Zod)
|
|
347
|
+
> - Use the types as a contract, not a guarantee
|
|
294
348
|
|
|
295
349
|
## Schema and Nested Modules
|
|
296
350
|
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Client-side usage with type-safe SendScript
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import math from './math.client.ts'
|
|
2
6
|
import Stringify from 'sendscript/stringify.mjs'
|
|
3
7
|
|
|
4
8
|
const stringify = Stringify()
|
|
5
9
|
|
|
6
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Send a SendScript program to the server
|
|
12
|
+
*
|
|
13
|
+
* TypeScript knows that the return type matches the program's return type.
|
|
14
|
+
* In this case, square(add(1, 2)) returns a number, so T is number.
|
|
15
|
+
*/
|
|
7
16
|
async function send<T>(program: T): Promise<T> {
|
|
8
17
|
return (await fetch('/api', {
|
|
9
18
|
method: 'POST',
|
|
@@ -11,4 +20,8 @@ async function send<T>(program: T): Promise<T> {
|
|
|
11
20
|
})).json()
|
|
12
21
|
}
|
|
13
22
|
|
|
14
|
-
|
|
23
|
+
// TypeScript provides full autocomplete for math.add and math.square
|
|
24
|
+
// It knows they take numbers and return numbers
|
|
25
|
+
const result = await send(math.square(math.add(1, 2)))
|
|
26
|
+
console.log(result) // 9
|
|
27
|
+
|
|
@@ -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
|
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side type-safe stubs for the math API
|
|
3
|
+
*
|
|
4
|
+
* This file creates typed references that mirror the server's functions.
|
|
5
|
+
* The 'as typeof mathTypes' cast gives us full TypeScript support and IDE autocomplete.
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import type * as mathTypes from './math.ts'
|
|
2
|
-
import Stringify from 'sendscript/stringify.mjs'
|
|
3
9
|
import references from 'sendscript/references.mjs'
|
|
4
10
|
|
|
11
|
+
// Create type-safe stubs - this tells TypeScript that 'add' and 'square'
|
|
12
|
+
// have the same signatures as the server functions
|
|
5
13
|
export default references(['add', 'square']) as typeof mathTypes
|
|
14
|
+
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Server-side math module with typed functions
|
|
3
|
+
* These functions will be called from the client through SendScript
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const add = (a: number, b: number): number => a + b
|
|
7
|
+
|
|
8
|
+
export const square = (a: number): number => a * a
|
|
9
|
+
|
package/package.json
CHANGED
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
|
-
})
|