Use generic type as type index

  Kiến thức lập trình

I’m trying to create a type structure which defines a set of projects (e.g. software projects), each with one or more apps (e.g. API and UI), each of which can have some strongly-typed configuration properties (secrets, env vars).

When I try what I have below, I get the errors:

Type ‘TProjectKey’ cannot be used to index type ‘AllProjects’.

Type ‘”apps”‘ cannot be used to index type ‘AllProjects[TProjectKey]’.

Type ‘TAppType’ cannot be used to index type ‘AllProjects[TProjectKey][“apps”]’.

Type ‘”secrets”‘ cannot be used to index type ‘AllProjects[TProjectKey][“apps”][TAppType]’.

My attempt is below — see incline comments.

enum ProjectKey {
    Project1 = "projectOne",
    Project2 = "projectTwo",
}

enum AppType {
  API = "api",
  UI = "ui",
}

type Project<TProjectKey extends ProjectKey> = {
    key: TProjectKey;
    name: string;
    apps: Partial<{ [ TAppType in AppType ]: App<TAppType> }>;
}

type App<TAppKey extends AppType = AppType, TSecretKeys extends string = never> = {
    type: TAppKey;
    name: string;
    secrets: Secrets<TSecretKeys>;
}

type OptionalSecret = {
  defaultValue?: string;
};

type RequiredSecret = {
  required: true;
};

type Secret = OptionalSecret | RequiredSecret;

type Secrets<TSecretKeys extends string> = { [ K in TSecretKeys ]: Secret };

// Define a project.

const Project1: Project<ProjectKey.Project1> = {
    key: ProjectKey.Project1,
    name: "Project One",
    apps: {
        api: {
            type: AppType.API,
            name: "Project One API",
            secrets: {
                "SECRET_1": { required: true },
                "SECRET_2": { defaultValue: "abc" },
            },
        },
    },
};


// Roll up all projects into a mapped type.

type AllProjects = {
    [ ProjectKey.Project1 ]: typeof Project1;
};

// Define a configuration which describes all of the projects.

type Config = {
    [ TProjectKey in ProjectKey ]?: ProjectConfigProps<TProjectKey>;
};

type ProjectConfigProps<TProjectKey extends ProjectKey> = {
  apps: { [ TAppType in AppType ]: AppConfigProps<TProjectKey, TAppType> };
};

// Error occurs below.
type AppConfigProps<TProjectKey extends ProjectKey, TAppType extends AppType> = {
  secrets?: {
    [ TSecretKey in keyof AllProjects[ TProjectKey ][ "apps" ][ TAppType ][ "secrets" ] ]:
        AllProjects[ TProjectKey ][ "apps" ][ TAppType ][ "secrets" ][ TSecretKey ] typeof RequiredSecret
        ? { value: string }
        : { value?: string }
    };
};


// Usage -- the goal is to strongly type the configuration object so that each required secret must be provided, and each optional one may or may not.

const config: Config = {
    "projectOne": {
        apps: {
            api: {
                secrets: {
                    SECRET_1: { value: "some_required_value" },
                    SECRET_2: { value: "some_default_value" },
                    // Or SECRET_2 can just not be supplied.
                }
            },

            // How to make this not required if there are no required secrets?
            ui: {
                secrets: undefined, 
            },
        },
    },
};

Check it out in the playground.

LEAVE A COMMENT