@muze-nl/simplystore 0.3.2 → 0.3.5
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/.eslintrc.cjs +15 -0
- package/package.json +8 -4
- package/src/commands.mjs +7 -0
- package/src/produce.mjs +354 -0
- package/src/server.mjs +153 -147
- package/src/share.mjs +159 -0
- package/src/util.mjs +46 -0
- package/src/worker-command-init.mjs +11 -0
- package/src/worker-command.mjs +51 -0
- package/src/worker-query-init.mjs +14 -0
- package/src/worker-query.mjs +146 -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,86 +1,46 @@
|
|
|
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 commands from './commands.mjs'
|
|
7
|
+
import {appendFile} from './util.mjs'
|
|
8
|
+
import {Piscina} from 'piscina'
|
|
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
|
+
const queryWorker = options.queryWorker || __dirname+'/src/worker-query-init.mjs'
|
|
23
|
+
const commandWorker = options.commandWorker || __dirname+'/src/worker-command-init.mjs'
|
|
63
24
|
|
|
64
|
-
|
|
65
|
-
JSON = JSONTag // monkeypatching
|
|
25
|
+
let jsontag = fs.readFileSync(datafile, 'utf-8')
|
|
66
26
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
process.exit()
|
|
27
|
+
function initWorkerPool(workerName, size=null) {
|
|
28
|
+
let options = {
|
|
29
|
+
filename: workerName,
|
|
30
|
+
workerData: jsontag
|
|
31
|
+
}
|
|
32
|
+
if (size) {
|
|
33
|
+
options.maxThreads = size
|
|
75
34
|
}
|
|
35
|
+
return new Piscina(options)
|
|
76
36
|
}
|
|
77
|
-
deepFreeze(dataspace)
|
|
78
37
|
|
|
79
|
-
let
|
|
80
|
-
|
|
38
|
+
let queryWorkerpool = initWorkerPool(queryWorker)
|
|
39
|
+
|
|
40
|
+
// const commandWorkerpool = initWorkerPool(commandWorker,1) // only one update worker so no changes can get lost
|
|
81
41
|
|
|
82
42
|
server.get('/', (req,res) => {
|
|
83
|
-
res.send('<h1>SimplyStore</h1>')
|
|
43
|
+
res.send('<h1>SimplyStore</h1>') //@TODO: implement something nice
|
|
84
44
|
})
|
|
85
45
|
|
|
86
46
|
server.use(express.static(wwwroot))
|
|
@@ -103,35 +63,34 @@ async function main(options) {
|
|
|
103
63
|
return true
|
|
104
64
|
}
|
|
105
65
|
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
path = '';
|
|
66
|
+
function sendResponse(response, res) {
|
|
67
|
+
if (response.code) {
|
|
68
|
+
res.status(response.code)
|
|
110
69
|
}
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
70
|
+
if (response.jsontag) {
|
|
71
|
+
res.setHeader('content-type','application/jsontag')
|
|
72
|
+
} else {
|
|
73
|
+
res.setHeader('content-type','application/json')
|
|
114
74
|
}
|
|
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
|
-
}
|
|
75
|
+
res.send(response.body)+"\n"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function sendCommandResponse(result, req, res) {
|
|
79
|
+
if (result.code) {
|
|
80
|
+
res.status(result.code)
|
|
81
|
+
}
|
|
82
|
+
if (req.accepts('application/jsontag')) {
|
|
83
|
+
res.setHeader('content-type','application/jsontag')
|
|
84
|
+
res.send(JSONTag.stringify(result, null, 4)+"\n")
|
|
127
85
|
} else {
|
|
128
|
-
|
|
86
|
+
res.setHeader('content-type','application/json')
|
|
87
|
+
res.send(JSON.stringify(result, null, 4)+"\n")
|
|
129
88
|
}
|
|
130
|
-
return [result,path]
|
|
131
89
|
}
|
|
132
|
-
|
|
90
|
+
|
|
133
91
|
server.get('/query/*', (req, res, next) =>
|
|
134
92
|
{
|
|
93
|
+
console.log('express query')
|
|
135
94
|
let start = Date.now()
|
|
136
95
|
|
|
137
96
|
if ( !accept(req,res,
|
|
@@ -151,21 +110,19 @@ async function main(options) {
|
|
|
151
110
|
// done
|
|
152
111
|
return
|
|
153
112
|
}
|
|
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")
|
|
113
|
+
let path = req.path.substr(6); // cut '/query'
|
|
114
|
+
let request = {
|
|
115
|
+
method: req.method,
|
|
116
|
+
url: req.originalUrl,
|
|
117
|
+
query: req.query,
|
|
118
|
+
jsontag: req.accepts('application/jsontag')
|
|
166
119
|
}
|
|
167
|
-
|
|
168
|
-
|
|
120
|
+
queryWorkerpool.run({pointer:path, request})
|
|
121
|
+
.then(response => {
|
|
122
|
+
sendResponse(response, res)
|
|
123
|
+
let end = Date.now()
|
|
124
|
+
console.log(path, (end-start), process.memoryUsage())
|
|
125
|
+
})
|
|
169
126
|
})
|
|
170
127
|
|
|
171
128
|
/**
|
|
@@ -178,62 +135,111 @@ async function main(options) {
|
|
|
178
135
|
) {
|
|
179
136
|
return
|
|
180
137
|
}
|
|
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
|
-
}
|
|
138
|
+
let query = req.body.toString() // raw body through express.raw()
|
|
139
|
+
let path = req.path.substr(6); // cut '/query'
|
|
140
|
+
let request = {
|
|
141
|
+
method: req.method,
|
|
142
|
+
url: req.originalUrl,
|
|
143
|
+
query: req.query,
|
|
144
|
+
jsontag: req.accepts('application/jsontag')
|
|
219
145
|
}
|
|
146
|
+
queryWorkerpool.run({pointer:path, request, query})
|
|
147
|
+
.then(response => {
|
|
148
|
+
sendResponse(response, res)
|
|
149
|
+
let end = Date.now()
|
|
150
|
+
console.log(path, (end-start), process.memoryUsage())
|
|
151
|
+
})
|
|
152
|
+
})
|
|
220
153
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
154
|
+
let status = new Map()
|
|
155
|
+
|
|
156
|
+
server.get('/command/:id', (req, res) => {
|
|
157
|
+
//@TODO: find the status of command with :id
|
|
158
|
+
//return that
|
|
159
|
+
if (status.has(req.params.id)) {
|
|
160
|
+
let result = status.get(req.params.id)
|
|
161
|
+
sendResponse({
|
|
162
|
+
jsontag: false,
|
|
163
|
+
body: JSON.stringify(result)
|
|
164
|
+
},res)
|
|
228
165
|
} else {
|
|
229
|
-
|
|
230
|
-
|
|
166
|
+
sendResponse({
|
|
167
|
+
code: 404,
|
|
168
|
+
jsontag: false,
|
|
169
|
+
body: JSON.stringify({"code":404,"message":"Command not found"})
|
|
170
|
+
}, res)
|
|
231
171
|
}
|
|
232
|
-
let end = Date.now()
|
|
233
|
-
console.log(path, (end-start), process.memoryUsage())
|
|
234
172
|
})
|
|
235
173
|
|
|
174
|
+
server.post('/command', async (req, res) => {
|
|
175
|
+
let start = Date.now()
|
|
176
|
+
if ( !accept(req,res,
|
|
177
|
+
['application/jsontag','application/json'])
|
|
178
|
+
) {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
let error, result
|
|
182
|
+
|
|
183
|
+
let commandStr = req.body.toString() // raw body through express.raw()
|
|
184
|
+
let command = JSONTag.parse(commandStr)
|
|
185
|
+
if (!command.id) {
|
|
186
|
+
error = {
|
|
187
|
+
code: 422,
|
|
188
|
+
message: "Command has no id"
|
|
189
|
+
}
|
|
190
|
+
sendCommandResponse(error, req, res)
|
|
191
|
+
return
|
|
192
|
+
} else if (status.has(command.id)) {
|
|
193
|
+
result = "OK"
|
|
194
|
+
sendCommandResponse(result, req, res)
|
|
195
|
+
return
|
|
196
|
+
} else if (!command.name || !commands[command.name]) {
|
|
197
|
+
error = {
|
|
198
|
+
code: 422,
|
|
199
|
+
message: "Command has no name or is unknown"
|
|
200
|
+
}
|
|
201
|
+
sendCommandResponse(error, req, res)
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
await appendFile(commandLog, JSONTag.stringify(command))
|
|
205
|
+
|
|
206
|
+
status.set(command.id, 'queued')
|
|
207
|
+
console.log('command',command)
|
|
208
|
+
|
|
209
|
+
result = "OK"
|
|
210
|
+
sendCommandResponse(result, req, res)
|
|
211
|
+
let request = {
|
|
212
|
+
method: req.method,
|
|
213
|
+
url: req.originalUrl,
|
|
214
|
+
query: req.query,
|
|
215
|
+
jsontag: req.accepts('application/jsontag')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
commandWorkerpool
|
|
219
|
+
.run({request, commandStr})
|
|
220
|
+
.then(response => {
|
|
221
|
+
//@TODO store response status, if response.code => error
|
|
222
|
+
if (!response.code) {
|
|
223
|
+
jsontag = response.body // global jsontag
|
|
224
|
+
let dataspace = JSONTag.parse(jsontag)
|
|
225
|
+
//@TODO: make sure queryWorkerpool is only replaced after
|
|
226
|
+
//workers are initialized, to prevent hickups if initialization takes a long time
|
|
227
|
+
let newQueryWorkerpool = initWorkerPool('./worker-query')
|
|
228
|
+
queryWorkerpool.terminate() // gracefully
|
|
229
|
+
queryWorkerpool = newQueryWorkerpool
|
|
230
|
+
//@TODO: write dataspace to disk
|
|
231
|
+
status.set(command.id, 'done')
|
|
232
|
+
let end = Date.now()
|
|
233
|
+
console.log(command.name, (end-start), process.memoryUsage())
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
.catch(err => {
|
|
237
|
+
console.error(err)
|
|
238
|
+
//@TODO: set status for this command to error with this err
|
|
239
|
+
status.set(command.id, err)
|
|
240
|
+
})
|
|
236
241
|
|
|
242
|
+
})
|
|
237
243
|
|
|
238
244
|
function handleWebRequest(req,res,options)
|
|
239
245
|
{
|
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,11 @@
|
|
|
1
|
+
import * as commandWorker from './worker-command.mjs'
|
|
2
|
+
import worker_threads from 'node:worker_threads'
|
|
3
|
+
|
|
4
|
+
async function initialize() {
|
|
5
|
+
let meta = {}
|
|
6
|
+
let dataspace = JSONTag.parse(worker_threads.workerData, null, meta)
|
|
7
|
+
await commandWorker.initialize(worker_threads.workerData)
|
|
8
|
+
return commandWorker.runCommand
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default initialize()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import JSONTag from '@muze-nl/jsontag'
|
|
2
|
+
import commands from './commands.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Command Worker for threads.js library
|
|
6
|
+
* returns JSONTag strings, since otherwise JSON.stringify is used
|
|
7
|
+
* and type+attribute data gets lost
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
let dataspace
|
|
11
|
+
|
|
12
|
+
export function setDataspace(d) {
|
|
13
|
+
dataspace = d
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @TODO: check for valid command id
|
|
18
|
+
* @TODO: write valid commands to command log, emit 'ok', check in server.mjs for that
|
|
19
|
+
* @TODO: setTimeout or do above checks in server.mjs before queueing
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export function async initialize(jsontag) {
|
|
23
|
+
if (!jsontag) { throw new Error('missing jsontag parameter')}
|
|
24
|
+
dataspace = jsontag
|
|
25
|
+
console.log('initialized command worker thread')
|
|
26
|
+
return true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function runCommand({request, commandStr}) {
|
|
30
|
+
if (!commandStr) { throw new Error('missing command parameter')}
|
|
31
|
+
if (!request) { throw new Error('missing request parameter')}
|
|
32
|
+
let response = {
|
|
33
|
+
jsontag: true
|
|
34
|
+
}
|
|
35
|
+
let command = JSONTag.parse(commandStr) // raw body through express.raw()
|
|
36
|
+
if (command && command.name && commands[command.name]) {
|
|
37
|
+
try {
|
|
38
|
+
commands[command.name](dataspace, command, request)
|
|
39
|
+
response.body = JSONTag.stringify(dataspace) //@TODO: this is inefficient, patch would be better
|
|
40
|
+
} catch(err) {
|
|
41
|
+
console.log(err)
|
|
42
|
+
response.code = 422;
|
|
43
|
+
response.body = '<object class="Error">{"message":'+JSON.stringify(''+err)+',"code":422}'
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
response.code = 404
|
|
47
|
+
response.body = '<object class="Error">{"message":"Command '+command.name+' not found","code":404}'
|
|
48
|
+
}
|
|
49
|
+
return response
|
|
50
|
+
}
|
|
51
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import JSONTag from '@muze-nl/jsontag';
|
|
2
|
+
import worker_threads from 'node:worker_threads';
|
|
3
|
+
import * as queryWorker from './worker-query.mjs';
|
|
4
|
+
|
|
5
|
+
async function initialize() {
|
|
6
|
+
let meta = {}
|
|
7
|
+
let dataspace = JSONTag.parse(worker_threads.workerData, null, meta)
|
|
8
|
+
//console.log('starting')
|
|
9
|
+
await queryWorker.initialize(dataspace,meta)
|
|
10
|
+
//console.log('initialized')
|
|
11
|
+
return queryWorker.runQuery
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default initialize()
|