@nymphjs/nymph 1.0.0-beta.6 → 1.0.0-beta.61

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 (64) hide show
  1. package/CHANGELOG.md +277 -0
  2. package/README.md +13 -21
  3. package/dist/Entity.d.ts +269 -7
  4. package/dist/Entity.js +431 -89
  5. package/dist/Entity.js.map +1 -1
  6. package/dist/Entity.test.js +38 -16
  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 +649 -156
  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 +111 -36
  19. package/dist/driver/NymphDriver.js +169 -68
  20. package/dist/driver/NymphDriver.js.map +1 -1
  21. package/dist/driver/testArtifacts.d.ts +1 -1
  22. package/dist/driver/testArtifacts.js +285 -18
  23. package/dist/driver/testArtifacts.js.map +1 -1
  24. package/dist/errors/EntityInvalidDataError.d.ts +6 -0
  25. package/dist/errors/EntityInvalidDataError.js +6 -0
  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.map +1 -1
  34. package/dist/errors/index.d.ts +2 -0
  35. package/dist/errors/index.js +2 -0
  36. package/dist/errors/index.js.map +1 -1
  37. package/dist/testArtifacts.d.ts +17 -5
  38. package/dist/testArtifacts.js +23 -19
  39. package/dist/testArtifacts.js.map +1 -1
  40. package/dist/testMocks.d.ts +15 -10
  41. package/dist/testMocks.js +34 -11
  42. package/dist/testMocks.js.map +1 -1
  43. package/dist/utils.d.ts +1 -1
  44. package/dist/utils.js +21 -5
  45. package/dist/utils.js.map +1 -1
  46. package/package.json +16 -14
  47. package/src/Entity.test.ts +30 -10
  48. package/src/Entity.ts +406 -135
  49. package/src/Entity.types.ts +32 -66
  50. package/src/Nymph.ts +626 -196
  51. package/src/Nymph.types.ts +97 -26
  52. package/src/conf/d.ts +8 -0
  53. package/src/conf/defaults.ts +24 -0
  54. package/src/driver/NymphDriver.ts +187 -243
  55. package/src/driver/testArtifacts.ts +327 -146
  56. package/src/errors/EntityUniqueConstraintError.ts +6 -0
  57. package/src/errors/InvalidStateError.ts +6 -0
  58. package/src/errors/index.ts +4 -0
  59. package/src/nymphJoiProps.ts +2 -2
  60. package/src/testArtifacts.ts +18 -19
  61. package/src/testMocks.ts +64 -32
  62. package/src/utils.ts +22 -8
  63. package/tsconfig.json +1 -1
  64. package/typedoc.json +4 -0
package/dist/Nymph.js CHANGED
@@ -6,26 +6,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const conf_1 = require("./conf");
7
7
  const Entity_1 = __importDefault(require("./Entity"));
8
8
  const errors_1 = require("./errors");
