lmcs-db 1.0.4 → 2.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/README.md +241 -85
- package/dist/core/collection.d.ts +33 -0
- package/dist/core/collection.js +287 -0
- package/dist/core/database.d.ts +35 -0
- package/dist/core/database.js +165 -0
- package/dist/core/indexer.d.ts +20 -0
- package/dist/core/indexer.js +89 -0
- package/dist/core/transaction-context.d.ts +13 -0
- package/dist/core/transaction-context.js +48 -0
- package/dist/core/transaction.d.ts +25 -0
- package/dist/core/transaction.js +122 -0
- package/dist/crypto/key-derivation.d.ts +0 -0
- package/dist/crypto/key-derivation.js +1 -0
- package/dist/crypto/manager.d.ts +22 -0
- package/dist/crypto/manager.js +76 -0
- package/dist/crypto/vault.d.ts +18 -0
- package/dist/crypto/vault.js +44 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +12 -9
- package/dist/persistence/AsyncWriteWorker.js +11 -7
- package/dist/storage/aol.d.ts +26 -0
- package/dist/storage/aol.js +166 -0
- package/dist/storage/base.d.ts +36 -0
- package/dist/storage/base.js +13 -0
- package/dist/storage/binary.d.ts +21 -0
- package/dist/storage/binary.js +124 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/index.js +13 -0
- package/dist/storage/json.d.ts +18 -0
- package/dist/storage/json.js +153 -0
- package/dist/storage/memory.d.ts +14 -0
- package/dist/storage/memory.js +42 -0
- package/dist/utils/checksum.d.ts +0 -0
- package/dist/utils/checksum.js +1 -0
- package/dist/utils/errors.d.ts +16 -0
- package/dist/utils/errors.js +37 -0
- package/dist/utils/lock.d.ts +9 -0
- package/dist/utils/lock.js +75 -0
- package/package.json +11 -5
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.JSONStorage = void 0;
|
|
37
|
+
const base_1 = require("./base");
|
|
38
|
+
const manager_1 = require("../crypto/manager");
|
|
39
|
+
const promises_1 = require("fs/promises");
|
|
40
|
+
const path_1 = require("path");
|
|
41
|
+
const lock_1 = require("../utils/lock");
|
|
42
|
+
class JSONStorage extends base_1.BaseStorage {
|
|
43
|
+
autosaveInterval;
|
|
44
|
+
cache = [];
|
|
45
|
+
crypto;
|
|
46
|
+
locker = new lock_1.FileLocker();
|
|
47
|
+
dirty = false;
|
|
48
|
+
autosaveTimer;
|
|
49
|
+
isInitialized = false;
|
|
50
|
+
constructor(config, autosaveInterval = 5000) {
|
|
51
|
+
super(config);
|
|
52
|
+
this.autosaveInterval = autosaveInterval;
|
|
53
|
+
if (config.encryptionKey) {
|
|
54
|
+
this.crypto = new manager_1.CryptoManager(config.encryptionKey);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async initialize() {
|
|
58
|
+
const filePath = this.getFilePath("json");
|
|
59
|
+
await (0, promises_1.mkdir)((0, path_1.dirname)(filePath), { recursive: true });
|
|
60
|
+
try {
|
|
61
|
+
await (0, promises_1.access)(filePath);
|
|
62
|
+
const content = await (0, promises_1.readFile)(filePath, "utf8");
|
|
63
|
+
if (content.trim()) {
|
|
64
|
+
let jsonContent;
|
|
65
|
+
if (this.crypto) {
|
|
66
|
+
const encrypted = JSON.parse(content);
|
|
67
|
+
jsonContent = this.crypto.decrypt(encrypted);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
jsonContent = content;
|
|
71
|
+
}
|
|
72
|
+
this.cache = JSON.parse(jsonContent);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
if (err.code !== "ENOENT") {
|
|
77
|
+
// Se falhar na descriptografia, limpa (chave errada)
|
|
78
|
+
console.warn("Failed to load existing data, starting fresh");
|
|
79
|
+
this.cache = [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Autosave
|
|
83
|
+
if (this.autosaveInterval > 0) {
|
|
84
|
+
this.autosaveTimer = setInterval(() => {
|
|
85
|
+
if (this.dirty)
|
|
86
|
+
this.flush().catch(console.error);
|
|
87
|
+
}, this.autosaveInterval);
|
|
88
|
+
}
|
|
89
|
+
this.isInitialized = true;
|
|
90
|
+
}
|
|
91
|
+
async append(entry) {
|
|
92
|
+
if (!this.isInitialized)
|
|
93
|
+
throw new Error("Storage not initialized");
|
|
94
|
+
// Calcula checksum se necessário
|
|
95
|
+
if (this.config.enableChecksums) {
|
|
96
|
+
const { createHash } = await Promise.resolve().then(() => __importStar(require("crypto")));
|
|
97
|
+
entry.checksum = createHash("sha256")
|
|
98
|
+
.update(JSON.stringify({ ...entry, checksum: "" }))
|
|
99
|
+
.digest("hex");
|
|
100
|
+
}
|
|
101
|
+
this.cache.push(entry);
|
|
102
|
+
this.dirty = true;
|
|
103
|
+
}
|
|
104
|
+
async *readStream() {
|
|
105
|
+
for (const entry of this.cache) {
|
|
106
|
+
yield entry;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async flush() {
|
|
110
|
+
if (!this.dirty || !this.isInitialized)
|
|
111
|
+
return;
|
|
112
|
+
const filePath = this.getFilePath("json");
|
|
113
|
+
const lockPath = `${filePath}.lock`;
|
|
114
|
+
await this.locker.withLock(lockPath, async () => {
|
|
115
|
+
const content = JSON.stringify(this.cache, null, 2);
|
|
116
|
+
if (this.crypto) {
|
|
117
|
+
const encrypted = this.crypto.encrypt(content);
|
|
118
|
+
await (0, promises_1.writeFile)(filePath, JSON.stringify(encrypted), "utf8");
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
await (0, promises_1.writeFile)(filePath, content, "utf8");
|
|
122
|
+
}
|
|
123
|
+
this.dirty = false;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async close() {
|
|
127
|
+
if (this.autosaveTimer)
|
|
128
|
+
clearInterval(this.autosaveTimer);
|
|
129
|
+
await this.flush();
|
|
130
|
+
this.cache = [];
|
|
131
|
+
this.isInitialized = false;
|
|
132
|
+
}
|
|
133
|
+
async compact() {
|
|
134
|
+
// Remove duplicados (mantém último estado)
|
|
135
|
+
const seen = new Map();
|
|
136
|
+
for (const entry of this.cache) {
|
|
137
|
+
const key = `${entry.collection}:${entry.id}`;
|
|
138
|
+
if (entry.op === "DELETE") {
|
|
139
|
+
seen.delete(key);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
seen.set(key, entry);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
this.cache = Array.from(seen.values());
|
|
146
|
+
this.dirty = true;
|
|
147
|
+
await this.flush();
|
|
148
|
+
}
|
|
149
|
+
clear() {
|
|
150
|
+
throw new Error("Method not implemented.");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
exports.JSONStorage = JSONStorage;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseStorage, LogEntry, StorageConfig } from "./base";
|
|
2
|
+
export declare class MemoryStorage extends BaseStorage {
|
|
3
|
+
private logs;
|
|
4
|
+
private isInitialized;
|
|
5
|
+
constructor(config: StorageConfig);
|
|
6
|
+
initialize(): Promise<void>;
|
|
7
|
+
append(entry: LogEntry): Promise<void>;
|
|
8
|
+
readStream(): AsyncGenerator<LogEntry>;
|
|
9
|
+
flush(): Promise<void>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
clear(): Promise<void>;
|
|
12
|
+
getSize(): number;
|
|
13
|
+
compact?(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemoryStorage = void 0;
|
|
4
|
+
const base_1 = require("./base");
|
|
5
|
+
class MemoryStorage extends base_1.BaseStorage {
|
|
6
|
+
logs = [];
|
|
7
|
+
isInitialized = false;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
super(config);
|
|
10
|
+
}
|
|
11
|
+
async initialize() {
|
|
12
|
+
this.isInitialized = true;
|
|
13
|
+
this.logs = [];
|
|
14
|
+
}
|
|
15
|
+
async append(entry) {
|
|
16
|
+
if (!this.isInitialized)
|
|
17
|
+
throw new Error("Storage not initialized");
|
|
18
|
+
this.logs.push({ ...entry });
|
|
19
|
+
}
|
|
20
|
+
async *readStream() {
|
|
21
|
+
for (const entry of this.logs) {
|
|
22
|
+
yield entry;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async flush() {
|
|
26
|
+
// Nada a fazer em memória
|
|
27
|
+
}
|
|
28
|
+
async close() {
|
|
29
|
+
this.logs = [];
|
|
30
|
+
this.isInitialized = false;
|
|
31
|
+
}
|
|
32
|
+
async clear() {
|
|
33
|
+
this.logs = [];
|
|
34
|
+
}
|
|
35
|
+
getSize() {
|
|
36
|
+
return this.logs.length;
|
|
37
|
+
}
|
|
38
|
+
compact() {
|
|
39
|
+
throw new Error("Method not implemented.");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.MemoryStorage = MemoryStorage;
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class LMCSError extends Error {
|
|
2
|
+
code: string;
|
|
3
|
+
constructor(message: string, code: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class ValidationError extends LMCSError {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class CorruptionError extends LMCSError {
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
export declare class ConcurrencyError extends LMCSError {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class TransactionError extends LMCSError {
|
|
15
|
+
constructor(message: string);
|
|
16
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TransactionError = exports.ConcurrencyError = exports.CorruptionError = exports.ValidationError = exports.LMCSError = void 0;
|
|
4
|
+
class LMCSError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(message, code) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = this.constructor.name;
|
|
10
|
+
Error.captureStackTrace(this, this.constructor);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.LMCSError = LMCSError;
|
|
14
|
+
class ValidationError extends LMCSError {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message, 'VALIDATION_ERROR');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.ValidationError = ValidationError;
|
|
20
|
+
class CorruptionError extends LMCSError {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message, 'DATA_CORRUPTION');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.CorruptionError = CorruptionError;
|
|
26
|
+
class ConcurrencyError extends LMCSError {
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super(message, 'CONCURRENCY_ERROR');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.ConcurrencyError = ConcurrencyError;
|
|
32
|
+
class TransactionError extends LMCSError {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message, 'TRANSACTION_ERROR');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.TransactionError = TransactionError;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class FileLocker {
|
|
2
|
+
private locks;
|
|
3
|
+
private queues;
|
|
4
|
+
acquire(filePath: string, options?: {
|
|
5
|
+
retries?: number;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
release(filePath: string): Promise<void>;
|
|
8
|
+
withLock<T>(filePath: string, fn: () => Promise<T>): Promise<T>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FileLocker = void 0;
|
|
37
|
+
const lockfile = __importStar(require("proper-lockfile"));
|
|
38
|
+
class FileLocker {
|
|
39
|
+
locks = new Map();
|
|
40
|
+
queues = new Map();
|
|
41
|
+
async acquire(filePath, options) {
|
|
42
|
+
const release = await lockfile.lock(filePath, {
|
|
43
|
+
retries: options?.retries ?? 5,
|
|
44
|
+
stale: 5000,
|
|
45
|
+
realpath: false
|
|
46
|
+
});
|
|
47
|
+
this.locks.set(filePath, release);
|
|
48
|
+
}
|
|
49
|
+
async release(filePath) {
|
|
50
|
+
const release = this.locks.get(filePath);
|
|
51
|
+
if (release) {
|
|
52
|
+
await release();
|
|
53
|
+
this.locks.delete(filePath);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async withLock(filePath, fn) {
|
|
57
|
+
// Serialize access within the same process
|
|
58
|
+
const currentQueue = this.queues.get(filePath) || Promise.resolve();
|
|
59
|
+
let result;
|
|
60
|
+
const nextPromise = currentQueue.then(async () => {
|
|
61
|
+
await this.acquire(filePath);
|
|
62
|
+
try {
|
|
63
|
+
result = await fn();
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
await this.release(filePath);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
// Update queue, catching errors to ensure the chain continues
|
|
70
|
+
this.queues.set(filePath, nextPromise.catch(() => { }));
|
|
71
|
+
await nextPromise;
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.FileLocker = FileLocker;
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lmcs-db",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Lightweight Modular Collection Storage - Secure & Transactional",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"prepublishOnly": "npm run build",
|
|
10
|
-
"test": "
|
|
10
|
+
"test": "jest"
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
13
|
"database",
|
|
@@ -37,11 +37,17 @@
|
|
|
37
37
|
},
|
|
38
38
|
"homepage": "https://github.com/leandroasilva/lmcs-db#readme",
|
|
39
39
|
"dependencies": {
|
|
40
|
+
"proper-lockfile": "^4.1.2",
|
|
40
41
|
"uuid": "^11.1.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
|
-
"@types/
|
|
44
|
-
"
|
|
44
|
+
"@types/jest": "^30.0.0",
|
|
45
|
+
"@types/node": "^20.0.0",
|
|
46
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
47
|
+
"@types/uuid": "^9.0.8",
|
|
48
|
+
"jest": "^30.2.0",
|
|
49
|
+
"ts-jest": "^29.4.6",
|
|
50
|
+
"tsx": "^4.21.0",
|
|
45
51
|
"typescript": "^5.0.0"
|
|
46
52
|
}
|
|
47
53
|
}
|