electrodb 2.4.1 → 2.4.2

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/index.d.ts CHANGED
@@ -2257,6 +2257,10 @@ export type EntityConfiguration = {
2257
2257
  client?: DocumentClient;
2258
2258
  listeners?: Array<ElectroEventListener>;
2259
2259
  logger?: ElectroEventListener;
2260
+ identifiers?: {
2261
+ entity?: string;
2262
+ version?: string;
2263
+ },
2260
2264
  };
2261
2265
 
2262
2266
  export class Entity<A extends string, F extends string, C extends string, S extends Schema<A,F,C>> {
@@ -2374,4 +2378,4 @@ declare function CustomAttributeType<T>(
2374
2378
  ? OpaquePrimitiveTypeName<T>
2375
2379
  : CustomAttributeTypeName<T>;
2376
2380
 
2377
- declare function createSchema<A extends string, F extends string, C extends string, S extends Schema<A,F,C>>(schema: S): S
2381
+ declare function createSchema<A extends string, F extends string, C extends string, S extends Schema<A,F,C>>(schema: S): S
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "2.4.1",
3
+ "version": "2.4.2",
4
4
  "description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/clauses.js CHANGED
@@ -37,7 +37,7 @@ let clauses = {
37
37
  return state;
38
38
  }
39
39
  try {
40
- const {pk, sk} = state.getCompositeAttributes();
40
+ const { pk, sk } = state.getCompositeAttributes();
41
41
  return state
42
42
  .setType(QueryTypes.clustered_collection)
43
43
  .setMethod(MethodTypes.query)
@@ -50,6 +50,22 @@ let clauses = {
50
50
  if (sk.length > 1) {
51
51
  state.filterProperties(FilterOperationNames.eq, {...unused, ...composites});
52
52
  }
53
+ })
54
+ .whenOptions(({ options, state }) => {
55
+ if (!options.ignoreOwnership) {
56
+ state.query.options.expressions.names = {
57
+ ...state.query.options.expressions.names,
58
+ ...state.query.options.identifiers.names,
59
+ };
60
+ state.query.options.expressions.values = {
61
+ ...state.query.options.expressions.values,
62
+ ...state.query.options.identifiers.values,
63
+ };
64
+ state.query.options.expressions.expression =
65
+ state.query.options.expressions.expression.length > 1
66
+ ? `(${state.query.options.expressions.expression}) AND ${state.query.options.identifiers.expression}`
67
+ : `${state.query.options.identifiers.expression}`;
68
+ }
53
69
  });
54
70
 
55
71
  } catch(err) {
@@ -72,7 +88,23 @@ let clauses = {
72
88
  .setType(QueryTypes.collection)
73
89
  .setMethod(MethodTypes.query)
74
90
  .setCollection(collection)
75
- .setPK(entity._expectFacets(facets, pk));
91
+ .setPK(entity._expectFacets(facets, pk))
92
+ .whenOptions(({ options, state }) => {
93
+ if (!options.ignoreOwnership) {
94
+ state.query.options.expressions.names = {
95
+ ...state.query.options.expressions.names,
96
+ ...state.query.options.identifiers.names,
97
+ };
98
+ state.query.options.expressions.values = {
99
+ ...state.query.options.expressions.values,
100
+ ...state.query.options.identifiers.values,
101
+ };
102
+ state.query.options.expressions.expression =
103
+ state.query.options.expressions.expression.length > 1
104
+ ? `(${state.query.options.expressions.expression}) AND ${state.query.options.identifiers.expression}`
105
+ : `${state.query.options.identifiers.expression}`;
106
+ }
107
+ });
76
108
  } catch(err) {
77
109
  state.setError(err);
78
110
  return state;
@@ -87,7 +119,13 @@ let clauses = {
87
119
  return state;
88
120
  }
89
121
  try {
90
- return state.setMethod(MethodTypes.scan);
122
+ return state.setMethod(MethodTypes.scan)
123
+ .whenOptions(({ state, options }) => {
124
+ if (!options.ignoreOwnership) {
125
+ state.unsafeApplyFilter(FilterOperationNames.eq, entity.identifiers.entity, entity.getName());
126
+ state.unsafeApplyFilter(FilterOperationNames.eq, entity.identifiers.version, entity.getVersion());
127
+ }
128
+ });
91
129
  } catch(err) {
92
130
  state.setError(err);
93
131
  return state;
@@ -478,10 +516,13 @@ let clauses = {
478
516
  if (sk.length > 1) {
479
517
  state.filterProperties(FilterOperationNames.eq, {...unused, ...composites});
480
518
  }
481
- if (state.query.options.indexType === IndexTypes.clustered && Object.keys(composites).length < sk.length) {
482
- state.unsafeApplyFilter(FilterOperationNames.eq, entity.identifiers.entity, entity.getName())
483
- .unsafeApplyFilter(FilterOperationNames.eq, entity.identifiers.version, entity.getVersion());
484
- }
519
+
520
+ state.whenOptions(({ options, state }) => {
521
+ if (state.query.options.indexType === IndexTypes.clustered && Object.keys(composites).length < sk.length && !options.ignoreOwnership) {
522
+ state.unsafeApplyFilter(FilterOperationNames.eq, entity.identifiers.entity, entity.getName())
523
+ .unsafeApplyFilter(FilterOperationNames.eq, entity.identifiers.version, entity.getVersion());
524
+ }
525
+ });
485
526
  });
486
527
  } catch(err) {
487
528
  state.setError(err);
@@ -632,20 +673,26 @@ let clauses = {
632
673
  throw new e.ElectroError(e.ErrorCodes.MissingTable, `Table name not defined. Table names must be either defined on the model, instance configuration, or as a query option.`);
633
674
  }
634
675
  const method = state.getMethod();
676
+ const normalizedOptions = entity._normalizeExecutionOptions({ provided: [ state.getOptions(), state.query.options, options ] });
677
+ state.applyWithOptions(normalizedOptions);
635
678
  let results;
636
679
  switch (method) {
637
- case MethodTypes.query:
638
- results = entity._queryParams(state, options);
680
+ case MethodTypes.query: {
681
+ results = entity._queryParams(state, normalizedOptions);
639
682
  break;
640
- case MethodTypes.batchWrite:
641
- results = entity._batchWriteParams(state, options);
642
- break
643
- case MethodTypes.batchGet:
644
- results = entity._batchGetParams(state, options);
683
+ }
684
+ case MethodTypes.batchWrite: {
685
+ results = entity._batchWriteParams(state, normalizedOptions);
645
686
  break;
646
- default:
647
- results = entity._params(state, options);
687
+ }
688
+ case MethodTypes.batchGet: {
689
+ results = entity._batchGetParams(state, normalizedOptions);
648
690
  break;
691
+ }
692
+ default: {
693
+ results = entity._params(state, normalizedOptions);
694
+ break;
695
+ }
649
696
  }
650
697
 
651
698
  if (method === MethodTypes.update && results.ExpressionAttributeValues && Object.keys(results.ExpressionAttributeValues).length === 0) {
@@ -653,6 +700,14 @@ let clauses = {
653
700
  // todo: change the getValues() method to return undefined in this case (would potentially require a more generous refactor)
654
701
  delete results.ExpressionAttributeValues;
655
702
  }
703
+
704
+ if (options._returnOptions) {
705
+ return {
706
+ params: results,
707
+ options: normalizedOptions,
708
+ }
709
+ }
710
+
656
711
  return results;
657
712
  } catch(err) {
658
713
  throw err;
@@ -671,9 +726,8 @@ let clauses = {
671
726
  throw new e.ElectroError(e.ErrorCodes.NoClientDefined, "No client defined on model");
672
727
  }
673
728
  options.terminalOperation = TerminalOperation.go;
674
- let params = clauses.params.action(entity, state, options);
675
- let {config} = entity._applyParameterOptions({}, state.getOptions(), options);
676
- return entity.go(state.getMethod(), params, config);
729
+ const paramResults = clauses.params.action(entity, state, { ...options, _returnOptions: true });
730
+ return entity.go(state.getMethod(), paramResults.params, paramResults.options);
677
731
  } catch(err) {
678
732
  return Promise.reject(err);
679
733
  }
@@ -718,6 +772,7 @@ class ChainState {
718
772
  options,
719
773
  };
720
774
  this.subStates = [];
775
+ this.applyAfterOptions = [];
721
776
  this.hasSortKey = hasSortKey;
722
777
  this.prev = null;
723
778
  this.self = null;
@@ -911,6 +966,18 @@ class ChainState {
911
966
  this.query.put.data = {...this.query.put.data, ...data};
912
967
  return this;
913
968
  }
969
+
970
+ whenOptions(fn) {
971
+ if (v.isFunction(fn)) {
972
+ this.applyAfterOptions.push((options) => {
973
+ fn({ options, state: this });
974
+ });
975
+ }
976
+ }
977
+
978
+ applyWithOptions(options = {}) {
979
+ this.applyAfterOptions.forEach((fn) => fn(options));
980
+ }
914
981
  }
915
982
 
916
983
  module.exports = {
package/src/entity.js CHANGED
@@ -583,7 +583,7 @@ class Entity {
583
583
  for (let i = 0; i < responses.length; i++) {
584
584
  const item = responses[i];
585
585
  const slot = orderMaintainer.getOrder(item);
586
- const formatted = this.formatResponse({Item: item}, index, config);
586
+ const formatted = this.formatResponse({ Item: item }, index, config);
587
587
  if (slot !== -1) {
588
588
  resultsAll[slot] = formatted.data;
589
589
  } else {
@@ -617,6 +617,8 @@ class Entity {
617
617
  if (Object.keys(results).length === 0) {
618
618
  results = null;
619
619
  }
620
+ } else if (!config._objectOnEmpty) {
621
+ results = null;
620
622
  }
621
623
  } else if (response.Items) {
622
624
  results = [];
@@ -881,7 +883,7 @@ class Entity {
881
883
  return pager
882
884
  }
883
885
 
884
- _applyParameterOptions(params, ...options) {
886
+ _normalizeExecutionOptions({ provided = [] } = {}) {
885
887
  let config = {
886
888
  includeKeys: false,
887
889
  originalErr: false,
@@ -909,7 +911,7 @@ class Entity {
909
911
  order: undefined,
910
912
  };
911
913
 
912
- config = options.reduce((config, option) => {
914
+ return provided.filter(Boolean).reduce((config, option) => {
913
915
  if (typeof option.order === 'string') {
914
916
  switch (option.order.toLowerCase()) {
915
917
  case 'asc':
@@ -933,7 +935,7 @@ class Entity {
933
935
  }
934
936
 
935
937
  if (option.formatCursor) {
936
- const isValid = ['serialize', 'deserialize'].every(method =>
938
+ const isValid = ['serialize', 'deserialize'].every(method =>
937
939
  method in option.formatCursor &&
938
940
  validations.isFunction(option.formatCursor[method])
939
941
  );
@@ -1058,16 +1060,18 @@ class Entity {
1058
1060
  config.params = Object.assign({}, config.params, option.params);
1059
1061
  return config;
1060
1062
  }, config);
1063
+ }
1061
1064
 
1065
+ _applyParameterOptions({ params = {}, options = {} } = {}) {
1062
1066
  let parameters = Object.assign({}, params);
1063
1067
 
1064
- for (let customParameter of Object.keys(config.params)) {
1065
- if (config.params[customParameter] !== undefined) {
1066
- parameters[customParameter] = config.params[customParameter];
1068
+ for (let customParameter of Object.keys(options.params || {})) {
1069
+ if (options.params[customParameter] !== undefined) {
1070
+ parameters[customParameter] = options.params[customParameter];
1067
1071
  }
1068
1072
  }
1069
1073
 
1070
- return { parameters, config };
1074
+ return parameters;
1071
1075
  }
1072
1076
 
1073
1077
  addListeners(logger) {
@@ -1117,7 +1121,7 @@ class Entity {
1117
1121
  }
1118
1122
  /* istanbul ignore next */
1119
1123
  _params(state, config = {}) {
1120
- let { keys = {}, method = "", put = {}, update = {}, filter = {}, options = {}, updateProxy, upsert } = state.query;
1124
+ const { keys = {}, method = "", put = {}, update = {}, filter = {}, upsert } = state.query;
1121
1125
  let consolidatedQueryFacets = this._consolidateQueryFacets(keys.sk);
1122
1126
  let params = {};
1123
1127
  switch (method) {
@@ -1148,8 +1152,13 @@ class Entity {
1148
1152
  default:
1149
1153
  throw new Error(`Invalid method: ${method}`);
1150
1154
  }
1151
- let applied = this._applyParameterOptions(params, options, config);
1152
- return this._applyParameterExpressions(method, applied.parameters, applied.config, filter);
1155
+
1156
+ let appliedParameters = this._applyParameterOptions({
1157
+ params,
1158
+ options: config,
1159
+ });
1160
+
1161
+ return this._applyParameterExpressions(method, appliedParameters, config, filter);
1153
1162
  }
1154
1163
 
1155
1164
  _applyParameterExpressions(method, parameters, config, filter) {
@@ -1251,12 +1260,19 @@ class Entity {
1251
1260
  _batchGetParams(state, config = {}) {
1252
1261
  let table = config.table || this.getTableName();
1253
1262
  let userDefinedParams = config.params || {};
1263
+
1264
+ // TableName is added when the config provided includes "table"
1265
+ // this is evaluated upstream so we remove it to avoid forming
1266
+ // bad syntax. Code should reconsider how this is applied to
1267
+ // make this cleaner :(
1268
+ delete userDefinedParams.TableName;
1269
+
1254
1270
  let records = [];
1255
1271
  for (let itemState of state.subStates) {
1256
1272
  let method = itemState.query.method;
1257
1273
  let params = this._params(itemState, config);
1258
1274
  if (method === MethodTypes.get) {
1259
- let {Key} = params;
1275
+ let { Key } = params;
1260
1276
  records.push(Key);
1261
1277
  }
1262
1278
  }
@@ -1356,11 +1372,7 @@ class Entity {
1356
1372
  ),
1357
1373
  FilterExpression: `begins_with(#${pkField}, :${pkField})`,
1358
1374
  };
1359
- params.ExpressionAttributeNames["#" + this.identifiers.entity] = this.identifiers.entity;
1360
- params.ExpressionAttributeNames["#" + this.identifiers.version] = this.identifiers.version;
1361
- params.ExpressionAttributeValues[":" + this.identifiers.entity] = this.getName();
1362
- params.ExpressionAttributeValues[":" + this.identifiers.version] = this.getVersion();
1363
- params.FilterExpression = `${params.FilterExpression} AND #${this.identifiers.entity} = :${this.identifiers.entity} AND #${this.identifiers.version} = :${this.identifiers.version}`;
1375
+
1364
1376
  if (hasSortKey) {
1365
1377
  let skField = this.model.indexes[accessPattern].sk.field;
1366
1378
  params.FilterExpression = `${params.FilterExpression} AND begins_with(#${skField}, :${skField})`;
@@ -1691,8 +1703,16 @@ class Entity {
1691
1703
  default:
1692
1704
  throw new Error(`Invalid query type: ${state.query.type}`);
1693
1705
  }
1694
- let applied = this._applyParameterOptions(parameters, state.query.options, options);
1695
- return this._applyProjectionExpressions(applied);
1706
+
1707
+ const appliedParameters = this._applyParameterOptions({
1708
+ params: parameters,
1709
+ options,
1710
+ });
1711
+
1712
+ return this._applyProjectionExpressions({
1713
+ parameters: appliedParameters,
1714
+ config: options,
1715
+ });
1696
1716
  }
1697
1717
 
1698
1718
  _makeBetweenQueryParams(index, filter, pk, ...sk) {
@@ -2293,7 +2313,6 @@ class Entity {
2293
2313
  indexType,
2294
2314
  isCollection = false,
2295
2315
  }) {
2296
-
2297
2316
  this._validateIndex(index);
2298
2317
  const excludePostfix = indexType === IndexTypes.clustered && isCollection;
2299
2318
  const transforms = this._makeKeyTransforms(queryType);
package/src/service.js CHANGED
@@ -391,13 +391,18 @@ class Service {
391
391
  return this.expectCursorOwner(cursor).deserializeCursor(cursor);
392
392
  }
393
393
  },
394
- expressions: {
394
+ identifiers: {
395
395
  names: identifiers.names || {},
396
396
  values: identifiers.values || {},
397
397
  expression: allEntities.length > 1
398
398
  ? `(${expression})`
399
399
  : expression
400
400
  },
401
+ expressions: {
402
+ names: {},
403
+ values: {},
404
+ expression: '',
405
+ },
401
406
  attributes,
402
407
  entities,
403
408
  indexType,