sendscript 0.0.0

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/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2023
4
+
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the “Software”), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,223 @@
1
+ # SendScript
2
+
3
+ Write JS code that you can run on servers, browsers or other clients.
4
+
5
+ [![NPM](https://img.shields.io/npm/v/sendscript?color=blue&style=flat-square)](https://www.npmjs.com/package/sendscript)
6
+ [![Standard Code Style](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](https://standardjs.com)
7
+ [![License](https://img.shields.io/npm/l/sendscript?color=brightgreen&style=flat-square)](./LICENSE)
8
+
9
+ > SendScript leaves it up to you to choose HTTP, web-sockets or any other method of
10
+ > communication between servers and clients that best fits your needs.
11
+
12
+ ## Socket example
13
+
14
+ For this example we'll use [socket.io][socket.io].
15
+
16
+ ```bash
17
+ npm install --no-save socket.io socket.io-client
18
+ ```
19
+
20
+ > We use the `--no-save` option because it's only for demonstration purposes.
21
+
22
+ ### Module
23
+
24
+ We write a simple module that only has an add function
25
+
26
+ ```js
27
+ // ./example/math.mjs
28
+
29
+ export const add = (a, b) => a + b
30
+ ```
31
+
32
+ ### Server
33
+
34
+ Here a simple module for running a socket.io server that runs SendScript programs.
35
+
36
+ ```js
37
+ // ./example/server.socket.io.mjs
38
+
39
+ import { Server } from 'socket.io'
40
+ import exec from '../exec.mjs'
41
+ import * as math from './math.mjs'
42
+
43
+ const server = new Server()
44
+ const port = process.env.PORT || 3000
45
+
46
+ server.on('connection', (socket) => {
47
+ socket.on('message', async (program, callback) => {
48
+ try {
49
+ const result = await exec(math, program)
50
+ callback(null, result) // Pass null as the first argument to indicate success
51
+ } catch (error) {
52
+ callback(error) // Pass the error to the callback
53
+ }
54
+ })
55
+ })
56
+
57
+ server.listen(port)
58
+ process.title = 'sendscript'
59
+ ```
60
+
61
+ ### Client
62
+
63
+ Now for a client that sends a program to the server.
64
+
65
+ ```js
66
+ // ./example/client.socket.io.mjs
67
+
68
+ import socketClient from 'socket.io-client'
69
+ import dsl from '../dsl.mjs'
70
+
71
+ const port = process.env.PORT || 3000
72
+ const client = socketClient(`http://localhost:${port}`)
73
+
74
+ const exec = program => {
75
+ return new Promise((resolve, reject) => {
76
+ client.emit('message', program, (error, result) => {
77
+ error
78
+ ? reject(error)
79
+ : resolve(result)
80
+ })
81
+ })
82
+ }
83
+
84
+ const { add } = dsl(['add'], exec)
85
+
86
+ console.log(
87
+ await add(1, add(add(2, 3), 4))
88
+ )
89
+
90
+ process.exit(0)
91
+ ```
92
+
93
+ Now we run this server and a client script.
94
+
95
+ ```bash
96
+ # Run the server
97
+ node ./example/server.socket.io.mjs&
98
+
99
+ # Run the client example
100
+ node ./example/client.socket.io.mjs
101
+
102
+ pkill sendscript
103
+ ```
104
+ ```
105
+ 10
106
+ ```
107
+
108
+ ## Reference
109
+
110
+ SendScript is essentially a way to serialize a program to then send over the wire
111
+ and execute it somewhere else.
112
+
113
+ We only have two modules. One that helps you write programs that can be sent
114
+ over the wire and another for running that program.
115
+
116
+ ### `sendscript/dsl.mjs`
117
+
118
+ The dsl module exports a function that takes two arguments.
119
+
120
+ 1. The schema, which represents the values that are available.
121
+ 2. The function that will be called with the serializable version of the
122
+ program.
123
+
124
+ It returns an object that contains functions which are defined in the schema.
125
+ These functions are a JavaScript DSL for writing programs that can be sent to
126
+ a server.
127
+
128
+ ```js
129
+ import dsl from './dsl.mjs'
130
+
131
+ const { add, subtract } = dsl(
132
+ ['add', 'subtract'],
133
+ serializableProgram => sendSomewhereToBeExecuted(serializableProgram)
134
+ )
135
+
136
+ await add(1, 2) // => 3
137
+ await subtract(1, 2) // => -1
138
+ await add(1, subtract(2, 3)) // => 0
139
+ ```
140
+
141
+ The add and subtract functions are thennable. The execute function is called as
142
+ soon as await or `.then` is used.
143
+
144
+ > Notice that you do not have to await the subtract call. You only need to
145
+ > await when you want to execute the program.
146
+
147
+ This DSL is composable and wrappable.
148
+
149
+ ### `sendscript/exec.mjs`
150
+
151
+ The exec function takes an environment object and any valid SendScript program.
152
+
153
+ ```js
154
+ import exec from './exec.mjs'
155
+
156
+ exec({
157
+ add: (a, b) => a + b,
158
+ subtract: (a, b) => a - b
159
+ }, ['add', 1, [subtract, 1, 2]])
160
+ ```
161
+
162
+ The array you see here is the LISP that SendScript uses to represent programs.
163
+
164
+ You could use SendScript without knowing the details of how the LISP works. It is an
165
+ implementation detail and might change over time.
166
+
167
+ ## Tests
168
+
169
+ Tests with 100% code coverage.
170
+
171
+ ```bash
172
+ npx c8 --100 npm t -- -R classic
173
+ ```
174
+ ```
175
+
176
+ > sendscript@0.0.0 test
177
+ > tap *.test.mjs --no-cov -R classic
178
+
179
+ curry.test.mjs ........................................ 4/4
180
+ dsl.test.mjs .......................................... 5/5
181
+ exec.test.mjs ....................................... 15/15
182
+ total ............................................... 24/24
183
+
184
+ 24 passing (400.194ms)
185
+
186
+ ok
187
+ -----------|---------|----------|---------|---------|-------------------
188
+ File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
189
+ -----------|---------|----------|---------|---------|-------------------
190
+ All files | 100 | 100 | 100 | 100 |
191
+ curry.mjs | 100 | 100 | 100 | 100 |
192
+ debug.mjs | 100 | 100 | 100 | 100 |
193
+ dsl.mjs | 100 | 100 | 100 | 100 |
194
+ error.mjs | 100 | 100 | 100 | 100 |
195
+ exec.mjs | 100 | 100 | 100 | 100 |
196
+ -----------|---------|----------|---------|---------|-------------------
197
+ ```
198
+
199
+ ## Formatting
200
+
201
+ Standard because no config.
202
+
203
+ ```bash
204
+ npx standard
205
+ ```
206
+
207
+ ## Changelog
208
+
209
+ The [changelog][changelog] is generated using the useful
210
+ [auto-changelog][auto-changelog] project.
211
+
212
+ ```bash
213
+ npx auto-changelog -p
214
+ ```
215
+
216
+ ## License
217
+
218
+ See the [LICENSE.txt][license] file for details.
219
+
220
+ [license]:./LICENSE.txt
221
+ [socket.io]:https://socket.io/
222
+ [changelog]:./CHANGELOG.md
223
+ [auto-changelog]:https://www.npmjs.com/package/auto-changelog
package/README.mz ADDED
@@ -0,0 +1,196 @@
1
+ # SendScript
2
+
3
+ Write JS code that you can run on servers, browsers or other clients.
4
+
5
+ [![NPM](https://img.shields.io/npm/v/sendscript?color=blue&style=flat-square)](https://www.npmjs.com/package/sendscript)
6
+ [![Standard Code Style](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](https://standardjs.com)
7
+ [![License](https://img.shields.io/npm/l/sendscript?color=brightgreen&style=flat-square)](./LICENSE)
8
+
9
+ > SendScript leaves it up to you to choose HTTP, web-sockets or any other method of
10
+ > communication between servers and clients that best fits your needs.
11
+
12
+ ## Socket example
13
+
14
+ For this example we'll use [socket.io][socket.io].
15
+
16
+ ```bash bash > /dev/null
17
+ npm install --no-save socket.io socket.io-client
18
+ ```
19
+
20
+ > We use the `--no-save` option because it's only for demonstration purposes.
21
+
22
+ ### Module
23
+
24
+ We write a simple module that only has an add function
25
+
26
+ ```js cat - > ./example/math.mjs
27
+ // ./example/math.mjs
28
+
29
+ export const add = (a, b) => a + b
30
+ ```
31
+
32
+ ### Server
33
+
34
+ Here a simple module for running a socket.io server that runs SendScript programs.
35
+
36
+ ```js cat - > ./example/server.socket.io.mjs
37
+ // ./example/server.socket.io.mjs
38
+
39
+ import { Server } from 'socket.io'
40
+ import exec from '../exec.mjs'
41
+ import * as math from './math.mjs'
42
+
43
+ const server = new Server()
44
+ const port = process.env.PORT || 3000
45
+
46
+ server.on('connection', (socket) => {
47
+ socket.on('message', async (program, callback) => {
48
+ try {
49
+ const result = await exec(math, program)
50
+ callback(null, result) // Pass null as the first argument to indicate success
51
+ } catch (error) {
52
+ callback(error) // Pass the error to the callback
53
+ }
54
+ })
55
+ })
56
+
57
+ server.listen(port)
58
+ process.title = 'sendscript'
59
+ ```
60
+
61
+ ### Client
62
+
63
+ Now for a client that sends a program to the server.
64
+
65
+ ```js cat - > ./example/client.socket.io.mjs
66
+ // ./example/client.socket.io.mjs
67
+
68
+ import socketClient from 'socket.io-client'
69
+ import dsl from '../dsl.mjs'
70
+
71
+ const port = process.env.PORT || 3000
72
+ const client = socketClient(`http://localhost:${port}`)
73
+
74
+ const exec = program => {
75
+ return new Promise((resolve, reject) => {
76
+ client.emit('message', program, (error, result) => {
77
+ error
78
+ ? reject(error)
79
+ : resolve(result)
80
+ })
81
+ })
82
+ }
83
+
84
+ const { add } = dsl(['add'], exec)
85
+
86
+ console.log(
87
+ await add(1, add(add(2, 3), 4))
88
+ )
89
+
90
+ process.exit(0)
91
+ ```
92
+
93
+ Now we run this server and a client script.
94
+
95
+ ```bash bash
96
+ # Run the server
97
+ node ./example/server.socket.io.mjs&
98
+
99
+ # Run the client example
100
+ node ./example/client.socket.io.mjs
101
+
102
+ pkill sendscript
103
+ ```
104
+
105
+ ## Reference
106
+
107
+ SendScript is essentially a way to serialize a program to then send over the wire
108
+ and execute it somewhere else.
109
+
110
+ We only have two modules. One that helps you write programs that can be sent
111
+ over the wire and another for running that program.
112
+
113
+ ### `sendscript/dsl.mjs`
114
+
115
+ The dsl module exports a function that takes two arguments.
116
+
117
+ 1. The schema, which represents the values that are available.
118
+ 2. The function that will be called with the serializable version of the
119
+ program.
120
+
121
+ It returns an object that contains functions which are defined in the schema.
122
+ These functions are a JavaScript DSL for writing programs that can be sent to
123
+ a server.
124
+
125
+ ```js
126
+ import dsl from './dsl.mjs'
127
+
128
+ const { add, subtract } = dsl(
129
+ ['add', 'subtract'],
130
+ serializableProgram => sendSomewhereToBeExecuted(serializableProgram)
131
+ )
132
+
133
+ await add(1, 2) // => 3
134
+ await subtract(1, 2) // => -1
135
+ await add(1, subtract(2, 3)) // => 0
136
+ ```
137
+
138
+ The add and subtract functions are thennable. The execute function is called as
139
+ soon as await or `.then` is used.
140
+
141
+ > Notice that you do not have to await the subtract call. You only need to
142
+ > await when you want to execute the program.
143
+
144
+ This DSL is composable and wrappable.
145
+
146
+ ### `sendscript/exec.mjs`
147
+
148
+ The exec function takes an environment object and any valid SendScript program.
149
+
150
+ ```js
151
+ import exec from './exec.mjs'
152
+
153
+ exec({
154
+ add: (a, b) => a + b,
155
+ subtract: (a, b) => a - b
156
+ }, ['add', 1, [subtract, 1, 2]])
157
+ ```
158
+
159
+ The array you see here is the LISP that SendScript uses to represent programs.
160
+
161
+ You could use SendScript without knowing the details of how the LISP works. It is an
162
+ implementation detail and might change over time.
163
+
164
+ ## Tests
165
+
166
+ Tests with 100% code coverage.
167
+
168
+ ```bash bash
169
+ npx c8 --100 npm t -- -R classic
170
+ ```
171
+
172
+ ## Formatting
173
+
174
+ Standard because no config.
175
+
176
+ ```bash bash
177
+ npx standard
178
+ ```
179
+
180
+ ## Changelog
181
+
182
+ The [changelog][changelog] is generated using the useful
183
+ [auto-changelog][auto-changelog] project.
184
+
185
+ ```bash bash > /dev/null
186
+ npx auto-changelog -p
187
+ ```
188
+
189
+ ## License
190
+
191
+ See the [LICENSE.txt][license] file for details.
192
+
193
+ [license]:./LICENSE.txt
194
+ [socket.io]:https://socket.io/
195
+ [changelog]:./CHANGELOG.md
196
+ [auto-changelog]:https://www.npmjs.com/package/auto-changelog
package/curry.mjs ADDED
@@ -0,0 +1,11 @@
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 ADDED
@@ -0,0 +1,35 @@
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
+ })
package/debug.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import * as debug from 'debug'
2
+
3
+ const { default: Debug } = debug
4
+
5
+ export default Debug('sendscript')
package/dsl.mjs ADDED
@@ -0,0 +1,40 @@
1
+ const symbol = Symbol('dsl')
2
+
3
+ // Only await values that are not part of the dsl.
4
+ const promiseAllNonDSL = async values => values.reduce(async (_acc, value) => {
5
+ const acc = await _acc
6
+
7
+ if (value[symbol] === symbol) {
8
+ acc.push(value)
9
+ } else {
10
+ acc.push(await value)
11
+ }
12
+
13
+ return acc
14
+ }, [])
15
+
16
+ export default function dsl (schema, call) {
17
+ return schema.reduce((api, name) => {
18
+ const then = (program) => (resolve, reject) =>
19
+ Promise.resolve(call(program)).then(resolve, reject)
20
+
21
+ const fn = (...args) => {
22
+ // Only await non dsl values
23
+ const v = [fn, ...args]
24
+
25
+ v[symbol] = symbol
26
+ v.then = async (...thenArgs) => {
27
+ return then(await promiseAllNonDSL(v))(...thenArgs)
28
+ }
29
+ return v
30
+ }
31
+
32
+ fn.toJSON = () => ['ref', name]
33
+ fn.then = then(fn)
34
+ fn[symbol] = symbol
35
+
36
+ api[name] = fn
37
+
38
+ return api
39
+ }, {})
40
+ }
package/dsl.test.mjs ADDED
@@ -0,0 +1,31 @@
1
+ import { test } from 'tap'
2
+ import dsl from './dsl.mjs'
3
+
4
+ test('return a constant value', async t => {
5
+ t.plan(1)
6
+
7
+ const { constant } = dsl(['constant'], v => {
8
+ t.equal(JSON.stringify(v), '["ref","constant"]')
9
+ })
10
+
11
+ await constant
12
+ })
13
+
14
+ test('use function calls as arguments', async t => {
15
+ t.plan(4)
16
+
17
+ const { add } = dsl(['add'], b => {
18
+ t.equal(JSON.stringify(awaited), JSON.stringify(b))
19
+ return 'done'
20
+ })
21
+
22
+ const program = add(add(add(1, 2), 3), 4)
23
+ const awaited = program
24
+ const thenned = program
25
+
26
+ t.equal(await awaited, 'done')
27
+
28
+ await thenned.then(result => {
29
+ t.equal(result, 'done')
30
+ })
31
+ })
package/error.mjs ADDED
@@ -0,0 +1,9 @@
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
+ }
@@ -0,0 +1,25 @@
1
+ // ./example/client.socket.io.mjs
2
+
3
+ import socketClient from 'socket.io-client'
4
+ import dsl from '../dsl.mjs'
5
+
6
+ const port = process.env.PORT || 3000
7
+ const client = socketClient(`http://localhost:${port}`)
8
+
9
+ const exec = program => {
10
+ return new Promise((resolve, reject) => {
11
+ client.emit('message', program, (error, result) => {
12
+ error
13
+ ? reject(error)
14
+ : resolve(result)
15
+ })
16
+ })
17
+ }
18
+
19
+ const { add } = dsl(['add'], exec)
20
+
21
+ console.log(
22
+ await add(1, add(add(2, 3), 4))
23
+ )
24
+
25
+ process.exit(0)
@@ -0,0 +1,3 @@
1
+ // ./example/math.mjs
2
+
3
+ export const add = (a, b) => a + b
@@ -0,0 +1,22 @@
1
+ // ./example/server.socket.io.mjs
2
+
3
+ import { Server } from 'socket.io'
4
+ import exec from '../exec.mjs'
5
+ import * as math from './math.mjs'
6
+
7
+ const server = new Server()
8
+ const port = process.env.PORT || 3000
9
+
10
+ server.on('connection', (socket) => {
11
+ socket.on('message', async (program, callback) => {
12
+ try {
13
+ const result = await exec(math, program)
14
+ callback(null, result) // Pass null as the first argument to indicate success
15
+ } catch (error) {
16
+ callback(error) // Pass the error to the callback
17
+ }
18
+ })
19
+ })
20
+
21
+ server.listen(port)
22
+ process.title = 'sendscript'
package/exec.mjs ADDED
@@ -0,0 +1,69 @@
1
+ import _debug from './debug.mjs'
2
+ import { BlendExpressionError } from './error.mjs'
3
+ import curry from './curry.mjs'
4
+
5
+ const debug = _debug.extend('lisp')
6
+ const debugError = debug.extend('error')
7
+ const isFunction = x => typeof x === 'function'
8
+ const castFunction = x => isFunction(x) ? x : () => x
9
+
10
+ const exec = curry(async (env, expression) => {
11
+ debug('exec', expression)
12
+
13
+ if (!Array.isArray(expression)) {
14
+ return expression
15
+ }
16
+
17
+ const [operator, ...args] = expression
18
+
19
+ // Yey, resolved and all and ready to call.
20
+ if (isFunction(operator)) {
21
+ try {
22
+ return await operator.call(env, ...(await Promise.all(args.map(exec(env)))))
23
+ } catch (error) {
24
+ debugError(error)
25
+ throw error
26
+ }
27
+ }
28
+
29
+ // Use JSON value when toJSON is implemented.
30
+ // NECESSARY? if (operator?.toJSON) { operator = operator.toJSON() }
31
+
32
+ if (operator === 'fn') {
33
+ const [body] = args
34
+
35
+ return (fnEnv) => exec(Object.assign(Object.create(env), fnEnv), body)
36
+ }
37
+
38
+ if (operator === 'let') {
39
+ const letEnv = Object.create(env)
40
+ const [letBindings, letBody] = args
41
+
42
+ await Promise.all(letBindings.map(async ([name, letExpr]) => {
43
+ letEnv[name] = await exec(letEnv, letExpr)
44
+ }))
45
+
46
+ return exec(letEnv, letBody)
47
+ }
48
+
49
+ if (operator === 'ref') {
50
+ const [name] = args
51
+ return env[name]
52
+ }
53
+
54
+ if (Array.isArray(operator)) {
55
+ return exec(env, [await exec(env, operator), ...args])
56
+ }
57
+
58
+ // Throw error if operator is not in env.
59
+ if (!(operator in env)) {
60
+ const error = new BlendExpressionError(`Unknown expression: ${operator}`)
61
+ debugError(error)
62
+ throw error
63
+ }
64
+
65
+ return exec(env, [castFunction(env[operator]), ...args])
66
+ })
67
+
68
+ export { exec }
69
+ export default exec
package/exec.test.mjs ADDED
@@ -0,0 +1,110 @@
1
+ import { test } from 'tap'
2
+ import { exec } from './exec.mjs'
3
+
4
+ test('should evaluate basic expressions correctly', (t) => {
5
+ t.plan(4)
6
+
7
+ exec({ '+': (a, b) => a + b }, ['+', 1, 2])
8
+ .then((result) => t.equal(result, 3, '1 + 2 = 3'))
9
+
10
+ exec({ '-': (a, b) => a - b }, ['-', 5, 3])
11
+ .then((result) => t.equal(result, 2, '5 - 3 = 2'))
12
+
13
+ exec({ '*': (a, b) => a * b }, ['*', 4, 3])
14
+ .then((result) => t.equal(result, 12, '4 * 3 = 12'))
15
+
16
+ exec({ '/': (a, b) => a / b }, ['/', 10, 2])
17
+ .then((result) => t.equal(result, 5, '10 / 2 = 5'))
18
+ })
19
+
20
+ test('should be able to call a partially applied function', async t => {
21
+ const env = {
22
+ '+': a => b => a + b
23
+ }
24
+
25
+ t.equal(typeof (await exec(env, ['+', 1])), 'function')
26
+ t.equal(await exec(env, [['+', 1], 2]), 3)
27
+ t.end()
28
+ })
29
+
30
+ test('should handle let expressions', (t) => {
31
+ t.plan(1)
32
+
33
+ exec({ '+': (a, b) => a + b }, ['let', [['x', 2], ['y', 3]], ['+', ['x'], ['y']]])
34
+ .then((result) => t.equal(result, 5, 'x + y = 5'))
35
+ })
36
+
37
+ test('should handle fn expressions', (t) => {
38
+ t.plan(2)
39
+
40
+ exec({}, ['fn', ['+', 1, 'x']])
41
+ .then((result) => t.equal(typeof result, 'function', 'should return a function'))
42
+
43
+ exec({ '+': (a, b) => a + b }, ['fn', ['+', 1, ['x']]])
44
+ .then(async (result) => {
45
+ t.equal(await result({ x: 2 }), 3, '1 + 2 = 3')
46
+ })
47
+ })
48
+
49
+ test('should throw an error for unknown expressions', (t) => {
50
+ t.plan(1)
51
+
52
+ exec({ '+': (a, b) => a + b }, ['-', 1, 2])
53
+ .catch((err) => t.equal(err.message, 'Unknown expression: -', 'should throw an error for unknown expressions'))
54
+ })
55
+
56
+ test('should evaluate nested expressions correctly', (t) => {
57
+ t.plan(1)
58
+
59
+ const env = {
60
+ '+': (a, b) => a + b,
61
+ '*': (a, b) => a * b
62
+ }
63
+
64
+ exec(env, ['*', 2, ['+', 1, 2]])
65
+ .then((result) => t.equal(result, 6, '2 * (1 + 2) = 6'))
66
+ })
67
+
68
+ test('should concatenate two arrays', async t => {
69
+ t.plan(1)
70
+
71
+ const env = {
72
+ array: (...args) => args,
73
+ concat: (a, b) => a.concat(b)
74
+ }
75
+ const expression = ['concat', ['array', 1, 2, 3], ['array', 4, 5, 6]]
76
+ const result = await exec(env, expression)
77
+
78
+ t.same(result, [1, 2, 3, 4, 5, 6])
79
+ })
80
+
81
+ test('should fetch the reference to the item on the env', async t => {
82
+ const value = 'yey'
83
+
84
+ const env = {
85
+ thing: () => value
86
+ }
87
+
88
+ const expression = ['ref', 'thing']
89
+ const result = await exec(env, expression)
90
+
91
+ t.same(result(), value)
92
+
93
+ const result2 = await exec(env, ['thing'])
94
+ t.same(result2, value)
95
+
96
+ t.end()
97
+ })
98
+
99
+ test('should reject with the error of the called function', async t => {
100
+ const error = new Error('BOOM')
101
+ const env = {
102
+ throws () {
103
+ throw error
104
+ }
105
+ }
106
+
107
+ await t.rejects(exec(env, ['throws']))
108
+
109
+ t.end()
110
+ })
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "sendscript",
3
+ "version": "0.0.0",
4
+ "description": "Blur the line between server and client code.",
5
+ "module": true,
6
+ "main": "index.mjs",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git@github.com:bas080/sendscript.git"
10
+ },
11
+ "scripts": {
12
+ "test": "tap *.test.mjs --no-cov",
13
+ "version": "npm run docs && git add *.md",
14
+ "docs": "markatzea README.mz | tee README.md"
15
+ },
16
+ "author": "Bas Huis",
17
+ "license": "MIT",
18
+ "devDependencies": {
19
+ "tap": "^16.3.4"
20
+ },
21
+ "dependencies": {
22
+ "debug": "^4.3.4"
23
+ }
24
+ }