Declare a new entity in .atl, apply it, and regenerate the typed client.
1. Declare the entity
Create a file under a path listed in tide.yaml’s schema_paths:
internal/orders/schema.atlentity Order in shop {
id bigint primary serial
customer_id varchar(8) not null references shop.Customer.id
total numeric(10, 2) not null
created_at timestamptz not null default now()
}2. Plan and apply
tide plan
tide applytide plan reports what would change without mutating. tide apply runs
the migration and updates the merged schema.
3. Regenerate the client
tide generatetide generate rewrites the client under output_dir from the server’s
canonical schema, scoped to the namespaces in generate:. Commit the
result with the .atl change. An old client keeps working without it;
code referring to the new entity does not compile until you regenerate.
Verify
tide show internal/ordersPrints each submitted file whose path contains the argument — here, the file with the new entity as the server now holds it:
--- internal/orders/schema.atl ---
entity Order in shop {
...
}Common patterns
Server-set timestamps with auto-touch
created_at timestamptz not null default now()
updated_at timestamptz not null default now()
touch_on_update by updated_attouch_on_update by installs a Postgres trigger that sets updated_at
on every UPDATE.
Soft delete
deleted_at timestamptz
soft_delete by deleted_atThe generated Delete RPC sets deleted_at to now() instead of
dropping the row. Get and Query filter deleted_at IS NULL
automatically. To read tombstones, declare a
custom query with the
inverse filter.
Per-tenant partition
When one table holds rows for many tenants, name the column that says which tenant a row belongs to:
tenant_id varchar(8) not null
partition by tenant_idThe column must be not null. A NULL in that column matches no tenant’s
policy, so the row becomes invisible to everyone, including whoever wrote
it. Tenant isolation covers what
PostgreSQL enforces, what atlantis trusts, and what does not work on a
partitioned entity.
Common errors
references unknown entity shop.Customer— the referenced entity is not registered yet. Runtide applyfrom the repo that declaresshop.Customerfirst.tide planexits 1 (backfill required) — you added anot nullcolumn without adefaultto an existing entity, or added a compositeunique by a, bto an existing entity, which can fail on existing duplicate tuples. Add adefault, dedupe, or supply backfill SQL.tide planexits 2 (cross-caller breaking) — another caller’s schema depends on a field you removed or renamed. The output names the conflict.
Related
- Tenant isolation — what
partition byenforces and what it trusts. - Use the sandbox — boot a copy of the schema to seed rows, run queries, and diff state before applying.
- DSL grammar — every modifier and entity-level clause.
- DSL types — each
.atltype’s Postgres, Go, and proto representation.