electrodb 1.7.0 → 1.8.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/CHANGELOG.md CHANGED
@@ -150,4 +150,20 @@ 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)]
158
+
159
+ ## [1.7.2] - 2022-03-27
160
+ ### Fixed
161
+ - Fixed issue#111, `update` method specific query option typing no longer lost when using a `where` method in a query chain
162
+ - Fixing incorrect typing for exposed `UpdateEntityItem` type. Exported type was missing composite key attributes
163
+
164
+ ## [1.8.0] - 2022-03-28
165
+ ### Added
166
+ - Expected typings for the injected v2 client now include methods for `transactWrite` and `transactGet`
167
+ ### Changed
168
+ - Map attributes will now always resolve to least an empty object on a `create` and `put` methods (instead of just the root map)
169
+ - In the past, default values for property attributes on maps only resolves when a user provided an object to place the values on. Now default values within maps attributes will now always resolve onto the object on `create` and `put` methods.
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
@@ -1,3 +1,9 @@
1
+ type Flatten<T> = T extends any[]
2
+ ? T
3
+ : T extends object
4
+ ? {[Key in keyof T]: Flatten<T[Key]>}
5
+ : T;
6
+
1
7
  declare const WhereSymbol: unique symbol;
2
8
  declare const UpdateDataSymbol: unique symbol;
3
9
 
@@ -987,6 +993,13 @@ interface UpdateQueryOptions extends QueryOptions {
987
993
  response?: "default" | "none" | 'all_old' | 'updated_old' | 'all_new' | 'updated_new';
988
994
  }
989
995
 
996
+ interface UpdateQueryParams {
997
+ response?: "default" | "none" | 'all_old' | 'updated_old' | 'all_new' | 'updated_new';
998
+ table?: string;
999
+ params?: object;
1000
+ originalErr?: boolean;
1001
+ }
1002
+
990
1003
  interface DeleteQueryOptions extends QueryOptions {
991
1004
  response?: "default" | "none" | 'all_old';
992
1005
  }
@@ -1042,7 +1055,13 @@ type SingleRecordOperationOptions<A extends string, F extends A, C extends strin
1042
1055
  type PutRecordOperationOptions<A extends string, F extends A, C extends string, S extends Schema<A,F,C>, ResponseType> = {
1043
1056
  go: GoRecord<ResponseType, PutQueryOptions>;
1044
1057
  params: ParamRecord<PutQueryOptions>;
1045
- where: WhereClause<A,F,C,S,Item<A,F,C,S,S["attributes"]>,SingleRecordOperationOptions<A,F,C,S,ResponseType>>;
1058
+ where: WhereClause<A,F,C,S,Item<A,F,C,S,S["attributes"]>,PutRecordOperationOptions<A,F,C,S,ResponseType>>;
1059
+ };
1060
+
1061
+ type UpdateRecordOperationOptions<A extends string, F extends A, C extends string, S extends Schema<A,F,C>, ResponseType> = {
1062
+ go: GoRecord<ResponseType, UpdateQueryOptions>;
1063
+ params: ParamRecord<UpdateQueryParams>;
1064
+ where: WhereClause<A,F,C,S,Item<A,F,C,S,S["attributes"]>,PutRecordOperationOptions<A,F,C,S,ResponseType>>;
1046
1065
  };
1047
1066
 
