import { GenericId } from "../values/index.js"; import { DocumentByName, GenericDataModel, NamedTableInfo, TableNamesInDataModel, } from "./data_model.js"; import { QueryInitializer } from "./query.js"; import { SystemDataModel } from "./schema.js"; import { WithOptionalSystemFields, WithoutSystemFields, } from "./system_fields.js"; interface BaseDatabaseReader { /** * Fetch a single document from the database by its {@link values.GenericId}. * * @param table - The name of the table to fetch the document from. * @param id - The {@link values.GenericId} of the document to fetch from the database. * @returns - The {@link GenericDocument} of the document at the given {@link values.GenericId}, or `null` if it no longer exists. */ get>( table: TableName, id: GenericId>, ): Promise | null>; /** * Fetch a single document from the database by its {@link values.GenericId}. * * @param id - The {@link values.GenericId} of the document to fetch from the database. * @returns - The {@link GenericDocument} of the document at the given {@link values.GenericId}, or `null` if it no longer exists. */ get>( id: GenericId, ): Promise | null>; /** * Begin a query for the given table name. * * Queries don't execute immediately, so calling this method and extending its * query are free until the results are actually used. * * @param tableName - The name of the table to query. * @returns - A {@link QueryInitializer} object to start building a query. */ query>( tableName: TableName, ): QueryInitializer>; /** * Returns the string ID format for the ID in a given table, or null if the ID * is from a different table or is not a valid ID. * * This accepts the string ID format as well as the `.toString()` representation * of the legacy class-based ID format. * * This does not guarantee that the ID exists (i.e. `db.get(id)` may return `null`). * * @param tableName - The name of the table. * @param id - The ID string. */ normalizeId>( tableName: TableName, id: string, ): GenericId | null; } interface BaseDatabaseReaderWithTable { /** * Scope the database to a specific table. */ table>( tableName: TableName, ): BaseTableReader; } export interface BaseTableReader< DataModel extends GenericDataModel, TableName extends TableNamesInDataModel, > { /** * Fetch a single document from the table by its {@link values.GenericId}. * * @param id - The {@link values.GenericId} of the document to fetch from the database. * @returns - The {@link GenericDocument} of the document at the given {@link values.GenericId}, or `null` if it no longer exists. */ get( id: GenericId, ): Promise | null>; /** * Begin a query for the table. * * Queries don't execute immediately, so calling this method and extending its * query are free until the results are actually used. * * @returns - A {@link QueryInitializer} object to start building a query. */ query(): QueryInitializer>; } /** * An interface to read from the database within Convex query functions. * * Available as `ctx.db` in queries (read-only) and mutations (read-write). * You should generally use the `DatabaseReader` type from * `"./_generated/server"`. * * The two entry points are: * - {@link GenericDatabaseReader.get}, which fetches a single document * by its {@link values.GenericId}. * - {@link GenericDatabaseReader.query}, which starts building a query. * * @example * ```typescript * // Fetch a single document by ID: * const user = await ctx.db.get("users", userId); * * // Query documents with an index: * const messages = await ctx.db * .query("messages") * .withIndex("by_channel", (q) => q.eq("channelId", channelId)) * .order("desc") * .take(50); * ``` * * **Best practice:** Use `.withIndex()` instead of `.filter()` for efficient * queries. Define indexes in your schema for fields you query frequently. * * @see https://docs.convex.dev/database/reading-data * @public */ export interface GenericDatabaseReader extends BaseDatabaseReader { /** * An interface to read from the system tables within Convex query functions. * * System tables include `_storage` (file metadata) and * `_scheduled_functions` (scheduled function state). Use `ctx.db.system.get()` * and `ctx.db.system.query()` just like regular tables. * * @example * ```typescript * // Get file metadata from the _storage system table: * const metadata = await ctx.db.system.get("_storage", storageId); * // metadata has: _id, _creationTime, contentType, sha256, size * ``` * * @public */ system: BaseDatabaseReader; } export interface GenericDatabaseReaderWithTable< DataModel extends GenericDataModel, > extends BaseDatabaseReaderWithTable { /** * An interface to read from the system tables within Convex query functions * * The two entry points are: * - {@link GenericDatabaseReader.get}, which fetches a single document * by its {@link values.GenericId}. * - {@link GenericDatabaseReader.query}, which starts building a query. * * @public */ system: BaseDatabaseReaderWithTable; } /** * An interface to read from and write to the database within Convex mutation * functions. * * Available as `ctx.db` in mutations. You should generally use the * `DatabaseWriter` type from `"./_generated/server"`. * * Extends {@link GenericDatabaseReader} * with write operations. All reads and writes within a single mutation are * executed **atomically**, you never have to worry about partial writes * leaving your data in an inconsistent state. * * @example * ```typescript * // Insert a new document: * const userId = await ctx.db.insert("users", { name: "Alice", email: "alice@example.com" }); * * // Update specific fields (shallow merge): * await ctx.db.patch("users", userId, { name: "Alice Smith" }); * * // Replace entire document (all non-system fields): * await ctx.db.replace("users", userId, { name: "Bob", email: "bob@example.com" }); * * // Delete a document: * await ctx.db.delete("users", userId); * * // Delete multiple documents (collect first, then delete each): * const oldTasks = await ctx.db * .query("tasks") * .withIndex("by_completed", (q) => q.eq("completed", true)) * .collect(); * for (const task of oldTasks) { * await ctx.db.delete("tasks", task._id); * } * ``` * * @see https://docs.convex.dev/database/writing-data * @public */ export interface GenericDatabaseWriter extends GenericDatabaseReader { /** * Insert a new document into a table. * * @example * ```typescript * const taskId = await ctx.db.insert("tasks", { * text: "Buy groceries", * completed: false, * }); * ``` * * @param table - The name of the table to insert a new document into. * @param value - The document to insert. System fields (`_id`, `_creationTime`) * are added automatically and should not be included. * @returns The {@link values.GenericId} of the new document. */ insert>( table: TableName, value: WithoutSystemFields>, ): Promise>; /** * Patch an existing document, shallow merging it with the given partial * document. * * New fields are added. Existing fields are overwritten. Fields set to * `undefined` are removed. Fields not specified in the patch are left * unchanged. * * This method will throw if the document does not exist. * * @example * ```typescript * // Update only the "completed" field, leaving other fields unchanged: * await ctx.db.patch("tasks", taskId, { completed: true }); * * // Remove an optional field by setting it to undefined: * await ctx.db.patch("tasks", taskId, { assignee: undefined }); * ``` * * **Tip:** Use `patch` for partial updates. Use `replace` when you want to * overwrite the entire document. * * @param table - The name of the table the document is in. * @param id - The {@link values.GenericId} of the document to patch. * @param value - The partial document to merge into the existing document. */ patch>( table: TableName, id: GenericId>, value: PatchValue>, ): Promise; /** * Patch an existing document, shallow merging it with the given partial * document. * * New fields are added. Existing fields are overwritten. Fields set to * `undefined` are removed. Fields not specified in the patch are left * unchanged. * * This method will throw if the document does not exist. * * @param id - The {@link values.GenericId} of the document to patch. * @param value - The partial document to merge into the existing document. */ patch>( id: GenericId, value: PatchValue>, ): Promise; /** * Replace the value of an existing document, overwriting its old value * completely. * * Unlike `patch`, which does a shallow merge, `replace` overwrites the * entire document. Any fields not included in the new value will be removed * (except system fields `_id` and `_creationTime`). * * This method will throw if the document does not exist. * * @example * ```typescript * // Replace the entire document: * await ctx.db.replace("users", userId, { * name: "New Name", * email: "new@example.com", * }); * ``` * * @param table - The name of the table the document is in. * @param id - The {@link values.GenericId} of the document to replace. * @param value - The new document. System fields can be omitted. */ replace>( table: TableName, id: GenericId>, value: WithOptionalSystemFields>, ): Promise; /** * Replace the value of an existing document, overwriting its old value * completely. * * Unlike `patch`, which does a shallow merge, `replace` overwrites the * entire document. * * @param id - The {@link values.GenericId} of the document to replace. * @param value - The new document. System fields can be omitted. */ replace>( id: GenericId, value: WithOptionalSystemFields>, ): Promise; /** * Delete an existing document. * * @example * ```typescript * await ctx.db.delete("tasks", taskId); * ``` * * @param table - The name of the table the document is in. * @param id - The {@link values.GenericId} of the document to remove. */ delete>( table: TableName, id: GenericId>, ): Promise; /** * Delete an existing document. * * **Note:** Convex queries do not support `.delete()` directly on query * results. To delete multiple documents, `.collect()` them first, then * delete each one individually. * * @param id - The {@link values.GenericId} of the document to remove. */ delete(id: GenericId>): Promise; } /** * An interface to read from and write to the database within Convex mutation * functions. * * You should generally use the `DatabaseWriter` type from * `"./_generated/server"`. * * Convex guarantees that all writes within a single mutation are * executed atomically, so you never have to worry about partial writes leaving * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control) * for the guarantees Convex provides your functions. * * @public */ export interface GenericDatabaseWriterWithTable< DataModel extends GenericDataModel, > extends GenericDatabaseReaderWithTable { /** * Scope the database to a specific table. */ table>( tableName: TableName, ): BaseTableWriter; } export interface BaseTableWriter< DataModel extends GenericDataModel, TableName extends TableNamesInDataModel, > extends BaseTableReader { /** * Insert a new document into the table. * * @param value - The {@link values.Value} to insert into the given table. * @returns - {@link values.GenericId} of the new document. */ insert( value: WithoutSystemFields>, ): Promise>; /** * Patch an existing document, shallow merging it with the given partial * document. * * New fields are added. Existing fields are overwritten. Fields set to * `undefined` are removed. * * @param id - The {@link values.GenericId} of the document to patch. * @param value - The partial {@link GenericDocument} to merge into the specified document. If this new value * specifies system fields like `_id`, they must match the document's existing field values. */ patch( id: GenericId, value: PatchValue>, ): Promise; /** * Replace the value of an existing document, overwriting its old value. * * @param id - The {@link values.GenericId} of the document to replace. * @param value - The new {@link GenericDocument} for the document. This value can omit the system fields, * and the database will fill them in. */ replace( id: GenericId, value: WithOptionalSystemFields>, ): Promise; /** * Delete an existing document. * * @param id - The {@link values.GenericId} of the document to remove. */ delete(id: GenericId): Promise; } /** * This prevents TypeScript from inferring that the generic `TableName` type is * a union type when `table` and `id` disagree. */ type NonUnion = T extends never // `never` is the bottom type for TypeScript unions ? never : T; /** * This is like Partial, but it also allows undefined to be passed to optional * fields when `exactOptionalPropertyTypes` is enabled in the tsconfig. */ type PatchValue = { [P in keyof T]?: undefined extends T[P] ? T[P] | undefined : T[P]; };