electrodb 2.5.1 → 2.6.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/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,10 +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
  : [];
579
+ // let ExclusiveStartKey = this._formatExclusiveStartKey({config, indexName: parameters.IndexName });
452
580
  let ExclusiveStartKey = this._formatExclusiveStartKey(config);
453
581
  if (ExclusiveStartKey === null) {
454
582
  ExclusiveStartKey = undefined;
@@ -457,13 +585,20 @@ class Entity {
457
585
  let max = this._normalizeLimitValue(config.limit);
458
586
  let iterations = 0;
459
587
  let count = 0;
588
+ let hydratedUnprocessed = [];
589
+ const shouldHydrate = config.hydrate && method === MethodTypes.query;
460
590
  do {
461
591
  let limit = max === undefined
462
592
  ? parameters.Limit
463
593
  : max - count;
464
- let response = await this._exec(method, {ExclusiveStartKey, ...parameters, Limit: limit}, config);
594
+ let response = await this._exec(method, { ExclusiveStartKey, ...parameters, Limit: limit }, config);
465
595
  ExclusiveStartKey = response.LastEvaluatedKey;
466
- response = this.formatResponse(response, parameters.IndexName, config);
596
+ response = this.formatResponse(response, parameters.IndexName, {
597
+ ...config,
598
+ includeKeys: shouldHydrate || config.includeKeys,
599
+ ignoreOwnership: shouldHydrate || config.ignoreOwnership,
600
+ });
601
+
467
602
  if (config.raw) {
468
603
  return response;
469
604
  } else if (config._isCollectionQuery) {
@@ -471,14 +606,26 @@ class Entity {
471
606
  if (max) {
472
607
  count += response.data[entity].length;
473
608
  }
609
+ let items = response.data[entity];
610
+ if (shouldHydrate && items.length) {
611
+ const hydrated = await config.hydrator(entity, parameters.IndexName, items, config);
612
+ items = hydrated.data;
613
+ hydratedUnprocessed = hydratedUnprocessed.concat(hydrated.unprocessed);
614
+ }
474
615
  results[entity] = results[entity] || [];
475
- results[entity] = [...results[entity], ...response.data[entity]];
616
+ results[entity] = [...results[entity], ...items];
476
617
  }
477
618
  } else if (Array.isArray(response.data)) {
478
619
  if (max) {
479
620
  count += response.data.length;
480
621
  }
481
- results = [...results, ...response.data];
622
+ let items = response.data;
623
+ if (shouldHydrate) {
624
+ const hydrated = await this.hydrate(parameters.IndexName, items, config);
625
+ items = hydrated.data;
626
+ hydratedUnprocessed = hydratedUnprocessed.concat(hydrated.unprocessed);
627
+ }
628
+ results = [...results, ...items];
482
629
  } else {
483
630
  return response;
484
631
  }
@@ -490,6 +637,14 @@ class Entity {
490
637
  );
491
638
 
492
639
  const cursor = this._formatReturnPager(config, ExclusiveStartKey);
640
+
641
+ if (shouldHydrate) {
642
+ return {
643
+ cursor,
644
+ data: results,
645
+ unprocessed: hydratedUnprocessed,
646
+ };
647
+ }
493
648
  return { data: results, cursor };
494
649
  }
495
650
 
@@ -628,7 +783,11 @@ class Entity {
628
783
  results = response;
629
784
  } else {
630
785
  if (response.Item) {
631
- if (config.ignoreOwnership || this.ownsItem(response.Item)) {
786
+ if (
787
+ (config.ignoreOwnership && config.attributes && config.attributes.length > 0 && !this._attributesIncludeKeys(config.attributes)) ||
788
+ ((config.ignoreOwnership || config.hydrate) && this.ownsKeys(response.Item)) ||
789
+ this.ownsItem(response.Item)
790
+ ) {
632
791
  results = this.model.schema.formatItemForRetrieval(response.Item, config);
633
792
  if (Object.keys(results).length === 0) {
634
793
  results = null;
@@ -639,7 +798,11 @@ class Entity {
639
798
  } else if (response.Items) {
640
799
  results = [];
641
800
  for (let item of response.Items) {
642
- if (config.ignoreOwnership || this.ownsItem(item)) {
801
+ if (
802
+ (config.ignoreOwnership && config.attributes && config.attributes.length > 0 && !this._attributesIncludeKeys(config.attributes)) ||
803
+ ((config.ignoreOwnership || config.hydrate) && this.ownsKeys(item)) ||
804
+ this.ownsItem(item)
805
+ ) {
643
806
  let record = this.model.schema.formatItemForRetrieval(item, config);
644
807
  if (Object.keys(record).length > 0) {
645
808
  results.push(record);
@@ -675,7 +838,6 @@ class Entity {
675
838
  }
676
839
  }
677
840
 
678
-
679
841
  parse(item, options = {}) {
680
842
  if (item === undefined || item === null) {
681
843
  return null;
@@ -687,6 +849,216 @@ class Entity {
687
849
  return this.formatResponse(item, TableIndex, config);
688
850
  }
689
851
 
852
+ _fromCompositeToKeys({provided}, options = {}) {
853
+ if (!provided || Object.keys(provided).length === 0) {
854
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCompositeProvided, 'Invalid conversion composite provided');
855
+ }
856
+
857
+ let keys = {};
858
+ const secondaryIndexStrictMode = options.strict === 'all' || options.strict === 'pk' ? 'pk' : 'none';
859
+ for (const { index } of Object.values(this.model.indexes)) {
860
+ const indexKeys = this._fromCompositeToKeysByIndex({ indexName: index, provided }, {
861
+ strict: index === TableIndex ? options.strict : secondaryIndexStrictMode,
862
+ });
863
+ if (indexKeys) {
864
+ keys = {
865
+ ...keys,
866
+ ...indexKeys,
867
+ }
868
+ }
869
+ }
870
+
871
+
872
+ if (Object.keys(keys).length === 0) {
873
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCompositeProvided, 'Invalid conversion composite provided');
874
+ }
875
+
876
+ return keys;
877
+ }
878
+
879
+ _fromCompositeToCursor({provided}, options = {}) {
880
+ const keys = this._fromCompositeToKeys({provided}, options);
881
+ if (!keys || Object.keys(keys).length === 0) {
882
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCompositeProvided, 'Invalid conversion composite provided');
883
+ }
884
+ return u.cursorFormatter.serialize(keys);
885
+ }
886
+
887
+ _fromKeysToCursor({provided}, options = {}) {
888
+ if (!provided || Object.keys(provided).length === 0) {
889
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, 'Invalid keys provided');
890
+ }
891
+ return u.cursorFormatter.serialize(provided);
892
+ }
893
+
894
+ _fromKeysToComposite({provided}, options = {}) {
895
+ if (!provided || Object.keys(provided).length === 0) {
896
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, 'Provided keys could not be used to form composite attributes');
897
+ }
898
+
899
+ let keys = {};
900
+ for (const { index } of Object.values(this.model.indexes)) {
901
+ const composite = this._fromKeysToCompositeByIndex({indexName: index, provided}, options);
902
+ if (composite) {
903
+ for (const attribute in composite) {
904
+ if (keys[attribute] === undefined) {
905
+ keys[attribute] = composite[attribute];
906
+ }
907
+ }
908
+ }
909
+ }
910
+
911
+ if (Object.keys(keys).length === 0) {
912
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, 'Provided keys could not be used to form composite attributes');
913
+ }
914
+
915
+ return keys;
916
+ }
917
+
918
+ _fromCursorToKeys({provided}, options = {}) {
919
+ if (typeof provided !== 'string') {
920
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCursorProvided, 'Invalid conversion cursor provided');
921
+ }
922
+
923
+ return u.cursorFormatter.deserialize(provided);
924
+ }
925
+
926
+ _fromCursorToComposite({provided}, options = {}) {
927
+ if (typeof provided !== 'string') {
928
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCursorProvided, 'Invalid conversion cursor provided');
929
+ }
930
+
931
+ const keys = this._fromCursorToKeys({provided}, options);
932
+ if (!keys){
933
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCursorProvided, 'Invalid conversion cursor provided');
934
+ }
935
+
936
+ return this._fromKeysToComposite({provided: keys}, options);
937
+ }
938
+
939
+ _fromCompositeToCursorByIndex({indexName = TableIndex, provided}, options = {}) {
940
+ if (!provided || Object.keys(provided).length === 0) {
941
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCompositeProvided, 'Invalid conversion composite provided');
942
+ }
943
+
944
+ const keys = this._formatSuppliedPager(indexName, provided, {
945
+ relaxedPk: false,
946
+ relaxedSk: false,
947
+ });
948
+
949
+ return this._fromKeysToCursorByIndex({indexName, provided: keys}, options);
950
+ }
951
+
952
+ _fromCompositeToKeysByIndex({indexName = TableIndex, provided}, options = {}) {
953
+ return this._formatSuppliedPager(indexName, provided, {
954
+ relaxedPk: options.strict !== 'pk' && options.strict !== 'all',
955
+ relaxedSk: options.strict !== 'all',
956
+ });
957
+ }
958
+
959
+ _fromCursorToKeysByIndex({ provided }, options = {}) {
960
+ if (typeof provided !== 'string' || provided.length < 1) {
961
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCursorProvided, 'Invalid conversion cursor provided');
962
+ }
963
+
964
+ return u.cursorFormatter.deserialize(provided);
965
+ }
966
+
967
+ _fromKeysToCursorByIndex({indexName = TableIndex, provided}, options = {}) {
968
+ const isValidTableIndex = this._verifyKeys({indexName: TableIndex, provided});
969
+ const isValidIndex = this._verifyKeys({indexName, provided});
970
+ if (!isValidTableIndex) {
971
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, 'Provided keys did not include valid properties for the primary index');
972
+ } else if (!isValidIndex) {
973
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, `Provided keys did not include valid properties for the index "${indexName}"`);
974
+ }
975
+
976
+ const keys = this._trimKeysToIndex({ indexName, provided });
977
+
978
+ if (!keys || Object.keys(keys).length === 0) {
979
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, `Provided keys not defined`);
980
+ }
981
+
982
+ return u.cursorFormatter.serialize(provided);
983
+ }
984
+
985
+ _fromKeysToCompositeByIndex({indexName = TableIndex, provided}, options = {}) {
986
+ let allKeys = {};
987
+
988
+ const indexKeys = this._deconstructIndex({ index: indexName, keys: provided });
989
+ if (!indexKeys) {
990
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, `Provided keys did not include valid properties for the index "${indexName}"`);
991
+ }
992
+
993
+ allKeys = {
994
+ ...indexKeys,
995
+ }
996
+
997
+ let tableKeys;
998
+ if (indexName !== TableIndex) {
999
+ tableKeys = this._deconstructIndex({index: TableIndex, keys: provided});
1000
+ }
1001
+
1002
+ if (tableKeys === null) {
1003
+ return allKeys;
1004
+ }
1005
+
1006
+ allKeys = {
1007
+ ...allKeys,
1008
+ ...tableKeys,
1009
+ };
1010
+
1011
+ if (Object.keys(allKeys).length === 0) {
1012
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, 'Provided keys could not be used to form composite attributes');
1013
+ }
1014
+
1015
+ return allKeys;
1016
+ }
1017
+
1018
+ _fromCursorToCompositeByIndex({indexName = TableIndex, provided}, options = {}) {
1019
+ const keys = this._fromCursorToKeysByIndex({indexName, provided}, options);
1020
+ if (!keys || Object.keys(keys).length === 0) {
1021
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionCursorProvided, 'Invalid conversion cursor provided');
1022
+ }
1023
+ return this._fromKeysToCompositeByIndex({indexName, provided: keys}, options);
1024
+ }
1025
+
1026
+ _trimKeysToIndex({indexName = TableIndex, provided}) {
1027
+ if (!provided) {
1028
+ return null;
1029
+ }
1030
+
1031
+ const pkName = this.model.translations.keys[indexName].pk;
1032
+ const skName = this.model.translations.keys[indexName].sk;
1033
+ const tablePKName = this.model.translations.keys[TableIndex].pk;
1034
+ const tableSKName = this.model.translations.keys[TableIndex].sk;
1035
+
1036
+ const keys = {
1037
+ [pkName]: provided[pkName],
1038
+ [skName]: provided[skName],
1039
+ [tablePKName]: provided[tablePKName],
1040
+ [tableSKName]: provided[tableSKName],
1041
+ };
1042
+
1043
+ if (!keys || Object.keys(keys).length === 0) {
1044
+ return null;
1045
+ }
1046
+
1047
+ return keys;
1048
+ }
1049
+
1050
+ _verifyKeys({indexName, provided}) {
1051
+ if (!provided) {
1052
+ throw new e.ElectroError(e.ErrorCodes.InvalidConversionKeysProvided, `Provided keys not defined`);
1053
+ }
1054
+
1055
+ const pkName = this.model.translations.keys[indexName].pk;
1056
+ const skName = this.model.translations.keys[indexName].sk;
1057
+
1058
+ return provided[pkName] !== undefined &&
1059
+ (!skName || provided[skName] !== undefined);
1060
+ }
1061
+
690
1062
  _formatReturnPager(config, lastEvaluatedKey) {
691
1063
  let page = lastEvaluatedKey || null;
692
1064
  if (config.raw || config.pager === Pager.raw) {
@@ -695,6 +1067,24 @@ class Entity {
695
1067
  return config.formatCursor.serialize(page) || null;
696
1068
  }
697
1069
 
1070
+ // _formatExclusiveStartKey({config, indexName = TableIndex}) {
1071
+ // let exclusiveStartKey = config.cursor;
1072
+ // if (config.raw || config.pager === Pager.raw) {
1073
+ // return this._trimKeysToIndex({ provided: exclusiveStartKey, indexName }) || null;
1074
+ // }
1075
+ // let keys;
1076
+ // if (config.pager === Pager.item) {
1077
+ // keys = this._fromCompositeToKeysByIndex({indexName, provided: exclusiveStartKey});
1078
+ // } else {
1079
+ // keys = config.formatCursor.deserialize(exclusiveStartKey);
1080
+ // }
1081
+ // if (!keys) {
1082
+ // return null;
1083
+ // }
1084
+ //
1085
+ // return this._trimKeysToIndex({provided: keys, indexName}) || null;
1086
+ // }
1087
+
698
1088
  _formatExclusiveStartKey(config) {
699
1089
  let exclusiveStartKey = config.cursor;
700
1090
  if (config.raw || config.pager === Pager.raw) {
@@ -782,121 +1172,154 @@ class Entity {
782
1172
  return value;
783
1173
  }
784
1174
 
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];
1175
+ _createKeyDeconstructor(prefixes = {}, labels = [], attributes = {}) {
1176
+ let {prefix, isCustom, postfix} = prefixes;
793
1177
  let names = [];
794
1178
  let types = [];
795
- let pattern = `^${this._regexpEscape(prefix)}`;
796
- let labels = this.model.facets.labels[index][keyType] || [];
1179
+ let pattern = `^${this._regexpEscape(prefix || '')}`;
797
1180
  for (let {name, label} of labels) {
798
- let attr = this.model.schema.attributes[name];
1181
+ let attr = attributes[name];
1182
+ if (isCustom && !name && label) {
1183
+ // this case is for when someone uses a direct attribute reference but with a postfix (zoinks ;P)
1184
+ pattern += `${this._regexpEscape(label)}`;
1185
+ } else if (isCustom) {
1186
+ pattern += `${this._regexpEscape(label === undefined ? "" : label)}(.+)`;
1187
+ } else {
1188
+ pattern += `#${this._regexpEscape(label === undefined ? name : label)}_(.+)`;
1189
+ }
1190
+ names.push(name);
799
1191
  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
1192
  types.push(attr.type);
807
1193
  }
808
1194
  }
1195
+ if (typeof postfix === 'string') {
1196
+ pattern += this._regexpEscape(postfix);
1197
+ }
809
1198
  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 {};
1199
+
1200
+ let regex = new RegExp(pattern, "i");
1201
+
1202
+ return ({ key } = {}) => {
1203
+ if (!['string', 'number'].includes(typeof key)) {
1204
+ return null;
832
1205
  }
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];
1206
+ key = `${key}`;
1207
+ let match = key.match(regex);
1208
+ let results = {};
1209
+ if (match) {
1210
+ for (let i = 0; i < names.length; i++) {
1211
+ let key = names[i];
1212
+ let value = match[i + 1];
1213
+ let type = types[i];
1214
+ switch (type) {
1215
+ case "number": {
1216
+ value = parseFloat(value);
1217
+ break;
1218
+ }
1219
+ case "boolean": {
1220
+ value = value === "true";
1221
+ break;
1222
+ }
1223
+ }
1224
+ if (key && value !== undefined) {
1225
+ results[key] = value;
1226
+ }
838
1227
  }
1228
+ } else {
1229
+ results = null;
839
1230
  }
1231
+
1232
+ return results;
840
1233
  }
841
- return results;
842
1234
  }
843
1235
 
844
-
845
-
846
- _deconstructIndex(index = TableIndex, lastEvaluated, lastReturned) {
1236
+ _deconstructIndex({index = TableIndex, keys = {}} = {}) {
1237
+ const hasIndex = !!this.model.translations.keys[index];
1238
+ if (!hasIndex) {
1239
+ return null;
1240
+ }
847
1241
  let pkName = this.model.translations.keys[index].pk;
848
1242
  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};
1243
+ const indexHasSortKey = this.model.lookup.indexHasSortKeys[index];
1244
+ const deconstructors = this.model.keys.deconstructors[index];
1245
+ const pk = keys[pkName];
1246
+ if (pk === undefined) {
1247
+ return null;
1248
+ }
1249
+ const pkComposites = deconstructors.pk({key: pk});
1250
+ if (pkComposites === null) {
1251
+ return null;
1252
+ }
1253
+ let skComposites = {};
1254
+ if (indexHasSortKey) {
1255
+ const sk = keys[skName];
1256
+ if (!sk) {
1257
+ return null;
1258
+ }
1259
+ skComposites = deconstructors.sk({key: sk});
1260
+ if (skComposites === null) {
1261
+ return null;
1262
+ }
1263
+ }
1264
+ return {
1265
+ ...pkComposites,
1266
+ ...skComposites,
854
1267
  }
855
- return facets;
856
1268
  }
857
1269
 
858
- _formatKeysToItem(index = TableIndex, lastEvaluated, lastReturned) {
859
- if (lastEvaluated === null || typeof lastEvaluated !== "object" || Object.keys(lastEvaluated).length === 0) {
860
- return lastEvaluated;
1270
+ _formatKeysToItem(index = TableIndex, keys) {
1271
+ if (keys === null || typeof keys !== "object" || Object.keys(keys).length === 0) {
1272
+ return keys;
861
1273
  }
862
1274
  let tableIndex = TableIndex;
863
- let pager = this._deconstructIndex(index, lastEvaluated, lastReturned);
1275
+ let indexParts = this._deconstructIndex({index, keys});
1276
+ if (indexParts === null) {
1277
+ return null;
1278
+ }
864
1279
  // lastEvaluatedKeys from query calls include the index pk/sk as well as the table index's pk/sk
865
1280
  if (index !== tableIndex) {
866
- pager = {...pager, ...this._deconstructIndex(tableIndex, lastEvaluated, lastReturned)};
1281
+ const tableIndexParts = this._deconstructIndex({index: tableIndex, keys});
1282
+ if (tableIndexParts === null) {
1283
+ return null;
1284
+ }
1285
+ indexParts = { ...indexParts, ...tableIndexParts };
867
1286
  }
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) {
1287
+ let noPartsFound = Object.keys(indexParts).length === 0;
1288
+ let partsAreIncomplete = this.model.facets.byIndex[tableIndex].all.find(facet => indexParts[facet.name] === undefined);
1289
+ if (noPartsFound || partsAreIncomplete) {
871
1290
  // In this case no suitable record could be found be the deconstructed pager.
872
1291
  // This can be valid in cases where a scan is performed but returns no results.
873
1292
  return null;
874
1293
  }
875
1294
 
876
- return pager;
1295
+ return indexParts;
877
1296
  }
878
1297
 
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);
1298
+ _constructPagerIndex(index = TableIndex, item, options = {}) {
1299
+ let pkAttributes = options.relaxedPk ? item : this._expectFacets(item, this.model.facets.byIndex[index].pk);
1300
+ let skAttributes = options.relaxedSk ? item : this._expectFacets(item, this.model.facets.byIndex[index].sk);
1301
+
882
1302
  let keys = this._makeIndexKeys({
883
1303
  index,
884
1304
  pkAttributes,
885
1305
  skAttributes: [skAttributes],
886
1306
  });
1307
+
887
1308
  return this._makeParameterKey(index, keys.pk, ...keys.sk);
888
1309
  }
889
1310
 
890
- _formatSuppliedPager(index = TableIndex, item) {
1311
+ _formatSuppliedPager(index = TableIndex, item, options = {}) {
891
1312
  if (typeof item !== "object" || Object.keys(item).length === 0) {
892
1313
  return item;
893
1314
  }
1315
+
894
1316
  let tableIndex = TableIndex;
895
- let pager = this._constructPagerIndex(index, item);
1317
+ let pager = this._constructPagerIndex(index, item, options);
896
1318
  if (index !== tableIndex) {
897
- pager = {...pager, ...this._constructPagerIndex(tableIndex, item)}
1319
+ pager = {...pager, ...this._constructPagerIndex(tableIndex, item, options)};
898
1320
  }
899
- return pager
1321
+
1322
+ return pager;
900
1323
  }
901
1324
 
902
1325
  _normalizeExecutionOptions({ provided = [], context = {} } = {}) {
@@ -916,6 +1339,7 @@ class Entity {
916
1339
  cursor: null,
917
1340
  data: 'attributes',
918
1341
  ignoreOwnership: false,
1342
+ _providedIgnoreOwnership: false,
919
1343
  _isPagination: false,
920
1344
  _isCollectionQuery: false,
921
1345
  pages: 1,
@@ -925,6 +1349,8 @@ class Entity {
925
1349
  terminalOperation: undefined,
926
1350
  formatCursor: u.cursorFormatter,
927
1351
  order: undefined,
1352
+ hydrate: false,
1353
+ hydrator: (_entity, _indexName, items) => items,
928
1354
  };
929
1355
 
930
1356
  return provided.filter(Boolean).reduce((config, option) => {
@@ -1060,6 +1486,7 @@ class Entity {
1060
1486
 
1061
1487
  if (option.ignoreOwnership) {
1062
1488
  config.ignoreOwnership = option.ignoreOwnership;
1489
+ config._providedIgnoreOwnership = option.ignoreOwnership;
1063
1490
  }
1064
1491
 
1065
1492
  if (option.listeners) {
@@ -1076,6 +1503,15 @@ class Entity {
1076
1503
  }
1077
1504
  }
1078
1505
 
1506
+ if (option.hydrate) {
1507
+ config.hydrate = true;
1508
+ config.ignoreOwnership = true;
1509
+ }
1510
+
1511
+ if (validations.isFunction(option.hydrator)) {
1512
+ config.hydrator = option.hydrator;
1513
+ }
1514
+
1079
1515
  config.page = Object.assign({}, config.page, option.page);
1080
1516
  config.params = Object.assign({}, config.params, option.params);
1081
1517
  return config;
@@ -1520,12 +1956,12 @@ class Entity {
1520
1956
  };
1521
1957
  }
1522
1958
 
1523
- _makeUpsertParams({update, upsert} = {}, pk, sk) {
1959
+ _makeUpsertParams({ update, upsert } = {}, pk, sk) {
1524
1960
  const { updatedKeys, setAttributes, indexKey } = this._getPutKeys(pk, sk && sk.facets, upsert.data);
1525
1961
  const upsertAttributes = this.model.schema.translateToFields(setAttributes);
1526
1962
  const keyNames = Object.keys(indexKey);
1527
- update.set(this.identifiers.entity, this.getName());
1528
- update.set(this.identifiers.version, this.getVersion());
1963
+ // update.set(this.identifiers.entity, this.getName());
1964
+ // update.set(this.identifiers.version, this.getVersion());
1529
1965
  for (const field of [...Object.keys(upsertAttributes), ...Object.keys(updatedKeys)]) {
1530
1966
  const value = u.getFirstDefined(upsertAttributes[field], updatedKeys[field]);
1531
1967
  if (!keyNames.includes(field)) {
@@ -2238,7 +2674,11 @@ class Entity {
2238
2674
  const subCollections = this.model.subCollections[collection];
2239
2675
  const index = this.model.translations.collections.fromCollectionToIndex[collection];
2240
2676
  const accessPattern = this.model.translations.indexes.fromIndexToAccessPattern[index];
2677
+ const prefixes = this.model.prefixes[index];
2241
2678
  const prefix = this._makeCollectionPrefix(subCollections);
2679
+ if (prefixes.sk && prefixes.sk.isCustom) {
2680
+ return '';
2681
+ }
2242
2682
  return this._formatKeyCasing(accessPattern, prefix);
2243
2683
  }
2244
2684
 
@@ -2293,7 +2733,8 @@ class Entity {
2293
2733
  if (!prefixes) {
2294
2734
  throw new Error(`Invalid index: ${index}`);
2295
2735
  }
2296
- let partitionKey = this._makeKey(prefixes.pk, facets.pk, pkFacets, this.model.facets.labels[index].pk, {excludeLabelTail: true});
2736
+ // let partitionKey = this._makeKey(prefixes.pk, facets.pk, pkFacets, this.model.facets.labels[index].pk, { excludeLabelTail: true });
2737
+ let partitionKey = this._makeKey(prefixes.pk, facets.pk, pkFacets, this.model.facets.labels[index].pk);
2297
2738
  let pk = partitionKey.key;
2298
2739
  let sk = [];
2299
2740
  let fulfilled = false;
@@ -2388,6 +2829,7 @@ class Entity {
2388
2829
  key: supplied[facets[0]],
2389
2830
  };
2390
2831
  }
2832
+
2391
2833
  let key = prefix;
2392
2834
  let foundCount = 0;
2393
2835
  for (let i = 0; i < labels.length; i++) {
@@ -2425,9 +2867,11 @@ class Entity {
2425
2867
  key += postfix;
2426
2868
  }
2427
2869
 
2870
+ const transformedKey = transform(u.formatKeyCasing(key, casing));
2871
+
2428
2872
  return {
2429
2873
  fulfilled,
2430
- key: transform(u.formatKeyCasing(key, casing))
2874
+ key: transformedKey,
2431
2875
  };
2432
2876
  }
2433
2877
 
@@ -3125,6 +3569,17 @@ class Entity {
3125
3569
  indexes[indexAccessPattern.fromIndexToAccessPattern[indexName]].pk.labels = facets.labels[indexName].pk;
3126
3570
  indexes[indexAccessPattern.fromIndexToAccessPattern[indexName]].sk.labels = facets.labels[indexName].sk;
3127
3571
  }
3572
+ const deconstructors = {};
3573
+ for (const indexName of Object.keys(facets.labels)) {
3574
+ const keyTypes = prefixes[indexName] || {};
3575
+ deconstructors[indexName] = {};
3576
+ for (const keyType in keyTypes) {
3577
+ const prefixes = keyTypes[keyType];
3578
+ const labels = facets.labels[indexName][keyType] || [];
3579
+ const attributes = schema.attributes;
3580
+ deconstructors[indexName][keyType] = this._createKeyDeconstructor(prefixes, labels, attributes);
3581
+ }
3582
+ }
3128
3583
 
3129
3584
  return {
3130
3585
  name,
@@ -3150,6 +3605,9 @@ class Entity {
3150
3605
  indexes: indexAccessPattern,
3151
3606
  collections: indexCollection,
3152
3607
  },
3608
+ keys: {
3609
+ deconstructors,
3610
+ },
3153
3611
  original: model,
3154
3612
  };
3155
3613
  }
@@ -3173,20 +3631,31 @@ function getEntityIdentifiers(entities) {
3173
3631
  return identifiers;
3174
3632
  }
3175
3633
 
3176
- function matchToEntityAlias({ paramItem, identifiers, record } = {}) {
3634
+ function matchToEntityAlias({ paramItem, identifiers, record, entities = {}, allowMatchOnKeys = false } = {}) {
3177
3635
  let entity;
3178
- let entityAlias;
3179
-
3180
3636
  if (paramItem && v.isFunction(paramItem[TransactionCommitSymbol])) {
3181
3637
  const committed = paramItem[TransactionCommitSymbol]();
3182
3638
  entity = committed.entity;
3183
3639
  }
3184
3640
 
3641
+ let entityAlias;
3185
3642
  for (let {name, version, nameField, versionField, alias} of identifiers) {
3186
3643
  if (entity && entity.model.entity === name && entity.model.version === version) {
3187
3644
  entityAlias = alias;
3188
3645
  break;
3189
- } else if (record[nameField] !== undefined && record[nameField] === name && record[versionField] !== undefined && record[versionField] === version) {
3646
+ } else if (record[nameField] !== undefined && record[versionField] !== undefined && record[nameField] === name && record[versionField] === version) {
3647
+ entityAlias = alias;
3648
+ break;
3649
+ // } else if (allowMatchOnKeys && entities[alias] && entities[alias].ownsKeys({keys: record})) {
3650
+ // if (entityAlias) {
3651
+ // if (alias !== entityAlias) {
3652
+ // throw new Error('Key ownership found to be not distinct');
3653
+ // }
3654
+ // } else {
3655
+ // entityAlias = alias;
3656
+ // }
3657
+ // }
3658
+ } else if (entities[alias] && entities[alias].ownsKeys(record)) {
3190
3659
  entityAlias = alias;
3191
3660
  break;
3192
3661
  }