Skip to main content
ExuluDatabase has two entry points — init() and update() — that share the same options. Both are idempotent; call either on every boot without risk to existing data.

init() and update()

Both methods call the same underlying routine. init() communicates “setting up from scratch”; update() communicates “adding to an existing schema”. Pick the name that best describes your intent — the behaviour is identical.

Options

contexts
ExuluContext[]
required
Array of ExuluContext instances for which to create or verify database tables. Pass all contexts your application uses, not just new ones — the routine is idempotent and skips tables that already exist.
litellm
boolean
default:"true"
When true (the default), also runs initLitellmDb(), which executes a guarded prisma db push against the database pointed to by LITELLM_DATABASE_URL. Set to false if you are not running the LiteLLM proxy, or if you manage the LiteLLM schema separately.

What init() creates

Core tables

init() iterates over every known schema and either creates the table or adds any missing columns. The core schemas created or updated on every run include (plural table names as stored in Postgres): All core tables include id (UUID primary key), createdAt, and updatedAt timestamp columns.

Default roles

Created only when absent:

Default admin user

Created at email: "admin@exulu.com" with password admin (bcrypt-hashed), super_admin: true, and the admin role. Created only when no user with that email exists.

Default API key

init() calls generateApiKey("exulu", "api@exulu.com") at the end of every run. The key is logged to stdout. It is persisted in the users table only on the first call — subsequent calls find the existing api@exulu.com user and return a freshly generated key that is never stored. See API reference — persistence semantics.

Per-context tables

For each ExuluContext in the contexts array, init() calls three idempotent helpers:
  1. Items tablecontext.tableExists() returns false → context.createItemsTable(). The table name defaults to {context.id}_items and includes the context’s field schema plus the standard id, createdAt, updatedAt columns.
  2. Chunks table — Only when context.embedder is configured. context.chunksTableExists() returns false → context.createChunksTable(). The chunks table stores the content text, an embedding VECTOR(N) column (where N is the model’s vectorDimensions), metadata JSONB, and a foreign key to the items table.
  3. Entity tables — Calls ensureEntityTables(context), which is a no-op when the entity layer is not configured on the context.
The pgvector column dimension is fixed at table creation time. Changing the embedding model after context tables exist requires manually recreating or migrating the chunks table. See Self-Hosting — Database for the migration path.

Adding a new context

When you add a new context to an existing application, pass it alongside your existing contexts:
The existing context’s tables are skipped (already exist); only newContext’s tables are created.

LiteLLM schema

When litellm is not false, initLitellmDb() runs prisma db push against LITELLM_DATABASE_URL. This keeps the LiteLLM proxy schema current without a separate migration step. Pass litellm: false when:
  • You are not running the LiteLLM proxy.
  • LITELLM_DATABASE_URL is not set.
  • You manage the LiteLLM schema outside of IMP’s boot sequence.

Complete initdb example

Environment variables

ExuluDatabase reads two environment variables:
DATABASE_URL
string
required
PostgreSQL connection string used by the Knex client. Format: postgresql://user:password@host:port/database.
NEXTAUTH_SECRET
string
required
Secret used for bcrypt password hashing and encrypted variable storage. Generate with openssl rand -base64 32.

Next steps

API reference

Full method signatures and api.key.generate() persistence semantics.

Self-hosting — database

Backups, the no-migration model, and the first-boot API key warning.