Write JS code that you can run on servers, browsers or other clients.
SendScript leaves it up to you to choose HTTP, web-sockets or any other method of communication between servers and clients that best fits your needs.
For this example we'll use socket.io.
npm install --no-save socket.io socket.io-client
We use the
--no-save
option because it's only for demonstration purposes.
We write a simple module.
// ./example/math.mjs
export const add = (a, b) => a + b
export const square = a => a * a
Here a socket.io server that runs SendScript programs.
// ./example/server.socket.io.mjs
import { Server } from 'socket.io'
import exec from '../exec.mjs'
import * as math from './math.mjs'
const server = new Server()
const port = process.env.PORT || 3000
server.on('connection', (socket) => {
socket.on('message', async (program, callback) => {
try {
const result = await exec(math, program)
callback(null, result) // Pass null as the first argument to indicate success
} catch (error) {
callback(error) // Pass the error to the callback
}
})
})
server.listen(port)
process.title = 'sendscript'
Now for a client that sends a program to the server.
// ./example/client.socket.io.mjs
import socketClient from 'socket.io-client'
import api from '../api.mjs'
const port = process.env.PORT || 3000
const client = socketClient(`http://localhost:${port}`)
const exec = program => {
return new Promise((resolve, reject) => {
client.emit('message', program, (error, result) => {
error
? reject(error)
: resolve(result)
})
})
}
const { add, square } = api(['add', 'square'], exec)
console.log(
await square(add(1, add(add(2, 3), 4)))
)
process.exit(0)
Now we run this server and a client script.
# Run the server
node ./example/server.socket.io.mjs&
# Run the client example
node ./example/client.socket.io.mjs
pkill sendscript
100
SendScript is essentially a way to serialize a program to then send over the wire and execute it somewhere else.
We only have two modules. One that helps you write programs that can be sent over the wire and another for running that program.
The api module exports a function that takes two arguments.
- The schema, which represents the values that are available.
- The function that will be called with the serializable version of the program.
It returns an object that contains functions which are defined in the schema. These functions are a JavaScript API for writing programs that can be sent to a server.
import api from './api.mjs'
const { add, subtract } = api(
['add', 'subtract'],
serializableProgram => sendSomewhereToBeExecuted(serializableProgram)
)
await add(1, 2) // => 3
await subtract(1, 2) // => -1
await add(1, subtract(2, 3)) // => 0
The add and subtract functions are thennable. The execute function is called as
soon as await or .then
is used.
Notice that you do not have to await the subtract call. You only need to await when you want to execute the program.
This API is composable and wrappable.
The exec function takes an environment object and any valid SendScript program.
import exec from './exec.mjs'
exec({
add: (a, b) => a + b,
subtract: (a, b) => a - b
}, ['add', 1, [subtract, 1, 2]])
The array you see here is the LISP that SendScript uses to represent programs.
You could use SendScript without knowing the details of how the LISP works. It is an implementation detail and might change over time.
There is a good use-case to write an environment module in TypeScript.
- Obviously the module would have the benefits that TypeScript offers when coding.
- You can use tools like typedoc to generate docs from your types to share with consumers of your API.
- You can use the types of the module to coerce your client to adopt the modules type.
# Create pretty docs for your module.
npx typedoc my-module.ts
Now we can use the my-module.ts
file for the client API.
import type * as MyModule from './my-module'
import sendScriptApi from 'sendscript/api.mjs'
export default sendScriptApi([
fnOne,
fnTwo,
], /* perform websocket request */) as typeof MyModule
Note
Although type coercion on the client side can improve the development experience, it does not represent the actual type. Values are likely subject to serialization and deserialization, particularly when interfacing with JSON formats.
Tests with 100% code coverage.
npm t -- -R silent
npm t -- report text-summary
> [email protected] test
> tap -R silent
> [email protected] test
> tap report text-summary
=============================== Coverage summary ===============================
Statements : 100% ( 110/110 )
Branches : 100% ( 32/32 )
Functions : 100% ( 10/10 )
Lines : 100% ( 110/110 )
================================================================================
Standard because no config.
npx standard
The changelog is generated using the useful auto-changelog project.
npx auto-changelog -p
Check if packages are up to date on release.
npm outdated && echo 'No outdated packages found'
No outdated packages found
See the LICENSE.txt file for details.