Is there a way to create a type interface that points to the structure of another type?

I am focused on developing a type interface that includes properties defined in another interface.

Below is the schema definition for a model object in TypeScript:

export interface ModelSchema {
  idAttribute: string;
  name: string;
  attributes: {
    [attrName: string]:
      { type: 'number', default?: number, readOnly?: boolean } |
      { type: 'string', default?: string, readOnly?: boolean } |
      { type: 'boolean', default?: boolean, readOnly?: boolean } |
      { type: 'date', default?: Date, readOnly?: boolean } |
      { type: 'array', default?: string[] | number[], readOnly?: boolean } |
      { type: 'object', default?: object, readOnly?: boolean }
  };
  relationships: {
    [relName: string]: {
      type: RelationshipSchema,
      readOnly?: boolean,
    },
  };
};

The key component we are discussing here is the relationships property. Each property within it represents a has-many relationship in the data model.

I aim to create a separate container for approvers - entities that determine permissions on CRUD operations based on specific relationships in the data model.

An ideal approach would be something like this:

export interface ApproverDefinition<S extends ModelSchema> {
  typeName: string,
  attributes: AttributesAuthorize,
  relationships: {
    [name: string]: RelationshipAuthorize,
  }
}

To ensure consistency, I want to enforce a rule where the names of properties in ApproverDefinition.relationships must match those in the relationships property of the related ModelSchema S.

Although the above almost works conceptually, there are two TypeScript errors regarding exporting private names and treating S as a namespace.

Despite the challenges, I prefer to keep the approver logic separate from the schema for various reasons. Extending ModelSchema directly may not be feasible in this scenario.

I can resort to runtime checks by comparing keys at runtime, but I strive to achieve this validation through TypeScript itself.

Is there a way to accomplish this task using TypeScript's type system?

EDIT: Here's an example of a model schema:

const ProfileSchema = {
  idAttribute: 'id',
  name: 'profiles',
  attributes: {
    id: { type: 'number', readOnly: true },
    short_text: { type: 'string', readOnly: false },
    long_text: { type: 'string', readOnly: true },
    // SNIP ...
  },
  relationships: {
    memberships: { type: Memberships, readOnly: false },
    conversations: { type: ProfilePermissions, readOnly: false },
    followingProfiles: { type: FollowingProfiles, readOnly: false},
    followingDocuments: { type: FollowingDocuments, readOnly: false},
    followingCommunities: { type: FollowingCommunities, readOnly: false},
    followers: { type: FollowingProfiles, readOnly: false},
  },
};

Now, I aim to define:

const ProfileApprover: ApproverDefinition<ProfileSchema> = {
  typeName: 'profiles'
  attributes: /* attribute approver */
  relationships: {
    memberships: /* approver function */,
    conversations: /* approver function */,
    followingProfiles: /* approver function */,
    followingDocuments: /* approver function */,
    followingCommunities: /* approver function */,
    followers: /* approver function */,
  }
}

Notice that ProfileApprover.relationships shares properties with ProfileSchema.relationships but with different values. The goal is to define rules in TypeScript ensuring all Approver instances align with their corresponding schemas.

If necessary, I could implement runtime checks for discrepancies, but I believe TypeScript should offer a way to statically enforce these rules.

Answer №1

It seems like you're looking for something along these lines

To start, we create an interface extracting the type of the relationships property from ModelSchema, making it independently accessible

export interface Relationships {
  [relName: string]: {
    type: RelationshipSchema,
    readOnly?: boolean,
  },
}

This interface is then utilized in ModelSchema instead of the previous object literal type

export interface ModelSchema {
  idAttribute: string;
  name: string;
  attributes: {
    [attrName: string]:
    {type: 'number', default?: number, readOnly?: boolean} |
    {type: 'string', default?: string, readOnly?: boolean} |
    {type: 'boolean', default?: boolean, readOnly?: boolean} |
    {type: 'date', default?: Date, readOnly?: boolean} |
    {type: 'array', default?: string[] | number[], readOnly?: boolean} |
    {type: 'object', default?: object, readOnly?: boolean}
  };
  retationships: Relationships;
}

As ApproverDefinition relies solely on the relationship property of ModelSchema, we can now use its type as our constraint, granting us access to its keys through keyof

export interface ApproverDefinition<R extends Relationships> {
  typeName: string;
  attributes: AttributesAuthorize;
  relationships: {
    [name in keyof R]: RelationshipAuthorize
  }
}

const ProfileSchema = {
  idAttribute: 'id',
  name: 'profiles',
  attributes: {
    id: {type: 'number', readOnly: true},
    short_text: {type: 'string', readOnly: false},
    long_text: {type: 'string', readOnly: true},
    // SNIP ...
  },
  relationships: {
    memberships: {type: Memberships, readOnly: false},
    conversations: {type: ProfilePermissions, readOnly: false},
    followingProfiles: {type: FollowingProfiles, readOnly: false},
    followingDocuments: {type: FollowingDocuments, readOnly: false},
    followingCommunities: {type: FollowingCommunities, readOnly: false},
    followers: {type: FollowingProfiles, readOnly: false},
  },
};

Finally, given that ProfileSchema is a value and not a type, we must apply typeof to its relationships property and utilize the outcome as the type argument for ApproverDefinition

const ProfileApprover: ApproverDefinition<typeof ProfileSchema.relationships> = {
  typeName: 'profiles',
  attributes: {}, /* attribute approver */
  relationships: {
    // all of these are now required by the type
    memberships: /* approver function */,
    conversations: /* approver function */,
    followingProfiles: /* approver function */,
    followingDocuments: /* approver function */,
    followingCommunities: /* approver function */,
    followers: /* approver function */,
  }
};

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Designing a platform for dynamic components in react-native - the ultimate wrapper for all elements

