Setup Ents in a new Convex project
Got an existing project? Follow this guide instead.
Install the NPM library
npm install convex-ents convex-helpers
This also installs convex-helpers
, used in step 3.
Create a schema
Create your schema file, for example:
convex/schema.ts
import { v } from "convex/values";
import { defineEnt, defineEntSchema, getEntDefinitions } from "convex-ents";
const schema = defineEntSchema({
messages: defineEnt({
text: v.string(),
})
.edge("user")
.edges("tags"),
users: defineEnt({
name: v.string(),
}).edges("messages", { ref: true }),
tags: defineEnt({
name: v.string(),
}).edges("messages"),
});
export default schema;
export const entDefinitions = getEntDefinitions(schema);
For more details on declaring the schema see Ent Schema.
Setup custom functions
Add a functions.ts
file with the following contents:
Click to show
convex/functions.ts
import { entsTableFactory } from "convex-ents";
import {
customCtx,
customMutation,
customQuery,
} from "convex-helpers/server/customFunctions";
import {
internalMutation as baseInternalMutation,
internalQuery as baseInternalQuery,
mutation as baseMutation,
query as baseQuery,
} from "./_generated/server";
import { entDefinitions } from "./schema";
export const query = customQuery(
baseQuery,
customCtx(async (ctx) => {
return {
table: entsTableFactory(ctx, entDefinitions),
db: undefined,
};
}),
);
export const internalQuery = customQuery(
baseInternalQuery,
customCtx(async (ctx) => {
return {
table: entsTableFactory(ctx, entDefinitions),
db: undefined,
};
}),
);
export const mutation = customMutation(
baseMutation,
customCtx(async (ctx) => {
return {
table: entsTableFactory(ctx, entDefinitions),
db: undefined,
};
}),
);
export const internalMutation = customMutation(
baseInternalMutation,
customCtx(async (ctx) => {
return {
table: entsTableFactory(ctx, entDefinitions),
db: undefined,
};
}),
);
For more details see Configuring Functions.
Add helper types
Add a types.ts
file with the following contents:
Click to show
convex/types.ts
import { GenericEnt, GenericEntWriter } from "convex-ents";
import { CustomCtx } from "convex-helpers/server/customFunctions";
import { TableNames } from "./_generated/dataModel";
import { mutation, query } from "./functions";
import { entDefinitions } from "./schema";
export type QueryCtx = CustomCtx<typeof query>;
export type MutationCtx = CustomCtx<typeof mutation>;
export type Ent<TableName extends TableNames> = GenericEnt<
typeof entDefinitions,
TableName
>;
export type EntWriter<TableName extends TableNames> = GenericEntWriter<
typeof entDefinitions,
TableName
>;
Use custom functions to read and write ents
In any of your Convex backend code, use the custom functions you set up in the
previous step instead of the ones that come from _generated
, which will
provide ctx.table
instead of ctx.db
:
convex/messages.ts
import { v } from "convex/values";
import { mutation, query } from "./functions";
export const list = query(async (ctx) => {
return await ctx.table("messages").map(async (message) => ({
text: message.text,
author: (await message.edge("user")).name,
}));
});
export const send = mutation({
args: { text: v.string(), userId: v.id("users") },
handler: async (ctx, { text, userId }) => {
await ctx.table("messages").insert({ text, userId });
},
});
export const tag = mutation({
args: { messageId: v.id("messages"), tagId: v.id("tags") },
handler: async (ctx, { messageId, tagId }) => {
await ctx
.table("messages")
.getX(messageId)
.patch({ tags: { add: [tagId] } });
},
});
For more details see Reading Ents and Writing Ents.