← back to Ventura Claw

server/connectors/airtable.js

54 lines

// Airtable — REAL. Use Personal Access Token (PAT).
const API = "https://api.airtable.com/v0";
function token(c) {
  const t = c?.AIRTABLE_PAT || process.env.AIRTABLE_PAT;
  if (!t) throw new Error("AIRTABLE_PAT not set");
  return t;
}
async function call(method, path, body, c) {
  const r = await fetch(`${API}${path}`, {
    method,
    headers: { "Authorization": `Bearer ${token(c)}`, "Content-Type": "application/json" },
    body: body ? JSON.stringify(body) : undefined
  });
  const d = r.status === 204 ? {} : await r.json();
  if (!r.ok) throw new Error(`airtable ${path} ${r.status}: ${d.error?.message || JSON.stringify(d).slice(0,200)}`);
  return d;
}
module.exports = {
  meta: { id: "airtable", name: "Airtable", category: "data", docsUrl: "https://airtable.com/developers/web/api", auth: "oauth2", realImpl: true },
  fields: [
    { key: "AIRTABLE_PAT",     label: "Personal Access Token", type: "password", required: true,  hint: "pat... — airtable.com/create/tokens" },
    { key: "AIRTABLE_BASE_ID", label: "Default Base ID",       type: "text",     required: false, hint: "appXXXX... (optional default; can override per call)" }
  ],
  configured(c) { return !!(c?.AIRTABLE_PAT || process.env.AIRTABLE_PAT); },
  async health(c) {
    if (!this.configured(c)) return { ok: false, reason: "no_token" };
    try { const d = await call("GET", "/meta/whoami", null, c); return { ok: true, user_id: d.id, email: d.email }; }
    catch (e) { return { ok: false, reason: e.message }; }
  },
  actions: {
    async "bases.list"(_, c) { return call("GET", "/meta/bases", null, c); },
    async "tables.list"({ base_id }, c) {
      const id = base_id || process.env.AIRTABLE_BASE_ID || c?.AIRTABLE_BASE_ID;
      if (!id) throw new Error("base_id required");
      return call("GET", `/meta/bases/${id}/tables`, null, c);
    },
    async "records.list"({ base_id, table, max }, c) {
      const id = base_id || process.env.AIRTABLE_BASE_ID || c?.AIRTABLE_BASE_ID;
      if (!id || !table) throw new Error("base_id and table required");
      return call("GET", `/${id}/${encodeURIComponent(table)}?maxRecords=${max||25}`, null, c);
    },
    async "record.create"({ base_id, table, fields }, c) {
      const id = base_id || process.env.AIRTABLE_BASE_ID || c?.AIRTABLE_BASE_ID;
      if (!id || !table) throw new Error("base_id and table required");
      return call("POST", `/${id}/${encodeURIComponent(table)}`, { fields }, c);
    },
    async "record.update"({ base_id, table, record_id, fields }, c) {
      const id = base_id || process.env.AIRTABLE_BASE_ID || c?.AIRTABLE_BASE_ID;
      if (!id || !table || !record_id) throw new Error("base_id, table, record_id required");
      return call("PATCH", `/${id}/${encodeURIComponent(table)}/${record_id}`, { fields }, c);
    }
  }
};