metal-orm 1.0.105 → 1.0.107

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.105",
3
+ "version": "1.0.107",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -17,36 +17,13 @@ export type RelationEntityFactory = (
17
17
 
18
18
  const proxifyRelationWrapper = <T extends object>(wrapper: T): T => {
19
19
  return new Proxy(wrapper, {
20
- get(target, prop, _receiver) {
21
- // Intercept toJSON to ensure proper serialization
22
- if (prop === 'toJSON') {
23
- return () => {
24
- // Call the wrapper's toJSON method if it exists
25
- const wrapperToJSON = (target as { toJSON?: () => unknown }).toJSON;
26
- if (typeof wrapperToJSON === 'function') {
27
- return wrapperToJSON.call(target);
28
- }
29
- // Fallback: return the underlying data
30
- const getRef = (target as { get?: () => unknown }).get;
31
- if (typeof getRef === 'function') {
32
- return getRef.call(target);
33
- }
34
- const getItems = (target as { getItems?: () => unknown }).getItems;
35
- if (typeof getItems === 'function') {
36
- return getItems.call(target);
37
- }
38
- return target;
39
- };
40
- }
41
-
20
+ get(target, prop, receiver) {
42
21
  if (typeof prop === 'symbol') {
43
- const value = Reflect.get(target, prop, target);
44
- return typeof value === 'function' ? value.bind(target) : value;
22
+ return Reflect.get(target, prop, receiver);
45
23
  }
46
24
 
47
25
  if (prop in target) {
48
- const value = Reflect.get(target, prop, target);
49
- return typeof value === 'function' ? value.bind(target) : value;
26
+ return Reflect.get(target, prop, receiver);
50
27
  }
51
28
 
52
29
  const getItems = (target as { getItems?: () => unknown }).getItems;
@@ -72,13 +49,13 @@ const proxifyRelationWrapper = <T extends object>(wrapper: T): T => {
72
49
  return undefined;
73
50
  },
74
51
 
75
- set(target, prop, value, _receiver) {
52
+ set(target, prop, value, receiver) {
76
53
  if (typeof prop === 'symbol') {
77
- return Reflect.set(target, prop, value, target);
54
+ return Reflect.set(target, prop, value, receiver);
78
55
  }
79
56
 
80
57
  if (prop in target) {
81
- return Reflect.set(target, prop, value, target);
58
+ return Reflect.set(target, prop, value, receiver);
82
59
  }
83
60
 
84
61
  const getRef = (target as { get?: () => unknown }).get;
@@ -95,7 +72,7 @@ const proxifyRelationWrapper = <T extends object>(wrapper: T): T => {
95
72
  return Reflect.set(items as object, prop, value);
96
73
  }
97
74
 
98
- return Reflect.set(target, prop, value, target);
75
+ return Reflect.set(target, prop, value, receiver);
99
76
  }
100
77
  });
101
78
  };
package/src/orm/entity.ts CHANGED
@@ -11,16 +11,7 @@ export { relationLoaderCache } from './entity-relation-cache.js';
11
11
 
12
12
  const isRelationWrapperLoaded = (value: unknown): boolean => {
13
13
  if (!value || typeof value !== 'object') return false;
14
- const wrapper = value as { isLoaded?: () => boolean };
15
- if (typeof wrapper.isLoaded === 'function') return wrapper.isLoaded();
16
- return false;
17
- };
18
-
19
- const unwrapRelationForJson = (value: unknown): unknown => {
20
- if (value && typeof value === 'object' && typeof (value as { toJSON?: () => unknown }).toJSON === 'function') {
21
- return (value as { toJSON: () => unknown }).toJSON();
22
- }
23
- return value;
14
+ return Boolean((value as { loaded?: boolean }).loaded);
24
15
  };
25
16
 
26
17
  type JsonSource<TTable extends TableDef> = EntityInstance<TTable> & Record<string, unknown>;
@@ -61,14 +52,16 @@ export const createEntityProxy = <
61
52
  const buildJson = (self: JsonSource<TTable>): Record<string, unknown> => {
62
53
  const json: Record<string, unknown> = {};
63
54
  for (const key of Object.keys(target)) {
64
- const value = self[key];
65
- // Unwrap relation wrappers to get the actual data
66
- json[key] = unwrapRelationForJson(value);
55
+ if (table.relations[key]) continue;
56
+ json[key] = self[key];
67
57
  }
68
- for (const [relationName, wrapper] of meta.relationWrappers) {
69
- if (Object.prototype.hasOwnProperty.call(json, relationName)) continue;
70
- if (isRelationWrapperLoaded(wrapper)) {
71
- json[relationName] = unwrapRelationForJson(wrapper);
58
+ for (const relationName of Object.keys(table.relations)) {
59
+ const wrapper = self[relationName];
60
+ if (wrapper && isRelationWrapperLoaded(wrapper)) {
61
+ const wrapperWithToJSON = wrapper as { toJSON?: () => unknown };
62
+ json[relationName] = typeof wrapperWithToJSON.toJSON === 'function'
63
+ ? wrapperWithToJSON.toJSON()
64
+ : wrapper;
72
65
  }
73
66
  }
74
67
  return json;
@@ -14,7 +14,19 @@ const hideInternal = (obj: object, keys: string[]): void => {
14
14
  Object.defineProperty(obj, key, {
15
15
  value: obj[key],
16
16
  writable: false,
17
- configurable: true, // Must be configurable for Proxy get trap to work properly
17
+ configurable: false,
18
+ enumerable: false
19
+ });
20
+ }
21
+ };
22
+
23
+ const hideWritable = (obj: object, keys: string[]): void => {
24
+ for (const key of keys) {
25
+ const value = obj[key as keyof typeof obj];
26
+ Object.defineProperty(obj, key, {
27
+ value,
28
+ writable: true,
29
+ configurable: true,
18
30
  enumerable: false
19
31
  });
20
32
  }
@@ -27,8 +39,8 @@ const hideInternal = (obj: object, keys: string[]): void => {
27
39
  * @template TParent The type of the parent entity.
28
40
  */
29
41
  export class DefaultBelongsToReference<TParent extends object> implements BelongsToReferenceApi<TParent> {
30
- #loaded = false;
31
- #current: TParent | null = null;
42
+ private loaded = false;
43
+ private current: TParent | null = null;
32
44
 
33
45
  /**
34
46
  * @param ctx The entity context for tracking changes.
@@ -53,32 +65,33 @@ export class DefaultBelongsToReference<TParent extends object> implements Belong
53
65
  private readonly targetKey: string
54
66
  ) {
55
67
  hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'targetKey']);
68
+ hideWritable(this, ['loaded', 'current']);
56
69
  this.populateFromHydrationCache();
57
70
  }
58
71
 
59
72
  async load(): Promise<TParent | null> {
60
- if (this.#loaded) return this.#current;
73
+ if (this.loaded) return this.current;
61
74
  const map = await this.loader();
62
75
  const fkValue = (this.root as Record<string, unknown>)[this.relation.foreignKey];
63
76
  if (fkValue === null || fkValue === undefined) {
64
- this.#current = null;
77
+ this.current = null;
65
78
  } else {
66
79
  const row = map.get(toKey(fkValue));
67
- this.#current = row ? this.createEntity(row) : null;
80
+ this.current = row ? this.createEntity(row) : null;
68
81
  }
69
- this.#loaded = true;
70
- return this.#current;
82
+ this.loaded = true;
83
+ return this.current;
71
84
  }
72
85
 
73
86
  get(): TParent | null {
74
- return this.#current;
87
+ return this.current;
75
88
  }
76
89
 
77
90
  set(data: Partial<TParent> | TParent | null): TParent | null {
78
91
  if (data === null) {
79
- const previous = this.#current;
92
+ const previous = this.current;
80
93
  (this.root as Record<string, unknown>)[this.relation.foreignKey] = null;
81
- this.#current = null;
94
+ this.current = null;
82
95
  this.ctx.registerRelationChange(
83
96
  this.root,
84
97
  this.relationKey,
@@ -95,7 +108,7 @@ export class DefaultBelongsToReference<TParent extends object> implements Belong
95
108
  if (pkValue !== undefined) {
96
109
  (this.root as Record<string, unknown>)[this.relation.foreignKey] = pkValue;
97
110
  }
98
- this.#current = entity;
111
+ this.current = entity;
99
112
  this.ctx.registerRelationChange(
100
113
  this.root,
101
114
  this.relationKey,
@@ -107,10 +120,6 @@ export class DefaultBelongsToReference<TParent extends object> implements Belong
107
120
  return entity;
108
121
  }
109
122
 
110
- isLoaded(): boolean {
111
- return this.#loaded;
112
- }
113
-
114
123
  private get relationKey(): RelationKey {
115
124
  return `${this.rootTable.name}.${this.relationName}`;
116
125
  }
@@ -120,11 +129,11 @@ export class DefaultBelongsToReference<TParent extends object> implements Belong
120
129
  if (fkValue === undefined || fkValue === null) return;
121
130
  const row = getHydrationRecord(this.meta, this.relationName, fkValue);
122
131
  if (!row) return;
123
- this.#current = this.createEntity(row);
124
- this.#loaded = true;
132
+ this.current = this.createEntity(row);
133
+ this.loaded = true;
125
134
  }
126
135
 
127
136
  toJSON(): TParent | null {
128
- return this.#current;
137
+ return this.current;
129
138
  }
130
139
  }
@@ -14,7 +14,19 @@ const hideInternal = (obj: object, keys: string[]): void => {
14
14
  Object.defineProperty(obj, key, {
15
15
  value: obj[key],
16
16
  writable: false,
17
- configurable: true, // Must be configurable for Proxy get trap to work properly
17
+ configurable: false,
18
+ enumerable: false
19
+ });
20
+ }
21
+ };
22
+
23
+ const hideWritable = (obj: object, keys: string[]): void => {
24
+ for (const key of keys) {
25
+ const value = obj[key as keyof typeof obj];
26
+ Object.defineProperty(obj, key, {
27
+ value,
28
+ writable: true,
29
+ configurable: true,
18
30
  enumerable: false
19
31
  });
20
32
  }
@@ -25,10 +37,10 @@ const hideInternal = (obj: object, keys: string[]): void => {
25
37
  * @template TChild - The type of child entities in the collection
26
38
  */
27
39
  export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChild> {
28
- #loaded = false;
29
- #items: TChild[] = [];
30
- readonly #added = new Set<TChild>();
31
- readonly #removed = new Set<TChild>();
40
+ private loaded = false;
41
+ private items: TChild[] = [];
42
+ private readonly added = new Set<TChild>();
43
+ private readonly removed = new Set<TChild>();
32
44
 
33
45
  /**
34
46
  * Creates a new DefaultHasManyCollection instance.
@@ -54,6 +66,7 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
54
66
  private readonly localKey: string
55
67
  ) {
56
68
  hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'localKey']);
69
+ hideWritable(this, ['loaded', 'items', 'added', 'removed']);
57
70
  this.hydrateFromCache();
58
71
  }
59
72
 
@@ -62,13 +75,13 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
62
75
  * @returns Promise resolving to the array of child entities
63
76
  */
64
77
  async load(): Promise<TChild[]> {
65
- if (this.#loaded) return this.#items;
78
+ if (this.loaded) return this.items;
66
79
  const map = await this.loader();
67
80
  const key = toKey((this.root as Record<string, unknown>)[this.localKey]);
68
81
  const rows = map.get(key) ?? [];
69
- this.#items = rows.map(row => this.createEntity(row));
70
- this.#loaded = true;
71
- return this.#items;
82
+ this.items = rows.map(row => this.createEntity(row));
83
+ this.loaded = true;
84
+ return this.items;
72
85
  }
73
86
 
74
87
  /**
@@ -76,21 +89,21 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
76
89
  * @returns Array of child entities
77
90
  */
78
91
  getItems(): TChild[] {
79
- return this.#items;
92
+ return this.items;
80
93
  }
81
-
94
+
82
95
  /**
83
96
  * Array-compatible length for testing frameworks.
84
97
  */
85
98
  get length(): number {
86
- return this.#items.length;
99
+ return this.items.length;
87
100
  }
88
101
 
89
102
  /**
90
103
  * Enables iteration over the collection like an array.
91
104
  */
92
105
  [Symbol.iterator](): Iterator<TChild> {
93
- return this.#items[Symbol.iterator]();
106
+ return this.items[Symbol.iterator]();
94
107
  }
95
108
 
96
109
  /**
@@ -105,8 +118,8 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
105
118
  [this.relation.foreignKey]: keyValue
106
119
  };
107
120
  const entity = this.createEntity(childRow);
108
- this.#added.add(entity);
109
- this.#items.push(entity);
121
+ this.added.add(entity);
122
+ this.items.push(entity);
110
123
  this.ctx.registerRelationChange(
111
124
  this.root,
112
125
  this.relationKey,
@@ -126,7 +139,7 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
126
139
  const keyValue = this.root[this.localKey];
127
140
  (entity as Record<string, unknown>)[this.relation.foreignKey] = keyValue;
128
141
  this.ctx.markDirty(entity as object);
129
- this.#items.push(entity);
142
+ this.items.push(entity);
130
143
  this.ctx.registerRelationChange(
131
144
  this.root,
132
145
  this.relationKey,
@@ -142,8 +155,8 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
142
155
  * @param entity - The entity to remove
143
156
  */
144
157
  remove(entity: TChild): void {
145
- this.#items = this.#items.filter(item => item !== entity);
146
- this.#removed.add(entity);
158
+ this.items = this.items.filter(item => item !== entity);
159
+ this.removed.add(entity);
147
160
  this.ctx.registerRelationChange(
148
161
  this.root,
149
162
  this.relationKey,
@@ -158,15 +171,11 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
158
171
  * Clears all entities from the collection.
159
172
  */
160
173
  clear(): void {
161
- for (const entity of [...this.#items]) {
174
+ for (const entity of [...this.items]) {
162
175
  this.remove(entity);
163
176
  }
164
177
  }
165
178
 
166
- isLoaded(): boolean {
167
- return this.#loaded;
168
- }
169
-
170
179
  private get relationKey(): RelationKey {
171
180
  return `${this.rootTable.name}.${this.relationName}`;
172
181
  }
@@ -176,8 +185,8 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
176
185
  if (keyValue === undefined || keyValue === null) return;
177
186
  const rows = getHydrationRows(this.meta, this.relationName, keyValue);
178
187
  if (!rows?.length) return;
179
- this.#items = rows.map(row => this.createEntity(row));
180
- this.#loaded = true;
188
+ this.items = rows.map(row => this.createEntity(row));
189
+ this.loaded = true;
181
190
  }
182
191
 
183
192
  /**
@@ -185,6 +194,6 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
185
194
  * @returns Array of child entities
186
195
  */
187
196
  toJSON(): TChild[] {
188
- return this.#items;
197
+ return this.items;
189
198
  }
190
199
  }
@@ -14,7 +14,19 @@ const hideInternal = (obj: object, keys: string[]): void => {
14
14
  Object.defineProperty(obj, key, {
15
15
  value: obj[key],
16
16
  writable: false,
17
- configurable: true, // Must be configurable for Proxy get trap to work properly
17
+ configurable: false,
18
+ enumerable: false
19
+ });
20
+ }
21
+ };
22
+
23
+ const hideWritable = (obj: object, keys: string[]): void => {
24
+ for (const key of keys) {
25
+ const value = obj[key as keyof typeof obj];
26
+ Object.defineProperty(obj, key, {
27
+ value,
28
+ writable: true,
29
+ configurable: true,
18
30
  enumerable: false
19
31
  });
20
32
  }
@@ -27,8 +39,8 @@ const hideInternal = (obj: object, keys: string[]): void => {
27
39
  * @template TChild The type of the child entity.
28
40
  */
29
41
  export class DefaultHasOneReference<TChild extends object> implements HasOneReferenceApi<TChild> {
30
- #loaded = false;
31
- #current: TChild | null = null;
42
+ private loaded = false;
43
+ private current: TChild | null = null;
32
44
 
33
45
  /**
34
46
  * @param ctx The entity context for tracking changes.
@@ -63,25 +75,26 @@ export class DefaultHasOneReference<TChild extends object> implements HasOneRefe
63
75
  'createEntity',
64
76
  'localKey'
65
77
  ]);
78
+ hideWritable(this, ['loaded', 'current']);
66
79
  this.populateFromHydrationCache();
67
80
  }
68
81
 
69
82
  async load(): Promise<TChild | null> {
70
- if (this.#loaded) return this.#current;
83
+ if (this.loaded) return this.current;
71
84
  const map = await this.loader();
72
85
  const keyValue = (this.root as Record<string, unknown>)[this.localKey];
73
86
  if (keyValue === undefined || keyValue === null) {
74
- this.#loaded = true;
75
- return this.#current;
87
+ this.loaded = true;
88
+ return this.current;
76
89
  }
77
90
  const row = map.get(toKey(keyValue));
78
- this.#current = row ? this.createEntity(row) : null;
79
- this.#loaded = true;
80
- return this.#current;
91
+ this.current = row ? this.createEntity(row) : null;
92
+ this.loaded = true;
93
+ return this.current;
81
94
  }
82
95
 
83
96
  get(): TChild | null {
84
- return this.#current;
97
+ return this.current;
85
98
  }
86
99
 
87
100
  set(data: Partial<TChild> | TChild | null): TChild | null {
@@ -90,20 +103,20 @@ export class DefaultHasOneReference<TChild extends object> implements HasOneRefe
90
103
  }
91
104
 
92
105
  const entity = hasEntityMeta(data) ? (data as TChild) : this.createEntity(data as Row);
93
- if (this.#current && this.#current !== entity) {
106
+ if (this.current && this.current !== entity) {
94
107
  this.ctx.registerRelationChange(
95
108
  this.root,
96
109
  this.relationKey,
97
110
  this.rootTable,
98
111
  this.relationName,
99
112
  this.relation,
100
- { kind: 'remove', entity: this.#current }
113
+ { kind: 'remove', entity: this.current }
101
114
  );
102
115
  }
103
116
 
104
117
  this.assignForeignKey(entity);
105
- this.#current = entity;
106
- this.#loaded = true;
118
+ this.current = entity;
119
+ this.loaded = true;
107
120
 
108
121
  this.ctx.registerRelationChange(
109
122
  this.root,
@@ -117,19 +130,15 @@ export class DefaultHasOneReference<TChild extends object> implements HasOneRefe
117
130
  return entity;
118
131
  }
119
132
 
120
- isLoaded(): boolean {
121
- return this.#loaded;
122
- }
123
-
124
133
  toJSON(): TChild | null {
125
- return this.#current;
134
+ return this.current;
126
135
  }
127
136
 
128
137
  private detachCurrent(): TChild | null {
129
- const previous = this.#current;
138
+ const previous = this.current;
130
139
  if (!previous) return null;
131
- this.#current = null;
132
- this.#loaded = true;
140
+ this.current = null;
141
+ this.loaded = true;
133
142
  this.ctx.registerRelationChange(
134
143
  this.root,
135
144
  this.relationKey,
@@ -156,7 +165,7 @@ export class DefaultHasOneReference<TChild extends object> implements HasOneRefe
156
165
  if (keyValue === undefined || keyValue === null) return;
157
166
  const row = getHydrationRecord(this.meta, this.relationName, keyValue);
158
167
  if (!row) return;
159
- this.#current = this.createEntity(row);
160
- this.#loaded = true;
168
+ this.current = this.createEntity(row);
169
+ this.loaded = true;
161
170
  }
162
171
  }
@@ -15,7 +15,19 @@ const hideInternal = (obj: object, keys: string[]): void => {
15
15
  Object.defineProperty(obj, key, {
16
16
  value: obj[key],
17
17
  writable: false,
18
- configurable: true, // Must be configurable for Proxy get trap to work properly
18
+ configurable: false,
19
+ enumerable: false
20
+ });
21
+ }
22
+ };
23
+
24
+ const hideWritable = (obj: object, keys: string[]): void => {
25
+ for (const key of keys) {
26
+ const value = obj[key as keyof typeof obj];
27
+ Object.defineProperty(obj, key, {
28
+ value,
29
+ writable: true,
30
+ configurable: true,
19
31
  enumerable: false
20
32
  });
21
33
  }
@@ -30,8 +42,8 @@ const hideInternal = (obj: object, keys: string[]): void => {
30
42
  */
31
43
  export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefined = undefined>
32
44
  implements ManyToManyCollection<TTarget, TPivot> {
33
- #loaded = false;
34
- #items: TTarget[] = [];
45
+ private loaded = false;
46
+ private items: TTarget[] = [];
35
47
 
36
48
  /**
37
49
  * @param ctx The entity context for tracking changes.
@@ -56,6 +68,7 @@ export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefi
56
68
  private readonly localKey: string
57
69
  ) {
58
70
  hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'localKey']);
71
+ hideWritable(this, ['loaded', 'items']);
59
72
  this.hydrateFromCache();
60
73
  }
61
74
 
@@ -64,19 +77,19 @@ export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefi
64
77
  * @returns A promise that resolves to the array of target entities.
65
78
  */
66
79
  async load(): Promise<TTarget[]> {
67
- if (this.#loaded) return this.#items;
80
+ if (this.loaded) return this.items;
68
81
  const map = await this.loader();
69
82
  const key = toKey(this.root[this.localKey]);
70
83
  const rows = map.get(key) ?? [];
71
- this.#items = rows.map(row => {
84
+ this.items = rows.map(row => {
72
85
  const entity = this.createEntity(row);
73
86
  if ((row as { _pivot?: unknown })._pivot) {
74
87
  (entity as { _pivot?: unknown })._pivot = (row as { _pivot?: unknown })._pivot;
75
88
  }
76
89
  return entity;
77
90
  });
78
- this.#loaded = true;
79
- return this.#items;
91
+ this.loaded = true;
92
+ return this.items;
80
93
  }
81
94
 
82
95
  /**
@@ -84,21 +97,21 @@ export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefi
84
97
  * @returns Array of target entities.
85
98
  */
86
99
  getItems(): TTarget[] {
87
- return this.#items;
100
+ return this.items;
88
101
  }
89
102
 
90
103
  /**
91
104
  * Array-compatible length for testing frameworks.
92
105
  */
93
106
  get length(): number {
94
- return this.#items.length;
107
+ return this.items.length;
95
108
  }
96
109
 
97
110
  /**
98
111
  * Enables iteration over the collection like an array.
99
112
  */
100
113
  [Symbol.iterator](): Iterator<TTarget> {
101
- return this.#items[Symbol.iterator]();
114
+ return this.items[Symbol.iterator]();
102
115
  }
103
116
 
104
117
  /**
@@ -109,13 +122,13 @@ export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefi
109
122
  attach(target: TTarget | number | string): void {
110
123
  const entity = this.ensureEntity(target);
111
124
  const id = this.extractId(entity);
112
- if (id != null && this.#items.some(item => this.extractId(item) === id)) {
125
+ if (id != null && this.items.some(item => this.extractId(item) === id)) {
113
126
  return;
114
127
  }
115
- if (id == null && this.#items.includes(entity)) {
128
+ if (id == null && this.items.includes(entity)) {
116
129
  return;
117
130
  }
118
- this.#items.push(entity);
131
+ this.items.push(entity);
119
132
  this.ctx.registerRelationChange(
120
133
  this.root,
121
134
  this.relationKey,
@@ -138,10 +151,10 @@ export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefi
138
151
 
139
152
  if (id == null) return;
140
153
 
141
- const existing = this.#items.find(item => this.extractId(item) === id);
154
+ const existing = this.items.find(item => this.extractId(item) === id);
142
155
  if (!existing) return;
143
156
 
144
- this.#items = this.#items.filter(item => this.extractId(item) !== id);
157
+ this.items = this.items.filter(item => this.extractId(item) !== id);
145
158
  this.ctx.registerRelationChange(
146
159
  this.root,
147
160
  this.relationKey,
@@ -160,7 +173,7 @@ export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefi
160
173
  async syncByIds(ids: (number | string)[]): Promise<void> {
161
174
  await this.load();
162
175
  const normalized = new Set(ids.map(id => toKey(id)));
163
- const currentIds = new Set(this.#items.map(item => toKey(this.extractId(item))));
176
+ const currentIds = new Set(this.items.map(item => toKey(this.extractId(item))));
164
177
 
165
178
  for (const id of normalized) {
166
179
  if (!currentIds.has(id)) {
@@ -168,7 +181,7 @@ export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefi
168
181
  }
169
182
  }
170
183
 
171
- for (const item of [...this.#items]) {
184
+ for (const item of [...this.items]) {
172
185
  const itemId = toKey(this.extractId(item));
173
186
  if (!normalized.has(itemId)) {
174
187
  this.detach(item);
@@ -176,10 +189,6 @@ export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefi
176
189
  }
177
190
  }
178
191
 
179
- isLoaded(): boolean {
180
- return this.#loaded;
181
- }
182
-
183
192
  private ensureEntity(target: TTarget | number | string): TTarget {
184
193
  if (typeof target === 'number' || typeof target === 'string') {
185
194
  const stub: Record<string, unknown> = {
@@ -211,7 +220,7 @@ export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefi
211
220
  if (keyValue === undefined || keyValue === null) return;
212
221
  const rows = getHydrationRows(this.meta, this.relationName, keyValue);
213
222
  if (!rows?.length) return;
214
- this.#items = rows.map(row => {
223
+ this.items = rows.map(row => {
215
224
  const entity = this.createEntity(row);
216
225
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
226
  if ((row as { _pivot?: unknown })._pivot) {
@@ -219,10 +228,10 @@ export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefi
219
228
  }
220
229
  return entity;
221
230
  });
222
- this.#loaded = true;
231
+ this.loaded = true;
223
232
  }
224
233
 
225
234
  toJSON(): TTarget[] {
226
- return this.#items;
235
+ return this.items;
227
236
  }
228
237
  }