Type Inference
Zeus provides automatic TypeScript type inference for all GraphQL operations, ensuring complete type safety without manual type definitions.
How It Works
Zeus generates TypeScript types directly from your GraphQL schema, then infers the exact return type based on your field selections.
import { Chain } from './zeus';
const chain = Chain('https://api.com/graphql');
// Zeus automatically infers the return type
const result = await chain('query')({
user: [
{ id: '123' },
{
id: true,
name: true,
email: true,
},
],
});
// Type: { user: { id: string; name: string; email: string } }
// TypeScript knows exactly what fields are available
console.log(result.user.name); // ✅ Type-safe
console.log(result.user.age); // ❌ TypeScript error - field not selectedSelection-Based Inference
Zeus infers types based on what you select, not what’s available in the schema:
// Only selecting 'name'
const minimal = await chain('query')({
user: [{ id: '123' }, { name: true }],
});
// Type: { user: { name: string } }
// Selecting multiple fields
const detailed = await chain('query')({
user: [
{ id: '123' },
{
name: true,
email: true,
profile: {
avatar: true,
bio: true,
},
},
],
});
// Type: { user: { name: string; email: string; profile: { avatar: string; bio: string } } }Nested Object Inference
Zeus handles complex nested selections:
const result = await chain('query')({
user: [
{ id: '123' },
{
name: true,
posts: [
{ first: 10 },
{
edges: {
node: {
title: true,
content: true,
author: {
name: true,
},
},
},
},
],
},
],
});
// Fully typed nested structure
result.user.posts.edges.forEach((edge) => {
console.log(edge.node.title); // ✅ Type-safe
console.log(edge.node.author.name); // ✅ Type-safe
});Array and Connection Types
Zeus correctly infers array types and GraphQL connections:
const result = await chain('query')({
users: {
id: true,
name: true,
},
});
// Type: { users: Array<{ id: string; name: string }> }
result.users.forEach((user) => {
console.log(user.name); // ✅ Type-safe array iteration
});
// GraphQL Relay connections
const posts = await chain('query')({
posts: [
{ first: 20 },
{
edges: {
cursor: true,
node: {
title: true,
},
},
pageInfo: {
hasNextPage: true,
endCursor: true,
},
},
],
});
// Fully typed connection structure
if (posts.posts.pageInfo.hasNextPage) {
console.log('More posts available');
}Union Type Inference
Zeus handles GraphQL unions with discriminated union types:
const result = await chain('query')({
search: [
{ query: 'Zeus' },
{
__typename: true,
'...on User': {
name: true,
email: true,
},
'...on Post': {
title: true,
content: true,
},
},
],
});
// Type narrowing with __typename
result.search.forEach((item) => {
if (item.__typename === 'User') {
console.log(item.name); // ✅ TypeScript knows this is a User
console.log(item.email); // ✅ Available on User
} else if (item.__typename === 'Post') {
console.log(item.title); // ✅ TypeScript knows this is a Post
console.log(item.content); // ✅ Available on Post
}
});Interface Type Inference
Similar handling for GraphQL interfaces:
const result = await chain('query')({
nodes: [
{ ids: ['1', '2', '3'] },
{
__typename: true,
id: true,
'...on User': {
name: true,
},
'...on Post': {
title: true,
},
},
],
});
// Type-safe interface fragments
result.nodes.forEach((node) => {
console.log(node.id); // ✅ Available on all nodes
if (node.__typename === 'User') {
console.log(node.name); // ✅ User-specific field
}
});Nullable Type Handling
Zeus respects GraphQL’s nullable type system:
const result = await chain('query')({
user: [
{ id: '123' },
{
name: true, // Non-nullable in schema
nickname: true, // Nullable in schema
profile: {
// Nullable object
bio: true,
},
},
],
});
// Type: {
// user: {
// name: string;
// nickname: string | null;
// profile: { bio: string } | null;
// }
// }
console.log(result.user.name.toUpperCase()); // ✅ Safe - never null
console.log(result.user.nickname?.toUpperCase()); // ✅ Optional chaining neededEnum Type Inference
Enums are typed as string literal unions:
const result = await chain('query')({
users: [
{ role: 'ADMIN' }, // Type-safe enum value
{
name: true,
role: true, // Type: 'ADMIN' | 'USER' | 'MODERATOR'
},
],
});
// TypeScript enforces valid enum values
result.users.forEach((user) => {
if (user.role === 'ADMIN') {
// ✅ Type-safe comparison
console.log('Administrator');
}
});Scalar Type Inference
Custom scalars are properly typed:
// In your schema: scalar DateTime, scalar JSON
const result = await chain('query')({
post: [
{ id: '123' },
{
createdAt: true, // Type: DateTime (string)
metadata: true, // Type: JSON (any or custom type)
},
],
});
// DateTime scalars typed as strings by default
const date = new Date(result.post.createdAt);
// JSON scalars can be typed via schema configuration
const metadata: PostMetadata = result.post.metadata;Variable Type Inference
Zeus infers variable types from your input:
// Variables are fully typed
const result = await chain('query')({
user: [
{
// Zeus infers the exact input type needed
id: $('id', 'ID!'), // Required ID
},
{
name: true,
posts: [
{
first: $('first', 'Int'), // Optional Int
},
{
title: true,
},
],
},
],
})({
id: '123', // ✅ Must be string (ID!)
first: 10, // ✅ Must be number (Int)
});Selector Type Inference
When using selectors, types are preserved:
import { Selector } from './zeus';
// Define typed selector
const userFields = Selector('User')({
id: true,
name: true,
email: true,
});
// Use in query - type is inferred
const result = await chain('query')({
user: [{ id: '123' }, userFields],
});
// Type: { user: { id: string; name: string; email: string } }Generic Type Utilities
Zeus provides utility types for advanced scenarios:
import { InputType, GraphQLTypes } from './zeus';
// Extract input types
type CreateUserInput = InputType<GraphQLTypes['CreateUserInput']>;
// Extract return types
type User = GraphQLTypes['User'];
// Use in functions
function processUser(user: User) {
// Fully typed user object
console.log(user.name);
}
// Type-safe input creation
const input: CreateUserInput = {
name: 'Zeus',
email: 'zeus@olympus.com',
};Type Assertion Utilities
Zeus exports types for runtime validation:
import { ModelTypes } from './zeus';
// Use generated model types
type User = ModelTypes['User'];
type Post = ModelTypes['Post'];
// Type guards
function isUser(obj: any): obj is User {
return obj && typeof obj.name === 'string';
}
// Runtime validation with type safety
const data = await chain('query')({
node: [
{ id: '123' },
{
__typename: true,
'...on User': {
name: true,
},
},
],
});
if (isUser(data.node)) {
console.log(data.node.name);
}Benefits
1. Zero Manual Types
No need to write or maintain TypeScript interfaces:
// ❌ Traditional approach
interface User {
id: string;
name: string;
email: string;
}
// ✅ Zeus approach - types inferred automatically
const result = await chain('query')({
user: [{ id: '123' }, { id: true, name: true, email: true }],
});
// Type automatically matches your selection2. Refactoring Safety
Schema changes are immediately reflected:
// If 'email' field is removed from schema
const result = await chain('query')({
user: [
{ id: '123' },
{
name: true,
email: true, // ❌ TypeScript error - field doesn't exist
},
],
});3. IntelliSense Support
Full autocomplete in your IDE:
await chain('query')({
user: [
{ id: '123' },
{
// IDE shows all available fields
name: true,
// Autocomplete suggests: email, profile, posts, etc.
},
],
});Next Steps
- Thunder - Custom fetch with type inference
- Generated Types - Understanding the generated code
- Selectors - Reusable typed selections