Compare commits

...

9 Commits

Author SHA1 Message Date
Juergen Kunz
09fc53aaff fix(core): Multiple fixes and improvements for version 2.0.24
Some checks failed
Default (tags) / security (push) Successful in 43s
Default (tags) / test (push) Successful in 1m1s
Default (tags) / release (push) Failing after 58s
Default (tags) / metadata (push) Successful in 1m9s
2025-07-19 08:19:59 +00:00
Juergen Kunz
bcb58dd012 chore(workspace): Add pnpm workspace configuration for built-only dependencies 2025-07-19 08:16:36 +00:00
Juergen Kunz
f0064bd94b 2.0.23
Some checks failed
Default (tags) / security (push) Successful in 46s
Default (tags) / test (push) Successful in 51s
Default (tags) / release (push) Failing after 45s
Default (tags) / metadata (push) Successful in 55s
2025-07-19 07:30:55 +00:00
Juergen Kunz
e9c527a9dc fix(ci): Update CI workflows to use new container registry and npmci package name 2025-07-19 07:30:55 +00:00
Juergen Kunz
a47d8bb3c7 fix(smartstate): Fix StateAction trigger method to properly return Promise
Some checks failed
Default (tags) / security (push) Failing after 27s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-07-19 07:18:53 +00:00
Juergen Kunz
aa6766ef36 2.0.21 2025-07-01 06:50:35 +00:00
Juergen Kunz
b0442e1227 update readme 2025-07-01 06:50:15 +00:00
Juergen Kunz
0f1eb6eb27 2.0.20 2025-06-19 23:57:56 +00:00
Juergen Kunz
dd18ef94bd fix(smartstate): Update build scripts and dependency versions; replace isohash with smarthashWeb for state hash generation 2025-06-19 23:57:56 +00:00
12 changed files with 5104 additions and 1608 deletions

View File

@@ -6,8 +6,8 @@ on:
- '**'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@code.foss.global/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
@@ -26,7 +26,7 @@ jobs:
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
pnpm install -g @ship.zone/npmci
- name: Run npm prepare
run: npmci npm prepare

View File

@@ -6,8 +6,8 @@ on:
- '*'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@code.foss.global/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
@@ -26,7 +26,7 @@ jobs:
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Audit production dependencies
@@ -54,7 +54,7 @@ jobs:
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Test stable
@@ -82,7 +82,7 @@ jobs:
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Release
@@ -104,7 +104,7 @@ jobs:
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Code quality

View File

@@ -1,5 +1,37 @@
# Changelog
## 2025-07-19 - 2.0.24 - fix(core)
Multiple fixes and improvements
- Fixed StateAction trigger method to properly return Promise<TStateType>
- Updated CI workflows to use new container registry and npmci package name
- Added pnpm workspace configuration for built-only dependencies
## 2025-07-19 - 2.0.23 - fix(ci)
Update CI workflows to use new container registry and npmci package name
- Changed CI image from 'registry.gitlab.com/hosttoday/ht-docker-node:npmci' to 'code.foss.global/host.today/ht-docker-node:npmci'
- Replaced npmci installation command from '@shipzone/npmci' to '@ship.zone/npmci' in workflow configurations
## 2025-07-19 - 2.0.22 - fix(smartstate)
Fix StateAction trigger method to properly return Promise
- Fixed StateAction.trigger() to return Promise<TStateType> as expected
- Updated readme with improved documentation and examples
- Replaced outdated legal information with Task Venture Capital GmbH details
- Added implementation notes in readme.hints.md
## 2025-06-19 - 2.0.21 - maintenance
General updates and improvements
## 2025-06-19 - 2.0.20 - fix(smartstate)
Update build scripts and dependency versions; replace isohash with smarthashWeb for state hash generation
- Adjusted package.json scripts to include verbose testing and modified build command
- Bumped development dependencies (tsbuild, tsbundle, tsrun, tstest, tapbundle) to newer versions
- Updated production dependencies (lik, smarthash, smartpromise, smartrx) with minor version bumps
- Replaced import of isohash with smarthashWeb in state hash generation, ensuring consistency across modules
## 2024-10-02 - 2.0.19 - fix(dependencies)
Update dependencies to latest versions

View File

