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
@@ -1,8 +1,16 @@
1
+ // Sumi bans classes, `this`, method syntax and writes to `this` outside a
2
+ // `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
3
+ // so those rules are waived here (D-63).
4
+ // @sumi-expect-error-file classes/no-class
5
+ // @sumi-expect-error-file banned-syntax/no-this
6
+ // @sumi-expect-error-file functions/prefer-arrow-function
7
+ // @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
1
8
  import { Optional, pipe } from 'ts-data-forge';
2
- import { createSyncChildObservable } from '../base/index.mjs';
9
+ import { SyncChildObservableClass } from '../class/index.mjs';
3
10
  import type {
4
11
  DropInitialValueOperator,
5
12
  Observable,
13
+ UpdateToken,
6
14
  WithCurrentValueFromOperatorObservable,
7
15
  } from '../types/index.mjs';
8
16
  import { maxDepth } from '../utils/index.mjs';
@@ -68,7 +76,7 @@ export const withCurrentValueFrom =
68
76
  observable: Observable<B>,
69
77
  ): DropInitialValueOperator<A, readonly [A, B]> =>
70
78
  (parentObservable) =>
71
- createWithCurrentValueFromObservable(parentObservable, observable);
79
+ new WithCurrentValueFromObservableClass(parentObservable, observable);
72
80
 
73
81
  /**
74
82
  * Alias for `withCurrentValueFrom`.
@@ -76,12 +84,14 @@ export const withCurrentValueFrom =
76
84
  */
77
85
  export const withLatestFrom = withCurrentValueFrom;
78
86
 
