The whole thing
Three files.
One backend.
This is real code. Describe what your data looks like, define what your app
does, declare where it runs. Done.
schema.ts — what your data looks like. Who can touch
it.
import { boolean, owner, schema, string, table } from "create-dillo/schema";
export default schema({
Project: table({
name: string().min(1).max(120),
active: boolean().default(true),
}).permissions({
view: owner, // the armor: enforced on every request
edit: owner,
}),
});
backend.ts — what your app does. Functions, validated.
import { defineBackend, defineFunction } from "create-dillo/backend";
import { string } from "create-dillo/schema";
import schema from "./schema.ts";
export default defineBackend({
schema,
functions: {
greet: defineFunction()
.input({ name: string().min(1) })
.output({ message: string() })
.handler((_ctx, input) => ({ message: `Hello ${input.name}` })),
},
});
armadillo.config.ts — where it runs. Your
infrastructure.
import { app } from "create-dillo/deploy/cloudflare";
import backend from "./backend.ts";
export default app({ id: "my-app", backend, signup: "open" });
// Workers + D1, wired. Deploy when you're ready.
client.ts — how your UI calls it. Types flow to your UI. Zero drift.
import { Armadillo } from "create-dillo/client";
import type { Project } from "./schema.ts"; // same types, shared
const api = Armadillo.client({ url: "/api" });
// Fully typed — Project comes straight from your schema.
const projects: Project[] = await api.tables.Project.query().find();
const { message } = await api.functions.greet({ name: "Victor" });
// Change the schema? The client breaks at build time, not runtime.