duck-storage

0.0.34 • Public • Published

duck-storage

Version

storage for schematized data objects

Installation

$ npm i duck-storage --save
# or
$ yarn add duck-storage

Features

stores schematized ducks

const createEvent = forEvent(Rack, 'create')
const entry = await Rack.create({
  firstName: 'Martin',
  lastName: 'Rafael'
})

t.truthy(entry)
t.true(Object.prototype.hasOwnProperty.call(entry, '_id'))
t.true(Object.prototype.hasOwnProperty.call(entry, '_v'))
t.is(entry.fullName, 'Martin Rafael')

const createEventPayload = (await createEvent)[0]

t.truthy(createEvent)
t.deepEqual(createEventPayload, entry)

entry.fullName = 'Pedro Perez'

t.is(entry.firstName, 'Pedro')
t.is(entry.lastName, 'Perez')

finds a duck

const entry = await Rack.create({
  firstName: 'Martin'
})

const found = await Rack.read(entry._id)
t.deepEqual(entry.toObject(), found.toObject())
t.not(entry, found)

updates information of a duck

const updateEvent = forEvent(Rack, 'update')
const created = (await Rack.create({
  firstName: 'Martin',
  lastName: 'Gonzalez'
})).consolidate()

const toUpdate = {
  firstName: 'Olivia'
}
const updated = await Rack.update({ lastName: { $eq: 'Gonzalez' } }, toUpdate)

const updatePayload = (await updateEvent)[0]

t.truthy(updatePayload)
t.deepEqual(updatePayload.oldEntry, created)
t.deepEqual(updatePayload.newEntry, toUpdate)
t.deepEqual(updatePayload.entry, updated[0])

t.true(Array.isArray(updated))
t.is(updated.length, 1)
t.is(updated[0].firstName, 'Olivia')
t.is(updated[0].lastName, 'Gonzalez')
t.is(updated[0]._v, 2)

const updatedEntry = await Rack.read(created._id)
t.is(updatedEntry.firstName, 'Olivia')

updates information of multiple ducks at a time

const updateEvent = forEvent(Rack, 'update', { trap: 2 })

const martin = (await Rack.create({
  firstName: 'Martin',
  lastName: 'Gonzalez'
})).toObject()

const ana = (await Rack.create({
  firstName: 'Ana',
  lastName: 'Sosa'
})).toObject()

const toUpdate = {
  firstName: 'Olivia'
}

const updated = await Rack.update({}, toUpdate)

t.true(Array.isArray(updated))

t.is(updated.length, 2)
t.is(updated[0]._id, martin._id)
t.is(updated[0].firstName, 'Olivia')
t.is(updated[0].lastName, 'Gonzalez')
t.is(updated[0]._v, 2)

t.is(updated[1]._id, ana._id)
t.is(updated[0].firstName, 'Olivia')
t.is(updated[0].lastName, 'Gonzalez')
t.is(updated[0]._v, 2)

const updatePayload = await updateEvent

t.truthy(updatePayload)

t.deepEqual(updatePayload[0].oldEntry, martin)
t.deepEqual(updatePayload[0].newEntry, toUpdate)
t.deepEqual(updatePayload[0].entry, updated[0])

t.deepEqual(updatePayload[1].oldEntry, ana)
t.deepEqual(updatePayload[1].newEntry, toUpdate)
t.deepEqual(updatePayload[1].entry, updated[1])

removes ducks from the rack

const deleteEvent = forEvent(Rack, 'delete')
const entry = await Rack.create({
  firstName: 'Martin',
  lastName: 'Gonzalez'
})
const deleted = await Rack.delete({
  _id: {
    $eq: entry._id
  }
})
const deletedPayload = (await deleteEvent)[0]

t.deepEqual(deletedPayload, deleted[0])

t.true(Array.isArray(deleted))
t.is(deleted.length, 1)
t.is(deleted[0]._id, entry._id)
t.is(deleted[0].firstName, 'Martin')
t.is(deleted[0].lastName, 'Gonzalez')

