sendscript 1.0.1 → 1.0.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,15 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [v1.0.2](https://github.com/bas080/sendscript/compare/v1.0.1...v1.0.2)
8
+
9
+ - Improve the typescript example [`6f75ed6`](https://github.com/bas080/sendscript/commit/6f75ed6a4b4db94217fde41ae50bc6b92a2cffda)
10
+ - Move errors to own module [`90d69a3`](https://github.com/bas080/sendscript/commit/90d69a337f92518c8dd5c2ad47dd9b1add80d0c3)
11
+
7
12
  #### [v1.0.1](https://github.com/bas080/sendscript/compare/v1.0.0...v1.0.1)
8
13
 
14
+ > 3 May 2025
15
+
9
16
  - Add async await section to readme [`7e56024`](https://github.com/bas080/sendscript/commit/7e560246e2a7b5b34f1de0ce5eb0df62897cc454)
10
17
 
11
18
  ### [v1.0.0](https://github.com/bas080/sendscript/compare/v0.1.4...v1.0.0)
package/README.md CHANGED
@@ -166,17 +166,67 @@ There is a good use-case to write a module in TypeScript.
166
166
  3. You can use the types of the module to coerce your client to adopt the
167
167
  module's type.
168
168
 
169
+ Let's say we have this module which we use on the server.
170
+
171
+ ```bash
172
+ cat ./example/typescript/math.ts
173
+ ```
169
174
  ```ts
170
- import type * as math from './example/math.ts'
175
+ export const add = (a: number, b: number) => a + b
176
+ export const square = (a: number) => a * a
177
+ ```
171
178
 
179
+ We want to use this module on the client. We create a client version of that module and coerce the types to match those of the server.
180
+
181
+ ```bash
182
+ cat ./example/typescript/math.client.ts
183
+ ```
184
+ ```ts
172
185
  import module from 'sendscript/module.mjs'
186
+ import type * as mathTypes from './math.ts'
187
+
188
+ const math = module([
189
+ 'add',
190
+ 'square'
191
+ ]) as typeof mathTypes
173
192
 
174
- export default module([
175
- add,
176
- squer,
177
- ]) as typeof math
193
+ export default math
178
194
  ```
179
195
 
196
+ We now use the client version of this module.
197
+
198
+ ```bash
199
+ cat ./example/typescript/client.ts
200
+ ```
201
+ ```ts
202
+ import stringify from 'sendscript/stringify.mjs'
203
+
204
+ async function send<T>(program: T): Promise<T>{
205
+ return (await fetch('/api', {
206
+ method: 'POST',
207
+ body: stringify(program)
208
+ })).json()
209
+ }
210
+
211
+ import math from './math.client.ts'
212
+
213
+ const { add, square } = math
214
+
215
+ send(square(add(1, 2)))
216
+ ```
217
+
218
+ We'll also generate the docs for this module.
219
+
220
+ ```bash
221
+ typedoc --plugin typedoc-plugin-markdown --out ./example/typescript/docs ./example/typescript/math.ts
222
+ ```
223
+ ```
224
+ [info] Loaded plugin typedoc-plugin-markdown
225
+ [info] markdown generated at ./example/typescript/docs
226
+ ```
227
+
228
+ You can see the docs [here](./example/typescript/docs/globals.md)
229
+
180
230
  > [!NOTE]
181
231
  > Although type coercion on the client side can improve the development
182
232
  > experience, it does not represent the actual type.
@@ -192,19 +242,19 @@ npm t -- report text-summary
192
242
  ```
193
243
  ```
194
244
 
195
- > sendscript@1.0.1 test
245
+ > sendscript@1.0.2 test
196
246
  > tap -R silent
197
247
 
198
248
 
199
- > sendscript@1.0.1 test
249
+ > sendscript@1.0.2 test
200
250
  > tap report text-summary
201
251
 
202
252
 
203
253
  =============================== Coverage summary ===============================
204
- Statements : 100% ( 239/239 )
205
- Branches : 100% ( 71/71 )
254
+ Statements : 100% ( 245/245 )
255
+ Branches : 100% ( 74/74 )
206
256
  Functions : 100% ( 18/18 )
207
- Lines : 100% ( 239/239 )
257
+ Lines : 100% ( 245/245 )
208
258
  ================================================================================
209
259
  ```
210
260
 
package/README.mz CHANGED
@@ -148,17 +148,32 @@ There is a good use-case to write a module in TypeScript.
148
148
  3. You can use the types of the module to coerce your client to adopt the
149
149
  module's type.
150
150
 
151
- ```ts
152
- import type * as math from './example/math.ts'
151
+ Let's say we have this module which we use on the server.
153
152
 
154
- import module from 'sendscript/module.mjs'
153
+ ```bash|ts bash
154
+ cat ./example/typescript/math.ts
155
+ ```
156
+
157
+ We want to use this module on the client. We create a client version of that module and coerce the types to match those of the server.
158
+
159
+ ```bash|ts bash
160
+ cat ./example/typescript/math.client.ts
161
+ ```
162
+
163
+ We now use the client version of this module.
155
164
 
156
- export default module([
157
- add,
158
- squer,
159
- ]) as typeof math
165
+ ```bash|ts bash
166
+ cat ./example/typescript/client.ts
160
167
  ```
161
168
 
169
+ We'll also generate the docs for this module.
170
+
171
+ ```bash bash
172
+ typedoc --plugin typedoc-plugin-markdown --out ./example/typescript/docs ./example/typescript/math.ts
173
+ ```
174
+
175
+ You can see the docs [here](./example/typescript/docs/globals.md)
176
+
162
177
  > [!NOTE]
163
178
  > Although type coercion on the client side can improve the development
164
179
  > experience, it does not represent the actual type.
package/error.mjs CHANGED
@@ -1,9 +1,2 @@
1
- class BlendError extends Error { }
2
- class BlendExpressionError extends BlendError {}
3
- class BlendInvalidSchemaError extends BlendError {}
4
-
5
- export {
6
- BlendError,
7
- BlendExpressionError,
8
- BlendInvalidSchemaError
9
- }
1
+ export class SendScriptError extends Error {}
2
+ export class SendScriptReferenceError extends SendScriptError {}
@@ -0,0 +1,14 @@
1
+ import stringify from 'sendscript/stringify.mjs'
2
+
3
+ async function send<T>(program: T): Promise<T>{
4
+ return (await fetch('/api', {
5
+ method: 'POST',
6
+ body: stringify(program)
7
+ })).json()
8
+ }
9
+
10
+ import math from './math.client.ts'
11
+
12
+ const { add, square } = math
13
+
14
+ send(square(add(1, 2)))
@@ -0,0 +1,207 @@
1
+ **sendscript**
2
+
3
+ ***
4
+
5
+ # SendScript
6
+
7
+ Write JS code that you can run on servers, browsers or other clients.
8
+
9
+ [![NPM](https://img.shields.io/npm/v/sendscript?color=blue&style=flat-square)](https://www.npmjs.com/package/sendscript)
10
+ [![100% Code Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen?style=flat-square)](#tests)
11
+ [![Standard Code Style](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](https://standardjs.com)
12
+ [![License](https://img.shields.io/npm/l/sendscript?color=brightgreen&style=flat-square)](./LICENSE.txt)
13
+
14
+ <!-- toc -->
15
+
16
+ SendScript leaves it up to you to choose HTTP, web-sockets or any other
17
+ method of communication between servers and clients that best fits your
18
+ needs.
19
+
20
+ ## Socket example
21
+
22
+ For this example we'll use [socket.io][socket.io].
23
+
24
+ ```bash
25
+ npm install --no-save socket.io socket.io-client
26
+ ```
27
+
28
+ > We use the `--no-save` option because it's only for demonstration purposes.
29
+
30
+ ### Module
31
+
32
+ We write a simple module.
33
+
34
+ ```js
35
+ // ./example/math.mjs
36
+
37
+ export const add = (a, b) => a + b
38
+ export const square = a => a * a
39
+ ```
40
+
41
+ ### Server
42
+
43
+ Here a socket.io server that runs SendScript programs.
44
+
45
+ ```js
46
+ // ./example/server.socket.io.mjs
47
+
48
+ import { Server } from 'socket.io'
49
+ import Parse from '../parse.mjs'
50
+ import * as math from './math.mjs'
51
+
52
+ const parse = Parse(math)
53
+ const server = new Server()
54
+ const port = process.env.PORT || 3000
55
+
56
+ server.on('connection', (socket) => {
57
+ socket.on('message', async (program, callback) => {
58
+ try {
59
+ const result = parse(program)
60
+ callback(null, result) // Pass null as the first argument to indicate success
61
+ } catch (error) {
62
+ callback(error) // Pass the error to the callback
63
+ }
64
+ })
65
+ })
66
+
67
+ server.listen(port)
68
+ process.title = 'sendscript'
69
+ ```
70
+
71
+ ### Client
72
+
73
+ Now for a client that sends a program to the server.
74
+
75
+ ```js
76
+ // ./example/client.socket.io.mjs
77
+
78
+ import socketClient from 'socket.io-client'
79
+ import stringify from '../stringify.mjs'
80
+ import module from '../module.mjs'
81
+ import * as math from './math.mjs'
82
+ import assert from 'node:assert'
83
+
84
+ const port = process.env.PORT || 3000
85
+ const client = socketClient(`http://localhost:${port}`)
86
+
87
+ const send = program => {
88
+ return new Promise((resolve, reject) => {
89
+ client.emit('message', stringify(program), (error, result) => {
90
+ error
91
+ ? reject(error)
92
+ : resolve(result)
93
+ })
94
+ })
95
+ }
96
+
97
+ const { add, square } = module(math)
98
+
99
+ // The program to be sent over the wire
100
+ const program = square(add(1, add(add(2, 3), 4)))
101
+
102
+ const result = await send(program)
103
+
104
+ console.log('Result: ', result)
105
+
106
+ assert.equal(result, 100)
107
+
108
+ process.exit(0)
109
+ ```
110
+
111
+ Now we run this server and a client script.
112
+
113
+ ```bash
114
+ set -e
115
+
116
+ # Run the server
117
+ node ./example/server.socket.io.mjs&
118
+
119
+ # Run the client example
120
+ node ./example/client.socket.io.mjs
121
+
122
+ pkill sendscript
123
+ ```
124
+ ```
125
+ Result: 100
126
+ ```
127
+
128
+ ## Async/Await
129
+
130
+ SendScript supports async/await seamlessly within a single request. This avoids the performance pitfalls of waterfall-style messaging, which can be especially slow on high-latency networks.
131
+
132
+ While it's possible to chain promises manually or use utility functions, native async/await support makes your code more readable, modern, and easier to reason about — aligning SendScript with today’s JavaScript best practices.
133
+
134
+ ```js
135
+ const userId = 'user-123'
136
+ const program = {
137
+ unread: await fetchUnreadMessages(userId),
138
+ emptyTrash: await emptyTrash(userId),
139
+ archived: await archiveMessages(selectMessages({ old: true }))
140
+ }
141
+
142
+ const result = await send(program)
143
+ ```
144
+
145
+ This operation is done in a single round-trip. The result is an object with the defined properties and returned values.
146
+
147
+ ## TypeScript
148
+
149
+ There is a good use-case to write a module in TypeScript.
150
+
151
+ 1. Obviously the module would have the benefits that TypeScript offers when
152
+ coding.
153
+ 2. You can use tools like [typedoc][typedoc] to generate docs from your types to
154
+ share with consumers of your API.
155
+ 3. You can use the types of the module to coerce your client to adopt the
156
+ module's type.
157
+
158
+ Let's say we have this module which we use on the server.
159
+
160
+ ```bash
161
+ cat ./example/typescript/math.ts
162
+ ```
163
+ ```ts
164
+ export const add = (a: number, b: number) => a + b
165
+ export const square = (a: number) => a * a
166
+ ```
167
+
168
+ We want to use this module on the client. We create a client version of that module and coerce the types to match those of the server.
169
+
170
+ ```bash
171
+ cat ./example/typescript/math.client.ts
172
+ ```
173
+ ```ts
174
+ import module from 'sendscript/module.mjs'
175
+ import type * as mathTypes from './math.ts'
176
+
177
+ const math = module([
178
+ 'add',
179
+ 'square'
180
+ ]) as typeof mathTypes
181
+
182
+ export default math
183
+ ```
184
+
185
+ We now use the client version of this module.
186
+
187
+ ```bash
188
+ cat ./example/typescript/client.ts
189
+ ```
190
+ ```ts
191
+ import stringify from 'sendscript/stringify.mjs'
192
+
193
+ async function send<T>(program: T): Promise<T>{
194
+ return (await fetch('/api', {
195
+ method: 'POST',
196
+ body: stringify(program)
197
+ })).json()
198
+ }
199
+
200
+ import math from './math.client.ts'
201
+
202
+ const { add, square } = math
203
+
204
+ send(square(add(1, 2)))
205
+ ```
206
+
207
+ We'll also generate the docs for this module.
@@ -0,0 +1,25 @@
1
+ [**sendscript**](../README.md)
2
+
3
+ ***
4
+
5
+ [sendscript](../globals.md) / add
6
+
7
+ # Function: add()
8
+
9
+ > **add**(`a`, `b`): `number`
10
+
11
+ Defined in: [math.ts:1](https://github.com/bas080/sendscript/blob/6f75ed6a4b4db94217fde41ae50bc6b92a2cffda/example/typescript/math.ts#L1)
12
+
13
+ ## Parameters
14
+
15
+ ### a
16
+
17
+ `number`
18
+
19
+ ### b
20
+
21
+ `number`
22
+
23
+ ## Returns
24
+
25
+ `number`
@@ -0,0 +1,21 @@
1
+ [**sendscript**](../README.md)
2
+
3
+ ***
4
+
5
+ [sendscript](../globals.md) / square
6
+
7
+ # Function: square()
8
+
9
+ > **square**(`a`): `number`
10
+
11
+ Defined in: [math.ts:2](https://github.com/bas080/sendscript/blob/6f75ed6a4b4db94217fde41ae50bc6b92a2cffda/example/typescript/math.ts#L2)
12
+
13
+ ## Parameters
14
+
15
+ ### a
16
+
17
+ `number`
18
+
19
+ ## Returns
20
+
21
+ `number`
@@ -0,0 +1,10 @@
1
+ [**sendscript**](README.md)
2
+
3
+ ***
4
+
5
+ # sendscript
6
+
7
+ ## Functions
8
+
9
+ - [add](functions/add.md)
10
+ - [square](functions/square.md)
@@ -0,0 +1,9 @@
1
+ import module from 'sendscript/module.mjs'
2
+ import type * as mathTypes from './math.ts'
3
+
4
+ const math = module([
5
+ 'add',
6
+ 'square'
7
+ ]) as typeof mathTypes
8
+
9
+ export default math
@@ -0,0 +1,2 @@
1
+ export const add = (a: number, b: number) => a + b
2
+ export const square = (a: number) => a * a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sendscript",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Blur the line between server and client code.",
5
5
  "module": true,
6
6
  "main": "index.mjs",
package/parse.mjs CHANGED
@@ -1,8 +1,6 @@
1
1
  import Debug from './debug.mjs'
2
2
  import isNil from './is-nil.mjs'
3
-
4
- class SendScriptError extends Error {}
5
- class SendScriptReferenceError extends SendScriptError {}
3
+ import { SendScriptReferenceError } from './error.mjs'
6
4
 
7
5
  const debug = Debug.extend('parse')
8
6
 
@@ -14,13 +12,19 @@ export default (env) =>
14
12
  const resolved = {}
15
13
 
16
14
  JSON.parse(program, (key, value) => {
17
- if (Array.isArray(value) && value[0] === 'await') {
18
- awaits.push(((program, awaitId) => async () => {
19
- const value = await JSON.parse(JSON.stringify(program), reviver)
20
- resolved[awaitId] = value
15
+ if (!Array.isArray(value)) return value
16
+
17
+ const [operator, ...rest] = value
21
18
 
22
- debug('awaits', awaits)
23
- })(value[1], value[2]))
19
+ if (operator === 'await') {
20
+ const [program, awaitId] = rest
21
+
22
+ awaits.push(((program, awaitId) => async () => {
23
+ resolved[awaitId] = await JSON.parse(
24
+ JSON.stringify(program),
25
+ reviver
26
+ )
27
+ })(program, awaitId))
24
28
  }
25
29
 
26
30
  return value