@push.rocks/smartmongo 2.2.0 → 4.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +1 -1
- package/dist_ts/index.js +3 -3
- package/dist_ts/tsmdb/engine/AggregationEngine.js +189 -0
- package/dist_ts/{congodb → tsmdb}/engine/IndexEngine.d.ts +23 -3
- package/dist_ts/tsmdb/engine/IndexEngine.js +678 -0
- package/dist_ts/tsmdb/engine/QueryEngine.js +271 -0
- package/dist_ts/tsmdb/engine/QueryPlanner.d.ts +64 -0
- package/dist_ts/tsmdb/engine/QueryPlanner.js +308 -0
- package/dist_ts/tsmdb/engine/SessionEngine.d.ts +117 -0
- package/dist_ts/tsmdb/engine/SessionEngine.js +232 -0
- package/dist_ts/{congodb → tsmdb}/engine/TransactionEngine.d.ts +1 -1
- package/dist_ts/tsmdb/engine/TransactionEngine.js +287 -0
- package/dist_ts/tsmdb/engine/UpdateEngine.js +461 -0
- package/dist_ts/{congodb/errors/CongoErrors.d.ts → tsmdb/errors/TsmdbErrors.d.ts} +16 -16
- package/dist_ts/tsmdb/errors/TsmdbErrors.js +155 -0
- package/dist_ts/{congodb → tsmdb}/index.d.ts +11 -4
- package/dist_ts/tsmdb/index.js +31 -0
- package/dist_ts/tsmdb/server/CommandRouter.d.ts +87 -0
- package/dist_ts/tsmdb/server/CommandRouter.js +222 -0
- package/dist_ts/{congodb/server/CongoServer.d.ts → tsmdb/server/TsmdbServer.d.ts} +6 -6
- package/dist_ts/tsmdb/server/TsmdbServer.js +229 -0
- package/dist_ts/{congodb → tsmdb}/server/WireProtocol.d.ts +1 -1
- package/dist_ts/tsmdb/server/WireProtocol.js +298 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/AdminHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/AdminHandler.js +668 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/AggregateHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/AggregateHandler.js +277 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/DeleteHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/DeleteHandler.js +95 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/FindHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/FindHandler.js +291 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/HelloHandler.d.ts +1 -1
- package/dist_ts/{congodb → tsmdb}/server/handlers/HelloHandler.js +2 -2
- package/dist_ts/{congodb → tsmdb}/server/handlers/IndexHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/IndexHandler.js +183 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/InsertHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/InsertHandler.js +79 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/UpdateHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/UpdateHandler.js +296 -0
- package/dist_ts/tsmdb/server/handlers/index.js +10 -0
- package/dist_ts/{congodb → tsmdb}/server/index.d.ts +2 -2
- package/dist_ts/tsmdb/server/index.js +7 -0
- package/dist_ts/{congodb → tsmdb}/storage/FileStorageAdapter.d.ts +27 -3
- package/dist_ts/tsmdb/storage/FileStorageAdapter.js +465 -0
- package/dist_ts/{congodb → tsmdb}/storage/IStorageAdapter.d.ts +7 -2
- package/dist_ts/{congodb → tsmdb}/storage/IStorageAdapter.js +1 -1
- package/dist_ts/{congodb → tsmdb}/storage/MemoryStorageAdapter.d.ts +3 -2
- package/dist_ts/tsmdb/storage/MemoryStorageAdapter.js +378 -0
- package/dist_ts/{congodb → tsmdb}/storage/OpLog.d.ts +1 -1
- package/dist_ts/tsmdb/storage/OpLog.js +221 -0
- package/dist_ts/tsmdb/storage/WAL.d.ts +117 -0
- package/dist_ts/tsmdb/storage/WAL.js +286 -0
- package/dist_ts/tsmdb/tsmdb.plugins.js +14 -0
- package/dist_ts/{congodb → tsmdb}/types/interfaces.d.ts +3 -3
- package/dist_ts/{congodb → tsmdb}/types/interfaces.js +1 -1
- package/dist_ts/tsmdb/utils/checksum.d.ts +30 -0
- package/dist_ts/tsmdb/utils/checksum.js +77 -0
- package/dist_ts/tsmdb/utils/index.d.ts +1 -0
- package/dist_ts/tsmdb/utils/index.js +2 -0
- package/package.json +1 -1
- package/readme.hints.md +7 -12
- package/readme.md +25 -25
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +2 -2
- package/ts/{congodb → tsmdb}/engine/AggregationEngine.ts +1 -1
- package/ts/tsmdb/engine/IndexEngine.ts +798 -0
- package/ts/{congodb → tsmdb}/engine/QueryEngine.ts +1 -1
- package/ts/tsmdb/engine/QueryPlanner.ts +393 -0
- package/ts/tsmdb/engine/SessionEngine.ts +292 -0
- package/ts/{congodb → tsmdb}/engine/TransactionEngine.ts +12 -12
- package/ts/{congodb → tsmdb}/engine/UpdateEngine.ts +1 -1
- package/ts/{congodb/errors/CongoErrors.ts → tsmdb/errors/TsmdbErrors.ts} +34 -34
- package/ts/{congodb → tsmdb}/index.ts +16 -7
- package/ts/{congodb → tsmdb}/server/CommandRouter.ts +114 -5
- package/ts/{congodb/server/CongoServer.ts → tsmdb/server/TsmdbServer.ts} +11 -8
- package/ts/{congodb → tsmdb}/server/WireProtocol.ts +1 -1
- package/ts/{congodb → tsmdb}/server/handlers/AdminHandler.ts +116 -11
- package/ts/{congodb → tsmdb}/server/handlers/AggregateHandler.ts +1 -1
- package/ts/{congodb → tsmdb}/server/handlers/DeleteHandler.ts +18 -3
- package/ts/{congodb → tsmdb}/server/handlers/FindHandler.ts +43 -14
- package/ts/{congodb → tsmdb}/server/handlers/HelloHandler.ts +1 -1
- package/ts/{congodb → tsmdb}/server/handlers/IndexHandler.ts +1 -1
- package/ts/{congodb → tsmdb}/server/handlers/InsertHandler.ts +7 -1
- package/ts/{congodb → tsmdb}/server/handlers/UpdateHandler.ts +34 -5
- package/ts/{congodb → tsmdb}/server/index.ts +2 -2
- package/ts/{congodb → tsmdb}/storage/FileStorageAdapter.ts +90 -7
- package/ts/{congodb → tsmdb}/storage/IStorageAdapter.ts +8 -2
- package/ts/{congodb → tsmdb}/storage/MemoryStorageAdapter.ts +14 -2
- package/ts/{congodb → tsmdb}/storage/OpLog.ts +1 -1
- package/ts/tsmdb/storage/WAL.ts +375 -0
- package/ts/{congodb → tsmdb}/types/interfaces.ts +3 -3
- package/ts/tsmdb/utils/checksum.ts +88 -0
- package/ts/tsmdb/utils/index.ts +1 -0
- package/dist_ts/congodb/congodb.plugins.js +0 -14
- package/dist_ts/congodb/engine/AggregationEngine.js +0 -189
- package/dist_ts/congodb/engine/IndexEngine.js +0 -376
- package/dist_ts/congodb/engine/QueryEngine.js +0 -271
- package/dist_ts/congodb/engine/TransactionEngine.js +0 -287
- package/dist_ts/congodb/engine/UpdateEngine.js +0 -461
- package/dist_ts/congodb/errors/CongoErrors.js +0 -155
- package/dist_ts/congodb/index.js +0 -26
- package/dist_ts/congodb/server/CommandRouter.d.ts +0 -51
- package/dist_ts/congodb/server/CommandRouter.js +0 -132
- package/dist_ts/congodb/server/CongoServer.js +0 -227
- package/dist_ts/congodb/server/WireProtocol.js +0 -298
- package/dist_ts/congodb/server/handlers/AdminHandler.js +0 -568
- package/dist_ts/congodb/server/handlers/AggregateHandler.js +0 -277
- package/dist_ts/congodb/server/handlers/DeleteHandler.js +0 -83
- package/dist_ts/congodb/server/handlers/FindHandler.js +0 -261
- package/dist_ts/congodb/server/handlers/IndexHandler.js +0 -183
- package/dist_ts/congodb/server/handlers/InsertHandler.js +0 -76
- package/dist_ts/congodb/server/handlers/UpdateHandler.js +0 -270
- package/dist_ts/congodb/server/handlers/index.js +0 -10
- package/dist_ts/congodb/server/index.js +0 -7
- package/dist_ts/congodb/storage/FileStorageAdapter.js +0 -396
- package/dist_ts/congodb/storage/MemoryStorageAdapter.js +0 -367
- package/dist_ts/congodb/storage/OpLog.js +0 -221
- package/ts/congodb/engine/IndexEngine.ts +0 -479
- /package/dist_ts/{congodb → tsmdb}/engine/AggregationEngine.d.ts +0 -0
- /package/dist_ts/{congodb → tsmdb}/engine/QueryEngine.d.ts +0 -0
- /package/dist_ts/{congodb → tsmdb}/engine/UpdateEngine.d.ts +0 -0
- /package/dist_ts/{congodb → tsmdb}/server/handlers/index.d.ts +0 -0
- /package/dist_ts/{congodb/congodb.plugins.d.ts → tsmdb/tsmdb.plugins.d.ts} +0 -0
- /package/ts/{congodb → tsmdb}/server/handlers/index.ts +0 -0
- /package/ts/{congodb/congodb.plugins.ts → tsmdb/tsmdb.plugins.ts} +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { TransactionEngine } from './TransactionEngine.js';
|
|
2
|
+
/**
|
|
3
|
+
* Session state
|
|
4
|
+
*/
|
|
5
|
+
export interface ISession {
|
|
6
|
+
/** Session ID (UUID) */
|
|
7
|
+
id: string;
|
|
8
|
+
/** Timestamp when the session was created */
|
|
9
|
+
createdAt: number;
|
|
10
|
+
/** Timestamp of the last activity */
|
|
11
|
+
lastActivityAt: number;
|
|
12
|
+
/** Current transaction ID if any */
|
|
13
|
+
txnId?: string;
|
|
14
|
+
/** Transaction number for ordering */
|
|
15
|
+
txnNumber?: number;
|
|
16
|
+
/** Whether the session is in a transaction */
|
|
17
|
+
inTransaction: boolean;
|
|
18
|
+
/** Session metadata */
|
|
19
|
+
metadata?: Record<string, any>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Session engine options
|
|
23
|
+
*/
|
|
24
|
+
export interface ISessionEngineOptions {
|
|
25
|
+
/** Session timeout in milliseconds (default: 30 minutes) */
|
|
26
|
+
sessionTimeoutMs?: number;
|
|
27
|
+
/** Interval to check for expired sessions in ms (default: 60 seconds) */
|
|
28
|
+
cleanupIntervalMs?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Session engine for managing client sessions
|
|
32
|
+
* - Tracks session lifecycle (create, touch, end)
|
|
33
|
+
* - Links sessions to transactions
|
|
34
|
+
* - Auto-aborts transactions on session expiry
|
|
35
|
+
*/
|
|
36
|
+
export declare class SessionEngine {
|
|
37
|
+
private sessions;
|
|
38
|
+
private sessionTimeoutMs;
|
|
39
|
+
private cleanupInterval?;
|
|
40
|
+
private transactionEngine?;
|
|
41
|
+
constructor(options?: ISessionEngineOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Set the transaction engine to use for auto-abort
|
|
44
|
+
*/
|
|
45
|
+
setTransactionEngine(engine: TransactionEngine): void;
|
|
46
|
+
/**
|
|
47
|
+
* Start a new session
|
|
48
|
+
*/
|
|
49
|
+
startSession(sessionId?: string, metadata?: Record<string, any>): ISession;
|
|
50
|
+
/**
|
|
51
|
+
* Get a session by ID
|
|
52
|
+
*/
|
|
53
|
+
getSession(sessionId: string): ISession | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Touch a session to update last activity time
|
|
56
|
+
*/
|
|
57
|
+
touchSession(sessionId: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* End a session explicitly
|
|
60
|
+
* This will also abort any active transaction
|
|
61
|
+
*/
|
|
62
|
+
endSession(sessionId: string): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Start a transaction in a session
|
|
65
|
+
*/
|
|
66
|
+
startTransaction(sessionId: string, txnId: string, txnNumber?: number): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* End a transaction in a session (commit or abort)
|
|
69
|
+
*/
|
|
70
|
+
endTransaction(sessionId: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Get transaction ID for a session
|
|
73
|
+
*/
|
|
74
|
+
getTransactionId(sessionId: string): string | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Check if session is in a transaction
|
|
77
|
+
*/
|
|
78
|
+
isInTransaction(sessionId: string): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Check if a session is expired
|
|
81
|
+
*/
|
|
82
|
+
isSessionExpired(session: ISession): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Cleanup expired sessions
|
|
85
|
+
* This is called periodically by the cleanup interval
|
|
86
|
+
*/
|
|
87
|
+
private cleanupExpiredSessions;
|
|
88
|
+
/**
|
|
89
|
+
* Get all active sessions
|
|
90
|
+
*/
|
|
91
|
+
listSessions(): ISession[];
|
|
92
|
+
/**
|
|
93
|
+
* Get session count
|
|
94
|
+
*/
|
|
95
|
+
getSessionCount(): number;
|
|
96
|
+
/**
|
|
97
|
+
* Get sessions with active transactions
|
|
98
|
+
*/
|
|
99
|
+
getSessionsWithTransactions(): ISession[];
|
|
100
|
+
/**
|
|
101
|
+
* Refresh session timeout
|
|
102
|
+
*/
|
|
103
|
+
refreshSession(sessionId: string): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Close the session engine and cleanup
|
|
106
|
+
*/
|
|
107
|
+
close(): void;
|
|
108
|
+
/**
|
|
109
|
+
* Get or create a session for a given session ID
|
|
110
|
+
* Useful for handling MongoDB driver session requests
|
|
111
|
+
*/
|
|
112
|
+
getOrCreateSession(sessionId: string): ISession;
|
|
113
|
+
/**
|
|
114
|
+
* Extract session ID from MongoDB lsid (logical session ID)
|
|
115
|
+
*/
|
|
116
|
+
static extractSessionId(lsid: any): string | undefined;
|
|
117
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import * as plugins from '../tsmdb.plugins.js';
|
|
2
|
+
/**
|
|
3
|
+
* Session engine for managing client sessions
|
|
4
|
+
* - Tracks session lifecycle (create, touch, end)
|
|
5
|
+
* - Links sessions to transactions
|
|
6
|
+
* - Auto-aborts transactions on session expiry
|
|
7
|
+
*/
|
|
8
|
+
export class SessionEngine {
|
|
9
|
+
sessions = new Map();
|
|
10
|
+
sessionTimeoutMs;
|
|
11
|
+
cleanupInterval;
|
|
12
|
+
transactionEngine;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.sessionTimeoutMs = options?.sessionTimeoutMs ?? 30 * 60 * 1000; // 30 minutes default
|
|
15
|
+
const cleanupIntervalMs = options?.cleanupIntervalMs ?? 60 * 1000; // 1 minute default
|
|
16
|
+
// Start cleanup interval
|
|
17
|
+
this.cleanupInterval = setInterval(() => {
|
|
18
|
+
this.cleanupExpiredSessions();
|
|
19
|
+
}, cleanupIntervalMs);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Set the transaction engine to use for auto-abort
|
|
23
|
+
*/
|
|
24
|
+
setTransactionEngine(engine) {
|
|
25
|
+
this.transactionEngine = engine;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Start a new session
|
|
29
|
+
*/
|
|
30
|
+
startSession(sessionId, metadata) {
|
|
31
|
+
const id = sessionId ?? new plugins.bson.UUID().toHexString();
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
const session = {
|
|
34
|
+
id,
|
|
35
|
+
createdAt: now,
|
|
36
|
+
lastActivityAt: now,
|
|
37
|
+
inTransaction: false,
|
|
38
|
+
metadata,
|
|
39
|
+
};
|
|
40
|
+
this.sessions.set(id, session);
|
|
41
|
+
return session;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get a session by ID
|
|
45
|
+
*/
|
|
46
|
+
getSession(sessionId) {
|
|
47
|
+
const session = this.sessions.get(sessionId);
|
|
48
|
+
if (session && this.isSessionExpired(session)) {
|
|
49
|
+
// Session expired, clean it up
|
|
50
|
+
this.endSession(sessionId);
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return session;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Touch a session to update last activity time
|
|
57
|
+
*/
|
|
58
|
+
touchSession(sessionId) {
|
|
59
|
+
const session = this.sessions.get(sessionId);
|
|
60
|
+
if (!session)
|
|
61
|
+
return false;
|
|
62
|
+
if (this.isSessionExpired(session)) {
|
|
63
|
+
this.endSession(sessionId);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
session.lastActivityAt = Date.now();
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* End a session explicitly
|
|
71
|
+
* This will also abort any active transaction
|
|
72
|
+
*/
|
|
73
|
+
async endSession(sessionId) {
|
|
74
|
+
const session = this.sessions.get(sessionId);
|
|
75
|
+
if (!session)
|
|
76
|
+
return false;
|
|
77
|
+
// If session has an active transaction, abort it
|
|
78
|
+
if (session.inTransaction && session.txnId && this.transactionEngine) {
|
|
79
|
+
try {
|
|
80
|
+
await this.transactionEngine.abortTransaction(session.txnId);
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
// Ignore abort errors during cleanup
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.sessions.delete(sessionId);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Start a transaction in a session
|
|
91
|
+
*/
|
|
92
|
+
startTransaction(sessionId, txnId, txnNumber) {
|
|
93
|
+
const session = this.sessions.get(sessionId);
|
|
94
|
+
if (!session)
|
|
95
|
+
return false;
|
|
96
|
+
if (this.isSessionExpired(session)) {
|
|
97
|
+
this.endSession(sessionId);
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
session.txnId = txnId;
|
|
101
|
+
session.txnNumber = txnNumber;
|
|
102
|
+
session.inTransaction = true;
|
|
103
|
+
session.lastActivityAt = Date.now();
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* End a transaction in a session (commit or abort)
|
|
108
|
+
*/
|
|
109
|
+
endTransaction(sessionId) {
|
|
110
|
+
const session = this.sessions.get(sessionId);
|
|
111
|
+
if (!session)
|
|
112
|
+
return false;
|
|
113
|
+
session.txnId = undefined;
|
|
114
|
+
session.txnNumber = undefined;
|
|
115
|
+
session.inTransaction = false;
|
|
116
|
+
session.lastActivityAt = Date.now();
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get transaction ID for a session
|
|
121
|
+
*/
|
|
122
|
+
getTransactionId(sessionId) {
|
|
123
|
+
const session = this.sessions.get(sessionId);
|
|
124
|
+
return session?.txnId;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Check if session is in a transaction
|
|
128
|
+
*/
|
|
129
|
+
isInTransaction(sessionId) {
|
|
130
|
+
const session = this.sessions.get(sessionId);
|
|
131
|
+
return session?.inTransaction ?? false;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check if a session is expired
|
|
135
|
+
*/
|
|
136
|
+
isSessionExpired(session) {
|
|
137
|
+
return Date.now() - session.lastActivityAt > this.sessionTimeoutMs;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Cleanup expired sessions
|
|
141
|
+
* This is called periodically by the cleanup interval
|
|
142
|
+
*/
|
|
143
|
+
async cleanupExpiredSessions() {
|
|
144
|
+
const expiredSessions = [];
|
|
145
|
+
for (const [id, session] of this.sessions) {
|
|
146
|
+
if (this.isSessionExpired(session)) {
|
|
147
|
+
expiredSessions.push(id);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// End all expired sessions (this will also abort their transactions)
|
|
151
|
+
for (const sessionId of expiredSessions) {
|
|
152
|
+
await this.endSession(sessionId);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get all active sessions
|
|
157
|
+
*/
|
|
158
|
+
listSessions() {
|
|
159
|
+
const activeSessions = [];
|
|
160
|
+
for (const session of this.sessions.values()) {
|
|
161
|
+
if (!this.isSessionExpired(session)) {
|
|
162
|
+
activeSessions.push(session);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return activeSessions;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get session count
|
|
169
|
+
*/
|
|
170
|
+
getSessionCount() {
|
|
171
|
+
return this.sessions.size;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get sessions with active transactions
|
|
175
|
+
*/
|
|
176
|
+
getSessionsWithTransactions() {
|
|
177
|
+
return this.listSessions().filter(s => s.inTransaction);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Refresh session timeout
|
|
181
|
+
*/
|
|
182
|
+
refreshSession(sessionId) {
|
|
183
|
+
return this.touchSession(sessionId);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Close the session engine and cleanup
|
|
187
|
+
*/
|
|
188
|
+
close() {
|
|
189
|
+
if (this.cleanupInterval) {
|
|
190
|
+
clearInterval(this.cleanupInterval);
|
|
191
|
+
this.cleanupInterval = undefined;
|
|
192
|
+
}
|
|
193
|
+
// Clear all sessions
|
|
194
|
+
this.sessions.clear();
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get or create a session for a given session ID
|
|
198
|
+
* Useful for handling MongoDB driver session requests
|
|
199
|
+
*/
|
|
200
|
+
getOrCreateSession(sessionId) {
|
|
201
|
+
let session = this.getSession(sessionId);
|
|
202
|
+
if (!session) {
|
|
203
|
+
session = this.startSession(sessionId);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
this.touchSession(sessionId);
|
|
207
|
+
}
|
|
208
|
+
return session;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Extract session ID from MongoDB lsid (logical session ID)
|
|
212
|
+
*/
|
|
213
|
+
static extractSessionId(lsid) {
|
|
214
|
+
if (!lsid)
|
|
215
|
+
return undefined;
|
|
216
|
+
// MongoDB session ID format: { id: UUID }
|
|
217
|
+
if (lsid.id) {
|
|
218
|
+
if (lsid.id instanceof plugins.bson.UUID) {
|
|
219
|
+
return lsid.id.toHexString();
|
|
220
|
+
}
|
|
221
|
+
if (typeof lsid.id === 'string') {
|
|
222
|
+
return lsid.id;
|
|
223
|
+
}
|
|
224
|
+
if (lsid.id.$binary?.base64) {
|
|
225
|
+
// Binary UUID format
|
|
226
|
+
return Buffer.from(lsid.id.$binary.base64, 'base64').toString('hex');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2Vzc2lvbkVuZ2luZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL3RzbWRiL2VuZ2luZS9TZXNzaW9uRW5naW5lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0scUJBQXFCLENBQUM7QUFpQy9DOzs7OztHQUtHO0FBQ0gsTUFBTSxPQUFPLGFBQWE7SUFDaEIsUUFBUSxHQUEwQixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQzVDLGdCQUFnQixDQUFTO0lBQ3pCLGVBQWUsQ0FBa0M7SUFDakQsaUJBQWlCLENBQXFCO0lBRTlDLFlBQVksT0FBK0I7UUFDekMsSUFBSSxDQUFDLGdCQUFnQixHQUFHLE9BQU8sRUFBRSxnQkFBZ0IsSUFBSSxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDLHFCQUFxQjtRQUMxRixNQUFNLGlCQUFpQixHQUFHLE9BQU8sRUFBRSxpQkFBaUIsSUFBSSxFQUFFLEdBQUcsSUFBSSxDQUFDLENBQUMsbUJBQW1CO1FBRXRGLHlCQUF5QjtRQUN6QixJQUFJLENBQUMsZUFBZSxHQUFHLFdBQVcsQ0FBQyxHQUFHLEVBQUU7WUFDdEMsSUFBSSxDQUFDLHNCQUFzQixFQUFFLENBQUM7UUFDaEMsQ0FBQyxFQUFFLGlCQUFpQixDQUFDLENBQUM7SUFDeEIsQ0FBQztJQUVEOztPQUVHO0lBQ0gsb0JBQW9CLENBQUMsTUFBeUI7UUFDNUMsSUFBSSxDQUFDLGlCQUFpQixHQUFHLE1BQU0sQ0FBQztJQUNsQyxDQUFDO0lBRUQ7O09BRUc7SUFDSCxZQUFZLENBQUMsU0FBa0IsRUFBRSxRQUE4QjtRQUM3RCxNQUFNLEVBQUUsR0FBRyxTQUFTLElBQUksSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQzlELE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUV2QixNQUFNLE9BQU8sR0FBYTtZ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
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import * as plugins from '../tsmdb.plugins.js';
|
|
2
|
+
import { TsmdbTransactionError, TsmdbWriteConflictError } from '../errors/TsmdbErrors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Transaction engine for ACID transaction support
|
|
5
|
+
*/
|
|
6
|
+
export class TransactionEngine {
|
|
7
|
+
storage;
|
|
8
|
+
transactions = new Map();
|
|
9
|
+
txnCounter = 0;
|
|
10
|
+
constructor(storage) {
|
|
11
|
+
this.storage = storage;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Start a new transaction
|
|
15
|
+
*/
|
|
16
|
+
startTransaction(sessionId, options) {
|
|
17
|
+
this.txnCounter++;
|
|
18
|
+
const txnId = `txn_${sessionId}_${this.txnCounter}`;
|
|
19
|
+
const transaction = {
|
|
20
|
+
id: txnId,
|
|
21
|
+
sessionId,
|
|
22
|
+
startTime: new plugins.bson.Timestamp({ t: Math.floor(Date.now() / 1000), i: this.txnCounter }),
|
|
23
|
+
status: 'active',
|
|
24
|
+
readSet: new Map(),
|
|
25
|
+
writeSet: new Map(),
|
|
26
|
+
snapshots: new Map(),
|
|
27
|
+
};
|
|
28
|
+
this.transactions.set(txnId, transaction);
|
|
29
|
+
return txnId;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get a transaction by ID
|
|
33
|
+
*/
|
|
34
|
+
getTransaction(txnId) {
|
|
35
|
+
return this.transactions.get(txnId);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Check if a transaction is active
|
|
39
|
+
*/
|
|
40
|
+
isActive(txnId) {
|
|
41
|
+
const txn = this.transactions.get(txnId);
|
|
42
|
+
return txn?.status === 'active';
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get or create a snapshot for a namespace
|
|
46
|
+
*/
|
|
47
|
+
async getSnapshot(txnId, dbName, collName) {
|
|
48
|
+
const txn = this.transactions.get(txnId);
|
|
49
|
+
if (!txn || txn.status !== 'active') {
|
|
50
|
+
throw new TsmdbTransactionError('Transaction is not active');
|
|
51
|
+
}
|
|
52
|
+
const ns = `${dbName}.${collName}`;
|
|
53
|
+
if (!txn.snapshots.has(ns)) {
|
|
54
|
+
const snapshot = await this.storage.createSnapshot(dbName, collName);
|
|
55
|
+
txn.snapshots.set(ns, snapshot);
|
|
56
|
+
}
|
|
57
|
+
// Apply transaction writes to snapshot
|
|
58
|
+
const snapshot = txn.snapshots.get(ns);
|
|
59
|
+
const writes = txn.writeSet.get(ns);
|
|
60
|
+
if (!writes) {
|
|
61
|
+
return snapshot;
|
|
62
|
+
}
|
|
63
|
+
// Create a modified view of the snapshot
|
|
64
|
+
const result = [];
|
|
65
|
+
const deletedIds = new Set();
|
|
66
|
+
const modifiedDocs = new Map();
|
|
67
|
+
for (const [idStr, write] of writes) {
|
|
68
|
+
if (write.op === 'delete') {
|
|
69
|
+
deletedIds.add(idStr);
|
|
70
|
+
}
|
|
71
|
+
else if (write.op === 'update' || write.op === 'insert') {
|
|
72
|
+
if (write.doc) {
|
|
73
|
+
modifiedDocs.set(idStr, write.doc);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Add existing documents (not deleted, possibly modified)
|
|
78
|
+
for (const doc of snapshot) {
|
|
79
|
+
const idStr = doc._id.toHexString();
|
|
80
|
+
if (deletedIds.has(idStr)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (modifiedDocs.has(idStr)) {
|
|
84
|
+
result.push(modifiedDocs.get(idStr));
|
|
85
|
+
modifiedDocs.delete(idStr);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
result.push(doc);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Add new documents (inserts)
|
|
92
|
+
for (const doc of modifiedDocs.values()) {
|
|
93
|
+
result.push(doc);
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Record a read operation
|
|
99
|
+
*/
|
|
100
|
+
recordRead(txnId, dbName, collName, docIds) {
|
|
101
|
+
const txn = this.transactions.get(txnId);
|
|
102
|
+
if (!txn || txn.status !== 'active')
|
|
103
|
+
return;
|
|
104
|
+
const ns = `${dbName}.${collName}`;
|
|
105
|
+
if (!txn.readSet.has(ns)) {
|
|
106
|
+
txn.readSet.set(ns, new Set());
|
|
107
|
+
}
|
|
108
|
+
const readSet = txn.readSet.get(ns);
|
|
109
|
+
for (const id of docIds) {
|
|
110
|
+
readSet.add(id);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Record a write operation (insert)
|
|
115
|
+
*/
|
|
116
|
+
recordInsert(txnId, dbName, collName, doc) {
|
|
117
|
+
const txn = this.transactions.get(txnId);
|
|
118
|
+
if (!txn || txn.status !== 'active') {
|
|
119
|
+
throw new TsmdbTransactionError('Transaction is not active');
|
|
120
|
+
}
|
|
121
|
+
const ns = `${dbName}.${collName}`;
|
|
122
|
+
if (!txn.writeSet.has(ns)) {
|
|
123
|
+
txn.writeSet.set(ns, new Map());
|
|
124
|
+
}
|
|
125
|
+
txn.writeSet.get(ns).set(doc._id.toHexString(), {
|
|
126
|
+
op: 'insert',
|
|
127
|
+
doc,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Record a write operation (update)
|
|
132
|
+
*/
|
|
133
|
+
recordUpdate(txnId, dbName, collName, originalDoc, updatedDoc) {
|
|
134
|
+
const txn = this.transactions.get(txnId);
|
|
135
|
+
if (!txn || txn.status !== 'active') {
|
|
136
|
+
throw new TsmdbTransactionError('Transaction is not active');
|
|
137
|
+
}
|
|
138
|
+
const ns = `${dbName}.${collName}`;
|
|
139
|
+
if (!txn.writeSet.has(ns)) {
|
|
140
|
+
txn.writeSet.set(ns, new Map());
|
|
141
|
+
}
|
|
142
|
+
const idStr = originalDoc._id.toHexString();
|
|
143
|
+
const existing = txn.writeSet.get(ns).get(idStr);
|
|
144
|
+
// If we already have a write for this document, update it
|
|
145
|
+
if (existing) {
|
|
146
|
+
existing.doc = updatedDoc;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
txn.writeSet.get(ns).set(idStr, {
|
|
150
|
+
op: 'update',
|
|
151
|
+
doc: updatedDoc,
|
|
152
|
+
originalDoc,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Record a write operation (delete)
|
|
158
|
+
*/
|
|
159
|
+
recordDelete(txnId, dbName, collName, doc) {
|
|
160
|
+
const txn = this.transactions.get(txnId);
|
|
161
|
+
if (!txn || txn.status !== 'active') {
|
|
162
|
+
throw new TsmdbTransactionError('Transaction is not active');
|
|
163
|
+
}
|
|
164
|
+
const ns = `${dbName}.${collName}`;
|
|
165
|
+
if (!txn.writeSet.has(ns)) {
|
|
166
|
+
txn.writeSet.set(ns, new Map());
|
|
167
|
+
}
|
|
168
|
+
const idStr = doc._id.toHexString();
|
|
169
|
+
const existing = txn.writeSet.get(ns).get(idStr);
|
|
170
|
+
if (existing && existing.op === 'insert') {
|
|
171
|
+
// If we inserted and then deleted, just remove the write
|
|
172
|
+
txn.writeSet.get(ns).delete(idStr);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
txn.writeSet.get(ns).set(idStr, {
|
|
176
|
+
op: 'delete',
|
|
177
|
+
originalDoc: doc,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Commit a transaction
|
|
183
|
+
*/
|
|
184
|
+
async commitTransaction(txnId) {
|
|
185
|
+
const txn = this.transactions.get(txnId);
|
|
186
|
+
if (!txn) {
|
|
187
|
+
throw new TsmdbTransactionError('Transaction not found');
|
|
188
|
+
}
|
|
189
|
+
if (txn.status !== 'active') {
|
|
190
|
+
throw new TsmdbTransactionError(`Cannot commit transaction in state: ${txn.status}`);
|
|
191
|
+
}
|
|
192
|
+
// Check for write conflicts
|
|
193
|
+
for (const [ns, writes] of txn.writeSet) {
|
|
194
|
+
const [dbName, collName] = ns.split('.');
|
|
195
|
+
const ids = Array.from(writes.keys()).map(id => new plugins.bson.ObjectId(id));
|
|
196
|
+
const hasConflicts = await this.storage.hasConflicts(dbName, collName, ids, txn.startTime);
|
|
197
|
+
if (hasConflicts) {
|
|
198
|
+
txn.status = 'aborted';
|
|
199
|
+
throw new TsmdbWriteConflictError();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Apply all writes
|
|
203
|
+
for (const [ns, writes] of txn.writeSet) {
|
|
204
|
+
const [dbName, collName] = ns.split('.');
|
|
205
|
+
for (const [idStr, write] of writes) {
|
|
206
|
+
switch (write.op) {
|
|
207
|
+
case 'insert':
|
|
208
|
+
if (write.doc) {
|
|
209
|
+
await this.storage.insertOne(dbName, collName, write.doc);
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
case 'update':
|
|
213
|
+
if (write.doc) {
|
|
214
|
+
await this.storage.updateById(dbName, collName, new plugins.bson.ObjectId(idStr), write.doc);
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
case 'delete':
|
|
218
|
+
await this.storage.deleteById(dbName, collName, new plugins.bson.ObjectId(idStr));
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
txn.status = 'committed';
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Abort a transaction
|
|
227
|
+
*/
|
|
228
|
+
async abortTransaction(txnId) {
|
|
229
|
+
const txn = this.transactions.get(txnId);
|
|
230
|
+
if (!txn) {
|
|
231
|
+
throw new TsmdbTransactionError('Transaction not found');
|
|
232
|
+
}
|
|
233
|
+
if (txn.status !== 'active') {
|
|
234
|
+
// Already committed or aborted, just return
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// Simply discard all buffered writes
|
|
238
|
+
txn.writeSet.clear();
|
|
239
|
+
txn.readSet.clear();
|
|
240
|
+
txn.snapshots.clear();
|
|
241
|
+
txn.status = 'aborted';
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* End a transaction (cleanup)
|
|
245
|
+
*/
|
|
246
|
+
endTransaction(txnId) {
|
|
247
|
+
this.transactions.delete(txnId);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get all pending writes for a namespace
|
|
251
|
+
*/
|
|
252
|
+
getPendingWrites(txnId, dbName, collName) {
|
|
253
|
+
const txn = this.transactions.get(txnId);
|
|
254
|
+
if (!txn)
|
|
255
|
+
return undefined;
|
|
256
|
+
const ns = `${dbName}.${collName}`;
|
|
257
|
+
return txn.writeSet.get(ns);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Execute a callback within a transaction, with automatic retry on conflict
|
|
261
|
+
*/
|
|
262
|
+
async withTransaction(sessionId, callback, options) {
|
|
263
|
+
const maxRetries = options?.maxRetries ?? 3;
|
|
264
|
+
let lastError;
|
|
265
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
266
|
+
const txnId = this.startTransaction(sessionId, options);
|
|
267
|
+
try {
|
|
268
|
+
const result = await callback(txnId);
|
|
269
|
+
await this.commitTransaction(txnId);
|
|
270
|
+
this.endTransaction(txnId);
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
await this.abortTransaction(txnId);
|
|
275
|
+
this.endTransaction(txnId);
|
|
276
|
+
if (error instanceof TsmdbWriteConflictError && attempt < maxRetries - 1) {
|
|
277
|
+
// Retry on write conflict
|
|
278
|
+
lastError = error;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
throw lastError || new TsmdbTransactionError('Transaction failed after max retries');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVHJhbnNhY3Rpb25FbmdpbmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy90c21kYi9lbmdpbmUvVHJhbnNhY3Rpb25FbmdpbmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxxQkFBcUIsQ0FBQztBQUcvQyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQWUxRjs7R0FFRztBQUNILE1BQU0sT0FBTyxpQkFBaUI7SUFDcEIsT0FBTyxDQUFrQjtJQUN6QixZQUFZLEdBQW1DLElBQUksR0FBRyxFQUFFLENBQUM7SUFDekQsVUFBVSxHQUFHLENBQUMsQ0FBQztJQUV2QixZQUFZLE9BQXdCO1FBQ2xDLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO0lBQ3pCLENBQUM7SUFFRDs7T0FFRztJQUNILGdCQUFnQixDQUFDLFNBQWlCLEVBQUUsT0FBNkI7UUFDL0QsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQ2xCLE1BQU0sS0FBSyxHQUFHLE9BQU8sU0FBUyxJQUFJLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUVwRCxNQUFNLFdBQVcsR0FBc0I7WUFDckMsRUFBRSxFQUFFLEtBQUs7WUFDVCxTQUFTO1lBQ1QsU0FBUyxFQUFFLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLEVBQUUsQ0FBQyxFQUFFLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztZQUMvRixNQUFNLEVBQUUsUUFBUTtZQUNoQixPQUFPLEVBQUUsSUFBSSxHQUFHLEVBQUU7WUFDbEIsUUFBUSxFQUFFLElBQUksR0FBRyxFQUFFO1lBQ25CLFNBQVMsRUFBRSxJQUFJLEdBQUcsRUFBRTtTQUNyQixDQUFDO1FBRUYsSUFBSSxD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
|