Skip to content

Commit

Permalink
Added the first implementation of the Pongo implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
oskardudycz committed Jul 5, 2024
1 parent a2ae083 commit d3ed45a
Show file tree
Hide file tree
Showing 14 changed files with 350 additions and 23 deletions.
8 changes: 8 additions & 0 deletions src/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@
"dist"
],
"devDependencies": {
"@faker-js/faker": "8.4.1",
"@types/mongodb": "^4.0.7",
"@types/node": "20.11.30",
"@types/pg": "^8.11.6",
"@types/pg-format": "^1.0.5",
"@faker-js/faker": "8.4.1",
"@types/mongodb": "^4.0.7",
"@types/uuid": "9.0.8",
"@typescript-eslint/eslint-plugin": "7.9.0",
"@typescript-eslint/parser": "7.9.0",
Expand All @@ -86,7 +86,8 @@
},
"peerDependencies": {
"pg": "^8.12.0",
"pg-format": "^1.0.4"
"pg-format": "^1.0.4",
"close-with-grace": "^1.3.0"
},
"workspaces": [
"packages/pongo"
Expand Down
5 changes: 3 additions & 2 deletions src/packages/pongo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@
],
"peerDependencies": {
"@types/uuid": "^9.0.8",
"uuid": "^9.0.1",
"close-with-grace": "^1.3.0",
"pg": "^8.12.0",
"pg-format": "^1.0.4"
"pg-format": "^1.0.4",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/node": "20.11.30",
Expand Down
2 changes: 2 additions & 0 deletions src/packages/pongo/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './main';
export * from './postgres';
13 changes: 13 additions & 0 deletions src/packages/pongo/src/main/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { getDbClient } from './dbClient';
import type { PongoClient, PongoDb } from './typing';

export const pongoClient = (connectionString: string): PongoClient => {
const dbClient = getDbClient(connectionString);

return {
connect: () => dbClient.connect(),
close: () => dbClient.close(),
db: (dbName?: string): PongoDb =>
dbName ? getDbClient(connectionString, dbName) : dbClient,
};
};
16 changes: 16 additions & 0 deletions src/packages/pongo/src/main/dbClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { postgresClient } from '../postgres';
import type { PongoCollection } from './typing';

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

export const getDbClient = (
connectionString: string,
database?: string,
): DbClient => {
// This is the place where in the future could come resolution of other database types
return postgresClient(connectionString, database);
};
3 changes: 3 additions & 0 deletions src/packages/pongo/src/main/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './client';
export * from './dbClient';
export * from './typing';
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
// src/pongoTypes.ts
export interface PongoClient {
connect(): Promise<void>;

close(): Promise<void>;

db(dbName?: string): PongoDb;
}

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

export interface PongoCollection<T> {
createCollection(): Promise<void>;
insertOne(document: T): Promise<PongoInsertResult>;
updateOne(
filter: PongoFilter<T>,
update: PongoUpdate<T>,
): Promise<PongoUpdateResult>;
deleteOne(filter: PongoFilter<T>): Promise<PongoDeleteResult>;
findOne(filter: PongoFilter<T>): Promise<T | null>;
find(filter: PongoFilter<T>): Promise<T[]>;
}

export type PongoFilter<T> = {
[P in keyof T]?: T[P] | PongoFilterOperator<T[P]>;
};
Expand All @@ -12,37 +35,24 @@ export type PongoFilterOperator<T> = {
$ne?: T;
$in?: T[];
$nin?: T[];
// Add more operators as needed
};

export type PongoUpdate<T> = {
$set?: Partial<T>;
$unset?: { [P in keyof T]?: '' };
$inc?: { [P in keyof T]?: number };
$push?: { [P in keyof T]?: T[P] };
// Add more update operators as needed
};

export interface PongoInsertResult {
insertedId: string;
insertedId: string | null;
insertedCount: number | null;
}

export interface PongoUpdateResult {
modifiedCount: number;
modifiedCount: number | null;
}

export interface PongoDeleteResult {
deletedCount: number;
}

export interface PongoCollection<T> {
createCollection(): Promise<void>;
insertOne(document: T): Promise<PongoInsertResult>;
updateOne(
filter: PongoFilter<T>,
update: PongoUpdate<T>,
): Promise<PongoUpdateResult>;
deleteOne(filter: PongoFilter<T>): Promise<PongoDeleteResult>;
findOne(filter: PongoFilter<T>): Promise<T | null>;
find(filter: PongoFilter<T>): Promise<T[]>;
deletedCount: number | null;
}
109 changes: 109 additions & 0 deletions src/packages/pongo/src/postgres/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import type { Pool } from 'pg';
import { v4 as uuid } from 'uuid';
import {
type DbClient,
type PongoCollection,
type PongoDeleteResult,
type PongoFilter,
type PongoInsertResult,
type PongoUpdate,
type PongoUpdateResult,
} from '../main';
import { constructFilterQuery } from './filter';
import { getPool } from './pool';
import { constructUpdateQuery } from './update';
import { sql } from './execute';

export const postgresClient = (
connectionString: string,
database?: string,
): DbClient => {
const pool = getPool({ connectionString, database });

return {
connect: () => Promise.resolve(),
close: () => Promise.resolve(),
collection: <T>(name: string) => postgresCollection<T>(name, pool),
};
};

export const postgresCollection = <T>(
collectionName: string,
pool: Pool,
): PongoCollection<T> => {
const createCollection = async (): Promise<void> => {
await sql(
pool,
'CREATE TABLE IF NOT EXISTS %I (id UUID PRIMARY KEY, data JSONB)',
collectionName,
);
};

return {
createCollection,
insertOne: async (document: T): Promise<PongoInsertResult> => {
await createCollection();

const id = uuid();

const result = await sql(
pool,
'INSERT INTO %I (id, data) VALUES (%L, %L)',
collectionName,
id,
JSON.stringify({ ...document, _id: id }),
);

return result.rowCount
? { insertedId: id, insertedCount: result.rowCount }
: { insertedId: null, insertedCount: null };
},
updateOne: async (
filter: PongoFilter<T>,
update: PongoUpdate<T>,
): Promise<PongoUpdateResult> => {
const filterQuery = constructFilterQuery(filter);
const updateQuery = constructUpdateQuery(update);

const result = await sql(
pool,
'UPDATE %I SET data = %s WHERE %s',
collectionName,
updateQuery,
filterQuery,
);
return { modifiedCount: result.rowCount };
},
deleteOne: async (filter: PongoFilter<T>): Promise<PongoDeleteResult> => {
const filterQuery = constructFilterQuery(filter);
const result = await sql(
pool,
'DELETE FROM %I WHERE %s',
collectionName,
filterQuery,
);
return { deletedCount: result.rowCount };
},
findOne: async (filter: PongoFilter<T>): Promise<T | null> => {
const filterQuery = constructFilterQuery(filter);
const result = await sql(
pool,
'SELECT data FROM %I WHERE %s LIMIT 1',
collectionName,
filterQuery,
);
return (result.rows[0]?.data ?? null) as T | null;
},
find: async (filter: PongoFilter<T>): Promise<T[]> => {
const filterQuery = constructFilterQuery(filter);
const result = await sql(
pool,
'SELECT data FROM %I WHERE %s LIMIT 1',
collectionName,
filterQuery,
);

return result.rows.map((row) => row.data as T);
},
};
};
28 changes: 28 additions & 0 deletions src/packages/pongo/src/postgres/execute/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { QueryResultRow, Pool, QueryResult, PoolClient } from 'pg';
import format from 'pg-format';

export const sql = async <Result extends QueryResultRow = QueryResultRow>(
pool: Pool,
sqlText: string,
...params: unknown[]
): Promise<QueryResult<Result>> => {
const client = await pool.connect();
try {
const query = format(sqlText, ...params);
return await client.query<Result>(query);
} finally {
client.release();
}
};

export const execute = async <Result = void>(
pool: Pool,
handle: (client: PoolClient) => Promise<Result>,
) => {
const client = await pool.connect();
try {
return await handle(client);
} finally {
client.release();
}
};
50 changes: 50 additions & 0 deletions src/packages/pongo/src/postgres/filter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import format from 'pg-format';
import type { PongoFilter } from '../../main';

export const constructFilterQuery = <T>(filter: PongoFilter<T>): string => {
const filters = Object.entries(filter).map(([key, value]) => {
if (typeof value === 'object' && !Array.isArray(value)) {
return constructComplexFilterQuery(key, value as Record<string, unknown>);
} else {
return format('data->>%I = %L', key, value);
}
});
return filters.join(' AND ');
};

export const constructComplexFilterQuery = (
key: string,
value: Record<string, unknown>,
): string => {
const subFilters = Object.entries(value).map(([operator, val]) => {
switch (operator) {
case '$eq':
return format('data->>%I = %L', key, val);
case '$gt':
return format('data->>%I > %L', key, val);
case '$gte':
return format('data->>%I >= %L', key, val);
case '$lt':
return format('data->>%I < %L', key, val);
case '$lte':
return format('data->>%I <= %L', key, val);
case '$ne':
return format('data->>%I != %L', key, val);
case '$in':
return format(
'data->>%I IN (%s)',
key,
(val as unknown[]).map((v) => format('%L', v)).join(', '),
);
case '$nin':
return format(
'data->>%I NOT IN (%s)',
key,
(val as unknown[]).map((v) => format('%L', v)).join(', '),
);
default:
throw new Error(`Unsupported operator: ${operator}`);
}
});
return subFilters.join(' AND ');
};
2 changes: 2 additions & 0 deletions src/packages/pongo/src/postgres/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './client';
export * from './pool';
Loading

0 comments on commit d3ed45a

Please sign in to comment.