@iobroker/db-objects-jsonl 4.0.0-alpha.8-20210909-001a711c → 4.0.3

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
@@ -4,4 +4,4 @@ The Library contains the JSONL Database classes for File based objects database
4
4
  ## License
5
5
  The MIT License (MIT)
6
6
 
7
- Copyright 2018-2021 bluefox <dogafox@gmail.com>
7
+ Copyright 2018-2022 bluefox <dogafox@gmail.com>
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Object DB in memory - Server
3
3
  *
4
- * Copyright 2013-2020 bluefox <dogafox@gmail.com>
4
+ * Copyright 2013-2022 bluefox <dogafox@gmail.com>
5
5
  *
6
6
  * MIT License
7
7
  *
@@ -13,49 +13,50 @@
13
13
  /* jshint -W061 */
14
14
  'use strict';
15
15
 
16
- const ObjectsInMemoryFileDB = require('@iobroker/db-objects-file').ObjectsInMemoryFileDB;
16
+ const ObjectsInMemoryFileDB = require('@iobroker/db-objects-file').ObjectsInMemoryFileDB;
17
17
  const { JsonlDB } = require('@alcalzone/jsonl-db');
18
18
  const path = require('path');
19
+ const fs = require('fs');
20
+ const os = require('os');
21
+ const { tools } = require('@iobroker/js-controller-common');
19
22
 
20
23
  /**
21
24
  * This class inherits InMemoryFileDB class and adds all relevant logic for objects
22
25
  * including the available methods for use by js-controller directly
23
26
  **/
