Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
09fc53aaff | ||
|
bcb58dd012 | ||
|
f0064bd94b | ||
|
e9c527a9dc | ||
|
a47d8bb3c7 | ||
|
aa6766ef36 | ||
|
b0442e1227 |
@@ -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
|
||||||
|
@@ -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
|
||||||
|
24
changelog.md
24
changelog.md
@@ -1,5 +1,29 @@
|
|||||||
# 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)
|
## 2025-06-19 - 2.0.20 - fix(smartstate)
|
||||||
Update build scripts and dependency versions; replace isohash with smarthashWeb for state hash generation
|
Update build scripts and dependency versions; replace isohash with smarthashWeb for state hash generation
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartstate",
|
"name": "@push.rocks/smartstate",
|
||||||
"version": "2.0.20",
|
"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",
|
||||||
|
4
pnpm-workspace.yaml
Normal file
4
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
|
- mongodb-memory-server
|
||||||
|
- puppeteer
|
@@ -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
123
readme.md
@@ -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.
|
||||||
|
|
||||||
|
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartstate',
|
name: '@push.rocks/smartstate',
|
||||||
version: '2.0.20',
|
version: '2.0.23',
|
||||||
description: 'A package for handling and managing state in applications.'
|
description: 'A package for handling and managing state in applications.'
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user