Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added missing methods: replaceOne, drop, rename, countDocuments and added handle method #28

Merged
merged 6 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 134 additions & 118 deletions src/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@event-driven-io/pongo-core",
"version": "0.3.0",
"version": "0.4.0",
"description": "Pongo - Mongo with strong consistency on top of Postgres",
"type": "module",
"engines": {
Expand Down
4 changes: 2 additions & 2 deletions src/packages/dumbo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@event-driven-io/dumbo",
"version": "0.1.0",
"version": "0.2.0",
"description": "Dumbo - tools for dealing with PostgreSQL",
"type": "module",
"scripts": {
Expand Down Expand Up @@ -60,6 +60,6 @@
"@types/node": "20.11.30"
},
"dependencies": {
"@event-driven-io/dumbo": "^0.1.0"
"@event-driven-io/dumbo": "^0.2.0"
}
}
4 changes: 2 additions & 2 deletions src/packages/pongo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@event-driven-io/pongo",
"version": "0.3.0",
"version": "0.4.0",
"description": "Pongo - Mongo with strong consistency on top of Postgres",
"type": "module",
"scripts": {
Expand Down Expand Up @@ -47,7 +47,7 @@
"dist"
],
"peerDependencies": {
"@event-driven-io/dumbo": "^0.1.0",
"@event-driven-io/dumbo": "^0.2.0",
"@types/mongodb": "^4.0.7",
"@types/pg": "^8.11.6",
"@types/pg-format": "^1.0.5",
Expand Down
154 changes: 153 additions & 1 deletion src/packages/pongo/src/e2e/compatibilityTest.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import {
type StartedPostgreSqlContainer,
} from '@testcontainers/postgresql';
import assert from 'assert';
import { Db as MongoDb, MongoClient as OriginalMongoClient } from 'mongodb';
import {
Db as MongoDb,
ObjectId,
MongoClient as OriginalMongoClient,
} from 'mongodb';
import { after, before, describe, it } from 'node:test';
import { v4 as uuid } from 'uuid';
import { MongoClient, type Db } from '../';

type History = { street: string };
Expand All @@ -20,6 +25,7 @@ type Address = {
};

type User = {
_id?: ObjectId;
name: string;
age: number;
address?: Address;
Expand Down Expand Up @@ -406,6 +412,51 @@ void describe('MongoDB Compatibility Tests', () => {
});
});

void describe('Replace Operations', () => {
void it('should replace a document in both PostgreSQL and MongoDB', async () => {
const pongoCollection = pongoDb.collection<User>('updateOne');
const mongoCollection = mongoDb.collection<User>('updateOne');
const doc = { name: 'Roger', age: 30 };

const pongoInsertResult = await pongoCollection.insertOne(doc);
const mongoInsertResult = await mongoCollection.insertOne(doc);

const replacement = { name: 'Not Roger', age: 100, tags: ['tag2'] };

await pongoCollection.replaceOne(
{ _id: pongoInsertResult.insertedId },
replacement,
);
await mongoCollection.replaceOne(
{ _id: mongoInsertResult.insertedId },
replacement,
);

const pongoDoc = await pongoCollection.findOne({
_id: pongoInsertResult.insertedId,
});
const mongoDoc = await mongoCollection.findOne({
_id: mongoInsertResult.insertedId,
});

assert.strictEqual(mongoDoc?.name, replacement.name);
assert.deepEqual(mongoDoc?.age, replacement.age);
assert.deepEqual(mongoDoc?.tags, replacement.tags);
assert.deepStrictEqual(
{
name: pongoDoc!.name,
age: pongoDoc!.age,
tags: pongoDoc!.tags,
},
{
name: mongoDoc.name,
age: mongoDoc.age,
tags: mongoDoc.tags,
},
);
});
});

