lmcs-db 1.0.3 → 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.
Files changed (47) hide show
  1. package/README.md +247 -68
  2. package/dist/LmcsDB.d.ts +13 -0
  3. package/dist/LmcsDB.js +20 -5
  4. package/dist/LmcsDB.js.map +1 -1
  5. package/dist/core/collection.d.ts +33 -0
  6. package/dist/core/collection.js +287 -0
  7. package/dist/core/database.d.ts +35 -0
  8. package/dist/core/database.js +165 -0
  9. package/dist/core/indexer.d.ts +20 -0
  10. package/dist/core/indexer.js +89 -0
  11. package/dist/core/transaction-context.d.ts +13 -0
  12. package/dist/core/transaction-context.js +48 -0
  13. package/dist/core/transaction.d.ts +25 -0
  14. package/dist/core/transaction.js +122 -0
  15. package/dist/crypto/key-derivation.d.ts +0 -0
  16. package/dist/crypto/key-derivation.js +1 -0
  17. package/dist/crypto/manager.d.ts +22 -0
  18. package/dist/crypto/manager.js +76 -0
  19. package/dist/crypto/vault.d.ts +18 -0
  20. package/dist/crypto/vault.js +44 -0
  21. package/dist/index.d.ts +5 -2
  22. package/dist/index.js +12 -9
  23. package/dist/persistence/AsyncWriteWorker.d.ts +30 -0
  24. package/dist/persistence/AsyncWriteWorker.js +76 -0
  25. package/dist/persistence/AsyncWriteWorker.js.map +1 -0
  26. package/dist/storage/BinaryStorage.d.ts +3 -0
  27. package/dist/storage/BinaryStorage.js +43 -5
  28. package/dist/storage/BinaryStorage.js.map +1 -1
  29. package/dist/storage/aol.d.ts +26 -0
  30. package/dist/storage/aol.js +166 -0
  31. package/dist/storage/base.d.ts +36 -0
  32. package/dist/storage/base.js +13 -0
  33. package/dist/storage/binary.d.ts +21 -0
  34. package/dist/storage/binary.js +124 -0
  35. package/dist/storage/index.d.ts +5 -0
  36. package/dist/storage/index.js +13 -0
  37. package/dist/storage/json.d.ts +18 -0
  38. package/dist/storage/json.js +153 -0
  39. package/dist/storage/memory.d.ts +14 -0
  40. package/dist/storage/memory.js +42 -0
  41. package/dist/utils/checksum.d.ts +0 -0
  42. package/dist/utils/checksum.js +1 -0
  43. package/dist/utils/errors.d.ts +16 -0
  44. package/dist/utils/errors.js +37 -0
  45. package/dist/utils/lock.d.ts +9 -0
  46. package/dist/utils/lock.js +75 -0
  47. package/package.json +11 -5
