From 6f32508156d9bc1130cd458bd1d8841dbc899a68 Mon Sep 17 00:00:00 2001 From: Michael Wittwer Date: Wed, 6 Feb 2019 09:58:21 +0100 Subject: [PATCH] docs(gitbook): initial doc from v1.0 --- docs/README.md | 22 ++- docs/SUMMARY.md | 22 ++- docs/api/dynamo-easy-config/README.md | 2 + docs/api/dynamo-easy-config/aws-sdk-config.md | 2 + docs/api/dynamo-easy-config/configuration.md | 66 +++++++ docs/api/model/README.md | 2 + docs/api/model/custommapper.md | 29 +++ docs/api/model/decorators.md | 164 +++++++++++++++++ docs/api/model/mapping-concept.md | 172 ++++++++++++++++++ docs/api/multi-model-requests.md | 126 +++++++++++++ docs/api/untitled/README.md | 36 ++++ docs/api/untitled/model-requests.md | 93 ++++++++++ docs/second-page.md | 6 - 13 files changed, 730 insertions(+), 12 deletions(-) create mode 100644 docs/api/dynamo-easy-config/README.md create mode 100644 docs/api/dynamo-easy-config/aws-sdk-config.md create mode 100644 docs/api/dynamo-easy-config/configuration.md create mode 100644 docs/api/model/README.md create mode 100644 docs/api/model/custommapper.md create mode 100644 docs/api/model/decorators.md create mode 100644 docs/api/model/mapping-concept.md create mode 100644 docs/api/multi-model-requests.md create mode 100644 docs/api/untitled/README.md create mode 100644 docs/api/untitled/model-requests.md delete mode 100644 docs/second-page.md diff --git a/docs/README.md b/docs/README.md index c533e2458..d0c19f4e0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,20 @@ ---- -description: Some description ---- +# Introduction + +{% code-tabs %} +{% code-tabs-item title="looks-easy-right.ts" %} +```typescript +dynamoStore + .scan() + .where( + attribute('name').equals('shiftcode'), + attribute('age').gt(20) + ) + .exec() +``` +{% endcode-tabs-item %} +{% endcode-tabs %} + +## Developed with ❤ by [www.shiftcode.ch](www.shiftcode.ch) + -# Dynamo-Easy Gitbook diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 7c0255139..0e3892246 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,5 +1,23 @@ # Table of contents -* [Dynamo-Easy Gitbook](README.md) -* [Second Page](second-page.md) +* [Introduction](README.md) + +## Get Started + +* [Installation](get-started/installation/README.md) + * [Browser vs. Node usage](get-started/installation/browser-vs.-node-usage.md) +* [Jump into code](get-started/jump-into-code.md) + +## API Doc + +* [Config](api/dynamo-easy-config/README.md) + * [Dynamo-Easy Config](api/dynamo-easy-config/configuration.md) + * [AWS SDK Config](api/dynamo-easy-config/aws-sdk-config.md) +* [Model](api/model/README.md) + * [Decorators](api/model/decorators.md) + * [Mapping Concept](api/model/mapping-concept.md) + * [CustomMapper](api/model/custommapper.md) +* [Dynamo Store](api/untitled/README.md) + * [Model Requests](api/untitled/model-requests.md) +* [Multi-Model Requests](api/multi-model-requests.md) diff --git a/docs/api/dynamo-easy-config/README.md b/docs/api/dynamo-easy-config/README.md new file mode 100644 index 000000000..fc0119863 --- /dev/null +++ b/docs/api/dynamo-easy-config/README.md @@ -0,0 +1,2 @@ +# Config + diff --git a/docs/api/dynamo-easy-config/aws-sdk-config.md b/docs/api/dynamo-easy-config/aws-sdk-config.md new file mode 100644 index 000000000..48bb5bfcc --- /dev/null +++ b/docs/api/dynamo-easy-config/aws-sdk-config.md @@ -0,0 +1,2 @@ +# AWS SDK Config + diff --git a/docs/api/dynamo-easy-config/configuration.md b/docs/api/dynamo-easy-config/configuration.md new file mode 100644 index 000000000..3d2f3154c --- /dev/null +++ b/docs/api/dynamo-easy-config/configuration.md @@ -0,0 +1,66 @@ +# Dynamo-Easy Config + +## Authorization + +```typescript +import { updateDynamoEasyConfig } from '@shiftcoders/dynamo-easy' + +updateDynamoEasyConfig({ + sessionValidityEnsurer: ():Observable => { + // do whatever you need to do to make sure the session is valid + // and return an Observable when done + return of(true).pipe(map(() => { return })) + } +}) +``` + +## Logging + +To receive log statements from dynamo-easy you need to provide a function + +```typescript +import { LogInfo, updateDynamoEasyConfig } from '@shiftcoders/dynamo-easy' + +updateDynamoEasyConfig({ + logReceiver: (logInfo: LogInfo) => { + const msg = `[${logInfo.level}] ${logInfo.timestamp} ${logInfo.className} (${ + logInfo.modelClass + }): ${logInfo.message}` + console.debug(msg, logInfo.data) + } +}) +``` + +## TableNameResolver + +To use different table names per stage you can provide the tableNameResolver function. + +This function will receive the default table name \(either resolved using the model name or custom value when a tableName was provided in the @Model decorator\) and needs to return the extended table name as string. + +```typescript +import { TableNameResolver, updateDynamoEasyConfig } from '@shiftcoders/dynamo-easy' + +const myTableNameResolver: TableNameResolver = (tableName: string) => { + return `myPrefix-${tableName}` +} + +updateDynamoEasyConfig({ + tableNameResolver: myTableNameResolver +}) +``` + +## DateMapper + +If you want to use a different type for the @DateProperty decorator \(eg. Moment or DayJS\) you need to define a custom mapper and provide it to the easy config. + +```typescript +import { updateDynamoEasyConfig } from '@shiftcoders/dynamo-easy' +import { momentDateMapper } from './moment-date.mapper' + +updateDynamoEasyConfig({ + dateMapper: momentDateMapper +}) +``` + +{% page-ref page="../model/custommapper.md" %} + diff --git a/docs/api/model/README.md b/docs/api/model/README.md new file mode 100644 index 000000000..acf0e43dd --- /dev/null +++ b/docs/api/model/README.md @@ -0,0 +1,2 @@ +# Model + diff --git a/docs/api/model/custommapper.md b/docs/api/model/custommapper.md new file mode 100644 index 000000000..d094789ea --- /dev/null +++ b/docs/api/model/custommapper.md @@ -0,0 +1,29 @@ +--- +description: >- + With custom mappers you're able to define how the JS values are mapped (toDb) + and how the dynamoDB attributes are parsed (fromDb). +--- + +# CustomMapper + +Scenarios you need to use a custom mapper: + +* Using complex objects as partition key or sort key \(since dynamoDB only supports \[N\]umber \| \[S\]tring \| \[B\]inary for such\) +* working with class instances instead of plain javascript objects \(e.g. Dates\) +* Storing complex objects in dynamoDB set \(only N\|S\|B sets are possible\) + +A mapper for date objects which would be stored as \[N\]umbers could look like this: + +{% code-tabs %} +{% code-tabs-item title="date-to-number.mapper.ts" %} +```typescript +import { MapperForType, NumberAttribute } from '@shiftcoders/dynamo-easy' + +export const dateToNumberMapper: MapperForType = { + fromDb: attributeValue => new Date(parseInt(attributeValue.N, 10)), + toDb: propertyValue => ({ N: `${propertyValue.getTime()}` }), +} +``` +{% endcode-tabs-item %} +{% endcode-tabs %} + diff --git a/docs/api/model/decorators.md b/docs/api/model/decorators.md new file mode 100644 index 000000000..b00ddb9ed --- /dev/null +++ b/docs/api/model/decorators.md @@ -0,0 +1,164 @@ +--- +description: >- + Decorators are used to add some metadata to our model classes, relevant to our + javascript-to-dynamo mapper. +--- + +# Decorators + +We rely on the reflect-metadata \([https://www.npmjs.com/package/reflect-metadata](https://www.npmjs.com/package/reflect-metadata)\) library for reflection api. + +To get started with decorators just add a [@Model\(\)](https://shiftcode.github.io/dynamo-easy/modules/_decorator_impl_model_model_decorator_.html) Decorator to any typescript class. + +If you need to read the metadata by hand for some purpose, use the [MetadataHelper](https://shiftcode.github.io/dynamo-easy/classes/_decorator_metadata_metadata_helper_.metadatahelper.html) to read the informations. + +We make heavy usage of compile time informations about our models and the property types. Here is a list of the types that can be retrieved from compile time information for the key `design:type`. \(The metadata will only be added if at least one decorator is present on a property\) + +* String +* Number +* Boolean +* Array \(no generics\) +* Custom Types +* ES6 types like Set, Map will be mapped to Object when calling for the type via Reflect.get\(design:type\), so we need some extra info. + +Generic information is never available due to some serialization limitations at the time of writing. + +## Model Decorators + +### @Model + +Use the `@Model` decorator to make it 'mappable' for the dynamo-easy mapper. +You can optionally pass an object containing the table name if you don't want the default table name. +The default table name is built with `${kebabCase(modelName)}s` + +```typescript +import { Model } from '@shiftcoders/dynamo-easy' + +@Model({tableName: 'my-model-table'}) +class MyModel { +} +``` + +{% hint style="info" %} +To use different table names on different stages the [tableNameResolver](../dynamo-easy-config/configuration.md#tablenameresolver) is the right choice. +{% endhint %} + +## Key Decorators + +### Primary Key + +* `PartitionKey` +* `SortKey` + +```typescript +import { Model, PartitionKey, SortKey } from '@shiftcoders/dynamo-easy' + +@Model() +class MyModel { + @PartitionKey() + myPartitionKey: string + + @SortKey() + mySortKey: number +} +``` + +### Global Secondary Index + +We provide two decorators to work with global secondary indexes: + +* `GSIPartitionKey` +* `GSISortKey` + +{% hint style="info" %} +You can use multiple GSI on the same model and also use the same property for different Indexes +{% endhint %} + +```typescript +import { Model, GSIPartitionKey, GSISortKey } from '@shiftcoders/dynamo-easy' + +@Model() +class MyModel { + @GSIPartitionKey('NameOfIndex') + myGsiPartitionKey: string + + @GSISortKey('NameOfIndex') + myGsiSortKey: number +} +``` + +### Local Secondary Index + +```typescript +import { Model, LSISortKey, PartitionKey, SortKey } from '@shiftcoders/dynamo-easy' + +@Model() +class MyModel { + @PartitionKey() + myPartitionKey: string + + @SortKey() + mySortKey: number + + @LSISortKey() + myLsiSortKey: number +} +``` + +## Type Decorators + +### @CollectionProperty\(options\) + +The CollectionProperty decorator is used for arrays and sets. It defines if the values should be mapped to \[L\]ist or \[\(N\|S\|B\)S\]et and stores the information how the Attributes should be parsed. + +#### options + +`itemType: ModelConstructor` provide the class of the items inside the collection if they have decorators \(this ItemClass also needs the `@Model` decorator\) + +`itemMapper: MapperForType` provide a custom mapper to map the complex items of your collection to \[S\]tring, \[N\]umber or \[B\]inary. This is mainly useful if you want to store them in a \[S\]et. + +`sorted: boolean` the collection will be stored as \[L\]ist \(\[S\]et does not preserve the order\) no matter if the javascript type is a `Set` or an `Array` . + +`name: string` define a different name \(than the property name\) for DynamoDB. + +{% hint style="info" %} +it does no make sense to provide both, the itemType and an itemMapper. An Error would be thrown. +{% endhint %} + +> further information how arrays and sets are mapped: + +{% page-ref page="mapping-concept.md" %} + +### @Property\(options\) + +`name: string` define a different name \(than the property name\) for DynamoDB. + +`mapper: MapperForType` define a custom mapper \(e.g if you want to use a complex object as PartitionKey or SortKey\) + +### @DateProperty\(\) + +The DateProperty decorator is just syntactic sugar for `@Property({mapper: dateMapper})` +all properties decorated with it will be mapped with the default dateMapper or the one you define with `updateDynamoEasyConfig({dateMapper:})`. + +## Other + +### @Transient + +The `@Transient` decorator can be used to ignore a property when the object is mapped. + +```typescript +import { Model, Transient } from '@shiftcoders/dynamo-easy' + +@Model() +class MyModel { + @Transient() + myPropertyToIgnore: any +} +``` + + + + + + + diff --git a/docs/api/model/mapping-concept.md b/docs/api/model/mapping-concept.md new file mode 100644 index 000000000..ab53fd553 --- /dev/null +++ b/docs/api/model/mapping-concept.md @@ -0,0 +1,172 @@ +--- +description: >- + Depending on the value type or the meta data provided from decorators, values + are mapped to dynamoDB attributes. +--- + +# Mapping Concept + +As long there are no decorators the mapper decides by the value type, to which dynamoDB Attribute it should be mapped. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
JS TypeMapped DynamoDB type
boolean[BOOL]
string[S]tring
number[N]umber
Array[L]ist
+

