metal-orm 1.0.102 → 1.0.104

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.102",
3
+ "version": "1.0.104",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -188,7 +188,7 @@ Flags:
188
188
  --include=tbl1,tbl2 Only include these tables
189
189
  --exclude=tbl3,tbl4 Exclude these tables
190
190
  --locale=pt-BR Naming locale for class/relation names (default: en)
191
- --naming-overrides Path to JSON map of irregular plurals { "singular": "plural" }
191
+ --naming-overrides Path to JSON config for naming customizations (see docs)
192
192
  --dry-run Print to stdout instead of writing a file
193
193
  --out=<file> Override the generated file (defaults to generated-entities.ts or the index inside --out-dir)
194
194
  --out-dir=<dir> Emit one file per entity inside this directory plus the shared index
@@ -5,7 +5,7 @@ import { loadDriver } from './drivers.mjs';
5
5
  import { renderEntityFile, renderSplitEntityFiles, renderSplitIndexFile } from './render.mjs';
6
6
  import { printDryRun, writeSingleFile, writeSplitFiles } from './emit.mjs';
7
7
 
8
- const loadIrregulars = async (filePath, fsPromises) => {
8
+ const loadNamingOverrides = async (filePath, fsPromises) => {
9
9
  const raw = await fsPromises.readFile(filePath, 'utf8');
10
10
  let parsed;
11
11
  try {
@@ -13,22 +13,31 @@ const loadIrregulars = async (filePath, fsPromises) => {
13
13
  } catch (err) {
14
14
  throw new Error(`Failed to parse naming overrides at ${filePath}: ${err.message || err}`);
15
15
  }
16
- const irregulars =
17
- parsed && typeof parsed === 'object' && !Array.isArray(parsed)
18
- ? parsed.irregulars && typeof parsed.irregulars === 'object' && !Array.isArray(parsed.irregulars)
19
- ? parsed.irregulars
20
- : parsed
21
- : undefined;
22
- if (!irregulars) {
23
- throw new Error(`Naming overrides at ${filePath} must be an object or { "irregulars": { ... } }`);
16
+
17
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
18
+ throw new Error(`Naming overrides at ${filePath} must be an object`);
24
19
  }
25
- return irregulars;
20
+
21
+ // Support both flat format { "singular": "plural" } and structured { irregulars: {...}, relationOverrides: {...} }
22
+ const hasStructuredFormat = parsed.irregulars || parsed.relationOverrides;
23
+
24
+ const irregulars = hasStructuredFormat
25
+ ? (parsed.irregulars && typeof parsed.irregulars === 'object' ? parsed.irregulars : {})
26
+ : parsed;
27
+
28
+ const relationOverrides = hasStructuredFormat && parsed.relationOverrides && typeof parsed.relationOverrides === 'object'
29
+ ? parsed.relationOverrides
30
+ : {};
31
+
32
+ return { irregulars, relationOverrides };
26
33
  };
27
34
 
28
35
  export const generateEntities = async (opts, context = {}) => {
29
36
  const { fs: fsPromises = fs, logger = console } = context;
30
- const irregulars = opts.namingOverrides ? await loadIrregulars(opts.namingOverrides, fsPromises) : undefined;
31
- const naming = createNamingStrategy(opts.locale, irregulars);
37
+ const { irregulars, relationOverrides } = opts.namingOverrides
38
+ ? await loadNamingOverrides(opts.namingOverrides, fsPromises)
39
+ : { irregulars: undefined, relationOverrides: {} };
40
+ const naming = createNamingStrategy(opts.locale, irregulars, relationOverrides);
32
41
 
33
42
  const { executor, cleanup } = await loadDriver(opts.dialect, opts.url, opts.dbPath);
34
43
  let schema;
@@ -259,26 +259,27 @@ const renderEntityClassLines = ({ table, className, naming, relations, resolveCl
259
259
  for (const rel of relations) {
260
260
  const targetClass = resolveClassName(rel.target);
261
261
  if (!targetClass) continue;
262
+ const propName = naming.applyRelationOverride(className, rel.property);
262
263
  switch (rel.kind) {
263
264
  case 'belongsTo':
264
265
  lines.push(
265
266
  ` @BelongsTo({ target: () => ${targetClass}, foreignKey: '${escapeJsString(rel.foreignKey)}' })`
266
267
  );
267
- lines.push(` ${rel.property}!: BelongsToReference<${targetClass}>;`);
268
+ lines.push(` ${propName}!: BelongsToReference<${targetClass}>;`);
268
269
  lines.push('');
269
270
  break;
270
271
  case 'hasMany':
271
272
  lines.push(
272
273
  ` @HasMany({ target: () => ${targetClass}, foreignKey: '${escapeJsString(rel.foreignKey)}' })`
273
274
  );
274
- lines.push(` ${rel.property}!: HasManyCollection<${targetClass}>;`);
275
+ lines.push(` ${propName}!: HasManyCollection<${targetClass}>;`);
275
276
  lines.push('');
276
277
  break;
277
278
  case 'hasOne':
278
279
  lines.push(
279
280
  ` @HasOne({ target: () => ${targetClass}, foreignKey: '${escapeJsString(rel.foreignKey)}' })`
280
281
  );
281
- lines.push(` ${rel.property}!: HasOneReference<${targetClass}>;`);
282
+ lines.push(` ${propName}!: HasOneReference<${targetClass}>;`);
282
283
  lines.push('');
283
284
  break;
284
285
  case 'belongsToMany': {
@@ -289,7 +290,7 @@ const renderEntityClassLines = ({ table, className, naming, relations, resolveCl
289
290
  rel.pivotForeignKeyToRoot
290
291
  )}', pivotForeignKeyToTarget: '${escapeJsString(rel.pivotForeignKeyToTarget)}' })`
291
292
  );
292
- lines.push(` ${rel.property}!: ManyToManyCollection<${targetClass}>;`);
293
+ lines.push(` ${propName}!: ManyToManyCollection<${targetClass}>;`);
293
294
  lines.push('');
294
295
  break;
295
296
  }
@@ -1,10 +1,12 @@
1
1
  import { resolveInflector } from './inflection/index.mjs';
2
2
 
3
3
  export class BaseNamingStrategy {
4
- constructor(irregulars = {}, inflector = resolveInflector('en')) {
4
+ constructor(irregulars = {}, inflector = resolveInflector('en'), relationOverrides = {}) {
5
5
  this.irregulars = new Map();
6
6
  this.inverseIrregulars = new Map();
7
7
  this.inflector = inflector;
8
+ this.relationOverrides = new Map();
9
+
8
10
  for (const [singular, plural] of Object.entries(irregulars)) {
9
11
  if (!singular || !plural) continue;
10
12
  const normalize = this.inflector.normalizeForIrregularKey || (value => String(value).toLowerCase());
@@ -13,6 +15,20 @@ export class BaseNamingStrategy {
13
15
  this.irregulars.set(singularKey, pluralValue);
14
16
  this.inverseIrregulars.set(pluralValue, singularKey);
15
17
  }
18
+
19
+ // Build relation overrides map: className -> { originalProp -> newProp }
20
+ for (const [className, overrides] of Object.entries(relationOverrides)) {
21
+ if (!overrides || typeof overrides !== 'object') continue;
22
+ this.relationOverrides.set(className, new Map(Object.entries(overrides)));
23
+ }
24
+ }
25
+
26
+ applyRelationOverride(className, propertyName) {
27
+ const classOverrides = this.relationOverrides.get(className);
28
+ if (classOverrides?.has(propertyName)) {
29
+ return classOverrides.get(propertyName);
30
+ }
31
+ return propertyName;
16
32
  }
17
33
 
18
34
  applyIrregular(word, direction) {
@@ -109,8 +125,8 @@ export class PortugueseNamingStrategy extends BaseNamingStrategy {
109
125
  }
110
126
  }
111
127
 
112
- export const createNamingStrategy = (locale = 'en', irregulars) => {
128
+ export const createNamingStrategy = (locale = 'en', irregulars, relationOverrides = {}) => {
113
129
  const inflector = resolveInflector(locale);
114
130
  const mergedIrregulars = { ...(inflector.defaultIrregulars || {}), ...(irregulars || {}) };
115
- return new BaseNamingStrategy(mergedIrregulars, inflector);
131
+ return new BaseNamingStrategy(mergedIrregulars, inflector, relationOverrides);
116
132
  };
@@ -1,207 +1,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
- if (typeof prop === 'symbol') {
22
- return Reflect.get(target, prop, receiver);
23
- }
24
-
25
- if (prop in target) {
26
- return Reflect.get(target, prop, receiver);
27
- }
28
-
29
- const getItems = (target as { getItems?: () => unknown }).getItems;
30
- if (typeof getItems === 'function') {
31
- const items = getItems.call(target);
32
- if (items && prop in (items as object)) {
33
- const propName = prop as string;
34
- const value = (items as Record<string, unknown>)[propName];
35
- return typeof value === 'function' ? value.bind(items) : value;
36
- }
37
- }
38
-
39
- const getRef = (target as { get?: () => unknown }).get;
40
- if (typeof getRef === 'function') {
41
- const current = getRef.call(target);
42
- if (current && prop in (current as object)) {
43
- const propName = prop as string;
44
- const value = (current as Record<string, unknown>)[propName];
45
- return typeof value === 'function' ? value.bind(current) : value;
46
- }
47
- }
48
-
49
- return undefined;
50
- },
51
-
52
- set(target, prop, value, receiver) {
53
- if (typeof prop === 'symbol') {
54
- return Reflect.set(target, prop, value, receiver);
55
- }
56
-
57
- if (prop in target) {
58
- return Reflect.set(target, prop, value, receiver);
59
- }
60
-
61
- const getRef = (target as { get?: () => unknown }).get;
62
- if (typeof getRef === 'function') {
63
- const current = getRef.call(target);
64
- if (current && typeof current === 'object') {
65
- return Reflect.set(current as object, prop, value);
66
- }
67
- }
68
-
69
- const getItems = (target as { getItems?: () => unknown }).getItems;
70
- if (typeof getItems === 'function') {
71
- const items = getItems.call(target);
72
- return Reflect.set(items as object, prop, value);
73
- }
74
-
75
- return Reflect.set(target, prop, value, receiver);
76
- }
77
- });
78
- };
79
-
80
- /**
81
- * Gets a relation wrapper for an entity.
82
- * @param meta - The entity metadata
83
- * @param relationName - The relation name
84
- * @param owner - The owner entity
85
- * @param createEntity - The entity factory for relation rows
86
- * @returns The relation wrapper or undefined
87
- */
88
- export const getRelationWrapper = <TTable extends TableDef>(
89
- meta: EntityMeta<TTable>,
90
- relationName: RelationKey<TTable> | string,
91
- owner: unknown,
92
- createEntity: RelationEntityFactory
93
- ): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
94
- const relationKey = relationName as string;
95
-
96
- if (meta.relationWrappers.has(relationKey)) {
97
- return meta.relationWrappers.get(relationKey) as HasManyCollection<unknown>;
98
- }
99
-
100
- const relation = meta.table.relations[relationKey];
101
- if (!relation) return undefined;
102
-
103
- const wrapper = instantiateWrapper(meta, relationKey, relation, owner, createEntity);
104
- if (!wrapper) return undefined;
105
-
106
- const proxied = proxifyRelationWrapper(wrapper as object);
107
- meta.relationWrappers.set(relationKey, proxied);
108
- return proxied as HasManyCollection<unknown>;
109
- };
110
-
111
- /**
112
- * Instantiates the appropriate relation wrapper based on relation type.
113
- * @param meta - The entity metadata
114
- * @param relationName - The relation name
115
- * @param relation - The relation definition
116
- * @param owner - The owner entity
117
- * @param createEntity - The entity factory for relation rows
118
- * @returns The relation wrapper or undefined
119
- */
120
- const instantiateWrapper = <TTable extends TableDef>(
121
- meta: EntityMeta<TTable>,
122
- relationName: string,
123
- relation: HasManyRelation | HasOneRelation | BelongsToRelation | BelongsToManyRelation,
124
- owner: unknown,
125
- createEntity: RelationEntityFactory
126
- ): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
127
- const metaBase = meta as unknown as EntityMeta<TableDef>;
128
- const loadCached = <T extends Map<string, unknown>>(factory: () => Promise<T>) =>
129
- relationLoaderCache(metaBase, relationName, factory);
130
- const resolveOptions = () => meta.lazyRelationOptions.get(relationName);
131
- switch (relation.type) {
132
- case RelationKinds.HasOne: {
133
- const hasOne = relation as HasOneRelation;
134
- const localKey = hasOne.localKey || findPrimaryKey(meta.table);
135
- const loader = () => loadCached(() =>
136
- loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne, resolveOptions())
137
- );
138
- return new DefaultHasOneReference(
139
- meta.ctx,
140
- metaBase,
141
- owner,
142
- relationName,
143
- hasOne,
144
- meta.table,
145
- loader,
146
- (row: Record<string, unknown>) => createEntity(hasOne.target, row),
147
- localKey
148
- );
149
- }
150
- case RelationKinds.HasMany: {
151
- const hasMany = relation as HasManyRelation;
152
- const localKey = hasMany.localKey || findPrimaryKey(meta.table);
153
- const loader = () => loadCached(() =>
154
- loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany, resolveOptions())
155
- );
156
- return new DefaultHasManyCollection(
157
- meta.ctx,
158
- metaBase,
159
- owner,
160
- relationName,
161
- hasMany,
162
- meta.table,
163
- loader,
164
- (row: Record<string, unknown>) => createEntity(relation.target, row),
165
- localKey
166
- );
167
- }
168
- case RelationKinds.BelongsTo: {
169
- const belongsTo = relation as BelongsToRelation;
170
- const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
171
- const loader = () => loadCached(() =>
172
- loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo, resolveOptions())
173
- );
174
- return new DefaultBelongsToReference(
175
- meta.ctx,
176
- metaBase,
177
- owner,
178
- relationName,
179
- belongsTo,
180
- meta.table,
181
- loader,
182
- (row: Record<string, unknown>) => createEntity(relation.target, row),
183
- targetKey
184
- );
185
- }
186
- case RelationKinds.BelongsToMany: {
187
- const many = relation as BelongsToManyRelation;
188
- const localKey = many.localKey || findPrimaryKey(meta.table);
189
- const loader = () => loadCached(() =>
190
- loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, resolveOptions())
191
- );
192
- return new DefaultManyToManyCollection(
193
- meta.ctx,
194
- metaBase,
195
- owner,
196
- relationName,
197
- many,
198
- meta.table,
199
- loader,
200
- (row: Record<string, unknown>) => createEntity(relation.target, row),
201
- localKey
202
- );
203
- }
204
- default:
205
- return undefined;
206
- }
207
- };
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
+ if (typeof prop === 'symbol') {
22
+ const value = Reflect.get(target, prop, target);
23
+ return typeof value === 'function' ? value.bind(target) : value;
24
+ }
25
+
26
+ if (prop in target) {
27
+ const value = Reflect.get(target, prop, target);
28
+ return typeof value === 'function' ? value.bind(target) : value;
29
+ }
30
+
31
+ const getItems = (target as { getItems?: () => unknown }).getItems;
32
+ if (typeof getItems === 'function') {
33
+ const items = getItems.call(target);
34
+ if (items && prop in (items as object)) {
35
+ const propName = prop as string;
36
+ const value = (items as Record<string, unknown>)[propName];
37
+ return typeof value === 'function' ? value.bind(items) : value;
38
+ }
39
+ }
40
+
41
+ const getRef = (target as { get?: () => unknown }).get;
42
+ if (typeof getRef === 'function') {
43
+ const current = getRef.call(target);
44
+ if (current && prop in (current as object)) {
45
+ const propName = prop as string;
46
+ const value = (current as Record<string, unknown>)[propName];
47
+ return typeof value === 'function' ? value.bind(current) : value;
48
+ }
49
+ }
50
+
51
+ return undefined;
52
+ },
53
+
54
+ set(target, prop, value, _receiver) {
55
+ if (typeof prop === 'symbol') {
56
+ return Reflect.set(target, prop, value, target);
57
+ }
58
+
59
+ if (prop in target) {
60
+ return Reflect.set(target, prop, value, target);
61
+ }
62
+
63
+ const getRef = (target as { get?: () => unknown }).get;
64
+ if (typeof getRef === 'function') {
65
+ const current = getRef.call(target);
66
+ if (current && typeof current === 'object') {
67
+ return Reflect.set(current as object, prop, value);
68
+ }
69
+ }
70
+
71
+ const getItems = (target as { getItems?: () => unknown }).getItems;
72
+ if (typeof getItems === 'function') {
73
+ const items = getItems.call(target);
74
+ return Reflect.set(items as object, prop, value);
75
+ }
76
+
77
+ return Reflect.set(target, prop, value, target);
78
+ }
79
+ });
80
+ };
81
+
82
+ /**
83
+ * Gets a relation wrapper for an entity.
84
+ * @param meta - The entity metadata
85
+ * @param relationName - The relation name
86
+ * @param owner - The owner entity
87
+ * @param createEntity - The entity factory for relation rows
88
+ * @returns The relation wrapper or undefined
89
+ */
90
+ export const getRelationWrapper = <TTable extends TableDef>(
91
+ meta: EntityMeta<TTable>,
92
+ relationName: RelationKey<TTable> | string,
93
+ owner: unknown,
94
+ createEntity: RelationEntityFactory
95
+ ): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
96
+ const relationKey = relationName as string;
97
+
98
+ if (meta.relationWrappers.has(relationKey)) {
99
+ return meta.relationWrappers.get(relationKey) as HasManyCollection<unknown>;
100
+ }
101
+
102
+ const relation = meta.table.relations[relationKey];
103
+ if (!relation) return undefined;
104
+
105
+ const wrapper = instantiateWrapper(meta, relationKey, relation, owner, createEntity);
106
+ if (!wrapper) return undefined;
107
+
108
+ const proxied = proxifyRelationWrapper(wrapper as object);
109
+ meta.relationWrappers.set(relationKey, proxied);
110
+ return proxied as HasManyCollection<unknown>;
111
+ };
112
+
113
+ /**
114
+ * Instantiates the appropriate relation wrapper based on relation type.
115
+ * @param meta - The entity metadata
116
+ * @param relationName - The relation name
117
+ * @param relation - The relation definition
118
+ * @param owner - The owner entity
119
+ * @param createEntity - The entity factory for relation rows
120
+ * @returns The relation wrapper or undefined
121
+ */
122
+ const instantiateWrapper = <TTable extends TableDef>(
123
+ meta: EntityMeta<TTable>,
124
+ relationName: string,
125
+ relation: HasManyRelation | HasOneRelation | BelongsToRelation | BelongsToManyRelation,
126
+ owner: unknown,
127
+ createEntity: RelationEntityFactory
128
+ ): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
129
+ const metaBase = meta as unknown as EntityMeta<TableDef>;
130
+ const loadCached = <T extends Map<string, unknown>>(factory: () => Promise<T>) =>
131
+ relationLoaderCache(metaBase, relationName, factory);
132
+ const resolveOptions = () => meta.lazyRelationOptions.get(relationName);
133
+ switch (relation.type) {
134
+ case RelationKinds.HasOne: {
135
+ const hasOne = relation as HasOneRelation;
136
+ const localKey = hasOne.localKey || findPrimaryKey(meta.table);
137
+ const loader = () => loadCached(() =>
138
+ loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne, resolveOptions())
139
+ );
140
+ return new DefaultHasOneReference(
141
+ meta.ctx,
142
+ metaBase,
143
+ owner,
144
+ relationName,
145
+ hasOne,
146
+ meta.table,
147
+ loader,
148
+ (row: Record<string, unknown>) => createEntity(hasOne.target, row),
149
+ localKey
150
+ );
151
+ }
152
+ case RelationKinds.HasMany: {
153
+ const hasMany = relation as HasManyRelation;
154
+ const localKey = hasMany.localKey || findPrimaryKey(meta.table);
155
+ const loader = () => loadCached(() =>
156
+ loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany, resolveOptions())
157
+ );
158
+ return new DefaultHasManyCollection(
159
+ meta.ctx,
160
+ metaBase,
161
+ owner,
162
+ relationName,
163
+ hasMany,
164
+ meta.table,
165
+ loader,
166
+ (row: Record<string, unknown>) => createEntity(relation.target, row),
167
+ localKey
168
+ );
169
+ }
170
+ case RelationKinds.BelongsTo: {
171
+ const belongsTo = relation as BelongsToRelation;
172
+ const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
173
+ const loader = () => loadCached(() =>
174
+ loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo, resolveOptions())
175
+ );
176
+ return new DefaultBelongsToReference(
177
+ meta.ctx,
178
+ metaBase,
179
+ owner,
180
+ relationName,
181
+ belongsTo,
182
+ meta.table,
183
+ loader,
184
+ (row: Record<string, unknown>) => createEntity(relation.target, row),
185
+ targetKey
186
+ );
187
+ }
188
+ case RelationKinds.BelongsToMany: {
189
+ const many = relation as BelongsToManyRelation;
190
+ const localKey = many.localKey || findPrimaryKey(meta.table);
191
+ const loader = () => loadCached(() =>
192
+ loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, resolveOptions())
193
+ );
194
+ return new DefaultManyToManyCollection(
195
+ meta.ctx,
196
+ metaBase,
197
+ owner,
198
+ relationName,
199
+ many,
200
+ meta.table,
201
+ loader,
202
+ (row: Record<string, unknown>) => createEntity(relation.target, row),
203
+ localKey
204
+ );
205
+ }
206
+ default:
207
+ return undefined;
208
+ }
209
+ };