Files
template-app/profiles/mariadb

The mariadb profile

Status (source review 2026-10-21): adapter and migration assets, not a deployable generated app profile. The CLI accepts MariaDB pruning, but only the Convex selection path rewrites the root runtime/deployment composition. The web/worker roots have no MySQL composition, and the previously described profile config file is absent. The platform injects DATABASE_URL; these draft assets still name MARIADB_URL, so their variable contract also needs reconciliation. The tables and generated-app steps below describe the target design. Adapter and migration-engine tests do not establish a generated application deployment.

The persistence profile for an app whose primary store is MariaDB instead of Postgres (spec §5.1): the same procedures, the same modules, the same migrations discipline, on the Kysely MySQL dialect (@conusai-kit/data-kysely/mysql) with jobs on BullMQ over Valkey (@conusai-kit/jobs-bullmq).

postgres (default) mariadb (this profile)
Primary store Postgres 18 MariaDB LTS
Data adapter @conusai-kit/data-kysely @conusai-kit/data-kysely/mysql
Jobs pg-boss (Postgres) BullMQ on Valkey (@conusai-kit/jobs-bullmq)
Tenant wall repository predicate + RLS (three roles, AT-D20) repository predicate alone — MariaDB has no RLS
Links postgres, identity mariadb, redis, identity
Process env POSTGRES_URL, POSTGRES_READER_URL, POSTGRES_MIGRATE_URL MARIADB_URL, REDIS_URL

What this folder carries

  • conusai.json — the profile's deployment contract: the same three services, links mariadb + redis + identity. There is no mariadb.migrate flag: the three-role split it declares on Postgres (data-services DS21) exists to make row-level security bind, and MariaDB has none, so every process holds the one MARIADB_URL and ordering is compose's service_completed_successfully alone (AT-D14).
  • compose.yaml, compose.dev.yaml — the profile's compose files. The dev overlay runs mariadb:lts and valkey/valkey:9-alpine, the images the platform's catalog offers.
  • migrations/ — the kernel tables (tenancy, sign-in, files, proposals, outbox, idempotency keys) as MariaDB DDL, generated by mysqlSchemaStatements from the same entity definitions as the postgres files and committed, forward-only. A module's own MariaDB migrations live with the module: modules/<id>/adapters/kysely/mysql/migrations/, declared in its manifest as migrations: { postgres: "migrations", mariadb: "adapters/kysely/mysql/migrations" }.

Target generated-app changes (not wired)

The profile is chosen at generation time (.conusai/template.json profile), and kit template:prune --profile mariadb removes the other dialects. Concretely:

  1. config/src/env.ts reads MARIADB_URL (every process) instead of the three Postgres URLs — one login, because there is no RLS to bind (AT-D20 is Postgres-only).
  2. apps/web and apps/worker compose createMysqlDataSource (Kysely MysqlDialect + mysql2) and the worker's queue is createBullmqJobQueue/createBullmqWorker over REDIS_URL.
  3. The migrate job runs runMysqlMigrationsFromEnv over profiles/mariadb/migrations/ (moved to migrations/mariadb/) and the modules' adapters/kysely/mysql/migrations/.
  4. This folder's conusai.json, compose.yaml and compose.dev.yaml replace the root ones.

The dialect differences are explicit, never generated at run time (spec §5.3): a module's MariaDB schema is its own numbered SQL folder, reviewed like any migration.

Proof

  • @conusai-kit/data-kysely's MySQL dialect passes the shared repository and capability contract suites against mariadb:lts in testcontainers — the tenant predicate is the wall, and the contract is what proves it.
  • @conusai-kit/jobs-bullmq passes the shared JobQueue contract against valkey/valkey:9 in testcontainers.
  • scripts/vendor/test-template.sh app --profile mariadb runs the template's gate and then applies this folder's and the modules' MariaDB migrations to a real MariaDB LTS container, so the committed DDL is proven against the engine, not just generated.