@bquery/bquery 1.11.1 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +52 -33
  2. package/dist/{a11y-DgUQ8-fI.js → a11y-IV_bfLyn.js} +3 -3
  3. package/dist/{a11y-DgUQ8-fI.js.map → a11y-IV_bfLyn.js.map} +1 -1
  4. package/dist/a11y.es.mjs +1 -1
  5. package/dist/{component-D8ydhe58.js → component-BtsRbf6c.js} +7 -7
  6. package/dist/{component-D8ydhe58.js.map → component-BtsRbf6c.js.map} +1 -1
  7. package/dist/component.es.mjs +1 -1
  8. package/dist/{concurrency-BU1wPEsZ.js → concurrency-kycgAZvW.js} +3 -3
  9. package/dist/{concurrency-BU1wPEsZ.js.map → concurrency-kycgAZvW.js.map} +1 -1
  10. package/dist/concurrency.es.mjs +1 -1
  11. package/dist/{config-DhT9auRm.js → config-BP7KwiR5.js} +1 -1
  12. package/dist/{config-DhT9auRm.js.map → config-BP7KwiR5.js.map} +1 -1
  13. package/dist/constraints-Dlbx_m1b.js.map +1 -1
  14. package/dist/core-tOP6QOrY.js.map +1 -1
  15. package/dist/{core-CongXJuo.js → core-yg9rJXiR.js} +1 -1
  16. package/dist/{core-CongXJuo.js.map → core-yg9rJXiR.js.map} +1 -1
  17. package/dist/custom-directives-5DlKqvd2.js.map +1 -1
  18. package/dist/devtools-QosAqo0T.js.map +1 -1
  19. package/dist/dnd-d2OU4len.js.map +1 -1
  20. package/dist/{effect-Cc51IH91.js → effect-v8OIEmPs.js} +2 -2
  21. package/dist/{effect-Cc51IH91.js.map → effect-v8OIEmPs.js.map} +1 -1
  22. package/dist/env-PvwYHnJq.js.map +1 -1
  23. package/dist/{forms-BLx4ZzT7.js → forms-DYcdlk_h.js} +8 -8
  24. package/dist/{forms-BLx4ZzT7.js.map → forms-DYcdlk_h.js.map} +1 -1
  25. package/dist/forms.es.mjs +1 -1
  26. package/dist/full.d.ts +11 -11
  27. package/dist/full.d.ts.map +1 -1
  28. package/dist/full.es.mjs +324 -279
  29. package/dist/full.iife.js +26 -26
  30. package/dist/full.iife.js.map +1 -1
  31. package/dist/full.umd.js +26 -26
  32. package/dist/full.umd.js.map +1 -1
  33. package/dist/function-Cybd57JV.js.map +1 -1
  34. package/dist/{i18n--p7PM-9r.js → i18n-unHU1jMo.js} +3 -3
  35. package/dist/{i18n--p7PM-9r.js.map → i18n-unHU1jMo.js.map} +1 -1
  36. package/dist/i18n.es.mjs +1 -1
  37. package/dist/index.es.mjs +198 -196
  38. package/dist/match-CrZRVC4z.js.map +1 -1
  39. package/dist/{media-gjbWNq50.js → media-Chh6mA0v.js} +3 -3
  40. package/dist/{media-gjbWNq50.js.map → media-Chh6mA0v.js.map} +1 -1
  41. package/dist/media.es.mjs +1 -1
  42. package/dist/{motion-BBMso9Ir.js → motion-27Od9aFE.js} +2 -2
  43. package/dist/{motion-BBMso9Ir.js.map → motion-27Od9aFE.js.map} +1 -1
  44. package/dist/motion.es.mjs +1 -1
  45. package/dist/{mount-0A9qtcRJ.js → mount-DwUFujZ_.js} +4 -4
  46. package/dist/{mount-0A9qtcRJ.js.map → mount-DwUFujZ_.js.map} +1 -1
  47. package/dist/object-BCk-1c8T.js.map +1 -1
  48. package/dist/{platform-BPHIXbw8.js → platform-2YkFA11t.js} +4 -4
  49. package/dist/{platform-BPHIXbw8.js.map → platform-2YkFA11t.js.map} +1 -1
  50. package/dist/platform.es.mjs +2 -2
  51. package/dist/plugin-SZEirbwq.js.map +1 -1
  52. package/dist/reactive/index.d.ts +2 -2
  53. package/dist/reactive/index.d.ts.map +1 -1
  54. package/dist/reactive/signal.d.ts +7 -7
  55. package/dist/reactive/signal.d.ts.map +1 -1
  56. package/dist/reactive/websocket.d.ts +15 -3
  57. package/dist/reactive/websocket.d.ts.map +1 -1
  58. package/dist/{reactive-BAd2hfl8.js → reactive-BvPR_FYA.js} +10 -10
  59. package/dist/{reactive-BAd2hfl8.js.map → reactive-BvPR_FYA.js.map} +1 -1
  60. package/dist/reactive.es.mjs +40 -40
  61. package/dist/{readonly-C0ZwS1Tf.js → readonly-Br-6pAgj.js} +2 -2
  62. package/dist/{readonly-C0ZwS1Tf.js.map → readonly-Br-6pAgj.js.map} +1 -1
  63. package/dist/registry-jpUQHf4E.js.map +1 -1
  64. package/dist/{router-C4weu0QL.js → router-CbnWKprL.js} +7 -7
  65. package/dist/{router-C4weu0QL.js.map → router-CbnWKprL.js.map} +1 -1
  66. package/dist/router.es.mjs +1 -1
  67. package/dist/sanitize-DOMkRO9G.js.map +1 -1
  68. package/dist/server/create-server.d.ts.map +1 -1
  69. package/dist/server/types.d.ts.map +1 -1
  70. package/dist/{server-QdyKtCS1.js → server-Dwiq_F49.js} +2 -2
  71. package/dist/server-Dwiq_F49.js.map +1 -0
  72. package/dist/server.es.mjs +1 -1
  73. package/dist/ssr/context.d.ts.map +1 -1
  74. package/dist/ssr/renderer.d.ts.map +1 -1
  75. package/dist/ssr/suspense.d.ts.map +1 -1
  76. package/dist/{ssr-Bt6BQA3J.js → ssr-CqJU1Ogp.js} +8 -8
  77. package/dist/ssr-CqJU1Ogp.js.map +1 -0
  78. package/dist/ssr.es.mjs +1 -1
  79. package/dist/store/index.d.ts +1 -1
  80. package/dist/store/index.d.ts.map +1 -1
  81. package/dist/store/plugins.d.ts +38 -0
  82. package/dist/store/plugins.d.ts.map +1 -1
  83. package/dist/{store-DnXuu6Li.js → store-DzrhVQ29.js} +69 -62
  84. package/dist/store-DzrhVQ29.js.map +1 -0
  85. package/dist/store.es.mjs +13 -11
  86. package/dist/storybook.es.mjs.map +1 -1
  87. package/dist/{testing-CeMUwrRD.js → testing-ByjwS2_D.js} +3 -3
  88. package/dist/{testing-CeMUwrRD.js.map → testing-ByjwS2_D.js.map} +1 -1
  89. package/dist/testing.es.mjs +1 -1
  90. package/dist/type-guards-BMX2c0LP.js.map +1 -1
  91. package/dist/{untrack-bjWDNdyE.js → untrack-uzz3JDNK.js} +3 -3
  92. package/dist/{untrack-bjWDNdyE.js.map → untrack-uzz3JDNK.js.map} +1 -1
  93. package/dist/view.es.mjs +8 -8
  94. package/package.json +8 -7
  95. package/src/full.ts +75 -6
  96. package/src/reactive/index.ts +5 -4
  97. package/src/reactive/signal.ts +7 -6
  98. package/src/reactive/websocket.ts +15 -2
  99. package/src/server/create-server.ts +10 -5
  100. package/src/server/types.ts +1 -5
  101. package/src/ssr/context.ts +6 -2
  102. package/src/ssr/renderer.ts +5 -2
  103. package/src/ssr/suspense.ts +17 -8
  104. package/src/store/index.ts +1 -1
  105. package/src/store/plugins.ts +48 -1
  106. package/dist/server-QdyKtCS1.js.map +0 -1
  107. package/dist/ssr-Bt6BQA3J.js.map +0 -1
  108. package/dist/store-DnXuu6Li.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"testing-CeMUwrRD.js","names":[],"sources":["../src/testing/testing.ts"],"sourcesContent":["/**\n * Testing utilities for bQuery.js.\n *\n * Provides helpers for mounting components, controlling signals, mocking\n * the router, dispatching events, and asserting async conditions — all\n * designed for use with `bun:test` and happy-dom.\n *\n * @module bquery/testing\n */\n\nimport { batch, Signal, signal } from '../reactive/index';\nimport { getNormalizedRouteConstraint } from '../router/constraints';\nimport type {\n FireEventOptions,\n MockRouteDefinition,\n MockRouter,\n MockRouterOptions,\n MockSignal,\n RenderComponentOptions,\n RenderResult,\n TestRoute,\n WaitForOptions,\n} from './types';\n\n// ============================================================================\n// renderComponent\n// ============================================================================\n\nconst isWordChar = (char: string | undefined): boolean =>\n char !== undefined &&\n ((char >= 'a' && char <= 'z') ||\n (char >= 'A' && char <= 'Z') ||\n (char >= '0' && char <= '9') ||\n char === '_');\n\nconst readRouteConstraint = (\n pattern: string,\n startIndex: number\n): { constraint: string; endIndex: number } | null => {\n let depth = 1;\n let constraint = '';\n let i = startIndex + 1;\n\n while (i < pattern.length) {\n const char = pattern[i];\n\n if (char === '\\\\' && i + 1 < pattern.length) {\n constraint += char + pattern[i + 1];\n i += 2;\n continue;\n }\n\n if (char === '(') {\n depth++;\n } else if (char === ')') {\n depth--;\n if (depth === 0) {\n return { constraint, endIndex: i + 1 };\n }\n }\n\n constraint += char;\n i++;\n }\n\n return null;\n};\n\nconst routeConstraintRegexCache = new Map<string, RegExp>();\n\nconst getRouteConstraintRegex = (constraint: string): RegExp => {\n const normalized = getNormalizedRouteConstraint(constraint);\n const cached = routeConstraintRegexCache.get(normalized);\n if (cached) {\n return cached;\n }\n\n const compiled = new RegExp(`^(?:${normalized})$`);\n routeConstraintRegexCache.set(normalized, compiled);\n return compiled;\n};\n\n/**\n * Mounts a custom element by tag name for testing and returns a handle\n * to interact with it.\n *\n * The element is created, configured with the given props and slots,\n * and appended to the container (defaults to `document.body`). Call\n * `unmount()` to remove the element and trigger its `disconnectedCallback`.\n *\n * @param tagName - The custom element tag name (must already be registered)\n * @param options - Props, slots, and container configuration\n * @returns A {@link RenderResult} with the element and an unmount function\n * @throws {Error} If the tag name is not a valid custom element name\n *\n * @example\n * ```ts\n * import { renderComponent } from '@bquery/bquery/testing';\n *\n * const { el, unmount } = renderComponent('my-counter', {\n * props: { start: '5' },\n * });\n * expect(el.shadowRoot?.textContent).toContain('5');\n * unmount();\n * ```\n */\nexport function renderComponent(\n tagName: string,\n options: RenderComponentOptions = {}\n): RenderResult {\n if (!tagName || !tagName.includes('-')) {\n throw new Error(\n `bQuery testing: \"${tagName}\" is not a valid custom element tag name (must contain a hyphen)`\n );\n }\n\n const { props, slots, container = document.body } = options;\n\n const el = document.createElement(tagName);\n\n // Set attributes (props) before connecting\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n if (value === null || value === undefined) continue;\n el.setAttribute(key, String(value));\n }\n }\n\n // Inject slot content before connecting so the component can discover it\n if (slots) {\n if (typeof slots === 'string') {\n el.innerHTML = slots;\n } else {\n const parts: string[] = [];\n for (const [slotName, html] of Object.entries(slots)) {\n if (slotName === 'default') {\n parts.push(html);\n } else {\n const safeSlotName = slotName\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n parts.push(`<div slot=\"${safeSlotName}\">${html}</div>`);\n }\n }\n el.innerHTML = parts.join('');\n }\n }\n\n // Connect — triggers connectedCallback\n container.appendChild(el);\n\n const unmount = (): void => {\n if (el.parentNode) {\n el.parentNode.removeChild(el);\n }\n };\n\n return { el, unmount };\n}\n\n// ============================================================================\n// flushEffects\n// ============================================================================\n\n/**\n * Synchronously flushes any pending reactive effects.\n *\n * In bQuery's reactive system, effects outside of a batch are executed\n * synchronously. This helper exists primarily for clarity and for\n * flushing effects that may have been deferred inside a batch.\n *\n * Internally it performs a no-op batch to trigger the flush of any\n * pending observers that were queued during a prior `batch()` call.\n *\n * @example\n * ```ts\n * import { signal, batch } from '@bquery/bquery/reactive';\n * import { flushEffects } from '@bquery/bquery/testing';\n *\n * const count = signal(0);\n * let observed = 0;\n * effect(() => { observed = count.value; });\n *\n * batch(() => { count.value = 42; });\n * flushEffects();\n * expect(observed).toBe(42);\n * ```\n */\nexport function flushEffects(): void {\n // A no-op batch triggers endBatch which flushes any pending observers.\n // Since bQuery's effects are synchronous outside of batches, this is\n // mainly useful after manual batch calls or micro-task boundaries.\n batch(() => {\n /* intentionally empty — triggers pending observer flush */\n });\n}\n\n// ============================================================================\n// mockSignal\n// ============================================================================\n\n/**\n * Creates a controllable signal for tests with `set()` and `reset()` helpers.\n *\n * This is a thin wrapper around `signal()` that records the initial value\n * and adds explicit `set()` / `reset()` methods for clearer test intent.\n *\n * @template T - The type of the signal value\n * @param initialValue - The initial value\n * @returns A {@link MockSignal} instance\n *\n * @example\n * ```ts\n * import { mockSignal } from '@bquery/bquery/testing';\n *\n * const count = mockSignal(0);\n * count.set(5);\n * expect(count.value).toBe(5);\n * count.reset();\n * expect(count.value).toBe(0);\n * ```\n */\nexport function mockSignal<T>(initialValue: T): MockSignal<T> {\n const s = signal(initialValue) as Signal<T> & {\n set: (value: T) => void;\n reset: () => void;\n initialValue: T;\n };\n\n Object.defineProperty(s, 'initialValue', {\n value: initialValue,\n writable: false,\n enumerable: true,\n });\n\n s.set = function (value: T): void {\n s.value = value;\n };\n\n s.reset = function (): void {\n s.value = initialValue;\n };\n\n return s as MockSignal<T>;\n}\n\n// ============================================================================\n// mockRouter\n// ============================================================================\n\n/**\n * Parses a path string into the route's `path`, `query`, and `hash` parts.\n * @internal\n */\nfunction parsePath(\n fullPath: string,\n base: string\n): { path: string; query: Record<string, string | string[]>; hash: string } {\n let working = fullPath;\n\n // Strip base prefix\n if (base && working.startsWith(base)) {\n working = working.slice(base.length) || '/';\n }\n\n // Extract hash\n let hash = '';\n const hashIdx = working.indexOf('#');\n if (hashIdx >= 0) {\n hash = working.slice(hashIdx + 1);\n working = working.slice(0, hashIdx);\n }\n\n // Extract query string\n const query: Record<string, string | string[]> = {};\n const qIdx = working.indexOf('?');\n if (qIdx >= 0) {\n const qs = working.slice(qIdx + 1);\n working = working.slice(0, qIdx);\n for (const pair of qs.split('&')) {\n const eqIdx = pair.indexOf('=');\n const key = eqIdx >= 0 ? decodeURIComponent(pair.slice(0, eqIdx)) : decodeURIComponent(pair);\n const val = eqIdx >= 0 ? decodeURIComponent(pair.slice(eqIdx + 1)) : '';\n const existing = query[key];\n if (existing !== undefined) {\n if (Array.isArray(existing)) {\n existing.push(val);\n } else {\n query[key] = [existing, val];\n }\n } else {\n query[key] = val;\n }\n }\n }\n\n return { path: working || '/', query, hash };\n}\n\n/**\n * Matches a path against a route definition, extracting params.\n * @internal\n */\nfunction matchRoute(\n path: string,\n routes: MockRouteDefinition[]\n): { matched: MockRouteDefinition | null; params: Record<string, string> } {\n for (const route of routes) {\n const params = matchRoutePattern(route.path, path);\n if (params) {\n return { matched: route, params };\n }\n }\n return { matched: null, params: {} };\n}\n\n/**\n * Builds param matches from a route path pattern without compiling the full path into a regex.\n * @internal\n */\nfunction matchRoutePattern(pattern: string, path: string): Record<string, string> | null {\n if (pattern === '*') {\n return {};\n }\n\n // Memoization keeps wildcard/param backtracking linear for repeated subproblems\n // within a single pattern/path match attempt.\n const memo = new Map<string, Record<string, string> | null>();\n\n const findSegmentBoundary = (value: string, startIndex: number): number => {\n const slashIndex = value.indexOf('/', startIndex);\n return slashIndex === -1 ? value.length : slashIndex;\n };\n\n const matchFrom = (patternIndex: number, pathIndex: number): Record<string, string> | null => {\n const memoKey = `${patternIndex}:${pathIndex}`;\n if (memo.has(memoKey)) {\n return memo.get(memoKey) ?? null;\n }\n\n if (patternIndex === pattern.length) {\n const result = pathIndex === path.length ? {} : null;\n memo.set(memoKey, result);\n return result;\n }\n\n const patternChar = pattern[patternIndex];\n\n if (patternChar === '*') {\n for (let candidateEnd = path.length; candidateEnd >= pathIndex; candidateEnd--) {\n const suffixMatch = matchFrom(patternIndex + 1, candidateEnd);\n if (suffixMatch) {\n memo.set(memoKey, suffixMatch);\n return suffixMatch;\n }\n }\n\n memo.set(memoKey, null);\n return null;\n }\n\n if (patternChar === ':' && isWordChar(pattern[patternIndex + 1])) {\n let nameEnd = patternIndex + 2;\n while (nameEnd < pattern.length && isWordChar(pattern[nameEnd])) {\n nameEnd++;\n }\n\n const name = pattern.slice(patternIndex + 1, nameEnd);\n let nextPatternIndex = nameEnd;\n let constraint: string | undefined;\n let catchAll = false;\n\n if (pattern[nameEnd] === '(') {\n const parsedConstraint = readRouteConstraint(pattern, nameEnd);\n if (parsedConstraint) {\n constraint = parsedConstraint.constraint;\n nextPatternIndex = parsedConstraint.endIndex;\n }\n }\n\n if (pattern[nextPatternIndex] === '*') {\n catchAll = true;\n nextPatternIndex++;\n }\n\n const candidateLimit = catchAll\n ? path.length\n : constraint\n ? path.length\n : findSegmentBoundary(path, pathIndex);\n\n for (let candidateEnd = candidateLimit; candidateEnd > pathIndex; candidateEnd--) {\n const candidateValue = path.slice(pathIndex, candidateEnd);\n if (constraint) {\n const constraintRegex = getRouteConstraintRegex(constraint);\n if (!constraintRegex.test(candidateValue)) {\n continue;\n }\n }\n\n const suffixMatch = matchFrom(nextPatternIndex, candidateEnd);\n if (suffixMatch) {\n const result = {\n [name]: candidateValue,\n ...suffixMatch,\n };\n memo.set(memoKey, result);\n return result;\n }\n }\n\n memo.set(memoKey, null);\n return null;\n }\n\n if (pathIndex >= path.length || patternChar !== path[pathIndex]) {\n memo.set(memoKey, null);\n return null;\n }\n\n const result = matchFrom(patternIndex + 1, pathIndex + 1);\n memo.set(memoKey, result);\n return result;\n };\n\n return matchFrom(0, 0);\n}\n\n/**\n * Creates a lightweight mock router for testing that does not interact\n * with the browser History API.\n *\n * The mock router provides a reactive `currentRoute` signal that updates\n * when `push()` or `replace()` is called, making it ideal for testing\n * components or logic that depend on route state.\n *\n * @param options - Mock router configuration\n * @returns A {@link MockRouter} instance\n *\n * @example\n * ```ts\n * import { mockRouter } from '@bquery/bquery/testing';\n *\n * const router = mockRouter({\n * routes: [\n * { path: '/', component: () => null },\n * { path: '/user/:id', component: () => null },\n * ],\n * initialPath: '/',\n * });\n *\n * router.push('/user/42');\n * expect(router.currentRoute.value.params.id).toBe('42');\n * router.destroy();\n * ```\n */\nexport function mockRouter(options: MockRouterOptions = {}): MockRouter {\n const routes = options.routes ?? [{ path: '*', component: () => null }];\n const base = options.base ?? '';\n const initialPath = options.initialPath ?? '/';\n\n const resolveRoute = (fullPath: string): TestRoute => {\n const { path, query, hash } = parsePath(fullPath, base);\n const { matched, params } = matchRoute(path, routes);\n return { path, params, query, matched, hash };\n };\n\n const routeSignal = signal<TestRoute>(resolveRoute(initialPath));\n\n return {\n push(path: string): void {\n routeSignal.value = resolveRoute(path);\n },\n replace(path: string): void {\n routeSignal.value = resolveRoute(path);\n },\n get currentRoute(): Signal<TestRoute> {\n return routeSignal;\n },\n get routes(): MockRouteDefinition[] {\n return routes;\n },\n destroy(): void {\n routeSignal.dispose();\n },\n };\n}\n\n// ============================================================================\n// fireEvent\n// ============================================================================\n\n/**\n * Dispatches a synthetic event on an element and flushes pending effects.\n *\n * By default the event bubbles, is cancelable, and is composed (crosses\n * shadow DOM boundaries). Pass a `detail` option to create a `CustomEvent`.\n *\n * @param el - The target element\n * @param eventName - The event type (e.g. 'click', 'input', 'my-event')\n * @param options - Event configuration\n * @returns `true` if the event was not cancelled\n *\n * @example\n * ```ts\n * import { fireEvent } from '@bquery/bquery/testing';\n *\n * const button = document.createElement('button');\n * let clicked = false;\n * button.addEventListener('click', () => { clicked = true; });\n * fireEvent(button, 'click');\n * expect(clicked).toBe(true);\n * ```\n */\nexport function fireEvent(el: Element, eventName: string, options: FireEventOptions = {}): boolean {\n if (!el) {\n throw new Error('bQuery testing: fireEvent requires a valid element');\n }\n if (!eventName) {\n throw new Error('bQuery testing: fireEvent requires an event name');\n }\n\n const { bubbles = true, cancelable = true, composed = true, detail } = options;\n\n let event: Event;\n if (detail !== undefined) {\n event = new CustomEvent(eventName, { bubbles, cancelable, composed, detail });\n } else {\n event = new Event(eventName, { bubbles, cancelable, composed });\n }\n\n const result = el.dispatchEvent(event);\n\n // Flush any effects triggered by event handlers\n flushEffects();\n\n return result;\n}\n\n// ============================================================================\n// waitFor\n// ============================================================================\n\n/**\n * Waits for a predicate to return `true`, polling at a configurable interval.\n *\n * Useful for asserting conditions that depend on asynchronous operations,\n * timers, or deferred reactive updates.\n *\n * @param predicate - A function that returns `true` when the condition is met\n * @param options - Timeout and interval configuration\n * @returns A promise that resolves when the predicate returns `true`\n * @throws {Error} If the predicate does not return `true` within the timeout\n *\n * @example\n * ```ts\n * import { waitFor } from '@bquery/bquery/testing';\n *\n * await waitFor(() => document.querySelector('.loaded') !== null, {\n * timeout: 2000,\n * });\n * ```\n */\nexport async function waitFor(\n predicate: () => boolean | Promise<boolean>,\n options: WaitForOptions = {}\n): Promise<void> {\n if (typeof predicate !== 'function') {\n throw new Error('bQuery testing: waitFor requires a predicate function');\n }\n\n const { timeout = 1000, interval = 10 } = options;\n const start = Date.now();\n\n while (true) {\n try {\n const result = await predicate();\n if (result) return;\n } catch {\n // Predicate threw — treat as not-yet-met and keep polling\n }\n\n if (Date.now() - start >= timeout) {\n throw new Error(\n `bQuery testing: waitFor timed out after ${timeout}ms — predicate never returned true`\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n}\n"],"mappings":";;;AA4BA,IAAM,IAAA,CAAc,MAClB,MAAS,WACP,KAAQ,OAAO,KAAQ,OACtB,KAAQ,OAAO,KAAQ,OACvB,KAAQ,OAAO,KAAQ,OACxB,MAAS,MAEP,IAAA,CACJ,GACA,MACoD;AACpD,MAAI,IAAQ,GACR,IAAa,IACb,IAAI,IAAa;AAErB,SAAO,IAAI,EAAQ,UAAQ;AACzB,UAAM,IAAO,EAAQ,CAAA;AAErB,QAAI,MAAS,QAAQ,IAAI,IAAI,EAAQ,QAAQ;AAC3C,MAAA,KAAc,IAAO,EAAQ,IAAI,CAAA,GACjC,KAAK;AACL;AAAA;AAGF,QAAI,MAAS,IACX,CAAA;AAAA,aACS,MAAS,QAClB,KACI,MAAU;AACZ,aAAO;AAAA,QAAE,YAAA;AAAA,QAAY,UAAU,IAAI;AAAA;AAIvC,IAAA,KAAc,GACd;AAAA;AAGF,SAAO;GAGH,IAA4B,oBAAI,IAAA,GAEhC,IAAA,CAA2B,MAA+B;AAC9D,QAAM,IAAa,EAA6B,CAAA,GAC1C,IAAS,EAA0B,IAAI,CAAA;AAC7C,MAAI,EACF,QAAO;AAGT,QAAM,IAAW,IAAI,OAAO,OAAO,CAAA,IAAW;AAC9C,SAAA,EAA0B,IAAI,GAAY,CAAA,GACnC;;AA2BT,SAAgB,EACd,GACA,IAAkC,CAAA,GACpB;AACd,MAAI,CAAC,KAAW,CAAC,EAAQ,SAAS,GAAA,EAChC,OAAM,IAAI,MACR,oBAAoB,CAAA,kEAAQ;AAIhC,QAAM,EAAE,OAAA,GAAO,OAAA,GAAO,WAAA,IAAY,SAAS,KAAA,IAAS,GAE9C,IAAK,SAAS,cAAc,CAAA;AAGlC,MAAI,EACF,YAAW,CAAC,GAAK,CAAA,KAAU,OAAO,QAAQ,CAAA;AACxC,IAAI,KAAU,QACd,EAAG,aAAa,GAAK,OAAO,CAAA,CAAM;AAKtC,MAAI,EACF,KAAI,OAAO,KAAU,SACnB,CAAA,EAAG,YAAY;AAAA,OACV;AACL,UAAM,IAAkB,CAAA;AACxB,eAAW,CAAC,GAAU,CAAA,KAAS,OAAO,QAAQ,CAAA,EAC5C,KAAI,MAAa,UACf,CAAA,EAAM,KAAK,CAAA;AAAA,SACN;AACL,YAAM,IAAe,EAClB,QAAQ,MAAM,OAAA,EACd,QAAQ,MAAM,QAAA,EACd,QAAQ,MAAM,MAAA,EACd,QAAQ,MAAM,MAAA;AACjB,MAAA,EAAM,KAAK,cAAc,CAAA,KAAiB,CAAA,QAAK;AAAA;AAGnD,IAAA,EAAG,YAAY,EAAM,KAAK,EAAA;AAAA;AAK9B,SAAA,EAAU,YAAY,CAAA,GAQf;AAAA,IAAE,IAAA;AAAA,IAAI,SANP,MAAsB;AAC1B,MAAI,EAAG,cACL,EAAG,WAAW,YAAY,CAAA;AAAA;;;AAmChC,SAAgB,IAAqB;AAInC,EAAA,EAAA,MAAY;AAAA,EAAA,CAAA;;AA8Bd,SAAgB,EAAc,GAAgC;AAC5D,QAAM,IAAI,EAAO,CAAA;AAMjB,gBAAO,eAAe,GAAG,gBAAgB;AAAA,IACvC,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,GACb,GAED,EAAE,MAAM,SAAU,GAAgB;AAChC,IAAA,EAAE,QAAQ;AAAA,KAGZ,EAAE,QAAQ,WAAkB;AAC1B,IAAA,EAAE,QAAQ;AAAA,KAGL;;AAWT,SAAS,EACP,GACA,GAC0E;AAC1E,MAAI,IAAU;AAGd,EAAI,KAAQ,EAAQ,WAAW,CAAA,MAC7B,IAAU,EAAQ,MAAM,EAAK,MAAA,KAAW;AAI1C,MAAI,IAAO;AACX,QAAM,IAAU,EAAQ,QAAQ,GAAA;AAChC,EAAI,KAAW,MACb,IAAO,EAAQ,MAAM,IAAU,CAAA,GAC/B,IAAU,EAAQ,MAAM,GAAG,CAAA;AAI7B,QAAM,IAA2C,CAAA,GAC3C,IAAO,EAAQ,QAAQ,GAAA;AAC7B,MAAI,KAAQ,GAAG;AACb,UAAM,IAAK,EAAQ,MAAM,IAAO,CAAA;AAChC,IAAA,IAAU,EAAQ,MAAM,GAAG,CAAA;AAC3B,eAAW,KAAQ,EAAG,MAAM,GAAA,GAAM;AAChC,YAAM,IAAQ,EAAK,QAAQ,GAAA,GACrB,IAAM,KAAS,IAAI,mBAAmB,EAAK,MAAM,GAAG,CAAA,CAAM,IAAI,mBAAmB,CAAA,GACjF,IAAM,KAAS,IAAI,mBAAmB,EAAK,MAAM,IAAQ,CAAA,CAAE,IAAI,IAC/D,IAAW,EAAM,CAAA;AACvB,MAAI,MAAa,SACX,MAAM,QAAQ,CAAA,IAChB,EAAS,KAAK,CAAA,IAEd,EAAM,CAAA,IAAO,CAAC,GAAU,CAAA,IAG1B,EAAM,CAAA,IAAO;AAAA;;AAKnB,SAAO;AAAA,IAAE,MAAM,KAAW;AAAA,IAAK,OAAA;AAAA,IAAO,MAAA;AAAA;;AAOxC,SAAS,EACP,GACA,GACyE;AACzE,aAAW,KAAS,GAAQ;AAC1B,UAAM,IAAS,EAAkB,EAAM,MAAM,CAAA;AAC7C,QAAI,EACF,QAAO;AAAA,MAAE,SAAS;AAAA,MAAO,QAAA;AAAA;;AAG7B,SAAO;AAAA,IAAE,SAAS;AAAA,IAAM,QAAQ,CAAA;AAAA;;AAOlC,SAAS,EAAkB,GAAiB,GAA6C;AACvF,MAAI,MAAY,IACd,QAAO,CAAA;AAKT,QAAM,IAAO,oBAAI,IAAA,GAEX,IAAA,CAAuB,GAAe,MAA+B;AACzE,UAAM,IAAa,EAAM,QAAQ,KAAK,CAAA;AACtC,WAAO,MAAe,KAAK,EAAM,SAAS;AAAA,KAGtC,IAAA,CAAa,GAAsB,MAAqD;AAC5F,UAAM,IAAU,GAAG,CAAA,IAAgB,CAAA;AACnC,QAAI,EAAK,IAAI,CAAA,EACX,QAAO,EAAK,IAAI,CAAA,KAAY;AAG9B,QAAI,MAAiB,EAAQ,QAAQ;AACnC,YAAM,IAAS,MAAc,EAAK,SAAS,CAAA,IAAK;AAChD,aAAA,EAAK,IAAI,GAAS,CAAA,GACX;AAAA;AAGT,UAAM,IAAc,EAAQ,CAAA;AAE5B,QAAI,MAAgB,KAAK;AACvB,eAAS,IAAe,EAAK,QAAQ,KAAgB,GAAW,KAAgB;AAC9E,cAAM,IAAc,EAAU,IAAe,GAAG,CAAA;AAChD,YAAI;AACF,iBAAA,EAAK,IAAI,GAAS,CAAA,GACX;AAAA;AAIX,aAAA,EAAK,IAAI,GAAS,IAAA,GACX;AAAA;AAGT,QAAI,MAAgB,OAAO,EAAW,EAAQ,IAAe,CAAA,CAAA,GAAK;AAChE,UAAI,IAAU,IAAe;AAC7B,aAAO,IAAU,EAAQ,UAAU,EAAW,EAAQ,CAAA,CAAA,IACpD,CAAA;AAGF,YAAM,IAAO,EAAQ,MAAM,IAAe,GAAG,CAAA;AAC7C,UAAI,IAAmB,GACnB,GACA,IAAW;AAEf,UAAI,EAAQ,CAAA,MAAa,KAAK;AAC5B,cAAM,IAAmB,EAAoB,GAAS,CAAA;AACtD,QAAI,MACF,IAAa,EAAiB,YAC9B,IAAmB,EAAiB;AAAA;AAIxC,MAAI,EAAQ,CAAA,MAAsB,QAChC,IAAW,IACX;AAGF,YAAM,IAAiB,KAEnB,IADA,EAAK,SAGH,EAAoB,GAAM,CAAA;AAEhC,eAAS,IAAe,GAAgB,IAAe,GAAW,KAAgB;AAChF,cAAM,IAAiB,EAAK,MAAM,GAAW,CAAA;AAC7C,YAAI,KAEE,CADoB,EAAwB,CAAA,EAC3B,KAAK,CAAA;AACxB;AAIJ,cAAM,IAAc,EAAU,GAAkB,CAAA;AAChD,YAAI,GAAa;AACf,gBAAM,IAAS;AAAA,aACZ,CAAA,GAAO;AAAA,YACR,GAAG;AAAA;AAEL,iBAAA,EAAK,IAAI,GAAS,CAAA,GACX;AAAA;;AAIX,aAAA,EAAK,IAAI,GAAS,IAAA,GACX;AAAA;AAGT,QAAI,KAAa,EAAK,UAAU,MAAgB,EAAK,CAAA;AACnD,aAAA,EAAK,IAAI,GAAS,IAAA,GACX;AAGT,UAAM,IAAS,EAAU,IAAe,GAAG,IAAY,CAAA;AACvD,WAAA,EAAK,IAAI,GAAS,CAAA,GACX;AAAA;AAGT,SAAO,EAAU,GAAG,CAAA;;AA+BtB,SAAgB,EAAW,IAA6B,CAAA,GAAgB;AACtE,QAAM,IAAS,EAAQ,UAAU,CAAC;AAAA,IAAE,MAAM;AAAA,IAAK,WAAA,MAAiB;AAAA,GAAM,GAChE,IAAO,EAAQ,QAAQ,IACvB,IAAc,EAAQ,eAAe,KAErC,IAAA,CAAgB,MAAgC;AACpD,UAAM,EAAE,MAAA,GAAM,OAAA,GAAO,MAAA,EAAA,IAAS,EAAU,GAAU,CAAA,GAC5C,EAAE,SAAA,GAAS,QAAA,EAAA,IAAW,EAAW,GAAM,CAAA;AAC7C,WAAO;AAAA,MAAE,MAAA;AAAA,MAAM,QAAA;AAAA,MAAQ,OAAA;AAAA,MAAO,SAAA;AAAA,MAAS,MAAA;AAAA;KAGnC,IAAc,EAAkB,EAAa,CAAA,CAAY;AAE/D,SAAO;AAAA,IACL,KAAK,GAAoB;AACvB,MAAA,EAAY,QAAQ,EAAa,CAAA;AAAA;IAEnC,QAAQ,GAAoB;AAC1B,MAAA,EAAY,QAAQ,EAAa,CAAA;AAAA;IAEnC,IAAI,eAAkC;AACpC,aAAO;AAAA;IAET,IAAI,SAAgC;AAClC,aAAO;AAAA;IAET,UAAgB;AACd,MAAA,EAAY,QAAA;AAAA;;;AA+BlB,SAAgB,EAAU,GAAa,GAAmB,IAA4B,CAAA,GAAa;AACjG,MAAI,CAAC,EACH,OAAM,IAAI,MAAM,oDAAA;AAElB,MAAI,CAAC,EACH,OAAM,IAAI,MAAM,kDAAA;AAGlB,QAAM,EAAE,SAAA,IAAU,IAAM,YAAA,IAAa,IAAM,UAAA,IAAW,IAAM,QAAA,EAAA,IAAW;AAEvE,MAAI;AACJ,EAAI,MAAW,SACb,IAAQ,IAAI,YAAY,GAAW;AAAA,IAAE,SAAA;AAAA,IAAS,YAAA;AAAA,IAAY,UAAA;AAAA,IAAU,QAAA;AAAA,GAAQ,IAE5E,IAAQ,IAAI,MAAM,GAAW;AAAA,IAAE,SAAA;AAAA,IAAS,YAAA;AAAA,IAAY,UAAA;AAAA,GAAU;AAGhE,QAAM,IAAS,EAAG,cAAc,CAAA;AAGhC,SAAA,EAAA,GAEO;;AA2BT,eAAsB,EACpB,GACA,IAA0B,CAAA,GACX;AACf,MAAI,OAAO,KAAc,WACvB,OAAM,IAAI,MAAM,uDAAA;AAGlB,QAAM,EAAE,SAAA,IAAU,KAAM,UAAA,IAAW,GAAA,IAAO,GACpC,IAAQ,KAAK,IAAA;AAEnB,aAAa;AACX,QAAI;AAEF,UAAI,MADiB,EAAA,EACT;AAAA,YACN;AAAA,IAAA;AAIR,QAAI,KAAK,IAAA,IAAQ,KAAS,EACxB,OAAM,IAAI,MACR,2CAA2C,CAAA,oCAAQ;AAIvD,UAAM,IAAI,QAAA,CAAS,MAAY,WAAW,GAAS,CAAA,CAAS;AAAA"}
