electrodb 1.11.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.travis.yml +1 -1
- package/README.md +769 -672
- package/index.d.ts +323 -204
- package/index.js +2 -1
- package/package.json +1 -1
- package/src/clauses.js +15 -32
- package/src/entity.js +176 -89
- package/src/errors.js +6 -0
- package/src/filters.js +2 -2
- package/src/operations.js +7 -1
- package/src/schema.js +39 -11
- package/src/service.js +30 -111
- package/src/types.js +18 -2
- package/src/util.js +24 -0
- package/src/where.js +2 -2
package/src/schema.js
CHANGED
|
@@ -107,7 +107,7 @@ class Attribute {
|
|
|
107
107
|
this.watchedBy = watchedBy;
|
|
108
108
|
this.watching = watching;
|
|
109
109
|
this.watchAll = watchAll;
|
|
110
|
-
let { type, enumArray } = this._makeType(this.name, definition
|
|
110
|
+
let { type, enumArray } = this._makeType(this.name, definition);
|
|
111
111
|
this.type = type;
|
|
112
112
|
this.enumArray = enumArray;
|
|
113
113
|
this.parentType = definition.parentType;
|
|
@@ -145,7 +145,7 @@ class Attribute {
|
|
|
145
145
|
const {items, client} = definition;
|
|
146
146
|
const prop = {...items, ...parent};
|
|
147
147
|
// The use of "*" is to ensure the child's name is "*" when added to the traverser and searching for the children of a list
|
|
148
|
-
return Schema.normalizeAttributes({ '*': prop }, {}, {client, traverser: parent.traverser}).attributes["*"];
|
|
148
|
+
return Schema.normalizeAttributes({ '*': prop }, {}, {client, traverser: parent.traverser, parent}).attributes["*"];
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
static buildChildSetItems(definition, parent) {
|
|
@@ -156,7 +156,7 @@ class Attribute {
|
|
|
156
156
|
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "items" definition for Set attribute: "${definition.path}". Acceptable item types include ${u.commaSeparatedString(allowedTypes)}`);
|
|
157
157
|
}
|
|
158
158
|
const prop = {type: items, ...parent};
|
|
159
|
-
return Schema.normalizeAttributes({ prop }, {}, {client, traverser: parent.traverser}).attributes.prop;
|
|
159
|
+
return Schema.normalizeAttributes({ prop }, {}, {client, traverser: parent.traverser, parent}).attributes.prop;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
static buildChildMapProperties(definition, parent) {
|
|
@@ -168,7 +168,7 @@ class Attribute {
|
|
|
168
168
|
for (let name of Object.keys(properties)) {
|
|
169
169
|
attributes[name] = {...properties[name], ...parent};
|
|
170
170
|
}
|
|
171
|
-
return Schema.normalizeAttributes(attributes, {}, {client, traverser: parent.traverser});
|
|
171
|
+
return Schema.normalizeAttributes(attributes, {}, {client, traverser: parent.traverser, parent});
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
static buildPath(name, type, parentPath) {
|
|
@@ -369,11 +369,14 @@ class Attribute {
|
|
|
369
369
|
_makeType(name, definition) {
|
|
370
370
|
let type = "";
|
|
371
371
|
let enumArray = [];
|
|
372
|
-
if (Array.isArray(definition)) {
|
|
372
|
+
if (Array.isArray(definition.type)) {
|
|
373
373
|
type = AttributeTypes.enum;
|
|
374
|
-
enumArray = [...definition];
|
|
374
|
+
enumArray = [...definition.type];
|
|
375
|
+
// } else if (definition.type === AttributeTypes.set && Array.isArray(definition.items)) {
|
|
376
|
+
// type = AttributeTypes.enumSet;
|
|
377
|
+
// enumArray = [...definition.items];
|
|
375
378
|
} else {
|
|
376
|
-
type = definition || "string";
|
|
379
|
+
type = definition.type || "string";
|
|
377
380
|
}
|
|
378
381
|
if (!AttributeTypeNames.includes(type)) {
|
|
379
382
|
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "type" property for attribute: "${name}". Acceptable types include ${AttributeTypeNames.join(", ")}`);
|
|
@@ -409,12 +412,19 @@ class Attribute {
|
|
|
409
412
|
let reason = [];
|
|
410
413
|
switch (this.type) {
|
|
411
414
|
case AttributeTypes.enum:
|
|
415
|
+
case AttributeTypes.enumSet:
|
|
416
|
+
// isTyped = this.enumArray.every(enumValue => {
|
|
417
|
+
// const val = Array.isArray(value) ? value : [value];
|
|
418
|
+
// return val.includes(enumValue);
|
|
419
|
+
// })
|
|
412
420
|
isTyped = this.enumArray.includes(value);
|
|
413
421
|
if (!isTyped) {
|
|
414
422
|
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Value not found in set of acceptable values: ${u.commaSeparatedString(this.enumArray)}`));
|
|
415
423
|
}
|
|
416
424
|
break;
|
|
417
425
|
case AttributeTypes.any:
|
|
426
|
+
case AttributeTypes.static:
|
|
427
|
+
case AttributeTypes.custom:
|
|
418
428
|
isTyped = true;
|
|
419
429
|
break;
|
|
420
430
|
case AttributeTypes.string:
|
|
@@ -903,9 +913,9 @@ class SetAttribute extends Attribute {
|
|
|
903
913
|
}
|
|
904
914
|
|
|
905
915
|
class Schema {
|
|
906
|
-
constructor(properties = {}, facets = {}, {traverser = new AttributeTraverser(), client} = {}) {
|
|
907
|
-
this._validateProperties(properties);
|
|
908
|
-
let schema = Schema.normalizeAttributes(properties, facets, {traverser, client});
|
|
916
|
+
constructor(properties = {}, facets = {}, {traverser = new AttributeTraverser(), client, parent} = {}) {
|
|
917
|
+
this._validateProperties(properties, parent);
|
|
918
|
+
let schema = Schema.normalizeAttributes(properties, facets, {traverser, client, parent});
|
|
909
919
|
this.client = client;
|
|
910
920
|
this.attributes = schema.attributes;
|
|
911
921
|
this.enums = schema.enums;
|
|
@@ -918,7 +928,8 @@ class Schema {
|
|
|
918
928
|
this.traverser = traverser;
|
|
919
929
|
}
|
|
920
930
|
|
|
921
|
-
static normalizeAttributes(attributes = {}, facets = {}, {traverser, client} = {}) {
|
|
931
|
+
static normalizeAttributes(attributes = {}, facets = {}, {traverser, client, parent} = {}) {
|
|
932
|
+
const attributeHasParent = !!parent;
|
|
922
933
|
let invalidProperties = [];
|
|
923
934
|
let normalized = {};
|
|
924
935
|
let usedAttrs = {};
|
|
@@ -1024,6 +1035,10 @@ class Schema {
|
|
|
1024
1035
|
parentType: attribute.parentType,
|
|
1025
1036
|
};
|
|
1026
1037
|
|
|
1038
|
+
if (definition.type === AttributeTypes.custom) {
|
|
1039
|
+
definition.type = AttributeTypes.any;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1027
1042
|
if (attribute.watch !== undefined) {
|
|
1028
1043
|
if (attribute.watch === AttributeWildCard) {
|
|
1029
1044
|
definition.watchAll = true;
|
|
@@ -1095,6 +1110,10 @@ class Schema {
|
|
|
1095
1110
|
case AttributeTypes.set:
|
|
1096
1111
|
normalized[name] = new SetAttribute(definition);
|
|
1097
1112
|
break;
|
|
1113
|
+
case AttributeTypes.any:
|
|
1114
|
+
if (attributeHasParent) {
|
|
1115
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid attribute "${definition.name}" defined within "${parent.parentPath}". Attributes with type ${u.commaSeparatedString([AttributeTypes.any, AttributeTypes.custom])} are only supported as root level attributes.`);
|
|
1116
|
+
}
|
|
1098
1117
|
default:
|
|
1099
1118
|
normalized[name] = new Attribute(definition);
|
|
1100
1119
|
}
|
|
@@ -1344,8 +1363,17 @@ class Schema {
|
|
|
1344
1363
|
}
|
|
1345
1364
|
}
|
|
1346
1365
|
|
|
1366
|
+
function createCustomAttribute(definition = {}) {
|
|
1367
|
+
return {
|
|
1368
|
+
...definition,
|
|
1369
|
+
type: 'custom'
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1347
1373
|
module.exports = {
|
|
1348
1374
|
Schema,
|
|
1349
1375
|
Attribute,
|
|
1376
|
+
SetAttribute,
|
|
1350
1377
|
CastTypes,
|
|
1378
|
+
createCustomAttribute,
|
|
1351
1379
|
};
|
package/src/service.js
CHANGED
|
@@ -243,13 +243,8 @@ class Service {
|
|
|
243
243
|
|
|
244
244
|
cleanseRetrievedData(collection = "", entities, data = {}, config = {}) {
|
|
245
245
|
if (config.raw) {
|
|
246
|
-
|
|
247
|
-
return [data.LastEvaluatedKey, data];
|
|
248
|
-
} else {
|
|
249
|
-
return data;
|
|
250
|
-
}
|
|
246
|
+
return data;
|
|
251
247
|
}
|
|
252
|
-
|
|
253
248
|
data.Items = data.Items || [];
|
|
254
249
|
let index = this.collectionSchema[collection].index;
|
|
255
250
|
let results = {};
|
|
@@ -268,18 +263,15 @@ class Service {
|
|
|
268
263
|
if (!entityAlias) {
|
|
269
264
|
continue;
|
|
270
265
|
}
|
|
271
|
-
|
|
272
266
|
// pager=false because we don't want the entity trying to parse the lastEvaluatedKey
|
|
273
|
-
let items = this.collectionSchema[collection].entities[entityAlias].formatResponse({Item: record}, index, {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
return [page, results];
|
|
280
|
-
} else {
|
|
281
|
-
return results;
|
|
267
|
+
let items = this.collectionSchema[collection].entities[entityAlias].formatResponse({Item: record}, index, {
|
|
268
|
+
...config,
|
|
269
|
+
pager: false,
|
|
270
|
+
parse: undefined
|
|
271
|
+
});
|
|
272
|
+
results[entityAlias].push(items.data);
|
|
282
273
|
}
|
|
274
|
+
return results;
|
|
283
275
|
}
|
|
284
276
|
|
|
285
277
|
findKeyOwner(lastEvaluatedKey) {
|
|
@@ -287,92 +279,25 @@ class Service {
|
|
|
287
279
|
.find((entity) => entity.ownsLastEvaluatedKey(lastEvaluatedKey));
|
|
288
280
|
}
|
|
289
281
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
let matchingEntities = [];
|
|
295
|
-
if (pager === null) {
|
|
296
|
-
return matchingEntities;
|
|
297
|
-
}
|
|
298
|
-
for (let entity of Object.values(this.collectionSchema[collection].entities)) {
|
|
299
|
-
if (entity.ownsPager(pager, this.collectionSchema[collection].index)) {
|
|
300
|
-
matchingEntities.push(entity);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return matchingEntities;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
findNamedPagerOwner(pager = {}) {
|
|
307
|
-
let identifiers = this._getEntityIdentifiers(this.entities);
|
|
308
|
-
for (let identifier of identifiers) {
|
|
309
|
-
let hasCorrectFieldProperties = typeof pager[identifier.nameField] === "string" && typeof pager[identifier.versionField] === "string";
|
|
310
|
-
let hasMatchingFieldValues = pager[identifier.nameField] === identifier.name && pager[identifier.versionField] === identifier.version;
|
|
311
|
-
if (hasCorrectFieldProperties && hasMatchingFieldValues) {
|
|
312
|
-
return identifier.entity;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
expectPagerOwner(type, collection, pager) {
|
|
318
|
-
if (type === Pager.raw) {
|
|
319
|
-
return Object.values(this.collectionSchema[collection].entities)[0];
|
|
320
|
-
} else if (type === Pager.named || type === undefined) {
|
|
321
|
-
let owner = this.findNamedPagerOwner(pager);
|
|
322
|
-
if (owner === undefined) {
|
|
323
|
-
throw new e.ElectroError(e.ErrorCodes.NoOwnerForPager, "Supplied Pager does not resolve to Entity within Service");
|
|
324
|
-
}
|
|
325
|
-
return owner;
|
|
326
|
-
} else if (type === Pager.item) {
|
|
327
|
-
let owners = this.findItemPagerOwner(collection, pager);
|
|
328
|
-
if (owners.length === 1) {
|
|
329
|
-
return owners[0];
|
|
330
|
-
} else {
|
|
331
|
-
throw new e.ElectroError(e.ErrorCodes.PagerNotUnique, "Supplied Pager did not resolve to single Entity");
|
|
332
|
-
}
|
|
333
|
-
} else {
|
|
334
|
-
throw new e.ElectroError(e.ErrorCodes.InvalidOptions, `Invalid value for option "pager" provider: "${pager}". Allowed values include ${u.commaSeparatedString(Object.keys(Pager))}.`)
|
|
282
|
+
expectKeyOwner(lastEvaluatedKey) {
|
|
283
|
+
const owner = this.findKeyOwner(lastEvaluatedKey);
|
|
284
|
+
if (owner === undefined) {
|
|
285
|
+
throw new e.ElectroError(e.ErrorCodes.NoOwnerForCursor, `Supplied cursor does not resolve to Entity within the Service ${this.service.name}`);
|
|
335
286
|
}
|
|
287
|
+
return owner;
|
|
336
288
|
}
|
|
337
289
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
let matchingIdentifiers = [];
|
|
343
|
-
if (pager === null) {
|
|
344
|
-
return matchingIdentifiers;
|
|
345
|
-
}
|
|
346
|
-
for (let entity of Object.values(this.collectionSchema[collection].entities)) {
|
|
347
|
-
if (entity.ownsPager(pager, this.collectionSchema[collection].index)) {
|
|
348
|
-
matchingIdentifiers.push({
|
|
349
|
-
[entity.identifiers.entity]: entity.getName(),
|
|
350
|
-
[entity.identifiers.version]: entity.getVersion(),
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return matchingIdentifiers;
|
|
290
|
+
findCursorOwner(cursor) {
|
|
291
|
+
return Object.values(this.entities)
|
|
292
|
+
.find(entity => entity.ownsCursor(cursor));
|
|
355
293
|
}
|
|
356
294
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return null;
|
|
362
|
-
} else {
|
|
363
|
-
let entity = this.findKeyOwner(lastEvaluatedKey);
|
|
364
|
-
if (!entity) {
|
|
365
|
-
return lastReturned !== undefined
|
|
366
|
-
? this._formatReturnPager(config, index, lastReturned)
|
|
367
|
-
: null
|
|
368
|
-
}
|
|
369
|
-
let page = entity._formatKeysToItem(index, lastEvaluatedKey);
|
|
370
|
-
if (config.pager === "named") {
|
|
371
|
-
page[entity.identifiers.entity] = entity.getName();
|
|
372
|
-
page[entity.identifiers.version] = entity.getVersion();
|
|
373
|
-
}
|
|
374
|
-
return page;
|
|
295
|
+
expectCursorOwner(cursor) {
|
|
296
|
+
const owner = this.findCursorOwner(cursor);
|
|
297
|
+
if (owner === undefined) {
|
|
298
|
+
throw new e.ElectroError(e.ErrorCodes.NoOwnerForCursor, `Supplied cursor does not resolve to Entity within the Service ${this.service.name}`);
|
|
375
299
|
}
|
|
300
|
+
return owner;
|
|
376
301
|
}
|
|
377
302
|
|
|
378
303
|
_getTableName() {
|
|
@@ -389,21 +314,7 @@ class Service {
|
|
|
389
314
|
_makeCollectionChain(name = "", attributes = {}, initialClauses = {}, expressions = {}, entities = {}, entity = {}, facets = {}) {
|
|
390
315
|
let filterBuilder = new FilterFactory(attributes, FilterOperations);
|
|
391
316
|
let whereBuilder = new WhereFactory(attributes, FilterOperations);
|
|
392
|
-
|
|
393
|
-
let pageClause = {...initialClauses.page};
|
|
394
|
-
let pageAction = initialClauses.page.action;
|
|
395
|
-
pageClause.action = (entity, state, page = null, options = {}) => {
|
|
396
|
-
try {
|
|
397
|
-
if (page === null) {
|
|
398
|
-
return pageAction(entity, state, page, options);
|
|
399
|
-
}
|
|
400
|
-
let owner = this.expectPagerOwner(options.pager, name, page);
|
|
401
|
-
return pageAction(owner, state, page, options);
|
|
402
|
-
} catch(err) {
|
|
403
|
-
return Promise.reject(err);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
let clauses = {...initialClauses, page: pageClause};
|
|
317
|
+
let clauses = {...initialClauses};
|
|
407
318
|
|
|
408
319
|
clauses = filterBuilder.injectFilterClauses(clauses);
|
|
409
320
|
clauses = whereBuilder.injectWhereClauses(clauses);
|
|
@@ -412,6 +323,14 @@ class Service {
|
|
|
412
323
|
// expressions, // DynamoDB doesnt return what I expect it would when provided with these entity filters
|
|
413
324
|
parse: (options, data) => {
|
|
414
325
|
return this.cleanseRetrievedData(name, entities, data, options);
|
|
326
|
+
},
|
|
327
|
+
formatCursor: {
|
|
328
|
+
serialize: (key) => {
|
|
329
|
+
return this.expectKeyOwner(key).serializeCursor(key);
|
|
330
|
+
},
|
|
331
|
+
deserialize: (cursor) => {
|
|
332
|
+
return this.expectCursorOwner(cursor).deserilizeCursor(cursor);
|
|
333
|
+
}
|
|
415
334
|
}
|
|
416
335
|
};
|
|
417
336
|
|
package/src/types.js
CHANGED
|
@@ -52,8 +52,11 @@ const AttributeTypes = {
|
|
|
52
52
|
enum: "enum",
|
|
53
53
|
map: "map",
|
|
54
54
|
set: "set",
|
|
55
|
+
// enumSet: "enumSet",
|
|
55
56
|
list: "list",
|
|
56
57
|
any: "any",
|
|
58
|
+
custom: "custom",
|
|
59
|
+
static: "static",
|
|
57
60
|
};
|
|
58
61
|
|
|
59
62
|
const PathTypes = {
|
|
@@ -107,7 +110,8 @@ const AttributeMutationMethods = {
|
|
|
107
110
|
const Pager = {
|
|
108
111
|
raw: "raw",
|
|
109
112
|
named: "named",
|
|
110
|
-
item: "item"
|
|
113
|
+
item: "item",
|
|
114
|
+
cursor: "cursor"
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
const UnprocessedTypes = {
|
|
@@ -191,6 +195,15 @@ const TerminalOperation = {
|
|
|
191
195
|
page: 'page',
|
|
192
196
|
}
|
|
193
197
|
|
|
198
|
+
const AllPages = 'all';
|
|
199
|
+
|
|
200
|
+
const ResultOrderOption = {
|
|
201
|
+
'asc': true,
|
|
202
|
+
'desc': false
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const ResultOrderParam = 'ScanIndexForward';
|
|
206
|
+
|
|
194
207
|
module.exports = {
|
|
195
208
|
Pager,
|
|
196
209
|
KeyTypes,
|
|
@@ -220,5 +233,8 @@ module.exports = {
|
|
|
220
233
|
AttributeProxySymbol,
|
|
221
234
|
ElectroInstanceTypes,
|
|
222
235
|
EventSubscriptionTypes,
|
|
223
|
-
AttributeMutationMethods
|
|
236
|
+
AttributeMutationMethods,
|
|
237
|
+
AllPages,
|
|
238
|
+
ResultOrderOption,
|
|
239
|
+
ResultOrderParam,
|
|
224
240
|
};
|
package/src/util.js
CHANGED
|
@@ -161,6 +161,29 @@ function getUnique(arr1, arr2) {
|
|
|
161
161
|
]));
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
const cursorFormatter = {
|
|
165
|
+
serialize: (key) => {
|
|
166
|
+
if (!key) {
|
|
167
|
+
return null;
|
|
168
|
+
} else if (typeof val !== 'string') {
|
|
169
|
+
key = JSON.stringify(key);
|
|
170
|
+
}
|
|
171
|
+
return Buffer.from(key).toString('base64url');
|
|
172
|
+
},
|
|
173
|
+
deserialize: (cursor) => {
|
|
174
|
+
if (!cursor) {
|
|
175
|
+
return undefined;
|
|
176
|
+
} else if (typeof cursor !== 'string') {
|
|
177
|
+
throw new Error(`Invalid cursor provided, expected type 'string' recieved: ${JSON.stringify(cursor)}`);
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
return JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
181
|
+
} catch(err) {
|
|
182
|
+
throw new Error('Unable to parse cursor');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
164
187
|
module.exports = {
|
|
165
188
|
getUnique,
|
|
166
189
|
batchItems,
|
|
@@ -171,6 +194,7 @@ module.exports = {
|
|
|
171
194
|
genericizeJSONPath,
|
|
172
195
|
commaSeparatedString,
|
|
173
196
|
formatAttributeCasing,
|
|
197
|
+
cursorFormatter,
|
|
174
198
|
applyBetaModelOverrides,
|
|
175
199
|
formatIndexNameForDisplay,
|
|
176
200
|
BatchGetOrderMaintainer,
|
package/src/where.js
CHANGED
|
@@ -120,7 +120,7 @@ class WhereFactory {
|
|
|
120
120
|
injected[name] = {
|
|
121
121
|
name,
|
|
122
122
|
action: this.buildClause(filter),
|
|
123
|
-
children: ["params", "go", "
|
|
123
|
+
children: ["params", "go", "where", ...modelFilters],
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
filterChildren.push("where");
|
|
@@ -129,7 +129,7 @@ class WhereFactory {
|
|
|
129
129
|
action: (entity, state, fn) => {
|
|
130
130
|
return this.buildClause(fn)(entity, state);
|
|
131
131
|
},
|
|
132
|
-
children: ["params", "go", "
|
|
132
|
+
children: ["params", "go", "where", ...modelFilters],
|
|
133
133
|
};
|
|
134
134
|
for (let parent of filterParents) {
|
|
135
135
|
injected[parent] = { ...injected[parent] };
|