Skip to content
Guides contents

GuidesQueries & schemas

Refining schema types

Refine generated JSON and string types in a handwritten module that survives schema regeneration.

View as Markdown

Generated schemas describe SQL column kinds and nullability. They cannot infer an application’s JSON interfaces or string literal unions.

refineTable narrows those TypeScript types. refineSchema replaces the table in the assembled schema. Keep both calls in a handwritten module so regeneration does not remove them.

Start with matching SQL columns

This independent example uses the schema generation workflow. Its issue table has these columns:

CREATE TABLE issue (
  id TEXT PRIMARY KEY,
  status TEXT NOT NULL,
  labels JSON NOT NULL,
  meta JSON NOT NULL
);

Apply the migration and run rindle schema gen --out schema.gen.ts. That file exports issue and schema; its columns use string() and json().

Refine the generated table

// schema.ts — handwritten
import { refineSchema, refineTable, json, string } from "@rindle/client";
import { schema as generatedSchema, issue as generatedIssue } from "./schema.gen.ts";

export type Label = "bug" | "feature" | "chore";
export interface Meta { spent: number; estimate: number }

export const issue = refineTable(generatedIssue, {
  status: string<"todo" | "doing" | "done">(),
  labels: json<Label[]>(),
  meta: json<Meta>(),
});

export const schema = refineSchema(generatedSchema, { tables: [issue] });

Build the query builder, backend, mutators, and fragments from this refined schema. Query rows then expose labels as Label[] and meta as Meta. Only refine columns that exist in your generated table.

Keep type assumptions valid

Refinements do not validate stored values. A TypeScript union does not stop a SQL writer from storing another string, and an interface does not validate JSON. Enforce those assumptions through argument validation and database constraints where appropriate. Include background writers in that policy.

The helpers check runtime schema compatibility. refineTable rejects changing a JSON column into a string column. refineSchema checks the table’s column kinds, primary key, and locality against the generated definition. These checks preserve the database shape; they do not prove your narrower value types.

Do not cast columns inside schema.gen.ts. Regeneration overwrites that file. The handwritten module can also use extendSchema to add local-only tables for the browser.