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

@bquery/bquery

v1.13.0

Published

bQuery.js — batteries-included TypeScript framework for the modern web: signals, SSR, Web Components, routing, and more, with a jQuery-inspired API.

Downloads

1,675

Readme


New in 1.13.0: @bquery/bquery/forms graduates into a true batteries-included tier with many new validators (integer, between, oneOf, arrayOf, requiredIf, dateAfter, fileSize, …), combinators (compose, all, not, withMessage), dynamic field arrays (createFieldArray), a fluent schema() builder, two-way DOM bindings (bindForm, bindField), scope-aware composables (useForm, useField, useFieldArray), and SSR resumability. @bquery/bquery/component adds slot/ref helpers (useSlot, useRef), DI (provide/inject), beforeUnmount and errorBoundary hooks, useAsync/whenIdle, sanitizer-safe delegated events (on/onClick/…), a css tagged template with adoptable stylesheets, and keyed list rendering. @bquery/bquery/motion ships a major expansion: the full Penner easing family with cubicBezier/steps/mix/chain, tween() with pause/resume/reverse/seek/stop/progress controls plus Promise-based animateValue(), animateTo(), springVector(), label-aware timelines (reverse, repeat, yoyo, progress, onUpdate), and new primitives scrollProgress/inView/magnetic/tilt/shake/pulse/countUp. @bquery/bquery/core adds a deep utilities expansion across arrays, functions, objects, strings, numbers, misc helpers, and type guards (groupBy, memoize, deep get/set, formatBytes, uuid, retry, …). All existing APIs from 1.12.x and earlier remain unchanged.

Highlights

  • Full-stack by default: signals, SSR, routing, server middleware, Web Components, and 15+ modules ship together — use only what you need, or bring everything.
  • Zero mandatory build step: start in plain HTML with the CDN entry points, or use your preferred bundler without changing the API surface.
  • Reactive data across the stack: fetch composables, HTTP clients, polling, pagination, WebSocket / SSE, REST helpers, and request coordination plug directly into signals.
  • From UI to backend: declarative views, forms, accessibility helpers, plugins, devtools, testing utilities, SSR, and server routing cover the full app lifecycle.
  • TypeScript-first and tree-shakeable: explicit entry points keep large apps typed while letting smaller apps import focused modules.
  • Security-focused: DOM writes are sanitized by default, with Trusted Types and CSP helpers built in.

Installation

Via npm/bun/pnpm

# npm
npm install @bquery/bquery

# bun
bun add @bquery/bquery

# pnpm
pnpm add @bquery/bquery

Via CDN (Zero-build)

ES Modules (recommended)

<script type="module">
  import { $, signal } from 'https://unpkg.com/@bquery/bquery@1/dist/full.es.mjs';

  const count = signal(0);
  $('#counter').text(`Count: ${count.value}`);
</script>

UMD (global variable)

<script src="https://unpkg.com/@bquery/bquery@1/dist/full.umd.js"></script>
<script>
  const { $, signal } = bQuery;
  const count = signal(0);
</script>

IIFE (self-executing)

<script src="https://unpkg.com/@bquery/bquery@1/dist/full.iife.js"></script>
<script>
  const { $, $$ } = bQuery;
  $$('.items').addClass('loaded');
</script>

Import Strategies

// Full bundle (all modules)
import {
  $,
  signal,
  component,
  registerDefaultComponents,
  defineBqueryConfig,
} from '@bquery/bquery';

// Core only
import { $, $$ } from '@bquery/bquery/core';

// Core utilities (named exports, tree-shakeable)
import { debounce, merge, uid, once, utils } from '@bquery/bquery/core';

// Reactive only
import {
  signal,
  computed,
  effect,
  linkedSignal,
  persistedSignal,
  useAsyncData,
  useFetch,
  createUseFetch,
  createHttp,
  http,
  usePolling,
  usePaginatedFetch,
  useInfiniteFetch,
  useWebSocket,
  useWebSocketChannel,
  useEventSource,
  useResource,
  useResourceList,
  useSubmit,
  createRestClient,
  createRequestQueue,
  deduplicateRequest,
} from '@bquery/bquery/reactive';
import type { WebSocketSendData } from '@bquery/bquery/reactive';

