@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.
Files changed (123) hide show
  1. package/CHANGELOG.md +644 -0
  2. package/README.md +34 -24
  3. package/dist/Entity.d.ts +304 -15
  4. package/dist/Entity.js +609 -154
  5. package/dist/Entity.js.map +1 -1
  6. package/dist/Entity.test.js +75 -29
  7. package/dist/Entity.test.js.map +1 -1
  8. package/dist/Entity.types.d.ts +275 -28
  9. package/dist/Entity.types.js +1 -2
  10. package/dist/Nymph.d.ts +575 -11
  11. package/dist/Nymph.js +825 -288
  12. package/dist/Nymph.js.map +1 -1
  13. package/dist/Nymph.types.d.ts +117 -7
  14. package/dist/Nymph.types.js +47 -5
  15. package/dist/Nymph.types.js.map +1 -1
  16. package/dist/conf/defaults.d.ts +1 -1
  17. package/dist/conf/defaults.js +26 -3
  18. package/dist/conf/defaults.js.map +1 -1
  19. package/dist/conf/index.d.ts +50 -2
  20. package/dist/conf/index.js +2 -8
  21. package/dist/conf/index.js.map +1 -1
  22. package/dist/decorators/index.d.ts +1 -0
  23. package/dist/decorators/index.js +2 -0
  24. package/dist/decorators/index.js.map +1 -0
  25. package/dist/decorators/transactional.d.ts +12 -0
  26. package/dist/decorators/transactional.js +45 -0
  27. package/dist/decorators/transactional.js.map +1 -0
  28. package/dist/driver/NymphDriver.d.ts +209 -41
  29. package/dist/driver/NymphDriver.js +737 -242
  30. package/dist/driver/NymphDriver.js.map +1 -1
  31. package/dist/driver/index.d.ts +1 -2
  32. package/dist/driver/index.js +2 -23
  33. package/dist/driver/index.js.map +1 -1
  34. package/dist/driver/testArtifacts.d.ts +4 -4
  35. package/dist/driver/testArtifacts.js +2016 -1086
  36. package/dist/driver/testArtifacts.js.map +1 -1
  37. package/dist/errors/ClassNotAvailableError.js +1 -5
  38. package/dist/errors/ClassNotAvailableError.js.map +1 -1
  39. package/dist/errors/EntityConflictError.js +1 -5
  40. package/dist/errors/EntityConflictError.js.map +1 -1
  41. package/dist/errors/EntityInvalidDataError.d.ts +6 -0
  42. package/dist/errors/EntityInvalidDataError.js +8 -6
  43. package/dist/errors/EntityInvalidDataError.js.map +1 -1
  44. package/dist/errors/EntityIsSleepingReferenceError.js +1 -5
  45. package/dist/errors/EntityIsSleepingReferenceError.js.map +1 -1
  46. package/dist/errors/EntityUniqueConstraintError.d.ts +3 -0
  47. package/dist/errors/EntityUniqueConstraintError.js +7 -0
  48. package/dist/errors/EntityUniqueConstraintError.js.map +1 -0
  49. package/dist/errors/HttpError.d.ts +5 -0
  50. package/dist/errors/HttpError.js +11 -0
  51. package/dist/errors/HttpError.js.map +1 -0
  52. package/dist/errors/InvalidParametersError.js +1 -5
  53. package/dist/errors/InvalidParametersError.js.map +1 -1
  54. package/dist/errors/InvalidStateError.d.ts +3 -0
  55. package/dist/errors/InvalidStateError.js +7 -0
  56. package/dist/errors/InvalidStateError.js.map +1 -0
  57. package/dist/errors/MethodFailedError.d.ts +3 -0
  58. package/dist/errors/MethodFailedError.js +7 -0
  59. package/dist/errors/MethodFailedError.js.map +1 -0
  60. package/dist/errors/NotConfiguredError.js +1 -5
  61. package/dist/errors/NotConfiguredError.js.map +1 -1
  62. package/dist/errors/QueryFailedError.d.ts +2 -1
  63. package/dist/errors/QueryFailedError.js +5 -6
  64. package/dist/errors/QueryFailedError.js.map +1 -1
  65. package/dist/errors/UnableToConnectError.js +1 -5
  66. package/dist/errors/UnableToConnectError.js.map +1 -1
  67. package/dist/errors/index.d.ts +12 -8
  68. package/dist/errors/index.js +12 -24
  69. package/dist/errors/index.js.map +1 -1
  70. package/dist/helpers/index.d.ts +1 -0
  71. package/dist/helpers/index.js +2 -0
  72. package/dist/helpers/index.js.map +1 -0
  73. package/dist/helpers/transaction.d.ts +15 -0
  74. package/dist/helpers/transaction.js +47 -0
  75. package/dist/helpers/transaction.js.map +1 -0
  76. package/dist/index.d.ts +13 -11
  77. package/dist/index.js +16 -34
  78. package/dist/index.js.map +1 -1
  79. package/dist/nymphJoiProps.d.ts +1 -1
  80. package/dist/nymphJoiProps.js +10 -15
  81. package/dist/nymphJoiProps.js.map +1 -1
  82. package/dist/testArtifacts.d.ts +21 -6
  83. package/dist/testArtifacts.js +124 -50
  84. package/dist/testArtifacts.js.map +1 -1
  85. package/dist/testMocks.d.ts +27 -12
  86. package/dist/testMocks.js +44 -33
  87. package/dist/testMocks.js.map +1 -1
  88. package/dist/utils.d.ts +5 -5
  89. package/dist/utils.js +106 -68
  90. package/dist/utils.js.map +1 -1
  91. package/jest.config.js +31 -5
  92. package/package.json +43 -24
  93. package/src/Entity.test.ts +73 -23
  94. package/src/Entity.ts +555 -169
  95. package/src/Entity.types.ts +102 -90
  96. package/src/Nymph.ts +930 -517
  97. package/src/Nymph.types.ts +145 -39
  98. package/src/conf/defaults.ts +34 -1
  99. package/src/conf/index.ts +50 -2
  100. package/src/decorators/index.ts +1 -0
  101. package/src/decorators/transactional.ts +63 -0
  102. package/src/driver/NymphDriver.ts +987 -417
  103. package/src/driver/index.ts +1 -3
  104. package/src/driver/testArtifacts.ts +2699 -1628
  105. package/src/errors/EntityUniqueConstraintError.ts +6 -0
  106. package/src/errors/HttpError.ts +12 -0
  107. package/src/errors/InvalidStateError.ts +6 -0
  108. package/src/errors/MethodFailedError.ts +6 -0
  109. package/src/errors/QueryFailedError.ts +3 -1
  110. package/src/errors/index.ts +16 -8
  111. package/src/helpers/index.ts +1 -0
  112. package/src/helpers/transaction.ts +65 -0
  113. package/src/index.ts +13 -11
  114. package/src/nymphJoiProps.ts +2 -2
  115. package/src/testArtifacts.ts +69 -20
  116. package/src/testMocks.ts +107 -41
  117. package/src/utils.ts +102 -67
  118. package/tsconfig.json +6 -3
  119. package/typedoc.json +4 -0
  120. package/dist/conf/d.d.ts +0 -6
  121. package/dist/conf/d.js +0 -3
  122. package/dist/conf/d.js.map +0 -1
  123. 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/strtotime';
