@hardlydifficult/state-tracker 1.0.0 → 2.0.1
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 +66 -12
- package/dist/StateTracker.d.ts +50 -0
- package/dist/StateTracker.d.ts.map +1 -1
- package/dist/StateTracker.js +196 -2
- package/dist/StateTracker.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hardlydifficult/state-tracker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Atomic JSON state persistence with sync and async APIs, auto-save, and graceful degradation.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,30 +8,84 @@ File-based state persistence for recovery across restarts.
|
|
|
8
8
|
npm install @hardlydifficult/state-tracker
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Usage
|
|
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.
|
|
12
16
|
|
|
13
17
|
```typescript
|
|
14
|
-
import { StateTracker } from
|
|
18
|
+
import { StateTracker } from "@hardlydifficult/state-tracker";
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
interface AppState {
|
|
21
|
+
requestCount: number;
|
|
22
|
+
lastActiveAt: string;
|
|
23
|
+
}
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
const store = new StateTracker<AppState>({
|
|
26
|
+
key: "my-service",
|
|
27
|
+
default: { requestCount: 0, lastActiveAt: "" },
|
|
28
|
+
stateDirectory: "/var/data",
|
|
29
|
+
autoSaveMs: 5000,
|
|
30
|
+
onEvent: ({ level, message }) => console.log(`[${level}] ${message}`),
|
|
31
|
+
});
|
|
20
32
|
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
await store.loadAsync();
|
|
34
|
+
|
|
35
|
+
store.state.requestCount; // read current state
|
|
36
|
+
store.update({ requestCount: store.state.requestCount + 1 }); // partial update
|
|
37
|
+
store.set({ requestCount: 0, lastActiveAt: new Date().toISOString() }); // full replace
|
|
38
|
+
await store.saveAsync(); // force immediate save
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Simple context (sync)
|
|
42
|
+
|
|
43
|
+
For tools and scripts, use the sync `load()`/`save()` API.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const store = new StateTracker<number>({
|
|
47
|
+
key: "counter",
|
|
48
|
+
default: 0,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const count = store.load();
|
|
52
|
+
store.save(count + 1);
|
|
23
53
|
```
|
|
24
54
|
|
|
25
55
|
## Options
|
|
26
56
|
|
|
27
|
-
| Option | Description |
|
|
57
|
+
| Option | Type | Description |
|
|
58
|
+
|--------|------|-------------|
|
|
59
|
+
| `key` | `string` | Unique identifier for the state file (required) |
|
|
60
|
+
| `default` | `T` | Default value when no state file exists (required) |
|
|
61
|
+
| `stateDirectory` | `string` | Directory for state files (default: `$STATE_TRACKER_DIR` or `~/.app-state`) |
|
|
62
|
+
| `autoSaveMs` | `number` | Auto-save interval after `update()`/`set()`/`reset()` (default: 0 = disabled) |
|
|
63
|
+
| `onEvent` | `function` | Event callback for logging (`{ level, message, context }`) |
|
|
64
|
+
|
|
65
|
+
## Properties
|
|
66
|
+
|
|
67
|
+
| Property | Type | Description |
|
|
68
|
+
|----------|------|-------------|
|
|
69
|
+
| `state` | `Readonly<T>` | Current in-memory state |
|
|
70
|
+
| `isPersistent` | `boolean` | Whether disk storage is available |
|
|
71
|
+
|
|
72
|
+
## Methods
|
|
73
|
+
|
|
74
|
+
| Method | Description |
|
|
28
75
|
|--------|-------------|
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
76
|
+
| `loadAsync()` | Async load with graceful degradation (safe to call multiple times) |
|
|
77
|
+
| `saveAsync()` | Async atomic save (temp file + rename) |
|
|
78
|
+
| `load()` | Sync load |
|
|
79
|
+
| `save(value)` | Sync save |
|
|
80
|
+
| `update(changes)` | Shallow merge for object state, triggers auto-save |
|
|
81
|
+
| `set(newState)` | Replace entire state, triggers auto-save |
|
|
82
|
+
| `reset()` | Restore to defaults, triggers auto-save |
|
|
32
83
|
|
|
33
84
|
## Features
|
|
34
85
|
|
|
35
86
|
- **Type inference** from the default value
|
|
36
87
|
- **Atomic writes** via temp file + rename to prevent corruption
|
|
37
88
|
- **Key sanitization** to prevent path traversal
|
|
89
|
+
- **Graceful degradation** — runs in-memory when disk is unavailable
|
|
90
|
+
- **Auto-save** — debounced saves after state mutations
|
|
91
|
+
- **Legacy format support** — reads both v1 envelope and raw formats
|
package/dist/StateTracker.d.ts
CHANGED
|
@@ -1,16 +1,66 @@
|
|
|
1
|
+
export type StateTrackerEventLevel = "debug" | "info" | "warn" | "error";
|
|
2
|
+
export interface StateTrackerEvent {
|
|
3
|
+
level: StateTrackerEventLevel;
|
|
4
|
+
message: string;
|
|
5
|
+
context?: Record<string, unknown>;
|
|
6
|
+
}
|
|
1
7
|
export interface StateTrackerOptions<T> {
|
|
2
8
|
key: string;
|
|
3
9
|
default: T;
|
|
4
10
|
stateDirectory?: string;
|
|
11
|
+
autoSaveMs?: number;
|
|
12
|
+
onEvent?: (event: StateTrackerEvent) => void;
|
|
5
13
|
}
|
|
14
|
+
/** Atomic JSON state persistence with file-based storage, auto-save, and graceful degradation to in-memory mode. */
|
|
6
15
|
export declare class StateTracker<T> {
|
|
7
16
|
private readonly filePath;
|
|
8
17
|
private readonly defaultValue;
|
|
18
|
+
private readonly autoSaveMs;
|
|
19
|
+
private readonly onEvent?;
|
|
20
|
+
private _state;
|
|
21
|
+
private _loaded;
|
|
22
|
+
private _storageAvailable;
|
|
23
|
+
private saveTimer;
|
|
9
24
|
private static sanitizeKey;
|
|
10
25
|
private static getDefaultStateDirectory;
|
|
11
26
|
constructor(options: StateTrackerOptions<T>);
|
|
27
|
+
/** Read-only getter for cached in-memory state */
|
|
28
|
+
get state(): Readonly<T>;
|
|
29
|
+
/** Whether storage is working */
|
|
30
|
+
get isPersistent(): boolean;
|
|
31
|
+
private emit;
|
|
32
|
+
/**
|
|
33
|
+
* Extract value from parsed JSON content.
|
|
34
|
+
* Handles v1 envelope format ({value, lastUpdated}) and legacy
|
|
35
|
+
* PersistentStore format (raw T without envelope, merged with defaults).
|
|
36
|
+
*/
|
|
37
|
+
private extractValue;
|
|
38
|
+
/** Sync load — v1 compatible. Also updates internal state cache. */
|
|
12
39
|
load(): T;
|
|
40
|
+
/** Sync save — v1 compatible. Also updates internal state cache. */
|
|
13
41
|
save(value: T): void;
|
|
42
|
+
/**
|
|
43
|
+
* Async load with graceful degradation.
|
|
44
|
+
* Sets _storageAvailable false on failure instead of throwing.
|
|
45
|
+
* Safe to call multiple times (subsequent calls are no-ops).
|
|
46
|
+
*/
|
|
47
|
+
loadAsync(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Async atomic save (temp file + rename).
|
|
50
|
+
* Cancels any pending auto-save before writing.
|
|
51
|
+
*/
|
|
52
|
+
saveAsync(): Promise<void>;
|
|
53
|
+
/** Replace entire state, schedules auto-save. */
|
|
54
|
+
set(newState: T): void;
|
|
55
|
+
/**
|
|
56
|
+
* Shallow merge for object types, schedules auto-save.
|
|
57
|
+
* Throws at runtime if T is not an object.
|
|
58
|
+
*/
|
|
59
|
+
update(changes: Partial<T>): void;
|
|
60
|
+
/** Restore to defaults, schedules auto-save. */
|
|
61
|
+
reset(): void;
|
|
14
62
|
getFilePath(): string;
|
|
63
|
+
private scheduleSave;
|
|
64
|
+
private cancelPendingSave;
|
|
15
65
|
}
|
|
16
66
|
//# sourceMappingURL=StateTracker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StateTracker.d.ts","sourceRoot":"","sources":["../src/StateTracker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"StateTracker.d.ts","sourceRoot":"","sources":["../src/StateTracker.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEzE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,sBAAsB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,CAAC,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC9C;AAED,oHAAoH;AACpH,qBAAa,YAAY,CAAC,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAI;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAqC;IAE9D,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,SAAS,CAA8C;IAE/D,OAAO,CAAC,MAAM,CAAC,WAAW;IAa1B,OAAO,CAAC,MAAM,CAAC,wBAAwB;gBAQ3B,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAe3C,kDAAkD;IAClD,IAAI,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,CAEvB;IAED,iCAAiC;IACjC,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,OAAO,CAAC,IAAI;IAUZ;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAkCpB,oEAAoE;IACpE,IAAI,IAAI,CAAC;IA6BT,oEAAoE;IACpE,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAYpB;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAoChC;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BhC,iDAAiD;IACjD,GAAG,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI;IAKtB;;;OAGG;IACH,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAcjC,gDAAgD;IAChD,KAAK,IAAI,IAAI;IAKb,WAAW,IAAI,MAAM;IAIrB,OAAO,CAAC,YAAY;IA8BpB,OAAO,CAAC,iBAAiB;CAM1B"}
|
package/dist/StateTracker.js
CHANGED
|
@@ -35,11 +35,19 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.StateTracker = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
|
+
const fsPromises = __importStar(require("fs/promises"));
|
|
38
39
|
const os = __importStar(require("os"));
|
|
39
40
|
const path = __importStar(require("path"));
|
|
41
|
+
/** Atomic JSON state persistence with file-based storage, auto-save, and graceful degradation to in-memory mode. */
|
|
40
42
|
class StateTracker {
|
|
41
43
|
filePath;
|
|
42
44
|
defaultValue;
|
|
45
|
+
autoSaveMs;
|
|
46
|
+
onEvent;
|
|
47
|
+
_state;
|
|
48
|
+
_loaded = false;
|
|
49
|
+
_storageAvailable = false;
|
|
50
|
+
saveTimer = null;
|
|
43
51
|
static sanitizeKey(key) {
|
|
44
52
|
const trimmed = key.trim();
|
|
45
53
|
if (trimmed === "") {
|
|
@@ -59,31 +67,95 @@ class StateTracker {
|
|
|
59
67
|
}
|
|
60
68
|
constructor(options) {
|
|
61
69
|
const sanitizedKey = StateTracker.sanitizeKey(options.key);
|
|
62
|
-
this.defaultValue = options.default;
|
|
70
|
+
this.defaultValue = structuredClone(options.default);
|
|
71
|
+
this._state = structuredClone(options.default);
|
|
72
|
+
this.autoSaveMs = options.autoSaveMs ?? 0;
|
|
73
|
+
this.onEvent = options.onEvent;
|
|
63
74
|
const stateDirectory = options.stateDirectory ?? StateTracker.getDefaultStateDirectory();
|
|
64
75
|
if (!fs.existsSync(stateDirectory)) {
|
|
65
76
|
fs.mkdirSync(stateDirectory, { recursive: true });
|
|
66
77
|
}
|
|
67
78
|
this.filePath = path.join(stateDirectory, `${sanitizedKey}.json`);
|
|
68
79
|
}
|
|
80
|
+
/** Read-only getter for cached in-memory state */
|
|
81
|
+
get state() {
|
|
82
|
+
return this._state;
|
|
83
|
+
}
|
|
84
|
+
/** Whether storage is working */
|
|
85
|
+
get isPersistent() {
|
|
86
|
+
return this._storageAvailable;
|
|
87
|
+
}
|
|
88
|
+
emit(level, message, context) {
|
|
89
|
+
if (this.onEvent) {
|
|
90
|
+
this.onEvent({ level, message, context });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Extract value from parsed JSON content.
|
|
95
|
+
* Handles v1 envelope format ({value, lastUpdated}) and legacy
|
|
96
|
+
* PersistentStore format (raw T without envelope, merged with defaults).
|
|
97
|
+
*/
|
|
98
|
+
extractValue(parsed) {
|
|
99
|
+
if (parsed !== null &&
|
|
100
|
+
typeof parsed === "object" &&
|
|
101
|
+
!Array.isArray(parsed) &&
|
|
102
|
+
"value" in parsed) {
|
|
103
|
+
// v1 envelope format: { value, lastUpdated }
|
|
104
|
+
const envelope = parsed;
|
|
105
|
+
if (envelope.value === undefined) {
|
|
106
|
+
return structuredClone(this.defaultValue);
|
|
107
|
+
}
|
|
108
|
+
return envelope.value;
|
|
109
|
+
}
|
|
110
|
+
// Legacy PersistentStore format: raw T without envelope
|
|
111
|
+
// Only merge if T is an object type
|
|
112
|
+
if (parsed !== null &&
|
|
113
|
+
typeof parsed === "object" &&
|
|
114
|
+
typeof this.defaultValue === "object" &&
|
|
115
|
+
this.defaultValue !== null &&
|
|
116
|
+
!Array.isArray(parsed)) {
|
|
117
|
+
return {
|
|
118
|
+
...structuredClone(this.defaultValue),
|
|
119
|
+
...parsed,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Fallback to defaults
|
|
123
|
+
return structuredClone(this.defaultValue);
|
|
124
|
+
}
|
|
125
|
+
/** Sync load — v1 compatible. Also updates internal state cache. */
|
|
69
126
|
load() {
|
|
70
127
|
if (!fs.existsSync(this.filePath)) {
|
|
71
|
-
|
|
128
|
+
this._state = structuredClone(this.defaultValue);
|
|
129
|
+
this._loaded = true;
|
|
130
|
+
this._storageAvailable = true;
|
|
131
|
+
return this._state;
|
|
72
132
|
}
|
|
73
133
|
try {
|
|
74
134
|
const data = fs.readFileSync(this.filePath, "utf-8");
|
|
75
135
|
const state = JSON.parse(data);
|
|
76
136
|
const { value } = state;
|
|
77
137
|
if (value === undefined) {
|
|
138
|
+
this._state = structuredClone(this.defaultValue);
|
|
139
|
+
this._loaded = true;
|
|
140
|
+
this._storageAvailable = true;
|
|
78
141
|
return this.defaultValue;
|
|
79
142
|
}
|
|
143
|
+
this._state = structuredClone(value);
|
|
144
|
+
this._loaded = true;
|
|
145
|
+
this._storageAvailable = true;
|
|
80
146
|
return value;
|
|
81
147
|
}
|
|
82
148
|
catch {
|
|
149
|
+
this._state = structuredClone(this.defaultValue);
|
|
150
|
+
this._loaded = true;
|
|
151
|
+
this._storageAvailable = true;
|
|
83
152
|
return this.defaultValue;
|
|
84
153
|
}
|
|
85
154
|
}
|
|
155
|
+
/** Sync save — v1 compatible. Also updates internal state cache. */
|
|
86
156
|
save(value) {
|
|
157
|
+
this.cancelPendingSave();
|
|
158
|
+
this._state = structuredClone(value);
|
|
87
159
|
const state = {
|
|
88
160
|
value,
|
|
89
161
|
lastUpdated: new Date().toISOString(),
|
|
@@ -92,9 +164,131 @@ class StateTracker {
|
|
|
92
164
|
fs.writeFileSync(tempFilePath, JSON.stringify(state, null, 2), "utf-8");
|
|
93
165
|
fs.renameSync(tempFilePath, this.filePath);
|
|
94
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Async load with graceful degradation.
|
|
169
|
+
* Sets _storageAvailable false on failure instead of throwing.
|
|
170
|
+
* Safe to call multiple times (subsequent calls are no-ops).
|
|
171
|
+
*/
|
|
172
|
+
async loadAsync() {
|
|
173
|
+
if (this._loaded) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
this._loaded = true;
|
|
177
|
+
try {
|
|
178
|
+
const dir = path.dirname(this.filePath);
|
|
179
|
+
if (!fs.existsSync(dir)) {
|
|
180
|
+
this.emit("info", "Creating storage directory", { dir });
|
|
181
|
+
await fsPromises.mkdir(dir, { recursive: true });
|
|
182
|
+
}
|
|
183
|
+
if (fs.existsSync(this.filePath)) {
|
|
184
|
+
const data = await fsPromises.readFile(this.filePath, "utf-8");
|
|
185
|
+
const parsed = JSON.parse(data);
|
|
186
|
+
this._state = this.extractValue(parsed);
|
|
187
|
+
this._storageAvailable = true;
|
|
188
|
+
this.emit("info", "Loaded state from disk", { path: this.filePath });
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
this._storageAvailable = true;
|
|
192
|
+
this.emit("info", "No existing state file, using defaults", {
|
|
193
|
+
path: this.filePath,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
this._storageAvailable = false;
|
|
199
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
200
|
+
this.emit("warn", "Storage unavailable, running in-memory", {
|
|
201
|
+
error: errorMessage,
|
|
202
|
+
path: this.filePath,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Async atomic save (temp file + rename).
|
|
208
|
+
* Cancels any pending auto-save before writing.
|
|
209
|
+
*/
|
|
210
|
+
async saveAsync() {
|
|
211
|
+
this.cancelPendingSave();
|
|
212
|
+
if (!this._storageAvailable) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const state = {
|
|
217
|
+
value: this._state,
|
|
218
|
+
lastUpdated: new Date().toISOString(),
|
|
219
|
+
};
|
|
220
|
+
const tempFilePath = `${this.filePath}.tmp`;
|
|
221
|
+
await fsPromises.writeFile(tempFilePath, JSON.stringify(state, null, 2), "utf-8");
|
|
222
|
+
await fsPromises.rename(tempFilePath, this.filePath);
|
|
223
|
+
this.emit("debug", "Saved state to disk", { path: this.filePath });
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
227
|
+
this.emit("error", "Failed to save state", {
|
|
228
|
+
error: errorMessage,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/** Replace entire state, schedules auto-save. */
|
|
233
|
+
set(newState) {
|
|
234
|
+
this._state = structuredClone(newState);
|
|
235
|
+
this.scheduleSave();
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Shallow merge for object types, schedules auto-save.
|
|
239
|
+
* Throws at runtime if T is not an object.
|
|
240
|
+
*/
|
|
241
|
+
update(changes) {
|
|
242
|
+
if (this._state === null ||
|
|
243
|
+
typeof this._state !== "object" ||
|
|
244
|
+
Array.isArray(this._state)) {
|
|
245
|
+
throw new Error("update() can only be used when state is a non-array object");
|
|
246
|
+
}
|
|
247
|
+
this._state = { ...this._state, ...changes };
|
|
248
|
+
this.scheduleSave();
|
|
249
|
+
}
|
|
250
|
+
/** Restore to defaults, schedules auto-save. */
|
|
251
|
+
reset() {
|
|
252
|
+
this._state = structuredClone(this.defaultValue);
|
|
253
|
+
this.scheduleSave();
|
|
254
|
+
}
|
|
95
255
|
getFilePath() {
|
|
96
256
|
return this.filePath;
|
|
97
257
|
}
|
|
258
|
+
scheduleSave() {
|
|
259
|
+
if (this.autoSaveMs <= 0 || !this._storageAvailable) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
this.cancelPendingSave();
|
|
263
|
+
this.saveTimer = setTimeout(() => {
|
|
264
|
+
this.saveTimer = null;
|
|
265
|
+
// Use sync save to avoid issues with untracked promises
|
|
266
|
+
try {
|
|
267
|
+
const state = {
|
|
268
|
+
value: this._state,
|
|
269
|
+
lastUpdated: new Date().toISOString(),
|
|
270
|
+
};
|
|
271
|
+
const tempFilePath = `${this.filePath}.tmp`;
|
|
272
|
+
fs.writeFileSync(tempFilePath, JSON.stringify(state, null, 2), "utf-8");
|
|
273
|
+
fs.renameSync(tempFilePath, this.filePath);
|
|
274
|
+
this.emit("debug", "Auto-saved state to disk", {
|
|
275
|
+
path: this.filePath,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
280
|
+
this.emit("error", "Failed to auto-save state", {
|
|
281
|
+
error: errorMessage,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}, this.autoSaveMs);
|
|
285
|
+
}
|
|
286
|
+
cancelPendingSave() {
|
|
287
|
+
if (this.saveTimer) {
|
|
288
|
+
clearTimeout(this.saveTimer);
|
|
289
|
+
this.saveTimer = null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
98
292
|
}
|
|
99
293
|
exports.StateTracker = StateTracker;
|
|
100
294
|
//# sourceMappingURL=StateTracker.js.map
|
package/dist/StateTracker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StateTracker.js","sourceRoot":"","sources":["../src/StateTracker.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AACzB,uCAAyB;AACzB,2CAA6B;
|
|
1
|
+
{"version":3,"file":"StateTracker.js","sourceRoot":"","sources":["../src/StateTracker.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { StateTracker, type StateTrackerOptions } from "./StateTracker";
|
|
1
|
+
export { StateTracker, type StateTrackerOptions, type StateTrackerEvent, type StateTrackerEventLevel, } from "./StateTracker";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+CAKwB;AAJtB,4GAAA,YAAY,OAAA"}
|