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