@nymphjs/nymph 1.0.0-beta.7 → 1.0.0-beta.71

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 +324 -0
  2. package/README.md +13 -21
  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 +39 -17
  7. package/dist/Entity.test.js.map +1 -1
  8. package/dist/Entity.types.d.ts +225 -19
  9. package/dist/Nymph.d.ts +423 -11
  10. package/dist/Nymph.js +685 -176
  11. package/dist/Nymph.js.map +1 -1
  12. package/dist/Nymph.types.d.ts +31 -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 +26 -0
  16. package/dist/conf/defaults.js +22 -0
  17. package/dist/conf/defaults.js.map +1 -1
  18. package/dist/driver/NymphDriver.d.ts +129 -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 +336 -42
  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 +15 -10
  42. package/dist/testMocks.js +37 -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 +32 -11
  49. package/src/Entity.ts +423 -134
  50. package/src/Entity.types.ts +32 -66
  51. package/src/Nymph.ts +618 -195
  52. package/src/Nymph.types.ts +97 -26
  53. package/src/conf/d.ts +8 -0
  54. package/src/conf/defaults.ts +24 -0
  55. package/src/driver/NymphDriver.ts +281 -311
  56. package/src/driver/testArtifacts.ts +384 -173
  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 +64 -32
  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,11 @@ 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
