1 | 'use strict';
|
2 | var isPlainObject = require('lodash/isPlainObject');
|
3 | var isFunction = require('lodash/isFunction');
|
4 | var _find = require('lodash/find');
|
5 | var _extend = require('lodash/extend');
|
6 | var _transform = require('lodash/transform');
|
7 | var _merge = require('lodash/merge');
|
8 | var _get = require('lodash/get');
|
9 | var _set = require('lodash/set');
|
10 | var Inflector = require('./inflector');
|
11 |
|
12 | module.exports = function (jsonapi, data, opts) {
|
13 | var alreadyIncluded = {};
|
14 |
|
15 | function isComplexType(obj) {
|
16 | return Array.isArray(obj) || isPlainObject(obj);
|
17 | }
|
18 |
|
19 | function getValueForRelationship(relationshipData, included) {
|
20 | if (opts && relationshipData && opts[relationshipData.type]) {
|
21 | var valueForRelationshipFct = opts[relationshipData.type]
|
22 | .valueForRelationship;
|
23 |
|
24 | return valueForRelationshipFct(relationshipData, included);
|
25 | } else {
|
26 | return included;
|
27 | }
|
28 | }
|
29 |
|
30 | function findIncluded(relationshipData, relationshipName, from) {
|
31 | return new Promise(function (resolve) {
|
32 | if (!jsonapi.included || !relationshipData) { return resolve(null); }
|
33 |
|
34 | var included = _find(jsonapi.included, {
|
35 | id: relationshipData.id,
|
36 | type: relationshipData.type
|
37 | });
|
38 |
|
39 | var path = [
|
40 | from.type,
|
41 | from.id,
|
42 | relationshipName,
|
43 | relationshipData.type,
|
44 | relationshipData.id,
|
45 | ]
|
46 |
|
47 |
|
48 |
|
49 | if (_get(alreadyIncluded, path, false)) {
|
50 | return resolve(null);
|
51 | } else {
|
52 | _merge(alreadyIncluded, _set({}, path, true));
|
53 | }
|
54 |
|
55 | if (included) {
|
56 | return Promise
|
57 | .all([extractAttributes(included), extractRelationships(included)])
|
58 | .then(function (results) {
|
59 | var attributes = results[0];
|
60 | var relationships = results[1];
|
61 | resolve(_extend(attributes, relationships));
|
62 | });
|
63 | } else {
|
64 | return resolve(null);
|
65 | }
|
66 | });
|
67 | }
|
68 |
|
69 | function keyForAttribute(attribute) {
|
70 | if (isPlainObject(attribute)) {
|
71 | return _transform(attribute, function (result, value, key) {
|
72 | if (isComplexType(value)) {
|
73 | result[keyForAttribute(key)] = keyForAttribute(value);
|
74 | } else {
|
75 | result[keyForAttribute(key)] = value;
|
76 | }
|
77 | });
|
78 | } else if (Array.isArray(attribute)) {
|
79 | return attribute.map(function (attr) {
|
80 | if (isComplexType(attr)) {
|
81 | return keyForAttribute(attr);
|
82 | } else {
|
83 | return attr;
|
84 | }
|
85 | });
|
86 | } else {
|
87 | if (isFunction(opts.keyForAttribute)) {
|
88 | return opts.keyForAttribute(attribute);
|
89 | } else {
|
90 | return Inflector.caserize(attribute, opts);
|
91 | }
|
92 | }
|
93 | }
|
94 |
|
95 | function extractAttributes(from) {
|
96 | var dest = keyForAttribute(from.attributes || {});
|
97 | if ('id' in from) { dest[opts.id || 'id'] = from.id; }
|
98 |
|
99 | if (opts.typeAsAttribute) {
|
100 | if ('type' in from) { dest.type = from.type; }
|
101 | }
|
102 | if ('meta' in from) { dest.meta = keyForAttribute(from.meta || {}) }
|
103 |
|
104 | return dest;
|
105 | }
|
106 |
|
107 | function extractRelationships(from) {
|
108 | if (!from.relationships) { return; }
|
109 |
|
110 | var dest = {};
|
111 |
|
112 | return Promise
|
113 | .all(Object.keys(from.relationships).map(function (key) {
|
114 | var relationship = from.relationships[key];
|
115 |
|
116 | if (relationship.data === null) {
|
117 | dest[keyForAttribute(key)] = null;
|
118 | } else if (Array.isArray(relationship.data)) {
|
119 | return Promise
|
120 | .all(relationship.data.map(function (relationshipData) {
|
121 | return extractIncludes(relationshipData, key, from);
|
122 | }))
|
123 | .then(function (includes) {
|
124 | if (includes) { dest[keyForAttribute(key)] = includes; }
|
125 | });
|
126 | } else {
|
127 | return extractIncludes(relationship.data, key, from)
|
128 | .then(function (include) {
|
129 | if (include) { dest[keyForAttribute(key)] = include; }
|
130 | });
|
131 | }
|
132 | }))
|
133 | .then(function() {
|
134 | return dest;
|
135 | });
|
136 | }
|
137 |
|
138 | function extractIncludes(relationshipData, relationshipName, from) {
|
139 | return findIncluded(relationshipData, relationshipName, from)
|
140 | .then(function (included) {
|
141 | var valueForRelationship = getValueForRelationship(relationshipData,
|
142 | included);
|
143 |
|
144 | if (valueForRelationship && isFunction(valueForRelationship.then)) {
|
145 | return valueForRelationship.then(function (value) {
|
146 | return value;
|
147 | });
|
148 | } else {
|
149 | return valueForRelationship;
|
150 | }
|
151 | });
|
152 | }
|
153 |
|
154 | this.perform = function () {
|
155 | return Promise
|
156 | .all([extractAttributes(data), extractRelationships(data)])
|
157 | .then(function (results) {
|
158 | var attributes = results[0];
|
159 | var relationships = results[1];
|
160 | var record = _extend(attributes, relationships);
|
161 |
|
162 |
|
163 | if (jsonapi.links) {
|
164 | record.links = jsonapi.links;
|
165 | }
|
166 |
|
167 |
|
168 |
|
169 | if (opts && opts.transform) {
|
170 | record = opts.transform(record);
|
171 | }
|
172 |
|
173 | return record;
|
174 | });
|
175 | };
|
176 | };
|