@prestizni-software/client-dem 0.5.7 → 0.5.9

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