@nymphjs/nymph 1.0.0-beta.12 → 1.0.0-beta.121
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 +588 -0
- package/README.md +23 -21
- package/dist/Entity.d.ts +295 -12
- package/dist/Entity.js +560 -148
- package/dist/Entity.js.map +1 -1
- package/dist/Entity.test.js +52 -13
- 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 +590 -13
- package/dist/Nymph.js +846 -180
- package/dist/Nymph.js.map +1 -1
- package/dist/Nymph.types.d.ts +131 -8
- package/dist/Nymph.types.js +47 -5
- package/dist/Nymph.types.js.map +1 -1
- 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 +50 -2
- package/dist/conf/index.js +2 -8
- package/dist/conf/index.js.map +1 -1
- package/dist/driver/NymphDriver.d.ts +205 -40
- package/dist/driver/NymphDriver.js +712 -238
- package/dist/driver/NymphDriver.js.map +1 -1
- package/dist/driver/index.d.ts +1 -2
- package/dist/driver/index.js +2 -23
- package/dist/driver/index.js.map +1 -1
- package/dist/driver/testArtifacts.d.ts +4 -4
- package/dist/driver/testArtifacts.js +1920 -1086
- 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/HttpError.d.ts +5 -0
- package/dist/errors/HttpError.js +11 -0
- package/dist/errors/HttpError.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.d.ts +2 -1
- package/dist/errors/QueryFailedError.js +5 -6
- 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 +11 -8
- package/dist/errors/index.js +11 -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.d.ts +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 +27 -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 +31 -5
- package/package.json +33 -23
- package/src/Entity.test.ts +50 -7
- package/src/Entity.ts +489 -150
- package/src/Entity.types.ts +72 -74
- package/src/Nymph.ts +991 -324
- package/src/Nymph.types.ts +201 -27
- package/src/conf/defaults.ts +34 -1
- package/src/conf/index.ts +50 -2
- package/src/driver/NymphDriver.ts +898 -413
- package/src/driver/index.ts +1 -3
- package/src/driver/testArtifacts.ts +2557 -1629
- package/src/errors/EntityUniqueConstraintError.ts +6 -0
- package/src/errors/HttpError.ts +12 -0
- package/src/errors/InvalidStateError.ts +6 -0
- package/src/errors/QueryFailedError.ts +3 -1
- package/src/errors/index.ts +14 -8
- package/src/index.ts +11 -11
- package/src/nymphJoiProps.ts +2 -2
- package/src/testArtifacts.ts +20 -20
- package/src/testMocks.ts +76 -35
- package/src/utils.ts +39 -18
- package/tsconfig.json +6 -3
- package/typedoc.json +4 -0
- package/dist/conf/d.d.ts +0 -6
- package/dist/conf/d.js +0 -3
- package/dist/conf/d.js.map +0 -1
- package/src/conf/d.ts +0 -24
|
@@ -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/index';
|
|
5
|
+
import { Tokenizer } from '@sciactive/tokenizer';
|
|
6
|
+
import { ClassNotAvailableError, 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]',
|
|
@@ -67,16 +87,21 @@ class NymphDriver {
|
|
|
67
87
|
let re = new RegExp(newPattern, caseInsensitive ? 'mi' : 'm');
|
|
68
88
|
return re.test(subject);
|
|
69
89
|
}
|
|
90
|
+
checkIndexName(name) {
|
|
91
|
+
if (name.match(/[^a-z0-9_]/)) {
|
|
92
|
+
throw new Error('Index names can only contain lowercase letters, numbers, and underscores.');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
70
95
|
async export(filename) {
|
|
71
96
|
try {
|
|
72
|
-
const fhandle =
|
|
97
|
+
const fhandle = fs.openSync(filename, 'w');
|
|
73
98
|
if (!fhandle) {
|
|
74
|
-
throw new
|
|
99
|
+
throw new InvalidParametersError('Provided filename is not writeable.');
|
|
75
100
|
}
|
|
76
|
-
await this.
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
101
|
+
for await (let entry of this.exportDataIterator()) {
|
|
102
|
+
fs.writeSync(fhandle, entry.content);
|
|
103
|
+
}
|
|
104
|
+
fs.closeSync(fhandle);
|
|
80
105
|
}
|
|
81
106
|
catch (e) {
|
|
82
107
|
return false;
|
|
@@ -84,98 +109,159 @@ class NymphDriver {
|
|
|
84
109
|
return true;
|
|
85
110
|
}
|
|
86
111
|
async exportPrint() {
|
|
87
|
-
await this.
|
|
88
|
-
console.log(
|
|
89
|
-
}
|
|
112
|
+
for await (let entry of this.exportDataIterator()) {
|
|
113
|
+
console.log(entry.content);
|
|
114
|
+
}
|
|
90
115
|
return true;
|
|
91
116
|
}
|
|
92
|
-
async
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
117
|
+
async importDataIterator(lines, options, transaction) {
|
|
118
|
+
const first = lines[Symbol.iterator]().next();
|
|
119
|
+
if (first.done || first.value !== '#nex2') {
|
|
120
|
+
throw new Error('Tried to import a file that is not a NEX v2 file.');
|
|
96
121
|
}
|
|
97
|
-
|
|
98
|
-
|
|
122
|
+
if (transaction) {
|
|
123
|
+
await this.internalTransaction('nymph-import');
|
|
99
124
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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';
|
|
125
|
+
try {
|
|
126
|
+
let guid = null;
|
|
127
|
+
let sdata = {};
|
|
128
|
+
let tags = [];
|
|
129
|
+
let etype = '__undefined';
|
|
130
|
+
for (let line of lines) {
|
|
131
|
+
if (line.match(/^\s*#/)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const entityMatch = line.match(/^\s*{([0-9A-Fa-f]+)}<([-\w_]+)>\[([^\]]*)\]\s*$/);
|
|
135
|
+
const propMatch = line.match(/^\s*([^=]+)\s*=\s*(.*\S)\s*$/);
|
|
136
|
+
const uidMatch = line.match(/^\s*<([^>]+)>\[(\d+)\]\s*$/);
|
|
137
|
+
if (uidMatch) {
|
|
138
|
+
// Add the UID.
|
|
139
|
+
await this.importUID({
|
|
140
|
+
name: uidMatch[1],
|
|
141
|
+
value: Number(uidMatch[2]),
|
|
142
|
+
});
|
|
126
143
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
144
|
+
else if (entityMatch) {
|
|
145
|
+
// Save the current entity.
|
|
146
|
+
if (guid) {
|
|
147
|
+
const cdate = Number(JSON.parse(sdata.cdate));
|
|
148
|
+
delete sdata.cdate;
|
|
149
|
+
const mdate = Number(JSON.parse(sdata.mdate));
|
|
150
|
+
delete sdata.mdate;
|
|
151
|
+
try {
|
|
152
|
+
await this.importEntity({
|
|
153
|
+
guid,
|
|
154
|
+
cdate,
|
|
155
|
+
mdate,
|
|
156
|
+
tags,
|
|
157
|
+
sdata,
|
|
158
|
+
etype,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
if (!(e instanceof ClassNotAvailableError) ||
|
|
163
|
+
!options?.ignoreUnknownETypes) {
|
|
164
|
+
throw e;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
guid = null;
|
|
168
|
+
tags = [];
|
|
169
|
+
sdata = {};
|
|
170
|
+
etype = '__undefined';
|
|
171
|
+
}
|
|
172
|
+
// Record the new entity's info.
|
|
173
|
+
guid = entityMatch[1];
|
|
174
|
+
etype = entityMatch[2];
|
|
175
|
+
tags = entityMatch[3].split(',');
|
|
176
|
+
}
|
|
177
|
+
else if (propMatch) {
|
|
178
|
+
// Add the variable to the new entity.
|
|
179
|
+
if (guid) {
|
|
180
|
+
sdata[propMatch[1]] = propMatch[2];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Clear the entity cache.
|
|
184
|
+
this.entityCache = {};
|
|
130
185
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
186
|
+
// Save the last entity.
|
|
187
|
+
if (guid) {
|
|
188
|
+
const cdate = Number(JSON.parse(sdata.cdate));
|
|
189
|
+
delete sdata.cdate;
|
|
190
|
+
const mdate = Number(JSON.parse(sdata.mdate));
|
|
191
|
+
delete sdata.mdate;
|
|
192
|
+
try {
|
|
193
|
+
await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
if (!(e instanceof ClassNotAvailableError) ||
|
|
197
|
+
!options?.ignoreUnknownETypes) {
|
|
198
|
+
throw e;
|
|
199
|
+
}
|
|
134
200
|
}
|
|
135
201
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
await saveEntityCallback(guid, tags, sdata, etype);
|
|
202
|
+
if (transaction) {
|
|
203
|
+
await this.commit('nymph-import');
|
|
204
|
+
}
|
|
140
205
|
}
|
|
141
|
-
|
|
142
|
-
await
|
|
206
|
+
catch (e) {
|
|
207
|
+
await this.rollback('nymph-import');
|
|
208
|
+
throw e;
|
|
143
209
|
}
|
|
144
210
|
return true;
|
|
145
211
|
}
|
|
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));
|
|
212
|
+
async importData(text, transaction) {
|
|
213
|
+
return await this.importDataIterator(text.split('\n'), undefined, transaction);
|
|
214
|
+
}
|
|
215
|
+
async import(filename, options, transaction) {
|
|
216
|
+
let rl;
|
|
217
|
+
try {
|
|
218
|
+
rl = new ReadLines(filename);
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
throw new InvalidParametersError('Provided filename is unreadable.');
|
|
222
|
+
}
|
|
223
|
+
const lines = {
|
|
224
|
+
*[Symbol.iterator]() {
|
|
225
|
+
let line;
|
|
226
|
+
while ((line = rl.next())) {
|
|
227
|
+
yield line.toString('utf8');
|
|
163
228
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
return await this.importDataIterator(lines, options, transaction);
|
|
232
|
+
}
|
|
233
|
+
checkData(etype, data, sdata, selectors, guid = null, tags = null) {
|
|
234
|
+
try {
|
|
235
|
+
const EntityClass = this.nymph.getEntityClassByEtype(etype);
|
|
236
|
+
const formattedSelectors = this.formatSelectors(selectors).selectors;
|
|
237
|
+
for (const curSelector of formattedSelectors) {
|
|
238
|
+
const type = curSelector.type;
|
|
239
|
+
const typeIsNot = type === '!&' || type === '!|';
|
|
240
|
+
const typeIsOr = type === '|' || type === '!|';
|
|
241
|
+
let pass = !typeIsOr;
|
|
242
|
+
for (const k in curSelector) {
|
|
243
|
+
const key = k;
|
|
244
|
+
const value = curSelector[key];
|
|
245
|
+
if (key === 'type' || value == null) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const clauseNot = key.substring(0, 1) === '!';
|
|
249
|
+
if (key === 'selector' || key === '!selector') {
|
|
250
|
+
const tmpArr = (Array.isArray(value) ? value : [value]);
|
|
251
|
+
pass = xor(this.checkData(etype, data, sdata, tmpArr, guid, tags), xor(typeIsNot, clauseNot));
|
|
167
252
|
}
|
|
168
253
|
else {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
254
|
+
if (key === 'qref' || key === '!qref') {
|
|
255
|
+
throw new Error("Can't use checkData on qref clauses.");
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// Check if it doesn't pass any for &, check if it passes any for |.
|
|
259
|
+
for (const curValue of value) {
|
|
260
|
+
if (curValue == null) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
178
263
|
const propName = curValue[0];
|
|
264
|
+
// Unserialize the data for this variable.
|
|
179
265
|
if (propName in sdata) {
|
|
180
266
|
data[propName] = JSON.parse(sdata[propName]);
|
|
181
267
|
delete sdata[propName];
|
|
@@ -183,99 +269,130 @@ class NymphDriver {
|
|
|
183
269
|
switch (key) {
|
|
184
270
|
case 'guid':
|
|
185
271
|
case '!guid':
|
|
186
|
-
pass =
|
|
272
|
+
pass = xor(guid == propName, xor(typeIsNot, clauseNot));
|
|
187
273
|
break;
|
|
188
274
|
case 'tag':
|
|
189
275
|
case '!tag':
|
|
190
|
-
pass =
|
|
276
|
+
pass = xor((tags ?? []).indexOf(propName) !== -1, xor(typeIsNot, clauseNot));
|
|
191
277
|
break;
|
|
192
278
|
case 'defined':
|
|
193
279
|
case '!defined':
|
|
194
|
-
pass =
|
|
280
|
+
pass = xor(propName in data, xor(typeIsNot, clauseNot));
|
|
195
281
|
break;
|
|
196
282
|
case 'truthy':
|
|
197
283
|
case '!truthy':
|
|
198
|
-
pass =
|
|
284
|
+
pass = xor(propName in data && data[propName], xor(typeIsNot, clauseNot));
|
|
199
285
|
break;
|
|
200
286
|
case 'ref':
|
|
201
287
|
case '!ref':
|
|
202
288
|
const testRefValue = curValue[1];
|
|
203
|
-
pass =
|
|
204
|
-
this.entityReferenceSearch(data[propName], testRefValue),
|
|
289
|
+
pass = xor(propName in data &&
|
|
290
|
+
this.entityReferenceSearch(data[propName], testRefValue), xor(typeIsNot, clauseNot));
|
|
205
291
|
break;
|
|
206
292
|
case 'equal':
|
|
207
293
|
case '!equal':
|
|
208
294
|
const testEqualValue = curValue[1];
|
|
209
|
-
pass =
|
|
295
|
+
pass = xor(propName in data &&
|
|
210
296
|
JSON.stringify(data[propName]) ===
|
|
211
|
-
JSON.stringify(testEqualValue),
|
|
297
|
+
JSON.stringify(testEqualValue), xor(typeIsNot, clauseNot));
|
|
212
298
|
break;
|
|
213
299
|
case 'contain':
|
|
214
300
|
case '!contain':
|
|
215
301
|
const testContainValue = curValue[1];
|
|
216
|
-
pass =
|
|
217
|
-
JSON.stringify(data[propName]).indexOf(JSON.stringify(testContainValue)) !== -1,
|
|
302
|
+
pass = xor(propName in data &&
|
|
303
|
+
JSON.stringify(data[propName]).indexOf(JSON.stringify(testContainValue)) !== -1, xor(typeIsNot, clauseNot));
|
|
218
304
|
break;
|
|
219
305
|
case 'like':
|
|
220
306
|
case '!like':
|
|
221
307
|
const testLikeValue = curValue[1];
|
|
222
|
-
pass =
|
|
308
|
+
pass = xor(propName in data &&
|
|
223
309
|
new RegExp('^' +
|
|
224
310
|
escapeRegExp(testLikeValue)
|
|
225
311
|
.replace('%', () => '.*')
|
|
226
312
|
.replace('_', () => '.') +
|
|
227
|
-
'$').test(data[propName]),
|
|
313
|
+
'$').test(data[propName]), xor(typeIsNot, clauseNot));
|
|
314
|
+
break;
|
|
315
|
+
case 'ilike':
|
|
316
|
+
case '!ilike':
|
|
317
|
+
const testILikeValue = curValue[1];
|
|
318
|
+
pass = xor(propName in data &&
|
|
319
|
+
new RegExp('^' +
|
|
320
|
+
escapeRegExp(testILikeValue)
|
|
321
|
+
.replace('%', () => '.*')
|
|
322
|
+
.replace('_', () => '.') +
|
|
323
|
+
'$', 'i').test(data[propName]), xor(typeIsNot, clauseNot));
|
|
228
324
|
break;
|
|
229
325
|
case 'match':
|
|
230
326
|
case '!match':
|
|
231
327
|
const testMatchValue = curValue[1];
|
|
232
|
-
|
|
233
|
-
|
|
328
|
+
// Convert a POSIX regex to a JS regex.
|
|
329
|
+
pass = xor(propName in data &&
|
|
330
|
+
this.posixRegexMatch(testMatchValue, data[propName]), xor(typeIsNot, clauseNot));
|
|
234
331
|
break;
|
|
235
332
|
case 'imatch':
|
|
236
333
|
case '!imatch':
|
|
237
334
|
const testIMatchValue = curValue[1];
|
|
238
|
-
|
|
239
|
-
|
|
335
|
+
// Convert a POSIX regex to a JS regex.
|
|
336
|
+
pass = xor(propName in data &&
|
|
337
|
+
this.posixRegexMatch(testIMatchValue, data[propName], true), xor(typeIsNot, clauseNot));
|
|
338
|
+
break;
|
|
339
|
+
case 'search':
|
|
340
|
+
case '!search':
|
|
341
|
+
const testSearchValue = curValue[1];
|
|
342
|
+
const tokenString = propName in data
|
|
343
|
+
? (EntityClass.getFTSText(propName, data[propName]) ??
|
|
344
|
+
'')
|
|
345
|
+
: '';
|
|
346
|
+
pass = xor(propName in data &&
|
|
347
|
+
this.tokenizer.searchString(testSearchValue, tokenString), xor(typeIsNot, clauseNot));
|
|
240
348
|
break;
|
|
241
349
|
case 'gt':
|
|
242
350
|
case '!gt':
|
|
243
351
|
const testGTValue = curValue[1];
|
|
244
|
-
pass =
|
|
352
|
+
pass = xor(propName in data && data[propName] > testGTValue, xor(typeIsNot, clauseNot));
|
|
245
353
|
break;
|
|
246
354
|
case 'gte':
|
|
247
355
|
case '!gte':
|
|
248
356
|
const testGTEValue = curValue[1];
|
|
249
|
-
pass =
|
|
357
|
+
pass = xor(propName in data && data[propName] >= testGTEValue, xor(typeIsNot, clauseNot));
|
|
250
358
|
break;
|
|
251
359
|
case 'lt':
|
|
252
360
|
case '!lt':
|
|
253
361
|
const testLTValue = curValue[1];
|
|
254
|
-
pass =
|
|
362
|
+
pass = xor(propName in data && data[propName] < testLTValue, xor(typeIsNot, clauseNot));
|
|
255
363
|
break;
|
|
256
364
|
case 'lte':
|
|
257
365
|
case '!lte':
|
|
258
366
|
const testLTEValue = curValue[1];
|
|
259
|
-
pass =
|
|
367
|
+
pass = xor(propName in data && data[propName] <= testLTEValue, xor(typeIsNot, clauseNot));
|
|
260
368
|
break;
|
|
261
369
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
370
|
+
if (!xor(typeIsOr, pass)) {
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
265
373
|
}
|
|
266
374
|
}
|
|
267
375
|
}
|
|
376
|
+
if (!xor(typeIsOr, pass)) {
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
268
379
|
}
|
|
269
|
-
if (!
|
|
270
|
-
|
|
380
|
+
if (!pass) {
|
|
381
|
+
return false;
|
|
271
382
|
}
|
|
272
383
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
this.nymph.config.debugError('nymph', `Failed to check entity data: ${e}`);
|
|
388
|
+
throw e;
|
|
276
389
|
}
|
|
277
|
-
return true;
|
|
278
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Remove all copies of an entity from the cache.
|
|
393
|
+
*
|
|
394
|
+
* @param guid The GUID of the entity to remove.
|
|
395
|
+
*/
|
|
279
396
|
cleanCache(guid) {
|
|
280
397
|
delete this.entityCache[guid];
|
|
281
398
|
}
|
|
@@ -292,7 +409,15 @@ class NymphDriver {
|
|
|
292
409
|
}
|
|
293
410
|
return ret;
|
|
294
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* Search through a value for an entity reference.
|
|
414
|
+
*
|
|
415
|
+
* @param value Any value to search.
|
|
416
|
+
* @param entity An entity, GUID, or array of either to search for.
|
|
417
|
+
* @returns True if the reference is found, false otherwise.
|
|
418
|
+
*/
|
|
295
419
|
entityReferenceSearch(value, entity) {
|
|
420
|
+
// Get the GUID, if the passed $entity is an object.
|
|
296
421
|
const guids = [];
|
|
297
422
|
if (Array.isArray(entity)) {
|
|
298
423
|
if (entity[0] === 'nymph_entity_reference') {
|
|
@@ -323,7 +448,8 @@ class NymphDriver {
|
|
|
323
448
|
guids.indexOf(value[1]) !== -1) {
|
|
324
449
|
return true;
|
|
325
450
|
}
|
|
326
|
-
|
|
451
|
+
// Search through arrays and objects looking for the reference.
|
|
452
|
+
if (value != null && (Array.isArray(value) || typeof value === 'object')) {
|
|
327
453
|
for (const key in value) {
|
|
328
454
|
if (this.entityReferenceSearch(value[key], guids)) {
|
|
329
455
|
return true;
|
|
@@ -332,6 +458,175 @@ class NymphDriver {
|
|
|
332
458
|
}
|
|
333
459
|
return false;
|
|
334
460
|
}
|
|
461
|
+
removeAndReturnACValues(etype, data, sdata) {
|
|
462
|
+
let user = null;
|
|
463
|
+
let group = null;
|
|
464
|
+
let acUser = null;
|
|
465
|
+
let acGroup = null;
|
|
466
|
+
let acOther = null;
|
|
467
|
+
let acRead = null;
|
|
468
|
+
let acWrite = null;
|
|
469
|
+
let acFull = null;
|
|
470
|
+
if (this.nymph.tilmeld != null) {
|
|
471
|
+
// Since Tilmeld is installed, we need to fill the AC vars.
|
|
472
|
+
const userValue = 'user' in sdata
|
|
473
|
+
? JSON.parse(sdata.user)
|
|
474
|
+
: 'user' in data
|
|
475
|
+
? data.user
|
|
476
|
+
: null;
|
|
477
|
+
if (Array.isArray(userValue) &&
|
|
478
|
+
userValue[0] === 'nymph_entity_reference' &&
|
|
479
|
+
userValue[2] === 'User' &&
|
|
480
|
+
typeof userValue[1] === 'string' &&
|
|
481
|
+
userValue[1].match(/^[0-9a-f]{24}$/i)) {
|
|
482
|
+
user = userValue[1];
|
|
483
|
+
}
|
|
484
|
+
const groupValue = 'group' in sdata
|
|
485
|
+
? JSON.parse(sdata.group)
|
|
486
|
+
: 'group' in data
|
|
487
|
+
? data.group
|
|
488
|
+
: null;
|
|
489
|
+
if (Array.isArray(groupValue) &&
|
|
490
|
+
groupValue[0] === 'nymph_entity_reference' &&
|
|
491
|
+
groupValue[2] === 'Group' &&
|
|
492
|
+
typeof groupValue[1] === 'string' &&
|
|
493
|
+
groupValue[1].match(/^[0-9a-f]{24}$/i)) {
|
|
494
|
+
group = groupValue[1];
|
|
495
|
+
}
|
|
496
|
+
if (etype !== 'tilmeld_user' && etype !== 'tilmeld_group') {
|
|
497
|
+
const acUserValue = 'acUser' in sdata
|
|
498
|
+
? JSON.parse(sdata.acUser)
|
|
499
|
+
: 'acUser' in data
|
|
500
|
+
? data.acUser
|
|
501
|
+
: null;
|
|
502
|
+
if (typeof acUserValue === 'number' &&
|
|
503
|
+
!isNaN(acUserValue) &&
|
|
504
|
+
acUserValue >= 0) {
|
|
505
|
+
acUser = acUserValue;
|
|
506
|
+
}
|
|
507
|
+
const acGroupValue = 'acGroup' in sdata
|
|
508
|
+
? JSON.parse(sdata.acGroup)
|
|
509
|
+
: 'acGroup' in data
|
|
510
|
+
? data.acGroup
|
|
511
|
+
: null;
|
|
512
|
+
if (typeof acGroupValue === 'number' &&
|
|
513
|
+
!isNaN(acGroupValue) &&
|
|
514
|
+
acGroupValue >= 0) {
|
|
515
|
+
acGroup = acGroupValue;
|
|
516
|
+
}
|
|
517
|
+
const acOtherValue = 'acOther' in sdata
|
|
518
|
+
? JSON.parse(sdata.acOther)
|
|
519
|
+
: 'acOther' in data
|
|
520
|
+
? data.acOther
|
|
521
|
+
: null;
|
|
522
|
+
if (typeof acOtherValue === 'number' &&
|
|
523
|
+
!isNaN(acOtherValue) &&
|
|
524
|
+
acOtherValue >= 0) {
|
|
525
|
+
acOther = acOtherValue;
|
|
526
|
+
}
|
|
527
|
+
const acReadValue = 'acRead' in sdata
|
|
528
|
+
? JSON.parse(sdata.acRead)
|
|
529
|
+
: 'acRead' in data
|
|
530
|
+
? data.acRead
|
|
531
|
+
: null;
|
|
532
|
+
if (Array.isArray(acReadValue)) {
|
|
533
|
+
const acReadArray = [];
|
|
534
|
+
for (let acReadEntry of acReadValue) {
|
|
535
|
+
if (typeof acReadEntry === 'string' &&
|
|
536
|
+
acReadEntry.match(/^[0-9a-f]{24}$/i)) {
|
|
537
|
+
acReadArray.push(acReadEntry);
|
|
538
|
+
}
|
|
539
|
+
else if (Array.isArray(acReadEntry) &&
|
|
540
|
+
acReadEntry[0] === 'nymph_entity_reference' &&
|
|
541
|
+
(acReadEntry[2] === 'User' || acReadEntry[2] === 'Group') &&
|
|
542
|
+
typeof acReadEntry[1] === 'string' &&
|
|
543
|
+
acReadEntry[1].match(/^[0-9a-f]{24}$/i)) {
|
|
544
|
+
acReadArray.push(acReadEntry[1]);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (acReadArray.length) {
|
|
548
|
+
acRead = acReadArray;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const acWriteValue = 'acWrite' in sdata
|
|
552
|
+
? JSON.parse(sdata.acWrite)
|
|
553
|
+
: 'acWrite' in data
|
|
554
|
+
? data.acWrite
|
|
555
|
+
: null;
|
|
556
|
+
if (Array.isArray(acWriteValue)) {
|
|
557
|
+
const acWriteArray = [];
|
|
558
|
+
for (let acWriteEntry of acWriteValue) {
|
|
559
|
+
if (typeof acWriteEntry === 'string' &&
|
|
560
|
+
acWriteEntry.match(/^[0-9a-f]{24}$/i)) {
|
|
561
|
+
acWriteArray.push(acWriteEntry);
|
|
562
|
+
}
|
|
563
|
+
else if (Array.isArray(acWriteEntry) &&
|
|
564
|
+
acWriteEntry[0] === 'nymph_entity_reference' &&
|
|
565
|
+
(acWriteEntry[2] === 'User' || acWriteEntry[2] === 'Group') &&
|
|
566
|
+
typeof acWriteEntry[1] === 'string' &&
|
|
567
|
+
acWriteEntry[1].match(/^[0-9a-f]{24}$/i)) {
|
|
568
|
+
acWriteArray.push(acWriteEntry[1]);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (acWriteArray.length) {
|
|
572
|
+
acWrite = acWriteArray;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
const acFullValue = 'acFull' in sdata
|
|
576
|
+
? JSON.parse(sdata.acFull)
|
|
577
|
+
: 'acFull' in data
|
|
578
|
+
? data.acFull
|
|
579
|
+
: null;
|
|
580
|
+
if (Array.isArray(acFullValue)) {
|
|
581
|
+
const acFullArray = [];
|
|
582
|
+
for (let acFullEntry of acFullValue) {
|
|
583
|
+
if (typeof acFullEntry === 'string' &&
|
|
584
|
+
acFullEntry.match(/^[0-9a-f]{24}$/i)) {
|
|
585
|
+
acFullArray.push(acFullEntry);
|
|
586
|
+
}
|
|
587
|
+
else if (Array.isArray(acFullEntry) &&
|
|
588
|
+
acFullEntry[0] === 'nymph_entity_reference' &&
|
|
589
|
+
(acFullEntry[2] === 'User' || acFullEntry[2] === 'Group') &&
|
|
590
|
+
typeof acFullEntry[1] === 'string' &&
|
|
591
|
+
acFullEntry[1].match(/^[0-9a-f]{24}$/i)) {
|
|
592
|
+
acFullArray.push(acFullEntry[1]);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (acFullArray.length) {
|
|
596
|
+
acFull = acFullArray;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
// Delete all of the data, so it doesn't go into the database as rows in
|
|
601
|
+
// the data tables.
|
|
602
|
+
delete sdata.user;
|
|
603
|
+
delete data.user;
|
|
604
|
+
delete sdata.group;
|
|
605
|
+
delete data.group;
|
|
606
|
+
delete sdata.acUser;
|
|
607
|
+
delete data.acUser;
|
|
608
|
+
delete sdata.acGroup;
|
|
609
|
+
delete data.acGroup;
|
|
610
|
+
delete sdata.acOther;
|
|
611
|
+
delete data.acOther;
|
|
612
|
+
delete sdata.acRead;
|
|
613
|
+
delete data.acRead;
|
|
614
|
+
delete sdata.acWrite;
|
|
615
|
+
delete data.acWrite;
|
|
616
|
+
delete sdata.acFull;
|
|
617
|
+
delete data.acFull;
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
user,
|
|
621
|
+
group,
|
|
622
|
+
acUser,
|
|
623
|
+
acGroup,
|
|
624
|
+
acOther,
|
|
625
|
+
acRead,
|
|
626
|
+
acWrite,
|
|
627
|
+
acFull,
|
|
628
|
+
};
|
|
629
|
+
}
|
|
335
630
|
formatSelectors(selectors, options = {}) {
|
|
336
631
|
const newSelectors = [];
|
|
337
632
|
const qrefs = [];
|
|
@@ -356,9 +651,9 @@ class NymphDriver {
|
|
|
356
651
|
for (let i = 0; i < tmpArr.length; i++) {
|
|
357
652
|
const name = tmpArr[i][0];
|
|
358
653
|
const [qrefOptions, ...qrefSelectors] = tmpArr[i][1];
|
|
359
|
-
const QrefEntityClass =
|
|
654
|
+
const QrefEntityClass = qrefOptions.class
|
|
360
655
|
? this.nymph.getEntityClass(qrefOptions.class)
|
|
361
|
-
:
|
|
656
|
+
: this.nymph.getEntityClass('Entity');
|
|
362
657
|
const newOptions = {
|
|
363
658
|
...qrefOptions,
|
|
364
659
|
class: QrefEntityClass,
|
|
@@ -377,24 +672,28 @@ class NymphDriver {
|
|
|
377
672
|
qrefs.push(...newSelectors.qrefs);
|
|
378
673
|
}
|
|
379
674
|
else if (!Array.isArray(value)) {
|
|
675
|
+
// @ts-ignore: ts doesn't know what value is here.
|
|
380
676
|
newSelector[key] = [[value]];
|
|
381
677
|
}
|
|
382
678
|
else if (!Array.isArray(value[0])) {
|
|
383
679
|
if (value.length === 3 &&
|
|
384
680
|
value[1] == null &&
|
|
385
681
|
typeof value[2] === 'string') {
|
|
386
|
-
|
|
682
|
+
// @ts-ignore: ts doesn't know what value is here.
|
|
683
|
+
newSelector[key] = [[value[0], strtotime(value[2]) * 1000]];
|
|
387
684
|
}
|
|
388
685
|
else {
|
|
686
|
+
// @ts-ignore: ts doesn't know what value is here.
|
|
389
687
|
newSelector[key] = [value];
|
|
390
688
|
}
|
|
391
689
|
}
|
|
392
690
|
else {
|
|
691
|
+
// @ts-ignore: ts doesn't know what value is here.
|
|
393
692
|
newSelector[key] = value.map((curValue) => {
|
|
394
693
|
if (curValue.length === 3 &&
|
|
395
694
|
curValue[1] == null &&
|
|
396
695
|
typeof curValue[2] === 'string') {
|
|
397
|
-
return [curValue[0], (
|
|
696
|
+
return [curValue[0], (strtotime(curValue[2]) || 0) * 1000];
|
|
398
697
|
}
|
|
399
698
|
return curValue;
|
|
400
699
|
});
|
|
@@ -416,7 +715,7 @@ class NymphDriver {
|
|
|
416
715
|
if (key === 'type') {
|
|
417
716
|
continue;
|
|
418
717
|
}
|
|
419
|
-
let curQuery = callback(key, value, typeIsOr, typeIsNot);
|
|
718
|
+
let curQuery = callback({ key, value, typeIsOr, typeIsNot });
|
|
420
719
|
if (curQuery) {
|
|
421
720
|
if (curSelectorQuery) {
|
|
422
721
|
curSelectorQuery += typeIsOr ? ' OR ' : ' AND ';
|
|
@@ -430,73 +729,116 @@ class NymphDriver {
|
|
|
430
729
|
}
|
|
431
730
|
return queryParts;
|
|
432
731
|
}
|
|
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) {
|
|
732
|
+
getEntitiesRowLike(options, selectors, performQueryCallback, rowFetchCallback, freeResultCallback, getCountCallback, getGUIDCallback, getTagsAndDatesCallback, getDataNameAndSValueCallback) {
|
|
448
733
|
if (!this.isConnected()) {
|
|
449
|
-
throw new
|
|
734
|
+
throw new UnableToConnectError('not connected to DB');
|
|
450
735
|
}
|
|
451
736
|
for (const selector of selectors) {
|
|
452
737
|
if (!selector ||
|
|
453
738
|
Object.keys(selector).length === 1 ||
|
|
454
739
|
!('type' in selector) ||
|
|
455
740
|
['&', '!&', '|', '!|'].indexOf(selector.type) === -1) {
|
|
456
|
-
throw new
|
|
741
|
+
throw new InvalidParametersError('Invalid query selector passed: ' + JSON.stringify(selector));
|
|
457
742
|
}
|
|
458
743
|
}
|
|
459
744
|
let entities = [];
|
|
460
745
|
let count = 0;
|
|
461
746
|
const EntityClass = options.class ?? this.nymph.getEntityClass('Entity');
|
|
462
747
|
const etype = EntityClass.ETYPE;
|
|
748
|
+
if (options.source === 'client' && options.return === 'object') {
|
|
749
|
+
throw new InvalidParametersError('Object return type not allowed from client.');
|
|
750
|
+
}
|
|
751
|
+
// Check if the requested entity is cached.
|
|
463
752
|
if (this.nymph.config.cache &&
|
|
464
753
|
!options.skipCache &&
|
|
754
|
+
selectors.length &&
|
|
465
755
|
'guid' in selectors[0] &&
|
|
466
756
|
typeof selectors[0].guid === 'number') {
|
|
757
|
+
// Only safe to use the cache option with no other selectors than a GUID
|
|
758
|
+
// and tags.
|
|
467
759
|
if (selectors.length === 1 &&
|
|
468
760
|
selectors[0].type === '&' &&
|
|
469
761
|
(Object.keys(selectors[0]).length === 2 ||
|
|
470
762
|
(Object.keys(selectors[0]).length === 3 && 'tag' in selectors[0]))) {
|
|
471
|
-
const
|
|
472
|
-
if (
|
|
473
|
-
|
|
763
|
+
const cacheEntity = this.pullCache(selectors[0]['guid']);
|
|
764
|
+
if (cacheEntity != null &&
|
|
765
|
+
cacheEntity.guid != null &&
|
|
474
766
|
(!('tag' in selectors[0]) ||
|
|
475
|
-
|
|
767
|
+
!(Array.isArray(selectors[0].tag)
|
|
476
768
|
? selectors[0].tag
|
|
477
|
-
: [selectors[0].tag])))) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
769
|
+
: [selectors[0].tag]).find((tag) => tag == null || !cacheEntity.tags.includes(tag)))) {
|
|
770
|
+
// Return the value from the cache.
|
|
771
|
+
const { guid, cdate, mdate, tags, data, sdata } = cacheEntity;
|
|
772
|
+
if (options.return === 'count') {
|
|
773
|
+
return {
|
|
774
|
+
result: Promise.resolve(null),
|
|
775
|
+
process: () => 1,
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
else if (options.return === 'guid') {
|
|
779
|
+
return {
|
|
780
|
+
result: Promise.resolve(null),
|
|
781
|
+
process: () => [guid],
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
else if (options.return === 'object') {
|
|
785
|
+
return {
|
|
786
|
+
result: Promise.resolve(null),
|
|
787
|
+
process: () => [
|
|
788
|
+
{
|
|
789
|
+
guid,
|
|
790
|
+
cdate,
|
|
791
|
+
mdate,
|
|
792
|
+
tags,
|
|
793
|
+
...data,
|
|
794
|
+
...Object.fromEntries(Object.entries(sdata).map(([name, value]) => [
|
|
795
|
+
name,
|
|
796
|
+
JSON.parse(value),
|
|
797
|
+
])),
|
|
798
|
+
},
|
|
799
|
+
],
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
const entity = this.nymph
|
|
804
|
+
.getEntityClass(EntityClass.class)
|
|
805
|
+
.factorySync();
|
|
806
|
+
if (entity) {
|
|
807
|
+
entity.$nymph = this.nymph;
|
|
808
|
+
if (options.skipAc != null) {
|
|
809
|
+
entity.$useSkipAc(!!options.skipAc);
|
|
810
|
+
}
|
|
811
|
+
entity.guid = guid;
|
|
812
|
+
entity.cdate = cdate;
|
|
813
|
+
entity.mdate = mdate;
|
|
814
|
+
entity.tags = tags;
|
|
815
|
+
entity.$putData(data, sdata, 'server');
|
|
816
|
+
return {
|
|
817
|
+
result: Promise.resolve(null),
|
|
818
|
+
process: () => [entity],
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
result: Promise.resolve(null),
|
|
823
|
+
process: () => [],
|
|
824
|
+
};
|
|
825
|
+
}
|
|
489
826
|
}
|
|
490
827
|
}
|
|
491
828
|
}
|
|
492
829
|
try {
|
|
493
830
|
const formattedSelectors = this.formatSelectors(selectors, options);
|
|
494
831
|
this.nymph.runQueryCallbacks(options, formattedSelectors.selectors);
|
|
495
|
-
for (let
|
|
496
|
-
const [options, ...selectors] =
|
|
832
|
+
for (let i = 0; i < formattedSelectors.qrefs.length; i++) {
|
|
833
|
+
const [options, ...selectors] = formattedSelectors.qrefs[i];
|
|
497
834
|
this.nymph.runQueryCallbacks(options, selectors);
|
|
835
|
+
formattedSelectors.qrefs[i] = [options, ...selectors];
|
|
498
836
|
}
|
|
499
|
-
const { result } = performQueryCallback(
|
|
837
|
+
const { result } = performQueryCallback({
|
|
838
|
+
options,
|
|
839
|
+
selectors: formattedSelectors.selectors,
|
|
840
|
+
etype,
|
|
841
|
+
});
|
|
500
842
|
return {
|
|
501
843
|
result,
|
|
502
844
|
process: () => {
|
|
@@ -521,9 +863,13 @@ class NymphDriver {
|
|
|
521
863
|
const cdate = tagsAndDates.cdate;
|
|
522
864
|
const mdate = tagsAndDates.mdate;
|
|
523
865
|
let dataNameAndSValue = getDataNameAndSValueCallback(row);
|
|
866
|
+
// Data.
|
|
524
867
|
const data = {};
|
|
868
|
+
// Serialized data.
|
|
525
869
|
const sdata = {};
|
|
526
870
|
if (dataNameAndSValue.name !== '') {
|
|
871
|
+
// This do will keep going and adding the data until the
|
|
872
|
+
// next entity is reached. $row will end on the next entity.
|
|
527
873
|
do {
|
|
528
874
|
dataNameAndSValue = getDataNameAndSValueCallback(row);
|
|
529
875
|
sdata[dataNameAndSValue.name] = dataNameAndSValue.svalue;
|
|
@@ -531,27 +877,85 @@ class NymphDriver {
|
|
|
531
877
|
} while (row != null && getGUIDCallback(row) === guid);
|
|
532
878
|
}
|
|
533
879
|
else {
|
|
880
|
+
// Make sure that $row is incremented :)
|
|
534
881
|
row = rowFetchCallback();
|
|
535
882
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
883
|
+
if (this.nymph.tilmeld) {
|
|
884
|
+
if ('user' in tagsAndDates && tagsAndDates.user != null) {
|
|
885
|
+
data.user = [
|
|
886
|
+
'nymph_entity_reference',
|
|
887
|
+
tagsAndDates.user,
|
|
888
|
+
'User',
|
|
889
|
+
];
|
|
890
|
+
delete sdata.user;
|
|
891
|
+
}
|
|
892
|
+
if ('group' in tagsAndDates && tagsAndDates.group != null) {
|
|
893
|
+
data.group = [
|
|
894
|
+
'nymph_entity_reference',
|
|
895
|
+
tagsAndDates.group,
|
|
896
|
+
'Group',
|
|
897
|
+
];
|
|
898
|
+
delete sdata.group;
|
|
899
|
+
}
|
|
900
|
+
if ('acUser' in tagsAndDates && tagsAndDates.acUser != null) {
|
|
901
|
+
data.acUser = tagsAndDates.acUser;
|
|
902
|
+
delete sdata.acUser;
|
|
903
|
+
}
|
|
904
|
+
if ('acGroup' in tagsAndDates && tagsAndDates.acGroup != null) {
|
|
905
|
+
data.acGroup = tagsAndDates.acGroup;
|
|
906
|
+
delete sdata.acGroup;
|
|
907
|
+
}
|
|
908
|
+
if ('acOther' in tagsAndDates && tagsAndDates.acOther != null) {
|
|
909
|
+
data.acOther = tagsAndDates.acOther;
|
|
910
|
+
delete sdata.acOther;
|
|
911
|
+
}
|
|
912
|
+
if ('acRead' in tagsAndDates && tagsAndDates.acRead != null) {
|
|
913
|
+
data.acRead = tagsAndDates.acRead;
|
|
914
|
+
delete sdata.acRead;
|
|
915
|
+
}
|
|
916
|
+
if ('acWrite' in tagsAndDates && tagsAndDates.acWrite != null) {
|
|
917
|
+
data.acWrite = tagsAndDates.acWrite;
|
|
918
|
+
delete sdata.acWrite;
|
|
919
|
+
}
|
|
920
|
+
if ('acFull' in tagsAndDates && tagsAndDates.acFull != null) {
|
|
921
|
+
data.acFull = tagsAndDates.acFull;
|
|
922
|
+
delete sdata.acFull;
|
|
923
|
+
}
|
|
540
924
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
925
|
+
if (options.return === 'object') {
|
|
926
|
+
entities.push({
|
|
927
|
+
guid,
|
|
928
|
+
cdate,
|
|
929
|
+
mdate,
|
|
930
|
+
tags,
|
|
931
|
+
...data,
|
|
932
|
+
...Object.fromEntries(Object.entries(sdata).map(([name, value]) => [
|
|
933
|
+
name,
|
|
934
|
+
JSON.parse(value),
|
|
935
|
+
])),
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
const entity = EntityClass.factorySync();
|
|
940
|
+
entity.$nymph = this.nymph;
|
|
941
|
+
if (options.skipAc != null) {
|
|
942
|
+
entity.$useSkipAc(!!options.skipAc);
|
|
943
|
+
}
|
|
944
|
+
entity.guid = guid;
|
|
945
|
+
entity.cdate = cdate;
|
|
946
|
+
entity.mdate = mdate;
|
|
947
|
+
entity.tags = tags;
|
|
948
|
+
this.putDataCounter++;
|
|
949
|
+
if (this.putDataCounter == 100) {
|
|
950
|
+
throw new Error('Infinite loop detected in Entity loading.');
|
|
951
|
+
}
|
|
952
|
+
entity.$putData(data, sdata, 'server');
|
|
953
|
+
this.putDataCounter--;
|
|
954
|
+
entities.push(entity);
|
|
548
955
|
}
|
|
549
|
-
entity.$putData(data, sdata);
|
|
550
|
-
this.putDataCounter--;
|
|
551
956
|
if (this.nymph.config.cache) {
|
|
552
957
|
this.pushCache(guid, cdate, mdate, tags, data, sdata);
|
|
553
958
|
}
|
|
554
|
-
entities.push(entity);
|
|
555
959
|
}
|
|
556
960
|
}
|
|
557
961
|
freeResultCallback();
|
|
@@ -569,89 +973,160 @@ class NymphDriver {
|
|
|
569
973
|
}
|
|
570
974
|
}
|
|
571
975
|
async saveEntityRowLike(entity, saveNewEntityCallback, saveExistingEntityCallback, startTransactionCallback = null, commitTransactionCallback = null) {
|
|
572
|
-
const
|
|
573
|
-
const
|
|
976
|
+
const originalGuid = entity.guid;
|
|
977
|
+
const originalCdate = entity.cdate;
|
|
978
|
+
const originalMdate = entity.mdate;
|
|
979
|
+
// Get a modified date.
|
|
980
|
+
let mdate = Date.now();
|
|
981
|
+
if (entity.mdate != null) {
|
|
982
|
+
if (this.nymph.config.updateMDate === false) {
|
|
983
|
+
mdate = entity.mdate;
|
|
984
|
+
}
|
|
985
|
+
else if (typeof this.nymph.config.updateMDate === 'number') {
|
|
986
|
+
mdate = entity.mdate + this.nymph.config.updateMDate;
|
|
987
|
+
}
|
|
988
|
+
else if (entity.mdate >= mdate) {
|
|
989
|
+
// If we're saving too fast (within 1 millisecond), increment mdate to
|
|
990
|
+
// make sure it doesn't conflict in the DB.
|
|
991
|
+
mdate = entity.mdate + 1;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
const tags = difference(entity.tags, ['']);
|
|
574
995
|
const data = entity.$getData();
|
|
575
996
|
const sdata = entity.$getSData();
|
|
997
|
+
const uniques = await entity.$getUniques();
|
|
576
998
|
const EntityClass = entity.constructor;
|
|
577
999
|
const etype = EntityClass.ETYPE;
|
|
578
1000
|
if (startTransactionCallback) {
|
|
579
1001
|
await startTransactionCallback();
|
|
580
1002
|
}
|
|
581
1003
|
let success = false;
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
1004
|
+
try {
|
|
1005
|
+
if (entity.guid == null) {
|
|
1006
|
+
const cdate = mdate;
|
|
1007
|
+
const newId = entity.$getGuaranteedGUID();
|
|
1008
|
+
success = await saveNewEntityCallback({
|
|
1009
|
+
entity,
|
|
1010
|
+
guid: newId,
|
|
1011
|
+
tags,
|
|
1012
|
+
data,
|
|
1013
|
+
sdata,
|
|
1014
|
+
uniques,
|
|
1015
|
+
cdate,
|
|
1016
|
+
etype,
|
|
1017
|
+
});
|
|
1018
|
+
if (success) {
|
|
1019
|
+
entity.guid = newId;
|
|
1020
|
+
entity.cdate = cdate;
|
|
1021
|
+
entity.mdate = mdate;
|
|
1022
|
+
}
|
|
590
1023
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
1024
|
+
else {
|
|
1025
|
+
// Removed any cached versions of this entity.
|
|
1026
|
+
if (this.nymph.config.cache) {
|
|
1027
|
+
this.cleanCache(entity.guid);
|
|
1028
|
+
}
|
|
1029
|
+
success = await saveExistingEntityCallback({
|
|
1030
|
+
entity,
|
|
1031
|
+
guid: entity.guid,
|
|
1032
|
+
tags,
|
|
1033
|
+
data,
|
|
1034
|
+
sdata,
|
|
1035
|
+
uniques,
|
|
1036
|
+
mdate,
|
|
1037
|
+
etype,
|
|
1038
|
+
});
|
|
1039
|
+
if (success) {
|
|
1040
|
+
entity.mdate = mdate;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (commitTransactionCallback) {
|
|
1044
|
+
success = await commitTransactionCallback(success);
|
|
595
1045
|
}
|
|
596
|
-
|
|
597
|
-
if (success) {
|
|
598
|
-
entity.mdate
|
|
1046
|
+
// Cache the entity.
|
|
1047
|
+
if (success && this.nymph.config.cache) {
|
|
1048
|
+
this.pushCache(entity.guid, entity.cdate, entity.mdate, entity.tags, data, sdata);
|
|
599
1049
|
}
|
|
600
1050
|
}
|
|
601
|
-
|
|
602
|
-
|
|
1051
|
+
catch (e) {
|
|
1052
|
+
entity.guid = originalGuid;
|
|
1053
|
+
entity.cdate = originalCdate;
|
|
1054
|
+
entity.mdate = originalMdate;
|
|
1055
|
+
throw e;
|
|
603
1056
|
}
|
|
604
|
-
if (success
|
|
605
|
-
|
|
1057
|
+
if (!success) {
|
|
1058
|
+
entity.guid = originalGuid;
|
|
1059
|
+
entity.cdate = originalCdate;
|
|
1060
|
+
entity.mdate = originalMdate;
|
|
606
1061
|
}
|
|
607
1062
|
return success;
|
|
608
1063
|
}
|
|
609
|
-
|
|
1064
|
+
/**
|
|
1065
|
+
* Pull an entity from the cache.
|
|
1066
|
+
*
|
|
1067
|
+
* @param guid The entity's GUID.
|
|
1068
|
+
* @returns The entity's data or null if it's not cached.
|
|
1069
|
+
*/
|
|
1070
|
+
pullCache(guid) {
|
|
1071
|
+
// Increment the entity access count.
|
|
610
1072
|
if (!(guid in this.entityCount)) {
|
|
611
1073
|
this.entityCount[guid] = 0;
|
|
612
1074
|
}
|
|
613
1075
|
this.entityCount[guid]++;
|
|
614
1076
|
if (guid in this.entityCache) {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
entity.$putData(this.entityCache[guid]['data'], this.entityCache[guid]['sdata']);
|
|
624
|
-
return entity;
|
|
625
|
-
}
|
|
1077
|
+
return {
|
|
1078
|
+
guid,
|
|
1079
|
+
cdate: this.entityCache[guid]['cdate'],
|
|
1080
|
+
mdate: this.entityCache[guid]['mdate'],
|
|
1081
|
+
tags: this.entityCache[guid]['tags'],
|
|
1082
|
+
data: this.entityCache[guid]['data'],
|
|
1083
|
+
sdata: this.entityCache[guid]['sdata'],
|
|
1084
|
+
};
|
|
626
1085
|
}
|
|
627
1086
|
return null;
|
|
628
1087
|
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Push an entity onto the cache.
|
|
1090
|
+
*
|
|
1091
|
+
* @param guid The entity's GUID.
|
|
1092
|
+
* @param cdate The entity's cdate.
|
|
1093
|
+
* @param mdate The entity's mdate.
|
|
1094
|
+
* @param tags The entity's tags.
|
|
1095
|
+
* @param data The entity's data.
|
|
1096
|
+
* @param sdata The entity's sdata.
|
|
1097
|
+
*/
|
|
629
1098
|
pushCache(guid, cdate, mdate, tags, data, sdata) {
|
|
630
1099
|
if (guid == null) {
|
|
631
1100
|
return;
|
|
632
1101
|
}
|
|
1102
|
+
// Increment the entity access count.
|
|
633
1103
|
if (!(guid in this.entityCount)) {
|
|
634
1104
|
this.entityCount[guid] = 0;
|
|
635
1105
|
}
|
|
636
1106
|
this.entityCount[guid]++;
|
|
1107
|
+
// Check the threshold.
|
|
637
1108
|
if (this.entityCount[guid] < this.nymph.config.cacheThreshold) {
|
|
638
1109
|
return;
|
|
639
1110
|
}
|
|
1111
|
+
// Cache the entity.
|
|
640
1112
|
this.entityCache[guid] = {
|
|
641
|
-
cdate
|
|
642
|
-
mdate
|
|
643
|
-
tags
|
|
644
|
-
data
|
|
645
|
-
sdata
|
|
1113
|
+
cdate,
|
|
1114
|
+
mdate,
|
|
1115
|
+
tags,
|
|
1116
|
+
data,
|
|
1117
|
+
sdata,
|
|
646
1118
|
};
|
|
647
1119
|
while (this.nymph.config.cacheLimit &&
|
|
648
1120
|
Object.keys(this.entityCache).length >= this.nymph.config.cacheLimit) {
|
|
1121
|
+
// Find which entity has been accessed the least.
|
|
649
1122
|
const least = Object.entries(this.entityCount)
|
|
650
|
-
.sort(([
|
|
1123
|
+
.sort(([_guidA, countA], [_guidB, countB]) => countA - countB)
|
|
651
1124
|
.pop()?.[0] ?? null;
|
|
652
1125
|
if (least == null) {
|
|
1126
|
+
// This should never happen.
|
|
653
1127
|
return;
|
|
654
1128
|
}
|
|
1129
|
+
// Remove it.
|
|
655
1130
|
delete this.entityCache[least];
|
|
656
1131
|
delete this.entityCount[least];
|
|
657
1132
|
}
|
|
@@ -665,5 +1140,4 @@ class NymphDriver {
|
|
|
665
1140
|
return matches.map((match) => match.replace(re, '$1'));
|
|
666
1141
|
}
|
|
667
1142
|
}
|
|
668
|
-
exports.default = NymphDriver;
|
|
669
1143
|
//# sourceMappingURL=NymphDriver.js.map
|