@push.rocks/smartmongo 3.0.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.
Files changed (47) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/tsmdb/engine/IndexEngine.d.ts +23 -3
  3. package/dist_ts/tsmdb/engine/IndexEngine.js +357 -55
  4. package/dist_ts/tsmdb/engine/QueryPlanner.d.ts +64 -0
  5. package/dist_ts/tsmdb/engine/QueryPlanner.js +308 -0
  6. package/dist_ts/tsmdb/engine/SessionEngine.d.ts +117 -0
  7. package/dist_ts/tsmdb/engine/SessionEngine.js +232 -0
  8. package/dist_ts/tsmdb/index.d.ts +7 -0
  9. package/dist_ts/tsmdb/index.js +6 -1
  10. package/dist_ts/tsmdb/server/CommandRouter.d.ts +36 -0
  11. package/dist_ts/tsmdb/server/CommandRouter.js +91 -1
  12. package/dist_ts/tsmdb/server/TsmdbServer.js +3 -1
  13. package/dist_ts/tsmdb/server/handlers/AdminHandler.js +106 -6
  14. package/dist_ts/tsmdb/server/handlers/DeleteHandler.js +15 -3
  15. package/dist_ts/tsmdb/server/handlers/FindHandler.js +44 -14
  16. package/dist_ts/tsmdb/server/handlers/InsertHandler.js +4 -1
  17. package/dist_ts/tsmdb/server/handlers/UpdateHandler.js +31 -5
  18. package/dist_ts/tsmdb/storage/FileStorageAdapter.d.ts +25 -1
  19. package/dist_ts/tsmdb/storage/FileStorageAdapter.js +75 -6
  20. package/dist_ts/tsmdb/storage/IStorageAdapter.d.ts +5 -0
  21. package/dist_ts/tsmdb/storage/MemoryStorageAdapter.d.ts +1 -0
  22. package/dist_ts/tsmdb/storage/MemoryStorageAdapter.js +12 -1
  23. package/dist_ts/tsmdb/storage/WAL.d.ts +117 -0
  24. package/dist_ts/tsmdb/storage/WAL.js +286 -0
  25. package/dist_ts/tsmdb/utils/checksum.d.ts +30 -0
  26. package/dist_ts/tsmdb/utils/checksum.js +77 -0
  27. package/dist_ts/tsmdb/utils/index.d.ts +1 -0
  28. package/dist_ts/tsmdb/utils/index.js +2 -0
  29. package/package.json +1 -1
  30. package/ts/00_commitinfo_data.ts +1 -1
  31. package/ts/tsmdb/engine/IndexEngine.ts +375 -56
  32. package/ts/tsmdb/engine/QueryPlanner.ts +393 -0
  33. package/ts/tsmdb/engine/SessionEngine.ts +292 -0
  34. package/ts/tsmdb/index.ts +9 -0
  35. package/ts/tsmdb/server/CommandRouter.ts +109 -0
  36. package/ts/tsmdb/server/TsmdbServer.ts +3 -0
  37. package/ts/tsmdb/server/handlers/AdminHandler.ts +110 -5
  38. package/ts/tsmdb/server/handlers/DeleteHandler.ts +17 -2
  39. package/ts/tsmdb/server/handlers/FindHandler.ts +42 -13
  40. package/ts/tsmdb/server/handlers/InsertHandler.ts +6 -0
  41. package/ts/tsmdb/server/handlers/UpdateHandler.ts +33 -4
  42. package/ts/tsmdb/storage/FileStorageAdapter.ts +88 -5
  43. package/ts/tsmdb/storage/IStorageAdapter.ts +6 -0
  44. package/ts/tsmdb/storage/MemoryStorageAdapter.ts +12 -0
  45. package/ts/tsmdb/storage/WAL.ts +375 -0
  46. package/ts/tsmdb/utils/checksum.ts +88 -0
  47. package/ts/tsmdb/utils/index.ts +1 -0
