@objectql/driver-mongo 4.1.0 → 4.2.1
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +36 -0
- package/dist/index.d.ts +5 -7
- package/dist/index.js +49 -31
- package/dist/index.js.map +1 -1
- package/package.json +13 -5
- package/src/index.ts +46 -38
- package/test/index.test.ts +2 -2
- package/test/integration.test.ts +1 -1
- package/test/queryast.test.ts +3 -3
- package/test/tck.test.ts +24 -7
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +15 -0
- package/jest.config.js +0 -22
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Data, System as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
type
|
|
1
|
+
import { Data, System as _SystemSpec } from '@objectstack/spec';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { QueryAST } from '@objectql/types';
|
|
4
|
+
type _DriverInterface = z.infer<typeof Data.DriverInterface>;
|
|
5
5
|
/**
|
|
6
6
|
* ObjectQL
|
|
7
7
|
* Copyright (c) 2026-present ObjectStack Inc.
|
|
@@ -10,7 +10,7 @@ type DriverInterface = Data.DriverInterface;
|
|
|
10
10
|
* LICENSE file in the root directory of this source tree.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { Driver } from '@objectql/types';
|
|
13
|
+
import { Driver, ObjectQLError } from '@objectql/types';
|
|
14
14
|
import { MongoClient, Db, Filter, ObjectId, FindOptions, FindOneAndUpdateOptions, UpdateFilter, ChangeStream, ChangeStreamDocument } from 'mongodb';
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -119,14 +119,14 @@ export class MongoDriver implements Driver {
|
|
|
119
119
|
if (!this.db) return false;
|
|
120
120
|
await this.db.admin().ping();
|
|
121
121
|
return true;
|
|
122
|
-
} catch (
|
|
122
|
+
} catch (_error) {
|
|
123
123
|
return false;
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
private async getCollection(objectName: string) {
|
|
128
128
|
await this.connected;
|
|
129
|
-
if (!this.db) throw new
|
|
129
|
+
if (!this.db) throw new ObjectQLError({ code: 'DRIVER_CONNECTION_FAILED', message: 'Database not initialized' });
|
|
130
130
|
return this.db.collection<any>(objectName);
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -336,7 +336,7 @@ export class MongoDriver implements Driver {
|
|
|
336
336
|
* QueryAST sort is array of {field, order}, while UnifiedQuery is array of [field, order].
|
|
337
337
|
* QueryAST uses 'aggregations', while legacy uses 'aggregate'.
|
|
338
338
|
*/
|
|
339
|
-
async find(objectName: string, query: any,
|
|
339
|
+
async find(objectName: string, query: any, _options?: any): Promise<any[]> {
|
|
340
340
|
const collection = await this.getCollection(objectName);
|
|
341
341
|
|
|
342
342
|
// Handle both new format (where) and legacy format (filters)
|
|
@@ -486,7 +486,7 @@ export class MongoDriver implements Driver {
|
|
|
486
486
|
return result.deletedCount;
|
|
487
487
|
}
|
|
488
488
|
|
|
489
|
-
async count(objectName: string, filters: any,
|
|
489
|
+
async count(objectName: string, filters: any, _options?: any): Promise<number> {
|
|
490
490
|
const collection = await this.getCollection(objectName);
|
|
491
491
|
// Handle both filter objects and query objects
|
|
492
492
|
let actualFilters = filters;
|
|
@@ -524,7 +524,7 @@ export class MongoDriver implements Driver {
|
|
|
524
524
|
const filter = this.mapFilters(filters);
|
|
525
525
|
|
|
526
526
|
// Remove 'id' field from update data as it shouldn't be updated
|
|
527
|
-
const { id:
|
|
527
|
+
const { id: _idToIgnore, ...updateData } = data;
|
|
528
528
|
|
|
529
529
|
const isAtomic = Object.keys(updateData).some(k => k.startsWith('$'));
|
|
530
530
|
const update = isAtomic ? updateData : { $set: updateData };
|
|
@@ -548,7 +548,7 @@ export class MongoDriver implements Driver {
|
|
|
548
548
|
return { deletedCount: result.deletedCount };
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
-
async aggregate(objectName: string, pipeline: any[],
|
|
551
|
+
async aggregate(objectName: string, pipeline: any[], _options?: any): Promise<any[]> {
|
|
552
552
|
const collection = await this.getCollection(objectName);
|
|
553
553
|
const results = await collection.aggregate(pipeline).toArray();
|
|
554
554
|
// Map MongoDB documents to API format (convert _id to id)
|
|
@@ -563,7 +563,7 @@ export class MongoDriver implements Driver {
|
|
|
563
563
|
* @param options - Optional query options
|
|
564
564
|
* @returns Array of distinct values
|
|
565
565
|
*/
|
|
566
|
-
async distinct(objectName: string, field: string, filters?: any,
|
|
566
|
+
async distinct(objectName: string, field: string, filters?: any, _options?: any): Promise<any[]> {
|
|
567
567
|
const collection = await this.getCollection(objectName);
|
|
568
568
|
|
|
569
569
|
// Convert ObjectQL filters to MongoDB query format
|
|
@@ -651,7 +651,7 @@ export class MongoDriver implements Driver {
|
|
|
651
651
|
*/
|
|
652
652
|
async commitTransaction(transaction: any): Promise<void> {
|
|
653
653
|
if (!transaction || typeof transaction.commitTransaction !== 'function') {
|
|
654
|
-
throw new
|
|
654
|
+
throw new ObjectQLError({ code: 'DRIVER_TRANSACTION_FAILED', message: 'Invalid transaction object. Must be a MongoDB ClientSession.' });
|
|
655
655
|
}
|
|
656
656
|
|
|
657
657
|
try {
|
|
@@ -677,7 +677,7 @@ export class MongoDriver implements Driver {
|
|
|
677
677
|
*/
|
|
678
678
|
async rollbackTransaction(transaction: any): Promise<void> {
|
|
679
679
|
if (!transaction || typeof transaction.abortTransaction !== 'function') {
|
|
680
|
-
throw new
|
|
680
|
+
throw new ObjectQLError({ code: 'DRIVER_TRANSACTION_FAILED', message: 'Invalid transaction object. Must be a MongoDB ClientSession.' });
|
|
681
681
|
}
|
|
682
682
|
|
|
683
683
|
try {
|
|
@@ -689,7 +689,7 @@ export class MongoDriver implements Driver {
|
|
|
689
689
|
|
|
690
690
|
async disconnect() {
|
|
691
691
|
// Close all active change streams
|
|
692
|
-
for (const [
|
|
692
|
+
for (const [_streamId, stream] of this.changeStreams.entries()) {
|
|
693
693
|
await stream.close();
|
|
694
694
|
}
|
|
695
695
|
this.changeStreams.clear();
|
|
@@ -755,13 +755,13 @@ export class MongoDriver implements Driver {
|
|
|
755
755
|
changeStream.on('change', async (change) => {
|
|
756
756
|
try {
|
|
757
757
|
await handler(change);
|
|
758
|
-
} catch (
|
|
759
|
-
|
|
758
|
+
} catch (_error) {
|
|
759
|
+
// Error silently ignored
|
|
760
760
|
}
|
|
761
761
|
});
|
|
762
762
|
|
|
763
|
-
changeStream.on('error', (
|
|
764
|
-
|
|
763
|
+
changeStream.on('error', (_error) => {
|
|
764
|
+
// Error silently ignored
|
|
765
765
|
});
|
|
766
766
|
|
|
767
767
|
return streamId;
|
|
@@ -822,9 +822,9 @@ export class MongoDriver implements Driver {
|
|
|
822
822
|
const cmdOptions = { ...options, ...command.options };
|
|
823
823
|
|
|
824
824
|
switch (command.type) {
|
|
825
|
-
case 'create':
|
|
825
|
+
case 'create': {
|
|
826
826
|
if (!command.data) {
|
|
827
|
-
throw new
|
|
827
|
+
throw new ObjectQLError({ code: 'DRIVER_QUERY_FAILED', message: 'Create command requires data' });
|
|
828
828
|
}
|
|
829
829
|
const created = await this.create(command.object, command.data, cmdOptions);
|
|
830
830
|
return {
|
|
@@ -832,10 +832,11 @@ export class MongoDriver implements Driver {
|
|
|
832
832
|
data: created,
|
|
833
833
|
affected: 1
|
|
834
834
|
};
|
|
835
|
+
}
|
|
835
836
|
|
|
836
|
-
case 'update':
|
|
837
|
+
case 'update': {
|
|
837
838
|
if (!command.id || !command.data) {
|
|
838
|
-
throw new
|
|
839
|
+
throw new ObjectQLError({ code: 'DRIVER_QUERY_FAILED', message: 'Update command requires id and data' });
|
|
839
840
|
}
|
|
840
841
|
const updated = await this.update(command.object, command.id, command.data, cmdOptions);
|
|
841
842
|
return {
|
|
@@ -843,20 +844,22 @@ export class MongoDriver implements Driver {
|
|
|
843
844
|
data: updated,
|
|
844
845
|
affected: updated ? 1 : 0
|
|
845
846
|
};
|
|
847
|
+
}
|
|
846
848
|
|
|
847
|
-
case 'delete':
|
|
849
|
+
case 'delete': {
|
|
848
850
|
if (!command.id) {
|
|
849
|
-
throw new
|
|
851
|
+
throw new ObjectQLError({ code: 'DRIVER_QUERY_FAILED', message: 'Delete command requires id' });
|
|
850
852
|
}
|
|
851
853
|
const deleteCount = await this.delete(command.object, command.id, cmdOptions);
|
|
852
854
|
return {
|
|
853
855
|
success: true,
|
|
854
856
|
affected: deleteCount
|
|
855
857
|
};
|
|
858
|
+
}
|
|
856
859
|
|
|
857
|
-
case 'bulkCreate':
|
|
860
|
+
case 'bulkCreate': {
|
|
858
861
|
if (!command.records || !Array.isArray(command.records)) {
|
|
859
|
-
throw new
|
|
862
|
+
throw new ObjectQLError({ code: 'DRIVER_QUERY_FAILED', message: 'BulkCreate command requires records array' });
|
|
860
863
|
}
|
|
861
864
|
const bulkCreated = await this.createMany(command.object, command.records, cmdOptions);
|
|
862
865
|
return {
|
|
@@ -864,10 +867,11 @@ export class MongoDriver implements Driver {
|
|
|
864
867
|
data: bulkCreated,
|
|
865
868
|
affected: command.records.length
|
|
866
869
|
};
|
|
870
|
+
}
|
|
867
871
|
|
|
868
|
-
case 'bulkUpdate':
|
|
872
|
+
case 'bulkUpdate': {
|
|
869
873
|
if (!command.updates || !Array.isArray(command.updates)) {
|
|
870
|
-
throw new
|
|
874
|
+
throw new ObjectQLError({ code: 'DRIVER_QUERY_FAILED', message: 'BulkUpdate command requires updates array' });
|
|
871
875
|
}
|
|
872
876
|
let updateCount = 0;
|
|
873
877
|
const updateResults = [];
|
|
@@ -881,10 +885,11 @@ export class MongoDriver implements Driver {
|
|
|
881
885
|
data: updateResults,
|
|
882
886
|
affected: updateCount
|
|
883
887
|
};
|
|
888
|
+
}
|
|
884
889
|
|
|
885
|
-
case 'bulkDelete':
|
|
890
|
+
case 'bulkDelete': {
|
|
886
891
|
if (!command.ids || !Array.isArray(command.ids)) {
|
|
887
|
-
throw new
|
|
892
|
+
throw new ObjectQLError({ code: 'DRIVER_QUERY_FAILED', message: 'BulkDelete command requires ids array' });
|
|
888
893
|
}
|
|
889
894
|
let deleted = 0;
|
|
890
895
|
for (const id of command.ids) {
|
|
@@ -895,10 +900,12 @@ export class MongoDriver implements Driver {
|
|
|
895
900
|
success: true,
|
|
896
901
|
affected: deleted
|
|
897
902
|
};
|
|
903
|
+
}
|
|
898
904
|
|
|
899
|
-
default:
|
|
905
|
+
default: {
|
|
900
906
|
const validTypes = ['create', 'update', 'delete', 'bulkCreate', 'bulkUpdate', 'bulkDelete'];
|
|
901
|
-
throw new
|
|
907
|
+
throw new ObjectQLError({ code: 'DRIVER_UNSUPPORTED_OPERATION', message: `Unknown command type: ${(command as any).type}. Valid types are: ${validTypes.join(', ')}` });
|
|
908
|
+
}
|
|
902
909
|
}
|
|
903
910
|
} catch (error: any) {
|
|
904
911
|
return {
|
|
@@ -934,11 +941,12 @@ export class MongoDriver implements Driver {
|
|
|
934
941
|
// MongoDB driver doesn't support raw command execution in the traditional SQL sense
|
|
935
942
|
// Use executeCommand() instead for mutations (create/update/delete)
|
|
936
943
|
// Example: await driver.executeCommand({ type: 'create', object: 'users', data: {...} })
|
|
937
|
-
throw new
|
|
938
|
-
|
|
939
|
-
'
|
|
940
|
-
|
|
941
|
-
|
|
944
|
+
throw new ObjectQLError({
|
|
945
|
+
code: 'DRIVER_UNSUPPORTED_OPERATION',
|
|
946
|
+
message: 'MongoDB driver does not support raw command execution. ' +
|
|
947
|
+
'Use executeCommand() for mutations or aggregate() for complex queries. ' +
|
|
948
|
+
'Example: driver.executeCommand({ type: "create", object: "users", data: {...} })'
|
|
949
|
+
});
|
|
942
950
|
}
|
|
943
951
|
}
|
|
944
952
|
|
package/test/index.test.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { MongoDriver } from '../src';
|
|
10
|
-
import {
|
|
10
|
+
import { vi } from 'vitest';
|
|
11
11
|
|
|
12
12
|
const mockCollection = {
|
|
13
13
|
find: jest.fn().mockReturnThis(),
|
|
@@ -36,7 +36,7 @@ const mockClient = {
|
|
|
36
36
|
db: jest.fn().mockReturnValue(mockDb)
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
vi.mock('mongodb', () => {
|
|
40
40
|
return {
|
|
41
41
|
MongoClient: jest.fn().mockImplementation(() => mockClient),
|
|
42
42
|
ObjectId: jest.fn().mockImplementation((id?: string) => ({
|
package/test/integration.test.ts
CHANGED
package/test/queryast.test.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { MongoDriver } from '../src';
|
|
10
|
-
import {
|
|
10
|
+
import { vi } from 'vitest';
|
|
11
11
|
|
|
12
12
|
// Mock data
|
|
13
13
|
const products = [
|
|
@@ -44,7 +44,7 @@ const mockClient = {
|
|
|
44
44
|
close: jest.fn().mockResolvedValue(undefined)
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
vi.mock('mongodb', () => {
|
|
48
48
|
return {
|
|
49
49
|
MongoClient: jest.fn().mockImplementation(() => mockClient),
|
|
50
50
|
ObjectId: jest.fn(id => id)
|
|
@@ -288,7 +288,7 @@ describe('MongoDriver (QueryAST Format)', () => {
|
|
|
288
288
|
filters: [['id', '=', '1']],
|
|
289
289
|
fields: ['id', 'name']
|
|
290
290
|
};
|
|
291
|
-
const
|
|
291
|
+
const _results = await driver.find('products', query);
|
|
292
292
|
|
|
293
293
|
expect(mockCollection.find).toHaveBeenCalledWith(
|
|
294
294
|
{ _id: '1' },
|
package/test/tck.test.ts
CHANGED
|
@@ -10,34 +10,50 @@
|
|
|
10
10
|
* MongoDB Driver TCK (Technology Compatibility Kit) Tests
|
|
11
11
|
*
|
|
12
12
|
* This test suite verifies that the MongoDB driver passes all TCK requirements.
|
|
13
|
+
* Uses mongodb-memory-server for isolated testing.
|
|
14
|
+
* Tests gracefully skip if MongoDB binary cannot be started.
|
|
13
15
|
*/
|
|
14
16
|
|
|
15
17
|
import { runDriverTCK } from '@objectql/driver-tck';
|
|
16
18
|
import { MongoDriver } from '../src';
|
|
17
19
|
import { MongoMemoryReplSet } from 'mongodb-memory-server';
|
|
18
20
|
|
|
21
|
+
let mongoServer: MongoMemoryReplSet | null = null;
|
|
22
|
+
let mongoAvailable = false;
|
|
23
|
+
|
|
19
24
|
describe('MongoDriver TCK Compliance', () => {
|
|
20
|
-
let mongoServer: MongoMemoryReplSet;
|
|
21
25
|
let driver: MongoDriver;
|
|
22
26
|
|
|
23
27
|
beforeAll(async () => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
try {
|
|
29
|
+
// Start MongoDB Memory Server with replica set (required for transactions)
|
|
30
|
+
mongoServer = await MongoMemoryReplSet.create({
|
|
31
|
+
replSet: { count: 1, storageEngine: 'wiredTiger' }
|
|
32
|
+
});
|
|
33
|
+
mongoAvailable = true;
|
|
34
|
+
} catch (error: unknown) {
|
|
35
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
36
|
+
console.warn('⚠️ MongoDB Memory Server setup failed, TCK tests will be skipped.');
|
|
37
|
+
console.warn(' Reason:', message);
|
|
38
|
+
console.warn(' This is expected in CI environments with network restrictions.');
|
|
39
|
+
mongoAvailable = false;
|
|
40
|
+
}
|
|
28
41
|
}, 120000);
|
|
29
42
|
|
|
30
43
|
afterAll(async () => {
|
|
31
44
|
if (driver) {
|
|
32
|
-
await driver.disconnect();
|
|
45
|
+
try { await driver.disconnect(); } catch { /* ignore */ }
|
|
33
46
|
}
|
|
34
47
|
if (mongoServer) {
|
|
35
|
-
await mongoServer.stop();
|
|
48
|
+
try { await mongoServer.stop(); } catch { /* ignore */ }
|
|
36
49
|
}
|
|
37
50
|
}, 60000);
|
|
38
51
|
|
|
39
52
|
runDriverTCK(
|
|
40
53
|
() => {
|
|
54
|
+
if (!mongoAvailable || !mongoServer) {
|
|
55
|
+
throw new Error('MongoDB not available - TCK tests cannot run');
|
|
56
|
+
}
|
|
41
57
|
const uri = mongoServer.getUri();
|
|
42
58
|
driver = new MongoDriver({
|
|
43
59
|
url: uri,
|
|
@@ -53,6 +69,7 @@ describe('MongoDriver TCK Compliance', () => {
|
|
|
53
69
|
timeout: 30000,
|
|
54
70
|
hooks: {
|
|
55
71
|
beforeEach: async () => {
|
|
72
|
+
if (!mongoAvailable) return;
|
|
56
73
|
// Wait for driver to connect
|
|
57
74
|
await driver['connected'];
|
|
58
75
|
|