@push.rocks/smartstate 2.0.26 → 2.0.30
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/dist_bundle/bundle.js +458 -3498
- package/dist_bundle/bundle.js.map +4 -4
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/smartstate.classes.smartstate.d.ts +1 -0
- package/dist_ts/smartstate.classes.smartstate.js +28 -7
- package/dist_ts/smartstate.classes.statepart.d.ts +3 -1
- package/dist_ts/smartstate.classes.statepart.js +41 -15
- package/npmextra.json +22 -5
- package/package.json +15 -14
- package/readme.hints.md +10 -2
- package/readme.md +61 -56
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/smartstate.classes.smartstate.ts +32 -7
- package/ts/smartstate.classes.statepart.ts +47 -16
- package/dist_ts/smartstate.classes.statecollection.d.ts +0 -8
- package/dist_ts/smartstate.classes.statecollection.js +0 -15
- package/dist_ts/smartstate.classes.stateobservable.d.ts +0 -10
- package/dist_ts/smartstate.classes.stateobservable.js +0 -15
|
@@ -9,6 +9,8 @@ export type TInitMode = 'soft' | 'mandatory' | 'force' | 'persistent';
|
|
|
9
9
|
export class Smartstate<StatePartNameType extends string> {
|
|
10
10
|
public statePartMap: { [key in StatePartNameType]?: StatePart<StatePartNameType, any> } = {};
|
|
11
11
|
|
|
12
|
+
private pendingStatePartCreation: Map<string, Promise<StatePart<StatePartNameType, any>>> = new Map();
|
|
13
|
+
|
|
12
14
|
constructor() {}
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -26,8 +28,14 @@ export class Smartstate<StatePartNameType extends string> {
|
|
|
26
28
|
initialArg?: PayloadType,
|
|
27
29
|
initMode: TInitMode = 'soft'
|
|
28
30
|
): Promise<StatePart<StatePartNameType, PayloadType>> {
|
|
31
|
+
// Return pending creation if one exists to prevent duplicate state parts
|
|
32
|
+
const pending = this.pendingStatePartCreation.get(statePartNameArg);
|
|
33
|
+
if (pending) {
|
|
34
|
+
return pending as Promise<StatePart<StatePartNameType, PayloadType>>;
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
const existingStatePart = this.statePartMap[statePartNameArg];
|
|
30
|
-
|
|
38
|
+
|
|
31
39
|
if (existingStatePart) {
|
|
32
40
|
switch (initMode) {
|
|
33
41
|
case 'mandatory':
|
|
@@ -36,7 +44,7 @@ export class Smartstate<StatePartNameType extends string> {
|
|
|
36
44
|
);
|
|
37
45
|
case 'force':
|
|
38
46
|
// Force mode: create new state part
|
|
39
|
-
|
|
47
|
+
break; // Fall through to creation
|
|
40
48
|
case 'soft':
|
|
41
49
|
case 'persistent':
|
|
42
50
|
default:
|
|
@@ -50,7 +58,16 @@ export class Smartstate<StatePartNameType extends string> {
|
|
|
50
58
|
`State part '${statePartNameArg}' does not exist and no initial state provided`
|
|
51
59
|
);
|
|
52
60
|
}
|
|
53
|
-
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const creationPromise = this.createStatePart<PayloadType>(statePartNameArg, initialArg, initMode);
|
|
64
|
+
this.pendingStatePartCreation.set(statePartNameArg, creationPromise);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const result = await creationPromise;
|
|
68
|
+
return result;
|
|
69
|
+
} finally {
|
|
70
|
+
this.pendingStatePartCreation.delete(statePartNameArg);
|
|
54
71
|
}
|
|
55
72
|
}
|
|
56
73
|
|
|
@@ -76,10 +93,18 @@ export class Smartstate<StatePartNameType extends string> {
|
|
|
76
93
|
);
|
|
77
94
|
await newState.init();
|
|
78
95
|
const currentState = newState.getState();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
|
|
97
|
+
if (initMode === 'persistent' && currentState !== undefined) {
|
|
98
|
+
// Persisted state exists - merge with defaults, persisted values take precedence
|
|
99
|
+
await newState.setState({
|
|
100
|
+
...initialPayloadArg,
|
|
101
|
+
...currentState,
|
|
102
|
+
});
|
|
103
|
+
} else {
|
|
104
|
+
// No persisted state or non-persistent mode
|
|
105
|
+
await newState.setState(initialPayloadArg);
|
|
106
|
+
}
|
|
107
|
+
|
|
83
108
|
this.statePartMap[statePartName] = newState;
|
|
84
109
|
return newState;
|
|
85
110
|
}
|
|
@@ -7,6 +7,8 @@ export class StatePart<TStatePartName, TStatePayload> {
|
|
|
7
7
|
public stateStore: TStatePayload | undefined;
|
|
8
8
|
private cumulativeDeferred = plugins.smartpromise.cumulativeDefer();
|
|
9
9
|
|
|
10
|
+
private pendingCumulativeNotification: ReturnType<typeof setTimeout> | null = null;
|
|
11
|
+
|
|
10
12
|
private webStoreOptions: plugins.webstore.IWebStoreOptions;
|
|
11
13
|
private webStore: plugins.webstore.WebStore<TStatePayload> | null = null; // Add WebStore instance
|
|
12
14
|
|
|
@@ -50,14 +52,16 @@ export class StatePart<TStatePartName, TStatePayload> {
|
|
|
50
52
|
if (!this.validateState(newStateArg)) {
|
|
51
53
|
throw new Error(`Invalid state structure for state part '${this.name}'`);
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
await this.notifyChange();
|
|
56
|
-
|
|
57
|
-
// Save state to WebStore if initialized
|
|
55
|
+
|
|
56
|
+
// Save to WebStore first to ensure atomicity - if save fails, memory state remains unchanged
|
|
58
57
|
if (this.webStore) {
|
|
59
58
|
await this.webStore.set(String(this.name), newStateArg);
|
|
60
59
|
}
|
|
60
|
+
|
|
61
|
+
// Update in-memory state after successful persistence
|
|
62
|
+
this.stateStore = newStateArg;
|
|
63
|
+
await this.notifyChange();
|
|
64
|
+
|
|
61
65
|
return this.stateStore;
|
|
62
66
|
}
|
|
63
67
|
|
|
@@ -79,7 +83,7 @@ export class StatePart<TStatePartName, TStatePayload> {
|
|
|
79
83
|
return;
|
|
80
84
|
}
|
|
81
85
|
const createStateHash = async (stateArg: any) => {
|
|
82
|
-
return await plugins.smarthashWeb.sha256FromString(plugins.smartjson.
|
|
86
|
+
return await plugins.smarthashWeb.sha256FromString(plugins.smartjson.stableOneWayStringify(stateArg));
|
|
83
87
|
};
|
|
84
88
|
const currentHash = await createStateHash(this.stateStore);
|
|
85
89
|
if (
|
|
@@ -98,8 +102,13 @@ export class StatePart<TStatePartName, TStatePayload> {
|
|
|
98
102
|
* creates a cumulative notification by adding a change notification at the end of the call stack;
|
|
99
103
|
*/
|
|
100
104
|
public notifyChangeCumulative() {
|
|
101
|
-
//
|
|
102
|
-
|
|
105
|
+
// Debounce: clear any pending notification
|
|
106
|
+
if (this.pendingCumulativeNotification) {
|
|
107
|
+
clearTimeout(this.pendingCumulativeNotification);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.pendingCumulativeNotification = setTimeout(async () => {
|
|
111
|
+
this.pendingCumulativeNotification = null;
|
|
103
112
|
if (this.stateStore) {
|
|
104
113
|
await this.notifyChange();
|
|
105
114
|
}
|
|
@@ -122,7 +131,8 @@ export class StatePart<TStatePartName, TStatePayload> {
|
|
|
122
131
|
try {
|
|
123
132
|
return selectorFn(stateArg);
|
|
124
133
|
} catch (e) {
|
|
125
|
-
|
|
134
|
+
console.error(`Selector error in state part '${this.name}':`, e);
|
|
135
|
+
return undefined;
|
|
126
136
|
}
|
|
127
137
|
})
|
|
128
138
|
);
|
|
@@ -151,20 +161,41 @@ export class StatePart<TStatePartName, TStatePayload> {
|
|
|
151
161
|
/**
|
|
152
162
|
* waits until a certain part of the state becomes available
|
|
153
163
|
* @param selectorFn
|
|
164
|
+
* @param timeoutMs - optional timeout in milliseconds to prevent indefinite waiting
|
|
154
165
|
*/
|
|
155
166
|
public async waitUntilPresent<T = TStatePayload>(
|
|
156
|
-
selectorFn?: (state: TStatePayload) => T
|
|
167
|
+
selectorFn?: (state: TStatePayload) => T,
|
|
168
|
+
timeoutMs?: number
|
|
157
169
|
): Promise<T> {
|
|
158
170
|
const done = plugins.smartpromise.defer<T>();
|
|
159
171
|
const selectedObservable = this.select(selectorFn);
|
|
160
|
-
|
|
161
|
-
|
|
172
|
+
let resolved = false;
|
|
173
|
+
|
|
174
|
+
const subscription = selectedObservable.subscribe((value) => {
|
|
175
|
+
if (value && !resolved) {
|
|
176
|
+
resolved = true;
|
|
162
177
|
done.resolve(value);
|
|
163
178
|
}
|
|
164
179
|
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
180
|
+
|
|
181
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
182
|
+
if (timeoutMs) {
|
|
183
|
+
timeoutId = setTimeout(() => {
|
|
184
|
+
if (!resolved) {
|
|
185
|
+
resolved = true;
|
|
186
|
+
subscription.unsubscribe();
|
|
187
|
+
done.reject(new Error(`waitUntilPresent timed out after ${timeoutMs}ms`));
|
|
188
|
+
}
|
|
189
|
+
}, timeoutMs);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const result = await done.promise;
|
|
194
|
+
return result;
|
|
195
|
+
} finally {
|
|
196
|
+
subscription.unsubscribe();
|
|
197
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
198
|
+
}
|
|
168
199
|
}
|
|
169
200
|
|
|
170
201
|
/**
|
|
@@ -175,6 +206,6 @@ export class StatePart<TStatePartName, TStatePayload> {
|
|
|
175
206
|
) {
|
|
176
207
|
const resultPromise = funcArg(this);
|
|
177
208
|
this.cumulativeDeferred.addPromise(resultPromise);
|
|
178
|
-
this.setState(await resultPromise);
|
|
209
|
+
await this.setState(await resultPromise);
|
|
179
210
|
}
|
|
180
211
|
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { StatePart } from './smartstate.classes.statepart';
|
|
2
|
-
/**
|
|
3
|
-
* A StatePartCollection is a collection of StateParts.
|
|
4
|
-
* It can be used for expressing interest in a certain set of StateParts.
|
|
5
|
-
*/
|
|
6
|
-
export declare class StatePartCollection<StatePartNameType, T> extends StatePart<StatePartNameType, T> {
|
|
7
|
-
constructor(nameArg: StatePartNameType);
|
|
8
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.StatePartCollection = void 0;
|
|
4
|
-
const smartstate_classes_statepart_1 = require("./smartstate.classes.statepart");
|
|
5
|
-
/**
|
|
6
|
-
* A StatePartCollection is a collection of StateParts.
|
|
7
|
-
* It can be used for expressing interest in a certain set of StateParts.
|
|
8
|
-
*/
|
|
9
|
-
class StatePartCollection extends smartstate_classes_statepart_1.StatePart {
|
|
10
|
-
constructor(nameArg) {
|
|
11
|
-
super(nameArg);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
exports.StatePartCollection = StatePartCollection;
|
|
15
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRzdGF0ZS5jbGFzc2VzLnN0YXRlY29sbGVjdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0c3RhdGUuY2xhc3Nlcy5zdGF0ZWNvbGxlY3Rpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EsaUZBQTJEO0FBRTNEOzs7R0FHRztBQUNILE1BQWEsbUJBQTBDLFNBQVEsd0NBQStCO0lBQzVGLFlBQVksT0FBMEI7UUFDcEMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ2pCLENBQUM7Q0FDRjtBQUpELGtEQUlDIn0=
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* State observable observes a StatePart and notifies everyone interested
|
|
3
|
-
*/
|
|
4
|
-
export declare class StateObservable {
|
|
5
|
-
/**
|
|
6
|
-
* creates an observable from a StateCollection
|
|
7
|
-
*/
|
|
8
|
-
static fromStatePartCollection(filterArg?: () => any): void;
|
|
9
|
-
constructor();
|
|
10
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.StateObservable = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* State observable observes a StatePart and notifies everyone interested
|
|
6
|
-
*/
|
|
7
|
-
class StateObservable {
|
|
8
|
-
/**
|
|
9
|
-
* creates an observable from a StateCollection
|
|
10
|
-
*/
|
|
11
|
-
static fromStatePartCollection(filterArg) { }
|
|
12
|
-
constructor() { }
|
|
13
|
-
}
|
|
14
|
-
exports.StateObservable = StateObservable;
|
|
15
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRzdGF0ZS5jbGFzc2VzLnN0YXRlb2JzZXJ2YWJsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0c3RhdGUuY2xhc3Nlcy5zdGF0ZW9ic2VydmFibGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBRUE7O0dBRUc7QUFDSCxNQUFhLGVBQWU7SUFDMUI7O09BRUc7SUFDSSxNQUFNLENBQUMsdUJBQXVCLENBQUMsU0FBcUIsSUFBRyxDQUFDO0lBRS9ELGdCQUFlLENBQUM7Q0FDakI7QUFQRCwwQ0FPQyJ9
|