electrodb 1.12.1 → 2.0.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 CHANGED
@@ -11,6 +11,11 @@
11
11
 
12
12
  ------------
13
13
 
14
+ <h1 align="center">ElectroDB has now reached 2.0.0!</h1>
15
+ For existing users, checkout the [CHANGELOG](./CHANGELOG.md) and/or the section [Version 2 Migration](#version-2-migration) to learn more about the recent move to 2.0.0 and the changes neccessary to move to the newest version.
16
+
17
+ ------------
18
+
14
19
  <a href="https://electrodb.fun"><h1 align="center">Introducing: The NEW ElectroDB Playground</h1></a>
15
20
 
16
21
  <p align="center">
@@ -31,7 +36,7 @@
31
36
  - [**Simplified Update Expression Composition**](#update-record) - Easily compose type safe update operations without having to format tedious `ExpressionAttributeNames`, `ExpressionAttributeValues`, and `UpdateExpressions`.
32
37
  - [**Easily Query Across Entities**](#collections) - Define "collections" to create powerful/idiomatic queries that return multiple entities in a single request.
33
38
  - [**Automatic Index Selection**](#find-records) - Use `.find()` or `.match()` methods to dynamically and efficiently query based on defined sort key structures.
34
- - [**Simplified Pagination API**](#page) - Use `.page()` to easily paginate through result sets.
39
+ - [**Simplified Pagination API**](#entity-pagination) - ElectroDB generates url safe cursors for pagination, allows for fine grain automated pagination, and supports async iteration.
35
40
  - [**Use With Your Existing Solution**](#composite-attribute-templates) - If you are already using DynamoDB, and want to use ElectroDB, use custom Composite Attribute Templates to leverage your existing key structures.
36
41
  - [**TypeScript Support**](#typescript-support) - Strong **TypeScript** support for both Entities and Services now in Beta.
37
42
  - [**Query Directly via the Terminal**](#electro-cli) - Execute queries against your `Entities`, `Services`, `Models` directly from the command line.
@@ -94,30 +99,25 @@ tasks
94
99
 
95
100
  ------------
96
101
 
102
+ ## Table of Contents
97
103
  - [ElectroDB](#electrodb)
98
104
  * [Features](#features)
99
- * [Table of Contents](#table-of-contents)
100
105
  - [Project Goals](#project-goals)
101
106
  - [Installation](#installation)
102
107
  - [Usage](#usage)
103
108
  - [Entities and Services](#entities-and-services)
109
+ - [Getting Started](#getting-started)
104
110
  - [Entities](#entities)
105
111
  - [Services](#services)
106
112
  * [TypeScript Support](#typescript-support)
107
- + [TypeScript Services](#typescript-services)
108
- * [Join](#join)
109
- - [Independent Models](#independent-models)
110
- - [Joining Entity instances to a Service](#joining-entity-instances-to-a-service)
111
- - [Joining models to a Service](#joining-models-to-a-service)
112
- - [Joining Entities or Models with an alias](#joining-entities-or-models-with-an-alias)
113
- - [Joining Entities at Service construction for TypeScript](#joining-entities-at-service-construction-for-typescript)
113
+ + [Services](#services-1)
114
+ - [Joining Entities together](#joining-entities-together)
114
115
  * [Model](#model)
115
116
  + [Model Properties](#model-properties)
116
117
  + [Service Options](#service-options)
117
118
  * [Attributes](#attributes)
118
- + [Simple Syntax](#simple-syntax)
119
- + [Expanded Syntax](#expanded-syntax)
120
- - [Attribute Definition](#attribute-definition)
119
+ + [Attribute Definition](#attribute-definition)
120
+ - [Attribute Definition](#attribute-definition-1)
121
121
  - [Enum Attributes](#enum-attributes)
122
122
  - [Map Attributes](#map-attributes)
123
123
  - [List Attributes](#list-attributes)
@@ -135,7 +135,6 @@ tasks
135
135
  + [Indexes With Sort Keys](#indexes-with-sort-keys)
136
136
  + [Numeric Keys](#numeric-keys)
137
137
  + [Index Casing](#index-casing)
138
- * [Facets](#facets)
139
138
  * [Composite Attributes](#composite-attributes)
140
139
  + [Composite Attribute Arrays](#composite-attribute-arrays)
141
140
  + [Composite Attribute Templates](#composite-attribute-templates)
@@ -150,10 +149,6 @@ tasks
150
149
  * [Index and Collection Naming Conventions](#index-and-collection-naming-conventions)
151
150
  + [Index Naming Conventions](#index-naming-conventions)
152
151
  * [Collection Naming Conventions](#collection-naming-conventions)
153
- * [Filters](#filters)
154
- + [Defined on the model](#defined-on-the-model)
155
- + [Defined via Filter method after query operators](#defined-via-filter-method-after-query-operators)
156
- + [Multiple Filters](#multiple-filters)
157
152
  * [Where](#where)
158
153
  + [FilterExpressions](#filterexpressions)
159
154
  + [ConditionExpressions](#conditionexpressions)
@@ -167,7 +162,7 @@ tasks
167
162
  + [Query App Records](#query-app-records)
168
163
  - [Partition Key Composite Attributes](#partition-key-composite-attributes)
169
164
  + [Sort Key Operations](#sort-key-operations)
170
- * [Query Chains](#query-chains)
165
+ * [Performing Queries](#performing-queries)
171
166
  + [Query Method](#query-method)
172
167
  + [Get Method](#get-method)
173
168
  + [Batch Get](#batch-get)
@@ -193,14 +188,15 @@ tasks
193
188
  + [Match Records](#match-records)
194
189
  + [Access Pattern Queries](#access-pattern-queries)
195
190
  - [Begins With Queries](#begins-with-queries)
196
- * [Collection Chains](#collection-chains)
197
- * [Execute Queries](#execute-queries)
191
+ * [Collection Queries](#collection-queries)
192
+ * [Executing Queries](#executing-queries)
198
193
  + [Params](#params)
199
194
  + [Go](#go)
200
- + [Page](#page)
201
195
  - [Entity Pagination](#entity-pagination)
196
+ * [Pagination Cursor](#pagination-cursor)
202
197
  - [Service Pagination](#service-pagination)
203
- - [Pager Query Options](#pager-query-options)
198
+ - [Pagination Query Options](#pagination-query-options)
199
+ * [Query Option Pager](#query-option-pager)
204
200
  * [Pagination Example](#pagination-example)
205
201
  * [Query Examples](#query-examples)
206
202
  * [Query Options](#query-options)
@@ -212,7 +208,7 @@ tasks
212
208
  * [Query Event](#query-event)
213
209
  * [Results Event](#results-event)
214
210
  - [Listeners](#listeners)
215
- - [Errors:](#errors-)
211
+ - [ElectroDB Errors](#electrodb-errors)
216
212
  + [No Client Defined On Model](#no-client-defined-on-model)
217
213
  + [Invalid Identifier](#invalid-identifier)
218
214
  + [Invalid Key Composite Attribute Template](#invalid-key-composite-attribute-template)
@@ -236,7 +232,6 @@ tasks
236
232
  + [Invalid Attribute](#invalid-attribute)
237
233
  + [AWS Error](#aws-error)
238
234
  + [Unknown Errors](#unknown-errors)
239
- + [Invalid Last Evaluated Key](#invalid-last-evaluated-key)
240
235
  + [No Owner For Pager](#no-owner-for-pager)
241
236
  + [Pager Not Unique](#pager-not-unique)
242
237
  - [Examples](#examples)
@@ -275,9 +270,11 @@ tasks
275
270
  - [TypeScript](#typescript)
276
271
  * [Custom Attributes](#custom-attributes)
277
272
  * [Exported Types](#exported-types)
273
+ + [QueryResponse Type](#queryresponse-type)
278
274
  + [EntityRecord Type](#entityrecord-type)
279
275
  + [EntityItem Type](#entityitem-type)
280
276
  + [CollectionItem Type](#collectionitem-type)
277
+ + [CollectionResponse](#collectionresponse)
281
278
  + [CreateEntityItem Type](#createentityitem-type)
282
279
  + [UpdateEntityItem Type](#updateentityitem-type)
283
280
  + [UpdateAddEntityItem Type](#updateaddentityitem-type)
@@ -287,11 +284,13 @@ tasks
287
284
  + [UpdateDeleteEntityItem Type](#updatedeleteentityitem-type)
288
285
  - [Using ElectroDB With Existing Data](#using-electrodb-with-existing-data)
289
286
  - [Electro CLI](#electro-cli)
287
+ - [Version 2 Migration](#version-2-migration)
288
+ * [New response format for all query methods.](#new-response-format-for-all-query-methods)
289
+ * [Unified Pagination APIs](#unified-pagination-apis)
290
290
  - [Version 1 Migration](#version-1-migration)
291
291
  * [New schema format/breaking key format change](#new-schema-format-breaking-key-format-change)
292
292
  * [The renaming of index property Facets to Composite and Template](#the-renaming-of-index-property-facets-to-composite-and-template)
293
293
  * [Get Method to Return null](#get-method-to-return-null)
294
- - [Coming Soon](#coming-soon)
295
294
 
296
295
  ----------
297
296
 
@@ -328,6 +327,9 @@ import { Entity, Service } from "electrodb";
328
327
 
329
328
  You can use Entities independent of Services, you do not need to import models into a Service to use them individually. However, If you intend to make queries that `join` or span multiple Entities you will need to use a Service.
330
329
 
330
+ # Getting Started
331
+ If you're looking to get started right away with ElectroDB, checkout code examples in the `/examples` directory, or for [guided examples](#examples) in this document below. Additionally the section [Building Queries](#building-queries) shows examples of every and has descriptions of all methods available in ElectroDB. If you use TypeScript, the section [TypeScript](#typescript) contains useful exported types to use in your project.
332
+
331
333
  # Entities
332
334
 
333
335
  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.
@@ -366,7 +368,7 @@ If you experience any issues using TypeScript with ElectroDB, your feedback is v
366
368
 
367
369
  See the section [Exported TypeScript Types](#exported-typescript-types) to read more about the useful types exported from ElectroDB.
368
370
 
369
- ### TypeScript Services
371
+ ### Services
370
372
 
371
373
  New with version `0.10.0` is TypeScript support. To ensure accurate types with, TypeScript users should create their services by passing an Object literal or const object that maps Entity alias names to Entity instances.
372
374
  ```typescript
@@ -382,48 +384,7 @@ Services take an optional second parameter, similar to Entities, with a `client`
382
384
 
383
385
  While not yet typed, this pattern will also accept Models, or a mix of Entities and Models, in the same object literal format.
384
386
 
385
- ## Join
386
- When using JavaScript, use `join` to add [Entities](#entities) or [Models](#model) onto a Service.
387
-
388
- > _NOTE: If using TypeScript, see [Joining Entities at Service construction for TypeScript](#joining-entities-at-service-construction-for-typescript) to learn how to "join" entities for use in a TypeScript project._
389
-
390
- #### Independent Models
391
-
392
- ```javascript
393
- let table = "my_table_name";
394
- let employees = new Entity(EmployeesModel, { client, table });
395
- let tasks = new Entity(TasksModel, { client, table });
396
- ```
397
-
398
- #### Joining Entity instances to a Service
399
-
400
- ```javascript
401
- // Joining Entity instances to a Service
402
- let TaskApp = new Service("TaskApp", { client, table });
403
- TaskApp
404
- .join(employees) // available at TaskApp.entities.employees
405
- .join(tasks); // available at TaskApp.entities.tasks
406
- ```
407
-
408
- #### Joining models to a Service
409
-
410
- ```javascript
411
- let TaskApp = new Service("TaskApp", { client, table });
412
- TaskApp
413
- .join(EmployeesModel) // available at TaskApp.entities.employees (based on entity name in model)
414
- .join(TasksModel); // available at TaskApp.entities.tasks (based on entity name in model)
415
- ```
416
-
417
- #### Joining Entities or Models with an alias
418
-
419
- ```javascript
420
- let TaskApp = new Service("TaskApp", { client, table });
421
- TaskApp
422
- .join("personnel", EmployeesModel) // available at TaskApp.entities.personnel
423
- .join("directives", TasksModel); // available at TaskApp.entities.directives
424
- ```
425
-
426
- #### Joining Entities at Service construction for TypeScript
387
+ #### Joining Entities together
427
388
 
428
389
  ```typescript
429
390
  let TaskApp = new Service({
@@ -634,7 +595,7 @@ Property | Description
634
595
  -------------- | -----------
635
596
  model.service | Name of the application using the entity, used to namespace all entities
636
597
  model.entity | Name of the entity that the schema represents
637
- model.version | (optional) The version number of the schema, used to namespace keys
598
+ model.version | The version number of the schema, used to namespace keys
638
599
  attributes | An object containing each attribute that makes up the schema
639
600
  indexes | An object containing table indexes, including the values for the table's default Partition Key and Sort Key
640
601
 
@@ -652,15 +613,7 @@ client | (optional) An instance of the `docClient` from the `aws-sdk` for use
652
613
  > **Pro-Tip:**
653
614
  > Using the `field` property, you can map an `AttributeName` to a different field name in your table. This can be useful to utilize existing tables, existing models, or even to reduce record sizes via shorter field names. For example, you may refer to an attribute as `organization` but want to save the attribute with a field name of `org` in DynamoDB.
654
615
 
655
- ### Simple Syntax
656
- Assign just the `type` of the attribute directly to the attribute name. Types currently supported options are "string", "number", "boolean", an array of strings representing a fixed set of possible values, or "any" which disables value type checking on that attribute.
657
- ```typescript
658
- attributes: {
659
- <AttributeName>: "string" | "number" | "boolean" | "list" | "map" | "set" | "any" | string[] | ReadonlyArray<string>
660
- }
661
- ```
662
-
663
- ### Expanded Syntax
616
+ ### Attribute Definition
664
617
  Use the expanded syntax build out more robust attribute options.
665
618
  ```typescript
666
619
  attributes: {
@@ -694,7 +647,6 @@ Property | Type | Req
694
647
  `field` | `string` | no | all | The name of the attribute as it exists in DynamoDB, if named differently in the schema attributes. Defaults to the `AttributeName` as defined in the schema.
695
648
  `readOnly` | `boolean` | no | all | Prevents an attribute from being updated after the record has been created. Attributes used in the composition of the table's primary Partition Key and Sort Key are read-only by default. The one exception to `readOnly` is for properties that also use the `watch` property, read [attribute watching](#attribute-watching) for more detail.
696
649
  `label` | `string` | no | all | Used in index composition to prefix key composite attributes. By default, the `AttributeName` is used as the label.
697
- `cast` | `"number"`, `"string"`, `"boolean"` | no | all | Optionally cast attribute values when interacting with DynamoDB. Current options include: "number", "string", and "boolean".
698
650
  `set` | `(attribute, schema) => value` | no | all | A synchronous callback allowing you to apply changes to a value before it is set in params or applied to the database. First value represents the value passed to ElectroDB, second value are the attributes passed on that update/put
699
651
  `get` | `(attribute, schema) => value` | no | all | A synchronous callback allowing you to apply changes to a value after it is retrieved from the database. First value represents the value passed to ElectroDB, second value are the attributes retrieved from the database.
700
652
  `watch` | `Attribute[], "*"` | no | root-only | Define other attributes that will always trigger your attribute's getter and setter callback after their getter/setter callbacks are executed. Only available on root level attributes.
@@ -851,7 +803,7 @@ If your attributes needs to watch for any changes to an item, you can model this
851
803
  ```typescript
852
804
  myAttr: {
853
805
  type: "string",
854
- watch: "*", // "watch all"
806
+ watch: "*", // <- "watch all"
855
807
  set: (myAttr, allAttributes) => {
856
808
  // Whenever an `update` or `patch` operation is performed, this callback will be fired.
857
809
  // Note: myAttr or the attributes under `allAttributes` could be independently undefined because either attribute could have triggered this callback
@@ -1073,8 +1025,6 @@ signature | behavior
1073
1025
  `(value: T) => boolean` | If a boolean value is returned, `true` or truthy values will signify than a value is invalid while `false` or falsey will be considered valid.
1074
1026
  `(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
1075
1027
 
1076
-
1077
-
1078
1028
  ## Indexes
1079
1029
  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"_.
1080
1030
 
@@ -1082,6 +1032,8 @@ All DynamoDB table start with at least a PartitionKey with an optional SortKey,
1082
1032
 
1083
1033
  In your model, the _Table Index_ this is expressed as an _Access Pattern_ *without* an `index` property. For Secondary Indexes (both GSIs and LSIs), use the `index` property to define the name of the index as defined on your DynamoDB table.
1084
1034
 
1035
+ > _NOTE: The 'index' property is simply a mapping of your AccessPatternName to your DynamoDB index name. ElectroDB does not create or alter DynamoDB tables, so your indexes will need to be created prior to use_
1036
+
1085
1037
  Within these _AccessPatterns_, you define the PartitionKey and (optionally) SortKeys that are present on your DynamoDB table and map the key's name on the table with the `field` property.
1086
1038
 
1087
1039
  ```typescript
@@ -1108,14 +1060,14 @@ indexes: {
1108
1060
  | `pk` | `object` | yes | Configuration for the pk of that index or table
1109
1061
  | `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).
1110
1062
  | `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).
1111
- | `pk.field` | `string` | yes | The name of the attribute as it exists in DynamoDB, if named differently in the schema attributes.
1063
+ | `pk.field` | `string` | yes | The name of the index Partition Key field as it exists in DynamoDB, if named differently in the schema attributes.
1112
1064
  | `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`.
1113
1065
  | `sk` | `object` | no | Configuration for the sk of that index or table
1114
1066
  | `sk.composite` | `string[]` | no | Either an Array that represents the order in which attributes are concatenated to composite attributes the key, or a String for a composite attribute template. (see [Composite Attributes](#composite-attributes) below for more on this functionality).
1115
1067
  | `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).
1116
- | `sk.field` | `string` | yes | The name of the attribute as it exists in DynamoDB, if named differently in the schema attributes.
1068
+ | `sk.field` | `string` | yes | The name of the index Sort Key field as it exists in DynamoDB, if named differently in the schema attributes.
1117
1069
  | `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`.
1118
- | `index` | `string` | no | Required when the `Index` defined is a *Secondary Index*; but is left blank for the table's primary index.
1070
+ | `index` | `string` | no | Required when the `Index` defined is a *Global/Local Secondary Index*; but is omitted for the table's primary index.
1119
1071
  | `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).
1120
1072
 
1121
1073
  ### Indexes Without Sort Keys
@@ -1237,14 +1189,6 @@ Casing Option | Effect
1237
1189
  `upper` | Will convert the key to uppercase prior it its use
1238
1190
  `none` | Will not perform any casing changes when building keys
1239
1191
 
1240
- ## Facets
1241
-
1242
- As of version `0.11.1`, "Facets" have been renamed to "Composite Attributes", and all documentation has been updated to reflect that change.
1243
-
1244
- - To learn about the latest syntax, checkout [Composite Attributes](#composite-attributes).
1245
- - To learn about why this change was made in preparation for 1.0 checkout [Renaming Facets](#the-renaming-of-index-property-facets-to-composite-and-template).
1246
-
1247
-
1248
1192
  ## Composite Attributes
1249
1193
  A **Composite Attribute** is a segment of a key based on one of the attributes. **Composite Attributes** are concatenated together from either a **Partition Key**, or a **Sort Key** key, which define an `index`.
1250
1194
 
@@ -1496,7 +1440,7 @@ Another approach allows you to use the `template` property, which allows you to
1496
1440
  "your_access_pattern_name": {
1497
1441
  pk: {
1498
1442
  field: "accountId",
1499
- composite: ["accountId"], // `composite` is optional when using `template` but is required when using TypeScript
1443
+ composite: ["accountId"],
1500
1444
  template: "${accountId}"
1501
1445
  },
1502
1446
  sk: {...}
@@ -1778,8 +1722,11 @@ let results = await TaskApp.collections
1778
1722
  .go();
1779
1723
 
1780
1724
  {
1781
- tasks: [...], // tasks for employeeId "JExotic"
1725
+ data: {
1726
+ tasks: [...], // tasks for employeeId "JExotic"
1782
1727
  employees: [...] // employee record(s) with employeeId "JExotic"
1728
+ },
1729
+ cursor: null
1783
1730
  }
1784
1731
  ```
1785
1732
 
@@ -1997,8 +1944,11 @@ const results = await TaskApp.collections
1997
1944
 
1998
1945
  // results
1999
1946
  {
2000
- tasks: [...], // tasks associated with projectId "SD-204
2001
- projectMembers: [...] // employees of project "SD-204"
1947
+ data: {
1948
+ tasks: [...], // tasks associated with projectId "SD-204
1949
+ projectMembers: [...] // employees of project "SD-204"
1950
+ },
1951
+ cursor: null,
2002
1952
  }
2003
1953
 
2004
1954
  // parameters
@@ -2026,9 +1976,12 @@ const results = await TaskApp.collections
2026
1976
 
2027
1977
  // results
2028
1978
  {
2029
- tasks: [...], // tasks assigned to employeeId "JExotic"
2030
- projectMembers: [...], // projects with employeeId "JExotic"
2031
- employees: [...] // employee record(s) with employeeId "JExotic"
1979
+ data: {
1980
+ tasks: [...], // tasks assigned to employeeId "JExotic"
1981
+ projectMembers: [...], // projects with employeeId "JExotic"
1982
+ employees: [...] // employee record(s) with employeeId "JExotic"
1983
+ },
1984
+ cursor: null,
2032
1985
  }
2033
1986
 
2034
1987
  {
@@ -2050,8 +2003,11 @@ const results = await TaskApp.collections
2050
2003
 
2051
2004
  // results
2052
2005
  {
2053
- tasks: [...], // tasks assigned to employeeId "JExotic"
2054
- projectMembers: [...], // projects with employeeId "JExotic"
2006
+ data: {
2007
+ tasks: [...], // tasks assigned to employeeId "JExotic"
2008
+ projectMembers: [...], // projects with employeeId "JExotic"
2009
+ },
2010
+ cursor: null,
2055
2011
  }
2056
2012
 
2057
2013
  {
@@ -2158,195 +2114,6 @@ For example, the `contributions` collection is named such because when given an
2158
2114
 
2159
2115
  In the case of `assignments`, we receive a subset of `contributions` when supplying an `employeeId`: Only the tasks and projects they are "assigned" are returned.
2160
2116
 
2161
- ## Filters
2162
-
2163
- > Filters are no longer the preferred way to add FilterExpressions. Checkout the [Where](#where) section to find out about how to apply FilterExpressions and ConditionExpressions.
2164
-
2165
- Building thoughtful indexes can make queries simple and performant. Sometimes you need to filter results down further. By adding Filters to your model, you can extend your queries with custom filters. Below is the traditional way you would add a filter to Dynamo's DocumentClient directly alongside how you would accomplish the same using a Filter function.
2166
-
2167
- ```json
2168
- {
2169
- "IndexName": "idx2",
2170
- "TableName": "StoreDirectory",
2171
- "ExpressionAttributeNames": {
2172
- "#rent": "rent",
2173
- "#discount": "discount",
2174
- "#pk": "idx2pk",
2175
- "#sk1": "idx2sk"
2176
- },
2177
- "ExpressionAttributeValues": {
2178
- ":rent1": "2000.00",
2179
- ":rent2": "5000.00",
2180
- ":discount1": "1000.00",
2181
- ":pk": "$mallstoredirectory_1#mallid_eastpointe",
2182
- ":sk1": "$mallstore#leaseenddate_2020-04-01#rent_",
2183
- ":sk2": "$mallstore#leaseenddate_2020-07-01#rent_"
2184
- },
2185
- "KeyConditionExpression": ",#pk = :pk and #sk1 BETWEEN :sk1 AND :sk2",
2186
- "FilterExpression": "(#rent between :rent1 and :rent2) AND #discount <= :discount1"
2187
- }
2188
- ```
2189
- ### Defined on the model
2190
-
2191
- > Deprecated but functional with 1.x
2192
-
2193
- Filters can be defined on the model and used in your query chain.
2194
-
2195
- ```javascript
2196
- /**
2197
- * Filter by low rent a specific mall or a leaseEnd withing a specific range
2198
- * @param {Object} attributes - All attributes from the model with methods for each filter operation
2199
- * @param {...*} values - Values passed when calling the filter in a query chain.
2200
- **/
2201
- filters: {
2202
- rentPromotions: function(attributes, minRent, maxRent, promotion) {
2203
- let {rent, discount} = attributes;
2204
- return `
2205
- ${rent.between(minRent, maxRent)} AND ${discount.lte(promotion)}
2206
- `
2207
- }
2208
- }
2209
-
2210
-
2211
- let StoreLocations = new Entity(model, {table: "StoreDirectory"});
2212
- let maxRent = "5000.00";
2213
- let minRent = "2000.00";
2214
- let promotion = "1000.00";
2215
- let stores = await MallStores.query
2216
- .stores({ mallId: "EastPointe" })
2217
- .between({ leaseEndDate: "2020-04-01" }, { leaseEndDate: "2020-07-01" })
2218
- .rentPromotions(minRent, maxRent, promotion)
2219
- .go();
2220
-
2221
- // Equivalent Parameters
2222
- {
2223
- IndexName: 'idx2',
2224
- TableName: 'StoreDirectory',
2225
- ExpressionAttributeNames: {
2226
- '#rent': 'rent',
2227
- '#discount': 'discount',
2228
- '#pk': 'idx2pk',
2229
- '#sk1': 'idx2sk'
2230
- },
2231
- ExpressionAttributeValues: {
2232
- ':rent1': '2000.00',
2233
- ':rent2': '5000.00',
2234
- ':discount1': '1000.00',
2235
- ':pk': '$mallstoredirectory_1#mallid_eastpointe',
2236
- ':sk1': '$mallstore#leaseenddate_2020-04-01#rent_',
2237
- ':sk2': '$mallstore#leaseenddate_2020-07-01#rent_'
2238
- },
2239
- KeyConditionExpression: '#pk = :pk and #sk1 BETWEEN :sk1 AND :sk2',
2240
- FilterExpression: '(#rent between :rent1 and :rent2) AND #discount <= :discount1'
2241
- }
2242
- ```
2243
- ### Defined via Filter method after query operators
2244
-
2245
- > Filters are no longer the preferred way to add FilterExpressions. Checkout the [Where](#where) section to find out about how to apply FilterExpressions and ConditionExpressions.
2246
-
2247
- The easiest way to use filters is to use them inline in your query chain.
2248
-
2249
- ```javascript
2250
- let StoreLocations = new Entity(model, {table: "StoreDirectory"});
2251
- let maxRent = "5000.00";
2252
- let minRent = "2000.00";
2253
- let promotion = "1000.00";
2254
- let stores = await StoreLocations.query
2255
- .leases({ mallId: "EastPointe" })
2256
- .between({ leaseEndDate: "2020-04-01" }, { leaseEndDate: "2020-07-01" })
2257
- .filter(({rent, discount}) => `
2258
- ${rent.between(minRent, maxRent)} AND ${discount.lte(promotion)}
2259
- `)
2260
- .go();
2261
-
2262
- // Equivalent Parameters
2263
- {
2264
- IndexName: 'idx2',
2265
- TableName: 'StoreDirectory',
2266
- ExpressionAttributeNames: {
2267
- '#rent': 'rent',
2268
- '#discount': 'discount',
2269
- '#pk': 'idx2pk',
2270
- '#sk1': 'idx2sk'
2271
- },
2272
- ExpressionAttributeValues: {
2273
- ':rent1': '2000.00',
2274
- ':rent2': '5000.00',
2275
- ':discount1': '1000.00',
2276
- ':pk': '$mallstoredirectory_1#mallid_eastpointe',
2277
- ':sk1': '$mallstore#leaseenddate_2020-04-01#rent_',
2278
- ':sk2': '$mallstore#leaseenddate_2020-07-01#rent_'
2279
- },
2280
- KeyConditionExpression: '#pk = :pk and #sk1 BETWEEN :sk1 AND :sk2',
2281
- FilterExpression: '(#rent between :rent1 and :rent2) AND #discount <= :discount1'
2282
- }
2283
- ```
2284
-
2285
- Filter functions allow you to write a `FilterExpression` without having to worry about the complexities of expression attributes. To accomplish this, ElectroDB injects an object `attributes` as the first parameter to all Filter Functions. This object contains every Attribute defined in the Entity's Model with the following operators as methods:
2286
-
2287
- operator | example | result
2288
- | ----------- | -------------------------------- |
2289
- `gte` | `rent.gte(maxRent)` | `#rent >= :rent1`
2290
- `gt` | `rent.gt(maxRent)` | `#rent > :rent1`
2291
- `lte` | `rent.lte(maxRent)` | `#rent <= :rent1`
2292
- `lt` | `rent.lt(maxRent)` | `#rent < :rent1`
2293
- `eq` | `rent.eq(maxRent)` | `#rent = :rent1`
2294
- `ne` | `rent.ne(maxRent)` | `#rent <> :rent1`
2295
- `begins` | `rent.begins(maxRent)` | `begins_with(#rent, :rent1)`
2296
- `exists` | `rent.exists()` | `attribute_exists(#rent)`
2297
- `notExists` | `rent.notExists()` | `attribute_not_exists(#rent)`
2298
- `contains` | `rent.contains(maxRent)` | `contains(#rent = :rent1)`
2299
- `notContains` | `rent.notContains(maxRent)` | `not contains(#rent = :rent1)`
2300
- `between` | `rent.between(minRent, maxRent)` | `(#rent between :rent1 and :rent2)`
2301
- `name` | `rent.name()` | `#rent`
2302
- `value` | `rent.value(maxRent)` | `:rent1`
2303
-
2304
- This functionality allows you to write the remaining logic of your `FilterExpression` with ease. Add complex nested `and`/`or` conditions or other `FilterExpression` logic while ElectroDB handles the `ExpressionAttributeNames` and `ExpressionAttributeValues`.
2305
-
2306
- ### Multiple Filters
2307
-
2308
- > Filters are no longer the preferred way to add FilterExpressions. Checkout the [Where](#where) section to find out about how to apply FilterExpressions and ConditionExpressions.
2309
-
2310
- It is possible to chain together multiple filters. The resulting FilterExpressions are concatenated with an implicit `AND` operator.
2311
-
2312
- ```javascript
2313
- let MallStores = new Entity(model, {table: "StoreDirectory"});
2314
- let stores = await MallStores.query
2315
- .leases({ mallId: "EastPointe" })
2316
- .between({ leaseEndDate: "2020-04-01" }, { leaseEndDate: "2020-07-01" })
2317
- .filter(({ rent, discount }) => `
2318
- ${rent.between("2000.00", "5000.00")} AND ${discount.eq("1000.00")}
2319
- `)
2320
- .filter(({ category }) => `
2321
- ${category.eq("food/coffee")}
2322
- `)
2323
- .go();
2324
-
2325
- // Equivalent Parameters
2326
- {
2327
- TableName: 'StoreDirectory',
2328
- ExpressionAttributeNames: {
2329
- '#rent': 'rent',
2330
- '#discount': 'discount',
2331
- '#category': 'category',
2332
- '#pk': 'idx2pk',
2333
- '#sk1': 'idx2sk'
2334
- },
2335
- ExpressionAttributeValues: {
2336
- ':rent1': '2000.00',
2337
- ':rent2': '5000.00',
2338
- ':discount1': '1000.00',
2339
- ':category1': 'food/coffee',
2340
- ':pk': '$mallstoredirectory_1#mallid_eastpointe',
2341
- ':sk1': '$mallstore#leaseenddate_2020-04-01#storeid_',
2342
- ':sk2': '$mallstore#leaseenddate_2020-07-01#storeid_'
2343
- },
2344
- KeyConditionExpression: '#pk = :pk and #sk1 BETWEEN :sk1 AND :sk2',
2345
- IndexName: 'idx2',
2346
- FilterExpression: '(#rent between :rent1 and :rent2) AND (#discount = :discount1 AND #category = :category1)'
2347
- }
2348
- ```
2349
-
2350
2117
  ## Where
2351
2118
 
2352
2119
  > The `where()` method is an improvement on the `filter()` method. Unlike `filter`, `where` will be compatible with upcoming features related to complex types.
@@ -2626,7 +2393,7 @@ attributes | string[] | _(all attributes)_ | The `attributes` option allo
2626
2393
 
2627
2394
  ElectroDB queries use DynamoDB's `query` method to find records based on your table's indexes.
2628
2395
 
2629
- > _NOTE: By default, ElectroDB will paginate through all items that match your query. To limit the number of items ElectroDB will retrieve, read more about the [Query Options](#query-options) `pages` and `limit`, or use the ElectroDB [Pagination API](#page) for fine-grain pagination support._
2396
+ > _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._
2630
2397
 
2631
2398
  Forming a composite **Partition Key** and **Sort Key** is a critical step in planning **Access Patterns** in **DynamoDB**. When planning composite keys, it is crucial to consider the order in which they are *composed*. As of the time of writing this documentation, **DynamoDB** has the following constraints that should be taken into account when planning your **Access Patterns**:
2632
2399
  1. You must always supply the **Partition Key** in full for all queries to **DynamoDB**.
@@ -2827,7 +2594,7 @@ The `StoreLocations` entity above, using just the `stores` **Index** alone enabl
2827
2594
  3. All `LatteLarrys` locations inside a specific *Mall*
2828
2595
  4. A specific `LatteLarrys` inside of a *Mall* and *Building*
2829
2596
 
2830
- ## Query Chains
2597
+ ## Performing Queries
2831
2598
  Queries in ***ElectroDB*** are built around the **Access Patterns** defined in the Schema and are capable of using partial key **Composite Attributes** to create performant lookups. To accomplish this, ***ElectroDB*** offers a predictable chainable API.
2832
2599
 
2833
2600
  > Examples in this section using the `StoreLocations` schema defined [above](#shopping-mall-stores) and can be directly experiment with on runkit: https://runkit.com/tywalch/electrodb-building-queries
@@ -2838,13 +2605,14 @@ The methods: Get (`get`), Create (`put`), Update (`update`), and Delete (`delete
2838
2605
 
2839
2606
  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)
2840
2607
 
2841
- > _NOTE: By default, ElectroDB will paginate through all items that match your query. To limit the number of items ElectroDB will retrieve, read more about the [Query Options](#query-options) `pages` and `limit`, or use the ElectroDB [Pagination API](#page) for fine-grain pagination support._
2608
+ > _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._
2842
2609
 
2843
2610
  ### Get Method
2844
2611
  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.
2845
2612
 
2846
2613
  > _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._
2847
2614
 
2615
+ Example:
2848
2616
  ```javascript
2849
2617
  let results = await StoreLocations.get({
2850
2618
  storeId: "LatteLarrys",
@@ -2852,15 +2620,24 @@ let results = await StoreLocations.get({
2852
2620
  buildingId: "F34",
2853
2621
  cityId: "Atlanta1"
2854
2622
  }).go();
2623
+ ```
2624
+ Response Format:
2625
+ ```typescript
2626
+ {
2627
+ data: Array<YOUR_SCHEMA>,
2628
+ cursor: string | undefined
2629
+ }
2630
+ ```
2855
2631
 
2856
- // Equivalent Params:
2857
- // {
2858
- // Key: {
2859
- // pk: "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
2860
- // sk: "$mallstore_1#buildingid_f34#storeid_lattelarrys"
2861
- // },
2862
- // TableName: 'StoreDirectory'
2863
- // }
2632
+ Equivalent DocClient Parameters:
2633
+ ```json
2634
+ {
2635
+ "Key": {
2636
+ "pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
2637
+ "sk": "$mallstore_1#buildingid_f34#storeid_lattelarrys"
2638
+ },
2639
+ "TableName": "YOUR_TABLE_NAME"
2640
+ }
2864
2641
  ```
2865
2642
 
2866
2643
  ### Batch Get
@@ -2879,6 +2656,7 @@ If you set the [Query Option](#query-options) `concurrent` to `2`, ElectroDB wil
2879
2656
 
2880
2657
  It is important to consider your Table's throughput considerations when setting this value.
2881
2658
 
2659
+ Example:
2882
2660
  ```javascript
2883
2661
  let [results, unprocessed] = await StoreLocations.get([
2884
2662
  {
@@ -2894,24 +2672,34 @@ let [results, unprocessed] = await StoreLocations.get([
2894
2672
  cityId: "Madison2"
2895
2673
  }
2896
2674
  ]).go({concurrent: 1}); // `concurrent` value is optional and default's to `1`
2675
+ ```
2897
2676
 
2898
- // Equivalent Params:
2899
- // {
2900
- // "RequestItems": {
2901
- // "electro": {
2902
- // "Keys": [
2903
- // {
2904
- // "pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
2905
- // "sk": "$mallstore_1#buildingid_f34#storeid_lattelarrys"
2906
- // },
2907
- // {
2908
- // "pk": "$mallstoredirectory#cityid_madison2#mallid_westend",
2909
- // "sk": "$mallstore_1#buildingid_a21#storeid_mochajoes"
2910
- // }
2911
- // ]
2912
- // }
2913
- // }
2914
- // }
2677
+ Response Format:
2678
+ ```typescript
2679
+ {
2680
+ data: Array<YOUR_SCHEMA>,
2681
+ unprocessed: Array<YOUR_COMPOSITE_ATTRIBUTES>
2682
+ }
2683
+ ```
2684
+
2685
+ Equivalent DocClient Parameters:
2686
+ ```json
2687
+ {
2688
+ "RequestItems": {
2689
+ "YOUR_TABLE_NAME": {
2690
+ "Keys": [
2691
+ {
2692
+ "pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
2693
+ "sk": "$mallstore_1#buildingid_f34#storeid_lattelarrys"
2694
+ },
2695
+ {
2696
+ "pk": "$mallstoredirectory#cityid_madison2#mallid_westend",
2697
+ "sk": "$mallstore_1#buildingid_a21#storeid_mochajoes"
2698
+ }
2699
+ ]
2700
+ }
2701
+ }
2702
+ }
2915
2703
  ```
2916
2704
 
2917
2705
  The two-dimensional array returned by batch get most easily used when deconstructed into two variables, in the above case: `results` and `unprocessed`.
@@ -2925,6 +2713,7 @@ Elements of the `unprocessed` array are unlike results received from a query. In
2925
2713
  ### Delete Method
2926
2714
  Provide all Table Index composite attributes in an object to the `delete` method to delete a record.
2927
2715
 
2716
+ Example:
2928
2717
  ```javascript
2929
2718
  await StoreLocations.delete({
2930
2719
  storeId: "LatteLarrys",
@@ -2932,15 +2721,24 @@ await StoreLocations.delete({
2932
2721
  buildingId: "F34",
2933
2722
  cityId: "Atlanta1"
2934
2723
  }).go();
2724
+ ```
2935
2725
 
2936
- // Equivalent Params:
2937
- // {
2938
- // Key: {
2939
- // pk: "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
2940
- // sk: "$mallstore_1#buildingid_f34#storeid_lattelarrys"
2941
- // },
2942
- // TableName: 'StoreDirectory'
2943
- // }
2726
+ Response Format:
2727
+ ```typescript
2728
+ {
2729
+ data: { YOUR_SCHEMA }
2730
+ }
2731
+ ```
2732
+
2733
+ Equivalent DocClient Parameters:
2734
+ ```json
2735
+ {
2736
+ "Key": {
2737
+ "pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
2738
+ "sk": "$mallstore_1#buildingid_f34#storeid_lattelarrys"
2739
+ },
2740
+ "TableName": "YOUR_TABLE_NAME"
2741
+ }
2944
2742
  ```
2945
2743
 
2946
2744
  ### Batch Write Delete Records
@@ -2959,6 +2757,7 @@ If you set the [Query Option](#query-options) `concurrent` to `2`, ElectroDB wil
2959
2757
 
2960
2758
  It is important to consider your Table's throughput considerations when setting this value.
2961
2759
 
2760
+ Example:
2962
2761
  ```javascript
2963
2762
  let unprocessed = await StoreLocations.delete([
2964
2763
  {
@@ -2974,8 +2773,17 @@ let unprocessed = await StoreLocations.delete([
2974
2773
  cityId: "LosAngeles1"
2975
2774
  }
2976
2775
  ]).go({concurrent: 1}); // `concurrent` value is optional and default's to `1`
2776
+ ```
2777
+
2778
+ Response Format:
2779
+ ```typescript
2780
+ {
2781
+ unprocessed: Array<YOUR_COMPOSITE_ATTRIBUTES>
2782
+ }
2783
+ ```
2977
2784
 
2978
- // Equivalent Params:
2785
+ Equivalent DocClient Parameters:
2786
+ ```json
2979
2787
  {
2980
2788
  "RequestItems": {
2981
2789
  "StoreDirectory": [
@@ -3005,7 +2813,9 @@ Elements of the `unprocessed` array are unlike results received from a query. In
3005
2813
  ### Put Record
3006
2814
  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.
3007
2815
 
3008
- This example includes an optional conditional expression
2816
+ Note: This example includes an optional conditional expression
2817
+
2818
+ Example:
3009
2819
  ```javascript
3010
2820
  await StoreLocations
3011
2821
  .put({
@@ -3020,8 +2830,17 @@ await StoreLocations
3020
2830
  })
3021
2831
  .where((attr, op) => op.eq(attr.rent, "4500.00"))
3022
2832
  .go()
2833
+ ```
3023
2834
 
3024
- // Equivalent Params:
2835
+ Response Format:
2836
+ ```typescript
2837
+ {
2838
+ data: { YOUR_SCHEMA }
2839
+ }
2840
+ ```
2841
+
2842
+ Equivalent DocClient Parameters:
2843
+ ```json
3025
2844
  {
3026
2845
  "Item": {
3027
2846
  "cityId": "Atlanta1",
@@ -3069,6 +2888,7 @@ If you set the [Query Option](#query-options) `concurrent` to `2`, ElectroDB wil
3069
2888
 
3070
2889
  It is important to consider your Table's throughput considerations when setting this value.
3071
2890
 
2891
+ Example:
3072
2892
  ```javascript
3073
2893
  let unprocessed = await StoreLocations.put([
3074
2894
  {
@@ -3092,8 +2912,17 @@ let unprocessed = await StoreLocations.put([
3092
2912
  rent: "1500.00"
3093
2913
  }
3094
2914
  ]).go({concurrent: 1}); // `concurrent` value is optional and default's to `1`
2915
+ ```
3095
2916
 
3096
- // Equivalent Params:
2917
+ Response Format:
2918
+ ```typescript
2919
+ {
2920
+ unprocessed: Array<YOUR_COMPOSITE_ATTRIBUTES>
2921
+ }
2922
+ ```
2923
+
2924
+ Equivalent DocClient Parameters:
2925
+ ```json
3097
2926
  {
3098
2927
  "RequestItems": {
3099
2928
  "StoreDirectory": [
@@ -3229,11 +3058,23 @@ For the defined indexes:
3229
3058
 
3230
3059
  A user could update `attr4` alone because ElectroDB is able to leverage the value for `attr2` from values supplied to the `update()` method:
3231
3060
 
3061
+
3062
+ Example:
3232
3063
  ```typescript
3233
3064
  entity.update({ attr1: "value1", attr2: "value2" })
3234
3065
  .set({ attr4: "value4" })
3235
3066
  .go();
3067
+ ```
3068
+
3069
+ Response Format:
3070
+ ```typescript
3071
+ {
3072
+ data: { YOUR_SCHEMA }
3073
+ }
3074
+ ```
3236
3075
 
3076
+ Equivalent DocClient Parameters:
3077
+ ```json
3237
3078
  {
3238
3079
  "UpdateExpression": "SET #attr4 = :attr4_u0, #gsi1sk = :gsi1sk_u0, #attr1 = :attr1_u0, #attr2 = :attr2_u0",
3239
3080
  "ExpressionAttributeNames": {
@@ -3249,7 +3090,7 @@ entity.update({ attr1: "value1", attr2: "value2" })
3249
3090
  ":attr1_u0": "value1",
3250
3091
  ":attr2_u0": "value2"
3251
3092
  },
3252
- "TableName": "test_table",
3093
+ "TableName": "YOUR_TABLE_NAME",
3253
3094
  "Key": {
3254
3095
  "pk": "$service#attr1_value1",
3255
3096
  "sk": "$entity_version#attr2_value2"
@@ -3263,14 +3104,24 @@ entity.update({ attr1: "value1", attr2: "value2" })
3263
3104
 
3264
3105
  The `set()` method will accept all attributes defined on the model. Provide a value to apply or replace onto the item.
3265
3106
 
3107
+ Example:
3266
3108
  ```javascript
3267
3109
  await StoreLocations
3268
3110
  .update({cityId, mallId, storeId, buildingId})
3269
3111
  .set({category: "food/meal"})
3270
3112
  .where((attr, op) => op.eq(attr.category, "food/coffee"))
3271
3113
  .go()
3114
+ ```
3115
+
3116
+ Response Format:
3117
+ ```typescript
3118
+ {
3119
+ data: { YOUR_SCHEMA }
3120
+ }
3121
+ ```
3272
3122
 
3273
- // Equivalent Params:
3123
+ Equivalent DocClient Parameters:
3124
+ ```json
3274
3125
  {
3275
3126
  "UpdateExpression": "SET #category = :category",
3276
3127
  "ExpressionAttributeNames": {
@@ -3295,14 +3146,24 @@ The `remove()` method will accept all attributes defined on the model. Unlike mo
3295
3146
 
3296
3147
  > _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._
3297
3148
 
3149
+ Example:
3298
3150
  ```javascript
3299
3151
  await StoreLocations
3300
3152
  .update({cityId, mallId, storeId, buildingId})
3301
3153
  .remove(["category"])
3302
3154
  .where((attr, op) => op.eq(attr.category, "food/coffee"))
3303
3155
  .go()
3156
+ ```
3304
3157
 
3305
- // Equivalent Params:
3158
+ Response Format:
3159
+ ```typescript
3160
+ {
3161
+ data: { YOUR_SCHEMA }
3162
+ }
3163
+ ```
3164
+
3165
+ Equivalent DocClient Parameters:
3166
+ ```json
3306
3167
  {
3307
3168
  "UpdateExpression": "REMOVE #category",
3308
3169
  "ExpressionAttributeNames": {
@@ -3326,6 +3187,7 @@ The `add()` method will accept attributes with type `number`, `set`, and `any` d
3326
3187
 
3327
3188
  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`.
3328
3189
 
3190
+ Example:
3329
3191
  ```javascript
3330
3192
  const newTenant = client.createSet("larry");
3331
3193
 
@@ -3337,8 +3199,17 @@ await StoreLocations
3337
3199
  })
3338
3200
  .where((attr, op) => op.eq(attr.category, "food/coffee"))
3339
3201
  .go()
3202
+ ```
3203
+
3204
+ Response Format:
3205
+ ```typescript
3206
+ {
3207
+ data: { YOUR_SCHEMA }
3208
+ }
3209
+ ```
3340
3210
 
3341
- // Equivalent Params:
3211
+ Equivalent DocClient Parameters:
3212
+ ```json
3342
3213
  {
3343
3214
  "UpdateExpression": "SET #rent = #rent + :rent0 ADD #tenant :tenant0",
3344
3215
  "ExpressionAttributeNames": {
@@ -3364,14 +3235,24 @@ await StoreLocations
3364
3235
 
3365
3236
  The `subtract()` method will accept attributes with type `number`. In the case of a `number` attribute, provide a number to _subtract_ from the existing attribute's value on the item.
3366
3237
 
3238
+ Example:
3367
3239
  ```javascript
3368
3240
  await StoreLocations
3369
3241
  .update({cityId, mallId, storeId, buildingId})
3370
3242
  .subtract({deposit: 500})
3371
3243
  .where((attr, op) => op.eq(attr.category, "food/coffee"))
3372
3244
  .go()
3245
+ ```
3373
3246
 
3374
- // Equivalent Params:
3247
+ Response Format:
3248
+ ```typescript
3249
+ {
3250
+ data: { YOUR_SCHEMA }
3251
+ }
3252
+ ```
3253
+
3254
+ Equivalent DocClient Parameters:
3255
+ ```json
3375
3256
  {
3376
3257
  "UpdateExpression": "SET #deposit = #deposit - :deposit0",
3377
3258
  "ExpressionAttributeNames": {
@@ -3395,6 +3276,7 @@ await StoreLocations
3395
3276
 
3396
3277
  The `append()` method will accept attributes with type `any`. This is a convenience method for working with DynamoDB lists, and is notably different that [`set`](#update-method-set) because it will add an element to an existing array, rather than overwrite the existing value.
3397
3278
 
3279
+ Example:
3398
3280
  ```javascript
3399
3281
  await StoreLocations
3400
3282
  .update({cityId, mallId, storeId, buildingId})
@@ -3406,8 +3288,17 @@ await StoreLocations
3406
3288
  })
3407
3289
  .where((attr, op) => op.eq(attr.category, "food/coffee"))
3408
3290
  .go()
3291
+ ```
3292
+
3293
+ Response Format:
3294
+ ```typescript
3295
+ {
3296
+ data: { YOUR_SCHEMA }
3297
+ }
3298
+ ```
3409
3299
 
3410
- // Equivalent Params:
3300
+ Equivalent DocClient Parameters:
3301
+ ```json
3411
3302
  {
3412
3303
  "UpdateExpression": "SET #rentalAgreement = list_append(#rentalAgreement, :rentalAgreement0)",
3413
3304
  "ExpressionAttributeNames": {
@@ -3436,14 +3327,24 @@ await StoreLocations
3436
3327
 
3437
3328
  The `delete()` method will accept attributes with type `any` or `set` . This operation removes items from a the `contract` attribute, defined as a `set` attribute.
3438
3329
 
3330
+ Example:
3439
3331
  ```javascript
3440
3332
  await StoreLocations
3441
3333
  .update({cityId, mallId, storeId, buildingId})
3442
3334
  .delete({contact: ['555-345-2222']})
3443
3335
  .where((attr, op) => op.eq(attr.category, "food/coffee"))
3444
3336
  .go()
3337
+ ```
3445
3338
 
3446
- // Equivalent Params:
3339
+ Response Format:
3340
+ ```typescript
3341
+ {
3342
+ data: { YOUR_SCHEMA }
3343
+ }
3344
+ ```
3345
+
3346
+ Equivalent DocClient Parameters:
3347
+ ```json
3447
3348
  {
3448
3349
  "UpdateExpression": "DELETE #contact :contact0",
3449
3350
  "ExpressionAttributeNames": {
@@ -3485,6 +3386,7 @@ operation | example | result
3485
3386
  `value` | `value(rent, amount)` | `:rent1` | Create a reference to a particular value, can be passed to other operation that allows leveraging existing attribute values in calculating new values
3486
3387
  `ifNotExists` | `ifNotExists(rent, amount)` | `#rent = if_not_exists(#rent, :rent0)` | Update a property's value only if that property doesn't yet exist on the record
3487
3388
 
3389
+ Example:
3488
3390
  ```javascript
3489
3391
  await StoreLocations
3490
3392
  .update({cityId, mallId, storeId, buildingId})
@@ -3506,8 +3408,17 @@ await StoreLocations
3506
3408
  })
3507
3409
  .where((attr, op) => op.eq(attr.category, "food/coffee"))
3508
3410
  .go()
3411
+ ```
3412
+
3413
+ Response Format:
3414
+ ```typescript
3415
+ {
3416
+ data: { YOUR_SCHEMA }
3417
+ }
3418
+ ```
3509
3419
 
3510
- // Equivalent Params:
3420
+ Equivalent DocClient Parameters:
3421
+ ```json
3511
3422
  {
3512
3423
  "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",
3513
3424
  "ExpressionAttributeNames": {
@@ -3619,6 +3530,7 @@ When scanning for rows, you can use filters the same as you would any query. For
3619
3530
 
3620
3531
  *Note: `Scan` functionality will be scoped to your Entity. This means your results will only include records that match the Entity defined in the model.*
3621
3532
 
3533
+ Example:
3622
3534
  ```javascript
3623
3535
  await StoreLocations.scan
3624
3536
  .where(({category}, {eq}) => `
@@ -3628,8 +3540,18 @@ await StoreLocations.scan
3628
3540
  ${between(leaseEndDate, "2020-03", "2020-04")}
3629
3541
  `)
3630
3542
  .go()
3543
+ ```
3544
+
3545
+ Response Format:
3546
+ ```typescript
3547
+ {
3548
+ data: Array<YOUR_SCHEMA>,
3549
+ cursor: string | undefined
3550
+ }
3551
+ ```
3631
3552
 
3632
- // Equivalent Params:
3553
+ Equivalent DocClient Parameters:
3554
+ ```json
3633
3555
  {
3634
3556
  "TableName": "StoreDirectory",
3635
3557
  "ExpressionAttributeNames": {
@@ -3664,20 +3586,68 @@ await StoreLocations.remove({
3664
3586
  buildingId: "F34",
3665
3587
  cityId: "Atlanta1"
3666
3588
  }).go();
3589
+ ```
3667
3590
 
3668
- // Equivalent Params:
3669
- // {
3670
- // Key: {
3671
- // pk: "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
3672
- // sk: "$mallstore_1#buildingid_f34#storeid_lattelarrys"
3673
- // },
3674
- // TableName: 'StoreDirectory'
3675
- // ConditionExpression: 'attribute_exists(pk) AND attribute_exists(sk)'
3676
- // }
3591
+ Response Format:
3592
+ ```typescript
3593
+ {
3594
+ data: { YOUR_SCHEMA }
3595
+ }
3596
+ ```
3597
+
3598
+ Equivalent DocClient Parameters:
3599
+ ```json
3600
+ {
3601
+ "Key": {
3602
+ "pk": "$mallstoredirectory#cityid_atlanta1#mallid_eastpointe",
3603
+ "sk": "$mallstore_1#buildingid_f34#storeid_lattelarrys"
3604
+ },
3605
+ "TableName": "YOUR_TABLE_TABLE"
3606
+ "ConditionExpression": "attribute_exists(pk) AND attribute_exists(sk)"
3607
+ }
3677
3608
  ```
3678
3609
 
3679
3610
  ### Patch Record
3680
3611
 
3612
+ ```javascript
3613
+ await entity.update({ attr1: "value1", attr2: "value2" })
3614
+ .set({ attr4: "value4" })
3615
+ .go();
3616
+ ```
3617
+
3618
+ Response Format:
3619
+ ```typescript
3620
+ {
3621
+ data: { YOUR_SCHEMA }
3622
+ }
3623
+ ```
3624
+
3625
+ Equivalent DocClient Parameters:
3626
+ ```json
3627
+ {
3628
+ "UpdateExpression": "SET #attr4 = :attr4_u0, #gsi1sk = :gsi1sk_u0, #attr1 = :attr1_u0, #attr2 = :attr2_u0",
3629
+ "ExpressionAttributeNames": {
3630
+ "#attr4": "attr4",
3631
+ "#gsi1sk": "gsi1sk",
3632
+ "#attr1": "attr1",
3633
+ "#attr2": "attr2"
3634
+ },
3635
+ "ExpressionAttributeValues": {
3636
+ ":attr4_u0": "value6",
3637
+ // This index was successfully built
3638
+ ":gsi1sk_u0": "$update-edgecases_1#attr2_value2#attr4_value6",
3639
+ ":attr1_u0": "value1",
3640
+ ":attr2_u0": "value2"
3641
+ },
3642
+ "TableName": "YOUR_TABLE_NAME",
3643
+ "Key": {
3644
+ "pk": "$service#attr1_value1",
3645
+ "sk": "$entity_version#attr2_value2"
3646
+ },
3647
+ "ConditionExpression": "attribute_exists(pk) AND attribute_exists(sk)"
3648
+ }
3649
+ ```
3650
+
3681
3651
  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.
3682
3652
 
3683
3653
  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()`.
@@ -3688,6 +3658,7 @@ In DynamoDB, `put` operations by default will overwrite a record if record being
3688
3658
 
3689
3659
  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.
3690
3660
 
3661
+ Example:
3691
3662
  ```javascript
3692
3663
  await StoreLocations
3693
3664
  .create({
@@ -3702,8 +3673,17 @@ await StoreLocations
3702
3673
  })
3703
3674
  .where((attr, op) => op.eq(attr.rent, "4500.00"))
3704
3675
  .go()
3676
+ ```
3677
+
3678
+ Response Format:
3679
+ ```typescript
3680
+ {
3681
+ data: { YOUR_SCHEMA }
3682
+ }
3683
+ ```
3705
3684
 
3706
- // Equivalent Params:
3685
+ Equivalent DocClient Parameters:
3686
+ ```json
3707
3687
  {
3708
3688
  "Item": {
3709
3689
  "cityId": "Atlanta1",
@@ -3743,13 +3723,24 @@ DynamoDB offers three methods to query records: `get`, `query`, and `scan`. In *
3743
3723
 
3744
3724
  The Find method is useful when the index chosen does not matter or is not known. If your secondary indexes do not contain all attributes then this method might not be right for you. The mechanism that picks the best index for a given payload is subject to improvement and change without triggering a breaking change release version.
3745
3725
 
3726
+ Example:
3746
3727
  ```javascript
3747
3728
  await StoreLocations.find({
3748
3729
  mallId: "EastPointe",
3749
3730
  buildingId: "BuildingA1",
3750
3731
  }).go()
3732
+ ```
3733
+
3734
+ Response Format:
3735
+ ```typescript
3736
+ {
3737
+ data: Array<YOUR_SCHEMA>,
3738
+ cursor: string | undefined
3739
+ }
3740
+ ```
3751
3741
 
3752
- // Equivalent Params:
3742
+ Equivalent DocClient Parameters:
3743
+ ```json
3753
3744
  {
3754
3745
  "KeyConditionExpression": "#pk = :pk and begins_with(#sk1, :sk1)",
3755
3746
  "TableName": "StoreDirectory",
@@ -3777,7 +3768,7 @@ Match is a convenience method based off of ElectroDB's [find](#find-records) met
3777
3768
 
3778
3769
  Match differs from [Find](#find-records) in that it will also include all supplied values into a query filter.
3779
3770
 
3780
-
3771
+ Example:
3781
3772
  ```javascript
3782
3773
  await StoreLocations.find({
3783
3774
  mallId: "EastPointe",
@@ -3785,8 +3776,18 @@ await StoreLocations.find({
3785
3776
  leaseEndDate: "2020-03-22",
3786
3777
  rent: "1500.00"
3787
3778
  }).go()
3779
+ ```
3788
3780
 
3789
- // Equivalent Params:
3781
+ Response Format:
3782
+ ```typescript
3783
+ {
3784
+ data: Array<YOUR_SCHEMA>,
3785
+ cursor: string | undefined
3786
+ }
3787
+ ```
3788
+
3789
+ Equivalent DocClient Parameters:
3790
+ ```json
3790
3791
  {
3791
3792
  "KeyConditionExpression": "#pk = :pk and begins_with(#sk1, :sk1)",
3792
3793
  "TableName": "StoreDirectory",
@@ -3876,7 +3877,7 @@ The second example allows you to make queries that do include buildings such as
3876
3877
 
3877
3878
  For these reasons it is important to consider that attributes passed to the Access Pattern method are considered to be full, known, data.
3878
3879
 
3879
- ## Collection Chains
3880
+ ## Collection Queries
3880
3881
  Collections allow you to query across Entities. They can be used on `Service` instance.
3881
3882
 
3882
3883
  ```javascript
@@ -4019,7 +4020,7 @@ TaskApp.collections
4019
4020
  }
4020
4021
  ```
4021
4022
 
4022
- ## Execute Queries
4023
+ ## Executing Queries
4023
4024
  Lastly, all query chains end with either a `.go()`, `.params()`, or `page()` method invocation. These terminal methods will either execute the query to DynamoDB (`.go()`) or return formatted parameters for use with the DynamoDB docClient (`.params()`).
4024
4025
 
4025
4026
  Both `.params()` and `.go()` take a query configuration object which is detailed more in the section [Query Options](#query-options).
@@ -4072,126 +4073,73 @@ let stores = MallStores.query
4072
4073
 
4073
4074
  ```
4074
4075
 
4075
- ### Page
4076
-
4077
- > _NOTE: By Default, ElectroDB queries will paginate through all results with the [`go()`](#building-queries) method. ElectroDB's `page()` method can be used to manually iterate through DynamoDB query results._
4078
-
4079
- The `page` method _ends_ a query chain, and asynchronously queries DynamoDB with the `client` provided in the model. Unlike the `.go()`, the `.page()` method returns a tuple.
4080
-
4081
- The first element for a page query is the "pager": an object contains the composite attributes that make up the `ExclusiveStartKey` that is returned by the DynamoDB client. This is very useful in multi-tenant applications where only some composite attributes are exposed to the client, or there is a need to prevent leaking keys between entities. If there is no `ExclusiveStartKey` this value will be null. On subsequent calls to `.page()`, pass the results returned from the previous call to `.page()` or construct the composite attributes yourself.
4082
-
4083
- The "pager" includes the associated entity's Identifiers.
4084
-
4085
- > _NOTE: It is *highly recommended* to use the [query option](#query-options) `pager: "raw""` flag when using `.page()` with `scan` operations. This is because when using scan on large tables the docClient may return an `ExclusiveStartKey` for a record that does not belong to entity making the query (regardless of the filters set). In these cases ElectroDB will return null (to avoid leaking the keys of other entities) when further pagination may be needed to find your records._
4076
+ #### Entity Pagination
4086
4077
 
4087
- The second element is the results of the query, exactly as it would be returned through a `query` operation.
4078
+ ##### Pagination Cursor
4088
4079
 
4089
- > _NOTE: When calling `.page()` the first argument is reserved for the "page" returned from a previous query, the second parameter is for Query Options. For more information on the options available in the `config` object, check out the section [Query Options](#query-options)._
4080
+ All ElectroDB `query` and `scan` operations return a `cursor`, which is a stringified and copy of DynamoDB's `LastEvaluatedKey` with a `base64url` encoding.
4090
4081
 
4091
- #### Entity Pagination
4082
+ The terminal method `go()` accepts a `cursor` when executing a `query` or `scan` to continue paginating for more results. Pass the cursor from the previous query to your next query and ElectroDB will continue its pagination where it left off.
4092
4083
 
4093
- ```javascript
4094
- let [next, stores] = await MallStores.query
4084
+ ```typescript
4085
+ const results1 = await MallStores.query
4095
4086
  .leases({ mallId })
4096
- .page(); // no "pager" passed to `.page()`
4087
+ .go(); // no "cursor" passed to `.go()`
4097
4088
 
4098
- let [pageTwo, moreStores] = await MallStores.query
4089
+ const results2 = await MallStores.query
4099
4090
  .leases({ mallId })
4100
- .page(next, {}); // the "pager" from the first query (`next`) passed to the second query
4101
-
4102
- // page:
4103
- // {
4104
- // storeId: "LatteLarrys",
4105
- // mallId: "EastPointe",
4106
- // buildingId: "BuildingA1",
4107
- // unitId: "B47"
4108
- // __edb_e__: "MallStore",
4109
- // __edb_v__: "version"
4110
- // }
4111
-
4112
- // stores
4113
- // [{
4114
- // mall: '3010aa0d-5591-4664-8385-3503ece58b1c',
4115
- // leaseEnd: '2020-01-20',
4116
- // sector: '7d0f5c19-ec1d-4c1e-b613-a4cc07eb4db5',
4117
- // store: 'MNO',
4118
- // unit: 'B5',
4119
- // id: 'e0705325-d735-4fe4-906e-74091a551a04',
4120
- // building: 'BuildingE',
4121
- // category: 'food/coffee',
4122
- // rent: '0.00'
4123
- // },
4124
- // {
4125
- // mall: '3010aa0d-5591-4664-8385-3503ece58b1c',
4126
- // leaseEnd: '2020-01-20',
4127
- // sector: '7d0f5c19-ec1d-4c1e-b613-a4cc07eb4db5',
4128
- // store: 'ZYX',
4129
- // unit: 'B9',
4130
- // id: 'f201a1d3-2126-46a2-aec9-758ade8ab2ab',
4131
- // building: 'BuildingI',
4132
- // category: 'food/coffee',
4133
- // rent: '0.00'
4134
- // }]
4135
- ```
4136
-
4137
- #### Service Pagination
4138
-
4139
- > _NOTE: By Default, ElectroDB will paginate through all results with the [`query()`](#building-queries) method. ElectroDB's `page()` method can be used to manually iterate through DynamoDB query results._
4140
-
4141
- Pagination with services is also possible. Similar to [Entity Pagination](#entity-pagination), calling the `.page()` method returns a `[pager, results]` tuple. Also, similar to pagination on Entities, the pager object returned by default is a deconstruction of the returned LastEvaluatedKey.
4142
-
4143
- #### Pager Query Options
4144
-
4145
- The `.page()` method also accepts [Query Options](#query-options) just like the `.go()` and `.params()` methods. Unlike those methods, however, the `.page()` method accepts Query Options as the _second_ parameter (the first parameter is reserved for the "pager").
4091
+ .go({cursor: results1.cursor}); // Paginate by querying with the "cursor" from your first query
4146
4092
 
4147
- A notable Query Option, that is available only to the `.page()` method, is an option called `pager`. This property defines the post-processing ElectroDB should perform on a returned `LastEvaluatedKey`, as well as how ElectroDB should interpret an _incoming_ pager, to use as an ExclusiveStartKey.
4148
-
4149
- > _NOTE: Because the "pager" object is destructured from the keys DynamoDB returns as the `LastEvaluatedKey`, these composite attributes differ from the record's actual attribute values in one important way: Their string values will all be lowercase. If you intend to use these attributes in ways where their casing _will_ matter (e.g. in a `where` filter), keep in mind this may result in unexpected outcomes._
4150
-
4151
- The three options for the query option `pager` are as follows:
4152
-
4153
- ```javascript
4154
- // LastEvaluatedKey
4093
+ // results1
4155
4094
  {
4156
- pk: '$taskapp#country_united states of america#state_oregon',
4157
- sk: '$offices_1#city_power#zip_34706#office_mobile branch',
4158
- gsi1pk: '$taskapp#office_mobile branch',
4159
- gsi1sk: '$workplaces#offices_1'
4095
+ cursor: '...'
4096
+ data: [{
4097
+ mall: '3010aa0d-5591-4664-8385-3503ece58b1c',
4098
+ leaseEnd: '2020-01-20',
4099
+ sector: '7d0f5c19-ec1d-4c1e-b613-a4cc07eb4db5',
4100
+ store: 'MNO',
4101
+ unit: 'B5',
4102
+ id: 'e0705325-d735-4fe4-906e-74091a551a04',
4103
+ building: 'BuildingE',
4104
+ category: 'food/coffee',
4105
+ rent: '0.00'
4106
+ },
4107
+ {
4108
+ mall: '3010aa0d-5591-4664-8385-3503ece58b1c',
4109
+ leaseEnd: '2020-01-20',
4110
+ sector: '7d0f5c19-ec1d-4c1e-b613-a4cc07eb4db5',
4111
+ store: 'ZYX',
4112
+ unit: 'B9',
4113
+ id: 'f201a1d3-2126-46a2-aec9-758ade8ab2ab',
4114
+ building: 'BuildingI',
4115
+ category: 'food/coffee',
4116
+ rent: '0.00'
4117
+ }]
4160
4118
  }
4161
4119
  ```
4162
4120
 
4163
- **"named" (default):** By default, ElectroDB will deconstruct the LastEvaluatedKey returned by the DocClient into it's individual composite attribute parts. The "named" option, chosen by default, also includes the Entity's column "identifiers" -- this is useful with Services where destructured pagers may be identical between more than one Entity in that Service.
4121
+ #### Service Pagination
4122
+
4123
+ Pagination with services is also possible. Similar to [Entity Pagination](#entity-pagination), calling the `.go()` method returns the following structure:
4164
4124
 
4165
- ```javascript
4166
- // {pager: "named"} | {pager: undefined}
4167
- {
4168
- "city": "power",
4169
- "country": "united states of america",
4170
- "state": "oregon",
4171
- "zip": "34706",
4172
- "office": "mobile branch",
4173
- "__edb_e__": "offices",
4174
- "__edb_v__": "1"
4125
+ ```typescript
4126
+ type GoResults = {
4127
+ cursor: string | null;
4128
+ data: {
4129
+ [entityName: string]: { /** EntityItem */ }[]
4130
+ }
4175
4131
  }
4176
4132
  ```
4177
4133
 
4178
- **"item":** Similar to "named", however without the Entity's "identifiers". If two Entities with a service have otherwise identical index definitions, using the "item" pager option can result in errors while paginating a Collection. If this is not a concern with your Service, or you are paginating with only an Entity, this option could be preferable because it has fewer properties.
4134
+ #### Pagination Query Options
4179
4135
 
4180
- ```javascript
4181
- // {pager: "item"}
4182
- {
4183
- "city": "power",
4184
- "country": "united states of america",
4185
- "state": "oregon",
4186
- "zip": "34706",
4187
- "office": "mobile branch",
4188
- }
4189
- ```
4136
+ ##### Query Option Pager
4137
+ A notable Pagination Option is `pager`. This property defines the post-processing ElectroDB should perform on a returned `LastEvaluatedKey`, as well as how ElectroDB should interpret an _incoming_ pager, to use as an ExclusiveStartKey.
4190
4138
 
4191
4139
  **"raw":** The `"raw"` option returns the LastEvaluatedKey as it was returned by the DynamoDB DocClient.
4192
4140
 
4193
- ```javascript
4194
- // {pager: "raw"}
4141
+ ```typescript
4142
+ // {pager: "raw"}
4195
4143
  {
4196
4144
  pk: '$taskapp#country_united states of america#state_oregon',
4197
4145
  sk: '$offices_1#city_power#zip_34706#office_mobile branch',
@@ -4207,14 +4155,14 @@ Simple pagination example:
4207
4155
  ```javascript
4208
4156
  async function getAllStores(mallId) {
4209
4157
  let stores = [];
4210
- let pager = null;
4158
+ let cursor = null;
4211
4159
 
4212
4160
  do {
4213
- let [next, results] = await MallStores.query
4161
+ const results = await MallStores.query
4214
4162
  .leases({ mallId })
4215
- .page(pager);
4216
- stores = [...stores, ...results];
4217
- pager = next;
4163
+ .go({ pager });
4164
+ stores = [...stores, ...results.data];
4165
+ cursor = results.cursor;
4218
4166
  } while(pager !== null);
4219
4167
 
4220
4168
  return stores;
@@ -4289,7 +4237,7 @@ await StoreLocations.query
4289
4237
  ```
4290
4238
 
4291
4239
  ## Query Options
4292
- Query options can be added the `.params()`, `.go()` and `.page()` to change query behavior or add customer parameters to a query.
4240
+ Query options can be added the `.params()` and `.go()`` to change query behavior or add customer parameters to a query.
4293
4241
 
4294
4242
  By default, **ElectroDB** enables you to work with records as the names and properties defined in the model. Additionally, it removes the need to deal directly with the docClient parameters which can be complex for a team without as much experience with DynamoDB. The Query Options object can be passed to both the `.params()` and `.go()` methods when building you query. Below are the options available:
4295
4243
 
@@ -4297,16 +4245,15 @@ By default, **ElectroDB** enables you to work with records as the names and prop
4297
4245
  {
4298
4246
  params?: object;
4299
4247
  table?: string;
4300
- raw?: boolean;
4301
- includeKeys?: boolean;
4302
- pager?: "raw" | "named" | "item";
4248
+ data?: 'raw' | 'includeKeys' | 'attributes';
4249
+ pager?: 'raw' | 'cursor';
4303
4250
  originalErr?: boolean;
4304
4251
  concurrent?: number;
4305
4252
  unprocessed?: "raw" | "item";
4306
4253
  response?: "default" | "none" | "all_old" | "updated_old" | "all_new" | "updated_new";
4307
4254
  ignoreOwnership?: boolean;
4308
4255
  limit?: number;
4309
- pages?: number;
4256
+ pages?: number | 'all';
4310
4257
  logger?: (event) => void;
4311
4258
  listeners Array<(event) => void>;
4312
4259
  preserveBatchOrder?: boolean;
@@ -4319,16 +4266,16 @@ Option | Default | Description
4319
4266
  params | `{}` | Properties added to this object will be merged onto the params sent to the document client. Any conflicts with **ElectroDB** will favor the params specified here.
4320
4267
  table | _(from constructor)_ | Use a different table than the one defined in the [Service Options](#service-options)
4321
4268
  attributes | _(all attributes)_ | The `attributes` query option allows you to specify ProjectionExpression Attributes for your `get` or `query` operation. As of `1.11.0` only root attributes are allowed to be specified.
4322
- raw | `false` | Returns query results as they were returned by the docClient.
4323
- includeKeys | `false` | By default, **ElectroDB** does not return partition, sort, or global keys in its response.
4324
- pager | `"named"` | Used in with pagination (`.pages()`) calls to override ElectroDBs default behaviour to break apart `LastEvaluatedKeys` records into composite attributes. See more detail about this in the sections for [Pager Query Options](#pager-query-options).
4269
+ data | `"attributes"` | Accepts the values `'raw'`, `'includeKeys'`, `'attributes'` or `undefined`. Use `'raw'` to return query results as they were returned by the docClient. Use `'includeKeys'` to include item partition and sort key values in your return object. By default, **ElectroDB** does not return partition, sort, or global keys in its response.
4270
+ pager | `cursor` | Used in with pagination calls to override ElectroDBs default behaviour to return a serialized string cursor. See more detail about this in the sections for [Pager Query Options](#pager-query-options).
4325
4271
  originalErr | `false` | By default, **ElectroDB** alters the stacktrace of any exceptions thrown by the DynamoDB client to give better visibility to the developer. Set this value equal to `true` to turn off this functionality and return the error unchanged.
4326
- concurrent | `1` | When performing batch operations, how many requests (1 batch operation == 1 request) to DynamoDB should ElectroDB make at one time. Be mindful of your DynamoDB throughput configurations
4272
+ concurrent | `1` | When performing batch operations, how many requests (1 batch operation == 1 request) to DynamoDB should ElectroDB make at one time. Be mindful of your DynamoDB throughput configurations.
4327
4273
  unprocessed | `"item"` | Used in batch processing to override ElectroDBs default behaviour to break apart DynamoDBs `Unprocessed` records into composite attributes. See more detail about this in the sections for [BatchGet](#batch-get), [BatchDelete](#batch-write-delete-records), and [BatchPut](#batch-write-put-records).
4328
4274
  response | `"default"` | Used as a convenience for applying the DynamoDB parameter `ReturnValues`. The options here are the same as the parameter values for the DocumentClient except lowercase. The `"none"` option will cause the method to return null and will bypass ElectroDB's response formatting -- useful if formatting performance is a concern.
4329
4275
  ignoreOwnership | `false` | By default, **ElectroDB** interrogates items returned from a query for the presence of matching entity "identifiers". This helps to ensure other entities, or other versions of an entity, are filtered from your results. If you are using ElectroDB with an existing table/dataset you can turn off this feature by setting this property to `true`.
4330
4276
  limit | _none_ | A target for the number of items to return from DynamoDB. If this option is passed, Queries on entities and through collections will paginate DynamoDB until this limit is reached or all items for that query have been returned.
4331
- pages | | How many DynamoDB pages should a query iterate through before stopping. By default ElectroDB paginate through all results for your query.
4277
+ pages | 1 | How many DynamoDB pages should a query iterate through before stopping. To have ElectroDB automatically paginate through all results, pass the string value `'all'`.
4278
+ sort | 'asc' | Convenience option for `ScanIndexForward`, to the change the order of queries based on your index's Sort Key -- valid options include 'asc' and 'desc'. [[read more](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html)]
4332
4279
  listeners | `[]` | An array of callbacks that are invoked when [internal ElectroDB events](#events) occur.
4333
4280
  logger | _none_ | A convenience option for a single event listener that semantically can be used for logging.
4334
4281
  preserveBatchOrder | `false` | When used with a [batchGet](#batch-get) operation, 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.
@@ -4477,7 +4424,7 @@ task.query
4477
4424
  ElectroDB can be supplied with callbacks (see: [logging](#logging) and [listeners](#listeners) to learn how) to be invoked after certain request lifecycles. This can be useful for logging, analytics, expanding functionality, and more. The following are events currently supported by ElectroDB -- if you would like to see additional events feel free to create a github issue to discuss your concept/need!
4478
4425
 
4479
4426
  ## Query Event
4480
- The `query` event occurs when a query is made via the terminal methods [`go()`](#go) and [`page()`](#page). The event includes the exact parameters given to the provided client, the ElectroDB method used, and the ElectroDB configuration provided.
4427
+ The `query` event occurs when a query is made via the terminal method [`go()`](#go) . The event includes the exact parameters given to the provided client, the ElectroDB method used, and the ElectroDB configuration provided.
4481
4428
 
4482
4429
  *Type:*
4483
4430
  ```typescript
@@ -4497,7 +4444,7 @@ const prop3 = "3ec9ed0c-7497-4d05-bdb8-86c09a618047";
4497
4444
 
4498
4445
  entity.update({ prop1, prop2 })
4499
4446
  .set({ prop3 })
4500
- .go()
4447
+ .go();
4501
4448
  ```
4502
4449
 
4503
4450
  *Example Output:*
@@ -4658,7 +4605,7 @@ task.query
4658
4605
  .go({ listeners: [listener1, listener2] });
4659
4606
  ```
4660
4607
 
4661
- # Errors:
4608
+ # ElectroDB Errors
4662
4609
 
4663
4610
  Error Code | Description
4664
4611
  :--------: | --------------------
@@ -4992,19 +4939,6 @@ By default ElectroDB tries to keep the stack trace close to your code, ideally t
4992
4939
 
4993
4940
  ### Unknown Errors
4994
4941
 
4995
- ### Invalid Last Evaluated Key
4996
- *Code: 5003*
4997
-
4998
- *Why this occurred:*
4999
- _Likely_ you were calling `.page()` on a `scan`. If you weren't please make an issue and include as much detail about your query as possible.
5000
-
5001
- *What to do about it:*
5002
- When paginating with *scan* queries, it is highly recommended that the query option, `{pager: "raw"}`. This is because when using scan on large tables the docClient may return an ExclusiveStartKey for a record that does not belong to entity making the query (regardless of the filters set). In these cases ElectroDB will return null (to avoid leaking the keys of other entities) when further pagination may be needed to find your records.
5003
- ```javascript
5004
- // example
5005
- myModel.scan.page(null, {pager: "raw"});
5006
- ```
5007
-
5008
4942
  ### No Owner For Pager
5009
4943
  *Code: 5004*
5010
4944
 
@@ -5130,16 +5064,7 @@ const EmployeesModel = {
5130
5064
  composite: ["team", "office", "employee"],
5131
5065
  },
5132
5066
  },
5133
- },
5134
- filters: {
5135
- upcomingCelebrations: (attributes, startDate, endDate) => {
5136
- let { dateHired, birthday } = attributes;
5137
- return `${dateHired.between(startDate, endDate)} OR ${birthday.between(
5138
- startDate,
5139
- endDate,
5140
- )}`;
5141
- },
5142
- },
5067
+ }
5143
5068
  };
5144
5069
 
5145
5070
  const TasksModel = {
@@ -5237,12 +5162,13 @@ const DynamoDB = require("aws-sdk/clients/dynamodb");
5237
5162
  const client = new DynamoDB.DocumentClient({region: "us-east-1"});
5238
5163
  const { Service } = require("electrodb");
5239
5164
  const table = "projectmanagement";
5240
- const EmployeeApp = new Service("EmployeeApp", { client, table });
5241
5165
 
5242
- EmployeeApp
5243
- .join(EmployeesModel) // EmployeeApp.entities.employees
5244
- .join(TasksModel) // EmployeeApp.entities.tasks
5245
- .join(OfficesModel); // EmployeeApp.entities.tasks
5166
+ const EmployeeApp = new Service({
5167
+ employees: EmployeesModel,
5168
+ tasks: TasksModel,
5169
+ offices: OfficesModel,
5170
+ }, { client, table });
5171
+
5246
5172
  ```
5247
5173
  ### Query Records
5248
5174
  #### All tasks and employee information for a given employee
@@ -5254,29 +5180,32 @@ EmployeeApp.collections.assignements({employee: "CBaskin"}).go();
5254
5180
  Returns the following:
5255
5181
  ```javascript
5256
5182
  {
5257
- employees: [{
5258
- employee: "cbaskin",
5259
- firstName: "carol",
5260
- lastName: "baskin",
5261
- office: "big cat rescue",
5262
- title: "owner",
5263
- team: "cool cats and kittens",
5264
- salary: "1,000,000",
5265
- manager: "",
5266
- dateHired: "1992-11-04",
5267
- birthday: "1961-06-06",
5268
- }],
5269
- tasks: [{
5270
- task: "Feed tigers",
5271
- description: "Prepare food for tigers to eat",
5272
- project: "Keep tigers alive",
5273
- employee: "cbaskin"
5274
- }, {
5275
- task: "Fill water bowls",
5276
- description: "Ensure the tigers have enough water",
5277
- project: "Keep tigers alive",
5278
- employee: "cbaskin"
5279
- }]
5183
+ data: {
5184
+ employees: [{
5185
+ employee: "cbaskin",
5186
+ firstName: "carol",
5187
+ lastName: "baskin",
5188
+ office: "big cat rescue",
5189
+ title: "owner",
5190
+ team: "cool cats and kittens",
5191
+ salary: "1,000,000",
5192
+ manager: "",
5193
+ dateHired: "1992-11-04",
5194
+ birthday: "1961-06-06",
5195
+ }],
5196
+ tasks: [{
5197
+ task: "Feed tigers",
5198
+ description: "Prepare food for tigers to eat",
5199
+ project: "Keep tigers alive",
5200
+ employee: "cbaskin"
5201
+ }, {
5202
+ task: "Fill water bowls",
5203
+ description: "Ensure the tigers have enough water",
5204
+ project: "Keep tigers alive",
5205
+ employee: "cbaskin"
5206
+ }]
5207
+ },
5208
+ cursor: '...'
5280
5209
  }
5281
5210
  ```
5282
5211
 
@@ -5289,26 +5218,29 @@ EmployeeApp.collections.workplaces({office: "big cat rescue"}).go()
5289
5218
  Returns the following:
5290
5219
  ```javascript
5291
5220
  {
5292
- employees: [{
5293
- employee: "cbaskin",
5294
- firstName: "carol",
5295
- lastName: "baskin",
5296
- office: "big cat rescue",
5297
- title: "owner",
5298
- team: "cool cats and kittens",
5299
- salary: "1,000,000",
5300
- manager: "",
5301
- dateHired: "1992-11-04",
5302
- birthday: "1961-06-06",
5303
- }],
5304
- offices: [{
5305
- office: "big cat rescue",
5306
- country: "usa",
5307
- state: "florida",
5308
- city: "tampa",
5309
- zip: "12345",
5310
- address: "123 Kitty Cat Lane"
5311
- }]
5221
+ data: {
5222
+ employees: [{
5223
+ employee: "cbaskin",
5224
+ firstName: "carol",
5225
+ lastName: "baskin",
5226
+ office: "big cat rescue",
5227
+ title: "owner",
5228
+ team: "cool cats and kittens",
5229
+ salary: "1,000,000",
5230
+ manager: "",
5231
+ dateHired: "1992-11-04",
5232
+ birthday: "1961-06-06",
5233
+ }],
5234
+ offices: [{
5235
+ office: "big cat rescue",
5236
+ country: "usa",
5237
+ state: "florida",
5238
+ city: "tampa",
5239
+ zip: "12345",
5240
+ address: "123 Kitty Cat Lane"
5241
+ }]
5242
+ },
5243
+ cursor: '...'
5312
5244
  }
5313
5245
  ```
5314
5246
 
@@ -5320,19 +5252,22 @@ EmployeeApp.entities.tasks.query.assigned({employee: "cbaskin"}).go();
5320
5252
  ```
5321
5253
  Returns the following:
5322
5254
  ```javascript
5323
- [
5324
- {
5325
- task: "Feed tigers",
5326
- description: "Prepare food for tigers to eat",
5327
- project: "Keep tigers alive",
5328
- employee: "cbaskin"
5329
- }, {
5330
- task: "Fill water bowls",
5331
- description: "Ensure the tigers have enough water",
5332
- project: "Keep tigers alive",
5333
- employee: "cbaskin"
5334
- }
5335
- ]
5255
+ {
5256
+ data: [
5257
+ {
5258
+ task: "Feed tigers",
5259
+ description: "Prepare food for tigers to eat",
5260
+ project: "Keep tigers alive",
5261
+ employee: "cbaskin"
5262
+ }, {
5263
+ task: "Fill water bowls",
5264
+ description: "Ensure the tigers have enough water",
5265
+ project: "Keep tigers alive",
5266
+ employee: "cbaskin"
5267
+ }
5268
+ ],
5269
+ cursor: '...',
5270
+ }
5336
5271
  ```
5337
5272
  #### Tasks for a given project
5338
5273
  Fulfilling [Requirement #4](#employee-app-requirements).
@@ -5341,14 +5276,17 @@ EmployeeApp.entities.tasks.query.project({project: "Murder Carol"}).go();
5341
5276
  ```
5342
5277
  Returns the following:
5343
5278
  ```javascript
5344
- [
5345
- {
5346
- task: "Hire hitman",
5347
- description: "Find someone to murder Carol",
5348
- project: "Murder Carol",
5349
- employee: "jexotic"
5350
- }
5351
- ];
5279
+ {
5280
+ data: [
5281
+ {
5282
+ task: "Hire hitman",
5283
+ description: "Find someone to murder Carol",
5284
+ project: "Murder Carol",
5285
+ employee: "jexotic"
5286
+ }
5287
+ ],
5288
+ cursor: '...'
5289
+ }
5352
5290
  ```
5353
5291
 
5354
5292
  #### Find office locations
@@ -5358,16 +5296,19 @@ EmployeeApp.entities.office.locations({country: "usa", state: "florida"}).go()
5358
5296
  ```
5359
5297
  Returns the following:
5360
5298
  ```javascript
5361
- [
5362
- {
5363
- office: "big cat rescue",
5364
- country: "usa",
5365
- state: "florida",
5366
- city: "tampa",
5367
- zip: "12345",
5368
- address: "123 Kitty Cat Lane"
5369
- }
5370
- ]
5299
+ {
5300
+ data: [
5301
+ {
5302
+ office: "big cat rescue",
5303
+ country: "usa",
5304
+ state: "florida",
5305
+ city: "tampa",
5306
+ zip: "12345",
5307
+ address: "123 Kitty Cat Lane"
5308
+ }
5309
+ ],
5310
+ cursor: '...'
5311
+ }
5371
5312
  ```
5372
5313
 
5373
5314
  #### Find employee salaries and titles
@@ -5380,70 +5321,87 @@ EmployeeApp.entities.employees
5380
5321
  ```
5381
5322
  Returns the following:
5382
5323
  ```javascript
5383
- [
5384
- {
5385
- employee: "ssaffery",
5386
- firstName: "saff",
5387
- lastName: "saffery",
5388
- office: "gw zoo",
5389
- title: "animal wrangler",
5390
- team: "keepers",
5391
- salary: "105.00",
5392
- manager: "jexotic",
5393
- dateHired: "1999-02-23",
5394
- birthday: "1960-07-11",
5395
- }
5396
- ]
5324
+ {
5325
+ data: [
5326
+ {
5327
+ employee: "ssaffery",
5328
+ firstName: "saff",
5329
+ lastName: "saffery",
5330
+ office: "gw zoo",
5331
+ title: "animal wrangler",
5332
+ team: "keepers",
5333
+ salary: "105.00",
5334
+ manager: "jexotic",
5335
+ dateHired: "1999-02-23",
5336
+ birthday: "1960-07-11",
5337
+ }
5338
+ ],
5339
+ cursor: '...'
5340
+ }
5397
5341
  ```
5398
5342
 
5399
5343
  #### Find employee birthdays or anniversaries
5400
5344
  Fulfilling [Requirement #7](#employee-app-requirements).
5401
5345
  ```javascript
5346
+ const startDate = "2020-05-01";
5347
+ const endDate = "2020-06-01";
5348
+
5402
5349
  EmployeeApp.entities.employees
5403
5350
  .workplaces({office: "gw zoo"})
5351
+ .where(({ birthday, dateHired }, { between }) => `
5352
+ ${between(dateHired, startDate, endDate)} OR
5353
+ ${between(birthday, startDate, endDate)}
5354
+ `)
5404
5355
  .upcomingCelebrations("2020-05-01", "2020-06-01")
5405
5356
  .go()
5406
5357
  ```
5407
5358
  Returns the following:
5408
5359
  ```javascript
5409
- [
5410
- {
5411
- employee: "jexotic",
5412
- firstName: "joe",
5413
- lastName: "maldonado-passage",
5414
- office: "gw zoo",
5415
- title: "tiger king",
5416
- team: "founders",
5417
- salary: "10000.00",
5418
- manager: "jlowe",
5419
- dateHired: "1999-02-23",
5420
- birthday: "1963-03-05",
5421
- }
5422
- ]
5360
+ {
5361
+ data: [
5362
+ {
5363
+ employee: "jexotic",
5364
+ firstName: "joe",
5365
+ lastName: "maldonado-passage",
5366
+ office: "gw zoo",
5367
+ title: "tiger king",
5368
+ team: "founders",
5369
+ salary: "10000.00",
5370
+ manager: "jlowe",
5371
+ dateHired: "1999-02-23",
5372
+ birthday: "1963-03-05",
5373
+ }
5374
+ ],
5375
+ cursor: '...'
5376
+ }
5423
5377
  ```
5424
5378
  #### Find direct reports
5425
5379
  Fulfilling [Requirement #8](#employee-app-requirements).
5426
5380
  ```javascript
5381
+
5427
5382
  EmployeeApp.entities.employees
5428
5383
  .reports({manager: "jlowe"})
5429
5384
  .go()
5430
5385
  ```
5431
5386
  Returns the following:
5432
5387
  ```javascript
5433
- [
5434
- {
5435
- employee: "jexotic",
5436
- firstName: "joe",
5437
- lastName: "maldonado-passage",
5438
- office: "gw zoo",
5439
- title: "tiger king",
5440
- team: "founders",
5441
- salary: "10000.00",
5442
- manager: "jlowe",
5443
- dateHired: "1999-02-23",
5444
- birthday: "1963-03-05",
5445
- }
5446
- ]
5388
+ {
5389
+ data: [
5390
+ {
5391
+ employee: "jexotic",
5392
+ firstName: "joe",
5393
+ lastName: "maldonado-passage",
5394
+ office: "gw zoo",
5395
+ title: "tiger king",
5396
+ team: "founders",
5397
+ salary: "10000.00",
5398
+ manager: "jlowe",
5399
+ dateHired: "1999-02-23",
5400
+ birthday: "1963-03-05",
5401
+ }
5402
+ ],
5403
+ cursor: '...'
5404
+ }
5447
5405
  ```
5448
5406
 
5449
5407
  ## Shopping Mall Property Management App
@@ -5480,14 +5438,16 @@ await StoreLocations.create({
5480
5438
  Returns the following:
5481
5439
  ```json
5482
5440
  {
5483
- "mallId": "EastPointe",
5484
- "storeId": "LatteLarrys",
5485
- "buildingId": "BuildingA1",
5486
- "unitId": "B47",
5487
- "category": "spite store",
5488
- "leaseEndDate": "2020-02-29",
5489
- "rent": "5000.00",
5490
- "discount": "0.00"
5441
+ "data": {
5442
+ "mallId": "EastPointe",
5443
+ "storeId": "LatteLarrys",
5444
+ "buildingId": "BuildingA1",
5445
+ "unitId": "B47",
5446
+ "category": "spite store",
5447
+ "leaseEndDate": "2020-02-29",
5448
+ "rent": "5000.00",
5449
+ "discount": "0.00"
5450
+ }
5491
5451
  }
5492
5452
  ```
5493
5453
  ---
@@ -5506,7 +5466,9 @@ await StoreLocations.update({storeId, mallId, buildingId, unitId}).set({
5506
5466
  Returns the following:
5507
5467
  ```json
5508
5468
  {
5509
- "leaseEndDate": "2021-02-28"
5469
+ "data": {
5470
+ "leaseEndDate": "2021-02-28"
5471
+ }
5510
5472
  }
5511
5473
  ```
5512
5474
 
@@ -5546,8 +5508,9 @@ let storeId = "LatteLarrys";
5546
5508
  await StoreLocations.delete({storeId, mallId, buildingId, unitId}).go();
5547
5509
  ```
5548
5510
  Returns the following:
5549
- ```
5550
- {}
5511
+
5512
+ ```json
5513
+ { "data": {} }
5551
5514
  ```
5552
5515
 
5553
5516
  ### Query Mall Records
@@ -5636,17 +5599,19 @@ If you have a need for a custom attribute type (beyond those supported by Electr
5636
5599
  ```typescript
5637
5600
  import { Entity, createCustomAttribute } from 'electrodb';
5638
5601
 
5602
+ const table = 'workplace_table';
5603
+
5639
5604
  type PersonnelRole = {
5640
5605
  type: 'employee';
5641
- startDate: string;
5642
- endDate?: string;
5606
+ startDate: number;
5607
+ endDate?: number;
5643
5608
  } | {
5644
5609
  type: 'contractor';
5645
- contractStartDate: string;
5646
- contractEndDate: string;
5610
+ contractStartDate: number;
5611
+ contractEndDate: number;
5647
5612
  };
5648
5613
 
5649
- const table = 'workplace_table';
5614
+
5650
5615
  const person = new Entity({
5651
5616
  model: {
5652
5617
  entity: 'personnel',
@@ -5665,11 +5630,11 @@ const person = new Entity({
5665
5630
  record: {
5666
5631
  pk: {
5667
5632
  field: 'pk',
5668
- compose: ['id']
5633
+ composite: ['id']
5669
5634
  },
5670
5635
  sk: {
5671
5636
  field: 'sk',
5672
- compose: [],
5637
+ composite: [],
5673
5638
  }
5674
5639
  }
5675
5640
  }
@@ -5678,7 +5643,33 @@ const person = new Entity({
5678
5643
 
5679
5644
  ## Exported Types
5680
5645
 
5681
- The following types are exported for easier use while using ElectroDB with TypeScript:
5646
+ The following types are exported for easier use while using ElectroDB with TypeScript. The naming convention for the types include three different kinds:
5647
+
5648
+ - `xResponse` -- Types with the postfix `Response` represent the returned interfaces directly from ElectroDB.
5649
+
5650
+ - `xItem` -- Types with the postfix `Item` represent an Entity row. Queries return multiple items, a get returns a single item, etc. The type for an item is inferred based on the attributes and index definitions within your model. For example if your attribute is marked as `required` then that attribute will never be undefined, if your attribute has a default value then it won't be required to be supplied on `put`, `list` attributes must be an array, etc.
5651
+
5652
+ - `xRecord` -- In some cases it is helpful to have a type that respresents all attributes of an item without nullable properties. Types with the postfix `Record` contain all properties in a non-nullable format.
5653
+
5654
+ The follow highlight many of the types exported utility types from ElectroDB:
5655
+
5656
+ ### QueryResponse Type
5657
+
5658
+ The QueryResponse type is the same type returned by an ElectroDB Query.
5659
+
5660
+ _Definition:_
5661
+
5662
+ ```typescript
5663
+ export type QueryResponse<E extends Entity<any, any, any, any>> = {
5664
+ data: EntityItem<E>;
5665
+ cursor: string | null;
5666
+ }
5667
+ ```
5668
+
5669
+ _Use:_
5670
+ ```typescript
5671
+ type EntitySchema = QueryResponse<typeof MyEntity>
5672
+ ```
5682
5673
 
5683
5674
  ### EntityRecord Type
5684
5675
 
@@ -5695,7 +5686,7 @@ type EntityRecord<E extends Entity<any, any, any, any>> =
5695
5686
 
5696
5687
  _Use:_
5697
5688
  ```typescript
5698
- type EntiySchema = EntityRecord<typeof MyEntity>
5689
+ type Item = EntityRecord<typeof MyEntity>
5699
5690
  ```
5700
5691
 
5701
5692
  ### EntityItem Type
@@ -5714,19 +5705,56 @@ export type EntityItem<E extends Entity<any, any, any, any>> =
5714
5705
  _Use:_
5715
5706
 
5716
5707
  ```typescript
5717
- type Thing = EntityItem<typeof MyEntityInstance>;
5708
+ type Item = EntityItem<typeof MyEntityInstance>;
5718
5709
  ```
5719
5710
 
5720
5711
  ### CollectionItem Type
5721
5712
 
5722
- This type represents the value returned from a collection query, and is similar to EntityItem.
5713
+ This type represents an item returned from a collection query, and is similar to EntityItem.
5714
+
5715
+ _Definition:_
5716
+ ```typescript
5717
+ export type CollectionItem<SERVICE extends Service<any>, COLLECTION extends keyof SERVICE["collections"]> =
5718
+ SERVICE extends Service<infer E>
5719
+ ? Pick<{
5720
+ [EntityName in keyof E]: E[EntityName] extends Entity<infer A, infer F, infer C, infer S>
5721
+ ? COLLECTION extends keyof CollectionAssociations<E>
5722
+ ? EntityName extends CollectionAssociations<E>[COLLECTION]
5723
+ ? ResponseItem<A,F,C,S>[]
5724
+ : never
5725
+ : never
5726
+ : never
5727
+ }, COLLECTION extends keyof CollectionAssociations<E>
5728
+ ? CollectionAssociations<E>[COLLECTION]
5729
+ : never>
5730
+ : never
5731
+ ```
5723
5732
 
5724
5733
  _Use:_
5725
5734
 
5726
- ```
5735
+ ```typescript
5727
5736
  type CollectionResults = CollectionItem<typeof MyServiceInstance, "collectionName">
5728
5737
  ```
5729
5738
 
5739
+ ### CollectionResponse
5740
+
5741
+ This type represents the value returned the collection query itself
5742
+
5743
+ _Definition:_
5744
+
5745
+ ```typescript
5746
+ export type CollectionResponse<SERVICE extends Service<any>, COLLECTION extends keyof SERVICE["collections"]> = {
5747
+ data: CollectionItem<SERVICE, COLLECTION>;
5748
+ cursor: string | null;
5749
+ }
5750
+ ```
5751
+
5752
+ _Use:_
5753
+
5754
+ ```typescript
5755
+ type CollectionResults = CollectionResponse<typeof MyServiceInstance, "collectionName">
5756
+ ```
5757
+
5730
5758
  ### CreateEntityItem Type
5731
5759
 
5732
5760
  This type represents an item that you would pass your entity's `put` or `create` method
@@ -5836,8 +5864,6 @@ Whenever using ElectroDB with existing tables/data, it is best to use the [Query
5836
5864
  .params({ignoreOwnership: true})
5837
5865
  // when querying the table
5838
5866
  .go({ignoreOwnership: true})
5839
- // when using pagination
5840
- .page(null, {ignoreOwnership: true})
5841
5867
  ```
5842
5868
 
5843
5869
  **Your existing index fields have values with mixed case:**
@@ -5862,6 +5888,22 @@ Electro is a CLI utility toolbox for extending the functionality of **ElectroDB*
5862
5888
 
5863
5889
  For usage and installation details you can learn more [here](https://github.com/tywalch/electrocli).
5864
5890
 
5891
+ # Version 2 Migration
5892
+ ## New response format for all query methods.
5893
+ Prior to 2.0.0, ElectroDB had multiple unique response signatures depending on the method used. Queries now return responses within an envelope object with results typically on a property called `data`. The section [Building Queries](#building-queries) now has response format examples for all methods, and the section [Exported Types](#exported-types) has new utility types you can use to express response types in your code.
5894
+
5895
+ ## Unified pagination APIs
5896
+ Version 2.0.0 removes the `.page()` terminal function and unifies pagination under the `.go()` method. The response signature for queries, scans, finds, and matches now include a cursor string that can be passed back into the go method as a query option (e.g. `go({cursor})`. This new cursor is a departure from the destructure object ElectroDB returned prior for pagination, and is a `base64url` type string making it url safe.
5897
+
5898
+ Note: It is still possible to return the native DynamoDB LastEvaluatedKey using the `pager` and/or `data` [query options](#query-options). This new `cursor`
5899
+
5900
+ Another change to pagination involves the "auto-pagination" used with the `.go()` method. Prior to 2.0.0 the `.go()` method would paginate through all _query_ results automatically. This was not the behavior for `scan` which caused some confusion. All queries and and query-like methods (scan, find, match, etc) now query a single page by default. You can use the [query options](#query-options) `pages` and `limit` to instruct electrodb to automatically iterate through multiple pages, or use `pages: 'all'` to have electrodb automatically exhaust pagination.
5901
+
5902
+ Checkout the section [#pagination-query-options] to read more on this topic and to find an example of how to perform pagination with ElectroDB 2.0.0.
5903
+
5904
+ ## Pagination with a string cursor
5905
+ All ElectroDB `query` and `scan` operations return a `cursor`, which is a stringified and copy of DynamoDB's `LastEvaluatedKey` with a `base64url` encoding. Read the section [Pagination Cursor](#pagination-cursor) to learn more about how the cursor is formed and how to use it to accomplish pagination in ElectroDB.
5906
+
5865
5907
  # Version 1 Migration
5866
5908
  This section is to detail any breaking changes made on the journey to a stable 1.0 product.
5867
5909
 
@@ -5905,6 +5947,7 @@ let old_schema = {
5905
5947
  attributes: {...},
5906
5948
  indexes: {...}
5907
5949
  };
5950
+
5908
5951
  new Entity(old_schema, {client});
5909
5952
 
5910
5953
  // new way
@@ -5917,6 +5960,7 @@ let new_schema = {
5917
5960
  attributes: {...},
5918
5961
  indexes: {...}
5919
5962
  };
5963
+
5920
5964
  new Entity(new_schema, {client, table});
5921
5965
  ```
5922
5966
 
@@ -5935,9 +5979,6 @@ new Service({
5935
5979
  }, {client});
5936
5980
 
5937
5981
  // new way
5938
- new Service("service_name", {client, table});
5939
-
5940
- // new way (for better TypeScript support)
5941
5982
  new Service({entity1, entity2, ...})
5942
5983
  ```
5943
5984
 
@@ -5954,6 +5995,3 @@ This change stems from the fact the `facets` is already a defined term in the Dy
5954
5995
  ## Get Method to Return null
5955
5996
 
5956
5997
  1.0.0 brings back a `null` response from the `get()` method when a record could not be found. Prior to `1.0.0` ElectroDB returned an empty object.
5957
-
5958
- # Coming Soon
5959
- - Default query options defined on the `model` to give more general control of interactions with the Entity.