electrodb 1.6.2 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -2
- package/README.md +348 -10
- package/index.d.ts +64 -7
- 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/operations.js +10 -3
- 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
|
@@ -140,6 +140,18 @@ All notable changes to this project will be documented in this file. Breaking ch
|
|
|
140
140
|
### Fixed
|
|
141
141
|
- In some cases the `find()` and `match()` methods would incorrectly select an index without a complete partition key. This would result in validation exceptions preventing the user from querying if an index definition and provided attribute object aligned improperly. This was fixed and a slightly more robust mechanism for ranking indexes was made.
|
|
142
142
|
|
|
143
|
-
## [1.6.2]
|
|
143
|
+
## [1.6.2] - 2022-01-27
|
|
144
144
|
### Changed
|
|
145
|
-
- The methods `create`, `patch`, and `remove` will now refer to primary table keys through parameters via ExpressionAttributeNames when using `attribute_exists()`/`attribute_not_exists()` DynamoDB conditions. Prior to this they were referenced directly which would fail in cases where key names include illegal characters. Parameter implementation change only, non-breaking.
|
|
145
|
+
- The methods `create`, `patch`, and `remove` will now refer to primary table keys through parameters via ExpressionAttributeNames when using `attribute_exists()`/`attribute_not_exists()` DynamoDB conditions. Prior to this they were referenced directly which would fail in cases where key names include illegal characters. Parameter implementation change only, non-breaking.
|
|
146
|
+
|
|
147
|
+
## [1.6.3] - 2022-02-22
|
|
148
|
+
### Added
|
|
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)]
|
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.
|
|
@@ -3462,7 +3470,8 @@ operation | example | result
|
|
|
3462
3470
|
`delete` | `delete(tenant, name)` | `#tenant :tenant1` | Remove item from existing `set` attribute
|
|
3463
3471
|
`del` | `del(tenant, name)` | `#tenant :tenant1` | Alias for `delete` operation
|
|
3464
3472
|
`name` | `name(rent)` | `#rent` | Reference another attribute's name, can be passed to other operation that allows leveraging existing attribute values in calculating new values
|
|
3465
|
-
`value` | `value(rent,
|
|
3473
|
+
`value` | `value(rent, amount)` | `:rent1` | Create a reference to a particular value, can be passed to other operation that allows leveraging existing attribute values in calculating new values
|
|
3474
|
+
`ifNotExists` | `ifNotExists(rent, amount)` | `#rent = if_not_exists(#rent, :rent0)` | Update a property's value only if that property doesn't yet exist on the record
|
|
3466
3475
|
|
|
3467
3476
|
```javascript
|
|
3468
3477
|
await StoreLocations
|
|
@@ -3999,7 +4008,7 @@ TaskApp.collections
|
|
|
3999
4008
|
```
|
|
4000
4009
|
|
|
4001
4010
|
## Execute Queries
|
|
4002
|
-
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()`).
|
|
4003
4012
|
|
|
4004
4013
|
Both `.params()` and `.go()` take a query configuration object which is detailed more in the section [Query Options](#query-options).
|
|
4005
4014
|
|
|
@@ -4286,6 +4295,8 @@ By default, **ElectroDB** enables you to work with records as the names and prop
|
|
|
4286
4295
|
ignoreOwnership?: boolean;
|
|
4287
4296
|
limit?: number;
|
|
4288
4297
|
pages?: number;
|
|
4298
|
+
logger?: (event) => void;
|
|
4299
|
+
listeners Array<(event) => void>;
|
|
4289
4300
|
};
|
|
4290
4301
|
```
|
|
4291
4302
|
|
|
@@ -4303,6 +4314,333 @@ response | `"default"` | Used as a convenience for applying the
|
|
|
4303
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`.
|
|
4304
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.
|
|
4305
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
|
+
```
|
|
4306
4644
|
|
|
4307
4645
|
# Errors:
|
|
4308
4646
|
|
|
@@ -4310,7 +4648,7 @@ Error Code | Description
|
|
|
4310
4648
|
:--------: | --------------------
|
|
4311
4649
|
1000s | Configuration Errors
|
|
4312
4650
|
2000s | Invalid Queries
|
|
4313
|
-
3000s | User Defined Errors
|
|
4651
|
+
3000s | User Defined Errors
|
|
4314
4652
|
4000s | DynamoDB Errors
|
|
4315
4653
|
5000s | Unexpected Errors
|
|
4316
4654
|
|
package/index.d.ts
CHANGED
|
@@ -954,6 +954,7 @@ type DataUpdateOperations<A extends string, F extends A, C extends string, S ext
|
|
|
954
954
|
del: <T, A extends DataUpdateAttributeSymbol<T>>(attr: A, value: A extends DataUpdateAttributeSymbol<infer V> ? V extends Array<any> ? V : never : never ) => any;
|
|
955
955
|
value: <T, A extends DataUpdateAttributeSymbol<T>>(attr: A, value: DataUpdateAttributeValues<A>) => Required<DataUpdateAttributeValues<A>>;
|
|
956
956
|
name: <T, A extends DataUpdateAttributeSymbol<T>>(attr: A) => any;
|
|
957
|
+
ifNotExists: <T, A extends DataUpdateAttributeSymbol<T>>(attr: A, value: DataUpdateAttributeValues<A>) => any;
|
|
957
958
|
};
|
|
958
959
|
|
|
959
960
|
type WhereCallback<A extends string, F extends A, C extends string, S extends Schema<A,F,C>, I extends Item<A,F,C,S,S["attributes"]>> =
|
|
@@ -973,6 +974,8 @@ interface QueryOptions {
|
|
|
973
974
|
originalErr?: boolean;
|
|
974
975
|
ignoreOwnership?: boolean;
|
|
975
976
|
pages?: number;
|
|
977
|
+
listeners?: Array<EventListener>;
|
|
978
|
+
logger?: EventListener;
|
|
976
979
|
}
|
|
977
980
|
|
|
978
981
|
// subset of QueryOptions
|
|
@@ -1117,23 +1120,77 @@ type DocumentClient = {
|
|
|
1117
1120
|
batchWrite: DocumentClientMethod;
|
|
1118
1121
|
batchGet: DocumentClientMethod;
|
|
1119
1122
|
scan: DocumentClientMethod;
|
|
1120
|
-
}
|
|
1123
|
+
} | {
|
|
1124
|
+
send: (command: any) => Promise<any>;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
type ElectroDBMethodTypes = "put" | "get" | "query" | "scan" | "update" | "delete" | "remove" | "patch" | "create" | "batchGet" | "batchWrite";
|
|
1128
|
+
|
|
1129
|
+
interface ElectroQueryEvent<P extends any = any> {
|
|
1130
|
+
type: 'query';
|
|
1131
|
+
method: ElectroDBMethodTypes;
|
|
1132
|
+
config: any;
|
|
1133
|
+
params: P;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
interface ElectroResultsEvent<R extends any = any> {
|
|
1137
|
+
type: 'results';
|
|
1138
|
+
method: ElectroDBMethodTypes;
|
|
1139
|
+
config: any;
|
|
1140
|
+
results: R;
|
|
1141
|
+
success: boolean;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
type ElectroEvent =
|
|
1145
|
+
ElectroQueryEvent
|
|
1146
|
+
| ElectroResultsEvent;
|
|
1147
|
+
|
|
1148
|
+
type ElectroEventType = Pick<ElectroEvent, 'type'>;
|
|
1149
|
+
|
|
1150
|
+
type EventListener = (event: ElectroEvent) => void;
|
|
1151
|
+
|
|
1152
|
+
// todo: coming soon, more events!
|
|
1153
|
+
// | {
|
|
1154
|
+
// name: "error";
|
|
1155
|
+
// type: "configuration_error" | "invalid_query" | "dynamodb_client";
|
|
1156
|
+
// message: string;
|
|
1157
|
+
// details: ElectroError;
|
|
1158
|
+
// } | {
|
|
1159
|
+
// name: "error";
|
|
1160
|
+
// type: "user_defined";
|
|
1161
|
+
// message: string;
|
|
1162
|
+
// details: ElectroValidationError;
|
|
1163
|
+
// } | {
|
|
1164
|
+
// name: "warn";
|
|
1165
|
+
// type: "deprecation_warning" | "optimization_suggestion";
|
|
1166
|
+
// message: string;
|
|
1167
|
+
// details: any;
|
|
1168
|
+
// } | {
|
|
1169
|
+
// name: "info";
|
|
1170
|
+
// type: "client_updated" | "table_overwritten";
|
|
1171
|
+
// message: string;
|
|
1172
|
+
// details: any;
|
|
1173
|
+
// };
|
|
1121
1174
|
|
|
1122
1175
|
type EntityConfiguration = {
|
|
1123
1176
|
table?: string;
|
|
1124
|
-
client?: DocumentClient
|
|
1177
|
+
client?: DocumentClient;
|
|
1178
|
+
listeners?: Array<EventListener>;
|
|
1179
|
+
logger?: EventListener;
|
|
1125
1180
|
};
|
|
1126
1181
|
|
|
1127
1182
|
type ServiceConfiguration = {
|
|
1128
1183
|
table?: string;
|
|
1129
|
-
client?: DocumentClient
|
|
1184
|
+
client?: DocumentClient;
|
|
1185
|
+
listeners?: Array<EventListener>;
|
|
1186
|
+
logger?: EventListener;
|
|
1130
1187
|
};
|
|
1131
1188
|
|
|
1132
1189
|
type ParseSingleInput = {
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1190
|
+
Item?: {[key: string]: any}
|
|
1191
|
+
} | {
|
|
1192
|
+
Attributes?: {[key: string]: any}
|
|
1193
|
+
} | null;
|
|
1137
1194
|
|
|
1138
1195
|
type ParseMultiInput = {
|
|
1139
1196
|
Items?: {[key: string]: any}[]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "electrodb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/tywalch/electrodb#readme",
|
|
27
27
|
"devDependencies": {
|
|
28
|
+
"@aws-sdk/client-dynamodb": "^3.54.1",
|
|
29
|
+
"@aws-sdk/lib-dynamodb": "^3.54.1",
|
|
28
30
|
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
|
29
31
|
"@types/chai": "^4.2.12",
|
|
30
32
|
"@types/mocha": "^8.0.3",
|
|
@@ -59,6 +61,7 @@
|
|
|
59
61
|
"directory": "test"
|
|
60
62
|
},
|
|
61
63
|
"dependencies": {
|
|
62
|
-
"jsonschema": "1.2.7"
|
|
64
|
+
"jsonschema": "1.2.7",
|
|
65
|
+
"@aws-sdk/lib-dynamodb": "^3.54.1"
|
|
63
66
|
}
|
|
64
67
|
}
|
package/src/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/operations.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const {AttributeTypes, ItemOperations, AttributeProxySymbol, BuilderTypes} = require("./types");
|
|
2
2
|
const e = require("./errors");
|
|
3
|
-
const
|
|
3
|
+
const u = require("./util");
|
|
4
4
|
|
|
5
5
|
const deleteOperations = {
|
|
6
6
|
canNest: false,
|
|
@@ -21,6 +21,13 @@ const deleteOperations = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
const UpdateOperations = {
|
|
24
|
+
ifNotExists: {
|
|
25
|
+
template: function if_not_exists(options, attr, path, value) {
|
|
26
|
+
const operation = ItemOperations.set;
|
|
27
|
+
const expression = `${path} = if_not_exists(${path}, ${value})`;
|
|
28
|
+
return {operation, expression};
|
|
29
|
+
}
|
|
30
|
+
},
|
|
24
31
|
name: {
|
|
25
32
|
canNest: true,
|
|
26
33
|
template: function name(options, attr, path) {
|
|
@@ -325,7 +332,7 @@ class AttributeOperationProxy {
|
|
|
325
332
|
fromObject(operation, record) {
|
|
326
333
|
for (let path of Object.keys(record)) {
|
|
327
334
|
const value = record[path];
|
|
328
|
-
const parts =
|
|
335
|
+
const parts = u.parseJSONPath(path);
|
|
329
336
|
let attribute = this.attributes;
|
|
330
337
|
for (let part of parts) {
|
|
331
338
|
attribute = attribute[part];
|
|
@@ -342,7 +349,7 @@ class AttributeOperationProxy {
|
|
|
342
349
|
|
|
343
350
|
fromArray(operation, paths) {
|
|
344
351
|
for (let path of paths) {
|
|
345
|
-
const parts =
|
|
352
|
+
const parts = u.parseJSONPath(path);
|
|
346
353
|
let attribute = this.attributes;
|
|
347
354
|
for (let part of parts) {
|
|
348
355
|
attribute = attribute[part];
|
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
|
};
|