@bquery/bquery 1.7.0 → 1.8.2

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 (262) hide show
  1. package/README.md +760 -716
  2. package/dist/{a11y-C5QOVvRn.js → a11y-DVBCy09c.js} +3 -3
  3. package/dist/a11y-DVBCy09c.js.map +1 -0
  4. package/dist/a11y.es.mjs +1 -1
  5. package/dist/component/library.d.ts.map +1 -1
  6. package/dist/{component-CuuTijA6.js → component-L3-JfOFz.js} +5 -5
  7. package/dist/component-L3-JfOFz.js.map +1 -0
  8. package/dist/component.es.mjs +1 -1
  9. package/dist/{config-BW35FKuA.js → config-DhT9auRm.js} +1 -1
  10. package/dist/{config-BW35FKuA.js.map → config-DhT9auRm.js.map} +1 -1
  11. package/dist/{constraints-3lV9yyBw.js → constraints-D5RHQLmP.js} +1 -1
  12. package/dist/constraints-D5RHQLmP.js.map +1 -0
  13. package/dist/core/collection.d.ts +86 -0
  14. package/dist/core/collection.d.ts.map +1 -1
  15. package/dist/core/element.d.ts +28 -0
  16. package/dist/core/element.d.ts.map +1 -1
  17. package/dist/core/shared.d.ts +6 -0
  18. package/dist/core/shared.d.ts.map +1 -1
  19. package/dist/core-DdtZHzsS.js +168 -0
  20. package/dist/core-DdtZHzsS.js.map +1 -0
  21. package/dist/{core-Cjl7GUu8.js → core-EMYSLzaT.js} +289 -259
  22. package/dist/core-EMYSLzaT.js.map +1 -0
  23. package/dist/core.es.mjs +48 -47
  24. package/dist/{custom-directives-7wAShnnd.js → custom-directives-Dr4C5lVV.js} +1 -1
  25. package/dist/custom-directives-Dr4C5lVV.js.map +1 -0
  26. package/dist/{devtools-D2fQLhDN.js → devtools-BhB2iDPT.js} +2 -2
  27. package/dist/devtools-BhB2iDPT.js.map +1 -0
  28. package/dist/devtools.es.mjs +1 -1
  29. package/dist/{dnd-B8EgyzaI.js → dnd-NwZBYh4l.js} +1 -1
  30. package/dist/dnd-NwZBYh4l.js.map +1 -0
  31. package/dist/dnd.es.mjs +1 -1
  32. package/dist/{env-NeVmr4Gf.js → env-CTdvLaH2.js} +1 -1
  33. package/dist/env-CTdvLaH2.js.map +1 -0
  34. package/dist/forms/create-form.d.ts.map +1 -1
  35. package/dist/forms/index.d.ts +3 -2
  36. package/dist/forms/index.d.ts.map +1 -1
  37. package/dist/forms/types.d.ts +46 -0
  38. package/dist/forms/types.d.ts.map +1 -1
  39. package/dist/forms/use-field.d.ts +34 -0
  40. package/dist/forms/use-field.d.ts.map +1 -0
  41. package/dist/forms/validators.d.ts +25 -0
  42. package/dist/forms/validators.d.ts.map +1 -1
  43. package/dist/forms-UcRHsYxC.js +227 -0
  44. package/dist/forms-UcRHsYxC.js.map +1 -0
  45. package/dist/forms.es.mjs +14 -12
  46. package/dist/full.d.ts +17 -26
  47. package/dist/full.d.ts.map +1 -1
  48. package/dist/full.es.mjs +206 -181
  49. package/dist/full.iife.js +33 -33
  50. package/dist/full.iife.js.map +1 -1
  51. package/dist/full.umd.js +33 -33
  52. package/dist/full.umd.js.map +1 -1
  53. package/dist/function-Cybd57JV.js +33 -0
  54. package/dist/function-Cybd57JV.js.map +1 -0
  55. package/dist/{i18n-BnnhTFOS.js → i18n-kuF6Ekj6.js} +3 -3
  56. package/dist/i18n-kuF6Ekj6.js.map +1 -0
  57. package/dist/i18n.es.mjs +1 -1
  58. package/dist/index.es.mjs +251 -228
  59. package/dist/media/breakpoints.d.ts.map +1 -1
  60. package/dist/media/types.d.ts +2 -2
  61. package/dist/media/types.d.ts.map +1 -1
  62. package/dist/{media-Di2Ta22s.js → media-i-fB5WxI.js} +3 -3
  63. package/dist/media-i-fB5WxI.js.map +1 -0
  64. package/dist/media.es.mjs +1 -1
  65. package/dist/{motion-qPj_TYGv.js → motion-BJsAuULb.js} +2 -2
  66. package/dist/motion-BJsAuULb.js.map +1 -0
  67. package/dist/motion.es.mjs +1 -1
  68. package/dist/{mount-SM07RUa6.js → mount-B4Y8bk8Z.js} +5 -5
  69. package/dist/mount-B4Y8bk8Z.js.map +1 -0
  70. package/dist/{platform-CPbCprb6.js → platform-Dw2gE3zI.js} +3 -3
  71. package/dist/{platform-CPbCprb6.js.map → platform-Dw2gE3zI.js.map} +1 -1
  72. package/dist/platform.es.mjs +2 -2
  73. package/dist/plugin/registry.d.ts.map +1 -1
  74. package/dist/{plugin-cPoOHFLY.js → plugin-C2WuC8SF.js} +20 -18
  75. package/dist/plugin-C2WuC8SF.js.map +1 -0
  76. package/dist/plugin.es.mjs +1 -1
  77. package/dist/reactive/async-data.d.ts +28 -3
  78. package/dist/reactive/async-data.d.ts.map +1 -1
  79. package/dist/reactive/computed.d.ts +3 -0
  80. package/dist/reactive/computed.d.ts.map +1 -1
  81. package/dist/reactive/effect.d.ts +3 -0
  82. package/dist/reactive/effect.d.ts.map +1 -1
  83. package/dist/reactive/http.d.ts +194 -0
  84. package/dist/reactive/http.d.ts.map +1 -0
  85. package/dist/reactive/index.d.ts +2 -2
  86. package/dist/reactive/index.d.ts.map +1 -1
  87. package/dist/reactive/pagination.d.ts +126 -0
  88. package/dist/reactive/pagination.d.ts.map +1 -0
  89. package/dist/reactive/polling.d.ts +55 -0
  90. package/dist/reactive/polling.d.ts.map +1 -0
  91. package/dist/reactive/readonly.d.ts +20 -1
  92. package/dist/reactive/readonly.d.ts.map +1 -1
  93. package/dist/reactive/rest.d.ts +293 -0
  94. package/dist/reactive/rest.d.ts.map +1 -0
  95. package/dist/reactive/scope.d.ts +140 -0
  96. package/dist/reactive/scope.d.ts.map +1 -0
  97. package/dist/reactive/signal.d.ts +16 -2
  98. package/dist/reactive/signal.d.ts.map +1 -1
  99. package/dist/reactive/to-value.d.ts +57 -0
  100. package/dist/reactive/to-value.d.ts.map +1 -0
  101. package/dist/reactive/websocket.d.ts +285 -0
  102. package/dist/reactive/websocket.d.ts.map +1 -0
  103. package/dist/reactive-DwkhUJfP.js +1148 -0
  104. package/dist/reactive-DwkhUJfP.js.map +1 -0
  105. package/dist/reactive.es.mjs +38 -19
  106. package/dist/{registry-CWf368tT.js → registry-B08iilIh.js} +1 -1
  107. package/dist/{registry-CWf368tT.js.map → registry-B08iilIh.js.map} +1 -1
  108. package/dist/router/constraints.d.ts.map +1 -1
  109. package/dist/router/index.d.ts +1 -1
  110. package/dist/router/index.d.ts.map +1 -1
  111. package/dist/router/router.d.ts.map +1 -1
  112. package/dist/router/state.d.ts +25 -2
  113. package/dist/router/state.d.ts.map +1 -1
  114. package/dist/router-CQikC9Ed.js +492 -0
  115. package/dist/router-CQikC9Ed.js.map +1 -0
  116. package/dist/router.es.mjs +9 -8
  117. package/dist/ssr/hydrate.d.ts.map +1 -1
  118. package/dist/{ssr-B2qd_WBB.js → ssr-_dAcGdzu.js} +4 -4
  119. package/dist/ssr-_dAcGdzu.js.map +1 -0
  120. package/dist/ssr.es.mjs +1 -1
  121. package/dist/store/persisted.d.ts.map +1 -1
  122. package/dist/{store-DWpyH6p5.js → store-Cb3gPRve.js} +7 -7
  123. package/dist/store-Cb3gPRve.js.map +1 -0
  124. package/dist/store.es.mjs +2 -2
  125. package/dist/storybook.es.mjs.map +1 -1
  126. package/dist/{testing-CsqjNUyy.js → testing-C5Sjfsna.js} +8 -8
  127. package/dist/testing-C5Sjfsna.js.map +1 -0
  128. package/dist/testing.es.mjs +1 -1
  129. package/dist/{type-guards-Do9DWgNp.js → type-guards-BMX2c0LP.js} +1 -1
  130. package/dist/{type-guards-Do9DWgNp.js.map → type-guards-BMX2c0LP.js.map} +1 -1
  131. package/dist/untrack-D0fnO5k2.js +36 -0
  132. package/dist/untrack-D0fnO5k2.js.map +1 -0
  133. package/dist/view/custom-directives.d.ts.map +1 -1
  134. package/dist/view.es.mjs +4 -4
  135. package/package.json +178 -177
  136. package/src/a11y/announce.ts +131 -131
  137. package/src/a11y/audit.ts +314 -314
  138. package/src/a11y/index.ts +68 -68
  139. package/src/a11y/media-preferences.ts +255 -255
  140. package/src/a11y/roving-tab-index.ts +164 -164
  141. package/src/a11y/skip-link.ts +255 -255
  142. package/src/a11y/trap-focus.ts +184 -184
  143. package/src/a11y/types.ts +183 -183
  144. package/src/component/component.ts +599 -599
  145. package/src/component/html.ts +153 -153
  146. package/src/component/index.ts +52 -52
  147. package/src/component/library.ts +540 -542
  148. package/src/component/scope.ts +212 -212
  149. package/src/component/types.ts +310 -310
  150. package/src/core/collection.ts +876 -707
  151. package/src/core/element.ts +1015 -981
  152. package/src/core/env.ts +60 -60
  153. package/src/core/index.ts +49 -49
  154. package/src/core/shared.ts +77 -62
  155. package/src/core/utils/index.ts +148 -148
  156. package/src/devtools/devtools.ts +410 -410
  157. package/src/devtools/index.ts +48 -48
  158. package/src/devtools/types.ts +104 -104
  159. package/src/dnd/draggable.ts +296 -296
  160. package/src/dnd/droppable.ts +228 -228
  161. package/src/dnd/index.ts +62 -62
  162. package/src/dnd/sortable.ts +307 -307
  163. package/src/dnd/types.ts +293 -293
  164. package/src/forms/create-form.ts +320 -278
  165. package/src/forms/index.ts +70 -65
  166. package/src/forms/types.ts +203 -154
  167. package/src/forms/use-field.ts +231 -0
  168. package/src/forms/validators.ts +294 -265
  169. package/src/full.ts +554 -480
  170. package/src/i18n/formatting.ts +67 -67
  171. package/src/i18n/i18n.ts +200 -200
  172. package/src/i18n/index.ts +67 -67
  173. package/src/i18n/translate.ts +182 -182
  174. package/src/i18n/types.ts +171 -171
  175. package/src/index.ts +108 -108
  176. package/src/media/battery.ts +116 -116
  177. package/src/media/breakpoints.ts +129 -131
  178. package/src/media/clipboard.ts +80 -80
  179. package/src/media/device-sensors.ts +158 -158
  180. package/src/media/geolocation.ts +119 -119
  181. package/src/media/index.ts +76 -76
  182. package/src/media/media-query.ts +92 -92
  183. package/src/media/network.ts +115 -115
  184. package/src/media/types.ts +177 -177
  185. package/src/media/viewport.ts +84 -84
  186. package/src/motion/index.ts +57 -57
  187. package/src/motion/morph.ts +151 -151
  188. package/src/motion/parallax.ts +120 -120
  189. package/src/motion/reduced-motion.ts +66 -66
  190. package/src/motion/types.ts +271 -271
  191. package/src/motion/typewriter.ts +164 -164
  192. package/src/plugin/index.ts +37 -37
  193. package/src/plugin/registry.ts +284 -269
  194. package/src/plugin/types.ts +137 -137
  195. package/src/reactive/async-data.ts +250 -29
  196. package/src/reactive/computed.ts +144 -130
  197. package/src/reactive/effect.ts +29 -6
  198. package/src/reactive/http.ts +790 -0
  199. package/src/reactive/index.ts +60 -0
  200. package/src/reactive/pagination.ts +317 -0
  201. package/src/reactive/polling.ts +179 -0
  202. package/src/reactive/readonly.ts +52 -8
  203. package/src/reactive/rest.ts +859 -0
  204. package/src/reactive/scope.ts +276 -0
  205. package/src/reactive/signal.ts +61 -1
  206. package/src/reactive/to-value.ts +71 -0
  207. package/src/reactive/websocket.ts +849 -0
  208. package/src/router/bq-link.ts +279 -279
  209. package/src/router/constraints.ts +204 -201
  210. package/src/router/index.ts +49 -49
  211. package/src/router/match.ts +312 -312
  212. package/src/router/path-pattern.ts +52 -52
  213. package/src/router/query.ts +38 -38
  214. package/src/router/router.ts +421 -402
  215. package/src/router/state.ts +51 -3
  216. package/src/router/types.ts +139 -139
  217. package/src/router/use-route.ts +68 -68
  218. package/src/router/utils.ts +157 -157
  219. package/src/security/index.ts +12 -12
  220. package/src/ssr/hydrate.ts +84 -82
  221. package/src/ssr/index.ts +70 -70
  222. package/src/ssr/render.ts +508 -508
  223. package/src/ssr/serialize.ts +296 -296
  224. package/src/ssr/types.ts +81 -81
  225. package/src/store/create-store.ts +467 -467
  226. package/src/store/index.ts +27 -27
  227. package/src/store/persisted.ts +245 -249
  228. package/src/store/types.ts +247 -247
  229. package/src/store/utils.ts +135 -135
  230. package/src/storybook/index.ts +480 -480
  231. package/src/testing/index.ts +42 -42
  232. package/src/testing/testing.ts +593 -593
  233. package/src/testing/types.ts +170 -170
  234. package/src/view/custom-directives.ts +28 -30
  235. package/src/view/evaluate.ts +292 -292
  236. package/src/view/process.ts +108 -108
  237. package/dist/a11y-C5QOVvRn.js.map +0 -1
  238. package/dist/component-CuuTijA6.js.map +0 -1
  239. package/dist/constraints-3lV9yyBw.js.map +0 -1
  240. package/dist/core-Cjl7GUu8.js.map +0 -1
  241. package/dist/core-DnlyjbF2.js +0 -112
  242. package/dist/core-DnlyjbF2.js.map +0 -1
  243. package/dist/custom-directives-7wAShnnd.js.map +0 -1
  244. package/dist/devtools-D2fQLhDN.js.map +0 -1
  245. package/dist/dnd-B8EgyzaI.js.map +0 -1
  246. package/dist/env-NeVmr4Gf.js.map +0 -1
  247. package/dist/forms-C3yovgH9.js +0 -141
  248. package/dist/forms-C3yovgH9.js.map +0 -1
  249. package/dist/i18n-BnnhTFOS.js.map +0 -1
  250. package/dist/media-Di2Ta22s.js.map +0 -1
  251. package/dist/motion-qPj_TYGv.js.map +0 -1
  252. package/dist/mount-SM07RUa6.js.map +0 -1
  253. package/dist/plugin-cPoOHFLY.js.map +0 -1
  254. package/dist/reactive-Cfv0RK6x.js +0 -233
  255. package/dist/reactive-Cfv0RK6x.js.map +0 -1
  256. package/dist/router-BrthaP_z.js +0 -473
  257. package/dist/router-BrthaP_z.js.map +0 -1
  258. package/dist/ssr-B2qd_WBB.js.map +0 -1
  259. package/dist/store-DWpyH6p5.js.map +0 -1
  260. package/dist/testing-CsqjNUyy.js.map +0 -1
  261. package/dist/untrack-DJVQQ2WM.js +0 -33
  262. package/dist/untrack-DJVQQ2WM.js.map +0 -1