// Concurrency only
import {
  batchTasks,
  callWorkerMethod,
  createReactiveRpcPool,
  createReactiveRpcWorker,
  createReactiveTaskPool,
  createReactiveTaskWorker,
  createRpcPool,
  createRpcWorker,
  createTaskPool,
  createTaskWorker,
  every,
  filter,
  find,
  getConcurrencySupport,
  map,
  parallel,
  pipeline,
  reduce,
  runTask,
  some,
} from '@bquery/bquery/concurrency';

// Components only
import {
  bool,
  component,
  defineComponent,
  html,
  registerDefaultComponents,
} from '@bquery/bquery/component';

// Motion only
import { transition, spring, animate, timeline } from '@bquery/bquery/motion';

// Security only
import { sanitize, sanitizeHtml, trusted } from '@bquery/bquery/security';

// Platform only
import { storage, cache, useCookie, definePageMeta, useAnnouncer } from '@bquery/bquery/platform';

// Router, Store, View
import { createRouter, navigate } from '@bquery/bquery/router';
import { clearPlugins, createStore, defineStore, unregisterPlugin } from '@bquery/bquery/store';
import { mount, createTemplate } from '@bquery/bquery/view';

// Forms, i18n, accessibility, drag & drop, media
import { createForm, required, email } from '@bquery/bquery/forms';
import { createI18n } from '@bquery/bquery/i18n';
import { trapFocus, rovingTabIndex } from '@bquery/bquery/a11y';
import { draggable, droppable, sortable } from '@bquery/bquery/dnd';
import {
  mediaQuery,
  useViewport,
  useIntersectionObserver,
  useResizeObserver,
  useMutationObserver,
  clipboard,
} from '@bquery/bquery/media';

// Plugins, devtools, testing, SSR, server
import { use } from '@bquery/bquery/plugin';
import { enableDevtools, inspectSignals } from '@bquery/bquery/devtools';
import { renderComponent, fireEvent, waitFor } from '@bquery/bquery/testing';
import {
  createSSRContext,
  hydrateMount,
  renderToResponse,
  renderToStream,
  renderToString,
  renderToStringAsync,
  serializeStoreState,
} from '@bquery/bquery/ssr';
import { createServer } from '@bquery/bquery/server';

// Storybook helpers
import { storyHtml, when } from '@bquery/bquery/storybook';

Modules at a glance

| Module | Status | Description | | --------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Core | Stable | Selectors, DOM manipulation, events, traversal, and typed utilities | | Reactive | Stable | signal, computed, effect, watchDebounce, watchThrottle, async data, HTTP clients, polling, pagination, WebSocket / SSE, and REST helpers | | Concurrency | Experimental | Zero-build worker tasks, explicit RPC helpers, optional reactive state wrappers, bounded worker pools, high-level collection helpers, and an optional fluent pipeline layer | | Component | Stable | Typed Web Components with scoped reactivity and configurable Shadow DOM | | Storybook | Beta | Safe story template helpers with boolean-attribute shorthand | | Motion | Stable | View transitions, FLIP, morphing, parallax, typewriter, springs, and timelines | | Security | Stable | HTML sanitization, Trusted Types, CSP helpers, and trusted fragment composition | | Platform | Stable | Storage, cache, cookies, page metadata, announcers, and shared runtime config | | Router | Stable | SPA routing, constrained params, redirects, guards, useRoute(), and <bq-link> | | Store | Stable | Signal-based state management, persistence, migrations, action hooks, and plugin lifecycle helpers | | View | Beta | Declarative DOM bindings with bq-* directives for content, classes, forms, errors, ARIA, and plugins | | Forms | Beta | Reactive form state with sync/async validation and submit handling | | i18n | Beta | Reactive locales, interpolation, pluralization, lazy loading, and Intl formatting | | A11y | Beta | Focus traps, live-region announcements, roving tabindex, skip links, and audits | | DnD | Beta | Draggable elements, droppable zones, and sortable lists | | Media | Beta | Reactive browser/device signals for viewport, network, battery, geolocation, clipboard, and DOM observers | | Plugin | Beta | Global plugin registration for custom directives and Web Components | | Devtools | Beta | Runtime inspection helpers for signals, stores, components, and timelines | | Testing | Beta | Component mounting, mock signals/router helpers, and async test utilities | | SSR | Experimental | Runtime-agnostic server-side rendering (Node ≥ 24, Deno, Bun), streaming, async loaders, hydration islands, head/asset/CSP-nonce management, runtime adapters | | Server | Experimental | Express-inspired backend routing, middleware, safe response helpers, SSR-aware request handling, and runtime-agnostic WebSocket sessions |

