@nymphjs/nymph 1.0.0-beta.7 → 1.0.0-beta.71
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/CHANGELOG.md +324 -0
- package/README.md +13 -21
- package/dist/Entity.d.ts +269 -7
- package/dist/Entity.js +491 -105
- package/dist/Entity.js.map +1 -1
- package/dist/Entity.test.js +39 -17
- package/dist/Entity.test.js.map +1 -1
- package/dist/Entity.types.d.ts +225 -19
- package/dist/Nymph.d.ts +423 -11
- package/dist/Nymph.js +685 -176
- package/dist/Nymph.js.map +1 -1
- package/dist/Nymph.types.d.ts +31 -7
- package/dist/Nymph.types.js +2 -1
- package/dist/Nymph.types.js.map +1 -1
- package/dist/conf/d.d.ts +26 -0
- package/dist/conf/defaults.js +22 -0
- package/dist/conf/defaults.js.map +1 -1
- package/dist/driver/NymphDriver.d.ts +129 -38
- package/dist/driver/NymphDriver.js +264 -131
- package/dist/driver/NymphDriver.js.map +1 -1
- package/dist/driver/testArtifacts.d.ts +1 -1
- package/dist/driver/testArtifacts.js +336 -42
- package/dist/driver/testArtifacts.js.map +1 -1
- package/dist/errors/EntityInvalidDataError.d.ts +6 -0
- package/dist/errors/EntityInvalidDataError.js +7 -1
- package/dist/errors/EntityInvalidDataError.js.map +1 -1
- package/dist/errors/EntityUniqueConstraintError.d.ts +3 -0
- package/dist/errors/EntityUniqueConstraintError.js +11 -0
- package/dist/errors/EntityUniqueConstraintError.js.map +1 -0
- package/dist/errors/InvalidStateError.d.ts +3 -0
- package/dist/errors/InvalidStateError.js +11 -0
- package/dist/errors/InvalidStateError.js.map +1 -0
- package/dist/errors/QueryFailedError.js +1 -0
- package/dist/errors/QueryFailedError.js.map +1 -1
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +2 -0
- package/dist/errors/index.js.map +1 -1
- package/dist/testArtifacts.d.ts +17 -5
- package/dist/testArtifacts.js +31 -27
- package/dist/testArtifacts.js.map +1 -1
- package/dist/testMocks.d.ts +15 -10
- package/dist/testMocks.js +37 -13
- package/dist/testMocks.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +21 -5
- package/dist/utils.js.map +1 -1
- package/package.json +16 -14
- package/src/Entity.test.ts +32 -11
- package/src/Entity.ts +423 -134
- package/src/Entity.types.ts +32 -66
- package/src/Nymph.ts +618 -195
- package/src/Nymph.types.ts +97 -26
- package/src/conf/d.ts +8 -0
- package/src/conf/defaults.ts +24 -0
- package/src/driver/NymphDriver.ts +281 -311
- package/src/driver/testArtifacts.ts +384 -173
- package/src/errors/EntityUniqueConstraintError.ts +6 -0
- package/src/errors/InvalidStateError.ts +6 -0
- package/src/errors/index.ts +4 -0
- package/src/nymphJoiProps.ts +2 -2
- package/src/testArtifacts.ts +18 -19
- package/src/testMocks.ts +64 -32
- package/src/utils.ts +22 -8
- package/tsconfig.json +3 -3
- package/typedoc.json +4 -0
|
@@ -10,25 +10,45 @@ const strtotime_1 = __importDefault(require("locutus/php/datetime/strtotime"));
|
|
|
10
10
|
const guid_1 = require("@nymphjs/guid");
|
|
11
11
|
const errors_1 = require("../errors");
|
|
12
12
|
const utils_1 = require("../utils");
|
|
13
|
+
// from: https://stackoverflow.com/a/6969486/664915
|
|
13
14
|
function escapeRegExp(string) {
|
|
14
|
-
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
15
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* A Nymph database driver.
|
|
19
|
+
*/
|
|
16
20
|
class NymphDriver {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
nymph = new Proxy({}, {
|
|
22
|
+
get() {
|
|
23
|
+
throw new Error('Attempted access of Nymph instance before driver initialization!');
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* A cache to make entity retrieval faster.
|
|
28
|
+
*/
|
|
29
|
+
entityCache = {};
|
|
30
|
+
/**
|
|
31
|
+
* A counter for the entity cache to determine the most accessed entities.
|
|
32
|
+
*/
|
|
33
|
+
entityCount = {};
|
|
34
|
+
/**
|
|
35
|
+
* Protect against infinite loops.
|
|
36
|
+
*/
|
|
37
|
+
putDataCounter = 0;
|
|
38
|
+
/**
|
|
39
|
+
* Initialize the Nymph driver.
|
|
40
|
+
*
|
|
41
|
+
* This is meant to be called internally by Nymph. Don't call this directly.
|
|
42
|
+
*
|
|
43
|
+
* @param nymph The Nymph instance.
|
|
44
|
+
*/
|
|
27
45
|
init(nymph) {
|
|
28
46
|
this.nymph = nymph;
|
|
29
47
|
}
|
|
30
48
|
posixRegexMatch(pattern, subject, caseInsensitive = false) {
|
|
31
49
|
const posixClasses = [
|
|
50
|
+
// '[[:<:]]',
|
|
51
|
+
// '[[:>:]]',
|
|
32
52
|
'[:alnum:]',
|
|
33
53
|
'[:alpha:]',
|
|
34
54
|
'[:ascii:]',
|
|
@@ -45,17 +65,21 @@ class NymphDriver {
|
|
|
45
65
|
'[:xdigit:]',
|
|
46
66
|
];
|
|
47
67
|
const jsClasses = [
|
|
68
|
+
// '\b(?=\w)',
|
|
69
|
+
// '(?<=\w)\b',
|
|
48
70
|
'[A-Za-z0-9]',
|
|
49
71
|
'[A-Za-z]',
|
|
50
72
|
'[\x00-\x7F]',
|
|
51
|
-
'[ \t]',
|
|
52
|
-
'[\x00-\x1F\x7F]',
|
|
53
|
-
'
|
|
54
|
-
'
|
|
73
|
+
'[ \t]', // '\s',
|
|
74
|
+
'[\x00-\x1F\x7F]', // '[\000\001\002\003\004\005\006\007\008\009\010\011\012\013\014'.
|
|
75
|
+
// '\015\016\017\018\019\020\021\022\023\024\025\026\027\028\029'.
|
|
76
|
+
// '\030\031\032\033\034\035\036\037\177]',
|
|
77
|
+
'[0-9]', // '\d',
|
|
78
|
+
'[\x21-\x7E]', // '[A-Za-z0-9!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
|
|
55
79
|
'[a-z]',
|
|
56
|
-
'[\x20-\x7E]',
|
|
57
|
-
'[!"#$%&\'()*+,-./:;<=>?@[\\\\\\]^_‘{|}~]',
|
|
58
|
-
'[ \t\r\n\v\f]',
|
|
80
|
+
'[\x20-\x7E]', // '[A-Za-z0-9!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
|
|
81
|
+
'[!"#$%&\'()*+,-./:;<=>?@[\\\\\\]^_‘{|}~]', // '[!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
|
|
82
|
+
'[ \t\r\n\v\f]', // '[\t\n\x0B\f\r ]',
|
|
59
83
|
'[A-Z]',
|
|
60
84
|
'[A-Za-z0-9_]',
|
|
61
85
|
'[0-9A-Fa-f]',
|
|
@@ -73,9 +97,9 @@ class NymphDriver {
|
|
|
73
97
|
if (!fhandle) {
|
|
74
98
|
throw new errors_1.InvalidParametersError('Provided filename is not writeable.');
|
|
75
99
|
}
|
|
76
|
-
await this.
|
|
77
|
-
fs_1.default.writeSync(fhandle,
|
|
78
|
-
}
|
|
100
|
+
for await (let entry of this.exportDataIterator()) {
|
|
101
|
+
fs_1.default.writeSync(fhandle, entry.content);
|
|
102
|
+
}
|
|
79
103
|
fs_1.default.closeSync(fhandle);
|
|
80
104
|
}
|
|
81
105
|
catch (e) {
|
|
@@ -84,98 +108,135 @@ class NymphDriver {
|
|
|
84
108
|
return true;
|
|
85
109
|
}
|
|
86
110
|
async exportPrint() {
|
|
87
|
-
await this.
|
|
88
|
-
console.log(
|
|
89
|
-
}
|
|
111
|
+
for await (let entry of this.exportDataIterator()) {
|
|
112
|
+
console.log(entry.content);
|
|
113
|
+
}
|
|
90
114
|
return true;
|
|
91
115
|
}
|
|
92
|
-
async
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
catch (e) {
|
|
98
|
-
throw new errors_1.InvalidParametersError('Provided filename is unreadable.');
|
|
116
|
+
async importDataIterator(lines, transaction) {
|
|
117
|
+
const first = lines[Symbol.iterator]().next();
|
|
118
|
+
if (first.done || first.value !== '#nex2') {
|
|
119
|
+
throw new Error('Tried to import a file that is not a NEX v2 file.');
|
|
99
120
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
let sdata = {};
|
|
103
|
-
let tags = [];
|
|
104
|
-
let etype = '__undefined';
|
|
105
|
-
if (startTransactionCallback) {
|
|
106
|
-
await startTransactionCallback();
|
|
121
|
+
if (transaction) {
|
|
122
|
+
await this.internalTransaction('nymph-import');
|
|
107
123
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (uidMatch) {
|
|
117
|
-
await saveUIDCallback(uidMatch[1], Number(uidMatch[2]));
|
|
118
|
-
}
|
|
119
|
-
else if (entityMatch) {
|
|
120
|
-
if (guid) {
|
|
121
|
-
await saveEntityCallback(guid, tags, sdata, etype);
|
|
122
|
-
guid = null;
|
|
123
|
-
tags = [];
|
|
124
|
-
sdata = {};
|
|
125
|
-
etype = '__undefined';
|
|
124
|
+
try {
|
|
125
|
+
let guid = null;
|
|
126
|
+
let sdata = {};
|
|
127
|
+
let tags = [];
|
|
128
|
+
let etype = '__undefined';
|
|
129
|
+
for (let line of lines) {
|
|
130
|
+
if (line.match(/^\s*#/)) {
|
|
131
|
+
continue;
|
|
126
132
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
const entityMatch = line.match(/^\s*{([0-9A-Fa-f]+)}<([-\w_]+)>\[([^\]]*)\]\s*$/);
|
|
134
|
+
const propMatch = line.match(/^\s*([^=]+)\s*=\s*(.*\S)\s*$/);
|
|
135
|
+
const uidMatch = line.match(/^\s*<([^>]+)>\[(\d+)\]\s*$/);
|
|
136
|
+
if (uidMatch) {
|
|
137
|
+
// Add the UID.
|
|
138
|
+
await this.importUID({
|
|
139
|
+
name: uidMatch[1],
|
|
140
|
+
value: Number(uidMatch[2]),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
else if (entityMatch) {
|
|
144
|
+
// Save the current entity.
|
|
145
|
+
if (guid) {
|
|
146
|
+
const cdate = Number(JSON.parse(sdata.cdate));
|
|
147
|
+
delete sdata.cdate;
|
|
148
|
+
const mdate = Number(JSON.parse(sdata.mdate));
|
|
149
|
+
delete sdata.mdate;
|
|
150
|
+
await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
|
|
151
|
+
guid = null;
|
|
152
|
+
tags = [];
|
|
153
|
+
sdata = {};
|
|
154
|
+
etype = '__undefined';
|
|
155
|
+
}
|
|
156
|
+
// Record the new entity's info.
|
|
157
|
+
guid = entityMatch[1];
|
|
158
|
+
etype = entityMatch[2];
|
|
159
|
+
tags = entityMatch[3].split(',');
|
|
160
|
+
}
|
|
161
|
+
else if (propMatch) {
|
|
162
|
+
// Add the variable to the new entity.
|
|
163
|
+
if (guid) {
|
|
164
|
+
sdata[propMatch[1]] = propMatch[2];
|
|
165
|
+
}
|
|
134
166
|
}
|
|
167
|
+
// Clear the entity cache.
|
|
168
|
+
this.entityCache = {};
|
|
169
|
+
}
|
|
170
|
+
// Save the last entity.
|
|
171
|
+
if (guid) {
|
|
172
|
+
const cdate = Number(JSON.parse(sdata.cdate));
|
|
173
|
+
delete sdata.cdate;
|
|
174
|
+
const mdate = Number(JSON.parse(sdata.mdate));
|
|
175
|
+
delete sdata.mdate;
|
|
176
|
+
await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
|
|
177
|
+
}
|
|
178
|
+
if (transaction) {
|
|
179
|
+
await this.commit('nymph-import');
|
|
135
180
|
}
|
|
136
|
-
this.entityCache = {};
|
|
137
|
-
}
|
|
138
|
-
if (guid) {
|
|
139
|
-
await saveEntityCallback(guid, tags, sdata, etype);
|
|
140
181
|
}
|
|
141
|
-
|
|
142
|
-
await
|
|
182
|
+
catch (e) {
|
|
183
|
+
await this.rollback('nymph-import');
|
|
184
|
+
throw e;
|
|
143
185
|
}
|
|
144
186
|
return true;
|
|
145
187
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
pass = (0, utils_1.xor)(this.checkData(data, sdata, tmpArr, guid, tags), (0, utils_1.xor)(typeIsNot, clauseNot));
|
|
188
|
+
async importData(text, transaction) {
|
|
189
|
+
return await this.importDataIterator(text.split('\n'), transaction);
|
|
190
|
+
}
|
|
191
|
+
async import(filename, transaction) {
|
|
192
|
+
let rl;
|
|
193
|
+
try {
|
|
194
|
+
rl = new n_readlines_1.default(filename);
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
throw new errors_1.InvalidParametersError('Provided filename is unreadable.');
|
|
198
|
+
}
|
|
199
|
+
const lines = {
|
|
200
|
+
*[Symbol.iterator]() {
|
|
201
|
+
let line;
|
|
202
|
+
while ((line = rl.next())) {
|
|
203
|
+
yield line.toString('utf8');
|
|
163
204
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
return await this.importDataIterator(lines, transaction);
|
|
208
|
+
}
|
|
209
|
+
checkData(data, sdata, selectors, guid = null, tags = null) {
|
|
210
|
+
try {
|
|
211
|
+
const formattedSelectors = this.formatSelectors(selectors).selectors;
|
|
212
|
+
for (const curSelector of formattedSelectors) {
|
|
213
|
+
const type = curSelector.type;
|
|
214
|
+
const typeIsNot = type === '!&' || type === '!|';
|
|
215
|
+
const typeIsOr = type === '|' || type === '!|';
|
|
216
|
+
let pass = !typeIsOr;
|
|
217
|
+
for (const k in curSelector) {
|
|
218
|
+
const key = k;
|
|
219
|
+
const value = curSelector[key];
|
|
220
|
+
if (key === 'type' || value == null) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const clauseNot = key.substring(0, 1) === '!';
|
|
224
|
+
if (key === 'selector' || key === '!selector') {
|
|
225
|
+
const tmpArr = (Array.isArray(value) ? value : [value]);
|
|
226
|
+
pass = (0, utils_1.xor)(this.checkData(data, sdata, tmpArr, guid, tags), (0, utils_1.xor)(typeIsNot, clauseNot));
|
|
167
227
|
}
|
|
168
228
|
else {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
229
|
+
if (key === 'qref' || key === '!qref') {
|
|
230
|
+
throw new Error("Can't use checkData on qref clauses.");
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
// Check if it doesn't pass any for &, check if it passes any for |.
|
|
234
|
+
for (const curValue of value) {
|
|
235
|
+
if (curValue == null) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
178
238
|
const propName = curValue[0];
|
|
239
|
+
// Unserialize the data for this variable.
|
|
179
240
|
if (propName in sdata) {
|
|
180
241
|
data[propName] = JSON.parse(sdata[propName]);
|
|
181
242
|
delete sdata[propName];
|
|
@@ -229,12 +290,14 @@ class NymphDriver {
|
|
|
229
290
|
case 'match':
|
|
230
291
|
case '!match':
|
|
231
292
|
const testMatchValue = curValue[1];
|
|
293
|
+
// Convert a POSIX regex to a JS regex.
|
|
232
294
|
pass = (0, utils_1.xor)(propName in data &&
|
|
233
295
|
this.posixRegexMatch(testMatchValue, data[propName]), (0, utils_1.xor)(typeIsNot, clauseNot));
|
|
234
296
|
break;
|
|
235
297
|
case 'imatch':
|
|
236
298
|
case '!imatch':
|
|
237
299
|
const testIMatchValue = curValue[1];
|
|
300
|
+
// Convert a POSIX regex to a JS regex.
|
|
238
301
|
pass = (0, utils_1.xor)(propName in data &&
|
|
239
302
|
this.posixRegexMatch(testIMatchValue, data[propName], true), (0, utils_1.xor)(typeIsNot, clauseNot));
|
|
240
303
|
break;
|
|
@@ -259,23 +322,32 @@ class NymphDriver {
|
|
|
259
322
|
pass = (0, utils_1.xor)(propName in data && data[propName] <= testLTEValue, (0, utils_1.xor)(typeIsNot, clauseNot));
|
|
260
323
|
break;
|
|
261
324
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
325
|
+
if (!(0, utils_1.xor)(typeIsOr, pass)) {
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
265
328
|
}
|
|
266
329
|
}
|
|
267
330
|
}
|
|
331
|
+
if (!(0, utils_1.xor)(typeIsOr, pass)) {
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
268
334
|
}
|
|
269
|
-
if (!
|
|
270
|
-
|
|
335
|
+
if (!pass) {
|
|
336
|
+
return false;
|
|
271
337
|
}
|
|
272
338
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
catch (e) {
|
|
342
|
+
this.nymph.config.debugError('nymph', `Failed to check entity data: ${e}`);
|
|
343
|
+
throw e;
|
|
276
344
|
}
|
|
277
|
-
return true;
|
|
278
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Remove all copies of an entity from the cache.
|
|
348
|
+
*
|
|
349
|
+
* @param guid The GUID of the entity to remove.
|
|
350
|
+
*/
|
|
279
351
|
cleanCache(guid) {
|
|
280
352
|
delete this.entityCache[guid];
|
|
281
353
|
}
|
|
@@ -292,7 +364,15 @@ class NymphDriver {
|
|
|
292
364
|
}
|
|
293
365
|
return ret;
|
|
294
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Search through a value for an entity reference.
|
|
369
|
+
*
|
|
370
|
+
* @param value Any value to search.
|
|
371
|
+
* @param entity An entity, GUID, or array of either to search for.
|
|
372
|
+
* @returns True if the reference is found, false otherwise.
|
|
373
|
+
*/
|
|
295
374
|
entityReferenceSearch(value, entity) {
|
|
375
|
+
// Get the GUID, if the passed $entity is an object.
|
|
296
376
|
const guids = [];
|
|
297
377
|
if (Array.isArray(entity)) {
|
|
298
378
|
if (entity[0] === 'nymph_entity_reference') {
|
|
@@ -323,6 +403,7 @@ class NymphDriver {
|
|
|
323
403
|
guids.indexOf(value[1]) !== -1) {
|
|
324
404
|
return true;
|
|
325
405
|
}
|
|
406
|
+
// Search through arrays and objects looking for the reference.
|
|
326
407
|
if (Array.isArray(value) || typeof value === 'object') {
|
|
327
408
|
for (const key in value) {
|
|
328
409
|
if (this.entityReferenceSearch(value[key], guids)) {
|
|
@@ -356,9 +437,9 @@ class NymphDriver {
|
|
|
356
437
|
for (let i = 0; i < tmpArr.length; i++) {
|
|
357
438
|
const name = tmpArr[i][0];
|
|
358
439
|
const [qrefOptions, ...qrefSelectors] = tmpArr[i][1];
|
|
359
|
-
const QrefEntityClass =
|
|
440
|
+
const QrefEntityClass = qrefOptions.class
|
|
360
441
|
? this.nymph.getEntityClass(qrefOptions.class)
|
|
361
|
-
:
|
|
442
|
+
: this.nymph.getEntityClass('Entity');
|
|
362
443
|
const newOptions = {
|
|
363
444
|
...qrefOptions,
|
|
364
445
|
class: QrefEntityClass,
|
|
@@ -377,19 +458,23 @@ class NymphDriver {
|
|
|
377
458
|
qrefs.push(...newSelectors.qrefs);
|
|
378
459
|
}
|
|
379
460
|
else if (!Array.isArray(value)) {
|
|
461
|
+
// @ts-ignore: ts doesn't know what value is here.
|
|
380
462
|
newSelector[key] = [[value]];
|
|
381
463
|
}
|
|
382
464
|
else if (!Array.isArray(value[0])) {
|
|
383
465
|
if (value.length === 3 &&
|
|
384
466
|
value[1] == null &&
|
|
385
467
|
typeof value[2] === 'string') {
|
|
468
|
+
// @ts-ignore: ts doesn't know what value is here.
|
|
386
469
|
newSelector[key] = [[value[0], (0, strtotime_1.default)(value[2]) * 1000]];
|
|
387
470
|
}
|
|
388
471
|
else {
|
|
472
|
+
// @ts-ignore: ts doesn't know what value is here.
|
|
389
473
|
newSelector[key] = [value];
|
|
390
474
|
}
|
|
391
475
|
}
|
|
392
476
|
else {
|
|
477
|
+
// @ts-ignore: ts doesn't know what value is here.
|
|
393
478
|
newSelector[key] = value.map((curValue) => {
|
|
394
479
|
if (curValue.length === 3 &&
|
|
395
480
|
curValue[1] == null &&
|
|
@@ -416,7 +501,7 @@ class NymphDriver {
|
|
|
416
501
|
if (key === 'type') {
|
|
417
502
|
continue;
|
|
418
503
|
}
|
|
419
|
-
let curQuery = callback(key, value, typeIsOr, typeIsNot);
|
|
504
|
+
let curQuery = callback({ key, value, typeIsOr, typeIsNot });
|
|
420
505
|
if (curQuery) {
|
|
421
506
|
if (curSelectorQuery) {
|
|
422
507
|
curSelectorQuery += typeIsOr ? ' OR ' : ' AND ';
|
|
@@ -430,21 +515,7 @@ class NymphDriver {
|
|
|
430
515
|
}
|
|
431
516
|
return queryParts;
|
|
432
517
|
}
|
|
433
|
-
|
|
434
|
-
if (typeof selectors[0] === 'string') {
|
|
435
|
-
selectors = [{ type: '&', guid: selectors[0] }];
|
|
436
|
-
}
|
|
437
|
-
options.limit = 1;
|
|
438
|
-
const entities = this.getEntitiesSync(options, ...selectors);
|
|
439
|
-
if (options.return === 'count') {
|
|
440
|
-
return entities;
|
|
441
|
-
}
|
|
442
|
-
if (!entities || !entities.length) {
|
|
443
|
-
return null;
|
|
444
|
-
}
|
|
445
|
-
return entities[0];
|
|
446
|
-
}
|
|
447
|
-
getEntitesRowLike(options, selectors, performQueryCallback, rowFetchCallback, freeResultCallback, getCountCallback, getGUIDCallback, getTagsAndDatesCallback, getDataNameAndSValueCallback) {
|
|
518
|
+
getEntitiesRowLike(options, selectors, performQueryCallback, rowFetchCallback, freeResultCallback, getCountCallback, getGUIDCallback, getTagsAndDatesCallback, getDataNameAndSValueCallback) {
|
|
448
519
|
if (!this.isConnected()) {
|
|
449
520
|
throw new errors_1.UnableToConnectError('not connected to DB');
|
|
450
521
|
}
|
|
@@ -460,10 +531,14 @@ class NymphDriver {
|
|
|
460
531
|
let count = 0;
|
|
461
532
|
const EntityClass = options.class ?? this.nymph.getEntityClass('Entity');
|
|
462
533
|
const etype = EntityClass.ETYPE;
|
|
534
|
+
// Check if the requested entity is cached.
|
|
463
535
|
if (this.nymph.config.cache &&
|
|
464
536
|
!options.skipCache &&
|
|
537
|
+
selectors.length &&
|
|
465
538
|
'guid' in selectors[0] &&
|
|
466
539
|
typeof selectors[0].guid === 'number') {
|
|
540
|
+
// Only safe to use the cache option with no other selectors than a GUID
|
|
541
|
+
// and tags.
|
|
467
542
|
if (selectors.length === 1 &&
|
|
468
543
|
selectors[0].type === '&' &&
|
|
469
544
|
(Object.keys(selectors[0]).length === 2 ||
|
|
@@ -492,11 +567,16 @@ class NymphDriver {
|
|
|
492
567
|
try {
|
|
493
568
|
const formattedSelectors = this.formatSelectors(selectors, options);
|
|
494
569
|
this.nymph.runQueryCallbacks(options, formattedSelectors.selectors);
|
|
495
|
-
for (let
|
|
496
|
-
const [options, ...selectors] =
|
|
570
|
+
for (let i = 0; i < formattedSelectors.qrefs.length; i++) {
|
|
571
|
+
const [options, ...selectors] = formattedSelectors.qrefs[i];
|
|
497
572
|
this.nymph.runQueryCallbacks(options, selectors);
|
|
573
|
+
formattedSelectors.qrefs[i] = [options, ...selectors];
|
|
498
574
|
}
|
|
499
|
-
const { result } = performQueryCallback(
|
|
575
|
+
const { result } = performQueryCallback({
|
|
576
|
+
options,
|
|
577
|
+
selectors: formattedSelectors.selectors,
|
|
578
|
+
etype,
|
|
579
|
+
});
|
|
500
580
|
return {
|
|
501
581
|
result,
|
|
502
582
|
process: () => {
|
|
@@ -521,9 +601,13 @@ class NymphDriver {
|
|
|
521
601
|
const cdate = tagsAndDates.cdate;
|
|
522
602
|
const mdate = tagsAndDates.mdate;
|
|
523
603
|
let dataNameAndSValue = getDataNameAndSValueCallback(row);
|
|
604
|
+
// Data.
|
|
524
605
|
const data = {};
|
|
606
|
+
// Serialized data.
|
|
525
607
|
const sdata = {};
|
|
526
608
|
if (dataNameAndSValue.name !== '') {
|
|
609
|
+
// This do will keep going and adding the data until the
|
|
610
|
+
// next entity is reached. $row will end on the next entity.
|
|
527
611
|
do {
|
|
528
612
|
dataNameAndSValue = getDataNameAndSValueCallback(row);
|
|
529
613
|
sdata[dataNameAndSValue.name] = dataNameAndSValue.svalue;
|
|
@@ -531,6 +615,7 @@ class NymphDriver {
|
|
|
531
615
|
} while (row != null && getGUIDCallback(row) === guid);
|
|
532
616
|
}
|
|
533
617
|
else {
|
|
618
|
+
// Make sure that $row is incremented :)
|
|
534
619
|
row = rowFetchCallback();
|
|
535
620
|
}
|
|
536
621
|
const entity = EntityClass.factorySync();
|
|
@@ -551,6 +636,7 @@ class NymphDriver {
|
|
|
551
636
|
if (this.nymph.config.cache) {
|
|
552
637
|
this.pushCache(guid, cdate, mdate, tags, data, sdata);
|
|
553
638
|
}
|
|
639
|
+
// @ts-ignore: ts doesn't know the return type here.
|
|
554
640
|
entities.push(entity);
|
|
555
641
|
}
|
|
556
642
|
}
|
|
@@ -569,10 +655,12 @@ class NymphDriver {
|
|
|
569
655
|
}
|
|
570
656
|
}
|
|
571
657
|
async saveEntityRowLike(entity, saveNewEntityCallback, saveExistingEntityCallback, startTransactionCallback = null, commitTransactionCallback = null) {
|
|
658
|
+
// Get a modified date.
|
|
572
659
|
const mdate = Date.now();
|
|
573
660
|
const tags = (0, lodash_1.difference)(entity.tags, ['']);
|
|
574
661
|
const data = entity.$getData();
|
|
575
662
|
const sdata = entity.$getSData();
|
|
663
|
+
const uniques = await entity.$getUniques();
|
|
576
664
|
const EntityClass = entity.constructor;
|
|
577
665
|
const etype = EntityClass.ETYPE;
|
|
578
666
|
if (startTransactionCallback) {
|
|
@@ -582,7 +670,16 @@ class NymphDriver {
|
|
|
582
670
|
if (entity.guid == null) {
|
|
583
671
|
const cdate = mdate;
|
|
584
672
|
const newId = (0, guid_1.guid)();
|
|
585
|
-
success = await saveNewEntityCallback(
|
|
673
|
+
success = await saveNewEntityCallback({
|
|
674
|
+
entity,
|
|
675
|
+
guid: newId,
|
|
676
|
+
tags,
|
|
677
|
+
data,
|
|
678
|
+
sdata,
|
|
679
|
+
uniques,
|
|
680
|
+
cdate,
|
|
681
|
+
etype,
|
|
682
|
+
});
|
|
586
683
|
if (success) {
|
|
587
684
|
entity.guid = newId;
|
|
588
685
|
entity.cdate = cdate;
|
|
@@ -590,10 +687,20 @@ class NymphDriver {
|
|
|
590
687
|
}
|
|
591
688
|
}
|
|
592
689
|
else {
|
|
690
|
+
// Removed any cached versions of this entity.
|
|
593
691
|
if (this.nymph.config.cache) {
|
|
594
692
|
this.cleanCache(entity.guid);
|
|
595
693
|
}
|
|
596
|
-
success = await saveExistingEntityCallback(
|
|
694
|
+
success = await saveExistingEntityCallback({
|
|
695
|
+
entity,
|
|
696
|
+
guid: entity.guid,
|
|
697
|
+
tags,
|
|
698
|
+
data,
|
|
699
|
+
sdata,
|
|
700
|
+
uniques,
|
|
701
|
+
mdate,
|
|
702
|
+
etype,
|
|
703
|
+
});
|
|
597
704
|
if (success) {
|
|
598
705
|
entity.mdate = mdate;
|
|
599
706
|
}
|
|
@@ -601,12 +708,22 @@ class NymphDriver {
|
|
|
601
708
|
if (commitTransactionCallback) {
|
|
602
709
|
success = await commitTransactionCallback(success);
|
|
603
710
|
}
|
|
711
|
+
// Cache the entity.
|
|
604
712
|
if (success && this.nymph.config.cache) {
|
|
605
713
|
this.pushCache(entity.guid, entity.cdate, entity.mdate, entity.tags, data, sdata);
|
|
606
714
|
}
|
|
607
715
|
return success;
|
|
608
716
|
}
|
|
717
|
+
/**
|
|
718
|
+
* Pull an entity from the cache.
|
|
719
|
+
*
|
|
720
|
+
* @param guid The entity's GUID.
|
|
721
|
+
* @param className The entity's class.
|
|
722
|
+
* @param useSkipAc Whether to tell the entity to use skip_ac.
|
|
723
|
+
* @returns The entity or null if it's not cached.
|
|
724
|
+
*/
|
|
609
725
|
pullCache(guid, className, useSkipAc = false) {
|
|
726
|
+
// Increment the entity access count.
|
|
610
727
|
if (!(guid in this.entityCount)) {
|
|
611
728
|
this.entityCount[guid] = 0;
|
|
612
729
|
}
|
|
@@ -626,17 +743,30 @@ class NymphDriver {
|
|
|
626
743
|
}
|
|
627
744
|
return null;
|
|
628
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* Push an entity onto the cache.
|
|
748
|
+
*
|
|
749
|
+
* @param guid The entity's GUID.
|
|
750
|
+
* @param cdate The entity's cdate.
|
|
751
|
+
* @param mdate The entity's mdate.
|
|
752
|
+
* @param tags The entity's tags.
|
|
753
|
+
* @param data The entity's data.
|
|
754
|
+
* @param sdata The entity's sdata.
|
|
755
|
+
*/
|
|
629
756
|
pushCache(guid, cdate, mdate, tags, data, sdata) {
|
|
630
757
|
if (guid == null) {
|
|
631
758
|
return;
|
|
632
759
|
}
|
|
760
|
+
// Increment the entity access count.
|
|
633
761
|
if (!(guid in this.entityCount)) {
|
|
634
762
|
this.entityCount[guid] = 0;
|
|
635
763
|
}
|
|
636
764
|
this.entityCount[guid]++;
|
|
765
|
+
// Check the threshold.
|
|
637
766
|
if (this.entityCount[guid] < this.nymph.config.cacheThreshold) {
|
|
638
767
|
return;
|
|
639
768
|
}
|
|
769
|
+
// Cache the entity.
|
|
640
770
|
this.entityCache[guid] = {
|
|
641
771
|
cdate: cdate,
|
|
642
772
|
mdate: mdate,
|
|
@@ -646,12 +776,15 @@ class NymphDriver {
|
|
|
646
776
|
};
|
|
647
777
|
while (this.nymph.config.cacheLimit &&
|
|
648
778
|
Object.keys(this.entityCache).length >= this.nymph.config.cacheLimit) {
|
|
779
|
+
// Find which entity has been accessed the least.
|
|
649
780
|
const least = Object.entries(this.entityCount)
|
|
650
|
-
.sort(([
|
|
781
|
+
.sort(([_guidA, countA], [_guidB, countB]) => countA - countB)
|
|
651
782
|
.pop()?.[0] ?? null;
|
|
652
783
|
if (least == null) {
|
|
784
|
+
// This should never happen.
|
|
653
785
|
return;
|
|
654
786
|
}
|
|
787
|
+
// Remove it.
|
|
655
788
|
delete this.entityCache[least];
|
|
656
789
|
delete this.entityCount[least];
|
|
657
790
|
}
|