@nymphjs/nymph 1.0.0-beta.8 → 1.0.0-beta.80

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 (65) hide show
  1. package/CHANGELOG.md +376 -0
  2. package/README.md +9 -19
  3. package/dist/Entity.d.ts +269 -7
  4. package/dist/Entity.js +491 -105
  5. package/dist/Entity.js.map +1 -1
  6. package/dist/Entity.test.js +25 -3
  7. package/dist/Entity.test.js.map +1 -1
  8. package/dist/Entity.types.d.ts +225 -19
  9. package/dist/Nymph.d.ts +430 -11
  10. package/dist/Nymph.js +686 -168
  11. package/dist/Nymph.js.map +1 -1
  12. package/dist/Nymph.types.d.ts +65 -7
  13. package/dist/Nymph.types.js +2 -1
  14. package/dist/Nymph.types.js.map +1 -1
  15. package/dist/conf/d.d.ts +30 -0
  16. package/dist/conf/defaults.js +27 -0
  17. package/dist/conf/defaults.js.map +1 -1
  18. package/dist/driver/NymphDriver.d.ts +136 -38
  19. package/dist/driver/NymphDriver.js +264 -131
  20. package/dist/driver/NymphDriver.js.map +1 -1
  21. package/dist/driver/testArtifacts.d.ts +1 -1
  22. package/dist/driver/testArtifacts.js +493 -51
  23. package/dist/driver/testArtifacts.js.map +1 -1
  24. package/dist/errors/EntityInvalidDataError.d.ts +6 -0
  25. package/dist/errors/EntityInvalidDataError.js +7 -1
  26. package/dist/errors/EntityInvalidDataError.js.map +1 -1
  27. package/dist/errors/EntityUniqueConstraintError.d.ts +3 -0
  28. package/dist/errors/EntityUniqueConstraintError.js +11 -0
  29. package/dist/errors/EntityUniqueConstraintError.js.map +1 -0
  30. package/dist/errors/InvalidStateError.d.ts +3 -0
  31. package/dist/errors/InvalidStateError.js +11 -0
  32. package/dist/errors/InvalidStateError.js.map +1 -0
  33. package/dist/errors/QueryFailedError.js +1 -0
  34. package/dist/errors/QueryFailedError.js.map +1 -1
  35. package/dist/errors/index.d.ts +2 -0
  36. package/dist/errors/index.js +2 -0
  37. package/dist/errors/index.js.map +1 -1
  38. package/dist/testArtifacts.d.ts +17 -5
  39. package/dist/testArtifacts.js +31 -27
  40. package/dist/testArtifacts.js.map +1 -1
  41. package/dist/testMocks.d.ts +13 -10
  42. package/dist/testMocks.js +32 -13
  43. package/dist/testMocks.js.map +1 -1
  44. package/dist/utils.d.ts +1 -1
  45. package/dist/utils.js +21 -5
  46. package/dist/utils.js.map +1 -1
  47. package/package.json +16 -14
  48. package/src/Entity.test.ts +22 -5
  49. package/src/Entity.ts +423 -134
  50. package/src/Entity.types.ts +32 -66
  51. package/src/Nymph.ts +609 -176
  52. package/src/Nymph.types.ts +131 -26
  53. package/src/conf/d.ts +12 -0
  54. package/src/conf/defaults.ts +31 -0
  55. package/src/driver/NymphDriver.ts +289 -311
  56. package/src/driver/testArtifacts.ts +562 -181
  57. package/src/errors/EntityUniqueConstraintError.ts +6 -0
  58. package/src/errors/InvalidStateError.ts +6 -0
  59. package/src/errors/index.ts +4 -0
  60. package/src/nymphJoiProps.ts +2 -2
  61. package/src/testArtifacts.ts +18 -19
  62. package/src/testMocks.ts +55 -30
  63. package/src/utils.ts +22 -8
  64. package/tsconfig.json +3 -3
  65. package/typedoc.json +4 -0
@@ -5,7 +5,9 @@ import strtotime from 'locutus/php/datetime/strtotime';
5
5
  import { guid } from '@nymphjs/guid';
6
6
 
7
7
  import type Nymph from '../Nymph';
