@push.rocks/smartstate 2.0.31 → 2.1.0
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.
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/index.d.ts +2 -0
- package/dist_ts/index.js +3 -1
- package/dist_ts/smartstate.classes.computed.d.ts +7 -0
- package/dist_ts/smartstate.classes.computed.js +10 -0
- package/dist_ts/smartstate.classes.smartstate.d.ts +19 -10
- package/dist_ts/smartstate.classes.smartstate.js +44 -17
- package/dist_ts/smartstate.classes.statepart.d.ts +26 -9
- package/dist_ts/smartstate.classes.statepart.js +120 -24
- package/dist_ts/smartstate.contextprovider.d.ts +16 -0
- package/dist_ts/smartstate.contextprovider.js +44 -0
- package/npmextra.json +8 -2
- package/package.json +9 -4
- package/readme.hints.md +47 -30
- package/readme.md +218 -213
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/index.ts +2 -0
- package/ts/smartstate.classes.computed.ts +16 -0
- package/ts/smartstate.classes.smartstate.ts +51 -17
- package/ts/smartstate.classes.statepart.ts +140 -23
- package/ts/smartstate.contextprovider.ts +61 -0
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @push.rocks/smartstate
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A TypeScript-first reactive state management library with middleware, computed state, batching, persistence, and Web Component Context Protocol support 🚀
|
|
4
4
|
|
|
5
5
|
## Issue Reporting and Security
|
|
6
6
|
|
|
@@ -8,306 +8,311 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
|
-
To install `@push.rocks/smartstate`, you can use pnpm, npm, or yarn:
|
|
12
|
-
|
|
13
11
|
```bash
|
|
14
|
-
# Using pnpm (recommended)
|
|
15
12
|
pnpm install @push.rocks/smartstate --save
|
|
13
|
+
```
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
npm install @push.rocks/smartstate --save
|
|
15
|
+
Or with npm:
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
```bash
|
|
18
|
+
npm install @push.rocks/smartstate --save
|
|
22
19
|
```
|
|
23
20
|
|
|
24
21
|
## Usage
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
### Getting Started
|
|
29
|
-
|
|
30
|
-
Import the necessary components from the library:
|
|
23
|
+
### Quick Start
|
|
31
24
|
|
|
32
25
|
```typescript
|
|
33
|
-
import { Smartstate
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### Creating a SmartState Instance
|
|
26
|
+
import { Smartstate } from '@push.rocks/smartstate';
|
|
37
27
|
|
|
38
|
-
|
|
28
|
+
// 1. Define your state part names
|
|
29
|
+
type AppParts = 'user' | 'settings';
|
|
39
30
|
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
```
|
|
31
|
+
// 2. Create the root instance
|
|
32
|
+
const state = new Smartstate<AppParts>();
|
|
43
33
|
|
|
44
|
-
|
|
34
|
+
// 3. Create state parts with initial values
|
|
35
|
+
const userState = await state.getStatePart<{ name: string; loggedIn: boolean }>('user', {
|
|
36
|
+
name: '',
|
|
37
|
+
loggedIn: false,
|
|
38
|
+
});
|
|
45
39
|
|
|
46
|
-
|
|
40
|
+
// 4. Subscribe to changes
|
|
41
|
+
userState.select((s) => s.name).subscribe((name) => {
|
|
42
|
+
console.log('Name changed:', name);
|
|
43
|
+
});
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
| `'mandatory'` | Requires state part to not exist, throws error if it does |
|
|
52
|
-
| `'force'` | Always creates new state part, overwriting any existing one |
|
|
53
|
-
| `'persistent'` | Like 'soft' but with WebStore persistence using IndexedDB |
|
|
45
|
+
// 5. Update state
|
|
46
|
+
await userState.setState({ name: 'Alice', loggedIn: true });
|
|
47
|
+
```
|
|
54
48
|
|
|
55
|
-
###
|
|
49
|
+
### State Parts & Init Modes
|
|
56
50
|
|
|
57
|
-
State parts
|
|
51
|
+
State parts are isolated, typed units of state. Create them with `getStatePart()`:
|
|
58
52
|
|
|
59
53
|
```typescript
|
|
60
|
-
|
|
61
|
-
enum AppStateParts {
|
|
62
|
-
UserState = 'UserState',
|
|
63
|
-
SettingsState = 'SettingsState'
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Option 2: Using string literal types (simpler approach)
|
|
67
|
-
type AppStateParts = 'UserState' | 'SettingsState';
|
|
54
|
+
const part = await state.getStatePart<IMyState>(name, initialState, initMode);
|
|
68
55
|
```
|
|
69
56
|
|
|
70
|
-
|
|
57
|
+
| Init Mode | Behavior |
|
|
58
|
+
|-----------|----------|
|
|
59
|
+
| `'soft'` (default) | Returns existing if found, creates new otherwise |
|
|
60
|
+
| `'mandatory'` | Throws if state part already exists |
|
|
61
|
+
| `'force'` | Always creates new, overwrites existing |
|
|
62
|
+
| `'persistent'` | Like `'soft'` but persists to IndexedDB via WebStore |
|
|
71
63
|
|
|
72
|
-
|
|
73
|
-
interface IUserState {
|
|
74
|
-
isLoggedIn: boolean;
|
|
75
|
-
username?: string;
|
|
76
|
-
}
|
|
64
|
+
#### Persistent State
|
|
77
65
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
'soft' // Init mode (optional, defaults to 'soft')
|
|
82
|
-
);
|
|
66
|
+
```typescript
|
|
67
|
+
const settings = await state.getStatePart('settings', { theme: 'dark' }, 'persistent');
|
|
68
|
+
// Automatically saved to IndexedDB. On next app load, persisted values override defaults.
|
|
83
69
|
```
|
|
84
70
|
|
|
85
|
-
###
|
|
71
|
+
### Selecting State
|
|
86
72
|
|
|
87
|
-
|
|
73
|
+
`select()` returns an RxJS Observable that emits the current value immediately and on every change:
|
|
88
74
|
|
|
89
75
|
```typescript
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
console.log(`User Logged In: ${currentState.isLoggedIn}`);
|
|
93
|
-
});
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Select a specific part of your state with a selector function:
|
|
76
|
+
// Full state
|
|
77
|
+
userState.select().subscribe((state) => console.log(state));
|
|
97
78
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (username) {
|
|
101
|
-
console.log(`Current user: ${username}`);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
79
|
+
// Derived value via selector
|
|
80
|
+
userState.select((s) => s.name).subscribe((name) => console.log(name));
|
|
104
81
|
```
|
|
105
82
|
|
|
106
|
-
|
|
83
|
+
Selectors are **memoized** — calling `select(fn)` with the same function reference returns the same cached Observable, shared across all subscribers.
|
|
84
|
+
|
|
85
|
+
#### AbortSignal Support
|
|
107
86
|
|
|
108
|
-
|
|
87
|
+
Clean up subscriptions without manual `unsubscribe()`:
|
|
109
88
|
|
|
110
89
|
```typescript
|
|
111
|
-
|
|
112
|
-
username: string;
|
|
113
|
-
}
|
|
90
|
+
const controller = new AbortController();
|
|
114
91
|
|
|
115
|
-
|
|
116
|
-
|
|
92
|
+
userState.select((s) => s.name, { signal: controller.signal }).subscribe((name) => {
|
|
93
|
+
console.log(name); // stops receiving when aborted
|
|
117
94
|
});
|
|
118
95
|
|
|
119
|
-
//
|
|
120
|
-
|
|
96
|
+
// Later: clean up
|
|
97
|
+
controller.abort();
|
|
121
98
|
```
|
|
122
99
|
|
|
123
|
-
###
|
|
100
|
+
### Actions
|
|
124
101
|
|
|
125
|
-
|
|
102
|
+
Actions provide controlled, named state mutations:
|
|
126
103
|
|
|
127
104
|
```typescript
|
|
128
|
-
|
|
129
|
-
|
|
105
|
+
const login = userState.createAction<{ name: string }>(async (statePart, payload) => {
|
|
106
|
+
return { ...statePart.getState(), name: payload.name, loggedIn: true };
|
|
107
|
+
});
|
|
130
108
|
|
|
131
|
-
//
|
|
132
|
-
|
|
109
|
+
// Two equivalent ways to dispatch:
|
|
110
|
+
await login.trigger({ name: 'Alice' });
|
|
111
|
+
await userState.dispatchAction(login, { name: 'Alice' });
|
|
133
112
|
```
|
|
134
113
|
|
|
135
|
-
|
|
114
|
+
### Middleware
|
|
136
115
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
`StatePart` provides several useful methods for state management:
|
|
116
|
+
Intercept every `setState()` call to transform, validate, or reject state changes:
|
|
140
117
|
|
|
141
118
|
```typescript
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Wait for state to be present
|
|
149
|
-
await userStatePart.waitUntilPresent();
|
|
150
|
-
|
|
151
|
-
// Wait for a specific property to be present
|
|
152
|
-
await userStatePart.waitUntilPresent(state => state.username);
|
|
119
|
+
// Logging middleware
|
|
120
|
+
userState.addMiddleware((newState, oldState) => {
|
|
121
|
+
console.log('State changing from', oldState, 'to', newState);
|
|
122
|
+
return newState;
|
|
123
|
+
});
|
|
153
124
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
125
|
+
// Validation middleware — throw to reject the change
|
|
126
|
+
userState.addMiddleware((newState) => {
|
|
127
|
+
if (!newState.name) throw new Error('Name is required');
|
|
128
|
+
return newState;
|
|
129
|
+
});
|
|
160
130
|
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return { ...statePart.getState(), ...userData };
|
|
131
|
+
// Transform middleware
|
|
132
|
+
userState.addMiddleware((newState) => {
|
|
133
|
+
return { ...newState, name: newState.name.trim() };
|
|
165
134
|
});
|
|
166
135
|
|
|
167
|
-
//
|
|
168
|
-
|
|
136
|
+
// Removal — addMiddleware returns a dispose function
|
|
137
|
+
const remove = userState.addMiddleware(myMiddleware);
|
|
138
|
+
remove(); // middleware no longer runs
|
|
169
139
|
```
|
|
170
140
|
|
|
171
|
-
|
|
141
|
+
Middleware runs sequentially in insertion order. If any middleware throws, the state is unchanged (atomic).
|
|
172
142
|
|
|
173
|
-
|
|
143
|
+
### Computed / Derived State
|
|
174
144
|
|
|
175
|
-
|
|
176
|
-
const settingsStatePart = await myAppSmartState.getStatePart<ISettingsState>(
|
|
177
|
-
AppStateParts.SettingsState,
|
|
178
|
-
{ theme: 'light' }, // Initial/default state
|
|
179
|
-
'persistent' // Mode
|
|
180
|
-
);
|
|
181
|
-
```
|
|
145
|
+
Derive reactive values from one or more state parts:
|
|
182
146
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
- Restores state on application restart
|
|
186
|
-
- Merges persisted values with defaults (persisted values take precedence)
|
|
187
|
-
- Ensures atomic writes (persistence happens before memory update)
|
|
147
|
+
```typescript
|
|
148
|
+
import { computed } from '@push.rocks/smartstate';
|
|
188
149
|
|
|
189
|
-
|
|
150
|
+
const userState = await state.getStatePart('user', { firstName: 'Jane', lastName: 'Doe' });
|
|
151
|
+
const settingsState = await state.getStatePart('settings', { locale: 'en' });
|
|
190
152
|
|
|
191
|
-
|
|
153
|
+
// Standalone function
|
|
154
|
+
const greeting$ = computed(
|
|
155
|
+
[userState, settingsState],
|
|
156
|
+
(user, settings) => `Hello, ${user.firstName} (${settings.locale})`,
|
|
157
|
+
);
|
|
192
158
|
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
await userStatePart.setState(null); // Throws error: Invalid state structure
|
|
159
|
+
greeting$.subscribe((msg) => console.log(msg));
|
|
160
|
+
// => "Hello, Jane (en)"
|
|
196
161
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
protected validateState(stateArg: any): stateArg is T {
|
|
200
|
-
return super.validateState(stateArg) && /* your validation */;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
162
|
+
// Also available as a method on Smartstate:
|
|
163
|
+
const greeting2$ = state.computed([userState, settingsState], (user, settings) => /* ... */);
|
|
203
164
|
```
|
|
204
165
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
`Smartstate` includes advanced performance optimizations:
|
|
166
|
+
Computed observables are **lazy** — they only subscribe to sources when someone subscribes to them.
|
|
208
167
|
|
|
209
|
-
|
|
210
|
-
- **🚫 Duplicate Prevention**: Identical state updates are automatically filtered out
|
|
211
|
-
- **📦 Cumulative Notifications**: Batch multiple state changes into a single notification using `notifyChangeCumulative()` with automatic debouncing
|
|
212
|
-
- **🎯 Selective Subscriptions**: Use selectors to subscribe only to specific state properties
|
|
213
|
-
- **✨ Undefined State Filtering**: The `select()` method automatically filters out undefined states
|
|
214
|
-
- **⚡ Concurrent Access Safety**: Prevents race conditions when multiple calls request the same state part simultaneously
|
|
168
|
+
### Batch Updates
|
|
215
169
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
`Smartstate` leverages RxJS for reactive state management:
|
|
170
|
+
Update multiple state parts without intermediate notifications:
|
|
219
171
|
|
|
220
172
|
```typescript
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
173
|
+
const partA = await state.getStatePart('a', { value: 1 });
|
|
174
|
+
const partB = await state.getStatePart('b', { value: 2 });
|
|
175
|
+
|
|
176
|
+
// Subscribers see no updates during the batch — only after it completes
|
|
177
|
+
await state.batch(async () => {
|
|
178
|
+
await partA.setState({ value: 10 });
|
|
179
|
+
await partB.setState({ value: 20 });
|
|
180
|
+
// Notifications are deferred here
|
|
227
181
|
});
|
|
182
|
+
// Both subscribers now fire with their new values
|
|
228
183
|
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
)
|
|
235
|
-
.subscribe(username => {
|
|
236
|
-
console.log('Username changed:', username);
|
|
184
|
+
// Nested batches are supported — flush happens at the outermost level
|
|
185
|
+
await state.batch(async () => {
|
|
186
|
+
await partA.setState({ value: 100 });
|
|
187
|
+
await state.batch(async () => {
|
|
188
|
+
await partB.setState({ value: 200 });
|
|
237
189
|
});
|
|
190
|
+
// Still deferred
|
|
191
|
+
});
|
|
192
|
+
// Now both fire
|
|
238
193
|
```
|
|
239
194
|
|
|
240
|
-
###
|
|
195
|
+
### Waiting for State
|
|
241
196
|
|
|
242
|
-
|
|
197
|
+
Wait for a specific state condition to be met:
|
|
243
198
|
|
|
244
199
|
```typescript
|
|
245
|
-
|
|
200
|
+
// Wait for any truthy state
|
|
201
|
+
const currentState = await userState.waitUntilPresent();
|
|
246
202
|
|
|
247
|
-
//
|
|
248
|
-
|
|
203
|
+
// Wait for a specific condition
|
|
204
|
+
const name = await userState.waitUntilPresent((s) => s.name || undefined);
|
|
249
205
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
username?: string;
|
|
253
|
-
email?: string;
|
|
254
|
-
}
|
|
206
|
+
// With timeout (backward compatible)
|
|
207
|
+
const name = await userState.waitUntilPresent((s) => s.name || undefined, 5000);
|
|
255
208
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
209
|
+
// With AbortSignal
|
|
210
|
+
const controller = new AbortController();
|
|
211
|
+
try {
|
|
212
|
+
const name = await userState.waitUntilPresent(
|
|
213
|
+
(s) => s.name || undefined,
|
|
214
|
+
{ timeoutMs: 5000, signal: controller.signal },
|
|
215
|
+
);
|
|
216
|
+
} catch (e) {
|
|
217
|
+
// 'Aborted' or timeout error
|
|
259
218
|
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Context Protocol Bridge (Web Components)
|
|
260
222
|
|
|
261
|
-
|
|
262
|
-
|
|
223
|
+
Expose state parts to web components via the [W3C Context Protocol](https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md):
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { attachContextProvider } from '@push.rocks/smartstate';
|
|
263
227
|
|
|
264
|
-
//
|
|
265
|
-
const
|
|
266
|
-
|
|
228
|
+
// Define a context key
|
|
229
|
+
const themeContext = Symbol('theme');
|
|
230
|
+
|
|
231
|
+
// Attach a provider to a DOM element
|
|
232
|
+
const cleanup = attachContextProvider(myElement, {
|
|
233
|
+
context: themeContext,
|
|
234
|
+
statePart: settingsState,
|
|
235
|
+
selectorFn: (s) => s.theme, // optional: provide derived value
|
|
267
236
|
});
|
|
268
237
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return {
|
|
281
|
-
isLoggedIn: true,
|
|
282
|
-
username: payload.username,
|
|
283
|
-
email: payload.email
|
|
284
|
-
};
|
|
285
|
-
}
|
|
238
|
+
// Any descendant can request this context:
|
|
239
|
+
myElement.dispatchEvent(
|
|
240
|
+
new CustomEvent('context-request', {
|
|
241
|
+
bubbles: true,
|
|
242
|
+
composed: true,
|
|
243
|
+
detail: {
|
|
244
|
+
context: themeContext,
|
|
245
|
+
callback: (theme) => console.log('Theme:', theme),
|
|
246
|
+
subscribe: true, // receive updates on state changes
|
|
247
|
+
},
|
|
248
|
+
}),
|
|
286
249
|
);
|
|
287
250
|
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
});
|
|
251
|
+
// Cleanup when done
|
|
252
|
+
cleanup();
|
|
253
|
+
```
|
|
292
254
|
|
|
293
|
-
|
|
294
|
-
|
|
255
|
+
This works with Lit's `@consume()` decorator, FAST, or any framework implementing the Context Protocol.
|
|
256
|
+
|
|
257
|
+
### State Validation
|
|
258
|
+
|
|
259
|
+
Built-in null/undefined validation. Extend for custom rules:
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
class ValidatedPart<T> extends StatePart<string, T> {
|
|
263
|
+
protected validateState(stateArg: any): stateArg is T {
|
|
264
|
+
return super.validateState(stateArg) && typeof stateArg.name === 'string';
|
|
265
|
+
}
|
|
266
|
+
}
|
|
295
267
|
```
|
|
296
268
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
|
269
|
+
### Performance Features
|
|
270
|
+
|
|
271
|
+
- **SHA256 Change Detection** — identical state values don't trigger notifications, even with different object references
|
|
272
|
+
- **Selector Memoization** — `select(fn)` caches observables by function reference, sharing one upstream subscription across all subscribers
|
|
273
|
+
- **Cumulative Notifications** — `notifyChangeCumulative()` debounces rapid changes into a single notification
|
|
274
|
+
- **Concurrent Safety** — simultaneous `getStatePart()` calls for the same name return the same promise, preventing duplicate creation
|
|
275
|
+
- **Atomic Persistence** — WebStore writes complete before in-memory state updates, ensuring consistency
|
|
276
|
+
- **Batch Deferred Notifications** — `batch()` suppresses all notifications until the batch completes
|
|
277
|
+
|
|
278
|
+
## API Reference
|
|
279
|
+
|
|
280
|
+
### `Smartstate<T>`
|
|
281
|
+
|
|
282
|
+
| Method | Description |
|
|
283
|
+
|--------|-------------|
|
|
284
|
+
| `getStatePart(name, initial?, initMode?)` | Get or create a state part |
|
|
285
|
+
| `batch(fn)` | Batch updates, defer notifications |
|
|
286
|
+
| `computed(sources, fn)` | Create computed observable |
|
|
287
|
+
| `isBatching` | Whether a batch is active |
|
|
288
|
+
|
|
289
|
+
### `StatePart<TName, TPayload>`
|
|
290
|
+
|
|
291
|
+
| Method | Description |
|
|
292
|
+
|--------|-------------|
|
|
293
|
+
| `getState()` | Get current state (or undefined) |
|
|
294
|
+
| `setState(newState)` | Set state (runs middleware, validates, persists, notifies) |
|
|
295
|
+
| `select(selectorFn?, options?)` | Subscribe to state changes |
|
|
296
|
+
| `createAction(actionDef)` | Create a named action |
|
|
297
|
+
| `dispatchAction(action, payload)` | Dispatch an action |
|
|
298
|
+
| `addMiddleware(fn)` | Add middleware, returns removal function |
|
|
299
|
+
| `waitUntilPresent(selectorFn?, options?)` | Wait for state condition |
|
|
300
|
+
| `notifyChange()` | Manually trigger notification |
|
|
301
|
+
| `notifyChangeCumulative()` | Debounced notification |
|
|
302
|
+
| `stateSetup(fn)` | Async state initialization |
|
|
303
|
+
|
|
304
|
+
### `StateAction<TState, TPayload>`
|
|
305
|
+
|
|
306
|
+
| Method | Description |
|
|
307
|
+
|--------|-------------|
|
|
308
|
+
| `trigger(payload)` | Dispatch the action |
|
|
309
|
+
|
|
310
|
+
### Standalone Functions
|
|
311
|
+
|
|
312
|
+
| Function | Description |
|
|
313
|
+
|----------|-------------|
|
|
314
|
+
| `computed(sources, fn)` | Create computed observable from state parts |
|
|
315
|
+
| `attachContextProvider(element, options)` | Bridge state to Context Protocol |
|
|
311
316
|
|
|
312
317
|
## License and Legal Information
|
|
313
318
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartstate',
|
|
6
|
-
version: '2.0
|
|
7
|
-
description: 'A
|
|
6
|
+
version: '2.1.0',
|
|
7
|
+
description: 'A TypeScript-first reactive state management library with middleware, computed state, batching, persistence, and Web Component Context Protocol support.'
|
|
8
8
|
}
|
package/ts/index.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as plugins from './smartstate.plugins.js';
|
|
2
|
+
import { combineLatest, map } from 'rxjs';
|
|
3
|
+
import type { StatePart } from './smartstate.classes.statepart.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* creates a computed observable derived from multiple state parts.
|
|
7
|
+
* the observable is lazy — it only subscribes to sources when subscribed to.
|
|
8
|
+
*/
|
|
9
|
+
export function computed<TResult>(
|
|
10
|
+
sources: StatePart<any, any>[],
|
|
11
|
+
computeFn: (...states: any[]) => TResult,
|
|
12
|
+
): plugins.smartrx.rxjs.Observable<TResult> {
|
|
13
|
+
return combineLatest(sources.map((sp) => sp.select())).pipe(
|
|
14
|
+
map((states) => computeFn(...states)),
|
|
15
|
+
) as plugins.smartrx.rxjs.Observable<TResult>;
|
|
16
|
+
}
|