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 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
- RPC and no-build with composable function calls in a single payload.
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)
@@ -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
- * [JSON-RPC](https://json-rpc.dev/) which allows you to do multiple requests but
59
- it does not allow you to compose the return value of one endpoint to be the
60
- input/arguments of another.
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
- * [GraphQL](https://graphql.org/) is very cool but also introduces a new
63
- languages and the tooling that is required to wield it.
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
- * The `references` function to create stubs to write the programs.
117
- * `stringify` which takes the program and returns a JSON string.
118
- * `parse` which takes the `stringify` JSON string and a real module and returns
119
- the result.
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)**&#x20;
139
-
140
- Returns **any**&#x20;
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)**&#x20;
149
- * `env` **Env** runtime environment for refs
150
- * `leafParse` (optional, default `defaultLeafParse`)
151
-
152
- Returns **[parse](#parse)**&#x20;
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>)**&#x20;
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)**&#x20;
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**&#x20;
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
- Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**&#x20;
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
- ### Stringify
219
-
220
- [stringify.mjs:56-157](https://github.com/bas080/sendscript/blob/3f1041020e00331ff30ffb5f972738dbaf7a50f3/stringify.mjs#L56-L157 "Source code on GitHub")
221
-
222
- Creates a stringify function for SendScript AST structures.
223
-
224
- #### Parameters
225
-
226
- * `leafStringify` (optional, default `defaultLeafStringify`)
227
-
228
- Returns **[stringify](#stringify)**&#x20;
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**&#x20;
239
-
240
- Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**&#x20;
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
- Result: 100
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 vs `v2.3`.
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
- There is a good use-case to write a module in TypeScript.
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
- 1. Obviously the module would have the benefits that TypeScript offers when
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
- Let's say we have this module which we use on the server.
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
- export const add = (a: number, b: number) => a + b
442
- export const square = (a: number) => a * a
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
- We can then coerce the types of the instrumented stubs.
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
- import math from './math.client.ts'
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
- // The return type of this function matches the type passed as the return of the program.
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
- send(square(add(1, 2)))
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
- We'll also generate the docs for this module.
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 ./example/typescript/docs ./example/typescript/math.ts
434
+ npx typedoc --plugin typedoc-plugin-markdown --out ./docs ./example/typescript/math.ts
435
+ ```
436
+ ```
437
+ [info] Loaded plugin typedoc-plugin-markdown
438
+ [info] markdown generated at ./docs
472
439
  ```
473
440
 
474
- You can see the docs [here](./example/typescript/docs/globals.md)
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
- > \[!NOTE] Although type coercion on the client side can improve the development
477
- > experience, it does not represent the actual type. Values are subject to
478
- > serialization and deserialization.
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
- * Ensures arguments match expected types and shapes.
544
- * Throws structured errors that can be propagated to clients.
545
- * Works with TypeScript for automatic type inference.
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 \[Ramda's tryCatch]\[tryCatch]. This will
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% ( 523/523 )
690
- Branches : 100% ( 153/153 )
691
- Functions : 100% ( 25/25 )
692
- Lines : 100% ( 523/523 )
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
- RPC and no-build with composable function calls in a single payload.
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
@@ -191,39 +249,7 @@ run it by simply typing `sendscript` in their console.
191
249
 
192
250
  ### .then / .catch
193
251
 
194
- Supported since vs `v2.3`.
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
- There is a good use-case to write a module in TypeScript.
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
- 1. Obviously the module would have the benefits that TypeScript offers when
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
- Let's say we have this module which we use on the server.
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
- We can then coerce the types of the instrumented stubs.
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
- We'll also generate the docs for this module.
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 1>&2
286
- npx typedoc --plugin typedoc-plugin-markdown --out ./example/typescript/docs ./example/typescript/math.ts
320
+ ```bash|ts bash
321
+ cat ./example/typescript/client.ts
287
322
  ```
288
323
 
289
- You can see the docs [here](./example/typescript/docs/globals.md)
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
- > [!NOTE] Although type coercion on the client side can improve the development
292
- > experience, it does not represent the actual type. Values are subject to
293
- > serialization and deserialization.
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
- import math from './math.client.ts'
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
- // The return type of this function matches the type passed as the return of the program.
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
- send(square(add(1, 2)))
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/3f1041020e00331ff30ffb5f972738dbaf7a50f3/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/3f1041020e00331ff30ffb5f972738dbaf7a50f3/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
 
@@ -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
- export const add = (a: number, b: number) => a + b
2
- export const square = (a: number) => a * a
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sendscript",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "description": "Blur the line between server and client code.",
5
5
  "module": true,
6
6
  "main": "index.mjs",
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
- })