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 two types of search capabilities: full-text search for querying text fields, and vector search for finding semantically similar content using embeddings.

Overview

Full-Text Search

Search text fields with tokenization and ranking

Vector Search

Find similar content using embeddings

Filter Results

Combine search with field filters

Ranked Results

Results ordered by relevance score
Full-text search lets you query text fields using natural language queries.

Defining Search Indexes

Add a search index to your table schema:
schema.ts
import { Table } from "@confect/server";
import { Schema } from "effect";

const articlesTable = Table.make(
  "articles",
  Schema.Struct({
    title: Schema.String,
    content: Schema.String,
    authorId: Schema.String,
    category: Schema.String,
    publishedAt: Schema.Number,
  })
)
  .searchIndex("search_content", {
    searchField: "content",
    filterFields: ["authorId", "category"],
  })
  .searchIndex("search_title", {
    searchField: "title",
  });
Search indexes require:
  • A searchField (the text field to search)
  • Optional filterFields (fields you can filter on)

Performing Searches

Search using the search method:
import { DatabaseReader } from "@confect/server";
import { Effect } from "effect";

const searchArticles = (query: string) =>
  Effect.gen(function* () {
    const db = yield* DatabaseReader<typeof databaseSchema>();
    
    const results = yield* db
      .table("articles")
      .search("search_content", (q) => q.search("content", query))
      .collect();
    
    return results;
  });

Search with Filters

Combine search with field filters:
const searchByAuthor = (query: string, authorId: string) =>
  Effect.gen(function* () {
    const db = yield* DatabaseReader<typeof databaseSchema>();
    
    const results = yield* db
      .table("articles")
      .search("search_content", (q) =>
        q
          .search("content", query)
          .eq("authorId", authorId)
      )
      .collect();
    
    return results;
  });

const searchByCategory = (query: string, category: string) =>
  Effect.gen(function* () {
    const db = yield* DatabaseReader<typeof databaseSchema>();
    
    const results = yield* db
      .table("articles")
      .search("search_content", (q) =>
        q
          .search("content", query)
          .eq("category", category)
      )
      .collect();
    
    return results;
  });
Filter fields must be declared in the search index’s filterFields array.

Limiting Results

const searchTop10 = (query: string) =>
  Effect.gen(function* () {
    const db = yield* DatabaseReader<typeof databaseSchema>();
    
    const results = yield* db
      .table("articles")
      .search("search_content", (q) => q.search("content", query))
      .take(10)
      .collect();
    
    return results;
  });

Search Example Implementation

confect/articles.ts
import { FunctionImpl, DatabaseReader } from "@confect/server";
import { Effect } from "effect";

const search = FunctionImpl.make(
  api,
  "articles",
  "search",
  ({ query, category, limit = 20 }) =>
    Effect.gen(function* () {
      const db = yield* DatabaseReader<typeof databaseSchema>();
      
      let searchQuery = db
        .table("articles")
        .search("search_content", (q) => {
          let builder = q.search("content", query);
          
          if (category) {
            builder = builder.eq("category", category);
          }
          
          return builder;
        });
      
      const results = yield* searchQuery
        .take(limit)
        .collect();
      
      return results.map((article) => ({
        id: article._id,
        title: article.title,
        excerpt: article.content.slice(0, 200),
        category: article.category,
        publishedAt: article.publishedAt,
      }));
    }).pipe(Effect.orDie)
);
Vector search finds semantically similar content using embeddings. It’s available in actions only.

Defining Vector Indexes

Add a vector index to your table schema:
schema.ts
import { Table } from "@confect/server";
import { Schema } from "effect";

const documentsTable = Table.make(
  "documents",
  Schema.Struct({
    text: Schema.String,
    embedding: Schema.Array(Schema.Number),
    category: Schema.String,
    createdAt: Schema.Number,
  })
)
  .vectorIndex("by_embedding", {
    vectorField: "embedding",
    dimensions: 1536, // OpenAI embedding size
    filterFields: ["category"],
  });
Vector indexes require:
  • A vectorField (array of numbers)
  • dimensions (must match your embedding size)
  • Optional filterFields
Use the VectorSearch service in actions:
import { VectorSearch } from "@confect/server";
import { Effect } from "effect";

const findSimilar = (embedding: number[]) =>
  Effect.gen(function* () {
    const search = yield* VectorSearch<typeof databaseSchema>();
    
    const results = yield* search(
      "documents",
      "by_embedding",
      {
        vector: embedding,
        limit: 10,
      }
    );
    
    return results;
  });

Vector Search with Filters

