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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -21,7 +21,9 @@ import {
21
21
  EntityOrTableTargetResolver,
22
22
  getAllEntityMetadata,
23
23
  getEntityMetadata,
24
- addTransformerMetadata
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.
@@ -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