Deploying
Download .mdDeploying
In production there's often no shell step to run weave push — you build a new
container image, swap it in, and restart. The schema change has to happen as the
app comes up: migrate, then serve. That's what pushAll is for.
pushAll is the project-level push as a plain function you call from your app's boot
before it starts serving. It lives in the main SDK entry, next to pushEntities and
pushScopes.
pushAll — push from your boot
You already import your entities and scopes to build the app — hand those objects
straight to pushAll. There's no file discovery, so it runs identically in dev
(.ts sources) and in a built image (.js).
import { pushAll } from "@mauroandre/weave-sdk";
import * as entities from "./weave/entities/index.js"; // Record<string, Entity>
import * as scopes from "./weave/scopes/index.js"; // {} when there are none
const { applied, review, scopes: pushed } = await pushAll({
url: process.env.WEAVE_URL!,
key: process.env.WEAVE_KEY!,
entities,
scopes, // optional — omit (or pass {}) if the project has no scopes
});It orders entities so a referenced one is applied before the one that references
it, sends them to the server, and — only once the entities converge — pushes the
scopes. It returns what was applied, the review still waiting on a decision,
and the scopes pushed.
A push is one atomic unit of intent: the whole project — entities and scopes — as
the deploy wants it. Unlike weave push, pushAll never writes back to your
files (gen is a CLI concern; the container is ephemeral).
Two differences from the CLI worth knowing. pushAll registers a pending migration
the dashboard can resolve (see below) — weave push --no-gen does not, so the two are
not equivalent despite the similar intent. And there is no renames option: a field
rename at boot degrades to drop-and-add and blocks the deploy on a 🔴 gate, so do renames
from a terminal with weave push --rename before shipping the image.
Migrate, then serve
The idiom is a short loop at boot: push, and start serving only when nothing is
left to review. A push that hits a destructive change doesn't fail — the server
holds those changes as a pending migration and reports them in review.
async function boot() {
for (;;) {
const { review } = await pushAll({ url, key, entities, scopes, source: "boot" });
if (review.length === 0) break; // converged — safe to serve
console.log(`waiting on ${review.length} change(s) — resolve in the dashboard`);
await sleep(5000); // hold; a human resolves it in the GUI
}
startServer();
}The new image never serves a half-applied schema: it either converges and starts, or it waits — visibly — for a human to resolve the risky part.
Resolving without a human
If the deploy pipeline already knows the answer, resolve it non-interactively —
pass confirm and fill the same way the CLI flags do, keyed by entity. The push
then converges on the first try.
await pushAll({
url, key, entities, scopes,
confirm: { product: ["legacy"] }, // ok to drop product.legacy
fill: { product: { sku: "N/A" } }, // backfill the new required product.sku
});Resolving in the dashboard
When a push can't converge on its own and no answer was supplied, the held changes
surface as a pending migration banner across the dashboard. Opening it shows
every waiting change, grouped by entity, with the same risk markers — 🔴 confirm a
drop, 🟡 give a value for existing rows, ⛔ can't apply here (revert in code).
Applying releases the deploy: the next pushAll in the boot loop sees an empty
review and serves.
Only the last pushAll matters. The server keeps a single pending slot, so a
newer deploy's push replaces whatever the previous one was waiting on — you always
resolve against the image that's actually trying to come up.
The server's environment
Your app needs WEAVE_URL and WEAVE_KEY. The server reads these:
| Var | |
|---|---|
DATABASE_URL |
the Postgres it manages. Required — the server won't start without it |
SESSION_SECRET |
signs the dashboard session. Required, no default — it used to fall back to a literal, which meant anyone could forge a session; now the server refuses to start instead. Use a long random string (openssl rand -hex 32) |
MASTER_USERNAME / MASTER_PASSWORD |
the dashboard login. Also what re-seeds the master user after a reset() — leave them set, or a reset locks you out of the dashboard |
WEAVE_API_KEY |
optional fixed god key straight from the environment. It survives a reset() (dashboard keys don't), which is why test suites use it |
WEAVE_DEV_MODE |
truthy enables weave.reset(). Never set in production |
WEAVE_ID_TYPE |
uuid (default) or objectId — see below. Set-once |
PORT |
defaults to 3000 |
PLATFORM_DATABASE_URL |
overrides DATABASE_URL when set. Only useful if you're pointing the control plane somewhere else; if a deploy reads the wrong database, look here first |
Migrating from MongoDB — `WEAVE_ID_TYPE`
Weave ids are UUID v7 by default. Migrating a MongoDB app, every id is a 24-hex ObjectId string, and your data (and every front-end link) references those strings — so brand-new UUIDs would break every link at cutover.
Set WEAVE_ID_TYPE=objectId on the server and Weave keeps the ObjectId shape end to
end: the id and all foreign-key columns become char(24), and new rows get an
ObjectId-compatible id (byte-exact Mongo layout — timestamp + random + counter, unique
and time-ordered, no bson dependency).
The cutover:
WEAVE_ID_TYPE=objectId # on the Weave server- Boot with the env set, then push your schema — tables are created with
char(24)ids. - Bulk-insert your Mongo documents with their original ids (Weave honours a supplied
id) — every foreign key still matches, so the links hold. - New rows written after the cutover get a generated ObjectId.
WEAVE_ID_TYPE is a fixed instance property — it describes your entities' id scheme
and must be chosen before the database has data (it can't switch on a live database).
Default is uuid; leave it unset for a normal deployment.