sendscript 0.0.0 → 0.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 +23 -0
- package/README.md +54 -34
- package/README.mz +23 -18
- package/api.mjs +32 -0
- package/{dsl.test.mjs → api.test.mjs} +3 -3
- package/example/client.socket.io.mjs +3 -3
- package/example/math.mjs +1 -0
- package/exec.test.mjs +15 -0
- package/package.json +2 -2
- package/promise-only.mjs +13 -0
- package/dsl.mjs +0 -40
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
### Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
|
4
|
+
|
|
5
|
+
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
|
+
|
|
7
|
+
#### [v0.0.2](https://github.com/bas080/sendscript/compare/v0.0.1...v0.0.2)
|
|
8
|
+
|
|
9
|
+
- Add table of content to readme [`a14f2d8`](https://github.com/bas080/sendscript/commit/a14f2d86f602a01721812d1c5be94b299e515397)
|
|
10
|
+
|
|
11
|
+
#### [v0.0.1](https://github.com/bas080/sendscript/compare/v0.0.0...v0.0.1)
|
|
12
|
+
|
|
13
|
+
> 26 September 2023
|
|
14
|
+
|
|
15
|
+
- Add square fn to the socket.io example [`8d7cfbd`](https://github.com/bas080/sendscript/commit/8d7cfbddd7933804e7168825cf1e17e792c91fe9)
|
|
16
|
+
- Rename dsl to api [`4a67324`](https://github.com/bas080/sendscript/commit/4a6732456656db743d02c5c35444f93a866d27ec)
|
|
17
|
+
- Move promise only concept to own module [`50adde6`](https://github.com/bas080/sendscript/commit/50adde694b3d23fb025a4ad20dcc83d7bb72b852)
|
|
18
|
+
|
|
19
|
+
#### v0.0.0
|
|
20
|
+
|
|
21
|
+
> 14 September 2023
|
|
22
|
+
|
|
23
|
+
- Initiate project [`2a192b3`](https://github.com/bas080/sendscript/commit/2a192b34e5390ab946b7b77fd299e23180c49f72)
|
package/README.md
CHANGED
|
@@ -3,11 +3,29 @@
|
|
|
3
3
|
Write JS code that you can run on servers, browsers or other clients.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/sendscript)
|
|
6
|
+
[](#tests)
|
|
6
7
|
[](https://standardjs.com)
|
|
7
|
-
[](./LICENSE)
|
|
8
|
+
[](./LICENSE.txt)
|
|
8
9
|
|
|
9
|
-
> SendScript leaves it up to you to choose HTTP, web-sockets or any other
|
|
10
|
-
> communication between servers and clients that best fits your
|
|
10
|
+
> SendScript leaves it up to you to choose HTTP, web-sockets or any other
|
|
11
|
+
> method of communication between servers and clients that best fits your
|
|
12
|
+
> needs.
|
|
13
|
+
|
|
14
|
+
<!-- toc -->
|
|
15
|
+
|
|
16
|
+
- [Socket example](#socket-example)
|
|
17
|
+
* [Module](#module)
|
|
18
|
+
* [Server](#server)
|
|
19
|
+
* [Client](#client)
|
|
20
|
+
- [Reference](#reference)
|
|
21
|
+
* [`sendscript/api.mjs`](#sendscriptapimjs)
|
|
22
|
+
* [`sendscript/exec.mjs`](#sendscriptexecmjs)
|
|
23
|
+
- [Tests](#tests)
|
|
24
|
+
- [Formatting](#formatting)
|
|
25
|
+
- [Changelog](#changelog)
|
|
26
|
+
- [License](#license)
|
|
27
|
+
|
|
28
|
+
<!-- tocstop -->
|
|
11
29
|
|
|
12
30
|
## Socket example
|
|
13
31
|
|
|
@@ -21,17 +39,18 @@ npm install --no-save socket.io socket.io-client
|
|
|
21
39
|
|
|
22
40
|
### Module
|
|
23
41
|
|
|
24
|
-
We write a simple module
|
|
42
|
+
We write a simple module.
|
|
25
43
|
|
|
26
44
|
```js
|
|
27
45
|
// ./example/math.mjs
|
|
28
46
|
|
|
29
47
|
export const add = (a, b) => a + b
|
|
48
|
+
export const square = a => a * a
|
|
30
49
|
```
|
|
31
50
|
|
|
32
51
|
### Server
|
|
33
52
|
|
|
34
|
-
Here a
|
|
53
|
+
Here a socket.io server that runs SendScript programs.
|
|
35
54
|
|
|
36
55
|
```js
|
|
37
56
|
// ./example/server.socket.io.mjs
|
|
@@ -66,7 +85,7 @@ Now for a client that sends a program to the server.
|
|
|
66
85
|
// ./example/client.socket.io.mjs
|
|
67
86
|
|
|
68
87
|
import socketClient from 'socket.io-client'
|
|
69
|
-
import
|
|
88
|
+
import api from '../api.mjs'
|
|
70
89
|
|
|
71
90
|
const port = process.env.PORT || 3000
|
|
72
91
|
const client = socketClient(`http://localhost:${port}`)
|
|
@@ -81,10 +100,10 @@ const exec = program => {
|
|
|
81
100
|
})
|
|
82
101
|
}
|
|
83
102
|
|
|
84
|
-
const { add } =
|
|
103
|
+
const { add, square } = api(['add', 'square'], exec)
|
|
85
104
|
|
|
86
105
|
console.log(
|
|
87
|
-
await add(1, add(add(2, 3), 4))
|
|
106
|
+
await square(add(1, add(add(2, 3), 4)))
|
|
88
107
|
)
|
|
89
108
|
|
|
90
109
|
process.exit(0)
|
|
@@ -102,33 +121,33 @@ node ./example/client.socket.io.mjs
|
|
|
102
121
|
pkill sendscript
|
|
103
122
|
```
|
|
104
123
|
```
|
|
105
|
-
|
|
124
|
+
100
|
|
106
125
|
```
|
|
107
126
|
|
|
108
127
|
## Reference
|
|
109
128
|
|
|
110
|
-
SendScript is essentially a way to serialize a program to then send over the
|
|
111
|
-
and execute it somewhere else.
|
|
129
|
+
SendScript is essentially a way to serialize a program to then send over the
|
|
130
|
+
wire and execute it somewhere else.
|
|
112
131
|
|
|
113
132
|
We only have two modules. One that helps you write programs that can be sent
|
|
114
133
|
over the wire and another for running that program.
|
|
115
134
|
|
|
116
|
-
### `sendscript/
|
|
135
|
+
### `sendscript/api.mjs`
|
|
117
136
|
|
|
118
|
-
The
|
|
137
|
+
The api module exports a function that takes two arguments.
|
|
119
138
|
|
|
120
139
|
1. The schema, which represents the values that are available.
|
|
121
140
|
2. The function that will be called with the serializable version of the
|
|
122
141
|
program.
|
|
123
142
|
|
|
124
143
|
It returns an object that contains functions which are defined in the schema.
|
|
125
|
-
These functions are a JavaScript
|
|
144
|
+
These functions are a JavaScript API for writing programs that can be sent to
|
|
126
145
|
a server.
|
|
127
146
|
|
|
128
147
|
```js
|
|
129
|
-
import
|
|
148
|
+
import api from './api.mjs'
|
|
130
149
|
|
|
131
|
-
const { add, subtract } =
|
|
150
|
+
const { add, subtract } = api(
|
|
132
151
|
['add', 'subtract'],
|
|
133
152
|
serializableProgram => sendSomewhereToBeExecuted(serializableProgram)
|
|
134
153
|
)
|
|
@@ -144,7 +163,7 @@ soon as await or `.then` is used.
|
|
|
144
163
|
> Notice that you do not have to await the subtract call. You only need to
|
|
145
164
|
> await when you want to execute the program.
|
|
146
165
|
|
|
147
|
-
This
|
|
166
|
+
This API is composable and wrappable.
|
|
148
167
|
|
|
149
168
|
### `sendscript/exec.mjs`
|
|
150
169
|
|
|
@@ -161,8 +180,8 @@ exec({
|
|
|
161
180
|
|
|
162
181
|
The array you see here is the LISP that SendScript uses to represent programs.
|
|
163
182
|
|
|
164
|
-
You could use SendScript without knowing the details of how the LISP works. It
|
|
165
|
-
implementation detail and might change over time.
|
|
183
|
+
You could use SendScript without knowing the details of how the LISP works. It
|
|
184
|
+
is an implementation detail and might change over time.
|
|
166
185
|
|
|
167
186
|
## Tests
|
|
168
187
|
|
|
@@ -173,27 +192,28 @@ npx c8 --100 npm t -- -R classic
|
|
|
173
192
|
```
|
|
174
193
|
```
|
|
175
194
|
|
|
176
|
-
> sendscript@0.0.
|
|
195
|
+
> sendscript@0.0.2 test
|
|
177
196
|
> tap *.test.mjs --no-cov -R classic
|
|
178
197
|
|
|
198
|
+
api.test.mjs .......................................... 5/5
|
|
179
199
|
curry.test.mjs ........................................ 4/4
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
total ............................................... 24/24
|
|
200
|
+
exec.test.mjs ....................................... 16/16
|
|
201
|
+
total ............................................... 25/25
|
|
183
202
|
|
|
184
|
-
|
|
203
|
+
25 passing (426.12ms)
|
|
185
204
|
|
|
186
205
|
ok
|
|
187
|
-
|
|
188
|
-
File
|
|
189
|
-
|
|
190
|
-
All files
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
error.mjs
|
|
195
|
-
exec.mjs
|
|
196
|
-
|
|
206
|
+
------------------|---------|----------|---------|---------|-------------------
|
|
207
|
+
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
208
|
+
------------------|---------|----------|---------|---------|-------------------
|
|
209
|
+
All files | 100 | 100 | 100 | 100 |
|
|
210
|
+
api.mjs | 100 | 100 | 100 | 100 |
|
|
211
|
+
curry.mjs | 100 | 100 | 100 | 100 |
|
|
212
|
+
debug.mjs | 100 | 100 | 100 | 100 |
|
|
213
|
+
error.mjs | 100 | 100 | 100 | 100 |
|
|
214
|
+
exec.mjs | 100 | 100 | 100 | 100 |
|
|
215
|
+
promise-only.mjs | 100 | 100 | 100 | 100 |
|
|
216
|
+
------------------|---------|----------|---------|---------|-------------------
|
|
197
217
|
```
|
|
198
218
|
|
|
199
219
|
## Formatting
|
package/README.mz
CHANGED
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
Write JS code that you can run on servers, browsers or other clients.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/sendscript)
|
|
6
|
+
[](#tests)
|
|
6
7
|
[](https://standardjs.com)
|
|
7
|
-
[](./LICENSE)
|
|
8
|
+
[](./LICENSE.txt)
|
|
8
9
|
|
|
9
|
-
> SendScript leaves it up to you to choose HTTP, web-sockets or any other
|
|
10
|
-
> communication between servers and clients that best fits your
|
|
10
|
+
> SendScript leaves it up to you to choose HTTP, web-sockets or any other
|
|
11
|
+
> method of communication between servers and clients that best fits your
|
|
12
|
+
> needs.
|
|
13
|
+
|
|
14
|
+
<!-- toc -->
|
|
11
15
|
|
|
12
16
|
## Socket example
|
|
13
17
|
|
|
@@ -21,17 +25,18 @@ npm install --no-save socket.io socket.io-client
|
|
|
21
25
|
|
|
22
26
|
### Module
|
|
23
27
|
|
|
24
|
-
We write a simple module
|
|
28
|
+
We write a simple module.
|
|
25
29
|
|
|
26
30
|
```js cat - > ./example/math.mjs
|
|
27
31
|
// ./example/math.mjs
|
|
28
32
|
|
|
29
33
|
export const add = (a, b) => a + b
|
|
34
|
+
export const square = a => a * a
|
|
30
35
|
```
|
|
31
36
|
|
|
32
37
|
### Server
|
|
33
38
|
|
|
34
|
-
Here a
|
|
39
|
+
Here a socket.io server that runs SendScript programs.
|
|
35
40
|
|
|
36
41
|
```js cat - > ./example/server.socket.io.mjs
|
|
37
42
|
// ./example/server.socket.io.mjs
|
|
@@ -66,7 +71,7 @@ Now for a client that sends a program to the server.
|
|
|
66
71
|
// ./example/client.socket.io.mjs
|
|
67
72
|
|
|
68
73
|
import socketClient from 'socket.io-client'
|
|
69
|
-
import
|
|
74
|
+
import api from '../api.mjs'
|
|
70
75
|
|
|
71
76
|
const port = process.env.PORT || 3000
|
|
72
77
|
const client = socketClient(`http://localhost:${port}`)
|
|
@@ -81,10 +86,10 @@ const exec = program => {
|
|
|
81
86
|
})
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
const { add } =
|
|
89
|
+
const { add, square } = api(['add', 'square'], exec)
|
|
85
90
|
|
|
86
91
|
console.log(
|
|
87
|
-
await add(1, add(add(2, 3), 4))
|
|
92
|
+
await square(add(1, add(add(2, 3), 4)))
|
|
88
93
|
)
|
|
89
94
|
|
|
90
95
|
process.exit(0)
|
|
@@ -104,28 +109,28 @@ pkill sendscript
|
|
|
104
109
|
|
|
105
110
|
## Reference
|
|
106
111
|
|
|
107
|
-
SendScript is essentially a way to serialize a program to then send over the
|
|
108
|
-
and execute it somewhere else.
|
|
112
|
+
SendScript is essentially a way to serialize a program to then send over the
|
|
113
|
+
wire and execute it somewhere else.
|
|
109
114
|
|
|
110
115
|
We only have two modules. One that helps you write programs that can be sent
|
|
111
116
|
over the wire and another for running that program.
|
|
112
117
|
|
|
113
|
-
### `sendscript/
|
|
118
|
+
### `sendscript/api.mjs`
|
|
114
119
|
|
|
115
|
-
The
|
|
120
|
+
The api module exports a function that takes two arguments.
|
|
116
121
|
|
|
117
122
|
1. The schema, which represents the values that are available.
|
|
118
123
|
2. The function that will be called with the serializable version of the
|
|
119
124
|
program.
|
|
120
125
|
|
|
121
126
|
It returns an object that contains functions which are defined in the schema.
|
|
122
|
-
These functions are a JavaScript
|
|
127
|
+
These functions are a JavaScript API for writing programs that can be sent to
|
|
123
128
|
a server.
|
|
124
129
|
|
|
125
130
|
```js
|
|
126
|
-
import
|
|
131
|
+
import api from './api.mjs'
|
|
127
132
|
|
|
128
|
-
const { add, subtract } =
|
|
133
|
+
const { add, subtract } = api(
|
|
129
134
|
['add', 'subtract'],
|
|
130
135
|
serializableProgram => sendSomewhereToBeExecuted(serializableProgram)
|
|
131
136
|
)
|
|
@@ -141,7 +146,7 @@ soon as await or `.then` is used.
|
|
|
141
146
|
> Notice that you do not have to await the subtract call. You only need to
|
|
142
147
|
> await when you want to execute the program.
|
|
143
148
|
|
|
144
|
-
This
|
|
149
|
+
This API is composable and wrappable.
|
|
145
150
|
|
|
146
151
|
### `sendscript/exec.mjs`
|
|
147
152
|
|
|
@@ -158,8 +163,8 @@ exec({
|
|
|
158
163
|
|
|
159
164
|
The array you see here is the LISP that SendScript uses to represent programs.
|
|
160
165
|
|
|
161
|
-
You could use SendScript without knowing the details of how the LISP works. It
|
|
162
|
-
implementation detail and might change over time.
|
|
166
|
+
You could use SendScript without knowing the details of how the LISP works. It
|
|
167
|
+
is an implementation detail and might change over time.
|
|
163
168
|
|
|
164
169
|
## Tests
|
|
165
170
|
|
package/api.mjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import promiseOnly from './promise-only.mjs'
|
|
2
|
+
|
|
3
|
+
const symbol = Symbol('api')
|
|
4
|
+
const isNonAPI = v => v?.[symbol] !== symbol
|
|
5
|
+
const promiseOnlyNonAPI = promiseOnly(isNonAPI)
|
|
6
|
+
|
|
7
|
+
export default function api (schema, call) {
|
|
8
|
+
return schema.reduce((api, name) => {
|
|
9
|
+
const then = (program) => (resolve, reject) =>
|
|
10
|
+
Promise.resolve(call(program)).then(resolve, reject)
|
|
11
|
+
|
|
12
|
+
const fn = (...args) => ({
|
|
13
|
+
[symbol]: symbol,
|
|
14
|
+
toJSON () {
|
|
15
|
+
return [fn, ...args]
|
|
16
|
+
},
|
|
17
|
+
async then (resolve, reject) {
|
|
18
|
+
return then(await promiseOnlyNonAPI([
|
|
19
|
+
fn, ...args
|
|
20
|
+
]))(resolve, reject)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
fn.toJSON = () => ['ref', name]
|
|
25
|
+
fn.then = then(fn)
|
|
26
|
+
fn[symbol] = symbol
|
|
27
|
+
|
|
28
|
+
api[name] = fn
|
|
29
|
+
|
|
30
|
+
return api
|
|
31
|
+
}, {})
|
|
32
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { test } from 'tap'
|
|
2
|
-
import
|
|
2
|
+
import api from './api.mjs'
|
|
3
3
|
|
|
4
4
|
test('return a constant value', async t => {
|
|
5
5
|
t.plan(1)
|
|
6
6
|
|
|
7
|
-
const { constant } =
|
|
7
|
+
const { constant } = api(['constant'], v => {
|
|
8
8
|
t.equal(JSON.stringify(v), '["ref","constant"]')
|
|
9
9
|
})
|
|
10
10
|
|
|
@@ -14,7 +14,7 @@ test('return a constant value', async t => {
|
|
|
14
14
|
test('use function calls as arguments', async t => {
|
|
15
15
|
t.plan(4)
|
|
16
16
|
|
|
17
|
-
const { add } =
|
|
17
|
+
const { add } = api(['add'], b => {
|
|
18
18
|
t.equal(JSON.stringify(awaited), JSON.stringify(b))
|
|
19
19
|
return 'done'
|
|
20
20
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ./example/client.socket.io.mjs
|
|
2
2
|
|
|
3
3
|
import socketClient from 'socket.io-client'
|
|
4
|
-
import
|
|
4
|
+
import api from '../api.mjs'
|
|
5
5
|
|
|
6
6
|
const port = process.env.PORT || 3000
|
|
7
7
|
const client = socketClient(`http://localhost:${port}`)
|
|
@@ -16,10 +16,10 @@ const exec = program => {
|
|
|
16
16
|
})
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const { add } =
|
|
19
|
+
const { add, square } = api(['add', 'square'], exec)
|
|
20
20
|
|
|
21
21
|
console.log(
|
|
22
|
-
await add(1, add(add(2, 3), 4))
|
|
22
|
+
await square(add(1, add(add(2, 3), 4)))
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
process.exit(0)
|
package/example/math.mjs
CHANGED
package/exec.test.mjs
CHANGED
|
@@ -108,3 +108,18 @@ test('should reject with the error of the called function', async t => {
|
|
|
108
108
|
|
|
109
109
|
t.end()
|
|
110
110
|
})
|
|
111
|
+
|
|
112
|
+
test('should not exec expressions in an object', async t => {
|
|
113
|
+
const env = {
|
|
114
|
+
throws () {
|
|
115
|
+
throw new Error('Should not have been called.')
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const result = await exec(env, { quoted: ['throws'] })
|
|
120
|
+
|
|
121
|
+
t.same(result, {
|
|
122
|
+
quoted: ['throws']
|
|
123
|
+
})
|
|
124
|
+
t.end()
|
|
125
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sendscript",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Blur the line between server and client code.",
|
|
5
5
|
"module": true,
|
|
6
6
|
"main": "index.mjs",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"test": "tap *.test.mjs --no-cov",
|
|
13
13
|
"version": "npm run docs && git add *.md",
|
|
14
|
-
"docs": "markatzea README.mz | tee README.md"
|
|
14
|
+
"docs": "markatzea README.mz | tee README.md && npx markdown-toc -i README.md"
|
|
15
15
|
},
|
|
16
16
|
"author": "Bas Huis",
|
|
17
17
|
"license": "MIT",
|
package/promise-only.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import curry from './curry.mjs'
|
|
2
|
+
|
|
3
|
+
const promiseOnly = curry(function promiseOnly (when, array) {
|
|
4
|
+
return array.reduce(async (asyncAcc, item) => {
|
|
5
|
+
const acc = await asyncAcc
|
|
6
|
+
|
|
7
|
+
acc.push(when(item) ? await item : item)
|
|
8
|
+
|
|
9
|
+
return acc
|
|
10
|
+
}, [])
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export default promiseOnly
|
package/dsl.mjs
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
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
|
-
}
|