LakeQL
Introduction
  • Overview
  • Why LakeQL
  • Key Concepts
Getting Started
  • Prerequisites
  • Quickstart
  • Environment
  • First Run
Architecture
  • System Overview
  • Data Flow
  • Request Lifecycle
Configuration
  • Config File
  • Runtime Lifecycle
  • Environment Variables
create-app
  • Usage
  • Template Structure
  • Post-Creation
Guides
  • Custom Resolvers
  • Extending the Schema
  • Mutations
  • Set Up a Trino Mutation
  • Deploying
Endpoints
  • Definition Format
  • Field Types
  • Nested Fields
  • Mutations
  • Glossary
Contributing
  • Local Development
  • Contribution Guide
  • Architecture
GitHub
LakeQL
  1. LakeQL
  2. Guides
  3. Extending the Schema

On this page

  1. Reach another source

Extending the Schema

Add a computed or related field to a type you pulled.

Custom resolvers add new root fields. Sometimes you instead want a field on a type you already pulled. You can add a computed displayName to a generated Order, or a related field that reaches another source.

This guide walks through one worked task: add a computed displayName to a generated Order type.

Keep extensions out of the generated directory
Re-running pull recursively deletes and recreates the endpoint's per-table generated directory (schemas/generated/<dataSource>/<catalog>/<schema>/<table>). Anything you put in that directory is lost on the next pull. Keep your extensions in your own modules.

Find the generated reference

Locate the generated object reference for the type you want to extend, such as OrderRef. It lives in the generated files for that table under schemas/generated. You import it to tell the builder which type the new field belongs to.

Create your own module

Put the extension in a file you own, such as src/extensions.ts. Do not edit the generated files, because a later pull deletes that directory.

1
2
3
// src/extensions.ts
import { builder } from "@lakeql/api/builder"

Add the field

Extend the generated type with builder.objectField. The resolver receives the parent object, so you can compute the value from its fields.

1
2
3
4
5
6
7
8
9
// src/extensions.ts
import { builder } from "@lakeql/api/builder"

builder.objectField(OrderRef, "displayName", (t) =>
  t.string({
    resolve: (order) => `#${order.id} — ${order.status}`,
  })
)

The field participates in the same schema as the generated ones. It shares the scope-auth scopes, the scalars, and the validation plugin. See extending core for the full objectField mechanics.

Import the module before you start the server

Add the import to your entry point, before the call to startServer(). The server only registers modules it imports.

1
2
3
4
import "./extensions"

await server.startServer()

See the two prerequisites for the discovery rule.

Run it

Start the dev server and query the new field.

pnpm dev
1
2
3
4
5
6
7
8
query {
  orders {
    id
    status
    displayName
  }
}

Reach another source #

A field on a generated type can also read from a different source. Resolve it through the context accessor, the same one custom resolvers use.

1
2
3
4
5
6
7
8
9
10
11
builder.objectField(OrderRef, "customer", (t) =>
  t.field({
    type: CustomerRef,
    resolve: async (order, _args, context) => {
      const strategy = context.getReadStrategy("crm")
      // read the related record with the strategy
      // ...
    },
  })
)

This keeps the join in your own module, so a later pull leaves it untouched.

Previous page

Custom Resolvers

Next page

Mutations