Collections as Code
Keep your collection schema in a file in your repo, diff it against the live project, apply additive changes, and make function deploys fail when a column your code writes is missing.
Server code that writes to collections (an Amba function, your own backend) assumes the columns it writes exist. Tests that run against an in-memory fake never check that, so a column you forgot to add only fails in production. Collections as code closes that gap: you declare the schema in a file next to your code, and Amba checks the live project against it before a function deploy goes live.
The schema file
Keep amba.collections.json at the root of your repo, next to amba.config.json if
you use environment promotion. Each entry is the same shape
amba_collections_create and POST /v1/admin/projects/:projectId/collections accept:
| Field | Meaning |
|---|---|
columns | Column name, type, nullable (default false), dimension for vector. Server-managed columns are never listed. |
indexes | columns ("col" or "col desc"), optional unique, optional name, optional vector spec. |
shared | true when rows may have no user_id (global content). |
owner_scope | user or group. Fixed when the collection is created. |
read_policy / write_policy | The access policy, as on collection create, including write_policy: "server". Omit a field to leave it unmanaged. |
id, user_id, group_id, created_at, updated_at and deleted_at are added to
every collection automatically and never appear in the file.
Adopt it in one step
An existing project writes its live schema to the file:
Commit the file. From then on, change the schema by editing the file and applying it.
Diff
diff prints two lists:
- Missing from the live project. Collections, columns, indexes, the shared flag,
NULL allowed on a column the file declares
nullablethat isNOT NULLlive, and policies the file declares and the live project lacks.applyadds these. - Differences apply leaves alone. A column type or vector dimension that differs,
a column the file declares
NOT NULLthat is nullable live, an owner scope that differs, a foreign key that differs, an index whose definition differs (uniqueness, column order or direction, vector method or distance), and columns, indexes or collections only the live project has. Each is marked[blocks deploy]when code written against the file can fail against the live project: a type mismatch, or a unique index live that the file declares non-unique.
diff exits with status 1 when the live project is missing anything the file declares,
so it works as a CI gate.
Apply
apply shows the plan, asks for confirmation (pass --yes in CI or any
non-interactive shell), and then:
- creates missing collections with their columns, indexes and policy;
- adds missing columns and indexes to existing collections;
- makes a collection shared when the file declares
shared: true; - drops
NOT NULLon a column the file declaresnullable(drop_not_null, metadata-only and instant); - sets a declared
read_policy/write_policythat differs from the live one, includingwrite_policy: "server"for tables only backend code writes.
Apply is additive. It never drops a column, index or collection, never changes a column's type,
and never adds NOT NULL, since existing NULLs would make that fail. Those differences stay
listed so you can decide on them yourself: amba_collections_alter with change_type or
set_not_null. set_not_null refuses with 409 NULLS_PRESENT and the NULL count while any row
holds NULL in the column.
A column added to a collection that already has rows is added as nullable, because
existing rows have no value for it. If the file declares it NOT NULL, the next diff
lists that nullability difference. It does not block a deploy, since the live column
accepts every value the file allows. Once the rows have values, run set_not_null to
match the file.
Changes run in order and stop at the first failure. A failure (for example a unique
index over rows that already hold duplicates) leaves everything applied before it in
place, and running apply again picks up where it stopped. A unique index whose build failed is listed
as missing until it is rebuilt; once the duplicate rows are fixed, apply drops the
unusable index and builds it again. New collections are created in foreign-key order, so a
collection is created after the collections its columns reference.
Function deploys check the schema
When amba.collections.json exists in the directory you deploy from,
amba functions deploy diffs it against the live project before uploading. If the live
project is missing anything the file declares, the deploy stops and prints the diff.
| Flag | Effect |
|---|---|
--apply-schema | Applies the additive changes, then deploys. Still stops on a difference marked [blocks deploy]. |
--skip-schema-check | Deploys without reading the schema file. |
--schema-file <path> | Checks a different file. The deploy fails when that file does not exist. |
--dry-run deploys stay local and skip the check.
For agents (MCP)
Two tools mirror the CLI. Both take the file's collections array as collections.
amba_collections_diffreturnsoperations(what apply would add),differences(what it leaves alone, each withblocks_deploy),deploy_readyandin_sync. Passinclude_live: trueto also get the live schema in file shape.amba_collections_applyapplies the additive operations. Called withoutconfirm: trueit returns the plan and changes nothing; show it to the person you work with, then repeat the call withconfirm: true.
API
Both endpoints take { "version"?: 1, "collections": [...] }.
POST /v1/admin/projects/:projectId/collections/diff
Optional include_live: true adds live, the current schema in file shape.
op is one of create_collection, add_column, add_index, relax_user_id,
drop_not_null, set_policy; each body is the matching collection create or PATCH body. kind is
one of type_mismatch, vector_dimension_mismatch, nullability_mismatch,
owner_scope_mismatch, shared_mismatch, column_not_declared, index_not_declared,
index_mismatch, foreign_key_mismatch, collection_not_declared.
POST /v1/admin/projects/:projectId/collections/apply
Optional dry_run: true returns the plan without changing anything.
Errors: 400 INVALID_COLLECTIONS_SCHEMA for a malformed schema, 400 SCHEMA_APPLY_FAILED when an operation fails (details.applied lists what ran,
details.failed names the operation and its error), 503 PROJECT_NOT_READY while the
project is still provisioning.