Skip to content

Commit

Permalink
update types to allow nested push, pull and pullAll (#391)
Browse files Browse the repository at this point in the history
* update types to allow nested ,  and

* cleanup

* format and add pull test
  • Loading branch information
andrinheusser authored Apr 4, 2023
1 parent 9eedf24 commit fbccfa3
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 14 deletions.
44 changes: 30 additions & 14 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -692,24 +692,24 @@ interface UpdateOperators<T> extends Document {
// deno-lint-ignore no-explicit-any
$pop?: DocumentOperator<T, Array<any>, (1 | -1)>;
$pull?: {
// deno-lint-ignore no-explicit-any
[Key in KeysOfType<T, Array<any>>]?:
| Flatten<T[Key]>
| FilterOperators<Flatten<T[Key]>>;
[Key in ArrayKeys<T>]?:
| Flatten<KeyWithSubKeys<T, Key>>
| FilterOperators<Flatten<KeyWithSubKeys<T, Key>>>;
};
$pullAll?: {
// deno-lint-ignore no-explicit-any
[Key in KeysOfType<T, Array<any>>]?: T[Key];
[Key in ArrayKeys<T>]?: KeyWithSubKeys<T, Key>;
};
$push?: {
// deno-lint-ignore no-explicit-any
[Key in KeysOfType<T, Array<any>>]?: {
$each?: T[Key];
$slice?: number;
$position?: number;
$sort?: 1 | -1;
// deno-lint-ignore no-explicit-any
} | (T[Key] extends Array<any> ? T[Key][number] : void);
[Key in ArrayKeys<T>]?:
| {
$each?: KeyWithSubKeys<T, Key>;
$slice?: number;
$position?: number;
$sort?: 1 | -1;
}
| (KeyWithSubKeys<T, Key> extends Array<unknown>
? KeyWithSubKeys<T, Key>[number]
: void);
};
$bit?: DocumentOperator<
T,
Expand Down Expand Up @@ -804,6 +804,22 @@ type KeysOfType<T, Type> = {
[Key in keyof T]: NonNullable<T[Key]> extends Type ? Key : never;
}[keyof T];

type ArrayKeys<T> = {
[K in keyof T]: T[K] extends Array<unknown> ? K
: T[K] extends Record<string, unknown>
? K extends string ? `${K}.${ArrayKeys<T[K]>}`
: never
: 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 KeyWithSubKeys<T, K extends string> = K extends
`${infer Key}.${infer Rest}` ? KeyWithSubKeys<T[Extract<Key, keyof T>], Rest>
: K extends keyof T ? T[K]
: never;

/** The document returned by the buildInfo command. */
export interface BuildInfo {
/**
Expand Down
115 changes: 115 additions & 0 deletions tests/cases/03_curd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ interface User {
date?: Date;
}

interface ComplexUser {
_id: string | ObjectId;
username?: string;
password?: string;
friends: string[];
likes: {
drinks: string[];
food: string[];
hobbies: {
indoor: string[];
outdoor: string[];
};
};
}

const dateNow = Date.now();

testWithClient("testListCollectionNames", async (client) => {
Expand Down Expand Up @@ -204,6 +219,106 @@ testWithTestDBClient("testUpdateOne", async (db) => {
upsertedId: undefined,
});
});
testWithTestDBClient("testUpdateOneWithPush", async (db) => {
const users = db.collection<ComplexUser>("mongo_test_users");
await users.insertOne({
likes: {
food: ["pizza", "pasta"],
drinks: [],
hobbies: { indoor: [], outdoor: [] },
},
friends: ["Alice", "Bob"],
});
const result = await users.updateOne({}, {
$push: { friends: { $each: ["Carol"] } },
});
assertEquals(result, {
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0,
upsertedId: undefined,
});
});
testWithTestDBClient("testUpdateOneWithPull", async (db) => {
const users = db.collection<ComplexUser>("mongo_test_users");
await users.insertOne({
likes: {
food: ["pizza", "pasta"],
drinks: [],
hobbies: { indoor: [], outdoor: [] },
},
friends: ["Alice", "Bob"],
});
const result = await users.updateOne({}, {
$pull: { friends: "Bob" },
});
assertEquals(result, {
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0,
upsertedId: undefined,
});
});
testWithTestDBClient("testUpdateOneWithNestedPush", async (db) => {
const users = db.collection<ComplexUser>("mongo_test_users");
await users.insertOne({
likes: {
food: ["pizza", "pasta"],
drinks: [],
hobbies: { indoor: [], outdoor: [] },
},
friends: ["Alice", "Bob"],
});
const result = await users.updateOne({}, {
$push: { "likes.hobbies.indoor": "board games" },
});
assertEquals(result, {
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0,
upsertedId: undefined,
});
});
testWithTestDBClient("testUpdateOneWithNestedPullAll", async (db) => {
const users = db.collection<ComplexUser>("mongo_test_users");
await users.insertOne({
likes: {
food: ["pizza", "pasta"],
drinks: [],
hobbies: { indoor: ["board games", "cooking"], outdoor: [] },
},
friends: ["Alice", "Bob"],
});
const result = await users.updateOne({}, {
$pullAll: { "likes.hobbies.indoor": ["board games", "cooking"] },
});
assertEquals(result, {
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0,
upsertedId: undefined,
});
});
testWithTestDBClient("testUpdateOneWithNestedPull", async (db) => {
const users = db.collection<ComplexUser>("mongo_test_users");
await users.insertOne({
likes: {
food: ["pizza", "pasta"],
drinks: [],
hobbies: { indoor: ["board games", "cooking"], outdoor: [] },
},
friends: ["Alice", "Bob"],
});
const result = await users.updateOne({}, {
$pull: { "likes.hobbies.indoor": "board games" },
});
assertEquals(result, {
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0,
upsertedId: undefined,
});
});

testWithTestDBClient("testUpdateOne Error", async (db) => { // TODO: move tesr errors to a new file
const users = db.collection<User>("mongo_test_users");
Expand Down

0 comments on commit fbccfa3

Please sign in to comment.