chat-platform 1.2.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/.eslintrc +17 -0
- package/__tests__/chat-platform.js +223 -0
- package/__tests__/context-provider-memory.js +222 -0
- package/__tests__/context-provider-plain-file.js +174 -0
- package/__tests__/context-provider-sqlite.js +239 -0
- package/__tests__/dummy/audio.mp3 +0 -0
- package/__tests__/dummy/file.bin +0 -0
- package/__tests__/dummy/file.mp4 +0 -0
- package/__tests__/dummy/file.pdf +0 -0
- package/__tests__/dummy/image.png +0 -0
- package/__tests__/dummy/mission-control.backup +0 -0
- package/__tests__/dummy/video.mov +0 -0
- package/__tests__/universal-platform.js +190 -0
- package/blank/empty.sqlite +0 -0
- package/chat-context-factory.js +92 -0
- package/chat-log.js +115 -0
- package/chat-platform.js +1276 -0
- package/helpers/lcd.js +120 -0
- package/helpers/promises-queue.js +41 -0
- package/helpers/utils.js +25 -0
- package/index.js +6 -0
- package/jest.config.js +5 -0
- package/lib/lcd.js +151 -0
- package/lib/red-stub.js +212 -0
- package/lib/utils.js +29 -0
- package/model.nlp +688 -0
- package/package.json +32 -0
- package/providers/memory.js +170 -0
- package/providers/plain-file.js +345 -0
- package/providers/sqlite.js +267 -0
- package/universal.js +67 -0
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chat-platform",
|
|
3
|
+
"version": "1.2.3",
|
|
4
|
+
"description": "Universal Chat Platform",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest",
|
|
8
|
+
"test-ci": "jest --watch",
|
|
9
|
+
"lint": "eslint ./*.js ./helpers/*.js"
|
|
10
|
+
},
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"cli-color": "^1.4.0",
|
|
15
|
+
"cli-table": "^0.3.1",
|
|
16
|
+
"moment": "^2.24.0",
|
|
17
|
+
"np": "^5.0.2",
|
|
18
|
+
"npm": "^6.9.0",
|
|
19
|
+
"prettyjson": "^1.2.1",
|
|
20
|
+
"request": "^2.88.0",
|
|
21
|
+
"sequelize": "^5.21.6",
|
|
22
|
+
"sqlite3": "^4.1.1",
|
|
23
|
+
"underscore": "^1.9.1",
|
|
24
|
+
"underscore.string": "^3.3.5"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"chai": "^4.1.1",
|
|
28
|
+
"chai-spies": "^1.0.0",
|
|
29
|
+
"eslint": "^4.12.1",
|
|
30
|
+
"jest": "^22.0.6"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const _ = require('underscore');
|
|
2
|
+
const _store = {};
|
|
3
|
+
const _storeUserIds = {};
|
|
4
|
+
|
|
5
|
+
const isEmpty = value => value == null || value === '';
|
|
6
|
+
|
|
7
|
+
function MemoryStore(chatId, userId, statics = {}, warnings = false) {
|
|
8
|
+
this.chatId = chatId != null ? String(chatId) : null;
|
|
9
|
+
this.userId = userId != null ? String(userId) : null;
|
|
10
|
+
// make sure userId is always a string
|
|
11
|
+
this.statics = Object.assign({}, statics, { userId: statics.userId != null ? String(statics.userId) : undefined });
|
|
12
|
+
if (warnings && _.isEmpty(statics)) {
|
|
13
|
+
console.trace('Warning: empty statics vars')
|
|
14
|
+
}
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
_.extend(MemoryStore.prototype, {
|
|
18
|
+
getPayload() {
|
|
19
|
+
// always precedence to userId
|
|
20
|
+
if (this.userId != null && _storeUserIds[this.userId] != null) {
|
|
21
|
+
return _storeUserIds[this.userId];
|
|
22
|
+
} else if (this.chatId != null && _store[this.chatId] != null) {
|
|
23
|
+
return _store[this.chatId];
|
|
24
|
+
} else if (this.userId != null) {
|
|
25
|
+
_storeUserIds[this.userId] = {};
|
|
26
|
+
return _storeUserIds[this.userId];
|
|
27
|
+
} else if (this.chatId != null) {
|
|
28
|
+
_store[this.chatId] = {};
|
|
29
|
+
return _store[this.chatId];
|
|
30
|
+
}
|
|
31
|
+
return {};
|
|
32
|
+
},
|
|
33
|
+
get(key) {
|
|
34
|
+
const keys = Array.from(arguments);
|
|
35
|
+
const payload = this.getPayload();
|
|
36
|
+
|
|
37
|
+
if (keys.length === 1) {
|
|
38
|
+
if (this.statics[keys[0]] != null) {
|
|
39
|
+
return this.statics[keys[0]];
|
|
40
|
+
} else {
|
|
41
|
+
return payload[key] != null ? payload[key] : null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const result = {};
|
|
45
|
+
keys.forEach(key => {
|
|
46
|
+
if (this.statics[key] != null) {
|
|
47
|
+
result[key] = this.statics[key];
|
|
48
|
+
} else {
|
|
49
|
+
result[key] = payload[key];
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return result;
|
|
53
|
+
},
|
|
54
|
+
remove() {
|
|
55
|
+
const keys = Array.from(arguments);
|
|
56
|
+
const payload = this.getPayload();
|
|
57
|
+
keys.forEach(key => {
|
|
58
|
+
// eslint-disable-next-line prefer-reflect
|
|
59
|
+
delete payload[key];
|
|
60
|
+
});
|
|
61
|
+
return this;
|
|
62
|
+
},
|
|
63
|
+
set(key, value) {
|
|
64
|
+
let payload = this.getPayload();
|
|
65
|
+
const staticKeys = Object.keys(this.statics);
|
|
66
|
+
if (_.isString(key) && staticKeys.includes(key)) {
|
|
67
|
+
console.log(`Warning: try to set a static key: ${key}`);
|
|
68
|
+
} else if (_.isObject(key) && _.intersection(staticKeys, Object.keys(key)).length !== 0) {
|
|
69
|
+
console.log(`Warning: try to set a static keys: ${_.intersection(staticKeys, Object.keys(key)).join(', ')}`);
|
|
70
|
+
}
|
|
71
|
+
// store values, skipping static keys
|
|
72
|
+
if (_.isString(key) && !staticKeys.includes(key)) {
|
|
73
|
+
payload[key] = value;
|
|
74
|
+
} else if (_.isObject(key)) {
|
|
75
|
+
payload = { ...payload, ..._.omit(key, staticKeys) };
|
|
76
|
+
}
|
|
77
|
+
// store the payload back
|
|
78
|
+
if (this.userId != null) {
|
|
79
|
+
_storeUserIds[this.userId] = payload;
|
|
80
|
+
} else if (this.chatId != null) {
|
|
81
|
+
_store[this.chatId] = payload;
|
|
82
|
+
}
|
|
83
|
+
return this;
|
|
84
|
+
},
|
|
85
|
+
dump() {
|
|
86
|
+
const payload = this.getPayload();
|
|
87
|
+
// eslint-disable-next-line no-console
|
|
88
|
+
console.log(payload);
|
|
89
|
+
},
|
|
90
|
+
all() {
|
|
91
|
+
const payload = this.getPayload();
|
|
92
|
+
return payload;
|
|
93
|
+
},
|
|
94
|
+
clear() {
|
|
95
|
+
if (this.userId != null) {
|
|
96
|
+
_storeUserIds[this.userId] = {};
|
|
97
|
+
_store[this.chatId] = null;
|
|
98
|
+
} else if (this.chatId != null) {
|
|
99
|
+
_store[this.chatId] = {};
|
|
100
|
+
_storeUserIds[this.userId] = null;
|
|
101
|
+
}
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
function MemoryFactory() {
|
|
107
|
+
|
|
108
|
+
this.getOrCreate = function(chatId, userId, statics) {
|
|
109
|
+
if (isEmpty(chatId) && isEmpty(userId)) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
// just create an class that just wraps chatId and userId, add static value (cline)
|
|
113
|
+
const store = new MemoryStore(chatId, userId, { ...statics });
|
|
114
|
+
return store;
|
|
115
|
+
/*const chatContext = this.get(chatId, userId);
|
|
116
|
+
if (chatContext == null) {
|
|
117
|
+
const memoryStore = new MemoryStore({ ...defaults });
|
|
118
|
+
_store[chatId] = memoryStore;
|
|
119
|
+
if (!isEmpty(userId)) {
|
|
120
|
+
_storeUserIds[userId] = memoryStore;
|
|
121
|
+
}
|
|
122
|
+
return _store[chatId];
|
|
123
|
+
}
|
|
124
|
+
return chatContext;
|
|
125
|
+
*/
|
|
126
|
+
};
|
|
127
|
+
this.get = function(chatId, userId, statics) {
|
|
128
|
+
/*if (!isEmpty(chatId) && _store[chatId] != null) {
|
|
129
|
+
return _store[chatId];
|
|
130
|
+
} else if (!isEmpty(userId) && _storeUserIds[userId] != null) {
|
|
131
|
+
return _storeUserIds[userId];
|
|
132
|
+
}
|
|
133
|
+
return null;*/
|
|
134
|
+
return new MemoryStore(chatId, userId, { ...statics });
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
_.extend(MemoryFactory.prototype, {
|
|
140
|
+
name: 'Memory',
|
|
141
|
+
description: 'Memory context provider, it\' fast and synchronous but it doesn\'t persists the values, once the'
|
|
142
|
+
+ ' server is restarted all contexts are lost. It doesn\'t requires any parameters. Good for testing.',
|
|
143
|
+
get: function(/*chatId, userId*/) {
|
|
144
|
+
},
|
|
145
|
+
getOrCreate: function(/*chatId, userId, statics*/) {
|
|
146
|
+
},
|
|
147
|
+
assignToUser(userId, context) {
|
|
148
|
+
// when merging a user into another, this trasnfer the current context to another user
|
|
149
|
+
// TODO perhaps remove other occurence
|
|
150
|
+
_storeUserIds[userId] = context;
|
|
151
|
+
},
|
|
152
|
+
reset() {
|
|
153
|
+
Object.keys(_store).forEach(key => delete _store[key]);
|
|
154
|
+
Object.keys(_storeUserIds).forEach(key => delete _storeUserIds[key]);
|
|
155
|
+
return this;
|
|
156
|
+
},
|
|
157
|
+
stop: function() {
|
|
158
|
+
return new Promise(function(resolve) {
|
|
159
|
+
resolve();
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
start: function() {
|
|
163
|
+
return new Promise(function(resolve) {
|
|
164
|
+
resolve();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
module.exports = MemoryFactory;
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
const _ = require('underscore');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const moment = require('moment');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const lcd = require('../helpers/lcd');
|
|
6
|
+
const FileQueue = require('../helpers/promises-queue');
|
|
7
|
+
const filesQueue = {};
|
|
8
|
+
|
|
9
|
+
// memory cache, each loaded store is here and it's saved to filesystem at every changes,
|
|
10
|
+
// subsequent read hit the cache
|
|
11
|
+
let _store = {};
|
|
12
|
+
// main index
|
|
13
|
+
let _index;
|
|
14
|
+
|
|
15
|
+
const parse = content => {
|
|
16
|
+
let obj = null;
|
|
17
|
+
const date = new RegExp('^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}\.[0-9]{1,3}Z$');
|
|
18
|
+
|
|
19
|
+
obj = JSON.parse(content);
|
|
20
|
+
// todo fix with forEach
|
|
21
|
+
// go through every key/value to search for a date-like string
|
|
22
|
+
_(obj).each(function(value, key) {
|
|
23
|
+
if (_.isString(value) && value.match(date)) {
|
|
24
|
+
obj[key] = moment(value);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return obj;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
const deleteFile = file => {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
fs.unlink(file, err => {
|
|
35
|
+
if (err) {
|
|
36
|
+
reject(err)
|
|
37
|
+
} else {
|
|
38
|
+
resolve(true);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const exists = (file) => {
|
|
45
|
+
return new Promise(resolve => {
|
|
46
|
+
fs.exists(file, exists => resolve(exists));
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const writeJson = (file, obj) => {
|
|
51
|
+
|
|
52
|
+
const serialized = JSON.stringify(obj);
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
fs.writeFile(file, serialized, err => {
|
|
55
|
+
if (err != null) {
|
|
56
|
+
reject(err);
|
|
57
|
+
} else {
|
|
58
|
+
resolve(obj);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const loadJson = file => {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
fs.readFile(file, (err, content) => {
|
|
67
|
+
|
|
68
|
+
if (err != null) {
|
|
69
|
+
reject(err);
|
|
70
|
+
} else {
|
|
71
|
+
const index = parse(String(content));
|
|
72
|
+
|
|
73
|
+
if (index != null) {
|
|
74
|
+
resolve(index);
|
|
75
|
+
} else {
|
|
76
|
+
reject(new Error(`Unable to parse file ${file}`))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
const loadOrCreateIndex = async ({ path }) => {
|
|
85
|
+
const indexPath = `${path}/index.json`;
|
|
86
|
+
const indexExists = await exists(indexPath);
|
|
87
|
+
if (!indexExists) {
|
|
88
|
+
let _index = { chatId: {}, userId: {} };
|
|
89
|
+
return writeJson(indexPath, _index);
|
|
90
|
+
} else {
|
|
91
|
+
return loadJson(indexPath);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const saveIndex = ({ path }) => {
|
|
96
|
+
const indexPath = `${path}/index.json`;
|
|
97
|
+
return writeJson(indexPath, _index);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
const getOrLoadOrCreateIndex = async ({ path }) => {
|
|
102
|
+
if (_index != null) {
|
|
103
|
+
return _index;
|
|
104
|
+
} else {
|
|
105
|
+
_index = await loadOrCreateIndex({ path })
|
|
106
|
+
return _index;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/*const loadFileStore = (path, file, statics) => {
|
|
111
|
+
if (_store[file] != null) {
|
|
112
|
+
return Promise.resolve(_store[file]);
|
|
113
|
+
}
|
|
114
|
+
const store = new FileStore(null, `${path}/${file}`, statics);
|
|
115
|
+
return store.load()
|
|
116
|
+
.then(() => store);
|
|
117
|
+
}*/
|
|
118
|
+
|
|
119
|
+
const generateFileName = function(chatId, userId) {
|
|
120
|
+
return 'store-' + crypto.createHash('md5').update(`${chatId}${userId}`).digest('hex') + '.json';
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
function FileFactory(params) {
|
|
125
|
+
|
|
126
|
+
params = params || {};
|
|
127
|
+
if (_.isEmpty(params.path)) {
|
|
128
|
+
throw 'Plain file context provider: missing parameter "path"';
|
|
129
|
+
}
|
|
130
|
+
if (!fs.existsSync(params.path)) {
|
|
131
|
+
throw 'Plain file context provider: "path" (' + params.path + ') doesn\'t exist';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.getOrCreate = function(chatId, userId = null, statics = {}) {
|
|
135
|
+
const { path } = params;
|
|
136
|
+
return new FileStore(chatId, userId, { ...statics }, { path });
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.get = function(chatId, userId, statics = {}) {
|
|
140
|
+
return this.getOrCreate(chatId, userId, statics);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
_.extend(FileFactory.prototype, {
|
|
147
|
+
name: 'Plain File',
|
|
148
|
+
description: 'Simple file context provider: chat context will be stored in plain json files. Specify the storage path in'
|
|
149
|
+
+ ' params as JSON config like this <pre style="margin-top: 10px;">\n'
|
|
150
|
+
+ '{\n'
|
|
151
|
+
+ '"path": "/my-path/my-context-files"\n'
|
|
152
|
+
+'}</pre>',
|
|
153
|
+
get(/*chatId*/) { },
|
|
154
|
+
getOrCreate(/*chatId, defaults*/) { },
|
|
155
|
+
start() {
|
|
156
|
+
return new Promise(function(resolve) {
|
|
157
|
+
resolve();
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
reset({ path }) {
|
|
161
|
+
const files = fs.readdirSync(path);
|
|
162
|
+
files.forEach(file => fs.unlinkSync(`${path}/${file}`));
|
|
163
|
+
//_store = {};
|
|
164
|
+
_index = null;
|
|
165
|
+
},
|
|
166
|
+
stop: function() {
|
|
167
|
+
return new Promise(function(resolve) {
|
|
168
|
+
resolve();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
//function FileStore(defaults, file, statics = {}) {
|
|
174
|
+
function FileStore(chatId, userId, statics = {}, params, warnings = false) {
|
|
175
|
+
this.userId = userId;
|
|
176
|
+
this.chatId = chatId;
|
|
177
|
+
this.params = params;
|
|
178
|
+
// make sure userId is always a string
|
|
179
|
+
this.statics = Object.assign({}, statics, { userId: statics.userId != null ? String(statics.userId) : undefined });
|
|
180
|
+
if (warnings && _.isEmpty(statics)) {
|
|
181
|
+
console.trace('Warning: empty statics vars')
|
|
182
|
+
}
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
_.extend(FileStore.prototype, {
|
|
186
|
+
async get(key) {
|
|
187
|
+
const keys = Array.from(arguments);
|
|
188
|
+
const payload = await this.getPayload();
|
|
189
|
+
if (keys.length === 1) {
|
|
190
|
+
if (this.statics[keys[0]] != null) {
|
|
191
|
+
return this.statics[keys[0]];
|
|
192
|
+
} else {
|
|
193
|
+
return payload[key] != null ? payload[key] : undefined;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const result = {};
|
|
197
|
+
keys.forEach(key => {
|
|
198
|
+
if (this.statics[key] != null) {
|
|
199
|
+
result[key] = this.statics[key];
|
|
200
|
+
} else {
|
|
201
|
+
result[key] = payload[key];
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
return result;
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
parse(content) {
|
|
208
|
+
let obj = null;
|
|
209
|
+
let date = new RegExp('^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}\.[0-9]{1,3}Z$');
|
|
210
|
+
try {
|
|
211
|
+
obj = JSON.parse(content);
|
|
212
|
+
// go through every key/value to search for a date-like string
|
|
213
|
+
_(obj).each((value, key) => {
|
|
214
|
+
if (_.isString(value) && value.match(date)) {
|
|
215
|
+
obj[key] = moment(value);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
} catch(e) {
|
|
219
|
+
// eslint-disable-next-line no-console
|
|
220
|
+
console.log(lcd.error('Error parsing context file: ' + this._file));
|
|
221
|
+
throw e;
|
|
222
|
+
}
|
|
223
|
+
return obj;
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
async getPayload() {
|
|
227
|
+
// get the index from the memory cache or from file, if doesn't exist create it
|
|
228
|
+
const userId = this.userId;
|
|
229
|
+
const chatId = this.chatId;
|
|
230
|
+
const { path } = this.params;
|
|
231
|
+
const index = await getOrLoadOrCreateIndex({ path })
|
|
232
|
+
|
|
233
|
+
// if context is already loaded, then return. Context is flushed to disk at every
|
|
234
|
+
// write, so if it's in memory no need to reload
|
|
235
|
+
if (this._context != null) {
|
|
236
|
+
return this._context;
|
|
237
|
+
} else if (userId != null && index.userId[userId] != null) {
|
|
238
|
+
this._file = `${path}/${index.userId[userId]}`;
|
|
239
|
+
await this.load();
|
|
240
|
+
return this._context;
|
|
241
|
+
} else if (chatId != null && index.chatId[chatId] != null) {
|
|
242
|
+
this._file = `${path}/${index.chatId[chatId]}`;
|
|
243
|
+
await this.load();
|
|
244
|
+
return this._context;
|
|
245
|
+
} else {
|
|
246
|
+
// file store doesn't exist yet, create one
|
|
247
|
+
const fileName = generateFileName(chatId, userId);
|
|
248
|
+
this._file = `${path}/${fileName}`;
|
|
249
|
+
this._context = {};
|
|
250
|
+
// store in the index the file reference
|
|
251
|
+
if (chatId != null) {
|
|
252
|
+
index.chatId[chatId] = fileName;
|
|
253
|
+
}
|
|
254
|
+
if (userId != null) {
|
|
255
|
+
index.userId[userId] = fileName;
|
|
256
|
+
}
|
|
257
|
+
// save index
|
|
258
|
+
await this.save();
|
|
259
|
+
await saveIndex({ path });
|
|
260
|
+
|
|
261
|
+
return this._context;
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
load() {
|
|
266
|
+
return new Promise((resolve, reject) => {
|
|
267
|
+
fs.readFile(this._file, (err, content) => {
|
|
268
|
+
if (err != null) {
|
|
269
|
+
reject(err);
|
|
270
|
+
} else {
|
|
271
|
+
this._context = this.parse(content);
|
|
272
|
+
resolve();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
},
|
|
277
|
+
save() {
|
|
278
|
+
// store the value, before the task is executed or can be overwritten, it's a snapshot
|
|
279
|
+
const serialized = JSON.stringify(_.clone(this._context));
|
|
280
|
+
let saveTask = (resolve, reject) => {
|
|
281
|
+
fs.writeFile(this._file, serialized, err => {
|
|
282
|
+
// put the object back, don't know what happens here but some key disapper
|
|
283
|
+
this._context = this.parse(serialized);
|
|
284
|
+
if (err != null) {
|
|
285
|
+
reject(err);
|
|
286
|
+
} else {
|
|
287
|
+
resolve();
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
if (filesQueue[this._file] == null) {
|
|
293
|
+
filesQueue[this._file] = new FileQueue();
|
|
294
|
+
}
|
|
295
|
+
// add to a queue to prevent concurrent writing
|
|
296
|
+
return filesQueue[this._file].add(saveTask);
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
async remove() {
|
|
300
|
+
const keys = Array.from(arguments);
|
|
301
|
+
const payload = await this.getPayload();
|
|
302
|
+
keys.forEach(key => {
|
|
303
|
+
// eslint-disable-next-line prefer-reflect
|
|
304
|
+
delete payload[key];
|
|
305
|
+
});
|
|
306
|
+
await this.save();
|
|
307
|
+
return this;
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
async set(key, value) {
|
|
311
|
+
let payload = await this.getPayload();
|
|
312
|
+
const staticKeys = Object.keys(this.statics);
|
|
313
|
+
if (_.isString(key) && staticKeys.includes(key)) {
|
|
314
|
+
console.log(`Warning: try to set a static key: ${key}`);
|
|
315
|
+
} else if (_.isObject(key) && _.intersection(staticKeys, Object.keys(key)).length !== 0) {
|
|
316
|
+
console.log(`Warning: try to set a static keys: ${_.intersection(staticKeys, Object.keys(key)).join(', ')}`);
|
|
317
|
+
}
|
|
318
|
+
// store values, skipping static keys
|
|
319
|
+
if (_.isString(key) && !staticKeys.includes(key)) {
|
|
320
|
+
payload[key] = value;
|
|
321
|
+
} else if (_.isObject(key)) {
|
|
322
|
+
Object.entries(key)
|
|
323
|
+
.forEach(([key, value]) => {
|
|
324
|
+
if (!staticKeys.includes(key)) {
|
|
325
|
+
payload[key] = value;
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
await this.save();
|
|
330
|
+
return this;
|
|
331
|
+
},
|
|
332
|
+
dump() {
|
|
333
|
+
// eslint-disable-next-line no-console
|
|
334
|
+
console.log(this._context);
|
|
335
|
+
},
|
|
336
|
+
all() {
|
|
337
|
+
return this._context;
|
|
338
|
+
},
|
|
339
|
+
clear() {
|
|
340
|
+
this._context = {};
|
|
341
|
+
return this.save();
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
module.exports = FileFactory;
|