electrodb 2.2.6 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
- + [Query Method](#query-method)
171
- + [Get Method](#get-method)
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
- + [Update Record](#update-record)
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
- + [Update Method: Complex Data Types](#update-method--complex-data-types)
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
- + [Patch Record](#patch-record)
190
- + [Create Record](#create-record)
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
- + [Query Records](#query-records)
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
- + [PUT Record](#put-record)
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 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._
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
- ### Query Method
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 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)
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
- Example:
2650
- ```javascript
2651
- let results = await StoreLocations.get({
2652
- storeId: "LatteLarrys",
2653
- mallId: "EastPointe",
2654
- buildingId: "F34",
2655
- cityId: "Atlanta1"
2656
- }).go();
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
- 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.
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
- ### Update Record
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. The methods can be used (and reused) in a chain to form update parameters, when finished with `.params()`, or an update operation, when finished with `.go()`. 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()`.
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 | Attribute Types | Parameter
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
- ### Update Method: Complex Data Types
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
- ### Patch Record
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 entity.patch({ attr1: "value1", attr2: "value2" })
3658
- .set({ attr4: "value4" })
3659
- .go();
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: { YOUR_SCHEMA }
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": "$service#attr1_value1",
3689
- "sk": "$entity_version#attr2_value2"
3711
+ "pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
3712
+ "sk": "$mallstore_1#buildingid_f34#storeid_lattelarrys"
3690
3713
  },
3691
- "ConditionExpression": "attribute_exists(pk) AND attribute_exists(sk)"
3714
+ "TableName": "YOUR_TABLE_NAME"
3692
3715
  }
3693
3716
  ```
3694
3717
 
3695
- 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.
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
- 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()`.
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
- ### Create Record
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
- 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.
3725
+ For example, 150 records (50 records over the DynamoDB maximum):
3702
3726
 
3703
- 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.
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
- .create({
3709
- cityId: "Atlanta1",
3710
- storeId: "LatteLarrys",
3711
- mallId: "EastPointe",
3712
- buildingId: "BuildingA1",
3713
- unitId: "B47",
3714
- category: "food/coffee",
3715
- leaseEndDate: "2020-03-22",
3716
- rent: "4500.00"
3717
- })
3718
- .where((attr, op) => op.eq(attr.rent, "4500.00"))
3719
- .go()
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: { YOUR_SCHEMA }
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
- "Item": {
3733
- "cityId": "Atlanta1",
3734
- "mallId": "EastPointe",
3735
- "storeId": "LatteLarrys",
3736
- "buildingId": "BuildingA1",
3737
- "unitId": "B47",
3738
- "category": "food/coffee",
3739
- "leaseEndDate": "2020-03-22",
3740
- "rent": "4500.00",
3741
- "discount": "0.00",
3742
- "pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
3743
- "sk": "$mallstore_1#buildingid_buildinga1#storeid_lattelarrys",
3744
- "gis1pk": "$mallstoredirectory#mallid_eastpointe",
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: "assignements",
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: "assignements",
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
- ### Query Records
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.assignements({employee: "CBaskin"}).go();
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
- ### PUT Record
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/notes ADDED
@@ -0,0 +1,23 @@
1
+ put
2
+ 1. checkCreate
3
+ - getValidate
4
+ - `val()`
5
+ - `cast()`
6
+ - undefined ? `default()`
7
+ - `isValid()`
8
+ - `_isType()`
9
+ - `validate()` - user validation
10
+
11
+ 2. items added to clause state via `applyPut`
12
+ 3. pk && sk are expected
13
+ put(batch)
14
+ create
15
+ 1. checkCreate
16
+ 2. items added to clause state via `applyPut`
17
+ upsert
18
+ 1. checkCreate
19
+ update
20
+ patch
21
+ remove
22
+ delete
23
+ delete(batch)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "2.2.6",
3
+ "version": "2.3.1",
4
4
  "description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb",
5
5
  "main": "index.js",
6
6
  "scripts": {
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[method](params).promise()
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);
@@ -1003,6 +1009,7 @@ class Entity {
1003
1009
 
1004
1010
  if (validations.isStringHasLength(option.table)) {
1005
1011
  config.params.TableName = option.table;
1012
+ config.table = option.table;
1006
1013
  }
1007
1014
 
1008
1015
  if (option.concurrent !== undefined) {
@@ -1110,7 +1117,7 @@ class Entity {
1110
1117
  }
1111
1118
  /* istanbul ignore next */
1112
1119
  _params(state, config = {}) {
1113
- let { keys = {}, method = "", put = {}, update = {}, filter = {}, options = {} } = state.query;
1120
+ let { keys = {}, method = "", put = {}, update = {}, filter = {}, options = {}, updateProxy, upsert } = state.query;
1114
1121
  let consolidatedQueryFacets = this._consolidateQueryFacets(keys.sk);
1115
1122
  let params = {};
1116
1123
  switch (method) {
@@ -1119,6 +1126,9 @@ class Entity {
1119
1126
  case MethodTypes.remove:
1120
1127
  params = this._makeSimpleIndexParams(keys.pk, ...consolidatedQueryFacets);
1121
1128
  break;
1129
+ case MethodTypes.upsert:
1130
+ params = this._makeUpsertParams({update, upsert}, keys.pk, ...keys.sk)
1131
+ break;
1122
1132
  case MethodTypes.put:
1123
1133
  case MethodTypes.create:
1124
1134
  params = this._makePutParams(put, keys.pk, ...keys.sk);
@@ -1477,6 +1487,28 @@ class Entity {
1477
1487
  };
1478
1488
  }
1479
1489
 
1490
+ _makeUpsertParams({update, upsert} = {}, pk, sk) {
1491
+ const { updatedKeys, setAttributes, indexKey } = this._getPutKeys(pk, sk && sk.facets, upsert.data);
1492
+ const upsertAttributes = this.model.schema.translateToFields(setAttributes);
1493
+ const keyNames = Object.keys(indexKey);
1494
+ update.set(this.identifiers.entity, this.getName());
1495
+ update.set(this.identifiers.version, this.getVersion());
1496
+ for (const field of [...Object.keys(upsertAttributes), ...Object.keys(updatedKeys)]) {
1497
+ const value = upsertAttributes[field] || updatedKeys[field];
1498
+ if (!keyNames.includes(field)) {
1499
+ update.set(field, value);
1500
+ }
1501
+ }
1502
+
1503
+ return {
1504
+ TableName: this.getTableName(),
1505
+ UpdateExpression: update.build(),
1506
+ ExpressionAttributeNames: update.getNames(),
1507
+ ExpressionAttributeValues: update.getValues(),
1508
+ Key: indexKey,
1509
+ };
1510
+ }
1511
+
1480
1512
  _updateExpressionBuilder(data) {
1481
1513
  let accessPattern = this.model.translations.indexes.fromIndexToAccessPattern[TableIndex]
1482
1514
  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,