-
-
Notifications
You must be signed in to change notification settings - Fork 92
/
denormalize.ts
125 lines (106 loc) · 3.71 KB
/
denormalize.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
import * as ImmutableUtils from './schemas/ImmutableUtils';
import * as ArrayUtils from './schemas/Array';
import * as ObjectUtils from './schemas/Object';
import { Denormalize, DenormalizeNullable, Schema } from './types';
import Entity, { isEntity } from './entities/Entity';
import FlatEntity from './entities/FlatEntity';
const unvisitEntity = (
id: any,
schema: any,
unvisit: any,
getEntity: any,
cache: Record<string, any>,
): [any, boolean] => {
const entity = getEntity(id, schema);
if (typeof entity !== 'object' || entity === null) {
return [entity, false];
}
if (!cache[schema.key]) {
cache[schema.key] = {};
}
let found = true;
if (!cache[schema.key][id]) {
// Ensure we don't mutate it non-immutable objects
const entityCopy =
ImmutableUtils.isImmutable(entity) || entity instanceof FlatEntity
? entity
: schema.fromJS(entity);
// Need to set this first so that if it is referenced further within the
// denormalization the reference will already exist.
cache[schema.key][id] = entityCopy;
[cache[schema.key][id], found] = schema.denormalize(entityCopy, unvisit);
}
return [cache[schema.key][id], found];
};
const getUnvisit = (entities: Record<string, any>) => {
const cache = {};
const getEntity = getEntities(entities);
return [
function unvisit(input: any, schema: any): [any, boolean] {
if (!schema) return [input, true];
if (!schema.denormalize || typeof schema.denormalize !== 'function') {
if (typeof schema === 'function') {
if (input instanceof schema) return [input, true];
return [new schema(input), true];
} else if (typeof schema === 'object') {
const method = Array.isArray(schema)
? ArrayUtils.denormalize
: ObjectUtils.denormalize;
return method(schema, input, unvisit);
}
}
// null is considered intentional, thus always 'found' as true
if (input === null) {
return [input, true];
}
if (isEntity(schema)) {
// unvisitEntity just can't handle undefined
if (input === undefined) {
return [input, false];
}
return unvisitEntity(input, schema, unvisit, getEntity, cache);
}
if (typeof schema.denormalize === 'function') {
return schema.denormalize(input, unvisit);
}
return [input, true];
},
cache,
] as const;
};
const getEntities = (entities: Record<string, any>) => {
const isImmutable = ImmutableUtils.isImmutable(entities);
return (entityOrId: Record<string, any> | string, schema: typeof Entity) => {
const schemaKey = schema.key;
if (typeof entityOrId === 'object') {
return entityOrId;
}
if (isImmutable) {
return entities.getIn([schemaKey, entityOrId]);
}
return entities[schemaKey] && entities[schemaKey][entityOrId];
};
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const denormalize = <S extends Schema>(
input: any,
schema: S,
entities: any,
):
| [Denormalize<S>, true, Record<string, Record<string, any>>]
| [DenormalizeNullable<S>, false, Record<string, Record<string, any>>] => {
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'production' && schema === undefined)
throw new Error('schema needed');
if (typeof input !== 'undefined') {
const [unvisit, cache] = getUnvisit(entities);
return [...unvisit(input, schema), cache] as any;
}
return [undefined, false, {}] as any;
};
export const denormalizeSimple = <S extends Schema>(
input: any,
schema: S,
entities: any,
): [Denormalize<S>, true] | [DenormalizeNullable<S>, false] =>
denormalize(input, schema, entities).slice(0, 2) as any;