LakeQL
Overview
  • Introduction
Server Setup
  • createApiServer
  • defineConfig
  • Yoga Configuration
Authentication
  • JWT Authentication
  • Permissions
  • Scope Authorization
Schema Builder
  • Builder Configuration
  • Scalar Types
  • Comparison Types
  • Pagination
  • Input Validation
Customization
  • Custom Queries & Mutations
  • Extending Core
  • CORS Configuration
GitHub
LakeQL
  1. API
  2. Customization
  3. Extending Core

On this page

  1. Custom GetUserResolver
  2. Custom ReadPermissionResolver
  3. Custom WritePermissionResolver
  4. Registering custom resolvers
  5. Adding custom scalars
  6. Overriding built-in scalar serialization
    1. Setup

Extending Core

Provide custom resolvers for authentication, permissions, and add additional scalars.

LakeQL's extension points let you replace built-in behavior with custom implementations. The primary customization targets are the authentication resolver, permission resolvers, and scalar types.

Custom GetUserResolver #

Replace the built-in mock auth with your own JWT verification:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import type { GetUserResolver } from "@lakeql/api/types"
import { jwtVerify } from "jose"

export const customGetUser: GetUserResolver = async (req) => {
  const token = req.headers.get("authorization")?.replace("Bearer ", "")
  if (!token) return null

  try {
    const { payload } = await jwtVerify(token, secretKey)
    return { ...payload, userName: payload.sub ?? "unknown" }
  } catch {
    return null
  }
}

Custom ReadPermissionResolver #

Override how read permissions are evaluated:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import type { ReadPermissionResolver } from "@lakeql/api/types"

export const customReadPermission: ReadPermissionResolver = ({
  context,
  catalog,
  schema,
  tableName,
}) => {
  // Allow all reads for admin users
  if (context.currentUser?.role === "admin") {
    return true
  }

  // Check against external policy engine
  return checkPolicy(context.currentUser, "read", {
    catalog,
    schema,
    tableName,
  })
}

Custom WritePermissionResolver #

Override how write permissions are evaluated:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import type { WritePermissionResolver } from "@lakeql/api/types"

export const customWritePermission: WritePermissionResolver = ({
  context,
  catalog,
  schema,
  tableName,
}) => {
  // Only service accounts can write
  if (!context.currentUser?.isServiceAccount) {
    return false
  }

  return checkPolicy(context.currentUser, "write", {
    catalog,
    schema,
    tableName,
  })
}

Registering custom resolvers #

Pass all custom resolvers via defineConfig:

1
2
3
4
5
6
7
8
9
10
11
12
import { defineConfig } from "@lakeql/api/config"
import { allConfigs } from "./generated/configs"
import { customGetUser } from "./auth"
import { customReadPermission, customWritePermission } from "./permissions"

export default defineConfig({
  allConfigs,
  getUser: customGetUser,
  hasReadPermission: customReadPermission,
  hasWritePermission: customWritePermission,
})

Adding custom scalars #

Register additional scalars on the shared builder in a query-schema.ts file. Place it in a directory that sorts alphabetically before your other schemas (e.g., 00-scalars/) to ensure the scalars are available when other schema files are loaded.

1
2
3
4
5
6
import { builder } from "@lakeql/api/builder"
import { JSONResolver, BigIntResolver } from "graphql-scalars"

builder.addScalarType("JSON", JSONResolver, {})
builder.addScalarType("BigInt", BigIntResolver, {})

graphql and graphql-scalars are transitive dependencies of @lakeql/api. To avoid issues if those internals ever change, install them explicitly in your project.

npm install graphql graphql-scalars

These scalars become available in all other schema files loaded after this one. Place scalar definitions in a directory that sorts alphabetically before other schemas (e.g., 00-scalars/) to ensure they're loaded first.

Overriding built-in scalar serialization #

GraphQL's built-in Int scalar only accepts 32-bit signed integers (max ~2.1 billion). Both Hive/Trino INT and BIGINT columns are mapped to the Integer field type by the pull command, so values beyond the 32-bit range can reach GraphQL at runtime. When that happens, the default Int.serialize throws a range error.

You can monkey-patch the serializer at startup to handle these edge cases.

Setup #

Create a query-schema.ts file in a directory that sorts alphabetically before your other schemas (e.g., 00-scalars/). LakeQL loads all query-schema.{ts,js,mjs} and mutation-schema.{ts,js,mjs} files in alphabetical directory order, so the override will be active before any resolvers execute.

1
2
3
4
5
6
7
8
src/
└── schemas/
    ├── 00-scalars/
    │   └── query-schema.ts   ← Int override goes here
    ├── my-catalog/
    │   └── ...
    └── ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import { GraphQLInt } from "graphql"

// Preserve the original serializer as a fallback
const originalIntSerialize = GraphQLInt.serialize.bind(GraphQLInt)

GraphQLInt.serialize = (value: unknown) => {
  // Pass through any safe integer value directly, even beyond 32-bit range
  if (
    typeof value === "number" &&
    Number.isInteger(value) &&
    Number.isFinite(value)
  ) {
    return value
  }

  // Handle numeric strings (e.g., from database drivers returning string IDs)
  if (typeof value === "string" && value !== "") {
    const num = Number(value)
    if (Number.isInteger(num) && Number.isFinite(num)) return num
  }

  // Fall back to default behavior for anything else
  return originalIntSerialize(value)
}

Since this file is named query-schema.ts and lives in 00-scalars/, LakeQL will import it automatically — no additional configuration or explicit import is needed.

Previous page

Custom Queries & Mutations

Next page

CORS Configuration

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
src/schemas/00-scalars/query-schema.ts
src/schemas/00-scalars/query-schema.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25