json-rows.mjs
105 lines 3.4 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Import a JSON file whose root is an array of objects (one note per object).
3 * Frontmatter: provenance + optional title; body holds the full object as fenced JSON.
4 */
5
6 import fs from 'fs';
7 import path from 'path';
8 import crypto from 'crypto';
9 import { writeNote } from '../write.mjs';
10 import { normalizeSlug } from '../vault.mjs';
11
12 const MAX_ITEMS = 10_000;
13 const MAX_JSON_BYTES = 50 * 1024 * 1024;
14
15 /**
16 * @param {string} input
17 * @param {{ vaultPath: string, outputBase: string, project?: string, tags: string[], dryRun: boolean }} ctx
18 */
19 export async function importJsonRows(input, ctx) {
20 const { vaultPath, outputBase, project, tags, dryRun } = ctx;
21 const absInput = path.isAbsolute(input) ? input : path.resolve(process.cwd(), input);
22 if (!fs.existsSync(absInput) || !fs.statSync(absInput).isFile()) {
23 throw new Error('json-rows import expects a path to a .json file.');
24 }
25 if (!absInput.toLowerCase().endsWith('.json')) {
26 throw new Error('json-rows import requires a .json file.');
27 }
28 const stat = fs.statSync(absInput);
29 if (stat.size > MAX_JSON_BYTES) {
30 throw new Error(`JSON file too large (max ${MAX_JSON_BYTES} bytes).`);
31 }
32
33 const raw = fs.readFileSync(absInput, 'utf8');
34 let data;
35 try {
36 data = JSON.parse(raw);
37 } catch (e) {
38 throw new Error(`json-rows: invalid JSON (${e && e.message ? e.message : e})`);
39 }
40 if (!Array.isArray(data)) {
41 throw new Error('json-rows: root JSON value must be an array of objects.');
42 }
43 if (data.length > MAX_ITEMS) {
44 throw new Error(`json-rows: array too long (max ${MAX_ITEMS} items).`);
45 }
46
47 const baseName = path.basename(absInput);
48 const imported = [];
49 const now = new Date().toISOString().slice(0, 10);
50 const subdir = path.join(outputBase, 'imports', 'json').replace(/\\/g, '/');
51
52 for (let i = 0; i < data.length; i++) {
53 const item = data[i];
54 if (item == null || typeof item !== 'object' || Array.isArray(item)) {
55 throw new Error(`json-rows: item at index ${i} must be a plain object, not an array.`);
56 }
57
58 const idVal =
59 item.id != null
60 ? String(item.id)
61 : item.uuid != null
62 ? String(item.uuid)
63 : item.source_id != null
64 ? String(item.source_id)
65 : null;
66 const sourceId = idVal
67 ? idVal.slice(0, 200)
68 : crypto.createHash('sha256').update(JSON.stringify(item)).digest('hex').slice(0, 32);
69
70 const titleFrom =
71 typeof item.title === 'string'
72 ? item.title.slice(0, 200)
73 : typeof item.name === 'string'
74 ? item.name.slice(0, 200)
75 : null;
76
77 const frontmatter = {
78 source: 'json-import',
79 source_id: sourceId,
80 date: now,
81 json_file: baseName,
82 item_index: i,
83 ...(titleFrom && { title: titleFrom }),
84 ...(project && { project: normalizeSlug(project) }),
85 ...(tags.length && { tags }),
86 };
87
88 const body = ['## Record', '', '```json', JSON.stringify(item, null, 2), '```'].join('\n');
89 const fileSlug = crypto
90 .createHash('sha256')
91 .update(JSON.stringify(item) + baseName + String(i))
92 .digest('hex')
93 .slice(0, 12);
94 const outputRel = path
95 .join(subdir, `item-${String(i).padStart(5, '0')}-${fileSlug}.md`)
96 .replace(/\\/g, '/');
97
98 if (!dryRun) {
99 writeNote(vaultPath, outputRel, { body, frontmatter });
100 }
101 imported.push({ path: outputRel, source_id: sourceId });
102 }
103
104 return { imported, count: imported.length };
105 }
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 2 days ago