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

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 +382 -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 +499 -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 +572 -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
@@ -10,25 +10,45 @@ const strtotime_1 = __importDefault(require("locutus/php/datetime/strtotime"));
10
10
  const guid_1 = require("@nymphjs/guid");
11
11
  const errors_1 = require("../errors");
12
12
  const utils_1 = require("../utils");
13
+ // from: https://stackoverflow.com/a/6969486/664915
13
14
  function escapeRegExp(string) {
14
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
15
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
15
16
  }
17
+ /**
18
+ * A Nymph database driver.
19
+ */
16
20
  class NymphDriver {
17
- constructor() {
18
- this.nymph = new Proxy({}, {
19
- get() {
20
- throw new Error('Attempted access of Nymph instance before driver initialization!');
21
- },
22
- });
23
- this.entityCache = {};
24
- this.entityCount = {};
25
- this.putDataCounter = 0;
26
- }
21
+ nymph = new Proxy({}, {
22
+ get() {
23
+ throw new Error('Attempted access of Nymph instance before driver initialization!');
24
+ },
25
+ });
26
+ /**
27
+ * A cache to make entity retrieval faster.
28
+ */
29
+ entityCache = {};
30
+ /**
31
+ * A counter for the entity cache to determine the most accessed entities.
32
+ */
33
+ entityCount = {};
34
+ /**
35
+ * Protect against infinite loops.
36
+ */
37
+ putDataCounter = 0;
38
+ /**
39
+ * Initialize the Nymph driver.
40
+ *
41
+ * This is meant to be called internally by Nymph. Don't call this directly.
42
+ *
43
+ * @param nymph The Nymph instance.
44
+ */
27
45
  init(nymph) {
28
46
  this.nymph = nymph;
29
47
  }
30
48
  posixRegexMatch(pattern, subject, caseInsensitive = false) {
31
49
  const posixClasses = [
50
+ // '[[:<:]]',
51
+ // '[[:>:]]',
32
52
  '[:alnum:]',
33
53
  '[:alpha:]',
34
54
  '[:ascii:]',
@@ -45,17 +65,21 @@ class NymphDriver {
45
65
  '[:xdigit:]',
46
66
  ];
47
67
  const jsClasses = [
68
+ // '\b(?=\w)',
69
+ // '(?<=\w)\b',
48
70
  '[A-Za-z0-9]',
49
71
  '[A-Za-z]',
50
72
  '[\x00-\x7F]',
51
- '[ \t]',
52
- '[\x00-\x1F\x7F]',
53
- '[0-9]',
54
- '[\x21-\x7E]',
73
+ '[ \t]', // '\s',
74
+ '[\x00-\x1F\x7F]', // '[\000\001\002\003\004\005\006\007\008\009\010\011\012\013\014'.
75
+ // '\015\016\017\018\019\020\021\022\023\024\025\026\027\028\029'.
76
+ // '\030\031\032\033\034\035\036\037\177]',
77
+ '[0-9]', // '\d',
78
+ '[\x21-\x7E]', // '[A-Za-z0-9!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
55
79
  '[a-z]',
56
- '[\x20-\x7E]',
57
- '[!"#$%&\'()*+,-./:;<=>?@[\\\\\\]^_‘{|}~]',
58
- '[ \t\r\n\v\f]',
80
+ '[\x20-\x7E]', // '[A-Za-z0-9!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
81
+ '[!"#$%&\'()*+,-./:;<=>?@[\\\\\\]^_‘{|}~]', // '[!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
82
+ '[ \t\r\n\v\f]', // '[\t\n\x0B\f\r ]',
59
83
  '[A-Z]',
60
84
  '[A-Za-z0-9_]',
61
85
  '[0-9A-Fa-f]',
@@ -73,9 +97,9 @@ class NymphDriver {
73
97
  if (!fhandle) {
74
98
  throw new errors_1.InvalidParametersError('Provided filename is not writeable.');
75
99
  }
76
- await this.exportEntities((line) => {
77
- fs_1.default.writeSync(fhandle, `${line}\n`);
78
- });
100
+ for await (let entry of this.exportDataIterator()) {
101
+ fs_1.default.writeSync(fhandle, entry.content);
102
+ }
79
103
  fs_1.default.closeSync(fhandle);
80
104
  }
81
105
  catch (e) {
@@ -84,98 +108,135 @@ class NymphDriver {
84
108
  return true;
85
109
  }
86
110
  async exportPrint() {
87
- await this.exportEntities((line) => {
88
- console.log(line);
89
- });
111
+ for await (let entry of this.exportDataIterator()) {
112
+ console.log(entry.content);
113
+ }
90
114
  return true;
91
115
  }
92
- async importFromFile(filename, saveEntityCallback, saveUIDCallback, startTransactionCallback = null, commitTransactionCallback = null) {
93
- let rl;
94
- try {
95
- rl = new n_readlines_1.default(filename);
96
- }
97
- catch (e) {
98
- throw new errors_1.InvalidParametersError('Provided filename is unreadable.');
116
+ async importDataIterator(lines, transaction) {
117
+ const first = lines[Symbol.iterator]().next();
118
+ if (first.done || first.value !== '#nex2') {
119
+ throw new Error('Tried to import a file that is not a NEX v2 file.');
99
120
  }
100
- let guid = null;
101
- let line = false;
102
- let sdata = {};
103
- let tags = [];
104
- let etype = '__undefined';
105
- if (startTransactionCallback) {
106
- await startTransactionCallback();
121
+ if (transaction) {
122
+ await this.internalTransaction('nymph-import');
107
123
  }
108
- while ((line = rl.next())) {
109
- const text = line.toString('utf8');
110
- if (text.match(/^\s*#/)) {
111
- continue;
112
- }
113
- const entityMatch = text.match(/^\s*{([0-9A-Fa-f]+)}<([-\w_]+)>\[([^\]]*)\]\s*$/);
114
- const propMatch = text.match(/^\s*([^=]+)\s*=\s*(.*\S)\s*$/);
115
- const uidMatch = text.match(/^\s*<([^>]+)>\[(\d+)\]\s*$/);
116
- if (uidMatch) {
117
- await saveUIDCallback(uidMatch[1], Number(uidMatch[2]));
118
- }
119
- else if (entityMatch) {
120
- if (guid) {
121
- await saveEntityCallback(guid, tags, sdata, etype);
122
- guid = null;
123
- tags = [];
124
- sdata = {};
125
- etype = '__undefined';
124
+ try {
125
+ let guid = null;
126
+ let sdata = {};
127
+ let tags = [];
128
+ let etype = '__undefined';
129
+ for (let line of lines) {
130
+ if (line.match(/^\s*#/)) {
131
+ continue;
126
132
  }
127
- guid = entityMatch[1];
128
- etype = entityMatch[2];
129
- tags = entityMatch[3].split(',');
130
- }
131
- else if (propMatch) {
132
- if (guid) {
133
- sdata[propMatch[1]] = propMatch[2];
133
+ const entityMatch = line.match(/^\s*{([0-9A-Fa-f]+)}<([-\w_]+)>\[([^\]]*)\]\s*$/);
134
+ const propMatch = line.match(/^\s*([^=]+)\s*=\s*(.*\S)\s*$/);
135
+ const uidMatch = line.match(/^\s*<([^>]+)>\[(\d+)\]\s*$/);
136
+ if (uidMatch) {
137
+ // Add the UID.
138
+ await this.importUID({
139
+ name: uidMatch[1],
140
+ value: Number(uidMatch[2]),
141
+ });
142
+ }
143
+ else if (entityMatch) {
144
+ // Save the current entity.
145
+ if (guid) {
146
+ const cdate = Number(JSON.parse(sdata.cdate));
147
+ delete sdata.cdate;
148
+ const mdate = Number(JSON.parse(sdata.mdate));
149
+ delete sdata.mdate;
150
+ await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
151
+ guid = null;
152
+ tags = [];
153
+ sdata = {};
154
+ etype = '__undefined';
155
+ }
156
+ // Record the new entity's info.
157
+ guid = entityMatch[1];
158
+ etype = entityMatch[2];
159
+ tags = entityMatch[3].split(',');
160
+ }
161
+ else if (propMatch) {
162
+ // Add the variable to the new entity.
163
+ if (guid) {
164
+ sdata[propMatch[1]] = propMatch[2];
165
+ }
134
166
  }
167
+ // Clear the entity cache.
168
+ this.entityCache = {};
169
+ }
170
+ // Save the last entity.
171
+ if (guid) {
172
+ const cdate = Number(JSON.parse(sdata.cdate));
173
+ delete sdata.cdate;
174
+ const mdate = Number(JSON.parse(sdata.mdate));
175
+ delete sdata.mdate;
176
+ await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
177
+ }
178
+ if (transaction) {
179
+ await this.commit('nymph-import');
135
180
  }
136
- this.entityCache = {};
137
- }
138
- if (guid) {
139
- await saveEntityCallback(guid, tags, sdata, etype);
140
181
  }
141
- if (commitTransactionCallback) {
142
- await commitTransactionCallback();
182
+ catch (e) {
183
+ await this.rollback('nymph-import');
184
+ throw e;
143
185
  }
144
186
  return true;
145
187
  }
146
- checkData(data, sdata, selectors, guid = null, tags = null) {
147
- const formattedSelectors = this.formatSelectors(selectors).selectors;
148
- for (const curSelector of formattedSelectors) {
149
- const type = curSelector.type;
150
- const typeIsNot = type === '!&' || type === '!|';
151
- const typeIsOr = type === '|' || type === '!|';
152
- let pass = !typeIsOr;
153
- for (const k in curSelector) {
154
- const key = k;
155
- const value = curSelector[key];
156
- if (key === 'type' || value == null) {
157
- continue;
158
- }
159
- const clauseNot = key.substr(0, 1) === '!';
160
- if (key === 'selector' || key === '!selector') {
161
- const tmpArr = (Array.isArray(value) ? value : [value]);
162
- pass = (0, utils_1.xor)(this.checkData(data, sdata, tmpArr, guid, tags), (0, utils_1.xor)(typeIsNot, clauseNot));
188
+ async importData(text, transaction) {
189
+ return await this.importDataIterator(text.split('\n'), transaction);
190
+ }
191
+ async import(filename, transaction) {
192
+ let rl;
193
+ try {
194
+ rl = new n_readlines_1.default(filename);
195
+ }
196
+ catch (e) {
197
+ throw new errors_1.InvalidParametersError('Provided filename is unreadable.');
198
+ }
199
+ const lines = {
200
+ *[Symbol.iterator]() {
201
+ let line;
202
+ while ((line = rl.next())) {
203
+ yield line.toString('utf8');
163
204
  }
164
- else {
165
- if (key === 'qref' || key === '!qref') {
166
- throw new Error("Can't use checkData on qref clauses.");
205
+ },
206
+ };
207
+ return await this.importDataIterator(lines, transaction);
208
+ }
209
+ checkData(data, sdata, selectors, guid = null, tags = null) {
210
+ try {
211
+ const formattedSelectors = this.formatSelectors(selectors).selectors;
212
+ for (const curSelector of formattedSelectors) {
213
+ const type = curSelector.type;
214
+ const typeIsNot = type === '!&' || type === '!|';
215
+ const typeIsOr = type === '|' || type === '!|';
216
+ let pass = !typeIsOr;
217
+ for (const k in curSelector) {
218
+ const key = k;
219
+ const value = curSelector[key];
220
+ if (key === 'type' || value == null) {
221
+ continue;
222
+ }
223
+ const clauseNot = key.substring(0, 1) === '!';
224
+ if (key === 'selector' || key === '!selector') {
225
+ const tmpArr = (Array.isArray(value) ? value : [value]);
226
+ pass = (0, utils_1.xor)(this.checkData(data, sdata, tmpArr, guid, tags), (0, utils_1.xor)(typeIsNot, clauseNot));
167
227
  }
168
228
  else {
169
- for (const curValue of value) {
170
- if (curValue == null) {
171
- continue;
172
- }
173
- if (((key === 'guid' || key === '!guid') && guid == null) ||
174
- ((key === 'tag' || key === '!tag') && tags == null)) {
175
- pass = true;
176
- }
177
- else {
229
+ if (key === 'qref' || key === '!qref') {
230
+ throw new Error("Can't use checkData on qref clauses.");
231
+ }
232
+ else {
233
+ // Check if it doesn't pass any for &, check if it passes any for |.
234
+ for (const curValue of value) {
235
+ if (curValue == null) {
236
+ continue;
237
+ }
178
238
  const propName = curValue[0];
239
+ // Unserialize the data for this variable.
179
240
  if (propName in sdata) {
180
241
  data[propName] = JSON.parse(sdata[propName]);
181
242
  delete sdata[propName];
@@ -229,12 +290,14 @@ class NymphDriver {
229
290
  case 'match':
230
291
  case '!match':
231
292
  const testMatchValue = curValue[1];
293
+ // Convert a POSIX regex to a JS regex.
232
294
  pass = (0, utils_1.xor)(propName in data &&
233
295
  this.posixRegexMatch(testMatchValue, data[propName]), (0, utils_1.xor)(typeIsNot, clauseNot));
234
296
  break;
235
297
  case 'imatch':
236
298
  case '!imatch':
237
299
  const testIMatchValue = curValue[1];
300
+ // Convert a POSIX regex to a JS regex.
238
301
  pass = (0, utils_1.xor)(propName in data &&
239
302
  this.posixRegexMatch(testIMatchValue, data[propName], true), (0, utils_1.xor)(typeIsNot, clauseNot));
240
303
  break;
@@ -259,23 +322,32 @@ class NymphDriver {
259
322
  pass = (0, utils_1.xor)(propName in data && data[propName] <= testLTEValue, (0, utils_1.xor)(typeIsNot, clauseNot));
260
323
  break;
261
324
  }
262
- }
263
- if (!(0, utils_1.xor)(typeIsOr, pass)) {
264
- break;
325
+ if (!(0, utils_1.xor)(typeIsOr, pass)) {
326
+ break;
327
+ }
265
328
  }
266
329
  }
267
330
  }
331
+ if (!(0, utils_1.xor)(typeIsOr, pass)) {
332
+ break;
333
+ }
268
334
  }
269
- if (!(0, utils_1.xor)(typeIsOr, pass)) {
270
- break;
335
+ if (!pass) {
336
+ return false;
271
337
  }
272
338
  }
273
- if (!pass) {
274
- return false;
275
- }
339
+ return true;
340
+ }
341
+ catch (e) {
342
+ this.nymph.config.debugError('nymph', `Failed to check entity data: ${e}`);
343
+ throw e;
276
344
  }
277
- return true;
278
345
  }
346
+ /**
347
+ * Remove all copies of an entity from the cache.
348
+ *
349
+ * @param guid The GUID of the entity to remove.
350
+ */
279
351
  cleanCache(guid) {
280
352
  delete this.entityCache[guid];
281
353
  }
@@ -292,7 +364,15 @@ class NymphDriver {
292
364
  }
293
365
  return ret;
294
366
  }
367
+ /**
368
+ * Search through a value for an entity reference.
369
+ *
370
+ * @param value Any value to search.
371
+ * @param entity An entity, GUID, or array of either to search for.
372
+ * @returns True if the reference is found, false otherwise.
373
+ */
295
374
  entityReferenceSearch(value, entity) {
375
+ // Get the GUID, if the passed $entity is an object.
296
376
  const guids = [];
297
377
  if (Array.isArray(entity)) {
298
378
  if (entity[0] === 'nymph_entity_reference') {
@@ -323,6 +403,7 @@ class NymphDriver {
323
403
  guids.indexOf(value[1]) !== -1) {
324
404
  return true;
325
405
  }
406
+ // Search through arrays and objects looking for the reference.
326
407
  if (Array.isArray(value) || typeof value === 'object') {
327
408
  for (const key in value) {
328
409
  if (this.entityReferenceSearch(value[key], guids)) {
@@ -356,9 +437,9 @@ class NymphDriver {
356
437
  for (let i = 0; i < tmpArr.length; i++) {
357
438
  const name = tmpArr[i][0];
358
439
  const [qrefOptions, ...qrefSelectors] = tmpArr[i][1];
359
- const QrefEntityClass = typeof qrefOptions.class === 'string'
440
+ const QrefEntityClass = qrefOptions.class
360
441
  ? this.nymph.getEntityClass(qrefOptions.class)
361
- : qrefOptions.class ?? this.nymph.getEntityClass('Entity');
442
+ : this.nymph.getEntityClass('Entity');
362
443
  const newOptions = {
363
444
  ...qrefOptions,
364
445
  class: QrefEntityClass,
@@ -377,19 +458,23 @@ class NymphDriver {
377
458
  qrefs.push(...newSelectors.qrefs);
378
459
  }
379
460
  else if (!Array.isArray(value)) {
461
+ // @ts-ignore: ts doesn't know what value is here.
380
462
  newSelector[key] = [[value]];
381
463
  }
382
464
  else if (!Array.isArray(value[0])) {
383
465
  if (value.length === 3 &&
384
466
  value[1] == null &&
385
467
  typeof value[2] === 'string') {
468
+ // @ts-ignore: ts doesn't know what value is here.
386
469
  newSelector[key] = [[value[0], (0, strtotime_1.default)(value[2]) * 1000]];
387
470
  }
388
471
  else {
472
+ // @ts-ignore: ts doesn't know what value is here.
389
473
  newSelector[key] = [value];
390
474
  }
391
475
  }
392
476
  else {
477
+ // @ts-ignore: ts doesn't know what value is here.
393
478
  newSelector[key] = value.map((curValue) => {
394
479
  if (curValue.length === 3 &&
395
480
  curValue[1] == null &&
@@ -416,7 +501,7 @@ class NymphDriver {
416
501
  if (key === 'type') {
417
502
  continue;
418
503
  }
419
- let curQuery = callback(key, value, typeIsOr, typeIsNot);
504
+ let curQuery = callback({ key, value, typeIsOr, typeIsNot });
420
505
  if (curQuery) {
421
506
  if (curSelectorQuery) {
422
507
  curSelectorQuery += typeIsOr ? ' OR ' : ' AND ';
@@ -430,21 +515,7 @@ class NymphDriver {
430
515
  }
431
516
  return queryParts;
432
517
  }
433
- getEntitySync(options = {}, ...selectors) {
434
- if (typeof selectors[0] === 'string') {
435
- selectors = [{ type: '&', guid: selectors[0] }];
436
- }
437
- options.limit = 1;
438
- const entities = this.getEntitiesSync(options, ...selectors);
439
- if (options.return === 'count') {
440
- return entities;
441
- }
442
- if (!entities || !entities.length) {
443
- return null;
444
- }
445
- return entities[0];
446
- }
447
- getEntitesRowLike(options, selectors, performQueryCallback, rowFetchCallback, freeResultCallback, getCountCallback, getGUIDCallback, getTagsAndDatesCallback, getDataNameAndSValueCallback) {
518
+ getEntitiesRowLike(options, selectors, performQueryCallback, rowFetchCallback, freeResultCallback, getCountCallback, getGUIDCallback, getTagsAndDatesCallback, getDataNameAndSValueCallback) {
448
519
  if (!this.isConnected()) {
449
520
  throw new errors_1.UnableToConnectError('not connected to DB');
450
521
  }
@@ -460,10 +531,14 @@ class NymphDriver {
460
531
  let count = 0;
461
532
  const EntityClass = options.class ?? this.nymph.getEntityClass('Entity');
462
533
  const etype = EntityClass.ETYPE;
534
+ // Check if the requested entity is cached.
463
535
  if (this.nymph.config.cache &&
464
536
  !options.skipCache &&
537
+ selectors.length &&
465
538
  'guid' in selectors[0] &&
466
539
  typeof selectors[0].guid === 'number') {
540
+ // Only safe to use the cache option with no other selectors than a GUID
541
+ // and tags.
467
542
  if (selectors.length === 1 &&
468
543
  selectors[0].type === '&' &&
469
544
  (Object.keys(selectors[0]).length === 2 ||
@@ -492,11 +567,16 @@ class NymphDriver {
492
567
  try {
493
568
  const formattedSelectors = this.formatSelectors(selectors, options);
494
569
  this.nymph.runQueryCallbacks(options, formattedSelectors.selectors);
495
- for (let qref of formattedSelectors.qrefs) {
496
- const [options, ...selectors] = qref;
570
+ for (let i = 0; i < formattedSelectors.qrefs.length; i++) {
571
+ const [options, ...selectors] = formattedSelectors.qrefs[i];
497
572
  this.nymph.runQueryCallbacks(options, selectors);
573
+ formattedSelectors.qrefs[i] = [options, ...selectors];
498
574
  }
499
- const { result } = performQueryCallback(options, formattedSelectors.selectors, etype);
575
+ const { result } = performQueryCallback({
576
+ options,
577
+ selectors: formattedSelectors.selectors,
578
+ etype,
579
+ });
500
580
  return {
501
581
  result,
502
582
  process: () => {
@@ -521,9 +601,13 @@ class NymphDriver {
521
601
  const cdate = tagsAndDates.cdate;
522
602
  const mdate = tagsAndDates.mdate;
523
603
  let dataNameAndSValue = getDataNameAndSValueCallback(row);
604
+ // Data.
524
605
  const data = {};
606
+ // Serialized data.
525
607
  const sdata = {};
526
608
  if (dataNameAndSValue.name !== '') {
609
+ // This do will keep going and adding the data until the
610
+ // next entity is reached. $row will end on the next entity.
527
611
  do {
528
612
  dataNameAndSValue = getDataNameAndSValueCallback(row);
529
613
  sdata[dataNameAndSValue.name] = dataNameAndSValue.svalue;
@@ -531,6 +615,7 @@ class NymphDriver {
531
615
  } while (row != null && getGUIDCallback(row) === guid);
532
616
  }
533
617
  else {
618
+ // Make sure that $row is incremented :)
534
619
  row = rowFetchCallback();
535
620
  }
536
621
  const entity = EntityClass.factorySync();
@@ -551,6 +636,7 @@ class NymphDriver {
551
636
  if (this.nymph.config.cache) {
552
637
  this.pushCache(guid, cdate, mdate, tags, data, sdata);
553
638
  }
639
+ // @ts-ignore: ts doesn't know the return type here.
554
640
  entities.push(entity);
555
641
  }
556
642
  }
@@ -569,10 +655,12 @@ class NymphDriver {
569
655
  }
570
656
  }
571
657
  async saveEntityRowLike(entity, saveNewEntityCallback, saveExistingEntityCallback, startTransactionCallback = null, commitTransactionCallback = null) {
658
+ // Get a modified date.
572
659
  const mdate = Date.now();
573
660
  const tags = (0, lodash_1.difference)(entity.tags, ['']);
574
661
  const data = entity.$getData();
575
662
  const sdata = entity.$getSData();
663
+ const uniques = await entity.$getUniques();
576
664
  const EntityClass = entity.constructor;
577
665
  const etype = EntityClass.ETYPE;
578
666
  if (startTransactionCallback) {
@@ -582,7 +670,16 @@ class NymphDriver {
582
670
  if (entity.guid == null) {
583
671
  const cdate = mdate;
584
672
  const newId = (0, guid_1.guid)();
585
- success = await saveNewEntityCallback(entity, newId, tags, data, sdata, cdate, etype);
673
+ success = await saveNewEntityCallback({
674
+ entity,
675
+ guid: newId,
676
+ tags,
677
+ data,
678
+ sdata,
679
+ uniques,
680
+ cdate,
681
+ etype,
682
+ });
586
683
  if (success) {
587
684
  entity.guid = newId;
588
685
  entity.cdate = cdate;
@@ -590,10 +687,20 @@ class NymphDriver {
590
687
  }
591
688
  }
592
689
  else {
690
+ // Removed any cached versions of this entity.
593
691
  if (this.nymph.config.cache) {
594
692
  this.cleanCache(entity.guid);
595
693
  }
596
- success = await saveExistingEntityCallback(entity, entity.guid, tags, data, sdata, mdate, etype);
694
+ success = await saveExistingEntityCallback({
695
+ entity,
696
+ guid: entity.guid,
697
+ tags,
698
+ data,
699
+ sdata,
700
+ uniques,
701
+ mdate,
702
+ etype,
703
+ });
597
704
  if (success) {
598
705
  entity.mdate = mdate;
599
706
  }
@@ -601,12 +708,22 @@ class NymphDriver {
601
708
  if (commitTransactionCallback) {
602
709
  success = await commitTransactionCallback(success);
603
710
  }
711
+ // Cache the entity.
604
712
  if (success && this.nymph.config.cache) {
605
713
  this.pushCache(entity.guid, entity.cdate, entity.mdate, entity.tags, data, sdata);
606
714
  }
607
715
  return success;
608
716
  }
717
+ /**
718
+ * Pull an entity from the cache.
719
+ *
720
+ * @param guid The entity's GUID.
721
+ * @param className The entity's class.
722
+ * @param useSkipAc Whether to tell the entity to use skip_ac.
723
+ * @returns The entity or null if it's not cached.
724
+ */
609
725
  pullCache(guid, className, useSkipAc = false) {
726
+ // Increment the entity access count.
610
727
  if (!(guid in this.entityCount)) {
611
728
  this.entityCount[guid] = 0;
612
729
  }
@@ -626,17 +743,30 @@ class NymphDriver {
626
743
  }
627
744
  return null;
628
745
  }
746
+ /**
747
+ * Push an entity onto the cache.
748
+ *
749
+ * @param guid The entity's GUID.
750
+ * @param cdate The entity's cdate.
751
+ * @param mdate The entity's mdate.
752
+ * @param tags The entity's tags.
753
+ * @param data The entity's data.
754
+ * @param sdata The entity's sdata.
755
+ */
629
756
  pushCache(guid, cdate, mdate, tags, data, sdata) {
630
757
  if (guid == null) {
631
758
  return;
632
759
  }
760
+ // Increment the entity access count.
633
761
  if (!(guid in this.entityCount)) {
634
762
  this.entityCount[guid] = 0;
635
763
  }
636
764
  this.entityCount[guid]++;
765
+ // Check the threshold.
637
766
  if (this.entityCount[guid] < this.nymph.config.cacheThreshold) {
638
767
  return;
639
768
  }
769
+ // Cache the entity.
640
770
  this.entityCache[guid] = {
641
771
  cdate: cdate,
642
772
  mdate: mdate,
@@ -646,12 +776,15 @@ class NymphDriver {
646
776
  };
647
777
  while (this.nymph.config.cacheLimit &&
648
778
  Object.keys(this.entityCache).length >= this.nymph.config.cacheLimit) {
779
+ // Find which entity has been accessed the least.
649
780
  const least = Object.entries(this.entityCount)
650
- .sort(([guidA, countA], [guidB, countB]) => countA - countB)
781
+ .sort(([_guidA, countA], [_guidB, countB]) => countA - countB)
651
782
  .pop()?.[0] ?? null;
652
783
  if (least == null) {
784
+ // This should never happen.
653
785
  return;
654
786
  }
787
+ // Remove it.
655
788
  delete this.entityCache[least];
656
789
  delete this.entityCount[least];
657
790
  }