electrodb 2.1.1 → 2.2.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/.travis.yml CHANGED
@@ -12,4 +12,4 @@ node_js:
12
12
  - '16'
13
13
 
14
14
  script:
15
- - npm run coverage-coveralls-local
15
+ - npm run coverage:local:coveralls
package/README.md CHANGED
@@ -333,7 +333,7 @@ If you're looking to get started right away with ElectroDB, checkout code exampl
333
333
 
334
334
  # Entities
335
335
 
336
- In ***ElectroDB*** an `Entity` is represents a single business object. For example, in a simple task tracking application, one Entity could represent an Employee and or a Task that is assigned to an employee.
336
+ In ***ElectroDB*** an `Entity` represents a single business object. For example, in a simple task tracking application, one Entity could represent an Employee and or a Task that is assigned to an employee.
337
337
 
338
338
  Require or import `Entity` from `electrodb`:
339
339
  ```javascript
@@ -1032,7 +1032,7 @@ signature | behavior
1032
1032
  `(value: T) => void` | A void or `undefined` value is returned, will be treated as successful, in this scenario you can throw an Error yourself to interrupt the query
1033
1033
 
1034
1034
  ## Indexes
1035
- When using ElectroDB, indexes are referenced by their `AccessPatternName`. This allows you to maintain generic index names on your DynamoDB table, but reference domain specific names while using your ElectroDB Entity. These will often be referenced as _"Access Patterns"_.
1035
+ When using ElectroDB, indexes are referenced by their `AccessPatternName`. This allows you to maintain generic index names on your DynamoDB table, but reference domain specific names while using your ElectroDB Entity. These will be referenced as _"Access Patterns"_.
1036
1036
 
1037
1037
  All DynamoDB table start with at least a PartitionKey with an optional SortKey, this can be referred to as the _"Table Index"_. The `indexes` object requires at least the definition of this _Table Index_ **Partition Key** and (if applicable) **Sort Key**.
1038
1038
 
@@ -1045,8 +1045,11 @@ Within these _AccessPatterns_, you define the PartitionKey and (optionally) Sort
1045
1045
  ```typescript
1046
1046
  indexes: {
1047
1047
  [AccessPatternName]: {
1048
+ index?: string;
1049
+ collection?: string | string[];
1050
+ type?: 'isolated' | 'clustered';
1048
1051
  pk: {
1049
- field: string;
1052
+ field: string;
1050
1053
  composite: AttributeName[];
1051
1054
  template?: string;
1052
1055
  },
@@ -1055,14 +1058,15 @@ indexes: {
1055
1058
  composite: AttributesName[];
1056
1059
  template?: string;
1057
1060
  },
1058
- index?: string
1059
- collection?: string | string[]
1060
1061
  }
1061
1062
  }
