electrodb 1.4.6 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -3
- package/README.md +127 -51
- package/index.d.ts +41 -1
- package/package.json +4 -2
- package/src/entity.js +83 -4
- package/src/errors.js +104 -8
- package/src/filters.js +1 -1
- package/src/operations.js +38 -31
- package/src/schema.js +101 -50
- package/src/update.js +1 -1
- package/tsconfig.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -98,10 +98,40 @@ All notable changes to this project will be documented in this file. Breaking ch
|
|
|
98
98
|
### Patched
|
|
99
99
|
- Updates did not include composite attributes involved in primary index. Though these values cannot be changed, they should be `set` on update method calls in case the update results in an item insert. [[read more]](./README.md#updates-to-composite-attributes)
|
|
100
100
|
|
|
101
|
-
## [1.4.5]
|
|
101
|
+
## [1.4.5] - 2021-10-17
|
|
102
102
|
### Fixed
|
|
103
103
|
- Improved .npmignore to remove playground oriented files, and created official directory to keep playground in sync with library changes.
|
|
104
104
|
|
|
105
|
-
## [1.4.6]
|
|
105
|
+
## [1.4.6] - 2021-10-20
|
|
106
106
|
### Added, Fixed
|
|
107
|
-
- Adding Entity identifiers to all update operations. When primary index composite attributes were added in 1.4.4, entities were written properly but did not include the identifiers. This resulted in entities being written but not being readable without the query option `ignoreOwnership` being used.
|
|
107
|
+
- Adding Entity identifiers to all update operations. When primary index composite attributes were added in 1.4.4, entities were written properly but did not include the identifiers. This resulted in entities being written but not being readable without the query option `ignoreOwnership` being used.
|
|
108
|
+
|
|
109
|
+
## [1.4.7] - 2021-10-20
|
|
110
|
+
### Changed
|
|
111
|
+
- Using `add()` update mutation now resolves to `ADD #prop :prop` update expression instead of a `SET #prop = #prop + :prop`
|
|
112
|
+
|
|
113
|
+
### Fixed
|
|
114
|
+
- Fixed param naming conflict during updates, when map attribute shares a name with another (separate) attribute.
|
|
115
|
+
|
|
116
|
+
## [1.4.8] - 2021-11-01
|
|
117
|
+
### Fixed
|
|
118
|
+
- Addressed issue#90 to flip batchGet's response tuple type definition.
|
|
119
|
+
|
|
120
|
+
## [1.5.0] - 2021-11-07
|
|
121
|
+
### Changed
|
|
122
|
+
- Queries will now fully paginate all responses. Prior to this change, ElectroDB would only return items from a single ElectroDB query result. Now ElectroDB will paginate through all query results. This will impact both uses of entity queries and service collections. [[read more](./README.md#query-method)]
|
|
123
|
+
- The query option `limit` has an extended meaning with the change to automatically paginate records on query. The option `limit` now represents 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. [[read more](./README.md#query-options)]
|
|
124
|
+
|
|
125
|
+
### Added
|
|
126
|
+
- A new query option `pages` has been added to coincide with the change to automatically paginate all records when queried. The `pages` option sets a max number of pagination iterations ElectroDB will perform on a query. When this option is paired with `limit`, ElectroDB will respect the first condition reached. [[read more](./README.md#query-options)]
|
|
127
|
+
|
|
128
|
+
## [1.6.0] - 2021-11-21
|
|
129
|
+
### Added
|
|
130
|
+
- Exporting TypeScript interfaces for `ElectroError` and `ElectroValidationError`
|
|
131
|
+
- Errors thrown within an attribute's validate callback are now wrapped and accessible after being thrown. Prior to this change, only the `message` of the error thrown by a validation function was persisted back through to the user, now the error itself is also accessible. Reference the exported interface typedef for `ElectroValidationError` [here](./index.d.ts) to see the new properties available on a thrown validation error.
|
|
132
|
+
|
|
133
|
+
### Changed
|
|
134
|
+
- As a byproduct of enhancing validation errors, the format of message text on a validation error has changed. This could be breaking if your app had a hardcoded dependency on the exact text of a thrown validation error.
|
|
135
|
+
|
|
136
|
+
### Fixed
|
|
137
|
+
- For Set attributes, the callback functions `get`, `set`, and `validate` are now consistently given an Array of values. These functions would sometimes (incorrectly) be called with a DynamoDB DocClient Set.
|
package/README.md
CHANGED
|
@@ -107,11 +107,11 @@ tasks
|
|
|
107
107
|
* [TypeScript Support](#typescript-support)
|
|
108
108
|
+ [TypeScript Services](#typescript-services)
|
|
109
109
|
* [Join](#join)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
- [Independent Models](#independent-models)
|
|
111
|
+
- [Joining Entity instances to a Service](#joining-entity-instances-to-a-service)
|
|
112
|
+
- [Joining models to a Service](#joining-models-to-a-service)
|
|
113
|
+
- [Joining Entities or Models with an alias](#joining-entities-or-models-with-an-alias)
|
|
114
|
+
- [Joining Entities at Service construction for TypeScript](#joining-entities-at-service-construction-for-typescript)
|
|
115
115
|
* [Model](#model)
|
|
116
116
|
+ [Model Properties](#model-properties)
|
|
117
117
|
+ [Service Options](#service-options)
|
|
@@ -125,7 +125,7 @@ tasks
|
|
|
125
125
|
- [Set Attributes](#set-attributes)
|
|
126
126
|
- [Attribute Getters and Setters](#attribute-getters-and-setters)
|
|
127
127
|
- [Attribute Watching](#attribute-watching)
|
|
128
|
-
* [Attribute Watching: Watch All](#attribute-watching
|
|
128
|
+
* [Attribute Watching: Watch All](#attribute-watching--watch-all)
|
|
129
129
|
* [Attribute Watching Examples](#attribute-watching-examples)
|
|
130
130
|
- [Calculated Attributes](#calculated-attributes)
|
|
131
131
|
- [Virtual Attributes](#virtual-attributes)
|
|
@@ -147,7 +147,7 @@ tasks
|
|
|
147
147
|
+ [Collection Queries vs Entity Queries](#collection-queries-vs-entity-queries)
|
|
148
148
|
+ [Collection Response Structure](#collection-response-structure)
|
|
149
149
|
* [Sub-Collections](#sub-collections)
|
|
150
|
-
|
|
150
|
+
- [Sub-Collection Entities](#sub-collection-entities)
|
|
151
151
|
* [Index and Collection Naming Conventions](#index-and-collection-naming-conventions)
|
|
152
152
|
+ [Index Naming Conventions](#index-naming-conventions)
|
|
153
153
|
* [Collection Naming Conventions](#collection-naming-conventions)
|
|
@@ -163,12 +163,13 @@ tasks
|
|
|
163
163
|
+ [Multiple Where Clauses](#multiple-where-clauses)
|
|
164
164
|
* [Parse](#parse)
|
|
165
165
|
- [Building Queries](#building-queries)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
+ [Using composite attributes to make hierarchical keys](#using-composite-attributes-to-make-hierarchical-keys)
|
|
167
|
+
- [Shopping Mall Stores](#shopping-mall-stores)
|
|
168
|
+
+ [Query App Records](#query-app-records)
|
|
169
|
+
- [Partition Key Composite Attributes](#partition-key-composite-attributes)
|
|
170
|
+
+ [Sort Key Operations](#sort-key-operations)
|
|
171
171
|
* [Query Chains](#query-chains)
|
|
172
|
+
+ [Query Method](#query-method)
|
|
172
173
|
+ [Get Method](#get-method)
|
|
173
174
|
+ [Batch Get](#batch-get)
|
|
174
175
|
+ [Delete Method](#delete-method)
|
|
@@ -177,14 +178,14 @@ tasks
|
|
|
177
178
|
+ [Batch Write Put Records](#batch-write-put-records)
|
|
178
179
|
+ [Update Record](#update-record)
|
|
179
180
|
- [Updates to Composite Attributes](#updates-to-composite-attributes)
|
|
180
|
-
- [Update Method: Set](#update-method
|
|
181
|
-
- [Update Method: Remove](#update-method
|
|
182
|
-
- [Update Method: Add](#update-method
|
|
183
|
-
- [Update Method: Subtract](#update-method
|
|
184
|
-
- [Update Method: Append](#update-method
|
|
185
|
-
- [Update Method: Delete](#update-method
|
|
186
|
-
- [Update Method: Data](#update-method
|
|
187
|
-
+ [Update Method: Complex Data Types](#update-method
|
|
181
|
+
- [Update Method: Set](#update-method--set)
|
|
182
|
+
- [Update Method: Remove](#update-method--remove)
|
|
183
|
+
- [Update Method: Add](#update-method--add)
|
|
184
|
+
- [Update Method: Subtract](#update-method--subtract)
|
|
185
|
+
- [Update Method: Append](#update-method--append)
|
|
186
|
+
- [Update Method: Delete](#update-method--delete)
|
|
187
|
+
- [Update Method: Data](#update-method--data)
|
|
188
|
+
+ [Update Method: Complex Data Types](#update-method--complex-data-types)
|
|
188
189
|
+ [Scan Records](#scan-records)
|
|
189
190
|
+ [Remove Method](#remove-method)
|
|
190
191
|
+ [Patch Record](#patch-record)
|
|
@@ -204,30 +205,33 @@ tasks
|
|
|
204
205
|
* [Pagination Example](#pagination-example)
|
|
205
206
|
* [Query Examples](#query-examples)
|
|
206
207
|
* [Query Options](#query-options)
|
|
207
|
-
- [Errors:](#errors)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
208
|
+
- [Errors:](#errors-)
|
|
209
|
+
+ [No Client Defined On Model](#no-client-defined-on-model)
|
|
210
|
+
+ [Invalid Identifier](#invalid-identifier)
|
|
211
|
+
+ [Invalid Key Composite Attribute Template](#invalid-key-composite-attribute-template)
|
|
212
|
+
+ [Duplicate Indexes](#duplicate-indexes)
|
|
213
|
+
+ [Collection Without An SK](#collection-without-an-sk)
|
|
214
|
+
+ [Duplicate Collections](#duplicate-collections)
|
|
215
|
+
+ [Missing Primary Index](#missing-primary-index)
|
|
216
|
+
+ [Invalid Attribute Definition](#invalid-attribute-definition)
|
|
217
|
+
+ [Invalid Model](#invalid-model)
|
|
218
|
+
+ [Invalid Options](#invalid-options)
|
|
219
|
+
+ [Duplicate Index Fields](#duplicate-index-fields)
|
|
220
|
+
+ [Duplicate Index Composite Attributes](#duplicate-index-composite-attributes)
|
|
221
|
+
+ [Incompatible Key Composite Attribute Template](#incompatible-key-composite-attribute-template)
|
|
222
|
+
+ [Invalid Index With Attribute Name](#invalid-index-with-attribute-name)
|
|
223
|
+
+ [Invalid Collection on Index With Attribute Field Names](#invalid-collection-on-index-with-attribute-field-names)
|
|
224
|
+
+ [Missing Composite Attributes](#missing-composite-attributes)
|
|
225
|
+
+ [Missing Table](#missing-table)
|
|
226
|
+
+ [Invalid Concurrency Option](#invalid-concurrency-option)
|
|
227
|
+
+ [Invalid Pages Option](#invalid-pages-option)
|
|
228
|
+
+ [Invalid Limit Option](#invalid-limit-option)
|
|
229
|
+
+ [Invalid Attribute](#invalid-attribute)
|
|
230
|
+
+ [AWS Error](#aws-error)
|
|
231
|
+
+ [Unknown Errors](#unknown-errors)
|
|
232
|
+
+ [Invalid Last Evaluated Key](#invalid-last-evaluated-key)
|
|
233
|
+
+ [No Owner For Pager](#no-owner-for-pager)
|
|
234
|
+
+ [Pager Not Unique](#pager-not-unique)
|
|
231
235
|
- [Examples](#examples)
|
|
232
236
|
* [Employee App](#employee-app)
|
|
233
237
|
+ [Employee App Requirements](#employee-app-requirements)
|
|
@@ -1564,9 +1568,11 @@ name: "your_item_name"
|
|
|
1564
1568
|
## Collections
|
|
1565
1569
|
A Collection is a grouping of Entities with the same Partition Key and allows you to make efficient query across multiple entities. If your background is SQL, imagine Partition Keys as Foreign Keys, a Collection represents a View with multiple joined Entities.
|
|
1566
1570
|
|
|
1571
|
+
> _NOTE: ElectroDB Collections use DynamoDB queries to retrieve results. One query is made to retrieve results for all Entities (the benefits of single table design), however like the [query method](#query-method), ElectroDB will paginate through all results for a given query._
|
|
1572
|
+
|
|
1567
1573
|
Collections are defined on an Index, and the name of the collection should represent what the query would return as a pseudo `Entity`. Additionally, Collection names must be unique across a `Service`.
|
|
1568
1574
|
|
|
1569
|
-
>
|
|
1575
|
+
> _NOTE: A `collection` name should be unique to a single common index across entities._
|
|
1570
1576
|
|
|
1571
1577
|
```javascript
|
|
1572
1578
|
const DynamoDB = require("aws-sdk/clients/dynamodb");
|
|
@@ -2600,6 +2606,10 @@ Option | Default | Notes
|
|
|
2600
2606
|
# Building Queries
|
|
2601
2607
|
> For hands-on learners: the following example can be followed along with **and** executed on runkit: https://runkit.com/tywalch/electrodb-building-queries
|
|
2602
2608
|
|
|
2609
|
+
ElectroDB queries use DynamoDB's `query` method to find records based on your table's indexes.
|
|
2610
|
+
|
|
2611
|
+
> _NOTE: By default, ElectroDB will paginate through all items that match your query. To limit the number of items ElectroDB will retrieve, read more about the [Query Options](#query-options) `pages` and `limit`, or use the ElectroDB [Pagination API](#page) for fine-grain pagination support._
|
|
2612
|
+
|
|
2603
2613
|
Forming a composite **Partition Key** and **Sort Key** is a critical step in planning **Access Patterns** in **DynamoDB**. When planning composite keys, it is crucial to consider the order in which they are *composed*. As of the time of writing this documentation, **DynamoDB** has the following constraints that should be taken into account when planning your **Access Patterns**:
|
|
2604
2614
|
1. You must always supply the **Partition Key** in full for all queries to **DynamoDB**.
|
|
2605
2615
|
2. You currently only have the following operators available on a **Sort Key**: `begins_with`, `between`, `>`, `>=`, `<`, `<=`, and `Equals`.
|
|
@@ -2806,6 +2816,12 @@ Queries in ***ElectroDB*** are built around the **Access Patterns** defined in t
|
|
|
2806
2816
|
|
|
2807
2817
|
The methods: Get (`get`), Create (`put`), Update (`update`), and Delete (`delete`) **require* all composite attributes described in the Entities' primary `PK` and `SK`.
|
|
2808
2818
|
|
|
2819
|
+
### Query Method
|
|
2820
|
+
|
|
2821
|
+
ElectroDB queries use DynamoDB's `query` method to find records based on your table's indexes. To read more about queries checkout the section [Building Queries](#building-queries)
|
|
2822
|
+
|
|
2823
|
+
> _NOTE: By default, ElectroDB will paginate through all items that match your query. To limit the number of items ElectroDB will retrieve, read more about the [Query Options](#query-options) `pages` and `limit`, or use the ElectroDB [Pagination API](#page) for fine-grain pagination support._
|
|
2824
|
+
|
|
2809
2825
|
### Get Method
|
|
2810
2826
|
Provide all Table Index composite attributes in an object to the `get` method. In the event no record is found, a value of `null` will be returned.
|
|
2811
2827
|
|
|
@@ -4032,7 +4048,7 @@ let stores = MallStores.query
|
|
|
4032
4048
|
|
|
4033
4049
|
### Page
|
|
4034
4050
|
|
|
4035
|
-
>
|
|
4051
|
+
> _NOTE: By Default, ElectroDB queries will paginate through all results with the [`go()`](#building-queries) method. ElectroDB's `page()` method can be used to manually iterate through DynamoDB query results._
|
|
4036
4052
|
|
|
4037
4053
|
The `page` method _ends_ a query chain, and asynchronously queries DynamoDB with the `client` provided in the model. Unlike the `.go()`, the `.page()` method returns a tuple.
|
|
4038
4054
|
|
|
@@ -4093,6 +4109,9 @@ let [pageTwo, moreStores] = await MallStores.query
|
|
|
4093
4109
|
```
|
|
4094
4110
|
|
|
4095
4111
|
#### Service Pagination
|
|
4112
|
+
|
|
4113
|
+
> _NOTE: By Default, ElectroDB will paginate through all results with the [`query()`](#building-queries) method. ElectroDB's `page()` method can be used to manually iterate through DynamoDB query results._
|
|
4114
|
+
|
|
4096
4115
|
Pagination with services is also possible. Similar to [Entity Pagination](#entity-pagination), calling the `.page()` method returns a `[pager, results]` tuple. Also, similar to pagination on Entities, the pager object returned by default is a deconstruction of the returned LastEvaluatedKey.
|
|
4097
4116
|
|
|
4098
4117
|
#### Pager Query Options
|
|
@@ -4261,6 +4280,7 @@ By default, **ElectroDB** enables you to work with records as the names and prop
|
|
|
4261
4280
|
response?: "default" | "none" | "all_old" | "updated_old" | "all_new" | "updated_new";
|
|
4262
4281
|
ignoreOwnership?: boolean;
|
|
4263
4282
|
limit?: number;
|
|
4283
|
+
pages?: number;
|
|
4264
4284
|
};
|
|
4265
4285
|
```
|
|
4266
4286
|
|
|
@@ -4276,7 +4296,8 @@ concurrent | `1` | When performing batch operations, how m
|
|
|
4276
4296
|
unprocessed | `"item"` | Used in batch processing to override ElectroDBs default behaviour to break apart DynamoDBs `Unprocessed` records into composite attributes. See more detail about this in the sections for [BatchGet](#batch-get), [BatchDelete](#batch-write-delete-records), and [BatchPut](#batch-write-put-records).
|
|
4277
4297
|
response | `"default"` | Used as a convenience for applying the DynamoDB parameter `ReturnValues`. The options here are the same as the parameter values for the DocumentClient except lowercase. The `"none"` option will cause the method to return null and will bypass ElectroDB's response formatting -- useful if formatting performance is a concern.
|
|
4278
4298
|
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`.
|
|
4279
|
-
limit | _none_ | A
|
|
4299
|
+
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.
|
|
4300
|
+
pages | ∞ | How many DynamoDB pages should a query iterate through before stopping. By default ElectroDB paginate through all results for your query.
|
|
4280
4301
|
|
|
4281
4302
|
# Errors:
|
|
4282
4303
|
|
|
@@ -4531,12 +4552,68 @@ Tables can be defined on the [Service Options](#service-options) object when you
|
|
|
4531
4552
|
*Code: 2004*
|
|
4532
4553
|
|
|
4533
4554
|
*Why this occurred:*
|
|
4534
|
-
When performing a bulk operation ([Batch Get](#batch-get), [Batch Delete Records](#batch-write-delete-records), [Batch Put Records](#batch-write-put-records)) you can pass a [Query Options](#query-options) called `concurrent`, which impacts how many batch requests can occur at the same time. Your value pass the test of both, `!isNaN(parseInt(value))` and `parseInt(value) > 0`.
|
|
4555
|
+
When performing a bulk operation ([Batch Get](#batch-get), [Batch Delete Records](#batch-write-delete-records), [Batch Put Records](#batch-write-put-records)) you can pass a [Query Options](#query-options) called `concurrent`, which impacts how many batch requests can occur at the same time. Your value should pass the test of both, `!isNaN(parseInt(value))` and `parseInt(value) > 0`.
|
|
4535
4556
|
|
|
4536
4557
|
*What to do about it:*
|
|
4537
|
-
Expect this error only if you're providing a concurrency
|
|
4558
|
+
Expect this error only if you're providing a `concurrency` option. Double-check the value you are providing is the value you expect to be passing, and that the value passes the tests listed above.
|
|
4559
|
+
|
|
4560
|
+
### Invalid Pages Option
|
|
4561
|
+
*Code: 2005*
|
|
4562
|
+
|
|
4563
|
+
*Why this occurred:*
|
|
4564
|
+
When performing a query [Query](#building-queries) you can pass a [Query Options](#query-options) called `pages`, which impacts how many DynamoDB pages a query should iterate through. Your value should pass the test of both, `!isNaN(parseInt(value))` and `parseInt(value) > 0`.
|
|
4565
|
+
|
|
4566
|
+
*What to do about it:*
|
|
4567
|
+
Expect this error only if you're providing a `pages` option. Double-check the value you are providing is the value you expect to be passing, and that the value passes the tests listed above.
|
|
4568
|
+
|
|
4569
|
+
### Invalid Limit Option
|
|
4570
|
+
*Code: 2006*
|
|
4571
|
+
|
|
4572
|
+
*Why this occurred:*
|
|
4573
|
+
When performing a query [Query](#building-queries) you can pass a [Query Options](#query-options) called `limit`, which impacts how many DynamoDB items a query should return. Your value should pass the test of both, `!isNaN(parseInt(value))` and `parseInt(value) > 0`.
|
|
4574
|
+
|
|
4575
|
+
*What to do about it:*
|
|
4576
|
+
Expect this error only if you're providing a `limit` option. Double-check the value you are providing is the value you expect to be passing, and that the value passes the tests listed above.
|
|
4577
|
+
|
|
4578
|
+
### Invalid Attribute
|
|
4579
|
+
*Code: 3001*
|
|
4580
|
+
|
|
4581
|
+
*Why this occurred:*
|
|
4582
|
+
The value received for a validation either failed type expectations (e.g. a "number" instead of a "string"), or the user provided "validate" callback on an attribute rejected a value.
|
|
4583
|
+
|
|
4584
|
+
*What to do about it:*
|
|
4585
|
+
Examine the error itself for more precise detail on why the failure occurred. The error object itself should have a property called "fields" which contains an array of every attribute that failed validation, and a reason for each. If the failure originated from a "validate" callback, the originally thrown error will be accessible via the `cause` property the corresponding element within the fields array.1
|
|
4586
|
+
|
|
4587
|
+
Below is the type definition for an ElectroValidationError:
|
|
4588
|
+
|
|
4589
|
+
```typescript
|
|
4590
|
+
ElectroValidationError<T extends Error = Error> extends ElectroError {
|
|
4591
|
+
readonly name: "ElectroValidationError"
|
|
4592
|
+
readonly fields: ReadonlyArray<{
|
|
4593
|
+
/**
|
|
4594
|
+
* The json path to the attribute that had a validation error
|
|
4595
|
+
*/
|
|
4596
|
+
readonly field: string;
|
|
4597
|
+
|
|
4598
|
+
/**
|
|
4599
|
+
* A description of the validation error for that attribute
|
|
4600
|
+
*/
|
|
4601
|
+
readonly reason: string;
|
|
4602
|
+
|
|
4603
|
+
/**
|
|
4604
|
+
* Index of the value passed (present only in List attribute validation errors)
|
|
4605
|
+
*/
|
|
4606
|
+
readonly index: number | undefined;
|
|
4607
|
+
|
|
4608
|
+
/**
|
|
4609
|
+
* The error thrown from the attribute's validate callback (if applicable)
|
|
4610
|
+
*/
|
|
4611
|
+
readonly cause: T | undefined;
|
|
4612
|
+
}>
|
|
4613
|
+
}
|
|
4614
|
+
```
|
|
4538
4615
|
|
|
4539
|
-
###
|
|
4616
|
+
### AWS Error
|
|
4540
4617
|
*Code: 4001*
|
|
4541
4618
|
|
|
4542
4619
|
*Why this occurred:*
|
|
@@ -5462,4 +5539,3 @@ This change stems from the fact the `facets` is already a defined term in the Dy
|
|
|
5462
5539
|
|
|
5463
5540
|
# Coming Soon
|
|
5464
5541
|
- Default query options defined on the `model` to give more general control of interactions with the Entity.
|
|
5465
|
-
- Complex attributes (list, map, set)
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
declare const WhereSymbol: unique symbol;
|
|
2
2
|
declare const UpdateDataSymbol: unique symbol;
|
|
3
3
|
|
|
4
|
+
export interface ElectroError extends Error {
|
|
5
|
+
readonly name: 'ElectroError';
|
|
6
|
+
readonly code: number;
|
|
7
|
+
readonly date: number;
|
|
8
|
+
readonly isElectroError: boolean;
|
|
9
|
+
ref: {
|
|
10
|
+
readonly code: number;
|
|
11
|
+
readonly section: string;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly sym: unique symbol;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ElectroValidationErrorFieldReference<T extends Error = Error> {
|
|
18
|
+
/**
|
|
19
|
+
* The json path to the attribute that had a validation error
|
|
20
|
+
*/
|
|
21
|
+
readonly field: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A description of the validation error for that attribute
|
|
25
|
+
*/
|
|
26
|
+
readonly reason: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Index of the value passed (present only in List attribute validation errors)
|
|
30
|
+
*/
|
|
31
|
+
readonly index: number | undefined;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The error thrown from the attribute's validate callback (if applicable)
|
|
35
|
+
*/
|
|
36
|
+
readonly cause: T | undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ElectroValidationError<T extends Error = Error> extends ElectroError {
|
|
40
|
+
readonly fields: ReadonlyArray<ElectroValidationErrorFieldReference<T>>;
|
|
41
|
+
}
|
|
42
|
+
|
|
4
43
|
interface ReadOnlyAttribute {
|
|
5
44
|
readonly readOnly: true;
|
|
6
45
|
}
|
|
@@ -933,6 +972,7 @@ interface QueryOptions {
|
|
|
933
972
|
includeKeys?: boolean;
|
|
934
973
|
originalErr?: boolean;
|
|
935
974
|
ignoreOwnership?: boolean;
|
|
975
|
+
pages?: number;
|
|
936
976
|
}
|
|
937
977
|
|
|
938
978
|
// subset of QueryOptions
|
|
@@ -1103,7 +1143,7 @@ export class Entity<A extends string, F extends A, C extends string, S extends S
|
|
|
1103
1143
|
readonly schema: S;
|
|
1104
1144
|
constructor(schema: S, config?: EntityConfiguration);
|
|
1105
1145
|
get(key: AllTableIndexCompositeAttributes<A,F,C,S>): SingleRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S> | null>;
|
|
1106
|
-
get(key: AllTableIndexCompositeAttributes<A,F,C,S>[]): BulkRecordOperationOptions<A,F,C,S, [
|
|
1146
|
+
get(key: AllTableIndexCompositeAttributes<A,F,C,S>[]): BulkRecordOperationOptions<A,F,C,S, [Array<ResponseItem<A,F,C,S>>, Array<AllTableIndexCompositeAttributes<A,F,C,S>>]>;
|
|
1107
1147
|
delete(key: AllTableIndexCompositeAttributes<A,F,C,S>): DeleteRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>;
|
|
1108
1148
|
delete(key: AllTableIndexCompositeAttributes<A,F,C,S>[]): BulkRecordOperationOptions<A,F,C,S, AllTableIndexCompositeAttributes<A,F,C,S>[]>;
|
|
1109
1149
|
remove(key: AllTableIndexCompositeAttributes<A,F,C,S>): DeleteRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "electrodb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test-types": "node ./node_modules/tsd/dist/cli.js",
|
|
12
12
|
"coverage": "nyc npm run test-all && nyc report --reporter=text-lcov | coveralls",
|
|
13
13
|
"coverage-coveralls-local": "nyc npm run test-all-local && nyc report --reporter=text-lcov | coveralls",
|
|
14
|
-
"coverage-html-local": "nyc npm run test-all-local && nyc report --reporter=html"
|
|
14
|
+
"coverage-html-local": "nyc npm run test-all-local && nyc report --reporter=html",
|
|
15
|
+
"build:browser": "browserify playground/browser.js -o playground/bundle.js"
|
|
15
16
|
},
|
|
16
17
|
"repository": {
|
|
17
18
|
"type": "git",
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
"@types/node": "^15.6.0",
|
|
31
32
|
"@types/uuid": "^8.3.0",
|
|
32
33
|
"aws-sdk": "2.630.0",
|
|
34
|
+
"browserify": "^17.0.0",
|
|
33
35
|
"chai": "4.2.0",
|
|
34
36
|
"coveralls": "^3.1.0",
|
|
35
37
|
"istanbul": "0.4.5",
|
package/src/entity.js
CHANGED
|
@@ -150,7 +150,8 @@ class Entity {
|
|
|
150
150
|
names: expressions.names || {},
|
|
151
151
|
values: expressions.values || {},
|
|
152
152
|
expression: expressions.expression || ""
|
|
153
|
-
}
|
|
153
|
+
},
|
|
154
|
+
_isCollectionQuery: true,
|
|
154
155
|
};
|
|
155
156
|
|
|
156
157
|
let index = this.model.translations.collections.fromCollectionToIndex[collection];
|
|
@@ -241,8 +242,10 @@ class Entity {
|
|
|
241
242
|
return await this.executeBulkWrite(parameters, config);
|
|
242
243
|
case MethodTypes.batchGet:
|
|
243
244
|
return await this.executeBulkGet(parameters, config);
|
|
245
|
+
case MethodTypes.query:
|
|
246
|
+
return await this.executeQuery(parameters, config)
|
|
244
247
|
default:
|
|
245
|
-
return await this.
|
|
248
|
+
return await this.executeOperation(method, parameters, config);
|
|
246
249
|
}
|
|
247
250
|
} catch (err) {
|
|
248
251
|
if (config.originalErr || stackTrace === undefined) {
|
|
@@ -324,7 +327,54 @@ class Entity {
|
|
|
324
327
|
return [resultsAll, unprocessedAll];
|
|
325
328
|
}
|
|
326
329
|
|
|
327
|
-
async executeQuery(
|
|
330
|
+
async executeQuery(parameters, config = {}) {
|
|
331
|
+
let results = config._isCollectionQuery
|
|
332
|
+
? {}
|
|
333
|
+
: [];
|
|
334
|
+
let ExclusiveStartKey;
|
|
335
|
+
let pages = this._normalizePagesValue(config.pages);
|
|
336
|
+
let max = this._normalizeLimitValue(config.limit);
|
|
337
|
+
let iterations = 0;
|
|
338
|
+
let count = 0;
|
|
339
|
+
do {
|
|
340
|
+
let limit = max === undefined
|
|
341
|
+
? parameters.Limit
|
|
342
|
+
: max - count;
|
|
343
|
+
let response = await this._exec("query", {ExclusiveStartKey, ...parameters, Limit: limit});
|
|
344
|
+
|
|
345
|
+
ExclusiveStartKey = response.LastEvaluatedKey;
|
|
346
|
+
|
|
347
|
+
if (validations.isFunction(config.parse)) {
|
|
348
|
+
response = config.parse(config, response);
|
|
349
|
+
} else {
|
|
350
|
+
response = this.formatResponse(response, parameters.IndexName, config);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (config.raw || config._isPagination) {
|
|
354
|
+
return response;
|
|
355
|
+
} else if (config._isCollectionQuery) {
|
|
356
|
+
for (const entity in response) {
|
|
357
|
+
if (max) {
|
|
358
|
+
count += response[entity].length;
|
|
359
|
+
}
|
|
360
|
+
results[entity] = results[entity] || [];
|
|
361
|
+
results[entity] = [...results[entity], ...response[entity]];
|
|
362
|
+
}
|
|
363
|
+
} else if (Array.isArray(response)) {
|
|
364
|
+
if (max) {
|
|
365
|
+
count += response.length;
|
|
366
|
+
}
|
|
367
|
+
results = [...results, ...response];
|
|
368
|
+
} else {
|
|
369
|
+
return response;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
iterations++;
|
|
373
|
+
} while(ExclusiveStartKey && iterations < pages && (max === undefined || count < max));
|
|
374
|
+
return results;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async executeOperation(method, parameters, config) {
|
|
328
378
|
let response = await this._exec(method, parameters);
|
|
329
379
|
if (validations.isFunction(config.parse)) {
|
|
330
380
|
return config.parse(config, response);
|
|
@@ -566,6 +616,24 @@ class Entity {
|
|
|
566
616
|
return value;
|
|
567
617
|
}
|
|
568
618
|
|
|
619
|
+
_normalizePagesValue(value = Number.MAX_SAFE_INTEGER) {
|
|
620
|
+
value = parseInt(value);
|
|
621
|
+
if (isNaN(value) || value < 1) {
|
|
622
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidPagesOption, "Query option 'pages' must be of type 'number' and greater than zero.");
|
|
623
|
+
}
|
|
624
|
+
return value;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
_normalizeLimitValue(value) {
|
|
628
|
+
if (value !== undefined) {
|
|
629
|
+
value = parseInt(value);
|
|
630
|
+
if (isNaN(value) || value < 1) {
|
|
631
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidLimitOption, "Query option 'limit' must be of type 'number' and greater than zero.");
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return value;
|
|
635
|
+
}
|
|
636
|
+
|
|
569
637
|
_deconstructKeys(index, keyType, key, backupFacets = {}) {
|
|
570
638
|
if (typeof key !== "string" || key.length === 0) {
|
|
571
639
|
return null;
|
|
@@ -693,6 +761,8 @@ class Entity {
|
|
|
693
761
|
response: 'default',
|
|
694
762
|
ignoreOwnership: false,
|
|
695
763
|
_isPagination: false,
|
|
764
|
+
_isCollectionQuery: false,
|
|
765
|
+
pages: undefined,
|
|
696
766
|
};
|
|
697
767
|
|
|
698
768
|
config = options.reduce((config, option) => {
|
|
@@ -705,6 +775,14 @@ class Entity {
|
|
|
705
775
|
config.params.ReturnValues = FormatToReturnValues[format];
|
|
706
776
|
}
|
|
707
777
|
|
|
778
|
+
if (option.pages !== undefined) {
|
|
779
|
+
config.pages = option.pages;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (option._isCollectionQuery === true) {
|
|
783
|
+
config._isCollectionQuery = true;
|
|
784
|
+
}
|
|
785
|
+
|
|
708
786
|
if (option.includeKeys === true) {
|
|
709
787
|
config.includeKeys = true;
|
|
710
788
|
}
|
|
@@ -727,7 +805,8 @@ class Entity {
|
|
|
727
805
|
config.unprocessed = UnprocessedTypes.raw;
|
|
728
806
|
}
|
|
729
807
|
|
|
730
|
-
if (
|
|
808
|
+
if (option.limit !== undefined) {
|
|
809
|
+
config.limit = option.limit;
|
|
731
810
|
config.params.Limit = option.limit;
|
|
732
811
|
}
|
|
733
812
|
|
package/src/errors.js
CHANGED
|
@@ -151,6 +151,18 @@ const ErrorCodes = {
|
|
|
151
151
|
name: "InvalidConcurrencyOption",
|
|
152
152
|
sym: ErrorCode
|
|
153
153
|
},
|
|
154
|
+
InvalidPagesOption: {
|
|
155
|
+
code: 2005,
|
|
156
|
+
section: "invalid-pages-option",
|
|
157
|
+
name: "InvalidPagesOption",
|
|
158
|
+
sym: ErrorCode,
|
|
159
|
+
},
|
|
160
|
+
InvalidLimitOption: {
|
|
161
|
+
code: 2006,
|
|
162
|
+
section: "invalid-limit-option",
|
|
163
|
+
name: "InvalidLimitOption",
|
|
164
|
+
sym: ErrorCode,
|
|
165
|
+
},
|
|
154
166
|
InvalidAttribute: {
|
|
155
167
|
code: 3001,
|
|
156
168
|
section: "invalid-attribute",
|
|
@@ -195,28 +207,112 @@ const ErrorCodes = {
|
|
|
195
207
|
},
|
|
196
208
|
};
|
|
197
209
|
|
|
210
|
+
function makeMessage(message, section) {
|
|
211
|
+
return `${message} - For more detail on this error reference: ${getHelpLink(section)}`
|
|
212
|
+
}
|
|
213
|
+
|
|
198
214
|
class ElectroError extends Error {
|
|
199
|
-
constructor(
|
|
215
|
+
constructor(code, message) {
|
|
200
216
|
super(message);
|
|
201
217
|
let detail = ErrorCodes.UnknownError;
|
|
202
|
-
if (
|
|
203
|
-
detail =
|
|
218
|
+
if (code && code.sym === ErrorCode) {
|
|
219
|
+
detail = code
|
|
204
220
|
}
|
|
205
|
-
this.
|
|
206
|
-
|
|
221
|
+
this._message = message;
|
|
222
|
+
// this.message = `${message} - For more detail on this error reference: ${getHelpLink(detail.section)}`;
|
|
223
|
+
this.message = makeMessage(message, detail.section);
|
|
207
224
|
if (Error.captureStackTrace) {
|
|
208
225
|
Error.captureStackTrace(this, ElectroError);
|
|
209
226
|
}
|
|
210
227
|
|
|
211
228
|
this.name = 'ElectroError';
|
|
212
|
-
this.ref =
|
|
229
|
+
this.ref = code;
|
|
213
230
|
this.code = detail.code;
|
|
214
|
-
this.date =
|
|
231
|
+
this.date = Date.now();
|
|
215
232
|
this.isElectroError = true;
|
|
216
233
|
}
|
|
217
234
|
}
|
|
218
235
|
|
|
236
|
+
class ElectroValidationError extends ElectroError {
|
|
237
|
+
constructor(errors = []) {
|
|
238
|
+
const fields = [];
|
|
239
|
+
const messages = [];
|
|
240
|
+
for (let i = 0; i < errors.length; i++) {
|
|
241
|
+
const error = errors[i];
|
|
242
|
+
const message = error ? (error._message || error.message) : undefined;
|
|
243
|
+
messages.push(message);
|
|
244
|
+
if (error instanceof ElectroUserValidationError) {
|
|
245
|
+
fields.push({
|
|
246
|
+
field: error.field,
|
|
247
|
+
index: error.index,
|
|
248
|
+
reason: message,
|
|
249
|
+
cause: error.cause,
|
|
250
|
+
type: 'validation'
|
|
251
|
+
});
|
|
252
|
+
} else if (error instanceof ElectroAttributeValidationError) {
|
|
253
|
+
fields.push({
|
|
254
|
+
field: error.field,
|
|
255
|
+
index: error.index,
|
|
256
|
+
reason: message,
|
|
257
|
+
cause: error.cause || error, // error | undefined
|
|
258
|
+
type: 'validation'
|
|
259
|
+
});
|
|
260
|
+
} else if (message) {
|
|
261
|
+
fields.push({
|
|
262
|
+
field: '',
|
|
263
|
+
index: error.index,
|
|
264
|
+
reason: message,
|
|
265
|
+
cause: error !== undefined ? error.cause || error : undefined,
|
|
266
|
+
type: 'fatal'
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const message = messages
|
|
272
|
+
.filter(message => typeof message === "string" && message.length)
|
|
273
|
+
.join(', ') || `Invalid value(s) provided`;
|
|
274
|
+
|
|
275
|
+
super(ErrorCodes.InvalidAttribute, message);
|
|
276
|
+
this.fields = fields;
|
|
277
|
+
this.name = "ElectroValidationError";
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
class ElectroUserValidationError extends ElectroError {
|
|
282
|
+
constructor(field, cause) {
|
|
283
|
+
let message;
|
|
284
|
+
let hasCause = false;
|
|
285
|
+
if (typeof cause === "string") {
|
|
286
|
+
message = cause;
|
|
287
|
+
} else if (cause !== undefined && typeof cause._message === "string" && cause._message.length) {
|
|
288
|
+
message = cause._message;
|
|
289
|
+
hasCause = true;
|
|
290
|
+
} else if (cause !== undefined && typeof cause.message === "string" && cause.message.length) {
|
|
291
|
+
message = cause.message;
|
|
292
|
+
hasCause = true;
|
|
293
|
+
} else {
|
|
294
|
+
message = "Invalid value provided";
|
|
295
|
+
}
|
|
296
|
+
super(ErrorCodes.InvalidAttribute, message);
|
|
297
|
+
this.field = field;
|
|
298
|
+
this.name = "ElectroUserValidationError";
|
|
299
|
+
if (hasCause) {
|
|
300
|
+
this.cause = cause;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
class ElectroAttributeValidationError extends ElectroError {
|
|
306
|
+
constructor(field, reason) {
|
|
307
|
+
super(ErrorCodes.InvalidAttribute, reason);
|
|
308
|
+
this.field = field;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
219
312
|
module.exports = {
|
|
313
|
+
ErrorCodes,
|
|
220
314
|
ElectroError,
|
|
221
|
-
|
|
315
|
+
ElectroValidationError,
|
|
316
|
+
ElectroUserValidationError,
|
|
317
|
+
ElectroAttributeValidationError
|
|
222
318
|
};
|
package/src/filters.js
CHANGED
package/src/operations.js
CHANGED
|
@@ -4,7 +4,7 @@ const v = require("./util");
|
|
|
4
4
|
|
|
5
5
|
const deleteOperations = {
|
|
6
6
|
canNest: false,
|
|
7
|
-
template: function del(attr, path, value) {
|
|
7
|
+
template: function del(options, attr, path, value) {
|
|
8
8
|
let operation = "";
|
|
9
9
|
let expression = "";
|
|
10
10
|
switch(attr.type) {
|
|
@@ -23,19 +23,19 @@ const deleteOperations = {
|
|
|
23
23
|
const UpdateOperations = {
|
|
24
24
|
name: {
|
|
25
25
|
canNest: true,
|
|
26
|
-
template: function name(attr, path) {
|
|
26
|
+
template: function name(options, attr, path) {
|
|
27
27
|
return path;
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
value: {
|
|
31
31
|
canNest: true,
|
|
32
|
-
template: function value(attr, path, value) {
|
|
32
|
+
template: function value(options, attr, path, value) {
|
|
33
33
|
return value;
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
append: {
|
|
37
37
|
canNest: false,
|
|
38
|
-
template: function append(attr, path, value) {
|
|
38
|
+
template: function append(options, attr, path, value) {
|
|
39
39
|
let operation = "";
|
|
40
40
|
let expression = "";
|
|
41
41
|
switch(attr.type) {
|
|
@@ -52,7 +52,7 @@ const UpdateOperations = {
|
|
|
52
52
|
},
|
|
53
53
|
add: {
|
|
54
54
|
canNest: false,
|
|
55
|
-
template: function add(attr, path, value) {
|
|
55
|
+
template: function add(options, attr, path, value) {
|
|
56
56
|
let operation = "";
|
|
57
57
|
let expression = "";
|
|
58
58
|
switch(attr.type) {
|
|
@@ -62,8 +62,13 @@ const UpdateOperations = {
|
|
|
62
62
|
expression = `${path} ${value}`;
|
|
63
63
|
break;
|
|
64
64
|
case AttributeTypes.number:
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
if (options.nestedValue) {
|
|
66
|
+
operation = ItemOperations.set;
|
|
67
|
+
expression = `${path} = ${path} + ${value}`;
|
|
68
|
+
} else {
|
|
69
|
+
operation = ItemOperations.add;
|
|
70
|
+
expression = `${path} ${value}`;
|
|
71
|
+
}
|
|
67
72
|
break;
|
|
68
73
|
default:
|
|
69
74
|
throw new Error(`Invalid Update Attribute Operation: "ADD" Operation can only be performed on attributes with type "number", "set", or "any".`);
|
|
@@ -73,7 +78,7 @@ const UpdateOperations = {
|
|
|
73
78
|
},
|
|
74
79
|
subtract: {
|
|
75
80
|
canNest: false,
|
|
76
|
-
template: function subtract(attr, path, value) {
|
|
81
|
+
template: function subtract(options, attr, path, value) {
|
|
77
82
|
let operation = "";
|
|
78
83
|
let expression = "";
|
|
79
84
|
switch(attr.type) {
|
|
@@ -91,7 +96,7 @@ const UpdateOperations = {
|
|
|
91
96
|
},
|
|
92
97
|
set: {
|
|
93
98
|
canNest: false,
|
|
94
|
-
template: function set(attr, path, value) {
|
|
99
|
+
template: function set(options, attr, path, value) {
|
|
95
100
|
let operation = "";
|
|
96
101
|
let expression = "";
|
|
97
102
|
switch(attr.type) {
|
|
@@ -114,7 +119,7 @@ const UpdateOperations = {
|
|
|
114
119
|
},
|
|
115
120
|
remove: {
|
|
116
121
|
canNest: false,
|
|
117
|
-
template: function remove(attr, ...paths) {
|
|
122
|
+
template: function remove(options, attr, ...paths) {
|
|
118
123
|
let operation = "";
|
|
119
124
|
let expression = "";
|
|
120
125
|
switch(attr.type) {
|
|
@@ -142,86 +147,86 @@ const UpdateOperations = {
|
|
|
142
147
|
|
|
143
148
|
const FilterOperations = {
|
|
144
149
|
ne: {
|
|
145
|
-
template: function eq(attr, name, value) {
|
|
150
|
+
template: function eq(options, attr, name, value) {
|
|
146
151
|
return `${name} <> ${value}`;
|
|
147
152
|
},
|
|
148
153
|
strict: false,
|
|
149
154
|
},
|
|
150
155
|
eq: {
|
|
151
|
-
template: function eq(attr, name, value) {
|
|
156
|
+
template: function eq(options, attr, name, value) {
|
|
152
157
|
return `${name} = ${value}`;
|
|
153
158
|
},
|
|
154
159
|
strict: false,
|
|
155
160
|
},
|
|
156
161
|
gt: {
|
|
157
|
-
template: function gt(attr, name, value) {
|
|
162
|
+
template: function gt(options, attr, name, value) {
|
|
158
163
|
return `${name} > ${value}`;
|
|
159
164
|
},
|
|
160
165
|
strict: false
|
|
161
166
|
},
|
|
162
167
|
lt: {
|
|
163
|
-
template: function lt(attr, name, value) {
|
|
168
|
+
template: function lt(options, attr, name, value) {
|
|
164
169
|
return `${name} < ${value}`;
|
|
165
170
|
},
|
|
166
171
|
strict: false
|
|
167
172
|
},
|
|
168
173
|
gte: {
|
|
169
|
-
template: function gte(attr, name, value) {
|
|
174
|
+
template: function gte(options, attr, name, value) {
|
|
170
175
|
return `${name} >= ${value}`;
|
|
171
176
|
},
|
|
172
177
|
strict: false
|
|
173
178
|
},
|
|
174
179
|
lte: {
|
|
175
|
-
template: function lte(attr, name, value) {
|
|
180
|
+
template: function lte(options, attr, name, value) {
|
|
176
181
|
return `${name} <= ${value}`;
|
|
177
182
|
},
|
|
178
183
|
strict: false
|
|
179
184
|
},
|
|
180
185
|
between: {
|
|
181
|
-
template: function between(attr, name, value1, value2) {
|
|
186
|
+
template: function between(options, attr, name, value1, value2) {
|
|
182
187
|
return `(${name} between ${value1} and ${value2})`;
|
|
183
188
|
},
|
|
184
189
|
strict: false
|
|
185
190
|
},
|
|
186
191
|
begins: {
|
|
187
|
-
template: function begins(attr, name, value) {
|
|
192
|
+
template: function begins(options, attr, name, value) {
|
|
188
193
|
return `begins_with(${name}, ${value})`;
|
|
189
194
|
},
|
|
190
195
|
strict: false
|
|
191
196
|
},
|
|
192
197
|
exists: {
|
|
193
|
-
template: function exists(attr, name) {
|
|
198
|
+
template: function exists(options, attr, name) {
|
|
194
199
|
return `attribute_exists(${name})`;
|
|
195
200
|
},
|
|
196
201
|
strict: false
|
|
197
202
|
},
|
|
198
203
|
notExists: {
|
|
199
|
-
template: function notExists(attr, name) {
|
|
204
|
+
template: function notExists(options, attr, name) {
|
|
200
205
|
return `attribute_not_exists(${name})`;
|
|
201
206
|
},
|
|
202
207
|
strict: false
|
|
203
208
|
},
|
|
204
209
|
contains: {
|
|
205
|
-
template: function contains(attr, name, value) {
|
|
210
|
+
template: function contains(options, attr, name, value) {
|
|
206
211
|
return `contains(${name}, ${value})`;
|
|
207
212
|
},
|
|
208
213
|
strict: false
|
|
209
214
|
},
|
|
210
215
|
notContains: {
|
|
211
|
-
template: function notContains(attr, name, value) {
|
|
216
|
+
template: function notContains(options, attr, name, value) {
|
|
212
217
|
return `not contains(${name}, ${value})`;
|
|
213
218
|
},
|
|
214
219
|
strict: false
|
|
215
220
|
},
|
|
216
221
|
value: {
|
|
217
|
-
template: function(attr, name, value) {
|
|
222
|
+
template: function(options, attr, name, value) {
|
|
218
223
|
return value;
|
|
219
224
|
},
|
|
220
225
|
strict: false,
|
|
221
226
|
canNest: true,
|
|
222
227
|
},
|
|
223
228
|
name: {
|
|
224
|
-
template: function(attr, name) {
|
|
229
|
+
template: function(options, attr, name) {
|
|
225
230
|
return name;
|
|
226
231
|
},
|
|
227
232
|
strict: false,
|
|
@@ -230,7 +235,7 @@ const FilterOperations = {
|
|
|
230
235
|
};
|
|
231
236
|
|
|
232
237
|
class ExpressionState {
|
|
233
|
-
constructor({prefix
|
|
238
|
+
constructor({prefix} = {}) {
|
|
234
239
|
this.names = {};
|
|
235
240
|
this.values = {};
|
|
236
241
|
this.paths = {};
|
|
@@ -238,13 +243,9 @@ class ExpressionState {
|
|
|
238
243
|
this.impacted = {};
|
|
239
244
|
this.expression = "";
|
|
240
245
|
this.prefix = prefix || "";
|
|
241
|
-
this.singleOccurrence = singleOccurrence;
|
|
242
246
|
}
|
|
243
247
|
|
|
244
248
|
incrementName(name) {
|
|
245
|
-
if (this.singleOccurrence) {
|
|
246
|
-
return `${this.prefix}${0}`
|
|
247
|
-
}
|
|
248
249
|
if (this.counts[name] === undefined) {
|
|
249
250
|
this.counts[name] = 0;
|
|
250
251
|
}
|
|
@@ -372,12 +373,14 @@ class AttributeOperationProxy {
|
|
|
372
373
|
if (property.__is_clause__ === AttributeProxySymbol) {
|
|
373
374
|
const {paths, root, target} = property();
|
|
374
375
|
const attributeValues = [];
|
|
376
|
+
let hasNestedValue = false;
|
|
375
377
|
for (let value of values) {
|
|
376
378
|
value = target.format(value);
|
|
377
379
|
// template.length is to see if function takes value argument
|
|
378
|
-
if (template.length >
|
|
380
|
+
if (template.length > 3) {
|
|
379
381
|
if (seen.has(value)) {
|
|
380
382
|
attributeValues.push(value);
|
|
383
|
+
hasNestedValue = true;
|
|
381
384
|
} else {
|
|
382
385
|
let attributeValueName = builder.setValue(target.name, value);
|
|
383
386
|
builder.setPath(paths.json, {value, name: attributeValueName});
|
|
@@ -386,7 +389,11 @@ class AttributeOperationProxy {
|
|
|
386
389
|
}
|
|
387
390
|
}
|
|
388
391
|
|
|
389
|
-
const
|
|
392
|
+
const options = {
|
|
393
|
+
nestedValue: hasNestedValue
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const formatted = template(options, target, paths.expression, ...attributeValues);
|
|
390
397
|
builder.setImpacted(operation, paths.json);
|
|
391
398
|
if (canNest) {
|
|
392
399
|
seen.add(paths.expression);
|
package/src/schema.js
CHANGED
|
@@ -289,7 +289,7 @@ class Attribute {
|
|
|
289
289
|
|
|
290
290
|
_makeCast(name, cast) {
|
|
291
291
|
if (cast !== undefined && !CastTypes.includes(cast)) {
|
|
292
|
-
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "cast" property for attribute: "${name}". Acceptable types include ${CastTypes.join(", "
|
|
292
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "cast" property for attribute: "${name}". Acceptable types include ${CastTypes.join(", ")}`,
|
|
293
293
|
);
|
|
294
294
|
} else if (cast === AttributeTypes.string) {
|
|
295
295
|
return (val) => {
|
|
@@ -327,20 +327,34 @@ class Attribute {
|
|
|
327
327
|
_makeValidate(definition) {
|
|
328
328
|
if (typeof definition === "function") {
|
|
329
329
|
return (val) => {
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
try {
|
|
331
|
+
let reason = definition(val);
|
|
332
|
+
const isValid = !reason;
|
|
333
|
+
if (isValid) {
|
|
334
|
+
return [isValid, []];
|
|
335
|
+
} else if (typeof reason === "boolean") {
|
|
336
|
+
return [isValid, [new e.ElectroUserValidationError(this.path, "Invalid value provided")]];
|
|
337
|
+
} else {
|
|
338
|
+
return [isValid, [new e.ElectroUserValidationError(this.path, reason)]];
|
|
339
|
+
}
|
|
340
|
+
} catch(err) {
|
|
341
|
+
return [false, [new e.ElectroUserValidationError(this.path, err)]];
|
|
342
|
+
}
|
|
332
343
|
};
|
|
333
344
|
} else if (definition instanceof RegExp) {
|
|
334
345
|
return (val) => {
|
|
335
346
|
if (val === undefined) {
|
|
336
|
-
return [true,
|
|
347
|
+
return [true, []];
|
|
337
348
|
}
|
|
338
349
|
let isValid = definition.test(val);
|
|
339
|
-
let reason =
|
|
350
|
+
let reason = [];
|
|
351
|
+
if (!isValid) {
|
|
352
|
+
reason.push(new e.ElectroUserValidationError(this.path, `Invalid value for attribute "${this.path}": Failed model defined regex`));
|
|
353
|
+
}
|
|
340
354
|
return [isValid, reason];
|
|
341
355
|
};
|
|
342
356
|
} else {
|
|
343
|
-
return (
|
|
357
|
+
return () => [true, []];
|
|
344
358
|
}
|
|
345
359
|
}
|
|
346
360
|
|
|
@@ -385,15 +399,19 @@ class Attribute {
|
|
|
385
399
|
|
|
386
400
|
_isType(value) {
|
|
387
401
|
if (value === undefined) {
|
|
388
|
-
|
|
402
|
+
let reason = [];
|
|
403
|
+
if (this.required) {
|
|
404
|
+
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Value is required.`));
|
|
405
|
+
}
|
|
406
|
+
return [!this.required, reason];
|
|
389
407
|
}
|
|
390
408
|
let isTyped = false;
|
|
391
|
-
let reason =
|
|
409
|
+
let reason = [];
|
|
392
410
|
switch (this.type) {
|
|
393
411
|
case AttributeTypes.enum:
|
|
394
412
|
isTyped = this.enumArray.includes(value);
|
|
395
413
|
if (!isTyped) {
|
|
396
|
-
reason
|
|
414
|
+
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Value not found in set of acceptable values: ${u.commaSeparatedString(this.enumArray)}`));
|
|
397
415
|
}
|
|
398
416
|
break;
|
|
399
417
|
case AttributeTypes.any:
|
|
@@ -405,7 +423,7 @@ class Attribute {
|
|
|
405
423
|
default:
|
|
406
424
|
isTyped = typeof value === this.type;
|
|
407
425
|
if (!isTyped) {
|
|
408
|
-
reason
|
|
426
|
+
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Received value of type "${typeof value}", expected value of type "${this.type}"`));
|
|
409
427
|
}
|
|
410
428
|
break;
|
|
411
429
|
}
|
|
@@ -414,12 +432,12 @@ class Attribute {
|
|
|
414
432
|
|
|
415
433
|
isValid(value) {
|
|
416
434
|
try {
|
|
417
|
-
let [isTyped,
|
|
418
|
-
let [isValid, validationError] = this.validate(value);
|
|
419
|
-
let
|
|
420
|
-
return [isTyped && isValid,
|
|
435
|
+
let [isTyped, typeErrorReason] = this._isType(value);
|
|
436
|
+
let [isValid, validationError] = isTyped ? this.validate(value) : [false, []];
|
|
437
|
+
let errors = [...typeErrorReason, ...validationError].filter(value => value !== undefined);
|
|
438
|
+
return [isTyped && isValid, errors];
|
|
421
439
|
} catch (err) {
|
|
422
|
-
return [false, err
|
|
440
|
+
return [false, [err]];
|
|
423
441
|
}
|
|
424
442
|
}
|
|
425
443
|
|
|
@@ -433,10 +451,9 @@ class Attribute {
|
|
|
433
451
|
|
|
434
452
|
getValidate(value) {
|
|
435
453
|
value = this.val(value);
|
|
436
|
-
let [isValid,
|
|
454
|
+
let [isValid, validationErrors] = this.isValid(value);
|
|
437
455
|
if (!isValid) {
|
|
438
|
-
|
|
439
|
-
throw new Error(validationError);
|
|
456
|
+
throw new e.ElectroValidationError(validationErrors);
|
|
440
457
|
}
|
|
441
458
|
return value;
|
|
442
459
|
}
|
|
@@ -509,13 +526,17 @@ class MapAttribute extends Attribute {
|
|
|
509
526
|
|
|
510
527
|
_isType(value) {
|
|
511
528
|
if (value === undefined) {
|
|
512
|
-
|
|
529
|
+
let reason = [];
|
|
530
|
+
if (this.required) {
|
|
531
|
+
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Value is required.`));
|
|
532
|
+
}
|
|
533
|
+
return [!this.required, reason];
|
|
513
534
|
}
|
|
514
535
|
const valueType = getValueType(value);
|
|
515
536
|
if (valueType !== ValueTypes.object) {
|
|
516
|
-
return [false, `Invalid value type at entity path "${this.path}. Received value of type "${valueType}", expected value of type "object"`];
|
|
537
|
+
return [false, [new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path "${this.path}. Received value of type "${valueType}", expected value of type "object"`)]];
|
|
517
538
|
}
|
|
518
|
-
let reason =
|
|
539
|
+
let reason = [];
|
|
519
540
|
const [childrenAreValid, childErrors] = this._validateChildren(value);
|
|
520
541
|
if (!childrenAreValid) {
|
|
521
542
|
reason = childErrors;
|
|
@@ -526,24 +547,24 @@ class MapAttribute extends Attribute {
|
|
|
526
547
|
_validateChildren(value) {
|
|
527
548
|
const valueType = getValueType(value);
|
|
528
549
|
const attributes = this.properties.attributes;
|
|
529
|
-
|
|
550
|
+
let errors = [];
|
|
530
551
|
if (valueType === ValueTypes.object) {
|
|
531
552
|
for (const child of Object.keys(attributes)) {
|
|
532
|
-
const [isValid,
|
|
553
|
+
const [isValid, errorValues] = attributes[child].isValid(value === undefined ? value : value[child])
|
|
533
554
|
if (!isValid) {
|
|
534
|
-
errors
|
|
555
|
+
errors = [...errors, ...errorValues]
|
|
535
556
|
}
|
|
536
557
|
}
|
|
537
558
|
} else if (valueType !== ValueTypes.object) {
|
|
538
559
|
errors.push(
|
|
539
|
-
`Invalid value type at entity path: "${this.path}". Expected value to be an object to fulfill attribute type "${this.type}"`
|
|
560
|
+
new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Expected value to be an object to fulfill attribute type "${this.type}"`)
|
|
540
561
|
);
|
|
541
562
|
} else if (this.properties.hasRequiredAttributes) {
|
|
542
563
|
errors.push(
|
|
543
|
-
`Invalid value type at entity path: "${this.path}". Map attribute requires at least the properties ${u.commaSeparatedString(Object.keys(attributes))}`
|
|
564
|
+
new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Map attribute requires at least the properties ${u.commaSeparatedString(Object.keys(attributes))}`)
|
|
544
565
|
);
|
|
545
566
|
}
|
|
546
|
-
return [errors.length === 0, errors
|
|
567
|
+
return [errors.length === 0, errors];
|
|
547
568
|
}
|
|
548
569
|
|
|
549
570
|
val(value) {
|
|
@@ -560,7 +581,7 @@ class MapAttribute extends Attribute {
|
|
|
560
581
|
} else if (value && valueType !== "object" && Object.keys(value).length === 0) {
|
|
561
582
|
return getValue(value);
|
|
562
583
|
} else if (valueType !== "object") {
|
|
563
|
-
throw new
|
|
584
|
+
throw new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Expected value to be an object to fulfill attribute type "${this.type}"`);
|
|
564
585
|
}
|
|
565
586
|
|
|
566
587
|
const data = {};
|
|
@@ -643,24 +664,29 @@ class ListAttribute extends Attribute {
|
|
|
643
664
|
}
|
|
644
665
|
|
|
645
666
|
_validateArrayValue(value) {
|
|
667
|
+
const reason = [];
|
|
646
668
|
const valueType = getValueType(value);
|
|
647
669
|
if (value !== undefined && valueType !== ValueTypes.array) {
|
|
648
|
-
return [false, `Invalid value type at entity path "${this.path}. Received value of type "${valueType}", expected value of type "array"`];
|
|
670
|
+
return [false, [new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path "${this.path}. Received value of type "${valueType}", expected value of type "array"`)]];
|
|
649
671
|
} else {
|
|
650
|
-
return [true,
|
|
672
|
+
return [true, []];
|
|
651
673
|
}
|
|
652
674
|
}
|
|
653
675
|
|
|
654
676
|
_isType(value) {
|
|
655
677
|
if (value === undefined) {
|
|
656
|
-
|
|
678
|
+
let reason = [];
|
|
679
|
+
if (this.required) {
|
|
680
|
+
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Value is required.`));
|
|
681
|
+
}
|
|
682
|
+
return [!this.required, reason];
|
|
657
683
|
}
|
|
658
684
|
|
|
659
685
|
const [isValidArray, errors] = this._validateArrayValue(value);
|
|
660
686
|
if (!isValidArray) {
|
|
661
687
|
return [isValidArray, errors];
|
|
662
688
|
}
|
|
663
|
-
let reason =
|
|
689
|
+
let reason = [];
|
|
664
690
|
const [childrenAreValid, childErrors] = this._validateChildren(value);
|
|
665
691
|
if (!childrenAreValid) {
|
|
666
692
|
reason = childErrors;
|
|
@@ -673,17 +699,22 @@ class ListAttribute extends Attribute {
|
|
|
673
699
|
const errors = [];
|
|
674
700
|
if (valueType === ValueTypes.array) {
|
|
675
701
|
for (const i in value) {
|
|
676
|
-
const [isValid,
|
|
702
|
+
const [isValid, errorValues] = this.items.isValid(value[i]);
|
|
677
703
|
if (!isValid) {
|
|
678
|
-
|
|
704
|
+
for (const err of errorValues) {
|
|
705
|
+
if (err instanceof e.ElectroAttributeValidationError || err instanceof e.ElectroUserValidationError) {
|
|
706
|
+
err.index = parseInt(i);
|
|
707
|
+
}
|
|
708
|
+
errors.push(err);
|
|
709
|
+
}
|
|
679
710
|
}
|
|
680
711
|
}
|
|
681
712
|
} else {
|
|
682
713
|
errors.push(
|
|
683
|
-
`Invalid value type at entity path: "${this.path}". Expected value to be an Array to fulfill attribute type "${this.type}"`
|
|
714
|
+
new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Expected value to be an Array to fulfill attribute type "${this.type}"`)
|
|
684
715
|
);
|
|
685
716
|
}
|
|
686
|
-
return [errors.length === 0, errors
|
|
717
|
+
return [errors.length === 0, errors];
|
|
687
718
|
}
|
|
688
719
|
|
|
689
720
|
val(value) {
|
|
@@ -700,7 +731,7 @@ class ListAttribute extends Attribute {
|
|
|
700
731
|
} else if (Array.isArray(value) && value.length === 0) {
|
|
701
732
|
return value;
|
|
702
733
|
} else if (!Array.isArray(value)) {
|
|
703
|
-
throw new
|
|
734
|
+
throw new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path "${this.path}. Received value of type "${getValueType(value)}", expected value of type "array"`);
|
|
704
735
|
}
|
|
705
736
|
|
|
706
737
|
const data = [];
|
|
@@ -731,6 +762,20 @@ class SetAttribute extends Attribute {
|
|
|
731
762
|
this.items = items;
|
|
732
763
|
this.get = this._makeGet(definition.get, items);
|
|
733
764
|
this.set = this._makeSet(definition.set, items);
|
|
765
|
+
this.validate = this._makeSetValidate(definition);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
_makeSetValidate(definition) {
|
|
769
|
+
const validate = this._makeValidate(definition.validate);
|
|
770
|
+
return (value) => {
|
|
771
|
+
if (Array.isArray(value)) {
|
|
772
|
+
return validate([...value]);
|
|
773
|
+
} else if (value && value.wrapperName === 'Set') {
|
|
774
|
+
return validate([...value.values])
|
|
775
|
+
} else {
|
|
776
|
+
return validate(value);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
734
779
|
}
|
|
735
780
|
|
|
736
781
|
fromDDBSet(value) {
|
|
@@ -768,7 +813,7 @@ class SetAttribute extends Attribute {
|
|
|
768
813
|
return this._createDDBSet(value);
|
|
769
814
|
}
|
|
770
815
|
default:
|
|
771
|
-
throw new
|
|
816
|
+
throw new e.ElectroAttributeValidationError(this.path, `Invalid attribute value supplied to "set" attribute "${this.path}". Received value of type "${valueType}". Set values must be supplied as either Arrays, native JavaScript Set objects, DocumentClient Set objects, strings, or numbers.`)
|
|
772
817
|
}
|
|
773
818
|
|
|
774
819
|
}
|
|
@@ -795,7 +840,9 @@ class SetAttribute extends Attribute {
|
|
|
795
840
|
this._checkGetSet(set, "set");
|
|
796
841
|
const setter = set || ((attr) => attr);
|
|
797
842
|
return (values, siblings) => {
|
|
798
|
-
const results =
|
|
843
|
+
const results = values && values.wrapperName === 'Set'
|
|
844
|
+
? setter(values.values, siblings)
|
|
845
|
+
: setter(values, siblings)
|
|
799
846
|
if (results !== undefined) {
|
|
800
847
|
return this.toDDBSet(results);
|
|
801
848
|
}
|
|
@@ -804,10 +851,14 @@ class SetAttribute extends Attribute {
|
|
|
804
851
|
|
|
805
852
|
_isType(value) {
|
|
806
853
|
if (value === undefined) {
|
|
807
|
-
|
|
854
|
+
const reason = [];
|
|
855
|
+
if (this.required) {
|
|
856
|
+
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Value is required.`));
|
|
857
|
+
}
|
|
858
|
+
return [!this.required, reason];
|
|
808
859
|
}
|
|
809
860
|
|
|
810
|
-
let reason =
|
|
861
|
+
let reason = [];
|
|
811
862
|
const [childrenAreValid, childErrors] = this._validateChildren(value);
|
|
812
863
|
if (!childrenAreValid) {
|
|
813
864
|
reason = childErrors;
|
|
@@ -817,7 +868,7 @@ class SetAttribute extends Attribute {
|
|
|
817
868
|
|
|
818
869
|
_validateChildren(value) {
|
|
819
870
|
const valueType = getValueType(value);
|
|
820
|
-
|
|
871
|
+
let errors = [];
|
|
821
872
|
let arr = [];
|
|
822
873
|
if (valueType === ValueTypes.array) {
|
|
823
874
|
arr = value;
|
|
@@ -827,16 +878,16 @@ class SetAttribute extends Attribute {
|
|
|
827
878
|
arr = value.values;
|
|
828
879
|
} else {
|
|
829
880
|
errors.push(
|
|
830
|
-
`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}"`
|
|
881
|
+
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}"`)
|
|
831
882
|
)
|
|
832
883
|
}
|
|
833
884
|
for (const item of arr) {
|
|
834
|
-
const [isValid,
|
|
885
|
+
const [isValid, errorValues] = this.items.isValid(item);
|
|
835
886
|
if (!isValid) {
|
|
836
|
-
errors
|
|
887
|
+
errors = [...errors, ...errorValues];
|
|
837
888
|
}
|
|
838
889
|
}
|
|
839
|
-
return [errors.length === 0, errors
|
|
890
|
+
return [errors.length === 0, errors];
|
|
840
891
|
}
|
|
841
892
|
|
|
842
893
|
val(value) {
|
|
@@ -1232,11 +1283,11 @@ class Schema {
|
|
|
1232
1283
|
for (const path of paths) {
|
|
1233
1284
|
const attribute = this.traverser.getPath(path);
|
|
1234
1285
|
if (!attribute) {
|
|
1235
|
-
throw new
|
|
1286
|
+
throw new e.ElectroAttributeValidationError(path, `Attribute "${path}" does not exist on model.`);
|
|
1236
1287
|
} else if (attribute.readOnly) {
|
|
1237
|
-
throw new
|
|
1288
|
+
throw new e.ElectroAttributeValidationError(attribute.path, `Attribute "${attribute.path}" is Read-Only and cannot be removed`);
|
|
1238
1289
|
} else if (attribute.required) {
|
|
1239
|
-
throw new
|
|
1290
|
+
throw new e.ElectroAttributeValidationError(attribute.path, `Attribute "${attribute.path}" is Required and cannot be removed`);
|
|
1240
1291
|
}
|
|
1241
1292
|
}
|
|
1242
1293
|
return paths;
|
|
@@ -1251,7 +1302,7 @@ class Schema {
|
|
|
1251
1302
|
}
|
|
1252
1303
|
if (attribute.readOnly) {
|
|
1253
1304
|
// todo: #electroerror
|
|
1254
|
-
throw new
|
|
1305
|
+
throw new e.ElectroAttributeValidationError(attribute.path, `Attribute "${attribute.path}" is Read-Only and cannot be updated`);
|
|
1255
1306
|
} else {
|
|
1256
1307
|
record[path] = attribute.getValidate(value);
|
|
1257
1308
|
}
|
package/src/update.js
CHANGED
package/tsconfig.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
/* Strict Type-Checking Options */
|
|
28
28
|
"strict": true, /* Enable all strict type-checking options. */
|
|
29
|
-
|
|
29
|
+
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
|
30
30
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
|
31
31
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
|
32
32
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|