serverless-cloud-data-utils

Utilities for working with serverless cloud data APIs.

Usage no npm install needed!

<script type="module">
  import serverlessCloudDataUtils from 'https://cdn.skypack.dev/serverless-cloud-data-utils';
</script>

README

Utilities for Serverless Data

tests security Codacy Badge version


Utilities for working with serverless cloud data APIs. By default, accessing and modifying data via @serverless/cloud API revolves around namespaces and key expressions, tracking which can be a headache (imagine a typo in a namespace or a key expression). With serverless-cloud-data-utils, you can define your models and their indexes (access paths) programmatically, and then access and modify your data in a consistent and type-safe manner.


How to Install

Install via NPM. Note that this library is a wrapper on top of serverless cloud, so it would only work in an environment where @serverless/cloud package is installed (and is not a mock), i.e. on serverless cloud apps and instances.

npm i serverless-cloud-data-utils

How to Use

๐Ÿ“ First you need to define your model and its indexes:

// order.model.ts

import { Model, buildIndex, indexBy, timekey } from 'serverless-cloud-data-utils'

//
// Indexes are ways you want to be able to access
// your records.
//

//
// ๐Ÿ‘‰ this is for accessing orders by their id
//
export const OrderId = buildIndex({ namespace: 'orders' })

//
// ๐Ÿ‘‰ this is for accessing orders by their time.
//    this is a secondary index, so it needs an explicit label
//    (which can be label1-label5)
//
export const OrderTime = buildIndex({
  namespace: 'orders',
  label: 'label1',
  converter: timekey,    // --> timestamp strings are illegal by default, this converter takes care of that.
})


// 
// ๐Ÿ‘‰ this parametric index allows accessing
//    orders of a specific user.
//
export const OrderOwner = owner => buildIndex({
  namespace: `orders_${owner.id}`,
  label: 'label2',
  converter: timekey,
})


export class Order extends Model<Order> {
  id: string
  owner: User
  amount: number
  date: string
  
  //
  // ๐Ÿ‘‰ this method indicates the indexes of each record
  //    and how they are mapped to its properties.
  //    note that you should have only one primary index,
  //    and the secondary indexes should have distinct
  //    labels.
  //
  keys() {
    return [
      indexBy(OrderTime).exact(this.date),
      indexBy(OrderId).exact(this.id),
      indexBy(OrderOwner(this.owner)).exact(this.date),
    ]
  }
}

โœจ Now you can create and store new objects:

import { Order } from './order.model'

const newOrder = new Order()
newOrder.amount = 42
newOrder.user = someUser
newOrder.date = new Date().toISOString()
newOrder.id = uuid.v4()

await newOrder.save()

โœ๏ธ Modify objects:

myOrder.amount += 10
await myOrder.save()

๐Ÿ—‘๏ธ Delete objects:

await someOrder.delete()

๐ŸŽฏ Get a specific object:

import { indexBy } from 'serverless-cloud-data-utils'
import { Order, OrderId } from './order.model'

const order = await indexBy(OrderId).exact('some_id').get(Order)

๐Ÿ” Or query objects with specific indexes:

import { indexBy } from 'serverless-cloud-data-utils'
import { Order, OrderTime } from './order.model'

//
// fetch last 10 orders
//

const latestOrders = await indexBy(OrderTime)
  .limit(10)
  .reverse()
  .get(Order)
import { indexBy } from 'serverless-cloud-data-utils'
import { Order, OrderOwner } from './order.model'

//
// get orders of a user
// which where issued last month
//

const lastMonth = new Date()
lastMonth.setMonth(lastMonth.getMonth() - 1)

const userOrders = await indexBy(OrderOwner(someUser))
  .after(lastMonth)
  .reverse()
  .get(Order)

๐Ÿ“ฆ Use .clean() method for sending model data over network:

import { api } from '@serverless/cloud'
import { indexBy } from 'serverless-cloud-data-utils'
import { Order, OrderId } from './order.model'

api.get('/:id', async (req, res) => {
  const order = await indexBy(OrderId).exact(req.params.id).get(Order)

  if (order) {
    res.send(order.clean())
  } else {
    res.status(404).send()
  }
})

๐Ÿงน You can also use .clean() to exclude some fields you don't want to send to the client:

order.clean(['id', 'owner'])

Formatting

Fields of your models MUST always be camel case, since any data retreived from the database will be converted into camel case. Conversely, when you call .clean(), the data is also converted to snake case for transfer over the network. These rules also apply to nested objects.


Contribution

Feedback and pull requests are more than welcome! Currently the main pressing issues are:

  • Full API documentation
  • Support for metadata

Here are some useful commands for contributing:

npm test          # runs all the tests
npm run cov:view  # checks the coverage status, try to avoid coverage regression!