UNPKG

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
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 };