Storybook authoring helpers are also available as a dedicated entry point via @bquery/bquery/storybook. Worker-task, RPC, worker-pool, high-level task-list / collection helpers, and the optional fluent pipeline layer ship as a dedicated entry point via @bquery/bquery/concurrency. Server-side middleware, HTTP routing, and runtime-agnostic WebSocket session helpers ship as a dedicated entry point via @bquery/bquery/server.

Reusable workers and pools can also opt into readonly signal mirrors such as state$, busy$, pending$, and size$ through the createReactive*() concurrency wrappers.

Quick examples

bQuery.js covers the full development lifecycle — from interactive DOM scripting to server-side rendering. The examples below show each layer independently; in practice they compose seamlessly.

Core – DOM & events

import { $, $$ } from '@bquery/bquery/core';

$('#save').on('click', (event) => {
  console.log('Saved', event.type);
});

$('#list').delegate('click', '.item', (event, target) => {
  console.log('Item clicked', target.textContent);
});

$('#box').addClass('active').css({ opacity: '0.8' }).attr('data-state', 'ready');

const color = $('#box').css('color');

if ($('#el').is('.active')) {
  console.log('Element is active');
}

$$('.container').find('.item').addClass('found');

Reactive – signals

import {
  signal,
  computed,
  effect,
  batch,
  watch,
  watchDebounce,
  watchThrottle,
  readonly,
  linkedSignal,
} from '@bquery/bquery/reactive';

const count = signal(0);
const doubled = computed(() => count.value * 2);

effect(() => {
  console.log('Count changed', count.value);
});

watch(count, (newVal, oldVal) => {
  console.log(`Changed from ${oldVal} to ${newVal}`);
});

watchDebounce(
  count,
  (newVal) => {
    console.log('Debounced count', newVal);
  },
  150
);

const readOnlyCount = readonly(count);

batch(() => {
  count.value++;
  count.value++;
});

count.dispose();

const first = signal('Ada');
const last = signal('Lovelace');
const fullName = linkedSignal(
  () => `${first.value} ${last.value}`,
  (next) => {
    const [nextFirst, nextLast] = next.split(' ');
    first.value = nextFirst ?? '';
    last.value = nextLast ?? '';
  }
);

fullName.value = 'Grace Hopper';

Concurrency – worker tasks

import { runTask } from '@bquery/bquery/concurrency';

const total = await runTask(
  ({ values }: { values: number[] }) => values.reduce((sum, value) => sum + value, 0),
  { values: [1, 2, 3, 4] },
  { timeout: 1_000 }
);

console.log(total); // 10

Concurrency – RPC-style worker methods

import { createRpcWorker } from '@bquery/bquery/concurrency';

const rpc = createRpcWorker({
  formatUser: ({ first, last }: { first: string; last: string }) => `${last}, ${first}`,
  sum: ({ values }: { values: number[] }) => values.reduce((total, value) => total + value, 0),
});

console.log(await rpc.call('formatUser', { first: 'Ada', last: 'Lovelace' }));
console.log(await rpc.call('sum', { values: [1, 2, 3] }));

rpc.terminate();

Concurrency – pooled worker execution

import { createTaskPool } from '@bquery/bquery/concurrency';

const pool = createTaskPool(({ value }: { value: number }) => value * 2, {
  concurrency: 4,
  maxQueue: 16,
  name: 'double-pool',
});

const results = await Promise.all([
  pool.run({ value: 1 }),
  pool.run({ value: 2 }),
  pool.run({ value: 3 }),
]);

console.log(results); // [2, 4, 6]
pool.terminate();

Concurrency – reactive pool state

import { createReactiveTaskPool } from '@bquery/bquery/concurrency';
import { effect } from '@bquery/bquery/reactive';

