@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.
Files changed (110) hide show
  1. package/.smartconfig.json +7 -4
  2. package/dist_rust/rustdb_linux_amd64 +0 -0
  3. package/dist_rust/rustdb_linux_arm64 +0 -0
  4. package/dist_ts/00_commitinfo_data.js +3 -3
  5. package/dist_ts/ts_local/classes.localsmartdb.d.ts +5 -5
  6. package/dist_ts/ts_local/classes.localsmartdb.js +5 -6
  7. package/dist_ts/ts_local/plugins.d.ts +1 -2
  8. package/dist_ts/ts_local/plugins.js +3 -3
  9. package/dist_ts/ts_smartdb/index.d.ts +1 -24
  10. package/dist_ts/ts_smartdb/index.js +4 -29
  11. package/dist_ts/ts_smartdb/plugins.d.ts +2 -10
  12. package/dist_ts/ts_smartdb/plugins.js +3 -13
  13. package/dist_ts/ts_smartdb/rust-db-bridge.d.ts +43 -0
  14. package/dist_ts/ts_smartdb/rust-db-bridge.js +98 -0
  15. package/dist_ts/ts_smartdb/server/SmartdbServer.d.ts +8 -37
  16. package/dist_ts/ts_smartdb/server/SmartdbServer.js +49 -204
  17. package/dist_ts/ts_smartdb/server/index.d.ts +0 -4
  18. package/dist_ts/ts_smartdb/server/index.js +1 -5
  19. package/license +3 -1
  20. package/package.json +9 -12
  21. package/readme.md +84 -171
  22. package/ts/00_commitinfo_data.ts +2 -2
  23. package/ts/ts_local/classes.localsmartdb.ts +5 -6
  24. package/ts/ts_local/plugins.ts +1 -3
  25. package/ts/ts_smartdb/index.ts +3 -41
  26. package/ts/ts_smartdb/plugins.ts +2 -15
  27. package/ts/ts_smartdb/rust-db-bridge.ts +138 -0
  28. package/ts/ts_smartdb/server/SmartdbServer.ts +53 -248
  29. package/ts/ts_smartdb/server/index.ts +0 -7
  30. package/dist_ts/ts_smartdb/engine/AggregationEngine.d.ts +0 -66
  31. package/dist_ts/ts_smartdb/engine/AggregationEngine.js +0 -189
  32. package/dist_ts/ts_smartdb/engine/IndexEngine.d.ts +0 -97
  33. package/dist_ts/ts_smartdb/engine/IndexEngine.js +0 -678
  34. package/dist_ts/ts_smartdb/engine/QueryEngine.d.ts +0 -54
  35. package/dist_ts/ts_smartdb/engine/QueryEngine.js +0 -271
  36. package/dist_ts/ts_smartdb/engine/QueryPlanner.d.ts +0 -64
  37. package/dist_ts/ts_smartdb/engine/QueryPlanner.js +0 -308
  38. package/dist_ts/ts_smartdb/engine/SessionEngine.d.ts +0 -117
  39. package/dist_ts/ts_smartdb/engine/SessionEngine.js +0 -232
  40. package/dist_ts/ts_smartdb/engine/TransactionEngine.d.ts +0 -85
  41. package/dist_ts/ts_smartdb/engine/TransactionEngine.js +0 -287
  42. package/dist_ts/ts_smartdb/engine/UpdateEngine.d.ts +0 -47
  43. package/dist_ts/ts_smartdb/engine/UpdateEngine.js +0 -461
  44. package/dist_ts/ts_smartdb/errors/SmartdbErrors.d.ts +0 -100
  45. package/dist_ts/ts_smartdb/errors/SmartdbErrors.js +0 -155
  46. package/dist_ts/ts_smartdb/server/CommandRouter.d.ts +0 -87
  47. package/dist_ts/ts_smartdb/server/CommandRouter.js +0 -222
  48. package/dist_ts/ts_smartdb/server/WireProtocol.d.ts +0 -117
  49. package/dist_ts/ts_smartdb/server/WireProtocol.js +0 -298
  50. package/dist_ts/ts_smartdb/server/handlers/AdminHandler.d.ts +0 -100
  51. package/dist_ts/ts_smartdb/server/handlers/AdminHandler.js +0 -668
  52. package/dist_ts/ts_smartdb/server/handlers/AggregateHandler.d.ts +0 -31
  53. package/dist_ts/ts_smartdb/server/handlers/AggregateHandler.js +0 -277
  54. package/dist_ts/ts_smartdb/server/handlers/DeleteHandler.d.ts +0 -8
  55. package/dist_ts/ts_smartdb/server/handlers/DeleteHandler.js +0 -95
  56. package/dist_ts/ts_smartdb/server/handlers/FindHandler.d.ts +0 -31
  57. package/dist_ts/ts_smartdb/server/handlers/FindHandler.js +0 -291
  58. package/dist_ts/ts_smartdb/server/handlers/HelloHandler.d.ts +0 -11
  59. package/dist_ts/ts_smartdb/server/handlers/HelloHandler.js +0 -62
  60. package/dist_ts/ts_smartdb/server/handlers/IndexHandler.d.ts +0 -20
  61. package/dist_ts/ts_smartdb/server/handlers/IndexHandler.js +0 -183
  62. package/dist_ts/ts_smartdb/server/handlers/InsertHandler.d.ts +0 -8
  63. package/dist_ts/ts_smartdb/server/handlers/InsertHandler.js +0 -79
  64. package/dist_ts/ts_smartdb/server/handlers/UpdateHandler.d.ts +0 -24
  65. package/dist_ts/ts_smartdb/server/handlers/UpdateHandler.js +0 -296
  66. package/dist_ts/ts_smartdb/server/handlers/index.d.ts +0 -8
  67. package/dist_ts/ts_smartdb/server/handlers/index.js +0 -10
  68. package/dist_ts/ts_smartdb/storage/FileStorageAdapter.d.ts +0 -85
  69. package/dist_ts/ts_smartdb/storage/FileStorageAdapter.js +0 -465
  70. package/dist_ts/ts_smartdb/storage/IStorageAdapter.d.ts +0 -145
  71. package/dist_ts/ts_smartdb/storage/IStorageAdapter.js +0 -2
  72. package/dist_ts/ts_smartdb/storage/MemoryStorageAdapter.d.ts +0 -67
  73. package/dist_ts/ts_smartdb/storage/MemoryStorageAdapter.js +0 -378
  74. package/dist_ts/ts_smartdb/storage/OpLog.d.ts +0 -93
  75. package/dist_ts/ts_smartdb/storage/OpLog.js +0 -221
  76. package/dist_ts/ts_smartdb/storage/WAL.d.ts +0 -117
  77. package/dist_ts/ts_smartdb/storage/WAL.js +0 -286
  78. package/dist_ts/ts_smartdb/types/interfaces.d.ts +0 -363
  79. package/dist_ts/ts_smartdb/types/interfaces.js +0 -2
  80. package/dist_ts/ts_smartdb/utils/checksum.d.ts +0 -30
  81. package/dist_ts/ts_smartdb/utils/checksum.js +0 -77
  82. package/dist_ts/ts_smartdb/utils/index.d.ts +0 -1
  83. package/dist_ts/ts_smartdb/utils/index.js +0 -2
  84. package/ts/ts_smartdb/engine/AggregationEngine.ts +0 -283
  85. package/ts/ts_smartdb/engine/IndexEngine.ts +0 -798
  86. package/ts/ts_smartdb/engine/QueryEngine.ts +0 -301
  87. package/ts/ts_smartdb/engine/QueryPlanner.ts +0 -393
  88. package/ts/ts_smartdb/engine/SessionEngine.ts +0 -292
  89. package/ts/ts_smartdb/engine/TransactionEngine.ts +0 -351
  90. package/ts/ts_smartdb/engine/UpdateEngine.ts +0 -506
  91. package/ts/ts_smartdb/errors/SmartdbErrors.ts +0 -181
  92. package/ts/ts_smartdb/server/CommandRouter.ts +0 -289
  93. package/ts/ts_smartdb/server/WireProtocol.ts +0 -416
  94. package/ts/ts_smartdb/server/handlers/AdminHandler.ts +0 -719
  95. package/ts/ts_smartdb/server/handlers/AggregateHandler.ts +0 -342
  96. package/ts/ts_smartdb/server/handlers/DeleteHandler.ts +0 -115
  97. package/ts/ts_smartdb/server/handlers/FindHandler.ts +0 -330
  98. package/ts/ts_smartdb/server/handlers/HelloHandler.ts +0 -78
  99. package/ts/ts_smartdb/server/handlers/IndexHandler.ts +0 -207
  100. package/ts/ts_smartdb/server/handlers/InsertHandler.ts +0 -97
  101. package/ts/ts_smartdb/server/handlers/UpdateHandler.ts +0 -344
  102. package/ts/ts_smartdb/server/handlers/index.ts +0 -10
  103. package/ts/ts_smartdb/storage/FileStorageAdapter.ts +0 -562
  104. package/ts/ts_smartdb/storage/IStorageAdapter.ts +0 -208
  105. package/ts/ts_smartdb/storage/MemoryStorageAdapter.ts +0 -455
  106. package/ts/ts_smartdb/storage/OpLog.ts +0 -282
  107. package/ts/ts_smartdb/storage/WAL.ts +0 -375
  108. package/ts/ts_smartdb/types/interfaces.ts +0 -433
  109. package/ts/ts_smartdb/utils/checksum.ts +0 -88
  110. package/ts/ts_smartdb/utils/index.ts +0 -1
