electrodb 2.5.1 → 2.6.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/entity.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  const { Schema } = require("./schema");
3
- const { AllPages,
3
+ const {
4
+ AllPages,
4
5
  KeyCasing,
5
6
  TableIndex,
6
7
  FormatToReturnValues,
@@ -35,7 +36,6 @@ const validations = require("./validations");
35
36
  const c = require('./client');
36
37
  const u = require("./util");
37
38
  const e = require("./errors");
38
- const { validate } = require("jsonschema");
39
39
  const v = require('./validations');
40
40
 
41
41
  class Entity {
@@ -59,6 +59,21 @@ class Entity {
59
59
  this._clausesWithFilters = this._whereBuilder.injectWhereClauses(this._clausesWithFilters);
60
60
 
61
61
  this.query = {};
62
+ this.conversions = {
63
+ fromComposite: {
64
+ toKeys: (composite, options = {}) => this._fromCompositeToKeys({ provided: composite }, options),
65
+ toCursor: (composite) => this._fromCompositeToCursor({ provided: composite }, { strict: 'all' }),
66
+ },
67
+ fromKeys: {
68
+ toCursor: (keys) => this._fromKeysToCursor({provided: keys}, {}),
69
+ toComposite: (keys) => this._fromKeysToComposite({ provided: keys }),
70
+ },
71
+ fromCursor: {
72
+ toKeys: (cursor) => this._fromCursorToKeys({provided: cursor}),
73
+ toComposite: (cursor) => this._fromCursorToComposite({provided: cursor}),
74
+ },
75
+ byAccessPattern: {}
76
+ };
62
77
  for (let accessPattern in this.model.indexes) {
63
78
  let index = this.model.indexes[accessPattern].index;
64
79
  this.query[accessPattern] = (...values) => {
@@ -67,7 +82,24 @@ class Entity {
67
82
  }
68
83
  return this._makeChain(index, this._clausesWithFilters, clauses.index, options).query(...values);
69
84
  };
85
+
86
+ this.conversions.byAccessPattern[accessPattern] = {
87
+ fromKeys: {
88
+ toCursor: (keys) => this._fromKeysToCursorByIndex({indexName: index, provided: keys}),
89
+ toComposite: (keys) => this._fromKeysToCompositeByIndex({indexName: index, provided: keys}),
90
+ },
91
+ fromCursor: {
92
+ toKeys: (cursor) => this._fromCursorToKeysByIndex({indexName: index, provided: cursor}),
93
+ toComposite: (cursor) => this._fromCursorToCompositeByIndex({indexName: index, provided: cursor}),
94
+ },
95
+ fromComposite: {
96
+ toCursor: (composite) => this._fromCompositeToCursorByIndex({indexName: index, provided: composite}, { strict: 'all' }),
97
+ toKeys: (composite, options = {}) => this._fromCompositeToKeysByIndex({indexName: index, provided: composite}, options),
98
+ },
99
+ };
100
+
70
101
  }
102
+
71
103
  this.config.identifiers = config.identifiers || {};
72
104
  this.identifiers = {
73
105
  entity: this.config.identifiers.entity || "__edb_e__",
@@ -98,6 +130,36 @@ class Entity {
98
130
  return this.model.version;
99
131
  }
100
132
 
133
+ // ownsItem(item) {
134
+ // return (
135
+ // item &&
136
+ // this.getName() === item[this.identifiers.entity] &&
137
+ // this.getVersion() === item[this.identifiers.version] &&
138
+ // validations.isStringHasLength(item[this.identifiers.entity]) &&
139
+ // validations.isStringHasLength(item[this.identifiers.version])
140
+ // ) || !!this.ownsKeys(item)
141
+ // }
142
+
143
+ // ownsKeys({keys = {}}) {
144
+ // let {pk, sk} = this.model.prefixes[TableIndex];
145
+ // let hasSK = this.model.lookup.indexHasSortKeys[TableIndex];
146
+ // let pkMatch = typeof keys[pk.field] === "string" && keys[pk.field].startsWith(pk.prefix);
147
+ // let skMatch = pkMatch && !hasSK;
148
+ // if (pkMatch && hasSK) {
149
+ // skMatch = typeof keys[sk.field] === "string" && keys[sk.field].startsWith(sk.prefix);
150
+ // }
151
+ //
152
+ // return (pkMatch && skMatch &&
153
+ // this._formatKeysToItem(TableIndex, key) !== null);
154
+ // }
155
+
156
+ // ownsCursor({ cursor }) {
157
+ // if (typeof cursor === 'string') {
158
+ // cursor = u.cursorFormatter.deserialize(cursor);
159
+ // }
160
+ // return this.ownsKeys({ keys: cursor });
161
+ // }
162
+
101
163
  ownsItem(item) {
102
164
  return (
103
165
  item &&
@@ -108,21 +170,43 @@ class Entity {
108
170
  )
109
171
  }
110
172
 
111
- ownsLastEvaluatedKey(key = {}) {
173
+ _attributesIncludeKeys(attributes = []) {
174
+ let {pk, sk} = this.model.prefixes[TableIndex];
175
+ let pkFound = false;
176
+ let skFound = false;
177
+ for (let i = 0; i < attributes.length; i++) {
178
+ const attribute = attributes[i];
179
+ if (attribute === sk.field) {
180
+ skFound = true;
181
+ }
182
+ if (attribute === pk.field) {
183
+ skFound = true;
184
+ }
185
+ if (pkFound && skFound) {
186
+ return true;
187
+ }
188
+ }
189
+ return false;
190
+ }
191
+
192
+ ownsKeys(key = {}) {
112
193
  let {pk, sk} = this.model.prefixes[TableIndex];
113
194
  let hasSK = this.model.lookup.indexHasSortKeys[TableIndex];
114
195
  let pkMatch = typeof key[pk.field] === "string" && key[pk.field].startsWith(pk.prefix);
196
+ let skMatch = pkMatch && !hasSK;
115
197
  if (pkMatch && hasSK) {
116
- return typeof key[sk.field] === "string" && key[sk.field].startsWith(sk.prefix);
198
+ skMatch = typeof key[sk.field] === "string" && key[sk.field].startsWith(sk.prefix);
117
199
  }
118
- return pkMatch;
200
+
201
+ return (pkMatch && skMatch &&
202
+ this._formatKeysToItem(TableIndex, key) !== null);
119
203
  }
120
204
 
121
205
  ownsCursor(cursor) {
122
206
  if (typeof cursor === 'string') {
123
207
  cursor = u.cursorFormatter.deserialize(cursor);
124
208
  }
125
- return this.ownsLastEvaluatedKey(cursor);
209
+ return this.ownsKeys(cursor);
126
210
  }
127
211
 
128
212
  serializeCursor(key) {
@@ -445,11 +529,54 @@ class Entity {
445
529
  return { data: resultsAll, unprocessed: unprocessedAll };
446
530
  }
447
531
 
532
+ async hydrate(index, keys = [], config) {
533
+ const items = [];
534
+ const validKeys = [];
535
+ for (let i = 0; i < keys.length; i++) {
536
+ const key = keys[i];
537
+ const item = this._formatKeysToItem(index, key);
538
+ if (item !== null) {
539
+ items.push(item);
540
+ validKeys.push(key);
541
+ }
542
+ }
543
+
544
+ const results = await this.get(items).go({
545
+ ...config,
546
+ hydrate: false,
547
+ parse: undefined,
548
+ hydrator: undefined,
549
+ _isCollectionQuery: false,
550
+ preserveBatchOrder: true,
551
+ ignoreOwnership: config._providedIgnoreOwnership
552
+ });
553
+
554
+ const unprocessed = [];
555
+ const data = [];
556
+
557
+ for (let i = 0; i < results.data.length; i++) {
558
+ const key = validKeys[i];
559
+ const item = results.data[i];
560
+ if (!item) {
561
+ if (key) {
562
+ unprocessed.push(key);
563
+ }
564
+ } else {
565
+ data.push(item);
566
+ }
567
+ }
568
+
569
+ return {
570
+ unprocessed,
571
+ data,
572
+ };
573
+ }
574
+
448
575
  async executeQuery(method, parameters, config = {}) {
449
576
  let results = config._isCollectionQuery
450
577
  ? {}
451
578
  : [];
452
- let ExclusiveStartKey = this._formatExclusiveStartKey(config);
579
+ let ExclusiveStartKey = this._formatExclusiveStartKey({config, indexName: parameters.IndexName });
453
580
  if (ExclusiveStartKey === null) {
454
581
  ExclusiveStartKey = undefined;
455
582
  }
@@ -457,13 +584,20 @@ class Entity {
457
584
  let max = this._normalizeLimitValue(config.limit);
458
585
  let iterations = 0;
459
586
  let count = 0;
587
+ let hydratedUnprocessed = [];
588
+ const shouldHydrate = config.hydrate && method === MethodTypes.query;
460
589
  do {
461
590
  let limit = max === undefined
462
591
  ? parameters.Limit
463
592
  : max - count;
464
- let response = await this._exec(method, {ExclusiveStartKey, ...parameters, Limit: limit}, config);
593
+ let response = await this._exec(method, { ExclusiveStartKey, ...parameters, Limit: limit }, config);
465
594
  ExclusiveStartKey = response.LastEvaluatedKey;
466
- response = this.formatResponse(response, parameters.IndexName, config);
595
+ response = this.formatResponse(response, parameters.IndexName, {
596
+ ...config,
597
+ includeKeys: shouldHydrate || config.includeKeys,
598
+ ignoreOwnership: shouldHydrate || config.ignoreOwnership,
599
+ });
600
+
467
601
  if (config.raw) {
468
602
  return response;
469
603
  } else if (config._isCollectionQuery) {
@@ -471,14 +605,26 @@ class Entity {
471
605
  if (max) {
472
606
  count += response.data[entity].length;
473
607
  }
608
+ let items = response.data[entity];
609
+ if (shouldHydrate && items.length) {
610
+ const hydrated = await config.hydrator(entity, parameters.IndexName, items, config);
611
+ items = hydrated.data;
612
+ hydratedUnprocessed = hydratedUnprocessed.concat(hydrated.unprocessed);
613
+ }
474
614
  results[entity] = results[entity] || [];
475
- results[entity] = [...results[entity], ...response.data[entity]];
615
+ results[entity] = [...results[entity], ...items];
476
616
  }
477
617
  } else if (Array.isArray(response.data)) {
478
618
  if (max) {
479
619
  count += response.data.length;
480
620
  }
481
- results = [...results, ...response.data];
621
+ let items = response.data;
622
+ if (shouldHydrate) {
623
+ const hydrated = await this.hydrate(parameters.IndexName, items, config);
624
+ items = hydrated.data;
625
+ hydratedUnprocessed = hydratedUnprocessed.concat(hydrated.unprocessed);
626
+ }
627
+ results = [...results, ...items];
482
628
  } else {
483
629
  return response;
484
630
  }
@@ -490,6 +636,14 @@ class Entity {
490
636
  );
491
637
 
492
638
  const cursor = this._formatReturnPager(config, ExclusiveStartKey);
639
+
640
+ if (shouldHydrate) {
641
+ return {
642
+ cursor,
643
+ data: results,
644
+ unprocessed: hydratedUnprocessed,
645
+ };
646
+ }
493
647
  return { data: results, cursor };
494
648
  }
495
649
 
@@ -628,7 +782,11 @@ class Entity {
628
782
  results = response;
629
783
  } else {
630
784
  if (response.Item) {
631
- if (config.ignoreOwnership || this.ownsItem(response.Item)) {
785
+ if (
786
+ (config.ignoreOwnership && config.attributes && config.attributes.length > 0 && !this._attributesIncludeKeys(config.attributes)) ||
787
+ ((config.ignoreOwnership || config.hydrate) && this.ownsKeys(response.Item)) ||
788
+ this.ownsItem(response.Item)
789
+ ) {
632
790
  results = this.model.schema.formatItemForRetrieval(response.Item, config);
633
791
  if (Object.keys(results).length === 0) {
634
792
  results = null;
@@ -639,7 +797,11 @@ class Entity {
639
797
  } else if (response.Items) {
640
798
  results = [];
641
799
  for (let item of response.Items) {
642
- if (config.ignoreOwnership || this.ownsItem(item)) {
800
+ if (
801
+ (config.ignoreOwnership && config.attributes && config.attributes.length > 0 && !this._attributesIncludeKeys(config.attributes)) ||
802
+ ((config.ignoreOwnership || config.hydrate) && this.ownsKeys(item)) ||
803
+ this.ownsItem(item)
804
+ ) {
643
805
  let record = this.model.schema.formatItemForRetrieval(item, config);
644
806
  if (Object.keys(record).length > 0) {
645
807
  results.push(record);
@@ -675,7 +837,6 @@ class Entity {
675
837
  }
676
838
  }
677
839
 
678
-
679
840
  parse(item, options = {}) {
680
841
  if (item === undefined || item === null) {
681
842
  return null;
@@ -687,6 +848,216 @@ class Entity {
687
848
  return this.formatResponse(item, TableIndex, config);
688
849
  }
689
850
 
851
+ _fromCompositeToKeys({provided}, options = {}) {
852
+ if (!provided || Object.keys(provided).length === 0) {
853
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCompositeProvided, 'Invalid conversion composite provided');
854
+ }
855
+
856
+ let keys = {};
857
+ const secondaryIndexStrictMode = options.strict === 'all' || options.strict === 'pk' ? 'pk' : 'none';
858
+ for (const { index } of Object.values(this.model.indexes)) {
859
+ const indexKeys = this._fromCompositeToKeysByIndex({ indexName: index, provided }, {
860
+ strict: index === TableIndex ? options.strict : secondaryIndexStrictMode,
861
+ });
862
+ if (indexKeys) {
863
+ keys = {
864
+ ...keys,
865
+ ...indexKeys,
866
+ }
867
+ }
868
+ }
869
+
870
+
871
+ if (Object.keys(keys).length === 0) {
872
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCompositeProvided, 'Invalid conversion composite provided');
873
+ }
874
+
875
+ return keys;
876
+ }
877
+
878
+ _fromCompositeToCursor({provided}, options = {}) {
879
+ const keys = this._fromCompositeToKeys({provided}, options);
880
+ if (!keys || Object.keys(keys).length === 0) {
881
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCompositeProvided, 'Invalid conversion composite provided');
882
+ }
883
+ return u.cursorFormatter.serialize(keys);
884
+ }
885
+
886
+ _fromKeysToCursor({provided}, options = {}) {
887
+ if (!provided || Object.keys(provided).length === 0) {
888
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, 'Invalid keys provided');
889
+ }
890
+ return u.cursorFormatter.serialize(provided);
891
+ }
892
+
893
+ _fromKeysToComposite({provided}, options = {}) {
894
+ if (!provided || Object.keys(provided).length === 0) {
895
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, 'Provided keys could not be used to form composite attributes');
896
+ }
897
+
898
+ let keys = {};
899
+ for (const { index } of Object.values(this.model.indexes)) {
900
+ const composite = this._fromKeysToCompositeByIndex({indexName: index, provided}, options);
901
+ if (composite) {
902
+ for (const attribute in composite) {
903
+ if (keys[attribute] === undefined) {
904
+ keys[attribute] = composite[attribute];
905
+ }
906
+ }
907
+ }
908
+ }
909
+
910
+ if (Object.keys(keys).length === 0) {
911
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, 'Provided keys could not be used to form composite attributes');
912
+ }
913
+
914
+ return keys;
915
+ }
916
+
917
+ _fromCursorToKeys({provided}, options = {}) {
918
+ if (typeof provided !== 'string') {
919
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCursorProvided, 'Invalid conversion cursor provided');
920
+ }
921
+
922
+ return u.cursorFormatter.deserialize(provided);
923
+ }
924
+
925
+ _fromCursorToComposite({provided}, options = {}) {
926
+ if (typeof provided !== 'string') {
927
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCursorProvided, 'Invalid conversion cursor provided');
928
+ }
929
+
930
+ const keys = this._fromCursorToKeys({provided}, options);
931
+ if (!keys){
932
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCursorProvided, 'Invalid conversion cursor provided');
933
+ }
934
+
935
+ return this._fromKeysToComposite({provided: keys}, options);
936
+ }
937
+
938
+ _fromCompositeToCursorByIndex({indexName = TableIndex, provided}, options = {}) {
939
+ if (!provided || Object.keys(provided).length === 0) {
940
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCompositeProvided, 'Invalid conversion composite provided');
941
+ }
942
+
943
+ const keys = this._formatSuppliedPager(indexName, provided, {
944
+ relaxedPk: false,
945
+ relaxedSk: false,
946
+ });
947
+
948
+ return this._fromKeysToCursorByIndex({indexName, provided: keys}, options);
949
+ }
950
+
951
+ _fromCompositeToKeysByIndex({indexName = TableIndex, provided}, options = {}) {
952
+ return this._formatSuppliedPager(indexName, provided, {
953
+ relaxedPk: options.strict !== 'pk' && options.strict !== 'all',
954
+ relaxedSk: options.strict !== 'all',
955
+ });
956
+ }
957
+
958
+ _fromCursorToKeysByIndex({ provided }, options = {}) {
959
+ if (typeof provided !== 'string' || provided.length < 1) {
960
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCursorProvided, 'Invalid conversion cursor provided');
961
+ }
962
+
963
+ return u.cursorFormatter.deserialize(provided);
964
+ }
965
+
966
+ _fromKeysToCursorByIndex({indexName = TableIndex, provided}, options = {}) {
967
+ const isValidTableIndex = this._verifyKeys({indexName: TableIndex, provided});
968
+ const isValidIndex = this._verifyKeys({indexName, provided});
969
+ if (!isValidTableIndex) {
970
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, 'Provided keys did not include valid properties for the primary index');
971
+ } else if (!isValidIndex) {
972
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, `Provided keys did not include valid properties for the index "${indexName}"`);
973
+ }
974
+
975
+ const keys = this._trimKeysToIndex({ indexName, provided });
976
+
977
+ if (!keys || Object.keys(keys).length === 0) {
978
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, `Provided keys not defined`);
979
+ }
980
+
981
+ return u.cursorFormatter.serialize(provided);
982
+ }
983
+
984
+ _fromKeysToCompositeByIndex({indexName = TableIndex, provided}, options = {}) {
985
+ let allKeys = {};
986
+
987
+ const indexKeys = this._deconstructIndex({ index: indexName, keys: provided });
988
+ if (!indexKeys) {
989
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, `Provided keys did not include valid properties for the index "${indexName}"`);
990
+ }
991
+
992
+ allKeys = {
993
+ ...indexKeys,
994
+ }
995
+
996
+ let tableKeys;
997
+ if (indexName !== TableIndex) {
998
+ tableKeys = this._deconstructIndex({index: TableIndex, keys: provided});
999
+ }
1000
+
1001
+ if (tableKeys === null) {
1002
+ return allKeys;
1003
+ }
1004
+
1005
+ allKeys = {
1006
+ ...allKeys,
1007
+ ...tableKeys,
1008
+ };
1009
+
1010
+ if (Object.keys(allKeys).length === 0) {
1011
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, 'Provided keys could not be used to form composite attributes');
1012
+ }
1013
+
1014
+ return allKeys;
1015
+ }
1016
+
1017
+ _fromCursorToCompositeByIndex({indexName = TableIndex, provided}, options = {}) {
1018
+ const keys = this._fromCursorToKeysByIndex({indexName, provided}, options);
1019
+ if (!keys || Object.keys(keys).length === 0) {
1020
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCursorProvided, 'Invalid conversion cursor provided');
1021
+ }
1022
+ return this._fromKeysToCompositeByIndex({indexName, provided: keys}, options);
1023
+ }
1024
+
1025
+ _trimKeysToIndex({indexName = TableIndex, provided}) {
1026
+ if (!provided) {
1027
+ return null;
1028
+ }
1029
+
1030
+ const pkName = this.model.translations.keys[indexName].pk;
1031
+ const skName = this.model.translations.keys[indexName].sk;
1032
+ const tablePKName = this.model.translations.keys[TableIndex].pk;
1033
+ const tableSKName = this.model.translations.keys[TableIndex].sk;
1034
+
1035
+ const keys = {
1036
+ [pkName]: provided[pkName],
1037
+ [skName]: provided[skName],
1038
+ [tablePKName]: provided[tablePKName],
1039
+ [tableSKName]: provided[tableSKName],
1040
+ };
1041
+
1042
+ if (!keys || Object.keys(keys).length === 0) {
1043
+ return null;
1044
+ }
1045
+
1046
+ return keys;
1047
+ }
1048
+
1049
+ _verifyKeys({indexName, provided}) {
1050
+ if (!provided) {
1051
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, `Provided keys not defined`);
1052
+ }
1053
+
1054
+ const pkName = this.model.translations.keys[indexName].pk;
1055
+ const skName = this.model.translations.keys[indexName].sk;
1056
+
1057
+ return provided[pkName] !== undefined &&
1058
+ (!skName || provided[skName] !== undefined);
1059
+ }
1060
+
690
1061
  _formatReturnPager(config, lastEvaluatedKey) {
691
1062
  let page = lastEvaluatedKey || null;
692
1063
  if (config.raw || config.pager === Pager.raw) {
@@ -695,12 +1066,22 @@ class Entity {
695
1066
  return config.formatCursor.serialize(page) || null;
696
1067
  }
697
1068
 
698
- _formatExclusiveStartKey(config) {
1069
+ _formatExclusiveStartKey({config, indexName = TableIndex}) {
699
1070
  let exclusiveStartKey = config.cursor;
700
1071
  if (config.raw || config.pager === Pager.raw) {
701
- return exclusiveStartKey || null;
1072
+ return this._trimKeysToIndex({ provided: exclusiveStartKey, indexName }) || null;
1073
+ }
1074
+ let keys;
1075
+ if (config.pager === Pager.item) {
1076
+ keys = this._fromCompositeToKeysByIndex({indexName, provided: exclusiveStartKey});
1077
+ } else {
1078
+ keys = config.formatCursor.deserialize(exclusiveStartKey);
1079
+ }
1080
+ if (!keys) {
1081
+ return null;
702
1082
  }
703
- return config.formatCursor.deserialize(exclusiveStartKey) || null;
1083
+
1084
+ return this._trimKeysToIndex({provided: keys, indexName}) || null;
704
1085
  }
705
1086
 
706
1087
  setClient(client) {
@@ -782,121 +1163,154 @@ class Entity {
782
1163
  return value;
783
1164
  }
784
1165
 
785
- _deconstructKeys(index, keyType, key, backupFacets = {}) {
786
- if (typeof key !== "string" || key.length === 0) {
787
- return null;
788
- }
789
-
790
- let accessPattern = this.model.translations.indexes.fromIndexToAccessPattern[index];
791
- let {prefix, isCustom} = this.model.prefixes[index][keyType];
792
- let {facets} = this.model.indexes[accessPattern][keyType];
1166
+ _createKeyDeconstructor(prefixes = {}, labels = [], attributes = {}) {
1167
+ let {prefix, isCustom, postfix} = prefixes;
793
1168
  let names = [];
794
1169
  let types = [];
795
- let pattern = `^${this._regexpEscape(prefix)}`;
796
- let labels = this.model.facets.labels[index][keyType] || [];
1170
+ let pattern = `^${this._regexpEscape(prefix || '')}`;
797
1171
  for (let {name, label} of labels) {
798
- let attr = this.model.schema.attributes[name];
1172
+ let attr = attributes[name];
1173
+ if (isCustom && !name && label) {
1174
+ // this case is for when someone uses a direct attribute reference but with a postfix (zoinks ;P)
1175
+ pattern += `${this._regexpEscape(label)}`;
1176
+ } else if (isCustom) {
1177
+ pattern += `${this._regexpEscape(label === undefined ? "" : label)}(.+)`;
1178
+ } else {
1179
+ pattern += `#${this._regexpEscape(label === undefined ? name : label)}_(.+)`;
1180
+ }
1181
+ names.push(name);
799
1182
  if (attr) {
800
- if (isCustom) {
801
- pattern += `${this._regexpEscape(label === undefined ? "" : label)}(.+)`;
802
- } else {
803
- pattern += `#${this._regexpEscape(label === undefined ? name : label)}_(.+)`;
804
- }
805
- names.push(name);
806
1183
  types.push(attr.type);
807
1184
  }
808
1185
  }
1186
+ if (typeof postfix === 'string') {
1187
+ pattern += this._regexpEscape(postfix);
1188
+ }
809
1189
  pattern += "$";
810
- let regex = RegExp(pattern);
811
- let match = key.match(regex);
812
- let results = {};
813
- if (match) {
814
- for (let i = 0; i < names.length; i++) {
815
- let key = names[i];
816
- let value = match[i+1];
817
- let type = types[i];
818
- switch (type) {
819
- case "number":
820
- value = parseFloat(value);
821
- break;
822
- case "boolean":
823
- value = value === "true";
824
- break;
825
- }
826
- results[key] = value;
827
- }
828
- } else {
829
- if (Object.keys(backupFacets || {}).length === 0) {
830
- // this can occur when a scan is performed but returns no results given the current filters or record timing
831
- return {};
1190
+
1191
+ let regex = new RegExp(pattern, "i");
1192
+
1193
+ return ({ key } = {}) => {
1194
+ if (!['string', 'number'].includes(typeof key)) {
1195
+ return null;
832
1196
  }
833
- for (let facet of facets) {
834
- if (backupFacets[facet] === undefined) {
835
- throw new e.ElectroError(e.ErrorCodes.LastEvaluatedKey, 'LastEvaluatedKey contains entity that does not match the entity used to query. Use {pager: "raw"} query option.');
836
- } else {
837
- results[facet] = backupFacets[facet];
1197
+ key = `${key}`;
1198
+ let match = key.match(regex);
1199
+ let results = {};
1200
+ if (match) {
1201
+ for (let i = 0; i < names.length; i++) {
1202
+ let key = names[i];
1203
+ let value = match[i + 1];
1204
+ let type = types[i];
1205
+ switch (type) {
1206
+ case "number": {
1207
+ value = parseFloat(value);
1208
+ break;
1209
+ }
1210
+ case "boolean": {
1211
+ value = value === "true";
1212
+ break;
1213
+ }
1214
+ }
1215
+ if (key && value !== undefined) {
1216
+ results[key] = value;
1217
+ }
838
1218
  }
1219
+ } else {
1220
+ results = null;
839
1221
  }
1222
+
1223
+ return results;
840
1224
  }
841
- return results;
842
1225
  }
843
1226
 
844
-
845
-
846
- _deconstructIndex(index = TableIndex, lastEvaluated, lastReturned) {
1227
+ _deconstructIndex({index = TableIndex, keys = {}} = {}) {
1228
+ const hasIndex = !!this.model.translations.keys[index];
1229
+ if (!hasIndex) {
1230
+ return null;
1231
+ }
847
1232
  let pkName = this.model.translations.keys[index].pk;
848
1233
  let skName = this.model.translations.keys[index].sk;
849
- let pkFacets = this._deconstructKeys(index, KeyTypes.pk, lastEvaluated[pkName], lastReturned);
850
- let skFacets = this._deconstructKeys(index, KeyTypes.sk, lastEvaluated[skName], lastReturned);
851
- let facets = {...pkFacets};
852
- if (skFacets && Object.keys(skFacets).length) {
853
- facets = {...skFacets, ...pkFacets};
1234
+ const indexHasSortKey = this.model.lookup.indexHasSortKeys[index];
1235
+ const deconstructors = this.model.keys.deconstructors[index];
1236
+ const pk = keys[pkName];
1237
+ if (pk === undefined) {
1238
+ return null;
1239
+ }
1240
+ const pkComposites = deconstructors.pk({key: pk});
1241
+ if (pkComposites === null) {
1242
+ return null;
1243
+ }
1244
+ let skComposites = {};
1245
+ if (indexHasSortKey) {
1246
+ const sk = keys[skName];
1247
+ if (!sk) {
1248
+ return null;
1249
+ }
1250
+ skComposites = deconstructors.sk({key: sk});
1251
+ if (skComposites === null) {
1252
+ return null;
1253
+ }
1254
+ }
1255
+ return {
1256
+ ...pkComposites,
1257
+ ...skComposites,
854
1258
  }
855
- return facets;
856
1259
  }
857
1260
 
858
- _formatKeysToItem(index = TableIndex, lastEvaluated, lastReturned) {
859
- if (lastEvaluated === null || typeof lastEvaluated !== "object" || Object.keys(lastEvaluated).length === 0) {
860
- return lastEvaluated;
1261
+ _formatKeysToItem(index = TableIndex, keys) {
1262
+ if (keys === null || typeof keys !== "object" || Object.keys(keys).length === 0) {
1263
+ return keys;
861
1264
  }
862
1265
  let tableIndex = TableIndex;
863
- let pager = this._deconstructIndex(index, lastEvaluated, lastReturned);
1266
+ let indexParts = this._deconstructIndex({index, keys});
1267
+ if (indexParts === null) {
1268
+ return null;
1269
+ }
864
1270
  // lastEvaluatedKeys from query calls include the index pk/sk as well as the table index's pk/sk
865
1271
  if (index !== tableIndex) {
866
- pager = {...pager, ...this._deconstructIndex(tableIndex, lastEvaluated, lastReturned)};
1272
+ const tableIndexParts = this._deconstructIndex({index: tableIndex, keys});
1273
+ if (tableIndexParts === null) {
1274
+ return null;
1275
+ }
1276
+ indexParts = { ...indexParts, ...tableIndexParts };
867
1277
  }
868
- let pagerIsEmpty = Object.keys(pager).length === 0;
869
- let pagerIsIncomplete = this.model.facets.byIndex[tableIndex].all.find(facet => pager[facet.name] === undefined);
870
- if (pagerIsEmpty || pagerIsIncomplete) {
1278
+ let noPartsFound = Object.keys(indexParts).length === 0;
1279
+ let partsAreIncomplete = this.model.facets.byIndex[tableIndex].all.find(facet => indexParts[facet.name] === undefined);
1280
+ if (noPartsFound || partsAreIncomplete) {
871
1281
  // In this case no suitable record could be found be the deconstructed pager.
872
1282
  // This can be valid in cases where a scan is performed but returns no results.
873
1283
  return null;
874
1284
  }
875
1285
 
876
- return pager;
1286
+ return indexParts;
877
1287
  }
878
1288
 
879
- _constructPagerIndex(index = TableIndex, item) {
880
- let pkAttributes = this._expectFacets(item, this.model.facets.byIndex[index].pk);
881
- let skAttributes = this._expectFacets(item, this.model.facets.byIndex[index].sk);
1289
+ _constructPagerIndex(index = TableIndex, item, options = {}) {
1290
+ let pkAttributes = options.relaxedPk ? item : this._expectFacets(item, this.model.facets.byIndex[index].pk);
1291
+ let skAttributes = options.relaxedSk ? item : this._expectFacets(item, this.model.facets.byIndex[index].sk);
1292
+
882
1293
  let keys = this._makeIndexKeys({
883
1294
  index,
884
1295
  pkAttributes,
885
1296
  skAttributes: [skAttributes],
886
1297
  });
1298
+
887
1299
  return this._makeParameterKey(index, keys.pk, ...keys.sk);
888
1300
  }
889
1301
 
890
- _formatSuppliedPager(index = TableIndex, item) {
1302
+ _formatSuppliedPager(index = TableIndex, item, options = {}) {
891
1303
  if (typeof item !== "object" || Object.keys(item).length === 0) {
892
1304
  return item;
893
1305
  }
1306
+
894
1307
  let tableIndex = TableIndex;
895
- let pager = this._constructPagerIndex(index, item);
1308
+ let pager = this._constructPagerIndex(index, item, options);
896
1309
  if (index !== tableIndex) {
897
- pager = {...pager, ...this._constructPagerIndex(tableIndex, item)}
1310
+ pager = {...pager, ...this._constructPagerIndex(tableIndex, item, options)};
898
1311
  }
899
- return pager
1312
+
1313
+ return pager;
900
1314
  }
901
1315
 
902
1316
  _normalizeExecutionOptions({ provided = [], context = {} } = {}) {
@@ -916,6 +1330,7 @@ class Entity {
916
1330
  cursor: null,
917
1331
  data: 'attributes',
918
1332
  ignoreOwnership: false,
1333
+ _providedIgnoreOwnership: false,
919
1334
  _isPagination: false,
920
1335
  _isCollectionQuery: false,
921
1336
  pages: 1,
@@ -925,6 +1340,8 @@ class Entity {
925
1340
  terminalOperation: undefined,
926
1341
  formatCursor: u.cursorFormatter,
927
1342
  order: undefined,
1343
+ hydrate: false,
1344
+ hydrator: (_entity, _indexName, items) => items,
928
1345
  };
929
1346
 
930
1347
  return provided.filter(Boolean).reduce((config, option) => {
@@ -1060,6 +1477,7 @@ class Entity {
1060
1477
 
1061
1478
  if (option.ignoreOwnership) {
1062
1479
  config.ignoreOwnership = option.ignoreOwnership;
1480
+ config._providedIgnoreOwnership = option.ignoreOwnership;
1063
1481
  }
1064
1482
 
1065
1483
  if (option.listeners) {
@@ -1076,6 +1494,15 @@ class Entity {
1076
1494
  }
1077
1495
  }
1078
1496
 
1497
+ if (option.hydrate) {
1498
+ config.hydrate = true;
1499
+ config.ignoreOwnership = true;
1500
+ }
1501
+
1502
+ if (validations.isFunction(option.hydrator)) {
1503
+ config.hydrator = option.hydrator;
1504
+ }
1505
+
1079
1506
  config.page = Object.assign({}, config.page, option.page);
1080
1507
  config.params = Object.assign({}, config.params, option.params);
1081
1508
  return config;
@@ -1520,12 +1947,12 @@ class Entity {
1520
1947
  };
1521
1948
  }
1522
1949
 
1523
- _makeUpsertParams({update, upsert} = {}, pk, sk) {
1950
+ _makeUpsertParams({ update, upsert } = {}, pk, sk) {
1524
1951
  const { updatedKeys, setAttributes, indexKey } = this._getPutKeys(pk, sk && sk.facets, upsert.data);
1525
1952
  const upsertAttributes = this.model.schema.translateToFields(setAttributes);
1526
1953
  const keyNames = Object.keys(indexKey);
1527
- update.set(this.identifiers.entity, this.getName());
1528
- update.set(this.identifiers.version, this.getVersion());
1954
+ // update.set(this.identifiers.entity, this.getName());
1955
+ // update.set(this.identifiers.version, this.getVersion());
1529
1956
  for (const field of [...Object.keys(upsertAttributes), ...Object.keys(updatedKeys)]) {
1530
1957
  const value = u.getFirstDefined(upsertAttributes[field], updatedKeys[field]);
1531
1958
  if (!keyNames.includes(field)) {
@@ -2238,7 +2665,11 @@ class Entity {
2238
2665
  const subCollections = this.model.subCollections[collection];
2239
2666
  const index = this.model.translations.collections.fromCollectionToIndex[collection];
2240
2667
  const accessPattern = this.model.translations.indexes.fromIndexToAccessPattern[index];
2668
+ const prefixes = this.model.prefixes[index];
2241
2669
  const prefix = this._makeCollectionPrefix(subCollections);
2670
+ if (prefixes.sk && prefixes.sk.isCustom) {
2671
+ return '';
2672
+ }
2242
2673
  return this._formatKeyCasing(accessPattern, prefix);
2243
2674
  }
2244
2675
 
@@ -2293,7 +2724,8 @@ class Entity {
2293
2724
  if (!prefixes) {
2294
2725
  throw new Error(`Invalid index: ${index}`);
2295
2726
  }
2296
- let partitionKey = this._makeKey(prefixes.pk, facets.pk, pkFacets, this.model.facets.labels[index].pk, {excludeLabelTail: true});
2727
+ // let partitionKey = this._makeKey(prefixes.pk, facets.pk, pkFacets, this.model.facets.labels[index].pk, { excludeLabelTail: true });
2728
+ let partitionKey = this._makeKey(prefixes.pk, facets.pk, pkFacets, this.model.facets.labels[index].pk);
2297
2729
  let pk = partitionKey.key;
2298
2730
  let sk = [];
2299
2731
  let fulfilled = false;
@@ -2388,6 +2820,7 @@ class Entity {
2388
2820
  key: supplied[facets[0]],
2389
2821
  };
2390
2822
  }
2823
+
2391
2824
  let key = prefix;
2392
2825
  let foundCount = 0;
2393
2826
  for (let i = 0; i < labels.length; i++) {
@@ -2425,9 +2858,11 @@ class Entity {
2425
2858
  key += postfix;
2426
2859
  }
2427
2860
 
2861
+ const transformedKey = transform(u.formatKeyCasing(key, casing));
2862
+
2428
2863
  return {
2429
2864
  fulfilled,
2430
- key: transform(u.formatKeyCasing(key, casing))
2865
+ key: transformedKey,
2431
2866
  };
2432
2867
  }
2433
2868
 
@@ -3125,6 +3560,17 @@ class Entity {
3125
3560
  indexes[indexAccessPattern.fromIndexToAccessPattern[indexName]].pk.labels = facets.labels[indexName].pk;
3126
3561
  indexes[indexAccessPattern.fromIndexToAccessPattern[indexName]].sk.labels = facets.labels[indexName].sk;
3127
3562
  }
3563
+ const deconstructors = {};
3564
+ for (const indexName of Object.keys(facets.labels)) {
3565
+ const keyTypes = prefixes[indexName] || {};
3566
+ deconstructors[indexName] = {};
3567
+ for (const keyType in keyTypes) {
3568
+ const prefixes = keyTypes[keyType];
3569
+ const labels = facets.labels[indexName][keyType] || [];
3570
+ const attributes = schema.attributes;
3571
+ deconstructors[indexName][keyType] = this._createKeyDeconstructor(prefixes, labels, attributes);
3572
+ }
3573
+ }
3128
3574
 
3129
3575
  return {
3130
3576
  name,
@@ -3150,6 +3596,9 @@ class Entity {
3150
3596
  indexes: indexAccessPattern,
3151
3597
  collections: indexCollection,
3152
3598
  },
3599
+ keys: {
3600
+ deconstructors,
3601
+ },
3153
3602
  original: model,
3154
3603
  };
3155
3604
  }
@@ -3173,20 +3622,31 @@ function getEntityIdentifiers(entities) {
3173
3622
  return identifiers;
3174
3623
  }
3175
3624
 
3176
- function matchToEntityAlias({ paramItem, identifiers, record } = {}) {
3625
+ function matchToEntityAlias({ paramItem, identifiers, record, entities = {}, allowMatchOnKeys = false } = {}) {
3177
3626
  let entity;
3178
- let entityAlias;
3179
-
3180
3627
  if (paramItem && v.isFunction(paramItem[TransactionCommitSymbol])) {
3181
3628
  const committed = paramItem[TransactionCommitSymbol]();
3182
3629
  entity = committed.entity;
3183
3630
  }
3184
3631
 
3632
+ let entityAlias;
3185
3633
  for (let {name, version, nameField, versionField, alias} of identifiers) {
3186
3634
  if (entity && entity.model.entity === name && entity.model.version === version) {
3187
3635
  entityAlias = alias;
3188
3636
  break;
3189
- } else if (record[nameField] !== undefined && record[nameField] === name && record[versionField] !== undefined && record[versionField] === version) {
3637
+ } else if (record[nameField] !== undefined && record[versionField] !== undefined && record[nameField] === name && record[versionField] === version) {
3638
+ entityAlias = alias;
3639
+ break;
3640
+ // } else if (allowMatchOnKeys && entities[alias] && entities[alias].ownsKeys({keys: record})) {
3641
+ // if (entityAlias) {
3642
+ // if (alias !== entityAlias) {
3643
+ // throw new Error('Key ownership found to be not distinct');
3644
+ // }
3645
+ // } else {
3646
+ // entityAlias = alias;
3647
+ // }
3648
+ // }
3649
+ } else if (entities[alias] && entities[alias].ownsKeys(record)) {
3190
3650
  entityAlias = alias;
3191
3651
  break;
3192
3652
  }