@push.rocks/smartdb 1.0.1 → 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/.smartconfig.json +7 -4
- package/dist_rust/rustdb_linux_amd64 +0 -0
- package/dist_rust/rustdb_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/ts_local/classes.localsmartdb.d.ts +5 -5
- package/dist_ts/ts_local/classes.localsmartdb.js +5 -6
- package/dist_ts/ts_local/plugins.d.ts +1 -2
- package/dist_ts/ts_local/plugins.js +3 -3
- package/dist_ts/ts_smartdb/index.d.ts +1 -24
- package/dist_ts/ts_smartdb/index.js +4 -29
- package/dist_ts/ts_smartdb/plugins.d.ts +2 -10
- package/dist_ts/ts_smartdb/plugins.js +3 -13
- package/dist_ts/ts_smartdb/rust-db-bridge.d.ts +43 -0
- package/dist_ts/ts_smartdb/rust-db-bridge.js +98 -0
- package/dist_ts/ts_smartdb/server/SmartdbServer.d.ts +8 -37
- package/dist_ts/ts_smartdb/server/SmartdbServer.js +49 -204
- package/dist_ts/ts_smartdb/server/index.d.ts +0 -4
- package/dist_ts/ts_smartdb/server/index.js +1 -5
- package/license +3 -1
- package/package.json +9 -12
- package/readme.md +84 -171
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/ts_local/classes.localsmartdb.ts +5 -6
- package/ts/ts_local/plugins.ts +1 -3
- package/ts/ts_smartdb/index.ts +3 -41
- package/ts/ts_smartdb/plugins.ts +2 -15
- package/ts/ts_smartdb/rust-db-bridge.ts +138 -0
- package/ts/ts_smartdb/server/SmartdbServer.ts +53 -248
- package/ts/ts_smartdb/server/index.ts +0 -7
- package/dist_ts/ts_smartdb/engine/AggregationEngine.d.ts +0 -66
- package/dist_ts/ts_smartdb/engine/AggregationEngine.js +0 -189
- package/dist_ts/ts_smartdb/engine/IndexEngine.d.ts +0 -97
- package/dist_ts/ts_smartdb/engine/IndexEngine.js +0 -678
- package/dist_ts/ts_smartdb/engine/QueryEngine.d.ts +0 -54
- package/dist_ts/ts_smartdb/engine/QueryEngine.js +0 -271
- package/dist_ts/ts_smartdb/engine/QueryPlanner.d.ts +0 -64
- package/dist_ts/ts_smartdb/engine/QueryPlanner.js +0 -308
- package/dist_ts/ts_smartdb/engine/SessionEngine.d.ts +0 -117
- package/dist_ts/ts_smartdb/engine/SessionEngine.js +0 -232
- package/dist_ts/ts_smartdb/engine/TransactionEngine.d.ts +0 -85
- package/dist_ts/ts_smartdb/engine/TransactionEngine.js +0 -287
- package/dist_ts/ts_smartdb/engine/UpdateEngine.d.ts +0 -47
- package/dist_ts/ts_smartdb/engine/UpdateEngine.js +0 -461
- package/dist_ts/ts_smartdb/errors/SmartdbErrors.d.ts +0 -100
- package/dist_ts/ts_smartdb/errors/SmartdbErrors.js +0 -155
- package/dist_ts/ts_smartdb/server/CommandRouter.d.ts +0 -87
- package/dist_ts/ts_smartdb/server/CommandRouter.js +0 -222
- package/dist_ts/ts_smartdb/server/WireProtocol.d.ts +0 -117
- package/dist_ts/ts_smartdb/server/WireProtocol.js +0 -298
- package/dist_ts/ts_smartdb/server/handlers/AdminHandler.d.ts +0 -100
- package/dist_ts/ts_smartdb/server/handlers/AdminHandler.js +0 -668
- package/dist_ts/ts_smartdb/server/handlers/AggregateHandler.d.ts +0 -31
- package/dist_ts/ts_smartdb/server/handlers/AggregateHandler.js +0 -277
- package/dist_ts/ts_smartdb/server/handlers/DeleteHandler.d.ts +0 -8
- package/dist_ts/ts_smartdb/server/handlers/DeleteHandler.js +0 -95
- package/dist_ts/ts_smartdb/server/handlers/FindHandler.d.ts +0 -31
- package/dist_ts/ts_smartdb/server/handlers/FindHandler.js +0 -291
- package/dist_ts/ts_smartdb/server/handlers/HelloHandler.d.ts +0 -11
- package/dist_ts/ts_smartdb/server/handlers/HelloHandler.js +0 -62
- package/dist_ts/ts_smartdb/server/handlers/IndexHandler.d.ts +0 -20
- package/dist_ts/ts_smartdb/server/handlers/IndexHandler.js +0 -183
- package/dist_ts/ts_smartdb/server/handlers/InsertHandler.d.ts +0 -8
- package/dist_ts/ts_smartdb/server/handlers/InsertHandler.js +0 -79
- package/dist_ts/ts_smartdb/server/handlers/UpdateHandler.d.ts +0 -24
- package/dist_ts/ts_smartdb/server/handlers/UpdateHandler.js +0 -296
- package/dist_ts/ts_smartdb/server/handlers/index.d.ts +0 -8
- package/dist_ts/ts_smartdb/server/handlers/index.js +0 -10
- package/dist_ts/ts_smartdb/storage/FileStorageAdapter.d.ts +0 -85
- package/dist_ts/ts_smartdb/storage/FileStorageAdapter.js +0 -465
- package/dist_ts/ts_smartdb/storage/IStorageAdapter.d.ts +0 -145
- package/dist_ts/ts_smartdb/storage/IStorageAdapter.js +0 -2
- package/dist_ts/ts_smartdb/storage/MemoryStorageAdapter.d.ts +0 -67
- package/dist_ts/ts_smartdb/storage/MemoryStorageAdapter.js +0 -378
- package/dist_ts/ts_smartdb/storage/OpLog.d.ts +0 -93
- package/dist_ts/ts_smartdb/storage/OpLog.js +0 -221
- package/dist_ts/ts_smartdb/storage/WAL.d.ts +0 -117
- package/dist_ts/ts_smartdb/storage/WAL.js +0 -286
- package/dist_ts/ts_smartdb/types/interfaces.d.ts +0 -363
- package/dist_ts/ts_smartdb/types/interfaces.js +0 -2
- package/dist_ts/ts_smartdb/utils/checksum.d.ts +0 -30
- package/dist_ts/ts_smartdb/utils/checksum.js +0 -77
- package/dist_ts/ts_smartdb/utils/index.d.ts +0 -1
- package/dist_ts/ts_smartdb/utils/index.js +0 -2
- package/ts/ts_smartdb/engine/AggregationEngine.ts +0 -283
- package/ts/ts_smartdb/engine/IndexEngine.ts +0 -798
- package/ts/ts_smartdb/engine/QueryEngine.ts +0 -301
- package/ts/ts_smartdb/engine/QueryPlanner.ts +0 -393
- package/ts/ts_smartdb/engine/SessionEngine.ts +0 -292
- package/ts/ts_smartdb/engine/TransactionEngine.ts +0 -351
- package/ts/ts_smartdb/engine/UpdateEngine.ts +0 -506
- package/ts/ts_smartdb/errors/SmartdbErrors.ts +0 -181
- package/ts/ts_smartdb/server/CommandRouter.ts +0 -289
- package/ts/ts_smartdb/server/WireProtocol.ts +0 -416
- package/ts/ts_smartdb/server/handlers/AdminHandler.ts +0 -719
- package/ts/ts_smartdb/server/handlers/AggregateHandler.ts +0 -342
- package/ts/ts_smartdb/server/handlers/DeleteHandler.ts +0 -115
- package/ts/ts_smartdb/server/handlers/FindHandler.ts +0 -330
- package/ts/ts_smartdb/server/handlers/HelloHandler.ts +0 -78
- package/ts/ts_smartdb/server/handlers/IndexHandler.ts +0 -207
- package/ts/ts_smartdb/server/handlers/InsertHandler.ts +0 -97
- package/ts/ts_smartdb/server/handlers/UpdateHandler.ts +0 -344
- package/ts/ts_smartdb/server/handlers/index.ts +0 -10
- package/ts/ts_smartdb/storage/FileStorageAdapter.ts +0 -562
- package/ts/ts_smartdb/storage/IStorageAdapter.ts +0 -208
- package/ts/ts_smartdb/storage/MemoryStorageAdapter.ts +0 -455
- package/ts/ts_smartdb/storage/OpLog.ts +0 -282
- package/ts/ts_smartdb/storage/WAL.ts +0 -375
- package/ts/ts_smartdb/types/interfaces.ts +0 -433
- package/ts/ts_smartdb/utils/checksum.ts +0 -88
- package/ts/ts_smartdb/utils/index.ts +0 -1
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as fs from 'fs/promises';
|
|
3
|
-
import * as plugins from '../plugins.js';
|
|
4
|
-
import { WireProtocol, OP_QUERY } from './WireProtocol.js';
|
|
5
|
-
import { CommandRouter } from './CommandRouter.js';
|
|
6
|
-
import { MemoryStorageAdapter } from '../storage/MemoryStorageAdapter.js';
|
|
7
|
-
import { FileStorageAdapter } from '../storage/FileStorageAdapter.js';
|
|
8
|
-
import type { IStorageAdapter } from '../storage/IStorageAdapter.js';
|
|
1
|
+
import { RustDbBridge } from '../rust-db-bridge.js';
|
|
9
2
|
|
|
10
3
|
/**
|
|
11
4
|
* Server configuration options
|
|
@@ -28,90 +21,41 @@ export interface ISmartdbServerOptions {
|
|
|
28
21
|
}
|
|
29
22
|
|
|
30
23
|
/**
|
|
31
|
-
*
|
|
32
|
-
*/
|
|
33
|
-
interface IConnectionState {
|
|
34
|
-
id: number;
|
|
35
|
-
socket: net.Socket;
|
|
36
|
-
buffer: Buffer;
|
|
37
|
-
authenticated: boolean;
|
|
38
|
-
database: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* SmartdbServer - MongoDB Wire Protocol compatible server
|
|
24
|
+
* SmartdbServer - Wire protocol compatible database server backed by Rust
|
|
43
25
|
*
|
|
44
|
-
* This server implements the
|
|
45
|
-
*
|
|
26
|
+
* This server implements the wire protocol to allow official drivers to
|
|
27
|
+
* connect and perform operations. The core engine runs as a Rust sidecar
|
|
28
|
+
* binary managed via @push.rocks/smartrust IPC.
|
|
46
29
|
*
|
|
47
30
|
* @example
|
|
48
31
|
* ```typescript
|
|
49
|
-
* import { SmartdbServer } from '@push.rocks/
|
|
32
|
+
* import { SmartdbServer } from '@push.rocks/smartdb';
|
|
50
33
|
* import { MongoClient } from 'mongodb';
|
|
51
34
|
*
|
|
52
35
|
* const server = new SmartdbServer({ port: 27017 });
|
|
53
36
|
* await server.start();
|
|
54
37
|
*
|
|
55
|
-
* const client = new MongoClient(
|
|
38
|
+
* const client = new MongoClient(server.getConnectionUri());
|
|
56
39
|
* await client.connect();
|
|
57
40
|
* ```
|
|
58
41
|
*/
|
|
59
42
|
export class SmartdbServer {
|
|
60
|
-
private options:
|
|
61
|
-
private
|
|
62
|
-
private storage: IStorageAdapter;
|
|
63
|
-
private commandRouter: CommandRouter;
|
|
64
|
-
private connections: Map<number, IConnectionState> = new Map();
|
|
65
|
-
private connectionIdCounter = 0;
|
|
43
|
+
private options: ISmartdbServerOptions;
|
|
44
|
+
private bridge: RustDbBridge;
|
|
66
45
|
private isRunning = false;
|
|
67
|
-
private
|
|
68
|
-
private useSocket: boolean;
|
|
46
|
+
private resolvedConnectionUri = '';
|
|
69
47
|
|
|
70
48
|
constructor(options: ISmartdbServerOptions = {}) {
|
|
71
|
-
this.useSocket = !!options.socketPath;
|
|
72
49
|
this.options = {
|
|
73
50
|
port: options.port ?? 27017,
|
|
74
51
|
host: options.host ?? '127.0.0.1',
|
|
75
|
-
socketPath: options.socketPath
|
|
52
|
+
socketPath: options.socketPath,
|
|
76
53
|
storage: options.storage ?? 'memory',
|
|
77
54
|
storagePath: options.storagePath ?? './data',
|
|
78
|
-
persistPath: options.persistPath
|
|
55
|
+
persistPath: options.persistPath,
|
|
79
56
|
persistIntervalMs: options.persistIntervalMs ?? 60000,
|
|
80
57
|
};
|
|
81
|
-
|
|
82
|
-
// Create storage adapter
|
|
83
|
-
if (this.options.storage === 'file') {
|
|
84
|
-
this.storage = new FileStorageAdapter(this.options.storagePath);
|
|
85
|
-
} else {
|
|
86
|
-
this.storage = new MemoryStorageAdapter({
|
|
87
|
-
persistPath: this.options.persistPath || undefined,
|
|
88
|
-
persistIntervalMs: this.options.persistPath ? this.options.persistIntervalMs : undefined,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Create command router
|
|
93
|
-
this.commandRouter = new CommandRouter(this.storage, this);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Get the storage adapter (for testing/debugging)
|
|
98
|
-
*/
|
|
99
|
-
getStorage(): IStorageAdapter {
|
|
100
|
-
return this.storage;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Get server uptime in seconds
|
|
105
|
-
*/
|
|
106
|
-
getUptime(): number {
|
|
107
|
-
return Math.floor((Date.now() - this.startTime.getTime()) / 1000);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Get current connection count
|
|
112
|
-
*/
|
|
113
|
-
getConnectionCount(): number {
|
|
114
|
-
return this.connections.size;
|
|
58
|
+
this.bridge = new RustDbBridge();
|
|
115
59
|
}
|
|
116
60
|
|
|
117
61
|
/**
|
|
@@ -122,213 +66,74 @@ export class SmartdbServer {
|
|
|
122
66
|
throw new Error('Server is already running');
|
|
123
67
|
}
|
|
124
68
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
await fs.unlink(this.options.socketPath);
|
|
132
|
-
} catch (err: any) {
|
|
133
|
-
// Ignore ENOENT (file doesn't exist)
|
|
134
|
-
if (err.code !== 'ENOENT') {
|
|
135
|
-
throw err;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
69
|
+
const spawned = await this.bridge.spawn();
|
|
70
|
+
if (!spawned) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
'smartdb Rust binary not found. Set SMARTDB_RUST_BINARY env var, ' +
|
|
73
|
+
'install the platform package, or build locally with `tsrust`.'
|
|
74
|
+
);
|
|
138
75
|
}
|
|
139
76
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this.server.on('error', (err) => {
|
|
146
|
-
if (!this.isRunning) {
|
|
147
|
-
reject(err);
|
|
148
|
-
} else {
|
|
149
|
-
console.error('Server error:', err);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
if (this.useSocket && this.options.socketPath) {
|
|
154
|
-
// Listen on Unix socket
|
|
155
|
-
this.server.listen(this.options.socketPath, () => {
|
|
156
|
-
this.isRunning = true;
|
|
157
|
-
this.startTime = new Date();
|
|
158
|
-
resolve();
|
|
159
|
-
});
|
|
160
|
-
} else {
|
|
161
|
-
// Listen on TCP
|
|
162
|
-
this.server.listen(this.options.port, this.options.host, () => {
|
|
163
|
-
this.isRunning = true;
|
|
164
|
-
this.startTime = new Date();
|
|
165
|
-
resolve();
|
|
166
|
-
});
|
|
77
|
+
// Forward unexpected exit
|
|
78
|
+
this.bridge.on('exit', (code: number | null, signal: string | null) => {
|
|
79
|
+
if (this.isRunning) {
|
|
80
|
+
console.error(`smartdb Rust process exited unexpectedly (code=${code}, signal=${signal})`);
|
|
167
81
|
}
|
|
168
82
|
});
|
|
83
|
+
|
|
84
|
+
// Send config, get back connectionUri
|
|
85
|
+
const result = await this.bridge.startDb({
|
|
86
|
+
port: this.options.port,
|
|
87
|
+
host: this.options.host,
|
|
88
|
+
socketPath: this.options.socketPath,
|
|
89
|
+
storage: this.options.storage ?? 'memory',
|
|
90
|
+
storagePath: this.options.storagePath,
|
|
91
|
+
persistPath: this.options.persistPath,
|
|
92
|
+
persistIntervalMs: this.options.persistIntervalMs,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
this.resolvedConnectionUri = result.connectionUri;
|
|
96
|
+
this.isRunning = true;
|
|
169
97
|
}
|
|
170
98
|
|
|
171
99
|
/**
|
|
172
100
|
* Stop the server
|
|
173
101
|
*/
|
|
174
102
|
async stop(): Promise<void> {
|
|
175
|
-
if (!this.isRunning
|
|
103
|
+
if (!this.isRunning) {
|
|
176
104
|
return;
|
|
177
105
|
}
|
|
178
106
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
107
|
+
try {
|
|
108
|
+
await this.bridge.stopDb();
|
|
109
|
+
} catch {
|
|
110
|
+
// Bridge may already be dead
|
|
182
111
|
}
|
|
183
|
-
this.connections.clear();
|
|
184
|
-
|
|
185
|
-
// Close command router (cleans up session engine, cursors, etc.)
|
|
186
|
-
this.commandRouter.close();
|
|
187
|
-
|
|
188
|
-
// Close storage
|
|
189
|
-
await this.storage.close();
|
|
190
|
-
|
|
191
|
-
return new Promise((resolve) => {
|
|
192
|
-
this.server!.close(async () => {
|
|
193
|
-
this.isRunning = false;
|
|
194
|
-
this.server = null;
|
|
195
|
-
|
|
196
|
-
// Clean up socket file if using Unix socket
|
|
197
|
-
if (this.useSocket && this.options.socketPath) {
|
|
198
|
-
try {
|
|
199
|
-
await fs.unlink(this.options.socketPath);
|
|
200
|
-
} catch (err: any) {
|
|
201
|
-
// Ignore ENOENT (file doesn't exist)
|
|
202
|
-
if (err.code !== 'ENOENT') {
|
|
203
|
-
console.error('Failed to remove socket file:', err);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
resolve();
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Handle a new client connection
|
|
215
|
-
*/
|
|
216
|
-
private handleConnection(socket: net.Socket): void {
|
|
217
|
-
const connectionId = ++this.connectionIdCounter;
|
|
218
|
-
|
|
219
|
-
const state: IConnectionState = {
|
|
220
|
-
id: connectionId,
|
|
221
|
-
socket,
|
|
222
|
-
buffer: Buffer.alloc(0),
|
|
223
|
-
authenticated: true, // No auth required for now
|
|
224
|
-
database: 'test',
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
this.connections.set(connectionId, state);
|
|
228
|
-
|
|
229
|
-
socket.on('data', (data) => {
|
|
230
|
-
this.handleData(state, Buffer.isBuffer(data) ? data : Buffer.from(data));
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
socket.on('close', () => {
|
|
234
|
-
this.connections.delete(connectionId);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
socket.on('error', (err) => {
|
|
238
|
-
// Connection errors are expected when clients disconnect
|
|
239
|
-
this.connections.delete(connectionId);
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Handle incoming data from a client
|
|
245
|
-
*/
|
|
246
|
-
private handleData(state: IConnectionState, data: Buffer): void {
|
|
247
|
-
// Append new data to buffer
|
|
248
|
-
state.buffer = Buffer.concat([state.buffer, data]);
|
|
249
|
-
|
|
250
|
-
// Process messages from buffer
|
|
251
|
-
this.processMessages(state);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Process complete messages from the buffer
|
|
256
|
-
*/
|
|
257
|
-
private async processMessages(state: IConnectionState): Promise<void> {
|
|
258
|
-
while (state.buffer.length >= 16) {
|
|
259
|
-
try {
|
|
260
|
-
const result = WireProtocol.parseMessage(state.buffer);
|
|
261
|
-
|
|
262
|
-
if (!result) {
|
|
263
|
-
// Not enough data for a complete message
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const { command, bytesConsumed } = result;
|
|
268
112
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
// Process the command
|
|
273
|
-
const response = await this.commandRouter.route(command);
|
|
274
|
-
|
|
275
|
-
// Encode and send response
|
|
276
|
-
let responseBuffer: Buffer;
|
|
277
|
-
if (command.opCode === OP_QUERY) {
|
|
278
|
-
// Legacy OP_QUERY gets OP_REPLY response
|
|
279
|
-
responseBuffer = WireProtocol.encodeOpReplyResponse(
|
|
280
|
-
command.requestID,
|
|
281
|
-
[response]
|
|
282
|
-
);
|
|
283
|
-
} else {
|
|
284
|
-
// OP_MSG gets OP_MSG response
|
|
285
|
-
responseBuffer = WireProtocol.encodeOpMsgResponse(
|
|
286
|
-
command.requestID,
|
|
287
|
-
response
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (!state.socket.destroyed) {
|
|
292
|
-
state.socket.write(responseBuffer);
|
|
293
|
-
}
|
|
294
|
-
} catch (error: any) {
|
|
295
|
-
// Send error response
|
|
296
|
-
const errorResponse = WireProtocol.encodeErrorResponse(
|
|
297
|
-
0, // We don't have the requestID at this point
|
|
298
|
-
1,
|
|
299
|
-
error.message || 'Internal error'
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
if (!state.socket.destroyed) {
|
|
303
|
-
state.socket.write(errorResponse);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Clear buffer on parse errors to avoid infinite loops
|
|
307
|
-
if (error.message?.includes('opCode') || error.message?.includes('section')) {
|
|
308
|
-
state.buffer = Buffer.alloc(0);
|
|
309
|
-
}
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
113
|
+
this.bridge.kill();
|
|
114
|
+
this.isRunning = false;
|
|
313
115
|
}
|
|
314
116
|
|
|
315
117
|
/**
|
|
316
118
|
* Get the connection URI for this server
|
|
317
119
|
*/
|
|
318
120
|
getConnectionUri(): string {
|
|
319
|
-
if (this.
|
|
320
|
-
|
|
121
|
+
if (this.resolvedConnectionUri) {
|
|
122
|
+
return this.resolvedConnectionUri;
|
|
123
|
+
}
|
|
124
|
+
// Fallback: compute from options
|
|
125
|
+
if (this.options.socketPath) {
|
|
321
126
|
const encodedPath = encodeURIComponent(this.options.socketPath);
|
|
322
127
|
return `mongodb://${encodedPath}`;
|
|
323
128
|
}
|
|
324
|
-
return `mongodb://${this.options.host}:${this.options.port}`;
|
|
129
|
+
return `mongodb://${this.options.host ?? '127.0.0.1'}:${this.options.port ?? 27017}`;
|
|
325
130
|
}
|
|
326
131
|
|
|
327
132
|
/**
|
|
328
133
|
* Get the socket path (if using Unix socket mode)
|
|
329
134
|
*/
|
|
330
135
|
get socketPath(): string | undefined {
|
|
331
|
-
return this.
|
|
136
|
+
return this.options.socketPath;
|
|
332
137
|
}
|
|
333
138
|
|
|
334
139
|
/**
|
|
@@ -342,13 +147,13 @@ export class SmartdbServer {
|
|
|
342
147
|
* Get the port the server is listening on
|
|
343
148
|
*/
|
|
344
149
|
get port(): number {
|
|
345
|
-
return this.options.port;
|
|
150
|
+
return this.options.port ?? 27017;
|
|
346
151
|
}
|
|
347
152
|
|
|
348
153
|
/**
|
|
349
154
|
* Get the host the server is bound to
|
|
350
155
|
*/
|
|
351
156
|
get host(): string {
|
|
352
|
-
return this.options.host;
|
|
157
|
+
return this.options.host ?? '127.0.0.1';
|
|
353
158
|
}
|
|
354
159
|
}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
1
|
// Server module exports
|
|
2
|
-
|
|
3
2
|
export { SmartdbServer } from './SmartdbServer.js';
|
|
4
3
|
export type { ISmartdbServerOptions } from './SmartdbServer.js';
|
|
5
|
-
export { WireProtocol } from './WireProtocol.js';
|
|
6
|
-
export { CommandRouter } from './CommandRouter.js';
|
|
7
|
-
export type { ICommandHandler, IHandlerContext, ICursorState } from './CommandRouter.js';
|
|
8
|
-
|
|
9
|
-
// Export handlers
|
|
10
|
-
export * from './handlers/index.js';
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { Document, IStoredDocument, IAggregateOptions } from '../types/interfaces.js';
|
|
2
|
-
/**
|
|
3
|
-
* Aggregation engine using mingo for MongoDB-compatible aggregation pipeline execution
|
|
4
|
-
*/
|
|
5
|
-
export declare class AggregationEngine {
|
|
6
|
-
/**
|
|
7
|
-
* Execute an aggregation pipeline on a collection of documents
|
|
8
|
-
*/
|
|
9
|
-
static aggregate(documents: IStoredDocument[], pipeline: Document[], options?: IAggregateOptions): Document[];
|
|
10
|
-
/**
|
|
11
|
-
* Execute aggregation and return an iterator for lazy evaluation
|
|
12
|
-
*/
|
|
13
|
-
static aggregateIterator(documents: IStoredDocument[], pipeline: Document[], options?: IAggregateOptions): Generator<Document>;
|
|
14
|
-
/**
|
|
15
|
-
* Execute a $lookup stage manually (for cross-collection lookups)
|
|
16
|
-
* This is used when the lookup references another collection in the same database
|
|
17
|
-
*/
|
|
18
|
-
static executeLookup(documents: IStoredDocument[], lookupSpec: {
|
|
19
|
-
from: string;
|
|
20
|
-
localField: string;
|
|
21
|
-
foreignField: string;
|
|
22
|
-
as: string;
|
|
23
|
-
}, foreignCollection: IStoredDocument[]): Document[];
|
|
24
|
-
/**
|
|
25
|
-
* Execute a $graphLookup stage manually
|
|
26
|
-
*/
|
|
27
|
-
static executeGraphLookup(documents: IStoredDocument[], graphLookupSpec: {
|
|
28
|
-
from: string;
|
|
29
|
-
startWith: string | Document;
|
|
30
|
-
connectFromField: string;
|
|
31
|
-
connectToField: string;
|
|
32
|
-
as: string;
|
|
33
|
-
maxDepth?: number;
|
|
34
|
-
depthField?: string;
|
|
35
|
-
restrictSearchWithMatch?: Document;
|
|
36
|
-
}, foreignCollection: IStoredDocument[]): Document[];
|
|
37
|
-
/**
|
|
38
|
-
* Execute a $facet stage manually
|
|
39
|
-
*/
|
|
40
|
-
static executeFacet(documents: IStoredDocument[], facetSpec: Record<string, Document[]>): Document;
|
|
41
|
-
/**
|
|
42
|
-
* Execute a $unionWith stage
|
|
43
|
-
*/
|
|
44
|
-
static executeUnionWith(documents: IStoredDocument[], otherDocuments: IStoredDocument[], pipeline?: Document[]): Document[];
|
|
45
|
-
/**
|
|
46
|
-
* Execute a $merge stage (output to another collection)
|
|
47
|
-
* Returns the documents that would be inserted/updated
|
|
48
|
-
*/
|
|
49
|
-
static prepareMerge(documents: Document[], mergeSpec: {
|
|
50
|
-
into: string;
|
|
51
|
-
on?: string | string[];
|
|
52
|
-
whenMatched?: 'replace' | 'keepExisting' | 'merge' | 'fail' | Document[];
|
|
53
|
-
whenNotMatched?: 'insert' | 'discard' | 'fail';
|
|
54
|
-
}): {
|
|
55
|
-
toInsert: Document[];
|
|
56
|
-
toUpdate: Array<{
|
|
57
|
-
filter: Document;
|
|
58
|
-
update: Document;
|
|
59
|
-
}>;
|
|
60
|
-
onField: string | string[];
|
|
61
|
-
whenMatched: string | Document[];
|
|
62
|
-
whenNotMatched: string;
|
|
63
|
-
};
|
|
64
|
-
private static getNestedValue;
|
|
65
|
-
private static valuesMatch;
|
|
66
|
-
}
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import * as plugins from '../plugins.js';
|
|
2
|
-
// Import mingo Aggregator
|
|
3
|
-
import { Aggregator } from 'mingo';
|
|
4
|
-
/**
|
|
5
|
-
* Aggregation engine using mingo for MongoDB-compatible aggregation pipeline execution
|
|
6
|
-
*/
|
|
7
|
-
export class AggregationEngine {
|
|
8
|
-
/**
|
|
9
|
-
* Execute an aggregation pipeline on a collection of documents
|
|
10
|
-
*/
|
|
11
|
-
static aggregate(documents, pipeline, options) {
|
|
12
|
-
if (!pipeline || pipeline.length === 0) {
|
|
13
|
-
return documents;
|
|
14
|
-
}
|
|
15
|
-
// Create mingo aggregator with the pipeline
|
|
16
|
-
const aggregator = new Aggregator(pipeline, {
|
|
17
|
-
collation: options?.collation,
|
|
18
|
-
});
|
|
19
|
-
// Run the aggregation
|
|
20
|
-
const result = aggregator.run(documents);
|
|
21
|
-
return Array.isArray(result) ? result : [];
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Execute aggregation and return an iterator for lazy evaluation
|
|
25
|
-
*/
|
|
26
|
-
static *aggregateIterator(documents, pipeline, options) {
|
|
27
|
-
const aggregator = new Aggregator(pipeline, {
|
|
28
|
-
collation: options?.collation,
|
|
29
|
-
});
|
|
30
|
-
// Get the cursor from mingo
|
|
31
|
-
const cursor = aggregator.stream(documents);
|
|
32
|
-
for (const doc of cursor) {
|
|
33
|
-
yield doc;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Execute a $lookup stage manually (for cross-collection lookups)
|
|
38
|
-
* This is used when the lookup references another collection in the same database
|
|
39
|
-
*/
|
|
40
|
-
static executeLookup(documents, lookupSpec, foreignCollection) {
|
|
41
|
-
const { localField, foreignField, as } = lookupSpec;
|
|
42
|
-
return documents.map(doc => {
|
|
43
|
-
const localValue = this.getNestedValue(doc, localField);
|
|
44
|
-
const matches = foreignCollection.filter(foreignDoc => {
|
|
45
|
-
const foreignValue = this.getNestedValue(foreignDoc, foreignField);
|
|
46
|
-
return this.valuesMatch(localValue, foreignValue);
|
|
47
|
-
});
|
|
48
|
-
return {
|
|
49
|
-
...doc,
|
|
50
|
-
[as]: matches,
|
|
51
|
-
};
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Execute a $graphLookup stage manually
|
|
56
|
-
*/
|
|
57
|
-
static executeGraphLookup(documents, graphLookupSpec, foreignCollection) {
|
|
58
|
-
const { startWith, connectFromField, connectToField, as, maxDepth = 10, depthField, restrictSearchWithMatch, } = graphLookupSpec;
|
|
59
|
-
return documents.map(doc => {
|
|
60
|
-
const startValue = typeof startWith === 'string' && startWith.startsWith('$')
|
|
61
|
-
? this.getNestedValue(doc, startWith.slice(1))
|
|
62
|
-
: startWith;
|
|
63
|
-
const results = [];
|
|
64
|
-
const visited = new Set();
|
|
65
|
-
const queue = [];
|
|
66
|
-
// Initialize with start value(s)
|
|
67
|
-
const startValues = Array.isArray(startValue) ? startValue : [startValue];
|
|
68
|
-
for (const val of startValues) {
|
|
69
|
-
queue.push({ value: val, depth: 0 });
|
|
70
|
-
}
|
|
71
|
-
while (queue.length > 0) {
|
|
72
|
-
const { value, depth } = queue.shift();
|
|
73
|
-
if (depth > maxDepth)
|
|
74
|
-
continue;
|
|
75
|
-
const valueKey = JSON.stringify(value);
|
|
76
|
-
if (visited.has(valueKey))
|
|
77
|
-
continue;
|
|
78
|
-
visited.add(valueKey);
|
|
79
|
-
// Find matching documents
|
|
80
|
-
for (const foreignDoc of foreignCollection) {
|
|
81
|
-
const foreignValue = this.getNestedValue(foreignDoc, connectToField);
|
|
82
|
-
if (this.valuesMatch(value, foreignValue)) {
|
|
83
|
-
// Check restrictSearchWithMatch
|
|
84
|
-
if (restrictSearchWithMatch) {
|
|
85
|
-
const matchQuery = new plugins.mingo.Query(restrictSearchWithMatch);
|
|
86
|
-
if (!matchQuery.test(foreignDoc))
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
const resultDoc = depthField
|
|
90
|
-
? { ...foreignDoc, [depthField]: depth }
|
|
91
|
-
: { ...foreignDoc };
|
|
92
|
-
// Avoid duplicates in results
|
|
93
|
-
const docKey = foreignDoc._id.toHexString();
|
|
94
|
-
if (!results.some(r => r._id?.toHexString?.() === docKey)) {
|
|
95
|
-
results.push(resultDoc);
|
|
96
|
-
// Add connected values to queue
|
|
97
|
-
const nextValue = this.getNestedValue(foreignDoc, connectFromField);
|
|
98
|
-
if (nextValue !== undefined) {
|
|
99
|
-
const nextValues = Array.isArray(nextValue) ? nextValue : [nextValue];
|
|
100
|
-
for (const nv of nextValues) {
|
|
101
|
-
queue.push({ value: nv, depth: depth + 1 });
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return {
|
|
109
|
-
...doc,
|
|
110
|
-
[as]: results,
|
|
111
|
-
};
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Execute a $facet stage manually
|
|
116
|
-
*/
|
|
117
|
-
static executeFacet(documents, facetSpec) {
|
|
118
|
-
const result = {};
|
|
119
|
-
for (const [facetName, pipeline] of Object.entries(facetSpec)) {
|
|
120
|
-
result[facetName] = this.aggregate(documents, pipeline);
|
|
121
|
-
}
|
|
122
|
-
return result;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Execute a $unionWith stage
|
|
126
|
-
*/
|
|
127
|
-
static executeUnionWith(documents, otherDocuments, pipeline) {
|
|
128
|
-
let unionDocs = otherDocuments;
|
|
129
|
-
if (pipeline && pipeline.length > 0) {
|
|
130
|
-
unionDocs = this.aggregate(otherDocuments, pipeline);
|
|
131
|
-
}
|
|
132
|
-
return [...documents, ...unionDocs];
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Execute a $merge stage (output to another collection)
|
|
136
|
-
* Returns the documents that would be inserted/updated
|
|
137
|
-
*/
|
|
138
|
-
static prepareMerge(documents, mergeSpec) {
|
|
139
|
-
const onField = mergeSpec.on || '_id';
|
|
140
|
-
const whenMatched = mergeSpec.whenMatched || 'merge';
|
|
141
|
-
const whenNotMatched = mergeSpec.whenNotMatched || 'insert';
|
|
142
|
-
return {
|
|
143
|
-
toInsert: [],
|
|
144
|
-
toUpdate: [],
|
|
145
|
-
onField,
|
|
146
|
-
whenMatched,
|
|
147
|
-
whenNotMatched,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
// ============================================================================
|
|
151
|
-
// Helper Methods
|
|
152
|
-
// ============================================================================
|
|
153
|
-
static getNestedValue(obj, path) {
|
|
154
|
-
const parts = path.split('.');
|
|
155
|
-
let current = obj;
|
|
156
|
-
for (const part of parts) {
|
|
157
|
-
if (current === null || current === undefined) {
|
|
158
|
-
return undefined;
|
|
159
|
-
}
|
|
160
|
-
current = current[part];
|
|
161
|
-
}
|
|
162
|
-
return current;
|
|
163
|
-
}
|
|
164
|
-
static valuesMatch(a, b) {
|
|
165
|
-
if (a === b)
|
|
166
|
-
return true;
|
|
167
|
-
// Handle ObjectId comparison
|
|
168
|
-
if (a instanceof plugins.bson.ObjectId && b instanceof plugins.bson.ObjectId) {
|
|
169
|
-
return a.equals(b);
|
|
170
|
-
}
|
|
171
|
-
// Handle array contains check
|
|
172
|
-
if (Array.isArray(a)) {
|
|
173
|
-
return a.some(item => this.valuesMatch(item, b));
|
|
174
|
-
}
|
|
175
|
-
if (Array.isArray(b)) {
|
|
176
|
-
return b.some(item => this.valuesMatch(a, item));
|
|
177
|
-
}
|
|
178
|
-
// Handle Date comparison
|
|
179
|
-
if (a instanceof Date && b instanceof Date) {
|
|
180
|
-
return a.getTime() === b.getTime();
|
|
181
|
-
}
|
|
182
|
-
// Handle object comparison
|
|
183
|
-
if (typeof a === 'object' && typeof b === 'object' && a !== null && b !== null) {
|
|
184
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
185
|
-
}
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQWdncmVnYXRpb25FbmdpbmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy90c19zbWFydGRiL2VuZ2luZS9BZ2dyZWdhdGlvbkVuZ2luZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUd6QywwQkFBMEI7QUFDMUIsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLE9BQU8sQ0FBQztBQUVuQzs7R0FFRztBQUNILE1BQU0sT0FBTyxpQkFBaUI7SUFDNUI7O09BRUc7SUFDSCxNQUFNLENBQUMsU0FBUyxDQUNkLFNBQTRCLEVBQzVCLFFBQW9CLEVBQ3BCLE9BQTJCO1FBRTNCLElBQUksQ0FBQyxRQUFRLElBQUksUUFBUSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUN2QyxPQUFPLFNBQVMsQ0FBQztRQUNuQixDQUFDO1FBRUQsNENBQTRDO1FBQzVDLE1BQU0sVUFBVSxHQUFHLElBQUksVUFBVSxDQUFDLFFBQVEsRUFBRTtZQUMxQyxTQUFTLEVBQUUsT0FBTyxFQUFFLFNBQWdCO1NBQ3JDLENBQUMsQ0FBQztRQUVILHNCQUFzQjtRQUN0QixNQUFNLE1BQU0sR0FBRyxVQUFVLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBRXpDLE9BQU8sS0FBSyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDN0MsQ0FBQztJQUVEOztPQUVHO0lBQ0gsTUFBTSxDQUFDLENBQUMsaUJBQWlCLENBQ3ZCLFNBQTRCLEVBQzVCLFFBQW9CLEVBQ3BCLE9BQTJCO1FBRTNCLE1BQU0sVUFBVSxHQUFHLElBQUksVUFBVSxDQUFDLFFBQVEsRUFBRTtZQUMxQyxTQUFTLEVBQUUsT0FBTyxFQUFFLFNBQWdCO1NBQ3JDLENBQUMsQ0FBQztRQUVILDRCQUE0QjtRQUM1QixNQUFNLE1BQU0sR0FB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
|