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 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
- Write JS code that you can run on servers, browsers or other clients.
3
+ Serialize and execute composable JavaScript function calls with JSON.
4
4
 
5
- [![NPM](https://img.shields.io/npm/v/sendscript?color=blue\&style=flat-square)](https://www.npmjs.com/package/sendscript)
5
+ [![NPM](https://img.shields.io/npm/v/sendscript?color=blue&style=flat-square)](https://www.npmjs.com/package/sendscript)
6
6
  [![100% Code Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen?style=flat-square)](#tests)
7
7
  [![Standard Code Style](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](https://standardjs.com)
8
- [![License](https://img.shields.io/npm/l/sendscript?color=brightgreen\&style=flat-square)](./LICENSE.txt)
8
+ [![License](https://img.shields.io/npm/l/sendscript?color=brightgreen&style=flat-square)](./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
- - [Reference](#reference)
14
- * [defaultLeafParse](#defaultleafparse)
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
- * [JSON-RPC](https://json-rpc.dev/) which allows you to do multiple requests but
51
- it does not allow you to compose the return value of one endpoint to be the
52
- input/arguments of another.
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
- * [GraphQL](https://graphql.org/) is very cool but also introduces a new
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
- * The `references` function to create stubs to write the programs.
109
- * `stringify` which takes the program and returns a JSON string.
110
- * `parse` which takes the `stringify` JSON string and a real module and returns
111
- the result.
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)**&#x20;
131
-
132
- Returns **any**&#x20;
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)**&#x20;
141
- * `env` **Env** runtime environment for refs
142
- * `leafParse` (optional, default `defaultLeafParse`)
143
-
144
- Returns **[parse](#parse)**&#x20;
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>)**&#x20;
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)**&#x20;
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
- [stringify.mjs:37-47](https://github.com/bas080/sendscript/blob/5249a85a08b10485f0d64058fd2c8bcaae84edda/stringify.mjs#L37-L47 "Source code on GitHub")
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
- Default strict serializer for leaf values.
201
-
202
- Rejects non-JSON-safe values.
203
-
204
- #### Parameters
205
-
206
- * `x` **any**&#x20;
207
-
208
- Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**&#x20;
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)**&#x20;
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**&#x20;
231
-
232
- Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**&#x20;
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
- Result: 100
277
+ ```
278
+ Result: 100
279
+ ```
336
280
 
337
281
  ## Repl
338
282
 
339
- Sendscript ships with a barebones (no-dependencies) node-repl script. One can
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
- > printingonly sendscript logs.
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 vs `v2.3`.
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. Obviously the module would have the benefits that TypeScript offers when
406
- coding.
407
- 2. You can use tools like [typedoc][typedoc] to generate docs from your types to
408
- share with consumers of your API.
409
- 3. You can use the types of the module to coerce your client to adopt the
410
- module's type.
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
- > \[!NOTE] Although type coercion on the client side can improve the development
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
- * Ensures arguments match expected types and shapes.
522
- * Throws structured errors that can be propagated to clients.
523
- * Works with TypeScript for automatic type inference.
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, leadDeserializer)
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
- Write JS code that you can run on servers, browsers or other clients.
3
+ Serialize and execute composable JavaScript function calls with JSON.
4
4
 
5
5
  [![NPM](https://img.shields.io/npm/v/sendscript?color=blue&style=flat-square)](https://www.npmjs.com/package/sendscript)
6
6
  [![100% Code Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen?style=flat-square)](#tests)
7
7
  [![Standard Code Style](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](https://standardjs.com)
8
8
  [![License](https://img.shields.io/npm/l/sendscript?color=brightgreen&style=flat-square)](./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
- languages and the tooling that is required to wield it.
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
- The naming could use more love and there are many things to solve either in the
73
- core or around it. Things like supporting more complex (de)serializers, errors
74
- and maybe mixing client functions with sendscript programs. Contact me if I have
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
- ## Reference
78
-
79
- <!-- Reference -->
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 barebones (no-dependencies) node-repl script. One can
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
- > printingonly sendscript logs.
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 vs `v2.3`.
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, leadDeserializer)
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/5249a85a08b10485f0d64058fd2c8bcaae84edda/example/typescript/math.ts#L1)
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/5249a85a08b10485f0d64058fd2c8bcaae84edda/example/typescript/math.ts#L2)
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.0",
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.6.3",
28
+ "tap": "^21.7.1",
29
29
  "tape-check": "^1.0.0-rc.0",
30
30
  "typedoc": "^0.28.18",
31
31
  "typedoc-plugin-markdown": "^4.11.0"
package/curry.mjs DELETED
@@ -1,11 +0,0 @@
1
- export default function curry (func) {
2
- return function curried (...args) {
3
- if (args.length >= func.length) {
4
- return func.apply(this, args)
5
- } else {
6
- return function (...args2) {
7
- return curried.apply(this, args.concat(args2))
8
- }
9
- }
10
- }
11
- }
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
- })