latticesql 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +161 -1
- package/dist/index.cjs +168 -47
- package/dist/index.d.cts +78 -3
- package/dist/index.d.ts +78 -3
- package/dist/index.js +168 -47
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -757,6 +757,165 @@ function truncateContent(content, budget) {
|
|
|
757
757
|
return content.slice(0, budget) + "\n\n*[truncated \u2014 context budget exceeded]*";
|
|
758
758
|
}
|
|
759
759
|
|
|
760
|
+
// src/render/markdown.ts
|
|
761
|
+
function frontmatter(fields) {
|
|
762
|
+
const lines = [`generated_at: "${(/* @__PURE__ */ new Date()).toISOString()}"`];
|
|
763
|
+
for (const [key, val] of Object.entries(fields)) {
|
|
764
|
+
lines.push(typeof val === "string" ? `${key}: "${val}"` : `${key}: ${String(val)}`);
|
|
765
|
+
}
|
|
766
|
+
return `---
|
|
767
|
+
${lines.join("\n")}
|
|
768
|
+
---
|
|
769
|
+
|
|
770
|
+
`;
|
|
771
|
+
}
|
|
772
|
+
function markdownTable(rows, columns) {
|
|
773
|
+
if (rows.length === 0 || columns.length === 0) return "";
|
|
774
|
+
const header = "| " + columns.map((c) => c.header).join(" | ") + " |";
|
|
775
|
+
const separator = "| " + columns.map(() => "---").join(" | ") + " |";
|
|
776
|
+
const body = rows.map((row) => {
|
|
777
|
+
const cells = columns.map((col) => {
|
|
778
|
+
const raw = row[col.key];
|
|
779
|
+
return col.format ? col.format(raw, row) : String(raw ?? "");
|
|
780
|
+
});
|
|
781
|
+
return "| " + cells.join(" | ") + " |";
|
|
782
|
+
});
|
|
783
|
+
return [header, separator, ...body].join("\n") + "\n";
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/session/constants.ts
|
|
787
|
+
function createReadOnlyHeader(options) {
|
|
788
|
+
const generator = options?.generator ?? "Lattice";
|
|
789
|
+
const docsRef = options?.docsRef ?? "the Lattice documentation";
|
|
790
|
+
return `<!-- READ ONLY \u2014 generated by ${generator}. Do not edit directly.
|
|
791
|
+
To update data in Lattice: write entries to SESSION.md in this directory.
|
|
792
|
+
Format: type: write | op: create/update/delete | table: <name> | target: <id>
|
|
793
|
+
See ${docsRef} for the SESSION.md format spec. -->
|
|
794
|
+
|
|
795
|
+
`;
|
|
796
|
+
}
|
|
797
|
+
var READ_ONLY_HEADER = createReadOnlyHeader();
|
|
798
|
+
|
|
799
|
+
// src/render/entity-templates.ts
|
|
800
|
+
var DEFAULT_HEADER = createReadOnlyHeader();
|
|
801
|
+
function compileEntityRender(spec) {
|
|
802
|
+
if (typeof spec === "function") return spec;
|
|
803
|
+
return compileTemplate(spec);
|
|
804
|
+
}
|
|
805
|
+
function compileTemplate(tmpl) {
|
|
806
|
+
switch (tmpl.template) {
|
|
807
|
+
case "entity-table":
|
|
808
|
+
return compileEntityTable(tmpl);
|
|
809
|
+
case "entity-profile":
|
|
810
|
+
return compileEntityProfile(tmpl);
|
|
811
|
+
case "entity-sections":
|
|
812
|
+
return compileEntitySections(tmpl);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
function compileEntityTable(tmpl) {
|
|
816
|
+
return (rows) => {
|
|
817
|
+
const data = tmpl.beforeRender ? tmpl.beforeRender(rows) : rows;
|
|
818
|
+
let md = DEFAULT_HEADER;
|
|
819
|
+
md += frontmatter(tmpl.frontmatter ?? {});
|
|
820
|
+
md += `# ${tmpl.heading}
|
|
821
|
+
|
|
822
|
+
`;
|
|
823
|
+
if (data.length === 0) {
|
|
824
|
+
md += tmpl.emptyMessage ?? "*No data.*\n";
|
|
825
|
+
} else {
|
|
826
|
+
md += markdownTable(data, tmpl.columns);
|
|
827
|
+
}
|
|
828
|
+
return md;
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
function compileEntityProfile(tmpl) {
|
|
832
|
+
return (rows) => {
|
|
833
|
+
const data = tmpl.beforeRender ? tmpl.beforeRender(rows) : rows;
|
|
834
|
+
const r = data[0];
|
|
835
|
+
if (!r) return "";
|
|
836
|
+
let md = DEFAULT_HEADER;
|
|
837
|
+
if (tmpl.frontmatter) {
|
|
838
|
+
const fm = typeof tmpl.frontmatter === "function" ? tmpl.frontmatter(r) : tmpl.frontmatter;
|
|
839
|
+
md += frontmatter(fm);
|
|
840
|
+
} else {
|
|
841
|
+
md += frontmatter({});
|
|
842
|
+
}
|
|
843
|
+
const heading = typeof tmpl.heading === "function" ? tmpl.heading(r) : tmpl.heading;
|
|
844
|
+
md += `# ${heading}
|
|
845
|
+
|
|
846
|
+
`;
|
|
847
|
+
for (const field of tmpl.fields) {
|
|
848
|
+
const val = r[field.key];
|
|
849
|
+
if (val === null || val === void 0) continue;
|
|
850
|
+
const formatted = field.format ? field.format(val, r) : String(val);
|
|
851
|
+
if (formatted) {
|
|
852
|
+
md += `**${field.label}:** ${formatted}
|
|
853
|
+
`;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (tmpl.sections) {
|
|
857
|
+
for (const section of tmpl.sections) {
|
|
858
|
+
const rawJson = r[`_${section.key}`];
|
|
859
|
+
if (!rawJson) continue;
|
|
860
|
+
if (section.condition && !section.condition(r)) continue;
|
|
861
|
+
const items = JSON.parse(rawJson);
|
|
862
|
+
if (items.length === 0) continue;
|
|
863
|
+
const sectionHeading = typeof section.heading === "function" ? section.heading(r) : section.heading;
|
|
864
|
+
md += `
|
|
865
|
+
## ${sectionHeading}
|
|
866
|
+
|
|
867
|
+
`;
|
|
868
|
+
if (section.render === "table" && section.columns) {
|
|
869
|
+
md += markdownTable(items, section.columns);
|
|
870
|
+
} else if (section.render === "list" && section.formatItem) {
|
|
871
|
+
for (const item of items) {
|
|
872
|
+
md += `- ${section.formatItem(item)}
|
|
873
|
+
`;
|
|
874
|
+
}
|
|
875
|
+
} else if (typeof section.render === "function") {
|
|
876
|
+
md += section.render(items);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return md;
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
function compileEntitySections(tmpl) {
|
|
884
|
+
return (rows) => {
|
|
885
|
+
const data = tmpl.beforeRender ? tmpl.beforeRender(rows) : rows;
|
|
886
|
+
let md = DEFAULT_HEADER;
|
|
887
|
+
md += frontmatter(tmpl.frontmatter ?? {});
|
|
888
|
+
md += `# ${tmpl.heading}
|
|
889
|
+
|
|
890
|
+
`;
|
|
891
|
+
if (data.length === 0) {
|
|
892
|
+
md += tmpl.emptyMessage ?? "*No data.*\n";
|
|
893
|
+
return md;
|
|
894
|
+
}
|
|
895
|
+
for (const row of data) {
|
|
896
|
+
md += `## ${tmpl.perRow.heading(row)}
|
|
897
|
+
`;
|
|
898
|
+
if (tmpl.perRow.metadata?.length) {
|
|
899
|
+
const parts = tmpl.perRow.metadata.map((m) => {
|
|
900
|
+
const val = row[m.key];
|
|
901
|
+
const formatted = m.format ? m.format(val) : String(val ?? "");
|
|
902
|
+
return `**${m.label}:** ${formatted}`;
|
|
903
|
+
}).filter(Boolean);
|
|
904
|
+
if (parts.length > 0) {
|
|
905
|
+
md += parts.join(" | ") + "\n";
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
if (tmpl.perRow.body) {
|
|
909
|
+
md += `
|
|
910
|
+
${tmpl.perRow.body(row)}
|
|
911
|
+
`;
|
|
912
|
+
}
|
|
913
|
+
md += "\n";
|
|
914
|
+
}
|
|
915
|
+
return md;
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
|
|
760
919
|
// src/lifecycle/cleanup.ts
|
|
761
920
|
import { join as join4 } from "path";
|
|
762
921
|
import { existsSync as existsSync4, readdirSync, unlinkSync, rmdirSync, statSync } from "fs";
|
|
@@ -966,7 +1125,8 @@ var RenderEngine = class {
|
|
|
966
1125
|
const source = mergeDefaults ? { ...def.sourceDefaults, ...spec.source } : spec.source;
|
|
967
1126
|
const rows = resolveEntitySource(source, entityRow, entityPk, this._adapter);
|
|
968
1127
|
if (spec.omitIfEmpty && rows.length === 0) continue;
|
|
969
|
-
const
|
|
1128
|
+
const renderFn = compileEntityRender(spec.render);
|
|
1129
|
+
const content = truncateContent(renderFn(rows), spec.budget);
|
|
970
1130
|
renderedFiles.set(filename, content);
|
|
971
1131
|
const filePath = join5(entityDir, filename);
|
|
972
1132
|
if (atomicWrite(filePath, content)) {
|
package/dist/index.cjs
CHANGED
|
@@ -499,6 +499,172 @@ function truncateContent(content, budget) {
|
|
|
499
499
|
return content.slice(0, budget) + "\n\n*[truncated \u2014 context budget exceeded]*";
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
+
// src/render/markdown.ts
|
|
503
|
+
function frontmatter(fields) {
|
|
504
|
+
const lines = [`generated_at: "${(/* @__PURE__ */ new Date()).toISOString()}"`];
|
|
505
|
+
for (const [key, val] of Object.entries(fields)) {
|
|
506
|
+
lines.push(typeof val === "string" ? `${key}: "${val}"` : `${key}: ${String(val)}`);
|
|
507
|
+
}
|
|
508
|
+
return `---
|
|
509
|
+
${lines.join("\n")}
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
`;
|
|
513
|
+
}
|
|
514
|
+
function markdownTable(rows, columns) {
|
|
515
|
+
if (rows.length === 0 || columns.length === 0) return "";
|
|
516
|
+
const header = "| " + columns.map((c) => c.header).join(" | ") + " |";
|
|
517
|
+
const separator = "| " + columns.map(() => "---").join(" | ") + " |";
|
|
518
|
+
const body = rows.map((row) => {
|
|
519
|
+
const cells = columns.map((col) => {
|
|
520
|
+
const raw = row[col.key];
|
|
521
|
+
return col.format ? col.format(raw, row) : String(raw ?? "");
|
|
522
|
+
});
|
|
523
|
+
return "| " + cells.join(" | ") + " |";
|
|
524
|
+
});
|
|
525
|
+
return [header, separator, ...body].join("\n") + "\n";
|
|
526
|
+
}
|
|
527
|
+
function slugify(name) {
|
|
528
|
+
return name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/\u0131/g, "i").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
529
|
+
}
|
|
530
|
+
function truncate(content, maxChars, notice = "\n\n*[truncated \u2014 context budget exceeded]*") {
|
|
531
|
+
if (content.length <= maxChars) return content;
|
|
532
|
+
return content.slice(0, maxChars) + notice;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// src/session/constants.ts
|
|
536
|
+
function createReadOnlyHeader(options) {
|
|
537
|
+
const generator = options?.generator ?? "Lattice";
|
|
538
|
+
const docsRef = options?.docsRef ?? "the Lattice documentation";
|
|
539
|
+
return `<!-- READ ONLY \u2014 generated by ${generator}. Do not edit directly.
|
|
540
|
+
To update data in Lattice: write entries to SESSION.md in this directory.
|
|
541
|
+
Format: type: write | op: create/update/delete | table: <name> | target: <id>
|
|
542
|
+
See ${docsRef} for the SESSION.md format spec. -->
|
|
543
|
+
|
|
544
|
+
`;
|
|
545
|
+
}
|
|
546
|
+
var READ_ONLY_HEADER = createReadOnlyHeader();
|
|
547
|
+
|
|
548
|
+
// src/render/entity-templates.ts
|
|
549
|
+
var DEFAULT_HEADER = createReadOnlyHeader();
|
|
550
|
+
function compileEntityRender(spec) {
|
|
551
|
+
if (typeof spec === "function") return spec;
|
|
552
|
+
return compileTemplate(spec);
|
|
553
|
+
}
|
|
554
|
+
function compileTemplate(tmpl) {
|
|
555
|
+
switch (tmpl.template) {
|
|
556
|
+
case "entity-table":
|
|
557
|
+
return compileEntityTable(tmpl);
|
|
558
|
+
case "entity-profile":
|
|
559
|
+
return compileEntityProfile(tmpl);
|
|
560
|
+
case "entity-sections":
|
|
561
|
+
return compileEntitySections(tmpl);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function compileEntityTable(tmpl) {
|
|
565
|
+
return (rows) => {
|
|
566
|
+
const data = tmpl.beforeRender ? tmpl.beforeRender(rows) : rows;
|
|
567
|
+
let md = DEFAULT_HEADER;
|
|
568
|
+
md += frontmatter(tmpl.frontmatter ?? {});
|
|
569
|
+
md += `# ${tmpl.heading}
|
|
570
|
+
|
|
571
|
+
`;
|
|
572
|
+
if (data.length === 0) {
|
|
573
|
+
md += tmpl.emptyMessage ?? "*No data.*\n";
|
|
574
|
+
} else {
|
|
575
|
+
md += markdownTable(data, tmpl.columns);
|
|
576
|
+
}
|
|
577
|
+
return md;
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function compileEntityProfile(tmpl) {
|
|
581
|
+
return (rows) => {
|
|
582
|
+
const data = tmpl.beforeRender ? tmpl.beforeRender(rows) : rows;
|
|
583
|
+
const r = data[0];
|
|
584
|
+
if (!r) return "";
|
|
585
|
+
let md = DEFAULT_HEADER;
|
|
586
|
+
if (tmpl.frontmatter) {
|
|
587
|
+
const fm = typeof tmpl.frontmatter === "function" ? tmpl.frontmatter(r) : tmpl.frontmatter;
|
|
588
|
+
md += frontmatter(fm);
|
|
589
|
+
} else {
|
|
590
|
+
md += frontmatter({});
|
|
591
|
+
}
|
|
592
|
+
const heading = typeof tmpl.heading === "function" ? tmpl.heading(r) : tmpl.heading;
|
|
593
|
+
md += `# ${heading}
|
|
594
|
+
|
|
595
|
+
`;
|
|
596
|
+
for (const field of tmpl.fields) {
|
|
597
|
+
const val = r[field.key];
|
|
598
|
+
if (val === null || val === void 0) continue;
|
|
599
|
+
const formatted = field.format ? field.format(val, r) : String(val);
|
|
600
|
+
if (formatted) {
|
|
601
|
+
md += `**${field.label}:** ${formatted}
|
|
602
|
+
`;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (tmpl.sections) {
|
|
606
|
+
for (const section of tmpl.sections) {
|
|
607
|
+
const rawJson = r[`_${section.key}`];
|
|
608
|
+
if (!rawJson) continue;
|
|
609
|
+
if (section.condition && !section.condition(r)) continue;
|
|
610
|
+
const items = JSON.parse(rawJson);
|
|
611
|
+
if (items.length === 0) continue;
|
|
612
|
+
const sectionHeading = typeof section.heading === "function" ? section.heading(r) : section.heading;
|
|
613
|
+
md += `
|
|
614
|
+
## ${sectionHeading}
|
|
615
|
+
|
|
616
|
+
`;
|
|
617
|
+
if (section.render === "table" && section.columns) {
|
|
618
|
+
md += markdownTable(items, section.columns);
|
|
619
|
+
} else if (section.render === "list" && section.formatItem) {
|
|
620
|
+
for (const item of items) {
|
|
621
|
+
md += `- ${section.formatItem(item)}
|
|
622
|
+
`;
|
|
623
|
+
}
|
|
624
|
+
} else if (typeof section.render === "function") {
|
|
625
|
+
md += section.render(items);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return md;
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function compileEntitySections(tmpl) {
|
|
633
|
+
return (rows) => {
|
|
634
|
+
const data = tmpl.beforeRender ? tmpl.beforeRender(rows) : rows;
|
|
635
|
+
let md = DEFAULT_HEADER;
|
|
636
|
+
md += frontmatter(tmpl.frontmatter ?? {});
|
|
637
|
+
md += `# ${tmpl.heading}
|
|
638
|
+
|
|
639
|
+
`;
|
|
640
|
+
if (data.length === 0) {
|
|
641
|
+
md += tmpl.emptyMessage ?? "*No data.*\n";
|
|
642
|
+
return md;
|
|
643
|
+
}
|
|
644
|
+
for (const row of data) {
|
|
645
|
+
md += `## ${tmpl.perRow.heading(row)}
|
|
646
|
+
`;
|
|
647
|
+
if (tmpl.perRow.metadata?.length) {
|
|
648
|
+
const parts = tmpl.perRow.metadata.map((m) => {
|
|
649
|
+
const val = row[m.key];
|
|
650
|
+
const formatted = m.format ? m.format(val) : String(val ?? "");
|
|
651
|
+
return `**${m.label}:** ${formatted}`;
|
|
652
|
+
}).filter(Boolean);
|
|
653
|
+
if (parts.length > 0) {
|
|
654
|
+
md += parts.join(" | ") + "\n";
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (tmpl.perRow.body) {
|
|
658
|
+
md += `
|
|
659
|
+
${tmpl.perRow.body(row)}
|
|
660
|
+
`;
|
|
661
|
+
}
|
|
662
|
+
md += "\n";
|
|
663
|
+
}
|
|
664
|
+
return md;
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
502
668
|
// src/lifecycle/cleanup.ts
|
|
503
669
|
var import_node_path3 = require("path");
|
|
504
670
|
var import_node_fs3 = require("fs");
|
|
@@ -708,7 +874,8 @@ var RenderEngine = class {
|
|
|
708
874
|
const source = mergeDefaults ? { ...def.sourceDefaults, ...spec.source } : spec.source;
|
|
709
875
|
const rows = resolveEntitySource(source, entityRow, entityPk, this._adapter);
|
|
710
876
|
if (spec.omitIfEmpty && rows.length === 0) continue;
|
|
711
|
-
const
|
|
877
|
+
const renderFn = compileEntityRender(spec.render);
|
|
878
|
+
const content = truncateContent(renderFn(rows), spec.budget);
|
|
712
879
|
renderedFiles.set(filename, content);
|
|
713
880
|
const filePath = (0, import_node_path4.join)(entityDir, filename);
|
|
714
881
|
if (atomicWrite(filePath, content)) {
|
|
@@ -1641,39 +1808,6 @@ var Lattice = class {
|
|
|
1641
1808
|
}
|
|
1642
1809
|
};
|
|
1643
1810
|
|
|
1644
|
-
// src/render/markdown.ts
|
|
1645
|
-
function frontmatter(fields) {
|
|
1646
|
-
const lines = [`generated_at: "${(/* @__PURE__ */ new Date()).toISOString()}"`];
|
|
1647
|
-
for (const [key, val] of Object.entries(fields)) {
|
|
1648
|
-
lines.push(typeof val === "string" ? `${key}: "${val}"` : `${key}: ${String(val)}`);
|
|
1649
|
-
}
|
|
1650
|
-
return `---
|
|
1651
|
-
${lines.join("\n")}
|
|
1652
|
-
---
|
|
1653
|
-
|
|
1654
|
-
`;
|
|
1655
|
-
}
|
|
1656
|
-
function markdownTable(rows, columns) {
|
|
1657
|
-
if (rows.length === 0 || columns.length === 0) return "";
|
|
1658
|
-
const header = "| " + columns.map((c) => c.header).join(" | ") + " |";
|
|
1659
|
-
const separator = "| " + columns.map(() => "---").join(" | ") + " |";
|
|
1660
|
-
const body = rows.map((row) => {
|
|
1661
|
-
const cells = columns.map((col) => {
|
|
1662
|
-
const raw = row[col.key];
|
|
1663
|
-
return col.format ? col.format(raw, row) : String(raw ?? "");
|
|
1664
|
-
});
|
|
1665
|
-
return "| " + cells.join(" | ") + " |";
|
|
1666
|
-
});
|
|
1667
|
-
return [header, separator, ...body].join("\n") + "\n";
|
|
1668
|
-
}
|
|
1669
|
-
function slugify(name) {
|
|
1670
|
-
return name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/\u0131/g, "i").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
1671
|
-
}
|
|
1672
|
-
function truncate(content, maxChars, notice = "\n\n*[truncated \u2014 context budget exceeded]*") {
|
|
1673
|
-
if (content.length <= maxChars) return content;
|
|
1674
|
-
return content.slice(0, maxChars) + notice;
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
1811
|
// src/session/parser.ts
|
|
1678
1812
|
var import_node_crypto3 = require("crypto");
|
|
1679
1813
|
function generateWriteEntryId(timestamp, agentName, op, table, target) {
|
|
@@ -2058,19 +2192,6 @@ function applyWriteEntry(db, entry) {
|
|
|
2058
2192
|
return { ok: false, reason: `DB error: ${message}` };
|
|
2059
2193
|
}
|
|
2060
2194
|
}
|
|
2061
|
-
|
|
2062
|
-
// src/session/constants.ts
|
|
2063
|
-
function createReadOnlyHeader(options) {
|
|
2064
|
-
const generator = options?.generator ?? "Lattice";
|
|
2065
|
-
const docsRef = options?.docsRef ?? "the Lattice documentation";
|
|
2066
|
-
return `<!-- READ ONLY \u2014 generated by ${generator}. Do not edit directly.
|
|
2067
|
-
To update data in Lattice: write entries to SESSION.md in this directory.
|
|
2068
|
-
Format: type: write | op: create/update/delete | table: <name> | target: <id>
|
|
2069
|
-
See ${docsRef} for the SESSION.md format spec. -->
|
|
2070
|
-
|
|
2071
|
-
`;
|
|
2072
|
-
}
|
|
2073
|
-
var READ_ONLY_HEADER = createReadOnlyHeader();
|
|
2074
2195
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2075
2196
|
0 && (module.exports = {
|
|
2076
2197
|
DEFAULT_ENTRY_TYPES,
|
package/dist/index.d.cts
CHANGED
|
@@ -266,14 +266,89 @@ type EntityFileSource = SelfSource | HasManySource | ManyToManySource | BelongsT
|
|
|
266
266
|
* }
|
|
267
267
|
* ```
|
|
268
268
|
*/
|
|
269
|
+
/** Column spec for entity-table template (reuses MarkdownTableColumn). */
|
|
270
|
+
interface EntityTableColumn {
|
|
271
|
+
key: string;
|
|
272
|
+
header: string;
|
|
273
|
+
format?: (val: unknown, row: Row) => string;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Render a heading + GFM table. Auto-prepends read-only header + frontmatter.
|
|
277
|
+
*/
|
|
278
|
+
interface EntityTableTemplate {
|
|
279
|
+
template: 'entity-table';
|
|
280
|
+
heading: string;
|
|
281
|
+
columns: EntityTableColumn[];
|
|
282
|
+
emptyMessage?: string;
|
|
283
|
+
frontmatter?: Record<string, string | number | boolean>;
|
|
284
|
+
beforeRender?: (rows: Row[]) => Row[];
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Field spec for entity-profile template.
|
|
288
|
+
*/
|
|
289
|
+
interface EntityProfileField {
|
|
290
|
+
key: string;
|
|
291
|
+
label: string;
|
|
292
|
+
format?: (val: unknown, row: Row) => string;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Section spec for entity-profile template (renders enriched JSON arrays).
|
|
296
|
+
*/
|
|
297
|
+
interface EntityProfileSection {
|
|
298
|
+
/** Key of the enriched field (e.g. 'agents' → reads row._agents). */
|
|
299
|
+
key: string;
|
|
300
|
+
heading: string | ((row: Row) => string);
|
|
301
|
+
condition?: (row: Row) => boolean;
|
|
302
|
+
render: 'table' | 'list' | ((items: Row[]) => string);
|
|
303
|
+
columns?: EntityTableColumn[];
|
|
304
|
+
formatItem?: (item: Row) => string;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Render entity profile: heading + field-value pairs + optional enriched sections.
|
|
308
|
+
*/
|
|
309
|
+
interface EntityProfileTemplate {
|
|
310
|
+
template: 'entity-profile';
|
|
311
|
+
heading: string | ((row: Row) => string);
|
|
312
|
+
fields: EntityProfileField[];
|
|
313
|
+
sections?: EntityProfileSection[];
|
|
314
|
+
frontmatter?: Record<string, string | number | boolean> | ((row: Row) => Record<string, string | number | boolean>);
|
|
315
|
+
beforeRender?: (rows: Row[]) => Row[];
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Per-row section spec for entity-sections template.
|
|
319
|
+
*/
|
|
320
|
+
interface EntitySectionPerRow {
|
|
321
|
+
heading: (row: Row) => string;
|
|
322
|
+
metadata?: Array<{
|
|
323
|
+
key: string;
|
|
324
|
+
label: string;
|
|
325
|
+
format?: (val: unknown) => string;
|
|
326
|
+
}>;
|
|
327
|
+
body?: (row: Row) => string;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Render per-row sections: heading + metadata key-value + body text per row.
|
|
331
|
+
*/
|
|
332
|
+
interface EntitySectionsTemplate {
|
|
333
|
+
template: 'entity-sections';
|
|
334
|
+
heading: string;
|
|
335
|
+
perRow: EntitySectionPerRow;
|
|
336
|
+
emptyMessage?: string;
|
|
337
|
+
frontmatter?: Record<string, string | number | boolean>;
|
|
338
|
+
beforeRender?: (rows: Row[]) => Row[];
|
|
339
|
+
}
|
|
340
|
+
/** Union of all entity render template types. */
|
|
341
|
+
type EntityRenderTemplate = EntityTableTemplate | EntityProfileTemplate | EntitySectionsTemplate;
|
|
342
|
+
/** Accepted values for EntityFileSpec.render — function or template object. */
|
|
343
|
+
type EntityRenderSpec = ((rows: Row[]) => string) | EntityRenderTemplate;
|
|
269
344
|
interface EntityFileSpec {
|
|
270
345
|
/** Determines what rows are passed to {@link render}. */
|
|
271
346
|
source: EntityFileSource;
|
|
272
347
|
/**
|
|
273
348
|
* Converts the resolved rows into the file's markdown content.
|
|
274
|
-
*
|
|
349
|
+
* Accepts a function `(rows) => string` or a declarative template object.
|
|
275
350
|
*/
|
|
276
|
-
render:
|
|
351
|
+
render: EntityRenderSpec;
|
|
277
352
|
/**
|
|
278
353
|
* Maximum number of characters allowed in the rendered output.
|
|
279
354
|
* Content exceeding this limit is truncated with a notice appended.
|
|
@@ -1230,4 +1305,4 @@ declare function createReadOnlyHeader(options?: ReadOnlyHeaderOptions): string;
|
|
|
1230
1305
|
*/
|
|
1231
1306
|
declare const READ_ONLY_HEADER: string;
|
|
1232
1307
|
|
|
1233
|
-
export { type ApplyWriteResult, type AuditEvent, type BelongsToRelation, type BelongsToSource, type BuiltinTemplateName, type CleanupOptions, type CleanupResult, type CountOptions, type CustomSource, DEFAULT_ENTRY_TYPES, DEFAULT_TYPE_ALIASES, type EnrichedSource, type EnrichmentLookup, type EntityContextDefinition, type EntityContextManifestEntry, type EntityFileSource, type EntityFileSpec, type Filter, type FilterOp, type HasManyRelation, type HasManySource, type InitOptions, Lattice, type LatticeConfig, type LatticeConfigInput, type LatticeEntityDef, type LatticeEntityRenderSpec, type LatticeFieldDef, type LatticeFieldType, type LatticeManifest, type LatticeOptions, type ManyToManySource, type MarkdownTableColumn, type Migration, type MultiTableDefinition, type OrderBySpec, type ParseError, type ParseResult, type ParsedConfig, type PkLookup, type PrimaryKey, type QueryOptions, READ_ONLY_HEADER, type ReadOnlyHeaderOptions, type ReconcileOptions, type ReconcileResult, type Relation, type RenderHooks, type RenderResult, type RenderSpec, type Row, type SecurityOptions, type SelfSource, type SessionEntry, type SessionParseOptions, type SessionWriteEntry, type SessionWriteOp, type SessionWriteParseResult, type SourceQueryOptions, type StopFn, type SyncResult, type TableDefinition, type TemplateRenderSpec, type WatchOptions, type WritebackDefinition, applyWriteEntry, createReadOnlyHeader, frontmatter, generateEntryId, generateWriteEntryId, manifestPath, markdownTable, parseConfigFile, parseConfigString, parseMarkdownEntries, parseSessionMD, parseSessionWrites, readManifest, slugify, truncate, validateEntryId, writeManifest };
|
|
1308
|
+
export { type ApplyWriteResult, type AuditEvent, type BelongsToRelation, type BelongsToSource, type BuiltinTemplateName, type CleanupOptions, type CleanupResult, type CountOptions, type CustomSource, DEFAULT_ENTRY_TYPES, DEFAULT_TYPE_ALIASES, type EnrichedSource, type EnrichmentLookup, type EntityContextDefinition, type EntityContextManifestEntry, type EntityFileSource, type EntityFileSpec, type EntityProfileField, type EntityProfileSection, type EntityProfileTemplate, type EntityRenderSpec, type EntityRenderTemplate, type EntitySectionPerRow, type EntitySectionsTemplate, type EntityTableColumn, type EntityTableTemplate, type Filter, type FilterOp, type HasManyRelation, type HasManySource, type InitOptions, Lattice, type LatticeConfig, type LatticeConfigInput, type LatticeEntityDef, type LatticeEntityRenderSpec, type LatticeFieldDef, type LatticeFieldType, type LatticeManifest, type LatticeOptions, type ManyToManySource, type MarkdownTableColumn, type Migration, type MultiTableDefinition, type OrderBySpec, type ParseError, type ParseResult, type ParsedConfig, type PkLookup, type PrimaryKey, type QueryOptions, READ_ONLY_HEADER, type ReadOnlyHeaderOptions, type ReconcileOptions, type ReconcileResult, type Relation, type RenderHooks, type RenderResult, type RenderSpec, type Row, type SecurityOptions, type SelfSource, type SessionEntry, type SessionParseOptions, type SessionWriteEntry, type SessionWriteOp, type SessionWriteParseResult, type SourceQueryOptions, type StopFn, type SyncResult, type TableDefinition, type TemplateRenderSpec, type WatchOptions, type WritebackDefinition, applyWriteEntry, createReadOnlyHeader, frontmatter, generateEntryId, generateWriteEntryId, manifestPath, markdownTable, parseConfigFile, parseConfigString, parseMarkdownEntries, parseSessionMD, parseSessionWrites, readManifest, slugify, truncate, validateEntryId, writeManifest };
|
package/dist/index.d.ts
CHANGED
|
@@ -266,14 +266,89 @@ type EntityFileSource = SelfSource | HasManySource | ManyToManySource | BelongsT
|
|
|
266
266
|
* }
|
|
267
267
|
* ```
|
|
268
268
|
*/
|
|
269
|
+
/** Column spec for entity-table template (reuses MarkdownTableColumn). */
|
|
270
|
+
interface EntityTableColumn {
|
|
271
|
+
key: string;
|
|
272
|
+
header: string;
|
|
273
|
+
format?: (val: unknown, row: Row) => string;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Render a heading + GFM table. Auto-prepends read-only header + frontmatter.
|
|
277
|
+
*/
|
|
278
|
+
interface EntityTableTemplate {
|
|
279
|
+
template: 'entity-table';
|
|
280
|
+
heading: string;
|
|
281
|
+
columns: EntityTableColumn[];
|
|
282
|
+
emptyMessage?: string;
|
|
283
|
+
frontmatter?: Record<string, string | number | boolean>;
|
|
284
|
+
beforeRender?: (rows: Row[]) => Row[];
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Field spec for entity-profile template.
|
|
288
|
+
*/
|
|
289
|
+
interface EntityProfileField {
|
|
290
|
+
key: string;
|
|
291
|
+
label: string;
|
|
292
|
+
format?: (val: unknown, row: Row) => string;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Section spec for entity-profile template (renders enriched JSON arrays).
|
|
296
|
+
*/
|
|
297
|
+
interface EntityProfileSection {
|
|
298
|
+
/** Key of the enriched field (e.g. 'agents' → reads row._agents). */
|
|
299
|
+
key: string;
|
|
300
|
+
heading: string | ((row: Row) => string);
|
|
301
|
+
condition?: (row: Row) => boolean;
|
|
302
|
+
render: 'table' | 'list' | ((items: Row[]) => string);
|
|
303
|
+
columns?: EntityTableColumn[];
|
|
304
|
+
formatItem?: (item: Row) => string;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Render entity profile: heading + field-value pairs + optional enriched sections.
|
|
308
|
+
*/
|
|
309
|
+
interface EntityProfileTemplate {
|
|
310
|
+
template: 'entity-profile';
|
|
311
|
+
heading: string | ((row: Row) => string);
|
|
312
|
+
fields: EntityProfileField[];
|
|
313
|
+
sections?: EntityProfileSection[];
|
|
314
|
+
frontmatter?: Record<string, string | number | boolean> | ((row: Row) => Record<string, string | number | boolean>);
|
|
315
|
+
beforeRender?: (rows: Row[]) => Row[];
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Per-row section spec for entity-sections template.
|
|
319
|
+
*/
|
|
320
|
+
interface EntitySectionPerRow {
|
|
321
|
+
heading: (row: Row) => string;
|
|
322
|
+
metadata?: Array<{
|
|
323
|
+
key: string;
|
|
324
|
+
label: string;
|
|
325
|
+
format?: (val: unknown) => string;
|
|
326
|
+
}>;
|
|
327
|
+
body?: (row: Row) => string;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Render per-row sections: heading + metadata key-value + body text per row.
|
|
331
|
+
*/
|
|
332
|
+
interface EntitySectionsTemplate {
|
|
333
|
+
template: 'entity-sections';
|
|
334
|
+
heading: string;
|
|
335
|
+
perRow: EntitySectionPerRow;
|
|
336
|
+
emptyMessage?: string;
|
|
337
|
+
frontmatter?: Record<string, string | number | boolean>;
|
|
338
|
+
beforeRender?: (rows: Row[]) => Row[];
|
|
339
|
+
}
|
|
340
|
+
/** Union of all entity render template types. */
|
|
341
|
+
type EntityRenderTemplate = EntityTableTemplate | EntityProfileTemplate | EntitySectionsTemplate;
|
|
342
|
+
/** Accepted values for EntityFileSpec.render — function or template object. */
|
|
343
|
+
type EntityRenderSpec = ((rows: Row[]) => string) | EntityRenderTemplate;
|
|
269
344
|
interface EntityFileSpec {
|
|
270
345
|
/** Determines what rows are passed to {@link render}. */
|
|
271
346
|
source: EntityFileSource;
|
|
272
347
|
/**
|
|
273
348
|
* Converts the resolved rows into the file's markdown content.
|
|
274
|
-
*
|
|
349
|
+
* Accepts a function `(rows) => string` or a declarative template object.
|
|
275
350
|
*/
|
|
276
|
-
render:
|
|
351
|
+
render: EntityRenderSpec;
|
|
277
352
|
/**
|
|
278
353
|
* Maximum number of characters allowed in the rendered output.
|
|
279
354
|
* Content exceeding this limit is truncated with a notice appended.
|
|
@@ -1230,4 +1305,4 @@ declare function createReadOnlyHeader(options?: ReadOnlyHeaderOptions): string;
|
|
|
1230
1305
|
*/
|
|
1231
1306
|
declare const READ_ONLY_HEADER: string;
|
|
1232
1307
|
|
|
1233
|
-
export { type ApplyWriteResult, type AuditEvent, type BelongsToRelation, type BelongsToSource, type BuiltinTemplateName, type CleanupOptions, type CleanupResult, type CountOptions, type CustomSource, DEFAULT_ENTRY_TYPES, DEFAULT_TYPE_ALIASES, type EnrichedSource, type EnrichmentLookup, type EntityContextDefinition, type EntityContextManifestEntry, type EntityFileSource, type EntityFileSpec, type Filter, type FilterOp, type HasManyRelation, type HasManySource, type InitOptions, Lattice, type LatticeConfig, type LatticeConfigInput, type LatticeEntityDef, type LatticeEntityRenderSpec, type LatticeFieldDef, type LatticeFieldType, type LatticeManifest, type LatticeOptions, type ManyToManySource, type MarkdownTableColumn, type Migration, type MultiTableDefinition, type OrderBySpec, type ParseError, type ParseResult, type ParsedConfig, type PkLookup, type PrimaryKey, type QueryOptions, READ_ONLY_HEADER, type ReadOnlyHeaderOptions, type ReconcileOptions, type ReconcileResult, type Relation, type RenderHooks, type RenderResult, type RenderSpec, type Row, type SecurityOptions, type SelfSource, type SessionEntry, type SessionParseOptions, type SessionWriteEntry, type SessionWriteOp, type SessionWriteParseResult, type SourceQueryOptions, type StopFn, type SyncResult, type TableDefinition, type TemplateRenderSpec, type WatchOptions, type WritebackDefinition, applyWriteEntry, createReadOnlyHeader, frontmatter, generateEntryId, generateWriteEntryId, manifestPath, markdownTable, parseConfigFile, parseConfigString, parseMarkdownEntries, parseSessionMD, parseSessionWrites, readManifest, slugify, truncate, validateEntryId, writeManifest };
|
|
1308
|
+
export { type ApplyWriteResult, type AuditEvent, type BelongsToRelation, type BelongsToSource, type BuiltinTemplateName, type CleanupOptions, type CleanupResult, type CountOptions, type CustomSource, DEFAULT_ENTRY_TYPES, DEFAULT_TYPE_ALIASES, type EnrichedSource, type EnrichmentLookup, type EntityContextDefinition, type EntityContextManifestEntry, type EntityFileSource, type EntityFileSpec, type EntityProfileField, type EntityProfileSection, type EntityProfileTemplate, type EntityRenderSpec, type EntityRenderTemplate, type EntitySectionPerRow, type EntitySectionsTemplate, type EntityTableColumn, type EntityTableTemplate, type Filter, type FilterOp, type HasManyRelation, type HasManySource, type InitOptions, Lattice, type LatticeConfig, type LatticeConfigInput, type LatticeEntityDef, type LatticeEntityRenderSpec, type LatticeFieldDef, type LatticeFieldType, type LatticeManifest, type LatticeOptions, type ManyToManySource, type MarkdownTableColumn, type Migration, type MultiTableDefinition, type OrderBySpec, type ParseError, type ParseResult, type ParsedConfig, type PkLookup, type PrimaryKey, type QueryOptions, READ_ONLY_HEADER, type ReadOnlyHeaderOptions, type ReconcileOptions, type ReconcileResult, type Relation, type RenderHooks, type RenderResult, type RenderSpec, type Row, type SecurityOptions, type SelfSource, type SessionEntry, type SessionParseOptions, type SessionWriteEntry, type SessionWriteOp, type SessionWriteParseResult, type SourceQueryOptions, type StopFn, type SyncResult, type TableDefinition, type TemplateRenderSpec, type WatchOptions, type WritebackDefinition, applyWriteEntry, createReadOnlyHeader, frontmatter, generateEntryId, generateWriteEntryId, manifestPath, markdownTable, parseConfigFile, parseConfigString, parseMarkdownEntries, parseSessionMD, parseSessionWrites, readManifest, slugify, truncate, validateEntryId, writeManifest };
|
package/dist/index.js
CHANGED
|
@@ -443,6 +443,172 @@ function truncateContent(content, budget) {
|
|
|
443
443
|
return content.slice(0, budget) + "\n\n*[truncated \u2014 context budget exceeded]*";
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
+
// src/render/markdown.ts
|
|
447
|
+
function frontmatter(fields) {
|
|
448
|
+
const lines = [`generated_at: "${(/* @__PURE__ */ new Date()).toISOString()}"`];
|
|
449
|
+
for (const [key, val] of Object.entries(fields)) {
|
|
450
|
+
lines.push(typeof val === "string" ? `${key}: "${val}"` : `${key}: ${String(val)}`);
|
|
451
|
+
}
|
|
452
|
+
return `---
|
|
453
|
+
${lines.join("\n")}
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
`;
|
|
457
|
+
}
|
|
458
|
+
function markdownTable(rows, columns) {
|
|
459
|
+
if (rows.length === 0 || columns.length === 0) return "";
|
|
460
|
+
const header = "| " + columns.map((c) => c.header).join(" | ") + " |";
|
|
461
|
+
const separator = "| " + columns.map(() => "---").join(" | ") + " |";
|
|
462
|
+
const body = rows.map((row) => {
|
|
463
|
+
const cells = columns.map((col) => {
|
|
464
|
+
const raw = row[col.key];
|
|
465
|
+
return col.format ? col.format(raw, row) : String(raw ?? "");
|
|
466
|
+
});
|
|
467
|
+
return "| " + cells.join(" | ") + " |";
|
|
468
|
+
});
|
|
469
|
+
return [header, separator, ...body].join("\n") + "\n";
|
|
470
|
+
}
|
|
471
|
+
function slugify(name) {
|
|
472
|
+
return name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/\u0131/g, "i").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
473
|
+
}
|
|
474
|
+
function truncate(content, maxChars, notice = "\n\n*[truncated \u2014 context budget exceeded]*") {
|
|
475
|
+
if (content.length <= maxChars) return content;
|
|
476
|
+
return content.slice(0, maxChars) + notice;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/session/constants.ts
|
|
480
|
+
function createReadOnlyHeader(options) {
|
|
481
|
+
const generator = options?.generator ?? "Lattice";
|
|
482
|
+
const docsRef = options?.docsRef ?? "the Lattice documentation";
|
|
483
|
+
return `<!-- READ ONLY \u2014 generated by ${generator}. Do not edit directly.
|
|
484
|
+
To update data in Lattice: write entries to SESSION.md in this directory.
|
|
485
|
+
Format: type: write | op: create/update/delete | table: <name> | target: <id>
|
|
486
|
+
See ${docsRef} for the SESSION.md format spec. -->
|
|
487
|
+
|
|
488
|
+
`;
|
|
489
|
+
}
|
|
490
|
+
var READ_ONLY_HEADER = createReadOnlyHeader();
|
|
491
|
+
|
|
492
|
+
// src/render/entity-templates.ts
|
|
493
|
+
var DEFAULT_HEADER = createReadOnlyHeader();
|
|
494
|
+
function compileEntityRender(spec) {
|
|
495
|
+
if (typeof spec === "function") return spec;
|
|
496
|
+
return compileTemplate(spec);
|
|
497
|
+
}
|
|
498
|
+
function compileTemplate(tmpl) {
|
|
499
|
+
switch (tmpl.template) {
|
|
500
|
+
case "entity-table":
|
|
501
|
+
return compileEntityTable(tmpl);
|
|
502
|
+
case "entity-profile":
|
|
503
|
+
return compileEntityProfile(tmpl);
|
|
504
|
+
case "entity-sections":
|
|
505
|
+
return compileEntitySections(tmpl);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function compileEntityTable(tmpl) {
|
|
509
|
+
return (rows) => {
|
|
510
|
+
const data = tmpl.beforeRender ? tmpl.beforeRender(rows) : rows;
|
|
511
|
+
let md = DEFAULT_HEADER;
|
|
512
|
+
md += frontmatter(tmpl.frontmatter ?? {});
|
|
513
|
+
md += `# ${tmpl.heading}
|
|
514
|
+
|
|
515
|
+
`;
|
|
516
|
+
if (data.length === 0) {
|
|
517
|
+
md += tmpl.emptyMessage ?? "*No data.*\n";
|
|
518
|
+
} else {
|
|
519
|
+
md += markdownTable(data, tmpl.columns);
|
|
520
|
+
}
|
|
521
|
+
return md;
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
function compileEntityProfile(tmpl) {
|
|
525
|
+
return (rows) => {
|
|
526
|
+
const data = tmpl.beforeRender ? tmpl.beforeRender(rows) : rows;
|
|
527
|
+
const r = data[0];
|
|
528
|
+
if (!r) return "";
|
|
529
|
+
let md = DEFAULT_HEADER;
|
|
530
|
+
if (tmpl.frontmatter) {
|
|
531
|
+
const fm = typeof tmpl.frontmatter === "function" ? tmpl.frontmatter(r) : tmpl.frontmatter;
|
|
532
|
+
md += frontmatter(fm);
|
|
533
|
+
} else {
|
|
534
|
+
md += frontmatter({});
|
|
535
|
+
}
|
|
536
|
+
const heading = typeof tmpl.heading === "function" ? tmpl.heading(r) : tmpl.heading;
|
|
537
|
+
md += `# ${heading}
|
|
538
|
+
|
|
539
|
+
`;
|
|
540
|
+
for (const field of tmpl.fields) {
|
|
541
|
+
const val = r[field.key];
|
|
542
|
+
if (val === null || val === void 0) continue;
|
|
543
|
+
const formatted = field.format ? field.format(val, r) : String(val);
|
|
544
|
+
if (formatted) {
|
|
545
|
+
md += `**${field.label}:** ${formatted}
|
|
546
|
+
`;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (tmpl.sections) {
|
|
550
|
+
for (const section of tmpl.sections) {
|
|
551
|
+
const rawJson = r[`_${section.key}`];
|
|
552
|
+
if (!rawJson) continue;
|
|
553
|
+
if (section.condition && !section.condition(r)) continue;
|
|
554
|
+
const items = JSON.parse(rawJson);
|
|
555
|
+
if (items.length === 0) continue;
|
|
556
|
+
const sectionHeading = typeof section.heading === "function" ? section.heading(r) : section.heading;
|
|
557
|
+
md += `
|
|
558
|
+
## ${sectionHeading}
|
|
559
|
+
|
|
560
|
+
`;
|
|
561
|
+
if (section.render === "table" && section.columns) {
|
|
562
|
+
md += markdownTable(items, section.columns);
|
|
563
|
+
} else if (section.render === "list" && section.formatItem) {
|
|
564
|
+
for (const item of items) {
|
|
565
|
+
md += `- ${section.formatItem(item)}
|
|
566
|
+
`;
|
|
567
|
+
}
|
|
568
|
+
} else if (typeof section.render === "function") {
|
|
569
|
+
md += section.render(items);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return md;
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function compileEntitySections(tmpl) {
|
|
577
|
+
return (rows) => {
|
|
578
|
+
const data = tmpl.beforeRender ? tmpl.beforeRender(rows) : rows;
|
|
579
|
+
let md = DEFAULT_HEADER;
|
|
580
|
+
md += frontmatter(tmpl.frontmatter ?? {});
|
|
581
|
+
md += `# ${tmpl.heading}
|
|
582
|
+
|
|
583
|
+
`;
|
|
584
|
+
if (data.length === 0) {
|
|
585
|
+
md += tmpl.emptyMessage ?? "*No data.*\n";
|
|
586
|
+
return md;
|
|
587
|
+
}
|
|
588
|
+
for (const row of data) {
|
|
589
|
+
md += `## ${tmpl.perRow.heading(row)}
|
|
590
|
+
`;
|
|
591
|
+
if (tmpl.perRow.metadata?.length) {
|
|
592
|
+
const parts = tmpl.perRow.metadata.map((m) => {
|
|
593
|
+
const val = row[m.key];
|
|
594
|
+
const formatted = m.format ? m.format(val) : String(val ?? "");
|
|
595
|
+
return `**${m.label}:** ${formatted}`;
|
|
596
|
+
}).filter(Boolean);
|
|
597
|
+
if (parts.length > 0) {
|
|
598
|
+
md += parts.join(" | ") + "\n";
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (tmpl.perRow.body) {
|
|
602
|
+
md += `
|
|
603
|
+
${tmpl.perRow.body(row)}
|
|
604
|
+
`;
|
|
605
|
+
}
|
|
606
|
+
md += "\n";
|
|
607
|
+
}
|
|
608
|
+
return md;
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
446
612
|
// src/lifecycle/cleanup.ts
|
|
447
613
|
import { join as join3 } from "path";
|
|
448
614
|
import { existsSync as existsSync3, readdirSync, unlinkSync, rmdirSync, statSync } from "fs";
|
|
@@ -652,7 +818,8 @@ var RenderEngine = class {
|
|
|
652
818
|
const source = mergeDefaults ? { ...def.sourceDefaults, ...spec.source } : spec.source;
|
|
653
819
|
const rows = resolveEntitySource(source, entityRow, entityPk, this._adapter);
|
|
654
820
|
if (spec.omitIfEmpty && rows.length === 0) continue;
|
|
655
|
-
const
|
|
821
|
+
const renderFn = compileEntityRender(spec.render);
|
|
822
|
+
const content = truncateContent(renderFn(rows), spec.budget);
|
|
656
823
|
renderedFiles.set(filename, content);
|
|
657
824
|
const filePath = join4(entityDir, filename);
|
|
658
825
|
if (atomicWrite(filePath, content)) {
|
|
@@ -1585,39 +1752,6 @@ var Lattice = class {
|
|
|
1585
1752
|
}
|
|
1586
1753
|
};
|
|
1587
1754
|
|
|
1588
|
-
// src/render/markdown.ts
|
|
1589
|
-
function frontmatter(fields) {
|
|
1590
|
-
const lines = [`generated_at: "${(/* @__PURE__ */ new Date()).toISOString()}"`];
|
|
1591
|
-
for (const [key, val] of Object.entries(fields)) {
|
|
1592
|
-
lines.push(typeof val === "string" ? `${key}: "${val}"` : `${key}: ${String(val)}`);
|
|
1593
|
-
}
|
|
1594
|
-
return `---
|
|
1595
|
-
${lines.join("\n")}
|
|
1596
|
-
---
|
|
1597
|
-
|
|
1598
|
-
`;
|
|
1599
|
-
}
|
|
1600
|
-
function markdownTable(rows, columns) {
|
|
1601
|
-
if (rows.length === 0 || columns.length === 0) return "";
|
|
1602
|
-
const header = "| " + columns.map((c) => c.header).join(" | ") + " |";
|
|
1603
|
-
const separator = "| " + columns.map(() => "---").join(" | ") + " |";
|
|
1604
|
-
const body = rows.map((row) => {
|
|
1605
|
-
const cells = columns.map((col) => {
|
|
1606
|
-
const raw = row[col.key];
|
|
1607
|
-
return col.format ? col.format(raw, row) : String(raw ?? "");
|
|
1608
|
-
});
|
|
1609
|
-
return "| " + cells.join(" | ") + " |";
|
|
1610
|
-
});
|
|
1611
|
-
return [header, separator, ...body].join("\n") + "\n";
|
|
1612
|
-
}
|
|
1613
|
-
function slugify(name) {
|
|
1614
|
-
return name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/\u0131/g, "i").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
1615
|
-
}
|
|
1616
|
-
function truncate(content, maxChars, notice = "\n\n*[truncated \u2014 context budget exceeded]*") {
|
|
1617
|
-
if (content.length <= maxChars) return content;
|
|
1618
|
-
return content.slice(0, maxChars) + notice;
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
1755
|
// src/session/parser.ts
|
|
1622
1756
|
import { createHash as createHash2 } from "crypto";
|
|
1623
1757
|
function generateWriteEntryId(timestamp, agentName, op, table, target) {
|
|
@@ -2002,19 +2136,6 @@ function applyWriteEntry(db, entry) {
|
|
|
2002
2136
|
return { ok: false, reason: `DB error: ${message}` };
|
|
2003
2137
|
}
|
|
2004
2138
|
}
|
|
2005
|
-
|
|
2006
|
-
// src/session/constants.ts
|
|
2007
|
-
function createReadOnlyHeader(options) {
|
|
2008
|
-
const generator = options?.generator ?? "Lattice";
|
|
2009
|
-
const docsRef = options?.docsRef ?? "the Lattice documentation";
|
|
2010
|
-
return `<!-- READ ONLY \u2014 generated by ${generator}. Do not edit directly.
|
|
2011
|
-
To update data in Lattice: write entries to SESSION.md in this directory.
|
|
2012
|
-
Format: type: write | op: create/update/delete | table: <name> | target: <id>
|
|
2013
|
-
See ${docsRef} for the SESSION.md format spec. -->
|
|
2014
|
-
|
|
2015
|
-
`;
|
|
2016
|
-
}
|
|
2017
|
-
var READ_ONLY_HEADER = createReadOnlyHeader();
|
|
2018
2139
|
export {
|
|
2019
2140
|
DEFAULT_ENTRY_TYPES,
|
|
2020
2141
|
DEFAULT_TYPE_ALIASES,
|