electrodb 2.1.1 → 2.2.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/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
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": {
7
- "test": "npm run test:types && npm run test:unit",
8
- "test:unit": "LOCAL_DYNAMO_ENDPOINT=http://localhost:8000 node ./test/init.js && LOCAL_DYNAMO_ENDPOINT=http://localhost:8000 mocha -r ts-node/register ./test/**.spec.*",
7
+ "test": "npm run test:types && npm run test:init && npm run test:unit",
8
+ "test:init": "LOCAL_DYNAMO_ENDPOINT=http://localhost:8000 node ./test/init.js",
9
+ "test:unit": "LOCAL_DYNAMO_ENDPOINT=http://localhost:8000 mocha -r ts-node/register ./test/**.spec.*",
9
10
  "test:types": "tsd",
10
- "coverage": "nyc npm test && nyc report --reporter=text-lcov | coveralls",
11
- "coverage-coveralls-local": "nyc npm test && nyc report --reporter=text-lcov | coveralls",
12
- "coverage-html-local": "nyc npm test && nyc report --reporter=html",
13
- "build:browser": "browserify playground/browser.js -o playground/bundle.js"
11
+ "coverage": "npm run test:init && nyc npm run test:unit && nyc report --reporter=text-lcov | coveralls",
12
+ "coverage:local:coveralls": "npm run test:init && nyc npm run test:unit && nyc report --reporter=text-lcov | coveralls",
13
+ "coverage:local:html": "npm run test:init && nyc npm run test:unit && nyc report --reporter=html",
14
+ "build:browser": "browserify playground/browser.js -o playground/bundle.js",
15
+ "build": "sh buildbrowser.sh"
14
16
  },
