@muze-nl/simplystore 0.1.2 → 0.1.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muze-nl/simplystore",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "main": "src/server.mjs",
5
5
  "type": "module",
6
6
  "scripts": {
package/package.json~ CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "@muze-nl/SimplyStore",
3
- "version": "0.1.2",
2
+ "name": "@muze-nl/simplystore",
3
+ "version": "0.1.3",
4
4
  "main": "src/server.mjs",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/server.mjs CHANGED
@@ -8,314 +8,316 @@ import _ from 'array-where-select'
8
8
  import { fileURLToPath } from 'url'
9
9
  import path from 'path'
10
10
 
11
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
11
+ const __dirname = path.dirname(path.dirname(fileURLToPath(import.meta.url)))
12
+
12
13
  const server = express()
13
14
 
14
15
  function deepFreeze(obj) {
15
- Object.freeze(obj)
16
- Object.keys(obj).forEach(prop => {
17
- if (typeof obj[prop] === 'object' && !Object.isFrozen(obj[prop])) {
18
- deepFreeze(obj[prop])
19
- }
20
- })
21
- return 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
22
23
  }
23
24
 
24
25
  function isString(s)
25
26
  {
26
- return typeof s === 'string' || s instanceof String
27
+ return typeof s === 'string' || s instanceof String
27
28
  }
28
29
 
29
30
  function joinArgs(args) {
30
- return args = args.map(arg => {
31
- if (isString(arg)) {
32
- return arg
33
- } else {
34
- return JSONTag.stringify(arg)
35
- }
36
- }).join(' ')
31
+ return args = args.map(arg => {
32
+ if (isString(arg)) {
33
+ return arg
34
+ } else {
35
+ return JSONTag.stringify(arg)
36
+ }
37
+ }).join(' ')
37
38
  }
38
39
 
39
40
  function connectConsole(res) {
40
- return {
41
- log: function(...args) {
42
- res.append('X-Console-Log', joinArgs(args))
43
- },
44
- warning: function(...args) {
45
- res.append('X-Console-Warning', joinArgs(args))
46
- },
47
- error: function(...args) {
48
- res.append('X-Console-Error', joinArgs(args))
49
- }
50
- }
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
+ }
51
52
  }
52
53
 
