Getting started
Installation
npm install @rjdellecese/confect
yarn add @rjdellecese/confect
pnpm add @rjdellecese/confect
exactOptionalPropertyTypes
exactOptionalPropertyTypes
Normally when using the Effect schema library, it's recommended to set exactOptionalPropertyTypes
in your tsconfig.json
to true
. However, this configuration is not supported by convex-js
at the moment, so to use Confect, you must set it to false
instead.
To understand the implications of this, see this explanation in the Effect docs.
Usage
1. Define your database schema
https://github.com/rjdellecese/confect/blob/main/example/convex/schema.ts
import { Id, defineSchema, defineTable } from "@rjdellecese/confect/server";
import { Schema } from "effect";
type Tag = {
readonly name: string;
readonly tags: readonly Tag[];
};
const Tag = Schema.Struct({
name: Schema.String,
tags: Schema.Array(Schema.suspend((): Schema.Schema<Tag> => Tag)),
});
export const confectSchema = defineSchema({
notes: defineTable(
Schema.Struct({
userId: Schema.optional(Id.Id("users")),
text: Schema.String.pipe(Schema.maxLength(100)),
tag: Schema.optional(Schema.String),
author: Schema.optional(
Schema.Struct({
role: Schema.Literal("admin", "user"),
name: Schema.String,
}),
),
embedding: Schema.optional(Schema.Array(Schema.Number)),
}),
)
.index("by_text", ["text"])
.index("by_role", ["author.role"])
.searchIndex("text", {
searchField: "text",
filterFields: ["tag"],
})
.vectorIndex("embedding", {
vectorField: "embedding",
filterFields: ["author.name", "tag"],
dimensions: 1536,
}),
users: defineTable(
Schema.Struct({
username: Schema.String,
}),
),
tags: defineTable(Tag),
});
export default confectSchema.convexSchemaDefinition;
2. Generate your Convex function constructors and types.
https://github.com/rjdellecese/confect/blob/main/example/convex/confect.ts
import {
ConfectActionCtx as ConfectActionCtxService,
type ConfectActionCtx as ConfectActionCtxType,
type ConfectDataModelFromConfectSchemaDefinition,
type ConfectDoc as ConfectDocType,
ConfectMutationCtx as ConfectMutationCtxService,
type ConfectMutationCtx as ConfectMutationCtxType,
ConfectQueryCtx as ConfectQueryCtxService,
type ConfectQueryCtx as ConfectQueryCtxType,
type TableNamesInConfectDataModel,
makeFunctions,
} from "@rjdellecese/confect/server";
import { confectSchema } from "./schema";
export const {
action,
internalAction,
internalMutation,
internalQuery,
mutation,
query,
} = makeFunctions(confectSchema);
type ConfectSchema = typeof confectSchema;
type ConfectDataModel =
ConfectDataModelFromConfectSchemaDefinition<ConfectSchema>;
export type ConfectDoc<
TableName extends TableNamesInConfectDataModel<ConfectDataModel>,
> = ConfectDocType<ConfectDataModel, TableName>;
export const ConfectQueryCtx = ConfectQueryCtxService<ConfectDataModel>();
export type ConfectQueryCtx = ConfectQueryCtxType<ConfectDataModel>;
export const ConfectMutationCtx = ConfectMutationCtxService<ConfectDataModel>();
export type ConfectMutationCtx = ConfectMutationCtxType<ConfectDataModel>;
export const ConfectActionCtx = ConfectActionCtxService<ConfectDataModel>();
export type ConfectActionCtx = ConfectActionCtxType<ConfectDataModel>;
3. Write some Convex functions!
https://github.com/rjdellecese/confect/blob/main/example/convex/functions.ts
import { Effect } from "effect";
import {
ConfectMutationCtx,
ConfectQueryCtx,
action,
mutation,
query,
} from "./confect";
import {
DeleteNoteArgs,
DeleteNoteResult,
GetFirstArgs,
GetFirstResult,
GetRandomArgs,
GetRandomResult,
InsertNoteArgs,
InsertNoteResult,
ListNotesArgs,
ListNotesResult,
} from "./functions.schemas";
export const insertNote = mutation({
args: InsertNoteArgs,
returns: InsertNoteResult,
handler: ({ text }) =>
Effect.gen(function* () {
const { db } = yield* ConfectMutationCtx;
return yield* db.insert("notes", { text });
}),
});
export const listNotes = query({
args: ListNotesArgs,
returns: ListNotesResult,
handler: () =>
Effect.gen(function* () {
const { db } = yield* ConfectQueryCtx;
return yield* db.query("notes").order("desc").collect();
}),
});
export const deleteNote = mutation({
args: DeleteNoteArgs,
returns: DeleteNoteResult,
handler: ({ noteId }) =>
Effect.gen(function* () {
const { db } = yield* ConfectMutationCtx;
return yield* db.delete(noteId).pipe(Effect.as(null));
}),
});
export const getRandom = action({
args: GetRandomArgs,
returns: GetRandomResult,
handler: () => Effect.succeed(Math.random()),
});
export const getFirst = query({
args: GetFirstArgs,
returns: GetFirstResult,
handler: () =>
Effect.gen(function* () {
const { db } = yield* ConfectQueryCtx;
return yield* db.query("notes").first();
}),
});
Example project
The above files are pulled from a real example project. Check it out here.
Last updated