electrodb 2.1.2 → 2.2.1

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
@@ -524,15 +524,22 @@ class MapAttribute extends Attribute {
524
524
  traverser: this.traverser
525
525
  });
526
526
  this.properties = properties;
527
+ this.isRoot = !!definition.isRoot;
527
528
  this.get = this._makeGet(definition.get, properties);
528
529
  this.set = this._makeSet(definition.set, properties);
529
530
  }
530
531
 
531
532
  _makeGet(get, properties) {
532
533
  this._checkGetSet(get, "get");
533
-
534
- const getter = get || ((attr) => attr);
535
-
534
+ const getter = get || ((val) => {
535
+ const isEmpty = !val || Object.keys(val).length === 0;
536
+ const isNotRequired = !this.required;
537
+ const isRoot = this.isRoot;
538
+ if (isEmpty && isRoot && !isNotRequired) {
539
+ return undefined;
540
+ }
541
+ return val;
542
+ });
536
543
  return (values, siblings) => {
537
544
  const data = {};
538
545
 
@@ -541,6 +548,9 @@ class MapAttribute extends Attribute {
541
548
  }
542
549
 
543
550
  if (values === undefined) {
551
+ if (!get) {
552
+ return undefined;
553
+ }
544
554
  return getter(data, siblings);
545
555
  }
546
556
 
@@ -561,11 +571,23 @@ class MapAttribute extends Attribute {
561
571
 
562
572
  _makeSet(set, properties) {
563
573
  this._checkGetSet(set, "set");
564
- const setter = set || ((attr) => attr);
574
+ const setter = set || ((val) => {
575
+ const isEmpty = !val || Object.keys(val).length === 0;
576
+ const isNotRequired = !this.required;
577
+ const isRoot = this.isRoot;
578
+ if (isEmpty && isRoot && !isNotRequired) {
579
+ return undefined;
580
+ }
581
+ return val;
582
+ });
583
+
565
584
  return (values, siblings) => {
566
585
  const data = {};
567
586
  if (values === undefined) {
568
- return setter(data, siblings);
587
+ if (!set) {
588
+ return undefined;
589
+ }
590
+ return setter(values, siblings);
569
591
  }
570
592
  for (const name of Object.keys(properties.attributes)) {
571
593
  const attribute = properties.attributes[name];
@@ -624,18 +646,18 @@ class MapAttribute extends Attribute {
624
646
  }
625
647
 
626
648
  val(value) {
627
- const getValue = (v) => {
628
- v = this.cast(v);
629
- if (v === undefined) {
630
- v = this.default();
649
+ const incomingIsEmpty = value === undefined;
650
+ let fromDefault = false;
651
+ let data;
652
+ if (value === undefined) {
653
+ data = this.default();
654
+ if (data !== undefined) {
655
+ fromDefault = true;
631
656
  }
632
- return v;
657
+ } else {
658
+ data = value;
633
659
  }
634
660
 
635
- let data = value === undefined
636
- ? getValue(value)
637
- : value;
638
-
639
661
  const valueType = getValueType(data);
640
662
 
641
663
  if (data === undefined) {
@@ -654,6 +676,10 @@ class MapAttribute extends Attribute {
654
676
  }
655
677
  }
656
678
 
679
+ if (Object.keys(response).length === 0 && !fromDefault && this.isRoot && !this.required && incomingIsEmpty) {
680
+ return undefined;
681
+ }
682
+
657
683
  return response;
658
684
  }
659
685
  }
@@ -959,9 +985,9 @@ class SetAttribute extends Attribute {
959
985
  }
960
986
 
961
987
  class Schema {
962
- constructor(properties = {}, facets = {}, {traverser = new AttributeTraverser(), client, parent} = {}) {
988
+ constructor(properties = {}, facets = {}, {traverser = new AttributeTraverser(), client, parent, isRoot} = {}) {
963
989
  this._validateProperties(properties, parent);
964
- let schema = Schema.normalizeAttributes(properties, facets, {traverser, client, parent});
990
+ let schema = Schema.normalizeAttributes(properties, facets, {traverser, client, parent, isRoot});
965
991
  this.client = client;
966
992
  this.attributes = schema.attributes;
967
993
  this.enums = schema.enums;
@@ -972,9 +998,10 @@ class Schema {
972
998
  this.requiredAttributes = schema.requiredAttributes;
973
999
  this.translationForWatching = this._formatWatchTranslations(this.attributes);
974
1000
  this.traverser = traverser;
1001
+ this.isRoot = !!isRoot;
975
1002
  }
976
1003
 
977
- static normalizeAttributes(attributes = {}, facets = {}, {traverser, client, parent} = {}) {
1004
+ static normalizeAttributes(attributes = {}, facets = {}, {traverser, client, parent, isRoot} = {}) {
978
1005
  const attributeHasParent = !!parent;
979
1006
  let invalidProperties = [];
980
1007
  let normalized = {};
@@ -1073,6 +1100,7 @@ class Schema {
1073
1100
  postfix,
1074
1101
  traverser,
1075
1102
  isKeyField,
1103
+ isRoot: !!isRoot,
1076
1104
  label: attribute.label,
1077
1105
  required: !!attribute.required,
1078
1106
  default: attribute.default,
package/src/service.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const { Entity } = require("./entity");
2
2
  const { clauses } = require("./clauses");
3
- const { KeyCasing, ServiceVersions, Pager, ElectroInstance, ElectroInstanceTypes, ModelVersions } = require("./types");
3
+ const { KeyCasing, ServiceVersions, Pager, ElectroInstance, ElectroInstanceTypes, ModelVersions, IndexTypes } = require("./types");
4
4
  const { FilterFactory } = require("./filters");
5
5
  const { FilterOperations } = require("./operations");
6
6
  const { WhereFactory } = require("./where");
@@ -65,7 +65,9 @@ class Service {
65
65
  this.entities = {};
66
66
  this.find = {};
67
67
  this.collectionSchema = {};
68
+ this.compositeAttributes = {};
68
69
  this.collections = {};
70
+ this.identifiers = {};
69
71
  this._instance = ElectroInstance.service;
70
72
  this._instanceType = ElectroInstanceTypes.service;
71
73
  }
@@ -89,7 +91,9 @@ class Service {
89
91
  this.entities = {};
90
92
  this.find = {};
91
93
  this.collectionSchema = {};
94
+ this.compositeAttributes = {};
92
95
  this.collections = {};
96
+ this.identifiers = {};
93
97
  this._instance = ElectroInstance.service;
94
98
  this._instanceType = ElectroInstanceTypes.service;
95
99
  }
@@ -183,12 +187,12 @@ class Service {
183
187
  throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `Service name defined on joined instance, ${entity.model.service}, does not match the name of this Service: ${this.service.name}. Verify or update the service name on the Entity/Model to match the name defined on this service.`);
184
188
  }
185
189
 
186
- if (this._getTableName()) {
187
- entity._setTableName(this._getTableName());
190
+ if (this.getTableName()) {
191
+ entity.setTableName(this.getTableName());
188
192
  }
189
193
 
190
194
  if (options.client) {
191
- entity._setClient(options.client);
195
+ entity.setClient(options.client);
192
196
  }
193
197
 
194
198
  if (options.logger) {
@@ -205,20 +209,63 @@ class Service {
205
209
 
206
210
  this.entities[name] = entity;
207
211
  for (let collection of this.entities[name].model.collections) {
212
+ // todo: this used to be inside the collection callback, it does not do well being ran multiple times
213
+ // this forlook adds the entity filters multiple times
208
214
  this._addCollectionEntity(collection, name, this.entities[name]);
209
215
  this.collections[collection] = (...facets) => {
210
- let { entities, attributes, identifiers } = this.collectionSchema[collection];
211
- return this._makeCollectionChain(collection, attributes, clauses, identifiers, entities, Object.values(entities)[0], ...facets);
216
+ return this._makeCollectionChain({
217
+ name: collection,
218
+ initialClauses: clauses,
219
+ }, ...facets);
212
220
  };
213
221
  }
222
+ for (const collection in this.collectionSchema) {
223
+ const collectionSchema = this.collectionSchema[collection];
224
+ this.compositeAttributes[collection] = this._collectionSchemaToCompositeAttributes(collectionSchema);
225
+ }
214
226
  this.find = { ...this.entities, ...this.collections };
215
227
  return this;
216
228
  }
217
229
 
218
- _setClient(client) {
230
+ _collectionSchemaToCompositeAttributes(schema) {
231
+ const keys = schema.keys;
232
+ return {
233
+ hasSortKeys: keys.hasSk,
234
+ customFacets: {
235
+ pk: keys.pk.isCustom,
236
+ sk: keys.sk.isCustom,
237
+ },
238
+ pk: keys.pk.facets,
239
+ sk: keys.sk.facets,
240
+ all: [
241
+ ...keys.pk.facets.map(name => {
242
+ return {
243
+ name,
244
+ index: keys.index,
245
+ type: 'pk',
246
+ };
247
+ }),
248
+ ...keys.sk.facets.map(name => {
249
+ return {
250
+ name,
251
+ index: keys.index,
252
+ type: 'sk',
253
+ };
254
+ })
255
+ ],
256
+ collection: keys.collection,
257
+ hasSubCollections: schema.hasSubCollections,
258
+ casing: {
259
+ pk: keys.pk.casing,
260
+ sk: keys.sk.casing,
261
+ },
262
+ }
263
+ }
264
+
265
+ setClient(client) {
219
266
  if (client !== undefined) {
220
267
  for (let entity of Object.values(this.entities)) {
221
- entity._setClient(client);
268
+ entity.setClient(client);
222
269
  }
223
270
  }
224
271
  }
@@ -275,8 +322,9 @@ class Service {
275
322
  }
276
323
 
277
324
  findKeyOwner(lastEvaluatedKey) {
278
- return Object.values(this.entities)
279
- .find((entity) => entity.ownsLastEvaluatedKey(lastEvaluatedKey));
325
+ return Object.values(this.entities)[0];
326
+ // return Object.values(this.entities)
327
+ // .find((entity) => entity.ownsLastEvaluatedKey(lastEvaluatedKey));
280
328
  }
281
329
 
282
330
  expectKeyOwner(lastEvaluatedKey) {
@@ -288,8 +336,9 @@ class Service {
288
336
  }
289
337
 
290
338
  findCursorOwner(cursor) {
291
- return Object.values(this.entities)
292
- .find(entity => entity.ownsCursor(cursor));
339
+ return Object.values(this.entities)[0];
340
+ // return Object.values(this.entities)
341
+ // .find(entity => entity.ownsCursor(cursor));
293
342
  }
294
343
 
295
344
  expectCursorOwner(cursor) {
@@ -300,18 +349,26 @@ class Service {
300
349
  return owner;
301
350
  }
302
351
 
303
- _getTableName() {
352
+ getTableName() {
304
353
  return this.service.table;
305
354
  }
306
355
 
307
- _setTableName(table) {
356
+ setTableName(table) {
308
357
  this.service.table = table;
309
358
  for (let entity of Object.values(this.entities)) {
310
- entity._setTableName(table);
359
+ entity.setTableName(table);
311
360
  }
312
361
  }
313
362
 
314
- _makeCollectionChain(name = "", attributes = {}, initialClauses = {}, expressions = {}, entities = {}, entity = {}, facets = {}) {
363
+ _makeCollectionChain({
364
+ name = "",
365
+ initialClauses = {},
366
+ }, facets = {}) {
367
+ const { entities, attributes, identifiers, indexType } = this.collectionSchema[name];
368
+ const compositeAttributes = this.compositeAttributes[name];
369
+ const allEntities = Object.values(entities);
370
+ const entity = allEntities[0];
371
+
315
372
  let filterBuilder = new FilterFactory(attributes, FilterOperations);
316
373
  let whereBuilder = new WhereFactory(attributes, FilterOperations);
317
374
  let clauses = {...initialClauses};
@@ -319,6 +376,8 @@ class Service {
319
376
  clauses = filterBuilder.injectFilterClauses(clauses);
320
377
  clauses = whereBuilder.injectWhereClauses(clauses);
321
378
 
379
+ const expression = identifiers.expression || "";
380
+
322
381
  let options = {
323
382
  // expressions, // DynamoDB doesnt return what I expect it would when provided with these entity filters
324
383
  parse: (options, data) => {
@@ -329,9 +388,20 @@ class Service {
329
388
  return this.expectKeyOwner(key).serializeCursor(key);
330
389
  },
331
390
  deserialize: (cursor) => {
332
- return this.expectCursorOwner(cursor).deserilizeCursor(cursor);
391
+ return this.expectCursorOwner(cursor).deserializeCursor(cursor);
333
392
  }
334
- }
393
+ },
394
+ expressions: {
395
+ names: identifiers.names || {},
396
+ values: identifiers.values || {},
397
+ expression: allEntities.length > 1
398
+ ? `(${expression})`
399
+ : expression
400
+ },
401
+ attributes,
402
+ entities,
403
+ indexType,
404
+ compositeAttributes,
335
405
  };
336
406
 
337
407
  return entity.collection(name, clauses, facets, options);
@@ -477,7 +547,33 @@ class Service {
477
547
  if (invalidDefinition) {
478
548
  throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `Validation Error while joining entity, "${name}". ${invalidIndexMessages.join(", ")}`);
479
549
  }
480
- return definition;
550
+ const sharedSortKeyAttributes = [];
551
+ const sharedSortKeyCompositeAttributeLabels = [];
552
+ const sharedSortKeyLabels = [];
553
+ if (providedIndex.hasSk && definition.hasSk && Array.isArray(definition.sk.labels)) {
554
+ for (let i = 0; i < definition.sk.labels.length; i++) {
555
+ const providedLabels = providedIndex.sk.labels[i];
556
+ const definedLabels = definition.sk.labels[i];
557
+
558
+ const namesMatch = providedLabels && providedLabels.name === definedLabels.name;
559
+ const labelsMatch = providedLabels && providedLabels.label === definedLabels.label;
560
+ if (!namesMatch || !labelsMatch) {
561
+ break;
562
+ }
563
+ sharedSortKeyLabels.push({...definedLabels});
564
+ sharedSortKeyCompositeAttributeLabels.push({...definition.sk.facetLabels[i]})
565
+ sharedSortKeyAttributes.push(definition.sk.facets[i]);
566
+ }
567
+ }
568
+ return {
569
+ ...definition,
570
+ sk: {
571
+ ...definition.sk,
572
+ facets: sharedSortKeyAttributes,
573
+ facetLabels: sharedSortKeyCompositeAttributeLabels,
574
+ labels: sharedSortKeyLabels,
575
+ }
576
+ };
481
577
  }
482
578
 
483
579
  _getEntityIndexFromCollectionName(collection, entity) {
@@ -506,7 +602,7 @@ class Service {
506
602
  );
507
603
  }
508
604
 
509
- _processSubCollections(existing, provided, entityName, collectionName) {
605
+ _processSubCollections(providedType, existing, provided, entityName, collectionName) {
510
606
  let existingSubCollections;
511
607
  let providedSubCollections;
512
608
  if (v.isArrayHasLength(existing)) {
@@ -520,15 +616,19 @@ class Service {
520
616
  providedSubCollections = [provided];
521
617
  }
522
618
 
619
+ if (providedSubCollections.length > 1 && providedType === IndexTypes.clustered) {
620
+ throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `Clustered indexes do not support sub-collections. The sub-collection "${collectionName}", on Entity "${entityName}" must be defined as either an individual collection name or the index must be redefined as an isolated cluster`);
621
+ }
523
622
  const existingRequiredIndex = existingSubCollections.indexOf(collectionName);
524
623
  const providedRequiredIndex = providedSubCollections.indexOf(collectionName);
525
624
  if (providedRequiredIndex < 0) {
526
- throw new Error(`The collection definition for Collection "${collectionName}" does not exist on Entity "${entityName}".`);
625
+ throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `The collection definition for Collection "${collectionName}" does not exist on Entity "${entityName}".`);
527
626
  }
528
627
  if (existingRequiredIndex >= 0 && existingRequiredIndex !== providedRequiredIndex) {
529
- throw new Error(`The collection definition for Collection "${collectionName}", on Entity "${entityName}", does not match the established sub-collection order for this service. The collection name provided in slot ${providedRequiredIndex + 1}, ${providedSubCollections[existingRequiredIndex] === undefined ? '(not found)' : `"${providedSubCollections[existingRequiredIndex]}"`}, on Entity "${entityName}", does not match the established collection name in slot ${existingRequiredIndex + 1}, "${collectionName}". When using sub-collections, all Entities within a Service must must implement the same order for all preceding sub-collections.`);
628
+ throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `The collection definition for Collection "${collectionName}", on Entity "${entityName}", does not match the established sub-collection order for this service. The collection name provided in slot ${providedRequiredIndex + 1}, ${providedSubCollections[existingRequiredIndex] === undefined ? '(not found)' : `"${providedSubCollections[existingRequiredIndex]}"`}, on Entity "${entityName}", does not match the established collection name in slot ${existingRequiredIndex + 1}, "${collectionName}". When using sub-collections, all Entities within a Service must must implement the same order for all preceding sub-collections.`);
530
629
  }
531
630
  let length = Math.max(existingRequiredIndex, providedRequiredIndex);
631
+
532
632
  for (let i = 0; i <= length; i++) {
533
633
  let existingCollection = existingSubCollections[i];
534
634
  let providedCollection = providedSubCollections[i];
@@ -537,7 +637,7 @@ class Service {
537
637
  return i;
538
638
  }
539
639
  if (existingCollection !== providedCollection) {
540
- throw new Error(`The collection definition for Collection "${collectionName}", on Entity "${entityName}", does not match the established sub-collection order for this service. The collection name provided in slot ${i+1}, "${providedCollection}", on Entity "${entityName}", does not match the established collection name in slot ${i + 1}, "${existingCollection}". When using sub-collections, all Entities within a Service must must implement the same order for all preceding sub-collections.`);
640
+ throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `The collection definition for Collection "${collectionName}", on Entity "${entityName}", does not match the established sub-collection order for this service. The collection name provided in slot ${i+1}, "${providedCollection}", on Entity "${entityName}", does not match the established collection name in slot ${i + 1}, "${existingCollection}". When using sub-collections, all Entities within a Service must must implement the same order for all preceding sub-collections.`);
541
641
  }
542
642
  } else if (v.isStringHasLength(providedCollection)) {
543
643
  if (providedCollection === collectionName) {
@@ -564,27 +664,43 @@ class Service {
564
664
  },
565
665
  index: undefined,
566
666
  table: "",
567
- collection: []
667
+ collection: [],
668
+ indexType: undefined,
669
+ hasSubCollections: undefined,
568
670
  };
671
+ const providedType = providedIndex.type || IndexTypes.isolated;
672
+ if (this.collectionSchema[collection].indexType === undefined) {
673
+ this.collectionSchema[collection].indexType = providedType;
674
+ } else if (this.collectionSchema[collection].indexType !== providedType) {
675
+ throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `Index type mismatch on collection ${collection}. The entity ${name} defines the index as type ${providedType} while the established type for that index is ${this.collectionSchema[collection].indexType}. Note that when omitted, indexes default to the type "${IndexTypes.isolated}"`);
676
+ }
569
677
  if (this.collectionSchema[collection].entities[name] !== undefined) {
570
678
  throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `Entity with name '${name}' has already been joined to this service.`);
571
679
  }
572
680
 
573
681
  if (this.collectionSchema[collection].table !== "") {
574
- if (this.collectionSchema[collection].table !== entity._getTableName()) {
575
- throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `Entity with name '${name}' is defined to use a different Table than what is defined on other Service Entities and/or the Service itself. Entity '${name}' is defined with table name '${entity._getTableName()}' but the Service has been defined to use table name '${this.collectionSchema[collection].table}'. All Entities in a Service must reference the same DynamoDB table. To ensure all Entities will use the same DynamoDB table, it is possible to apply the property 'table' to the Service constructor's configuration parameter.`);
682
+ if (this.collectionSchema[collection].table !== entity.getTableName()) {
683
+ throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `Entity with name '${name}' is defined to use a different Table than what is defined on other Service Entities and/or the Service itself. Entity '${name}' is defined with table name '${entity.getTableName()}' but the Service has been defined to use table name '${this.collectionSchema[collection].table}'. All Entities in a Service must reference the same DynamoDB table. To ensure all Entities will use the same DynamoDB table, it is possible to apply the property 'table' to the Service constructor's configuration parameter.`);
576
684
  }
577
685
  } else {
578
- this.collectionSchema[collection].table = entity._getTableName();
686
+ this.collectionSchema[collection].table = entity.getTableName();
579
687
  }
688
+
580
689
  this.collectionSchema[collection].keys = this._processEntityKeys(name, this.collectionSchema[collection].keys, providedIndex);
581
690
  this.collectionSchema[collection].attributes = this._processEntityAttributes(name, this.collectionSchema[collection].attributes, entity.model.schema.attributes, this.collectionSchema[collection].keys);
582
691
  this.collectionSchema[collection].entities[name] = entity;
583
692
  this.collectionSchema[collection].identifiers = this._processEntityIdentifiers(this.collectionSchema[collection].identifiers, entity.getIdentifierExpressions(name));
584
693
  this.collectionSchema[collection].index = this._processEntityCollectionIndex(this.collectionSchema[collection].index, providedIndex.index, name, collection);
585
- let collectionIndex = this._processSubCollections(this.collectionSchema[collection].collection, providedIndex.collection, name, collection);
694
+ let collectionIndex = this._processSubCollections(
695
+ providedType,
696
+ this.collectionSchema[collection].collection,
697
+ providedIndex.collection,
698
+ name,
699
+ collection
700
+ );
586
701
  this.collectionSchema[collection].collection[collectionIndex] = collection;
587
-
702
+ this.collectionSchema[collection].hasSubCollections = this.collectionSchema[collection].hasSubCollections || Array.isArray(providedIndex.collection);
703
+ return this.collectionSchema[collection];
588
704
  }
589
705
 
590
706
  _processEntityCollectionIndex(existing, provided, name, collection) {
package/src/types.js CHANGED
@@ -18,6 +18,7 @@ const QueryTypes = {
18
18
  begins: "begins",
19
19
  between: "between",
20
20
  collection: "collection",
21
+ clustered_collection: 'clustered_collection',
21
22
  is: "is"
22
23
  };
23
24
 
@@ -36,11 +37,62 @@ const MethodTypes = {
36
37
  batchWrite: "batchWrite"
37
38
  };
38
39
 
40
+ const IndexTypes = {
41
+ isolated: 'isolated',
42
+ clustered: 'clustered',
43
+ }
44
+
39
45
  const Comparisons = {
46
+ lte: '<=',
47
+ lt: "<",
40
48
  gte: ">=",
41
- gt: ">",
42
- lte: "<=",
49
+ gt: '>'
50
+ }
51
+
52
+ const PartialComparisons = {
43
53
  lt: "<",
54
+ gte: ">=",
55
+
56
+ /**
57
+ * gt becomes gte and last character of incoming value is shifted up one character code
58
+ * example:
59
+ * sk > '2020-09-05'
60
+ * expected
61
+ * - 2020-09-06@05:05_hero
62
+ * - 2020-10-05@05:05_hero
63
+ * - 2022-02-05@05:05_villian
64
+ * - 2022-06-05@05:05_clown
65
+ * - 2022-09-06@05:05_clown
66
+ * actual (bad - includes all 2020-09-05 records)
67
+ * - 2020-09-05@05:05_hero
68
+ * - 2020-09-06@05:05_hero
69
+ * - 2020-10-05@05:05_hero
70
+ * - 2022-02-05@05:05_villian
71
+ * - 2022-06-05@05:05_clown
72
+ */
73
+ gt: ">=",
74
+
75
+ /**
76
+ * lte becomes lt and last character of incoming value is shifted up one character code
77
+ * example:
78
+ * sk >= '2020-09-05'
79
+ * expected
80
+ * - 2012-02-05@05:05_clown
81
+ * - 2015-10-05@05:05_hero
82
+ * - 2017-02-05@05:05_clown
83
+ * - 2017-02-05@05:05_villian
84
+ * - 2020-02-05@05:05_clown
85
+ * - 2020-02-25@05:05_clown
86
+ * - 2020-09-05@05:05_hero
87
+ * actual (bad - missing all 2020-09-05 records)
88
+ * - 2012-02-05@05:05_clown
89
+ * - 2015-10-05@05:05_hero
90
+ * - 2017-02-05@05:05_clown
91
+ * - 2017-02-05@05:05_villian
92
+ * - 2020-02-05@05:05_clown
93
+ * - 2020-02-25@05:05_clown
94
+ */
95
+ lte: "<",
44
96
  };
45
97
 
46
98
  const CastTypes = ["string", "number"];
@@ -210,6 +262,7 @@ module.exports = {
210
262
  CastTypes,
211
263
  KeyCasing,
212
264
  PathTypes,
265
+ IndexTypes,
213
266
  QueryTypes,
214
267
  ValueTypes,
215
268
  TableIndex,
@@ -229,6 +282,7 @@ module.exports = {
229
282
  UnprocessedTypes,
230
283
  AttributeWildCard,
231
284
  TerminalOperation,
285
+ PartialComparisons,
232
286
  FormatToReturnValues,
233
287
  AttributeProxySymbol,
234
288
  ElectroInstanceTypes,
package/src/util.js CHANGED
@@ -220,6 +220,19 @@ function removePadding({padding = {}, value = ''} = {}) {
220
220
  return formatted;
221
221
  }
222
222
 
223
+ function shiftSortOrder(str = '', codePoint) {
224
+ let newString = '';
225
+ for (let i = 0; i < str.length; i++) {
226
+ const isLast = i === str.length - 1;
227
+ let char = str[i];
228
+ if (isLast) {
229
+ char = String.fromCodePoint(char.codePointAt(0) + codePoint);
230
+ }
231
+ newString += char;
232
+ }
233
+ return newString;
234
+ }
235
+
223
236
  module.exports = {
224
237
  getUnique,
225
238
  batchItems,
@@ -227,13 +240,14 @@ module.exports = {
227
240
  removePadding,
228
241
  removeFixings,
229
242
  parseJSONPath,
243
+ shiftSortOrder,
230
244
  getInstanceType,
231
245
  getModelVersion,
232
246
  formatKeyCasing,
247
+ cursorFormatter,
233
248
  genericizeJSONPath,
234
249
  commaSeparatedString,
235
250
  formatAttributeCasing,
236
- cursorFormatter,
237
251
  applyBetaModelOverrides,
238
252
  formatIndexNameForDisplay,
239
253
  BatchGetOrderMaintainer,
@@ -154,6 +154,11 @@ const Index = {
154
154
  },
155
155
  collection: {
156
156
  type: ["array", "string"]
157
+ },
158
+ type: {
159
+ type: 'string',
160
+ enum: ['clustered', 'isolated'],
161
+ required: false,
157
162
  }
158
163
  },
159
164
  };
package/src/where.js CHANGED
@@ -50,12 +50,8 @@ class FilterExpression extends ExpressionState {
50
50
  throw new Error(`Invalid operation: "${operation}". Please report`);
51
51
  }
52
52
  const names = this.setName({}, name, name);
53
- if (values.length) {
54
- for (const value of values) {
55
- this.setValue(name, value);
56
- }
57
- }
58
- const condition = template({}, name.expression, names.prop, ...values);
53
+ const valueExpressions = values.map(value => this.setValue(name, value));
54
+ const condition = template({}, names.expression, names.prop, ...valueExpressions);
59
55
  this.add(condition);
60
56
  }
61
57
 
@@ -70,19 +66,19 @@ class WhereFactory {
70
66
  this.operations = {...operations};
71
67
  }
72
68
 
73
- getExpressionType(methodType) {
74
- switch (methodType) {
75
- case MethodTypes.put:
76
- case MethodTypes.create:
77
- case MethodTypes.update:
78
- case MethodTypes.patch:
79
- case MethodTypes.delete:
80
- case MethodTypes.remove:
81
- return ExpressionTypes.ConditionExpression
82
- default:
83
- return ExpressionTypes.FilterExpression
84
- }
69
+ getExpressionType(methodType) {
70
+ switch (methodType) {
71
+ case MethodTypes.put:
72
+ case MethodTypes.create:
73
+ case MethodTypes.update:
74
+ case MethodTypes.patch:
75
+ case MethodTypes.delete:
76
+ case MethodTypes.remove:
77
+ return ExpressionTypes.ConditionExpression
78
+ default:
79
+ return ExpressionTypes.FilterExpression
85
80
  }
81
+ }
86
82
 
87
83
  buildClause(cb) {
88
84
  if (typeof cb !== "function") {