metabase-cli 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +291 -0
- package/dist/index.d.mts +408 -0
- package/dist/index.d.ts +408 -0
- package/dist/index.js +735 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +675 -0
- package/dist/index.mjs.map +1 -0
- package/dist/metabase.js +1710 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
CardApi: () => CardApi,
|
|
34
|
+
CollectionApi: () => CollectionApi,
|
|
35
|
+
DashboardApi: () => DashboardApi,
|
|
36
|
+
DatabaseApi: () => DatabaseApi,
|
|
37
|
+
DatasetApi: () => DatasetApi,
|
|
38
|
+
FieldApi: () => FieldApi,
|
|
39
|
+
MetabaseClient: () => MetabaseClient,
|
|
40
|
+
SafetyGuard: () => SafetyGuard,
|
|
41
|
+
SearchApi: () => SearchApi,
|
|
42
|
+
SessionApi: () => SessionApi,
|
|
43
|
+
SnippetApi: () => SnippetApi,
|
|
44
|
+
TableApi: () => TableApi,
|
|
45
|
+
UserApi: () => UserApi,
|
|
46
|
+
addProfile: () => addProfile,
|
|
47
|
+
formatDatasetResponse: () => formatDatasetResponse,
|
|
48
|
+
formatEntityTable: () => formatEntityTable,
|
|
49
|
+
formatJson: () => formatJson,
|
|
50
|
+
getActiveProfile: () => getActiveProfile,
|
|
51
|
+
listProfiles: () => listProfiles,
|
|
52
|
+
loadConfig: () => loadConfig,
|
|
53
|
+
removeProfile: () => removeProfile,
|
|
54
|
+
saveConfig: () => saveConfig,
|
|
55
|
+
setActiveProfile: () => setActiveProfile,
|
|
56
|
+
updateProfile: () => updateProfile
|
|
57
|
+
});
|
|
58
|
+
module.exports = __toCommonJS(index_exports);
|
|
59
|
+
|
|
60
|
+
// src/config/store.ts
|
|
61
|
+
var fs = __toESM(require("fs"));
|
|
62
|
+
var path = __toESM(require("path"));
|
|
63
|
+
var os = __toESM(require("os"));
|
|
64
|
+
function getConfigDir() {
|
|
65
|
+
return path.join(os.homedir(), ".metabase-cli");
|
|
66
|
+
}
|
|
67
|
+
function getConfigFile() {
|
|
68
|
+
return path.join(getConfigDir(), "config.json");
|
|
69
|
+
}
|
|
70
|
+
function ensureConfigDir() {
|
|
71
|
+
if (!fs.existsSync(getConfigDir())) {
|
|
72
|
+
fs.mkdirSync(getConfigDir(), { recursive: true, mode: 448 });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function defaultConfig() {
|
|
76
|
+
return { activeProfile: "", profiles: {} };
|
|
77
|
+
}
|
|
78
|
+
function loadConfig() {
|
|
79
|
+
ensureConfigDir();
|
|
80
|
+
if (!fs.existsSync(getConfigFile())) {
|
|
81
|
+
return defaultConfig();
|
|
82
|
+
}
|
|
83
|
+
const raw = fs.readFileSync(getConfigFile(), "utf-8");
|
|
84
|
+
return JSON.parse(raw);
|
|
85
|
+
}
|
|
86
|
+
function saveConfig(config) {
|
|
87
|
+
ensureConfigDir();
|
|
88
|
+
fs.writeFileSync(getConfigFile(), JSON.stringify(config, null, 2), {
|
|
89
|
+
encoding: "utf-8",
|
|
90
|
+
mode: 384
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function getActiveProfile() {
|
|
94
|
+
const config = loadConfig();
|
|
95
|
+
if (!config.activeProfile || !config.profiles[config.activeProfile]) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return config.profiles[config.activeProfile];
|
|
99
|
+
}
|
|
100
|
+
function setActiveProfile(name) {
|
|
101
|
+
const config = loadConfig();
|
|
102
|
+
if (!config.profiles[name]) {
|
|
103
|
+
throw new Error(`Profile "${name}" does not exist`);
|
|
104
|
+
}
|
|
105
|
+
config.activeProfile = name;
|
|
106
|
+
saveConfig(config);
|
|
107
|
+
}
|
|
108
|
+
function addProfile(profile) {
|
|
109
|
+
const config = loadConfig();
|
|
110
|
+
config.profiles[profile.name] = profile;
|
|
111
|
+
if (!config.activeProfile) {
|
|
112
|
+
config.activeProfile = profile.name;
|
|
113
|
+
}
|
|
114
|
+
saveConfig(config);
|
|
115
|
+
}
|
|
116
|
+
function removeProfile(name) {
|
|
117
|
+
const config = loadConfig();
|
|
118
|
+
if (!config.profiles[name]) {
|
|
119
|
+
throw new Error(`Profile "${name}" does not exist`);
|
|
120
|
+
}
|
|
121
|
+
delete config.profiles[name];
|
|
122
|
+
if (config.activeProfile === name) {
|
|
123
|
+
const remaining = Object.keys(config.profiles);
|
|
124
|
+
config.activeProfile = remaining.length > 0 ? remaining[0] : "";
|
|
125
|
+
}
|
|
126
|
+
saveConfig(config);
|
|
127
|
+
}
|
|
128
|
+
function updateProfile(name, updates) {
|
|
129
|
+
const config = loadConfig();
|
|
130
|
+
if (!config.profiles[name]) {
|
|
131
|
+
throw new Error(`Profile "${name}" does not exist`);
|
|
132
|
+
}
|
|
133
|
+
config.profiles[name] = { ...config.profiles[name], ...updates };
|
|
134
|
+
saveConfig(config);
|
|
135
|
+
}
|
|
136
|
+
function listProfiles() {
|
|
137
|
+
const config = loadConfig();
|
|
138
|
+
return {
|
|
139
|
+
profiles: Object.values(config.profiles),
|
|
140
|
+
active: config.activeProfile
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/client.ts
|
|
145
|
+
var MetabaseClient = class {
|
|
146
|
+
domain;
|
|
147
|
+
sessionToken;
|
|
148
|
+
apiKey;
|
|
149
|
+
profile;
|
|
150
|
+
constructor(profile) {
|
|
151
|
+
this.profile = profile;
|
|
152
|
+
this.domain = profile.domain.replace(/\/+$/, "");
|
|
153
|
+
if (profile.auth.method === "session") {
|
|
154
|
+
this.sessionToken = profile.auth.sessionToken;
|
|
155
|
+
} else {
|
|
156
|
+
this.apiKey = profile.auth.apiKey;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
getHeaders() {
|
|
160
|
+
const headers = {
|
|
161
|
+
"Content-Type": "application/json"
|
|
162
|
+
};
|
|
163
|
+
if (this.apiKey) {
|
|
164
|
+
headers["X-Api-Key"] = this.apiKey;
|
|
165
|
+
} else if (this.sessionToken) {
|
|
166
|
+
headers["X-Metabase-Session"] = this.sessionToken;
|
|
167
|
+
}
|
|
168
|
+
return headers;
|
|
169
|
+
}
|
|
170
|
+
async request(method, path2, body, params) {
|
|
171
|
+
let url = `${this.domain}${path2}`;
|
|
172
|
+
if (params) {
|
|
173
|
+
const qs = new URLSearchParams(params).toString();
|
|
174
|
+
url += `?${qs}`;
|
|
175
|
+
}
|
|
176
|
+
const res = await fetch(url, {
|
|
177
|
+
method,
|
|
178
|
+
headers: this.getHeaders(),
|
|
179
|
+
body: body ? JSON.stringify(body) : void 0
|
|
180
|
+
});
|
|
181
|
+
if (res.status === 401 && this.profile.auth.method === "session") {
|
|
182
|
+
await this.login();
|
|
183
|
+
const retryRes = await fetch(url, {
|
|
184
|
+
method,
|
|
185
|
+
headers: this.getHeaders(),
|
|
186
|
+
body: body ? JSON.stringify(body) : void 0
|
|
187
|
+
});
|
|
188
|
+
if (!retryRes.ok) {
|
|
189
|
+
const err = await retryRes.text();
|
|
190
|
+
throw new Error(`${retryRes.status} ${retryRes.statusText}: ${err}`);
|
|
191
|
+
}
|
|
192
|
+
const retryText = await retryRes.text();
|
|
193
|
+
if (!retryText) return void 0;
|
|
194
|
+
return JSON.parse(retryText);
|
|
195
|
+
}
|
|
196
|
+
if (!res.ok) {
|
|
197
|
+
const err = await res.text();
|
|
198
|
+
throw new Error(`${res.status} ${res.statusText}: ${err}`);
|
|
199
|
+
}
|
|
200
|
+
const text = await res.text();
|
|
201
|
+
if (!text) return void 0;
|
|
202
|
+
return JSON.parse(text);
|
|
203
|
+
}
|
|
204
|
+
async get(path2, params) {
|
|
205
|
+
return this.request("GET", path2, void 0, params);
|
|
206
|
+
}
|
|
207
|
+
async post(path2, body) {
|
|
208
|
+
return this.request("POST", path2, body);
|
|
209
|
+
}
|
|
210
|
+
async put(path2, body) {
|
|
211
|
+
return this.request("PUT", path2, body);
|
|
212
|
+
}
|
|
213
|
+
async delete(path2) {
|
|
214
|
+
return this.request("DELETE", path2);
|
|
215
|
+
}
|
|
216
|
+
async requestFormExport(path2, fields) {
|
|
217
|
+
const url = `${this.domain}${path2}`;
|
|
218
|
+
const headers = {};
|
|
219
|
+
if (this.apiKey) {
|
|
220
|
+
headers["X-Api-Key"] = this.apiKey;
|
|
221
|
+
} else if (this.sessionToken) {
|
|
222
|
+
headers["X-Metabase-Session"] = this.sessionToken;
|
|
223
|
+
}
|
|
224
|
+
const body = new URLSearchParams(fields);
|
|
225
|
+
const res = await fetch(url, { method: "POST", headers, body });
|
|
226
|
+
if (res.status === 401 && this.profile.auth.method === "session") {
|
|
227
|
+
await this.login();
|
|
228
|
+
if (this.sessionToken) {
|
|
229
|
+
headers["X-Metabase-Session"] = this.sessionToken;
|
|
230
|
+
}
|
|
231
|
+
return fetch(url, { method: "POST", headers, body: new URLSearchParams(fields) });
|
|
232
|
+
}
|
|
233
|
+
return res;
|
|
234
|
+
}
|
|
235
|
+
async requestRaw(method, path2, body) {
|
|
236
|
+
const url = `${this.domain}${path2}`;
|
|
237
|
+
const res = await fetch(url, {
|
|
238
|
+
method,
|
|
239
|
+
headers: this.getHeaders(),
|
|
240
|
+
body: body ? JSON.stringify(body) : void 0
|
|
241
|
+
});
|
|
242
|
+
if (res.status === 401 && this.profile.auth.method === "session") {
|
|
243
|
+
await this.login();
|
|
244
|
+
return fetch(url, {
|
|
245
|
+
method,
|
|
246
|
+
headers: this.getHeaders(),
|
|
247
|
+
body: body ? JSON.stringify(body) : void 0
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return res;
|
|
251
|
+
}
|
|
252
|
+
async login() {
|
|
253
|
+
const auth = this.profile.auth;
|
|
254
|
+
const res = await fetch(`${this.domain}/api/session`, {
|
|
255
|
+
method: "POST",
|
|
256
|
+
headers: { "Content-Type": "application/json" },
|
|
257
|
+
body: JSON.stringify({ username: auth.email, password: auth.password })
|
|
258
|
+
});
|
|
259
|
+
if (!res.ok) {
|
|
260
|
+
const err = await res.text();
|
|
261
|
+
throw new Error(`Login failed: ${res.status} ${err}`);
|
|
262
|
+
}
|
|
263
|
+
const session = await res.json();
|
|
264
|
+
this.sessionToken = session.id;
|
|
265
|
+
updateProfile(this.profile.name, {
|
|
266
|
+
auth: { ...auth, sessionToken: session.id }
|
|
267
|
+
});
|
|
268
|
+
const user = await this.get("/api/user/current");
|
|
269
|
+
const cachedUser = {
|
|
270
|
+
id: user.id,
|
|
271
|
+
email: user.email,
|
|
272
|
+
first_name: user.first_name,
|
|
273
|
+
last_name: user.last_name,
|
|
274
|
+
is_superuser: user.is_superuser
|
|
275
|
+
};
|
|
276
|
+
updateProfile(this.profile.name, { user: cachedUser });
|
|
277
|
+
this.profile.user = cachedUser;
|
|
278
|
+
return session;
|
|
279
|
+
}
|
|
280
|
+
async logout() {
|
|
281
|
+
await this.delete("/api/session");
|
|
282
|
+
this.sessionToken = void 0;
|
|
283
|
+
if (this.profile.auth.method === "session") {
|
|
284
|
+
updateProfile(this.profile.name, {
|
|
285
|
+
auth: { ...this.profile.auth, sessionToken: void 0 }
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async ensureAuthenticated() {
|
|
290
|
+
if (this.apiKey) return;
|
|
291
|
+
if (!this.sessionToken) {
|
|
292
|
+
await this.login();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
getProfile() {
|
|
296
|
+
return this.profile;
|
|
297
|
+
}
|
|
298
|
+
getUserId() {
|
|
299
|
+
return this.profile.user?.id ?? null;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// src/safety/guard.ts
|
|
304
|
+
var SafetyGuard = class {
|
|
305
|
+
client;
|
|
306
|
+
unsafe;
|
|
307
|
+
constructor(client, unsafe = false) {
|
|
308
|
+
this.client = client;
|
|
309
|
+
this.unsafe = unsafe;
|
|
310
|
+
}
|
|
311
|
+
async checkOwnership(entityType, entityId) {
|
|
312
|
+
const pathMap = {
|
|
313
|
+
card: `/api/card/${entityId}`,
|
|
314
|
+
dashboard: `/api/dashboard/${entityId}`,
|
|
315
|
+
snippet: `/api/native-query-snippet/${entityId}`,
|
|
316
|
+
collection: `/api/collection/${entityId}`
|
|
317
|
+
};
|
|
318
|
+
const path2 = pathMap[entityType];
|
|
319
|
+
if (!path2) {
|
|
320
|
+
throw new Error(`Unknown entity type: ${entityType}`);
|
|
321
|
+
}
|
|
322
|
+
const entity = await this.client.get(path2);
|
|
323
|
+
const userId = this.client.getUserId();
|
|
324
|
+
if (userId === null) {
|
|
325
|
+
throw new Error("No cached user ID. Run 'metabase-cli login' or 'metabase-cli whoami --refresh' first.");
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
owned: entity.creator_id === userId,
|
|
329
|
+
creatorId: entity.creator_id
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
async guard(entityType, entityId, action, fn) {
|
|
333
|
+
if (this.unsafe) {
|
|
334
|
+
return fn();
|
|
335
|
+
}
|
|
336
|
+
const { owned, creatorId } = await this.checkOwnership(entityType, entityId);
|
|
337
|
+
if (!owned) {
|
|
338
|
+
const userId = this.client.getUserId();
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Safe mode: Cannot ${action} ${entityType} #${entityId} \u2014 owned by user #${creatorId}, you are user #${userId}. Use --unsafe to bypass.`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
return fn();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// src/api/session.ts
|
|
348
|
+
var SessionApi = class {
|
|
349
|
+
constructor(client) {
|
|
350
|
+
this.client = client;
|
|
351
|
+
}
|
|
352
|
+
async getCurrentUser() {
|
|
353
|
+
return this.client.get("/api/user/current");
|
|
354
|
+
}
|
|
355
|
+
async getSessionProperties() {
|
|
356
|
+
return this.client.get("/api/session/properties");
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// src/utils/export.ts
|
|
361
|
+
function checkExportError(buf, format) {
|
|
362
|
+
if (format !== "json" && buf.length > 0 && buf[0] === 123) {
|
|
363
|
+
try {
|
|
364
|
+
const parsed = JSON.parse(buf.toString("utf-8"));
|
|
365
|
+
if (parsed.status === "failed" || parsed.error) {
|
|
366
|
+
throw new Error(`Query failed: ${parsed.error || "unknown error"}`);
|
|
367
|
+
}
|
|
368
|
+
} catch (e) {
|
|
369
|
+
if (e.message.startsWith("Query failed:")) throw e;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/api/dataset.ts
|
|
375
|
+
var DatasetApi = class {
|
|
376
|
+
constructor(client) {
|
|
377
|
+
this.client = client;
|
|
378
|
+
}
|
|
379
|
+
async query(datasetQuery) {
|
|
380
|
+
return this.client.post("/api/dataset", datasetQuery);
|
|
381
|
+
}
|
|
382
|
+
async queryNative(database, sql, templateTags) {
|
|
383
|
+
return this.query({
|
|
384
|
+
type: "native",
|
|
385
|
+
database,
|
|
386
|
+
native: {
|
|
387
|
+
query: sql,
|
|
388
|
+
"template-tags": templateTags ?? {}
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
async export(datasetQuery, format) {
|
|
393
|
+
const res = await this.client.requestRaw(
|
|
394
|
+
"POST",
|
|
395
|
+
`/api/dataset/${format}`,
|
|
396
|
+
datasetQuery
|
|
397
|
+
);
|
|
398
|
+
if (!res.ok) {
|
|
399
|
+
throw new Error(`Export failed: ${res.status} ${await res.text()}`);
|
|
400
|
+
}
|
|
401
|
+
return res.text();
|
|
402
|
+
}
|
|
403
|
+
async exportBinary(datasetQuery, format) {
|
|
404
|
+
const res = await this.client.requestFormExport(
|
|
405
|
+
`/api/dataset/${format}`,
|
|
406
|
+
{ query: JSON.stringify(datasetQuery) }
|
|
407
|
+
);
|
|
408
|
+
if (!res.ok) {
|
|
409
|
+
throw new Error(`Export failed: ${res.status} ${await res.text()}`);
|
|
410
|
+
}
|
|
411
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
412
|
+
checkExportError(buf, format);
|
|
413
|
+
return buf;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// src/api/card.ts
|
|
418
|
+
var CardApi = class {
|
|
419
|
+
constructor(client) {
|
|
420
|
+
this.client = client;
|
|
421
|
+
}
|
|
422
|
+
async list(params) {
|
|
423
|
+
return this.client.get("/api/card", params);
|
|
424
|
+
}
|
|
425
|
+
async get(id) {
|
|
426
|
+
return this.client.get(`/api/card/${id}`);
|
|
427
|
+
}
|
|
428
|
+
async create(params) {
|
|
429
|
+
return this.client.post("/api/card", params);
|
|
430
|
+
}
|
|
431
|
+
async update(id, params) {
|
|
432
|
+
return this.client.put(`/api/card/${id}`, params);
|
|
433
|
+
}
|
|
434
|
+
async delete(id) {
|
|
435
|
+
await this.client.delete(`/api/card/${id}`);
|
|
436
|
+
}
|
|
437
|
+
async copy(id, overrides) {
|
|
438
|
+
return this.client.post(`/api/card/${id}/copy`, overrides);
|
|
439
|
+
}
|
|
440
|
+
async query(id, parameters) {
|
|
441
|
+
return this.client.post(`/api/card/${id}/query`, {
|
|
442
|
+
parameters: parameters ?? []
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
async queryExport(id, format, parameters) {
|
|
446
|
+
const res = await this.client.requestRaw(
|
|
447
|
+
"POST",
|
|
448
|
+
`/api/card/${id}/query/${format}`,
|
|
449
|
+
{ parameters: parameters ?? [] }
|
|
450
|
+
);
|
|
451
|
+
if (!res.ok) {
|
|
452
|
+
throw new Error(`Export failed: ${res.status} ${await res.text()}`);
|
|
453
|
+
}
|
|
454
|
+
return res.text();
|
|
455
|
+
}
|
|
456
|
+
async queryExportBinary(id, format, parameters) {
|
|
457
|
+
const fields = {};
|
|
458
|
+
if (parameters && parameters.length > 0) {
|
|
459
|
+
fields.parameters = JSON.stringify(parameters);
|
|
460
|
+
}
|
|
461
|
+
const res = await this.client.requestFormExport(
|
|
462
|
+
`/api/card/${id}/query/${format}`,
|
|
463
|
+
fields
|
|
464
|
+
);
|
|
465
|
+
if (!res.ok) {
|
|
466
|
+
throw new Error(`Export failed: ${res.status} ${await res.text()}`);
|
|
467
|
+
}
|
|
468
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
469
|
+
checkExportError(buf, format);
|
|
470
|
+
return buf;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// src/api/dashboard.ts
|
|
475
|
+
var DashboardApi = class {
|
|
476
|
+
constructor(client) {
|
|
477
|
+
this.client = client;
|
|
478
|
+
}
|
|
479
|
+
async list(params) {
|
|
480
|
+
return this.client.get("/api/dashboard", params);
|
|
481
|
+
}
|
|
482
|
+
async get(id) {
|
|
483
|
+
return this.client.get(`/api/dashboard/${id}`);
|
|
484
|
+
}
|
|
485
|
+
async create(params) {
|
|
486
|
+
return this.client.post("/api/dashboard", params);
|
|
487
|
+
}
|
|
488
|
+
async update(id, params) {
|
|
489
|
+
return this.client.put(`/api/dashboard/${id}`, params);
|
|
490
|
+
}
|
|
491
|
+
async delete(id) {
|
|
492
|
+
await this.client.delete(`/api/dashboard/${id}`);
|
|
493
|
+
}
|
|
494
|
+
async copy(id, overrides) {
|
|
495
|
+
return this.client.post(`/api/dashboard/${id}/copy`, overrides);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
// src/api/collection.ts
|
|
500
|
+
var CollectionApi = class {
|
|
501
|
+
constructor(client) {
|
|
502
|
+
this.client = client;
|
|
503
|
+
}
|
|
504
|
+
async list() {
|
|
505
|
+
return this.client.get("/api/collection");
|
|
506
|
+
}
|
|
507
|
+
async tree() {
|
|
508
|
+
return this.client.get("/api/collection/tree");
|
|
509
|
+
}
|
|
510
|
+
async get(id) {
|
|
511
|
+
return this.client.get(`/api/collection/${id}`);
|
|
512
|
+
}
|
|
513
|
+
async items(id, params) {
|
|
514
|
+
return this.client.get(`/api/collection/${id}/items`, params);
|
|
515
|
+
}
|
|
516
|
+
async create(params) {
|
|
517
|
+
return this.client.post("/api/collection", params);
|
|
518
|
+
}
|
|
519
|
+
async update(id, params) {
|
|
520
|
+
return this.client.put(`/api/collection/${id}`, params);
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
// src/api/database.ts
|
|
525
|
+
var DatabaseApi = class {
|
|
526
|
+
constructor(client) {
|
|
527
|
+
this.client = client;
|
|
528
|
+
}
|
|
529
|
+
async list() {
|
|
530
|
+
return this.client.get("/api/database");
|
|
531
|
+
}
|
|
532
|
+
async get(id) {
|
|
533
|
+
return this.client.get(`/api/database/${id}`);
|
|
534
|
+
}
|
|
535
|
+
async metadata(id) {
|
|
536
|
+
return this.client.get(`/api/database/${id}/metadata`);
|
|
537
|
+
}
|
|
538
|
+
async schemas(id) {
|
|
539
|
+
return this.client.get(`/api/database/${id}/schemas`);
|
|
540
|
+
}
|
|
541
|
+
async tablesInSchema(id, schema) {
|
|
542
|
+
return this.client.get(
|
|
543
|
+
`/api/database/${id}/schema/${encodeURIComponent(schema)}`
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
// src/api/table.ts
|
|
549
|
+
var TableApi = class {
|
|
550
|
+
constructor(client) {
|
|
551
|
+
this.client = client;
|
|
552
|
+
}
|
|
553
|
+
async get(id) {
|
|
554
|
+
return this.client.get(`/api/table/${id}`);
|
|
555
|
+
}
|
|
556
|
+
async queryMetadata(id) {
|
|
557
|
+
return this.client.get(`/api/table/${id}/query_metadata`);
|
|
558
|
+
}
|
|
559
|
+
async foreignKeys(id) {
|
|
560
|
+
return this.client.get(`/api/table/${id}/fks`);
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
// src/api/field.ts
|
|
565
|
+
var FieldApi = class {
|
|
566
|
+
constructor(client) {
|
|
567
|
+
this.client = client;
|
|
568
|
+
}
|
|
569
|
+
async get(id) {
|
|
570
|
+
return this.client.get(`/api/field/${id}`);
|
|
571
|
+
}
|
|
572
|
+
async values(id) {
|
|
573
|
+
return this.client.get(`/api/field/${id}/values`);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// src/api/snippet.ts
|
|
578
|
+
var SnippetApi = class {
|
|
579
|
+
constructor(client) {
|
|
580
|
+
this.client = client;
|
|
581
|
+
}
|
|
582
|
+
async list(params) {
|
|
583
|
+
return this.client.get("/api/native-query-snippet", params);
|
|
584
|
+
}
|
|
585
|
+
async get(id) {
|
|
586
|
+
return this.client.get(`/api/native-query-snippet/${id}`);
|
|
587
|
+
}
|
|
588
|
+
async create(params) {
|
|
589
|
+
return this.client.post("/api/native-query-snippet", params);
|
|
590
|
+
}
|
|
591
|
+
async update(id, params) {
|
|
592
|
+
return this.client.put(`/api/native-query-snippet/${id}`, params);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
// src/api/search.ts
|
|
597
|
+
var SearchApi = class {
|
|
598
|
+
constructor(client) {
|
|
599
|
+
this.client = client;
|
|
600
|
+
}
|
|
601
|
+
async search(query, params) {
|
|
602
|
+
const qs = { q: query };
|
|
603
|
+
if (params?.models?.length) {
|
|
604
|
+
qs.models = params.models.join(",");
|
|
605
|
+
}
|
|
606
|
+
if (params?.limit !== void 0) qs.limit = String(params.limit);
|
|
607
|
+
if (params?.offset !== void 0) qs.offset = String(params.offset);
|
|
608
|
+
return this.client.get("/api/search", qs);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
// src/api/user.ts
|
|
613
|
+
var UserApi = class {
|
|
614
|
+
constructor(client) {
|
|
615
|
+
this.client = client;
|
|
616
|
+
}
|
|
617
|
+
async list(params) {
|
|
618
|
+
return this.client.get("/api/user", params);
|
|
619
|
+
}
|
|
620
|
+
async get(id) {
|
|
621
|
+
return this.client.get(`/api/user/${id}`);
|
|
622
|
+
}
|
|
623
|
+
async current() {
|
|
624
|
+
return this.client.get("/api/user/current");
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// src/utils/output.ts
|
|
629
|
+
var import_cli_table3 = __toESM(require("cli-table3"));
|
|
630
|
+
function formatDatasetResponse(dataset, format = "table", columns) {
|
|
631
|
+
const cols = dataset.data.cols;
|
|
632
|
+
const rows = dataset.data.rows;
|
|
633
|
+
let colIndices;
|
|
634
|
+
if (columns?.length) {
|
|
635
|
+
colIndices = columns.map((name) => {
|
|
636
|
+
const idx = cols.findIndex(
|
|
637
|
+
(c) => c.name === name || c.display_name === name
|
|
638
|
+
);
|
|
639
|
+
if (idx === -1) throw new Error(`Column "${name}" not found`);
|
|
640
|
+
return idx;
|
|
641
|
+
});
|
|
642
|
+
} else {
|
|
643
|
+
colIndices = cols.map((_, i) => i);
|
|
644
|
+
}
|
|
645
|
+
const filteredCols = colIndices.map((i) => cols[i]);
|
|
646
|
+
const filteredRows = rows.map((row) => colIndices.map((i) => row[i]));
|
|
647
|
+
switch (format) {
|
|
648
|
+
case "json":
|
|
649
|
+
return JSON.stringify(
|
|
650
|
+
filteredRows.map(
|
|
651
|
+
(row) => Object.fromEntries(
|
|
652
|
+
filteredCols.map((col, i) => [col.name, row[i]])
|
|
653
|
+
)
|
|
654
|
+
),
|
|
655
|
+
null,
|
|
656
|
+
2
|
|
657
|
+
);
|
|
658
|
+
case "csv":
|
|
659
|
+
return formatDelimited(filteredCols, filteredRows, ",");
|
|
660
|
+
case "tsv":
|
|
661
|
+
return formatDelimited(filteredCols, filteredRows, " ");
|
|
662
|
+
case "table":
|
|
663
|
+
default:
|
|
664
|
+
return formatTable(filteredCols, filteredRows);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function formatDelimited(cols, rows, delimiter) {
|
|
668
|
+
const header = cols.map((c) => escapeCsvField(String(c.name), delimiter)).join(delimiter);
|
|
669
|
+
const body = rows.map(
|
|
670
|
+
(row) => row.map((cell) => escapeCsvField(formatCell(cell), delimiter)).join(delimiter)
|
|
671
|
+
);
|
|
672
|
+
return [header, ...body].join("\n");
|
|
673
|
+
}
|
|
674
|
+
function escapeCsvField(value, delimiter) {
|
|
675
|
+
if (value.includes(delimiter) || value.includes('"') || value.includes("\n")) {
|
|
676
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
677
|
+
}
|
|
678
|
+
return value;
|
|
679
|
+
}
|
|
680
|
+
function formatTable(cols, rows) {
|
|
681
|
+
const table = new import_cli_table3.default({
|
|
682
|
+
head: cols.map((c) => c.display_name),
|
|
683
|
+
style: { head: ["cyan"] }
|
|
684
|
+
});
|
|
685
|
+
for (const row of rows) {
|
|
686
|
+
table.push(row.map((cell) => formatCell(cell)));
|
|
687
|
+
}
|
|
688
|
+
return table.toString();
|
|
689
|
+
}
|
|
690
|
+
function formatCell(value) {
|
|
691
|
+
if (value === null || value === void 0) return "";
|
|
692
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
693
|
+
return String(value);
|
|
694
|
+
}
|
|
695
|
+
function formatJson(data) {
|
|
696
|
+
return JSON.stringify(data, null, 2);
|
|
697
|
+
}
|
|
698
|
+
function formatEntityTable(items, columns) {
|
|
699
|
+
const table = new import_cli_table3.default({
|
|
700
|
+
head: columns.map((c) => c.header),
|
|
701
|
+
style: { head: ["cyan"] }
|
|
702
|
+
});
|
|
703
|
+
for (const item of items) {
|
|
704
|
+
table.push(columns.map((c) => formatCell(item[c.key])));
|
|
705
|
+
}
|
|
706
|
+
return table.toString();
|
|
707
|
+
}
|
|
708
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
709
|
+
0 && (module.exports = {
|
|
710
|
+
CardApi,
|
|
711
|
+
CollectionApi,
|
|
712
|
+
DashboardApi,
|
|
713
|
+
DatabaseApi,
|
|
714
|
+
DatasetApi,
|
|
715
|
+
FieldApi,
|
|
716
|
+
MetabaseClient,
|
|
717
|
+
SafetyGuard,
|
|
718
|
+
SearchApi,
|
|
719
|
+
SessionApi,
|
|
720
|
+
SnippetApi,
|
|
721
|
+
TableApi,
|
|
722
|
+
UserApi,
|
|
723
|
+
addProfile,
|
|
724
|
+
formatDatasetResponse,
|
|
725
|
+
formatEntityTable,
|
|
726
|
+
formatJson,
|
|
727
|
+
getActiveProfile,
|
|
728
|
+
listProfiles,
|
|
729
|
+
loadConfig,
|
|
730
|
+
removeProfile,
|
|
731
|
+
saveConfig,
|
|
732
|
+
setActiveProfile,
|
|
733
|
+
updateProfile
|
|
734
|
+
});
|
|
735
|
+
//# sourceMappingURL=index.js.map
|