Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rjdellecese/confect/llms.txt

Use this file to discover all available pages before exploring further.

Confect provides Effect services that wrap Convex platform capabilities. These services give you type-safe, composable access to the database, file storage, scheduling, and authentication.

Available Services

DatabaseReader

Query and read from the database

DatabaseWriter

Insert, update, and delete documents

Storage

Store and retrieve files

Scheduler

Schedule functions to run later

Auth

Access user authentication data

Accessing Services

Services are accessed in function implementations using Effect’s service pattern:
impl/notes/list.ts
import { FunctionImpl } from "@confect/server";
import { Effect } from "effect";
import api from "../../_generated/api";
import { DatabaseReader } from "../../_generated/services";

export const list = FunctionImpl.make(
  api,
  "notes",
  "list",
  () =>
    Effect.gen(function* () {
      // Access the DatabaseReader service
      const reader = yield* DatabaseReader;
      
      // Use the service
      const notes = yield* reader
        .table("notes")
        .index("by_creation_time", "desc")
        .collect();
      
      return notes;
    }).pipe(Effect.orDie),
);
Services are automatically provided by Confect’s runtime. You don’t need to manually provide them in most cases.

DatabaseReader

The DatabaseReader service provides type-safe read access to your database.

Querying Tables

const reader = yield* DatabaseReader;

// Get a single document by ID
const note = yield* reader
  .table("notes")
  .get(noteId);

// Query with an index
const recentNotes = yield* reader
  .table("notes")
  .index("by_creation_time", "desc")
  .take(10);

Query Methods

Get a document by ID.
const note = yield* reader.table("notes").get(noteId);
// Returns: Effect<Document | null>
Query using an index.
const query = reader
  .table("notes")
  .index("by_creation_time", "desc");
Order: "asc" (default) or "desc"
Filter results with a predicate function.
const filtered = yield* reader
  .table("notes")
  .index("by_user")
  .filter((q, note) => q.eq(note.status, "published"))
  .collect();
Collect all results into an array.
const allNotes = yield* reader
  .table("notes")
  .index("by_creation_time")
  .collect();
// Returns: Effect<Document[]>
Take the first N results.
const firstTen = yield* reader
  .table("notes")
  .index("by_creation_time")
  .take(10);
// Returns: Effect<Document[]>
Get the first result.
const firstNote = yield* reader
  .table("notes")
  .index("by_creation_time")
  .first();
// Returns: Effect<Option<Document>>
Get a unique result (for unique indexes).
const user = yield* reader
  .table("users")
  .index("by_email")
  .eq("email", userEmail)
  .unique();
// Returns: Effect<Option<Document>>

System Tables

Access system tables like _storage:
const file = yield* reader
  .table("_storage")
  .get(storageId);
System tables are automatically included and type-safe.

DatabaseWriter

The DatabaseWriter service provides write access to your database.

Insert Documents

const writer = yield* DatabaseWriter;

const noteId = yield* writer.table("notes").insert({
  text: "Hello, World!",
  userId: user.id,
  tags: ["greeting"],
});
System fields (_id, _creationTime) are automatically added by Convex.

Update Documents

Patch (Partial Update)

// Update specific fields
yield* writer.table("notes").patch(noteId, {
  text: "Updated text",
});

// Remove a field by setting it to undefined
yield* writer.table("notes").patch(noteId, {
  userId: undefined,
});

Replace (Full Update)

// Replace the entire document (except system fields)
yield* writer.table("notes").replace(noteId, {
  text: "New text",
  userId: user.id,
  tags: [],
});
replace requires all non-optional fields. Use patch for partial updates.

Delete Documents

yield* writer.table("notes").delete(noteId);

Writer Methods

Insert a new document.
const id = yield* writer.table("notes").insert({
  text: "Hello",
});
// Returns: Effect<GenericId<TableName>>
Partially update a document.
yield* writer.table("notes").patch(noteId, {
  text: "Updated",
});
// Returns: Effect<void>
Replace a document entirely.
yield* writer.table("notes").replace(noteId, {
  text: "New",
  userId: "123",
});
// Returns: Effect<void>
Delete a document.
yield* writer.table("notes").delete(noteId);
// Returns: Effect<void>

Storage

The Storage service provides access to Convex’s file storage.

StorageReader

Available in queries and mutations:
import { Storage } from "@confect/server";

const reader = yield* Storage.StorageReader;

// Get a URL for a stored file
const url = yield* reader.getUrl(storageId);

StorageWriter

