1

Playground link

Assuming the given type:

export declare type MongoManagerFilter<T> = {
  [P in keyof T]?: (P extends keyof T ? T[P] : any);
};

When trying to specify the filter:

update.$setOnInsert.createdAt = new Date();

I get this error:

Type 'Date' is not assignable to type '"createdAt" extends keyof T ? T[keyof T & "createdAt"] : any'.ts(2322)

But if I change the filter to this it works:

export declare type MongoManagerFilter<T> = {
  [P in keyof T]?: any;
};

Why does the conditional statement not actually figure out the type? It's just keeping the code as the type.

I know the filter doesn't make sense how it is, I have another type instead of keyof T to catch dot notation, but I am having the same issue with conditional statements not figuring out the actual type.

Full example

interface CollectionDocument {
  _id?: string;
  createdAt: Date;
}

type PathImpl<T, Key extends keyof T> =
  Key extends string
  ? T[Key] extends Record<string, any>
    ? `${Key}.${PathImpl<T[Key], Exclude<keyof T[Key], keyof Date | keyof Object | keyof string> & string>}`
      | `${Key}.${Exclude<keyof T[Key], keyof Date | keyof Object | keyof string> & string}`
    : never
  : never;

type Path<T> = keyof T | PathImpl<T, keyof T>;

type PathValue<T, P extends Path<T>> =
    P extends `${infer Key}.${infer Rest}`
    ? Key extends keyof T
      ? Rest extends Path<T[Key]>
        ? PathValue<T[Key], Rest>
        : never
      : never
    : P extends keyof T
      ? T[P]
      : any;

export declare type MongoManagerFilter<T> = {
    [P in Path<T>]?: PathValue<T, P>;
};

export class MongoManagerCollection<T extends CollectionDocument> {
  constructor() {}
  
  updateOne(filter: MongoManagerFilter<T>) {
    // THIS DOES NOT WORK
    filter._id = '';
    filter.createdAt = new Date();
  }
}

// THIS WORKS
let testModel = new MongoManagerCollection<CollectionDocument>();
testModel.updateOne({_id: 'test', createdAt: new Date()});

EDIT Adding another layer to this playground for more issues along same lines:

Playground link

Seems I am getting this error as well due to the recursion in the types:

Type instantiation is excessively deep and possibly infinite.ts(2589)
4
  • 2
    Please provide a minimal reproducible example that clearly demonstrates the issue you are facing. Ideally someone could drop the code into a standalone IDE like The TypeScript Playground (link here!) and immediately get to work solving the problem without first needing to re-create it. So there should be no pseudocode, typos, unrelated errors, or undeclared types or values. Commented Dec 15, 2021 at 18:54
  • Working on it now Commented Dec 15, 2021 at 18:55
  • 1
    It feels like a bug, though I don't really understand what you intend to do with this condition. If P is a key of T, then when would P not extend of a key from T? It feels like the condition will always be true and therefor equivalent to [P in keyof T]?: T[P];. Which would work just fine. I know you wrote "I know the filter doesn't make sense how it is", but it makes it hard to help you figure out the problem. Commented Dec 15, 2021 at 23:00
  • I will adjust this to add the dot notation Commented Dec 15, 2021 at 23:15

1 Answer 1

1

Generic type T in:

export class MongoManagerCollection<T extends CollectionDocument> {
  constructor() { }

  updateOne(filter: MongoManagerFilter<T>) {
    // THIS DOES NOT WORK
    filter._id = '';
    filter.createdAt = new Date();
  }
}

is a black box. filter._id is resolved as a non evaluated conditional type. Treat it as a non called function. Function which is has never been called does not return any value. Same is with filter._id. filter._id coresponds to this:

type PathValue<T, P extends Path<T>> =
  P extends `${infer Key}.${infer Rest}`
  ? Key extends keyof T
  ? Rest extends Path<T[Key]>
  ? PathValue<T[Key], Rest>
  : never
  : never
  : P extends keyof T ////////////////////////////////////////////////
  ? T[P]              // Property is resolved as a conditional type //
  : any;              ///////////////////////////////////////////////

As you might have noticed, filter._id is P extends keyof T ? T[P] : any instead of just T[P].

There is a workaround. You can use T[P & keyof T] instead of conditional type. See working example:

interface CollectionDocument {
  _id?: string;
  createdAt: Date;
}

type PathImpl<T, Key extends keyof T> =
  Key extends string
  ? T[Key] extends Record<string, any>
  ? `${Key}.${PathImpl<T[Key], Exclude<keyof T[Key], keyof Date | keyof Object | keyof string> & string>}`
  | `${Key}.${Exclude<keyof T[Key], keyof Date | keyof Object | keyof string> & string}`
  : never
  : never;

type Path<T> = keyof T | PathImpl<T, keyof T>;

type PathValue<T, P extends Path<T>> =
  P extends `${infer Key}.${infer Rest}`
  ? Key extends keyof T
  ? Rest extends Path<T[Key]>
  ? PathValue<T[Key], Rest>
  : never
  : never
  : T[P & keyof T]

export declare type MongoManagerFilter<T> = {
  [P in Path<T>]?: PathValue<T, P>;
};

export class MongoManagerCollection<T extends CollectionDocument> {
  constructor() { }

  updateOne(filter: MongoManagerFilter<T>) {
    filter._id = ''; // ok
    filter.createdAt = new Date(); // ok
    filter.createdAt = 'sdf'; // expected error

  }
}

// THIS WORKS
let testModel = new MongoManagerCollection<CollectionDocument>();
testModel.updateOne({ _id: 'test', createdAt: new Date() });

Playground

If you are interested in path implementation, you can take a look at this answer, this utility and/or my article.

I don't know MongoDB api and the requirements of your types so I can't say whether they need to be modified or not.

Also, you can get rid of generic :

export class MongoManagerCollection {
  constructor() { }

  updateOne(filter: MongoManagerFilter<CollectionDocument>) {
    filter._id = ''; // ok
    filter.createdAt = new Date(); // ok

  }
}

// THIS WORKS
let testModel = new MongoManagerCollection();
testModel.updateOne({ _id: 'test', createdAt: new Date() });

but I don't think this is what you want.

Sign up to request clarification or add additional context in comments.

1 Comment

I am going to add another playground for one step further. My filter isn't working properly for update. Added in all my mongo types.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.