# HTTP Source: https://confect.dev/clients/js/http Call your Confect functions over HTTP from any JavaScript runtime. `HttpClient` is an [Effect service](https://effect.website/docs/requirements-management/services/) that wraps Convex's [`ConvexHttpClient`](https://docs.convex.dev/api/classes/browser.ConvexHttpClient). It provides `query`, `mutation`, and `action` methods that accept refs and automatically encode args and decode return values through the schemas defined in your function specs. It works in any JavaScript runtime that supports `fetch`. ## Setup Create the `HttpClient` layer by passing your Convex deployment URL. ```ts theme={null} import { HttpClient } from "@confect/js"; import * as Effect from "effect/Effect"; const HttpClientLive = HttpClient.layer("https://example-123.convex.cloud"); ``` The layer can then be provided to any Effect that uses the `HttpClient` service. ## Calling functions Use the `HttpClient` service inside `Effect.gen` to call your functions with refs, the same way you would with `@confect/react` hooks or `@confect/test`. ```ts theme={null} import { HttpClient } from "@confect/js"; import * as Effect from "effect/Effect"; import refs from "./confect/_generated/refs"; const program = Effect.gen(function* () { const client = yield* HttpClient.HttpClient; const notes = yield* client.query(refs.public.notes.list); const noteId = yield* client.mutation(refs.public.notes.insert, { text: "Hello from the server", }); const result = yield* client.action(refs.public.random.getNumber); }); ``` Each method returns an `Effect` that can fail with `HttpClientError` (wrapping transport-level errors) or `ParseResult.ParseError` (if schema encoding or decoding fails). ## Typed errors When a ref's spec declares an `error` schema, the decoded error is added to the returned `Effect`'s error channel alongside `HttpClientError` and `ParseError`. See [Error Handling](/server/error-handling) for how to declare error schemas. ```ts theme={null} import { HttpClient } from "@confect/js"; import * as Effect from "effect/Effect"; import refs from "./confect/_generated/refs"; const lookup = Effect.gen(function* () { const client = yield* HttpClient.HttpClient; return yield* client.query(refs.public.notes.getOrFail, { noteId }); }); // Effect.Effect ``` Recover from a typed failure with `Effect.catchTag` (or any other `Effect` error combinator). ```ts theme={null} lookup.pipe( Effect.catchTag("NoteNotFound", (error) => Effect.succeed(`Note ${error.noteId} not found.`), ), ); ``` ## Authentication Set or clear the auth token before making authenticated requests. ```ts theme={null} Effect.gen(function* () { const client = yield* HttpClient.HttpClient; yield* client.setAuth(token); const identity = yield* client.query(refs.public.auth.getIdentity); yield* client.clearAuth; }); ``` ## Running programs Provide the `HttpClient` layer when running your program. ```ts theme={null} import { HttpClient } from "@confect/js"; import * as Console from "effect/Console"; import * as Effect from "effect/Effect"; const HttpClientLive = HttpClient.layer("https://example-123.convex.cloud"); const program = Effect.gen(function* () { const client = yield* HttpClient.HttpClient; const notes = yield* client.query(refs.public.notes.list); yield* Console.log(notes); }); Effect.runPromise(program.pipe(Effect.provide(HttpClientLive))); ``` ## Differences from `ConvexHttpClient` | `ConvexHttpClient` | `HttpClient` | | ---------------------------------------- | --------------------------------------------------------------- | | Functions referenced via `api.module.fn` | Functions referenced via `refs` | | Args passed directly to Convex as-is | Args are schema-encoded from `Type` to `Encoded` before sending | | Return values received directly as-is | Return values are schema-decoded from `Encoded` to `Type` | | Methods return `Promise` | Methods return `Effect` with typed errors | # WebSocket Source: https://confect.dev/clients/js/websocket Call your Confect functions and subscribe to reactive queries over a persistent WebSocket connection. `WebSocketClient` is an [Effect service](https://effect.website/docs/requirements-management/services/) that wraps Convex's [`ConvexClient`](https://docs.convex.dev/api/classes/browser.ConvexClient). It provides the same `query`, `mutation`, and `action` methods as [`HttpClient`](/clients/js/http), plus a `reactiveQuery` method that returns a [`Stream`](https://effect.website/docs/stream/introduction/) of live results. It works in any JavaScript environment that supports `WebSocket`. The WebSocket connection is managed as a [scoped resource](https://effect.website/docs/resource-management/scope/)—it is opened when the layer is provided and closed automatically when the scope ends. ## Setup Create the `WebSocketClient` layer by passing your Convex deployment URL. ```ts theme={null} import { WebSocketClient } from "@confect/js"; const WebSocketClientLive = WebSocketClient.layer( "https://example-123.convex.cloud", ); ``` The layer can then be provided to any Effect that uses the `WebSocketClient` service. The underlying WebSocket connection is closed automatically when the layer's scope ends—there is no need to close it manually. ## Calling functions Use the `WebSocketClient` service inside `Effect.gen` to call your functions with refs, the same way you would with `HttpClient`, `@confect/react` hooks, or `@confect/test`. ```ts theme={null} import { WebSocketClient } from "@confect/js"; import * as Effect from "effect/Effect"; import refs from "./confect/_generated/refs"; const program = Effect.gen(function* () { const client = yield* WebSocketClient.WebSocketClient; const notes = yield* client.query(refs.public.notes.list); const noteId = yield* client.mutation(refs.public.notes.insert, { text: "Hello from the server", }); const result = yield* client.action(refs.public.random.getNumber); }); ``` Each method returns an `Effect` that can fail with `WebSocketClientError` (wrapping transport-level errors) or `ParseResult.ParseError` (if schema encoding or decoding fails). ## Reactive queries `reactiveQuery` subscribes to a query over the WebSocket connection and returns a `Stream` that emits a new value whenever the query result changes on the server. ```ts theme={null} import { WebSocketClient } from "@confect/js"; import * as Console from "effect/Console"; import * as Effect from "effect/Effect"; import * as Stream from "effect/Stream"; import refs from "./confect/_generated/refs"; const program = Effect.gen(function* () { const client = yield* WebSocketClient.WebSocketClient; yield* client .reactiveQuery(refs.public.notes.list) .pipe(Stream.runForEach((notes) => Console.log(notes))); }); ``` The underlying WebSocket subscription is cleaned up automatically when the stream's scope ends (for example, when the consuming Effect is interrupted or when an operator like `Stream.take` completes). ## Typed errors When a ref's spec declares an `error` schema, the decoded error is added to the error channel of `query`, `mutation`, `action`, and `reactiveQuery` alongside `WebSocketClientError` and `ParseError`. See [Error Handling](/server/error-handling) for how to declare error schemas. ```ts theme={null} import { WebSocketClient } from "@confect/js"; import * as Effect from "effect/Effect"; import refs from "./confect/_generated/refs"; const lookup = Effect.gen(function* () { const client = yield* WebSocketClient.WebSocketClient; return yield* client.query(refs.public.notes.getOrFail, { noteId }); }); // Effect.Effect ``` For `reactiveQuery`, a typed failure terminates the `Stream` with the decoded value in its error channel—the subscription does not stay open across failures. Recover with `Stream.catchTag` (or any other `Stream` error combinator) to keep the stream alive across typed failures. ```ts theme={null} import { WebSocketClient } from "@confect/js"; import * as Effect from "effect/Effect"; import * as Stream from "effect/Stream"; import refs from "./confect/_generated/refs"; const lookups = Effect.gen(function* () { const client = yield* WebSocketClient.WebSocketClient; return client .reactiveQuery(refs.public.notes.getOrFail, { noteId }) .pipe( Stream.catchTag("NoteNotFound", (error) => Stream.succeed(`Note ${error.noteId} not found.`), ), ); }); ``` ## Authentication Set the authentication token provider before making authenticated requests. `setAuth` accepts an Effect-returning function that is called whenever a token is needed or expires. ```ts theme={null} Effect.gen(function* () { const client = yield* WebSocketClient.WebSocketClient; yield* client.setAuth( ({ forceRefreshToken }) => Effect.promise(() => getToken({ forceRefreshToken })), (isAuthenticated) => Console.log(`Auth state changed: ${isAuthenticated}`), ); const identity = yield* client.query(refs.public.auth.getIdentity); }); ``` The optional second argument is a function that receives the current `isAuthenticated` status and returns an `Effect` to run whenever the authentication state changes. ## Running programs Provide the `WebSocketClient` layer when running your program. ```ts theme={null} import { WebSocketClient } from "@confect/js"; import * as Console from "effect/Console"; import * as Effect from "effect/Effect"; import * as Stream from "effect/Stream"; const WebSocketClientLive = WebSocketClient.layer( "https://example-123.convex.cloud", ); const program = Effect.gen(function* () { const client = yield* WebSocketClient.WebSocketClient; yield* client.reactiveQuery(refs.public.notes.list).pipe( Stream.take(10), Stream.runForEach((notes) => Console.log(notes)), ); }); Effect.runPromise(program.pipe(Effect.provide(WebSocketClientLive))); ``` ## Differences from `ConvexClient` | `ConvexClient` | `WebSocketClient` | | ------------------------------------------------------------- | --------------------------------------------------------------- | | Functions referenced via `api.module.fn` | Functions referenced via `refs` | | Args passed directly to Convex as-is | Args are schema-encoded from `Type` to `Encoded` before sending | | Return values received directly as-is | Return values are schema-decoded from `Encoded` to `Type` | | `query`/`mutation`/`action` return `Promise` | `query`/`mutation`/`action` return `Effect` with typed errors | | `onUpdate` uses callbacks and returns an unsubscribe function | `reactiveQuery` returns a `Stream` with automatic cleanup | | Must call `close()` manually | Connection closed automatically when the layer's scope ends | | `setAuth` takes a `Promise`-returning callback | `setAuth` takes an `Effect`-returning function | # React Source: https://confect.dev/clients/react Use Confect's React hooks to call your functions from the client. `@confect/react` provides drop-in replacements for Convex's React hooks. Each hook automatically encodes your args and decodes return values through the [Effect Schemas](/concepts/schema-restrictions) defined in your [function specs](/concepts/spec-impl-model). You work with the Schema `Type` (decoded) values on both sides—the hooks handle the round-trip to Convex's `Encoded` representation transparently. Functions are referenced via `refs` (from `confect/_generated/refs`) instead of Convex's `api` object. Each ref carries the `args`, `returns`, and (optionally) `error` schemas from the corresponding function spec, which is what enables the automatic encoding and decoding. ## Setup The React provider setup is the same as vanilla Convex. ```tsx src/main.tsx theme={null} import { ConvexProvider, ConvexReactClient } from "convex/react"; import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; const convexClient = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL); ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( , ); ``` ## `useQuery` Encodes args using the spec's `args` schema, passes them to Convex, and decodes the result using the spec's `returns` schema. Returns a `QueryResult`—a tagged union with `Loading`, `Success`, and `Failure` variants. Given this spec: ```ts confect/notes.spec.ts theme={null} import { FunctionSpec, GroupSpec } from "@confect/core"; import * as Schema from "effect/Schema"; import notes from "./_generated/tables/notes"; export default GroupSpec.make().addFunction( FunctionSpec.publicQuery({ name: "list", args: () => Schema.Struct({}), returns: () => Schema.Array(notes.Doc), }), ); ``` The hook accepts `{}` (the `Type` of `Schema.Struct({})`) as args and returns a `QueryResult`. Match it with `QueryResult.match`: ```tsx theme={null} import { QueryResult, useQuery } from "@confect/react"; import refs from "../confect/_generated/refs"; const NoteList = () => { const notes = useQuery(refs.public.notes.list, {}); return QueryResult.match(notes, { onLoading: () =>

