@muze-nl/simplystore 0.3.2 → 0.3.3
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/.npmignore~ +8 -0
- package/package.json +7 -4
- package/package.json~ +32 -0
- package/src/commands.mjs +7 -0
- package/src/produce.mjs +354 -0
- package/src/server.mjs +152 -147
- package/src/share.mjs +159 -0
- package/src/util.mjs +46 -0
- package/src/worker-command.js +49 -0
- package/src/worker-query.js +148 -0
- package/test/produce.mjs +79 -0
- package/test/share.mjs +18 -0
- package/test/test.jsontag +20 -0
- package/www/codemirror/keymap/vim.js +3 -3
- package/design/access-management.md +0 -25
- package/design/acid.md +0 -31
- package/design/commands.md +0 -25
- package/design/identity.md +0 -71
- package/design/immutability.md +0 -26
- package/design/jsontag-selector.md +0 -365
- package/design/multitasking.md +0 -13
- package/design/thoughts.md +0 -32
- package/docs/docker.md +0 -129
- package/www/assets/css/page.css +0 -34
- package/www/help/index.html +0 -94
package/src/server.mjs
CHANGED
|
@@ -1,83 +1,40 @@
|
|
|
1
1
|
import express from 'express'
|
|
2
2
|
import fs from 'fs'
|
|
3
|
-
import pointer from 'json-pointer'
|
|
4
3
|
import JSONTag from '@muze-nl/jsontag'
|
|
5
|
-
import {JSONPath} from 'jsonpath-plus'
|
|
6
|
-
import {VM} from 'vm2'
|
|
7
|
-
import {_,from,not,anyOf,allOf} from 'array-where-select'
|
|
8
4
|
import { fileURLToPath } from 'url'
|
|
9
5
|
import path from 'path'
|
|
6
|
+
import { spawn, Pool, Worker } from 'threads'
|
|
7
|
+
import commands from './commands.mjs'
|
|
8
|
+
import {appendFile} from './util.mjs'
|
|
10
9
|
|
|
11
10
|
const __dirname = path.dirname(path.dirname(fileURLToPath(import.meta.url)))
|
|
12
11
|
|
|
13
12
|
const server = express()
|
|
14
13
|
|
|
15
|
-
function deepFreeze(obj) {
|
|
16
|
-
Object.freeze(obj)
|
|
17
|
-
Object.keys(obj).forEach(prop => {
|
|
18
|
-
if (typeof obj[prop] === 'object' && !Object.isFrozen(obj[prop])) {
|
|
19
|
-
deepFreeze(obj[prop])
|
|
20
|
-
}
|
|
21
|
-
})
|
|
22
|
-
return obj
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function isString(s)
|
|
26
|
-
{
|
|
27
|
-
return typeof s === 'string' || s instanceof String
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function joinArgs(args) {
|
|
31
|
-
return args = args.map(arg => {
|
|
32
|
-
if (isString(arg)) {
|
|
33
|
-
return arg
|
|
34
|
-
} else {
|
|
35
|
-
return JSONTag.stringify(arg)
|
|
36
|
-
}
|
|
37
|
-
}).join(' ')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function connectConsole(res) {
|
|
41
|
-
return {
|
|
42
|
-
log: function(...args) {
|
|
43
|
-
res.append('X-Console-Log', joinArgs(args))
|
|
44
|
-
},
|
|
45
|
-
warning: function(...args) {
|
|
46
|
-
res.append('X-Console-Warning', joinArgs(args))
|
|
47
|
-
},
|
|
48
|
-
error: function(...args) {
|
|
49
|
-
res.append('X-Console-Error', joinArgs(args))
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
14
|
async function main(options) {
|
|
55
15
|
if (!options) {
|
|
56
16
|
options = {}
|
|
57
17
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
18
|
+
const port = options.port || 3000
|
|
19
|
+
const datafile = options.datafile || 'data.jsontag'
|
|
20
|
+
const wwwroot = options.wwwroot || __dirname+'/www'
|
|
21
|
+
const commandLog = options.commandlog || 'commandlog.jsontag'
|
|
22
|
+
let meta = options.meta || {}
|
|
63
23
|
|
|
64
|
-
|
|
65
|
-
JSON = JSONTag // monkeypatching
|
|
24
|
+
let jsontag = fs.readFileSync(datafile, 'utf-8')
|
|
66
25
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
process.exit()
|
|
75
|
-
}
|
|
26
|
+
function initWorkerPool(workerName, size=null) {
|
|
27
|
+
return Pool(() => {
|
|
28
|
+
return spawn(new Worker(workerName)).then(worker => {
|
|
29
|
+
worker.initialize(jsontag)
|
|
30
|
+
return worker
|
|
31
|
+
})
|
|
32
|
+
}, size)
|
|
76
33
|
}
|
|
77
|
-
deepFreeze(dataspace)
|
|
78
34
|
|
|
79
|
-
let
|
|
80
|
-
|
|
35
|
+
let queryWorkerpool = initWorkerPool('./worker-query')
|
|
36
|
+
|
|
37
|
+
const commandWorkerpool = initWorkerPool('./worker-command',1) // only one update worker so no changes can get lost
|
|
81
38
|
|
|
82
39
|
server.get('/', (req,res) => {
|
|
83
40
|
res.send('<h1>SimplyStore</h1>') //TODO: implement something nice
|
|
@@ -103,35 +60,34 @@ async function main(options) {
|
|
|
103
60
|
return true
|
|
104
61
|
}
|
|
105
62
|
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
path = '';
|
|
63
|
+
function sendResponse(response, res) {
|
|
64
|
+
if (response.code) {
|
|
65
|
+
res.status(response.code)
|
|
110
66
|
}
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
67
|
+
if (response.jsontag) {
|
|
68
|
+
res.setHeader('content-type','application/jsontag')
|
|
69
|
+
} else {
|
|
70
|
+
res.setHeader('content-type','application/json')
|
|
114
71
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
result = JSONTag.parse('<object class="Error">{"message":'+originalJSON.stringify(err.message)+', "code":500}')
|
|
126
|
-
}
|
|
72
|
+
res.send(response.body)+"\n"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function sendCommandResponse(result, req, res) {
|
|
76
|
+
if (result.code) {
|
|
77
|
+
res.status(result.code)
|
|
78
|
+
}
|
|
79
|
+
if (req.accepts('application/jsontag')) {
|
|
80
|
+
res.setHeader('content-type','application/jsontag')
|
|
81
|
+
res.send(JSONTag.stringify(result, null, 4)+"\n")
|
|
127
82
|
} else {
|
|
128
|
-
|
|
83
|
+
res.setHeader('content-type','application/json')
|
|
84
|
+
res.send(JSON.stringify(result, null, 4)+"\n")
|
|
129
85
|
}
|
|
130
|
-
return [result,path]
|
|
131
86
|
}
|
|
132
|
-
|
|
87
|
+
|
|
133
88
|
server.get('/query/*', (req, res, next) =>
|
|
134
89
|
{
|
|
90
|
+
console.log('express query')
|
|
135
91
|
let start = Date.now()
|
|
136
92
|
|
|
137
93
|
if ( !accept(req,res,
|
|
@@ -151,21 +107,20 @@ async function main(options) {
|
|
|
151
107
|
// done
|
|
152
108
|
return
|
|
153
109
|
}
|
|
154
|
-
|
|
155
|
-
let
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (req.accepts('application/jsontag')) {
|
|
161
|
-
res.setHeader('content-type','application/jsontag+json')
|
|
162
|
-
res.send(JSONTag.stringify(result, null, 4)+"\n")
|
|
163
|
-
} else {
|
|
164
|
-
res.setHeader('content-type','application/json')
|
|
165
|
-
res.send(originalJSON.stringify(result, null, 4)+"\n")
|
|
110
|
+
let path = req.path.substr(6); // cut '/query'
|
|
111
|
+
let request = {
|
|
112
|
+
method: req.method,
|
|
113
|
+
url: req.originalUrl,
|
|
114
|
+
query: req.query,
|
|
115
|
+
jsontag: req.accepts('application/jsontag')
|
|
166
116
|
}
|
|
167
|
-
|
|
168
|
-
|
|
117
|
+
queryWorkerpool.queue(queryWorker => {
|
|
118
|
+
return queryWorker.runQuery(path, request)
|
|
119
|
+
}).then(response => {
|
|
120
|
+
sendResponse(response, res)
|
|
121
|
+
let end = Date.now()
|
|
122
|
+
console.log(path, (end-start), process.memoryUsage())
|
|
123
|
+
})
|
|
169
124
|
})
|
|
170
125
|
|
|
171
126
|
/**
|
|
@@ -178,62 +133,112 @@ async function main(options) {
|
|
|
178
133
|
) {
|
|
179
134
|
return
|
|
180
135
|
}
|
|
181
|
-
let
|
|
182
|
-
let
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// @todo add arc tree dive function?
|
|
189
|
-
const vm = new VM({
|
|
190
|
-
// timeout: 1000,
|
|
191
|
-
allowAsync: false,
|
|
192
|
-
sandbox: {
|
|
193
|
-
root: dataspace,
|
|
194
|
-
data: result,
|
|
195
|
-
meta: meta,
|
|
196
|
-
_: _,
|
|
197
|
-
from: from,
|
|
198
|
-
not: not,
|
|
199
|
-
anyOf: anyOf,
|
|
200
|
-
allOf: allOf,
|
|
201
|
-
console: connectConsole(res),
|
|
202
|
-
JSONTag: JSONTag,
|
|
203
|
-
request: {
|
|
204
|
-
method: req.method,
|
|
205
|
-
url: req.originalUrl,
|
|
206
|
-
query: req.query
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
wasm: false
|
|
210
|
-
})
|
|
211
|
-
try {
|
|
212
|
-
result = vm.run(query)
|
|
213
|
-
let used = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
214
|
-
console.log(`(${used} MB)`);
|
|
215
|
-
} catch(err) {
|
|
216
|
-
console.log(err)
|
|
217
|
-
error = JSONTag.parse('<object class="Error">{"message":'+originalJSON.stringify(''+err)+',"code":422}')
|
|
218
|
-
}
|
|
136
|
+
let query = req.body.toString() // raw body through express.raw()
|
|
137
|
+
let path = req.path.substr(6); // cut '/query'
|
|
138
|
+
let request = {
|
|
139
|
+
method: req.method,
|
|
140
|
+
url: req.originalUrl,
|
|
141
|
+
query: req.query,
|
|
142
|
+
jsontag: req.accepts('application/jsontag')
|
|
219
143
|
}
|
|
144
|
+
queryWorkerpool.queue(queryWorker => {
|
|
145
|
+
return queryWorker.runQuery(path, request, query)
|
|
146
|
+
}).then(response => {
|
|
147
|
+
sendResponse(response, res)
|
|
148
|
+
let end = Date.now()
|
|
149
|
+
console.log(path, (end-start), process.memoryUsage())
|
|
150
|
+
})
|
|
151
|
+
})
|
|
220
152
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
153
|
+
let status = new Map()
|
|
154
|
+
|
|
155
|
+
server.get('/command/:id', (req, res) => {
|
|
156
|
+
//@TODO: find the status of command with :id
|
|
157
|
+
//return that
|
|
158
|
+
if (status.has(req.params.id)) {
|
|
159
|
+
let result = status.get(req.params.id)
|
|
160
|
+
sendResponse({
|
|
161
|
+
jsontag: false,
|
|
162
|
+
body: JSON.stringify(result)
|
|
163
|
+
},res)
|
|
228
164
|
} else {
|
|
229
|
-
|
|
230
|
-
|
|
165
|
+
sendResponse({
|
|
166
|
+
code: 404,
|
|
167
|
+
jsontag: false,
|
|
168
|
+
body: JSON.stringify({"code":404,"message":"Command not found"})
|
|
169
|
+
}, res)
|
|
231
170
|
}
|
|
232
|
-
let end = Date.now()
|
|
233
|
-
console.log(path, (end-start), process.memoryUsage())
|
|
234
171
|
})
|
|
235
172
|
|
|
173
|
+
server.post('/command', async (req, res) => {
|
|
174
|
+
let start = Date.now()
|
|
175
|
+
if ( !accept(req,res,
|
|
176
|
+
['application/jsontag','application/json'])
|
|
177
|
+
) {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
let error, result
|
|
181
|
+
|
|
182
|
+
let commandStr = req.body.toString() // raw body through express.raw()
|
|
183
|
+
let command = JSONTag.parse(commandStr)
|
|
184
|
+
if (!command.id) {
|
|
185
|
+
error = {
|
|
186
|
+
code: 422,
|
|
187
|
+
message: "Command has no id"
|
|
188
|
+
}
|
|
189
|
+
sendCommandResponse(error, req, res)
|
|
190
|
+
return
|
|
191
|
+
} else if (status.has(command.id)) {
|
|
192
|
+
result = "OK"
|
|
193
|
+
sendCommandResponse(result, req, res)
|
|
194
|
+
return
|
|
195
|
+
} else if (!command.name || !commands[command.name]) {
|
|
196
|
+
error = {
|
|
197
|
+
code: 422,
|
|
198
|
+
message: "Command has no name or is unknown"
|
|
199
|
+
}
|
|
200
|
+
sendCommandResponse(error, req, res)
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
await appendFile(commandLog, JSONTag.stringify(command))
|
|
204
|
+
|
|
205
|
+
status.set(command.id, 'queued')
|
|
206
|
+
console.log('command',command)
|
|
207
|
+
|
|
208
|
+
result = "OK"
|
|
209
|
+
sendCommandResponse(result, req, res)
|
|
210
|
+
let request = {
|
|
211
|
+
method: req.method,
|
|
212
|
+
url: req.originalUrl,
|
|
213
|
+
query: req.query,
|
|
214
|
+
jsontag: req.accepts('application/jsontag')
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
commandWorkerpool
|
|
218
|
+
.queue(commandWorker => commandWorker.runCommand(request, commandStr))
|
|
219
|
+
.then(response => {
|
|
220
|
+
//@TODO store response status, if response.code => error
|
|
221
|
+
if (!response.code) {
|
|
222
|
+
jsontag = response.body // global jsontag
|
|
223
|
+
let dataspace = JSONTag.parse(jsontag)
|
|
224
|
+
//@TODO: make sure queryWorkerpool is only replaced after
|
|
225
|
+
//workers are initialized, to prevent hickups if initialization takes a long time
|
|
226
|
+
let newQueryWorkerpool = initWorkerPool('./worker-query')
|
|
227
|
+
queryWorkerpool.terminate() // gracefully
|
|
228
|
+
queryWorkerpool = newQueryWorkerpool
|
|
229
|
+
//@TODO: write dataspace to disk
|
|
230
|
+
status.set(command.id, 'done')
|
|
231
|
+
let end = Date.now()
|
|
232
|
+
console.log(command.name, (end-start), process.memoryUsage())
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
.catch(err => {
|
|
236
|
+
console.error(err)
|
|
237
|
+
//@TODO: set status for this command to error with this err
|
|
238
|
+
status.set(command.id, err)
|
|
239
|
+
})
|
|
236
240
|
|
|
241
|
+
})
|
|
237
242
|
|
|
238
243
|
function handleWebRequest(req,res,options)
|
|
239
244
|
{
|
package/src/share.mjs
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import JSONTag from '@muze-nl/jsontag'
|
|
2
|
+
|
|
3
|
+
const handler = function(root, index, buffer)
|
|
4
|
+
{
|
|
5
|
+
return {
|
|
6
|
+
get(target, key, receiver)
|
|
7
|
+
{
|
|
8
|
+
//@FIXME: array.length needs to be handled
|
|
9
|
+
//@FIXME: array functions need to be handled
|
|
10
|
+
let keyNumber = index.key[key]
|
|
11
|
+
if (root[keyNumber]) {
|
|
12
|
+
let keyStart = root[keyNumber].s + root.s
|
|
13
|
+
let keyEnd = root[keyNumber].e + root.s
|
|
14
|
+
// subobjecten hoeven niet geparsed...
|
|
15
|
+
if (root[keyNumber].c) {
|
|
16
|
+
// object
|
|
17
|
+
return new jsontagProxy(root[keyNumber], index, buffer)
|
|
18
|
+
}
|
|
19
|
+
return JSON.parse(buffer.slice(keyStart,keyEnd))
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
set() {
|
|
24
|
+
throw new Error('This data is immutable')
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
deleteProperty() {
|
|
28
|
+
throw new Error('This data is immutable')
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
has(target, key)
|
|
32
|
+
{
|
|
33
|
+
let keyNumber = index.key[key]
|
|
34
|
+
return typeof root[keyNumber] !== 'undefined'
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
ownKeys(target)
|
|
38
|
+
{
|
|
39
|
+
return root.c.map(n => index.reverse[n])
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getJsontagProxy(root, index, buffer) {
|
|
45
|
+
return new Proxy({}, handler(root, index, buffer))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function share(data)
|
|
49
|
+
{
|
|
50
|
+
let jsontag = JSONTag.stringify(data)
|
|
51
|
+
let buffer = new SharedArrayBuffer(jsontag.length)
|
|
52
|
+
let dv = new DataView(buffer)
|
|
53
|
+
let encoder = new TextEncoder('utf-8')
|
|
54
|
+
let root = {}
|
|
55
|
+
let index = {
|
|
56
|
+
key: {},
|
|
57
|
+
reverse: [],
|
|
58
|
+
types: {}
|
|
59
|
+
}
|
|
60
|
+
let current = 0
|
|
61
|
+
let seen = new Map()
|
|
62
|
+
|
|
63
|
+
function getKey(key) {
|
|
64
|
+
let l;
|
|
65
|
+
if (typeof index.key[key] == 'undefined') {
|
|
66
|
+
l = Object.keys(index.key).length
|
|
67
|
+
index.key[key] = l
|
|
68
|
+
index.reverse[l] = key
|
|
69
|
+
}
|
|
70
|
+
return index.key[key]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getType(type) {
|
|
74
|
+
if (typeof index.types[type] == 'undefined') {
|
|
75
|
+
let l = Object.keys(index.types).length;
|
|
76
|
+
index.types[type] = l
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function store(container, key, value) {
|
|
82
|
+
let l = getKey(key)
|
|
83
|
+
if (seen.has(value)) {
|
|
84
|
+
container[l] = seen.get(value)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
container[l] = {
|
|
88
|
+
s: current
|
|
89
|
+
}
|
|
90
|
+
container = container[l]
|
|
91
|
+
let attributes = JSONTag.getAttributes(value)
|
|
92
|
+
if (attributes) {
|
|
93
|
+
container.a = {
|
|
94
|
+
s: current,
|
|
95
|
+
c: {}
|
|
96
|
+
}
|
|
97
|
+
Object.entries(attributes).forEach(([k,v]) => {
|
|
98
|
+
let l = getKey(k)
|
|
99
|
+
let t = getType('string')
|
|
100
|
+
container.a.c[k] = {
|
|
101
|
+
s: current,
|
|
102
|
+
t
|
|
103
|
+
}
|
|
104
|
+
v = JSON.stringify(v)
|
|
105
|
+
let {r,w} = encoder.encodeInto(v, dv, current)
|
|
106
|
+
current += w
|
|
107
|
+
container.a.c[k].e = current
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
let type = JSONTag.getType(value)
|
|
111
|
+
let t = getType(type)
|
|
112
|
+
container.t = t
|
|
113
|
+
let v = JSON.stringify(value)
|
|
114
|
+
let uint8buffer = new Uint8Array(buffer)
|
|
115
|
+
let {r,w} = encoder.encodeInto(v, uint8buffer, current)
|
|
116
|
+
current+= w
|
|
117
|
+
container.e = current
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
function walk(node, parentKey, parent, save)
|
|
122
|
+
{
|
|
123
|
+
if (seen.has(node)) {
|
|
124
|
+
Object.assign(save, seen.get(node))
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
let type = JSONTag.getType(node)
|
|
128
|
+
let t = getType(type)
|
|
129
|
+
switch (type) {
|
|
130
|
+
case 'array':
|
|
131
|
+
seen.set(node,save)
|
|
132
|
+
save.s = current
|
|
133
|
+
save.t = t
|
|
134
|
+
save.c = []
|
|
135
|
+
node.forEach((v,k) => walk(v, k, node, save.c))
|
|
136
|
+
save.e = current
|
|
137
|
+
break
|
|
138
|
+
case 'object':
|
|
139
|
+
seen.set(node, save)
|
|
140
|
+
save.s = current
|
|
141
|
+
save.c = []
|
|
142
|
+
save.t = t
|
|
143
|
+
Object.entries(node).forEach(([k,v]) => walk(v, k, node, save.c))
|
|
144
|
+
save.e = current
|
|
145
|
+
break
|
|
146
|
+
default:
|
|
147
|
+
store(save, parentKey, node)
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
walk(data, null, null, root)
|
|
153
|
+
console.log(root)
|
|
154
|
+
return {
|
|
155
|
+
root: getJsontagProxy(root, index, buffer),
|
|
156
|
+
index,
|
|
157
|
+
buffer
|
|
158
|
+
}
|
|
159
|
+
}
|
package/src/util.mjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import JSONTag from '@muze-nl/jsontag'
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
export function deepFreeze(obj) {
|
|
5
|
+
Object.freeze(obj)
|
|
6
|
+
Object.keys(obj).forEach(prop => {
|
|
7
|
+
if (typeof obj[prop] === 'object' && !Object.isFrozen(obj[prop])) {
|
|
8
|
+
deepFreeze(obj[prop])
|
|
9
|
+
}
|
|
10
|
+
})
|
|
11
|
+
return obj
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isString(s)
|
|
15
|
+
{
|
|
16
|
+
return typeof s === 'string' || s instanceof String
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function joinArgs(args) {
|
|
20
|
+
return args = args.map(arg => {
|
|
21
|
+
if (isString(arg)) {
|
|
22
|
+
return arg
|
|
23
|
+
} else {
|
|
24
|
+
return JSONTag.stringify(arg)
|
|
25
|
+
}
|
|
26
|
+
}).join(' ')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* atomic append to a log file, adds newline after each write
|
|
31
|
+
* @param {string} filename The filename to append to
|
|
32
|
+
* @param {string} data The line to write
|
|
33
|
+
* @return {void}
|
|
34
|
+
*/
|
|
35
|
+
export async function appendFile(filename, data) {
|
|
36
|
+
console.log('appending command')
|
|
37
|
+
let handle;
|
|
38
|
+
try {
|
|
39
|
+
handle = await fs.open(filename, 'a')
|
|
40
|
+
await handle.appendFile(data+"\n")
|
|
41
|
+
await handle.datasync()
|
|
42
|
+
return true
|
|
43
|
+
} finally {
|
|
44
|
+
await handle.close()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {expose} from 'threads/worker'
|
|
2
|
+
import JSONTag from '@muze-nl/jsontag'
|
|
3
|
+
import commands from './commands.mjs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Command Worker for threads.js library
|
|
7
|
+
* returns JSONTag strings, since otherwise JSON.stringify is used
|
|
8
|
+
* and type+attribute data gets lost
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
let dataspace
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @TODO: check for valid command id
|
|
15
|
+
* @TODO: write valid commands to command log, emit 'ok', check in server.mjs for that
|
|
16
|
+
* @TODO: setTimeout or do above checks in server.mjs before queueing
|
|
17
|
+
*/
|
|
18
|
+
const command = {
|
|
19
|
+
initialize(jsontag) {
|
|
20
|
+
if (!jsontag) { throw new Error('missing jsontag parameter')}
|
|
21
|
+
dataspace = JSONTag.parse(jsontag)
|
|
22
|
+
console.log('initialized command worker thread')
|
|
23
|
+
return true
|
|
24
|
+
},
|
|
25
|
+
runCommand(request, commandStr) {
|
|
26
|
+
if (!commandStr) { throw new Error('missing command parameter')}
|
|
27
|
+
if (!request) { throw new Error('missing request parameter')}
|
|
28
|
+
let response = {
|
|
29
|
+
jsontag: true
|
|
30
|
+
}
|
|
31
|
+
let command = JSONTag.parse(commandStr) // raw body through express.raw()
|
|
32
|
+
if (command && command.name && commands[command.name]) {
|
|
33
|
+
try {
|
|
34
|
+
commands[command.name](dataspace, command, request)
|
|
35
|
+
response.body = JSONTag.stringify(dataspace) //@TODO: this is inefficient, patch would be better
|
|
36
|
+
} catch(err) {
|
|
37
|
+
console.log(err)
|
|
38
|
+
response.code = 422;
|
|
39
|
+
response.body = '<object class="Error">{"message":'+JSON.stringify(''+err)+',"code":422}'
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
response.code = 404
|
|
43
|
+
response.body = '<object class="Error">{"message":"Command '+command.name+' not found","code":404}'
|
|
44
|
+
}
|
|
45
|
+
return response
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
expose(command)
|