8
- import {
8
+ import type { Selector, Options, FormattedSelector } from '../Nymph.types';
9
+ import Entity, { type EntityInstanceType } from '../Entity';
10
+ import type {
9
11
  EntityConstructor,
10
12
  EntityData,
11
13
  EntityInterface,
@@ -13,7 +15,6 @@ import {
13
15
  SerializedEntityData,
14
16
  } from '../Entity.types';
15
17
  import { InvalidParametersError, UnableToConnectError } from '../errors';
16
- import { Selector, Options, FormattedSelector } from '../Nymph.types';
17
18
  import { xor } from '../utils';
18
19
 
19
20
  // from: https://stackoverflow.com/a/6969486/664915
@@ -28,7 +29,7 @@ export default abstract class NymphDriver {
28
29
  protected nymph: Nymph = new Proxy({} as Nymph, {
29
30
  get() {
30
31
  throw new Error(
31
- 'Attempted access of Nymph instance before driver initialization!'
32
+ 'Attempted access of Nymph instance before driver initialization!',
32
33
  );
33
34
  },
34
35
  });
@@ -67,13 +68,14 @@ export default abstract class NymphDriver {
67
68
  abstract connect(): Promise<boolean>;
68
69
 
69
70
  abstract isConnected(): boolean;
71
+ protected abstract internalTransaction(name: string): Promise<any>;
70
72
  abstract startTransaction(name: string): Promise<Nymph>;
71
73
  abstract commit(name: string): Promise<boolean>;
72
74
  abstract rollback(name: string): Promise<boolean>;
73
75
  abstract inTransaction(): Promise<boolean>;
74
76
  abstract deleteEntityByID(
75
77
  guid: string,
76
- classConstructor: EntityConstructor
78
+ classConstructor: EntityConstructor,
77
79
  ): Promise<boolean>;
78
80
  abstract deleteEntityByID(guid: string, className?: string): Promise<boolean>;
79
81
  abstract deleteUID(name: string): Promise<boolean>;
@@ -83,9 +85,19 @@ export default abstract class NymphDriver {
83
85
  */
84
86
  abstract disconnect(): Promise<boolean>;
85
87
 
86
- protected abstract exportEntities(
87
- writeLine: (line: string) => void
88
- ): Promise<void>;
88
+ /**
89
+ * Detect whether the database needs to be migrated.
90
+ *
91
+ * If true, the database should be exported with an old version of Nymph, then
92
+ * imported into a fresh database with this version.
93
+ */
94
+ abstract needsMigration(): Promise<boolean>;
95
+
96
+ abstract exportDataIterator(): AsyncGenerator<
97
+ { type: 'comment' | 'uid' | 'entity'; content: string },
98
+ void,
99
+ undefined | false
100
+ >;
89
101
  abstract getEntities<T extends EntityConstructor = EntityConstructor>(
90
102
  options: Options<T> & { return: 'count' },
91
103
  ...selectors: Selector[]
@@ -97,37 +109,21 @@ export default abstract class NymphDriver {
97
109
  abstract getEntities<T extends EntityConstructor = EntityConstructor>(
98
110
  options?: Options<T>,
99
111
  ...selectors: Selector[]
100
- ): Promise<ReturnType<T['factorySync']>[]>;
112
+ ): Promise<EntityInstanceType<T>[]>;
101
113
  abstract getEntities<T extends EntityConstructor = EntityConstructor>(
102
114
  options?: Options<T>,
103
115
  ...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
- >(
126
- options?: Options<T>,
127
- ...selectors: Selector[]
128
- ): ReturnType<T['factorySync']>[] | string[] | number;
116
+ ): Promise<EntityInstanceType<T>[] | string[] | number>;
129
117
  abstract getUID(name: string): Promise<number | null>;
130
- abstract import(filename: string): Promise<boolean>;
118
+ abstract importEntity(entity: {
119
+ guid: string;
120
+ cdate: number;
121
+ mdate: number;
122
+ tags: string[];
123
+ sdata: SerializedEntityData;
124
+ etype: string;
125
+ }): Promise<void>;
126
+ abstract importUID(uid: { name: string; value: number }): Promise<void>;
131
127
  abstract newUID(name: string): Promise<number | null>;
132
128
  abstract renameUID(oldName: string, newName: string): Promise<boolean>;
133
129
  abstract saveEntity(entity: EntityInterface): Promise<boolean>;
@@ -147,7 +143,7 @@ export default abstract class NymphDriver {
147
143
  protected posixRegexMatch(
148
144
  pattern: string,
149
145
  subject: string,
150
- caseInsensitive = false
146
+ caseInsensitive = false,
151
147
  ) {
152
148
  const posixClasses = [
153
149
  // '[[:<:]]',
@@ -204,9 +200,9 @@ export default abstract class NymphDriver {
204
200
  if (!fhandle) {
205
201
  throw new InvalidParametersError('Provided filename is not writeable.');
206
202
  }
207
- await this.exportEntities((line: string) => {
208
- fs.writeSync(fhandle, `${line}\n`);
209
- });
203
+ for await (let entry of this.exportDataIterator()) {
204
+ fs.writeSync(fhandle, entry.content);
205
+ }
210
206
  fs.closeSync(fhandle);
211
207
  } catch (e: any) {
212
208
  return false;
@@ -215,130 +211,155 @@ export default abstract class NymphDriver {
215
211
  }
216
212
 
217
213
  public async exportPrint() {
218
- await this.exportEntities((line: string) => {
219
- console.log(line);
220
- });
214
+ for await (let entry of this.exportDataIterator()) {
215
+ console.log(entry.content);
216
+ }
221
217
  return true;
222
218
  }
223
219
 
224
- protected async importFromFile(
225
- 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
220
+ public async importDataIterator(
221
+ lines: Iterable<string>,
222
+ transaction?: boolean,
235
223
  ) {
236
- let rl: ReadLines;
237
- try {
238
- rl = new ReadLines(filename);
239
- } catch (e: any) {
240
- throw new InvalidParametersError('Provided filename is unreadable.');
224
+ const first = lines[Symbol.iterator]().next();
225
+
226
+ if (first.done || first.value !== '#nex2') {
227
+ throw new Error('Tried to import a file that is not a NEX v2 file.');
241
228
  }
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();
229
+
230
+ if (transaction) {
231
+ await this.internalTransaction('nymph-import');
249
232
  }
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';
233
+ try {
234
+ let guid: string | null = null;
235
+ let sdata: SerializedEntityData = {};
236
+ let tags: string[] = [];
237
+ let etype = '__undefined';
238
+ for (let line of lines) {
239
+ if (line.match(/^\s*#/)) {
240
+ continue;
271
241
  }
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];
242
+ const entityMatch = line.match(
243
+ /^\s*{([0-9A-Fa-f]+)}<([-\w_]+)>\[([^\]]*)\]\s*$/,
244
+ );
245
+ const propMatch = line.match(/^\s*([^=]+)\s*=\s*(.*\S)\s*$/);
246
+ const uidMatch = line.match(/^\s*<([^>]+)>\[(\d+)\]\s*$/);
247
+ if (uidMatch) {
248
+ // Add the UID.
249
+ await this.importUID({
250
+ name: uidMatch[1],
251
+ value: Number(uidMatch[2]),
252
+ });
253
+ } else if (entityMatch) {
254
+ // Save the current entity.
255
+ if (guid) {
256
+ const cdate = Number(JSON.parse(sdata.cdate));
257
+ delete sdata.cdate;
258
+ const mdate = Number(JSON.parse(sdata.mdate));
259
+ delete sdata.mdate;
260
+ await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
261
+ guid = null;
262
+ tags = [];
263
+ sdata = {};
264
+ etype = '__undefined';
265
+ }
266
+ // Record the new entity's info.
267
+ guid = entityMatch[1];
268
+ etype = entityMatch[2];
269
+ tags = entityMatch[3].split(',');
270
+ } else if (propMatch) {
271
+ // Add the variable to the new entity.
272
+ if (guid) {
273
+ sdata[propMatch[1]] = propMatch[2];
274
+ }
280
275
  }
276
+ // Clear the entity cache.
277
+ this.entityCache = {};
281
278
  }
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();
279
+ // Save the last entity.
280
+ if (guid) {
281
+ const cdate = Number(JSON.parse(sdata.cdate));
282
+ delete sdata.cdate;
283
+ const mdate = Number(JSON.parse(sdata.mdate));
284
+ delete sdata.mdate;
285
+ await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
286
+ }
287
+ if (transaction) {
288
+ await this.commit('nymph-import');
289
+ }
290
+ } catch (e: any) {
291
+ await this.rollback('nymph-import');
292
+ throw e;
291
293
  }
292
294
  return true;
293
295
  }
294
296
 
297
+ public async importData(text: string, transaction?: boolean) {
298
+ return await this.importDataIterator(text.split('\n'), transaction);
299
+ }
300
+
301
+ public async import(filename: string, transaction?: boolean) {
302
+ let rl: ReadLines;
303
+ try {
304
+ rl = new ReadLines(filename);
305
+ } catch (e: any) {
306
+ throw new InvalidParametersError('Provided filename is unreadable.');
307
+ }
308
+
309
+ const lines = {
310
+ *[Symbol.iterator]() {
311
+ let line: false | Buffer;
312
+ while ((line = rl.next())) {
313
+ yield line.toString('utf8');
314
+ }
315
+ },
316
+ };
317
+
318
+ return await this.importDataIterator(lines, transaction);
319
+ }
320
+
295
321
  public checkData(
296
322
  data: EntityData,
297
323
  sdata: SerializedEntityData,
298
324
  selectors: Selector[],
299
325
  guid: string | null = null,
300
- tags: string[] | null = null
326
+ tags: string[] | null = null,
301
327
  ) {
302
- const formattedSelectors = this.formatSelectors(selectors).selectors;
328
+ try {
329
+ const formattedSelectors = this.formatSelectors(selectors).selectors;
303
330
 
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];
331
+ for (const curSelector of formattedSelectors) {
332
+ const type = curSelector.type;
333
+ const typeIsNot = type === '!&' || type === '!|';
334
+ const typeIsOr = type === '|' || type === '!|';
335
+ let pass = !typeIsOr;
336
+ for (const k in curSelector) {
337
+ const key = k as keyof Selector;
338
+ const value = curSelector[key];
312
339
 
313
- if (key === 'type' || value == null) {
314
- continue;
315
- }
340
+ if (key === 'type' || value == null) {
341
+ continue;
342
+ }
316
343
 
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.");
344
+ const clauseNot = key.substring(0, 1) === '!';
345
+ if (key === 'selector' || key === '!selector') {
346
+ const tmpArr = (
347
+ Array.isArray(value) ? value : [value]
348
+ ) as Selector[];
349
+ pass = xor(
350
+ this.checkData(data, sdata, tmpArr, guid, tags),
351
+ xor(typeIsNot, clauseNot),
352
+ );
327
353
  } 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
- }
354
+ if (key === 'qref' || key === '!qref') {
355
+ throw new Error("Can't use checkData on qref clauses.");
356
+ } else {
357
+ // Check if it doesn't pass any for &, check if it passes any for |.
358
+ for (const curValue of value) {
359
+ if (curValue == null) {
360
+ continue;
361
+ }
334
362
 
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
363
  const propName = (curValue as string[])[0];
343
364
 
344
365
  // Unserialize the data for this variable.
@@ -356,7 +377,7 @@ export default abstract class NymphDriver {
356
377
  case '!tag':
357
378
  pass = xor(
358
379
  (tags ?? []).indexOf(propName) !== -1,
359
- xor(typeIsNot, clauseNot)
380
+ xor(typeIsNot, clauseNot),
360
381
  );
361
382
  break;
362
383
  case 'defined':
@@ -367,7 +388,7 @@ export default abstract class NymphDriver {
367
388
  case '!truthy':
368
389
  pass = xor(
369
390
  propName in data && data[propName],
370
- xor(typeIsNot, clauseNot)
391
+ xor(typeIsNot, clauseNot),
371
392
  );
372
393
  break;
373
394
  case 'ref':
@@ -377,9 +398,9 @@ export default abstract class NymphDriver {
377
398
  propName in data &&
378
399
  this.entityReferenceSearch(
379
400
  data[propName],
380
- testRefValue
401
+ testRefValue,
381
402
  ),
382
- xor(typeIsNot, clauseNot)
403
+ xor(typeIsNot, clauseNot),
383
404
  );
384
405
  break;
385
406
  case 'equal':
@@ -391,7 +412,7 @@ export default abstract class NymphDriver {
391
412
  propName in data &&
392
413
  JSON.stringify(data[propName]) ===
393
414
  JSON.stringify(testEqualValue),
394
- xor(typeIsNot, clauseNot)
415
+ xor(typeIsNot, clauseNot),
395
416
  );
396
417
  break;
397
418
  case 'contain':
@@ -402,9 +423,9 @@ export default abstract class NymphDriver {
402
423
  pass = xor(
403
424
  propName in data &&
404
425
  JSON.stringify(data[propName]).indexOf(
405
- JSON.stringify(testContainValue)
426
+ JSON.stringify(testContainValue),
406
427
  ) !== -1,
407
- xor(typeIsNot, clauseNot)
428
+ xor(typeIsNot, clauseNot),
408
429
  );
409
430
  break;
410
431
  case 'like':
@@ -419,9 +440,9 @@ export default abstract class NymphDriver {
419
440
  escapeRegExp(testLikeValue)
420
441
  .replace('%', () => '.*')
421
442
  .replace('_', () => '.') +
422
- '$'
443
+ '$',
423
444
  ).test(data[propName]),
424
- xor(typeIsNot, clauseNot)
445
+ xor(typeIsNot, clauseNot),
425
446
  );
426
447
  break;
427
448
  case 'match':
@@ -433,7 +454,7 @@ export default abstract class NymphDriver {
433
454
  pass = xor(
434
455
  propName in data &&
435
456
  this.posixRegexMatch(testMatchValue, data[propName]),
436
- xor(typeIsNot, clauseNot)
457
+ xor(typeIsNot, clauseNot),
437
458
  );
438
459
  break;
439
460
  case 'imatch':
@@ -447,9 +468,9 @@ export default abstract class NymphDriver {
447
468
  this.posixRegexMatch(
448
469
  testIMatchValue,
449
470
  data[propName],
450
- true
471
+ true,
451
472
  ),
452
- xor(typeIsNot, clauseNot)
473
+ xor(typeIsNot, clauseNot),
453
474
  );
454
475
  break;
455
476
  case 'gt':
@@ -459,7 +480,7 @@ export default abstract class NymphDriver {
459
480
  )[1] as number;
460
481
  pass = xor(
461
482
  propName in data && data[propName] > testGTValue,
462
- xor(typeIsNot, clauseNot)
483
+ xor(typeIsNot, clauseNot),
463
484
  );
464
485
  break;
465
486
  case 'gte':
@@ -469,7 +490,7 @@ export default abstract class NymphDriver {
469
490
  )[1] as number;
470
491
  pass = xor(
471
492
  propName in data && data[propName] >= testGTEValue,
472
- xor(typeIsNot, clauseNot)
493
+ xor(typeIsNot, clauseNot),
473
494
  );
474
495
  break;
475
496
  case 'lt':
@@ -479,7 +500,7 @@ export default abstract class NymphDriver {
479
500
  )[1] as number;
480
501
  pass = xor(
481
502
  propName in data && data[propName] < testLTValue,
482
- xor(typeIsNot, clauseNot)
503
+ xor(typeIsNot, clauseNot),
483
504
  );
484
505
  break;
485
506
  case 'lte':
@@ -489,26 +510,33 @@ export default abstract class NymphDriver {
489
510
  )[1] as number;
490
511
  pass = xor(
491
512
  propName in data && data[propName] <= testLTEValue,
492
- xor(typeIsNot, clauseNot)
513
+ xor(typeIsNot, clauseNot),
493
514
  );
494
515
  break;
495
516
  }
496
- }
497
- if (!xor(typeIsOr, pass)) {
498
- break;
517
+
518
+ if (!xor(typeIsOr, pass)) {
519
+ break;
520
+ }
499
521
  }
500
522
  }
501
523
  }
524
+ if (!xor(typeIsOr, pass)) {
525
+ break;
526
+ }
502
527
  }
503
- if (!xor(typeIsOr, pass)) {
504
- break;
528
+ if (!pass) {
529
+ return false;
505
530
  }
506
531
  }
507
- if (!pass) {
508
- return false;
509
- }
532
+ return true;
533
+ } catch (e: any) {
534
+ this.nymph.config.debugError(
535
+ 'nymph',
536
+ `Failed to check entity data: ${e}`,
537
+ );
538
+ throw e;
510
539
  }
511
- return true;
512
540
  }
513
541
 
514
542
  /**
@@ -547,7 +575,7 @@ export default abstract class NymphDriver {
547
575
  | EntityInterface
548
576
  | EntityReference
549
577
  | string
550
- | (EntityInterface | EntityReference | string)[]
578
+ | (EntityInterface | EntityReference | string)[],
551
579
  ) {
552
580
  // Get the GUID, if the passed $entity is an object.
553
581
  const guids: string[] = [];
@@ -590,7 +618,7 @@ export default abstract class NymphDriver {
590
618
 
591
619
  public formatSelectors(
592
620
  selectors: Selector[],
593
- options: Options = {}
621
+ options: Options = {},
594
622
  ): {
595
623
  selectors: FormattedSelector[];
596
624
  qrefs: [Options, ...FormattedSelector[]][];
@@ -625,10 +653,9 @@ export default abstract class NymphDriver {
625
653
  for (let i = 0; i < tmpArr.length; i++) {
626
654
  const name = tmpArr[i][0];
627
655
  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');
656
+ const QrefEntityClass = qrefOptions.class
657
+ ? this.nymph.getEntityClass(qrefOptions.class)
658
+ : this.nymph.getEntityClass('Entity');
632
659
  const newOptions = {
633
660
  ...qrefOptions,
634
661
  class: QrefEntityClass,
@@ -637,7 +664,7 @@ export default abstract class NymphDriver {
637
664
  const newSelectors = this.formatSelectors(qrefSelectors, options);
638
665
  qrefs.push(
639
666
  [newOptions, ...newSelectors.selectors],
640
- ...newSelectors.qrefs
667
+ ...newSelectors.qrefs,
641
668
  );
642
669
  formatArr[i] = [name, [newOptions, ...newSelectors.selectors]];
643
670
  }
@@ -685,12 +712,12 @@ export default abstract class NymphDriver {
685
712
 
686
713
  protected iterateSelectorsForQuery(
687
714
  selectors: FormattedSelector[],
688
- callback: (
689
- key: string,
690
- value: any,
691
- typeIsOr: boolean,
692
- typeIsNot: boolean
693
- ) => string
715
+ callback: (data: {
716
+ key: string;
717
+ value: any;
718
+ typeIsOr: boolean;
719
+ typeIsNot: boolean;
720
+ }) => string,
694
721
  ) {
695
722
  const queryParts = [];
696
723
  for (const curSelector of selectors) {
@@ -703,7 +730,7 @@ export default abstract class NymphDriver {
703
730
  if (key === 'type') {
704
731
  continue;
705
732
  }
706
- let curQuery = callback(key, value, typeIsOr, typeIsNot);
733
+ let curQuery = callback({ key, value, typeIsOr, typeIsNot });
707
734
  if (curQuery) {
708
735
  if (curSelectorQuery) {
709
736
  curSelectorQuery += typeIsOr ? ' OR ' : ' AND ';
@@ -719,71 +746,14 @@ export default abstract class NymphDriver {
719
746
  return queryParts;
720
747
  }
721
748
 
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>(
749
+ protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
780
750
  options: Options<T> & { return: 'count' },
781
751
  selectors: Selector[],
782
- performQueryCallback: (
783
- options: Options<T>,
784
- formattedSelectors: FormattedSelector[],
785
- etype: string
786
- ) => {
752
+ performQueryCallback: (data: {
753
+ options: Options<T>;
754
+ selectors: FormattedSelector[];
755
+ etype: string;
756
+ }) => {
787
757
  result: any;
788
758
  },
789
759
  rowFetchCallback: () => any,
@@ -798,16 +768,16 @@ export default abstract class NymphDriver {
798
768
  getDataNameAndSValueCallback: (row: any) => {
799
769
  name: string;
800
770
  svalue: string;
801
- }
771
+ },
802
772
  ): { result: any; process: () => number | Error };
803
- protected getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
773
+ protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
804
774
  options: Options<T> & { return: 'guid' },
805
775
  selectors: Selector[],
806
- performQueryCallback: (
807
- options: Options<T>,
808
- formattedSelectors: FormattedSelector[],
809
- etype: string
810
- ) => {
776
+ performQueryCallback: (data: {
777
+ options: Options<T>;
778
+ selectors: FormattedSelector[];
779
+ etype: string;
780
+ }) => {
811
781
  result: any;
812
782
  },
813
783
  rowFetchCallback: () => any,
@@ -822,16 +792,16 @@ export default abstract class NymphDriver {
822
792
  getDataNameAndSValueCallback: (row: any) => {
823
793
  name: string;
824
794
  svalue: string;
825
- }
795
+ },
826
796
  ): { result: any; process: () => string[] | Error };
827
- protected getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
797
+ protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
828
798
  options: Options<T>,
829
799
  selectors: Selector[],
830
- performQueryCallback: (
831
- options: Options<T>,
832
- formattedSelectors: FormattedSelector[],
833
- etype: string
834
- ) => {
800
+ performQueryCallback: (data: {
801
+ options: Options<T>;
802
+ selectors: FormattedSelector[];
803
+ etype: string;
804
+ }) => {
835
805
  result: any;
836
806
  },
837
807
  rowFetchCallback: () => any,
@@ -846,16 +816,19 @@ export default abstract class NymphDriver {
846
816
  getDataNameAndSValueCallback: (row: any) => {
847
817
  name: string;
848
818
  svalue: string;
849
- }
850
- ): { result: any; process: () => ReturnType<T['factorySync']>[] | Error };
851
- protected getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
819
+ },
820
+ ): {
821
+ result: any;
822
+ process: () => EntityInstanceType<T>[] | Error;
823
+ };
824
+ protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
852
825
  options: Options<T>,
853
826
  selectors: Selector[],
854
- performQueryCallback: (
855
- options: Options<T>,
856
- formattedSelectors: FormattedSelector[],
857
- etype: string
858
- ) => {
827
+ performQueryCallback: (data: {
828
+ options: Options<T>;
829
+ selectors: FormattedSelector[];
830
+ etype: string;
831
+ }) => {
859
832
  result: any;
860
833
  },
861
834
  rowFetchCallback: () => any,
@@ -870,10 +843,10 @@ export default abstract class NymphDriver {
870
843
  getDataNameAndSValueCallback: (row: any) => {
871
844
  name: string;
872
845
  svalue: string;
873
- }
846
+ },
874
847
  ): {
875
848
  result: any;
876
- process: () => ReturnType<T['factorySync']>[] | string[] | number | Error;
849
+ process: () => EntityInstanceType<T>[] | string[] | number | Error;
877
850
  } {
878
851
  if (!this.isConnected()) {
879
852
  throw new UnableToConnectError('not connected to DB');
@@ -886,12 +859,12 @@ export default abstract class NymphDriver {
886
859
  ['&', '!&', '|', '!|'].indexOf(selector.type) === -1
887
860
  ) {
888
861
  throw new InvalidParametersError(
889
- 'Invalid query selector passed: ' + JSON.stringify(selector)
862
+ 'Invalid query selector passed: ' + JSON.stringify(selector),
890
863
  );
891
864
  }
892
865
  }
893
866
 
894
- let entities: ReturnType<T['factorySync']>[] | string[] = [];
867
+ let entities: EntityInstanceType<T>[] | string[] = [];
895
868
  let count = 0;
896
869
  const EntityClass =
897
870
  options.class ?? (this.nymph.getEntityClass('Entity') as T);
@@ -901,6 +874,7 @@ export default abstract class NymphDriver {
901
874
  if (
902
875
  this.nymph.config.cache &&
903
876
  !options.skipCache &&
877
+ selectors.length &&
904
878
  'guid' in selectors[0] &&
905
879
  typeof selectors[0].guid === 'number'
906
880
  ) {
@@ -912,10 +886,10 @@ export default abstract class NymphDriver {
912
886
  (Object.keys(selectors[0]).length === 2 ||
913
887
  (Object.keys(selectors[0]).length === 3 && 'tag' in selectors[0]))
914
888
  ) {
915
- const entity = this.pullCache<ReturnType<T['factorySync']>>(
889
+ const entity = this.pullCache<EntityInstanceType<T>>(
916
890
  selectors[0]['guid'],
917
891
  EntityClass.class,
918
- !!options.skipAc
892
+ !!options.skipAc,
919
893
  );
920
894
  if (
921
895
  entity != null &&
@@ -924,7 +898,7 @@ export default abstract class NymphDriver {
924
898
  entity.hasTag(
925
899
  ...(Array.isArray(selectors[0].tag)
926
900
  ? selectors[0].tag
927
- : [selectors[0].tag])
901
+ : [selectors[0].tag]),
928
902
  ))
929
903
  ) {
930
904
  const guid = entity.guid;
@@ -946,15 +920,16 @@ export default abstract class NymphDriver {
946
920
  try {
947
921
  const formattedSelectors = this.formatSelectors(selectors, options);
948
922
  this.nymph.runQueryCallbacks(options, formattedSelectors.selectors);
949
- for (let qref of formattedSelectors.qrefs) {
950
- const [options, ...selectors] = qref;
923
+ for (let i = 0; i < formattedSelectors.qrefs.length; i++) {
924
+ const [options, ...selectors] = formattedSelectors.qrefs[i];
951
925
  this.nymph.runQueryCallbacks(options, selectors);
926
+ formattedSelectors.qrefs[i] = [options, ...selectors];
952
927
  }
953
- const { result } = performQueryCallback(
928
+ const { result } = performQueryCallback({
954
929
  options,
955
- formattedSelectors.selectors,
956
- etype
957
- );
930
+ selectors: formattedSelectors.selectors,
931
+ etype,
932
+ });
958
933
 
959
934
  return {
960
935
  result,
@@ -994,9 +969,7 @@ export default abstract class NymphDriver {
994
969
  // Make sure that $row is incremented :)
995
970
  row = rowFetchCallback();
996
971
  }
997
- const entity = EntityClass.factorySync() as ReturnType<
998
- T['factorySync']
999
- >;
972
+ const entity = EntityClass.factorySync() as EntityInstanceType<T>;
1000
973
  entity.$nymph = this.nymph;
1001
974
  if (options.skipAc != null) {
1002
975
  entity.$useSkipAc(!!options.skipAc);
@@ -1036,34 +1009,37 @@ export default abstract class NymphDriver {
1036
1009
 
1037
1010
  protected async saveEntityRowLike(
1038
1011
  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>,
1012
+ saveNewEntityCallback: (data: {
1013
+ entity: EntityInterface;
1014
+ guid: string;
1015
+ tags: string[];
1016
+ data: EntityData;
1017
+ sdata: SerializedEntityData;
1018
+ uniques: string[];
1019
+ cdate: number;
1020
+ etype: string;
1021
+ }) => Promise<boolean>,
1022
+ saveExistingEntityCallback: (data: {
1023
+ entity: EntityInterface;
1024
+ guid: string;
1025
+ tags: string[];
1026
+ data: EntityData;
1027
+ sdata: SerializedEntityData;
1028
+ uniques: string[];
1029
+ mdate: number;
1030
+ etype: string;
1031
+ }) => Promise<boolean>,
1057
1032
  startTransactionCallback: (() => Promise<void>) | null = null,
1058
1033
  commitTransactionCallback:
1059
1034
  | ((success: boolean) => Promise<boolean>)
1060
- | null = null
1035
+ | null = null,
1061
1036
  ) {
1062
1037
  // Get a modified date.
1063
1038
  const mdate = Date.now();
1064
1039
  const tags = difference(entity.tags, ['']);
1065
1040
  const data = entity.$getData();
1066
1041
  const sdata = entity.$getSData();
1042
+ const uniques = await entity.$getUniques();
1067
1043
  const EntityClass = entity.constructor as EntityConstructor;
1068
1044
  const etype = EntityClass.ETYPE;
1069
1045
  if (startTransactionCallback) {
@@ -1073,15 +1049,16 @@ export default abstract class NymphDriver {
1073
1049
  if (entity.guid == null) {
1074
1050
  const cdate = mdate;
1075
1051
  const newId = guid();
1076
- success = await saveNewEntityCallback(
1052
+ success = await saveNewEntityCallback({
1077
1053
  entity,
1078
- newId,
1054
+ guid: newId,
1079
1055
  tags,
1080
1056
  data,
1081
1057
  sdata,
1058
+ uniques,
1082
1059
  cdate,
1083
- etype
1084
- );
1060
+ etype,
1061
+ });
1085
1062
  if (success) {
1086
1063
  entity.guid = newId;
1087
1064
  entity.cdate = cdate;
@@ -1092,15 +1069,16 @@ export default abstract class NymphDriver {
1092
1069
  if (this.nymph.config.cache) {
1093
1070
  this.cleanCache(entity.guid);
1094
1071
  }
1095
- success = await saveExistingEntityCallback(
1072
+ success = await saveExistingEntityCallback({
1096
1073
  entity,
1097
- entity.guid,
1074
+ guid: entity.guid,
1098
1075
  tags,
1099
1076
  data,
1100
1077
  sdata,
1078
+ uniques,
1101
1079
  mdate,
1102
- etype
1103
- );
1080
+ etype,
1081
+ });
1104
1082
  if (success) {
1105
1083
  entity.mdate = mdate;
1106
1084
  }
@@ -1116,7 +1094,7 @@ export default abstract class NymphDriver {
1116
1094
  entity.mdate as number,
1117
1095
  entity.tags,
1118
1096
  data,
1119
- sdata
1097
+ sdata,
1120
1098
  );
1121
1099
  }
1122
1100
  return success;
@@ -1130,10 +1108,10 @@ export default abstract class NymphDriver {
1130
1108
  * @param useSkipAc Whether to tell the entity to use skip_ac.
1131
1109
  * @returns The entity or null if it's not cached.
1132
1110
  */
1133
- protected pullCache<T extends EntityInterface = EntityInterface>(
1111
+ protected pullCache<T extends Entity>(
1134
1112
  guid: string,
1135
1113
  className: string,
1136
- useSkipAc = false
1114
+ useSkipAc = false,
1137
1115
  ): T | null {
1138
1116
  // Increment the entity access count.
1139
1117
  if (!(guid in this.entityCount)) {
@@ -1151,7 +1129,7 @@ export default abstract class NymphDriver {
1151
1129
  entity.tags = this.entityCache[guid]['tags'];
1152
1130
  entity.$putData(
1153
1131
  this.entityCache[guid]['data'],
1154
- this.entityCache[guid]['sdata']
1132
+ this.entityCache[guid]['sdata'],
1155
1133
  );
1156
1134
  return entity;
1157
1135
  }
@@ -1175,7 +1153,7 @@ export default abstract class NymphDriver {
1175
1153
  mdate: number,
1176
1154
  tags: string[],
1177
1155
  data: EntityData,
1178
- sdata: SerializedEntityData
1156
+ sdata: SerializedEntityData,
1179
1157
  ) {
1180
1158
  if (guid == null) {
1181
1159
  return;
@@ -1204,7 +1182,7 @@ export default abstract class NymphDriver {
1204
1182
  // Find which entity has been accessed the least.
1205
1183
  const least =
1206
1184
  Object.entries(this.entityCount)
1207
- .sort(([guidA, countA], [guidB, countB]) => countA - countB)
1185
+ .sort(([_guidA, countA], [_guidB, countB]) => countA - countB)
1208
1186
  .pop()?.[0] ?? null;
1209
1187
  if (least == null) {
1210
1188
  // This should never happen.