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/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.type);
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
- if (config._isPagination) {
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, {...config, pager: false});
274
- results[entityAlias].push(items);
275
- }
276
-
277
- if (config._isPagination) {
278
- let page = this._formatReturnPager(config, index, data.LastEvaluatedKey, data.Items[data.Items.length - 1]);
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
- findItemPagerOwner(collection, pager = {}) {
291
- if (this.collectionSchema[collection] === undefined) {
292
- throw new Error("Invalid collection");
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
- findPagerIdentifiers(collection, pager = {}) {
339
- if (this.collectionSchema[collection] === undefined) {
340
- throw new Error("Invalid collection");
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
- _formatReturnPager(config, index, lastEvaluatedKey, lastReturned) {
358
- if (config.pager === "raw") {
359
- return lastEvaluatedKey;
360
- } else if (!lastEvaluatedKey) {
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", "page", "where", ...modelFilters],
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", "page", "where", ...modelFilters],
132
+ children: ["params", "go", "where", ...modelFilters],
133
133
  };
134
134
  for (let parent of filterParents) {
135
135
  injected[parent] = { ...injected[parent] };