Skip to content

Commit

Permalink
Merge pull request #17 from baethon/events
Browse files Browse the repository at this point in the history
Events
  • Loading branch information
radmen committed May 18, 2020
2 parents bf888e0 + 33097f9 commit ab462b9
Show file tree
Hide file tree
Showing 23 changed files with 1,126 additions and 117 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"lint:fix": "standard --fix"
},
"dependencies": {
"@baethon/promise-duck": "^1.0.1",
"dataloader": "^2.0.0",
"lodash.flow": "^3.5.0",
"lodash.frompairs": "^4.0.1",
Expand All @@ -25,6 +26,7 @@
},
"devDependencies": {
"ava": "^3.6.0",
"faker": "^4.1.0",
"husky": "^4.2.3",
"knex": "^0.20.13",
"lint-staged": "^10.1.1",
Expand Down
18 changes: 18 additions & 0 deletions src/events/deleted.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const Event = require('./event')

class Deleted extends Event {
static get eventName () {
return 'deleted'
}

/**
* @param {*} results
*/
constructor (results) {
super()

this.result = results
}
}

module.exports = Deleted
29 changes: 29 additions & 0 deletions src/events/deleting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const Event = require('./event')
const Deleted = require('./deleted')

class Deleting extends Event {
static get eventName () {
return 'deleting'
}

/**
* @param {String|String[]} returning
*/
constructor (returning) {
super()
this.returning = returning
}

mutateQueryBuilder (qb) {
qb._single.returning = this.returning
}

/**
* @inheritdoc
*/
toAfterEvent (results) {
return new Deleted(results)
}
}

module.exports = Deleting
34 changes: 34 additions & 0 deletions src/events/event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const { KexError } = require('../errors')

class Event {
static get eventName () {
throw new KexError('Event name should be set in child class')
}

constructor () {
this.cancelled = false
this.emitted = false
}

cancel () {
this.cancelled = true
}

markEmitted () {
this.emitted = true
}

mutateQueryBuilder (qb) {
// extend on when required
}

/**
* @param {*} results
* @return {Event}
*/
toAfterEvent (results) {
throw new KexError('toAfterEvent() should be implemented in the child class')
}
}

module.exports = Event
18 changes: 18 additions & 0 deletions src/events/fetched.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const Event = require('./event')

class Fetched extends Event {
static get eventName () {
return 'fetched'
}

/**
* @param {Object|Object[]} results
*/
constructor (results) {
super()

this.results = results
}
}

module.exports = Fetched
18 changes: 18 additions & 0 deletions src/events/fetching.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const Event = require('./event')
const Fetched = require('./fetched')

class Fetching extends Event {
static get eventName () {
return 'fetching'
}

/**
* @param {*} results
* @return {import('./fetched')}
*/
toAfterEvent (results) {
return new Fetched(results)
}
}

module.exports = Fetching
11 changes: 11 additions & 0 deletions src/events/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
EventsPipeline: require('./pipeline'),
DeletingEvent: require('./deleting'),
DeletedEvent: require('./deleted'),
FetchingEvent: require('./fetching'),
FetchedEvent: require('./fetched'),
UpdatingEvent: require('./updating'),
UpdatedEvent: require('./updated'),
InsertingEvent: require('./inserting'),
InsertedEvent: require('./inserted')
}
19 changes: 19 additions & 0 deletions src/events/inserted.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const Event = require('./event')

class Inserted extends Event {
static get eventName () {
return 'inserted'
}

/**
* @param {*} results
* @param {Object|Object[]} values
*/
constructor (results, values) {
super()
this.results = results
this.values = values
}
}

module.exports = Inserted
33 changes: 33 additions & 0 deletions src/events/inserting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const Event = require('./event')
const Inserted = require('./inserted')

class Inserting extends Event {
static get eventName () {
return 'inserting'
}

/**
* @param {Object|Object[]} values
* @param {String|String[]} returning
*/
constructor (values, returning) {
super()

this.values = values
this.returning = returning
}

mutateQueryBuilder (qb) {
qb._single.insert = this.values
qb._single.returning = this.returning
}

/**
* @inheritdoc
*/
toAfterEvent (results) {
return new Inserted(results, this.values)
}
}

