sendscript 0.0.4 → 0.1.1
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 +14 -0
- package/README.md +47 -17
- package/README.mz +39 -0
- package/api.mjs +21 -13
- package/{promise-only.mjs → await-when.mjs} +1 -3
- package/exec.mjs +6 -44
- package/exec.test.mjs +29 -112
- package/package.json +2 -2
- package/schema.json +53 -0
- package/api.test.mjs +0 -31
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
|
+
#### [v0.1.1](https://github.com/bas080/sendscript/compare/v0.1.0...v0.1.1)
|
|
8
|
+
|
|
9
|
+
- Add a json schema for the sendscript program [`2acf884`](https://github.com/bas080/sendscript/commit/2acf8848134e1ae175afc0af9c5f1e93c4039f8f)
|
|
10
|
+
- Document a way of using sendscript with typescript [`2683c28`](https://github.com/bas080/sendscript/commit/2683c2873b8c4833711cdb00c777792c0630954a)
|
|
11
|
+
- Improve naming of promise helper [`e177f39`](https://github.com/bas080/sendscript/commit/e177f39c740bc16864f1ed4fc90a6daa3321751f)
|
|
12
|
+
|
|
13
|
+
#### [v0.1.0](https://github.com/bas080/sendscript/compare/v0.0.4...v0.1.0)
|
|
14
|
+
|
|
15
|
+
> 21 October 2023
|
|
16
|
+
|
|
17
|
+
- Allow nesting arrays, calls and args [`48e5804`](https://github.com/bas080/sendscript/commit/48e5804891db0ee0ed8c274f20f81d49d6609a54)
|
|
18
|
+
|
|
7
19
|
#### [v0.0.4](https://github.com/bas080/sendscript/compare/v0.0.3...v0.0.4)
|
|
8
20
|
|
|
21
|
+
> 6 October 2023
|
|
22
|
+
|
|
9
23
|
- Add keywords to package.json [`e24d77b`](https://github.com/bas080/sendscript/commit/e24d77b14e67399cedc085294fbef3024a060087)
|
|
10
24
|
- Add .tap to gitignore [`d0abbca`](https://github.com/bas080/sendscript/commit/d0abbcaafab8b915449118b3d2926ca7ff8dc133)
|
|
11
25
|
|
package/README.md
CHANGED
|
@@ -16,11 +16,11 @@ Write JS code that you can run on servers, browsers or other clients.
|
|
|
16
16
|
- [Reference](#reference)
|
|
17
17
|
* [`sendscript/api.mjs`](#sendscriptapimjs)
|
|
18
18
|
* [`sendscript/exec.mjs`](#sendscriptexecmjs)
|
|
19
|
+
- [TypeScript](#typescript)
|
|
19
20
|
- [Tests](#tests)
|
|
20
21
|
- [Formatting](#formatting)
|
|
21
22
|
- [Changelog](#changelog)
|
|
22
23
|
- [Dependencies](#dependencies)
|
|
23
|
-
- [License](#license)
|
|
24
24
|
|
|
25
25
|
<!-- tocstop -->
|
|
26
26
|
|
|
@@ -184,6 +184,44 @@ The array you see here is the LISP that SendScript uses to represent programs.
|
|
|
184
184
|
You could use SendScript without knowing the details of how the LISP works. It
|
|
185
185
|
is an implementation detail and might change over time.
|
|
186
186
|
|
|
187
|
+
## TypeScript
|
|
188
|
+
|
|
189
|
+
There is a good use-case to write an environment module in TypeScript.
|
|
190
|
+
|
|
191
|
+
1. Obviously the module would have the benefits that TypeScript offers when
|
|
192
|
+
coding.
|
|
193
|
+
2. You can use tools like [typedoc][typedoc] to generate docs from your types to
|
|
194
|
+
share with consumers of your API.
|
|
195
|
+
3. You can generate a .d.ts with `tsc --declaration` and use it to coerce your
|
|
196
|
+
client to adopt the modules type.
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# Create pretty docs for your module.
|
|
200
|
+
npx typedoc my-module.ts
|
|
201
|
+
|
|
202
|
+
# Create the .d.ts file for your module.
|
|
203
|
+
tsc --declaration
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Now we can use the `my-module.d.ts` file for the client API.
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
import type * as MyModule from './my-module.d.ts'
|
|
210
|
+
|
|
211
|
+
import sendScriptApi from 'sendscript/api.mjs'
|
|
212
|
+
|
|
213
|
+
export default sendScriptApi([
|
|
214
|
+
fnOne,
|
|
215
|
+
fnTwo,
|
|
216
|
+
], /* perform websocket request */) as typeof MyModule
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
> [!NOTE]
|
|
220
|
+
> Although type coercion on the client side can improve the development
|
|
221
|
+
> experience, it does not represent the actual type.
|
|
222
|
+
> Values are likely subject to serialization and deserialization,
|
|
223
|
+
> particularly when interfacing with JSON formats.
|
|
224
|
+
|
|
187
225
|
## Tests
|
|
188
226
|
|
|
189
227
|
Tests with 100% code coverage.
|
|
@@ -194,19 +232,19 @@ npm t -- report text-summary
|
|
|
194
232
|
```
|
|
195
233
|
```
|
|
196
234
|
|
|
197
|
-
> sendscript@0.
|
|
235
|
+
> sendscript@0.1.1 test
|
|
198
236
|
> tap -R silent
|
|
199
237
|
|
|
200
238
|
|
|
201
|
-
> sendscript@0.
|
|
239
|
+
> sendscript@0.1.1 test
|
|
202
240
|
> tap report text-summary
|
|
203
241
|
|
|
204
242
|
|
|
205
243
|
=============================== Coverage summary ===============================
|
|
206
|
-
Statements : 100% (
|
|
207
|
-
Branches : 100% (
|
|
208
|
-
Functions : 100% (
|
|
209
|
-
Lines : 100% (
|
|
244
|
+
Statements : 100% ( 98/98 )
|
|
245
|
+
Branches : 100% ( 30/30 )
|
|
246
|
+
Functions : 100% ( 10/10 )
|
|
247
|
+
Lines : 100% ( 98/98 )
|
|
210
248
|
================================================================================
|
|
211
249
|
```
|
|
212
250
|
|
|
@@ -235,14 +273,6 @@ Check if packages are up to date on release.
|
|
|
235
273
|
npm outdated && echo 'No outdated packages found'
|
|
236
274
|
```
|
|
237
275
|
```
|
|
238
|
-
|
|
276
|
+
Package Current Wanted Latest Location Depended by
|
|
277
|
+
tap 18.5.2 18.6.1 18.6.1 node_modules/tap sendscript
|
|
239
278
|
```
|
|
240
|
-
|
|
241
|
-
## License
|
|
242
|
-
|
|
243
|
-
See the [LICENSE.txt][license] file for details.
|
|
244
|
-
|
|
245
|
-
[license]:./LICENSE.txt
|
|
246
|
-
[socket.io]:https://socket.io/
|
|
247
|
-
[changelog]:./CHANGELOG.md
|
|
248
|
-
[auto-changelog]:https://www.npmjs.com/package/auto-changelog
|
package/README.mz
CHANGED
|
@@ -166,6 +166,44 @@ The array you see here is the LISP that SendScript uses to represent programs.
|
|
|
166
166
|
You could use SendScript without knowing the details of how the LISP works. It
|
|
167
167
|
is an implementation detail and might change over time.
|
|
168
168
|
|
|
169
|
+
## TypeScript
|
|
170
|
+
|
|
171
|
+
There is a good use-case to write an environment module in TypeScript.
|
|
172
|
+
|
|
173
|
+
1. Obviously the module would have the benefits that TypeScript offers when
|
|
174
|
+
coding.
|
|
175
|
+
2. You can use tools like [typedoc][typedoc] to generate docs from your types to
|
|
176
|
+
share with consumers of your API.
|
|
177
|
+
3. You can generate a .d.ts with `tsc --declaration` and use it to coerce your
|
|
178
|
+
client to adopt the modules type.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Create pretty docs for your module.
|
|
182
|
+
npx typedoc my-module.ts
|
|
183
|
+
|
|
184
|
+
# Create the .d.ts file for your module.
|
|
185
|
+
tsc --declaration
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Now we can use the `my-module.d.ts` file for the client API.
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import type * as MyModule from './my-module.d.ts'
|
|
192
|
+
|
|
193
|
+
import sendScriptApi from 'sendscript/api.mjs'
|
|
194
|
+
|
|
195
|
+
export default sendScriptApi([
|
|
196
|
+
fnOne,
|
|
197
|
+
fnTwo,
|
|
198
|
+
], /* perform websocket request */) as typeof MyModule
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
> [!NOTE]
|
|
202
|
+
> Although type coercion on the client side can improve the development
|
|
203
|
+
> experience, it does not represent the actual type.
|
|
204
|
+
> Values are likely subject to serialization and deserialization,
|
|
205
|
+
> particularly when interfacing with JSON formats.
|
|
206
|
+
|
|
169
207
|
## Tests
|
|
170
208
|
|
|
171
209
|
Tests with 100% code coverage.
|
|
@@ -208,3 +246,4 @@ See the [LICENSE.txt][license] file for details.
|
|
|
208
246
|
[socket.io]:https://socket.io/
|
|
209
247
|
[changelog]:./CHANGELOG.md
|
|
210
248
|
[auto-changelog]:https://www.npmjs.com/package/auto-changelog
|
|
249
|
+
[typedoc]:https://github.com/TypeStrong/typedoc
|
package/api.mjs
CHANGED
|
@@ -1,25 +1,33 @@
|
|
|
1
|
-
import
|
|
1
|
+
import awaitWhen from './await-when.mjs'
|
|
2
2
|
|
|
3
3
|
const symbol = Symbol('api')
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const isNotStub = v => v?.[symbol] !== symbol
|
|
5
|
+
const awaitWhenNotStub = awaitWhen(isNotStub)
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Create stubs to be used to build the program.
|
|
9
|
+
*
|
|
10
|
+
* @param {string[]} schema - Names of stubs.
|
|
11
|
+
* @param {Function} call - Function to call when awaited.
|
|
12
|
+
*
|
|
13
|
+
* @return {Object} An object containing the named stubs.
|
|
14
|
+
*/
|
|
7
15
|
export default function api (schema, call) {
|
|
8
16
|
return schema.reduce((api, name) => {
|
|
9
17
|
const then = (program) => (resolve, reject) =>
|
|
10
18
|
Promise.resolve(call(program)).then(resolve, reject)
|
|
11
19
|
|
|
12
|
-
const fn = (...args) =>
|
|
13
|
-
[
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const fn = (...args) => {
|
|
21
|
+
const toJSON = () => ['call', fn, args]
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
[symbol]: symbol,
|
|
25
|
+
toJSON,
|
|
26
|
+
async then (resolve, reject) {
|
|
27
|
+
return then(await awaitWhenNotStub(toJSON()))(resolve, reject)
|
|
28
|
+
}
|
|
21
29
|
}
|
|
22
|
-
}
|
|
30
|
+
}
|
|
23
31
|
|
|
24
32
|
fn.toJSON = () => ['ref', name]
|
|
25
33
|
fn.then = then(fn)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import curry from './curry.mjs'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export default curry(function awaitWhen (when, array) {
|
|
4
4
|
return array.reduce(async (asyncAcc, item) => {
|
|
5
5
|
const acc = await asyncAcc
|
|
6
6
|
|
|
@@ -9,5 +9,3 @@ const promiseOnly = curry(function promiseOnly (when, array) {
|
|
|
9
9
|
return acc
|
|
10
10
|
}, [])
|
|
11
11
|
})
|
|
12
|
-
|
|
13
|
-
export default promiseOnly
|
package/exec.mjs
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import _debug from './debug.mjs'
|
|
2
|
-
import { BlendExpressionError } from './error.mjs'
|
|
3
2
|
import curry from './curry.mjs'
|
|
4
3
|
|
|
5
4
|
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
5
|
|
|
10
6
|
const exec = curry(async (env, expression) => {
|
|
11
7
|
debug('exec', expression)
|
|
@@ -16,54 +12,20 @@ const exec = curry(async (env, expression) => {
|
|
|
16
12
|
|
|
17
13
|
const [operator, ...args] = expression
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
15
|
+
if (operator === 'call') {
|
|
16
|
+
const [fnRef, fnArgs] = args
|
|
41
17
|
|
|
42
|
-
await
|
|
43
|
-
|
|
44
|
-
}))
|
|
45
|
-
|
|
46
|
-
return exec(letEnv, letBody)
|
|
18
|
+
const fn = await exec(env, fnRef)
|
|
19
|
+
return fn.apply(env, await exec(env, fnArgs))
|
|
47
20
|
}
|
|
48
21
|
|
|
49
22
|
if (operator === 'ref') {
|
|
50
23
|
const [name] = args
|
|
51
|
-
return env[name]
|
|
52
|
-
}
|
|
53
24
|
|
|
54
|
-
|
|
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
|
|
25
|
+
return env[name]
|
|
63
26
|
}
|
|
64
27
|
|
|
65
|
-
return exec(env
|
|
28
|
+
return await Promise.all(expression.map(exec(env)))
|
|
66
29
|
})
|
|
67
30
|
|
|
68
|
-
export { exec }
|
|
69
31
|
export default exec
|
package/exec.test.mjs
CHANGED
|
@@ -1,125 +1,42 @@
|
|
|
1
1
|
import { test } from 'tap'
|
|
2
|
-
import
|
|
2
|
+
import api from './api.mjs'
|
|
3
|
+
import exec from './exec.mjs'
|
|
3
4
|
|
|
4
|
-
test('should evaluate basic expressions correctly', (t) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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,
|
|
5
|
+
test('should evaluate basic expressions correctly', async (t) => {
|
|
6
|
+
const module = {
|
|
7
|
+
add: (a, b) => a + b,
|
|
8
|
+
identity: x => x,
|
|
73
9
|
concat: (a, b) => a.concat(b)
|
|
74
10
|
}
|
|
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
11
|
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
t.end()
|
|
97
|
-
})
|
|
12
|
+
const evaluate = exec(module)
|
|
13
|
+
const { add, concat, identity } = api(Object.keys(module), program =>
|
|
14
|
+
evaluate(JSON.parse(JSON.stringify(program))))
|
|
98
15
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
throw error
|
|
104
|
-
}
|
|
105
|
-
}
|
|
16
|
+
t.equal(
|
|
17
|
+
await evaluate(add(1, 2)),
|
|
18
|
+
3
|
|
19
|
+
)
|
|
106
20
|
|
|
107
|
-
|
|
21
|
+
t.strictSame(
|
|
22
|
+
await evaluate([]),
|
|
23
|
+
[]
|
|
24
|
+
)
|
|
108
25
|
|
|
109
|
-
t.
|
|
110
|
-
|
|
26
|
+
t.strictSame(
|
|
27
|
+
await evaluate(concat([1, 2], [[add(1, 2)]])),
|
|
28
|
+
[1, 2, [3]]
|
|
29
|
+
)
|
|
111
30
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
}
|
|
31
|
+
t.strictSame(
|
|
32
|
+
await evaluate(concat([1, 2], [add(1, 2), add(2, 2)])),
|
|
33
|
+
[1, 2, 3, 4]
|
|
34
|
+
)
|
|
118
35
|
|
|
119
|
-
|
|
36
|
+
t.strictSame(
|
|
37
|
+
await evaluate([identity(1), 2, 3]),
|
|
38
|
+
[1, 2, 3]
|
|
39
|
+
)
|
|
120
40
|
|
|
121
|
-
t.same(result, {
|
|
122
|
-
quoted: ['throws']
|
|
123
|
-
})
|
|
124
41
|
t.end()
|
|
125
42
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sendscript",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Blur the line between server and client code.",
|
|
5
5
|
"module": true,
|
|
6
6
|
"main": "index.mjs",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"author": "Bas Huis",
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"tap": "^18.
|
|
26
|
+
"tap": "^18.5.2"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"debug": "^4.3.4"
|
package/schema.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "JSON S-expression Language Schema",
|
|
4
|
+
"description": "Schema for a custom JSON S-expression language.",
|
|
5
|
+
"definitions": {
|
|
6
|
+
"ref": {
|
|
7
|
+
"title": "Reference Operator",
|
|
8
|
+
"description": "A reference to a function or identifier.",
|
|
9
|
+
"type": "array",
|
|
10
|
+
"minItems": 2,
|
|
11
|
+
"maxItems": 2,
|
|
12
|
+
"items": [
|
|
13
|
+
{ "enum": ["ref"], "title": "Operator", "description": "The operator type, representing a reference to a function or identifier." },
|
|
14
|
+
{ "type": "string", "title": "Identifier", "description": "The identifier or function name." }
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"call": {
|
|
18
|
+
"title": "Call Operator",
|
|
19
|
+
"description": "A function call.",
|
|
20
|
+
"type": "array",
|
|
21
|
+
"minItems": 2,
|
|
22
|
+
"maxItems": 3,
|
|
23
|
+
"items": [
|
|
24
|
+
{ "const": "call", "title": "Operator", "description": "The operator type, representing a function call." },
|
|
25
|
+
{ "$ref": "#/definitions/ref", "title": "Reference", "description": "The reference to a function or identifier." },
|
|
26
|
+
{
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": {
|
|
29
|
+
"$ref": "#"
|
|
30
|
+
},
|
|
31
|
+
"title": "Arguments",
|
|
32
|
+
"description": "The arguments passed to the function."
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"oneOf": [
|
|
38
|
+
{ "$ref": "#/definitions/ref" },
|
|
39
|
+
{ "$ref": "#/definitions/call" },
|
|
40
|
+
{ "type": "number", "title": "Number", "description": "A JSON number." },
|
|
41
|
+
{ "type": "string", "title": "String", "description": "A JSON string." },
|
|
42
|
+
{ "type": "boolean", "title": "Boolean", "description": "A JSON boolean." },
|
|
43
|
+
{ "type": "null", "title": "Null", "description": "A JSON null value." },
|
|
44
|
+
{
|
|
45
|
+
"title": "Object",
|
|
46
|
+
"description": "A generic JSON object with additional properties.",
|
|
47
|
+
"type": "object",
|
|
48
|
+
"additionalProperties": {
|
|
49
|
+
"$ref": "#"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
package/api.test.mjs
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { test } from 'tap'
|
|
2
|
-
import api from './api.mjs'
|
|
3
|
-
|
|
4
|
-
test('return a constant value', async t => {
|
|
5
|
-
t.plan(1)
|
|
6
|
-
|
|
7
|
-
const { constant } = api(['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 } = api(['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
|
-
})
|