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