synstate 3.2.1 → 3.4.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.
- package/README.md +2 -2
- package/dist/core/base/create-child-observable.d.mts +9 -2
- package/dist/core/base/create-child-observable.d.mts.map +1 -1
- package/dist/core/base/create-child-observable.mjs +49 -11
- package/dist/core/base/create-child-observable.mjs.map +1 -1
- package/dist/core/base/create-observable-base.d.mts +5 -1
- package/dist/core/base/create-observable-base.d.mts.map +1 -1
- package/dist/core/base/create-observable-base.mjs +24 -4
- package/dist/core/base/create-observable-base.mjs.map +1 -1
- package/dist/core/base/create-root-observable.d.mts +9 -0
- package/dist/core/base/create-root-observable.d.mts.map +1 -1
- package/dist/core/base/create-root-observable.mjs +32 -3
- package/dist/core/base/create-root-observable.mjs.map +1 -1
- package/dist/core/combine/merge.d.mts +0 -3
- package/dist/core/combine/merge.d.mts.map +1 -1
- package/dist/core/combine/merge.mjs +0 -3
- package/dist/core/combine/merge.mjs.map +1 -1
- package/dist/core/combine/zip.d.mts +4 -2
- package/dist/core/combine/zip.d.mts.map +1 -1
- package/dist/core/combine/zip.mjs +21 -3
- package/dist/core/combine/zip.mjs.map +1 -1
- package/dist/core/create/counter.d.mts +9 -3
- package/dist/core/create/counter.d.mts.map +1 -1
- package/dist/core/create/counter.mjs +16 -3
- package/dist/core/create/counter.mjs.map +1 -1
- package/dist/core/create/from-subscribable.d.mts.map +1 -1
- package/dist/core/create/from-subscribable.mjs +34 -11
- package/dist/core/create/from-subscribable.mjs.map +1 -1
- package/dist/core/create/source.d.mts +7 -0
- package/dist/core/create/source.d.mts.map +1 -1
- package/dist/core/create/source.mjs +6 -2
- package/dist/core/create/source.mjs.map +1 -1
- package/dist/core/operators/merge-map.d.mts +0 -3
- package/dist/core/operators/merge-map.d.mts.map +1 -1
- package/dist/core/operators/merge-map.mjs +0 -3
- package/dist/core/operators/merge-map.mjs.map +1 -1
- package/dist/core/operators/skip-until.d.mts.map +1 -1
- package/dist/core/operators/skip-until.mjs +15 -6
- package/dist/core/operators/skip-until.mjs.map +1 -1
- package/dist/core/operators/switch-map.d.mts +0 -3
- package/dist/core/operators/switch-map.d.mts.map +1 -1
- package/dist/core/operators/switch-map.mjs +0 -3
- package/dist/core/operators/switch-map.mjs.map +1 -1
- package/dist/core/operators/take-until.d.mts.map +1 -1
- package/dist/core/operators/take-until.mjs +13 -1
- package/dist/core/operators/take-until.mjs.map +1 -1
- package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
- package/dist/core/operators/with-buffered-from.mjs +12 -3
- package/dist/core/operators/with-buffered-from.mjs.map +1 -1
- package/dist/core/types/observable-family.d.mts +5 -1
- package/dist/core/types/observable-family.d.mts.map +1 -1
- package/dist/core/types/observable-family.mjs.map +1 -1
- package/dist/core/types/observable.d.mts +17 -0
- package/dist/core/types/observable.d.mts.map +1 -1
- package/dist/core/types/observable.mjs.map +1 -1
- package/dist/utils/create-event-emitter.d.mts +2 -2
- package/dist/utils/create-reducer.d.mts +10 -1
- package/dist/utils/create-reducer.d.mts.map +1 -1
- package/dist/utils/create-reducer.mjs +13 -5
- package/dist/utils/create-reducer.mjs.map +1 -1
- package/dist/utils/create-state.d.mts +7 -0
- package/dist/utils/create-state.d.mts.map +1 -1
- package/dist/utils/create-state.mjs +7 -0
- package/dist/utils/create-state.mjs.map +1 -1
- package/package.json +4 -4
- package/src/core/base/create-child-observable.mts +68 -10
- package/src/core/base/create-observable-base.mts +31 -3
- package/src/core/base/create-root-observable.mts +43 -2
- package/src/core/base/write-during-propagation.test.mts +234 -0
- package/src/core/combine/merge.mts +0 -3
- package/src/core/combine/zip.mts +36 -5
- package/src/core/create/counter.mts +27 -6
- package/src/core/create/counter.test.mts +82 -0
- package/src/core/create/from-subscribable.mts +33 -5
- package/src/core/create/from-subscribable.test.mts +129 -0
- package/src/core/create/source.mts +18 -3
- package/src/core/create/source.test.mts +41 -0
- package/src/core/operators/merge-map.mts +0 -3
- package/src/core/operators/skip-until.mts +21 -9
- package/src/core/operators/switch-map.mts +0 -3
- package/src/core/operators/take-until-skip-until-teardown.test.mts +127 -0
- package/src/core/operators/take-until.mts +15 -1
- package/src/core/operators/with-buffered-from.mts +15 -3
- package/src/core/operators/with-buffered-from.test.mts +56 -0
- package/src/core/types/observable-family.mts +5 -1
- package/src/core/types/observable.mts +19 -0
- package/src/utils/create-event-emitter.mts +2 -2
- package/src/utils/create-reducer.mts +14 -8
- package/src/utils/create-reducer.test.mts +12 -0
- package/src/utils/create-state.mts +7 -0
- package/src/utils/create-state.test.mts +20 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-reducer.d.mts","sourceRoot":"","sources":["../../src/utils/create-reducer.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAU,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAEvE
|
|
1
|
+
{"version":3,"file":"create-reducer.d.mts","sourceRoot":"","sources":["../../src/utils/create-reducer.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAU,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,CAAC,WACvB,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,gBACR,CAAC,YACL,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KACjC,SAAS,CACV,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,EAC1B,KAAK,EAAE,QAAQ,CAAC;IACd,WAAW,EAAE,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC;CACjB,CAAC,CAoBH,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,QAAQ,CAAC;IACrC;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;CAC3C,CAAC,CAAC"}
|
|
@@ -56,6 +56,15 @@ import { source } from '../core/index.mjs';
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
59
68
|
|
|
60
69
|
|
|
61
70
|
|
|
@@ -72,11 +81,10 @@ export const createReducer = (reducer, initialState, options) => {
|
|
|
72
81
|
const dispatch = (action) => {
|
|
73
82
|
const currentState = state.getSnapshot().value;
|
|
74
83
|
const nextState = reducer(currentState, action);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return nextState;
|
|
84
|
+
|
|
85
|
+
return equals?.(currentState, nextState) === true
|
|
86
|
+
? currentState
|
|
87
|
+
: state.next(nextState);
|
|
80
88
|
};
|
|
81
89
|
const getSnapshot = () => state.getSnapshot().value;
|
|
82
90
|
return [state, dispatch, { getSnapshot, initialState }];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-reducer.mjs","sourceRoot":"","sources":["../../src/utils/create-reducer.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAA8B,MAAM,mBAAmB,CAAC;AAEvE
|
|
1
|
+
{"version":3,"file":"create-reducer.mjs","sourceRoot":"","sources":["../../src/utils/create-reducer.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAA8B,MAAM,mBAAmB,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,OAAsB,EACtB,YAAe,EACf,OAAkC,EAQlC,EAAE;IACF,MAAM,KAAK,GAAG,MAAM,CAAI,YAAY,CAAC,CAAC;IAEtC,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC;IAE/B,MAAM,QAAQ,GAAG,CAAC,MAAS,EAAK,EAAE;QAChC,MAAM,YAAY,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC;QAE/C,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QAEhD,8DAA8D;QAC9D,OAAO,MAAM,EAAE,CAAC,YAAY,EAAE,SAAS,CAAC,KAAK,IAAI;YAC/C,CAAC,CAAC,YAAY;YACd,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,GAAM,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC;IAEvD,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,CAAU,CAAC;AACnE,CAAC,CAAC"}
|
|
@@ -66,6 +66,13 @@ import { type StateOptions } from './create-reducer.mjs';
|
|
|
66
66
|
* 'label:bug page:2',
|
|
67
67
|
* ]);
|
|
68
68
|
* ```
|
|
69
|
+
*
|
|
70
|
+
* @note Do not call `setState`, `updateState` or `resetState` from a subscriber or a projection while another
|
|
71
|
+
* update is propagating: the write propagates immediately, so subscribers can
|
|
72
|
+
* see an intermediate state, and it logs a `console.warn` (once per
|
|
73
|
+
* observable). Derive the value from its source instead —
|
|
74
|
+
* `const b$ = a$.pipe(map((a) => a * 2))` rather than
|
|
75
|
+
* `a$.subscribe((a) => { setB(a * 2); })`.
|
|
69
76
|
*/
|
|
70
77
|
export declare const createState: <S>(initialState: S, options?: StateOptions<NoInfer<S>>) => readonly [state: InitializedObservable<S>, setState: (v: S) => S, utils: Readonly<{
|
|
71
78
|
updateState: (updateFn: (prev: S) => S) => S;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-state.d.mts","sourceRoot":"","sources":["../../src/utils/create-state.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAuBxE
|
|
1
|
+
{"version":3,"file":"create-state.d.mts","sourceRoot":"","sources":["../../src/utils/create-state.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAuBxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyEG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,gBACb,CAAC,YACL,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KACjC,SAAS,CACV,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAC/B,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EACrB,KAAK,EAAE,QAAQ,CAAC;IACd,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC;CACjB,CAAC,CA0BH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-state.mjs","sourceRoot":"","sources":["../../src/utils/create-state.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAqB,MAAM,sBAAsB,CAAC;AAaxE,MAAM,OAAO,GAAG,CAAK,KAAQ,EAAE,MAAiB,EAAK,EAAE;IACrD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,SAAS,CAAC;QAE1B,KAAK,QAAQ;YACX,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;AACH,CAAC,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"create-state.mjs","sourceRoot":"","sources":["../../src/utils/create-state.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAqB,MAAM,sBAAsB,CAAC;AAaxE,MAAM,OAAO,GAAG,CAAK,KAAQ,EAAE,MAAiB,EAAK,EAAE;IACrD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,SAAS,CAAC;QAE1B,KAAK,QAAQ;YACX,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,YAAe,EACf,OAAkC,EAUlC,EAAE;IACF,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,CAAC,GAAG,aAAa,CACtD,OAAO,EACP,YAAY,EACZ,OAAO,CACR,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,QAAwB,EAAK,EAAE,CAClD,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;IAEzC,MAAM,QAAQ,GAAG,CAAC,SAAY,EAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAE3E,MAAM,UAAU,GAAG,GAAM,EAAE,CACzB,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC;IAErD,OAAO;QACL,KAAK;QACL,QAAQ;QACR;YACE,WAAW;YACX,UAAU;YACV,WAAW;YACX,YAAY;SACb;KACO,CAAC;AACb,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synstate",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
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.3",
|
|
41
41
|
"argparse": "3.0.2",
|
|
42
|
-
"eslint-config-typed": "5.
|
|
42
|
+
"eslint-config-typed": "5.18.0",
|
|
43
43
|
"eslint-plugin-ts-data-forge": "0.8.6",
|
|
44
44
|
"eslint-plugin-ts-fortress": "1.3.3",
|
|
45
45
|
"eslint-plugin-ts-type-forge": "0.8.8",
|
|
@@ -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.
|
|
55
|
-
"synstate-react-hooks-compat": "3.
|
|
54
|
+
"synstate-react-hooks": "3.4.0",
|
|
55
|
+
"synstate-react-hooks-compat": "3.4.0",
|
|
56
56
|
"ts-repo-utils": "10.7.1",
|
|
57
57
|
"valtio": "2.3.2",
|
|
58
58
|
"zustand": "5.0.15"
|
|
@@ -57,6 +57,14 @@ type ChildObservableConfigBase<P extends NonEmptyUnknownList> = Readonly<{
|
|
|
57
57
|
* `override complete() { teardown(); super.complete(); }`.
|
|
58
58
|
*/
|
|
59
59
|
onComplete?: () => void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether this observable can emit nothing more although some parent is
|
|
63
|
+
* still live, asked whenever a parent completes. `true` completes it then,
|
|
64
|
+
* as all parents completing does (`zip` once a completed source's queue is
|
|
65
|
+
* empty).
|
|
66
|
+
*/
|
|
67
|
+
canEmitNoMore?: () => boolean;
|
|
60
68
|
}>;
|
|
61
69
|
|
|
62
70
|
export type ChildObservableConfig<
|
|
@@ -91,6 +99,7 @@ export const createSyncChildObservable = <A, P extends NonEmptyUnknownList>(
|
|
|
91
99
|
depth = 1 + maxDepth(parents),
|
|
92
100
|
initialValue,
|
|
93
101
|
onComplete,
|
|
102
|
+
canEmitNoMore,
|
|
94
103
|
}: ChildObservableConfig<A, P>,
|
|
95
104
|
createTryUpdate: (
|
|
96
105
|
tools: SyncChildObservableTools<A>,
|
|
@@ -101,7 +110,10 @@ export const createSyncChildObservable = <A, P extends NonEmptyUnknownList>(
|
|
|
101
110
|
/** Set once assembled; see {@link unregisterChild}. */
|
|
102
111
|
let mut_self: ChildObservable<A> | undefined = undefined;
|
|
103
112
|
|
|
104
|
-
const
|
|
113
|
+
const autoComplete =
|
|
114
|
+
onComplete !== undefined || parents.some((p) => p.autoComplete);
|
|
115
|
+
|
|
116
|
+
const dispose = (): void => {
|
|
105
117
|
// A completed node is reached again through every parent and child that
|
|
106
118
|
// completes after it; running again would repeat the teardown and the
|
|
107
119
|
// ancestor walk in `unregisterChild` for each of them.
|
|
@@ -116,6 +128,14 @@ export const createSyncChildObservable = <A, P extends NonEmptyUnknownList>(
|
|
|
116
128
|
if (mut_self !== undefined) {
|
|
117
129
|
unregisterChild(mut_self, parents);
|
|
118
130
|
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const complete = (): void => {
|
|
134
|
+
if (handle.isCompleted()) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
dispose();
|
|
119
139
|
|
|
120
140
|
// propagate to parents
|
|
121
141
|
for (const par of parents) {
|
|
@@ -129,8 +149,10 @@ export const createSyncChildObservable = <A, P extends NonEmptyUnknownList>(
|
|
|
129
149
|
}
|
|
130
150
|
|
|
131
151
|
tryCompleteChild({
|
|
152
|
+
canEmitNoMore,
|
|
132
153
|
hasSubscriber: handle.hasSubscriber(),
|
|
133
154
|
hasActiveChild: handle.hasActiveChild(),
|
|
155
|
+
autoComplete,
|
|
134
156
|
parents,
|
|
135
157
|
complete,
|
|
136
158
|
});
|
|
@@ -149,12 +171,21 @@ export const createSyncChildObservable = <A, P extends NonEmptyUnknownList>(
|
|
|
149
171
|
tryUpdate,
|
|
150
172
|
tryComplete,
|
|
151
173
|
complete,
|
|
174
|
+
dispose,
|
|
175
|
+
autoComplete,
|
|
152
176
|
extra: { parents },
|
|
153
177
|
});
|
|
154
178
|
|
|
155
|
-
|
|
179
|
+
// Completed parents never update again, so a child created under them is
|
|
180
|
+
// complete from the start; registering it would only leave a reference in
|
|
181
|
+
// each parent (#2248).
|
|
182
|
+
if (parents.every((p) => p.isCompleted)) {
|
|
183
|
+
dispose();
|
|
184
|
+
} else {
|
|
185
|
+
registerChild(observable, parents);
|
|
156
186
|
|
|
157
|
-
|
|
187
|
+
mut_self = observable;
|
|
188
|
+
}
|
|
158
189
|
|
|
159
190
|
return observable;
|
|
160
191
|
};
|
|
@@ -196,6 +227,7 @@ export const createAsyncChildObservable = <A, P extends NonEmptyUnknownList>(
|
|
|
196
227
|
depth = 1 + maxDepth(parents),
|
|
197
228
|
initialValue,
|
|
198
229
|
onComplete,
|
|
230
|
+
canEmitNoMore,
|
|
199
231
|
}: ChildObservableConfig<A, P>,
|
|
200
232
|
createTryUpdate: (
|
|
201
233
|
tools: AsyncChildObservableTools<A>,
|
|
@@ -209,7 +241,10 @@ export const createAsyncChildObservable = <A, P extends NonEmptyUnknownList>(
|
|
|
209
241
|
/** Set once assembled; see {@link unregisterChild}. */
|
|
210
242
|
let mut_self: ChildObservable<A> | undefined = undefined;
|
|
211
243
|
|
|
212
|
-
const
|
|
244
|
+
const autoComplete =
|
|
245
|
+
onComplete !== undefined || parents.some((p) => p.autoComplete);
|
|
246
|
+
|
|
247
|
+
const dispose = (): void => {
|
|
213
248
|
// A completed node is reached again through every parent and child that
|
|
214
249
|
// completes after it; running again would repeat the teardown and the
|
|
215
250
|
// ancestor walk in `unregisterChild` for each of them.
|
|
@@ -224,6 +259,14 @@ export const createAsyncChildObservable = <A, P extends NonEmptyUnknownList>(
|
|
|
224
259
|
if (mut_self !== undefined) {
|
|
225
260
|
unregisterChild(mut_self, parents);
|
|
226
261
|
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const complete = (): void => {
|
|
265
|
+
if (handle.isCompleted()) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
dispose();
|
|
227
270
|
|
|
228
271
|
// propagate to parents
|
|
229
272
|
for (const par of parents) {
|
|
@@ -237,8 +280,10 @@ export const createAsyncChildObservable = <A, P extends NonEmptyUnknownList>(
|
|
|
237
280
|
}
|
|
238
281
|
|
|
239
282
|
tryCompleteChild({
|
|
283
|
+
canEmitNoMore,
|
|
240
284
|
hasSubscriber: handle.hasSubscriber(),
|
|
241
285
|
hasActiveChild: handle.hasActiveChild(),
|
|
286
|
+
autoComplete,
|
|
242
287
|
parents,
|
|
243
288
|
complete,
|
|
244
289
|
});
|
|
@@ -258,12 +303,21 @@ export const createAsyncChildObservable = <A, P extends NonEmptyUnknownList>(
|
|
|
258
303
|
tryUpdate,
|
|
259
304
|
tryComplete,
|
|
260
305
|
complete,
|
|
306
|
+
dispose,
|
|
307
|
+
autoComplete,
|
|
261
308
|
extra: { parents, addDescendant, deleteDescendant },
|
|
262
309
|
});
|
|
263
310
|
|
|
264
|
-
|
|
311
|
+
// Completed parents never update again, so a child created under them is
|
|
312
|
+
// complete from the start; registering it would only leave a reference in
|
|
313
|
+
// each parent (#2248).
|
|
314
|
+
if (parents.every((p) => p.isCompleted)) {
|
|
315
|
+
dispose();
|
|
316
|
+
} else {
|
|
317
|
+
registerChild(observable, parents);
|
|
265
318
|
|
|
266
|
-
|
|
319
|
+
mut_self = observable;
|
|
320
|
+
}
|
|
267
321
|
|
|
268
322
|
return observable;
|
|
269
323
|
};
|
|
@@ -398,25 +452,29 @@ const unregisterChild = <A,>(
|
|
|
398
452
|
};
|
|
399
453
|
|
|
400
454
|
const tryCompleteChild = <A,>({
|
|
455
|
+
canEmitNoMore,
|
|
401
456
|
hasSubscriber,
|
|
402
457
|
hasActiveChild,
|
|
458
|
+
autoComplete,
|
|
403
459
|
parents,
|
|
404
460
|
complete,
|
|
405
461
|
}: Readonly<{
|
|
462
|
+
canEmitNoMore: (() => boolean) | undefined;
|
|
406
463
|
hasSubscriber: boolean;
|
|
407
464
|
hasActiveChild: boolean;
|
|
465
|
+
autoComplete: boolean;
|
|
408
466
|
parents: ChildObservable<A>['parents'];
|
|
409
467
|
complete: () => void;
|
|
410
468
|
}>): void => {
|
|
411
|
-
// If there is no working parent node
|
|
412
|
-
if (parents.every((r) => r.isCompleted)) {
|
|
469
|
+
// If there is no working parent node, or no value can come from the live ones
|
|
470
|
+
if (parents.every((r) => r.isCompleted) || canEmitNoMore?.() === true) {
|
|
413
471
|
complete();
|
|
414
472
|
|
|
415
473
|
return;
|
|
416
474
|
}
|
|
417
475
|
|
|
418
|
-
// If there are no active child node
|
|
419
|
-
if (!hasSubscriber && !hasActiveChild) {
|
|
476
|
+
// If there are no active child node, and completing releases something
|
|
477
|
+
if (autoComplete && !hasSubscriber && !hasActiveChild) {
|
|
420
478
|
complete();
|
|
421
479
|
}
|
|
422
480
|
|
|
@@ -285,6 +285,16 @@ export const createObservableBaseHandle = <A,>(
|
|
|
285
285
|
};
|
|
286
286
|
};
|
|
287
287
|
|
|
288
|
+
/**
|
|
289
|
+
* How many `startUpdate` calls are running, across every observable. A root
|
|
290
|
+
* written to while this is non-zero is written to from inside a subscriber or
|
|
291
|
+
* a projection, which {@link isPropagating} lets it detect (#2336).
|
|
292
|
+
*/
|
|
293
|
+
const mut_propagation = { depth: 0 };
|
|
294
|
+
|
|
295
|
+
/** Whether some observable's update is propagating right now. */
|
|
296
|
+
export const isPropagating = (): boolean => mut_propagation.depth > 0;
|
|
297
|
+
|
|
288
298
|
/**
|
|
289
299
|
* The extra members every manager observable — a root or an async child — owns:
|
|
290
300
|
* the depth-ordered descendant list and the update propagation that walks it.
|
|
@@ -389,10 +399,20 @@ export const createManagerObservableParts = <A,>(
|
|
|
389
399
|
const startUpdate = (nextValue: A): void => {
|
|
390
400
|
const updateToken = issueUpdateToken();
|
|
391
401
|
|
|
392
|
-
|
|
402
|
+
mut_propagation.depth += 1;
|
|
393
403
|
|
|
394
|
-
|
|
395
|
-
|
|
404
|
+
// A subscriber or projection may throw, and the depth has to come back
|
|
405
|
+
// down regardless, or every later write would warn. The exception itself
|
|
406
|
+
// is not caught: it reaches the caller of `next` as it always has.
|
|
407
|
+
// @sumi-expect-error exceptions/no-try
|
|
408
|
+
try {
|
|
409
|
+
handle.setNext(nextValue, updateToken);
|
|
410
|
+
|
|
411
|
+
for (const p of getPropagationOrder()) {
|
|
412
|
+
p.tryUpdate(updateToken);
|
|
413
|
+
}
|
|
414
|
+
} finally {
|
|
415
|
+
mut_propagation.depth -= 1;
|
|
396
416
|
}
|
|
397
417
|
};
|
|
398
418
|
|
|
@@ -425,6 +445,8 @@ type AssembleObservableArgs<
|
|
|
425
445
|
tryUpdate: (updateToken: UpdateToken) => void;
|
|
426
446
|
tryComplete: () => void;
|
|
427
447
|
complete: () => void;
|
|
448
|
+
dispose: () => void;
|
|
449
|
+
autoComplete: boolean;
|
|
428
450
|
/**
|
|
429
451
|
* Kind-specific public members (`parents`, `addDescendant`) and leaf
|
|
430
452
|
* extensions (`next`, `start`). They are merged here, in the one place the
|
|
@@ -454,6 +476,8 @@ export const assembleObservable = <
|
|
|
454
476
|
tryUpdate,
|
|
455
477
|
tryComplete,
|
|
456
478
|
complete,
|
|
479
|
+
dispose,
|
|
480
|
+
autoComplete,
|
|
457
481
|
extra,
|
|
458
482
|
}: AssembleObservableArgs<A, Kind, Extra>): Extra &
|
|
459
483
|
ObservableBase<A> &
|
|
@@ -507,12 +531,16 @@ export const assembleObservable = <
|
|
|
507
531
|
|
|
508
532
|
hasActiveChild: handle.hasActiveChild,
|
|
509
533
|
|
|
534
|
+
autoComplete,
|
|
535
|
+
|
|
510
536
|
tryUpdate,
|
|
511
537
|
|
|
512
538
|
tryComplete,
|
|
513
539
|
|
|
514
540
|
complete,
|
|
515
541
|
|
|
542
|
+
dispose,
|
|
543
|
+
|
|
516
544
|
subscribe: handle.subscribe,
|
|
517
545
|
|
|
518
546
|
pipe,
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
createManagerObservableParts,
|
|
7
7
|
createObservableBaseHandle,
|
|
8
8
|
createTryUpdateNotImplemented,
|
|
9
|
+
isPropagating,
|
|
9
10
|
} from './create-observable-base.mjs';
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -17,6 +18,8 @@ export type RootObservableTools<A> = Readonly<{
|
|
|
17
18
|
/** Emits `nextValue` and propagates the update to all descendants. */
|
|
18
19
|
startUpdate: (nextValue: A) => void;
|
|
19
20
|
|
|
21
|
+
getSnapshot: () => Optional<A>;
|
|
22
|
+
|
|
20
23
|
isCompleted: () => boolean;
|
|
21
24
|
|
|
22
25
|
/** Completes this observable (leaf teardown included). */
|
|
@@ -68,18 +71,43 @@ export const createRootObservable = <
|
|
|
68
71
|
handle.completeBase();
|
|
69
72
|
};
|
|
70
73
|
|
|
74
|
+
// A root without a teardown (`source`, `createState`, `just`) releases
|
|
75
|
+
// nothing by completing, so a child completing never completes it (#2133).
|
|
76
|
+
const autoComplete = onComplete !== undefined;
|
|
77
|
+
|
|
71
78
|
const tryComplete = (): void => {
|
|
72
|
-
if (!handle.hasSubscriber() && !handle.hasActiveChild()) {
|
|
79
|
+
if (autoComplete && !handle.hasSubscriber() && !handle.hasActiveChild()) {
|
|
73
80
|
complete();
|
|
74
81
|
}
|
|
75
82
|
};
|
|
76
83
|
|
|
84
|
+
// A root created while an update propagates (inside a `switchMap`
|
|
85
|
+
// projection, say) may emit synchronously while `init` runs, as
|
|
86
|
+
// `fromSubscribable` does for a synchronous source. Nothing is derived from
|
|
87
|
+
// it yet, so that is its initial value rather than a write (#2336).
|
|
88
|
+
let mut_isInitializing = true;
|
|
89
|
+
|
|
90
|
+
let mut_hasWarned = false;
|
|
91
|
+
|
|
92
|
+
const startUpdateWithWarning = (nextValue: A): void => {
|
|
93
|
+
if (!mut_isInitializing && !mut_hasWarned && isPropagating()) {
|
|
94
|
+
mut_hasWarned = true;
|
|
95
|
+
|
|
96
|
+
console.warn(writeDuringPropagationWarning);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
startUpdate(nextValue);
|
|
100
|
+
};
|
|
101
|
+
|
|
77
102
|
const extension: Extension = init({
|
|
78
|
-
startUpdate,
|
|
103
|
+
startUpdate: startUpdateWithWarning,
|
|
104
|
+
getSnapshot: handle.getSnapshot,
|
|
79
105
|
isCompleted: handle.isCompleted,
|
|
80
106
|
complete,
|
|
81
107
|
});
|
|
82
108
|
|
|
109
|
+
mut_isInitializing = false;
|
|
110
|
+
|
|
83
111
|
return assembleObservable({
|
|
84
112
|
kind: 'root',
|
|
85
113
|
depth: 0,
|
|
@@ -87,6 +115,9 @@ export const createRootObservable = <
|
|
|
87
115
|
tryUpdate: createTryUpdateNotImplemented,
|
|
88
116
|
tryComplete,
|
|
89
117
|
complete,
|
|
118
|
+
// A root has no parents to keep out of it.
|
|
119
|
+
dispose: complete,
|
|
120
|
+
autoComplete,
|
|
90
121
|
extra: {
|
|
91
122
|
...extension,
|
|
92
123
|
addDescendant,
|
|
@@ -95,6 +126,16 @@ export const createRootObservable = <
|
|
|
95
126
|
});
|
|
96
127
|
};
|
|
97
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Shown once per root observable, the first time it is written to (`next`,
|
|
131
|
+
* `setState`, `dispatch`, …) while another update is still propagating —
|
|
132
|
+
* from a subscriber or a projection. The write still propagates immediately,
|
|
133
|
+
* as it always has; the warning is there because the pattern is wrong, not
|
|
134
|
+
* because anything was refused.
|
|
135
|
+
*/
|
|
136
|
+
export const writeDuringPropagationWarning =
|
|
137
|
+
'synstate: an observable was written to (next / setState / dispatch) while an update was propagating, so subscribers can see intermediate states. Derive the value with map / combine instead of writing from a subscriber or a projection.';
|
|
138
|
+
|
|
98
139
|
if (import.meta.vitest !== undefined) {
|
|
99
140
|
test('isRootObservable', () => {
|
|
100
141
|
assert.isTrue(
|