const findSimilarInCategory = (embedding: number[], category: string) =>
  Effect.gen(function* () {
    const search = yield* VectorSearch<typeof databaseSchema>();
    
    const results = yield* search(
      "documents",
      "by_embedding",
      {
        vector: embedding,
        filter: (q) => q.eq("category", category),
        limit: 10,
      }
    );
    
    return results;
  });

Generating Embeddings

Generate embeddings using an AI model:
import { Effect } from "effect";

const generateEmbedding = (text: string) =>
  Effect.gen(function* () {
    const response = yield* Effect.tryPromise(() =>
      fetch("https://api.openai.com/v1/embeddings", {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          model: "text-embedding-3-small",
          input: text,
        }),
      })
    );
    
    const data = yield* Effect.tryPromise(() => response.json());
    
    return data.data[0].embedding as number[];
  });

Semantic Search Example

confect/documents.ts
import { FunctionImpl, VectorSearch, DatabaseReader } from "@confect/server";
import { Effect } from "effect";

const semanticSearch = FunctionImpl.make(
  api,
  "documents",
  "semanticSearch",
  ({ query, limit = 10 }) =>
    Effect.gen(function* () {
      const db = yield* DatabaseReader<typeof databaseSchema>();
      const search = yield* VectorSearch<typeof databaseSchema>();
      
      // Generate embedding for query
      const embedding = yield* generateEmbedding(query);
      
      // Search for similar documents
      const results = yield* search(
        "documents",
        "by_embedding",
        {
          vector: embedding,
          limit,
        }
      );
      
      // Fetch full documents
      const documents = yield* Effect.forEach(
        results,
        (result) =>
          Effect.gen(function* () {
            const doc = yield* db.table("documents").get(result._id);
            return {
              id: doc._id,
              text: doc.text,
              score: result._score,
            };
          })
      );
      
      return documents;
    }).pipe(Effect.orDie)
);

Storing Documents with Embeddings

const addDocument = FunctionImpl.make(
  api,
  "documents",
  "add",
  ({ text, category }) =>
    Effect.gen(function* () {
      const db = yield* DatabaseWriter<typeof databaseSchema>();
      
      // Generate embedding
      const embedding = yield* generateEmbedding(text);
      
      // Store document with embedding
      const documentId = yield* db.table("documents").insert({
        text,
        embedding,
        category,
        createdAt: Date.now(),
      });
      
      return { documentId };
    }).pipe(Effect.orDie)
);
Combine full-text and vector search for better results:
const hybridSearch = FunctionImpl.make(
  api,
  "articles",
  "hybridSearch",
  ({ query, limit = 20 }) =>
    Effect.gen(function* () {
      const db = yield* DatabaseReader<typeof databaseSchema>();
      const search = yield* VectorSearch<typeof databaseSchema>();
      
      // Full-text search
      const textResults = yield* db
        .table("articles")
        .search("search_content", (q) => q.search("content", query))
        .take(limit)
        .collect();
      
      // Vector search
      const embedding = yield* generateEmbedding(query);
      const vectorResults = yield* search(
        "articles",
        "by_embedding",
        { vector: embedding, limit }
      );
      
      // Combine and deduplicate
      const combined = [
        ...textResults.map((r) => ({ ...r, source: "text" })),
        ...vectorResults.map((r) => ({
          ...r,
          source: "vector",
          score: r._score,
        })),
      ];
      
      // Remove duplicates and sort by relevance
      const unique = Array.from(
        new Map(combined.map((r) => [r._id, r])).values()
      ).slice(0, limit);
      
      return unique;
    }).pipe(Effect.orDie)
);

Search Relevance

Both search types return results ordered by relevance:
// Full-text search (implicit score)
const textSearch = yield* db
  .table("articles")
  .search("search_content", (q) => q.search("content", query))
  .collect();
// Results are automatically sorted by relevance

// Vector search (explicit score)
const vectorResults = yield* search("documents", "by_embedding", {
  vector: embedding,
  limit: 10,
});
// Each result has a _score field
console.log(vectorResults[0]._score); // 0.95

Best Practices

1

Choose the Right Search Type

Use full-text for exact keyword matches, vector for semantic similarity.
2

Optimize Index Fields

Only include necessary fields in search and filter fields.
3

Cache Embeddings

Store embeddings to avoid regenerating them on every search.
4

Combine Search Types

Use hybrid search for the best results.
Vector search is only available in actions due to its computational requirements. Use full-text search in queries for real-time results.
Search indexes are built asynchronously. New documents may not appear in search results immediately.

Next Steps

Database

Learn about database queries

Functions

Implement search functions

Node Actions

Generate embeddings in Node

Convex Search

Convex search documentation

Build docs developers (and LLMs) love