@muze-nl/simplystore 0.3.3 → 0.3.6

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 ADDED
@@ -0,0 +1,15 @@
1
+ module.exports = {
2
+ "env": {
3
+ "browser": true,
4
+ "es2021": true
5
+ },
6
+ "extends": "eslint:recommended",
7
+ "overrides": [
8
+ ],
9
+ "parserOptions": {
10
+ "ecmaVersion": "latest",
11
+ "sourceType": "module"
12
+ },
13
+ "rules": {
14
+ }
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muze-nl/simplystore",
3
- "version": "0.3.3",
3
+ "version": "0.3.6",
4
4
  "main": "src/server.mjs",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -22,10 +22,11 @@
22
22
  "express": "^4.18.1",
23
23
  "json-pointer": "^0.6.2",
24
24
  "jsonpath-plus": "^7.2.0",
25
- "threads": "^1.7.0",
25
+ "piscina": "^4.1.0",
26
26
  "vm2": "^3.9.13"
27
27
  },
28
28
  "devDependencies": {
29
+ "eslint": "^8.48.0",
29
30
  "process": "^0.11.10",
30
31
  "tap": "^16.3.8"
31
32
  }
package/src/commands.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  export default {
2
2
  addPerson: (dataspace, command) => {
3
+ console.log('dataspace',dataspace)
3
4
  dataspace.persons.push(command.value)
4
5
  // dataspace.persons[0].name = 'Jan';
5
6
  return 'foo'
package/src/server.mjs CHANGED
@@ -3,9 +3,9 @@ import fs from 'fs'
3
3
  import JSONTag from '@muze-nl/jsontag'
4
4
  import { fileURLToPath } from 'url'
5
5
  import path from 'path'
6
- import { spawn, Pool, Worker } from 'threads'
7
6
  import commands from './commands.mjs'
8
7
  import {appendFile} from './util.mjs'
8
+ import {Piscina} from 'piscina'
9
9
 
10
10
  const __dirname = path.dirname(path.dirname(fileURLToPath(import.meta.url)))
11
11
 
@@ -15,29 +15,32 @@ async function main(options) {
15
15
  if (!options) {
16
16
  options = {}
17
17
  }
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 || {}
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'
23
24
 
24
- let jsontag = fs.readFileSync(datafile, 'utf-8')
25
+ let jsontag = fs.readFileSync(datafile, 'utf-8')
25
26
 
26
27
  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)
28
+ let options = {
29
+ filename: workerName,
30
+ workerData: jsontag
31
+ }
32
+ if (size) {
33
+ options.maxThreads = size
34
+ }
35
+ return new Piscina(options)
33
36
  }
34
37
 
35
- let queryWorkerpool = initWorkerPool('./worker-query')
38
+ let queryWorkerpool = initWorkerPool(queryWorker)
36
39
 
37
- const commandWorkerpool = initWorkerPool('./worker-command',1) // only one update worker so no changes can get lost
40
+ const commandWorkerpool = initWorkerPool(commandWorker,1) // only one update worker so no changes can get lost
38
41
 
39
42
  server.get('/', (req,res) => {
40
- res.send('<h1>SimplyStore</h1>') //TODO: implement something nice
43
+ res.send('<h1>SimplyStore</h1>') //@TODO: implement something nice
41
44
  })
42
45
 
43
46
  server.use(express.static(wwwroot))
@@ -114,9 +117,8 @@ async function main(options) {
114
117
  query: req.query,
115
118
  jsontag: req.accepts('application/jsontag')
116
119
  }
117
- queryWorkerpool.queue(queryWorker => {
118
- return queryWorker.runQuery(path, request)
119
- }).then(response => {
120
+ queryWorkerpool.run({pointer:path, request})
121
+ .then(response => {
120
122
  sendResponse(response, res)
121
123
  let end = Date.now()
122
124
  console.log(path, (end-start), process.memoryUsage())
@@ -141,9 +143,8 @@ async function main(options) {
141
143
  query: req.query,
142
144
  jsontag: req.accepts('application/jsontag')
143
145
  }
144
- queryWorkerpool.queue(queryWorker => {
145
- return queryWorker.runQuery(path, request, query)
146
- }).then(response => {
146
+ queryWorkerpool.run({pointer:path, request, query})
147
+ .then(response => {
147
148
  sendResponse(response, res)
148
149
  let end = Date.now()
149
150
  console.log(path, (end-start), process.memoryUsage())
@@ -215,7 +216,7 @@ async function main(options) {
215
216
  }
216
217
 
217
218
  commandWorkerpool
218
- .queue(commandWorker => commandWorker.runCommand(request, commandStr))
219
+ .run({request, commandStr})
219
220
  .then(response => {
220
221
  //@TODO store response status, if response.code => error
221
222
  if (!response.code) {
@@ -223,8 +224,8 @@ async function main(options) {
223
224
  let dataspace = JSONTag.parse(jsontag)
224
225
  //@TODO: make sure queryWorkerpool is only replaced after
225
226
  //workers are initialized, to prevent hickups if initialization takes a long time
226
- let newQueryWorkerpool = initWorkerPool('./worker-query')
227
- queryWorkerpool.terminate() // gracefully
227
+ let newQueryWorkerpool = initWorkerPool(queryWorker)
228
+ queryWorkerpool.destroy() // gracefully
228
229
  queryWorkerpool = newQueryWorkerpool
229
230
  //@TODO: write dataspace to disk
230
231
  status.set(command.id, 'done')
@@ -0,0 +1,12 @@
1
+ import * as commandWorker from './worker-command.mjs'
2
+ import worker_threads from 'node:worker_threads'
3
+ import JSONTag from '@muze-nl/jsontag'
4
+
5
+ async function initialize() {
6
+ let meta = {}
7
+ let dataspace = JSONTag.parse(worker_threads.workerData, null, meta)
8
+ await commandWorker.initialize(dataspace,meta)
9
+ return commandWorker.runCommand
10
+ }
11
+
12
+ 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, meta
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 async function initialize(jsontag, m) {
23
+ if (!jsontag) { throw new Error('missing jsontag parameter')}
24
+ dataspace = jsontag
25
+ meta = m
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()
@@ -0,0 +1,146 @@
1
+ import JSONTag from "@muze-nl/jsontag"
2
+ import pointer from 'json-pointer'
3
+ import {_,from,not,anyOf,allOf} from 'array-where-select'
4
+ import {deepFreeze} from './util.mjs'
5
+ import {VM} from 'vm2'
6
+
7
+ let dataspace, meta = {};
8
+
9
+ export function setDataspace(d, m) {
10
+ dataspace = d
11
+ if(m) {
12
+ meta = m
13
+ }
14
+ }
15
+
16
+ export function getDataSpace(path, dataspace) {
17
+ if (path.substring(path.length-1)==='/') {
18
+ //jsonpointer doesn't allow a trailing '/'
19
+ path = path.substring(0, path.length-1)
20
+ }
21
+ let result
22
+ if (path) {
23
+ //jsonpointer doesn't allow an empty pointer
24
+ try {
25
+ if (pointer.has(dataspace, path)) {
26
+ result = pointer.get(dataspace, path)
27
+ } else {
28
+ result = JSONTag.parse('<object class="Error">{"message":"Not found", "code":404}')
29
+ }
30
+ } catch(err) {
31
+ result = JSONTag.parse('<object class="Error">{"message":'+JSON.stringify(err.message)+', "code":500}')
32
+ }
33
+ } else {
34
+ result = dataspace
35
+ }
36
+ return [result,path]
37
+ }
38
+
39
+ export function linkReplacer(data, baseURL) {
40
+ let type = JSONTag.getType(data)
41
+ let attributes = JSONTag.getAttributes(data)
42
+ if (Array.isArray(data)) {
43
+ data = data.map((entry,index) => {
44
+ return linkReplacer(data[index], baseURL+index+'/')
45
+ })
46
+ } else if (type === 'link') {
47
+ // do nothing
48
+ } else if (data && typeof data === 'object') {
49
+ data = JSONTag.clone(data)
50
+ Object.keys(data).forEach(key => {
51
+ if (Array.isArray(data[key])) {
52
+ data[key] = new JSONTag.Link(baseURL+key+'/')
53
+ } else if (data[key] && typeof data[key] === 'object') {
54
+ if (JSONTag.getType(data[key])!=='link') {
55
+ let id=JSONTag.getAttribute(data[key], 'id')
56
+ if (!id) {
57
+ id = baseURL+key+'/'
58
+ }
59
+ data[key] = new JSONTag.Link(id)
60
+ }
61
+ }
62
+ })
63
+ }
64
+ return data
65
+ }
66
+
67
+ //@TODO: emit console events that server.mjs picks up
68
+ function connectConsole(res) {
69
+ return {
70
+ log: function(...args) {
71
+ // res.append('X-Console-Log', joinArgs(args))
72
+ },
73
+ warning: function(...args) {
74
+ // res.append('X-Console-Warning', joinArgs(args))
75
+ },
76
+ error: function(...args) {
77
+ // res.append('X-Console-Error', joinArgs(args))
78
+ }
79
+ }
80
+ }
81
+
82
+ export async function initialize(jsontag, m) {
83
+ if (!jsontag) { throw new Error('missing jsontag parameter')}
84
+ dataspace = jsontag
85
+ meta = m
86
+ deepFreeze(dataspace)
87
+ return true
88
+ }
89
+
90
+ export function runQuery({pointer, request, query}) {
91
+ if (!pointer) { throw new Error('missing pointer parameter')}
92
+ if (!request) { throw new Error('missing request parameter')}
93
+ console.log('query',pointer)
94
+ let response = {
95
+ jsontag: request.jsontag
96
+ }
97
+ let [result,path] = getDataSpace(pointer, dataspace)
98
+
99
+ if (query) {
100
+ // @todo add text search: https://github.com/nextapps-de/flexsearch
101
+ // @todo add tree walk map/reduce/find/filter style functions
102
+ // @todo add arc tree dive function?
103
+ // @todo replace VM with V8 isolate
104
+ const vm = new VM({
105
+ timeout: 1000,
106
+ allowAsync: false,
107
+ sandbox: {
108
+ root: dataspace, //@TODO: if we don't pass the root, we can later shard
109
+ data: result,
110
+ meta,
111
+ _,
112
+ from,
113
+ not,
114
+ anyOf,
115
+ allOf,
116
+ // console: connectConsole(res),
117
+ JSONTag,
118
+ request
119
+ },
120
+ wasm: false
121
+ })
122
+ try {
123
+ result = vm.run(query)
124
+ let used = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
125
+ console.log(`(${used} MB)`);
126
+ } catch(err) {
127
+ console.log(err)
128
+ response.code = 422;
129
+ if (request.jsontag) {
130
+ response.body = '<object class="Error">{"message":'+JSON.stringify(''+err)+',"code":422}'
131
+ } else {
132
+ response.body = JSON.stringify({message:err, code: 422})
133
+ }
134
+ }
135
+ } else {
136
+ result = linkReplacer(result, path+'/')
137
+ }
138
+ if (!response.code) {
139
+ if (response.jsontag) {
140
+ response.body = JSONTag.stringify(result)
141
+ } else {
142
+ response.body = JSON.stringify(result)
143
+ }
144
+ }
145
+ return response
146
+ }
package/.npmignore~ DELETED
@@ -1,8 +0,0 @@
1
- node_modules
2
- .git
3
- .gitmodules
4
- tests
5
- example
6
- design
7
- docs
8
- test-command.jsontag
package/package.json~ DELETED
@@ -1,32 +0,0 @@
1
- {
2
- "name": "@muze-nl/simplystore",
3
- "version": "0.3.2",
4
- "main": "src/server.mjs",
5
- "type": "module",
6
- "scripts": {
7
- "start": "node src/run.mjs",
8
- "test": "tap test/*.mjs"
9
- },
10
- "author": "auke@muze.nl",
11
- "license": "MIT",
12
- "repository": {
13
- "type": "git",
14
- "url": "git+https://github.com/simplyedit/simplystore.git"
15
- },
16
- "bugs": "https://github.com/simplyedit/simplystore/issues",
17
- "homepage": "https://github.com/simplyedit/simplystore#readme",
18
- "dependencies": {
19
- "@muze-nl/jsontag": "^0.8.5",
20
- "array-where-select": "^0.3.1",
21
- "codemirror": "^6.0.1",
22
- "express": "^4.18.1",
23
- "json-pointer": "^0.6.2",
24
- "jsonpath-plus": "^7.2.0",
25
- "threads": "^1.7.0",
26
- "vm2": "^3.9.13"
27
- },
28
- "devDependencies": {
29
- "process": "^0.11.10",
30
- "tap": "^16.3.8"
31
- }
32
- }
@@ -1,49 +0,0 @@
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)
@@ -1,148 +0,0 @@
1
- import {expose} from "threads/worker"
2
- import JSONTag from "@muze-nl/jsontag"
3
- import pointer from 'json-pointer'
4
- import {_,from,not,anyOf,allOf} from 'array-where-select'
5
- import {deepFreeze} from './util.mjs'
6
- import {VM} from 'vm2'
7
-
8
- /**
9
- * Query Worker for threads.js library
10
- * returns JSONTag strings, since otherwise JSON.stringify is used
11
- * and type+attribute data gets lost
12
- * perhaps return [result,err] instead, so errors can be detected?
13
- */
14
-
15
- let dataspace, meta = {};
16
-
17
- function getDataSpace(path, dataspace) {
18
- if (path.substring(path.length-1)==='/') {
19
- //jsonpointer doesn't allow a trailing '/'
20
- path = path.substring(0, path.length-1)
21
- }
22
- let result
23
- if (path) {
24
- //jsonpointer doesn't allow an empty pointer
25
- try {
26
- if (pointer.has(dataspace, path)) {
27
- result = pointer.get(dataspace, path)
28
- } else {
29
- result = JSONTag.parse('<object class="Error">{"message":"Not found", "code":404}')
30
- }
31
- } catch(err) {
32
- result = JSONTag.parse('<object class="Error">{"message":'+originalJSON.stringify(err.message)+', "code":500}')
33
- }
34
- } else {
35
- result = dataspace
36
- }
37
- return [result,path]
38
- }
39
-
40
- function linkReplacer(data, baseURL) {
41
- let type = JSONTag.getType(data)
42
- let attributes = JSONTag.getAttributes(data)
43
- if (Array.isArray(data)) {
44
- data = data.map((entry,index) => {
45
- return linkReplacer(data[index], baseURL+index+'/')
46
- })
47
- } else if (type === 'link') {
48
- // do nothing
49
- } else if (data && typeof data === 'object') {
50
- data = JSONTag.clone(data)
51
- Object.keys(data).forEach(key => {
52
- if (Array.isArray(data[key])) {
53
- data[key] = new JSONTag.Link(baseURL+key+'/')
54
- } else if (typeof data[key] === 'object') {
55
- if (JSONTag.getType(data[key])!=='link') {
56
- let id=JSONTag.getAttribute(data[key], 'id')
57
- if (!id) {
58
- id = baseURL+key+'/'
59
- }
60
- data[key] = new JSONTag.Link(id)
61
- }
62
- }
63
- })
64
- }
65
- return data
66
- }
67
-
68
- //@TODO: emit console events that server.mjs picks up
69
- function connectConsole(res) {
70
- return {
71
- log: function(...args) {
72
- // res.append('X-Console-Log', joinArgs(args))
73
- },
74
- warning: function(...args) {
75
- // res.append('X-Console-Warning', joinArgs(args))
76
- },
77
- error: function(...args) {
78
- // res.append('X-Console-Error', joinArgs(args))
79
- }
80
- }
81
- }
82
-
83
- const query = {
84
- initialize(jsontag) {
85
- if (!jsontag) { throw new Error('missing jsontag parameter')}
86
- dataspace = JSONTag.parse(jsontag, null, meta)
87
- deepFreeze(dataspace)
88
- console.log('initialized query worker thread')
89
- return true
90
- },
91
- runQuery(pointer, request, query=null) {
92
- if (!pointer) { throw new Error('missing pointer parameter')}
93
- if (!request) { throw new Error('missing request parameter')}
94
- console.log('query',pointer)
95
- let response = {
96
- jsontag: request.jsontag
97
- }
98
- let [result,path] = getDataSpace(pointer, dataspace)
99
-
100
- if (query) {
101
- // @todo add text search: https://github.com/nextapps-de/flexsearch
102
- // @todo add tree walk map/reduce/find/filter style functions
103
- // @todo add arc tree dive function?
104
- // @todo replace VM with V8 isolate
105
- const vm = new VM({
106
- timeout: 1000,
107
- allowAsync: false,
108
- sandbox: {
109
- root: dataspace, //@TODO: if we don't pass the root, we can later shard
110
- data: result,
111
- meta: meta,
112
- _: _,
113
- from: from,
114
- not: not,
115
- anyOf: anyOf,
116
- allOf: allOf,
117
- // console: connectConsole(res),
118
- JSONTag: JSONTag,
119
- request: request
120
- },
121
- wasm: false
122
- })
123
- try {
124
- result = vm.run(query)
125
- let used = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
126
- console.log(`(${used} MB)`);
127
- } catch(err) {
128
- console.log(err)
129
- response.code = 422;
130
- if (request.jsontag) {
131
- response.body = '<object class="Error">{"message":'+originalJSON.stringify(''+err)+',"code":422}'
132
- } else {
133
- response.body = JSON.stringify({message:err, code: 422})
134
- }
135
- }
136
- }
137
- if (!response.code) {
138
- if (response.jsontag) {
139
- response.body = JSONTag.stringify(linkReplacer(result, path+'/'))
140
- } else {
141
- response.body = JSON.stringify(result)
142
- }
143
- }
144
- return response
145
- }
146
- }
147
-
148
- expose(query)