Skip to content

Commit

Permalink
Refactor Filter type to only allow valid keys (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrinheusser committed Apr 4, 2023
1 parent fbccfa3 commit a49057d
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 8 deletions.
16 changes: 11 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ type IntegerType = number | Int32 | Long;

type NumericType = IntegerType | Decimal128 | Double;

interface RootFilterOperators<T> extends Document {
interface RootFilterOperators<T> {
$and?: Filter<T>[];
$nor?: Filter<T>[];
$or?: Filter<T>[];
Expand All @@ -637,7 +637,7 @@ interface RootFilterOperators<T> extends Document {
*
* @see https://docs.mongodb.com/manual/reference/operator/query/
*/
interface FilterOperators<TValue> extends Document {
interface FilterOperators<TValue> {
$eq?: TValue;
$gt?: TValue;
$gte?: TValue;
Expand Down Expand Up @@ -775,7 +775,9 @@ export type Filter<T> =
& NotImplementedOperators<"$type">
& RootFilterOperators<T>
& {
[Key in keyof T]?: T[Key] | FilterOperators<T[Key]>;
[Key in AllKeys<T>]?:
| KeyWithSubKeys<T, Key>
| FilterOperators<KeyWithSubKeys<T, Key>>;
};

export type UpdateFilter<T> =
Expand Down Expand Up @@ -812,8 +814,12 @@ type ArrayKeys<T> = {
: never;
}[keyof T] extends infer Keys ? Keys extends string ? Keys : never : never;

type Split<S extends string, D extends string> = S extends
`${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : [S];
type AllKeys<T> = T extends Document ? {
[K in keyof T]: T[K] extends Record<string, unknown>
? `${string & K}.${AllKeys<T[K]>}` | `${string & K}`
: `${string & K}`;
}[keyof T]
: never;

type KeyWithSubKeys<T, K extends string> = K extends
`${infer Key}.${infer Rest}` ? KeyWithSubKeys<T[Extract<Key, keyof T>], Rest>
Expand Down
60 changes: 57 additions & 3 deletions tests/cases/03_curd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ testWithTestDBClient("testFindOne", async (db) => {
const user1 = await users.findOne();
assertEquals(Object.keys(user1!), ["_id", "username", "password", "date"]);

const query = { test: 1 };
const query = { username: "does not exist" };
const findNull = await users.findOne(query);
assertEquals(findNull, undefined);
const projectionUser = await users.findOne(
Expand All @@ -136,6 +136,60 @@ testWithTestDBClient("testFindOne", async (db) => {
assertEquals(Object.keys(projectionUserWithId!), ["_id", "username"]);
});

testWithTestDBClient("testFindOneWithNestedDocument", async (db) => {
const users = db.collection<ComplexUser>("mongo_test_users");
await users.insertOne({
username: "user1",
password: "pass1",
friends: ["Alice", "Bob"],
likes: {
drinks: ["coffee", "tea"],
food: ["pizza", "pasta"],
hobbies: {
indoor: ["puzzles", "reading"],
outdoor: ["hiking", "climbing"],
},
},
});
const user1 = await users.findOne({
"likes.hobbies.outdoor": { $elemMatch: { $eq: "climbing" } },
});
assertEquals(Object.keys(user1!), [
"_id",
"username",
"password",
"friends",
"likes",
]);
});

testWithTestDBClient("testFindOneWithNestedDocument", async (db) => {
const users = db.collection<ComplexUser>("mongo_test_users");
await users.insertOne({
username: "user1",
password: "pass1",
friends: ["Alice", "Bob"],
likes: {
drinks: ["coffee", "tea"],
food: ["pizza", "pasta"],
hobbies: {
indoor: ["puzzles", "reading"],
outdoor: ["hiking", "climbing"],
},
},
});
const user1 = await users.findOne({
"likes.hobbies.outdoor": { $elemMatch: { $eq: "climbing" } },
});
assertEquals(Object.keys(user1!), [
"_id",
"username",
"password",
"friends",
"likes",
]);
});

testWithTestDBClient("testInsertMany", async (db) => {
const users = db.collection<User>("mongo_test_users");
const { insertedCount, insertedIds } = await users.insertMany([
Expand Down Expand Up @@ -425,7 +479,7 @@ testWithTestDBClient("testFind", async (db) => {
assert(findUsers instanceof Array);
assertEquals(findUsers.length, 1);

const notFound = await users.find({ test: 1 }).toArray();
const notFound = await users.find({ username: "does not exist" }).toArray();
assertEquals(notFound, []);
});

Expand Down Expand Up @@ -688,7 +742,7 @@ testWithTestDBClient("testFindEmptyAsyncIteration", async (db) => {
uid: i,
});
}
const cursor = users.find({ nonexistent: "foo" });
const cursor = users.find({ username: "does not exist" });
const docs = [];
for await (const doc of cursor) {
docs.push(doc);
Expand Down

0 comments on commit a49057d

Please sign in to comment.