1048
1067
  type DeleteRecordOperationOptions<A extends string, F extends A, C extends string, S extends Schema<A,F,C>, ResponseType> = {
@@ -1058,7 +1077,7 @@ type BulkRecordOperationOptions<A extends string, F extends A, C extends string,
1058
1077
 
1059
1078
  type SetRecordActionOptions<A extends string, F extends A, C extends string, S extends Schema<A,F,C>, SetAttr,IndexCompositeAttributes,TableItem> = {
1060
1079
  go: GoRecord<Partial<TableItem>, UpdateQueryOptions>;
1061
- params: ParamRecord<UpdateQueryOptions>;
1080
+ params: ParamRecord<UpdateQueryParams>;
1062
1081
  set: SetRecord<A,F,C,S, SetItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
1063
1082
  remove: SetRecord<A,F,C,S, Array<keyof SetItem<A,F,C,S>>,IndexCompositeAttributes,TableItem>;
1064
1083
  add: SetRecord<A,F,C,S, AddItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
@@ -1066,7 +1085,7 @@ type SetRecordActionOptions<A extends string, F extends A, C extends string, S e
1066
1085
  append: SetRecord<A,F,C,S, AppendItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
1067
1086
  delete: SetRecord<A,F,C,S, DeleteItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
1068
1087
  data: DataUpdateMethodRecord<A,F,C,S, Item<A,F,C,S,S["attributes"]>,IndexCompositeAttributes,TableItem>;
1069
- where: WhereClause<A,F,C,S, Item<A,F,C,S,S["attributes"]>,RecordsActionOptions<A,F,C,S,TableItem,IndexCompositeAttributes>>;
1088
+ where: WhereClause<A,F,C,S, Item<A,F,C,S,S["attributes"]>,SetRecordActionOptions<A,F,C,S,SetAttr,IndexCompositeAttributes,TableItem>>;
1070
1089
  }
1071
1090
 
1072
1091
  type SetRecord<A extends string, F extends A, C extends string, S extends Schema<A,F,C>, SetAttr, IndexCompositeAttributes, TableItem> = (properties: SetAttr) => SetRecordActionOptions<A,F,C,S, SetAttr, IndexCompositeAttributes, TableItem>;
@@ -1120,6 +1139,10 @@ type DocumentClient = {
1120
1139
  batchWrite: DocumentClientMethod;
1121
1140
  batchGet: DocumentClientMethod;
1122
1141
  scan: DocumentClientMethod;
1142
+ transactGet: DocumentClientMethod;
1143
+ transactWrite: DocumentClientMethod;
1144
+ } | {
1145
+ send: (command: any) => Promise<any>;
1123
1146
  }
1124
1147
 
1125
1148
  type ElectroDBMethodTypes = "put" | "get" | "query" | "scan" | "update" | "delete" | "remove" | "patch" | "create" | "batchGet" | "batchWrite";
@@ -1480,7 +1503,7 @@ export type CreateEntityItem<E extends Entity<any, any, any, any>> =
1480
1503
 
1481
1504
  export type UpdateEntityItem<E extends Entity<any, any, any, any>> =
1482
1505
  E extends Entity<infer A, infer F, infer C, infer S>
1483
- ? SetItem<A, F, C, S>
1506
+ ? Partial<ResponseItem<A,F,C,S>>
1484
1507
  : never;
1485
1508
 
1486
1509
  export type UpdateAddEntityItem<E extends Entity<any, any, any, any>> =
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "1.7.0",
3
+ "version": "1.8.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": {
@@ -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,147 @@
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', 'transactWrite', 'transactGet'];
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
+ transactWrite(params) {
85
+ return this.promiseWrap(async () => {
86
+ const command = new this.lib.TransactWriteCommand(params);
87
+ return this.client.send(command);
88
+ });
89
+ }
90
+ transactGet(params) {
91
+ return this.promiseWrap(async () => {
92
+ const command = new this.lib.TransactGetCommand(params);
93
+ return this.client.send(command);
94
+ });
95
+ }
96
+ createSet(value) {
97
+ if (Array.isArray(value)) {
98
+ return new Set(value);
99
+ } else {
100
+ return new Set([value]);
101
+ }
102
+ }
103
+ }
104
+
105
+ function identifyClientVersion(client = {}) {
106
+ if (client instanceof DocumentClientV3Wrapper) return DocumentClientVersions.electro;
107
+ for (const [version, methods] of Object.entries(supportedClientVersions)) {
108
+ const hasMethods = methods.every(method => {
109
+ return method in client && isFunction(client[method]);
110
+ });
111
+ if (hasMethods) {
112
+ return version;
113
+ }
114
+ }
115
+ }
116
+
117
+ function normalizeClient(client) {
118
+ if (client === undefined) return client;
119
+ const version = identifyClientVersion(client);
120
+ switch(version) {
121
+ case DocumentClientVersions.v3:
122
+ return DocumentClientV3Wrapper.init(client);
123
+ case DocumentClientVersions.v2:
124
+ case DocumentClientVersions.electro:
125
+ return client;
126
+ default:
127
+ throw new ElectroError(ErrorCodes.InvalidClientProvided, 'Invalid DynamoDB Document Client provided. ElectroDB supports the v2 and v3 DynamoDB Document Clients from the aws-sdk');
128
+ }
129
+ }
130
+
131
+ function normalizeConfig(config = {}) {
132
+ return {
133
+ ...config,
134
+ client: normalizeClient(config.client)
135
+ }
136
+ }
137
+
138
+ module.exports = {
139
+ v2Methods,
140
+ v3Methods,
141
+ normalizeClient,
142
+ normalizeConfig,
143
+ identifyClientVersion,
144
+ DocumentClientVersions,
145
+ supportedClientVersions,
146
+ DocumentClientV3Wrapper,
147
+ };
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
@@ -575,30 +575,30 @@ class MapAttribute extends Attribute {
575
575
  }
576
576
  return v;
577
577
  }