+ abstract exportDataIterator(): AsyncGenerator<
89
+ { type: 'comment' | 'uid' | 'entity'; content: string },
90
+ void,
91
+ undefined | false
92
+ >;
89
93
  abstract getEntities<T extends EntityConstructor = EntityConstructor>(
90
94
  options: Options<T> & { return: 'count' },
91
95
  ...selectors: Selector[]
@@ -97,37 +101,21 @@ export default abstract class NymphDriver {
97
101
  abstract getEntities<T extends EntityConstructor = EntityConstructor>(
98
102
  options?: Options<T>,
99
103
  ...selectors: Selector[]
100
- ): Promise<ReturnType<T['factorySync']>[]>;
104
+ ): Promise<EntityInstanceType<T>[]>;
101
105
  abstract getEntities<T extends EntityConstructor = EntityConstructor>(
102
106
  options?: Options<T>,
103
107
  ...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;
108
+ ): Promise<EntityInstanceType<T>[] | string[] | number>;
129
109
  abstract getUID(name: string): Promise<number | null>;
130
- abstract import(filename: string): Promise<boolean>;
110
+ abstract importEntity(entity: {
111
+ guid: string;
112
+ cdate: number;
113
+ mdate: number;
114
+ tags: string[];
115
+ sdata: SerializedEntityData;
116
+ etype: string;
117
+ }): Promise<void>;
118
+ abstract importUID(uid: { name: string; value: number }): Promise<void>;
131
119
  abstract newUID(name: string): Promise<number | null>;
132
120
  abstract renameUID(oldName: string, newName: string): Promise<boolean>;
133
121
  abstract saveEntity(entity: EntityInterface): Promise<boolean>;
@@ -147,7 +135,7 @@ export default abstract class NymphDriver {
147
135
  protected posixRegexMatch(
148
136
  pattern: string,
149
137
  subject: string,
150
- caseInsensitive = false
138
+ caseInsensitive = false,
151
139
  ) {
152
140
  const posixClasses = [
153
141
  // '[[:<:]]',
@@ -204,9 +192,9 @@ export default abstract class NymphDriver {
204
192
  if (!fhandle) {
205
193
  throw new InvalidParametersError('Provided filename is not writeable.');
206
194
  }
207
- await this.exportEntities((line: string) => {
208
- fs.writeSync(fhandle, `${line}\n`);
209
- });
195
+ for await (let entry of this.exportDataIterator()) {
196
+ fs.writeSync(fhandle, entry.content);
197
+ }
210
198
  fs.closeSync(fhandle);
211
199
  } catch (e: any) {
212
200
  return false;
@@ -215,130 +203,155 @@ export default abstract class NymphDriver {
215
203
  }
216
204
 
217
205
  public async exportPrint() {
218
- await this.exportEntities((line: string) => {
219
- console.log(line);
220
- });
206
+ for await (let entry of this.exportDataIterator()) {
207
+ console.log(entry.content);
208
+ }
221
209
  return true;
222
210
  }
223
211
 
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
212
+ public async importDataIterator(
213
+ lines: Iterable<string>,
214
+ transaction?: boolean,
235
215
  ) {
236
- let rl: ReadLines;
237
- try {
238
- rl = new ReadLines(filename);
239
- } catch (e: any) {
240
- throw new InvalidParametersError('Provided filename is unreadable.');
216
+ const first = lines[Symbol.iterator]().next();
217
+
218
+ if (first.done || first.value !== '#nex2') {
219
+ throw new Error('Tried to import a file that is not a NEX v2 file.');
241
220
  }
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();
221
+
222
+ if (transaction) {
223
+ await this.internalTransaction('nymph-import');
249
224
  }
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';
225
+ try {
226
+ let guid: string | null = null;
227
+ let sdata: SerializedEntityData = {};
228
+ let tags: string[] = [];
229
+ let etype = '__undefined';
230
+ for (let line of lines) {
231
+ if (line.match(/^\s*#/)) {
232
+ continue;
271
233
  }
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];
234
+ const entityMatch = line.match(
235
+ /^\s*{([0-9A-Fa-f]+)}<([-\w_]+)>\[([^\]]*)\]\s*$/,
236
+ );
237
+ const propMatch = line.match(/^\s*([^=]+)\s*=\s*(.*\S)\s*$/);
238
+ const uidMatch = line.match(/^\s*<([^>]+)>\[(\d+)\]\s*$/);
239
+ if (uidMatch) {
240
+ // Add the UID.
241
+ await this.importUID({
242
+ name: uidMatch[1],
243
+ value: Number(uidMatch[2]),
244
+ });
245
+ } else if (entityMatch) {
246
+ // Save the current entity.
247
+ if (guid) {
248
+ const cdate = Number(JSON.parse(sdata.cdate));
249
+ delete sdata.cdate;
250
+ const mdate = Number(JSON.parse(sdata.mdate));
251
+ delete sdata.mdate;
252
+ await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
253
+ guid = null;
254
+ tags = [];
255
+ sdata = {};
256
+ etype = '__undefined';
257
+ }
258
+ // Record the new entity's info.
259
+ guid = entityMatch[1];
260
+ etype = entityMatch[2];
261
+ tags = entityMatch[3].split(',');
262
+ } else if (propMatch) {
263
+ // Add the variable to the new entity.
264
+ if (guid) {
265
+ sdata[propMatch[1]] = propMatch[2];
266
+ }
280
267
  }
268
+ // Clear the entity cache.
269
+ this.entityCache = {};
281
270
  }
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();
271
+ // Save the last entity.
272
+ if (guid) {
273
+ const cdate = Number(JSON.parse(sdata.cdate));
274
+ delete sdata.cdate;
275
+ const mdate = Number(JSON.parse(sdata.mdate));
276
+ delete sdata.mdate;
277
+ await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
278
+ }
279
+ if (transaction) {
280
+ await this.commit('nymph-import');
281
+ }
282
+ } catch (e: any) {
283
+ await this.rollback('nymph-import');
284
+ throw e;
291
285
  }
292
286
  return true;
293
287
  }
294
288
 
289
+ public async importData(text: string, transaction?: boolean) {
290
+ return await this.importDataIterator(text.split('\n'), transaction);
291
+ }
292
+
293
+ public async import(filename: string, transaction?: boolean) {
294
+ let rl: ReadLines;
295
+ try {
296
+ rl = new ReadLines(filename);
297
+ } catch (e: any) {
298
+ throw new InvalidParametersError('Provided filename is unreadable.');
299
+ }
300
+
301
+ const lines = {
302
+ *[Symbol.iterator]() {
303
+ let line: false | Buffer;
304
+ while ((line = rl.next())) {
305
+ yield line.toString('utf8');
306
+ }
307
+ },
308
+ };
309
+
310
+ return await this.importDataIterator(lines, transaction);
311
+ }
312
+
295
313
  public checkData(
296
314
  data: EntityData,
297
315
  sdata: SerializedEntityData,
298
316
  selectors: Selector[],
299
317
  guid: string | null = null,
300
- tags: string[] | null = null
318
+ tags: string[] | null = null,
301
319
  ) {
302
- const formattedSelectors = this.formatSelectors(selectors).selectors;
320
+ try {
321
+ const formattedSelectors = this.formatSelectors(selectors).selectors;
303
322
 
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];
323
+ for (const curSelector of formattedSelectors) {
324
+ const type = curSelector.type;
325
+ const typeIsNot = type === '!&' || type === '!|';
326
+ const typeIsOr = type === '|' || type === '!|';
327
+ let pass = !typeIsOr;
328
+ for (const k in curSelector) {
329
+ const key = k as keyof Selector;
330
+ const value = curSelector[key];
312
331
 
313
- if (key === 'type' || value == null) {
314
- continue;
315
- }
332
+ if (key === 'type' || value == null) {
333
+ continue;
334
+ }
316
335
 
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.");
336
+ const clauseNot = key.substring(0, 1) === '!';
337
+ if (key === 'selector' || key === '!selector') {
338
+ const tmpArr = (
339
+ Array.isArray(value) ? value : [value]
340
+ ) as Selector[];
341
+ pass = xor(
342
+ this.checkData(data, sdata, tmpArr, guid, tags),
343
+ xor(typeIsNot, clauseNot),
344
+ );
327
345
  } 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
- }
346
+ if (key === 'qref' || key === '!qref') {
347
+ throw new Error("Can't use checkData on qref clauses.");
348
+ } else {
349
+ // Check if it doesn't pass any for &, check if it passes any for |.
350
+ for (const curValue of value) {
351
+ if (curValue == null) {
352
+ continue;
353
+ }
334
354
 
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
355
  const propName = (curValue as string[])[0];
343
356
 
344
357
  // Unserialize the data for this variable.
@@ -356,7 +369,7 @@ export default abstract class NymphDriver {
356
369
  case '!tag':
357
370
  pass = xor(
358
371
  (tags ?? []).indexOf(propName) !== -1,
359
- xor(typeIsNot, clauseNot)
372
+ xor(typeIsNot, clauseNot),
360
373
  );
361
374
  break;
362
375
  case 'defined':
@@ -367,7 +380,7 @@ export default abstract class NymphDriver {
367
380
  case '!truthy':
368
381
  pass = xor(
369
382
  propName in data && data[propName],
370
- xor(typeIsNot, clauseNot)
383
+ xor(typeIsNot, clauseNot),
371
384
  );
372
385
  break;
373
386
  case 'ref':
@@ -377,9 +390,9 @@ export default abstract class NymphDriver {
377
390
  propName in data &&
378
391
  this.entityReferenceSearch(
379
392
  data[propName],
380
- testRefValue
393
+ testRefValue,
381
394
  ),
382
- xor(typeIsNot, clauseNot)
395
+ xor(typeIsNot, clauseNot),
383
396
  );
384
397
  break;
385
398
  case 'equal':
@@ -391,7 +404,7 @@ export default abstract class NymphDriver {
391
404
  propName in data &&
392
405
  JSON.stringify(data[propName]) ===
393
406
  JSON.stringify(testEqualValue),
394
- xor(typeIsNot, clauseNot)
407
+ xor(typeIsNot, clauseNot),
395
408
  );
396
409
  break;
397
410
  case 'contain':
@@ -402,9 +415,9 @@ export default abstract class NymphDriver {
402
415
  pass = xor(
403
416
  propName in data &&
404
417
  JSON.stringify(data[propName]).indexOf(
405
- JSON.stringify(testContainValue)
418
+ JSON.stringify(testContainValue),
406
419
  ) !== -1,
407
- xor(typeIsNot, clauseNot)
420
+ xor(typeIsNot, clauseNot),
408
421
  );
409
422
  break;
410
423
  case 'like':
@@ -419,9 +432,9 @@ export default abstract class NymphDriver {
419
432
  escapeRegExp(testLikeValue)
420
433
  .replace('%', () => '.*')
421
434
  .replace('_', () => '.') +
422
- '$'
435
+ '$',
423
436
  ).test(data[propName]),
424
- xor(typeIsNot, clauseNot)
437
+ xor(typeIsNot, clauseNot),
425
438
  );
426
439
  break;
427
440
  case 'match':
@@ -433,7 +446,7 @@ export default abstract class NymphDriver {
433
446
  pass = xor(
434
447
  propName in data &&
435
448
  this.posixRegexMatch(testMatchValue, data[propName]),
436
- xor(typeIsNot, clauseNot)
449
+ xor(typeIsNot, clauseNot),
437
450
  );
438
451
  break;
439
452
  case 'imatch':
@@ -447,9 +460,9 @@ export default abstract class NymphDriver {
447
460
  this.posixRegexMatch(
448
461
  testIMatchValue,
449
462
  data[propName],
450
- true
463
+ true,
451
464
  ),
452
- xor(typeIsNot, clauseNot)
465
+ xor(typeIsNot, clauseNot),
453
466
  );
454
467
  break;
455
468
  case 'gt':
@@ -459,7 +472,7 @@ export default abstract class NymphDriver {
459
472
  )[1] as number;
460
473
  pass = xor(
461
474
  propName in data && data[propName] > testGTValue,
462
- xor(typeIsNot, clauseNot)
475
+ xor(typeIsNot, clauseNot),
463
476
  );
464
477
  break;
465
478
  case 'gte':
@@ -469,7 +482,7 @@ export default abstract class NymphDriver {
469
482
  )[1] as number;
470
483
  pass = xor(
471
484
  propName in data && data[propName] >= testGTEValue,
472
- xor(typeIsNot, clauseNot)
485
+ xor(typeIsNot, clauseNot),
473
486
  );
474
487
  break;
475
488
  case 'lt':
@@ -479,7 +492,7 @@ export default abstract class NymphDriver {
479
492
  )[1] as number;
480
493
  pass = xor(
481
494
  propName in data && data[propName] < testLTValue,
482
- xor(typeIsNot, clauseNot)
495
+ xor(typeIsNot, clauseNot),
483
496
  );
484
497
  break;
485
498
  case 'lte':
@@ -489,26 +502,33 @@ export default abstract class NymphDriver {
489
502
  )[1] as number;
490
503
  pass = xor(
491
504
  propName in data && data[propName] <= testLTEValue,
492
- xor(typeIsNot, clauseNot)
505
+ xor(typeIsNot, clauseNot),
493
506
  );
494
507
  break;
495
508
  }
496
- }
497
- if (!xor(typeIsOr, pass)) {
498
- break;
509
+
510
+ if (!xor(typeIsOr, pass)) {
511
+ break;
512
+ }
499
513
  }
500
514
  }
501
515
  }
516
+ if (!xor(typeIsOr, pass)) {
517
+ break;
518
+ }
502
519
  }
503
- if (!xor(typeIsOr, pass)) {
504
- break;
520
+ if (!pass) {
521
+ return false;
505
522
  }
506
523
  }
507
- if (!pass) {
508
- return false;
509
- }
524
+ return true;
525
+ } catch (e: any) {
526
+ this.nymph.config.debugError(
527
+ 'nymph',
528
+ `Failed to check entity data: ${e}`,
529
+ );
530
+ throw e;
510
531
  }
511
- return true;
512
532
  }
513
533
 
514
534
  /**
@@ -547,7 +567,7 @@ export default abstract class NymphDriver {
547
567
  | EntityInterface
548
568
  | EntityReference
549
569
  | string
550
- | (EntityInterface | EntityReference | string)[]
570
+ | (EntityInterface | EntityReference | string)[],
551
571
  ) {
552
572
  // Get the GUID, if the passed $entity is an object.
553
573
  const guids: string[] = [];
@@ -590,7 +610,7 @@ export default abstract class NymphDriver {
590
610
 
591
611
  public formatSelectors(
592
612
  selectors: Selector[],
593
- options: Options = {}
613
+ options: Options = {},
594
614
  ): {
595
615
  selectors: FormattedSelector[];
596
616
  qrefs: [Options, ...FormattedSelector[]][];
@@ -625,10 +645,9 @@ export default abstract class NymphDriver {
625
645
  for (let i = 0; i < tmpArr.length; i++) {
626
646
  const name = tmpArr[i][0];
627
647
  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');
648
+ const QrefEntityClass = qrefOptions.class
649
+ ? this.nymph.getEntityClass(qrefOptions.class)
650
+ : this.nymph.getEntityClass('Entity');
632
651
  const newOptions = {
633
652
  ...qrefOptions,
634
653
  class: QrefEntityClass,
@@ -637,7 +656,7 @@ export default abstract class NymphDriver {
637
656
  const newSelectors = this.formatSelectors(qrefSelectors, options);
638
657
  qrefs.push(
639
658
  [newOptions, ...newSelectors.selectors],
640
- ...newSelectors.qrefs
659
+ ...newSelectors.qrefs,
641
660
  );
642
661
  formatArr[i] = [name, [newOptions, ...newSelectors.selectors]];
643
662
  }
@@ -685,12 +704,12 @@ export default abstract class NymphDriver {
685
704
 
686
705
  protected iterateSelectorsForQuery(
687
706
  selectors: FormattedSelector[],
688
- callback: (
689
- key: string,
690
- value: any,
691
- typeIsOr: boolean,
692
- typeIsNot: boolean
693
- ) => string
707
+ callback: (data: {
708
+ key: string;
709
+ value: any;
710
+ typeIsOr: boolean;
711
+ typeIsNot: boolean;
712
+ }) => string,
694
713
  ) {
695
714
  const queryParts = [];
696
715
  for (const curSelector of selectors) {
@@ -703,7 +722,7 @@ export default abstract class NymphDriver {
703
722
  if (key === 'type') {
704
723
  continue;
705
724
  }
706
- let curQuery = callback(key, value, typeIsOr, typeIsNot);
725
+ let curQuery = callback({ key, value, typeIsOr, typeIsNot });
707
726
  if (curQuery) {
708
727
  if (curSelectorQuery) {
709
728
  curSelectorQuery += typeIsOr ? ' OR ' : ' AND ';
@@ -719,71 +738,14 @@ export default abstract class NymphDriver {
719
738
  return queryParts;
720
739
  }
721
740
 
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>(
741
+ protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
780
742
  options: Options<T> & { return: 'count' },
781
743
  selectors: Selector[],
782
- performQueryCallback: (
783
- options: Options<T>,
784
- formattedSelectors: FormattedSelector[],
785
- etype: string
786
- ) => {
744
+ performQueryCallback: (data: {
745
+ options: Options<T>;
746
+ selectors: FormattedSelector[];
747
+ etype: string;
748
+ }) => {
787
749
  result: any;
788
750
  },
789
751
  rowFetchCallback: () => any,
@@ -798,16 +760,16 @@ export default abstract class NymphDriver {
798
760
  getDataNameAndSValueCallback: (row: any) => {
799
761
  name: string;
800
762
  svalue: string;
801
- }
763
+ },
802
764
  ): { result: any; process: () => number | Error };
803
- protected getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
765
+ protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
804
766
  options: Options<T> & { return: 'guid' },
805
767
  selectors: Selector[],
806
- performQueryCallback: (
807
- options: Options<T>,
808
- formattedSelectors: FormattedSelector[],
809
- etype: string
810
- ) => {
768
+ performQueryCallback: (data: {
769
+ options: Options<T>;
770
+ selectors: FormattedSelector[];
771
+ etype: string;
772
+ }) => {
811
773
  result: any;
812
774
  },
813
775
  rowFetchCallback: () => any,
@@ -822,16 +784,16 @@ export default abstract class NymphDriver {
822
784
  getDataNameAndSValueCallback: (row: any) => {
823
785
  name: string;
824
786
  svalue: string;
825
- }
787
+ },
826
788
  ): { result: any; process: () => string[] | Error };
827
- protected getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
789
+ protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
828
790
  options: Options<T>,
829
791
  selectors: Selector[],
830
- performQueryCallback: (
831
- options: Options<T>,
832
- formattedSelectors: FormattedSelector[],
833
- etype: string
834
- ) => {
792
+ performQueryCallback: (data: {
793
+ options: Options<T>;
794
+ selectors: FormattedSelector[];
795
+ etype: string;
796
+ }) => {
835
797
  result: any;
836
798
  },
837
799
  rowFetchCallback: () => any,
@@ -846,16 +808,19 @@ export default abstract class NymphDriver {
846
808
  getDataNameAndSValueCallback: (row: any) => {
847
809
  name: string;
848
810
  svalue: string;
849
- }
850
- ): { result: any; process: () => ReturnType<T['factorySync']>[] | Error };
851
- protected getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
811
+ },
812
+ ): {
813
+ result: any;
814
+ process: () => EntityInstanceType<T>[] | Error;
815
+ };
816
+ protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
852
817
  options: Options<T>,
853
818
  selectors: Selector[],
854
- performQueryCallback: (
855
- options: Options<T>,
856
- formattedSelectors: FormattedSelector[],
857
- etype: string
858
- ) => {
819
+ performQueryCallback: (data: {
820
+ options: Options<T>;
821
+ selectors: FormattedSelector[];
822
+ etype: string;
823
+ }) => {
859
824
  result: any;
860
825
  },
861
826
  rowFetchCallback: () => any,
@@ -870,10 +835,10 @@ export default abstract class NymphDriver {
870
835
  getDataNameAndSValueCallback: (row: any) => {
871
836
  name: string;
872
837
  svalue: string;
873
- }
838
+ },
874
839
  ): {
875
840
  result: any;
876
- process: () => ReturnType<T['factorySync']>[] | string[] | number | Error;
841
+ process: () => EntityInstanceType<T>[] | string[] | number | Error;
877
842
  } {
878
843
  if (!this.isConnected()) {
879
844
  throw new UnableToConnectError('not connected to DB');
@@ -886,12 +851,12 @@ export default abstract class NymphDriver {
886
851
  ['&', '!&', '|', '!|'].indexOf(selector.type) === -1
887
852
  ) {
888
853
  throw new InvalidParametersError(
889
- 'Invalid query selector passed: ' + JSON.stringify(selector)
854
+ 'Invalid query selector passed: ' + JSON.stringify(selector),
890
855
  );
891
856
  }
892
857
  }
893
858
 
894
- let entities: ReturnType<T['factorySync']>[] | string[] = [];
859
+ let entities: EntityInstanceType<T>[] | string[] = [];
895
860
  let count = 0;
896
861
  const EntityClass =
897
862
  options.class ?? (this.nymph.getEntityClass('Entity') as T);
@@ -901,6 +866,7 @@ export default abstract class NymphDriver {
901
866
  if (
902
867
  this.nymph.config.cache &&
903
868
  !options.skipCache &&
869
+ selectors.length &&
904
870
  'guid' in selectors[0] &&
905
871
  typeof selectors[0].guid === 'number'
906
872
  ) {
@@ -912,10 +878,10 @@ export default abstract class NymphDriver {
912
878
  (Object.keys(selectors[0]).length === 2 ||
913
879
  (Object.keys(selectors[0]).length === 3 && 'tag' in selectors[0]))
914
880
  ) {
915
- const entity = this.pullCache<ReturnType<T['factorySync']>>(
881
+ const entity = this.pullCache<EntityInstanceType<T>>(
916
882
  selectors[0]['guid'],
917
883
  EntityClass.class,
918
- !!options.skipAc
884
+ !!options.skipAc,
919
885
  );
920
886
  if (
921
887
  entity != null &&
@@ -924,7 +890,7 @@ export default abstract class NymphDriver {
924
890
  entity.hasTag(
925
891
  ...(Array.isArray(selectors[0].tag)
926
892
  ? selectors[0].tag
927
- : [selectors[0].tag])
893
+ : [selectors[0].tag]),
928
894
  ))
929
895
  ) {
930
896
  const guid = entity.guid;
@@ -946,15 +912,16 @@ export default abstract class NymphDriver {
946
912
  try {
947
913
  const formattedSelectors = this.formatSelectors(selectors, options);
948
914
  this.nymph.runQueryCallbacks(options, formattedSelectors.selectors);
949
- for (let qref of formattedSelectors.qrefs) {
950
- const [options, ...selectors] = qref;
915
+ for (let i = 0; i < formattedSelectors.qrefs.length; i++) {
916
+ const [options, ...selectors] = formattedSelectors.qrefs[i];
951
917
  this.nymph.runQueryCallbacks(options, selectors);
918
+ formattedSelectors.qrefs[i] = [options, ...selectors];
952
919
  }
953
- const { result } = performQueryCallback(
920
+ const { result } = performQueryCallback({
954
921
  options,
955
- formattedSelectors.selectors,
956
- etype
957
- );
922
+ selectors: formattedSelectors.selectors,
923
+ etype,
924
+ });
958
925
 
959
926
  return {
960
927
  result,
@@ -994,9 +961,7 @@ export default abstract class NymphDriver {
994
961
  // Make sure that $row is incremented :)
995
962
  row = rowFetchCallback();
996
963
  }
997
- const entity = EntityClass.factorySync() as ReturnType<
998
- T['factorySync']
999
- >;
964
+ const entity = EntityClass.factorySync() as EntityInstanceType<T>;
1000
965
  entity.$nymph = this.nymph;
1001
966
  if (options.skipAc != null) {
1002
967
  entity.$useSkipAc(!!options.skipAc);
@@ -1036,34 +1001,37 @@ export default abstract class NymphDriver {
1036
1001
 
1037
1002
  protected async saveEntityRowLike(
1038
1003
  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>,
1004
+ saveNewEntityCallback: (data: {
1005
+ entity: EntityInterface;
1006
+ guid: string;
1007
+ tags: string[];
1008
+ data: EntityData;
1009
+ sdata: SerializedEntityData;
1010
+ uniques: string[];
1011
+ cdate: number;
1012
+ etype: string;
1013
+ }) => Promise<boolean>,
1014
+ saveExistingEntityCallback: (data: {
1015
+ entity: EntityInterface;
1016
+ guid: string;
1017
+ tags: string[];
1018
+ data: EntityData;
1019
+ sdata: SerializedEntityData;
1020
+ uniques: string[];
1021
+ mdate: number;
1022
+ etype: string;
1023
+ }) => Promise<boolean>,
1057
1024
  startTransactionCallback: (() => Promise<void>) | null = null,
1058
1025
  commitTransactionCallback:
1059
1026
  | ((success: boolean) => Promise<boolean>)
1060
- | null = null
1027
+ | null = null,
1061
1028
  ) {
1062
1029
  // Get a modified date.
1063
1030
  const mdate = Date.now();
1064
1031
  const tags = difference(entity.tags, ['']);
1065
1032
  const data = entity.$getData();
1066
1033
  const sdata = entity.$getSData();
1034
+ const uniques = await entity.$getUniques();
1067
1035
  const EntityClass = entity.constructor as EntityConstructor;
1068
1036
  const etype = EntityClass.ETYPE;
1069
1037
  if (startTransactionCallback) {
@@ -1073,15 +1041,16 @@ export default abstract class NymphDriver {
1073
1041
  if (entity.guid == null) {
1074
1042
  const cdate = mdate;
1075
1043
  const newId = guid();
1076
- success = await saveNewEntityCallback(
1044
+ success = await saveNewEntityCallback({
1077
1045
  entity,
1078
- newId,
1046
+ guid: newId,
1079
1047
  tags,
1080
1048
  data,
1081
1049
  sdata,
1050
+ uniques,
1082
1051
  cdate,
1083
- etype
1084
- );
1052
+ etype,
1053
+ });
1085
1054
  if (success) {
1086
1055
  entity.guid = newId;
1087
1056
  entity.cdate = cdate;
@@ -1092,15 +1061,16 @@ export default abstract class NymphDriver {
1092
1061
  if (this.nymph.config.cache) {
1093
1062
  this.cleanCache(entity.guid);
1094
1063
  }
1095
- success = await saveExistingEntityCallback(
1064
+ success = await saveExistingEntityCallback({
1096
1065
  entity,
1097
- entity.guid,
1066
+ guid: entity.guid,
1098
1067
  tags,
1099
1068
  data,
1100
1069
  sdata,
1070
+ uniques,
1101
1071
  mdate,
1102
- etype
1103
- );
1072
+ etype,
1073
+ });
1104
1074
  if (success) {
1105
1075
  entity.mdate = mdate;
1106
1076
  }
@@ -1116,7 +1086,7 @@ export default abstract class NymphDriver {
1116
1086
  entity.mdate as number,
1117
1087
  entity.tags,
1118
1088
  data,
1119
- sdata
1089
+ sdata,
1120
1090
  );
1121
1091
  }
1122
1092
  return success;
@@ -1130,10 +1100,10 @@ export default abstract class NymphDriver {
1130
1100
  * @param useSkipAc Whether to tell the entity to use skip_ac.
1131
1101
  * @returns The entity or null if it's not cached.
1132
1102
  */
1133
- protected pullCache<T extends EntityInterface = EntityInterface>(
1103
+ protected pullCache<T extends Entity>(
1134
1104
  guid: string,
1135
1105
  className: string,
1136
- useSkipAc = false
1106
+ useSkipAc = false,
1137
1107
  ): T | null {
1138
1108
  // Increment the entity access count.
1139
1109
  if (!(guid in this.entityCount)) {
@@ -1151,7 +1121,7 @@ export default abstract class NymphDriver {
1151
1121
  entity.tags = this.entityCache[guid]['tags'];
1152
1122
  entity.$putData(
1153
1123
  this.entityCache[guid]['data'],
1154
- this.entityCache[guid]['sdata']
1124
+ this.entityCache[guid]['sdata'],
1155
1125
  );
1156
1126
  return entity;
1157
1127
  }
@@ -1175,7 +1145,7 @@ export default abstract class NymphDriver {
1175
1145
  mdate: number,
1176
1146
  tags: string[],
1177
1147
  data: EntityData,
1178
- sdata: SerializedEntityData
1148
+ sdata: SerializedEntityData,
1179
1149
  ) {
1180
1150
  if (guid == null) {
1181
1151
  return;
@@ -1204,7 +1174,7 @@ export default abstract class NymphDriver {
1204
1174
  // Find which entity has been accessed the least.
1205
1175
  const least =
1206
1176
  Object.entries(this.entityCount)
1207
- .sort(([guidA, countA], [guidB, countB]) => countA - countB)
1177
+ .sort(([_guidA, countA], [_guidB, countB]) => countA - countB)
1208
1178
  .pop()?.[0] ?? null;
1209
1179
  if (least == null) {
1210
1180
  // This should never happen.