@@ -1,6 +1,6 @@
{
"name": "@push.rocks/smartstate",
"version": "2.0.19",
"version": "2.0.24",
"private": false,
"description": "A package for handling and managing state in applications.",
"main": "dist_ts/index.js",
@@ -9,24 +9,24 @@
"author": "Lossless GmbH",
"license": "MIT",
"scripts": {
"test": "(tstest test/)",
"build": "(tsbuild --web --allowimplicitany && tsbundle npm)",
"test": "(tstest test/ --verbose)",
"build": "(tsbuild tsfolders --allowimplicitany && tsbundle npm)",
"buildDocs": "tsdoc"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.1.84",
"@git.zone/tsbundle": "^2.0.15",
"@git.zone/tsrun": "^1.2.49",
"@git.zone/tstest": "^1.0.90",
"@push.rocks/tapbundle": "^5.3.0",
"@git.zone/tsbuild": "^2.6.4",
"@git.zone/tsbundle": "^2.4.0",
"@git.zone/tsrun": "^1.3.3",
"@git.zone/tstest": "^2.3.1",
"@push.rocks/tapbundle": "^6.0.3",
"@types/node": "^22.7.4"
},
"dependencies": {
"@push.rocks/isohash": "^2.0.1",
"@push.rocks/lik": "^6.0.15",
"@push.rocks/lik": "^6.2.2",
"@push.rocks/smarthash": "^3.2.0",
"@push.rocks/smartjson": "^5.0.20",
"@push.rocks/smartpromise": "^4.0.4",
"@push.rocks/smartrx": "^3.0.7",
"@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrx": "^3.0.10",
"@push.rocks/webstore": "^2.0.20"
},
"files": [
@@ -60,5 +60,6 @@
"repository": {
"type": "git",
"url": "https://code.foss.global/push.rocks/smartstate.git"
}
},
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
}

6456
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,4 @@
onlyBuiltDependencies:
- esbuild
- mongodb-memory-server
- puppeteer

View File

@@ -1 +1,39 @@
# Smartstate Implementation Notes
## Current API (as of analysis)
### State Part Initialization
- State parts can be created with different init modes: 'soft', 'mandatory', 'force', 'persistent'
- Persistent mode automatically calls init() internally - no need to call it manually
- WebStore integration for persistent state uses IndexedDB
### Actions
- Actions are created with `createAction()` method
- Two ways to dispatch actions:
1. `stateAction.trigger(payload)` - returns Promise<TStatePayload>
2. `await statePart.dispatchAction(stateAction, payload)` - returns Promise<TStatePayload>
- Both methods now return the same Promise, providing flexibility in usage
### State Management Methods
- `select()` - returns Observable with startWith current state
- `waitUntilPresent()` - waits for specific state condition
- `stateSetup()` - async state initialization with cumulative defer
- `notifyChangeCumulative()` - defers notification to end of call stack (no callback parameter)
### State Hash Detection
- Uses SHA256 hash to detect actual state changes
- Bug: Currently stores the state object itself as hash instead of the actual hash
- This prevents proper duplicate notification prevention
### Type System
- Can use either enums or string literal types for state part names
- Test uses simple string types: `type TMyStateParts = 'testStatePart'`
## Fixed Issues in Documentation
1. Updated trigger() to return Promise (API enhancement)
2. Added dispatchAction as alternative method
3. Corrected notifyChangeCumulative usage
4. Clarified persistent mode auto-init
5. Added stateSetup documentation
6. Fixed state hash detection description
7. Both trigger() and dispatchAction() now return Promise for consistency

123
readme.md
View File

