Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions packages/core/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export interface Hook extends EventOptions {
}

export class EventsService {
_hooks: Record<keyof any, Hook[]> = {}
_hooks: Record<keyof any, Hook[]> = Object.create(null)

constructor(private ctx: Context) {
defineProperty(this, symbols.tracker, {
Expand Down Expand Up @@ -71,8 +71,9 @@ export class EventsService {

private _resolve(type: string, args: any[]) {
const thisArg = typeof args[0] === 'object' || typeof args[0] === 'function' ? args.shift() : null
const name: string = args.shift()
if (!name.startsWith('internal/') && this._hooks['internal/dispatch']?.length) {
const name: string | symbol = args.shift()
const isInternal = typeof name === 'string' && name.startsWith('internal/')
if (!isInternal && this._hooks['internal/dispatch']?.length) {
this.emit('internal/dispatch', type, name, args, thisArg)
}
const filter = thisArg?.[Context.filter]
Expand Down Expand Up @@ -174,5 +175,5 @@ export interface Events {
'internal/get'(ctx: Context, name: string, error: Error, next: () => any): any
'internal/set'(ctx: Context, name: string, value: any, error: Error, next: () => boolean): boolean
'internal/listener'(this: Context, name: string, listener: any, prepend: boolean): void
'internal/dispatch'(mode: DispatchMode, name: string, args: any[], thisArg: any): void
'internal/dispatch'(mode: DispatchMode, name: string | symbol, args: any[], thisArg: any): void
}
39 changes: 38 additions & 1 deletion packages/core/tests/events.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Context, Events } from '../src'
import { expect, describe, it } from 'vitest'
import { mock } from 'node:test'
import { event, Filter, Session } from './utils'
import { event, symbolEvent, Filter, Session } from './utils'

export function createArray<T>(length: number, create: (index: number) => T) {
return [...new Array(length).keys()].map(create)
Expand Down Expand Up @@ -41,6 +41,43 @@ describe('Events', () => {
expect(callback.mock.calls).to.have.length(1)
})

it('symbol events', async () => {
const { root } = setup()
const callback = mock.fn()
const dispose = root.on(symbolEvent, callback)
root.emit(symbolEvent)
expect(callback.mock.calls).to.have.length(1)
root.emit(symbolEvent)
expect(callback.mock.calls).to.have.length(2)
dispose()
root.emit(symbolEvent)
expect(callback.mock.calls).to.have.length(2)
})

it('symbol events dispatch across modes', async () => {
const { root } = setup()
const callback = mock.fn()
root.on(symbolEvent, callback)
root.emit(symbolEvent)
await root.parallel(symbolEvent)
await root.serial(symbolEvent)
root.bail(symbolEvent)
expect(callback.mock.calls).to.have.length(4)
})

it('prototype-named events', async () => {
const { root } = setup()
const callback = mock.fn()
const dispose = root.on('__proto__', callback)
root.emit('__proto__')
expect(callback.mock.calls).to.have.length(1)
dispose()
root.emit('__proto__')
expect(callback.mock.calls).to.have.length(1)
// the last listener disposal should release the event bucket
expect(root.events._hooks['__proto__']).to.have.length(0)
})

it('ctx.parallel()', async () => {
const { root } = setup()
await root.parallel(event)
Expand Down
3 changes: 3 additions & 0 deletions packages/core/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function sleep(ms = 0) {
}

export const event = 'custom-event'
export const symbolEvent = Symbol('symbol-event')

export class Session {
constructor(public flag: boolean) {}
Expand All @@ -41,6 +42,8 @@ export class Filter {
declare module '../src/events' {
interface Events {
[event](): void
[symbolEvent](): void
'__proto__'(): void
'test/waterfall'(value: number, next: () => number): number
}
}
Expand Down