Reputation: 4222
I'm using TypeScript for my React + Apollo project and I'm using the graphql-code-generator which generates the following types:
type Maybe<T> = T | null;
type Scalars = {
ID: string,
String: string,
Boolean: boolean,
Int: number,
Float: number,
DateTime: any,
ISO8601DateTime: any,
Date: any,
Json: any,
};
type RelayNode = {
id: Scalars['ID'],
};
type Project = RelayNode & {
__typename?: 'Project',
number: Scalars['Int'],
title: Scalars['String']
projectManagers?: Maybe<Array<{ id: Scalars['String'], name: Scalars['String'] }>>,
};
type GetProjectQuery = (
{ __typename?: 'Query' }
& { project: Maybe<(
{ __typename?: 'Project' }
& Pick<Project, 'number' | 'title' | 'projectManagers'>
)> }
);
type ProjectInput = {
title?: Maybe<Scalars['String']>,
number?: Maybe<Scalars['Int']>,
projectManagerIds?: Maybe<Array<Scalars['String']>>,
};
Now I want to build a form that takes a project
for my initial values. An example project may looks like ...
const project: GetProjectQuery['project'] = {
__typename: 'Project',
number: 123,
title: 'My awesome project',
projectManagers: [{ id: '123', name: 'Me & myself'}]
}
... but my form only allows a ProjectInput
and not a Project
(since the input variables may differ from the output), so I'm doing this ...
const input: ProjectInput = project
... but this seems to be valid – Typescript doesn't throw any errors. But I want to enforce a warning that the object should not have projectManagers
defined. The goal is to enforce an input object hat doesn't have projectManagers
but projectManagerIds
defined.
I created a minimal test case here, where the "rejection" works:
type Project = {
title?: string
number?: number
};
const project: Project = {
title: 'foo',
number: 123,
foo: 'bar' // this key isn't allowed
}
But I don't get why this doesn't work with my generated typings above.
Upvotes: 0
Views: 508
Reputation: 249536
You can't enforce this on a variable. A basic principle of OOP is that a a sub-type is assignable to a base type reference. Since typescript uses structural typing, the base type/sub type relationship is not explicit, but given the structure of GetProjectQuery['project']
and ProjectInput
, GetProjectQuery['project']
is a sub-type of ProjectInput
Now typescript sometimes intentionally violates the sub-type is assignable to base-type rule for very specific scenarios:
Your scenario is neither of these so you will not get an error.
If you want to perform this validation when you calling a function, then we can use a bit of generic type parameter magic to capture the actual type of the parameter and force an error on any extra properties:
function withProjectInput<T extends ProjectInput>(p: T & Record<Exclude<keyof T, keyof ProjectInput>, ["No excess properties allowed here"]>) {
}
withProjectInput(project); // error here
Upvotes: 2