53
54
  async function main(options) {
54
- if (!options) {
55
- options = {}
56
- }
57
- const port = options.port || 3000;
58
- const datafile = options.datafile || 'data.jsontag'
59
- const wwwroot = options.wwwroot || __dirname+'../www'
55
+ if (!options) {
56
+ options = {}
57
+ }
58
+ const port = options.port || 3000
59
+ const datafile = options.datafile || 'data.jsontag'
60
+ const wwwroot = options.wwwroot || __dirname+'/www'
61
+ let meta = options.meta || {}
62
+ let dataspace = options.dataspace || null
60
63
 
61
- const originalJSON = JSON
62
- JSON = JSONTag // monkeypatching
64
+ const originalJSON = JSON
65
+ JSON = JSONTag // monkeypatching
63
66
 
64
- console.log('loading data...')
65
- let file = fs.readFileSync(datafile)
66
- let dataspace;
67
- let meta = {}
68
- try {
69
- dataspace = JSONTag.parse(file.toString(), null, meta)
70
- if (typeof options.index == 'function') {
71
- options.index(dataspace, meta)
67
+ if (!dataspace) {
68
+ console.log('loading data...')
69
+ let file = fs.readFileSync(datafile)
70
+ try {
71
+ dataspace = JSONTag.parse(file.toString(), null, meta)
72
+ } catch(e) {
73
+ console.error(e)
74
+ process.exit()
75
+ }
72
76
  }
73
- deepFreeze(dataspace)
74
- } catch(e) {
75
- console.error(e)
76
- process.exit()
77
- }
77
+ deepFreeze(dataspace)
78
78
 
79
- let used = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
80
- console.log(`data loaded (${used} MB)`);
79
+ let used = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
80
+ console.log(`data loaded (${used} MB)`);
81
81
 
82
- server.get('/', (req,res) => {
83
- res.send('<h1SimplyStore</h1>') //TODO: implement something nice
84
- })
82
+ server.get('/', (req,res) => {
83
+ res.send('<h1>SimplyStore</h1>') //TODO: implement something nice
84
+ })
85
85
 
86
- server.use(express.static(wwwroot))
86
+ server.use(express.static(wwwroot))
87
87
 
88
- // allow access to raw body, used to parse a query send as post body
89
- server.use(express.raw({
90
- type: (req) => true // parse body on all requests
91
- }))
88
+ // allow access to raw body, used to parse a query send as post body
89
+ server.use(express.raw({
90
+ type: (req) => true // parse body on all requests
91
+ }))
92
92
 
93
- function accept(req, res, mimetypes, handler) {
94
- let accept = req.accepts(mimetypes)
95
- if (!accept) {
96
- res.status(406)
97
- res.send("<h1>406 Unacceptable</h1>\n")
98
- return false
99
- }
100
- if (typeof handler === 'function') {
101
- return handler(req, res, accept)
102
- }
103
- return true
104
- }
93
+ function accept(req, res, mimetypes, handler) {
94
+ let accept = req.accepts(mimetypes)
95
+ if (!accept) {
96
+ res.status(406)
97
+ res.send("<h1>406 Unacceptable</h1>\n")
98
+ return false
99
+ }
100
+ if (typeof handler === 'function') {
101
+ return handler(req, res, accept)
102
+ }
103
+ return true
104
+ }
105
105
 
106
- function getDataSpace(req, res, dataspace) {
107
- let path = req.path.substr(6); // cut '/query'
108
- if (!path) {
109
- path = '';
110
- }
111
- if (path.substring(path.length-1)==='/') {
112
- //jsonpointer doesn't allow a trailing '/'
113
- path = path.substring(0, path.length-1)
114
- }
115
- let result
116
- if (path) {
117
- //jsonpointer doesn't allow an empty pointer
118
- try {
119
- if (pointer.has(dataspace, path)) {
120
- result = pointer.get(dataspace, path)
121
- } else {
122
- result = JSONTag.parse('<object class="Error">{"message":"Not found", "code":404}')
123
- }
124
- } catch(err) {
125
- result = JSONTag.parse('<object class="Error">{"message":'+originalJSON.stringify(err.message)+', "code":500}')
126
- }
127
- } else {
128
- result = dataspace
129
- }
130
- return [result,path]
131
- }
132
-
133
- let seen = new Map();
134
- function countObjects(obj) {
135
- if (seen.has(obj)) {
136
- return 0
137
- }
138
- seen.set(obj, true)
139
- let count = 0
140
- let values = []
141
- if (Array.isArray(obj)) {
142
- values = obj
143
- count++
144
- } else if (typeof obj === 'object') {
145
- if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
146
- // console.log('skipped', obj, typeof obj)
147
- } else {
148
- values = Object.values(obj)
149
- count++
150
- }
151
- } else {
152
- // console.log('skipped', obj, typeof obj)
153
- }
154
- return values
155
- .filter((o) => typeof o === 'object')
156
- .reduce((count, o) => count + countObjects(o), count)
157
- }
106
+ function getDataSpace(req, res, dataspace) {
107
+ let path = req.path.substr(6); // cut '/query'
108
+ if (!path) {
109
+ path = '';
110
+ }
111
+ if (path.substring(path.length-1)==='/') {
112
+ //jsonpointer doesn't allow a trailing '/'
113
+ path = path.substring(0, path.length-1)
114
+ }
115
+ let result
116
+ if (path) {
117
+ //jsonpointer doesn't allow an empty pointer
118
+ try {
119
+ if (pointer.has(dataspace, path)) {
120
+ result = pointer.get(dataspace, path)
121
+ } else {
122
+ result = JSONTag.parse('<object class="Error">{"message":"Not found", "code":404}')
123
+ }
124
+ } catch(err) {
125
+ result = JSONTag.parse('<object class="Error">{"message":'+originalJSON.stringify(err.message)+', "code":500}')
126
+ }
127
+ } else {
128
+ result = dataspace
129
+ }
130
+ return [result,path]
131
+ }
132
+
133
+ let seen = new Map();
134
+ function countObjects(obj) {
135
+ if (seen.has(obj)) {
136
+ return 0
137
+ }
138
+ seen.set(obj, true)
139
+ let count = 0
140
+ let values = []
141
+ if (Array.isArray(obj)) {
142
+ values = obj
143
+ count++
144
+ } else if (typeof obj === 'object') {
145
+ if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
146
+ // console.log('skipped', obj, typeof obj)
147
+ } else {
148
+ values = Object.values(obj)
149
+ count++
150
+ }
151
+ } else {
152
+ // console.log('skipped', obj, typeof obj)
153
+ }
154
+ return values
155
+ .filter((o) => typeof o === 'object')
156
+ .reduce((count, o) => count + countObjects(o), count)
157
+ }
158
158
 
