Library root

This project and documentation are work in progress

Example code: src/lib/index

The library root exports two things:

  • a createLibrary function

  • a bunch of types

createLibrary

This function is used in each app to instantiate a library from a set of adapters.

import * as Operations from './operations'

import { ContextAdapter, wrapAdapter, mergeAdapters } from './context'

export function createLibrary <Adapter extends ContextAdapter> (adapter: Adapter) {
  return {
    book: {
      add:    wrapAdapter(adapter, Operations.addBook)
    },
    loan: {
      take:   wrapAdapter(adapter, Operations.loanBook),
      return: wrapAdapter(adapter, Operations.returnBook)
    },
    user: {
      add:    wrapAdapter(adapter, Operations.addUser),
      remove: wrapAdapter(adapter, Operations.removeUser)
    }
  }
}

So, the adapter essentially maps over the set of operations and calls wrapAdapter (discussed in the last chapter).

The resulting type looks like this:

type = {
  book: {
    add: (input: AddInput, params?: AdapterParams) => Promise<AddOutput>
  },
  loan: {
    take: (input: LoanInput, params?: AdapterParams) => Promise<TakeLoanOutput>,
    return: (input: ReturnInput, params?: AdapterParams) => Promise<ReturnBookOutput>
  },
  user: {
    add: (input: AddUserInput, params?: AdapterParams) => Promise<AddUserOutput>,
    remove: (input: RemoveUserInput, params?: AdapterParams) => Promise<RemoveUserOutput>
  }
}

So, to recap on how this works - taking as example calling user.add -

  1. We call user.add with the user input params (name, address, etc) and any adapter params (an object of things that might be used by the adapters).

  2. The function created by wrapAdapter calls the adapter passed into createLibrary

  3. That adapter may be a merged adapter calling all the others

  4. Now the resulting context is passed to the operation

  5. The operation does its business and either returns the output, or throws an error

Exactly how adapters work is documented in the next chapter.

Other exports

In the example code we've exported quite a few types from Lib:

import * as Operations from './operations'
import * as Entities from './entities'
import * as Errors from './errors'
import * as Events from './events'
import * as Ctx from './context'

import { ContextAdapter, wrapAdapter, mergeAdapters } from './context'

export {
  Ctx,
  Entities,
  Errors,
  Events,
  mergeAdapters
}
  • Exporting the Ctx Type helps us type our adapters

  • Exporting Entities Types lets us use the library in a type-safe way

  • Exporting Errors Types lets us do useful things with typed errors

  • Exporting Event Types lets us read from events in a safe way

  • Exporting mergeAdapters is necessary for any apps

(Obviously for JS projects only mergeAdapters is needed)

Now we have a library

The library is essentially a unit of 'pure' code - that is, all our domain operations, without side effects. It's now ready to be given adapters to emit outputs, and passed to bindings to receive inputs from the outside world.

Last updated