-
-
Notifications
You must be signed in to change notification settings - Fork 496
/
EntityAssigner.ts
150 lines (120 loc) · 5.65 KB
/
EntityAssigner.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
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
import { inspect } from 'util';
import { Collection } from './Collection';
import { SCALAR_TYPES } from './EntityFactory';
import { EntityManager } from '../EntityManager';
import { EntityData, EntityMetadata, EntityProperty, AnyEntity } from '../typings';
import { Utils } from '../utils';
import { ReferenceType } from './enums';
import { Reference } from './Reference';
import { wrap } from './EntityHelper';
export class EntityAssigner {
static assign<T extends AnyEntity<T>>(entity: T, data: EntityData<T>, options?: AssignOptions): T;
static assign<T extends AnyEntity<T>>(entity: T, data: EntityData<T>, onlyProperties?: boolean): T;
static assign<T extends AnyEntity<T>>(entity: T, data: EntityData<T>, onlyProperties: AssignOptions | boolean = false): T {
const options = (typeof onlyProperties === 'boolean' ? { onlyProperties } : onlyProperties);
const em = options.em || wrap(entity).__em;
const meta = wrap(entity).__internal.metadata.get(entity.constructor.name);
const root = Utils.getRootEntity(wrap(entity).__internal.metadata, meta);
const validator = wrap(entity).__internal.validator;
const platform = wrap(entity).__internal.platform;
const props = meta.properties;
Object.keys(data).forEach(prop => {
if (options.onlyProperties && !(prop in props)) {
return;
}
if (props[prop]?.inherited || root.discriminatorColumn === prop) {
return;
}
let value = data[prop as keyof EntityData<T>];
if (props[prop] && props[prop].customType && !Utils.isEntity(data)) {
value = props[prop].customType.convertToJSValue(value, platform);
}
if (props[prop] && [ReferenceType.MANY_TO_ONE, ReferenceType.ONE_TO_ONE].includes(props[prop].reference) && Utils.isDefined(value, true) && EntityAssigner.validateEM(em)) {
return EntityAssigner.assignReference<T>(entity, value, props[prop], em!);
}
if (props[prop] && Utils.isCollection(entity[prop as keyof T], props[prop]) && Array.isArray(value) && EntityAssigner.validateEM(em)) {
return EntityAssigner.assignCollection<T>(entity, entity[prop as keyof T] as unknown as Collection<AnyEntity>, value, props[prop], em!);
}
if (props[prop] && props[prop].reference === ReferenceType.SCALAR && SCALAR_TYPES.includes(props[prop].type) && (props[prop].setter || !props[prop].getter)) {
return entity[prop as keyof T] = validator.validateProperty(props[prop], value, entity);
}
if (options.mergeObjects && Utils.isObject(value)) {
Utils.merge(entity[prop as keyof T], value);
} else if (!props[prop] || !props[prop].getter || props[prop].setter) {
entity[prop as keyof T] = value;
}
});
return entity;
}
/**
* auto-wire 1:1 inverse side with owner as in no-sql drivers it can't be joined
* also makes sure the link is bidirectional when creating new entities from nested structures
* @internal
*/
static autoWireOneToOne<T extends AnyEntity<T>>(prop: EntityProperty, entity: T): void {
if (prop.reference !== ReferenceType.ONE_TO_ONE) {
return;
}
const meta2 = entity[prop.name].__meta as EntityMetadata;
const prop2 = meta2.properties[prop.inversedBy || prop.mappedBy];
if (prop2 && !entity[prop.name][prop2.name]) {
if (entity[prop.name] instanceof Reference) {
entity[prop.name].unwrap()[prop2.name] = Utils.wrapReference(entity, prop2);
} else {
entity[prop.name][prop2.name] = Utils.wrapReference(entity, prop2);
}
}
}
private static validateEM(em?: EntityManager): boolean {
if (!em) {
throw new Error(`To use assign() on not managed entities, explicitly provide EM instance: wrap(entity).assign(data, { em: orm.em })`);
}
return true;
}
private static assignReference<T extends AnyEntity<T>>(entity: T, value: any, prop: EntityProperty, em: EntityManager): void {
let valid = false;
if (Utils.isEntity(value, true)) {
entity[prop.name] = value;
valid = true;
} else if (Utils.isPrimaryKey(value, true)) {
entity[prop.name] = Utils.wrapReference(em.getReference<T>(prop.type, value), prop);
valid = true;
} else if (Utils.isObject<T[keyof T]>(value)) {
entity[prop.name] = Utils.wrapReference(em.create(prop.type, value), prop);
valid = true;
}
if (!valid) {
const name = entity.constructor.name;
throw new Error(`Invalid reference value provided for '${name}.${prop.name}' in ${name}.assign(): ${JSON.stringify(value)}`);
}
EntityAssigner.autoWireOneToOne(prop, entity);
}
private static assignCollection<T extends AnyEntity<T>, U extends AnyEntity<U> = AnyEntity>(entity: T, collection: Collection<U>, value: any[], prop: EntityProperty, em: EntityManager): void {
const invalid: any[] = [];
const items = value.map((item: any) => this.createCollectionItem<U>(item, em, prop, invalid));
if (invalid.length > 0) {
const name = entity.constructor.name;
throw new Error(`Invalid collection values provided for '${name}.${prop.name}' in ${name}.assign(): ${inspect(invalid)}`);
}
collection.hydrate(items, true, false);
collection.setDirty();
}
private static createCollectionItem<T extends AnyEntity<T>>(item: any, em: EntityManager, prop: EntityProperty, invalid: any[]): T {
if (Utils.isEntity<T>(item)) {
return item;
}
if (Utils.isPrimaryKey(item)) {
return em.getReference(prop.type, item);
}
if (Utils.isObject<T>(item)) {
return em.create<T>(prop.type, item);
}
invalid.push(item);
return item;
}
}
export interface AssignOptions {
onlyProperties?: boolean;
mergeObjects?: boolean;
em?: EntityManager;
}