<div align="center">
<img src="https://raw.githubusercontent.com/bQuery/bQuery/main/assets/bquerry-logo.svg" alt="bQuery.js" height="80" /><br/><br/>
<h1>bQuery.js</h1>
<p><strong>The full-stack web framework that speaks jQuery.</strong></p>
<p><em>Batteries-included TypeScript framework for the modern web — signals, SSR, Web Components,<br/>routing, and more — with a jQuery-inspired API and zero mandatory build step.</em></p>
<br/>
<a href="https://www.npmjs.com/package/@bquery/bquery"><img src="https://img.shields.io/npm/v/@bquery/bquery?style=flat&logo=npm" alt="npm version"/></a>&nbsp;
<a href="https://bundlephobia.com/package/@bquery/bquery"><img src="https://img.shields.io/bundlephobia/minzip/@bquery/bquery?style=flat" alt="Bundle Size"/></a>&nbsp;
<a href="https://github.com/bQuery/bQuery/blob/main/LICENSE.md"><img src="https://img.shields.io/github/license/bquery/bquery?style=flat" alt="License"/></a><br/>
<a href="https://github.com/bQuery/bQuery/stargazers"><img src="https://img.shields.io/github/stars/bquery/bquery?style=flat&logo=github" alt="Stars"/></a>&nbsp;
<a href="https://github.com/bQuery/bQuery/issues"><img src="https://img.shields.io/github/issues/bquery/bquery?style=flat&logo=github" alt="Issues"/></a>&nbsp;
<a href="https://www.codefactor.io/repository/github/bquery/bquery"><img src="https://www.codefactor.io/repository/github/bquery/bquery/badge" alt="CodeFactor"/></a>&nbsp;
<a href="https://www.jsdelivr.com/package/npm/@bquery/bquery"><img src="https://data.jsdelivr.com/v1/package/npm/@bquery/bquery/badge" alt="jsDelivr"/></a>
</div>

---

> **New in 1.11.0:** Runtime-agnostic SSR now adds DOM-free fallback rendering, `renderToStringAsync()`, `renderToStream()`, `renderToResponse()`, runtime adapters, hydration strategies, store snapshots, and resumability hooks, alongside the new `@bquery/bquery/server` entry point for dependency-free backend routing and WebSocket sessions.

## 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

```bash
# npm
npm install @bquery/bquery

# bun
bun add @bquery/bquery

# pnpm
pnpm add @bquery/bquery
```

### Via CDN (Zero-build)

#### ES Modules (recommended)

```html
<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)

```html
<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)

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

## Import Strategies

```ts
// 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';

// 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 { createStore, defineStore } 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 { renderToString, hydrateMount, 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, and action hooks                                                                                                    |
| **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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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' };
```

```html
<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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
import { use } from '@bquery/bquery/plugin';
import { enableDevtools, getTimeline } from '@bquery/bquery/devtools';
import { renderComponent, fireEvent } from '@bquery/bquery/testing';
import { 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):
import { createSSRContext, renderToResponse } from '@bquery/bquery/ssr';
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

```ts
import {
  createStore,
  createPersistedStore,
  defineStore,
  mapGetters,
  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);
  }
);
```

### View – declarative bindings

```ts
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

- **Getting Started**: [docs/guide/getting-started.md](docs/guide/getting-started.md)
- **Core API**: [docs/guide/api-core.md](docs/guide/api-core.md)
- **Agents**: [docs/guide/agents.md](docs/guide/agents.md)
- **Components**: [docs/guide/components.md](docs/guide/components.md)
- **Storybook**: [docs/guide/storybook.md](docs/guide/storybook.md)
- **Reactivity**: [docs/guide/reactive.md](docs/guide/reactive.md)
- **Motion**: [docs/guide/motion.md](docs/guide/motion.md)
- **Security**: [docs/guide/security.md](docs/guide/security.md)
- **Platform**: [docs/guide/platform.md](docs/guide/platform.md)
- **Router**: [docs/guide/router.md](docs/guide/router.md)
- **Store**: [docs/guide/store.md](docs/guide/store.md)
- **View**: [docs/guide/view.md](docs/guide/view.md)
- **Forms**: [docs/guide/forms.md](docs/guide/forms.md)
- **i18n**: [docs/guide/i18n.md](docs/guide/i18n.md)
- **Accessibility**: [docs/guide/a11y.md](docs/guide/a11y.md)
- **Drag & Drop**: [docs/guide/dnd.md](docs/guide/dnd.md)
- **Media**: [docs/guide/media.md](docs/guide/media.md)
- **Plugin System**: [docs/guide/plugin.md](docs/guide/plugin.md)
- **Devtools**: [docs/guide/devtools.md](docs/guide/devtools.md)
- **Testing Utilities**: [docs/guide/testing.md](docs/guide/testing.md)
- **SSR / Hydration**: [docs/guide/ssr.md](docs/guide/ssr.md)
- **Server**: [docs/guide/server.md](docs/guide/server.md)

## Local Development

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

```bash
# 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

```text
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](CONTRIBUTING.md) for guidelines.

## AI Agent Support

This project provides dedicated context files for AI coding agents:

- **[AGENT.md](AGENT.md)** — Architecture, module API reference, coding conventions, common tasks
- **[llms.txt](llms.txt)** — Compact LLM-optimized project summary
- **[.github/copilot-instructions.md](.github/copilot-instructions.md)** — GitHub Copilot context
- **`bun run check:ai-guidance`** — Lightweight sync check for version / engine / AI guidance drift

## License

MIT – See [LICENSE.md](LICENSE.md) for details.
