Options
All
  • Public
  • Public/Protected
  • All
Menu

Dynamo-Easy

Travis Coverage Status Dev Dependencies Greenkeeper badge styled with prettier All Contributors

Abstracts away the complexity of the low level aws dynamosdk. Provides an easy to use fluent api to for requests and supports typescript decorators, to define some metadata for your models. You don't need to care about the mapping of javascript types to their dynamo types any more. We got you covered.

Checkout the full technical api documentation here.

Built with :heart: by shiftcode.

Get Started

@Model()
class Person{
  @PartitionKeyUUID() 
  id: string

  name: string
}

const dynamoStore = new DynamoStore(Person)

// add a new item
dynamoStore.put({name: 'peter'})
  .exec().subscribe(()=>{
    console.log('peter was saved')
  })

// search for all persons which start with the character 'p'
dynamoStore.query()
  .where('name').startsWith('p')
  .exec()
  .subscribe((persons: Person[])=>{
    console.log('got persons')
  })


// returns all persons
dynamoStore.scan()
  .exec()
  .subscribe((persons: Person[]) => {
    console.log('all persons')
  })

Want to use this library with Angular (>4) checkout our angular-service.

Decorators

Decorators are used to add some metadata to our model classes, relevant to our javascript-to-dynamo mapper.

This is an experimental feature and requires to set the following typescript compiler options:

  • "experimentalDecorators": true
  • "emitDecoratorMetadata": true