Set + +

+

Set + +

+

Set + +

+
+

[N]umber[S]et

+

[S]tring[S]et

+

[B]inary[S]et

+
+

Mixed item types are

+

only supported with

+

decorator (see below)

+
Object[M]ap
> \*Binary is not yet supported + +{% hint style="danger" %} +Avoid using Set for types other than string\|number\|Binary without decorator. + + +`Set` would be mapped implicitly to \[L\]ist of \[M\]aps. But when parsing the Attribute from DynamoDB, there's no information about the Set and will therefore be parsed to an array. To fix this, use the @CollectionProperty\(\) decorator. +{% endhint %} + +{% hint style="info" %} +It is not possible to store an Array to a \[S\]et even if you use the `CollectionProperty` decorator. +{% endhint %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
JS Type + CollectionProperty decoratorDynamoDB Type
+

@CollectionProperty() +

+

Set<string | number> +

+
[L]istonly list supports different types
+

@CollectionProperty({ sorted: true }) +

+

Set<string> +

+
[L]istonly list preserves the order
+

@CollectionProperty({ sorted: true }) +

+

Set<number> +

+
[L]istonly list preserves the order
+

@CollectionProperty({ itemType: CustomType* }) +

+

Set<CustomType> +

+
[L]istonly makes sense when CustomType is @Model decorated - will be used for + mapping
+

