electrodb 2.2.6 → 2.3.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/README.md +255 -231
- package/index.d.ts +7 -0
- package/package.json +1 -1
- package/src/clauses.js +34 -1
- package/src/entity.js +35 -4
- package/src/types.js +18 -2
package/README.md
CHANGED
|
@@ -103,6 +103,7 @@ tasks
|
|
|
103
103
|
## Table of Contents
|
|
104
104
|
- [ElectroDB](#electrodb)
|
|
105
105
|
* [Features](#features)
|
|
106
|
+
* [Table of Contents](#table-of-contents)
|
|
106
107
|
- [Project Goals](#project-goals)
|
|
107
108
|
- [Installation](#installation)
|
|
108
109
|
- [Usage](#usage)
|
|
@@ -167,14 +168,13 @@ tasks
|
|
|
167
168
|
- [Partition Key Composite Attributes](#partition-key-composite-attributes)
|
|
168
169
|
+ [Sort Key Operations](#sort-key-operations)
|
|
169
170
|
* [Performing Queries](#performing-queries)
|
|
170
|
-
+ [
|
|
171
|
-
+ [
|
|
172
|
-
+ [Batch Get](#batch-get)
|
|
173
|
-
+ [Delete Method](#delete-method)
|
|
171
|
+
+ [Mutation Methods](#mutation-methods)
|
|
172
|
+
+ [Delete Record](#delete-record)
|
|
174
173
|
+ [Batch Write Delete Records](#batch-write-delete-records)
|
|
175
174
|
+ [Put Record](#put-record)
|
|
176
175
|
+ [Batch Write Put Records](#batch-write-put-records)
|
|
177
|
-
+ [
|
|
176
|
+
+ [Create Record](#create-record)
|
|
177
|
+
- [Update Record](#update-record)
|
|
178
178
|
- [Updates to Composite Attributes](#updates-to-composite-attributes)
|
|
179
179
|
- [Update Method: Set](#update-method--set)
|
|
180
180
|
- [Update Method: Remove](#update-method--remove)
|
|
@@ -183,11 +183,14 @@ tasks
|
|
|
183
183
|
- [Update Method: Append](#update-method--append)
|
|
184
184
|
- [Update Method: Delete](#update-method--delete)
|
|
185
185
|
- [Update Method: Data](#update-method--data)
|
|
186
|
-
|
|
186
|
+
- [Update Method: Complex Data Types](#update-method--complex-data-types)
|
|
187
|
+
+ [Patch Record](#patch-record)
|
|
188
|
+
+ [Upsert Record](#upsert-record)
|
|
187
189
|
+ [Scan Records](#scan-records)
|
|
188
190
|
+ [Remove Method](#remove-method)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
* [Query Methods](#query-methods)
|
|
192
|
+
* [Get Method](#get-method)
|
|
193
|
+
+ [Batch Get](#batch-get)
|
|
191
194
|
+ [Find Records](#find-records)
|
|
192
195
|
+ [Match Records](#match-records)
|
|
193
196
|
+ [Access Pattern Queries](#access-pattern-queries)
|
|
@@ -242,7 +245,7 @@ tasks
|
|
|
242
245
|
* [Employee App](#employee-app)
|
|
243
246
|
+ [Employee App Requirements](#employee-app-requirements)
|
|
244
247
|
+ [App Entities](#app-entities)
|
|
245
|
-
+ [
|
|
248
|
+
+ [Querying Your model](#querying-your-model)
|
|
246
249
|
- [All tasks and employee information for a given employee](#all-tasks-and-employee-information-for-a-given-employee)
|
|
247
250
|
- [Find all employees and office details for a given office](#find-all-employees-and-office-details-for-a-given-office)
|
|
248
251
|
- [Tasks for a given employee](#tasks-for-a-given-employee)
|
|
@@ -254,13 +257,10 @@ tasks
|
|
|
254
257
|
* [Shopping Mall Property Management App](#shopping-mall-property-management-app)
|
|
255
258
|
+ [Shopping Mall Requirements](#shopping-mall-requirements)
|
|
256
259
|
+ [Access Patterns are accessible on the StoreLocation](#access-patterns-are-accessible-on-the-storelocation)
|
|
257
|
-
+ [
|
|
260
|
+
+ [Create New Record](#create-new-record)
|
|
258
261
|
- [Add a new Store to the Mall](#add-a-new-store-to-the-mall)
|
|
259
|
-
+ [UPDATE Record](#update-record)
|
|
260
262
|
- [Change the Stores Lease Date](#change-the-stores-lease-date)
|
|
261
|
-
+ [GET Record](#get-record)
|
|
262
263
|
- [Retrieve a specific Store in a Mall](#retrieve-a-specific-store-in-a-mall)
|
|
263
|
-
+ [DELETE Record](#delete-record)
|
|
264
264
|
- [Remove a Store location from the Mall](#remove-a-store-location-from-the-mall)
|
|
265
265
|
+ [Query Mall Records](#query-mall-records)
|
|
266
266
|
- [All Stores in a particular mall](#all-stores-in-a-particular-mall)
|
|
@@ -273,6 +273,8 @@ tasks
|
|
|
273
273
|
- [All Latte Larrys in a particular mall building](#all-latte-larrys-in-a-particular-mall-building)
|
|
274
274
|
- [TypeScript](#typescript)
|
|
275
275
|
* [Custom Attributes](#custom-attributes)
|
|
276
|
+
+ [CustomAttributeType](#customattributetype)
|
|
277
|
+
+ [Opaque Keys](#opaque-keys)
|
|
276
278
|
* [Exported Types](#exported-types)
|
|
277
279
|
+ [QueryResponse Type](#queryresponse-type)
|
|
278
280
|
+ [EntityRecord Type](#entityrecord-type)
|
|
@@ -1561,7 +1563,7 @@ name: "your_item_name"
|
|
|
1561
1563
|
## Collections
|
|
1562
1564
|
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.
|
|
1563
1565
|
|
|
1564
|
-
> _NOTE: ElectroDB Collections use DynamoDB
|
|
1566
|
+
> _NOTE: ElectroDB Collections use a single DynamoDB query to retrieve results. One query is made to retrieve results for all Entities (the benefits of single table design), however keep in mind that DynamoDB returns all records in order of the Entity's sort key. In cases where your partition contains a large volume of items, it is possible some entities will not return items during pagination. This can be mitigated through the use of [Index Types](#index-types).
|
|
1565
1567
|
|
|
1566
1568
|
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`.
|
|
1567
1569
|
|
|
@@ -2635,123 +2637,30 @@ Queries in ***ElectroDB*** are built around the **Access Patterns** defined in t
|
|
|
2635
2637
|
|
|
2636
2638
|
The methods: Get (`get`), Create (`put`), Update (`update`), and Delete (`delete`) **require* all composite attributes described in the Entities' primary `PK` and `SK`.
|
|
2637
2639
|
|
|
2638
|
-
###
|
|
2640
|
+
### Mutation Methods
|
|
2641
|
+
DynamoDB offers three methods for updating and creating records: `put`, `update`, and `batchWrite`. For the uninitiated, all three of these methods will create an item if it doesn't exist. The difference between `put`/`batchWrite` and `update` this that a `put` will overwrite the existing item while an `update` will only modify the fields provided if the item already exists.
|
|
2639
2642
|
|
|
2640
|
-
ElectroDB
|
|
2641
|
-
|
|
2642
|
-
> _NOTE: 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._
|
|
2643
|
-
|
|
2644
|
-
### Get Method
|
|
2645
|
-
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.
|
|
2646
|
-
|
|
2647
|
-
> _NOTE: As part of ElectroDB's roll out of 1.0.0, a breaking change was made to the `get` method. Prior to 1.0.0, the `get` method would return an empty object if a record was not found. This has been changed to now return a value of `null` in this case._
|
|
2643
|
+
ElectroDB offers a few mutation methods beyond `put`, `update`, and `delete` to more ergonomically fit your use case. Below is a table that explains each ElectroDB method, which DynamoDB operation the method maps to, and a short description of the method's purpose.
|
|
2648
2644
|
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
Response Format:
|
|
2659
|
-
```typescript
|
|
2660
|
-
{
|
|
2661
|
-
data: Array<YOUR_SCHEMA>,
|
|
2662
|
-
cursor: string | undefined
|
|
2663
|
-
}
|
|
2664
|
-
```
|
|
2665
|
-
|
|
2666
|
-
Equivalent DocClient Parameters:
|
|
2667
|
-
```json
|
|
2668
|
-
{
|
|
2669
|
-
"Key": {
|
|
2670
|
-
"pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
|
|
2671
|
-
"sk": "$mallstore_1#buildingid_f34#storeid_lattelarrys"
|
|
2672
|
-
},
|
|
2673
|
-
"TableName": "YOUR_TABLE_NAME"
|
|
2674
|
-
}
|
|
2675
|
-
```
|
|
2676
|
-
|
|
2677
|
-
### Batch Get
|
|
2678
|
-
Provide all Table Index composite attributes in an array of objects to the `get` method to perform a BatchGet query.
|
|
2679
|
-
|
|
2680
|
-
> _NOTE: When performing a BatchGet the `.params()` method will return an _array_ of parameters, rather than just the parameters for one docClient query. This is because ElectroDB BatchGet allows queries larger than the docClient's limit of 100 records.
|
|
2681
|
-
|
|
2682
|
-
If the number of records you are requesting is above the BatchGet threshold of 100 records, ElectroDB will make multiple requests to DynamoDB and return the results in a single array. By default, ElectroDB will make these requests in series, one after another. If you are confident your table can handle the throughput, you can use the [Query Option](#query-options) `concurrent`. This value can be set to any number greater than zero, and will execute that number of requests simultaneously.
|
|
2683
|
-
|
|
2684
|
-
For example, 150 records (50 records over the DynamoDB maximum):
|
|
2645
|
+
ElectroDB Method | DynamoDB Method | Purpose
|
|
2646
|
+
------------------------- | ---------------------- | -------------------
|
|
2647
|
+
[put](#put-record) | `put`, `batchWrite` | Creates or overwrites an existing item with the values provided
|
|
2648
|
+
[create](#create-record) | `put` | Creates an item if the item does not currently exist, or throws if the item exists
|
|
2649
|
+
[upsert](#upsert-record) | `update` | Upsert is similar to `put` in that it will create a record if one does not exist, except `upsert` perform an update if that record already exists.
|
|
2650
|
+
[update](#update-record) | `update` | Performs update on an existing record or creates a new record per the DynamoDB spec ([read more here](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html))
|
|
2651
|
+
[patch](#patch-record) | `update` | Performs an update on existing item or throws if that item does not already exist.however
|
|
2652
|
+
[delete](#delete-record) | `delete`, `batchWrite` | Deletes an item regardless of whether or not the specified item exists
|
|
2653
|
+
[remove](#remove-record) | `delete` | Deletes an item or throws if the item does not currently exist
|
|
2685
2654
|
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
If you set the [Query Option](#query-options) `concurrent` to `2`, ElectroDB will execute a BatchGet request of 100 records, and another BatchGet request for 50 records without waiting for the first request to finish.
|
|
2689
|
-
|
|
2690
|
-
It is important to consider your Table's throughput considerations when setting this value.
|
|
2691
|
-
|
|
2692
|
-
Example:
|
|
2693
|
-
```javascript
|
|
2694
|
-
let [results, unprocessed] = await StoreLocations.get([
|
|
2695
|
-
{
|
|
2696
|
-
storeId: "LatteLarrys",
|
|
2697
|
-
mallId: "EastPointe",
|
|
2698
|
-
buildingId: "F34",
|
|
2699
|
-
cityId: "Atlanta1"
|
|
2700
|
-
},
|
|
2701
|
-
{
|
|
2702
|
-
storeId: "MochaJoes",
|
|
2703
|
-
mallId: "WestEnd",
|
|
2704
|
-
buildingId: "A21",
|
|
2705
|
-
cityId: "Madison2"
|
|
2706
|
-
}
|
|
2707
|
-
]).go({concurrent: 1}); // `concurrent` value is optional and default's to `1`
|
|
2708
|
-
```
|
|
2709
|
-
|
|
2710
|
-
Response Format:
|
|
2711
|
-
```typescript
|
|
2712
|
-
{
|
|
2713
|
-
data: Array<YOUR_SCHEMA>,
|
|
2714
|
-
unprocessed: Array<YOUR_COMPOSITE_ATTRIBUTES>
|
|
2715
|
-
}
|
|
2716
|
-
```
|
|
2717
|
-
|
|
2718
|
-
Equivalent DocClient Parameters:
|
|
2719
|
-
```json
|
|
2720
|
-
{
|
|
2721
|
-
"RequestItems": {
|
|
2722
|
-
"YOUR_TABLE_NAME": {
|
|
2723
|
-
"Keys": [
|
|
2724
|
-
{
|
|
2725
|
-
"pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
|
|
2726
|
-
"sk": "$mallstore_1#buildingid_f34#storeid_lattelarrys"
|
|
2727
|
-
},
|
|
2728
|
-
{
|
|
2729
|
-
"pk": "$mallstoredirectory#cityid_madison2#mallid_westend",
|
|
2730
|
-
"sk": "$mallstore_1#buildingid_a21#storeid_mochajoes"
|
|
2731
|
-
}
|
|
2732
|
-
]
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
}
|
|
2736
|
-
```
|
|
2737
|
-
|
|
2738
|
-
The two-dimensional array returned by batch get most easily used when deconstructed into two variables, in the above case: `results` and `unprocessed`.
|
|
2739
|
-
|
|
2740
|
-
The `results` array are records that were returned DynamoDB as `Responses` on the BatchGet query. They will appear in the same format as other ElectroDB queries.
|
|
2741
|
-
|
|
2742
|
-
> _NOTE: By default ElectroDB will return items without concern for order. If the order returned by ElectroDB must match the order provided, the [query option](#query-options) `preserveBatchOrder` can be used. When enabled, ElectroDB will ensure the order returned by a batchGet will be the same as the order provided. When enabled, if a record is returned from DynamoDB as "unprocessed" ([read more here](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html)), ElectroDB will return a null value at that index._
|
|
2743
|
-
|
|
2744
|
-
Elements of the `unprocessed` array are unlike results received from a query. Instead of containing all the attributes of a record, an unprocessed record only includes the composite attributes defined in the Table Index. This is in keeping with DynamoDB's practice of returning only Keys in the case of unprocessed records. For convenience, ElectroDB will return these keys as composite attributes, but you can pass the [query option](#query-options) `{unprocessed:"raw"}` override this behavior and return the Keys as they came from DynamoDB.
|
|
2745
|
-
|
|
2746
|
-
### Delete Method
|
|
2655
|
+
### Delete Record
|
|
2747
2656
|
Provide all Table Index composite attributes in an object to the `delete` method to delete a record.
|
|
2748
2657
|
|
|
2749
2658
|
Example:
|
|
2750
2659
|
```javascript
|
|
2751
2660
|
await StoreLocations.delete({
|
|
2752
|
-
storeId: "LatteLarrys",
|
|
2753
|
-
mallId: "EastPointe",
|
|
2754
|
-
buildingId: "F34",
|
|
2661
|
+
storeId: "LatteLarrys",
|
|
2662
|
+
mallId: "EastPointe",
|
|
2663
|
+
buildingId: "F34",
|
|
2755
2664
|
cityId: "Atlanta1"
|
|
2756
2665
|
}).go();
|
|
2757
2666
|
```
|
|
@@ -2794,18 +2703,18 @@ Example:
|
|
|
2794
2703
|
```javascript
|
|
2795
2704
|
let unprocessed = await StoreLocations.delete([
|
|
2796
2705
|
{
|
|
2797
|
-
storeId: "LatteLarrys",
|
|
2798
|
-
mallId: "EastPointe",
|
|
2799
|
-
buildingId: "F34",
|
|
2706
|
+
storeId: "LatteLarrys",
|
|
2707
|
+
mallId: "EastPointe",
|
|
2708
|
+
buildingId: "F34",
|
|
2800
2709
|
cityId: "LosAngeles1"
|
|
2801
2710
|
},
|
|
2802
2711
|
{
|
|
2803
|
-
storeId: "MochaJoes",
|
|
2804
|
-
mallId: "EastPointe",
|
|
2805
|
-
buildingId: "F35",
|
|
2712
|
+
storeId: "MochaJoes",
|
|
2713
|
+
mallId: "EastPointe",
|
|
2714
|
+
buildingId: "F35",
|
|
2806
2715
|
cityId: "LosAngeles1"
|
|
2807
2716
|
}
|
|
2808
|
-
]).go({concurrent: 1}); // `concurrent` value is optional and default's to `1`
|
|
2717
|
+
]).go({concurrent: 1}); // `concurrent` value is optional and default's to `1`
|
|
2809
2718
|
```
|
|
2810
2719
|
|
|
2811
2720
|
Response Format:
|
|
@@ -2844,7 +2753,7 @@ Equivalent DocClient Parameters:
|
|
|
2844
2753
|
Elements of the `unprocessed` array are unlike results received from a query. Instead of containing all the attributes of a record, an unprocessed record only includes the composite attributes defined in the Table Index. This is in keeping with DynamoDB's practice of returning only Keys in the case of unprocessed records. For convenience, ElectroDB will return these keys as composite attributes, but you can pass the [query option](#query-options) `{unprocessed:"raw"}` override this behavior and return the Keys as they came from DynamoDB.
|
|
2845
2754
|
|
|
2846
2755
|
### Put Record
|
|
2847
|
-
Provide all *required* Attributes as defined in the model to create a new record. **ElectroDB** will enforce any defined validations, defaults, casting, and field aliasing. A Put operation will trigger the `default`, and `set` attribute callbacks when writing to DynamoDB. By default, after performing a `put()` or `create()` operation, ElectroDB will format and return the record through the same process as a Get/Query. This process will invoke the `get` callback on all included attributes. If this behaviour is not desired, use the [Query Option](#query-options) `response:"none"` to return a null value.
|
|
2756
|
+
Provide all *required* Attributes as defined in the model to create a new record. **ElectroDB** will enforce any defined validations, defaults, casting, and field aliasing. A Put operation will trigger the `default`, and `set` attribute callbacks when writing to DynamoDB. By default, after performing a `put()` or `create()` operation, ElectroDB will format and return the record through the same process as a Get/Query. This process will invoke the `get` callback on all included attributes. If this behaviour is not desired, use the [Query Option](#query-options) `response:"none"` to return a null value.
|
|
2848
2757
|
|
|
2849
2758
|
Note: This example includes an optional conditional expression
|
|
2850
2759
|
|
|
@@ -3012,13 +2921,76 @@ Equivalent DocClient Parameters:
|
|
|
3012
2921
|
|
|
3013
2922
|
Elements of the `unprocessed` array are unlike results received from a query. Instead of containing all the attributes of a record, an unprocessed record only includes the composite attributes defined in the Table Index. This is in keeping with DynamoDB's practice of returning only Keys in the case of unprocessed records. For convenience, ElectroDB will return these keys as composite attributes, but you can pass the [query option](#query-options) `{unprocessed:"raw"}` override this behavior and return the Keys as they came from DynamoDB.
|
|
3014
2923
|
|
|
3015
|
-
###
|
|
2924
|
+
### Create Record
|
|
2925
|
+
|
|
2926
|
+
In DynamoDB, `put` operations by default will overwrite a record if record being updated does not exist. In **_ElectroDB_**, the `create` method will utilize the `attribute_not_exists()` parameter dynamically to ensure records are only "created" and not overwritten when inserting new records into the table.
|
|
2927
|
+
|
|
2928
|
+
A Put operation will trigger the `default`, and `set` attribute callbacks when writing to DynamoDB. By default, after writing to DynamoDB, ElectroDB will format and return the record through the same process as a Get/Query, which will invoke the `get` callback on all included attributes. If this behaviour is not desired, use the [Query Option](#query-options) `response:"none"` to return a null value.
|
|
2929
|
+
|
|
2930
|
+
Example:
|
|
2931
|
+
```javascript
|
|
2932
|
+
await StoreLocations
|
|
2933
|
+
.create({
|
|
2934
|
+
cityId: "Atlanta1",
|
|
2935
|
+
storeId: "LatteLarrys",
|
|
2936
|
+
mallId: "EastPointe",
|
|
2937
|
+
buildingId: "BuildingA1",
|
|
2938
|
+
unitId: "B47",
|
|
2939
|
+
category: "food/coffee",
|
|
2940
|
+
leaseEndDate: "2020-03-22",
|
|
2941
|
+
rent: "4500.00"
|
|
2942
|
+
})
|
|
2943
|
+
.where((attr, op) => op.eq(attr.rent, "4500.00"))
|
|
2944
|
+
.go()
|
|
2945
|
+
```
|
|
2946
|
+
|
|
2947
|
+
Response Format:
|
|
2948
|
+
```typescript
|
|
2949
|
+
{
|
|
2950
|
+
data: { YOUR_SCHEMA }
|
|
2951
|
+
}
|
|
2952
|
+
```
|
|
2953
|
+
|
|
2954
|
+
Equivalent DocClient Parameters:
|
|
2955
|
+
```json
|
|
2956
|
+
{
|
|
2957
|
+
"Item": {
|
|
2958
|
+
"cityId": "Atlanta1",
|
|
2959
|
+
"mallId": "EastPointe",
|
|
2960
|
+
"storeId": "LatteLarrys",
|
|
2961
|
+
"buildingId": "BuildingA1",
|
|
2962
|
+
"unitId": "B47",
|
|
2963
|
+
"category": "food/coffee",
|
|
2964
|
+
"leaseEndDate": "2020-03-22",
|
|
2965
|
+
"rent": "4500.00",
|
|
2966
|
+
"discount": "0.00",
|
|
2967
|
+
"pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
|
|
2968
|
+
"sk": "$mallstore_1#buildingid_buildinga1#storeid_lattelarrys",
|
|
2969
|
+
"gis1pk": "$mallstoredirectory#mallid_eastpointe",
|
|
2970
|
+
"gsi1sk": "$mallstore_1#buildingid_buildinga1#unitid_b47",
|
|
2971
|
+
"gis2pk": "$mallstoredirectory#storeid_lattelarrys",
|
|
2972
|
+
"gsi2sk": "$mallstore_1#leaseenddate_2020-03-22",
|
|
2973
|
+
"__edb_e__": "MallStore",
|
|
2974
|
+
"__edb_v__": "1"
|
|
2975
|
+
},
|
|
2976
|
+
"TableName": "StoreDirectory",
|
|
2977
|
+
"ConditionExpression": "attribute_not_exists(pk) AND attribute_not_exists(sk) AND #rent = :rent_w1",
|
|
2978
|
+
"ExpressionAttributeNames": {
|
|
2979
|
+
"#rent": "rent"
|
|
2980
|
+
},
|
|
2981
|
+
"ExpressionAttributeValues": {
|
|
2982
|
+
":rent_w1": "4500.00"
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
```
|
|
2986
|
+
|
|
2987
|
+
#### Update Record
|
|
3016
2988
|
|
|
3017
|
-
Update Methods are available **_after_** the method `update()` is called, and allow you to perform alter an item stored dynamodb.
|
|
2989
|
+
Update Methods are available **_after_** the method `update()` is called, and allow you to perform alter an item stored dynamodb. Each Update Method corresponds to a [DynamoDB UpdateExpression clause](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html).
|
|
3018
2990
|
|
|
3019
|
-
> ElectroDB will validate an attribute's type when performing an operation (e.g. that the `subtract()` method can only be performed on numbers), but will defer checking the logical validity your update operation to the DocumentClient. If your query performs multiple mutations on a single attribute, or perform other illogical operations given nature of an item/attribute, ElectroDB will not validate these edge cases and instead will simply pass back any error(s) thrown by the Document Client.
|
|
2991
|
+
> _NOTE: ElectroDB will validate an attribute's type when performing an operation (e.g. that the `subtract()` method can only be performed on numbers), but will defer checking the logical validity your update operation to the DocumentClient. For example, If your query performs multiple mutations on a single attribute, or perform other illogical operations given nature of an item/attribute, ElectroDB will not validate these edge cases and instead will simply pass back any error(s) thrown by the Document Client._
|
|
3020
2992
|
|
|
3021
|
-
Update Method
|
|
2993
|
+
Update/Patch Method | Attribute Types | Parameter
|
|
3022
2994
|
-------------------------------------- | ------------------------------------------------------------ | ---------
|
|
3023
2995
|
[set](#update-method-set) | `number` `string` `boolean` `enum` `map` `list` `set` `any` | `object`
|
|
3024
2996
|
[remove](#update-method-remove) | `number` `string` `boolean` `enum` `map` `list` `set` `any` | `array`
|
|
@@ -3028,9 +3000,13 @@ Update Method | Attribute Types
|
|
|
3028
3000
|
[delete](#update-method-delete) | `any` `set` | `object`
|
|
3029
3001
|
[data](#update-method-data) | `*` | `callback`
|
|
3030
3002
|
|
|
3003
|
+
The methods above can be used (and reused) in a chain to form update parameters, when finished with `.params()` or `.go()` terminal. If your application requires the update method to return values related to the update (e.g. via the `ReturnValues` DocumentClient parameters), you can use the [Query Option](#query-options) `{response: "none" | "all_old" | "updated_old" | "all_new" | "updated_new"}` with the value that matches your need. By default, the Update operation returns an empty object when using `.go()`.
|
|
3004
|
+
|
|
3005
|
+
> _NOTE: The DynamoDB method `update` will create an item if one does not exist. Because updates have reduced attribute validations when compared to `put`, the practical ramifications of this is that an `update` can create a record without all the attributes you'd expect from a newly created item. Depending on your project's unique needs, the methods [patch](#patch-record) or [upsert](#upsert) may be a better fit._
|
|
3006
|
+
|
|
3031
3007
|
#### Updates to Composite Attributes
|
|
3032
3008
|
|
|
3033
|
-
ElectroDB adds some constraints to update calls to prevent the accidental loss of data. If an access pattern is defined with multiple composite attributes, then ElectroDB ensure the attributes cannot be updated individually. If an attribute involved in an index composite is updated, then the index key also must be updated, and if the whole key cannot be formed by the attributes supplied to the update, then it cannot create a composite key without overwriting the old data.
|
|
3009
|
+
ElectroDB adds some constraints to update calls to prevent the accidental loss of data. If an access pattern is defined with multiple composite attributes, then ElectroDB ensure the attributes cannot be updated individually. If an attribute involved in an index composite is updated, then the index key also must be updated, and if the whole key cannot be formed by the attributes supplied to the update, then it cannot create a composite key without overwriting the old data.
|
|
3034
3010
|
|
|
3035
3011
|
This example shows why a partial update to a composite key is prevented by ElectroDB:
|
|
3036
3012
|
|
|
@@ -3057,9 +3033,9 @@ The above secondary index definition would generate the following index keys:
|
|
|
3057
3033
|
}
|
|
3058
3034
|
```
|
|
3059
3035
|
|
|
3060
|
-
If a user attempts to update the attribute `attr2`, then ElectroDB has no way of knowing value of the attribute `attr3` or if forming the composite key without it would overwrite its value. The same problem exists if a user were to update `attr3`, ElectroDB cannot update the key without knowing each composite attribute's value.
|
|
3036
|
+
If a user attempts to update the attribute `attr2`, then ElectroDB has no way of knowing value of the attribute `attr3` or if forming the composite key without it would overwrite its value. The same problem exists if a user were to update `attr3`, ElectroDB cannot update the key without knowing each composite attribute's value.
|
|
3061
3037
|
|
|
3062
|
-
In the event that a secondary index includes composite values from the table's primary index, ElectroDB will draw from the values supplied for the update key to address index gaps in the secondary index. For example:
|
|
3038
|
+
In the event that a secondary index includes composite values from the table's primary index, ElectroDB will draw from the values supplied for the update key to address index gaps in the secondary index. For example:
|
|
3063
3039
|
|
|
3064
3040
|
For the defined indexes:
|
|
3065
3041
|
|
|
@@ -3124,9 +3100,9 @@ Equivalent DocClient Parameters:
|
|
|
3124
3100
|
":attr2_u0": "value2"
|
|
3125
3101
|
},
|
|
3126
3102
|
"TableName": "YOUR_TABLE_NAME",
|
|
3127
|
-
"Key": {
|
|
3128
|
-
"pk": "$service#attr1_value1",
|
|
3129
|
-
"sk": "$entity_version#attr2_value2"
|
|
3103
|
+
"Key": {
|
|
3104
|
+
"pk": "$service#attr1_value1",
|
|
3105
|
+
"sk": "$entity_version#attr2_value2"
|
|
3130
3106
|
}
|
|
3131
3107
|
}
|
|
3132
3108
|
```
|
|
@@ -3177,7 +3153,7 @@ Equivalent DocClient Parameters:
|
|
|
3177
3153
|
|
|
3178
3154
|
The `remove()` method will accept all attributes defined on the model. Unlike most other update methods, the `remove()` method accepts an array with the names of the attributes that should be removed.
|
|
3179
3155
|
|
|
3180
|
-
> _NOTE that the attribute property `required` functions as a sort of `NOT NULL` flag. Because of this, if a property exists as `required:true` it will not be possible to _remove_ that property in particular. If the attribute is a property is on "map", and the "map" is not required, then the "map" _can_ be removed._
|
|
3156
|
+
> _NOTE that the attribute property `required` functions as a sort of `NOT NULL` flag. Because of this, if a property exists as `required:true` it will not be possible to _remove_ that property in particular. If the attribute is a property is on "map", and the "map" is not required, then the "map" _can_ be removed._
|
|
3181
3157
|
|
|
3182
3158
|
Example:
|
|
3183
3159
|
```javascript
|
|
@@ -3218,7 +3194,7 @@ Equivalent DocClient Parameters:
|
|
|
3218
3194
|
|
|
3219
3195
|
The `add()` method will accept attributes with type `number`, `set`, and `any` defined on the model. In the case of a `number` attribute, provide a number to _add_ to the existing attribute's value on the item.
|
|
3220
3196
|
|
|
3221
|
-
If the attribute is defined as `any`, the syntax compatible with the attribute type `set` will be used. For this reason, do not use the attribute type `any` to represent a `number`.
|
|
3197
|
+
If the attribute is defined as `any`, the syntax compatible with the attribute type `set` will be used. For this reason, do not use the attribute type `any` to represent a `number`.
|
|
3222
3198
|
|
|
3223
3199
|
Example:
|
|
3224
3200
|
```javascript
|
|
@@ -3315,7 +3291,7 @@ await StoreLocations
|
|
|
3315
3291
|
.update({cityId, mallId, storeId, buildingId})
|
|
3316
3292
|
.append({
|
|
3317
3293
|
rentalAgreement: [{
|
|
3318
|
-
type: "ammendment",
|
|
3294
|
+
type: "ammendment",
|
|
3319
3295
|
detail: "no soup for you"
|
|
3320
3296
|
}]
|
|
3321
3297
|
})
|
|
@@ -3501,7 +3477,7 @@ Equivalent DocClient Parameters:
|
|
|
3501
3477
|
}
|
|
3502
3478
|
```
|
|
3503
3479
|
|
|
3504
|
-
|
|
3480
|
+
#### Update Method: Complex Data Types
|
|
3505
3481
|
|
|
3506
3482
|
ElectroDB supports updating DynamoDB's complex types (`list`, `map`, `set`) with all of its Update Methods.
|
|
3507
3483
|
|
|
@@ -3522,7 +3498,7 @@ await StoreLocations
|
|
|
3522
3498
|
.set({'mapAttribute.mapProperty': "value"})
|
|
3523
3499
|
.go();
|
|
3524
3500
|
|
|
3525
|
-
// via Data Method
|
|
3501
|
+
// via Data Method
|
|
3526
3502
|
await StoreLocations
|
|
3527
3503
|
.update({cityId, mallId, storeId, buildingId})
|
|
3528
3504
|
.data(({mapAttribute}, {set}) => set(mapAttribute.mapProperty, "value"))
|
|
@@ -3540,7 +3516,7 @@ await StoreLocations
|
|
|
3540
3516
|
.remove(['listAttribute[0]'])
|
|
3541
3517
|
.go();
|
|
3542
3518
|
|
|
3543
|
-
// via Data Method
|
|
3519
|
+
// via Data Method
|
|
3544
3520
|
await StoreLocations
|
|
3545
3521
|
.update({cityId, mallId, storeId, buildingId})
|
|
3546
3522
|
.data(({listAttribute}, {remove}) => remove(listAttribute[0]))
|
|
@@ -3553,9 +3529,9 @@ All other complex structures are simply variations on the above two examples.
|
|
|
3553
3529
|
|
|
3554
3530
|
```javascript
|
|
3555
3531
|
// Set values must use the DocumentClient to create a `set`
|
|
3556
|
-
const newSetValue = StoreLocations.client.createSet("setItemValue");
|
|
3532
|
+
const newSetValue = StoreLocations.client.createSet("setItemValue");
|
|
3557
3533
|
|
|
3558
|
-
// via Data Method
|
|
3534
|
+
// via Data Method
|
|
3559
3535
|
await StoreLocations
|
|
3560
3536
|
.update({cityId, mallId, storeId, buildingId})
|
|
3561
3537
|
.add({'listAttribute[1].setAttribute': newSetValue})
|
|
@@ -3569,6 +3545,51 @@ await StoreLocations
|
|
|
3569
3545
|
.go();
|
|
3570
3546
|
```
|
|
3571
3547
|
|
|
3548
|
+
### Patch Record
|
|
3549
|
+
|
|
3550
|
+
```javascript
|
|
3551
|
+
await entity.patch({ attr1: "value1", attr2: "value2" })
|
|
3552
|
+
.set({ attr4: "value4" })
|
|
3553
|
+
.go();
|
|
3554
|
+
```
|
|
3555
|
+
|
|
3556
|
+
Response Format:
|
|
3557
|
+
```typescript
|
|
3558
|
+
{
|
|
3559
|
+
data: { YOUR_SCHEMA }
|
|
3560
|
+
}
|
|
3561
|
+
```
|
|
3562
|
+
|
|
3563
|
+
Equivalent DocClient Parameters:
|
|
3564
|
+
```json
|
|
3565
|
+
{
|
|
3566
|
+
"UpdateExpression": "SET #attr4 = :attr4_u0, #gsi1sk = :gsi1sk_u0, #attr1 = :attr1_u0, #attr2 = :attr2_u0",
|
|
3567
|
+
"ExpressionAttributeNames": {
|
|
3568
|
+
"#attr4": "attr4",
|
|
3569
|
+
"#gsi1sk": "gsi1sk",
|
|
3570
|
+
"#attr1": "attr1",
|
|
3571
|
+
"#attr2": "attr2"
|
|
3572
|
+
},
|
|
3573
|
+
"ExpressionAttributeValues": {
|
|
3574
|
+
":attr4_u0": "value6",
|
|
3575
|
+
// This index was successfully built
|
|
3576
|
+
":gsi1sk_u0": "$update-edgecases_1#attr2_value2#attr4_value6",
|
|
3577
|
+
":attr1_u0": "value1",
|
|
3578
|
+
":attr2_u0": "value2"
|
|
3579
|
+
},
|
|
3580
|
+
"TableName": "YOUR_TABLE_NAME",
|
|
3581
|
+
"Key": {
|
|
3582
|
+
"pk": "$service#attr1_value1",
|
|
3583
|
+
"sk": "$entity_version#attr2_value2"
|
|
3584
|
+
},
|
|
3585
|
+
"ConditionExpression": "attribute_exists(pk) AND attribute_exists(sk)"
|
|
3586
|
+
}
|
|
3587
|
+
```
|
|
3588
|
+
|
|
3589
|
+
### Upsert Record
|
|
3590
|
+
|
|
3591
|
+
The `upsert` method is another ElectroDB exclusive method. Upsert is similar to the [put-method](#put-record) in that it will create a record if one does not exist. Unlike the `put` method, however, `upsert` perform an update if that record already exists.
|
|
3592
|
+
|
|
3572
3593
|
### Scan Records
|
|
3573
3594
|
When scanning for rows, you can use filters the same as you would any query. For more information on filters, see the [Where](#where) section.
|
|
3574
3595
|
|
|
@@ -3578,7 +3599,7 @@ Example:
|
|
|
3578
3599
|
```javascript
|
|
3579
3600
|
await StoreLocations.scan
|
|
3580
3601
|
.where(({category}, {eq}) => `
|
|
3581
|
-
${eq(category, "food/coffee")} OR ${eq(category, "spite store")}
|
|
3602
|
+
${eq(category, "food/coffee")} OR ${eq(category, "spite store")}
|
|
3582
3603
|
`)
|
|
3583
3604
|
.where(({leaseEndDate}, {between}) => `
|
|
3584
3605
|
${between(leaseEndDate, "2020-03", "2020-04")}
|
|
@@ -3625,9 +3646,9 @@ A convenience method for `delete` with ConditionExpression that the item being d
|
|
|
3625
3646
|
|
|
3626
3647
|
```javascript
|
|
3627
3648
|
await StoreLocations.remove({
|
|
3628
|
-
storeId: "LatteLarrys",
|
|
3629
|
-
mallId: "EastPointe",
|
|
3630
|
-
buildingId: "F34",
|
|
3649
|
+
storeId: "LatteLarrys",
|
|
3650
|
+
mallId: "EastPointe",
|
|
3651
|
+
buildingId: "F34",
|
|
3631
3652
|
cityId: "Atlanta1"
|
|
3632
3653
|
}).go();
|
|
3633
3654
|
```
|
|
@@ -3646,119 +3667,123 @@ Equivalent DocClient Parameters:
|
|
|
3646
3667
|
"pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
|
|
3647
3668
|
"sk": "$mallstore_1#buildingid_f34#storeid_lattelarrys"
|
|
3648
3669
|
},
|
|
3649
|
-
"TableName": "YOUR_TABLE_TABLE"
|
|
3670
|
+
"TableName": "YOUR_TABLE_TABLE",
|
|
3650
3671
|
"ConditionExpression": "attribute_exists(pk) AND attribute_exists(sk)"
|
|
3651
3672
|
}
|
|
3652
3673
|
```
|
|
3653
3674
|
|
|
3654
|
-
|
|
3675
|
+
In DynamoDB, `update` operations by default will insert a record if record being updated does not exist. In **_ElectroDB_**, the `patch` method will utilize the `attribute_exists()` parameter dynamically to ensure records are only "patched" and not inserted when updating.
|
|
3676
|
+
|
|
3677
|
+
For more detail on how to use the `patch()` method, see the section [Update Record](#update-record) to see all the transferable requirements and capabilities available to `patch()`.
|
|
3678
|
+
|
|
3679
|
+
## Query Methods
|
|
3680
|
+
|
|
3681
|
+
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)
|
|
3682
|
+
|
|
3683
|
+
> _NOTE: 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._
|
|
3684
|
+
|
|
3685
|
+
## Get Method
|
|
3686
|
+
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.
|
|
3687
|
+
|
|
3688
|
+
> _NOTE: As part of ElectroDB's roll out of 1.0.0, a breaking change was made to the `get` method. Prior to 1.0.0, the `get` method would return an empty object if a record was not found. This has been changed to now return a value of `null` in this case._
|
|
3655
3689
|
|
|
3690
|
+
Example:
|
|
3656
3691
|
```javascript
|
|
3657
|
-
await
|
|
3658
|
-
|
|
3659
|
-
|
|
3692
|
+
let results = await StoreLocations.get({
|
|
3693
|
+
storeId: "LatteLarrys",
|
|
3694
|
+
mallId: "EastPointe",
|
|
3695
|
+
buildingId: "F34",
|
|
3696
|
+
cityId: "Atlanta1"
|
|
3697
|
+
}).go();
|
|
3660
3698
|
```
|
|
3661
|
-
|
|
3662
3699
|
Response Format:
|
|
3663
3700
|
```typescript
|
|
3664
3701
|
{
|
|
3665
|
-
data:
|
|
3702
|
+
data: Array<YOUR_SCHEMA>,
|
|
3703
|
+
cursor: string | undefined
|
|
3666
3704
|
}
|
|
3667
3705
|
```
|
|
3668
3706
|
|
|
3669
3707
|
Equivalent DocClient Parameters:
|
|
3670
3708
|
```json
|
|
3671
3709
|
{
|
|
3672
|
-
"UpdateExpression": "SET #attr4 = :attr4_u0, #gsi1sk = :gsi1sk_u0, #attr1 = :attr1_u0, #attr2 = :attr2_u0",
|
|
3673
|
-
"ExpressionAttributeNames": {
|
|
3674
|
-
"#attr4": "attr4",
|
|
3675
|
-
"#gsi1sk": "gsi1sk",
|
|
3676
|
-
"#attr1": "attr1",
|
|
3677
|
-
"#attr2": "attr2"
|
|
3678
|
-
},
|
|
3679
|
-
"ExpressionAttributeValues": {
|
|
3680
|
-
":attr4_u0": "value6",
|
|
3681
|
-
// This index was successfully built
|
|
3682
|
-
":gsi1sk_u0": "$update-edgecases_1#attr2_value2#attr4_value6",
|
|
3683
|
-
":attr1_u0": "value1",
|
|
3684
|
-
":attr2_u0": "value2"
|
|
3685
|
-
},
|
|
3686
|
-
"TableName": "YOUR_TABLE_NAME",
|
|
3687
3710
|
"Key": {
|
|
3688
|
-
"pk": "$
|
|
3689
|
-
"sk": "$
|
|
3711
|
+
"pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
|
|
3712
|
+
"sk": "$mallstore_1#buildingid_f34#storeid_lattelarrys"
|
|
3690
3713
|
},
|
|
3691
|
-
"
|
|
3714
|
+
"TableName": "YOUR_TABLE_NAME"
|
|
3692
3715
|
}
|
|
3693
3716
|
```
|
|
3694
3717
|
|
|
3695
|
-
|
|
3718
|
+
### Batch Get
|
|
3719
|
+
Provide all Table Index composite attributes in an array of objects to the `get` method to perform a BatchGet query.
|
|
3696
3720
|
|
|
3697
|
-
|
|
3721
|
+
> _NOTE: When performing a BatchGet the `.params()` method will return an _array_ of parameters, rather than just the parameters for one docClient query. This is because ElectroDB BatchGet allows queries larger than the docClient's limit of 100 records.
|
|
3698
3722
|
|
|
3699
|
-
|
|
3723
|
+
If the number of records you are requesting is above the BatchGet threshold of 100 records, ElectroDB will make multiple requests to DynamoDB and return the results in a single array. By default, ElectroDB will make these requests in series, one after another. If you are confident your table can handle the throughput, you can use the [Query Option](#query-options) `concurrent`. This value can be set to any number greater than zero, and will execute that number of requests simultaneously.
|
|
3700
3724
|
|
|
3701
|
-
|
|
3725
|
+
For example, 150 records (50 records over the DynamoDB maximum):
|
|
3702
3726
|
|
|
3703
|
-
|
|
3727
|
+
The default value of `concurrent` will be `1`. ElectroDB will execute a BatchGet request of 100, then after that request has responded, make another BatchGet request for 50 records.
|
|
3728
|
+
|
|
3729
|
+
If you set the [Query Option](#query-options) `concurrent` to `2`, ElectroDB will execute a BatchGet request of 100 records, and another BatchGet request for 50 records without waiting for the first request to finish.
|
|
3730
|
+
|
|
3731
|
+
It is important to consider your Table's throughput considerations when setting this value.
|
|
3704
3732
|
|
|
3705
3733
|
Example:
|
|
3706
3734
|
```javascript
|
|
3707
|
-
await StoreLocations
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3735
|
+
let [results, unprocessed] = await StoreLocations.get([
|
|
3736
|
+
{
|
|
3737
|
+
storeId: "LatteLarrys",
|
|
3738
|
+
mallId: "EastPointe",
|
|
3739
|
+
buildingId: "F34",
|
|
3740
|
+
cityId: "Atlanta1"
|
|
3741
|
+
},
|
|
3742
|
+
{
|
|
3743
|
+
storeId: "MochaJoes",
|
|
3744
|
+
mallId: "WestEnd",
|
|
3745
|
+
buildingId: "A21",
|
|
3746
|
+
cityId: "Madison2"
|
|
3747
|
+
}
|
|
3748
|
+
]).go({concurrent: 1}); // `concurrent` value is optional and default's to `1`
|
|
3720
3749
|
```
|
|
3721
3750
|
|
|
3722
3751
|
Response Format:
|
|
3723
3752
|
```typescript
|
|
3724
3753
|
{
|
|
3725
|
-
data:
|
|
3754
|
+
data: Array<YOUR_SCHEMA>,
|
|
3755
|
+
unprocessed: Array<YOUR_COMPOSITE_ATTRIBUTES>
|
|
3726
3756
|
}
|
|
3727
3757
|
```
|
|
3728
3758
|
|
|
3729
3759
|
Equivalent DocClient Parameters:
|
|
3730
3760
|
```json
|
|
3731
3761
|
{
|
|
3732
|
-
"
|
|
3733
|
-
"
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
"gsi1sk": "$mallstore_1#buildingid_buildinga1#unitid_b47",
|
|
3746
|
-
"gis2pk": "$mallstoredirectory#storeid_lattelarrys",
|
|
3747
|
-
"gsi2sk": "$mallstore_1#leaseenddate_2020-03-22",
|
|
3748
|
-
"__edb_e__": "MallStore",
|
|
3749
|
-
"__edb_v__": "1"
|
|
3750
|
-
},
|
|
3751
|
-
"TableName": "StoreDirectory",
|
|
3752
|
-
"ConditionExpression": "attribute_not_exists(pk) AND attribute_not_exists(sk) AND #rent = :rent_w1",
|
|
3753
|
-
"ExpressionAttributeNames": {
|
|
3754
|
-
"#rent": "rent"
|
|
3755
|
-
},
|
|
3756
|
-
"ExpressionAttributeValues": {
|
|
3757
|
-
":rent_w1": "4500.00"
|
|
3762
|
+
"RequestItems": {
|
|
3763
|
+
"YOUR_TABLE_NAME": {
|
|
3764
|
+
"Keys": [
|
|
3765
|
+
{
|
|
3766
|
+
"pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
|
|
3767
|
+
"sk": "$mallstore_1#buildingid_f34#storeid_lattelarrys"
|
|
3768
|
+
},
|
|
3769
|
+
{
|
|
3770
|
+
"pk": "$mallstoredirectory#cityid_madison2#mallid_westend",
|
|
3771
|
+
"sk": "$mallstore_1#buildingid_a21#storeid_mochajoes"
|
|
3772
|
+
}
|
|
3773
|
+
]
|
|
3774
|
+
}
|
|
3758
3775
|
}
|
|
3759
3776
|
}
|
|
3760
3777
|
```
|
|
3761
3778
|
|
|
3779
|
+
The two-dimensional array returned by batch get most easily used when deconstructed into two variables, in the above case: `results` and `unprocessed`.
|
|
3780
|
+
|
|
3781
|
+
The `results` array are records that were returned DynamoDB as `Responses` on the BatchGet query. They will appear in the same format as other ElectroDB queries.
|
|
3782
|
+
|
|
3783
|
+
> _NOTE: By default ElectroDB will return items without concern for order. If the order returned by ElectroDB must match the order provided, the [query option](#query-options) `preserveBatchOrder` can be used. When enabled, ElectroDB will ensure the order returned by a batchGet will be the same as the order provided. When enabled, if a record is returned from DynamoDB as "unprocessed" ([read more here](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html)), ElectroDB will return a null value at that index._
|
|
3784
|
+
|
|
3785
|
+
Elements of the `unprocessed` array are unlike results received from a query. Instead of containing all the attributes of a record, an unprocessed record only includes the composite attributes defined in the Table Index. This is in keeping with DynamoDB's practice of returning only Keys in the case of unprocessed records. For convenience, ElectroDB will return these keys as composite attributes, but you can pass the [query option](#query-options) `{unprocessed:"raw"}` override this behavior and return the Keys as they came from DynamoDB.
|
|
3786
|
+
|
|
3762
3787
|
### Find Records
|
|
3763
3788
|
|
|
3764
3789
|
DynamoDB offers three methods to query records: `get`, `query`, and `scan`. In **_ElectroDB_**, there is a fourth type: `find`. Unlike `get` and `query`, the `find` method does not require you to provide keys, but under the covers it will leverage the attributes provided to choose the best index to query on. Provide the `find` method will all properties known to match a record and **_ElectroDB_** will generate the most performant query it can to locate the results. This can be helpful with highly dynamic querying needs. If an index cannot be satisfied with the attributes provided, `scan` will be used as a last resort.
|
|
@@ -5076,7 +5101,7 @@ const EmployeesModel = {
|
|
|
5076
5101
|
},
|
|
5077
5102
|
},
|
|
5078
5103
|
employeeLookup: {
|
|
5079
|
-
collection: "
|
|
5104
|
+
collection: "assignments",
|
|
5080
5105
|
index: "gsi3pk-gsi3sk-index",
|
|
5081
5106
|
pk: {
|
|
5082
5107
|
field: "gsi3pk",
|
|
@@ -5147,7 +5172,7 @@ const TasksModel = {
|
|
|
5147
5172
|
},
|
|
5148
5173
|
},
|
|
5149
5174
|
assigned: {
|
|
5150
|
-
collection: "
|
|
5175
|
+
collection: "assignments",
|
|
5151
5176
|
index: "gsi3pk-gsi3sk-index",
|
|
5152
5177
|
pk: {
|
|
5153
5178
|
field: "gsi3pk",
|
|
@@ -5215,12 +5240,12 @@ const EmployeeApp = new Service({
|
|
|
5215
5240
|
}, { client, table });
|
|
5216
5241
|
|
|
5217
5242
|
```
|
|
5218
|
-
###
|
|
5243
|
+
### Querying Your model
|
|
5219
5244
|
#### All tasks and employee information for a given employee
|
|
5220
5245
|
Fulfilling [Requirement #1](#employee-app-requirements).
|
|
5221
5246
|
|
|
5222
5247
|
```javascript
|
|
5223
|
-
EmployeeApp.collections.
|
|
5248
|
+
EmployeeApp.collections.assignments({employee: "CBaskin"}).go();
|
|
5224
5249
|
```
|
|
5225
5250
|
Returns the following:
|
|
5226
5251
|
```javascript
|
|
@@ -5467,7 +5492,7 @@ const StoreLocations = new Entity(model, {client, table: "StoreLocations"});
|
|
|
5467
5492
|
|
|
5468
5493
|
### Access Patterns are accessible on the StoreLocation
|
|
5469
5494
|
|
|
5470
|
-
###
|
|
5495
|
+
### Create New Record
|
|
5471
5496
|
#### Add a new Store to the Mall
|
|
5472
5497
|
```javascript
|
|
5473
5498
|
await StoreLocations.create({
|
|
@@ -5496,9 +5521,10 @@ Returns the following:
|
|
|
5496
5521
|
}
|
|
5497
5522
|
```
|
|
5498
5523
|
---
|
|
5499
|
-
### UPDATE Record
|
|
5500
5524
|
#### Change the Stores Lease Date
|
|
5525
|
+
|
|
5501
5526
|
>When updating a record, you must include all **Composite Attributes** associated with the table's *primary* **PK** and **SK**.
|
|
5527
|
+
|
|
5502
5528
|
```javascript
|
|
5503
5529
|
let storeId = "LatteLarrys";
|
|
5504
5530
|
let mallId = "EastPointe";
|
|
@@ -5517,7 +5543,6 @@ Returns the following:
|
|
|
5517
5543
|
}
|
|
5518
5544
|
```
|
|
5519
5545
|
|
|
5520
|
-
### GET Record
|
|
5521
5546
|
#### Retrieve a specific Store in a Mall
|
|
5522
5547
|
>When retrieving a specific record, you must include all **Composite Attributes** associated with the table's *primary* **PK** and **SK**.
|
|
5523
5548
|
```javascript
|
|
@@ -5541,7 +5566,6 @@ Returns the following:
|
|
|
5541
5566
|
}
|
|
5542
5567
|
```
|
|
5543
5568
|
|
|
5544
|
-
### DELETE Record
|
|
5545
5569
|
#### Remove a Store location from the Mall
|
|
5546
5570
|
>When removing a specific record, you must include all **Composite Attributes** associated with the table's *primary* **PK** and **SK**.
|
|
5547
5571
|
```javascript
|
package/index.d.ts
CHANGED
|
@@ -711,6 +711,12 @@ export interface PutRecordOperationOptions<A extends string, F extends string, C
|
|
|
711
711
|
where: WhereClause<A, F, C, S, Item<A, F, C, S, S["attributes"]>, PutRecordOperationOptions<A, F, C, S, ResponseType>>;
|
|
712
712
|
}
|
|
713
713
|
|
|
714
|
+
export interface UpsertRecordOperationOptions<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
|
|
715
|
+
go: PutRecordGo<ResponseType, UpdateQueryParams>;
|
|
716
|
+
params: ParamRecord<UpdateQueryParams>;
|
|
717
|
+
where: WhereClause<A, F, C, S, Item<A, F, C, S, S["attributes"]>, UpsertRecordOperationOptions<A, F, C, S, ResponseType>>;
|
|
718
|
+
}
|
|
719
|
+
|
|
714
720
|
export interface DeleteRecordOperationOptions<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
|
|
715
721
|
go: DeleteRecordOperationGo<ResponseType, DeleteQueryOptions>;
|
|
716
722
|
params: ParamRecord<DeleteQueryOptions>;
|
|
@@ -2240,6 +2246,7 @@ export class Entity<A extends string, F extends string, C extends string, S exte
|
|
|
2240
2246
|
delete(key: AllTableIndexCompositeAttributes<A,F,C,S>[]): BatchWriteOperationOptions<A,F,C,S, AllTableIndexCompositeAttributes<A,F,C,S>[]>;
|
|
2241
2247
|
remove(key: AllTableIndexCompositeAttributes<A,F,C,S>): DeleteRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>
|
|
2242
2248
|
|
|
2249
|
+
upsert(record: PutItem<A,F,C,S>): UpsertRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>;
|
|
2243
2250
|
put(record: PutItem<A,F,C,S>): PutRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>;
|
|
2244
2251
|
put(record: PutItem<A,F,C,S>[]): BatchWriteOperationOptions<A,F,C,S, AllTableIndexCompositeAttributes<A,F,C,S>[]>;
|
|
2245
2252
|
create(record: PutItem<A,F,C,S>): PutRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>
|
package/package.json
CHANGED
package/src/clauses.js
CHANGED
|
@@ -28,7 +28,7 @@ function batchAction(action, type, entity, state, payload) {
|
|
|
28
28
|
let clauses = {
|
|
29
29
|
index: {
|
|
30
30
|
name: "index",
|
|
31
|
-
children: ["get", "delete", "update", "query", "put", "scan", "collection", "clusteredCollection", "create", "remove", "patch", "batchPut", "batchDelete", "batchGet"],
|
|
31
|
+
children: ["get", "delete", "update", "query", "upsert", "put", "scan", "collection", "clusteredCollection", "create", "remove", "patch", "batchPut", "batchDelete", "batchGet"],
|
|
32
32
|
},
|
|
33
33
|
clusteredCollection: {
|
|
34
34
|
name: "clusteredCollection",
|
|
@@ -184,6 +184,31 @@ let clauses = {
|
|
|
184
184
|
},
|
|
185
185
|
children: ["where", "params", "go"],
|
|
186
186
|
},
|
|
187
|
+
upsert: {
|
|
188
|
+
name: 'upsert',
|
|
189
|
+
action(entity, state, payload = {}) {
|
|
190
|
+
if (state.getError() !== null) {
|
|
191
|
+
return state;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
let record = entity.model.schema.checkCreate({...payload});
|
|
195
|
+
const attributes = state.getCompositeAttributes();
|
|
196
|
+
return state
|
|
197
|
+
.setMethod(MethodTypes.upsert)
|
|
198
|
+
.setType(QueryTypes.eq)
|
|
199
|
+
.applyUpsert(record)
|
|
200
|
+
.setPK(entity._expectFacets(record, attributes.pk))
|
|
201
|
+
.ifSK(() => {
|
|
202
|
+
entity._expectFacets(record, attributes.sk);
|
|
203
|
+
state.setSK(entity._buildQueryFacets(record, attributes.sk));
|
|
204
|
+
});
|
|
205
|
+
} catch(err) {
|
|
206
|
+
state.setError(err);
|
|
207
|
+
return state;
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
children: ["params", "go", "where"],
|
|
211
|
+
},
|
|
187
212
|
put: {
|
|
188
213
|
name: "put",
|
|
189
214
|
/* istanbul ignore next */
|
|
@@ -678,6 +703,9 @@ class ChainState {
|
|
|
678
703
|
put: {
|
|
679
704
|
data: {},
|
|
680
705
|
},
|
|
706
|
+
upsert: {
|
|
707
|
+
data: {}
|
|
708
|
+
},
|
|
681
709
|
keys: {
|
|
682
710
|
provided: [],
|
|
683
711
|
pk: {},
|
|
@@ -874,6 +902,11 @@ class ChainState {
|
|
|
874
902
|
}
|
|
875
903
|
}
|
|
876
904
|
|
|
905
|
+
applyUpsert(data = {}) {
|
|
906
|
+
this.query.upsert.data = {...this.query.upsert.data, ...data};
|
|
907
|
+
return this;
|
|
908
|
+
}
|
|
909
|
+
|
|
877
910
|
applyPut(data = {}) {
|
|
878
911
|
this.query.put.data = {...this.query.put.data, ...data};
|
|
879
912
|
return this;
|
package/src/entity.js
CHANGED
|
@@ -23,6 +23,7 @@ const { AllPages,
|
|
|
23
23
|
ResultOrderParam,
|
|
24
24
|
IndexTypes,
|
|
25
25
|
PartialComparisons,
|
|
26
|
+
MethodTypeTranslation,
|
|
26
27
|
} = require("./types");
|
|
27
28
|
const { FilterFactory } = require("./filters");
|
|
28
29
|
const { FilterOperations } = require("./operations");
|
|
@@ -256,6 +257,11 @@ class Entity {
|
|
|
256
257
|
}
|
|
257
258
|
}
|
|
258
259
|
|
|
260
|
+
upsert(attributes = {}) {
|
|
261
|
+
let index = TableIndex;
|
|
262
|
+
return this._makeChain(index, this._clausesWithFilters, clauses.index).upsert(attributes);
|
|
263
|
+
}
|
|
264
|
+
|
|
259
265
|
create(attributes = {}) {
|
|
260
266
|
let index = TableIndex;
|
|
261
267
|
let options = {};
|
|
@@ -314,7 +320,6 @@ class Entity {
|
|
|
314
320
|
}
|
|
315
321
|
|
|
316
322
|
async _exec(method, params, config = {}) {
|
|
317
|
-
const entity = this;
|
|
318
323
|
const notifyQuery = () => {
|
|
319
324
|
this.eventManager.trigger({
|
|
320
325
|
type: "query",
|
|
@@ -332,8 +337,8 @@ class Entity {
|
|
|
332
337
|
results,
|
|
333
338
|
}, config.listeners);
|
|
334
339
|
}
|
|
335
|
-
|
|
336
|
-
return this.client[
|
|
340
|
+
const dynamoDBMethod = MethodTypeTranslation[method];
|
|
341
|
+
return this.client[dynamoDBMethod](params).promise()
|
|
337
342
|
.then((results) => {
|
|
338
343
|
notifyQuery();
|
|
339
344
|
notifyResults(results, true);
|
|
@@ -497,6 +502,7 @@ class Entity {
|
|
|
497
502
|
case MethodTypes.patch:
|
|
498
503
|
case MethodTypes.delete:
|
|
499
504
|
case MethodTypes.remove:
|
|
505
|
+
case MethodTypes.upsert:
|
|
500
506
|
return this.formatResponse(response, index, {...config, _objectOnEmpty: true});
|
|
501
507
|
default:
|
|
502
508
|
return this.formatResponse(response, index, config);
|
|
@@ -1110,7 +1116,7 @@ class Entity {
|
|
|
1110
1116
|
}
|
|
1111
1117
|
/* istanbul ignore next */
|
|
1112
1118
|
_params(state, config = {}) {
|
|
1113
|
-
let { keys = {}, method = "", put = {}, update = {}, filter = {}, options = {} } = state.query;
|
|
1119
|
+
let { keys = {}, method = "", put = {}, update = {}, filter = {}, options = {}, updateProxy, upsert } = state.query;
|
|
1114
1120
|
let consolidatedQueryFacets = this._consolidateQueryFacets(keys.sk);
|
|
1115
1121
|
let params = {};
|
|
1116
1122
|
switch (method) {
|
|
@@ -1119,6 +1125,9 @@ class Entity {
|
|
|
1119
1125
|
case MethodTypes.remove:
|
|
1120
1126
|
params = this._makeSimpleIndexParams(keys.pk, ...consolidatedQueryFacets);
|
|
1121
1127
|
break;
|
|
1128
|
+
case MethodTypes.upsert:
|
|
1129
|
+
params = this._makeUpsertParams({update, upsert}, keys.pk, ...keys.sk)
|
|
1130
|
+
break;
|
|
1122
1131
|
case MethodTypes.put:
|
|
1123
1132
|
case MethodTypes.create:
|
|
1124
1133
|
params = this._makePutParams(put, keys.pk, ...keys.sk);
|
|
@@ -1477,6 +1486,28 @@ class Entity {
|
|
|
1477
1486
|
};
|
|
1478
1487
|
}
|
|
1479
1488
|
|
|
1489
|
+
_makeUpsertParams({update, upsert} = {}, pk, sk) {
|
|
1490
|
+
const { updatedKeys, setAttributes, indexKey } = this._getPutKeys(pk, sk && sk.facets, upsert.data);
|
|
1491
|
+
const upsertAttributes = this.model.schema.translateToFields(setAttributes);
|
|
1492
|
+
const keyNames = Object.keys(indexKey);
|
|
1493
|
+
update.set(this.identifiers.entity, this.getName());
|
|
1494
|
+
update.set(this.identifiers.version, this.getVersion());
|
|
1495
|
+
for (const field of [...Object.keys(upsertAttributes), ...Object.keys(updatedKeys)]) {
|
|
1496
|
+
const value = upsertAttributes[field] || updatedKeys[field];
|
|
1497
|
+
if (!keyNames.includes(field)) {
|
|
1498
|
+
update.set(field, value);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
return {
|
|
1503
|
+
TableName: this.getTableName(),
|
|
1504
|
+
UpdateExpression: update.build(),
|
|
1505
|
+
ExpressionAttributeNames: update.getNames(),
|
|
1506
|
+
ExpressionAttributeValues: update.getValues(),
|
|
1507
|
+
Key: indexKey,
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1480
1511
|
_updateExpressionBuilder(data) {
|
|
1481
1512
|
let accessPattern = this.model.translations.indexes.fromIndexToAccessPattern[TableIndex]
|
|
1482
1513
|
let skip = [
|
package/src/types.js
CHANGED
|
@@ -30,13 +30,28 @@ const MethodTypes = {
|
|
|
30
30
|
update: "update",
|
|
31
31
|
delete: "delete",
|
|
32
32
|
remove: "remove",
|
|
33
|
-
scan: "scan",
|
|
34
33
|
patch: "patch",
|
|
35
34
|
create: "create",
|
|
36
35
|
batchGet: "batchGet",
|
|
37
|
-
batchWrite: "batchWrite"
|
|
36
|
+
batchWrite: "batchWrite",
|
|
37
|
+
upsert: "upsert",
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
const MethodTypeTranslation = {
|
|
41
|
+
put: "put",
|
|
42
|
+
get: "get",
|
|
43
|
+
query: "query",
|
|
44
|
+
scan: "scan",
|
|
45
|
+
update: "update",
|
|
46
|
+
delete: "delete",
|
|
47
|
+
remove: "delete",
|
|
48
|
+
patch: "update",
|
|
49
|
+
create: "put",
|
|
50
|
+
batchGet: "batchGet",
|
|
51
|
+
batchWrite: "batchWrite",
|
|
52
|
+
upsert: "update",
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
const IndexTypes = {
|
|
41
56
|
isolated: 'isolated',
|
|
42
57
|
clustered: 'clustered',
|
|
@@ -286,6 +301,7 @@ module.exports = {
|
|
|
286
301
|
FormatToReturnValues,
|
|
287
302
|
AttributeProxySymbol,
|
|
288
303
|
ElectroInstanceTypes,
|
|
304
|
+
MethodTypeTranslation,
|
|
289
305
|
EventSubscriptionTypes,
|
|
290
306
|
AttributeMutationMethods,
|
|
291
307
|
AllPages,
|