@prestizni-software/client-dem 0.4.113 → 0.4.116

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,44 +127,49 @@ 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) {
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
170
  await 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
+ reject(new Error(`Timeout waiting for preloaded: ${this.className}`));
160
173
  }, 10000);
161
174
  this.emitter.once(EVENT_INTERNAL_PRE_LOADED + this.EmitterID, (failed, reason) => {
162
175
  clearTimeout(timer);
@@ -169,10 +182,14 @@ export class AutoUpdatedClientObject {
169
182
  }
170
183
  handleNewObject(data) {
171
184
  this.isLoading = true;
185
+ if (!this.socket || typeof this.socket.emit !== "function") {
186
+ this.isLoading = false;
187
+ this.emitter.emit(EVENT_INTERNAL_PRE_LOADED + this.EmitterID, true, "Socket not available");
188
+ return;
189
+ }
172
190
  this.socket.emit(EVENT_NEW + this.className, data, (res) => {
173
191
  if (!res.success) {
174
192
  this.isLoading = false;
175
- this.loggers.error("Could not create data on server: " + res.message);
176
193
  this.emitter.emit(EVENT_INTERNAL_PRE_LOADED + this.EmitterID, true, res.message);
177
194
  return;
178
195
  }
@@ -186,13 +203,13 @@ export class AutoUpdatedClientObject {
186
203
  }
187
204
  get extractedData() {
188
205
  const extracted = processIsRefProperties(this.data, this, null, [], {}, this.loggers).newData;
189
- return _.cloneDeep(extracted);
206
+ return extracted;
190
207
  }
191
208
  get isLoaded() {
192
209
  return !this.isLoading;
193
210
  }
194
211
  async isPreLoadedAsync() {
195
- await this.loadShit();
212
+ await this.waitForPreloaded();
196
213
  return true;
197
214
  }
198
215
  async loadMissingReferences() {
@@ -200,25 +217,34 @@ export class AutoUpdatedClientObject {
200
217
  this.generateSettersAndGetters();
201
218
  }
202
219
  openSockets() {
203
- const id = this.data?._id ?? this._id;
220
+ const dataRec = this.data;
221
+ const id = dataRec["_id"];
222
+ if (!id || !this.socket || typeof this.socket.on !== "function")
223
+ return;
204
224
  const event = EVENT_UPDATE + this.className + id.toString();
205
225
  this.socket.on(event, async (update, ack) => {
206
226
  const res = await this.handleUpdateRequest(update);
207
- if (ack && typeof ack === "function")
227
+ if (ack)
208
228
  ack(res);
209
229
  return res;
210
230
  });
211
231
  }
212
232
  async handleUpdateRequest(update) {
213
233
  try {
214
- await this.setValue(update.key, update.value, { silent: true });
234
+ await this.setValue(update.key, update.value, {
235
+ silent: true,
236
+ });
215
237
  if (this.isLoaded)
216
238
  this.callbacks.update(this, update.key);
217
239
  return { success: true, data: undefined, message: "" };
218
240
  }
219
241
  catch (error) {
220
- this.loggers.error(`[${this.data._id}] Error applying patch: ${error.message}`);
221
- return { success: false, message: "Error applying update: " + error.message };
242
+ const dataRec = this.data;
243
+ this.loggers.error?.(`[${dataRec["_id"]?.toString()}] Error applying patch: ${error.message}`);
244
+ return {
245
+ success: false,
246
+ message: "Error applying update: " + error.message,
247
+ };
222
248
  }
223
249
  }
224
250
  generateSettersAndGetters() {
@@ -230,10 +256,13 @@ export class AutoUpdatedClientObject {
230
256
  const isRef = getMetadataRecursive("isRef", this, key);
231
257
  Object.defineProperty(this, key, {
232
258
  get: () => {
233
- let val = this.data[key];
259
+ const dataRec = this.data;
260
+ let val = dataRec ? dataRec[key] : undefined;
234
261
  if (isRef && val) {
235
262
  if (Array.isArray(val)) {
236
- return val.map((id) => this.findReference(id, key)).filter(Boolean);
263
+ return val
264
+ .map((id) => this.findReference(id, key))
265
+ .filter(Boolean);
237
266
  }
238
267
  else {
239
268
  return this.findReference(val, key);
@@ -242,7 +271,8 @@ export class AutoUpdatedClientObject {
242
271
  return val;
243
272
  },
244
273
  set: (v) => {
245
- throw new Error("Cannot set value of a reference pointer directly.");
274
+ if (this.data)
275
+ this.data[key] = v;
246
276
  },
247
277
  enumerable: true,
248
278
  configurable: true,
@@ -251,26 +281,31 @@ export class AutoUpdatedClientObject {
251
281
  }
252
282
  getValue(key_) {
253
283
  const key = key_;
254
- // Shallow only now
255
- return (this)[key] === undefined ? this.data[key] : this[key];
284
+ const dataRec = this.data;
285
+ return this[key] === undefined
286
+ ? dataRec
287
+ ? dataRec[key_]
288
+ : undefined
289
+ : this[key_];
256
290
  }
257
291
  findReference(id, key) {
258
- if (!id)
292
+ if (!id || !this.parentManager)
259
293
  return undefined;
260
294
  const idStr = id.toString();
261
- if (this.parentManager.cache.references[key])
262
- return this.parentManager.cache.references[key].getObject(idStr);
295
+ const cacheRec = this.parentManager.cache.references;
296
+ if (cacheRec[key])
297
+ return cacheRec[key].getObject(idStr);
263
298
  for (const manager of Object.values(this.parentManager.managers)) {
264
299
  const result = manager.getObject(idStr);
265
300
  if (result) {
266
- this.parentManager.cache.references[key] = manager;
301
+ cacheRec[key] = manager;
267
302
  return result;
268
303
  }
269
304
  }
270
305
  return undefined;
271
306
  }
272
307
  async setValue(key, val, options = {}) {
273
- const { silent = false, noUpdate = false, isParentUpdate = false } = options;
308
+ const { silent = false, noUpdate = false, isParentUpdate = false, } = options;
274
309
  try {
275
310
  const isRef = getMetadataRecursive("isRef", this, key);
276
311
  const pointer = getMetadataRecursive("refsTo", this, key);
@@ -281,24 +316,29 @@ export class AutoUpdatedClientObject {
281
316
  if (isRef) {
282
317
  valueToStore = Array.isArray(val)
283
318
  ? val.map((v) => v._id?.toString() ?? v.toString())
284
- : (val?._id ?? val?.toString() ?? val);
319
+ : (val?._id?.toString() ?? val?.toString() ?? val);
285
320
  }
286
- const currentVal = this.data[key];
321
+ const dataRec = this.data;
322
+ const currentVal = dataRec ? dataRec[key] : undefined;
287
323
  if (_.isEqual(currentVal, valueToStore))
288
324
  return { success: true, msg: "Successfully set " + key + " to " + val };
289
325
  const res = await this.setValueInternal(key, valueToStore, silent, noUpdate);
290
326
  if (res.success) {
291
- this.data[key] = valueToStore;
327
+ if (this.data)
328
+ this.data[key] = valueToStore;
292
329
  await this.findAndLoadReferences(key, valueToStore);
293
- if (isRef && this.parentManager.isLoaded)
330
+ if (isRef && this.parentManager && this.parentManager.isLoaded)
294
331
  await this.contactChildren();
295
332
  if (this.isLoaded)
296
333
  this.callbacks.update(this, key);
297
334
  }
298
- return { ...res, msg: res.msg ?? "Successfully set " + key + " to " + val };
335
+ return {
336
+ ...res,
337
+ msg: res.msg ?? "Successfully set " + key + " to " + val,
338
+ };
299
339
  }
300
340
  catch (error) {
301
- this.loggers.error(`Error setting value ${key}: ${error.message}`);
341
+ this.loggers.error?.(`Error setting value ${key}: ${error.message}`);
302
342
  return { success: false, msg: error.message };
303
343
  }
304
344
  }
@@ -306,21 +346,37 @@ export class AutoUpdatedClientObject {
306
346
  if (silent || noUpdate)
307
347
  return { success: true, msg: "Silent or no update" };
308
348
  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") });
349
+ const dataRec = this.data;
350
+ const id = dataRec ? dataRec["_id"] : undefined;
351
+ if (!id)
352
+ return resolve({ success: false, msg: "Missing _id" });
353
+ if (!this.socket || typeof this.socket.emit !== "function") {
354
+ return resolve({ success: false, msg: "Socket not available" });
355
+ }
356
+ const timeout = setTimeout(() => {
357
+ resolve({ success: false, msg: "Timeout waiting for server response" });
358
+ }, 5000);
359
+ this.socket.emit(EVENT_UPDATE + this.className + id.toString(), { _id: id.toString(), key, value }, (res) => {
360
+ clearTimeout(timeout);
361
+ resolve({
362
+ success: res.success,
363
+ msg: res.message ?? (res.success ? "Success" : "Error"),
364
+ });
312
365
  });
313
366
  });
314
367
  }
315
368
  makeUpdate(key, value) {
316
- const id = this.data?._id ?? this._id;
369
+ const dataRec = this.data;
370
+ const id = dataRec ? dataRec["_id"] : undefined;
317
371
  if (!id) {
318
- this.loggers.error(`Probably missing the identifier ['_id'] again: ${key} = ${value}`);
372
+ this.loggers.error?.(`Probably missing the identifier ['_id'] again: ${key} = ${value}`);
319
373
  throw new Error(`Cannot make update for ${this.className} because _id is missing.`);
320
374
  }
321
- return { _id: id.toString(), key, value };
375
+ return { _id: id, key, value };
322
376
  }
323
377
  async resolveReference(id) {
378
+ if (!this.parentManager)
379
+ return null;
324
380
  for (const manager of Object.values(this.parentManager.managers)) {
325
381
  const obj = manager.getObject(id);
326
382
  if (obj)
@@ -330,7 +386,7 @@ export class AutoUpdatedClientObject {
330
386
  }
331
387
  async findAndLoadReferences(lastPath, value) {
332
388
  const isRef = getMetadataRecursive("isRef", this, lastPath);
333
- if (isRef) {
389
+ if (isRef && this.parentManager) {
334
390
  for (const id of Array.isArray(value) ? value : [value]) {
335
391
  if (!id)
336
392
  continue;
@@ -347,47 +403,60 @@ export class AutoUpdatedClientObject {
347
403
  }
348
404
  }
349
405
  async wipeSelf() {
350
- if (this.data.Wiped)
406
+ const dataRec = this.data;
407
+ if (!dataRec || dataRec["Wiped"])
351
408
  return;
352
- const id = this.data?._id ?? this._id;
409
+ const id = dataRec["_id"];
353
410
  const _id = id ? id.toString() : "unknown";
354
- for (const key of Object.keys(this.data)) {
355
- delete this.data[key];
411
+ for (const key of Object.keys(dataRec)) {
412
+ delete dataRec[key];
356
413
  }
357
- this.data = { Wiped: true };
358
- this.loggers.info(`[${_id}] ${this.className} object wiped`);
414
+ dataRec["Wiped"] = true;
415
+ this.loggers.info?.(`[${_id}] ${this.className} object wiped`);
359
416
  }
360
417
  async loadForceReferences(obj = this.data, proto = this, alreadySeen = []) {
418
+ if (!obj)
419
+ return;
361
420
  if (obj === this.data) {
362
- const myId = this.data?._id?.toString();
421
+ const dataRec = this.data;
422
+ const myId = dataRec["_id"]?.toString();
363
423
  if (myId && !alreadySeen.includes(myId))
364
424
  alreadySeen.push(myId);
365
425
  }
366
426
  const props = Reflect.getMetadata("props", proto) || [];
367
427
  for (const key of props) {
368
- if (typeof key !== "string")
369
- continue;
370
428
  const isRef = Reflect.getMetadata("isRef", proto, key);
371
429
  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]);
430
+ const objRec = obj;
431
+ if (pointer &&
432
+ obj === this.data &&
433
+ objRec[key] &&
434
+ !alreadySeen.includes(JSON.stringify(obj))) {
435
+ await this.createdWithParent(pointer.split(":"), objRec[key]);
374
436
  }
375
- if (obj[key] && !alreadySeen.includes(obj[key]))
376
- alreadySeen.push(obj[key]);
437
+ if (objRec[key] && !alreadySeen.includes(objRec[key]))
438
+ alreadySeen.push(objRec[key]);
377
439
  if (isRef)
378
440
  await this.handleLoad(obj, key, alreadySeen);
379
- const val = obj[key];
441
+ const val = objRec[key];
380
442
  if (val && typeof val === "object") {
381
443
  const nestedProto = Object.getPrototypeOf(val);
382
- if (nestedProto && !alreadySeen.includes(val)) {
383
- alreadySeen.push(val);
444
+ if (nestedProto &&
445
+ nestedProto !== Object.prototype &&
446
+ !alreadySeen.includes(JSON.stringify(val))) {
447
+ alreadySeen.push(JSON.stringify(val));
384
448
  await this.loadForceReferences(val, nestedProto, alreadySeen);
385
449
  }
386
450
  }
387
451
  }
388
452
  }
389
453
  async handleLoad(obj, key, alreadySeen) {
390
- const refIds = Array.isArray(obj[key]) ? obj[key] : [obj[key]];
454
+ if (!this.parentManager)
455
+ return;
456
+ const objRec = obj;
457
+ const refIds = Array.isArray(objRec[key])
458
+ ? objRec[key]
459
+ : [objRec[key]];
391
460
  for (const refId of refIds) {
392
461
  if (refId) {
393
462
  const idStr = refId.toString();
@@ -410,33 +479,60 @@ export class AutoUpdatedClientObject {
410
479
  if (noUpdate)
411
480
  return;
412
481
  await this.callbacks?.onUpdate?.(this, (key, val) => {
413
- return this.setValue(key, val, { silent: false, noGet: true, noUpdate: true });
482
+ return this.setValue(key, val, {
483
+ silent: false,
484
+ noGet: true,
485
+ noUpdate: true,
486
+ });
414
487
  });
415
488
  }
416
489
  async createdWithParent(pointer, parent) {
417
- if (pointer.length !== 2)
490
+ if (pointer.length !== 2 || !this.parentManager)
418
491
  return;
419
492
  const parentId = parent._id?.toString() ?? parent.toString();
420
- const obj = this.parentManager.managers[pointer[0]]?.getObject(parentId);
493
+ const ac = this.parentManager.managers[pointer[0]];
494
+ if (!ac)
495
+ return;
496
+ const obj = ac.getObject(parentId);
421
497
  if (!obj)
422
498
  return;
423
499
  const val = obj.getValue(pointer[1]);
424
- const myId = this.data._id.toString();
500
+ const dataRec = this.data;
501
+ const myId = dataRec["_id"]?.toString();
502
+ if (!myId)
503
+ return;
425
504
  if (Array.isArray(val)) {
426
- const ids = val.map(v => v._id?.toString() ?? v.toString());
505
+ const ids = val.map((v) => v._id?.toString() ?? v.toString());
427
506
  if (!ids.includes(myId)) {
428
507
  await obj.setValue(pointer[1], [...val, myId], { silent: true, isParentUpdate: true });
429
508
  }
430
509
  }
431
510
  else if ((val?._id?.toString() ?? val?.toString()) !== myId) {
432
- await obj.setValue(pointer[1], myId, { silent: true, isParentUpdate: true });
511
+ await obj.setValue(pointer[1], myId, {
512
+ silent: true,
513
+ isParentUpdate: true,
514
+ });
433
515
  }
434
516
  }
435
517
  async destroy(once = false) {
436
- if (!once)
437
- return await this.parentManager.deleteObject(this.data._id.toString());
518
+ const dataRec = this.data;
519
+ const id = dataRec ? dataRec["_id"] : undefined;
520
+ if (!id)
521
+ return { success: false, message: "Missing _id" };
522
+ if (!once && this.parentManager)
523
+ return await this.parentManager.deleteObject(id);
438
524
  return new Promise((resolve) => {
439
- this.socket.emit(EVENT_DELETE + this.className, this.data._id, (res) => {
525
+ if (!this.socket || typeof this.socket.emit !== "function") {
526
+ return resolve({ success: false, message: "Socket not available" });
527
+ }
528
+ const timeout = setTimeout(() => {
529
+ resolve({
530
+ success: false,
531
+ message: "Timeout waiting for server deletion",
532
+ });
533
+ }, 5000);
534
+ this.socket.emit(EVENT_DELETE + this.className, id.toString(), (res) => {
535
+ clearTimeout(timeout);
440
536
  resolve({ success: res.success, message: res.message ?? "" });
441
537
  });
442
538
  });
@@ -444,7 +540,7 @@ export class AutoUpdatedClientObject {
444
540
  async checkForMissingRefs() {
445
541
  for (const prop of this.properties) {
446
542
  const pointer = getMetadataRecursive("refsTo", this, prop.toString());
447
- if (pointer) {
543
+ if (typeof pointer === "string") {
448
544
  const parts = pointer.split(":");
449
545
  if (parts.length === 2)
450
546
  await this.findMissingObjectReference(prop, parts);
@@ -452,13 +548,18 @@ export class AutoUpdatedClientObject {
452
548
  }
453
549
  }
454
550
  async findMissingObjectReference(prop, pointer) {
455
- if (this.checkedMissingRefs)
551
+ if (this.checkedMissingRefs || !this.parentManager)
456
552
  return;
457
553
  this.checkedMissingRefs = true;
458
554
  const ac = this.parentManager.managers[pointer[0]];
459
555
  if (!ac)
460
556
  return;
461
- const targetId = this.data._id.toString();
557
+ const dataRec = this.data;
558
+ const targetId = dataRec
559
+ ? dataRec["_id"]?.toString()
560
+ : undefined;
561
+ if (!targetId)
562
+ return;
462
563
  const allObjects = Object.values(ac.objects);
463
564
  for (const obj of allObjects) {
464
565
  if (!obj.isLoaded)
@@ -466,9 +567,11 @@ export class AutoUpdatedClientObject {
466
567
  const val = obj.getValue(pointer[1]);
467
568
  if (!val)
468
569
  continue;
469
- const ids = Array.isArray(val) ? val.map(v => v._id?.toString() ?? v.toString()) : [val._id?.toString() ?? val.toString()];
570
+ const ids = Array.isArray(val)
571
+ ? val.map((v) => v._id?.toString() ?? v.toString())
572
+ : [val._id?.toString() ?? val.toString()];
470
573
  if (ids.includes(targetId)) {
471
- this.data[prop] = obj._id;
574
+ dataRec[prop] = obj._id;
472
575
  return;
473
576
  }
474
577
  }
@@ -484,7 +587,8 @@ export class AutoUpdatedClientObject {
484
587
  continue;
485
588
  const children = Array.isArray(obj) ? obj : [obj];
486
589
  for (const child of children) {
487
- if (child && typeof child.loadMissingReferences === "function") {
590
+ if (child &&
591
+ typeof child.loadMissingReferences === "function") {
488
592
  await child.loadMissingReferences();
489
593
  }
490
594
  }
@@ -497,6 +601,8 @@ export function processIsRefProperties(instance, target, prefix, allProps, newDa
497
601
  for (const prop of props) {
498
602
  const path = prefix ? `${prefix}.${prop}` : prop;
499
603
  allProps.push(path);
604
+ if (!instance)
605
+ continue;
500
606
  newData[prop] = ObjectId.isValid(instance[prop])
501
607
  ? instance[prop]?.toString()
502
608
  : instance[prop];
@@ -511,18 +617,16 @@ export function processIsRefProperties(instance, target, prefix, allProps, newDa
511
617
  }
512
618
  const type = Reflect.getMetadata("design:type", target, prop);
513
619
  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
- }
620
+ // DO NOT RECURSE into nested prototypes to avoid circularity issues
518
621
  }
519
622
  }
520
623
  return { allProps, newData };
521
624
  }
522
- export function getMetadataRecursive(metaKey, proto, prop) {
625
+ export function getMetadataRecursive(metaKey, target, prop) {
626
+ let proto = target;
523
627
  while (proto) {
524
628
  const meta = Reflect.getMetadata(metaKey, proto, prop);
525
- if (meta)
629
+ if (meta !== undefined)
526
630
  return meta;
527
631
  proto = Object.getPrototypeOf(proto);
528
632
  }