@muze-nl/simplystore 0.8.1 → 0.9.0
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/README.md +3 -3
- package/package.json +2 -2
- package/src/server.mjs +93 -62
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
SimplyStore is a radically simpler backend storage server. It does not have a database, certainly no SQL or GraphQL, it is not REST. In return it has a well defined API that is automatically derived from your dataset. It supports JSONTag to allow for semantically meaningful data, without having to do the full switch to Linked Data and triple stores. The query format is javascript, you can post javascript queries that will run on the server. All data is read into memory and is available to these javascript queries without needing (or allowing) disk access or indexes.
|
|
4
4
|
|
|
5
|
-
[JSONTag](https://github.com/
|
|
5
|
+
[JSONTag](https://github.com/muze-nl/jsontag) is an enhancement over JSON that allows you to tag JSON data with metadata using HTML-like tags.
|
|
6
6
|
Javascript queries are run in a [VM2](https://www.npmjs.com/package/vm2) sandbox.
|
|
7
|
-
You can query data using the [
|
|
7
|
+
You can query data using the [jaqt](https://github.com/muze-nl/jaqt/) library.
|
|
8
8
|
|
|
9
9
|
Note: _There are known security issues in VM2, so the project will switch to V8-isolate. For now make sure SimplyStore is not publically accessible, by adding an api gateway in front of it for example_
|
|
10
10
|
|
|
@@ -168,4 +168,4 @@ In addition, SimplyStore is meant to be a real-world testcase for JSONTag.
|
|
|
168
168
|
[MIT](LICENSE) © Muze.nl
|
|
169
169
|
|
|
170
170
|
## Contributions
|
|
171
|
-
Contributions are welcome, but make sure that all code is MIT licensed. If you want to send a merge request, please make sure that there is a ticket that shows the bug/feature and reference it. If you find any problem, please do file a ticket, but you should not expect a timely resolution. This project is still very experimental, don't use it in production unless you are ready to fix problems yourself.
|
|
171
|
+
Contributions are welcome, but make sure that all code is MIT licensed. If you want to send a merge request, please make sure that there is a ticket that shows the bug/feature and reference it. If you find any problem, please do file a ticket, but you should not expect a timely resolution. This project is still very experimental, don't use it in production unless you are ready to fix problems yourself.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muze-nl/simplystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"main": "src/server.mjs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"bugs": "https://github.com/simplyedit/simplystore/issues",
|
|
17
17
|
"homepage": "https://github.com/simplyedit/simplystore#readme",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@muze-nl/jaqt": "^0.10.
|
|
19
|
+
"@muze-nl/jaqt": "^0.10.3",
|
|
20
20
|
"@muze-nl/jsontag": "^0.10.2",
|
|
21
21
|
"@muze-nl/od-jsontag": "^0.4.1",
|
|
22
22
|
"codemirror": "^6.0.1",
|
package/src/server.mjs
CHANGED
|
@@ -40,7 +40,6 @@ async function main(options) {
|
|
|
40
40
|
}))
|
|
41
41
|
|
|
42
42
|
let status = loadCommandStatus(commandStatus)
|
|
43
|
-
let commandQueue = loadCommandLog(status, commandLog)
|
|
44
43
|
|
|
45
44
|
try {
|
|
46
45
|
let data = await loadData(Array.from(status.keys())) // command id's (keys) are used to generate filenames of changes
|
|
@@ -51,6 +50,8 @@ async function main(options) {
|
|
|
51
50
|
process.exit(1)
|
|
52
51
|
}
|
|
53
52
|
|
|
53
|
+
let commandQueue = loadCommandLog(status, commandLog)
|
|
54
|
+
|
|
54
55
|
const queryWorkerInitTask = () => {
|
|
55
56
|
return {
|
|
56
57
|
name: 'init',
|
|
@@ -77,6 +78,14 @@ async function main(options) {
|
|
|
77
78
|
console.error(`Port ${port} is already occupied, aborting.`)
|
|
78
79
|
process.exit()
|
|
79
80
|
} catch(err) {
|
|
81
|
+
let result
|
|
82
|
+
do {
|
|
83
|
+
try {
|
|
84
|
+
result = await runNextCommand()
|
|
85
|
+
} catch(err) {
|
|
86
|
+
// console.log(err) // ignore errors here, already logged to console
|
|
87
|
+
}
|
|
88
|
+
} while(result)
|
|
80
89
|
server.listen(port, () => {
|
|
81
90
|
console.log('SimplyStore listening on port '+port)
|
|
82
91
|
let used = Math.round(process.memoryUsage().rss / 1024 / 1024);
|
|
@@ -115,10 +124,18 @@ async function main(options) {
|
|
|
115
124
|
let lines = log.split("\n").filter(Boolean)
|
|
116
125
|
for(let line of lines) {
|
|
117
126
|
let command = JSONTag.parse(line)
|
|
118
|
-
let state = status.get(command.id)
|
|
127
|
+
let state = status.get(command.id)?.status
|
|
119
128
|
switch(state) {
|
|
120
129
|
case 'accepted': // enqueue
|
|
121
|
-
commands.push(
|
|
130
|
+
commands.push({
|
|
131
|
+
id: command.id,
|
|
132
|
+
command: line,
|
|
133
|
+
request: null,
|
|
134
|
+
meta,
|
|
135
|
+
data:jsontagBuffers,
|
|
136
|
+
commandsFile,
|
|
137
|
+
datafile
|
|
138
|
+
})
|
|
122
139
|
break;
|
|
123
140
|
case 'done': // do nothing
|
|
124
141
|
break;
|
|
@@ -238,8 +255,10 @@ async function main(options) {
|
|
|
238
255
|
commandsFile,
|
|
239
256
|
datafile
|
|
240
257
|
})
|
|
241
|
-
|
|
242
|
-
|
|
258
|
+
let result
|
|
259
|
+
do {
|
|
260
|
+
result = await runNextCommand()
|
|
261
|
+
} while(result)
|
|
243
262
|
} catch(err) {
|
|
244
263
|
let s = {code:err.code||500, status:'failed', message:err.message, details:err.details}
|
|
245
264
|
status.set(commandId, s)
|
|
@@ -265,67 +284,80 @@ async function main(options) {
|
|
|
265
284
|
}
|
|
266
285
|
|
|
267
286
|
async function runNextCommand() {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
287
|
+
return new Promise(async (mainResolve, mainReject) => {
|
|
288
|
+
if (commandWorkerInstance) {
|
|
289
|
+
mainReject('commandWorker already running')
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
let command = commandQueue.shift()
|
|
293
|
+
if (command) {
|
|
294
|
+
console.log('starting command',command.id)
|
|
295
|
+
let start = (resolve, reject) => {
|
|
272
296
|
commandWorkerInstance = new Worker(commandWorker)
|
|
297
|
+
commandWorkerInstance.on('message', async result => {
|
|
298
|
+
await commandWorkerInstance.terminate()
|
|
299
|
+
commandWorkerInstance = null
|
|
300
|
+
resolve(result)
|
|
301
|
+
})
|
|
302
|
+
commandWorkerInstance.on('error', async error => {
|
|
303
|
+
await commandWorkerInstance.terminate()
|
|
304
|
+
commandWorkerInstance = null
|
|
305
|
+
reject(error)
|
|
306
|
+
})
|
|
307
|
+
commandWorkerInstance.postMessage(command)
|
|
273
308
|
}
|
|
274
|
-
|
|
275
|
-
resolve(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (!data || (data.code>=300 && data.code<=499)) {
|
|
289
|
-
console.error('ERROR: SimplyStore cannot run command ', command.id, data)
|
|
290
|
-
if (!data?.code) {
|
|
291
|
-
s = {code: 500, status: "failed"}
|
|
309
|
+
start(
|
|
310
|
+
// resolve()
|
|
311
|
+
(data) => {
|
|
312
|
+
let s
|
|
313
|
+
if (!data || (data.code>=300 && data.code<=499)) {
|
|
314
|
+
console.error('ERROR: SimplyStore cannot run command ', command.id, data)
|
|
315
|
+
if (!data?.code) {
|
|
316
|
+
s = {code: 500, status: "failed"}
|
|
317
|
+
} else {
|
|
318
|
+
s = {code: data.code, status: "failed", message: data.message, details: data.details}
|
|
319
|
+
}
|
|
320
|
+
status.set(command.id, s)
|
|
321
|
+
appendFile(commandStatus, JSONTag.stringify(Object.assign({command:command.id}, s)))
|
|
322
|
+
mainReject(s)
|
|
292
323
|
} else {
|
|
293
|
-
s = {code:
|
|
324
|
+
s = {code: 200, status: "done"}
|
|
325
|
+
status.set(command.id, s)
|
|
326
|
+
if (data.data) { // data has changed, commands may do other things instead of changing data
|
|
327
|
+
jsontagBuffers.push(data.data) // push changeset to jsontagBuffers so that new query workers get all changes from scratch
|
|
328
|
+
Object.assign(meta, data.meta)
|
|
329
|
+
queryWorkerPool.update({
|
|
330
|
+
name: 'update',
|
|
331
|
+
req: {
|
|
332
|
+
body: jsontagBuffers[jsontagBuffers.length-1], // only add the last change, update tasks for earlier changes have already been sent
|
|
333
|
+
meta
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
appendFile(commandStatus, JSONTag.stringify(Object.assign({command:command.id}, s)))
|
|
338
|
+
mainResolve(s)
|
|
294
339
|
}
|
|
340
|
+
},
|
|
341
|
+
//reject()
|
|
342
|
+
(error) => {
|
|
343
|
+
let s = {status: "failed", code: error.code, message: error.message, details: error.details}
|
|
295
344
|
status.set(command.id, s)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (data.data) { // data has changed, commands may do other things instead of changing data
|
|
300
|
-
jsontagBuffers.push(data.data) // push changeset to jsontagBuffers so that new query workers get all changes from scratch
|
|
301
|
-
Object.assign(meta, data.meta)
|
|
302
|
-
queryWorkerPool.update({
|
|
303
|
-
name: 'update',
|
|
304
|
-
req: {
|
|
305
|
-
body: jsontagBuffers[jsontagBuffers.length-1], // only add the last change, update tasks for earlier changes have already been sent
|
|
306
|
-
meta
|
|
307
|
-
}
|
|
308
|
-
})
|
|
309
|
-
}
|
|
345
|
+
appendFile(commandStatus, JSONTag.stringify(Object.assign({command:command.id}, s)))
|
|
346
|
+
console.log('command error', command.id, error)
|
|
347
|
+
mainReject(s)
|
|
310
348
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
349
|
+
)
|
|
350
|
+
} else {
|
|
351
|
+
console.log('no pending commands')
|
|
352
|
+
// this code can never be triggered from the post(/command/) route, since it always adds a command to the queue
|
|
353
|
+
// so you can only get here from commandWorkerInstance.on() route
|
|
354
|
+
// which means that the commandWorkerInstance has finished running the previous command
|
|
355
|
+
if (commandWorkerInstance) {
|
|
356
|
+
await commandWorkerInstance.terminate()
|
|
319
357
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
// so you can only get here from commandWorkerInstance.on() route
|
|
324
|
-
// which means that the commandWorkerInstance has finished running the previous command
|
|
325
|
-
await commandWorkerInstance.terminate()
|
|
326
|
-
commandWorkerInstance.unref() // @FIXME is this needed?
|
|
327
|
-
commandWorkerInstance = null // @FIXME or this?
|
|
328
|
-
}
|
|
358
|
+
mainResolve(false)
|
|
359
|
+
}
|
|
360
|
+
})
|
|
329
361
|
}
|
|
330
362
|
|
|
331
363
|
function checkCommand(req, res) {
|
|
@@ -368,13 +400,12 @@ async function main(options) {
|
|
|
368
400
|
sendResponse({code:422, body: JSON.stringify(error)}, res)
|
|
369
401
|
return false
|
|
370
402
|
}
|
|
371
|
-
appendFile(commandLog, JSONTag.stringify(command))
|
|
403
|
+
appendFile(commandLog, JSONTag.stringify(command)) //FIXME: this loses request data
|
|
372
404
|
appendFile(commandStatus, JSONTag.stringify(commandOK))
|
|
373
405
|
status.set(command.id, commandOK)
|
|
374
406
|
sendResponse({code: 202, body: JSON.stringify(commandOK)}, res)
|
|
375
407
|
return command.id
|
|
376
408
|
}
|
|
377
|
-
|
|
378
409
|
}
|
|
379
410
|
|
|
380
411
|
function sendResponse(response, res) {
|
|
@@ -435,4 +466,4 @@ function handleWebRequest(req,res,options)
|
|
|
435
466
|
} else {
|
|
436
467
|
res.sendFile('/index.html', fileOptions)
|
|
437
468
|
}
|
|
438
|
-
}
|
|
469
|
+
}
|