1

I'm looking for a spec / library or standard that allows me to specify an interface for instance:

interface Customer {
  firstName: string,
  lastName: string,
  createdAt: Date,
  age: number,
}

type CustomerQuery = Query<Customer>

Where this would be valid:

const customerQuery:CustomerQuery = {
  age: {
    greaterThan: '29'
  }
}

This query would be valid because all properties would be optional ? and Query would see the types of the incoming interface and allow number to queryable with greaterThen, greaterThenEqual, lessThen, lessThenEqual, or a value itself, etc.

5
  • 1
    You're looking for some existing thing and not a way to define it? Commented Jan 29, 2019 at 17:00
  • We can create the Query type to transform the interface, as to the library recommendation, not sure if there is one Commented Jan 29, 2019 at 17:01
  • I am looking to see if it is possible with typescript. Commented Jan 29, 2019 at 17:05
  • I may not completely understand a question, are not you looking for type CustomerQuery = Query<Partial<Customer>> Commented Jan 29, 2019 at 17:07
  • No, not that simple. I am looking to dynamically create an interface with logic that decides based on the type what the query allows, for instance only numbers and dates would have >, <, properties, strings could have regexes possibly. Commented Jan 29, 2019 at 17:08

2 Answers 2

2

Replace the number type with an interface

interface NumberField {
    greaterThan?: number
    greaterOrEqual?: number
    lessThan?: number
    lessOrEqual?: number
}

type Query<T> = {
    [P in keyof T]?: T[P] extends number ? NumberField | number : T[P]
}

Or, replace number and Date types with the same interface

interface ComparableField<T> {
    greaterThan?: T
    greaterOrEqual?: T
    lessThan?: T
    lessOrEqual?: T
}

type Query<T> = {
    [P in keyof T]?:
        T[P] extends number | Date ? ComparableField<T[P]> | T[P]: T[P]
}

Or, replace number and Date types with distinct interfaces

interface NumberField {
    greaterThan?: number
    greaterOrEqual?: number
    lessThan?: number
    lessOrEqual?: number
}

interface DateField {
    before?: Date
    after?: Date
}

type Query<T> = {
    [P in keyof T]?:
        T[P] extends number ? NumberField | number :
        T[P] extends Date ? DateField | Date :
        T[P]
}

Read also

A notice: the Date type doesn't exist in JSON.

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

2 Comments

This is amazing, thank you. How would this also conditionally extend Date?
@ThomasReggi I edited to add two solutions with Date.
0

I'd add a few more special types over @Paleo's answer to make mapping between more types and special Fields a breeze.

The idea is to consider a type of the form type ConstraintsDict = ((s: number) => NumberField) | ((d: Date) => DateField); as a dictionary (like you'd write in pure code const dict = { number: 'NumberField', Date: 'DateField'}).

Then, provided you have a complete dictionary mapping between types and field definitions, you'd want to get NumberField when giving a number: type GetFromDict<TDict, Tkey> = TDict extends (arg: Tkey) => infer TValue? TValue: never; does this. Same analogy as before, it can be thought of in pure code as a dict[key] which gives 'NumberField' when key is 'number' and gives undefined - which is never in our case - when the key does not exist in the dict.

The complete example:

interface NumberField {
  greaterThan?: number;
  greaterOrEqual?: number;
  lessThan?: number;
  lessOrEqual?: number;
}

interface DateField {
  before?: Date;
  after?: Date;
}

// Here you can add whatever mapping between a type and its contraints' fields
type ConstraintsDict = ((s: number) => NumberField) | ((d: Date) => DateField);

type GetFromDict<TDict, Tkey> = TDict extends (arg: Tkey) => infer TValue
  ? TValue
  : never;

type Query<T> = { [P in keyof T]?: GetFromDict<ConstraintsDict, T[P]> | T[P] };

/**
 * The following is equivalent to:
  type QueryStuff = {
    a?: number | NumberField;
    b?: Date | DateField;
    c?: string;
  }
 */
type QueryStuff = Query<{
  a: number;
  b: Date;
  c: string;
}>;

Comments

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.