synstate 3.0.5 → 3.0.6

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.
package/README.md CHANGED
@@ -24,7 +24,7 @@
24
24
  - 🎯 **Simple State Management**: Easy-to-use `createState` and `createReducer` similar to React useState/useReducer for global state
25
25
  - ⚡ **High Performance**: Optimized for fast state updates and minimal re-renders
26
26
  - 🎨 **Type-Safe**: Full TypeScript support with precise type inference
27
- - 🚀 **Lightweight**: <!-- bundle-size:synstate -->~4.6 kB min+gzip<!-- /bundle-size:synstate --> with two external runtime dependencies ([ts-data-forge](https://www.npmjs.com/package/ts-data-forge) and [ts-std-forge](https://www.npmjs.com/package/ts-std-forge), and the first depends on the second)
27
+ - 🚀 **Lightweight**: <!-- bundle-size:synstate -->~4.7 kB min+gzip<!-- /bundle-size:synstate --> with two external runtime dependencies ([ts-data-forge](https://www.npmjs.com/package/ts-data-forge) and [ts-std-forge](https://www.npmjs.com/package/ts-std-forge), and the first depends on the second)
28
28
  - 🌐 **Framework Agnostic**: Works with React, Vue, Svelte, or vanilla JavaScript
29
29
  - 🔄 **Reactive Updates**: Automatic propagation of state changes to all subscribers
30
30
  - 📡 **Event System**: Built-in `createValueEmitter`, `createEventEmitter` for event-driven architecture
@@ -15,12 +15,42 @@ import { type ChildObservable, type ObservableBase, type ObservableId, type Obse
15
15
  */
16
16
  export type ObservableBaseHandle<A> = Readonly<{
17
17
  id: ObservableId;
18
+ /**
19
+ * Registers the public object {@link assembleObservable} built from this
20
+ * handle, so that its four state flags (`isCompleted`, `updateToken`,
21
+ * `hasSubscriber`, `hasChild`) can be mirrored onto it as plain data
22
+ * properties.
23
+ *
24
+ * They used to be getters delegating to this handle's closures, and that
25
+ * costs more than it looks. Reading one was an accessor call plus a closure
26
+ * call where a field read would do — `updateToken` is read once per child
27
+ * per update, so the propagation path paid it on every step — and an object
28
+ * literal carrying accessors is far more expensive to create than one
29
+ * carrying data, which is most of what building a graph costs. Measured on
30
+ * one machine with `pnpm run benchmark`: the derived chain's 100,000 updates
31
+ * 16.7 ms -> 13.6 ms, and the cascaded diamond at N=20, whose measurement is
32
+ * dominated by graph construction, 587 ms -> 362 ms.
33
+ *
34
+ * Every write below therefore has to keep the mirror in step; that is the
35
+ * price of not reading through a closure on the hot path. It is paid in
36
+ * clarity too, and on purpose: `hasSubscriber` and `hasChild` are derived
37
+ * from `mut_subscribers` and `mut_children`, which a getter stated once and
38
+ * declaratively, whereas the mirror restates it at every write.
39
+ *
40
+ * Nor can the rarely read flags alone go back to getters while `updateToken`
41
+ * stays data. A single accessor in the literal is enough to lose nearly all
42
+ * of the gain: measured on Node 26 with the `...extra` spread followed by
43
+ * these properties, one getter among data properties made the literal about
44
+ * 10x slower to create than all data (four getters: about 15x) and gave
45
+ * every instance its own hidden class (`%HaveSameMap` false). Keep all four
46
+ * as data, or measure again before changing that.
47
+ */
48
+ attach: (writers: ObservableStateWriters) => void;
18
49
  addChild: <B>(child: ChildObservable<B>) => void;
19
50
  getSnapshot: () => Optional<A>;
20
51
  isCompleted: () => boolean;
21
52
  updateToken: () => UpdateToken;
22
53
  hasSubscriber: () => boolean;
23
- hasChild: () => boolean;
24
54
  hasActiveChild: () => boolean;
25
55
  subscribe: (onNext: (v: A) => void, onComplete?: () => void) => Subscription;
26
56
  /** Stores `nextValue` as the current value and notifies subscribers. */
@@ -33,6 +63,19 @@ export type ObservableBaseHandle<A> = Readonly<{
33
63
  */
34
64
  completeBase: () => void;
35
65
  }>;
66
+ /**
67
+ * How the handle writes the four state flags onto the public object. The
68
+ * writers are closures over that object rather than the object itself, because
69
+ * a parameter this side could mutate has to be a mutable type, which
70
+ * `@typescript-eslint/prefer-readonly-parameter-types` rejects — and the object
71
+ * is `Readonly` to everyone else.
72
+ */
73
+ type ObservableStateWriters = Readonly<{
74
+ setIsCompleted: (value: boolean) => void;
75
+ setUpdateToken: (value: UpdateToken) => void;
76
+ setHasSubscriber: (value: boolean) => void;
77
+ setHasChild: (value: boolean) => void;
78
+ }>;
36
79
  export declare const createObservableBaseHandle: <A>(initialValue: Optional<A>) => ObservableBaseHandle<A>;
37
80
  /**
38
81
  * The extra members every manager observable — a root or an async child — owns:
@@ -63,17 +106,19 @@ type AssembleObservableArgs<A, Kind extends ObservableKind, Extra extends Readon
63
106
  /**
64
107
  * Kind-specific public members (`parents`, `addDescendant`) and leaf
65
108
  * extensions (`next`, `start`). They are merged here, in the one place the
66
- * final object literal is created, because the public getters below must not
67
- * be copied by a spread (spreading would freeze their current values).
109
+ * final object literal is created, because the handle keeps the state flags
110
+ * up to date on that object alone: a copy made by spreading it afterwards
111
+ * would freeze their current values.
68
112
  */
69
113
  extra: Extra;
70
114
  }>;
71
115
  /**
72
116
  * Builds the public observable object from a base handle. This replaces the
73
117
  * implicit assembly a `class` performs: state stays in the handle's closures,
74
- * `isCompleted` / `updateToken` / `hasSubscriber` / `hasChild` become getters
75
- * delegating to the handle, and the kind-specific behavior (`tryUpdate`,
76
- * `tryComplete`, `complete`) is passed in already composed.
118
+ * `isCompleted` / `updateToken` / `hasSubscriber` / `hasChild` become data
119
+ * properties the handle keeps in step through `attach`, and the kind-specific
120
+ * behavior (`tryUpdate`, `tryComplete`, `complete`) is passed in already
121
+ * composed.
77
122
  */
78
123
  export declare const assembleObservable: <A, Kind extends ObservableKind, Extra extends ReadonlyRecord<string, unknown>>({ kind, depth, handle, tryUpdate, tryComplete, complete, extra, }: AssembleObservableArgs<A, Kind, Extra>) => Extra & ObservableBase<A> & Readonly<{
79
124
  kind: Kind;
@@ -1 +1 @@
1
- {"version":3,"file":"create-observable-base.d.mts","sourceRoot":"","sources":["../../../src/core/base/create-observable-base.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create-observable-base.d.mts","sourceRoot":"","sources":["../../../src/core/base/create-observable-base.mts"],"names":[],"mappings":"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"}
@@ -8,26 +8,43 @@ export const createObservableBaseHandle = (initialValue) => {
8
8
  let mut_currentValue = initialValue;
9
9
  let mut_isCompleted = false;
10
10
  let mut_updateToken = issueUpdateToken();
11
+
12
+
13
+
14
+
15
+
16
+
17
+ let mut_writers;
18
+ const attach = (writers) => {
19
+ mut_writers = writers;
20
+ writers.setIsCompleted(mut_isCompleted);
21
+ writers.setUpdateToken(mut_updateToken);
22
+ writers.setHasSubscriber(mut_subscribers.size > 0);
23
+ writers.setHasChild(Arr.isNonEmpty(mut_children));
24
+ };
11
25
  const addSubscriber = (s) => {
12
26
 
13
27
  const subscriberId = issueSubscriberId();
14
28
  mut_subscribers.set(subscriberId, s);
29
+ mut_writers?.setHasSubscriber(true);
15
30
  return subscriberId;
16
31
  };
17
32
  const removeSubscriber = (subscriberId) => {
18
33
  mut_subscribers.delete(subscriberId);
34
+ mut_writers?.setHasSubscriber(mut_subscribers.size > 0);
19
35
  };
20
36
  const addChild = (child) => {
21
37
  mut_children = Arr.toPushed(mut_children, child);
38
+ mut_writers?.setHasChild(true);
22
39
  };
23
40
  const getSnapshot = () => mut_currentValue;
24
41
  const isCompleted = () => mut_isCompleted;
25
42
  const updateToken = () => mut_updateToken;
26
43
  const hasSubscriber = () => mut_subscribers.size > 0;
27
- const hasChild = () => Arr.isNonEmpty(mut_children);
28
44
  const hasActiveChild = () => mut_children.some((c) => !c.isCompleted);
29
45
  const setNext = (nextValue, nextUpdateToken) => {
30
46
  mut_updateToken = nextUpdateToken;
47
+ mut_writers?.setUpdateToken(nextUpdateToken);
31
48
  mut_currentValue = Optional.some(nextValue);
32
49
  for (const s of mut_subscribers.values()) {
33
50
  s.onNext(nextValue);
@@ -39,12 +56,14 @@ export const createObservableBaseHandle = (initialValue) => {
39
56
  }
40
57
 
41
58
  mut_isCompleted = true;
59
+ mut_writers?.setIsCompleted(true);
42
60
 
43
61
  for (const s of mut_subscribers.values()) {
44
62
  s.onComplete();
45
63
  }
46
64
 
47
65
  mut_subscribers.clear();
66
+ mut_writers?.setHasSubscriber(false);
48
67
 
49
68
  for (const o of mut_children) {
50
69
  o.tryComplete();
@@ -71,12 +90,12 @@ export const createObservableBaseHandle = (initialValue) => {
71
90
  };
72
91
  return {
73
92
  id,
93
+ attach,
74
94
  addChild,
75
95
  getSnapshot,
76
96
  isCompleted,
77
97
  updateToken,
78
98
  hasSubscriber,
79
- hasChild,
80
99
  hasActiveChild,
81
100
  subscribe,
82
101
  setNext,
@@ -128,32 +147,30 @@ const tryUpdateNotImplemented = (_updateToken) => {
128
147
 
129
148
 
130
149
 
150
+
131
151
  export const assembleObservable = ({ kind, depth, handle, tryUpdate, tryComplete, complete, extra, }) => {
132
152
  function pipe(operator) {
133
153
  return operator(
134
154
 
135
155
 
136
- observable);
156
+ mut_observable);
137
157
  }
138
- const observable = {
158
+
159
+
160
+
161
+
162
+
163
+ const mut_observable = {
139
164
  ...extra,
140
165
  id: handle.id,
141
166
  kind,
142
167
  depth,
143
168
  addChild: handle.addChild,
144
169
  getSnapshot: handle.getSnapshot,
145
- get isCompleted() {
146
- return handle.isCompleted();
147
- },
148
- get updateToken() {
149
- return handle.updateToken();
150
- },
151
- get hasSubscriber() {
152
- return handle.hasSubscriber();
153
- },
154
- get hasChild() {
155
- return handle.hasChild();
156
- },
170
+ isCompleted: false,
171
+ updateToken: handle.updateToken(),
172
+ hasSubscriber: false,
173
+ hasChild: false,
157
174
  hasActiveChild: handle.hasActiveChild,
158
175
  tryUpdate,
159
176
  tryComplete,
@@ -161,6 +178,20 @@ export const assembleObservable = ({ kind, depth, handle, tryUpdate, tryComplete
161
178
  subscribe: handle.subscribe,
162
179
  pipe,
163
180
  };
164
- return observable;
181
+ handle.attach({
182
+ setIsCompleted: (value) => {
183
+ mut_observable.isCompleted = value;
184
+ },
185
+ setUpdateToken: (value) => {
186
+ mut_observable.updateToken = value;
187
+ },
188
+ setHasSubscriber: (value) => {
189
+ mut_observable.hasSubscriber = value;
190
+ },
191
+ setHasChild: (value) => {
192
+ mut_observable.hasChild = value;
193
+ },
194
+ });
195
+ return mut_observable;
165
196
  };
166
197
  //# sourceMappingURL=create-observable-base.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-observable-base.mjs","sourceRoot":"","sources":["../../../src/core/base/create-observable-base.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create-observable-base.mjs","sourceRoot":"","sources":["../../../src/core/base/create-observable-base.mts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synstate",
3
- "version": "3.0.5",
3
+ "version": "3.0.6",
4
4
  "description": "Type-safe State Management Library for TypeScript/JavaScript",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,7 +39,7 @@
39
39
  "@types/react": "19.3.0",
40
40
  "@vitest/browser-playwright": "5.0.1",
41
41
  "argparse": "3.0.2",
42
- "eslint-config-typed": "5.15.0",
42
+ "eslint-config-typed": "5.15.1",
43
43
  "eslint-plugin-ts-data-forge": "0.8.1",
44
44
  "eslint-plugin-ts-fortress": "1.2.3",
45
45
  "eslint-plugin-ts-type-forge": "0.8.4",
@@ -51,8 +51,8 @@
51
51
  "prettier-plugin-packagejson": "3.0.2",
52
52
  "react": "19.3.0",
53
53
  "rxjs": "7.8.2",
54
- "synstate-react-hooks": "3.0.5",
55
- "synstate-react-hooks-compat": "3.0.5",
54
+ "synstate-react-hooks": "3.0.6",
55
+ "synstate-react-hooks-compat": "3.0.6",
56
56
  "ts-repo-utils": "10.7.1",
57
57
  "valtio": "2.3.2",
58
58
  "zustand": "5.0.15"
@@ -42,6 +42,38 @@ import {
42
42
  export type ObservableBaseHandle<A> = Readonly<{
43
43
  id: ObservableId;
44
44
 
45
+ /**
46
+ * Registers the public object {@link assembleObservable} built from this
47
+ * handle, so that its four state flags (`isCompleted`, `updateToken`,
48
+ * `hasSubscriber`, `hasChild`) can be mirrored onto it as plain data
49
+ * properties.
50
+ *
51
+ * They used to be getters delegating to this handle's closures, and that
52
+ * costs more than it looks. Reading one was an accessor call plus a closure
53
+ * call where a field read would do — `updateToken` is read once per child
54
+ * per update, so the propagation path paid it on every step — and an object
55
+ * literal carrying accessors is far more expensive to create than one
56
+ * carrying data, which is most of what building a graph costs. Measured on
57
+ * one machine with `pnpm run benchmark`: the derived chain's 100,000 updates
58
+ * 16.7 ms -> 13.6 ms, and the cascaded diamond at N=20, whose measurement is
59
+ * dominated by graph construction, 587 ms -> 362 ms.
60
+ *
61
+ * Every write below therefore has to keep the mirror in step; that is the
62
+ * price of not reading through a closure on the hot path. It is paid in
63
+ * clarity too, and on purpose: `hasSubscriber` and `hasChild` are derived
64
+ * from `mut_subscribers` and `mut_children`, which a getter stated once and
65
+ * declaratively, whereas the mirror restates it at every write.
66
+ *
67
+ * Nor can the rarely read flags alone go back to getters while `updateToken`
68
+ * stays data. A single accessor in the literal is enough to lose nearly all
69
+ * of the gain: measured on Node 26 with the `...extra` spread followed by
70
+ * these properties, one getter among data properties made the literal about
71
+ * 10x slower to create than all data (four getters: about 15x) and gave
72
+ * every instance its own hidden class (`%HaveSameMap` false). Keep all four
73
+ * as data, or measure again before changing that.
74
+ */
75
+ attach: (writers: ObservableStateWriters) => void;
76
+
45
77
  addChild: <B>(child: ChildObservable<B>) => void;
46
78
 
47
79
  getSnapshot: () => Optional<A>;
@@ -52,8 +84,6 @@ export type ObservableBaseHandle<A> = Readonly<{
52
84
 
53
85
  hasSubscriber: () => boolean;
54
86
 
55
- hasChild: () => boolean;
56
-
57
87
  hasActiveChild: () => boolean;
58
88
 
59
89
  subscribe: (onNext: (v: A) => void, onComplete?: () => void) => Subscription;
@@ -70,6 +100,20 @@ export type ObservableBaseHandle<A> = Readonly<{
70
100
  completeBase: () => void;
71
101
  }>;
72
102
 
103
+ /**
104
+ * How the handle writes the four state flags onto the public object. The
105
+ * writers are closures over that object rather than the object itself, because
106
+ * a parameter this side could mutate has to be a mutable type, which
107
+ * `@typescript-eslint/prefer-readonly-parameter-types` rejects — and the object
108
+ * is `Readonly` to everyone else.
109
+ */
110
+ type ObservableStateWriters = Readonly<{
111
+ setIsCompleted: (value: boolean) => void;
112
+ setUpdateToken: (value: UpdateToken) => void;
113
+ setHasSubscriber: (value: boolean) => void;
114
+ setHasChild: (value: boolean) => void;
115
+ }>;
116
+
73
117
  export const createObservableBaseHandle = <A,>(
74
118
  initialValue: Optional<A>,
75
119
  ): ObservableBaseHandle<A> => {
@@ -85,17 +129,41 @@ export const createObservableBaseHandle = <A,>(
85
129
 
86
130
  let mut_updateToken: UpdateToken = issueUpdateToken();
87
131
 
132
+ /**
133
+ * Set once {@link assembleObservable} has built the public object. It does
134
+ * not exist while a leaf factory's `init` callback runs — that callback may
135
+ * already subscribe or complete — so every write below is optional, and
136
+ * `attach` copies the state as it stands rather than assuming defaults.
137
+ */
138
+ let mut_writers: ObservableStateWriters | undefined;
139
+
140
+ const attach = (writers: ObservableStateWriters): void => {
141
+ mut_writers = writers;
142
+
143
+ writers.setIsCompleted(mut_isCompleted);
144
+
145
+ writers.setUpdateToken(mut_updateToken);
146
+
147
+ writers.setHasSubscriber(mut_subscribers.size > 0);
148
+
149
+ writers.setHasChild(Arr.isNonEmpty(mut_children));
150
+ };
151
+
88
152
  const addSubscriber = (s: Subscriber<A>): SubscriberId => {
89
153
  // return the id of added subscriber
90
154
  const subscriberId = issueSubscriberId();
91
155
 
92
156
  mut_subscribers.set(subscriberId, s);
93
157
 
158
+ mut_writers?.setHasSubscriber(true);
159
+
94
160
  return subscriberId;
95
161
  };
96
162
 
97
163
  const removeSubscriber = (subscriberId: SubscriberId): void => {
98
164
  mut_subscribers.delete(subscriberId);
165
+
166
+ mut_writers?.setHasSubscriber(mut_subscribers.size > 0);
99
167
  };
100
168
 
101
169
  const addChild = <B,>(child: ChildObservable<B>): void => {
@@ -104,6 +172,8 @@ export const createObservableBaseHandle = <A,>(
104
172
 
105
173
  child as ChildObservable<unknown>,
106
174
  );
175
+
176
+ mut_writers?.setHasChild(true);
107
177
  };
108
178
 
109
179
  const getSnapshot = (): Optional<A> => mut_currentValue;
@@ -114,14 +184,14 @@ export const createObservableBaseHandle = <A,>(
114
184
 
115
185
  const hasSubscriber = (): boolean => mut_subscribers.size > 0;
116
186
 
117
- const hasChild = (): boolean => Arr.isNonEmpty(mut_children);
118
-
119
187
  const hasActiveChild = (): boolean =>
120
188
  mut_children.some((c) => !c.isCompleted);
121
189
 
122
190
  const setNext = (nextValue: A, nextUpdateToken: UpdateToken): void => {
123
191
  mut_updateToken = nextUpdateToken;
124
192
 
193
+ mut_writers?.setUpdateToken(nextUpdateToken);
194
+
125
195
  mut_currentValue = Optional.some(nextValue);
126
196
 
127
197
  for (const s of mut_subscribers.values()) {
@@ -137,6 +207,8 @@ export const createObservableBaseHandle = <A,>(
137
207
  // change state
138
208
  mut_isCompleted = true;
139
209
 
210
+ mut_writers?.setIsCompleted(true);
211
+
140
212
  // run subscribers for the current value
141
213
  for (const s of mut_subscribers.values()) {
142
214
  s.onComplete();
@@ -145,6 +217,8 @@ export const createObservableBaseHandle = <A,>(
145
217
  // remove all subscribers
146
218
  mut_subscribers.clear();
147
219
 
220
+ mut_writers?.setHasSubscriber(false);
221
+
148
222
  // propagate to children
149
223
  for (const o of mut_children) {
150
224
  o.tryComplete();
@@ -183,12 +257,12 @@ export const createObservableBaseHandle = <A,>(
183
257
 
184
258
  return {
185
259
  id,
260
+ attach,
186
261
  addChild,
187
262
  getSnapshot,
188
263
  isCompleted,
189
264
  updateToken,
190
265
  hasSubscriber,
191
- hasChild,
192
266
  hasActiveChild,
193
267
  subscribe,
194
268
  setNext,
@@ -282,8 +356,9 @@ type AssembleObservableArgs<
282
356
  /**
283
357
  * Kind-specific public members (`parents`, `addDescendant`) and leaf
284
358
  * extensions (`next`, `start`). They are merged here, in the one place the
285
- * final object literal is created, because the public getters below must not
286
- * be copied by a spread (spreading would freeze their current values).
359
+ * final object literal is created, because the handle keeps the state flags
360
+ * up to date on that object alone: a copy made by spreading it afterwards
361
+ * would freeze their current values.
287
362
  */
288
363
  extra: Extra;
289
364
  }>;
@@ -291,9 +366,10 @@ type AssembleObservableArgs<
291
366
  /**
292
367
  * Builds the public observable object from a base handle. This replaces the
293
368
  * implicit assembly a `class` performs: state stays in the handle's closures,
294
- * `isCompleted` / `updateToken` / `hasSubscriber` / `hasChild` become getters
295
- * delegating to the handle, and the kind-specific behavior (`tryUpdate`,
296
- * `tryComplete`, `complete`) is passed in already composed.
369
+ * `isCompleted` / `updateToken` / `hasSubscriber` / `hasChild` become data
370
+ * properties the handle keeps in step through `attach`, and the kind-specific
371
+ * behavior (`tryUpdate`, `tryComplete`, `complete`) is passed in already
372
+ * composed.
297
373
  */
298
374
  export const assembleObservable = <
299
375
  A,
@@ -323,11 +399,16 @@ export const assembleObservable = <
323
399
  return operator(
324
400
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
325
401
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
326
- observable as unknown as InitializedObservable<A>,
402
+ mut_observable as unknown as InitializedObservable<A>,
327
403
  );
328
404
  }
329
405
 
330
- const observable = {
406
+ // The four state flags are plain data properties kept in step by the handle,
407
+ // not getters delegating to it — see `ObservableBaseHandle.attach` for the
408
+ // measurements that decided this. `updateToken` in particular is read once
409
+ // per child per update, and `tryUpdate` reading a field rather than calling
410
+ // through an accessor is most of what the change buys.
411
+ const mut_observable = {
331
412
  ...extra,
332
413
 
333
414
  id: handle.id,
@@ -340,21 +421,13 @@ export const assembleObservable = <
340
421
 
341
422
  getSnapshot: handle.getSnapshot,
342
423
 
343
- get isCompleted(): boolean {
344
- return handle.isCompleted();
345
- },
424
+ isCompleted: false,
346
425
 
347
- get updateToken(): UpdateToken {
348
- return handle.updateToken();
349
- },
426
+ updateToken: handle.updateToken(),
350
427
 
351
- get hasSubscriber(): boolean {
352
- return handle.hasSubscriber();
353
- },
428
+ hasSubscriber: false,
354
429
 
355
- get hasChild(): boolean {
356
- return handle.hasChild();
357
- },
430
+ hasChild: false,
358
431
 
359
432
  hasActiveChild: handle.hasActiveChild,
360
433
 
@@ -367,7 +440,25 @@ export const assembleObservable = <
367
440
  subscribe: handle.subscribe,
368
441
 
369
442
  pipe,
370
- } as const;
443
+ };
444
+
445
+ handle.attach({
446
+ setIsCompleted: (value) => {
447
+ mut_observable.isCompleted = value;
448
+ },
449
+
450
+ setUpdateToken: (value) => {
451
+ mut_observable.updateToken = value;
452
+ },
453
+
454
+ setHasSubscriber: (value) => {
455
+ mut_observable.hasSubscriber = value;
456
+ },
457
+
458
+ setHasChild: (value) => {
459
+ mut_observable.hasChild = value;
460
+ },
461
+ });
371
462
 
372
- return observable;
463
+ return mut_observable;
373
464
  };