UNPKG

@andrewlwn77/nocodb-mcp

Version:
326 lines 13.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NocoDBClient = exports.NocoDBError = void 0; const axios_1 = __importDefault(require("axios")); const form_data_1 = __importDefault(require("form-data")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); class NocoDBError extends Error { statusCode; details; constructor(message, statusCode, details) { super(message); this.statusCode = statusCode; this.details = details; this.name = "NocoDBError"; } } exports.NocoDBError = NocoDBError; class NocoDBClient { client; config; constructor(config) { this.config = config; this.client = axios_1.default.create({ baseURL: config.baseUrl, headers: { "Content-Type": "application/json", ...(config.apiToken && { "xc-token": config.apiToken }), ...(config.authToken && { "xc-auth": config.authToken }), }, }); // Add response interceptor for error handling this.client.interceptors.response.use((response) => response, (error) => { const data = error.response?.data; const message = data?.msg || data?.message || error.message; throw new NocoDBError(message, error.response?.status, error.response?.data); }); } // Base/Project operations async listBases() { const response = await this.client.get("/api/v1/db/meta/projects"); return response.data.list; } async getBase(baseId) { const response = await this.client.get(`/api/v1/db/meta/projects/${baseId}`); return response.data; } // Table operations async listTables(baseId) { const response = await this.client.get(`/api/v1/db/meta/projects/${baseId}/tables`); return response.data.list; } async getTable(tableId) { const response = await this.client.get(`/api/v1/db/meta/tables/${tableId}`); return response.data; } async createTable(baseId, tableName, columns) { const response = await this.client.post(`/api/v1/db/meta/projects/${baseId}/tables`, { table_name: tableName, title: tableName, columns: columns, }); return response.data; } async deleteTable(tableId) { await this.client.delete(`/api/v1/db/meta/tables/${tableId}`); } // Column operations async listColumns(tableId) { // Get columns from table info since dedicated columns endpoint doesn't exist const response = await this.client.get(`/api/v1/db/meta/tables/${tableId}`); return response.data.columns || []; } async addColumn(tableId, columnDefinition) { const response = await this.client.post(`/api/v2/meta/tables/${tableId}/columns`, columnDefinition); // The API returns the whole table object with columns, // so we need to find the newly added column const tableData = response.data; if (tableData.columns) { // Find the column that matches our title or column_name const newColumn = tableData.columns.find((col) => col.title === columnDefinition.title || col.column_name === columnDefinition.column_name); if (newColumn) { return newColumn; } } // Fallback to returning the whole response if we can't find the column return response.data; } async deleteColumn(columnId) { await this.client.delete(`/api/v2/meta/columns/${columnId}`); } // Record operations async createRecord(baseId, tableName, data) { // First get table ID from table name const tables = await this.listTables(baseId); const table = tables.find((t) => t.table_name === tableName || t.title === tableName); if (!table) { throw new NocoDBError(`Table ${tableName} not found`); } const response = await this.client.post(`/api/v2/tables/${table.id}/records`, data); return response.data; } async bulkInsert(baseId, tableName, options) { // First get table ID from table name const tables = await this.listTables(baseId); const table = tables.find((t) => t.table_name === tableName || t.title === tableName); if (!table) { throw new NocoDBError(`Table ${tableName} not found`); } const response = await this.client.post(`/api/v2/tables/${table.id}/records`, options.records); return response.data; } async getRecord(baseId, tableName, recordId) { // First get table ID from table name const tables = await this.listTables(baseId); const table = tables.find((t) => t.table_name === tableName || t.title === tableName); if (!table) { throw new NocoDBError(`Table ${tableName} not found`); } const response = await this.client.get(`/api/v2/tables/${table.id}/records/${recordId}`); return response.data; } async listRecords(baseId, tableName, options) { // First get table ID from table name const tables = await this.listTables(baseId); const table = tables.find((t) => t.table_name === tableName || t.title === tableName); if (!table) { throw new NocoDBError(`Table ${tableName} not found`); } const params = new URLSearchParams(); if (options?.where) params.append("where", options.where); if (options?.sort) { const sortStr = Array.isArray(options.sort) ? options.sort.join(",") : options.sort; params.append("sort", sortStr); } if (options?.fields) { const fieldsStr = Array.isArray(options.fields) ? options.fields.join(",") : options.fields; params.append("fields", fieldsStr); } if (options?.limit) params.append("limit", options.limit.toString()); if (options?.offset) params.append("offset", options.offset.toString()); if (options?.viewId) params.append("viewId", options.viewId); const response = await this.client.get(`/api/v2/tables/${table.id}/records?${params.toString()}`); return response.data; } async updateRecord(baseId, tableName, recordId, data) { // First get table ID from table name const tables = await this.listTables(baseId); const table = tables.find((t) => t.table_name === tableName || t.title === tableName); if (!table) { throw new NocoDBError(`Table ${tableName} not found`); } // Get the primary key field name (usually ID but can vary) const columns = await this.listColumns(table.id); const pkColumn = columns.find((col) => col.pk) || columns.find((col) => col.title === "ID"); const pkField = pkColumn?.title || "ID"; const response = await this.client.patch(`/api/v2/tables/${table.id}/records`, { [pkField]: recordId, ...data, }); return response.data; } async deleteRecord(baseId, tableName, recordId) { // First get table ID from table name const tables = await this.listTables(baseId); const table = tables.find((t) => t.table_name === tableName || t.title === tableName); if (!table) { throw new NocoDBError(`Table ${tableName} not found`); } // Get the primary key field name (usually ID but can vary) const columns = await this.listColumns(table.id); const pkColumn = columns.find((col) => col.pk) || columns.find((col) => col.title === "ID"); const pkField = pkColumn?.title || "ID"; await this.client.delete(`/api/v2/tables/${table.id}/records`, { data: { [pkField]: recordId }, }); } // View operations async listViews(tableId) { const response = await this.client.get(`/api/v2/meta/tables/${tableId}/views`); return response.data.list || []; } async createView(tableId, title, type = 1) { const response = await this.client.post(`/api/v2/meta/tables/${tableId}/views`, { title, type, }); return response.data; } // Search operation async searchRecords(baseId, tableName, query, options) { // For now, use regular list with client-side filtering // since NocoDB search syntax is complex const records = await this.listRecords(baseId, tableName, options); const filtered = records.list.filter((record) => { return Object.values(record).some((value) => String(value).toLowerCase().includes(query.toLowerCase())); }); return { list: filtered, pageInfo: records.pageInfo }; } // Aggregate operations async aggregate(baseId, tableName, options) { // For now, implement client-side aggregation // as the aggregate endpoint might not be available in all versions const records = await this.listRecords(baseId, tableName, { where: options.where, }); const values = records.list.map((r) => Number(r[options.column_name]) || 0); switch (options.func) { case "count": return records.list.length; case "sum": return values.reduce((a, b) => a + b, 0); case "avg": return values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0; case "min": return Math.min(...values); case "max": return Math.max(...values); default: throw new NocoDBError(`Unknown aggregate function: ${options.func}`); } } // Group by operation async groupBy(baseId, tableName, columnName, options) { // Implement client-side grouping const records = await this.listRecords(baseId, tableName, options); const groups = new Map(); records.list.forEach((record) => { const value = record[columnName]; groups.set(value, (groups.get(value) || 0) + 1); }); const result = Array.from(groups.entries()).map(([value, count]) => ({ [columnName]: value, count, })); // Apply sorting if specified if (options?.sort) { const sortField = Array.isArray(options.sort) ? options.sort[0] : options.sort; const desc = sortField.startsWith("-"); result.sort((a, b) => { const aVal = a[columnName]; const bVal = b[columnName]; return desc ? (bVal > aVal ? 1 : -1) : aVal > bVal ? 1 : -1; }); } // Apply limit and offset const start = options?.offset || 0; const end = options?.limit ? start + options.limit : undefined; return result.slice(start, end); } // File upload operations async uploadFile(filePath, storagePath) { const formData = new form_data_1.default(); const fileStream = fs.createReadStream(filePath); const fileName = path.basename(filePath); formData.append("file", fileStream, fileName); if (storagePath) { formData.append("path", storagePath); } const response = await this.client.post("/api/v2/storage/upload", formData, { headers: { ...formData.getHeaders(), }, }); return response.data; } async uploadByUrl(urls, storagePath) { const urlData = urls.map((url) => ({ url })); const data = storagePath ? { urls: urlData, path: storagePath } : urlData; const response = await this.client.post("/api/v2/storage/upload-by-url", data); return response.data; } } exports.NocoDBClient = NocoDBClient; //# sourceMappingURL=nocodb-api.js.map