metal-orm 1.0.104 → 1.0.105
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/dist/index.cjs +27 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +27 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/orm/entity-relations.ts +230 -209
- package/src/orm/entity.ts +3 -1
- package/src/orm/relations/belongs-to.ts +1 -1
- package/src/orm/relations/has-many.ts +2 -2
- package/src/orm/relations/has-one.ts +1 -1
- package/src/orm/relations/many-to-many.ts +1 -1
package/package.json
CHANGED
|
@@ -1,209 +1,230 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { EntityInstance, HasManyCollection, HasOneReference, BelongsToReference, ManyToManyCollection } from '../schema/types.js';
|
|
3
|
-
import { EntityMeta, RelationKey } from './entity-meta.js';
|
|
4
|
-
import { DefaultHasManyCollection } from './relations/has-many.js';
|
|
5
|
-
import { DefaultHasOneReference } from './relations/has-one.js';
|
|
6
|
-
import { DefaultBelongsToReference } from './relations/belongs-to.js';
|
|
7
|
-
import { DefaultManyToManyCollection } from './relations/many-to-many.js';
|
|
8
|
-
import { HasManyRelation, HasOneRelation, BelongsToRelation, BelongsToManyRelation, RelationKinds } from '../schema/relation.js';
|
|
9
|
-
import { loadHasManyRelation, loadHasOneRelation, loadBelongsToRelation, loadBelongsToManyRelation } from './lazy-batch.js';
|
|
10
|
-
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
11
|
-
import { relationLoaderCache } from './entity-relation-cache.js';
|
|
12
|
-
|
|
13
|
-
export type RelationEntityFactory = (
|
|
14
|
-
table: TableDef,
|
|
15
|
-
row: Record<string, unknown>
|
|
16
|
-
) => EntityInstance<TableDef>;
|
|
17
|
-
|
|
18
|
-
const proxifyRelationWrapper = <T extends object>(wrapper: T): T => {
|
|
19
|
-
return new Proxy(wrapper, {
|
|
20
|
-
get(target, prop, _receiver) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (typeof
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
meta
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
owner
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
meta.ctx,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
meta.ctx,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
meta.ctx,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { EntityInstance, HasManyCollection, HasOneReference, BelongsToReference, ManyToManyCollection } from '../schema/types.js';
|
|
3
|
+
import { EntityMeta, RelationKey } from './entity-meta.js';
|
|
4
|
+
import { DefaultHasManyCollection } from './relations/has-many.js';
|
|
5
|
+
import { DefaultHasOneReference } from './relations/has-one.js';
|
|
6
|
+
import { DefaultBelongsToReference } from './relations/belongs-to.js';
|
|
7
|
+
import { DefaultManyToManyCollection } from './relations/many-to-many.js';
|
|
8
|
+
import { HasManyRelation, HasOneRelation, BelongsToRelation, BelongsToManyRelation, RelationKinds } from '../schema/relation.js';
|
|
9
|
+
import { loadHasManyRelation, loadHasOneRelation, loadBelongsToRelation, loadBelongsToManyRelation } from './lazy-batch.js';
|
|
10
|
+
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
11
|
+
import { relationLoaderCache } from './entity-relation-cache.js';
|
|
12
|
+
|
|
13
|
+
export type RelationEntityFactory = (
|
|
14
|
+
table: TableDef,
|
|
15
|
+
row: Record<string, unknown>
|
|
16
|
+
) => EntityInstance<TableDef>;
|
|
17
|
+
|
|
18
|
+
const proxifyRelationWrapper = <T extends object>(wrapper: T): T => {
|
|
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
|
+
|
|
42
|
+
if (typeof prop === 'symbol') {
|
|
43
|
+
const value = Reflect.get(target, prop, target);
|
|
44
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (prop in target) {
|
|
48
|
+
const value = Reflect.get(target, prop, target);
|
|
49
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const getItems = (target as { getItems?: () => unknown }).getItems;
|
|
53
|
+
if (typeof getItems === 'function') {
|
|
54
|
+
const items = getItems.call(target);
|
|
55
|
+
if (items && prop in (items as object)) {
|
|
56
|
+
const propName = prop as string;
|
|
57
|
+
const value = (items as Record<string, unknown>)[propName];
|
|
58
|
+
return typeof value === 'function' ? value.bind(items) : value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const getRef = (target as { get?: () => unknown }).get;
|
|
63
|
+
if (typeof getRef === 'function') {
|
|
64
|
+
const current = getRef.call(target);
|
|
65
|
+
if (current && prop in (current as object)) {
|
|
66
|
+
const propName = prop as string;
|
|
67
|
+
const value = (current as Record<string, unknown>)[propName];
|
|
68
|
+
return typeof value === 'function' ? value.bind(current) : value;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return undefined;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
set(target, prop, value, _receiver) {
|
|
76
|
+
if (typeof prop === 'symbol') {
|
|
77
|
+
return Reflect.set(target, prop, value, target);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (prop in target) {
|
|
81
|
+
return Reflect.set(target, prop, value, target);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const getRef = (target as { get?: () => unknown }).get;
|
|
85
|
+
if (typeof getRef === 'function') {
|
|
86
|
+
const current = getRef.call(target);
|
|
87
|
+
if (current && typeof current === 'object') {
|
|
88
|
+
return Reflect.set(current as object, prop, value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const getItems = (target as { getItems?: () => unknown }).getItems;
|
|
93
|
+
if (typeof getItems === 'function') {
|
|
94
|
+
const items = getItems.call(target);
|
|
95
|
+
return Reflect.set(items as object, prop, value);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return Reflect.set(target, prop, value, target);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Gets a relation wrapper for an entity.
|
|
105
|
+
* @param meta - The entity metadata
|
|
106
|
+
* @param relationName - The relation name
|
|
107
|
+
* @param owner - The owner entity
|
|
108
|
+
* @param createEntity - The entity factory for relation rows
|
|
109
|
+
* @returns The relation wrapper or undefined
|
|
110
|
+
*/
|
|
111
|
+
export const getRelationWrapper = <TTable extends TableDef>(
|
|
112
|
+
meta: EntityMeta<TTable>,
|
|
113
|
+
relationName: RelationKey<TTable> | string,
|
|
114
|
+
owner: unknown,
|
|
115
|
+
createEntity: RelationEntityFactory
|
|
116
|
+
): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
|
|
117
|
+
const relationKey = relationName as string;
|
|
118
|
+
|
|
119
|
+
if (meta.relationWrappers.has(relationKey)) {
|
|
120
|
+
return meta.relationWrappers.get(relationKey) as HasManyCollection<unknown>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const relation = meta.table.relations[relationKey];
|
|
124
|
+
if (!relation) return undefined;
|
|
125
|
+
|
|
126
|
+
const wrapper = instantiateWrapper(meta, relationKey, relation, owner, createEntity);
|
|
127
|
+
if (!wrapper) return undefined;
|
|
128
|
+
|
|
129
|
+
const proxied = proxifyRelationWrapper(wrapper as object);
|
|
130
|
+
meta.relationWrappers.set(relationKey, proxied);
|
|
131
|
+
return proxied as HasManyCollection<unknown>;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Instantiates the appropriate relation wrapper based on relation type.
|
|
136
|
+
* @param meta - The entity metadata
|
|
137
|
+
* @param relationName - The relation name
|
|
138
|
+
* @param relation - The relation definition
|
|
139
|
+
* @param owner - The owner entity
|
|
140
|
+
* @param createEntity - The entity factory for relation rows
|
|
141
|
+
* @returns The relation wrapper or undefined
|
|
142
|
+
*/
|
|
143
|
+
const instantiateWrapper = <TTable extends TableDef>(
|
|
144
|
+
meta: EntityMeta<TTable>,
|
|
145
|
+
relationName: string,
|
|
146
|
+
relation: HasManyRelation | HasOneRelation | BelongsToRelation | BelongsToManyRelation,
|
|
147
|
+
owner: unknown,
|
|
148
|
+
createEntity: RelationEntityFactory
|
|
149
|
+
): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
|
|
150
|
+
const metaBase = meta as unknown as EntityMeta<TableDef>;
|
|
151
|
+
const loadCached = <T extends Map<string, unknown>>(factory: () => Promise<T>) =>
|
|
152
|
+
relationLoaderCache(metaBase, relationName, factory);
|
|
153
|
+
const resolveOptions = () => meta.lazyRelationOptions.get(relationName);
|
|
154
|
+
switch (relation.type) {
|
|
155
|
+
case RelationKinds.HasOne: {
|
|
156
|
+
const hasOne = relation as HasOneRelation;
|
|
157
|
+
const localKey = hasOne.localKey || findPrimaryKey(meta.table);
|
|
158
|
+
const loader = () => loadCached(() =>
|
|
159
|
+
loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne, resolveOptions())
|
|
160
|
+
);
|
|
161
|
+
return new DefaultHasOneReference(
|
|
162
|
+
meta.ctx,
|
|
163
|
+
metaBase,
|
|
164
|
+
owner,
|
|
165
|
+
relationName,
|
|
166
|
+
hasOne,
|
|
167
|
+
meta.table,
|
|
168
|
+
loader,
|
|
169
|
+
(row: Record<string, unknown>) => createEntity(hasOne.target, row),
|
|
170
|
+
localKey
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
case RelationKinds.HasMany: {
|
|
174
|
+
const hasMany = relation as HasManyRelation;
|
|
175
|
+
const localKey = hasMany.localKey || findPrimaryKey(meta.table);
|
|
176
|
+
const loader = () => loadCached(() =>
|
|
177
|
+
loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany, resolveOptions())
|
|
178
|
+
);
|
|
179
|
+
return new DefaultHasManyCollection(
|
|
180
|
+
meta.ctx,
|
|
181
|
+
metaBase,
|
|
182
|
+
owner,
|
|
183
|
+
relationName,
|
|
184
|
+
hasMany,
|
|
185
|
+
meta.table,
|
|
186
|
+
loader,
|
|
187
|
+
(row: Record<string, unknown>) => createEntity(relation.target, row),
|
|
188
|
+
localKey
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
case RelationKinds.BelongsTo: {
|
|
192
|
+
const belongsTo = relation as BelongsToRelation;
|
|
193
|
+
const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
|
|
194
|
+
const loader = () => loadCached(() =>
|
|
195
|
+
loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo, resolveOptions())
|
|
196
|
+
);
|
|
197
|
+
return new DefaultBelongsToReference(
|
|
198
|
+
meta.ctx,
|
|
199
|
+
metaBase,
|
|
200
|
+
owner,
|
|
201
|
+
relationName,
|
|
202
|
+
belongsTo,
|
|
203
|
+
meta.table,
|
|
204
|
+
loader,
|
|
205
|
+
(row: Record<string, unknown>) => createEntity(relation.target, row),
|
|
206
|
+
targetKey
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
case RelationKinds.BelongsToMany: {
|
|
210
|
+
const many = relation as BelongsToManyRelation;
|
|
211
|
+
const localKey = many.localKey || findPrimaryKey(meta.table);
|
|
212
|
+
const loader = () => loadCached(() =>
|
|
213
|
+
loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, resolveOptions())
|
|
214
|
+
);
|
|
215
|
+
return new DefaultManyToManyCollection(
|
|
216
|
+
meta.ctx,
|
|
217
|
+
metaBase,
|
|
218
|
+
owner,
|
|
219
|
+
relationName,
|
|
220
|
+
many,
|
|
221
|
+
meta.table,
|
|
222
|
+
loader,
|
|
223
|
+
(row: Record<string, unknown>) => createEntity(relation.target, row),
|
|
224
|
+
localKey
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
default:
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
};
|
package/src/orm/entity.ts
CHANGED
|
@@ -61,7 +61,9 @@ export const createEntityProxy = <
|
|
|
61
61
|
const buildJson = (self: JsonSource<TTable>): Record<string, unknown> => {
|
|
62
62
|
const json: Record<string, unknown> = {};
|
|
63
63
|
for (const key of Object.keys(target)) {
|
|
64
|
-
|
|
64
|
+
const value = self[key];
|
|
65
|
+
// Unwrap relation wrappers to get the actual data
|
|
66
|
+
json[key] = unwrapRelationForJson(value);
|
|
65
67
|
}
|
|
66
68
|
for (const [relationName, wrapper] of meta.relationWrappers) {
|
|
67
69
|
if (Object.prototype.hasOwnProperty.call(json, relationName)) continue;
|
|
@@ -14,7 +14,7 @@ const hideInternal = (obj: object, keys: string[]): void => {
|
|
|
14
14
|
Object.defineProperty(obj, key, {
|
|
15
15
|
value: obj[key],
|
|
16
16
|
writable: false,
|
|
17
|
-
configurable:
|
|
17
|
+
configurable: true, // Must be configurable for Proxy get trap to work properly
|
|
18
18
|
enumerable: false
|
|
19
19
|
});
|
|
20
20
|
}
|
|
@@ -14,7 +14,7 @@ const hideInternal = (obj: object, keys: string[]): void => {
|
|
|
14
14
|
Object.defineProperty(obj, key, {
|
|
15
15
|
value: obj[key],
|
|
16
16
|
writable: false,
|
|
17
|
-
configurable:
|
|
17
|
+
configurable: true, // Must be configurable for Proxy get trap to work properly
|
|
18
18
|
enumerable: false
|
|
19
19
|
});
|
|
20
20
|
}
|
|
@@ -78,7 +78,7 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
|
|
|
78
78
|
getItems(): TChild[] {
|
|
79
79
|
return this.#items;
|
|
80
80
|
}
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
/**
|
|
83
83
|
* Array-compatible length for testing frameworks.
|
|
84
84
|
*/
|
|
@@ -14,7 +14,7 @@ const hideInternal = (obj: object, keys: string[]): void => {
|
|
|
14
14
|
Object.defineProperty(obj, key, {
|
|
15
15
|
value: obj[key],
|
|
16
16
|
writable: false,
|
|
17
|
-
configurable:
|
|
17
|
+
configurable: true, // Must be configurable for Proxy get trap to work properly
|
|
18
18
|
enumerable: false
|
|
19
19
|
});
|
|
20
20
|
}
|
|
@@ -15,7 +15,7 @@ const hideInternal = (obj: object, keys: string[]): void => {
|
|
|
15
15
|
Object.defineProperty(obj, key, {
|
|
16
16
|
value: obj[key],
|
|
17
17
|
writable: false,
|
|
18
|
-
configurable:
|
|
18
|
+
configurable: true, // Must be configurable for Proxy get trap to work properly
|
|
19
19
|
enumerable: false
|
|
20
20
|
});
|
|
21
21
|
}
|