synstate 3.4.0 → 4.0.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 (294) hide show
  1. package/README.md +13 -8
  2. package/dist/core/authoring.d.mts +7 -0
  3. package/dist/core/authoring.d.mts.map +1 -0
  4. package/dist/core/authoring.mjs +7 -0
  5. package/dist/core/authoring.mjs.map +1 -0
  6. package/dist/core/class/child-observable-class.d.mts +93 -0
  7. package/dist/core/class/child-observable-class.d.mts.map +1 -0
  8. package/dist/core/class/child-observable-class.mjs +248 -0
  9. package/dist/core/class/child-observable-class.mjs.map +1 -0
  10. package/dist/core/class/create-child-observable.d.mts +159 -0
  11. package/dist/core/class/create-child-observable.d.mts.map +1 -0
  12. package/dist/core/class/create-child-observable.mjs +183 -0
  13. package/dist/core/class/create-child-observable.mjs.map +1 -0
  14. package/dist/core/class/create-root-observable.d.mts +37 -0
  15. package/dist/core/class/create-root-observable.d.mts.map +1 -0
  16. package/dist/core/class/create-root-observable.mjs +52 -0
  17. package/dist/core/class/create-root-observable.mjs.map +1 -0
  18. package/dist/core/class/index.d.mts +9 -0
  19. package/dist/core/class/index.d.mts.map +1 -0
  20. package/dist/core/class/index.mjs +9 -0
  21. package/dist/core/class/index.mjs.map +1 -0
  22. package/dist/core/class/manager-observable-class.d.mts +26 -0
  23. package/dist/core/class/manager-observable-class.d.mts.map +1 -0
  24. package/dist/core/class/manager-observable-class.mjs +116 -0
  25. package/dist/core/class/manager-observable-class.mjs.map +1 -0
  26. package/dist/core/class/observable-base-class.d.mts +97 -0
  27. package/dist/core/class/observable-base-class.d.mts.map +1 -0
  28. package/dist/core/class/observable-base-class.mjs +219 -0
  29. package/dist/core/class/observable-base-class.mjs.map +1 -0
  30. package/dist/core/class/observable-node.d.mts +40 -0
  31. package/dist/core/class/observable-node.d.mts.map +1 -0
  32. package/dist/core/class/observable-node.mjs +10 -0
  33. package/dist/core/class/observable-node.mjs.map +1 -0
  34. package/dist/core/class/propagation-state.d.mts +24 -0
  35. package/dist/core/class/propagation-state.d.mts.map +1 -0
  36. package/dist/core/class/propagation-state.mjs +66 -0
  37. package/dist/core/class/propagation-state.mjs.map +1 -0
  38. package/dist/core/class/root-observable-class.d.mts +23 -0
  39. package/dist/core/class/root-observable-class.d.mts.map +1 -0
  40. package/dist/core/class/root-observable-class.mjs +58 -0
  41. package/dist/core/class/root-observable-class.mjs.map +1 -0
  42. package/dist/core/combine/combine.d.mts.map +1 -1
  43. package/dist/core/combine/combine.mjs +25 -14
  44. package/dist/core/combine/combine.mjs.map +1 -1
  45. package/dist/core/combine/merge.d.mts.map +1 -1
  46. package/dist/core/combine/merge.mjs +26 -14
  47. package/dist/core/combine/merge.mjs.map +1 -1
  48. package/dist/core/combine/zip.d.mts.map +1 -1
  49. package/dist/core/combine/zip.mjs +43 -25
  50. package/dist/core/combine/zip.mjs.map +1 -1
  51. package/dist/core/create/counter.d.mts.map +1 -1
  52. package/dist/core/create/counter.mjs +65 -44
  53. package/dist/core/create/counter.mjs.map +1 -1
  54. package/dist/core/create/from-abortable-promise.d.mts.map +1 -1
  55. package/dist/core/create/from-abortable-promise.mjs +26 -18
  56. package/dist/core/create/from-abortable-promise.mjs.map +1 -1
  57. package/dist/core/create/from-promise.d.mts.map +1 -1
  58. package/dist/core/create/from-promise.mjs +30 -21
  59. package/dist/core/create/from-promise.mjs.map +1 -1
  60. package/dist/core/create/from-subscribable.d.mts.map +1 -1
  61. package/dist/core/create/from-subscribable.mjs +37 -24
  62. package/dist/core/create/from-subscribable.mjs.map +1 -1
  63. package/dist/core/create/just.d.mts.map +1 -1
  64. package/dist/core/create/just.mjs +4 -2
  65. package/dist/core/create/just.mjs.map +1 -1
  66. package/dist/core/create/source.d.mts.map +1 -1
  67. package/dist/core/create/source.mjs +27 -17
  68. package/dist/core/create/source.mjs.map +1 -1
  69. package/dist/core/create/timer.d.mts.map +1 -1
  70. package/dist/core/create/timer.mjs +40 -25
  71. package/dist/core/create/timer.mjs.map +1 -1
  72. package/dist/core/index.d.mts +2 -0
  73. package/dist/core/index.d.mts.map +1 -1
  74. package/dist/core/index.mjs +2 -0
  75. package/dist/core/index.mjs.map +1 -1
  76. package/dist/core/operators/audit.d.mts +3 -0
  77. package/dist/core/operators/audit.d.mts.map +1 -1
  78. package/dist/core/operators/audit.mjs +56 -28
  79. package/dist/core/operators/audit.mjs.map +1 -1
  80. package/dist/core/operators/debounce.d.mts +3 -0
  81. package/dist/core/operators/debounce.d.mts.map +1 -1
  82. package/dist/core/operators/debounce.mjs +57 -26
  83. package/dist/core/operators/debounce.mjs.map +1 -1
  84. package/dist/core/operators/filter.d.mts.map +1 -1
  85. package/dist/core/operators/filter.mjs +35 -21
  86. package/dist/core/operators/filter.mjs.map +1 -1
  87. package/dist/core/operators/index.d.mts +1 -0
  88. package/dist/core/operators/index.d.mts.map +1 -1
  89. package/dist/core/operators/index.mjs +1 -0
  90. package/dist/core/operators/index.mjs.map +1 -1
  91. package/dist/core/operators/map.d.mts.map +1 -1
  92. package/dist/core/operators/map.mjs +31 -17
  93. package/dist/core/operators/map.mjs.map +1 -1
  94. package/dist/core/operators/merge-map.d.mts +5 -0
  95. package/dist/core/operators/merge-map.d.mts.map +1 -1
  96. package/dist/core/operators/merge-map.mjs +52 -26
  97. package/dist/core/operators/merge-map.mjs.map +1 -1
  98. package/dist/core/operators/pairwise.d.mts.map +1 -1
  99. package/dist/core/operators/pairwise.mjs +31 -20
  100. package/dist/core/operators/pairwise.mjs.map +1 -1
  101. package/dist/core/operators/scan.d.mts.map +1 -1
  102. package/dist/core/operators/scan.mjs +27 -14
  103. package/dist/core/operators/scan.mjs.map +1 -1
  104. package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
  105. package/dist/core/operators/skip-if-no-change.mjs +33 -19
  106. package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
  107. package/dist/core/operators/skip-until.d.mts.map +1 -1
  108. package/dist/core/operators/skip-until.mjs +40 -26
  109. package/dist/core/operators/skip-until.mjs.map +1 -1
  110. package/dist/core/operators/skip-while.d.mts.map +1 -1
  111. package/dist/core/operators/skip-while.mjs +35 -20
  112. package/dist/core/operators/skip-while.mjs.map +1 -1
  113. package/dist/core/operators/switch-map.d.mts +5 -0
  114. package/dist/core/operators/switch-map.d.mts.map +1 -1
  115. package/dist/core/operators/switch-map.mjs +56 -25
  116. package/dist/core/operators/switch-map.mjs.map +1 -1
  117. package/dist/core/operators/take-until.d.mts.map +1 -1
  118. package/dist/core/operators/take-until.mjs +36 -29
  119. package/dist/core/operators/take-until.mjs.map +1 -1
  120. package/dist/core/operators/take-while.d.mts.map +1 -1
  121. package/dist/core/operators/take-while.mjs +36 -22
  122. package/dist/core/operators/take-while.mjs.map +1 -1
  123. package/dist/core/{predefined/operators → operators}/take.d.mts +7 -3
  124. package/dist/core/operators/take.d.mts.map +1 -0
  125. package/dist/core/operators/take.mjs +102 -0
  126. package/dist/core/operators/take.mjs.map +1 -0
  127. package/dist/core/operators/throttle.d.mts.map +1 -1
  128. package/dist/core/operators/throttle.mjs +41 -26
  129. package/dist/core/operators/throttle.mjs.map +1 -1
  130. package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
  131. package/dist/core/operators/with-buffered-from.mjs +43 -39
  132. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  133. package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
  134. package/dist/core/operators/with-current-value-from.mjs +36 -21
  135. package/dist/core/operators/with-current-value-from.mjs.map +1 -1
  136. package/dist/core/operators/with-initial-value.d.mts.map +1 -1
  137. package/dist/core/operators/with-initial-value.mjs +24 -12
  138. package/dist/core/operators/with-initial-value.mjs.map +1 -1
  139. package/dist/core/predefined/operators/index.d.mts +0 -1
  140. package/dist/core/predefined/operators/index.d.mts.map +1 -1
  141. package/dist/core/predefined/operators/index.mjs +0 -1
  142. package/dist/core/predefined/operators/index.mjs.map +1 -1
  143. package/dist/core/scope/index.d.mts +2 -0
  144. package/dist/core/scope/index.d.mts.map +1 -0
  145. package/dist/core/scope/index.mjs +2 -0
  146. package/dist/core/scope/index.mjs.map +1 -0
  147. package/dist/core/scope/scope.d.mts +51 -0
  148. package/dist/core/scope/scope.d.mts.map +1 -0
  149. package/dist/core/scope/scope.mjs +194 -0
  150. package/dist/core/scope/scope.mjs.map +1 -0
  151. package/dist/core/types/id.d.mts +0 -2
  152. package/dist/core/types/id.d.mts.map +1 -1
  153. package/dist/core/types/index.d.mts +1 -0
  154. package/dist/core/types/index.d.mts.map +1 -1
  155. package/dist/core/types/index.mjs +1 -0
  156. package/dist/core/types/index.mjs.map +1 -1
  157. package/dist/core/types/observable-family.d.mts +1 -0
  158. package/dist/core/types/observable-family.d.mts.map +1 -1
  159. package/dist/core/types/observable.d.mts +40 -28
  160. package/dist/core/types/observable.d.mts.map +1 -1
  161. package/dist/core/types/observable.mjs +24 -0
  162. package/dist/core/types/observable.mjs.map +1 -1
  163. package/dist/core/types/scope.d.mts +27 -0
  164. package/dist/core/types/scope.d.mts.map +1 -0
  165. package/dist/core/types/scope.mjs +2 -0
  166. package/dist/core/types/scope.mjs.map +1 -0
  167. package/dist/core/utils/current-scope.d.mts +30 -0
  168. package/dist/core/utils/current-scope.d.mts.map +1 -0
  169. package/dist/core/utils/current-scope.mjs +26 -0
  170. package/dist/core/utils/current-scope.mjs.map +1 -0
  171. package/dist/core/utils/id-maker.d.mts +14 -3
  172. package/dist/core/utils/id-maker.d.mts.map +1 -1
  173. package/dist/core/utils/id-maker.mjs +18 -14
  174. package/dist/core/utils/id-maker.mjs.map +1 -1
  175. package/dist/core/utils/index.d.mts +2 -1
  176. package/dist/core/utils/index.d.mts.map +1 -1
  177. package/dist/core/utils/index.mjs +2 -1
  178. package/dist/core/utils/index.mjs.map +1 -1
  179. package/dist/core/utils/max-depth.d.mts +1 -0
  180. package/dist/core/utils/max-depth.d.mts.map +1 -1
  181. package/dist/core/utils/max-depth.mjs +9 -7
  182. package/dist/core/utils/max-depth.mjs.map +1 -1
  183. package/dist/core/utils/observable-brand.d.mts +13 -0
  184. package/dist/core/utils/observable-brand.d.mts.map +1 -0
  185. package/dist/core/utils/observable-brand.mjs +13 -0
  186. package/dist/core/utils/observable-brand.mjs.map +1 -0
  187. package/dist/utils/create-boolean-state.d.mts +2 -2
  188. package/dist/utils/create-reducer.d.mts +7 -7
  189. package/dist/utils/create-reducer.mjs +2 -2
  190. package/dist/utils/create-reducer.mjs.map +1 -1
  191. package/dist/utils/create-state.d.mts +2 -2
  192. package/package.json +16 -16
  193. package/src/core/authoring.mts +19 -0
  194. package/src/core/class/child-observable-class.mts +374 -0
  195. package/src/core/{base → class}/circular-dependency-comparison.test.mts +46 -38
  196. package/src/core/{base → class}/circular-dependency.test.mts +38 -2
  197. package/src/core/class/complete-propagation.test.mts +264 -0
  198. package/src/core/class/create-child-observable.mts +370 -0
  199. package/src/core/class/create-root-observable.mts +107 -0
  200. package/src/core/class/index.mts +8 -0
  201. package/src/core/class/manager-observable-class.mts +166 -0
  202. package/src/core/class/observable-base-class.mts +315 -0
  203. package/src/core/{base → class}/observable-base.test.mts +1 -1
  204. package/src/core/class/observable-node.mts +54 -0
  205. package/src/core/class/propagation-order.test.mts +92 -0
  206. package/src/core/class/propagation-state.mts +80 -0
  207. package/src/core/class/remove-completed.test.mts +369 -0
  208. package/src/core/class/root-observable-class.mts +85 -0
  209. package/src/core/{base → class}/write-during-propagation.test.mts +3 -3
  210. package/src/core/combine/combine.mts +67 -55
  211. package/src/core/combine/merge.mts +33 -23
  212. package/src/core/combine/zip.mts +95 -81
  213. package/src/core/create/counter.mts +95 -65
  214. package/src/core/create/from-abortable-promise.mts +54 -42
  215. package/src/core/create/from-promise.mts +38 -29
  216. package/src/core/create/from-subscribable.mts +62 -45
  217. package/src/core/create/from-subscribable.test.mts +8 -3
  218. package/src/core/create/just.mts +4 -5
  219. package/src/core/create/source.mts +29 -20
  220. package/src/core/create/timer.mts +51 -32
  221. package/src/core/index.mts +2 -0
  222. package/src/core/operators/audit.mts +79 -45
  223. package/src/core/operators/debounce-audit-complete.test.mts +121 -0
  224. package/src/core/operators/debounce.mts +81 -40
  225. package/src/core/operators/filter.mts +48 -31
  226. package/src/core/operators/index.mts +1 -0
  227. package/src/core/operators/inner-ownership.test.mts +280 -0
  228. package/src/core/operators/map.mts +49 -32
  229. package/src/core/operators/merge-map.mts +81 -43
  230. package/src/core/operators/pairwise.mts +40 -30
  231. package/src/core/operators/scan.mts +41 -24
  232. package/src/core/operators/skip-if-no-change.mts +45 -31
  233. package/src/core/operators/skip-until.mts +55 -41
  234. package/src/core/operators/skip-while.mts +49 -31
  235. package/src/core/operators/switch-map.mts +74 -35
  236. package/src/core/operators/take-until-skip-until-teardown.test.mts +18 -12
  237. package/src/core/operators/take-until.mts +45 -41
  238. package/src/core/operators/take-while.mts +50 -33
  239. package/src/core/operators/take.mts +138 -0
  240. package/src/core/operators/throttle.mts +55 -38
  241. package/src/core/operators/with-buffered-from.mts +42 -44
  242. package/src/core/operators/with-buffered-from.test.mts +4 -3
  243. package/src/core/operators/with-current-value-from.mts +39 -25
  244. package/src/core/operators/with-initial-value.mts +30 -21
  245. package/src/core/predefined/operators/index.mts +0 -1
  246. package/src/core/scope/index.mts +1 -0
  247. package/src/core/scope/scope.mts +260 -0
  248. package/src/core/scope/scope.test.mts +462 -0
  249. package/src/core/types/id.mts +0 -4
  250. package/src/core/types/index.mts +1 -0
  251. package/src/core/types/observable-family.mts +2 -0
  252. package/src/core/types/observable.mts +77 -42
  253. package/src/core/types/scope.mts +27 -0
  254. package/src/core/utils/current-scope.mts +61 -0
  255. package/src/core/utils/id-maker.mts +20 -30
  256. package/src/core/utils/index.mts +2 -1
  257. package/src/core/utils/max-depth.mts +12 -9
  258. package/src/core/utils/observable-brand.mts +12 -0
  259. package/src/utils/create-boolean-state.mts +2 -2
  260. package/src/utils/create-boolean-state.test.mts +18 -4
  261. package/src/utils/create-reducer.mts +9 -9
  262. package/src/utils/create-reducer.test.mts +31 -14
  263. package/src/utils/create-state.mts +2 -2
  264. package/src/utils/create-state.test.mts +12 -14
  265. package/dist/core/base/create-child-observable.d.mts +0 -72
  266. package/dist/core/base/create-child-observable.d.mts.map +0 -1
  267. package/dist/core/base/create-child-observable.mjs +0 -285
  268. package/dist/core/base/create-child-observable.mjs.map +0 -1
  269. package/dist/core/base/create-observable-base.d.mts +0 -149
  270. package/dist/core/base/create-observable-base.d.mts.map +0 -1
  271. package/dist/core/base/create-observable-base.mjs +0 -273
  272. package/dist/core/base/create-observable-base.mjs.map +0 -1
  273. package/dist/core/base/create-root-observable.d.mts +0 -46
  274. package/dist/core/base/create-root-observable.d.mts.map +0 -1
  275. package/dist/core/base/create-root-observable.mjs +0 -80
  276. package/dist/core/base/create-root-observable.mjs.map +0 -1
  277. package/dist/core/base/index.d.mts +0 -3
  278. package/dist/core/base/index.d.mts.map +0 -1
  279. package/dist/core/base/index.mjs +0 -3
  280. package/dist/core/base/index.mjs.map +0 -1
  281. package/dist/core/predefined/operators/take.d.mts.map +0 -1
  282. package/dist/core/predefined/operators/take.mjs +0 -44
  283. package/dist/core/predefined/operators/take.mjs.map +0 -1
  284. package/dist/core/utils/utils.d.mts +0 -6
  285. package/dist/core/utils/utils.d.mts.map +0 -1
  286. package/dist/core/utils/utils.mjs +0 -37
  287. package/dist/core/utils/utils.mjs.map +0 -1
  288. package/src/core/base/create-child-observable.mts +0 -485
  289. package/src/core/base/create-observable-base.mts +0 -568
  290. package/src/core/base/create-root-observable.mts +0 -147
  291. package/src/core/base/index.mts +0 -2
  292. package/src/core/predefined/operators/take.mts +0 -48
  293. package/src/core/utils/utils.mts +0 -56
  294. package/src/core/utils/utils.test.mts +0 -88
