@nymphjs/nymph 1.0.0-beta.1 → 1.0.0-beta.100
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 +535 -0
- package/README.md +14 -22
- package/dist/Entity.d.ts +293 -10
- package/dist/Entity.js +571 -149
- package/dist/Entity.js.map +1 -1
- package/dist/Entity.test.js +67 -26
- package/dist/Entity.test.js.map +1 -1
- package/dist/Entity.types.d.ts +256 -23
- package/dist/Entity.types.js +1 -2
- package/dist/Nymph.d.ts +497 -13
- package/dist/Nymph.js +761 -188
- package/dist/Nymph.js.map +1 -1
- package/dist/Nymph.types.d.ts +95 -26
- package/dist/Nymph.types.js +3 -5
- package/dist/Nymph.types.js.map +1 -1
- package/dist/conf/d.d.ts +43 -0
- package/dist/conf/d.js +1 -2
- package/dist/conf/defaults.d.ts +1 -1
- package/dist/conf/defaults.js +26 -3
- package/dist/conf/defaults.js.map +1 -1
- package/dist/conf/index.d.ts +2 -2
- package/dist/conf/index.js +2 -8
- package/dist/conf/index.js.map +1 -1
- package/dist/driver/NymphDriver.d.ts +181 -43
- package/dist/driver/NymphDriver.js +532 -285
- package/dist/driver/NymphDriver.js.map +1 -1
- package/dist/driver/index.d.ts +2 -2
- package/dist/driver/index.js +3 -23
- package/dist/driver/index.js.map +1 -1
- package/dist/driver/testArtifacts.d.ts +2 -2
- package/dist/driver/testArtifacts.js +939 -219
- package/dist/driver/testArtifacts.js.map +1 -1
- package/dist/errors/ClassNotAvailableError.js +1 -5
- package/dist/errors/ClassNotAvailableError.js.map +1 -1
- package/dist/errors/EntityConflictError.js +1 -5
- package/dist/errors/EntityConflictError.js.map +1 -1
- package/dist/errors/EntityInvalidDataError.d.ts +6 -0
- package/dist/errors/EntityInvalidDataError.js +8 -6
- package/dist/errors/EntityInvalidDataError.js.map +1 -1
- package/dist/errors/EntityIsSleepingReferenceError.js +1 -5
- package/dist/errors/EntityIsSleepingReferenceError.js.map +1 -1
- package/dist/errors/EntityUniqueConstraintError.d.ts +3 -0
- package/dist/errors/EntityUniqueConstraintError.js +7 -0
- package/dist/errors/EntityUniqueConstraintError.js.map +1 -0
- package/dist/errors/InvalidParametersError.js +1 -5
- package/dist/errors/InvalidParametersError.js.map +1 -1
- package/dist/errors/InvalidStateError.d.ts +3 -0
- package/dist/errors/InvalidStateError.js +7 -0
- package/dist/errors/InvalidStateError.js.map +1 -0
- package/dist/errors/NotConfiguredError.js +1 -5
- package/dist/errors/NotConfiguredError.js.map +1 -1
- package/dist/errors/QueryFailedError.js +2 -5
- package/dist/errors/QueryFailedError.js.map +1 -1
- package/dist/errors/UnableToConnectError.js +1 -5
- package/dist/errors/UnableToConnectError.js.map +1 -1
- package/dist/errors/index.d.ts +10 -8
- package/dist/errors/index.js +10 -24
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +14 -34
- package/dist/index.js.map +1 -1
- package/dist/nymphJoiProps.js +10 -15
- package/dist/nymphJoiProps.js.map +1 -1
- package/dist/testArtifacts.d.ts +19 -6
- package/dist/testArtifacts.js +32 -37
- package/dist/testArtifacts.js.map +1 -1
- package/dist/testMocks.d.ts +28 -11
- package/dist/testMocks.js +44 -28
- package/dist/testMocks.js.map +1 -1
- package/dist/utils.d.ts +4 -4
- package/dist/utils.js +40 -29
- package/dist/utils.js.map +1 -1
- package/jest.config.js +11 -2
- package/package.json +24 -20
- package/src/Entity.test.ts +63 -13
- package/src/Entity.ts +498 -156
- package/src/Entity.types.ts +72 -74
- package/src/Nymph.ts +904 -336
- package/src/Nymph.types.ts +179 -44
- package/src/conf/d.ts +25 -0
- package/src/conf/defaults.ts +34 -1
- package/src/conf/index.ts +2 -2
- package/src/driver/NymphDriver.ts +650 -460
- package/src/driver/index.ts +2 -2
- package/src/driver/testArtifacts.ts +966 -196
- package/src/errors/EntityUniqueConstraintError.ts +6 -0
- package/src/errors/InvalidStateError.ts +6 -0
- package/src/errors/index.ts +12 -8
- package/src/index.ts +11 -11
- package/src/nymphJoiProps.ts +2 -2
- package/src/testArtifacts.ts +20 -20
- package/src/testMocks.ts +85 -37
- package/src/type/locutus.d.ts +3 -0
- package/src/utils.ts +39 -18
- package/tsconfig.json +5 -3
- package/typedoc.json +4 -0
|
@@ -1,34 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const strtotime_1 = __importDefault(require("locutus/php/datetime/strtotime"));
|
|
10
|
-
const guid_1 = require("@nymphjs/guid");
|
|
11
|
-
const errors_1 = require("../errors");
|
|
12
|
-
const utils_1 = require("../utils");
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { difference } from 'lodash-es';
|
|
3
|
+
import ReadLines from 'n-readlines';
|
|
4
|
+
import strtotime from 'locutus/php/datetime/strtotime.js';
|
|
5
|
+
import { Tokenizer } from '@sciactive/tokenizer';
|
|
6
|
+
import { InvalidParametersError, UnableToConnectError, } from '../errors/index.js';
|
|
7
|
+
import { xor } from '../utils.js';
|
|
8
|
+
// from: https://stackoverflow.com/a/6969486/664915
|
|
13
9
|
function escapeRegExp(string) {
|
|
14
|
-
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
10
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
15
11
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
/**
|
|
13
|
+
* A Nymph database driver.
|
|
14
|
+
*/
|
|
15
|
+
export default class NymphDriver {
|
|
16
|
+
nymph = new Proxy({}, {
|
|
17
|
+
get() {
|
|
18
|
+
throw new Error('Attempted access of Nymph instance before driver initialization!');
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
tokenizer = new Tokenizer();
|
|
22
|
+
/**
|
|
23
|
+
* A cache to make entity retrieval faster.
|
|
24
|
+
*/
|
|
25
|
+
entityCache = {};
|
|
26
|
+
/**
|
|
27
|
+
* A counter for the entity cache to determine the most accessed entities.
|
|
28
|
+
*/
|
|
29
|
+
entityCount = {};
|
|
30
|
+
/**
|
|
31
|
+
* Protect against infinite loops.
|
|
32
|
+
*/
|
|
33
|
+
putDataCounter = 0;
|
|
34
|
+
/**
|
|
35
|
+
* Initialize the Nymph driver.
|
|
36
|
+
*
|
|
37
|
+
* This is meant to be called internally by Nymph. Don't call this directly.
|
|
38
|
+
*
|
|
39
|
+
* @param nymph The Nymph instance.
|
|
40
|
+
*/
|
|
27
41
|
init(nymph) {
|
|
28
42
|
this.nymph = nymph;
|
|
29
43
|
}
|
|
30
44
|
posixRegexMatch(pattern, subject, caseInsensitive = false) {
|
|
31
45
|
const posixClasses = [
|
|
46
|
+
// '[[:<:]]',
|
|
47
|
+
// '[[:>:]]',
|
|
32
48
|
'[:alnum:]',
|
|
33
49
|
'[:alpha:]',
|
|
34
50
|
'[:ascii:]',
|
|
@@ -45,17 +61,21 @@ class NymphDriver {
|
|
|
45
61
|
'[:xdigit:]',
|
|
46
62
|
];
|
|
47
63
|
const jsClasses = [
|
|
64
|
+
// '\b(?=\w)',
|
|
65
|
+
// '(?<=\w)\b',
|
|
48
66
|
'[A-Za-z0-9]',
|
|
49
67
|
'[A-Za-z]',
|
|
50
68
|
'[\x00-\x7F]',
|
|
51
|
-
'[ \t]',
|
|
52
|
-
'[\x00-\x1F\x7F]',
|
|
53
|
-
'
|
|
54
|
-
'
|
|
69
|
+
'[ \t]', // '\s',
|
|
70
|
+
'[\x00-\x1F\x7F]', // '[\000\001\002\003\004\005\006\007\008\009\010\011\012\013\014'.
|
|
71
|
+
// '\015\016\017\018\019\020\021\022\023\024\025\026\027\028\029'.
|
|
72
|
+
// '\030\031\032\033\034\035\036\037\177]',
|
|
73
|
+
'[0-9]', // '\d',
|
|
74
|
+
'[\x21-\x7E]', // '[A-Za-z0-9!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
|
|
55
75
|
'[a-z]',
|
|
56
|
-
'[\x20-\x7E]',
|
|
57
|
-
'[!"#$%&\'()*+,-./:;<=>?@[\\\\\\]^_‘{|}~]',
|
|
58
|
-
'[ \t\r\n\v\f]',
|
|
76
|
+
'[\x20-\x7E]', // '[A-Za-z0-9!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
|
|
77
|
+
'[!"#$%&\'()*+,-./:;<=>?@[\\\\\\]^_‘{|}~]', // '[!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
|
|
78
|
+
'[ \t\r\n\v\f]', // '[\t\n\x0B\f\r ]',
|
|
59
79
|
'[A-Z]',
|
|
60
80
|
'[A-Za-z0-9_]',
|
|
61
81
|
'[0-9A-Fa-f]',
|
|
@@ -69,14 +89,14 @@ class NymphDriver {
|
|
|
69
89
|
}
|
|
70
90
|
async export(filename) {
|
|
71
91
|
try {
|
|
72
|
-
const fhandle =
|
|
92
|
+
const fhandle = fs.openSync(filename, 'w');
|
|
73
93
|
if (!fhandle) {
|
|
74
|
-
throw new
|
|
94
|
+
throw new InvalidParametersError('Provided filename is not writeable.');
|
|
75
95
|
}
|
|
76
|
-
await this.
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
96
|
+
for await (let entry of this.exportDataIterator()) {
|
|
97
|
+
fs.writeSync(fhandle, entry.content);
|
|
98
|
+
}
|
|
99
|
+
fs.closeSync(fhandle);
|
|
80
100
|
}
|
|
81
101
|
catch (e) {
|
|
82
102
|
return false;
|
|
@@ -84,98 +104,136 @@ class NymphDriver {
|
|
|
84
104
|
return true;
|
|
85
105
|
}
|
|
86
106
|
async exportPrint() {
|
|
87
|
-
await this.
|
|
88
|
-
console.log(
|
|
89
|
-
}
|
|
107
|
+
for await (let entry of this.exportDataIterator()) {
|
|
108
|
+
console.log(entry.content);
|
|
109
|
+
}
|
|
90
110
|
return true;
|
|
91
111
|
}
|
|
92
|
-
async
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
catch (e) {
|
|
98
|
-
throw new errors_1.InvalidParametersError('Provided filename is unreadable.');
|
|
112
|
+
async importDataIterator(lines, transaction) {
|
|
113
|
+
const first = lines[Symbol.iterator]().next();
|
|
114
|
+
if (first.done || first.value !== '#nex2') {
|
|
115
|
+
throw new Error('Tried to import a file that is not a NEX v2 file.');
|
|
99
116
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
let sdata = {};
|
|
103
|
-
let tags = [];
|
|
104
|
-
let etype = '__undefined';
|
|
105
|
-
if (startTransactionCallback) {
|
|
106
|
-
await startTransactionCallback();
|
|
117
|
+
if (transaction) {
|
|
118
|
+
await this.internalTransaction('nymph-import');
|
|
107
119
|
}
|
|
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';
|
|
120
|
+
try {
|
|
121
|
+
let guid = null;
|
|
122
|
+
let sdata = {};
|
|
123
|
+
let tags = [];
|
|
124
|
+
let etype = '__undefined';
|
|
125
|
+
for (let line of lines) {
|
|
126
|
+
if (line.match(/^\s*#/)) {
|
|
127
|
+
continue;
|
|
126
128
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
const entityMatch = line.match(/^\s*{([0-9A-Fa-f]+)}<([-\w_]+)>\[([^\]]*)\]\s*$/);
|
|
130
|
+
const propMatch = line.match(/^\s*([^=]+)\s*=\s*(.*\S)\s*$/);
|
|
131
|
+
const uidMatch = line.match(/^\s*<([^>]+)>\[(\d+)\]\s*$/);
|
|
132
|
+
if (uidMatch) {
|
|
133
|
+
// Add the UID.
|
|
134
|
+
await this.importUID({
|
|
135
|
+
name: uidMatch[1],
|
|
136
|
+
value: Number(uidMatch[2]),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
else if (entityMatch) {
|
|
140
|
+
// Save the current entity.
|
|
141
|
+
if (guid) {
|
|
142
|
+
const cdate = Number(JSON.parse(sdata.cdate));
|
|
143
|
+
delete sdata.cdate;
|
|
144
|
+
const mdate = Number(JSON.parse(sdata.mdate));
|
|
145
|
+
delete sdata.mdate;
|
|
146
|
+
await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
|
|
147
|
+
guid = null;
|
|
148
|
+
tags = [];
|
|
149
|
+
sdata = {};
|
|
150
|
+
etype = '__undefined';
|
|
151
|
+
}
|
|
152
|
+
// Record the new entity's info.
|
|
153
|
+
guid = entityMatch[1];
|
|
154
|
+
etype = entityMatch[2];
|
|
155
|
+
tags = entityMatch[3].split(',');
|
|
156
|
+
}
|
|
157
|
+
else if (propMatch) {
|
|
158
|
+
// Add the variable to the new entity.
|
|
159
|
+
if (guid) {
|
|
160
|
+
sdata[propMatch[1]] = propMatch[2];
|
|
161
|
+
}
|
|
134
162
|
}
|
|
163
|
+
// Clear the entity cache.
|
|
164
|
+
this.entityCache = {};
|
|
165
|
+
}
|
|
166
|
+
// Save the last entity.
|
|
167
|
+
if (guid) {
|
|
168
|
+
const cdate = Number(JSON.parse(sdata.cdate));
|
|
169
|
+
delete sdata.cdate;
|
|
170
|
+
const mdate = Number(JSON.parse(sdata.mdate));
|
|
171
|
+
delete sdata.mdate;
|
|
172
|
+
await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
|
|
173
|
+
}
|
|
174
|
+
if (transaction) {
|
|
175
|
+
await this.commit('nymph-import');
|
|
135
176
|
}
|
|
136
|
-
this.entityCache = {};
|
|
137
|
-
}
|
|
138
|
-
if (guid) {
|
|
139
|
-
await saveEntityCallback(guid, tags, sdata, etype);
|
|
140
177
|
}
|
|
141
|
-
|
|
142
|
-
await
|
|
178
|
+
catch (e) {
|
|
179
|
+
await this.rollback('nymph-import');
|
|
180
|
+
throw e;
|
|
143
181
|
}
|
|
144
182
|
return true;
|
|
145
183
|
}
|
|
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));
|
|
184
|
+
async importData(text, transaction) {
|
|
185
|
+
return await this.importDataIterator(text.split('\n'), transaction);
|
|
186
|
+
}
|
|
187
|
+
async import(filename, transaction) {
|
|
188
|
+
let rl;
|
|
189
|
+
try {
|
|
190
|
+
rl = new ReadLines(filename);
|
|
191
|
+
}
|
|
192
|
+
catch (e) {
|
|
193
|
+
throw new InvalidParametersError('Provided filename is unreadable.');
|
|
194
|
+
}
|
|
195
|
+
const lines = {
|
|
196
|
+
*[Symbol.iterator]() {
|
|
197
|
+
let line;
|
|
198
|
+
while ((line = rl.next())) {
|
|
199
|
+
yield line.toString('utf8');
|
|
163
200
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
return await this.importDataIterator(lines, transaction);
|
|
204
|
+
}
|
|
205
|
+
checkData(etype, data, sdata, selectors, guid = null, tags = null) {
|
|
206
|
+
try {
|
|
207
|
+
const EntityClass = this.nymph.getEntityClassByEtype(etype);
|
|
208
|
+
const formattedSelectors = this.formatSelectors(selectors).selectors;
|
|
209
|
+
for (const curSelector of formattedSelectors) {
|
|
210
|
+
const type = curSelector.type;
|
|
211
|
+
const typeIsNot = type === '!&' || type === '!|';
|
|
212
|
+
const typeIsOr = type === '|' || type === '!|';
|
|
213
|
+
let pass = !typeIsOr;
|
|
214
|
+
for (const k in curSelector) {
|
|
215
|
+
const key = k;
|
|
216
|
+
const value = curSelector[key];
|
|
217
|
+
if (key === 'type' || value == null) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const clauseNot = key.substring(0, 1) === '!';
|
|
221
|
+
if (key === 'selector' || key === '!selector') {
|
|
222
|
+
const tmpArr = (Array.isArray(value) ? value : [value]);
|
|
223
|
+
pass = xor(this.checkData(etype, data, sdata, tmpArr, guid, tags), xor(typeIsNot, clauseNot));
|
|
167
224
|
}
|
|
168
225
|
else {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
226
|
+
if (key === 'qref' || key === '!qref') {
|
|
227
|
+
throw new Error("Can't use checkData on qref clauses.");
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
// Check if it doesn't pass any for &, check if it passes any for |.
|
|
231
|
+
for (const curValue of value) {
|
|
232
|
+
if (curValue == null) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
178
235
|
const propName = curValue[0];
|
|
236
|
+
// Unserialize the data for this variable.
|
|
179
237
|
if (propName in sdata) {
|
|
180
238
|
data[propName] = JSON.parse(sdata[propName]);
|
|
181
239
|
delete sdata[propName];
|
|
@@ -183,99 +241,130 @@ class NymphDriver {
|
|
|
183
241
|
switch (key) {
|
|
184
242
|
case 'guid':
|
|
185
243
|
case '!guid':
|
|
186
|
-
pass =
|
|
244
|
+
pass = xor(guid == propName, xor(typeIsNot, clauseNot));
|
|
187
245
|
break;
|
|
188
246
|
case 'tag':
|
|
189
247
|
case '!tag':
|
|
190
|
-
pass =
|
|
248
|
+
pass = xor((tags ?? []).indexOf(propName) !== -1, xor(typeIsNot, clauseNot));
|
|
191
249
|
break;
|
|
192
250
|
case 'defined':
|
|
193
251
|
case '!defined':
|
|
194
|
-
pass =
|
|
252
|
+
pass = xor(propName in data, xor(typeIsNot, clauseNot));
|
|
195
253
|
break;
|
|
196
254
|
case 'truthy':
|
|
197
255
|
case '!truthy':
|
|
198
|
-
pass =
|
|
256
|
+
pass = xor(propName in data && data[propName], xor(typeIsNot, clauseNot));
|
|
199
257
|
break;
|
|
200
258
|
case 'ref':
|
|
201
259
|
case '!ref':
|
|
202
260
|
const testRefValue = curValue[1];
|
|
203
|
-
pass =
|
|
204
|
-
this.entityReferenceSearch(data[propName], testRefValue),
|
|
261
|
+
pass = xor(propName in data &&
|
|
262
|
+
this.entityReferenceSearch(data[propName], testRefValue), xor(typeIsNot, clauseNot));
|
|
205
263
|
break;
|
|
206
264
|
case 'equal':
|
|
207
265
|
case '!equal':
|
|
208
266
|
const testEqualValue = curValue[1];
|
|
209
|
-
pass =
|
|
267
|
+
pass = xor(propName in data &&
|
|
210
268
|
JSON.stringify(data[propName]) ===
|
|
211
|
-
JSON.stringify(testEqualValue),
|
|
269
|
+
JSON.stringify(testEqualValue), xor(typeIsNot, clauseNot));
|
|
212
270
|
break;
|
|
213
271
|
case 'contain':
|
|
214
272
|
case '!contain':
|
|
215
273
|
const testContainValue = curValue[1];
|
|
216
|
-
pass =
|
|
217
|
-
JSON.stringify(data[propName]).indexOf(JSON.stringify(testContainValue)) !== -1,
|
|
274
|
+
pass = xor(propName in data &&
|
|
275
|
+
JSON.stringify(data[propName]).indexOf(JSON.stringify(testContainValue)) !== -1, xor(typeIsNot, clauseNot));
|
|
218
276
|
break;
|
|
219
277
|
case 'like':
|
|
220
278
|
case '!like':
|
|
221
279
|
const testLikeValue = curValue[1];
|
|
222
|
-
pass =
|
|
280
|
+
pass = xor(propName in data &&
|
|
223
281
|
new RegExp('^' +
|
|
224
282
|
escapeRegExp(testLikeValue)
|
|
225
283
|
.replace('%', () => '.*')
|
|
226
284
|
.replace('_', () => '.') +
|
|
227
|
-
'$').test(data[propName]),
|
|
285
|
+
'$').test(data[propName]), xor(typeIsNot, clauseNot));
|
|
286
|
+
break;
|
|
287
|
+
case 'ilike':
|
|
288
|
+
case '!ilike':
|
|
289
|
+
const testILikeValue = curValue[1];
|
|
290
|
+
pass = xor(propName in data &&
|
|
291
|
+
new RegExp('^' +
|
|
292
|
+
escapeRegExp(testILikeValue)
|
|
293
|
+
.replace('%', () => '.*')
|
|
294
|
+
.replace('_', () => '.') +
|
|
295
|
+
'$', 'i').test(data[propName]), xor(typeIsNot, clauseNot));
|
|
228
296
|
break;
|
|
229
297
|
case 'match':
|
|
230
298
|
case '!match':
|
|
231
299
|
const testMatchValue = curValue[1];
|
|
232
|
-
|
|
233
|
-
|
|
300
|
+
// Convert a POSIX regex to a JS regex.
|
|
301
|
+
pass = xor(propName in data &&
|
|
302
|
+
this.posixRegexMatch(testMatchValue, data[propName]), xor(typeIsNot, clauseNot));
|
|
234
303
|
break;
|
|
235
304
|
case 'imatch':
|
|
236
305
|
case '!imatch':
|
|
237
306
|
const testIMatchValue = curValue[1];
|
|
238
|
-
|
|
239
|
-
|
|
307
|
+
// Convert a POSIX regex to a JS regex.
|
|
308
|
+
pass = xor(propName in data &&
|
|
309
|
+
this.posixRegexMatch(testIMatchValue, data[propName], true), xor(typeIsNot, clauseNot));
|
|
310
|
+
break;
|
|
311
|
+
case 'search':
|
|
312
|
+
case '!search':
|
|
313
|
+
const testSearchValue = curValue[1];
|
|
314
|
+
const tokenString = propName in data
|
|
315
|
+
? (EntityClass.getFTSText(propName, data[propName]) ??
|
|
316
|
+
'')
|
|
317
|
+
: '';
|
|
318
|
+
pass = xor(propName in data &&
|
|
319
|
+
this.tokenizer.searchString(testSearchValue, tokenString), xor(typeIsNot, clauseNot));
|
|
240
320
|
break;
|
|
241
321
|
case 'gt':
|
|
242
322
|
case '!gt':
|
|
243
323
|
const testGTValue = curValue[1];
|
|
244
|
-
pass =
|
|
324
|
+
pass = xor(propName in data && data[propName] > testGTValue, xor(typeIsNot, clauseNot));
|
|
245
325
|
break;
|
|
246
326
|
case 'gte':
|
|
247
327
|
case '!gte':
|
|
248
328
|
const testGTEValue = curValue[1];
|
|
249
|
-
pass =
|
|
329
|
+
pass = xor(propName in data && data[propName] >= testGTEValue, xor(typeIsNot, clauseNot));
|
|
250
330
|
break;
|
|
251
331
|
case 'lt':
|
|
252
332
|
case '!lt':
|
|
253
333
|
const testLTValue = curValue[1];
|
|
254
|
-
pass =
|
|
334
|
+
pass = xor(propName in data && data[propName] < testLTValue, xor(typeIsNot, clauseNot));
|
|
255
335
|
break;
|
|
256
336
|
case 'lte':
|
|
257
337
|
case '!lte':
|
|
258
338
|
const testLTEValue = curValue[1];
|
|
259
|
-
pass =
|
|
339
|
+
pass = xor(propName in data && data[propName] <= testLTEValue, xor(typeIsNot, clauseNot));
|
|
260
340
|
break;
|
|
261
341
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
342
|
+
if (!xor(typeIsOr, pass)) {
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
265
345
|
}
|
|
266
346
|
}
|
|
267
347
|
}
|
|
348
|
+
if (!xor(typeIsOr, pass)) {
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
268
351
|
}
|
|
269
|
-
if (!
|
|
270
|
-
|
|
352
|
+
if (!pass) {
|
|
353
|
+
return false;
|
|
271
354
|
}
|
|
272
355
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
this.nymph.config.debugError('nymph', `Failed to check entity data: ${e}`);
|
|
360
|
+
throw e;
|
|
276
361
|
}
|
|
277
|
-
return true;
|
|
278
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Remove all copies of an entity from the cache.
|
|
365
|
+
*
|
|
366
|
+
* @param guid The GUID of the entity to remove.
|
|
367
|
+
*/
|
|
279
368
|
cleanCache(guid) {
|
|
280
369
|
delete this.entityCache[guid];
|
|
281
370
|
}
|
|
@@ -292,7 +381,15 @@ class NymphDriver {
|
|
|
292
381
|
}
|
|
293
382
|
return ret;
|
|
294
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Search through a value for an entity reference.
|
|
386
|
+
*
|
|
387
|
+
* @param value Any value to search.
|
|
388
|
+
* @param entity An entity, GUID, or array of either to search for.
|
|
389
|
+
* @returns True if the reference is found, false otherwise.
|
|
390
|
+
*/
|
|
295
391
|
entityReferenceSearch(value, entity) {
|
|
392
|
+
// Get the GUID, if the passed $entity is an object.
|
|
296
393
|
const guids = [];
|
|
297
394
|
if (Array.isArray(entity)) {
|
|
298
395
|
if (entity[0] === 'nymph_entity_reference') {
|
|
@@ -323,7 +420,8 @@ class NymphDriver {
|
|
|
323
420
|
guids.indexOf(value[1]) !== -1) {
|
|
324
421
|
return true;
|
|
325
422
|
}
|
|
326
|
-
|
|
423
|
+
// Search through arrays and objects looking for the reference.
|
|
424
|
+
if (value != null && (Array.isArray(value) || typeof value === 'object')) {
|
|
327
425
|
for (const key in value) {
|
|
328
426
|
if (this.entityReferenceSearch(value[key], guids)) {
|
|
329
427
|
return true;
|
|
@@ -334,6 +432,7 @@ class NymphDriver {
|
|
|
334
432
|
}
|
|
335
433
|
formatSelectors(selectors, options = {}) {
|
|
336
434
|
const newSelectors = [];
|
|
435
|
+
const qrefs = [];
|
|
337
436
|
for (const curSelector of selectors) {
|
|
338
437
|
const newSelector = {
|
|
339
438
|
type: curSelector.type,
|
|
@@ -355,43 +454,49 @@ class NymphDriver {
|
|
|
355
454
|
for (let i = 0; i < tmpArr.length; i++) {
|
|
356
455
|
const name = tmpArr[i][0];
|
|
357
456
|
const [qrefOptions, ...qrefSelectors] = tmpArr[i][1];
|
|
358
|
-
const QrefEntityClass =
|
|
457
|
+
const QrefEntityClass = qrefOptions.class
|
|
359
458
|
? this.nymph.getEntityClass(qrefOptions.class)
|
|
360
|
-
:
|
|
459
|
+
: this.nymph.getEntityClass('Entity');
|
|
361
460
|
const newOptions = {
|
|
362
461
|
...qrefOptions,
|
|
363
462
|
class: QrefEntityClass,
|
|
364
463
|
source: options.source,
|
|
365
464
|
};
|
|
366
465
|
const newSelectors = this.formatSelectors(qrefSelectors, options);
|
|
367
|
-
|
|
368
|
-
formatArr[i] = [name, [newOptions, ...newSelectors]];
|
|
466
|
+
qrefs.push([newOptions, ...newSelectors.selectors], ...newSelectors.qrefs);
|
|
467
|
+
formatArr[i] = [name, [newOptions, ...newSelectors.selectors]];
|
|
369
468
|
}
|
|
370
469
|
newSelector[key] = formatArr;
|
|
371
470
|
}
|
|
372
471
|
else if (key === 'selector' || key === '!selector') {
|
|
373
472
|
const tmpArr = (Array.isArray(value) ? value : [value]);
|
|
374
|
-
|
|
473
|
+
const newSelectors = this.formatSelectors(tmpArr, options);
|
|
474
|
+
newSelector[key] = newSelectors.selectors;
|
|
475
|
+
qrefs.push(...newSelectors.qrefs);
|
|
375
476
|
}
|
|
376
477
|
else if (!Array.isArray(value)) {
|
|
478
|
+
// @ts-ignore: ts doesn't know what value is here.
|
|
377
479
|
newSelector[key] = [[value]];
|
|
378
480
|
}
|
|
379
481
|
else if (!Array.isArray(value[0])) {
|
|
380
482
|
if (value.length === 3 &&
|
|
381
483
|
value[1] == null &&
|
|
382
484
|
typeof value[2] === 'string') {
|
|
383
|
-
|
|
485
|
+
// @ts-ignore: ts doesn't know what value is here.
|
|
486
|
+
newSelector[key] = [[value[0], strtotime(value[2]) * 1000]];
|
|
384
487
|
}
|
|
385
488
|
else {
|
|
489
|
+
// @ts-ignore: ts doesn't know what value is here.
|
|
386
490
|
newSelector[key] = [value];
|
|
387
491
|
}
|
|
388
492
|
}
|
|
389
493
|
else {
|
|
494
|
+
// @ts-ignore: ts doesn't know what value is here.
|
|
390
495
|
newSelector[key] = value.map((curValue) => {
|
|
391
496
|
if (curValue.length === 3 &&
|
|
392
497
|
curValue[1] == null &&
|
|
393
498
|
typeof curValue[2] === 'string') {
|
|
394
|
-
return [curValue[0], (
|
|
499
|
+
return [curValue[0], strtotime(curValue[2]) * 1000];
|
|
395
500
|
}
|
|
396
501
|
return curValue;
|
|
397
502
|
});
|
|
@@ -399,7 +504,7 @@ class NymphDriver {
|
|
|
399
504
|
}
|
|
400
505
|
newSelectors.push(newSelector);
|
|
401
506
|
}
|
|
402
|
-
return newSelectors;
|
|
507
|
+
return { selectors: newSelectors, qrefs };
|
|
403
508
|
}
|
|
404
509
|
iterateSelectorsForQuery(selectors, callback) {
|
|
405
510
|
const queryParts = [];
|
|
@@ -413,7 +518,7 @@ class NymphDriver {
|
|
|
413
518
|
if (key === 'type') {
|
|
414
519
|
continue;
|
|
415
520
|
}
|
|
416
|
-
let curQuery = callback(key, value, typeIsOr, typeIsNot);
|
|
521
|
+
let curQuery = callback({ key, value, typeIsOr, typeIsNot });
|
|
417
522
|
if (curQuery) {
|
|
418
523
|
if (curSelectorQuery) {
|
|
419
524
|
curSelectorQuery += typeIsOr ? ' OR ' : ' AND ';
|
|
@@ -427,214 +532,357 @@ class NymphDriver {
|
|
|
427
532
|
}
|
|
428
533
|
return queryParts;
|
|
429
534
|
}
|
|
430
|
-
|
|
431
|
-
if (typeof selectors[0] === 'string') {
|
|
432
|
-
selectors = [{ type: '&', guid: selectors[0] }];
|
|
433
|
-
}
|
|
434
|
-
options.limit = 1;
|
|
435
|
-
const entities = this.getEntitiesSync(options, ...selectors);
|
|
436
|
-
if (options.return === 'count') {
|
|
437
|
-
return entities;
|
|
438
|
-
}
|
|
439
|
-
if (!entities || !entities.length) {
|
|
440
|
-
return null;
|
|
441
|
-
}
|
|
442
|
-
return entities[0];
|
|
443
|
-
}
|
|
444
|
-
getEntitesRowLike(options, selectors, performQueryCallback, rowFetchCallback, freeResultCallback, getCountCallback, getGUIDCallback, getTagsAndDatesCallback, getDataNameAndSValueCallback) {
|
|
535
|
+
getEntitiesRowLike(options, selectors, performQueryCallback, rowFetchCallback, freeResultCallback, getCountCallback, getGUIDCallback, getTagsAndDatesCallback, getDataNameAndSValueCallback) {
|
|
445
536
|
if (!this.isConnected()) {
|
|
446
|
-
throw new
|
|
537
|
+
throw new UnableToConnectError('not connected to DB');
|
|
447
538
|
}
|
|
448
539
|
for (const selector of selectors) {
|
|
449
540
|
if (!selector ||
|
|
450
541
|
Object.keys(selector).length === 1 ||
|
|
451
542
|
!('type' in selector) ||
|
|
452
543
|
['&', '!&', '|', '!|'].indexOf(selector.type) === -1) {
|
|
453
|
-
throw new
|
|
544
|
+
throw new InvalidParametersError('Invalid query selector passed: ' + JSON.stringify(selector));
|
|
454
545
|
}
|
|
455
546
|
}
|
|
456
547
|
let entities = [];
|
|
457
548
|
let count = 0;
|
|
458
549
|
const EntityClass = options.class ?? this.nymph.getEntityClass('Entity');
|
|
459
550
|
const etype = EntityClass.ETYPE;
|
|
551
|
+
if (options.source === 'client' && options.return === 'object') {
|
|
552
|
+
throw new InvalidParametersError('Object return type not allowed from client.');
|
|
553
|
+
}
|
|
554
|
+
// Check if the requested entity is cached.
|
|
460
555
|
if (this.nymph.config.cache &&
|
|
461
556
|
!options.skipCache &&
|
|
557
|
+
selectors.length &&
|
|
462
558
|
'guid' in selectors[0] &&
|
|
463
559
|
typeof selectors[0].guid === 'number') {
|
|
560
|
+
// Only safe to use the cache option with no other selectors than a GUID
|
|
561
|
+
// and tags.
|
|
464
562
|
if (selectors.length === 1 &&
|
|
465
563
|
selectors[0].type === '&' &&
|
|
466
564
|
(Object.keys(selectors[0]).length === 2 ||
|
|
467
565
|
(Object.keys(selectors[0]).length === 3 && 'tag' in selectors[0]))) {
|
|
468
|
-
const
|
|
469
|
-
if (
|
|
470
|
-
|
|
566
|
+
const cacheEntity = this.pullCache(selectors[0]['guid']);
|
|
567
|
+
if (cacheEntity != null &&
|
|
568
|
+
cacheEntity.guid != null &&
|
|
471
569
|
(!('tag' in selectors[0]) ||
|
|
472
|
-
|
|
570
|
+
!(Array.isArray(selectors[0].tag)
|
|
473
571
|
? selectors[0].tag
|
|
474
|
-
: [selectors[0].tag])))) {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
: options.return === 'guid'
|
|
483
|
-
? [guid]
|
|
484
|
-
: [entity],
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
const formattedSelectors = this.formatSelectors(selectors, options);
|
|
490
|
-
this.nymph.runQueryCallbacks(options, formattedSelectors);
|
|
491
|
-
const { result } = performQueryCallback(options, formattedSelectors, etype);
|
|
492
|
-
return {
|
|
493
|
-
result,
|
|
494
|
-
process: () => {
|
|
495
|
-
let row = rowFetchCallback();
|
|
496
|
-
if (options.return === 'count') {
|
|
497
|
-
while (row != null) {
|
|
498
|
-
count += getCountCallback(row);
|
|
499
|
-
row = rowFetchCallback();
|
|
572
|
+
: [selectors[0].tag]).find((tag) => tag == null || !cacheEntity.tags.includes(tag)))) {
|
|
573
|
+
// Return the value from the cache.
|
|
574
|
+
const { guid, cdate, mdate, tags, data, sdata } = cacheEntity;
|
|
575
|
+
if (options.return === 'count') {
|
|
576
|
+
return {
|
|
577
|
+
result: Promise.resolve(null),
|
|
578
|
+
process: () => 1,
|
|
579
|
+
};
|
|
500
580
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
581
|
+
else if (options.return === 'guid') {
|
|
582
|
+
return {
|
|
583
|
+
result: Promise.resolve(null),
|
|
584
|
+
process: () => [guid],
|
|
585
|
+
};
|
|
506
586
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
587
|
+
else if (options.return === 'object') {
|
|
588
|
+
return {
|
|
589
|
+
result: Promise.resolve(null),
|
|
590
|
+
process: () => [
|
|
591
|
+
{
|
|
592
|
+
guid,
|
|
593
|
+
cdate,
|
|
594
|
+
mdate,
|
|
595
|
+
tags,
|
|
596
|
+
...data,
|
|
597
|
+
...Object.fromEntries(Object.entries(sdata).map(([name, value]) => [
|
|
598
|
+
name,
|
|
599
|
+
JSON.parse(value),
|
|
600
|
+
])),
|
|
601
|
+
},
|
|
602
|
+
],
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
const entity = this.nymph
|
|
607
|
+
.getEntityClass(EntityClass.class)
|
|
608
|
+
.factorySync();
|
|
609
|
+
if (entity) {
|
|
610
|
+
entity.$nymph = this.nymph;
|
|
611
|
+
if (options.skipAc != null) {
|
|
612
|
+
entity.$useSkipAc(!!options.skipAc);
|
|
613
|
+
}
|
|
614
|
+
entity.guid = guid;
|
|
615
|
+
entity.cdate = cdate;
|
|
616
|
+
entity.mdate = mdate;
|
|
617
|
+
entity.tags = tags;
|
|
618
|
+
entity.$putData(data, sdata, 'server');
|
|
619
|
+
return {
|
|
620
|
+
result: Promise.resolve(null),
|
|
621
|
+
process: () => [entity],
|
|
622
|
+
};
|
|
524
623
|
}
|
|
525
|
-
|
|
624
|
+
return {
|
|
625
|
+
result: Promise.resolve(null),
|
|
626
|
+
process: () => [],
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
try {
|
|
633
|
+
const formattedSelectors = this.formatSelectors(selectors, options);
|
|
634
|
+
this.nymph.runQueryCallbacks(options, formattedSelectors.selectors);
|
|
635
|
+
for (let i = 0; i < formattedSelectors.qrefs.length; i++) {
|
|
636
|
+
const [options, ...selectors] = formattedSelectors.qrefs[i];
|
|
637
|
+
this.nymph.runQueryCallbacks(options, selectors);
|
|
638
|
+
formattedSelectors.qrefs[i] = [options, ...selectors];
|
|
639
|
+
}
|
|
640
|
+
const { result } = performQueryCallback({
|
|
641
|
+
options,
|
|
642
|
+
selectors: formattedSelectors.selectors,
|
|
643
|
+
etype,
|
|
644
|
+
});
|
|
645
|
+
return {
|
|
646
|
+
result,
|
|
647
|
+
process: () => {
|
|
648
|
+
let row = rowFetchCallback();
|
|
649
|
+
if (options.return === 'count') {
|
|
650
|
+
while (row != null) {
|
|
651
|
+
count += getCountCallback(row);
|
|
526
652
|
row = rowFetchCallback();
|
|
527
653
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
entity.guid = guid;
|
|
534
|
-
entity.cdate = cdate;
|
|
535
|
-
entity.mdate = mdate;
|
|
536
|
-
entity.tags = tags;
|
|
537
|
-
this.putDataCounter++;
|
|
538
|
-
if (this.putDataCounter == 100) {
|
|
539
|
-
throw new Error('Infinite loop detected in Entity loading.');
|
|
654
|
+
}
|
|
655
|
+
else if (options.return === 'guid') {
|
|
656
|
+
while (row != null) {
|
|
657
|
+
entities.push(getGUIDCallback(row));
|
|
658
|
+
row = rowFetchCallback();
|
|
540
659
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
while (row != null) {
|
|
663
|
+
const guid = getGUIDCallback(row);
|
|
664
|
+
const tagsAndDates = getTagsAndDatesCallback(row);
|
|
665
|
+
const tags = tagsAndDates.tags;
|
|
666
|
+
const cdate = tagsAndDates.cdate;
|
|
667
|
+
const mdate = tagsAndDates.mdate;
|
|
668
|
+
let dataNameAndSValue = getDataNameAndSValueCallback(row);
|
|
669
|
+
// Data.
|
|
670
|
+
const data = {};
|
|
671
|
+
// Serialized data.
|
|
672
|
+
const sdata = {};
|
|
673
|
+
if (dataNameAndSValue.name !== '') {
|
|
674
|
+
// This do will keep going and adding the data until the
|
|
675
|
+
// next entity is reached. $row will end on the next entity.
|
|
676
|
+
do {
|
|
677
|
+
dataNameAndSValue = getDataNameAndSValueCallback(row);
|
|
678
|
+
sdata[dataNameAndSValue.name] = dataNameAndSValue.svalue;
|
|
679
|
+
row = rowFetchCallback();
|
|
680
|
+
} while (row != null && getGUIDCallback(row) === guid);
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
// Make sure that $row is incremented :)
|
|
684
|
+
row = rowFetchCallback();
|
|
685
|
+
}
|
|
686
|
+
if (options.return === 'object') {
|
|
687
|
+
entities.push({
|
|
688
|
+
guid,
|
|
689
|
+
cdate,
|
|
690
|
+
mdate,
|
|
691
|
+
tags,
|
|
692
|
+
...data,
|
|
693
|
+
...Object.fromEntries(Object.entries(sdata).map(([name, value]) => [
|
|
694
|
+
name,
|
|
695
|
+
JSON.parse(value),
|
|
696
|
+
])),
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
const entity = EntityClass.factorySync();
|
|
701
|
+
entity.$nymph = this.nymph;
|
|
702
|
+
if (options.skipAc != null) {
|
|
703
|
+
entity.$useSkipAc(!!options.skipAc);
|
|
704
|
+
}
|
|
705
|
+
entity.guid = guid;
|
|
706
|
+
entity.cdate = cdate;
|
|
707
|
+
entity.mdate = mdate;
|
|
708
|
+
entity.tags = tags;
|
|
709
|
+
this.putDataCounter++;
|
|
710
|
+
if (this.putDataCounter == 100) {
|
|
711
|
+
throw new Error('Infinite loop detected in Entity loading.');
|
|
712
|
+
}
|
|
713
|
+
entity.$putData(data, sdata, 'server');
|
|
714
|
+
this.putDataCounter--;
|
|
715
|
+
entities.push(entity);
|
|
716
|
+
}
|
|
717
|
+
if (this.nymph.config.cache) {
|
|
718
|
+
this.pushCache(guid, cdate, mdate, tags, data, sdata);
|
|
719
|
+
}
|
|
545
720
|
}
|
|
546
|
-
entities.push(entity);
|
|
547
721
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
}
|
|
722
|
+
freeResultCallback();
|
|
723
|
+
return options.return === 'count' ? count : entities;
|
|
724
|
+
},
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
catch (e) {
|
|
728
|
+
return {
|
|
729
|
+
result: Promise.resolve(e),
|
|
730
|
+
process: () => {
|
|
731
|
+
return e;
|
|
732
|
+
},
|
|
733
|
+
};
|
|
734
|
+
}
|
|
553
735
|
}
|
|
554
736
|
async saveEntityRowLike(entity, saveNewEntityCallback, saveExistingEntityCallback, startTransactionCallback = null, commitTransactionCallback = null) {
|
|
555
|
-
const
|
|
556
|
-
const
|
|
737
|
+
const originalGuid = entity.guid;
|
|
738
|
+
const originalCdate = entity.cdate;
|
|
739
|
+
const originalMdate = entity.mdate;
|
|
740
|
+
// Get a modified date.
|
|
741
|
+
let mdate = Date.now();
|
|
742
|
+
if (entity.mdate != null) {
|
|
743
|
+
if (this.nymph.config.updateMDate === false) {
|
|
744
|
+
mdate = entity.mdate;
|
|
745
|
+
}
|
|
746
|
+
else if (typeof this.nymph.config.updateMDate === 'number') {
|
|
747
|
+
mdate = entity.mdate + this.nymph.config.updateMDate;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
const tags = difference(entity.tags, ['']);
|
|
557
751
|
const data = entity.$getData();
|
|
558
752
|
const sdata = entity.$getSData();
|
|
753
|
+
const uniques = await entity.$getUniques();
|
|
559
754
|
const EntityClass = entity.constructor;
|
|
560
755
|
const etype = EntityClass.ETYPE;
|
|
561
756
|
if (startTransactionCallback) {
|
|
562
757
|
await startTransactionCallback();
|
|
563
758
|
}
|
|
564
759
|
let success = false;
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
760
|
+
try {
|
|
761
|
+
if (entity.guid == null) {
|
|
762
|
+
const cdate = mdate;
|
|
763
|
+
const newId = entity.$getGuaranteedGUID();
|
|
764
|
+
success = await saveNewEntityCallback({
|
|
765
|
+
entity,
|
|
766
|
+
guid: newId,
|
|
767
|
+
tags,
|
|
768
|
+
data,
|
|
769
|
+
sdata,
|
|
770
|
+
uniques,
|
|
771
|
+
cdate,
|
|
772
|
+
etype,
|
|
773
|
+
});
|
|
774
|
+
if (success) {
|
|
775
|
+
entity.guid = newId;
|
|
776
|
+
entity.cdate = cdate;
|
|
777
|
+
entity.mdate = mdate;
|
|
778
|
+
}
|
|
573
779
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
780
|
+
else {
|
|
781
|
+
// Removed any cached versions of this entity.
|
|
782
|
+
if (this.nymph.config.cache) {
|
|
783
|
+
this.cleanCache(entity.guid);
|
|
784
|
+
}
|
|
785
|
+
success = await saveExistingEntityCallback({
|
|
786
|
+
entity,
|
|
787
|
+
guid: entity.guid,
|
|
788
|
+
tags,
|
|
789
|
+
data,
|
|
790
|
+
sdata,
|
|
791
|
+
uniques,
|
|
792
|
+
mdate,
|
|
793
|
+
etype,
|
|
794
|
+
});
|
|
795
|
+
if (success) {
|
|
796
|
+
entity.mdate = mdate;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (commitTransactionCallback) {
|
|
800
|
+
success = await commitTransactionCallback(success);
|
|
578
801
|
}
|
|
579
|
-
|
|
580
|
-
if (success) {
|
|
581
|
-
entity.mdate
|
|
802
|
+
// Cache the entity.
|
|
803
|
+
if (success && this.nymph.config.cache) {
|
|
804
|
+
this.pushCache(entity.guid, entity.cdate, entity.mdate, entity.tags, data, sdata);
|
|
582
805
|
}
|
|
583
806
|
}
|
|
584
|
-
|
|
585
|
-
|
|
807
|
+
catch (e) {
|
|
808
|
+
entity.guid = originalGuid;
|
|
809
|
+
entity.cdate = originalCdate;
|
|
810
|
+
entity.mdate = originalMdate;
|
|
811
|
+
throw e;
|
|
586
812
|
}
|
|
587
|
-
if (success
|
|
588
|
-
|
|
813
|
+
if (!success) {
|
|
814
|
+
entity.guid = originalGuid;
|
|
815
|
+
entity.cdate = originalCdate;
|
|
816
|
+
entity.mdate = originalMdate;
|
|
589
817
|
}
|
|
590
818
|
return success;
|
|
591
819
|
}
|
|
592
|
-
|
|
820
|
+
/**
|
|
821
|
+
* Pull an entity from the cache.
|
|
822
|
+
*
|
|
823
|
+
* @param guid The entity's GUID.
|
|
824
|
+
* @returns The entity's data or null if it's not cached.
|
|
825
|
+
*/
|
|
826
|
+
pullCache(guid) {
|
|
827
|
+
// Increment the entity access count.
|
|
593
828
|
if (!(guid in this.entityCount)) {
|
|
594
829
|
this.entityCount[guid] = 0;
|
|
595
830
|
}
|
|
596
831
|
this.entityCount[guid]++;
|
|
597
832
|
if (guid in this.entityCache) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
entity.$putData(this.entityCache[guid]['data'], this.entityCache[guid]['sdata']);
|
|
607
|
-
return entity;
|
|
608
|
-
}
|
|
833
|
+
return {
|
|
834
|
+
guid,
|
|
835
|
+
cdate: this.entityCache[guid]['cdate'],
|
|
836
|
+
mdate: this.entityCache[guid]['mdate'],
|
|
837
|
+
tags: this.entityCache[guid]['tags'],
|
|
838
|
+
data: this.entityCache[guid]['data'],
|
|
839
|
+
sdata: this.entityCache[guid]['sdata'],
|
|
840
|
+
};
|
|
609
841
|
}
|
|
610
842
|
return null;
|
|
611
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Push an entity onto the cache.
|
|
846
|
+
*
|
|
847
|
+
* @param guid The entity's GUID.
|
|
848
|
+
* @param cdate The entity's cdate.
|
|
849
|
+
* @param mdate The entity's mdate.
|
|
850
|
+
* @param tags The entity's tags.
|
|
851
|
+
* @param data The entity's data.
|
|
852
|
+
* @param sdata The entity's sdata.
|
|
853
|
+
*/
|
|
612
854
|
pushCache(guid, cdate, mdate, tags, data, sdata) {
|
|
613
855
|
if (guid == null) {
|
|
614
856
|
return;
|
|
615
857
|
}
|
|
858
|
+
// Increment the entity access count.
|
|
616
859
|
if (!(guid in this.entityCount)) {
|
|
617
860
|
this.entityCount[guid] = 0;
|
|
618
861
|
}
|
|
619
862
|
this.entityCount[guid]++;
|
|
863
|
+
// Check the threshold.
|
|
620
864
|
if (this.entityCount[guid] < this.nymph.config.cacheThreshold) {
|
|
621
865
|
return;
|
|
622
866
|
}
|
|
867
|
+
// Cache the entity.
|
|
623
868
|
this.entityCache[guid] = {
|
|
624
|
-
cdate
|
|
625
|
-
mdate
|
|
626
|
-
tags
|
|
627
|
-
data
|
|
628
|
-
sdata
|
|
869
|
+
cdate,
|
|
870
|
+
mdate,
|
|
871
|
+
tags,
|
|
872
|
+
data,
|
|
873
|
+
sdata,
|
|
629
874
|
};
|
|
630
875
|
while (this.nymph.config.cacheLimit &&
|
|
631
876
|
Object.keys(this.entityCache).length >= this.nymph.config.cacheLimit) {
|
|
877
|
+
// Find which entity has been accessed the least.
|
|
632
878
|
const least = Object.entries(this.entityCount)
|
|
633
|
-
.sort(([
|
|
879
|
+
.sort(([_guidA, countA], [_guidB, countB]) => countA - countB)
|
|
634
880
|
.pop()?.[0] ?? null;
|
|
635
881
|
if (least == null) {
|
|
882
|
+
// This should never happen.
|
|
636
883
|
return;
|
|
637
884
|
}
|
|
885
|
+
// Remove it.
|
|
638
886
|
delete this.entityCache[least];
|
|
639
887
|
delete this.entityCount[least];
|
|
640
888
|
}
|
|
@@ -648,5 +896,4 @@ class NymphDriver {
|
|
|
648
896
|
return matches.map((match) => match.replace(re, '$1'));
|
|
649
897
|
}
|
|
650
898
|
}
|
|
651
|
-
exports.default = NymphDriver;
|
|
652
899
|
//# sourceMappingURL=NymphDriver.js.map
|