Skip to content

Commit a8b9edd

Browse files
committed
Added dummy implementation of Mongo shim
1 parent d3ed45a commit a8b9edd

14 files changed

+1844
-67
lines changed

src/package-lock.json

+955-22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/package.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
],
6464
"devDependencies": {
6565
"@faker-js/faker": "8.4.1",
66+
"@testcontainers/mongodb": "^10.10.1",
67+
"@testcontainers/postgresql": "^10.10.1",
6668
"@types/mongodb": "^4.0.7",
6769
"@types/node": "20.11.30",
6870
"@types/pg": "^8.11.6",
@@ -85,12 +87,14 @@
8587
"vitepress": "1.0.1"
8688
},
8789
"peerDependencies": {
90+
"close-with-grace": "^1.3.0",
8891
"pg": "^8.12.0",
89-
"pg-format": "^1.0.4",
90-
"close-with-grace": "^1.3.0"
92+
"pg-format": "^1.0.4"
9193
},
9294
"workspaces": [
9395
"packages/pongo"
9496
],
95-
"dependencies": {}
97+
"dependencies": {
98+
"testcontainers": "^10.10.1"
99+
}
96100
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import {
2+
MongoDBContainer,
3+
type StartedMongoDBContainer,
4+
} from '@testcontainers/mongodb';
5+
import {
6+
PostgreSqlContainer,
7+
type StartedPostgreSqlContainer,
8+
} from '@testcontainers/postgresql';
9+
import assert from 'assert';
10+
import { Db as MongoDb, MongoClient as OriginalMongoClient } from 'mongodb';
11+
import { after, before, describe, it } from 'node:test';
12+
import MongoClient from '../mongo/mongoClient';
13+
import type { Db } from '../mongo/mongoDb';
14+
import { endAllPools } from '../postgres';
15+
16+
type User = { name: string; age: number };
17+
18+
void describe('MongoDB Compatibility Tests', () => {
19+
let postgres: StartedPostgreSqlContainer;
20+
let postgresConnectionString: string;
21+
let pongoClient: MongoClient;
22+
23+
let mongo: StartedMongoDBContainer;
24+
let mongoConnectionString: string;
25+
let mongoClient: OriginalMongoClient;
26+
27+
let pongoDb: Db;
28+
let mongoDb: MongoDb;
29+
30+
before(async () => {
31+
postgres = await new PostgreSqlContainer().start();
32+
postgresConnectionString = postgres.getConnectionUri();
33+
pongoClient = new MongoClient(postgresConnectionString);
34+
await pongoClient.connect();
35+
36+
mongo = await new MongoDBContainer('mongo:6.0.12').start();
37+
mongoConnectionString = mongo.getConnectionString();
38+
mongoClient = new OriginalMongoClient(mongoConnectionString, {
39+
directConnection: true,
40+
});
41+
await mongoClient.connect();
42+
43+
const dbName = postgres.getDatabase();
44+
45+
pongoDb = pongoClient.db(dbName);
46+
mongoDb = mongoClient.db(dbName);
47+
});
48+
49+
after(async () => {
50+
try {
51+
await endAllPools();
52+
await postgres.stop();
53+
} catch (error) {
54+
console.log(error);
55+
}
56+
try {
57+
await mongoClient.close();
58+
await mongo.stop();
59+
} catch (error) {
60+
console.log(error);
61+
}
62+
});
63+
64+
void describe('Insert Operations', () => {
65+
void it('should insert a document into both PostgreSQL and MongoDB', async () => {
66+
const pongoCollection = pongoDb.collection<User>('testCollection');
67+
const mongoCollection = mongoDb.collection<User>('testCollection');
68+
69+
const doc = { name: 'Alice', age: 25 };
70+
71+
const pongoInsertResult = await pongoCollection.insertOne(doc);
72+
const mongoInsertResult = await mongoCollection.insertOne(doc);
73+
74+
assert(pongoInsertResult.insertedId);
75+
assert(mongoInsertResult.insertedId);
76+
77+
const pongoDoc = await pongoCollection.findOne({
78+
_id: pongoInsertResult.insertedId,
79+
});
80+
const mongoDoc = await mongoCollection.findOne({
81+
_id: mongoInsertResult.insertedId,
82+
});
83+
84+
assert.deepStrictEqual(
85+
{
86+
name: pongoDoc!.name,
87+
age: pongoDoc!.age,
88+
},
89+
{
90+
name: mongoDoc!.name,
91+
age: mongoDoc!.age,
92+
},
93+
);
94+
});
95+
});
96+
97+
void describe('Update Operations', () => {
98+
void it('should update a document in both PostgreSQL and MongoDB', async () => {
99+
const pongoCollection = pongoDb.collection<User>('testCollection');
100+
const mongoCollection = mongoDb.collection<User>('testCollection');
101+
const doc = { name: 'Bob', age: 30 };
102+
103+
const pongoInsertResult = await pongoCollection.insertOne(doc);
104+
const mongoInsertResult = await mongoCollection.insertOne(doc);
105+
106+
const update = { $set: { age: 31 } };
107+
108+
await pongoCollection.updateOne(
109+
{ _id: pongoInsertResult.insertedId },
110+
update,
111+
);
112+
await mongoCollection.updateOne(
113+
{ _id: mongoInsertResult.insertedId },
114+
update,
115+
);
116+
117+
const pongoDoc = await pongoCollection.findOne({
118+
_id: pongoInsertResult.insertedId,
119+
});
120+
const mongoDoc = await mongoCollection.findOne({
121+
_id: mongoInsertResult.insertedId,
122+
});
123+
124+
assert.deepStrictEqual(
125+
{
126+
name: pongoDoc!.name,
127+
age: 31,
128+
},
129+
{
130+
name: mongoDoc!.name,
131+
age: 31,
132+
},
133+
);
134+
});
135+
});
136+
137+
void describe('Delete Operations', () => {
138+
void it('should delete a document from both PostgreSQL and MongoDB', async () => {
139+
const pongoCollection = pongoDb.collection<User>('testCollection');
140+
const mongoCollection = mongoDb.collection<User>('testCollection');
141+
const doc = { name: 'Charlie', age: 35 };
142+
143+
const pongoInsertResult = await pongoCollection.insertOne(doc);
144+
const mongoInsertResult = await mongoCollection.insertOne(doc);
145+
146+
await pongoCollection.deleteOne({ _id: pongoInsertResult.insertedId });
147+
await mongoCollection.deleteOne({ _id: mongoInsertResult.insertedId });
148+
149+
const pongoDoc = await pongoCollection.findOne({
150+
_id: pongoInsertResult.insertedId,
151+
});
152+
const mongoDoc = await mongoCollection.findOne({
153+
_id: mongoInsertResult.insertedId,
154+
});
155+
156+
assert.strictEqual(pongoDoc, null);
157+
assert.strictEqual(mongoDoc, null);
158+
});
159+
});
160+
161+
void describe('Find Operations', () => {
162+
void it('should find documents with a filter in both PostgreSQL and MongoDB', async () => {
163+
const pongoCollection = pongoDb.collection<User>('testCollection');
164+
const mongoCollection = mongoDb.collection<User>('testCollection');
165+
const docs = [
166+
{ name: 'David', age: 40 },
167+
{ name: 'Eve', age: 45 },
168+
{ name: 'Frank', age: 50 },
169+
];
170+
171+
await pongoCollection.insertOne(docs[0]!);
172+
await pongoCollection.insertOne(docs[1]!);
173+
await pongoCollection.insertOne(docs[2]!);
174+
175+
await mongoCollection.insertOne(docs[0]!);
176+
await mongoCollection.insertOne(docs[1]!);
177+
await mongoCollection.insertOne(docs[2]!);
178+
179+
const pongoDocs = await pongoCollection
180+
.find({ age: { $gte: 45 } })
181+
.toArray();
182+
const mongoDocs = await mongoCollection
183+
.find({ age: { $gte: 45 } })
184+
.toArray();
185+
186+
assert.strictEqual(pongoDocs.length, 2);
187+
188+
assert.deepStrictEqual(
189+
pongoDocs.map((d) => ({ name: d.name, age: d.age })),
190+
mongoDocs.map((d) => ({ name: d.name, age: d.age })),
191+
);
192+
});
193+
194+
void it('should find one document with a filter in both PostgreSQL and MongoDB', async () => {
195+
const pongoCollection = pongoDb.collection<User>('testCollection');
196+
const mongoCollection = mongoDb.collection<User>('testCollection');
197+
const doc = { name: 'Grace', age: 55 };
198+
199+
await pongoCollection.insertOne(doc);
200+
await mongoCollection.insertOne(doc);
201+
202+
const pongoDoc = await pongoCollection.findOne({ name: 'Grace' });
203+
const mongoDoc = await mongoCollection.findOne({ name: 'Grace' });
204+
205+
assert.deepStrictEqual(
206+
{
207+
name: pongoDoc!.name,
208+
age: pongoDoc!.age,
209+
},
210+
{
211+
name: mongoDoc!.name,
212+
age: mongoDoc!.age,
213+
},
214+
);
215+
});
216+
});
217+
});

