@prestizni-software/server-dem 0.5.7 → 0.5.8

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