@getanthill/datastore
Version:
Event-Sourced Datastore
334 lines • 11.5 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const axios_1 = __importDefault(require("axios"));
const get_1 = __importDefault(require("lodash/get"));
const has_1 = __importDefault(require("lodash/has"));
const DEFAULT_HEADERS = Object.freeze({
'Content-Type': 'application/json',
Accept: 'application/json',
});
class Metabase {
constructor(config) {
this.config = {
baseUrl: 'http://localhost:3000',
};
this.sessionToken = '';
this.config = {
...this.config,
...config,
};
this.axios = axios_1.default.create({
baseURL: this.config.baseUrl,
timeout: this.config.timeout,
headers: {
...DEFAULT_HEADERS,
},
});
}
static toSnakeCase(str) {
return str.replace(/ +/g, '_').toLowerCase();
}
static toMetabaseDisplayCase(str) {
return str
.split('_')
.map((s) => s[0].toUpperCase() + s.slice(1))
.join(' ');
}
setSessionToken(token) {
this.sessionToken = token;
this.axios.defaults.headers.common['X-Metabase-Session'] = token;
}
async authenticate() {
const { data: { id }, } = await this.axios.request({
method: 'post',
url: '/api/session',
data: {
username: this.config.username,
password: this.config.password,
},
});
this.setSessionToken(id);
return id;
}
async request(config) {
if (!this.sessionToken) {
throw Metabase.ERRORS.UNAUTHENTICATED;
}
const { data } = await this.axios.request(config);
return data;
}
getCurrentUser() {
return this.request({
method: 'get',
url: '/api/user/current',
});
}
getDatabases() {
return this.request({
method: 'get',
url: '/api/database',
});
}
getTableForeignKeys(tableId) {
return this.request({
method: 'get',
url: `/api/table/${tableId}/fks`,
});
}
getTableFields(tableId) {
return this.getTables().find((t) => t.id === tableId)?.fields ?? [];
}
getField(fieldId) {
return this.request({
method: 'get',
url: `/api/field/${fieldId}`,
});
}
updateField(fieldId, field) {
return this.request({
method: 'put',
url: `/api/field/${fieldId}`,
data: field,
});
}
async getDatabaseTables(databaseId) {
const metadata = await this.request({
method: 'get',
url: `/api/database/${databaseId}/metadata`,
params: {
include_hidden: true,
},
});
return metadata.tables;
}
updateTable(tableId, table) {
return this.request({
method: 'put',
url: `/api/table/${tableId}`,
data: table,
});
}
/** Model schema sync */
static getNormalizedName(item) {
if (Array.isArray(item.nfc_path)) {
let paths = item.nfc_path;
if (['event', 'entity'].includes(paths[0])) {
paths.shift();
}
paths = paths.filter((p) => !!p);
return paths.map((p) => Metabase.toSnakeCase(p)).join('.');
}
return Metabase.toSnakeCase(item.name);
}
static getModelNameFromTable(table) {
return Metabase.getNormalizedName(table).replace(/_(events|snapshots)$/, '');
}
static isEventsTable(table) {
return Metabase.getNormalizedName(table).endsWith('_events');
}
static isSnapshotsTable(table) {
return Metabase.getNormalizedName(table).endsWith('_snapshots');
}
static isCorrelationField(field, model) {
const fieldName = Metabase.getNormalizedName(field);
return model.correlation_field === fieldName;
}
static isEncryptedField(field, model) {
const fieldName = Metabase.getNormalizedName(field);
return (model.encrypted_fields ?? []).includes(fieldName);
}
static getFieldVisibility(field, model, defaultValue = 'sensitive') {
if (Metabase.isEncryptedField(field, model) === true) {
return 'sensitive';
}
if (Metabase.isCorrelationField(field, model) === true) {
return 'normal';
}
const fieldName = Metabase.getNormalizedName(field);
const isEventsTable = Metabase.isEventsTable({
name: field.table_name,
});
if (isEventsTable === true && fieldName === 'type') {
return 'normal';
}
if (['created_at', 'updated_at', 'version'].includes(fieldName)) {
return 'normal';
}
if ((0, has_1.default)(model, `schema.model.properties.${fieldName.split('.').join('.properties.')}`)) {
return 'normal';
}
return defaultValue;
}
/**
* @see https://github.com/metabase/metabase/blob/6a6327646964559e735c3557d8c39f5ceff5dcd8/shared/src/metabase/types.cljc
*/
static getBaseTypeFromJsonSchema(field, model, fieldSchema, defaultBaseType = null) {
if (fieldSchema.type === 'boolean') {
return 'type/Boolean';
}
if (fieldSchema.type === 'integer') {
return 'type/Integer';
}
if (fieldSchema.type === 'number') {
return 'type/Float';
}
return defaultBaseType;
}
/**
* @see https://github.com/metabase/metabase/blob/6a6327646964559e735c3557d8c39f5ceff5dcd8/shared/src/metabase/types.cljc
*/
static getSemanticTypeFromJsonSchema(field, model, fieldSchema, defaultSemanticType = null) {
if (Metabase.isCorrelationField(field, model)) {
return 'type/PK';
}
const fieldName = Metabase.getNormalizedName(field);
const isEventsTable = Metabase.isEventsTable({
name: field.table_name,
});
if (isEventsTable === true && fieldName === 'type') {
return 'type/Category';
}
if (fieldName === 'name') {
return 'type/Name';
}
if (fieldName === 'description') {
return 'type/Description';
}
if (fieldName === 'created_at') {
return 'type/CreationTimestamp';
}
if (fieldName === 'updated_at') {
return 'type/UpdatedTimestamp';
}
if (fieldSchema.type === 'boolean') {
return 'type/Category';
}
return defaultSemanticType;
}
setTables(tables) {
this.config.tables = tables;
return this;
}
getTables() {
return this.config.tables ?? [];
}
getTableById(id) {
return this.getTables().find((table) => table.id === id);
}
setFields(fields) {
this.config.fields = fields;
return this;
}
getFields() {
return this.config.fields ?? [];
}
setModels(models) {
this.config.models = models;
return this;
}
getModels() {
return this.config.models ?? [];
}
setGraph(graph) {
this.config.graph = graph;
return this;
}
getGraph() {
return (this.config.graph ?? {
nodes: [],
edges: [],
});
}
getModelFromTable(table) {
const modelName = Metabase.getModelNameFromTable(table);
return this.getModels().find((m) => modelName === m.name ||
`${this.config.namespace}_${m.name}` === modelName ||
`${m.db}_${m.name}` === modelName);
}
getTableUpdatePayload(table) {
const model = this.getModelFromTable(table);
if (!model) {
return null;
}
const tableName = Metabase.getNormalizedName(table);
const payload = {
name: tableName,
display_name: tableName,
description: model.description ?? null,
visibility_type: Metabase.isSnapshotsTable(table) === true ? 'hidden' : null,
};
if (Metabase.isEventsTable(table) === true) {
payload.description =
'Events associated to these entities. ' + (payload.description || '');
}
return payload;
}
getFieldUpdatePayload(field) {
const table = this.getTableById(field.table_id);
if (!table) {
return null;
}
field.table_name = table.name;
const model = this.getModelFromTable({
name: field.table_name,
});
if (!model) {
return null;
}
const fieldName = Metabase.getNormalizedName(field);
const isCorrelationField = Metabase.isCorrelationField(field, model);
const fieldJsonSchema = (0, get_1.default)(model, `schema.model.properties.${fieldName}`, {});
const payload = {
name: fieldName,
display_name: fieldName,
description: isCorrelationField === true
? 'Correlation field'
: fieldJsonSchema?.description || null,
base_type: Metabase.getBaseTypeFromJsonSchema(field, model, fieldJsonSchema, field.base_type),
semantic_type: Metabase.getSemanticTypeFromJsonSchema(field, model, fieldJsonSchema, field.semantic_type),
visibility_type: Metabase.getFieldVisibility(field, model, field.visibility_type),
};
return this.getFieldUpdatePayloadWithForeignKeys(field, payload);
}
getFieldUpdatePayloadWithForeignKeys(field, payload) {
const graph = this.getGraph();
const table = this.getTableById(field.table_id);
if (!table) {
return payload;
}
const model = this.getModelFromTable(table);
if (!model) {
return payload;
}
const fieldName = Metabase.getNormalizedName(field);
const isCorrelationField = Metabase.isCorrelationField(field, model);
const isEventsTable = Metabase.isEventsTable(table);
const edges = graph.edges.filter((f) => f.source === model.name && f.key === fieldName);
if (isCorrelationField === true && isEventsTable === true) {
const correlationField = this.getFields().find((f) => Metabase.getNormalizedName(this.getTableById(f.table_id)) ===
model.name && Metabase.getNormalizedName(f) === fieldName);
if (correlationField) {
payload.semantic_type = 'type/FK';
payload.fk_target_field_id = correlationField.id;
}
}
else if (edges.length === 1) {
const correlationField = this.getFields().find((f) => Metabase.getNormalizedName(this.getTableById(f.table_id)) ===
edges[0].target &&
Metabase.getNormalizedName(f) === edges[0].correlation_field);
if (correlationField) {
payload.semantic_type = 'type/FK';
payload.fk_target_field_id = correlationField.id;
}
}
return payload;
}
}
Metabase.ERRORS = {
UNAUTHENTICATED: new Error('Client must be authenticated'),
};
exports.default = Metabase;
//# sourceMappingURL=Metabase.js.map