convex → briven
port a convex.dev project onto briven. follow the ten-step playbook on /migration — this page documents only the convex-specific parts.
schema port — the 90% rules
convex types map to briven schema dsl as follows:
// convex/schema.ts
defineTable({
email: v.string(),
status: v.union(v.literal('pending'), v.literal('active')),
createdAt: v.int64(),
ownerId: v.id('users'),
isPrimary: v.boolean(),
});
// briven/schema.ts
import { bigint, boolean, schema, table, text } from '@briven/cli/schema';
export default schema({
notes: table({
columns: {
email: text().notNull(),
status: text().notNull(), // union → text + app-level validation
createdAt: bigint().notNull(), // int64 / number → bigint (ms-since-epoch)
ownerId: text().references('users', 'id'), // v.id() → text + foreign key
isPrimary: boolean().notNull(),
},
}),
});v.union(v.literal(...))— no enum helper today; usetext()and validate at the function layer.v.int64()+v.number()for timestamps and money in cents both →bigint().v.id('users')→text().references('users', 'id').v.optional(X)→ drop the.notNull().- convex's implicit
_creationTimedoesn't carry over — add an explicitcreatedAt: bigint().notNull()if you need it. - indexes that convex declares with
.index("by_owner", ["ownerId"])move to the table'sindexes: [{ columns: ['ownerId'] }]array.
functions port
convex's query() / mutation() / action() map 1:1 onto the same names from @briven/cli/server:
// convex/notes.ts
export const getNotes = query({
args: { authorId: v.id('users') },
handler: async (ctx, args) => {
return await ctx.db.query('notes').withIndex('by_owner', q => q.eq('ownerId', args.authorId)).collect();
},
});
// briven/functions/getNotes.ts
import { query, type Ctx } from '@briven/cli/server';
export default query(async (ctx: Ctx, args: { authorId: string }) => {
return await ctx.db('notes').select().where({ ownerId: args.authorId });
});differences to know about up front:
- file = function name. briven discovers functions by file basename; export the handler as
default. one function per file. convex packs many into one file, so you'll split. - no validators in the wrapper. convex's
args:schemas don't exist; validate with zod inside the handler. - ctx.db is a typed query builder, not a sql escape hatch. see /functions for the surface. for the rare query the builder doesn't cover, use
ctx.db.execute('…', params). - no scheduler primitives yet. convex's
ctx.scheduler.runAfter(...)isn't in briven phase 1; use apg_cronentry or a brief sleep-and-poll loop in an action() handler until the scheduler lands.
data export from convex
convex ships an export command that dumps every table to a single zip:
npx convex export --path ./convex-backup-$(date +%Y%m%d).zipunzip and treat each per-table json file as a stream — the rows match the briven column names you defined above. the import path is currently a small node script; briven import --from-convex <zip> arrives with the public beta.
auth port
convex auth (clerk / auth0 / custom) doesn't carry over — briven uses Better Auth with magic-link + email/password + GitHub OAuth out of the box. plan for a one-time forced sign-in on the cutover; users keep their email-as-identity but get a fresh session.
if you need to preserve userIdstability across the cut, set the briven user's id to the convex user id during the data-import step rather than letting briven mint a new ULID.
reactivity
briven's useQuery("getNotes", args)on the client matches convex's shape — same hook signature. under the hood briven runs LISTEN/NOTIFY per touched table; convex uses its mutation log. tail latency is comparable; for burst patterns where convex's log shines, briven realtime is a refactor target, not a regression today.