1062
1063
  ```
1063
1064
 
1064
1065
  | Property | Type | Required | Description |
1065
1066
  | -------------- | :------------------------------------: | :------: | ----------- |
1067
+ | `index` | `string` | no | Required when the `Index` defined is a *Global/Local Secondary Index*; but is omitted for the table's primary index.
1068
+ | `collection` | `string`, `string[]` | no | Used when models are joined to a `Service`. When two entities share a `collection` on the same `index`, they can be queried with one request to DynamoDB. The name of the collection should represent what the query would return as a pseudo `Entity`. (see [Collections](#collections) below for more on this functionality).
1069
+ | `type` | `isolated`, `clustered` | no | Allows you to optimize your index for either [entity isolation](#isolated-indexes) (high volume of records per partition) or (entity relationships)[#clustered-indexes] (high relationship density per partition). When omitted, ElectroDB defaults to `isolation`.
1066
1070
  | `pk` | `object` | yes | Configuration for the pk of that index or table
1067
1071
  | `pk.composite` | `string[]` | yes | An array that represents the order in which attributes are concatenated to composite attributes the key (see [Composite Attributes](#composite-attributes) below for more on this functionality).
1068
1072
  | `pk.template` | `string` | no | A string that represents the template in which attributes composed to form a key (see [Composite Attribute Templates](#composite-attribute-templates) below for more on this functionality).
@@ -1073,8 +1077,26 @@ indexes: {
1073
1077
  | `sk.template` | `string` | no | A string that represents the template in which attributes composed to form a key (see [Composite Attribute Templates](#composite-attribute-templates) below for more on this functionality).
1074
1078
  | `sk.field` | `string` | yes | The name of the index Sort Key field as it exists in DynamoDB, if named differently in the schema attributes.
1075
1079
  | `pk.casing` | `default`, `upper`, `lower`, `none`, | no | Choose a case for ElectroDB to convert your keys to, to avoid casing pitfalls when querying data. Default: `lower`.
1076
- | `index` | `string` | no | Required when the `Index` defined is a *Global/Local Secondary Index*; but is omitted for the table's primary index.
1077
- | `collection` | `string`, `string[]` | no | Used when models are joined to a `Service`. When two entities share a `collection` on the same `index`, they can be queried with one request to DynamoDB. The name of the collection should represent what the query would return as a pseudo `Entity`. (see [Collections](#collections) below for more on this functionality).
1080
+
1081
+
1082
+ ### Index Types
1083
+ ElectroDB helps manage your key structure, and works to abstract out the details of how your keys are created/formatted. Depending on your unique data set, you may need ElectroDB to optimize your index for either [entity isolation](#isolated-indexes) (i.e. high volume of records per partition) or (entity relationships)[#clustered-indexes] (i.e. high relationship density per partition).
1084
+
1085
+ This option changes how ElectroDB formats your keys for storage, so it is an important consideration to make early in your modeling phase. As a result, this choice cannot be simply walked back without requiring a migration. The choice between `clustered` and `isolated` depends wholly on your unique dataset and access patterns.
1086
+
1087
+ > _NOTE: You can use [Collections](#collections) with both `isolated` and `clustered` indexes. Isolated indexes are limited to only querying across the partition key while Clustered indexes can also leverage the Sort Key_
1088
+
1089
+ #### Isolated Indexes
1090
+ By default, and when omitted, ElectroDB will create your index as an `isolated` index. Isolated indexes optimizes your index structure for faster and more efficient retrieval of items within an individual Entity.
1091
+
1092
+ *Choose* `isolated` if you have strong access pattern requirements to retrieve only records for only your entity on that index. While an `isolated` index is more limited in its ability to be used in a [collection](#collections), it can perform better than a `clustered` index if a collection contains a highly unequal distribution of entities within a collection.
1093
+ *Don't choose* `isolated` if the primary use-cases for your index is to query across entities -- this index type does limit the extent to which indexes can be leveraged to improve query efficiency.
1094
+
1095
+ #### Clustered Indexes
1096
+ When your index type is defined as `clustered`, ElectroDB will optimize your index for relationships within a partition. Clustered indexes optimize your index structure for more homogenous partitions, which allows for more efficient queries across multiple entities.
1097
+
1098
+ *Choose* `clustered` if you have a high degree of grouped or similar data that needs to be frequently accessed together. This index works best in [collections](#collections) when member entities are more evenly distributed within a partition.
1099
+ *Don't choose* `clustered` if your need to query across entities is secondary to its primary purpose -- this index type limits the efficiency of querying your individual entity.
1078
1100
 
1079
1101
  ### Indexes Without Sort Keys
1080
1102
  When using indexes without Sort Keys, that should be expressed as an index *without* an `sk` property at all. Indexes without an `sk` cannot have a collection, see [Collections](#collections) for more detail.
@@ -1661,7 +1683,7 @@ await TaskApp.collections
1661
1683
 
1662
1684
  ### Collection Queries vs Entity Queries
1663
1685
 
1664
- To query across entities, collection queries make use of ElectroDB's Sort Key structure, which prefixes Sort Key fields with the collection name. Unlike an Entity Query, Collection Queries only leverage [Composite Attributes](#composite-attributes) from an access pattern's Partition Key.
1686
+ To query across entities, collection queries make use of ElectroDB's Sort Key structure, which prefixes Sort Key fields with the collection name. Unlike an Entity Query, Collection queries for [isolated indexes](#isolated-indexes) only leverage [Composite Attributes](#composite-attributes) from an access pattern's Partition Key, while Collection queries for [clustered indexes](#clustered-indexes) allow you to query on both Partition and Sort Keys.
1665
1687
 
1666
1688
  To better explain how Collection Queries are formed, here is a juxtaposition of an Entity Query's parameters vs a Collection Query's parameters:
1667
1689
 
@@ -1740,7 +1762,9 @@ Because the Tasks and Employee Entities both associated their index (`gsi2`) wit
1740
1762
 
1741
1763
  ## Sub-Collections
1742
1764
 
1743
- Sub-Collections are an extension of [Collection](#collections) functionality that allow you to model more advanced access patterns. Collections and Sub-Collections are defined on [Indexes](#indexes) via a property called `collection`, as either a string or string array respectively.
1765
+ Sub-Collections are an extension of [Collection](#collections) functionality that allow you to model more advanced access patterns. Collections and Sub-Collections are defined on [Indexes](#indexes) via a property called `collection`, as either a string or string array respectively.
1766
+
1767
+ > _NOTE: Sub-Collections are only supported on ["isolated" index](#isolated-indexes) types _
1744
1768
 
1745
1769
  The following is an example of functionally identical collections, implemented as a string (referred to as a "collection") and then as a string array (referred to as sub-collections):
1746
1770
 
@@ -3401,7 +3425,7 @@ await StoreLocations
3401
3425
  o.add(a.tenant, newTenant); // electrodb "set" -> dynamodb "set"
3402
3426
  o.add(a.rent, 100); // electrodb "number" -> dynamodb "number"
3403
3427
  o.subtract(a.deposit, 200); // electrodb "number" -> dynamodb "number"
3404
- o.remove(a.leaseEndDate); // electrodb "string" -> dynamodb "string"
3428
+ o.remove(attr.discount); // electrodb "number" -> dynamodb "number"
3405
3429
  o.append(a.rentalAgreement, [{ // electrodb "list" -> dynamodb "list"
3406
3430
  type: "ammendment", // electrodb "map" -> dynamodb "map"
3407
3431
  detail: "no soup for you"
@@ -3425,40 +3449,49 @@ Response Format:
3425
3449
  Equivalent DocClient Parameters:
3426
3450
  ```json
