@nymphjs/nymph 1.0.0-beta.13 → 1.0.0-beta.131
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 +650 -0
- package/README.md +34 -24
- package/dist/Entity.d.ts +306 -15
- package/dist/Entity.js +688 -226
- 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 +646 -252
- 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/Entity.js
CHANGED
|
@@ -1,32 +1,228 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
import { difference, intersection, isEqual } from 'lodash-es';
|
|
2
|
+
import { guid } from '@nymphjs/guid';
|
|
3
|
+
import { EntityConflictError, EntityIsSleepingReferenceError, InvalidParametersError, InvalidStateError, MethodFailedError, } from './errors/index.js';
|
|
4
|
+
import { classNamesToEntityConstructors, entitiesToReferences, entityConstructorsToClassNames, referencesToEntities, uniqueStrings, } from './utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* Database abstraction object.
|
|
7
|
+
*
|
|
8
|
+
* Provides a way to access, manipulate, and store data in Nymph.
|
|
9
|
+
*
|
|
10
|
+
* The GUID is not set until the entity is saved. GUIDs must be unique forever,
|
|
11
|
+
* even after deletion. It's the job of the Nymph DB driver to make sure no two
|
|
12
|
+
* entities ever have the same GUID. This is generally done by using a large
|
|
13
|
+
* randomly generated ID.
|
|
14
|
+
*
|
|
15
|
+
* Each entity class has an etype that determines which table(s) in the database
|
|
16
|
+
* it belongs to. If two entity classes have the same etype, their data will be
|
|
17
|
+
* stored in the same table(s). This isn't a good idea, however, because
|
|
18
|
+
* references to an entity store a class name, not an etype.
|
|
19
|
+
*
|
|
20
|
+
* Tags are used to classify entities. Where an etype is used to separate data
|
|
21
|
+
* by tables, tags can be used to separate entities within a table. You can
|
|
22
|
+
* define specific tags to be protected, meaning they cannot be added/removed
|
|
23
|
+
* from the client. It can be useful to allow user defined tags, such as for
|
|
24
|
+
* blog posts.
|
|
25
|
+
*
|
|
26
|
+
* Simply calling $delete() will not unset the entity. It will still take up
|
|
27
|
+
* memory. Likewise, simply calling unset will not delete the entity from the
|
|
28
|
+
* DB.
|
|
29
|
+
*
|
|
30
|
+
* Some notes about $equals() and $is(), the replacements for "==":
|
|
31
|
+
*
|
|
32
|
+
* The == operator will likely not give you the result you want, since two
|
|
33
|
+
* instances of the same entity will fail that check, even though they represent
|
|
34
|
+
* the same data in the database.
|
|
35
|
+
*
|
|
36
|
+
* $equals() performs a more strict comparison of the entity to another. Use
|
|
37
|
+
* $equals() instead of the == operator when you want to check both the entities
|
|
38
|
+
* they represent, and the data inside them. In order to return true for
|
|
39
|
+
* $equals(), the entity and object must meet the following criteria:
|
|
40
|
+
*
|
|
41
|
+
* - They must be entities.
|
|
42
|
+
* - They must have equal GUIDs, or both must have no GUID.
|
|
43
|
+
* - Their data and tags must be equal.
|
|
44
|
+
*
|
|
45
|
+
* $is() performs a less strict comparison of the entity to another. Use $is()
|
|
46
|
+
* instead of the == operator when the entity's data may have been changed, but
|
|
47
|
+
* you only care if they represent the same entity. In order to return true, the
|
|
48
|
+
* entity and object must meet the following criteria:
|
|
49
|
+
*
|
|
50
|
+
* - They must be entities.
|
|
51
|
+
* - They must have equal GUIDs, or both must have no GUID.
|
|
52
|
+
* - If they have no GUIDs, their data and tags must be equal.
|
|
53
|
+
*
|
|
54
|
+
* Some notes about saving entities in other entity's properties:
|
|
55
|
+
*
|
|
56
|
+
* Entities use references in the DB to store an entity in their properties. The
|
|
57
|
+
* reference is stored as an array with the values:
|
|
58
|
+
*
|
|
59
|
+
* - 0 => The string 'nymph_entity_reference'
|
|
60
|
+
* - 1 => The referenced entity's GUID.
|
|
61
|
+
* - 2 => The referenced entity's class name.
|
|
62
|
+
*
|
|
63
|
+
* Since the referenced entity's class name (meaning the `class` static
|
|
64
|
+
* property, not the name of the class itself) is stored in the reference on the
|
|
65
|
+
* parent entity, if you change the class name in an update, you need to
|
|
66
|
+
* reassign all referenced entities of that class and resave.
|
|
67
|
+
*
|
|
68
|
+
* When an entity is loaded, it does not request its referenced entities from
|
|
69
|
+
* Nymph. Instead, it creates instances without data called sleeping references.
|
|
70
|
+
* When you first access an entity's data, if it is a sleeping reference, it
|
|
71
|
+
* will fill its data from the DB. You can call clearCache() to turn all the
|
|
72
|
+
* entities back into sleeping references.
|
|
73
|
+
*/
|
|
74
|
+
export default class Entity {
|
|
75
|
+
/**
|
|
76
|
+
* The instance of Nymph to use for queries.
|
|
77
|
+
*/
|
|
78
|
+
static nymph = {};
|
|
79
|
+
/**
|
|
80
|
+
* A unique name for this type of entity used to separate its data from other
|
|
81
|
+
* types of entities in the database.
|
|
82
|
+
*/
|
|
83
|
+
static ETYPE = 'entity';
|
|
84
|
+
/**
|
|
85
|
+
* The lookup name for this entity.
|
|
86
|
+
*
|
|
87
|
+
* This is used for reference arrays (and sleeping references) and client
|
|
88
|
+
* requests.
|
|
89
|
+
*/
|
|
90
|
+
static class = 'Entity';
|
|
91
|
+
$nymph;
|
|
92
|
+
guid = null;
|
|
93
|
+
cdate = null;
|
|
94
|
+
mdate = null;
|
|
95
|
+
tags = [];
|
|
96
|
+
/**
|
|
97
|
+
* The data proxy handler.
|
|
98
|
+
*/
|
|
99
|
+
$dataHandler;
|
|
100
|
+
/**
|
|
101
|
+
* The actual data store.
|
|
102
|
+
*/
|
|
103
|
+
$dataStore;
|
|
104
|
+
/**
|
|
105
|
+
* The actual sdata store.
|
|
106
|
+
*/
|
|
107
|
+
$sdata;
|
|
108
|
+
/**
|
|
109
|
+
* The data proxy object.
|
|
110
|
+
*/
|
|
111
|
+
$data;
|
|
112
|
+
/**
|
|
113
|
+
* Whether this instance is a sleeping reference.
|
|
114
|
+
*/
|
|
115
|
+
$isASleepingReference = false;
|
|
116
|
+
/**
|
|
117
|
+
* The reference to use to wake.
|
|
118
|
+
*/
|
|
119
|
+
$sleepingReference = null;
|
|
120
|
+
/**
|
|
121
|
+
* A promise that resolved when the entity's data is wake.
|
|
122
|
+
*/
|
|
123
|
+
$wakePromise = null;
|
|
124
|
+
/**
|
|
125
|
+
* Properties that will not be serialized into JSON with toJSON(). This
|
|
126
|
+
* can be considered a denylist, because these properties will not be set
|
|
127
|
+
* with incoming JSON.
|
|
128
|
+
*
|
|
129
|
+
* Clients CAN still determine what is in these properties, unless they are
|
|
130
|
+
* also listed in searchRestrictedData.
|
|
131
|
+
*/
|
|
132
|
+
$privateData = [];
|
|
133
|
+
/**
|
|
134
|
+
* Whether this entity should publish changes to PubSub servers.
|
|
135
|
+
*/
|
|
136
|
+
static pubSubEnabled = true;
|
|
137
|
+
/**
|
|
138
|
+
* Whether this entity should be accessible on the frontend through the REST
|
|
139
|
+
* server.
|
|
140
|
+
*
|
|
141
|
+
* If this is false, any request from the client that attempts to use this
|
|
142
|
+
* entity will fail.
|
|
143
|
+
*/
|
|
144
|
+
static restEnabled = true;
|
|
145
|
+
/**
|
|
146
|
+
* Properties that will not be searchable from the frontend. If the frontend
|
|
147
|
+
* includes any of these properties in any of their clauses, they will be
|
|
148
|
+
* filtered out before the search is executed.
|
|
149
|
+
*/
|
|
150
|
+
static searchRestrictedData = [];
|
|
151
|
+
/**
|
|
152
|
+
* Properties that can only be modified by server side code. They will still
|
|
153
|
+
* be visible on the frontend, unlike $privateData, but any changes to them
|
|
154
|
+
* that come from the frontend will be ignored.
|
|
155
|
+
*
|
|
156
|
+
* In addition to what's listed here, all of the access control properties
|
|
157
|
+
* will be included when Tilmeld is being used. These are:
|
|
158
|
+
*
|
|
159
|
+
* - acUser
|
|
160
|
+
* - acGroup
|
|
161
|
+
* - acOther
|
|
162
|
+
* - acRead
|
|
163
|
+
* - acWrite
|
|
164
|
+
* - acFull
|
|
165
|
+
* - user
|
|
166
|
+
* - group
|
|
167
|
+
*
|
|
168
|
+
* You should modify these through client enabled methods or the $save method
|
|
169
|
+
* instead, for safety.
|
|
170
|
+
*/
|
|
171
|
+
$protectedData = [];
|
|
172
|
+
/**
|
|
173
|
+
* If this is defined, then it lists the only properties that will be
|
|
174
|
+
* accepted from incoming JSON. Any other properties will be ignored.
|
|
175
|
+
*
|
|
176
|
+
* If you use an allowlist, you don't need to use protectedData, since you
|
|
177
|
+
* can simply leave those entries out of allowlistData.
|
|
178
|
+
*/
|
|
179
|
+
$allowlistData;
|
|
180
|
+
/**
|
|
181
|
+
* Tags that can only be added/removed by server side code. They will still be
|
|
182
|
+
* visible on the frontend, but any changes to them that come from the
|
|
183
|
+
* frontend will be ignored.
|
|
184
|
+
*/
|
|
185
|
+
$protectedTags = [];
|
|
186
|
+
/**
|
|
187
|
+
* If this is defined, then it lists the only tags that will be accepted from
|
|
188
|
+
* incoming JSON. Any other tags will be ignored.
|
|
189
|
+
*/
|
|
190
|
+
$allowlistTags;
|
|
191
|
+
/**
|
|
192
|
+
* The names of methods allowed to be called by the frontend with serverCall.
|
|
193
|
+
*/
|
|
194
|
+
$clientEnabledMethods = [];
|
|
195
|
+
/**
|
|
196
|
+
* The names of static methods allowed to be called by the frontend with
|
|
197
|
+
* serverCallStatic.
|
|
198
|
+
*/
|
|
199
|
+
static clientEnabledStaticMethods = [];
|
|
200
|
+
/**
|
|
201
|
+
* Whether to use "skipAc" when accessing entity references.
|
|
202
|
+
*/
|
|
203
|
+
$skipAc = false;
|
|
204
|
+
/**
|
|
205
|
+
* The AC properties' values when the entity was loaded.
|
|
206
|
+
*/
|
|
207
|
+
$originalAcValues = null;
|
|
208
|
+
/**
|
|
209
|
+
* This is used to hold a generated GUID for a new entity.
|
|
210
|
+
*/
|
|
211
|
+
$guaranteedGUID = null;
|
|
212
|
+
/**
|
|
213
|
+
* Initialize an entity.
|
|
214
|
+
*/
|
|
215
|
+
constructor(..._rest) {
|
|
20
216
|
this.$nymph = this.constructor.nymph;
|
|
21
217
|
this.$dataHandler = {
|
|
22
218
|
has: (data, name) => {
|
|
23
|
-
this.$
|
|
219
|
+
this.$check();
|
|
24
220
|
return name in data || name in this.$sdata;
|
|
25
221
|
},
|
|
26
222
|
get: (data, name) => {
|
|
27
|
-
this.$
|
|
223
|
+
this.$check();
|
|
28
224
|
if (this.$sdata.hasOwnProperty(name)) {
|
|
29
|
-
data[name] = (
|
|
225
|
+
data[name] = referencesToEntities(classNamesToEntityConstructors(this.$nymph, JSON.parse(this.$sdata[name]), false), this.$nymph, this.$skipAc);
|
|
30
226
|
delete this.$sdata[name];
|
|
31
227
|
}
|
|
32
228
|
if (data.hasOwnProperty(name)) {
|
|
@@ -35,7 +231,7 @@ class Entity {
|
|
|
35
231
|
return undefined;
|
|
36
232
|
},
|
|
37
233
|
set: (data, name, value) => {
|
|
38
|
-
this.$
|
|
234
|
+
this.$check();
|
|
39
235
|
if (this.$sdata.hasOwnProperty(name)) {
|
|
40
236
|
delete this.$sdata[name];
|
|
41
237
|
}
|
|
@@ -43,7 +239,7 @@ class Entity {
|
|
|
43
239
|
return true;
|
|
44
240
|
},
|
|
45
241
|
deleteProperty: (data, name) => {
|
|
46
|
-
this.$
|
|
242
|
+
this.$check();
|
|
47
243
|
if (this.$sdata.hasOwnProperty(name)) {
|
|
48
244
|
return delete this.$sdata[name];
|
|
49
245
|
}
|
|
@@ -53,7 +249,7 @@ class Entity {
|
|
|
53
249
|
return true;
|
|
54
250
|
},
|
|
55
251
|
defineProperty: (data, name, descriptor) => {
|
|
56
|
-
this.$
|
|
252
|
+
this.$check();
|
|
57
253
|
if (this.$sdata.hasOwnProperty(name)) {
|
|
58
254
|
delete this.$sdata[name];
|
|
59
255
|
}
|
|
@@ -61,119 +257,123 @@ class Entity {
|
|
|
61
257
|
return true;
|
|
62
258
|
},
|
|
63
259
|
getOwnPropertyDescriptor: (data, name) => {
|
|
64
|
-
this.$
|
|
260
|
+
this.$check();
|
|
65
261
|
if (this.$sdata.hasOwnProperty(name)) {
|
|
66
|
-
data[name] = (
|
|
262
|
+
data[name] = referencesToEntities(classNamesToEntityConstructors(this.$nymph, JSON.parse(this.$sdata[name]), false), this.$nymph, this.$skipAc);
|
|
67
263
|
delete this.$sdata[name];
|
|
68
264
|
}
|
|
69
265
|
return Object.getOwnPropertyDescriptor(data, name);
|
|
70
266
|
},
|
|
71
267
|
ownKeys: (data) => {
|
|
72
|
-
this.$
|
|
268
|
+
this.$check();
|
|
73
269
|
return Object.getOwnPropertyNames(data).concat(Object.getOwnPropertyNames(this.$sdata));
|
|
74
270
|
},
|
|
75
271
|
};
|
|
76
272
|
this.$dataStore = {};
|
|
77
273
|
this.$sdata = {};
|
|
78
274
|
this.$data = new Proxy(this.$dataStore, this.$dataHandler);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
has: (entity, name) => {
|
|
91
|
-
if (typeof name !== 'string' ||
|
|
92
|
-
name in entity ||
|
|
93
|
-
name.substring(0, 1) === '$') {
|
|
94
|
-
return name in entity;
|
|
95
|
-
}
|
|
96
|
-
this.$referenceWake();
|
|
97
|
-
return name in entity.$data;
|
|
98
|
-
},
|
|
99
|
-
get: (entity, name) => {
|
|
100
|
-
if (typeof name !== 'string' ||
|
|
101
|
-
name in entity ||
|
|
102
|
-
name.substring(0, 1) === '$') {
|
|
103
|
-
if (name === 'tags' || name === 'cdate' || name === 'mdate') {
|
|
104
|
-
this.$referenceWake();
|
|
275
|
+
return this.$proxy();
|
|
276
|
+
}
|
|
277
|
+
$existingProxy;
|
|
278
|
+
$proxy() {
|
|
279
|
+
if (this.$existingProxy == null) {
|
|
280
|
+
this.$existingProxy = new Proxy(this, {
|
|
281
|
+
has: (entity, name) => {
|
|
282
|
+
if (typeof name !== 'string' ||
|
|
283
|
+
name in entity ||
|
|
284
|
+
name.substring(0, 1) === '$') {
|
|
285
|
+
return name in entity;
|
|
105
286
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
name.substring(0, 1) === '$') {
|
|
118
|
-
if (name === 'tags' || name === 'cdate' || name === 'mdate') {
|
|
119
|
-
this.$referenceWake();
|
|
287
|
+
this.$check();
|
|
288
|
+
return name in entity.$data;
|
|
289
|
+
},
|
|
290
|
+
get: (entity, name) => {
|
|
291
|
+
if (typeof name !== 'string' ||
|
|
292
|
+
name in entity ||
|
|
293
|
+
name.substring(0, 1) === '$') {
|
|
294
|
+
if (name === 'tags' || name === 'cdate' || name === 'mdate') {
|
|
295
|
+
this.$check();
|
|
296
|
+
}
|
|
297
|
+
return entity[name];
|
|
120
298
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this.$referenceWake();
|
|
125
|
-
entity.$data[name] = value;
|
|
126
|
-
}
|
|
127
|
-
return true;
|
|
128
|
-
},
|
|
129
|
-
deleteProperty: (entity, name) => {
|
|
130
|
-
if (name in entity) {
|
|
131
|
-
return delete entity[name];
|
|
132
|
-
}
|
|
133
|
-
else if (name in entity.$data) {
|
|
134
|
-
this.$referenceWake();
|
|
135
|
-
return delete entity.$data[name];
|
|
136
|
-
}
|
|
137
|
-
return true;
|
|
138
|
-
},
|
|
139
|
-
getPrototypeOf: (entity) => {
|
|
140
|
-
return entity.constructor.prototype;
|
|
141
|
-
},
|
|
142
|
-
defineProperty: (entity, name, descriptor) => {
|
|
143
|
-
if (typeof name !== 'string' ||
|
|
144
|
-
name in entity ||
|
|
145
|
-
name.substring(0, 1) === '$') {
|
|
146
|
-
if (name === 'tags' || name === 'cdate' || name === 'mdate') {
|
|
147
|
-
this.$referenceWake();
|
|
299
|
+
this.$check();
|
|
300
|
+
if (name in entity.$data) {
|
|
301
|
+
return entity.$data[name];
|
|
148
302
|
}
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
name in entity ||
|
|
160
|
-
name.substring(0, 1) === '$') {
|
|
161
|
-
if (name === 'tags' || name === 'cdate' || name === 'mdate') {
|
|
162
|
-
this.$referenceWake();
|
|
303
|
+
return undefined;
|
|
304
|
+
},
|
|
305
|
+
set: (entity, name, value) => {
|
|
306
|
+
if (typeof name !== 'string' ||
|
|
307
|
+
name in entity ||
|
|
308
|
+
name.substring(0, 1) === '$') {
|
|
309
|
+
if (name === 'tags' || name === 'cdate' || name === 'mdate') {
|
|
310
|
+
this.$check();
|
|
311
|
+
}
|
|
312
|
+
entity[name] = value;
|
|
163
313
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
314
|
+
else {
|
|
315
|
+
this.$check();
|
|
316
|
+
entity.$data[name] = value;
|
|
317
|
+
}
|
|
318
|
+
return true;
|
|
319
|
+
},
|
|
320
|
+
deleteProperty: (entity, name) => {
|
|
321
|
+
if (name in entity) {
|
|
322
|
+
return delete entity[name];
|
|
323
|
+
}
|
|
324
|
+
else if (name in entity.$data) {
|
|
325
|
+
this.$check();
|
|
326
|
+
return delete entity.$data[name];
|
|
327
|
+
}
|
|
328
|
+
return true;
|
|
329
|
+
},
|
|
330
|
+
getPrototypeOf: (entity) => {
|
|
331
|
+
return entity.constructor.prototype;
|
|
332
|
+
},
|
|
333
|
+
defineProperty: (entity, name, descriptor) => {
|
|
334
|
+
if (typeof name !== 'string' ||
|
|
335
|
+
name in entity ||
|
|
336
|
+
name.substring(0, 1) === '$') {
|
|
337
|
+
if (name === 'tags' || name === 'cdate' || name === 'mdate') {
|
|
338
|
+
this.$check();
|
|
339
|
+
}
|
|
340
|
+
Object.defineProperty(entity, name, descriptor);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
this.$check();
|
|
344
|
+
Object.defineProperty(entity.$data, name, descriptor);
|
|
345
|
+
}
|
|
346
|
+
return true;
|
|
347
|
+
},
|
|
348
|
+
getOwnPropertyDescriptor: (entity, name) => {
|
|
349
|
+
if (typeof name !== 'string' ||
|
|
350
|
+
name in entity ||
|
|
351
|
+
name.substring(0, 1) === '$') {
|
|
352
|
+
if (name === 'tags' || name === 'cdate' || name === 'mdate') {
|
|
353
|
+
this.$check();
|
|
354
|
+
}
|
|
355
|
+
return Object.getOwnPropertyDescriptor(entity, name);
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
this.$check();
|
|
359
|
+
return Object.getOwnPropertyDescriptor(entity.$data, name);
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
ownKeys: (entity) => {
|
|
363
|
+
this.$check();
|
|
364
|
+
return Object.getOwnPropertyNames(entity).concat(Object.getOwnPropertyNames(entity.$data));
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
return this.$existingProxy;
|
|
176
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* Create or retrieve a new entity instance.
|
|
372
|
+
*
|
|
373
|
+
* Note that this will always return an entity, even if the GUID is not found.
|
|
374
|
+
*
|
|
375
|
+
* @param guid An optional GUID to retrieve.
|
|
376
|
+
*/
|
|
177
377
|
static async factory(guid) {
|
|
178
378
|
const entity = new this();
|
|
179
379
|
if (guid != null) {
|
|
@@ -186,43 +386,143 @@ class Entity {
|
|
|
186
386
|
}
|
|
187
387
|
return entity;
|
|
188
388
|
}
|
|
189
|
-
|
|
190
|
-
|
|
389
|
+
/**
|
|
390
|
+
* Create a new entity instance.
|
|
391
|
+
*/
|
|
392
|
+
static factorySync() {
|
|
393
|
+
return new this();
|
|
191
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* Create a new sleeping reference instance.
|
|
397
|
+
*
|
|
398
|
+
* Sleeping references won't retrieve their data from the database until they
|
|
399
|
+
* are readied with `$wake()` or a parent's `$wakeAll()`.
|
|
400
|
+
*
|
|
401
|
+
* @param reference The Nymph Entity Reference to use to wake.
|
|
402
|
+
* @returns The new instance.
|
|
403
|
+
*/
|
|
192
404
|
static factoryReference(reference) {
|
|
193
405
|
const entity = new this();
|
|
194
406
|
entity.$referenceSleep(reference);
|
|
195
407
|
return entity;
|
|
196
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Get an array of strings that **must** be unique across the current etype.
|
|
411
|
+
*
|
|
412
|
+
* When you try to save another entity with any of the same unique strings,
|
|
413
|
+
* Nymph will throw an error.
|
|
414
|
+
*
|
|
415
|
+
* The default implementation of this static method instantiates the entity,
|
|
416
|
+
* assigns all of the given data, then calls `$getUniques` and returns its
|
|
417
|
+
* output. This can have a performance impact if a lot of extra processing
|
|
418
|
+
* happens during any of these steps. You can override this method to
|
|
419
|
+
* calculate the unique strings faster, but you must return the same strings
|
|
420
|
+
* that would be returned by `$getUniques`.
|
|
421
|
+
*
|
|
422
|
+
* @returns Resolves to an array of entity's unique constraint strings.
|
|
423
|
+
*/
|
|
424
|
+
static async getUniques({ guid, cdate, mdate, tags, data, sdata, }) {
|
|
425
|
+
const entity = new this();
|
|
426
|
+
if (guid != null) {
|
|
427
|
+
entity.guid = guid;
|
|
428
|
+
}
|
|
429
|
+
if (cdate != null) {
|
|
430
|
+
entity.cdate = cdate;
|
|
431
|
+
}
|
|
432
|
+
if (mdate != null) {
|
|
433
|
+
entity.mdate = mdate;
|
|
434
|
+
}
|
|
435
|
+
entity.tags = tags;
|
|
436
|
+
entity.$putData(data, sdata);
|
|
437
|
+
return await entity.$getUniques();
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Get a string for full text search for one of an entity's properties.
|
|
441
|
+
*
|
|
442
|
+
* The result will be tokenized and stored as the full text search index for
|
|
443
|
+
* use with "search" clauses.
|
|
444
|
+
*
|
|
445
|
+
* Return null to not include any tokens in the full text search storage.
|
|
446
|
+
*
|
|
447
|
+
* You shouldn't use any other entity data to transform the text, as it won't
|
|
448
|
+
* always be available. This function is meant for things like stripping HTML
|
|
449
|
+
* tags.
|
|
450
|
+
*
|
|
451
|
+
* @returns By default, returns the value if it is a string.
|
|
452
|
+
*/
|
|
453
|
+
static getFTSText(_name, value) {
|
|
454
|
+
if (typeof value === 'string') {
|
|
455
|
+
return value;
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
197
459
|
toJSON() {
|
|
198
460
|
if (this.$isASleepingReference) {
|
|
199
461
|
return this.$sleepingReference;
|
|
200
462
|
}
|
|
201
463
|
const obj = {
|
|
202
|
-
class: this.constructor
|
|
464
|
+
class: entityConstructorsToClassNames(this.constructor),
|
|
203
465
|
guid: this.guid,
|
|
204
466
|
cdate: this.cdate,
|
|
205
467
|
mdate: this.mdate,
|
|
206
468
|
tags: [...this.tags],
|
|
207
469
|
data: {},
|
|
208
470
|
};
|
|
209
|
-
const
|
|
210
|
-
|
|
471
|
+
for (const key of [
|
|
472
|
+
...Object.keys(this.$dataStore),
|
|
473
|
+
...Object.keys(this.$sdata),
|
|
474
|
+
]) {
|
|
211
475
|
if (this.$privateData.indexOf(key) === -1) {
|
|
212
|
-
obj.data[key] = (
|
|
476
|
+
obj.data[key] = entityConstructorsToClassNames(entitiesToReferences(this.$data[key], true));
|
|
213
477
|
}
|
|
214
478
|
}
|
|
215
479
|
return obj;
|
|
216
480
|
}
|
|
481
|
+
$setNymph(nymph) {
|
|
482
|
+
this.$nymph = nymph;
|
|
483
|
+
if (!this.$asleep()) {
|
|
484
|
+
const recurseData = (data) => {
|
|
485
|
+
if (Array.isArray(data)) {
|
|
486
|
+
for (let name in data) {
|
|
487
|
+
recurseData(data[name]);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
else if (data instanceof Entity) {
|
|
491
|
+
if (data.$nymph !== nymph) {
|
|
492
|
+
data.$setNymph(nymph);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
else if (typeof data === 'function' &&
|
|
496
|
+
data.prototype instanceof Entity) {
|
|
497
|
+
data.nymph = nymph;
|
|
498
|
+
}
|
|
499
|
+
else if (typeof data === 'object') {
|
|
500
|
+
for (let name in data) {
|
|
501
|
+
recurseData(data[name]);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
recurseData(this.$data);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
$getGuaranteedGUID() {
|
|
509
|
+
if (this.guid !== null) {
|
|
510
|
+
return this.guid;
|
|
511
|
+
}
|
|
512
|
+
if (this.$guaranteedGUID == null) {
|
|
513
|
+
this.$guaranteedGUID = guid();
|
|
514
|
+
}
|
|
515
|
+
return this.$guaranteedGUID;
|
|
516
|
+
}
|
|
217
517
|
$addTag(...tags) {
|
|
218
|
-
this.$
|
|
518
|
+
this.$check();
|
|
219
519
|
if (tags.length < 1) {
|
|
220
520
|
return;
|
|
221
521
|
}
|
|
222
|
-
this.tags =
|
|
522
|
+
this.tags = uniqueStrings([...this.tags, ...tags]);
|
|
223
523
|
}
|
|
224
524
|
$arraySearch(array, strict = false) {
|
|
225
|
-
this.$
|
|
525
|
+
this.$check();
|
|
226
526
|
if (!Array.isArray(array)) {
|
|
227
527
|
return -1;
|
|
228
528
|
}
|
|
@@ -235,18 +535,20 @@ class Entity {
|
|
|
235
535
|
return -1;
|
|
236
536
|
}
|
|
237
537
|
$clearCache() {
|
|
238
|
-
this.$
|
|
239
|
-
this.$putData(this.$getData(), this.$getSData());
|
|
538
|
+
this.$check();
|
|
539
|
+
this.$putData(this.$getData(false), this.$getSData());
|
|
240
540
|
}
|
|
241
541
|
$getClientEnabledMethods() {
|
|
242
542
|
return this.$clientEnabledMethods;
|
|
243
543
|
}
|
|
244
544
|
async $delete() {
|
|
245
|
-
this.$
|
|
246
|
-
|
|
545
|
+
await this.$wake();
|
|
546
|
+
if (!(await this.$nymph.deleteEntity(this))) {
|
|
547
|
+
throw new MethodFailedError('Failed to delete entity.');
|
|
548
|
+
}
|
|
247
549
|
}
|
|
248
550
|
$equals(object) {
|
|
249
|
-
this.$
|
|
551
|
+
this.$check();
|
|
250
552
|
if (!(object instanceof Entity)) {
|
|
251
553
|
return false;
|
|
252
554
|
}
|
|
@@ -263,41 +565,79 @@ class Entity {
|
|
|
263
565
|
}
|
|
264
566
|
const obTags = [...object.tags].sort();
|
|
265
567
|
const myTags = [...this.tags].sort();
|
|
266
|
-
if (!
|
|
568
|
+
if (!isEqual(obTags, myTags)) {
|
|
267
569
|
return false;
|
|
268
570
|
}
|
|
269
|
-
const obData = object.$getData(true);
|
|
270
|
-
const myData = this.$getData(true);
|
|
271
|
-
return
|
|
571
|
+
const obData = entityConstructorsToClassNames(entitiesToReferences(object.$getData(true), true));
|
|
572
|
+
const myData = entityConstructorsToClassNames(entitiesToReferences(this.$getData(true), true));
|
|
573
|
+
return isEqual(obData, myData);
|
|
272
574
|
}
|
|
273
575
|
$getData(includeSData = false) {
|
|
274
|
-
this.$
|
|
576
|
+
this.$check();
|
|
275
577
|
if (includeSData) {
|
|
578
|
+
// Access all the serialized properties to initialize them.
|
|
276
579
|
for (const key in this.$sdata) {
|
|
277
580
|
const _unused = this[key];
|
|
278
581
|
}
|
|
279
582
|
}
|
|
280
|
-
return
|
|
583
|
+
return { ...this.$dataStore };
|
|
281
584
|
}
|
|
282
585
|
$getSData() {
|
|
283
|
-
this.$
|
|
586
|
+
this.$check();
|
|
284
587
|
return this.$sdata;
|
|
285
588
|
}
|
|
589
|
+
async $getUniques() {
|
|
590
|
+
return [];
|
|
591
|
+
}
|
|
286
592
|
$getOriginalAcValues() {
|
|
287
|
-
this.$
|
|
288
|
-
return
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
593
|
+
this.$check();
|
|
594
|
+
return this.$originalAcValues ?? this.$getCurrentAcValues();
|
|
595
|
+
}
|
|
596
|
+
$getCurrentAcValues() {
|
|
597
|
+
this.$check();
|
|
598
|
+
return {
|
|
599
|
+
user: this.$getAcUid(),
|
|
600
|
+
group: this.$getAcGid(),
|
|
601
|
+
acUser: this.$data.acUser ?? null,
|
|
602
|
+
acGroup: this.$data.acGroup ?? null,
|
|
603
|
+
acOther: this.$data.acOther ?? null,
|
|
604
|
+
acRead: this.$getAcReadIds(),
|
|
605
|
+
acWrite: this.$getAcWriteIds(),
|
|
606
|
+
acFull: this.$getAcFullIds(),
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
$getAcUid() {
|
|
610
|
+
if ('user' in this.$sdata) {
|
|
611
|
+
const userValue = JSON.parse(this.$sdata.user);
|
|
612
|
+
if (Array.isArray(userValue) &&
|
|
613
|
+
userValue[0] === 'nymph_entity_reference') {
|
|
614
|
+
return userValue[1];
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return (this.$data.user?.guid ?? null);
|
|
618
|
+
}
|
|
619
|
+
$getAcGid() {
|
|
620
|
+
if ('group' in this.$sdata) {
|
|
621
|
+
const groupValue = JSON.parse(this.$sdata.group);
|
|
622
|
+
if (Array.isArray(groupValue) &&
|
|
623
|
+
groupValue[0] === 'nymph_entity_reference') {
|
|
624
|
+
return groupValue[1];
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return (this.$data.group?.guid ?? null);
|
|
628
|
+
}
|
|
629
|
+
$getAcReadIds() {
|
|
630
|
+
return (this.$data.acRead?.map((entity) => typeof entity === 'string' ? entity : entity.guid) ?? null);
|
|
631
|
+
}
|
|
632
|
+
$getAcWriteIds() {
|
|
633
|
+
return (this.$data.acWrite?.map((entity) => typeof entity === 'string' ? entity : entity.guid) ?? null);
|
|
634
|
+
}
|
|
635
|
+
$getAcFullIds() {
|
|
636
|
+
return (this.$data.acFull?.map((entity) => typeof entity === 'string' ? entity : entity.guid) ?? null);
|
|
298
637
|
}
|
|
299
638
|
$getValidatable() {
|
|
300
|
-
this.$
|
|
639
|
+
this.$check();
|
|
640
|
+
// Access all the serialized properties to initialize them.
|
|
301
641
|
for (const key in this.$sdata) {
|
|
302
642
|
const _unused = this[key];
|
|
303
643
|
}
|
|
@@ -306,15 +646,15 @@ class Entity {
|
|
|
306
646
|
cdate: this.cdate,
|
|
307
647
|
mdate: this.mdate,
|
|
308
648
|
tags: this.tags,
|
|
309
|
-
...
|
|
649
|
+
...this.$dataStore,
|
|
310
650
|
};
|
|
311
651
|
}
|
|
312
652
|
$getTags() {
|
|
313
|
-
this.$
|
|
653
|
+
this.$check();
|
|
314
654
|
return this.tags;
|
|
315
655
|
}
|
|
316
656
|
$hasTag(...tags) {
|
|
317
|
-
this.$
|
|
657
|
+
this.$check();
|
|
318
658
|
if (!tags.length) {
|
|
319
659
|
return false;
|
|
320
660
|
}
|
|
@@ -332,44 +672,50 @@ class Entity {
|
|
|
332
672
|
if (!(object instanceof Entity)) {
|
|
333
673
|
return false;
|
|
334
674
|
}
|
|
335
|
-
if (this
|
|
675
|
+
if (this === object) {
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
if (this.guid != null || object.guid != null) {
|
|
336
679
|
return this.guid === object.guid;
|
|
337
680
|
}
|
|
338
|
-
this.$
|
|
681
|
+
this.$check();
|
|
339
682
|
if (typeof object.$getData !== 'function') {
|
|
340
683
|
return false;
|
|
341
684
|
}
|
|
342
685
|
else {
|
|
343
686
|
const obTags = [...object.tags].sort();
|
|
344
687
|
const myTags = [...this.tags].sort();
|
|
345
|
-
if (!
|
|
688
|
+
if (!isEqual(obTags, myTags)) {
|
|
346
689
|
return false;
|
|
347
690
|
}
|
|
348
|
-
const obData = object.$getData(true);
|
|
349
|
-
const myData = this.$getData(true);
|
|
350
|
-
return
|
|
691
|
+
const obData = entityConstructorsToClassNames(entitiesToReferences(object.$getData(true), true));
|
|
692
|
+
const myData = entityConstructorsToClassNames(entitiesToReferences(this.$getData(true), true));
|
|
693
|
+
return isEqual(obData, myData);
|
|
351
694
|
}
|
|
352
695
|
}
|
|
353
696
|
$jsonAcceptData(input, allowConflict = false) {
|
|
354
|
-
this.$
|
|
697
|
+
this.$check();
|
|
355
698
|
if (this.guid != input.guid) {
|
|
356
|
-
throw new
|
|
699
|
+
throw new EntityConflictError('Tried to accept JSON input for the wrong entity.');
|
|
357
700
|
}
|
|
701
|
+
// Accept the modified date.
|
|
358
702
|
const mdate = input.mdate ?? 0;
|
|
359
703
|
const thismdate = this.mdate ?? 0;
|
|
360
704
|
if (mdate < thismdate && !allowConflict) {
|
|
361
|
-
throw new
|
|
705
|
+
throw new EntityConflictError('This entity is newer than JSON input.');
|
|
362
706
|
}
|
|
363
707
|
this.mdate = input.mdate;
|
|
708
|
+
// Accept the tags.
|
|
364
709
|
const currentTags = this.$getTags();
|
|
365
|
-
const protectedTags =
|
|
366
|
-
let tags =
|
|
710
|
+
const protectedTags = intersection(this.$protectedTags, currentTags);
|
|
711
|
+
let tags = difference(input.tags, this.$protectedTags);
|
|
367
712
|
if (this.$allowlistTags != null) {
|
|
368
|
-
tags =
|
|
713
|
+
tags = intersection(tags, this.$allowlistTags);
|
|
369
714
|
}
|
|
370
715
|
this.$removeTag(...currentTags);
|
|
371
716
|
this.$addTag(...protectedTags, ...tags);
|
|
372
|
-
|
|
717
|
+
// Accept the data.
|
|
718
|
+
const data = { ...input.data };
|
|
373
719
|
const privateData = {};
|
|
374
720
|
for (const name of this.$privateData) {
|
|
375
721
|
if (name in this.$data) {
|
|
@@ -381,10 +727,21 @@ class Entity {
|
|
|
381
727
|
}
|
|
382
728
|
const protectedData = {};
|
|
383
729
|
const protectedProps = [...this.$protectedData];
|
|
384
|
-
if (this.$nymph.tilmeld
|
|
385
|
-
|
|
386
|
-
protectedProps.push('
|
|
387
|
-
protectedProps.push('
|
|
730
|
+
if (this.$nymph.tilmeld) {
|
|
731
|
+
protectedProps.push('acUser');
|
|
732
|
+
protectedProps.push('acGroup');
|
|
733
|
+
protectedProps.push('acOther');
|
|
734
|
+
protectedProps.push('acRead');
|
|
735
|
+
protectedProps.push('acWrite');
|
|
736
|
+
protectedProps.push('acFull');
|
|
737
|
+
if ((this.constructor.class !== 'User' &&
|
|
738
|
+
this.constructor.class !== 'Group') ||
|
|
739
|
+
!this.$nymph.tilmeld.currentUser ||
|
|
740
|
+
(!this.$nymph.tilmeld.currentUser.abilities?.includes('tilmeld/admin') &&
|
|
741
|
+
!this.$nymph.tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
742
|
+
protectedProps.push('user');
|
|
743
|
+
protectedProps.push('group');
|
|
744
|
+
}
|
|
388
745
|
}
|
|
389
746
|
for (const name of protectedProps) {
|
|
390
747
|
if (name in this.$data) {
|
|
@@ -414,29 +771,47 @@ class Entity {
|
|
|
414
771
|
});
|
|
415
772
|
}
|
|
416
773
|
$jsonAcceptPatch(patch, allowConflict = false) {
|
|
417
|
-
this.$
|
|
774
|
+
this.$check();
|
|
418
775
|
if (this.guid != patch.guid) {
|
|
419
|
-
throw new
|
|
776
|
+
throw new EntityConflictError('Tried to accept JSON patch for the wrong entity.');
|
|
420
777
|
}
|
|
778
|
+
// Accept the modified date.
|
|
421
779
|
const mdate = patch.mdate ?? 0;
|
|
422
780
|
const thismdate = this.mdate ?? 0;
|
|
423
781
|
if (mdate < thismdate && !allowConflict) {
|
|
424
|
-
throw new
|
|
782
|
+
throw new EntityConflictError('This entity is newer than JSON patch.');
|
|
425
783
|
}
|
|
426
784
|
this.mdate = patch.mdate;
|
|
785
|
+
const protectedProps = [...this.$protectedData];
|
|
786
|
+
if (this.$nymph.tilmeld) {
|
|
787
|
+
protectedProps.push('acUser');
|
|
788
|
+
protectedProps.push('acGroup');
|
|
789
|
+
protectedProps.push('acOther');
|
|
790
|
+
protectedProps.push('acRead');
|
|
791
|
+
protectedProps.push('acWrite');
|
|
792
|
+
protectedProps.push('acFull');
|
|
793
|
+
if ((this.constructor.class !== 'User' &&
|
|
794
|
+
this.constructor.class !== 'Group') ||
|
|
795
|
+
!this.$nymph.tilmeld.currentUser ||
|
|
796
|
+
(!this.$nymph.tilmeld.currentUser.abilities?.includes('tilmeld/admin') &&
|
|
797
|
+
!this.$nymph.tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
798
|
+
protectedProps.push('user');
|
|
799
|
+
protectedProps.push('group');
|
|
800
|
+
}
|
|
801
|
+
}
|
|
427
802
|
for (const name in patch.set) {
|
|
428
803
|
if ((this.$allowlistData != null &&
|
|
429
804
|
this.$allowlistData.indexOf(name) === -1) ||
|
|
430
|
-
|
|
805
|
+
protectedProps.indexOf(name) !== -1 ||
|
|
431
806
|
this.$privateData.indexOf(name) !== -1) {
|
|
432
807
|
continue;
|
|
433
808
|
}
|
|
434
|
-
this.$data[name] =
|
|
809
|
+
this.$data[name] = patch.set[name];
|
|
435
810
|
}
|
|
436
811
|
for (const name of patch.unset) {
|
|
437
812
|
if ((this.$allowlistData != null &&
|
|
438
813
|
this.$allowlistData.indexOf(name) === -1) ||
|
|
439
|
-
|
|
814
|
+
protectedProps.indexOf(name) !== -1 ||
|
|
440
815
|
this.$privateData.indexOf(name) !== -1) {
|
|
441
816
|
continue;
|
|
442
817
|
}
|
|
@@ -459,63 +834,154 @@ class Entity {
|
|
|
459
834
|
this.$removeTag(tag);
|
|
460
835
|
}
|
|
461
836
|
}
|
|
462
|
-
$putData(data, sdata) {
|
|
463
|
-
this.$
|
|
464
|
-
const mySdata = sdata ?? this.$getSData();
|
|
465
|
-
for (const name in data) {
|
|
466
|
-
delete mySdata[name];
|
|
467
|
-
}
|
|
837
|
+
$putData(data, sdata, _source) {
|
|
838
|
+
this.$check();
|
|
468
839
|
for (const name in this.$dataStore) {
|
|
469
840
|
delete this.$dataStore[name];
|
|
470
841
|
}
|
|
471
842
|
for (const name in data) {
|
|
472
|
-
this.$dataStore[name] =
|
|
843
|
+
this.$dataStore[name] = data[name];
|
|
473
844
|
}
|
|
474
|
-
this.$sdata =
|
|
845
|
+
this.$sdata = sdata ?? {};
|
|
846
|
+
// Set original AC values if not set..
|
|
475
847
|
if (this.$originalAcValues == null) {
|
|
476
|
-
this.$originalAcValues = this.$
|
|
848
|
+
this.$originalAcValues = this.$getCurrentAcValues();
|
|
477
849
|
}
|
|
478
850
|
}
|
|
851
|
+
/**
|
|
852
|
+
* Set up a sleeping reference.
|
|
853
|
+
* @param array $reference The reference to use to wake.
|
|
854
|
+
*/
|
|
479
855
|
$referenceSleep(reference) {
|
|
480
856
|
if (reference.length !== 3 ||
|
|
481
857
|
reference[0] !== 'nymph_entity_reference' ||
|
|
482
858
|
typeof reference[1] !== 'string' ||
|
|
483
859
|
typeof reference[2] !== 'string') {
|
|
484
|
-
throw new
|
|
860
|
+
throw new InvalidParametersError('referenceSleep expects parameter 1 to be a valid Nymph entity ' +
|
|
485
861
|
'reference.');
|
|
486
862
|
}
|
|
487
863
|
const thisClass = this.constructor.class;
|
|
488
864
|
if (reference[2] !== thisClass) {
|
|
489
|
-
throw new
|
|
865
|
+
throw new InvalidParametersError('referenceSleep can only be called with an entity reference of the ' +
|
|
490
866
|
`same class. Given class: ${reference[2]}; this class: ${thisClass}.`);
|
|
491
867
|
}
|
|
492
868
|
this.$isASleepingReference = true;
|
|
493
869
|
this.guid = reference[1];
|
|
494
870
|
this.$sleepingReference = reference;
|
|
495
871
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
872
|
+
/**
|
|
873
|
+
* Check if this is a sleeping reference and throw an error if so.
|
|
874
|
+
*/
|
|
875
|
+
$check() {
|
|
876
|
+
if (this.$asleep()) {
|
|
877
|
+
throw new EntityIsSleepingReferenceError('This entity is in a sleeping reference state. You must use .$wake() to wake it.');
|
|
499
878
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Check if this is a sleeping reference.
|
|
882
|
+
*/
|
|
883
|
+
$asleep() {
|
|
884
|
+
return this.$isASleepingReference || this.$sleepingReference != null;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Wake from a sleeping reference.
|
|
888
|
+
*/
|
|
889
|
+
$wake() {
|
|
890
|
+
if (!this.$asleep()) {
|
|
891
|
+
this.$wakePromise = null;
|
|
892
|
+
return Promise.resolve(this.$proxy());
|
|
507
893
|
}
|
|
508
|
-
this.$
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
this
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
894
|
+
if (this.$sleepingReference?.[1] == null) {
|
|
895
|
+
throw new InvalidStateError('Tried to wake a sleeping reference with no GUID.');
|
|
896
|
+
}
|
|
897
|
+
if (!this.$wakePromise) {
|
|
898
|
+
const EntityClass = this.$nymph.getEntityClass(this.$sleepingReference[2]);
|
|
899
|
+
this.$wakePromise = this.$nymph
|
|
900
|
+
.getEntity({
|
|
901
|
+
class: EntityClass,
|
|
902
|
+
skipAc: this.$skipAc,
|
|
903
|
+
}, { type: '&', guid: this.$sleepingReference[1] })
|
|
904
|
+
.then((entity) => {
|
|
905
|
+
if (entity == null || entity.guid == null) {
|
|
906
|
+
return Promise.reject(new InvalidStateError('The sleeping reference could not be retrieved.'));
|
|
907
|
+
}
|
|
908
|
+
this.$isASleepingReference = false;
|
|
909
|
+
this.$sleepingReference = null;
|
|
910
|
+
this.guid = entity.guid;
|
|
911
|
+
this.tags = entity.tags;
|
|
912
|
+
this.cdate = entity.cdate;
|
|
913
|
+
this.mdate = entity.mdate;
|
|
914
|
+
this.$putData(entity.$getData(false), entity.$getSData(), 'server');
|
|
915
|
+
return this.$proxy();
|
|
916
|
+
})
|
|
917
|
+
.finally(() => {
|
|
918
|
+
this.$wakePromise = null;
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
return this.$wakePromise;
|
|
922
|
+
}
|
|
923
|
+
$wakeAll(level) {
|
|
924
|
+
return new Promise((resolve, reject) => {
|
|
925
|
+
// Run this once this entity is wake.
|
|
926
|
+
const wakeProps = () => {
|
|
927
|
+
let newLevel;
|
|
928
|
+
// If level is undefined, keep going forever, otherwise, stop once we've
|
|
929
|
+
// gone deep enough.
|
|
930
|
+
if (level !== undefined) {
|
|
931
|
+
newLevel = level - 1;
|
|
932
|
+
}
|
|
933
|
+
if (newLevel !== undefined && newLevel < 0) {
|
|
934
|
+
resolve(this.$proxy());
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
const promises = [];
|
|
938
|
+
// Go through data looking for entities to wake.
|
|
939
|
+
const walkData = (data) => {
|
|
940
|
+
if (data == null) {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
else if (typeof data === 'function' &&
|
|
944
|
+
data.prototype instanceof Entity) {
|
|
945
|
+
// Ignore this, it is an entity class.
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
else if (data instanceof Entity) {
|
|
949
|
+
// Wake it up.
|
|
950
|
+
if (data.$isASleepingReference) {
|
|
951
|
+
promises.push(data.$wakeAll(newLevel));
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
else if (Array.isArray(data)) {
|
|
955
|
+
// Recurse into lower arrays.
|
|
956
|
+
for (let i = 0; i < data.length; i++) {
|
|
957
|
+
walkData(data[i]);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
else if (data instanceof Object) {
|
|
961
|
+
// Recurse into objects.
|
|
962
|
+
for (let [key, value] of Object.entries(data)) {
|
|
963
|
+
walkData(value);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
walkData(this.$data);
|
|
968
|
+
if (promises.length) {
|
|
969
|
+
Promise.all(promises).then(() => resolve(this.$proxy()), (errObj) => reject(errObj));
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
resolve(this.$proxy());
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
if (this.$isASleepingReference) {
|
|
976
|
+
this.$wake().then(wakeProps, (errObj) => reject(errObj));
|
|
977
|
+
}
|
|
978
|
+
else {
|
|
979
|
+
wakeProps();
|
|
980
|
+
}
|
|
981
|
+
});
|
|
516
982
|
}
|
|
517
983
|
async $refresh() {
|
|
518
|
-
this.$
|
|
984
|
+
await this.$wake();
|
|
519
985
|
if (this.guid == null) {
|
|
520
986
|
return false;
|
|
521
987
|
}
|
|
@@ -525,32 +991,34 @@ class Entity {
|
|
|
525
991
|
skipAc: this.$skipAc,
|
|
526
992
|
}, { type: '&', guid: this.guid });
|
|
527
993
|
if (refresh == null) {
|
|
528
|
-
|
|
994
|
+
throw new MethodFailedError("The entity's data could not be refreshed.");
|
|
529
995
|
}
|
|
530
996
|
this.tags = refresh.tags;
|
|
531
997
|
this.cdate = refresh.cdate;
|
|
532
998
|
this.mdate = refresh.mdate;
|
|
533
|
-
this.$putData(refresh.$getData(), refresh.$getSData());
|
|
999
|
+
this.$putData(refresh.$getData(false), refresh.$getSData(), 'server');
|
|
534
1000
|
return true;
|
|
535
1001
|
}
|
|
536
1002
|
$removeTag(...tags) {
|
|
537
|
-
this.$
|
|
538
|
-
this.tags =
|
|
1003
|
+
this.$check();
|
|
1004
|
+
this.tags = difference(this.tags, tags);
|
|
539
1005
|
}
|
|
540
1006
|
async $save() {
|
|
541
|
-
this.$
|
|
542
|
-
|
|
1007
|
+
await this.$wake();
|
|
1008
|
+
if (!(await this.$nymph.saveEntity(this))) {
|
|
1009
|
+
throw new MethodFailedError('Failed to save entity.');
|
|
1010
|
+
}
|
|
543
1011
|
}
|
|
544
|
-
$toReference() {
|
|
1012
|
+
$toReference(existingOnly) {
|
|
545
1013
|
if (this.$isASleepingReference && this.$sleepingReference != null) {
|
|
546
1014
|
return this.$sleepingReference;
|
|
547
1015
|
}
|
|
548
|
-
if (this.guid == null) {
|
|
549
|
-
return this;
|
|
1016
|
+
if (existingOnly && this.guid == null) {
|
|
1017
|
+
return this.$proxy();
|
|
550
1018
|
}
|
|
551
1019
|
return [
|
|
552
1020
|
'nymph_entity_reference',
|
|
553
|
-
this
|
|
1021
|
+
this.$getGuaranteedGUID(),
|
|
554
1022
|
this.constructor.class,
|
|
555
1023
|
];
|
|
556
1024
|
}
|
|
@@ -558,10 +1026,4 @@ class Entity {
|
|
|
558
1026
|
this.$skipAc = !!skipAc;
|
|
559
1027
|
}
|
|
560
1028
|
}
|
|
561
|
-
exports.default = Entity;
|
|
562
|
-
Entity.nymph = {};
|
|
563
|
-
Entity.ETYPE = 'entity';
|
|
564
|
-
Entity.class = 'Entity';
|
|
565
|
-
Entity.searchRestrictedData = [];
|
|
566
|
-
Entity.clientEnabledStaticMethods = [];
|
|
567
1029
|
//# sourceMappingURL=Entity.js.map
|