@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.
Files changed (305) hide show
  1. package/README.md +501 -427
  2. package/dist/batch-4LAvfLE7.js +13 -0
  3. package/dist/batch-4LAvfLE7.js.map +1 -0
  4. package/dist/component/component.d.ts +69 -0
  5. package/dist/component/component.d.ts.map +1 -0
  6. package/dist/component/html.d.ts +35 -0
  7. package/dist/component/html.d.ts.map +1 -0
  8. package/dist/component/index.d.ts +3 -126
  9. package/dist/component/index.d.ts.map +1 -1
  10. package/dist/component/props.d.ts +18 -0
  11. package/dist/component/props.d.ts.map +1 -0
  12. package/dist/component/types.d.ts +77 -0
  13. package/dist/component/types.d.ts.map +1 -0
  14. package/dist/component.es.mjs +90 -59
  15. package/dist/component.es.mjs.map +1 -1
  16. package/dist/core/collection.d.ts +36 -0
  17. package/dist/core/collection.d.ts.map +1 -1
  18. package/dist/core/dom.d.ts +6 -0
  19. package/dist/core/dom.d.ts.map +1 -0
  20. package/dist/core/element.d.ts +8 -0
  21. package/dist/core/element.d.ts.map +1 -1
  22. package/dist/core/index.d.ts +1 -0
  23. package/dist/core/index.d.ts.map +1 -1
  24. package/dist/core/utils/array.d.ts +74 -0
  25. package/dist/core/utils/array.d.ts.map +1 -0
  26. package/dist/core/utils/function.d.ts +70 -0
  27. package/dist/core/utils/function.d.ts.map +1 -0
  28. package/dist/core/utils/index.d.ts +70 -0
  29. package/dist/core/utils/index.d.ts.map +1 -0
  30. package/dist/core/utils/misc.d.ts +63 -0
  31. package/dist/core/utils/misc.d.ts.map +1 -0
  32. package/dist/core/utils/number.d.ts +65 -0
  33. package/dist/core/utils/number.d.ts.map +1 -0
  34. package/dist/core/utils/object.d.ts +133 -0
  35. package/dist/core/utils/object.d.ts.map +1 -0
  36. package/dist/core/utils/string.d.ts +80 -0
  37. package/dist/core/utils/string.d.ts.map +1 -0
  38. package/dist/core/utils/type-guards.d.ts +79 -0
  39. package/dist/core/utils/type-guards.d.ts.map +1 -0
  40. package/dist/core-COenAZjD.js +145 -0
  41. package/dist/core-COenAZjD.js.map +1 -0
  42. package/dist/core.es.mjs +411 -448
  43. package/dist/core.es.mjs.map +1 -1
  44. package/dist/full.d.ts +2 -2
  45. package/dist/full.d.ts.map +1 -1
  46. package/dist/full.es.mjs +87 -64
  47. package/dist/full.es.mjs.map +1 -1
  48. package/dist/full.iife.js +2 -2
  49. package/dist/full.iife.js.map +1 -1
  50. package/dist/full.umd.js +2 -2
  51. package/dist/full.umd.js.map +1 -1
  52. package/dist/index.es.mjs +138 -68
  53. package/dist/index.es.mjs.map +1 -1
  54. package/dist/motion/animate.d.ts +25 -0
  55. package/dist/motion/animate.d.ts.map +1 -0
  56. package/dist/motion/easing.d.ts +30 -0
  57. package/dist/motion/easing.d.ts.map +1 -0
  58. package/dist/motion/flip.d.ts +55 -0
  59. package/dist/motion/flip.d.ts.map +1 -0
  60. package/dist/motion/index.d.ts +11 -138
  61. package/dist/motion/index.d.ts.map +1 -1
  62. package/dist/motion/keyframes.d.ts +21 -0
  63. package/dist/motion/keyframes.d.ts.map +1 -0
  64. package/dist/motion/reduced-motion.d.ts +12 -0
  65. package/dist/motion/reduced-motion.d.ts.map +1 -0
  66. package/dist/motion/scroll.d.ts +15 -0
  67. package/dist/motion/scroll.d.ts.map +1 -0
  68. package/dist/motion/spring.d.ts +42 -0
  69. package/dist/motion/spring.d.ts.map +1 -0
  70. package/dist/motion/stagger.d.ts +22 -0
  71. package/dist/motion/stagger.d.ts.map +1 -0
  72. package/dist/motion/timeline.d.ts +21 -0
  73. package/dist/motion/timeline.d.ts.map +1 -0
  74. package/dist/motion/transition.d.ts +22 -0
  75. package/dist/motion/transition.d.ts.map +1 -0
  76. package/dist/motion/types.d.ts +182 -0
  77. package/dist/motion/types.d.ts.map +1 -0
  78. package/dist/motion.es.mjs +320 -61
  79. package/dist/motion.es.mjs.map +1 -1
  80. package/dist/persisted-Dz_ryNuC.js +278 -0
  81. package/dist/persisted-Dz_ryNuC.js.map +1 -0
  82. package/dist/reactive/batch.d.ts +13 -0
  83. package/dist/reactive/batch.d.ts.map +1 -0
  84. package/dist/reactive/computed.d.ts +50 -0
  85. package/dist/reactive/computed.d.ts.map +1 -0
  86. package/dist/reactive/core.d.ts +60 -0
  87. package/dist/reactive/core.d.ts.map +1 -0
  88. package/dist/reactive/effect.d.ts +15 -0
  89. package/dist/reactive/effect.d.ts.map +1 -0
  90. package/dist/reactive/index.d.ts +2 -2
  91. package/dist/reactive/index.d.ts.map +1 -1
  92. package/dist/reactive/internals.d.ts +36 -0
  93. package/dist/reactive/internals.d.ts.map +1 -0
  94. package/dist/reactive/linked.d.ts +36 -0
  95. package/dist/reactive/linked.d.ts.map +1 -0
  96. package/dist/reactive/persisted.d.ts +14 -0
  97. package/dist/reactive/persisted.d.ts.map +1 -0
  98. package/dist/reactive/readonly.d.ts +26 -0
  99. package/dist/reactive/readonly.d.ts.map +1 -0
  100. package/dist/reactive/signal.d.ts +13 -312
  101. package/dist/reactive/signal.d.ts.map +1 -1
  102. package/dist/reactive/type-guards.d.ts +20 -0
  103. package/dist/reactive/type-guards.d.ts.map +1 -0
  104. package/dist/reactive/untrack.d.ts +29 -0
  105. package/dist/reactive/untrack.d.ts.map +1 -0
  106. package/dist/reactive/watch.d.ts +42 -0
  107. package/dist/reactive/watch.d.ts.map +1 -0
  108. package/dist/reactive.es.mjs +30 -163
  109. package/dist/reactive.es.mjs.map +1 -1
  110. package/dist/router/index.d.ts +6 -252
  111. package/dist/router/index.d.ts.map +1 -1
  112. package/dist/router/links.d.ts +44 -0
  113. package/dist/router/links.d.ts.map +1 -0
  114. package/dist/router/match.d.ts +20 -0
  115. package/dist/router/match.d.ts.map +1 -0
  116. package/dist/router/navigation.d.ts +45 -0
  117. package/dist/router/navigation.d.ts.map +1 -0
  118. package/dist/router/query.d.ts +16 -0
  119. package/dist/router/query.d.ts.map +1 -0
  120. package/dist/router/router.d.ts +34 -0
  121. package/dist/router/router.d.ts.map +1 -0
  122. package/dist/router/state.d.ts +27 -0
  123. package/dist/router/state.d.ts.map +1 -0
  124. package/dist/router/types.d.ts +88 -0
  125. package/dist/router/types.d.ts.map +1 -0
  126. package/dist/router/utils.d.ts +65 -0
  127. package/dist/router/utils.d.ts.map +1 -0
  128. package/dist/router.es.mjs +168 -132
  129. package/dist/router.es.mjs.map +1 -1
  130. package/dist/sanitize-1FBEPAFH.js +272 -0
  131. package/dist/sanitize-1FBEPAFH.js.map +1 -0
  132. package/dist/security/constants.d.ts +42 -0
  133. package/dist/security/constants.d.ts.map +1 -0
  134. package/dist/security/csp.d.ts +24 -0
  135. package/dist/security/csp.d.ts.map +1 -0
  136. package/dist/security/index.d.ts +4 -2
  137. package/dist/security/index.d.ts.map +1 -1
  138. package/dist/security/sanitize-core.d.ts +13 -0
  139. package/dist/security/sanitize-core.d.ts.map +1 -0
  140. package/dist/security/sanitize.d.ts +5 -57
  141. package/dist/security/sanitize.d.ts.map +1 -1
  142. package/dist/security/trusted-types.d.ts +25 -0
  143. package/dist/security/trusted-types.d.ts.map +1 -0
  144. package/dist/security/types.d.ts +36 -0
  145. package/dist/security/types.d.ts.map +1 -0
  146. package/dist/security.es.mjs +50 -277
  147. package/dist/security.es.mjs.map +1 -1
  148. package/dist/store/create-store.d.ts +15 -0
  149. package/dist/store/create-store.d.ts.map +1 -0
  150. package/dist/store/define-store.d.ts +28 -0
  151. package/dist/store/define-store.d.ts.map +1 -0
  152. package/dist/store/devtools.d.ts +22 -0
  153. package/dist/store/devtools.d.ts.map +1 -0
  154. package/dist/store/index.d.ts +10 -286
  155. package/dist/store/index.d.ts.map +1 -1
  156. package/dist/store/mapping.d.ts +28 -0
  157. package/dist/store/mapping.d.ts.map +1 -0
  158. package/dist/store/persisted.d.ts +13 -0
  159. package/dist/store/persisted.d.ts.map +1 -0
  160. package/dist/store/plugins.d.ts +13 -0
  161. package/dist/store/plugins.d.ts.map +1 -0
  162. package/dist/store/registry.d.ts +28 -0
  163. package/dist/store/registry.d.ts.map +1 -0
  164. package/dist/store/types.d.ts +71 -0
  165. package/dist/store/types.d.ts.map +1 -0
  166. package/dist/store/utils.d.ts +28 -0
  167. package/dist/store/utils.d.ts.map +1 -0
  168. package/dist/store/watch.d.ts +23 -0
  169. package/dist/store/watch.d.ts.map +1 -0
  170. package/dist/store.es.mjs +22 -224
  171. package/dist/store.es.mjs.map +1 -1
  172. package/dist/type-guards-DRma3-Kc.js +16 -0
  173. package/dist/type-guards-DRma3-Kc.js.map +1 -0
  174. package/dist/untrack-BuEQKH7_.js +6 -0
  175. package/dist/untrack-BuEQKH7_.js.map +1 -0
  176. package/dist/view/directives/bind.d.ts +7 -0
  177. package/dist/view/directives/bind.d.ts.map +1 -0
  178. package/dist/view/directives/class.d.ts +8 -0
  179. package/dist/view/directives/class.d.ts.map +1 -0
  180. package/dist/view/directives/for.d.ts +23 -0
  181. package/dist/view/directives/for.d.ts.map +1 -0
  182. package/dist/view/directives/html.d.ts +7 -0
  183. package/dist/view/directives/html.d.ts.map +1 -0
  184. package/dist/view/directives/if.d.ts +7 -0
  185. package/dist/view/directives/if.d.ts.map +1 -0
  186. package/dist/view/directives/index.d.ts +12 -0
  187. package/dist/view/directives/index.d.ts.map +1 -0
  188. package/dist/view/directives/model.d.ts +7 -0
  189. package/dist/view/directives/model.d.ts.map +1 -0
  190. package/dist/view/directives/on.d.ts +7 -0
  191. package/dist/view/directives/on.d.ts.map +1 -0
  192. package/dist/view/directives/ref.d.ts +7 -0
  193. package/dist/view/directives/ref.d.ts.map +1 -0
  194. package/dist/view/directives/show.d.ts +7 -0
  195. package/dist/view/directives/show.d.ts.map +1 -0
  196. package/dist/view/directives/style.d.ts +7 -0
  197. package/dist/view/directives/style.d.ts.map +1 -0
  198. package/dist/view/directives/text.d.ts +7 -0
  199. package/dist/view/directives/text.d.ts.map +1 -0
  200. package/dist/view/evaluate.d.ts +43 -0
  201. package/dist/view/evaluate.d.ts.map +1 -0
  202. package/dist/view/index.d.ts +3 -93
  203. package/dist/view/index.d.ts.map +1 -1
  204. package/dist/view/mount.d.ts +69 -0
  205. package/dist/view/mount.d.ts.map +1 -0
  206. package/dist/view/process.d.ts +26 -0
  207. package/dist/view/process.d.ts.map +1 -0
  208. package/dist/view/types.d.ts +36 -0
  209. package/dist/view/types.d.ts.map +1 -0
  210. package/dist/view.es.mjs +368 -267
  211. package/dist/view.es.mjs.map +1 -1
  212. package/dist/watch-CXyaBC_9.js +58 -0
  213. package/dist/watch-CXyaBC_9.js.map +1 -0
  214. package/package.json +132 -132
  215. package/src/component/component.ts +289 -0
  216. package/src/component/html.ts +53 -0
  217. package/src/component/index.ts +40 -414
  218. package/src/component/props.ts +116 -0
  219. package/src/component/types.ts +85 -0
  220. package/src/core/collection.ts +588 -454
  221. package/src/core/dom.ts +38 -0
  222. package/src/core/element.ts +746 -740
  223. package/src/core/index.ts +43 -0
  224. package/src/core/utils/array.ts +102 -0
  225. package/src/core/utils/function.ts +110 -0
  226. package/src/core/utils/index.ts +83 -0
  227. package/src/core/utils/misc.ts +82 -0
  228. package/src/core/utils/number.ts +78 -0
  229. package/src/core/utils/object.ts +206 -0
  230. package/src/core/utils/string.ts +112 -0
  231. package/src/core/utils/type-guards.ts +112 -0
  232. package/src/full.ts +187 -150
  233. package/src/index.ts +36 -36
  234. package/src/motion/animate.ts +113 -0
  235. package/src/motion/easing.ts +40 -0
  236. package/src/motion/flip.ts +176 -0
  237. package/src/motion/index.ts +41 -358
  238. package/src/motion/keyframes.ts +46 -0
  239. package/src/motion/reduced-motion.ts +17 -0
  240. package/src/motion/scroll.ts +57 -0
  241. package/src/motion/spring.ts +150 -0
  242. package/src/motion/stagger.ts +43 -0
  243. package/src/motion/timeline.ts +246 -0
  244. package/src/motion/transition.ts +51 -0
  245. package/src/motion/types.ts +198 -0
  246. package/src/reactive/batch.ts +22 -0
  247. package/src/reactive/computed.ts +92 -0
  248. package/src/reactive/core.ts +93 -0
  249. package/src/reactive/effect.ts +43 -0
  250. package/src/reactive/index.ts +23 -22
  251. package/src/reactive/internals.ts +105 -0
  252. package/src/reactive/linked.ts +56 -0
  253. package/src/reactive/persisted.ts +74 -0
  254. package/src/reactive/readonly.ts +35 -0
  255. package/src/reactive/signal.ts +20 -520
  256. package/src/reactive/type-guards.ts +22 -0
  257. package/src/reactive/untrack.ts +31 -0
  258. package/src/reactive/watch.ts +73 -0
  259. package/src/router/index.ts +41 -718
  260. package/src/router/links.ts +130 -0
  261. package/src/router/match.ts +106 -0
  262. package/src/router/navigation.ts +71 -0
  263. package/src/router/query.ts +35 -0
  264. package/src/router/router.ts +211 -0
  265. package/src/router/state.ts +46 -0
  266. package/src/router/types.ts +93 -0
  267. package/src/router/utils.ts +116 -0
  268. package/src/security/constants.ts +209 -0
  269. package/src/security/csp.ts +77 -0
  270. package/src/security/index.ts +4 -12
  271. package/src/security/sanitize-core.ts +343 -0
  272. package/src/security/sanitize.ts +66 -625
  273. package/src/security/trusted-types.ts +69 -0
  274. package/src/security/types.ts +40 -0
  275. package/src/store/create-store.ts +329 -0
  276. package/src/store/define-store.ts +48 -0
  277. package/src/store/devtools.ts +45 -0
  278. package/src/store/index.ts +22 -848
  279. package/src/store/mapping.ts +73 -0
  280. package/src/store/persisted.ts +61 -0
  281. package/src/store/plugins.ts +32 -0
  282. package/src/store/registry.ts +51 -0
  283. package/src/store/types.ts +94 -0
  284. package/src/store/utils.ts +141 -0
  285. package/src/store/watch.ts +52 -0
  286. package/src/view/directives/bind.ts +23 -0
  287. package/src/view/directives/class.ts +70 -0
  288. package/src/view/directives/for.ts +275 -0
  289. package/src/view/directives/html.ts +19 -0
  290. package/src/view/directives/if.ts +30 -0
  291. package/src/view/directives/index.ts +11 -0
  292. package/src/view/directives/model.ts +56 -0
  293. package/src/view/directives/on.ts +41 -0
  294. package/src/view/directives/ref.ts +41 -0
  295. package/src/view/directives/show.ts +26 -0
  296. package/src/view/directives/style.ts +47 -0
  297. package/src/view/directives/text.ts +15 -0
  298. package/src/view/evaluate.ts +274 -0
  299. package/src/view/index.ts +112 -1041
  300. package/src/view/mount.ts +200 -0
  301. package/src/view/process.ts +92 -0
  302. package/src/view/types.ts +44 -0
  303. package/dist/core/utils.d.ts +0 -313
  304. package/dist/core/utils.d.ts.map +0 -1
  305. package/src/core/utils.ts +0 -444
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Trusted Types helpers for CSP compatibility.
3
+ *
4
+ * @module bquery/security
5
+ */
6
+
7
+ import { POLICY_NAME } from './constants';
8
+ import { sanitizeHtmlCore } from './sanitize-core';
9
+ import type { TrustedHTML, TrustedTypePolicy, TrustedTypesWindow } from './types';
10
+
11
+ /** Cached Trusted Types policy */
12
+ let cachedPolicy: TrustedTypePolicy | null = null;
13
+
14
+ /** Whether policy initialization has been attempted (to avoid retry spam) */
15
+ let policyInitAttempted = false;
16
+
17
+ /**
18
+ * Check if Trusted Types API is available.
19
+ * @returns True if Trusted Types are supported
20
+ */
21
+ export const isTrustedTypesSupported = (): boolean => {
22
+ return (
23
+ typeof window !== 'undefined' &&
24
+ typeof (window as TrustedTypesWindow).trustedTypes !== 'undefined'
25
+ );
26
+ };
27
+
28
+ /**
29
+ * Get or create the bQuery Trusted Types policy.
30
+ * @returns The Trusted Types policy or null if unsupported
31
+ */
32
+ export const getTrustedTypesPolicy = (): TrustedTypePolicy | null => {
33
+ if (cachedPolicy) return cachedPolicy;
34
+ if (policyInitAttempted) return null;
35
+
36
+ if (typeof window === 'undefined') return null;
37
+
38
+ const win = window as TrustedTypesWindow;
39
+ if (!win.trustedTypes) return null;
40
+
41
+ policyInitAttempted = true;
42
+
43
+ try {
44
+ cachedPolicy = win.trustedTypes.createPolicy(POLICY_NAME, {
45
+ createHTML: (input: string) => sanitizeHtmlCore(input),
46
+ });
47
+ return cachedPolicy;
48
+ } catch (error) {
49
+ // Policy may already exist or be blocked by CSP
50
+ const errorMessage = error instanceof Error ? error.message : String(error);
51
+ console.warn(`bQuery: Could not create Trusted Types policy "${POLICY_NAME}": ${errorMessage}`);
52
+ return null;
53
+ }
54
+ };
55
+
56
+ /**
57
+ * Create a Trusted HTML value for use with Trusted Types-enabled sites.
58
+ * Falls back to regular string when Trusted Types are unavailable.
59
+ *
60
+ * @param html - The HTML string to wrap
61
+ * @returns Trusted HTML value or sanitized string
62
+ */
63
+ export const createTrustedHtml = (html: string): TrustedHTML | string => {
64
+ const policy = getTrustedTypesPolicy();
65
+ if (policy) {
66
+ return policy.createHTML(html);
67
+ }
68
+ return sanitizeHtmlCore(html);
69
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Security types for sanitization, CSP compatibility, and Trusted Types.
3
+ *
4
+ * @module bquery/security
5
+ */
6
+
7
+ /**
8
+ * Sanitizer configuration options.
9
+ */
10
+ export interface SanitizeOptions {
11
+ /** Allow these additional tags (default: none) */
12
+ allowTags?: string[];
13
+ /** Allow these additional attributes (default: none) */
14
+ allowAttributes?: string[];
15
+ /** Allow data-* attributes (default: true) */
16
+ allowDataAttributes?: boolean;
17
+ /** Strip all tags and return plain text (default: false) */
18
+ stripAllTags?: boolean;
19
+ }
20
+
21
+ /** Window interface extended with Trusted Types */
22
+ export interface TrustedTypesWindow extends Window {
23
+ trustedTypes?: {
24
+ createPolicy: (
25
+ name: string,
26
+ rules: { createHTML?: (input: string) => string }
27
+ ) => TrustedTypePolicy;
28
+ isHTML?: (value: unknown) => boolean;
29
+ };
30
+ }
31
+
32
+ /** Trusted Types policy interface */
33
+ export interface TrustedTypePolicy {
34
+ createHTML: (input: string) => TrustedHTML;
35
+ }
36
+
37
+ /** Trusted HTML type placeholder for environments without Trusted Types */
38
+ export interface TrustedHTML {
39
+ toString(): string;
40
+ }
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Store creation logic.
3
+ */
4
+
5
+ import {
6
+ batch,
7
+ computed,
8
+ signal,
9
+ untrack,
10
+ type ReadonlySignal,
11
+ type Signal,
12
+ } from '../reactive/index';
13
+ import { notifyDevtoolsStateChange, registerDevtoolsStore } from './devtools';
14
+ import { applyPlugins } from './plugins';
15
+ import { getStore, hasStore, registerStore } from './registry';
16
+ import type { Getters, Store, StoreDefinition, StoreSubscriber } from './types';
17
+ import { deepClone, detectNestedMutations, isDev } from './utils';
18
+
19
+ /**
20
+ * Creates a reactive store with state, getters, and actions.
21
+ *
22
+ * @template S - State type
23
+ * @template G - Getters type
24
+ * @template A - Actions type
25
+ * @param definition - Store definition
26
+ * @returns The reactive store instance
27
+ */
28
+ export const createStore = <
29
+ S extends Record<string, unknown>,
30
+ G extends Record<string, unknown> = Record<string, never>,
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ A extends Record<string, (...args: any[]) => any> = Record<string, never>,
33
+ >(
34
+ definition: StoreDefinition<S, G, A>
35
+ ): Store<S, G, A> => {
36
+ const { id, state: stateFactory, getters = {} as Getters<S, G>, actions = {} as A } = definition;
37
+
38
+ // Check for duplicate store IDs
39
+ if (hasStore(id)) {
40
+ console.warn(`bQuery store: Store "${id}" already exists. Returning existing instance.`);
41
+ return getStore(id) as Store<S, G, A>;
42
+ }
43
+
44
+ // Create initial state
45
+ const initialState = stateFactory();
46
+
47
+ // Create signals for each state property
48
+ const stateSignals = new Map<keyof S, Signal<unknown>>();
49
+ for (const key of Object.keys(initialState) as Array<keyof S>) {
50
+ stateSignals.set(key, signal(initialState[key]));
51
+ }
52
+
53
+ // Subscribers for $subscribe
54
+ const subscribers: Array<StoreSubscriber<S>> = [];
55
+
56
+ /**
57
+ * Gets the current state.
58
+ *
59
+ * For subscriber notifications (where a plain object snapshot is needed),
60
+ * this creates a shallow copy. For internal reads, use stateProxy directly.
61
+ *
62
+ * **Note:** Returns a shallow snapshot. Nested object mutations will NOT
63
+ * trigger reactive updates. This differs from frameworks like Pinia that
64
+ * use deep reactivity. To update nested state, replace the entire object.
65
+ *
66
+ * Uses `untrack()` to prevent accidental dependency tracking when called
67
+ * from within reactive contexts (e.g., `effect()` or `computed()`).
68
+ *
69
+ * @internal
70
+ */
71
+ const getCurrentState = (): S =>
72
+ untrack(() => {
73
+ return { ...stateProxy };
74
+ });
75
+
76
+ /**
77
+ * Notifies subscribers of state changes.
78
+ * Short-circuits if there are no subscribers and devtools aren't active
79
+ * to avoid unnecessary snapshot overhead.
80
+ * @internal
81
+ */
82
+ const notifySubscribers = (): void => {
83
+ // Early return if no subscribers and no devtools hook
84
+ const hasDevtools =
85
+ typeof window !== 'undefined' &&
86
+ typeof window.__BQUERY_DEVTOOLS__?.onStateChange === 'function';
87
+ if (subscribers.length === 0 && !hasDevtools) {
88
+ return;
89
+ }
90
+
91
+ const currentState = getCurrentState();
92
+ for (const callback of subscribers) {
93
+ callback(currentState);
94
+ }
95
+
96
+ notifyDevtoolsStateChange(id, currentState);
97
+ };
98
+
99
+ /**
100
+ * Cached state proxy that lazily reads signal values.
101
+ * Uses a Proxy to avoid creating new objects on each access.
102
+ *
103
+ * **Note:** This returns a shallow snapshot of the state. Nested object
104
+ * mutations will NOT trigger reactive updates. For nested reactivity,
105
+ * replace the entire object or use signals for nested properties.
106
+ *
107
+ * @internal
108
+ */
109
+ const stateProxy = new Proxy({} as S, {
110
+ get: (_, prop: string | symbol) => {
111
+ const key = prop as keyof S;
112
+ if (stateSignals.has(key)) {
113
+ return stateSignals.get(key)!.value;
114
+ }
115
+ return undefined;
116
+ },
117
+ ownKeys: () => Array.from(stateSignals.keys()) as string[],
118
+ getOwnPropertyDescriptor: (_, prop) => {
119
+ if (stateSignals.has(prop as keyof S)) {
120
+ return { enumerable: true, configurable: true };
121
+ }
122
+ return undefined;
123
+ },
124
+ has: (_, prop) => stateSignals.has(prop as keyof S),
125
+ });
126
+
127
+ // Create computed getters
128
+ const getterComputed = new Map<keyof G, ReadonlySignal<unknown>>();
129
+
130
+ // Build the store proxy
131
+ const store = {} as Store<S, G, A>;
132
+
133
+ // Define state properties with getters/setters
134
+ for (const key of Object.keys(initialState) as Array<keyof S>) {
135
+ Object.defineProperty(store, key, {
136
+ get: () => stateSignals.get(key)!.value,
137
+ set: (value: unknown) => {
138
+ stateSignals.get(key)!.value = value;
139
+ notifySubscribers();
140
+ },
141
+ enumerable: true,
142
+ configurable: false,
143
+ });
144
+ }
145
+
146
+ // Define getters as computed properties
147
+ for (const key of Object.keys(getters) as Array<keyof G>) {
148
+ const getterFn = getters[key];
149
+
150
+ // Create computed that reads from state signals via proxy (more efficient)
151
+ const computedGetter = computed(() => {
152
+ const state = stateProxy;
153
+ // For getter dependencies, pass a proxy that reads from computed getters
154
+ const getterProxy = new Proxy({} as G, {
155
+ get: (_, prop: string | symbol) => {
156
+ const propKey = prop as keyof G;
157
+ if (getterComputed.has(propKey)) {
158
+ return getterComputed.get(propKey)!.value;
159
+ }
160
+ return undefined;
161
+ },
162
+ });
163
+ return getterFn(state, getterProxy);
164
+ });
165
+
166
+ getterComputed.set(key, computedGetter as unknown as ReadonlySignal<unknown>);
167
+
168
+ Object.defineProperty(store, key, {
169
+ get: () => computedGetter.value,
170
+ enumerable: true,
171
+ configurable: false,
172
+ });
173
+ }
174
+
175
+ // Bind actions to the store context
176
+ for (const key of Object.keys(actions) as Array<keyof A>) {
177
+ const actionFn = actions[key];
178
+
179
+ // Wrap action to enable 'this' binding
180
+ (store as Record<string, unknown>)[key as string] = function (...args: unknown[]) {
181
+ // Create a context that allows 'this.property' access
182
+ const context = new Proxy(store, {
183
+ get: (target, prop) => {
184
+ if (typeof prop === 'string' && stateSignals.has(prop as keyof S)) {
185
+ return stateSignals.get(prop as keyof S)!.value;
186
+ }
187
+ return (target as Record<string, unknown>)[prop as string];
188
+ },
189
+ set: (target, prop, value) => {
190
+ if (typeof prop === 'string' && stateSignals.has(prop as keyof S)) {
191
+ stateSignals.get(prop as keyof S)!.value = value;
192
+ notifySubscribers();
193
+ return true;
194
+ }
195
+ // Allow non-state property assignments (e.g., temporary variables in actions)
196
+ // by delegating to the target object rather than returning false
197
+ return Reflect.set(target, prop, value);
198
+ },
199
+ });
200
+
201
+ return actionFn.apply(context, args);
202
+ };
203
+ }
204
+
205
+ // Add store utility methods
206
+ Object.defineProperties(store, {
207
+ $id: {
208
+ value: id,
209
+ writable: false,
210
+ enumerable: false,
211
+ },
212
+ $reset: {
213
+ value: () => {
214
+ const fresh = stateFactory();
215
+ batch(() => {
216
+ for (const [key, sig] of stateSignals) {
217
+ sig.value = fresh[key];
218
+ }
219
+ });
220
+ notifySubscribers();
221
+ },
222
+ writable: false,
223
+ enumerable: false,
224
+ },
225
+ $subscribe: {
226
+ value: (callback: StoreSubscriber<S>) => {
227
+ subscribers.push(callback);
228
+ return () => {
229
+ const index = subscribers.indexOf(callback);
230
+ if (index > -1) subscribers.splice(index, 1);
231
+ };
232
+ },
233
+ writable: false,
234
+ enumerable: false,
235
+ },
236
+ $patch: {
237
+ value: (partial: Partial<S> | ((state: S) => void)) => {
238
+ batch(() => {
239
+ if (typeof partial === 'function') {
240
+ // Capture state before mutation for nested mutation detection
241
+ const stateBefore = isDev ? deepClone(getCurrentState()) : null;
242
+ const signalValuesBefore = isDev
243
+ ? new Map(Array.from(stateSignals.entries()).map(([k, s]) => [k, s.value]))
244
+ : null;
245
+
246
+ // Mutation function
247
+ const state = getCurrentState();
248
+ partial(state);
249
+
250
+ // Detect nested mutations in development mode
251
+ if (isDev && stateBefore && signalValuesBefore) {
252
+ const mutatedKeys = detectNestedMutations(stateBefore, state, signalValuesBefore);
253
+ if (mutatedKeys.length > 0) {
254
+ console.warn(
255
+ `[bQuery store "${id}"] Nested mutation detected in $patch() for keys: ${mutatedKeys
256
+ .map(String)
257
+ .join(', ')}.\n` +
258
+ 'Nested object mutations do not trigger reactive updates because the store uses shallow reactivity.\n' +
259
+ 'To fix this, either:\n' +
260
+ ' 1. Replace the entire object: state.user = { ...state.user, name: "New" }\n' +
261
+ ' 2. Use $patchDeep() for automatic deep cloning\n' +
262
+ 'See: https://bquery.dev/guide/store#deep-reactivity'
263
+ );
264
+ }
265
+ }
266
+
267
+ for (const [key, value] of Object.entries(state) as Array<[keyof S, unknown]>) {
268
+ if (stateSignals.has(key)) {
269
+ stateSignals.get(key)!.value = value;
270
+ }
271
+ }
272
+ } else {
273
+ // Partial object
274
+ for (const [key, value] of Object.entries(partial) as Array<[keyof S, unknown]>) {
275
+ if (stateSignals.has(key)) {
276
+ stateSignals.get(key)!.value = value;
277
+ }
278
+ }
279
+ }
280
+ });
281
+ notifySubscribers();
282
+ },
283
+ writable: false,
284
+ enumerable: false,
285
+ },
286
+ $patchDeep: {
287
+ value: (partial: Partial<S> | ((state: S) => void)) => {
288
+ batch(() => {
289
+ if (typeof partial === 'function') {
290
+ // Deep clone state before mutation to ensure new references
291
+ const state = deepClone(getCurrentState());
292
+ partial(state);
293
+
294
+ for (const [key, value] of Object.entries(state) as Array<[keyof S, unknown]>) {
295
+ if (stateSignals.has(key)) {
296
+ stateSignals.get(key)!.value = value;
297
+ }
298
+ }
299
+ } else {
300
+ // Deep clone each value in partial to ensure new references
301
+ for (const [key, value] of Object.entries(partial) as Array<[keyof S, unknown]>) {
302
+ if (stateSignals.has(key)) {
303
+ stateSignals.get(key)!.value = deepClone(value);
304
+ }
305
+ }
306
+ }
307
+ });
308
+ notifySubscribers();
309
+ },
310
+ writable: false,
311
+ enumerable: false,
312
+ },
313
+ $state: {
314
+ get: () => getCurrentState(),
315
+ enumerable: false,
316
+ },
317
+ });
318
+
319
+ // Register store
320
+ registerStore(id, store);
321
+
322
+ // Apply plugins
323
+ applyPlugins(store, definition);
324
+
325
+ // Notify devtools
326
+ registerDevtoolsStore(id, store);
327
+
328
+ return store;
329
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Store factory helpers.
3
+ */
4
+
5
+ import { createStore } from './create-store';
6
+ import { getStore, hasStore } from './registry';
7
+ import type { Store, StoreDefinition } from './types';
8
+
9
+ /**
10
+ * Creates a store factory that returns the store instance.
11
+ *
12
+ * The store is lazily created on first call and cached in the global store
13
+ * registry. Subsequent calls return the same instance. After calling
14
+ * `destroyStore(id)`, the next factory call will create a fresh store.
15
+ *
16
+ * @param id - Store identifier
17
+ * @param definition - Store definition without id
18
+ * @returns A function that returns the store instance
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const useCounter = defineStore('counter', {
23
+ * state: () => ({ count: 0 }),
24
+ * actions: { increment() { this.count++; } },
25
+ * });
26
+ *
27
+ * const counter = useCounter();
28
+ * counter.increment();
29
+ * ```
30
+ */
31
+ export const defineStore = <
32
+ S extends Record<string, unknown>,
33
+ G extends Record<string, unknown> = Record<string, never>,
34
+ A extends Record<string, (...args: unknown[]) => unknown> = Record<string, never>,
35
+ >(
36
+ id: string,
37
+ definition: Omit<StoreDefinition<S, G, A>, 'id'>
38
+ ): (() => Store<S, G, A>) => {
39
+ // Check registry first to avoid noisy warnings from createStore()
40
+ // when the factory is called multiple times (intended usage pattern).
41
+ // createStore() only called when store doesn't exist or was destroyed.
42
+ return () => {
43
+ if (hasStore(id)) {
44
+ return getStore(id) as Store<S, G, A>;
45
+ }
46
+ return createStore({ id, ...definition });
47
+ };
48
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Devtools integration for stores.
3
+ * @internal
4
+ */
5
+
6
+ declare global {
7
+ interface Window {
8
+ __BQUERY_DEVTOOLS__?: {
9
+ stores: Map<string, unknown>;
10
+ onStoreCreated?: (id: string, store: unknown) => void;
11
+ onStateChange?: (id: string, state: unknown) => void;
12
+ };
13
+ }
14
+ }
15
+
16
+ export type DevtoolsHook = {
17
+ stores: Map<string, unknown>;
18
+ onStoreCreated?: (id: string, store: unknown) => void;
19
+ onStateChange?: (id: string, state: unknown) => void;
20
+ };
21
+
22
+ const ensureDevtools = (): DevtoolsHook | undefined => {
23
+ if (typeof window === 'undefined') return undefined;
24
+ if (!window.__BQUERY_DEVTOOLS__) {
25
+ window.__BQUERY_DEVTOOLS__ = { stores: new Map() };
26
+ }
27
+ return window.__BQUERY_DEVTOOLS__;
28
+ };
29
+
30
+ export const registerDevtoolsStore = (id: string, store: unknown): void => {
31
+ const devtools = ensureDevtools();
32
+ if (!devtools) return;
33
+ devtools.stores.set(id, store);
34
+ devtools.onStoreCreated?.(id, store);
35
+ };
36
+
37
+ export const unregisterDevtoolsStore = (id: string): void => {
38
+ if (typeof window === 'undefined' || !window.__BQUERY_DEVTOOLS__) return;
39
+ window.__BQUERY_DEVTOOLS__.stores.delete(id);
40
+ };
41
+
42
+ export const notifyDevtoolsStateChange = (id: string, state: unknown): void => {
43
+ if (typeof window === 'undefined') return;
44
+ window.__BQUERY_DEVTOOLS__?.onStateChange?.(id, state);
45
+ };