@nymphjs/nymph 1.0.0-beta.13 → 1.0.0-beta.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +644 -0
- package/README.md +34 -24
- package/dist/Entity.d.ts +304 -15
- package/dist/Entity.js +609 -154
- package/dist/Entity.js.map +1 -1
- package/dist/Entity.test.js +75 -29
- package/dist/Entity.test.js.map +1 -1
- package/dist/Entity.types.d.ts +275 -28
- package/dist/Entity.types.js +1 -2
- package/dist/Nymph.d.ts +575 -11
- package/dist/Nymph.js +825 -288
- package/dist/Nymph.js.map +1 -1
- package/dist/Nymph.types.d.ts +117 -7
- package/dist/Nymph.types.js +47 -5
- package/dist/Nymph.types.js.map +1 -1
- package/dist/conf/defaults.d.ts +1 -1
- package/dist/conf/defaults.js +26 -3
- package/dist/conf/defaults.js.map +1 -1
- package/dist/conf/index.d.ts +50 -2
- package/dist/conf/index.js +2 -8
- package/dist/conf/index.js.map +1 -1
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/index.js +2 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/transactional.d.ts +12 -0
- package/dist/decorators/transactional.js +45 -0
- package/dist/decorators/transactional.js.map +1 -0
- package/dist/driver/NymphDriver.d.ts +209 -41
- package/dist/driver/NymphDriver.js +737 -242
- package/dist/driver/NymphDriver.js.map +1 -1
- package/dist/driver/index.d.ts +1 -2
- package/dist/driver/index.js +2 -23
- package/dist/driver/index.js.map +1 -1
- package/dist/driver/testArtifacts.d.ts +4 -4
- package/dist/driver/testArtifacts.js +2016 -1086
- package/dist/driver/testArtifacts.js.map +1 -1
- package/dist/errors/ClassNotAvailableError.js +1 -5
- package/dist/errors/ClassNotAvailableError.js.map +1 -1
- package/dist/errors/EntityConflictError.js +1 -5
- package/dist/errors/EntityConflictError.js.map +1 -1
- package/dist/errors/EntityInvalidDataError.d.ts +6 -0
- package/dist/errors/EntityInvalidDataError.js +8 -6
- package/dist/errors/EntityInvalidDataError.js.map +1 -1
- package/dist/errors/EntityIsSleepingReferenceError.js +1 -5
- package/dist/errors/EntityIsSleepingReferenceError.js.map +1 -1
- package/dist/errors/EntityUniqueConstraintError.d.ts +3 -0
- package/dist/errors/EntityUniqueConstraintError.js +7 -0
- package/dist/errors/EntityUniqueConstraintError.js.map +1 -0
- package/dist/errors/HttpError.d.ts +5 -0
- package/dist/errors/HttpError.js +11 -0
- package/dist/errors/HttpError.js.map +1 -0
- package/dist/errors/InvalidParametersError.js +1 -5
- package/dist/errors/InvalidParametersError.js.map +1 -1
- package/dist/errors/InvalidStateError.d.ts +3 -0
- package/dist/errors/InvalidStateError.js +7 -0
- package/dist/errors/InvalidStateError.js.map +1 -0
- package/dist/errors/MethodFailedError.d.ts +3 -0
- package/dist/errors/MethodFailedError.js +7 -0
- package/dist/errors/MethodFailedError.js.map +1 -0
- package/dist/errors/NotConfiguredError.js +1 -5
- package/dist/errors/NotConfiguredError.js.map +1 -1
- package/dist/errors/QueryFailedError.d.ts +2 -1
- package/dist/errors/QueryFailedError.js +5 -6
- package/dist/errors/QueryFailedError.js.map +1 -1
- package/dist/errors/UnableToConnectError.js +1 -5
- package/dist/errors/UnableToConnectError.js.map +1 -1
- package/dist/errors/index.d.ts +12 -8
- package/dist/errors/index.js +12 -24
- package/dist/errors/index.js.map +1 -1
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +2 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/transaction.d.ts +15 -0
- package/dist/helpers/transaction.js +47 -0
- package/dist/helpers/transaction.js.map +1 -0
- package/dist/index.d.ts +13 -11
- package/dist/index.js +16 -34
- package/dist/index.js.map +1 -1
- package/dist/nymphJoiProps.d.ts +1 -1
- package/dist/nymphJoiProps.js +10 -15
- package/dist/nymphJoiProps.js.map +1 -1
- package/dist/testArtifacts.d.ts +21 -6
- package/dist/testArtifacts.js +124 -50
- package/dist/testArtifacts.js.map +1 -1
- package/dist/testMocks.d.ts +27 -12
- package/dist/testMocks.js +44 -33
- package/dist/testMocks.js.map +1 -1
- package/dist/utils.d.ts +5 -5
- package/dist/utils.js +106 -68
- package/dist/utils.js.map +1 -1
- package/jest.config.js +31 -5
- package/package.json +43 -24
- package/src/Entity.test.ts +73 -23
- package/src/Entity.ts +555 -169
- package/src/Entity.types.ts +102 -90
- package/src/Nymph.ts +930 -517
- package/src/Nymph.types.ts +145 -39
- package/src/conf/defaults.ts +34 -1
- package/src/conf/index.ts +50 -2
- package/src/decorators/index.ts +1 -0
- package/src/decorators/transactional.ts +63 -0
- package/src/driver/NymphDriver.ts +987 -417
- package/src/driver/index.ts +1 -3
- package/src/driver/testArtifacts.ts +2699 -1628
- package/src/errors/EntityUniqueConstraintError.ts +6 -0
- package/src/errors/HttpError.ts +12 -0
- package/src/errors/InvalidStateError.ts +6 -0
- package/src/errors/MethodFailedError.ts +6 -0
- package/src/errors/QueryFailedError.ts +3 -1
- package/src/errors/index.ts +16 -8
- package/src/helpers/index.ts +1 -0
- package/src/helpers/transaction.ts +65 -0
- package/src/index.ts +13 -11
- package/src/nymphJoiProps.ts +2 -2
- package/src/testArtifacts.ts +69 -20
- package/src/testMocks.ts +107 -41
- package/src/utils.ts +102 -67
- package/tsconfig.json +6 -3
- package/typedoc.json +4 -0
- package/dist/conf/d.d.ts +0 -6
- package/dist/conf/d.js +0 -3
- package/dist/conf/d.js.map +0 -1
- package/src/conf/d.ts +0 -24
|
@@ -1,20 +1,32 @@
|
|
|
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/
|
|
5
|
-
import {
|
|
4
|
+
import { strtotime } from 'locutus/php/datetime/index';
|
|
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 Entity from '../Entity.js';
|
|
10
|
+
import type { EntityInstanceType, EntityObjectType } from '../Entity.js';
|
|
11
|
+
import type {
|
|
9
12
|
EntityConstructor,
|
|
10
13
|
EntityData,
|
|
11
14
|
EntityInterface,
|
|
12
15
|
EntityReference,
|
|
13
16
|
SerializedEntityData,
|
|
14
|
-
} from '../Entity.types';
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
} from '../Entity.types.js';
|
|
18
|
+
import {
|
|
19
|
+
ClassNotAvailableError,
|
|
20
|
+
InvalidParametersError,
|
|
21
|
+
UnableToConnectError,
|
|
22
|
+
} from '../errors/index.js';
|
|
23
|
+
import {
|
|
24
|
+
classNamesToEntityConstructors,
|
|
25
|
+
entitiesToReferences,
|
|
26
|
+
entityConstructorsToClassNames,
|
|
27
|
+
referencesToEntities,
|
|
28
|
+
xor,
|
|
29
|
+
} from '../utils.js';
|
|
18
30
|
|
|
19
31
|
// from: https://stackoverflow.com/a/6969486/664915
|
|
20
32
|
function escapeRegExp(string: string): string {
|
|
@@ -28,11 +40,13 @@ export default abstract class NymphDriver {
|
|
|
28
40
|
protected nymph: Nymph = new Proxy({} as Nymph, {
|
|
29
41
|
get() {
|
|
30
42
|
throw new Error(
|
|
31
|
-
'Attempted access of Nymph instance before driver initialization!'
|
|
43
|
+
'Attempted access of Nymph instance before driver initialization!',
|
|
32
44
|
);
|
|
33
45
|
},
|
|
34
46
|
});
|
|
35
47
|
|
|
48
|
+
protected tokenizer = new Tokenizer();
|
|
49
|
+
|
|
36
50
|
/**
|
|
37
51
|
* A cache to make entity retrieval faster.
|
|
38
52
|
*/
|
|
@@ -67,13 +81,14 @@ export default abstract class NymphDriver {
|
|
|
67
81
|
abstract connect(): Promise<boolean>;
|
|
68
82
|
|
|
69
83
|
abstract isConnected(): boolean;
|
|
84
|
+
protected abstract internalTransaction(name: string): Promise<any>;
|
|
70
85
|
abstract startTransaction(name: string): Promise<Nymph>;
|
|
71
86
|
abstract commit(name: string): Promise<boolean>;
|
|
72
87
|
abstract rollback(name: string): Promise<boolean>;
|
|
73
88
|
abstract inTransaction(): Promise<boolean>;
|
|
74
89
|
abstract deleteEntityByID(
|
|
75
90
|
guid: string,
|
|
76
|
-
classConstructor: EntityConstructor
|
|
91
|
+
classConstructor: EntityConstructor,
|
|
77
92
|
): Promise<boolean>;
|
|
78
93
|
abstract deleteEntityByID(guid: string, className?: string): Promise<boolean>;
|
|
79
94
|
abstract deleteUID(name: string): Promise<boolean>;
|
|
@@ -83,9 +98,42 @@ export default abstract class NymphDriver {
|
|
|
83
98
|
*/
|
|
84
99
|
abstract disconnect(): Promise<boolean>;
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
abstract needsMigration(): Promise<
|
|
102
|
+
'json' | 'tokens' | 'tilmeldColumns' | false
|
|
103
|
+
>;
|
|
104
|
+
abstract liveMigration(
|
|
105
|
+
migrationType: 'tokenTables' | 'tilmeldColumns' | 'tilmeldRemoveOldRows',
|
|
88
106
|
): Promise<void>;
|
|
107
|
+
|
|
108
|
+
abstract getEtypes(): Promise<string[]>;
|
|
109
|
+
abstract getIndexes(etype: string): Promise<
|
|
110
|
+
{
|
|
111
|
+
scope: 'data' | 'references' | 'tokens';
|
|
112
|
+
name: string;
|
|
113
|
+
property: string;
|
|
114
|
+
}[]
|
|
115
|
+
>;
|
|
116
|
+
abstract addIndex(
|
|
117
|
+
etype: string,
|
|
118
|
+
definition: {
|
|
119
|
+
scope: 'data' | 'references' | 'tokens';
|
|
120
|
+
name: string;
|
|
121
|
+
property: string;
|
|
122
|
+
},
|
|
123
|
+
): Promise<boolean>;
|
|
124
|
+
abstract deleteIndex(
|
|
125
|
+
etype: string,
|
|
126
|
+
scope: 'data' | 'references' | 'tokens',
|
|
127
|
+
name: string,
|
|
128
|
+
): Promise<boolean>;
|
|
129
|
+
|
|
130
|
+
abstract exportDataIterator(): AsyncGenerator<
|
|
131
|
+
{ type: 'comment' | 'uid' | 'entity'; content: string },
|
|
132
|
+
void,
|
|
133
|
+
undefined | false
|
|
134
|
+
>;
|
|
135
|
+
abstract exportEntity(etype: string, guid: string): Promise<string>;
|
|
136
|
+
|
|
89
137
|
abstract getEntities<T extends EntityConstructor = EntityConstructor>(
|
|
90
138
|
options: Options<T> & { return: 'count' },
|
|
91
139
|
...selectors: Selector[]
|
|
@@ -95,44 +143,53 @@ export default abstract class NymphDriver {
|
|
|
95
143
|
...selectors: Selector[]
|
|
96
144
|
): Promise<string[]>;
|
|
97
145
|
abstract getEntities<T extends EntityConstructor = EntityConstructor>(
|
|
98
|
-
options
|
|
146
|
+
options: Options<T> & { return: 'object' },
|
|
99
147
|
...selectors: Selector[]
|
|
100
|
-
): Promise<
|
|
148
|
+
): Promise<EntityObjectType<T>[]>;
|
|
101
149
|
abstract getEntities<T extends EntityConstructor = EntityConstructor>(
|
|
102
150
|
options?: Options<T>,
|
|
103
151
|
...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
|
-
>(
|
|
152
|
+
): Promise<EntityInstanceType<T>[]>;
|
|
153
|
+
abstract getEntities<T extends EntityConstructor = EntityConstructor>(
|
|
126
154
|
options?: Options<T>,
|
|
127
155
|
...selectors: Selector[]
|
|
128
|
-
):
|
|
156
|
+
): Promise<
|
|
157
|
+
EntityInstanceType<T>[] | EntityObjectType<T>[] | string[] | number
|
|
158
|
+
>;
|
|
129
159
|
abstract getUID(name: string): Promise<number | null>;
|
|
130
|
-
|
|
160
|
+
|
|
161
|
+
abstract importEntity(entity: {
|
|
162
|
+
guid: string;
|
|
163
|
+
cdate: number;
|
|
164
|
+
mdate: number;
|
|
165
|
+
tags: string[];
|
|
166
|
+
sdata: SerializedEntityData;
|
|
167
|
+
etype: string;
|
|
168
|
+
}): Promise<void>;
|
|
169
|
+
abstract importEntityTokens(entity: {
|
|
170
|
+
guid: string;
|
|
171
|
+
cdate: number;
|
|
172
|
+
mdate: number;
|
|
173
|
+
tags: string[];
|
|
174
|
+
sdata: SerializedEntityData;
|
|
175
|
+
etype: string;
|
|
176
|
+
}): Promise<void>;
|
|
177
|
+
abstract importEntityTilmeldAC(entity: {
|
|
178
|
+
guid: string;
|
|
179
|
+
cdate: number;
|
|
180
|
+
mdate: number;
|
|
181
|
+
tags: string[];
|
|
182
|
+
sdata: SerializedEntityData;
|
|
183
|
+
etype: string;
|
|
184
|
+
}): Promise<void>;
|
|
185
|
+
abstract importUID(uid: { name: string; value: number }): Promise<void>;
|
|
186
|
+
|
|
131
187
|
abstract newUID(name: string): Promise<number | null>;
|
|
132
188
|
abstract renameUID(oldName: string, newName: string): Promise<boolean>;
|
|
133
|
-
abstract saveEntity(entity: EntityInterface): Promise<boolean>;
|
|
134
189
|
abstract setUID(name: string, value: number): Promise<boolean>;
|
|
135
190
|
|
|
191
|
+
abstract saveEntity(entity: EntityInterface): Promise<boolean>;
|
|
192
|
+
|
|
136
193
|
/**
|
|
137
194
|
* Initialize the Nymph driver.
|
|
138
195
|
*
|
|
@@ -147,7 +204,7 @@ export default abstract class NymphDriver {
|
|
|
147
204
|
protected posixRegexMatch(
|
|
148
205
|
pattern: string,
|
|
149
206
|
subject: string,
|
|
150
|
-
caseInsensitive = false
|
|
207
|
+
caseInsensitive = false,
|
|
151
208
|
) {
|
|
152
209
|
const posixClasses = [
|
|
153
210
|
// '[[:<:]]',
|
|
@@ -198,15 +255,23 @@ export default abstract class NymphDriver {
|
|
|
198
255
|
return re.test(subject);
|
|
199
256
|
}
|
|
200
257
|
|
|
258
|
+
public checkIndexName(name: string) {
|
|
259
|
+
if (name.match(/[^a-z0-9_]/)) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
'Index names can only contain lowercase letters, numbers, and underscores.',
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
201
266
|
public async export(filename: string) {
|
|
202
267
|
try {
|
|
203
268
|
const fhandle = fs.openSync(filename, 'w');
|
|
204
269
|
if (!fhandle) {
|
|
205
270
|
throw new InvalidParametersError('Provided filename is not writeable.');
|
|
206
271
|
}
|
|
207
|
-
await this.
|
|
208
|
-
fs.writeSync(fhandle,
|
|
209
|
-
}
|
|
272
|
+
for await (let entry of this.exportDataIterator()) {
|
|
273
|
+
fs.writeSync(fhandle, entry.content);
|
|
274
|
+
}
|
|
210
275
|
fs.closeSync(fhandle);
|
|
211
276
|
} catch (e: any) {
|
|
212
277
|
return false;
|
|
@@ -215,23 +280,127 @@ export default abstract class NymphDriver {
|
|
|
215
280
|
}
|
|
216
281
|
|
|
217
282
|
public async exportPrint() {
|
|
218
|
-
await this.
|
|
219
|
-
console.log(
|
|
220
|
-
}
|
|
283
|
+
for await (let entry of this.exportDataIterator()) {
|
|
284
|
+
console.log(entry.content);
|
|
285
|
+
}
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
public async importDataIterator(
|
|
290
|
+
lines: Iterable<string>,
|
|
291
|
+
options?: { ignoreUnknownETypes?: boolean },
|
|
292
|
+
transaction?: boolean,
|
|
293
|
+
) {
|
|
294
|
+
const first = lines[Symbol.iterator]().next();
|
|
295
|
+
|
|
296
|
+
if (first.done || first.value !== '#nex2') {
|
|
297
|
+
throw new Error('Tried to import a file that is not a NEX v2 file.');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (transaction) {
|
|
301
|
+
await this.internalTransaction('nymph-import');
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
let guid: string | null = null;
|
|
305
|
+
let sdata: SerializedEntityData = {};
|
|
306
|
+
let tags: string[] = [];
|
|
307
|
+
let etype = '__undefined';
|
|
308
|
+
for (let line of lines) {
|
|
309
|
+
if (line.match(/^\s*#/)) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const entityMatch = line.match(
|
|
313
|
+
/^\s*{([0-9A-Fa-f]+)}<([-\w_]+)>\[([^\]]*)\]\s*$/,
|
|
314
|
+
);
|
|
315
|
+
const propMatch = line.match(/^\s*([^=]+)\s*=\s*(.*\S)\s*$/);
|
|
316
|
+
const uidMatch = line.match(/^\s*<([^>]+)>\[(\d+)\]\s*$/);
|
|
317
|
+
if (uidMatch) {
|
|
318
|
+
// Add the UID.
|
|
319
|
+
await this.importUID({
|
|
320
|
+
name: uidMatch[1],
|
|
321
|
+
value: Number(uidMatch[2]),
|
|
322
|
+
});
|
|
323
|
+
} else if (entityMatch) {
|
|
324
|
+
// Save the current entity.
|
|
325
|
+
if (guid) {
|
|
326
|
+
const cdate = Number(JSON.parse(sdata.cdate));
|
|
327
|
+
delete sdata.cdate;
|
|
328
|
+
const mdate = Number(JSON.parse(sdata.mdate));
|
|
329
|
+
delete sdata.mdate;
|
|
330
|
+
try {
|
|
331
|
+
await this.importEntity({
|
|
332
|
+
guid,
|
|
333
|
+
cdate,
|
|
334
|
+
mdate,
|
|
335
|
+
tags,
|
|
336
|
+
sdata,
|
|
337
|
+
etype,
|
|
338
|
+
});
|
|
339
|
+
} catch (e: any) {
|
|
340
|
+
if (
|
|
341
|
+
!(e instanceof ClassNotAvailableError) ||
|
|
342
|
+
!options?.ignoreUnknownETypes
|
|
343
|
+
) {
|
|
344
|
+
throw e;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
guid = null;
|
|
348
|
+
tags = [];
|
|
349
|
+
sdata = {};
|
|
350
|
+
etype = '__undefined';
|
|
351
|
+
}
|
|
352
|
+
// Record the new entity's info.
|
|
353
|
+
guid = entityMatch[1];
|
|
354
|
+
etype = entityMatch[2];
|
|
355
|
+
tags = entityMatch[3].split(',');
|
|
356
|
+
} else if (propMatch) {
|
|
357
|
+
// Add the variable to the new entity.
|
|
358
|
+
if (guid) {
|
|
359
|
+
sdata[propMatch[1]] = propMatch[2];
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// Clear the entity cache.
|
|
363
|
+
this.entityCache = {};
|
|
364
|
+
}
|
|
365
|
+
// Save the last entity.
|
|
366
|
+
if (guid) {
|
|
367
|
+
const cdate = Number(JSON.parse(sdata.cdate));
|
|
368
|
+
delete sdata.cdate;
|
|
369
|
+
const mdate = Number(JSON.parse(sdata.mdate));
|
|
370
|
+
delete sdata.mdate;
|
|
371
|
+
try {
|
|
372
|
+
await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
|
|
373
|
+
} catch (e: any) {
|
|
374
|
+
if (
|
|
375
|
+
!(e instanceof ClassNotAvailableError) ||
|
|
376
|
+
!options?.ignoreUnknownETypes
|
|
377
|
+
) {
|
|
378
|
+
throw e;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (transaction) {
|
|
383
|
+
await this.commit('nymph-import');
|
|
384
|
+
}
|
|
385
|
+
} catch (e: any) {
|
|
386
|
+
await this.rollback('nymph-import');
|
|
387
|
+
throw e;
|
|
388
|
+
}
|
|
221
389
|
return true;
|
|
222
390
|
}
|
|
223
391
|
|
|
224
|
-
|
|
392
|
+
public async importData(text: string, transaction?: boolean) {
|
|
393
|
+
return await this.importDataIterator(
|
|
394
|
+
text.split('\n'),
|
|
395
|
+
undefined,
|
|
396
|
+
transaction,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
public async import(
|
|
225
401
|
filename: string,
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
402
|
+
options?: { ignoreUnknownETypes?: boolean },
|
|
403
|
+
transaction?: boolean,
|
|
235
404
|
) {
|
|
236
405
|
let rl: ReadLines;
|
|
237
406
|
try {
|
|
@@ -239,106 +408,63 @@ export default abstract class NymphDriver {
|
|
|
239
408
|
} catch (e: any) {
|
|
240
409
|
throw new InvalidParametersError('Provided filename is unreadable.');
|
|
241
410
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
await startTransactionCallback();
|
|
249
|
-
}
|
|
250
|
-
while ((line = rl.next())) {
|
|
251
|
-
const text = line.toString('utf8');
|
|
252
|
-
if (text.match(/^\s*#/)) {
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
const entityMatch = text.match(
|
|
256
|
-
/^\s*{([0-9A-Fa-f]+)}<([-\w_]+)>\[([^\]]*)\]\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';
|
|
271
|
-
}
|
|
272
|
-
// Record the new entity's info.
|
|
273
|
-
guid = entityMatch[1];
|
|
274
|
-
etype = entityMatch[2];
|
|
275
|
-
tags = entityMatch[3].split(',');
|
|
276
|
-
} else if (propMatch) {
|
|
277
|
-
// Add the variable to the new entity.
|
|
278
|
-
if (guid) {
|
|
279
|
-
sdata[propMatch[1]] = propMatch[2];
|
|
411
|
+
|
|
412
|
+
const lines = {
|
|
413
|
+
*[Symbol.iterator]() {
|
|
414
|
+
let line: null | Buffer;
|
|
415
|
+
while ((line = rl.next())) {
|
|
416
|
+
yield line.toString('utf8');
|
|
280
417
|
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
// Save the last entity.
|
|
286
|
-
if (guid) {
|
|
287
|
-
await saveEntityCallback(guid, tags, sdata, etype);
|
|
288
|
-
}
|
|
289
|
-
if (commitTransactionCallback) {
|
|
290
|
-
await commitTransactionCallback();
|
|
291
|
-
}
|
|
292
|
-
return true;
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
return await this.importDataIterator(lines, options, transaction);
|
|
293
422
|
}
|
|
294
423
|
|
|
295
424
|
public checkData(
|
|
425
|
+
etype: string,
|
|
296
426
|
data: EntityData,
|
|
297
427
|
sdata: SerializedEntityData,
|
|
298
428
|
selectors: Selector[],
|
|
299
429
|
guid: string | null = null,
|
|
300
|
-
tags: string[] | null = null
|
|
430
|
+
tags: string[] | null = null,
|
|
301
431
|
) {
|
|
302
|
-
|
|
432
|
+
try {
|
|
433
|
+
const EntityClass = this.nymph.getEntityClassByEtype(etype);
|
|
434
|
+
const formattedSelectors = this.formatSelectors(selectors).selectors;
|
|
303
435
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
436
|
+
for (const curSelector of formattedSelectors) {
|
|
437
|
+
const type = curSelector.type;
|
|
438
|
+
const typeIsNot = type === '!&' || type === '!|';
|
|
439
|
+
const typeIsOr = type === '|' || type === '!|';
|
|
440
|
+
let pass = !typeIsOr;
|
|
441
|
+
for (const k in curSelector) {
|
|
442
|
+
const key = k as keyof Selector;
|
|
443
|
+
const value = curSelector[key];
|
|
312
444
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
445
|
+
if (key === 'type' || value == null) {
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
316
448
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
xor(
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
throw new Error("Can't use checkData on qref clauses.");
|
|
449
|
+
const clauseNot = key.substring(0, 1) === '!';
|
|
450
|
+
if (key === 'selector' || key === '!selector') {
|
|
451
|
+
const tmpArr = (
|
|
452
|
+
Array.isArray(value) ? value : [value]
|
|
453
|
+
) as Selector[];
|
|
454
|
+
pass = xor(
|
|
455
|
+
this.checkData(etype, data, sdata, tmpArr, guid, tags),
|
|
456
|
+
xor(typeIsNot, clauseNot),
|
|
457
|
+
);
|
|
327
458
|
} else {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if
|
|
332
|
-
|
|
333
|
-
|
|
459
|
+
if (key === 'qref' || key === '!qref') {
|
|
460
|
+
throw new Error("Can't use checkData on qref clauses.");
|
|
461
|
+
} else {
|
|
462
|
+
// Check if it doesn't pass any for &, check if it passes any for |.
|
|
463
|
+
for (const curValue of value) {
|
|
464
|
+
if (curValue == null) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
334
467
|
|
|
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
468
|
const propName = (curValue as string[])[0];
|
|
343
469
|
|
|
344
470
|
// Unserialize the data for this variable.
|
|
@@ -356,7 +482,7 @@ export default abstract class NymphDriver {
|
|
|
356
482
|
case '!tag':
|
|
357
483
|
pass = xor(
|
|
358
484
|
(tags ?? []).indexOf(propName) !== -1,
|
|
359
|
-
xor(typeIsNot, clauseNot)
|
|
485
|
+
xor(typeIsNot, clauseNot),
|
|
360
486
|
);
|
|
361
487
|
break;
|
|
362
488
|
case 'defined':
|
|
@@ -367,7 +493,7 @@ export default abstract class NymphDriver {
|
|
|
367
493
|
case '!truthy':
|
|
368
494
|
pass = xor(
|
|
369
495
|
propName in data && data[propName],
|
|
370
|
-
xor(typeIsNot, clauseNot)
|
|
496
|
+
xor(typeIsNot, clauseNot),
|
|
371
497
|
);
|
|
372
498
|
break;
|
|
373
499
|
case 'ref':
|
|
@@ -377,9 +503,9 @@ export default abstract class NymphDriver {
|
|
|
377
503
|
propName in data &&
|
|
378
504
|
this.entityReferenceSearch(
|
|
379
505
|
data[propName],
|
|
380
|
-
testRefValue
|
|
506
|
+
testRefValue,
|
|
381
507
|
),
|
|
382
|
-
xor(typeIsNot, clauseNot)
|
|
508
|
+
xor(typeIsNot, clauseNot),
|
|
383
509
|
);
|
|
384
510
|
break;
|
|
385
511
|
case 'equal':
|
|
@@ -391,7 +517,7 @@ export default abstract class NymphDriver {
|
|
|
391
517
|
propName in data &&
|
|
392
518
|
JSON.stringify(data[propName]) ===
|
|
393
519
|
JSON.stringify(testEqualValue),
|
|
394
|
-
xor(typeIsNot, clauseNot)
|
|
520
|
+
xor(typeIsNot, clauseNot),
|
|
395
521
|
);
|
|
396
522
|
break;
|
|
397
523
|
case 'contain':
|
|
@@ -402,9 +528,9 @@ export default abstract class NymphDriver {
|
|
|
402
528
|
pass = xor(
|
|
403
529
|
propName in data &&
|
|
404
530
|
JSON.stringify(data[propName]).indexOf(
|
|
405
|
-
JSON.stringify(testContainValue)
|
|
531
|
+
JSON.stringify(testContainValue),
|
|
406
532
|
) !== -1,
|
|
407
|
-
xor(typeIsNot, clauseNot)
|
|
533
|
+
xor(typeIsNot, clauseNot),
|
|
408
534
|
);
|
|
409
535
|
break;
|
|
410
536
|
case 'like':
|
|
@@ -419,9 +545,27 @@ export default abstract class NymphDriver {
|
|
|
419
545
|
escapeRegExp(testLikeValue)
|
|
420
546
|
.replace('%', () => '.*')
|
|
421
547
|
.replace('_', () => '.') +
|
|
422
|
-
'$'
|
|
548
|
+
'$',
|
|
423
549
|
).test(data[propName]),
|
|
424
|
-
xor(typeIsNot, clauseNot)
|
|
550
|
+
xor(typeIsNot, clauseNot),
|
|
551
|
+
);
|
|
552
|
+
break;
|
|
553
|
+
case 'ilike':
|
|
554
|
+
case '!ilike':
|
|
555
|
+
const testILikeValue = (
|
|
556
|
+
curValue as [string, string]
|
|
557
|
+
)[1] as string;
|
|
558
|
+
pass = xor(
|
|
559
|
+
propName in data &&
|
|
560
|
+
new RegExp(
|
|
561
|
+
'^' +
|
|
562
|
+
escapeRegExp(testILikeValue)
|
|
563
|
+
.replace('%', () => '.*')
|
|
564
|
+
.replace('_', () => '.') +
|
|
565
|
+
'$',
|
|
566
|
+
'i',
|
|
567
|
+
).test(data[propName]),
|
|
568
|
+
xor(typeIsNot, clauseNot),
|
|
425
569
|
);
|
|
426
570
|
break;
|
|
427
571
|
case 'match':
|
|
@@ -433,7 +577,7 @@ export default abstract class NymphDriver {
|
|
|
433
577
|
pass = xor(
|
|
434
578
|
propName in data &&
|
|
435
579
|
this.posixRegexMatch(testMatchValue, data[propName]),
|
|
436
|
-
xor(typeIsNot, clauseNot)
|
|
580
|
+
xor(typeIsNot, clauseNot),
|
|
437
581
|
);
|
|
438
582
|
break;
|
|
439
583
|
case 'imatch':
|
|
@@ -447,9 +591,28 @@ export default abstract class NymphDriver {
|
|
|
447
591
|
this.posixRegexMatch(
|
|
448
592
|
testIMatchValue,
|
|
449
593
|
data[propName],
|
|
450
|
-
true
|
|
594
|
+
true,
|
|
451
595
|
),
|
|
452
|
-
xor(typeIsNot, clauseNot)
|
|
596
|
+
xor(typeIsNot, clauseNot),
|
|
597
|
+
);
|
|
598
|
+
break;
|
|
599
|
+
case 'search':
|
|
600
|
+
case '!search':
|
|
601
|
+
const testSearchValue = (
|
|
602
|
+
curValue as [string, string]
|
|
603
|
+
)[1] as string;
|
|
604
|
+
const tokenString =
|
|
605
|
+
propName in data
|
|
606
|
+
? (EntityClass.getFTSText(propName, data[propName]) ??
|
|
607
|
+
'')
|
|
608
|
+
: '';
|
|
609
|
+
pass = xor(
|
|
610
|
+
propName in data &&
|
|
611
|
+
this.tokenizer.searchString(
|
|
612
|
+
testSearchValue,
|
|
613
|
+
tokenString,
|
|
614
|
+
),
|
|
615
|
+
xor(typeIsNot, clauseNot),
|
|
453
616
|
);
|
|
454
617
|
break;
|
|
455
618
|
case 'gt':
|
|
@@ -459,7 +622,7 @@ export default abstract class NymphDriver {
|
|
|
459
622
|
)[1] as number;
|
|
460
623
|
pass = xor(
|
|
461
624
|
propName in data && data[propName] > testGTValue,
|
|
462
|
-
xor(typeIsNot, clauseNot)
|
|
625
|
+
xor(typeIsNot, clauseNot),
|
|
463
626
|
);
|
|
464
627
|
break;
|
|
465
628
|
case 'gte':
|
|
@@ -469,7 +632,7 @@ export default abstract class NymphDriver {
|
|
|
469
632
|
)[1] as number;
|
|
470
633
|
pass = xor(
|
|
471
634
|
propName in data && data[propName] >= testGTEValue,
|
|
472
|
-
xor(typeIsNot, clauseNot)
|
|
635
|
+
xor(typeIsNot, clauseNot),
|
|
473
636
|
);
|
|
474
637
|
break;
|
|
475
638
|
case 'lt':
|
|
@@ -479,7 +642,7 @@ export default abstract class NymphDriver {
|
|
|
479
642
|
)[1] as number;
|
|
480
643
|
pass = xor(
|
|
481
644
|
propName in data && data[propName] < testLTValue,
|
|
482
|
-
xor(typeIsNot, clauseNot)
|
|
645
|
+
xor(typeIsNot, clauseNot),
|
|
483
646
|
);
|
|
484
647
|
break;
|
|
485
648
|
case 'lte':
|
|
@@ -489,26 +652,33 @@ export default abstract class NymphDriver {
|
|
|
489
652
|
)[1] as number;
|
|
490
653
|
pass = xor(
|
|
491
654
|
propName in data && data[propName] <= testLTEValue,
|
|
492
|
-
xor(typeIsNot, clauseNot)
|
|
655
|
+
xor(typeIsNot, clauseNot),
|
|
493
656
|
);
|
|
494
657
|
break;
|
|
495
658
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
659
|
+
|
|
660
|
+
if (!xor(typeIsOr, pass)) {
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
499
663
|
}
|
|
500
664
|
}
|
|
501
665
|
}
|
|
666
|
+
if (!xor(typeIsOr, pass)) {
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
502
669
|
}
|
|
503
|
-
if (!
|
|
504
|
-
|
|
670
|
+
if (!pass) {
|
|
671
|
+
return false;
|
|
505
672
|
}
|
|
506
673
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
674
|
+
return true;
|
|
675
|
+
} catch (e: any) {
|
|
676
|
+
this.nymph.config.debugError(
|
|
677
|
+
'nymph',
|
|
678
|
+
`Failed to check entity data: ${e}`,
|
|
679
|
+
);
|
|
680
|
+
throw e;
|
|
510
681
|
}
|
|
511
|
-
return true;
|
|
512
682
|
}
|
|
513
683
|
|
|
514
684
|
/**
|
|
@@ -547,7 +717,7 @@ export default abstract class NymphDriver {
|
|
|
547
717
|
| EntityInterface
|
|
548
718
|
| EntityReference
|
|
549
719
|
| string
|
|
550
|
-
| (EntityInterface | EntityReference | string)[]
|
|
720
|
+
| (EntityInterface | EntityReference | string)[],
|
|
551
721
|
) {
|
|
552
722
|
// Get the GUID, if the passed $entity is an object.
|
|
553
723
|
const guids: string[] = [];
|
|
@@ -577,8 +747,19 @@ export default abstract class NymphDriver {
|
|
|
577
747
|
) {
|
|
578
748
|
return true;
|
|
579
749
|
}
|
|
750
|
+
if (
|
|
751
|
+
value instanceof Entity &&
|
|
752
|
+
value.guid != null &&
|
|
753
|
+
guids.indexOf(value.guid) !== -1
|
|
754
|
+
) {
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
580
757
|
// Search through arrays and objects looking for the reference.
|
|
581
|
-
if (
|
|
758
|
+
if (
|
|
759
|
+
value != null &&
|
|
760
|
+
(Array.isArray(value) ||
|
|
761
|
+
(typeof value === 'object' && !(value instanceof Entity)))
|
|
762
|
+
) {
|
|
582
763
|
for (const key in value) {
|
|
583
764
|
if (this.entityReferenceSearch(value[key], guids)) {
|
|
584
765
|
return true;
|
|
@@ -588,9 +769,222 @@ export default abstract class NymphDriver {
|
|
|
588
769
|
return false;
|
|
589
770
|
}
|
|
590
771
|
|
|
772
|
+
protected removeAndReturnACValues(
|
|
773
|
+
etype: string,
|
|
774
|
+
data: EntityData,
|
|
775
|
+
sdata: SerializedEntityData,
|
|
776
|
+
) {
|
|
777
|
+
let user: string | null = null;
|
|
778
|
+
let group: string | null = null;
|
|
779
|
+
let acUser: number | null = null;
|
|
780
|
+
let acGroup: number | null = null;
|
|
781
|
+
let acOther: number | null = null;
|
|
782
|
+
let acRead: string[] | null = null;
|
|
783
|
+
let acWrite: string[] | null = null;
|
|
784
|
+
let acFull: string[] | null = null;
|
|
785
|
+
|
|
786
|
+
if (this.nymph.tilmeld != null) {
|
|
787
|
+
// Since Tilmeld is installed, we need to fill the AC vars.
|
|
788
|
+
const userValue =
|
|
789
|
+
'user' in sdata
|
|
790
|
+
? JSON.parse(sdata.user)
|
|
791
|
+
: 'user' in data
|
|
792
|
+
? data.user
|
|
793
|
+
: null;
|
|
794
|
+
if (
|
|
795
|
+
Array.isArray(userValue) &&
|
|
796
|
+
userValue[0] === 'nymph_entity_reference' &&
|
|
797
|
+
userValue[2] === 'User' &&
|
|
798
|
+
typeof userValue[1] === 'string' &&
|
|
799
|
+
userValue[1].match(/^[0-9a-f]{24}$/i)
|
|
800
|
+
) {
|
|
801
|
+
user = userValue[1];
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const groupValue =
|
|
805
|
+
'group' in sdata
|
|
806
|
+
? JSON.parse(sdata.group)
|
|
807
|
+
: 'group' in data
|
|
808
|
+
? data.group
|
|
809
|
+
: null;
|
|
810
|
+
if (
|
|
811
|
+
Array.isArray(groupValue) &&
|
|
812
|
+
groupValue[0] === 'nymph_entity_reference' &&
|
|
813
|
+
groupValue[2] === 'Group' &&
|
|
814
|
+
typeof groupValue[1] === 'string' &&
|
|
815
|
+
groupValue[1].match(/^[0-9a-f]{24}$/i)
|
|
816
|
+
) {
|
|
817
|
+
group = groupValue[1];
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (etype !== 'tilmeld_user' && etype !== 'tilmeld_group') {
|
|
821
|
+
const acUserValue =
|
|
822
|
+
'acUser' in sdata
|
|
823
|
+
? JSON.parse(sdata.acUser)
|
|
824
|
+
: 'acUser' in data
|
|
825
|
+
? data.acUser
|
|
826
|
+
: null;
|
|
827
|
+
if (
|
|
828
|
+
typeof acUserValue === 'number' &&
|
|
829
|
+
!isNaN(acUserValue) &&
|
|
830
|
+
acUserValue >= 0
|
|
831
|
+
) {
|
|
832
|
+
acUser = acUserValue;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const acGroupValue =
|
|
836
|
+
'acGroup' in sdata
|
|
837
|
+
? JSON.parse(sdata.acGroup)
|
|
838
|
+
: 'acGroup' in data
|
|
839
|
+
? data.acGroup
|
|
840
|
+
: null;
|
|
841
|
+
if (
|
|
842
|
+
typeof acGroupValue === 'number' &&
|
|
843
|
+
!isNaN(acGroupValue) &&
|
|
844
|
+
acGroupValue >= 0
|
|
845
|
+
) {
|
|
846
|
+
acGroup = acGroupValue;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const acOtherValue =
|
|
850
|
+
'acOther' in sdata
|
|
851
|
+
? JSON.parse(sdata.acOther)
|
|
852
|
+
: 'acOther' in data
|
|
853
|
+
? data.acOther
|
|
854
|
+
: null;
|
|
855
|
+
if (
|
|
856
|
+
typeof acOtherValue === 'number' &&
|
|
857
|
+
!isNaN(acOtherValue) &&
|
|
858
|
+
acOtherValue >= 0
|
|
859
|
+
) {
|
|
860
|
+
acOther = acOtherValue;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const acReadValue =
|
|
864
|
+
'acRead' in sdata
|
|
865
|
+
? JSON.parse(sdata.acRead)
|
|
866
|
+
: 'acRead' in data
|
|
867
|
+
? data.acRead
|
|
868
|
+
: null;
|
|
869
|
+
if (Array.isArray(acReadValue)) {
|
|
870
|
+
const acReadArray = [];
|
|
871
|
+
for (let acReadEntry of acReadValue) {
|
|
872
|
+
if (
|
|
873
|
+
typeof acReadEntry === 'string' &&
|
|
874
|
+
acReadEntry.match(/^[0-9a-f]{24}$/i)
|
|
875
|
+
) {
|
|
876
|
+
acReadArray.push(acReadEntry);
|
|
877
|
+
} else if (
|
|
878
|
+
Array.isArray(acReadEntry) &&
|
|
879
|
+
acReadEntry[0] === 'nymph_entity_reference' &&
|
|
880
|
+
(acReadEntry[2] === 'User' || acReadEntry[2] === 'Group') &&
|
|
881
|
+
typeof acReadEntry[1] === 'string' &&
|
|
882
|
+
acReadEntry[1].match(/^[0-9a-f]{24}$/i)
|
|
883
|
+
) {
|
|
884
|
+
acReadArray.push(acReadEntry[1]);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
if (acReadArray.length) {
|
|
888
|
+
acRead = acReadArray;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const acWriteValue =
|
|
893
|
+
'acWrite' in sdata
|
|
894
|
+
? JSON.parse(sdata.acWrite)
|
|
895
|
+
: 'acWrite' in data
|
|
896
|
+
? data.acWrite
|
|
897
|
+
: null;
|
|
898
|
+
if (Array.isArray(acWriteValue)) {
|
|
899
|
+
const acWriteArray = [];
|
|
900
|
+
for (let acWriteEntry of acWriteValue) {
|
|
901
|
+
if (
|
|
902
|
+
typeof acWriteEntry === 'string' &&
|
|
903
|
+
acWriteEntry.match(/^[0-9a-f]{24}$/i)
|
|
904
|
+
) {
|
|
905
|
+
acWriteArray.push(acWriteEntry);
|
|
906
|
+
} else if (
|
|
907
|
+
Array.isArray(acWriteEntry) &&
|
|
908
|
+
acWriteEntry[0] === 'nymph_entity_reference' &&
|
|
909
|
+
(acWriteEntry[2] === 'User' || acWriteEntry[2] === 'Group') &&
|
|
910
|
+
typeof acWriteEntry[1] === 'string' &&
|
|
911
|
+
acWriteEntry[1].match(/^[0-9a-f]{24}$/i)
|
|
912
|
+
) {
|
|
913
|
+
acWriteArray.push(acWriteEntry[1]);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (acWriteArray.length) {
|
|
917
|
+
acWrite = acWriteArray;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const acFullValue =
|
|
922
|
+
'acFull' in sdata
|
|
923
|
+
? JSON.parse(sdata.acFull)
|
|
924
|
+
: 'acFull' in data
|
|
925
|
+
? data.acFull
|
|
926
|
+
: null;
|
|
927
|
+
if (Array.isArray(acFullValue)) {
|
|
928
|
+
const acFullArray = [];
|
|
929
|
+
for (let acFullEntry of acFullValue) {
|
|
930
|
+
if (
|
|
931
|
+
typeof acFullEntry === 'string' &&
|
|
932
|
+
acFullEntry.match(/^[0-9a-f]{24}$/i)
|
|
933
|
+
) {
|
|
934
|
+
acFullArray.push(acFullEntry);
|
|
935
|
+
} else if (
|
|
936
|
+
Array.isArray(acFullEntry) &&
|
|
937
|
+
acFullEntry[0] === 'nymph_entity_reference' &&
|
|
938
|
+
(acFullEntry[2] === 'User' || acFullEntry[2] === 'Group') &&
|
|
939
|
+
typeof acFullEntry[1] === 'string' &&
|
|
940
|
+
acFullEntry[1].match(/^[0-9a-f]{24}$/i)
|
|
941
|
+
) {
|
|
942
|
+
acFullArray.push(acFullEntry[1]);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (acFullArray.length) {
|
|
946
|
+
acFull = acFullArray;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Delete all of the data, so it doesn't go into the database as rows in
|
|
952
|
+
// the data tables.
|
|
953
|
+
delete sdata.user;
|
|
954
|
+
delete data.user;
|
|
955
|
+
delete sdata.group;
|
|
956
|
+
delete data.group;
|
|
957
|
+
delete sdata.acUser;
|
|
958
|
+
delete data.acUser;
|
|
959
|
+
delete sdata.acGroup;
|
|
960
|
+
delete data.acGroup;
|
|
961
|
+
delete sdata.acOther;
|
|
962
|
+
delete data.acOther;
|
|
963
|
+
delete sdata.acRead;
|
|
964
|
+
delete data.acRead;
|
|
965
|
+
delete sdata.acWrite;
|
|
966
|
+
delete data.acWrite;
|
|
967
|
+
delete sdata.acFull;
|
|
968
|
+
delete data.acFull;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
return {
|
|
972
|
+
data,
|
|
973
|
+
sdata,
|
|
974
|
+
user,
|
|
975
|
+
group,
|
|
976
|
+
acUser,
|
|
977
|
+
acGroup,
|
|
978
|
+
acOther,
|
|
979
|
+
acRead,
|
|
980
|
+
acWrite,
|
|
981
|
+
acFull,
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
|
|
591
985
|
public formatSelectors(
|
|
592
986
|
selectors: Selector[],
|
|
593
|
-
options: Options = {}
|
|
987
|
+
options: Options = {},
|
|
594
988
|
): {
|
|
595
989
|
selectors: FormattedSelector[];
|
|
596
990
|
qrefs: [Options, ...FormattedSelector[]][];
|
|
@@ -625,10 +1019,9 @@ export default abstract class NymphDriver {
|
|
|
625
1019
|
for (let i = 0; i < tmpArr.length; i++) {
|
|
626
1020
|
const name = tmpArr[i][0];
|
|
627
1021
|
const [qrefOptions, ...qrefSelectors] = tmpArr[i][1];
|
|
628
|
-
const QrefEntityClass =
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
: qrefOptions.class ?? this.nymph.getEntityClass('Entity');
|
|
1022
|
+
const QrefEntityClass = qrefOptions.class
|
|
1023
|
+
? this.nymph.getEntityClass(qrefOptions.class)
|
|
1024
|
+
: this.nymph.getEntityClass('Entity');
|
|
632
1025
|
const newOptions = {
|
|
633
1026
|
...qrefOptions,
|
|
634
1027
|
class: QrefEntityClass,
|
|
@@ -637,7 +1030,7 @@ export default abstract class NymphDriver {
|
|
|
637
1030
|
const newSelectors = this.formatSelectors(qrefSelectors, options);
|
|
638
1031
|
qrefs.push(
|
|
639
1032
|
[newOptions, ...newSelectors.selectors],
|
|
640
|
-
...newSelectors.qrefs
|
|
1033
|
+
...newSelectors.qrefs,
|
|
641
1034
|
);
|
|
642
1035
|
formatArr[i] = [name, [newOptions, ...newSelectors.selectors]];
|
|
643
1036
|
}
|
|
@@ -670,7 +1063,7 @@ export default abstract class NymphDriver {
|
|
|
670
1063
|
curValue[1] == null &&
|
|
671
1064
|
typeof curValue[2] === 'string'
|
|
672
1065
|
) {
|
|
673
|
-
return [curValue[0], strtotime(curValue[2]) * 1000];
|
|
1066
|
+
return [curValue[0], (strtotime(curValue[2]) || 0) * 1000];
|
|
674
1067
|
}
|
|
675
1068
|
return curValue;
|
|
676
1069
|
});
|
|
@@ -685,12 +1078,12 @@ export default abstract class NymphDriver {
|
|
|
685
1078
|
|
|
686
1079
|
protected iterateSelectorsForQuery(
|
|
687
1080
|
selectors: FormattedSelector[],
|
|
688
|
-
callback: (
|
|
689
|
-
key: string
|
|
690
|
-
value: any
|
|
691
|
-
typeIsOr: boolean
|
|
692
|
-
typeIsNot: boolean
|
|
693
|
-
) => string
|
|
1081
|
+
callback: (data: {
|
|
1082
|
+
key: string;
|
|
1083
|
+
value: any;
|
|
1084
|
+
typeIsOr: boolean;
|
|
1085
|
+
typeIsNot: boolean;
|
|
1086
|
+
}) => string,
|
|
694
1087
|
) {
|
|
695
1088
|
const queryParts = [];
|
|
696
1089
|
for (const curSelector of selectors) {
|
|
@@ -703,7 +1096,7 @@ export default abstract class NymphDriver {
|
|
|
703
1096
|
if (key === 'type') {
|
|
704
1097
|
continue;
|
|
705
1098
|
}
|
|
706
|
-
let curQuery = callback(key, value, typeIsOr, typeIsNot);
|
|
1099
|
+
let curQuery = callback({ key, value, typeIsOr, typeIsNot });
|
|
707
1100
|
if (curQuery) {
|
|
708
1101
|
if (curSelectorQuery) {
|
|
709
1102
|
curSelectorQuery += typeIsOr ? ' OR ' : ' AND ';
|
|
@@ -719,71 +1112,14 @@ export default abstract class NymphDriver {
|
|
|
719
1112
|
return queryParts;
|
|
720
1113
|
}
|
|
721
1114
|
|
|
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>(
|
|
1115
|
+
protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
|
|
780
1116
|
options: Options<T> & { return: 'count' },
|
|
781
1117
|
selectors: Selector[],
|
|
782
|
-
performQueryCallback: (
|
|
783
|
-
options: Options<T
|
|
784
|
-
|
|
785
|
-
etype: string
|
|
786
|
-
) => {
|
|
1118
|
+
performQueryCallback: (data: {
|
|
1119
|
+
options: Options<T>;
|
|
1120
|
+
selectors: FormattedSelector[];
|
|
1121
|
+
etype: string;
|
|
1122
|
+
}) => {
|
|
787
1123
|
result: any;
|
|
788
1124
|
},
|
|
789
1125
|
rowFetchCallback: () => any,
|
|
@@ -798,16 +1134,16 @@ export default abstract class NymphDriver {
|
|
|
798
1134
|
getDataNameAndSValueCallback: (row: any) => {
|
|
799
1135
|
name: string;
|
|
800
1136
|
svalue: string;
|
|
801
|
-
}
|
|
1137
|
+
},
|
|
802
1138
|
): { result: any; process: () => number | Error };
|
|
803
|
-
protected
|
|
1139
|
+
protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
|
|
804
1140
|
options: Options<T> & { return: 'guid' },
|
|
805
1141
|
selectors: Selector[],
|
|
806
|
-
performQueryCallback: (
|
|
807
|
-
options: Options<T
|
|
808
|
-
|
|
809
|
-
etype: string
|
|
810
|
-
) => {
|
|
1142
|
+
performQueryCallback: (data: {
|
|
1143
|
+
options: Options<T>;
|
|
1144
|
+
selectors: FormattedSelector[];
|
|
1145
|
+
etype: string;
|
|
1146
|
+
}) => {
|
|
811
1147
|
result: any;
|
|
812
1148
|
},
|
|
813
1149
|
rowFetchCallback: () => any,
|
|
@@ -822,16 +1158,40 @@ export default abstract class NymphDriver {
|
|
|
822
1158
|
getDataNameAndSValueCallback: (row: any) => {
|
|
823
1159
|
name: string;
|
|
824
1160
|
svalue: string;
|
|
825
|
-
}
|
|
1161
|
+
},
|
|
826
1162
|
): { result: any; process: () => string[] | Error };
|
|
827
|
-
protected
|
|
1163
|
+
protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
|
|
1164
|
+
options: Options<T> & { return: 'object' },
|
|
1165
|
+
selectors: Selector[],
|
|
1166
|
+
performQueryCallback: (data: {
|
|
1167
|
+
options: Options<T>;
|
|
1168
|
+
selectors: FormattedSelector[];
|
|
1169
|
+
etype: string;
|
|
1170
|
+
}) => {
|
|
1171
|
+
result: any;
|
|
1172
|
+
},
|
|
1173
|
+
rowFetchCallback: () => any,
|
|
1174
|
+
freeResultCallback: () => void,
|
|
1175
|
+
getCountCallback: (row: any) => number,
|
|
1176
|
+
getGUIDCallback: (row: any) => string,
|
|
1177
|
+
getTagsAndDatesCallback: (row: any) => {
|
|
1178
|
+
tags: string[];
|
|
1179
|
+
cdate: number;
|
|
1180
|
+
mdate: number;
|
|
1181
|
+
},
|
|
1182
|
+
getDataNameAndSValueCallback: (row: any) => {
|
|
1183
|
+
name: string;
|
|
1184
|
+
svalue: string;
|
|
1185
|
+
},
|
|
1186
|
+
): { result: any; process: () => EntityObjectType<T>[] | Error };
|
|
1187
|
+
protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
|
|
828
1188
|
options: Options<T>,
|
|
829
1189
|
selectors: Selector[],
|
|
830
|
-
performQueryCallback: (
|
|
831
|
-
options: Options<T
|
|
832
|
-
|
|
833
|
-
etype: string
|
|
834
|
-
) => {
|
|
1190
|
+
performQueryCallback: (data: {
|
|
1191
|
+
options: Options<T>;
|
|
1192
|
+
selectors: FormattedSelector[];
|
|
1193
|
+
etype: string;
|
|
1194
|
+
}) => {
|
|
835
1195
|
result: any;
|
|
836
1196
|
},
|
|
837
1197
|
rowFetchCallback: () => any,
|
|
@@ -846,16 +1206,19 @@ export default abstract class NymphDriver {
|
|
|
846
1206
|
getDataNameAndSValueCallback: (row: any) => {
|
|
847
1207
|
name: string;
|
|
848
1208
|
svalue: string;
|
|
849
|
-
}
|
|
850
|
-
): {
|
|
851
|
-
|
|
1209
|
+
},
|
|
1210
|
+
): {
|
|
1211
|
+
result: any;
|
|
1212
|
+
process: () => EntityInstanceType<T>[] | Error;
|
|
1213
|
+
};
|
|
1214
|
+
protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
|
|
852
1215
|
options: Options<T>,
|
|
853
1216
|
selectors: Selector[],
|
|
854
|
-
performQueryCallback: (
|
|
855
|
-
options: Options<T
|
|
856
|
-
|
|
857
|
-
etype: string
|
|
858
|
-
) => {
|
|
1217
|
+
performQueryCallback: (data: {
|
|
1218
|
+
options: Options<T>;
|
|
1219
|
+
selectors: FormattedSelector[];
|
|
1220
|
+
etype: string;
|
|
1221
|
+
}) => {
|
|
859
1222
|
result: any;
|
|
860
1223
|
},
|
|
861
1224
|
rowFetchCallback: () => any,
|
|
@@ -866,14 +1229,27 @@ export default abstract class NymphDriver {
|
|
|
866
1229
|
tags: string[];
|
|
867
1230
|
cdate: number;
|
|
868
1231
|
mdate: number;
|
|
1232
|
+
user?: string;
|
|
1233
|
+
group?: string;
|
|
1234
|
+
acUser?: number;
|
|
1235
|
+
acGroup?: number;
|
|
1236
|
+
acOther?: number;
|
|
1237
|
+
acRead?: string[];
|
|
1238
|
+
acWrite?: string[];
|
|
1239
|
+
acFull?: string[];
|
|
869
1240
|
},
|
|
870
1241
|
getDataNameAndSValueCallback: (row: any) => {
|
|
871
1242
|
name: string;
|
|
872
1243
|
svalue: string;
|
|
873
|
-
}
|
|
1244
|
+
},
|
|
874
1245
|
): {
|
|
875
1246
|
result: any;
|
|
876
|
-
process: () =>
|
|
1247
|
+
process: () =>
|
|
1248
|
+
| EntityInstanceType<T>[]
|
|
1249
|
+
| EntityObjectType<T>[]
|
|
1250
|
+
| string[]
|
|
1251
|
+
| number
|
|
1252
|
+
| Error;
|
|
877
1253
|
} {
|
|
878
1254
|
if (!this.isConnected()) {
|
|
879
1255
|
throw new UnableToConnectError('not connected to DB');
|
|
@@ -886,21 +1262,29 @@ export default abstract class NymphDriver {
|
|
|
886
1262
|
['&', '!&', '|', '!|'].indexOf(selector.type) === -1
|
|
887
1263
|
) {
|
|
888
1264
|
throw new InvalidParametersError(
|
|
889
|
-
'Invalid query selector passed: ' + JSON.stringify(selector)
|
|
1265
|
+
'Invalid query selector passed: ' + JSON.stringify(selector),
|
|
890
1266
|
);
|
|
891
1267
|
}
|
|
892
1268
|
}
|
|
893
1269
|
|
|
894
|
-
let entities:
|
|
1270
|
+
let entities: EntityInstanceType<T>[] | EntityObjectType<T>[] | string[] =
|
|
1271
|
+
[];
|
|
895
1272
|
let count = 0;
|
|
896
1273
|
const EntityClass =
|
|
897
1274
|
options.class ?? (this.nymph.getEntityClass('Entity') as T);
|
|
898
1275
|
const etype = EntityClass.ETYPE;
|
|
899
1276
|
|
|
1277
|
+
if (options.source === 'client' && options.return === 'object') {
|
|
1278
|
+
throw new InvalidParametersError(
|
|
1279
|
+
'Object return type not allowed from client.',
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
900
1283
|
// Check if the requested entity is cached.
|
|
901
1284
|
if (
|
|
902
1285
|
this.nymph.config.cache &&
|
|
903
1286
|
!options.skipCache &&
|
|
1287
|
+
selectors.length &&
|
|
904
1288
|
'guid' in selectors[0] &&
|
|
905
1289
|
typeof selectors[0].guid === 'number'
|
|
906
1290
|
) {
|
|
@@ -912,33 +1296,88 @@ export default abstract class NymphDriver {
|
|
|
912
1296
|
(Object.keys(selectors[0]).length === 2 ||
|
|
913
1297
|
(Object.keys(selectors[0]).length === 3 && 'tag' in selectors[0]))
|
|
914
1298
|
) {
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
EntityClass.class,
|
|
918
|
-
!!options.skipAc
|
|
919
|
-
);
|
|
1299
|
+
const cacheEntity = this.pullCache(selectors[0]['guid']);
|
|
1300
|
+
|
|
920
1301
|
if (
|
|
921
|
-
|
|
922
|
-
|
|
1302
|
+
cacheEntity != null &&
|
|
1303
|
+
cacheEntity.guid != null &&
|
|
923
1304
|
(!('tag' in selectors[0]) ||
|
|
924
|
-
|
|
925
|
-
|
|
1305
|
+
!(
|
|
1306
|
+
Array.isArray(selectors[0].tag)
|
|
926
1307
|
? selectors[0].tag
|
|
927
|
-
: [selectors[0].tag]
|
|
928
|
-
))
|
|
1308
|
+
: [selectors[0].tag]
|
|
1309
|
+
).find((tag) => tag == null || !cacheEntity.tags.includes(tag)))
|
|
929
1310
|
) {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1311
|
+
// Return the value from the cache.
|
|
1312
|
+
const { guid, cdate, mdate, tags, data, sdata } = cacheEntity;
|
|
1313
|
+
|
|
1314
|
+
if (options.return === 'count') {
|
|
1315
|
+
return {
|
|
1316
|
+
result: Promise.resolve(null),
|
|
1317
|
+
process: () => 1,
|
|
1318
|
+
};
|
|
1319
|
+
} else if (options.return === 'guid') {
|
|
1320
|
+
return {
|
|
1321
|
+
result: Promise.resolve(null),
|
|
1322
|
+
process: () => [guid],
|
|
1323
|
+
};
|
|
1324
|
+
} else if (options.return === 'object') {
|
|
1325
|
+
return {
|
|
1326
|
+
result: Promise.resolve(null),
|
|
1327
|
+
process: () => [
|
|
1328
|
+
{
|
|
1329
|
+
guid,
|
|
1330
|
+
cdate,
|
|
1331
|
+
mdate,
|
|
1332
|
+
tags,
|
|
1333
|
+
...data,
|
|
1334
|
+
...Object.fromEntries(
|
|
1335
|
+
Object.entries(sdata).map(([name, value]) => [
|
|
1336
|
+
name,
|
|
1337
|
+
JSON.parse(value),
|
|
1338
|
+
]),
|
|
1339
|
+
),
|
|
1340
|
+
} as EntityObjectType<T>,
|
|
1341
|
+
],
|
|
1342
|
+
};
|
|
1343
|
+
} else {
|
|
1344
|
+
const entity = this.nymph
|
|
1345
|
+
.getEntityClass(EntityClass.class)
|
|
1346
|
+
.factorySync() as EntityInstanceType<T>;
|
|
1347
|
+
if (entity) {
|
|
1348
|
+
entity.$nymph = this.nymph;
|
|
1349
|
+
if (options.skipAc != null) {
|
|
1350
|
+
entity.$useSkipAc(!!options.skipAc);
|
|
1351
|
+
}
|
|
1352
|
+
entity.guid = guid;
|
|
1353
|
+
entity.cdate = cdate;
|
|
1354
|
+
entity.mdate = mdate;
|
|
1355
|
+
entity.tags = tags;
|
|
1356
|
+
entity.$putData(
|
|
1357
|
+
classNamesToEntityConstructors(
|
|
1358
|
+
this.nymph,
|
|
1359
|
+
referencesToEntities(
|
|
1360
|
+
data,
|
|
1361
|
+
this.nymph,
|
|
1362
|
+
!!options.skipAc,
|
|
1363
|
+
false,
|
|
1364
|
+
),
|
|
1365
|
+
false,
|
|
1366
|
+
),
|
|
1367
|
+
sdata,
|
|
1368
|
+
'server',
|
|
1369
|
+
);
|
|
1370
|
+
return {
|
|
1371
|
+
result: Promise.resolve(null),
|
|
1372
|
+
process: () => [entity],
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
return {
|
|
1377
|
+
result: Promise.resolve(null),
|
|
1378
|
+
process: () => [],
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
942
1381
|
}
|
|
943
1382
|
}
|
|
944
1383
|
}
|
|
@@ -946,15 +1385,16 @@ export default abstract class NymphDriver {
|
|
|
946
1385
|
try {
|
|
947
1386
|
const formattedSelectors = this.formatSelectors(selectors, options);
|
|
948
1387
|
this.nymph.runQueryCallbacks(options, formattedSelectors.selectors);
|
|
949
|
-
for (let
|
|
950
|
-
const [options, ...selectors] =
|
|
1388
|
+
for (let i = 0; i < formattedSelectors.qrefs.length; i++) {
|
|
1389
|
+
const [options, ...selectors] = formattedSelectors.qrefs[i];
|
|
951
1390
|
this.nymph.runQueryCallbacks(options, selectors);
|
|
1391
|
+
formattedSelectors.qrefs[i] = [options, ...selectors];
|
|
952
1392
|
}
|
|
953
|
-
const { result } = performQueryCallback(
|
|
1393
|
+
const { result } = performQueryCallback({
|
|
954
1394
|
options,
|
|
955
|
-
formattedSelectors.selectors,
|
|
956
|
-
etype
|
|
957
|
-
);
|
|
1395
|
+
selectors: formattedSelectors.selectors,
|
|
1396
|
+
etype,
|
|
1397
|
+
});
|
|
958
1398
|
|
|
959
1399
|
return {
|
|
960
1400
|
result,
|
|
@@ -994,28 +1434,117 @@ export default abstract class NymphDriver {
|
|
|
994
1434
|
// Make sure that $row is incremented :)
|
|
995
1435
|
row = rowFetchCallback();
|
|
996
1436
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1437
|
+
|
|
1438
|
+
if (this.nymph.tilmeld) {
|
|
1439
|
+
if ('user' in tagsAndDates && tagsAndDates.user != null) {
|
|
1440
|
+
data.user = [
|
|
1441
|
+
'nymph_entity_reference',
|
|
1442
|
+
tagsAndDates.user,
|
|
1443
|
+
'User',
|
|
1444
|
+
];
|
|
1445
|
+
delete sdata.user;
|
|
1446
|
+
}
|
|
1447
|
+
if ('group' in tagsAndDates && tagsAndDates.group != null) {
|
|
1448
|
+
data.group = [
|
|
1449
|
+
'nymph_entity_reference',
|
|
1450
|
+
tagsAndDates.group,
|
|
1451
|
+
'Group',
|
|
1452
|
+
];
|
|
1453
|
+
delete sdata.group;
|
|
1454
|
+
}
|
|
1455
|
+
if ('acUser' in tagsAndDates && tagsAndDates.acUser != null) {
|
|
1456
|
+
data.acUser = tagsAndDates.acUser;
|
|
1457
|
+
delete sdata.acUser;
|
|
1458
|
+
}
|
|
1459
|
+
if ('acGroup' in tagsAndDates && tagsAndDates.acGroup != null) {
|
|
1460
|
+
data.acGroup = tagsAndDates.acGroup;
|
|
1461
|
+
delete sdata.acGroup;
|
|
1462
|
+
}
|
|
1463
|
+
if ('acOther' in tagsAndDates && tagsAndDates.acOther != null) {
|
|
1464
|
+
data.acOther = tagsAndDates.acOther;
|
|
1465
|
+
delete sdata.acOther;
|
|
1466
|
+
}
|
|
1467
|
+
if ('acRead' in tagsAndDates && tagsAndDates.acRead != null) {
|
|
1468
|
+
data.acRead = tagsAndDates.acRead;
|
|
1469
|
+
delete sdata.acRead;
|
|
1470
|
+
}
|
|
1471
|
+
if ('acWrite' in tagsAndDates && tagsAndDates.acWrite != null) {
|
|
1472
|
+
data.acWrite = tagsAndDates.acWrite;
|
|
1473
|
+
delete sdata.acWrite;
|
|
1474
|
+
}
|
|
1475
|
+
if ('acFull' in tagsAndDates && tagsAndDates.acFull != null) {
|
|
1476
|
+
data.acFull = tagsAndDates.acFull;
|
|
1477
|
+
delete sdata.acFull;
|
|
1478
|
+
}
|
|
1003
1479
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1480
|
+
|
|
1481
|
+
if (options.return === 'object') {
|
|
1482
|
+
(entities as EntityObjectType<T>[]).push({
|
|
1483
|
+
guid,
|
|
1484
|
+
cdate,
|
|
1485
|
+
mdate,
|
|
1486
|
+
tags,
|
|
1487
|
+
...classNamesToEntityConstructors(
|
|
1488
|
+
this.nymph,
|
|
1489
|
+
referencesToEntities(
|
|
1490
|
+
data,
|
|
1491
|
+
this.nymph,
|
|
1492
|
+
!!options.skipAc,
|
|
1493
|
+
false,
|
|
1494
|
+
),
|
|
1495
|
+
false,
|
|
1496
|
+
),
|
|
1497
|
+
...Object.fromEntries(
|
|
1498
|
+
Object.entries(sdata).map(([name, value]) => [
|
|
1499
|
+
name,
|
|
1500
|
+
classNamesToEntityConstructors(
|
|
1501
|
+
this.nymph,
|
|
1502
|
+
referencesToEntities(
|
|
1503
|
+
JSON.parse(value),
|
|
1504
|
+
this.nymph,
|
|
1505
|
+
!!options.skipAc,
|
|
1506
|
+
false,
|
|
1507
|
+
),
|
|
1508
|
+
false,
|
|
1509
|
+
),
|
|
1510
|
+
]),
|
|
1511
|
+
),
|
|
1512
|
+
} as EntityObjectType<T>);
|
|
1513
|
+
} else {
|
|
1514
|
+
const entity =
|
|
1515
|
+
EntityClass.factorySync() as EntityInstanceType<T>;
|
|
1516
|
+
entity.$nymph = this.nymph;
|
|
1517
|
+
if (options.skipAc != null) {
|
|
1518
|
+
entity.$useSkipAc(!!options.skipAc);
|
|
1519
|
+
}
|
|
1520
|
+
entity.guid = guid;
|
|
1521
|
+
entity.cdate = cdate;
|
|
1522
|
+
entity.mdate = mdate;
|
|
1523
|
+
entity.tags = tags;
|
|
1524
|
+
this.putDataCounter++;
|
|
1525
|
+
if (this.putDataCounter == 100) {
|
|
1526
|
+
throw new Error('Infinite loop detected in Entity loading.');
|
|
1527
|
+
}
|
|
1528
|
+
entity.$putData(
|
|
1529
|
+
classNamesToEntityConstructors(
|
|
1530
|
+
this.nymph,
|
|
1531
|
+
referencesToEntities(
|
|
1532
|
+
data,
|
|
1533
|
+
this.nymph,
|
|
1534
|
+
!!options.skipAc,
|
|
1535
|
+
false,
|
|
1536
|
+
),
|
|
1537
|
+
false,
|
|
1538
|
+
),
|
|
1539
|
+
sdata,
|
|
1540
|
+
'server',
|
|
1541
|
+
);
|
|
1542
|
+
this.putDataCounter--;
|
|
1543
|
+
entities.push(entity);
|
|
1011
1544
|
}
|
|
1012
|
-
entity.$putData(data, sdata);
|
|
1013
|
-
this.putDataCounter--;
|
|
1014
1545
|
if (this.nymph.config.cache) {
|
|
1015
1546
|
this.pushCache(guid, cdate, mdate, tags, data, sdata);
|
|
1016
1547
|
}
|
|
1017
|
-
// @ts-ignore: ts doesn't know the return type here.
|
|
1018
|
-
entities.push(entity);
|
|
1019
1548
|
}
|
|
1020
1549
|
}
|
|
1021
1550
|
|
|
@@ -1036,125 +1565,166 @@ export default abstract class NymphDriver {
|
|
|
1036
1565
|
|
|
1037
1566
|
protected async saveEntityRowLike(
|
|
1038
1567
|
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
|
-
|
|
1568
|
+
saveNewEntityCallback: (data: {
|
|
1569
|
+
entity: EntityInterface;
|
|
1570
|
+
guid: string;
|
|
1571
|
+
tags: string[];
|
|
1572
|
+
data: EntityData;
|
|
1573
|
+
sdata: SerializedEntityData;
|
|
1574
|
+
uniques: string[];
|
|
1575
|
+
cdate: number;
|
|
1576
|
+
etype: string;
|
|
1577
|
+
}) => Promise<boolean>,
|
|
1578
|
+
saveExistingEntityCallback: (data: {
|
|
1579
|
+
entity: EntityInterface;
|
|
1580
|
+
guid: string;
|
|
1581
|
+
tags: string[];
|
|
1582
|
+
data: EntityData;
|
|
1583
|
+
sdata: SerializedEntityData;
|
|
1584
|
+
uniques: string[];
|
|
1585
|
+
mdate: number;
|
|
1586
|
+
etype: string;
|
|
1587
|
+
}) => Promise<boolean>,
|
|
1057
1588
|
startTransactionCallback: (() => Promise<void>) | null = null,
|
|
1058
1589
|
commitTransactionCallback:
|
|
1059
|
-
|
|
1060
|
-
| null = null
|
|
1590
|
+
((success: boolean) => Promise<boolean>) | null = null,
|
|
1061
1591
|
) {
|
|
1592
|
+
const originalGuid = entity.guid;
|
|
1593
|
+
const originalCdate = entity.cdate;
|
|
1594
|
+
const originalMdate = entity.mdate;
|
|
1062
1595
|
// Get a modified date.
|
|
1063
|
-
|
|
1596
|
+
let mdate = Date.now();
|
|
1597
|
+
if (entity.mdate != null) {
|
|
1598
|
+
if (this.nymph.config.updateMDate === false) {
|
|
1599
|
+
mdate = entity.mdate;
|
|
1600
|
+
} else if (typeof this.nymph.config.updateMDate === 'number') {
|
|
1601
|
+
mdate = entity.mdate + this.nymph.config.updateMDate;
|
|
1602
|
+
} else if (entity.mdate >= mdate) {
|
|
1603
|
+
// If we're saving too fast (within 1 millisecond), increment mdate to
|
|
1604
|
+
// make sure it doesn't conflict in the DB.
|
|
1605
|
+
mdate = entity.mdate + 1;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1064
1608
|
const tags = difference(entity.tags, ['']);
|
|
1065
|
-
const data =
|
|
1609
|
+
const data = entityConstructorsToClassNames(
|
|
1610
|
+
// Explicitly save references to entities that aren't saved yet to help
|
|
1611
|
+
// save circular references by saving with a guaranteed GUID.
|
|
1612
|
+
entitiesToReferences(entity.$getData(), false),
|
|
1613
|
+
);
|
|
1066
1614
|
const sdata = entity.$getSData();
|
|
1615
|
+
const uniques = await entity.$getUniques();
|
|
1067
1616
|
const EntityClass = entity.constructor as EntityConstructor;
|
|
1068
1617
|
const etype = EntityClass.ETYPE;
|
|
1069
1618
|
if (startTransactionCallback) {
|
|
1070
1619
|
await startTransactionCallback();
|
|
1071
1620
|
}
|
|
1072
1621
|
let success = false;
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1622
|
+
try {
|
|
1623
|
+
if (entity.guid == null) {
|
|
1624
|
+
const cdate = mdate;
|
|
1625
|
+
const newId = entity.$getGuaranteedGUID();
|
|
1626
|
+
success = await saveNewEntityCallback({
|
|
1627
|
+
entity,
|
|
1628
|
+
guid: newId,
|
|
1629
|
+
tags,
|
|
1630
|
+
data,
|
|
1631
|
+
sdata,
|
|
1632
|
+
uniques,
|
|
1633
|
+
cdate,
|
|
1634
|
+
etype,
|
|
1635
|
+
});
|
|
1636
|
+
if (success) {
|
|
1637
|
+
entity.guid = newId;
|
|
1638
|
+
entity.cdate = cdate;
|
|
1639
|
+
entity.mdate = mdate;
|
|
1640
|
+
}
|
|
1641
|
+
} else {
|
|
1642
|
+
// Removed any cached versions of this entity.
|
|
1643
|
+
if (this.nymph.config.cache) {
|
|
1644
|
+
this.cleanCache(entity.guid);
|
|
1645
|
+
}
|
|
1646
|
+
success = await saveExistingEntityCallback({
|
|
1647
|
+
entity,
|
|
1648
|
+
guid: entity.guid,
|
|
1649
|
+
tags,
|
|
1650
|
+
data,
|
|
1651
|
+
sdata,
|
|
1652
|
+
uniques,
|
|
1653
|
+
mdate,
|
|
1654
|
+
etype,
|
|
1655
|
+
});
|
|
1656
|
+
if (success) {
|
|
1657
|
+
entity.mdate = mdate;
|
|
1658
|
+
}
|
|
1089
1659
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
if (this.nymph.config.cache) {
|
|
1093
|
-
this.cleanCache(entity.guid);
|
|
1660
|
+
if (commitTransactionCallback) {
|
|
1661
|
+
success = await commitTransactionCallback(success);
|
|
1094
1662
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
entity.mdate = mdate;
|
|
1663
|
+
// Cache the entity.
|
|
1664
|
+
if (success && this.nymph.config.cache) {
|
|
1665
|
+
this.pushCache(
|
|
1666
|
+
entity.guid as string,
|
|
1667
|
+
entity.cdate as number,
|
|
1668
|
+
entity.mdate as number,
|
|
1669
|
+
entity.tags,
|
|
1670
|
+
data,
|
|
1671
|
+
sdata,
|
|
1672
|
+
);
|
|
1106
1673
|
}
|
|
1674
|
+
|
|
1675
|
+
if (!success) {
|
|
1676
|
+
entity.guid = originalGuid;
|
|
1677
|
+
entity.cdate = originalCdate;
|
|
1678
|
+
entity.mdate = originalMdate;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
return success;
|
|
1682
|
+
} catch (e: any) {
|
|
1683
|
+
if (commitTransactionCallback) {
|
|
1684
|
+
try {
|
|
1685
|
+
await commitTransactionCallback(false);
|
|
1686
|
+
} catch (e: any) {
|
|
1687
|
+
this.nymph.config.debugError(
|
|
1688
|
+
'nymph',
|
|
1689
|
+
`Rollback of entity save transaction failed, reason: ${e.message}`,
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
entity.guid = originalGuid;
|
|
1694
|
+
entity.cdate = originalCdate;
|
|
1695
|
+
entity.mdate = originalMdate;
|
|
1696
|
+
throw e;
|
|
1107
1697
|
}
|
|
1108
|
-
if (commitTransactionCallback) {
|
|
1109
|
-
success = await commitTransactionCallback(success);
|
|
1110
|
-
}
|
|
1111
|
-
// Cache the entity.
|
|
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
|
-
);
|
|
1121
|
-
}
|
|
1122
|
-
return success;
|
|
1123
1698
|
}
|
|
1124
1699
|
|
|
1125
1700
|
/**
|
|
1126
1701
|
* Pull an entity from the cache.
|
|
1127
1702
|
*
|
|
1128
1703
|
* @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.
|
|
1704
|
+
* @returns The entity's data or null if it's not cached.
|
|
1132
1705
|
*/
|
|
1133
|
-
protected pullCache
|
|
1134
|
-
guid: string
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1706
|
+
protected pullCache(guid: string): {
|
|
1707
|
+
guid: string;
|
|
1708
|
+
cdate: number;
|
|
1709
|
+
mdate: number;
|
|
1710
|
+
tags: string[];
|
|
1711
|
+
data: EntityData;
|
|
1712
|
+
sdata: SerializedEntityData;
|
|
1713
|
+
} | null {
|
|
1138
1714
|
// Increment the entity access count.
|
|
1139
1715
|
if (!(guid in this.entityCount)) {
|
|
1140
1716
|
this.entityCount[guid] = 0;
|
|
1141
1717
|
}
|
|
1142
1718
|
this.entityCount[guid]++;
|
|
1143
1719
|
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
|
-
}
|
|
1720
|
+
return {
|
|
1721
|
+
guid,
|
|
1722
|
+
cdate: this.entityCache[guid]['cdate'],
|
|
1723
|
+
mdate: this.entityCache[guid]['mdate'],
|
|
1724
|
+
tags: this.entityCache[guid]['tags'],
|
|
1725
|
+
data: this.entityCache[guid]['data'],
|
|
1726
|
+
sdata: this.entityCache[guid]['sdata'],
|
|
1727
|
+
};
|
|
1158
1728
|
}
|
|
1159
1729
|
return null;
|
|
1160
1730
|
}
|
|
@@ -1175,7 +1745,7 @@ export default abstract class NymphDriver {
|
|
|
1175
1745
|
mdate: number,
|
|
1176
1746
|
tags: string[],
|
|
1177
1747
|
data: EntityData,
|
|
1178
|
-
sdata: SerializedEntityData
|
|
1748
|
+
sdata: SerializedEntityData,
|
|
1179
1749
|
) {
|
|
1180
1750
|
if (guid == null) {
|
|
1181
1751
|
return;
|
|
@@ -1191,11 +1761,11 @@ export default abstract class NymphDriver {
|
|
|
1191
1761
|
}
|
|
1192
1762
|
// Cache the entity.
|
|
1193
1763
|
this.entityCache[guid] = {
|
|
1194
|
-
cdate
|
|
1195
|
-
mdate
|
|
1196
|
-
tags
|
|
1197
|
-
data
|
|
1198
|
-
sdata
|
|
1764
|
+
cdate,
|
|
1765
|
+
mdate,
|
|
1766
|
+
tags,
|
|
1767
|
+
data,
|
|
1768
|
+
sdata,
|
|
1199
1769
|
};
|
|
1200
1770
|
while (
|
|
1201
1771
|
this.nymph.config.cacheLimit &&
|
|
@@ -1204,7 +1774,7 @@ export default abstract class NymphDriver {
|
|
|
1204
1774
|
// Find which entity has been accessed the least.
|
|
1205
1775
|
const least =
|
|
1206
1776
|
Object.entries(this.entityCount)
|
|
1207
|
-
.sort(([
|
|
1777
|
+
.sort(([_guidA, countA], [_guidB, countB]) => countA - countB)
|
|
1208
1778
|
.pop()?.[0] ?? null;
|
|
1209
1779
|
if (least == null) {
|
|
1210
1780
|
// This should never happen.
|