@nymphjs/nymph 1.0.0-beta.54 → 1.0.0-beta.56

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.
@@ -10,9 +10,13 @@ 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
21
  constructor() {
18
22
  this.nymph = new Proxy({}, {
@@ -20,15 +24,33 @@ class NymphDriver {
20
24
  throw new Error('Attempted access of Nymph instance before driver initialization!');
21
25
  },
22
26
  });
27
+ /**
28
+ * A cache to make entity retrieval faster.
29
+ */
23
30
  this.entityCache = {};
31
+ /**
32
+ * A counter for the entity cache to determine the most accessed entities.
33
+ */
24
34
  this.entityCount = {};
35
+ /**
36
+ * Protect against infinite loops.
37
+ */
25
38
  this.putDataCounter = 0;
26
39
  }
40
+ /**
41
+ * Initialize the Nymph driver.
42
+ *
43
+ * This is meant to be called internally by Nymph. Don't call this directly.
44
+ *
45
+ * @param nymph The Nymph instance.
46
+ */
27
47
  init(nymph) {
28
48
  this.nymph = nymph;
29
49
  }
30
50
  posixRegexMatch(pattern, subject, caseInsensitive = false) {
31
51
  const posixClasses = [
52
+ // '[[:<:]]',
53
+ // '[[:>:]]',
32
54
  '[:alnum:]',
33
55
  '[:alpha:]',
34
56
  '[:ascii:]',
@@ -45,17 +67,21 @@ class NymphDriver {
45
67
  '[:xdigit:]',
46
68
  ];
47
69
  const jsClasses = [
70
+ // '\b(?=\w)',
71
+ // '(?<=\w)\b',
48
72
  '[A-Za-z0-9]',
49
73
  '[A-Za-z]',
50
74
  '[\x00-\x7F]',
51
- '[ \t]',
52
- '[\x00-\x1F\x7F]',
53
- '[0-9]',
54
- '[\x21-\x7E]',
75
+ '[ \t]', // '\s',
76
+ '[\x00-\x1F\x7F]', // '[\000\001\002\003\004\005\006\007\008\009\010\011\012\013\014'.
77
+ // '\015\016\017\018\019\020\021\022\023\024\025\026\027\028\029'.
78
+ // '\030\031\032\033\034\035\036\037\177]',
79
+ '[0-9]', // '\d',
80
+ '[\x21-\x7E]', // '[A-Za-z0-9!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
55
81
  '[a-z]',
56
- '[\x20-\x7E]',
57
- '[!"#$%&\'()*+,-./:;<=>?@[\\\\\\]^_‘{|}~]',
58
- '[ \t\r\n\v\f]',
82
+ '[\x20-\x7E]', // '[A-Za-z0-9!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
83
+ '[!"#$%&\'()*+,-./:;<=>?@[\\\\\\]^_‘{|}~]', // '[!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
84
+ '[ \t\r\n\v\f]', // '[\t\n\x0B\f\r ]',
59
85
  '[A-Z]',
60
86
  '[A-Za-z0-9_]',
61
87
  '[0-9A-Fa-f]',
@@ -114,9 +140,11 @@ class NymphDriver {
114
140
  const propMatch = text.match(/^\s*([^=]+)\s*=\s*(.*\S)\s*$/);
115
141
  const uidMatch = text.match(/^\s*<([^>]+)>\[(\d+)\]\s*$/);
116
142
  if (uidMatch) {
143
+ // Add the UID.
117
144
  await saveUIDCallback(uidMatch[1], Number(uidMatch[2]));
118
145
  }
119
146
  else if (entityMatch) {
147
+ // Save the current entity.
120
148
  if (guid) {
121
149
  await saveEntityCallback(guid, tags, sdata, etype);
122
150
  guid = null;
@@ -124,17 +152,21 @@ class NymphDriver {
124
152
  sdata = {};
125
153
  etype = '__undefined';
126
154
  }
155
+ // Record the new entity's info.
127
156
  guid = entityMatch[1];
128
157
  etype = entityMatch[2];
129
158
  tags = entityMatch[3].split(',');
130
159
  }
131
160
  else if (propMatch) {
161
+ // Add the variable to the new entity.
132
162
  if (guid) {
133
163
  sdata[propMatch[1]] = propMatch[2];
134
164
  }
135
165
  }
166
+ // Clear the entity cache.
136
167
  this.entityCache = {};
137
168
  }
169
+ // Save the last entity.
138
170
  if (guid) {
139
171
  await saveEntityCallback(guid, tags, sdata, etype);
140
172
  }
@@ -144,132 +176,147 @@ class NymphDriver {
144
176
  return true;
145
177
  }
146
178
  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.substring(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));
163
- }
164
- else {
165
- if (key === 'qref' || key === '!qref') {
166
- throw new Error("Can't use checkData on qref clauses.");
179
+ try {
180
+ const formattedSelectors = this.formatSelectors(selectors).selectors;
181
+ for (const curSelector of formattedSelectors) {
182
+ const type = curSelector.type;
183
+ const typeIsNot = type === '!&' || type === '!|';
184
+ const typeIsOr = type === '|' || type === '!|';
185
+ let pass = !typeIsOr;
186
+ for (const k in curSelector) {
187
+ const key = k;
188
+ const value = curSelector[key];
189
+ if (key === 'type' || value == null) {
190
+ continue;
191
+ }
192
+ const clauseNot = key.substring(0, 1) === '!';
193
+ if (key === 'selector' || key === '!selector') {
194
+ const tmpArr = (Array.isArray(value) ? value : [value]);
195
+ pass = (0, utils_1.xor)(this.checkData(data, sdata, tmpArr, guid, tags), (0, utils_1.xor)(typeIsNot, clauseNot));
167
196
  }
168
197
  else {
169
- for (const curValue of value) {
170
- if (curValue == null) {
171
- continue;
172
- }
173
- const propName = curValue[0];
174
- if (propName in sdata) {
175
- data[propName] = JSON.parse(sdata[propName]);
176
- delete sdata[propName];
177
- }
178
- switch (key) {
179
- case 'guid':
180
- case '!guid':
181
- pass = (0, utils_1.xor)(guid == propName, (0, utils_1.xor)(typeIsNot, clauseNot));
182
- break;
183
- case 'tag':
184
- case '!tag':
185
- pass = (0, utils_1.xor)((tags ?? []).indexOf(propName) !== -1, (0, utils_1.xor)(typeIsNot, clauseNot));
186
- break;
187
- case 'defined':
188
- case '!defined':
189
- pass = (0, utils_1.xor)(propName in data, (0, utils_1.xor)(typeIsNot, clauseNot));
190
- break;
191
- case 'truthy':
192
- case '!truthy':
193
- pass = (0, utils_1.xor)(propName in data && data[propName], (0, utils_1.xor)(typeIsNot, clauseNot));
194
- break;
195
- case 'ref':
196
- case '!ref':
197
- const testRefValue = curValue[1];
198
- pass = (0, utils_1.xor)(propName in data &&
199
- this.entityReferenceSearch(data[propName], testRefValue), (0, utils_1.xor)(typeIsNot, clauseNot));
200
- break;
201
- case 'equal':
202
- case '!equal':
203
- const testEqualValue = curValue[1];
204
- pass = (0, utils_1.xor)(propName in data &&
205
- JSON.stringify(data[propName]) ===
206
- JSON.stringify(testEqualValue), (0, utils_1.xor)(typeIsNot, clauseNot));
207
- break;
208
- case 'contain':
209
- case '!contain':
210
- const testContainValue = curValue[1];
211
- pass = (0, utils_1.xor)(propName in data &&
212
- JSON.stringify(data[propName]).indexOf(JSON.stringify(testContainValue)) !== -1, (0, utils_1.xor)(typeIsNot, clauseNot));
213
- break;
214
- case 'like':
215
- case '!like':
216
- const testLikeValue = curValue[1];
217
- pass = (0, utils_1.xor)(propName in data &&
218
- new RegExp('^' +
219
- escapeRegExp(testLikeValue)
220
- .replace('%', () => '.*')
221
- .replace('_', () => '.') +
222
- '$').test(data[propName]), (0, utils_1.xor)(typeIsNot, clauseNot));
223
- break;
224
- case 'match':
225
- case '!match':
226
- const testMatchValue = curValue[1];
227
- pass = (0, utils_1.xor)(propName in data &&
228
- this.posixRegexMatch(testMatchValue, data[propName]), (0, utils_1.xor)(typeIsNot, clauseNot));
229
- break;
230
- case 'imatch':
231
- case '!imatch':
232
- const testIMatchValue = curValue[1];
233
- pass = (0, utils_1.xor)(propName in data &&
234
- this.posixRegexMatch(testIMatchValue, data[propName], true), (0, utils_1.xor)(typeIsNot, clauseNot));
235
- break;
236
- case 'gt':
237
- case '!gt':
238
- const testGTValue = curValue[1];
239
- pass = (0, utils_1.xor)(propName in data && data[propName] > testGTValue, (0, utils_1.xor)(typeIsNot, clauseNot));
240
- break;
241
- case 'gte':
242
- case '!gte':
243
- const testGTEValue = curValue[1];
244
- pass = (0, utils_1.xor)(propName in data && data[propName] >= testGTEValue, (0, utils_1.xor)(typeIsNot, clauseNot));
245
- break;
246
- case 'lt':
247
- case '!lt':
248
- const testLTValue = curValue[1];
249
- pass = (0, utils_1.xor)(propName in data && data[propName] < testLTValue, (0, utils_1.xor)(typeIsNot, clauseNot));
250
- break;
251
- case 'lte':
252
- case '!lte':
253
- const testLTEValue = curValue[1];
254
- pass = (0, utils_1.xor)(propName in data && data[propName] <= testLTEValue, (0, utils_1.xor)(typeIsNot, clauseNot));
198
+ if (key === 'qref' || key === '!qref') {
199
+ throw new Error("Can't use checkData on qref clauses.");
200
+ }
201
+ else {
202
+ // Check if it doesn't pass any for &, check if it passes any for |.
203
+ for (const curValue of value) {
204
+ if (curValue == null) {
205
+ continue;
206
+ }
207
+ const propName = curValue[0];
208
+ // Unserialize the data for this variable.
209
+ if (propName in sdata) {
210
+ data[propName] = JSON.parse(sdata[propName]);
211
+ delete sdata[propName];
212
+ }
213
+ switch (key) {
214
+ case 'guid':
215
+ case '!guid':
216
+ pass = (0, utils_1.xor)(guid == propName, (0, utils_1.xor)(typeIsNot, clauseNot));
217
+ break;
218
+ case 'tag':
219
+ case '!tag':
220
+ pass = (0, utils_1.xor)((tags ?? []).indexOf(propName) !== -1, (0, utils_1.xor)(typeIsNot, clauseNot));
221
+ break;
222
+ case 'defined':
223
+ case '!defined':
224
+ pass = (0, utils_1.xor)(propName in data, (0, utils_1.xor)(typeIsNot, clauseNot));
225
+ break;
226
+ case 'truthy':
227
+ case '!truthy':
228
+ pass = (0, utils_1.xor)(propName in data && data[propName], (0, utils_1.xor)(typeIsNot, clauseNot));
229
+ break;
230
+ case 'ref':
231
+ case '!ref':
232
+ const testRefValue = curValue[1];
233
+ pass = (0, utils_1.xor)(propName in data &&
234
+ this.entityReferenceSearch(data[propName], testRefValue), (0, utils_1.xor)(typeIsNot, clauseNot));
235
+ break;
236
+ case 'equal':
237
+ case '!equal':
238
+ const testEqualValue = curValue[1];
239
+ pass = (0, utils_1.xor)(propName in data &&
240
+ JSON.stringify(data[propName]) ===
241
+ JSON.stringify(testEqualValue), (0, utils_1.xor)(typeIsNot, clauseNot));
242
+ break;
243
+ case 'contain':
244
+ case '!contain':
245
+ const testContainValue = curValue[1];
246
+ pass = (0, utils_1.xor)(propName in data &&
247
+ JSON.stringify(data[propName]).indexOf(JSON.stringify(testContainValue)) !== -1, (0, utils_1.xor)(typeIsNot, clauseNot));
248
+ break;
249
+ case 'like':
250
+ case '!like':
251
+ const testLikeValue = curValue[1];
252
+ pass = (0, utils_1.xor)(propName in data &&
253
+ new RegExp('^' +
254
+ escapeRegExp(testLikeValue)
255
+ .replace('%', () => '.*')
256
+ .replace('_', () => '.') +
257
+ '$').test(data[propName]), (0, utils_1.xor)(typeIsNot, clauseNot));
258
+ break;
259
+ case 'match':
260
+ case '!match':
261
+ const testMatchValue = curValue[1];
262
+ // Convert a POSIX regex to a JS regex.
263
+ pass = (0, utils_1.xor)(propName in data &&
264
+ this.posixRegexMatch(testMatchValue, data[propName]), (0, utils_1.xor)(typeIsNot, clauseNot));
265
+ break;
266
+ case 'imatch':
267
+ case '!imatch':
268
+ const testIMatchValue = curValue[1];
269
+ // Convert a POSIX regex to a JS regex.
270
+ pass = (0, utils_1.xor)(propName in data &&
271
+ this.posixRegexMatch(testIMatchValue, data[propName], true), (0, utils_1.xor)(typeIsNot, clauseNot));
272
+ break;
273
+ case 'gt':
274
+ case '!gt':
275
+ const testGTValue = curValue[1];
276
+ pass = (0, utils_1.xor)(propName in data && data[propName] > testGTValue, (0, utils_1.xor)(typeIsNot, clauseNot));
277
+ break;
278
+ case 'gte':
279
+ case '!gte':
280
+ const testGTEValue = curValue[1];
281
+ pass = (0, utils_1.xor)(propName in data && data[propName] >= testGTEValue, (0, utils_1.xor)(typeIsNot, clauseNot));
282
+ break;
283
+ case 'lt':
284
+ case '!lt':
285
+ const testLTValue = curValue[1];
286
+ pass = (0, utils_1.xor)(propName in data && data[propName] < testLTValue, (0, utils_1.xor)(typeIsNot, clauseNot));
287
+ break;
288
+ case 'lte':
289
+ case '!lte':
290
+ const testLTEValue = curValue[1];
291
+ pass = (0, utils_1.xor)(propName in data && data[propName] <= testLTEValue, (0, utils_1.xor)(typeIsNot, clauseNot));
292
+ break;
293
+ }
294
+ if (!(0, utils_1.xor)(typeIsOr, pass)) {
255
295
  break;
256
- }
257
- if (!(0, utils_1.xor)(typeIsOr, pass)) {
258
- break;
296
+ }
259
297
  }
260
298
  }
261
299
  }
300
+ if (!(0, utils_1.xor)(typeIsOr, pass)) {
301
+ break;
302
+ }
262
303
  }
263
- if (!(0, utils_1.xor)(typeIsOr, pass)) {
264
- break;
304
+ if (!pass) {
305
+ return false;
265
306
  }
266
307
  }
267
- if (!pass) {
268
- return false;
269
- }
308
+ return true;
309
+ }
310
+ catch (e) {
311
+ this.nymph.config.debugError('nymph', `Failed to check entity data: ${e}`);
312
+ throw e;
270
313
  }
271
- return true;
272
314
  }
315
+ /**
316
+ * Remove all copies of an entity from the cache.
317
+ *
318
+ * @param guid The GUID of the entity to remove.
319
+ */
273
320
  cleanCache(guid) {
274
321
  delete this.entityCache[guid];
275
322
  }
@@ -286,7 +333,15 @@ class NymphDriver {
286
333
  }
287
334
  return ret;
288
335
  }
336
+ /**
337
+ * Search through a value for an entity reference.
338
+ *
339
+ * @param value Any value to search.
340
+ * @param entity An entity, GUID, or array of either to search for.
341
+ * @returns True if the reference is found, false otherwise.
342
+ */
289
343
  entityReferenceSearch(value, entity) {
344
+ // Get the GUID, if the passed $entity is an object.
290
345
  const guids = [];
291
346
  if (Array.isArray(entity)) {
292
347
  if (entity[0] === 'nymph_entity_reference') {
@@ -317,6 +372,7 @@ class NymphDriver {
317
372
  guids.indexOf(value[1]) !== -1) {
318
373
  return true;
319
374
  }
375
+ // Search through arrays and objects looking for the reference.
320
376
  if (Array.isArray(value) || typeof value === 'object') {
321
377
  for (const key in value) {
322
378
  if (this.entityReferenceSearch(value[key], guids)) {
@@ -371,19 +427,23 @@ class NymphDriver {
371
427
  qrefs.push(...newSelectors.qrefs);
372
428
  }
373
429
  else if (!Array.isArray(value)) {
430
+ // @ts-ignore: ts doesn't know what value is here.
374
431
  newSelector[key] = [[value]];
375
432
  }
376
433
  else if (!Array.isArray(value[0])) {
377
434
  if (value.length === 3 &&
378
435
  value[1] == null &&
379
436
  typeof value[2] === 'string') {
437
+ // @ts-ignore: ts doesn't know what value is here.
380
438
  newSelector[key] = [[value[0], (0, strtotime_1.default)(value[2]) * 1000]];
381
439
  }
382
440
  else {
441
+ // @ts-ignore: ts doesn't know what value is here.
383
442
  newSelector[key] = [value];
384
443
  }
385
444
  }
386
445
  else {
446
+ // @ts-ignore: ts doesn't know what value is here.
387
447
  newSelector[key] = value.map((curValue) => {
388
448
  if (curValue.length === 3 &&
389
449
  curValue[1] == null &&
@@ -440,10 +500,14 @@ class NymphDriver {
440
500
  let count = 0;
441
501
  const EntityClass = options.class ?? this.nymph.getEntityClass('Entity');
442
502
  const etype = EntityClass.ETYPE;
503
+ // Check if the requested entity is cached.
443
504
  if (this.nymph.config.cache &&
444
505
  !options.skipCache &&
506
+ selectors.length &&
445
507
  'guid' in selectors[0] &&
446
508
  typeof selectors[0].guid === 'number') {
509
+ // Only safe to use the cache option with no other selectors than a GUID
510
+ // and tags.
447
511
  if (selectors.length === 1 &&
448
512
  selectors[0].type === '&' &&
449
513
  (Object.keys(selectors[0]).length === 2 ||
@@ -501,9 +565,13 @@ class NymphDriver {
501
565
  const cdate = tagsAndDates.cdate;
502
566
  const mdate = tagsAndDates.mdate;
503
567
  let dataNameAndSValue = getDataNameAndSValueCallback(row);
568
+ // Data.
504
569
  const data = {};
570
+ // Serialized data.
505
571
  const sdata = {};
506
572
  if (dataNameAndSValue.name !== '') {
573
+ // This do will keep going and adding the data until the
574
+ // next entity is reached. $row will end on the next entity.
507
575
  do {
508
576
  dataNameAndSValue = getDataNameAndSValueCallback(row);
509
577
  sdata[dataNameAndSValue.name] = dataNameAndSValue.svalue;
@@ -511,6 +579,7 @@ class NymphDriver {
511
579
  } while (row != null && getGUIDCallback(row) === guid);
512
580
  }
513
581
  else {
582
+ // Make sure that $row is incremented :)
514
583
  row = rowFetchCallback();
515
584
  }
516
585
  const entity = EntityClass.factorySync();
@@ -531,6 +600,7 @@ class NymphDriver {
531
600
  if (this.nymph.config.cache) {
532
601
  this.pushCache(guid, cdate, mdate, tags, data, sdata);
533
602
  }
603
+ // @ts-ignore: ts doesn't know the return type here.
534
604
  entities.push(entity);
535
605
  }
536
606
  }
@@ -549,6 +619,7 @@ class NymphDriver {
549
619
  }
550
620
  }
551
621
  async saveEntityRowLike(entity, saveNewEntityCallback, saveExistingEntityCallback, startTransactionCallback = null, commitTransactionCallback = null) {
622
+ // Get a modified date.
552
623
  const mdate = Date.now();
553
624
  const tags = (0, lodash_1.difference)(entity.tags, ['']);
554
625
  const data = entity.$getData();
@@ -570,6 +641,7 @@ class NymphDriver {
570
641
  }
571
642
  }
572
643
  else {
644
+ // Removed any cached versions of this entity.
573
645
  if (this.nymph.config.cache) {
574
646
  this.cleanCache(entity.guid);
575
647
  }
@@ -581,12 +653,22 @@ class NymphDriver {
581
653
  if (commitTransactionCallback) {
582
654
  success = await commitTransactionCallback(success);
583
655
  }
656
+ // Cache the entity.
584
657
  if (success && this.nymph.config.cache) {
585
658
  this.pushCache(entity.guid, entity.cdate, entity.mdate, entity.tags, data, sdata);
586
659
  }
587
660
  return success;
588
661
  }
662
+ /**
663
+ * Pull an entity from the cache.
664
+ *
665
+ * @param guid The entity's GUID.
666
+ * @param className The entity's class.
667
+ * @param useSkipAc Whether to tell the entity to use skip_ac.
668
+ * @returns The entity or null if it's not cached.
669
+ */
589
670
  pullCache(guid, className, useSkipAc = false) {
671
+ // Increment the entity access count.
590
672
  if (!(guid in this.entityCount)) {
591
673
  this.entityCount[guid] = 0;
592
674
  }
@@ -606,17 +688,30 @@ class NymphDriver {
606
688
  }
607
689
  return null;
608
690
  }
691
+ /**
692
+ * Push an entity onto the cache.
693
+ *
694
+ * @param guid The entity's GUID.
695
+ * @param cdate The entity's cdate.
696
+ * @param mdate The entity's mdate.
697
+ * @param tags The entity's tags.
698
+ * @param data The entity's data.
699
+ * @param sdata The entity's sdata.
700
+ */
609
701
  pushCache(guid, cdate, mdate, tags, data, sdata) {
610
702
  if (guid == null) {
611
703
  return;
612
704
  }
705
+ // Increment the entity access count.
613
706
  if (!(guid in this.entityCount)) {
614
707
  this.entityCount[guid] = 0;
615
708
  }
616
709
  this.entityCount[guid]++;
710
+ // Check the threshold.
617
711
  if (this.entityCount[guid] < this.nymph.config.cacheThreshold) {
618
712
  return;
619
713
  }
714
+ // Cache the entity.
620
715
  this.entityCache[guid] = {
621
716
  cdate: cdate,
622
717
  mdate: mdate,
@@ -626,12 +721,15 @@ class NymphDriver {
626
721
  };
627
722
  while (this.nymph.config.cacheLimit &&
628
723
  Object.keys(this.entityCache).length >= this.nymph.config.cacheLimit) {
724
+ // Find which entity has been accessed the least.
629
725
  const least = Object.entries(this.entityCount)
630
726
  .sort(([guidA, countA], [guidB, countB]) => countA - countB)
631
727
  .pop()?.[0] ?? null;
632
728
  if (least == null) {
729
+ // This should never happen.
633
730
  return;
634
731
  }
732
+ // Remove it.
635
733
  delete this.entityCache[least];
636
734
  delete this.entityCount[least];
637
735
  }