@objectql/driver-mongo 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.
package/src/index.ts CHANGED
@@ -1,7 +1,60 @@
1
+ /**
2
+ * ObjectQL
3
+ * Copyright (c) 2026-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
1
9
  import { Driver } from '@objectql/types';
10
+ import { DriverInterface, QueryAST, FilterNode, SortNode } from '@objectstack/spec';
2
11
  import { MongoClient, Db, Filter, ObjectId, FindOptions } from 'mongodb';
3
12
 
4
- export class MongoDriver implements Driver {
13
+ /**
14
+ * Command interface for executeCommand method
15
+ */
16
+ export interface Command {
17
+ type: 'create' | 'update' | 'delete' | 'bulkCreate' | 'bulkUpdate' | 'bulkDelete';
18
+ object: string;
19
+ data?: any;
20
+ id?: string | number;
21
+ ids?: Array<string | number>;
22
+ records?: any[];
23
+ updates?: Array<{id: string | number, data: any}>;
24
+ options?: any;
25
+ }
26
+
27
+ /**
28
+ * Command result interface
29
+ */
30
+ export interface CommandResult {
31
+ success: boolean;
32
+ data?: any;
33
+ affected: number;
34
+ error?: string;
35
+ }
36
+
37
+ /**
38
+ * MongoDB Driver for ObjectQL
39
+ *
40
+ * Implements both the legacy Driver interface from @objectql/types and
41
+ * the standard DriverInterface from @objectstack/spec for compatibility
42
+ * with the new kernel-based plugin system.
43
+ *
44
+ * The driver internally converts QueryAST format to MongoDB query format.
45
+ */
46
+ export class MongoDriver implements Driver, DriverInterface {
47
+ // Driver metadata (ObjectStack-compatible)
48
+ public readonly name = 'MongoDriver';
49
+ public readonly version = '3.0.1';
50
+ public readonly supports = {
51
+ transactions: true,
52
+ joins: false,
53
+ fullTextSearch: true,
54
+ jsonFields: true,
55
+ arrayFields: true
56
+ };
57
+
5
58
  private client: MongoClient;
6
59
  private db?: Db;
7
60
  private config: any;
@@ -10,14 +63,39 @@ export class MongoDriver implements Driver {
10
63
  constructor(config: { url: string, dbName?: string }) {
11
64
  this.config = config;
12
65
  this.client = new MongoClient(config.url);
13
- this.connected = this.connect();
66
+ this.connected = this.internalConnect();
14
67
  }
15
68
 
16
- private async connect() {
69
+ /**
70
+ * Internal connect method used in constructor
71
+ */
72
+ private async internalConnect() {
17
73
  await this.client.connect();
18
74
  this.db = this.client.db(this.config.dbName);
19
75
  }
20
76
 
77
+ /**
78
+ * Connect to the database (for DriverInterface compatibility)
79
+ * This method ensures the connection is established.
80
+ */
81
+ async connect(): Promise<void> {
82
+ await this.connected;
83
+ }
84
+
85
+ /**
86
+ * Check database connection health
87
+ */
88
+ async checkHealth(): Promise<boolean> {
89
+ try {
90
+ await this.connected;
91
+ if (!this.db) return false;
92
+ await this.db.admin().ping();
93
+ return true;
94
+ } catch (error) {
95
+ return false;
96
+ }
97
+ }
98
+
21
99
  private async getCollection(objectName: string) {
22
100
  await this.connected;
23
101
  if (!this.db) throw new Error("Database not initialized");
@@ -162,29 +240,79 @@ export class MongoDriver implements Driver {
162
240
  return mongoCondition;
163
241
  }
164
242
 
243
+ /**
244
+ * Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
245
+ * This ensures backward compatibility while supporting the new @objectstack/spec interface.
246
+ *
247
+ * QueryAST format uses 'top' for limit, while UnifiedQuery uses 'limit'.
248
+ * QueryAST sort is array of {field, order}, while UnifiedQuery is array of [field, order].
249
+ * QueryAST uses 'aggregations', while legacy uses 'aggregate'.
250
+ */
251
+ private normalizeQuery(query: any): any {
252
+ if (!query) return {};
253
+
254
+ const normalized: any = { ...query };
255
+
256
+ // Normalize limit/top
257
+ if (normalized.top !== undefined && normalized.limit === undefined) {
258
+ normalized.limit = normalized.top;
259
+ }
260
+
261
+ // Normalize aggregations/aggregate
262
+ if (normalized.aggregations !== undefined && normalized.aggregate === undefined) {
263
+ // Convert QueryAST aggregations format to legacy aggregate format
264
+ normalized.aggregate = normalized.aggregations.map((agg: any) => ({
265
+ func: agg.function || agg.func,
266
+ field: agg.field,
267
+ alias: agg.alias
268
+ }));
269
+ }
270
+
271
+ // Normalize sort format
272
+ if (normalized.sort && Array.isArray(normalized.sort)) {
273
+ // Check if it's already in the array format [field, order]
274
+ const firstSort = normalized.sort[0];
275
+ if (firstSort && typeof firstSort === 'object' && !Array.isArray(firstSort)) {
276
+ // Convert from QueryAST format {field, order} to internal format [field, order]
277
+ normalized.sort = normalized.sort.map((item: any) => [
278
+ item.field,
279
+ item.order || item.direction || item.dir || 'asc'
280
+ ]);
281
+ }
282
+ }
283
+
284
+ return normalized;
285
+ }
286
+
165
287
  async find(objectName: string, query: any, options?: any): Promise<any[]> {
288
+ const normalizedQuery = this.normalizeQuery(query);
166
289
  const collection = await this.getCollection(objectName);
167
- const filter = this.mapFilters(query.filters);
290
+ const filter = this.mapFilters(normalizedQuery.filters);
168
291
 
169
292
  const findOptions: FindOptions = {};
170
- if (query.skip) findOptions.skip = query.skip;
171
- if (query.limit) findOptions.limit = query.limit;
172
- if (query.sort) {
293
+ if (normalizedQuery.skip) findOptions.skip = normalizedQuery.skip;
294
+ if (normalizedQuery.limit) findOptions.limit = normalizedQuery.limit;
295
+ if (normalizedQuery.sort) {
173
296
  // map [['field', 'desc']] to { field: -1 }
174
297
  findOptions.sort = {};
175
- for (const [field, order] of query.sort) {
298
+ for (const [field, order] of normalizedQuery.sort) {
176
299
  // Map both 'id' and '_id' to '_id' for backward compatibility
177
300
  const dbField = (field === 'id' || field === '_id') ? '_id' : field;
178
301
  (findOptions.sort as any)[dbField] = order === 'desc' ? -1 : 1;
179
302
  }
180
303
  }
181
- if (query.fields && query.fields.length > 0) {
304
+ if (normalizedQuery.fields && normalizedQuery.fields.length > 0) {
182
305
  findOptions.projection = {};
183
- for (const field of query.fields) {
306
+ for (const field of normalizedQuery.fields) {
184
307
  // Map both 'id' and '_id' to '_id' for backward compatibility
185
308
  const dbField = (field === 'id' || field === '_id') ? '_id' : field;
186
309
  (findOptions.projection as any)[dbField] = 1;
187
310
  }
311
+ // Explicitly exclude _id if 'id' is not in the requested fields
312
+ const hasIdField = normalizedQuery.fields.some((f: string) => f === 'id' || f === '_id');
313
+ if (!hasIdField) {
314
+ (findOptions.projection as any)._id = 0;
315
+ }
188
316
  }
189
317
 
190
318
  const results = await collection.find(filter, findOptions).toArray();
@@ -245,7 +373,10 @@ export class MongoDriver implements Driver {
245
373
 
246
374
  async count(objectName: string, filters: any, options?: any): Promise<number> {
247
375
  const collection = await this.getCollection(objectName);
248
- const filter = this.mapFilters(filters);
376
+ // Normalize to support both filter arrays and full query objects
377
+ const normalizedQuery = this.normalizeQuery(filters);
378
+ const actualFilters = normalizedQuery.filters || filters;
379
+ const filter = this.mapFilters(actualFilters);
249
380
  return await collection.countDocuments(filter);
250
381
  }
251
382
 
@@ -299,5 +430,205 @@ export class MongoDriver implements Driver {
299
430
  await this.client.close();
300
431
  }
301
432
  }
433
+
434
+ /**
435
+ * Execute a query using QueryAST (DriverInterface v4.0 method)
436
+ *
437
+ * This is the new standard method for query execution using the
438
+ * ObjectStack QueryAST format.
439
+ *
440
+ * @param ast - The QueryAST representing the query
441
+ * @param options - Optional execution options
442
+ * @returns Query results with value and count
443
+ */
444
+ async executeQuery(ast: QueryAST, options?: any): Promise<{ value: any[]; count?: number }> {
445
+ const objectName = ast.object || '';
446
+
447
+ // Convert QueryAST to legacy query format
448
+ const legacyQuery: any = {
449
+ fields: ast.fields,
450
+ filters: this.convertFilterNodeToLegacy(ast.filters),
451
+ sort: ast.sort?.map((s: SortNode) => [s.field, s.order]),
452
+ limit: ast.top,
453
+ skip: ast.skip,
454
+ };
455
+
456
+ // Use existing find method
457
+ const results = await this.find(objectName, legacyQuery, options);
458
+
459
+ return {
460
+ value: results,
461
+ count: results.length
462
+ };
463
+ }
464
+
465
+ /**
466
+ * Execute a command (DriverInterface v4.0 method)
467
+ *
468
+ * This method handles all mutation operations (create, update, delete)
469
+ * using a unified command interface.
470
+ *
471
+ * @param command - The command to execute
472
+ * @param options - Optional execution options
473
+ * @returns Command execution result
474
+ */
475
+ async executeCommand(command: Command, options?: any): Promise<CommandResult> {
476
+ try {
477
+ const cmdOptions = { ...options, ...command.options };
478
+
479
+ switch (command.type) {
480
+ case 'create':
481
+ if (!command.data) {
482
+ throw new Error('Create command requires data');
483
+ }
484
+ const created = await this.create(command.object, command.data, cmdOptions);
485
+ return {
486
+ success: true,
487
+ data: created,
488
+ affected: 1
489
+ };
490
+
491
+ case 'update':
492
+ if (!command.id || !command.data) {
493
+ throw new Error('Update command requires id and data');
494
+ }
495
+ const updated = await this.update(command.object, command.id, command.data, cmdOptions);
496
+ return {
497
+ success: true,
498
+ data: updated,
499
+ affected: updated ? 1 : 0
500
+ };
501
+
502
+ case 'delete':
503
+ if (!command.id) {
504
+ throw new Error('Delete command requires id');
505
+ }
506
+ const deleteCount = await this.delete(command.object, command.id, cmdOptions);
507
+ return {
508
+ success: true,
509
+ affected: deleteCount
510
+ };
511
+
512
+ case 'bulkCreate':
513
+ if (!command.records || !Array.isArray(command.records)) {
514
+ throw new Error('BulkCreate command requires records array');
515
+ }
516
+ const bulkCreated = await this.createMany(command.object, command.records, cmdOptions);
517
+ return {
518
+ success: true,
519
+ data: bulkCreated,
520
+ affected: command.records.length
521
+ };
522
+
523
+ case 'bulkUpdate':
524
+ if (!command.updates || !Array.isArray(command.updates)) {
525
+ throw new Error('BulkUpdate command requires updates array');
526
+ }
527
+ let updateCount = 0;
528
+ const updateResults = [];
529
+ for (const update of command.updates) {
530
+ const result = await this.update(command.object, update.id, update.data, cmdOptions);
531
+ updateResults.push(result);
532
+ if (result) updateCount++;
533
+ }
534
+ return {
535
+ success: true,
536
+ data: updateResults,
537
+ affected: updateCount
538
+ };
539
+
540
+ case 'bulkDelete':
541
+ if (!command.ids || !Array.isArray(command.ids)) {
542
+ throw new Error('BulkDelete command requires ids array');
543
+ }
544
+ let deleted = 0;
545
+ for (const id of command.ids) {
546
+ const result = await this.delete(command.object, id, cmdOptions);
547
+ deleted += result;
548
+ }
549
+ return {
550
+ success: true,
551
+ affected: deleted
552
+ };
553
+
554
+ default:
555
+ const validTypes = ['create', 'update', 'delete', 'bulkCreate', 'bulkUpdate', 'bulkDelete'];
556
+ throw new Error(`Unknown command type: ${(command as any).type}. Valid types are: ${validTypes.join(', ')}`);
557
+ }
558
+ } catch (error: any) {
559
+ return {
560
+ success: false,
561
+ error: error.message || 'Command execution failed',
562
+ affected: 0
563
+ };
564
+ }
565
+ }
566
+
567
+ /**
568
+ * Convert FilterNode (QueryAST format) to legacy filter array format
569
+ * This allows reuse of existing filter logic while supporting new QueryAST
570
+ *
571
+ * @private
572
+ */
573
+ private convertFilterNodeToLegacy(node?: FilterNode): any {
574
+ if (!node) return undefined;
575
+
576
+ switch (node.type) {
577
+ case 'comparison':
578
+ // Convert comparison node to [field, operator, value] format
579
+ const operator = node.operator || '=';
580
+ return [[node.field, operator, node.value]];
581
+
582
+ case 'and':
583
+ case 'or':
584
+ // Convert AND/OR node to array with separator
585
+ if (!node.children || node.children.length === 0) return undefined;
586
+ const results: any[] = [];
587
+ const separator = node.type; // 'and' or 'or'
588
+
589
+ for (const child of node.children) {
590
+ const converted = this.convertFilterNodeToLegacy(child);
591
+ if (converted) {
592
+ if (results.length > 0) {
593
+ results.push(separator);
594
+ }
595
+ results.push(...(Array.isArray(converted) ? converted : [converted]));
596
+ }
597
+ }
598
+ return results.length > 0 ? results : undefined;
599
+
600
+ case 'not':
601
+ // NOT is not directly supported in the legacy filter format
602
+ // MongoDB supports $not, but legacy array format doesn't have a NOT operator
603
+ // Use native MongoDB queries with $not instead:
604
+ // Example: { field: { $not: { $eq: value } } }
605
+ throw new Error(
606
+ 'NOT filters are not supported in legacy filter format. ' +
607
+ 'Use native MongoDB queries with $not operator instead. ' +
608
+ 'Example: { field: { $not: { $eq: value } } }'
609
+ );
610
+
611
+ default:
612
+ return undefined;
613
+ }
614
+ }
615
+
616
+ /**
617
+ * Execute command (alternative signature for compatibility)
618
+ *
619
+ * @param command - Command string or object
620
+ * @param parameters - Command parameters
621
+ * @param options - Execution options
622
+ */
623
+ async execute(command: any, parameters?: any[], options?: any): Promise<any> {
624
+ // MongoDB driver doesn't support raw command execution in the traditional SQL sense
625
+ // Use executeCommand() instead for mutations (create/update/delete)
626
+ // Example: await driver.executeCommand({ type: 'create', object: 'users', data: {...} })
627
+ throw new Error(
628
+ 'MongoDB driver does not support raw command execution. ' +
629
+ 'Use executeCommand() for mutations or aggregate() for complex queries. ' +
630
+ 'Example: driver.executeCommand({ type: "create", object: "users", data: {...} })'
631
+ );
632
+ }
302
633
  }
303
634
 
@@ -1,3 +1,11 @@
1
+ /**
2
+ * ObjectQL
3
+ * Copyright (c) 2026-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
1
9
  import { MongoDriver } from '../src';
2
10
  import { MongoClient } from 'mongodb';
3
11
 
@@ -9,6 +17,10 @@ const mockCollection = {
9
17
  toArray: jest.fn().mockResolvedValue([]),
10
18
  findOne: jest.fn().mockResolvedValue(null),
11
19
  insertOne: jest.fn().mockResolvedValue({ insertedId: '123' }),
20
+ insertMany: jest.fn().mockResolvedValue({
21
+ insertedIds: { 0: 'id1', 1: 'id2' },
22
+ insertedCount: 2
23
+ }),
12
24
  updateOne: jest.fn().mockResolvedValue({ modifiedCount: 1 }),
13
25
  deleteOne: jest.fn().mockResolvedValue({ deletedCount: 1 }),
14
26
  countDocuments: jest.fn().mockResolvedValue(10)
@@ -26,7 +38,10 @@ const mockClient = {
26
38
  jest.mock('mongodb', () => {
27
39
  return {
28
40
  MongoClient: jest.fn().mockImplementation(() => mockClient),
29
- ObjectId: jest.fn(id => id)
41
+ ObjectId: jest.fn().mockImplementation((id?: string) => ({
42
+ toHexString: () => id || 'generated-object-id',
43
+ toString: () => id || 'generated-object-id'
44
+ }))
30
45
  };
31
46
  });
32
47
 
@@ -320,4 +335,246 @@ describe('MongoDriver', () => {
320
335
  );
321
336
  });
322
337
 
338
+ describe('DriverInterface v4.0 methods', () => {
339
+ describe('executeQuery', () => {
340
+ it('should execute a simple QueryAST query', async () => {
341
+ const ast = {
342
+ object: 'users',
343
+ fields: ['name', 'email'],
344
+ filters: {
345
+ type: 'comparison' as const,
346
+ field: 'status',
347
+ operator: '=',
348
+ value: 'active'
349
+ },
350
+ top: 10,
351
+ skip: 0
352
+ };
353
+
354
+ mockCollection.toArray.mockResolvedValue([
355
+ { name: 'User 1', email: 'user1@example.com' },
356
+ { name: 'User 2', email: 'user2@example.com' }
357
+ ]);
358
+
359
+ const result = await driver.executeQuery(ast);
360
+
361
+ expect(result.value).toHaveLength(2);
362
+ expect(result.count).toBe(2);
363
+ expect(mockCollection.find).toHaveBeenCalled();
364
+ });
365
+
366
+ it('should handle complex QueryAST with AND filters', async () => {
367
+ const ast = {
368
+ object: 'users',
369
+ filters: {
370
+ type: 'and' as const,
371
+ children: [
372
+ {
373
+ type: 'comparison' as const,
374
+ field: 'status',
375
+ operator: '=',
376
+ value: 'active'
377
+ },
378
+ {
379
+ type: 'comparison' as const,
380
+ field: 'age',
381
+ operator: '>',
382
+ value: 18
383
+ }
384
+ ]
385
+ }
386
+ };
387
+
388
+ mockCollection.toArray.mockResolvedValue([]);
389
+
390
+ const result = await driver.executeQuery(ast);
391
+
392
+ expect(result.value).toEqual([]);
393
+ expect(mockCollection.find).toHaveBeenCalled();
394
+ });
395
+
396
+ it('should handle QueryAST with sort', async () => {
397
+ const ast = {
398
+ object: 'users',
399
+ sort: [
400
+ { field: 'name', order: 'asc' as const }
401
+ ]
402
+ };
403
+
404
+ mockCollection.toArray.mockResolvedValue([]);
405
+
406
+ await driver.executeQuery(ast);
407
+
408
+ expect(mockCollection.find).toHaveBeenCalledWith(
409
+ {},
410
+ expect.objectContaining({
411
+ sort: { name: 1 }
412
+ })
413
+ );
414
+ });
415
+ });
416
+
417
+ describe('executeCommand', () => {
418
+ it('should execute create command', async () => {
419
+ const command = {
420
+ type: 'create' as const,
421
+ object: 'users',
422
+ data: { name: 'New User', email: 'new@example.com' }
423
+ };
424
+
425
+ mockCollection.insertOne.mockResolvedValue({
426
+ insertedId: 'new123',
427
+ acknowledged: true
428
+ } as any);
429
+
430
+ const result = await driver.executeCommand(command);
431
+
432
+ expect(result.success).toBe(true);
433
+ expect(result.affected).toBe(1);
434
+ expect(result.data).toBeDefined();
435
+ expect(mockCollection.insertOne).toHaveBeenCalled();
436
+ });
437
+
438
+ it('should execute update command', async () => {
439
+ const command = {
440
+ type: 'update' as const,
441
+ object: 'users',
442
+ id: '123',
443
+ data: { name: 'Updated User' }
444
+ };
445
+
446
+ mockCollection.updateOne.mockResolvedValue({
447
+ modifiedCount: 1,
448
+ acknowledged: true
449
+ } as any);
450
+ mockCollection.findOne.mockResolvedValue({
451
+ _id: '123',
452
+ name: 'Updated User'
453
+ });
454
+
455
+ const result = await driver.executeCommand(command);
456
+
457
+ expect(result.success).toBe(true);
458
+ expect(result.affected).toBe(1);
459
+ expect(mockCollection.updateOne).toHaveBeenCalled();
460
+ });
461
+
462
+ it('should execute delete command', async () => {
463
+ const command = {
464
+ type: 'delete' as const,
465
+ object: 'users',
466
+ id: '123'
467
+ };
468
+
469
+ mockCollection.deleteOne.mockResolvedValue({
470
+ deletedCount: 1,
471
+ acknowledged: true
472
+ } as any);
473
+
474
+ const result = await driver.executeCommand(command);
475
+
476
+ expect(result.success).toBe(true);
477
+ expect(result.affected).toBe(1);
478
+ expect(mockCollection.deleteOne).toHaveBeenCalled();
479
+ });
480
+
481
+ it('should execute bulkCreate command', async () => {
482
+ const command = {
483
+ type: 'bulkCreate' as const,
484
+ object: 'users',
485
+ records: [
486
+ { name: 'User 1', email: 'user1@example.com' },
487
+ { name: 'User 2', email: 'user2@example.com' }
488
+ ]
489
+ };
490
+
491
+ mockCollection.insertOne.mockResolvedValue({
492
+ insertedId: 'id1',
493
+ acknowledged: true
494
+ } as any);
495
+
496
+ const result = await driver.executeCommand(command);
497
+
498
+ expect(result.success).toBe(true);
499
+ expect(result.affected).toBe(2);
500
+ expect(result.data).toHaveLength(2);
501
+ });
502
+
503
+ it('should execute bulkUpdate command', async () => {
504
+ const command = {
505
+ type: 'bulkUpdate' as const,
506
+ object: 'users',
507
+ updates: [
508
+ { id: '1', data: { name: 'Updated 1' } },
509
+ { id: '2', data: { name: 'Updated 2' } }
510
+ ]
511
+ };
512
+
513
+ mockCollection.updateOne.mockResolvedValue({
514
+ modifiedCount: 1,
515
+ acknowledged: true
516
+ } as any);
517
+ mockCollection.findOne.mockResolvedValue({ _id: '1', name: 'Updated 1' });
518
+
519
+ const result = await driver.executeCommand(command);
520
+
521
+ expect(result.success).toBe(true);
522
+ expect(result.affected).toBe(2);
523
+ });
524
+
525
+ it('should execute bulkDelete command', async () => {
526
+ const command = {
527
+ type: 'bulkDelete' as const,
528
+ object: 'users',
529
+ ids: ['1', '2', '3']
530
+ };
531
+
532
+ mockCollection.deleteOne.mockResolvedValue({
533
+ deletedCount: 1,
534
+ acknowledged: true
535
+ } as any);
536
+
537
+ const result = await driver.executeCommand(command);
538
+
539
+ expect(result.success).toBe(true);
540
+ expect(result.affected).toBe(3);
541
+ });
542
+
543
+ it('should handle command errors gracefully', async () => {
544
+ const command = {
545
+ type: 'create' as const,
546
+ object: 'users',
547
+ data: undefined // Invalid data
548
+ };
549
+
550
+ const result = await driver.executeCommand(command);
551
+
552
+ expect(result.success).toBe(false);
553
+ expect(result.error).toBeDefined();
554
+ expect(result.affected).toBe(0);
555
+ });
556
+
557
+ it('should reject unknown command types', async () => {
558
+ const command = {
559
+ type: 'invalidCommand' as any,
560
+ object: 'users'
561
+ };
562
+
563
+ const result = await driver.executeCommand(command);
564
+
565
+ expect(result.success).toBe(false);
566
+ expect(result.error).toContain('Unknown command type');
567
+ expect(result.error).toContain('Valid types are');
568
+ });
569
+ });
570
+
571
+ describe('execute', () => {
572
+ it('should throw error as MongoDB does not support raw command execution', async () => {
573
+ await expect(driver.execute('SELECT * FROM users')).rejects.toThrow(
574
+ 'MongoDB driver does not support raw command execution'
575
+ );
576
+ });
577
+ });
578
+ });
579
+
323
580
  });
@@ -1,3 +1,11 @@
1
+ /**
2
+ * ObjectQL
3
+ * Copyright (c) 2026-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
1
9
  import { MongoDriver } from '../src';
2
10
  import { MongoClient } from 'mongodb';
3
11
  import { MongoMemoryServer } from 'mongodb-memory-server';