const pool = createReactiveTaskPool(
  async ({ delay, value }: { delay: number; value: number }) => {
    await new Promise((resolve) => setTimeout(resolve, delay));
    return value * 2;
  },
  { concurrency: 2, maxQueue: 8 }
);

effect(() => {
  console.log(pool.state$.value, pool.pending$.value, pool.size$.value);
});

await Promise.all([
  pool.run({ delay: 20, value: 1 }),
  pool.run({ delay: 20, value: 2 }),
  pool.run({ delay: 0, value: 3 }),
]);

pool.terminate();

Concurrency – task lists, collection helpers & pipelines

import {
  batchTasks,
  every,
  filter,
  find,
  map,
  parallel,
  pipeline,
  reduce,
  some,
} from '@bquery/bquery/concurrency';

const tasks = await parallel([
  { handler: (value: number) => value * 2, input: 5 },
  {
    handler: ({ first, last }: { first: string; last: string }) => `${last}, ${first}`,
    input: { first: 'Ada', last: 'Lovelace' },
  },
]);

const batched = await batchTasks(
  [
    { handler: (value: number) => value * 2, input: 1 },
    { handler: (value: number) => value * 2, input: 2 },
    { handler: (value: number) => value * 2, input: 3 },
  ],
  2
);

const mapped = await map([1, 2, 3, 4], (value, index) => value + index, {
  batchSize: 2,
  concurrency: 2,
});

const filtered = await filter([5, 2, 9, 4], (value) => value % 2 === 1);
const hasEven = await some([1, 3, 4], (value) => value % 2 === 0);
const allEven = await every([2, 4, 6], (value) => value % 2 === 0);
const firstLarge = await find([3, 8, 11, 14], (value) => value > 10);
const reduced = await reduce([1, 2, 3, 4], (accumulator, value) => accumulator + value, 0);
const piped = await pipeline([1, 2, 3, 4], { batchSize: 2, concurrency: 2 })
  .map((value) => value * 2)
  .filter((value) => value > 4)
  .toArray();

console.log(tasks, batched, mapped, filtered, hasEven, allEven, firstLarge, reduced, piped);

Reactive – async data & fetch

import { signal, useFetch, createUseFetch } from '@bquery/bquery/reactive';

const userId = signal(1);

const user = useFetch<{ id: number; name: string }>(() => `/users/${userId.value}`, {
  baseUrl: 'https://api.example.com',
  watch: [userId],
  query: { include: 'profile' },
});

const useApiFetch = createUseFetch({
  baseUrl: 'https://api.example.com',
  headers: { 'x-client': 'bquery-readme' },
});

const settings = useApiFetch<{ theme: string }>('/settings');

console.log(user.pending.value, user.data.value, settings.error.value);

View – directives

import { mount } from '@bquery/bquery/view';
import { signal } from '@bquery/bquery/reactive';

const formError = signal('');
const fieldState = signal({ invalid: false, describedBy: '' });

mount('#profile-form', {
  formError,
  fieldState,
});

formError.value = 'Email is required';
fieldState.value = { invalid: true, describedBy: 'email-error' };
<input
  id="email"
  bq-aria="{ invalid: fieldState.value.invalid, 'aria-describedby': fieldState.value.describedBy }"
/>
<p id="email-error" bq-error="formError"></p>

Reactive – HTTP, streaming & request coordination

import {
  createHttp,
  createRequestQueue,
  deduplicateRequest,
  useEventSource,
  useWebSocket,
} from '@bquery/bquery/reactive';

const api = createHttp({
  baseUrl: 'https://api.example.com',
  retry: {
    count: 2,
    onRetry: (error, attempt) => console.warn(`Retry #${attempt}`, error.message),
  },
});

const queue = createRequestQueue({ concurrency: 4 });
const ws = useWebSocket<{ type: string; payload: unknown }>('wss://api.example.com/live');
const sse = useEventSource<{ token: string }>('/api/stream');

const users = await deduplicateRequest('/users', () => queue.add(() => api.get('/users')));

console.log(users.data, ws.status.value, sse.eventName.value);

Components – Web Components

import {
  bool,
  component,
  defineComponent,
  html,
  registerDefaultComponents,
  safeHtml,
} from '@bquery/bquery/component';
import { sanitizeHtml, trusted } from '@bquery/bquery/security';