@@ -1,467 +1,467 @@
1
- /**
2
- * Store creation logic.
3
- */
4
-
5
- import { isPromise } from '../core/utils/type-guards';
6
- import {
7
- batch,
8
- computed,
9
- signal,
10
- untrack,
11
- type ReadonlySignal,
12
- type Signal,
13
- } from '../reactive/index';
14
- import { notifyDevtoolsStateChange, registerDevtoolsStore } from './devtools';
15
- import { applyPlugins } from './plugins';
16
- import { getStore, hasStore, registerStore } from './registry';
17
- import type {
18
- ActionContext,
19
- Getters,
20
- OnActionCallback,
21
- Store,
22
- StoreDefinition,
23
- StoreSubscriber,
24
- } from './types';
25
- import { deepClone, detectNestedMutations, isDev } from './utils';
26
-
27
- /**
28
- * Creates a reactive store with state, getters, and actions.
29
- *
30
- * @template S - State type
31
- * @template G - Getters type
32
- * @template A - Actions type
33
- * @param definition - Store definition
34
- * @returns The reactive store instance
35
- */
36
- export const createStore = <
37
- S extends Record<string, unknown>,
38
- G extends Record<string, unknown> = Record<string, never>,
39
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
- A extends Record<string, (...args: any[]) => any> = Record<string, never>,
41
- >(
42
- definition: StoreDefinition<S, G, A>
43
- ): Store<S, G, A> => {
44
- const { id, state: stateFactory, getters = {} as Getters<S, G>, actions = {} as A } = definition;
45
-
46
- // Check for duplicate store IDs
47
- if (hasStore(id)) {
48
- console.warn(`bQuery store: Store "${id}" already exists. Returning existing instance.`);
49
- return getStore(id) as Store<S, G, A>;
50
- }
51
-
52
- // Create initial state
53
- const initialState = stateFactory();
54
-
55
- // Create signals for each state property
56
- const stateSignals = new Map<keyof S, Signal<unknown>>();
57
- for (const key of Object.keys(initialState) as Array<keyof S>) {
58
- stateSignals.set(key, signal(initialState[key]));
59
- }
60
-
61
- // Subscribers for $subscribe
62
- const subscribers: Array<StoreSubscriber<S>> = [];
63
-
64
- // Action lifecycle hooks for $onAction
65
- const actionListeners: Array<OnActionCallback<S, G, A>> = [];
66
-
67
- const reportOnActionError = (
68
- phase: 'listener' | 'after' | 'onError',
69
- actionName: string,
70
- error: unknown
71
- ): void => {
72
- if (!isDev() || typeof console === 'undefined' || typeof console.error !== 'function') return;
73
- console.error(
74
- `[bQuery store "${id}"] Error in $onAction ${phase} for action "${actionName}"`,
75
- error
76
- );
77
- };
78
-
79
- const warnedAsyncOnActionListeners = new WeakSet<OnActionCallback<S, G, A>>();
80
-
81
- const warnAsyncOnActionListener = (
82
- listener: OnActionCallback<S, G, A>,
83
- actionName: string
84
- ): void => {
85
- if (!isDev() || typeof console === 'undefined' || typeof console.warn !== 'function') return;
86
- if (warnedAsyncOnActionListeners.has(listener)) return;
87
- warnedAsyncOnActionListeners.add(listener);
88
- console.warn(
89
- `[bQuery store "${id}"] Async $onAction listener detected for action "${actionName}". If it awaits, register after()/onError() before the first await; late registrations will not affect the current action.`
90
- );
91
- };
92
-
93
- /**
94
- * Executes an action observer callback without allowing observer failures to
95
- * affect the action result. Handles both synchronous exceptions and async
96
- * rejections, routing all failures through the standard $onAction logger.
97
- *
98
- * @internal
99
- */
100
- const runOnActionCallback = (
101
- phase: 'listener' | 'after' | 'onError',
102
- actionName: string,
103
- callback: () => unknown,
104
- listener?: OnActionCallback<S, G, A>
105
- ): void => {
106
- try {
107
- const result = callback();
108
- if (isPromise(result)) {
109
- if (phase === 'listener' && listener) {
110
- warnAsyncOnActionListener(listener, actionName);
111
- }
112
- void result.catch((error) => {
113
- reportOnActionError(phase, actionName, error);
114
- });
115
- }
116
- } catch (error) {
117
- reportOnActionError(phase, actionName, error);
118
- }
119
- };
120
-
121
- /**
122
- * Gets the current state.
123
- *
124
- * For subscriber notifications (where a plain object snapshot is needed),
125
- * this creates a shallow copy. For internal reads, use stateProxy directly.
126
- *
127
- * **Note:** Returns a shallow snapshot. Nested object mutations will NOT
128
- * trigger reactive updates. This differs from frameworks like Pinia that
129
- * use deep reactivity. To update nested state, replace the entire object.
130
- *
131
- * Uses `untrack()` to prevent accidental dependency tracking when called
132
- * from within reactive contexts (e.g., `effect()` or `computed()`).
133
- *
134
- * @internal
135
- */
136
- const getCurrentState = (): S =>
137
- untrack(() => {
138
- return { ...stateProxy };
139
- });
140
-
141
- /**
142
- * Notifies subscribers of state changes.
143
- * Short-circuits if there are no subscribers and devtools aren't active
144
- * to avoid unnecessary snapshot overhead.
145
- * @internal
146
- */
147
- const notifySubscribers = (): void => {
148
- // Early return if no subscribers and no devtools hook
149
- const hasDevtools =
150
- typeof window !== 'undefined' &&
151
- typeof window.__BQUERY_DEVTOOLS__?.onStateChange === 'function';
152
- if (subscribers.length === 0 && !hasDevtools) {
153
- return;
154
- }
155
-
156
- const currentState = getCurrentState();
157
- for (const callback of subscribers) {
158
- callback(currentState);
159
- }
160
-
161
- notifyDevtoolsStateChange(id, currentState);
162
- };
163
-
164
- /**
165
- * Cached state proxy that lazily reads signal values.
166
- * Uses a Proxy to avoid creating new objects on each access.
167
- *
168
- * **Note:** This returns a shallow snapshot of the state. Nested object
169
- * mutations will NOT trigger reactive updates. For nested reactivity,
170
- * replace the entire object or use signals for nested properties.
171
- *
172
- * @internal
173
- */
174
- const stateProxy = new Proxy({} as S, {
175
- get: (_, prop: string | symbol) => {
176
- const key = prop as keyof S;
177
- if (stateSignals.has(key)) {
178
- return stateSignals.get(key)!.value;
179
- }
180
- return undefined;
181
- },
182
- ownKeys: () => Array.from(stateSignals.keys()) as string[],
183
- getOwnPropertyDescriptor: (_, prop) => {
184
- if (stateSignals.has(prop as keyof S)) {
185
- return { enumerable: true, configurable: true };
186
- }
187
- return undefined;
188
- },
189
- has: (_, prop) => stateSignals.has(prop as keyof S),
190
- });
191
-
192
- // Create computed getters
193
- const getterComputed = new Map<keyof G, ReadonlySignal<unknown>>();
194
-
195
- // Build the store proxy
196
- const store = {} as Store<S, G, A>;
197
-
198
- // Define state properties with getters/setters
199
- for (const key of Object.keys(initialState) as Array<keyof S>) {
200
- Object.defineProperty(store, key, {
201
- get: () => stateSignals.get(key)!.value,
202
- set: (value: unknown) => {
203
- stateSignals.get(key)!.value = value;
204
- notifySubscribers();
205
- },
206
- enumerable: true,
207
- configurable: false,
208
- });
209
- }
210
-
211
- // Define getters as computed properties
212
- for (const key of Object.keys(getters) as Array<keyof G>) {
213
- const getterFn = getters[key];
214
-
215
- // Create computed that reads from state signals via proxy (more efficient)
216
- const computedGetter = computed(() => {
217
- const state = stateProxy;
218
- // For getter dependencies, pass a proxy that reads from computed getters
219
- const getterProxy = new Proxy({} as G, {
220
- get: (_, prop: string | symbol) => {
221
- const propKey = prop as keyof G;
222
- if (getterComputed.has(propKey)) {
223
- return getterComputed.get(propKey)!.value;
224
- }
225
- return undefined;
226
- },
227
- });
228
- return getterFn(state, getterProxy);
229
- });
230
-
231
- getterComputed.set(key, computedGetter as unknown as ReadonlySignal<unknown>);
232
-
233
- Object.defineProperty(store, key, {
234
- get: () => computedGetter.value,
235
- enumerable: true,
236
- configurable: false,
237
- });
238
- }
239
-
240
- // Bind actions to the store context, with $onAction lifecycle support
241
- for (const key of Object.keys(actions) as Array<keyof A>) {
242
- const actionFn = actions[key];
243
- const actionName = key as keyof A & string;
244
-
245
- // Wrap action to enable 'this' binding and $onAction hooks
246
- (store as Record<string, unknown>)[actionName] = function (...args: unknown[]) {
247
- // Create a context that allows 'this.property' access
248
- const context = new Proxy(store, {
249
- get: (target, prop) => {
250
- if (typeof prop === 'string' && stateSignals.has(prop as keyof S)) {
251
- return stateSignals.get(prop as keyof S)!.value;
252
- }
253
- return (target as Record<string, unknown>)[prop as string];
254
- },
255
- set: (target, prop, value) => {
256
- if (typeof prop === 'string' && stateSignals.has(prop as keyof S)) {
257
- stateSignals.get(prop as keyof S)!.value = value;
258
- notifySubscribers();
259
- return true;
260
- }
261
- // Allow non-state property assignments (e.g., temporary variables in actions)
262
- // by delegating to the target object rather than returning false
263
- return Reflect.set(target, prop, value);
264
- },
265
- });
266
-
267
- // Run $onAction hooks if any listeners are registered
268
- if (actionListeners.length === 0) {
269
- return actionFn.apply(context, args);
270
- }
271
-
272
- const afterHooks: Array<(result: unknown) => void> = [];
273
- const errorHooks: Array<(error: unknown) => void> = [];
274
- const listenerSnapshot = [...actionListeners];
275
-
276
- const listenerContext = {
277
- name: actionName,
278
- store,
279
- args: args as Parameters<A[typeof actionName]>,
280
- after: (callback: (result: Awaited<ReturnType<A[typeof actionName]>>) => void) => {
281
- afterHooks.push((result) =>
282
- callback(result as Awaited<ReturnType<A[typeof actionName]>>)
283
- );
284
- },
285
- onError: (callback: (error: unknown) => void) => {
286
- errorHooks.push(callback);
287
- },
288
- } satisfies ActionContext<S, G, A, typeof actionName>;
289
-
290
- // Notify all action listeners (before phase)
291
- for (const listener of listenerSnapshot) {
292
- runOnActionCallback('listener', actionName, () => listener(listenerContext), listener);
293
- }
294
-
295
- let result: unknown;
296
- try {
297
- result = actionFn.apply(context, args);
298
- } catch (error) {
299
- for (const hook of errorHooks) {
300
- runOnActionCallback('onError', actionName, () => hook(error));
301
- }
302
- throw error;
303
- }
304
-
305
- // Handle async actions (promises)
306
- if (isPromise(result)) {
307
- return result.then(
308
- (resolved) => {
309
- for (const hook of afterHooks) {
310
- runOnActionCallback('after', actionName, () => hook(resolved));
311
- }
312
- return resolved;
313
- },
314
- (error) => {
315
- for (const hook of errorHooks) {
316
- runOnActionCallback('onError', actionName, () => hook(error));
317
- }
318
- throw error;
319
- }
320
- );
321
- }
322
-
323
- // Sync action — run after hooks immediately
324
- for (const hook of afterHooks) {
325
- runOnActionCallback('after', actionName, () => hook(result));
326
- }
327
- return result;
328
- };
329
- }
330
-
331
- // Add store utility methods
332
- Object.defineProperties(store, {
333
- $id: {
334
- value: id,
335
- writable: false,
336
- enumerable: false,
337
- },
338
- $reset: {
339
- value: () => {
340
- const fresh = stateFactory();
341
- batch(() => {
342
- for (const [key, sig] of stateSignals) {
343
- sig.value = fresh[key];
344
- }
345
- });
346
- notifySubscribers();
347
- },
348
- writable: false,
349
- enumerable: false,
350
- },
351
- $subscribe: {
352
- value: (callback: StoreSubscriber<S>) => {
353
- subscribers.push(callback);
354
- return () => {
355
- const index = subscribers.indexOf(callback);
356
- if (index > -1) subscribers.splice(index, 1);
357
- };
358
- },
359
- writable: false,
360
- enumerable: false,
361
- },
362
- $onAction: {
363
- value: (callback: OnActionCallback<S, G, A>) => {
364
- actionListeners.push(callback);
365
- return () => {
366
- const index = actionListeners.indexOf(callback);
367
- if (index > -1) actionListeners.splice(index, 1);
368
- };
369
- },
370
- writable: false,
371
- enumerable: false,
372
- },
373
- $patch: {
374
- value: (partial: Partial<S> | ((state: S) => void)) => {
375
- batch(() => {
376
- if (typeof partial === 'function') {
377
- // Capture state before mutation for nested mutation detection
378
- const devMode = isDev();
379
- const stateBefore = devMode ? deepClone(getCurrentState()) : null;
380
- const signalValuesBefore = devMode
381
- ? new Map(Array.from(stateSignals.entries()).map(([k, s]) => [k, s.value]))
382
- : null;
383
-
384
- // Mutation function
385
- const state = getCurrentState();
386
- partial(state);
387
-
388
- // Detect nested mutations in development mode
389
- if (devMode && stateBefore && signalValuesBefore) {
390
- const mutatedKeys = detectNestedMutations(stateBefore, state, signalValuesBefore);
391
- if (mutatedKeys.length > 0) {
392
- console.warn(
393
- `[bQuery store "${id}"] Nested mutation detected in $patch() for keys: ${mutatedKeys
394
- .map(String)
395
- .join(', ')}.\n` +
396
- 'Nested object mutations do not trigger reactive updates because the store uses shallow reactivity.\n' +
397
- 'To fix this, either:\n' +
398
- ' 1. Replace the entire object: state.user = { ...state.user, name: "New" }\n' +
399
- ' 2. Use $patchDeep() for automatic deep cloning\n' +
400
- 'See: https://bquery.dev/guide/store#deep-reactivity'
401
- );
402
- }
403
- }
404
-
405
- for (const [key, value] of Object.entries(state) as Array<[keyof S, unknown]>) {
406
- if (stateSignals.has(key)) {
407
- stateSignals.get(key)!.value = value;
408
- }
409
- }
410
- } else {
411
- // Partial object
412
- for (const [key, value] of Object.entries(partial) as Array<[keyof S, unknown]>) {
413
- if (stateSignals.has(key)) {
414
- stateSignals.get(key)!.value = value;
415
- }
416
- }
417
- }
418
- });
419
- notifySubscribers();
420
- },
421
- writable: false,
422
- enumerable: false,
423
- },
424
- $patchDeep: {
425
- value: (partial: Partial<S> | ((state: S) => void)) => {
426
- batch(() => {
427
- if (typeof partial === 'function') {
428
- // Deep clone state before mutation to ensure new references
429
- const state = deepClone(getCurrentState());
430
- partial(state);
431
-
432
- for (const [key, value] of Object.entries(state) as Array<[keyof S, unknown]>) {
433
- if (stateSignals.has(key)) {
434
- stateSignals.get(key)!.value = value;
435
- }
436
- }
437
- } else {
438
- // Deep clone each value in partial to ensure new references
439
- for (const [key, value] of Object.entries(partial) as Array<[keyof S, unknown]>) {
440
- if (stateSignals.has(key)) {
441
- stateSignals.get(key)!.value = deepClone(value);
442
- }
443
- }
444
- }
445
- });
446
- notifySubscribers();
447
- },
448
- writable: false,
449
- enumerable: false,
450
- },
451
- $state: {
452
- get: () => getCurrentState(),
453
- enumerable: false,
454
- },
455
- });
456
-
457
- // Register store
458
- registerStore(id, store);
459
-
460
- // Apply plugins
461
- applyPlugins(store, definition);
462
-
463
- // Notify devtools
464
- registerDevtoolsStore(id, store);
465
-
466
- return store;
467
- };
1
+ /**
2
+ * Store creation logic.
3
+ */
4
+
5
+ import { isPromise } from '../core/utils/type-guards';
6
+ import {
7
+ batch,
8
+ computed,
9
+ signal,
10
+ untrack,
11
+ type ReadonlySignal,
12
+ type Signal,
13
+ } from '../reactive/index';
14
+ import { notifyDevtoolsStateChange, registerDevtoolsStore } from './devtools';
15
+ import { applyPlugins } from './plugins';
16
+ import { getStore, hasStore, registerStore } from './registry';
17
+ import type {
18
+ ActionContext,
19
+ Getters,
20
+ OnActionCallback,
21
+ Store,
22
+ StoreDefinition,
23
+ StoreSubscriber,
24
+ } from './types';
25
+ import { deepClone, detectNestedMutations, isDev } from './utils';
26
+
27
+ /**
28
+ * Creates a reactive store with state, getters, and actions.
29
+ *
30
+ * @template S - State type
31
+ * @template G - Getters type
32
+ * @template A - Actions type
33
+ * @param definition - Store definition
34
+ * @returns The reactive store instance
35
+ */
36
+ export const createStore = <
37
+ S extends Record<string, unknown>,
38
+ G extends Record<string, unknown> = Record<string, never>,
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
+ A extends Record<string, (...args: any[]) => any> = Record<string, never>,
41
+ >(
42
+ definition: StoreDefinition<S, G, A>
43
+ ): Store<S, G, A> => {
44
+ const { id, state: stateFactory, getters = {} as Getters<S, G>, actions = {} as A } = definition;
45
+
46
+ // Check for duplicate store IDs
47
+ if (hasStore(id)) {
48
+ console.warn(`bQuery store: Store "${id}" already exists. Returning existing instance.`);
49
+ return getStore(id) as Store<S, G, A>;
50
+ }
51
+
52
+ // Create initial state
53
+ const initialState = stateFactory();
54
+
55
+ // Create signals for each state property
56
+ const stateSignals = new Map<keyof S, Signal<unknown>>();
57
+ for (const key of Object.keys(initialState) as Array<keyof S>) {
58
+ stateSignals.set(key, signal(initialState[key]));
59
+ }
60
+
61
+ // Subscribers for $subscribe
62
+ const subscribers: Array<StoreSubscriber<S>> = [];
63
+
64
+ // Action lifecycle hooks for $onAction
65
+ const actionListeners: Array<OnActionCallback<S, G, A>> = [];
66
+
67
+ const reportOnActionError = (
68
+ phase: 'listener' | 'after' | 'onError',
69
+ actionName: string,
70
+ error: unknown
71
+ ): void => {
72
+ if (!isDev() || typeof console === 'undefined' || typeof console.error !== 'function') return;
73
+ console.error(
74
+ `[bQuery store "${id}"] Error in $onAction ${phase} for action "${actionName}"`,
75
+ error
76
+ );
77
+ };
78
+
79
+ const warnedAsyncOnActionListeners = new WeakSet<OnActionCallback<S, G, A>>();
80
+
81
+ const warnAsyncOnActionListener = (
82
+ listener: OnActionCallback<S, G, A>,
83
+ actionName: string
84
+ ): void => {
85
+ if (!isDev() || typeof console === 'undefined' || typeof console.warn !== 'function') return;
86
+ if (warnedAsyncOnActionListeners.has(listener)) return;
87
+ warnedAsyncOnActionListeners.add(listener);
88
+ console.warn(
89
+ `[bQuery store "${id}"] Async $onAction listener detected for action "${actionName}". If it awaits, register after()/onError() before the first await; late registrations will not affect the current action.`
90
+ );
91
+ };
92
+
93
+ /**
94
+ * Executes an action observer callback without allowing observer failures to
95
+ * affect the action result. Handles both synchronous exceptions and async
96
+ * rejections, routing all failures through the standard $onAction logger.
97
+ *
98
+ * @internal
99
+ */
100
+ const runOnActionCallback = (
101
+ phase: 'listener' | 'after' | 'onError',
102
+ actionName: string,
103
+ callback: () => unknown,
104
+ listener?: OnActionCallback<S, G, A>
105
+ ): void => {
106
+ try {
107
+ const result = callback();
108
+ if (isPromise(result)) {
109
+ if (phase === 'listener' && listener) {
110
+ warnAsyncOnActionListener(listener, actionName);
111
+ }
112
+ void result.catch((error) => {
113
+ reportOnActionError(phase, actionName, error);
114
+ });
115
+ }
116
+ } catch (error) {
117
+ reportOnActionError(phase, actionName, error);
118
+ }
119
+ };
120
+
121
+ /**
122
+ * Gets the current state.
123
+ *
124
+ * For subscriber notifications (where a plain object snapshot is needed),
125
+ * this creates a shallow copy. For internal reads, use stateProxy directly.
126
+ *
127
+ * **Note:** Returns a shallow snapshot. Nested object mutations will NOT
128
+ * trigger reactive updates. This differs from frameworks like Pinia that
129
+ * use deep reactivity. To update nested state, replace the entire object.
130
+ *
131
+ * Uses `untrack()` to prevent accidental dependency tracking when called
132
+ * from within reactive contexts (e.g., `effect()` or `computed()`).
133
+ *
134
+ * @internal
135
+ */
136
+ const getCurrentState = (): S =>
137
+ untrack(() => {
138
+ return { ...stateProxy };
139
+ });
140
+
141
+ /**
142
+ * Notifies subscribers of state changes.
143
+ * Short-circuits if there are no subscribers and devtools aren't active
144
+ * to avoid unnecessary snapshot overhead.
145
+ * @internal
146
+ */
147
+ const notifySubscribers = (): void => {
148
+ // Early return if no subscribers and no devtools hook
149
+ const hasDevtools =
150
+ typeof window !== 'undefined' &&
151
+ typeof window.__BQUERY_DEVTOOLS__?.onStateChange === 'function';
152
+ if (subscribers.length === 0 && !hasDevtools) {
153
+ return;
154
+ }
155
+
156
+ const currentState = getCurrentState();
157
+ for (const callback of subscribers) {
158
+ callback(currentState);
159
+ }
160
+
161
+ notifyDevtoolsStateChange(id, currentState);
162
+ };
163
+
164
+ /**
165
+ * Cached state proxy that lazily reads signal values.
166
+ * Uses a Proxy to avoid creating new objects on each access.
167
+ *
168
+ * **Note:** This returns a shallow snapshot of the state. Nested object
169
+ * mutations will NOT trigger reactive updates. For nested reactivity,
170
+ * replace the entire object or use signals for nested properties.
171
+ *
172
+ * @internal
173
+ */
174
+ const stateProxy = new Proxy({} as S, {
175
+ get: (_, prop: string | symbol) => {
176
+ const key = prop as keyof S;
177
+ if (stateSignals.has(key)) {
178
+ return stateSignals.get(key)!.value;
179
+ }
180
+ return undefined;
181
+ },
182
+ ownKeys: () => Array.from(stateSignals.keys()) as string[],
183
+ getOwnPropertyDescriptor: (_, prop) => {
184
+ if (stateSignals.has(prop as keyof S)) {
185
+ return { enumerable: true, configurable: true };
186
+ }
187
+ return undefined;
188
+ },
189
+ has: (_, prop) => stateSignals.has(prop as keyof S),
190
+ });
191
+
192
+ // Create computed getters
193
+ const getterComputed = new Map<keyof G, ReadonlySignal<unknown>>();
194
+
195
+ // Build the store proxy
196
+ const store = {} as Store<S, G, A>;
197
+
198
+ // Define state properties with getters/setters
199
+ for (const key of Object.keys(initialState) as Array<keyof S>) {
200
+ Object.defineProperty(store, key, {
201
+ get: () => stateSignals.get(key)!.value,
202
+ set: (value: unknown) => {
203
+ stateSignals.get(key)!.value = value;
204
+ notifySubscribers();
205
+ },
206
+ enumerable: true,
207
+ configurable: false,
208
+ });
209
+ }
210
+
211
+ // Define getters as computed properties
212
+ for (const key of Object.keys(getters) as Array<keyof G>) {
213
+ const getterFn = getters[key];
214
+
215
+ // Create computed that reads from state signals via proxy (more efficient)
216
+ const computedGetter = computed(() => {
217
+ const state = stateProxy;
218
+ // For getter dependencies, pass a proxy that reads from computed getters
219
+ const getterProxy = new Proxy({} as G, {
220
+ get: (_, prop: string | symbol) => {
221
+ const propKey = prop as keyof G;
222
+ if (getterComputed.has(propKey)) {
223
+ return getterComputed.get(propKey)!.value;
224
+ }
225
+ return undefined;
226
+ },
227
+ });
228
+ return getterFn(state, getterProxy);
229
+ });
230
+
231
+ getterComputed.set(key, computedGetter as unknown as ReadonlySignal<unknown>);
232
+
233
+ Object.defineProperty(store, key, {
234
+ get: () => computedGetter.value,
235
+ enumerable: true,
236
+ configurable: false,
237
+ });
238
+ }
239
+
240
+ // Bind actions to the store context, with $onAction lifecycle support
241
+ for (const key of Object.keys(actions) as Array<keyof A>) {
242
+ const actionFn = actions[key];
243
+ const actionName = key as keyof A & string;
244
+
245
+ // Wrap action to enable 'this' binding and $onAction hooks
246
+ (store as Record<string, unknown>)[actionName] = function (...args: unknown[]) {
247
+ // Create a context that allows 'this.property' access
248
+ const context = new Proxy(store, {
249
+ get: (target, prop) => {
250
+ if (typeof prop === 'string' && stateSignals.has(prop as keyof S)) {
251
+ return stateSignals.get(prop as keyof S)!.value;
252
+ }
253
+ return (target as Record<string, unknown>)[prop as string];
254
+ },
255
+ set: (target, prop, value) => {
256
+ if (typeof prop === 'string' && stateSignals.has(prop as keyof S)) {
257
+ stateSignals.get(prop as keyof S)!.value = value;
258
+ notifySubscribers();
259
+ return true;
260
+ }
261
+ // Allow non-state property assignments (e.g., temporary variables in actions)
262
+ // by delegating to the target object rather than returning false
263
+ return Reflect.set(target, prop, value);
264
+ },
265
+ });
266
+
267
+ // Run $onAction hooks if any listeners are registered
268
+ if (actionListeners.length === 0) {
269
+ return actionFn.apply(context, args);
270
+ }
271
+
272
+ const afterHooks: Array<(result: unknown) => void> = [];
273
+ const errorHooks: Array<(error: unknown) => void> = [];
274
+ const listenerSnapshot = [...actionListeners];
275
+
276
+ const listenerContext = {
277
+ name: actionName,
278
+ store,
279
+ args: args as Parameters<A[typeof actionName]>,
280
+ after: (callback: (result: Awaited<ReturnType<A[typeof actionName]>>) => void) => {
281
+ afterHooks.push((result) =>
282
+ callback(result as Awaited<ReturnType<A[typeof actionName]>>)
283
+ );
284
+ },
285
+ onError: (callback: (error: unknown) => void) => {
286
+ errorHooks.push(callback);
287
+ },
288
+ } satisfies ActionContext<S, G, A, typeof actionName>;
289
+
290
+ // Notify all action listeners (before phase)
291
+ for (const listener of listenerSnapshot) {
292
+ runOnActionCallback('listener', actionName, () => listener(listenerContext), listener);
293
+ }
294
+
295
+ let result: unknown;
296
+ try {
297
+ result = actionFn.apply(context, args);
298
+ } catch (error) {
299
+ for (const hook of errorHooks) {
300
+ runOnActionCallback('onError', actionName, () => hook(error));
301
+ }
302
+ throw error;
303
+ }
304
+
305
+ // Handle async actions (promises)
306
+ if (isPromise(result)) {
307
+ return result.then(
308
+ (resolved) => {
309
+ for (const hook of afterHooks) {
310
+ runOnActionCallback('after', actionName, () => hook(resolved));
311
+ }
312
+ return resolved;
313
+ },
314
+ (error) => {
315
+ for (const hook of errorHooks) {
316
+ runOnActionCallback('onError', actionName, () => hook(error));
317
+ }
318
+ throw error;
319
+ }
320
+ );
321
+ }
322
+
323
+ // Sync action — run after hooks immediately
324
+ for (const hook of afterHooks) {
325
+ runOnActionCallback('after', actionName, () => hook(result));
326
+ }
327
+ return result;
328
+ };
329
+ }
330
+
331
+ // Add store utility methods
332
+ Object.defineProperties(store, {
333
+ $id: {
334
+ value: id,
335
+ writable: false,
336
+ enumerable: false,
337
+ },
338
+ $reset: {
339
+ value: () => {
340
+ const fresh = stateFactory();
341
+ batch(() => {
342
+ for (const [key, sig] of stateSignals) {
343
+ sig.value = fresh[key];
344
+ }
345
+ });
346
+ notifySubscribers();
347
+ },
348
+ writable: false,
349
+ enumerable: false,
350
+ },
351
+ $subscribe: {
352
+ value: (callback: StoreSubscriber<S>) => {
353
+ subscribers.push(callback);
354
+ return () => {
355
+ const index = subscribers.indexOf(callback);
356
+ if (index > -1) subscribers.splice(index, 1);
357
+ };
358
+ },
359
+ writable: false,
360
+ enumerable: false,
361
+ },
362
+ $onAction: {
363
+ value: (callback: OnActionCallback<S, G, A>) => {
364
+ actionListeners.push(callback);
365
+ return () => {
366
+ const index = actionListeners.indexOf(callback);
367
+ if (index > -1) actionListeners.splice(index, 1);
368
+ };
369
+ },
370
+ writable: false,
371
+ enumerable: false,
372
+ },
373
+ $patch: {
374
+ value: (partial: Partial<S> | ((state: S) => void)) => {
375
+ batch(() => {
376
+ if (typeof partial === 'function') {
377
+ // Capture state before mutation for nested mutation detection
378
+ const devMode = isDev();
379
+ const stateBefore = devMode ? deepClone(getCurrentState()) : null;
380
+ const signalValuesBefore = devMode
381
+ ? new Map(Array.from(stateSignals.entries()).map(([k, s]) => [k, s.value]))
382
+ : null;
383
+
384
+ // Mutation function
385
+ const state = getCurrentState();
386
+ partial(state);
387
+
388
+ // Detect nested mutations in development mode
389
+ if (devMode && stateBefore && signalValuesBefore) {
390
+ const mutatedKeys = detectNestedMutations(stateBefore, state, signalValuesBefore);
391
+ if (mutatedKeys.length > 0) {
392
+ console.warn(
393
+ `[bQuery store "${id}"] Nested mutation detected in $patch() for keys: ${mutatedKeys
394
+ .map(String)
395
+ .join(', ')}.\n` +
396
+ 'Nested object mutations do not trigger reactive updates because the store uses shallow reactivity.\n' +
397
+ 'To fix this, either:\n' +
398
+ ' 1. Replace the entire object: state.user = { ...state.user, name: "New" }\n' +
399
+ ' 2. Use $patchDeep() for automatic deep cloning\n' +
400
+ 'See: https://bquery.dev/guide/store#deep-reactivity'
401
+ );
402
+ }
403
+ }
404
+
405
+ for (const [key, value] of Object.entries(state) as Array<[keyof S, unknown]>) {
406
+ if (stateSignals.has(key)) {
407
+ stateSignals.get(key)!.value = value;
408
+ }
409
+ }
410
+ } else {
411
+ // Partial object
412
+ for (const [key, value] of Object.entries(partial) as Array<[keyof S, unknown]>) {
413
+ if (stateSignals.has(key)) {
414
+ stateSignals.get(key)!.value = value;
415
+ }
416
+ }
417
+ }
418
+ });
419
+ notifySubscribers();
420
+ },
421
+ writable: false,
422
+ enumerable: false,
423
+ },
424
+ $patchDeep: {
425
+ value: (partial: Partial<S> | ((state: S) => void)) => {
426
+ batch(() => {
427
+ if (typeof partial === 'function') {
428
+ // Deep clone state before mutation to ensure new references
429
+ const state = deepClone(getCurrentState());
430
+ partial(state);
431
+
432
+ for (const [key, value] of Object.entries(state) as Array<[keyof S, unknown]>) {
433
+ if (stateSignals.has(key)) {
434
+ stateSignals.get(key)!.value = value;
435
+ }
436
+ }
437
+ } else {
438
+ // Deep clone each value in partial to ensure new references
439
+ for (const [key, value] of Object.entries(partial) as Array<[keyof S, unknown]>) {
440
+ if (stateSignals.has(key)) {
441
+ stateSignals.get(key)!.value = deepClone(value);
442
+ }
443
+ }
444
+ }
445
+ });
446
+ notifySubscribers();
447
+ },
448
+ writable: false,
449
+ enumerable: false,
450
+ },
451
+ $state: {
452
+ get: () => getCurrentState(),
453
+ enumerable: false,
454
+ },
455
+ });
456
+
457
+ // Register store
458
+ registerStore(id, store);
459
+
460
+ // Apply plugins
461
+ applyPlugins(store, definition);
462
+
463
+ // Notify devtools
464
+ registerDevtoolsStore(id, store);
465
+
466
+ return store;
467
+ };