@bquery/bquery 1.2.0 → 1.3.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.
- package/README.md +501 -427
- package/dist/batch-4LAvfLE7.js +13 -0
- package/dist/batch-4LAvfLE7.js.map +1 -0
- package/dist/component/component.d.ts +69 -0
- package/dist/component/component.d.ts.map +1 -0
- package/dist/component/html.d.ts +35 -0
- package/dist/component/html.d.ts.map +1 -0
- package/dist/component/index.d.ts +3 -126
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/props.d.ts +18 -0
- package/dist/component/props.d.ts.map +1 -0
- package/dist/component/types.d.ts +77 -0
- package/dist/component/types.d.ts.map +1 -0
- package/dist/component.es.mjs +90 -59
- package/dist/component.es.mjs.map +1 -1
- package/dist/core/collection.d.ts +36 -0
- package/dist/core/collection.d.ts.map +1 -1
- package/dist/core/dom.d.ts +6 -0
- package/dist/core/dom.d.ts.map +1 -0
- package/dist/core/element.d.ts +8 -0
- package/dist/core/element.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/utils/array.d.ts +74 -0
- package/dist/core/utils/array.d.ts.map +1 -0
- package/dist/core/utils/function.d.ts +70 -0
- package/dist/core/utils/function.d.ts.map +1 -0
- package/dist/core/utils/index.d.ts +70 -0
- package/dist/core/utils/index.d.ts.map +1 -0
- package/dist/core/utils/misc.d.ts +63 -0
- package/dist/core/utils/misc.d.ts.map +1 -0
- package/dist/core/utils/number.d.ts +65 -0
- package/dist/core/utils/number.d.ts.map +1 -0
- package/dist/core/utils/object.d.ts +133 -0
- package/dist/core/utils/object.d.ts.map +1 -0
- package/dist/core/utils/string.d.ts +80 -0
- package/dist/core/utils/string.d.ts.map +1 -0
- package/dist/core/utils/type-guards.d.ts +79 -0
- package/dist/core/utils/type-guards.d.ts.map +1 -0
- package/dist/core-COenAZjD.js +145 -0
- package/dist/core-COenAZjD.js.map +1 -0
- package/dist/core.es.mjs +411 -448
- package/dist/core.es.mjs.map +1 -1
- package/dist/full.d.ts +2 -2
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +87 -64
- package/dist/full.es.mjs.map +1 -1
- package/dist/full.iife.js +2 -2
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +2 -2
- package/dist/full.umd.js.map +1 -1
- package/dist/index.es.mjs +138 -68
- package/dist/index.es.mjs.map +1 -1
- package/dist/motion/animate.d.ts +25 -0
- package/dist/motion/animate.d.ts.map +1 -0
- package/dist/motion/easing.d.ts +30 -0
- package/dist/motion/easing.d.ts.map +1 -0
- package/dist/motion/flip.d.ts +55 -0
- package/dist/motion/flip.d.ts.map +1 -0
- package/dist/motion/index.d.ts +11 -138
- package/dist/motion/index.d.ts.map +1 -1
- package/dist/motion/keyframes.d.ts +21 -0
- package/dist/motion/keyframes.d.ts.map +1 -0
- package/dist/motion/reduced-motion.d.ts +12 -0
- package/dist/motion/reduced-motion.d.ts.map +1 -0
- package/dist/motion/scroll.d.ts +15 -0
- package/dist/motion/scroll.d.ts.map +1 -0
- package/dist/motion/spring.d.ts +42 -0
- package/dist/motion/spring.d.ts.map +1 -0
- package/dist/motion/stagger.d.ts +22 -0
- package/dist/motion/stagger.d.ts.map +1 -0
- package/dist/motion/timeline.d.ts +21 -0
- package/dist/motion/timeline.d.ts.map +1 -0
- package/dist/motion/transition.d.ts +22 -0
- package/dist/motion/transition.d.ts.map +1 -0
- package/dist/motion/types.d.ts +182 -0
- package/dist/motion/types.d.ts.map +1 -0
- package/dist/motion.es.mjs +320 -61
- package/dist/motion.es.mjs.map +1 -1
- package/dist/persisted-Dz_ryNuC.js +278 -0
- package/dist/persisted-Dz_ryNuC.js.map +1 -0
- package/dist/reactive/batch.d.ts +13 -0
- package/dist/reactive/batch.d.ts.map +1 -0
- package/dist/reactive/computed.d.ts +50 -0
- package/dist/reactive/computed.d.ts.map +1 -0
- package/dist/reactive/core.d.ts +60 -0
- package/dist/reactive/core.d.ts.map +1 -0
- package/dist/reactive/effect.d.ts +15 -0
- package/dist/reactive/effect.d.ts.map +1 -0
- package/dist/reactive/index.d.ts +2 -2
- package/dist/reactive/index.d.ts.map +1 -1
- package/dist/reactive/internals.d.ts +36 -0
- package/dist/reactive/internals.d.ts.map +1 -0
- package/dist/reactive/linked.d.ts +36 -0
- package/dist/reactive/linked.d.ts.map +1 -0
- package/dist/reactive/persisted.d.ts +14 -0
- package/dist/reactive/persisted.d.ts.map +1 -0
- package/dist/reactive/readonly.d.ts +26 -0
- package/dist/reactive/readonly.d.ts.map +1 -0
- package/dist/reactive/signal.d.ts +13 -312
- package/dist/reactive/signal.d.ts.map +1 -1
- package/dist/reactive/type-guards.d.ts +20 -0
- package/dist/reactive/type-guards.d.ts.map +1 -0
- package/dist/reactive/untrack.d.ts +29 -0
- package/dist/reactive/untrack.d.ts.map +1 -0
- package/dist/reactive/watch.d.ts +42 -0
- package/dist/reactive/watch.d.ts.map +1 -0
- package/dist/reactive.es.mjs +30 -163
- package/dist/reactive.es.mjs.map +1 -1
- package/dist/router/index.d.ts +6 -252
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/links.d.ts +44 -0
- package/dist/router/links.d.ts.map +1 -0
- package/dist/router/match.d.ts +20 -0
- package/dist/router/match.d.ts.map +1 -0
- package/dist/router/navigation.d.ts +45 -0
- package/dist/router/navigation.d.ts.map +1 -0
- package/dist/router/query.d.ts +16 -0
- package/dist/router/query.d.ts.map +1 -0
- package/dist/router/router.d.ts +34 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/router/state.d.ts +27 -0
- package/dist/router/state.d.ts.map +1 -0
- package/dist/router/types.d.ts +88 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/router/utils.d.ts +65 -0
- package/dist/router/utils.d.ts.map +1 -0
- package/dist/router.es.mjs +168 -132
- package/dist/router.es.mjs.map +1 -1
- package/dist/sanitize-1FBEPAFH.js +272 -0
- package/dist/sanitize-1FBEPAFH.js.map +1 -0
- package/dist/security/constants.d.ts +42 -0
- package/dist/security/constants.d.ts.map +1 -0
- package/dist/security/csp.d.ts +24 -0
- package/dist/security/csp.d.ts.map +1 -0
- package/dist/security/index.d.ts +4 -2
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/sanitize-core.d.ts +13 -0
- package/dist/security/sanitize-core.d.ts.map +1 -0
- package/dist/security/sanitize.d.ts +5 -57
- package/dist/security/sanitize.d.ts.map +1 -1
- package/dist/security/trusted-types.d.ts +25 -0
- package/dist/security/trusted-types.d.ts.map +1 -0
- package/dist/security/types.d.ts +36 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/security.es.mjs +50 -277
- package/dist/security.es.mjs.map +1 -1
- package/dist/store/create-store.d.ts +15 -0
- package/dist/store/create-store.d.ts.map +1 -0
- package/dist/store/define-store.d.ts +28 -0
- package/dist/store/define-store.d.ts.map +1 -0
- package/dist/store/devtools.d.ts +22 -0
- package/dist/store/devtools.d.ts.map +1 -0
- package/dist/store/index.d.ts +10 -286
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/mapping.d.ts +28 -0
- package/dist/store/mapping.d.ts.map +1 -0
- package/dist/store/persisted.d.ts +13 -0
- package/dist/store/persisted.d.ts.map +1 -0
- package/dist/store/plugins.d.ts +13 -0
- package/dist/store/plugins.d.ts.map +1 -0
- package/dist/store/registry.d.ts +28 -0
- package/dist/store/registry.d.ts.map +1 -0
- package/dist/store/types.d.ts +71 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/utils.d.ts +28 -0
- package/dist/store/utils.d.ts.map +1 -0
- package/dist/store/watch.d.ts +23 -0
- package/dist/store/watch.d.ts.map +1 -0
- package/dist/store.es.mjs +22 -224
- package/dist/store.es.mjs.map +1 -1
- package/dist/type-guards-DRma3-Kc.js +16 -0
- package/dist/type-guards-DRma3-Kc.js.map +1 -0
- package/dist/untrack-BuEQKH7_.js +6 -0
- package/dist/untrack-BuEQKH7_.js.map +1 -0
- package/dist/view/directives/bind.d.ts +7 -0
- package/dist/view/directives/bind.d.ts.map +1 -0
- package/dist/view/directives/class.d.ts +8 -0
- package/dist/view/directives/class.d.ts.map +1 -0
- package/dist/view/directives/for.d.ts +23 -0
- package/dist/view/directives/for.d.ts.map +1 -0
- package/dist/view/directives/html.d.ts +7 -0
- package/dist/view/directives/html.d.ts.map +1 -0
- package/dist/view/directives/if.d.ts +7 -0
- package/dist/view/directives/if.d.ts.map +1 -0
- package/dist/view/directives/index.d.ts +12 -0
- package/dist/view/directives/index.d.ts.map +1 -0
- package/dist/view/directives/model.d.ts +7 -0
- package/dist/view/directives/model.d.ts.map +1 -0
- package/dist/view/directives/on.d.ts +7 -0
- package/dist/view/directives/on.d.ts.map +1 -0
- package/dist/view/directives/ref.d.ts +7 -0
- package/dist/view/directives/ref.d.ts.map +1 -0
- package/dist/view/directives/show.d.ts +7 -0
- package/dist/view/directives/show.d.ts.map +1 -0
- package/dist/view/directives/style.d.ts +7 -0
- package/dist/view/directives/style.d.ts.map +1 -0
- package/dist/view/directives/text.d.ts +7 -0
- package/dist/view/directives/text.d.ts.map +1 -0
- package/dist/view/evaluate.d.ts +43 -0
- package/dist/view/evaluate.d.ts.map +1 -0
- package/dist/view/index.d.ts +3 -93
- package/dist/view/index.d.ts.map +1 -1
- package/dist/view/mount.d.ts +69 -0
- package/dist/view/mount.d.ts.map +1 -0
- package/dist/view/process.d.ts +26 -0
- package/dist/view/process.d.ts.map +1 -0
- package/dist/view/types.d.ts +36 -0
- package/dist/view/types.d.ts.map +1 -0
- package/dist/view.es.mjs +368 -267
- package/dist/view.es.mjs.map +1 -1
- package/dist/watch-CXyaBC_9.js +58 -0
- package/dist/watch-CXyaBC_9.js.map +1 -0
- package/package.json +132 -132
- package/src/component/component.ts +289 -0
- package/src/component/html.ts +53 -0
- package/src/component/index.ts +40 -414
- package/src/component/props.ts +116 -0
- package/src/component/types.ts +85 -0
- package/src/core/collection.ts +588 -454
- package/src/core/dom.ts +38 -0
- package/src/core/element.ts +746 -740
- package/src/core/index.ts +43 -0
- package/src/core/utils/array.ts +102 -0
- package/src/core/utils/function.ts +110 -0
- package/src/core/utils/index.ts +83 -0
- package/src/core/utils/misc.ts +82 -0
- package/src/core/utils/number.ts +78 -0
- package/src/core/utils/object.ts +206 -0
- package/src/core/utils/string.ts +112 -0
- package/src/core/utils/type-guards.ts +112 -0
- package/src/full.ts +187 -150
- package/src/index.ts +36 -36
- package/src/motion/animate.ts +113 -0
- package/src/motion/easing.ts +40 -0
- package/src/motion/flip.ts +176 -0
- package/src/motion/index.ts +41 -358
- package/src/motion/keyframes.ts +46 -0
- package/src/motion/reduced-motion.ts +17 -0
- package/src/motion/scroll.ts +57 -0
- package/src/motion/spring.ts +150 -0
- package/src/motion/stagger.ts +43 -0
- package/src/motion/timeline.ts +246 -0
- package/src/motion/transition.ts +51 -0
- package/src/motion/types.ts +198 -0
- package/src/reactive/batch.ts +22 -0
- package/src/reactive/computed.ts +92 -0
- package/src/reactive/core.ts +93 -0
- package/src/reactive/effect.ts +43 -0
- package/src/reactive/index.ts +23 -22
- package/src/reactive/internals.ts +105 -0
- package/src/reactive/linked.ts +56 -0
- package/src/reactive/persisted.ts +74 -0
- package/src/reactive/readonly.ts +35 -0
- package/src/reactive/signal.ts +20 -520
- package/src/reactive/type-guards.ts +22 -0
- package/src/reactive/untrack.ts +31 -0
- package/src/reactive/watch.ts +73 -0
- package/src/router/index.ts +41 -718
- package/src/router/links.ts +130 -0
- package/src/router/match.ts +106 -0
- package/src/router/navigation.ts +71 -0
- package/src/router/query.ts +35 -0
- package/src/router/router.ts +211 -0
- package/src/router/state.ts +46 -0
- package/src/router/types.ts +93 -0
- package/src/router/utils.ts +116 -0
- package/src/security/constants.ts +209 -0
- package/src/security/csp.ts +77 -0
- package/src/security/index.ts +4 -12
- package/src/security/sanitize-core.ts +343 -0
- package/src/security/sanitize.ts +66 -625
- package/src/security/trusted-types.ts +69 -0
- package/src/security/types.ts +40 -0
- package/src/store/create-store.ts +329 -0
- package/src/store/define-store.ts +48 -0
- package/src/store/devtools.ts +45 -0
- package/src/store/index.ts +22 -848
- package/src/store/mapping.ts +73 -0
- package/src/store/persisted.ts +61 -0
- package/src/store/plugins.ts +32 -0
- package/src/store/registry.ts +51 -0
- package/src/store/types.ts +94 -0
- package/src/store/utils.ts +141 -0
- package/src/store/watch.ts +52 -0
- package/src/view/directives/bind.ts +23 -0
- package/src/view/directives/class.ts +70 -0
- package/src/view/directives/for.ts +275 -0
- package/src/view/directives/html.ts +19 -0
- package/src/view/directives/if.ts +30 -0
- package/src/view/directives/index.ts +11 -0
- package/src/view/directives/model.ts +56 -0
- package/src/view/directives/on.ts +41 -0
- package/src/view/directives/ref.ts +41 -0
- package/src/view/directives/show.ts +26 -0
- package/src/view/directives/style.ts +47 -0
- package/src/view/directives/text.ts +15 -0
- package/src/view/evaluate.ts +274 -0
- package/src/view/index.ts +112 -1041
- package/src/view/mount.ts +200 -0
- package/src/view/process.ts +92 -0
- package/src/view/types.ts +44 -0
- package/dist/core/utils.d.ts +0 -313
- package/dist/core/utils.d.ts.map +0 -1
- package/src/core/utils.ts +0 -444
package/dist/router.es.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.es.mjs","sources":["../src/router/index.ts"],"sourcesContent":["/**\r\n * Minimal SPA router with History API integration.\r\n *\r\n * This module provides a lightweight, signal-based router for single-page\r\n * applications. Features include:\r\n * - History API navigation\r\n * - Route matching with params and wildcards\r\n * - Lazy route loading\r\n * - Navigation guards (beforeEach, afterEach)\r\n * - Reactive current route via signals\r\n * - Multi-value query params (e.g., `?tag=a&tag=b` → `{ tag: ['a', 'b'] }`)\r\n *\r\n * @module bquery/router\r\n *\r\n * @example\r\n * ```ts\r\n * import { createRouter, navigate, currentRoute } from 'bquery/router';\r\n * import { effect } from 'bquery/reactive';\r\n *\r\n * const router = createRouter({\r\n * routes: [\r\n * { path: '/', component: () => import('./Home') },\r\n * { path: '/user/:id', component: () => import('./User') },\r\n * { path: '*', component: () => import('./NotFound') },\r\n * ],\r\n * });\r\n *\r\n * effect(() => {\r\n * console.log('Route changed:', currentRoute.value);\r\n * });\r\n *\r\n * navigate('/user/42');\r\n * ```\r\n */\r\n\r\nimport { computed, signal, type ReadonlySignal, type Signal } from '../reactive/index';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\n/**\r\n * Represents a parsed route with matched params.\r\n */\r\nexport type Route = {\r\n /** The current path (e.g., '/user/42') */\r\n path: string;\r\n /** Extracted route params (e.g., { id: '42' }) */\r\n params: Record<string, string>;\r\n /**\r\n * Query string params.\r\n * Each key maps to a single string value by default.\r\n * Only keys that appear multiple times in the query string become arrays.\r\n * @example\r\n * // ?foo=1 → { foo: '1' }\r\n * // ?tag=a&tag=b → { tag: ['a', 'b'] }\r\n * // ?x=1&y=2&x=3 → { x: ['1', '3'], y: '2' }\r\n */\r\n query: Record<string, string | string[]>;\r\n /** The matched route definition */\r\n matched: RouteDefinition | null;\r\n /** Hash fragment without # */\r\n hash: string;\r\n};\r\n\r\n/**\r\n * Route definition for configuration.\r\n */\r\nexport type RouteDefinition = {\r\n /** Path pattern (e.g., '/user/:id', '/posts/*') */\r\n path: string;\r\n /** Component loader (sync or async) */\r\n component: () => unknown | Promise<unknown>;\r\n /** Optional route name for programmatic navigation */\r\n name?: string;\r\n /** Optional metadata */\r\n meta?: Record<string, unknown>;\r\n /** Nested child routes */\r\n children?: RouteDefinition[];\r\n};\r\n\r\n/**\r\n * Router configuration options.\r\n */\r\nexport type RouterOptions = {\r\n /** Array of route definitions */\r\n routes: RouteDefinition[];\r\n /** Base path for all routes (default: '') */\r\n base?: string;\r\n /** Use hash-based routing instead of history (default: false) */\r\n hash?: boolean;\r\n};\r\n\r\n/**\r\n * Navigation guard function type.\r\n */\r\nexport type NavigationGuard = (to: Route, from: Route) => boolean | void | Promise<boolean | void>;\r\n\r\n/**\r\n * Router instance returned by createRouter.\r\n */\r\nexport type Router = {\r\n /** Navigate to a path */\r\n push: (path: string) => Promise<void>;\r\n /** Replace current history entry */\r\n replace: (path: string) => Promise<void>;\r\n /** Go back in history */\r\n back: () => void;\r\n /** Go forward in history */\r\n forward: () => void;\r\n /** Go to a specific history entry */\r\n go: (delta: number) => void;\r\n /** Add a beforeEach guard */\r\n beforeEach: (guard: NavigationGuard) => () => void;\r\n /** Add an afterEach hook */\r\n afterEach: (hook: (to: Route, from: Route) => void) => () => void;\r\n /** Current route (reactive) */\r\n currentRoute: ReadonlySignal<Route>;\r\n /** All route definitions */\r\n routes: RouteDefinition[];\r\n /** Destroy the router and cleanup listeners */\r\n destroy: () => void;\r\n};\r\n\r\n// ============================================================================\r\n// Internal State\r\n// ============================================================================\r\n\r\n/** @internal */\r\nlet activeRouter: Router | null = null;\r\n\r\n/** @internal */\r\nconst routeSignal: Signal<Route> = signal<Route>({\r\n path: '',\r\n params: {},\r\n query: {},\r\n matched: null,\r\n hash: '',\r\n});\r\n\r\n/**\r\n * Reactive signal containing the current route.\r\n *\r\n * @example\r\n * ```ts\r\n * import { currentRoute } from 'bquery/router';\r\n * import { effect } from 'bquery/reactive';\r\n *\r\n * effect(() => {\r\n * document.title = `Page: ${currentRoute.value.path}`;\r\n * });\r\n * ```\r\n */\r\nexport const currentRoute: ReadonlySignal<Route> = computed(() => routeSignal.value);\r\n\r\n// ============================================================================\r\n// Route Matching\r\n// ============================================================================\r\n\r\n/**\r\n * Converts a route path pattern to a RegExp for matching.\r\n * Uses placeholder approach to preserve :param and * patterns during escaping.\r\n * @internal\r\n */\r\nconst pathToRegex = (path: string): RegExp => {\r\n // Handle wildcard-only route\r\n if (path === '*') {\r\n return /^.*$/;\r\n }\r\n\r\n // Unique placeholders using null chars (won't appear in normal paths)\r\n const PARAM_MARKER = '\\u0000P\\u0000';\r\n const WILDCARD_MARKER = '\\u0000W\\u0000';\r\n\r\n // Store param names for restoration\r\n const paramNames: string[] = [];\r\n\r\n // Step 1: Extract :param patterns before escaping\r\n let pattern = path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name) => {\r\n paramNames.push(name);\r\n return PARAM_MARKER;\r\n });\r\n\r\n // Step 2: Extract * wildcards before escaping\r\n pattern = pattern.replace(/\\*/g, WILDCARD_MARKER);\r\n\r\n // Step 3: Escape ALL regex metacharacters: \\ ^ $ . * + ? ( ) [ ] { } |\r\n pattern = pattern.replace(/[\\\\^$.*+?()[\\]{}|]/g, '\\\\$&');\r\n\r\n // Step 4: Restore param capture groups\r\n let paramIdx = 0;\r\n pattern = pattern.replace(/\\u0000P\\u0000/g, () => `(?<${paramNames[paramIdx++]}>[^/]+)`);\r\n\r\n // Step 5: Restore wildcards as .*\r\n pattern = pattern.replace(/\\u0000W\\u0000/g, '.*');\r\n\r\n return new RegExp(`^${pattern}$`);\r\n};\r\n\r\n/**\r\n * Extracts param names from a route path.\r\n * @internal\r\n */\r\nconst extractParamNames = (path: string): string[] => {\r\n const matches = path.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g);\r\n return matches ? matches.map((m) => m.slice(1)) : [];\r\n};\r\n\r\n/**\r\n * Matches a path against route definitions and extracts params.\r\n * @internal\r\n */\r\nconst matchRoute = (\r\n path: string,\r\n routes: RouteDefinition[]\r\n): { matched: RouteDefinition; params: Record<string, string> } | null => {\r\n for (const route of routes) {\r\n const regex = pathToRegex(route.path);\r\n const match = path.match(regex);\r\n\r\n if (match) {\r\n const paramNames = extractParamNames(route.path);\r\n const params: Record<string, string> = {};\r\n\r\n // Extract named groups if available\r\n if (match.groups) {\r\n Object.assign(params, match.groups);\r\n } else {\r\n // Fallback for browsers without named groups\r\n paramNames.forEach((name, index) => {\r\n params[name] = match[index + 1] || '';\r\n });\r\n }\r\n\r\n return { matched: route, params };\r\n }\r\n }\r\n\r\n return null;\r\n};\r\n\r\n/**\r\n * Parses query string into an object.\r\n * Single values are stored as strings, duplicate keys become arrays.\r\n * @internal\r\n *\r\n * @example\r\n * parseQuery('?foo=1') // { foo: '1' }\r\n * parseQuery('?tag=a&tag=b') // { tag: ['a', 'b'] }\r\n * parseQuery('?x=1&y=2&x=3') // { x: ['1', '3'], y: '2' }\r\n */\r\nconst parseQuery = (search: string): Record<string, string | string[]> => {\r\n const query: Record<string, string | string[]> = {};\r\n const params = new URLSearchParams(search);\r\n\r\n params.forEach((value, key) => {\r\n const existing = query[key];\r\n if (existing === undefined) {\r\n // First occurrence: store as string\r\n query[key] = value;\r\n } else if (Array.isArray(existing)) {\r\n // Already an array: append\r\n existing.push(value);\r\n } else {\r\n // Second occurrence: convert to array\r\n query[key] = [existing, value];\r\n }\r\n });\r\n\r\n return query;\r\n};\r\n\r\n/**\r\n * Creates a Route object from the current URL.\r\n * @internal\r\n */\r\nconst createRoute = (\r\n pathname: string,\r\n search: string,\r\n hash: string,\r\n routes: RouteDefinition[]\r\n): Route => {\r\n const result = matchRoute(pathname, routes);\r\n\r\n return {\r\n path: pathname,\r\n params: result?.params ?? {},\r\n query: parseQuery(search),\r\n matched: result?.matched ?? null,\r\n hash: hash.replace(/^#/, ''),\r\n };\r\n};\r\n\r\n// ============================================================================\r\n// Navigation\r\n// ============================================================================\r\n\r\n/**\r\n * Navigates to a new path.\r\n *\r\n * @param path - The path to navigate to\r\n * @param options - Navigation options\r\n *\r\n * @example\r\n * ```ts\r\n * import { navigate } from 'bquery/router';\r\n *\r\n * // Push to history\r\n * await navigate('/dashboard');\r\n *\r\n * // Replace current entry\r\n * await navigate('/login', { replace: true });\r\n * ```\r\n */\r\nexport const navigate = async (\r\n path: string,\r\n options: { replace?: boolean } = {}\r\n): Promise<void> => {\r\n if (!activeRouter) {\r\n throw new Error('bQuery router: No router initialized. Call createRouter() first.');\r\n }\r\n\r\n await activeRouter[options.replace ? 'replace' : 'push'](path);\r\n};\r\n\r\n/**\r\n * Programmatically go back in history.\r\n *\r\n * @example\r\n * ```ts\r\n * import { back } from 'bquery/router';\r\n * back();\r\n * ```\r\n */\r\nexport const back = (): void => {\r\n if (activeRouter) {\r\n activeRouter.back();\r\n } else {\r\n history.back();\r\n }\r\n};\r\n\r\n/**\r\n * Programmatically go forward in history.\r\n *\r\n * @example\r\n * ```ts\r\n * import { forward } from 'bquery/router';\r\n * forward();\r\n * ```\r\n */\r\nexport const forward = (): void => {\r\n if (activeRouter) {\r\n activeRouter.forward();\r\n } else {\r\n history.forward();\r\n }\r\n};\r\n\r\n// ============================================================================\r\n// Router Creation\r\n// ============================================================================\r\n\r\n/**\r\n * Creates and initializes a router instance.\r\n *\r\n * @param options - Router configuration\r\n * @returns The router instance\r\n *\r\n * @example\r\n * ```ts\r\n * import { createRouter } from 'bquery/router';\r\n *\r\n * const router = createRouter({\r\n * routes: [\r\n * { path: '/', component: () => import('./pages/Home') },\r\n * { path: '/about', component: () => import('./pages/About') },\r\n * { path: '/user/:id', component: () => import('./pages/User') },\r\n * { path: '*', component: () => import('./pages/NotFound') },\r\n * ],\r\n * base: '/app',\r\n * });\r\n *\r\n * router.beforeEach((to, from) => {\r\n * if (to.path === '/admin' && !isAuthenticated()) {\r\n * return false; // Cancel navigation\r\n * }\r\n * });\r\n * ```\r\n */\r\nexport const createRouter = (options: RouterOptions): Router => {\r\n // Clean up any existing router to prevent guard leakage\r\n if (activeRouter) {\r\n activeRouter.destroy();\r\n }\r\n\r\n const { routes, base = '', hash: useHash = false } = options;\r\n\r\n // Instance-specific guards and hooks (not shared globally)\r\n const beforeGuards: NavigationGuard[] = [];\r\n const afterHooks: Array<(to: Route, from: Route) => void> = [];\r\n\r\n // Flatten nested routes\r\n const flatRoutes = flattenRoutes(routes, base);\r\n\r\n /**\r\n * Gets the current path from the URL.\r\n */\r\n const getCurrentPath = (): { pathname: string; search: string; hash: string } => {\r\n if (useHash) {\r\n const hashPath = window.location.hash.slice(1) || '/';\r\n const [pathname, rest = ''] = hashPath.split('?');\r\n const [search, hashPart = ''] = rest.split('#');\r\n return {\r\n pathname,\r\n search: search ? `?${search}` : '',\r\n hash: hashPart ? `#${hashPart}` : '',\r\n };\r\n }\r\n\r\n let pathname = window.location.pathname;\r\n if (base && pathname.startsWith(base)) {\r\n pathname = pathname.slice(base.length) || '/';\r\n }\r\n\r\n return {\r\n pathname,\r\n search: window.location.search,\r\n hash: window.location.hash,\r\n };\r\n };\r\n\r\n /**\r\n * Updates the route signal with current URL state.\r\n */\r\n const syncRoute = (): void => {\r\n const { pathname, search, hash } = getCurrentPath();\r\n const newRoute = createRoute(pathname, search, hash, flatRoutes);\r\n routeSignal.value = newRoute;\r\n };\r\n\r\n /**\r\n * Performs navigation with guards.\r\n */\r\n const performNavigation = async (\r\n path: string,\r\n method: 'pushState' | 'replaceState'\r\n ): Promise<void> => {\r\n const { pathname, search, hash } = getCurrentPath();\r\n const from = createRoute(pathname, search, hash, flatRoutes);\r\n\r\n // Parse the target path\r\n const url = new URL(path, window.location.origin);\r\n const toPath = useHash ? path : url.pathname;\r\n const to = createRoute(toPath, url.search, url.hash, flatRoutes);\r\n\r\n // Run beforeEach guards\r\n for (const guard of beforeGuards) {\r\n const result = await guard(to, from);\r\n if (result === false) {\r\n return; // Cancel navigation\r\n }\r\n }\r\n\r\n // Update browser history\r\n const fullPath = useHash ? `#${path}` : `${base}${path}`;\r\n history[method]({}, '', fullPath);\r\n\r\n // Update route signal\r\n syncRoute();\r\n\r\n // Run afterEach hooks\r\n for (const hook of afterHooks) {\r\n hook(routeSignal.value, from);\r\n }\r\n };\r\n\r\n /**\r\n * Handle popstate events (back/forward).\r\n */\r\n const handlePopState = async (): Promise<void> => {\r\n const { pathname, search, hash } = getCurrentPath();\r\n const from = routeSignal.value;\r\n const to = createRoute(pathname, search, hash, flatRoutes);\r\n\r\n // Run beforeEach guards (supports async guards)\r\n for (const guard of beforeGuards) {\r\n const result = await guard(to, from);\r\n if (result === false) {\r\n // Restore previous state\r\n const restorePath = useHash ? `#${from.path}` : `${base}${from.path}`;\r\n history.pushState({}, '', restorePath);\r\n return;\r\n }\r\n }\r\n\r\n syncRoute();\r\n\r\n for (const hook of afterHooks) {\r\n hook(routeSignal.value, from);\r\n }\r\n };\r\n\r\n // Attach popstate listener\r\n window.addEventListener('popstate', handlePopState);\r\n\r\n // Initialize route\r\n syncRoute();\r\n\r\n const router: Router = {\r\n push: (path: string) => performNavigation(path, 'pushState'),\r\n replace: (path: string) => performNavigation(path, 'replaceState'),\r\n back: () => history.back(),\r\n forward: () => history.forward(),\r\n go: (delta: number) => history.go(delta),\r\n\r\n beforeEach: (guard: NavigationGuard) => {\r\n beforeGuards.push(guard);\r\n return () => {\r\n const index = beforeGuards.indexOf(guard);\r\n if (index > -1) beforeGuards.splice(index, 1);\r\n };\r\n },\r\n\r\n afterEach: (hook: (to: Route, from: Route) => void) => {\r\n afterHooks.push(hook);\r\n return () => {\r\n const index = afterHooks.indexOf(hook);\r\n if (index > -1) afterHooks.splice(index, 1);\r\n };\r\n },\r\n\r\n currentRoute,\r\n routes: flatRoutes,\r\n\r\n destroy: () => {\r\n window.removeEventListener('popstate', handlePopState);\r\n beforeGuards.length = 0;\r\n afterHooks.length = 0;\r\n activeRouter = null;\r\n },\r\n };\r\n\r\n activeRouter = router;\r\n return router;\r\n};\r\n\r\n// ============================================================================\r\n// Utilities\r\n// ============================================================================\r\n\r\n/**\r\n * Flattens nested routes into a single array with full paths.\r\n * @internal\r\n */\r\nconst flattenRoutes = (routes: RouteDefinition[], base = ''): RouteDefinition[] => {\r\n const result: RouteDefinition[] = [];\r\n\r\n for (const route of routes) {\r\n const fullPath = route.path === '*' ? '*' : `${base}${route.path}`.replace(/\\/+/g, '/');\r\n\r\n result.push({\r\n ...route,\r\n path: fullPath,\r\n });\r\n\r\n if (route.children) {\r\n result.push(...flattenRoutes(route.children, fullPath));\r\n }\r\n }\r\n\r\n return result;\r\n};\r\n\r\n/**\r\n * Resolves a route by name and params.\r\n *\r\n * @param name - The route name\r\n * @param params - Route params to interpolate\r\n * @returns The resolved path\r\n *\r\n * @example\r\n * ```ts\r\n * import { resolve } from 'bquery/router';\r\n *\r\n * const path = resolve('user', { id: '42' });\r\n * // Returns '/user/42' if route is defined as { name: 'user', path: '/user/:id' }\r\n * ```\r\n */\r\nexport const resolve = (name: string, params: Record<string, string> = {}): string => {\r\n if (!activeRouter) {\r\n throw new Error('bQuery router: No router initialized.');\r\n }\r\n\r\n const route = activeRouter.routes.find((r) => r.name === name);\r\n if (!route) {\r\n throw new Error(`bQuery router: Route \"${name}\" not found.`);\r\n }\r\n\r\n let path = route.path;\r\n for (const [key, value] of Object.entries(params)) {\r\n path = path.replace(`:${key}`, encodeURIComponent(value));\r\n }\r\n\r\n return path;\r\n};\r\n\r\n/**\r\n * Checks if a path matches the current route.\r\n *\r\n * @param path - Path to check\r\n * @param exact - Whether to match exactly (default: false)\r\n * @returns True if the path matches\r\n *\r\n * @example\r\n * ```ts\r\n * import { isActive } from 'bquery/router';\r\n *\r\n * if (isActive('/dashboard')) {\r\n * // Highlight nav item\r\n * }\r\n * ```\r\n */\r\nexport const isActive = (path: string, exact = false): boolean => {\r\n const current = routeSignal.value.path;\r\n return exact ? current === path : current.startsWith(path);\r\n};\r\n\r\n/**\r\n * Creates a computed signal that checks if a path is active.\r\n *\r\n * @param path - Path to check\r\n * @param exact - Whether to match exactly\r\n * @returns A reactive signal\r\n *\r\n * @example\r\n * ```ts\r\n * import { isActiveSignal } from 'bquery/router';\r\n * import { effect } from 'bquery/reactive';\r\n *\r\n * const dashboardActive = isActiveSignal('/dashboard');\r\n * effect(() => {\r\n * navItem.classList.toggle('active', dashboardActive.value);\r\n * });\r\n * ```\r\n */\r\nexport const isActiveSignal = (path: string, exact = false): ReadonlySignal<boolean> => {\r\n return computed(() => {\r\n const current = routeSignal.value.path;\r\n return exact ? current === path : current.startsWith(path);\r\n });\r\n};\r\n\r\n// ============================================================================\r\n// Router Link Helper\r\n// ============================================================================\r\n\r\n/**\r\n * Creates click handler for router links.\r\n * Attach to anchor elements to enable client-side navigation.\r\n *\r\n * @param path - Target path\r\n * @param options - Navigation options\r\n * @returns Click event handler\r\n *\r\n * @example\r\n * ```ts\r\n * import { link } from 'bquery/router';\r\n * import { $ } from 'bquery/core';\r\n *\r\n * $('#nav-home').on('click', link('/'));\r\n * $('#nav-about').on('click', link('/about'));\r\n * ```\r\n */\r\nexport const link = (path: string, options: { replace?: boolean } = {}): ((e: Event) => void) => {\r\n return (e: Event) => {\r\n e.preventDefault();\r\n navigate(path, options);\r\n };\r\n};\r\n\r\n/**\r\n * Intercepts all link clicks within a container for client-side routing.\r\n * Only intercepts links with matching origins and no target attribute.\r\n *\r\n * @param container - The container element to intercept links in\r\n * @returns Cleanup function to remove the listener\r\n *\r\n * @example\r\n * ```ts\r\n * import { interceptLinks } from 'bquery/router';\r\n *\r\n * // Intercept all links in the app\r\n * const cleanup = interceptLinks(document.body);\r\n *\r\n * // Later, remove the interceptor\r\n * cleanup();\r\n * ```\r\n */\r\nexport const interceptLinks = (container: Element = document.body): (() => void) => {\r\n const handler = (e: Event) => {\r\n const target = e.target as HTMLElement;\r\n const anchor = target.closest('a');\r\n\r\n if (!anchor) return;\r\n if (anchor.target) return; // Has target attribute\r\n if (anchor.hasAttribute('download')) return;\r\n if (anchor.origin !== window.location.origin) return; // External link\r\n\r\n const path = anchor.pathname + anchor.search + anchor.hash;\r\n\r\n e.preventDefault();\r\n navigate(path);\r\n };\r\n\r\n container.addEventListener('click', handler);\r\n return () => container.removeEventListener('click', handler);\r\n};\r\n"],"names":["activeRouter","routeSignal","signal","currentRoute","computed","pathToRegex","path","PARAM_MARKER","WILDCARD_MARKER","paramNames","pattern","_","name","paramIdx","extractParamNames","matches","m","matchRoute","routes","route","regex","match","params","index","parseQuery","search","query","value","key","existing","createRoute","pathname","hash","result","navigate","options","back","forward","createRouter","base","useHash","beforeGuards","afterHooks","flatRoutes","flattenRoutes","getCurrentPath","hashPath","rest","hashPart","syncRoute","newRoute","performNavigation","method","from","url","toPath","to","guard","fullPath","hook","handlePopState","restorePath","router","delta","resolve","r","isActive","exact","current","isActiveSignal","link","interceptLinks","container","handler","anchor"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"router.es.mjs","sources":["../src/router/state.ts","../src/router/navigation.ts","../src/router/links.ts","../src/router/query.ts","../src/router/match.ts","../src/router/utils.ts","../src/router/router.ts"],"sourcesContent":["/**\n * Internal router state (active router and current route signal).\n * @module bquery/router\n */\n\nimport { computed, signal, type ReadonlySignal, type Signal } from '../reactive/index';\nimport type { Route, Router } from './types';\n\n// ============================================================================\n// Internal State\n// ============================================================================\n\n/** @internal */\nlet activeRouter: Router | null = null;\n\n/** @internal */\nexport const routeSignal: Signal<Route> = signal<Route>({\n path: '',\n params: {},\n query: {},\n matched: null,\n hash: '',\n});\n\n/**\n * Reactive signal containing the current route.\n *\n * @example\n * ```ts\n * import { currentRoute } from 'bquery/router';\n * import { effect } from 'bquery/reactive';\n *\n * effect(() => {\n * document.title = `Page: ${currentRoute.value.path}`;\n * });\n * ```\n */\nexport const currentRoute: ReadonlySignal<Route> = computed(() => routeSignal.value);\n\n/** @internal */\nexport const getActiveRouter = (): Router | null => activeRouter;\n\n/** @internal */\nexport const setActiveRouter = (router: Router | null): void => {\n activeRouter = router;\n};\n","/**\n * Navigation helpers and global router access.\n * @module bquery/router\n */\n\nimport { getActiveRouter } from './state';\n\n/**\n * Navigates to a new path.\n *\n * @param path - The path to navigate to\n * @param options - Navigation options\n *\n * @example\n * ```ts\n * import { navigate } from 'bquery/router';\n *\n * // Push to history\n * await navigate('/dashboard');\n *\n * // Replace current entry\n * await navigate('/login', { replace: true });\n * ```\n */\nexport const navigate = async (\n path: string,\n options: { replace?: boolean } = {}\n): Promise<void> => {\n const activeRouter = getActiveRouter();\n if (!activeRouter) {\n throw new Error('bQuery router: No router initialized. Call createRouter() first.');\n }\n\n await activeRouter[options.replace ? 'replace' : 'push'](path);\n};\n\n/**\n * Programmatically go back in history.\n *\n * @example\n * ```ts\n * import { back } from 'bquery/router';\n * back();\n * ```\n */\nexport const back = (): void => {\n const activeRouter = getActiveRouter();\n if (activeRouter) {\n activeRouter.back();\n } else {\n history.back();\n }\n};\n\n/**\n * Programmatically go forward in history.\n *\n * @example\n * ```ts\n * import { forward } from 'bquery/router';\n * forward();\n * ```\n */\nexport const forward = (): void => {\n const activeRouter = getActiveRouter();\n if (activeRouter) {\n activeRouter.forward();\n } else {\n history.forward();\n }\n};\n","/**\n * Link helpers for client-side navigation.\n * @module bquery/router\n */\n\nimport { getActiveRouter } from './state';\nimport { navigate } from './navigation';\n\n// ============================================================================\n// Router Link Helper\n// ============================================================================\n\n/**\n * Creates click handler for router links.\n * Attach to anchor elements to enable client-side navigation.\n *\n * @param path - Target path\n * @param options - Navigation options\n * @returns Click event handler\n *\n * @example\n * ```ts\n * import { link } from 'bquery/router';\n * import { $ } from 'bquery/core';\n *\n * $('#nav-home').on('click', link('/'));\n * $('#nav-about').on('click', link('/about'));\n * ```\n */\nexport const link = (path: string, options: { replace?: boolean } = {}): ((e: Event) => void) => {\n return (e: Event) => {\n e.preventDefault();\n void navigate(path, options).catch((err) => {\n console.error('Navigation failed:', err);\n });\n };\n};\n\n/**\n * Intercepts all link clicks within a container for client-side routing.\n * Only intercepts links with matching origins and no target attribute.\n *\n * @param container - The container element to intercept links in\n * @returns Cleanup function to remove the listener\n *\n * @example\n * ```ts\n * import { interceptLinks } from 'bquery/router';\n *\n * // Intercept all links in the app\n * const cleanup = interceptLinks(document.body);\n *\n * // Later, remove the interceptor\n * cleanup();\n * ```\n */\nexport const interceptLinks = (container?: Element): (() => void) => {\n // Provide safe default in DOM environments only\n const targetContainer = container ?? (typeof document !== 'undefined' ? document.body : null);\n if (!targetContainer) {\n // No container available (SSR or invalid input)\n return () => undefined;\n }\n\n const handler = (e: Event) => {\n // Only intercept standard left-clicks without modifier keys\n if (!(e instanceof MouseEvent)) return;\n if (e.defaultPrevented) return; // Already handled\n if (e.button !== 0) return; // Not left-click (middle-click opens new tab, right-click shows context menu)\n if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return; // Modifier keys (Ctrl/Cmd-click = new tab)\n\n // Guard against non-Element targets and non-DOM environments\n if (typeof Element === 'undefined' || !(e.target instanceof Element)) return;\n const target = e.target as HTMLElement;\n const anchor = target.closest('a');\n\n if (!anchor) return;\n\n // Cross-realm compatible anchor check: use owner document's constructor if available\n const anchorWindow = anchor.ownerDocument.defaultView;\n const AnchorConstructor = anchorWindow?.HTMLAnchorElement ?? HTMLAnchorElement;\n if (!(anchor instanceof AnchorConstructor)) return;\n\n if (anchor.target) return; // Has target attribute\n if (anchor.hasAttribute('download')) return;\n if (typeof window === 'undefined') return; // Non-window environment\n if (anchor.origin !== window.location.origin) return; // External link\n\n // Get active router config to handle base paths correctly.\n // If no router is active, proceed with no base/hash; navigate() will throw a\n // \"No router initialized\" error, which is caught and logged below.\n const router = getActiveRouter();\n if (!router) {\n // No active router - trigger navigate(), allowing its error to be logged here\n e.preventDefault();\n void navigate(anchor.pathname + anchor.search + anchor.hash).catch((err) => {\n console.error('Navigation failed:', err);\n });\n return;\n }\n\n const base = router.base;\n const useHash = router.hash;\n\n // Detect hash-routing mode: links written as href=\"#/page\"\n // In this case, anchor.hash contains the route path\n let path: string;\n if (useHash && anchor.hash && anchor.hash.startsWith('#/')) {\n // Hash-routing mode: extract path from the hash\n // e.g., href=\"#/page?foo=bar\" → path = \"/page?foo=bar\"\n path = anchor.hash.slice(1); // Remove leading #\n } else {\n // History mode: use pathname + search + hash\n // Strip base from pathname to avoid duplication (router.push() re-adds it)\n let pathname = anchor.pathname;\n if (base && base !== '/' && pathname.startsWith(base)) {\n pathname = pathname.slice(base.length) || '/';\n }\n path = pathname + anchor.search + anchor.hash;\n }\n\n e.preventDefault();\n void navigate(path).catch((err) => {\n console.error('Navigation failed:', err);\n });\n };\n\n targetContainer.addEventListener('click', handler);\n return () => targetContainer.removeEventListener('click', handler);\n};\n","/**\n * Query string helpers.\n * @module bquery/router\n */\n\n/**\n * Parses query string into an object.\n * Single values are stored as strings, duplicate keys become arrays.\n * @internal\n *\n * @example\n * parseQuery('?foo=1') // { foo: '1' }\n * parseQuery('?tag=a&tag=b') // { tag: ['a', 'b'] }\n * parseQuery('?x=1&y=2&x=3') // { x: ['1', '3'], y: '2' }\n */\nexport const parseQuery = (search: string): Record<string, string | string[]> => {\n const query: Record<string, string | string[]> = {};\n const params = new URLSearchParams(search);\n\n params.forEach((value, key) => {\n const existing = query[key];\n if (existing === undefined) {\n // First occurrence: store as string\n query[key] = value;\n } else if (Array.isArray(existing)) {\n // Already an array: append\n existing.push(value);\n } else {\n // Second occurrence: convert to array\n query[key] = [existing, value];\n }\n });\n\n return query;\n};\n","/**\n * Route matching helpers.\n * @module bquery/router\n */\n\nimport { parseQuery } from './query';\nimport type { Route, RouteDefinition } from './types';\n\n// ============================================================================\n// Route Matching\n// ============================================================================\n\n/**\n * Converts a route path pattern to a RegExp for matching.\n * Uses placeholder approach to preserve :param and * patterns during escaping.\n * Returns positional capture groups for maximum compatibility.\n * @internal\n */\nconst pathToRegex = (path: string): RegExp => {\n // Handle wildcard-only route\n if (path === '*') {\n return /^.*$/;\n }\n\n // Unique placeholders using null chars (won't appear in normal paths)\n const PARAM_MARKER = '\\u0000P\\u0000';\n const WILDCARD_MARKER = '\\u0000W\\u0000';\n\n // Step 1: Extract :param patterns before escaping\n let pattern = path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, () => {\n return PARAM_MARKER;\n });\n\n // Step 2: Extract * wildcards before escaping\n pattern = pattern.replace(/\\*/g, WILDCARD_MARKER);\n\n // Step 3: Escape ALL regex metacharacters: \\ ^ $ . * + ? ( ) [ ] { } |\n pattern = pattern.replace(/[\\\\^$.*+?()[\\]{}|]/g, '\\\\$&');\n\n // Step 4: Restore param capture groups (positional, not named)\n pattern = pattern.replace(/\\u0000P\\u0000/g, () => `([^/]+)`);\n\n // Step 5: Restore wildcards as .*\n pattern = pattern.replace(/\\u0000W\\u0000/g, '.*');\n\n return new RegExp(`^${pattern}$`);\n};\n\n/**\n * Extracts param names from a route path.\n * @internal\n */\nconst extractParamNames = (path: string): string[] => {\n const matches = path.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g);\n return matches ? matches.map((m) => m.slice(1)) : [];\n};\n\n/**\n * Matches a path against route definitions and extracts params.\n * Uses positional captures for maximum compatibility.\n * @internal\n */\nexport const matchRoute = (\n path: string,\n routes: RouteDefinition[]\n): { matched: RouteDefinition; params: Record<string, string> } | null => {\n for (const route of routes) {\n const regex = pathToRegex(route.path);\n const match = path.match(regex);\n\n if (match) {\n const paramNames = extractParamNames(route.path);\n const params: Record<string, string> = {};\n\n // Map positional captures to param names\n paramNames.forEach((name, index) => {\n params[name] = match[index + 1] || '';\n });\n\n return { matched: route, params };\n }\n }\n\n return null;\n};\n\n/**\n * Creates a Route object from the current URL.\n * @internal\n */\nexport const createRoute = (\n pathname: string,\n search: string,\n hash: string,\n routes: RouteDefinition[]\n): Route => {\n const result = matchRoute(pathname, routes);\n\n return {\n path: pathname,\n params: result?.params ?? {},\n query: parseQuery(search),\n matched: result?.matched ?? null,\n hash: hash.replace(/^#/, ''),\n };\n};\n","/**\n * Router utilities.\n * @module bquery/router\n */\n\nimport { computed, type ReadonlySignal } from '../reactive/index';\nimport { getActiveRouter, routeSignal } from './state';\nimport type { RouteDefinition } from './types';\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Flattens nested routes into a single array with full paths.\n * Does NOT include the router base - base is only for browser history.\n * @internal\n */\nexport const flattenRoutes = (routes: RouteDefinition[], parentPath = ''): RouteDefinition[] => {\n const result: RouteDefinition[] = [];\n\n for (const route of routes) {\n const fullPath = route.path === '*' ? '*' : `${parentPath}${route.path}`.replace(/\\/+/g, '/');\n\n result.push({\n ...route,\n path: fullPath,\n });\n\n if (route.children) {\n result.push(...flattenRoutes(route.children, fullPath));\n }\n }\n\n return result;\n};\n\n/**\n * Resolves a route by name and params.\n *\n * @param name - The route name\n * @param params - Route params to interpolate\n * @returns The resolved path\n *\n * @example\n * ```ts\n * import { resolve } from 'bquery/router';\n *\n * const path = resolve('user', { id: '42' });\n * // Returns '/user/42' if route is defined as { name: 'user', path: '/user/:id' }\n * ```\n */\nexport const resolve = (name: string, params: Record<string, string> = {}): string => {\n const activeRouter = getActiveRouter();\n if (!activeRouter) {\n throw new Error('bQuery router: No router initialized.');\n }\n\n const route = activeRouter.routes.find((r) => r.name === name);\n if (!route) {\n throw new Error(`bQuery router: Route \"${name}\" not found.`);\n }\n\n let path = route.path;\n for (const [key, value] of Object.entries(params)) {\n path = path.replace(`:${key}`, encodeURIComponent(value));\n }\n\n return path;\n};\n\n/**\n * Checks if a path matches the current route.\n *\n * @param path - Path to check\n * @param exact - Whether to match exactly (default: false)\n * @returns True if the path matches\n *\n * @example\n * ```ts\n * import { isActive } from 'bquery/router';\n *\n * if (isActive('/dashboard')) {\n * // Highlight nav item\n * }\n * ```\n */\nexport const isActive = (path: string, exact = false): boolean => {\n const current = routeSignal.value.path;\n return exact ? current === path : current.startsWith(path);\n};\n\n/**\n * Creates a computed signal that checks if a path is active.\n *\n * @param path - Path to check\n * @param exact - Whether to match exactly\n * @returns A reactive signal\n *\n * @example\n * ```ts\n * import { isActiveSignal } from 'bquery/router';\n * import { effect } from 'bquery/reactive';\n *\n * const dashboardActive = isActiveSignal('/dashboard');\n * effect(() => {\n * navItem.classList.toggle('active', dashboardActive.value);\n * });\n * ```\n */\nexport const isActiveSignal = (path: string, exact = false): ReadonlySignal<boolean> => {\n return computed(() => {\n const current = routeSignal.value.path;\n return exact ? current === path : current.startsWith(path);\n });\n};\n","/**\n * Router creation and lifecycle management.\n * @module bquery/router\n */\n\nimport { createRoute } from './match';\nimport { currentRoute, getActiveRouter, routeSignal, setActiveRouter } from './state';\nimport type { NavigationGuard, Route, Router, RouterOptions } from './types';\nimport { flattenRoutes } from './utils';\n\n// ============================================================================\n// Router Creation\n// ============================================================================\n\n/**\n * Creates and initializes a router instance.\n *\n * @param options - Router configuration\n * @returns The router instance\n *\n * @example\n * ```ts\n * import { createRouter } from 'bquery/router';\n *\n * const router = createRouter({\n * routes: [\n * { path: '/', component: () => import('./pages/Home') },\n * { path: '/about', component: () => import('./pages/About') },\n * { path: '/user/:id', component: () => import('./pages/User') },\n * { path: '*', component: () => import('./pages/NotFound') },\n * ],\n * base: '/app',\n * });\n *\n * router.beforeEach((to, from) => {\n * if (to.path === '/admin' && !isAuthenticated()) {\n * return false; // Cancel navigation\n * }\n * });\n * ```\n */\nexport const createRouter = (options: RouterOptions): Router => {\n // Clean up any existing router to prevent guard leakage\n const existingRouter = getActiveRouter();\n if (existingRouter) {\n existingRouter.destroy();\n }\n\n const { routes, base = '', hash: useHash = false } = options;\n\n // Instance-specific guards and hooks (not shared globally)\n const beforeGuards: NavigationGuard[] = [];\n const afterHooks: Array<(to: Route, from: Route) => void> = [];\n\n // Flatten nested routes (base-relative, not including the base path)\n const flatRoutes = flattenRoutes(routes);\n\n /**\n * Gets the current path from the URL.\n */\n const getCurrentPath = (): { pathname: string; search: string; hash: string } => {\n if (useHash) {\n const hashPath = window.location.hash.slice(1) || '/';\n // In hash routing, URL structure is #/path?query#fragment\n // Extract hash fragment first (after the second #)\n const [pathWithQuery, hashPart = ''] = hashPath.split('#');\n // Then extract query from the path\n const [pathname, search = ''] = pathWithQuery.split('?');\n return {\n pathname,\n search: search ? `?${search}` : '',\n hash: hashPart ? `#${hashPart}` : '',\n };\n }\n\n let pathname = window.location.pathname;\n if (base && (pathname === base || pathname.startsWith(base + '/'))) {\n pathname = pathname.slice(base.length) || '/';\n }\n\n return {\n pathname,\n search: window.location.search,\n hash: window.location.hash,\n };\n };\n\n /**\n * Updates the route signal with current URL state.\n */\n const syncRoute = (): void => {\n const { pathname, search, hash } = getCurrentPath();\n const newRoute = createRoute(pathname, search, hash, flatRoutes);\n routeSignal.value = newRoute;\n };\n\n /**\n * Performs navigation with guards.\n */\n const performNavigation = async (\n path: string,\n method: 'pushState' | 'replaceState'\n ): Promise<void> => {\n const { pathname, search, hash } = getCurrentPath();\n const from = createRoute(pathname, search, hash, flatRoutes);\n\n // Parse the target path\n const url = new URL(path, window.location.origin);\n const to = createRoute(url.pathname, url.search, url.hash, flatRoutes);\n\n // Run beforeEach guards\n for (const guard of beforeGuards) {\n const result = await guard(to, from);\n if (result === false) {\n return; // Cancel navigation\n }\n }\n\n // Update browser history\n const fullPath = useHash ? `#${path}` : `${base}${path}`;\n history[method]({}, '', fullPath);\n\n // Update route signal\n syncRoute();\n\n // Run afterEach hooks\n for (const hook of afterHooks) {\n hook(routeSignal.value, from);\n }\n };\n\n /**\n * Handle popstate events (back/forward).\n */\n const handlePopState = async (): Promise<void> => {\n const { pathname, search, hash } = getCurrentPath();\n const from = routeSignal.value;\n const to = createRoute(pathname, search, hash, flatRoutes);\n\n // Run beforeEach guards (supports async guards)\n for (const guard of beforeGuards) {\n const result = await guard(to, from);\n if (result === false) {\n // Restore previous state with full URL (including query/hash)\n const queryString = new URLSearchParams(\n Object.entries(from.query).flatMap(([key, value]) =>\n Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]]\n )\n ).toString();\n const search = queryString ? `?${queryString}` : '';\n const hash = from.hash ? `#${from.hash}` : '';\n const restorePath = useHash\n ? `#${from.path}${search}${hash}`\n : `${base}${from.path}${search}${hash}`;\n history.replaceState({}, '', restorePath);\n return;\n }\n }\n\n syncRoute();\n\n for (const hook of afterHooks) {\n hook(routeSignal.value, from);\n }\n };\n\n // Attach popstate listener\n window.addEventListener('popstate', handlePopState);\n\n // Initialize route\n syncRoute();\n\n const router: Router = {\n push: (path: string) => performNavigation(path, 'pushState'),\n replace: (path: string) => performNavigation(path, 'replaceState'),\n back: () => history.back(),\n forward: () => history.forward(),\n go: (delta: number) => history.go(delta),\n\n beforeEach: (guard: NavigationGuard) => {\n beforeGuards.push(guard);\n return () => {\n const index = beforeGuards.indexOf(guard);\n if (index > -1) beforeGuards.splice(index, 1);\n };\n },\n\n afterEach: (hook: (to: Route, from: Route) => void) => {\n afterHooks.push(hook);\n return () => {\n const index = afterHooks.indexOf(hook);\n if (index > -1) afterHooks.splice(index, 1);\n };\n },\n\n currentRoute,\n routes: flatRoutes,\n base,\n hash: useHash,\n\n destroy: () => {\n window.removeEventListener('popstate', handlePopState);\n beforeGuards.length = 0;\n afterHooks.length = 0;\n setActiveRouter(null);\n },\n };\n\n setActiveRouter(router);\n return router;\n};\n"],"names":["activeRouter","routeSignal","signal","currentRoute","computed","getActiveRouter","setActiveRouter","router","navigate","path","options","back","forward","link","e","err","interceptLinks","container","targetContainer","handler","anchor","AnchorConstructor","base","useHash","pathname","parseQuery","search","query","value","key","existing","pathToRegex","PARAM_MARKER","WILDCARD_MARKER","pattern","extractParamNames","matches","m","matchRoute","routes","route","regex","match","paramNames","params","name","index","createRoute","hash","result","flattenRoutes","parentPath","fullPath","resolve","r","isActive","exact","current","isActiveSignal","createRouter","existingRouter","beforeGuards","afterHooks","flatRoutes","getCurrentPath","hashPath","pathWithQuery","hashPart","syncRoute","newRoute","performNavigation","method","from","url","to","guard","hook","handlePopState","queryString","v","restorePath","delta"],"mappings":"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"}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
const z = "bquery-sanitizer", v = /* @__PURE__ */ new Set([
|
|
2
|
+
"a",
|
|
3
|
+
"abbr",
|
|
4
|
+
"address",
|
|
5
|
+
"article",
|
|
6
|
+
"aside",
|
|
7
|
+
"b",
|
|
8
|
+
"bdi",
|
|
9
|
+
"bdo",
|
|
10
|
+
"blockquote",
|
|
11
|
+
"br",
|
|
12
|
+
"button",
|
|
13
|
+
"caption",
|
|
14
|
+
"cite",
|
|
15
|
+
"code",
|
|
16
|
+
"col",
|
|
17
|
+
"colgroup",
|
|
18
|
+
"data",
|
|
19
|
+
"dd",
|
|
20
|
+
"del",
|
|
21
|
+
"details",
|
|
22
|
+
"dfn",
|
|
23
|
+
"div",
|
|
24
|
+
"dl",
|
|
25
|
+
"dt",
|
|
26
|
+
"em",
|
|
27
|
+
"figcaption",
|
|
28
|
+
"figure",
|
|
29
|
+
"footer",
|
|
30
|
+
"form",
|
|
31
|
+
"h1",
|
|
32
|
+
"h2",
|
|
33
|
+
"h3",
|
|
34
|
+
"h4",
|
|
35
|
+
"h5",
|
|
36
|
+
"h6",
|
|
37
|
+
"header",
|
|
38
|
+
"hgroup",
|
|
39
|
+
"hr",
|
|
40
|
+
"i",
|
|
41
|
+
"img",
|
|
42
|
+
"input",
|
|
43
|
+
"ins",
|
|
44
|
+
"kbd",
|
|
45
|
+
"label",
|
|
46
|
+
"legend",
|
|
47
|
+
"li",
|
|
48
|
+
"main",
|
|
49
|
+
"mark",
|
|
50
|
+
"nav",
|
|
51
|
+
"ol",
|
|
52
|
+
"optgroup",
|
|
53
|
+
"option",
|
|
54
|
+
"p",
|
|
55
|
+
"picture",
|
|
56
|
+
"pre",
|
|
57
|
+
"progress",
|
|
58
|
+
"q",
|
|
59
|
+
"rp",
|
|
60
|
+
"rt",
|
|
61
|
+
"ruby",
|
|
62
|
+
"s",
|
|
63
|
+
"samp",
|
|
64
|
+
"section",
|
|
65
|
+
"select",
|
|
66
|
+
"small",
|
|
67
|
+
"source",
|
|
68
|
+
"span",
|
|
69
|
+
"strong",
|
|
70
|
+
"sub",
|
|
71
|
+
"summary",
|
|
72
|
+
"sup",
|
|
73
|
+
"table",
|
|
74
|
+
"tbody",
|
|
75
|
+
"td",
|
|
76
|
+
"textarea",
|
|
77
|
+
"tfoot",
|
|
78
|
+
"th",
|
|
79
|
+
"thead",
|
|
80
|
+
"time",
|
|
81
|
+
"tr",
|
|
82
|
+
"u",
|
|
83
|
+
"ul",
|
|
84
|
+
"var",
|
|
85
|
+
"wbr"
|
|
86
|
+
]), A = /* @__PURE__ */ new Set([
|
|
87
|
+
"script",
|
|
88
|
+
"iframe",
|
|
89
|
+
"frame",
|
|
90
|
+
"frameset",
|
|
91
|
+
"object",
|
|
92
|
+
"embed",
|
|
93
|
+
"applet",
|
|
94
|
+
"link",
|
|
95
|
+
"meta",
|
|
96
|
+
"style",
|
|
97
|
+
"base",
|
|
98
|
+
"template",
|
|
99
|
+
"slot",
|
|
100
|
+
"math",
|
|
101
|
+
"svg",
|
|
102
|
+
"foreignobject",
|
|
103
|
+
"noscript"
|
|
104
|
+
]), N = /* @__PURE__ */ new Set([
|
|
105
|
+
// Global objects
|
|
106
|
+
"document",
|
|
107
|
+
"window",
|
|
108
|
+
"location",
|
|
109
|
+
"top",
|
|
110
|
+
"self",
|
|
111
|
+
"parent",
|
|
112
|
+
"frames",
|
|
113
|
+
"history",
|
|
114
|
+
"navigator",
|
|
115
|
+
"screen",
|
|
116
|
+
// Dangerous functions
|
|
117
|
+
"alert",
|
|
118
|
+
"confirm",
|
|
119
|
+
"prompt",
|
|
120
|
+
"eval",
|
|
121
|
+
"function",
|
|
122
|
+
// Document properties
|
|
123
|
+
"cookie",
|
|
124
|
+
"domain",
|
|
125
|
+
"referrer",
|
|
126
|
+
"body",
|
|
127
|
+
"head",
|
|
128
|
+
"forms",
|
|
129
|
+
"images",
|
|
130
|
+
"links",
|
|
131
|
+
"scripts",
|
|
132
|
+
// DOM traversal properties
|
|
133
|
+
"children",
|
|
134
|
+
"parentnode",
|
|
135
|
+
"firstchild",
|
|
136
|
+
"lastchild",
|
|
137
|
+
// Content manipulation
|
|
138
|
+
"innerhtml",
|
|
139
|
+
"outerhtml",
|
|
140
|
+
"textcontent"
|
|
141
|
+
]), D = /* @__PURE__ */ new Set([
|
|
142
|
+
"alt",
|
|
143
|
+
"class",
|
|
144
|
+
"dir",
|
|
145
|
+
"height",
|
|
146
|
+
"hidden",
|
|
147
|
+
"href",
|
|
148
|
+
"id",
|
|
149
|
+
"lang",
|
|
150
|
+
"loading",
|
|
151
|
+
"name",
|
|
152
|
+
"rel",
|
|
153
|
+
"role",
|
|
154
|
+
"src",
|
|
155
|
+
"srcset",
|
|
156
|
+
"tabindex",
|
|
157
|
+
"target",
|
|
158
|
+
"title",
|
|
159
|
+
"type",
|
|
160
|
+
"width",
|
|
161
|
+
"aria-*"
|
|
162
|
+
]), F = ["on", "formaction", "xlink:", "xmlns:"], O = ["javascript:", "data:", "vbscript:", "file:"], R = (t, e, r) => {
|
|
163
|
+
const s = t.toLowerCase();
|
|
164
|
+
for (const c of F)
|
|
165
|
+
if (s.startsWith(c)) return !1;
|
|
166
|
+
return r && s.startsWith("data-") || s.startsWith("aria-") ? !0 : e.has(s);
|
|
167
|
+
}, W = (t) => {
|
|
168
|
+
const e = t.toLowerCase().trim();
|
|
169
|
+
return !N.has(e);
|
|
170
|
+
}, U = (t) => t.replace(/[\u0000-\u001F\u007F]+/g, "").replace(/[\u200B-\u200D\uFEFF\u2028\u2029]+/g, "").replace(/\\u[\da-fA-F]{4}/g, "").replace(/\s+/g, "").toLowerCase(), _ = (t) => {
|
|
171
|
+
const e = U(t);
|
|
172
|
+
for (const r of O)
|
|
173
|
+
if (e.startsWith(r)) return !1;
|
|
174
|
+
return !0;
|
|
175
|
+
}, k = (t) => {
|
|
176
|
+
try {
|
|
177
|
+
const e = t.trim();
|
|
178
|
+
if (e.startsWith("//"))
|
|
179
|
+
return !0;
|
|
180
|
+
const r = e.toLowerCase();
|
|
181
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(e) && !r.startsWith("http://") && !r.startsWith("https://") ? !0 : !r.startsWith("http://") && !r.startsWith("https://") ? !1 : typeof window > "u" || !window.location ? !0 : new URL(e, window.location.href).origin !== window.location.origin;
|
|
182
|
+
} catch {
|
|
183
|
+
return !0;
|
|
184
|
+
}
|
|
185
|
+
}, H = (t) => new DOMParser().parseFromString(t, "text/html"), S = (t) => {
|
|
186
|
+
const e = (typeof t == "string" ? t : String(t ?? "")).trim(), r = document.createDocumentFragment();
|
|
187
|
+
if (e.length === 0)
|
|
188
|
+
return r;
|
|
189
|
+
if (!(e.includes("<") || e.includes(">")))
|
|
190
|
+
return r.appendChild(document.createTextNode(e)), r;
|
|
191
|
+
const l = H(e).body;
|
|
192
|
+
if (!l)
|
|
193
|
+
return r;
|
|
194
|
+
for (; l.firstChild; )
|
|
195
|
+
r.appendChild(l.firstChild);
|
|
196
|
+
return r;
|
|
197
|
+
}, E = (t, e = {}) => {
|
|
198
|
+
const {
|
|
199
|
+
allowTags: r = [],
|
|
200
|
+
allowAttributes: s = [],
|
|
201
|
+
allowDataAttributes: c = !0,
|
|
202
|
+
stripAllTags: l = !1
|
|
203
|
+
} = e, T = new Set(
|
|
204
|
+
[...v, ...r.map((o) => o.toLowerCase())].filter(
|
|
205
|
+
(o) => !A.has(o)
|
|
206
|
+
)
|
|
207
|
+
), L = /* @__PURE__ */ new Set([
|
|
208
|
+
...D,
|
|
209
|
+
...s.map((o) => o.toLowerCase())
|
|
210
|
+
]), u = S(t);
|
|
211
|
+
if (l)
|
|
212
|
+
return u.textContent ?? "";
|
|
213
|
+
const p = document.createTreeWalker(u, NodeFilter.SHOW_ELEMENT), f = [];
|
|
214
|
+
for (; p.nextNode(); ) {
|
|
215
|
+
const o = p.currentNode, i = o.tagName.toLowerCase();
|
|
216
|
+
if (A.has(i)) {
|
|
217
|
+
f.push(o);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (!T.has(i)) {
|
|
221
|
+
f.push(o);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const d = [];
|
|
225
|
+
for (const n of Array.from(o.attributes)) {
|
|
226
|
+
const a = n.name.toLowerCase();
|
|
227
|
+
if (!R(a, L, c)) {
|
|
228
|
+
d.push(n.name);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if ((a === "id" || a === "name") && !W(n.value)) {
|
|
232
|
+
d.push(n.name);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
(a === "href" || a === "src" || a === "srcset") && !_(n.value) && d.push(n.name);
|
|
236
|
+
}
|
|
237
|
+
for (const n of d)
|
|
238
|
+
o.removeAttribute(n);
|
|
239
|
+
if (i === "a") {
|
|
240
|
+
const n = o.getAttribute("href"), y = o.getAttribute("target")?.toLowerCase() === "_blank", C = n && k(n);
|
|
241
|
+
if (y || C) {
|
|
242
|
+
const b = o.getAttribute("rel"), m = new Set(b ? b.split(/\s+/).filter(Boolean) : []);
|
|
243
|
+
m.add("noopener"), m.add("noreferrer"), o.setAttribute("rel", Array.from(m).join(" "));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
for (const o of f)
|
|
248
|
+
o.remove();
|
|
249
|
+
const h = (o) => {
|
|
250
|
+
const i = document.createElement("div");
|
|
251
|
+
return i.appendChild(o.cloneNode(!0)), i.innerHTML;
|
|
252
|
+
}, g = h(u), x = S(g), w = h(x);
|
|
253
|
+
return g !== w ? u.textContent ?? "" : w;
|
|
254
|
+
}, P = (t, e = {}) => E(t, e), j = (t) => {
|
|
255
|
+
const e = {
|
|
256
|
+
"&": "&",
|
|
257
|
+
"<": "<",
|
|
258
|
+
">": ">",
|
|
259
|
+
'"': """,
|
|
260
|
+
"'": "'",
|
|
261
|
+
"`": "`"
|
|
262
|
+
};
|
|
263
|
+
return t.replace(/[&<>"'`]/g, (r) => e[r]);
|
|
264
|
+
}, G = (t) => E(t, { stripAllTags: !0 });
|
|
265
|
+
export {
|
|
266
|
+
z as P,
|
|
267
|
+
G as a,
|
|
268
|
+
E as b,
|
|
269
|
+
j as e,
|
|
270
|
+
P as s
|
|
271
|
+
};
|
|
272
|
+
//# sourceMappingURL=sanitize-1FBEPAFH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize-1FBEPAFH.js","sources":["../src/security/constants.ts","../src/security/sanitize-core.ts","../src/security/sanitize.ts"],"sourcesContent":["/**\n * Security constants and safe lists.\n *\n * @module bquery/security\n */\n\n/**\n * Trusted Types policy name.\n */\nexport const POLICY_NAME = 'bquery-sanitizer';\n\n/**\n * Default allowed HTML tags considered safe.\n */\nexport const DEFAULT_ALLOWED_TAGS = new Set([\n 'a',\n 'abbr',\n 'address',\n 'article',\n 'aside',\n 'b',\n 'bdi',\n 'bdo',\n 'blockquote',\n 'br',\n 'button',\n 'caption',\n 'cite',\n 'code',\n 'col',\n 'colgroup',\n 'data',\n 'dd',\n 'del',\n 'details',\n 'dfn',\n 'div',\n 'dl',\n 'dt',\n 'em',\n 'figcaption',\n 'figure',\n 'footer',\n 'form',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'header',\n 'hgroup',\n 'hr',\n 'i',\n 'img',\n 'input',\n 'ins',\n 'kbd',\n 'label',\n 'legend',\n 'li',\n 'main',\n 'mark',\n 'nav',\n 'ol',\n 'optgroup',\n 'option',\n 'p',\n 'picture',\n 'pre',\n 'progress',\n 'q',\n 'rp',\n 'rt',\n 'ruby',\n 's',\n 'samp',\n 'section',\n 'select',\n 'small',\n 'source',\n 'span',\n 'strong',\n 'sub',\n 'summary',\n 'sup',\n 'table',\n 'tbody',\n 'td',\n 'textarea',\n 'tfoot',\n 'th',\n 'thead',\n 'time',\n 'tr',\n 'u',\n 'ul',\n 'var',\n 'wbr',\n]);\n\n/**\n * Explicitly dangerous tags that should never be allowed.\n * These are checked even if somehow added to allowTags.\n */\nexport const DANGEROUS_TAGS = new Set([\n 'script',\n 'iframe',\n 'frame',\n 'frameset',\n 'object',\n 'embed',\n 'applet',\n 'link',\n 'meta',\n 'style',\n 'base',\n 'template',\n 'slot',\n 'math',\n 'svg',\n 'foreignobject',\n 'noscript',\n]);\n\n/**\n * Reserved IDs that could cause DOM clobbering attacks.\n * These are prevented to avoid overwriting global browser objects.\n */\nexport const RESERVED_IDS = new Set([\n // Global objects\n 'document',\n 'window',\n 'location',\n 'top',\n 'self',\n 'parent',\n 'frames',\n 'history',\n 'navigator',\n 'screen',\n // Dangerous functions\n 'alert',\n 'confirm',\n 'prompt',\n 'eval',\n 'function',\n // Document properties\n 'cookie',\n 'domain',\n 'referrer',\n 'body',\n 'head',\n 'forms',\n 'images',\n 'links',\n 'scripts',\n // DOM traversal properties\n 'children',\n 'parentnode',\n 'firstchild',\n 'lastchild',\n // Content manipulation\n 'innerhtml',\n 'outerhtml',\n 'textcontent',\n]);\n\n/**\n * Default allowed attributes considered safe.\n * Note: 'style' is excluded by default because inline CSS can be abused for:\n * - UI redressing attacks\n * - Data exfiltration via url() in CSS\n * - CSS injection vectors\n * If you need to allow inline styles, add 'style' to allowAttributes in your\n * sanitizeHtml options, but ensure you implement proper CSS value validation.\n */\nexport const DEFAULT_ALLOWED_ATTRIBUTES = new Set([\n 'alt',\n 'class',\n 'dir',\n 'height',\n 'hidden',\n 'href',\n 'id',\n 'lang',\n 'loading',\n 'name',\n 'rel',\n 'role',\n 'src',\n 'srcset',\n 'tabindex',\n 'target',\n 'title',\n 'type',\n 'width',\n 'aria-*',\n]);\n\n/**\n * Dangerous attribute prefixes to always remove.\n */\nexport const DANGEROUS_ATTR_PREFIXES = ['on', 'formaction', 'xlink:', 'xmlns:'];\n\n/**\n * Dangerous URL protocols to block.\n */\nexport const DANGEROUS_PROTOCOLS = ['javascript:', 'data:', 'vbscript:', 'file:'];\n","/**\n * Core HTML sanitization logic.\n *\n * @module bquery/security\n * @internal\n */\n\nimport {\n DANGEROUS_ATTR_PREFIXES,\n DANGEROUS_PROTOCOLS,\n DANGEROUS_TAGS,\n DEFAULT_ALLOWED_ATTRIBUTES,\n DEFAULT_ALLOWED_TAGS,\n RESERVED_IDS,\n} from './constants';\nimport type { SanitizeOptions } from './types';\n\n/**\n * Check if an attribute name is allowed.\n * @internal\n */\nconst isAllowedAttribute = (\n name: string,\n allowedSet: Set<string>,\n allowDataAttrs: boolean\n): boolean => {\n const lowerName = name.toLowerCase();\n\n // Check dangerous prefixes\n for (const prefix of DANGEROUS_ATTR_PREFIXES) {\n if (lowerName.startsWith(prefix)) return false;\n }\n\n // Check data attributes\n if (allowDataAttrs && lowerName.startsWith('data-')) return true;\n\n // Check aria attributes (allowed by default)\n if (lowerName.startsWith('aria-')) return true;\n\n // Check explicit allow list\n return allowedSet.has(lowerName);\n};\n\n/**\n * Check if an ID/name value could cause DOM clobbering.\n * @internal\n */\nconst isSafeIdOrName = (value: string): boolean => {\n const lowerValue = value.toLowerCase().trim();\n return !RESERVED_IDS.has(lowerValue);\n};\n\n/**\n * Normalize URL by removing control characters, whitespace, and Unicode tricks.\n * Enhanced to prevent various bypass techniques.\n * @internal\n */\nconst normalizeUrl = (value: string): string =>\n value\n // Remove null bytes and control characters\n .replace(/[\\u0000-\\u001F\\u007F]+/g, '')\n // Remove zero-width characters that could hide malicious content\n .replace(/[\\u200B-\\u200D\\uFEFF\\u2028\\u2029]+/g, '')\n // Remove escaped Unicode sequences\n .replace(/\\\\u[\\da-fA-F]{4}/g, '')\n // Remove whitespace\n .replace(/\\s+/g, '')\n // Normalize case\n .toLowerCase();\n\n/**\n * Check if a URL value is safe.\n * @internal\n */\nconst isSafeUrl = (value: string): boolean => {\n const normalized = normalizeUrl(value);\n for (const protocol of DANGEROUS_PROTOCOLS) {\n if (normalized.startsWith(protocol)) return false;\n }\n return true;\n};\n\n/**\n * Check if a URL is external (different origin).\n * @internal\n */\nconst isExternalUrl = (url: string): boolean => {\n try {\n // Normalize URL by trimming whitespace\n const trimmedUrl = url.trim();\n\n // Protocol-relative URLs (//example.com) are always external.\n // CRITICAL: This check must run before the relative-URL check below;\n // otherwise, a protocol-relative URL like \"//evil.com\" would be treated\n // as a non-http(s) relative URL and incorrectly classified as same-origin.\n // Handling them up front guarantees correct security classification.\n if (trimmedUrl.startsWith('//')) {\n return true;\n }\n\n // Normalize URL for case-insensitive protocol checks\n const lowerUrl = trimmedUrl.toLowerCase();\n\n // Check for non-http(s) protocols which are considered external/special\n // (mailto:, tel:, ftp:, etc.)\n const hasProtocol = /^[a-z][a-z0-9+.-]*:/i.test(trimmedUrl);\n if (hasProtocol && !lowerUrl.startsWith('http://') && !lowerUrl.startsWith('https://')) {\n // These are special protocols, not traditional \"external\" links\n // but we treat them as external for security consistency\n return true;\n }\n\n // Relative URLs are not external\n if (!lowerUrl.startsWith('http://') && !lowerUrl.startsWith('https://')) {\n return false;\n }\n\n // In non-browser environments (e.g., Node.js), treat all absolute URLs as external\n if (typeof window === 'undefined' || !window.location) {\n return true;\n }\n\n const urlObj = new URL(trimmedUrl, window.location.href);\n return urlObj.origin !== window.location.origin;\n } catch {\n // If URL parsing fails, treat as potentially external for safety\n return true;\n }\n};\n\n/**\n * Parse an HTML string into a Document using DOMParser.\n * This helper is intentionally separated to make the control-flow around HTML parsing\n * explicit for static analysis tools. It should ONLY be called when the input is\n * known to contain HTML syntax (angle brackets).\n *\n * DOMParser creates an inert document where scripts don't execute, making it safe\n * for parsing untrusted HTML that will subsequently be sanitized.\n *\n * @param htmlContent - A string that is known to contain HTML markup (has < or >)\n * @returns The parsed Document\n * @internal\n */\nconst parseHtmlDocument = (htmlContent: string): Document => {\n const parser = new DOMParser();\n // Parse as a full HTML document in an inert context; scripts won't execute\n return parser.parseFromString(htmlContent, 'text/html');\n};\n\n/**\n * Safely parse HTML string into a DocumentFragment using DOMParser.\n * DOMParser is preferred over innerHTML for security as it creates an inert document\n * where scripts don't execute and provides better static analysis recognition.\n *\n * This function includes input normalization to satisfy static analysis tools:\n * - Coerces input to string and trims whitespace\n * - For plain text (no HTML tags), creates a Text node directly without parsing\n * - Only invokes DOMParser for actual HTML-like content via parseHtmlDocument\n *\n * The separation between plain text handling and HTML parsing is intentional:\n * DOM text that contains no HTML syntax is never fed into an HTML parser,\n * preventing \"DOM text reinterpreted as HTML\" issues.\n *\n * @internal\n */\nconst parseHtmlSafely = (html: string): DocumentFragment => {\n // Step 1: Normalize input - coerce to string and trim\n // This defensive check handles edge cases even though TypeScript says it's a string\n const normalizedHtml = (typeof html === 'string' ? html : String(html ?? '')).trim();\n\n // Step 2: Create the fragment that will hold our result\n const fragment = document.createDocumentFragment();\n\n // Step 3: Early return for empty input\n if (normalizedHtml.length === 0) {\n return fragment;\n }\n\n // Step 4: If input contains no angle brackets, it's plain text - no HTML parsing needed.\n // Plain text is handled as a Text node, never passed to an HTML parser.\n // This explicitly prevents \"DOM text reinterpreted as HTML\" for purely textual inputs.\n const containsHtmlSyntax = normalizedHtml.includes('<') || normalizedHtml.includes('>');\n if (!containsHtmlSyntax) {\n fragment.appendChild(document.createTextNode(normalizedHtml));\n return fragment;\n }\n\n // Step 5: Input contains HTML syntax - parse it via the dedicated HTML parsing helper.\n // This separation makes the data-flow explicit: only strings with HTML syntax\n // are passed to DOMParser, satisfying static analysis requirements.\n const doc = parseHtmlDocument(normalizedHtml);\n\n // Move all children from the document body into the fragment.\n // This avoids interpolating untrusted HTML into an outer wrapper string.\n const body = doc.body;\n\n if (!body) {\n return fragment;\n }\n\n while (body.firstChild) {\n fragment.appendChild(body.firstChild);\n }\n\n return fragment;\n};\n\n/**\n * Core sanitization logic (without Trusted Types wrapper).\n * @internal\n */\nexport const sanitizeHtmlCore = (html: string, options: SanitizeOptions = {}): string => {\n const {\n allowTags = [],\n allowAttributes = [],\n allowDataAttributes = true,\n stripAllTags = false,\n } = options;\n\n // Build combined allow sets (excluding dangerous tags even if specified)\n const allowedTags = new Set(\n [...DEFAULT_ALLOWED_TAGS, ...allowTags.map((t) => t.toLowerCase())].filter(\n (tag) => !DANGEROUS_TAGS.has(tag)\n )\n );\n const allowedAttrs = new Set([\n ...DEFAULT_ALLOWED_ATTRIBUTES,\n ...allowAttributes.map((a) => a.toLowerCase()),\n ]);\n\n // Use DOMParser for safe HTML parsing (inert context, no script execution)\n const fragment = parseHtmlSafely(html);\n\n if (stripAllTags) {\n return fragment.textContent ?? '';\n }\n\n // Walk the DOM tree\n const walker = document.createTreeWalker(fragment, NodeFilter.SHOW_ELEMENT);\n\n const toRemove: Element[] = [];\n\n while (walker.nextNode()) {\n const el = walker.currentNode as Element;\n const tagName = el.tagName.toLowerCase();\n\n // Remove explicitly dangerous tags even if in allow list\n if (DANGEROUS_TAGS.has(tagName)) {\n toRemove.push(el);\n continue;\n }\n\n // Remove disallowed tags entirely\n if (!allowedTags.has(tagName)) {\n toRemove.push(el);\n continue;\n }\n\n // Process attributes\n const attrsToRemove: string[] = [];\n for (const attr of Array.from(el.attributes)) {\n const attrName = attr.name.toLowerCase();\n\n // Check if attribute is allowed\n if (!isAllowedAttribute(attrName, allowedAttrs, allowDataAttributes)) {\n attrsToRemove.push(attr.name);\n continue;\n }\n\n // Check for DOM clobbering on id and name attributes\n if ((attrName === 'id' || attrName === 'name') && !isSafeIdOrName(attr.value)) {\n attrsToRemove.push(attr.name);\n continue;\n }\n\n // Validate URL attributes\n if (\n (attrName === 'href' || attrName === 'src' || attrName === 'srcset') &&\n !isSafeUrl(attr.value)\n ) {\n attrsToRemove.push(attr.name);\n }\n }\n\n // Remove disallowed attributes\n for (const attrName of attrsToRemove) {\n el.removeAttribute(attrName);\n }\n\n // Add rel=\"noopener noreferrer\" to external links for security\n if (tagName === 'a') {\n const href = el.getAttribute('href');\n const target = el.getAttribute('target');\n const hasTargetBlank = target?.toLowerCase() === '_blank';\n const isExternal = href && isExternalUrl(href);\n\n // Add security attributes to links opening in new window or external links\n if (hasTargetBlank || isExternal) {\n const existingRel = el.getAttribute('rel');\n const relValues = new Set(existingRel ? existingRel.split(/\\s+/).filter(Boolean) : []);\n\n // Add noopener and noreferrer\n relValues.add('noopener');\n relValues.add('noreferrer');\n\n el.setAttribute('rel', Array.from(relValues).join(' '));\n }\n }\n }\n\n // Remove disallowed elements\n for (const el of toRemove) {\n el.remove();\n }\n\n // Serialize the sanitized fragment to HTML string.\n // We use a temporary container to get the innerHTML of the fragment.\n const serializeFragment = (frag: DocumentFragment): string => {\n const container = document.createElement('div');\n container.appendChild(frag.cloneNode(true));\n return container.innerHTML;\n };\n\n // Double-parse to prevent mutation XSS (mXSS).\n // Browsers may normalize HTML during serialization in ways that could create\n // new dangerous content when re-parsed. By re-parsing the sanitized output\n // and verifying stability, we ensure the final HTML is safe.\n const firstPass = serializeFragment(fragment);\n\n // Re-parse through DOMParser for mXSS detection.\n // Using DOMParser instead of innerHTML for security.\n const verifyFragment = parseHtmlSafely(firstPass);\n const secondPass = serializeFragment(verifyFragment);\n\n // Verify stability: if content mutates between parses, it indicates mXSS attempt\n if (firstPass !== secondPass) {\n // Content mutated during re-parse - potential mXSS detected.\n // Return safely escaped text content as fallback.\n return fragment.textContent ?? '';\n }\n\n return secondPass;\n};\n","/**\n * Security utilities for HTML sanitization.\n * All DOM writes are sanitized by default to prevent XSS attacks.\n *\n * @module bquery/security\n */\n\nimport { sanitizeHtmlCore } from './sanitize-core';\nimport type { SanitizeOptions } from './types';\nexport { generateNonce } from './csp';\nexport { isTrustedTypesSupported } from './trusted-types';\n\n/**\n * Sanitize HTML string, removing dangerous elements and attributes.\n * Uses Trusted Types when available for CSP compliance.\n *\n * @param html - The HTML string to sanitize\n * @param options - Sanitization options\n * @returns Sanitized HTML string\n *\n * @example\n * ```ts\n * const safe = sanitizeHtml('<div onclick=\"alert(1)\">Hello</div>');\n * // Returns: '<div>Hello</div>'\n * ```\n */\nexport const sanitizeHtml = (html: string, options: SanitizeOptions = {}): string => {\n return sanitizeHtmlCore(html, options);\n};\n\n/**\n * Escape HTML entities to prevent XSS.\n * Use this for displaying user content as text.\n *\n * @param text - The text to escape\n * @returns Escaped HTML string\n *\n * @example\n * ```ts\n * escapeHtml('<script>alert(1)</script>');\n * // Returns: '<script>alert(1)</script>'\n * ```\n */\nexport const escapeHtml = (text: string): string => {\n const escapeMap: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n '`': '`',\n };\n return text.replace(/[&<>\"'`]/g, (char) => escapeMap[char]);\n};\n\n/**\n * Strip all HTML tags and return plain text.\n *\n * @param html - The HTML string to strip\n * @returns Plain text content\n */\nexport const stripTags = (html: string): string => {\n return sanitizeHtmlCore(html, { stripAllTags: true });\n};\n\nexport type { SanitizeOptions } from './types';\n"],"names":["POLICY_NAME","DEFAULT_ALLOWED_TAGS","DANGEROUS_TAGS","RESERVED_IDS","DEFAULT_ALLOWED_ATTRIBUTES","DANGEROUS_ATTR_PREFIXES","DANGEROUS_PROTOCOLS","isAllowedAttribute","name","allowedSet","allowDataAttrs","lowerName","prefix","isSafeIdOrName","value","lowerValue","normalizeUrl","isSafeUrl","normalized","protocol","isExternalUrl","url","trimmedUrl","lowerUrl","parseHtmlDocument","htmlContent","parseHtmlSafely","html","normalizedHtml","fragment","body","sanitizeHtmlCore","options","allowTags","allowAttributes","allowDataAttributes","stripAllTags","allowedTags","t","tag","allowedAttrs","a","walker","toRemove","el","tagName","attrsToRemove","attr","attrName","href","hasTargetBlank","isExternal","existingRel","relValues","serializeFragment","frag","container","firstPass","verifyFragment","secondPass","sanitizeHtml","escapeHtml","text","escapeMap","char","stripTags"],"mappings":"AASO,MAAMA,IAAc,oBAKdC,wBAA2B,IAAI;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC,GAMYC,wBAAqB,IAAI;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC,GAMYC,wBAAmB,IAAI;AAAA;AAAA,EAElC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;A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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security constants and safe lists.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/security
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Trusted Types policy name.
|
|
8
|
+
*/
|
|
9
|
+
export declare const POLICY_NAME = "bquery-sanitizer";
|
|
10
|
+
/**
|
|
11
|
+
* Default allowed HTML tags considered safe.
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_ALLOWED_TAGS: Set<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Explicitly dangerous tags that should never be allowed.
|
|
16
|
+
* These are checked even if somehow added to allowTags.
|
|
17
|
+
*/
|
|
18
|
+
export declare const DANGEROUS_TAGS: Set<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Reserved IDs that could cause DOM clobbering attacks.
|
|
21
|
+
* These are prevented to avoid overwriting global browser objects.
|
|
22
|
+
*/
|
|
23
|
+
export declare const RESERVED_IDS: Set<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Default allowed attributes considered safe.
|
|
26
|
+
* Note: 'style' is excluded by default because inline CSS can be abused for:
|
|
27
|
+
* - UI redressing attacks
|
|
28
|
+
* - Data exfiltration via url() in CSS
|
|
29
|
+
* - CSS injection vectors
|
|
30
|
+
* If you need to allow inline styles, add 'style' to allowAttributes in your
|
|
31
|
+
* sanitizeHtml options, but ensure you implement proper CSS value validation.
|
|
32
|
+
*/
|
|
33
|
+
export declare const DEFAULT_ALLOWED_ATTRIBUTES: Set<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Dangerous attribute prefixes to always remove.
|
|
36
|
+
*/
|
|
37
|
+
export declare const DANGEROUS_ATTR_PREFIXES: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Dangerous URL protocols to block.
|
|
40
|
+
*/
|
|
41
|
+
export declare const DANGEROUS_PROTOCOLS: string[];
|
|
42
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/security/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,eAAO,MAAM,WAAW,qBAAqB,CAAC;AAE9C;;GAEG;AACH,eAAO,MAAM,oBAAoB,aAqF/B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,cAAc,aAkBzB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,YAAY,aAqCvB,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,aAqBrC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,uBAAuB,UAA2C,CAAC;AAEhF;;GAEG;AACH,eAAO,MAAM,mBAAmB,UAAiD,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Security Policy helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/security
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Generate a nonce for inline scripts/styles.
|
|
8
|
+
* Use with Content-Security-Policy nonce directives.
|
|
9
|
+
*
|
|
10
|
+
* @param length - Nonce length in bytes (default: 16, max: 1024)
|
|
11
|
+
* @returns Cryptographically random nonce string
|
|
12
|
+
* @throws {Error} If crypto.getRandomValues or btoa are not available
|
|
13
|
+
* @throws {RangeError} If length is invalid (negative, non-integer, or exceeds maximum)
|
|
14
|
+
*/
|
|
15
|
+
export declare const generateNonce: (length?: number) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Check if a CSP header is present with specific directive.
|
|
18
|
+
* Useful for feature detection and fallback strategies.
|
|
19
|
+
*
|
|
20
|
+
* @param directive - The CSP directive to check (e.g., 'script-src')
|
|
21
|
+
* @returns True if the directive appears to be enforced
|
|
22
|
+
*/
|
|
23
|
+
export declare const hasCSPDirective: (directive: string) => boolean;
|
|
24
|
+
//# sourceMappingURL=csp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp.d.ts","sourceRoot":"","sources":["../../src/security/csp.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GAAI,SAAQ,MAAW,KAAG,MAiCnD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,WAAW,MAAM,KAAG,OAanD,CAAC"}
|
package/dist/security/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module bquery/security
|
|
5
5
|
*/
|
|
6
|
-
export {
|
|
7
|
-
export
|
|
6
|
+
export { generateNonce, hasCSPDirective } from './csp';
|
|
7
|
+
export { escapeHtml, sanitizeHtml as sanitize, sanitizeHtml, stripTags } from './sanitize';
|
|
8
|
+
export { createTrustedHtml, getTrustedTypesPolicy, isTrustedTypesSupported } from './trusted-types';
|
|
9
|
+
export type { SanitizeOptions } from './types';
|
|
8
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,YAAY,IAAI,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC3F,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AACpG,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core HTML sanitization logic.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/security
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
import type { SanitizeOptions } from './types';
|
|
8
|
+
/**
|
|
9
|
+
* Core sanitization logic (without Trusted Types wrapper).
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const sanitizeHtmlCore: (html: string, options?: SanitizeOptions) => string;
|
|
13
|
+
//# sourceMappingURL=sanitize-core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize-core.d.ts","sourceRoot":"","sources":["../../src/security/sanitize-core.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAgM/C;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,EAAE,UAAS,eAAoB,KAAG,MAmI9E,CAAC"}
|