electrodb 1.6.3 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/README.md +346 -9
- package/index.d.ts +86 -11
- package/package.json +5 -2
- package/src/clauses.js +3 -2
- package/src/client.js +135 -0
- package/src/entity.js +77 -32
- package/src/errors.js +13 -1
- package/src/events.js +67 -0
- package/src/schema.js +20 -16
- package/src/service.js +8 -0
- package/src/types.js +7 -1
- package/src/util.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -146,4 +146,17 @@ All notable changes to this project will be documented in this file. Breaking ch
|
|
|
146
146
|
|
|
147
147
|
## [1.6.3] - 2022-02-22
|
|
148
148
|
### Added
|
|
149
|
-
- Add `data` update operation `ifNotExists` to allow for use of the UpdateExpression function "if_not_exists()".
|
|
149
|
+
- Add `data` update operation `ifNotExists` to allow for use of the UpdateExpression function "if_not_exists()".
|
|
150
|
+
|
|
151
|
+
## [1.7.0] - 2022-03-13
|
|
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. [[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
|
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)
|
|
@@ -305,9 +313,9 @@ npm install electrodb --save
|
|
|
305
313
|
# Usage
|
|
306
314
|
Require/import `Entity` and/or `Service` from `electrodb`:
|
|
307
315
|
```javascript
|
|
308
|
-
const {Entity, Service} = require("electrodb");
|
|
316
|
+
const { Entity, Service } = require("electrodb");
|
|
309
317
|
// or
|
|
310
|
-
import {Entity, Service} from "electrodb";
|
|
318
|
+
import { Entity, Service } from "electrodb";
|
|
311
319
|
```
|
|
312
320
|
|
|
313
321
|
# Entities and Services
|
|
@@ -325,9 +333,9 @@ In ***ElectroDB*** an `Entity` is represents a single business object. For examp
|
|
|
325
333
|
|
|
326
334
|
Require or import `Entity` from `electrodb`:
|
|
327
335
|
```javascript
|
|
328
|
-
const {Entity} = require("electrodb");
|
|
336
|
+
const { Entity } = require("electrodb");
|
|
329
337
|
// or
|
|
330
|
-
import {Entity} from "electrodb";
|
|
338
|
+
import { Entity } from "electrodb";
|
|
331
339
|
```
|
|
332
340
|
|
|
333
341
|
> When using TypeScript, for strong type checking, be sure to either add your model as an object literal to the Entity constructor or create your model using const assertions with the `as const` syntax.
|
|
@@ -337,9 +345,9 @@ In ***ElectroDB*** a `Service` represents a collection of related Entities. Serv
|
|
|
337
345
|
|
|
338
346
|
Require:
|
|
339
347
|
```javascript
|
|
340
|
-
const {Service} = require("electrodb");
|
|
348
|
+
const { Service } = require("electrodb");
|
|
341
349
|
// or
|
|
342
|
-
import {Service} from "electrodb";
|
|
350
|
+
import { Service } from "electrodb";
|
|
343
351
|
```
|
|
344
352
|
|
|
345
353
|
## TypeScript Support
|
|
@@ -371,7 +379,7 @@ The property name you assign the entity will then be "alias", or name, you can r
|
|
|
371
379
|
|
|
372
380
|
Services take an optional second parameter, similar to Entities, with a `client` and `table`. Using this constructor interface, the Service will utilize the values from those entities, if they were provided, or be passed values to override the `client` or `table` name on the individual entities.
|
|
373
381
|
|
|
374
|
-
|
|
382
|
+
While not yet typed, this pattern will also accept Models, or a mix of Entities and Models, in the same object literal format.
|
|
375
383
|
|
|
376
384
|
## Join
|
|
377
385
|
When using JavaScript, use `join` to add [Entities](#entities) or [Models](#model) onto a Service.
|
|
@@ -4000,7 +4008,7 @@ TaskApp.collections
|
|
|
4000
4008
|
```
|
|
4001
4009
|
|
|
4002
4010
|
## Execute Queries
|
|
4003
|
-
Lastly, all query chains end with either a `.go()
|
|
4011
|
+
Lastly, all query chains end with either a `.go()`, `.params()`, or `page()` method invocation. These terminal methods will either execute the query to DynamoDB (`.go()`) or return formatted parameters for use with the DynamoDB docClient (`.params()`).
|
|
4004
4012
|
|
|
4005
4013
|
Both `.params()` and `.go()` take a query configuration object which is detailed more in the section [Query Options](#query-options).
|
|
4006
4014
|
|
|
@@ -4287,6 +4295,8 @@ By default, **ElectroDB** enables you to work with records as the names and prop
|
|
|
4287
4295
|
ignoreOwnership?: boolean;
|
|
4288
4296
|
limit?: number;
|
|
4289
4297
|
pages?: number;
|
|
4298
|
+
logger?: (event) => void;
|
|
4299
|
+
listeners Array<(event) => void>;
|
|
4290
4300
|
};
|
|
4291
4301
|
```
|
|
4292
4302
|
|
|
@@ -4304,6 +4314,333 @@ response | `"default"` | Used as a convenience for applying the
|
|
|
4304
4314
|
ignoreOwnership | `false` | By default, **ElectroDB** interrogates items returned from a query for the presence of matching entity "identifiers". This helps to ensure other entities, or other versions of an entity, are filtered from your results. If you are using ElectroDB with an existing table/dataset you can turn off this feature by setting this property to `true`.
|
|
4305
4315
|
limit | _none_ | A target for the number of items to return from DynamoDB. If this option is passed, Queries on entities and through collections will paginate DynamoDB until this limit is reached or all items for that query have been returned.
|
|
4306
4316
|
pages | ∞ | How many DynamoDB pages should a query iterate through before stopping. By default ElectroDB paginate through all results for your query.
|
|
4317
|
+
listeners | `[]` | An array of callbacks that are invoked when [internal ElectroDB events](#events) occur.
|
|
4318
|
+
logger | _none_ | A convenience option for a single event listener that semantically can be used for logging.
|
|
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
|
+
|
|
4460
|
+
# Events
|
|
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!
|
|
4462
|
+
|
|
4463
|
+
## Query Event
|
|
4464
|
+
The `query` event occurs when a query is made via the terminal methods [`go()`](#go) and [`page()`](#page). The event includes the exact parameters given to the provided client, the ElectroDB method used, and the ElectroDB configuration provided.
|
|
4465
|
+
|
|
4466
|
+
*Type:*
|
|
4467
|
+
```typescript
|
|
4468
|
+
interface ElectroQueryEvent<P extends any = any> {
|
|
4469
|
+
type: 'query';
|
|
4470
|
+
method: "put" | "get" | "query" | "scan" | "update" | "delete" | "remove" | "patch" | "create" | "batchGet" | "batchWrite";
|
|
4471
|
+
config: any;
|
|
4472
|
+
params: P;
|
|
4473
|
+
}
|
|
4474
|
+
```
|
|
4475
|
+
|
|
4476
|
+
*Example Input:*
|
|
4477
|
+
```typescript
|
|
4478
|
+
const prop1 = "22874c81-27c4-4264-92c3-b280aa79aa30";
|
|
4479
|
+
const prop2 = "366aade8-a7c0-4328-8e14-0331b185de4e";
|
|
4480
|
+
const prop3 = "3ec9ed0c-7497-4d05-bdb8-86c09a618047";
|
|
4481
|
+
|
|
4482
|
+
entity.update({ prop1, prop2 })
|
|
4483
|
+
.set({ prop3 })
|
|
4484
|
+
.go()
|
|
4485
|
+
```
|
|
4486
|
+
|
|
4487
|
+
*Example Output:*
|
|
4488
|
+
```json
|
|
4489
|
+
{
|
|
4490
|
+
"type": "query",
|
|
4491
|
+
"method": "update",
|
|
4492
|
+
"params": {
|
|
4493
|
+
"UpdateExpression": "SET #prop3 = :prop3_u0, #prop1 = :prop1_u0, #prop2 = :prop2_u0, #__edb_e__ = :__edb_e___u0, #__edb_v__ = :__edb_v___u0",
|
|
4494
|
+
"ExpressionAttributeNames": {
|
|
4495
|
+
"#prop3": "prop3",
|
|
4496
|
+
"#prop1": "prop1",
|
|
4497
|
+
"#prop2": "prop2",
|
|
4498
|
+
"#__edb_e__": "__edb_e__",
|
|
4499
|
+
"#__edb_v__": "__edb_v__"
|
|
4500
|
+
},
|
|
4501
|
+
"ExpressionAttributeValues": {
|
|
4502
|
+
":prop3_u0": "3ec9ed0c-7497-4d05-bdb8-86c09a618047",
|
|
4503
|
+
":prop1_u0": "22874c81-27c4-4264-92c3-b280aa79aa30",
|
|
4504
|
+
":prop2_u0": "366aade8-a7c0-4328-8e14-0331b185de4e",
|
|
4505
|
+
":__edb_e___u0": "entity",
|
|
4506
|
+
":__edb_v___u0": "1"
|
|
4507
|
+
},
|
|
4508
|
+
"TableName": "electro",
|
|
4509
|
+
"Key": {
|
|
4510
|
+
"pk": "$test#prop1_22874c81-27c4-4264-92c3-b280aa79aa30",
|
|
4511
|
+
"sk": "$testcollection#entity_1#prop2_366aade8-a7c0-4328-8e14-0331b185de4e"
|
|
4512
|
+
}
|
|
4513
|
+
},
|
|
4514
|
+
"config": { }
|
|
4515
|
+
}
|
|
4516
|
+
```
|
|
4517
|
+
|
|
4518
|
+
## Results Event
|
|
4519
|
+
The `results` event occurs when results are returned from DynamoDB. The event includes the exact results returned from the provided client, the ElectroDB method used, and the ElectroDB configuration provided. Note this event handles both failed (or thrown) results in addition to returned (or resolved) results.
|
|
4520
|
+
|
|
4521
|
+
> **Pro-Tip:**
|
|
4522
|
+
> Use this event to hook into the DyanmoDB's [consumed capacity](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ReturnConsumedCapacity) statistics to learn more about the impact and cost associated with your queries.
|
|
4523
|
+
|
|
4524
|
+
*Type::*
|
|
4525
|
+
```typescript
|
|
4526
|
+
interface ElectroResultsEvent<R extends any = any> {
|
|
4527
|
+
type: 'results';
|
|
4528
|
+
method: "put" | "get" | "query" | "scan" | "update" | "delete" | "remove" | "patch" | "create" | "batchGet" | "batchWrite";
|
|
4529
|
+
config: any;
|
|
4530
|
+
results: R;
|
|
4531
|
+
success: boolean;
|
|
4532
|
+
}
|
|
4533
|
+
```
|
|
4534
|
+
|
|
4535
|
+
*Example Input:*
|
|
4536
|
+
```typescript
|
|
4537
|
+
const prop1 = "22874c81-27c4-4264-92c3-b280aa79aa30";
|
|
4538
|
+
const prop2 = "366aade8-a7c0-4328-8e14-0331b185de4e";
|
|
4539
|
+
|
|
4540
|
+
entity.get({ prop1, prop2 }).go();
|
|
4541
|
+
```
|
|
4542
|
+
|
|
4543
|
+
*Example Output:*
|
|
4544
|
+
```typescript
|
|
4545
|
+
{
|
|
4546
|
+
"type": "results",
|
|
4547
|
+
"method": "get",
|
|
4548
|
+
"config": { },
|
|
4549
|
+
"success": true,
|
|
4550
|
+
"results": {
|
|
4551
|
+
"Item": {
|
|
4552
|
+
"prop2": "366aade8-a7c0-4328-8e14-0331b185de4e",
|
|
4553
|
+
"sk": "$testcollection#entity_1#prop2_366aade8-a7c0-4328-8e14-0331b185de4e",
|
|
4554
|
+
"prop1": "22874c81-27c4-4264-92c3-b280aa79aa30",
|
|
4555
|
+
"prop3": "3ec9ed0c-7497-4d05-bdb8-86c09a618047",
|
|
4556
|
+
"__edb_e__": "entity",
|
|
4557
|
+
"__edb_v__": "1",
|
|
4558
|
+
"pk": "$test_1#prop1_22874c81-27c4-4264-92c3-b280aa79aa30"
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
```
|
|
4563
|
+
|
|
4564
|
+
# Listeners
|
|
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).
|
|
4566
|
+
|
|
4567
|
+
> _NOTE: Listeners treated as synchronous callbacks and are not awaited. In the event that a callback throws an exception, ElectroDB will quietly catch and log the exception with `console.error` to prevent the exception from impacting your query.
|
|
4568
|
+
|
|
4569
|
+
*On the instantiation of an `Entity`:*
|
|
4570
|
+
```typescript
|
|
4571
|
+
import { DynamoDB } from 'aws-sdk';
|
|
4572
|
+
import { Entity, ElectroEvent } from 'electrodb';
|
|
4573
|
+
|
|
4574
|
+
const table = "my_table_name";
|
|
4575
|
+
const client = new DynamoDB.DocumentClient();
|
|
4576
|
+
const listener1 = (event: ElectroEvent) => {
|
|
4577
|
+
// do work
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
const listener2 = (event: ElectroEvent) => {
|
|
4581
|
+
// do work
|
|
4582
|
+
}
|
|
4583
|
+
|
|
4584
|
+
const task = new Entity({
|
|
4585
|
+
// your model
|
|
4586
|
+
}, {
|
|
4587
|
+
client,
|
|
4588
|
+
table,
|
|
4589
|
+
listeners: [
|
|
4590
|
+
listener1,
|
|
4591
|
+
listener2, // <----- supports multiple listeners
|
|
4592
|
+
]
|
|
4593
|
+
});
|
|
4594
|
+
```
|
|
4595
|
+
|
|
4596
|
+
*On the instantiation of an `Service`:*
|
|
4597
|
+
```typescript
|
|
4598
|
+
import { DynamoDB } from 'aws-sdk';
|
|
4599
|
+
import { Entity, ElectroEvent } from 'electrodb';
|
|
4600
|
+
|
|
4601
|
+
const table = "my_table_name";
|
|
4602
|
+
const client = new DynamoDB.DocumentClient();
|
|
4603
|
+
|
|
4604
|
+
const listener1 = (event: ElectroEvent) => {
|
|
4605
|
+
// do work
|
|
4606
|
+
}
|
|
4607
|
+
|
|
4608
|
+
const listener2 = (event: ElectroEvent) => {
|
|
4609
|
+
// do work
|
|
4610
|
+
}
|
|
4611
|
+
|
|
4612
|
+
const task = new Entity({
|
|
4613
|
+
// your model
|
|
4614
|
+
});
|
|
4615
|
+
|
|
4616
|
+
const user = new Entity({
|
|
4617
|
+
// your model
|
|
4618
|
+
});
|
|
4619
|
+
|
|
4620
|
+
const service = new Service({ task, user }, {
|
|
4621
|
+
client,
|
|
4622
|
+
table,
|
|
4623
|
+
listeners: [
|
|
4624
|
+
listener1,
|
|
4625
|
+
listener2, // <----- supports multiple listeners
|
|
4626
|
+
]
|
|
4627
|
+
});
|
|
4628
|
+
```
|
|
4629
|
+
|
|
4630
|
+
*As a [Query Option](#query-options):*
|
|
4631
|
+
```typescript
|
|
4632
|
+
const listener1 = (event: ElectroEvent) => {
|
|
4633
|
+
// do work
|
|
4634
|
+
}
|
|
4635
|
+
|
|
4636
|
+
const listener2 = (event: ElectroEvent) => {
|
|
4637
|
+
// do work
|
|
4638
|
+
}
|
|
4639
|
+
|
|
4640
|
+
task.query
|
|
4641
|
+
.assigned({ userId })
|
|
4642
|
+
.go({ listeners: [listener1, listener2] });
|
|
4643
|
+
```
|
|
4307
4644
|
|
|
4308
4645
|
# Errors:
|
|
4309
4646
|
|
|
@@ -4311,7 +4648,7 @@ Error Code | Description
|
|
|
4311
4648
|
:--------: | --------------------
|
|
4312
4649
|
1000s | Configuration Errors
|
|
4313
4650
|
2000s | Invalid Queries
|
|
4314
|
-
3000s | User Defined Errors
|
|
4651
|
+
3000s | User Defined Errors
|
|
4315
4652
|
4000s | DynamoDB Errors
|
|
4316
4653
|
5000s | Unexpected Errors
|
|
4317
4654
|
|
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
|
|
|
@@ -974,6 +980,8 @@ interface QueryOptions {
|
|
|
974
980
|
originalErr?: boolean;
|
|
975
981
|
ignoreOwnership?: boolean;
|
|
976
982
|
pages?: number;
|
|
983
|
+
listeners?: Array<EventListener>;
|
|
984
|
+
logger?: EventListener;
|
|
977
985
|
}
|
|
978
986
|
|
|
979
987
|
// subset of QueryOptions
|
|
@@ -985,6 +993,13 @@ interface UpdateQueryOptions extends QueryOptions {
|
|
|
985
993
|
response?: "default" | "none" | 'all_old' | 'updated_old' | 'all_new' | 'updated_new';
|
|
986
994
|
}
|
|
987
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
|
+
|
|
988
1003
|
interface DeleteQueryOptions extends QueryOptions {
|
|
989
1004
|
response?: "default" | "none" | 'all_old';
|
|
990
1005
|
}
|
|
@@ -1040,7 +1055,13 @@ type SingleRecordOperationOptions<A extends string, F extends A, C extends strin
|
|
|
1040
1055
|
type PutRecordOperationOptions<A extends string, F extends A, C extends string, S extends Schema<A,F,C>, ResponseType> = {
|
|
1041
1056
|
go: GoRecord<ResponseType, PutQueryOptions>;
|
|
1042
1057
|
params: ParamRecord<PutQueryOptions>;
|
|
1043
|
-
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>>;
|
|
1044
1065
|
};
|
|
1045
1066
|
|
|
1046
1067
|
type DeleteRecordOperationOptions<A extends string, F extends A, C extends string, S extends Schema<A,F,C>, ResponseType> = {
|
|
@@ -1056,7 +1077,7 @@ type BulkRecordOperationOptions<A extends string, F extends A, C extends string,
|
|
|
1056
1077
|
|
|
1057
1078
|
type SetRecordActionOptions<A extends string, F extends A, C extends string, S extends Schema<A,F,C>, SetAttr,IndexCompositeAttributes,TableItem> = {
|
|
1058
1079
|
go: GoRecord<Partial<TableItem>, UpdateQueryOptions>;
|
|
1059
|
-
params: ParamRecord<
|
|
1080
|
+
params: ParamRecord<UpdateQueryParams>;
|
|
1060
1081
|
set: SetRecord<A,F,C,S, SetItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
|
|
1061
1082
|
remove: SetRecord<A,F,C,S, Array<keyof SetItem<A,F,C,S>>,IndexCompositeAttributes,TableItem>;
|
|
1062
1083
|
add: SetRecord<A,F,C,S, AddItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
|
|
@@ -1064,7 +1085,7 @@ type SetRecordActionOptions<A extends string, F extends A, C extends string, S e
|
|
|
1064
1085
|
append: SetRecord<A,F,C,S, AppendItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
|
|
1065
1086
|
delete: SetRecord<A,F,C,S, DeleteItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
|
|
1066
1087
|
data: DataUpdateMethodRecord<A,F,C,S, Item<A,F,C,S,S["attributes"]>,IndexCompositeAttributes,TableItem>;
|
|
1067
|
-
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>>;
|
|
1068
1089
|
}
|
|
1069
1090
|
|
|
1070
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>;
|
|
@@ -1118,23 +1139,77 @@ type DocumentClient = {
|
|
|
1118
1139
|
batchWrite: DocumentClientMethod;
|
|
1119
1140
|
batchGet: DocumentClientMethod;
|
|
1120
1141
|
scan: DocumentClientMethod;
|
|
1121
|
-
}
|
|
1142
|
+
} | {
|
|
1143
|
+
send: (command: any) => Promise<any>;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
type ElectroDBMethodTypes = "put" | "get" | "query" | "scan" | "update" | "delete" | "remove" | "patch" | "create" | "batchGet" | "batchWrite";
|
|
1147
|
+
|
|
1148
|
+
interface ElectroQueryEvent<P extends any = any> {
|
|
1149
|
+
type: 'query';
|
|
1150
|
+
method: ElectroDBMethodTypes;
|
|
1151
|
+
config: any;
|
|
1152
|
+
params: P;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
interface ElectroResultsEvent<R extends any = any> {
|
|
1156
|
+
type: 'results';
|
|
1157
|
+
method: ElectroDBMethodTypes;
|
|
1158
|
+
config: any;
|
|
1159
|
+
results: R;
|
|
1160
|
+
success: boolean;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
type ElectroEvent =
|
|
1164
|
+
ElectroQueryEvent
|
|
1165
|
+
| ElectroResultsEvent;
|
|
1166
|
+
|
|
1167
|
+
type ElectroEventType = Pick<ElectroEvent, 'type'>;
|
|
1168
|
+
|
|
1169
|
+
type EventListener = (event: ElectroEvent) => void;
|
|
1170
|
+
|
|
1171
|
+
// todo: coming soon, more events!
|
|
1172
|
+
// | {
|
|
1173
|
+
// name: "error";
|
|
1174
|
+
// type: "configuration_error" | "invalid_query" | "dynamodb_client";
|
|
1175
|
+
// message: string;
|
|
1176
|
+
// details: ElectroError;
|
|
1177
|
+
// } | {
|
|
1178
|
+
// name: "error";
|
|
1179
|
+
// type: "user_defined";
|
|
1180
|
+
// message: string;
|
|
1181
|
+
// details: ElectroValidationError;
|
|
1182
|
+
// } | {
|
|
1183
|
+
// name: "warn";
|
|
1184
|
+
// type: "deprecation_warning" | "optimization_suggestion";
|
|
1185
|
+
// message: string;
|
|
1186
|
+
// details: any;
|
|
1187
|
+
// } | {
|
|
1188
|
+
// name: "info";
|
|
1189
|
+
// type: "client_updated" | "table_overwritten";
|
|
1190
|
+
// message: string;
|
|
1191
|
+
// details: any;
|
|
1192
|
+
// };
|
|
1122
1193
|
|
|
1123
1194
|
type EntityConfiguration = {
|
|
1124
1195
|
table?: string;
|
|
1125
|
-
client?: DocumentClient
|
|
1196
|
+
client?: DocumentClient;
|
|
1197
|
+
listeners?: Array<EventListener>;
|
|
1198
|
+
logger?: EventListener;
|
|
1126
1199
|
};
|
|
1127
1200
|
|
|
1128
1201
|
type ServiceConfiguration = {
|
|
1129
1202
|
table?: string;
|
|
1130
|
-
client?: DocumentClient
|
|
1203
|
+
client?: DocumentClient;
|
|
1204
|
+
listeners?: Array<EventListener>;
|
|
1205
|
+
logger?: EventListener;
|
|
1131
1206
|
};
|
|
1132
1207
|
|
|
1133
1208
|
type ParseSingleInput = {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1209
|
+
Item?: {[key: string]: any}
|
|
1210
|
+
} | {
|
|
1211
|
+
Attributes?: {[key: string]: any}
|
|
1212
|
+
} | null;
|
|
1138
1213
|
|
|
1139
1214
|
type ParseMultiInput = {
|
|
1140
1215
|
Items?: {[key: string]: any}[]
|
|
@@ -1426,7 +1501,7 @@ export type CreateEntityItem<E extends Entity<any, any, any, any>> =
|
|
|
1426
1501
|
|
|
1427
1502
|
export type UpdateEntityItem<E extends Entity<any, any, any, any>> =
|
|
1428
1503
|
E extends Entity<infer A, infer F, infer C, infer S>
|
|
1429
|
-
?
|
|
1504
|
+
? Partial<ResponseItem<A,F,C,S>>
|
|
1430
1505
|
: never;
|
|
1431
1506
|
|
|
1432
1507
|
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.7.2",
|
|
4
4
|
"description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -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/clauses.js
CHANGED
|
@@ -540,8 +540,9 @@ let clauses = {
|
|
|
540
540
|
if (!v.isStringHasLength(options.table) && !v.isStringHasLength(entity._getTableName())) {
|
|
541
541
|
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.`);
|
|
542
542
|
}
|
|
543
|
+
const method = state.getMethod();
|
|
543
544
|
let results;
|
|
544
|
-
switch (
|
|
545
|
+
switch (method) {
|
|
545
546
|
case MethodTypes.query:
|
|
546
547
|
results = entity._queryParams(state, options);
|
|
547
548
|
break;
|
|
@@ -556,7 +557,7 @@ let clauses = {
|
|
|
556
557
|
break;
|
|
557
558
|
}
|
|
558
559
|
|
|
559
|
-
if (
|
|
560
|
+
if (method === MethodTypes.update && results.ExpressionAttributeValues && Object.keys(results.ExpressionAttributeValues).length === 0) {
|
|
560
561
|
// An update that only does a `remove` operation would result in an empty object
|
|
561
562
|
// todo: change the getValues() method to return undefined in this case (would potentially require a more generous refactor)
|
|
562
563
|
delete results.ExpressionAttributeValues;
|
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
|
@@ -5,12 +5,19 @@ const { FilterFactory } = require("./filters");
|
|
|
5
5
|
const { FilterOperations } = require("./operations");
|
|
6
6
|
const { WhereFactory } = require("./where");
|
|
7
7
|
const { clauses, ChainState } = require("./clauses");
|
|
8
|
+
const {EventManager} = require('./events');
|
|
8
9
|
const validations = require("./validations");
|
|
9
|
-
const
|
|
10
|
+
const c = require('./client');
|
|
11
|
+
const u = require("./util");
|
|
10
12
|
const e = require("./errors");
|
|
11
13
|
|
|
12
14
|
class Entity {
|
|
13
15
|
constructor(model, config = {}) {
|
|
16
|
+
config = c.normalizeConfig(config);
|
|
17
|
+
this.eventManager = new EventManager({
|
|
18
|
+
listeners: config.listeners
|
|
19
|
+
});
|
|
20
|
+
this.eventManager.add(config.logger);
|
|
14
21
|
this._validateModel(model);
|
|
15
22
|
this.version = EntityVersions.v1;
|
|
16
23
|
this.config = config;
|
|
@@ -43,7 +50,7 @@ class Entity {
|
|
|
43
50
|
|
|
44
51
|
setIdentifier(type = "", identifier = "") {
|
|
45
52
|
if (!this.identifiers[type]) {
|
|
46
|
-
throw new e.ElectroError(e.ErrorCodes.InvalidIdentifier, `Invalid identifier type: "${type}". Valid identifiers include: ${
|
|
53
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidIdentifier, `Invalid identifier type: "${type}". Valid identifiers include: ${u.commaSeparatedString(Object.keys(this.identifiers))}`);
|
|
47
54
|
} else {
|
|
48
55
|
this.identifiers[type] = identifier;
|
|
49
56
|
}
|
|
@@ -252,9 +259,32 @@ class Entity {
|
|
|
252
259
|
}
|
|
253
260
|
}
|
|
254
261
|
|
|
255
|
-
async _exec(method,
|
|
256
|
-
|
|
262
|
+
async _exec(method, params, config = {}) {
|
|
263
|
+
const notifyQuery = () => {
|
|
264
|
+
this.eventManager.trigger({
|
|
265
|
+
type: "query",
|
|
266
|
+
method,
|
|
267
|
+
params,
|
|
268
|
+
config,
|
|
269
|
+
}, config.listeners);
|
|
270
|
+
};
|
|
271
|
+
const notifyResults = (results, success) => {
|
|
272
|
+
this.eventManager.trigger({
|
|
273
|
+
type: "results",
|
|
274
|
+
method,
|
|
275
|
+
config,
|
|
276
|
+
success,
|
|
277
|
+
results,
|
|
278
|
+
}, config.listeners);
|
|
279
|
+
}
|
|
280
|
+
return this.client[method](params).promise()
|
|
281
|
+
.then((results) => {
|
|
282
|
+
notifyQuery();
|
|
283
|
+
notifyResults(results, true);
|
|
284
|
+
return results;
|
|
285
|
+
})
|
|
257
286
|
.catch(err => {
|
|
287
|
+
notifyResults(err, false);
|
|
258
288
|
err.__isAWSError = true;
|
|
259
289
|
throw err;
|
|
260
290
|
});
|
|
@@ -266,10 +296,10 @@ class Entity {
|
|
|
266
296
|
}
|
|
267
297
|
let results = [];
|
|
268
298
|
let concurrent = this._normalizeConcurrencyValue(config.concurrent)
|
|
269
|
-
let concurrentOperations =
|
|
299
|
+
let concurrentOperations = u.batchItems(parameters, concurrent);
|
|
270
300
|
for (let operation of concurrentOperations) {
|
|
271
301
|
await Promise.all(operation.map(async params => {
|
|
272
|
-
let response = await this._exec(MethodTypes.batchWrite, params);
|
|
302
|
+
let response = await this._exec(MethodTypes.batchWrite, params, config);
|
|
273
303
|
if (validations.isFunction(config.parse)) {
|
|
274
304
|
let parsed = await config.parse(config, response);
|
|
275
305
|
if (parsed) {
|
|
@@ -292,13 +322,13 @@ class Entity {
|
|
|
292
322
|
parameters = [parameters];
|
|
293
323
|
}
|
|
294
324
|
let concurrent = this._normalizeConcurrencyValue(config.concurrent)
|
|
295
|
-
let concurrentOperations =
|
|
325
|
+
let concurrentOperations = u.batchItems(parameters, concurrent);
|
|
296
326
|
|
|
297
327
|
let resultsAll = [];
|
|
298
328
|
let unprocessedAll = [];
|
|
299
329
|
for (let operation of concurrentOperations) {
|
|
300
330
|
await Promise.all(operation.map(async params => {
|
|
301
|
-
let response = await this._exec(MethodTypes.batchGet, params);
|
|
331
|
+
let response = await this._exec(MethodTypes.batchGet, params, config);
|
|
302
332
|
if (validations.isFunction(config.parse)) {
|
|
303
333
|
resultsAll.push(await config.parse(config, response));
|
|
304
334
|
return;
|
|
@@ -328,7 +358,7 @@ class Entity {
|
|
|
328
358
|
let limit = max === undefined
|
|
329
359
|
? parameters.Limit
|
|
330
360
|
: max - count;
|
|
331
|
-
let response = await this._exec("query", {ExclusiveStartKey, ...parameters, Limit: limit});
|
|
361
|
+
let response = await this._exec("query", {ExclusiveStartKey, ...parameters, Limit: limit}, config);
|
|
332
362
|
|
|
333
363
|
ExclusiveStartKey = response.LastEvaluatedKey;
|
|
334
364
|
|
|
@@ -363,7 +393,7 @@ class Entity {
|
|
|
363
393
|
}
|
|
364
394
|
|
|
365
395
|
async executeOperation(method, parameters, config) {
|
|
366
|
-
let response = await this._exec(method, parameters);
|
|
396
|
+
let response = await this._exec(method, parameters, config);
|
|
367
397
|
if (validations.isFunction(config.parse)) {
|
|
368
398
|
return config.parse(config, response);
|
|
369
399
|
}
|
|
@@ -560,7 +590,7 @@ class Entity {
|
|
|
560
590
|
|
|
561
591
|
_setClient(client) {
|
|
562
592
|
if (client) {
|
|
563
|
-
this.client = client;
|
|
593
|
+
this.client = c.normalizeClient(client);
|
|
564
594
|
}
|
|
565
595
|
}
|
|
566
596
|
|
|
@@ -751,13 +781,14 @@ class Entity {
|
|
|
751
781
|
_isPagination: false,
|
|
752
782
|
_isCollectionQuery: false,
|
|
753
783
|
pages: undefined,
|
|
784
|
+
listeners: [],
|
|
754
785
|
};
|
|
755
786
|
|
|
756
787
|
config = options.reduce((config, option) => {
|
|
757
788
|
if (typeof option.response === 'string' && option.response.length) {
|
|
758
789
|
const format = ReturnValues[option.response];
|
|
759
790
|
if (format === undefined) {
|
|
760
|
-
throw new e.ElectroError(e.ErrorCodes.InvalidOptions, `Invalid value for query option "format" provided: "${option.format}". Allowed values include ${
|
|
791
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidOptions, `Invalid value for query option "format" provided: "${option.format}". Allowed values include ${u.commaSeparatedString(Object.keys(ReturnValues))}.`);
|
|
761
792
|
}
|
|
762
793
|
config.response = format;
|
|
763
794
|
config.params.ReturnValues = FormatToReturnValues[format];
|
|
@@ -814,7 +845,7 @@ class Entity {
|
|
|
814
845
|
if (typeof Pager[option.pager] === "string") {
|
|
815
846
|
config.pager = option.pager;
|
|
816
847
|
} else {
|
|
817
|
-
throw new e.ElectroError(e.ErrorCodes.InvalidOptions, `Invalid value for option "pager" provided: "${option.pager}". Allowed values include ${
|
|
848
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidOptions, `Invalid value for option "pager" provided: "${option.pager}". Allowed values include ${u.commaSeparatedString(Object.keys(Pager))}.`);
|
|
818
849
|
}
|
|
819
850
|
}
|
|
820
851
|
|
|
@@ -822,7 +853,7 @@ class Entity {
|
|
|
822
853
|
if (typeof UnprocessedTypes[option.unprocessed] === "string") {
|
|
823
854
|
config.unproessed = UnprocessedTypes[option.unprocessed];
|
|
824
855
|
} else {
|
|
825
|
-
throw new e.ElectroError(e.ErrorCodes.InvalidOptions, `Invalid value for option "unprocessed" provided: "${option.unprocessed}". Allowed values include ${
|
|
856
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidOptions, `Invalid value for option "unprocessed" provided: "${option.unprocessed}". Allowed values include ${u.commaSeparatedString(Object.keys(UnprocessedTypes))}.`);
|
|
826
857
|
}
|
|
827
858
|
}
|
|
828
859
|
|
|
@@ -830,6 +861,20 @@ class Entity {
|
|
|
830
861
|
config.ignoreOwnership = option.ignoreOwnership;
|
|
831
862
|
}
|
|
832
863
|
|
|
864
|
+
if (option.listeners) {
|
|
865
|
+
if (Array.isArray(option.listeners)) {
|
|
866
|
+
config.listeners = config.listeners.concat(option.listeners);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (option.logger) {
|
|
871
|
+
if (validations.isFunction(option.logger)) {
|
|
872
|
+
config.listeners.push(option.logger);
|
|
873
|
+
} else {
|
|
874
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidLoggerProvided, `Loggers must be of type function`);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
833
878
|
config.page = Object.assign({}, config.page, option.page);
|
|
834
879
|
config.params = Object.assign({}, config.params, option.params);
|
|
835
880
|
return config;
|
|
@@ -933,7 +978,7 @@ class Entity {
|
|
|
933
978
|
records.push(Key);
|
|
934
979
|
}
|
|
935
980
|
}
|
|
936
|
-
let batches =
|
|
981
|
+
let batches = u.batchItems(records, MaxBatchItems.batchGet);
|
|
937
982
|
return batches.map(batch => {
|
|
938
983
|
return {
|
|
939
984
|
RequestItems: {
|
|
@@ -966,7 +1011,7 @@ class Entity {
|
|
|
966
1011
|
throw new Error("Invalid method type");
|
|
967
1012
|
}
|
|
968
1013
|
}
|
|
969
|
-
let batches =
|
|
1014
|
+
let batches = u.batchItems(records, MaxBatchItems.batchWrite);
|
|
970
1015
|
return batches.map(batch => {
|
|
971
1016
|
return {
|
|
972
1017
|
RequestItems: {
|
|
@@ -1220,7 +1265,7 @@ class Entity {
|
|
|
1220
1265
|
let props = Object.keys(item);
|
|
1221
1266
|
let missing = require.filter((prop) => !props.includes(prop));
|
|
1222
1267
|
if (!missing) {
|
|
1223
|
-
throw new e.ElectroError(e.ErrorCodes.MissingAttribute, `Item is missing attributes: ${
|
|
1268
|
+
throw new e.ElectroError(e.ErrorCodes.MissingAttribute, `Item is missing attributes: ${u.commaSeparatedString(missing)}`);
|
|
1224
1269
|
}
|
|
1225
1270
|
}
|
|
1226
1271
|
|
|
@@ -1229,7 +1274,7 @@ class Entity {
|
|
|
1229
1274
|
throw new Error(`Invalid attribute ${prop}`);
|
|
1230
1275
|
}
|
|
1231
1276
|
if (restrict.length && !restrict.includes(prop)) {
|
|
1232
|
-
throw new Error(`${prop} is not a valid attribute: ${
|
|
1277
|
+
throw new Error(`${prop} is not a valid attribute: ${u.commaSeparatedString(restrict)}`);
|
|
1233
1278
|
}
|
|
1234
1279
|
if (prop === undefined || skip.includes(prop)) {
|
|
1235
1280
|
continue;
|
|
@@ -1392,7 +1437,7 @@ class Entity {
|
|
|
1392
1437
|
_makeComparisonQueryParams(index = TableIndex, comparison = "", filter = {}, pk = {}, sk = {}) {
|
|
1393
1438
|
let operator = Comparisons[comparison];
|
|
1394
1439
|
if (!operator) {
|
|
1395
|
-
throw new Error(`Unexpected comparison operator "${comparison}", expected ${
|
|
1440
|
+
throw new Error(`Unexpected comparison operator "${comparison}", expected ${u.commaSeparatedString(Object.values(Comparisons))}`);
|
|
1396
1441
|
}
|
|
1397
1442
|
let keyExpressions = this._queryKeyExpressionAttributeBuilder(
|
|
1398
1443
|
index,
|
|
@@ -1429,7 +1474,7 @@ class Entity {
|
|
|
1429
1474
|
let incompleteAccessPatterns = incomplete.map(({index}) => this.model.translations.indexes.fromIndexToAccessPattern[index]);
|
|
1430
1475
|
let missingFacets = incomplete.reduce((result, { missing }) => [...result, ...missing], []);
|
|
1431
1476
|
throw new e.ElectroError(e.ErrorCodes.IncompleteCompositeAttributes,
|
|
1432
|
-
`Incomplete composite attributes: Without the composite attributes ${
|
|
1477
|
+
`Incomplete composite attributes: Without the composite attributes ${u.commaSeparatedString(missingFacets)} the following access patterns cannot be updated: ${u.commaSeparatedString(incompleteAccessPatterns.filter((val) => val !== undefined))} `,
|
|
1433
1478
|
);
|
|
1434
1479
|
}
|
|
1435
1480
|
return complete;
|
|
@@ -1648,7 +1693,7 @@ class Entity {
|
|
|
1648
1693
|
_expectFacets(obj = {}, properties = [], type = "key composite attributes") {
|
|
1649
1694
|
let [incompletePk, missing, matching] = this._expectProperties(obj, properties);
|
|
1650
1695
|
if (incompletePk) {
|
|
1651
|
-
throw new e.ElectroError(e.ErrorCodes.IncompleteCompositeAttributes, `Incomplete or invalid ${type} supplied. Missing properties: ${
|
|
1696
|
+
throw new e.ElectroError(e.ErrorCodes.IncompleteCompositeAttributes, `Incomplete or invalid ${type} supplied. Missing properties: ${u.commaSeparatedString(missing)}`);
|
|
1652
1697
|
} else {
|
|
1653
1698
|
return matching;
|
|
1654
1699
|
}
|
|
@@ -1721,10 +1766,10 @@ class Entity {
|
|
|
1721
1766
|
|
|
1722
1767
|
// If keys arent custom, set the prefixes
|
|
1723
1768
|
if (!keys.pk.isCustom) {
|
|
1724
|
-
keys.pk.prefix =
|
|
1769
|
+
keys.pk.prefix = u.formatKeyCasing(pk, tableIndex.pk.casing);
|
|
1725
1770
|
}
|
|
1726
1771
|
if (!keys.sk.isCustom) {
|
|
1727
|
-
keys.sk.prefix =
|
|
1772
|
+
keys.sk.prefix = u.formatKeyCasing(sk, tableIndex.sk.casing);
|
|
1728
1773
|
}
|
|
1729
1774
|
|
|
1730
1775
|
return keys;
|
|
@@ -1735,7 +1780,7 @@ class Entity {
|
|
|
1735
1780
|
? this.model.indexes[accessPattern].sk.casing
|
|
1736
1781
|
: undefined;
|
|
1737
1782
|
|
|
1738
|
-
return
|
|
1783
|
+
return u.formatKeyCasing(key, casing);
|
|
1739
1784
|
}
|
|
1740
1785
|
|
|
1741
1786
|
_validateIndex(index) {
|
|
@@ -1859,7 +1904,7 @@ class Entity {
|
|
|
1859
1904
|
key = `${key}${supplied[name]}`;
|
|
1860
1905
|
}
|
|
1861
1906
|
|
|
1862
|
-
return
|
|
1907
|
+
return u.formatKeyCasing(key, casing);
|
|
1863
1908
|
}
|
|
1864
1909
|
|
|
1865
1910
|
_findBestIndexKeyMatch(attributes = {}) {
|
|
@@ -2139,7 +2184,7 @@ class Entity {
|
|
|
2139
2184
|
let indexName = index.index || TableIndex;
|
|
2140
2185
|
if (seenIndexes[indexName] !== undefined) {
|
|
2141
2186
|
if (indexName === TableIndex) {
|
|
2142
|
-
throw new e.ElectroError(e.ErrorCodes.DuplicateIndexes, `Duplicate index defined in model found in Access Pattern '${accessPattern}': '${
|
|
2187
|
+
throw new e.ElectroError(e.ErrorCodes.DuplicateIndexes, `Duplicate index defined in model found in Access Pattern '${accessPattern}': '${u.formatIndexNameForDisplay(indexName)}'. This could be because you forgot to specify the index name of a secondary index defined in your model.`);
|
|
2143
2188
|
} else {
|
|
2144
2189
|
throw new e.ElectroError(e.ErrorCodes.DuplicateIndexes, `Duplicate index defined in model found in Access Pattern '${accessPattern}': '${indexName}'`);
|
|
2145
2190
|
}
|
|
@@ -2148,7 +2193,7 @@ class Entity {
|
|
|
2148
2193
|
let hasSk = !!index.sk;
|
|
2149
2194
|
let inCollection = !!index.collection;
|
|
2150
2195
|
if (!hasSk && inCollection) {
|
|
2151
|
-
throw new e.ElectroError(e.ErrorCodes.CollectionNoSK, `Invalid Access pattern definition for '${accessPattern}': '${
|
|
2196
|
+
throw new e.ElectroError(e.ErrorCodes.CollectionNoSK, `Invalid Access pattern definition for '${accessPattern}': '${u.formatIndexNameForDisplay(indexName)}', contains a collection definition without a defined SK. Collections can only be defined on indexes with a defined SK.`);
|
|
2152
2197
|
}
|
|
2153
2198
|
let collection = index.collection || "";
|
|
2154
2199
|
let customFacets = {
|
|
@@ -2219,7 +2264,7 @@ class Entity {
|
|
|
2219
2264
|
if (Array.isArray(sk.facets)) {
|
|
2220
2265
|
let duplicates = pk.facets.filter(facet => sk.facets.includes(facet));
|
|
2221
2266
|
if (duplicates.length !== 0) {
|
|
2222
|
-
throw new e.ElectroError(e.ErrorCodes.DuplicateIndexCompositeAttributes, `The Access Pattern '${accessPattern}' contains duplicate references the composite attribute(s): ${
|
|
2267
|
+
throw new e.ElectroError(e.ErrorCodes.DuplicateIndexCompositeAttributes, `The Access Pattern '${accessPattern}' contains duplicate references the composite attribute(s): ${u.commaSeparatedString(duplicates)}. Composite attributes may only be used once within an index. If this leaves the Sort Key (sk) without any composite attributes simply set this to be an empty array.`);
|
|
2223
2268
|
}
|
|
2224
2269
|
}
|
|
2225
2270
|
|
|
@@ -2322,13 +2367,13 @@ class Entity {
|
|
|
2322
2367
|
|
|
2323
2368
|
let pkTemplateIsCompatible = this._compositeTemplateAreCompatible(parsedPKAttributes, index.pk.composite);
|
|
2324
2369
|
if (!pkTemplateIsCompatible) {
|
|
2325
|
-
throw new e.ElectroError(e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate, `Incompatible PK 'template' and 'composite' properties for defined on index "${
|
|
2370
|
+
throw new e.ElectroError(e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate, `Incompatible PK 'template' and 'composite' properties for defined on index "${u.formatIndexNameForDisplay(indexName)}". PK "template" string is defined as having composite attributes ${u.commaSeparatedString(parsedPKAttributes.attributes)} while PK "composite" array is defined with composite attributes ${u.commaSeparatedString(index.pk.composite)}`);
|
|
2326
2371
|
}
|
|
2327
2372
|
|
|
2328
2373
|
if (index.sk !== undefined && Array.isArray(index.sk.composite) && typeof index.sk.template === "string") {
|
|
2329
2374
|
let skTemplateIsCompatible = this._compositeTemplateAreCompatible(parsedSKAttributes, index.sk.composite);
|
|
2330
2375
|
if (!skTemplateIsCompatible) {
|
|
2331
|
-
throw new e.ElectroError(e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate, `Incompatible SK 'template' and 'composite' properties for defined on index "${
|
|
2376
|
+
throw new e.ElectroError(e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate, `Incompatible SK 'template' and 'composite' properties for defined on index "${u.formatIndexNameForDisplay(indexName)}". SK "template" string is defined as having composite attributes ${u.commaSeparatedString(parsedSKAttributes.attributes)} while SK "composite" array is defined with composite attributes ${u.commaSeparatedString(index.sk.composite)}`);
|
|
2332
2377
|
}
|
|
2333
2378
|
}
|
|
2334
2379
|
}
|
|
@@ -2356,7 +2401,7 @@ class Entity {
|
|
|
2356
2401
|
|
|
2357
2402
|
for (let [name, fn] of Object.entries(filters)) {
|
|
2358
2403
|
if (invalidFilterNames.includes(name)) {
|
|
2359
|
-
throw new e.ElectroError(e.ErrorCodes.InvalidFilter, `Invalid filter name: ${name}. Filter cannot be named ${
|
|
2404
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidFilter, `Invalid filter name: ${name}. Filter cannot be named ${u.commaSeparatedString(invalidFilterNames)}`);
|
|
2360
2405
|
} else {
|
|
2361
2406
|
normalized[name] = fn;
|
|
2362
2407
|
}
|
|
@@ -2475,7 +2520,7 @@ class Entity {
|
|
|
2475
2520
|
_parseModel(model, config = {}) {
|
|
2476
2521
|
/** start beta/v1 condition **/
|
|
2477
2522
|
const {client} = config;
|
|
2478
|
-
let modelVersion =
|
|
2523
|
+
let modelVersion = u.getModelVersion(model);
|
|
2479
2524
|
let service, entity, version, table, name;
|
|
2480
2525
|
switch(modelVersion) {
|
|
2481
2526
|
case ModelVersions.beta:
|
package/src/errors.js
CHANGED
|
@@ -127,6 +127,18 @@ const ErrorCodes = {
|
|
|
127
127
|
name: "InvalidIndexCompositeWithAttributeName",
|
|
128
128
|
sym: ErrorCode,
|
|
129
129
|
},
|
|
130
|
+
InvalidListenerProvided: {
|
|
131
|
+
code: 1020,
|
|
132
|
+
section: "invalid-listener-provided",
|
|
133
|
+
name: "InvalidListenerProvided",
|
|
134
|
+
sym: ErrorCode,
|
|
135
|
+
},
|
|
136
|
+
InvalidClientProvided: {
|
|
137
|
+
code: 1021,
|
|
138
|
+
section: "invalid-client-provided",
|
|
139
|
+
name: "InvalidClientProvided",
|
|
140
|
+
sym: ErrorCode,
|
|
141
|
+
},
|
|
130
142
|
MissingAttribute: {
|
|
131
143
|
code: 2001,
|
|
132
144
|
section: "missing-attribute",
|
|
@@ -204,7 +216,7 @@ const ErrorCodes = {
|
|
|
204
216
|
section: "pager-not-unique",
|
|
205
217
|
name: "NoOwnerForPager",
|
|
206
218
|
sym: ErrorCode,
|
|
207
|
-
}
|
|
219
|
+
}
|
|
208
220
|
};
|
|
209
221
|
|
|
210
222
|
function makeMessage(message, section) {
|
package/src/events.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const e = require("./errors");
|
|
2
|
+
const v = require('./validations');
|
|
3
|
+
|
|
4
|
+
class EventManager {
|
|
5
|
+
static createSafeListener(listener) {
|
|
6
|
+
if (listener === undefined) {
|
|
7
|
+
return undefined;
|
|
8
|
+
} if (!v.isFunction(listener)) {
|
|
9
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidListenerProvided, `Provided listener is not of type 'function'`);
|
|
10
|
+
} else {
|
|
11
|
+
return (...params) => {
|
|
12
|
+
try {
|
|
13
|
+
listener(...params);
|
|
14
|
+
} catch(err) {
|
|
15
|
+
console.error(`Error invoking user supplied listener`, err);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static normalizeListeners(listeners = []) {
|
|
22
|
+
if (!Array.isArray(listeners)) {
|
|
23
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidListenerProvided, `Listeners must be provided as an array of functions`);
|
|
24
|
+
}
|
|
25
|
+
return listeners
|
|
26
|
+
.map(listener => EventManager.createSafeListener(listener))
|
|
27
|
+
.filter(listener => {
|
|
28
|
+
switch (typeof listener) {
|
|
29
|
+
case 'function':
|
|
30
|
+
return true;
|
|
31
|
+
case 'undefined':
|
|
32
|
+
return false;
|
|
33
|
+
default:
|
|
34
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidListenerProvided, `Provided listener is not of type 'function`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
constructor({listeners = []} = {}) {
|
|
40
|
+
this.listeners = EventManager.normalizeListeners(listeners);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
add(listeners = []) {
|
|
44
|
+
if (!Array.isArray(listeners)) {
|
|
45
|
+
listeners = [listeners];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.listeners = this.listeners.concat(
|
|
49
|
+
EventManager.normalizeListeners(listeners)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
trigger(event, adHocListeners = []) {
|
|
54
|
+
const allListeners = [
|
|
55
|
+
...this.listeners,
|
|
56
|
+
...EventManager.normalizeListeners(adHocListeners)
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
for (const listener of allListeners) {
|
|
60
|
+
listener(event);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
EventManager
|
|
67
|
+
};
|
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
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
-
|
|
783
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
|
@@ -58,6 +59,9 @@ class Service {
|
|
|
58
59
|
|
|
59
60
|
this.config = config;
|
|
60
61
|
this.client = config.client;
|
|
62
|
+
if (v.isFunction(config.logger)) {
|
|
63
|
+
this.logger = config.logger;
|
|
64
|
+
}
|
|
61
65
|
this.entities = {};
|
|
62
66
|
this.find = {};
|
|
63
67
|
this.collectionSchema = {};
|
|
@@ -79,6 +83,9 @@ class Service {
|
|
|
79
83
|
|
|
80
84
|
this.config = config;
|
|
81
85
|
this.client = config.client;
|
|
86
|
+
if (v.isFunction(config.logger)) {
|
|
87
|
+
this.logger = config.logger;
|
|
88
|
+
}
|
|
82
89
|
this.entities = {};
|
|
83
90
|
this.find = {};
|
|
84
91
|
this.collectionSchema = {};
|
|
@@ -98,6 +105,7 @@ class Service {
|
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
constructor(service = "", config = {}) {
|
|
108
|
+
config = c.normalizeConfig(config);
|
|
101
109
|
this.version = ServiceVersions.v1;
|
|
102
110
|
let type = inferConstructorType(service);
|
|
103
111
|
switch(type) {
|
package/src/types.js
CHANGED
|
@@ -179,7 +179,12 @@ const KeyCasing = {
|
|
|
179
179
|
upper: "upper",
|
|
180
180
|
lower: "lower",
|
|
181
181
|
default: "default",
|
|
182
|
-
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const EventSubscriptionTypes = [
|
|
185
|
+
"query",
|
|
186
|
+
"results"
|
|
187
|
+
];
|
|
183
188
|
|
|
184
189
|
module.exports = {
|
|
185
190
|
Pager,
|
|
@@ -208,5 +213,6 @@ module.exports = {
|
|
|
208
213
|
FormatToReturnValues,
|
|
209
214
|
AttributeProxySymbol,
|
|
210
215
|
ElectroInstanceTypes,
|
|
216
|
+
EventSubscriptionTypes,
|
|
211
217
|
AttributeMutationMethods
|
|
212
218
|
};
|