@@ -0,0 +1,61 @@
1
+ import type { Scope } from '../types/index.mjs';
2
+
3
+ /** What a scope owns: an observable, as far as the scope needs to know it. */
4
+ export type ScopeMember = Readonly<{
5
+ isCompleted: boolean;
6
+ dispose: () => void;
7
+ }>;
8
+
9
+ /**
10
+ * The side of a scope the rest of the core talks to, on top of its public
11
+ * face {@link Scope}. Kept out of the public API (`core/utils/**` is excluded
12
+ * from index generation), so that only `createScope` and its siblings decide
13
+ * what a scope owns.
14
+ */
15
+ export type ScopeInternals = Scope &
16
+ Readonly<{
17
+ /** Takes `node` into the scope. */
18
+ adopt: (node: ScopeMember) => void;
19
+
20
+ /**
21
+ * Tells the scope that one of the nodes it adopted completed on its own,
22
+ * so that the scope can drop it and keep no reference to it. The node is
23
+ * completed (`isCompleted`) by the time it calls this.
24
+ */
25
+ release: () => void;
26
+
27
+ addCleanup: (cleanup: () => void) => void;
28
+ }>;
29
+
30
+ /**
31
+ * The scope whose `run` is executing. A field rather than a module-level
32
+ * `let`, so that the one place writing it is visibly this object.
33
+ */
34
+ const mut_slot: { current: ScopeInternals | undefined } = {
35
+ current: undefined,
36
+ };
37
+
38
+ export const getCurrentScopeInternals = (): ScopeInternals | undefined =>
39
+ mut_slot.current;
40
+
41
+ /**
42
+ * Calls `body` with `scope` as the current scope (`undefined`: none) and
43
+ * restores the previous one afterwards, also when `body` throws.
44
+ */
45
+ export const runWithScope = <R,>(
46
+ scope: ScopeInternals | undefined,
47
+ body: () => R,
48
+ ): R => {
49
+ const prev = mut_slot.current;
50
+
51
+ mut_slot.current = scope;
52
+
53
+ // `finally` is the point: an exception from `body` must not leave its scope
54
+ // current for whatever runs next. Nothing is caught.
55
+ // @sumi-expect-error exceptions/no-try
56
+ try {
57
+ return body();
58
+ } finally {
59
+ mut_slot.current = prev;
60
+ }
61
+ };
@@ -1,31 +1,21 @@
1
- import type {
2
- ObservableId,
3
- SubscriberId,
4
- UpdateToken,
5
- } from '../types/index.mjs';
6
-
7
- function* idMaker<T extends symbol>(): Generator<T, T, T> {
8
- let mut_i = 0;
9
-
10
- while (true) {
11
- // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
12
- yield Symbol(mut_i.toString()) as T;
13
-
14
- mut_i += 1;
15
- }
16
- }
17
-
18
- const observableIdMaker = idMaker<ObservableId>();
19
-
20
- const subscriberIdMaker = idMaker<SubscriberId>();
21
-
22
- const updateTokenMaker = idMaker<UpdateToken>();
23
-
24
- export const issueObservableId = (): ObservableId =>
25
- observableIdMaker.next().value;
26
-
27
- export const issueSubscriberId = (): SubscriberId =>
28
- subscriberIdMaker.next().value;
29
-
1
+ import type { UpdateToken } from '../types/index.mjs';
2
+
3
+ /**
4
+ * The token of a new update, which the update's root or async child passes to
5
+ * every node it reaches. A `Symbol` with no description, issued once per
6
+ * update and never per node, so its cost is spread over every node the
7
+ * update reaches. Unique without any counter, so the tokens of two copies of
8
+ * synstate in one program never coincide, and nothing is kept in a global.
9
+ */
30
10
  export const issueUpdateToken = (): UpdateToken =>