const badge = trusted(sanitizeHtml('<span class="badge">Active</span>'));

component('user-card', {
  props: {
    username: { type: String, required: true },
    age: { type: Number, validator: (v) => v >= 0 && v <= 150 },
  },
  state: { count: 0 },
  beforeMount() {
    console.log('About to mount');
  },
  connected() {
    console.log('Mounted');
  },
  beforeUpdate(newProps, oldProps) {
    return newProps.username !== oldProps.username;
  },
  updated(change) {
    console.log('Updated because of', change?.name ?? 'state/signal change');
  },
  onError(error) {
    console.error('Component error:', error);
  },
  render({ props, state }) {
    return safeHtml`
      <button class="user-card" ${bool('disabled', state.count > 3)}>
        ${badge}
        <span>Hello ${props.username}</span>
      </button>
    `;
  },
});

const UserCard = defineComponent('user-card-manual', {
  props: { username: { type: String, required: true } },
  render: ({ props }) => html`<div>Hello ${props.username}</div>`,
});

customElements.define('user-card-manual', UserCard);

const tags = registerDefaultComponents({ prefix: 'ui' });
console.log(tags.button); // ui-button

Storybook – authoring helpers

import { storyHtml, when } from '@bquery/bquery/storybook';

export const Primary = {
  args: { disabled: false, label: 'Save' },
  render: ({ disabled, label }) =>
    storyHtml`
      <ui-card>
        <ui-button ?disabled=${disabled}>${label}</ui-button>
        ${when(!disabled, '<small>Ready to submit</small>')}
      </ui-card>
    `,
};

Motion – animations

import { animate, keyframePresets, spring, transition } from '@bquery/bquery/motion';

await transition({
  update: () => {
    $('#content').text('Updated');
  },
  classes: ['page-transition'],
  types: ['navigation'],
  skipOnReducedMotion: true,
});

await animate(card, {
  keyframes: keyframePresets.pop(),
  options: { duration: 240, easing: 'ease-out' },
});

const x = spring(0, { stiffness: 120, damping: 14 });
x.onChange((value) => {
  element.style.transform = `translateX(${value}px)`;
});
await x.to(100);

Security – sanitizing

import { sanitize, escapeHtml, sanitizeHtml, trusted } from '@bquery/bquery/security';
import { safeHtml } from '@bquery/bquery/component';

const safeMarkup = sanitize(userInput);
const safe = sanitize('<form id="cookie">...</form>');
const urlSafe = sanitize('<a href="java\u200Bscript:alert(1)">click</a>');
const secureLink = sanitize('<a href="https://external.com" target="_blank">Link</a>');
const safeSrcset = sanitize('<img srcset="safe.jpg 1x, javascript:alert(1) 2x">');
const safeForm = sanitize('<form action="javascript:alert(1)">...</form>');
const escaped = escapeHtml('<script>alert(1)</script>');
const icon = trusted(sanitizeHtml('<span class="icon">♥</span>'));
const button = safeHtml`<button>${icon}<span>Save</span></button>`;

Platform – config, cookies & accessibility

import {
  defineBqueryConfig,
  useCookie,
  definePageMeta,
  useAnnouncer,
  storage,
  notifications,
} from '@bquery/bquery/platform';

defineBqueryConfig({
  fetch: { baseUrl: 'https://api.example.com' },
  transitions: { skipOnReducedMotion: true, classes: ['page-transition'] },
  components: { prefix: 'ui' },
});

const theme = useCookie<'light' | 'dark'>('theme', { defaultValue: 'light' });
const cleanupMeta = definePageMeta({ title: 'Dashboard' });
const announcer = useAnnouncer();

theme.value = 'dark';
announcer.announce('Preferences saved');
cleanupMeta();

const local = storage.local();
await local.set('theme', theme.value);

const permission = await notifications.requestPermission();
if (permission === 'granted') {
  notifications.send('Build complete', {
    body: 'Your docs are ready.',
  });
}

Media – queries, viewport & observers

import {
  mediaQuery,
  useViewport,
  useIntersectionObserver,
  useResizeObserver,
  useMutationObserver,
} from '@bquery/bquery/media';
import { effect } from '@bquery/bquery/reactive';

