While it's true that TypeScript type literals are almost identical to interfaces (as mentioned in #1810), one of the key exceptions is that interfaces are open to declaration merging, where types are not.
This is very important for generated code, where declaration merging is the only avenue to apply manual tweaks to type information.
Please add an option to generate interfaces rather than types.
For posterity, here is a fabricated example of declaration merging a generated interface:
import "./graphql";
declare module "./graphql" {
export interface SomeType {
extraProperty: boolean;
}
}
Hi @benquarmby !
You are right about interfaces and the differences. interfaces can be merged within a scope only, which means that you need to have it either in the same file of the original declaration, or have it in the same namespace.
Since 1.0, we no longer generate TypeScript namespaces (to make it easier and compatible with most environments).
I'm not sure that your example will work in a real environment, because I'm not sure you can re-declare a file module. But let me know if I'm wrong.
Regarding the interfaces extension, you can always declare you custom types and use intersection (&) to add fields.
If you wish to have it system-wide you can wrap the generated file, and re-export anything you want to extend.
Other than that, I'm not really against interfaces, it was just easier to deal with when generating types, because you can't redeclare the same type twice, and it's easier for us to generate robust code this way (because we can easily detect mismatches and duplicate identifiers).
We are always open for PRs and we encourage customizability (and I think it shouldn't be too complicated, because we just need to modify DeclarationBlock class).
I can assure you that the example I posted would definitely work to expand an existing interface from another module (in this case the generated graphql.ts). It would even work across packages - this is the primary way to fix incorrect @types interface declarations.
The rational to use type literals by default makes perfect sense. My need is possibly an edge case, so it should definitely be opt-in.
In either case, opening a PR sounds fair. I'll take a look.
Going to leave this here before I move onto the next thing. Just scratching out some potential configuration modelling:
enum DeclarationKind {
// Let gqlcg decide based on the input
// Always implied if not otherwise specified
default = "default",
// Explicit type
type = "type",
// Explicit interface
interface = "interface"
// Perhaps also "class" in the future?
}
interface DeclarationKindConfig {
scalar?: DeclarationKind;
input?: DeclarationKind;
union?: DeclarationKind;
type?: DeclarationKind;
interface?: DeclarationKind;
arguments?: DeclarationKind;
}
interface TypeScriptPluginConfig {
// Implied `DeclarationKind.default` if not specified
declarationKind?: DeclarationKind | DeclarationKindConfig;
}
Edit: one more revision:
export type ObjectDeclarationKind = 'type' | 'interface';
export type EnumDeclarationKind = 'enum' | 'const enum' | ObjectDeclarationKind;
export interface DeclarationKindConfig {
scalar?: ObjectDeclarationKind;
input?: ObjectDeclarationKind;
union?: ObjectDeclarationKind;
type?: ObjectDeclarationKind;
interface?: ObjectDeclarationKind;
arguments?: ObjectDeclarationKind;
enum?: EnumDeclarationKind;
}
Available in 1.3.0 馃帀
@dotansimha it looks like this supports interfaces for the base types of a schema - is there any support for generating them in fragment types? I have a similar desire to have interface merging for fragments in order to support consumers who may want to add Apollo client resolvers on top of types generated for the base schema.
I realize that this is possible as input to codegeneration - but in this case, the types are being redistributed into a package in the form of an SDK.
@tbrannam I see, It's not supported at the moment, I think due to the amount of features that we are using that are specific type TS type, it might be a bit complex, and I'm not sure if all are supported in interfaces. PRs are always welcome ;)
Most helpful comment
I can assure you that the example I posted would definitely work to expand an existing interface from another module (in this case the generated
graphql.ts). It would even work across packages - this is the primary way to fix incorrect@typesinterface declarations.The rational to use type literals by default makes perfect sense. My need is possibly an edge case, so it should definitely be opt-in.
In either case, opening a PR sounds fair. I'll take a look.