@notkannan/cache-with-persistence 1.0.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/LICENSE +13 -0
- package/README.md +42 -0
- package/dist/HashMap.d.ts +14 -0
- package/dist/HashMap.d.ts.map +1 -0
- package/dist/HashMap.js +90 -0
- package/dist/HashMap.js.map +1 -0
- package/dist/Store.d.ts +12 -0
- package/dist/Store.d.ts.map +1 -0
- package/dist/Store.js +76 -0
- package/dist/Store.js.map +1 -0
- package/dist/WAL.d.ts +14 -0
- package/dist/WAL.d.ts.map +1 -0
- package/dist/WAL.js +31 -0
- package/dist/WAL.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright (c) 2026, Kannan Karthikeyan
|
|
2
|
+
|
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
4
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
5
|
+
copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
8
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
9
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
10
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
11
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
12
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
13
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @notkannan/cache-with-persistence
|
|
2
|
+
|
|
3
|
+
Redis-inspired in-memory key-value store with append-only file persistence.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @notkannan/cache-with-persistence
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Store } from '@notkannan/cache-with-persistence';
|
|
15
|
+
|
|
16
|
+
const store = new Store('./data');
|
|
17
|
+
|
|
18
|
+
store.set('user:1', 'alice');
|
|
19
|
+
store.set('session', 'abc', 3600); // expires in 1 hour
|
|
20
|
+
|
|
21
|
+
store.get('user:1'); // 'alice'
|
|
22
|
+
store.del('user:1'); // true
|
|
23
|
+
store.exists('user:1'); // false
|
|
24
|
+
store.ttl('session'); // remaining ms, -1 if no expiry, -2 if missing
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Operations are written to a WAL (`store.aof`) and replayed on startup.
|
|
28
|
+
|
|
29
|
+
## API
|
|
30
|
+
|
|
31
|
+
| Method | Description |
|
|
32
|
+
|--------|-------------|
|
|
33
|
+
| `new Store(dataDir?)` | Create store; defaults to `./data` |
|
|
34
|
+
| `set(key, value, ttlSeconds?)` | Set a key, optional TTL |
|
|
35
|
+
| `get(key)` | Get value, or `null` if missing/expired |
|
|
36
|
+
| `del(key)` | Delete key; returns `true` if it existed |
|
|
37
|
+
| `exists(key)` | Check if key exists |
|
|
38
|
+
| `ttl(key)` | Remaining ms, `-1` (no expiry), or `-2` (missing) |
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
ISC
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class HashMap<V> {
|
|
2
|
+
private buckets;
|
|
3
|
+
private size;
|
|
4
|
+
private capacity;
|
|
5
|
+
constructor(capacity?: number);
|
|
6
|
+
private hash;
|
|
7
|
+
set(key: string, value: V): void;
|
|
8
|
+
get(key: string): V | undefined;
|
|
9
|
+
delete(key: string): boolean;
|
|
10
|
+
has(key: string): boolean;
|
|
11
|
+
getSize(): number;
|
|
12
|
+
debug(): void;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=HashMap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HashMap.d.ts","sourceRoot":"","sources":["../src/HashMap.ts"],"names":[],"mappings":"AAOA,qBAAa,OAAO,CAAC,CAAC;IAClB,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,QAAQ,CAAS;gBAEb,QAAQ,SAAK;IAOzB,OAAO,CAAC,IAAI;IAKZ,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAyBhC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAa/B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAuB5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,OAAO,IAAI,MAAM;IAKjB,KAAK,IAAI,IAAI;CAUhB"}
|
package/dist/HashMap.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export class HashMap {
|
|
2
|
+
buckets;
|
|
3
|
+
size; // how many key/value pairs are stored
|
|
4
|
+
capacity; // number of buckets in the HashMap
|
|
5
|
+
constructor(capacity = 16) {
|
|
6
|
+
this.capacity = capacity;
|
|
7
|
+
this.size = 0;
|
|
8
|
+
this.buckets = new Array(capacity).fill(null);
|
|
9
|
+
}
|
|
10
|
+
// Hash function
|
|
11
|
+
hash(key) {
|
|
12
|
+
return 1;
|
|
13
|
+
}
|
|
14
|
+
// Set function
|
|
15
|
+
set(key, value) {
|
|
16
|
+
const index = this.hash(key); // get the index by hashing to the bucket array
|
|
17
|
+
let entry = this.buckets[index] ?? null;
|
|
18
|
+
while (entry !== null) {
|
|
19
|
+
if (entry.key === key) {
|
|
20
|
+
entry.value = value;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
entry = entry.next;
|
|
24
|
+
}
|
|
25
|
+
// Key not found — prepend a new entry at the head of the chain
|
|
26
|
+
// (prepending is O(1); appending would require walking to the end)
|
|
27
|
+
const newEntry = {
|
|
28
|
+
key,
|
|
29
|
+
value,
|
|
30
|
+
next: this.buckets[index] ?? null, // new entry points to the old head
|
|
31
|
+
};
|
|
32
|
+
this.buckets[index] = newEntry; // new entry becomes the new head
|
|
33
|
+
this.size++;
|
|
34
|
+
}
|
|
35
|
+
// get function
|
|
36
|
+
get(key) {
|
|
37
|
+
const index = this.hash(key);
|
|
38
|
+
let entry = this.buckets[index] ?? null;
|
|
39
|
+
// Walk the chain until we find the key or run out
|
|
40
|
+
while (entry !== null) {
|
|
41
|
+
if (entry.key === key)
|
|
42
|
+
return entry.value;
|
|
43
|
+
entry = entry.next;
|
|
44
|
+
}
|
|
45
|
+
return undefined; // key doesn't exist
|
|
46
|
+
}
|
|
47
|
+
delete(key) {
|
|
48
|
+
const index = this.hash(key);
|
|
49
|
+
let entry = this.buckets[index] ?? null;
|
|
50
|
+
let prev = null;
|
|
51
|
+
while (entry !== null) {
|
|
52
|
+
if (entry.key === key) {
|
|
53
|
+
// Stitch the chain back together, skipping this entry
|
|
54
|
+
if (prev === null) {
|
|
55
|
+
this.buckets[index] = entry.next; // it was the head
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
prev.next = entry.next; // bypass this node
|
|
59
|
+
}
|
|
60
|
+
this.size--;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
prev = entry;
|
|
64
|
+
entry = entry.next;
|
|
65
|
+
}
|
|
66
|
+
return false; // not found
|
|
67
|
+
}
|
|
68
|
+
// HELPER FUNCTION
|
|
69
|
+
has(key) {
|
|
70
|
+
return this.get(key) !== undefined;
|
|
71
|
+
}
|
|
72
|
+
getSize() {
|
|
73
|
+
return this.size;
|
|
74
|
+
}
|
|
75
|
+
// For debugging — lets you see the raw bucket structure
|
|
76
|
+
debug() {
|
|
77
|
+
this.buckets.forEach((entry, i) => {
|
|
78
|
+
if (entry === null)
|
|
79
|
+
return;
|
|
80
|
+
const chain = [];
|
|
81
|
+
let e = entry;
|
|
82
|
+
while (e) {
|
|
83
|
+
chain.push(`${e.key}:${e.value}`);
|
|
84
|
+
e = e.next;
|
|
85
|
+
}
|
|
86
|
+
console.log(`bucket[${i}] → ${chain.join(' → ')}`);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=HashMap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HashMap.js","sourceRoot":"","sources":["../src/HashMap.ts"],"names":[],"mappings":"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"}
|
package/dist/Store.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class Store {
|
|
2
|
+
private map;
|
|
3
|
+
private wal;
|
|
4
|
+
constructor(dataDir?: string);
|
|
5
|
+
private recover;
|
|
6
|
+
set(key: string, value: string, ttlSeconds?: number): void;
|
|
7
|
+
get(key: string): string | null;
|
|
8
|
+
del(key: string): boolean;
|
|
9
|
+
exists(key: string): boolean;
|
|
10
|
+
ttl(key: string): number;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=Store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Store.d.ts","sourceRoot":"","sources":["../src/Store.ts"],"names":[],"mappings":"AASA,qBAAa,KAAK;IAChB,OAAO,CAAC,GAAG,CAAsB;IACjC,OAAO,CAAC,GAAG,CAAM;gBAEL,OAAO,GAAE,MAAgB;IAQrC,OAAO,CAAC,OAAO;IAqBf,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI;IAkB1D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAc/B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAMzB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAK5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAOzB"}
|
package/dist/Store.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { HashMap } from './HashMap.js';
|
|
3
|
+
import { WAL } from './WAL.js';
|
|
4
|
+
export class Store {
|
|
5
|
+
map;
|
|
6
|
+
wal;
|
|
7
|
+
constructor(dataDir = './data') {
|
|
8
|
+
this.map = new HashMap();
|
|
9
|
+
this.wal = new WAL(dataDir);
|
|
10
|
+
this.recover();
|
|
11
|
+
}
|
|
12
|
+
recover() {
|
|
13
|
+
const entries = this.wal.readAll();
|
|
14
|
+
if (entries.length === 0)
|
|
15
|
+
return; // clean WAL file
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
if (entry.op === 'SET') {
|
|
18
|
+
const expiresAt = entry.ttlSeconds
|
|
19
|
+
? entry.timestamp + entry.ttlSeconds * 1000
|
|
20
|
+
: null;
|
|
21
|
+
if (expiresAt !== null && Date.now() > expiresAt)
|
|
22
|
+
continue;
|
|
23
|
+
this.map.set(entry.key, { value: entry.value, expiresAt });
|
|
24
|
+
}
|
|
25
|
+
else if (entry.op === 'DEL') {
|
|
26
|
+
this.map.delete(entry.key);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// SET key value [EX seconds]
|
|
31
|
+
set(key, value, ttlSeconds) {
|
|
32
|
+
this.wal.append({
|
|
33
|
+
op: 'SET',
|
|
34
|
+
key,
|
|
35
|
+
value,
|
|
36
|
+
timestamp: Date.now(),
|
|
37
|
+
...(ttlSeconds !== undefined && { ttlSeconds })
|
|
38
|
+
});
|
|
39
|
+
const expiresAt = ttlSeconds
|
|
40
|
+
? Date.now() + ttlSeconds * 1000
|
|
41
|
+
: null;
|
|
42
|
+
this.map.set(key, { value, expiresAt });
|
|
43
|
+
}
|
|
44
|
+
// GET key → returns the value, or null if missing/expired
|
|
45
|
+
get(key) {
|
|
46
|
+
const entry = this.map.get(key);
|
|
47
|
+
if (!entry)
|
|
48
|
+
return null;
|
|
49
|
+
// Lazy expiry — we check on read, not on a background timer
|
|
50
|
+
if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
|
|
51
|
+
this.map.delete(key); // clean up expired key
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return entry.value;
|
|
55
|
+
}
|
|
56
|
+
// DEL key → returns true if deleted, false if key didn't exist
|
|
57
|
+
del(key) {
|
|
58
|
+
this.wal.append({ op: 'DEL', key, timestamp: Date.now() });
|
|
59
|
+
return this.map.delete(key);
|
|
60
|
+
}
|
|
61
|
+
// EXISTS key
|
|
62
|
+
exists(key) {
|
|
63
|
+
return this.get(key) !== null; // reuses expiry logic from get()
|
|
64
|
+
}
|
|
65
|
+
// TTL key → remaining milliseconds, -1 if no expiry, -2 if not found
|
|
66
|
+
ttl(key) {
|
|
67
|
+
const entry = this.map.get(key);
|
|
68
|
+
if (!entry)
|
|
69
|
+
return -2;
|
|
70
|
+
if (entry.expiresAt === null)
|
|
71
|
+
return -1;
|
|
72
|
+
const remaining = entry.expiresAt - Date.now();
|
|
73
|
+
return remaining > 0 ? remaining : -2;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=Store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Store.js","sourceRoot":"","sources":["../src/Store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,GAAG,EAAiB,MAAM,UAAU,CAAA;AAO7C,MAAM,OAAO,KAAK;IACR,GAAG,CAAsB;IACzB,GAAG,CAAM;IAEjB,YAAY,UAAiB,QAAQ;QACnC,IAAI,CAAC,GAAG,GAAG,IAAI,OAAO,EAAc,CAAC;QACrC,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QAG5B,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAEO,OAAO;QACb,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,iBAAiB;QAEnD,KAAI,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC3B,IAAI,KAAK,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;gBACvB,MAAM,SAAS,GAAG,KAAK,CAAC,UAAU;oBAChC,CAAC,CAAC,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,UAAU,GAAG,IAAI;oBAC3C,CAAC,CAAC,IAAI,CAAC;gBAEX,IAAI,SAAS,KAAK,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;oBAAE,SAAS;gBAE3D,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YAE5D,CAAC;iBAAM,IAAI,KAAK,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;gBAC9B,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;IAED,6BAA6B;IAC7B,GAAG,CAAC,GAAW,EAAE,KAAa,EAAE,UAAmB;QAEjD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YACd,EAAE,EAAE,KAAK;YACT,GAAG;YACH,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,GAAG,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,CAAC;SAChD,CAAC,CAAC;QAEH,MAAM,SAAS,GAAG,UAAU;YAC1B,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,IAAI;YAChC,CAAC,CAAC,IAAI,CAAC;QAET,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,0DAA0D;IAC1D,GAAG,CAAC,GAAW;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,4DAA4D;QAC5D,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YAC7D,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,uBAAuB;YAC7C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,+DAA+D;IAC/D,GAAG,CAAC,GAAW;QACb,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC3D,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC;IAED,aAAa;IACb,MAAM,CAAC,GAAW;QAChB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,iCAAiC;IAClE,CAAC;IAED,qEAAqE;IACrE,GAAG,CAAC,GAAW;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC,CAAC;QACtB,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI;YAAE,OAAO,CAAC,CAAC,CAAC;QACxC,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC/C,OAAO,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC;CACF"}
|
package/dist/WAL.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface WALEntry {
|
|
2
|
+
op: 'SET' | 'DEL';
|
|
3
|
+
key: string;
|
|
4
|
+
value?: string;
|
|
5
|
+
ttlSeconds?: number;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
}
|
|
8
|
+
export declare class WAL {
|
|
9
|
+
private filePath;
|
|
10
|
+
constructor(dataDir: string);
|
|
11
|
+
append(entry: WALEntry): void;
|
|
12
|
+
readAll(): WALEntry[];
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=WAL.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WAL.d.ts","sourceRoot":"","sources":["../src/WAL.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,QAAQ;IACrB,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,GAAG;IACZ,OAAO,CAAC,QAAQ,CAAS;gBAEb,OAAO,EAAE,MAAM;IAK3B,MAAM,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAK7B,OAAO,IAAI,QAAQ,EAAE;CAiBxB"}
|
package/dist/WAL.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
export class WAL {
|
|
4
|
+
filePath;
|
|
5
|
+
constructor(dataDir) {
|
|
6
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
7
|
+
this.filePath = path.join(dataDir, 'store.aof');
|
|
8
|
+
}
|
|
9
|
+
append(entry) {
|
|
10
|
+
const line = JSON.stringify(entry) + '\n';
|
|
11
|
+
fs.appendFileSync(this.filePath, line, 'utf8');
|
|
12
|
+
}
|
|
13
|
+
readAll() {
|
|
14
|
+
if (!fs.existsSync(this.filePath))
|
|
15
|
+
return [];
|
|
16
|
+
const raw = fs.readFileSync(this.filePath, 'utf8');
|
|
17
|
+
const entries = [];
|
|
18
|
+
for (const line of raw.split('\n')) {
|
|
19
|
+
if (!line.trim())
|
|
20
|
+
continue; // skip blank lines
|
|
21
|
+
try {
|
|
22
|
+
entries.push(JSON.parse(line));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
console.warn('[WAL] Skipping malformed line:', line);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return entries;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=WAL.js.map
|
package/dist/WAL.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WAL.js","sourceRoot":"","sources":["../src/WAL.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAU7B,MAAM,OAAO,GAAG;IACJ,QAAQ,CAAS;IAEzB,YAAY,OAAe;QACvB,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,CAAC,KAAe;QAClB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;QAC1C,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;IAED,OAAO;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,EAAE,CAAC;QAE7C,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACnD,MAAM,OAAO,GAAe,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;gBAAE,SAAS,CAAC,mBAAmB;YAC/C,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAa,CAAC,CAAC;YAC7C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,IAAI,CAAC,gCAAgC,EAAE,IAAI,CAAC,CAAC;YACvD,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC;IACnB,CAAC;CACJ"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,GAAG,EAAiB,MAAM,UAAU,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@notkannan/cache-with-persistence",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "A Redis-inspired in-memory key-value store with persistence",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/notkannan/cache-with-persistence.git"
|
|
20
|
+
},
|
|
21
|
+
"author": "Kannan Karthikeyan",
|
|
22
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": ["redis", "in-memory", "key-value", "store", "wal", "persistence"],
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^25.9.3",
|
|
34
|
+
"typescript": "^6.0.3"
|
|
35
|
+
}
|
|
36
|
+
}
|