@@ -1,12 +1,12 @@
# @push.rocks/smartstate
a package that handles state in a good way
A package for handling and managing state in applications
## Install
To install `@push.rocks/smartstate`, you can use npm (Node Package Manager). Run the following command in your terminal:
To install `@push.rocks/smartstate`, you can use pnpm (Performant Node Package Manager). Run the following command in your terminal:
```bash
npm install @push.rocks/smartstate --save
pnpm install @push.rocks/smartstate --save
```
This will add `@push.rocks/smartstate` to your project's dependencies.
@@ -31,17 +31,30 @@ import { Smartstate, StatePart, StateAction } from '@push.rocks/smartstate';
const myAppSmartState = new Smartstate<YourStatePartNamesEnum>();
```
### Understanding Init Modes
When creating state parts, you can specify different initialization modes:
- **`'soft'`** - Allows existing state parts to remain (default behavior)
- **`'mandatory'`** - Fails if there's an existing state part with the same name
- **`'force'`** - Overwrites any existing state part
- **`'persistent'`** - Enables WebStore persistence using IndexedDB
### Defining State Parts
State parts represent separable sections of your state, making it easier to manage and modularize. For example, you may have a state part for user data and another for application settings.
Define an enum for state part names for better management:
Define state part names using either enums or string literal types:
```typescript
// Option 1: Using enums
enum AppStateParts {
UserState,
SettingsState
UserState = 'UserState',
SettingsState = 'SettingsState'
}
// Option 2: Using string literal types (simpler approach)
type AppStateParts = 'UserState' | 'SettingsState';
```
Now, let's create a state part within our `myAppSmartState` instance:
@@ -54,8 +67,11 @@ interface IUserState {
const userStatePart = await myAppSmartState.getStatePart<IUserState>(
AppStateParts.UserState,
{ isLoggedIn: false } // Initial state
{ isLoggedIn: false }, // Initial state
'soft' // Init mode (optional, defaults to 'soft')
);
// Note: Persistent state parts are automatically initialized internally
```
### Subscribing to State Changes
@@ -93,27 +109,110 @@ const loginUserAction = userStatePart.createAction<ILoginPayload>(async (statePa
// Dispatch the action to update the state
loginUserAction.trigger({ username: 'johnDoe' });
// or await the result
const newState = await loginUserAction.trigger({ username: 'johnDoe' });
```
### Persistent State
### Dispatching Actions
`Smartstate` supports the concept of persistent states, where you can maintain state across sessions. To utilize this, specify a persistent mode when getting a state part:
There are two ways to dispatch actions:
```typescript
const settingsStatePart = await myAppSmartState.getStatePart<AppStateParts, ISettingsState>(
// Method 1: Using trigger on the action (returns promise)
const newState = await loginUserAction.trigger({ username: 'johnDoe' });
// or fire and forget
loginUserAction.trigger({ username: 'johnDoe' });
// Method 2: Using dispatchAction on the state part (returns promise)
const newState = await userStatePart.dispatchAction(loginUserAction, { username: 'johnDoe' });
```
Both methods return a Promise with the new state, giving you flexibility in how you handle the result.
### Additional State Methods
`StatePart` provides several useful methods for state management:
```typescript
// Wait for a specific state condition
await userStatePart.waitUntilPresent();
// Wait for a specific property to be present
await userStatePart.waitUntilPresent(state => state.username);
// Setup initial state with async operations
await userStatePart.stateSetup(async (statePart) => {
// Perform async initialization
const userData = await fetchUserData();
return { ...statePart.getState(), ...userData };
});
// Defer notification to end of call stack
userStatePart.notifyChangeCumulative();
```
### Persistent State with WebStore
`Smartstate` supports persistent states using WebStore (IndexedDB-based storage), allowing you to maintain state across sessions:
```typescript
const settingsStatePart = await myAppSmartState.getStatePart<ISettingsState>(
AppStateParts.SettingsState,
{ theme: 'light' }, // Initial state
'persistent' // Mode
);
// Note: init() is called automatically for persistent mode
```
This mode ensures that the state is saved and can be reloaded even after the application restarts, providing a seamless user experience.
Persistent state automatically:
- Saves state changes to IndexedDB
- Restores state on application restart
- Manages storage with configurable database and store names
### Performance Optimization
`Smartstate` includes built-in performance optimizations:
- **State Change Detection**: Detects actual state changes to prevent unnecessary notifications when state values haven't truly changed
- **Cumulative Notifications**: Batch multiple state changes into a single notification using `notifyChangeCumulative()`
- **Selective Subscriptions**: Use selectors to subscribe only to specific state properties
### RxJS Integration
`Smartstate` leverages RxJS for reactive state management:
```typescript
// State is exposed as an RxJS Subject
const stateObservable = userStatePart.select();
// Automatically starts with current state value
stateObservable.subscribe((state) => {
console.log('Current state:', state);
});
// Use selectors for specific properties
userStatePart.select(state => state.username)
.pipe(
distinctUntilChanged(),
filter(username => username !== undefined)
)
.subscribe(username => {
console.log('Username changed:', username);
});
```
### Comprehensive Usage
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.
Remember to leverage TypeScript for its excellent support for types and interfaces, enhancing your development experience with type checking and IntelliSense, ensuring a more reliable and maintainable codebase.
Key features:
- **Type-safe state management** with full TypeScript support
- **Reactive state updates** using RxJS observables
- **Persistent state** with IndexedDB storage
- **Performance optimized** with state hash detection
- **Modular architecture** with separate state parts
- **Action-based updates** for predictable state modifications
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.

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartstate',
version: '2.0.19',
version: '2.0.23',
description: 'A package for handling and managing state in applications.'
}

View File

@@ -14,7 +14,7 @@ export class StateAction<TStateType, TActionPayloadType> {
public actionDef: IActionDef<TStateType, TActionPayloadType>
) {}
public trigger(payload: TActionPayloadType) {
this.statePartRef.dispatchAction(this, payload);
public trigger(payload: TActionPayloadType): Promise<TStateType> {
return this.statePartRef.dispatchAction(this, payload);
}
}

View File

@@ -61,7 +61,7 @@ export class StatePart<TStatePartName, TStatePayload> {
*/
public notifyChange() {
const createStateHash = (stateArg: any) => {
return plugins.isohash.sha256FromString(plugins.smartjson.stringify(stateArg));
return plugins.smarthashWeb.sha256FromString(plugins.smartjson.stringify(stateArg));
};
if (
this.stateStore &&

View File

@@ -1,7 +1,7 @@
import * as isohash from '@push.rocks/isohash';
import * as smarthashWeb from '@push.rocks/smarthash/web';
import * as smartjson from '@push.rocks/smartjson';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrx from '@push.rocks/smartrx';
import * as webstore from '@push.rocks/webstore';
export { isohash, smartjson, smartpromise, smartrx, webstore };
export { smarthashWeb, smartjson, smartpromise, smartrx, webstore };