9
+ /**
10
+ * An object relational mapper for Node.js.
11
+ *
12
+ * Written by Hunter Perrin for SciActive.
13
+ *
14
+ * @author Hunter Perrin <hperrin@gmail.com>
15
+ * @copyright SciActive Inc
16
+ * @see http://nymph.io/
17
+ */
9
18
  class Nymph {
10
- addEntityClass(entityClass) {
11
- const nymph = this;
12
- class NymphEntity extends entityClass {
13
- constructor(...args) {
14
- super(...args);
15
- }
16
- }
17
- NymphEntity.nymph = nymph;
18
- this.entityClasses[entityClass.class] = NymphEntity;
19
- return NymphEntity;
20
- }
21
- getEntityClass(className) {
22
- if (className in this.entityClasses) {
23
- return this.entityClasses[className];
24
- }
25
- throw new errors_1.ClassNotAvailableError('Tried to use class: ' + className);
26
- }
27
- constructor(config, driver, tilmeld) {
19
+ /**
20
+ * Initialize Nymph.
21
+ *
22
+ * @param config The Nymph configuration.
23
+ * @param driver The Nymph database driver.
24
+ * @param tilmeld The Tilmeld user/group manager instance, if you want to use it.
25
+ * @param parent Used internally by Nymph. Don't set this.
26
+ */
27
+ constructor(config, driver, tilmeld, parent) {
28
+ /**
29
+ * An optional Tilmeld user/group manager instance.
30
+ */
28
31
  this.tilmeld = undefined;
32
+ /**
33
+ * A simple map of names to Entity classes.
34
+ */
29
35
  this.entityClasses = {};
30
36
  this.connectCallbacks = [];
31
37
  this.disconnectCallbacks = [];
@@ -34,89 +40,215 @@ class Nymph {
34
40
  this.beforeGetEntitiesCallbacks = [];
35
41
  this.beforeSaveEntityCallbacks = [];
36
42
  this.afterSaveEntityCallbacks = [];
43
+ this.failedSaveEntityCallbacks = [];
37
44
  this.beforeDeleteEntityCallbacks = [];
38
45
  this.afterDeleteEntityCallbacks = [];
46
+ this.failedDeleteEntityCallbacks = [];
39
47
  this.beforeDeleteEntityByIDCallbacks = [];
40
48
  this.afterDeleteEntityByIDCallbacks = [];
49
+ this.failedDeleteEntityByIDCallbacks = [];
41
50
  this.beforeNewUIDCallbacks = [];
42
51
  this.afterNewUIDCallbacks = [];
52
+ this.failedNewUIDCallbacks = [];
43
53
  this.beforeSetUIDCallbacks = [];
44
54
  this.afterSetUIDCallbacks = [];
55
+ this.failedSetUIDCallbacks = [];
45
56
  this.beforeRenameUIDCallbacks = [];
46
57
  this.afterRenameUIDCallbacks = [];
58
+ this.failedRenameUIDCallbacks = [];
47
59
  this.beforeDeleteUIDCallbacks = [];
48
60
  this.afterDeleteUIDCallbacks = [];
61
+ this.failedDeleteUIDCallbacks = [];
62
+ this.beforeStartTransactionCallbacks = [];
63
+ this.afterStartTransactionCallbacks = [];
64
+ this.beforeCommitTransactionCallbacks = [];
65
+ this.afterCommitTransactionCallbacks = [];
66
+ this.beforeRollbackTransactionCallbacks = [];
67
+ this.afterRollbackTransactionCallbacks = [];
49
68
  this.config = { ...conf_1.ConfigDefaults, ...config };
50
69
  this.driver = driver;
51
- this.addEntityClass(Entity_1.default);
52
- this.Entity = this.getEntityClass('Entity');
53
- if (typeof tilmeld !== 'undefined') {
70
+ this.parent = parent ?? null;
71
+ this.Entity = this.addEntityClass(Entity_1.default);
72
+ if (parent) {
73
+ for (const name in parent.entityClasses) {
74
+ if (name === 'Entity' ||
75
+ parent.entityClasses[name].skipOnClone) {
76
+ continue;
77
+ }
78
+ this.addEntityClass(parent.entityClasses[name]);
79
+ }
80
+ const events = [
81
+ 'connect',
82
+ 'disconnect',
83
+ 'query',
84
+ 'beforeGetEntity',
85
+ 'beforeGetEntities',
86
+ 'beforeSaveEntity',
87
+ 'afterSaveEntity',
88
+ 'failedSaveEntity',
89
+ 'beforeDeleteEntity',
90
+ 'afterDeleteEntity',
91
+ 'failedDeleteEntity',
92
+ 'beforeDeleteEntityByID',
93
+ 'afterDeleteEntityByID',
94
+ 'failedDeleteEntityByID',
95
+ 'beforeNewUID',
96
+ 'afterNewUID',
97
+ 'failedNewUID',
98
+ 'beforeSetUID',
99
+ 'afterSetUID',
100
+ 'failedSetUID',
101
+ 'beforeRenameUID',
102
+ 'afterRenameUID',
103
+ 'failedRenameUID',
104
+ 'beforeDeleteUID',
105
+ 'afterDeleteUID',
106
+ 'failedDeleteUID',
107
+ 'beforeStartTransaction',
108
+ 'afterStartTransaction',
109
+ 'beforeCommitTransaction',
110
+ 'afterCommitTransaction',
111
+ 'beforeRollbackTransaction',
112
+ 'afterRollbackTransaction',
113
+ ];
114
+ for (let event of events) {
115
+ const prop = event + 'Callbacks';
116
+ // @ts-ignore: The callback should be the right type here.
117
+ const callbacks = parent[prop];
118
+ for (let callback of callbacks) {
119
+ if (callback.skipOnClone) {
120
+ continue;
121
+ }
122
+ this.on(event, callback);
123
+ }
124
+ }
125
+ }
126
+ this.config.debugLog('nymph', 'Nymph loaded.');
127
+ if (tilmeld != null) {
54
128
  this.tilmeld = tilmeld;
55
129
  }
56
130
  this.driver.init(this);
131
+ this.config.debugLog('nymph', 'Nymph driver loaded.');
57
132
  if (this.tilmeld) {
58
133
  this.tilmeld.init(this);
134
+ this.config.debugLog('nymph', 'Tilmeld loaded.');
59
135
  }
60
136
  }
61
- clone() {
62
- const nymph = new Nymph(this.config, this.driver, this.tilmeld?.clone());
63
- for (const name in this.entityClasses) {
64
- if (name === 'Entity' || this.entityClasses[name].skipOnClone) {
65
- continue;
66
- }
67
- nymph.addEntityClass(this.entityClasses[name]);
68
- }
69
- const events = [
70
- 'connect',
71
- 'disconnect',
72
- 'query',
73
- 'beforeGetEntity',
74
- 'beforeGetEntities',
75
- 'beforeSaveEntity',
76
- 'afterSaveEntity',
77
- 'beforeDeleteEntity',
78
- 'afterDeleteEntity',
79
- 'beforeDeleteEntityByID',
80
- 'afterDeleteEntityByID',
81
- 'beforeNewUID',
82
- 'afterNewUID',
83
- 'beforeSetUID',
84
- 'afterSetUID',
85
- 'beforeRenameUID',
86
- 'afterRenameUID',
87
- 'beforeDeleteUID',
88
- 'afterDeleteUID',
89
- ];
90
- for (let event of events) {
91
- const prop = event + 'Callbacks';
92
- const callbacks = this[prop];
93
- for (let callback of callbacks) {
94
- if (callback.skipOnClone) {
95
- continue;
137
+ /**
138
+ * Add your class to this instance.
139
+ *
140
+ * This will create a class that extends your class within this instance of
141
+ * Nymph and return it. You can then use this class's constructor and methods,
142
+ * which will use this instance of Nymph.
143
+ *
144
+ * Because this creates a subclass, don't use the class
145
+ * returned from `getEntityClass` to check with `instanceof`.
146
+ */
147
+ addEntityClass(EntityClass) {
148
+ var _a;
149
+ if (EntityClass.class in this.entityClasses) {
150
+ this.config.debugLog('nymph', `Adding duplicate class "${EntityClass.class}". This may be a mistake.`);
151
+ }
152
+ // Check that it doesn't have the same etype as any other class.
153
+ for (const ExistingClass of Object.values(this.entityClasses)) {
154
+ if (EntityClass.ETYPE === ExistingClass.ETYPE &&
155
+ EntityClass.class !== EntityClass.class) {
156
+ this.config.debugLog('nymph', `Adding class "${EntityClass.class}" with same etype as existing class "${ExistingClass.class}". This may be a mistake.`);
157
+ }
158
+ }
159
+ const nymph = this;
160
+ this.entityClasses[EntityClass.class] = (_a = class extends EntityClass {
161
+ constructor(...args) {
162
+ super(...args);
96
163
  }
97
- nymph.on(event, callback);
164
+ },
165
+ _a.nymph = nymph,
166
+ _a);
167
+ return this.entityClasses[EntityClass.class];
168
+ }
169
+ getEntityClass(className) {
170
+ let key = null;
171
+ if (typeof className === 'string') {
172
+ key = className;
173
+ }
174
+ else {
175
+ key = className.class;
176
+ }
177
+ if (key in this.entityClasses) {
178
+ return this.entityClasses[key];
179
+ }
180
+ this.config.debugError('nymph', `Tried to use missing class "${key}".`);
181
+ throw new errors_1.ClassNotAvailableError(`Tried to use missing class "${key}".`);
182
+ }
183
+ /**
184
+ * Get the class that uses the specified etype.
185
+ *
186
+ * Note that it is fine, though unusual, for two classes to use the same
187
+ * etype. However, this can lead to very hard to diagnose bugs, so is
188
+ * generally discouraged.
189
+ */
190
+ getEntityClassByEtype(etype) {
191
+ for (const EntityClass of Object.values(this.entityClasses)) {
192
+ if (EntityClass.ETYPE === etype) {
193
+ return EntityClass;
98
194
  }
99
195
  }
100
- return nymph;
196
+ this.config.debugError('nymph', `Tried to use missing class by etype "${etype}".`);
197
+ throw new errors_1.ClassNotAvailableError(`Tried to use missing class by etype "${etype}".`);
101
198
  }
199
+ /**
200
+ * Get a clone of this instance with cloned classes and event listeners.
201
+ *
202
+ * @returns A clone of this instance.
203
+ */
204
+ clone() {
205
+ return new Nymph(this.config, this.driver.clone(), this.tilmeld?.clone(), this);
206
+ }
207
+ /**
208
+ * Connect to the database.
209
+ *
210
+ * @returns Whether the instance is connected to the database.
211
+ */
102
212
  async connect() {
103
- const result = this.driver.connect();
104
- for (let callback of this.connectCallbacks) {
105
- if (callback) {
106
- await callback(this, result);
213
+ try {
214
+ const result = this.driver.connect();
215
+ for (let callback of this.connectCallbacks) {
216
+ if (callback) {
217
+ await callback(this, result);
218
+ }
107
219
  }
220
+ this.config.debugLog('nymph', 'Driver connected.');
221
+ return await result;
222
+ }
223
+ catch (e) {
224
+ this.config.debugError('nymph', `Failed to connect: ${e}`);
225
+ throw e;
108
226
  }
109
- return await result;
110
227
  }
228
+ /**
229
+ * Disconnect from the database.
230
+ *
231
+ * @returns Whether the instance is connected to the database.
232
+ */
111
233
  async disconnect() {
112
- const result = this.driver.disconnect();
113
- for (let callback of this.disconnectCallbacks) {
114
- if (callback) {
115
- await callback(this, result);
234
+ try {
235
+ const result = this.driver.disconnect();
236
+ for (let callback of this.disconnectCallbacks) {
237
+ if (callback) {
238
+ await callback(this, result);
239
+ }
116
240
  }
241
+ this.config.debugLog('nymph', 'Driver disconnected.');
242
+ return await result;
243
+ }
244
+ catch (e) {
245
+ this.config.debugError('nymph', `Failed to disconnect: ${e}`);
246
+ throw e;
117
247
  }
118
- return await result;
119
248
  }
249
+ /**
250
+ * Run all the query callbacks on a query.
251
+ */
120
252
  runQueryCallbacks(options, selectors) {
121
253
  for (let callback of this.queryCallbacks) {
122
254
  if (callback) {
@@ -124,160 +256,519 @@ class Nymph {
124
256
  }
125
257
  }
126
258
  }
259
+ /**
260
+ * Start an atomic transaction and returns a new instance of Nymph.
261
+ *
262
+ * All proceeding changes using this new instance will wait to be written to
263
+ * the database's permanent storage until commit() is called. You can also
264
+ * undo all the changes since this function ran with rollback().
265
+ *
266
+ * Transactions will nest as long as every name is unique. Internally, Nymph
267
+ * uses names prefixed with "nymph-".
268
+ *
269
+ * @returns A new instance of Nymph that should be used for the transaction.
270
+ */
127
271
  async startTransaction(name) {
128
- return await this.driver.startTransaction(name);
272
+ try {
273
+ for (let callback of this.beforeStartTransactionCallbacks) {
274
+ if (callback) {
275
+ await callback(this, name);
276
+ }
277
+ }
278
+ this.config.debugLog('nymph', `Starting transaction "${name}".`);
279
+ const result = await this.driver.startTransaction(name);
280
+ for (let callback of this.afterStartTransactionCallbacks) {
281
+ if (callback) {
282
+ await callback(this, name, result);
283
+ }
284
+ }
285
+ return result;
286
+ }
287
+ catch (e) {
288
+ this.config.debugError('nymph', `Failed to start transaction: ${e}`);
289
+ throw e;
290
+ }
129
291
  }
292
+ /**
293
+ * Commit the named transaction.
294
+ *
295
+ * After this is called, the transaction instance should be discarded.
296
+ *
297
+ * @returns True on success, false on failure.
298
+ */
130
299
  async commit(name) {
131
- return await this.driver.commit(name);
300
+ try {
301
+ for (let callback of this.beforeCommitTransactionCallbacks) {
302
+ if (callback) {
303
+ await callback(this, name);
304
+ }
305
+ }
306
+ this.config.debugLog('nymph', `Committing transaction "${name}".`);
307
+ const result = await this.driver.commit(name);
308
+ for (let callback of this.afterCommitTransactionCallbacks) {
309
+ if (callback) {
310
+ await callback(this, name, result);
311
+ }
312
+ }
313
+ return result;
314
+ }
315
+ catch (e) {
316
+ this.config.debugError('nymph', `Failed to commit transaction: ${e}`);
317
+ throw e;
318
+ }
132
319
  }
320
+ /**
321
+ * Rollback the named transaction.
322
+ *
323
+ * After this is called, the transaction instance should be discarded.
324
+ *
325
+ * @returns True on success, false on failure.
326
+ */
133
327
  async rollback(name) {
134
- return await this.driver.rollback(name);
328
+ try {
329
+ for (let callback of this.beforeRollbackTransactionCallbacks) {
330
+ if (callback) {
331
+ await callback(this, name);
332
+ }
333
+ }
334
+ this.config.debugLog('nymph', `Rolling back transaction "${name}".`);
335
+ const result = await this.driver.rollback(name);
336
+ for (let callback of this.afterRollbackTransactionCallbacks) {
337
+ if (callback) {
338
+ await callback(this, name, result);
339
+ }
340
+ }
341
+ return result;
342
+ }
343
+ catch (e) {
344
+ this.config.debugError('nymph', `Failed to roll back transaction: ${e}`);
345
+ throw e;
346
+ }
135
347
  }
348
+ /**
349
+ * Check if there is any open transaction.
350
+ *
351
+ * @returns True if there is a transaction.
352
+ */
136
353
  async inTransaction() {
137
354
  return await this.driver.inTransaction();
138
355
  }
356
+ /**
357
+ * Increment or create a unique ID and return the new value.
358
+ *
359
+ * Unique IDs, or UIDs are similar to GUIDs, but numeric and sequential.
360
+ *
361
+ * A UID can be used to identify an object when the GUID doesn't suffice. On
362
+ * a system where a new entity is created many times per second, referring
363
+ * to something by its GUID may be unintuitive. However, the component
364
+ * designer is responsible for assigning UIDs to the component's entities.
365
+ * Beware that if a UID is incremented for an entity, and the entity cannot
366
+ * be saved, there is no safe, and therefore, no recommended way to
367
+ * decrement the UID back to its previous value.
368
+ *
369
+ * If newUID() is passed the name of a UID which does not exist yet, one
370
+ * will be created with that name, and assigned the value 1. If the UID
371
+ * already exists, its value will be incremented. The new value will be
372
+ * returned.
373
+ *
374
+ * @param name The UID's name.
375
+ * @returns The UID's new value, or null on failure.
376
+ */
139
377
  async newUID(name) {
140
- for (let callback of this.beforeNewUIDCallbacks) {
141
- if (callback) {
142
- await callback(this, name);
378
+ try {
379
+ for (let callback of this.beforeNewUIDCallbacks) {
380
+ if (callback) {
381
+ await callback(this, name);
382
+ }
143
383
  }
144
- }
145
- const result = this.driver.newUID(name);
146
- for (let callback of this.afterNewUIDCallbacks) {
147
- if (callback) {
148
- await callback(this, result);
384
+ let result;
385
+ try {
386
+ result = this.driver.newUID(name);
387
+ }
388
+ catch (e) {
389
+ for (let callback of this.failedNewUIDCallbacks) {
390
+ if (callback) {
391
+ await callback(this, e);
392
+ }
393
+ }
394
+ throw e;
395
+ }
396
+ for (let callback of this.afterNewUIDCallbacks) {
397
+ if (callback) {
398
+ await callback(this, result);
399
+ }
149
400
  }
401
+ return await result;
402
+ }
403
+ catch (e) {
404
+ this.config.debugError('nymph', `Failed to create UID "${name}": ${e}`);
405
+ throw e;
150
406
  }
151
- return await result;
152
407
  }
408
+ /**
409
+ * Get the current value of a unique ID.
410
+ * @param name The UID's name.
411
+ * @returns The UID's value, or null on failure and if it doesn't exist.
412
+ */
153
413
  async getUID(name) {
154
- return await this.driver.getUID(name);
414
+ try {
415
+ return await this.driver.getUID(name);
416
+ }
417
+ catch (e) {
418
+ this.config.debugError('nymph', `Failed to get UID "${name}": ${e}`);
419
+ throw e;
420
+ }
155
421
  }
422
+ /**
423
+ * Set the value of a UID.
424
+ *
425
+ * @param name The UID's name.
426
+ * @param value The value.
427
+ * @returns True on success, false on failure.
428
+ */
156
429
  async setUID(name, value) {
157
- for (let callback of this.beforeSetUIDCallbacks) {
158
- if (callback) {
159
- await callback(this, name, value);
430
+ try {
431
+ for (let callback of this.beforeSetUIDCallbacks) {
432
+ if (callback) {
433
+ await callback(this, name, value);
434
+ }
160
435
  }
161
- }
162
- const result = this.driver.setUID(name, value);
163
- for (let callback of this.afterSetUIDCallbacks) {
164
- if (callback) {
165
- await callback(this, result);
436
+ let result;
437
+ try {
438
+ result = this.driver.setUID(name, value);
166
439
  }
440
+ catch (e) {
441
+ for (let callback of this.failedSetUIDCallbacks) {
442
+ if (callback) {
443
+ await callback(this, e);
444
+ }
445
+ }
446
+ throw e;
447
+ }
448
+ for (let callback of this.afterSetUIDCallbacks) {
449
+ if (callback) {
450
+ await callback(this, result);
451
+ }
452
+ }
453
+ return await result;
454
+ }
455
+ catch (e) {
456
+ this.config.debugError('nymph', `Failed to set UID "${name}": ${e}`);
457
+ throw e;
167
458
  }
168
- return await result;
169
459
  }
460
+ /**
461
+ * Delete a unique ID.
462
+ *
463
+ * @param name The UID's name.
464
+ * @returns True on success, false on failure.
465
+ */
170
466
  async deleteUID(name) {
171
- for (let callback of this.beforeDeleteUIDCallbacks) {
172
- if (callback) {
173
- await callback(this, name);
467
+ try {
468
+ for (let callback of this.beforeDeleteUIDCallbacks) {
469
+ if (callback) {
470
+ await callback(this, name);
471
+ }
174
472
  }
175
- }
176
- const result = this.driver.deleteUID(name);
177
- for (let callback of this.afterDeleteUIDCallbacks) {
178
- if (callback) {
179
- await callback(this, result);
473
+ let result;
474
+ try {
475
+ result = this.driver.deleteUID(name);
180
476
  }
477
+ catch (e) {
478
+ for (let callback of this.failedDeleteUIDCallbacks) {
479
+ if (callback) {
480
+ await callback(this, e);
481
+ }
482
+ }
483
+ throw e;
484
+ }
485
+ for (let callback of this.afterDeleteUIDCallbacks) {
486
+ if (callback) {
487
+ await callback(this, result);
488
+ }
489
+ }
490
+ return await result;
491
+ }
492
+ catch (e) {
493
+ this.config.debugError('nymph', `Failed to delete UID "${name}": ${e}`);
494
+ throw e;
181
495
  }
182
- return await result;
183
496
  }
497
+ /**
498
+ * Rename a unique ID.
499
+ *
500
+ * @param oldName The old name.
501
+ * @param newName The new name.
502
+ * @returns True on success, false on failure.
503
+ */
184
504
  async renameUID(oldName, newName) {
185
- for (let callback of this.beforeRenameUIDCallbacks) {
186
- if (callback) {
187
- await callback(this, oldName, newName);
505
+ try {
506
+ for (let callback of this.beforeRenameUIDCallbacks) {
507
+ if (callback) {
508
+ await callback(this, oldName, newName);
509
+ }
188
510
  }
189
- }
190
- const result = this.driver.renameUID(oldName, newName);
191
- for (let callback of this.afterRenameUIDCallbacks) {
192
- if (callback) {
193
- await callback(this, result);
511
+ let result;
512
+ try {
513
+ result = this.driver.renameUID(oldName, newName);
514
+ }
515
+ catch (e) {
516
+ for (let callback of this.failedRenameUIDCallbacks) {
517
+ if (callback) {
518
+ await callback(this, e);
519
+ }
520
+ }
521
+ throw e;
522
+ }
523
+ for (let callback of this.afterRenameUIDCallbacks) {
524
+ if (callback) {
525
+ await callback(this, result);
526
+ }
194
527
  }
528
+ return await result;
529
+ }
530
+ catch (e) {
531
+ this.config.debugError('nymph', `Failed to rename UID "${oldName}" to "${newName}": ${e}`);
532
+ throw e;
195
533
  }
196
- return await result;
197
534
  }
535
+ /**
536
+ * Save an entity to the database.
537
+ *
538
+ * If the entity has never been saved (has no GUID), a variable "cdate"
539
+ * is set on it with the current Unix timestamp.
540
+ *
541
+ * The variable "mdate" is set to the current Unix timestamp.
542
+ *
543
+ * @param entity The entity.
544
+ * @returns True on success, false on failure.
545
+ */
198
546
  async saveEntity(entity) {
199
- for (let callback of this.beforeSaveEntityCallbacks) {
200
- if (callback) {
201
- await callback(this, entity);
547
+ try {
548
+ for (let callback of this.beforeSaveEntityCallbacks) {
549
+ if (callback) {
550
+ await callback(this, entity);
551
+ }
202
552
  }
203
- }
204
- const result = this.driver.saveEntity(entity);
205
- for (let callback of this.afterSaveEntityCallbacks) {
206
- if (callback) {
207
- await callback(this, result);
553
+ let result;
554
+ try {
555
+ result = this.driver.saveEntity(entity);
556
+ }
557
+ catch (e) {
558
+ for (let callback of this.failedSaveEntityCallbacks) {
559
+ if (callback) {
560
+ await callback(this, e);
561
+ }
562
+ }
563
+ throw e;
208
564
  }
565
+ for (let callback of this.afterSaveEntityCallbacks) {
566
+ if (callback) {
567
+ await callback(this, result);
568
+ }
569
+ }
570
+ return await result;
571
+ }
572
+ catch (e) {
573
+ this.config.debugError('nymph', `Failed to save entity: ${e}`);
574
+ throw e;
209
575
  }
210
- return await result;
211
576
  }
212
577
  async getEntities(options = {}, ...selectors) {
213
- for (let callback of this.beforeGetEntitiesCallbacks) {
214
- if (callback) {
215
- await callback(this, options, selectors);
578
+ try {
579
+ for (let callback of this.beforeGetEntitiesCallbacks) {
580
+ if (callback) {
581
+ await callback(this, options, selectors);
582
+ }
216
583
  }
584
+ return await this.driver.getEntities(options, ...selectors);
585
+ }
586
+ catch (e) {
587
+ this.config.debugError('nymph', `Failed to get entities: ${e}`);
588
+ throw e;
217
589
  }
218
- return await this.driver.getEntities(options, ...selectors);
219
590
  }
220
591
  async getEntity(options = {}, ...selectors) {
221
- if (typeof selectors[0] === 'string') {
222
- selectors = [{ type: '&', guid: selectors[0] }];
223
- }
224
- options.limit = 1;
225
- for (let callback of this.beforeGetEntityCallbacks) {
226
- if (callback) {
227
- await callback(this, options, selectors);
592
+ try {
593
+ // Set up options and selectors.
594
+ if (typeof selectors[0] === 'string') {
595
+ selectors = [{ type: '&', guid: selectors[0] }];
228
596
  }
597
+ options.limit = 1;
598
+ for (let callback of this.beforeGetEntityCallbacks) {
599
+ if (callback) {
600
+ await callback(this, options, selectors);
601
+ }
602
+ }
603
+ const entities = await this.driver.getEntities(options, ...selectors);
604
+ if (options.return === 'count') {
605
+ return entities;
606
+ }
607
+ if (!entities || !entities.length) {
608
+ return null;
609
+ }
610
+ return entities[0];
229
611
  }
230
- const entities = await this.driver.getEntities(options, ...selectors);
231
- if (options.return === 'count') {
232
- return entities;
233
- }
234
- if (!entities || !entities.length) {
235
- return null;
612
+ catch (e) {
613
+ this.config.debugError('nymph', `Failed to get entity: ${e}`);
614
+ throw e;
236
615
  }
237
- return entities[0];
238
616
  }
617
+ /**
618
+ * Delete an entity from the database.
619
+ *
620
+ * @param entity The entity.
621
+ * @returns True on success, false on failure.
622
+ */
239
623
  async deleteEntity(entity) {
240
- for (let callback of this.beforeDeleteEntityCallbacks) {
241
- if (callback) {
242
- await callback(this, entity);
624
+ try {
625
+ for (let callback of this.beforeDeleteEntityCallbacks) {
626
+ if (callback) {
627
+ await callback(this, entity);
628
+ }
243
629
  }
244
- }
245
- const result = this.driver.deleteEntity(entity);
246
- for (let callback of this.afterDeleteEntityCallbacks) {
247
- if (callback) {
248
- await callback(this, result);
630
+ let result;
631
+ try {
632
+ result = this.driver.deleteEntity(entity);
633
+ }
634
+ catch (e) {
635
+ for (let callback of this.failedDeleteEntityCallbacks) {
636
+ if (callback) {
637
+ await callback(this, e);
638
+ }
639
+ }
640
+ throw e;
641
+ }
642
+ for (let callback of this.afterDeleteEntityCallbacks) {
643
+ if (callback) {
644
+ await callback(this, result);
645
+ }
249
646
  }
647
+ return await result;
648
+ }
649
+ catch (e) {
650
+ this.config.debugError('nymph', `Failed to delete entity: ${e}`);
651
+ throw e;
250
652
  }
251
- return await result;
252
653
  }
654
+ /**
655
+ * Delete an entity from the database by its GUID.
656
+ *
657
+ * @param guid The entity's GUID.
658
+ * @param className The entity's class name.
659
+ * @returns True on success, false on failure.
660
+ */
253
661
  async deleteEntityByID(guid, className) {
254
- for (let callback of this.beforeDeleteEntityByIDCallbacks) {
255
- if (callback) {
256
- await callback(this, guid, className);
662
+ try {
663
+ for (let callback of this.beforeDeleteEntityByIDCallbacks) {
664
+ if (callback) {
665
+ await callback(this, guid, className);
666
+ }
257
667
  }
258
- }
259
- const result = this.driver.deleteEntityByID(guid, className);
260
- for (let callback of this.afterDeleteEntityByIDCallbacks) {
261
- if (callback) {
262
- await callback(this, result);
668
+ let result;
669
+ try {
670
+ result = this.driver.deleteEntityByID(guid, className);
671
+ }
672
+ catch (e) {
673
+ for (let callback of this.failedDeleteEntityByIDCallbacks) {
674
+ if (callback) {
675
+ await callback(this, e);
676
+ }
677
+ }
678
+ throw e;
679
+ }
680
+ for (let callback of this.afterDeleteEntityByIDCallbacks) {
681
+ if (callback) {
682
+ await callback(this, result);
683
+ }
263
684
  }
685
+ return await result;
686
+ }
687
+ catch (e) {
688
+ this.config.debugError('nymph', `Failed to delete entity by ID: ${e}`);
689
+ throw e;
264
690
  }
265
- return await result;
266
691
  }
692
+ /**
693
+ * Export entities to a local file.
694
+ *
695
+ * This is the file format:
696
+ *
697
+ * ```
698
+ * #nex2
699
+ * # The above line must be the first thing in the file.
700
+ * # Comments begin with #
701
+ * # And can have white space before them.
702
+ * # This defines a UID.
703
+ * <name/of/uid>[5]
704
+ * <another uid>[8000]
705
+ * # For UIDs, the name is in angle brackets (<>) and the value follows
706
+ * # in square brackets ([]).
707
+ * # This starts a new entity.
708
+ * {1234abcd}<etype>[tag,list,with,commas]
709
+ * # For entities, the GUID is in curly brackets ({}), then the etype in
710
+ * # angle brackets, then the comma separated tag list follows in square
711
+ * # brackets ([]).
712
+ * # Properties are stored like this:
713
+ * # propname=JSON.stringify(value)
714
+ * abilities=["system/admin"]
715
+ * groups=[]
716
+ * inheritAbilities=false
717
+ * name="admin"
718
+ * # White space before/after "=" and at beginning/end of line is ignored.
719
+ * username = "admin"
720
+ * {2}<etype>[tag,list]
721
+ * another="This is another entity."
722
+ * newline="\n"
723
+ * ```
724
+ *
725
+ * @param filename The file to export to.
726
+ * @returns True on success, false on failure.
727
+ */
267
728
  async export(filename) {
268
- return await this.driver.export(filename);
729
+ try {
730
+ return await this.driver.export(filename);
731
+ }
732
+ catch (e) {
733
+ this.config.debugError('nymph', `Failed to export: ${e}`);
734
+ throw e;
735
+ }
269
736
  }
737
+ /**
738
+ * Export entities to the console.
739
+ *
740
+ * @returns True on success, false on failure.
741
+ */
270
742
  async exportPrint() {
271
- return await this.driver.exportPrint();
743
+ try {
744
+ return await this.driver.exportPrint();
745
+ }
746
+ catch (e) {
747
+ this.config.debugError('nymph', `Failed to export: ${e}`);
748
+ throw e;
749
+ }
272
750
  }
751
+ /**
752
+ * Import entities from a file.
753
+ *
754
+ * @param filename The file to import from.
755
+ * @returns True on success, false on failure.
756
+ */
273
757
  async import(filename) {
274
- return await this.driver.import(filename);
758
+ try {
759
+ return await this.driver.import(filename);
760
+ }
761
+ catch (e) {
762
+ this.config.debugError('nymph', `Failed to import: ${e}`);
763
+ throw e;
764
+ }
275
765
  }
276
766
  on(event, callback) {
277
767
  const prop = (event + 'Callbacks');
278
768
  if (!(prop in this)) {
279
769
  throw new Error('Invalid event type.');
280
770
  }
771
+ // @ts-ignore: The callback should be the right type here.
281
772
  this[prop].push(callback);
282
773
  return () => this.off(event, callback);
283
774
  }
@@ -286,8 +777,10 @@ class Nymph {
286
777
  if (!(prop in this)) {
287
778
  return false;
288
779
  }
780
+ // @ts-ignore: The callback should always be the right type here.
289
781
  const i = this[prop].indexOf(callback);
290
782
  if (i > -1) {
783
+ // @ts-ignore: The callback should always be the right type here.
291
784
  this[prop].splice(i, 1);
292
785
  }
293
786
  return true;