@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 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/poef/jsontag) is an enhancement over JSON that allows you to tag JSON data with metadata using HTML-like tags.
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 [array-where-select](https://www.npmjs.com/package/array-where-select) extension.
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.8.1",
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.2",
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(command)
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
- runNextCommand()
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
- let command = commandQueue.shift()
269
- if (command) {
270
- let start = (resolve, reject) => {
271
- if (!commandWorkerInstance) {
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
- commandWorkerInstance.on('message', result => {
275
- resolve(result)
276
- runNextCommand()
277
- })
278
- commandWorkerInstance.on('error', error => {
279
- reject(error)
280
- runNextCommand()
281
- })
282
- commandWorkerInstance.postMessage(command)
283
- }
284
- start(
285
- // resolve()
286
- (data) => {
287
- let s
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: data.code, status: "failed", message: data.message, details: data.details}
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
- } else {
297
- s = {code: 200, status: "done"}
298
- status.set(command.id, s)
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
- appendFile(commandStatus, JSONTag.stringify(Object.assign({command:command.id}, s)))
312
- },
313
- //reject()
314
- (error) => {
315
- console.error(error)
316
- let s = {status: "failed", code: error.code, message: error.message, details: error.details}
317
- status.set(command.id, s)
318
- appendFile(commandStatus, JSONTag.stringify(Object.assign({command:command.id}, s)))
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
- } else {
322
- // this code can never be triggered from the post(/command/) route, since it always adds a command to the queue
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
+ }