@nymphjs/nymph 1.0.0-beta.10 → 1.0.0-beta.100

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 (96) hide show
  1. package/CHANGELOG.md +483 -0
  2. package/README.md +10 -20
  3. package/dist/Entity.d.ts +293 -10
  4. package/dist/Entity.js +563 -148
  5. package/dist/Entity.js.map +1 -1
  6. package/dist/Entity.test.js +52 -13
  7. package/dist/Entity.test.js.map +1 -1
  8. package/dist/Entity.types.d.ts +256 -23
  9. package/dist/Entity.types.js +1 -2
  10. package/dist/Nymph.d.ts +496 -12
  11. package/dist/Nymph.js +752 -179
  12. package/dist/Nymph.js.map +1 -1
  13. package/dist/Nymph.types.d.ts +77 -8
  14. package/dist/Nymph.types.js +3 -5
  15. package/dist/Nymph.types.js.map +1 -1
  16. package/dist/conf/d.d.ts +43 -0
  17. package/dist/conf/d.js +1 -2
  18. package/dist/conf/defaults.d.ts +1 -1
  19. package/dist/conf/defaults.js +26 -3
  20. package/dist/conf/defaults.js.map +1 -1
  21. package/dist/conf/index.d.ts +2 -2
  22. package/dist/conf/index.js +2 -8
  23. package/dist/conf/index.js.map +1 -1
  24. package/dist/driver/NymphDriver.d.ts +174 -40
  25. package/dist/driver/NymphDriver.js +469 -239
  26. package/dist/driver/NymphDriver.js.map +1 -1
  27. package/dist/driver/index.d.ts +2 -2
  28. package/dist/driver/index.js +3 -23
  29. package/dist/driver/index.js.map +1 -1
  30. package/dist/driver/testArtifacts.d.ts +2 -2
  31. package/dist/driver/testArtifacts.js +814 -92
  32. package/dist/driver/testArtifacts.js.map +1 -1
  33. package/dist/errors/ClassNotAvailableError.js +1 -5
  34. package/dist/errors/ClassNotAvailableError.js.map +1 -1
  35. package/dist/errors/EntityConflictError.js +1 -5
  36. package/dist/errors/EntityConflictError.js.map +1 -1
  37. package/dist/errors/EntityInvalidDataError.d.ts +6 -0
  38. package/dist/errors/EntityInvalidDataError.js +8 -6
  39. package/dist/errors/EntityInvalidDataError.js.map +1 -1
  40. package/dist/errors/EntityIsSleepingReferenceError.js +1 -5
  41. package/dist/errors/EntityIsSleepingReferenceError.js.map +1 -1
  42. package/dist/errors/EntityUniqueConstraintError.d.ts +3 -0
  43. package/dist/errors/EntityUniqueConstraintError.js +7 -0
  44. package/dist/errors/EntityUniqueConstraintError.js.map +1 -0
  45. package/dist/errors/InvalidParametersError.js +1 -5
  46. package/dist/errors/InvalidParametersError.js.map +1 -1
  47. package/dist/errors/InvalidStateError.d.ts +3 -0
  48. package/dist/errors/InvalidStateError.js +7 -0
  49. package/dist/errors/InvalidStateError.js.map +1 -0
  50. package/dist/errors/NotConfiguredError.js +1 -5
  51. package/dist/errors/NotConfiguredError.js.map +1 -1
  52. package/dist/errors/QueryFailedError.js +2 -5
  53. package/dist/errors/QueryFailedError.js.map +1 -1
  54. package/dist/errors/UnableToConnectError.js +1 -5
  55. package/dist/errors/UnableToConnectError.js.map +1 -1
  56. package/dist/errors/index.d.ts +10 -8
  57. package/dist/errors/index.js +10 -24
  58. package/dist/errors/index.js.map +1 -1
  59. package/dist/index.d.ts +11 -11
  60. package/dist/index.js +14 -34
  61. package/dist/index.js.map +1 -1
  62. package/dist/nymphJoiProps.js +10 -15
  63. package/dist/nymphJoiProps.js.map +1 -1
  64. package/dist/testArtifacts.d.ts +19 -6
  65. package/dist/testArtifacts.js +32 -37
  66. package/dist/testArtifacts.js.map +1 -1
  67. package/dist/testMocks.d.ts +27 -12
  68. package/dist/testMocks.js +40 -29
  69. package/dist/testMocks.js.map +1 -1
  70. package/dist/utils.d.ts +4 -4
  71. package/dist/utils.js +40 -29
  72. package/dist/utils.js.map +1 -1
  73. package/jest.config.js +11 -2
  74. package/package.json +23 -19
  75. package/src/Entity.test.ts +50 -7
  76. package/src/Entity.ts +483 -147
  77. package/src/Entity.types.ts +72 -74
  78. package/src/Nymph.ts +868 -322
  79. package/src/Nymph.types.ts +146 -27
  80. package/src/conf/d.ts +25 -0
  81. package/src/conf/defaults.ts +34 -1
  82. package/src/conf/index.ts +2 -2
  83. package/src/driver/NymphDriver.ts +562 -414
  84. package/src/driver/index.ts +2 -2
  85. package/src/driver/testArtifacts.ts +954 -185
  86. package/src/errors/EntityUniqueConstraintError.ts +6 -0
  87. package/src/errors/InvalidStateError.ts +6 -0
  88. package/src/errors/index.ts +12 -8
  89. package/src/index.ts +11 -11
  90. package/src/nymphJoiProps.ts +2 -2
  91. package/src/testArtifacts.ts +20 -20
  92. package/src/testMocks.ts +76 -35
  93. package/src/type/locutus.d.ts +3 -0
  94. package/src/utils.ts +39 -18
  95. package/tsconfig.json +5 -3
  96. package/typedoc.json +4 -0
