diff --git a/packages/dispatcher/src/dispatcher.ts b/packages/dispatcher/src/dispatcher.ts index 1906d49d..b896b508 100644 --- a/packages/dispatcher/src/dispatcher.ts +++ b/packages/dispatcher/src/dispatcher.ts @@ -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 { private _customStateKeyDelegate?: StateKeyDelegate private _customStorage?: StateService + private _deps: DispatcherDependencies = {} + private _errorHandler?: ( err: Error, update: ParsedUpdate & T, @@ -193,6 +199,42 @@ export class Dispatcher { 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: 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): void + inject( + name: Name | Partial, + 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 { 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 { 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 } /** diff --git a/packages/dispatcher/tests/dispatcher.test.ts b/packages/dispatcher/tests/dispatcher.test.ts index e218c6ff..6ec7a0f2 100644 --- a/packages/dispatcher/tests/dispatcher.test.ts +++ b/packages/dispatcher/tests/dispatcher.test.ts @@ -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)', + ]) + }) + }) }) diff --git a/tsconfig.json b/tsconfig.json index fac8a718..7f150ec2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "noImplicitAny": true, "noImplicitThis": true, "incremental": true, - "stripInternal": true, + "stripInternal": false, "skipLibCheck": true, "composite": true, "types": [