@@ -1,11 +1,4 @@
1
- import * as net from 'net';
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
- * Connection state for each client
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 MongoDB wire protocol (OP_MSG) to allow
45
- * official MongoDB drivers to connect and perform operations.
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/smartmongo/smartdb';
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('mongodb://127.0.0.1:27017');
38
+ * const client = new MongoClient(server.getConnectionUri());
56
39
  * await client.connect();
57
40
  * ```
58
41
  */
59
42
  export class SmartdbServer {
60
- private options: Required<Omit<ISmartdbServerOptions, 'socketPath'>> & { socketPath: string };
61
- private server: net.Server | null = null;
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 startTime: Date = new Date();
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
- // Initialize storage
126
- await this.storage.initialize();
127
-
128
- // Clean up stale socket file if using Unix socket
129
- if (this.useSocket && this.options.socketPath) {
130
- try {
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
- return new Promise((resolve, reject) => {
141
- this.server = net.createServer((socket) => {
142
- this.handleConnection(socket);
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 || !this.server) {
103
+ if (!this.isRunning) {
176
104
  return;
177
105
  }
178
106
 
179
- // Close all connections
180
- for (const conn of this.connections.values()) {
181
- conn.socket.destroy();
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
- // Remove processed bytes from buffer
270
- state.buffer = state.buffer.subarray(bytesConsumed);
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.useSocket && this.options.socketPath) {
320
- // URL-encode the socket path (replace / with %2F)
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.useSocket ? this.options.socketPath : undefined;
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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