@@ -0,0 +1,287 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Collection = void 0;
4
+ const manager_1 = require("../crypto/manager");
5
+ const uuid_1 = require("uuid");
6
+ class Collection {
7
+ name;
8
+ storage;
9
+ data = new Map();
10
+ indexes = new Map();
11
+ crypto = new manager_1.CryptoManager(); // Instância sem chave para hash apenas
12
+ constructor(name, storage) {
13
+ this.name = name;
14
+ this.storage = storage;
15
+ this.loadFromStorage().catch(console.error);
16
+ }
17
+ async loadFromStorage() {
18
+ try {
19
+ if (!this.storage.readStream) {
20
+ throw new Error("Storage does not support streaming");
21
+ }
22
+ const stream = this.storage.readStream();
23
+ for await (const entry of stream) {
24
+ this.applyLogEntry(entry);
25
+ }
26
+ }
27
+ catch (err) {
28
+ console.error(`Failed to load collection ${this.name}:`, err);
29
+ }
30
+ }
31
+ applyLogEntry(entry) {
32
+ if (entry.collection !== this.name)
33
+ return;
34
+ const id = entry.id;
35
+ switch (entry.op) {
36
+ case "INSERT":
37
+ case "UPDATE":
38
+ const existing = this.data.get(id);
39
+ if (existing) {
40
+ this.removeFromIndexes(id, existing);
41
+ }
42
+ this.data.set(id, entry.data);
43
+ this.addToIndexes(id, entry.data);
44
+ break;
45
+ case "DELETE":
46
+ const old = this.data.get(id);
47
+ if (old) {
48
+ this.removeFromIndexes(id, old);
49
+ }
50
+ this.data.delete(id);
51
+ break;
52
+ }
53
+ }
54
+ addToIndexes(id, doc) {
55
+ for (const [field, index] of this.indexes) {
56
+ const value = doc[field];
57
+ if (value !== undefined) {
58
+ if (!index.has(value))
59
+ index.set(value, new Set());
60
+ index.get(value).add(id);
61
+ }
62
+ }
63
+ }
64
+ removeFromIndexes(id, doc) {
65
+ for (const [field, index] of this.indexes) {
66
+ const value = doc[field];
67
+ if (value !== undefined) {
68
+ index.get(value)?.delete(id);
69
+ }
70
+ }
71
+ }
72
+ async insert(doc) {
73
+ const id = doc._id || (0, uuid_1.v7)();
74
+ if (this.data.has(id)) {
75
+ throw new Error(`Document with id ${id} already exists`);
76
+ }
77
+ const fullDoc = { ...doc, _id: id };
78
+ await this.storage.append({
79
+ op: "INSERT",
80
+ collection: this.name,
81
+ id,
82
+ data: fullDoc,
83
+ checksum: "",
84
+ timestamp: Date.now(),
85
+ });
86
+ this.data.set(id, fullDoc);
87
+ this.addToIndexes(id, fullDoc);
88
+ return fullDoc;
89
+ }
90
+ async update(filter, updates) {
91
+ let count = 0;
92
+ for (const [id, doc] of this.data.entries()) {
93
+ if (this.matchesFilter(doc, filter)) {
94
+ const newDoc = { ...doc, ...updates, _id: id };
95
+ await this.storage.append({
96
+ op: "UPDATE",
97
+ collection: this.name,
98
+ id,
99
+ data: newDoc,
100
+ checksum: "",
101
+ timestamp: Date.now(),
102
+ });
103
+ this.removeFromIndexes(id, doc);
104
+ this.data.set(id, newDoc);
105
+ this.addToIndexes(id, newDoc);
106
+ count++;
107
+ }
108
+ }
109
+ return count;
110
+ }
111
+ async delete(filter) {
112
+ let count = 0;
113
+ const toDelete = [];
114
+ for (const [id, doc] of this.data.entries()) {
115
+ if (this.matchesFilter(doc, filter)) {
116
+ toDelete.push(id);
117
+ }
118
+ }
119
+ for (const id of toDelete) {
120
+ const doc = this.data.get(id);
121
+ await this.storage.append({
122
+ op: "DELETE",
123
+ collection: this.name,
124
+ id,
125
+ checksum: "",
126
+ timestamp: Date.now(),
127
+ });
128
+ this.removeFromIndexes(id, doc);
129
+ this.data.delete(id);
130
+ count++;
131
+ }
132
+ return count;
133
+ }
134
+ async findOne(filter) {
135
+ // Tenta usar índice primeiro
136
+ const indexedId = this.queryByIndex(filter);
137
+ if (indexedId) {
138
+ return this.data.get(indexedId) || null;
139
+ }
140
+ for (const doc of this.data.values()) {
141
+ if (this.matchesFilter(doc, filter))
142
+ return doc;
143
+ }
144
+ return null;
145
+ }
146
+ async findAll(options = {}) {
147
+ let results = Array.from(this.data.values());
148
+ if (options.filter) {
149
+ results = results.filter((d) => this.matchesFilter(d, options.filter));
150
+ }
151
+ if (options.sort) {
152
+ results.sort((a, b) => {
153
+ for (const [field, dir] of Object.entries(options.sort)) {
154
+ const aVal = a[field];
155
+ const bVal = b[field];
156
+ if (aVal < bVal)
157
+ return dir === 1 ? -1 : 1;
158
+ if (aVal > bVal)
159
+ return dir === 1 ? 1 : -1;
160
+ }
161
+ return 0;
162
+ });
163
+ }
164
+ if (options.skip)
165
+ results = results.slice(options.skip);
166
+ if (options.limit)
167
+ results = results.slice(0, options.limit);
168
+ return results;
169
+ }
170
+ async *findStream(options = {}) {
171
+ if (options.sort) {
172
+ const all = await this.findAll(options);
173
+ for (const doc of all)
174
+ yield doc;
175
+ return;
176
+ }
177
+ let count = 0;
178
+ let skipped = 0;
179
+ for (const doc of this.data.values()) {
180
+ if (options.filter && !this.matchesFilter(doc, options.filter))
181
+ continue;
182
+ if (options.skip && skipped < options.skip) {
183
+ skipped++;
184
+ continue;
185
+ }
186
+ if (options.limit && count >= options.limit)
187
+ break;
188
+ yield doc;
189
+ count++;
190
+ }
191
+ }
192
+ createIndex(field) {
193
+ if (this.indexes.has(field))
194
+ return;
195
+ this.indexes.set(field, new Map());
196
+ // Indexa existentes
197
+ for (const [id, doc] of this.data.entries()) {
198
+ this.addToIndexes(id, doc);
199
+ }
200
+ }
201
+ matchesFilter(doc, filter) {
202
+ for (const [key, value] of Object.entries(filter)) {
203
+ if (key === "$or") {
204
+ if (!Array.isArray(value))
205
+ return false;
206
+ if (!value.some((condition) => this.matchesFilter(doc, condition)))
207
+ return false;
208
+ continue;
209
+ }
210
+ if (key === "$and") {
211
+ if (!Array.isArray(value))
212
+ return false;
213
+ if (!value.every((condition) => this.matchesFilter(doc, condition)))
214
+ return false;
215
+ continue;
216
+ }
217
+ // Handle dot notation for nested fields
218
+ const docValue = this.getNestedValue(doc, key);
219
+ if (typeof value === "object" && value !== null) {
220
+ // Handle operators like $gt, $lt, etc.
221
+ for (const [op, opValue] of Object.entries(value)) {
222
+ switch (op) {
223
+ case "$gt":
224
+ if (!(docValue > opValue))
225
+ return false;
226
+ break;
227
+ case "$gte":
228
+ if (!(docValue >= opValue))
229
+ return false;
230
+ break;
231
+ case "$lt":
232
+ if (!(docValue < opValue))
233
+ return false;
234
+ break;
235
+ case "$lte":
236
+ if (!(docValue <= opValue))
237
+ return false;
238
+ break;
239
+ case "$ne":
240
+ if (docValue === opValue)
241
+ return false;
242
+ break;
243
+ case "$in":
244
+ if (!Array.isArray(opValue) || !opValue.includes(docValue))
245
+ return false;
246
+ break;
247
+ case "$nin":
248
+ if (Array.isArray(opValue) && opValue.includes(docValue))
249
+ return false;
250
+ break;
251
+ default:
252
+ // If it's not an operator, treat as equality check for object
253
+ if (JSON.stringify(docValue) !== JSON.stringify(value))
254
+ return false;
255
+ }
256
+ }
257
+ }
258
+ else {
259
+ // Direct equality
260
+ if (docValue !== value)
261
+ return false;
262
+ }
263
+ }
264
+ return true;
265
+ }
266
+ getNestedValue(obj, path) {
267
+ return path.split('.').reduce((o, p) => (o ? o[p] : undefined), obj);
268
+ }
269
+ queryByIndex(filter) {
270
+ const entries = Object.entries(filter);
271
+ if (entries.length !== 1)
272
+ return null;
273
+ const [field, value] = entries[0];
274
+ const index = this.indexes.get(field);
275
+ if (!index)
276
+ return null;
277
+ const ids = index.get(value);
278
+ if (ids && ids.size > 0) {
279
+ return Array.from(ids)[0];
280
+ }
281
+ return null;
282
+ }
283
+ count() {
284
+ return this.data.size;
285
+ }
286
+ }
287
+ exports.Collection = Collection;
@@ -0,0 +1,35 @@
1
+ import { Collection } from './collection';
2
+ import { TransactionContext } from './transaction-context';
3
+ export type StorageType = 'memory' | 'json' | 'aol' | 'binary';
4
+ export interface DatabaseOptions {
5
+ storageType: StorageType;
6
+ databaseName: string;
7
+ encryptionKey?: string;
8
+ customPath?: string;
9
+ compactionInterval?: number;
10
+ bufferSize?: number;
11
+ autosaveInterval?: number;
12
+ enableChecksums?: boolean;
13
+ }
14
+ export declare class Database {
15
+ private options;
16
+ private storage;
17
+ private collections;
18
+ private locker;
19
+ private lockPath;
20
+ private initialized;
21
+ private txManager?;
22
+ private txLock;
23
+ constructor(options: DatabaseOptions);
24
+ initialize(): Promise<void>;
25
+ collection<T extends Record<string, any>>(name: string): Collection<T>;
26
+ transaction<T>(fn: (trx: TransactionContext) => Promise<T>): Promise<T>;
27
+ save(): Promise<void>;
28
+ compact(): Promise<void>;
29
+ close(): Promise<void>;
30
+ get stats(): {
31
+ collections: number;
32
+ storage: StorageType;
33
+ };
34
+ }
35
+ export declare function createDatabase(options: DatabaseOptions): Promise<Database>;
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Database = void 0;
4
+ exports.createDatabase = createDatabase;
5
+ const path_1 = require("path");
6
+ const promises_1 = require("fs/promises");
7
+ const storage_1 = require("../storage");
8
+ const collection_1 = require("./collection");
9
+ const transaction_1 = require("./transaction");
10
+ const transaction_context_1 = require("./transaction-context");
11
+ const lock_1 = require("../utils/lock");
12
+ class Database {
13
+ options;
14
+ storage;
15
+ collections = new Map();
16
+ locker;
17
+ lockPath;
18
+ initialized = false;
19
+ txManager;
20
+ txLock = Promise.resolve();
21
+ constructor(options) {
22
+ this.options = options;
23
+ const basePath = options.customPath || './data';
24
+ const config = {
25
+ dbPath: basePath,
26
+ dbName: options.databaseName,
27
+ encryptionKey: options.encryptionKey,
28
+ enableChecksums: options.enableChecksums ?? false
29
+ };
30
+ // Factory switch
31
+ switch (options.storageType) {
32
+ case 'memory':
33
+ this.storage = new storage_1.MemoryStorage(config);
34
+ break;
35
+ case 'json':
36
+ this.storage = new storage_1.JSONStorage(config, options.autosaveInterval);
37
+ break;
38
+ case 'aol':
39
+ this.storage = new storage_1.AOLStorage({
40
+ ...config,
41
+ compactionInterval: options.compactionInterval,
42
+ bufferSize: options.bufferSize
43
+ });
44
+ break;
45
+ case 'binary':
46
+ this.storage = new storage_1.BinaryStorage(config);
47
+ break;
48
+ default:
49
+ throw new Error(`Unknown storage type: ${options.storageType}`);
50
+ }
51
+ this.lockPath = (0, path_1.join)(basePath, `${options.databaseName}.lock`);
52
+ this.locker = new lock_1.FileLocker();
53
+ // Enable transactions by default or if configured
54
+ this.txManager = new transaction_1.TransactionManager(this.storage);
55
+ }
56
+ async initialize() {
57
+ if (this.initialized)
58
+ return;
59
+ const lockDir = (0, path_1.dirname)(this.lockPath);
60
+ await (0, promises_1.mkdir)(lockDir, { recursive: true });
61
+ try {
62
+ await (0, promises_1.access)(this.lockPath);
63
+ }
64
+ catch {
65
+ await (0, promises_1.writeFile)(this.lockPath, '', 'utf8');
66
+ }
67
+ await this.locker.acquire(this.lockPath);
68
+ await this.storage.initialize();
69
+ if (this.txManager) {
70
+ await this.txManager.recover();
71
+ }
72
+ this.initialized = true;
73
+ }
74
+ collection(name) {
75
+ if (!this.initialized)
76
+ throw new Error('Database not initialized');
77
+ if (!this.collections.has(name)) {
78
+ this.collections.set(name, new collection_1.Collection(name, this.storage));
79
+ }
80
+ return this.collections.get(name);
81
+ }
82
+ async transaction(fn) {
83
+ // Acquire lock for transaction serialization
84
+ let releaseLock;
85
+ const acquire = new Promise(resolve => releaseLock = resolve);
86
+ const currentLock = this.txLock;
87
+ this.txLock = this.txLock.then(() => acquire);
88
+ await currentLock;
89
+ try {
90
+ if (!this.txManager) {
91
+ throw new Error('Transactions not enabled');
92
+ }
93
+ const txId = await this.txManager.begin();
94
+ const context = new transaction_context_1.TransactionContext(txId, this.txManager, this.storage, async (col, id) => {
95
+ return this.collection(col).findOne({ _id: id });
96
+ });
97
+ try {
98
+ const result = await fn(context);
99
+ const ops = await this.txManager.commit(txId);
100
+ // Update in-memory state
101
+ for (const op of ops) {
102
+ const collection = this.collections.get(op.collection);
103
+ if (collection) {
104
+ let logOp;
105
+ switch (op.type) {
106
+ case 'insert':
107
+ logOp = 'INSERT';
108
+ break;
109
+ case 'update':
110
+ logOp = 'UPDATE';
111
+ break;
112
+ case 'delete':
113
+ logOp = 'DELETE';
114
+ break;
115
+ default: continue;
116
+ }
117
+ collection.applyLogEntry({
118
+ op: logOp,
119
+ collection: op.collection,
120
+ id: op.id,
121
+ data: op.newData,
122
+ checksum: '',
123
+ timestamp: Date.now(),
124
+ txId
125
+ });
126
+ }
127
+ }
128
+ return result;
129
+ }
130
+ catch (error) {
131
+ await this.txManager.rollback(txId);
132
+ throw error;
133
+ }
134
+ }
135
+ finally {
136
+ releaseLock();
137
+ }
138
+ }
139
+ async save() {
140
+ await this.storage.flush();
141
+ }
142
+ async compact() {
143
+ if (this.storage.compact) {
144
+ await this.storage.compact();
145
+ }
146
+ }
147
+ async close() {
148
+ await this.storage.close();
149
+ await this.locker.release(this.lockPath);
150
+ this.initialized = false;
151
+ }
152
+ get stats() {
153
+ return {
154
+ collections: this.collections.size,
155
+ storage: this.options.storageType
156
+ };
157
+ }
158
+ }
159
+ exports.Database = Database;
160
+ // Factory function para conveniência
161
+ async function createDatabase(options) {
162
+ const db = new Database(options);
163
+ await db.initialize();
164
+ return db;
165
+ }
@@ -0,0 +1,20 @@
1
+ export interface IndexDefinition {
2
+ fields: string[];
3
+ unique: boolean;
4
+ sparse: boolean;
5
+ name: string;
6
+ }
7
+ export declare class IndexManager {
8
+ private indexes;
9
+ private indexDefs;
10
+ createIndex(collection: string, fields: string | string[], options?: {
11
+ unique?: boolean;
12
+ sparse?: boolean;
13
+ }): void;
14
+ indexDocument(collection: string, id: string, doc: Record<string, any>): void;
15
+ removeDocument(collection: string, id: string, doc: Record<string, any>): void;
16
+ queryByIndex(collection: string, filter: Record<string, any>): Set<string> | null;
17
+ private extractKeyValue;
18
+ private getNestedValue;
19
+ getIndexDefinitions(collection: string): IndexDefinition[];
20
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IndexManager = void 0;
4
+ class IndexManager {
5
+ indexes = new Map();
6
+ indexDefs = new Map();
7
+ createIndex(collection, fields, options) {
8
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
9
+ const key = fieldArray.join(':');
10
+ if (!this.indexes.has(collection)) {
11
+ this.indexes.set(collection, new Map());
12
+ this.indexDefs.set(collection, []);
13
+ }
14
+ const colIndexes = this.indexes.get(collection);
15
+ if (colIndexes.has(key)) {
16
+ throw new Error(`Index already exists on ${key}`);
17
+ }
18
+ colIndexes.set(key, new Map());
19
+ this.indexDefs.get(collection).push({
20
+ fields: fieldArray,
21
+ unique: options?.unique ?? false,
22
+ sparse: options?.sparse ?? false,
23
+ name: key
24
+ });
25
+ }
26
+ indexDocument(collection, id, doc) {
27
+ const colIndexes = this.indexes.get(collection);
28
+ if (!colIndexes)
29
+ return;
30
+ for (const [key, indexMap] of colIndexes) {
31
+ const value = this.extractKeyValue(doc, key);
32
+ if (value === undefined)
33
+ continue;
34
+ const valueKey = JSON.stringify(value);
35
+ if (!indexMap.has(valueKey)) {
36
+ indexMap.set(valueKey, new Set());
37
+ }
38
+ indexMap.get(valueKey).add(id);
39
+ }
40
+ }
41
+ removeDocument(collection, id, doc) {
42
+ const colIndexes = this.indexes.get(collection);
43
+ if (!colIndexes)
44
+ return;
45
+ for (const [key, indexMap] of colIndexes) {
46
+ const value = this.extractKeyValue(doc, key);
47
+ if (value === undefined)
48
+ continue;
49
+ const valueKey = JSON.stringify(value);
50
+ const set = indexMap.get(valueKey);
51
+ if (set) {
52
+ set.delete(id);
53
+ if (set.size === 0)
54
+ indexMap.delete(valueKey);
55
+ }
56
+ }
57
+ }
58
+ queryByIndex(collection, filter) {
59
+ const colIndexes = this.indexes.get(collection);
60
+ if (!colIndexes)
61
+ return null;
62
+ const candidates = [];
63
+ for (const [key, indexMap] of colIndexes) {
64
+ const filterValue = this.extractKeyValue(filter, key);
65
+ if (filterValue !== undefined) {
66
+ const result = indexMap.get(JSON.stringify(filterValue));
67
+ if (result && result.size > 0)
68
+ candidates.push(new Set(result));
69
+ }
70
+ }
71
+ if (candidates.length === 0)
72
+ return null;
73
+ if (candidates.length === 1)
74
+ return candidates[0];
75
+ return candidates.reduce((a, b) => new Set([...a].filter(x => b.has(x))));
76
+ }
77
+ extractKeyValue(doc, key) {
78
+ const fields = key.split(':');
79
+ const values = fields.map(f => this.getNestedValue(doc, f));
80
+ return fields.length === 1 ? values[0] : values;
81
+ }
82
+ getNestedValue(obj, path) {
83
+ return path.split('.').reduce((o, p) => o?.[p], obj);
84
+ }
85
+ getIndexDefinitions(collection) {
86
+ return this.indexDefs.get(collection) || [];
87
+ }
88
+ }
89
+ exports.IndexManager = IndexManager;
@@ -0,0 +1,13 @@
1
+ import { IStorage } from '../storage/base';
2
+ import { TransactionManager } from './transaction';
3
+ export declare class TransactionContext {
4
+ private txId;
5
+ private txManager;
6
+ private storage;
7
+ private getData;
8
+ constructor(txId: string, txManager: TransactionManager, storage: IStorage, getData: (collection: string, id: string) => Promise<any>);
9
+ get id(): string;
10
+ insert(collection: string, data: any): Promise<void>;
11
+ update(collection: string, id: string, data: any): Promise<void>;
12
+ delete(collection: string, id: string): Promise<void>;
13
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransactionContext = void 0;
4
+ const uuid_1 = require("uuid");
5
+ class TransactionContext {
6
+ txId;
7
+ txManager;
8
+ storage;
9
+ getData;
10
+ constructor(txId, txManager, storage, getData) {
11
+ this.txId = txId;
12
+ this.txManager = txManager;
13
+ this.storage = storage;
14
+ this.getData = getData;
15
+ }
16
+ get id() {
17
+ return this.txId;
18
+ }
19
+ async insert(collection, data) {
20
+ await this.txManager.addOperation(this.txId, {
21
+ type: 'insert',
22
+ collection,
23
+ id: data._id || (0, uuid_1.v7)(),
24
+ newData: data
25
+ });
26
+ }
27
+ async update(collection, id, data) {
28
+ const current = await this.getData(collection, id);
29
+ if (!current) {
30
+ throw new Error(`Document with id ${id} not found in collection ${collection}`);
31
+ }
32
+ const newData = { ...current, ...data };
33
+ await this.txManager.addOperation(this.txId, {
34
+ type: 'update',
35
+ collection,
36
+ id,
37
+ newData: newData
38
+ });
39
+ }
40
+ async delete(collection, id) {
41
+ await this.txManager.addOperation(this.txId, {
42
+ type: 'delete',
43
+ collection,
44
+ id
45
+ });
46
+ }
47
+ }
48
+ exports.TransactionContext = TransactionContext;
@@ -0,0 +1,25 @@
1
+ import { IStorage } from '../storage/base';
2
+ export interface Transaction {
3
+ id: string;
4
+ operations: Operation[];
5
+ status: 'pending' | 'committed' | 'aborted';
6
+ timestamp: number;
7
+ }
8
+ export interface Operation {
9
+ type: 'insert' | 'update' | 'delete';
10
+ collection: string;
11
+ id: string;
12
+ previousData?: unknown;
13
+ newData?: unknown;
14
+ }
15
+ export declare class TransactionManager {
16
+ private activeTransactions;
17
+ private storage;
18
+ constructor(storage: IStorage);
19
+ begin(): Promise<string>;
20
+ addOperation(txId: string, op: Operation): Promise<void>;
21
+ commit(txId: string): Promise<Operation[]>;
22
+ rollback(txId: string): Promise<void>;
23
+ getTransaction(txId: string): Transaction | undefined;
24
+ recover(): Promise<void>;
25
+ }