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