15
17
  "repository": {
16
18
  "type": "git",
package/src/clauses.js CHANGED
@@ -1,4 +1,4 @@
1
- const { QueryTypes, MethodTypes, ItemOperations, ExpressionTypes, TableIndex, TerminalOperation, KeyTypes } = require("./types");
1
+ const { QueryTypes, MethodTypes, ItemOperations, ExpressionTypes, TableIndex, TerminalOperation, KeyTypes, IndexTypes } = require("./types");
2
2
  const {AttributeOperationProxy, UpdateOperations, FilterOperationNames} = require("./operations");
3
3
  const {UpdateExpression} = require("./update");
4
4
  const {FilterExpression} = require("./where");
@@ -28,7 +28,36 @@ function batchAction(action, type, entity, state, payload) {
28
28
  let clauses = {
29
29
  index: {
30
30
  name: "index",
31
- children: ["get", "delete", "update", "query", "put", "scan", "collection", "create", "remove", "patch", "batchPut", "batchDelete", "batchGet"],
31
+ children: ["get", "delete", "update", "query", "put", "scan", "collection", "clusteredCollection", "create", "remove", "patch", "batchPut", "batchDelete", "batchGet"],
32
+ },
33
+ clusteredCollection: {
34
+ name: "clusteredCollection",
35
+ action(entity, state, collection = "", facets /* istanbul ignore next */ = {}) {
36
+ if (state.getError() !== null) {
37
+ return state;
38
+ }
39
+ try {
40
+ const {pk, sk} = state.getCompositeAttributes();
41
+ return state
42
+ .setType(QueryTypes.clustered_collection)
43
+ .setMethod(MethodTypes.query)
44
+ .setCollection(collection)
45
+ .setPK(entity._expectFacets(facets, pk))
46
+ .ifSK(() => {
47
+ const {composites, unused} = state.identifyCompositeAttributes(facets, sk, pk);
48
+ state.setSK(composites);
49
+ // we must apply eq on filter on all provided because if the user then does a sort key operation, it'd actually then unexpect results
50
+ if (sk.length > 1) {
51
+ state.filterProperties(FilterOperationNames.eq, {...unused, ...composites});
52
+ }
53
+ });
54
+
55
+ } catch(err) {
56
+ state.setError(err);
57
+ return state;
58
+ }
59
+ },
60
+ children: ["between", "gte", "gt", "lte", "lt", "begins", "params", "go"],
32
61
  },
33
62
  collection: {
34
63
  name: "collection",
@@ -38,13 +67,12 @@ let clauses = {
38
67
  return state;
39
68
  }
40
69
  try {
41
- const {pk} = state.getCompositeAttributes();
70
+ const {pk, sk} = state.getCompositeAttributes();
42
71
  return state
43
72
  .setType(QueryTypes.collection)
44
73
  .setMethod(MethodTypes.query)
45
74
  .setCollection(collection)
46
75
  .setPK(entity._expectFacets(facets, pk));
47
-
48
76
  } catch(err) {
49
77
  state.setError(err);
50
78
  return state;
@@ -75,14 +103,15 @@ let clauses = {
75
103
  return state;
76
104
  }
77
105
  try {
78
- const attributes = state.getCompositeAttributes();
106
+ const {pk, sk} = state.getCompositeAttributes();
107
+ const {composites} = state.identifyCompositeAttributes(facets, sk, pk);
79
108
  return state
80
109
  .setMethod(MethodTypes.get)
81
110
  .setType(QueryTypes.eq)
82
- .setPK(entity._expectFacets(facets, attributes.pk))
111
+ .setPK(entity._expectFacets(facets, pk))
83
112
  .ifSK(() => {
84
- entity._expectFacets(facets, attributes.sk);
85
- state.setSK(entity._buildQueryFacets(facets, attributes.sk));
113
+ entity._expectFacets(facets, sk);
114
+ state.setSK(composites);
86
115
  });
87
116
  } catch(err) {
88
117
  state.setError(err);
@@ -109,14 +138,14 @@ let clauses = {
109
138
  return state;
110
139
  }
111
140
  try {
112
- const attributes = state.getCompositeAttributes();
141
+ const {pk, sk} = state.getCompositeAttributes();
113
142
  return state
114
143
  .setMethod(MethodTypes.delete)
115
144
  .setType(QueryTypes.eq)
116
- .setPK(entity._expectFacets(facets, attributes.pk))
145
+ .setPK(entity._expectFacets(facets, pk))
117
146
  .ifSK(() => {
118
- entity._expectFacets(facets, attributes.sk);
119
- state.setSK(entity._buildQueryFacets(facets, attributes.sk));
147
+ entity._expectFacets(facets, sk);
148
+ state.setSK(state.buildQueryComposites(facets, sk));
120
149
  });
121
150
  } catch(err) {
122
151
  state.setError(err);
@@ -146,7 +175,7 @@ let clauses = {
146
175
  .setPK(entity._expectFacets(facets, attributes.pk))
147
176
  .ifSK(() => {
148
177
  entity._expectFacets(facets, attributes.sk);
149
- state.setSK(entity._buildQueryFacets(facets, attributes.sk));
178
+ state.setSK(state.buildQueryComposites(facets, attributes.sk));
150
179
  });
151
180
  } catch(err) {
152
181
  state.setError(err);
@@ -172,7 +201,7 @@ let clauses = {
172
201
  .setPK(entity._expectFacets(record, attributes.pk))
173
202
  .ifSK(() => {
174
203
  entity._expectFacets(record, attributes.sk);
175
- state.setSK(entity._buildQueryFacets(record, attributes.sk));
204
+ state.setSK(state.buildQueryComposites(record, attributes.sk));
176
205
  });
177
206
  } catch(err) {
178
207
  state.setError(err);
@@ -208,7 +237,7 @@ let clauses = {
208
237
  .setPK(entity._expectFacets(record, attributes.pk))
209
238
  .ifSK(() => {
210
239
  entity._expectFacets(record, attributes.sk);
211
- state.setSK(entity._buildQueryFacets(record, attributes.sk));
240
+ state.setSK(state.buildQueryComposites(record, attributes.sk));
212
241
  });
213
242
  } catch(err) {
214
243
  state.setError(err);
@@ -237,7 +266,7 @@ let clauses = {
237
266
  .setPK(entity._expectFacets(facets, attributes.pk))
238
267
  .ifSK(() => {
239
268
  entity._expectFacets(facets, attributes.sk);
240
- state.setSK(entity._buildQueryFacets(facets, attributes.sk));
269
+ state.setSK(state.buildQueryComposites(facets, attributes.sk));
241
270
  });
242
271
  } catch(err) {
243
272
  state.setError(err);
@@ -260,7 +289,7 @@ let clauses = {
260
289
  .setPK(entity._expectFacets(facets, attributes.pk))
261
290
  .ifSK(() => {
262
291
  entity._expectFacets(facets, attributes.sk);
263
- state.setSK(entity._buildQueryFacets(facets, attributes.sk));
292
+ state.setSK(state.buildQueryComposites(facets, attributes.sk));
264
293
  });
265
294
  } catch(err) {
266
295
  state.setError(err);
@@ -277,6 +306,11 @@ let clauses = {
277
306
  }
278
307
  try {
279
308
  state.query.updateProxy.invokeCallback(cb);
309
+ for (const path of Object.keys(state.query.update.refs)) {
310
+ const operation = state.query.update.impacted[path];
311
+ const attribute = state.query.update.refs[path];
312
+ entity.model.schema.checkOperation(attribute, operation);
313
+ }
280
314
  return state;
281
315
  } catch(err) {
282
316
  state.setError(err);
@@ -398,13 +432,22 @@ let clauses = {
398
432
  }
399
433
  try {
400
434
  state.addOption('_isPagination', true);
401
- const attributes = state.getCompositeAttributes();
435
+ const {pk, sk} = state.getCompositeAttributes();
402
436
  return state
403
437
  .setMethod(MethodTypes.query)
404
438
  .setType(QueryTypes.is)
405
- .setPK(entity._expectFacets(facets, attributes.pk))
439
+ .setPK(entity._expectFacets(facets, pk))
406
440
  .ifSK(() => {
407
- state.setSK(entity._buildQueryFacets(facets, attributes.sk));
441
+ const {composites, unused} = state.identifyCompositeAttributes(facets, sk, pk);
442
+ state.setSK(state.buildQueryComposites(facets, sk));
443
+ // we must apply eq on filter on all provided because if the user then does a sort key operation, it'd actually then unexpect results
444
+ if (sk.length > 1) {
445
+ state.filterProperties(FilterOperationNames.eq, {...unused, ...composites});
446
+ }
447
+ if (state.query.options.indexType === IndexTypes.clustered && Object.keys(composites).length < sk.length) {
448
+ state.unsafeApplyFilter(FilterOperationNames.eq, entity.identifiers.entity, entity.getName())
449
+ .unsafeApplyFilter(FilterOperationNames.eq, entity.identifiers.version, entity.getVersion());
450
+ }
408
451
  });
409
452
  } catch(err) {
410
453
  state.setError(err);
@@ -420,12 +463,15 @@ let clauses = {
420
463
  return state;
421
464
  }
422
465
  try {
423
- const attributes = state.getCompositeAttributes();
466
+ const {pk, sk} = state.getCompositeAttributes();
467
+ const endingSk = state.identifyCompositeAttributes(endingFacets, sk, pk);
468
+ const startingSk = state.identifyCompositeAttributes(startingFacets, sk, pk);
424
469
  return state
425
470
  .setType(QueryTypes.and)
426
- .setSK(entity._buildQueryFacets(endingFacets, attributes.sk))
471
+ .setSK(endingSk.composites)
427
472
  .setType(QueryTypes.between)
428
- .setSK(entity._buildQueryFacets(startingFacets, attributes.sk))
473
+ .setSK(startingSk.composites)
474
+ .filterProperties(FilterOperationNames.lte, endingSk.composites);
429
475
  } catch(err) {
430
476
  state.setError(err);
431
477
  return state;
@@ -444,7 +490,7 @@ let clauses = {
444
490
  .setType(QueryTypes.begins)
445
491
  .ifSK(state => {
446
492
  const attributes = state.getCompositeAttributes();
447
- state.setSK(entity._buildQueryFacets(facets, attributes.sk))
493
+ state.setSK(state.buildQueryComposites(facets, attributes.sk));
448
494
  });
449
495
  } catch(err) {
450
496
  state.setError(err);
@@ -460,11 +506,16 @@ let clauses = {
460
506
  return state;
461
507
  }
462
508
  try {
509
+
463
510
  return state
464
511
  .setType(QueryTypes.gt)
465
512
  .ifSK(state => {
466
- const attributes = state.getCompositeAttributes();
467
- state.setSK(entity._buildQueryFacets(facets, attributes.sk))
513
+ const {pk, sk} = state.getCompositeAttributes();
514
+ const {composites} = state.identifyCompositeAttributes(facets, sk, pk);
515
+ state.setSK(composites);
516
+ state.filterProperties(FilterOperationNames.gt, {
517
+ ...composites,
518
+ });
468
519
  });
469
520
  } catch(err) {
470
521
  state.setError(err);
@@ -484,7 +535,7 @@ let clauses = {
484
535
  .setType(QueryTypes.gte)
485
536
  .ifSK(state => {
486
537
  const attributes = state.getCompositeAttributes();
487
- state.setSK(entity._buildQueryFacets(facets, attributes.sk))
538
+ state.setSK(state.buildQueryComposites(facets, attributes.sk));
488
539
  });
489
540
  } catch(err) {
490
541
  state.setError(err);
@@ -502,8 +553,9 @@ let clauses = {
502
553
  try {
503
554
  return state.setType(QueryTypes.lt)
504
555
  .ifSK(state => {
505
- const attributes = state.getCompositeAttributes();
506
- state.setSK(entity._buildQueryFacets(facets, attributes.sk))
556
+ const {pk, sk} = state.getCompositeAttributes();
557
+ const {composites} = state.identifyCompositeAttributes(facets, sk, pk);
558
+ state.setSK(composites);
507
559
  });
508
560
  } catch(err) {
509
561
  state.setError(err);
@@ -521,8 +573,12 @@ let clauses = {
521
573
  try {
522
574
  return state.setType(QueryTypes.lte)
523
575
  .ifSK(state => {
524
- const attributes = state.getCompositeAttributes();
525
- state.setSK(entity._buildQueryFacets(facets, attributes.sk))
576
+ const {pk, sk} = state.getCompositeAttributes();
577
+ const {composites} = state.identifyCompositeAttributes(facets, sk, pk);
578
+ state.setSK(composites);
579
+ state.filterProperties(FilterOperationNames.lte, {
580
+ ...composites,
581
+ });
526
582
  });
527
583
  } catch(err) {
528
584
  state.setError(err);
@@ -538,7 +594,7 @@ let clauses = {
538
594
  throw state.error;
539
595
  }
540
596
  try {
541
- if (!v.isStringHasLength(options.table) && !v.isStringHasLength(entity._getTableName())) {
597
+ if (!v.isStringHasLength(options.table) && !v.isStringHasLength(entity.getTableName())) {
542
598
  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.`);
543
599
  }
544
600
  const method = state.getMethod();
@@ -688,6 +744,71 @@ class ChainState {
688
744
  return this.query.facets;
689
745
  }
690
746
 
747
+ buildQueryComposites(provided, definition) {
748
+ return definition
749
+ .map(name => [name, provided[name]])
750
+ .reduce(
751
+ (result, [name, value]) => {
752
+ if (value !== undefined) {
753
+ result[name] = value;
754
+ }
755
+ return result;
756
+ },
757
+ {},
758
+ );
759
+ }
760
+
761
+ identifyCompositeAttributes(provided, defined, skip) {
762
+ // todo: make sure attributes are valid
763
+ const composites = {};
764
+ const unused = {};
765
+ const definedSet = new Set(defined || []);
766
+ const skipSet = new Set(skip || []);
767
+ for (const key of Object.keys(provided)) {
768
+ const value = provided[key];
769
+ if (definedSet.has(key)) {
770
+ composites[key] = value;
771
+ } else if (skipSet.has(key)) {
772
+ continue;
773
+ } else {
774
+ unused[key] = value;
775
+ }
776
+ }
777
+
778
+ return {
779
+ composites,
780
+ unused,
781
+ }
782
+ }
783
+
784
+ applyFilter(operation, name, ...values) {
785
+ if (FilterOperationNames[operation] !== undefined & name !== undefined && values.length > 0) {
786
+ const attribute = this.attributes[name];
787
+ if (attribute !== undefined) {
788
+ this.unsafeApplyFilter(operation, attribute.field, ...values);
789
+ }
790
+ }
791
+ return this;
792
+ }
793
+
794
+ unsafeApplyFilter(operation, name, ...values) {
795
+ if (FilterOperationNames[operation] !== undefined & name !== undefined && values.length > 0) {
796
+ const filter = this.query.filter[ExpressionTypes.FilterExpression];
797
+ filter.unsafeSet(operation, name, ...values);
798
+ }
799
+ return this;
800
+ }
801
+
802
+ filterProperties(operation, obj = {}) {
803
+ for (const property in obj) {
804
+ const value = obj[property];
805
+ if (value !== undefined) {
806
+ this.applyFilter(operation, property, value);
807
+ }
808
+ }
809
+ return this;
810
+ }
811
+
691
812
  setSK(attributes, type = this.query.type) {
692
813
  if (this.hasSortKey) {
693
814
  this.query.keys.sk.push({
package/src/client.js CHANGED
@@ -1,6 +1,6 @@
1
+ const lib = require('@aws-sdk/lib-dynamodb')
1
2
  const { isFunction } = require('./validations');
2
3
  const { ElectroError, ErrorCodes } = require('./errors');
3
- const lib = require('@aws-sdk/lib-dynamodb');
4
4
 
5
5
  const DocumentClientVersions = {
6
6
  v2: 'v2',
@@ -144,4 +144,4 @@ module.exports = {
144
144
  DocumentClientVersions,
145
145
  supportedClientVersions,
146
146
  DocumentClientV3Wrapper,
147
- };
147
+ };