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 +17 -1
- package/README.md +150 -62
- package/index.d.ts +27 -4
- package/package.json +5 -2
- package/src/client.js +147 -0
- package/src/entity.js +3 -1
- package/src/errors.js +6 -0
- package/src/schema.js +45 -44
- package/src/service.js +2 -0
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"]>,
|
|
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<
|
|
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"]>,
|
|
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
|
-
?
|
|
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.
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
|
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(
|
|
595
|
+
const results = attribute.val(data[attribute.name]);
|
|
592
596
|
if (results !== undefined) {
|
|
593
|
-
|
|
597
|
+
response[name] = results;
|
|
594
598
|
}
|
|
595
599
|
}
|
|
596
600
|
|
|
597
|
-
|
|
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
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
743
|
+
response.push(results);
|
|
743
744
|
}
|
|
744
745
|
}
|
|
745
746
|
|
|
746
|
-
|
|
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
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
-
|
|
783
|
-
|
|
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
|
-
|
|
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
|
|
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) {
|