159
- server.get('/status/', (req, res, next) =>
160
- {
161
- seen = new Map()
162
- let result = {
163
- memory: Math.round(process.memoryUsage().heapUsed / 1024 / 1024)+'MB',
164
- datasets: Object.keys(dataspace),
165
- objects: countObjects(dataspace)
166
- }
167
- res.setHeader('content-type','application/json')
168
- res.send(originalJSON.stringify(result, null, 4)+"\n")
169
- })
159
+ server.get('/status/', (req, res, next) =>
160
+ {
161
+ seen = new Map()
162
+ let result = {
163
+ memory: Math.round(process.memoryUsage().heapUsed / 1024 / 1024)+'MB',
164
+ datasets: Object.keys(dataspace),
165
+ objects: countObjects(dataspace)
166
+ }
167
+ res.setHeader('content-type','application/json')
168
+ res.send(originalJSON.stringify(result, null, 4)+"\n")
169
+ })
170
170
 
171
- server.get('/query/*', (req, res, next) =>
172
- {
173
- let start = Date.now()
171
+ server.get('/query/*', (req, res, next) =>
172
+ {
173
+ let start = Date.now()
174
174
 
175
- if ( !accept(req,res,
176
- ['application/jsontag','application/json','text/html','text/javascript','image/*'],
177
- function(req, res, accept) {
178
- switch(accept) {
179
- case 'text/html':
180
- case 'image/*':
181
- case 'text/javascript':
182
- handleWebRequest(req,res,options);
183
- return false
184
- break
185
- }
186
- return true
187
- }
188
- )) {
189
- // done
190
- return
191
- }
175
+ if ( !accept(req,res,
176
+ ['application/jsontag','application/json','text/html','text/javascript','image/*'],
177
+ function(req, res, accept) {
178
+ switch(accept) {
179
+ case 'text/html':
180
+ case 'image/*':
181
+ case 'text/javascript':
182
+ handleWebRequest(req,res,options);
183
+ return false
184
+ break
185
+ }
186
+ return true
187
+ }
188
+ )) {
189
+ // done
190
+ return
191
+ }
192
192
 
193
- let [result,path] = getDataSpace(req, res, dataspace)
194
- if (JSONTag.getAttribute(result, 'class')==='Error') {
195
- res.status(result.code)
196
- }
197
- result = linkReplacer(result, path+'/')
198
- if (req.accepts('application/jsontag')) {
199
- res.setHeader('content-type','application/jsontag+json')
200
- res.send(JSONTag.stringify(result, null, 4)+"\n")
201
- } else {
202
- res.setHeader('content-type','application/json')
203
- res.send(originalJSON.stringify(result, null, 4)+"\n")
204
- }
205
- let end = Date.now()
206
- console.log(path, (end-start), process.memoryUsage())
207
- })
193
+ let [result,path] = getDataSpace(req, res, dataspace)
194
+ if (JSONTag.getAttribute(result, 'class')==='Error') {
195
+ res.status(result.code)
196
+ }
197
+ result = linkReplacer(result, path+'/')
198
+ if (req.accepts('application/jsontag')) {
199
+ res.setHeader('content-type','application/jsontag+json')
200
+ res.send(JSONTag.stringify(result, null, 4)+"\n")
201
+ } else {
202
+ res.setHeader('content-type','application/json')
203
+ res.send(originalJSON.stringify(result, null, 4)+"\n")
204
+ }
205
+ let end = Date.now()
206
+ console.log(path, (end-start), process.memoryUsage())
207
+ })
208
208
 