578
- const valueType = getValueType(value);
579
- if (value === undefined) {
580
- return getValue(value);
581
- } else if (value && valueType !== "object" && Object.keys(value).length === 0) {
582
- return getValue(value);
578
+
579
+ let data = value === undefined
580
+ ? getValue(value)
581
+ : value;
582
+
583
+ const valueType = getValueType(data);
584
+
585
+ if (data === undefined) {
586
+ data = {};
583
587
  } else if (valueType !== "object") {
584
588
  throw new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Expected value to be an object to fulfill attribute type "${this.type}"`);
585
589
  }
586
590
 
587
- const data = {};
591
+ const response = {};
588
592
 
589
593
  for (const name of Object.keys(this.properties.attributes)) {
590
594
  const attribute = this.properties.attributes[name];
591
- const results = attribute.val(value[attribute.name]);
595
+ const results = attribute.val(data[attribute.name]);
592
596
  if (results !== undefined) {
593
- data[name] = results;
597
+ response[name] = results;
594
598
  }
595
599
  }
596
600
 
597
- if (Object.keys(data).length > 0) {
598
- return getValue(data);
599
- } else {
600
- return getValue();
601
- }
601
+ return response;
602
602
  }
603
603
  }
604
604
 
@@ -726,28 +726,25 @@ class ListAttribute extends Attribute {
726
726
  return v;
727
727
  }
728
728
 
729
- if (value === undefined) {
730
- return this.default();
731
- } else if (Array.isArray(value) && value.length === 0) {
732
- return value;
733
- } else if (!Array.isArray(value)) {
729
+ const data = value === undefined
730
+ ? getValue(value)
731
+ : value;
732
+
733
+ if (data === undefined) {
734
+ return data;
735
+ } else if (!Array.isArray(data)) {
734
736
  throw new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path "${this.path}. Received value of type "${getValueType(value)}", expected value of type "array"`);
735
737
  }
736
738
 
737
- const data = [];
738
-
739
- for (const v of value) {
740
- const results = this.items.val(v);
739
+ const response = [];
740
+ for (const d of data) {
741
+ const results = this.items.val(d);
741
742
  if (results !== undefined) {
742
- data.push(results);
743
+ response.push(results);
743
744
  }
744
745
  }
745
746
 
746
- if (data.filter(value => value !== undefined).length > 0) {
747
- return getValue(data);
748
- } else {
749
- return getValue();
750
- }
747
+ return response;
751
748
  }
752
749
  }
753
750
 
@@ -768,21 +765,28 @@ class SetAttribute extends Attribute {
768
765
  _makeSetValidate(definition) {
769
766
  const validate = this._makeValidate(definition.validate);
770
767
  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);
768
+ switch (getValueType(value)) {
769
+ case ValueTypes.array:
770
+ return validate([...value]);
771
+ case ValueTypes.aws_set:
772
+ return validate([...value.values]);
773
+ case ValueTypes.set:
774
+ return validate(Array.from(value));
775
+ default:
776
+ return validate(value);
777
777
  }
778
778
  }
779
779
  }
780
780
 
781
781
  fromDDBSet(value) {
782
- if (getValueType(value) === ValueTypes.aws_set) {
783
- return value.values;
782
+ switch (getValueType(value)) {
783
+ case ValueTypes.aws_set:
784
+ return [...value.values];
785
+ case ValueTypes.set:
786
+ return Array.from(value);
787
+ default:
788
+ return value;
784
789
  }
785
- return value;
786
790
  }
787
791
 
788
792
  _createDDBSet(value) {
@@ -820,15 +824,14 @@ class SetAttribute extends Attribute {
820
824
 
821
825
  _makeGet(get, items) {
822
826
  this._checkGetSet(get, "get");
823
-
824
827
  const getter = get || ((attr) => attr);
825
-
826
828
  return (values, siblings) => {
827
829
  if (values !== undefined) {
828
830
  const data = this.fromDDBSet(values);
829
831
  return getter(data, siblings);
830
832
  }
831
- let results = getter(data, siblings);
833
+ const data = this.fromDDBSet(values);
834
+ const results = getter(data, siblings);
832
835
  if (results !== undefined) {
833
836
  // if not undefined, try to convert, else no need to return
834
837
  return this.fromDDBSet(results);
@@ -840,9 +843,7 @@ class SetAttribute extends Attribute {
840
843
  this._checkGetSet(set, "set");
841
844
  const setter = set || ((attr) => attr);
842
845
  return (values, siblings) => {
843
- const results = values && values.wrapperName === 'Set'
844
- ? setter(values.values, siblings)
845
- : setter(values, siblings)
846
+ const results = setter(this.fromDDBSet(values), siblings);
846
847
  if (results !== undefined) {
847
848
  return this.toDDBSet(results);
848
849
  }
@@ -879,7 +880,7 @@ class SetAttribute extends Attribute {
879
880
  } else {
880
881
  errors.push(
881
882
  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
- )
883
+ );
883
884
  }
884
885
  for (const item of arr) {
885
886
  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) {