Skip to content
MatrixAIPublic

About

Events for push-flow abstractions

Topics

Resources

Stars

0 stars

Watchers

3 watching

Forks

Repository files navigation

js-events

Events for push-flow abstractions.

AbstractEvent

// For when you just want a regular event without `detail`
// Note that the `detail` type is `undefined`, unlike `CustomEvent`
class Event1 extends AbstractEvent {}

// For when you want a event with `detail`
class Event2 extends AbstractEvent<string> {}

// Allow caller to customise the `detail` type
// Note that the `detail` type is `unknown`
// This would be rare to use, prefer `Event4`
class Event3<T> extends AbstractEvent<T> {}

// Allow caller to customise the `detail` type
class Event4<T extends Event = Event> extends AbstractEvent<T> {}

// When you need to customise the constructor signature
class Event5 extends AbstractEvent<string> {
  constructor(options: EventInit & { detail: string }) {
    // Make sure you pass `arguments`!
    super(Event5.name, options, arguments);
  }
}

When redispatching an event, you must call event.clone(). The same instance cannot be redispatched. When the event is cloned, all constructor parameters are shallow-copied.

Evented

We combine Evented with AbstractEvent to gain type-safety and convenience of the wildcard any handler.

class EventCustom extends AbstractEvent {}

interface X extends Evented {}
@Evented()
class X {}

const x = new X();

// Handle specific event, use the `name` property as the key
x.addEventListener(EventCustom.name, (e) => {
  console.log(e as EventCustom);
});

// Handle unhandled events
x.addEventListener(EventDefault.name, (e) => {
  // This is the wrapped underlying event
  console.log((e as EventDefault).detail);
});

// Handle all events
x.addEventListener(EventAll.name, (e) => {
  // This is the wrapped underlying event
  console.log((e as EventAll).detail);
});

You can use this style to handle relevant events to perform side-effects, as well as propagate upwards irrelevant events.

Note that some side-effects you perform may trigger an infinite loop by causing something to emit the specific event type that you are handling. In these cases you should specialise handling of those events with a once: true option, so that they are only handled once.

x.addEventListener(
  EventInfinite.name,
  (e) => {
    console.log(e as EventInfinite);
    performActionThatMayTriggerEventInfinite();
  },
  { once: true },
);

This will terminate the infinite loop on the first time it gets handled.

Therefore it is a good idea to always be as specific with your event types as possible.

Furthermore any unhandled rejections or uncaught exceptions will be redispatched as EventError. However if there's no listener registered for this, it will be thrown up as an uncaught exception.

Installation and package boundary

Require Node.js 24 or newer.

npm install --save @matrixai/events

Development

Require Node.js 24 or newer. The package emits ES2022 ESM and declarations using NodeNext resolution. Use standard decorators, without legacy decorator metadata. The root export, .js subpaths, wildcard passthrough, and package metadata export remain compatible; #* is internal to build-first tests, not a consumer API. The package has no runtime dependencies. Published files are allowlisted in the manifest.

import { AbstractEvent, Evented } from '@matrixai/events';
import EventAll from '@matrixai/events/EventAll.js';

The private standards submodule and library-js profile are governed by AGENTS. Initialize standards with authorized SSH access:

git submodule update --init --depth 1 -- .matrixai/matrixai-standards
nix run './.matrixai/matrixai-standards#skills-materializer' -- --standards-root ./.matrixai/matrixai-standards

Use direnv allow after reviewing .envrc, or enter the same integrated shell manually with nix develop --no-pure-eval. Activation does not fetch secrets, install npm dependencies, or change persistent shell options. Local state in .direnv, .devenv, and tmp is ignored. Once active, run commands directly rather than nesting direnv activation. Devenv is the flake integration compatibility wrapper, not a separate flakeless project. Its 2.4.0 commands are tasks, test, up, and version; inspect devenv --help for the pinned surface.

npm ci --ignore-scripts
npm run lintfix
npm run typecheck
npm test -- --runInBand
npm run check:package
npm run check:portable
npm run check

MatrixAI lint comes from pkgs.matrixai-lint in the selected public producer, with explicit Nix and shell backends. nix fmt uses that same lint authority. There is no npm lint dependency or separate lint flake input. Lint scripts need the development shell's tools; hermetic checks provide their own environment.

Tests build first and use Jest/SWC standard decorators with a 20-second default timeout. JUnit is written to tmp/junit/junit.xml. Package checks unpack a real archive, test ESM root/subpath/metadata consumers, validate declarations, and verify the canonical manifest and file allowlist. Static portability checks reject non-relative emitted imports. Optional npm run check:browser requires Firefox on PATH (or EVENTS_BROWSER pointing to Firefox) and runs an isolated headless import smoke. It is not full browser conformance.

Hermetic checks and prepared archives

The flake exposes independent verify, lint, and default checks on x86_64-linux. They do not depend on direnv, devenv, installed local npm packages, or live developer state. Reports and reusable artifacts are in each check output. Before Git-backed Nix evaluation, stage exact new input files; tracked edits and deletions are already visible.

nix flake check --no-write-lock-file -L
nix build '.#packages.x86_64-linux.npm-package' --no-link --print-out-paths
nix build '.#packages.x86_64-linux.npm-package-unpacked' --no-link --print-out-paths
nix eval '.#packages.x86_64-linux.npm-package.npmPackage' --json

npm-package is a prepared .tgz with compiled code and declarations, not a Git source archive requiring a consumer-side build. Its manifest is unchanged. The descriptor records npm dependency and source provenance metadata; npmDistribution identifies the prepared archive and has no runtime providers.

Documentation is handwritten Markdown. The local override retires TypeDoc, generated docs, ignore-based npm packaging, and automatic publication workflows. Releases are Git-based and require deliberate review. Publishing, tagging, and version changes are not automatic consequences of a tooling commit.

About

Events for push-flow abstractions

Topics

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Used by

Contributors

Languages