@muze-nl/simplystore 0.9.3 → 0.10.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/package.json +6 -7
- package/scripts/convert.mjs +63 -2
- package/scripts/index.id.json +1 -0
- package/scripts/index.offset.json +1 -0
- package/src/command-worker-module.mjs +11 -1
- package/src/index.id.mjs +45 -0
- package/src/index.mjs +19 -0
- package/src/index.offset.mjs +39 -0
- package/src/load-worker.mjs +16 -2
- package/src/query-worker-module.mjs +15 -21
- package/src/server.mjs +60 -15
- package/src/workerPool.mjs +2 -2
- package/www/home.html +21 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muze-nl/simplystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"main": "src/server.mjs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -16,14 +16,13 @@
|
|
|
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.6",
|
|
20
20
|
"@muze-nl/jsontag": "^0.10.4",
|
|
21
|
-
"@muze-nl/od-jsontag": "^0.4.
|
|
21
|
+
"@muze-nl/od-jsontag": "^0.4.6",
|
|
22
22
|
"codemirror": "^6.0.1",
|
|
23
|
-
"express": "^
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"write-file-atomic": "^5.0.1"
|
|
23
|
+
"express": "^5.2.1",
|
|
24
|
+
"vm2": "^3.10.5",
|
|
25
|
+
"write-file-atomic": "^7.0.1"
|
|
27
26
|
},
|
|
28
27
|
"devDependencies": {
|
|
29
28
|
"@eslint/js": "^9.36.0",
|
package/scripts/convert.mjs
CHANGED
|
@@ -1,15 +1,42 @@
|
|
|
1
1
|
import JSONTag from '@muze-nl/jsontag'
|
|
2
2
|
import serialize, { stringify } from '@muze-nl/od-jsontag/src/serialize.mjs'
|
|
3
|
+
import Parser from '@muze-nl/od-jsontag'
|
|
3
4
|
import fs from 'node:fs'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
|
|
7
|
+
const __dirname = import.meta.dirname;
|
|
4
8
|
|
|
5
9
|
if (process.argv.length<=3) {
|
|
6
|
-
console.log('usage: node ./convert.mjs {inputfile} {outputfile}')
|
|
10
|
+
console.log('usage: node ./convert.mjs {inputfile} {outputfile} {indexlib?}')
|
|
7
11
|
process.exit()
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
// parse command line
|
|
11
15
|
let inputFile = process.argv[2]
|
|
12
16
|
let outputFile = process.argv[3]
|
|
17
|
+
let indexFile = process.argv[4]
|
|
18
|
+
if (indexFile && indexFile[0]!='/') {
|
|
19
|
+
indexFile = process.cwd()+'/'+indexFile
|
|
20
|
+
} else if (!indexFile) {
|
|
21
|
+
indexFile = __dirname+'/../src/index.mjs'
|
|
22
|
+
}
|
|
23
|
+
let schemaFile = process.argv[5]
|
|
24
|
+
if (schemaFile && schemaFile[0]!='/') {
|
|
25
|
+
schemaFile = process.cwd()+'/'+schemaFile
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const index = await import(indexFile).then(mod => {
|
|
29
|
+
return mod.default
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// now create indexes
|
|
33
|
+
console.log('Using index library:', indexFile)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
let schema = {}
|
|
37
|
+
if (schemaFile) {
|
|
38
|
+
schema = JSONTag.parse(fs.readFileSync(schemaFile, 'utf-8'))
|
|
39
|
+
}
|
|
13
40
|
|
|
14
41
|
// load file
|
|
15
42
|
let input = fs.readFileSync(inputFile, 'utf-8')
|
|
@@ -17,9 +44,43 @@ let input = fs.readFileSync(inputFile, 'utf-8')
|
|
|
17
44
|
// parse jsontag
|
|
18
45
|
let data = JSONTag.parse(input)
|
|
19
46
|
|
|
47
|
+
console.log('input data parsed')
|
|
20
48
|
// write resultset to output
|
|
21
49
|
let strData = stringify(serialize(data))
|
|
22
50
|
|
|
23
|
-
|
|
51
|
+
console.log('od-jsontag created')
|
|
24
52
|
|
|
53
|
+
// indexes need the position data which is only available after
|
|
54
|
+
// parsing the od-jsontag data
|
|
55
|
+
const parser = new Parser('https://localhost/',false) // allow mutations
|
|
56
|
+
const odData = parser.parse(strData)
|
|
57
|
+
|
|
58
|
+
console.log('offsets parsed')
|
|
59
|
+
|
|
60
|
+
let meta = {
|
|
61
|
+
index: {
|
|
62
|
+
id: new Map()
|
|
63
|
+
},
|
|
64
|
+
schema,
|
|
65
|
+
resultArray: parser.meta.resultArray,
|
|
66
|
+
data: path.dirname(outputFile)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (let ob of meta.resultArray) {
|
|
70
|
+
let id = JSONTag.getAttribute(ob, 'id')
|
|
71
|
+
if (id) {
|
|
72
|
+
meta.index.id.set(id, ob)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log('Indexing...')
|
|
77
|
+
//FIXME: offset index can only be calculated by parsing serialized odData
|
|
78
|
+
//but index.create updates that data, so previous information is no longer correct
|
|
79
|
+
//TODO: split index creation in internal (updates odData) and external (creates index files)
|
|
80
|
+
index.create(odData, meta)
|
|
81
|
+
console.log('Indexes created')
|
|
82
|
+
|
|
83
|
+
strData = stringify(serialize(odData))
|
|
84
|
+
|
|
85
|
+
fs.writeFileSync(outputFile, strData)
|
|
25
86
|
console.log('Converted data written to ',outputFile)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import JSONTag from '@muze-nl/jsontag'
|
|
2
|
-
import {getIndex, resultSet} from '@muze-nl/od-jsontag/src/symbols.mjs'
|
|
2
|
+
import {getIndex, resultSet, isChanged} from '@muze-nl/od-jsontag/src/symbols.mjs'
|
|
3
3
|
import Parser from '@muze-nl/od-jsontag/src/parse.mjs'
|
|
4
4
|
import serialize from '@muze-nl/od-jsontag/src/serialize.mjs'
|
|
5
5
|
import writeFileAtomic from 'write-file-atomic'
|
|
6
6
|
|
|
7
7
|
let commands = {}
|
|
8
|
+
let index = {}
|
|
8
9
|
let resultArr = []
|
|
9
10
|
let dataspace
|
|
10
11
|
let datafile, basefile, extension
|
|
@@ -79,6 +80,9 @@ export async function initialize(task) {
|
|
|
79
80
|
commands = await import(task.commandsFile).then(mod => {
|
|
80
81
|
return mod.default
|
|
81
82
|
})
|
|
83
|
+
index = await import(task.indexFile).then(mod => {
|
|
84
|
+
return mod.default
|
|
85
|
+
})
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
export default async function runCommand(commandStr, request) {
|
|
@@ -94,6 +98,12 @@ export default async function runCommand(commandStr, request) {
|
|
|
94
98
|
commands[task.name](dataspace, task, request, metaProxy)
|
|
95
99
|
//TODO: if command/task makes no changes, skip updating data.jsontag and writing it, skip response.data
|
|
96
100
|
|
|
101
|
+
const changes = meta.resultArray.filter(e => e[isChanged])
|
|
102
|
+
//FIXME: new entities should also report isChanged = true
|
|
103
|
+
if (changes.length) {
|
|
104
|
+
changes.uuid = task.id
|
|
105
|
+
index.update(dataspace, meta, changes)
|
|
106
|
+
}
|
|
97
107
|
const uint8sab = serialize(dataspace, {meta, changes: true}) // serialize only changes
|
|
98
108
|
response.data = uint8sab
|
|
99
109
|
response.meta = {
|
package/src/index.id.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import JSONTag from '@muze-nl/jsontag'
|
|
3
|
+
import { getIndex } from '@muze-nl/od-jsontag/src/symbols.mjs'
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
create(data, meta) {
|
|
7
|
+
console.log('creating '+meta.data+'/index.id.json')
|
|
8
|
+
// jsontag parse automatically fills meta.index.id, so no need to create anything
|
|
9
|
+
// just store meta.index.id in index.id.json
|
|
10
|
+
const index = {}
|
|
11
|
+
for (const key of meta.index.id.keys()) {
|
|
12
|
+
if (!key) {
|
|
13
|
+
continue
|
|
14
|
+
}
|
|
15
|
+
const entity = meta.index.id.get(key)
|
|
16
|
+
if (!entity) {
|
|
17
|
+
continue
|
|
18
|
+
}
|
|
19
|
+
index[key] = entity[getIndex]
|
|
20
|
+
}
|
|
21
|
+
fs.writeFileSync(meta.data+'/index.id.json', JSON.stringify(index))
|
|
22
|
+
},
|
|
23
|
+
update(data, meta, changes) {
|
|
24
|
+
if (!changes.length) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
const index = {}
|
|
28
|
+
for (const entry of changes) {
|
|
29
|
+
const id = JSONTag.getAttribute(entry, 'id')
|
|
30
|
+
if (id) {
|
|
31
|
+
index[id] = entry[getIndex]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
fs.writeFileSync(meta.data+'/index.id.'+changes.uuid+'.json', JSON.stringify(index))
|
|
35
|
+
},
|
|
36
|
+
load(uuid=null) {
|
|
37
|
+
let filename
|
|
38
|
+
if (!uuid) {
|
|
39
|
+
filename = 'index.id.json'
|
|
40
|
+
} else {
|
|
41
|
+
filename = 'index.id.'+filename+'.json'
|
|
42
|
+
}
|
|
43
|
+
return JSON.parse(fs.readFileSync(meta.data+filename))
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import idIndex from './index.id.mjs'
|
|
2
|
+
import offsetIndex from './index.offset.mjs'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
create(data, meta) {
|
|
6
|
+
idIndex.create(data, meta)
|
|
7
|
+
offsetIndex.create(data, meta)
|
|
8
|
+
},
|
|
9
|
+
update(data, meta, changes) {
|
|
10
|
+
idIndex.update(data, meta, changes)
|
|
11
|
+
offsetIndex.update(data, meta, changes)
|
|
12
|
+
},
|
|
13
|
+
load(meta, uuid=null) {
|
|
14
|
+
return {
|
|
15
|
+
id: idIndex.load(uuid),
|
|
16
|
+
offset: offsetIndex.load(uuid)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import JSONTag from '@muze-nl/jsontag'
|
|
3
|
+
import { getIndex, position } from '@muze-nl/od-jsontag/src/symbols.mjs'
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
create(data, meta) {
|
|
7
|
+
console.log('creating '+meta.data+'/index.offset.json')
|
|
8
|
+
// jsontag parse automatically fills meta.index.offset, so no need to create anything
|
|
9
|
+
const index = {}
|
|
10
|
+
const max = meta.resultArray.length
|
|
11
|
+
for (let i=0;i<max;i++) {
|
|
12
|
+
const entity = meta.resultArray[i]
|
|
13
|
+
index[i] = [ entity[position].start, entity[position].end ]
|
|
14
|
+
}
|
|
15
|
+
fs.writeFileSync(meta.data+'/index.offset.json', JSON.stringify(index))
|
|
16
|
+
},
|
|
17
|
+
update(data, meta, changes) {
|
|
18
|
+
if (!changes.length) {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
const index = {}
|
|
22
|
+
for (const entry of changes) {
|
|
23
|
+
let pos = entry[position]
|
|
24
|
+
if (pos) {
|
|
25
|
+
index[entry[getIndex]] = [ pos.start, pos.end ]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
fs.writeFileSync(meta.data+'/index.offset.'+changes.uuid+'.json', JSON.stringify(index))
|
|
29
|
+
},
|
|
30
|
+
load(uuid=null) {
|
|
31
|
+
let filename
|
|
32
|
+
if (!uuid) {
|
|
33
|
+
filename = 'index.offset.json'
|
|
34
|
+
} else {
|
|
35
|
+
filename = 'index.offset.'+filename+'.json'
|
|
36
|
+
}
|
|
37
|
+
return JSON.parse(fs.readFileSync(meta.data+filename))
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/load-worker.mjs
CHANGED
|
@@ -2,25 +2,39 @@ import { parentPort } from 'node:worker_threads'
|
|
|
2
2
|
import JSONTag from '@muze-nl/jsontag'
|
|
3
3
|
import Parser from '@muze-nl/od-jsontag/src/parse.mjs'
|
|
4
4
|
import fs from 'fs'
|
|
5
|
+
import path from 'path'
|
|
5
6
|
import serialize from '@muze-nl/od-jsontag/src/serialize.mjs'
|
|
6
7
|
|
|
7
8
|
const parser = new Parser()
|
|
8
|
-
|
|
9
|
+
let index = {}
|
|
10
|
+
|
|
11
|
+
parentPort.on('message', async (files) => {
|
|
9
12
|
let meta = {
|
|
10
13
|
index: {
|
|
11
14
|
id: new Map()
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
17
|
|
|
18
|
+
index = await import(files.indexFile).then(mod => {
|
|
19
|
+
return mod.default
|
|
20
|
+
})
|
|
15
21
|
const extension = files.dataFile.split('.').pop()
|
|
16
22
|
const basefile = files.dataFile.substring(0, files.dataFile.length - (extension.length + 1)) //+1 for . character
|
|
17
|
-
|
|
23
|
+
meta.data = path.dirname(basefile)
|
|
18
24
|
let count = 0
|
|
19
25
|
let data
|
|
20
26
|
let jsontag
|
|
21
27
|
let datafile = files.dataFile
|
|
22
28
|
let commands = files.commands
|
|
23
29
|
commands.push('done')
|
|
30
|
+
// TODO
|
|
31
|
+
// - only load index files
|
|
32
|
+
// - for each command id
|
|
33
|
+
// - load files as raw bytes
|
|
34
|
+
// - index.id.*.jsontag and index.offset.*.jsontag to create proxies that will get the correct offset on access
|
|
35
|
+
// - do the same for resultSet[0] - the dataspace root entity
|
|
36
|
+
// don't parse entire files with od-jsontag
|
|
37
|
+
// add version info in proxies with a symbol to get that information
|
|
24
38
|
do {
|
|
25
39
|
if (fs.existsSync(datafile)) {
|
|
26
40
|
jsontag = fs.readFileSync(datafile)
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import pointer from 'json-pointer'
|
|
2
1
|
import {VM} from 'vm2'
|
|
3
2
|
import { memoryUsage } from 'node:process'
|
|
4
3
|
import JSONTag from '@muze-nl/jsontag'
|
|
5
4
|
import {source} from '@muze-nl/od-jsontag/src/symbols.mjs'
|
|
6
5
|
import Parser from '@muze-nl/od-jsontag/src/parse.mjs'
|
|
7
|
-
import {_,from,not,anyOf,allOf,asc,desc,sum,count,avg,max,min,many,one,distinct} from '@muze-nl/jaqt'
|
|
6
|
+
import {_,from,not,anyOf,allOf,asc,desc,sum,count,avg,max,min,many,one,first,distinct} from '@muze-nl/jaqt'
|
|
8
7
|
import process from 'node:process'
|
|
9
8
|
|
|
10
9
|
let dataspace
|
|
@@ -56,7 +55,7 @@ const tasks = {
|
|
|
56
55
|
return true
|
|
57
56
|
},
|
|
58
57
|
query: async (task) => {
|
|
59
|
-
return runQuery(task.req.path, task.req, task.req.body)
|
|
58
|
+
return runQuery(task.req.path, task.req, task.req.body, task.timeout)
|
|
60
59
|
},
|
|
61
60
|
memoryUsage: async () => {
|
|
62
61
|
let result = memoryUsage()
|
|
@@ -67,7 +66,7 @@ const tasks = {
|
|
|
67
66
|
|
|
68
67
|
export default tasks
|
|
69
68
|
|
|
70
|
-
export function runQuery(pointer, request, query) {
|
|
69
|
+
export function runQuery(pointer, request, query, timeout=1000) {
|
|
71
70
|
if (!pointer) { throw new Error('missing pointer parameter')}
|
|
72
71
|
if (!request) { throw new Error('missing request parameter')}
|
|
73
72
|
let response = {
|
|
@@ -79,7 +78,7 @@ export function runQuery(pointer, request, query) {
|
|
|
79
78
|
// @todo add text search: https://github.com/nextapps-de/flexsearch
|
|
80
79
|
// @todo replace VM with V8 isolate
|
|
81
80
|
const vm = new VM({
|
|
82
|
-
timeout:
|
|
81
|
+
timeout: timeout,
|
|
83
82
|
allowAsync: false,
|
|
84
83
|
sandbox: {
|
|
85
84
|
root: dataspace, //@TODO: if we don't pass the root, we can later shard
|
|
@@ -99,6 +98,7 @@ export function runQuery(pointer, request, query) {
|
|
|
99
98
|
min,
|
|
100
99
|
many,
|
|
101
100
|
one,
|
|
101
|
+
first,
|
|
102
102
|
distinct,
|
|
103
103
|
// console: connectConsole(res),
|
|
104
104
|
JSONTag,
|
|
@@ -171,24 +171,18 @@ function parseAllObjects(o, reset=true) {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
export function getDataSpace(path, dataspace) {
|
|
174
|
-
if (path.substring(path.length-1)
|
|
175
|
-
//jsonpointer doesn't allow a trailing '/'
|
|
174
|
+
if (path.substring(path.length-1)=='/') {
|
|
176
175
|
path = path.substring(0, path.length-1)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
result = pointer.get(dataspace, path)
|
|
184
|
-
} else {
|
|
185
|
-
result = JSONTag.parse('<object class="Error">{"message":"Not found", "code":404}')
|
|
186
|
-
}
|
|
187
|
-
} catch(err) {
|
|
188
|
-
result = JSONTag.parse('<object class="Error">{"message":'+JSON.stringify(err.message)+', "code":500}')
|
|
176
|
+
}
|
|
177
|
+
const pointer = path.split('/')
|
|
178
|
+
let result = dataspace
|
|
179
|
+
for (const part of pointer) {
|
|
180
|
+
if (part && result) {
|
|
181
|
+
result = result[part]
|
|
189
182
|
}
|
|
190
|
-
}
|
|
191
|
-
|
|
183
|
+
}
|
|
184
|
+
if (result===undefined) {
|
|
185
|
+
result = JSONTag.parse(`<object class="Error">{"message":"Path Not found", "code":404, "path":"${path}"}`)
|
|
192
186
|
}
|
|
193
187
|
return [result,path]
|
|
194
188
|
}
|
package/src/server.mjs
CHANGED
|
@@ -27,11 +27,14 @@ async function main(options) {
|
|
|
27
27
|
const loadWorker = options.loadWorker || __dirname+'/src/load-worker.mjs'
|
|
28
28
|
const commandWorker = options.commandWorker || __dirname+'/src/command-worker.mjs'
|
|
29
29
|
const commandsFile = options.commandsFile || __dirname+'/src/commands.mjs'
|
|
30
|
+
const indexFile = options.indexFile || __dirname+'/src/index.mjs'
|
|
30
31
|
const commandLog = options.commandLog || './command-log.jsontag'
|
|
31
32
|
const commandStatus = options.commandStatus || './command-status.jsontag'
|
|
32
33
|
const access = options.access || null
|
|
34
|
+
const timeout = options.timeout || 1000
|
|
35
|
+
const slowTimeout = options.slowTimeout || 10000
|
|
33
36
|
|
|
34
|
-
server.
|
|
37
|
+
server.get('/', serveHomepage)
|
|
35
38
|
|
|
36
39
|
// allow access to raw body, used to parse a query send as post body
|
|
37
40
|
server.use(express.raw({
|
|
@@ -59,17 +62,33 @@ async function main(options) {
|
|
|
59
62
|
body: jsontagBuffers,
|
|
60
63
|
meta,
|
|
61
64
|
access
|
|
62
|
-
}
|
|
65
|
+
},
|
|
66
|
+
timeout
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
const slowQueryWorkerInitTask = () => {
|
|
71
|
+
let result = queryWorkerInitTask()
|
|
72
|
+
result.timeout = slowTimeout
|
|
73
|
+
return result
|
|
74
|
+
}
|
|
75
|
+
|
|
66
76
|
let queryWorkerPool = new WorkerPool(maxWorkers, queryWorker, queryWorkerInitTask())
|
|
77
|
+
let slowQueryWorkerPool = new WorkerPool(1, queryWorker, slowQueryWorkerInitTask())
|
|
67
78
|
let commandWorkerInstance
|
|
68
79
|
|
|
69
|
-
server.get('/query
|
|
70
|
-
server.
|
|
71
|
-
server.
|
|
72
|
-
server.get('/
|
|
80
|
+
server.get('/query/', (req,next) => handleGetQuery(req,next))
|
|
81
|
+
server.get('/query/*remainder', (req,next) => handleGetQuery(req,next))
|
|
82
|
+
server.get('/slowquery/', (req,next) => handleSlowGetQuery(req,next))
|
|
83
|
+
server.get('/slowquery/*remainder', (req,next) => handleSlowGetQuery(req,next))
|
|
84
|
+
server.post('/query/', (req,next) => handlePostQuery(req,next))
|
|
85
|
+
server.post('/query/*remainder', (req,next) => handlePostQuery(req,next))
|
|
86
|
+
server.post('/slowquery/', (req,next) => handleSlowPostQuery(req,next))
|
|
87
|
+
server.post('/slowquery/*remainder', (req,next) => handleSlowPostQuery(req,next))
|
|
88
|
+
server.post('/command', (req,next) => handlePostCommand(req,next))
|
|
89
|
+
server.get('/command/:id', (req,next) => handleGetCommand(req,next))
|
|
90
|
+
|
|
91
|
+
server.use(express.static(wwwroot))
|
|
73
92
|
|
|
74
93
|
try {
|
|
75
94
|
await fetch(`http://localhost:${port}`, {
|
|
@@ -95,6 +114,11 @@ async function main(options) {
|
|
|
95
114
|
|
|
96
115
|
/* ------ */
|
|
97
116
|
|
|
117
|
+
function serveHomepage(req, res) {
|
|
118
|
+
res.setHeader('content-type', 'text/html');
|
|
119
|
+
res.send(fs.readFileSync(wwwroot+'/home.html'))
|
|
120
|
+
}
|
|
121
|
+
|
|
98
122
|
function loadCommandStatus(commandStatusFile) {
|
|
99
123
|
let status = new Map()
|
|
100
124
|
if (fs.existsSync(commandStatusFile)) {
|
|
@@ -134,6 +158,7 @@ async function main(options) {
|
|
|
134
158
|
meta,
|
|
135
159
|
data:jsontagBuffers,
|
|
136
160
|
commandsFile,
|
|
161
|
+
indexFile,
|
|
137
162
|
datafile
|
|
138
163
|
})
|
|
139
164
|
break;
|
|
@@ -158,12 +183,15 @@ async function main(options) {
|
|
|
158
183
|
reject(error)
|
|
159
184
|
worker.terminate()
|
|
160
185
|
})
|
|
161
|
-
worker.postMessage({dataFile:datafile,schemaFile,commands})
|
|
186
|
+
worker.postMessage({dataFile:datafile,indexFile,schemaFile,commands})
|
|
162
187
|
})
|
|
163
188
|
}
|
|
164
189
|
|
|
165
|
-
async function handleGetQuery(req, res) {
|
|
190
|
+
async function handleGetQuery(req, res, pool=null) {
|
|
166
191
|
let start = Date.now()
|
|
192
|
+
if (!pool) {
|
|
193
|
+
pool = queryWorkerPool
|
|
194
|
+
}
|
|
167
195
|
if ( !accept(req,res,
|
|
168
196
|
['application/jsontag','application/json','text/html','text/javascript','image/*'],
|
|
169
197
|
function(req, res, accept) {
|
|
@@ -182,7 +210,7 @@ async function main(options) {
|
|
|
182
210
|
// done
|
|
183
211
|
return
|
|
184
212
|
}
|
|
185
|
-
let path = req.
|
|
213
|
+
let path = req.params.remainder?.join('/') || '/'
|
|
186
214
|
console.log('query',path)
|
|
187
215
|
let request = {
|
|
188
216
|
method: req.method,
|
|
@@ -194,7 +222,7 @@ async function main(options) {
|
|
|
194
222
|
request.jsontag = true
|
|
195
223
|
}
|
|
196
224
|
try {
|
|
197
|
-
let result = await
|
|
225
|
+
let result = await pool.run('query', request, { timeout })
|
|
198
226
|
sendResponse(result, res)
|
|
199
227
|
} catch(error) {
|
|
200
228
|
sendError(error, res)
|
|
@@ -203,7 +231,17 @@ async function main(options) {
|
|
|
203
231
|
console.log(path, (end-start), process.memoryUsage())
|
|
204
232
|
}
|
|
205
233
|
|
|
206
|
-
async function
|
|
234
|
+
async function handleSlowGetQuery(req, res) {
|
|
235
|
+
return handleGetQuery(req, res, slowQueryWorkerPool)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function handlePostQuery(req,res,pool=null, slowTimeout=null) {
|
|
239
|
+
if (!pool) {
|
|
240
|
+
pool = queryWorkerPool
|
|
241
|
+
}
|
|
242
|
+
if (!slowTimeout) {
|
|
243
|
+
slowTimeout = timeout
|
|
244
|
+
}
|
|
207
245
|
let start = Date.now()
|
|
208
246
|
if ( !accept(req,res,
|
|
209
247
|
['application/jsontag','application/json'])
|
|
@@ -211,7 +249,7 @@ async function main(options) {
|
|
|
211
249
|
sendError({code:406, message:'Not Acceptable',accept:['application/json','application/jsontag']},res)
|
|
212
250
|
return
|
|
213
251
|
}
|
|
214
|
-
let path = req.
|
|
252
|
+
let path = req.params.remainder?.join('/') || '/'
|
|
215
253
|
let request = {
|
|
216
254
|
method: req.method,
|
|
217
255
|
url: req.originalUrl,
|
|
@@ -223,7 +261,7 @@ async function main(options) {
|
|
|
223
261
|
request.jsontag = true
|
|
224
262
|
}
|
|
225
263
|
try {
|
|
226
|
-
let result = await
|
|
264
|
+
let result = await pool.run('query', request, {slowTimeout})
|
|
227
265
|
sendResponse(result, res)
|
|
228
266
|
} catch(error) {
|
|
229
267
|
sendError(error, res)
|
|
@@ -233,6 +271,10 @@ async function main(options) {
|
|
|
233
271
|
// queryWorkerPool.memoryUsage()
|
|
234
272
|
}
|
|
235
273
|
|
|
274
|
+
async function handleSlowPostQuery(req, res) {
|
|
275
|
+
return handlePostQuery(req, res, slowQueryWorkerPool, slowTimeout)
|
|
276
|
+
}
|
|
277
|
+
|
|
236
278
|
async function handlePostCommand(req, res) {
|
|
237
279
|
let commandId = checkCommand(req, res)
|
|
238
280
|
if (!commandId) {
|
|
@@ -253,6 +295,7 @@ async function main(options) {
|
|
|
253
295
|
meta,
|
|
254
296
|
data:jsontagBuffers,
|
|
255
297
|
commandsFile,
|
|
298
|
+
indexFile,
|
|
256
299
|
datafile
|
|
257
300
|
})
|
|
258
301
|
let result
|
|
@@ -326,13 +369,15 @@ async function main(options) {
|
|
|
326
369
|
if (data.data) { // data has changed, commands may do other things instead of changing data
|
|
327
370
|
jsontagBuffers.push(data.data) // push changeset to jsontagBuffers so that new query workers get all changes from scratch
|
|
328
371
|
Object.assign(meta, data.meta)
|
|
329
|
-
|
|
372
|
+
const updateTask = {
|
|
330
373
|
name: 'update',
|
|
331
374
|
req: {
|
|
332
375
|
body: jsontagBuffers[jsontagBuffers.length-1], // only add the last change, update tasks for earlier changes have already been sent
|
|
333
376
|
meta
|
|
334
377
|
}
|
|
335
|
-
}
|
|
378
|
+
}
|
|
379
|
+
queryWorkerPool.update(updateTask)
|
|
380
|
+
slowQueryWorkerPool.update(updateTask)
|
|
336
381
|
}
|
|
337
382
|
appendFile(commandStatus, JSONTag.stringify(Object.assign({command:command.id}, s)))
|
|
338
383
|
mainResolve(s)
|
package/src/workerPool.mjs
CHANGED
|
@@ -68,9 +68,9 @@ export default class WorkerPool extends EventEmitter {
|
|
|
68
68
|
worker.postMessage(this.initTask);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
async run(name, req) {
|
|
71
|
+
async run(name, req, options={}) {
|
|
72
72
|
return new Promise((resolve, reject) => {
|
|
73
|
-
this.runTask({name,req}, (error, result) => {
|
|
73
|
+
this.runTask({name,req,...options}, (error, result) => {
|
|
74
74
|
if (error) {
|
|
75
75
|
return reject(error)
|
|
76
76
|
}
|
package/www/home.html
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<meta charset="utf-8">
|
|
3
|
+
<link rel="stylesheet" href="/assets/css/style.css">
|
|
4
|
+
<title>SimplyStore</title>
|
|
5
|
+
<body>
|
|
6
|
+
<h1>SimplyStore</h1>
|
|
7
|
+
<h2>In memory database and API</h2>
|
|
8
|
+
<p>This is a placeholder page. To run queries go to <a href="query/">the <code>/query</code> endpoint</a></p>
|
|
9
|
+
<p>You can override this placeholder page by adding this to your SimplyStore server code:</p>
|
|
10
|
+
<pre>
|
|
11
|
+
import SimplyStore from '@muze-nl/simplystore'
|
|
12
|
+
|
|
13
|
+
SimplyStore.get('/', function(req, res) {
|
|
14
|
+
res.setHeader('Content-Type', 'text/html')
|
|
15
|
+
res.send('Your homepage content')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
SimplyStore.run()
|
|
19
|
+
</pre>
|
|
20
|
+
</body>
|
|
21
|
+
|