209
- /**
210
- * handle queries, query is the post body
211
- */
212
- server.post('/query/*', (req, res) => {
213
- let start = Date.now()
214
- if ( !accept(req,res,
215
- ['application/jsontag','application/json'])
216
- ) {
217
- return
218
- }
219
- let error
220
- let [result,path] = getDataSpace(req, res, dataspace)
221
- if (result) {
222
- // do the query here
223
- let query = req.body.toString() // raw body through express.raw()
224
- // @todo add text search: https://github.com/nextapps-de/flexsearch
225
- // @todo add tree walk map/reduce/find/filter style functions
226
- // @todo add arc tree dive function?
227
- const vm = new VM({
228
- // timeout: 1000,
229
- allowAsync: false,
230
- sandbox: {
231
- root: dataspace,
232
- data: result,
233
- meta: meta,
234
- _: _,
235
- console: connectConsole(res),
236
- JSONTag: JSONTag,
237
- request: req,
238
- Array: Array
239
- },
240
- wasm: false
241
- })
242
- try {
243
- result = vm.run(query)
244
- let used = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
245
- console.log(`(${used} MB)`);
246
- } catch(err) {
247
- console.log(err)
248
- error = JSONTag.parse('<object class="Error">{"message":'+originalJSON.stringify(''+err)+',"code":422}')
249
- }
250
- }
209
+ /**
210
+ * handle queries, query is the post body
211
+ */
212
+ server.post('/query/*', (req, res) => {
213
+ let start = Date.now()
214
+ if ( !accept(req,res,
215
+ ['application/jsontag','application/json'])
216
+ ) {
217
+ return
218
+ }
219
+ let error
220
+ let [result,path] = getDataSpace(req, res, dataspace)
221
+ if (result) {
222
+ // do the query here
223
+ let query = req.body.toString() // raw body through express.raw()
224
+ // @todo add text search: https://github.com/nextapps-de/flexsearch
225
+ // @todo add tree walk map/reduce/find/filter style functions
226
+ // @todo add arc tree dive function?
227
+ const vm = new VM({
228
+ // timeout: 1000,
229
+ allowAsync: false,
230
+ sandbox: {
231
+ root: dataspace,
232
+ data: result,
233
+ meta: meta,
234
+ _: _,
235
+ console: connectConsole(res),
236
+ JSONTag: JSONTag,
237
+ request: req,
238
+ Array: Array
239
+ },
240
+ wasm: false
241
+ })
242
+ try {
243
+ result = vm.run(query)
244
+ let used = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
245
+ console.log(`(${used} MB)`);
246
+ } catch(err) {
247
+ console.log(err)
248
+ error = JSONTag.parse('<object class="Error">{"message":'+originalJSON.stringify(''+err)+',"code":422}')
249
+ }
250
+ }
251
251
 
252
- if (error) {
253
- res.status(error.code)
254
- result = error
255
- }
256
- if (req.accepts('application/jsontag')) {
257
- res.setHeader('content-type','application/jsontag+json')
258
- res.send(JSONTag.stringify(result, null, 4)+"\n")
259
- } else {
260
- res.setHeader('content-type','application/json')
261
- res.send(originalJSON.stringify(result, null, 4)+"\n")
262
- }
263
- let end = Date.now()
264
- console.log(path, (end-start), process.memoryUsage())
265
- })
252
+ if (error) {
253
+ res.status(error.code)
254
+ result = error
255
+ }
256
+ if (req.accepts('application/jsontag')) {
257
+ res.setHeader('content-type','application/jsontag+json')
258
+ res.send(JSONTag.stringify(result, null, 4)+"\n")
259
+ } else {
260
+ res.setHeader('content-type','application/json')
261
+ res.send(originalJSON.stringify(result, null, 4)+"\n")
262
+ }
263
+ let end = Date.now()
264
+ console.log(path, (end-start), process.memoryUsage())
265
+ })
266
266
 