@CollectionProperty({ itemType: CustomType* }) +

+

Array<CustomType> +

+
[L]istonly makes sense when CustomType is @Model decorated - will be used for + mapping
+

@CollectionProperty({itemMapper:CustomTypeMapper}) +

+

Set<CustomType> +

+
[(N|S|B)S]etitemMapper must return N|S|B - Attribute
> \*If CustomType is string\|number\|Binary, it will be mapped to the respective \[S\]et + +Handling of empty values + diff --git a/docs/api/multi-model-requests.md b/docs/api/multi-model-requests.md new file mode 100644 index 000000000..e47cf0671 --- /dev/null +++ b/docs/api/multi-model-requests.md @@ -0,0 +1,126 @@ +--- +description: >- + There are different Request you can use to read from or write to different + tables +--- + +# Multi-Model Requests + +### Params + +Equivalent to the model requests you can access the params as property on all multi-model requests + +```typescript +const r = new BatchGetRequest() + // BatchWriteRequest() + // TransactGetRequest() + // TransactWriteRequest() +console.log(r.params) +``` + +### Consumed Capacity + +You might want to receive the consumed capacity which can be achieved by applying the `returnConsumedCapacity('INDEXES' | 'TOTAL')` method and the usage of `execFullResponse()` instead of `exec()`. + +## BatchGet + +{% embed url="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API\_BatchGetItem.html" %} + +{% code-tabs %} +{% code-tabs-item title="batch-get-request.example.ts" %} +```typescript +import { BatchGetRequest } from '@shiftcoders/dynamo-easy' + +const keysToFetch: Array> = [{ id: 'my-id' }] +const otherKeysToFetch: Array> = [{ propA: 'Foo', propB: 'Bar' }] +new BatchGetRequest() + .forModel(ExampleModel, keysToFetch) + .forModel(AnotherModel, otherKeysToFetch) + .exec() + .then((result: BatchGetResponse) => { + const itemsFetchedFromExampleTable = result['tableNameOfExampleModel'] + const itemsFetchedFromAnotherModelTable = result['tableNameOfAnotherModel'] + }) +``` +{% endcode-tabs-item %} +{% endcode-tabs %} + +## BatchWrite + +{% embed url="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API\_BatchWriteItem.html" %} + +{% code-tabs %} +{% code-tabs-item title="batch-write-request.example.ts" %} +```typescript +import { BatchWriteRequest } from '@shiftcoders/dynamo-easy' + +const keysToDelete: Array> = [{ id: 'my-id' }] +const otherKeysToDelete: Array> = [{ propA: 'Foo', propB: 'Bar' }] +const objectsToPut: Array = [{id: 'foo', value: 'bar'}, {id: 'foo2', value: 'bar2'}] + +new BatchWriteRequest() + .returnConsumedCapacity('TOTAL') + .delete(ExampleModel, keysToDelete) + .delete(AnotherModel, otherKeysToDelete) + .put(YetAnotherModel, objectsToPut) + .execFullResponse() + .then(resp => { + console.log(resp.ConsumedCapacity); + }) +``` +{% endcode-tabs-item %} +{% endcode-tabs %} + +## TransactGet + +Execute transactional read operations by providing one or multiple models + keys. + +{% embed url="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API\_TransactGetItems.html" caption="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API\_TransactGetItems.html" %} + +{% code-tabs %} +{% code-tabs-item title="transact-get-request.example.ts" %} +```typescript +import { TransactGetRequest } from '@shiftcoders/dynamo-easy' + +new TransactGetRequest() + .forModel(ExampleModel, { id: 'my-id' }) + .forModel(AnotherModel, { propA: 'Foo', propB: 'Bar' }) + .returnConsumedCapacity('TOTAL') + .exec() + .then(result => { + console.log(result[0]) // ExampleModel item + console.log(result[1]) // AnotherModel item + }) + + + +``` +{% endcode-tabs-item %} +{% endcode-tabs %} + +## TransactWrite + +Execute transactional write operations by providing one or multiple transact items \(TransactConditionCheck, TransactDelete, TransactPut, TransactUpdate\). + +{% embed url="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API\_TransactWriteItems.html" %} + +{% code-tabs %} +{% code-tabs-item title="transact-write-request.example.ts" %} +```typescript +import { TransactWriteRequest } from '@shiftcoders/dynamo-easy' + +const objectToPut: YetAnotherModel = { id: 'Foo', value: 'Bar' } + +new TransactWriteRequest() + .transact( + new TransactConditionCheck(ExampleModel, 'check-ID').onlyIf(attribute('age').gt(18)), + new TransactDelete(AnotherModel, 'Foo', 'Bar'), + new TransactPut(YetAnotherModel, objectToPut), + new TransactUpdate(ExampleModel, 'myId').updateAttribute('age').set(22), + ) + .exec() + .then(() => console.log('done')) +``` +{% endcode-tabs-item %} +{% endcode-tabs %} + diff --git a/docs/api/untitled/README.md b/docs/api/untitled/README.md new file mode 100644 index 000000000..9a6d85785 --- /dev/null +++ b/docs/api/untitled/README.md @@ -0,0 +1,36 @@ +--- +description: >- + The DynamoStore provides an easy way to create requests for a single model + class. +--- + +# Dynamo Store + +{% code-tabs %} +{% code-tabs-item title="store.example.ts" %} +```typescript +import { DynamoStore } from '@shiftcoders/dynamo-easy' + +const store = new DynamoStore(ExampleModel) +``` +{% endcode-tabs-item %} +{% endcode-tabs %} + +simple example for consistently reading a single item: + +```typescript +store.get('myPartitionKey') // returns an instance of GetRequest + .consistentRead(true) // sets params.ConsistentRead = true + .exec() // returns a Promise + .then((obj:ExampleModel)=>console.log(obj)) +``` + +simple example for updating a single property with one condition: + +```typescript +store.update('myPartitionKey') // returns an instance of UpdateRequest + .updateAttribute('myPropInExampleModel').set('myNewValue') + .onlyIfAttribute('anotherPropInExampleModel').equals('aValue') + .exec() +``` + diff --git a/docs/api/untitled/model-requests.md b/docs/api/untitled/model-requests.md new file mode 100644 index 000000000..d638654dc --- /dev/null +++ b/docs/api/untitled/model-requests.md @@ -0,0 +1,93 @@ +--- +description: SingleModel-Requests you can create directly from DynamoStore. +--- + +# Model Requests + +## Put + +{% embed url="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API\_PutItem.html" %} + +{% tabs %} +{% tab title="Simple" %} +```typescript +const myObjectToWrite = {id:'myId', propA: 42, propB: ['foo', 'bar']} +await store.put(myObjectToWrite) + .ifNotExists() + .exec() +``` +{% endtab %} + +{% tab title="Complex" %} + +{% endtab %} +{% endtabs %} + +## Get + +{% embed url="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API\_GetItem.html" %} + +## Update + +{% embed url="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API\_UpdateItem.html" caption="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API\_UpdateItem.html" %} + +{% tabs %} +{% tab title="Simple" %} +```typescript + +await store.update('myPartitionKey', 'mySortKey') + .updateAttribute('propertyA').set('newValue') + .onlyIfAttribute('otherProp').equals('aValue') + .exec() +``` +{% endtab %} + +{% tab title="Complex" %} +```typescript + +const index = 3 +const oneHouerAgo = new Date(Date.now() - 1000 * 60 * 60) + +await store.update('myPartitionKey', 'mySortKey') + .operations( + update(`myNestedList[${index}].propertyX`).set('value'), + update('updated').set(new Date()) + ) + .onlyIf( + or( + attribute('id').attributeNotExists(), // item not existing + attribute('updated').lt(oneHouerAgo), // or was not updated in the last hour + ) + .exec() +``` +{% endtab %} +{% endtabs %} + +## Delete + +{% embed url="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API\_DeleteItem.html" %} + +## Query + +{% embed url="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API\_Query.html" %} + +## Scan + +{% embed url="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API\_Scan.html" %} + +## BatchGet + +This is a special implementation from BatchGetRequest which only allows to read from a single table + +## BatchWrite + +This is a special implementation from BatchWriteRequest which only allows to write to a single table + +## TransactGet + +This is a special implementation from TransactGetRequest which only allows to read from a single table + + + + + diff --git a/docs/second-page.md b/docs/second-page.md deleted file mode 100644 index 310ffb3a0..000000000 --- a/docs/second-page.md +++ /dev/null @@ -1,6 +0,0 @@ -# Second Page - -```text -console.log('blub') -``` -