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