metal-orm 1.1.5 → 1.1.7
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 +70 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/decorators/bootstrap.ts +114 -1
- package/src/schema/table.ts +21 -0
package/package.json
CHANGED
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
EntityOrTableTargetResolver,
|
|
22
22
|
getAllEntityMetadata,
|
|
23
23
|
getEntityMetadata,
|
|
24
|
-
|
|
24
|
+
addRelationMetadata,
|
|
25
|
+
addTransformerMetadata,
|
|
26
|
+
type RelationMetadata
|
|
25
27
|
} from '../orm/entity-metadata.js';
|
|
26
28
|
import { getDecoratorMetadata } from './decorator-metadata.js';
|
|
27
29
|
|
|
@@ -179,6 +181,117 @@ export const bootstrapEntities = (): TableDef[] => {
|
|
|
179
181
|
return metas.map(meta => meta.table!) as TableDef[];
|
|
180
182
|
};
|
|
181
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Builds a single RelationDef from RelationMetadata using the current set of
|
|
186
|
+
* already-bootstrapped entity tables as the resolution map.
|
|
187
|
+
*/
|
|
188
|
+
const resolveSingleRelation = (
|
|
189
|
+
relationName: string,
|
|
190
|
+
relation: RelationMetadata,
|
|
191
|
+
rootMeta: EntityMetadata
|
|
192
|
+
): RelationDef => {
|
|
193
|
+
// Build a tableMap from all entities that are already bootstrapped
|
|
194
|
+
const tableMap = new Map<EntityConstructor, TableDef>();
|
|
195
|
+
for (const m of getAllEntityMetadata()) {
|
|
196
|
+
if (m.table) tableMap.set(m.target, m.table);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
switch (relation.kind) {
|
|
200
|
+
case RelationKinds.HasOne: {
|
|
201
|
+
const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(rootMeta)}_id`;
|
|
202
|
+
return hasOne(
|
|
203
|
+
resolveTableTarget(relation.target, tableMap),
|
|
204
|
+
foreignKey,
|
|
205
|
+
relation.localKey,
|
|
206
|
+
relation.cascade
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
case RelationKinds.HasMany: {
|
|
210
|
+
const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(rootMeta)}_id`;
|
|
211
|
+
return hasMany(
|
|
212
|
+
resolveTableTarget(relation.target, tableMap),
|
|
213
|
+
foreignKey,
|
|
214
|
+
relation.localKey,
|
|
215
|
+
relation.cascade
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
case RelationKinds.BelongsTo: {
|
|
219
|
+
return belongsTo(
|
|
220
|
+
resolveTableTarget(relation.target, tableMap),
|
|
221
|
+
relation.foreignKey,
|
|
222
|
+
relation.localKey,
|
|
223
|
+
relation.cascade
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
case RelationKinds.BelongsToMany: {
|
|
227
|
+
const pivotForeignKeyToRoot =
|
|
228
|
+
relation.pivotForeignKeyToRoot ?? `${getPivotKeyBaseFromRoot(rootMeta)}_id`;
|
|
229
|
+
const pivotForeignKeyToTarget =
|
|
230
|
+
relation.pivotForeignKeyToTarget ?? `${getPivotKeyBaseFromTarget(relation.target)}_id`;
|
|
231
|
+
return belongsToMany(
|
|
232
|
+
resolveTableTarget(relation.target, tableMap),
|
|
233
|
+
resolveTableTarget(relation.pivotTable, tableMap),
|
|
234
|
+
{
|
|
235
|
+
pivotForeignKeyToRoot,
|
|
236
|
+
pivotForeignKeyToTarget,
|
|
237
|
+
localKey: relation.localKey,
|
|
238
|
+
targetKey: relation.targetKey,
|
|
239
|
+
pivotPrimaryKey: relation.pivotPrimaryKey,
|
|
240
|
+
defaultPivotColumns: relation.defaultPivotColumns,
|
|
241
|
+
cascade: relation.cascade
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
default:
|
|
246
|
+
throw new Error(`Unknown relation kind for relation '${relationName}'`);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Adds (or replaces) a single named relation on a decorator-based entity at any
|
|
252
|
+
* time — before or after `bootstrapEntities()` has been called.
|
|
253
|
+
*
|
|
254
|
+
* - Always writes the metadata into the entity's `EntityMetadata.relations` so
|
|
255
|
+
* that a future bootstrap will include it.
|
|
256
|
+
* - If the entity table has already been built, the resolved `RelationDef` is
|
|
257
|
+
* also patched directly into `table.relations` so the change is immediately
|
|
258
|
+
* visible to query builders and hydration.
|
|
259
|
+
*
|
|
260
|
+
* @param ctor - The entity class decorated with `@Entity`
|
|
261
|
+
* @param name - The relation property name (key used in `.include()`)
|
|
262
|
+
* @param relation - Relation metadata in the same format as decorators expect
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```ts
|
|
266
|
+
* // Same options as @HasMany decorator, usable at runtime
|
|
267
|
+
* addEntityRelation(User, 'comments', {
|
|
268
|
+
* kind: RelationKinds.HasMany,
|
|
269
|
+
* propertyKey: 'comments',
|
|
270
|
+
* target: () => Comment,
|
|
271
|
+
* foreignKey: 'user_id',
|
|
272
|
+
* });
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
export const addEntityRelation = (
|
|
276
|
+
ctor: EntityConstructor,
|
|
277
|
+
name: string,
|
|
278
|
+
relation: RelationMetadata
|
|
279
|
+
): void => {
|
|
280
|
+
// Check registration BEFORE auto-creating metadata via addRelationMetadata
|
|
281
|
+
const meta = getEntityMetadata(ctor);
|
|
282
|
+
if (!meta) {
|
|
283
|
+
throw new Error(`Entity '${ctor.name}' is not registered. Did you decorate it with @Entity?`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 1. Write to the decorator-layer metadata store (survives a future bootstrap)
|
|
287
|
+
addRelationMetadata(ctor, name, relation);
|
|
288
|
+
|
|
289
|
+
// 2. If the table is already built, patch it immediately
|
|
290
|
+
if (meta.table) {
|
|
291
|
+
meta.table.relations[name] = resolveSingleRelation(name, relation, meta);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
182
295
|
/**
|
|
183
296
|
* Gets the table definition for a given entity constructor.
|
|
184
297
|
* Bootstraps entities if necessary.
|
package/src/schema/table.ts
CHANGED
|
@@ -136,6 +136,27 @@ export function setRelations<
|
|
|
136
136
|
table.relations = relations;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Adds (or replaces) a single named relation on an existing table definition.
|
|
141
|
+
* Safe to call at any time — before or after queries are built.
|
|
142
|
+
*
|
|
143
|
+
* @param table - The table definition to mutate
|
|
144
|
+
* @param name - The relation name (property key used in `.include()` / hydration)
|
|
145
|
+
* @param relation - The fully-resolved relation definition
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* addRelation(usersTable, 'comments', hasMany(commentsTable, 'user_id'));
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export function addRelation<TTable extends TableDef>(
|
|
153
|
+
table: TTable,
|
|
154
|
+
name: string,
|
|
155
|
+
relation: RelationDef
|
|
156
|
+
): void {
|
|
157
|
+
table.relations[name] = relation;
|
|
158
|
+
}
|
|
159
|
+
|
|
139
160
|
type DirectColumnKeys<T extends TableDef> =
|
|
140
161
|
Exclude<keyof T["columns"] & string, keyof T | "$">;
|
|
141
162
|
|