267
- function handleWebRequest(req,res,options)
268
- {
269
- let path = req.path;
270
- path = path.replace(/[^a-z0-9_\.\-\/]*/gi, '') // whitelist acceptable file paths
271
- path = path.replace(/\.+/g, '.') // blacklist '..'
272
- if (!path) {
273
- path = '/'
274
- }
275
- if (path.substring(path.length-1)==='/') {
276
- path += 'index.html'
277
- }
278
- const fileOptions = {
279
- root: options.root || process.cwd()+'/www'
280
- }
281
- if (fs.existsSync(fileOptions.root+path)) {
282
- res.sendFile(path, fileOptions)
283
- } else {
284
- res.sendFile('/index.html', fileOptions)
285
- }
286
- }
287
267
 
288
- function linkReplacer(data, baseURL) {
289
- let type = JSONTag.getType(data)
290
- let attributes = JSONTag.getAttributes(data)
291
- if (Array.isArray(data)) {
292
- data = data.map((entry,index) => {
293
- return linkReplacer(data[index], baseURL+index+'/')
294
- })
295
- } else if (type === 'link') {
296
- // do nothing
297
- } else if (data && typeof data === 'object') {
298
- data = JSONTag.clone(data)
299
- Object.keys(data).forEach(key => {
300
- if (Array.isArray(data[key])) {
301
- data[key] = new JSONTag.Link(baseURL+key+'/')
302
- } else if (typeof data[key] === 'object') {
303
- if (JSONTag.getType(data[key])!=='link') {
304
- let id=JSONTag.getAttribute(data[key], 'id')
305
- if (!id) {
306
- id = baseURL+key+'/'
307
- }
308
- data[key] = new JSONTag.Link(id)
309
- }
310
- }
311
- })
312
- }
313
- return data
314
- }
315
268
 
316
- server.listen(port, () => {
317
- console.log('SimplyStore listening on port '+port)
318
- })
269
+ function handleWebRequest(req,res,options)
270
+ {
271
+ let path = req.path;
272
+ path = path.replace(/[^a-z0-9_\.\-\/]*/gi, '') // whitelist acceptable file paths
273
+ path = path.replace(/\.+/g, '.') // blacklist '..'
274
+ if (!path) {
275
+ path = '/'
276
+ }
277
+ if (path.substring(path.length-1)==='/') {
278
+ path += 'index.html'
279
+ }
280
+ const fileOptions = {
281
+ root: options.root || wwwroot
282
+ }
283
+ if (fs.existsSync(fileOptions.root+path)) {
284
+ res.sendFile(path, fileOptions)
285
+ } else {
286
+ res.sendFile('/index.html', fileOptions)
287
+ }
288
+ }
289
+
290
+ function linkReplacer(data, baseURL) {
291
+ let type = JSONTag.getType(data)
292
+ let attributes = JSONTag.getAttributes(data)
293
+ if (Array.isArray(data)) {
294
+ data = data.map((entry,index) => {
295
+ return linkReplacer(data[index], baseURL+index+'/')
296
+ })
297
+ } else if (type === 'link') {
298
+ // do nothing
299
+ } else if (data && typeof data === 'object') {
300
+ data = JSONTag.clone(data)
301
+ Object.keys(data).forEach(key => {
302
+ if (Array.isArray(data[key])) {
303
+ data[key] = new JSONTag.Link(baseURL+key+'/')
304
+ } else if (typeof data[key] === 'object') {
305
+ if (JSONTag.getType(data[key])!=='link') {
306
+ let id=JSONTag.getAttribute(data[key], 'id')
307
+ if (!id) {
308
+ id = baseURL+key+'/'
309
+ }
310
+ data[key] = new JSONTag.Link(id)
311
+ }
312
+ }
313
+ })
314
+ }
315
+ return data
316
+ }
317
+
318
+ server.listen(port, () => {
319
+ console.log('SimplyStore listening on port '+port)
320
+ })
319
321
 
320
322
  }
321
323