79
- const createWithCurrentValueFromObservable = <A, B>(
80
- parentObservable: Observable<A>,
81
- observable: Observable<B>,
82
- ): WithCurrentValueFromOperatorObservable<A, B> =>
83
- createSyncChildObservable<readonly [A, B], readonly [A]>(
84
- {
87
+ class WithCurrentValueFromObservableClass<A, B>
88
+ extends SyncChildObservableClass<readonly [A, B], readonly [A]>
89
+ implements WithCurrentValueFromOperatorObservable<A, B>
90
+ {
91
+ readonly #observable: Observable<B>;
92
+
93
+ constructor(parentObservable: Observable<A>, observable: Observable<B>) {
94
+ super({
85
95
  parents: [parentObservable],
86
96
  depth: 1 + maxDepth([parentObservable, observable]),
87
97
  initialValue: pipe({
@@ -92,24 +102,28 @@ const createWithCurrentValueFromObservable = <A, B>(
92
102
  ? Optional.none
93
103
  : Optional.some([par.value, me.value] as const),
94
104
  ).value,
95
- },
96
- ({ setNext }) =>
97
- (updateToken) => {
98
- const ps = parentObservable.getSnapshot();
105
+ });
106
+
107
+ this.#observable = observable;
108
+
109
+ this.attach();
110
+ }
111
+
112
+ override tryUpdate(updateToken: UpdateToken): void {
113
+ const par = this.parents[0];
114
+
115
+ const ps = par.getSnapshot();
99
116
 
100
- if (
101
- parentObservable.updateToken !== updateToken ||
102
- Optional.isNone(ps)
103
- ) {
104
- return; // skip update
105
- }
117
+ if (par.updateToken !== updateToken || Optional.isNone(ps)) {
118
+ return; // skip update
119
+ }
106
120
 
107
- const curr = observable.getSnapshot();
121
+ const curr = this.#observable.getSnapshot();
108
122
 
109
- if (Optional.isNone(curr)) {
110
- return; // skip update
111
- }
123
+ if (Optional.isNone(curr)) {
124
+ return; // skip update
125
+ }
112
126
 
113
- setNext([ps.value, curr.value], updateToken);
114
- },
115
- );
127
+ this.setNext([ps.value, curr.value], updateToken);
128
+ }
129
+ }
@@ -1,9 +1,16 @@
1
+ // Sumi bans classes, `this`, method syntax and writes to `this` outside a
2
+ // `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
3
+ // so those rules are waived here (D-63).
4
+ // @sumi-expect-error-file classes/no-class
5
+ // @sumi-expect-error-file banned-syntax/no-this
6
+ // @sumi-expect-error-file functions/prefer-arrow-function
1
7
  import { expectType, Optional } from 'ts-data-forge';
2
- import { createInitializedSyncChildObservable } from '../base/index.mjs';
8
+ import { InitializedSyncChildObservableClass } from '../class/index.mjs';
3
9
  import { source } from '../create/index.mjs';
4
10
  import type {
5
11
  InitializedObservable,
6
12
  Observable,
13
+ UpdateToken,
7
14
  WithInitialValueOperator,
8
15
  WithInitialValueOperatorObservable,
9
16
  } from '../types/index.mjs';
@@ -56,31 +63,33 @@ import type {
56
63
  export const withInitialValue =
57
64
  <A, I = A>(initialValue: I): WithInitialValueOperator<A, A | I> =>
58
65
  (parentObservable) =>
59
- createWithInitialValueObservable(parentObservable, initialValue);
66
+ new WithInitialValueObservableClass(parentObservable, initialValue);
60
67
 
61
- const createWithInitialValueObservable = <A, I>(
62
- parentObservable: Observable<A>,
63
- initialValue: I,
64
- ): WithInitialValueOperatorObservable<A, I> =>
65
- createInitializedSyncChildObservable<A | I, readonly [A]>(
66
- {
68
+ class WithInitialValueObservableClass<A, I>
69
+ extends InitializedSyncChildObservableClass<A | I, readonly [A]>
70
+ implements WithInitialValueOperatorObservable<A, I>
71
+ {
72
+ constructor(parentObservable: Observable<A>, initialValue: I) {
73
+ super({
67
74
  parents: [parentObservable],
68
75
  initialValue: Optional.some(initialValue),
69
- },
70
- ({ setNext }) =>
71
- (updateToken) => {
72
- const sn = parentObservable.getSnapshot();
76
+ });
73
77
 
74
- if (
75
- parentObservable.updateToken !== updateToken ||
76
- Optional.isNone(sn)
77
- ) {
78
- return; // skip update
79
- }
78
+ this.attach();
79
+ }
80
+
81
+ override tryUpdate(updateToken: UpdateToken): void {
82
+ const par = this.parents[0];
83
+
84
+ const sn = par.getSnapshot();
80
85
 
81
- setNext(sn.value, updateToken);
82
- },
83
- );
86
+ if (par.updateToken !== updateToken || Optional.isNone(sn)) {
87
+ return; // skip update
88
+ }
89
+
90
+ this.setNext(sn.value, updateToken);
91
+ }
92
+ }
84
93
 
85
94
  if (import.meta.vitest !== undefined) {
86
95
  test('type test', () => {
@@ -5,7 +5,6 @@ export * from './map-result-ok.mjs';
5
5
  export * from './map-to.mjs';
6
6
  export * from './pluck.mjs';
7
7
  export * from './skip.mjs';
8
- export * from './take.mjs';
9
8
  export * from './unwrap-optional.mjs';
10
9
  export * from './unwrap-result-err.mjs';
11
10
  export * from './unwrap-result-ok.mjs';
@@ -0,0 +1 @@
1
+ export * from './scope.mjs';
@@ -0,0 +1,260 @@
1
+ // Sumi bans classes, `this`, method syntax and writes to `this` outside a
2
+ // `mut_` binding. A scope is a class for the reason the observables are (see
3
+ // `ScopeClass` below), so those four rules are waived here (D-63).
4
+ // @sumi-expect-error-file classes/no-class
5
+ // @sumi-expect-error-file banned-syntax/no-this
6
+ // @sumi-expect-error-file functions/prefer-arrow-function
7
+ // @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
8
+ import type { Scope } from '../types/index.mjs';
9
+ import {
10
+ getCurrentScopeInternals,
11
+ runWithScope,
12
+ type ScopeInternals,
13
+ type ScopeMember,
14
+ } from '../utils/index.mjs';
15
+
16
+ /**
17
+ * Creates a {@link Scope}. Observables created synchronously inside
18
+ * `scope.run(body)` belong to it, and `scope.dispose()` disposes them without
19
+ * completing the shared observables they were derived from.
20
+ *
21
+ * With `parent`, the new scope is disposed together with it; without one it
22
+ * stands alone and lives until its own `dispose()`. A parent that is already
23
+ * disposed gives a scope that is born disposed, with a warning.
24
+ *
25
+ * Intended for authors of libraries built on synstate rather than for
26
+ * application code; state defined at module level belongs to no scope and
27
+ * lives as long as the page.
28
+ *
29
+ * `run` and `dispose` read the scope they are called on, so call them on it
30
+ * (`scope.dispose()`) rather than taking them off it.
31
+ *
32
+ * @param parent - The scope whose disposal also disposes the new one
33
+ * @returns The new scope
34
+ */
35
+ export const createScope = (parent?: Scope): Scope =>
36
+ new ScopeClass(parent instanceof ScopeClass ? parent : undefined);
37
+
38
+ /**
39
+ * Returns the scope whose `run` is executing, or `undefined` outside every
40
+ * `run` and inside {@link untracked}.
41
+ *
42
+ * To put work that runs later (after an `await`, in a callback) into the
43
+ * current scope, take it here while still synchronous and wrap the later work
44
+ * in its `run`.
45
+ *
46
+ * @returns The current scope, if any
47
+ */
48
+ export const getCurrentScope = (): Scope | undefined =>
49
+ getCurrentScopeInternals();
50
+
51
+ /**
52
+ * Registers `cleanup` to be called when the current scope is disposed. Cleanups
53
+ * run after the child scopes are disposed and before the owned observables
54
+ * are, newest first.
55
+ *
56
+ * Outside every scope it warns and registers nothing.
57
+ *
58
+ * @param cleanup - The function to call
59
+ */
60
+ export const onScopeDispose = (cleanup: () => void): void => {
61
+ const current = getCurrentScopeInternals();
62
+
63
+ if (current === undefined) {
64
+ console.warn(
65
+ 'synstate: onScopeDispose() was called outside any scope; the cleanup will never run.',
66
+ );
67
+
68
+ return;
69
+ }
70
+
71
+ current.addCleanup(cleanup);
72
+ };
73
+
74
+ /**
75
+ * Calls `body` with no current scope and returns what it returns, so that the
76
+ * observables it creates belong to no scope and outlive the surrounding one.
77
+ *
78
+ * @param body - The function to call
79
+ * @returns What `body` returns
80
+ */
81
+ export const untracked = <R,>(body: () => R): R =>
82
+ runWithScope(undefined, body);
83
+
84
+ /**
85
+ * A scope is created for every row and branch of a UI, so creating one has to
86
+ * be cheap. It used to be a set of closures with a `Set` of children, a `Set`
87
+ * of owned observables and a record in a `WeakMap`; it is now one object whose
88
+ * methods live on the prototype and whose lists are arrays made when the
89
+ * first entry arrives.
90
+ *
91
+ * Whatever leaves a list on its own (a child scope disposed by itself, an
92
+ * observable that completed) is not looked up and spliced out: it only counts
93
+ * itself gone, and once at least half of a list is gone the list is rebuilt
94
+ * from what is left. Disposal walks the lists newest first, as before.
95
+ *
96
+ * The class itself is not exported; `createScope` is the way to make one.
97
+ */
98
+ class ScopeClass implements ScopeInternals {
99
+ readonly #parent: ScopeClass | undefined;
100
+ // Pushed to in place: a scope gains children, cleanups and nodes one at a
101
+ // time, and copying the list for each would make a big scope quadratic.
102
+ // @sumi-expect-error readonly/require-readonly-type
103
+ #mut_children: ScopeClass[] | undefined;
104
+ #mut_goneChildren: number;
105
+ // @sumi-expect-error readonly/require-readonly-type
106
+ #mut_cleanups: (() => void)[] | undefined;
107
+ // @sumi-expect-error readonly/require-readonly-type
108
+ #mut_nodes: ScopeMember[] | undefined;
109
+ #mut_goneNodes: number;
110
+ #mut_isDisposed: boolean;
111
+
112
+ constructor(parent: ScopeClass | undefined) {
113
+ this.#parent = parent;
114
+
115
+ this.#mut_children = undefined;
116
+
117
+ this.#mut_goneChildren = 0;
118
+
119
+ this.#mut_cleanups = undefined;
120
+
121
+ this.#mut_nodes = undefined;
122
+
123
+ this.#mut_goneNodes = 0;
124
+
125
+ this.#mut_isDisposed = false;
126
+
127
+ if (parent !== undefined) {
128
+ if (parent.#mut_isDisposed) {
129
+ console.warn(
130
+ 'synstate: createScope() was given a disposed parent; the new scope is born disposed.',
131
+ );
132
+
133
+ this.#mut_isDisposed = true;
134
+ } else if (parent.#mut_children === undefined) {
135
+ parent.#mut_children = [this];
136
+ } else {
137
+ parent.#mut_children.push(this);
138
+ }
139
+ }
140
+ }
141
+
142
+ /** Called by a child scope that was disposed on its own. */
143
+ #childGone(): void {
144
+ const children = this.#mut_children;
145
+
146
+ if (children === undefined) {
147
+ return;
148
+ }
149
+
150
+ this.#mut_goneChildren += 1;
151
+
152
+ if (!isMostlyGone(this.#mut_goneChildren, children.length)) {
153
+ return;
154
+ }
155
+
156
+ this.#mut_children = children.filter((c) => !c.#mut_isDisposed);
157
+
158
+ this.#mut_goneChildren = 0;
159
+ }
160
+
161
+ run<R>(body: () => R): R {
162
+ if (this.#mut_isDisposed) {
163
+ console.warn(
164
+ 'synstate: run() was called on a disposed scope; what it creates belongs to no scope.',
165
+ );
166
+
167
+ return runWithScope(undefined, body);
168
+ }
169
+
170
+ return runWithScope(this, body);
171
+ }
172
+
173
+ dispose(): void {
174
+ if (this.#mut_isDisposed) {
175
+ return;
176
+ }
177
+
178
+ // Set first: the children, cleanups and nodes below call back into this
179
+ // scope (`childGone`, `release`), which has nothing to do once it is
180
+ // disposing everything anyway.
181
+ this.#mut_isDisposed = true;
182
+
183
+ const children = this.#mut_children;
184
+
185
+ if (children !== undefined) {
186
+ this.#mut_children = undefined;
187
+
188
+ for (let mut_i = children.length - 1; mut_i >= 0; mut_i -= 1) {
189
+ children[mut_i]?.dispose();
190
+ }
191
+ }
192
+
193
+ const cleanups = this.#mut_cleanups;
194
+
195
+ if (cleanups !== undefined) {
196
+ this.#mut_cleanups = undefined;
197
+
198
+ for (let mut_i = cleanups.length - 1; mut_i >= 0; mut_i -= 1) {
199
+ cleanups[mut_i]?.();
200
+ }
201
+ }
202
+
203
+ const nodes = this.#mut_nodes;
204
+
205
+ if (nodes !== undefined) {
206
+ this.#mut_nodes = undefined;
207
+
208
+ for (let mut_i = nodes.length - 1; mut_i >= 0; mut_i -= 1) {
209
+ nodes[mut_i]?.dispose();
210
+ }
211
+ }
212
+
213
+ if (this.#parent !== undefined) {
214
+ this.#parent.#childGone();
215
+ }
216
+ }
217
+
218
+ adopt(node: ScopeMember): void {
219
+ if (this.#mut_nodes === undefined) {
220
+ this.#mut_nodes = [node];
221
+ } else {
222
+ this.#mut_nodes.push(node);
223
+ }
224
+ }
225
+
226
+ release(): void {
227
+ const nodes = this.#mut_nodes;
228
+
229
+ if (nodes === undefined) {
230
+ return;
231
+ }
232
+
233
+ this.#mut_goneNodes += 1;
234
+
235
+ if (!isMostlyGone(this.#mut_goneNodes, nodes.length)) {
236
+ return;
237
+ }
238
+
239
+ this.#mut_nodes = nodes.filter((n) => !n.isCompleted);
240
+
241
+ this.#mut_goneNodes = 0;
242
+ }
243
+
244
+ addCleanup(cleanup: () => void): void {
245
+ if (this.#mut_cleanups === undefined) {
246
+ this.#mut_cleanups = [cleanup];
247
+ } else {
248
+ this.#mut_cleanups.push(cleanup);
249
+ }
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Whether a list is worth rebuilding: at least half of it is gone, and enough
255
+ * of it that the rebuild is paid for by the entries it drops.
256
+ */
257
+ const isMostlyGone = (gone: number, length: number): boolean =>
258
+ gone >= minGoneToCompact && gone * 2 >= length;
259
+
260
+ const minGoneToCompact = 16;