src/packages/pongo/src/main/client.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ import type { PongoClient, PongoDb } from './typing';
44
export const pongoClient = (connectionString: string): PongoClient => {
55
const dbClient = getDbClient(connectionString);
66

7-
return {
8-
connect: () => dbClient.connect(),
7+
const pongoClient: PongoClient = {
8+
connect: async () => {
9+
await dbClient.connect();
10+
return pongoClient;
11+
},
912
close: () => dbClient.close(),
1013
db: (dbName?: string): PongoDb =>
1114
dbName ? getDbClient(connectionString, dbName) : dbClient,
1215
};
16+
17+
return pongoClient;
1318
};

src/packages/pongo/src/main/typing.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export interface PongoClient {
2-
connect(): Promise<void>;
2+
connect(): Promise<this>;
33

44
close(): Promise<void>;
55

@@ -12,7 +12,7 @@ export interface PongoDb {
1212

1313
export interface PongoCollection<T> {
1414
createCollection(): Promise<void>;
15-
insertOne(document: T): Promise<PongoInsertResult>;
15+
insertOne(document: T): Promise<PongoInsertOneResult>;
1616
updateOne(
1717
filter: PongoFilter<T>,
1818
update: PongoUpdate<T>,
@@ -44,15 +44,17 @@ export type PongoUpdate<T> = {
4444
$push?: { [P in keyof T]?: T[P] };
4545
};
4646

47-
export interface PongoInsertResult {
47+
export interface PongoInsertOneResult {
4848
insertedId: string | null;
49-
insertedCount: number | null;
49+
acknowledged: boolean;
5050
}
5151

5252
export interface PongoUpdateResult {
53-
modifiedCount: number | null;
53+
acknowledged: boolean;
54+
modifiedCount: number;
5455
}
5556

5657
export interface PongoDeleteResult {
57-
deletedCount: number | null;
58+
acknowledged: boolean;
59+
deletedCount: number;
5860
}
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export class FindCursor<T> {
2+
private findDocumentsPromise: Promise<T[]>;
3+
private documents: T[] | null = null;
4+
private index: number = 0;
5+
6+
constructor(documents: Promise<T[]>) {
7+
this.findDocumentsPromise = documents;
8+
}
9+
10+
async toArray(): Promise<T[]> {
11+
return this.findDocuments();
12+
}
13+
14+
async forEach(callback: (doc: T) => void): Promise<void> {
15+
const docs = await this.findDocuments();
16+
17+
for (const doc of docs) {
18+
callback(doc);
19+
}
20+
return Promise.resolve();
21+
}
22+
23+
hasNext(): boolean {
24+
if (this.documents === null) throw Error('Error while fetching documents');
25+
return this.index < this.documents.length;
26+
}
27+
28+
async next(): Promise<T | null> {
29+
const docs = await this.findDocuments();
30+
return this.hasNext() ? docs[this.index++] ?? null : null;
31+
}
32+
33+
private async findDocuments(): Promise<T[]> {
34+
this.documents = await this.findDocumentsPromise;
35+
return this.documents;
36+
}
37+
}

src/packages/pongo/src/mongo/index.ts

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// src/MongoClientShim.ts
2+
import { pongoClient, type PongoClient } from '../main';
3+
import { Db } from './mongoDb';
4+
5+
export default class MongoClient {
6+
private pongoClient: PongoClient;
7+
8+
constructor(connectionString: string) {
9+
this.pongoClient = pongoClient(connectionString);
10+
}
11+
12+
async connect() {
13+
await this.pongoClient.connect();
14+
return this;
15+
}
16+
17+
async close() {
18+
await this.pongoClient.close();
19+
}
20+
21+
db(dbName: string): Db {
22+
return new Db(this.pongoClient.db(dbName));
23+
}
24+
}

0 commit comments

Comments
 (0)