24
27
  class ObjectsInMemoryJsonlDB extends ObjectsInMemoryFileDB {
25
-
26
28
  constructor(settings) {
27
29
  settings = settings || {};
28
30
  settings.fileDB = {
29
31
  fileName: 'objects.json',
30
32
  backupDirName: 'backup-objects'
31
33
  };
34
+ super(settings);
35
+
32
36
  /** @type {import("@alcalzone/jsonl-db").JsonlDBOptions<any>} */
33
- const jsonlOptions = {
37
+ const jsonlOptions = this.settings.connection.jsonlOptions || {
34
38
  autoCompress: {
35
39
  sizeFactor: 2,
36
- sizeFactorMinimumSize: 1000
40
+ sizeFactorMinimumSize: 25000
37
41
  },
38
42
  ignoreReadErrors: true,
39
43
  throttleFS: {
40
44
  intervalMs: 60000,
41
- maxBufferedCommands: 100
45
+ maxBufferedCommands: 1000
42
46
  }
43
47
  };
44
48
  settings.jsonlDB = {
45
- fileName: 'objects.jsonl',
46
- options: jsonlOptions
49
+ fileName: 'objects.jsonl'
47
50
  };
48
- super(settings);
49
51
 
50
52
  /** @type {JsonlDB<any>} */
51
- this._db = new JsonlDB(
52
- path.join(this.dataDir, settings.jsonlDB.fileName),
53
- jsonlOptions
54
- );
53
+ this._db = new JsonlDB(path.join(this.dataDir, settings.jsonlDB.fileName), jsonlOptions);
55
54
  }
56
55
 
57
56
  async open() {
58
- await this._db.open();
57
+ if (!(await this._maybeMigrateFileDB())) {
58
+ await this._db.open();
59
+ }
59
60
 
60
61
  // Create an object-like wrapper around the internal Map
61
62
  this.dataset = new Proxy(this._db, {
@@ -92,18 +93,128 @@ class ObjectsInMemoryJsonlDB extends ObjectsInMemoryFileDB {
92
93
  };
93
94
  }
94
95
  });
96
+
97
+ if (this.settings.backup && this.settings.backup.period && !this.settings.backup.disabled) {
98
+ this._backupInterval = setInterval(() => {
99
+ this.saveBackup();
100
+ }, this.settings.backup.period);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Checks if an existing file DB should be migrated to JSONL
106
+ * @returns {Promise<boolean>} true if the file DB was migrated. false if not.
107
+ * If this returns true, the jsonl DB was opened and doesn't need to be opened again.
108
+ */
109
+ async _maybeMigrateFileDB() {
110
+ const jsonlFileName = path.join(this.dataDir, this.settings.jsonlDB.fileName);
111
+ const jsonFileName = path.join(this.dataDir, this.settings.fileDB.fileName);
112
+ const bakFileName = path.join(this.dataDir, this.settings.fileDB.fileName + '.bak');
113
+
114
+ // Check the timestamps of each file, defaulting to 0 if they don't exist
115
+ let jsonlTimeStamp = 0;
116
+ let jsonTimeStamp = 0;
117
+ let bakTimeStamp = 0;
118
+ try {
119
+ const stat = fs.statSync(jsonlFileName);
120
+ if (stat.isFile()) {
121
+ jsonlTimeStamp = stat.mtime;
122
+ }
123
+ } catch {
124
+ // ignore
125
+ }
126
+ try {
127
+ const stat = fs.statSync(jsonFileName);
128
+ if (stat.isFile()) {
129
+ jsonTimeStamp = stat.mtime;
130
+ }
131
+ } catch {
132
+ // ignore
133
+ }
134
+ try {
135
+ const stat = fs.statSync(bakFileName);
136
+ if (stat.isFile()) {
137
+ bakTimeStamp = stat.mtime;
138
+ }
139
+ } catch {
140
+ // ignore
141
+ }
142
+
143
+ // Figure out which file needs to be imported
144
+ /** @type {string} */
145
+ let importFilename;
146
+ if (jsonTimeStamp > 0 && jsonTimeStamp >= bakTimeStamp && jsonTimeStamp >= jsonlTimeStamp) {
147
+ importFilename = jsonFileName;
148
+ } else if (bakTimeStamp > 0 && bakTimeStamp >= jsonTimeStamp && bakTimeStamp >= jsonlTimeStamp) {
149
+ importFilename = bakFileName;
150
+ } else {
151
+ // None of the File DB files are newer than the JSONL file
152
+ // There is nothing to restore, we're done
153
+ return false;
154
+ }
155
+
156
+ await this._db.open();
157
+ this._db.clear();
158
+ await this._db.importJson(importFilename);
159
+
160
+ // And rename the existing files to avoid redoing the work next time
161
+ if (fs.existsSync(jsonFileName)) {
162
+ try {
163
+ fs.renameSync(jsonFileName, `${jsonFileName}.migrated`);
164
+ } catch {
165
+ // ignore
166
+ }
167
+ }
168
+ if (fs.existsSync(bakFileName)) {
169
+ try {
170
+ fs.renameSync(bakFileName, `${bakFileName}.migrated`);
171
+ } catch {
172
+ // ignore
173
+ }
174
+ }
175
+
176
+ // Signal to the caller that the DB is already open
177
+ return true;
95
178
  }
96
179
 
97
180
  async saveState() {
98
181
  // Nothing to do, the DB saves behind the scenes
99
182
  }
100
183
 
184
+ // Is regularly called and stores a compressed backup of the DB
185
+ async saveBackup() {
186
+ const now = Date.now();
187
+ const tmpBackupFileName = path.join(os.tmpdir(), `${this.getTimeStr(now)}_${this.settings.jsonlDB.fileName}`);
188
+ const backupFileName = path.join(
189
+ this.backupDir,
190
+ `${this.getTimeStr(now)}_${this.settings.jsonlDB.fileName}.gz`
191
+ );
192
+ try {
193
+ if (fs.existsSync(backupFileName)) {
194
+ return;
195
+ }
196
+
197
+ // Create a DB dump
198
+ await this._db.dump(tmpBackupFileName);
199
+ // and zip it
200
+ await tools.compressFileGZip(tmpBackupFileName, backupFileName, { deleteInput: true });
201
+ // figure out if older files need to be deleted
202
+ this.deleteOldBackupFiles(this.settings.jsonlDB.fileName);
203
+ } catch (e) {
204
+ this.log.error(`${this.namespace} Cannot save backup ${backupFileName}: ${e.message}`);
205
+ }
206
+ }
207
+
101
208
  async destroy() {
209
+ await super.destroy();
210
+
102
211
  if (this._db) {
103
212
  await this._db.close();
104
213
  }
214
+ if (this._backupInterval) {
215
+ clearInterval(this._backupInterval);
216
+ }
105
217
  }
106
-
107
218
  }
108
219
 
109
220
  module.exports = ObjectsInMemoryJsonlDB;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * States DB in memory - Server with Redis protocol
3
3
  *
4
- * Copyright 2013-2020 bluefox <dogafox@gmail.com>
4
+ * Copyright 2013-2022 bluefox <dogafox@gmail.com>
5
5
  *
6
6
  * MIT License
7
7
  *
@@ -18,28 +18,12 @@ const ObjectsInRedisClient = require('@iobroker/db-objects-redis').Client;
18
18
  const ObjectsInMemServer = require('./objectsInMemServerRedis');
19
19
 
20
20
  class ObjectsInMemoryServerClass extends ObjectsInRedisClient {
21
-
22
21
  constructor(settings) {
23
22
  settings.autoConnect = false; // delay Client connection to when we need it
24
-
25
- // hack around testing problem where subscribe was called before connect
26
- // Should be removed for a later release
27
- const origConnected = settings.connected;
28
- settings.connected = () => {
29
- this.clientConnected = true;
30
- if (Array.isArray(this.storedSubscribes) && this.storedSubscribes.length) {
31
- this.log.warn(`${this.namespace} Replay ${this.storedSubscribes.length} subscription calls for Objects Server that were done before the client was connected initially`);
32
- this.storedSubscribes.forEach((s => this.subscribe(s.pattern, s.options, s.callback)));
33
- this.storedSubscribes = [];
34
- }
35
- origConnected();
36
- };
37
23
  super(settings);
38
- this.clientConnected = false;
39
- this.storedSubscribes = [];
40
24
 
41
25
  const serverSettings = {
42
- namespace: settings.namespace ? `${settings.namespace}-Server` : 'Server',
26
+ namespace: settings.namespace ? `${settings.namespace}-Server` : 'Server',
43
27
  connection: settings.connection,
44
28
  logger: settings.logger,
45
29
  hostname: settings.hostname,
@@ -66,13 +50,5 @@ class ObjectsInMemoryServerClass extends ObjectsInRedisClient {
66
50
  dirExists(id, name) {
67
51
  return this.objectsServer.dirExists(id, name);
68
52
  }
69
-
70
- subscribe(pattern, options, callback) {
71
- if (!this.clientConnected) {
72
- this.storedSubscribes.push({pattern, options, callback}); // we ignore the promise return because not used for this testing issue we work around here
73
- } else {
74
- return super.subscribe(pattern, options, callback);
75
- }
76
- }
77
53
  }
78
54
  module.exports = ObjectsInMemoryServerClass;
@@ -13,14 +13,14 @@
13
13
  /* jshint strict:false */
14
14
  /* jslint node: true */
15
15
  'use strict';
16
- const net = require('net');
17
- const fs = require('fs-extra');
18
- const path = require('path');
16
+ const net = require('net');
17
+ const fs = require('fs-extra');
18
+ const path = require('path');
19
19
  const crypto = require('crypto');
20
- const utils = require('@iobroker/db-objects-redis').objectsUtils;
21
- const tools = require('@iobroker/db-base').tools;
20
+ const utils = require('@iobroker/db-objects-redis').objectsUtils;
21
+ const tools = require('@iobroker/db-base').tools;
22
22
 
23
- const RedisHandler = require('@iobroker/db-base').redisHandler;
23
+ const RedisHandler = require('@iobroker/db-base').redisHandler;
24
24
  const ObjectsInMemoryJsonlDB = require('./objectsInMemJsonlDB');
25
25
 
26
26
  // settings = {
@@ -57,30 +57,48 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
57
57
  super(settings);
58
58
 
59
59
  this.serverConnections = {};
60
- this.namespaceObjects = (this.settings.redisNamespace || (settings.connection && settings.connection.redisNamespace) || 'cfg') + '.';
61
- this.namespaceFile = this.namespaceObjects + 'f.';
62
- this.namespaceObj = this.namespaceObjects + 'o.';
60
+ this.namespaceObjects =
61
+ (this.settings.redisNamespace || (settings.connection && settings.connection.redisNamespace) || 'cfg') +
62
+ '.';
63
+ this.namespaceFile = this.namespaceObjects + 'f.';
64
+ this.namespaceObj = this.namespaceObjects + 'o.';
65
+ this.namespaceSet = this.namespaceObjects + 's.';
66
+ this.namespaceSetLen = this.namespaceSet.length;
67
+
63
68
  // this.namespaceObjectsLen = this.namespaceObjects.length;
64
- this.namespaceFileLen = this.namespaceFile.length;
65
- this.namespaceObjLen = this.namespaceObj.length;
69
+ this.namespaceFileLen = this.namespaceFile.length;
70
+ this.namespaceObjLen = this.namespaceObj.length;
71
+ this.metaNamespace = (this.settings.metaNamespace || 'meta') + '.';
72
+ this.metaNamespaceLen = this.metaNamespace.length;
66
73
 
67
74
  this.knownScripts = {};
68
75
 
69
76
  this.normalizeFileRegex1 = new RegExp('^(.*)\\$%\\$(.*)\\$%\\$(meta|data)$');
70
77
  this.normalizeFileRegex2 = new RegExp('^(.*)\\$%\\$(.*)\\/?\\*$');
71
78
 
72
- this.open().then(() => {
73
- return this._initRedisServer(this.settings.connection);
74
- }).then(() => {
75
- this.log.debug(this.namespace + ' ' + (settings.secure ? 'Secure ' : '') + ' Redis inMem-objects listening on port ' + (settings.port || 9001));
79
+ this.open()
80
+ .then(() => {
81
+ return this._initRedisServer(this.settings.connection);
82
+ })
83
+ .then(() => {
84
+ this.log.debug(
85
+ this.namespace +
86
+ ' ' +
87
+ (settings.secure ? 'Secure ' : '') +
88
+ ' Redis inMem-objects listening on port ' +
89
+ (settings.port || 9001)
90
+ );
76
91
 
77
- if (typeof this.settings.connected === 'function') {
78
- setImmediate(() => this.settings.connected());
79
- }
80
- }).catch(e => {
81
- this.log.error(this.namespace + ' Cannot start inMem-objects on port ' + (settings.port || 9001) + ': ' + e.message);
82
- process.exit(24); // todo: replace it with exitcode
83
- });
92
+ if (typeof this.settings.connected === 'function') {
93
+ setImmediate(() => this.settings.connected());
94
+ }
95
+ })
96
+ .catch(e => {
97
+ this.log.error(
98
+ this.namespace + ' Cannot start inMem-objects on port ' + (settings.port || 9001) + ': ' + e.message
99
+ );
100
+ process.exit(24); // todo: replace it with exitcode
101
+ });
84
102
  }
85
103
 
86
104
  /**
@@ -98,7 +116,7 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
98
116
  if (Array.isArray(idWithNamespace)) {
99
117
  const ids = [];
100
118
  idWithNamespace.forEach(el => {
101
- const {id, namespace} = this._normalizeId(el);
119
+ const { id, namespace } = this._normalizeId(el);
102
120
  ids.push(id);
103
121
  ns = namespace; // we ignore the pot. case from arrays with different namespaces
104
122
  });
@@ -111,6 +129,8 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
111
129
  idx = this.namespaceObjLen;
112
130
  } else if (idWithNamespace.startsWith(this.namespaceFile)) {
113
131
  idx = this.namespaceFileLen;
132
+ } else if (idWithNamespace.startsWith(this.namespaceSet)) {
133
+ idx = this.namespaceSetLen;
114
134
  }
115
135
  if (idx !== -1) {
116
136
  ns = idWithNamespace.substr(0, idx);
@@ -121,7 +141,7 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
121
141
  if (fileIdDetails) {
122
142
  id = fileIdDetails[1];
123
143
  name = fileIdDetails[2] || '';
124
- isMeta = (fileIdDetails[3] === 'meta');
144
+ isMeta = fileIdDetails[3] === 'meta';
125
145
  } else {
126
146
  fileIdDetails = id.match(this.normalizeFileRegex2);
127
147
  if (fileIdDetails) {
@@ -134,9 +154,15 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
134
154
  }
135
155
  }
136
156
  }
157
+ } else if (idWithNamespace.startsWith(this.metaNamespace)) {
158
+ const idx = this.metaNamespaceLen;
159
+ if (idx !== -1) {
160
+ ns = idWithNamespace.substr(0, idx);
161
+ id = idWithNamespace.substr(idx);
162
+ }
137
163
  }
138
164
  }
139
- return {id, namespace: ns, name, isMeta};
165
+ return { id, namespace: ns, name, isMeta };
140
166
  }
141
167
 
142
168
  /**
@@ -155,11 +181,18 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
155
181
 
156
182
  const found = s.find(sub => sub.regex.test(id));
157
183
  if (found) {
158
- const objString = JSON.stringify(obj);
159
- this.log.silly(this.namespace + ' Redis Publish Object ' + id + '=' + objString);
160
- const sendPattern = (type === 'objects' ? '' : this.namespaceObjects) + found.pattern;
161
- const sendId = (type === 'objects' ? this.namespaceObj : this.namespaceObjects) + id;
162
- client.sendArray(null,['pmessage', sendPattern, sendId, objString]);
184
+ if (type === 'meta') {
185
+ this.log.silly(`${this.namespace} Redis Publish Meta ${id}=${obj}`);
186
+ const sendPattern = this.metaNamespace + found.pattern;
187
+ const sendId = this.metaNamespace + id;
188
+ client.sendArray(null, ['pmessage', sendPattern, sendId, obj]);
189
+ } else {
190
+ const objString = JSON.stringify(obj);
191
+ this.log.silly(this.namespace + ' Redis Publish Object ' + id + '=' + objString);
192
+ const sendPattern = (type === 'objects' ? '' : this.namespaceObjects) + found.pattern;
193
+ const sendId = (type === 'objects' ? this.namespaceObj : this.namespaceObjects) + id;
194
+ client.sendArray(null, ['pmessage', sendPattern, sendId, objString]);
195
+ }
163
196
  return 1;
164
197
  }
165
198
  return 0;
@@ -174,12 +207,11 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
174
207
  */
175
208
  getFileId(id, name, isMeta) {
176
209
  // e.g. ekey.admin and admin/ekey.png
177
- if (id.match(/\.admin$/)) {
178
- if (name.match(/^admin\//)) {
210
+ if (id.endsWith('.admin')) {
211
+ if (name.startsWith('admin/')) {
179
212
  name = name.replace(/^admin\//, '');
180
- } else
181
- // e.g. ekey.admin and iobroker.ekey/admin/ekey.png
182
- if (name.match(/^iobroker.[-\d\w]\/admin\//i)) {
213
+ } else if (name.match(/^iobroker.[-\d\w]\/admin\//i)) {
214
+ // e.g. ekey.admin and iobroker.ekey/admin/ekey.png
183
215
  name = name.replace(/^iobroker.[-\d\w]\/admin\//i, '');
184
216
  }
185
217
  }
@@ -243,22 +275,41 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
243
275
  search = scriptSearch[1];
244
276
  }
245
277
 
246
- this.knownScripts[scriptChecksum] = {design: design, search: search};
278
+ this.knownScripts[scriptChecksum] = { design: design, search: search };
247
279
  if (this.settings.connection.enhancedLogging) {
248
- this.log.silly(`${namespaceLog} Register View LUA Script: ${scriptChecksum} = ${JSON.stringify(this.knownScripts[scriptChecksum])}`);
280
+ this.log.silly(
281
+ `${namespaceLog} Register View LUA Script: ${scriptChecksum} = ${JSON.stringify(
282
+ this.knownScripts[scriptChecksum]
283
+ )}`
284
+ );
249
285
  }
250
286
  handler.sendBulk(responseId, scriptChecksum);
251
287
  } else if (scriptFunc && scriptFunc[1]) {
252
- this.knownScripts[scriptChecksum] = {func: scriptFunc[1]};
288
+ this.knownScripts[scriptChecksum] = { func: scriptFunc[1] };
253
289
  if (this.settings.connection.enhancedLogging) {
254
- this.log.silly(`${namespaceLog} Register Func LUA Script: ${scriptChecksum} = ${JSON.stringify(this.knownScripts[scriptChecksum])}`);
290
+ this.log.silly(
291
+ `${namespaceLog} Register Func LUA Script: ${scriptChecksum} = ${JSON.stringify(
292
+ this.knownScripts[scriptChecksum]
293
+ )}`
294
+ );
295
+ }
296
+ handler.sendBulk(responseId, scriptChecksum);
297
+ } else if (data[1].includes('-- REDLOCK SCRIPT')) {
298
+ // redlock scripts are currently not needed for Simulator
299
+ this.knownScripts[scriptChecksum] = { redlock: true };
300
+ if (this.settings.connection.enhancedLogging) {
301
+ this.log.silly(
302
+ `${namespaceLog} Register Func LUA Script: ${scriptChecksum} = ${JSON.stringify(
303
+ this.knownScripts[scriptChecksum]
304
+ )}`
305
+ );
255
306
  }
256
307
  handler.sendBulk(responseId, scriptChecksum);
257
308
  } else {
258
- handler.sendError(responseId, new Error('Unknown LUA script ' + data[0]));
309
+ handler.sendError(responseId, new Error(`Unknown LUA script ${data[1]}`));
259
310
  }
260
311
  } else {
261
- handler.sendError(responseId, new Error('Unsupported Script command ' + data[0]));
312
+ handler.sendError(responseId, new Error(`Unsupported Script command ${data[0]}`));
262
313
  }
263
314
  });
264
315
 
@@ -269,7 +320,7 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
269
320
  }
270
321
  if (this.knownScripts[data[0]].design) {
271
322
  const scriptDesign = this.knownScripts[data[0]].design;
272
- if (data[2] === this.namespaceObj && data.length > 4) {
323
+ if (typeof data[2] === 'string' && data[2].startsWith(this.namespaceObj) && data.length > 4) {
273
324
  let scriptSearch = this.knownScripts[data[0]].search;
274
325
  if (scriptDesign === 'system' && !scriptSearch && data[5]) {
275
326
  scriptSearch = data[5];
@@ -278,7 +329,9 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
278
329
  scriptSearch = 'state';
279
330
  }
280
331
  if (this.settings.connection.enhancedLogging) {
281
- this.log.silly(`${namespaceLog} Script transformed into getObjectView: design=${scriptDesign}, search=${scriptSearch}`);
332
+ this.log.silly(
333
+ `${namespaceLog} Script transformed into getObjectView: design=${scriptDesign}, search=${scriptSearch}`
334
+ );
282
335
  }
283
336
  let objs;
284
337
  try {
@@ -288,13 +341,17 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
288
341
  include_docs: true
289
342
  });
290
343
  } catch (err) {
291
- return void handler.sendError(responseId, new Error('_getObjectView Error for ' + scriptDesign + '/' + scriptSearch + ': ' + err.message));
344
+ return void handler.sendError(
345
+ responseId,
346
+ new Error(`_getObjectView Error for ${scriptDesign}/${scriptSearch}: ${err.message}`)
347
+ );
292
348
  }
349
+
293
350
  const res = objs.rows.map(obj => JSON.stringify(this.dataset[obj.value._id || obj.id]));
294
351
  handler.sendArray(responseId, res);
295
352
  }
296
353
  } else if (this.knownScripts[data[0]].func && data.length > 4) {
297
- const scriptFunc = {map: this.knownScripts[data[0]].func.replace('%1', data[5])};
354
+ const scriptFunc = { map: this.knownScripts[data[0]].func.replace('%1', data[5]) };
298
355
  if (this.settings.connection.enhancedLogging) {
299
356
  this.log.silly(`${namespaceLog} Script transformed into _applyView: func=${scriptFunc.map}`);
300
357
  }
@@ -306,16 +363,20 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
306
363
  const res = objs.rows.map(obj => JSON.stringify(this.dataset[obj.value._id || obj.id]));
307
364
 
308
365
  return void handler.sendArray(responseId, res);
366
+ } else if (this.knownScripts[data[0]].redlock) {
367
+ // just return a dummy
368
+ return void handler.sendArray(responseId, [0]);
309
369
  } else {
310
- handler.sendError(responseId, new Error('Unknown LUA script eval call ' + JSON.stringify(data)));
370
+ handler.sendError(responseId, new Error(`Unknown LUA script eval call ${JSON.stringify(data)}`));
311
371
  }
312
372
  });
313
373
 
314
374
  // Handle Redis "PUBLISH" request
315
375
  handler.on('publish', (data, responseId) => {
316
- const {id, namespace} = this._normalizeId(data[0]);
376
+ const { id, namespace } = this._normalizeId(data[0]);
317
377
 
318
- if (namespace === this.namespaceObj) { // a "set" always comes afterwards, so do not publish
378
+ if (namespace === this.namespaceObj || namespace === this.metaNamespace) {
379
+ // a "set" always comes afterwards, so do not publish
319
380
  return void handler.sendInteger(responseId, 0); // do not publish for now
320
381
  }
321
382
  const publishCount = this.publishAll(namespace.substr(0, namespace.length - 1), id, JSON.parse(data[1]));
@@ -327,15 +388,17 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
327
388
  if (!data || !data.length) {
328
389
  return void handler.sendArray(responseId, []);
329
390
  }
330
- const {namespace, isMeta} = this._normalizeId(data[0]);
391
+ const { namespace, isMeta } = this._normalizeId(data[0]);
331
392
 
332
393
  if (namespace === this.namespaceObj) {
333
394
  const keys = [];
334
395
  data.forEach(dataId => {
335
- const {id, namespace} = this._normalizeId(dataId);
396
+ const { id, namespace } = this._normalizeId(dataId);
336
397
  if (namespace !== this.namespaceObj) {
337
398
  keys.push(null);
338
- this.log.warn(`${namespaceLog} Got MGET request for non Object-ID in Objects-ID chunk for ${namespace} / ${dataId}`);
399
+ this.log.warn(
400
+ `${namespaceLog} Got MGET request for non Object-ID in Objects-ID chunk for ${namespace} / ${dataId}`
401
+ );
339
402
  return;
340
403
  }
341
404
  keys.push(id);
@@ -346,17 +409,19 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
346
409
  } catch (err) {
347
410
  return void handler.sendError(responseId, new Error('ERROR _getObjects: ' + err.message));
348
411
  }
349
- result = result.map(el => el ? JSON.stringify(el) : null);
412
+ result = result.map(el => (el ? JSON.stringify(el) : null));
350
413
  handler.sendArray(responseId, result);
351
414
  } else if (namespace === this.namespaceFile) {
352
415
  // Handle request for Meta data for files
353
416
  if (isMeta) {
354
417
  const response = [];
355
418
  data.forEach(dataId => {
356
- const {id, namespace, name} = this._normalizeId(dataId);
419
+ const { id, namespace, name } = this._normalizeId(dataId);
357
420
  if (namespace !== this.namespaceFile) {
358
421
  response.push(null);
359
- this.log.warn(`${namespaceLog} Got MGET request for non File ID in File-ID chunk for ${dataId}`);
422
+ this.log.warn(
423
+ `${namespaceLog} Got MGET request for non File ID in File-ID chunk for ${dataId}`
424
+ );
360
425
  return;
361
426
  }
362
427
  this._loadFileSettings(id);
@@ -370,7 +435,9 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
370
435
  obj.stats = fs.statSync(path.join(this.objectsDir, id, name));
371
436
  } catch (err) {
372
437
  if (!name.endsWith('/_data.json')) {
373
- this.log.warn(`${namespaceLog} Got MGET request for non existing file ${dataId}, err: ${err.message}`);
438
+ this.log.warn(
439
+ `${namespaceLog} Got MGET request for non existing file ${dataId}, err: ${err.message}`
440
+ );
374
441
  }
375
442
  response.push(null);
376
443
  return;
@@ -383,13 +450,16 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
383
450
  handler.sendError(responseId, new Error('MGET-UNSUPPORTED for file data'));
384
451
  }
385
452
  } else {
386
- handler.sendError(responseId, new Error('MGET-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data)));
453
+ handler.sendError(
454
+ responseId,
455
+ new Error('MGET-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data))
456
+ );
387
457
  }
388
458
  });
389
459
 
390
460
  // Handle Redis "GET" requests
391
461
  handler.on('get', (data, responseId) => {
392
- const {id, namespace, name, isMeta} = this._normalizeId(data[0]);
462
+ const { id, namespace, name, isMeta } = this._normalizeId(data[0]);
393
463
 
394
464
  if (namespace === this.namespaceObj) {
395
465
  const result = this._getObject(id);
@@ -408,11 +478,14 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
408
478
  return void handler.sendNull(responseId);
409
479
  }
410
480
  if (stats.isDirectory()) {
411
- return void handler.sendBulk(responseId, JSON.stringify({
412
- file: name,
413
- stats: {},
414
- isDir: true
415
- }));
481
+ return void handler.sendBulk(
482
+ responseId,
483
+ JSON.stringify({
484
+ file: name,
485
+ stats: {},
486
+ isDir: true
487
+ })
488
+ );
416
489
  }
417
490
  this._loadFileSettings(id);
418
491
  if (!this.fileOptions[id] || !this.fileOptions[id][name]) {
@@ -424,9 +497,16 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
424
497
  obj = {
425
498
  mimeType: obj,
426
499
  acl: {
427
- owner: (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER,
428
- ownerGroup: (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) || utils.CONSTS.SYSTEM_ADMIN_GROUP,
429
- permissions: (this.defaultNewAcl && this.defaultNewAcl.file.permissions) || (utils.CONSTS.ACCESS_USER_ALL | utils.CONSTS.ACCESS_GROUP_ALL | utils.CONSTS.ACCESS_EVERY_ALL) // 777
500
+ owner:
501
+ (this.defaultNewAcl && this.defaultNewAcl.owner) || utils.CONSTS.SYSTEM_ADMIN_USER,
502
+ ownerGroup:
503
+ (this.defaultNewAcl && this.defaultNewAcl.ownerGroup) ||
504
+ utils.CONSTS.SYSTEM_ADMIN_GROUP,
505
+ permissions:
506
+ (this.defaultNewAcl && this.defaultNewAcl.file.permissions) ||
507
+ utils.CONSTS.ACCESS_USER_ALL |
508
+ utils.CONSTS.ACCESS_GROUP_ALL |
509
+ utils.CONSTS.ACCESS_EVERY_ALL // 777
430
510
  }
431
511
  };
432
512
  }
@@ -447,18 +527,36 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
447
527
  if (!Buffer.isBuffer(fileData) && tools.isObject(fileData)) {
448
528
  // if its an invalid object, stringify it and log warning
449
529
  fileData = JSON.stringify(fileData);
450
- this.log.warn(`${namespaceLog} Data of "${id}/${name}" has invalid structure at file data request: ${fileData}`);
530
+ this.log.warn(
531
+ `${namespaceLog} Data of "${id}/${name}" has invalid structure at file data request: ${fileData}`
532
+ );
451
533
  }
452
534
  handler.sendBufBulk(responseId, Buffer.from(fileData));
453
535
  }
536
+ } else if (namespace === this.metaNamespace) {
537
+ // special handling for the primaryHost
538
+ if (id === 'objects.primaryHost') {
539
+ // we are the server -> we are primary
540
+ handler.sendString(this.settings.hostname);
541
+ } else {
542
+ const result = this.getMeta(id);
543
+ if (result === undefined || result === null) {
544
+ handler.sendNull(responseId);
545
+ } else {
546
+ handler.sendBulk(responseId, result);
547
+ }
548
+ }
454
549
  } else {
455
- handler.sendError(responseId, new Error('GET-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data)));
550
+ handler.sendError(
551
+ responseId,
552
+ new Error('GET-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data))
553
+ );
456
554
  }
457
555
  });
458
556
 
459
557
  // Handle Redis "SET" requests
460
558
  handler.on('set', (data, responseId) => {
461
- const {id, namespace, name, isMeta} = this._normalizeId(data[0]);
559
+ const { id, namespace, name, isMeta } = this._normalizeId(data[0]);
462
560
 
463
561
  if (namespace === this.namespaceObj) {
464
562
  try {
@@ -480,10 +578,16 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
480
578
  // only set if the meta object is already/still existing
481
579
  if (this.fileOptions[id]) {
482
580
  this.fileOptions[id][name] = JSON.parse(data[1].toString('utf-8'));
483
- fs.writeFileSync(path.join(this.objectsDir, id, '_data.json'), JSON.stringify(this.fileOptions[id]));
581
+ fs.writeFileSync(
582
+ path.join(this.objectsDir, id, '_data.json'),
583
+ JSON.stringify(this.fileOptions[id])
584
+ );
484
585
  }
485
586
  } catch (err) {
486
- return void handler.sendError(responseId, new Error(`ERROR writeFile-Meta id=${id}: ${err.message}`));
587
+ return void handler.sendError(
588
+ responseId,
589
+ new Error(`ERROR writeFile-Meta id=${id}: ${err.message}`)
590
+ );
487
591
  }
488
592
  handler.sendString(responseId, 'OK');
489
593
  } else {
@@ -491,12 +595,21 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
491
595
  try {
492
596
  this._writeFile(id, name, data[1]);
493
597
  } catch (err) {
494
- return void handler.sendError(responseId, new Error(`ERROR writeFile id=${id}: ${err.message}`));
598
+ return void handler.sendError(
599
+ responseId,
600
+ new Error(`ERROR writeFile id=${id}: ${err.message}`)
601
+ );
495
602
  }
496
603
  handler.sendString(responseId, 'OK');
497
604
  }
605
+ } else if (namespace === this.metaNamespace) {
606
+ this.setMeta(id, data[1].toString('utf-8'));
607
+ handler.sendString(responseId, 'OK');
498
608
  } else {
499
- handler.sendError(responseId, new Error(`SET-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`));
609
+ handler.sendError(
610
+ responseId,
611
+ new Error(`SET-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`)
612
+ );
500
613
  }
501
614
  });
502
615
 
@@ -507,7 +620,10 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
507
620
 
508
621
  if (oldDetails.namespace === this.namespaceFile) {
509
622
  if (oldDetails.id !== newDetails.id) {
510
- return void handler.sendError(responseId, new Error('ERROR renameObject: id needs to stay the same'));
623
+ return void handler.sendError(
624
+ responseId,
625
+ new Error('ERROR renameObject: id needs to stay the same')
626
+ );
511
627
  }
512
628
 
513
629
  // Handle request for Meta data for files
@@ -523,13 +639,16 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
523
639
  handler.sendString(responseId, 'OK');
524
640
  }
525
641
  } else {
526
- handler.sendError(responseId, new Error(`RENAME-UNSUPPORTED for namespace ${oldDetails.namespace}: Data=${JSON.stringify(data)}`));
642
+ handler.sendError(
643
+ responseId,
644
+ new Error(`RENAME-UNSUPPORTED for namespace ${oldDetails.namespace}: Data=${JSON.stringify(data)}`)
645
+ );
527
646
  }
528
647
  });
529
648
 
530
649
  // Handle Redis "DEL" request for state and session namespace
531
650
  handler.on('del', (data, responseId) => {
532
- const {id, namespace, name, isMeta} = this._normalizeId(data[0]);
651
+ const { id, namespace, name, isMeta } = this._normalizeId(data[0]);
533
652
 
534
653
  if (namespace === this.namespaceObj) {
535
654
  try {
@@ -553,7 +672,10 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
553
672
  handler.sendString(responseId, 'OK');
554
673
  }
555
674
  } else {
556
- handler.sendError(responseId, new Error(`DEL-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`));
675
+ handler.sendError(
676
+ responseId,
677
+ new Error(`DEL-UNSUPPORTED for namespace ${namespace}: Data=${JSON.stringify(data)}`)
678
+ );
557
679
  }
558
680
  });
559
681
 
@@ -563,7 +685,7 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
563
685
  }
564
686
 
565
687
  // Note: we only simulate single key existence check
566
- const {id, namespace, name} = this._normalizeId(data[0]);
688
+ const { id, namespace, name } = this._normalizeId(data[0]);
567
689
 
568
690
  if (namespace === this.namespaceObj) {
569
691
  let exists;
@@ -581,6 +703,9 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
581
703
  return void handler.sendError(responseId, e);
582
704
  }
583
705
  handler.sendInteger(responseId, exists ? 1 : 0);
706
+ } else if (namespace === this.namespaceSet) {
707
+ // we are not using sets in simulator, so just say it exists
708
+ return void handler.sendInteger(responseId, 1);
584
709
  } else {
585
710
  handler.sendError(responseId, new Error(`EXISTS-UNSUPPORTED for namespace ${namespace}`));
586
711
  }
@@ -604,33 +729,73 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
604
729
  return this._handleScanOrKeys(handler, data[0], responseId);
605
730
  });
606
731
 
732
+ // commands for redis SETS, just dummies
733
+ handler.on('sadd', (data, responseId) => {
734
+ return void handler.sendInteger(responseId, 1);
735
+ });
736
+
737
+ handler.on('srem', (data, responseId) => {
738
+ return void handler.sendInteger(responseId, 1);
739
+ });
740
+
741
+ handler.on('sscan', (data, responseId) => {
742
+ // for file DB it does the same as scan but data looks different
743
+ if (!data || data.length < 4) {
744
+ return void handler.sendArray(responseId, ['0', []]);
745
+ }
746
+
747
+ return this._handleScanOrKeys(handler, data[3], responseId, true);
748
+ });
749
+
607
750
  // Handle Redis "PSUBSCRIBE" request for state, log and session namespace
608
751
  handler.on('psubscribe', (data, responseId) => {
609
- const {id, namespace} = this._normalizeId(data[0]);
752
+ const { id, namespace } = this._normalizeId(data[0]);
610
753
 
611
754
  if (namespace === this.namespaceObj) {
612
755
  this._subscribeConfigForClient(handler, id);
613
756
  handler.sendArray(responseId, ['psubscribe', data[0], 1]);
757
+ } else if (namespace === this.metaNamespace) {
758
+ this._subscribeMeta(handler, id);
759
+ handler.sendArray(responseId, ['psubscribe', data[0], 1]);
614
760
  } else {
615
- handler.sendError(responseId, new Error('PSUBSCRIBE-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data)));
761
+ handler.sendError(
762
+ responseId,
763
+ new Error('PSUBSCRIBE-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data))
764
+ );
616
765
  }
617
766
  });
618
767
 
619
768
  // Handle Redis "UNSUBSCRIBE" request for state, log and session namespace
620
769
  handler.on('punsubscribe', (data, responseId) => {
621
- const {id, namespace} = this._normalizeId(data[0]);
770
+ const { id, namespace } = this._normalizeId(data[0]);
622
771
 
623
772
  if (namespace === this.namespaceObj) {
624
773
  this._unsubscribeConfigForClient(handler, id);
625
774
  handler.sendArray(responseId, ['punsubscribe', data[0], 1]);
626
775
  } else {
627
- handler.sendError(responseId, new Error('PUNSUBSCRIBE-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data)));
776
+ handler.sendError(
777
+ responseId,
778
+ new Error('PUNSUBSCRIBE-UNSUPPORTED for namespace ' + namespace + ': Data=' + JSON.stringify(data))
779
+ );
780
+ }
781
+ });
782
+
783
+ // Handle Redis "SUBSCRIBE" ... currently mainly ignored
784
+ handler.on('subscribe', (data, responseId) => {
785
+ if (data[0].startsWith('__keyevent@')) {
786
+ // we ignore these type of events because we publish expires anyway directly
787
+ handler.sendArray(responseId, ['subscribe', data[0], 1]);
788
+ } else {
789
+ handler.sendError(responseId, new Error(`SUBSCRIBE-UNSUPPORTED for ${data[0]}`));
628
790
  }
629
791
  });
630
792
 
631
793
  // Handle Redis "CONFIG" ... currently mainly ignored
632
794
  handler.on('config', (data, responseId) => {
633
- if (data[0] === 'set' && data[1] === 'lua-time-limit') {
795
+ if (data[0] === 'set' && data[1] === 'notify-keyspace-events') {
796
+ // we ignore these type of commands for now, should only be to subscribe to keyspace events
797
+ handler.sendString(responseId, 'OK');
798
+ } else if (data[0] === 'set' && data[1] === 'lua-time-limit') {
634
799
  // we ignore these type of commands for now, irrelevant
635
800
  handler.sendString(responseId, 'OK');
636
801
  } else {
@@ -684,17 +849,19 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
684
849
  delete this.serverConnections[s];
685
850
  });
686
851
 
687
- return /** @type {Promise<void>} */ (new Promise(resolve => {
688
- if (!this.server) {
689
- return void resolve();
690
- }
691
- try {
692
- this.server.close(() => resolve());
693
- } catch (e) {
694
- console.log(e.message);
695
- resolve();
696
- }
697
- }));
852
+ return /** @type {Promise<void>} */ (
853
+ new Promise(resolve => {
854
+ if (!this.server) {
855
+ return void resolve();
856
+ }
857
+ try {
858
+ this.server.close(() => resolve());
859
+ } catch (e) {
860
+ console.log(e.message);
861
+ resolve();
862
+ }
863
+ })
864
+ );
698
865
  }
699
866
  }
700
867
 
@@ -708,13 +875,14 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
708
875
  * @private
709
876
  */
710
877
  _handleScanOrKeys(handler, pattern, responseId, isScan = false) {
711
- const {id, namespace, name, isMeta} = this._normalizeId(pattern);
878
+ const { id, namespace, name, isMeta } = this._normalizeId(pattern);
712
879
 
713
880
  let response = [];
714
881
  if (namespace === this.namespaceObj || namespace === this.namespaceObjects) {
715
- response = this._getKeys(id).map(val => this.namespaceObj + val);
882
+ response = this._getKeys(id).map(val => this.namespaceObj + val);
716
883
  // if scan, we send the cursor as first argument
717
- if (namespace !== this.namespaceObjects) { // When it was not the full DB namespace send out response
884
+ if (namespace !== this.namespaceObjects) {
885
+ // When it was not the full DB namespace send out response
718
886
  return void handler.sendArray(responseId, isScan ? ['0', response] : response);
719
887
  }
720
888
  }
@@ -725,13 +893,15 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
725
893
  try {
726
894
  res = this._readDir(id, name);
727
895
  if (!res || !res.length) {
728
- res = [{
729
- file: '_data.json',
730
- stats: {},
731
- isDir: false,
732
- virtualFile: true,
733
- notExists: true
734
- }];
896
+ res = [
897
+ {
898
+ file: '_data.json',
899
+ stats: {},
900
+ isDir: false,
901
+ virtualFile: true,
902
+ notExists: true
903
+ }
904
+ ];
735
905
  }
736
906
  } catch (err) {
737
907
  if (!err.message.endsWith(utils.ERRORS.ERROR_NOT_FOUND)) {
@@ -758,11 +928,17 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
758
928
  response.push(this.getFileId(entryId, baseName + arr.file, false));
759
929
  });
760
930
  handler.sendArray(responseId, isScan ? ['0', response] : response); // send out file or full db response
761
- } else { // such a request should never happen
931
+ } else {
932
+ // such a request should never happen
762
933
  handler.sendArray(responseId, isScan ? ['0', []] : []); // send out file or full db response
763
934
  }
935
+ } else if (namespace === this.namespaceSet) {
936
+ handler.sendArray(responseId, isScan ? ['0', []] : []); // send out empty array, we have no sets
764
937
  } else {
765
- handler.sendError(responseId, new Error(`${isScan ? 'SCAN' : 'KEYS'}-UNSUPPORTED for namespace ${namespace}: Pattern=${pattern}`));
938
+ handler.sendError(
939
+ responseId,
940
+ new Error(`${isScan ? 'SCAN' : 'KEYS'}-UNSUPPORTED for namespace ${namespace}: Pattern=${pattern}`)
941
+ );
766
942
  }
767
943
  }
768
944
 
@@ -806,7 +982,12 @@ class ObjectsInMemoryServer extends ObjectsInMemoryJsonlDB {
806
982
  try {
807
983
  this.server = net.createServer();
808
984
  this.server.on('error', err =>
809
- this.log.info(`${this.namespace} ${settings.secure ? 'Secure ' : ''} Error inMem-objects listening on port ${settings.port || 9001}: ${err}`));
985
+ this.log.info(
986
+ `${this.namespace} ${settings.secure ? 'Secure ' : ''} Error inMem-objects listening on port ${
987
+ settings.port || 9001
988
+ }: ${err}`
989
+ )
990
+ );
810
991
  this.server.on('connection', socket => this._initSocket(socket));
811
992
 
812
993
  this.server.listen(
package/package.json CHANGED
@@ -1,17 +1,16 @@
1
1
  {
2
2
  "name": "@iobroker/db-objects-jsonl",
3
- "version": "4.0.0-alpha.8-20210909-001a711c",
3
+ "version": "4.0.3",
4
4
  "engines": {
5
5
  "node": ">=12.0.0"
6
6
  },
7
7
  "dependencies": {
8
- "@alcalzone/jsonl-db": "^2.1.0",
9
- "@iobroker/db-base": "4.0.0-alpha.8-20210909-001a711c",
10
- "@iobroker/db-objects-file": "4.0.0-alpha.8-20210909-001a711c",
11
- "@iobroker/db-objects-redis": "4.0.0-alpha.8-20210909-001a711c",
8
+ "@alcalzone/jsonl-db": "~2.4.1",
9
+ "@iobroker/db-base": "4.0.3",
10
+ "@iobroker/db-objects-file": "4.0.3",
11
+ "@iobroker/db-objects-redis": "4.0.3",
12
12
  "deep-clone": "^3.0.3",
13
- "fs-extra": "^10.0.0",
14
- "node.extend": "^2.0.2"
13
+ "fs-extra": "^10.0.0"
15
14
  },
16
15
  "keywords": [
17
16
  "ioBroker",
@@ -38,5 +37,5 @@
38
37
  "lib/",
39
38
  "index.js"
40
39
  ],
41
- "gitHead": "4c022b8b6845fdd4792fc0003dbfc740fef4a942"
40
+ "gitHead": "79a76a082db91399d0e432ef02bf2981a6739107"
42
41
  }