metal-orm 1.0.104 → 1.0.106
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 +81 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -6
- package/dist/index.d.ts +8 -6
- package/dist/index.js +81 -103
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/orm/entity-relations.ts +207 -209
- package/src/orm/entity.ts +151 -160
- package/src/orm/relations/belongs-to.ts +126 -130
- package/src/orm/relations/has-many.ts +186 -190
- package/src/orm/relations/has-one.ts +158 -162
- package/src/orm/relations/many-to-many.ts +224 -228
package/src/orm/entity.ts
CHANGED
|
@@ -1,160 +1,151 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { EntityInstance } from '../schema/types.js';
|
|
3
|
-
import type { EntityContext, PrimaryKey } from './entity-context.js';
|
|
4
|
-
import { ENTITY_META, EntityMeta, RelationKey } from './entity-meta.js';
|
|
5
|
-
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
6
|
-
import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
7
|
-
import { populateHydrationCache } from './entity-hydration.js';
|
|
8
|
-
import { getRelationWrapper, RelationEntityFactory } from './entity-relations.js';
|
|
9
|
-
|
|
10
|
-
export { relationLoaderCache } from './entity-relation-cache.js';
|
|
11
|
-
|
|
12
|
-
const isRelationWrapperLoaded = (value: unknown): boolean => {
|
|
13
|
-
if (!value || typeof value !== 'object') return false;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
ctx,
|
|
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
|
-
if (typeof prop === 'string' && table.
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
|
|
153
|
-
if (pkValue !== undefined && pkValue !== null) {
|
|
154
|
-
ctx.trackManaged(table, pkValue as PrimaryKey, entity);
|
|
155
|
-
} else {
|
|
156
|
-
ctx.trackNew(table, entity);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return entity as TResult;
|
|
160
|
-
};
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { EntityInstance } from '../schema/types.js';
|
|
3
|
+
import type { EntityContext, PrimaryKey } from './entity-context.js';
|
|
4
|
+
import { ENTITY_META, EntityMeta, RelationKey } from './entity-meta.js';
|
|
5
|
+
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
6
|
+
import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
7
|
+
import { populateHydrationCache } from './entity-hydration.js';
|
|
8
|
+
import { getRelationWrapper, RelationEntityFactory } from './entity-relations.js';
|
|
9
|
+
|
|
10
|
+
export { relationLoaderCache } from './entity-relation-cache.js';
|
|
11
|
+
|
|
12
|
+
const isRelationWrapperLoaded = (value: unknown): boolean => {
|
|
13
|
+
if (!value || typeof value !== 'object') return false;
|
|
14
|
+
return Boolean((value as { loaded?: boolean }).loaded);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type JsonSource<TTable extends TableDef> = EntityInstance<TTable> & Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates an entity proxy with lazy loading capabilities.
|
|
21
|
+
* @template TTable - The table type
|
|
22
|
+
* @template TLazy - The lazy relation keys
|
|
23
|
+
* @param ctx - The entity context
|
|
24
|
+
* @param table - The table definition
|
|
25
|
+
* @param row - The database row
|
|
26
|
+
* @param lazyRelations - Optional lazy relations
|
|
27
|
+
* @returns The entity instance
|
|
28
|
+
*/
|
|
29
|
+
export const createEntityProxy = <
|
|
30
|
+
TTable extends TableDef,
|
|
31
|
+
TLazy extends RelationKey<TTable> = RelationKey<TTable>
|
|
32
|
+
>(
|
|
33
|
+
ctx: EntityContext,
|
|
34
|
+
table: TTable,
|
|
35
|
+
row: Record<string, unknown>,
|
|
36
|
+
lazyRelations: TLazy[] = [] as TLazy[],
|
|
37
|
+
lazyRelationOptions: Map<string, RelationIncludeOptions> = new Map()
|
|
38
|
+
): EntityInstance<TTable> => {
|
|
39
|
+
const target: Record<string, unknown> = { ...row };
|
|
40
|
+
const meta: EntityMeta<TTable> = {
|
|
41
|
+
ctx,
|
|
42
|
+
table,
|
|
43
|
+
lazyRelations: [...lazyRelations],
|
|
44
|
+
lazyRelationOptions: new Map(lazyRelationOptions),
|
|
45
|
+
relationCache: new Map(),
|
|
46
|
+
relationHydration: new Map(),
|
|
47
|
+
relationWrappers: new Map()
|
|
48
|
+
};
|
|
49
|
+
const createRelationEntity: RelationEntityFactory = (relationTable, relationRow) =>
|
|
50
|
+
createEntityFromRow(meta.ctx, relationTable, relationRow);
|
|
51
|
+
|
|
52
|
+
const buildJson = (self: JsonSource<TTable>): Record<string, unknown> => {
|
|
53
|
+
const json: Record<string, unknown> = {};
|
|
54
|
+
for (const key of Object.keys(target)) {
|
|
55
|
+
json[key] = self[key];
|
|
56
|
+
}
|
|
57
|
+
for (const [relationName, wrapper] of meta.relationWrappers) {
|
|
58
|
+
if (Object.prototype.hasOwnProperty.call(json, relationName)) continue;
|
|
59
|
+
if (isRelationWrapperLoaded(wrapper)) {
|
|
60
|
+
json[relationName] = wrapper;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return json;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
Object.defineProperty(target, ENTITY_META, {
|
|
67
|
+
value: meta,
|
|
68
|
+
enumerable: false,
|
|
69
|
+
writable: false
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const handler: ProxyHandler<object> = {
|
|
73
|
+
get(targetObj, prop, receiver) {
|
|
74
|
+
if (prop === ENTITY_META) {
|
|
75
|
+
return meta;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (prop === '$load') {
|
|
79
|
+
return async (relationName: RelationKey<TTable>) => {
|
|
80
|
+
const wrapper = getRelationWrapper(meta, relationName, receiver, createRelationEntity);
|
|
81
|
+
if (wrapper && typeof wrapper.load === 'function') {
|
|
82
|
+
return wrapper.load();
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (prop === 'toJSON') {
|
|
89
|
+
if (prop in targetObj) {
|
|
90
|
+
return Reflect.get(targetObj, prop, receiver);
|
|
91
|
+
}
|
|
92
|
+
return () => buildJson(receiver as JsonSource<TTable>);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (typeof prop === 'string' && table.relations[prop]) {
|
|
96
|
+
return getRelationWrapper(meta, prop as RelationKey<TTable>, receiver, createRelationEntity);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return Reflect.get(targetObj, prop, receiver);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
set(targetObj, prop, value, receiver) {
|
|
103
|
+
const result = Reflect.set(targetObj, prop, value, receiver);
|
|
104
|
+
if (typeof prop === 'string' && table.columns[prop]) {
|
|
105
|
+
ctx.markDirty(receiver);
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const proxy = new Proxy(target, handler) as EntityInstance<TTable>;
|
|
112
|
+
populateHydrationCache(proxy, row, meta);
|
|
113
|
+
return proxy;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Creates an entity instance from a database row.
|
|
118
|
+
* @template TTable - The table type
|
|
119
|
+
* @template TResult - The result type
|
|
120
|
+
* @param ctx - The entity context
|
|
121
|
+
* @param table - The table definition
|
|
122
|
+
* @param row - The database row
|
|
123
|
+
* @param lazyRelations - Optional lazy relations
|
|
124
|
+
* @returns The entity instance
|
|
125
|
+
*/
|
|
126
|
+
export const createEntityFromRow = <
|
|
127
|
+
TTable extends TableDef,
|
|
128
|
+
TResult extends EntityInstance<TTable> = EntityInstance<TTable>
|
|
129
|
+
>(
|
|
130
|
+
ctx: EntityContext,
|
|
131
|
+
table: TTable,
|
|
132
|
+
row: Record<string, unknown>,
|
|
133
|
+
lazyRelations: RelationKey<TTable>[] = [],
|
|
134
|
+
lazyRelationOptions: Map<string, RelationIncludeOptions> = new Map()
|
|
135
|
+
): TResult => {
|
|
136
|
+
const pkName = findPrimaryKey(table);
|
|
137
|
+
const pkValue = row[pkName];
|
|
138
|
+
if (pkValue !== undefined && pkValue !== null) {
|
|
139
|
+
const tracked = ctx.getEntity(table, pkValue as PrimaryKey);
|
|
140
|
+
if (tracked) return tracked as TResult;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
|
|
144
|
+
if (pkValue !== undefined && pkValue !== null) {
|
|
145
|
+
ctx.trackManaged(table, pkValue as PrimaryKey, entity);
|
|
146
|
+
} else {
|
|
147
|
+
ctx.trackNew(table, entity);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return entity as TResult;
|
|
151
|
+
};
|
|
@@ -1,130 +1,126 @@
|
|
|
1
|
-
import { BelongsToReferenceApi } from '../../schema/types.js';
|
|
2
|
-
import { EntityContext } from '../entity-context.js';
|
|
3
|
-
import { RelationKey } from '../runtime-types.js';
|
|
4
|
-
import { BelongsToRelation } from '../../schema/relation.js';
|
|
5
|
-
import { TableDef } from '../../schema/table.js';
|
|
6
|
-
import { EntityMeta, getHydrationRecord, hasEntityMeta } from '../entity-meta.js';
|
|
7
|
-
|
|
8
|
-
type Rows = Record<string, unknown>;
|
|
9
|
-
|
|
10
|
-
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
11
|
-
|
|
12
|
-
const hideInternal = (obj: object, keys: string[]): void => {
|
|
13
|
-
for (const key of keys) {
|
|
14
|
-
Object.defineProperty(obj, key, {
|
|
15
|
-
value: obj[key],
|
|
16
|
-
writable: false,
|
|
17
|
-
configurable: false,
|
|
18
|
-
enumerable: false
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Default implementation of a belongs-to reference.
|
|
25
|
-
* Manages a reference to a parent entity from a child entity through a foreign key.
|
|
26
|
-
*
|
|
27
|
-
* @template TParent The type of the parent entity.
|
|
28
|
-
*/
|
|
29
|
-
export class DefaultBelongsToReference<TParent extends object> implements BelongsToReferenceApi<TParent> {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @param ctx The entity context for tracking changes.
|
|
35
|
-
* @param meta Metadata for the child entity.
|
|
36
|
-
* @param root The child entity instance (carrying the foreign key).
|
|
37
|
-
* @param relationName The name of the relation.
|
|
38
|
-
* @param relation Relation definition.
|
|
39
|
-
* @param rootTable Table definition of the child entity.
|
|
40
|
-
* @param loader Function to load the parent entity.
|
|
41
|
-
* @param createEntity Function to create entity instances from rows.
|
|
42
|
-
* @param targetKey The primary key of the target (parent) table.
|
|
43
|
-
*/
|
|
44
|
-
constructor(
|
|
45
|
-
private readonly ctx: EntityContext,
|
|
46
|
-
private readonly meta: EntityMeta<TableDef>,
|
|
47
|
-
private readonly root: unknown,
|
|
48
|
-
private readonly relationName: string,
|
|
49
|
-
private readonly relation: BelongsToRelation,
|
|
50
|
-
private readonly rootTable: TableDef,
|
|
51
|
-
private readonly loader: () => Promise<Map<string, Rows>>,
|
|
52
|
-
private readonly createEntity: (row: Record<string, unknown>) => TParent,
|
|
53
|
-
private readonly targetKey: string
|
|
54
|
-
) {
|
|
55
|
-
hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'targetKey']);
|
|
56
|
-
this.populateFromHydrationCache();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async load(): Promise<TParent | null> {
|
|
60
|
-
if (this
|
|
61
|
-
const map = await this.loader();
|
|
62
|
-
const fkValue = (this.root as Record<string, unknown>)[this.relation.foreignKey];
|
|
63
|
-
if (fkValue === null || fkValue === undefined) {
|
|
64
|
-
this
|
|
65
|
-
} else {
|
|
66
|
-
const row = map.get(toKey(fkValue));
|
|
67
|
-
this
|
|
68
|
-
}
|
|
69
|
-
this
|
|
70
|
-
return this
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
get(): TParent | null {
|
|
74
|
-
return this
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
set(data: Partial<TParent> | TParent | null): TParent | null {
|
|
78
|
-
if (data === null) {
|
|
79
|
-
const previous = this
|
|
80
|
-
(this.root as Record<string, unknown>)[this.relation.foreignKey] = null;
|
|
81
|
-
this
|
|
82
|
-
this.ctx.registerRelationChange(
|
|
83
|
-
this.root,
|
|
84
|
-
this.relationKey,
|
|
85
|
-
this.rootTable,
|
|
86
|
-
this.relationName,
|
|
87
|
-
this.relation,
|
|
88
|
-
{ kind: 'remove', entity: previous }
|
|
89
|
-
);
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const entity = hasEntityMeta(data) ? (data as TParent) : this.createEntity(data as Record<string, unknown>);
|
|
94
|
-
const pkValue = (entity as Record<string, unknown>)[this.targetKey];
|
|
95
|
-
if (pkValue !== undefined) {
|
|
96
|
-
(this.root as Record<string, unknown>)[this.relation.foreignKey] = pkValue;
|
|
97
|
-
}
|
|
98
|
-
this
|
|
99
|
-
this.ctx.registerRelationChange(
|
|
100
|
-
this.root,
|
|
101
|
-
this.relationKey,
|
|
102
|
-
this.rootTable,
|
|
103
|
-
this.relationName,
|
|
104
|
-
this.relation,
|
|
105
|
-
{ kind: 'attach', entity }
|
|
106
|
-
);
|
|
107
|
-
return entity;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return this
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
private
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
toJSON(): TParent | null {
|
|
128
|
-
return this.#current;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
1
|
+
import { BelongsToReferenceApi } from '../../schema/types.js';
|
|
2
|
+
import { EntityContext } from '../entity-context.js';
|
|
3
|
+
import { RelationKey } from '../runtime-types.js';
|
|
4
|
+
import { BelongsToRelation } from '../../schema/relation.js';
|
|
5
|
+
import { TableDef } from '../../schema/table.js';
|
|
6
|
+
import { EntityMeta, getHydrationRecord, hasEntityMeta } from '../entity-meta.js';
|
|
7
|
+
|
|
8
|
+
type Rows = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
11
|
+
|
|
12
|
+
const hideInternal = (obj: object, keys: string[]): void => {
|
|
13
|
+
for (const key of keys) {
|
|
14
|
+
Object.defineProperty(obj, key, {
|
|
15
|
+
value: obj[key],
|
|
16
|
+
writable: false,
|
|
17
|
+
configurable: false,
|
|
18
|
+
enumerable: false
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default implementation of a belongs-to reference.
|
|
25
|
+
* Manages a reference to a parent entity from a child entity through a foreign key.
|
|
26
|
+
*
|
|
27
|
+
* @template TParent The type of the parent entity.
|
|
28
|
+
*/
|
|
29
|
+
export class DefaultBelongsToReference<TParent extends object> implements BelongsToReferenceApi<TParent> {
|
|
30
|
+
private loaded = false;
|
|
31
|
+
private current: TParent | null = null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param ctx The entity context for tracking changes.
|
|
35
|
+
* @param meta Metadata for the child entity.
|
|
36
|
+
* @param root The child entity instance (carrying the foreign key).
|
|
37
|
+
* @param relationName The name of the relation.
|
|
38
|
+
* @param relation Relation definition.
|
|
39
|
+
* @param rootTable Table definition of the child entity.
|
|
40
|
+
* @param loader Function to load the parent entity.
|
|
41
|
+
* @param createEntity Function to create entity instances from rows.
|
|
42
|
+
* @param targetKey The primary key of the target (parent) table.
|
|
43
|
+
*/
|
|
44
|
+
constructor(
|
|
45
|
+
private readonly ctx: EntityContext,
|
|
46
|
+
private readonly meta: EntityMeta<TableDef>,
|
|
47
|
+
private readonly root: unknown,
|
|
48
|
+
private readonly relationName: string,
|
|
49
|
+
private readonly relation: BelongsToRelation,
|
|
50
|
+
private readonly rootTable: TableDef,
|
|
51
|
+
private readonly loader: () => Promise<Map<string, Rows>>,
|
|
52
|
+
private readonly createEntity: (row: Record<string, unknown>) => TParent,
|
|
53
|
+
private readonly targetKey: string
|
|
54
|
+
) {
|
|
55
|
+
hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'targetKey']);
|
|
56
|
+
this.populateFromHydrationCache();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async load(): Promise<TParent | null> {
|
|
60
|
+
if (this.loaded) return this.current;
|
|
61
|
+
const map = await this.loader();
|
|
62
|
+
const fkValue = (this.root as Record<string, unknown>)[this.relation.foreignKey];
|
|
63
|
+
if (fkValue === null || fkValue === undefined) {
|
|
64
|
+
this.current = null;
|
|
65
|
+
} else {
|
|
66
|
+
const row = map.get(toKey(fkValue));
|
|
67
|
+
this.current = row ? this.createEntity(row) : null;
|
|
68
|
+
}
|
|
69
|
+
this.loaded = true;
|
|
70
|
+
return this.current;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get(): TParent | null {
|
|
74
|
+
return this.current;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
set(data: Partial<TParent> | TParent | null): TParent | null {
|
|
78
|
+
if (data === null) {
|
|
79
|
+
const previous = this.current;
|
|
80
|
+
(this.root as Record<string, unknown>)[this.relation.foreignKey] = null;
|
|
81
|
+
this.current = null;
|
|
82
|
+
this.ctx.registerRelationChange(
|
|
83
|
+
this.root,
|
|
84
|
+
this.relationKey,
|
|
85
|
+
this.rootTable,
|
|
86
|
+
this.relationName,
|
|
87
|
+
this.relation,
|
|
88
|
+
{ kind: 'remove', entity: previous }
|
|
89
|
+
);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const entity = hasEntityMeta(data) ? (data as TParent) : this.createEntity(data as Record<string, unknown>);
|
|
94
|
+
const pkValue = (entity as Record<string, unknown>)[this.targetKey];
|
|
95
|
+
if (pkValue !== undefined) {
|
|
96
|
+
(this.root as Record<string, unknown>)[this.relation.foreignKey] = pkValue;
|
|
97
|
+
}
|
|
98
|
+
this.current = entity;
|
|
99
|
+
this.ctx.registerRelationChange(
|
|
100
|
+
this.root,
|
|
101
|
+
this.relationKey,
|
|
102
|
+
this.rootTable,
|
|
103
|
+
this.relationName,
|
|
104
|
+
this.relation,
|
|
105
|
+
{ kind: 'attach', entity }
|
|
106
|
+
);
|
|
107
|
+
return entity;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private get relationKey(): RelationKey {
|
|
111
|
+
return `${this.rootTable.name}.${this.relationName}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private populateFromHydrationCache(): void {
|
|
115
|
+
const fkValue = (this.root as Record<string, unknown>)[this.relation.foreignKey];
|
|
116
|
+
if (fkValue === undefined || fkValue === null) return;
|
|
117
|
+
const row = getHydrationRecord(this.meta, this.relationName, fkValue);
|
|
118
|
+
if (!row) return;
|
|
119
|
+
this.current = this.createEntity(row);
|
|
120
|
+
this.loaded = true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
toJSON(): TParent | null {
|
|
124
|
+
return this.current;
|
|
125
|
+
}
|
|
126
|
+
}
|