const notFound = await Rack.read({ _id: { $eq: entry._id } })
t.is(notFound, undefined)
t.is(Object.keys(Rack.storeKey).length, 0)

lists ducks in the rack

const entry1 = (await Rack.create({
  firstName: 'Martin',
  lastName: 'Gonzalez'
})).toObject()

const entry2 = (await Rack.create({
  firstName: 'Olivia',
  lastName: 'Gonzalez'
})).toObject()

const res = await Rack.list()
t.true(Array.isArray(res))
t.is(res.length, 2)
t.deepEqual(res.map(entry => entry.consolidate()), [entry1, entry2])

loads references of ducks in other racks

const orderSchema = new Schema({
  customer: {
    type: 'ObjectId',
    duckRack: 'customer'
  },
  amount: {
    type: Number,
    integer: true
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
})
const customerSchema = new Schema({
  firstName: String,
  lastName: String,
  email: String
})

const OrderModel = new Duck({ schema: orderSchema })
const CustomerModel = new Duck({ schema: customerSchema })

const OrderBucket = new DuckRack('order', {
  duckModel: OrderModel
})

OrderBucket.hook('before', 'create', function (entry) {
  return entry
})

async function loadReferences (entry) {
  const entriesToLoad = this.duckModel
    .schema
    .paths
    .filter((path) => {
      return this.duckModel.schema.schemaAtPath(path).settings.duckRack && Utils.find(entry, path)
    })
    .map(path => {
      const Rack = DuckStorage.getRackByName(this.duckModel.schema.schemaAtPath(path).settings.duckRack)
      const _idPayload = Utils.find(entry, path)
      const _id = Rack.duckModel.schema.isValid(_idPayload) ? _idPayload._id : _idPayload
      return { duckRack: this.duckModel.schema.schemaAtPath(path).settings.duckRack, _id, path }
    })

  for (const entryToLoad of entriesToLoad) {
    set(entry, entryToLoad.path, await DuckStorage.getRackByName(entryToLoad.duckRack).findOneById(entryToLoad._id))
  }

  return entry
}

OrderBucket.hook('after', 'read', loadReferences)
OrderBucket.hook('after', 'create', loadReferences)

const CustomerBucket = new DuckRack('customer', {
  duckModel: CustomerModel
})

const customer = await CustomerBucket.create({
  firstName: 'Martin',
  lastName: 'Rafael',
  email: 'tin@devtin.io'
})

t.truthy(customer._id)

// console.log({ customer })
// console.log(CustomerModel.schema.parse(customer))

try {
  await OrderModel.schema.parse({
    customer,
    amount: 100
  })
} catch (err) {
  console.log('\n\nERROR\n\n', err)
  throw err
}

const order = await OrderBucket.create({
  customer,
  amount: 100
})

t.deepEqual(order.customer, customer.consolidate())

const readOrder = await OrderBucket.read(order._id)
t.deepEqual(readOrder.customer, customer.consolidate())

defines duck rack methods

const userSchema = new Schema({
  name: String,
  level: String
})
const userDuckModel = new Duck({ schema: userSchema })
const UserRack = new DuckRack('some-user', {
  duckModel: userDuckModel,
  methods: {
    changeLevel: {
      input: {
        userId: 'ObjectId',
        newLevel: String
      },
      async handler ({ userId, newLevel }) {
        const user = await this.findOneById(userId)
        user.level = newLevel
        return this.update(userId, user)
      }
    },
    getAdmins () {
      return this.list({
        level: {
          $eq: 'admin'
        }
      })
    },
    getUsers () {
      return this.list({
        level: {
          $eq: 'user'
        }
      })
    }
  }
})

await UserRack.create({
  name: 'Martin',
  level: 'admin'
})

await UserRack.create({
  name: 'Rafael',
  level: 'admin'
})

await UserRack.create({
  name: 'Pedro',
  level: 'user'
})

const admins = await UserRack.getAdmins()
t.truthy(admins)
t.is(admins.length, 2)

const users = await UserRack.getUsers()
t.truthy(users)
t.is(users.length, 1)

UserRack.changeLevel({
  userId: admins[0]._id,
  newLevel: 'user'
})

validates properties in realtime

const duckModel = Duck.create({ schema: AdvancedSchema })
let err

err = t.throws(() => { return duckModel.dont.do.this.to.me })
t.is(err.message, 'Unknown property dont')

err = t.throws(() => { duckModel.firstName = 123 })
t.is(err.message, 'Invalid string')

err = t.throws(() => { duckModel.address.zip = '33q29' })
t.is(err.message, 'Invalid number')

err = t.throws(() => { duckModel.email = 'martin' })
t.is(err.message, 'Invalid e-mail address')

t.notThrows(() => { duckModel.firstName = 'Martin' })
t.notThrows(() => { duckModel.lastName = 'Rafael' })
t.notThrows(() => { duckModel.email = 'tin@devtin.io' })
t.notThrows(() => { duckModel.address.line1 = 'Brickell' })
t.notThrows(() => { duckModel.address.zip = 305 })

t.is(duckModel.firstName, 'Martin')
t.is(duckModel.lastName, 'Rafael')
t.is(duckModel.email, 'tin@devtin.io')
t.is(duckModel.address.line1, 'Brickell')
t.is(duckModel.address.zip, 305)

err = t.throws(() => duckModel.getEmailDomain())
t.is(err.message, 'consolidate the model prior invoking method getEmailDomain')

duckModel.consolidate()

t.truthy(duckModel._id)
t.is(duckModel.getEmailDomain(), 'devtin.io')

t.deepEqual(duckModel.toObject(), {
  _id: duckModel._id,
  _v: 1,
  firstName: 'Martin',
  lastName: 'Rafael',
  email: 'tin@devtin.io',
  address: {
    line1: 'Brickell',
    zip: 305
  }
})

registers duck racks from given dir

checks all events emitted by a duck

const User = new Schema({
  name: String,
  emails: {
    type: Array,
    default () {
      return []
    }
  }
}, {
  methods: {
    addEmail: {
      events: {
        emailAdded: String
      },
      input: String,
      handler (email) {
        this.$field.emails.push(email)
        this.$emit('emailAdded', email)
      }
    }
  }
})

const userPayload = User.parse({
  name: 'Martin'
})

const eventsFired = schemaDuckMonitor(User, userPayload)

t.is(eventsFired.length, 0)
userPayload.addEmail('martin')
t.is(eventsFired.length, 1)


DuckRack

Description:

Stores only ducks specified by the duckModel


duckRack.read(_id) ⇒ Promise.<*>

Param
_id

Description:

Sugar for find(entityName, { _id: { $eq: _id } })


Duck

Description:

A duck model


duck.getModel([defaultValues], [state]) ⇒ Object

Param Type
[defaultValues] Object
[state] Object

Returns: Object - the duck proxy model
Description:

Prepares a duck proxy model to be used with the defined schema


Duck.create(duckPayload, [...modelPayload]) ⇒ Object

Param Type Description
duckPayload Object the duck constructor payload
[...modelPayload] the model payload

Returns: Object - the duck proxy model
Description:

Sugar for calling new Duck({...}).getModel()


schemaDuckMonitor ⇒ Array

Param Type
schema Object
payload Object

Returns: Array - an array with all of the events fired
Description:

Logs all events emitted by a duck


License

MIT

© 2020-present Martin Rafael Gonzalez tin@devtin.io

Readme

Keywords

Package Sidebar

Install

npm i duck-storage

Weekly Downloads

0

Version

0.0.34

License

MIT

Unpacked Size

2.51 MB

Total Files

77

Last publish

Collaborators

  • tin_r