GraphQL type sharing in Base
One decorated class that is both your TypeScript type and your GraphQL type, so they cannot drift.
GraphQL type sharing in Base
A GraphQL API has two type systems that have to agree: the schema, which clients see and query against, and the TypeScript types, which your server code is written in. The usual pain of GraphQL is keeping those two in sync. You define a schema, you generate or hand-write matching TypeScript types, and every change risks the two drifting apart. Base removes that whole class of problem by making them the same definition. You write one decorated class, and it is both your TypeScript type and your GraphQL type. There is nothing to sync because there is only one source.
This post assumes you have read the GraphQL worker post. Here we go deeper on how the types flow.
One class, two type systems
In Base you define a GraphQL type by decorating a TypeScript class. You mark the class as a GraphQL object type and you mark each field with its GraphQL type. That single class now plays two roles at once. To your TypeScript code, it is a normal class with normal typed fields, used like any other type. To the GraphQL schema, it is an object type with those fields, built from the same decorations.
Because the class is the single definition, the schema cannot drift from the code. Add a field to the class with its field decorator, and it is in both the TypeScript type and the schema. Change a field's type, and both move together. The agreement between schema and code is not maintained, it is structural, because there is only one thing to maintain.
A foundation to extend
Base ships base entity types for GraphQL so that the common fields every entity shares, an id, a created-at timestamp, are defined once and inherited. You extend the base GraphQL entity and add your own fields, and your type gets the shared fields already decorated and the schema gets them too. This is plain class inheritance doing schema work: the parent contributes its fields to the schema, the child adds its own, and the result is a complete type with no duplication of the common parts across every entity you define.
The same machinery covers the richer parts of a schema. There are builders for the things a plain object type cannot express on its own: input types for arguments, interface types for shared shapes, union types for "one of several," enums, and custom scalars. Each is defined the same code-first way, so the entire schema, not just the simple objects, comes from your TypeScript rather than from a separate schema file you keep in step.
Why code-first wins here
The code-first approach, schema derived from decorated code rather than code derived from a schema file, pays off most exactly where APIs get complicated. When a type is used in three places, defining it once as a class and referencing the class everywhere means a change lands everywhere at once. When you refactor, the type checker follows the type through your resolvers, because to TypeScript it was always just a type. You get the schema as an output of well-typed code, instead of treating the schema as a separate artifact your code has to be reconciled against.
This is the same instinct that runs through all of Base: let the types be the contract, and let one definition serve both sides of a boundary. GraphQL is just the place where that instinct saves the most pain, because GraphQL is where the two-type-systems problem is sharpest.
The mental model to keep
GraphQL type sharing in Base means your TypeScript type and your GraphQL type are one decorated class, so they cannot drift. Mark a class as a GraphQL object type, decorate its fields, and it serves both your code and your schema at once. Extend the shipped base entity types to inherit common fields, and use the input, interface, union, enum, and scalar builders to express the rest of the schema the same code-first way. One definition, two type systems, no sync step.