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

excaliburjs / Excalibur / 18827545564

27 Oct 2025 02:06AM UTC coverage: 88.6% (-0.007%) from 88.607%
18827545564

Pull #3550

github

web-flow
Merge 22eba81e9 into 301b69eea
Pull Request #3550: [feat] Adding hasChild() to Entity's

5254 of 7154 branches covered (73.44%)

7 of 8 new or added lines in 1 file covered. (87.5%)

1 existing line in 1 file now uncovered.

14425 of 16281 relevant lines covered (88.6%)

24363.83 hits per line

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

90.45
/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

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

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

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

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

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

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

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

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

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

100
  public name = `Entity#${this.id}`;
7,553✔
101

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

122
  constructor(options: EntityOptions<TKnownComponents>);
123
  constructor(components?: TKnownComponents[], name?: string);
124
  constructor(componentsOrOptions?: TKnownComponents[] | EntityOptions<TKnownComponents>, name?: string) {
125
    let componentsToAdd!: TKnownComponents[];
126
    let nameToAdd: string | undefined;
127

128
    if (Array.isArray(componentsOrOptions)) {
7,553✔
129
      componentsToAdd = componentsOrOptions;
2,934✔
130
      nameToAdd = name;
2,934✔
131
    } else if (componentsOrOptions && typeof componentsOrOptions === 'object') {
4,619!
132
      const { components, name } = componentsOrOptions;
×
133
      componentsToAdd = components ?? [];
×
134
      nameToAdd = name;
×
135
    }
136
    if (nameToAdd) {
7,553✔
137
      this.name = nameToAdd;
10✔
138
    }
139
    if (componentsToAdd) {
7,553✔
140
      for (const component of componentsToAdd) {
2,934✔
141
        this.addComponent(component);
8,656✔
142
      }
143
    }
144
  }
145

146
  /**
147
   * The current scene that the entity is in, if any
148
   */
149
  public scene: Scene | null = null;
7,553✔
150

151
  /**
152
   * Whether this entity is active, if set to false it will be reclaimed
153
   * @deprecated use isActive
154
   */
155
  public get active(): boolean {
156
    return this.isActive;
1✔
157
  }
158

159
  /**
160
   * Whether this entity is active, if set to false it will be reclaimed
161
   * @deprecated use isActive
162
   */
163
  public set active(val: boolean) {
164
    this.isActive = val;
1✔
165
  }
166

167
  /**
168
   * Whether this entity is active, if set to false it will be reclaimed
169
   */
170
  public isActive: boolean = true;
7,553✔
171

172
  /**
173
   * Kill the entity, means it will no longer be updated. Kills are deferred to the end of the update.
174
   * If parented it will be removed from the parent when killed.
175
   */
176
  public kill() {
177
    if (this.isActive) {
30!
178
      this.isActive = false;
30✔
179
      this.unparent();
30✔
180
    }
181
    this.emit('kill', new KillEvent(this));
30✔
182
  }
183

184
  public isKilled() {
185
    return !this.isActive;
2✔
186
  }
187

188
  /**
189
   * Specifically get the tags on the entity from {@apilink TagsComponent}
190
   */
191
  public get tags(): Set<string> {
192
    return this._tags;
18✔
193
  }
194

195
  /**
196
   * Check if a tag exists on the entity
197
   * @param tag name to check for
198
   */
199
  public hasTag(tag: string): boolean {
200
    return this._tags.has(tag);
5,067✔
201
  }
202

203
  /**
204
   * Adds a tag to an entity
205
   * @param tag
206
   */
207
  public addTag(tag: string): Entity<TKnownComponents> {
208
    this._tags.add(tag);
64✔
209
    this.tagAdded$.notifyAll(tag);
64✔
210
    return this;
64✔
211
  }
212

213
  /**
214
   * Removes a tag on the entity
215
   *
216
   * Removals are deferred until the end of update
217
   * @param tag
218
   */
219
  public removeTag(tag: string): Entity<TKnownComponents> {
220
    this._tags.delete(tag);
8✔
221
    this.tagRemoved$.notifyAll(tag);
8✔
222
    return this;
8✔
223
  }