5
- import { guid } from '@nymphjs/guid';
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 { InvalidParametersError, UnableToConnectError } from '../errors';
16
- import { Selector, Options, FormattedSelector } from '../Nymph.types';
17
- import { xor } from '../utils';
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
- protected abstract exportEntities(
87
- writeLine: (line: string) => void
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?: Options<T>,
146
+ options: Options<T> & { return: 'object' },
99
147
  ...selectors: Selector[]
100
- ): Promise<ReturnType<T['factorySync']>[]>;
148
+ ): Promise<EntityObjectType<T>[]>;
101
149
  abstract getEntities<T extends EntityConstructor = EntityConstructor>(
102
150
  options?: Options<T>,
103
151
  ...selectors: Selector[]
104
- ): Promise<ReturnType<T['factorySync']>[] | string[] | number>;
105
- protected abstract getEntitiesSync<
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
- ): ReturnType<T['factorySync']>[] | string[] | number;
156
+ ): Promise<
157
+ EntityInstanceType<T>[] | EntityObjectType<T>[] | string[] | number
158
+ >;
129
159
  abstract getUID(name: string): Promise<number | null>;
130
- abstract import(filename: string): Promise<boolean>;
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.exportEntities((line: string) => {
208
- fs.writeSync(fhandle, `${line}\n`);
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.exportEntities((line: string) => {
219
- console.log(line);
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
- protected async importFromFile(
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
- saveEntityCallback: (
227
- guid: string,
228
- tags: string[],
229
- sdata: SerializedEntityData,
230
- etype: string
231
- ) => Promise<void>,
232
- saveUIDCallback: (name: string, value: number) => Promise<void>,
233
- startTransactionCallback: (() => Promise<void>) | null = null,
234
- commitTransactionCallback: (() => Promise<void>) | null = null
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
- let guid: string | null = null;
243
- let line: Buffer | false = false;
244
- let sdata: SerializedEntityData = {};
245
- let tags: string[] = [];
246
- let etype = '__undefined';
247
- if (startTransactionCallback) {
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
- // Clear the entity cache.
283
- this.entityCache = {};
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
- const formattedSelectors = this.formatSelectors(selectors).selectors;
432
+ try {
433
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
434
+ const formattedSelectors = this.formatSelectors(selectors).selectors;
303
435
 
304
- for (const curSelector of formattedSelectors) {
305
- const type = curSelector.type;
306
- const typeIsNot = type === '!&' || type === '!|';
307
- const typeIsOr = type === '|' || type === '!|';
308
- let pass = !typeIsOr;
309
- for (const k in curSelector) {
310
- const key = k as keyof Selector;
311
- const value = curSelector[key];
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
- if (key === 'type' || value == null) {
314
- continue;
315
- }
445
+ if (key === 'type' || value == null) {
446
+ continue;
447
+ }
316
448
 
317
- const clauseNot = key.substr(0, 1) === '!';
318
- if (key === 'selector' || key === '!selector') {
319
- const tmpArr = (Array.isArray(value) ? value : [value]) as Selector[];
320
- pass = xor(
321
- this.checkData(data, sdata, tmpArr, guid, tags),
322
- xor(typeIsNot, clauseNot)
323
- );
324
- } else {
325
- if (key === 'qref' || key === '!qref') {
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
- // Check if it doesn't pass any for &, check if it
329
- // passes any for |.
330
- for (const curValue of value) {
331
- if (curValue == null) {
332
- continue;
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
- if (!xor(typeIsOr, pass)) {
498
- break;
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 (!xor(typeIsOr, pass)) {
504
- break;
670
+ if (!pass) {
671
+ return false;
505
672
  }
506
673
  }
507
- if (!pass) {
508
- return false;
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 (Array.isArray(value) || typeof value === 'object') {
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
- typeof qrefOptions.class === 'string'
630
- ? this.nymph.getEntityClass(qrefOptions.class)
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
- formattedSelectors: FormattedSelector[],
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 getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
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
- formattedSelectors: FormattedSelector[],
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 getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
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
- formattedSelectors: FormattedSelector[],
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
- ): { result: any; process: () => ReturnType<T['factorySync']>[] | Error };
851
- protected getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
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
- formattedSelectors: FormattedSelector[],
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: () => ReturnType<T['factorySync']>[] | string[] | number | Error;
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: ReturnType<T['factorySync']>[] | string[] = [];
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 entity = this.pullCache<ReturnType<T['factorySync']>>(
916
- selectors[0]['guid'],
917
- EntityClass.class,
918
- !!options.skipAc
919
- );
1299
+ const cacheEntity = this.pullCache(selectors[0]['guid']);
1300
+
920
1301
  if (
921
- entity != null &&
922
- entity.guid != null &&
1302
+ cacheEntity != null &&
1303
+ cacheEntity.guid != null &&
923
1304
  (!('tag' in selectors[0]) ||
924
- entity.hasTag(
925
- ...(Array.isArray(selectors[0].tag)
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
- const guid = entity.guid;
931
- return {
932
- result: Promise.resolve(null),
933
- process: () =>
934
- options.return === 'count'
935
- ? guid
936
- ? 1
937
- : 0
938
- : options.return === 'guid'
939
- ? [guid]
940
- : [entity],
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 qref of formattedSelectors.qrefs) {
950
- const [options, ...selectors] = qref;
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
- const entity = EntityClass.factorySync() as ReturnType<
998
- T['factorySync']
999
- >;
1000
- entity.$nymph = this.nymph;
1001
- if (options.skipAc != null) {
1002
- entity.$useSkipAc(!!options.skipAc);
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
- entity.guid = guid;
1005
- entity.cdate = cdate;
1006
- entity.mdate = mdate;
1007
- entity.tags = tags;
1008
- this.putDataCounter++;
1009
- if (this.putDataCounter == 100) {
1010
- throw new Error('Infinite loop detected in Entity loading.');
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
- cdate: number,
1046
- etype: string
1047
- ) => Promise<boolean>,
1048
- saveExistingEntityCallback: (
1049
- entity: EntityInterface,
1050
- guid: string,
1051
- tags: string[],
1052
- data: EntityData,
1053
- sdata: SerializedEntityData,
1054
- mdate: number,
1055
- etype: string
1056
- ) => Promise<boolean>,
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
- | ((success: boolean) => Promise<boolean>)
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
- const mdate = Date.now();
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 = entity.$getData();
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
- if (entity.guid == null) {
1074
- const cdate = mdate;
1075
- const newId = guid();
1076
- success = await saveNewEntityCallback(
1077
- entity,
1078
- newId,
1079
- tags,
1080
- data,
1081
- sdata,
1082
- cdate,
1083
- etype
1084
- );
1085
- if (success) {
1086
- entity.guid = newId;
1087
- entity.cdate = cdate;
1088
- entity.mdate = mdate;
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
- } else {
1091
- // Removed any cached versions of this entity.
1092
- if (this.nymph.config.cache) {
1093
- this.cleanCache(entity.guid);
1660
+ if (commitTransactionCallback) {
1661
+ success = await commitTransactionCallback(success);
1094
1662
  }
1095
- success = await saveExistingEntityCallback(
1096
- entity,
1097
- entity.guid,
1098
- tags,
1099
- data,
1100
- sdata,
1101
- mdate,
1102
- etype
1103
- );
1104
- if (success) {
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
- * @param className The entity's class.
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<T extends EntityInterface = EntityInterface>(
1134
- guid: string,
1135
- className: string,
1136
- useSkipAc = false
1137
- ): T | null {
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
- const entity = this.nymph.getEntityClass(className).factorySync() as T;
1145
- if (entity) {
1146
- entity.$nymph = this.nymph;
1147
- entity.$useSkipAc(!!useSkipAc);
1148
- entity.guid = guid;
1149
- entity.cdate = this.entityCache[guid]['cdate'];
1150
- entity.mdate = this.entityCache[guid]['mdate'];
1151
- entity.tags = this.entityCache[guid]['tags'];
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: cdate,
1195
- mdate: mdate,
1196
- tags: tags,
1197
- data: data,
1198
- sdata: 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(([guidA, countA], [guidB, countB]) => countA - countB)
1777
+ .sort(([_guidA, countA], [_guidB, countB]) => countA - countB)
1208
1778
  .pop()?.[0] ?? null;
1209
1779
  if (least == null) {
1210
1780
  // This should never happen.