Loading…

, onSuccess: (notes) => (
    {notes.map((note) => (
  • {note.text}
  • ))}
), }); }; ``` `QueryResult` also exposes the lower-level predicates `QueryResult.isLoading`, `QueryResult.isSuccess`, and `QueryResult.isFailure` for cases where pattern matching is awkward. ### Typed errors When the ref's spec declares an `error` schema, `useQuery` returns `QueryResult` and `QueryResult.match` requires an `onFailure` handler that receives the decoded typed error. See [Error Handling](/server/error-handling) for how to declare error schemas. ```tsx theme={null} import { QueryResult, useQuery } from "@confect/react"; import refs from "../confect/_generated/refs"; const NoteLookup = ({ noteId }: { noteId: string }) => { const lookup = useQuery(refs.public.notes.getOrFail, { noteId }); return (
{QueryResult.match(lookup, { onLoading: () => "Looking up…", onSuccess: (note) => `Found: ${note.text}`, onFailure: (error) => `Note ${error.noteId} not found.`, })}
); }; ``` Failures that are not declared in the `error` schema are not surfaced as `Failure`. They propagate the same way they do with `convex/react`'s `useQuery` (typically reaching the nearest [error boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)). ### Skipping queries Pass `"skip"` instead of args to disable the query subscription. The hook returns a `Loading` variant whose `skipped` flag is `true`, which lets you distinguish a query that is genuinely in flight from one sitting idle because no args have been provided. ```tsx theme={null} import { QueryResult, useQuery } from "@confect/react"; import refs from "../confect/_generated/refs"; const NoteDetail = ({ selectedId }: { selectedId: string | undefined }) => { const note = useQuery( refs.public.notes.get, selectedId !== undefined ? { id: selectedId } : "skip", ); return QueryResult.match(note, { onLoading: (skipped) =>

{skipped ? "Select a note" : "Loading…"}

, onSuccess: (note) =>

{note.text}

, }); }; ``` ## `usePaginatedQuery` Loads data reactively from a [paginated query](/server/functions#paginated-queries), mirroring the ergonomics of `usePaginatedQuery` from `convex/react`. The ref must come from a spec defined with `FunctionSpec.publicPaginatedQuery` — passing any other ref fails at runtime with an error pointing at the constructor. Args are encoded using the spec's user-args schema (`paginationOpts` is managed by the hook, not the caller), and each loaded page is decoded using the spec's `item` schema. Pass `"skip"` instead of args to disable the query, like `useQuery`. Returns a `PaginatedQueryResult`. The loaded variants — `LoadingFirstPage`, `LoadingMore`, `CanLoadMore`, and `Exhausted` — all carry `results` and `isLoading`, and `CanLoadMore` additionally carries `loadMore`, so the common UI needs only field access and a couple of predicates: ```tsx theme={null} import { PaginatedQueryResult, usePaginatedQuery } from "@confect/react"; import refs from "../confect/_generated/refs"; const PaginatedNoteList = () => { const notes = usePaginatedQuery( refs.public.notes.listByAuthor, { author: "ada" }, { initialNumItems: 10 }, ); return (
    {notes.results.map((note) => (
  • {note.text}
  • ))}
{notes.isLoading &&

Loading…

} {PaginatedQueryResult.isCanLoadMore(notes) && ( )}
); }; ``` `loadMore` lives only on `CanLoadMore`, the one state it can make progress from — the underlying Convex hook exposes it on every status, but calling it while a page is in flight, once the list is exhausted, or after a failure is an intentional no-op there. Narrowing with `isCanLoadMore` (or `match`'s `onCanLoadMore`) makes that statically apparent instead of silently dropping the call. `PaginatedQueryResult` also provides `match` for exhaustive pattern matching, and the predicates `isLoadingFirstPage`, `isLoadingMore`, `isExhausted`, `isLoading`, and `isFailure`. ### Typed errors When the spec declares an `error` schema, the result type gains a `Failure` variant carrying the decoded typed error — errors are values, consistent with `useQuery`. Narrow with `PaginatedQueryResult.isFailure` (or handle `onFailure` in `match`) to reach it: ```tsx theme={null} const notes = usePaginatedQuery( refs.public.notes.listOrFail, {}, { initialNumItems: 10 }, ); if (PaginatedQueryResult.isFailure(notes)) { // `results` holds the pages loaded before the failure, so they can still // be rendered alongside the error. return (

Failed to load more: {notes.error.reason}

); } // notes: narrowed to the non-`Failure` variants — isLoading is available too. ``` Every variant carries `results`, `Failure` included: when a later page fails, the pages already loaded are not discarded. Without an `error` schema, the `Failure` variant is excluded from the type entirely, so no narrowing is needed. Failures not declared in the `error` schema are thrown during render and propagate to the nearest [error boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary). `usePaginatedQuery` requires `convex` 1.36.0 or newer. ## `useMutation` Returns a function that encodes args using the spec's `args` schema, calls the Convex mutation, and decodes the result using the spec's `returns` schema. The returned promise's shape depends on whether the spec declares an `error` schema. ### Without an `error` schema The function returns `Promise`, matching `convex/react`'s `useMutation`. Undeclared failures still reject the promise. ```ts confect/notes.spec.ts theme={null} import { Id } from "./_generated/id"; FunctionSpec.publicMutation({ name: "insert", args: () => Schema.Struct({ text: Schema.String }), returns: () => Id("notes"), }); ``` ```tsx theme={null} import { useMutation } from "@confect/react"; import refs from "../confect/_generated/refs"; const InsertNote = () => { const insertNote = useMutation(refs.public.notes.insert); return ( ); }; ``` ### With an `error` schema When the spec declares an `error` schema, the function returns `Promise>`. Unwrap with `Either.match` (or another `Either` combinator) to handle both branches. Undeclared failures still reject the promise. ```ts confect/notes.spec.ts theme={null} import { Id } from "./_generated/id"; FunctionSpec.publicMutation({ name: "deleteOrFail", args: () => Schema.Struct({ noteId: Id("notes") }), returns: () => Schema.Null, error: () => Schema.Union(NoteNotFound, Forbidden), }); ``` ```tsx theme={null} import { useMutation } from "@confect/react"; import * as Either from "effect/Either"; import refs from "../confect/_generated/refs"; const DeleteNote = ({ noteId }: { noteId: string }) => { const deleteOrFail = useMutation(refs.public.notes.deleteOrFail); const handleClick = async () => { const result = await deleteOrFail({ noteId }); Either.match(result, { onLeft: (error) => console.error(error._tag, error), onRight: () => console.log("deleted"), }); }; return ; }; ``` ## `useAction` Same shape as `useMutation`: returns `Promise` when the ref has no `error` schema, and `Promise>` when it does. ```ts confect/random.spec.ts theme={null} FunctionSpec.publicAction({ name: "getNumber", args: () => Schema.Struct({}), returns: () => Schema.Number, }); ``` ```tsx theme={null} import { useAction } from "@confect/react"; import refs from "../confect/_generated/refs"; const RandomNumber = () => { const getRandom = useAction(refs.public.random.getNumber); const handleClick = () => { void getRandom({}).then(console.log); }; return ; }; ``` ## Differences from vanilla Convex hooks | Vanilla Convex | Confect | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | Functions referenced via `api.module.fn` | Functions referenced via `refs` | | Args passed directly to Convex as-is | Args are schema-encoded from `Type` to `Encoded` before sending to Convex | | Return values received directly from Convex as-is | Return values are schema-decoded from `Encoded` to `Type` before returning | | `useQuery` returns `T \| undefined` | `useQuery` returns `QueryResult` | | Cannot distinguish loading from skipped | `Loading` carries a `skipped: boolean` flag | | `useMutation`/`useAction` always return `Promise` | Refs with an `error` schema return `Promise>`; refs without one still return `Promise` | | `usePaginatedQuery` throws query errors during render | `usePaginatedQuery` returns `PaginatedQueryResult` with declared errors as a `Failure` variant | | Pass `"skip"` to disable query subscription | Same—pass `"skip"` to disable query subscription | # File Naming Conventions Source: https://confect.dev/concepts/file-naming-conventions Recommended patterns for organizing spec, impl, and related files in your confect directory. Confect requires a small number of fixed entry-point files in your `confect/` directory (see [Project Structure](/concepts/project-structure)). Beyond those, organize your API as colocated `*.spec.ts`/`*.impl.ts` pairs, one pair per group. The group's name is the file's path within `confect/` (its stem for top-level groups, the dot-joined directory path for nested groups). ## Spec and impl files Name each group's spec and impl with `.spec.ts` and `.impl.ts` suffixes. Each file must default-export its `GroupSpec` or `GroupImpl`; additional named exports on `.spec.ts` (for example error classes) are fine. A complete pair looks like this. The impl default-imports its sibling spec, passes it to `FunctionImpl.make` and `GroupImpl.make`, and finalizes the resulting layer with `GroupImpl.finalize`: ```ts confect/notes.spec.ts theme={null} import { GroupSpec, FunctionSpec } from "@confect/core"; import * as Schema from "effect/Schema"; import notes from "./_generated/tables/notes"; const list = FunctionSpec.publicQuery({ name: "list", args: () => Schema.Struct({}), returns: () => Schema.Array(notes.Doc), }); export default GroupSpec.make().addFunction(list); ``` ```ts confect/notes.impl.ts theme={null} import { FunctionImpl, GroupImpl } from "@confect/server"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import databaseSchema from "./_generated/schema"; import { DatabaseReader } from "./_generated/services"; import notes from "./notes.spec"; const list = FunctionImpl.make(databaseSchema, notes, "list", () => Effect.gen(function* () { const reader = yield* DatabaseReader; return yield* reader .table("notes") .index("by_creation_time", "desc") .collect(); }).pipe(Effect.orDie), ); export default GroupImpl.make(databaseSchema, notes).pipe( Layer.provide(list), GroupImpl.finalize, ); ``` Run `confect codegen` after adding or changing specs and impls. A file's path within `confect/` determines the public Convex API path it produces—`confect/notes_and_random/notes.spec.ts` becomes `internal.notes_and_random.notes.list`. Renaming a file or directory renames the API path, so treat these paths as part of your API's contract. Some test runners (such as Vitest and Jest) treat `.spec.ts` files as test files by default. If your test runner is picking up Confect spec files, configure it to exclude them. For example, in Vitest you can add `"**/*.spec.ts"` to the `test.exclude` array in your config. ## Native Convex functions When a group wraps [native Convex functions](/server/plain-convex-functions) (for use with components or other libraries), place the plain function definitions in a file named after the group—without a suffix. This puts all three files for a group side by side: ``` confect/ workpool.ts ← native Convex function definitions workpool.spec.ts ← spec (type-only imports from workpool.ts) workpool.impl.ts ← impl (runtime imports from workpool.ts) ``` ## Node actions [Node action](/server/node-actions) groups follow the same `.spec.ts`/`.impl.ts` naming and nesting conventions as any other group. A group is Node-runtime when its spec is built with `GroupSpec.makeNode()` (rather than `GroupSpec.make()`); its impl is otherwise identical to a non-node impl, passing the database schema from `_generated/schema`. Confect emits Convex's `"use node"` directive into the generated module based on the spec. ``` confect/ email.spec.ts email.impl.ts ``` ## Nested groups When a group contains subgroups, place each subgroup's `.spec.ts`/`.impl.ts` pair inside a subdirectory named after the parent group. The parent group itself does not need a spec or impl file—it is composed from its subgroups by their paths. ``` confect/ billing/ invoices.spec.ts ← group path billing.invoices invoices.impl.ts subscriptions.spec.ts ← group path billing.subscriptions subscriptions.impl.ts ``` ## Full example Putting it all together, a project using all of these conventions might look like this: # Packages Source: https://confect.dev/concepts/packages Understand the packages that make up the Confect ecosystem. | Package | Description | | ------------------------------------------------------------------ | ---------------------------------------------------------------- | | [`@confect/core`](https://www.npmjs.com/package/@confect/core) | Shared specs and schemas used by all Confect packages | | [`@confect/cli`](https://www.npmjs.com/package/@confect/cli) | Developer tooling for codegen and sync | | [`@confect/server`](https://www.npmjs.com/package/@confect/server) | Backend bindings to the Convex platform | | [`@confect/js`](https://www.npmjs.com/package/@confect/js) | JavaScript client bindings for any JS runtime | | [`@confect/react`](https://www.npmjs.com/package/@confect/react) | Client-side bindings for React apps | | [`@confect/test`](https://www.npmjs.com/package/@confect/test) | Utilities for testing Confect apps without a live Convex backend | # Project Structure Source: https://confect.dev/concepts/project-structure Understand the structure of a Confect project. ## `confect/` ### `_generated/` #### `components.ts` Contains a typed `components` registry with one entry per Convex [component](https://docs.convex.dev/components) installed via `app.use(...)` in `convex/convex.config.ts`. Use it wherever a component client expects a component reference (e.g. `new Workpool(components.workpool, ...)`). Unlike the `components` export of `convex/_generated/api`, this file is safe to import from your impl files' import graphs. See [Components](/server/components). #### `convexSchema.ts` Contains the Convex deploy-time `SchemaDefinition` (a single `defineSchema({...})` call) assembled from every `confect/tables/*.ts` module. `convex/schema.ts` re-exports its default so Convex's CLI and `convex-test` find it where they expect. You should never import this file directly from your own code — use `_generated/schema.ts` for runtime needs. #### `docs.ts` Contains a named TypeScript `interface` for each table's document — `NotesDoc`, `UsersDoc`, etc. — plus a `Docs` registry mapping each table name to its interface. These are pure types (no runtime code): use them to annotate a value with a table's document type, e.g. `const note: NotesDoc = …`. The `Docs` registry is what lets the `DatabaseReader`/`DatabaseWriter` services hand documents back under their named types. See [Document types](/server/database/schema#document-types). #### `id.ts` Contains a type-constrained `Id` constructor whose only argument is the union of your table names — for example, `Id("users")` returns the `Schema` for an `_id` value in the `users` table. Use this when defining cross-table references (e.g. a `userId: Id("users")` field) and inside spec `args`/`returns` schemas. The type-level constraint catches typos at compile time, replacing the loosely-typed `GenericId.GenericId("users")` form. #### `registeredFunctions/` Contains one module per group (for example `registeredFunctions/notes_and_random/notes.ts`), each exporting that group's functions in a form the Convex CLI can consume. These modules are consumed by the generated `convex/` files; you should not need to import them directly. #### `refs.ts` Contains a single default `Refs` export, which is a map of your Convex functions and their [function names](https://docs.convex.dev/api/modules/server#getfunctionname) and args/returns `Schema`s. Use this to invoke your Convex functions from the client or inside Convex functions using the `*Runner` services (`QueryRunner`, `MutationRunner`, and `ActionRunner`). #### `schema.ts` Contains the runtime `DatabaseSchema` — the codec-lookup view of your tables. Impls import it and pass it to `FunctionImpl.make` / `GroupImpl.make`. It is generated from `confect/tables/*.ts` and intentionally avoids any `convex/server` import so a runtime cold start never evaluates `defineSchema(...)`. Each table's field-schema is constructed lazily on the first access to its `Fields`, `Doc`, or `tableDefinition` and then cached, so a function only pays the schema-construction cost at cold start for the tables it actually touches via `db.table(name)`. Use this when a test or a non-codegen-generated module needs to refer to your `DatabaseSchema` type or value. #### `tables/` Contains one wrapper module per user-authored table (for example `tables/notes.ts` for `confect/tables/notes.ts`). Each wrapper applies the filename-derived table name to the `Table` defined in `confect/tables/.ts` and re-exports it as the default. Specs, impls, and other consumers should default-import from `confect/_generated/tables/` to reach a table's `Doc`, `Fields`, and `tableName` properties — for example, `import notes from "../_generated/tables/notes"` and then `notes.Doc`. #### `services.ts` Contains Effect service wrappers for Convex platform capabilities, scoped to your app's database schema. Use these in your function implementation handlers. See [Services](/concepts/services) for a full list. #### `spec.ts` Contains your assembled Confect spec — every function group, of any runtime (including Node action groups) — used to build `refs.ts`. ### `auth.ts` Optional Corresponds 1:1 with `convex/auth.config.ts`. Use this to [configure auth](https://docs.convex.dev/auth). ### `crons.ts` Optional Expects a default `CronJobs` export defining your [cron jobs](/server/cron-jobs). ### `http.ts` Optional Expects a default Convex `HttpRouter` export. Construct this using Confect's `HttpApi.make` and `@effect/platform`'s [HTTP API modules](https://github.com/Effect-TS/effect/blob/main/packages/platform/README.md#http-api). ### `tables/` Required Defines your database tables, one file per table. The **filename is the table name** — `confect/tables/notes.ts` defines a table called `notes` — so filenames must be valid JS identifiers and may not start with `_` (Convex reserves underscore-prefixed names for system tables). Each module **must default-export a `Table`** (built with `Table.make(...)`); codegen reads `module.default`, validates the filename, and applies it as the table name to produce `_generated/tables/.ts`. Other modules import from the wrapper, not directly from `tables/`. Codegen also scans this directory to produce `_generated/schema.ts` (runtime), `_generated/convexSchema.ts` (deploy), and `_generated/id.ts` (the cross-table `Id` constructor). See [Schema](/server/database/schema). ### `*.spec.ts`/`*.impl.ts` Required Your Convex API is defined as colocated `*.spec.ts`/`*.impl.ts` pairs, one pair per group. Each file's path within `confect/` becomes the group's name. See [File Naming Conventions](/concepts/file-naming-conventions) and [The Spec/Impl Model](/concepts/spec-impl-model). ## Rules * Your `confect/` directory should always be a sibling of your `convex/` directory. * The `confect/_generated/` directory is generated by the Confect CLI. You should never modify files in this directory directly. * Confect treats the `convex` directory as a codegen target. While using Confect, you should never modify files in the `convex` folder directly, except for `tsconfig.json` and `convex.config.ts`. # Schema Restrictions Source: https://confect.dev/concepts/schema-restrictions Understand the restrictions on Effect schemas that can be used in Confect. Not every Effect `Schema` is valid for use in Confect. Remember that an Effect `Schema` looks like this: ```typescript theme={null} type Schema ``` For `Schema`s used in Confect: * `Type` represents the value that you'll be operating on in your code. Any TypeScript type is permitted here. * `Encoded` represents the value that is stored in the database or serialized as the argument/output of a Convex function. This must be a valid [Convex value](https://docs.convex.dev/database/types#convex-values). * `Context` is not currently supported. It should always be `never`. ## Additional caveats ### No-op returns from Convex functions Unlike the vanilla APIs, Convex functions defined with Confect may not return `undefined` or `void`—use `null` (`Schema.Null` as the `returns` validator) instead. Convex coerces `undefined`/`void` returns to `null` anyways—this just makes that more explicit. ```typescript ✅ theme={null} FunctionSpec.publicQuery({ name: "myQuery", args: () => Schema.Struct({}), returns: () => Schema.Null, }); ``` ```typescript ❌ theme={null} FunctionSpec.publicQuery({ name: "myQuery", args: () => Schema.Struct({}), returns: () => Schema.Undefined, }); ``` ```typescript ❌ theme={null} FunctionSpec.publicQuery({ name: "myQuery", args: () => Schema.Struct({}), returns: () => Schema.Void, }); ``` # Services Source: https://confect.dev/concepts/services Understand which Effect services Confect provides. Confect exposes Effect services that wrap Convex platform capabilities. These services are generated in `confect/_generated/services.ts` and are available when implementing your Confect functions. ```ts theme={null} import { ActionRunner, Auth, DatabaseReader, DatabaseWriter, MutationRunner, QueryRunner, Scheduler, StorageReader, StorageWriter, StorageActionWriter, VectorSearch, } from "./_generated/services"; ``` ## Database ### `DatabaseReader` Read documents from the database. ### `DatabaseWriter` Insert, edit, and delete documents from the database. ## Function invocation ### `QueryRunner` Run Confect queries. ### `MutationRunner` Run Confect mutations. ### `ActionRunner` Run Confect actions. ## Storage ### `StorageReader` Retrieve URLs for blobs in storage. ### `StorageWriter` Generate upload URLs and delete blobs. ### `StorageActionWriter` Get and store blobs. ## Authentication ### `Auth` Access the current user's identity. ## Scheduling ### `Scheduler` Schedule Confect functions to run after a delay or at a specific time. ## Search ### `VectorSearch` Query vector indexes for semantic similarity search. ## Convex context ### `QueryCtx` Raw Convex query context. ### `MutationCtx` Raw Convex mutation context. ### `ActionCtx` Raw Convex action context. # The Spec/Impl Model Source: https://confect.dev/concepts/spec-impl-model Understand how Confect separates function interfaces from their implementations. Your Confect API is broken up into two parts: a **spec** and its implementation (or **impl**). ## Spec A spec defines the interface of your Confect API. It is made up of group specs and function specs. Each function spec defines the function's name, arguments schema, and returns schema—but not the function's logic. The `args` and `returns` schemas (and an optional `error` schema) are passed as `() => Schema` callbacks, evaluated lazily on first invocation. Specs are built using `@confect/core`, which means they can be shared between the server and client. This separation is what enables end-to-end schema decoding and encoding: the client knows the exact shape of every function's arguments and return value without importing any server code. Each group spec is the default export of a `*.spec.ts` file. The group's name comes from the file's path—its stem for top-level groups, or the dot-joined directory path for nested groups: ```ts confect/notes_and_random/notes.spec.ts theme={null} import { GroupSpec, FunctionSpec } from "@confect/core"; import * as Schema from "effect/Schema"; import notes from "../_generated/tables/notes"; export default GroupSpec.make().addFunction( FunctionSpec.publicQuery({ name: "list", args: () => Schema.Struct({}), returns: () => Schema.Array(notes.Doc), }), ); ``` ## Impl An impl provides the logic for each function declared in your spec. Each impl is the default export of a `*.impl.ts` file colocated with its sibling spec. It default-imports that sibling spec, passes it to `FunctionImpl.make` and `GroupImpl.make`, and finalizes the resulting layer with `GroupImpl.finalize`: ```ts confect/notes_and_random/notes.impl.ts theme={null} import { FunctionImpl, GroupImpl } from "@confect/server"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import databaseSchema from "../_generated/schema"; import { DatabaseReader } from "../_generated/services"; import notes from "./notes.spec"; const list = FunctionImpl.make(databaseSchema, notes, "list", () => Effect.gen(function* () { const reader = yield* DatabaseReader; return yield* reader .table("notes") .index("by_creation_time", "desc") .collect(); }).pipe(Effect.orDie), ); export default GroupImpl.make(databaseSchema, notes).pipe( Layer.provide(list), GroupImpl.finalize, ); ``` `GroupImpl.finalize` rejects, at compile time, any pipeline that has not provided a `FunctionImpl` for every function declared by the spec. This guarantees that the group is fully implemented before it is handed to the generated `convex/` module. Run `confect codegen` after adding or changing specs and impls. # Introduction Source: https://confect.dev/getting-started/introduction Confect deeply integrates Effect with Convex for end-to-end type-safe full-stack apps. Confect is a framework that deeply integrates [Effect](https://effect.website) with [Convex](https://convex.dev). It's more than just Effect bindings! Confect allows you to: * Define your Convex database schema using Effect schemas. * Write Convex function args and returns validators using Effect's schema library. * Use Confect functions to automatically decode and encode your data according to your Effect schema definitions for end-to-end rich types, from client to function to database (and back). * Use Effect's HTTP API modules to define your HTTP API(s). Includes interactive OpenAPI documentation powered by [Scalar](https://github.com/scalar/scalar). * Access Convex platform capabilities via Effect services. ## Prerequisites It's recommended that you have some familiarity with both Effect and Convex, including the vanilla `convex` APIs, before getting started with Confect. Learn about Effect's core concepts. Learn about Convex's platform and APIs. ## Supported platforms Confect requires Node.js 22 or later, and runs on Linux, macOS, and Windows. On Windows, the `confect` CLI (`confect codegen`, `confect dev`) runs natively. Convex's own [local deployments](https://docs.convex.dev/cli/local-deployments) — anonymous development and self-hosting — depend on a `convex-local-backend` binary that Convex does not publish for Windows, so those workflows need WSL or Docker. Developing against a Convex cloud deployment is unaffected. ## Next steps Install Confect and build your first app. # Quickstart Source: https://confect.dev/getting-started/quickstart Install Confect and build a type-safe Convex app with Effect schemas in minutes. ## Installation ```bash pnpm theme={null} pnpm add convex @confect/core @confect/server @confect/cli @confect/react ``` ```bash npm theme={null} npm install convex @confect/core @confect/server @confect/cli @confect/react ``` ```bash yarn theme={null} yarn add convex @confect/core @confect/server @confect/cli @confect/react ``` ```bash bun theme={null} bun add convex @confect/core @confect/server @confect/cli @confect/react ``` ## Usage Run `convex dev` to set up your Convex dev deployment. Not every Effect `Schema` is valid for use in Confect. See [Schema Restrictions](/concepts/schema-restrictions) for more information about what's permitted and what's not. Define a table — one file per table under `confect/tables/`, where the **filename is the table name** (so filenames must be valid JS identifiers and may not start with `_`). Each file **must default-export** a `Table`; codegen reads `module.default` and applies the filename as the table name. From other modules (e.g. specs), default-import the codegen-emitted wrapper at `confect/_generated/tables/` to reach the table's derived `Schema`s (`notes.Doc`, `notes.Fields`). ```ts confect/tables/notes.ts theme={null} import { Table } from "@confect/server"; import * as Schema from "effect/Schema"; export default Table.make(() => Schema.Struct({ text: Schema.String, }), ); ``` Codegen will assemble every `confect/tables/*.ts` into a `DatabaseSchema` (in `confect/_generated/schema.ts`) and a Convex deploy `SchemaDefinition` (in `confect/_generated/convexSchema.ts`) for you — you never write a `confect/schema.ts`. It also emits a `confect/_generated/id.ts` module exporting a type-safe `Id` constructor (`Id("notes")`) for cross-table references. Create a `GroupSpec` and add some functions to it. ```ts confect/notes.spec.ts theme={null} import { FunctionSpec, GroupSpec } from "@confect/core"; import * as Schema from "effect/Schema"; import { Id } from "./_generated/id"; import notes from "./_generated/tables/notes"; export default GroupSpec.make() .addFunction( FunctionSpec.publicQuery({ name: "list", args: () => Schema.Struct({}), returns: () => Schema.Array(notes.Doc), }), ) .addFunction( FunctionSpec.publicMutation({ name: "create", args: () => Schema.Struct({ text: Schema.String }), returns: () => Id("notes"), }), ); ``` Generate your app's `confect/_generated/` files. ```bash theme={null} confect codegen ``` Create a `GroupImpl` and implement its functions. ```ts confect/notes.impl.ts theme={null} import { FunctionImpl, GroupImpl } from "@confect/server"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import databaseSchema from "./_generated/schema"; import { DatabaseReader, DatabaseWriter } from "./_generated/services"; import notes from "./notes.spec"; const list = FunctionImpl.make(databaseSchema, notes, "list", () => Effect.gen(function* () { const reader = yield* DatabaseReader; return yield* reader .table("notes") .index("by_creation_time", "desc") .collect(); }).pipe(Effect.orDie), ); const create = FunctionImpl.make(databaseSchema, notes, "create", ({ text }) => Effect.gen(function* () { const writer = yield* DatabaseWriter; return yield* writer.table("notes").insert({ text }); }).pipe(Effect.orDie), ); export default GroupImpl.make(databaseSchema, notes).pipe( Layer.provide(list), Layer.provide(create), GroupImpl.finalize, ); ``` `GroupImpl.finalize` only typechecks once every function declared by the spec has a corresponding `FunctionImpl`, so it acts as a per-group completeness check. Run `confect codegen` again to pick up the new impl. Run the `confect dev` command to generate your app's Convex functions. ```bash theme={null} confect dev ``` In another terminal, run the `convex dev` command to start your Convex dev deployment. ```bash theme={null} convex dev ``` Set up the Convex React client and provider. ```tsx src/main.tsx theme={null} import { ConvexProvider, ConvexReactClient } from "convex/react"; import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; const convexClient = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL); ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( , ); ``` Use Confect's React hooks alongside your Confect public refs to call your Confect functions. ```tsx src/App.tsx theme={null} import { QueryResult, useMutation, useQuery } from "@confect/react"; import * as Array from "effect/Array"; import { useState } from "react"; import refs from "../confect/_generated/refs"; const App = () => { const notes = useQuery(refs.public.notes.list, {}); const createNote = useMutation(refs.public.notes.create); const [newNote, setNewNote] = useState(""); return (
    {QueryResult.match(notes, { onLoading: () =>

    Loading…

    , onSuccess: (notes) => Array.map(notes, (note) => (
  • {note.text}
  • )), })}