224

225
  /**
226
   * The types of the components on the Entity
227
   */
228
  public get types(): ComponentCtor[] {
229
    return Array.from(this.components.keys()) as ComponentCtor[];
24✔
230
  }
231

232
  /**
233
   * Returns all component instances on entity
234
   */
235
  public getComponents(): Component[] {
236
    return Array.from(this.components.values());
8✔
237
  }
238

239
  /**
240
   * Verifies that an entity has all the required types
241
   * @param requiredTypes
242
   */
243
  hasAll<TComponent extends Component>(requiredTypes: ComponentCtor<TComponent>[]): boolean {
244
    for (let i = 0; i < requiredTypes.length; i++) {
×
245
      if (!this.components.has(requiredTypes[i])) {
×
246
        return false;
×
247
      }
248
    }
249
    return true;
×
250
  }
251

252
  /**
253
   * Verifies that an entity has all the required tags
254
   * @param requiredTags
255
   */
256
  hasAllTags(requiredTags: string[]): boolean {
257
    for (let i = 0; i < requiredTags.length; i++) {
7✔
258
      if (!this.tags.has(requiredTags[i])) {
14✔
259
        return false;
2✔
260
      }
261
    }
262
    return true;
5✔
263
  }
264

265
  get<TComponent extends Component>(type: ComponentCtor<TComponent>): MaybeKnownComponent<TComponent, TKnownComponents> {
266
    return this.components.get(type) as MaybeKnownComponent<TComponent, TKnownComponents>;
68,615✔
267
  }
268

269
  private _parent: Entity | null = null;
7,553✔
270
  public get parent(): Entity | null {
271
    return this._parent;
15,220✔
272
  }
273

274
  public childrenAdded$ = new Observable<Entity>();
7,553✔
275
  public childrenRemoved$ = new Observable<Entity>();
7,553✔
276

277
  private _children: Entity[] = [];
7,553✔
278
  /**
279
   * Get the direct children of this entity
280
   */
281
  public get children(): readonly Entity[] {
282
    return this._children;
20,013✔
283
  }
284

285
  /**
286
   * Unparents this entity, if there is a parent. Otherwise it does nothing.
287
   */
288
  public unparent() {
289
    if (this._parent) {
135✔
290
      this._parent.removeChild(this);
7✔
291
      this._parent = null;
7✔
292
    }
293
  }
294

295
  /**
296
   * Check if a child entity exists on the parent entity
297
   * @param child entity to check for
298
   * @param recursive whether to check recursively
299
   */
300
  public hasChild(child: Entity, recursive = false): boolean {
2✔
301
    if (!recursive) {
7✔
302
      return child.parent === this;
4✔
303
    }
304

305
    for (const c of this.children) {
3✔
306
      if (c === child) {
3✔
307
        return true;
2✔
308
      }
309
      if (recursive && c.hasChild(child, true)) {
1!
310
        return true;
1✔
311
      }
312
    }
313

NEW
314
    return false;
×
315
  }
316

317
  /**
318
   * Adds an entity to be a child of this entity
319
   * @param entity
320
   */
321
  public addChild(entity: Entity): Entity {
322
    if (entity.parent === null) {
2,971✔
323
      if (this.getAncestors().includes(entity)) {
2,970✔
324
        throw new Error('Cycle detected, cannot add entity');
1✔
325
      }
326
      this._children.push(entity);
2,969✔
327
      entity._parent = this;
2,969✔
328
      this.childrenAdded$.notifyAll(entity);
2,969✔
329
    } else {
330
      throw new Error('Entity already has a parent, cannot add without unparenting');
1✔
331
    }
332
    return this;
2,969✔
333
  }
334

335
  /**
336
   * Remove an entity from children if it exists
337
   * @param entity
338
   */
339
  public removeChild(entity: Entity): Entity {
340
    if (entity.parent === this) {
22!
341
      removeItemFromArray(entity, this._children);
22✔
342
      entity._parent = null;
22✔
343
      this.childrenRemoved$.notifyAll(entity);
22✔
344
    }
345
    return this;
22✔
346
  }
