Skip to main content

Documentation Index

Fetch the complete documentation index at: https://confect.dev/llms.txt

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

The DatabaseReader service is used to read documents from the database.
import { Effect } from "effect";
import { DatabaseReader } from "./confect/_generated/services";

Effect.gen(function* () {
  const reader = yield* DatabaseReader;

  return yield* reader
    .table("notes")
    .index("by_creation_time")
    .collect();
});

Retrieve a single document

By ID

reader.table("notes").get(noteId);

By index

Single field

reader.table("notes").get("by_author", "John Doe");

Multiple fields

reader.table("users").get("by_name_and_age", "John Doe", 21);

Retrieve multiple documents

Indexes

Every query must specify either a standard index or a search index.

Standard indexes

Standard indexes are what you’ll use most of the time. There are two default standard indexes, which exist for every table: by_creation_time and by_id. The rest are defined in your schema. Standard indexes determine both the sort order of the results and which fields may be filtered on. Fields must be filtered in the order they are defined in the index. Order is ascending by default, but can be specified in the final argument to the index method.
Users sorted by name and age, ascending
reader.table("users").index("by_name_and_age");
Users sorted by name and age, descending
reader.table("users").index("by_name_and_age", "desc");
Users sorted by name and age, where name equals "John Doe", ascending
reader
  .table("users")
  .index("by_name_and_age", (q) => q.eq("name", "John Doe"));
Users sorted by name and age, where name equals "John Doe", and age equals 21, descending
reader
  .table("users")
  .index(
    "by_name_and_age",
    (q) => q.eq("name", "John Doe").eq("age", 21),
    "desc",
  );

Search indexes

Search indexes are used for full-text search, and are defined in your schema. The results are always sorted by relevance, descending.
reader
  .table("notes")
  .search("text", (q) =>
    q.search("text", "hello").eq("tag", "colors"),
  );

Methods

The following methods are available for queries using both database indexes and search indexes.

Collect

Collect all documents matching the query.
reader.table("notes").index("by_creation_time").collect();

First

Retrieve the first document matching the query.
reader.table("notes").index("by_creation_time").first();

Take

Take the first n documents matching the query.
reader.table("notes").index("by_creation_time").take(10);

Paginate

Retrieve a page of documents matching the query. Expects a PaginationOptions object.
reader
  .table("notes")
  .index("by_creation_time")
  .paginate(paginationOptions);

Stream

Get a Stream of documents matching the query.
reader.table("notes").index("by_creation_time").stream();

Accessing the current time

Convex queries are cached based on the data they read. Calling Date.now() directly in a query reads real wall-clock time, which invalidates the cache and causes unnecessary re-execution — Convex’s own best practices advise against it. To keep queries cacheable by default, confect stubs Date.now() to return 0 for the span of a query handler. If you need the current time inside a query, reach for Effect’s Clock service instead:
import { Clock, Effect } from "effect";
import { DatabaseReader } from "./confect/_generated/services";

Effect.gen(function* () {
  const reader = yield* DatabaseReader;
  const rows = yield* reader
    .table("notes")
    .index("by_creation_time")
    .collect();
  const fetchedAt = yield* Clock.currentTimeMillis;

  return { fetchedAt, rows };
});
Reading Clock.currentTimeMillis (or Clock.currentTimeNanos) returns the real timestamp. Note that this is an opt-in to cache invalidation: any query that reads the clock will frequently bust the cache and be re-executed, rather than reliably served from cache. This matches Convex’s behavior for any query that observes real time. If you don’t actually need sub-second accuracy, prefer Convex’s recommended patterns — passing a coarse timestamp as a query argument, or maintaining a boolean/status field updated by a scheduled function — to preserve caching. Mutations and actions are unaffected: Date.now() works as expected there, and the Clock service returns the real timestamp without any caching concerns.