Tables
Per-resource API handles for queries and mutations.
Overview
A DatafnTable is a per-resource handle that provides query, mutation, transaction, signal, and subscription methods. Every method automatically injects the resource name and version from your schema, so you never need to specify them manually.
Accessing a Table
table() Method
const todos = client.table("todos");Property Accessor
Table handles are also available as direct properties on the client, keyed by resource name:
const todos = client.todos;Both accessors return the same cached instance. Table handles have stable object identity -- calling client.table("todos") multiple times returns the same object.
DatafnTable Interface
interface DatafnTable<S, Name, TRecord> {
/** Resource name from schema. */
name: Name;
/** Resource version from schema. */
version: number;
/** Execute a query scoped to this resource. */
query(q: DfqlQueryFragment): Promise<unknown>;
/** Execute one or more mutations scoped to this resource. */
mutate(m: DfqlMutationFragment | DfqlMutationFragment[]): Promise<unknown>;
/** Execute a transaction. */
transact(payload: DfqlTransact): Promise<unknown>;
/** Create a reactive signal for a query on this resource. */
signal(q: DfqlQueryFragment, options?: { disableOptimistic?: boolean }): DatafnSignal<unknown>;
/** Subscribe to events scoped to this resource. */
subscribe(handler: EventHandler, filter?: EventFilter): () => void;
}Auto-Merging
When you call methods on a table handle, the handle merges resource and version into your payload. You only provide the query or mutation fragment:
// You write:
await client.table("todos").query({
filters: { completed: { eq: false } },
limit: 10,
});
// The table handle sends:
// {
// resource: "todos",
// version: 1,
// filters: { completed: { eq: false } },
// limit: 10,
// }The same applies to mutations:
// You write:
await client.table("todos").mutate({
operation: "insert",
record: { title: "New todo", completed: false },
});
// The table handle sends:
// {
// resource: "todos",
// version: 1,
// operation: "insert",
// id: "td:generated-uuid", <-- auto-generated from idPrefix
// record: { title: "New todo", completed: false },
// }Auto-Generated IDs
For insert operations, if no id is provided, the table handle generates one using the resource's idPrefix from the schema. The default generator uses crypto.randomUUID() prefixed with {idPrefix}:. You can supply a custom generator via the generateId config option.
Scoped Subscriptions
When you call subscribe() on a table handle, the resource filter is automatically set to the table's resource name:
// Only receives events for the "todos" resource
const unsubscribe = client.table("todos").subscribe((event) => {
console.log("Todo event:", event.type, event.ids);
});You can provide additional filters that are merged with the resource scope:
client.table("todos").subscribe(
(event) => { /* ... */ },
{ type: "mutation_applied", action: "delete" },
);Table Registry
The client creates table handles from your schema at initialization time. The registry ensures:
- Object identity: The same handle instance is returned for repeated lookups.
- Schema validation: Requesting a table for an unknown resource name throws a
DFQL_UNKNOWN_RESOURCEerror. - Version binding: Each handle is bound to the resource version declared in your schema.