Available in mutations:
const writer = yield* Storage.StorageWriter;

// Generate an upload URL
const uploadUrl = yield* writer.generateUploadUrl();

// Delete a file
yield* writer.delete(storageId);

StorageActionWriter

Available in actions for direct file operations:
const storage = yield* Storage.StorageActionWriter;

// Get file contents
const blob = yield* storage.get(storageId);

// Store a file
const storageId = yield* storage.store(blob);
Use StorageActionWriter for server-side file processing. For client uploads, use generateUploadUrl.

Scheduler

The Scheduler service allows you to schedule functions to run later.

Schedule After Duration

import { Scheduler } from "@confect/server";
import { Duration } from "effect";
import { api } from "../_generated/api";

const scheduler = yield* Scheduler;

// Schedule a function to run in 1 hour
yield* scheduler.runAfter(
  Duration.hours(1),
  api.notes.sendReminder,
  { noteId }
);

Schedule At Specific Time

import { DateTime } from "effect";

const scheduler = yield* Scheduler;

// Schedule for a specific date/time
const tomorrow = DateTime.now().pipe(
  DateTime.addDays(1)
);

yield* scheduler.runAt(
  tomorrow,
  api.notes.archive,
  { noteId }
);
Scheduled functions must be mutations or actions, not queries.

Auth

The Auth service provides access to user authentication.

Get User Identity

import { Auth } from "@confect/server";

const auth = yield* Auth;

// Get the current user's identity
const identity = yield* auth.getUserIdentity;

// Access user properties
const userId = identity.subject;
const email = identity.email;
const name = identity.name;

Handle Unauthenticated Requests

import { Effect } from "effect";

const identity = yield* auth.getUserIdentity.pipe(
  Effect.catchTag("NoUserIdentityFoundError", () =>
    Effect.fail(new Error("User must be authenticated"))
  )
);
getUserIdentity fails with NoUserIdentityFoundError if the user is not authenticated.

Custom Claims

Access custom claims from your auth provider:
import { UserIdentity } from "@confect/core";
import { Schema } from "effect";

// Define custom claims schema
const CustomClaims = UserIdentity.UserIdentity(
  Schema.Struct({
    role: Schema.Literal("admin", "user"),
  })
);

// Access custom claims
const identity = yield* auth.getUserIdentity;
const role = identity.role; // "admin" | "user"

Service Composition

Services compose naturally with Effect:
import { FunctionImpl } from "@confect/server";
import { Effect } from "effect";
import api from "../../_generated/api";
import { DatabaseReader, DatabaseWriter, Auth } from "../../_generated/services";

export const create = FunctionImpl.make(
  api,
  "notes",
  "create",
  ({ text }) =>
    Effect.gen(function* () {
      // Use multiple services together
      const auth = yield* Auth;
      const reader = yield* DatabaseReader;
      const writer = yield* DatabaseWriter;
      
      // Get authenticated user
      const identity = yield* auth.getUserIdentity;
      
      // Check user hasn't exceeded quota
      const noteCount = yield* reader
        .table("notes")
        .index("by_user")
        .eq("userId", identity.subject)
        .collect()
        .pipe(Effect.map((notes) => notes.length));
      
      if (noteCount >= 100) {
        return yield* Effect.fail(new Error("Note quota exceeded"));
      }
      
      // Create the note
      const noteId = yield* writer.table("notes").insert({
        text,
        userId: identity.subject,
      });
      
      return noteId;
    }).pipe(Effect.orDie),
);

Service Availability

Different services are available in different function types:
ServiceQueryMutationActionNode Action
DatabaseReader
DatabaseWriter
StorageReader
StorageWriter
StorageActionWriter
Scheduler
Auth
Actions and Node actions can call mutations to access the database indirectly.

Error Handling

Services return Effects that can fail:
const note = yield* reader
  .table("notes")
  .get(noteId)
  .pipe(
    Effect.flatMap((maybeNote) =>
      maybeNote === null
        ? Effect.fail(new Error("Note not found"))
        : Effect.succeed(maybeNote)
    )
  );
Or use Option for nullable results:
import { Option } from "effect";

const maybeNote = yield* reader
  .table("notes")
  .index("by_creation_time")
  .first();
// Returns: Option<Document>

if (Option.isSome(maybeNote)) {
  const note = maybeNote.value;
  // Use note
}

Next Steps

Schema Restrictions

Learn about Effect schema limitations in Confect

Spec-Impl Model

Understand how to use services in implementations

Build docs developers (and LLMs) love