const prefersDark = mediaQuery('(prefers-color-scheme: dark)');
const viewport = useViewport();
const intersection = useIntersectionObserver(document.querySelector('#hero'));
const resize = useResizeObserver(document.querySelector('#panel'));
const mutations = useMutationObserver(document.querySelector('#feed'), {
  childList: true,
  subtree: true,
});

effect(() => {
  console.log(prefersDark.value, viewport.value.width, intersection.value.isIntersecting);
});

console.log(resize.value.width, mutations.value.mutations.length);

Router – SPA navigation

import { effect } from '@bquery/bquery/reactive';
import { createRouter, navigate, currentRoute } from '@bquery/bquery/router';

const router = createRouter({
  routes: [
    { path: '/', name: 'home', component: HomePage },
    { path: '/user/:id', name: 'user', component: UserPage },
    { path: '*', component: NotFound },
  ],
});

router.beforeEach(async (to) => {
  if (to.path === '/admin' && !isAuthenticated()) {
    await navigate('/login');
    return false;
  }
});

effect(() => {
  console.log('Current path:', currentRoute.value.path);
});

Forms – reactive validation

import { createForm, email, required } from '@bquery/bquery/forms';

const form = createForm({
  fields: {
    name: { initialValue: '', validators: [required()] },
    email: { initialValue: '', validators: [required(), email()] },
  },
  onSubmit: async (values) => {
    await fetch('/api/signup', {
      method: 'POST',
      body: JSON.stringify(values),
    });
  },
});

await form.handleSubmit();
console.log(form.isValid.value, form.fields.email.error.value);

i18n – locale-aware content

import { createI18n } from '@bquery/bquery/i18n';

const i18n = createI18n({
  locale: 'en',
  fallbackLocale: 'en',
  messages: {
    en: { greeting: 'Hello, {name}!' },
    de: { greeting: 'Hallo, {name}!' },
  },
});

console.log(i18n.t('greeting', { name: 'Ada' }));
i18n.$locale.value = 'de';

Accessibility, media, and drag & drop

import { trapFocus, announceToScreenReader } from '@bquery/bquery/a11y';
import { mediaQuery, useViewport } from '@bquery/bquery/media';
import { draggable } from '@bquery/bquery/dnd';

const modalTrap = trapFocus(document.querySelector('#dialog')!);
announceToScreenReader('Dialog opened');

const isDark = mediaQuery('(prefers-color-scheme: dark)');
const viewport = useViewport();
const drag = draggable(document.querySelector('#card')!, { bounds: 'parent' });

console.log(isDark.value, viewport.value.width);

drag.destroy();
modalTrap.release();

Plugins, devtools, testing, SSR, and server

import { use } from '@bquery/bquery/plugin';
import { enableDevtools, getTimeline } from '@bquery/bquery/devtools';
import { renderComponent, fireEvent } from '@bquery/bquery/testing';
import { createSSRContext, renderToResponse, renderToString } from '@bquery/bquery/ssr';
import { createServer } from '@bquery/bquery/server';

use({
  name: 'focus-plugin',
  install(ctx) {
    ctx.directive('focus', (el) => (el as HTMLElement).focus());
  },
});

enableDevtools(true, { logToConsole: true });
console.log(getTimeline());

const mounted = renderComponent('ui-button', { props: { variant: 'primary' } });
fireEvent(mounted.el, 'click');

const { html } = renderToString('<p bq-text="label"></p>', { label: 'Hello SSR' });
console.log(html);

const app = createServer();
app.get('/hello/:name', (ctx) => ctx.json({ name: ctx.params.name, q: ctx.query.q }));

// Runtime-agnostic async render with head injection (works on Node, Deno, Bun):
const ctx = createSSRContext({ request: new Request('http://localhost/') });
ctx.head.add({ title: 'Home' });
ctx.assets.module('/app.js');
const response = await renderToResponse(
  '<html><head></head><body><p bq-text="label"></p></body></html>',
  { label: 'Hello' },
  { context: ctx, etag: true }
);

mounted.unmount();

Store – state management

import {
  clearPlugins,
  createStore,
  createPersistedStore,
  defineStore,
  mapGetters,
  registerPlugin,
  unregisterPlugin,
  watchStore,
} from '@bquery/bquery/store';

