@push.rocks/smartstate 2.0.23 → 2.0.25

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.
@@ -10,9 +10,10 @@ export declare class Smartstate<StatePartNameType extends string> {
10
10
  constructor();
11
11
  /**
12
12
  * Allows getting and initializing a new statepart
13
- * initMode === 'soft' it will allow existing stateparts
14
- * initMode === 'mandatory' will fail if there is an existing statepart
15
- * initMode === 'force' will overwrite any existing statepart
13
+ * initMode === 'soft' (default) - returns existing statepart if exists, creates new if not
14
+ * initMode === 'mandatory' - requires statepart to not exist, fails if it does
15
+ * initMode === 'force' - always creates new statepart, overwriting any existing
16
+ * initMode === 'persistent' - like 'soft' but with webstore persistence
16
17
  * @param statePartNameArg
17
18
  * @param initialArg
18
19
  * @param initMode
@@ -22,6 +23,7 @@ export declare class Smartstate<StatePartNameType extends string> {
22
23
  * Creates a statepart
23
24
  * @param statePartName
24
25
  * @param initialPayloadArg
26
+ * @param initMode
25
27
  */
26
28
  private createStatePart;
27
29
  }
@@ -9,23 +9,34 @@ export class Smartstate {
9
9
  }
10
10
  /**
11
11
  * Allows getting and initializing a new statepart
12
- * initMode === 'soft' it will allow existing stateparts
13
- * initMode === 'mandatory' will fail if there is an existing statepart
14
- * initMode === 'force' will overwrite any existing statepart
12
+ * initMode === 'soft' (default) - returns existing statepart if exists, creates new if not
13
+ * initMode === 'mandatory' - requires statepart to not exist, fails if it does
14
+ * initMode === 'force' - always creates new statepart, overwriting any existing
15
+ * initMode === 'persistent' - like 'soft' but with webstore persistence
15
16
  * @param statePartNameArg
16
17
  * @param initialArg
17
18
  * @param initMode
18
19
  */