31
- updateTokenMaker.next().value;
11
+ // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
12
+ Symbol() as UpdateToken;
13
+
14
+ /**
15
+ * The token a node holds before its first update: one value shared by every
16
+ * node of this copy, since no update is ever issued it, so creating a node
17
+ * issues nothing.
18
+ */
19
+ export const notUpdatedYet: UpdateToken =
20
+ // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
21
+ Symbol('synstate/not-updated-yet') as UpdateToken;
@@ -1,4 +1,5 @@
1
+ export * from './current-scope.mjs';
1
2
  export * from './id-maker.mjs';
2
3
  export * from './max-depth.mjs';
4
+ export * from './observable-brand.mjs';
3
5
  export * from './observable-utils.mjs';
4
- export * from './utils.mjs';
@@ -1,11 +1,14 @@
1
- import { Arr, Optional, pipe } from 'ts-data-forge';
2
1
  import type { Observable } from '../types/index.mjs';
3
2
 
4
- export const maxDepth = (parents: readonly Observable<unknown>[]): number =>
5
- pipe(Arr.maxBy(parents, (p) => p.depth))
6
- // Explicit return type + non-curried `Optional.unwrapOr` call: TypeScript
7
- // 7 (native)'s inference for this chained generic call resolves to
8
- // `unknown` without them, even though TypeScript <= 6.0's checker
9
- // resolves this fine.
10
- .mapOptional((o): number => o.depth)
11
- .map(Optional.unwrapOr(0)).value;
3
+ /** The greatest `depth` among `parents`, or 0 when there are none. */
4
+ export const maxDepth = (parents: readonly Observable<unknown>[]): number => {
5
+ let mut_max = 0;
6
+
7
+ for (const p of parents) {
8
+ if (p.depth > mut_max) {
9
+ mut_max = p.depth;
10
+ }
11
+ }
12
+
13
+ return mut_max;
14
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The key every observable carries, set to `true` by `assembleObservable`. It
3
+ * lets `isObservable` tell an observable from any other value at run time —
4
+ * a `subscribe` method is not enough, since plain values may have one — and,
5
+ * as a type, makes the public observable types impossible to satisfy with an
6
+ * object literal: an imitation passed to an operator would lack the internal
7
+ * members propagation reads and fail at run time instead of at compile time.
8
+ *
9
+ * `Symbol.for`, so that an observable made by another copy of synstate in the
10
+ * same program is still recognized.
11
+ */
12
+ export const observableBrand: unique symbol = Symbol.for('synstate/observable');
@@ -7,8 +7,8 @@ import { createState } from './create-state.mjs';
7
7
  * Extends `createState` with boolean-specific helpers like `toggle`, `setTrue`, and `setFalse`.
8
8
  *
9
9
  * @param initialState - The initial boolean value
10
- * @param options - {@link StateOptions}: `equals`, to pass on only the
11
- * updates that change the state
10
+ * @param options - {@link StateOptions}: `equals` (default `Object.is`),
11
+ * to pass on only the updates that change the state
12
12
  * @returns A 2-element tuple: `[state, { setTrue, setFalse, toggle, setState, updateState, resetState, getSnapshot, initialState }]`
13
13
  *
14
14
  * @example
@@ -1,10 +1,8 @@
1
1
  import { createBooleanState } from './create-boolean-state.mjs';
2
2
 
3
3
  describe(createBooleanState, () => {
4
- test('with `equals`, setting what it already holds passes nothing on', () => {
5
- const [state, { setTrue, setFalse }] = createBooleanState(false, {
6
- equals: Object.is,
7
- });
4
+ test('setting what it already holds passes nothing on when no `equals` is given', () => {
5
+ const [state, { setTrue, setFalse }] = createBooleanState(false);
8
6
 
9
7
  const mut_seen: boolean[] = [];
10
8
 
@@ -20,4 +18,20 @@ describe(createBooleanState, () => {
20
18
 
21
19
  assert.deepStrictEqual(mut_seen, [false, true]);
22
20
  });
21
+
22
+ test('passes on every update, equal or not, with `equals: () => false`', () => {
23
+ const [state, { setTrue }] = createBooleanState(true, {
24
+ equals: () => false,
25
+ });
26
+
27
+ const mut_seen: boolean[] = [];
28
+
29
+ state.subscribe((value) => {
30
+ mut_seen.push(value);
31
+ });
32
+
33
+ setTrue();
34
+
35
+ assert.deepStrictEqual(mut_seen, [true, true]);
36
+ });
23
37
  });
@@ -9,8 +9,8 @@ import { source, type InitializedObservable } from '../core/index.mjs';
9
9
  * @template A - The type of actions
10
10
  * @param reducer - A pure function that takes current state and action, returns new state
11
11
  * @param initialState - The initial value of the state
12
- * @param options - {@link StateOptions}: `equals`, to pass on only the
13
- * dispatches that change the state
12
+ * @param options - {@link StateOptions}: `equals` (default `Object.is`),
13
+ * to pass on only the dispatches that change the state
14
14
  * @returns A 3-element tuple: `[state, dispatch, { getSnapshot, initialState }]`.
15
15
  * `dispatch` returns the state it leaves; once `state` is completed, that
16
16
  * is the state held.
@@ -54,7 +54,7 @@ import { source, type InitializedObservable } from '../core/index.mjs';
54
54
  * (s, action: Readonly<{ type: 'add'; amount: number }>) =>
55
55
  * s + action.amount,
56
56
  * 0,
57
- * { equals: Object.is },
57
+ * { equals: () => false },
58
58
  * );
59
59
  *
60
60
  * const countHistory: number[] = [];
@@ -63,11 +63,11 @@ import { source, type InitializedObservable } from '../core/index.mjs';
63
63
  * countHistory.push(value);
64
64
  * });
65
65
  *
66
- * dispatch({ type: 'add', amount: 0 }); // 0 again: nothing is passed on
66
+ * dispatch({ type: 'add', amount: 0 }); // 0 again, passed on all the same
67
67
  *
68
68
  * dispatch({ type: 'add', amount: 2 });
69
69
  *
70
- * assert.deepStrictEqual(countHistory, [0, 2]);
70
+ * assert.deepStrictEqual(countHistory, [0, 0, 2]);
71
71
  * ```
72
72
  *
73
73
  * @note Do not call `dispatch` from a subscriber or a projection while another
@@ -91,7 +91,7 @@ export const createReducer = <S, A>(
91
91
  ] => {
92
92
  const state = source<S>(initialState);
93
93
 
94
- const equals = options?.equals;
94
+ const equals = options?.equals ?? Object.is;
95
95
 
96
96
  const dispatch = (action: A): S => {
97
97
  const currentState = state.getSnapshot().value;
@@ -99,7 +99,7 @@ export const createReducer = <S, A>(
99
99
  const nextState = reducer(currentState, action);
100
100
 
101
101
  // Once completed, `next` keeps the state held and returns it.
102
- return equals?.(currentState, nextState) === true
102
+ return equals(currentState, nextState)
103
103
  ? currentState
104
104
  : state.next(nextState);
105
105
  };
@@ -124,8 +124,8 @@ export type StateOptions<S> = Readonly<{
124
124
  * `skipIfNoChange`: an update passed on here reaches whatever reads the
125
125
  * state directly before any operator can thin it out.
126
126
  *
127
- * When omitted, every update is passed on, equal or not. That default is
128
- * stocked in `NEXT_MAJOR.md` to become `Object.is`.
127
+ * Defaults to `Object.is`, so writing the value already held passes
128
+ * nothing on. Pass `() => false` to pass on every update, equal or not.
129
129
  *
130
130
  * @param current - The state held now
131
131
  * @param next - The state the update would set
@@ -2,34 +2,51 @@ import type { FixedLengthTuple } from 'ts-type-forge';
2
2
  import { createReducer } from './create-reducer.mjs';
3
3
 
4
4
  describe(createReducer, () => {
5
- test('passes on every dispatch when no `equals` is given, equal or not', () => {
6
- const [state, dispatch] = createReducer(keepOrAdd, 0);
5
+ test('does not pass on a dispatch whose next state is `Object.is`-equal to the current one when no `equals` is given', () => {
6
+ const [state, dispatch, { getSnapshot }] = createReducer(keepOrAdd, 0);
7
7
 
8
8
  const seen = record(state);
9
9
 
10
- dispatch({ type: 'keep' });
10
+ assert.strictEqual(dispatch({ type: 'keep' }), 0);
11
11
 
12
- dispatch({ type: 'add', amount: 1 });
12
+ assert.strictEqual(dispatch({ type: 'add', amount: 1 }), 1);
13
13
 
14
- assert.deepStrictEqual(seen, [0, 0, 1]);
14
+ assert.strictEqual(dispatch({ type: 'add', amount: 0 }), 1);
15
+
16
+ assert.deepStrictEqual(seen, [0, 1]);
17
+
18
+ assert.strictEqual(getSnapshot(), 1);
15
19
  });
16
20
 
17
- test('with `equals`, does not pass on a dispatch whose next state is equal to the current one', () => {
18
- const [state, dispatch, { getSnapshot }] = createReducer(keepOrAdd, 0, {
19
- equals: Object.is,
20
- });
21
+ test('compares with `Object.is` by default, so `NaN` equals itself and `-0` differs from `0`', () => {
22
+ const [state, dispatch] = createReducer(
23
+ (_current: number, next: number) => next,
24
+ 0,
25
+ );
21
26
 
22
27
  const seen = record(state);
23
28
 
24
- assert.strictEqual(dispatch({ type: 'keep' }), 0);
29
+ dispatch(-0);
25
30
 
26
- assert.strictEqual(dispatch({ type: 'add', amount: 1 }), 1);
31
+ dispatch(Number.NaN);
27
32
 
28
- assert.strictEqual(dispatch({ type: 'add', amount: 0 }), 1);
33
+ dispatch(Number.NaN);
29
34
 
30
- assert.deepStrictEqual(seen, [0, 1]);
35
+ assert.deepStrictEqual(seen, [0, -0, Number.NaN]);
36
+ });
31
37
 
32
- assert.strictEqual(getSnapshot(), 1);
38
+ test('passes on every dispatch, equal or not, with `equals: () => false`', () => {
39
+ const [state, dispatch] = createReducer(keepOrAdd, 0, {
40
+ equals: () => false,
41
+ });
42
+
43
+ const seen = record(state);
44
+
45
+ dispatch({ type: 'keep' });
46
+
47
+ dispatch({ type: 'add', amount: 1 });
48
+
49
+ assert.deepStrictEqual(seen, [0, 0, 1]);
33
50
  });
34
51
 
35
52
  test('keeps the current object when `equals` finds the next one equal to it', () => {
@@ -28,8 +28,8 @@ const reducer = <S,>(state: S, action: Action<S>): S => {
28
28
  *
29
29
  * @template S - The type of the state
30
30
  * @param initialState - The initial value of the state
31
- * @param options - {@link StateOptions}: `equals`, to pass on only the
32
- * updates that change the state
31
+ * @param options - {@link StateOptions}: `equals` (default `Object.is`),
32
+ * to pass on only the updates that change the state
33
33
  * @returns A 3-element tuple: `[state, setState, { updateState, resetState, getSnapshot, initialState }]`
34
34
  *
35
35
  * @example
@@ -1,20 +1,8 @@
1
1
  import { createState } from './create-state.mjs';
2
2
 
3
3
  describe(createState, () => {
4
- test('passes on every update when no `equals` is given, equal or not', () => {
5
- const [state, setState] = createState(0);
6
-
7
- const seen = record(state);
8
-
9
- setState(0);
10
-
11
- assert.deepStrictEqual(seen, [0, 0]);
12
- });
13
-
14
- test('with `equals`, setState, updateState and resetState do not pass on a value equal to the current one', () => {
15
- const [state, setState, { updateState, resetState }] = createState(0, {
16
- equals: Object.is,
17
- });
4
+ test('setState, updateState and resetState do not pass on a value `Object.is`-equal to the current one when no `equals` is given', () => {
5
+ const [state, setState, { updateState, resetState }] = createState(0);
18
6
 
19
7
  const seen = record(state);
20
8
 
@@ -54,6 +42,16 @@ describe(createState, () => {
54
42
  assert.strictEqual(getSnapshot(), 1);
55
43
  });
56
44
 
45
+ test('passes on every update, equal or not, with `equals: () => false`', () => {
46
+ const [state, setState] = createState(0, { equals: () => false });
47
+
48
+ const seen = record(state);
49
+
50
+ setState(0);
51
+
52
+ assert.deepStrictEqual(seen, [0, 0]);
53
+ });
54
+
57
55
  // What `equals` is for: whatever reads the state, directly or through
58
56
  // anything derived from it, is told only of a real change, and an equal
59
57
  // value written over the current one leaves the object that is there.
@@ -1,72 +0,0 @@
1
- import { type Optional, type Some } from 'ts-data-forge';
2
- import { type AsyncChildObservable, type InitializedSyncChildObservable, type NonEmptyUnknownList, type SyncChildObservable, type UpdateToken, type Wrap } from '../types/index.mjs';
3
- /**
4
- * The internal tools a sync-child leaf factory (`map`, `filter`, `zip`, …)
5
- * receives — the replacement for the `protected` methods a subclass of
6
- * `SyncChildObservableClass` used to inherit.
7
- */
8
- export type SyncChildObservableTools<A> = Readonly<{
9
- /** Emits `nextValue` under `updateToken` and notifies subscribers. */
10
- setNext: (nextValue: A, updateToken: UpdateToken) => void;
11
- getSnapshot: () => Optional<A>;
12
- /** Completes this observable (leaf teardown included). */
13
- complete: () => void;
14
- }>;
15
- /**
16
- * The internal tools an async-child leaf factory (`debounce`, `switchMap`, …)
17
- * receives. Async children additionally start their own update propagation.
18
- */
19
- export type AsyncChildObservableTools<A> = Readonly<{
20
- /** Emits `nextValue` and propagates the update to all descendants. */
21
- startUpdate: (nextValue: A) => void;
22
- }> & SyncChildObservableTools<A>;
23
- type ChildObservableConfigBase<P extends NonEmptyUnknownList> = Readonly<{
24
- parents: Wrap<P>;
25
- depth?: number;
26
- /**
27
- * Leaf teardown (clear timers, unsubscribe inner observables, …), run at
28
- * the beginning of `complete()` — the closure replacement for
29
- * `override complete() { teardown(); super.complete(); }`.
30
- */
31
- onComplete?: () => void;
32
- /**
33
- * Whether this observable can emit nothing more although some parent is
34
- * still live, asked whenever a parent completes. `true` completes it then,
35
- * as all parents completing does (`zip` once a completed source's queue is
36
- * empty).
37
- */
38
- canEmitNoMore?: () => boolean;
39
- }>;
40
- export type ChildObservableConfig<A, P extends NonEmptyUnknownList> = ChildObservableConfigBase<P> & Readonly<{
41
- initialValue: Optional<A>;
42
- }>;
43
- export type InitializedChildObservableConfig<A, P extends NonEmptyUnknownList> = ChildObservableConfigBase<P> & Readonly<{
44
- initialValue: Some<A>;
45
- }>;
46
- /**
47
- * Creates a sync-child observable — the classless replacement for extending
48
- * `SyncChildObservableClass`.
49
- *
50
- * `createTryUpdate` receives the internal tools and returns the `tryUpdate`
51
- * implementation (the former `override tryUpdate`). It runs during
52
- * construction, before the observable is registered to its parents; leaf
53
- * state can live either in the enclosing factory scope or inside the
54
- * `createTryUpdate` closure.
55
- */
56
- export declare const createSyncChildObservable: <A, P extends NonEmptyUnknownList>({ parents, depth, initialValue, onComplete, canEmitNoMore, }: ChildObservableConfig<A, P>, createTryUpdate: (tools: SyncChildObservableTools<A>) => (updateToken: UpdateToken) => void) => SyncChildObservable<A, P>;
57
- /**
58
- * Creates a sync-child observable that always holds a value.
59
- *
60
- * Runtime-wise this is exactly `createSyncChildObservable`; only the types of
61
- * `getSnapshot` and `pipe` are narrowed, which is what the
62
- * `InitializedSyncChildObservableClass` overrides existed for.
63
- */
64
- export declare const createInitializedSyncChildObservable: <A, P extends NonEmptyUnknownList>(config: InitializedChildObservableConfig<A, P>, createTryUpdate: (tools: SyncChildObservableTools<A>) => (updateToken: UpdateToken) => void) => InitializedSyncChildObservable<A, P>;
65
- /**
66
- * Creates an async-child observable — the classless replacement for extending
67
- * `AsyncChildObservableClass`. See {@link createSyncChildObservable} for the
68
- * `createTryUpdate` contract.
69
- */
70
- export declare const createAsyncChildObservable: <A, P extends NonEmptyUnknownList>({ parents, depth, initialValue, onComplete, canEmitNoMore, }: ChildObservableConfig<A, P>, createTryUpdate: (tools: AsyncChildObservableTools<A>) => (updateToken: UpdateToken) => void) => AsyncChildObservable<A, P>;
71
- export {};
72
- //# sourceMappingURL=create-child-observable.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"create-child-observable.d.mts","sourceRoot":"","sources":["../../../src/core/base/create-child-observable.mts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,QAAQ,EAAE,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AAG9D,OAAO,EAGL,KAAK,oBAAoB,EAEzB,KAAK,8BAA8B,EACnC,KAAK,mBAAmB,EAGxB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,IAAI,EACV,MAAM,oBAAoB,CAAC;AAQ5B;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,CAAC,CAAC,IAAI,QAAQ,CAAC;IACjD,sEAAsE;IACtE,OAAO,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,WAAW,EAAE,WAAW,KAAK,IAAI,CAAC;IAE1D,WAAW,EAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC;IAE/B,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI,QAAQ,CAAC;IAClD,sEAAsE;IACtE,WAAW,EAAE,CAAC,SAAS,EAAE,CAAC,KAAK,IAAI,CAAC;CACrC,CAAC,GACA,wBAAwB,CAAC,CAAC,CAAC,CAAC;AAE9B,KAAK,yBAAyB,CAAC,CAAC,SAAS,mBAAmB,IAAI,QAAQ,CAAC;IACvE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAEjB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IAExB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC;CAC/B,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,CAC/B,CAAC,EACD,CAAC,SAAS,mBAAmB,IAC3B,yBAAyB,CAAC,CAAC,CAAC,GAC9B,QAAQ,CAAC;IACP,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;CAC3B,CAAC,CAAC;AAEL,MAAM,MAAM,gCAAgC,CAC1C,CAAC,EACD,CAAC,SAAS,mBAAmB,IAC3B,yBAAyB,CAAC,CAAC,CAAC,GAC9B,QAAQ,CAAC;IACP,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;CACvB,CAAC,CAAC;AAEL;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GAAI,CAAC,EAAE,CAAC,SAAS,mBAAmB,gEAOrE,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC,mBACb,CACf,KAAK,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAC/B,CAAC,WAAW,EAAE,WAAW,KAAK,IAAI,KACtC,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAoF1B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oCAAoC,GAC/C,CAAC,EACD,CAAC,SAAS,mBAAmB,UAErB,gCAAgC,CAAC,CAAC,EAAE,CAAC,CAAC,mBAC7B,CACf,KAAK,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAC/B,CAAC,WAAW,EAAE,WAAW,KAAK,IAAI,KACtC,8BAA8B,CAAC,CAAC,EAAE,CAAC,CASrC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,GAAI,CAAC,EAAE,CAAC,SAAS,mBAAmB,gEAOtE,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC,mBACb,CACf,KAAK,EAAE,yBAAyB,CAAC,CAAC,CAAC,KAChC,CAAC,WAAW,EAAE,WAAW,KAAK,IAAI,KACtC,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAwF3B,CAAC"}