@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
127 lines • 5.26 kB
JavaScript
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { fetchColumnComments, fetchColumns, fetchColumnStats, fetchEnumColumns, fetchForeignKeys, fetchPrimaryKey, fetchTableComment, } from "./client.js";
const SAMPLE_VALUES_LIMIT = 10;
const TIME_TYPES = new Set([
'date',
'time without time zone',
'time with time zone',
'timestamp without time zone',
'timestamp with time zone',
]);
const NUMERIC_TYPES = new Set([
'bigint',
'double precision',
'integer',
'money',
'numeric',
'real',
'smallint',
]);
// Assign a column its semantic role. Mirrors the dimension / time_dimension /
// fact split of the Snowflake semantic view vocabulary. Keys stay dimensions
// even when numeric: an id is something you group or join by, never sum.
export function classifyColumn(name, dataType, keyColumns) {
if (keyColumns.has(name))
return 'dimensions';
if (TIME_TYPES.has(dataType))
return 'time_dimensions';
if (NUMERIC_TYPES.has(dataType))
return 'facts';
return 'dimensions';
}
// Render one column in the semantic vocabulary. Empty fields are omitted
// rather than emitted as null: the whole point of this artifact is to fit an
// agent's context budget, so absent metadata should cost nothing.
export function buildColumnEntry(column, comment, enumInfo, stats) {
const entry = {
name: column.name,
expr: column.name,
data_type: enumInfo ? enumInfo.type : column.type,
};
if (comment)
entry.description = comment;
if (enumInfo) {
entry.is_enum = true;
entry.sample_values = enumInfo.labels.slice(0, SAMPLE_VALUES_LIMIT);
}
else if (stats && stats.most_common_vals.length > 0) {
// most_common_vals is null for high-cardinality columns, so this
// self-selects the ones where example values actually help.
entry.sample_values = stats.most_common_vals.slice(0, SAMPLE_VALUES_LIMIT);
}
return entry;
}
// Render foreign keys as semantic relationships.
export function buildRelationships(foreignKeys, schema, name) {
const relationships = [];
for (const fk of foreignKeys) {
const ref = fk.references;
const columns = [];
fk.columns.forEach((left, i) => {
const right = ref.columns[i];
if (right !== undefined)
columns.push({ left_column: left, right_column: right });
});
relationships.push({
left_table: `${schema}.${name}`,
right_table: `${ref.schema}.${ref.table}`,
relationship_columns: columns,
});
}
return relationships;
}
// Build the derived semantic model for one entity. Uses the Snowflake
// semantic view field vocabulary so the artifact is familiar to models and
// interchangeable with a curated one. Everything here is derived from the
// catalog; synonyms, metrics and verified queries have no catalog source and
// are left for a curated overlay.
export async function buildEntitySemanticJson(accessor, schema, name, kind) {
const columns = await fetchColumns(accessor, schema, name);
const pk = await fetchPrimaryKey(accessor, schema, name);
const fks = await fetchForeignKeys(accessor, schema, name);
const tableComment = await fetchTableComment(accessor, schema, name);
const comments = await fetchColumnComments(accessor, schema, name);
const enums = await fetchEnumColumns(accessor, schema, name);
const stats = await fetchColumnStats(accessor, schema, name);
const keyColumns = new Set(pk);
for (const fk of fks)
for (const c of fk.columns)
keyColumns.add(c);
const buckets = {
dimensions: [],
time_dimensions: [],
facts: [],
};
for (const column of columns) {
const role = classifyColumn(column.name, column.type, keyColumns);
buckets[role].push(buildColumnEntry(column, comments.get(column.name), enums.get(column.name), stats.get(column.name)));
}
const doc = { name, schema, kind };
if (tableComment)
doc.description = tableComment;
if (pk.length > 0)
doc.primary_key = pk;
if (buckets.dimensions.length > 0)
doc.dimensions = buckets.dimensions;
if (buckets.time_dimensions.length > 0)
doc.time_dimensions = buckets.time_dimensions;
if (buckets.facts.length > 0)
doc.facts = buckets.facts;
const relationships = buildRelationships(fks, schema, name);
if (relationships.length > 0)
doc.relationships = relationships;
return doc;
}
//# sourceMappingURL=semantic.js.map