@furystack/filesystem-store 7.0.34 → 7.0.36
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/CHANGELOG.md +23 -0
- package/esm/filesystem-store.d.ts +0 -1
- package/esm/filesystem-store.d.ts.map +1 -1
- package/esm/filesystem-store.js +13 -36
- package/esm/filesystem-store.js.map +1 -1
- package/esm/filesystem-store.spec.js +29 -29
- package/esm/filesystem-store.spec.js.map +1 -1
- package/package.json +6 -7
- package/src/filesystem-store.spec.ts +37 -32
- package/src/filesystem-store.ts +14 -34
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [7.0.36] - 2026-02-11
|
|
4
|
+
|
|
5
|
+
### ♻️ Refactoring
|
|
6
|
+
|
|
7
|
+
- Removed semaphore-based file locking from all store operations (`get`, `add`, `find`, `count`, `remove`, `update`, `saveChanges`, `reloadData`). Operations now delegate directly to the in-memory store without lock wrapping.
|
|
8
|
+
|
|
9
|
+
### 🧪 Tests
|
|
10
|
+
|
|
11
|
+
- Wrapped `FileSystemStore` instances in `usingAsync()` to ensure cleanup runs even when assertions fail
|
|
12
|
+
|
|
13
|
+
### ⬆️ Dependencies
|
|
14
|
+
|
|
15
|
+
- Bump `vitest` from `^4.0.17` to `^4.0.18`
|
|
16
|
+
- Bump `@types/node` from `^25.0.10` to `^25.2.3`
|
|
17
|
+
- Removed `semaphore-async-await` dependency
|
|
18
|
+
|
|
19
|
+
## [7.0.35] - 2026-02-09
|
|
20
|
+
|
|
21
|
+
### ⬆️ Dependencies
|
|
22
|
+
|
|
23
|
+
- Updated `@furystack/core` dependency
|
|
24
|
+
- Updated `@furystack/*` dependencies
|
|
25
|
+
|
|
3
26
|
## [7.0.34] - 2026-01-26
|
|
4
27
|
|
|
5
28
|
### 🔧 Chores
|
|
@@ -30,7 +30,6 @@ export declare class FileSystemStore<T, TPrimaryKey extends keyof T> extends Eve
|
|
|
30
30
|
add(...entries: T[]): Promise<import("@furystack/core").CreateResult<T>>;
|
|
31
31
|
find<TFields extends Array<keyof T>>(filter: FindOptions<T, TFields>): Promise<import("@furystack/core").PartialResult<T, TFields>[]>;
|
|
32
32
|
count(filter?: FilterType<T>): Promise<number>;
|
|
33
|
-
private fileLock;
|
|
34
33
|
saveChanges(): Promise<void>;
|
|
35
34
|
[Symbol.asyncDispose](): Promise<void>;
|
|
36
35
|
reloadData(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem-store.d.ts","sourceRoot":"","sources":["../src/filesystem-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAE7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE3C,OAAO,EAAE,QAAQ,EAAS,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"filesystem-store.d.ts","sourceRoot":"","sources":["../src/filesystem-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAE7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE3C,OAAO,EAAE,QAAQ,EAAS,MAAM,IAAI,CAAA;AAIpC;;GAEG;AACH,qBAAa,eAAe,CAAC,CAAC,EAAE,WAAW,SAAS,MAAM,CAAC,CACzD,SAAQ,QAAQ,CAAC;IACf,aAAa,EAAE;QAAE,MAAM,EAAE,CAAC,CAAA;KAAE,CAAA;IAC5B,eAAe,EAAE;QAAE,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;KAAE,CAAA;IAC3D,eAAe,EAAE;QAAE,GAAG,EAAE,CAAC,CAAC,WAAW,CAAC,CAAA;KAAE,CAAA;CACzC,CACD,YAAW,aAAa,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;IAmFzC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAjF1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAW;IAEpC,SAAgB,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;IAEvC,SAAgB,UAAU,EAAE,WAAW,CAAA;IAEvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA+B;IAE7D,OAAO,KAAK,KAAK,GAEhB;IAEY,MAAM,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3D,IAAI,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;IACpC,UAAU,UAAQ;IACZ,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAIhD,GAAG,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE;IAMnB,IAAI,CAAC,OAAO,SAAS,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,EAAE,OAAO,CAAC;IAIpE,KAAK,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;IAI5B,WAAW;IAYX,CAAC,MAAM,CAAC,YAAY,CAAC;IAOrB,UAAU;IAgBV,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC;IAKxC,QAAQ,2BAAoB;IAC5B,SAAS,4BAAqB;gBAGlB,OAAO,EAAE;QACxB,QAAQ,EAAE,MAAM,CAAA;QAChB,UAAU,EAAE,WAAW,CAAA;QACvB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;KACxB;CA2BJ"}
|
package/esm/filesystem-store.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InMemoryStore } from '@furystack/core';
|
|
2
2
|
import { EventHub } from '@furystack/utils';
|
|
3
3
|
import { promises, watch } from 'fs';
|
|
4
|
-
|
|
4
|
+
const DEFAULT_TICK_MS = 3000;
|
|
5
5
|
/**
|
|
6
6
|
* Store implementation that stores info in a simple JSON file
|
|
7
7
|
*/
|
|
@@ -15,52 +15,35 @@ export class FileSystemStore extends EventHub {
|
|
|
15
15
|
return this.inMemoryStore.cache;
|
|
16
16
|
}
|
|
17
17
|
async remove(...keys) {
|
|
18
|
-
await this.
|
|
19
|
-
await this.inMemoryStore.remove(...keys);
|
|
20
|
-
});
|
|
18
|
+
await this.inMemoryStore.remove(...keys);
|
|
21
19
|
this.hasChanges = true;
|
|
22
20
|
}
|
|
23
21
|
tick;
|
|
24
22
|
hasChanges = false;
|
|
25
23
|
async get(key, select) {
|
|
26
|
-
return await this.
|
|
27
|
-
return await this.inMemoryStore.get(key, select);
|
|
28
|
-
});
|
|
24
|
+
return await this.inMemoryStore.get(key, select);
|
|
29
25
|
}
|
|
30
26
|
async add(...entries) {
|
|
31
|
-
const result = await this.
|
|
32
|
-
return await this.inMemoryStore.add(...entries);
|
|
33
|
-
});
|
|
27
|
+
const result = await this.inMemoryStore.add(...entries);
|
|
34
28
|
this.hasChanges = true;
|
|
35
29
|
return result;
|
|
36
30
|
}
|
|
37
31
|
async find(filter) {
|
|
38
|
-
return
|
|
39
|
-
return this.inMemoryStore.find(filter);
|
|
40
|
-
});
|
|
32
|
+
return this.inMemoryStore.find(filter);
|
|
41
33
|
}
|
|
42
34
|
async count(filter) {
|
|
43
|
-
return
|
|
44
|
-
return this.inMemoryStore.count(filter);
|
|
45
|
-
});
|
|
35
|
+
return this.inMemoryStore.count(filter);
|
|
46
36
|
}
|
|
47
|
-
fileLock = new Lock();
|
|
48
37
|
async saveChanges() {
|
|
49
38
|
if (!this.hasChanges) {
|
|
50
39
|
return;
|
|
51
40
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
for (const key of this.cache.keys()) {
|
|
56
|
-
values.push(this.cache.get(key));
|
|
57
|
-
}
|
|
58
|
-
await this.writeFile(this.options.fileName, JSON.stringify(values));
|
|
59
|
-
this.hasChanges = false;
|
|
60
|
-
}
|
|
61
|
-
finally {
|
|
62
|
-
this.fileLock.release();
|
|
41
|
+
const values = [];
|
|
42
|
+
for (const key of this.cache.keys()) {
|
|
43
|
+
values.push(this.cache.get(key));
|
|
63
44
|
}
|
|
45
|
+
await this.writeFile(this.options.fileName, JSON.stringify(values));
|
|
46
|
+
this.hasChanges = false;
|
|
64
47
|
}
|
|
65
48
|
async [Symbol.asyncDispose]() {
|
|
66
49
|
await this.saveChanges();
|
|
@@ -70,7 +53,6 @@ export class FileSystemStore extends EventHub {
|
|
|
70
53
|
}
|
|
71
54
|
async reloadData() {
|
|
72
55
|
try {
|
|
73
|
-
await this.fileLock.acquire();
|
|
74
56
|
const data = await this.readFile(this.options.fileName);
|
|
75
57
|
const json = data ? JSON.parse(data.toString()) : [];
|
|
76
58
|
this.cache.clear();
|
|
@@ -84,14 +66,9 @@ export class FileSystemStore extends EventHub {
|
|
|
84
66
|
throw err;
|
|
85
67
|
}
|
|
86
68
|
}
|
|
87
|
-
finally {
|
|
88
|
-
this.fileLock.release();
|
|
89
|
-
}
|
|
90
69
|
}
|
|
91
70
|
async update(id, data) {
|
|
92
|
-
await this.
|
|
93
|
-
return this.inMemoryStore.update(id, data);
|
|
94
|
-
});
|
|
71
|
+
await this.inMemoryStore.update(id, data);
|
|
95
72
|
this.hasChanges = true;
|
|
96
73
|
}
|
|
97
74
|
readFile = promises.readFile;
|
|
@@ -102,7 +79,7 @@ export class FileSystemStore extends EventHub {
|
|
|
102
79
|
this.primaryKey = options.primaryKey;
|
|
103
80
|
this.model = options.model;
|
|
104
81
|
this.inMemoryStore = new InMemoryStore({ model: this.model, primaryKey: this.primaryKey });
|
|
105
|
-
this.tick = setInterval(() => void this.saveChanges(), this.options.tickMs ||
|
|
82
|
+
this.tick = setInterval(() => void this.saveChanges(), this.options.tickMs || DEFAULT_TICK_MS);
|
|
106
83
|
this.inMemoryStore.subscribe('onEntityAdded', ({ entity }) => {
|
|
107
84
|
this.emit('onEntityAdded', { entity });
|
|
108
85
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem-store.js","sourceRoot":"","sources":["../src/filesystem-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAE/C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE3C,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"filesystem-store.js","sourceRoot":"","sources":["../src/filesystem-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAE/C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE3C,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,IAAI,CAAA;AAEpC,MAAM,eAAe,GAAG,IAAI,CAAA;AAE5B;;GAEG;AACH,MAAM,OAAO,eACX,SAAQ,QAIN;IAoFiB;IAjFF,OAAO,CAAY;IAEpB,KAAK,CAAkB;IAEvB,UAAU,CAAa;IAEtB,aAAa,CAA+B;IAE7D,IAAY,KAAK;QACf,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,CAAA;IACjC,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,GAAG,IAA2B;QAChD,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAA;QACxC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;IACxB,CAAC;IAEM,IAAI,CAAgC;IACpC,UAAU,GAAG,KAAK,CAAA;IAClB,KAAK,CAAC,GAAG,CAAC,GAAmB,EAAE,MAAuB;QAC3D,OAAO,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAClD,CAAC;IAEM,KAAK,CAAC,GAAG,CAAC,GAAG,OAAY;QAC9B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAA;QACvD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACtB,OAAO,MAAM,CAAA;IACf,CAAC;IAEM,KAAK,CAAC,IAAI,CAAiC,MAA+B;QAC/E,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACxC,CAAC;IAEM,KAAK,CAAC,KAAK,CAAC,MAAsB;QACvC,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IACzC,CAAC;IAEM,KAAK,CAAC,WAAW;QACtB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,OAAM;QACR,CAAC;QACD,MAAM,MAAM,GAAQ,EAAE,CAAA;QACtB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAM,CAAC,CAAA;QACvC,CAAC;QACD,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAA;QACnE,IAAI,CAAC,UAAU,GAAG,KAAK,CAAA;IACzB,CAAC;IAEM,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC;QAChC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAA;QACxB,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAA;QACrB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACxB,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;IACzB,CAAC;IAEM,KAAK,CAAC,UAAU;QACrB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YACvD,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAS,CAAC,CAAC,CAAC,EAAE,CAAA;YAC7D,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;YAClB,KAAK,MAAM,MAAM,IAAI,IAAI,EAAE,CAAC;gBAC1B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,CAAA;YACjD,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,gCAAgC;YAChC,IAAI,GAAG,YAAY,KAAK,IAAK,GAAyB,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACzE,MAAM,GAAG,CAAA;YACX,CAAC;QACH,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,EAAkB,EAAE,IAAO;QAC7C,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;QACzC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;IACxB,CAAC;IAEM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAA;IAC5B,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAA;IAErC,YACmB,OAKhB;QAED,KAAK,EAAE,CAAA;QAPU,YAAO,GAAP,OAAO,CAKvB;QAGD,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAA;QACpC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;QAC1B,IAAI,CAAC,aAAa,GAAG,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAA;QAC1F,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,eAAe,CAAC,CAAA;QAE9F,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,eAAe,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE;YAC3D,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;QACxC,CAAC,CAAC,CAAA;QACF,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,iBAAiB,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;YACjE,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;QAC9C,CAAC,CAAC,CAAA;QACF,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,iBAAiB,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;YAC1D,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAA;QACvC,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC;YACH,KAAK,IAAI,CAAC,UAAU,EAAE,CAAA;YACtB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,GAAG,EAAE;gBACvE,KAAK,IAAI,CAAC,UAAU,EAAE,CAAA;YACxB,CAAC,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,+EAA+E;QACjF,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StoreManager } from '@furystack/core';
|
|
2
2
|
import { TestClass, createStoreTest } from '@furystack/core/create-physical-store-tests';
|
|
3
|
-
import { sleepAsync } from '@furystack/utils';
|
|
3
|
+
import { sleepAsync, usingAsync } from '@furystack/utils';
|
|
4
4
|
import { promises } from 'fs';
|
|
5
5
|
import { afterAll, describe, expect, it, vi } from 'vitest';
|
|
6
6
|
import { FileSystemStore } from './filesystem-store.js';
|
|
@@ -26,40 +26,40 @@ describe('FileSystemStore', () => {
|
|
|
26
26
|
it('Should save data on tick', async () => {
|
|
27
27
|
const fileName = `filestore-test-${storeCount++}.json`;
|
|
28
28
|
storeNames.push(fileName);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
await usingAsync(new FileSystemStore({ model: TestClass, fileName, primaryKey: 'id', tickMs: 500 }), async (store) => {
|
|
30
|
+
store.saveChanges = vi.fn(store.saveChanges.bind(store));
|
|
31
|
+
await store.add({
|
|
32
|
+
id: 1,
|
|
33
|
+
dateValue: new Date(),
|
|
34
|
+
stringValue1: 'alma',
|
|
35
|
+
stringValue2: 'korte',
|
|
36
|
+
booleanValue: false,
|
|
37
|
+
numberValue1: 1,
|
|
38
|
+
numberValue2: 2,
|
|
39
|
+
});
|
|
40
|
+
await sleepAsync(501);
|
|
41
|
+
expect(store.saveChanges).toHaveBeenCalled();
|
|
39
42
|
});
|
|
40
|
-
await sleepAsync(501);
|
|
41
|
-
expect(store.saveChanges).toHaveBeenCalled();
|
|
42
|
-
await store[Symbol.asyncDispose]();
|
|
43
43
|
});
|
|
44
44
|
it('Should reload data from disk', async () => {
|
|
45
45
|
const fileName = `filestore-test-${storeCount++}.json`;
|
|
46
46
|
storeNames.push(fileName);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
await usingAsync(new FileSystemStore({ model: TestClass, fileName, primaryKey: 'id', tickMs: 500 }), async (store) => {
|
|
48
|
+
await store.add({
|
|
49
|
+
id: 1,
|
|
50
|
+
dateValue: new Date(),
|
|
51
|
+
stringValue1: 'alma',
|
|
52
|
+
stringValue2: 'korte',
|
|
53
|
+
booleanValue: false,
|
|
54
|
+
numberValue1: 1,
|
|
55
|
+
numberValue2: 2,
|
|
56
|
+
});
|
|
57
|
+
await store.saveChanges();
|
|
58
|
+
await store.remove(1);
|
|
59
|
+
await store.reloadData();
|
|
60
|
+
const count = await store.count();
|
|
61
|
+
expect(count).toBe(1);
|
|
56
62
|
});
|
|
57
|
-
await store.saveChanges();
|
|
58
|
-
await store.remove(1);
|
|
59
|
-
await store.reloadData();
|
|
60
|
-
const count = await store.count();
|
|
61
|
-
expect(count).toBe(1);
|
|
62
|
-
await store[Symbol.asyncDispose]();
|
|
63
63
|
});
|
|
64
64
|
afterAll(async () => {
|
|
65
65
|
for (const fileName of storeNames) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem-store.spec.js","sourceRoot":"","sources":["../src/filesystem-store.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAA;AAExF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"filesystem-store.spec.js","sourceRoot":"","sources":["../src/filesystem-store.spec.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furystack/filesystem-store",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.36",
|
|
4
4
|
"description": "Simple File System store implementation for FuryStack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -38,15 +38,14 @@
|
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://github.com/furystack/furystack",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@furystack/core": "^15.0.
|
|
42
|
-
"@furystack/inject": "^12.0.
|
|
43
|
-
"@furystack/utils": "^8.1.
|
|
44
|
-
"semaphore-async-await": "^1.5.1"
|
|
41
|
+
"@furystack/core": "^15.0.36",
|
|
42
|
+
"@furystack/inject": "^12.0.30",
|
|
43
|
+
"@furystack/utils": "^8.1.10"
|
|
45
44
|
},
|
|
46
45
|
"devDependencies": {
|
|
47
|
-
"@types/node": "^25.
|
|
46
|
+
"@types/node": "^25.2.3",
|
|
48
47
|
"typescript": "^5.9.3",
|
|
49
|
-
"vitest": "^4.0.
|
|
48
|
+
"vitest": "^4.0.18"
|
|
50
49
|
},
|
|
51
50
|
"engines": {
|
|
52
51
|
"node": ">=22.0.0"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StoreManager } from '@furystack/core'
|
|
2
2
|
import { TestClass, createStoreTest } from '@furystack/core/create-physical-store-tests'
|
|
3
3
|
import type { Injector } from '@furystack/inject'
|
|
4
|
-
import { sleepAsync } from '@furystack/utils'
|
|
4
|
+
import { sleepAsync, usingAsync } from '@furystack/utils'
|
|
5
5
|
import { promises } from 'fs'
|
|
6
6
|
import { afterAll, describe, expect, it, vi } from 'vitest'
|
|
7
7
|
import { FileSystemStore } from './filesystem-store.js'
|
|
@@ -33,43 +33,48 @@ describe('FileSystemStore', () => {
|
|
|
33
33
|
it('Should save data on tick', async () => {
|
|
34
34
|
const fileName = `filestore-test-${storeCount++}.json`
|
|
35
35
|
storeNames.push(fileName)
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
await usingAsync(
|
|
37
|
+
new FileSystemStore({ model: TestClass, fileName, primaryKey: 'id', tickMs: 500 }),
|
|
38
|
+
async (store) => {
|
|
39
|
+
store.saveChanges = vi.fn(store.saveChanges.bind(store))
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
await store.add({
|
|
42
|
+
id: 1,
|
|
43
|
+
dateValue: new Date(),
|
|
44
|
+
stringValue1: 'alma',
|
|
45
|
+
stringValue2: 'korte',
|
|
46
|
+
booleanValue: false,
|
|
47
|
+
numberValue1: 1,
|
|
48
|
+
numberValue2: 2,
|
|
49
|
+
})
|
|
50
|
+
await sleepAsync(501)
|
|
51
|
+
expect(store.saveChanges).toHaveBeenCalled()
|
|
52
|
+
},
|
|
53
|
+
)
|
|
52
54
|
})
|
|
53
55
|
|
|
54
56
|
it('Should reload data from disk', async () => {
|
|
55
57
|
const fileName = `filestore-test-${storeCount++}.json`
|
|
56
58
|
storeNames.push(fileName)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
await usingAsync(
|
|
60
|
+
new FileSystemStore({ model: TestClass, fileName, primaryKey: 'id', tickMs: 500 }),
|
|
61
|
+
async (store) => {
|
|
62
|
+
await store.add({
|
|
63
|
+
id: 1,
|
|
64
|
+
dateValue: new Date(),
|
|
65
|
+
stringValue1: 'alma',
|
|
66
|
+
stringValue2: 'korte',
|
|
67
|
+
booleanValue: false,
|
|
68
|
+
numberValue1: 1,
|
|
69
|
+
numberValue2: 2,
|
|
70
|
+
})
|
|
71
|
+
await store.saveChanges()
|
|
72
|
+
await store.remove(1)
|
|
73
|
+
await store.reloadData()
|
|
74
|
+
const count = await store.count()
|
|
75
|
+
expect(count).toBe(1)
|
|
76
|
+
},
|
|
77
|
+
)
|
|
73
78
|
})
|
|
74
79
|
|
|
75
80
|
afterAll(async () => {
|
package/src/filesystem-store.ts
CHANGED
|
@@ -4,7 +4,8 @@ import type { Constructable } from '@furystack/inject'
|
|
|
4
4
|
import { EventHub } from '@furystack/utils'
|
|
5
5
|
import type { FSWatcher } from 'fs'
|
|
6
6
|
import { promises, watch } from 'fs'
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
const DEFAULT_TICK_MS = 3000
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Store implementation that stores info in a simple JSON file
|
|
@@ -30,56 +31,40 @@ export class FileSystemStore<T, TPrimaryKey extends keyof T>
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
public async remove(...keys: Array<T[TPrimaryKey]>): Promise<void> {
|
|
33
|
-
await this.
|
|
34
|
-
await this.inMemoryStore.remove(...keys)
|
|
35
|
-
})
|
|
34
|
+
await this.inMemoryStore.remove(...keys)
|
|
36
35
|
this.hasChanges = true
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
public tick: ReturnType<typeof setInterval>
|
|
40
39
|
public hasChanges = false
|
|
41
40
|
public async get(key: T[TPrimaryKey], select?: Array<keyof T>) {
|
|
42
|
-
return await this.
|
|
43
|
-
return await this.inMemoryStore.get(key, select)
|
|
44
|
-
})
|
|
41
|
+
return await this.inMemoryStore.get(key, select)
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
public async add(...entries: T[]) {
|
|
48
|
-
const result = await this.
|
|
49
|
-
return await this.inMemoryStore.add(...entries)
|
|
50
|
-
})
|
|
45
|
+
const result = await this.inMemoryStore.add(...entries)
|
|
51
46
|
this.hasChanges = true
|
|
52
47
|
return result
|
|
53
48
|
}
|
|
54
49
|
|
|
55
50
|
public async find<TFields extends Array<keyof T>>(filter: FindOptions<T, TFields>) {
|
|
56
|
-
return
|
|
57
|
-
return this.inMemoryStore.find(filter)
|
|
58
|
-
})
|
|
51
|
+
return this.inMemoryStore.find(filter)
|
|
59
52
|
}
|
|
60
53
|
|
|
61
54
|
public async count(filter?: FilterType<T>) {
|
|
62
|
-
return
|
|
63
|
-
return this.inMemoryStore.count(filter)
|
|
64
|
-
})
|
|
55
|
+
return this.inMemoryStore.count(filter)
|
|
65
56
|
}
|
|
66
57
|
|
|
67
|
-
private fileLock = new Lock()
|
|
68
58
|
public async saveChanges() {
|
|
69
59
|
if (!this.hasChanges) {
|
|
70
60
|
return
|
|
71
61
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
for (const key of this.cache.keys()) {
|
|
76
|
-
values.push(this.cache.get(key) as T)
|
|
77
|
-
}
|
|
78
|
-
await this.writeFile(this.options.fileName, JSON.stringify(values))
|
|
79
|
-
this.hasChanges = false
|
|
80
|
-
} finally {
|
|
81
|
-
this.fileLock.release()
|
|
62
|
+
const values: T[] = []
|
|
63
|
+
for (const key of this.cache.keys()) {
|
|
64
|
+
values.push(this.cache.get(key) as T)
|
|
82
65
|
}
|
|
66
|
+
await this.writeFile(this.options.fileName, JSON.stringify(values))
|
|
67
|
+
this.hasChanges = false
|
|
83
68
|
}
|
|
84
69
|
|
|
85
70
|
public async [Symbol.asyncDispose]() {
|
|
@@ -91,7 +76,6 @@ export class FileSystemStore<T, TPrimaryKey extends keyof T>
|
|
|
91
76
|
|
|
92
77
|
public async reloadData() {
|
|
93
78
|
try {
|
|
94
|
-
await this.fileLock.acquire()
|
|
95
79
|
const data = await this.readFile(this.options.fileName)
|
|
96
80
|
const json = data ? (JSON.parse(data.toString()) as T[]) : []
|
|
97
81
|
this.cache.clear()
|
|
@@ -103,15 +87,11 @@ export class FileSystemStore<T, TPrimaryKey extends keyof T>
|
|
|
103
87
|
if (err instanceof Error && (err as { code?: string }).code !== 'ENOENT') {
|
|
104
88
|
throw err
|
|
105
89
|
}
|
|
106
|
-
} finally {
|
|
107
|
-
this.fileLock.release()
|
|
108
90
|
}
|
|
109
91
|
}
|
|
110
92
|
|
|
111
93
|
public async update(id: T[TPrimaryKey], data: T) {
|
|
112
|
-
await this.
|
|
113
|
-
return this.inMemoryStore.update(id, data)
|
|
114
|
-
})
|
|
94
|
+
await this.inMemoryStore.update(id, data)
|
|
115
95
|
this.hasChanges = true
|
|
116
96
|
}
|
|
117
97
|
|
|
@@ -130,7 +110,7 @@ export class FileSystemStore<T, TPrimaryKey extends keyof T>
|
|
|
130
110
|
this.primaryKey = options.primaryKey
|
|
131
111
|
this.model = options.model
|
|
132
112
|
this.inMemoryStore = new InMemoryStore({ model: this.model, primaryKey: this.primaryKey })
|
|
133
|
-
this.tick = setInterval(() => void this.saveChanges(), this.options.tickMs ||
|
|
113
|
+
this.tick = setInterval(() => void this.saveChanges(), this.options.tickMs || DEFAULT_TICK_MS)
|
|
134
114
|
|
|
135
115
|
this.inMemoryStore.subscribe('onEntityAdded', ({ entity }) => {
|
|
136
116
|
this.emit('onEntityAdded', { entity })
|