-
Notifications
You must be signed in to change notification settings - Fork 7
/
node.js
241 lines (210 loc) · 6.93 KB
/
node.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
'use strict';
var cuid = require('cuid');
var {
calculateGSIK,
prefixTenant,
parseItem,
atob,
btoa
} = require('./modules/utils.js');
var { num2hex } = require('hex-2-num');
module.exports = nodeFactory;
// ---
/**
* Returns a function that can interact with Nodes stored on a DynamoDB table.
* @param {object} config - Main configuration object.
* @property {object} documentClient - DynamoDB Document Client driver.
* @property {string} [table] - DynamoDB table name.
* @property {number} [maxGSIK] - Max GSIK value.
* @property {string} [tenant=''] - Tenant identifier.
* @return {function} Node function.
*/
function nodeFactory(config = {}) {
var { documentClient, table, maxGSIK, tenant = '' } = config;
var pTenant = prefixTenant(tenant);
var getNodeTypes = require('./getNodeTypes.js')(config);
var _query = require('./query.js')(config);
return node;
// ---
/**
* Returns an object with methods capable to interact with the configured Node
* @param {object} options - Node options object.
* @property {string} [id] - Node ID.
* @property {string} [type] - Node type
* @return {object} Object with functions to interact with the configured Node
*/
function node(options = {}) {
var { id, type } = options;
if (id !== undefined && typeof id !== 'string')
throw new Error('Node ID is not a string');
var api = {
create,
get,
edges: items('edge'),
props: items('prop'),
query,
destroy
};
return api;
// ---
/**
* Attempts to run a query against the DynamoDB table.
* @param {object} attributes - Query configuration object.
* @property {object} [where] - Where condition object.
* @property {object} [filter] - Filter condition object.
* @return {Promise} DynamoDB query promise.
*/
function query(attributes = {}) {
var { where = {}, filter = {} } = attributes;
var { data } = where;
var { type } = filter;
// Switch where.data for filter.type if defined.
if (type !== undefined && data !== undefined) {
attributes.where = { type };
attributes.filter = { data };
}
return _query(Object.assign({}, attributes, { node: id }));
}
/**
* Attempts to destroy a Node item from DynamoDB.
*/
function destroy() {
if (id !== undefined && type !== undefined)
return documentClient
.delete({
TableName: table,
Key: {
Node: pTenant(id),
Type: type
}
})
.promise();
return Promise.resolve();
}
/**
* Attempts to get one or more Node items from DynamoDB.
* @param {string[]} types - List of Node types.
* @return {Promise} A DynamoDB query to get one or more Node items.
*/
function get(types) {
if (id === undefined) throw new Error('Node is undefined');
if (type === undefined && types === undefined){
return documentClient
.query({
TableName: table,
KeyConditionExpression: 'Node = :Node',
FilterExpression: 'attribute_exists(Target) AND Target = :Node',
ExpressionAttributeValues: {':Node': pTenant(id)}
})
.promise()
.then(({Items}) => Items.length && parseItem(Items[0]));
}
if (type !== undefined && Array.isArray(types) === true)
types = [type].concat(types);
var promise =
Array.isArray(types) === true
? getNodeTypes({ node: pTenant(id), types })
: documentClient
.get({
TableName: table,
Key: {
Node: pTenant(id),
Type: type
}
})
.promise()
.then(parseItem);
return promise;
}
/**
* Helper function to build other function that can interact with a specific
* type of Node item ("edge" or "prop").
* @param {"edge"|"prop"} itemType - Node item type.
* @return {function} Pre-configured function to interact with Node items of
* type `itemType`.
*/
function items(itemType) {
return function(attributes = {}) {
if (id === undefined) throw new Error('Node ID is undefined');
var { limit, offset } = attributes;
var params = {
TableName: table,
ExpressionAttributeNames: {
'#Node': 'Node',
'#Target': 'Target'
},
ExpressionAttributeValues: {
':Node': pTenant(id)
},
KeyConditionExpression: '#Node = :Node'
};
if (itemType === 'edge')
params.FilterExpression =
'attribute_exists(#Target) AND #Target <> :Node';
else if (itemType === 'prop')
params.FilterExpression = 'attribute_not_exists(#Target)';
if (limit > 0) params.Limit = limit;
if (typeof offset === 'string')
params.ExclusiveStartKey = {
Node: id,
Type: atob(offset)
};
return documentClient
.query(params)
.promise()
.then(response =>
Object.assign(
{},
response,
{
Items: response.Items.map(parseItem)
},
response.LastEvaluatedKey !== undefined
? { Offset: btoa(response.LastEvaluatedKey.Type) }
: {}
)
);
};
}
/**
* Attempts to create a new Node, Node edge, or Node prop, constructed from
* the provided attributes.
* @param {object} attributes - Create attribute object.
* @property {string} [data] - Main or edge Node data.
* @property {string} [target] - Node edge target.
* @property {string} [prop] - Prop node data.
* @return {Promise} DynamoDB create request promise.
*/
function create(attributes) {
if (attributes === undefined) throw new Error('Options is undefined');
if (type === undefined) throw new Error('Type is undefined');
var { data, target, prop } = attributes;
if (
(target !== undefined && prop !== undefined) ||
(data !== undefined && prop !== undefined)
)
throw new Error(
'Can configure `prop`, `target`, and `data` values at the same type'
);
id || (id = cuid());
var itemData = data || prop;
if (typeof itemData === 'number') itemData = num2hex(itemData);
var item = {
Node: pTenant(id),
Type: type,
Data: itemData,
Target: pTenant(target || id),
GSIK: calculateGSIK({ node: id, maxGSIK, tenant })
};
var params = {
TableName: table,
Item: item
};
if (prop !== undefined) delete params.Item.Target;
return documentClient
.put(params)
.promise()
.then(() => ({ Item: parseItem(item) }));
}
}
}