Core API
The core module provides selectors, DOM manipulation, events, and utilities. The API mirrors jQuery’s ergonomics while staying explicit and debuggable. It is built on three building blocks:
$(selector)— wraps a single element in aBQueryElementand throws if the selector matches nothing. Use it when the absence of the element is a bug.$$(selector)— wraps any number of elements (including zero) in aBQueryCollection. Use it for lists, optional matches, or iteration.- Utilities — pure helpers (
debounce,throttle,merge,uid,template, type guards, …) exposed both as named exports and via theutilsnamespace for IntelliSense-friendly destructuring.
All mutating methods return this so they chain. Read-only getters (text(), attr(name), css(prop), val(), etc.) return the underlying value. HTML insertion methods such as html(), append(), prepend(), before(), and after() sanitize string content by default, while setters like attr(), data(), and css() write directly; the Security guide covers how to opt in to raw HTML when you genuinely need it.
BQueryElementalways wraps exactly one element.BQueryCollectionwraps zero or more — its mutating methods apply to every element, and its getters read from the first element. Convert between them with$$(...).eq(0)orcollection.firstEl(), both of which returnBQueryElement | undefinedwhen the collection is empty.
Selectors
import { $, $$ } from '@bquery/bquery/core';
const button = $('#submit');
const items = $$('.list-item');$() is the strict variant: missing elements raise an Error with the message bQuery: element not found for selector "...", which surfaces typos and DOM-timing bugs at the call site instead of crashing later. $$() is the permissive variant: an empty collection is a valid result, and the chainable API on it becomes a safe no-op ($$('.missing').addClass('x') does nothing rather than throwing).
$ (single element)
- Accepts a selector string or an
Element. - Throws if a selector string matches no element.
const el = $('#app');
const wrap = $(document.body);$$ (collection)
- Accepts a selector string, an array of
Element, orNodeListOf<Element>. - Always returns a
BQueryCollection(empty if no matches).
const list = $$('.item');
const fromArray = $$([document.body]);BQueryElement (single element wrapper)
BQueryElement is the single-element wrapper returned by $(). Every method that changes the element returns this so calls can be chained fluently; every method that reads state returns a primitive (string, number, boolean, DOMRect, …). The underlying DOM node is always available via the .raw (alias .node) getter when you need to drop down to raw browser APIs.
Class & attribute helpers
These helpers mirror standard classList / setAttribute semantics but stay chainable. attr(name) (single argument) returns the current attribute value as a string, using '' when the attribute is missing; calling attr(name, value) sets it. prop() is the equivalent for IDL properties on the wrapped Element; in TypeScript, control-specific properties such as checked or value may require narrowing or casting the element type, or using the underlying .raw node directly. data() reads and writes data-* attributes with automatic key normalization.
addClass(...classNames)removeClass(...classNames)toggleClass(className, force?)hasClass(className)attr(name, value?)removeAttr(name)toggleAttr(name, force?)prop(name, value?)data(name, value?)
Content & HTML
text() reads or writes textContent, so any HTML in the input is rendered as plain text — this is the safest way to display user-supplied data. html(value) runs the input through the default sanitizer (see Security) before assignment, removing scripts and dangerous attributes. htmlUnsafe(value) skips sanitization and is the explicit opt-in for trusted markup. Insertion helpers (append, prepend, before, after) accept either sanitized strings or DOM nodes; passing nodes preserves listeners and references.
text(value?)html(value)– sanitized by defaulthtmlUnsafe(value)– bypasses sanitizationempty()append(content)prepend(content)before(content)after(content)
contentcan be a string (sanitized) orElement/Element[].
Style & visibility
css() is overloaded: with a single property name it acts as a getter that returns the resolved value from getComputedStyle() (always a string); with a property + value (or an object) it acts as a setter that writes to element.style and stays chainable. show() / hide() toggle the inline display style, and show(display) lets you opt into a specific display value (e.g. 'flex') instead of the browser default. toggle() flips visibility, or accepts a boolean force to set it explicitly.
css(property)– getter: returns computed style value viagetComputedStyle()css(property, value)– setter: sets a single CSS propertycss(properties)– setter: sets multiple CSS properties from an objectshow(display?)hide()toggle(force?)
Events (Element)
on() attaches a listener that fires for every dispatched event of that type, once() auto-removes itself after the first invocation, and off() removes a previously attached handler (the exact function reference must match). trigger() synthesises and dispatches a CustomEvent, with the optional detail payload available on event.detail. delegate() is the preferred way to react to events on dynamically inserted descendants: a single listener on the wrapper element matches the inner selector at dispatch time, so newly added children are picked up automatically.
on(event, handler)once(event, handler)off(event, handler)trigger(event, detail?)delegate(event, selector, handler)– event delegation for dynamic content
Event Delegation
Event delegation allows handling events on dynamically added elements:
// Handle clicks on .item elements, even if added later
$('#list').delegate('click', '.item', (event, target) => {
console.log('Clicked:', target.textContent);
});CSS Getter
The css() method works as a getter when called with a single property name:
// Get computed style value
const color = $('#box').css('color');
// Set styles (chainable)
$('#box').css('color', 'red');
$('#box').css({ color: 'red', 'font-size': '16px' });Selector Matching
if ($('#el').is('.active')) {
console.log('Element is active');
}
// Equivalent to
$('#el').matches('.active');Traversal & utilities
Traversal methods follow the DOM tree without mutating the receiver, but their return types mirror the underlying DOM operation. On BQueryElement, find() returns all matching descendants as Element[], findOne() returns the first match as Element | null, and helpers like closest(), parent(), next(), and prev() return raw Element | null values. On BQueryCollection, traversal helpers return new BQueryCollection wrappers so you can keep chaining across multiple matches. closest() walks up the tree (including the element itself when it matches), which is the canonical pattern for resolving the target of a delegate('click', selector, …) callback to a known ancestor.
Layout getters come in two flavours: innerWidth/innerHeight measure the content + padding box (matching clientWidth/clientHeight), while outerWidth/outerHeight measure the border box and accept an optional includeMargin boolean. rect() returns a live DOMRect, offset() returns offsetWidth/offsetHeight plus offsetTop/offsetLeft (relative to the offset parent), and position() returns coordinates relative to the offset parent.
find(selector)findOne(selector)closest(selector)parent()children()siblings()next()prev()matches(selector)is(selector)– alias formatches()clone(deep?)val(newValue?)rect()offset()index()contents()offsetParent()position()innerWidth()– content + padding width (clientWidth)innerHeight()– content + padding height (clientHeight)outerWidth(includeMargin?)– border-box width, optionally with marginsouterHeight(includeMargin?)– border-box height, optionally with marginsfocus()/blur()raw(getter) /node(getter)
DOM Manipulation
wrap(wrapper)– wrap element with new parent (accepts tag name or Element)unwrap()– remove parent, keeping elementreplaceWith(content)– replace element with new contentdetach()– remove element from DOM without discarding the wrapperscrollTo(options?)– scroll element into view
// Wrap element with a div
$('#content').wrap('div');
// Wrap with an existing element
const wrapper = document.createElement('section');
wrapper.className = 'wrapper';
$('#content').wrap(wrapper);
// Unwrap (remove parent)
$('#content').unwrap();
// Replace element
$('#old').replaceWith('<div id="new">New content</div>');
// Detach and reinsert later
const item = $('#item').detach();
document.body.appendChild(item.raw);
// Smooth scroll to element
$('#section').scrollTo({ behavior: 'smooth', block: 'center' });Form Serialization
serialize() collects every named, non-disabled control inside the wrapped <form> into a plain object — input values, selected option(s), checked checkboxes, radio groups, multi-select arrays, and <textarea> content are all supported. serializeString() returns the same data already URL-encoded so it can be appended to a query string or sent as application/x-www-form-urlencoded body. Both helpers respect the standard form rules: unnamed fields, disabled controls, and type="submit" buttons are skipped, and unchecked checkboxes / radios are excluded rather than serialized as false.
serialize()– returns form data as objectserializeString()– returns URL-encoded string
// Get form data as object
const data = $('form').serialize();
// { name: 'John', email: 'john@example.com' }
// Get as query string
const query = $('form').serializeString();
// 'name=John&email=john%40example.com'BQueryCollection (multi-element wrapper)
BQueryCollection is the zero-or-more-element wrapper returned by $$() (and by collection-returning methods like find(), siblings(), children()). It behaves like an array of BQueryElements but flattens common operations: writing through the collection iterates every element, while reading returns the value from the first element only — matching jQuery's "first-wins" convention. Use .each() / .map() / .toArray() when you need per-element access, and .eq(n) to narrow back down to a single-element collection.
All mutating methods are chainable and apply to every element. Getter methods return values from the first element.
Collection helpers
length(getter)eq(index)firstEl()lastEl()each(callback)map(callback)filter(predicate)reduce(callback, initialValue)toArray()find(selector)– query all descendant elements matching a selector across all collection elements (deduplicates shared descendants)
DOM traversal
closest(selector)– closest matching element, including the element itself when it matches (deduplicated)parent()– unique parent elementschildren()– all child elements across the collectionsiblings()– all siblings excluding collection elementsnext()– next sibling of each elementprev()– previous sibling of each element
// Navigate the DOM from a collection
$$('.item').parent().addClass('has-items');
$$('.active').siblings().removeClass('active');
$$('.current').next().addClass('upcoming');// Find all .item descendants across multiple containers
$$('.container').find('.item').addClass('highlight');DOM & class helpers
addClass(...classNames)removeClass(...classNames)toggleClass(className, force?)attr(name, value?)removeAttr(name)toggleAttr(name, force?)text(value?)html(value?)– sanitized by defaulthtmlUnsafe(value)append(content)prepend(content)before(content)after(content)wrap(wrapper)unwrap()replaceWith(content)detach()index()contents()offsetParent()position()innerWidth()– content + padding width (first element)innerHeight()– content + padding height (first element)outerWidth(includeMargin?)– border-box width (first element)outerHeight(includeMargin?)– border-box height (first element)css(property)– getter: returns computed style value (first element)css(property, value)– setter: sets a single CSS propertycss(properties)– setter: sets an object of propertiesshow(display?)hide()remove()empty()
Events
on(event, handler)once(event, handler)off(event, handler)trigger(event, detail?)delegate(event, selector, handler)– event delegation
Utilities
The core module also exposes a large set of pure, tree-shakeable utility functions — covering objects, functions, strings, numbers, arrays, type guards, and miscellaneous helpers (UID, UUID, polling, retry, …). Two access styles are supported and equivalent:
- Named imports (best for tree-shaking with a bundler):
import { debounce, merge, uid } from '@bquery/bquery/core'; - Namespace import (best for editor autocomplete and IntelliSense):
import { utils } from '@bquery/bquery/core';thenutils.debounce(...).
Function wrappers (debounce, throttle, memoize, retry) return a callable with extra control methods such as .cancel(), .flush(), and .clear() — see each entry below for specifics. None of the utilities mutate their arguments, and deep object helpers (get, set, merge, defaults) refuse to traverse prototype keys (__proto__, constructor, prototype), preventing prototype-pollution exploits.
import { debounce, throttle, merge, uid, utils } from '@bquery/bquery/core';
const id = uid();
const merged = merge({ a: 1 }, { b: 2 });
const delayed = debounce(() => console.log('Saved'), 200);
delayed.cancel(); // Cancel pending invocation
const scrollHandler = throttle(() => console.log('Scroll'), 100);
scrollHandler.cancel(); // Reset throttle, next call executes immediately
const legacyId = utils.uid();utils is also explicitly typed as BQueryUtils, which makes namespace-style access play nicely with editor IntelliSense when you prefer utils.debounce(...) over named imports.
import { utils, type BQueryUtils } from '@bquery/bquery/core';
const typedUtils: BQueryUtils = utils;
const later = typedUtils.debounce(() => console.log('Saved'), 200);
later.cancel();Utility list
Object
clone(value)merge(...sources)pick(obj, keys)omit(obj, keys)hasOwn(obj, key)isPlainObject(value)get(obj, path, default?)/set(obj, path, value)/has(obj, path)– prototype-pollution-safe deep accessors ('a.b.c'or'list[0].name')mapValues(obj, fn)/mapKeys(obj, fn)/invert(obj)deepEqual(a, b)(aliasisEqual)freeze(obj)– deepObject.freezedefaults(target, ...sources)– fillundefinedkeysentriesTyped(obj)/keysTyped(obj)– key-preserving typed wrappers
Function
debounce(fn, delayMs, options?)–DebouncedFnwith.cancel(),.flush(). Options:{ leading?, trailing?, maxWait? }throttle(fn, intervalMs, options?)–ThrottledFnwith.cancel(),.flush(). Options:{ leading?, trailing? }once(fn)/noop()memoize(fn, keyFn?)–MemoizedFnwith.clear(),.delete(key)compose(...fns)/pipe(...fns)curry(fn)/partial(fn, ...preset)retry(fn, opts?)– exponential backoff with jitter andAbortSignal
Misc
uid(prefix?)– short hash-style iduuid()– RFC 4122 v4 (usescrypto.randomUUID()/getRandomValues()when available)isEmpty(value)/parseJson(json, fallback)/sleep(ms)tryCatch(fn)– Go-style[error, value]for sync or async functionstimes(n, fn)/pollUntil(predicate, opts?)/nextFrame()/nextTick()
Type guards
isElement/isCollection/isFunction/isString/isNumber/isBoolean/isArray/isDate/isPromise/isObjectisError/isMap/isSet/isRegExp/isSymbol/isBigIntisAsyncFunction/isIterable/isAsyncIterableisNullish(value)/isDefined(value)
Number
randomInt(min, max)/randomFloat(min, max)clamp(value, min, max)/inRange(value, min, max, inclusive?)/toNumber(value, fallback?)round(value, precision?)/roundTo(value, step)lerp(a, b, t)/inverseLerp(a, b, value)/mapRange(value, inMin, inMax, outMin, outMax)formatBytes(bytes, opts?)– decimal/binary, locale-aware viaIntl.NumberFormatsum(items)/average(items)/median(items)degToRad(deg)/radToDeg(rad)
The locale-aware
formatNumberis exposed by@bquery/bquery/i18n.utils.formatBytes()accepts the samelocaleoption for consistent localized output without pulling in i18n.
String
capitalize/toKebabCase/toCamelCase/toSnakeCase/toPascalCase/toTitleCasetruncate(str, maxLength, suffix?)/slugify(str)/escapeRegExp(str)pad(str, length, char?)/padStart(str, length, char?)/padEnd(str, length, char?)wordCount(str)/lines(str)template(str, vars)– safe${name}interpolation (noeval)stripHtml(str)– DOM-free tag removal (not a sanitizer; usesanitizeHtml()from@bquery/bquery/securityfor untrusted input)randomString(length, charset?)– crypto-backed when available
Array
ensureArray(value)/unique(items)/uniqueBy(items, fn)chunk(items, size)/chunkBy(items, predicate)/compact(items)flatten(items)/flattenDeep(items)groupBy(items, key|fn)/keyBy(items, key|fn)partition(items, pred)/zip(...arrays)range(start, end, step?)first(items)/last(items)/take(items, n)/drop(items, n)sample(items)/shuffle(items)(Fisher–Yates)sortBy(items, fn|fns)intersection(a, b)/difference(a, b)move(items, from, to)