const counterStore = createStore({
  id: 'counter',
  state: () => ({ count: 0, name: 'Counter' }),
  getters: {
    doubled: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++;
    },
  },
});

const settingsStore = createPersistedStore({
  id: 'settings',
  state: () => ({ theme: 'dark', language: 'en' }),
});

const useCounter = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    doubled: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++;
    },
  },
});

const counter = useCounter();
const getters = mapGetters(counter, ['doubled']);

watchStore(
  counter,
  (state) => state.count,
  (value) => {
    console.log('Count changed:', value, getters.doubled);
  }
);

const logger = ({ store, options }) => {
  store.$subscribe((state) => console.log(`[${options.id}]`, state));
};

registerPlugin(logger);
unregisterPlugin(logger); // true when the plugin registration was present
clearPlugins(); // useful in test teardown

View – declarative bindings

import { mount, createTemplate } from '@bquery/bquery/view';
import { signal } from '@bquery/bquery/reactive';

const count = signal(0);
const items = signal(['Apple', 'Banana', 'Cherry']);

mount('#app', {
  count,
  items,
  increment: () => count.value++,
});

Browser Support

| Browser | Version | Support | | ------- | ------- | ------- | | Chrome | 90+ | ✅ Full | | Firefox | 90+ | ✅ Full | | Safari | 15+ | ✅ Full | | Edge | 90+ | ✅ Full |

No IE support by design.

Server-side runtimes: Node.js ≥ 24, Bun ≥ 1.3.13, and Deno 2 are supported for SSR and server modules.

Documentation

Local Development

The cross-runtime SSR examples in examples/ import directly from src/, so you can run them from a repo checkout without building dist/ first.

# Install dependencies
bun install

# Start VitePress docs
bun run dev

# Run Storybook
bun run storybook

# Run tests
bun test

# Build library
bun run build

# Verify AI guidance / release metadata sync
bun run check:ai-guidance

# Build docs
bun run build:docs

# Generate API documentation
bun run docs:api

# Run the cross-runtime SSR examples directly from source
bun examples/ssr-bun/serve.ts
deno run -A examples/ssr-deno/serve.ts
node --experimental-strip-types examples/ssr-node/serve.ts

Project Structure

bQuery.js
├── src/
│   ├── core/       # Selectors, DOM ops, events, utils
│   ├── reactive/   # Signals, computed, effects, async data
│   ├── concurrency/ # Zero-build worker tasks, RPC, pools, collection helpers
│   ├── component/  # Web Components helper + default library
│   ├── storybook/  # Story template helpers
│   ├── motion/     # View transitions, FLIP, springs
│   ├── security/   # Sanitizer, CSP, Trusted Types
│   ├── platform/   # Storage, cache, cookies, meta, config
│   ├── router/     # SPA routing, navigation guards
│   ├── store/      # State management, persistence
│   ├── view/       # Declarative DOM bindings
│   ├── forms/      # Reactive forms + validators
│   ├── i18n/       # Internationalization + formatting
│   ├── a11y/       # Accessibility utilities
│   ├── dnd/        # Drag & drop helpers
│   ├── media/      # Browser and device reactive signals
│   ├── plugin/     # Global plugin system
│   ├── devtools/   # Runtime inspection helpers
│   ├── testing/    # Test utilities
│   ├── ssr/        # Runtime-agnostic server-side rendering + hydration
│   └── server/     # Backend helpers and WebSocket sessions
├── docs/           # VitePress documentation
├── .storybook/     # Storybook config
├── stories/        # Component stories
├── tests/          # bun:test suites
└── dist/           # Built files (ESM, UMD, IIFE)

Contributing

See CONTRIBUTING.md for guidelines.

AI Agent Support

This project provides dedicated context files for AI coding agents:

  • AGENT.md — Architecture, module API reference, coding conventions, common tasks
  • llms.txt — Compact LLM-optimized project summary
  • .github/copilot-instructions.md — GitHub Copilot context
  • bun run check:ai-guidance — Lightweight sync check for version / engine / AI guidance drift
  • bun run check:full-bundle — Static guard for /full runtime and type export drift

License

MIT – See LICENSE.md for details.