@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/src/Entity.ts
CHANGED
|
@@ -1,22 +1,42 @@
|
|
|
1
|
-
import { difference, intersection, isEqual } from 'lodash';
|
|
1
|
+
import { difference, intersection, isEqual } from 'lodash-es';
|
|
2
|
+
import { guid } from '@nymphjs/guid';
|
|
2
3
|
|
|
3
|
-
import type Nymph from './Nymph';
|
|
4
|
-
import {
|
|
4
|
+
import type Nymph from './Nymph.js';
|
|
5
|
+
import type { Options } from './Nymph.types.js';
|
|
6
|
+
import type {
|
|
5
7
|
ACProperties,
|
|
6
8
|
EntityConstructor,
|
|
7
9
|
EntityData,
|
|
8
10
|
EntityInterface,
|
|
9
11
|
EntityJson,
|
|
12
|
+
EntityObject,
|
|
10
13
|
EntityPatch,
|
|
11
14
|
EntityReference,
|
|
12
15
|
SerializedEntityData,
|
|
13
|
-
} from './Entity.types';
|
|
14
|
-
import {
|
|
16
|
+
} from './Entity.types.js';
|
|
17
|
+
import {
|
|
18
|
+
EntityConflictError,
|
|
19
|
+
EntityIsSleepingReferenceError,
|
|
20
|
+
InvalidParametersError,
|
|
21
|
+
InvalidStateError,
|
|
22
|
+
MethodFailedError,
|
|
23
|
+
} from './errors/index.js';
|
|
15
24
|
import {
|
|
25
|
+
classNamesToEntityConstructors,
|
|
16
26
|
entitiesToReferences,
|
|
27
|
+
entityConstructorsToClassNames,
|
|
17
28
|
referencesToEntities,
|
|
18
29
|
uniqueStrings,
|
|
19
|
-
} from './utils';
|
|
30
|
+
} from './utils.js';
|
|
31
|
+
|
|
32
|
+
export type EntityDataType<T> =
|
|
33
|
+
T extends Entity<infer DataType> ? DataType : never;
|
|
34
|
+
|
|
35
|
+
export type EntityInstanceType<T extends EntityConstructor> =
|
|
36
|
+
T extends new () => infer E ? E & EntityDataType<E> : never;
|
|
37
|
+
|
|
38
|
+
export type EntityObjectType<T extends EntityConstructor> =
|
|
39
|
+
T extends new () => infer E ? EntityObject & EntityDataType<E> : never;
|
|
20
40
|
|
|
21
41
|
/**
|
|
22
42
|
* Database abstraction object.
|
|
@@ -87,9 +107,9 @@ import {
|
|
|
87
107
|
* will fill its data from the DB. You can call clearCache() to turn all the
|
|
88
108
|
* entities back into sleeping references.
|
|
89
109
|
*/
|
|
90
|
-
export default class Entity<
|
|
91
|
-
|
|
92
|
-
{
|
|
110
|
+
export default class Entity<
|
|
111
|
+
T extends EntityData = EntityData,
|
|
112
|
+
> implements EntityInterface {
|
|
93
113
|
/**
|
|
94
114
|
* The instance of Nymph to use for queries.
|
|
95
115
|
*/
|
|
@@ -136,6 +156,10 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
136
156
|
* The reference to use to wake.
|
|
137
157
|
*/
|
|
138
158
|
protected $sleepingReference: EntityReference | null = null;
|
|
159
|
+
/**
|
|
160
|
+
* A promise that resolved when the entity's data is wake.
|
|
161
|
+
*/
|
|
162
|
+
protected $wakePromise: Promise<Entity<T>> | null = null;
|
|
139
163
|
/**
|
|
140
164
|
* Properties that will not be serialized into JSON with toJSON(). This
|
|
141
165
|
* can be considered a denylist, because these properties will not be set
|
|
@@ -145,6 +169,18 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
145
169
|
* also listed in searchRestrictedData.
|
|
146
170
|
*/
|
|
147
171
|
protected $privateData: string[] = [];
|
|
172
|
+
/**
|
|
173
|
+
* Whether this entity should publish changes to PubSub servers.
|
|
174
|
+
*/
|
|
175
|
+
public static pubSubEnabled = true;
|
|
176
|
+
/**
|
|
177
|
+
* Whether this entity should be accessible on the frontend through the REST
|
|
178
|
+
* server.
|
|
179
|
+
*
|
|
180
|
+
* If this is false, any request from the client that attempts to use this
|
|
181
|
+
* entity will fail.
|
|
182
|
+
*/
|
|
183
|
+
public static restEnabled = true;
|
|
148
184
|
/**
|
|
149
185
|
* Properties that will not be searchable from the frontend. If the frontend
|
|
150
186
|
* includes any of these properties in any of their clauses, they will be
|
|
@@ -156,8 +192,20 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
156
192
|
* be visible on the frontend, unlike $privateData, but any changes to them
|
|
157
193
|
* that come from the frontend will be ignored.
|
|
158
194
|
*
|
|
159
|
-
* In addition to what's listed here, the
|
|
160
|
-
* be
|
|
195
|
+
* In addition to what's listed here, all of the access control properties
|
|
196
|
+
* will be included when Tilmeld is being used. These are:
|
|
197
|
+
*
|
|
198
|
+
* - acUser
|
|
199
|
+
* - acGroup
|
|
200
|
+
* - acOther
|
|
201
|
+
* - acRead
|
|
202
|
+
* - acWrite
|
|
203
|
+
* - acFull
|
|
204
|
+
* - user
|
|
205
|
+
* - group
|
|
206
|
+
*
|
|
207
|
+
* You should modify these through client enabled methods or the $save method
|
|
208
|
+
* instead, for safety.
|
|
161
209
|
*/
|
|
162
210
|
protected $protectedData: string[] = [];
|
|
163
211
|
/**
|
|
@@ -196,28 +244,43 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
196
244
|
* The AC properties' values when the entity was loaded.
|
|
197
245
|
*/
|
|
198
246
|
private $originalAcValues: ACProperties | null = null;
|
|
247
|
+
/**
|
|
248
|
+
* This is used to hold a generated GUID for a new entity.
|
|
249
|
+
*/
|
|
250
|
+
private $guaranteedGUID: string | null = null;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Alter the options for a query for this entity.
|
|
254
|
+
*
|
|
255
|
+
* @param options The current options.
|
|
256
|
+
* @returns The altered options.
|
|
257
|
+
*/
|
|
258
|
+
static alterOptions?<T extends Options>(options: T): T;
|
|
199
259
|
|
|
200
260
|
/**
|
|
201
|
-
*
|
|
202
|
-
* @param guid The ID of the entity to load, undefined for a new entity.
|
|
261
|
+
* Initialize an entity.
|
|
203
262
|
*/
|
|
204
|
-
public constructor(
|
|
263
|
+
public constructor(..._rest: any[]) {
|
|
205
264
|
this.$nymph = (this.constructor as EntityConstructor).nymph;
|
|
206
265
|
this.$dataHandler = {
|
|
207
266
|
has: (data: EntityData, name: string) => {
|
|
208
|
-
this.$
|
|
267
|
+
this.$check();
|
|
209
268
|
|
|
210
269
|
return name in data || name in this.$sdata;
|
|
211
270
|
},
|
|
212
271
|
|
|
213
272
|
get: (data: EntityData, name: string) => {
|
|
214
|
-
this.$
|
|
273
|
+
this.$check();
|
|
215
274
|
|
|
216
275
|
if (this.$sdata.hasOwnProperty(name)) {
|
|
217
276
|
data[name] = referencesToEntities(
|
|
218
|
-
|
|
277
|
+
classNamesToEntityConstructors(
|
|
278
|
+
this.$nymph,
|
|
279
|
+
JSON.parse(this.$sdata[name]),
|
|
280
|
+
false,
|
|
281
|
+
),
|
|
219
282
|
this.$nymph,
|
|
220
|
-
this.$skipAc
|
|
283
|
+
this.$skipAc,
|
|
221
284
|
);
|
|
222
285
|
delete this.$sdata[name];
|
|
223
286
|
}
|
|
@@ -228,7 +291,7 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
228
291
|
},
|
|
229
292
|
|
|
230
293
|
set: (data: EntityData, name: string, value: any) => {
|
|
231
|
-
this.$
|
|
294
|
+
this.$check();
|
|
232
295
|
|
|
233
296
|
if (this.$sdata.hasOwnProperty(name)) {
|
|
234
297
|
delete this.$sdata[name];
|
|
@@ -238,7 +301,7 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
238
301
|
},
|
|
239
302
|
|
|
240
303
|
deleteProperty: (data: EntityData, name: string) => {
|
|
241
|
-
this.$
|
|
304
|
+
this.$check();
|
|
242
305
|
|
|
243
306
|
if (this.$sdata.hasOwnProperty(name)) {
|
|
244
307
|
return delete this.$sdata[name];
|
|
@@ -252,9 +315,9 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
252
315
|
defineProperty: (
|
|
253
316
|
data: EntityData,
|
|
254
317
|
name: string,
|
|
255
|
-
descriptor: PropertyDescriptor
|
|
318
|
+
descriptor: PropertyDescriptor,
|
|
256
319
|
) => {
|
|
257
|
-
this.$
|
|
320
|
+
this.$check();
|
|
258
321
|
|
|
259
322
|
if (this.$sdata.hasOwnProperty(name)) {
|
|
260
323
|
delete this.$sdata[name];
|
|
@@ -264,13 +327,17 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
264
327
|
},
|
|
265
328
|
|
|
266
329
|
getOwnPropertyDescriptor: (data: EntityData, name: string) => {
|
|
267
|
-
this.$
|
|
330
|
+
this.$check();
|
|
268
331
|
|
|
269
332
|
if (this.$sdata.hasOwnProperty(name)) {
|
|
270
333
|
data[name] = referencesToEntities(
|
|
271
|
-
|
|
334
|
+
classNamesToEntityConstructors(
|
|
335
|
+
this.$nymph,
|
|
336
|
+
JSON.parse(this.$sdata[name]),
|
|
337
|
+
false,
|
|
338
|
+
),
|
|
272
339
|
this.$nymph,
|
|
273
|
-
this.$skipAc
|
|
340
|
+
this.$skipAc,
|
|
274
341
|
);
|
|
275
342
|
delete this.$sdata[name];
|
|
276
343
|
}
|
|
@@ -278,10 +345,10 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
278
345
|
},
|
|
279
346
|
|
|
280
347
|
ownKeys: (data: EntityData) => {
|
|
281
|
-
this.$
|
|
348
|
+
this.$check();
|
|
282
349
|
|
|
283
350
|
return Object.getOwnPropertyNames(data).concat(
|
|
284
|
-
Object.getOwnPropertyNames(this.$sdata)
|
|
351
|
+
Object.getOwnPropertyNames(this.$sdata),
|
|
285
352
|
);
|
|
286
353
|
},
|
|
287
354
|
};
|
|
@@ -290,154 +357,247 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
290
357
|
|
|
291
358
|
this.$data = new Proxy(this.$dataStore, this.$dataHandler);
|
|
292
359
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
{ class: this.constructor as EntityConstructor },
|
|
296
|
-
{ type: '&', guid }
|
|
297
|
-
);
|
|
298
|
-
if (entity) {
|
|
299
|
-
this.guid = entity.guid;
|
|
300
|
-
this.tags = entity.tags;
|
|
301
|
-
this.cdate = entity.cdate;
|
|
302
|
-
this.mdate = entity.mdate;
|
|
303
|
-
this.$putData(entity.$getData(), entity.$getSData());
|
|
304
|
-
}
|
|
305
|
-
}
|
|
360
|
+
return this.$proxy();
|
|
361
|
+
}
|
|
306
362
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
363
|
+
private $existingProxy?: this & T;
|
|
364
|
+
protected $proxy() {
|
|
365
|
+
if (this.$existingProxy == null) {
|
|
366
|
+
this.$existingProxy = new Proxy(this, {
|
|
367
|
+
has: (entity: this, name: string) => {
|
|
368
|
+
if (
|
|
369
|
+
typeof name !== 'string' ||
|
|
370
|
+
name in entity ||
|
|
371
|
+
name.substring(0, 1) === '$'
|
|
372
|
+
) {
|
|
373
|
+
return name in entity;
|
|
374
|
+
}
|
|
316
375
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
376
|
+
this.$check();
|
|
377
|
+
return name in entity.$data;
|
|
378
|
+
},
|
|
320
379
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
380
|
+
get: (entity: this, name: string) => {
|
|
381
|
+
if (
|
|
382
|
+
typeof name !== 'string' ||
|
|
383
|
+
name in entity ||
|
|
384
|
+
name.substring(0, 1) === '$'
|
|
385
|
+
) {
|
|
386
|
+
if (name === 'tags' || name === 'cdate' || name === 'mdate') {
|
|
387
|
+
this.$check();
|
|
388
|
+
}
|
|
389
|
+
return (entity as any)[name];
|
|
329
390
|
}
|
|
330
|
-
return (entity as any)[name];
|
|
331
|
-
}
|
|
332
391
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
392
|
+
this.$check();
|
|
393
|
+
if (name in entity.$data) {
|
|
394
|
+
return entity.$data[name];
|
|
395
|
+
}
|
|
396
|
+
return undefined;
|
|
397
|
+
},
|
|
339
398
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
399
|
+
set: (entity: this, name: string, value: any) => {
|
|
400
|
+
if (
|
|
401
|
+
typeof name !== 'string' ||
|
|
402
|
+
name in entity ||
|
|
403
|
+
name.substring(0, 1) === '$'
|
|
404
|
+
) {
|
|
405
|
+
if (name === 'tags' || name === 'cdate' || name === 'mdate') {
|
|
406
|
+
this.$check();
|
|
407
|
+
}
|
|
408
|
+
(entity as any)[name] = value;
|
|
409
|
+
} else {
|
|
410
|
+
this.$check();
|
|
411
|
+
(entity.$data as any)[name] = value;
|
|
348
412
|
}
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
this.$referenceWake();
|
|
352
|
-
(entity.$data as any)[name] = value;
|
|
353
|
-
}
|
|
354
|
-
return true;
|
|
355
|
-
},
|
|
413
|
+
return true;
|
|
414
|
+
},
|
|
356
415
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
416
|
+
deleteProperty: (entity: this, name: string) => {
|
|
417
|
+
if (name in entity) {
|
|
418
|
+
return delete (entity as any)[name];
|
|
419
|
+
} else if (name in entity.$data) {
|
|
420
|
+
this.$check();
|
|
421
|
+
return delete entity.$data[name];
|
|
422
|
+
}
|
|
423
|
+
return true;
|
|
424
|
+
},
|
|
366
425
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
426
|
+
getPrototypeOf: (entity: this) => {
|
|
427
|
+
return entity.constructor.prototype;
|
|
428
|
+
},
|
|
370
429
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
430
|
+
defineProperty: (
|
|
431
|
+
entity: this,
|
|
432
|
+
name: string,
|
|
433
|
+
descriptor: PropertyDescriptor,
|
|
434
|
+
) => {
|
|
435
|
+
if (
|
|
436
|
+
typeof name !== 'string' ||
|
|
437
|
+
name in entity ||
|
|
438
|
+
name.substring(0, 1) === '$'
|
|
439
|
+
) {
|
|
440
|
+
if (name === 'tags' || name === 'cdate' || name === 'mdate') {
|
|
441
|
+
this.$check();
|
|
442
|
+
}
|
|
443
|
+
Object.defineProperty(entity, name, descriptor);
|
|
444
|
+
} else {
|
|
445
|
+
this.$check();
|
|
446
|
+
Object.defineProperty(entity.$data, name, descriptor);
|
|
383
447
|
}
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
this.$referenceWake();
|
|
387
|
-
Object.defineProperty(entity.$data, name, descriptor);
|
|
388
|
-
}
|
|
389
|
-
return true;
|
|
390
|
-
},
|
|
448
|
+
return true;
|
|
449
|
+
},
|
|
391
450
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
451
|
+
getOwnPropertyDescriptor: (entity: this, name: string) => {
|
|
452
|
+
if (
|
|
453
|
+
typeof name !== 'string' ||
|
|
454
|
+
name in entity ||
|
|
455
|
+
name.substring(0, 1) === '$'
|
|
456
|
+
) {
|
|
457
|
+
if (name === 'tags' || name === 'cdate' || name === 'mdate') {
|
|
458
|
+
this.$check();
|
|
459
|
+
}
|
|
460
|
+
return Object.getOwnPropertyDescriptor(entity, name);
|
|
461
|
+
} else {
|
|
462
|
+
this.$check();
|
|
463
|
+
return Object.getOwnPropertyDescriptor(entity.$data, name);
|
|
400
464
|
}
|
|
401
|
-
|
|
402
|
-
} else {
|
|
403
|
-
this.$referenceWake();
|
|
404
|
-
return Object.getOwnPropertyDescriptor(entity.$data, name);
|
|
405
|
-
}
|
|
406
|
-
},
|
|
465
|
+
},
|
|
407
466
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
467
|
+
ownKeys: (entity: this) => {
|
|
468
|
+
this.$check();
|
|
469
|
+
return Object.getOwnPropertyNames(entity).concat(
|
|
470
|
+
Object.getOwnPropertyNames(entity.$data),
|
|
471
|
+
);
|
|
472
|
+
},
|
|
473
|
+
}) as this & T;
|
|
474
|
+
}
|
|
475
|
+
return this.$existingProxy;
|
|
415
476
|
}
|
|
416
477
|
|
|
417
|
-
|
|
478
|
+
/**
|
|
479
|
+
* Create or retrieve a new entity instance.
|
|
480
|
+
*
|
|
481
|
+
* Note that this will always return an entity, even if the GUID is not found.
|
|
482
|
+
*
|
|
483
|
+
* @param guid An optional GUID to retrieve.
|
|
484
|
+
*/
|
|
485
|
+
public static async factory<E extends Entity>(
|
|
486
|
+
this: {
|
|
487
|
+
new (): E;
|
|
488
|
+
},
|
|
489
|
+
guid?: string,
|
|
490
|
+
): Promise<E & EntityDataType<E>> {
|
|
418
491
|
const entity = new this();
|
|
419
492
|
if (guid != null) {
|
|
420
|
-
const entity = await
|
|
493
|
+
const entity = await (
|
|
494
|
+
this as unknown as EntityConstructor
|
|
495
|
+
).nymph.getEntity(
|
|
421
496
|
{
|
|
422
|
-
class: this as
|
|
497
|
+
class: this as unknown as EntityConstructor,
|
|
423
498
|
},
|
|
424
|
-
{ type: '&', guid }
|
|
499
|
+
{ type: '&', guid },
|
|
425
500
|
);
|
|
426
501
|
if (entity != null) {
|
|
427
|
-
return entity
|
|
502
|
+
return entity as E & EntityDataType<E>;
|
|
428
503
|
}
|
|
429
504
|
}
|
|
430
|
-
return entity
|
|
505
|
+
return entity as E & EntityDataType<E>;
|
|
431
506
|
}
|
|
432
507
|
|
|
433
|
-
|
|
434
|
-
|
|
508
|
+
/**
|
|
509
|
+
* Create a new entity instance.
|
|
510
|
+
*/
|
|
511
|
+
public static factorySync<E extends Entity>(this: {
|
|
512
|
+
new (): E;
|
|
513
|
+
}): E & EntityDataType<E> {
|
|
514
|
+
return new this() as E & EntityDataType<E>;
|
|
435
515
|
}
|
|
436
516
|
|
|
437
|
-
|
|
517
|
+
/**
|
|
518
|
+
* Create a new sleeping reference instance.
|
|
519
|
+
*
|
|
520
|
+
* Sleeping references won't retrieve their data from the database until they
|
|
521
|
+
* are readied with `$wake()` or a parent's `$wakeAll()`.
|
|
522
|
+
*
|
|
523
|
+
* @param reference The Nymph Entity Reference to use to wake.
|
|
524
|
+
* @returns The new instance.
|
|
525
|
+
*/
|
|
526
|
+
public static factoryReference<E extends Entity>(
|
|
527
|
+
this: {
|
|
528
|
+
new (): E;
|
|
529
|
+
},
|
|
530
|
+
reference: EntityReference,
|
|
531
|
+
): E & EntityDataType<E> {
|
|
438
532
|
const entity = new this();
|
|
439
533
|
entity.$referenceSleep(reference);
|
|
440
|
-
return entity
|
|
534
|
+
return entity as E & EntityDataType<E>;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Get an array of strings that **must** be unique across the current etype.
|
|
539
|
+
*
|
|
540
|
+
* When you try to save another entity with any of the same unique strings,
|
|
541
|
+
* Nymph will throw an error.
|
|
542
|
+
*
|
|
543
|
+
* The default implementation of this static method instantiates the entity,
|
|
544
|
+
* assigns all of the given data, then calls `$getUniques` and returns its
|
|
545
|
+
* output. This can have a performance impact if a lot of extra processing
|
|
546
|
+
* happens during any of these steps. You can override this method to
|
|
547
|
+
* calculate the unique strings faster, but you must return the same strings
|
|
548
|
+
* that would be returned by `$getUniques`.
|
|
549
|
+
*
|
|
550
|
+
* @returns Resolves to an array of entity's unique constraint strings.
|
|
551
|
+
*/
|
|
552
|
+
public static async getUniques({
|
|
553
|
+
guid,
|
|
554
|
+
cdate,
|
|
555
|
+
mdate,
|
|
556
|
+
tags,
|
|
557
|
+
data,
|
|
558
|
+
sdata,
|
|
559
|
+
}: {
|
|
560
|
+
guid?: string;
|
|
561
|
+
cdate?: number;
|
|
562
|
+
mdate?: number;
|
|
563
|
+
tags: string[];
|
|
564
|
+
data: EntityData;
|
|
565
|
+
sdata?: SerializedEntityData;
|
|
566
|
+
}) {
|
|
567
|
+
const entity = new this();
|
|
568
|
+
if (guid != null) {
|
|
569
|
+
entity.guid = guid;
|
|
570
|
+
}
|
|
571
|
+
if (cdate != null) {
|
|
572
|
+
entity.cdate = cdate;
|
|
573
|
+
}
|
|
574
|
+
if (mdate != null) {
|
|
575
|
+
entity.mdate = mdate;
|
|
576
|
+
}
|
|
577
|
+
entity.tags = tags;
|
|
578
|
+
entity.$putData(data, sdata);
|
|
579
|
+
return await entity.$getUniques();
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Get a string for full text search for one of an entity's properties.
|
|
584
|
+
*
|
|
585
|
+
* The result will be tokenized and stored as the full text search index for
|
|
586
|
+
* use with "search" clauses.
|
|
587
|
+
*
|
|
588
|
+
* Return null to not include any tokens in the full text search storage.
|
|
589
|
+
*
|
|
590
|
+
* You shouldn't use any other entity data to transform the text, as it won't
|
|
591
|
+
* always be available. This function is meant for things like stripping HTML
|
|
592
|
+
* tags.
|
|
593
|
+
*
|
|
594
|
+
* @returns By default, returns the value if it is a string.
|
|
595
|
+
*/
|
|
596
|
+
public static getFTSText(_name: string, value: any) {
|
|
597
|
+
if (typeof value === 'string') {
|
|
598
|
+
return value;
|
|
599
|
+
}
|
|
600
|
+
return null;
|
|
441
601
|
}
|
|
442
602
|
|
|
443
603
|
public toJSON() {
|
|
@@ -445,24 +605,65 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
445
605
|
return this.$sleepingReference;
|
|
446
606
|
}
|
|
447
607
|
const obj: EntityJson = {
|
|
448
|
-
class: (this.constructor
|
|
608
|
+
class: entityConstructorsToClassNames(this.constructor),
|
|
449
609
|
guid: this.guid,
|
|
450
610
|
cdate: this.cdate,
|
|
451
611
|
mdate: this.mdate,
|
|
452
612
|
tags: [...this.tags],
|
|
453
613
|
data: {},
|
|
454
614
|
};
|
|
455
|
-
const
|
|
456
|
-
|
|
615
|
+
for (const key of [
|
|
616
|
+
...Object.keys(this.$dataStore),
|
|
617
|
+
...Object.keys(this.$sdata),
|
|
618
|
+
]) {
|
|
457
619
|
if (this.$privateData.indexOf(key) === -1) {
|
|
458
|
-
obj.data[key] =
|
|
620
|
+
obj.data[key] = entityConstructorsToClassNames(
|
|
621
|
+
entitiesToReferences(this.$data[key], true),
|
|
622
|
+
);
|
|
459
623
|
}
|
|
460
624
|
}
|
|
461
625
|
return obj;
|
|
462
626
|
}
|
|
463
627
|
|
|
628
|
+
public $setNymph(nymph: Nymph) {
|
|
629
|
+
this.$nymph = nymph;
|
|
630
|
+
if (!this.$asleep()) {
|
|
631
|
+
const recurseData = (data: any) => {
|
|
632
|
+
if (Array.isArray(data)) {
|
|
633
|
+
for (let name in data) {
|
|
634
|
+
recurseData(data[name]);
|
|
635
|
+
}
|
|
636
|
+
} else if (data instanceof Entity) {
|
|
637
|
+
if (data.$nymph !== nymph) {
|
|
638
|
+
data.$setNymph(nymph);
|
|
639
|
+
}
|
|
640
|
+
} else if (
|
|
641
|
+
typeof data === 'function' &&
|
|
642
|
+
data.prototype instanceof Entity
|
|
643
|
+
) {
|
|
644
|
+
data.nymph = nymph;
|
|
645
|
+
} else if (typeof data === 'object') {
|
|
646
|
+
for (let name in data) {
|
|
647
|
+
recurseData(data[name]);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
recurseData(this.$data);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
public $getGuaranteedGUID() {
|
|
656
|
+
if (this.guid !== null) {
|
|
657
|
+
return this.guid;
|
|
658
|
+
}
|
|
659
|
+
if (this.$guaranteedGUID == null) {
|
|
660
|
+
this.$guaranteedGUID = guid();
|
|
661
|
+
}
|
|
662
|
+
return this.$guaranteedGUID;
|
|
663
|
+
}
|
|
664
|
+
|
|
464
665
|
public $addTag(...tags: string[]) {
|
|
465
|
-
this.$
|
|
666
|
+
this.$check();
|
|
466
667
|
|
|
467
668
|
if (tags.length < 1) {
|
|
468
669
|
return;
|
|
@@ -471,7 +672,7 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
471
672
|
}
|
|
472
673
|
|
|
473
674
|
public $arraySearch(array: any[], strict = false) {
|
|
474
|
-
this.$
|
|
675
|
+
this.$check();
|
|
475
676
|
|
|
476
677
|
if (!Array.isArray(array)) {
|
|
477
678
|
return -1;
|
|
@@ -486,23 +687,25 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
486
687
|
}
|
|
487
688
|
|
|
488
689
|
public $clearCache() {
|
|
489
|
-
this.$
|
|
690
|
+
this.$check();
|
|
490
691
|
|
|
491
|
-
this.$putData(this.$getData(), this.$getSData());
|
|
692
|
+
this.$putData(this.$getData(false), this.$getSData());
|
|
492
693
|
}
|
|
493
694
|
|
|
494
695
|
public $getClientEnabledMethods() {
|
|
495
696
|
return this.$clientEnabledMethods;
|
|
496
697
|
}
|
|
497
698
|
|
|
498
|
-
public async $delete(): Promise<
|
|
499
|
-
this.$
|
|
699
|
+
public async $delete(): Promise<void> {
|
|
700
|
+
await this.$wake();
|
|
500
701
|
|
|
501
|
-
|
|
702
|
+
if (!(await this.$nymph.deleteEntity(this))) {
|
|
703
|
+
throw new MethodFailedError('Failed to delete entity.');
|
|
704
|
+
}
|
|
502
705
|
}
|
|
503
706
|
|
|
504
707
|
public $equals(object: any) {
|
|
505
|
-
this.$
|
|
708
|
+
this.$check();
|
|
506
709
|
|
|
507
710
|
if (!(object instanceof Entity)) {
|
|
508
711
|
return false;
|
|
@@ -523,13 +726,17 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
523
726
|
if (!isEqual(obTags, myTags)) {
|
|
524
727
|
return false;
|
|
525
728
|
}
|
|
526
|
-
const obData =
|
|
527
|
-
|
|
729
|
+
const obData = entityConstructorsToClassNames(
|
|
730
|
+
entitiesToReferences(object.$getData(true), true),
|
|
731
|
+
);
|
|
732
|
+
const myData = entityConstructorsToClassNames(
|
|
733
|
+
entitiesToReferences(this.$getData(true), true),
|
|
734
|
+
);
|
|
528
735
|
return isEqual(obData, myData);
|
|
529
736
|
}
|
|
530
737
|
|
|
531
738
|
public $getData(includeSData = false) {
|
|
532
|
-
this.$
|
|
739
|
+
this.$check();
|
|
533
740
|
|
|
534
741
|
if (includeSData) {
|
|
535
742
|
// Access all the serialized properties to initialize them.
|
|
@@ -537,34 +744,82 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
537
744
|
const _unused: any = (this as any)[key];
|
|
538
745
|
}
|
|
539
746
|
}
|
|
540
|
-
return
|
|
747
|
+
return { ...this.$dataStore };
|
|
541
748
|
}
|
|
542
749
|
|
|
543
750
|
public $getSData() {
|
|
544
|
-
this.$
|
|
751
|
+
this.$check();
|
|
545
752
|
|
|
546
753
|
return this.$sdata;
|
|
547
754
|
}
|
|
548
755
|
|
|
549
|
-
public $
|
|
550
|
-
|
|
756
|
+
public async $getUniques(): Promise<string[]> {
|
|
757
|
+
return [];
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
public $getOriginalAcValues(): ACProperties {
|
|
761
|
+
this.$check();
|
|
762
|
+
|
|
763
|
+
return this.$originalAcValues ?? this.$getCurrentAcValues();
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
public $getCurrentAcValues(): ACProperties {
|
|
767
|
+
this.$check();
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
user: this.$getAcUid(),
|
|
771
|
+
group: this.$getAcGid(),
|
|
772
|
+
acUser: this.$data.acUser ?? null,
|
|
773
|
+
acGroup: this.$data.acGroup ?? null,
|
|
774
|
+
acOther: this.$data.acOther ?? null,
|
|
775
|
+
acRead: this.$getAcReadIds(),
|
|
776
|
+
acWrite: this.$getAcWriteIds(),
|
|
777
|
+
acFull: this.$getAcFullIds(),
|
|
778
|
+
};
|
|
779
|
+
}
|
|
551
780
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
acWrite: this.$data.acWrite,
|
|
561
|
-
acFull: this.$data.acFull,
|
|
781
|
+
public $getAcUid() {
|
|
782
|
+
if ('user' in this.$sdata) {
|
|
783
|
+
const userValue = JSON.parse(this.$sdata.user);
|
|
784
|
+
if (
|
|
785
|
+
Array.isArray(userValue) &&
|
|
786
|
+
userValue[0] === 'nymph_entity_reference'
|
|
787
|
+
) {
|
|
788
|
+
return userValue[1];
|
|
562
789
|
}
|
|
563
|
-
|
|
790
|
+
}
|
|
791
|
+
return (this.$data.user?.guid ?? null) as string | null;
|
|
792
|
+
}
|
|
793
|
+
public $getAcGid() {
|
|
794
|
+
if ('group' in this.$sdata) {
|
|
795
|
+
const groupValue = JSON.parse(this.$sdata.group);
|
|
796
|
+
if (
|
|
797
|
+
Array.isArray(groupValue) &&
|
|
798
|
+
groupValue[0] === 'nymph_entity_reference'
|
|
799
|
+
) {
|
|
800
|
+
return groupValue[1];
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return (this.$data.group?.guid ?? null) as string | null;
|
|
804
|
+
}
|
|
805
|
+
public $getAcReadIds() {
|
|
806
|
+
return (this.$data.acRead?.map((entity: EntityInterface | string) =>
|
|
807
|
+
typeof entity === 'string' ? entity : entity.guid,
|
|
808
|
+
) ?? null) as (string | null)[] | null;
|
|
809
|
+
}
|
|
810
|
+
public $getAcWriteIds() {
|
|
811
|
+
return (this.$data.acWrite?.map((entity: EntityInterface | string) =>
|
|
812
|
+
typeof entity === 'string' ? entity : entity.guid,
|
|
813
|
+
) ?? null) as (string | null)[] | null;
|
|
814
|
+
}
|
|
815
|
+
public $getAcFullIds() {
|
|
816
|
+
return (this.$data.acFull?.map((entity: EntityInterface | string) =>
|
|
817
|
+
typeof entity === 'string' ? entity : entity.guid,
|
|
818
|
+
) ?? null) as (string | null)[] | null;
|
|
564
819
|
}
|
|
565
820
|
|
|
566
821
|
public $getValidatable() {
|
|
567
|
-
this.$
|
|
822
|
+
this.$check();
|
|
568
823
|
|
|
569
824
|
// Access all the serialized properties to initialize them.
|
|
570
825
|
for (const key in this.$sdata) {
|
|
@@ -575,18 +830,18 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
575
830
|
cdate: this.cdate,
|
|
576
831
|
mdate: this.mdate,
|
|
577
832
|
tags: this.tags,
|
|
578
|
-
...
|
|
833
|
+
...this.$dataStore,
|
|
579
834
|
};
|
|
580
835
|
}
|
|
581
836
|
|
|
582
837
|
public $getTags() {
|
|
583
|
-
this.$
|
|
838
|
+
this.$check();
|
|
584
839
|
|
|
585
840
|
return this.tags;
|
|
586
841
|
}
|
|
587
842
|
|
|
588
843
|
public $hasTag(...tags: string[]) {
|
|
589
|
-
this.$
|
|
844
|
+
this.$check();
|
|
590
845
|
|
|
591
846
|
if (!tags.length) {
|
|
592
847
|
return false;
|
|
@@ -608,11 +863,15 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
608
863
|
return false;
|
|
609
864
|
}
|
|
610
865
|
|
|
611
|
-
if (this
|
|
866
|
+
if (this === object) {
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (this.guid != null || object.guid != null) {
|
|
612
871
|
return this.guid === object.guid;
|
|
613
872
|
}
|
|
614
873
|
|
|
615
|
-
this.$
|
|
874
|
+
this.$check();
|
|
616
875
|
if (typeof object.$getData !== 'function') {
|
|
617
876
|
return false;
|
|
618
877
|
} else {
|
|
@@ -621,19 +880,22 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
621
880
|
if (!isEqual(obTags, myTags)) {
|
|
622
881
|
return false;
|
|
623
882
|
}
|
|
624
|
-
const obData =
|
|
625
|
-
|
|
883
|
+
const obData = entityConstructorsToClassNames(
|
|
884
|
+
entitiesToReferences(object.$getData(true), true),
|
|
885
|
+
);
|
|
886
|
+
const myData = entityConstructorsToClassNames(
|
|
887
|
+
entitiesToReferences(this.$getData(true), true),
|
|
888
|
+
);
|
|
626
889
|
return isEqual(obData, myData);
|
|
627
890
|
}
|
|
628
891
|
}
|
|
629
892
|
|
|
630
893
|
public $jsonAcceptData(input: EntityJson, allowConflict = false) {
|
|
631
|
-
|
|
632
|
-
this.$referenceWake();
|
|
894
|
+
this.$check();
|
|
633
895
|
|
|
634
896
|
if (this.guid != input.guid) {
|
|
635
897
|
throw new EntityConflictError(
|
|
636
|
-
'Tried to accept JSON input for the wrong entity.'
|
|
898
|
+
'Tried to accept JSON input for the wrong entity.',
|
|
637
899
|
);
|
|
638
900
|
}
|
|
639
901
|
|
|
@@ -658,7 +920,7 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
658
920
|
this.$addTag(...protectedTags, ...tags);
|
|
659
921
|
|
|
660
922
|
// Accept the data.
|
|
661
|
-
const data = input.data;
|
|
923
|
+
const data = { ...input.data };
|
|
662
924
|
const privateData: EntityData = {};
|
|
663
925
|
for (const name of this.$privateData) {
|
|
664
926
|
if (name in this.$data) {
|
|
@@ -671,12 +933,27 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
671
933
|
|
|
672
934
|
const protectedData: EntityData = {};
|
|
673
935
|
const protectedProps = [...this.$protectedData];
|
|
674
|
-
if (
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
protectedProps.push('
|
|
679
|
-
protectedProps.push('
|
|
936
|
+
if (this.$nymph.tilmeld) {
|
|
937
|
+
protectedProps.push('acUser');
|
|
938
|
+
protectedProps.push('acGroup');
|
|
939
|
+
protectedProps.push('acOther');
|
|
940
|
+
protectedProps.push('acRead');
|
|
941
|
+
protectedProps.push('acWrite');
|
|
942
|
+
protectedProps.push('acFull');
|
|
943
|
+
if (
|
|
944
|
+
((this.constructor as EntityConstructor).class !== 'User' &&
|
|
945
|
+
(this.constructor as EntityConstructor).class !== 'Group') ||
|
|
946
|
+
!(this.$nymph.tilmeld as any).currentUser ||
|
|
947
|
+
(!(this.$nymph.tilmeld as any).currentUser.abilities?.includes(
|
|
948
|
+
'tilmeld/admin',
|
|
949
|
+
) &&
|
|
950
|
+
!(this.$nymph.tilmeld as any).currentUser.abilities?.includes(
|
|
951
|
+
'system/admin',
|
|
952
|
+
))
|
|
953
|
+
) {
|
|
954
|
+
protectedProps.push('user');
|
|
955
|
+
protectedProps.push('group');
|
|
956
|
+
}
|
|
680
957
|
}
|
|
681
958
|
for (const name of protectedProps) {
|
|
682
959
|
if (name in this.$data) {
|
|
@@ -709,11 +986,11 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
709
986
|
}
|
|
710
987
|
|
|
711
988
|
public $jsonAcceptPatch(patch: EntityPatch, allowConflict = false) {
|
|
712
|
-
this.$
|
|
989
|
+
this.$check();
|
|
713
990
|
|
|
714
991
|
if (this.guid != patch.guid) {
|
|
715
992
|
throw new EntityConflictError(
|
|
716
|
-
'Tried to accept JSON patch for the wrong entity.'
|
|
993
|
+
'Tried to accept JSON patch for the wrong entity.',
|
|
717
994
|
);
|
|
718
995
|
}
|
|
719
996
|
|
|
@@ -725,27 +1002,47 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
725
1002
|
}
|
|
726
1003
|
this.mdate = patch.mdate;
|
|
727
1004
|
|
|
1005
|
+
const protectedProps = [...this.$protectedData];
|
|
1006
|
+
if (this.$nymph.tilmeld) {
|
|
1007
|
+
protectedProps.push('acUser');
|
|
1008
|
+
protectedProps.push('acGroup');
|
|
1009
|
+
protectedProps.push('acOther');
|
|
1010
|
+
protectedProps.push('acRead');
|
|
1011
|
+
protectedProps.push('acWrite');
|
|
1012
|
+
protectedProps.push('acFull');
|
|
1013
|
+
if (
|
|
1014
|
+
((this.constructor as EntityConstructor).class !== 'User' &&
|
|
1015
|
+
(this.constructor as EntityConstructor).class !== 'Group') ||
|
|
1016
|
+
!(this.$nymph.tilmeld as any).currentUser ||
|
|
1017
|
+
(!(this.$nymph.tilmeld as any).currentUser.abilities?.includes(
|
|
1018
|
+
'tilmeld/admin',
|
|
1019
|
+
) &&
|
|
1020
|
+
!(this.$nymph.tilmeld as any).currentUser.abilities?.includes(
|
|
1021
|
+
'system/admin',
|
|
1022
|
+
))
|
|
1023
|
+
) {
|
|
1024
|
+
protectedProps.push('user');
|
|
1025
|
+
protectedProps.push('group');
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
728
1029
|
for (const name in patch.set) {
|
|
729
1030
|
if (
|
|
730
1031
|
(this.$allowlistData != null &&
|
|
731
1032
|
this.$allowlistData.indexOf(name) === -1) ||
|
|
732
|
-
|
|
1033
|
+
protectedProps.indexOf(name) !== -1 ||
|
|
733
1034
|
this.$privateData.indexOf(name) !== -1
|
|
734
1035
|
) {
|
|
735
1036
|
continue;
|
|
736
1037
|
}
|
|
737
|
-
(this.$data as any)[name] =
|
|
738
|
-
patch.set[name],
|
|
739
|
-
this.$nymph,
|
|
740
|
-
this.$skipAc
|
|
741
|
-
);
|
|
1038
|
+
(this.$data as any)[name] = patch.set[name];
|
|
742
1039
|
}
|
|
743
1040
|
|
|
744
1041
|
for (const name of patch.unset) {
|
|
745
1042
|
if (
|
|
746
1043
|
(this.$allowlistData != null &&
|
|
747
1044
|
this.$allowlistData.indexOf(name) === -1) ||
|
|
748
|
-
|
|
1045
|
+
protectedProps.indexOf(name) !== -1 ||
|
|
749
1046
|
this.$privateData.indexOf(name) !== -1
|
|
750
1047
|
) {
|
|
751
1048
|
continue;
|
|
@@ -776,27 +1073,23 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
776
1073
|
}
|
|
777
1074
|
}
|
|
778
1075
|
|
|
779
|
-
public $putData(
|
|
780
|
-
|
|
1076
|
+
public $putData(
|
|
1077
|
+
data: EntityData,
|
|
1078
|
+
sdata?: SerializedEntityData,
|
|
1079
|
+
_source?: 'server',
|
|
1080
|
+
) {
|
|
1081
|
+
this.$check();
|
|
781
1082
|
|
|
782
|
-
const mySdata = sdata ?? this.$getSData();
|
|
783
|
-
for (const name in data) {
|
|
784
|
-
delete mySdata[name];
|
|
785
|
-
}
|
|
786
1083
|
for (const name in this.$dataStore) {
|
|
787
1084
|
delete this.$dataStore[name];
|
|
788
1085
|
}
|
|
789
1086
|
for (const name in data) {
|
|
790
|
-
(this.$dataStore as any)[name] =
|
|
791
|
-
data[name],
|
|
792
|
-
this.$nymph,
|
|
793
|
-
this.$skipAc
|
|
794
|
-
);
|
|
1087
|
+
(this.$dataStore as any)[name] = data[name];
|
|
795
1088
|
}
|
|
796
|
-
this.$sdata =
|
|
1089
|
+
this.$sdata = sdata ?? {};
|
|
797
1090
|
// Set original AC values if not set..
|
|
798
1091
|
if (this.$originalAcValues == null) {
|
|
799
|
-
this.$originalAcValues = this.$
|
|
1092
|
+
this.$originalAcValues = this.$getCurrentAcValues();
|
|
800
1093
|
}
|
|
801
1094
|
}
|
|
802
1095
|
|
|
@@ -813,14 +1106,14 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
813
1106
|
) {
|
|
814
1107
|
throw new InvalidParametersError(
|
|
815
1108
|
'referenceSleep expects parameter 1 to be a valid Nymph entity ' +
|
|
816
|
-
'reference.'
|
|
1109
|
+
'reference.',
|
|
817
1110
|
);
|
|
818
1111
|
}
|
|
819
1112
|
const thisClass: string = (this.constructor as any).class;
|
|
820
1113
|
if (reference[2] !== thisClass) {
|
|
821
1114
|
throw new InvalidParametersError(
|
|
822
1115
|
'referenceSleep can only be called with an entity reference of the ' +
|
|
823
|
-
`same class. Given class: ${reference[2]}; this class: ${thisClass}
|
|
1116
|
+
`same class. Given class: ${reference[2]}; this class: ${thisClass}.`,
|
|
824
1117
|
);
|
|
825
1118
|
}
|
|
826
1119
|
this.$isASleepingReference = true;
|
|
@@ -828,38 +1121,137 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
828
1121
|
this.$sleepingReference = reference;
|
|
829
1122
|
}
|
|
830
1123
|
|
|
1124
|
+
/**
|
|
1125
|
+
* Check if this is a sleeping reference and throw an error if so.
|
|
1126
|
+
*/
|
|
1127
|
+
protected $check() {
|
|
1128
|
+
if (this.$asleep()) {
|
|
1129
|
+
throw new EntityIsSleepingReferenceError(
|
|
1130
|
+
'This entity is in a sleeping reference state. You must use .$wake() to wake it.',
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Check if this is a sleeping reference.
|
|
1137
|
+
*/
|
|
1138
|
+
public $asleep() {
|
|
1139
|
+
return this.$isASleepingReference || this.$sleepingReference != null;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
831
1142
|
/**
|
|
832
1143
|
* Wake from a sleeping reference.
|
|
833
|
-
*
|
|
834
|
-
* @returns True on success, false on failure.
|
|
835
1144
|
*/
|
|
836
|
-
|
|
837
|
-
if (!this.$
|
|
838
|
-
|
|
1145
|
+
public $wake() {
|
|
1146
|
+
if (!this.$asleep()) {
|
|
1147
|
+
this.$wakePromise = null;
|
|
1148
|
+
return Promise.resolve(this.$proxy());
|
|
839
1149
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
skipAc: this.$skipAc,
|
|
845
|
-
},
|
|
846
|
-
{ type: '&', guid: this.$sleepingReference[1] }
|
|
847
|
-
);
|
|
848
|
-
if (entity == null) {
|
|
849
|
-
return false;
|
|
1150
|
+
if (this.$sleepingReference?.[1] == null) {
|
|
1151
|
+
throw new InvalidStateError(
|
|
1152
|
+
'Tried to wake a sleeping reference with no GUID.',
|
|
1153
|
+
);
|
|
850
1154
|
}
|
|
851
|
-
this.$
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1155
|
+
if (!this.$wakePromise) {
|
|
1156
|
+
const EntityClass = this.$nymph.getEntityClass(
|
|
1157
|
+
this.$sleepingReference[2],
|
|
1158
|
+
);
|
|
1159
|
+
this.$wakePromise = this.$nymph
|
|
1160
|
+
.getEntity(
|
|
1161
|
+
{
|
|
1162
|
+
class: EntityClass,
|
|
1163
|
+
skipAc: this.$skipAc,
|
|
1164
|
+
},
|
|
1165
|
+
{ type: '&', guid: this.$sleepingReference[1] },
|
|
1166
|
+
)
|
|
1167
|
+
.then((entity) => {
|
|
1168
|
+
if (entity == null || entity.guid == null) {
|
|
1169
|
+
return Promise.reject(
|
|
1170
|
+
new InvalidStateError(
|
|
1171
|
+
'The sleeping reference could not be retrieved.',
|
|
1172
|
+
),
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
this.$isASleepingReference = false;
|
|
1176
|
+
this.$sleepingReference = null;
|
|
1177
|
+
this.guid = entity.guid;
|
|
1178
|
+
this.tags = entity.tags;
|
|
1179
|
+
this.cdate = entity.cdate;
|
|
1180
|
+
this.mdate = entity.mdate;
|
|
1181
|
+
this.$putData(entity.$getData(false), entity.$getSData(), 'server');
|
|
1182
|
+
|
|
1183
|
+
return this.$proxy();
|
|
1184
|
+
})
|
|
1185
|
+
.finally(() => {
|
|
1186
|
+
this.$wakePromise = null;
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
return this.$wakePromise;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
public $wakeAll(level?: number) {
|
|
1193
|
+
return new Promise((resolve, reject) => {
|
|
1194
|
+
// Run this once this entity is wake.
|
|
1195
|
+
const wakeProps = () => {
|
|
1196
|
+
let newLevel: number | undefined;
|
|
1197
|
+
// If level is undefined, keep going forever, otherwise, stop once we've
|
|
1198
|
+
// gone deep enough.
|
|
1199
|
+
if (level !== undefined) {
|
|
1200
|
+
newLevel = level - 1;
|
|
1201
|
+
}
|
|
1202
|
+
if (newLevel !== undefined && newLevel < 0) {
|
|
1203
|
+
resolve(this.$proxy());
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const promises: Promise<any>[] = [];
|
|
1207
|
+
// Go through data looking for entities to wake.
|
|
1208
|
+
const walkData = (data: any) => {
|
|
1209
|
+
if (data == null) {
|
|
1210
|
+
return;
|
|
1211
|
+
} else if (
|
|
1212
|
+
typeof data === 'function' &&
|
|
1213
|
+
data.prototype instanceof Entity
|
|
1214
|
+
) {
|
|
1215
|
+
// Ignore this, it is an entity class.
|
|
1216
|
+
return;
|
|
1217
|
+
} else if (data instanceof Entity) {
|
|
1218
|
+
// Wake it up.
|
|
1219
|
+
if (data.$isASleepingReference) {
|
|
1220
|
+
promises.push(data.$wakeAll(newLevel));
|
|
1221
|
+
}
|
|
1222
|
+
} else if (Array.isArray(data)) {
|
|
1223
|
+
// Recurse into lower arrays.
|
|
1224
|
+
for (let i = 0; i < data.length; i++) {
|
|
1225
|
+
walkData(data[i]);
|
|
1226
|
+
}
|
|
1227
|
+
} else if (data instanceof Object) {
|
|
1228
|
+
// Recurse into objects.
|
|
1229
|
+
for (let [key, value] of Object.entries(data)) {
|
|
1230
|
+
walkData(value);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
walkData(this.$data);
|
|
1235
|
+
if (promises.length) {
|
|
1236
|
+
Promise.all(promises).then(
|
|
1237
|
+
() => resolve(this.$proxy()),
|
|
1238
|
+
(errObj) => reject(errObj),
|
|
1239
|
+
);
|
|
1240
|
+
} else {
|
|
1241
|
+
resolve(this.$proxy());
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1245
|
+
if (this.$isASleepingReference) {
|
|
1246
|
+
this.$wake().then(wakeProps, (errObj) => reject(errObj));
|
|
1247
|
+
} else {
|
|
1248
|
+
wakeProps();
|
|
1249
|
+
}
|
|
1250
|
+
}) as Promise<Entity<T>>;
|
|
859
1251
|
}
|
|
860
1252
|
|
|
861
1253
|
public async $refresh() {
|
|
862
|
-
this.$
|
|
1254
|
+
await this.$wake();
|
|
863
1255
|
|
|
864
1256
|
if (this.guid == null) {
|
|
865
1257
|
return false;
|
|
@@ -870,40 +1262,42 @@ export default class Entity<T extends EntityData = EntityData>
|
|
|
870
1262
|
skipCache: true,
|
|
871
1263
|
skipAc: this.$skipAc,
|
|
872
1264
|
},
|
|
873
|
-
{ type: '&', guid: this.guid }
|
|
1265
|
+
{ type: '&', guid: this.guid },
|
|
874
1266
|
);
|
|
875
1267
|
if (refresh == null) {
|
|
876
|
-
|
|
1268
|
+
throw new MethodFailedError("The entity's data could not be refreshed.");
|
|
877
1269
|
}
|
|
878
1270
|
this.tags = refresh.tags;
|
|
879
1271
|
this.cdate = refresh.cdate;
|
|
880
1272
|
this.mdate = refresh.mdate;
|
|
881
|
-
this.$putData(refresh.$getData(), refresh.$getSData());
|
|
1273
|
+
this.$putData(refresh.$getData(false), refresh.$getSData(), 'server');
|
|
882
1274
|
return true;
|
|
883
1275
|
}
|
|
884
1276
|
|
|
885
1277
|
public $removeTag(...tags: string[]) {
|
|
886
|
-
this.$
|
|
1278
|
+
this.$check();
|
|
887
1279
|
|
|
888
1280
|
this.tags = difference(this.tags, tags);
|
|
889
1281
|
}
|
|
890
1282
|
|
|
891
|
-
public async $save(): Promise<
|
|
892
|
-
this.$
|
|
1283
|
+
public async $save(): Promise<void> {
|
|
1284
|
+
await this.$wake();
|
|
893
1285
|
|
|
894
|
-
|
|
1286
|
+
if (!(await this.$nymph.saveEntity(this))) {
|
|
1287
|
+
throw new MethodFailedError('Failed to save entity.');
|
|
1288
|
+
}
|
|
895
1289
|
}
|
|
896
1290
|
|
|
897
|
-
public $toReference() {
|
|
1291
|
+
public $toReference(existingOnly?: boolean) {
|
|
898
1292
|
if (this.$isASleepingReference && this.$sleepingReference != null) {
|
|
899
1293
|
return this.$sleepingReference;
|
|
900
1294
|
}
|
|
901
|
-
if (this.guid == null) {
|
|
902
|
-
return this;
|
|
1295
|
+
if (existingOnly && this.guid == null) {
|
|
1296
|
+
return this.$proxy();
|
|
903
1297
|
}
|
|
904
1298
|
return [
|
|
905
1299
|
'nymph_entity_reference',
|
|
906
|
-
this
|
|
1300
|
+
this.$getGuaranteedGUID(),
|
|
907
1301
|
(this.constructor as any).class as string,
|
|
908
1302
|
] as EntityReference;
|
|
909
1303
|
}
|