• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excaliburjs / Excalibur / 15926655389

27 Jun 2025 12:47PM UTC coverage: 87.851% (-0.02%) from 87.869%
15926655389

Pull #3380

github

web-flow
Merge 13b75c379 into 7cdeaedec
Pull Request #3380: feat: support any, all and not component/tag filters on Query

5083 of 7051 branches covered (72.09%)

59 of 63 new or added lines in 3 files covered. (93.65%)

4 existing lines in 1 file now uncovered.

13710 of 15606 relevant lines covered (87.85%)

25174.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.42
/src/engine/EntityComponentSystem/Entity.ts
1
import type { ComponentCtor } from './Component';
2
import { Component, isComponentCtor } from './Component';
3

4
import type { Message } from '../Util/Observable';
5
import { Observable } from '../Util/Observable';
6
import type { OnInitialize, OnPreUpdate, OnPostUpdate, OnAdd, OnRemove } from '../Interfaces/LifecycleEvents';
7
import type { Engine } from '../Engine';
8
import { InitializeEvent, PreUpdateEvent, PostUpdateEvent, AddEvent, RemoveEvent } from '../Events';
9
import { KillEvent } from '../Events';
10
import type { EventKey, Handler, Subscription } from '../EventEmitter';
11
import { EventEmitter } from '../EventEmitter';
12
import type { Scene } from '../Scene';
13
import { removeItemFromArray } from '../Util/Util';
14
import type { MaybeKnownComponent } from './Types';
15
import { Logger } from '../Util/Log';
16

17
/**
18
 * Interface holding an entity component pair
19
 */
20
export interface EntityComponent {
21
  component: Component;
22
  entity: Entity;
23
}
24

25
/**
26
 * AddedComponent message
27
 */
28
export class AddedComponent implements Message<EntityComponent> {
29
  readonly type: 'Component Added' = 'Component Added';
×
30
  constructor(public data: EntityComponent) {}
×
31
}
32

33
/**
34
 * Type guard to know if message is f an Added Component
35
 */
36
export function isAddedComponent(x: Message<EntityComponent>): x is AddedComponent {
37
  return !!x && x.type === 'Component Added';
×
38
}
39

40
/**
41
 * RemovedComponent message
42
 */
43
export class RemovedComponent implements Message<EntityComponent> {
44
  readonly type: 'Component Removed' = 'Component Removed';
×
45
  constructor(public data: EntityComponent) {}
×
46
}
47

48
/**
49
 * Type guard to know if message is for a Removed Component
50
 */
51
export function isRemovedComponent(x: Message<EntityComponent>): x is RemovedComponent {
52
  return !!x && x.type === 'Component Removed';
×
53
}
54

55
/**
56
 * Built in events supported by all entities
57
 */
58
export type EntityEvents = {
59
  initialize: InitializeEvent;
60
  //@ts-ignore
61
  add: AddEvent;
62
  //@ts-ignore
63
  remove: RemoveEvent;
64
  preupdate: PreUpdateEvent;
65
  postupdate: PostUpdateEvent;
66
  kill: KillEvent;
67
};
68

69
export const EntityEvents = {
118✔
70
  Add: 'add',
71
  Remove: 'remove',
72
  Initialize: 'initialize',
73
  PreUpdate: 'preupdate',
74
  PostUpdate: 'postupdate',
75
  Kill: 'kill'
76
} as const;
77

78
export interface EntityOptions<TComponents extends Component> {
79
  name?: string;
80
  components?: TComponents[];
81
  silenceWarnings?: boolean;
82
}
83

84
/**
85
 * An Entity is the base type of anything that can have behavior in Excalibur, they are part of the built in entity component system
86
 *
87
 * Entities can be strongly typed with the components they contain
88
 *
89
 * ```typescript
90
 * const entity = new Entity<ComponentA | ComponentB>();
91
 * entity.components.a; // Type ComponentA
92
 * entity.components.b; // Type ComponentB
93
 * ```
94
 */
