@nymphjs/nymph 1.0.0-beta.13 → 1.0.0-beta.130
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 +644 -0
- package/README.md +34 -24
- package/dist/Entity.d.ts +304 -15
- package/dist/Entity.js +609 -154
- package/dist/Entity.js.map +1 -1
- package/dist/Entity.test.js +75 -29
- package/dist/Entity.test.js.map +1 -1
- package/dist/Entity.types.d.ts +275 -28
- package/dist/Entity.types.js +1 -2
- package/dist/Nymph.d.ts +575 -11
- package/dist/Nymph.js +825 -288
- package/dist/Nymph.js.map +1 -1
- package/dist/Nymph.types.d.ts +117 -7
- package/dist/Nymph.types.js +47 -5
- package/dist/Nymph.types.js.map +1 -1
- 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 +50 -2
- package/dist/conf/index.js +2 -8
- package/dist/conf/index.js.map +1 -1
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/index.js +2 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/transactional.d.ts +12 -0
- package/dist/decorators/transactional.js +45 -0
- package/dist/decorators/transactional.js.map +1 -0
- package/dist/driver/NymphDriver.d.ts +209 -41
- package/dist/driver/NymphDriver.js +737 -242
- package/dist/driver/NymphDriver.js.map +1 -1
- package/dist/driver/index.d.ts +1 -2
- package/dist/driver/index.js +2 -23
- package/dist/driver/index.js.map +1 -1
- package/dist/driver/testArtifacts.d.ts +4 -4
- package/dist/driver/testArtifacts.js +2016 -1086
- 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/HttpError.d.ts +5 -0
- package/dist/errors/HttpError.js +11 -0
- package/dist/errors/HttpError.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/MethodFailedError.d.ts +3 -0
- package/dist/errors/MethodFailedError.js +7 -0
- package/dist/errors/MethodFailedError.js.map +1 -0
- package/dist/errors/NotConfiguredError.js +1 -5
- package/dist/errors/NotConfiguredError.js.map +1 -1
- package/dist/errors/QueryFailedError.d.ts +2 -1
- package/dist/errors/QueryFailedError.js +5 -6
- 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 +12 -8
- package/dist/errors/index.js +12 -24
- package/dist/errors/index.js.map +1 -1
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +2 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/transaction.d.ts +15 -0
- package/dist/helpers/transaction.js +47 -0
- package/dist/helpers/transaction.js.map +1 -0
- package/dist/index.d.ts +13 -11
- package/dist/index.js +16 -34
- package/dist/index.js.map +1 -1
- package/dist/nymphJoiProps.d.ts +1 -1
- package/dist/nymphJoiProps.js +10 -15
- package/dist/nymphJoiProps.js.map +1 -1
- package/dist/testArtifacts.d.ts +21 -6
- package/dist/testArtifacts.js +124 -50
- package/dist/testArtifacts.js.map +1 -1
- package/dist/testMocks.d.ts +27 -12
- package/dist/testMocks.js +44 -33
- package/dist/testMocks.js.map +1 -1
- package/dist/utils.d.ts +5 -5
- package/dist/utils.js +106 -68
- package/dist/utils.js.map +1 -1
- package/jest.config.js +31 -5
- package/package.json +43 -24
- package/src/Entity.test.ts +73 -23
- package/src/Entity.ts +555 -169
- package/src/Entity.types.ts +102 -90
- package/src/Nymph.ts +930 -517
- package/src/Nymph.types.ts +145 -39
- package/src/conf/defaults.ts +34 -1
- package/src/conf/index.ts +50 -2
- package/src/decorators/index.ts +1 -0
- package/src/decorators/transactional.ts +63 -0
- package/src/driver/NymphDriver.ts +987 -417
- package/src/driver/index.ts +1 -3
- package/src/driver/testArtifacts.ts +2699 -1628
- package/src/errors/EntityUniqueConstraintError.ts +6 -0
- package/src/errors/HttpError.ts +12 -0
- package/src/errors/InvalidStateError.ts +6 -0
- package/src/errors/MethodFailedError.ts +6 -0
- package/src/errors/QueryFailedError.ts +3 -1
- package/src/errors/index.ts +16 -8
- package/src/helpers/index.ts +1 -0
- package/src/helpers/transaction.ts +65 -0
- package/src/index.ts +13 -11
- package/src/nymphJoiProps.ts +2 -2
- package/src/testArtifacts.ts +69 -20
- package/src/testMocks.ts +107 -41
- package/src/utils.ts +102 -67
- package/tsconfig.json +6 -3
- package/typedoc.json +4 -0
- package/dist/conf/d.d.ts +0 -6
- package/dist/conf/d.js +0 -3
- package/dist/conf/d.js.map +0 -1
- package/src/conf/d.ts +0 -24
package/dist/Nymph.js
CHANGED
|
@@ -1,149 +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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
1
|
+
import { ConfigDefaults as defaults } from './conf/index.js';
|
|
2
|
+
import Entity from './Entity.js';
|
|
3
|
+
import { ClassNotAvailableError, InvalidParametersError, } 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@port87.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 };
|
|
46
81
|
this.driver = driver;
|
|
47
|
-
this.parent = null;
|
|
48
|
-
this.Entity = this.addEntityClass(
|
|
49
|
-
if (
|
|
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) {
|
|
50
140
|
this.tilmeld = tilmeld;
|
|
51
141
|
}
|
|
52
142
|
this.driver.init(this);
|
|
143
|
+
this.config.debugInfo('nymph', 'Nymph driver loaded.');
|
|
53
144
|
if (this.tilmeld) {
|
|
54
145
|
this.tilmeld.init(this);
|
|
146
|
+
this.config.debugInfo('nymph', 'Tilmeld loaded.');
|
|
55
147
|
}
|
|
56
148
|
}
|
|
57
|
-
|
|
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
|
+
}
|
|
58
170
|
const nymph = this;
|
|
59
|
-
class
|
|
171
|
+
this.entityClasses[EntityClass.class] = class extends EntityClass {
|
|
172
|
+
static nymph = nymph;
|
|
60
173
|
constructor(...args) {
|
|
61
174
|
super(...args);
|
|
62
175
|
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
this.entityClasses[entityClass.class] = NymphEntity;
|
|
66
|
-
return NymphEntity;
|
|
176
|
+
};
|
|
177
|
+
return this.entityClasses[EntityClass.class];
|
|
67
178
|
}
|
|
68
179
|
getEntityClass(className) {
|
|
69
|
-
|
|
70
|
-
|
|
180
|
+
let key = null;
|
|
181
|
+
if (typeof className === 'string') {
|
|
182
|
+
key = className;
|
|
71
183
|
}
|
|
72
|
-
|
|
184
|
+
else {
|
|
185
|
+
key = className.class;
|
|
186
|
+
}
|
|
187
|
+
if (key in this.entityClasses) {
|
|
188
|
+
return this.entityClasses[key];
|
|
189
|
+
}
|
|
190
|
+
this.config.debugError('nymph', `Tried to use missing class "${key}".`);
|
|
191
|
+
throw new ClassNotAvailableError(`Tried to use missing class "${key}".`);
|
|
73
192
|
}
|
|
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;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
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}".`);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get a clone of this instance with cloned classes and event listeners.
|
|
211
|
+
*
|
|
212
|
+
* @returns A clone of this instance.
|
|
213
|
+
*/
|
|
74
214
|
clone() {
|
|
75
|
-
|
|
76
|
-
nymph.parent = this;
|
|
77
|
-
for (const name in this.entityClasses) {
|
|
78
|
-
if (name === 'Entity' || this.entityClasses[name].skipOnClone) {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
nymph.addEntityClass(this.entityClasses[name]);
|
|
82
|
-
}
|
|
83
|
-
const events = [
|
|
84
|
-
'connect',
|
|
85
|
-
'disconnect',
|
|
86
|
-
'query',
|
|
87
|
-
'beforeGetEntity',
|
|
88
|
-
'beforeGetEntities',
|
|
89
|
-
'beforeSaveEntity',
|
|
90
|
-
'afterSaveEntity',
|
|
91
|
-
'failedSaveEntity',
|
|
92
|
-
'beforeDeleteEntity',
|
|
93
|
-
'afterDeleteEntity',
|
|
94
|
-
'failedDeleteEntity',
|
|
95
|
-
'beforeDeleteEntityByID',
|
|
96
|
-
'afterDeleteEntityByID',
|
|
97
|
-
'failedDeleteEntityByID',
|
|
98
|
-
'beforeNewUID',
|
|
99
|
-
'afterNewUID',
|
|
100
|
-
'failedNewUID',
|
|
101
|
-
'beforeSetUID',
|
|
102
|
-
'afterSetUID',
|
|
103
|
-
'failedSetUID',
|
|
104
|
-
'beforeRenameUID',
|
|
105
|
-
'afterRenameUID',
|
|
106
|
-
'failedRenameUID',
|
|
107
|
-
'beforeDeleteUID',
|
|
108
|
-
'afterDeleteUID',
|
|
109
|
-
'failedDeleteUID',
|
|
110
|
-
'beforeStartTransaction',
|
|
111
|
-
'afterStartTransaction',
|
|
112
|
-
'beforeCommitTransaction',
|
|
113
|
-
'afterCommitTransaction',
|
|
114
|
-
'beforeRollbackTransaction',
|
|
115
|
-
'afterRollbackTransaction',
|
|
116
|
-
];
|
|
117
|
-
for (let event of events) {
|
|
118
|
-
const prop = event + 'Callbacks';
|
|
119
|
-
const callbacks = this[prop];
|
|
120
|
-
for (let callback of callbacks) {
|
|
121
|
-
if (callback.skipOnClone) {
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
nymph.on(event, callback);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return nymph;
|
|
215
|
+
return new Nymph(this.config, this.driver.clone(), this.tilmeld?.clone(), this);
|
|
128
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Connect to the database.
|
|
219
|
+
*
|
|
220
|
+
* @returns Whether the instance is connected to the database.
|
|
221
|
+
*/
|
|
129
222
|
async connect() {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
223
|
+
try {
|
|
224
|
+
const result = this.driver.connect();
|
|
225
|
+
for (let callback of this.connectCallbacks) {
|
|
226
|
+
if (callback) {
|
|
227
|
+
await callback(this, result);
|
|
228
|
+
}
|
|
134
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;
|
|
135
236
|
}
|
|
136
|
-
return await result;
|
|
137
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Disconnect from the database.
|
|
240
|
+
*
|
|
241
|
+
* @returns Whether the instance is connected to the database.
|
|
242
|
+
*/
|
|
138
243
|
async disconnect() {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
244
|
+
try {
|
|
245
|
+
const result = this.driver.disconnect();
|
|
246
|
+
for (let callback of this.disconnectCallbacks) {
|
|
247
|
+
if (callback) {
|
|
248
|
+
await callback(this, result);
|
|
249
|
+
}
|
|
143
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;
|
|
144
257
|
}
|
|
145
|
-
return await result;
|
|
146
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Run all the query callbacks on a query.
|
|
261
|
+
*/
|
|
147
262
|
runQueryCallbacks(options, selectors) {
|
|
148
263
|
for (let callback of this.queryCallbacks) {
|
|
149
264
|
if (callback) {
|
|
@@ -151,270 +266,691 @@ class Nymph {
|
|
|
151
266
|
}
|
|
152
267
|
}
|
|
153
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
|
+
*/
|
|
154
281
|
async startTransaction(name) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
282
|
+
try {
|
|
283
|
+
for (let callback of this.beforeStartTransactionCallbacks) {
|
|
284
|
+
if (callback) {
|
|
285
|
+
await callback(this, name);
|
|
286
|
+
}
|
|
158
287
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
}
|
|
164
294
|
}
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
catch (e) {
|
|
298
|
+
this.config.debugError('nymph', `Failed to start transaction: ${e}`);
|
|
299
|
+
throw e;
|
|
165
300
|
}
|
|
166
|
-
return result;
|
|
167
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
|
+
*/
|
|
168
309
|
async commit(name) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
310
|
+
try {
|
|
311
|
+
for (let callback of this.beforeCommitTransactionCallbacks) {
|
|
312
|
+
if (callback) {
|
|
313
|
+
await callback(this, name);
|
|
314
|
+
}
|
|
172
315
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
+
}
|
|
178
322
|
}
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
catch (e) {
|
|
326
|
+
this.config.debugError('nymph', `Failed to commit transaction: ${e}`);
|
|
327
|
+
throw e;
|
|
179
328
|
}
|
|
180
|
-
return result;
|
|
181
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
|
+
*/
|
|
182
337
|
async rollback(name) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
338
|
+
try {
|
|
339
|
+
for (let callback of this.beforeRollbackTransactionCallbacks) {
|
|
340
|
+
if (callback) {
|
|
341
|
+
await callback(this, name);
|
|
342
|
+
}
|
|
186
343
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
}
|
|
192
350
|
}
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
catch (e) {
|
|
354
|
+
this.config.debugError('nymph', `Failed to roll back transaction: ${e}`);
|
|
355
|
+
throw e;
|
|
193
356
|
}
|
|
194
|
-
return result;
|
|
195
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Check if there is any open transaction.
|
|
360
|
+
*
|
|
361
|
+
* @returns True if there is a transaction.
|
|
362
|
+
*/
|
|
196
363
|
async inTransaction() {
|
|
197
364
|
return await this.driver.inTransaction();
|
|
198
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
|
+
*/
|
|
199
387
|
async newUID(name) {
|
|
200
|
-
for (let callback of this.beforeNewUIDCallbacks) {
|
|
201
|
-
if (callback) {
|
|
202
|
-
await callback(this, name);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
let result;
|
|
206
388
|
try {
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
catch (e) {
|
|
210
|
-
for (let callback of this.failedNewUIDCallbacks) {
|
|
389
|
+
for (let callback of this.beforeNewUIDCallbacks) {
|
|
211
390
|
if (callback) {
|
|
212
|
-
await callback(this,
|
|
391
|
+
await callback(this, name);
|
|
213
392
|
}
|
|
214
393
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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;
|
|
405
|
+
}
|
|
406
|
+
for (let callback of this.afterNewUIDCallbacks) {
|
|
407
|
+
if (callback) {
|
|
408
|
+
await callback(this, result);
|
|
409
|
+
}
|
|
220
410
|
}
|
|
411
|
+
return await result;
|
|
412
|
+
}
|
|
413
|
+
catch (e) {
|
|
414
|
+
this.config.debugError('nymph', `Failed to create UID "${name}": ${e}`);
|
|
415
|
+
throw e;
|
|
221
416
|
}
|
|
222
|
-
return await result;
|
|
223
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
|
+
*/
|
|
224
423
|
async getUID(name) {
|
|
225
|
-
return await this.driver.getUID(name);
|
|
226
|
-
}
|
|
227
|
-
async setUID(name, value) {
|
|
228
|
-
for (let callback of this.beforeSetUIDCallbacks) {
|
|
229
|
-
if (callback) {
|
|
230
|
-
await callback(this, name, value);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
let result;
|
|
234
424
|
try {
|
|
235
|
-
|
|
425
|
+
return await this.driver.getUID(name);
|
|
236
426
|
}
|
|
237
427
|
catch (e) {
|
|
238
|
-
|
|
428
|
+
this.config.debugError('nymph', `Failed to get UID "${name}": ${e}`);
|
|
429
|
+
throw e;
|
|
430
|
+
}
|
|
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
|
+
*/
|
|
439
|
+
async setUID(name, value) {
|
|
440
|
+
try {
|
|
441
|
+
for (let callback of this.beforeSetUIDCallbacks) {
|
|
239
442
|
if (callback) {
|
|
240
|
-
await callback(this,
|
|
443
|
+
await callback(this, name, value);
|
|
241
444
|
}
|
|
242
445
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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;
|
|
457
|
+
}
|
|
458
|
+
for (let callback of this.afterSetUIDCallbacks) {
|
|
459
|
+
if (callback) {
|
|
460
|
+
await callback(this, result);
|
|
461
|
+
}
|
|
248
462
|
}
|
|
463
|
+
return await result;
|
|
464
|
+
}
|
|
465
|
+
catch (e) {
|
|
466
|
+
this.config.debugError('nymph', `Failed to set UID "${name}": ${e}`);
|
|
467
|
+
throw e;
|
|
249
468
|
}
|
|
250
|
-
return await result;
|
|
251
469
|
}
|
|
470
|
+
/**
|
|
471
|
+
* Delete a unique ID.
|
|
472
|
+
*
|
|
473
|
+
* @param name The UID's name.
|
|
474
|
+
* @returns True on success, false on failure.
|
|
475
|
+
*/
|
|
252
476
|
async deleteUID(name) {
|
|
253
|
-
for (let callback of this.beforeDeleteUIDCallbacks) {
|
|
254
|
-
if (callback) {
|
|
255
|
-
await callback(this, name);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
let result;
|
|
259
477
|
try {
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
catch (e) {
|
|
263
|
-
for (let callback of this.failedDeleteUIDCallbacks) {
|
|
478
|
+
for (let callback of this.beforeDeleteUIDCallbacks) {
|
|
264
479
|
if (callback) {
|
|
265
|
-
await callback(this,
|
|
480
|
+
await callback(this, name);
|
|
266
481
|
}
|
|
267
482
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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;
|
|
273
494
|
}
|
|
495
|
+
for (let callback of this.afterDeleteUIDCallbacks) {
|
|
496
|
+
if (callback) {
|
|
497
|
+
await callback(this, result);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return await result;
|
|
501
|
+
}
|
|
502
|
+
catch (e) {
|
|
503
|
+
this.config.debugError('nymph', `Failed to delete UID "${name}": ${e}`);
|
|
504
|
+
throw e;
|
|
274
505
|
}
|
|
275
|
-
return await result;
|
|
276
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
|
+
*/
|
|
277
514
|
async renameUID(oldName, newName) {
|
|
278
|
-
for (let callback of this.beforeRenameUIDCallbacks) {
|
|
279
|
-
if (callback) {
|
|
280
|
-
await callback(this, oldName, newName);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
let result;
|
|
284
515
|
try {
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
catch (e) {
|
|
288
|
-
for (let callback of this.failedRenameUIDCallbacks) {
|
|
516
|
+
for (let callback of this.beforeRenameUIDCallbacks) {
|
|
289
517
|
if (callback) {
|
|
290
|
-
await callback(this,
|
|
518
|
+
await callback(this, oldName, newName);
|
|
291
519
|
}
|
|
292
520
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
+
}
|
|
298
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;
|
|
299
543
|
}
|
|
300
|
-
return await result;
|
|
301
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
|
+
*/
|
|
302
556
|
async saveEntity(entity) {
|
|
303
|
-
for (let callback of this.beforeSaveEntityCallbacks) {
|
|
304
|
-
if (callback) {
|
|
305
|
-
await callback(this, entity);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
let result;
|
|
309
557
|
try {
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
catch (e) {
|
|
313
|
-
for (let callback of this.failedSaveEntityCallbacks) {
|
|
558
|
+
for (let callback of this.beforeSaveEntityCallbacks) {
|
|
314
559
|
if (callback) {
|
|
315
|
-
await callback(this,
|
|
560
|
+
await callback(this, entity);
|
|
316
561
|
}
|
|
317
562
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (callback) {
|
|
322
|
-
await callback(this, result);
|
|
563
|
+
let result;
|
|
564
|
+
try {
|
|
565
|
+
result = this.driver.saveEntity(entity);
|
|
323
566
|
}
|
|
567
|
+
catch (e) {
|
|
568
|
+
for (let callback of this.failedSaveEntityCallbacks) {
|
|
569
|
+
if (callback) {
|
|
570
|
+
await callback(this, e);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
throw e;
|
|
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;
|
|
324
585
|
}
|
|
325
|
-
return await result;
|
|
326
586
|
}
|
|
327
587
|
async getEntities(options = {}, ...selectors) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
588
|
+
if (options.source === 'client' && options.return === 'object') {
|
|
589
|
+
throw new InvalidParametersError('Object return type not allowed from client.');
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
for (let callback of this.beforeGetEntitiesCallbacks) {
|
|
593
|
+
if (callback) {
|
|
594
|
+
await callback(this, options, selectors);
|
|
595
|
+
}
|
|
331
596
|
}
|
|
597
|
+
return await this.driver.getEntities(options, ...selectors);
|
|
598
|
+
}
|
|
599
|
+
catch (e) {
|
|
600
|
+
this.config.debugError('nymph', `Failed to get entities: ${e}`);
|
|
601
|
+
throw e;
|
|
332
602
|
}
|
|
333
|
-
return await this.driver.getEntities(options, ...selectors);
|
|
334
603
|
}
|
|
335
604
|
async getEntity(options = {}, ...selectors) {
|
|
336
|
-
if (
|
|
337
|
-
|
|
605
|
+
if (options.source === 'client' && options.return === 'object') {
|
|
606
|
+
throw new InvalidParametersError('Object return type not allowed from client.');
|
|
338
607
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
|
|
608
|
+
try {
|
|
609
|
+
// Set up options and selectors.
|
|
610
|
+
if (typeof selectors[0] === 'string') {
|
|
611
|
+
selectors = [{ type: '&', guid: selectors[0] }];
|
|
343
612
|
}
|
|
613
|
+
options.limit = 1;
|
|
614
|
+
for (let callback of this.beforeGetEntityCallbacks) {
|
|
615
|
+
if (callback) {
|
|
616
|
+
await callback(this, options, selectors);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const entities = await this.driver.getEntities(options, ...selectors);
|
|
620
|
+
if (options.return === 'count') {
|
|
621
|
+
return entities;
|
|
622
|
+
}
|
|
623
|
+
if (!entities || !entities.length) {
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
return entities[0];
|
|
344
627
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
if (!entities || !entities.length) {
|
|
350
|
-
return null;
|
|
628
|
+
catch (e) {
|
|
629
|
+
this.config.debugError('nymph', `Failed to get entity: ${e}`);
|
|
630
|
+
throw e;
|
|
351
631
|
}
|
|
352
|
-
return entities[0];
|
|
353
632
|
}
|
|
633
|
+
/**
|
|
634
|
+
* Delete an entity from the database.
|
|
635
|
+
*
|
|
636
|
+
* @param entity The entity.
|
|
637
|
+
* @returns True on success, false on failure.
|
|
638
|
+
*/
|
|
354
639
|
async deleteEntity(entity) {
|
|
355
|
-
for (let callback of this.beforeDeleteEntityCallbacks) {
|
|
356
|
-
if (callback) {
|
|
357
|
-
await callback(this, entity);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
let result;
|
|
361
640
|
try {
|
|
362
|
-
|
|
641
|
+
for (let callback of this.beforeDeleteEntityCallbacks) {
|
|
642
|
+
if (callback) {
|
|
643
|
+
await callback(this, entity);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
let result;
|
|
647
|
+
try {
|
|
648
|
+
result = this.driver.deleteEntity(entity);
|
|
649
|
+
}
|
|
650
|
+
catch (e) {
|
|
651
|
+
for (let callback of this.failedDeleteEntityCallbacks) {
|
|
652
|
+
if (callback) {
|
|
653
|
+
await callback(this, e);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
throw e;
|
|
657
|
+
}
|
|
658
|
+
for (let callback of this.afterDeleteEntityCallbacks) {
|
|
659
|
+
if (callback) {
|
|
660
|
+
await callback(this, result);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return await result;
|
|
363
664
|
}
|
|
364
665
|
catch (e) {
|
|
365
|
-
|
|
666
|
+
this.config.debugError('nymph', `Failed to delete entity: ${e}`);
|
|
667
|
+
throw e;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Delete an entity from the database by its GUID.
|
|
672
|
+
*
|
|
673
|
+
* @param guid The entity's GUID.
|
|
674
|
+
* @param className The entity's class name.
|
|
675
|
+
* @returns True on success, false on failure.
|
|
676
|
+
*/
|
|
677
|
+
async deleteEntityByID(guid, className) {
|
|
678
|
+
try {
|
|
679
|
+
for (let callback of this.beforeDeleteEntityByIDCallbacks) {
|
|
680
|
+
if (callback) {
|
|
681
|
+
await callback(this, guid, className);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
let result;
|
|
685
|
+
try {
|
|
686
|
+
result = this.driver.deleteEntityByID(guid, className);
|
|
687
|
+
}
|
|
688
|
+
catch (e) {
|
|
689
|
+
for (let callback of this.failedDeleteEntityByIDCallbacks) {
|
|
690
|
+
if (callback) {
|
|
691
|
+
await callback(this, e);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
throw e;
|
|
695
|
+
}
|
|
696
|
+
for (let callback of this.afterDeleteEntityByIDCallbacks) {
|
|
366
697
|
if (callback) {
|
|
367
|
-
await callback(this,
|
|
698
|
+
await callback(this, result);
|
|
368
699
|
}
|
|
369
700
|
}
|
|
701
|
+
return await result;
|
|
702
|
+
}
|
|
703
|
+
catch (e) {
|
|
704
|
+
this.config.debugError('nymph', `Failed to delete entity by ID: ${e}`);
|
|
370
705
|
throw e;
|
|
371
706
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Export entities to a local file.
|
|
710
|
+
*
|
|
711
|
+
* This is the file format:
|
|
712
|
+
*
|
|
713
|
+
* ```
|
|
714
|
+
* #nex2
|
|
715
|
+
* # The above line must be the first thing in the file.
|
|
716
|
+
* # Comments begin with #
|
|
717
|
+
* # And can have white space before them.
|
|
718
|
+
* # This defines a UID.
|
|
719
|
+
* <name/of/uid>[5]
|
|
720
|
+
* <another uid>[8000]
|
|
721
|
+
* # For UIDs, the name is in angle brackets (<>) and the value follows
|
|
722
|
+
* # in square brackets ([]).
|
|
723
|
+
* # This starts a new entity.
|
|
724
|
+
* {1234abcd}<etype>[tag,list,with,commas]
|
|
725
|
+
* # For entities, the GUID is in curly brackets ({}), then the etype in
|
|
726
|
+
* # angle brackets, then the comma separated tag list follows in square
|
|
727
|
+
* # brackets ([]).
|
|
728
|
+
* # Properties are stored like this:
|
|
729
|
+
* # propname=JSON.stringify(value)
|
|
730
|
+
* abilities=["system/admin"]
|
|
731
|
+
* groups=[]
|
|
732
|
+
* inheritAbilities=false
|
|
733
|
+
* name="admin"
|
|
734
|
+
* # White space before/after "=" and at beginning/end of line is ignored.
|
|
735
|
+
* username = "admin"
|
|
736
|
+
* {2}<etype>[tag,list]
|
|
737
|
+
* another="This is another entity."
|
|
738
|
+
* newline="\n"
|
|
739
|
+
* ```
|
|
740
|
+
*
|
|
741
|
+
* @param filename The file to export to.
|
|
742
|
+
* @returns True on success, false on failure.
|
|
743
|
+
*/
|
|
744
|
+
async export(filename) {
|
|
745
|
+
try {
|
|
746
|
+
return await this.driver.export(filename);
|
|
747
|
+
}
|
|
748
|
+
catch (e) {
|
|
749
|
+
this.config.debugError('nymph', `Failed to export: ${e}`);
|
|
750
|
+
throw e;
|
|
376
751
|
}
|
|
377
|
-
return await result;
|
|
378
752
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
753
|
+
/**
|
|
754
|
+
* Export entities to the console.
|
|
755
|
+
*
|
|
756
|
+
* @returns True on success, false on failure.
|
|
757
|
+
*/
|
|
758
|
+
async exportPrint() {
|
|
759
|
+
try {
|
|
760
|
+
return await this.driver.exportPrint();
|
|
384
761
|
}
|
|
385
|
-
|
|
762
|
+
catch (e) {
|
|
763
|
+
this.config.debugError('nymph', `Failed to export: ${e}`);
|
|
764
|
+
throw e;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
async exportEntity(etype, guid) {
|
|
386
768
|
try {
|
|
387
|
-
|
|
769
|
+
return await this.driver.exportEntity(etype, guid);
|
|
388
770
|
}
|
|
389
771
|
catch (e) {
|
|
390
|
-
|
|
391
|
-
if (callback) {
|
|
392
|
-
await callback(this, e);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
772
|
+
this.config.debugError('nymph', `Failed to export: ${e}`);
|
|
395
773
|
throw e;
|
|
396
774
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Import entities from a file.
|
|
778
|
+
*
|
|
779
|
+
* @param filename The file to import from.
|
|
780
|
+
* @param options The options for the import.
|
|
781
|
+
* @returns True on success, false on failure.
|
|
782
|
+
*/
|
|
783
|
+
async import(filename, options) {
|
|
784
|
+
try {
|
|
785
|
+
return await this.driver.import(filename, options);
|
|
786
|
+
}
|
|
787
|
+
catch (e) {
|
|
788
|
+
this.config.debugError('nymph', `Failed to import: ${e}`);
|
|
789
|
+
throw e;
|
|
401
790
|
}
|
|
402
|
-
return await result;
|
|
403
791
|
}
|
|
404
|
-
|
|
405
|
-
|
|
792
|
+
/**
|
|
793
|
+
* Import a single entity.
|
|
794
|
+
*
|
|
795
|
+
* @param entity The entity data to import.
|
|
796
|
+
*/
|
|
797
|
+
async importEntity(entity) {
|
|
798
|
+
try {
|
|
799
|
+
return await this.driver.importEntity(entity);
|
|
800
|
+
}
|
|
801
|
+
catch (e) {
|
|
802
|
+
this.config.debugError('nymph', `Failed to import: ${e}`);
|
|
803
|
+
throw e;
|
|
804
|
+
}
|
|
406
805
|
}
|
|
407
|
-
|
|
408
|
-
|
|
806
|
+
/**
|
|
807
|
+
* Import a single entity's tokens.
|
|
808
|
+
*
|
|
809
|
+
* This does not update any of the actual data, only the tokens used for full
|
|
810
|
+
* text search.
|
|
811
|
+
*
|
|
812
|
+
* Be careful with this function, since you can overwrite existing tokens with
|
|
813
|
+
* data that is out of sync. The DB _should_ prevent you from inserting tokens
|
|
814
|
+
* for an entity that doesn't exist, but it _won't_ prevent you from inserting
|
|
815
|
+
* tokens for properties that don't exist on an entity.
|
|
816
|
+
*
|
|
817
|
+
* @param entity The entity data to import.
|
|
818
|
+
*/
|
|
819
|
+
async importEntityTokens(entity) {
|
|
820
|
+
try {
|
|
821
|
+
return await this.driver.importEntityTokens(entity);
|
|
822
|
+
}
|
|
823
|
+
catch (e) {
|
|
824
|
+
this.config.debugError('nymph', `Failed to import: ${e}`);
|
|
825
|
+
throw e;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Import a single entity's Tilmeld access control properties.
|
|
830
|
+
*
|
|
831
|
+
* This does not update any of the other data, only the access control
|
|
832
|
+
* columns.
|
|
833
|
+
*
|
|
834
|
+
* @param entity The entity data to import.
|
|
835
|
+
*/
|
|
836
|
+
async importEntityTilmeldAC(entity) {
|
|
837
|
+
try {
|
|
838
|
+
return await this.driver.importEntityTilmeldAC(entity);
|
|
839
|
+
}
|
|
840
|
+
catch (e) {
|
|
841
|
+
this.config.debugError('nymph', `Failed to import: ${e}`);
|
|
842
|
+
throw e;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Detect whether the database needs to be migrated.
|
|
847
|
+
*
|
|
848
|
+
* If not false (so, one of the string values), the database should be
|
|
849
|
+
* exported with an old version of Nymph, then imported into a fresh database
|
|
850
|
+
* with this version. Sometimes, a live migration can be done instead, which
|
|
851
|
+
* will require no downtime. You should still export the database beforehand,
|
|
852
|
+
* in case anything goes wrong.
|
|
853
|
+
*
|
|
854
|
+
* 'json' means the data tables are missing the JSON field, and the DB
|
|
855
|
+
* **must** be exported and imported to fix it.
|
|
856
|
+
*
|
|
857
|
+
* 'tokens' means the FTS token tables are missing, and you can get away with
|
|
858
|
+
* a live migration with `liveMigration('tokenTables')` and doing
|
|
859
|
+
* `importEntityTokens(entity)` for each entity.
|
|
860
|
+
*
|
|
861
|
+
* 'tilmeldColumns' means the entity columns for "user", "group", and access
|
|
862
|
+
* controls are missing, and you can get away with a live migration with
|
|
863
|
+
* `liveMigration('tilmeldColumns')` and doing
|
|
864
|
+
* `importEntityTilmeldAC(entity)` for each entity. DO NOT update to any
|
|
865
|
+
* version later than 1.0.0-beta.109 before this is done, or access controls
|
|
866
|
+
* WILL NOT work, and everyone will have access to every entity!
|
|
867
|
+
*/
|
|
868
|
+
async needsMigration() {
|
|
869
|
+
return await this.driver.needsMigration();
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Perform a live migration on the DB.
|
|
873
|
+
*
|
|
874
|
+
* A 'tokenTables' migration will simply add the missing token tables. It will
|
|
875
|
+
* **not** fill them. You must use `importEntityTokens` on each entity after
|
|
876
|
+
* running this to fill the token tables and enable full text search matching
|
|
877
|
+
* on existing entities.
|
|
878
|
+
*
|
|
879
|
+
* A 'tilmeldColumns' migration will simply add the missing Tilmeld columns
|
|
880
|
+
* and indexes to each entity table. It will **not** fill them, and you must
|
|
881
|
+
* fill them before updating to the latest version of Nymph, or everyone will
|
|
882
|
+
* be able to access every entity! You must use `importEntityTilmeldAC` on
|
|
883
|
+
* each entity after running this.
|
|
884
|
+
*
|
|
885
|
+
* A 'tilmeldRemoveOldRows' migration will remove all of the old access
|
|
886
|
+
* control rows in the data tables that are no longer used as of
|
|
887
|
+
* 1.0.0-beta.110. MAKE SURE YOU HAVE RUN `importEntityTilmeldAC` ON ALL
|
|
888
|
+
* ENTITIES BEFORE RUNNING THIS, OR THE DATA WILL BE IRRETRIEVABLY LOST!
|
|
889
|
+
*/
|
|
890
|
+
async liveMigration(migrationType) {
|
|
891
|
+
return await this.driver.liveMigration(migrationType);
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Returns a list of the etypes stored in the DB.
|
|
895
|
+
*/
|
|
896
|
+
async getEtypes() {
|
|
897
|
+
return await this.driver.getEtypes();
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Get all the indexes defined for a specific etype.
|
|
901
|
+
*
|
|
902
|
+
* @param etype The etype to get indexes for.
|
|
903
|
+
*/
|
|
904
|
+
async getIndexes(etype) {
|
|
905
|
+
return await this.driver.getIndexes(etype);
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Add an index on a specific property to an etype.
|
|
909
|
+
*
|
|
910
|
+
* Nymph adds general indexes on etypes, but they span across all properties,
|
|
911
|
+
* so they can only speed up a query so much. You can add indexes that are
|
|
912
|
+
* specific to a property with this function, which will speed up queries for
|
|
913
|
+
* that property. If an etype only has one or two properties, you may not need
|
|
914
|
+
* a specific index, but if your etypes include many properties or properties
|
|
915
|
+
* that generally contain large amounts of data, adding a specific index can
|
|
916
|
+
* drastically improve the performance of your queries.
|
|
917
|
+
*
|
|
918
|
+
* The scope of the index will affect which queries it will speed up:
|
|
919
|
+
*
|
|
920
|
+
* - data: This will speed up comparisons and equality checks in queries.
|
|
921
|
+
* - references: This will speed up ref and qref clauses in queries.
|
|
922
|
+
* - tokens: This will speed up search clauses in queries.
|
|
923
|
+
*
|
|
924
|
+
* The name must be unique within the etype and scope or the existing index
|
|
925
|
+
* will be overwritten.
|
|
926
|
+
*
|
|
927
|
+
* You do not need to add reference indexes for the "user" and "group"
|
|
928
|
+
* properties, because these are already specifically indexed by Nymph.
|
|
929
|
+
*
|
|
930
|
+
* @param etype The etype to add the index on.
|
|
931
|
+
* @param definition The definition of the index.
|
|
932
|
+
*/
|
|
933
|
+
async addIndex(etype, definition) {
|
|
934
|
+
this.driver.checkIndexName(definition.name);
|
|
935
|
+
return await this.driver.addIndex(etype, definition);
|
|
409
936
|
}
|
|
410
|
-
|
|
411
|
-
|
|
937
|
+
/**
|
|
938
|
+
* Delete an index from an etype.
|
|
939
|
+
*
|
|
940
|
+
* @param etype The etype to delete the index on.
|
|
941
|
+
* @param scope The scope of the index.
|
|
942
|
+
* @param name The name of the index to delete.
|
|
943
|
+
*/
|
|
944
|
+
async deleteIndex(etype, scope, name) {
|
|
945
|
+
this.driver.checkIndexName(name);
|
|
946
|
+
return await this.driver.deleteIndex(etype, scope, name);
|
|
412
947
|
}
|
|
413
948
|
on(event, callback) {
|
|
414
949
|
const prop = (event + 'Callbacks');
|
|
415
950
|
if (!(prop in this)) {
|
|
416
951
|
throw new Error('Invalid event type.');
|
|
417
952
|
}
|
|
953
|
+
// @ts-ignore: The callback should be the right type here.
|
|
418
954
|
this[prop].push(callback);
|
|
419
955
|
return () => this.off(event, callback);
|
|
420
956
|
}
|
|
@@ -423,12 +959,13 @@ class Nymph {
|
|
|
423
959
|
if (!(prop in this)) {
|
|
424
960
|
return false;
|
|
425
961
|
}
|
|
962
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
426
963
|
const i = this[prop].indexOf(callback);
|
|
427
964
|
if (i > -1) {
|
|
965
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
428
966
|
this[prop].splice(i, 1);
|
|
429
967
|
}
|
|
430
968
|
return true;
|
|
431
969
|
}
|
|
432
970
|
}
|
|
433
|
-
exports.default = Nymph;
|
|
434
971
|
//# sourceMappingURL=Nymph.js.map
|