export interface IWEProps { accessibilityLabel: string; onPress?: ((status: string | undefined) => void) | undefined; localePrefix: string; children: JSX.Element[]; style: IWEStyle; type?: string; } class WrappingElement extends React.Pure ...

Attempting to combine numerous observables into a single entity within an Angular 2 project

I am grappling with the concept of Observables in RxJs. My task involves displaying all users for a specific site on a page. The User and SiteUser entities are located in separate API endpoints. Here are the relevant endpoints: userService.getSiteUsers(si ...

Guide on automatically inserting a colon (:) after every pair of characters in Angular

I am looking to automatically insert a colon (:) after every 2 characters in my input field: HTML <input nz-input type="text" appLimitInput="textAndNumbers" name="mac" formControlName="mac" (keydown.space)=&qu ...

Upon initializing an Angular project from the ground up, I encountered an issue where a custom pipe I had created was not

After creating an Angular 16.1.0 application and a custom pipe, I encountered error messages during compilation. Here are the steps I took: Ran ng new exampleProject Generated a pipe using ng generate pipe power Modified the content of app.compone ...

What is the best way to ensure the secure signing of a transaction in my Solana decentralized application (

I am currently involved in an NFT project that recently experienced a security breach, and I am developing a dapp to rectify the situation. Our plan is to eliminate all NFTs from the compromised collection and issue a new set of NFTs using our updated auth ...

Determine whether a many-to-many relationship involves a specific entity

I am currently working on developing an API for managing surveys. One challenge I'm facing is determining whether a specific user has moderation privileges for a particular survey. A many-to-many relationship has been set up between the two entities. ...

Tips for converting necessary constructor choices into discretionary ones after they have been designated by the MyClass.defaults(options) method

If I create a class called Base with a constructor that needs one object argument containing at least a version key, the Base class should also include a static method called .defaults() which can set defaults for any options on the new constructor it retu ...

Generating swagger documentation for TypeScript-based Express applications

I have successfully set up the swagger URL following a helpful guide on configuring Swagger using Express API with autogenerated OpenAPI documentation through Swagger. Currently, I am utilizing TypeScript which outputs .js files in the dist folder without ...

What is the reason behind the lack of exported interfaces in the redux-form typings?

I've been exploring how to create custom input fields for redux-form. My journey began by examining the Material UI example found in the documentation here. const renderTextField = ({input, label, meta: { touched, error }, ...custom }) => < ...

We've encountered an issue with Redux and Typescript: 'Unable to find property 'prop' in type 'string[]'

When attempting to fetch data in redux and return only a portion of it, I encountered an issue with Typescript stating that "Property 'xxx' does not exist on type 'string[]'". I have reviewed the interface and initialState, but I am una ...

Restricting types through property union types

I'm currently in the process of refining a type to a specific variant within a type, but I am encountering difficulties in accurately defining the correct type. At this moment, my type Item has the potential for having various types for its details. t ...

Tips for defining a distinct series of key-value pairs in typescript

Having experience with a different language where this was simple, I am finding it challenging to articulate a sequence of pairs: Each pair is made up of two basic elements (such as strings or numbers) Each element may appear multiple times within the lis ...

Intellisense in VS Code is failing to provide assistance for data within Vue single file components

I am working with a simple code snippet like this https://i.sstatic.net/JSEWJ.png However, within the method, the variable 'name' is being recognized as type any. Interestingly, when I hover over 'name' in the data, it shows up as a s ...

Using Pydantic to define models with both fixed and additional fields based on a Dict[str, OtherModel], mirroring the TypeScript [key: string] approach

Referencing a similar question, the objective is to construct a TypeScript interface that resembles the following: interface ExpandedModel { fixed: number; [key: string]: OtherModel; } However, it is necessary to validate the OtherModel, so using the ...

The resource in CosmosDB cannot be found

I have successfully stored documents on Cosmos, but I am encountering an issue when trying to retrieve them using the "read" method. this.cosmos = new CosmosClient({ endpoint: '' key: '' }); this.partitionKey = '/id' thi ...

Tips for eliminating contenthash (hash) from the names of JavaScript and CSS files

Google's cached pages are only updated once or twice a day, which can result in broken sites on these cached versions. To prevent this issue, it is recommended to remove the contenthash from the middle of the filename for JavaScript files and eliminat ...

When comparing the results of running the NextJS build file and VS Code locally, there are noticeable discrepancies in

I am encountering an issue with the .next/ build directory. After running npm run dev, I have a working version locally, but I didn't realize to test the build file before deployment. The problem came to my attention when trying to deploy the code o ...

Exploring the Power of Modules in NestJS

Having trouble with this error - anyone know why? [Nest] 556 - 2020-06-10 18:52:55 [ExceptionHandler] Nest can't resolve dependencies of the JwtService (?). Check that JWT_MODULE_OPTIONS at index [0] is available in the JwtModule context. Possib ...

"Loop through an array using forEach leads to a subscription that

I am a beginner in Angular and struggling to understand how async functions work. I have written the following code, but I am encountering an error: GET https://localhost:44353/api/ecams/id/undefined 400 and ["The value 'undefined' is not va ...

Using async-await in canActivate() within Angular 7

I am currently working on a code that verifies whether the browser contains user information. If not, the browser will make an API call to retrieve the user information from the server. Only users with valid information are granted access to navigate to di ...