3427
3451
  {
3428
- "UpdateExpression": "SET #category = :category_u0, #rent = #rent + :rent_u0, #deposit = #deposit - :deposit_u0, #rentalAgreement = list_append(#rentalAgreement, :rentalAgreement_u0), #totalFees = #totalFees + #petFee REMOVE #leaseEndDate, #gsi2sk ADD #tenant :tenant_u0, #leaseHolders :tenant_u0 DELETE #tags :tags_u0, #contact :contact_u0",
3429
- "ExpressionAttributeNames": {
3430
- "#category": "category",
3431
- "#tenant": "tenant",
3432
- "#rent": "rent",
3433
- "#deposit": "deposit",
3434
- "#leaseEndDate": "leaseEndDate",
3435
- "#rentalAgreement": "rentalAgreement",
3436
- "#tags": "tags",
3437
- "#contact": "contact",
3438
- "#totalFees": "totalFees",
3439
- "#petFee": "petFee",
3440
- "#leaseHolders": "leaseHolders",
3441
- "#gsi2sk": "gsi2sk"
3442
- },
3443
- "ExpressionAttributeValues": {
3444
- ":category0": "food/coffee",
3445
- ":category_u0": "food/meal",
3446
- ":tenant_u0": ["larry"],
3447
- ":rent_u0": 100,
3448
- ":deposit_u0": 200,
3449
- ":rentalAgreement_u0": [{
3450
- "type": "amendment",
3451
- "detail": "no soup for you"
3452
- }],
3453
- ":tags_u0": ["coffee"], // <- DynamoDB Set
3454
- ":contact_u0": ["555-345-2222"], // <- DynamoDB Set
3452
+ "UpdateExpression": "SET #category = :category_u0, #deposit = #deposit - :deposit_u0, #rentalAgreement = list_append(#rentalAgreement, :rentalAgreement_u0), #totalFees = #totalFees + #petFee, #cityId = :cityId_u0, #mallId = :mallId_u0, #buildingId = :buildingId_u0, #storeId = :storeId_u0, #__edb_e__ = :__edb_e___u0, #__edb_v__ = :__edb_v___u0 REMOVE #discount ADD #tenant :tenant_u0, #rent :rent_u0, #leaseHolders :tenant_u0 DELETE #tags :tags_u0, #contact :contact_u0",
3453
+ "ExpressionAttributeNames": {
3454
+ "#category": "category",
3455
+ "#tenant": "tenant",
3456
+ "#rent": "rent",
3457
+ "#deposit": "deposit",
3458
+ "#discount": "discount",
3459
+ "#rentalAgreement": "rentalAgreement",
3460
+ "#tags": "tags",
3461
+ "#contact": "contact",
3462
+ "#totalFees": "totalFees",
3463
+ "#petFee": "petFee",
3464
+ "#leaseHolders": "leaseHolders",
3465
+ "#buildingId": "buildingId",
3466
+ "#cityId": "cityId",
3467
+ "#mallId": "mallId",
3468
+ "#storeId": "storeId",
3469
+ "#__edb_e__": "__edb_e__", "#__edb_v__": "__edb_v__",
3455
3470
  },
3456
- "TableName": "electro",
3457
- "Key": {
3458
- "pk": `$mallstoredirectory#cityid_12345#mallid_eastpointe`,
3459
- "sk": "$mallstore_1#buildingid_a34#storeid_lattelarrys"
3460
- },
3461
- "ConditionExpression": "#category = :category0"
3471
+ "ExpressionAttributeValues": {
3472
+ ":buildingId_u0": "A34",
3473
+ ":cityId_u0": "portland",
3474
+ ":category0": "food/coffee",
3475
+ ":category_u0": "food/meal",
3476
+ ":tenant_u0": ["larry"],
3477
+ ":rent_u0": 100,
3478
+ ":deposit_u0": 200,
3479
+ ":rentalAgreement_u0": [{
3480
+ "type": "amendment",
3481
+ "detail": "no soup for you"
3482
+ }],
3483
+ ":tags_u0": ["coffee"],
3484
+ ":contact_u0": ["555-345-2222"],
3485
+ ":mallId_u0": "EastPointe",
3486
+ ":storeId_u0": "LatteLarrys",
3487
+ ":__edb_e___u0": "MallStore", ":__edb_v___u0": "1",
3488
+ },
3489
+ "TableName": "electro",
3490
+ "Key": {
3491
+ "pk": "$mallstoredirectory#cityid_portland#mallid_eastpointe",
3492
+ "sk": "$mallstore_1#buildingid_a34#storeid_lattelarrys"
3493
+ },
3494
+ "ConditionExpression": "#category = :category0"
3462
3495
  }
3463
3496
  ```
3464
3497
 
@@ -3615,7 +3648,7 @@ Equivalent DocClient Parameters:
3615
3648
  ### Patch Record
3616
3649
 
3617
3650
  ```javascript
3618
- await entity.update({ attr1: "value1", attr2: "value2" })
3651
+ await entity.patch({ attr1: "value1", attr2: "value2" })
3619
3652
  .set({ attr4: "value4" })
3620
3653
  .go();
3621
3654
  ```
@@ -3659,7 +3692,7 @@ For more detail on how to use the `patch()` method, see the section [Update Reco
3659
3692
 
3660
3693
  ### Create Record
3661
3694
 
3662
- In DynamoDB, `put` operations by default will overwrite a record if record being updated does not exist. In **_ElectroDB_**, the `patch` 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.
3695
+ 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.
3663
3696
 
3664
3697
  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.
3665
3698