Overview
The Table Builder API allows you to define database tables and their structure in Zero. Tables are created using the table() function and configured through method chaining.
Functions
table()
Creates a new table definition with the specified name.
function table<TName extends string>(name: TName): TableBuilder
Parameters:
name - The table name (used as the client-side name)
Returns: A TableBuilder instance for further configuration
Example:
const user = table('user')
.columns({
id: string(),
name: string(),
email: string(),
})
.primaryKey('id');
createSchema()
Combines multiple table definitions and relationships into a complete schema.
function createSchema<
const TTables extends readonly TableBuilderWithColumns<TableSchema>[],
const TRelationships extends readonly Relationships[],
>(options: {
readonly tables: TTables;
readonly relationships?: TRelationships | undefined;
readonly enableLegacyQueries?: boolean | undefined;
readonly enableLegacyMutators?: boolean | undefined;
}): Schema
Parameters:
options.tables - Array of table definitions
options.relationships - (Optional) Array of relationship definitions
options.enableLegacyQueries - (Optional) Enable legacy query API
options.enableLegacyMutators - (Optional) Enable legacy mutator API
Returns: A complete schema object
Example:
export const schema = createSchema({
tables: [user, project, issue, comment],
relationships: [userRelationships, issueRelationships],
enableLegacyMutators: false,
enableLegacyQueries: false,
});
TableBuilder Methods
columns()
Defines the columns for the table.
columns<TColumns extends Record<string, ColumnBuilder>>(columns: TColumns): TableBuilderWithColumns
Parameters:
columns - Object mapping column names to column type definitions
Returns: A TableBuilderWithColumns instance
Example:
table('issue')
.columns({
id: string(),
title: string(),
open: boolean(),
created: number(),
projectID: string(),
})
primaryKey()
Defines the primary key for the table. Can be a single column or composite key.
primaryKey<TPKColNames extends (keyof TColumns)[]>(...pkColumnNames: TPKColNames): TableBuilderWithColumns
Parameters:
pkColumnNames - One or more column names that form the primary key
Returns: The configured table builder
Example:
// Single column primary key
table('user')
.columns({ id: string(), name: string() })
.primaryKey('id');
// Composite primary key
table('viewState')
.columns({
issueID: string(),
userID: string(),
viewed: number(),
})
.primaryKey('userID', 'issueID');
from()
Maps the client-side table name to a different server-side table name.
from<ServerName extends string>(serverName: ServerName): TableBuilder
Parameters:
serverName - The server-side table name in PostgreSQL
Returns: The table builder with server name configured
Example:
table('user')
.from('public.users') // Maps to 'users' table in PostgreSQL
.columns({ id: string(), name: string() })
.primaryKey('id');
The public. schema prefix is automatically stripped when creating the replica table.
Complete Example
Here’s a complete example from the zbugs reference app:
import {
createSchema,
table,
string,
number,
boolean,
enumeration,
relationships,
} from '@rocicorp/zero';
const user = table('user')
.columns({
id: string(),
login: string(),
name: string().optional(),
avatar: string(),
role: enumeration<Role>(),
})
.primaryKey('id');
const issue = table('issue')
.columns({
id: string(),
shortID: number().optional(),
title: string(),
open: boolean(),
modified: number(),
created: number(),
projectID: string(),
creatorID: string(),
assigneeID: string().optional(),
description: string(),
visibility: enumeration<'internal' | 'public'>(),
})
.primaryKey('id');
const issueRelationships = relationships(issue, ({ one, many }) => ({
project: one({
sourceField: ['projectID'],
destField: ['id'],
destSchema: project,
}),
creator: one({
sourceField: ['creatorID'],
destField: ['id'],
destSchema: user,
}),
comments: many({
sourceField: ['id'],
destField: ['issueID'],
destSchema: comment,
}),
}));
export const schema = createSchema({
tables: [user, issue],
relationships: [issueRelationships],
enableLegacyMutators: false,
enableLegacyQueries: false,
});
Type Safety
The Table Builder API is fully type-safe. TypeScript will:
- Infer column types from your definitions
- Ensure primary key columns exist in the table
- Validate relationship field references
- Provide autocomplete for column names and methods
Best Practices
- Name Consistency: Use consistent naming between client and server tables when possible
- Primary Keys: Always define a primary key before passing tables to
createSchema()
- Order: Define columns before calling
primaryKey()
- Relationships: Define relationships separately from tables for better organization