trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
162 lines (159 loc) • 4.38 kB
JavaScript
import {
init_eav_store,
jsonEntityFacts
} from "./chunk-G3XIHPSQ.js";
import {
PROVENANCE,
init_canonical_op
} from "./chunk-RUMOVKR4.js";
// src/server/import.ts
init_eav_store();
init_canonical_op();
import { readFileSync } from "fs";
import { extname } from "path";
async function importFile(kernel, filePath, opts) {
const ext = extname(filePath).toLowerCase();
switch (ext) {
case ".json":
return importJson(kernel, filePath, opts);
case ".ndjson":
case ".jsonl":
return importNdjson(kernel, filePath, opts);
case ".csv":
case ".tsv":
return importCsv(kernel, filePath, opts);
case ".parquet":
return importParquet(kernel, filePath, opts);
default:
throw new Error(
`Unsupported file format: ${ext}. Supported: .json, .ndjson, .jsonl, .csv, .tsv, .parquet`
);
}
}
async function importRecords(kernel, records, opts) {
return _ingestRows(kernel, records, opts);
}
async function importJson(kernel, filePath, opts) {
const raw = readFileSync(filePath, "utf8");
const parsed = JSON.parse(raw);
const rows = Array.isArray(parsed) ? parsed : [parsed];
return _ingestRows(kernel, rows, opts);
}
async function importNdjson(kernel, filePath, opts) {
const raw = readFileSync(filePath, "utf8");
const rows = raw.split("\n").map((l) => l.trim()).filter(Boolean).map((line, i) => {
try {
return JSON.parse(line);
} catch {
return null;
}
}).filter(Boolean);
return _ingestRows(kernel, rows, opts);
}
async function importCsv(kernel, filePath, opts) {
const raw = readFileSync(filePath, "utf8");
const sep = filePath.endsWith(".tsv") ? " " : ",";
const rows = parseCsv(raw, sep);
return _ingestRows(kernel, rows, opts);
}
function parseCsv(raw, sep = ",") {
const lines = raw.split(/\r?\n/);
if (lines.length < 2) return [];
const headers = splitCsvLine(lines[0], sep);
const result = [];
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
if (!line || line.trim() === "") continue;
const values = splitCsvLine(line, sep);
const row = {};
headers.forEach((h, idx) => {
row[h] = values[idx] ?? "";
});
result.push(row);
}
return result;
}
function splitCsvLine(line, sep) {
const result = [];
let current = "";
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (ch === '"') {
if (inQuotes && line[i + 1] === '"') {
current += '"';
i++;
} else {
inQuotes = !inQuotes;
}
} else if (ch === sep && !inQuotes) {
result.push(current);
current = "";
} else {
current += ch;
}
}
result.push(current);
return result;
}
async function importParquet(kernel, filePath, opts) {
let parquet;
try {
parquet = await import("parquetjs");
} catch {
try {
parquet = await import("@dsnp/parquetjs");
} catch {
throw new Error(
"Parquet support requires `parquetjs` or `@dsnp/parquetjs`.\nRun: bun add parquetjs"
);
}
}
const reader = await parquet.ParquetReader.openFile(filePath);
const cursor = reader.getCursor();
const rows = [];
let row;
while ((row = await cursor.next()) !== null) {
rows.push(row);
}
await reader.close();
return _ingestRows(kernel, rows, opts);
}
async function _ingestRows(kernel, rows, opts) {
const result = {
imported: 0,
skipped: 0,
errors: [],
entityIds: []
};
const limit = opts.limit ?? Infinity;
let count = 0;
for (let i = 0; i < rows.length; i++) {
if (count >= limit) break;
const row = rows[i];
if (!row || opts.skipEmpty && Object.keys(row).length === 0) {
result.skipped++;
continue;
}
try {
const entityId = opts.idField ? `${opts.idPrefix ?? "import:"}${String(row[opts.idField] ?? "")}` : `${opts.idPrefix ?? "import:"}${crypto.randomUUID()}`;
const facts = jsonEntityFacts(entityId, row, opts.type);
await kernel.mutate("addFacts", { facts }, {
provenance: PROVENANCE.migration
});
result.imported++;
result.entityIds.push(entityId);
count++;
} catch (err) {
result.errors.push({
row: i,
message: err instanceof Error ? err.message : String(err)
});
}
}
return result;
}
export {
importFile,
importRecords
};