347

348
  /**
349
   * Removes all children from this entity
350
   */
351
  public removeAllChildren(): Entity {
352
    // Avoid modifying the array issue by walking backwards
353
    for (let i = this.children.length - 1; i >= 0; i--) {
1✔
354
      this.removeChild(this.children[i]);
6✔
355
    }
356
    return this;
1✔
357
  }
358

359
  /**
360
   * Returns a list of parent entities starting with the topmost parent. Includes the current entity.
361
   */
362
  public getAncestors(): Entity[] {
363
    const result: Entity[] = [this];
7,804✔
364
    let current = this.parent;
7,804✔
365
    while (current) {
7,804✔
366
      result.push(current);
2,053✔
367
      current = current.parent;
2,053✔
368
    }
369
    return result.reverse();
7,804✔
370
  }
371

372
  /**
373
   * Returns a list of all the entities that descend from this entity. Includes the current entity.
374
   */
375
  public getDescendants(): Entity[] {
376
    let result: Entity[] = [this];
1✔
377
    let queue: Entity[] = [this];
1✔
378
    while (queue.length > 0) {
1✔
379
      const curr = queue.pop();
3✔
380
      if (curr) {
3!
381
        queue = queue.concat(curr.children);
3✔
382
        result = result.concat(curr.children);
3✔
383
      }
384
    }
385
    return result;
1✔
386
  }
387

388
  /**
389
   * Creates a deep copy of the entity and a copy of all its components
390
   */
391
  public clone(): Entity {
392
    const newEntity = new Entity();
10✔
393
    for (const c of this.types) {
10✔
394
      const componentInstance = this.get(c);
14✔
395
      if (componentInstance) {
14!
396
        newEntity.addComponent(componentInstance.clone());
14✔
397
      }
398
    }
399
    for (const child of this.children) {
10✔
400
      newEntity.addChild(child.clone());
2✔
401
    }
402
    return newEntity;
10✔
403
  }
404

405
  /**
406
   * Adds a copy of all the components from another template entity as a "prefab"
407
   * @param templateEntity Entity to use as a template
408
   * @param force Force component replacement if it already exists on the target entity
409
   */
410
  public addTemplate(templateEntity: Entity, force: boolean = false): Entity {
3✔
411
    for (const c of templateEntity.getComponents()) {
3✔
412
      this.addComponent(c.clone(), force);
5✔
413
    }
414
    for (const child of templateEntity.children) {
3✔
415
      this.addChild(child.clone().addTemplate(child));
2✔
416
    }
417
    return this;
3✔
418
  }
419

420
  private _getClassHierarchyRoot(componentType: ComponentCtor): ComponentCtor {
421
    let current = componentType;
25,934✔
422
    let parent = Object.getPrototypeOf(current.prototype)?.constructor;
25,934!
423

424
    while (parent && parent !== Object && parent !== Component) {
25,934✔
425
      current = parent;
2✔
426
      parent = Object.getPrototypeOf(current.prototype)?.constructor;
2!
427
    }
428
    return current;
25,934✔
429
  }
430

431
  /**
432
   * Adds a component to the entity
433
   * @param component Component or Entity to add copy of components from
434
   * @param force Optionally overwrite any existing components of the same type
435
   */
436
  public addComponent<TComponent extends Component>(component: TComponent, force: boolean = false): Entity<TKnownComponents | TComponent> {
29,524✔
437
    // if component already exists, skip if not forced
438
    if (this.has(component.constructor as ComponentCtor)) {
29,549✔
439
      if (force) {
3,658✔
440
        // Remove existing component type if exists when forced
441
        this.removeComponent(component.constructor as ComponentCtor, true);
7✔
442
      } else {
443
        // early exit component exits
444
        return this as Entity<TKnownComponents | TComponent>;
3,651✔
445
      }
446
    }
447

448
    // TODO circular dependencies will be a problem
449
    if (component.dependencies && component.dependencies.length) {
25,898✔
450
      for (const ctor of component.dependencies) {
1,833✔
451
        this.addComponent(new ctor());
3,665✔
452
      }
453
    }
454

455
    component.owner = this;
25,898✔
456
    const rootComponent = this._getClassHierarchyRoot(component.constructor as ComponentCtor);
25,898✔
457
    this.components.set(rootComponent, component);
25,898✔
458
    this.components.set(component.constructor, component);
25,898✔
459
    this.componentValues.push(component);
25,898✔
460
    if (component.onAdd) {
25,898✔
461
      component.onAdd(this);
10,170✔
462
    }
463

464
    this.componentAdded$.notifyAll(component);
25,898✔
465
    return this as Entity<TKnownComponents | TComponent>;
25,898✔
466
  }
