@nymphjs/nymph 1.0.0-beta.13 → 1.0.0-beta.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/CHANGELOG.md +644 -0
  2. package/README.md +34 -24
  3. package/dist/Entity.d.ts +304 -15
  4. package/dist/Entity.js +609 -154
  5. package/dist/Entity.js.map +1 -1
  6. package/dist/Entity.test.js +75 -29
  7. package/dist/Entity.test.js.map +1 -1
  8. package/dist/Entity.types.d.ts +275 -28
  9. package/dist/Entity.types.js +1 -2
  10. package/dist/Nymph.d.ts +575 -11
  11. package/dist/Nymph.js +825 -288
  12. package/dist/Nymph.js.map +1 -1
  13. package/dist/Nymph.types.d.ts +117 -7
  14. package/dist/Nymph.types.js +47 -5
  15. package/dist/Nymph.types.js.map +1 -1
  16. package/dist/conf/defaults.d.ts +1 -1
  17. package/dist/conf/defaults.js +26 -3
  18. package/dist/conf/defaults.js.map +1 -1
  19. package/dist/conf/index.d.ts +50 -2
  20. package/dist/conf/index.js +2 -8
  21. package/dist/conf/index.js.map +1 -1
  22. package/dist/decorators/index.d.ts +1 -0
  23. package/dist/decorators/index.js +2 -0
  24. package/dist/decorators/index.js.map +1 -0
  25. package/dist/decorators/transactional.d.ts +12 -0
  26. package/dist/decorators/transactional.js +45 -0
  27. package/dist/decorators/transactional.js.map +1 -0
  28. package/dist/driver/NymphDriver.d.ts +209 -41
  29. package/dist/driver/NymphDriver.js +737 -242
  30. package/dist/driver/NymphDriver.js.map +1 -1
  31. package/dist/driver/index.d.ts +1 -2
  32. package/dist/driver/index.js +2 -23
  33. package/dist/driver/index.js.map +1 -1
  34. package/dist/driver/testArtifacts.d.ts +4 -4
  35. package/dist/driver/testArtifacts.js +2016 -1086
  36. package/dist/driver/testArtifacts.js.map +1 -1
  37. package/dist/errors/ClassNotAvailableError.js +1 -5
  38. package/dist/errors/ClassNotAvailableError.js.map +1 -1
  39. package/dist/errors/EntityConflictError.js +1 -5
  40. package/dist/errors/EntityConflictError.js.map +1 -1
  41. package/dist/errors/EntityInvalidDataError.d.ts +6 -0
  42. package/dist/errors/EntityInvalidDataError.js +8 -6
  43. package/dist/errors/EntityInvalidDataError.js.map +1 -1
  44. package/dist/errors/EntityIsSleepingReferenceError.js +1 -5
  45. package/dist/errors/EntityIsSleepingReferenceError.js.map +1 -1
  46. package/dist/errors/EntityUniqueConstraintError.d.ts +3 -0
  47. package/dist/errors/EntityUniqueConstraintError.js +7 -0
  48. package/dist/errors/EntityUniqueConstraintError.js.map +1 -0
  49. package/dist/errors/HttpError.d.ts +5 -0
  50. package/dist/errors/HttpError.js +11 -0
  51. package/dist/errors/HttpError.js.map +1 -0
  52. package/dist/errors/InvalidParametersError.js +1 -5
  53. package/dist/errors/InvalidParametersError.js.map +1 -1
  54. package/dist/errors/InvalidStateError.d.ts +3 -0
  55. package/dist/errors/InvalidStateError.js +7 -0
  56. package/dist/errors/InvalidStateError.js.map +1 -0
  57. package/dist/errors/MethodFailedError.d.ts +3 -0
  58. package/dist/errors/MethodFailedError.js +7 -0
  59. package/dist/errors/MethodFailedError.js.map +1 -0
  60. package/dist/errors/NotConfiguredError.js +1 -5
  61. package/dist/errors/NotConfiguredError.js.map +1 -1
  62. package/dist/errors/QueryFailedError.d.ts +2 -1
  63. package/dist/errors/QueryFailedError.js +5 -6
  64. package/dist/errors/QueryFailedError.js.map +1 -1
  65. package/dist/errors/UnableToConnectError.js +1 -5
  66. package/dist/errors/UnableToConnectError.js.map +1 -1
  67. package/dist/errors/index.d.ts +12 -8
  68. package/dist/errors/index.js +12 -24
  69. package/dist/errors/index.js.map +1 -1
  70. package/dist/helpers/index.d.ts +1 -0
  71. package/dist/helpers/index.js +2 -0
  72. package/dist/helpers/index.js.map +1 -0
  73. package/dist/helpers/transaction.d.ts +15 -0
  74. package/dist/helpers/transaction.js +47 -0
  75. package/dist/helpers/transaction.js.map +1 -0
  76. package/dist/index.d.ts +13 -11
  77. package/dist/index.js +16 -34
  78. package/dist/index.js.map +1 -1
  79. package/dist/nymphJoiProps.d.ts +1 -1
  80. package/dist/nymphJoiProps.js +10 -15
  81. package/dist/nymphJoiProps.js.map +1 -1
  82. package/dist/testArtifacts.d.ts +21 -6
  83. package/dist/testArtifacts.js +124 -50
  84. package/dist/testArtifacts.js.map +1 -1
  85. package/dist/testMocks.d.ts +27 -12
  86. package/dist/testMocks.js +44 -33
  87. package/dist/testMocks.js.map +1 -1
  88. package/dist/utils.d.ts +5 -5
  89. package/dist/utils.js +106 -68
  90. package/dist/utils.js.map +1 -1
  91. package/jest.config.js +31 -5
  92. package/package.json +43 -24
  93. package/src/Entity.test.ts +73 -23
  94. package/src/Entity.ts +555 -169
  95. package/src/Entity.types.ts +102 -90
  96. package/src/Nymph.ts +930 -517
  97. package/src/Nymph.types.ts +145 -39
  98. package/src/conf/defaults.ts +34 -1
  99. package/src/conf/index.ts +50 -2
  100. package/src/decorators/index.ts +1 -0
  101. package/src/decorators/transactional.ts +63 -0
  102. package/src/driver/NymphDriver.ts +987 -417
  103. package/src/driver/index.ts +1 -3
  104. package/src/driver/testArtifacts.ts +2699 -1628
  105. package/src/errors/EntityUniqueConstraintError.ts +6 -0
  106. package/src/errors/HttpError.ts +12 -0
  107. package/src/errors/InvalidStateError.ts +6 -0
  108. package/src/errors/MethodFailedError.ts +6 -0
  109. package/src/errors/QueryFailedError.ts +3 -1
  110. package/src/errors/index.ts +16 -8
  111. package/src/helpers/index.ts +1 -0
  112. package/src/helpers/transaction.ts +65 -0
  113. package/src/index.ts +13 -11
  114. package/src/nymphJoiProps.ts +2 -2
  115. package/src/testArtifacts.ts +69 -20
  116. package/src/testMocks.ts +107 -41
  117. package/src/utils.ts +102 -67
  118. package/tsconfig.json +6 -3
  119. package/typedoc.json +4 -0
  120. package/dist/conf/d.d.ts +0 -6
  121. package/dist/conf/d.js +0 -3
  122. package/dist/conf/d.js.map +0 -1
  123. 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 { EntityConflictError, InvalidParametersError } from './errors';
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<T extends EntityData = EntityData>
91
- implements EntityInterface
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 'user' and 'group' properties will
160
- * be filtered for non-admins when Tilmeld is being used.
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;
199
251
 
