import type { MetabaseCard, MetabaseCollection, MetabaseDashboard, MetabaseDatabase, MetabaseSnippet, } from './types.js' export class MetabaseClient { private baseUrl: string private headers: Record constructor(baseUrl: string, auth: { sessionToken?: string; apiKey?: string }) { this.baseUrl = baseUrl.replace(/\/$/, '') this.headers = { 'Content-Type': 'application/json' } if (auth.apiKey) { this.headers['x-api-key'] = auth.apiKey } else if (auth.sessionToken) { this.headers['X-Metabase-Session'] = auth.sessionToken } } static async login(baseUrl: string, username: string, password: string): Promise { const url = `${baseUrl.replace(/\/$/, '')}/api/session` const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }) if (!res.ok) { const text = await res.text() throw new Error(`Login failed (${res.status}): ${text}`) } const { id } = (await res.json()) as { id: string } return new MetabaseClient(baseUrl, { sessionToken: id }) } static withApiKey(baseUrl: string, apiKey: string): MetabaseClient { return new MetabaseClient(baseUrl, { apiKey }) } static withSession(baseUrl: string, sessionToken: string): MetabaseClient { return new MetabaseClient(baseUrl, { sessionToken }) } // ─── Collections ─── async getCollections(): Promise { return this.get('/api/collection') } async createCollection(data: { name: string description?: string parent_id?: number | null }): Promise { return this.post('/api/collection', data) } async updateCollection( id: number, data: { name?: string; description?: string; parent_id?: number | null }, ): Promise { return this.put(`/api/collection/${id}`, data) } // ─── Cards (Questions) ─── async getCards(): Promise { return this.get('/api/card') } async getCard(id: number): Promise { return this.get(`/api/card/${id}`) } async createCard(data: Record): Promise { return this.post('/api/card', data) } async updateCard(id: number, data: Record): Promise { return this.put(`/api/card/${id}`, data) } // ─── Dashboards ─── async getDashboards(): Promise { return this.get('/api/dashboard') } async getDashboard(id: number): Promise { return this.get(`/api/dashboard/${id}`) } async createDashboard(data: Record): Promise { return this.post('/api/dashboard', data) } async updateDashboard(id: number, data: Record): Promise { return this.put(`/api/dashboard/${id}`, data) } async addCardToDashboard( dashboardId: number, data: Record, ): Promise { return this.post(`/api/dashboard/${dashboardId}/cards`, data) } async updateDashboardCards( dashboardId: number, cards: Record[], ): Promise { return this.put(`/api/dashboard/${dashboardId}/cards`, { cards }) } // ─── Snippets ─── async getSnippets(): Promise { return this.get('/api/native-query-snippet') } async createSnippet(data: Record): Promise { return this.post('/api/native-query-snippet', data) } async updateSnippet(id: number, data: Record): Promise { return this.put(`/api/native-query-snippet/${id}`, data) } // ─── Databases ─── async getDatabases(): Promise { const result = await this.get<{ data: MetabaseDatabase[] }>('/api/database') return result.data } async getDatabaseMetadata(id: number): Promise { return this.get(`/api/database/${id}/metadata`) } // ─── HTTP helpers ─── async get(path: string): Promise { const res = await fetch(`${this.baseUrl}${path}`, { headers: this.headers }) if (!res.ok) { const text = await res.text() throw new Error(`GET ${path} failed (${res.status}): ${text}`) } return res.json() as Promise } private async post(path: string, body: unknown): Promise { const res = await fetch(`${this.baseUrl}${path}`, { method: 'POST', headers: this.headers, body: JSON.stringify(body), }) if (!res.ok) { const text = await res.text() throw new Error(`POST ${path} failed (${res.status}): ${text}`) } return res.json() as Promise } private async put(path: string, body: unknown): Promise { const res = await fetch(`${this.baseUrl}${path}`, { method: 'PUT', headers: this.headers, body: JSON.stringify(body), }) if (!res.ok) { const text = await res.text() throw new Error(`PUT ${path} failed (${res.status}): ${text}`) } return res.json() as Promise } }