467

468
  /**
469
   * Removes a component from the entity, by default removals are deferred to the end of entity update to avoid consistency issues
470
   *
471
   * Components can be force removed with the `force` flag, the removal is not deferred and happens immediately
472
   * @param typeOrInstance
473
   * @param force
474
   */
475
  public removeComponent<TComponent extends Component>(
476
    typeOrInstance: ComponentCtor<TComponent> | TComponent,
477
    force = false
19✔
478
  ): Entity<Exclude<TKnownComponents, TComponent>> {
479
    let type: ComponentCtor<TComponent>;
480
    if (isComponentCtor(typeOrInstance)) {
55✔
481
      type = typeOrInstance;
54✔
482
    } else {
483
      type = typeOrInstance.constructor as ComponentCtor<TComponent>;
1✔
484
    }
485

486
    if (force) {
55✔
487
      const componentToRemove = this.components.get(type);
36✔
488
      if (componentToRemove) {
36!
489
        this.componentRemoved$.notifyAll(componentToRemove);
36✔
490
        componentToRemove.owner = undefined;
36✔
491
        if (componentToRemove.onRemove) {
36✔
492
          componentToRemove.onRemove(this);
8✔
493
        }
494
        const componentIndex = this.componentValues.indexOf(componentToRemove);
36✔
495
        if (componentIndex > -1) {
36!
496
          this.componentValues.splice(componentIndex, 1);
36✔
497
        }
498
      }
499

500
      const rootComponent = this._getClassHierarchyRoot(type);
36✔
501
      this.components.delete(rootComponent);
36✔
502
      this.components.delete(type); // remove after the notify to preserve typing
36✔
503
    } else {
504
      this._componentsToRemove.push(type);
19✔
505
    }
506

507
    return this as any;
55✔
508
  }
509

510
  public clearComponents() {
511
    const components = this.types;
2✔
512
    for (const c of components) {
2✔
513
      this.removeComponent(c);
14✔
514
    }
515
  }
516

517
  /**
518
   * @hidden
519
   * @internal
520
   */
521
  public processComponentRemoval() {
522
    for (const type of this._componentsToRemove) {
5,622✔
523
      this.removeComponent(type, true);
18✔
524
    }
525
    this._componentsToRemove.length = 0;
5,622✔
526
  }
527

528
  /**
529
   * Check if a component type exists
530
   * @param type
531
   */
532
  public has<TComponent extends Component>(type: ComponentCtor<TComponent>): boolean {
533
    return this.components.has(type);
52,861✔
534
  }
535

536
  private _isInitialized = false;
7,553✔
537
  private _isAdded = false;
7,553✔
538

539
  /**
540
   * Gets whether the actor is Initialized
541
   */
542
  public get isInitialized(): boolean {
543
    return this._isInitialized;
3,391✔
544
  }
545

546
  public get isAdded(): boolean {
547
    return this._isAdded;
6,186✔
548
  }
549

550
  /**
551
   * Initializes this entity, meant to be called by the Scene before first update not by users of Excalibur.
552
   *
553
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
554
   * @internal
555
   */
556
  public _initialize(engine: Engine) {
557
    if (!this.isInitialized) {
3,350✔
558
      this.onInitialize(engine);
1,309✔
559
      this.events.emit('initialize', new InitializeEvent(engine, this));
1,309✔
560
      this._isInitialized = true;
1,309✔
561
    }
562
  }