1
+ {"version":3,"file":"testing-ByjwS2_D.js","names":[],"sources":["../src/testing/testing.ts"],"sourcesContent":["/**\n * Testing utilities for bQuery.js.\n *\n * Provides helpers for mounting components, controlling signals, mocking\n * the router, dispatching events, and asserting async conditions — all\n * designed for use with `bun:test` and happy-dom.\n *\n * @module bquery/testing\n */\n\nimport { batch, Signal, signal } from '../reactive/index';\nimport { getNormalizedRouteConstraint } from '../router/constraints';\nimport type {\n FireEventOptions,\n MockRouteDefinition,\n MockRouter,\n MockRouterOptions,\n MockSignal,\n RenderComponentOptions,\n RenderResult,\n TestRoute,\n WaitForOptions,\n} from './types';\n\n// ============================================================================\n// renderComponent\n// ============================================================================\n\nconst isWordChar = (char: string | undefined): boolean =>\n char !== undefined &&\n ((char >= 'a' && char <= 'z') ||\n (char >= 'A' && char <= 'Z') ||\n (char >= '0' && char <= '9') ||\n char === '_');\n\nconst readRouteConstraint = (\n pattern: string,\n startIndex: number\n): { constraint: string; endIndex: number } | null => {\n let depth = 1;\n let constraint = '';\n let i = startIndex + 1;\n\n while (i < pattern.length) {\n const char = pattern[i];\n\n if (char === '\\\\' && i + 1 < pattern.length) {\n constraint += char + pattern[i + 1];\n i += 2;\n continue;\n }\n\n if (char === '(') {\n depth++;\n } else if (char === ')') {\n depth--;\n if (depth === 0) {\n return { constraint, endIndex: i + 1 };\n }\n }\n\n constraint += char;\n i++;\n }\n\n return null;\n};\n\nconst routeConstraintRegexCache = new Map<string, RegExp>();\n\nconst getRouteConstraintRegex = (constraint: string): RegExp => {\n const normalized = getNormalizedRouteConstraint(constraint);\n const cached = routeConstraintRegexCache.get(normalized);\n if (cached) {\n return cached;\n }\n\n const compiled = new RegExp(`^(?:${normalized})$`);\n routeConstraintRegexCache.set(normalized, compiled);\n return compiled;\n};\n\n/**\n * Mounts a custom element by tag name for testing and returns a handle\n * to interact with it.\n *\n * The element is created, configured with the given props and slots,\n * and appended to the container (defaults to `document.body`). Call\n * `unmount()` to remove the element and trigger its `disconnectedCallback`.\n *\n * @param tagName - The custom element tag name (must already be registered)\n * @param options - Props, slots, and container configuration\n * @returns A {@link RenderResult} with the element and an unmount function\n * @throws {Error} If the tag name is not a valid custom element name\n *\n * @example\n * ```ts\n * import { renderComponent } from '@bquery/bquery/testing';\n *\n * const { el, unmount } = renderComponent('my-counter', {\n * props: { start: '5' },\n * });\n * expect(el.shadowRoot?.textContent).toContain('5');\n * unmount();\n * ```\n */\nexport function renderComponent(\n tagName: string,\n options: RenderComponentOptions = {}\n): RenderResult {\n if (!tagName || !tagName.includes('-')) {\n throw new Error(\n `bQuery testing: \"${tagName}\" is not a valid custom element tag name (must contain a hyphen)`\n );\n }\n\n const { props, slots, container = document.body } = options;\n\n const el = document.createElement(tagName);\n\n // Set attributes (props) before connecting\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n if (value === null || value === undefined) continue;\n el.setAttribute(key, String(value));\n }\n }\n\n // Inject slot content before connecting so the component can discover it\n if (slots) {\n if (typeof slots === 'string') {\n el.innerHTML = slots;\n } else {\n const parts: string[] = [];\n for (const [slotName, html] of Object.entries(slots)) {\n if (slotName === 'default') {\n parts.push(html);\n } else {\n const safeSlotName = slotName\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n parts.push(`<div slot=\"${safeSlotName}\">${html}</div>`);\n }\n }\n el.innerHTML = parts.join('');\n }\n }\n\n // Connect — triggers connectedCallback\n container.appendChild(el);\n\n const unmount = (): void => {\n if (el.parentNode) {\n el.parentNode.removeChild(el);\n }\n };\n\n return { el, unmount };\n}\n\n// ============================================================================\n// flushEffects\n// ============================================================================\n\n/**\n * Synchronously flushes any pending reactive effects.\n *\n * In bQuery's reactive system, effects outside of a batch are executed\n * synchronously. This helper exists primarily for clarity and for\n * flushing effects that may have been deferred inside a batch.\n *\n * Internally it performs a no-op batch to trigger the flush of any\n * pending observers that were queued during a prior `batch()` call.\n *\n * @example\n * ```ts\n * import { signal, batch } from '@bquery/bquery/reactive';\n * import { flushEffects } from '@bquery/bquery/testing';\n *\n * const count = signal(0);\n * let observed = 0;\n * effect(() => { observed = count.value; });\n *\n * batch(() => { count.value = 42; });\n * flushEffects();\n * expect(observed).toBe(42);\n * ```\n */\nexport function flushEffects(): void {\n // A no-op batch triggers endBatch which flushes any pending observers.\n // Since bQuery's effects are synchronous outside of batches, this is\n // mainly useful after manual batch calls or micro-task boundaries.\n batch(() => {\n /* intentionally empty — triggers pending observer flush */\n });\n}\n\n// ============================================================================\n// mockSignal\n// ============================================================================\n\n/**\n * Creates a controllable signal for tests with `set()` and `reset()` helpers.\n *\n * This is a thin wrapper around `signal()` that records the initial value\n * and adds explicit `set()` / `reset()` methods for clearer test intent.\n *\n * @template T - The type of the signal value\n * @param initialValue - The initial value\n * @returns A {@link MockSignal} instance\n *\n * @example\n * ```ts\n * import { mockSignal } from '@bquery/bquery/testing';\n *\n * const count = mockSignal(0);\n * count.set(5);\n * expect(count.value).toBe(5);\n * count.reset();\n * expect(count.value).toBe(0);\n * ```\n */\nexport function mockSignal<T>(initialValue: T): MockSignal<T> {\n const s = signal(initialValue) as Signal<T> & {\n set: (value: T) => void;\n reset: () => void;\n initialValue: T;\n };\n\n Object.defineProperty(s, 'initialValue', {\n value: initialValue,\n writable: false,\n enumerable: true,\n });\n\n s.set = function (value: T): void {\n s.value = value;\n };\n\n s.reset = function (): void {\n s.value = initialValue;\n };\n\n return s as MockSignal<T>;\n}\n\n// ============================================================================\n// mockRouter\n// ============================================================================\n\n/**\n * Parses a path string into the route's `path`, `query`, and `hash` parts.\n * @internal\n */\nfunction parsePath(\n fullPath: string,\n base: string\n): { path: string; query: Record<string, string | string[]>; hash: string } {\n let working = fullPath;\n\n // Strip base prefix\n if (base && working.startsWith(base)) {\n working = working.slice(base.length) || '/';\n }\n\n // Extract hash\n let hash = '';\n const hashIdx = working.indexOf('#');\n if (hashIdx >= 0) {\n hash = working.slice(hashIdx + 1);\n working = working.slice(0, hashIdx);\n }\n\n // Extract query string\n const query: Record<string, string | string[]> = {};\n const qIdx = working.indexOf('?');\n if (qIdx >= 0) {\n const qs = working.slice(qIdx + 1);\n working = working.slice(0, qIdx);\n for (const pair of qs.split('&')) {\n const eqIdx = pair.indexOf('=');\n const key = eqIdx >= 0 ? decodeURIComponent(pair.slice(0, eqIdx)) : decodeURIComponent(pair);\n const val = eqIdx >= 0 ? decodeURIComponent(pair.slice(eqIdx + 1)) : '';\n const existing = query[key];\n if (existing !== undefined) {\n if (Array.isArray(existing)) {\n existing.push(val);\n } else {\n query[key] = [existing, val];\n }\n } else {\n query[key] = val;\n }\n }\n }\n\n return { path: working || '/', query, hash };\n}\n\n/**\n * Matches a path against a route definition, extracting params.\n * @internal\n */\nfunction matchRoute(\n path: string,\n routes: MockRouteDefinition[]\n): { matched: MockRouteDefinition | null; params: Record<string, string> } {\n for (const route of routes) {\n const params = matchRoutePattern(route.path, path);\n if (params) {\n return { matched: route, params };\n }\n }\n return { matched: null, params: {} };\n}\n\n/**\n * Builds param matches from a route path pattern without compiling the full path into a regex.\n * @internal\n */\nfunction matchRoutePattern(pattern: string, path: string): Record<string, string> | null {\n if (pattern === '*') {\n return {};\n }\n\n // Memoization keeps wildcard/param backtracking linear for repeated subproblems\n // within a single pattern/path match attempt.\n const memo = new Map<string, Record<string, string> | null>();\n\n const findSegmentBoundary = (value: string, startIndex: number): number => {\n const slashIndex = value.indexOf('/', startIndex);\n return slashIndex === -1 ? value.length : slashIndex;\n };\n\n const matchFrom = (patternIndex: number, pathIndex: number): Record<string, string> | null => {\n const memoKey = `${patternIndex}:${pathIndex}`;\n if (memo.has(memoKey)) {\n return memo.get(memoKey) ?? null;\n }\n\n if (patternIndex === pattern.length) {\n const result = pathIndex === path.length ? {} : null;\n memo.set(memoKey, result);\n return result;\n }\n\n const patternChar = pattern[patternIndex];\n\n if (patternChar === '*') {\n for (let candidateEnd = path.length; candidateEnd >= pathIndex; candidateEnd--) {\n const suffixMatch = matchFrom(patternIndex + 1, candidateEnd);\n if (suffixMatch) {\n memo.set(memoKey, suffixMatch);\n return suffixMatch;\n }\n }\n\n memo.set(memoKey, null);\n return null;\n }\n\n if (patternChar === ':' && isWordChar(pattern[patternIndex + 1])) {\n let nameEnd = patternIndex + 2;\n while (nameEnd < pattern.length && isWordChar(pattern[nameEnd])) {\n nameEnd++;\n }\n\n const name = pattern.slice(patternIndex + 1, nameEnd);\n let nextPatternIndex = nameEnd;\n let constraint: string | undefined;\n let catchAll = false;\n\n if (pattern[nameEnd] === '(') {\n const parsedConstraint = readRouteConstraint(pattern, nameEnd);\n if (parsedConstraint) {\n constraint = parsedConstraint.constraint;\n nextPatternIndex = parsedConstraint.endIndex;\n }\n }\n\n if (pattern[nextPatternIndex] === '*') {\n catchAll = true;\n nextPatternIndex++;\n }\n\n const candidateLimit = catchAll\n ? path.length\n : constraint\n ? path.length\n : findSegmentBoundary(path, pathIndex);\n\n for (let candidateEnd = candidateLimit; candidateEnd > pathIndex; candidateEnd--) {\n const candidateValue = path.slice(pathIndex, candidateEnd);\n if (constraint) {\n const constraintRegex = getRouteConstraintRegex(constraint);\n if (!constraintRegex.test(candidateValue)) {\n continue;\n }\n }\n\n const suffixMatch = matchFrom(nextPatternIndex, candidateEnd);\n if (suffixMatch) {\n const result = {\n [name]: candidateValue,\n ...suffixMatch,\n };\n memo.set(memoKey, result);\n return result;\n }\n }\n\n memo.set(memoKey, null);\n return null;\n }\n\n if (pathIndex >= path.length || patternChar !== path[pathIndex]) {\n memo.set(memoKey, null);\n return null;\n }\n\n const result = matchFrom(patternIndex + 1, pathIndex + 1);\n memo.set(memoKey, result);\n return result;\n };\n\n return matchFrom(0, 0);\n}\n\n/**\n * Creates a lightweight mock router for testing that does not interact\n * with the browser History API.\n *\n * The mock router provides a reactive `currentRoute` signal that updates\n * when `push()` or `replace()` is called, making it ideal for testing\n * components or logic that depend on route state.\n *\n * @param options - Mock router configuration\n * @returns A {@link MockRouter} instance\n *\n * @example\n * ```ts\n * import { mockRouter } from '@bquery/bquery/testing';\n *\n * const router = mockRouter({\n * routes: [\n * { path: '/', component: () => null },\n * { path: '/user/:id', component: () => null },\n * ],\n * initialPath: '/',\n * });\n *\n * router.push('/user/42');\n * expect(router.currentRoute.value.params.id).toBe('42');\n * router.destroy();\n * ```\n */\nexport function mockRouter(options: MockRouterOptions = {}): MockRouter {\n const routes = options.routes ?? [{ path: '*', component: () => null }];\n const base = options.base ?? '';\n const initialPath = options.initialPath ?? '/';\n\n const resolveRoute = (fullPath: string): TestRoute => {\n const { path, query, hash } = parsePath(fullPath, base);\n const { matched, params } = matchRoute(path, routes);\n return { path, params, query, matched, hash };\n };\n\n const routeSignal = signal<TestRoute>(resolveRoute(initialPath));\n\n return {\n push(path: string): void {\n routeSignal.value = resolveRoute(path);\n },\n replace(path: string): void {\n routeSignal.value = resolveRoute(path);\n },\n get currentRoute(): Signal<TestRoute> {\n return routeSignal;\n },\n get routes(): MockRouteDefinition[] {\n return routes;\n },\n destroy(): void {\n routeSignal.dispose();\n },\n };\n}\n\n// ============================================================================\n// fireEvent\n// ============================================================================\n\n/**\n * Dispatches a synthetic event on an element and flushes pending effects.\n *\n * By default the event bubbles, is cancelable, and is composed (crosses\n * shadow DOM boundaries). Pass a `detail` option to create a `CustomEvent`.\n *\n * @param el - The target element\n * @param eventName - The event type (e.g. 'click', 'input', 'my-event')\n * @param options - Event configuration\n * @returns `true` if the event was not cancelled\n *\n * @example\n * ```ts\n * import { fireEvent } from '@bquery/bquery/testing';\n *\n * const button = document.createElement('button');\n * let clicked = false;\n * button.addEventListener('click', () => { clicked = true; });\n * fireEvent(button, 'click');\n * expect(clicked).toBe(true);\n * ```\n */\nexport function fireEvent(el: Element, eventName: string, options: FireEventOptions = {}): boolean {\n if (!el) {\n throw new Error('bQuery testing: fireEvent requires a valid element');\n }\n if (!eventName) {\n throw new Error('bQuery testing: fireEvent requires an event name');\n }\n\n const { bubbles = true, cancelable = true, composed = true, detail } = options;\n\n let event: Event;\n if (detail !== undefined) {\n event = new CustomEvent(eventName, { bubbles, cancelable, composed, detail });\n } else {\n event = new Event(eventName, { bubbles, cancelable, composed });\n }\n\n const result = el.dispatchEvent(event);\n\n // Flush any effects triggered by event handlers\n flushEffects();\n\n return result;\n}\n\n// ============================================================================\n// waitFor\n// ============================================================================\n\n/**\n * Waits for a predicate to return `true`, polling at a configurable interval.\n *\n * Useful for asserting conditions that depend on asynchronous operations,\n * timers, or deferred reactive updates.\n *\n * @param predicate - A function that returns `true` when the condition is met\n * @param options - Timeout and interval configuration\n * @returns A promise that resolves when the predicate returns `true`\n * @throws {Error} If the predicate does not return `true` within the timeout\n *\n * @example\n * ```ts\n * import { waitFor } from '@bquery/bquery/testing';\n *\n * await waitFor(() => document.querySelector('.loaded') !== null, {\n * timeout: 2000,\n * });\n * ```\n */\nexport async function waitFor(\n predicate: () => boolean | Promise<boolean>,\n options: WaitForOptions = {}\n): Promise<void> {\n if (typeof predicate !== 'function') {\n throw new Error('bQuery testing: waitFor requires a predicate function');\n }\n\n const { timeout = 1000, interval = 10 } = options;\n const start = Date.now();\n\n while (true) {\n try {\n const result = await predicate();\n if (result) return;\n } catch {\n // Predicate threw — treat as not-yet-met and keep polling\n }\n\n if (Date.now() - start >= timeout) {\n throw new Error(\n `bQuery testing: waitFor timed out after ${timeout}ms — predicate never returned true`\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n}\n"],"mappings":";;;AA4BA,IAAM,IAAA,CAAc,MAClB,MAAS,WACP,KAAQ,OAAO,KAAQ,OACtB,KAAQ,OAAO,KAAQ,OACvB,KAAQ,OAAO,KAAQ,OACxB,MAAS,MAEP,IAAA,CACJ,GACA,MACoD;AACpD,MAAI,IAAQ,GACR,IAAa,IACb,IAAI,IAAa;AAErB,SAAO,IAAI,EAAQ,UAAQ;AACzB,UAAM,IAAO,EAAQ,CAAA;AAErB,QAAI,MAAS,QAAQ,IAAI,IAAI,EAAQ,QAAQ;AAC3C,MAAA,KAAc,IAAO,EAAQ,IAAI,CAAA,GACjC,KAAK;AACL;AAAA,IACF;AAEA,QAAI,MAAS,IACX,CAAA;AAAA,aACS,MAAS,QAClB,KACI,MAAU;AACZ,aAAO;AAAA,QAAE,YAAA;AAAA,QAAY,UAAU,IAAI;AAAA,MAAE;AAIzC,IAAA,KAAc,GACd;AAAA,EACF;AAEA,SAAO;AACT,GAEM,IAA4B,oBAAI,IAAoB,GAEpD,IAAA,CAA2B,MAA+B;AAC9D,QAAM,IAAa,EAA6B,CAAU,GACpD,IAAS,EAA0B,IAAI,CAAU;AACvD,MAAI,EACF,QAAO;AAGT,QAAM,IAAW,IAAI,OAAO,OAAO,CAAA,IAAc;AACjD,SAAA,EAA0B,IAAI,GAAY,CAAQ,GAC3C;AACT;AA0BA,SAAgB,EACd,GACA,IAAkC,CAAC,GACrB;AACd,MAAI,CAAC,KAAW,CAAC,EAAQ,SAAS,GAAG,EACnC,OAAM,IAAI,MACR,oBAAoB,CAAA,kEACtB;AAGF,QAAM,EAAE,OAAA,GAAO,OAAA,GAAO,WAAA,IAAY,SAAS,KAAA,IAAS,GAE9C,IAAK,SAAS,cAAc,CAAO;AAGzC,MAAI,EACF,YAAW,CAAC,GAAK,CAAA,KAAU,OAAO,QAAQ,CAAK;AAC7C,IAAI,KAAU,QACd,EAAG,aAAa,GAAK,OAAO,CAAK,CAAC;AAKtC,MAAI,EACF,KAAI,OAAO,KAAU,SACnB,CAAA,EAAG,YAAY;AAAA,OACV;AACL,UAAM,IAAkB,CAAC;AACzB,eAAW,CAAC,GAAU,CAAA,KAAS,OAAO,QAAQ,CAAK,EACjD,KAAI,MAAa,UACf,CAAA,EAAM,KAAK,CAAI;AAAA,SACV;AACL,YAAM,IAAe,EAClB,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM;AACvB,MAAA,EAAM,KAAK,cAAc,CAAA,KAAiB,CAAA,QAAY;AAAA,IACxD;AAEF,IAAA,EAAG,YAAY,EAAM,KAAK,EAAE;AAAA,EAC9B;AAIF,SAAA,EAAU,YAAY,CAAE,GAQjB;AAAA,IAAE,IAAA;AAAA,IAAI,SANP,MAAsB;AAC1B,MAAI,EAAG,cACL,EAAG,WAAW,YAAY,CAAE;AAAA,IAEhC;AAAA,EAEqB;AACvB;AA8BA,SAAgB,IAAqB;AAInC,EAAA,EAAA,MAAY;AAAA,EAEZ,CAAC;AACH;AA2BA,SAAgB,EAAc,GAAgC;AAC5D,QAAM,IAAI,EAAO,CAAY;AAM7B,gBAAO,eAAe,GAAG,gBAAgB;AAAA,IACvC,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,EACd,CAAC,GAED,EAAE,MAAM,SAAU,GAAgB;AAChC,IAAA,EAAE,QAAQ;AAAA,EACZ,GAEA,EAAE,QAAQ,WAAkB;AAC1B,IAAA,EAAE,QAAQ;AAAA,EACZ,GAEO;AACT;AAUA,SAAS,EACP,GACA,GAC0E;AAC1E,MAAI,IAAU;AAGd,EAAI,KAAQ,EAAQ,WAAW,CAAI,MACjC,IAAU,EAAQ,MAAM,EAAK,MAAM,KAAK;AAI1C,MAAI,IAAO;AACX,QAAM,IAAU,EAAQ,QAAQ,GAAG;AACnC,EAAI,KAAW,MACb,IAAO,EAAQ,MAAM,IAAU,CAAC,GAChC,IAAU,EAAQ,MAAM,GAAG,CAAO;AAIpC,QAAM,IAA2C,CAAC,GAC5C,IAAO,EAAQ,QAAQ,GAAG;AAChC,MAAI,KAAQ,GAAG;AACb,UAAM,IAAK,EAAQ,MAAM,IAAO,CAAC;AACjC,IAAA,IAAU,EAAQ,MAAM,GAAG,CAAI;AAC/B,eAAW,KAAQ,EAAG,MAAM,GAAG,GAAG;AAChC,YAAM,IAAQ,EAAK,QAAQ,GAAG,GACxB,IAAM,KAAS,IAAI,mBAAmB,EAAK,MAAM,GAAG,CAAK,CAAC,IAAI,mBAAmB,CAAI,GACrF,IAAM,KAAS,IAAI,mBAAmB,EAAK,MAAM,IAAQ,CAAC,CAAC,IAAI,IAC/D,IAAW,EAAM,CAAA;AACvB,MAAI,MAAa,SACX,MAAM,QAAQ,CAAQ,IACxB,EAAS,KAAK,CAAG,IAEjB,EAAM,CAAA,IAAO,CAAC,GAAU,CAAG,IAG7B,EAAM,CAAA,IAAO;AAAA,IAEjB;AAAA,EACF;AAEA,SAAO;AAAA,IAAE,MAAM,KAAW;AAAA,IAAK,OAAA;AAAA,IAAO,MAAA;AAAA,EAAK;AAC7C;AAMA,SAAS,EACP,GACA,GACyE;AACzE,aAAW,KAAS,GAAQ;AAC1B,UAAM,IAAS,EAAkB,EAAM,MAAM,CAAI;AACjD,QAAI,EACF,QAAO;AAAA,MAAE,SAAS;AAAA,MAAO,QAAA;AAAA,IAAO;AAAA,EAEpC;AACA,SAAO;AAAA,IAAE,SAAS;AAAA,IAAM,QAAQ,CAAC;AAAA,EAAE;AACrC;AAMA,SAAS,EAAkB,GAAiB,GAA6C;AACvF,MAAI,MAAY,IACd,QAAO,CAAC;AAKV,QAAM,IAAO,oBAAI,IAA2C,GAEtD,IAAA,CAAuB,GAAe,MAA+B;AACzE,UAAM,IAAa,EAAM,QAAQ,KAAK,CAAU;AAChD,WAAO,MAAe,KAAK,EAAM,SAAS;AAAA,EAC5C,GAEM,IAAA,CAAa,GAAsB,MAAqD;AAC5F,UAAM,IAAU,GAAG,CAAA,IAAgB,CAAA;AACnC,QAAI,EAAK,IAAI,CAAO,EAClB,QAAO,EAAK,IAAI,CAAO,KAAK;AAG9B,QAAI,MAAiB,EAAQ,QAAQ;AACnC,YAAM,IAAS,MAAc,EAAK,SAAS,CAAC,IAAI;AAChD,aAAA,EAAK,IAAI,GAAS,CAAM,GACjB;AAAA,IACT;AAEA,UAAM,IAAc,EAAQ,CAAA;AAE5B,QAAI,MAAgB,KAAK;AACvB,eAAS,IAAe,EAAK,QAAQ,KAAgB,GAAW,KAAgB;AAC9E,cAAM,IAAc,EAAU,IAAe,GAAG,CAAY;AAC5D,YAAI;AACF,iBAAA,EAAK,IAAI,GAAS,CAAW,GACtB;AAAA,MAEX;AAEA,aAAA,EAAK,IAAI,GAAS,IAAI,GACf;AAAA,IACT;AAEA,QAAI,MAAgB,OAAO,EAAW,EAAQ,IAAe,CAAA,CAAE,GAAG;AAChE,UAAI,IAAU,IAAe;AAC7B,aAAO,IAAU,EAAQ,UAAU,EAAW,EAAQ,CAAA,CAAQ,IAC5D,CAAA;AAGF,YAAM,IAAO,EAAQ,MAAM,IAAe,GAAG,CAAO;AACpD,UAAI,IAAmB,GACnB,GACA,IAAW;AAEf,UAAI,EAAQ,CAAA,MAAa,KAAK;AAC5B,cAAM,IAAmB,EAAoB,GAAS,CAAO;AAC7D,QAAI,MACF,IAAa,EAAiB,YAC9B,IAAmB,EAAiB;AAAA,MAExC;AAEA,MAAI,EAAQ,CAAA,MAAsB,QAChC,IAAW,IACX;AAGF,YAAM,IAAiB,KAEnB,IADA,EAAK,SAGH,EAAoB,GAAM,CAAS;AAEzC,eAAS,IAAe,GAAgB,IAAe,GAAW,KAAgB;AAChF,cAAM,IAAiB,EAAK,MAAM,GAAW,CAAY;AACzD,YAAI,KAEE,CADoB,EAAwB,CAC3C,EAAgB,KAAK,CAAc;AACtC;AAIJ,cAAM,IAAc,EAAU,GAAkB,CAAY;AAC5D,YAAI,GAAa;AACf,gBAAM,IAAS;AAAA,aACZ,CAAA,GAAO;AAAA,YACR,GAAG;AAAA,UACL;AACA,iBAAA,EAAK,IAAI,GAAS,CAAM,GACjB;AAAA,QACT;AAAA,MACF;AAEA,aAAA,EAAK,IAAI,GAAS,IAAI,GACf;AAAA,IACT;AAEA,QAAI,KAAa,EAAK,UAAU,MAAgB,EAAK,CAAA;AACnD,aAAA,EAAK,IAAI,GAAS,IAAI,GACf;AAGT,UAAM,IAAS,EAAU,IAAe,GAAG,IAAY,CAAC;AACxD,WAAA,EAAK,IAAI,GAAS,CAAM,GACjB;AAAA,EACT;AAEA,SAAO,EAAU,GAAG,CAAC;AACvB;AA8BA,SAAgB,EAAW,IAA6B,CAAC,GAAe;AACtE,QAAM,IAAS,EAAQ,UAAU,CAAC;AAAA,IAAE,MAAM;AAAA,IAAK,WAAA,MAAiB;AAAA,EAAK,CAAC,GAChE,IAAO,EAAQ,QAAQ,IACvB,IAAc,EAAQ,eAAe,KAErC,IAAA,CAAgB,MAAgC;AACpD,UAAM,EAAE,MAAA,GAAM,OAAA,GAAO,MAAA,EAAA,IAAS,EAAU,GAAU,CAAI,GAChD,EAAE,SAAA,GAAS,QAAA,EAAA,IAAW,EAAW,GAAM,CAAM;AACnD,WAAO;AAAA,MAAE,MAAA;AAAA,MAAM,QAAA;AAAA,MAAQ,OAAA;AAAA,MAAO,SAAA;AAAA,MAAS,MAAA;AAAA,IAAK;AAAA,EAC9C,GAEM,IAAc,EAAkB,EAAa,CAAW,CAAC;AAE/D,SAAO;AAAA,IACL,KAAK,GAAoB;AACvB,MAAA,EAAY,QAAQ,EAAa,CAAI;AAAA,IACvC;AAAA,IACA,QAAQ,GAAoB;AAC1B,MAAA,EAAY,QAAQ,EAAa,CAAI;AAAA,IACvC;AAAA,IACA,IAAI,eAAkC;AACpC,aAAO;AAAA,IACT;AAAA,IACA,IAAI,SAAgC;AAClC,aAAO;AAAA,IACT;AAAA,IACA,UAAgB;AACd,MAAA,EAAY,QAAQ;AAAA,IACtB;AAAA,EACF;AACF;AA4BA,SAAgB,EAAU,GAAa,GAAmB,IAA4B,CAAC,GAAY;AACjG,MAAI,CAAC,EACH,OAAM,IAAI,MAAM,oDAAoD;AAEtE,MAAI,CAAC,EACH,OAAM,IAAI,MAAM,kDAAkD;AAGpE,QAAM,EAAE,SAAA,IAAU,IAAM,YAAA,IAAa,IAAM,UAAA,IAAW,IAAM,QAAA,EAAA,IAAW;AAEvE,MAAI;AACJ,EAAI,MAAW,SACb,IAAQ,IAAI,YAAY,GAAW;AAAA,IAAE,SAAA;AAAA,IAAS,YAAA;AAAA,IAAY,UAAA;AAAA,IAAU,QAAA;AAAA,EAAO,CAAC,IAE5E,IAAQ,IAAI,MAAM,GAAW;AAAA,IAAE,SAAA;AAAA,IAAS,YAAA;AAAA,IAAY,UAAA;AAAA,EAAS,CAAC;AAGhE,QAAM,IAAS,EAAG,cAAc,CAAK;AAGrC,SAAA,EAAa,GAEN;AACT;AA0BA,eAAsB,EACpB,GACA,IAA0B,CAAC,GACZ;AACf,MAAI,OAAO,KAAc,WACvB,OAAM,IAAI,MAAM,uDAAuD;AAGzE,QAAM,EAAE,SAAA,IAAU,KAAM,UAAA,IAAW,GAAA,IAAO,GACpC,IAAQ,KAAK,IAAI;AAEvB,aAAa;AACX,QAAI;AAEF,UAAI,MADiB,EAAU,EACnB;AAAA,IACd,QAAQ;AAAA,IAER;AAEA,QAAI,KAAK,IAAI,IAAI,KAAS,EACxB,OAAM,IAAI,MACR,2CAA2C,CAAA,oCAC7C;AAGF,UAAM,IAAI,QAAA,CAAS,MAAY,WAAW,GAAS,CAAQ,CAAC;AAAA,EAC9D;AACF"}
@@ -1,4 +1,4 @@
1
- import { a, i as r, n as e, o as s, r as t, t as n } from "./testing-CeMUwrRD.js";
1
+ import { a, i as r, n as e, o as s, r as t, t as n } from "./testing-ByjwS2_D.js";
2
2
  export {
3
3
  n as fireEvent,
4
4
  e as flushEffects,
@@ -1 +1 @@
1
- {"version":3,"file":"type-guards-BMX2c0LP.js","names":[],"sources":["../src/core/utils/type-guards.ts"],"sourcesContent":["/**\n * Type guard helpers.\n *\n * @module bquery/core/utils/type-guards\n */\n\n/**\n * Checks if a value is a DOM Element.\n *\n * @param value - The value to check\n * @returns True if the value is an Element\n */\nexport function isElement(value: unknown): value is Element {\n return typeof Element !== 'undefined' && value instanceof Element;\n}\n\n/**\n * Checks if a value is a BQueryCollection-like object.\n *\n * @param value - The value to check\n * @returns True if the value has an elements array property\n */\nexport function isCollection(value: unknown): value is { elements: Element[] } {\n return Boolean(value && typeof value === 'object' && 'elements' in (value as object));\n}\n\n/**\n * Checks if a value is a function.\n *\n * @param value - The value to check\n * @returns True if the value is a function\n */\nexport function isFunction(value: unknown): value is (...args: unknown[]) => unknown {\n return typeof value === 'function';\n}\n\n/**\n * Checks if a value is a string.\n *\n * @param value - The value to check\n * @returns True if the value is a string\n */\nexport function isString(value: unknown): value is string {\n return typeof value === 'string';\n}\n\n/**\n * Checks if a value is a number (excluding NaN).\n *\n * @param value - The value to check\n * @returns True if the value is a valid number\n */\nexport function isNumber(value: unknown): value is number {\n return typeof value === 'number' && !Number.isNaN(value);\n}\n\n/**\n * Checks if a value is a boolean.\n *\n * @param value - The value to check\n * @returns True if the value is a boolean\n */\nexport function isBoolean(value: unknown): value is boolean {\n return typeof value === 'boolean';\n}\n\n/**\n * Checks if a value is an array.\n *\n * @template T - The type of array elements\n * @param value - The value to check\n * @returns True if the value is an array\n */\nexport function isArray<T = unknown>(value: unknown): value is T[] {\n return Array.isArray(value);\n}\n\n/**\n * Checks if a value is a Date instance.\n *\n * @param value - The value to check\n * @returns True if the value is a Date\n */\nexport function isDate(value: unknown): value is Date {\n return value instanceof Date;\n}\n\n/**\n * Checks if a value is a Promise-like object.\n *\n * @param value - The value to check\n * @returns True if the value is a Promise-like object\n */\nexport function isPromise<T = unknown>(value: unknown): value is Promise<T> {\n return Boolean(\n value &&\n (value instanceof Promise ||\n (typeof value === 'object' &&\n 'then' in (value as object) &&\n typeof (value as { then?: unknown }).then === 'function'))\n );\n}\n\n/**\n * Checks if a value is a non-null object.\n *\n * @param value - The value to check\n * @returns True if the value is an object\n */\nexport function isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n"],"mappings":"AAYA,SAAgB,EAAU,GAAkC;AAC1D,SAAO,OAAO,UAAY,OAAe,aAAiB;;AAS5D,SAAgB,EAAa,GAAkD;AAC7E,SAAO,GAAQ,KAAS,OAAO,KAAU,YAAY,cAAe;;AAStE,SAAgB,EAAW,GAA0D;AACnF,SAAO,OAAO,KAAU;;AAS1B,SAAgB,EAAS,GAAiC;AACxD,SAAO,OAAO,KAAU;;AAS1B,SAAgB,EAAS,GAAiC;AACxD,SAAO,OAAO,KAAU,YAAY,CAAC,OAAO,MAAM,CAAA;;AASpD,SAAgB,EAAU,GAAkC;AAC1D,SAAO,OAAO,KAAU;;AAU1B,SAAgB,EAAqB,GAA8B;AACjE,SAAO,MAAM,QAAQ,CAAA;;AASvB,SAAgB,EAAO,GAA+B;AACpD,SAAO,aAAiB;;AAS1B,SAAgB,EAAuB,GAAqC;AAC1E,SAAO,GACL,MACC,aAAiB,WACf,OAAO,KAAU,YAChB,UAAW,KACX,OAAQ,EAA6B,QAAS;;AAUtD,SAAgB,EAAS,GAAkD;AACzE,SAAO,OAAO,KAAU,YAAY,MAAU"}
1
+ {"version":3,"file":"type-guards-BMX2c0LP.js","names":[],"sources":["../src/core/utils/type-guards.ts"],"sourcesContent":["/**\n * Type guard helpers.\n *\n * @module bquery/core/utils/type-guards\n */\n\n/**\n * Checks if a value is a DOM Element.\n *\n * @param value - The value to check\n * @returns True if the value is an Element\n */\nexport function isElement(value: unknown): value is Element {\n return typeof Element !== 'undefined' && value instanceof Element;\n}\n\n/**\n * Checks if a value is a BQueryCollection-like object.\n *\n * @param value - The value to check\n * @returns True if the value has an elements array property\n */\nexport function isCollection(value: unknown): value is { elements: Element[] } {\n return Boolean(value && typeof value === 'object' && 'elements' in (value as object));\n}\n\n/**\n * Checks if a value is a function.\n *\n * @param value - The value to check\n * @returns True if the value is a function\n */\nexport function isFunction(value: unknown): value is (...args: unknown[]) => unknown {\n return typeof value === 'function';\n}\n\n/**\n * Checks if a value is a string.\n *\n * @param value - The value to check\n * @returns True if the value is a string\n */\nexport function isString(value: unknown): value is string {\n return typeof value === 'string';\n}\n\n/**\n * Checks if a value is a number (excluding NaN).\n *\n * @param value - The value to check\n * @returns True if the value is a valid number\n */\nexport function isNumber(value: unknown): value is number {\n return typeof value === 'number' && !Number.isNaN(value);\n}\n\n/**\n * Checks if a value is a boolean.\n *\n * @param value - The value to check\n * @returns True if the value is a boolean\n */\nexport function isBoolean(value: unknown): value is boolean {\n return typeof value === 'boolean';\n}\n\n/**\n * Checks if a value is an array.\n *\n * @template T - The type of array elements\n * @param value - The value to check\n * @returns True if the value is an array\n */\nexport function isArray<T = unknown>(value: unknown): value is T[] {\n return Array.isArray(value);\n}\n\n/**\n * Checks if a value is a Date instance.\n *\n * @param value - The value to check\n * @returns True if the value is a Date\n */\nexport function isDate(value: unknown): value is Date {\n return value instanceof Date;\n}\n\n/**\n * Checks if a value is a Promise-like object.\n *\n * @param value - The value to check\n * @returns True if the value is a Promise-like object\n */\nexport function isPromise<T = unknown>(value: unknown): value is Promise<T> {\n return Boolean(\n value &&\n (value instanceof Promise ||\n (typeof value === 'object' &&\n 'then' in (value as object) &&\n typeof (value as { then?: unknown }).then === 'function'))\n );\n}\n\n/**\n * Checks if a value is a non-null object.\n *\n * @param value - The value to check\n * @returns True if the value is an object\n */\nexport function isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n"],"mappings":"AAYA,SAAgB,EAAU,GAAkC;AAC1D,SAAO,OAAO,UAAY,OAAe,aAAiB;AAC5D;AAQA,SAAgB,EAAa,GAAkD;AAC7E,SAAO,GAAQ,KAAS,OAAO,KAAU,YAAY,cAAe;AACtE;AAQA,SAAgB,EAAW,GAA0D;AACnF,SAAO,OAAO,KAAU;AAC1B;AAQA,SAAgB,EAAS,GAAiC;AACxD,SAAO,OAAO,KAAU;AAC1B;AAQA,SAAgB,EAAS,GAAiC;AACxD,SAAO,OAAO,KAAU,YAAY,CAAC,OAAO,MAAM,CAAK;AACzD;AAQA,SAAgB,EAAU,GAAkC;AAC1D,SAAO,OAAO,KAAU;AAC1B;AASA,SAAgB,EAAqB,GAA8B;AACjE,SAAO,MAAM,QAAQ,CAAK;AAC5B;AAQA,SAAgB,EAAO,GAA+B;AACpD,SAAO,aAAiB;AAC1B;AAQA,SAAgB,EAAuB,GAAqC;AAC1E,SAAO,GACL,MACC,aAAiB,WACf,OAAO,KAAU,YAChB,UAAW,KACX,OAAQ,EAA6B,QAAS;AAEtD;AAQA,SAAgB,EAAS,GAAkD;AACzE,SAAO,OAAO,KAAU,YAAY,MAAU;AAChD"}
@@ -1,5 +1,5 @@
1
- import { c as h, i, l as a, o as c, s as u, u as r } from "./core-CongXJuo.js";
2
- import { a as d, r as o } from "./effect-Cc51IH91.js";
1
+ import { c as h, i, l as a, o as c, s as u, u as r } from "./core-yg9rJXiR.js";
2
+ import { a as d, r as o } from "./effect-v8OIEmPs.js";
3
3
  var l = class {
4
4
  constructor(s) {
5
5
  this.compute = s, this.hasCachedValue = !1, this.dirty = !0, this.disposed = !1, this.subscribers = /* @__PURE__ */ new Set(), this.markDirty = () => {
@@ -34,4 +34,4 @@ export {
34
34
  b as t
35
35
  };
36
36
 
37
- //# sourceMappingURL=untrack-bjWDNdyE.js.map
37
+ //# sourceMappingURL=untrack-uzz3JDNK.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"untrack-bjWDNdyE.js","names":[],"sources":["../src/reactive/computed.ts","../src/reactive/untrack.ts"],"sourcesContent":["/**\n * Computed reactive values.\n */\n\nimport {\n clearDependencies,\n getCurrentObserver,\n registerDependency,\n scheduleObserver,\n track,\n withoutCurrentObserver,\n type ReactiveSource,\n} from './internals';\nimport { getActiveScope, hasScopeDisposer } from './scope';\n\n/**\n * A computed value that derives from other reactive sources.\n *\n * Computed values are lazily evaluated and cached. They only\n * recompute when their dependencies change.\n *\n * @template T - The type of the computed value\n */\nexport class Computed<T> implements ReactiveSource {\n private cachedValue!: T;\n private hasCachedValue = false;\n private dirty = true;\n private disposed = false;\n private subscribers = new Set<() => void>();\n private readonly markDirty = () => {\n if (this.disposed) {\n return;\n }\n this.dirty = true;\n // Create snapshot to avoid issues with subscribers modifying the set during iteration\n const subscribersSnapshot = Array.from(this.subscribers);\n for (const subscriber of subscribersSnapshot) {\n scheduleObserver(subscriber);\n }\n };\n\n /**\n * Creates a new computed value.\n * @param compute - Function that computes the value\n */\n constructor(private readonly compute: () => T) {}\n\n /**\n * Gets the computed value, recomputing if dependencies changed.\n * During untrack calls, getCurrentObserver returns undefined, preventing dependency tracking.\n */\n get value(): T {\n if (this.disposed) {\n if (!this.hasCachedValue) {\n this.cachedValue = withoutCurrentObserver(() => this.compute());\n this.hasCachedValue = true;\n }\n return this.cachedValue;\n }\n\n const current = getCurrentObserver();\n if (current) {\n this.subscribers.add(current);\n registerDependency(current, this);\n }\n if (this.dirty) {\n this.dirty = false;\n // Clear old dependencies before recomputing\n clearDependencies(this.markDirty);\n this.cachedValue = track(this.markDirty, this.compute);\n this.hasCachedValue = true;\n }\n return this.cachedValue;\n }\n\n /**\n * Reads the current computed value without tracking.\n * Useful when you need the value but don't want to create a dependency.\n *\n * @returns The current cached value (recomputes if dirty)\n */\n peek(): T {\n if (this.disposed) {\n if (!this.hasCachedValue) {\n this.cachedValue = withoutCurrentObserver(() => this.compute());\n this.hasCachedValue = true;\n }\n return this.cachedValue;\n }\n\n if (this.dirty) {\n this.dirty = false;\n // Clear old dependencies before recomputing\n clearDependencies(this.markDirty);\n this.cachedValue = track(this.markDirty, this.compute);\n this.hasCachedValue = true;\n }\n return this.cachedValue;\n }\n\n /**\n * Removes an observer from this computed's subscriber set.\n * @internal\n */\n unsubscribe(observer: () => void): void {\n this.subscribers.delete(observer);\n }\n\n /**\n * Disposes the computed value by unsubscribing its internal observer\n * from all upstream dependencies and clearing subscribers.\n */\n dispose(): void {\n this.disposed = true;\n if (this.dirty) {\n this.hasCachedValue = false;\n }\n this.dirty = false;\n clearDependencies(this.markDirty);\n this.subscribers.clear();\n }\n}\n\n/**\n * Creates a new computed value.\n *\n * If created inside an {@link effectScope}, the computed value is automatically\n * collected and will be disposed when the scope stops.\n *\n * @template T - The type of the computed value\n * @param fn - Function that computes the value from reactive sources\n * @returns A new Computed instance\n */\nexport const computed = <T>(fn: () => T): Computed<T> => {\n const c = new Computed(fn);\n\n // Auto-register with the current scope so scope.stop() disposes this computed\n const scope = getActiveScope();\n if (hasScopeDisposer(scope)) {\n scope._addDisposer(() => c.dispose());\n }\n\n return c;\n};\n","/**\n * Dependency tracking control helpers.\n */\n\nimport { withoutCurrentObserver } from './internals';\n\n/**\n * Executes a function without tracking any signal dependencies.\n * Useful when reading a signal value without creating a reactive dependency.\n *\n * This implementation temporarily hides the current observer rather than\n * disabling tracking globally. This ensures that nested reactive internals\n * (e.g., computed recomputation triggered during untrack) can still properly\n * track their own dependencies.\n *\n * @template T - The return type of the function\n * @param fn - The function to execute without tracking\n * @returns The result of the function\n *\n * @example\n * ```ts\n * const count = signal(0);\n * effect(() => {\n * // This read creates a dependency\n * console.log(count.value);\n * // This read does not create a dependency\n * const snapshot = untrack(() => count.value);\n * });\n * ```\n */\nexport const untrack = <T>(fn: () => T): T => withoutCurrentObserver(fn);\n"],"mappings":";;AAuBA,IAAa,IAAb,MAAmD;AAAA,EAsBjD,YAAY,GAAmC;AAAlB,SAAA,UAAA,yBApBJ,iBACT,oBACG,uBACG,oBAAI,IAAA,0BACS;AACjC,UAAI,KAAK,SACP;AAEF,WAAK,QAAQ;AAEb,YAAM,IAAsB,MAAM,KAAK,KAAK,WAAA;AAC5C,iBAAW,KAAc,EACvB,CAAA,EAAiB,CAAA;AAAA;;EAcrB,IAAI,QAAW;AACb,QAAI,KAAK;AACP,aAAK,KAAK,mBACR,KAAK,cAAc,EAAA,MAA6B,KAAK,QAAA,CAAS,GAC9D,KAAK,iBAAiB,KAEjB,KAAK;AAGd,UAAM,IAAU,EAAA;AAChB,WAAI,MACF,KAAK,YAAY,IAAI,CAAA,GACrB,EAAmB,GAAS,IAAA,IAE1B,KAAK,UACP,KAAK,QAAQ,IAEb,EAAkB,KAAK,SAAA,GACvB,KAAK,cAAc,EAAM,KAAK,WAAW,KAAK,OAAA,GAC9C,KAAK,iBAAiB,KAEjB,KAAK;AAAA;EASd,OAAU;AACR,WAAI,KAAK,YACF,KAAK,mBACR,KAAK,cAAc,EAAA,MAA6B,KAAK,QAAA,CAAS,GAC9D,KAAK,iBAAiB,KAEjB,KAAK,gBAGV,KAAK,UACP,KAAK,QAAQ,IAEb,EAAkB,KAAK,SAAA,GACvB,KAAK,cAAc,EAAM,KAAK,WAAW,KAAK,OAAA,GAC9C,KAAK,iBAAiB,KAEjB,KAAK;AAAA;EAOd,YAAY,GAA4B;AACtC,SAAK,YAAY,OAAO,CAAA;AAAA;EAO1B,UAAgB;AACd,SAAK,WAAW,IACZ,KAAK,UACP,KAAK,iBAAiB,KAExB,KAAK,QAAQ,IACb,EAAkB,KAAK,SAAA,GACvB,KAAK,YAAY,MAAA;AAAA;GAcR,IAAA,CAAe,MAA6B;AACvD,QAAM,IAAI,IAAI,EAAS,CAAA,GAGjB,IAAQ,EAAA;AACd,SAAI,EAAiB,CAAA,KACnB,EAAM,aAAA,MAAmB,EAAE,QAAA,CAAS,GAG/B;GChHI,IAAA,CAAc,MAAmB,EAAuB,CAAA"}
1
+ {"version":3,"file":"untrack-uzz3JDNK.js","names":[],"sources":["../src/reactive/computed.ts","../src/reactive/untrack.ts"],"sourcesContent":["/**\n * Computed reactive values.\n */\n\nimport {\n clearDependencies,\n getCurrentObserver,\n registerDependency,\n scheduleObserver,\n track,\n withoutCurrentObserver,\n type ReactiveSource,\n} from './internals';\nimport { getActiveScope, hasScopeDisposer } from './scope';\n\n/**\n * A computed value that derives from other reactive sources.\n *\n * Computed values are lazily evaluated and cached. They only\n * recompute when their dependencies change.\n *\n * @template T - The type of the computed value\n */\nexport class Computed<T> implements ReactiveSource {\n private cachedValue!: T;\n private hasCachedValue = false;\n private dirty = true;\n private disposed = false;\n private subscribers = new Set<() => void>();\n private readonly markDirty = () => {\n if (this.disposed) {\n return;\n }\n this.dirty = true;\n // Create snapshot to avoid issues with subscribers modifying the set during iteration\n const subscribersSnapshot = Array.from(this.subscribers);\n for (const subscriber of subscribersSnapshot) {\n scheduleObserver(subscriber);\n }\n };\n\n /**\n * Creates a new computed value.\n * @param compute - Function that computes the value\n */\n constructor(private readonly compute: () => T) {}\n\n /**\n * Gets the computed value, recomputing if dependencies changed.\n * During untrack calls, getCurrentObserver returns undefined, preventing dependency tracking.\n */\n get value(): T {\n if (this.disposed) {\n if (!this.hasCachedValue) {\n this.cachedValue = withoutCurrentObserver(() => this.compute());\n this.hasCachedValue = true;\n }\n return this.cachedValue;\n }\n\n const current = getCurrentObserver();\n if (current) {\n this.subscribers.add(current);\n registerDependency(current, this);\n }\n if (this.dirty) {\n this.dirty = false;\n // Clear old dependencies before recomputing\n clearDependencies(this.markDirty);\n this.cachedValue = track(this.markDirty, this.compute);\n this.hasCachedValue = true;\n }\n return this.cachedValue;\n }\n\n /**\n * Reads the current computed value without tracking.\n * Useful when you need the value but don't want to create a dependency.\n *\n * @returns The current cached value (recomputes if dirty)\n */\n peek(): T {\n if (this.disposed) {\n if (!this.hasCachedValue) {\n this.cachedValue = withoutCurrentObserver(() => this.compute());\n this.hasCachedValue = true;\n }\n return this.cachedValue;\n }\n\n if (this.dirty) {\n this.dirty = false;\n // Clear old dependencies before recomputing\n clearDependencies(this.markDirty);\n this.cachedValue = track(this.markDirty, this.compute);\n this.hasCachedValue = true;\n }\n return this.cachedValue;\n }\n\n /**\n * Removes an observer from this computed's subscriber set.\n * @internal\n */\n unsubscribe(observer: () => void): void {\n this.subscribers.delete(observer);\n }\n\n /**\n * Disposes the computed value by unsubscribing its internal observer\n * from all upstream dependencies and clearing subscribers.\n */\n dispose(): void {\n this.disposed = true;\n if (this.dirty) {\n this.hasCachedValue = false;\n }\n this.dirty = false;\n clearDependencies(this.markDirty);\n this.subscribers.clear();\n }\n}\n\n/**\n * Creates a new computed value.\n *\n * If created inside an {@link effectScope}, the computed value is automatically\n * collected and will be disposed when the scope stops.\n *\n * @template T - The type of the computed value\n * @param fn - Function that computes the value from reactive sources\n * @returns A new Computed instance\n */\nexport const computed = <T>(fn: () => T): Computed<T> => {\n const c = new Computed(fn);\n\n // Auto-register with the current scope so scope.stop() disposes this computed\n const scope = getActiveScope();\n if (hasScopeDisposer(scope)) {\n scope._addDisposer(() => c.dispose());\n }\n\n return c;\n};\n","/**\n * Dependency tracking control helpers.\n */\n\nimport { withoutCurrentObserver } from './internals';\n\n/**\n * Executes a function without tracking any signal dependencies.\n * Useful when reading a signal value without creating a reactive dependency.\n *\n * This implementation temporarily hides the current observer rather than\n * disabling tracking globally. This ensures that nested reactive internals\n * (e.g., computed recomputation triggered during untrack) can still properly\n * track their own dependencies.\n *\n * @template T - The return type of the function\n * @param fn - The function to execute without tracking\n * @returns The result of the function\n *\n * @example\n * ```ts\n * const count = signal(0);\n * effect(() => {\n * // This read creates a dependency\n * console.log(count.value);\n * // This read does not create a dependency\n * const snapshot = untrack(() => count.value);\n * });\n * ```\n */\nexport const untrack = <T>(fn: () => T): T => withoutCurrentObserver(fn);\n"],"mappings":";;AAuBA,IAAa,IAAb,MAAmD;AAAA,EAsBjD,YAAY,GAAmC;AAAlB,SAAA,UAAA,yBApBJ,iBACT,oBACG,uBACG,oBAAI,IAAgB,0BACP;AACjC,UAAI,KAAK,SACP;AAEF,WAAK,QAAQ;AAEb,YAAM,IAAsB,MAAM,KAAK,KAAK,WAAW;AACvD,iBAAW,KAAc,EACvB,CAAA,EAAiB,CAAU;AAAA,IAE/B;AAAA,EAMgD;AAAA,EAMhD,IAAI,QAAW;AACb,QAAI,KAAK;AACP,aAAK,KAAK,mBACR,KAAK,cAAc,EAAA,MAA6B,KAAK,QAAQ,CAAC,GAC9D,KAAK,iBAAiB,KAEjB,KAAK;AAGd,UAAM,IAAU,EAAmB;AACnC,WAAI,MACF,KAAK,YAAY,IAAI,CAAO,GAC5B,EAAmB,GAAS,IAAI,IAE9B,KAAK,UACP,KAAK,QAAQ,IAEb,EAAkB,KAAK,SAAS,GAChC,KAAK,cAAc,EAAM,KAAK,WAAW,KAAK,OAAO,GACrD,KAAK,iBAAiB,KAEjB,KAAK;AAAA,EACd;AAAA,EAQA,OAAU;AACR,WAAI,KAAK,YACF,KAAK,mBACR,KAAK,cAAc,EAAA,MAA6B,KAAK,QAAQ,CAAC,GAC9D,KAAK,iBAAiB,KAEjB,KAAK,gBAGV,KAAK,UACP,KAAK,QAAQ,IAEb,EAAkB,KAAK,SAAS,GAChC,KAAK,cAAc,EAAM,KAAK,WAAW,KAAK,OAAO,GACrD,KAAK,iBAAiB,KAEjB,KAAK;AAAA,EACd;AAAA,EAMA,YAAY,GAA4B;AACtC,SAAK,YAAY,OAAO,CAAQ;AAAA,EAClC;AAAA,EAMA,UAAgB;AACd,SAAK,WAAW,IACZ,KAAK,UACP,KAAK,iBAAiB,KAExB,KAAK,QAAQ,IACb,EAAkB,KAAK,SAAS,GAChC,KAAK,YAAY,MAAM;AAAA,EACzB;AACF,GAYa,IAAA,CAAe,MAA6B;AACvD,QAAM,IAAI,IAAI,EAAS,CAAE,GAGnB,IAAQ,EAAe;AAC7B,SAAI,EAAiB,CAAK,KACxB,EAAM,aAAA,MAAmB,EAAE,QAAQ,CAAC,GAG/B;AACT,GCjHa,IAAA,CAAc,MAAmB,EAAuB,CAAE"}
package/dist/view.es.mjs CHANGED
@@ -1,14 +1,14 @@
1
- import { n as o } from "./core-CongXJuo.js";
2
- import { r as a } from "./readonly-C0ZwS1Tf.js";
3
- import { t as e } from "./effect-Cc51IH91.js";
4
- import { r as p } from "./untrack-bjWDNdyE.js";
5
- import { n as i, r as c, t as n } from "./mount-0A9qtcRJ.js";
1
+ import { n as o } from "./core-yg9rJXiR.js";
2
+ import { t as a } from "./effect-v8OIEmPs.js";
3
+ import { r as e } from "./untrack-uzz3JDNK.js";
4
+ import { r as p } from "./readonly-Br-6pAgj.js";
5
+ import { n as i, r as c, t as n } from "./mount-DwUFujZ_.js";
6
6
  export {
7
- a as batch,
7
+ p as batch,
8
8
  c as clearExpressionCache,
9
- p as computed,
9
+ e as computed,
10
10
  n as createTemplate,
11
- e as effect,
11
+ a as effect,
12
12
  i as mount,
13
13
  o as signal
14
14
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bquery/bquery",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "description": "bQuery.js — batteries-included TypeScript framework for the modern web: signals, SSR, Web Components, routing, and more, with a jQuery-inspired API.",
5
5
  "type": "module",
6
6
  "main": "./dist/full.umd.js",
@@ -122,6 +122,7 @@
122
122
  "build:umd": "vite build --config vite.umd.config.ts",
123
123
  "build:types": "tsc --emitDeclarationOnly --outDir dist",
124
124
  "check:ai-guidance": "bun scripts/check-ai-guidance.mjs",
125
+ "check:full-bundle": "bun scripts/check-full-bundle.mjs",
125
126
  "test": "bun test",
126
127
  "test:types": "tsc -p tsconfig.component-test.json --noEmit",
127
128
  "test:watch": "bun test --watch",
@@ -167,21 +168,21 @@
167
168
  "bun": ">=1.3.13"
168
169
  },
169
170
  "devDependencies": {
170
- "@storybook/addon-docs": "^10.3.6",
171
- "@storybook/web-components-vite": "^10.3.6",
171
+ "@storybook/addon-docs": "^10.4.0",
172
+ "@storybook/web-components-vite": "^10.4.0",
172
173
  "@typescript-eslint/eslint-plugin": "^8.59.3",
173
174
  "@typescript-eslint/parser": "^8.59.3",
174
- "bun-types": "^1.3.13",
175
- "eslint": "^10.3.0",
175
+ "bun-types": "^1.3.14",
176
+ "eslint": "^10.4.0",
176
177
  "eslint-config-prettier": "^10.1.8",
177
178
  "globals": "^17.6.0",
178
179
  "happy-dom": "^20.9.0",
179
180
  "prettier": "^3.8.3",
180
181
  "rimraf": "^6.1.3",
181
- "storybook": "^10.3.6",
182
+ "storybook": "^10.4.0",
182
183
  "typedoc": "^0.28.19",
183
184
  "typescript": "^6.0.3",
184
- "vite": "^8.0.12",
185
+ "vite": "^8.0.13",
185
186
  "vitepress": "^1.6.4"
186
187
  }
187
188
  }
package/src/full.ts CHANGED
@@ -38,8 +38,53 @@
38
38
  // ============================================================================
39
39
  // Core Module: Selectors, DOM operations, events, utilities
40
40
  // ============================================================================
41
- export { $, $$, BQueryCollection, BQueryElement, utils } from './core/index';
42
- export type { BQueryUtils } from './core/index';
41
+ export {
42
+ $,
43
+ $$,
44
+ BQueryCollection,
45
+ BQueryElement,
46
+ capitalize,
47
+ chunk,
48
+ clamp,
49
+ clone,
50
+ compact,
51
+ debounce,
52
+ ensureArray,
53
+ escapeRegExp,
54
+ flatten,
55
+ hasOwn,
56
+ inRange,
57
+ isArray,
58
+ isBoolean,
59
+ isCollection,
60
+ isDate,
61
+ isElement,
62
+ isEmpty,
63
+ isFunction,
64
+ isNumber,
65
+ isObject,
66
+ isPlainObject,
67
+ isPromise,
68
+ isString,
69
+ merge,
70
+ noop,
71
+ omit,
72
+ once,
73
+ parseJson,
74
+ pick,
75
+ randomInt,
76
+ sleep,
77
+ slugify,
78
+ throttle,
79
+ toCamelCase,
80
+ toKebabCase,
81
+ toNumber,
82
+ truncate,
83
+ uid,
84
+ unique,
85
+ utils,
86
+ } from './core/index';
87
+ export type { BQueryUtils, DebouncedFn, ThrottledFn } from './core/index';
43
88
 
44
89
  // ============================================================================
45
90
  // Reactive Module: Signals, computed values, effects, batching
@@ -134,6 +179,7 @@ export type {
134
179
  WatchOptions,
135
180
  WebSocketHeartbeatConfig,
136
181
  WebSocketReconnectConfig,
182
+ WebSocketSendData,
137
183
  WebSocketSerializer,
138
184
  WebSocketStatus,
139
185
  } from './reactive/index';
@@ -322,12 +368,22 @@ export {
322
368
  export type {
323
369
  AnnounceOptions,
324
370
  AnnouncerHandle,
371
+ BqueryAnnouncerConfig,
372
+ BqueryComponentLibraryConfig,
325
373
  BqueryConfig,
374
+ BqueryCookieConfig,
375
+ BqueryFetchConfig,
376
+ BqueryFetchParseAs,
377
+ BqueryPageMetaConfig,
378
+ BqueryTransitionConfig,
326
379
  Bucket,
327
380
  CacheHandle,
328
381
  IndexedDBOptions,
329
382
  NotificationOptions,
383
+ PageLinkTag,
384
+ PageMetaCleanup,
330
385
  PageMetaDefinition,
386
+ PageMetaTag,
331
387
  StorageAdapter,
332
388
  UseAnnouncerOptions,
333
389
  UseCookieOptions,
@@ -365,6 +421,7 @@ export type {
365
421
  // Store Module: Signal-based state management
366
422
  // ============================================================================
367
423
  export {
424
+ clearPlugins,
368
425
  createPersistedStore,
369
426
  createStore,
370
427
  defineStore,
@@ -375,6 +432,7 @@ export {
375
432
  mapGetters,
376
433
  mapState,
377
434
  registerPlugin,
435
+ unregisterPlugin,
378
436
  watchStore,
379
437
  } from './store/index';
380
438
  export type {
@@ -396,7 +454,7 @@ export type {
396
454
  // ============================================================================
397
455
  // View Module: Declarative DOM bindings without compiler
398
456
  // ============================================================================
399
- export { createTemplate, mount } from './view/index';
457
+ export { clearExpressionCache, createTemplate, mount } from './view/index';
400
458
  export type { BindingContext, MountOptions, View } from './view/index';
401
459
 
402
460
  // ============================================================================
@@ -476,6 +534,7 @@ export type {
476
534
  ColorScheme,
477
535
  ContrastPreference,
478
536
  FocusTrapHandle,
537
+ MediaPreferenceSignal,
479
538
  RovingTabIndexHandle,
480
539
  RovingTabIndexOptions,
481
540
  SkipLinkHandle,
@@ -521,20 +580,30 @@ export {
521
580
  useViewport,
522
581
  } from './media/index';
523
582
  export type {
583
+ BatterySignal,
524
584
  BatteryState,
525
585
  BreakpointMap,
526
586
  ClipboardAPI,
587
+ DeviceMotionSignal,
527
588
  DeviceMotionState,
589
+ DeviceOrientationSignal,
528
590
  DeviceOrientationState,
529
591
  GeolocationOptions,
592
+ GeolocationSignal,
530
593
  GeolocationState,
531
594
  IntersectionObserverOptions,
595
+ IntersectionObserverSignal,
532
596
  IntersectionObserverState,
597
+ MediaSignalHandle,
533
598
  MutationObserverOptions,
599
+ MutationObserverSignal,
534
600
  MutationObserverState,
601
+ NetworkSignal,
535
602
  NetworkState,
536
603
  ResizeObserverOptions,
604
+ ResizeObserverSignal,
537
605
  ResizeObserverState,
606
+ ViewportSignal,
538
607
  ViewportState,
539
608
  } from './media/index';
540
609
 
@@ -630,7 +699,6 @@ export {
630
699
  detectRuntime,
631
700
  getSSRConfig,
632
701
  getSSRRuntimeFeatures,
633
- HYDRATION_HASH_ATTR,
634
702
  hydrateIsland,
635
703
  hydrateMount,
636
704
  hydrateOnIdle,
@@ -640,6 +708,7 @@ export {
640
708
  hydrateStore,
641
709
  hydrateStores,
642
710
  hydrateStoreSnapshot,
711
+ HYDRATION_HASH_ATTR,
643
712
  isBrowserRuntime,
644
713
  isServerRuntime,
645
714
  readStoreSnapshot,
@@ -688,9 +757,9 @@ export type {
688
757
  SSRLink,
689
758
  SSRLoader,
690
759
  SSRMeta,
760
+ SSRRendererBackend,
691
761
  SSRRequestHandler,
692
762
  SSRResult,
693
- SSRRendererBackend,
694
763
  SSRRouteLoader,
695
764
  SSRRuntime,
696
765
  SSRRuntimeFeatures,
@@ -710,7 +779,6 @@ export type {
710
779
  ServerApp,
711
780
  ServerContext,
712
781
  ServerHandler,
713
- ServerResult,
714
782
  ServerHtmlResponseInit,
715
783
  ServerMiddleware,
716
784
  ServerNext,
@@ -718,6 +786,7 @@ export type {
718
786
  ServerRenderResponseOptions,
719
787
  ServerRequestInit,
720
788
  ServerResponseInit,
789
+ ServerResult,
721
790
  ServerRoute,
722
791
  ServerWebSocketConnection,
723
792
  ServerWebSocketData,
@@ -5,9 +5,8 @@
5
5
  */
6
6
 
7
7
  export {
8
- Computed,
9
- Signal,
10
8
  batch,
9
+ Computed,
11
10
  computed,
12
11
  createHttp,
13
12
  createRequestQueue,
@@ -25,8 +24,10 @@ export {
25
24
  onScopeDispose,
26
25
  persistedSignal,
27
26
  readonly,
27
+ Signal,
28
28
  signal,
29
29
  toValue,
30
+ untrack,
30
31
  useAsyncData,
31
32
  useEventSource,
32
33
  useFetch,
@@ -38,7 +39,6 @@ export {
38
39
  useSubmit,
39
40
  useWebSocket,
40
41
  useWebSocketChannel,
41
- untrack,
42
42
  watch,
43
43
  watchDebounce,
44
44
  watchThrottle,
@@ -52,6 +52,7 @@ export type {
52
52
  ChannelSubscription,
53
53
  CleanupFn,
54
54
  EffectScope,
55
+ EventSourceStatus,
55
56
  FetchInput,
56
57
  HttpClient,
57
58
  HttpProgressEvent,
@@ -64,7 +65,6 @@ export type {
64
65
  LinkedSignal,
65
66
  MaybeSignal,
66
67
  Observer,
67
- EventSourceStatus,
68
68
  PaginatedState,
69
69
  PollingState,
70
70
  ReadonlySignal,
@@ -95,6 +95,7 @@ export type {
95
95
  WatchOptions,
96
96
  WebSocketHeartbeatConfig,
97
97
  WebSocketReconnectConfig,
98
+ WebSocketSendData,
98
99
  WebSocketSerializer,
99
100
  WebSocketStatus,
100
101
  } from './signal';
@@ -4,8 +4,8 @@
4
4
  * @module bquery/reactive
5
5
  */
6
6
 
7
- export { batch } from './batch';
8
7
  export { createUseFetch, useAsyncData, useFetch } from './async-data';
8
+ export { batch } from './batch';
9
9
  export { Computed, computed } from './computed';
10
10
  export { Signal, signal } from './core';
11
11
  export { effect } from './effect';
@@ -23,13 +23,12 @@ export {
23
23
  useSubmit,
24
24
  } from './rest';
25
25
  export { effectScope, getCurrentScope, onScopeDispose } from './scope';
26
- export { isComputed, isSignal } from './type-guards';
27
26
  export { toValue } from './to-value';
27
+ export { isComputed, isSignal } from './type-guards';
28
28
  export { untrack } from './untrack';
29
29
  export { watch, watchDebounce, watchThrottle } from './watch';
30
30
  export { useEventSource, useWebSocket, useWebSocketChannel } from './websocket';
31
31
 
32
- export type { CleanupFn, Observer } from './internals';
33
32
  export type {
34
33
  AsyncDataState,
35
34
  AsyncDataStatus,
@@ -50,6 +49,8 @@ export type {
50
49
  RequestQueueOptions,
51
50
  RetryConfig,
52
51
  } from './http';
52
+ export type { CleanupFn, Observer } from './internals';
53
+ export type { LinkedSignal } from './linked';
53
54
  export type {
54
55
  InfiniteState,
55
56
  PaginatedState,
@@ -57,7 +58,7 @@ export type {
57
58
  UsePaginatedFetchOptions,
58
59
  } from './pagination';
59
60
  export type { PollingState, UsePollingOptions } from './polling';
60
- export type { WatchOptions } from './watch';
61
+ export type { ReadonlySignal, ReadonlySignalHandle } from './readonly';
61
62
  export type {
62
63
  IdExtractor,
63
64
  ResourceListActions,
@@ -70,9 +71,8 @@ export type {
70
71
  UseSubmitReturn,
71
72
  } from './rest';
72
73
  export type { EffectScope } from './scope';
73
- export type { LinkedSignal } from './linked';
74
74
  export type { MaybeSignal } from './to-value';
75
- export type { ReadonlySignal, ReadonlySignalHandle } from './readonly';
75
+ export type { WatchOptions } from './watch';
76
76
  export type {
77
77
  ChannelMessage,
78
78
  ChannelSubscription,
@@ -85,6 +85,7 @@ export type {
85
85
  UseWebSocketReturn,
86
86
  WebSocketHeartbeatConfig,
87
87
  WebSocketReconnectConfig,
88
+ WebSocketSendData,
88
89
  WebSocketSerializer,
89
90
  WebSocketStatus,
90
91
  } from './websocket';
@@ -8,8 +8,21 @@
8
8
  import { computed } from './computed';
9
9
  import { Signal, signal } from './core';
10
10
 
11
- /** @internal */
12
- type WebSocketSendData = string | Blob | ArrayBufferLike | ArrayBufferView;
11
+ /**
12
+ * Binary/text payloads accepted by reactive WebSocket helpers.
13
+ *
14
+ * Mirrors the shape accepted by the native `WebSocket.send()` method and is
15
+ * used by {@link UseWebSocketReturn.sendRaw}, {@link WebSocketSerializer.serialize}'s
16
+ * return type, and {@link WebSocketHeartbeatConfig.message}.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import type { WebSocketSendData } from '@bquery/bquery/reactive';
21
+ *
22
+ * const frames: WebSocketSendData[] = ['hello', new Blob(['x']), new Uint8Array([1])];
23
+ * ```
24
+ */
25
+ export type WebSocketSendData = string | Blob | ArrayBufferLike | ArrayBufferView;
13
26
 
14
27
  // ---------------------------------------------------------------------------
15
28
  // Types
@@ -525,7 +525,10 @@ const adaptHttpMiddlewareToWebSocket = (middleware: ServerMiddleware): WebSocket
525
525
  return middlewareResponse;
526
526
  }
527
527
 
528
- if (middlewareResponse instanceof Response && isWebSocketPassthroughResponse(middlewareResponse)) {
528
+ if (
529
+ middlewareResponse instanceof Response &&
530
+ isWebSocketPassthroughResponse(middlewareResponse)
531
+ ) {
529
532
  return downstream;
530
533
  }
531
534
 
@@ -650,7 +653,11 @@ export const createServer = (options: CreateServerOptions = {}): ServerApp => {
650
653
  },
651
654
 
652
655
  ws(path, handler, routeMiddlewares) {
653
- return addWebSocketRoute(path, handler as ServerWebSocketRouteHandler<unknown>, routeMiddlewares);
656
+ return addWebSocketRoute(
657
+ path,
658
+ handler as ServerWebSocketRouteHandler<unknown>,
659
+ routeMiddlewares
660
+ );
654
661
  },
655
662
 
656
663
  async handle(input) {
@@ -739,9 +746,7 @@ export const createServer = (options: CreateServerOptions = {}): ServerApp => {
739
746
  ];
740
747
  return await runWebSocketPipeline(context, stack, async () => {
741
748
  const handlers =
742
- typeof route.handler === 'function'
743
- ? await route.handler(context)
744
- : route.handler;
749
+ typeof route.handler === 'function' ? await route.handler(context) : route.handler;
745
750
  return createWebSocketSession(context, handlers as ServerWebSocketHandlerSet<unknown>);
746
751
  });
747
752
  } catch (error) {
@@ -215,11 +215,7 @@ export interface ServerContext {
215
215
  * return ctx.render('<h1 bq-text="title"></h1>', { title: 'Dashboard' });
216
216
  * ```
217
217
  */
218
- render(
219
- template: string,
220
- data: BindingContext,
221
- options?: ServerRenderResponseOptions
222
- ): Response;
218
+ render(template: string, data: BindingContext, options?: ServerRenderResponseOptions): Response;
223
219
 
224
220
  /**
225
221
  * `true` when the incoming request is a WebSocket upgrade handshake.