95
export class Entity<TKnownComponents extends Component = any> implements OnInitialize, OnPreUpdate, OnPostUpdate, OnAdd, OnRemove {
118✔
96
  private static _ID = 0;
97
  /**
98
   * The unique identifier for the entity
99
   */
100
  public id: number = Entity._ID++;
7,536✔
101

102
  public name = `Entity#${this.id}`;
7,536✔
103

104
  /**
105
   * Listen to or emit events for an entity
106
   */
107
  public events = new EventEmitter<EntityEvents>();
7,536✔
108
  private _tags = new Set<string>();
7,536✔
109
  public componentAdded$ = new Observable<Component>();
7,536✔
110
  public componentRemoved$ = new Observable<Component>();
7,536✔
111
  public tagAdded$ = new Observable<string>();
7,536✔
112
  public tagRemoved$ = new Observable<string>();
7,536✔
113
  /**
114
   * Current components on the entity
115
   *
116
   * **Do not modify**
117
   *
118
   * Use addComponent/removeComponent otherwise the ECS will not be notified of changes.
119
   */
120
  public readonly components = new Map<Function, Component>();
7,536✔
121
  public componentValues: Component[] = [];
7,536✔
122
  private _componentsToRemove: ComponentCtor[] = [];
7,536✔
123

124
  constructor(options: EntityOptions<TKnownComponents>);
125
  constructor(components?: TKnownComponents[], name?: string);
126
  constructor(componentsOrOptions?: TKnownComponents[] | EntityOptions<TKnownComponents>, name?: string) {
127
    let componentsToAdd!: TKnownComponents[];
128
    let nameToAdd: string | undefined;
129
    let silence = false;
7,536✔
130
    if (Array.isArray(componentsOrOptions)) {
7,536✔
131
      componentsToAdd = componentsOrOptions;
2,934✔
132
      nameToAdd = name;
2,934✔
133
    } else if (componentsOrOptions && typeof componentsOrOptions === 'object') {
4,602✔
134
      const { components, name, silenceWarnings } = componentsOrOptions;
3,500✔
135
      componentsToAdd = components ?? [];
3,500!
136
      nameToAdd = name;
3,500✔
137
      silence = !!silenceWarnings;
3,500✔
138
    }
139
    if (nameToAdd) {
7,536✔
140
      this.name = nameToAdd;
10✔
141
    }
142
    if (componentsToAdd) {
7,536✔
143
      for (const component of componentsToAdd) {
6,434✔
144
        this.addComponent(component);
8,656✔
145
      }
146
    }
147

148
    if (process.env.NODE_ENV === 'development') {
7,536!
149
      if (!silence) {
7,536✔
150
        setTimeout(() => {
4,036✔
151
          if (!this.scene && !this.isInitialized) {
183✔
152
            Logger.getInstance().warn(`Entity "${this.name || this.id}" was not added to a scene.`);
84!
153
          }
154
        }, 5000);
155
      }
156
    }
157
  }
158

159
  /**
160
   * The current scene that the entity is in, if any
161
   */
162
  public scene: Scene | null = null;
7,536✔
163

164
  /**
165
   * Whether this entity is active, if set to false it will be reclaimed
166
   * @deprecated use isActive
167
   */
168
  public get active(): boolean {
169
    return this.isActive;
1✔
170
  }
171

172
  /**
173
   * Whether this entity is active, if set to false it will be reclaimed
174
   * @deprecated use isActive
175
   */
176
  public set active(val: boolean) {
177
    this.isActive = val;
1✔
178
  }
179

180
  /**
181
   * Whether this entity is active, if set to false it will be reclaimed
182
   */
183
  public isActive: boolean = true;
7,536✔
184

185
  /**
186
   * Kill the entity, means it will no longer be updated. Kills are deferred to the end of the update.
187
   * If parented it will be removed from the parent when killed.
188
   */
189
  public kill() {
190
    if (this.isActive) {
30!
191
      this.isActive = false;
30✔
192
      this.unparent();
30✔
193
    }
194
    this.emit('kill', new KillEvent(this));
30✔
195
  }
196

197
  public isKilled() {
198
    return !this.isActive;
2✔
199
  }
200

201
  /**
202
   * Specifically get the tags on the entity from {@apilink TagsComponent}
203
   */
204
  public get tags(): Set<string> {
205
    return this._tags;
30✔
206
  }
207

208
  /**
209
   * Check if a tag exists on the entity
210
   * @param tag name to check for
211
   */
212
  public hasTag(tag: string): boolean {
213
    return this._tags.has(tag);
5,022✔
214
  }
215

216
  /**
217
   * Adds a tag to an entity
218
   * @param tag
219
   */
220
  public addTag(tag: string): Entity<TKnownComponents> {
221
    this._tags.add(tag);
58✔
222
    this.tagAdded$.notifyAll(tag);
58✔
223
    return this;
58✔
224
  }
225

226
  /**
227
   * Removes a tag on the entity
228
   *
229
   * Removals are deferred until the end of update
230
   * @param tag
231
   */
232
  public removeTag(tag: string): Entity<TKnownComponents> {
233
    this._tags.delete(tag);
4✔
234
    this.tagRemoved$.notifyAll(tag);
4✔
235
    return this;
4✔
236
  }
237

238
  /**
239
   * The types of the components on the Entity
240
   */
241
  public get types(): ComponentCtor[] {
242
    return Array.from(this.components.keys()) as ComponentCtor[];
24✔
243
  }
244

245
  /**
246
   * Returns all component instances on entity
247
   */
248
  public getComponents(): Component[] {
249
    return Array.from(this.components.values());
8✔
250
  }
251

252
  /**
253
   * Verifies that an entity has all the required types
254
   * @param requiredTypes
255
   */
256
  hasAll<TComponent extends Component>(requiredTypes: ComponentCtor<TComponent>[]): boolean {
UNCOV
257
    for (let i = 0; i < requiredTypes.length; i++) {
×
UNCOV
258
      if (!this.components.has(requiredTypes[i])) {
×
UNCOV
259
        return false;
×
260
      }
261
    }
UNCOV
262
    return true;
×
263
  }
264

265
  /**
266
   * Verifies that an entity has all the required tags
267
   * @param requiredTags
268
   */
269
  hasAllTags(requiredTags: string[]): boolean {
270
    for (let i = 0; i < requiredTags.length; i++) {
13✔
271
      if (!this.tags.has(requiredTags[i])) {
26✔
272
        return false;
2✔
273
      }
274
    }
275
    return true;
11✔
276
  }
277

278
  get<TComponent extends Component>(type: ComponentCtor<TComponent>): MaybeKnownComponent<TComponent, TKnownComponents> {
279
    return this.components.get(type) as MaybeKnownComponent<TComponent, TKnownComponents>;
68,490✔
280
  }
281

282
  private _parent: Entity | null = null;
7,536✔
283
  public get parent(): Entity | null {
284
    return this._parent;
15,217✔
285
  }
286

287
  public childrenAdded$ = new Observable<Entity>();
7,536✔
288
  public childrenRemoved$ = new Observable<Entity>();
7,536✔
289

290
  private _children: Entity[] = [];
7,536✔
291
  /**
292
   * Get the direct children of this entity
293
   */
294
  public get children(): readonly Entity[] {
295
    return this._children;
20,001✔
296
  }
297

298
  /**
299
   * Unparents this entity, if there is a parent. Otherwise it does nothing.
300
   */
301
  public unparent() {
302
    if (this._parent) {
31✔
303
      this._parent.removeChild(this);
7✔
304
      this._parent = null;
7✔
305
    }
306
  }
307

308
  /**
309
   * Adds an entity to be a child of this entity
310
   * @param entity
311
   */
312
  public addChild(entity: Entity): Entity {
313
    if (entity.parent === null) {
2,969✔
314
      if (this.getAncestors().includes(entity)) {
2,968✔
315
        throw new Error('Cycle detected, cannot add entity');
1✔
316
      }
317
      this._children.push(entity);
2,967✔
318
      entity._parent = this;
2,967✔
319
      this.childrenAdded$.notifyAll(entity);
2,967✔
320
    } else {
321
      throw new Error('Entity already has a parent, cannot add without unparenting');
1✔
322
    }
323
    return this;
2,967✔
324
  }
325

326
  /**
327
   * Remove an entity from children if it exists
328
   * @param entity
329
   */
330
  public removeChild(entity: Entity): Entity {
331
    if (entity.parent === this) {
22!
332
      removeItemFromArray(entity, this._children);
22✔
333
      entity._parent = null;
22✔
334
      this.childrenRemoved$.notifyAll(entity);
22✔
335
    }
336
    return this;
22✔
337
  }
338

339
  /**
340
   * Removes all children from this entity
341
   */
342
  public removeAllChildren(): Entity {
343
    // Avoid modifying the array issue by walking backwards
344
    for (let i = this.children.length - 1; i >= 0; i--) {
1✔
345
      this.removeChild(this.children[i]);
6✔
346
    }
347
    return this;
1✔
348
  }
349

350
  /**
351
   * Returns a list of parent entities starting with the topmost parent. Includes the current entity.
352
   */
353
  public getAncestors(): Entity[] {
354
    const result: Entity[] = [this];
7,808✔
355
    let current = this.parent;
7,808✔
356
    while (current) {
7,808✔
357
      result.push(current);
2,052✔
358
      current = current.parent;
2,052✔
359
    }
360
    return result.reverse();
7,808✔
361
  }
362

363
  /**
364
   * Returns a list of all the entities that descend from this entity. Includes the current entity.
365
   */
366
  public getDescendants(): Entity[] {
367
    let result: Entity[] = [this];
1✔
368
    let queue: Entity[] = [this];
1✔
369
    while (queue.length > 0) {
1✔
370
      const curr = queue.pop();
3✔
371
      if (curr) {
3!
372
        queue = queue.concat(curr.children);
3✔
373
        result = result.concat(curr.children);
3✔
374
      }
375
    }
376
    return result;
1✔
377
  }
378

379
  /**
380
   * Creates a deep copy of the entity and a copy of all its components
381
   */
382
  public clone(): Entity {
383
    const newEntity = new Entity();
10✔
384
    for (const c of this.types) {
10✔
385
      const componentInstance = this.get(c);
14✔
386
      if (componentInstance) {
14!
387
        newEntity.addComponent(componentInstance.clone());
14✔
388
      }
389
    }
390
    for (const child of this.children) {
10✔
391
      newEntity.addChild(child.clone());
2✔
392
    }
393
    return newEntity;
10✔
394
  }
395

396
  /**
397
   * Adds a copy of all the components from another template entity as a "prefab"
398
   * @param templateEntity Entity to use as a template
399
   * @param force Force component replacement if it already exists on the target entity
400
   */
401
  public addTemplate(templateEntity: Entity, force: boolean = false): Entity {
3✔
402
    for (const c of templateEntity.getComponents()) {
3✔
403
      this.addComponent(c.clone(), force);
5✔
404
    }
405
    for (const child of templateEntity.children) {
3✔
406
      this.addChild(child.clone().addTemplate(child));
2✔
407
    }
408
    return this;
3✔
409
  }
410

411
  private _getClassHierarchyRoot(componentType: ComponentCtor): ComponentCtor {
412
    let current = componentType;
25,881✔
413
    let parent = Object.getPrototypeOf(current.prototype)?.constructor;
25,881!
414

415
    while (parent && parent !== Object && parent !== Component) {
25,881✔
416
      current = parent;
2✔
417
      parent = Object.getPrototypeOf(current.prototype)?.constructor;
2!
418
    }
419
    return current;
25,881✔
420
  }
421

422
  /**
423
   * Adds a component to the entity
424
   * @param component Component or Entity to add copy of components from
425
   * @param force Optionally overwrite any existing components of the same type
426
   */
427
  public addComponent<TComponent extends Component>(component: TComponent, force: boolean = false): Entity<TKnownComponents | TComponent> {
29,455✔
428
    // if component already exists, skip if not forced
429
    if (this.has(component.constructor as ComponentCtor)) {
29,480✔
430
      if (force) {
3,638✔
431
        // Remove existing component type if exists when forced
432
        this.removeComponent(component.constructor as ComponentCtor, true);
7✔
433
      } else {
434
        // early exit component exits
435
        return this as Entity<TKnownComponents | TComponent>;
3,631✔
436
      }
437
    }
438

439
    // TODO circular dependencies will be a problem
440
    if (component.dependencies && component.dependencies.length) {
25,849✔
441
      for (const ctor of component.dependencies) {
1,823✔
442
        this.addComponent(new ctor());
3,645✔
443
      }
444
    }
445

446
    component.owner = this;
25,849✔
447
    const rootComponent = this._getClassHierarchyRoot(component.constructor as ComponentCtor);
25,849✔
448
    this.components.set(rootComponent, component);
25,849✔
449
    this.components.set(component.constructor, component);
25,849✔
450
    this.componentValues.push(component);
25,849✔
451
    if (component.onAdd) {
25,849✔
452
      component.onAdd(this);
10,150✔
453
    }
454

455
    this.componentAdded$.notifyAll(component);
25,849✔
456
    return this as Entity<TKnownComponents | TComponent>;
25,849✔
457
  }
458

459
  /**
460
   * Removes a component from the entity, by default removals are deferred to the end of entity update to avoid consistency issues
461
   *
462
   * Components can be force removed with the `force` flag, the removal is not deferred and happens immediately
463
   * @param typeOrInstance
464
   * @param force
465
   */
466
  public removeComponent<TComponent extends Component>(
467
    typeOrInstance: ComponentCtor<TComponent> | TComponent,
468
    force = false
20✔
469
  ): Entity<Exclude<TKnownComponents, TComponent>> {
470
    let type: ComponentCtor<TComponent>;
471
    if (isComponentCtor(typeOrInstance)) {
52✔
472
      type = typeOrInstance;
51✔
473
    } else {
474
      type = typeOrInstance.constructor as ComponentCtor<TComponent>;
1✔
475
    }
476

477
    if (force) {
52✔
478
      const componentToRemove = this.components.get(type);
32✔
479
      if (componentToRemove) {
32!
480
        this.componentRemoved$.notifyAll(componentToRemove);
32✔
481
        componentToRemove.owner = undefined;
32✔
482
        if (componentToRemove.onRemove) {
32✔
483
          componentToRemove.onRemove(this);
8✔
484
        }
485
        const componentIndex = this.componentValues.indexOf(componentToRemove);
32✔
486
        if (componentIndex > -1) {
32!
487
          this.componentValues.splice(componentIndex, 1);
32✔
488
        }
489
      }
490

491
      const rootComponent = this._getClassHierarchyRoot(type);
32✔
492
      this.components.delete(rootComponent);
32✔
493
      this.components.delete(type); // remove after the notify to preserve typing
32✔
494
    } else {
495
      this._componentsToRemove.push(type);
20✔
496
    }
497

498
    return this as any;
52✔
499
  }
500

501
  public clearComponents() {
502
    const components = this.types;
2✔
503
    for (const c of components) {
2✔
504
      this.removeComponent(c);
14✔
505
    }
506
  }
507

508
  /**
509
   * @hidden
510
   * @internal
511
   */
512
  public processComponentRemoval() {
513
    for (const type of this._componentsToRemove) {
5,628✔
514
      this.removeComponent(type, true);
18✔
515
    }
516
    this._componentsToRemove.length = 0;
5,628✔
517
  }
518

519
  /**
520
   * Check if a component type exists
521
   * @param type
522
   */
523
  public has<TComponent extends Component>(type: ComponentCtor<TComponent>): boolean {
524
    return this.components.has(type);
52,694✔
525
  }
526

527
  private _isInitialized = false;
7,536✔
528
  private _isAdded = false;
7,536✔
529

530
  /**
531
   * Gets whether the actor is Initialized
532
   */
533
  public get isInitialized(): boolean {
534
    return this._isInitialized;
3,486✔
535
  }
536

537
  public get isAdded(): boolean {
538
    return this._isAdded;
6,192✔
539
  }
540

541
  /**
542
   * Initializes this entity, meant to be called by the Scene before first update not by users of Excalibur.
543
   *
544
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
545
   * @internal
546
   */
547
  public _initialize(engine: Engine) {
548
    if (!this.isInitialized) {
3,353✔
549
      this.onInitialize(engine);
1,309✔
550
      this.events.emit('initialize', new InitializeEvent(engine, this));
1,309✔
551
      this._isInitialized = true;
1,309✔
552
    }
553
  }
554

555
  /**
556
   * Adds this Actor, meant to be called by the Scene when Actor is added.
557
   *
558
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
559
   * @internal
560
   */
561
  public _add(engine: Engine) {
562
    if (!this.isAdded && this.isActive) {
3,096✔
563
      this.onAdd(engine);
1,271✔
564
      this.events.emit('add', new AddEvent(engine, this));
1,271✔
565
      this._isAdded = true;
1,271✔
566
    }
567
  }
568

569
  /**
570
   * Removes Actor, meant to be called by the Scene when Actor is added.
571
   *
572
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
573
   * @internal
574
   */
575
  public _remove(engine: Engine) {
576
    if (this.isAdded && !this.isActive) {
3,096✔
577
      this.onRemove(engine);
5✔
578
      this.events.emit('remove', new RemoveEvent(engine, this));
5✔
579
      this._isAdded = false;
5✔
580
    }
581
  }
582

583
  /**
584
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
585
   *
586
   * Internal _preupdate handler for {@apilink onPreUpdate} lifecycle event
587
   * @internal
588
   */
589
  public _preupdate(engine: Engine, elapsed: number): void {
590
    this.events.emit('preupdate', new PreUpdateEvent(engine, elapsed, this));
923✔
591
    this.onPreUpdate(engine, elapsed);
923✔
592
  }
593

594
  /**
595
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
596
   *
597
   * Internal _preupdate handler for {@apilink onPostUpdate} lifecycle event
598
   * @internal
599
   */
600
  public _postupdate(engine: Engine, elapsed: number): void {
601
    this.events.emit('postupdate', new PostUpdateEvent(engine, elapsed, this));
923✔
602
    this.onPostUpdate(engine, elapsed);
923✔
603
  }
604

605
  /**
606
   * `onInitialize` is called before the first update of the entity. This method is meant to be
607
   * overridden.
608
   *
609
   * Synonymous with the event handler `.on('initialize', (evt) => {...})`
610
   */
611
  public onInitialize(engine: Engine): void {
612
    // Override me
613
  }
614

615
  /**
616
   * `onAdd` is called when Actor is added to scene. This method is meant to be
617
   * overridden.
618
   *
619
   * Synonymous with the event handler `.on('add', (evt) => {...})`
620
   */
621
  public onAdd(engine: Engine): void {
622
    // Override me
623
  }
624

625
  /**
626
   * `onRemove` is called when Actor is added to scene. This method is meant to be
627
   * overridden.
628
   *
629
   * Synonymous with the event handler `.on('remove', (evt) => {...})`
630
   */
631
  public onRemove(engine: Engine): void {
632
    // Override me
633
  }
634

635
  /**
636
   * Safe to override onPreUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
637
   *
638
   * `onPreUpdate` is called directly before an entity is updated.
639
   */
640
  public onPreUpdate(engine: Engine, elapsed: number): void {
641
    // Override me
642
  }
643

644
  /**
645
   * Safe to override onPostUpdate lifecycle event handler. Synonymous with `.on('postupdate', (evt) =>{...})`
646
   *
647
   * `onPostUpdate` is called directly after an entity is updated.
648
   */
649
  public onPostUpdate(engine: Engine, elapsed: number): void {
650
    // Override me
651
  }
652

653
  /**
654
   *
655
   * Entity update lifecycle, called internally
656
   * @internal
657
   * @param engine
658
   * @param elapsed
659
   */
660
  public update(engine: Engine, elapsed: number): void {
661
    this._initialize(engine);
921✔
662
    this._add(engine);
921✔
663
    this._preupdate(engine, elapsed);
921✔
664
    for (const child of this.children) {
921✔
665
      child.update(engine, elapsed);
×
666
    }
667
    this._postupdate(engine, elapsed);
921✔
668
    this._remove(engine);
921✔
669
  }
670

671
  public emit<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, event: EntityEvents[TEventName]): void;
672
  public emit(eventName: string, event?: any): void;
673
  public emit<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, event?: any): void {
674
    this.events.emit(eventName, event);
8✔
675
  }
676

677
  public on<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, handler: Handler<EntityEvents[TEventName]>): Subscription;
678
  public on(eventName: string, handler: Handler<unknown>): Subscription;
679
  public on<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
680
    return this.events.on(eventName, handler);
9✔
681
  }
682

683
  public once<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, handler: Handler<EntityEvents[TEventName]>): Subscription;
684
  public once(eventName: string, handler: Handler<unknown>): Subscription;
685
  public once<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
686
    return this.events.once(eventName, handler);
×
687
  }
688

689
  public off<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, handler: Handler<EntityEvents[TEventName]>): void;
690
  public off(eventName: string, handler: Handler<unknown>): void;
691
  public off(eventName: string): void;
692
  public off<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, handler?: Handler<any>): void {
693
    if (handler) {
×
694
      this.events.off(eventName, handler);
×
695
    } else {
696
      this.events.off(eventName);
×
697
    }
698
  }
699
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc