mozilla / node-convict

Featureful configuration management library for Node.js
Other
2.35k stars 146 forks source link

Infer typescript types of getProperties #415

Open cjsewell opened 1 year ago

cjsewell commented 1 year ago

Is there a method of inferring typescript types of getProperties?

Similar to @sinclair/typebox's Static and zod's infer

I have created my own limited and simple version, it can infer the concrete typescript types from a schema, and it seems to work okay but before continuing further, I thought I would share it to see if a solution already exists, or if anyone can offer feedback.

type NumberTypes = NumberConstructor | number | 'port' | 'int' | 'nat' | 'duration' | 'timestamp';
type BooleanTypes = BooleanConstructor | boolean;
type UnknownTypes = '*';
type StringTypes = StringConstructor | string | 'url' | 'email' | 'ipaddress' | 'nat';

type InferredProps<T> = {
    [k in keyof T]-?: T[k] extends SchemaObj
        ? T[k]['format'] extends NumberTypes
            ? number
            : T[k]['format'] extends BooleanTypes
            ? boolean
            : T[k]['format'] extends UnknownTypes
            ? unknown
            : T[k]['format'] extends StringTypes
            ? string
            : // eslint-disable-next-line @typescript-eslint/ban-types
            T[k]['format'] extends Function
            ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
              // @ts-ignore
              ReturnType<T[k]['format']>
            : T[k]
        : InferredProps<T[k]>;
    // eslint-disable-next-line @typescript-eslint/ban-types
} & {};

Given a schema like:

const testSchema = {
    number: {
        format: Number,
        default: null,
        env: 'SOME_VAR',
    },
    port: {
        format: 'port' as const,
        default: null,
        env: 'SOME_VAR',
    },
    duration: {
        format: 'duration' as const,
        default: null,
        env: 'SOME_VAR',
    },
    url: {
        format: 'url' as const,
        default: null,
        env: 'SOME_VAR',
    },
    string: {
        format: String,
        default: null,
        env: 'SOME_VAR',
    },
    bool: {
        format: Boolean,
        default: null,
        env: 'SOME_VAR',
    },
    nested: {
        string: {
            format: String,
            default: null,
            env: 'SOME_VAR',
        },
        bool: {
            format: Boolean,
            default: null,
            env: 'SOME_VAR',
        },
    },
};

It can be used like

type ConfigProps = InferredProps<typeof testSchema>;
const config = convict(testSchema);

const configPropsInferred = config.getProperties() as ConfigProps;
//    ^? const config: {number: number, port: number, duration: number, url: string, string: string, bool: boolean, nested: {string: string, bool: boolean}}

While using getConfig returns the correct keys, it fails to infer the type and just returns null

const configProps = config.getProperties();
//    ^? const configProps: {number: null, port: null, duration: null, url: null, string: null, bool: null, nested: {string: null, bool: null}}

Any feedback welcome!