@@ -1,34 +1,50 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const fs_1 = __importDefault(require("fs"));
7
- const lodash_1 = require("lodash");
8
- const n_readlines_1 = __importDefault(require("n-readlines"));
9
- const strtotime_1 = __importDefault(require("locutus/php/datetime/strtotime"));
10
- const guid_1 = require("@nymphjs/guid");
11
- const errors_1 = require("../errors");
12
- const utils_1 = require("../utils");
1
+ import fs from 'node:fs';
2
+ import { difference } from 'lodash-es';
3
+ import ReadLines from 'n-readlines';
4
+ import strtotime from 'locutus/php/datetime/strtotime.js';
5
+ import { Tokenizer } from '@sciactive/tokenizer';
6
+ import { InvalidParametersError, UnableToConnectError, } from '../errors/index.js';
7
+ import { xor } from '../utils.js';
8
+ // from: https://stackoverflow.com/a/6969486/664915
13
9
  function escapeRegExp(string) {
14
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
10
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
15
11
  }
16
- 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
- }
12
+ /**
13
+ * A Nymph database driver.
14
+ */
15
+ export default class NymphDriver {
16
+ nymph = new Proxy({}, {
17
+ get() {
18
+ throw new Error('Attempted access of Nymph instance before driver initialization!');
19
+ },
20
+ });
21
+ tokenizer = new Tokenizer();
22
+ /**
23
+ * A cache to make entity retrieval faster.
24
+ */
25
+ entityCache = {};
26
+ /**
27
+ * A counter for the entity cache to determine the most accessed entities.
28
+ */
29
+ entityCount = {};
30
+ /**
31
+ * Protect against infinite loops.
32
+ */
33
+ putDataCounter = 0;
34
+ /**
35
+ * Initialize the Nymph driver.
36
+ *
37
+ * This is meant to be called internally by Nymph. Don't call this directly.
38
+ *
39
+ * @param nymph The Nymph instance.
40
+ */
27
41
  init(nymph) {
28
42
  this.nymph = nymph;
29
43
  }
30
44
  posixRegexMatch(pattern, subject, caseInsensitive = false) {
31
45
  const posixClasses = [
46
+ // '[[:<:]]',
47
+ // '[[:>:]]',
32
48
  '[:alnum:]',
33
49
  '[:alpha:]',
34
50
  '[:ascii:]',
@@ -45,17 +61,21 @@ class NymphDriver {
45
61
  '[:xdigit:]',
46
62
  ];
47
63
  const jsClasses = [
64
+ // '\b(?=\w)',
65
+ // '(?<=\w)\b',
48
66
  '[A-Za-z0-9]',
49
67
  '[A-Za-z]',
50
68
  '[\x00-\x7F]',
51
- '[ \t]',
52
- '[\x00-\x1F\x7F]',
53
- '[0-9]',
54
- '[\x21-\x7E]',
69
+ '[ \t]', // '\s',
70
+ '[\x00-\x1F\x7F]', // '[\000\001\002\003\004\005\006\007\008\009\010\011\012\013\014'.
71
+ // '\015\016\017\018\019\020\021\022\023\024\025\026\027\028\029'.
72
+ // '\030\031\032\033\034\035\036\037\177]',
73
+ '[0-9]', // '\d',
74
+ '[\x21-\x7E]', // '[A-Za-z0-9!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
55
75
  '[a-z]',
56
- '[\x20-\x7E]',
57
- '[!"#$%&\'()*+,-./:;<=>?@[\\\\\\]^_‘{|}~]',
58
- '[ \t\r\n\v\f]',
76
+ '[\x20-\x7E]', // '[A-Za-z0-9!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
77
+ '[!"#$%&\'()*+,-./:;<=>?@[\\\\\\]^_‘{|}~]', // '[!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}\~]',
78
+ '[ \t\r\n\v\f]', // '[\t\n\x0B\f\r ]',
59
79
  '[A-Z]',
60
80
  '[A-Za-z0-9_]',
61
81
  '[0-9A-Fa-f]',
@@ -69,14 +89,14 @@ class NymphDriver {
69
89
  }
70
90
  async export(filename) {
71
91
  try {
72
- const fhandle = fs_1.default.openSync(filename, 'w');
92
+ const fhandle = fs.openSync(filename, 'w');
73
93
  if (!fhandle) {
74
- throw new errors_1.InvalidParametersError('Provided filename is not writeable.');
94
+ throw new InvalidParametersError('Provided filename is not writeable.');
75
95
  }
76
- await this.exportEntities((line) => {
77
- fs_1.default.writeSync(fhandle, `${line}\n`);
78
- });
79
- fs_1.default.closeSync(fhandle);
96
+ for await (let entry of this.exportDataIterator()) {
97
+ fs.writeSync(fhandle, entry.content);
98
+ }
99
+ fs.closeSync(fhandle);
80
100
  }
81
101
  catch (e) {
82
102
  return false;
@@ -84,98 +104,136 @@ class NymphDriver {
84
104
  return true;
85
105
  }
86
106
  async exportPrint() {
87
- await this.exportEntities((line) => {
88
- console.log(line);
89
- });
107
+ for await (let entry of this.exportDataIterator()) {
108
+ console.log(entry.content);
109
+ }
90
110
  return true;
91
111
  }
92
- async importFromFile(filename, saveEntityCallback, saveUIDCallback, startTransactionCallback = null, commitTransactionCallback = null) {
93
- let rl;
94
- try {
95
- rl = new n_readlines_1.default(filename);
112
+ async importDataIterator(lines, transaction) {
113
+ const first = lines[Symbol.iterator]().next();
114
+ if (first.done || first.value !== '#nex2') {
115
+ throw new Error('Tried to import a file that is not a NEX v2 file.');
96
116
  }
97
- catch (e) {
98
- throw new errors_1.InvalidParametersError('Provided filename is unreadable.');
99
- }
100
- let guid = null;
101
- let line = false;
102
- let sdata = {};
103
- let tags = [];
104
- let etype = '__undefined';
105
- if (startTransactionCallback) {
106
- await startTransactionCallback();
117
+ if (transaction) {
118
+ await this.internalTransaction('nymph-import');
107
119
  }
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';
120
+ try {
121
+ let guid = null;
122
+ let sdata = {};
123
+ let tags = [];
124
+ let etype = '__undefined';
125
+ for (let line of lines) {
126
+ if (line.match(/^\s*#/)) {
127
+ continue;
126
128
  }
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];
129
+ const entityMatch = line.match(/^\s*{([0-9A-Fa-f]+)}<([-\w_]+)>\[([^\]]*)\]\s*$/);
130
+ const propMatch = line.match(/^\s*([^=]+)\s*=\s*(.*\S)\s*$/);
131
+ const uidMatch = line.match(/^\s*<([^>]+)>\[(\d+)\]\s*$/);
132
+ if (uidMatch) {
133
+ // Add the UID.
134
+ await this.importUID({
135
+ name: uidMatch[1],
136
+ value: Number(uidMatch[2]),
137
+ });
138
+ }
139
+ else if (entityMatch) {
140
+ // Save the current entity.
141
+ if (guid) {
142
+ const cdate = Number(JSON.parse(sdata.cdate));
143
+ delete sdata.cdate;
144
+ const mdate = Number(JSON.parse(sdata.mdate));
145
+ delete sdata.mdate;
146
+ await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
147
+ guid = null;
148
+ tags = [];
149
+ sdata = {};
150
+ etype = '__undefined';
151
+ }
152
+ // Record the new entity's info.
153
+ guid = entityMatch[1];
154
+ etype = entityMatch[2];
155
+ tags = entityMatch[3].split(',');
156
+ }
157
+ else if (propMatch) {
158
+ // Add the variable to the new entity.
159
+ if (guid) {
160
+ sdata[propMatch[1]] = propMatch[2];
161
+ }
134
162
  }
163
+ // Clear the entity cache.
164
+ this.entityCache = {};
165
+ }
166
+ // Save the last entity.
167
+ if (guid) {
168
+ const cdate = Number(JSON.parse(sdata.cdate));
169
+ delete sdata.cdate;
170
+ const mdate = Number(JSON.parse(sdata.mdate));
171
+ delete sdata.mdate;
172
+ await this.importEntity({ guid, cdate, mdate, tags, sdata, etype });
173
+ }
174
+ if (transaction) {
175
+ await this.commit('nymph-import');
135
176
  }
136
- this.entityCache = {};
137
- }
138
- if (guid) {
139
- await saveEntityCallback(guid, tags, sdata, etype);
140
177
  }
141
- if (commitTransactionCallback) {
142
- await commitTransactionCallback();
178
+ catch (e) {
179
+ await this.rollback('nymph-import');
180
+ throw e;
143
181
  }
144
182
  return true;
145
183
  }
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));
184
+ async importData(text, transaction) {
185
+ return await this.importDataIterator(text.split('\n'), transaction);
186
+ }
187
+ async import(filename, transaction) {
188
+ let rl;
189
+ try {
190
+ rl = new ReadLines(filename);
191
+ }
192
+ catch (e) {
193
+ throw new InvalidParametersError('Provided filename is unreadable.');
194
+ }
195
+ const lines = {
196
+ *[Symbol.iterator]() {
197
+ let line;
198
+ while ((line = rl.next())) {
199
+ yield line.toString('utf8');
163
200
  }
164
- else {
165
- if (key === 'qref' || key === '!qref') {
166
- throw new Error("Can't use checkData on qref clauses.");
201
+ },
202
+ };
203
+ return await this.importDataIterator(lines, transaction);
204
+ }
205
+ checkData(etype, data, sdata, selectors, guid = null, tags = null) {
206
+ try {
207
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
208
+ const formattedSelectors = this.formatSelectors(selectors).selectors;
209
+ for (const curSelector of formattedSelectors) {
210
+ const type = curSelector.type;
211
+ const typeIsNot = type === '!&' || type === '!|';
212
+ const typeIsOr = type === '|' || type === '!|';
213
+ let pass = !typeIsOr;
214
+ for (const k in curSelector) {
215
+ const key = k;
216
+ const value = curSelector[key];
217
+ if (key === 'type' || value == null) {
218
+ continue;
219
+ }
220
+ const clauseNot = key.substring(0, 1) === '!';
221
+ if (key === 'selector' || key === '!selector') {
222
+ const tmpArr = (Array.isArray(value) ? value : [value]);
223
+ pass = xor(this.checkData(etype, data, sdata, tmpArr, guid, tags), xor(typeIsNot, clauseNot));
167
224
  }
168
225
  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 {
226
+ if (key === 'qref' || key === '!qref') {
227
+ throw new Error("Can't use checkData on qref clauses.");
228
+ }
229
+ else {
230
+ // Check if it doesn't pass any for &, check if it passes any for |.
231
+ for (const curValue of value) {
232
+ if (curValue == null) {
233
+ continue;
234
+ }
178
235
  const propName = curValue[0];
236
+ // Unserialize the data for this variable.
179
237
  if (propName in sdata) {
180
238
  data[propName] = JSON.parse(sdata[propName]);
181
239
  delete sdata[propName];
@@ -183,99 +241,130 @@ class NymphDriver {
183
241
  switch (key) {
184
242
  case 'guid':
185
243
  case '!guid':
186
- pass = (0, utils_1.xor)(guid == propName, (0, utils_1.xor)(typeIsNot, clauseNot));
244
+ pass = xor(guid == propName, xor(typeIsNot, clauseNot));
187
245
  break;
188
246
  case 'tag':
189
247
  case '!tag':
190
- pass = (0, utils_1.xor)((tags ?? []).indexOf(propName) !== -1, (0, utils_1.xor)(typeIsNot, clauseNot));
248
+ pass = xor((tags ?? []).indexOf(propName) !== -1, xor(typeIsNot, clauseNot));
191
249
  break;
192
250
  case 'defined':
193
251
  case '!defined':
194
- pass = (0, utils_1.xor)(propName in data, (0, utils_1.xor)(typeIsNot, clauseNot));
252
+ pass = xor(propName in data, xor(typeIsNot, clauseNot));
195
253
  break;
196
254
  case 'truthy':
197
255
  case '!truthy':
198
- pass = (0, utils_1.xor)(propName in data && data[propName], (0, utils_1.xor)(typeIsNot, clauseNot));
256
+ pass = xor(propName in data && data[propName], xor(typeIsNot, clauseNot));
199
257
  break;
200
258
  case 'ref':
201
259
  case '!ref':
202
260
  const testRefValue = curValue[1];
203
- pass = (0, utils_1.xor)(propName in data &&
204
- this.entityReferenceSearch(data[propName], testRefValue), (0, utils_1.xor)(typeIsNot, clauseNot));
261
+ pass = xor(propName in data &&
262
+ this.entityReferenceSearch(data[propName], testRefValue), xor(typeIsNot, clauseNot));
205
263
  break;
206
264
  case 'equal':
207
265
  case '!equal':
208
266
  const testEqualValue = curValue[1];
209
- pass = (0, utils_1.xor)(propName in data &&
267
+ pass = xor(propName in data &&
210
268
  JSON.stringify(data[propName]) ===
211
- JSON.stringify(testEqualValue), (0, utils_1.xor)(typeIsNot, clauseNot));
269
+ JSON.stringify(testEqualValue), xor(typeIsNot, clauseNot));
212
270
  break;
213
271
  case 'contain':
214
272
  case '!contain':
215
273
  const testContainValue = curValue[1];
216
- pass = (0, utils_1.xor)(propName in data &&
217
- JSON.stringify(data[propName]).indexOf(JSON.stringify(testContainValue)) !== -1, (0, utils_1.xor)(typeIsNot, clauseNot));
274
+ pass = xor(propName in data &&
275
+ JSON.stringify(data[propName]).indexOf(JSON.stringify(testContainValue)) !== -1, xor(typeIsNot, clauseNot));
218
276
  break;
219
277
  case 'like':
220
278
  case '!like':
221
279
  const testLikeValue = curValue[1];
222
- pass = (0, utils_1.xor)(propName in data &&
280
+ pass = xor(propName in data &&
223
281
  new RegExp('^' +
224
282
  escapeRegExp(testLikeValue)
225
283
  .replace('%', () => '.*')
226
284
  .replace('_', () => '.') +
227
- '$').test(data[propName]), (0, utils_1.xor)(typeIsNot, clauseNot));
285
+ '$').test(data[propName]), xor(typeIsNot, clauseNot));
286
+ break;
287
+ case 'ilike':
288
+ case '!ilike':
289
+ const testILikeValue = curValue[1];
290
+ pass = xor(propName in data &&
291
+ new RegExp('^' +
292
+ escapeRegExp(testILikeValue)
293
+ .replace('%', () => '.*')
294
+ .replace('_', () => '.') +
295
+ '$', 'i').test(data[propName]), xor(typeIsNot, clauseNot));
228
296
  break;
229
297
  case 'match':
230
298
  case '!match':
231
299
  const testMatchValue = curValue[1];
232
- pass = (0, utils_1.xor)(propName in data &&
233
- this.posixRegexMatch(testMatchValue, data[propName]), (0, utils_1.xor)(typeIsNot, clauseNot));
300
+ // Convert a POSIX regex to a JS regex.
301
+ pass = xor(propName in data &&
302
+ this.posixRegexMatch(testMatchValue, data[propName]), xor(typeIsNot, clauseNot));
234
303
  break;
235
304
  case 'imatch':
236
305
  case '!imatch':
237
306
  const testIMatchValue = curValue[1];
238
- pass = (0, utils_1.xor)(propName in data &&
239
- this.posixRegexMatch(testIMatchValue, data[propName], true), (0, utils_1.xor)(typeIsNot, clauseNot));
307
+ // Convert a POSIX regex to a JS regex.
308
+ pass = xor(propName in data &&
309
+ this.posixRegexMatch(testIMatchValue, data[propName], true), xor(typeIsNot, clauseNot));
310
+ break;
311
+ case 'search':
312
+ case '!search':
313
+ const testSearchValue = curValue[1];
314
+ const tokenString = propName in data
315
+ ? (EntityClass.getFTSText(propName, data[propName]) ??
316
+ '')
317
+ : '';
318
+ pass = xor(propName in data &&
319
+ this.tokenizer.searchString(testSearchValue, tokenString), xor(typeIsNot, clauseNot));
240
320
  break;
241
321
  case 'gt':
242
322
  case '!gt':
243
323
  const testGTValue = curValue[1];
244
- pass = (0, utils_1.xor)(propName in data && data[propName] > testGTValue, (0, utils_1.xor)(typeIsNot, clauseNot));
324
+ pass = xor(propName in data && data[propName] > testGTValue, xor(typeIsNot, clauseNot));
245
325
  break;
246
326
  case 'gte':
247
327
  case '!gte':
248
328
  const testGTEValue = curValue[1];
249
- pass = (0, utils_1.xor)(propName in data && data[propName] >= testGTEValue, (0, utils_1.xor)(typeIsNot, clauseNot));
329
+ pass = xor(propName in data && data[propName] >= testGTEValue, xor(typeIsNot, clauseNot));
250
330
  break;
251
331
  case 'lt':
252
332
  case '!lt':
253
333
  const testLTValue = curValue[1];
254
- pass = (0, utils_1.xor)(propName in data && data[propName] < testLTValue, (0, utils_1.xor)(typeIsNot, clauseNot));
334
+ pass = xor(propName in data && data[propName] < testLTValue, xor(typeIsNot, clauseNot));
255
335
  break;
256
336
  case 'lte':
257
337
  case '!lte':
258
338
  const testLTEValue = curValue[1];
259
- pass = (0, utils_1.xor)(propName in data && data[propName] <= testLTEValue, (0, utils_1.xor)(typeIsNot, clauseNot));
339
+ pass = xor(propName in data && data[propName] <= testLTEValue, xor(typeIsNot, clauseNot));
260
340
  break;
261
341
  }
262
- }
263
- if (!(0, utils_1.xor)(typeIsOr, pass)) {
264
- break;
342
+ if (!xor(typeIsOr, pass)) {
343
+ break;
344
+ }
265
345
  }
266
346
  }
267
347
  }
348
+ if (!xor(typeIsOr, pass)) {
349
+ break;
350
+ }
268
351
  }
269
- if (!(0, utils_1.xor)(typeIsOr, pass)) {
270
- break;
352
+ if (!pass) {
353
+ return false;
271
354
  }
272
355
  }
273
- if (!pass) {
274
- return false;
275
- }
356
+ return true;
357
+ }
358
+ catch (e) {
359
+ this.nymph.config.debugError('nymph', `Failed to check entity data: ${e}`);
360
+ throw e;
276
361
  }
277
- return true;
278
362
  }
363
+ /**
364
+ * Remove all copies of an entity from the cache.
365
+ *
366
+ * @param guid The GUID of the entity to remove.
367
+ */
279
368
  cleanCache(guid) {
280
369
  delete this.entityCache[guid];
281
370
  }
@@ -292,7 +381,15 @@ class NymphDriver {
292
381
  }
293
382
  return ret;
294
383
  }
384
+ /**
385
+ * Search through a value for an entity reference.
386
+ *
387
+ * @param value Any value to search.
388
+ * @param entity An entity, GUID, or array of either to search for.
389
+ * @returns True if the reference is found, false otherwise.
390
+ */
295
391
  entityReferenceSearch(value, entity) {
392
+ // Get the GUID, if the passed $entity is an object.
296
393
  const guids = [];
297
394
  if (Array.isArray(entity)) {
298
395
  if (entity[0] === 'nymph_entity_reference') {
@@ -323,7 +420,8 @@ class NymphDriver {
323
420
  guids.indexOf(value[1]) !== -1) {
324
421
  return true;
325
422
  }
326
- if (Array.isArray(value) || typeof value === 'object') {
423
+ // Search through arrays and objects looking for the reference.
424
+ if (value != null && (Array.isArray(value) || typeof value === 'object')) {
327
425
  for (const key in value) {
328
426
  if (this.entityReferenceSearch(value[key], guids)) {
329
427
  return true;
@@ -356,9 +454,9 @@ class NymphDriver {
356
454
  for (let i = 0; i < tmpArr.length; i++) {
357
455
  const name = tmpArr[i][0];
358
456
  const [qrefOptions, ...qrefSelectors] = tmpArr[i][1];
359
- const QrefEntityClass = typeof qrefOptions.class === 'string'
457
+ const QrefEntityClass = qrefOptions.class
360
458
  ? this.nymph.getEntityClass(qrefOptions.class)
361
- : qrefOptions.class ?? this.nymph.getEntityClass('Entity');
459
+ : this.nymph.getEntityClass('Entity');
362
460
  const newOptions = {
363
461
  ...qrefOptions,
364
462
  class: QrefEntityClass,
@@ -377,24 +475,28 @@ class NymphDriver {
377
475
  qrefs.push(...newSelectors.qrefs);
378
476
  }
379
477
  else if (!Array.isArray(value)) {
478
+ // @ts-ignore: ts doesn't know what value is here.
380
479
  newSelector[key] = [[value]];
381
480
  }
382
481
  else if (!Array.isArray(value[0])) {
383
482
  if (value.length === 3 &&
384
483
  value[1] == null &&
385
484
  typeof value[2] === 'string') {
386
- newSelector[key] = [[value[0], (0, strtotime_1.default)(value[2]) * 1000]];
485
+ // @ts-ignore: ts doesn't know what value is here.
486
+ newSelector[key] = [[value[0], strtotime(value[2]) * 1000]];
387
487
  }
388
488
  else {
489
+ // @ts-ignore: ts doesn't know what value is here.
389
490
  newSelector[key] = [value];
390
491
  }
391
492
  }
392
493
  else {
494
+ // @ts-ignore: ts doesn't know what value is here.
393
495
  newSelector[key] = value.map((curValue) => {
394
496
  if (curValue.length === 3 &&
395
497
  curValue[1] == null &&
396
498
  typeof curValue[2] === 'string') {
397
- return [curValue[0], (0, strtotime_1.default)(curValue[2]) * 1000];
499
+ return [curValue[0], strtotime(curValue[2]) * 1000];
398
500
  }
399
501
  return curValue;
400
502
  });
@@ -416,7 +518,7 @@ class NymphDriver {
416
518
  if (key === 'type') {
417
519
  continue;
418
520
  }
419
- let curQuery = callback(key, value, typeIsOr, typeIsNot);
521
+ let curQuery = callback({ key, value, typeIsOr, typeIsNot });
420
522
  if (curQuery) {
421
523
  if (curSelectorQuery) {
422
524
  curSelectorQuery += typeIsOr ? ' OR ' : ' AND ';
@@ -430,73 +532,116 @@ class NymphDriver {
430
532
  }
431
533
  return queryParts;
432
534
  }
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) {
535
+ getEntitiesRowLike(options, selectors, performQueryCallback, rowFetchCallback, freeResultCallback, getCountCallback, getGUIDCallback, getTagsAndDatesCallback, getDataNameAndSValueCallback) {
448
536
  if (!this.isConnected()) {
449
- throw new errors_1.UnableToConnectError('not connected to DB');
537
+ throw new UnableToConnectError('not connected to DB');
450
538
  }
451
539
  for (const selector of selectors) {
452
540
  if (!selector ||
453
541
  Object.keys(selector).length === 1 ||
454
542
  !('type' in selector) ||
455
543
  ['&', '!&', '|', '!|'].indexOf(selector.type) === -1) {
456
- throw new errors_1.InvalidParametersError('Invalid query selector passed: ' + JSON.stringify(selector));
544
+ throw new InvalidParametersError('Invalid query selector passed: ' + JSON.stringify(selector));
457
545
  }
458
546
  }
459
547
  let entities = [];
460
548
  let count = 0;
461
549
  const EntityClass = options.class ?? this.nymph.getEntityClass('Entity');
462
550
  const etype = EntityClass.ETYPE;
551
+ if (options.source === 'client' && options.return === 'object') {
552
+ throw new InvalidParametersError('Object return type not allowed from client.');
553
+ }
554
+ // Check if the requested entity is cached.
463
555
  if (this.nymph.config.cache &&
464
556
  !options.skipCache &&
557
+ selectors.length &&
465
558
  'guid' in selectors[0] &&
466
559
  typeof selectors[0].guid === 'number') {
560
+ // Only safe to use the cache option with no other selectors than a GUID
561
+ // and tags.
467
562
  if (selectors.length === 1 &&
468
563
  selectors[0].type === '&' &&
469
564
  (Object.keys(selectors[0]).length === 2 ||
470
565
  (Object.keys(selectors[0]).length === 3 && 'tag' in selectors[0]))) {
471
- const entity = this.pullCache(selectors[0]['guid'], EntityClass.class, !!options.skipAc);
472
- if (entity != null &&
473
- entity.guid != null &&
566
+ const cacheEntity = this.pullCache(selectors[0]['guid']);
567
+ if (cacheEntity != null &&
568
+ cacheEntity.guid != null &&
474
569
  (!('tag' in selectors[0]) ||
475
- entity.hasTag(...(Array.isArray(selectors[0].tag)
570
+ !(Array.isArray(selectors[0].tag)
476
571
  ? selectors[0].tag
477
- : [selectors[0].tag])))) {
478
- const guid = entity.guid;
479
- return {
480
- result: Promise.resolve(null),
481
- process: () => options.return === 'count'
482
- ? guid
483
- ? 1
484
- : 0
485
- : options.return === 'guid'
486
- ? [guid]
487
- : [entity],
488
- };
572
+ : [selectors[0].tag]).find((tag) => tag == null || !cacheEntity.tags.includes(tag)))) {
573
+ // Return the value from the cache.
574
+ const { guid, cdate, mdate, tags, data, sdata } = cacheEntity;
575
+ if (options.return === 'count') {
576
+ return {
577
+ result: Promise.resolve(null),
578
+ process: () => 1,
579
+ };
580
+ }
581
+ else if (options.return === 'guid') {
582
+ return {
583
+ result: Promise.resolve(null),
584
+ process: () => [guid],
585
+ };
586
+ }
587
+ else if (options.return === 'object') {
588
+ return {
589
+ result: Promise.resolve(null),
590
+ process: () => [
591
+ {
592
+ guid,
593
+ cdate,
594
+ mdate,
595
+ tags,
596
+ ...data,
597
+ ...Object.fromEntries(Object.entries(sdata).map(([name, value]) => [
598
+ name,
599
+ JSON.parse(value),
600
+ ])),
601
+ },
602
+ ],
603
+ };
604
+ }
605
+ else {
606
+ const entity = this.nymph
607
+ .getEntityClass(EntityClass.class)
608
+ .factorySync();
609
+ if (entity) {
610
+ entity.$nymph = this.nymph;
611
+ if (options.skipAc != null) {
612
+ entity.$useSkipAc(!!options.skipAc);
613
+ }
614
+ entity.guid = guid;
615
+ entity.cdate = cdate;
616
+ entity.mdate = mdate;
617
+ entity.tags = tags;
618
+ entity.$putData(data, sdata, 'server');
619
+ return {
620
+ result: Promise.resolve(null),
621
+ process: () => [entity],
622
+ };
623
+ }
624
+ return {
625
+ result: Promise.resolve(null),
626
+ process: () => [],
627
+ };
628
+ }
489
629
  }
490
630
  }
491
631
  }
492
632
  try {
493
633
  const formattedSelectors = this.formatSelectors(selectors, options);
494
634
  this.nymph.runQueryCallbacks(options, formattedSelectors.selectors);
495
- for (let qref of formattedSelectors.qrefs) {
496
- const [options, ...selectors] = qref;
635
+ for (let i = 0; i < formattedSelectors.qrefs.length; i++) {
636
+ const [options, ...selectors] = formattedSelectors.qrefs[i];
497
637
  this.nymph.runQueryCallbacks(options, selectors);
638
+ formattedSelectors.qrefs[i] = [options, ...selectors];
498
639
  }
499
- const { result } = performQueryCallback(options, formattedSelectors.selectors, etype);
640
+ const { result } = performQueryCallback({
641
+ options,
642
+ selectors: formattedSelectors.selectors,
643
+ etype,
644
+ });
500
645
  return {
501
646
  result,
502
647
  process: () => {
@@ -521,9 +666,13 @@ class NymphDriver {
521
666
  const cdate = tagsAndDates.cdate;
522
667
  const mdate = tagsAndDates.mdate;
523
668
  let dataNameAndSValue = getDataNameAndSValueCallback(row);
669
+ // Data.
524
670
  const data = {};
671
+ // Serialized data.
525
672
  const sdata = {};
526
673
  if (dataNameAndSValue.name !== '') {
674
+ // This do will keep going and adding the data until the
675
+ // next entity is reached. $row will end on the next entity.
527
676
  do {
528
677
  dataNameAndSValue = getDataNameAndSValueCallback(row);
529
678
  sdata[dataNameAndSValue.name] = dataNameAndSValue.svalue;
@@ -531,27 +680,43 @@ class NymphDriver {
531
680
  } while (row != null && getGUIDCallback(row) === guid);
532
681
  }
533
682
  else {
683
+ // Make sure that $row is incremented :)
534
684
  row = rowFetchCallback();
535
685
  }
536
- const entity = EntityClass.factorySync();
537
- entity.$nymph = this.nymph;
538
- if (options.skipAc != null) {
539
- entity.$useSkipAc(!!options.skipAc);
686
+ if (options.return === 'object') {
687
+ entities.push({
688
+ guid,
689
+ cdate,
690
+ mdate,
691
+ tags,
692
+ ...data,
693
+ ...Object.fromEntries(Object.entries(sdata).map(([name, value]) => [
694
+ name,
695
+ JSON.parse(value),
696
+ ])),
697
+ });
540
698
  }
541
- entity.guid = guid;
542
- entity.cdate = cdate;
543
- entity.mdate = mdate;
544
- entity.tags = tags;
545
- this.putDataCounter++;
546
- if (this.putDataCounter == 100) {
547
- throw new Error('Infinite loop detected in Entity loading.');
699
+ else {
700
+ const entity = EntityClass.factorySync();
701
+ entity.$nymph = this.nymph;
702
+ if (options.skipAc != null) {
703
+ entity.$useSkipAc(!!options.skipAc);
704
+ }
705
+ entity.guid = guid;
706
+ entity.cdate = cdate;
707
+ entity.mdate = mdate;
708
+ entity.tags = tags;
709
+ this.putDataCounter++;
710
+ if (this.putDataCounter == 100) {
711
+ throw new Error('Infinite loop detected in Entity loading.');
712
+ }
713
+ entity.$putData(data, sdata, 'server');
714
+ this.putDataCounter--;
715
+ entities.push(entity);
548
716
  }
549
- entity.$putData(data, sdata);
550
- this.putDataCounter--;
551
717
  if (this.nymph.config.cache) {
552
718
  this.pushCache(guid, cdate, mdate, tags, data, sdata);
553
719
  }
554
- entities.push(entity);
555
720
  }
556
721
  }
557
722
  freeResultCallback();
@@ -569,89 +734,155 @@ class NymphDriver {
569
734
  }
570
735
  }
571
736
  async saveEntityRowLike(entity, saveNewEntityCallback, saveExistingEntityCallback, startTransactionCallback = null, commitTransactionCallback = null) {
572
- const mdate = Date.now();
573
- const tags = (0, lodash_1.difference)(entity.tags, ['']);
737
+ const originalGuid = entity.guid;
738
+ const originalCdate = entity.cdate;
739
+ const originalMdate = entity.mdate;
740
+ // Get a modified date.
741
+ let mdate = Date.now();
742
+ if (entity.mdate != null) {
743
+ if (this.nymph.config.updateMDate === false) {
744
+ mdate = entity.mdate;
745
+ }
746
+ else if (typeof this.nymph.config.updateMDate === 'number') {
747
+ mdate = entity.mdate + this.nymph.config.updateMDate;
748
+ }
749
+ }
750
+ const tags = difference(entity.tags, ['']);
574
751
  const data = entity.$getData();
575
752
  const sdata = entity.$getSData();
753
+ const uniques = await entity.$getUniques();
576
754
  const EntityClass = entity.constructor;
577
755
  const etype = EntityClass.ETYPE;
578
756
  if (startTransactionCallback) {
579
757
  await startTransactionCallback();
580
758
  }
581
759
  let success = false;
582
- if (entity.guid == null) {
583
- const cdate = mdate;
584
- const newId = (0, guid_1.guid)();
585
- success = await saveNewEntityCallback(entity, newId, tags, data, sdata, cdate, etype);
586
- if (success) {
587
- entity.guid = newId;
588
- entity.cdate = cdate;
589
- entity.mdate = mdate;
760
+ try {
761
+ if (entity.guid == null) {
762
+ const cdate = mdate;
763
+ const newId = entity.$getGuaranteedGUID();
764
+ success = await saveNewEntityCallback({
765
+ entity,
766
+ guid: newId,
767
+ tags,
768
+ data,
769
+ sdata,
770
+ uniques,
771
+ cdate,
772
+ etype,
773
+ });
774
+ if (success) {
775
+ entity.guid = newId;
776
+ entity.cdate = cdate;
777
+ entity.mdate = mdate;
778
+ }
590
779
  }
591
- }
592
- else {
593
- if (this.nymph.config.cache) {
594
- this.cleanCache(entity.guid);
780
+ else {
781
+ // Removed any cached versions of this entity.
782
+ if (this.nymph.config.cache) {
783
+ this.cleanCache(entity.guid);
784
+ }
785
+ success = await saveExistingEntityCallback({
786
+ entity,
787
+ guid: entity.guid,
788
+ tags,
789
+ data,
790
+ sdata,
791
+ uniques,
792
+ mdate,
793
+ etype,
794
+ });
795
+ if (success) {
796
+ entity.mdate = mdate;
797
+ }
595
798
  }
596
- success = await saveExistingEntityCallback(entity, entity.guid, tags, data, sdata, mdate, etype);
597
- if (success) {
598
- entity.mdate = mdate;
799
+ if (commitTransactionCallback) {
800
+ success = await commitTransactionCallback(success);
801
+ }
802
+ // Cache the entity.
803
+ if (success && this.nymph.config.cache) {
804
+ this.pushCache(entity.guid, entity.cdate, entity.mdate, entity.tags, data, sdata);
599
805
  }
600
806
  }
601
- if (commitTransactionCallback) {
602
- success = await commitTransactionCallback(success);
807
+ catch (e) {
808
+ entity.guid = originalGuid;
809
+ entity.cdate = originalCdate;
810
+ entity.mdate = originalMdate;
811
+ throw e;
603
812
  }
604
- if (success && this.nymph.config.cache) {
605
- this.pushCache(entity.guid, entity.cdate, entity.mdate, entity.tags, data, sdata);
813
+ if (!success) {
814
+ entity.guid = originalGuid;
815
+ entity.cdate = originalCdate;
816
+ entity.mdate = originalMdate;
606
817
  }
607
818
  return success;
608
819
  }
609
- pullCache(guid, className, useSkipAc = false) {
820
+ /**
821
+ * Pull an entity from the cache.
822
+ *
823
+ * @param guid The entity's GUID.
824
+ * @returns The entity's data or null if it's not cached.
825
+ */
826
+ pullCache(guid) {
827
+ // Increment the entity access count.
610
828
  if (!(guid in this.entityCount)) {
611
829
  this.entityCount[guid] = 0;
612
830
  }
613
831
  this.entityCount[guid]++;
614
832
  if (guid in this.entityCache) {
615
- const entity = this.nymph.getEntityClass(className).factorySync();
616
- if (entity) {
617
- entity.$nymph = this.nymph;
618
- entity.$useSkipAc(!!useSkipAc);
619
- entity.guid = guid;
620
- entity.cdate = this.entityCache[guid]['cdate'];
621
- entity.mdate = this.entityCache[guid]['mdate'];
622
- entity.tags = this.entityCache[guid]['tags'];
623
- entity.$putData(this.entityCache[guid]['data'], this.entityCache[guid]['sdata']);
624
- return entity;
625
- }
833
+ return {
834
+ guid,
835
+ cdate: this.entityCache[guid]['cdate'],
836
+ mdate: this.entityCache[guid]['mdate'],
837
+ tags: this.entityCache[guid]['tags'],
838
+ data: this.entityCache[guid]['data'],
839
+ sdata: this.entityCache[guid]['sdata'],
840
+ };
626
841
  }
627
842
  return null;
628
843
  }
844
+ /**
845
+ * Push an entity onto the cache.
846
+ *
847
+ * @param guid The entity's GUID.
848
+ * @param cdate The entity's cdate.
849
+ * @param mdate The entity's mdate.
850
+ * @param tags The entity's tags.
851
+ * @param data The entity's data.
852
+ * @param sdata The entity's sdata.
853
+ */
629
854
  pushCache(guid, cdate, mdate, tags, data, sdata) {
630
855
  if (guid == null) {
631
856
  return;
632
857
  }
858
+ // Increment the entity access count.
633
859
  if (!(guid in this.entityCount)) {
634
860
  this.entityCount[guid] = 0;
635
861
  }
636
862
  this.entityCount[guid]++;
863
+ // Check the threshold.
637
864
  if (this.entityCount[guid] < this.nymph.config.cacheThreshold) {
638
865
  return;
639
866
  }
867
+ // Cache the entity.
640
868
  this.entityCache[guid] = {
641
- cdate: cdate,
642
- mdate: mdate,
643
- tags: tags,
644
- data: data,
645
- sdata: sdata,
869
+ cdate,
870
+ mdate,
871
+ tags,
872
+ data,
873
+ sdata,
646
874
  };
647
875
  while (this.nymph.config.cacheLimit &&
648
876
  Object.keys(this.entityCache).length >= this.nymph.config.cacheLimit) {
877
+ // Find which entity has been accessed the least.
649
878
  const least = Object.entries(this.entityCount)
650
- .sort(([guidA, countA], [guidB, countB]) => countA - countB)
879
+ .sort(([_guidA, countA], [_guidB, countB]) => countA - countB)
651
880
  .pop()?.[0] ?? null;
652
881
  if (least == null) {
882
+ // This should never happen.
653
883
  return;
654
884
  }
885
+ // Remove it.
655
886
  delete this.entityCache[least];
656
887
  delete this.entityCount[least];
657
888
  }
@@ -665,5 +896,4 @@ class NymphDriver {
665
896
  return matches.map((match) => match.replace(re, '$1'));
666
897
  }
667
898
  }
668
- exports.default = NymphDriver;
669
899
  //# sourceMappingURL=NymphDriver.js.map