Skip to content

Commit

Permalink
refactor: New memory, store, middlewares, adapters classes
Browse files Browse the repository at this point in the history
Using collection instances allows more consistent interfaces for start/load/shutdown of each
collection. Also memory modules is now only "short-term" with new store module for persistent data.

BREAKING CHANGE: All methods for interacting with memory, middleware and adpater collections are now
on those class instances instead of the bot. e.g. `bot.get` is now `bot.memory.get`,
`bot.hearMiddleware` is now `bot.middleware.hear`.

fix #96
  • Loading branch information
timkinnane committed Aug 23, 2018
1 parent 67d267f commit 2f2d584
Show file tree
Hide file tree
Showing 14 changed files with 587 additions and 527 deletions.
2 changes: 1 addition & 1 deletion src/adapters/mongo.spec.ts
Expand Up @@ -137,7 +137,7 @@ describe('[adapter-mongo]', () => {
new bot.CustomBranch(() => 2, () => 2, { id: 'B', force: true })
]
for (let branch of branches) await branch.process(b, new bot.Middleware('test'))
await adapter.keep('states', bot.convertInstance(b))
await adapter.keep('states', bot.store.plainObject(b))
const states = await mongo.getModel(testCollection).findOne({
sub: 'states',
type: 'store'
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -16,6 +16,7 @@ export * from './lib/path'
export * from './lib/branch'
export * from './lib/bit'
export * from './lib/memory'
export * from './lib/store'
export * from './lib/core'
export * from './lib/thought'
export * from './lib/json'
96 changes: 49 additions & 47 deletions src/lib/adapter.spec.ts
Expand Up @@ -28,56 +28,58 @@ describe('[adapter]', () => {
sinon.assert.calledOnce(use)
})
})
describe('.loadAdapters', () => {
it('loads nothing if none configured', () => {
expect(() => bot.loadAdapters()).to.not.throw()
describe('Adapters', () => {
describe('.load', () => {
it('loads nothing if none configured', () => {
expect(() => bot.adapters.load()).to.not.throw()
})
it('throws if bad path in config for adapter', () => {
bot.settings.set('messageAdapter', 'foo'),
expect(() => bot.adapters.load()).to.throw()
})
it('loads all configured adapters at valid path', () => {
bot.settings.set('storageAdapter', './lib/adapter.spec')
bot.settings.set('nluAdapter', './lib/adapter.spec')
bot.adapters.load()
sinon.assert.calledTwice(use)
})
it('keeps loaded adapters in collection', () => {
bot.settings.set('messageAdapter', './lib/adapter.spec')
bot.adapters.load()
expect(bot.adapters.message).to.be.instanceof(bot.Adapter)
expect(typeof bot.adapters.nlu).to.equal('undefined')
})
})
it('throws if bad path in config for adapter', () => {
bot.settings.set('messageAdapter', 'foo'),
expect(() => bot.loadAdapters()).to.throw()
describe('.start', () => {
it('starts all loaded adapters', async () => {
bot.adapters.storage = new MockAdapter(bot)
bot.adapters.nlu = new MockAdapter(bot)
const startStorage = sinon.spy(bot.adapters.storage, 'start')
const startNLU = sinon.spy(bot.adapters.nlu, 'start')
await bot.adapters.start()
sinon.assert.calledOnce(startStorage)
sinon.assert.calledOnce(startNLU)
})
})
it('loads all configured adapters at valid path', () => {
bot.settings.set('storageAdapter', './lib/adapter.spec')
bot.settings.set('nluAdapter', './lib/adapter.spec')
bot.loadAdapters()
sinon.assert.calledTwice(use)
describe('.shutdown', () => {
it('shuts down all loaded adapters', async () => {
bot.adapters.storage = new MockAdapter(bot)
bot.adapters.nlu = new MockAdapter(bot)
const shutdownStorage = sinon.spy(bot.adapters.storage, 'shutdown')
const shutdownNLU = sinon.spy(bot.adapters.nlu, 'shutdown')
await bot.adapters.shutdown()
sinon.assert.calledOnce(shutdownStorage)
sinon.assert.calledOnce(shutdownNLU)
})
})
it('keeps loaded adapters in collection', () => {
bot.settings.set('messageAdapter', './lib/adapter.spec')
bot.loadAdapters()
expect(bot.adapters.message).to.be.instanceof(bot.Adapter)
expect(typeof bot.adapters.nlu).to.equal('undefined')
})
})
describe('.startAdapters', () => {
it('starts all loaded adapters', async () => {
bot.adapters.storage = new MockAdapter(bot)
bot.adapters.nlu = new MockAdapter(bot)
const startStorage = sinon.spy(bot.adapters.storage, 'start')
const startNLU = sinon.spy(bot.adapters.nlu, 'start')
await bot.startAdapters()
sinon.assert.calledOnce(startStorage)
sinon.assert.calledOnce(startNLU)
})
})
describe('.shutdownAdapters', () => {
it('shuts down all loaded adapters', async () => {
bot.adapters.storage = new MockAdapter(bot)
bot.adapters.nlu = new MockAdapter(bot)
const shutdownStorage = sinon.spy(bot.adapters.storage, 'shutdown')
const shutdownNLU = sinon.spy(bot.adapters.nlu, 'shutdown')
await bot.shutdownAdapters()
sinon.assert.calledOnce(shutdownStorage)
sinon.assert.calledOnce(shutdownNLU)
})
})
describe('.unloadAdapters', () => {
it('clears all configured adapters', async () => {
bot.adapters.message = bot.loadAdapter('./lib/adapter.spec')
bot.adapters.nlu = bot.loadAdapter('./lib/adapter.spec')
bot.adapters.storage = bot.loadAdapter('./lib/adapter.spec')
bot.unloadAdapters()
expect(bot.adapters).to.eql({})
describe('.unload', () => {
it('clears all configured adapters', async () => {
bot.adapters.message = bot.loadAdapter('./lib/adapter.spec')
bot.adapters.nlu = bot.loadAdapter('./lib/adapter.spec')
bot.adapters.storage = bot.loadAdapter('./lib/adapter.spec')
bot.adapters.unload()
expect(bot.adapters).to.eql({})
})
})
})
})
108 changes: 55 additions & 53 deletions src/lib/adapter.ts
@@ -1,17 +1,6 @@
import path from 'path'
import * as bot from '..'

/** Collection of allowed adapter types for loading. */
const adapterTypes = ['message', 'nlu', 'storage']

/** Collection of adapter types and their loaded adapter. */
export const adapters: {
[key: string]: bot.Adapter | undefined
message?: bot.MessageAdapter | undefined
nlu?: bot.NLUAdapter | undefined
storage?: bot.StorageAdapter | undefined
} = {}

/**
* Require adapter module from local path or NPM package.
* If a module name is given, it will be required as normal or from the parent
Expand Down Expand Up @@ -57,52 +46,65 @@ export function loadAdapter (adapterPath?: string) {
}
}

/** Load all adapters, but don't yet start them. */
export function loadAdapters () {
bot.unloadAdapters()
for (let type of adapterTypes) {
const adapterPath = bot.settings.get(`${type}-adapter`)
if (adapterPath && adapterPath !== '' && !adapters[type]) {
try {
adapters[type] = loadAdapter(adapterPath)
} catch (err) {
bot.logger.error(err.message)
throw new Error(`[adapter] failed to load all adapters`)
/** Collection of allowed adapter types for loading. */
const adapterTypes = ['message', 'nlu', 'storage']

/** Collection of adapter types and their loaded adapter. */
export class Adapters {
[key: string]: any
message?: bot.MessageAdapter | undefined
nlu?: bot.NLUAdapter | undefined
storage?: bot.StorageAdapter | undefined

/** Load all adapters, but don't yet start them. */
load () {
this.unload()
for (let type of adapterTypes) {
const adapterPath = bot.settings.get(`${type}-adapter`)
if (adapterPath && adapterPath !== '' && !this[type]) {
try {
this[type] = loadAdapter(adapterPath)
} catch (err) {
bot.logger.error(err.message)
throw new Error(`[adapter] failed to load all adapters`)
}
}
}
}
}

/** Start each adapter concurrently, to resolve when all ready. */
export function startAdapters () {
return Promise.all(Object.keys(adapters).map((type) => {
let adapter = adapters[type]
if (adapter) {
bot.logger.debug(`[adapter] starting ${type} adapter: ${adapter.name}`)
return Promise.resolve(adapter.start()).catch((err) => {
bot.logger.error(`[adapter] startup failed: ${err.message}`)
throw err
})
} else {
bot.logger.debug(`[adapter] no ${type} type adapter defined`)
}
return undefined
}))
}
/** Start each adapter concurrently, to resolve when all ready. */
start () {
return Promise.all(Object.keys(this).map((type) => {
let adapter = this[type]
if (adapter) {
bot.logger.debug(`[adapter] starting ${type} adapter: ${adapter.name}`)
return Promise.resolve(adapter.start()).catch((err) => {
bot.logger.error(`[adapter] startup failed: ${err.message}`)
throw err
})
} else {
bot.logger.debug(`[adapter] no ${type} type adapter defined`)
}
return undefined
}))
}

/** Run shutdown on each adapter concurrently, to resolve when all shutdown */
export function shutdownAdapters () {
return Promise.all(Object.keys(adapters).map((type) => {
let adapter = adapters[type]
if (adapter) {
bot.logger.debug(`[adapter] shutdown ${type} adapter: ${adapter.name}`)
return Promise.resolve(adapter.shutdown())
}
return undefined
}))
}
/** Run shutdown on each adapter concurrently, to resolve when all shutdown */
shutdown () {
return Promise.all(Object.keys(this).map((type) => {
let adapter = this[type]
if (adapter) {
bot.logger.debug(`[adapter] shutdown ${type} adapter: ${adapter.name}`)
return Promise.resolve(adapter.shutdown())
}
return undefined
}))
}

/** Unload adapters for resetting bot */
export function unloadAdapters () {
for (let type of adapterTypes) delete adapters[type]
/** Unload adapters for resetting bot */
unload () {
for (let type of adapterTypes) delete this[type]
}
}

export const adapters = new Adapters()
16 changes: 8 additions & 8 deletions src/lib/core.ts
Expand Up @@ -36,8 +36,8 @@ export async function load () {
if (getStatus() !== 'waiting') await reset()
setStatus('loading')
try {
bot.loadMiddleware()
bot.loadAdapters()
bot.middlewares.load()
bot.adapters.load()
await eventDelay()
setStatus('loaded')
bot.events.emit('loaded')
Expand All @@ -57,8 +57,8 @@ export async function start () {
if (getStatus() !== 'loaded') await load()
setStatus('starting')
try {
await bot.startAdapters()
await bot.startMemory()
await bot.adapters.start()
await bot.memory.start()
} catch (err) {
bot.logger.error('[core] failed to start')
await bot.shutdown(1).catch()
Expand All @@ -84,8 +84,8 @@ export async function shutdown (exit = 0) {
} else if (status === 'starting') {
await new Promise((resolve) => bot.events.on('started', () => resolve()))
}
await bot.shutdownAdapters()
await bot.shutdownMemory()
await bot.adapters.shutdown()
await bot.memory.shutdown()
await eventDelay()
setStatus('shutdown')
bot.events.emit('shutdown')
Expand All @@ -111,8 +111,8 @@ export async function reset () {
const status = getStatus()
if (status !== 'shutdown') await shutdown()
try {
bot.unloadAdapters()
bot.unloadMiddleware()
bot.adapters.unload()
bot.middlewares.unload()
bot.global.reset()
bot.settings.resetConfig()
} catch (err) {
Expand Down
6 changes: 3 additions & 3 deletions src/lib/e2e-tests/e2e.spec.ts
Expand Up @@ -22,10 +22,10 @@ describe('[E2E]', () => {
})
it('responds from middleware', async () => {
bot.adapters.message!.dispatch = sandbox.stub()
bot.loadMiddleware()
bot.hearMiddleware((b, _, done) => b.respond('test').then(() => done()))
bot.middlewares.load()
bot.middleware.hear((b, _, done) => b.respond('test').then(() => done()))
await bot.receive(new bot.TextMessage(new bot.User(), ''))
sinon.assert.calledOnce((bot.adapters.message!.dispatch as sinon.SinonStub))
bot.unloadMiddleware()
bot.middlewares.unload()
})
})

0 comments on commit 2f2d584

Please sign in to comment.