@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
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Reactive effect scopes for grouped disposal.
3
+ *
4
+ * An `EffectScope` collects all effects, computed values, and watches created
5
+ * inside its `run()` callback so they can be disposed together with a single
6
+ * `stop()` call. Scopes nest — an inner scope is collected by its parent.
7
+ *
8
+ * @module bquery/reactive
9
+ */
10
+
11
+ import type { CleanupFn } from './internals';
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Types
15
+ // ---------------------------------------------------------------------------
16
+
17
+ /**
18
+ * A scope that collects reactive resources for grouped disposal.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { effectScope, signal, effect, computed } from '@bquery/bquery/reactive';
23
+ *
24
+ * const scope = effectScope();
25
+ *
26
+ * scope.run(() => {
27
+ * const count = signal(0);
28
+ * effect(() => console.log(count.value));
29
+ * const doubled = computed(() => count.value * 2);
30
+ * });
31
+ *
32
+ * scope.stop(); // All effects and computed values disposed
33
+ * ```
34
+ */
35
+ export interface EffectScope {
36
+ /** Whether the scope has not yet been stopped. */
37
+ readonly active: boolean;
38
+
39
+ /**
40
+ * Executes `fn` inside this scope, collecting any reactive resources
41
+ * (effects, computed values, watches, nested scopes) created during the call.
42
+ *
43
+ * `run()` is synchronous-only. Do not pass an async function or a function
44
+ * that returns a Promise — resources created after an `await` cannot be
45
+ * collected reliably.
46
+ *
47
+ * @template T - Return type of the provided function
48
+ * @param fn - Function to run inside the scope
49
+ * @returns The return value of `fn`
50
+ * @throws {Error} If the scope has already been stopped
51
+ */
52
+ run<T>(fn: () => T): T;
53
+
54
+ /**
55
+ * Disposes all collected resources and marks the scope as inactive.
56
+ * Calling `stop()` on an already-stopped scope is a safe no-op.
57
+ */
58
+ stop(): void;
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Internal scope stack
63
+ // ---------------------------------------------------------------------------
64
+
65
+ /** @internal */
66
+ interface ScopeInternal extends EffectScope {
67
+ /** @internal – Register a cleanup callback to run when the scope stops. */
68
+ _addDisposer(fn: CleanupFn): void;
69
+ }
70
+
71
+ const scopeStack: ScopeInternal[] = [];
72
+
73
+ /** @internal */
74
+ export const hasScopeDisposer = (
75
+ scope: EffectScope | undefined
76
+ ): scope is EffectScope & { _addDisposer(fn: CleanupFn): void } =>
77
+ typeof scope === 'object' && scope !== null && '_addDisposer' in scope;
78
+
79
+ const isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>
80
+ (typeof value === 'object' || typeof value === 'function') &&
81
+ value !== null &&
82
+ typeof (value as { then?: unknown }).then === 'function';
83
+
84
+ /**
85
+ * Best-effort detection for native async functions so `run()` can reject them
86
+ * before invocation. Transpiled async functions may not preserve this shape, so
87
+ * promise-like return values are still checked after execution as a fallback.
88
+ * @internal
89
+ */
90
+ const isAsyncFunction = (value: unknown): value is (...args: never[]) => Promise<unknown> => {
91
+ const constructorName =
92
+ typeof (value as { constructor?: unknown }).constructor === 'function'
93
+ ? (value as { constructor: { name?: unknown } }).constructor.name
94
+ : undefined;
95
+
96
+ return (
97
+ typeof value === 'function' &&
98
+ ((Symbol.toStringTag in value &&
99
+ (value as { [Symbol.toStringTag]?: unknown })[Symbol.toStringTag] === 'AsyncFunction') ||
100
+ constructorName === 'AsyncFunction')
101
+ );
102
+ };
103
+
104
+ /**
105
+ * Returns the currently active scope, or `undefined` if none.
106
+ * @internal
107
+ */
108
+ export const getActiveScope = (): EffectScope | undefined => {
109
+ for (let i = scopeStack.length - 1; i >= 0; i--) {
110
+ if (scopeStack[i].active) {
111
+ return scopeStack[i];
112
+ }
113
+ }
114
+
115
+ return undefined;
116
+ };
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // EffectScope implementation
120
+ // ---------------------------------------------------------------------------
121
+
122
+ class EffectScopeImpl implements ScopeInternal {
123
+ private disposers: CleanupFn[] = [];
124
+ private _active = true;
125
+
126
+ get active(): boolean {
127
+ return this._active;
128
+ }
129
+
130
+ /** @internal */
131
+ _addDisposer(fn: CleanupFn): void {
132
+ if (this._active) {
133
+ this.disposers.push(fn);
134
+ }
135
+ }
136
+
137
+ run<T>(fn: () => T): T {
138
+ if (!this._active) {
139
+ throw new Error('bQuery reactive: Cannot run in a stopped effectScope');
140
+ }
141
+ if (isAsyncFunction(fn)) {
142
+ throw new Error('bQuery reactive: effectScope.run() only supports synchronous callbacks');
143
+ }
144
+
145
+ scopeStack.push(this);
146
+ try {
147
+ const result = fn();
148
+ if (isPromiseLike(result)) {
149
+ this.stop();
150
+ throw new Error('bQuery reactive: effectScope.run() only supports synchronous callbacks');
151
+ }
152
+ return result;
153
+ } finally {
154
+ scopeStack.pop();
155
+ }
156
+ }
157
+
158
+ stop(): void {
159
+ if (!this._active) return;
160
+ this._active = false;
161
+
162
+ // Dispose in reverse order (LIFO) to mirror creation order
163
+ for (let i = this.disposers.length - 1; i >= 0; i--) {
164
+ try {
165
+ this.disposers[i]();
166
+ } catch (error) {
167
+ console.error('bQuery reactive: Error in scope cleanup', error);
168
+ }
169
+ }
170
+ this.disposers.length = 0;
171
+ }
172
+ }
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // Public API
176
+ // ---------------------------------------------------------------------------
177
+
178
+ /**
179
+ * Creates a new effect scope for grouped disposal of reactive resources.
180
+ *
181
+ * All `effect()`, `computed()`, `watch()`, and nested `effectScope()` calls
182
+ * made inside `scope.run(fn)` are automatically collected. Calling
183
+ * `scope.stop()` disposes them all at once.
184
+ *
185
+ * `run()` is synchronous-only. Create the scope outside async flows when
186
+ * needed, but keep the callback itself synchronous so cleanup registration
187
+ * stays deterministic.
188
+ *
189
+ * @returns A new {@link EffectScope}
190
+ *
191
+ * @example
192
+ * ```ts
193
+ * import { effectScope, signal, effect, onScopeDispose } from '@bquery/bquery/reactive';
194
+ *
195
+ * const scope = effectScope();
196
+ *
197
+ * scope.run(() => {
198
+ * const count = signal(0);
199
+ *
200
+ * effect(() => console.log(count.value));
201
+ *
202
+ * onScopeDispose(() => {
203
+ * console.log('Custom cleanup');
204
+ * });
205
+ * });
206
+ *
207
+ * scope.stop(); // logs "Custom cleanup", all effects stopped
208
+ * ```
209
+ */
210
+ export const effectScope = (): EffectScope => {
211
+ const scope = new EffectScopeImpl();
212
+
213
+ // If created inside another scope, auto-collect as a nested scope
214
+ const parent = getActiveScope();
215
+ if (hasScopeDisposer(parent)) {
216
+ parent._addDisposer(() => scope.stop());
217
+ }
218
+
219
+ return scope;
220
+ };
221
+
222
+ /**
223
+ * Returns the currently active {@link EffectScope}, or `undefined` if
224
+ * code is not running inside any scope's `run()` callback.
225
+ *
226
+ * @returns The active scope, or `undefined`
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * import { effectScope, getCurrentScope } from '@bquery/bquery/reactive';
231
+ *
232
+ * const scope = effectScope();
233
+ * scope.run(() => {
234
+ * console.log(getCurrentScope() !== undefined); // true
235
+ * });
236
+ *
237
+ * console.log(getCurrentScope()); // undefined
238
+ * ```
239
+ */
240
+ export const getCurrentScope = (): EffectScope | undefined => getActiveScope();
241
+
242
+ /**
243
+ * Registers a cleanup callback on the currently active scope.
244
+ *
245
+ * The callback runs when the scope is stopped. This is useful for
246
+ * registering arbitrary cleanup (e.g. event listeners, timers)
247
+ * alongside effects and computed values.
248
+ *
249
+ * @param fn - Cleanup function to run when the scope stops
250
+ * @throws {Error} If called outside an active scope
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * import { effectScope, onScopeDispose } from '@bquery/bquery/reactive';
255
+ *
256
+ * const scope = effectScope();
257
+ *
258
+ * scope.run(() => {
259
+ * const controller = new AbortController();
260
+ * fetch('/api/data', { signal: controller.signal });
261
+ *
262
+ * onScopeDispose(() => controller.abort());
263
+ * });
264
+ *
265
+ * scope.stop(); // abort() is called
266
+ * ```
267
+ */
268
+ export const onScopeDispose = (fn: CleanupFn): void => {
269
+ const scope = getActiveScope();
270
+ if (!scope || !scope.active || !hasScopeDisposer(scope)) {
271
+ throw new Error(
272
+ 'bQuery reactive: onScopeDispose() must be called inside an active effectScope'
273
+ );
274
+ }
275
+ scope._addDisposer(fn);
276
+ };
@@ -9,12 +9,25 @@ export { createUseFetch, useAsyncData, useFetch } from './async-data';
9
9
  export { Computed, computed } from './computed';
10
10
  export { Signal, signal } from './core';
11
11
  export { effect } from './effect';
12
+ export { createHttp, createRequestQueue, http, HttpError } from './http';
12
13
  export { linkedSignal } from './linked';
14
+ export { useInfiniteFetch, usePaginatedFetch } from './pagination';
13
15
  export { persistedSignal } from './persisted';
16
+ export { usePolling } from './polling';
14
17
  export { readonly } from './readonly';
18
+ export {
19
+ createRestClient,
20
+ deduplicateRequest,
21
+ useResource,
22
+ useResourceList,
23
+ useSubmit,
24
+ } from './rest';
25
+ export { effectScope, getCurrentScope, onScopeDispose } from './scope';
15
26
  export { isComputed, isSignal } from './type-guards';
27
+ export { toValue } from './to-value';
16
28
  export { untrack } from './untrack';
17
29
  export { watch } from './watch';
30
+ export { useEventSource, useWebSocket, useWebSocketChannel } from './websocket';
18
31
 
19
32
  export type { CleanupFn, Observer } from './internals';
20
33
  export type {
@@ -24,6 +37,53 @@ export type {
24
37
  FetchInput,
25
38
  UseAsyncDataOptions,
26
39
  UseFetchOptions,
40
+ UseFetchRetryConfig,
27
41
  } from './async-data';
42
+ export type {
43
+ HttpClient,
44
+ HttpProgressEvent,
45
+ HttpRequestConfig,
46
+ HttpResponse,
47
+ Interceptor,
48
+ InterceptorManager,
49
+ RequestQueue,
50
+ RequestQueueOptions,
51
+ RetryConfig,
52
+ } from './http';
53
+ export type {
54
+ InfiniteState,
55
+ PaginatedState,
56
+ UseInfiniteFetchOptions,
57
+ UsePaginatedFetchOptions,
58
+ } from './pagination';
59
+ export type { PollingState, UsePollingOptions } from './polling';
60
+ export type {
61
+ IdExtractor,
62
+ ResourceListActions,
63
+ RestClient,
64
+ UseResourceListOptions,
65
+ UseResourceListReturn,
66
+ UseResourceOptions,
67
+ UseResourceReturn,
68
+ UseSubmitOptions,
69
+ UseSubmitReturn,
70
+ } from './rest';
71
+ export type { EffectScope } from './scope';
28
72
  export type { LinkedSignal } from './linked';
29
- export type { ReadonlySignal } from './readonly';
73
+ export type { MaybeSignal } from './to-value';
74
+ export type { ReadonlySignal, ReadonlySignalHandle } from './readonly';
75
+ export type {
76
+ ChannelMessage,
77
+ ChannelSubscription,
78
+ EventSourceStatus,
79
+ UseEventSourceOptions,
80
+ UseEventSourceReturn,
81
+ UseWebSocketChannelOptions,
82
+ UseWebSocketChannelReturn,
83
+ UseWebSocketOptions,
84
+ UseWebSocketReturn,
85
+ WebSocketHeartbeatConfig,
86
+ WebSocketReconnectConfig,
87
+ WebSocketSerializer,
88
+ WebSocketStatus,
89
+ } from './websocket';
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Utility to unwrap reactive or plain values.
3
+ */
4
+
5
+ import { Computed } from './computed';
6
+ import { Signal } from './core';
7
+ import { readonly, isReadonlySignal } from './readonly';
8
+
9
+ /**
10
+ * A value that may be a raw value, a Signal, a `readonly()` wrapper, or a Computed.
11
+ *
12
+ * Useful for APIs that accept both reactive and plain inputs.
13
+ *
14
+ * Readonly wrappers are limited to the values returned by {@link readonly}. This keeps
15
+ * the type aligned with runtime behavior, where arbitrary structural `{ value, peek }`
16
+ * objects are intentionally returned unchanged.
17
+ *
18
+ * @template T - The underlying value type
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * function useTitle(title: MaybeSignal<string>) {
23
+ * document.title = toValue(title);
24
+ * }
25
+ *
26
+ * useTitle('Hello'); // plain string
27
+ * useTitle(signal('Hello')); // reactive signal
28
+ * useTitle(computed(() => 'Hi')); // computed value
29
+ * ```
30
+ */
31
+ export type MaybeSignal<T> = T | Signal<T> | ReturnType<typeof readonly<T>> | Computed<T>;
32
+
33
+ /**
34
+ * Extracts the current value from a Signal, a bQuery `readonly()` wrapper, a
35
+ * Computed, or returns the raw value as-is. This eliminates repetitive
36
+ * `isSignal(x) ? x.value : x` patterns throughout user code.
37
+ *
38
+ * Reading a Signal or Computed via `toValue()` uses `.value`, so the
39
+ * read **does** participate in reactive tracking when called inside
40
+ * an effect or computed.
41
+ *
42
+ * @template T - The underlying value type
43
+ * @param source - A plain value, Signal, bQuery readonly wrapper, or Computed
44
+ * @returns The unwrapped value
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { signal, computed, toValue } from '@bquery/bquery/reactive';
49
+ *
50
+ * const count = signal(5);
51
+ * const doubled = computed(() => count.value * 2);
52
+ *
53
+ * toValue(42); // 42
54
+ * toValue(count); // 5
55
+ * toValue(doubled); // 10
56
+ * toValue(null); // null
57
+ * ```
58
+ */
59
+ export const toValue = <T>(source: MaybeSignal<T>): T => {
60
+ if (source instanceof Signal || source instanceof Computed) {
61
+ return source.value;
62
+ }
63
+
64
+ if (isReadonlySignal<T>(source)) {
65
+ return source.value;
66
+ }
67
+
68
+ // Remaining values are plain `T` inputs. Structural readonly-like objects that are not
69
+ // branded bQuery wrappers intentionally fall through and are returned unchanged.
70
+ return source as T;
71
+ };