module.exports = Inserting
90 changes: 90 additions & 0 deletions src/events/pipeline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/** @typedef { import('./event') } Event */

/**
* @callback EventListener
* @param {Event} event
*/

/** @typedef {Map<String, EventListener[]>} Listeners */

class EventsPipeline {
/**
* @param {Array} listeners entries for listeners mapTo
*/
constructor (listeners = []) {
/** @type {Listeners} */
this.listeners = new Map(listeners)
}

/**
* @param {String} eventName
* @param {EventListener} listener
* @return {Function} a callback which removes the listener
*/
on (eventName, listener) {
const list = this.listeners.get(eventName) || []
this.listeners.set(eventName, list.concat(listener))

return () => {
const list = this.listeners.get(eventName)
const index = list.indexOf(listener)

if (index >= 0) {
list.splice(index, 1)
this.listeners.set(eventName, list)
}
}
}

/**
* Execute all listeners of given event.
*
* The listeners are called serially.
* Event instance can be emitted only once. To repeat it emission, create new event.
*
* @param {Event} event
* @param {*} [bind] value to bind with the listener
* @return {Promise<Boolean>} the result of calling the listener;
* FALSE indicates that event was cancelled
*/
async emit (event, bind = null) {
if (event.emitted) {
return false
}

const { eventName } = event.constructor
const list = this.listeners.get(eventName) || []

event.markEmitted()

for (let i = 0; i < list.length; i++) {
const fn = list[i]

await fn.call(bind, event)

if (event.cancelled) {
return false
}
}

return true
}

/**
* Create copy of current instance.
*
* This method makes sure that all lists are dereferenced.
*
* @return {EventsPipeline}
*/
clone () {
const entries = Array.from(this.listeners.entries())

return new this.constructor(entries.map(([name, listeners]) => ([
name,
[...listeners]
])))
}
}

module.exports = EventsPipeline
20 changes: 20 additions & 0 deletions src/events/updated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const Event = require('./event')

class Updated extends Event {
static get eventName () {
return 'updated'
}

/**
* @param {*} results
* @param {Object|Object[]} values
*/
constructor (results, values) {
super()

this.results = results
this.values = values
}
}

module.exports = Updated
33 changes: 33 additions & 0 deletions src/events/updating.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const Event = require('./event')
const Updated = require('./updated')

class Updating extends Event {
static get eventName () {
return 'updating'
}

/**
* @param {Object|Object[]} values
* @param {String|String[]} returning
*/
constructor (values, returning) {
super()

this.values = values
this.returning = returning
}

mutateQueryBuilder (qb) {
qb._single.update = this.values
qb._single.returning = this.returning
}

/**
* @inheritdoc
*/
toAfterEvent (results) {
return new Updated(results, this.values)
}
}

module.exports = Updating
14 changes: 14 additions & 0 deletions src/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ const pluralize = require('pluralize')
const snakeCase = require('lodash.snakecase')
const QueryBuilder = require('./query-builder')
const { KexError } = require('./errors')
const { EventsPipeline } = require('./events')

/** @typedef { import('./plugins/soft-deletes').SoftDeleteOptions } SoftDeleteOptions */
/** @typedef { import('./relations/relation') } Relation */
/** @typedef { import('./query-builder').Scope } Scope */
/** @typedef { import('./plugins/timestamps').TimestampsOptions } TimestampsOptions */
/** @typedef { import('./events/pipeline').EventListener } EventListener */

/**
* @typedef {Object} ModelOptions
Expand Down Expand Up @@ -39,6 +41,7 @@ class Model {
this.options = options
this.QueryBuilder = QueryBuilder.createChildClass(this)
this.booted = false
this.events = new EventsPipeline()
}

get tableName () {
Expand All @@ -53,6 +56,7 @@ class Model {

query () {
this.bootIfNotBooted()

return this.QueryBuilder.create(this.kex.getKnexClient())
}

Expand All @@ -74,6 +78,16 @@ class Model {
}
}

/**
* @param {String} eventName
* @param {EventListener} listener
* @return {Model}
*/
on (eventName, listener) {
this.events.on(eventName, listener)
return this
}

/**
* @private
*/
Expand Down

0 comments on commit ab462b9

Please sign in to comment.