563

564
  /**
565
   * Adds this Actor, meant to be called by the Scene when Actor is added.
566
   *
567
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
568
   * @internal
569
   */
570
  public _add(engine: Engine) {
571
    if (!this.isAdded && this.isActive) {
3,093✔
572
      this.onAdd(engine);
1,271✔
573
      this.events.emit('add', new AddEvent(engine, this));
1,271✔
574
      this._isAdded = true;
1,271✔
575
    }
576
  }
577

578
  /**
579
   * Removes Actor, meant to be called by the Scene when Actor is added.
580
   *
581
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
582
   * @internal
583
   */
584
  public _remove(engine: Engine) {
585
    if (this.isAdded && !this.isActive) {
3,093✔
586
      this.onRemove(engine);
5✔
587
      this.events.emit('remove', new RemoveEvent(engine, this));
5✔
588
      this._isAdded = false;
5✔
589
    }
590
  }
591

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

603
  /**
604
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
605
   *
606
   * Internal _preupdate handler for {@apilink onPostUpdate} lifecycle event
607
   * @internal
608
   */
609
  public _postupdate(engine: Engine, elapsed: number): void {
610
    this.events.emit('postupdate', new PostUpdateEvent(engine, elapsed, this));
920✔
611
    this.onPostUpdate(engine, elapsed);
920✔
612
  }
613

614
  /**
615
   * `onInitialize` is called before the first update of the entity. This method is meant to be
616
   * overridden.
617
   *
618
   * Synonymous with the event handler `.on('initialize', (evt) => {...})`
619
   */
620
  public onInitialize(engine: Engine): void {
621
    // Override me
622
  }
623

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

634
  /**
635
   * `onRemove` is called when Actor is added to scene. This method is meant to be
636
   * overridden.
637
   *
638
   * Synonymous with the event handler `.on('remove', (evt) => {...})`
639
   */
640
  public onRemove(engine: Engine): void {
641
    // Override me
642
  }
643

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

653
  /**
654
   * Safe to override onPostUpdate lifecycle event handler. Synonymous with `.on('postupdate', (evt) =>{...})`
655
   *
656
   * `onPostUpdate` is called directly after an entity is updated.
657
   */
658
  public onPostUpdate(engine: Engine, elapsed: number): void {
659
    // Override me
660
  }
661

662
  /**
663
   *
664
   * Entity update lifecycle, called internally
665
   * @internal
666
   * @param engine
667
   * @param elapsed
668
   */
669
  public update(engine: Engine, elapsed: number): void {
670
    this._initialize(engine);
918✔
671
    this._add(engine);
918✔
672
    this._preupdate(engine, elapsed);
918✔
673
    for (const child of this.children) {
918✔
674
      child.update(engine, elapsed);
×
675
    }
676
    this._postupdate(engine, elapsed);
918✔
677
    this._remove(engine);
918✔
678
  }
679

680
  public emit<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, event: EntityEvents[TEventName]): void;
681
  public emit(eventName: string, event?: any): void;
682
  public emit<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, event?: any): void {
683
    this.events.emit(eventName, event);
8✔
684
  }
685

686
  public on<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, handler: Handler<EntityEvents[TEventName]>): Subscription;
687
  public on(eventName: string, handler: Handler<unknown>): Subscription;
688
  public on<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
689
    return this.events.on(eventName, handler);
9✔
690
  }
691

692
  public once<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, handler: Handler<EntityEvents[TEventName]>): Subscription;
693
  public once(eventName: string, handler: Handler<unknown>): Subscription;
694
  public once<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
695
    return this.events.once(eventName, handler);
×
696
  }
697

698
  public off<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, handler: Handler<EntityEvents[TEventName]>): void;
699
  public off(eventName: string, handler: Handler<unknown>): void;
700
  public off(eventName: string): void;
701
  public off<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, handler?: Handler<any>): void {
702
    if (handler) {
×
703
      this.events.off(eventName, handler);
×
704
    } else {
705
      this.events.off(eventName);
×
706
    }
707
  }
708
}
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