Compare commits

..

11 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
a0189921a5 2.0.19 2024-10-02 17:49:56 +02:00
b409f1aa55 fix(dependencies): Update dependencies to latest versions 2024-10-02 17:49:56 +02:00
12 changed files with 8812 additions and 4101 deletions

View File

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

View File

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

View File

@@ -1,5 +1,52 @@
# Changelog # 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
- Updated @git.zone/tsbuild to version ^2.1.84
- Updated @git.zone/tsbundle to version ^2.0.15
- Updated @git.zone/tsrun to version ^1.2.49
- Updated @git.zone/tstest to version ^1.0.90
- Updated @push.rocks/tapbundle to version ^5.3.0
- Updated @types/node to version ^22.7.4
- Updated @push.rocks/lik to version ^6.0.15
- Updated @push.rocks/smartjson to version ^5.0.20
- Updated @push.rocks/smartpromise to version ^4.0.4
- Updated @push.rocks/smartrx to version ^3.0.7
- Updated @push.rocks/webstore to version ^2.0.20
## 2024-10-02 - 2.0.18 - fix(core) ## 2024-10-02 - 2.0.18 - fix(core)
Fix type errors and typos in Smartstate class Fix type errors and typos in Smartstate class

View File

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

12636
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 # @push.rocks/smartstate
a package that handles state in a good way A package for handling and managing state in applications
## Install ## 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 ```bash
npm install @push.rocks/smartstate --save pnpm install @push.rocks/smartstate --save
``` ```
This will add `@push.rocks/smartstate` to your project's dependencies. 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>(); 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 ### 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. 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 ```typescript
// Option 1: Using enums
enum AppStateParts { enum AppStateParts {
UserState, UserState = 'UserState',
SettingsState 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: Now, let's create a state part within our `myAppSmartState` instance:
@@ -54,8 +67,11 @@ interface IUserState {
const userStatePart = await myAppSmartState.getStatePart<IUserState>( const userStatePart = await myAppSmartState.getStatePart<IUserState>(
AppStateParts.UserState, 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 ### Subscribing to State Changes
@@ -93,27 +109,110 @@ const loginUserAction = userStatePart.createAction<ILoginPayload>(async (statePa
// Dispatch the action to update the state // Dispatch the action to update the state
loginUserAction.trigger({ username: 'johnDoe' }); 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 ```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, AppStateParts.SettingsState,
{ theme: 'light' }, // Initial state { theme: 'light' }, // Initial state
'persistent' // Mode '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 ### 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. 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. 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 = { export const commitinfo = {
name: '@push.rocks/smartstate', name: '@push.rocks/smartstate',
version: '2.0.18', version: '2.0.23',
description: 'A package for handling and managing state in applications.' 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 actionDef: IActionDef<TStateType, TActionPayloadType>
) {} ) {}
public trigger(payload: TActionPayloadType) { public trigger(payload: TActionPayloadType): Promise<TStateType> {
this.statePartRef.dispatchAction(this, payload); return this.statePartRef.dispatchAction(this, payload);
} }
} }

View File

@@ -61,7 +61,7 @@ export class StatePart<TStatePartName, TStatePayload> {
*/ */
public notifyChange() { public notifyChange() {
const createStateHash = (stateArg: any) => { const createStateHash = (stateArg: any) => {
return plugins.isohash.sha256FromString(plugins.smartjson.stringify(stateArg)); return plugins.smarthashWeb.sha256FromString(plugins.smartjson.stringify(stateArg));
}; };
if ( if (
this.stateStore && 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 smartjson from '@push.rocks/smartjson';
import * as smartpromise from '@push.rocks/smartpromise'; import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrx from '@push.rocks/smartrx'; import * as smartrx from '@push.rocks/smartrx';
import * as webstore from '@push.rocks/webstore'; import * as webstore from '@push.rocks/webstore';
export { isohash, smartjson, smartpromise, smartrx, webstore }; export { smarthashWeb, smartjson, smartpromise, smartrx, webstore };