npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

eslint-plugin-minecraft-isvalid

v0.1.0

Published

Type-aware ESLint rules that enforce isValid() checks on @minecraft/server references after invalidation points.

Readme

eslint-plugin-minecraft-isvalid

Type-aware ESLint rules that enforce isValid checks on @minecraft/server references after invalidation points — and flag the redundant checks you no longer need.

What this protects you against

In the Bedrock Script API, almost everything you hold onto is a native handleEntity, Player, Block, Container, ContainerSlot, components, ScreenDisplay, ScoreboardObjective, and more. These handles can become invalid at any time: an entity unloads or dies, a block is broken or its chunk unloads, a player disconnects. Once a handle is invalid, reading or calling almost any member throws (InvalidEntityError, etc.).

The danger is that the handle looks fine in your code — the crash only happens at runtime, usually a tick or more later, after an await or inside a deferred callback where the world has moved on:

// 💥 Crashes if the player logged off during the await
async function teleportSoon(player: Player) {
  await system.waitTicks(40);
  player.teleport(target); // player may be invalid now
}

// 💥 Crashes if the entity unloaded before this callback runs
function blowUpLater(entity: Entity) {
  system.runTimeout(() => {
    entity.applyDamage(100); // entity may be invalid now
  }, 100);
}

These bugs are easy to write, hard to spot in review, and show up as hard-to-reproduce production errors. require-is-valid-check catches them at lint time and tells you exactly where a check is missing:

async function teleportSoon(player: Player) {
  await system.waitTicks(40);
  if (!player.isValid) return; // ✅ re-validate after the await
  player.teleport(target);
}

Once you have checked (or the API guarantees a live handle), no-unnecessary-is-valid-check flags the leftover checks so they don't pile up as noise.

The rules are type-aware: they use TypeScript type information — plus an optional Valid* type-branding convention (see below) — to track exactly which references are known-valid at each point, so they stay quiet on safe code and only fire where a real invalidation boundary was crossed.

Rules

| Rule | Description | Recommended | | ------------------------------------------------------------------------------ | --------------------------------------------------------------------- | ----------- | | require-is-valid-check | Ensure references are validated before use after invalidation points. | error | | no-unnecessary-is-valid-check | Warn when checking isValid on references already known to be valid. | warn |

Installation

npm install --save-dev eslint-plugin-minecraft-isvalid

Both rules require typed linting, so you also need typescript and @typescript-eslint/parser.

Usage (flat config)

// eslint.config.mjs
import tseslint from 'typescript-eslint';
import minecraftIsValid from 'eslint-plugin-minecraft-isvalid';

export default tseslint.config({
  files: ['src/**/*.ts'],
  languageOptions: {
    parser: tseslint.parser,
    parserOptions: { projectService: true },
  },
  plugins: { 'minecraft-isvalid': minecraftIsValid },
  rules: {
    'minecraft-isvalid/require-is-valid-check': 'error',
    'minecraft-isvalid/no-unnecessary-is-valid-check': 'warn',
  },
});

Or extend the bundled recommended config:

import minecraftIsValid from 'eslint-plugin-minecraft-isvalid';

export default [
  // ...your type-aware language options...
  minecraftIsValid.configs.recommended,
];

The Valid* branding convention

The rules get most of their precision from a structural brand that marks a reference as "already validated" — e.g. ValidPlayer = Player & { readonly __validMinecraftReferenceBrand?: true }. APIs that always return live handles (world.getAllPlayers(), event payloads, …) are typed to return the branded variant.

This package ships that augmentation. Activate it once anywhere in your project:

/// <reference types="eslint-plugin-minecraft-isvalid/valid-types" />

You can still mix in your own project-specific @minecraft/server augmentations; they merge with the shipped one.

Without the branding, the rules still work — they simply treat every tracked reference as "plain" and require a check before each use.

Use Valid* types in your APIs

The Valid* helpers are not just an internal implementation detail. They are one of the best ways to make your own code communicate its safety contract.

If a function requires a handle that has already been checked, type the parameter as the branded version:

import type { ValidEntity, ValidPlayer } from '@minecraft/server';

function awardKillCredit(player: ValidPlayer, defeated: ValidEntity) {
  // No extra isValid check needed here: the function contract says both handles
  // must be live when this synchronous helper is called.
  player.sendMessage(`Defeated ${defeated.typeId}`);
}

Callers can satisfy that contract by checking isValid first:

import type { Entity, Player } from '@minecraft/server';

function maybeAwardKillCredit(player: Player, defeated: Entity) {
  if (!player.isValid || !defeated.isValid) return;
  awardKillCredit(player, defeated);
}

This pattern is especially useful for shared helpers, form/menu openers, model methods, event utilities, and any API where accepting a plain Player/Entity would force every callee to repeat the same guard. Use the most specific branded type that matches the value you expect: ValidPlayer, ValidEntity, ValidBlock, ValidContainer, ValidContainerSlot, ValidEntityComponent, and so on.

The brand means "known valid at this point in synchronous execution." If the function itself crosses an invalidation boundary, re-check before using the handle again:

async function rewardAfterDelay(player: ValidPlayer) {
  await system.waitTicks(20);
  if (!player.isValid) return; // required again after the await
  player.sendMessage('Reward granted!');
}

Options

See each rule's documentation for the full schema. require-is-valid-check accepts:

  • trackedTypes — the @minecraft/server type names to enforce. The defaults cover every exported handle that has an isValid member, and matching is inheritance-aware (e.g. Entity also covers Player; Component covers every concrete component).
  • trackedModules — the modules tracked types must originate from (default: @minecraft/server).
  • safeProperties — a per-type map of members that stay readable even on an invalidated handle. This is deliberately type-scoped: only Entity.id and Entity.typeId are documented as safe by the API, so the default is { "Entity": ["id", "typeId"] } (inherited by Player/SimulatedPlayer). A property like typeId is not assumed safe on other handles such as Block or ContainerSlot.

Development

npm install
npm run build      # compile to dist/
npm test           # run the type-aware rule tests
npm run lint       # lint the plugin source
npm run typecheck  # type-check without emitting

See AGENTS.md for the architecture and contribution workflow.

License

MIT