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

excaliburjs / Excalibur / 20722503662

05 Jan 2026 04:48PM UTC coverage: 88.806% (+0.06%) from 88.745%
20722503662

Pull #3635

github

web-flow
Merge 40c841144 into 2c7b0c4fa
Pull Request #3635: remove!: deprecations for v1

5333 of 7246 branches covered (73.6%)

24 of 25 new or added lines in 9 files covered. (96.0%)

6 existing lines in 3 files now uncovered.

14653 of 16500 relevant lines covered (88.81%)

24848.14 hits per line

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

88.32
/src/engine/entity-component-system/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/lifecycle-events';
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 '../event-emitter';
11
import { EventEmitter } from '../event-emitter';
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 interface 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 = {
244✔
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 {
244✔
94
  private static _ID = 0;
95
  /**
96
   * The unique identifier for the entity
97
   */
98
  public id: number = Entity._ID++;
7,547✔
99

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

102
  /**
103
   * Listen to or emit events for an entity
104
   */
105
  public events = new EventEmitter<EntityEvents>();
7,547✔
106
  private _tags = new Set<string>();
7,547✔
107
  public componentAdded$ = new Observable<Component>();
7,547✔
108
  public componentRemoved$ = new Observable<Component>();
7,547✔
109
  public tagAdded$ = new Observable<string>();
7,547✔
110
  public tagRemoved$ = new Observable<string>();
7,547✔
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,547✔
119
  public componentValues: Component[] = [];
7,547✔
120
  public _componentsToRemove: ComponentCtor[] = [];
7,547✔
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,547✔
129
      componentsToAdd = componentsOrOptions;
2,934✔
130
      nameToAdd = name;
2,934✔
131
    } else if (componentsOrOptions && typeof componentsOrOptions === 'object') {
4,613!
132
      const { components, name } = componentsOrOptions;
×
133
      componentsToAdd = components ?? [];
×
134
      nameToAdd = name;
×
135
    }
136
    if (nameToAdd) {
7,547✔
137
      this.name = nameToAdd;
10✔
138
    }
139
    if (componentsToAdd) {
7,547✔
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,547✔
150

151
  /**
152
   * Whether this entity is active, if set to false it will be reclaimed
153
   */
154
  public isActive: boolean = true;
7,547✔
155

156
  /**
157
   * Kill the entity, means it will no longer be updated. Kills are deferred to the end of the update.
158
   * If parented it will be removed from the parent when killed.
159
   */
160
  public kill() {
161
    if (this.isActive) {
31!
162
      this.isActive = false;
31✔
163
      this.unparent();
31✔
164
    }
165
    this.emit('kill', new KillEvent(this));
31✔
166
  }
167

168
  public isKilled() {
169
    return !this.isActive;
2✔
170
  }
171

172
  /**
173
   * Specifically get the tags on the entity from {@apilink TagsComponent}
174
   */
175
  public get tags(): Set<string> {
176
    return this._tags;
4✔
177
  }
178

179
  /**
180
   * Check if a tag exists on the entity
181
   * @param tag name to check for
182
   */
183
  public hasTag(tag: string): boolean {
184
    return this._tags.has(tag);
7,003✔
185
  }
186

187
  /**
188
   * Adds a tag to an entity
189
   * @param tag
190
   */
191
  public addTag(tag: string): Entity<TKnownComponents> {
192
    this._tags.add(tag);
53✔
193
    this.tagAdded$.notifyAll(tag);
53✔
194
    return this;
53✔
195
  }
196

197
  /**
198
   * Removes a tag on the entity
199
   *
200
   * Removals are deferred until the end of update
201
   * @param tag
202
   */
203
  public removeTag(tag: string): Entity<TKnownComponents> {
204
    this._tags.delete(tag);
8✔
205
    this.tagRemoved$.notifyAll(tag);
8✔
206
    return this;
8✔
207
  }
208

209
  /**
210
   * The types of the components on the Entity
211
   */
212
  public get types(): ComponentCtor[] {
213
    return Array.from(this.components.keys()) as ComponentCtor[];
24✔
214
  }
215

216
  /**
217
   * Returns all component instances on entity
218
   */
219
  public getComponents(): Component[] {
220
    return Array.from(this.components.values());
8✔
221
  }
222

223
  /**
224
   * Verifies that an entity has all the required types
225
   * @param requiredTypes
226
   */
227
  hasAll<TComponent extends Component>(requiredTypes: ComponentCtor<TComponent>[]): boolean {
228
    for (let i = 0; i < requiredTypes.length; i++) {
×
229
      if (!this.components.has(requiredTypes[i])) {
×
230
        return false;
×
231
      }
232
    }
233
    return true;
×
234
  }
235

236
  /**
237
   * Verifies that an entity has all the required tags
238
   * @param requiredTags
239
   */
240
  hasAllTags(requiredTags: string[]): boolean {
UNCOV
241
    for (let i = 0; i < requiredTags.length; i++) {
×
UNCOV
242
      if (!this.tags.has(requiredTags[i])) {
×
UNCOV
243
        return false;
×
244
      }
245
    }
UNCOV
246
    return true;
×
247
  }
248

249
  get<TComponent extends Component>(type: ComponentCtor<TComponent>): MaybeKnownComponent<TComponent, TKnownComponents> {
250
    return this.components.get(type) as MaybeKnownComponent<TComponent, TKnownComponents>;
69,075✔
251
  }
252

253
  private _parent: Entity | null = null;
7,547✔
254
  public get parent(): Entity | null {
255
    return this._parent;
15,224✔
256
  }
257

258
  public childrenAdded$ = new Observable<Entity>();
7,547✔
259
  public childrenRemoved$ = new Observable<Entity>();
7,547✔
260

261
  private _children: Entity[] = [];
7,547✔
262
  /**
263
   * Get the direct children of this entity
264
   */
265
  public get children(): readonly Entity[] {
266
    return this._children;
20,034✔
267
  }
268

269
  /**
270
   * Unparents this entity, if there is a parent. Otherwise it does nothing.
271
   */
272
  public unparent() {
273
    if (this._parent) {
136✔
274
      this._parent.removeChild(this);
7✔
275
      this._parent = null;
7✔
276
    }
277
  }
278

279
  /**
280
   * Check if a child entity exists on the parent entity
281
   * @param child entity to check for
282
   * @param recursive whether to check recursively
283
   */
284
  public hasChild(child: Entity, recursive = false): boolean {
2✔
285
    if (!recursive) {
7✔
286
      return child.parent === this;
4✔
287
    }
288

289
    for (const c of this.children) {
3✔
290
      if (c === child) {
3✔
291
        return true;
2✔
292
      }
293
      if (recursive && c.hasChild(child, true)) {
1!
294
        return true;
1✔
295
      }
296
    }
297

298
    return false;
×
299
  }
300

301
  /**
302
   * Adds an entity to be a child of this entity
303
   * @param entity
304
   */
305
  public addChild(entity: Entity): Entity {
306
    if (entity.parent === null) {
2,971✔
307
      if (this.getAncestors().includes(entity)) {
2,970✔
308
        throw new Error('Cycle detected, cannot add entity');
1✔
309
      }
310
      this._children.push(entity);
2,969✔
311
      entity._parent = this;
2,969✔
312
      this.childrenAdded$.notifyAll(entity);
2,969✔
313
    } else {
314
      throw new Error('Entity already has a parent, cannot add without unparenting');
1✔
315
    }
316
    return this;
2,969✔
317
  }
318

319
  /**
320
   * Remove an entity from children if it exists
321
   * @param entity
322
   */
323
  public removeChild(entity: Entity): Entity {
324
    if (entity.parent === this) {
22!
325
      removeItemFromArray(entity, this._children);
22✔
326
      entity._parent = null;
22✔
327
      this.childrenRemoved$.notifyAll(entity);
22✔
328
    }
329
    return this;
22✔
330
  }
331

332
  /**
333
   * Removes all children from this entity
334
   */
335
  public removeAllChildren(): Entity {
336
    // Avoid modifying the array issue by walking backwards
337
    for (let i = this.children.length - 1; i >= 0; i--) {
1✔
338
      this.removeChild(this.children[i]);
6✔
339
    }
340
    return this;
1✔
341
  }
342

343
  /**
344
   * Returns a list of parent entities starting with the topmost parent. Includes the current entity.
345
   */
346
  public getAncestors(): Entity[] {
347
    const result: Entity[] = [this];
7,804✔
348
    let current = this.parent;
7,804✔
349
    while (current) {
7,804✔
350
      result.push(current);
2,053✔
351
      current = current.parent;
2,053✔
352
    }
353
    return result.reverse();
7,804✔
354
  }
355

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

372
  /**
373
   * Creates a deep copy of the entity and a copy of all its components
374
   */
375
  public clone(): Entity {
376
    const newEntity = new Entity();
10✔
377
    for (const c of this.types) {
10✔
378
      const componentInstance = this.get(c);
14✔
379
      if (componentInstance) {
14!
380
        newEntity.addComponent(componentInstance.clone());
14✔
381
      }
382
    }
383
    for (const child of this.children) {
10✔
384
      newEntity.addChild(child.clone());
2✔
385
    }
386
    return newEntity;
10✔
387
  }
388

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

404
  private _getClassHierarchyRoot(componentType: ComponentCtor): ComponentCtor {
405
    let current = componentType;
25,941✔
406
    let parent = Object.getPrototypeOf(current.prototype)?.constructor;
25,941!
407

408
    while (parent && parent !== Object && parent !== Component) {
25,941✔
409
      current = parent;
2✔
410
      parent = Object.getPrototypeOf(current.prototype)?.constructor;
2!
411
    }
412
    return current;
25,941✔
413
  }
414

415
  /**
416
   * Adds a component to the entity
417
   * @param component Component or Entity to add copy of components from
418
   * @param force Optionally overwrite any existing components of the same type
419
   */
420
  public addComponent<TComponent extends Component>(component: TComponent, force: boolean = false): Entity<TKnownComponents | TComponent> {
29,535✔
421
    // if component already exists, skip if not forced
422
    if (this.has(component.constructor as ComponentCtor)) {
29,560✔
423
      if (force) {
3,662✔
424
        // Remove existing component type if exists when forced
425
        this.removeComponent(component.constructor as ComponentCtor, true);
7✔
426
      } else {
427
        // early exit component exits
428
        return this as Entity<TKnownComponents | TComponent>;
3,655✔
429
      }
430
    }
431

432
    // TODO circular dependencies will be a problem
433
    if (component.dependencies && component.dependencies.length) {
25,905✔
434
      for (const ctor of component.dependencies) {
1,835✔
435
        this.addComponent(new ctor());
3,669✔
436
      }
437
    }
438

439
    component.owner = this;
25,905✔
440
    const rootComponent = this._getClassHierarchyRoot(component.constructor as ComponentCtor);
25,905✔
441
    this.components.set(rootComponent, component);
25,905✔
442
    this.components.set(component.constructor, component);
25,905✔
443
    this.componentValues.push(component);
25,905✔
444
    if (component.onAdd) {
25,905✔
445
      component.onAdd(this);
10,174✔
446
    }
447

448
    this.componentAdded$.notifyAll(component);
25,905✔
449
    return this as Entity<TKnownComponents | TComponent>;
25,905✔
450
  }
451

452
  /**
453
   * Removes a component from the entity, by default removals are deferred to the end of entity update to avoid consistency issues
454
   *
455
   * Components can be force removed with the `force` flag, the removal is not deferred and happens immediately
456
   * @param typeOrInstance
457
   * @param force
458
   */
459
  public removeComponent<TComponent extends Component>(
460
    typeOrInstance: ComponentCtor<TComponent> | TComponent,
461
    force = false
19✔
462
  ): Entity<Exclude<TKnownComponents, TComponent>> {
463
    let type: ComponentCtor<TComponent>;
464
    if (isComponentCtor(typeOrInstance)) {
55✔
465
      type = typeOrInstance;
54✔
466
    } else {
467
      type = typeOrInstance.constructor as ComponentCtor<TComponent>;
1✔
468
    }
469

470
    if (force) {
55✔
471
      const componentToRemove = this.components.get(type);
36✔
472
      if (componentToRemove) {
36!
473
        this.componentRemoved$.notifyAll(componentToRemove);
36✔
474
        componentToRemove.owner = undefined;
36✔
475
        if (componentToRemove.onRemove) {
36✔
476
          componentToRemove.onRemove(this);
8✔
477
        }
478
        const componentIndex = this.componentValues.indexOf(componentToRemove);
36✔
479
        if (componentIndex > -1) {
36!
480
          this.componentValues.splice(componentIndex, 1);
36✔
481
        }
482
      }
483

484
      const rootComponent = this._getClassHierarchyRoot(type);
36✔
485
      this.components.delete(rootComponent);
36✔
486
      this.components.delete(type); // remove after the notify to preserve typing
36✔
487
    } else {
488
      this._componentsToRemove.push(type);
19✔
489
    }
490

491
    return this as any;
55✔
492
  }
493

494
  public clearComponents() {
495
    const components = this.types;
2✔
496
    for (const c of components) {
2✔
497
      this.removeComponent(c);
14✔
498
    }
499
  }
500

501
  /**
502
   * @hidden
503
   * @internal
504
   */
505
  public processComponentRemoval() {
506
    for (const type of this._componentsToRemove) {
5,629✔
507
      this.removeComponent(type, true);
18✔
508
    }
509
    this._componentsToRemove.length = 0;
5,629✔
510
  }
511

512
  /**
513
   * Check if a component type exists
514
   * @param type
515
   */
516
  public has<TComponent extends Component>(type: ComponentCtor<TComponent>): boolean {
517
    return this.components.has(type);
54,825✔
518
  }
519

520
  private _isInitialized = false;
7,547✔
521
  private _isAdded = false;
7,547✔
522

523
  /**
524
   * Gets whether the actor is Initialized
525
   */
526
  public get isInitialized(): boolean {
527
    return this._isInitialized;
3,399✔
528
  }
529

530
  public get isAdded(): boolean {
531
    return this._isAdded;
6,251✔
532
  }
533

534
  /**
535
   * Initializes this entity, meant to be called by the Scene before first update not by users of Excalibur.
536
   *
537
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
538
   * @internal
539
   */
540
  public _initialize(engine: Engine) {
541
    if (!this.isInitialized) {
3,358✔
542
      this.onInitialize(engine);
1,311✔
543
      this.events.emit('initialize', new InitializeEvent(engine, this));
1,311✔
544
      this._isInitialized = true;
1,311✔
545
    }
546
  }
547

548
  /**
549
   * Adds this Actor, meant to be called by the Scene when Actor is added.
550
   *
551
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
552
   * @internal
553
   */
554
  public _add(engine: Engine) {
555
    if (!this.isAdded && this.isActive) {
3,100✔
556
      this.onAdd(engine);
1,273✔
557
      this.events.emit('add', new AddEvent(engine, this));
1,273✔
558
      this._isAdded = true;
1,273✔
559
    }
560
  }
561

562
  /**
563
   * Removes Actor, meant to be called by the Scene when Actor is removed.
564
   *
565
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
566
   * @internal
567
   */
568
  public _remove(engine: Engine) {
569
    if (this.isAdded && !this.isActive) {
3,151✔
570
      this.onRemove(engine);
12✔
571
      this.events.emit('remove', new RemoveEvent(engine, this));
12✔
572
      this._isAdded = false;
12✔
573
    }
574
  }
575

576
  /**
577
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
578
   *
579
   * Internal _preupdate handler for {@apilink onPreUpdate} lifecycle event
580
   * @internal
581
   */
582
  public _preupdate(engine: Engine, elapsed: number): void {
583
    this.events.emit('preupdate', new PreUpdateEvent(engine, elapsed, this));
921✔
584
    this.onPreUpdate(engine, elapsed);
921✔
585
  }
586

587
  /**
588
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
589
   *
590
   * Internal _preupdate handler for {@apilink onPostUpdate} lifecycle event
591
   * @internal
592
   */
593
  public _postupdate(engine: Engine, elapsed: number): void {
594
    this.events.emit('postupdate', new PostUpdateEvent(engine, elapsed, this));
921✔
595
    this.onPostUpdate(engine, elapsed);
921✔
596
  }
597

598
  /**
599
   * `onInitialize` is called before the first update of the entity. This method is meant to be
600
   * overridden.
601
   *
602
   * Synonymous with the event handler `.on('initialize', (evt) => {...})`
603
   */
604
  public onInitialize(engine: Engine): void {
605
    // Override me
606
  }
607

608
  /**
609
   * `onAdd` is called when Actor is added to scene. This method is meant to be
610
   * overridden.
611
   *
612
   * Synonymous with the event handler `.on('add', (evt) => {...})`
613
   */
614
  public onAdd(engine: Engine): void {
615
    // Override me
616
  }
617

618
  /**
619
   * `onRemove` is called when Actor is removed from a scene. This method is meant to be
620
   * overridden.
621
   *
622
   * Synonymous with the event handler `.on('remove', (evt) => {...})`
623
   */
624
  public onRemove(engine: Engine): void {
625
    // Override me
626
  }
627

628
  /**
629
   * Safe to override onPreUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
630
   *
631
   * `onPreUpdate` is called directly before an entity is updated.
632
   */
633
  public onPreUpdate(engine: Engine, elapsed: number): void {
634
    // Override me
635
  }
636

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

646
  /**
647
   *
648
   * Entity update lifecycle, called internally
649
   * @internal
650
   * @param engine
651
   * @param elapsed
652
   */
653
  public update(engine: Engine, elapsed: number): void {
654
    this._initialize(engine);
919✔
655
    this._add(engine);
919✔
656
    this._preupdate(engine, elapsed);
919✔
657
    for (const child of this.children) {
919✔
658
      child.update(engine, elapsed);
×
659
    }
660
    this._postupdate(engine, elapsed);
919✔
661
    this._remove(engine);
919✔
662
  }
663

664
  public emit<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, event: EntityEvents[TEventName]): void;
665
  public emit(eventName: string, event?: any): void;
666
  public emit<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, event?: any): void {
667
    this.events.emit(eventName, event);
9✔
668
  }
669

670
  public on<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, handler: Handler<EntityEvents[TEventName]>): Subscription;
671
  public on(eventName: string, handler: Handler<unknown>): Subscription;
672
  public on<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
673
    return this.events.on(eventName, handler);
9✔
674
  }
675

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

682
  public off<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, handler: Handler<EntityEvents[TEventName]>): void;
683
  public off(eventName: string, handler: Handler<unknown>): void;
684
  public off(eventName: string): void;
685
  public off<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, handler?: Handler<any>): void {
686
    if (handler) {
×
687
      this.events.off(eventName, handler);
×
688
    } else {
689
      this.events.off(eventName);
×
690
    }
691
  }
692
}
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