@nymphjs/nymph 1.0.0-beta.10 → 1.0.0-beta.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +483 -0
- package/README.md +10 -20
- package/dist/Entity.d.ts +293 -10
- package/dist/Entity.js +563 -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 +496 -12
- package/dist/Nymph.js +752 -179
- package/dist/Nymph.js.map +1 -1
- package/dist/Nymph.types.d.ts +77 -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 +174 -40
- package/dist/driver/NymphDriver.js +469 -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 +814 -92
- package/dist/driver/testArtifacts.js.map +1 -1
- package/dist/errors/ClassNotAvailableError.js +1 -5
- package/dist/errors/ClassNotAvailableError.js.map +1 -1
- package/dist/errors/EntityConflictError.js +1 -5
- package/dist/errors/EntityConflictError.js.map +1 -1
- package/dist/errors/EntityInvalidDataError.d.ts +6 -0
- package/dist/errors/EntityInvalidDataError.js +8 -6
- package/dist/errors/EntityInvalidDataError.js.map +1 -1
- package/dist/errors/EntityIsSleepingReferenceError.js +1 -5
- package/dist/errors/EntityIsSleepingReferenceError.js.map +1 -1
- package/dist/errors/EntityUniqueConstraintError.d.ts +3 -0
- package/dist/errors/EntityUniqueConstraintError.js +7 -0
- package/dist/errors/EntityUniqueConstraintError.js.map +1 -0
- package/dist/errors/InvalidParametersError.js +1 -5
- package/dist/errors/InvalidParametersError.js.map +1 -1
- package/dist/errors/InvalidStateError.d.ts +3 -0
- package/dist/errors/InvalidStateError.js +7 -0
- package/dist/errors/InvalidStateError.js.map +1 -0
- package/dist/errors/NotConfiguredError.js +1 -5
- package/dist/errors/NotConfiguredError.js.map +1 -1
- package/dist/errors/QueryFailedError.js +2 -5
- package/dist/errors/QueryFailedError.js.map +1 -1
- package/dist/errors/UnableToConnectError.js +1 -5
- package/dist/errors/UnableToConnectError.js.map +1 -1
- package/dist/errors/index.d.ts +10 -8
- package/dist/errors/index.js +10 -24
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +14 -34
- package/dist/index.js.map +1 -1
- package/dist/nymphJoiProps.js +10 -15
- package/dist/nymphJoiProps.js.map +1 -1
- package/dist/testArtifacts.d.ts +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 +23 -19
- package/src/Entity.test.ts +50 -7
- package/src/Entity.ts +483 -147
- package/src/Entity.types.ts +72 -74
- package/src/Nymph.ts +868 -322
- package/src/Nymph.types.ts +146 -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 +562 -414
- package/src/driver/index.ts +2 -2
- package/src/driver/testArtifacts.ts +954 -185
- package/src/errors/EntityUniqueConstraintError.ts +6 -0
- package/src/errors/InvalidStateError.ts +6 -0
- package/src/errors/index.ts +12 -8
- package/src/index.ts +11 -11
- package/src/nymphJoiProps.ts +2 -2
- package/src/testArtifacts.ts +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,20 +1,24 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import { difference } from 'lodash';
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { difference } from 'lodash-es';
|
|
3
3
|
import ReadLines from 'n-readlines';
|
|
4
|
-
import strtotime from 'locutus/php/datetime/strtotime';
|
|
5
|
-
import {
|
|
4
|
+
import strtotime from 'locutus/php/datetime/strtotime.js';
|
|
5
|
+
import { Tokenizer } from '@sciactive/tokenizer';
|
|
6
6
|
|
|
7
|
-
import type Nymph from '../Nymph';
|
|
8
|
-
import {
|
|
7
|
+
import type Nymph from '../Nymph.js';
|
|
8
|
+
import type { Selector, Options, FormattedSelector } from '../Nymph.types.js';
|
|
9
|
+
import type { EntityInstanceType, EntityObjectType } from '../Entity.js';
|
|
10
|
+
import type {
|
|
9
11
|
EntityConstructor,
|
|
10
12
|
EntityData,
|
|
11
13
|
EntityInterface,
|
|
12
14
|
EntityReference,
|
|
13
15
|
SerializedEntityData,
|
|
14
|
-
} from '../Entity.types';
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
} from '../Entity.types.js';
|
|
17
|
+
import {
|
|
18
|
+
InvalidParametersError,
|
|
19
|
+
UnableToConnectError,
|
|
20
|
+
} from '../errors/index.js';
|
|
21
|
+
import { xor } from '../utils.js';
|
|
18
22
|
|
|
19
23
|
// from: https://stackoverflow.com/a/6969486/664915
|
|
20
24
|
function escapeRegExp(string: string): string {
|
|
@@ -28,11 +32,13 @@ export default abstract class NymphDriver {
|
|
|
28
32
|
protected nymph: Nymph = new Proxy({} as Nymph, {
|
|
29
33
|
get() {
|
|
30
34
|
throw new Error(
|
|
31
|
-
'Attempted access of Nymph instance before driver initialization!'
|
|
35
|
+
'Attempted access of Nymph instance before driver initialization!',
|
|
32
36
|
);
|
|
33
37
|
},
|
|
34
38
|
});
|
|
35
39
|
|
|
40
|
+
protected tokenizer = new Tokenizer();
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* A cache to make entity retrieval faster.
|
|
38
44
|
*/
|
|
@@ -67,13 +73,14 @@ export default abstract class NymphDriver {
|
|
|
67
73
|
abstract connect(): Promise<boolean>;
|
|
68
74
|
|
|
69
75
|
abstract isConnected(): boolean;
|
|
76
|
+
protected abstract internalTransaction(name: string): Promise<any>;
|
|
70
77
|
abstract startTransaction(name: string): Promise<Nymph>;
|
|
71
78
|
abstract commit(name: string): Promise<boolean>;
|
|
72
79
|
abstract rollback(name: string): Promise<boolean>;
|
|
73
80
|
abstract inTransaction(): Promise<boolean>;
|
|
74
81
|
abstract deleteEntityByID(
|
|
75
82
|
guid: string,
|
|
76
|
-
classConstructor: EntityConstructor
|
|
83
|
+
classConstructor: EntityConstructor,
|
|
77
84
|
): Promise<boolean>;
|
|
78
85
|
abstract deleteEntityByID(guid: string, className?: string): Promise<boolean>;
|
|
79
86
|
abstract deleteUID(name: string): Promise<boolean>;
|
|
@@ -83,9 +90,19 @@ export default abstract class NymphDriver {
|
|
|
83
90
|
*/
|
|
84
91
|
abstract disconnect(): Promise<boolean>;
|
|
85
92
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
abstract needsMigration(): Promise<'json' | 'tokens' | false>;
|
|
94
|
+
abstract liveMigration(migrationType: 'tokenTables'): Promise<void>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Returns a list of the etypes stored in the DB.
|
|
98
|
+
*/
|
|
99
|
+
abstract getEtypes(): Promise<string[]>;
|
|
100
|
+
|
|
101
|
+
abstract exportDataIterator(): AsyncGenerator<
|
|
102
|
+
{ type: 'comment' | 'uid' | 'entity'; content: string },
|
|
103
|
+
void,
|
|
104
|
+
undefined | false
|
|
105
|
+
>;
|
|
89
106
|
abstract getEntities<T extends EntityConstructor = EntityConstructor>(
|
|
90
107
|
options: Options<T> & { return: 'count' },
|
|
91
108
|
...selectors: Selector[]
|
|
@@ -95,39 +112,37 @@ export default abstract class NymphDriver {
|
|
|
95
112
|
...selectors: Selector[]
|
|
96
113
|
): Promise<string[]>;
|
|
97
114
|
abstract getEntities<T extends EntityConstructor = EntityConstructor>(
|
|
98
|
-
options
|
|
115
|
+
options: Options<T> & { return: 'object' },
|
|
99
116
|
...selectors: Selector[]
|
|
100
|
-
): Promise<
|
|
117
|
+
): Promise<EntityObjectType<T>[]>;
|
|
101
118
|
abstract getEntities<T extends EntityConstructor = EntityConstructor>(
|
|
102
119
|
options?: Options<T>,
|
|
103
120
|
...selectors: Selector[]
|
|
104
|
-
): Promise<
|
|
105
|
-
|
|
106
|
-
T extends EntityConstructor = EntityConstructor
|
|
107
|
-
>(
|
|
108
|
-
options: Options<T> & { return: 'count' },
|
|
109
|
-
...selectors: Selector[]
|
|
110
|
-
): number;
|
|
111
|
-
protected abstract getEntitiesSync<
|
|
112
|
-
T extends EntityConstructor = EntityConstructor
|
|
113
|
-
>(
|
|
114
|
-
options: Options<T> & { return: 'guid' },
|
|
115
|
-
...selectors: Selector[]
|
|
116
|
-
): string[];
|
|
117
|
-
protected abstract getEntitiesSync<
|
|
118
|
-
T extends EntityConstructor = EntityConstructor
|
|
119
|
-
>(
|
|
120
|
-
options?: Options<T>,
|
|
121
|
-
...selectors: Selector[]
|
|
122
|
-
): ReturnType<T['factorySync']>[];
|
|
123
|
-
protected abstract getEntitiesSync<
|
|
124
|
-
T extends EntityConstructor = EntityConstructor
|
|
125
|
-
>(
|
|
121
|
+
): Promise<EntityInstanceType<T>[]>;
|
|
122
|
+
abstract getEntities<T extends EntityConstructor = EntityConstructor>(
|
|
126
123
|
options?: Options<T>,
|
|
127
124
|
...selectors: Selector[]
|
|
128
|
-
):
|
|
125
|
+
): Promise<
|
|
126
|
+
EntityInstanceType<T>[] | EntityObjectType<T>[] | string[] | number
|
|
127
|
+
>;
|
|
129
128
|
abstract getUID(name: string): Promise<number | null>;
|
|
130
|
-
abstract
|
|
129
|
+
abstract importEntity(entity: {
|
|
130
|
+
guid: string;
|
|
131
|
+
cdate: number;
|
|
132
|
+
mdate: number;
|
|
133
|
+
tags: string[];
|
|
134
|
+
sdata: SerializedEntityData;
|
|
135
|
+
etype: string;
|
|
136
|
+
}): Promise<void>;
|
|
137
|
+
abstract importEntityTokens(entity: {
|
|
138
|
+
guid: string;
|
|
139
|
+
cdate: number;
|
|
140
|
+
mdate: number;
|
|
141
|
+
tags: string[];
|
|
142
|
+
sdata: SerializedEntityData;
|
|
143
|
+
etype: string;
|
|
144
|
+
}): Promise<void>;
|
|
145
|
+
abstract importUID(uid: { name: string; value: number }): Promise<void>;
|
|
131
146
|
abstract newUID(name: string): Promise<number | null>;
|
|
132
147
|
abstract renameUID(oldName: string, newName: string): Promise<boolean>;
|
|
133
148
|
abstract saveEntity(entity: EntityInterface): Promise<boolean>;
|
|
@@ -147,7 +162,7 @@ export default abstract class NymphDriver {
|
|
|
147
162
|
protected posixRegexMatch(
|
|
148
163
|
pattern: string,
|
|
149
164
|
subject: string,
|
|
150
|
-
caseInsensitive = false
|
|
165
|
+
caseInsensitive = false,
|
|
151
166
|
) {
|
|
152
167
|
const posixClasses = [
|
|
153
168
|
// '[[:<:]]',
|
|
@@ -204,9 +219,9 @@ export default abstract class NymphDriver {
|
|
|
204
219
|
if (!fhandle) {
|
|
205
220
|
throw new InvalidParametersError('Provided filename is not writeable.');
|
|
206
221
|
}
|
|
207
|
-
await this.
|
|
208
|
-
fs.writeSync(fhandle,
|
|
209
|
-
}
|
|
222
|
+
for await (let entry of this.exportDataIterator()) {
|
|
223
|
+
fs.writeSync(fhandle, entry.content);
|
|
224
|
+
}
|
|
210
225
|
fs.closeSync(fhandle);
|
|
211
226
|
} catch (e: any) {
|
|
212
227
|
return false;
|
|
@@ -215,130 +230,157 @@ export default abstract class NymphDriver {
|
|
|
215
230
|
}
|
|
216
231
|
|
|
217
232
|
public async exportPrint() {
|
|
218
|
-
await this.
|
|
219
|
-
console.log(
|
|
220
|
-
}
|
|
233
|
+
for await (let entry of this.exportDataIterator()) {
|
|
234
|
+
console.log(entry.content);
|
|
235
|
+
}
|
|
221
236
|
return true;
|
|
222
237
|
}
|
|
223
238
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
guid: string,
|
|
228
|
-
tags: string[],
|
|
229
|
-
sdata: SerializedEntityData,
|
|
230
|
-
etype: string
|
|
231
|
-
) => Promise<void>,
|
|
232
|
-
saveUIDCallback: (name: string, value: number) => Promise<void>,
|
|
233
|
-
startTransactionCallback: (() => Promise<void>) | null = null,
|
|
234
|
-
commitTransactionCallback: (() => Promise<void>) | null = null
|
|
239
|
+
public async importDataIterator(
|
|
240
|
+
lines: Iterable<string>,
|
|
241
|
+
transaction?: boolean,
|
|
235
242
|
) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
throw new InvalidParametersError('Provided filename is unreadable.');
|
|
243
|
+
const first = lines[Symbol.iterator]().next();
|
|
244
|
+
|
|
245
|
+
if (first.done || first.value !== '#nex2') {
|
|
246
|
+
throw new Error('Tried to import a file that is not a NEX v2 file.');
|
|
241
247
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
let tags: string[] = [];
|
|
246
|
-
let etype = '__undefined';
|
|
247
|
-
if (startTransactionCallback) {
|
|
248
|
-
await startTransactionCallback();
|
|
248
|
+
|
|
249
|
+
if (transaction) {
|
|
250
|
+
await this.internalTransaction('nymph-import');
|
|
249
251
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
/^\s
|
|
257
|
-
|
|
258
|
-
const propMatch = text.match(/^\s*([^=]+)\s*=\s*(.*\S)\s*$/);
|
|
259
|
-
const uidMatch = text.match(/^\s*<([^>]+)>\[(\d+)\]\s*$/);
|
|
260
|
-
if (uidMatch) {
|
|
261
|
-
// Add the UID.
|
|
262
|
-
await saveUIDCallback(uidMatch[1], Number(uidMatch[2]));
|
|
263
|
-
} else if (entityMatch) {
|
|
264
|
-
// Save the current entity.
|
|
265
|
-
if (guid) {
|
|
266
|
-
await saveEntityCallback(guid, tags, sdata, etype);
|
|
267
|
-
guid = null;
|
|
268
|
-
tags = [];
|
|
269
|
-
sdata = {};
|
|
270
|
-
etype = '__undefined';
|
|
252
|
+
try {
|
|
253
|
+
let guid: string | null = null;
|
|
254
|
+
let sdata: SerializedEntityData = {};
|
|
255
|
+
let tags: string[] = [];
|
|
256
|
+
let etype = '__undefined';
|
|
257
|
+
for (let line of lines) {
|
|
258
|
+
if (line.match(/^\s*#/)) {
|
|
259
|
+
continue;
|
|
271
260
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
261
|
+
const entityMatch = line.match(
|
|
262
|
+
/^\s*{([0-9A-Fa-f]+)}<([-\w_]+)>\[([^\]]*)\]\s*$/,
|
|
263
|
+
);
|
|
264
|
+
const propMatch = line.match(/^\s*([^=]+)\s*=\s*(.*\S)\s*$/);
|
|
265
|
+
const uidMatch = line.match(/^\s*<([^>]+)>\[(\d+)\]\s*$/);
|
|
266
|
+
if (uidMatch) {
|
|
267
|
+
// Add the UID.
|
|
268
|
+
await this.importUID({
|
|
269
|
+
name: uidMatch[1],
|
|
270
|
+
value: Number(uidMatch[2]),
|
|
271
|
+
});
|
|
272
|
+
} else if (entityMatch) {
|
|
273
|
+
// Save the current entity.
|
|
274
|
+
if (guid) {
|
|
275
|
+
const cdate = Number(JSON.parse(sdata.cdate));
|
|
276
|
+
delete sdata.cdate;
|
|
277
|
+
const mdate = Number(JSON.parse(sdata.mdate));
|
|
278
|
+
delete sdata.mdate;
|
|
279
|
+
await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
|
|
280
|
+
guid = null;
|
|
281
|
+
tags = [];
|
|
282
|
+
sdata = {};
|
|
283
|
+
etype = '__undefined';
|
|
284
|
+
}
|
|
285
|
+
// Record the new entity's info.
|
|
286
|
+
guid = entityMatch[1];
|
|
287
|
+
etype = entityMatch[2];
|
|
288
|
+
tags = entityMatch[3].split(',');
|
|
289
|
+
} else if (propMatch) {
|
|
290
|
+
// Add the variable to the new entity.
|
|
291
|
+
if (guid) {
|
|
292
|
+
sdata[propMatch[1]] = propMatch[2];
|
|
293
|
+
}
|
|
280
294
|
}
|
|
295
|
+
// Clear the entity cache.
|
|
296
|
+
this.entityCache = {};
|
|
281
297
|
}
|
|
282
|
-
//
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
298
|
+
// Save the last entity.
|
|
299
|
+
if (guid) {
|
|
300
|
+
const cdate = Number(JSON.parse(sdata.cdate));
|
|
301
|
+
delete sdata.cdate;
|
|
302
|
+
const mdate = Number(JSON.parse(sdata.mdate));
|
|
303
|
+
delete sdata.mdate;
|
|
304
|
+
await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
|
|
305
|
+
}
|
|
306
|
+
if (transaction) {
|
|
307
|
+
await this.commit('nymph-import');
|
|
308
|
+
}
|
|
309
|
+
} catch (e: any) {
|
|
310
|
+
await this.rollback('nymph-import');
|
|
311
|
+
throw e;
|
|
291
312
|
}
|
|
292
313
|
return true;
|
|
293
314
|
}
|
|
294
315
|
|
|
316
|
+
public async importData(text: string, transaction?: boolean) {
|
|
317
|
+
return await this.importDataIterator(text.split('\n'), transaction);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
public async import(filename: string, transaction?: boolean) {
|
|
321
|
+
let rl: ReadLines;
|
|
322
|
+
try {
|
|
323
|
+
rl = new ReadLines(filename);
|
|
324
|
+
} catch (e: any) {
|
|
325
|
+
throw new InvalidParametersError('Provided filename is unreadable.');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const lines = {
|
|
329
|
+
*[Symbol.iterator]() {
|
|
330
|
+
let line: false | Buffer;
|
|
331
|
+
while ((line = rl.next())) {
|
|
332
|
+
yield line.toString('utf8');
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
return await this.importDataIterator(lines, transaction);
|
|
338
|
+
}
|
|
339
|
+
|
|
295
340
|
public checkData(
|
|
341
|
+
etype: string,
|
|
296
342
|
data: EntityData,
|
|
297
343
|
sdata: SerializedEntityData,
|
|
298
344
|
selectors: Selector[],
|
|
299
345
|
guid: string | null = null,
|
|
300
|
-
tags: string[] | null = null
|
|
346
|
+
tags: string[] | null = null,
|
|
301
347
|
) {
|
|
302
|
-
|
|
348
|
+
try {
|
|
349
|
+
const EntityClass = this.nymph.getEntityClassByEtype(etype);
|
|
350
|
+
const formattedSelectors = this.formatSelectors(selectors).selectors;
|
|
303
351
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
352
|
+
for (const curSelector of formattedSelectors) {
|
|
353
|
+
const type = curSelector.type;
|
|
354
|
+
const typeIsNot = type === '!&' || type === '!|';
|
|
355
|
+
const typeIsOr = type === '|' || type === '!|';
|
|
356
|
+
let pass = !typeIsOr;
|
|
357
|
+
for (const k in curSelector) {
|
|
358
|
+
const key = k as keyof Selector;
|
|
359
|
+
const value = curSelector[key];
|
|
312
360
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
361
|
+
if (key === 'type' || value == null) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
316
364
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
xor(
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
throw new Error("Can't use checkData on qref clauses.");
|
|
365
|
+
const clauseNot = key.substring(0, 1) === '!';
|
|
366
|
+
if (key === 'selector' || key === '!selector') {
|
|
367
|
+
const tmpArr = (
|
|
368
|
+
Array.isArray(value) ? value : [value]
|
|
369
|
+
) as Selector[];
|
|
370
|
+
pass = xor(
|
|
371
|
+
this.checkData(etype, data, sdata, tmpArr, guid, tags),
|
|
372
|
+
xor(typeIsNot, clauseNot),
|
|
373
|
+
);
|
|
327
374
|
} else {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if
|
|
332
|
-
|
|
333
|
-
|
|
375
|
+
if (key === 'qref' || key === '!qref') {
|
|
376
|
+
throw new Error("Can't use checkData on qref clauses.");
|
|
377
|
+
} else {
|
|
378
|
+
// Check if it doesn't pass any for &, check if it passes any for |.
|
|
379
|
+
for (const curValue of value) {
|
|
380
|
+
if (curValue == null) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
334
383
|
|
|
335
|
-
if (
|
|
336
|
-
((key === 'guid' || key === '!guid') && guid == null) ||
|
|
337
|
-
((key === 'tag' || key === '!tag') && tags == null)
|
|
338
|
-
) {
|
|
339
|
-
// Skip because it has already been checked (by the query).
|
|
340
|
-
pass = true;
|
|
341
|
-
} else {
|
|
342
384
|
const propName = (curValue as string[])[0];
|
|
343
385
|
|
|
344
386
|
// Unserialize the data for this variable.
|
|
@@ -356,7 +398,7 @@ export default abstract class NymphDriver {
|
|
|
356
398
|
case '!tag':
|
|
357
399
|
pass = xor(
|
|
358
400
|
(tags ?? []).indexOf(propName) !== -1,
|
|
359
|
-
xor(typeIsNot, clauseNot)
|
|
401
|
+
xor(typeIsNot, clauseNot),
|
|
360
402
|
);
|
|
361
403
|
break;
|
|
362
404
|
case 'defined':
|
|
@@ -367,7 +409,7 @@ export default abstract class NymphDriver {
|
|
|
367
409
|
case '!truthy':
|
|
368
410
|
pass = xor(
|
|
369
411
|
propName in data && data[propName],
|
|
370
|
-
xor(typeIsNot, clauseNot)
|
|
412
|
+
xor(typeIsNot, clauseNot),
|
|
371
413
|
);
|
|
372
414
|
break;
|
|
373
415
|
case 'ref':
|
|
@@ -377,9 +419,9 @@ export default abstract class NymphDriver {
|
|
|
377
419
|
propName in data &&
|
|
378
420
|
this.entityReferenceSearch(
|
|
379
421
|
data[propName],
|
|
380
|
-
testRefValue
|
|
422
|
+
testRefValue,
|
|
381
423
|
),
|
|
382
|
-
xor(typeIsNot, clauseNot)
|
|
424
|
+
xor(typeIsNot, clauseNot),
|
|
383
425
|
);
|
|
384
426
|
break;
|
|
385
427
|
case 'equal':
|
|
@@ -391,7 +433,7 @@ export default abstract class NymphDriver {
|
|
|
391
433
|
propName in data &&
|
|
392
434
|
JSON.stringify(data[propName]) ===
|
|
393
435
|
JSON.stringify(testEqualValue),
|
|
394
|
-
xor(typeIsNot, clauseNot)
|
|
436
|
+
xor(typeIsNot, clauseNot),
|
|
395
437
|
);
|
|
396
438
|
break;
|
|
397
439
|
case 'contain':
|
|
@@ -402,9 +444,9 @@ export default abstract class NymphDriver {
|
|
|
402
444
|
pass = xor(
|
|
403
445
|
propName in data &&
|
|
404
446
|
JSON.stringify(data[propName]).indexOf(
|
|
405
|
-
JSON.stringify(testContainValue)
|
|
447
|
+
JSON.stringify(testContainValue),
|
|
406
448
|
) !== -1,
|
|
407
|
-
xor(typeIsNot, clauseNot)
|
|
449
|
+
xor(typeIsNot, clauseNot),
|
|
408
450
|
);
|
|
409
451
|
break;
|
|
410
452
|
case 'like':
|
|
@@ -419,9 +461,27 @@ export default abstract class NymphDriver {
|
|
|
419
461
|
escapeRegExp(testLikeValue)
|
|
420
462
|
.replace('%', () => '.*')
|
|
421
463
|
.replace('_', () => '.') +
|
|
422
|
-
'$'
|
|
464
|
+
'$',
|
|
423
465
|
).test(data[propName]),
|
|
424
|
-
xor(typeIsNot, clauseNot)
|
|
466
|
+
xor(typeIsNot, clauseNot),
|
|
467
|
+
);
|
|
468
|
+
break;
|
|
469
|
+
case 'ilike':
|
|
470
|
+
case '!ilike':
|
|
471
|
+
const testILikeValue = (
|
|
472
|
+
curValue as [string, string]
|
|
473
|
+
)[1] as string;
|
|
474
|
+
pass = xor(
|
|
475
|
+
propName in data &&
|
|
476
|
+
new RegExp(
|
|
477
|
+
'^' +
|
|
478
|
+
escapeRegExp(testILikeValue)
|
|
479
|
+
.replace('%', () => '.*')
|
|
480
|
+
.replace('_', () => '.') +
|
|
481
|
+
'$',
|
|
482
|
+
'i',
|
|
483
|
+
).test(data[propName]),
|
|
484
|
+
xor(typeIsNot, clauseNot),
|
|
425
485
|
);
|
|
426
486
|
break;
|
|
427
487
|
case 'match':
|
|
@@ -433,7 +493,7 @@ export default abstract class NymphDriver {
|
|
|
433
493
|
pass = xor(
|
|
434
494
|
propName in data &&
|
|
435
495
|
this.posixRegexMatch(testMatchValue, data[propName]),
|
|
436
|
-
xor(typeIsNot, clauseNot)
|
|
496
|
+
xor(typeIsNot, clauseNot),
|
|
437
497
|
);
|
|
438
498
|
break;
|
|
439
499
|
case 'imatch':
|
|
@@ -447,9 +507,28 @@ export default abstract class NymphDriver {
|
|
|
447
507
|
this.posixRegexMatch(
|
|
448
508
|
testIMatchValue,
|
|
449
509
|
data[propName],
|
|
450
|
-
true
|
|
510
|
+
true,
|
|
451
511
|
),
|
|
452
|
-
xor(typeIsNot, clauseNot)
|
|
512
|
+
xor(typeIsNot, clauseNot),
|
|
513
|
+
);
|
|
514
|
+
break;
|
|
515
|
+
case 'search':
|
|
516
|
+
case '!search':
|
|
517
|
+
const testSearchValue = (
|
|
518
|
+
curValue as [string, string]
|
|
519
|
+
)[1] as string;
|
|
520
|
+
const tokenString =
|
|
521
|
+
propName in data
|
|
522
|
+
? (EntityClass.getFTSText(propName, data[propName]) ??
|
|
523
|
+
'')
|
|
524
|
+
: '';
|
|
525
|
+
pass = xor(
|
|
526
|
+
propName in data &&
|
|
527
|
+
this.tokenizer.searchString(
|
|
528
|
+
testSearchValue,
|
|
529
|
+
tokenString,
|
|
530
|
+
),
|
|
531
|
+
xor(typeIsNot, clauseNot),
|
|
453
532
|
);
|
|
454
533
|
break;
|
|
455
534
|
case 'gt':
|
|
@@ -459,7 +538,7 @@ export default abstract class NymphDriver {
|
|
|
459
538
|
)[1] as number;
|
|
460
539
|
pass = xor(
|
|
461
540
|
propName in data && data[propName] > testGTValue,
|
|
462
|
-
xor(typeIsNot, clauseNot)
|
|
541
|
+
xor(typeIsNot, clauseNot),
|
|
463
542
|
);
|
|
464
543
|
break;
|
|
465
544
|
case 'gte':
|
|
@@ -469,7 +548,7 @@ export default abstract class NymphDriver {
|
|
|
469
548
|
)[1] as number;
|
|
470
549
|
pass = xor(
|
|
471
550
|
propName in data && data[propName] >= testGTEValue,
|
|
472
|
-
xor(typeIsNot, clauseNot)
|
|
551
|
+
xor(typeIsNot, clauseNot),
|
|
473
552
|
);
|
|
474
553
|
break;
|
|
475
554
|
case 'lt':
|
|
@@ -479,7 +558,7 @@ export default abstract class NymphDriver {
|
|
|
479
558
|
)[1] as number;
|
|
480
559
|
pass = xor(
|
|
481
560
|
propName in data && data[propName] < testLTValue,
|
|
482
|
-
xor(typeIsNot, clauseNot)
|
|
561
|
+
xor(typeIsNot, clauseNot),
|
|
483
562
|
);
|
|
484
563
|
break;
|
|
485
564
|
case 'lte':
|
|
@@ -489,26 +568,33 @@ export default abstract class NymphDriver {
|
|
|
489
568
|
)[1] as number;
|
|
490
569
|
pass = xor(
|
|
491
570
|
propName in data && data[propName] <= testLTEValue,
|
|
492
|
-
xor(typeIsNot, clauseNot)
|
|
571
|
+
xor(typeIsNot, clauseNot),
|
|
493
572
|
);
|
|
494
573
|
break;
|
|
495
574
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
575
|
+
|
|
576
|
+
if (!xor(typeIsOr, pass)) {
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
499
579
|
}
|
|
500
580
|
}
|
|
501
581
|
}
|
|
582
|
+
if (!xor(typeIsOr, pass)) {
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
502
585
|
}
|
|
503
|
-
if (!
|
|
504
|
-
|
|
586
|
+
if (!pass) {
|
|
587
|
+
return false;
|
|
505
588
|
}
|
|
506
589
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
590
|
+
return true;
|
|
591
|
+
} catch (e: any) {
|
|
592
|
+
this.nymph.config.debugError(
|
|
593
|
+
'nymph',
|
|
594
|
+
`Failed to check entity data: ${e}`,
|
|
595
|
+
);
|
|
596
|
+
throw e;
|
|
510
597
|
}
|
|
511
|
-
return true;
|
|
512
598
|
}
|
|
513
599
|
|
|
514
600
|
/**
|
|
@@ -547,7 +633,7 @@ export default abstract class NymphDriver {
|
|
|
547
633
|
| EntityInterface
|
|
548
634
|
| EntityReference
|
|
549
635
|
| string
|
|
550
|
-
| (EntityInterface | EntityReference | string)[]
|
|
636
|
+
| (EntityInterface | EntityReference | string)[],
|
|
551
637
|
) {
|
|
552
638
|
// Get the GUID, if the passed $entity is an object.
|
|
553
639
|
const guids: string[] = [];
|
|
@@ -578,7 +664,7 @@ export default abstract class NymphDriver {
|
|
|
578
664
|
return true;
|
|
579
665
|
}
|
|
580
666
|
// Search through arrays and objects looking for the reference.
|
|
581
|
-
if (Array.isArray(value) || typeof value === 'object') {
|
|
667
|
+
if (value != null && (Array.isArray(value) || typeof value === 'object')) {
|
|
582
668
|
for (const key in value) {
|
|
583
669
|
if (this.entityReferenceSearch(value[key], guids)) {
|
|
584
670
|
return true;
|
|
@@ -590,7 +676,7 @@ export default abstract class NymphDriver {
|
|
|
590
676
|
|
|
591
677
|
public formatSelectors(
|
|
592
678
|
selectors: Selector[],
|
|
593
|
-
options: Options = {}
|
|
679
|
+
options: Options = {},
|
|
594
680
|
): {
|
|
595
681
|
selectors: FormattedSelector[];
|
|
596
682
|
qrefs: [Options, ...FormattedSelector[]][];
|
|
@@ -625,10 +711,9 @@ export default abstract class NymphDriver {
|
|
|
625
711
|
for (let i = 0; i < tmpArr.length; i++) {
|
|
626
712
|
const name = tmpArr[i][0];
|
|
627
713
|
const [qrefOptions, ...qrefSelectors] = tmpArr[i][1];
|
|
628
|
-
const QrefEntityClass =
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
: qrefOptions.class ?? this.nymph.getEntityClass('Entity');
|
|
714
|
+
const QrefEntityClass = qrefOptions.class
|
|
715
|
+
? this.nymph.getEntityClass(qrefOptions.class)
|
|
716
|
+
: this.nymph.getEntityClass('Entity');
|
|
632
717
|
const newOptions = {
|
|
633
718
|
...qrefOptions,
|
|
634
719
|
class: QrefEntityClass,
|
|
@@ -637,7 +722,7 @@ export default abstract class NymphDriver {
|
|
|
637
722
|
const newSelectors = this.formatSelectors(qrefSelectors, options);
|
|
638
723
|
qrefs.push(
|
|
639
724
|
[newOptions, ...newSelectors.selectors],
|
|
640
|
-
...newSelectors.qrefs
|
|
725
|
+
...newSelectors.qrefs,
|
|
641
726
|
);
|
|
642
727
|
formatArr[i] = [name, [newOptions, ...newSelectors.selectors]];
|
|
643
728
|
}
|
|
@@ -685,12 +770,12 @@ export default abstract class NymphDriver {
|
|
|
685
770
|
|
|
686
771
|
protected iterateSelectorsForQuery(
|
|
687
772
|
selectors: FormattedSelector[],
|
|
688
|
-
callback: (
|
|
689
|
-
key: string
|
|
690
|
-
value: any
|
|
691
|
-
typeIsOr: boolean
|
|
692
|
-
typeIsNot: boolean
|
|
693
|
-
) => string
|
|
773
|
+
callback: (data: {
|
|
774
|
+
key: string;
|
|
775
|
+
value: any;
|
|
776
|
+
typeIsOr: boolean;
|
|
777
|
+
typeIsNot: boolean;
|
|
778
|
+
}) => string,
|
|
694
779
|
) {
|
|
695
780
|
const queryParts = [];
|
|
696
781
|
for (const curSelector of selectors) {
|
|
@@ -703,7 +788,7 @@ export default abstract class NymphDriver {
|
|
|
703
788
|
if (key === 'type') {
|
|
704
789
|
continue;
|
|
705
790
|
}
|
|
706
|
-
let curQuery = callback(key, value, typeIsOr, typeIsNot);
|
|
791
|
+
let curQuery = callback({ key, value, typeIsOr, typeIsNot });
|
|
707
792
|
if (curQuery) {
|
|
708
793
|
if (curSelectorQuery) {
|
|
709
794
|
curSelectorQuery += typeIsOr ? ' OR ' : ' AND ';
|
|
@@ -719,71 +804,14 @@ export default abstract class NymphDriver {
|
|
|
719
804
|
return queryParts;
|
|
720
805
|
}
|
|
721
806
|
|
|
722
|
-
|
|
723
|
-
* Get the first entity to match all options/selectors, synchronously.
|
|
724
|
-
*
|
|
725
|
-
* This should really only be used internally by the Entity class. It's a bad
|
|
726
|
-
* idea to get entities synchronously. The Entity class uses this to load
|
|
727
|
-
* sleeping references.
|
|
728
|
-
*
|
|
729
|
-
* @param options The options.
|
|
730
|
-
* @param selectors Unlimited optional selectors to search for, or a single GUID. If none are given, all entities are searched for the given options.
|
|
731
|
-
* @returns An entity, or null on failure or nothing found.
|
|
732
|
-
*/
|
|
733
|
-
public getEntitySync<T extends EntityConstructor = EntityConstructor>(
|
|
734
|
-
options: Options<T> & { return: 'count' },
|
|
735
|
-
...selectors: Selector[]
|
|
736
|
-
): number;
|
|
737
|
-
public getEntitySync<T extends EntityConstructor = EntityConstructor>(
|
|
738
|
-
options: Options<T> & { return: 'guid' },
|
|
739
|
-
...selectors: Selector[]
|
|
740
|
-
): string | null;
|
|
741
|
-
public getEntitySync<T extends EntityConstructor = EntityConstructor>(
|
|
742
|
-
options: Options<T>,
|
|
743
|
-
...selectors: Selector[]
|
|
744
|
-
): ReturnType<T['factorySync']> | null;
|
|
745
|
-
public getEntitySync<T extends EntityConstructor = EntityConstructor>(
|
|
746
|
-
options: Options<T> & { return: 'count' },
|
|
747
|
-
guid: string
|
|
748
|
-
): number;
|
|
749
|
-
public getEntitySync<T extends EntityConstructor = EntityConstructor>(
|
|
750
|
-
options: Options<T> & { return: 'guid' },
|
|
751
|
-
guid: string
|
|
752
|
-
): string | null;
|
|
753
|
-
public getEntitySync<T extends EntityConstructor = EntityConstructor>(
|
|
754
|
-
options: Options<T>,
|
|
755
|
-
guid: string
|
|
756
|
-
): ReturnType<T['factorySync']> | null;
|
|
757
|
-
public getEntitySync<T extends EntityConstructor = EntityConstructor>(
|
|
758
|
-
options: Options<T> = {},
|
|
759
|
-
...selectors: Selector[] | string[]
|
|
760
|
-
): ReturnType<T['factorySync']> | string | number | null {
|
|
761
|
-
// Set up options and selectors.
|
|
762
|
-
if (typeof selectors[0] === 'string') {
|
|
763
|
-
selectors = [{ type: '&', guid: selectors[0] }];
|
|
764
|
-
}
|
|
765
|
-
options.limit = 1;
|
|
766
|
-
const entities = this.getEntitiesSync(
|
|
767
|
-
options,
|
|
768
|
-
...(selectors as Selector[])
|
|
769
|
-
);
|
|
770
|
-
if (options.return === 'count') {
|
|
771
|
-
return entities as unknown as number;
|
|
772
|
-
}
|
|
773
|
-
if (!entities || !entities.length) {
|
|
774
|
-
return null;
|
|
775
|
-
}
|
|
776
|
-
return entities[0];
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
protected getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
|
|
807
|
+
protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
|
|
780
808
|
options: Options<T> & { return: 'count' },
|
|
781
809
|
selectors: Selector[],
|
|
782
|
-
performQueryCallback: (
|
|
783
|
-
options: Options<T
|
|
784
|
-
|
|
785
|
-
etype: string
|
|
786
|
-
) => {
|
|
810
|
+
performQueryCallback: (data: {
|
|
811
|
+
options: Options<T>;
|
|
812
|
+
selectors: FormattedSelector[];
|
|
813
|
+
etype: string;
|
|
814
|
+
}) => {
|
|
787
815
|
result: any;
|
|
788
816
|
},
|
|
789
817
|
rowFetchCallback: () => any,
|
|
@@ -798,16 +826,16 @@ export default abstract class NymphDriver {
|
|
|
798
826
|
getDataNameAndSValueCallback: (row: any) => {
|
|
799
827
|
name: string;
|
|
800
828
|
svalue: string;
|
|
801
|
-
}
|
|
829
|
+
},
|
|
802
830
|
): { result: any; process: () => number | Error };
|
|
803
|
-
protected
|
|
831
|
+
protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
|
|
804
832
|
options: Options<T> & { return: 'guid' },
|
|
805
833
|
selectors: Selector[],
|
|
806
|
-
performQueryCallback: (
|
|
807
|
-
options: Options<T
|
|
808
|
-
|
|
809
|
-
etype: string
|
|
810
|
-
) => {
|
|
834
|
+
performQueryCallback: (data: {
|
|
835
|
+
options: Options<T>;
|
|
836
|
+
selectors: FormattedSelector[];
|
|
837
|
+
etype: string;
|
|
838
|
+
}) => {
|
|
811
839
|
result: any;
|
|
812
840
|
},
|
|
813
841
|
rowFetchCallback: () => any,
|
|
@@ -822,16 +850,40 @@ export default abstract class NymphDriver {
|
|
|
822
850
|
getDataNameAndSValueCallback: (row: any) => {
|
|
823
851
|
name: string;
|
|
824
852
|
svalue: string;
|
|
825
|
-
}
|
|
853
|
+
},
|
|
826
854
|
): { result: any; process: () => string[] | Error };
|
|
827
|
-
protected
|
|
855
|
+
protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
|
|
856
|
+
options: Options<T> & { return: 'object' },
|
|
857
|
+
selectors: Selector[],
|
|
858
|
+
performQueryCallback: (data: {
|
|
859
|
+
options: Options<T>;
|
|
860
|
+
selectors: FormattedSelector[];
|
|
861
|
+
etype: string;
|
|
862
|
+
}) => {
|
|
863
|
+
result: any;
|
|
864
|
+
},
|
|
865
|
+
rowFetchCallback: () => any,
|
|
866
|
+
freeResultCallback: () => void,
|
|
867
|
+
getCountCallback: (row: any) => number,
|
|
868
|
+
getGUIDCallback: (row: any) => string,
|
|
869
|
+
getTagsAndDatesCallback: (row: any) => {
|
|
870
|
+
tags: string[];
|
|
871
|
+
cdate: number;
|
|
872
|
+
mdate: number;
|
|
873
|
+
},
|
|
874
|
+
getDataNameAndSValueCallback: (row: any) => {
|
|
875
|
+
name: string;
|
|
876
|
+
svalue: string;
|
|
877
|
+
},
|
|
878
|
+
): { result: any; process: () => EntityObjectType<T>[] | Error };
|
|
879
|
+
protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
|
|
828
880
|
options: Options<T>,
|
|
829
881
|
selectors: Selector[],
|
|
830
|
-
performQueryCallback: (
|
|
831
|
-
options: Options<T
|
|
832
|
-
|
|
833
|
-
etype: string
|
|
834
|
-
) => {
|
|
882
|
+
performQueryCallback: (data: {
|
|
883
|
+
options: Options<T>;
|
|
884
|
+
selectors: FormattedSelector[];
|
|
885
|
+
etype: string;
|
|
886
|
+
}) => {
|
|
835
887
|
result: any;
|
|
836
888
|
},
|
|
837
889
|
rowFetchCallback: () => any,
|
|
@@ -846,16 +898,19 @@ export default abstract class NymphDriver {
|
|
|
846
898
|
getDataNameAndSValueCallback: (row: any) => {
|
|
847
899
|
name: string;
|
|
848
900
|
svalue: string;
|
|
849
|
-
}
|
|
850
|
-
): {
|
|
851
|
-
|
|
901
|
+
},
|
|
902
|
+
): {
|
|
903
|
+
result: any;
|
|
904
|
+
process: () => EntityInstanceType<T>[] | Error;
|
|
905
|
+
};
|
|
906
|
+
protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
|
|
852
907
|
options: Options<T>,
|
|
853
908
|
selectors: Selector[],
|
|
854
|
-
performQueryCallback: (
|
|
855
|
-
options: Options<T
|
|
856
|
-
|
|
857
|
-
etype: string
|
|
858
|
-
) => {
|
|
909
|
+
performQueryCallback: (data: {
|
|
910
|
+
options: Options<T>;
|
|
911
|
+
selectors: FormattedSelector[];
|
|
912
|
+
etype: string;
|
|
913
|
+
}) => {
|
|
859
914
|
result: any;
|
|
860
915
|
},
|
|
861
916
|
rowFetchCallback: () => any,
|
|
@@ -870,10 +925,15 @@ export default abstract class NymphDriver {
|
|
|
870
925
|
getDataNameAndSValueCallback: (row: any) => {
|
|
871
926
|
name: string;
|
|
872
927
|
svalue: string;
|
|
873
|
-
}
|
|
928
|
+
},
|
|
874
929
|
): {
|
|
875
930
|
result: any;
|
|
876
|
-
process: () =>
|
|
931
|
+
process: () =>
|
|
932
|
+
| EntityInstanceType<T>[]
|
|
933
|
+
| EntityObjectType<T>[]
|
|
934
|
+
| string[]
|
|
935
|
+
| number
|
|
936
|
+
| Error;
|
|
877
937
|
} {
|
|
878
938
|
if (!this.isConnected()) {
|
|
879
939
|
throw new UnableToConnectError('not connected to DB');
|
|
@@ -886,21 +946,29 @@ export default abstract class NymphDriver {
|
|
|
886
946
|
['&', '!&', '|', '!|'].indexOf(selector.type) === -1
|
|
887
947
|
) {
|
|
888
948
|
throw new InvalidParametersError(
|
|
889
|
-
'Invalid query selector passed: ' + JSON.stringify(selector)
|
|
949
|
+
'Invalid query selector passed: ' + JSON.stringify(selector),
|
|
890
950
|
);
|
|
891
951
|
}
|
|
892
952
|
}
|
|
893
953
|
|
|
894
|
-
let entities:
|
|
954
|
+
let entities: EntityInstanceType<T>[] | EntityObjectType<T>[] | string[] =
|
|
955
|
+
[];
|
|
895
956
|
let count = 0;
|
|
896
957
|
const EntityClass =
|
|
897
958
|
options.class ?? (this.nymph.getEntityClass('Entity') as T);
|
|
898
959
|
const etype = EntityClass.ETYPE;
|
|
899
960
|
|
|
961
|
+
if (options.source === 'client' && options.return === 'object') {
|
|
962
|
+
throw new InvalidParametersError(
|
|
963
|
+
'Object return type not allowed from client.',
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
|
|
900
967
|
// Check if the requested entity is cached.
|
|
901
968
|
if (
|
|
902
969
|
this.nymph.config.cache &&
|
|
903
970
|
!options.skipCache &&
|
|
971
|
+
selectors.length &&
|
|
904
972
|
'guid' in selectors[0] &&
|
|
905
973
|
typeof selectors[0].guid === 'number'
|
|
906
974
|
) {
|
|
@@ -912,33 +980,75 @@ export default abstract class NymphDriver {
|
|
|
912
980
|
(Object.keys(selectors[0]).length === 2 ||
|
|
913
981
|
(Object.keys(selectors[0]).length === 3 && 'tag' in selectors[0]))
|
|
914
982
|
) {
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
EntityClass.class,
|
|
918
|
-
!!options.skipAc
|
|
919
|
-
);
|
|
983
|
+
const cacheEntity = this.pullCache(selectors[0]['guid']);
|
|
984
|
+
|
|
920
985
|
if (
|
|
921
|
-
|
|
922
|
-
|
|
986
|
+
cacheEntity != null &&
|
|
987
|
+
cacheEntity.guid != null &&
|
|
923
988
|
(!('tag' in selectors[0]) ||
|
|
924
|
-
|
|
925
|
-
|
|
989
|
+
!(
|
|
990
|
+
Array.isArray(selectors[0].tag)
|
|
926
991
|
? selectors[0].tag
|
|
927
|
-
: [selectors[0].tag]
|
|
928
|
-
))
|
|
992
|
+
: [selectors[0].tag]
|
|
993
|
+
).find((tag) => tag == null || !cacheEntity.tags.includes(tag)))
|
|
929
994
|
) {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
995
|
+
// Return the value from the cache.
|
|
996
|
+
const { guid, cdate, mdate, tags, data, sdata } = cacheEntity;
|
|
997
|
+
|
|
998
|
+
if (options.return === 'count') {
|
|
999
|
+
return {
|
|
1000
|
+
result: Promise.resolve(null),
|
|
1001
|
+
process: () => 1,
|
|
1002
|
+
};
|
|
1003
|
+
} else if (options.return === 'guid') {
|
|
1004
|
+
return {
|
|
1005
|
+
result: Promise.resolve(null),
|
|
1006
|
+
process: () => [guid],
|
|
1007
|
+
};
|
|
1008
|
+
} else if (options.return === 'object') {
|
|
1009
|
+
return {
|
|
1010
|
+
result: Promise.resolve(null),
|
|
1011
|
+
process: () => [
|
|
1012
|
+
{
|
|
1013
|
+
guid,
|
|
1014
|
+
cdate,
|
|
1015
|
+
mdate,
|
|
1016
|
+
tags,
|
|
1017
|
+
...data,
|
|
1018
|
+
...Object.fromEntries(
|
|
1019
|
+
Object.entries(sdata).map(([name, value]) => [
|
|
1020
|
+
name,
|
|
1021
|
+
JSON.parse(value),
|
|
1022
|
+
]),
|
|
1023
|
+
),
|
|
1024
|
+
} as EntityObjectType<T>,
|
|
1025
|
+
],
|
|
1026
|
+
};
|
|
1027
|
+
} else {
|
|
1028
|
+
const entity = this.nymph
|
|
1029
|
+
.getEntityClass(EntityClass.class)
|
|
1030
|
+
.factorySync() as EntityInstanceType<T>;
|
|
1031
|
+
if (entity) {
|
|
1032
|
+
entity.$nymph = this.nymph;
|
|
1033
|
+
if (options.skipAc != null) {
|
|
1034
|
+
entity.$useSkipAc(!!options.skipAc);
|
|
1035
|
+
}
|
|
1036
|
+
entity.guid = guid;
|
|
1037
|
+
entity.cdate = cdate;
|
|
1038
|
+
entity.mdate = mdate;
|
|
1039
|
+
entity.tags = tags;
|
|
1040
|
+
entity.$putData(data, sdata, 'server');
|
|
1041
|
+
return {
|
|
1042
|
+
result: Promise.resolve(null),
|
|
1043
|
+
process: () => [entity],
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return {
|
|
1048
|
+
result: Promise.resolve(null),
|
|
1049
|
+
process: () => [],
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
942
1052
|
}
|
|
943
1053
|
}
|
|
944
1054
|
}
|
|
@@ -946,15 +1056,16 @@ export default abstract class NymphDriver {
|
|
|
946
1056
|
try {
|
|
947
1057
|
const formattedSelectors = this.formatSelectors(selectors, options);
|
|
948
1058
|
this.nymph.runQueryCallbacks(options, formattedSelectors.selectors);
|
|
949
|
-
for (let
|
|
950
|
-
const [options, ...selectors] =
|
|
1059
|
+
for (let i = 0; i < formattedSelectors.qrefs.length; i++) {
|
|
1060
|
+
const [options, ...selectors] = formattedSelectors.qrefs[i];
|
|
951
1061
|
this.nymph.runQueryCallbacks(options, selectors);
|
|
1062
|
+
formattedSelectors.qrefs[i] = [options, ...selectors];
|
|
952
1063
|
}
|
|
953
|
-
const { result } = performQueryCallback(
|
|
1064
|
+
const { result } = performQueryCallback({
|
|
954
1065
|
options,
|
|
955
|
-
formattedSelectors.selectors,
|
|
956
|
-
etype
|
|
957
|
-
);
|
|
1066
|
+
selectors: formattedSelectors.selectors,
|
|
1067
|
+
etype,
|
|
1068
|
+
});
|
|
958
1069
|
|
|
959
1070
|
return {
|
|
960
1071
|
result,
|
|
@@ -994,28 +1105,43 @@ export default abstract class NymphDriver {
|
|
|
994
1105
|
// Make sure that $row is incremented :)
|
|
995
1106
|
row = rowFetchCallback();
|
|
996
1107
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1108
|
+
|
|
1109
|
+
if (options.return === 'object') {
|
|
1110
|
+
(entities as EntityObjectType<T>[]).push({
|
|
1111
|
+
guid,
|
|
1112
|
+
cdate,
|
|
1113
|
+
mdate,
|
|
1114
|
+
tags,
|
|
1115
|
+
...data,
|
|
1116
|
+
...Object.fromEntries(
|
|
1117
|
+
Object.entries(sdata).map(([name, value]) => [
|
|
1118
|
+
name,
|
|
1119
|
+
JSON.parse(value),
|
|
1120
|
+
]),
|
|
1121
|
+
),
|
|
1122
|
+
} as EntityObjectType<T>);
|
|
1123
|
+
} else {
|
|
1124
|
+
const entity =
|
|
1125
|
+
EntityClass.factorySync() as EntityInstanceType<T>;
|
|
1126
|
+
entity.$nymph = this.nymph;
|
|
1127
|
+
if (options.skipAc != null) {
|
|
1128
|
+
entity.$useSkipAc(!!options.skipAc);
|
|
1129
|
+
}
|
|
1130
|
+
entity.guid = guid;
|
|
1131
|
+
entity.cdate = cdate;
|
|
1132
|
+
entity.mdate = mdate;
|
|
1133
|
+
entity.tags = tags;
|
|
1134
|
+
this.putDataCounter++;
|
|
1135
|
+
if (this.putDataCounter == 100) {
|
|
1136
|
+
throw new Error('Infinite loop detected in Entity loading.');
|
|
1137
|
+
}
|
|
1138
|
+
entity.$putData(data, sdata, 'server');
|
|
1139
|
+
this.putDataCounter--;
|
|
1140
|
+
entities.push(entity);
|
|
1011
1141
|
}
|
|
1012
|
-
entity.$putData(data, sdata);
|
|
1013
|
-
this.putDataCounter--;
|
|
1014
1142
|
if (this.nymph.config.cache) {
|
|
1015
1143
|
this.pushCache(guid, cdate, mdate, tags, data, sdata);
|
|
1016
1144
|
}
|
|
1017
|
-
// @ts-ignore: ts doesn't know the return type here.
|
|
1018
|
-
entities.push(entity);
|
|
1019
1145
|
}
|
|
1020
1146
|
}
|
|
1021
1147
|
|
|
@@ -1036,88 +1162,115 @@ export default abstract class NymphDriver {
|
|
|
1036
1162
|
|
|
1037
1163
|
protected async saveEntityRowLike(
|
|
1038
1164
|
entity: EntityInterface,
|
|
1039
|
-
saveNewEntityCallback: (
|
|
1040
|
-
entity: EntityInterface
|
|
1041
|
-
guid: string
|
|
1042
|
-
tags: string[]
|
|
1043
|
-
data: EntityData
|
|
1044
|
-
sdata: SerializedEntityData
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1165
|
+
saveNewEntityCallback: (data: {
|
|
1166
|
+
entity: EntityInterface;
|
|
1167
|
+
guid: string;
|
|
1168
|
+
tags: string[];
|
|
1169
|
+
data: EntityData;
|
|
1170
|
+
sdata: SerializedEntityData;
|
|
1171
|
+
uniques: string[];
|
|
1172
|
+
cdate: number;
|
|
1173
|
+
etype: string;
|
|
1174
|
+
}) => Promise<boolean>,
|
|
1175
|
+
saveExistingEntityCallback: (data: {
|
|
1176
|
+
entity: EntityInterface;
|
|
1177
|
+
guid: string;
|
|
1178
|
+
tags: string[];
|
|
1179
|
+
data: EntityData;
|
|
1180
|
+
sdata: SerializedEntityData;
|
|
1181
|
+
uniques: string[];
|
|
1182
|
+
mdate: number;
|
|
1183
|
+
etype: string;
|
|
1184
|
+
}) => Promise<boolean>,
|
|
1057
1185
|
startTransactionCallback: (() => Promise<void>) | null = null,
|
|
1058
1186
|
commitTransactionCallback:
|
|
1059
1187
|
| ((success: boolean) => Promise<boolean>)
|
|
1060
|
-
| null = null
|
|
1188
|
+
| null = null,
|
|
1061
1189
|
) {
|
|
1190
|
+
const originalGuid = entity.guid;
|
|
1191
|
+
const originalCdate = entity.cdate;
|
|
1192
|
+
const originalMdate = entity.mdate;
|
|
1062
1193
|
// Get a modified date.
|
|
1063
|
-
|
|
1194
|
+
let mdate = Date.now();
|
|
1195
|
+
if (entity.mdate != null) {
|
|
1196
|
+
if (this.nymph.config.updateMDate === false) {
|
|
1197
|
+
mdate = entity.mdate;
|
|
1198
|
+
} else if (typeof this.nymph.config.updateMDate === 'number') {
|
|
1199
|
+
mdate = entity.mdate + this.nymph.config.updateMDate;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1064
1202
|
const tags = difference(entity.tags, ['']);
|
|
1065
1203
|
const data = entity.$getData();
|
|
1066
1204
|
const sdata = entity.$getSData();
|
|
1205
|
+
const uniques = await entity.$getUniques();
|
|
1067
1206
|
const EntityClass = entity.constructor as EntityConstructor;
|
|
1068
1207
|
const etype = EntityClass.ETYPE;
|
|
1069
1208
|
if (startTransactionCallback) {
|
|
1070
1209
|
await startTransactionCallback();
|
|
1071
1210
|
}
|
|
1072
1211
|
let success = false;
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1212
|
+
try {
|
|
1213
|
+
if (entity.guid == null) {
|
|
1214
|
+
const cdate = mdate;
|
|
1215
|
+
const newId = entity.$getGuaranteedGUID();
|
|
1216
|
+
success = await saveNewEntityCallback({
|
|
1217
|
+
entity,
|
|
1218
|
+
guid: newId,
|
|
1219
|
+
tags,
|
|
1220
|
+
data,
|
|
1221
|
+
sdata,
|
|
1222
|
+
uniques,
|
|
1223
|
+
cdate,
|
|
1224
|
+
etype,
|
|
1225
|
+
});
|
|
1226
|
+
if (success) {
|
|
1227
|
+
entity.guid = newId;
|
|
1228
|
+
entity.cdate = cdate;
|
|
1229
|
+
entity.mdate = mdate;
|
|
1230
|
+
}
|
|
1231
|
+
} else {
|
|
1232
|
+
// Removed any cached versions of this entity.
|
|
1233
|
+
if (this.nymph.config.cache) {
|
|
1234
|
+
this.cleanCache(entity.guid);
|
|
1235
|
+
}
|
|
1236
|
+
success = await saveExistingEntityCallback({
|
|
1237
|
+
entity,
|
|
1238
|
+
guid: entity.guid,
|
|
1239
|
+
tags,
|
|
1240
|
+
data,
|
|
1241
|
+
sdata,
|
|
1242
|
+
uniques,
|
|
1243
|
+
mdate,
|
|
1244
|
+
etype,
|
|
1245
|
+
});
|
|
1246
|
+
if (success) {
|
|
1247
|
+
entity.mdate = mdate;
|
|
1248
|
+
}
|
|
1089
1249
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
if (this.nymph.config.cache) {
|
|
1093
|
-
this.cleanCache(entity.guid);
|
|
1250
|
+
if (commitTransactionCallback) {
|
|
1251
|
+
success = await commitTransactionCallback(success);
|
|
1094
1252
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
entity.mdate = mdate;
|
|
1253
|
+
// Cache the entity.
|
|
1254
|
+
if (success && this.nymph.config.cache) {
|
|
1255
|
+
this.pushCache(
|
|
1256
|
+
entity.guid as string,
|
|
1257
|
+
entity.cdate as number,
|
|
1258
|
+
entity.mdate as number,
|
|
1259
|
+
entity.tags,
|
|
1260
|
+
data,
|
|
1261
|
+
sdata,
|
|
1262
|
+
);
|
|
1106
1263
|
}
|
|
1264
|
+
} catch (e: any) {
|
|
1265
|
+
entity.guid = originalGuid;
|
|
1266
|
+
entity.cdate = originalCdate;
|
|
1267
|
+
entity.mdate = originalMdate;
|
|
1268
|
+
throw e;
|
|
1107
1269
|
}
|
|
1108
|
-
if (
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
if (success && this.nymph.config.cache) {
|
|
1113
|
-
this.pushCache(
|
|
1114
|
-
entity.guid as string,
|
|
1115
|
-
entity.cdate as number,
|
|
1116
|
-
entity.mdate as number,
|
|
1117
|
-
entity.tags,
|
|
1118
|
-
data,
|
|
1119
|
-
sdata
|
|
1120
|
-
);
|
|
1270
|
+
if (!success) {
|
|
1271
|
+
entity.guid = originalGuid;
|
|
1272
|
+
entity.cdate = originalCdate;
|
|
1273
|
+
entity.mdate = originalMdate;
|
|
1121
1274
|
}
|
|
1122
1275
|
return success;
|
|
1123
1276
|
}
|
|
@@ -1126,35 +1279,30 @@ export default abstract class NymphDriver {
|
|
|
1126
1279
|
* Pull an entity from the cache.
|
|
1127
1280
|
*
|
|
1128
1281
|
* @param guid The entity's GUID.
|
|
1129
|
-
* @
|
|
1130
|
-
* @param useSkipAc Whether to tell the entity to use skip_ac.
|
|
1131
|
-
* @returns The entity or null if it's not cached.
|
|
1282
|
+
* @returns The entity's data or null if it's not cached.
|
|
1132
1283
|
*/
|
|
1133
|
-
protected pullCache
|
|
1134
|
-
guid: string
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1284
|
+
protected pullCache(guid: string): {
|
|
1285
|
+
guid: string;
|
|
1286
|
+
cdate: number;
|
|
1287
|
+
mdate: number;
|
|
1288
|
+
tags: string[];
|
|
1289
|
+
data: EntityData;
|
|
1290
|
+
sdata: SerializedEntityData;
|
|
1291
|
+
} | null {
|
|
1138
1292
|
// Increment the entity access count.
|
|
1139
1293
|
if (!(guid in this.entityCount)) {
|
|
1140
1294
|
this.entityCount[guid] = 0;
|
|
1141
1295
|
}
|
|
1142
1296
|
this.entityCount[guid]++;
|
|
1143
1297
|
if (guid in this.entityCache) {
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
entity.$putData(
|
|
1153
|
-
this.entityCache[guid]['data'],
|
|
1154
|
-
this.entityCache[guid]['sdata']
|
|
1155
|
-
);
|
|
1156
|
-
return entity;
|
|
1157
|
-
}
|
|
1298
|
+
return {
|
|
1299
|
+
guid,
|
|
1300
|
+
cdate: this.entityCache[guid]['cdate'],
|
|
1301
|
+
mdate: this.entityCache[guid]['mdate'],
|
|
1302
|
+
tags: this.entityCache[guid]['tags'],
|
|
1303
|
+
data: this.entityCache[guid]['data'],
|
|
1304
|
+
sdata: this.entityCache[guid]['sdata'],
|
|
1305
|
+
};
|
|
1158
1306
|
}
|
|
1159
1307
|
return null;
|
|
1160
1308
|
}
|
|
@@ -1175,7 +1323,7 @@ export default abstract class NymphDriver {
|
|
|
1175
1323
|
mdate: number,
|
|
1176
1324
|
tags: string[],
|
|
1177
1325
|
data: EntityData,
|
|
1178
|
-
sdata: SerializedEntityData
|
|
1326
|
+
sdata: SerializedEntityData,
|
|
1179
1327
|
) {
|
|
1180
1328
|
if (guid == null) {
|
|
1181
1329
|
return;
|
|
@@ -1191,11 +1339,11 @@ export default abstract class NymphDriver {
|
|
|
1191
1339
|
}
|
|
1192
1340
|
// Cache the entity.
|
|
1193
1341
|
this.entityCache[guid] = {
|
|
1194
|
-
cdate
|
|
1195
|
-
mdate
|
|
1196
|
-
tags
|
|
1197
|
-
data
|
|
1198
|
-
sdata
|
|
1342
|
+
cdate,
|
|
1343
|
+
mdate,
|
|
1344
|
+
tags,
|
|
1345
|
+
data,
|
|
1346
|
+
sdata,
|
|
1199
1347
|
};
|
|
1200
1348
|
while (
|
|
1201
1349
|
this.nymph.config.cacheLimit &&
|
|
@@ -1204,7 +1352,7 @@ export default abstract class NymphDriver {
|
|
|
1204
1352
|
// Find which entity has been accessed the least.
|
|
1205
1353
|
const least =
|
|
1206
1354
|
Object.entries(this.entityCount)
|
|
1207
|
-
.sort(([
|
|
1355
|
+
.sort(([_guidA, countA], [_guidB, countB]) => countA - countB)
|
|
1208
1356
|
.pop()?.[0] ?? null;
|
|
1209
1357
|
if (least == null) {
|
|
1210
1358
|
// This should never happen.
|