200
252
  /**
201
- * Load an entity.
202
- * @param guid The ID of the entity to load, undefined for a new entity.
253
+ * Alter the options for a query for this entity.
254
+ *
255
+ * @param options The current options.
256
+ * @returns The altered options.
203
257
  */
204
- public constructor(guid?: string) {
258
+ static alterOptions?<T extends Options>(options: T): T;
259
+
260
+ /**
261
+ * Initialize an entity.
262
+ */
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.$referenceWake();
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.$referenceWake();
273
+ this.$check();
215
274
 
216
275
  if (this.$sdata.hasOwnProperty(name)) {
217
276
  data[name] = referencesToEntities(
218
- JSON.parse(this.$sdata[name]),
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.$referenceWake();
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.$referenceWake();
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.$referenceWake();
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.$referenceWake();
330
+ this.$check();
268
331
 
269
332
  if (this.$sdata.hasOwnProperty(name)) {
270
333
  data[name] = referencesToEntities(
271
- JSON.parse(this.$sdata[name]),
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.$referenceWake();
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,20 +357,6 @@ export default class Entity<T extends EntityData = EntityData>
290
357
 
291
358
  this.$data = new Proxy(this.$dataStore, this.$dataHandler);
292
359
 
293
- if (guid != null) {
294
- const entity = this.$nymph.driver.getEntitySync(
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
- }
306
-
307
360
  return new Proxy(this, {
308
361
  has: (entity: Entity, name: string) => {
309
362
  if (
@@ -314,7 +367,7 @@ export default class Entity<T extends EntityData = EntityData>
314
367
  return name in entity;
315
368
  }
316
369
 
317
- this.$referenceWake();
370
+ this.$check();
318
371
  return name in entity.$data;
319
372
  },
320
373
 
@@ -325,12 +378,12 @@ export default class Entity<T extends EntityData = EntityData>
325
378
  name.substring(0, 1) === '$'
326
379
  ) {
327
380
  if (name === 'tags' || name === 'cdate' || name === 'mdate') {
328
- this.$referenceWake();
381
+ this.$check();
329
382
  }
330
383
  return (entity as any)[name];
331
384
  }
332
385
 
333
- this.$referenceWake();
386
+ this.$check();
334
387
  if (name in entity.$data) {
335
388
  return entity.$data[name];
336
389
  }
@@ -344,11 +397,11 @@ export default class Entity<T extends EntityData = EntityData>
344
397
  name.substring(0, 1) === '$'
345
398
  ) {
346
399
  if (name === 'tags' || name === 'cdate' || name === 'mdate') {
347
- this.$referenceWake();
400
+ this.$check();
348
401
  }
349
402
  (entity as any)[name] = value;
350
403
  } else {
351
- this.$referenceWake();
404
+ this.$check();
352
405
  (entity.$data as any)[name] = value;
353
406
  }
354
407
  return true;
@@ -358,7 +411,7 @@ export default class Entity<T extends EntityData = EntityData>
358
411
  if (name in entity) {
359
412
  return delete (entity as any)[name];
360
413
  } else if (name in entity.$data) {
361
- this.$referenceWake();
414
+ this.$check();
362
415
  return delete entity.$data[name];
363
416
  }
364
417
  return true;
@@ -371,7 +424,7 @@ export default class Entity<T extends EntityData = EntityData>
371
424
  defineProperty: (
372
425
  entity: Entity,
373
426
  name: string,
374
- descriptor: PropertyDescriptor
427
+ descriptor: PropertyDescriptor,
375
428
  ) => {
376
429
  if (
377
430
  typeof name !== 'string' ||
@@ -379,11 +432,11 @@ export default class Entity<T extends EntityData = EntityData>
379
432
  name.substring(0, 1) === '$'
380
433
  ) {
381
434
  if (name === 'tags' || name === 'cdate' || name === 'mdate') {
382
- this.$referenceWake();
435
+ this.$check();
383
436
  }
384
437
  Object.defineProperty(entity, name, descriptor);
385
438
  } else {
386
- this.$referenceWake();
439
+ this.$check();
387
440
  Object.defineProperty(entity.$data, name, descriptor);
388
441
  }
389
442
  return true;
@@ -396,48 +449,147 @@ export default class Entity<T extends EntityData = EntityData>
396
449
  name.substring(0, 1) === '$'
397
450
  ) {
398
451
  if (name === 'tags' || name === 'cdate' || name === 'mdate') {
399
- this.$referenceWake();
452
+ this.$check();
400
453
  }
401
454
  return Object.getOwnPropertyDescriptor(entity, name);
402
455
  } else {
403
- this.$referenceWake();
456
+ this.$check();
404
457
  return Object.getOwnPropertyDescriptor(entity.$data, name);
405
458
  }
406
459
  },
407
460
 
408
461
  ownKeys: (entity: Entity) => {
409
- this.$referenceWake();
462
+ this.$check();
410
463
  return Object.getOwnPropertyNames(entity).concat(
411
- Object.getOwnPropertyNames(entity.$data)
464
+ Object.getOwnPropertyNames(entity.$data),
412
465
  );
413
466
  },
414
467
  }) as Entity<T>;
415
468
  }
416
469
 
417
- public static async factory(guid?: string) {
470
+ /**
471
+ * Create or retrieve a new entity instance.
472
+ *
473
+ * Note that this will always return an entity, even if the GUID is not found.
474
+ *
475
+ * @param guid An optional GUID to retrieve.
476
+ */
477
+ public static async factory<E extends Entity>(
478
+ this: {
479
+ new (): E;
480
+ },
481
+ guid?: string,
482
+ ): Promise<E & EntityDataType<E>> {
418
483
  const entity = new this();
419
484
  if (guid != null) {
420
- const entity = await this.nymph.getEntity(
485
+ const entity = await (
486
+ this as unknown as EntityConstructor
487
+ ).nymph.getEntity(
421
488
  {
422
- class: this as any as EntityConstructor,
489
+ class: this as unknown as EntityConstructor,
423
490
  },
424
- { type: '&', guid }
491
+ { type: '&', guid },
425
492
  );
426
493
  if (entity != null) {
427
- return entity;
494
+ return entity as E & EntityDataType<E>;
428
495
  }
429
496
  }
430
- return entity;
497
+ return entity as E & EntityDataType<E>;
431
498
  }
432
499
 
433
- public static factorySync(guid?: string) {
434
- return new this(guid);
500
+ /**
501
+ * Create a new entity instance.
502
+ */
503
+ public static factorySync<E extends Entity>(this: {
504
+ new (): E;
505
+ }): E & EntityDataType<E> {
506
+ return new this() as E & EntityDataType<E>;
435
507
  }
436
508
 
437
- public static factoryReference(reference: EntityReference) {
509
+ /**
510
+ * Create a new sleeping reference instance.
511
+ *
512
+ * Sleeping references won't retrieve their data from the database until they
513
+ * are readied with `$wake()` or a parent's `$wakeAll()`.
514
+ *
515
+ * @param reference The Nymph Entity Reference to use to wake.
516
+ * @returns The new instance.
517
+ */
518
+ public static factoryReference<E extends Entity>(
519
+ this: {
520
+ new (): E;
521
+ },
522
+ reference: EntityReference,
523
+ ): E & EntityDataType<E> {
438
524
  const entity = new this();
439
525
  entity.$referenceSleep(reference);
440
- return entity;
526
+ return entity as E & EntityDataType<E>;
527
+ }
528
+
529
+ /**
530
+ * Get an array of strings that **must** be unique across the current etype.
531
+ *
532
+ * When you try to save another entity with any of the same unique strings,
533
+ * Nymph will throw an error.
534
+ *
535
+ * The default implementation of this static method instantiates the entity,
536
+ * assigns all of the given data, then calls `$getUniques` and returns its
537
+ * output. This can have a performance impact if a lot of extra processing
538
+ * happens during any of these steps. You can override this method to
539
+ * calculate the unique strings faster, but you must return the same strings
540
+ * that would be returned by `$getUniques`.
541
+ *
542
+ * @returns Resolves to an array of entity's unique constraint strings.
543
+ */
544
+ public static async getUniques({
545
+ guid,
546
+ cdate,
547
+ mdate,
548
+ tags,
549
+ data,
550
+ sdata,
551
+ }: {
552
+ guid?: string;
553
+ cdate?: number;
554
+ mdate?: number;
555
+ tags: string[];
556
+ data: EntityData;
557
+ sdata?: SerializedEntityData;
558
+ }) {
559
+ const entity = new this();
560
+ if (guid != null) {
561
+ entity.guid = guid;
562
+ }
563
+ if (cdate != null) {
564
+ entity.cdate = cdate;
565
+ }
566
+ if (mdate != null) {
567
+ entity.mdate = mdate;
568
+ }
569
+ entity.tags = tags;
570
+ entity.$putData(data, sdata);
571
+ return await entity.$getUniques();
572
+ }
573
+
574
+ /**
575
+ * Get a string for full text search for one of an entity's properties.
576
+ *
577
+ * The result will be tokenized and stored as the full text search index for
578
+ * use with "search" clauses.
579
+ *
580
+ * Return null to not include any tokens in the full text search storage.
581
+ *
582
+ * You shouldn't use any other entity data to transform the text, as it won't
583
+ * always be available. This function is meant for things like stripping HTML
584
+ * tags.
585
+ *
586
+ * @returns By default, returns the value if it is a string.
587
+ */
588
+ public static getFTSText(_name: string, value: any) {
589
+ if (typeof value === 'string') {
590
+ return value;
591
+ }
592
+ return null;
441
593
  }
442
594
 
443
595
  public toJSON() {
@@ -445,24 +597,65 @@ export default class Entity<T extends EntityData = EntityData>
445
597
  return this.$sleepingReference;
446
598
  }
447
599
  const obj: EntityJson = {
448
- class: (this.constructor as any).class as string,
600
+ class: entityConstructorsToClassNames(this.constructor),
449
601
  guid: this.guid,
450
602
  cdate: this.cdate,
451
603
  mdate: this.mdate,
452
604
  tags: [...this.tags],
453
605
  data: {},
454
606
  };
455
- const data = this.$getData(true);
456
- for (const key in data) {
607
+ for (const key of [
608
+ ...Object.keys(this.$dataStore),
609
+ ...Object.keys(this.$sdata),
610
+ ]) {
457
611
  if (this.$privateData.indexOf(key) === -1) {
458
- obj.data[key] = entitiesToReferences(data[key]);
612
+ obj.data[key] = entityConstructorsToClassNames(
613
+ entitiesToReferences(this.$data[key], true),
614
+ );
459
615
  }
460
616
  }
461
617
  return obj;
462
618
  }
463
619
 
620
+ public $setNymph(nymph: Nymph) {
621
+ this.$nymph = nymph;
622
+ if (!this.$asleep()) {
623
+ const recurseData = (data: any) => {
624
+ if (Array.isArray(data)) {
625
+ for (let name in data) {
626
+ recurseData(data[name]);
627
+ }
628
+ } else if (data instanceof Entity) {
629
+ if (data.$nymph !== nymph) {
630
+ data.$setNymph(nymph);
631
+ }
632
+ } else if (
633
+ typeof data === 'function' &&
634
+ data.prototype instanceof Entity
635
+ ) {
636
+ data.nymph = nymph;
637
+ } else if (typeof data === 'object') {
638
+ for (let name in data) {
639
+ recurseData(data[name]);
640
+ }
641
+ }
642
+ };
643
+ recurseData(this.$data);
644
+ }
645
+ }
646
+
647
+ public $getGuaranteedGUID() {
648
+ if (this.guid !== null) {
649
+ return this.guid;
650
+ }
651
+ if (this.$guaranteedGUID == null) {
652
+ this.$guaranteedGUID = guid();
653
+ }
654
+ return this.$guaranteedGUID;
655
+ }
656
+
464
657
  public $addTag(...tags: string[]) {
465
- this.$referenceWake();
658
+ this.$check();
466
659
 
467
660
  if (tags.length < 1) {
468
661
  return;
@@ -471,7 +664,7 @@ export default class Entity<T extends EntityData = EntityData>
471
664
  }
472
665
 
473
666
  public $arraySearch(array: any[], strict = false) {
474
- this.$referenceWake();
667
+ this.$check();
475
668
 
476
669
  if (!Array.isArray(array)) {
477
670
  return -1;
@@ -486,23 +679,25 @@ export default class Entity<T extends EntityData = EntityData>
486
679
  }
487
680
 
488
681
  public $clearCache() {
489
- this.$referenceWake();
682
+ this.$check();
490
683
 
491
- this.$putData(this.$getData(), this.$getSData());
684
+ this.$putData(this.$getData(false), this.$getSData());
492
685
  }
493
686
 
494
687
  public $getClientEnabledMethods() {
495
688
  return this.$clientEnabledMethods;
496
689
  }
497
690
 
498
- public async $delete(): Promise<boolean> {
499
- this.$referenceWake();
691
+ public async $delete(): Promise<void> {
692
+ await this.$wake();
500
693
 
501
- return await this.$nymph.deleteEntity(this);
694
+ if (!(await this.$nymph.deleteEntity(this))) {
695
+ throw new MethodFailedError('Failed to delete entity.');
696
+ }
502
697
  }
503
698
 
504
699
  public $equals(object: any) {
505
- this.$referenceWake();
700
+ this.$check();
506
701
 
507
702
  if (!(object instanceof Entity)) {
508
703
  return false;
@@ -523,13 +718,17 @@ export default class Entity<T extends EntityData = EntityData>
523
718
  if (!isEqual(obTags, myTags)) {
524
719
  return false;
525
720
  }
526
- const obData = object.$getData(true);
527
- const myData = this.$getData(true);
721
+ const obData = entityConstructorsToClassNames(
722
+ entitiesToReferences(object.$getData(true), true),
723
+ );
724
+ const myData = entityConstructorsToClassNames(
725
+ entitiesToReferences(this.$getData(true), true),
726
+ );
528
727
  return isEqual(obData, myData);
529
728
  }
530
729
 
531
730
  public $getData(includeSData = false) {
532
- this.$referenceWake();
731
+ this.$check();
533
732
 
534
733
  if (includeSData) {
535
734
  // Access all the serialized properties to initialize them.
@@ -537,34 +736,82 @@ export default class Entity<T extends EntityData = EntityData>
537
736
  const _unused: any = (this as any)[key];
538
737
  }
539
738
  }
540
- return entitiesToReferences({ ...this.$dataStore });
739
+ return { ...this.$dataStore };
541
740
  }
542
741
 
543
742
  public $getSData() {
544
- this.$referenceWake();
743
+ this.$check();
545
744
 
546
745
  return this.$sdata;
547
746
  }
548
747
 
549
- public $getOriginalAcValues() {
550
- this.$referenceWake();
748
+ public async $getUniques(): Promise<string[]> {
749
+ return [];
750
+ }
751
+
752
+ public $getOriginalAcValues(): ACProperties {
753
+ this.$check();
551
754
 
552
- return (
553
- this.$originalAcValues ?? {
554
- user: this.$data.user,
555
- group: this.$data.group,
556
- acUser: this.$data.acUser,
557
- acGroup: this.$data.acGroup,
558
- acOther: this.$data.acOther,
559
- acRead: this.$data.acRead,
560
- acWrite: this.$data.acWrite,
561
- acFull: this.$data.acFull,
755
+ return this.$originalAcValues ?? this.$getCurrentAcValues();
756
+ }
757
+
758
+ public $getCurrentAcValues(): ACProperties {
759
+ this.$check();
760
+
761
+ return {
762
+ user: this.$getAcUid(),
763
+ group: this.$getAcGid(),
764
+ acUser: this.$data.acUser ?? null,
765
+ acGroup: this.$data.acGroup ?? null,
766
+ acOther: this.$data.acOther ?? null,
767
+ acRead: this.$getAcReadIds(),
768
+ acWrite: this.$getAcWriteIds(),
769
+ acFull: this.$getAcFullIds(),
770
+ };
771
+ }
772
+
773
+ public $getAcUid() {
774
+ if ('user' in this.$sdata) {
775
+ const userValue = JSON.parse(this.$sdata.user);
776
+ if (
777
+ Array.isArray(userValue) &&
778
+ userValue[0] === 'nymph_entity_reference'
779
+ ) {
780
+ return userValue[1];
562
781
  }
563
- );
782
+ }
783
+ return (this.$data.user?.guid ?? null) as string | null;
784
+ }
785
+ public $getAcGid() {
786
+ if ('group' in this.$sdata) {
787
+ const groupValue = JSON.parse(this.$sdata.group);
788
+ if (
789
+ Array.isArray(groupValue) &&
790
+ groupValue[0] === 'nymph_entity_reference'
791
+ ) {
792
+ return groupValue[1];
793
+ }
794
+ }
795
+ return (this.$data.group?.guid ?? null) as string | null;
796
+ }
797
+ public $getAcReadIds() {
798
+ return (this.$data.acRead?.map((entity: EntityInterface | string) =>
799
+ typeof entity === 'string' ? entity : entity.guid,
800
+ ) ?? null) as (string | null)[] | null;
801
+ }
802
+ public $getAcWriteIds() {
803
+ return (this.$data.acWrite?.map((entity: EntityInterface | string) =>
804
+ typeof entity === 'string' ? entity : entity.guid,
805
+ ) ?? null) as (string | null)[] | null;
806
+ }
807
+ public $getAcFullIds() {
808
+ return (this.$data.acFull?.map((entity: EntityInterface | string) =>
809
+ typeof entity === 'string' ? entity : entity.guid,
810
+ ) ?? null) as (string | null)[] | null;
564
811
  }
565
812
 
566
813
  public $getValidatable() {
567
- this.$referenceWake();
814
+ this.$check();
568
815
 
569
816
  // Access all the serialized properties to initialize them.
570
817
  for (const key in this.$sdata) {
@@ -575,18 +822,18 @@ export default class Entity<T extends EntityData = EntityData>
575
822
  cdate: this.cdate,
576
823
  mdate: this.mdate,
577
824
  tags: this.tags,
578
- ...referencesToEntities(this.$dataStore, this.$nymph, this.$skipAc),
825
+ ...this.$dataStore,
579
826
  };
580
827
  }
581
828
 
582
829
  public $getTags() {
583
- this.$referenceWake();
830
+ this.$check();
584
831
 
585
832
  return this.tags;
586
833
  }
587
834
 
588
835
  public $hasTag(...tags: string[]) {
589
- this.$referenceWake();
836
+ this.$check();
590
837
 
591
838
  if (!tags.length) {
592
839
  return false;
@@ -608,11 +855,15 @@ export default class Entity<T extends EntityData = EntityData>
608
855
  return false;
609
856
  }
610
857
 
611
- if (this.guid || object.guid) {
858
+ if (this === object) {
859
+ return true;
860
+ }
861
+
862
+ if (this.guid != null || object.guid != null) {
612
863
  return this.guid === object.guid;
613
864
  }
614
865
 
615
- this.$referenceWake();
866
+ this.$check();
616
867
  if (typeof object.$getData !== 'function') {
617
868
  return false;
618
869
  } else {
@@ -621,19 +872,22 @@ export default class Entity<T extends EntityData = EntityData>
621
872
  if (!isEqual(obTags, myTags)) {
622
873
  return false;
623
874
  }
624
- const obData = object.$getData(true);
625
- const myData = this.$getData(true);
875
+ const obData = entityConstructorsToClassNames(
876
+ entitiesToReferences(object.$getData(true), true),
877
+ );
878
+ const myData = entityConstructorsToClassNames(
879
+ entitiesToReferences(this.$getData(true), true),
880
+ );
626
881
  return isEqual(obData, myData);
627
882
  }
628
883
  }
629
884
 
630
885
  public $jsonAcceptData(input: EntityJson, allowConflict = false) {
631
- // TODO: Do this without causing everything to become unserialized.
632
- this.$referenceWake();
886
+ this.$check();
633
887
 
634
888
  if (this.guid != input.guid) {
635
889
  throw new EntityConflictError(
636
- 'Tried to accept JSON input for the wrong entity.'
890
+ 'Tried to accept JSON input for the wrong entity.',
637
891
  );
638
892
  }
639
893
 
@@ -658,7 +912,7 @@ export default class Entity<T extends EntityData = EntityData>
658
912
  this.$addTag(...protectedTags, ...tags);
659
913
 
660
914
  // Accept the data.
661
- const data = input.data;
915
+ const data = { ...input.data };
662
916
  const privateData: EntityData = {};
663
917
  for (const name of this.$privateData) {
664
918
  if (name in this.$data) {
@@ -671,12 +925,27 @@ export default class Entity<T extends EntityData = EntityData>
671
925
 
672
926
  const protectedData: EntityData = {};
673
927
  const protectedProps = [...this.$protectedData];
674
- if (
675
- this.$nymph.tilmeld &&
676
- !this.$nymph.tilmeld.gatekeeper('tilmeld/admin')
677
- ) {
678
- protectedProps.push('user');
679
- protectedProps.push('group');
928
+ if (this.$nymph.tilmeld) {
929
+ protectedProps.push('acUser');
930
+ protectedProps.push('acGroup');
931
+ protectedProps.push('acOther');
932
+ protectedProps.push('acRead');
933
+ protectedProps.push('acWrite');
934
+ protectedProps.push('acFull');
935
+ if (
936
+ ((this.constructor as EntityConstructor).class !== 'User' &&
937
+ (this.constructor as EntityConstructor).class !== 'Group') ||
938
+ !(this.$nymph.tilmeld as any).currentUser ||
939
+ (!(this.$nymph.tilmeld as any).currentUser.abilities?.includes(
940
+ 'tilmeld/admin',
941
+ ) &&
942
+ !(this.$nymph.tilmeld as any).currentUser.abilities?.includes(
943
+ 'system/admin',
944
+ ))
945
+ ) {
946
+ protectedProps.push('user');
947
+ protectedProps.push('group');
948
+ }
680
949
  }
681
950
  for (const name of protectedProps) {
682
951
  if (name in this.$data) {
@@ -709,11 +978,11 @@ export default class Entity<T extends EntityData = EntityData>
709
978
  }
710
979
 
711
980
  public $jsonAcceptPatch(patch: EntityPatch, allowConflict = false) {
712
- this.$referenceWake();
981
+ this.$check();
713
982
 
714
983
  if (this.guid != patch.guid) {
715
984
  throw new EntityConflictError(
716
- 'Tried to accept JSON patch for the wrong entity.'
985
+ 'Tried to accept JSON patch for the wrong entity.',
717
986
  );
718
987
  }
719
988
 
@@ -725,27 +994,47 @@ export default class Entity<T extends EntityData = EntityData>
725
994
  }
726
995
  this.mdate = patch.mdate;
727
996
 
997
+ const protectedProps = [...this.$protectedData];
998
+ if (this.$nymph.tilmeld) {
999
+ protectedProps.push('acUser');
1000
+ protectedProps.push('acGroup');
1001
+ protectedProps.push('acOther');
1002
+ protectedProps.push('acRead');
1003
+ protectedProps.push('acWrite');
1004
+ protectedProps.push('acFull');
1005
+ if (
1006
+ ((this.constructor as EntityConstructor).class !== 'User' &&
1007
+ (this.constructor as EntityConstructor).class !== 'Group') ||
1008
+ !(this.$nymph.tilmeld as any).currentUser ||
1009
+ (!(this.$nymph.tilmeld as any).currentUser.abilities?.includes(
1010
+ 'tilmeld/admin',
1011
+ ) &&
1012
+ !(this.$nymph.tilmeld as any).currentUser.abilities?.includes(
1013
+ 'system/admin',
1014
+ ))
1015
+ ) {
1016
+ protectedProps.push('user');
1017
+ protectedProps.push('group');
1018
+ }
1019
+ }
1020
+
728
1021
  for (const name in patch.set) {
729
1022
  if (
730
1023
  (this.$allowlistData != null &&
731
1024
  this.$allowlistData.indexOf(name) === -1) ||
732
- this.$protectedData.indexOf(name) !== -1 ||
1025
+ protectedProps.indexOf(name) !== -1 ||
733
1026
  this.$privateData.indexOf(name) !== -1
734
1027
  ) {
735
1028
  continue;
736
1029
  }
737
- (this.$data as any)[name] = referencesToEntities(
738
- patch.set[name],
739
- this.$nymph,
740
- this.$skipAc
741
- );
1030
+ (this.$data as any)[name] = patch.set[name];
742
1031
  }
743
1032
 
744
1033
  for (const name of patch.unset) {
745
1034
  if (
746
1035
  (this.$allowlistData != null &&
747
1036
  this.$allowlistData.indexOf(name) === -1) ||
748
- this.$protectedData.indexOf(name) !== -1 ||
1037
+ protectedProps.indexOf(name) !== -1 ||
749
1038
  this.$privateData.indexOf(name) !== -1
750
1039
  ) {
751
1040
  continue;
@@ -776,27 +1065,23 @@ export default class Entity<T extends EntityData = EntityData>
776
1065
  }
777
1066
  }
778
1067
 
779
- public $putData(data: EntityData, sdata?: SerializedEntityData) {
780
- this.$referenceWake();
1068
+ public $putData(
1069
+ data: EntityData,
1070
+ sdata?: SerializedEntityData,
1071
+ _source?: 'server',
1072
+ ) {
1073
+ this.$check();
781
1074
 
782
- const mySdata = sdata ?? this.$getSData();
783
- for (const name in data) {
784
- delete mySdata[name];
785
- }
786
1075
  for (const name in this.$dataStore) {
787
1076
  delete this.$dataStore[name];
788
1077
  }
789
1078
  for (const name in data) {
790
- (this.$dataStore as any)[name] = referencesToEntities(
791
- data[name],
792
- this.$nymph,
793
- this.$skipAc
794
- );
1079
+ (this.$dataStore as any)[name] = data[name];
795
1080
  }
796
- this.$sdata = mySdata;
1081
+ this.$sdata = sdata ?? {};
797
1082
  // Set original AC values if not set..
798
1083
  if (this.$originalAcValues == null) {
799
- this.$originalAcValues = this.$getOriginalAcValues();
1084
+ this.$originalAcValues = this.$getCurrentAcValues();
800
1085
  }
801
1086
  }
802
1087
 
@@ -813,14 +1098,14 @@ export default class Entity<T extends EntityData = EntityData>
813
1098
  ) {
814
1099
  throw new InvalidParametersError(
815
1100
  'referenceSleep expects parameter 1 to be a valid Nymph entity ' +
816
- 'reference.'
1101
+ 'reference.',
817
1102
  );
818
1103
  }
819
1104
  const thisClass: string = (this.constructor as any).class;
820
1105
  if (reference[2] !== thisClass) {
821
1106
  throw new InvalidParametersError(
822
1107
  'referenceSleep can only be called with an entity reference of the ' +
823
- `same class. Given class: ${reference[2]}; this class: ${thisClass}.`
1108
+ `same class. Given class: ${reference[2]}; this class: ${thisClass}.`,
824
1109
  );
825
1110
  }
826
1111
  this.$isASleepingReference = true;
@@ -828,38 +1113,137 @@ export default class Entity<T extends EntityData = EntityData>
828
1113
  this.$sleepingReference = reference;
829
1114
  }
830
1115
 
1116
+ /**
1117
+ * Check if this is a sleeping reference and throw an error if so.
1118
+ */
1119
+ protected $check() {
1120
+ if (this.$isASleepingReference || this.$sleepingReference != null) {
1121
+ throw new EntityIsSleepingReferenceError(
1122
+ 'This entity is in a sleeping reference state. You must use .$wake() to wake it.',
1123
+ );
1124
+ }
1125
+ }
1126
+
1127
+ /**
1128
+ * Check if this is a sleeping reference.
1129
+ */
1130
+ public $asleep() {
1131
+ return this.$isASleepingReference || this.$sleepingReference != null;
1132
+ }
1133
+
831
1134
  /**
832
1135
  * Wake from a sleeping reference.
833
- *
834
- * @returns True on success, false on failure.
835
1136
  */
836
- protected $referenceWake() {
1137
+ public $wake() {
837
1138
  if (!this.$isASleepingReference || this.$sleepingReference == null) {
838
- return true;
1139
+ this.$wakePromise = null;
1140
+ return Promise.resolve(this);
839
1141
  }
840
- const EntityClass = this.$nymph.getEntityClass(this.$sleepingReference[2]);
841
- const entity = this.$nymph.driver.getEntitySync(
842
- {
843
- class: EntityClass,
844
- skipAc: this.$skipAc,
845
- },
846
- { type: '&', guid: this.$sleepingReference[1] }
847
- );
848
- if (entity == null) {
849
- return false;
1142
+ if (this.$sleepingReference?.[1] == null) {
1143
+ throw new InvalidStateError(
1144
+ 'Tried to wake a sleeping reference with no GUID.',
1145
+ );
850
1146
  }
851
- this.$isASleepingReference = false;
852
- this.$sleepingReference = null;
853
- this.guid = entity.guid;
854
- this.tags = entity.tags;
855
- this.cdate = entity.cdate;
856
- this.mdate = entity.mdate;
857
- this.$putData(entity.$getData(), entity.$getSData());
858
- return true;
1147
+ if (!this.$wakePromise) {
1148
+ const EntityClass = this.$nymph.getEntityClass(
1149
+ this.$sleepingReference[2],
1150
+ );
1151
+ this.$wakePromise = this.$nymph
1152
+ .getEntity(
1153
+ {
1154
+ class: EntityClass,
1155
+ skipAc: this.$skipAc,
1156
+ },
1157
+ { type: '&', guid: this.$sleepingReference[1] },
1158
+ )
1159
+ .then((entity) => {
1160
+ if (entity == null || entity.guid == null) {
1161
+ return Promise.reject(
1162
+ new InvalidStateError(
1163
+ 'The sleeping reference could not be retrieved.',
1164
+ ),
1165
+ );
1166
+ }
1167
+ this.$isASleepingReference = false;
1168
+ this.$sleepingReference = null;
1169
+ this.guid = entity.guid;
1170
+ this.tags = entity.tags;
1171
+ this.cdate = entity.cdate;
1172
+ this.mdate = entity.mdate;
1173
+ this.$putData(entity.$getData(false), entity.$getSData(), 'server');
1174
+
1175
+ return this;
1176
+ })
1177
+ .finally(() => {
1178
+ this.$wakePromise = null;
1179
+ });
1180
+ }
1181
+ return this.$wakePromise;
1182
+ }
1183
+
1184
+ public $wakeAll(level?: number) {
1185
+ return new Promise((resolve, reject) => {
1186
+ // Run this once this entity is wake.
1187
+ const wakeProps = () => {
1188
+ let newLevel: number | undefined;
1189
+ // If level is undefined, keep going forever, otherwise, stop once we've
1190
+ // gone deep enough.
1191
+ if (level !== undefined) {
1192
+ newLevel = level - 1;
1193
+ }
1194
+ if (newLevel !== undefined && newLevel < 0) {
1195
+ resolve(this);
1196
+ return;
1197
+ }
1198
+ const promises: Promise<any>[] = [];
1199
+ // Go through data looking for entities to wake.
1200
+ const walkData = (data: any) => {
1201
+ if (data == null) {
1202
+ return;
1203
+ } else if (
1204
+ typeof data === 'function' &&
1205
+ data.prototype instanceof Entity
1206
+ ) {
1207
+ // Ignore this, it is an entity class.
1208
+ return;
1209
+ } else if (data instanceof Entity) {
1210
+ // Wake it up.
1211
+ if (data.$isASleepingReference) {
1212
+ promises.push(data.$wakeAll(newLevel));
1213
+ }
1214
+ } else if (Array.isArray(data)) {
1215
+ // Recurse into lower arrays.
1216
+ for (let i = 0; i < data.length; i++) {
1217
+ walkData(data[i]);
1218
+ }
1219
+ } else if (data instanceof Object) {
1220
+ // Recurse into objects.
1221
+ for (let [key, value] of Object.entries(data)) {
1222
+ walkData(value);
1223
+ }
1224
+ }
1225
+ };
1226
+ walkData(this.$data);
1227
+ if (promises.length) {
1228
+ Promise.all(promises).then(
1229
+ () => resolve(this),
1230
+ (errObj) => reject(errObj),
1231
+ );
1232
+ } else {
1233
+ resolve(this);
1234
+ }
1235
+ };
1236
+
1237
+ if (this.$isASleepingReference) {
1238
+ this.$wake().then(wakeProps, (errObj) => reject(errObj));
1239
+ } else {
1240
+ wakeProps();
1241
+ }
1242
+ }) as Promise<Entity<T>>;
859
1243
  }
860
1244
 
861
1245
  public async $refresh() {
862
- this.$referenceWake();
1246
+ await this.$wake();
863
1247
 
864
1248
  if (this.guid == null) {
865
1249
  return false;
@@ -870,40 +1254,42 @@ export default class Entity<T extends EntityData = EntityData>
870
1254
  skipCache: true,
871
1255
  skipAc: this.$skipAc,
872
1256
  },
873
- { type: '&', guid: this.guid }
1257
+ { type: '&', guid: this.guid },
874
1258
  );
875
1259
  if (refresh == null) {
876
- return 0;
1260
+ throw new MethodFailedError("The entity's data could not be refreshed.");
877
1261
  }
878
1262
  this.tags = refresh.tags;
879
1263
  this.cdate = refresh.cdate;
880
1264
  this.mdate = refresh.mdate;
881
- this.$putData(refresh.$getData(), refresh.$getSData());
1265
+ this.$putData(refresh.$getData(false), refresh.$getSData(), 'server');
882
1266
  return true;
883
1267
  }
884
1268
 
885
1269
  public $removeTag(...tags: string[]) {
886
- this.$referenceWake();
1270
+ this.$check();
887
1271
 
888
1272
  this.tags = difference(this.tags, tags);
889
1273
  }
890
1274
 
891
- public async $save(): Promise<boolean> {
892
- this.$referenceWake();
1275
+ public async $save(): Promise<void> {
1276
+ await this.$wake();
893
1277
 
894
- return await this.$nymph.saveEntity(this);
1278
+ if (!(await this.$nymph.saveEntity(this))) {
1279
+ throw new MethodFailedError('Failed to save entity.');
1280
+ }
895
1281
  }
896
1282
 
897
- public $toReference() {
1283
+ public $toReference(existingOnly?: boolean) {
898
1284
  if (this.$isASleepingReference && this.$sleepingReference != null) {
899
1285
  return this.$sleepingReference;
900
1286
  }
901
- if (this.guid == null) {
1287
+ if (existingOnly && this.guid == null) {
902
1288
  return this;
903
1289
  }
904
1290
  return [
905
1291
  'nymph_entity_reference',
906
- this.guid,
1292
+ this.$getGuaranteedGUID(),
907
1293
  (this.constructor as any).class as string,
908
1294
  ] as EntityReference;
909
1295
  }