Skip to content

Commit

Permalink
feat: support update API
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Jun 4, 2022
1 parent 8f45243 commit c23564c
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 37 deletions.
83 changes: 46 additions & 37 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,25 @@ export namespace Plugin {
: T extends Object<infer U> ? U
: never

export class State {
export abstract class State {
id = Math.random().toString(36).slice(2, 10)
runtime: Runtime
context: Context
disposables: Disposable[] = []

abstract execute(): void

constructor(public parent: Context, public config: any) {
this.context = parent.fork({ state: this })
}

dispose() {
update(config: any) {
this.reset()
this.config = config
this.execute()
}

protected reset() {
this.disposables.splice(0, Infinity).forEach(dispose => dispose())
}
}
Expand All @@ -54,15 +62,24 @@ export namespace Plugin {
constructor(parent: Context, config: any, runtime: Runtime) {
super(parent, config)
this.runtime = runtime
this.dispose = this.dispose.bind(this)
defineProperty(this.dispose, kState, true)
defineProperty(this.dispose, 'name', `state <${parent.source}>`)
runtime.children.push(this)
runtime.disposables.push(this.dispose)
parent.state?.disposables.push(this.dispose)
this.execute()
}

dispose = () => {
super.dispose()
execute() {
if (!this.runtime.isActive) return
for (const fork of this.runtime.forkers) {
fork(this.context, this.config)
}
}

dispose() {
this.reset()
remove(this.runtime.disposables, this.dispose)
if (remove(this.runtime.children, this) && !this.runtime.children.length) {
this.runtime.dispose()
Expand All @@ -76,7 +93,7 @@ export namespace Plugin {
schema: any
using: readonly string[] = []
forkers: Function[] = []
children: State[] = []
children: Fork[] = []
isActive = false

constructor(private registry: Registry, public plugin: Plugin, config: any) {
Expand All @@ -85,24 +102,31 @@ export namespace Plugin {
return this.children.some(p => p.context.match(session))
}
registry.set(plugin, this)
if (plugin) this.start()
if (plugin) this.init()
}

fork(parent: Context, config: any) {
const state = new Fork(parent, config, this)
if (this.isActive) {
this.executeFork(state)
}
return state
return new Fork(parent, config, this)
}

dispose = () => {
super.dispose()
if (this.plugin) this.stop()
dispose() {
super.reset()
if (this.plugin) {
this.registry.delete(this.plugin)
this.context.emit('logger/debug', 'app', 'dispose:', this.plugin.name)
this.context.emit('plugin-removed', this)
}
return this
}

start() {
reset() {
this.disposables = this.disposables.filter((dispose) => {
if (dispose[kState]) return true
dispose()
})
}

init() {
this.schema = this.plugin['Config'] || this.plugin['schema']
this.using = this.plugin['using'] || []
this.registry.app.emit('plugin-added', this)
Expand All @@ -113,30 +137,15 @@ export namespace Plugin {
}

if (this.using.length) {
this.context.on('service', (name) => {
const dispose = this.context.on('service', (name) => {
if (!this.using.includes(name)) return
this.disposables = this.disposables.filter((dispose, index) => {
// the first element is the "service" event listener
if (!index || dispose[kState]) return true
dispose()
})
this.callback()
this.reset()
this.execute()
})
defineProperty(dispose, kState, true)
}

this.callback()
}

stop() {
this.registry.delete(this.plugin)
this.context.emit('logger/debug', 'app', 'dispose:', this.plugin.name)
this.context.emit('plugin-removed', this)
}

private executeFork(state: State) {
for (const fork of this.forkers) {
fork(state.context, state.config)
}
this.execute()
}

private apply = (context: Context, config: any) => {
Expand All @@ -154,7 +163,7 @@ export namespace Plugin {
}
}

private callback() {
execute() {
if (this.using.some(name => !this.context[name])) return

// execute plugin body
Expand All @@ -164,7 +173,7 @@ export namespace Plugin {
}

for (const state of this.children) {
this.executeFork(state)
state.execute()
}
}
}
Expand Down
65 changes: 65 additions & 0 deletions tests/update.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { App, Plugin } from '../src'
import { expect } from 'chai'
import { event } from './shared'
import * as jest from 'jest-mock'

describe('Config', () => {
it('basic support', () => {
const app = new App()
const dispose = jest.fn(() => {})
const callback = jest.fn<Plugin.Function>((ctx) => {
ctx.on('dispose', dispose)
ctx.on(event, () => {
ctx.state.update({ value: 2 })
})
})

app.plugin(callback, { value: 1 })
expect(dispose.mock.calls).to.have.length(0)
expect(callback.mock.calls).to.have.length(1)
app.emit(event)
expect(dispose.mock.calls).to.have.length(1)
expect(callback.mock.calls).to.have.length(2)

expect(callback.mock.calls[0][0]).to.equal(callback.mock.calls[1][0])
expect(callback.mock.calls[0][1]).to.deep.equal({ value: 1 })
expect(callback.mock.calls[1][1]).to.deep.equal({ value: 2 })
})

it('update fork', () => {
const app = new App()
const inner = jest.fn<Plugin.Function>()
const outer = jest.fn<Plugin.Function>((ctx) => {
ctx.on('fork', inner)
})

const fork1 = app.plugin(outer, { value: 1 })
const fork2 = app.plugin(outer, { value: 0 })
expect(inner.mock.calls).to.have.length(2)
expect(outer.mock.calls).to.have.length(1)

fork2.update({ value: 2 })
expect(inner.mock.calls).to.have.length(3)
expect(outer.mock.calls).to.have.length(1)
expect(fork1.config).to.deep.equal({ value: 1 })
expect(fork2.config).to.deep.equal({ value: 2 })
})

it('deferred update', () => {
const app = new App()
const callback = jest.fn()
const plugin = {
using: ['foo'],
reusable: true,
apply: callback,
}

const fork = app.plugin(plugin, { value: 1 })
expect(callback.mock.calls).to.have.length(0)
fork.update({ value: 2 })
expect(callback.mock.calls).to.have.length(0)
app.foo = {}
expect(callback.mock.calls).to.have.length(1)
expect(callback.mock.calls[0][1]).to.deep.equal({ value: 2 })
})
})

0 comments on commit c23564c

Please sign in to comment.