electrodb 1.7.0 → 1.7.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/CHANGELOG.md CHANGED
@@ -150,4 +150,8 @@ All notable changes to this project will be documented in this file. Breaking ch
150
150
 
151
151
  ## [1.7.0] - 2022-03-13
152
152
  ### Added
153
- - New feature: "Listeners". Listeners open the door to some really cool tooling that was not possible because of how ElectroDB augments raw DynamoDB responses and did not provide easy access to raw DyanmoDB parameters.
153
+ - New feature: "Listeners". Listeners open the door to some really cool tooling that was not possible because of how ElectroDB augments raw DynamoDB responses and did not provide easy access to raw DyanmoDB parameters. [[read more](./README.md#listeners)]
154
+
155
+ ## [1.7.1] - 2022-03-19
156
+ ### Added
157
+ - Adding support for the v3 DyanmoDBClient. This change also brings in a new ElectroDB dependency [@aws-sdk/lib-dynamodb](https://www.npmjs.com/package/@aws-sdk/client-dynamodb). [[read more](./README.md#aws-dynamodb-client)]
package/README.md CHANGED
@@ -205,6 +205,14 @@ tasks
205
205
  * [Pagination Example](#pagination-example)
206
206
  * [Query Examples](#query-examples)
207
207
  * [Query Options](#query-options)
208
+ - [AWS DynamoDB Client](#aws-dynamodb-client)
209
+ * [V2 Client](#v2-client)
210
+ * [V3 Client](#v3-client)
211
+ - [Events](#events)
212
+ * [Query Event](#query-event)
213
+ * [Results Event](#results-event)
214
+ - [Logging](#logging)
215
+ - [Listeners](#listeners)
208
216
  - [Errors:](#errors-)
209
217
  + [No Client Defined On Model](#no-client-defined-on-model)
210
218
  + [Invalid Identifier](#invalid-identifier)
@@ -4309,6 +4317,146 @@ pages | ∞ | How many DynamoDB pages should a quer
4309
4317
  listeners | `[]` | An array of callbacks that are invoked when [internal ElectroDB events](#events) occur.
4310
4318
  logger | _none_ | A convenience option for a single event listener that semantically can be used for logging.
4311
4319
 
4320
+ # AWS DynamoDB Client
4321
+ ElectroDB supports both the [v2](https://www.npmjs.com/package/aws-sdk) and [v3](https://www.npmjs.com/package/@aws-sdk/client-dynamodb) aws clients. The client can be supplied creating a new Entity or Service, or added to a Entity/Service instance via the `setClient()` method.
4322
+
4323
+ *On the instantiation of an `Entity`:*
4324
+ ```typescript
4325
+ import { Entity } from 'electrodb';
4326
+ import { DocumentClient } from "aws-sdk/clients/dynamodb";
4327
+ const table = "my_table_name";
4328
+ const client = new DocumentClient({
4329
+ region: "us-east-1"
4330
+ });
4331
+
4332
+ const task = new Entity({
4333
+ // your model
4334
+ }, {
4335
+ client, // <----- client
4336
+ table,
4337
+ });
4338
+ ```
4339
+
4340
+ *On the instantiation of an `Service`:*
4341
+ ```typescript
4342
+ import { Entity } from 'electrodb';
4343
+ import { DocumentClient } from "aws-sdk/clients/dynamodb";
4344
+ const table = "my_table_name";
4345
+ const client = new DocumentClient({
4346
+ region: "us-east-1"
4347
+ });
4348
+
4349
+ const task = new Entity({
4350
+ // your model
4351
+ });
4352
+
4353
+ const user = new Entity({
4354
+ // your model
4355
+ });
4356
+
4357
+ const service = new Service({ task, user }, {
4358
+ client, // <----- client
4359
+ table,
4360
+ });
4361
+ ```
4362
+
4363
+ *Via the `setClient` method:*
4364
+ ```typescript
4365
+ import { Entity } from 'electrodb';
4366
+ import { DocumentClient } from "aws-sdk/clients/dynamodb";
4367
+ const table = "my_table_name";
4368
+ const client = new DocumentClient({
4369
+ region: "us-east-1"
4370
+ });
4371
+
4372
+ const task = new Entity({
4373
+ // your model
4374
+ });
4375
+
4376
+ task.setClient(client);
4377
+ ```
4378
+
4379
+ ## V2 Client
4380
+ The [v2](https://www.npmjs.com/package/aws-sdk) sdk will work out of the box with the the DynamoDB DocumentClient.
4381
+
4382
+ *Example:*
4383
+ ```typescript
4384
+ import { DocumentClient } from "aws-sdk/clients/dynamodb";
4385
+ const client = new DocumentClient({
4386
+ region: "us-east-1"
4387
+ });
4388
+ ```
4389
+
4390
+ ## V3 Client
4391
+ The [v3](https://www.npmjs.com/package/@aws-sdk/client-dynamodb) client will work out of the box with the the DynamoDBClient.
4392
+
4393
+ ```typescript
4394
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
4395
+ const client = new DynamoDBClient({
4396
+ region: "us-east-1"
4397
+ });
4398
+ ```
4399
+
4400
+ # Logging
4401
+ A logger callback function can be provided both the at the instantiation of an `Entity` or `Service` instance or as a [Query Option](#query-options). The property `logger` is implemented as a convenience property; under the hood ElectroDB uses this property identically to how it uses a [Listener](#listeners).
4402
+
4403
+ *On the instantiation of an `Entity`:*
4404
+ ```typescript
4405
+ import { DynamoDB } from 'aws-sdk';
4406
+ import { Entity, ElectroEvent } from 'electrodb';
4407
+
4408
+ const table = "my_table_name";
4409
+ const client = new DynamoDB.DocumentClient();
4410
+ const logger = (event: ElectroEvent) => {
4411
+ console.log(JSON.stringify(event, null, 4));
4412
+ }
4413
+
4414
+ const task = new Entity({
4415
+ // your model
4416
+ }, {
4417
+ client,
4418
+ table,
4419
+ logger // <----- logger listener
4420
+ });
4421
+ ```
4422
+
4423
+ *On the instantiation of an `Service`:*
4424
+ ```typescript
4425
+ import { DynamoDB } from 'aws-sdk';
4426
+ import { Entity, ElectroEvent } from 'electrodb';
4427
+
4428
+ const table = "my_table_name";
4429
+ const client = new DynamoDB.DocumentClient();
4430
+ const logger = (event: ElectroEvent) => {
4431
+ console.log(JSON.stringify(event, null, 4));
4432
+ }
4433
+
4434
+ const task = new Entity({
4435
+ // your model
4436
+ });
4437
+
4438
+ const user = new Entity({
4439
+ // your model
4440
+ });
4441
+
4442
+ const service = new Service({ task, user }, {
4443
+ client,
4444
+ table,
4445
+ logger // <----- logger listener
4446
+ });
4447
+ ```
4448
+
4449
+ *As a [Query Option](#query-options):*
4450
+ ```typescript
4451
+ const logger = (event: ElectroEvent) => {
4452
+ console.log(JSON.stringify(event, null, 4));
4453
+ }
4454
+
4455
+ task.query
4456
+ .assigned({ userId })
4457
+ .go({ logger });
4458
+ ```
4459
+
4312
4460
  # Events
4313
4461
  ElectroDB can be supplied with callbacks (see: [logging](#logging) and [listeners](#listeners) to learn how) to be invoked after certain request lifecycles. This can be useful for logging, analytics, expanding functionality, and more. The following are events currently supported by ElectroDB -- if you would like to see additional events feel free to create a github issue to discuss your concept/need!
4314
4462
 
@@ -4413,66 +4561,6 @@ entity.get({ prop1, prop2 }).go();
4413
4561
  }
4414
4562
  ```
4415
4563
 
4416
- # Logging
4417
- A logger callback function can be provided both the at the instantiation of an `Entity` or `Service` instance or as a [Query Option](#query-options). The property `logger` is implemented as a convenience property; under the hood ElectroDB uses this property identically to how it uses a [Listener](#listeners).
4418
-
4419
- *On the instantiation of an `Entity`:*
4420
- ```typescript
4421
- import { DynamoDB } from 'aws-sdk';
4422
- import {Entity, ElectroEvent} from 'electrodb';
4423
-
4424
- const table = "my_table_name";
4425
- const client = new DynamoDB.DocumentClient();
4426
- const logger = (event: ElectroEvent) => {
4427
- console.log(JSON.stringify(event, null, 4));
4428
- }
4429
-
4430
- const task = new Entity({
4431
- // your model
4432
- }, {
4433
- client,
4434
- table,
4435
- logger // <----- logger listener
4436
- });
4437
- ```
4438
-
4439
- *On the instantiation of an `Service`:*
4440
- ```typescript
4441
- import { DynamoDB } from 'aws-sdk';
4442
- import {Entity, ElectroEvent} from 'electrodb';
4443
-
4444
- const table = "my_table_name";
4445
- const client = new DynamoDB.DocumentClient();
4446
- const logger = (event: ElectroEvent) => {
4447
- console.log(JSON.stringify(event, null, 4));
4448
- }
4449
-
4450
- const task = new Entity({
4451
- // your model
4452
- });
4453
-
4454
- const user = new Entity({
4455
- // your model
4456
- });
4457
-
4458
- const service = new Service({ task, user }, {
4459
- client,
4460
- table,
4461
- logger // <----- logger listener
4462
- });
4463
- ```
4464
-
4465
- *As a [Query Option](#query-options):*
4466
- ```typescript
4467
- const logger = (event: ElectroEvent) => {
4468
- console.log(JSON.stringify(event, null, 4));
4469
- }
4470
-
4471
- task.query
4472
- .assigned({ userId })
4473
- .go({ logger });
4474
- ```
4475
-
4476
4564
  # Listeners
4477
4565
  ElectroDB can be supplied with callbacks (called "Listeners") to be invoked after certain request lifecycles. Unlike [Attribute Getters and Setters](#attribute-getters-and-setters), Listeners are implemented to react to events passively, not to modify values during the request lifecycle. Listeners can be useful for logging, analytics, expanding functionality, and more. Listeners can be provide both the at the instantiation of an `Entity` or `Service` instance or as a [Query Option](#query-options).
4478
4566
 
@@ -4481,7 +4569,7 @@ ElectroDB can be supplied with callbacks (called "Listeners") to be invoked afte
4481
4569
  *On the instantiation of an `Entity`:*
4482
4570
  ```typescript
4483
4571
  import { DynamoDB } from 'aws-sdk';
4484
- import {Entity, ElectroEvent} from 'electrodb';
4572
+ import { Entity, ElectroEvent } from 'electrodb';
4485
4573
 
4486
4574
  const table = "my_table_name";
4487
4575
  const client = new DynamoDB.DocumentClient();
@@ -4508,7 +4596,7 @@ const task = new Entity({
4508
4596
  *On the instantiation of an `Service`:*
4509
4597
  ```typescript
4510
4598
  import { DynamoDB } from 'aws-sdk';
4511
- import {Entity, ElectroEvent} from 'electrodb';
4599
+ import { Entity, ElectroEvent } from 'electrodb';
4512
4600
 
4513
4601
  const table = "my_table_name";
4514
4602
  const client = new DynamoDB.DocumentClient();
package/index.d.ts CHANGED
@@ -1120,6 +1120,8 @@ type DocumentClient = {
1120
1120
  batchWrite: DocumentClientMethod;
1121
1121
  batchGet: DocumentClientMethod;
1122
1122
  scan: DocumentClientMethod;
1123
+ } | {
1124
+ send: (command: any) => Promise<any>;
1123
1125
  }
1124
1126
 
1125
1127
  type ElectroDBMethodTypes = "put" | "get" | "query" | "scan" | "update" | "delete" | "remove" | "patch" | "create" | "batchGet" | "batchWrite";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
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": {
@@ -25,6 +25,8 @@
25
25
  },
26
26
  "homepage": "https://github.com/tywalch/electrodb#readme",
27
27
  "devDependencies": {
28
+ "@aws-sdk/client-dynamodb": "^3.54.1",
29
+ "@aws-sdk/lib-dynamodb": "^3.54.1",
28
30
  "@istanbuljs/nyc-config-typescript": "^1.0.2",
29
31
  "@types/chai": "^4.2.12",
30
32
  "@types/mocha": "^8.0.3",
@@ -59,6 +61,7 @@
59
61
  "directory": "test"
60
62
  },
61
63
  "dependencies": {
62
- "jsonschema": "1.2.7"
64
+ "jsonschema": "1.2.7",
65
+ "@aws-sdk/lib-dynamodb": "^3.54.1"
63
66
  }
64
67
  }
package/src/client.js ADDED
@@ -0,0 +1,135 @@
1
+ const {isFunction} = require('./validations');
2
+ const {ElectroError, ErrorCodes} = require('./errors');
3
+ const lib = require('@aws-sdk/lib-dynamodb');
4
+
5
+ const DocumentClientVersions = {
6
+ v2: 'v2',
7
+ v3: 'v3',
8
+ electro: 'electro',
9
+ };
10
+
11
+ const v3Methods = ['send'];
12
+ const v2Methods = ['get', 'put', 'update', 'delete', 'batchWrite', 'batchGet', 'scan', 'query', 'createSet'];
13
+ const supportedClientVersions = {
14
+ [DocumentClientVersions.v2]: v2Methods,
15
+ [DocumentClientVersions.v3]: v3Methods,
16
+ }
17
+
18
+ class DocumentClientV3Wrapper {
19
+ static init(client) {
20
+ return new DocumentClientV3Wrapper(client, lib);
21
+ }
22
+
23
+ constructor(client, lib) {
24
+ this.client = client;
25
+ this.lib = lib;
26
+ }
27
+
28
+ promiseWrap(fn) {
29
+ return {
30
+ promise: async () => {
31
+ return fn();
32
+ }
33
+ }
34
+ }
35
+
36
+ get(params) {
37
+ return this.promiseWrap(() => {
38
+ const command = new this.lib.GetCommand(params);
39
+ return this.client.send(command);
40
+ });
41
+ }
42
+ put(params) {
43
+ return this.promiseWrap(() => {
44
+ const command = new this.lib.PutCommand(params);
45
+ return this.client.send(command);
46
+ });
47
+ }
48
+ update(params) {
49
+ return this.promiseWrap(() => {
50
+ const command = new this.lib.UpdateCommand(params);
51
+ return this.client.send(command);
52
+ });
53
+ }
54
+ delete(params) {
55
+ return this.promiseWrap(async () => {
56
+ const command = new this.lib.DeleteCommand(params);
57
+ return this.client.send(command);
58
+ });
59
+ }
60
+ batchWrite(params) {
61
+ return this.promiseWrap(async () => {
62
+ const command = new this.lib.BatchWriteCommand(params);
63
+ return this.client.send(command);
64
+ });
65
+ }
66
+ batchGet(params) {
67
+ return this.promiseWrap(async () => {
68
+ const command = new this.lib.BatchGetCommand(params);
69
+ return this.client.send(command);
70
+ });
71
+ }
72
+ scan(params) {
73
+ return this.promiseWrap(async () => {
74
+ const command = new this.lib.ScanCommand(params);
75
+ return this.client.send(command);
76
+ });
77
+ }
78
+ query(params) {
79
+ return this.promiseWrap(async () => {
80
+ const command = new this.lib.QueryCommand(params);
81
+ return this.client.send(command);
82
+ });
83
+ }
84
+ createSet(value) {
85
+ if (Array.isArray(value)) {
86
+ return new Set(value);
87
+ } else {
88
+ return new Set([value]);
89
+ }
90
+ }
91
+ }
92
+
93
+ function identifyClientVersion(client = {}) {
94
+ if (client instanceof DocumentClientV3Wrapper) return DocumentClientVersions.electro;
95
+ for (const [version, methods] of Object.entries(supportedClientVersions)) {
96
+ const hasMethods = methods.every(method => {
97
+ return method in client && isFunction(client[method]);
98
+ });
99
+ if (hasMethods) {
100
+ return version;
101
+ }
102
+ }
103
+ }
104
+
105
+ function normalizeClient(client) {
106
+ if (client === undefined) return client;
107
+ const version = identifyClientVersion(client);
108
+ switch(version) {
109
+ case DocumentClientVersions.v3:
110
+ return DocumentClientV3Wrapper.init(client);
111
+ case DocumentClientVersions.v2:
112
+ case DocumentClientVersions.electro:
113
+ return client;
114
+ default:
115
+ throw new ElectroError(ErrorCodes.InvalidClientProvided, 'Invalid DynamoDB Document Client provided. ElectroDB supports the v2 and v3 DynamoDB Document Clients from the aws-sdk');
116
+ }
117
+ }
118
+
119
+ function normalizeConfig(config = {}) {
120
+ return {
121
+ ...config,
122
+ client: normalizeClient(config.client)
123
+ }
124
+ }
125
+
126
+ module.exports = {
127
+ v2Methods,
128
+ v3Methods,
129
+ normalizeClient,
130
+ normalizeConfig,
131
+ identifyClientVersion,
132
+ DocumentClientVersions,
133
+ supportedClientVersions,
134
+ DocumentClientV3Wrapper,
135
+ };
package/src/entity.js CHANGED
@@ -7,11 +7,13 @@ const { WhereFactory } = require("./where");
7
7
  const { clauses, ChainState } = require("./clauses");
8
8
  const {EventManager} = require('./events');
9
9
  const validations = require("./validations");
10
+ const c = require('./client');
10
11
  const u = require("./util");
11
12
  const e = require("./errors");
12
13
 
13
14
  class Entity {
14
15
  constructor(model, config = {}) {
16
+ config = c.normalizeConfig(config);
15
17
  this.eventManager = new EventManager({
16
18
  listeners: config.listeners
17
19
  });
@@ -588,7 +590,7 @@ class Entity {
588
590
 
589
591
  _setClient(client) {
590
592
  if (client) {
591
- this.client = client;
593
+ this.client = c.normalizeClient(client);
592
594
  }
593
595
  }
594
596
 
package/src/errors.js CHANGED
@@ -133,6 +133,12 @@ const ErrorCodes = {
133
133
  name: "InvalidListenerProvided",
134
134
  sym: ErrorCode,
135
135
  },
136
+ InvalidClientProvided: {
137
+ code: 1021,
138
+ section: "invalid-client-provided",
139
+ name: "InvalidClientProvided",
140
+ sym: ErrorCode,
141
+ },
136
142
  MissingAttribute: {
137
143
  code: 2001,
138
144
  section: "missing-attribute",
package/src/schema.js CHANGED
@@ -768,21 +768,28 @@ class SetAttribute extends Attribute {
768
768
  _makeSetValidate(definition) {
769
769
  const validate = this._makeValidate(definition.validate);
770
770
  return (value) => {
771
- if (Array.isArray(value)) {
772
- return validate([...value]);
773
- } else if (value && value.wrapperName === 'Set') {
774
- return validate([...value.values])
775
- } else {
776
- return validate(value);
771
+ switch (getValueType(value)) {
772
+ case ValueTypes.array:
773
+ return validate([...value]);
774
+ case ValueTypes.aws_set:
775
+ return validate([...value.values]);
776
+ case ValueTypes.set:
777
+ return validate(Array.from(value));
778
+ default:
779
+ return validate(value);
777
780
  }
778
781
  }
779
782
  }
780
783
 
781
784
  fromDDBSet(value) {
782
- if (getValueType(value) === ValueTypes.aws_set) {
783
- return value.values;
785
+ switch (getValueType(value)) {
786
+ case ValueTypes.aws_set:
787
+ return [...value.values];
788
+ case ValueTypes.set:
789
+ return Array.from(value);
790
+ default:
791
+ return value;
784
792
  }
785
- return value;
786
793
  }
787
794
 
788
795
  _createDDBSet(value) {
@@ -820,15 +827,14 @@ class SetAttribute extends Attribute {
820
827
 
821
828
  _makeGet(get, items) {
822
829
  this._checkGetSet(get, "get");
823
-
824
830
  const getter = get || ((attr) => attr);
825
-
826
831
  return (values, siblings) => {
827
832
  if (values !== undefined) {
828
833
  const data = this.fromDDBSet(values);
829
834
  return getter(data, siblings);
830
835
  }
831
- let results = getter(data, siblings);
836
+ const data = this.fromDDBSet(values);
837
+ const results = getter(data, siblings);
832
838
  if (results !== undefined) {
833
839
  // if not undefined, try to convert, else no need to return
834
840
  return this.fromDDBSet(results);
@@ -840,9 +846,7 @@ class SetAttribute extends Attribute {
840
846
  this._checkGetSet(set, "set");
841
847
  const setter = set || ((attr) => attr);
842
848
  return (values, siblings) => {
843
- const results = values && values.wrapperName === 'Set'
844
- ? setter(values.values, siblings)
845
- : setter(values, siblings)
849
+ const results = setter(this.fromDDBSet(values), siblings);
846
850
  if (results !== undefined) {
847
851
  return this.toDDBSet(results);
848
852
  }
@@ -879,7 +883,7 @@ class SetAttribute extends Attribute {
879
883
  } else {
880
884
  errors.push(
881
885
  new e.ElectroAttributeValidationError(this.path, `Invalid value type at attribute path: "${this.path}". Expected value to be an Expected value to be an Array, native JavaScript Set objects, or DocumentClient Set objects to fulfill attribute type "${this.type}"`)
882
- )
886
+ );
883
887
  }
884
888
  for (const item of arr) {
885
889
  const [isValid, errorValues] = this.items.isValid(item);
package/src/service.js CHANGED
@@ -6,6 +6,7 @@ const { FilterOperations } = require("./operations");
6
6
  const { WhereFactory } = require("./where");
7
7
  const { getInstanceType, getModelVersion, applyBetaModelOverrides } = require("./util");
8
8
  const v = require("./validations");
9
+ const c = require('./client');
9
10
  const e = require("./errors");
10
11
  const u = require("./util");
11
12
 
@@ -104,6 +105,7 @@ class Service {
104
105
  }
105
106
 
106
107
  constructor(service = "", config = {}) {
108
+ config = c.normalizeConfig(config);
107
109
  this.version = ServiceVersions.v1;
108
110
  let type = inferConstructorType(service);
109
111
  switch(type) {