domsure

npm version TypeScript Size GitHub

Project Overview

domsure replaces your ! non-null assertions with real runtime checks. Eight DOM query helpers under 1 KB gzipped. ESM and CJS. No dependencies.

Before / After

// Before: the ! hides a missing element. Silent null deref at runtime.
const navbar = document.getElementById("navbar")!;
navbar.classList.add("active");

// After: throws a clear error if the selector is wrong or the element is gone.
const navbar = $.required("#navbar");
navbar.classList.add("active");

API

import { $, $$, defineSelectors, DomsureError } from 'domsure';

$(selector)        // HTMLElement | null  - silent query
$.required(sel)    // HTMLElement        - throw if missing
$.tryRequired(sel) // [DomsureError|null, HTMLElement|null] - required, never throws
$.optional(sel)    // HTMLElement | null - warn once in dev if missing
$.exists(sel)      // boolean            - presence check
$$(selector)       // HTMLElement[]      - querySelectorAll as an array
$$.required(sel)   // HTMLElement[]      - throw if none match
$$.tryRequired(sel)// [DomsureError|null, HTMLElement[]] - required, never throws
$$.optional(sel)   // HTMLElement[]      - warn once in dev if none match
$$.exists(sel)     // boolean            - presence check
defineSelectors(s) // Readonly registry  - frozen, typed selector map
resetWarnings()    // void               - clear the warn-once dedup set

$

Silent single-element query. Simple #id selectors use getElementById. Everything else, including compound selectors like #app .item or #nav.active, falls through to querySelector. Never warns.

const modal = $('#modal');        // HTMLElement | null

$.required

Asserts the element exists. Throws DomsureError if it does not. This is the whole reason the package exists.

const app = $.required('#app');   // HTMLElement, never null

$.tryRequired

Required semantics, but returns a [error, element] tuple instead of throwing. [null, el] on success. [DomsureError, null] on a miss.

Use it where a throw is unrecoverable. The main case is React useEffect and useLayoutEffect. Error boundaries catch render-phase throws, but not effect throws. So a $.required inside an effect bypasses the boundary and crashes the page. $.tryRequired lets the effect degrade instead.

const [err, nav] = $.tryRequired('#navbar');
if (err) { report(err); return; }
nav.classList.add('active');

Unlike $.required, it is safe under SSR. It returns [DomsureError, null] instead of throwing.

$.optional

Like $, but warns once per selector in development when the element is missing. The dedup keeps React and Vue re-renders from flooding the console. No warnings in production.

const tooltip = $.optional('#tooltip');

$.exists

Boolean check. No warnings, no element back.

if ($.exists('#tooltip')) { /* ... */ }

$$ and its parity methods

querySelectorAll returned as a real Array. map, filter, and reduce work directly. $$ mirrors $ with .required, .optional, and .exists for the multi-element case.

const items = $$('.item').map(el => el.textContent);

const required = $$.required('.row');    // throws if zero rows match
const [err, rows] = $$.tryRequired('.row'); // never throws
const maybe    = $$.optional('.row');    // warns once in dev if zero match
if ($$.exists('.row')) { /* ... */ }

defineSelectors

Frozen, typed selector registry. Pass as const for string-literal inference. In dev, it rejects non-string values and duplicate selectors across keys, which is usually a copy-paste typo. Production builds strip the checks out via dead-code elimination.

const S = defineSelectors({
  navbar: '#navbar',
  items: '.item',
} as const);

S.navbar;  // typed as "#navbar", not string

resetWarnings

Clears the warn-once dedup set so $.optional and $$.optional warn again for selectors that already fired one this session. Handy in long-lived SPAs after a route change, when previously missing elements reappear.

resetWarnings();

DomsureError

Every failure throws a DomsureError, not a raw DOMException or a string. Catch it by type instead of regex-matching a message prefix. It carries the offending selector as structured data, so your logs can group by selector without parsing prose.

try {
  $.required('#missing');
} catch (e) {
  if (e instanceof DomsureError) {
    console.error(e.selector, e.message);
  }
}

Browser-only

domsure reads document directly. Under SSR, where document is undefined, $ and $$ throw a DomsureError instead of failing silently. Guard isomorphic code:

if (typeof window !== 'undefined') {
  const el = $.required('#app');
}

Size

raw gzipped
ESM (dist/index.js) ~2.3 KB ~1.0 KB
CJS (dist/index.cjs) ~2.8 KB ~1.24 KB

Zero runtime dependencies.