# Airtable

Lightweight, dependency-free, in-memory Airtable REST API fake for testing code that uses the real `airtable` client.

Default port: `4611`

## Implemented Operations

Table records, compatible with the `airtable` npm client's public table methods:

- `GET /v0/:baseId/:tableName` lists/selects records.
- `POST /v0/:baseId/:tableName?method=list` lists records with a JSON body for long query use cases.
- `POST /v0/:baseId/:tableName/listRecords` is accepted as a JSON-body list alias.
- `GET /v0/:baseId/:tableName/:recordId` finds one record.
- `POST /v0/:baseId/:tableName` creates one record with `{ "fields": { ... } }`.
- `POST /v0/:baseId/:tableName` creates up to 10 records with `{ "records": [{ "fields": { ... } }] }`.
- `PATCH /v0/:baseId/:tableName/:recordId` updates one record by merging fields.
- `PUT /v0/:baseId/:tableName/:recordId` replaces one record's fields.
- `PATCH /v0/:baseId/:tableName` updates up to 10 records with `{ "records": [{ "id": "rec...", "fields": { ... } }] }`.
- `PUT /v0/:baseId/:tableName` replaces up to 10 records with `{ "records": [{ "id": "rec...", "fields": { ... } }] }`.
- `DELETE /v0/:baseId/:tableName/:recordId` deletes one record.
- `DELETE /v0/:baseId/:tableName?records[]=recA&records[]=recB` deletes up to 10 records.

Query options for list/select:

- `fields[]` or `fields` projects returned fields.
- `filterByFormula` supports common client-test expressions: `{Field}`, `{Field} = value`, `{Field} != value`, numeric comparisons, `FIND('text', {Field})`, `SEARCH('text', {Field})`, `AND(...)`, and `OR(...)`.
- `maxRecords` caps the result set.
- `pageSize` returns up to 100 records and emits an `offset` string when more records are available.
- `offset` resumes pagination.
- `sort[0][field]` and `sort[0][direction]` sort results using the real client query format.
- `view`, `cellFormat`, `timeZone`, and `userLocale` are accepted as inert query parameters.

Service operations:

- `GET /` returns service metadata.
- `GET /health` returns `{ "status": "ok" }`.
- `OPTIONS *` returns `204`.
- `POST /__reset` clears all in-memory state.
- `server.reset()` clears all in-memory state when used in-process.

## Quick Start

```js
import Airtable from "airtable";
import { AirtableServer } from "./services/airtable/src/server.js";

const server = new AirtableServer(4611);
await server.start();

const base = new Airtable({
  apiKey: "keyParlel",
  endpointUrl: "http://127.0.0.1:4611",
}).base("appParlel");

const created = await base("Tasks").create({ Name: "Test locally" });
const records = await base("Tasks").select({ pageSize: 10 }).all();

await server.stop();
```

## Supported Features

| Feature | Status | Notes |
| --- | --- | --- |
| Real `airtable` client table methods | Supported | `select`, `all`, `eachPage`, `find`, `create`, `update`, `replace`, and `destroy` map to implemented REST operations. |
| Bearer auth and `api_key` query auth | Supported | Any non-empty token is accepted. Missing auth returns an Airtable-shaped `401`. |
| In-memory bases and tables | Supported | Bases/tables are created lazily by writes and cleared by reset. |
| Record field values | Supported | JSON values are stored as-is. |
| Pagination, projection, sorting | Supported | Matches the wire shape used by Airtable list responses. |
| Common `filterByFormula` expressions | Supported | Intended for app tests, not a complete Airtable formula engine. |
| Batch write/delete limit | Supported | Enforces Airtable's 10-record request limit. |
| Attachments, collaborators, links, computed fields | Stored only | Values are preserved as plain field JSON; Airtable-specific processing is intentionally unsupported. |
| Metadata API/schema API | Unsupported | The `airtable` table client does not require these endpoints. |
| Persistence | Unsupported | State is ephemeral by design. |
| Rate limiting | Unsupported | Local tests should not pay Airtable costs or hit side effects. |

## Error Shapes

All JSON errors use Airtable-style framing:

```json
{
  "error": {
    "type": "NOT_FOUND",
    "message": "Could not find record"
  }
}
```

Returned error types:

| Status | Type | When |
| --- | --- | --- |
| `401` | `AUTHENTICATION_REQUIRED` | Missing `Authorization: Bearer ...` and missing `api_key`. |
| `404` | `NOT_FOUND` | Unknown endpoint or missing record. |
| `405` | `METHOD_NOT_ALLOWED` | Endpoint exists but the HTTP method is unsupported. |
| `422` | `INVALID_REQUEST_BODY` | Required `fields`, `records`, ids, or delete query parameters are malformed or missing. |
| `422` | `INVALID_REQUEST_UNKNOWN` | More than 10 records are sent in a batch operation. |
| `500` | `SERVER_ERROR` | Unexpected server exception. |
