0

I am trying to be able to build a function/method to build an option from an array. For instance, I am using Mikro-ORM, and there is an type call FindOptions<T> which I can populate with my database sort order request.

The FindOptions interface is defined as:

export interface FindOptions<T, P extends string = never> {
    populate?: readonly AutoPath<T, P>[] | boolean;
    orderBy?: (QueryOrderMap<T> & {
        0?: never;
    }) | QueryOrderMap<T>[];
    limit?: number;
    offset?: number;
    ...
}

When working with the FindOptions directly, I can specify my Entity as the generic const MikroORMSoryBy: FindOptions<TimeEntryEntity> which works for code completion and compiler hints, etc.

I can build my sort by doing:

const MikroORMSoryBy: FindOptions<TimeEntryEntity> = {
    orderBy: { EntryDate: QueryOrder.ASC, Id: QueryOrder.ASC, UserId: QueryOrder.ASC }
};

I can clearly list my fields without an issue. I can even list them as strings, and this works.

orderBy: { 'EntryDate': QueryOrder.ASC, 'Id': QueryOrder.ASC, 'UserId': QueryOrder.ASC }

My question is, how can I convert this so that I can build the orderBy using code? For instance, I could pass a string[] of the column names (['EntryDate', 'Id', 'UserId']) and loop through this with a forEach or something.

const Fields: Array<string> = ['EntryDate', 'Id', 'UserId'];
Fields.forEach( (OneField: string) => {
    ...
});

What I do not know how to do though, is build the orderBy, to be built from my string array, instead of having to hard code the names. This would allow me to build a common function for building a sort option.

Ideally, I want something like:

const Fields: Array<string> = ['EntryDate', 'Id', 'UserId'];
Fields.forEach( (OneField: string) => {
    orderBy[OneField] = QueryOption.ASC;  // ???
});
8
  • But how do you know how to sort each field? Your last example doesn't have any way of associating each field to ASC or DESC. Commented Sep 23, 2022 at 20:21
  • I would change the string for that. Assume just to set them all to be QueryOption.ASC as that is not the issue I am having, it is building the structure properly. Commented Sep 23, 2022 at 20:38
  • Does something like this work for you? You may need to change it a bit because I don't have access to the complete FindOptions type. Commented Sep 23, 2022 at 20:47
  • It does not compile: TS2559: Type '{ EntryDate: QueryOrder; Id: QueryOrder; UserId: QueryOrder; }' has no properties in common with type FindOptions<TimeEntryEntity, never>'. Commented Sep 23, 2022 at 21:00
  • Well yeah that's because you need to set the result to orderBy. It doesn't generate all of your find options. Commented Sep 23, 2022 at 21:01

1 Answer 1

1

We'll start by defining a new function that takes two generic parameters:

function buildOrderBy<E, Fields extends (keyof E)[]>(fields: [...Fields]) => {
    return fields.reduce((r, f) => ({ ...r, [f]: QueryOrder.ASC }), {} as { [K in Fields[number]]: QueryOrder });
}

Simply put, we take the keys of an entity and construct an object of the keys that map to QueryOrder.ASC.

But then if we try to call it here:

const orderByBuilder = buildOrderBy<MyEntity>([...]);

We'll get an error because we did not provide the Fields generic parameter. If we do, then we essentially just duplicated our input to this function, which is terrible. This is called partial type inference (or lack thereof), and one way to get around it is to use currying:

function buildOrderBy<E>() {
    return <Fields extends (keyof E)[]>(fields: [...Fields]) => {
        return fields.reduce((r, f) => ({ ...r, [f]: QueryOrder.ASC }), {} as { [K in Fields[number]]: QueryOrder });
    };
}

Now we can call it like this:

const orderBy = buildOrderBy<MyEntity>()([...]);

Which might be more useful since you can store the intermediary function:

const orderBy = buildOrderBy<MyEntity>();

orderBy([...]); // use independently

Playground

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

3 Comments

Is there a way to ignore unknown fields (not in entity) instead of it generating an error? For instance, if the field list had 'ABC' in it, which is not in the entity, skip adding it to the orderBy object, and do not error out?
@StevenScott It'll look something like this. Unfortunately I don't think there is an easy way to filter out these keys.
Thanks. That is sort of what I was playing around with and getting to. Maybe I will have to try to handle the dynamic nature with something similar in a try/catch to at least determine the error, and return accordingly.

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.