void describe('Delete Operations', () => {
void it('should delete a document from both PostgreSQL and MongoDB', async () => {
const pongoCollection = pongoDb.collection<User>('testCollection');
Expand Down Expand Up @@ -859,4 +910,105 @@ void describe('MongoDB Compatibility Tests', () => {
);
});
});

void describe('Handle Operations', () => {
void it('should insert a new document if it does not exist', async () => {
const pongoCollection = pongoDb.collection<User>('handleCollection');
const nonExistingId = uuid() as unknown as ObjectId;

const newDoc: User = { name: 'John', age: 25 };

const handle = (_existing: User | null) => newDoc;

const resultPongo = await pongoCollection.handle(nonExistingId, handle);
assert.deepStrictEqual(resultPongo, { ...newDoc, _id: nonExistingId });

const pongoDoc = await pongoCollection.findOne({
_id: nonExistingId,
});

assert.deepStrictEqual(pongoDoc, { ...newDoc, _id: nonExistingId });
});

void it('should replace an existing document', async () => {
const pongoCollection = pongoDb.collection<User>('handleCollection');

const existingDoc: User = { name: 'John', age: 25 };
const updatedDoc: User = { name: 'John', age: 30 };

const pongoInsertResult = await pongoCollection.insertOne(existingDoc);

const handle = (_existing: User | null) => updatedDoc;

const resultPongo = await pongoCollection.handle(
pongoInsertResult.insertedId,
handle,
);

assert.deepStrictEqual(resultPongo, {
...updatedDoc,
});

const pongoDoc = await pongoCollection.findOne({
_id: pongoInsertResult.insertedId,
});

assert.deepStrictEqual(pongoDoc, {
...updatedDoc,
_id: pongoInsertResult.insertedId,
});
});

void it('should delete an existing document if the handler returns null', async () => {
const pongoCollection = pongoDb.collection<User>('handleCollection');

const existingDoc: User = { name: 'John', age: 25 };

const pongoInsertResult = await pongoCollection.insertOne(existingDoc);

const handle = (_existing: User | null) => null;

const resultPongo = await pongoCollection.handle(
pongoInsertResult.insertedId,
handle,
);

assert.strictEqual(resultPongo, null);

const pongoDoc = await pongoCollection.findOne({
_id: pongoInsertResult.insertedId,
});

assert.strictEqual(pongoDoc, null);
});

void it('should do nothing if the handler returns the existing document unchanged', async () => {
const pongoCollection = pongoDb.collection<User>('handleCollection');

const existingDoc: User = { name: 'John', age: 25 };

const pongoInsertResult = await pongoCollection.insertOne(existingDoc);

const handle = (existing: User | null) => existing;

const resultPongo = await pongoCollection.handle(
pongoInsertResult.insertedId,
handle,
);

assert.deepStrictEqual(resultPongo, {
...existingDoc,
_id: pongoInsertResult.insertedId,
});

const pongoDoc = await pongoCollection.findOne({
_id: pongoInsertResult.insertedId,
});

assert.deepStrictEqual(pongoDoc, {
...existingDoc,
_id: pongoInsertResult.insertedId,
});
});
});
});
4 changes: 2 additions & 2 deletions src/packages/pongo/src/main/dbClient.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { postgresClient, type PongoClientOptions } from '../postgres';
import type { PongoCollection } from './typing/operations';
import type { PongoCollection, PongoDocument } from './typing/operations';

export interface DbClient {
connect(): Promise<void>;
close(): Promise<void>;
collection: <T>(name: string) => PongoCollection<T>;
collection: <T extends PongoDocument>(name: string) => PongoCollection<T>;
}

export const getDbClient = (options: PongoClientOptions): DbClient => {
Expand Down
2 changes: 1 addition & 1 deletion src/packages/pongo/src/main/pongoClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const pongoClient = (connectionString: string): PongoClient => {
return (
dbClients.get(dbName) ??
dbClients
.set(dbName, getDbClient({ connectionString, database: dbName }))
.set(dbName, getDbClient({ connectionString, dbName: dbName }))
.get(dbName)!
);
},
Expand Down
21 changes: 19 additions & 2 deletions src/packages/pongo/src/main/typing/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ export interface PongoClient {
}

export interface PongoDb {
collection<T>(name: string): PongoCollection<T>;
collection<T extends PongoDocument>(name: string): PongoCollection<T>;
}

export interface PongoCollection<T> {
export interface PongoCollection<T extends PongoDocument> {
readonly dbName: string;
readonly collectionName: string;
createCollection(): Promise<void>;
insertOne(document: T): Promise<PongoInsertOneResult>;
insertMany(documents: T[]): Promise<PongoInsertManyResult>;
updateOne(
filter: PongoFilter<T>,
update: PongoUpdate<T>,
): Promise<PongoUpdateResult>;
replaceOne(
filter: PongoFilter<T>,
document: WithoutId<T>,
): Promise<PongoUpdateResult>;
updateMany(
filter: PongoFilter<T>,
update: PongoUpdate<T>,
Expand All @@ -26,12 +32,17 @@ export interface PongoCollection<T> {
deleteMany(filter: PongoFilter<T>): Promise<PongoDeleteResult>;
findOne(filter: PongoFilter<T>): Promise<T | null>;
find(filter: PongoFilter<T>): Promise<T[]>;
drop(): Promise<boolean>;
rename(newName: string): Promise<PongoCollection<T>>;
handle(id: string, handle: DocumentHandler<T>): Promise<T | null>;
}

export type HasId = { _id: string };

export type WithId<T> = T & HasId;

export type WithoutId<T> = Omit<T, '_id'>;

export type PongoFilter<T> =
| {
[P in keyof T]?: T[P] | PongoFilterOperator<T[P]>;
Expand Down Expand Up @@ -91,3 +102,9 @@ export interface PongoDeleteManyResult {
acknowledged: boolean;
deletedCount: number;
}

export type PongoDocument = Record<string, unknown>;

export type DocumentHandler<T extends PongoDocument> =
| ((document: T | null) => T | null)
| ((document: T | null) => Promise<T | null>);
Loading