@prestizni-software/client-dem 0.5.8 → 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,45 +185,38 @@ 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() {
224
- this.loggers.debug?.(`loadMissingReferences for ${this.className}:${this._id}`);
225
198
  await this.checkForMissingRefs();
226
199
  this.generateSettersAndGetters();
227
200
  }
228
201
  openSockets() {
229
- const dataRec = this.data;
230
- const id = dataRec["_id"];
231
- if (!id || !this.socket || typeof this.socket.on !== "function")
232
- return;
202
+ const id = this.data?._id ?? this._id;
233
203
  const event = EVENT_UPDATE + this.className + id.toString();
234
204
  this.socket.on(event, async (update, ack) => {
235
205
  const res = await this.handleUpdateRequest(update);
236
- if (ack)
206
+ if (ack && typeof ack === "function")
237
207
  ack(res);
238
208
  return res;
239
209
  });
240
210
  }
241
211
  async handleUpdateRequest(update) {
242
212
  try {
243
- await this.setValue(update.key, update.value, {
244
- silent: true,
245
- });
213
+ await this.setValue__(update.key, update.value, true);
246
214
  if (this.isLoaded)
247
215
  this.callbacks.update(this, update.key);
248
216
  return { success: true, data: undefined, message: "" };
249
217
  }
250
218
  catch (error) {
251
- const dataRec = this.data;
252
- this.loggers.error?.(`[${dataRec["_id"]?.toString()}] Error applying patch: ${error.message}`);
219
+ this.loggers.error?.(`[${this.data._id}] Error applying patch: ${error.message}`);
253
220
  return {
254
221
  success: false,
255
222
  message: "Error applying update: " + error.message,
@@ -263,10 +230,14 @@ export class AutoUpdatedClientObject {
263
230
  if (typeof key !== "string")
264
231
  continue;
265
232
  const isRef = getMetadataRecursive("isRef", this, key);
233
+ delete this[key];
266
234
  Object.defineProperty(this, key, {
267
235
  get: () => {
268
- const dataRec = this.data;
269
- 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;
270
241
  if (isRef && val) {
271
242
  if (Array.isArray(val)) {
272
243
  return val
@@ -290,36 +261,43 @@ export class AutoUpdatedClientObject {
290
261
  }
291
262
  getValue(key_) {
292
263
  const key = key_;
293
- const dataRec = this.data;
294
- return this[key] === undefined
295
- ? dataRec
296
- ? dataRec[key_]
297
- : undefined
298
- : 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;
299
281
  }
300
282
  findReference(id, key) {
301
- if (!id || !this.parentManager)
283
+ if (!id)
302
284
  return undefined;
303
285
  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
- }
286
+ if (this.parentManager.cache.references[key])
287
+ return this.parentManager.cache.references[key].getObject(idStr);
310
288
  for (const manager of Object.values(this.parentManager.managers)) {
311
289
  const result = manager.getObject(idStr);
312
290
  if (result) {
313
- this.loggers.debug?.(`findReference: Resolved ${idStr} for ${key} in manager ${manager.className}`);
314
- cacheRec[key] = manager;
291
+ this.parentManager.cache.references[key] = manager;
315
292
  return result;
316
293
  }
317
294
  }
318
- this.loggers.warn?.(`findReference: Could NOT resolve ${idStr} for property ${key} in any manager.`);
319
295
  return undefined;
320
296
  }
321
- async setValue(key, val, options = {}) {
322
- 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) {
323
301
  try {
324
302
  const isRef = getMetadataRecursive("isRef", this, key);
325
303
  const pointer = getMetadataRecursive("refsTo", this, key);
@@ -330,18 +308,39 @@ export class AutoUpdatedClientObject {
330
308
  if (isRef) {
331
309
  valueToStore = Array.isArray(val)
332
310
  ? val.map((v) => v._id?.toString() ?? v.toString())
333
- : (val?._id?.toString() ?? val?.toString() ?? val);
311
+ : val?._id ?? val?.toString() ?? val;
334
312
  }
335
- const dataRec = this.data;
336
- const currentVal = dataRec ? dataRec[key] : undefined;
337
- 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))
338
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
+ }
339
334
  const res = await this.setValueInternal(key, valueToStore, silent, noUpdate);
340
335
  if (res.success) {
341
- if (this.data)
342
- 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;
343
342
  await this.findAndLoadReferences(key, valueToStore);
344
- if (isRef && this.parentManager && this.parentManager.isLoaded)
343
+ if (isRef && this.parentManager.isLoaded)
345
344
  await this.contactChildren();
346
345
  if (this.isLoaded)
347
346
  this.callbacks.update(this, key);
@@ -357,17 +356,11 @@ export class AutoUpdatedClientObject {
357
356
  }
358
357
  }
359
358
  async setValueInternal(key, value, silent = false, noUpdate = false) {
360
- if (silent || noUpdate)
361
- return { success: true, msg: "Silent or no update" };
359
+ if (silent)
360
+ return { success: true, msg: "Silent" };
362
361
  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) => {
362
+ const id = this.data?._id ?? this._id;
363
+ this.socket.emit(EVENT_UPDATE + this.className + id, { _id: id.toString(), key, value }, (res) => {
371
364
  resolve({
372
365
  success: res.success,
373
366
  msg: res.message ?? (res.success ? "Success" : "Error"),
@@ -376,17 +369,14 @@ export class AutoUpdatedClientObject {
376
369
  });
377
370
  }
378
371
  makeUpdate(key, value) {
379
- const dataRec = this.data;
380
- const id = dataRec ? dataRec["_id"] : undefined;
372
+ const id = this.data?._id ?? this._id;
381
373
  if (!id) {
382
374
  this.loggers.error?.(`Probably missing the identifier ['_id'] again: ${key} = ${value}`);
383
375
  throw new Error(`Cannot make update for ${this.className} because _id is missing.`);
384
376
  }
385
- return { _id: id, key, value };
377
+ return { _id: id.toString(), key, value };
386
378
  }
387
379
  async resolveReference(id) {
388
- if (!this.parentManager)
389
- return null;
390
380
  for (const manager of Object.values(this.parentManager.managers)) {
391
381
  const obj = manager.getObject(id);
392
382
  if (obj)
@@ -396,7 +386,7 @@ export class AutoUpdatedClientObject {
396
386
  }
397
387
  async findAndLoadReferences(lastPath, value) {
398
388
  const isRef = getMetadataRecursive("isRef", this, lastPath);
399
- if (isRef && this.parentManager) {
389
+ if (isRef) {
400
390
  for (const id of Array.isArray(value) ? value : [value]) {
401
391
  if (!id)
402
392
  continue;
@@ -413,60 +403,48 @@ export class AutoUpdatedClientObject {
413
403
  }
414
404
  }
415
405
  async wipeSelf() {
416
- const dataRec = this.data;
417
- if (!dataRec || dataRec["Wiped"])
406
+ if (this.data.Wiped)
418
407
  return;
419
- const id = dataRec["_id"];
408
+ const id = this.data?._id ?? this._id;
420
409
  const _id = id ? id.toString() : "unknown";
421
- for (const key of Object.keys(dataRec)) {
422
- delete dataRec[key];
410
+ for (const key of Object.keys(this.data)) {
411
+ delete this.data[key];
423
412
  }
424
- dataRec["Wiped"] = true;
413
+ this.data = { Wiped: true };
425
414
  this.loggers.info?.(`[${_id}] ${this.className} object wiped`);
426
415
  }
416
+ destroyImmediate() {
417
+ this.wipeSelf();
418
+ }
427
419
  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
420
  const props = Reflect.getMetadata("props", proto) || [];
437
421
  for (const key of props) {
422
+ if (typeof key !== "string")
423
+ continue;
438
424
  const isRef = Reflect.getMetadata("isRef", proto, key);
439
425
  const pointer = Reflect.getMetadata("refsTo", proto, key);
440
- const objRec = obj;
441
426
  if (pointer &&
442
427
  obj === this.data &&
443
- objRec[key] &&
444
- !alreadySeen.includes(JSON.stringify(obj))) {
445
- await this.createdWithParent(pointer.split(":"), objRec[key]);
428
+ obj[key] &&
429
+ !alreadySeen.includes(obj)) {
430
+ await this.createdWithParent(pointer.split(":"), obj[key]);
446
431
  }
447
- if (objRec[key] && !alreadySeen.includes(objRec[key]))
448
- alreadySeen.push(objRec[key]);
432
+ if (obj[key] && !alreadySeen.includes(obj[key]))
433
+ alreadySeen.push(obj[key]);
449
434
  if (isRef)
450
435
  await this.handleLoad(obj, key, alreadySeen);
451
- const val = objRec[key];
436
+ const val = obj[key];
452
437
  if (val && typeof val === "object") {
453
438
  const nestedProto = Object.getPrototypeOf(val);
454
- if (nestedProto &&
455
- nestedProto !== Object.prototype &&
456
- !alreadySeen.includes(JSON.stringify(val))) {
457
- alreadySeen.push(JSON.stringify(val));
439
+ if (nestedProto && !alreadySeen.includes(val)) {
440
+ alreadySeen.push(val);
458
441
  await this.loadForceReferences(val, nestedProto, alreadySeen);
459
442
  }
460
443
  }
461
444
  }
462
445
  }
463
446
  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]];
447
+ const refIds = Array.isArray(obj[key]) ? obj[key] : [obj[key]];
470
448
  for (const refId of refIds) {
471
449
  if (refId) {
472
450
  const idStr = refId.toString();
@@ -485,82 +463,38 @@ export class AutoUpdatedClientObject {
485
463
  }
486
464
  }
487
465
  }
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
466
  async createdWithParent(pointer, parent) {
505
- if (pointer.length !== 2 || !this.parentManager)
467
+ if (pointer.length !== 2)
506
468
  return;
507
469
  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);
470
+ const obj = this.parentManager.managers[pointer[0]]?.getObject(parentId);
512
471
  if (!obj)
513
472
  return;
514
473
  const val = obj.getValue(pointer[1]);
515
- const dataRec = this.data;
516
- const myId = dataRec["_id"]?.toString();
517
- if (!myId)
518
- return;
474
+ const myId = this.data._id.toString();
519
475
  if (Array.isArray(val)) {
520
476
  const ids = val.map((v) => v._id?.toString() ?? v.toString());
521
477
  if (!ids.includes(myId)) {
522
- await obj.setValue(pointer[1], [...val, myId], { silent: true, isParentUpdate: true });
478
+ await obj.setValue__(pointer[1], [...val, myId], true, false, false, true);
523
479
  }
524
480
  }
525
481
  else if ((val?._id?.toString() ?? val?.toString()) !== myId) {
526
- await obj.setValue(pointer[1], myId, {
527
- silent: true,
528
- isParentUpdate: true,
529
- });
482
+ await obj.setValue__(pointer[1], myId, true, false, false, true);
530
483
  }
531
484
  }
532
485
  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);
486
+ if (!once)
487
+ return await this.parentManager.deleteObject(this.data._id);
543
488
  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) => {
489
+ this.socket.emit(EVENT_DELETE + this.className, this.data._id, (res) => {
548
490
  resolve({ success: res.success, message: res.message ?? "" });
549
491
  });
550
492
  });
551
493
  }
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
494
  async checkForMissingRefs() {
561
495
  for (const prop of this.properties) {
562
496
  const pointer = getMetadataRecursive("refsTo", this, prop.toString());
563
- if (typeof pointer === "string") {
497
+ if (pointer) {
564
498
  const parts = pointer.split(":");
565
499
  if (parts.length === 2)
566
500
  await this.findMissingObjectReference(prop, parts);
@@ -568,22 +502,14 @@ export class AutoUpdatedClientObject {
568
502
  }
569
503
  }
570
504
  async findMissingObjectReference(prop, pointer) {
571
- if (this.checkedMissingRefs || !this.parentManager)
505
+ if (this.checkedMissingRefs)
572
506
  return;
573
507
  this.checkedMissingRefs = true;
574
508
  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)
509
+ if (!ac)
584
510
  return;
511
+ const targetId = this.data._id.toString();
585
512
  const allObjects = Object.values(ac.objects);
586
- this.loggers.debug?.(`findMissingObjectReference: Checking ${allObjects.length} objects in ${pointer[0]} for child link on ${pointer[1]}`);
587
513
  for (const obj of allObjects) {
588
514
  if (!obj.isLoaded)
589
515
  await obj.waitForPreloaded();
@@ -594,56 +520,30 @@ export class AutoUpdatedClientObject {
594
520
  ? val.map((v) => v._id?.toString() ?? v.toString())
595
521
  : [val._id?.toString() ?? val.toString()];
596
522
  if (ids.includes(targetId)) {
597
- this.loggers.info?.(`findMissingObjectReference: Found parent ${obj.className}:${obj._id} for property ${prop}`);
598
- dataRec[prop] = obj._id;
523
+ this.data[prop] = obj._id;
599
524
  return;
600
525
  }
601
526
  }
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
527
  }
610
528
  async contactChildren() {
611
- if (!this.parentManager)
612
- return;
613
- this.loggers.debug?.(`contactChildren for ${this.className}:${this._id}`);
614
529
  for (const prop of this.properties) {
615
530
  const isRef = getMetadataRecursive("isRef", this, prop.toString());
616
531
  const pointer = getMetadataRecursive("refsTo", this, prop.toString());
617
532
  if (!isRef || pointer)
618
533
  continue;
619
- const val = this.getValue(prop);
620
- if (!val)
534
+ const obj = this.getValue(prop);
535
+ if (!obj)
621
536
  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}`);
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();
643
541
  }
644
542
  }
645
543
  }
646
- this.generateSettersAndGetters();
544
+ }
545
+ async onUpdate() {
546
+ // Placeholder for server-side override
647
547
  }
648
548
  }
649
549
  export function processIsRefProperties(instance, target, prefix, allProps, newData, loggers) {
@@ -651,8 +551,6 @@ export function processIsRefProperties(instance, target, prefix, allProps, newDa
651
551
  for (const prop of props) {
652
552
  const path = prefix ? `${prefix}.${prop}` : prop;
653
553
  allProps.push(path);
654
- if (!instance)
655
- continue;
656
554
  newData[prop] = ObjectId.isValid(instance[prop])
657
555
  ? instance[prop]?.toString()
658
556
  : instance[prop];
@@ -667,16 +565,18 @@ export function processIsRefProperties(instance, target, prefix, allProps, newDa
667
565
  }
668
566
  const type = Reflect.getMetadata("design:type", target, prop);
669
567
  if (type?.prototype) {
670
- // 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
+ }
671
572
  }
672
573
  }
673
574
  return { allProps, newData };
674
575
  }
675
- export function getMetadataRecursive(metaKey, target, prop) {
676
- let proto = target;
576
+ export function getMetadataRecursive(metaKey, proto, prop) {
677
577
  while (proto) {
678
578
  const meta = Reflect.getMetadata(metaKey, proto, prop);
679
- if (meta !== undefined)
579
+ if (meta)
680
580
  return meta;
681
581
  proto = Object.getPrototypeOf(proto);
682
582
  }