Additionally we rely on the reflect-metadata (https://www.npmjs.com/package/reflect-metadata) library for reflection api.

To get started with decorators just add a @Model() Decorator to any typescript class.

If you need to read the metadata by hand for some purpose, use the MetadataHelper 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.

A word on Collections (Array & Set)

###Array Javascript Arrays with a a items of type String, Number or Binary will be mapped to a S(et) type, by default all other types are mapped to L(ist) type. If an item of an Array has a complex type the type can be defined using the @TypedArray() Decorator.

###Set ES6 Set types will be marshalled to dynamoDb set type if the type of the set is supported, if the type is not supported it will be marshalled to an dynamoDB List.

When one of the following decorators is added, the value is marshalled to a List type. @SortedSet(itemType?: ModelClazz), @TypedSet(itemType?: ModelClazz)

Model

###Custom TableName Here is the rule how a table name is built ${kebabCase(modelName)}s so for a model called Product the table will be named products, this is a default implementation.

There are two possibilities to change the name:

  • override the name using the tableName parameter @Model({tableName: tableName})
  • provide a TableNameResolver function when instantiating a DynamoStore. This method will receive the default table name (either resolved using the model name or custom value when a tableName was provided in @Model decorator)

Mapper

Types

We do the mapping from javascript objects to dynamodb types for you in requests and responses

Simple Type (no decorators required to work)

  • String
  • Number
  • Boolean
  • Null
  • Array
  • Date (we only support MomentJS)

Complex Types (properties with these types need some decorators to work properly)

  • Set
  • Map
  • Array

Date

Right now we only support (MomentJS)[http://momentjs.com/] Dates.

If you want to explicitly mark a property to be a Date use the @Date() decorator. If we find a moment value we automatically map it to a String (using ISO-8601 format). When coming from db we do a regex test for ISO-8601 format and map it back to a moment object.

Requests API

To start making requests create an instance of DynamoStore and execute the desired operation using the provided api. We support all the common dynamodb operations.

The request api has support for the following operations:

  • Put
  • Get
  • Update
  • Delete
  • Scan
  • Query
  • MakeRequest (generic low level method for special scenarios)

For most of the api there is probably no explanation required, here are some topics we think need some more info.

There is always the possibility to access the Params object directly to add values which are not covered with our api.

Authentication

In a real world scenario you'll have some kind of authentication to protect your dynamodb ressources. You can customize on how to authenticate when providing a custom SessionValidityEnsurer function to the DynamoStore when creating a new instance. The default implementation is a no-op function.

Session Validity Ensurer

Here is an example of an implementation using amazon cognito

function sessionValidityEnsurer(): Observable<>{
  return Observable.of(this.isLoggedIn())
    .switchMap(isLoggedIn => {
       if (isLoggedIn) {
          this.logger.debug('withValidSession :: cognitoService.isLoggedIn() -> we have a valid session -> proceed')
          return Observable.of(true)
        } else {
          this.logger.debug('withValidSession :: cognitoService.isLoggedIn() -> user is not logged in or token expired, try to get a new session')
          return this.getUser()
            .catch((err, caught): Observable<boolean> => {
              this.logger.error('withValidSession :: there was an error when refreshing the session', err)
              throw new AuthError('SC_UNAUTHENTICATED', 'Could not refresh the token' + JSON.stringify(err))
            })
            .do(user => this.logger.debug('withValidSession :: we got new valid session', user))
        }
      })
      .map((value: boolean | CognitoUser) => {
        return
      })
  }

Expressions (AWS Doc)

By default we create a substitution placeholder for all the attributes, just to not implement a blacklist with reserved words in the context of aws dynamodb.

attributename: age

expression: '#age = :age'
attributeExpressionNames: {'#age': 'age'}
attributeExpressionValues: {':age': {N: '10'}}

this works seemlesly for top level attribtues, but if we wanna build an expression for where the attribute needs to be accessed with a document path, we need some special logic nested attribute: person.age

attributeExpressionNames: {'#person':'person', '#age': 'age'}
attributeExpressionValues: {':age': {N: '10'}}
expression: '#person.#age = :age'

we can't use #personAge as a placeholder for 'person.age' because if the dot is part of an attribute name it is not treated as a metacharacter compared to when using directly in expression, so the above solution needs to be used

these are the accessor rules for nested attribute types

  • [n] — for list elements
  • . (dot) — for map elements

Pagination

TODO

Development

NPM scripts

  • npm t: Run test suite
  • npm start: Runs npm run build in watch mode
  • npm run test:watch: Run test suite in interactive watch mode
  • npm run test:prod: Run linting and generate coverage
  • npm run build: Generage bundles and typings, create docs
  • npm run lint: Lints code
  • npm run commit: Commit using conventional commit style (husky will tell you to use it if you haven't :wink:)

Automatic releases

Use the npm comand npm run commit, which is a convenient way to create conventional commits. Those messages are used to run semantic releases, which publishes our code automatically on github and npm, plus generates automatically a changelog. This setup is highly influenced by Kent C. Dodds course on egghead.io

Git Hooks

We use 2 git hooks:

precommit

  • to format the code with Prettier :nail_care: before sending it to the git repo.
  • to check if the commit message follows a conventional commit message

prepush

  • if the code can be built running npm run build
  • if all the tests pass

Credits

https://github.com/alexjoverm/typescript-library-starter For the awesome project which helps to scaffold, develop and build a typescript library project https://github.com/ryanfitz/vogels - To get an idea on how to build the chainable api http://densebrain.github.io/typestore/ - Thats where the base idea on how to implement the model decorators came came from

Contributors

Made with :heart: by @michaelwittwer and all these wonderful contributors (emoji key):


Michael Wittwer

💻 📖 ⚠️

This project follows the all-contributors specification. Contributions of any kind welcome!

Index

External modules

Legend

  • Module
  • Object literal
  • Variable
  • Function
  • Function with type parameter
  • Index signature
  • Type alias
  • Enumeration
  • Enumeration member
  • Property
  • Method
  • Interface
  • Interface with type parameter
  • Constructor
  • Property
  • Method
  • Index signature
  • Class
  • Class with type parameter
  • Constructor
  • Property
  • Method
  • Accessor
  • Index signature
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Inherited accessor
  • Protected property
  • Protected method
  • Protected accessor
  • Private property
  • Private method
  • Private accessor
  • Static property
  • Static method

Generated using TypeDoc