DB Typing Helpers
Type-level helpers for distinguishing tables from views and for expressing different read and write shapes.
Quarry's default DB shape is still plain TypeScript:
interface DB {
users: {
id: number;
email: string;
};
}Plain object sources are treated as regular tables.
Use the helpers on this page when you want more precision than a plain object can express.
TypedView
TypedView<Row> marks a source as selectable but not insertable.
import type { TypedView } from "@oorestisime/quarry";
interface DB {
daily_users: TypedView<{
signup_date: string;
total_users: string;
}>;
}This means:
db.selectFrom("daily_users")is alloweddb.insertInto("daily_users")is rejected by the type systemdb.table("daily_users")is rejected by the type system
TypedTable
TypedTable<Row> is the table counterpart.
import type { TypedTable } from "@oorestisime/quarry";
interface DB {
users: TypedTable<{
id: number;
email: string;
}>;
}You do not need TypedTable for ordinary handwritten schemas because a plain
object already behaves like a table. It is mainly useful for symmetry with
generated introspection output.
TypedDictionary
TypedDictionary<Row> marks a source as a ClickHouse dictionary.
import type { TypedDictionary } from "@oorestisime/quarry";
interface DB {
partner_rates: TypedDictionary<{
rate_cents: number;
currency: string;
}>;
}Dictionaries are not queryable tables. You cannot selectFrom or insertInto
a dictionary. Instead, you use the typed dictGet, dictGetOrDefault, and
dictHas functions from the expression builder.
db.selectFrom("events as e").selectExpr((eb) => [
eb.fn.dictGet("partner_rates", "rate_cents", "e.partner_id").as("rate"),
eb.fn.dictHas("partner_rates", "e.partner_id").as("has_rate"),
]);What the Row interface contains
The Row type in TypedDictionary<Row> contains only attribute columns —
the columns you can retrieve with dictGet. It does not include:
- Primary key columns — these are the lookup input, not a retrievable value.
This applies to all layouts:
HASHED,COMPLEX_KEY_HASHED,RANGE_HASHED,CACHE,DIRECT,IP_TRIE, and every other layout. - Range columns (for
RANGE_HASHEDlayouts) — these are internal engine mechanics used to select the correct row, not queryable attributes.
This matches ClickHouse runtime behavior: dictGet('dict', 'key_col', key)
fails at runtime when 'key_col' is a primary key, not an attribute.
For example, with a RANGE_HASHED dictionary:
CREATE DICTIONARY partner_revenue_dict (
partner_id Int64, -- PRIMARY KEY (not in TypedDictionary)
start_date Date, -- RANGE MIN (not in TypedDictionary)
end_date Date, -- RANGE MAX (not in TypedDictionary)
revenue_per_user Decimal(10, 4), -- attribute
partner_name String -- attribute
)
PRIMARY KEY partner_id
LAYOUT(RANGE_HASHED())
RANGE(MIN start_date MAX end_date)The generated interface will only contain the attributes:
interface DB {
partner_revenue_dict: TypedDictionary<{
revenue_per_user: number;
partner_name: string;
}>;
}ColumnType
ColumnType<Select, Insert, Where> lets a column read as one type while
accepting a broader or different type for inserts and predicates.
import type { ColumnType } from "@oorestisime/quarry";
type MetricText = ColumnType<string, number, number>;
interface DB {
metrics: {
display_value: MetricText;
};
}That means:
- selected rows expose
display_valueasstring - inserts accept
number where("display_value", "=", ...)acceptsnumber
ClickHouse aliases
Quarry exports a small set of ClickHouse-specific aliases built on top of
ColumnType.
Date and time
ClickHouseDate: reads asstring, inserts asstringClickHouseDate32: reads asstring, inserts asstringClickHouseDateTime: reads asstring, inserts asstring | DateClickHouseDateTime64: reads asstring, inserts asstring | Date
Large numeric types
ClickHouseUInt64: reads asstring, inserts asstring | number | bigintClickHouseInt64: reads asstring, inserts asstring | number | bigintClickHouseDecimal: reads asnumber, inserts asnumber | string
These aliases match Quarry's current runtime semantics under JSONEachRow and
the ClickHouse JS client.
Generated output
The CLI introspection flow uses these helpers automatically.
When ClickHouse metadata is broader than your application type, such as a JSON
payload column that should be UserPayload, configure column type overrides in
the introspection guide.
import type {
ClickHouseDate,
ClickHouseDateTime64,
ClickHouseUInt64,
TypedTable,
TypedView,
} from "@oorestisime/quarry";
export interface Users {
id: number;
created_at: ClickHouseDateTime64;
}
export interface DailyUsers {
signup_date: ClickHouseDate;
total_users: ClickHouseUInt64;
}
export interface Tables {
users: TypedTable<Users>;
}
export interface Views {
daily_users: TypedView<DailyUsers>;
}
export interface DB extends Tables, Views {}