# crud
_(no submodules)_
## Purpose
`crud` exposes the typed record-level CRUD primitives (`create`, `select`, `update`, `delete`, `selectAll`) used by `SurrealAdapter`. Splitting them into a standalone module keeps `adapter.ts` under the 300-line hard limit and lets advanced consumers reach for the helpers directly when they already hold a `SurrealClientLike` reference.
Each helper is a plain async function that takes the client as its first positional argument — it never reads adapter state — which makes the CRUD layer trivially testable with a real fake client and free of hidden lifecycle coupling.
## Features
- `surrealCreate` — `INSERT INTO
CONTENT $data` (works around the surrealdb.js v2+ create bug)
- `surrealSelect` / `surrealUpdate` / `surrealDelete` — record-id-scoped operations using `type::thing($table, $id)`
- `surrealSelectAll` — full-table read
- Driver errors are normalised through `wrapQueryError` so every failure surfaces as a `SurrealError` with a stable code
- `AbortSignal` is honoured at the start of every call via `signal.throwIfAborted()`
## Usage: Basic Example
```ts
import { surrealCreate, surrealSelect } from "@suaveplan/surrealdb/adapter/crud";
const created = await surrealCreate(client, "user", { name: "Alice" });
const fetched = await surrealSelect<{ name: string }>(client, "user", "alice");
```
## Usage: Advanced Example
```ts
import {
surrealSelectAll,
surrealUpdate,
surrealDelete,
} from "@suaveplan/surrealdb/adapter/crud";
const all = await surrealSelectAll<{ id: string; archived: boolean }>(client, "doc");
for (const doc of all) {
if (!doc.archived) continue;
const ac = new AbortController();
setTimeout(() => ac.abort(), 5000);
await surrealUpdate(client, "doc", doc.id, { archived: false }, { signal: ac.signal });
await surrealDelete(client, "tombstone", doc.id, { signal: ac.signal });
}
```
## API Reference
### `surrealCreate(client, table, data, opts?): Promise`
Throws `SurrealError(QUERY_FAILED)` when the driver returns zero rows or any other error.
### `surrealSelect(client, table, id, opts?): Promise`
Returns `null` when no row matches. Throws `SurrealError(QUERY_FAILED)` on driver errors.
### `surrealUpdate(client, table, id, data, opts?): Promise`
Returns `null` when no row matched. Throws `SurrealError(QUERY_FAILED)` on driver errors.
### `surrealDelete(client, table, id, opts?): Promise`
Throws `SurrealError(QUERY_FAILED)` on driver errors.
### `surrealSelectAll(client, table, opts?): Promise`
Returns the row array (possibly empty). Throws `SurrealError(QUERY_FAILED)` on driver errors.
## Implementation Notes
- Each helper accepts a `SurrealClientLike` rather than the full adapter so consumers can reuse them with custom client wrappers, mocks, or in transactional bodies that already hold the client.
- `AbortSignal` is consulted only at the entry point; the underlying driver does not currently accept a per-call signal, so once the `await client.query(...)` is in flight cancellation cannot interrupt it. Callers needing harder cancellation should race the call against a timeout external to the helper.
- All error paths unify on `wrapQueryError`, except `surrealCreate` which can throw a code-specific `SurrealError(QUERY_FAILED)` when the row array is empty — that case is distinguishable from a thrown driver error because it carries the exact `INSERT INTO returned no rows` message.
- The `flattenRows` helper from `query-helpers` shields callers from the surrealdb.js v2 `[[...]]` result-wrapping detail.