feat(dispatcher): dependency injection (#55)

This commit is contained in:
alina sireneva 2024-05-01 23:47:25 +03:00 committed by GitHub
commit cb0dbb712a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 86 additions and 5 deletions

View file

@ -90,6 +90,10 @@ export interface DispatcherParams {
key?: StateKeyDelegate
}
export interface DispatcherDependencies {
// intentionally empty, to be extended by consumers
}
/**
* Updates dispatcher
*/
@ -112,6 +116,8 @@ export class Dispatcher<State extends object = never> {
private _customStateKeyDelegate?: StateKeyDelegate
private _customStorage?: StateService
private _deps: DispatcherDependencies = {}
private _errorHandler?: <T = {}>(
err: Error,
update: ParsedUpdate & T,
@ -193,6 +199,42 @@ export class Dispatcher<State extends object = never> {
return this._scene
}
/**
* Inject a dependency to be available in this dispatcher and all its children.
*
* **Note**: This is only available for the root dispatcher.
*/
inject<Name extends keyof DispatcherDependencies>(name: Name, value: DispatcherDependencies[Name]): void
/**
* Inject dependencies to be available in this dispatcher and all its children.
*
* **Note**: This is only available for the root dispatcher.
*/
inject(deps: Partial<DispatcherDependencies>): void
inject<Name extends keyof DispatcherDependencies>(
name: Name | Partial<DispatcherDependencies>,
value?: DispatcherDependencies[Name],
): void {
if (this._parent) {
throw new MtArgumentError('Cannot inject dependencies to child dispatchers')
}
if (typeof name === 'object') {
for (const [k, v] of Object.entries(name)) {
(this._deps as any)[k] = v
}
} else {
this._deps[name] = value!
}
}
/**
* Get the dependencies injected into this dispatcher.
*/
get deps(): DispatcherDependencies {
return this._deps
}
/**
* Bind the dispatcher to the client.
* Called by the constructor automatically if
@ -678,6 +720,7 @@ export class Dispatcher<State extends object = never> {
child._parent = this as any
child._client = this._client
child._storage = this._storage
child._deps = this._deps
child._stateKeyDelegate = this._stateKeyDelegate
child._customStorage ??= this._customStorage
child._customStateKeyDelegate ??= this._customStateKeyDelegate
@ -773,8 +816,9 @@ export class Dispatcher<State extends object = never> {
private _unparent(): void {
this._parent = this._client = undefined
;(this as any)._stateKeyDelegate = undefined
;(this as any)._storage = undefined
this._deps = {} // to avoid dangling references
this._stateKeyDelegate = undefined
this._storage = undefined
}
/**

View file

@ -1,8 +1,8 @@
import { describe, expect, it } from 'vitest'
import { PeersIndex } from '@mtcute/core'
import { Message, PeersIndex } from '@mtcute/core'
import { TelegramClient } from '@mtcute/core/client.js'
import { StubTelegramClient } from '@mtcute/test'
import { createStub, StubTelegramClient } from '@mtcute/test'
import { Dispatcher, PropagationAction } from '../src/index.js'
@ -237,4 +237,41 @@ describe('Dispatcher', () => {
])
})
})
describe('Dependency injection', () => {
it('should inject dependencies into update contexts', async () => {
const dp = Dispatcher.for(client)
dp.inject('foo' as never, 'foo' as never)
const log: string[] = []
dp.onNewMessage(() => {
// eslint-disable-next-line
log.push(`received ${(dp.deps as any).foo}`)
})
const dp2 = Dispatcher.child()
dp2.onNewMessage(() => {
// eslint-disable-next-line
log.push(`received ${(dp.deps as any).foo} (child)`)
})
dp.addChild(dp2)
await dp.dispatchUpdateNow({
name: 'new_message',
data: new Message(
createStub('message'),
emptyPeers,
),
})
expect(log).eql([
'received foo',
'received foo (child)',
])
})
})
})

View file

@ -14,7 +14,7 @@
"noImplicitAny": true,
"noImplicitThis": true,
"incremental": true,
"stripInternal": true,
"stripInternal": false,
"skipLibCheck": true,
"composite": true,
"types": [