fix(smartstate): prevent duplicate statepart creation and fix persistence/notification race conditions

This commit is contained in:
2026-02-02 01:05:57 +00:00
parent eb1c48bee4
commit 6436370abc
4 changed files with 91 additions and 23 deletions

View File

@@ -9,6 +9,8 @@ export type TInitMode = 'soft' | 'mandatory' | 'force' | 'persistent';
export class Smartstate<StatePartNameType extends string> {
public statePartMap: { [key in StatePartNameType]?: StatePart<StatePartNameType, any> } = {};
private pendingStatePartCreation: Map<string, Promise<StatePart<StatePartNameType, any>>> = new Map();
constructor() {}
/**
@@ -26,8 +28,14 @@ export class Smartstate<StatePartNameType extends string> {
initialArg?: PayloadType,
initMode: TInitMode = 'soft'
): Promise<StatePart<StatePartNameType, PayloadType>> {
// Return pending creation if one exists to prevent duplicate state parts
const pending = this.pendingStatePartCreation.get(statePartNameArg);
if (pending) {
return pending as Promise<StatePart<StatePartNameType, PayloadType>>;
}
const existingStatePart = this.statePartMap[statePartNameArg];
if (existingStatePart) {
switch (initMode) {
case 'mandatory':
@@ -36,7 +44,7 @@ export class Smartstate<StatePartNameType extends string> {
);
case 'force':
// Force mode: create new state part
return this.createStatePart<PayloadType>(statePartNameArg, initialArg, initMode);
break; // Fall through to creation
case 'soft':
case 'persistent':
default:
@@ -50,7 +58,16 @@ export class Smartstate<StatePartNameType extends string> {
`State part '${statePartNameArg}' does not exist and no initial state provided`
);
}
return this.createStatePart<PayloadType>(statePartNameArg, initialArg, initMode);
}
const creationPromise = this.createStatePart<PayloadType>(statePartNameArg, initialArg, initMode);
this.pendingStatePartCreation.set(statePartNameArg, creationPromise);
try {
const result = await creationPromise;
return result;
} finally {
this.pendingStatePartCreation.delete(statePartNameArg);
}
}
@@ -76,10 +93,18 @@ export class Smartstate<StatePartNameType extends string> {
);
await newState.init();
const currentState = newState.getState();
await newState.setState({
...currentState,
...initialPayloadArg,
});
if (initMode === 'persistent' && currentState !== undefined) {
// Persisted state exists - merge with defaults, persisted values take precedence
await newState.setState({
...initialPayloadArg,
...currentState,
});
} else {
// No persisted state or non-persistent mode
await newState.setState(initialPayloadArg);
}
this.statePartMap[statePartName] = newState;
return newState;
}