@prestizni-software/server-dem 0.4.95 → 0.4.97

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.
@@ -7,7 +7,9 @@ export class AutoUpdatedClientObject {
7
7
  preLoad;
8
8
  registerSocket;
9
9
  readyLoggers;
10
- loadFromDB(a) { }
10
+ loadFromDB(a) {
11
+ return a;
12
+ }
11
13
  socket;
12
14
  data;
13
15
  isServer = false;
@@ -31,11 +33,14 @@ export class AutoUpdatedClientObject {
31
33
  this.generateSettersAndGetters();
32
34
  await this.loadForceReferences();
33
35
  for (const thing of this.toChangeOnParents) {
34
- await this.setValue(thing.key, thing.value, { silent: true, isParentUpdate: true });
36
+ await this.setValue(thing.key, thing.value, {
37
+ silent: true,
38
+ isParentUpdate: true,
39
+ });
35
40
  }
36
41
  }
37
42
  catch (error) {
38
- this.loggers.error("Error loading references: " + error.message);
43
+ // this.loggers?.error?.("Error loading references: " + error.message);
39
44
  }
40
45
  finally {
41
46
  this.isLoadingReferences = false;
@@ -59,18 +64,8 @@ export class AutoUpdatedClientObject {
59
64
  this.parentManager = parentManager;
60
65
  this.callbacks = callback;
61
66
  this.emitter = emitter;
62
- this.properties = undefined;
63
- if (!classParam &&
64
- !socket &&
65
- !data &&
66
- !loggers &&
67
- !className &&
68
- !parentManager &&
69
- !callback &&
70
- !emitter)
71
- return;
72
- else
73
- throw new Error("Missing arguments???");
67
+ this.properties = [];
68
+ return;
74
69
  }
75
70
  this.classParam = classParam;
76
71
  this.socket = socket;
@@ -83,31 +78,44 @@ export class AutoUpdatedClientObject {
83
78
  const allProps = new Set();
84
79
  let proto = classParam.prototype;
85
80
  while (proto && proto !== Object.prototype) {
86
- const props = Reflect.getOwnMetadata("props", proto) || [];
87
- for (const p of props)
88
- allProps.add(p);
81
+ const props = Reflect.getOwnMetadata("props", proto);
82
+ if (props) {
83
+ for (const p of props)
84
+ allProps.add(p);
85
+ }
89
86
  proto = Object.getPrototypeOf(proto);
90
87
  }
91
88
  this.properties = Array.from(allProps);
92
- this.callbacks = callback;
93
- this.loggers = {
94
- debug: (s) => loggers.debug(`[${this.className}: ${this.data?._id ?? this._id ?? "not loaded"}] ${s}`),
95
- info: (s) => loggers.info(`[${this.className}: ${this.data?._id ?? this._id ?? "not loaded"}] ${s}`),
96
- warn: (s) => loggers.warn(`[${this.className}: ${this.data?._id ?? this._id ?? "not loaded"}] ${s}`),
97
- error: (s) => loggers.error(`[${this.className}: ${this.data?._id ?? this._id ?? "not loaded"}] ${s}`),
89
+ this.callbacks = callback || {
90
+ new: () => { },
91
+ update: () => { },
92
+ delete: () => { },
93
+ progress: () => { },
98
94
  };
99
95
  if (typeof data === "string") {
100
96
  this.data = { _id: data };
97
+ }
98
+ else {
99
+ this.data = data;
100
+ }
101
+ const currentId = this.data?._id?.toString() ?? "not loaded";
102
+ this.loggers = {
103
+ debug: (s) => loggers.debug?.(`[${this.className}: ${currentId}] ${s}`),
104
+ info: (s) => loggers.info?.(`[${this.className}: ${currentId}] ${s}`),
105
+ warn: (s) => loggers.warn?.(`[${this.className}: ${currentId}] ${s}`),
106
+ error: (s) => loggers.error?.(`[${this.className}: ${currentId}] ${s}`),
107
+ };
108
+ if (typeof data === "string") {
101
109
  if (this.isServer) {
102
110
  this.isLoading = false;
103
111
  this.generateSettersAndGetters();
112
+ this.emitter.emit(EVENT_INTERNAL_PRE_LOADED + this.EmitterID);
104
113
  return;
105
114
  }
106
115
  this.socket.emit(EVENT_GET + this.className + data, null, async (res) => {
107
116
  if (!res.success) {
108
117
  this.isLoading = false;
109
- this.loggers.error("Could not load data from server: " + res.message);
110
- this.emitter.emit(EVENT_INTERNAL_PRE_LOADED + this.EmitterID);
118
+ this.emitter.emit(EVENT_INTERNAL_PRE_LOADED + this.EmitterID, true, res.message);
111
119
  return;
112
120
  }
113
121
  this.data = res.data;
@@ -119,60 +127,71 @@ export class AutoUpdatedClientObject {
119
127
  });
120
128
  }
121
129
  else {
122
- this.isLoading = true;
123
- this.data = data;
124
- for (const key of this.properties || []) {
130
+ const currentDataRec = this.data;
131
+ for (const key of this.properties) {
125
132
  const isRef = getMetadataRecursive("isRef", this, key);
126
- if (isRef && this.data[key]) {
127
- if (Array.isArray(this.data[key])) {
128
- this.data[key] = this.data[key].map((obj) => obj._id?.toString() ?? obj?.toString());
133
+ if (isRef && currentDataRec[key]) {
134
+ if (Array.isArray(currentDataRec[key])) {
135
+ currentDataRec[key] = currentDataRec[key].map((obj) => obj._id?.toString() ?? obj?.toString());
129
136
  }
130
137
  else {
131
- this.data[key] = this.data[key]?._id?.toString() ?? this.data[key]?.toString();
138
+ currentDataRec[key] =
139
+ currentDataRec[key]?._id?.toString() ??
140
+ currentDataRec[key]?.toString();
132
141
  }
133
142
  }
134
143
  }
135
- if ((!this.data._id || this.data._id === "") && !this.isServer) {
144
+ if ((!currentDataRec["_id"] || currentDataRec["_id"] === "") &&
145
+ !this.isServer) {
146
+ this.isLoading = true;
136
147
  this.handleNewObject(this.data);
137
148
  }
138
149
  else {
139
150
  this.isLoading = false;
151
+ this.emitter.emit(EVENT_INTERNAL_PRE_LOADED + this.EmitterID);
140
152
  if (!this.isServer) {
141
153
  this.openSockets();
142
- this.onUpdate(); // Client load/creation trigger
154
+ this.onUpdate();
143
155
  }
144
156
  }
145
157
  }
146
158
  this.generateSettersAndGetters();
147
- // Re-apply getters in a microtask to override any shadowing from subclass field initializers.
159
+ // Ensure setters/getters are set after child constructor
148
160
  Promise.resolve().then(() => {
149
161
  this.generateSettersAndGetters();
150
162
  });
163
+ if (!this.isServer && !this.isLoaded) {
164
+ this.loadShit();
165
+ }
151
166
  }
152
167
  async waitForPreloaded() {
153
168
  if (this.isLoaded)
154
169
  return;
155
- // console.log(`[DEBUG] ${this.className} Waiting for preloaded - ID: ${this.data?._id ?? (this as any)._id}`);
156
- await new Promise((resolve, reject) => {
170
+ return new Promise((resolve, reject) => {
157
171
  const timer = setTimeout(() => {
158
- console.error(`[FATAL] ${this.className} TIMEOUT waiting for preloaded - ID: ${this.data?._id ?? this._id}`);
159
- reject(new Error("Timeout waiting for preloaded"));
172
+ this.emitter.off(EVENT_INTERNAL_PRE_LOADED + this.EmitterID, onPreloaded);
173
+ reject(new Error(`Timeout waiting for preloaded: ${this.className}`));
160
174
  }, 10000);
161
- this.emitter.once(EVENT_INTERNAL_PRE_LOADED + this.EmitterID, (failed, reason) => {
175
+ const onPreloaded = (failed, reason) => {
162
176
  clearTimeout(timer);
163
177
  if (failed)
164
178
  reject(new Error(reason));
165
179
  else
166
180
  resolve();
167
- });
181
+ };
182
+ this.emitter.once(EVENT_INTERNAL_PRE_LOADED + this.EmitterID, onPreloaded);
168
183
  });
169
184
  }
170
185
  handleNewObject(data) {
171
186
  this.isLoading = true;
187
+ if (!this.socket || typeof this.socket.emit !== "function") {
188
+ this.isLoading = false;
189
+ this.emitter.emit(EVENT_INTERNAL_PRE_LOADED + this.EmitterID, true, "Socket not available");
190
+ return;
191
+ }
172
192
  this.socket.emit(EVENT_NEW + this.className, data, (res) => {
173
193
  if (!res.success) {
174
194
  this.isLoading = false;
175
- this.loggers.error("Could not create data on server: " + res.message);
176
195
  this.emitter.emit(EVENT_INTERNAL_PRE_LOADED + this.EmitterID, true, res.message);
177
196
  return;
178
197
  }
@@ -186,13 +205,13 @@ export class AutoUpdatedClientObject {
186
205
  }
187
206
  get extractedData() {
188
207
  const extracted = processIsRefProperties(this.data, this, null, [], {}, this.loggers).newData;
189
- return _.cloneDeep(extracted);
208
+ return extracted;
190
209
  }
191
210
  get isLoaded() {
192
211
  return !this.isLoading;
193
212
  }
194
213
  async isPreLoadedAsync() {
195
- await this.loadShit();
214
+ await this.waitForPreloaded();
196
215
  return true;
197
216
  }
198
217
  async loadMissingReferences() {
@@ -200,25 +219,34 @@ export class AutoUpdatedClientObject {
200
219
  this.generateSettersAndGetters();
201
220
  }
202
221
  openSockets() {
203
- const id = this.data?._id ?? this._id;
222
+ const dataRec = this.data;
223
+ const id = dataRec["_id"];
224
+ if (!id || !this.socket || typeof this.socket.on !== "function")
225
+ return;
204
226
  const event = EVENT_UPDATE + this.className + id.toString();
205
227
  this.socket.on(event, async (update, ack) => {
206
228
  const res = await this.handleUpdateRequest(update);
207
- if (ack && typeof ack === "function")
229
+ if (ack)
208
230
  ack(res);
209
231
  return res;
210
232
  });
211
233
  }
212
234
  async handleUpdateRequest(update) {
213
235
  try {
214
- await this.setValue(update.key, update.value, { silent: true });
236
+ await this.setValue(update.key, update.value, {
237
+ silent: true,
238
+ });
215
239
  if (this.isLoaded)
216
240
  this.callbacks.update(this, update.key);
217
241
  return { success: true, data: undefined, message: "" };
218
242
  }
219
243
  catch (error) {
220
- this.loggers.error(`[${this.data._id}] Error applying patch: ${error.message}`);
221
- return { success: false, message: "Error applying update: " + error.message };
244
+ const dataRec = this.data;
245
+ this.loggers.error?.(`[${dataRec["_id"]?.toString()}] Error applying patch: ${error.message}`);
246
+ return {
247
+ success: false,
248
+ message: "Error applying update: " + error.message,
249
+ };
222
250
  }
223
251
  }
224
252
  generateSettersAndGetters() {
@@ -230,10 +258,13 @@ export class AutoUpdatedClientObject {
230
258
  const isRef = getMetadataRecursive("isRef", this, key);
231
259
  Object.defineProperty(this, key, {
232
260
  get: () => {
233
- let val = this.data[key];
261
+ const dataRec = this.data;
262
+ let val = dataRec ? dataRec[key] : undefined;
234
263
  if (isRef && val) {
235
264
  if (Array.isArray(val)) {
236
- return val.map((id) => this.findReference(id, key)).filter(Boolean);
265
+ return val
266
+ .map((id) => this.findReference(id, key))
267
+ .filter(Boolean);
237
268
  }
238
269
  else {
239
270
  return this.findReference(val, key);
@@ -242,7 +273,8 @@ export class AutoUpdatedClientObject {
242
273
  return val;
243
274
  },
244
275
  set: (v) => {
245
- throw new Error("Cannot set value of a reference pointer directly.");
276
+ if (this.data)
277
+ this.data[key] = v;
246
278
  },
247
279
  enumerable: true,
248
280
  configurable: true,
@@ -251,26 +283,31 @@ export class AutoUpdatedClientObject {
251
283
  }
252
284
  getValue(key_) {
253
285
  const key = key_;
254
- // Shallow only now
255
- return (this)[key] === undefined ? this.data[key] : this[key];
286
+ const dataRec = this.data;
287
+ return this[key] === undefined
288
+ ? dataRec
289
+ ? dataRec[key_]
290
+ : undefined
291
+ : this[key_];
256
292
  }
257
293
  findReference(id, key) {
258
- if (!id)
294
+ if (!id || !this.parentManager)
259
295
  return undefined;
260
296
  const idStr = id.toString();
261
- if (this.parentManager.cache.references[key])
262
- return this.parentManager.cache.references[key].getObject(idStr);
297
+ const cacheRec = this.parentManager.cache.references;
298
+ if (cacheRec[key])
299
+ return cacheRec[key].getObject(idStr);
263
300
  for (const manager of Object.values(this.parentManager.managers)) {
264
301
  const result = manager.getObject(idStr);
265
302
  if (result) {
266
- this.parentManager.cache.references[key] = manager;
303
+ cacheRec[key] = manager;
267
304
  return result;
268
305
  }
269
306
  }
270
307
  return undefined;
271
308
  }
272
309
  async setValue(key, val, options = {}) {
273
- const { silent = false, noUpdate = false, isParentUpdate = false } = options;
310
+ const { silent = false, noUpdate = false, isParentUpdate = false, } = options;
274
311
  try {
275
312
  const isRef = getMetadataRecursive("isRef", this, key);
276
313
  const pointer = getMetadataRecursive("refsTo", this, key);
@@ -281,24 +318,29 @@ export class AutoUpdatedClientObject {
281
318
  if (isRef) {
282
319
  valueToStore = Array.isArray(val)
283
320
  ? val.map((v) => v._id?.toString() ?? v.toString())
284
- : (val?._id ?? val?.toString() ?? val);
321
+ : (val?._id?.toString() ?? val?.toString() ?? val);
285
322
  }
286
- const currentVal = this.data[key];
323
+ const dataRec = this.data;
324
+ const currentVal = dataRec ? dataRec[key] : undefined;
287
325
  if (_.isEqual(currentVal, valueToStore))
288
326
  return { success: true, msg: "Successfully set " + key + " to " + val };
289
327
  const res = await this.setValueInternal(key, valueToStore, silent, noUpdate);
290
328
  if (res.success) {
291
- this.data[key] = valueToStore;
329
+ if (this.data)
330
+ this.data[key] = valueToStore;
292
331
  await this.findAndLoadReferences(key, valueToStore);
293
- if (isRef && this.parentManager.isLoaded)
332
+ if (isRef && this.parentManager && this.parentManager.isLoaded)
294
333
  await this.contactChildren();
295
334
  if (this.isLoaded)
296
335
  this.callbacks.update(this, key);
297
336
  }
298
- return { ...res, msg: res.msg ?? "Successfully set " + key + " to " + val };
337
+ return {
338
+ ...res,
339
+ msg: res.msg ?? "Successfully set " + key + " to " + val,
340
+ };
299
341
  }
300
342
  catch (error) {
301
- this.loggers.error(`Error setting value ${key}: ${error.message}`);
343
+ this.loggers.error?.(`Error setting value ${key}: ${error.message}`);
302
344
  return { success: false, msg: error.message };
303
345
  }
304
346
  }
@@ -306,21 +348,37 @@ export class AutoUpdatedClientObject {
306
348
  if (silent || noUpdate)
307
349
  return { success: true, msg: "Silent or no update" };
308
350
  return new Promise((resolve) => {
309
- const id = this.data?._id ?? this._id;
310
- this.socket.emit(EVENT_UPDATE + this.className + id, { _id: id.toString(), key, value }, (res) => {
311
- resolve({ success: res.success, msg: res.message ?? (res.success ? "Success" : "Error") });
351
+ const dataRec = this.data;
352
+ const id = dataRec ? dataRec["_id"] : undefined;
353
+ if (!id)
354
+ return resolve({ success: false, msg: "Missing _id" });
355
+ if (!this.socket || typeof this.socket.emit !== "function") {
356
+ return resolve({ success: false, msg: "Socket not available" });
357
+ }
358
+ const timeout = setTimeout(() => {
359
+ resolve({ success: false, msg: "Timeout waiting for server response" });
360
+ }, 5000);
361
+ this.socket.emit(EVENT_UPDATE + this.className + id.toString(), { _id: id.toString(), key, value }, (res) => {
362
+ clearTimeout(timeout);
363
+ resolve({
364
+ success: res.success,
365
+ msg: res.message ?? (res.success ? "Success" : "Error"),
366
+ });
312
367
  });
313
368
  });
314
369
  }
315
370
  makeUpdate(key, value) {
316
- const id = this.data?._id ?? this._id;
371
+ const dataRec = this.data;
372
+ const id = dataRec ? dataRec["_id"] : undefined;
317
373
  if (!id) {
318
- this.loggers.error(`Probably missing the identifier ['_id'] again: ${key} = ${value}`);
374
+ this.loggers.error?.(`Probably missing the identifier ['_id'] again: ${key} = ${value}`);
319
375
  throw new Error(`Cannot make update for ${this.className} because _id is missing.`);
320
376
  }
321
- return { _id: id.toString(), key, value };
377
+ return { _id: id, key, value };
322
378
  }
323
379
  async resolveReference(id) {
380
+ if (!this.parentManager)
381
+ return null;
324
382
  for (const manager of Object.values(this.parentManager.managers)) {
325
383
  const obj = manager.getObject(id);
326
384
  if (obj)
@@ -330,7 +388,7 @@ export class AutoUpdatedClientObject {
330
388
  }
331
389
  async findAndLoadReferences(lastPath, value) {
332
390
  const isRef = getMetadataRecursive("isRef", this, lastPath);
333
- if (isRef) {
391
+ if (isRef && this.parentManager) {
334
392
  for (const id of Array.isArray(value) ? value : [value]) {
335
393
  if (!id)
336
394
  continue;
@@ -347,47 +405,60 @@ export class AutoUpdatedClientObject {
347
405
  }
348
406
  }
349
407
  async wipeSelf() {
350
- if (this.data.Wiped)
408
+ const dataRec = this.data;
409
+ if (!dataRec || dataRec["Wiped"])
351
410
  return;
352
- const id = this.data?._id ?? this._id;
411
+ const id = dataRec["_id"];
353
412
  const _id = id ? id.toString() : "unknown";
354
- for (const key of Object.keys(this.data)) {
355
- delete this.data[key];
413
+ for (const key of Object.keys(dataRec)) {
414
+ delete dataRec[key];
356
415
  }
357
- this.data = { Wiped: true };
358
- this.loggers.info(`[${_id}] ${this.className} object wiped`);
416
+ dataRec["Wiped"] = true;
417
+ this.loggers.info?.(`[${_id}] ${this.className} object wiped`);
359
418
  }
360
419
  async loadForceReferences(obj = this.data, proto = this, alreadySeen = []) {
420
+ if (!obj)
421
+ return;
361
422
  if (obj === this.data) {
362
- const myId = this.data?._id?.toString();
423
+ const dataRec = this.data;
424
+ const myId = dataRec["_id"]?.toString();
363
425
  if (myId && !alreadySeen.includes(myId))
364
426
  alreadySeen.push(myId);
365
427
  }
366
428
  const props = Reflect.getMetadata("props", proto) || [];
367
429
  for (const key of props) {
368
- if (typeof key !== "string")
369
- continue;
370
430
  const isRef = Reflect.getMetadata("isRef", proto, key);
371
431
  const pointer = Reflect.getMetadata("refsTo", proto, key);
372
- if (pointer && obj === this.data && obj[key] && !alreadySeen.includes(obj)) {
373
- await this.createdWithParent(pointer.split(":"), obj[key]);
432
+ const objRec = obj;
433
+ if (pointer &&
434
+ obj === this.data &&
435
+ objRec[key] &&
436
+ !alreadySeen.includes(JSON.stringify(obj))) {
437
+ await this.createdWithParent(pointer.split(":"), objRec[key]);
374
438
  }
375
- if (obj[key] && !alreadySeen.includes(obj[key]))
376
- alreadySeen.push(obj[key]);
439
+ if (objRec[key] && !alreadySeen.includes(objRec[key]))
440
+ alreadySeen.push(objRec[key]);
377
441
  if (isRef)
378
442
  await this.handleLoad(obj, key, alreadySeen);
379
- const val = obj[key];
443
+ const val = objRec[key];
380
444
  if (val && typeof val === "object") {
381
445
  const nestedProto = Object.getPrototypeOf(val);
382
- if (nestedProto && !alreadySeen.includes(val)) {
383
- alreadySeen.push(val);
446
+ if (nestedProto &&
447
+ nestedProto !== Object.prototype &&
448
+ !alreadySeen.includes(JSON.stringify(val))) {
449
+ alreadySeen.push(JSON.stringify(val));
384
450
  await this.loadForceReferences(val, nestedProto, alreadySeen);
385
451
  }
386
452
  }
387
453
  }
388
454
  }
389
455
  async handleLoad(obj, key, alreadySeen) {
390
- const refIds = Array.isArray(obj[key]) ? obj[key] : [obj[key]];
456
+ if (!this.parentManager)
457
+ return;
458
+ const objRec = obj;
459
+ const refIds = Array.isArray(objRec[key])
460
+ ? objRec[key]
461
+ : [objRec[key]];
391
462
  for (const refId of refIds) {
392
463
  if (refId) {
393
464
  const idStr = refId.toString();
@@ -410,33 +481,60 @@ export class AutoUpdatedClientObject {
410
481
  if (noUpdate)
411
482
  return;
412
483
  await this.callbacks?.onUpdate?.(this, (key, val) => {
413
- return this.setValue(key, val, { silent: false, noGet: true, noUpdate: true });
484
+ return this.setValue(key, val, {
485
+ silent: false,
486
+ noGet: true,
487
+ noUpdate: true,
488
+ });
414
489
  });
415
490
  }
416
491
  async createdWithParent(pointer, parent) {
417
- if (pointer.length !== 2)
492
+ if (pointer.length !== 2 || !this.parentManager)
418
493
  return;
419
494
  const parentId = parent._id?.toString() ?? parent.toString();
420
- const obj = this.parentManager.managers[pointer[0]]?.getObject(parentId);
495
+ const ac = this.parentManager.managers[pointer[0]];
496
+ if (!ac)
497
+ return;
498
+ const obj = ac.getObject(parentId);
421
499
  if (!obj)
422
500
  return;
423
501
  const val = obj.getValue(pointer[1]);
424
- const myId = this.data._id.toString();
502
+ const dataRec = this.data;
503
+ const myId = dataRec["_id"]?.toString();
504
+ if (!myId)
505
+ return;
425
506
  if (Array.isArray(val)) {
426
- const ids = val.map(v => v._id?.toString() ?? v.toString());
507
+ const ids = val.map((v) => v._id?.toString() ?? v.toString());
427
508
  if (!ids.includes(myId)) {
428
509
  await obj.setValue(pointer[1], [...val, myId], { silent: true, isParentUpdate: true });
429
510
  }
430
511
  }
431
512
  else if ((val?._id?.toString() ?? val?.toString()) !== myId) {
432
- await obj.setValue(pointer[1], myId, { silent: true, isParentUpdate: true });
513
+ await obj.setValue(pointer[1], myId, {
514
+ silent: true,
515
+ isParentUpdate: true,
516
+ });
433
517
  }
434
518
  }
435
519
  async destroy(once = false) {
436
- if (!once)
437
- return await this.parentManager.deleteObject(this.data._id.toString());
520
+ const dataRec = this.data;
521
+ const id = dataRec ? dataRec["_id"] : undefined;
522
+ if (!id)
523
+ return { success: false, message: "Missing _id" };
524
+ if (!once && this.parentManager)
525
+ return await this.parentManager.deleteObject(id);
438
526
  return new Promise((resolve) => {
439
- this.socket.emit(EVENT_DELETE + this.className, this.data._id, (res) => {
527
+ if (!this.socket || typeof this.socket.emit !== "function") {
528
+ return resolve({ success: false, message: "Socket not available" });
529
+ }
530
+ const timeout = setTimeout(() => {
531
+ resolve({
532
+ success: false,
533
+ message: "Timeout waiting for server deletion",
534
+ });
535
+ }, 5000);
536
+ this.socket.emit(EVENT_DELETE + this.className, id.toString(), (res) => {
537
+ clearTimeout(timeout);
440
538
  resolve({ success: res.success, message: res.message ?? "" });
441
539
  });
442
540
  });
@@ -444,7 +542,7 @@ export class AutoUpdatedClientObject {
444
542
  async checkForMissingRefs() {
445
543
  for (const prop of this.properties) {
446
544
  const pointer = getMetadataRecursive("refsTo", this, prop.toString());
447
- if (pointer) {
545
+ if (typeof pointer === "string") {
448
546
  const parts = pointer.split(":");
449
547
  if (parts.length === 2)
450
548
  await this.findMissingObjectReference(prop, parts);
@@ -452,13 +550,18 @@ export class AutoUpdatedClientObject {
452
550
  }
453
551
  }
454
552
  async findMissingObjectReference(prop, pointer) {
455
- if (this.checkedMissingRefs)
553
+ if (this.checkedMissingRefs || !this.parentManager)
456
554
  return;
457
555
  this.checkedMissingRefs = true;
458
556
  const ac = this.parentManager.managers[pointer[0]];
459
557
  if (!ac)
460
558
  return;
461
- const targetId = this.data._id.toString();
559
+ const dataRec = this.data;
560
+ const targetId = dataRec
561
+ ? dataRec["_id"]?.toString()
562
+ : undefined;
563
+ if (!targetId)
564
+ return;
462
565
  const allObjects = Object.values(ac.objects);
463
566
  for (const obj of allObjects) {
464
567
  if (!obj.isLoaded)
@@ -466,9 +569,11 @@ export class AutoUpdatedClientObject {
466
569
  const val = obj.getValue(pointer[1]);
467
570
  if (!val)
468
571
  continue;
469
- const ids = Array.isArray(val) ? val.map(v => v._id?.toString() ?? v.toString()) : [val._id?.toString() ?? val.toString()];
572
+ const ids = Array.isArray(val)
573
+ ? val.map((v) => v._id?.toString() ?? v.toString())
574
+ : [val._id?.toString() ?? val.toString()];
470
575
  if (ids.includes(targetId)) {
471
- this.data[prop] = obj._id;
576
+ dataRec[prop] = obj._id;
472
577
  return;
473
578
  }
474
579
  }
@@ -484,7 +589,8 @@ export class AutoUpdatedClientObject {
484
589
  continue;
485
590
  const children = Array.isArray(obj) ? obj : [obj];
486
591
  for (const child of children) {
487
- if (child && typeof child.loadMissingReferences === "function") {
592
+ if (child &&
593
+ typeof child.loadMissingReferences === "function") {
488
594
  await child.loadMissingReferences();
489
595
  }
490
596
  }
@@ -497,6 +603,8 @@ export function processIsRefProperties(instance, target, prefix, allProps, newDa
497
603
  for (const prop of props) {
498
604
  const path = prefix ? `${prefix}.${prop}` : prop;
499
605
  allProps.push(path);
606
+ if (!instance)
607
+ continue;
500
608
  newData[prop] = ObjectId.isValid(instance[prop])
501
609
  ? instance[prop]?.toString()
502
610
  : instance[prop];
@@ -511,18 +619,16 @@ export function processIsRefProperties(instance, target, prefix, allProps, newDa
511
619
  }
512
620
  const type = Reflect.getMetadata("design:type", target, prop);
513
621
  if (type?.prototype) {
514
- const nestedProps = Reflect.getMetadata("props", type.prototype);
515
- if (nestedProps && instance[prop]) {
516
- newData[prop] = processIsRefProperties(instance[prop], type.prototype, path, allProps, {}, loggers).newData;
517
- }
622
+ // DO NOT RECURSE into nested prototypes to avoid circularity issues
518
623
  }
519
624
  }
520
625
  return { allProps, newData };
521
626
  }
522
- export function getMetadataRecursive(metaKey, proto, prop) {
627
+ export function getMetadataRecursive(metaKey, target, prop) {
628
+ let proto = target;
523
629
  while (proto) {
524
630
  const meta = Reflect.getMetadata(metaKey, proto, prop);
525
- if (meta)
631
+ if (meta !== undefined)
526
632
  return meta;
527
633
  proto = Object.getPrototypeOf(proto);
528
634
  }