@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,93 @@
1
+ /**
2
+ * Core reactive signals.
3
+ */
4
+
5
+ import {
6
+ getCurrentObserver,
7
+ registerDependency,
8
+ scheduleObserver,
9
+ type ReactiveSource,
10
+ } from './internals';
11
+
12
+ /**
13
+ * A reactive value container that notifies subscribers on change.
14
+ *
15
+ * Signals are the foundational primitive of the reactive system.
16
+ * Reading a signal's value inside an effect or computed automatically
17
+ * establishes a reactive dependency.
18
+ *
19
+ * @template T - The type of the stored value
20
+ */
21
+ export class Signal<T> implements ReactiveSource {
22
+ private subscribers = new Set<() => void>();
23
+
24
+ /**
25
+ * Creates a new signal with an initial value.
26
+ * @param _value - The initial value
27
+ */
28
+ constructor(private _value: T) {}
29
+
30
+ /**
31
+ * Gets the current value and tracks the read if inside an observer.
32
+ * During untrack calls, getCurrentObserver returns undefined, preventing dependency tracking.
33
+ */
34
+ get value(): T {
35
+ const current = getCurrentObserver();
36
+ if (current) {
37
+ this.subscribers.add(current);
38
+ registerDependency(current, this);
39
+ }
40
+ return this._value;
41
+ }
42
+
43
+ /**
44
+ * Sets a new value and notifies all subscribers if the value changed.
45
+ * Uses Object.is for equality comparison.
46
+ */
47
+ set value(next: T) {
48
+ if (Object.is(this._value, next)) return;
49
+ this._value = next;
50
+ // Create snapshot to avoid issues with subscribers modifying the set during iteration
51
+ const subscribersSnapshot = Array.from(this.subscribers);
52
+ for (const subscriber of subscribersSnapshot) {
53
+ scheduleObserver(subscriber);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Reads the current value without tracking.
59
+ * Useful when you need the value but don't want to create a dependency.
60
+ *
61
+ * @returns The current value
62
+ */
63
+ peek(): T {
64
+ return this._value;
65
+ }
66
+
67
+ /**
68
+ * Updates the value using a function.
69
+ * Useful for updates based on the current value.
70
+ *
71
+ * @param updater - Function that receives current value and returns new value
72
+ */
73
+ update(updater: (current: T) => T): void {
74
+ this.value = updater(this._value);
75
+ }
76
+
77
+ /**
78
+ * Removes an observer from this signal's subscriber set.
79
+ * @internal
80
+ */
81
+ unsubscribe(observer: () => void): void {
82
+ this.subscribers.delete(observer);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Creates a new reactive signal.
88
+ *
89
+ * @template T - The type of the signal value
90
+ * @param value - The initial value
91
+ * @returns A new Signal instance
92
+ */
93
+ export const signal = <T>(value: T): Signal<T> => new Signal(value);
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Reactive effects.
3
+ */
4
+
5
+ import { CleanupFn, Observer, track, clearDependencies } from './internals';
6
+
7
+ /**
8
+ * Creates a side effect that automatically re-runs when dependencies change.
9
+ *
10
+ * The effect runs immediately upon creation and then re-runs whenever
11
+ * any signal or computed value read inside it changes.
12
+ *
13
+ * @param fn - The effect function to run
14
+ * @returns A cleanup function to stop the effect
15
+ */
16
+ export const effect = (fn: () => void | CleanupFn): CleanupFn => {
17
+ let cleanupFn: CleanupFn | void;
18
+ let isDisposed = false;
19
+
20
+ const observer: Observer = () => {
21
+ if (isDisposed) return;
22
+
23
+ if (cleanupFn) {
24
+ cleanupFn();
25
+ }
26
+
27
+ // Clear old dependencies before running to avoid stale subscriptions
28
+ clearDependencies(observer);
29
+
30
+ cleanupFn = track(observer, fn);
31
+ };
32
+
33
+ observer();
34
+
35
+ return () => {
36
+ isDisposed = true;
37
+ if (cleanupFn) {
38
+ cleanupFn();
39
+ }
40
+ // Clean up all dependencies when effect is disposed
41
+ clearDependencies(observer);
42
+ };
43
+ };
@@ -1,22 +1,23 @@
1
- /**
2
- * Reactive module providing fine-grained reactivity primitives.
3
- *
4
- * @module bquery/reactive
5
- */
6
-
7
- export {
8
- Computed,
9
- Signal,
10
- batch,
11
- computed,
12
- effect,
13
- isComputed,
14
- isSignal,
15
- persistedSignal,
16
- readonly,
17
- signal,
18
- untrack,
19
- watch,
20
- } from './signal';
21
-
22
- export type { CleanupFn, Observer, ReadonlySignal } from './signal';
1
+ /**
2
+ * Reactive module providing fine-grained reactivity primitives.
3
+ *
4
+ * @module bquery/reactive
5
+ */
6
+
7
+ export {
8
+ Computed,
9
+ Signal,
10
+ batch,
11
+ computed,
12
+ effect,
13
+ isComputed,
14
+ isSignal,
15
+ linkedSignal,
16
+ persistedSignal,
17
+ readonly,
18
+ signal,
19
+ untrack,
20
+ watch,
21
+ } from './signal';
22
+
23
+ export type { CleanupFn, LinkedSignal, Observer, ReadonlySignal } from './signal';
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Internal reactive plumbing shared across primitives.
3
+ * @internal
4
+ */
5
+
6
+ export type Observer = () => void;
7
+ export type CleanupFn = () => void;
8
+
9
+ /**
10
+ * Interface for reactive sources (Signals, Computed) that can unsubscribe observers.
11
+ * @internal
12
+ */
13
+ export interface ReactiveSource {
14
+ unsubscribe(observer: Observer): void;
15
+ }
16
+
17
+ const observerStack: Observer[] = [];
18
+ let batchDepth = 0;
19
+ const pendingObservers = new Set<Observer>();
20
+
21
+ // Track dependencies for each observer to enable cleanup
22
+ const observerDependencies = new WeakMap<Observer, Set<ReactiveSource>>();
23
+
24
+ export const track = <T>(observer: Observer, fn: () => T): T => {
25
+ observerStack.push(observer);
26
+ try {
27
+ return fn();
28
+ } finally {
29
+ observerStack.pop();
30
+ }
31
+ };
32
+
33
+ export const getCurrentObserver = (): Observer | undefined =>
34
+ observerStack[observerStack.length - 1];
35
+
36
+ /**
37
+ * Executes a function without exposing the current observer to dependencies.
38
+ * Unlike disabling tracking globally, this still allows nested reactive internals
39
+ * (e.g., computed recomputation) to track their own dependencies.
40
+ * @internal
41
+ */
42
+ export const withoutCurrentObserver = <T>(fn: () => T): T => {
43
+ // Push undefined to temporarily "hide" the current observer
44
+ // This way, Signal.value reads won't link to the previous observer,
45
+ // but nested track() calls (e.g., computed recompute) still work normally.
46
+ observerStack.push(undefined as unknown as Observer);
47
+ try {
48
+ return fn();
49
+ } finally {
50
+ observerStack.pop();
51
+ }
52
+ };
53
+
54
+ export const scheduleObserver = (observer: Observer): void => {
55
+ if (batchDepth > 0) {
56
+ pendingObservers.add(observer);
57
+ return;
58
+ }
59
+ observer();
60
+ };
61
+
62
+ const flushObservers = (): void => {
63
+ for (const observer of Array.from(pendingObservers)) {
64
+ pendingObservers.delete(observer);
65
+ observer();
66
+ }
67
+ };
68
+
69
+ export const beginBatch = (): void => {
70
+ batchDepth += 1;
71
+ };
72
+
73
+ export const endBatch = (): void => {
74
+ batchDepth -= 1;
75
+ if (batchDepth === 0) {
76
+ flushObservers();
77
+ }
78
+ };
79
+
80
+ /**
81
+ * Registers a dependency between an observer and a reactive source.
82
+ * @internal
83
+ */
84
+ export const registerDependency = (observer: Observer, source: ReactiveSource): void => {
85
+ let deps = observerDependencies.get(observer);
86
+ if (!deps) {
87
+ deps = new Set();
88
+ observerDependencies.set(observer, deps);
89
+ }
90
+ deps.add(source);
91
+ };
92
+
93
+ /**
94
+ * Clears all dependencies for an observer, unsubscribing from all sources.
95
+ * @internal
96
+ */
97
+ export const clearDependencies = (observer: Observer): void => {
98
+ const deps = observerDependencies.get(observer);
99
+ if (deps) {
100
+ for (const source of deps) {
101
+ source.unsubscribe(observer);
102
+ }
103
+ deps.clear();
104
+ }
105
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Linked (writable) computed helpers.
3
+ */
4
+
5
+ import { computed, Computed } from './computed';
6
+
7
+ /**
8
+ * A writable computed-like signal.
9
+ */
10
+ export interface LinkedSignal<T> {
11
+ /** Gets or sets the current value with dependency tracking. */
12
+ value: T;
13
+ /** Gets the current value without dependency tracking. */
14
+ peek(): T;
15
+ }
16
+
17
+ /**
18
+ * Creates a writable computed signal by linking a getter and setter.
19
+ *
20
+ * @template T - The derived value type
21
+ * @param getValue - Getter that derives the current value
22
+ * @param setValue - Setter that writes back to underlying signals
23
+ * @returns A writable computed-like signal
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const first = signal('Ada');
28
+ * const last = signal('Lovelace');
29
+ * const fullName = linkedSignal(
30
+ * () => `${first.value} ${last.value}`,
31
+ * (next) => {
32
+ * const [a, b] = next.split(' ');
33
+ * first.value = a ?? '';
34
+ * last.value = b ?? '';
35
+ * }
36
+ * );
37
+ * ```
38
+ */
39
+ export const linkedSignal = <T>(
40
+ getValue: () => T,
41
+ setValue: (value: T) => void
42
+ ): LinkedSignal<T> => {
43
+ const derived: Computed<T> = computed(getValue);
44
+
45
+ return {
46
+ get value(): T {
47
+ return derived.value;
48
+ },
49
+ set value(next: T) {
50
+ setValue(next);
51
+ },
52
+ peek(): T {
53
+ return derived.peek();
54
+ },
55
+ };
56
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * LocalStorage-backed signals.
3
+ */
4
+
5
+ import { signal, Signal } from './core';
6
+ import { effect } from './effect';
7
+
8
+ /**
9
+ * Creates a signal that persists to localStorage.
10
+ *
11
+ * @template T - The type of the signal value
12
+ * @param key - The localStorage key
13
+ * @param initialValue - The initial value if not found in storage
14
+ * @returns A Signal that syncs with localStorage (falls back to in-memory if unavailable)
15
+ */
16
+ export const persistedSignal = <T>(key: string, initialValue: T): Signal<T> => {
17
+ // Check if localStorage is available and accessible
18
+ let hasLocalStorage = false;
19
+ let storage: Storage | null = null;
20
+
21
+ try {
22
+ // In Safari private mode, accessing localStorage can throw SecurityError
23
+ storage = globalThis.localStorage;
24
+ if (storage) {
25
+ // Test actual access to ensure it's not just present but usable
26
+ // Use a randomized test key to avoid overwriting real user data
27
+ const testKey = `__bquery_test_${Math.random().toString(36).slice(2, 9)}__`;
28
+ const testValue = '__test__';
29
+ try {
30
+ storage.setItem(testKey, testValue);
31
+ storage.getItem(testKey);
32
+ hasLocalStorage = true;
33
+ } finally {
34
+ // Ensure we don't leave any test data behind
35
+ try {
36
+ storage.removeItem(testKey);
37
+ } catch {
38
+ // Ignore cleanup errors (e.g., storage becoming unavailable)
39
+ }
40
+ }
41
+ }
42
+ } catch {
43
+ // localStorage unavailable or access denied (Safari private mode, sandboxed iframes, etc.)
44
+ hasLocalStorage = false;
45
+ }
46
+
47
+ let stored: T = initialValue;
48
+
49
+ if (hasLocalStorage && storage) {
50
+ try {
51
+ const raw = storage.getItem(key);
52
+ if (raw !== null) {
53
+ stored = JSON.parse(raw) as T;
54
+ }
55
+ } catch {
56
+ // Use initial value on parse error or access denial
57
+ }
58
+ }
59
+
60
+ const sig = signal(stored);
61
+
62
+ // Only set up persistence effect if localStorage is available
63
+ if (hasLocalStorage && storage) {
64
+ effect(() => {
65
+ try {
66
+ storage!.setItem(key, JSON.stringify(sig.value));
67
+ } catch {
68
+ // Ignore storage errors (quota exceeded, sandboxed iframes, etc.)
69
+ }
70
+ });
71
+ }
72
+
73
+ return sig;
74
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Read-only signal wrappers.
3
+ */
4
+
5
+ import type { Signal } from './core';
6
+
7
+ /**
8
+ * A readonly wrapper around a signal that prevents writes.
9
+ * Provides read-only access to a signal's value while maintaining reactivity.
10
+ *
11
+ * @template T - The type of the wrapped value
12
+ */
13
+ export interface ReadonlySignal<T> {
14
+ /** Gets the current value with dependency tracking. */
15
+ readonly value: T;
16
+ /** Gets the current value without dependency tracking. */
17
+ peek(): T;
18
+ }
19
+
20
+ /**
21
+ * Creates a read-only view of a signal.
22
+ * Useful for exposing reactive state without allowing modifications.
23
+ *
24
+ * @template T - The type of the signal value
25
+ * @param sig - The signal to wrap
26
+ * @returns A readonly signal wrapper
27
+ */
28
+ export const readonly = <T>(sig: Signal<T>): ReadonlySignal<T> => ({
29
+ get value(): T {
30
+ return sig.value;
31
+ },
32
+ peek(): T {
33
+ return sig.peek();
34
+ },
35
+ });