{"schemaVersion":"1.0.0","docId":"api","source":"api","slug":"api","path":"/docs/api","raw_path":"/raw/api.md","title":"API","headings":[],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: API\nnavTitle: API\ndescription: GraphQL API server with Hono, Yoga, and Pothos.\nentrypoint: /docs/api/overview/introduction\n---\n","description":"GraphQL API server with Hono, Yoga, and Pothos.","navTitle":"API","keywords":["graphql","server","pothos"]}
{"schemaVersion":"1.0.0","docId":"api/authentication/jwt-authentication","source":"api","slug":"authentication/jwt-authentication","path":"/docs/api/authentication/jwt-authentication","raw_path":"/raw/api/authentication/jwt-authentication.md","title":"JWT Authentication","headings":[{"level":2,"text":"GetUserResolver type","id":"get-user-resolver-type"},{"level":2,"text":"Default behavior","id":"default-behavior"},{"level":2,"text":"Mock authentication","id":"mock-authentication"},{"level":2,"text":"Custom JWT verification","id":"custom-jwt-verification"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/authentication/jwt-authentication/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: JWT Authentication\nnavTitle: JWT Authentication\ndescription: Configure user authentication via JWT tokens or mock authentication for development.\n---\n\nLakeQL uses JWT-based authentication to identify the current user on each request. The `getUser` resolver extracts user information from the `Authorization` header and makes it available in the GraphQL context.\n\n## GetUserResolver type\n\n```ts\ntype GetUserResolver = (\n  req: Request\n) => Promise<JWTPayload | null | undefined> | JWTPayload | null | undefined\n```\n\nReturning `null` or `undefined` means the request is unauthenticated. The `JWTPayload` type extends jose's `JWTPayload` with an additional `userName` field:\n\n```ts\n// Extends jose JWTPayload\ninterface JWTPayload {\n  userName: string\n  // ...standard JWT claims (iss, sub, aud, exp, etc.)\n}\n```\n\n## Default behavior\n\nThe built-in `getUser` resolver supports mock authentication for local development. It does not perform real JWT verification — you must provide a custom resolver for production.\n\n## Mock authentication\n\nSet the following environment variables to enable mock auth:\n\n```bash\nAUTH_MOCK=true\nAUTH_MOCK_TOKEN=my-dev-token\n```\n\nThen pass the token as the `Authorization` header and the username via `x-username`:\n\n```bash\ncurl -X POST http://localhost:4000/graphql \\\n  -H \"Authorization: my-dev-token\" \\\n  -H \"x-username: developer\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"query\": \"{ __typename }\"}'\n```\n\n## Custom JWT verification\n\nFor production, provide a custom `getUser` resolver that verifies tokens using your identity provider:\n\n```ts path=\"src/auth.ts\"\nimport type { GetUserResolver } from \"@lakeql/api/types\"\nimport { jwtVerify, createRemoteJWKSet } from \"jose\"\n\nconst jwks = createRemoteJWKSet(\n  new URL(\"https://auth.example.com/.well-known/jwks.json\")\n)\n\nexport const getUser: GetUserResolver = async (req) => {\n  const authHeader = req.headers.get(\"authorization\")\n  if (!authHeader?.startsWith(\"Bearer \")) {\n    return null\n  }\n\n  const token = authHeader.slice(7)\n\n  try {\n    const { payload } = await jwtVerify(token, jwks, {\n      issuer: \"https://auth.example.com\",\n      audience: \"lakeql-api\",\n    })\n\n    return {\n      ...payload,\n      userName: payload.sub ?? \"unknown\",\n    }\n  } catch {\n    return null\n  }\n}\n```\n\nPass it to `defineConfig` in your `src/config.ts`:\n\n```ts path=\"src/config.ts\"\nimport { defineConfig } from \"@lakeql/api/config\"\n\nimport { getUser } from \"./auth\"\nimport { allConfigs } from \"./config-registry\"\n\nexport const config = defineConfig({\n  allConfigs,\n  baseDir: import.meta.dirname,\n  getUser,\n  schemaPath: \"./schemas\",\n})\n```\n\nThe resolved user is available in every GraphQL resolver via `context.currentUser`.\n","description":"Configure user authentication via JWT tokens or mock authentication for development.","navTitle":"JWT Authentication","keywords":["authentication","configure","tokens","development","getuserresolver"]}
{"schemaVersion":"1.0.0","docId":"api/authentication/permissions","source":"api","slug":"authentication/permissions","path":"/docs/api/authentication/permissions","raw_path":"/raw/api/authentication/permissions.md","title":"Permissions","headings":[{"level":2,"text":"Permission interface","id":"permission-interface"},{"level":2,"text":"Wildcard access","id":"wildcard-access"},{"level":2,"text":"createPermission helper","id":"create-permission-helper"},{"level":2,"text":"How permissions are evaluated","id":"how-permissions-are-evaluated"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/authentication/permissions/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: Permissions\ndescription: Define table-level permission rules for technical users via the Permission interface.\n---\n\nPermissions define which catalogs, schemas, and tables a technical user can access. They act as an application-level allow list on top of Trino's built-in authorization — primarily useful for service accounts that execute queries via a shared system user.\n\n## Permission interface\n\n<InterfaceReference file=\"api/src/types\" name=\"Permission\" />\n\n## Wildcard access\n\nUse `[\"*\"]` in the `tables` array to grant access to all tables within a catalog/schema combination:\n\n```ts\n{\n  catalog: \"hive\",\n  schema: \"analytics\",\n  tables: [\"*\"] // access all tables in hive.analytics\n}\n```\n\n## createPermission helper\n\nFor type-safe permission construction that validates against your generated configs, use the `createPermission` helper with `defineConfig`:\n\n```ts path=\"src/permissions.ts\"\nimport { createPermission as createPermissionFromApi } from \"@lakeql/api/helpers\"\nimport type { Permission } from \"@lakeql/api/types\"\n\nimport { allConfigs } from \"./config-registry\"\n\nconst createPermission = createPermissionFromApi(allConfigs)\n\nexport const permissions: Permission[] = [\n  {\n    name: \"data-pipeline-service\",\n    useSystemUser: true,\n    permissions: {\n      Query: [\n        createPermission(\"hive\", \"raw_data\", [\"events\", \"users\", \"sessions\"]),\n      ],\n      Mutation: [createPermission(\"hive\", \"processed\", [\"aggregated_events\"])],\n    },\n  },\n  {\n    name: \"reporting-service\",\n    useSystemUser: true,\n    permissions: {\n      Query: [createPermission(\"hive\", \"analytics\", [\"*\"])],\n      Mutation: [],\n    },\n  },\n]\n```\n\n## How permissions are evaluated\n\n- **Read (Query):** If no permission entry exists for a user, reads are allowed (Trino handles auth for human users). If rules exist, at least one must match the requested catalog/schema/table.\n- **Write (Mutation):** If no permission entry exists, writes are denied. Rules must explicitly grant access.\n\nSee [Scope Authorization](/docs/api/authentication/scope-authorization) for the full evaluation logic.\n","description":"Define table-level permission rules for technical users via the Permission interface.","keywords":["permission","permissions","interface","define","table-level"]}
{"schemaVersion":"1.0.0","docId":"api/authentication/scope-authorization","source":"api","slug":"authentication/scope-authorization","path":"/docs/api/authentication/scope-authorization","raw_path":"/raw/api/authentication/scope-authorization.md","title":"Scope Authorization","headings":[{"level":2,"text":"Auth scopes","id":"auth-scopes"},{"level":2,"text":"Read permission logic","id":"read-permission-logic"},{"level":2,"text":"Write permission logic","id":"write-permission-logic"},{"level":2,"text":"Custom resolvers","id":"custom-resolvers"},{"level":2,"text":"Applying scopes to fields","id":"applying-scopes-to-fields"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/authentication/scope-authorization/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: Scope Authorization\ndescription: Three authorization scopes control access to GraphQL fields — authorized, readPermission, and writePermission.\n---\n\nLakeQL uses Pothos's scope auth plugin to enforce authorization at the field level. Every query or mutation field can declare which scope is required to access it.\n\n## Auth scopes\n\n| Scope             | Check                                              | Description                            |\n| ----------------- | -------------------------------------------------- | -------------------------------------- |\n| `authorized`      | `!!context.currentUser`                            | User is authenticated (any valid JWT)  |\n| `readPermission`  | Evaluates catalog/schema/table against permissions | User can read from the specified table |\n| `writePermission` | Evaluates catalog/schema/table against permissions | User can write to the specified table  |\n\n## Read permission logic\n\nThe default `hasReadPermission` resolver follows this decision model:\n\n1. **No user** → deny\n2. **No permission entry for user** → allow (Trino handles auth for human users)\n3. **Permission entry exists but no Query rules** → allow\n4. **Query rules exist** → at least one rule must match the catalog, schema, and table name\n\nThis default-allow model works because human users (OAuth2 Authorization Code Flow) typically have direct Trino identities. Technical users that go through a shared system account need explicit rules.\n\n## Write permission logic\n\nThe default `hasWritePermission` resolver is stricter:\n\n1. **No user** → deny\n2. **No permission entry for user** → deny\n3. **Permission entry exists but no Mutation rules** → deny\n4. **Mutation rules exist** → at least one rule must match the catalog, schema, and table name\n\nWrites always require explicit permission because they execute via a system user in Trino and bypass Trino's per-user authorization.\n\n## Custom resolvers\n\nOverride the default logic by providing custom resolvers in `defineConfig`:\n\n```ts path=\"src/config.ts\"\nimport { defineConfig } from \"@lakeql/api/config\"\n\nimport { allConfigs } from \"./config-registry\"\n\nexport const config = defineConfig({\n  allConfigs,\n  hasReadPermission: ({ context, catalog, schema, tableName }) => {\n    // Custom logic: check external authorization service\n    return checkExternalAuthService(context.currentUser, {\n      action: \"read\",\n      resource: `${catalog}.${schema}.${tableName}`,\n    })\n  },\n  hasWritePermission: ({ context, catalog, schema, tableName }) => {\n    // Custom logic: all writes require admin role\n    return context.currentUser?.role === \"admin\"\n  },\n})\n```\n\n## Applying scopes to fields\n\nWhen building custom query schemas, use `authScopes` to protect fields:\n\n```ts\nimport { builder } from \"@lakeql/api/builder\"\n\nbuilder.queryField(\"sensitiveData\", (t) =>\n  t.field({\n    type: \"String\",\n    authScopes: {\n      readPermission: {\n        catalog: \"hive\",\n        schema: \"internal\",\n        tableName: \"secrets\",\n      },\n    },\n    resolve: () => \"classified information\",\n  })\n)\n```\n\nFor mutations, use `writePermission`:\n\n```ts\nbuilder.mutationField(\"updateRecord\", (t) =>\n  t.field({\n    type: \"Boolean\",\n    authScopes: {\n      writePermission: {\n        catalog: \"hive\",\n        schema: \"production\",\n        tableName: \"records\",\n      },\n    },\n    args: {\n      id: t.arg.string({ required: true }),\n      value: t.arg.string({ required: true }),\n    },\n    resolve: async (_parent, args, context) => {\n      // perform the write operation\n      return true\n    },\n  })\n)\n```\n\nFields without `authScopes` are publicly accessible (no authentication required).\n","description":"Three authorization scopes control access to GraphQL fields — authorized, readPermission, and writePermission.","keywords":["scopes","authorization","fields","permission","logic"]}
{"schemaVersion":"1.0.0","docId":"api/customization/cors-configuration","source":"api","slug":"customization/cors-configuration","path":"/docs/api/customization/cors-configuration","raw_path":"/raw/api/customization/cors-configuration.md","title":"CORS Configuration","headings":[{"level":2,"text":"Default CORS configuration","id":"default-cors-configuration"},{"level":2,"text":"Customizing CORS","id":"customizing-cors"},{"level":2,"text":"Adding custom middleware","id":"adding-custom-middleware"},{"level":2,"text":"Adding authentication middleware","id":"adding-authentication-middleware"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/customization/cors-configuration/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: CORS Configuration\nnavTitle: CORS Configuration\ndescription: Customize CORS settings and add additional Hono middleware to the API server.\n---\n\nBy default, `createApiServer` applies permissive CORS settings on the GraphQL endpoint. For production deployments, you'll typically want to restrict the allowed origins and customize headers.\n\n## Default CORS configuration\n\nThe built-in CORS middleware is applied to POST, GET, and OPTIONS requests on the GraphQL path:\n\n| Setting        | Default Value                       |\n| -------------- | ----------------------------------- |\n| `origin`       | `\"*\"` (all origins)                 |\n| `allowMethods` | `[\"POST\", \"GET\", \"OPTIONS\"]`        |\n| `allowHeaders` | `[\"content-type\", \"authorization\"]` |\n| `credentials`  | `true`                              |\n\n## Customizing CORS\n\nSince `createApiServer` returns the Hono `app` instance, you can add or override middleware:\n\n```ts\nimport { createApiServer } from \"@lakeql/api/server\"\nimport { cors } from \"hono/cors\"\n\nconst { app } = await createApiServer({\n  schemaPath: \"./src/schemas\",\n})\n\n// Add restrictive CORS for a specific path\napp.use(\n  \"/graphql/*\",\n  cors({\n    origin: [\"https://app.example.com\", \"https://admin.example.com\"],\n    allowMethods: [\"POST\", \"OPTIONS\"],\n    allowHeaders: [\"content-type\", \"authorization\", \"x-request-id\"],\n    credentials: true,\n    maxAge: 86400,\n  })\n)\n```\n\n## Adding custom middleware\n\nThe Hono app supports any compatible middleware. Add rate limiting, request logging, or custom headers:\n\n```ts\nimport { createApiServer, startApiServer } from \"@lakeql/api/server\"\nimport { serve } from \"@hono/node-server\"\n\nconst { app, yoga } = await createApiServer({\n  schemaPath: \"./src/schemas\",\n})\n\n// Custom request ID header\napp.use(\"*\", async (c, next) => {\n  const requestId = crypto.randomUUID()\n  c.header(\"x-request-id\", requestId)\n  await next()\n})\n\n// Custom health endpoint\napp.get(\"/ready\", (c) =>\n  c.json({ status: \"ready\", timestamp: new Date().toISOString() })\n)\n\n// Start manually with the customized app\nserve({\n  fetch: app.fetch,\n  port: 4000,\n})\n```\n\n## Adding authentication middleware\n\nFor scenarios where you need pre-GraphQL authentication checks:\n\n```ts\nimport { createApiServer } from \"@lakeql/api/server\"\n\nconst { app } = await createApiServer({\n  schemaPath: \"./src/schemas\",\n})\n\n// Block unauthenticated requests before they reach Yoga\napp.use(\"/graphql/*\", async (c, next) => {\n  const auth = c.req.header(\"authorization\")\n  if (!auth && c.req.method !== \"OPTIONS\") {\n    return c.json({ error: \"Authorization required\" }, 401)\n  }\n  await next()\n})\n```\n\nNote that this is separate from the GraphQL-level `authScopes` — it blocks requests at the HTTP layer before they reach the GraphQL resolver.\n","description":"Customize CORS settings and add additional Hono middleware to the API server.","navTitle":"CORS Configuration","keywords":["middleware","configuration","adding","customize","settings"]}
{"schemaVersion":"1.0.0","docId":"api/customization/custom-queries-mutations","source":"api","slug":"customization/custom-queries-mutations","path":"/docs/api/customization/custom-queries-mutations","raw_path":"/raw/api/customization/custom-queries-mutations.md","title":"Custom Queries & Mutations","headings":[{"level":2,"text":"Creating an endpoint definition","id":"creating-an-endpoint-definition"},{"level":2,"text":"Generating the endpoint","id":"generating-the-endpoint"},{"level":2,"text":"Query-only endpoints","id":"query-only-endpoints"},{"level":2,"text":"Field options","id":"field-options"},{"level":2,"text":"Mutation load strategies","id":"mutation-load-strategies"},{"level":2,"text":"File discovery","id":"file-discovery"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/customization/custom-queries-mutations/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: Custom Queries & Mutations\nnavTitle: Custom Queries & Mutations\ndescription: Add custom query and mutation endpoints using the CLI endpoint builder.\n---\n\nThe recommended way to add custom queries and mutations is through the CLI's `create-endpoint` command. It generates all necessary files (schema, config, types, JSON Schema) from a simple endpoint definition — the same way the `pull` command works, but from a JSON definition instead of an existing Trino table.\n\n## Creating an endpoint definition\n\nDefine your endpoint as a JSON file:\n\n```json path=\"my-endpoint.json\"\n{\n  \"version\": \"1.0\",\n  \"tableName\": \"user_events\",\n  \"catalog\": \"hive\",\n  \"schema\": \"analytics\",\n  \"fields\": [\n    { \"name\": \"event_id\", \"type\": \"String\", \"options\": { \"required\": true } },\n    { \"name\": \"message\", \"type\": \"String\" },\n    { \"name\": \"timestamp\", \"type\": \"DateTime\" }\n  ],\n  \"mutation\": {\n    \"loadStrategy\": \"full_load\",\n    \"type\": \"minio\",\n    \"bucket\": \"my-datalake\",\n    \"basePath\": \"analytics/user_events\",\n    \"endpoint\": \"http://localhost:9000\"\n  }\n}\n```\n\n## Generating the endpoint\n\nRun the CLI to generate all files:\n\n<Command variant=\"exec\">\n  lakeql-cli create-endpoint --from-file ./my-endpoint.json\n</Command>\n\nThis generates:\n\n```\nsrc/schemas/custom/hive/analytics/user_events/\n├── config.ts              # Hive + storage config\n├── endpoint.json          # Persisted definition\n├── json-schema.json       # JSON Schema for Parquet serialization\n├── query-schema.ts        # GraphQL query with sorting, filtering, paging\n└── mutation-schema.ts     # GraphQL mutation with write pipeline\n```\n\nThe `config-registry.ts` is updated automatically to include the new endpoint.\n\n## Query-only endpoints\n\nOmit the `mutation` field to generate a query-only endpoint:\n\n```json path=\"query-only.json\"\n{\n  \"version\": \"1.0\",\n  \"tableName\": \"reports\",\n  \"catalog\": \"hive\",\n  \"schema\": \"analytics\",\n  \"fields\": [\n    { \"name\": \"report_id\", \"type\": \"String\" },\n    { \"name\": \"title\", \"type\": \"String\" },\n    { \"name\": \"created_at\", \"type\": \"DateTime\" }\n  ]\n}\n```\n\nOr explicitly disable mutations:\n\n```json\n{ \"mutation\": false }\n```\n\n## Field options\n\nEach field supports options for mutation input behavior:\n\n| Option        | Default | Description                                                    |\n| ------------- | ------- | -------------------------------------------------------------- |\n| `required`    | `false` | Field is required in mutation input                            |\n| `readOnly`    | `false` | Field appears in queries but is excluded from mutation input   |\n| `validations` | `[]`    | Zod validation refinements (email, url, uuid, min, max, regex) |\n\n```json\n{\n  \"name\": \"email\",\n  \"type\": \"String\",\n  \"options\": {\n    \"required\": true,\n    \"validations\": [{ \"type\": \"email\" }]\n  }\n}\n```\n\n## Mutation load strategies\n\nThe `mutation` config supports three strategies. See [Load Strategies](/docs/adapters/write-pipeline/load-strategies) for details.\n\n| Strategy           | Behavior                        |\n| ------------------ | ------------------------------- |\n| `full_load`        | Replace all data on every write |\n| `full_load_append` | Replace latest + keep history   |\n| `append`           | Append only                     |\n\n## File discovery\n\nThe API server automatically discovers all schema files at startup by scanning for:\n\n```\nschemas/**/query-schema.{ts,js,mjs}\nschemas/**/mutation-schema.{ts,js,mjs}\n```\n\nNo manual registration is needed — just generate and start the server.\n","description":"Add custom query and mutation endpoints using the CLI endpoint builder.","navTitle":"Custom Queries & Mutations","keywords":["endpoint","custom","mutation","endpoints","queries"]}
{"schemaVersion":"1.0.0","docId":"api/customization/extending-core","source":"api","slug":"customization/extending-core","path":"/docs/api/customization/extending-core","raw_path":"/raw/api/customization/extending-core.md","title":"Extending Core","headings":[{"level":2,"text":"Custom GetUserResolver","id":"custom-get-user-resolver"},{"level":2,"text":"Custom ReadPermissionResolver","id":"custom-read-permission-resolver"},{"level":2,"text":"Custom WritePermissionResolver","id":"custom-write-permission-resolver"},{"level":2,"text":"Registering custom resolvers","id":"registering-custom-resolvers"},{"level":2,"text":"Adding custom scalars","id":"adding-custom-scalars"},{"level":2,"text":"Overriding built-in scalar serialization","id":"overriding-built-in-scalar-serialization"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/customization/extending-core/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: Extending Core\ndescription: Provide custom resolvers for authentication, permissions, and add additional scalars.\n---\n\nLakeQL'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.\n\n## Custom GetUserResolver\n\nReplace the built-in mock auth with your own JWT verification:\n\n```ts\nimport type { GetUserResolver } from \"@lakeql/api/types\"\nimport { jwtVerify } from \"jose\"\n\nexport const customGetUser: GetUserResolver = async (req) => {\n  const token = req.headers.get(\"authorization\")?.replace(\"Bearer \", \"\")\n  if (!token) return null\n\n  try {\n    const { payload } = await jwtVerify(token, secretKey)\n    return { ...payload, userName: payload.sub ?? \"unknown\" }\n  } catch {\n    return null\n  }\n}\n```\n\n## Custom ReadPermissionResolver\n\nOverride how read permissions are evaluated:\n\n```ts\nimport type { ReadPermissionResolver } from \"@lakeql/api/types\"\n\nexport const customReadPermission: ReadPermissionResolver = ({\n  context,\n  catalog,\n  schema,\n  tableName,\n}) => {\n  // Allow all reads for admin users\n  if (context.currentUser?.role === \"admin\") {\n    return true\n  }\n\n  // Check against external policy engine\n  return checkPolicy(context.currentUser, \"read\", {\n    catalog,\n    schema,\n    tableName,\n  })\n}\n```\n\n## Custom WritePermissionResolver\n\nOverride how write permissions are evaluated:\n\n```ts\nimport type { WritePermissionResolver } from \"@lakeql/api/types\"\n\nexport const customWritePermission: WritePermissionResolver = ({\n  context,\n  catalog,\n  schema,\n  tableName,\n}) => {\n  // Only service accounts can write\n  if (!context.currentUser?.isServiceAccount) {\n    return false\n  }\n\n  return checkPolicy(context.currentUser, \"write\", {\n    catalog,\n    schema,\n    tableName,\n  })\n}\n```\n\n## Registering custom resolvers\n\nPass all custom resolvers via `defineConfig`:\n\n```ts\nimport { defineConfig } from \"@lakeql/api/config\"\nimport { allConfigs } from \"./generated/configs\"\nimport { customGetUser } from \"./auth\"\nimport { customReadPermission, customWritePermission } from \"./permissions\"\n\nexport default defineConfig({\n  allConfigs,\n  getUser: customGetUser,\n  hasReadPermission: customReadPermission,\n  hasWritePermission: customWritePermission,\n})\n```\n\n## Adding custom scalars\n\nRegister 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.\n\n```ts path=\"src/schemas/00-scalars/query-schema.ts\"\nimport { builder } from \"@lakeql/api/builder\"\nimport { JSONResolver, BigIntResolver } from \"graphql-scalars\"\n\nbuilder.addScalarType(\"JSON\", JSONResolver, {})\nbuilder.addScalarType(\"BigInt\", BigIntResolver, {})\n```\n\n<Callout type=\"info\">\n  `graphql` and `graphql-scalars` are transitive dependencies of `@lakeql/api`.\n  To avoid issues if those internals ever change, install them explicitly in\n  your project.\n\n<Command variant=\"install\">graphql graphql-scalars</Command>\n\n</Callout>\n\nThese 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.\n\n## Overriding built-in scalar serialization\n\nGraphQL'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.\n\nYou can monkey-patch the serializer at startup to handle these edge cases.\n\n### Setup\n\nCreate 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.\n\n```text\nsrc/\n└── schemas/\n    ├── 00-scalars/\n    │   └── query-schema.ts   ← Int override goes here\n    ├── my-catalog/\n    │   └── ...\n    └── ...\n```\n\n```ts path=\"src/schemas/00-scalars/query-schema.ts\"\nimport { GraphQLInt } from \"graphql\"\n\n// Preserve the original serializer as a fallback\nconst originalIntSerialize = GraphQLInt.serialize.bind(GraphQLInt)\n\nGraphQLInt.serialize = (value: unknown) => {\n  // Pass through any safe integer value directly, even beyond 32-bit range\n  if (\n    typeof value === \"number\" &&\n    Number.isInteger(value) &&\n    Number.isFinite(value)\n  ) {\n    return value\n  }\n\n  // Handle numeric strings (e.g., from database drivers returning string IDs)\n  if (typeof value === \"string\" && value !== \"\") {\n    const num = Number(value)\n    if (Number.isInteger(num) && Number.isFinite(num)) return num\n  }\n\n  // Fall back to default behavior for anything else\n  return originalIntSerialize(value)\n}\n```\n\nSince 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.\n","description":"Provide custom resolvers for authentication, permissions, and add additional scalars.","keywords":["custom","resolvers","scalars","extending","provide"]}
{"schemaVersion":"1.0.0","docId":"api/overview/introduction","source":"api","slug":"overview/introduction","path":"/docs/api/overview/introduction","raw_path":"/raw/api/overview/introduction.md","title":"Introduction","headings":[{"level":2,"text":"How it connects","id":"how-it-connects"},{"level":2,"text":"Schema loading","id":"schema-loading"},{"level":2,"text":"Quick start","id":"quick-start"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/overview/introduction/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: Introduction\ndescription: What @lakeql/api provides and how it fits into the LakeQL ecosystem.\n---\n\n`@lakeql/api` is the runtime GraphQL server package in the LakeQL ecosystem. It exposes a fully typed GraphQL API that queries data from Trino — a distributed SQL query engine — and returns structured, paginated results to clients.\n\nThe package combines three core libraries into a cohesive server stack:\n\n- **Hono** — A lightweight, high-performance HTTP framework that handles routing, CORS, and middleware.\n- **GraphQL Yoga** — A spec-compliant GraphQL server with built-in support for subscriptions, file uploads, and health checks.\n- **Pothos** — A code-first, type-safe GraphQL schema builder that generates the schema from TypeScript definitions.\n\n## How it connects\n\n`@lakeql/api` sits at the center of the LakeQL architecture. It depends on several sibling packages:\n\n| Package                        | Role                                                          |\n| ------------------------------ | ------------------------------------------------------------- |\n| `@lakeql/trino-client`         | Executes SQL queries against Trino                            |\n| `@lakeql/query-builder`        | Constructs SQL from GraphQL arguments (filters, pagination)   |\n| `@lakeql/response-transformer` | Transforms raw Trino responses into GraphQL-compatible shapes |\n| `@lakeql/helpers`              | Shared utility functions                                      |\n| `@lakeql/logger`               | Structured logging                                            |\n\n## Schema loading\n\nQuery and mutation schemas are loaded automatically at startup using glob patterns. The server scans for files matching `schemas/**/{query,mutation}-schema.{ts,js,mjs}` in the configured schema directory. Each schema file registers its types and resolvers on the shared Pothos builder — no manual wiring required.\n\n## Quick start\n\nInstall the required packages:\n\n<Command variant=\"install\">\n  @lakeql/api @lakeql/query-builder @lakeql/trino-client @t3-oss/env-core zod\n</Command>\n\nSet up the configuration (`src/config.ts`):\n\n```ts path=\"src/config.ts\"\nimport { defineConfig } from \"@lakeql/api/config\"\n\nimport { getUser } from \"./auth\"\nimport { allConfigs } from \"./config-registry\"\nimport { permissions } from \"./permissions\"\n\nconst baseDir = import.meta.dirname\n\nexport const config = defineConfig({\n  allConfigs,\n  baseDir,\n  getUser,\n  graphqlPath: \"/graphql\",\n  healthCheckEndpoint: \"/live\",\n  permissions,\n  port: 4000,\n  schemaPath: \"./schemas\",\n})\n```\n\nCreate the entry point (`src/index.ts`):\n\n```ts path=\"src/index.ts\"\nimport { config } from \"./config\"\n\nawait config.startServer()\n```\n\nThe server starts on `http://localhost:4000` with GraphiQL available at `/graphql` in development mode.\n\n<Note title=\"Use @lakeql/create-app for a ready-to-go setup\">\n  Instead of wiring everything manually, use\n  [`@lakeql/create-app`](/docs/lakeql/create-app/usage) to scaffold a fully\n  functional project with auth, permissions, environment validation, and CLI\n  integration pre-configured.\n</Note>\n","description":"What @lakeql/api provides and how it fits into the LakeQL ecosystem.","keywords":["introduction","lakeqlapi","provides","lakeql","ecosystem"]}
{"schemaVersion":"1.0.0","docId":"api/schema-builder/builder-configuration","source":"api","slug":"schema-builder/builder-configuration","path":"/docs/api/schema-builder/builder-configuration","raw_path":"/raw/api/schema-builder/builder-configuration.md","title":"Builder Configuration","headings":[{"level":2,"text":"Builder setup","id":"builder-setup"},{"level":2,"text":"Context type","id":"context-type"},{"level":2,"text":"Auth scopes","id":"auth-scopes"},{"level":2,"text":"SortDirection enum","id":"sort-direction-enum"},{"level":2,"text":"Importing the builder","id":"importing-the-builder"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/schema-builder/builder-configuration/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: Builder Configuration\ndescription: Pothos SchemaBuilder setup with scope auth, validation plugins, and the GraphQL context type.\n---\n\nThe Pothos `SchemaBuilder` is the foundation of LakeQL's type-safe GraphQL schema. It's pre-configured with auth scopes and validation, and shared across all query schema files.\n\n## Builder setup\n\n```ts\nimport SchemaBuilder from \"@pothos/core\"\nimport ScopeAuthPlugin from \"@pothos/plugin-scope-auth\"\nimport ValidationPlugin from \"@pothos/plugin-validation\"\n\nconst builder = new SchemaBuilder<{\n  Context: Context\n  Scalars: Partial<UserScalars[\"Scalars\"]>\n  AuthScopes: {\n    authorized: boolean\n    readPermission: PermissionFields\n    writePermission: PermissionFields\n  }\n}>({\n  plugins: [ScopeAuthPlugin, ValidationPlugin],\n  scopeAuth: {\n    /* ... */\n  },\n  validation: {\n    /* ... */\n  },\n})\n```\n\n## Context type\n\nEvery resolver receives this context object:\n\n<InterfaceReference file=\"api/src/types\" name=\"Context\" />\n\nA `logger` instance is also injected into the context by the Yoga server setup.\n\n## Auth scopes\n\nThe builder defines three scopes that can be applied to any field:\n\n```ts\nAuthScopes: {\n  authorized: boolean // user is authenticated\n  readPermission: PermissionFields // { catalog, schema, tableName }\n  writePermission: PermissionFields // { catalog, schema, tableName }\n}\n```\n\n## SortDirection enum\n\nA shared enum for ordering query results:\n\n```ts\nimport { SortDirection } from \"@lakeql/api/builder\"\n// Values: \"ASC\" | \"DESC\"\n```\n\n## Importing the builder\n\nCustom query schemas import the shared builder instance to register types and fields:\n\n```ts\nimport { builder } from \"@lakeql/api/builder\"\n\nbuilder.queryField(\"hello\", (t) =>\n  t.string({\n    resolve: () => \"world\",\n  })\n)\n```\n\nAll files that import `builder` and define types or fields are automatically discovered during schema loading — no explicit registration needed.\n","description":"Pothos SchemaBuilder setup with scope auth, validation plugins, and the GraphQL context type.","keywords":["builder","setup","context","configuration","pothos"]}
{"schemaVersion":"1.0.0","docId":"api/schema-builder/comparison-types","source":"api","slug":"schema-builder/comparison-types","path":"/docs/api/schema-builder/comparison-types","raw_path":"/raw/api/schema-builder/comparison-types.md","title":"Comparison Types","headings":[{"level":2,"text":"Available comparison types","id":"available-comparison-types"},{"level":2,"text":"Usage in GraphQL queries","id":"usage-in-graph-ql-queries"},{"level":2,"text":"Importing comparison types","id":"importing-comparison-types"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/schema-builder/comparison-types/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: Comparison Types\ndescription: Pre-built GraphQL input types for filtering queries with typed comparison operators.\n---\n\nLakeQL provides seven comparison input types that power the generated `Where` filter inputs. Each type exposes operators appropriate for its scalar type.\n\n## Available comparison types\n\n### StringFieldComparison\n\n| Operator  | Type       | Description                |\n| --------- | ---------- | -------------------------- |\n| `eq`      | `String`   | Equal to                   |\n| `neq`     | `String`   | Not equal to               |\n| `like`    | `String`   | SQL LIKE pattern match     |\n| `notLike` | `String`   | SQL NOT LIKE pattern match |\n| `in`      | `[String]` | Value in list              |\n| `notIn`   | `[String]` | Value not in list          |\n\n### IntFieldComparison\n\n| Operator | Type    | Description           |\n| -------- | ------- | --------------------- |\n| `eq`     | `Int`   | Equal to              |\n| `neq`    | `Int`   | Not equal to          |\n| `lt`     | `Int`   | Less than             |\n| `lte`    | `Int`   | Less than or equal    |\n| `gt`     | `Int`   | Greater than          |\n| `gte`    | `Int`   | Greater than or equal |\n| `in`     | `[Int]` | Value in list         |\n| `notIn`  | `[Int]` | Value not in list     |\n\n### FloatFieldComparison\n\n| Operator | Type      | Description           |\n| -------- | --------- | --------------------- |\n| `eq`     | `Float`   | Equal to              |\n| `neq`    | `Float`   | Not equal to          |\n| `lt`     | `Float`   | Less than             |\n| `lte`    | `Float`   | Less than or equal    |\n| `gt`     | `Float`   | Greater than          |\n| `gte`    | `Float`   | Greater than or equal |\n| `in`     | `[Float]` | Value in list         |\n| `notIn`  | `[Float]` | Value not in list     |\n\n### BooleanFieldComparison\n\n| Operator | Type      | Description |\n| -------- | --------- | ----------- |\n| `is`     | `Boolean` | Is true     |\n| `isNot`  | `Boolean` | Is false    |\n\n### DateFieldComparison\n\n| Operator | Type   | Description           |\n| -------- | ------ | --------------------- |\n| `eq`     | `Date` | Equal to              |\n| `neq`    | `Date` | Not equal to          |\n| `lt`     | `Date` | Less than             |\n| `lte`    | `Date` | Less than or equal    |\n| `gt`     | `Date` | Greater than          |\n| `gte`    | `Date` | Greater than or equal |\n\n### DateTimeFieldComparison\n\n| Operator | Type       | Description           |\n| -------- | ---------- | --------------------- |\n| `eq`     | `DateTime` | Equal to              |\n| `neq`    | `DateTime` | Not equal to          |\n| `lt`     | `DateTime` | Less than             |\n| `lte`    | `DateTime` | Less than or equal    |\n| `gt`     | `DateTime` | Greater than          |\n| `gte`    | `DateTime` | Greater than or equal |\n\n### IDFieldComparison\n\n| Operator | Type   | Description       |\n| -------- | ------ | ----------------- |\n| `eq`     | `ID`   | Equal to          |\n| `neq`    | `ID`   | Not equal to      |\n| `in`     | `[ID]` | Value in list     |\n| `notIn`  | `[ID]` | Value not in list |\n\n## Usage in GraphQL queries\n\nThese comparison types appear in generated `Where` input types for each table. Use them to filter query results:\n\n```graphql\nquery FilteredUsers {\n  users(\n    where: {\n      age: { gte: 18, lt: 65 }\n      email: { like: \"%@example.com\" }\n      status: { in: [\"active\", \"pending\"] }\n    }\n  ) {\n    nodes {\n      id\n      name\n      email\n    }\n  }\n}\n```\n\n## Importing comparison types\n\nIf you need to reference these types in custom schemas:\n\n```ts\nimport {\n  StringFieldComparison,\n  IntFieldComparison,\n  FloatFieldComparison,\n  BooleanFieldComparison,\n  DateFieldComparison,\n  DateTimeFieldComparison,\n  IDFieldComparison,\n} from \"@lakeql/api/builder\"\n```\n","description":"Pre-built GraphQL input types for filtering queries with typed comparison operators.","keywords":["comparison","types","graphql","queries","pre-built"]}
{"schemaVersion":"1.0.0","docId":"api/schema-builder/input-validation","source":"api","slug":"schema-builder/input-validation","path":"/docs/api/schema-builder/input-validation","raw_path":"/raw/api/schema-builder/input-validation.md","title":"Input Validation","headings":[{"level":2,"text":"How it works","id":"how-it-works"},{"level":2,"text":"Error response format","id":"error-response-format"},{"level":2,"text":"Built-in validation","id":"built-in-validation"},{"level":2,"text":"Adding validation to custom inputs","id":"adding-validation-to-custom-inputs"},{"level":2,"text":"Validation on query arguments","id":"validation-on-query-arguments"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/schema-builder/input-validation/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: Input Validation\ndescription: Validate GraphQL input fields using Zod schemas via the Pothos ValidationPlugin.\n---\n\nLakeQL integrates Pothos's ValidationPlugin with Zod to validate input arguments before they reach your resolvers. Invalid inputs return a structured `VALIDATION_FAILED` error with detailed issues.\n\n## How it works\n\nThe ValidationPlugin is registered on the shared builder and uses Zod schemas to validate input field values. When validation fails, a custom error handler formats the response with error code `200` (VALIDATION_FAILED) and the list of Zod issues.\n\n## Error response format\n\nWhen validation fails, the GraphQL error includes:\n\n```json\n{\n  \"errors\": [\n    {\n      \"message\": \"Validation failed\",\n      \"extensions\": {\n        \"code\": \"VALIDATION_FAILED\",\n        \"http\": { \"status\": 400 },\n        \"additionalInformation\": [\n          {\n            \"message\": \"Value must be less than or equal to 2000\",\n            \"path\": [\"perPage\"]\n          }\n        ]\n      }\n    }\n  ]\n}\n```\n\n## Built-in validation\n\nThe `Paging` input type uses validation to enforce `perPage` limits:\n\n```ts\nimport { z } from \"zod\"\nimport { builder, getMaxRecordsPerPage } from \"@lakeql/api/builder\"\n\nconst Paging = builder.inputType(\"Paging\", {\n  fields: (t) => ({\n    page: t.int({ defaultValue: 1 }),\n    perPage: t.int({\n      defaultValue: 100,\n      validate: z\n        .number()\n        .min(1)\n        .refine((value) => value <= getMaxRecordsPerPage(), {\n          message: `Value must be less than or equal to ${getMaxRecordsPerPage()}`,\n        }),\n    }),\n  }),\n})\n```\n\n## Adding validation to custom inputs\n\nUse the `validate` option on any input field to attach a Zod schema:\n\n```ts\nimport { z } from \"zod\"\nimport { builder } from \"@lakeql/api/builder\"\n\nconst CreateUserInput = builder.inputType(\"CreateUserInput\", {\n  fields: (t) => ({\n    email: t.string({\n      required: true,\n      validate: z.string().email(\"Must be a valid email address\"),\n    }),\n    name: t.string({\n      required: true,\n      validate: z.string().min(2).max(100),\n    }),\n    age: t.int({\n      validate: z.number().min(0).max(150),\n    }),\n  }),\n})\n```\n\n## Validation on query arguments\n\nYou can also validate query-level arguments:\n\n```ts\nimport { z } from \"zod\"\nimport { builder } from \"@lakeql/api/builder\"\n\nbuilder.queryField(\"search\", (t) =>\n  t.field({\n    type: [\"String\"],\n    args: {\n      query: t.arg.string({\n        required: true,\n        validate: z\n          .string()\n          .min(3, \"Search query must be at least 3 characters\"),\n      }),\n      limit: t.arg.int({\n        defaultValue: 10,\n        validate: z.number().min(1).max(100),\n      }),\n    },\n    resolve: (_parent, args) => {\n      // args.query is guaranteed to be at least 3 chars\n      // args.limit is guaranteed to be between 1 and 100\n      return [`Result for: ${args.query}`]\n    },\n  })\n)\n```\n","description":"Validate GraphQL input fields using Zod schemas via the Pothos ValidationPlugin.","keywords":["validation","input","validate","graphql","fields"]}
{"schemaVersion":"1.0.0","docId":"api/schema-builder/pagination","source":"api","slug":"schema-builder/pagination","path":"/docs/api/schema-builder/pagination","raw_path":"/raw/api/schema-builder/pagination.md","title":"Pagination","headings":[{"level":2,"text":"Paging input","id":"paging-input"},{"level":2,"text":"PageInfo output","id":"page-info-output"},{"level":2,"text":"ConnectionInterface","id":"connection-interface"},{"level":2,"text":"Configuring maxRecordsPerPage","id":"configuring-max-records-per-page"},{"level":2,"text":"Example query","id":"example-query"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/schema-builder/pagination/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: Pagination\ndescription: Built-in page-based pagination with Paging input, PageInfo output, and ConnectionInterface.\n---\n\nLakeQL uses offset-based pagination (page + perPage) rather than cursor-based pagination. This maps naturally to Trino's `LIMIT` and `OFFSET` clauses and provides predictable navigation for tabular data.\n\n## Paging input\n\nThe `Paging` input type is available on all generated query fields:\n\n```graphql\ninput Paging {\n  page: Int = 1\n  perPage: Int = 100\n}\n```\n\n| Field     | Default | Constraints                         |\n| --------- | ------- | ----------------------------------- |\n| `page`    | `1`     | Must be ≥ 1                         |\n| `perPage` | `100`   | Must be ≥ 1 and ≤ maxRecordsPerPage |\n\n## PageInfo output\n\nEvery paginated response includes a `PageInfo` object:\n\n```graphql\ntype PageInfo {\n  currentPage: Int!\n  hasNext: Boolean!\n  hasPrevious: Boolean!\n  maxPages: Int!\n  nextPage: Int\n  previousPage: Int\n}\n```\n\n| Field          | Description                                           |\n| -------------- | ----------------------------------------------------- |\n| `currentPage`  | The current page number                               |\n| `hasNext`      | Whether more pages exist after the current one        |\n| `hasPrevious`  | Whether pages exist before the current one            |\n| `maxPages`     | Total number of pages based on totalCount and perPage |\n| `nextPage`     | Page number for the next page (null if on last page)  |\n| `previousPage` | Page number for the previous page (null if on first)  |\n\n## ConnectionInterface\n\nAll paginated query responses conform to the `ConnectionInterface`:\n\n```ts\ninterface ConnectionInterface<T> {\n  totalCount: number\n  pageInfo: PageInfoInterface\n  nodes: T[]\n}\n```\n\nIn GraphQL, this looks like:\n\n```graphql\ntype UserConnection {\n  totalCount: Int!\n  pageInfo: PageInfo!\n  nodes: [User!]!\n}\n```\n\n## Configuring maxRecordsPerPage\n\nThe maximum allowed value for `perPage` is controlled by:\n\n1. The `API_MAX_RECORDS_PER_PAGE` environment variable (default: `2000`)\n2. The `maxRecordsPerPage` option in `defineConfig`\n\n```ts\nimport { defineConfig } from \"@lakeql/api/config\"\nimport { allConfigs } from \"./generated/configs\"\n\nexport default defineConfig({\n  allConfigs,\n  maxRecordsPerPage: 500, // override the env default\n})\n```\n\nRequesting more than `maxRecordsPerPage` returns a validation error.\n\n## Example query\n\n```graphql\nquery PaginatedUsers {\n  users(paging: { page: 2, perPage: 25 }) {\n    totalCount\n    pageInfo {\n      currentPage\n      hasNext\n      hasPrevious\n      maxPages\n      nextPage\n      previousPage\n    }\n    nodes {\n      id\n      name\n      email\n    }\n  }\n}\n```\n","description":"Built-in page-based pagination with Paging input, PageInfo output, and ConnectionInterface.","keywords":["pagination","paging","input","pageinfo","output"]}
{"schemaVersion":"1.0.0","docId":"api/schema-builder/scalar-types","source":"api","slug":"schema-builder/scalar-types","path":"/docs/api/schema-builder/scalar-types","raw_path":"/raw/api/schema-builder/scalar-types.md","title":"Scalar Types","headings":[{"level":2,"text":"Date","id":"date"},{"level":2,"text":"DateTime","id":"date-time"},{"level":2,"text":"File","id":"file"},{"level":2,"text":"Source code","id":"source-code"},{"level":2,"text":"Usage in custom schemas","id":"usage-in-custom-schemas"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/schema-builder/scalar-types/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: Scalar Types\ndescription: Custom GraphQL scalar types for Date, DateTime, and File uploads.\n---\n\nLakeQL registers three custom scalar types on the shared builder. These extend the standard GraphQL scalars to handle common data types from Trino.\n\n## Date\n\nA date-only scalar (no time component). Uses `DateResolver` from `graphql-scalars`.\n\n- **Input:** ISO 8601 date string (e.g., `\"2024-01-15\"`)\n- **Output:** JavaScript `Date` object\n\n## DateTime\n\nA full timestamp scalar with timezone support. Uses `DateTimeResolver` from `graphql-scalars`.\n\n- **Input:** ISO 8601 datetime string (e.g., `\"2024-01-15T10:30:00Z\"`)\n- **Output:** JavaScript `Date` object\n\n## File\n\nAn input-only scalar for file uploads. Attempting to serialize (output) a `File` scalar throws an error.\n\n- **Input:** `File` object (from multipart form data)\n- **Output:** Not supported (throws `\"Uploads can only be used as input types\"`)\n\n## Source code\n\n```ts\nimport { DateResolver, DateTimeResolver } from \"graphql-scalars\"\nimport { builder } from \"@lakeql/api/builder\"\n\nbuilder.addScalarType(\"Date\", DateResolver, {})\nbuilder.addScalarType(\"DateTime\", DateTimeResolver, {})\n\nbuilder.scalarType(\"File\", {\n  serialize: () => {\n    throw new Error(\"Uploads can only be used as input types\")\n  },\n})\n```\n\n## Usage in custom schemas\n\nReference these scalars by name when defining fields:\n\n```ts\nimport { builder } from \"@lakeql/api/builder\"\n\nconst EventType = builder\n  .objectRef<{\n    id: string\n    name: string\n    createdAt: Date\n    occurredOn: Date\n  }>(\"Event\")\n  .implement({\n    fields: (t) => ({\n      id: t.exposeID(\"id\"),\n      name: t.exposeString(\"name\"),\n      createdAt: t.expose(\"createdAt\", { type: \"DateTime\" }),\n      occurredOn: t.expose(\"occurredOn\", { type: \"Date\" }),\n    }),\n  })\n```\n","description":"Custom GraphQL scalar types for Date, DateTime, and File uploads.","keywords":["scalar","types","custom","datetime","graphql"]}
{"schemaVersion":"1.0.0","docId":"api/server-setup/create-api-server","source":"api","slug":"server-setup/create-api-server","path":"/docs/api/server-setup/create-api-server","raw_path":"/raw/api/server-setup/create-api-server.md","title":"createApiServer","headings":[{"level":2,"text":"Signature","id":"signature"},{"level":2,"text":"Return type","id":"return-type"},{"level":2,"text":"What it sets up","id":"what-it-sets-up"},{"level":2,"text":"Usage","id":"usage"},{"level":2,"text":"Starting the server","id":"starting-the-server"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/server-setup/create-api-server/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: createApiServer\nnavTitle: createApiServer\ndescription: Create a configured Hono application with GraphQL Yoga integration, CORS, and logging middleware.\n---\n\n`createApiServer` is the primary factory function for building a LakeQL API instance. It wires together Hono, GraphQL Yoga, and all middleware into a ready-to-use server object.\n\n## Signature\n\n```ts\nfunction createApiServer(options?: ApiRuntimeConfig): Promise<ApiServer>\n```\n\n## Return type\n\n<InterfaceReference file=\"api/src/server\" name=\"ApiServer\" mode=\"declaration\" />\n\n## What it sets up\n\n1. Creates a structured logger via `@lakeql/logger`\n2. Initializes GraphQL Yoga with the loaded schema and context\n3. Mounts Hono logger middleware on all routes\n4. Configures CORS on the GraphQL path (POST, GET, OPTIONS)\n5. Connects the Yoga request handler to Hono\n\n## Usage\n\n```ts path=\"src/server.ts\"\nimport { createApiServer } from \"@lakeql/api/server\"\n\nconst { app, logger, yoga } = await createApiServer({\n  baseDir: import.meta.dirname,\n  schemaPath: \"./schemas\",\n  graphqlPath: \"/graphql\",\n  port: 4000,\n})\n\n// Add custom middleware or routes to the Hono app\napp.get(\"/health\", (c) => c.json({ status: \"ok\" }))\n```\n\n## Starting the server\n\nFor most projects, use `defineConfig` with `startServer()` instead of calling `createApiServer` directly:\n\n```ts path=\"src/index.ts\"\nimport { config } from \"./config\"\n\nawait config.startServer()\n```\n\nIf you don't need `defineConfig`, `startApiServer` calls `createApiServer` internally and binds to a port:\n\n```ts path=\"src/index.ts\"\nimport { startApiServer } from \"@lakeql/api/server\"\n\nawait startApiServer({\n  baseDir: import.meta.dirname,\n  schemaPath: \"./schemas\",\n  port: 4000,\n})\n```\n\n`startApiServer` uses `@hono/node-server` to serve the application via Node.js `http` module.\n","description":"Create a configured Hono application with GraphQL Yoga integration, CORS, and logging middleware.","navTitle":"createApiServer","keywords":["createapiserver","create","configured","application","graphql"]}
{"schemaVersion":"1.0.0","docId":"api/server-setup/define-config","source":"api","slug":"server-setup/define-config","path":"/docs/api/server-setup/define-config","raw_path":"/raw/api/server-setup/define-config.md","title":"defineConfig","headings":[{"level":2,"text":"Signature","id":"signature"},{"level":2,"text":"Configuration options","id":"configuration-options"},{"level":2,"text":"Return type","id":"return-type"},{"level":2,"text":"Usage","id":"usage"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/server-setup/define-config/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: defineConfig\nnavTitle: defineConfig\ndescription: Type-safe configuration wrapper that provides createYogaServer and startServer methods.\n---\n\n`defineConfig` is the recommended way to configure your LakeQL API server. It provides full type safety for permissions by inferring catalog, schema, and table names from your generated configs.\n\n## Signature\n\n```ts\nfunction defineConfig<const TConfig extends readonly SchemaConfigEntry[]>(\n  input: TConfig | DefineConfigOptions<TConfig>\n): DefinedApiConfig<TConfig>\n```\n\n## Configuration options\n\nThe `DefineConfigOptions` interface extends `ApiRuntimeConfig` with a required `allConfigs` field:\n\n<InterfaceReference file=\"api/src/config\" name=\"ApiRuntimeConfig\" />\n\n## Return type\n\n`defineConfig` returns a `DefinedApiConfig` object that includes all your config options plus two convenience methods:\n\n```ts\ninterface DefinedApiConfig<TConfig> {\n  // ...all config options\n  allConfigs: TConfig\n  createYogaServer: (logger) => Promise<ApiYoga>\n  startServer: () => Promise<void>\n}\n```\n\n## Usage\n\n### Minimal — pass only configs\n\n```ts path=\"src/config.ts\"\nimport { defineConfig } from \"@lakeql/api/config\"\nimport { allConfigs } from \"./config-registry\"\n\nexport const config = defineConfig(allConfigs)\n```\n\n### Full configuration (matching the template)\n\n```ts path=\"src/config.ts\"\nimport { defineConfig } from \"@lakeql/api/config\"\n\nimport { getUser } from \"./auth\"\nimport { allConfigs } from \"./config-registry\"\nimport { permissions } from \"./permissions\"\n\nconst baseDir = import.meta.dirname\n\nexport const config = defineConfig({\n  allConfigs,\n  baseDir,\n  getUser,\n  graphqlPath: \"/graphql\",\n  healthCheckEndpoint: \"/live\",\n  permissions,\n  port: 4000,\n  schemaPath: \"./schemas\",\n})\n```\n\n### Using startServer\n\n```ts path=\"src/index.ts\"\nimport { config } from \"./config\"\n\nawait config.startServer()\n```\n\nThis calls `startApiServer` internally with your defined configuration.\n","description":"Type-safe configuration wrapper that provides createYogaServer and startServer methods.","navTitle":"defineConfig","keywords":["configuration","defineconfig","type-safe","wrapper","provides"]}
{"schemaVersion":"1.0.0","docId":"api/server-setup/yoga-configuration","source":"api","slug":"server-setup/yoga-configuration","path":"/docs/api/server-setup/yoga-configuration","raw_path":"/raw/api/server-setup/yoga-configuration.md","title":"Yoga Configuration","headings":[{"level":2,"text":"Type","id":"type"},{"level":2,"text":"Available overrides","id":"available-overrides"},{"level":2,"text":"Default behavior","id":"default-behavior"},{"level":2,"text":"Usage","id":"usage"},{"level":2,"text":"Logging configuration","id":"logging-configuration"}],"documentType":"unknown","contentOrigin":"static-doc","canonicalUrl":"/docs/api/server-setup/yoga-configuration/","buildId":"local-1782300258905","generatedAt":"2026-06-24T11:24:18.905Z","content":"---\ntitle: Yoga Configuration\ndescription: Customize GraphQL Yoga options like GraphiQL, error masking, logging, and landing page.\n---\n\nThe `yogaConfig` option in `defineConfig` (or `createApiServer`) lets you override GraphQL Yoga's built-in behavior. It accepts all Yoga options except `schema` and `context`, which are managed internally.\n\n## Type\n\n```ts\ntype YogaConfigOverrides = Omit<\n  NonNullable<Parameters<typeof createYoga>[0]>,\n  \"schema\" | \"context\"\n>\n```\n\n## Available overrides\n\n| Option                | Type                  | Default                   | Description                              |\n| --------------------- | --------------------- | ------------------------- | ---------------------------------------- |\n| `healthCheckEndpoint` | `string`              | `\"/live\"`                 | Path for the health check endpoint       |\n| `graphiql`            | `boolean \\| object`   | `true` in development     | Enable/configure GraphiQL IDE            |\n| `logging`             | `boolean \\| LogLevel` | Based on `API_LOGGER` env | Control Yoga's internal logging          |\n| `maskedErrors`        | `boolean`             | `true`                    | Hide internal error details from clients |\n| `landingPage`         | `boolean`             | `false`                   | Show Yoga's default landing page         |\n\n## Default behavior\n\nWithout any overrides, the server applies these defaults:\n\n- **GraphiQL** is enabled when `NODE_ENV=development`\n- **maskedErrors** is `true` — internal errors are replaced with generic messages\n- **Logging** level is controlled by the `API_LOGGER` environment variable (default: `warn`). Set to `silent` to disable Yoga logging entirely.\n- **Landing page** is disabled\n\n## Usage\n\n```ts path=\"src/config.ts\"\nimport { defineConfig } from \"@lakeql/api/config\"\n\nimport { getUser } from \"./auth\"\nimport { allConfigs } from \"./config-registry\"\nimport { permissions } from \"./permissions\"\n\nconst baseDir = import.meta.dirname\n\nexport const config = defineConfig({\n  allConfigs,\n  baseDir,\n  getUser,\n  permissions,\n  port: 4000,\n  schemaPath: \"./schemas\",\n  yogaConfig: {\n    graphiql: {\n      title: \"LakeQL Explorer\",\n      defaultQuery: \"{ __typename }\",\n    },\n    maskedErrors: false, // expose full errors in development\n    healthCheckEndpoint: \"/healthz\",\n  },\n})\n```\n\n## Logging configuration\n\nThe `API_LOGGER` environment variable accepts these values:\n\n| Value    | Effect                        |\n| -------- | ----------------------------- |\n| `debug`  | All messages                  |\n| `info`   | Info, warnings, and errors    |\n| `warn`   | Warnings and errors (default) |\n| `error`  | Errors only                   |\n| `silent` | No Yoga logging output        |\n\n```bash\n# .env\nAPI_LOGGER=debug\n```\n","description":"Customize GraphQL Yoga options like GraphiQL, error masking, logging, and landing page.","keywords":["configuration","logging","customize","graphql","options"]}