19
- async getStatePart(statePartNameArg, initialArg, initMode) {
20
- if (this.statePartMap[statePartNameArg]) {
21
- if (initialArg && (!initMode || initMode !== 'soft')) {
22
- throw new Error(`${statePartNameArg} already exists, yet you try to set an initial state again`);
20
+ async getStatePart(statePartNameArg, initialArg, initMode = 'soft') {
21
+ const existingStatePart = this.statePartMap[statePartNameArg];
22
+ if (existingStatePart) {
23
+ switch (initMode) {
24
+ case 'mandatory':
25
+ throw new Error(`State part '${statePartNameArg}' already exists, but initMode is 'mandatory'`);
26
+ case 'force':
27
+ // Force mode: create new state part
28
+ return this.createStatePart(statePartNameArg, initialArg, initMode);
29
+ case 'soft':
30
+ case 'persistent':
31
+ default:
32
+ // Return existing state part
33
+ return existingStatePart;
23
34
  }
24
- return this.statePartMap[statePartNameArg];
25
35
  }
26
36
  else {
37
+ // State part doesn't exist
27
38
  if (!initialArg) {
28
- throw new Error(`${statePartNameArg} does not yet exist, yet you don't provide an initial state`);
39
+ throw new Error(`State part '${statePartNameArg}' does not exist and no initial state provided`);
29
40
  }
30
41
  return this.createStatePart(statePartNameArg, initialArg, initMode);
31
42
  }
@@ -34,8 +45,9 @@ export class Smartstate {
34
45
  * Creates a statepart
35
46
  * @param statePartName
36
47
  * @param initialPayloadArg
48
+ * @param initMode
37
49
  */
38
- async createStatePart(statePartName, initialPayloadArg, initMode) {
50
+ async createStatePart(statePartName, initialPayloadArg, initMode = 'soft') {
39
51
  const newState = new StatePart(statePartName, initMode === 'persistent'
40
52
  ? {
41
53
  dbName: 'smartstate',
@@ -45,11 +57,11 @@ export class Smartstate {
45
57
  await newState.init();
46
58
  const currentState = newState.getState();
47
59
  await newState.setState({
48
- ...initialPayloadArg,
49
60
  ...currentState,
61
+ ...initialPayloadArg,
50
62
  });
51
63
  this.statePartMap[statePartName] = newState;
52
64
  return newState;
53
65
  }
54
66
  }
55
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRzdGF0ZS5jbGFzc2VzLnNtYXJ0c3RhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHN0YXRlLmNsYXNzZXMuc21hcnRzdGF0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBQ25ELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUk5RDs7R0FFRztBQUNILE1BQU0sT0FBTyxVQUFVO0lBR3JCO1FBRk8saUJBQVksR0FBdUUsRUFBRSxDQUFDO0lBRTlFLENBQUM7SUFFaEI7Ozs7Ozs7O09BUUc7SUFDSSxLQUFLLENBQUMsWUFBWSxDQUN2QixnQkFBbUMsRUFDbkMsVUFBd0IsRUFDeEIsUUFBb0I7UUFFcEIsSUFBSSxJQUFJLENBQUMsWUFBWSxDQUFDLGdCQUFnQixDQUFDLEVBQUUsQ0FBQztZQUN4QyxJQUFJLFVBQVUsSUFBSSxDQUFDLENBQUMsUUFBUSxJQUFJLFFBQVEsS0FBSyxNQUFNLENBQUMsRUFBRSxDQUFDO2dCQUNyRCxNQUFNLElBQUksS0FBSyxDQUNiLEdBQUcsZ0JBQWdCLDREQUE0RCxDQUNoRixDQUFDO1lBQ0osQ0FBQztZQUNELE9BQU8sSUFBSSxDQUFDLFlBQVksQ0FBQyxnQkFBZ0IsQ0FBOEMsQ0FBQztRQUMxRixDQUFDO2FBQU0sQ0FBQztZQUNOLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztnQkFDaEIsTUFBTSxJQUFJLEtBQUssQ0FDYixHQUFHLGdCQUFnQiw2REFBNkQsQ0FDakYsQ0FBQztZQUNKLENBQUM7WUFDRCxPQUFPLElBQUksQ0FBQyxlQUFlLENBQWMsZ0JBQWdCLEVBQUUsVUFBVSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQ25GLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNLLEtBQUssQ0FBQyxlQUFlLENBQzNCLGFBQWdDLEVBQ2hDLGlCQUE4QixFQUM5QixRQUFvQjtRQUVwQixNQUFNLFFBQVEsR0FBRyxJQUFJLFNBQVMsQ0FDNUIsYUFBYSxFQUNiLFFBQVEsS0FBSyxZQUFZO1lBQ3ZCLENBQUMsQ0FBQztnQkFDRSxNQUFNLEVBQUUsWUFBWTtnQkFDcEIsU0FBUyxFQUFFLGFBQWE7YUFDekI7WUFDSCxDQUFDLENBQUMsSUFBSSxDQUNULENBQUM7UUFDRixNQUFNLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUN0QixNQUFNLFlBQVksR0FBRyxRQUFRLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDekMsTUFBTSxRQUFRLENBQUMsUUFBUSxDQUFDO1lBQ3RCLEdBQUcsaUJBQWlCO1lBQ3BCLEdBQUcsWUFBWTtTQUNoQixDQUFDLENBQUM7UUFDSCxJQUFJLENBQUMsWUFBWSxDQUFDLGFBQWEsQ0FBQyxHQUFHLFFBQVEsQ0FBQztRQUM1QyxPQUFPLFFBQVEsQ0FBQztJQUNsQixDQUFDO0NBQ0YifQ==
67
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRzdGF0ZS5jbGFzc2VzLnNtYXJ0c3RhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHN0YXRlLmNsYXNzZXMuc21hcnRzdGF0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBQ25ELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUk5RDs7R0FFRztBQUNILE1BQU0sT0FBTyxVQUFVO0lBR3JCO1FBRk8saUJBQVksR0FBdUUsRUFBRSxDQUFDO0lBRTlFLENBQUM7SUFFaEI7Ozs7Ozs7OztPQVNHO0lBQ0ksS0FBSyxDQUFDLFlBQVksQ0FDdkIsZ0JBQW1DLEVBQ25DLFVBQXdCLEVBQ3hCLFdBQXNCLE1BQU07UUFFNUIsTUFBTSxpQkFBaUIsR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLGdCQUFnQixDQUFDLENBQUM7UUFFOUQsSUFBSSxpQkFBaUIsRUFBRSxDQUFDO1lBQ3RCLFFBQVEsUUFBUSxFQUFFLENBQUM7Z0JBQ2pCLEtBQUssV0FBVztvQkFDZCxNQUFNLElBQUksS0FBSyxDQUNiLGVBQWUsZ0JBQWdCLCtDQUErQyxDQUMvRSxDQUFDO2dCQUNKLEtBQUssT0FBTztvQkFDVixvQ0FBb0M7b0JBQ3BDLE9BQU8sSUFBSSxDQUFDLGVBQWUsQ0FBYyxnQkFBZ0IsRUFBRSxVQUFVLEVBQUUsUUFBUSxDQUFDLENBQUM7Z0JBQ25GLEtBQUssTUFBTSxDQUFDO2dCQUNaLEtBQUssWUFBWSxDQUFDO2dCQUNsQjtvQkFDRSw2QkFBNkI7b0JBQzdCLE9BQU8saUJBQThELENBQUM7WUFDMUUsQ0FBQztRQUNILENBQUM7YUFBTSxDQUFDO1lBQ04sMkJBQTJCO1lBQzNCLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztnQkFDaEIsTUFBTSxJQUFJLEtBQUssQ0FDYixlQUFlLGdCQUFnQixnREFBZ0QsQ0FDaEYsQ0FBQztZQUNKLENBQUM7WUFDRCxPQUFPLElBQUksQ0FBQyxlQUFlLENBQWMsZ0JBQWdCLEVBQUUsVUFBVSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQ25GLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSyxLQUFLLENBQUMsZUFBZSxDQUMzQixhQUFnQyxFQUNoQyxpQkFBOEIsRUFDOUIsV0FBc0IsTUFBTTtRQUU1QixNQUFNLFFBQVEsR0FBRyxJQUFJLFNBQVMsQ0FDNUIsYUFBYSxFQUNiLFFBQVEsS0FBSyxZQUFZO1lBQ3ZCLENBQUMsQ0FBQztnQkFDRSxNQUFNLEVBQUUsWUFBWTtnQkFDcEIsU0FBUyxFQUFFLGFBQWE7YUFDekI7WUFDSCxDQUFDLENBQUMsSUFBSSxDQUNULENBQUM7UUFDRixNQUFNLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUN0QixNQUFNLFlBQVksR0FBRyxRQUFRLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDekMsTUFBTSxRQUFRLENBQUMsUUFBUSxDQUFDO1lBQ3RCLEdBQUcsWUFBWTtZQUNmLEdBQUcsaUJBQWlCO1NBQ3JCLENBQUMsQ0FBQztRQUNILElBQUksQ0FBQyxZQUFZLENBQUMsYUFBYSxDQUFDLEdBQUcsUUFBUSxDQUFDO1FBQzVDLE9BQU8sUUFBUSxDQUFDO0lBQ2xCLENBQUM7Q0FDRiJ9
@@ -3,7 +3,7 @@ import { StateAction, type IActionDef } from './smartstate.classes.stateaction.j
3
3
  export declare class StatePart<TStatePartName, TStatePayload> {
4
4
  name: TStatePartName;
5
5
  state: plugins.smartrx.rxjs.Subject<TStatePayload>;
6
- stateStore: TStatePayload;
6
+ stateStore: TStatePayload | undefined;
7
7
  private cumulativeDeferred;
8
8
  private webStoreOptions;
9
9
  private webStore;
@@ -15,16 +15,21 @@ export declare class StatePart<TStatePartName, TStatePayload> {
15
15
  /**
16
16
  * gets the state from the state store
17
17
  */
18
- getState(): TStatePayload;
18
+ getState(): TStatePayload | undefined;
19
19
  /**
20
20
  * sets the stateStore to the new state
21
21
  * @param newStateArg
22
22
  */
23
23
  setState(newStateArg: TStatePayload): Promise<TStatePayload>;
24
+ /**
25
+ * Validates state structure - can be overridden for custom validation
26
+ * @param stateArg
27
+ */
28
+ protected validateState(stateArg: any): stateArg is TStatePayload;
24
29
  /**
25
30
  * notifies of a change on the state
26
31
  */
27
- notifyChange(): void;
32
+ notifyChange(): Promise<void>;
28
33
  private lastStateNotificationPayloadHash;
29
34
  /**
30
35
  * creates a cumulative notification by adding a change notification at the end of the call stack;
@@ -19,9 +19,9 @@ export class StatePart {
19
19
  this.webStore = new plugins.webstore.WebStore(this.webStoreOptions);
20
20
  await this.webStore.init();
21
21
  const storedState = await this.webStore.get(String(this.name));
22
- if (storedState) {
22
+ if (storedState && this.validateState(storedState)) {
23
23
  this.stateStore = storedState;
24
- this.notifyChange();
24
+ await this.notifyChange();
25
25
  }
26
26
  }
27
27
  }
@@ -36,28 +36,44 @@ export class StatePart {
36
36
  * @param newStateArg
37
37
  */
38
38
  async setState(newStateArg) {
39
+ // Validate state structure
40
+ if (!this.validateState(newStateArg)) {
41
+ throw new Error(`Invalid state structure for state part '${this.name}'`);
42
+ }
39
43
  this.stateStore = newStateArg;
40
- this.notifyChange();
44
+ await this.notifyChange();
41
45
  // Save state to WebStore if initialized
42
46
  if (this.webStore) {
43
47
  await this.webStore.set(String(this.name), newStateArg);
44
48
  }
45
49
  return this.stateStore;
46
50
  }
51
+ /**
52
+ * Validates state structure - can be overridden for custom validation
53
+ * @param stateArg
54
+ */
55
+ validateState(stateArg) {
56
+ // Basic validation - ensure state is not null/undefined
57
+ // Subclasses can override for more specific validation
58
+ return stateArg !== null && stateArg !== undefined;
59
+ }
47
60
  /**
48
61
  * notifies of a change on the state
49
62
  */
50
- notifyChange() {
51
- const createStateHash = (stateArg) => {
52
- return plugins.smarthashWeb.sha256FromString(plugins.smartjson.stringify(stateArg));
63
+ async notifyChange() {
64
+ if (!this.stateStore) {
65
+ return;
66
+ }
67
+ const createStateHash = async (stateArg) => {
68
+ return await plugins.smarthashWeb.sha256FromString(plugins.smartjson.stringify(stateArg));
53
69
  };
54
- if (this.stateStore &&
55
- this.lastStateNotificationPayloadHash &&
56
- createStateHash(this.stateStore) === this.lastStateNotificationPayloadHash) {
70
+ const currentHash = await createStateHash(this.stateStore);
71
+ if (this.lastStateNotificationPayloadHash &&
72
+ currentHash === this.lastStateNotificationPayloadHash) {
57
73
  return;
58
74
  }
59
75
  else {
60
- this.lastStateNotificationPayloadHash = this.stateStore;
76
+ this.lastStateNotificationPayloadHash = currentHash;
61
77
  }
62
78
  this.state.next(this.stateStore);
63
79
  }
@@ -66,7 +82,11 @@ export class StatePart {
66
82
  */
67
83
  notifyChangeCumulative() {
68
84
  // TODO: check viability
69
- setTimeout(() => this.state.next(this.stateStore), 0);
85
+ setTimeout(async () => {
86
+ if (this.stateStore) {
87
+ await this.notifyChange();
88
+ }
89
+ }, 0);
70
90
  }
71
91
  /**
72
92
  * selects a state or a substate
@@ -75,7 +95,7 @@ export class StatePart {
75
95
  if (!selectorFn) {
76
96
  selectorFn = (state) => state;
77
97
  }
78
- const mapped = this.state.pipe(plugins.smartrx.rxjs.ops.startWith(this.getState()), plugins.smartrx.rxjs.ops.map((stateArg) => {
98
+ const mapped = this.state.pipe(plugins.smartrx.rxjs.ops.startWith(this.getState()), plugins.smartrx.rxjs.ops.filter((stateArg) => stateArg !== undefined), plugins.smartrx.rxjs.ops.map((stateArg) => {
79
99
  try {
80
100
  return selectorFn(stateArg);
81
101
  }
@@ -125,4 +145,4 @@ export class StatePart {
125
145
  this.setState(await resultPromise);
126
146
  }
127
147
  }
128
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRzdGF0ZS5jbGFzc2VzLnN0YXRlcGFydC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0c3RhdGUuY2xhc3Nlcy5zdGF0ZXBhcnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSx5QkFBeUIsQ0FBQztBQUNuRCxPQUFPLEVBQUUsV0FBVyxFQUFtQixNQUFNLHFDQUFxQyxDQUFDO0FBRW5GLE1BQU0sT0FBTyxTQUFTO0lBU3BCLFlBQVksT0FBdUIsRUFBRSxrQkFBc0Q7UUFQcEYsVUFBSyxHQUFHLElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFpQixDQUFDO1FBRXpELHVCQUFrQixHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUMsZUFBZSxFQUFFLENBQUM7UUFHNUQsYUFBUSxHQUFvRCxJQUFJLENBQUMsQ0FBQyx3QkFBd0I7UUFHaEcsSUFBSSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUM7UUFFcEIsc0RBQXNEO1FBQ3RELElBQUksa0JBQWtCLEVBQUUsQ0FBQztZQUN2QixJQUFJLENBQUMsZUFBZSxHQUFHLGtCQUFrQixDQUFDO1FBQzVDLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsSUFBSTtRQUNmLElBQUksSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO1lBQ3pCLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBZ0IsSUFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDO1lBQ25GLE1BQU0sSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUMzQixNQUFNLFdBQVcsR0FBRyxNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztZQUMvRCxJQUFJLFdBQVcsRUFBRSxDQUFDO2dCQUNoQixJ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
148
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRzdGF0ZS5jbGFzc2VzLnN0YXRlcGFydC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0c3RhdGUuY2xhc3Nlcy5zdGF0ZXBhcnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSx5QkFBeUIsQ0FBQztBQUNuRCxPQUFPLEVBQUUsV0FBVyxFQUFtQixNQUFNLHFDQUFxQyxDQUFDO0FBRW5GLE1BQU0sT0FBTyxTQUFTO0lBU3BCLFlBQVksT0FBdUIsRUFBRSxrQkFBc0Q7UUFQcEYsVUFBSyxHQUFHLElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFpQixDQUFDO1FBRXpELHVCQUFrQixHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUMsZUFBZSxFQUFFLENBQUM7UUFHNUQsYUFBUSxHQUFvRCxJQUFJLENBQUMsQ0FBQyx3QkFBd0I7UUFHaEcsSUFBSSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUM7UUFFcEIsc0RBQXNEO1FBQ3RELElBQUksa0JBQWtCLEVBQUUsQ0FBQztZQUN2QixJQUFJLENBQUMsZUFBZSxHQUFHLGtCQUFrQixDQUFDO1FBQzVDLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsSUFBSTtRQUNmLElBQUksSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO1lBQ3pCLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBZ0IsSUFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDO1lBQ25GLE1BQU0sSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUMzQixNQUFNLFdBQVcsR0FBRyxNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztZQUMvRCxJQUFJLFdBQVcsSUFBSSxJQUFJLENBQUMsYUFB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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartstate",
3
- "version": "2.0.23",
3
+ "version": "2.0.25",
4
4
  "private": false,
5
5
  "description": "A package for handling and managing state in applications.",
6
6
  "main": "dist_ts/index.js",
@@ -8,6 +8,11 @@
8
8
  "type": "module",
9
9
  "author": "Lossless GmbH",
10
10
  "license": "MIT",
11
+ "scripts": {
12
+ "test": "(tstest test/ --verbose)",
13
+ "build": "(tsbuild tsfolders --allowimplicitany && tsbundle npm)",
14
+ "buildDocs": "tsdoc"
15
+ },
11
16
  "devDependencies": {
12
17
  "@git.zone/tsbuild": "^2.6.4",
13
18
  "@git.zone/tsbundle": "^2.4.0",
@@ -56,9 +61,5 @@
56
61
  "type": "git",
57
62
  "url": "https://code.foss.global/push.rocks/smartstate.git"
58
63
  },
59
- "scripts": {
60
- "test": "(tstest test/ --verbose)",
61
- "build": "(tsbuild tsfolders --allowimplicitany && tsbundle npm)",
62
- "buildDocs": "tsdoc"
63
- }
64
- }
64
+ "packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
65
+ }
package/readme.hints.md CHANGED
@@ -1,39 +1,52 @@
1
1
  # Smartstate Implementation Notes
2
2
 
3
- ## Current API (as of analysis)
3
+ ## Current API (as of v2.0.24+)
4
4
 
5
5
  ### State Part Initialization
6
- - State parts can be created with different init modes: 'soft', 'mandatory', 'force', 'persistent'
6
+ - State parts can be created with different init modes: 'soft' (default), 'mandatory', 'force', 'persistent'
7
+ - 'soft' - returns existing state part if exists, creates new if not
8
+ - 'mandatory' - requires state part to not exist, fails if it does
9
+ - 'force' - always creates new state part, overwriting any existing
10
+ - 'persistent' - like 'soft' but with WebStore persistence (IndexedDB)
7
11
  - Persistent mode automatically calls init() internally - no need to call it manually
8
- - WebStore integration for persistent state uses IndexedDB
12
+ - State merge order fixed: initial state takes precedence over stored state
9
13
 
10
14
  ### Actions
11
15
  - Actions are created with `createAction()` method
12
16
  - Two ways to dispatch actions:
13
17
  1. `stateAction.trigger(payload)` - returns Promise<TStatePayload>
14
18
  2. `await statePart.dispatchAction(stateAction, payload)` - returns Promise<TStatePayload>
15
- - Both methods now return the same Promise, providing flexibility in usage
19
+ - Both methods return the same Promise, providing flexibility in usage
16
20
 
17
21
  ### State Management Methods
18
- - `select()` - returns Observable with startWith current state
22
+ - `select()` - returns Observable with startWith current state, filters undefined states
19
23
  - `waitUntilPresent()` - waits for specific state condition
20
24
  - `stateSetup()` - async state initialization with cumulative defer
21
- - `notifyChangeCumulative()` - defers notification to end of call stack (no callback parameter)
25
+ - `notifyChangeCumulative()` - defers notification to end of call stack
26
+ - `getState()` - returns current state or undefined
27
+ - `setState()` - validates state before setting, notifies only on actual changes
22
28
 
23
29
  ### State Hash Detection
24
30
  - Uses SHA256 hash to detect actual state changes
25
- - Bug: Currently stores the state object itself as hash instead of the actual hash
26
- - This prevents proper duplicate notification prevention
31
+ - Fixed: Hash comparison now properly awaits async hash calculation
32
+ - Prevents duplicate notifications for identical state values
33
+ - `notifyChange()` is now async to support proper hash comparison
34
+
35
+ ### State Validation
36
+ - Basic validation ensures state is not null/undefined
37
+ - `validateState()` method can be overridden in subclasses for custom validation
38
+ - Validation runs on both setState() and when loading from persistent storage
27
39
 
28
40
  ### Type System
29
41
  - Can use either enums or string literal types for state part names
30
42
  - Test uses simple string types: `type TMyStateParts = 'testStatePart'`
43
+ - State can be undefined initially, handled properly in select() and other methods
31
44
 
32
- ## Fixed Issues in Documentation
33
- 1. Updated trigger() to return Promise (API enhancement)
34
- 2. Added dispatchAction as alternative method
35
- 3. Corrected notifyChangeCumulative usage
36
- 4. Clarified persistent mode auto-init
37
- 5. Added stateSetup documentation
38
- 6. Fixed state hash detection description
39
- 7. Both trigger() and dispatchAction() now return Promise for consistency
45
+ ## Recent Fixes (v2.0.24+)
46
+ 1. Fixed state hash bug - now properly compares hash values instead of promises
47
+ 2. Fixed state initialization merge order - initial state now takes precedence
48
+ 3. Ensured stateStore is properly typed as potentially undefined
49
+ 4. Simplified init mode logic with clear behavior for each mode
50
+ 5. Added state validation with extensible validateState() method
51
+ 6. Made notifyChange() async to support proper hash comparison
52
+ 7. Updated select() to filter undefined states
package/readme.md CHANGED
@@ -1,12 +1,19 @@
1
1
  # @push.rocks/smartstate
2
- A package for handling and managing state in applications
2
+ A powerful TypeScript library for elegant state management using RxJS and reactive programming patterns
3
3
 
4
4
  ## Install
5
5
 
6
- To install `@push.rocks/smartstate`, you can use pnpm (Performant Node Package Manager). Run the following command in your terminal:
6
+ To install `@push.rocks/smartstate`, you can use pnpm, npm, or yarn:
7
7
 
8
8
  ```bash
9
+ # Using pnpm (recommended)
9
10
  pnpm install @push.rocks/smartstate --save
11
+
12
+ # Using npm
13
+ npm install @push.rocks/smartstate --save
14
+
15
+ # Using yarn
16
+ yarn add @push.rocks/smartstate
10
17
  ```
11
18
 
12
19
  This will add `@push.rocks/smartstate` to your project's dependencies.
@@ -35,10 +42,10 @@ const myAppSmartState = new Smartstate<YourStatePartNamesEnum>();
35
42
 
36
43
  When creating state parts, you can specify different initialization modes:
37
44
 
38
- - **`'soft'`** - Allows existing state parts to remain (default behavior)
39
- - **`'mandatory'`** - Fails if there's an existing state part with the same name
40
- - **`'force'`** - Overwrites any existing state part
41
- - **`'persistent'`** - Enables WebStore persistence using IndexedDB
45
+ - **`'soft'`** (default) - Returns existing state part if it exists, creates new if not
46
+ - **`'mandatory'`** - Requires state part to not exist, fails if it does
47
+ - **`'force'`** - Always creates new state part, overwriting any existing one
48
+ - **`'persistent'`** - Like 'soft' but with WebStore persistence using IndexedDB
42
49
 
43
50
  ### Defining State Parts
44
51
 
@@ -79,6 +86,7 @@ const userStatePart = await myAppSmartState.getStatePart<IUserState>(
79
86
  You can subscribe to changes in a state part to perform actions accordingly:
80
87
 
81
88
  ```typescript
89
+ // The select() method automatically filters out undefined states
82
90
  userStatePart.select().subscribe((currentState) => {
83
91
  console.log(`User Logged In: ${currentState.isLoggedIn}`);
84
92
  });
@@ -134,6 +142,12 @@ Both methods return a Promise with the new state, giving you flexibility in how
134
142
  `StatePart` provides several useful methods for state management:
135
143
 
136
144
  ```typescript
145
+ // Get current state (may be undefined initially)
146
+ const currentState = userStatePart.getState();
147
+ if (currentState) {
148
+ console.log('Current user:', currentState.username);
149
+ }
150
+
137
151
  // Wait for a specific state condition
138
152
  await userStatePart.waitUntilPresent();
139
153
 
@@ -170,13 +184,33 @@ Persistent state automatically:
170
184
  - Restores state on application restart
171
185
  - Manages storage with configurable database and store names
172
186
 
187
+ ### State Validation
188
+
189
+ `Smartstate` includes built-in state validation to ensure data integrity:
190
+
191
+ ```typescript
192
+ // Basic validation (built-in)
193
+ // Ensures state is not null or undefined
194
+ await userStatePart.setState(null); // Throws error: Invalid state structure
195
+
196
+ // Custom validation by extending StatePart
197
+ class ValidatedStatePart<T> extends StatePart<string, T> {
198
+ protected validateState(stateArg: any): stateArg is T {
199
+ // Add your custom validation logic
200
+ return super.validateState(stateArg) && /* your validation */;
201
+ }
202
+ }
203
+ ```
204
+
173
205
  ### Performance Optimization
174
206
 
175
- `Smartstate` includes built-in performance optimizations:
207
+ `Smartstate` includes advanced performance optimizations:
176
208
 
177
- - **State Change Detection**: Detects actual state changes to prevent unnecessary notifications when state values haven't truly changed
209
+ - **Async State Hash Detection**: Uses SHA256 hashing to detect actual state changes, preventing unnecessary notifications when state values haven't truly changed
210
+ - **Duplicate Prevention**: Identical state updates are automatically filtered out
178
211
  - **Cumulative Notifications**: Batch multiple state changes into a single notification using `notifyChangeCumulative()`
179
212
  - **Selective Subscriptions**: Use selectors to subscribe only to specific state properties
213
+ - **Undefined State Filtering**: The `select()` method automatically filters out undefined states
180
214
 
181
215
  ### RxJS Integration
182
216
 
@@ -202,19 +236,75 @@ userStatePart.select(state => state.username)
202
236
  });
203
237
  ```
204
238
 
205
- ### Comprehensive Usage
239
+ ### Complete Example
240
+
241
+ Here's a comprehensive example showcasing the power of `@push.rocks/smartstate`:
242
+
243
+ ```typescript
244
+ import { Smartstate, StatePart, StateAction } from '@push.rocks/smartstate';
245
+
246
+ // Define your state structure
247
+ type AppStateParts = 'user' | 'settings' | 'cart';
248
+
249
+ interface IUserState {
250
+ isLoggedIn: boolean;
251
+ username?: string;
252
+ email?: string;
253
+ }
254
+
255
+ interface ICartState {
256
+ items: Array<{ id: string; quantity: number }>;
257
+ total: number;
258
+ }
259
+
260
+ // Create the smartstate instance
261
+ const appState = new Smartstate<AppStateParts>();
262
+
263
+ // Initialize state parts
264
+ const userState = await appState.getStatePart<IUserState>('user', {
265
+ isLoggedIn: false
266
+ });
267
+
268
+ const cartState = await appState.getStatePart<ICartState>('cart', {
269
+ items: [],
270
+ total: 0
271
+ }, 'persistent'); // Persists across sessions
272
+
273
+ // Create actions
274
+ const loginAction = userState.createAction<{ username: string; email: string }>(
275
+ async (statePart, payload) => {
276
+ // Simulate API call
277
+ await new Promise(resolve => setTimeout(resolve, 1000));
278
+
279
+ return {
280
+ isLoggedIn: true,
281
+ username: payload.username,
282
+ email: payload.email
283
+ };
284
+ }
285
+ );
286
+
287
+ // Subscribe to changes
288
+ userState.select(state => state.isLoggedIn).subscribe(isLoggedIn => {
289
+ console.log('Login status changed:', isLoggedIn);
290
+ });
291
+
292
+ // Dispatch actions
293
+ await loginAction.trigger({ username: 'john', email: 'john@example.com' });
294
+ ```
206
295
 
207
- Putting it all together, `@push.rocks/smartstate` offers a flexible and powerful pattern for managing application state. By modularizing state parts, subscribing to state changes, and controlling state modifications through actions, developers can maintain a clean and scalable architecture. Combining these strategies with persistent states unlocks the full potential for creating dynamic and user-friendly applications.
296
+ ### Key Features
208
297
 
209
- Key features:
210
- - **Type-safe state management** with full TypeScript support
211
- - **Reactive state updates** using RxJS observables
212
- - **Persistent state** with IndexedDB storage
213
- - **Performance optimized** with state hash detection
214
- - **Modular architecture** with separate state parts
215
- - **Action-based updates** for predictable state modifications
298
+ `@push.rocks/smartstate` provides a robust foundation for state management:
216
299
 
217
- For more complex scenarios, consider combining multiple state parts, creating hierarchical state structures, and integrating with other state management solutions as needed. With `@push.rocks/smartstate`, the possibilities are vast, empowering you to tailor the state management approach to fit the unique requirements of your project.
300
+ - **🎯 Type-safe** - Full TypeScript support with intelligent type inference
301
+ - **⚡ Performance optimized** - Async state hash detection prevents unnecessary re-renders
302
+ - **💾 Persistent state** - Built-in IndexedDB support for state persistence
303
+ - **🔄 Reactive** - Powered by RxJS for elegant async handling
304
+ - **🧩 Modular** - Organize state into logical, reusable parts
305
+ - **✅ Validated** - Built-in state validation with extensible validation logic
306
+ - **🎭 Flexible init modes** - Choose how state parts are initialized
307
+ - **📦 Zero config** - Works out of the box with sensible defaults
218
308
 
219
309
  ## License and Legal Information
220
310
 
@@ -13,9 +13,10 @@ export class Smartstate<StatePartNameType extends string> {
13
13
 
14
14
  /**
15
15
  * Allows getting and initializing a new statepart
16
- * initMode === 'soft' it will allow existing stateparts
17
- * initMode === 'mandatory' will fail if there is an existing statepart
18
- * initMode === 'force' will overwrite any existing statepart
16
+ * initMode === 'soft' (default) - returns existing statepart if exists, creates new if not
17
+ * initMode === 'mandatory' - requires statepart to not exist, fails if it does
18
+ * initMode === 'force' - always creates new statepart, overwriting any existing
19
+ * initMode === 'persistent' - like 'soft' but with webstore persistence
19
20
  * @param statePartNameArg
20
21
  * @param initialArg
21
22
  * @param initMode
@@ -23,19 +24,30 @@ export class Smartstate<StatePartNameType extends string> {
23
24
  public async getStatePart<PayloadType>(
24
25
  statePartNameArg: StatePartNameType,
25
26
  initialArg?: PayloadType,
26
- initMode?: TInitMode
27
+ initMode: TInitMode = 'soft'
27
28
  ): Promise<StatePart<StatePartNameType, PayloadType>> {
28
- if (this.statePartMap[statePartNameArg]) {
29
- if (initialArg && (!initMode || initMode !== 'soft')) {
30
- throw new Error(
31
- `${statePartNameArg} already exists, yet you try to set an initial state again`
32
- );
29
+ const existingStatePart = this.statePartMap[statePartNameArg];
30
+
31
+ if (existingStatePart) {
32
+ switch (initMode) {
33
+ case 'mandatory':
34
+ throw new Error(
35
+ `State part '${statePartNameArg}' already exists, but initMode is 'mandatory'`
36
+ );
37
+ case 'force':
38
+ // Force mode: create new state part
39
+ return this.createStatePart<PayloadType>(statePartNameArg, initialArg, initMode);
40
+ case 'soft':
41
+ case 'persistent':
42
+ default:
43
+ // Return existing state part
44
+ return existingStatePart as StatePart<StatePartNameType, PayloadType>;
33
45
  }
34
- return this.statePartMap[statePartNameArg] as StatePart<StatePartNameType, PayloadType>;
35
46
  } else {
47
+ // State part doesn't exist
36
48
  if (!initialArg) {
37
49
  throw new Error(
38
- `${statePartNameArg} does not yet exist, yet you don't provide an initial state`
50
+ `State part '${statePartNameArg}' does not exist and no initial state provided`
39
51
  );
40
52
  }
41
53
  return this.createStatePart<PayloadType>(statePartNameArg, initialArg, initMode);
@@ -46,11 +58,12 @@ export class Smartstate<StatePartNameType extends string> {
46
58
  * Creates a statepart
47
59
  * @param statePartName
48
60
  * @param initialPayloadArg
61
+ * @param initMode
49
62
  */
50
63
  private async createStatePart<PayloadType>(
51
64
  statePartName: StatePartNameType,
52
65
  initialPayloadArg: PayloadType,
53
- initMode?: TInitMode
66
+ initMode: TInitMode = 'soft'
54
67
  ): Promise<StatePart<StatePartNameType, PayloadType>> {
55
68
  const newState = new StatePart<StatePartNameType, PayloadType>(
56
69
  statePartName,
@@ -64,8 +77,8 @@ export class Smartstate<StatePartNameType extends string> {
64
77
  await newState.init();
65
78
  const currentState = newState.getState();
66
79
  await newState.setState({
67
- ...initialPayloadArg,
68
80
  ...currentState,
81
+ ...initialPayloadArg,
69
82
  });
70
83
  this.statePartMap[statePartName] = newState;
71
84
  return newState;