This repository has been archived by the owner on Jun 17, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
151 lines (127 loc) · 4.34 KB
/
index.js
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
151
// @flow
import _ from 'lodash'
type Schemas = {
[root: string]: {
__keys: Array<string>
} & {[override: string]: any | Schemas}
}
type Config = {
schemas: Schemas,
unions: Array<string>,
inferReference: Function
}
type Union = {
id: string,
schema: string
}
type Reference = {
id: string,
schema: string
}
export default (config: Config) => (data: Object, limiter: string | Array<string>) => {
const {schemas, unions, inferReference} = config
/* Given a property name, decide whether or not
* it contains a relationship to another entity.
* */
const findRelationship = (property: string, currentRoot: string): ?string => {
let reference = null
/* First check for overrides on the current
* schema. The current schema is inferred
* from the provided root.
* */
_.forEach(schemas[currentRoot], (root, override) => {
if (override == property) reference = root
})
/* Check to see if the property is a union. If
* a match is found, return a pseudo root.
* */
if (!reference) {
_.forEach(unions, union => {
if (union == property) reference = union
})
}
if (!reference) {
_.forEach(schemas, ({__keys}, root: string) => {
_.forEach(__keys, validKey => {
if (validKey == property) {
reference = root
}
})
})
}
return reference
}
/* Loop over an entities properties and check if any
* of them map to a root. If a mapping is found,
* replace the property with a getter.
* */
const applyGetters = (root: string) => (entity: Object) => {
const parsedEntity = {...entity}
_.forEach(entity, (value, property) => {
const relationship = findRelationship(property, root)
if (relationship) {
/* Construct a reference from a value. This is used
* to properly understand union relationships.
* */
const constructReference = (value: string | Union): Reference => {
if (typeof value === 'object') {
if (typeof inferReference === 'function') return inferReference(value)
return value
}
return {schema: relationship, id: value}
}
/* Replace a property with a getter. When called
* the getter will apply the next layer of getters
* */
Object.defineProperty(parsedEntity, property, {
get() {
if (Array.isArray(value)) {
return _.map(value, (entityId) => {
const {id, schema} = constructReference(entityId)
const targetEntities = data[schema]
if (targetEntities && targetEntities[id]) return applyGetters(schema)(targetEntities[id])
return entityId
})
}
const {id, schema} = constructReference(value)
const targetEntities = data[schema]
if (targetEntities && targetEntities[id]) return applyGetters(schema)(targetEntities[id])
return value
}
})
} else {
/* If the entity property is an object, continue
* checking nested properties for any relationships.
* */
if (!Array.isArray(value) && typeof value === 'object')
parsedEntity[property] = applyGetters(root)(value)
}
})
return parsedEntity
}
/* If a limiter has been provided, only parse the
* roots present in the limiter and merge the
* remaining raw data alongside it.
* */
if (limiter) {
if (Array.isArray(limiter)) {
const parsed = _.mapValues(_.pick(data, limiter), (entities, root) =>
_.mapValues(entities, applyGetters(root))
)
return {
...data,
...parsed
}
}
return {
...data,
[limiter]: _.mapValues(data[limiter], applyGetters(limiter))
}
}
/* Parse the top level of all entities to replace
* all relationships with getters.
* */
return _.mapValues(data, (entities, root) =>
_.mapValues(entities, applyGetters(root))
)
}