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