Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"proseWrap": "always",
"printWidth": 80,
"semi": false,
"singleQuote": true
}
28 changes: 25 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
# Contributing


For the README.mz examples to work we need sendscript to be linked.

```bash bash > /dev/null
set -e

npm ci
npm link
npm link sendscript
cd ./example
npm ci
npm link sendscript
```

Check if packages are up to date on release.

```bash bash
npm outdated && echo 'No outdated packages found'
```

Check if no vulnerable dependencies

```bash bash
npm audit
```

Check if code follows standard formatting.

```bash bash
npx standard
```

Check if markdown is correctly formatted.

```bash bash
npx prettier --check --parser markdown ./README.mz ./CONTRIBUTING.md
```

Generate the README from the mz file.
Expand Down
151 changes: 75 additions & 76 deletions README.mz
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,25 @@ Write JS code that you can run on servers, browsers or other clients.

## Introduction

There has been interest in improving APIs by allowing aggregations in a
single request. Examples include
There has been interest in improving APIs by allowing aggregations in a single
request. Examples include

- [JSON-RPC](https://json-rpc.dev/) which allows you to do multiple requests
but it does not allow you to compose the return value of one endpoint to be the
- [JSON-RPC](https://json-rpc.dev/) which allows you to do multiple requests but
it does not allow you to compose the return value of one endpoint to be the
input/arguments of another.

- [GraphQL](https://graphql.org/) is very cool but also introduces a new languages and the
tooling that is required to wield it.
- [GraphQL](https://graphql.org/) is very cool but also introduces a new
languages and the tooling that is required to wield it.

What SendScript attempts is to allow for very expressive queries and mutations to be performed
that read and write like ordinary JS. That means that the queries and complete programs
that are sent to the server from a client can also just run on the server as is. The only
limitation being the serialization which by default is limited by JSON and could be extended by
using more advanced (de)serialization libraries.
What SendScript attempts is to allow for very expressive queries and mutations
to be performed that read and write like ordinary JS. That means that the
queries and complete programs that are sent to the server from a client can also
just run on the server as is. The only limitation being the serialization which
by default is limited by JSON and could be extended by using more advanced
(de)serialization libraries.

SendScript produces an intermediate JSON representation of the program. Let's see what that looks like.
SendScript produces an intermediate JSON representation of the program. Let's
see what that looks like.

```js|json node --input-type=module | tee /tmp/sendscript.json
import Stringify from 'sendscript/stringify.mjs'
Expand Down Expand Up @@ -60,22 +62,22 @@ console.log(parse(program))
SendScript does more than a simple function call. It supports function
composition and even await.

This package is nothing more than the absolute core of sendscript. It
includes:
This package is nothing more than the absolute core of sendscript. It includes:

- The `references` function to create stubs to write the programs.
- `stringify` which takes the program and returns a JSON string.
- `parse` which takes the `stringify` JSON string and a real module and returns the result.
- `parse` which takes the `stringify` JSON string and a real module and returns
the result.

The naming could use more love and there are many things to solve either in the core or around it.
Things like supporting more complex (de)serializers, errors and maybe mixing client functions with
sendscript programs. Contact me if I have piqued your interest.
The naming could use more love and there are many things to solve either in the
core or around it. Things like supporting more complex (de)serializers, errors
and maybe mixing client functions with sendscript programs. Contact me if I have
piqued your interest.

---

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.
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.

## Socket example

Expand All @@ -89,7 +91,7 @@ We write a simple module.
// ./example/math.mjs

export const add = (a, b) => a + b
export const square = a => a * a
export const square = (a) => a * a
```

### Server
Expand Down Expand Up @@ -141,12 +143,10 @@ const stringify = Stringify()
const port = process.env.PORT || 3000
const client = socketClient(`http://localhost:${port}`)

const send = program => {
const send = (program) => {
return new Promise((resolve, reject) => {
client.emit('message', stringify(program), (error, result) => {
error
? reject(error)
: resolve(result)
error ? reject(error) : resolve(result)
})
})
}
Expand Down Expand Up @@ -179,9 +179,11 @@ pkill sendscript

## Repl

Sendscript ships with a barebones (no-dependencies) node-repl script. One can run it by simply typing `sendscript` in their console.
Sendscript ships with a barebones (no-dependencies) node-repl script. One can
run it by simply typing `sendscript` in their console.

> Use the `DEBUG='*'` to enable all logs or `DEBUG='sendscript:*'` for printingonly sendscript logs.
> Use the `DEBUG='*'` to enable all logs or `DEBUG='sendscript:*'` for
> printingonly sendscript logs.

## Promises

Expand All @@ -193,27 +195,33 @@ Supported since vs `v2.3`.
const getOrCreatePost = send(createPost(title).catch(createPost(title)))
```

You will likely need to define better helpers that makes it safer to handle rejections and work with promises. It is however sensible to have
this basic behavior for the sendscript DSL and parser.
You will likely need to define better helpers that makes it safer to handle
rejections and work with promises. It is however sensible to have this basic
behavior for the sendscript DSL and parser.

### await

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.
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.

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.
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.

```js
const userId = 'user-123'
const program = {
unread: await fetchUnreadMessages(userId),
emptyTrash: await emptyTrash(userId),
archived: await archiveMessages(selectMessages({ old: true }))
archived: await archiveMessages(selectMessages({ old: true })),
}

const result = await send(program)
```

This operation is done in a single round-trip. The result is an object with the defined properties and returned values.
This operation is done in a single round-trip. The result is an object with the
defined properties and returned values.

## TypeScript

Expand Down Expand Up @@ -246,15 +254,16 @@ npx typedoc --plugin typedoc-plugin-markdown --out ./example/typescript/docs ./e

You can see the docs [here](./example/typescript/docs/globals.md)

> [!NOTE]
> Although type coercion on the client side can improve the development
> experience, it does not represent the actual type.
> Values are subject to serialization and deserialization.

> [!NOTE] Although type coercion on the client side can improve the development
> experience, it does not represent the actual type. Values are subject to
> serialization and deserialization.

## Schema and Nested Modules

Sendscript allows you to define your API as a **nested object of functions**, making it easy to organize your DSL into modules and submodules. Each function is instrumented so that when serialized, it produces a structured reference that can be safely sent and executed elsewhere.
Sendscript allows you to define your API as a **nested object of functions**,
making it easy to organize your DSL into modules and submodules. Each function
is instrumented so that when serialized, it produces a structured reference that
can be safely sent and executed elsewhere.

### Defining a Nested Module

Expand Down Expand Up @@ -286,25 +295,21 @@ Functions are referenced via their **path in the module tree**:
```js
const { math, vector } = references(schema)

math.add(
1,
vector.length(
vector.multiply([1,2], 3)
)
)
math.add(1, vector.length(vector.multiply([1, 2], 3)))
```

## Validation (using Zod)

SendScript focuses on program serialization and execution. For runtime input validation, you can use [Zod](https://zod.dev).
SendScript focuses on program serialization and execution. For runtime input
validation, you can use [Zod](https://zod.dev).

### Validating structured input

```js
const userSchema = z.object({
id: z.string().uuid(),
name: z.string(),
roles: z.array(z.string())
roles: z.array(z.string()),
})

export function createUser(user) {
Expand All @@ -322,13 +327,19 @@ export function createUser(user) {

## Leaf Serializer

By default, SendScript uses JSON for serialization, which limits support to primitives and plain objects/arrays. To support richer JavaScript types like `Date`, `RegExp`, `BigInt`, `Map`, `Set`, and `undefined`, you can provide custom serialization functions.
By default, SendScript uses JSON for serialization, which limits support to
primitives and plain objects/arrays. To support richer JavaScript types like
`Date`, `RegExp`, `BigInt`, `Map`, `Set`, and `undefined`, you can provide
custom serialization functions.

The `stringify` function accepts an optional `leafSerializer` parameter, and `parse` accepts an optional `leafDeserializer` parameter. These functions control how non-SendScript values (leaves) are encoded and decoded.
The `stringify` function accepts an optional `leafSerializer` parameter, and
`parse` accepts an optional `leafDeserializer` parameter. These functions
control how non-SendScript values (leaves) are encoded and decoded.

### Example with superjson

Here's how to use [superjson](https://github.com/blitz-js/superjson) to support extended types:
Here's how to use [superjson](https://github.com/blitz-js/superjson) to support
extended types:

```js
import SuperJSON from 'superjson'
Expand Down Expand Up @@ -358,7 +369,10 @@ const program = {
pattern: /foo/gi,
count: BigInt('9007199254740992'),
items: new Set([1, 2, 3]),
mapping: new Map([['a', 1], ['b', 2]])
mapping: new Map([
['a', 1],
['b', 2],
]),
}

// Serialize with custom leaf serializer
Expand All @@ -368,8 +382,8 @@ const json = stringify(processData(program))
const env = {
processData: (data) => ({
success: true,
received: data
})
received: data,
}),
}

// Parse with custom leaf deserializer
Expand All @@ -378,7 +392,8 @@ const parse = Parse(schema, env, leadDeserializer)
const result = parse(json)
```

The leaf wrapper format is `['leaf', serializedPayload]`, making it unambiguous and safe from colliding with SendScript operators.
The leaf wrapper format is `['leaf', serializedPayload]`, making it unambiguous
and safe from colliding with SendScript operators.

## Tests

Expand All @@ -389,14 +404,6 @@ npm t -- -R silent
npm t -- report text-summary
```

## Formatting

Standard because no config.

```bash bash
npx standard
```

## Changelog

The [changelog][changelog] is generated using the useful
Expand All @@ -406,14 +413,6 @@ The [changelog][changelog] is generated using the useful
npx auto-changelog -p
```

## Dependencies

Check if packages are up to date on release.

```bash bash
npm outdated && echo 'No outdated packages found'
```

## License

See the [LICENSE.txt][license] file for details.
Expand All @@ -422,8 +421,8 @@ See the [LICENSE.txt][license] file for details.

- [ ] Support for simple lambdas to compose functions more easily.

[license]:./LICENSE.txt
[socket.io]:https://socket.io/
[changelog]:./CHANGELOG.md
[auto-changelog]:https://www.npmjs.com/package/auto-changelog
[typedoc]:https://github.com/TypeStrong/typedoc
[license]: ./LICENSE.txt
[socket.io]: https://socket.io/
[changelog]: ./CHANGELOG.md
[auto-changelog]: https://www.npmjs.com/package/auto-changelog
[typedoc]: https://github.com/TypeStrong/typedoc