@@ -0,0 +1,64 @@
1
+ import type { Document } from '../types/interfaces.js';
2
+ import { IndexEngine } from './IndexEngine.js';
3
+ /**
4
+ * Query execution plan types
5
+ */
6
+ export type TQueryPlanType = 'IXSCAN' | 'COLLSCAN' | 'FETCH' | 'IXSCAN_RANGE';
7
+ /**
8
+ * Represents a query execution plan
9
+ */
10
+ export interface IQueryPlan {
11
+ /** The type of scan used */
12
+ type: TQueryPlanType;
13
+ /** Index name if using an index */
14
+ indexName?: string;
15
+ /** Index key specification */
16
+ indexKey?: Record<string, 1 | -1 | string>;
17
+ /** Whether the query can be fully satisfied by the index */
18
+ indexCovering: boolean;
19
+ /** Estimated selectivity (0-1, lower is more selective) */
20
+ selectivity: number;
21
+ /** Whether range operators are used */
22
+ usesRange: boolean;
23
+ /** Fields used from the index */
24
+ indexFieldsUsed: string[];
25
+ /** Filter conditions that must be applied post-index lookup */
26
+ residualFilter?: Document;
27
+ /** Explanation for debugging */
28
+ explanation: string;
29
+ }
30
+ /**
31
+ * QueryPlanner - Analyzes queries and selects optimal execution plans
32
+ */
33
+ export declare class QueryPlanner {
34
+ private indexEngine;
35
+ constructor(indexEngine: IndexEngine);
36
+ /**
37
+ * Generate an execution plan for a query filter
38
+ */
39
+ plan(filter: Document): Promise<IQueryPlan>;
40
+ /**
41
+ * Analyze filter to extract operator information per field
42
+ */
43
+ private analyzeFilter;
44
+ /**
45
+ * Score an index for the given filter
46
+ */
47
+ private scoreIndex;
48
+ /**
49
+ * Calculate overall score for a plan (higher is better)
50
+ */
51
+ private calculateScore;
52
+ /**
53
+ * Explain a query - returns detailed plan information
54
+ */
55
+ explain(filter: Document): Promise<{
56
+ queryPlanner: {
57
+ plannerVersion: number;
58
+ namespace: string;
59
+ indexFilterSet: boolean;
60
+ winningPlan: IQueryPlan;
61
+ rejectedPlans: IQueryPlan[];
62
+ };
63
+ }>;
64
+ }
@@ -0,0 +1,308 @@
1
+ import * as plugins from '../tsmdb.plugins.js';
2
+ import { IndexEngine } from './IndexEngine.js';
3
+ /**
4
+ * QueryPlanner - Analyzes queries and selects optimal execution plans
5
+ */
6
+ export class QueryPlanner {
7
+ indexEngine;
8
+ constructor(indexEngine) {
9
+ this.indexEngine = indexEngine;
10
+ }
11
+ /**
12
+ * Generate an execution plan for a query filter
13
+ */
14
+ async plan(filter) {
15
+ await this.indexEngine['initialize']();
16
+ // Empty filter = full collection scan
17
+ if (!filter || Object.keys(filter).length === 0) {
18
+ return {
19
+ type: 'COLLSCAN',
20
+ indexCovering: false,
21
+ selectivity: 1.0,
22
+ usesRange: false,
23
+ indexFieldsUsed: [],
24
+ explanation: 'No filter specified, full collection scan required',
25
+ };
26
+ }
27
+ // Analyze the filter
28
+ const operatorInfo = this.analyzeFilter(filter);
29
+ // Get available indexes
30
+ const indexes = await this.indexEngine.listIndexes();
31
+ // Score each index
32
+ let bestPlan = null;
33
+ let bestScore = -1;
34
+ for (const index of indexes) {
35
+ const plan = this.scoreIndex(index, operatorInfo, filter);
36
+ if (plan.selectivity < 1.0) {
37
+ const score = this.calculateScore(plan);
38
+ if (score > bestScore) {
39
+ bestScore = score;
40
+ bestPlan = plan;
41
+ }
42
+ }
43
+ }
44
+ // If no suitable index found, fall back to collection scan
45
+ if (!bestPlan || bestScore <= 0) {
46
+ return {
47
+ type: 'COLLSCAN',
48
+ indexCovering: false,
49
+ selectivity: 1.0,
50
+ usesRange: false,
51
+ indexFieldsUsed: [],
52
+ explanation: 'No suitable index found for this query',
53
+ };
54
+ }
55
+ return bestPlan;
56
+ }
57
+ /**
58
+ * Analyze filter to extract operator information per field
59
+ */
60
+ analyzeFilter(filter, prefix = '') {
61
+ const result = new Map();
62
+ for (const [key, value] of Object.entries(filter)) {
63
+ // Skip logical operators at the top level
64
+ if (key.startsWith('$')) {
65
+ if (key === '$and' && Array.isArray(value)) {
66
+ // Merge $and conditions
67
+ for (const subFilter of value) {
68
+ const subInfo = this.analyzeFilter(subFilter, prefix);
69
+ for (const [field, info] of subInfo) {
70
+ if (result.has(field)) {
71
+ // Merge operators
72
+ const existing = result.get(field);
73
+ existing.operators.push(...info.operators);
74
+ existing.equality = existing.equality || info.equality;
75
+ existing.range = existing.range || info.range;
76
+ existing.in = existing.in || info.in;
77
+ Object.assign(existing.values, info.values);
78
+ }
79
+ else {
80
+ result.set(field, info);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ continue;
86
+ }
87
+ const fullKey = prefix ? `${prefix}.${key}` : key;
88
+ const info = {
89
+ field: fullKey,
90
+ operators: [],
91
+ equality: false,
92
+ range: false,
93
+ in: false,
94
+ exists: false,
95
+ regex: false,
96
+ values: {},
97
+ };
98
+ if (typeof value !== 'object' || value === null || value instanceof plugins.bson.ObjectId || value instanceof Date) {
99
+ // Direct equality
100
+ info.equality = true;
101
+ info.operators.push('$eq');
102
+ info.values['$eq'] = value;
103
+ }
104
+ else if (Array.isArray(value)) {
105
+ // Array equality (rare, but possible)
106
+ info.equality = true;
107
+ info.operators.push('$eq');
108
+ info.values['$eq'] = value;
109
+ }
110
+ else {
111
+ // Operator object
112
+ for (const [op, opValue] of Object.entries(value)) {
113
+ if (op.startsWith('$')) {
114
+ info.operators.push(op);
115
+ info.values[op] = opValue;
116
+ switch (op) {
117
+ case '$eq':
118
+ info.equality = true;
119
+ break;
120
+ case '$ne':
121
+ case '$not':
122
+ // These can use indexes but with low selectivity
123
+ break;
124
+ case '$in':
125
+ info.in = true;
126
+ break;
127
+ case '$nin':
128
+ // Can't efficiently use indexes
129
+ break;
130
+ case '$gt':
131
+ case '$gte':
132
+ case '$lt':
133
+ case '$lte':
134
+ info.range = true;
135
+ break;
136
+ case '$exists':
137
+ info.exists = true;
138
+ break;
139
+ case '$regex':
140
+ info.regex = true;
141
+ break;
142
+ }
143
+ }
144
+ else {
145
+ // Nested object - recurse
146
+ const nestedInfo = this.analyzeFilter({ [op]: opValue }, fullKey);
147
+ for (const [nestedField, nestedFieldInfo] of nestedInfo) {
148
+ result.set(nestedField, nestedFieldInfo);
149
+ }
150
+ }
151
+ }
152
+ }
153
+ if (info.operators.length > 0) {
154
+ result.set(fullKey, info);
155
+ }
156
+ }
157
+ return result;
158
+ }
159
+ /**
160
+ * Score an index for the given filter
161
+ */
162
+ scoreIndex(index, operatorInfo, filter) {
163
+ const indexFields = Object.keys(index.key);
164
+ const usedFields = [];
165
+ let usesRange = false;
166
+ let canUseIndex = true;
167
+ let selectivity = 1.0;
168
+ let residualFilter;
169
+ // Check each index field in order
170
+ for (const field of indexFields) {
171
+ const info = operatorInfo.get(field);
172
+ if (!info) {
173
+ // Index field not in filter - stop here
174
+ break;
175
+ }
176
+ usedFields.push(field);
177
+ // Calculate selectivity based on operator
178
+ if (info.equality) {
179
+ // Equality has high selectivity
180
+ selectivity *= 0.01; // Assume 1% match
181
+ }
182
+ else if (info.in) {
183
+ // $in selectivity depends on array size
184
+ const inValues = info.values['$in'];
185
+ if (Array.isArray(inValues)) {
186
+ selectivity *= Math.min(0.5, inValues.length * 0.01);
187
+ }
188
+ else {
189
+ selectivity *= 0.1;
190
+ }
191
+ }
192
+ else if (info.range) {
193
+ // Range queries have moderate selectivity
194
+ selectivity *= 0.25;
195
+ usesRange = true;
196
+ // After range, can't use more index fields efficiently
197
+ break;
198
+ }
199
+ else if (info.exists) {
200
+ // $exists can use sparse indexes
201
+ selectivity *= 0.5;
202
+ }
203
+ else {
204
+ // Other operators may not be indexable
205
+ canUseIndex = false;
206
+ break;
207
+ }
208
+ }
209
+ if (!canUseIndex || usedFields.length === 0) {
210
+ return {
211
+ type: 'COLLSCAN',
212
+ indexCovering: false,
213
+ selectivity: 1.0,
214
+ usesRange: false,
215
+ indexFieldsUsed: [],
216
+ explanation: `Index ${index.name} cannot be used for this query`,
217
+ };
218
+ }
219
+ // Build residual filter for conditions not covered by index
220
+ const coveredFields = new Set(usedFields);
221
+ const residualConditions = {};
222
+ for (const [field, info] of operatorInfo) {
223
+ if (!coveredFields.has(field)) {
224
+ // This field isn't covered by the index
225
+ if (info.equality) {
226
+ residualConditions[field] = info.values['$eq'];
227
+ }
228
+ else {
229
+ residualConditions[field] = info.values;
230
+ }
231
+ }
232
+ }
233
+ if (Object.keys(residualConditions).length > 0) {
234
+ residualFilter = residualConditions;
235
+ }
236
+ // Unique indexes have better selectivity for equality
237
+ if (index.unique && usedFields.length === indexFields.length) {
238
+ selectivity = Math.min(selectivity, 0.001); // At most 1 document
239
+ }
240
+ return {
241
+ type: usesRange ? 'IXSCAN_RANGE' : 'IXSCAN',
242
+ indexName: index.name,
243
+ indexKey: index.key,
244
+ indexCovering: Object.keys(residualConditions).length === 0,
245
+ selectivity,
246
+ usesRange,
247
+ indexFieldsUsed: usedFields,
248
+ residualFilter,
249
+ explanation: `Using index ${index.name} on fields [${usedFields.join(', ')}]`,
250
+ };
251
+ }
252
+ /**
253
+ * Calculate overall score for a plan (higher is better)
254
+ */
255
+ calculateScore(plan) {
256
+ let score = 0;
257
+ // Lower selectivity is better (fewer documents to fetch)
258
+ score += (1 - plan.selectivity) * 100;
259
+ // Index covering queries are best
260
+ if (plan.indexCovering) {
261
+ score += 50;
262
+ }
263
+ // More index fields used is better
264
+ score += plan.indexFieldsUsed.length * 10;
265
+ // Equality scans are better than range scans
266
+ if (!plan.usesRange) {
267
+ score += 20;
268
+ }
269
+ return score;
270
+ }
271
+ /**
272
+ * Explain a query - returns detailed plan information
273
+ */
274
+ async explain(filter) {
275
+ await this.indexEngine['initialize']();
276
+ // Analyze the filter
277
+ const operatorInfo = this.analyzeFilter(filter);
278
+ // Get available indexes
279
+ const indexes = await this.indexEngine.listIndexes();
280
+ // Score all indexes
281
+ const plans = [];
282
+ for (const index of indexes) {
283
+ const plan = this.scoreIndex(index, operatorInfo, filter);
284
+ plans.push(plan);
285
+ }
286
+ // Add collection scan as fallback
287
+ plans.push({
288
+ type: 'COLLSCAN',
289
+ indexCovering: false,
290
+ selectivity: 1.0,
291
+ usesRange: false,
292
+ indexFieldsUsed: [],
293
+ explanation: 'Full collection scan',
294
+ });
295
+ // Sort by score (best first)
296
+ plans.sort((a, b) => this.calculateScore(b) - this.calculateScore(a));
297
+ return {
298
+ queryPlanner: {
299
+ plannerVersion: 1,
300
+ namespace: `${this.indexEngine['dbName']}.${this.indexEngine['collName']}`,
301
+ indexFilterSet: false,
302
+ winningPlan: plans[0],
303
+ rejectedPlans: plans.slice(1),
304
+ },
305
+ };
306
+ }
307
+ }
308
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUXVlcnlQbGFubmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvdHNtZGIvZW5naW5lL1F1ZXJ5UGxhbm5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHFCQUFxQixDQUFDO0FBRS9DLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQTZDL0M7O0dBRUc7QUFDSCxNQUFNLE9BQU8sWUFBWTtJQUNmLFdBQVcsQ0FBYztJQUVqQyxZQUFZLFdBQXdCO1FBQ2xDLElBQUksQ0FBQyxXQUFXLEdBQUcsV0FBVyxDQUFDO0lBQ2pDLENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBZ0I7UUFDekIsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUM7UUFFdkMsc0NBQXNDO1FBQ3RDLElBQUksQ0FBQyxNQUFNLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDaEQsT0FBTztnQkFDTCxJQUFJLEVBQUUsVUFBVTtnQkFDaEIsYUFBYSxFQUFFLEtBQUs7Z0JBQ3BCLFdBQVcsRUFBRSxHQUFHO2dCQUNoQixTQUFTLEVBQUUsS0FBSztnQkFDaEIsZUFBZSxFQUFFLEVBQUU7Z0JBQ25CLFdBQVcsRUFBRSxvREFBb0Q7YUFDbEUsQ0FBQztRQUNKLENBQUM7UUFFRCxxQkFBcUI7UUFDckIsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUVoRCx3QkFBd0I7UUFDeEIsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBRXJELG1CQUFtQjtRQUNuQixJQUFJLFFBQVEsR0FBc0IsSUFBSSxDQUFDO1FBQ3ZDLElBQUksU0FBUyxHQUFHLENBQUMsQ0FBQyxD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
@@ -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
+ }