@hardlydifficult/state-tracker 2.0.4 → 2.0.5
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/README.md +62 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hardlydifficult/state-tracker
|
|
2
2
|
|
|
3
|
-
Atomic JSON state persistence with sync
|
|
3
|
+
Atomic JSON state persistence with sync/async APIs, auto-save, and graceful degradation for TypeScript applications.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,11 +8,7 @@ Atomic JSON state persistence with sync and async APIs, auto-save, and graceful
|
|
|
8
8
|
npm install @hardlydifficult/state-tracker
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
### Server context (async with auto-save)
|
|
14
|
-
|
|
15
|
-
For long-running servers, use `loadAsync()` + `autoSaveMs` + `update()`. State degrades gracefully to in-memory if disk is unavailable.
|
|
11
|
+
## Quick Start
|
|
16
12
|
|
|
17
13
|
```typescript
|
|
18
14
|
import { StateTracker } from "@hardlydifficult/state-tracker";
|
|
@@ -38,11 +34,17 @@ store.set({ requestCount: 0, lastActiveAt: new Date().toISOString() }); // full
|
|
|
38
34
|
await store.saveAsync(); // force immediate save
|
|
39
35
|
```
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
## State Persistence
|
|
38
|
+
|
|
39
|
+
The `StateTracker` class provides atomic JSON state persistence using file-based storage with graceful fallback to in-memory mode when disk access fails.
|
|
40
|
+
|
|
41
|
+
### Sync API
|
|
42
42
|
|
|
43
|
-
For tools and scripts, use the
|
|
43
|
+
For tools and scripts, use the synchronous API:
|
|
44
44
|
|
|
45
45
|
```typescript
|
|
46
|
+
import { StateTracker } from "@hardlydifficult/state-tracker";
|
|
47
|
+
|
|
46
48
|
const store = new StateTracker<number>({
|
|
47
49
|
key: "counter",
|
|
48
50
|
default: 0,
|
|
@@ -52,6 +54,23 @@ const count = store.load();
|
|
|
52
54
|
store.save(count + 1);
|
|
53
55
|
```
|
|
54
56
|
|
|
57
|
+
### Async API
|
|
58
|
+
|
|
59
|
+
For long-running servers, use the async API with auto-save:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const store = new StateTracker<AppState>({
|
|
63
|
+
key: "app",
|
|
64
|
+
default: { version: 1 },
|
|
65
|
+
stateDirectory: "/var/state",
|
|
66
|
+
autoSaveMs: 5000,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await store.loadAsync();
|
|
70
|
+
store.set({ version: 2 });
|
|
71
|
+
await store.saveAsync(); // Force immediate save
|
|
72
|
+
```
|
|
73
|
+
|
|
55
74
|
## Options
|
|
56
75
|
|
|
57
76
|
| Option | Type | Description |
|
|
@@ -60,7 +79,7 @@ store.save(count + 1);
|
|
|
60
79
|
| `default` | `T` | Default value when no state file exists (required) |
|
|
61
80
|
| `stateDirectory` | `string` | Directory for state files (default: `$STATE_TRACKER_DIR` or `~/.app-state`) |
|
|
62
81
|
| `autoSaveMs` | `number` | Auto-save interval after `update()`/`set()`/`reset()` (default: 0 = disabled) |
|
|
63
|
-
| `onEvent` | `
|
|
82
|
+
| `onEvent` | `(event: StateTrackerEvent) => void` | Event callback for logging (`{ level, message, context }`) |
|
|
64
83
|
|
|
65
84
|
## Properties
|
|
66
85
|
|
|
@@ -76,16 +95,47 @@ store.save(count + 1);
|
|
|
76
95
|
| `loadAsync()` | Async load with graceful degradation (safe to call multiple times) |
|
|
77
96
|
| `saveAsync()` | Async atomic save (temp file + rename) |
|
|
78
97
|
| `load()` | Sync load |
|
|
79
|
-
| `save(value)` | Sync save |
|
|
98
|
+
| `save(value)` | Sync save (overwrites entire state) |
|
|
80
99
|
| `update(changes)` | Shallow merge for object state, triggers auto-save |
|
|
81
100
|
| `set(newState)` | Replace entire state, triggers auto-save |
|
|
82
101
|
| `reset()` | Restore to defaults, triggers auto-save |
|
|
102
|
+
| `getFilePath()` | Returns the full path to the state file |
|
|
103
|
+
|
|
104
|
+
## Event Handling
|
|
105
|
+
|
|
106
|
+
Events are emitted for key lifecycle operations with configurable logging:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const store = new StateTracker<AppState>({
|
|
110
|
+
key: "app",
|
|
111
|
+
default: { version: 1 },
|
|
112
|
+
onEvent: ({ level, message, context }) => {
|
|
113
|
+
console.log(`[${level}] ${message}`, context);
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Event levels: `"debug"`, `"info"`, `"warn"`, `"error"`
|
|
83
119
|
|
|
84
120
|
## Features
|
|
85
121
|
|
|
86
122
|
- **Type inference** from the default value
|
|
87
123
|
- **Atomic writes** via temp file + rename to prevent corruption
|
|
88
|
-
- **Key sanitization** to prevent path traversal
|
|
124
|
+
- **Key sanitization** to prevent path traversal (alphanumeric, hyphens, underscores only)
|
|
89
125
|
- **Graceful degradation** — runs in-memory when disk is unavailable
|
|
90
126
|
- **Auto-save** — debounced saves after state mutations
|
|
91
|
-
- **Legacy format support** — reads both v1 envelope and raw formats
|
|
127
|
+
- **Legacy format support** — reads both v1 envelope format and raw PersistentStore formats
|
|
128
|
+
|
|
129
|
+
## Legacy Format Migration
|
|
130
|
+
|
|
131
|
+
The library transparently handles migration from legacy formats:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// If disk contains legacy format: { count: 42 }
|
|
135
|
+
// Load merges with defaults: { count: 42, extra: true }
|
|
136
|
+
await store.loadAsync();
|
|
137
|
+
|
|
138
|
+
// Subsequent save writes new envelope format:
|
|
139
|
+
// { value: { count: 42, extra: true }, lastUpdated: "..." }
|
|
140
|
+
await store.saveAsync();
|
|
141
|
+
```
|