latticesql 0.13.0 → 0.14.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 CHANGED
@@ -1821,6 +1821,114 @@ var Lattice = class {
1821
1821
  _inferFk(table) {
1822
1822
  return `${table}_id`;
1823
1823
  }
1824
+ // -------------------------------------------------------------------------
1825
+ // Report framework (v0.14+)
1826
+ // -------------------------------------------------------------------------
1827
+ /**
1828
+ * Build a report by querying data from tables within a time window.
1829
+ * Each section runs a filtered query and formats the results.
1830
+ */
1831
+ async buildReport(config) {
1832
+ const notInit = this._notInitError();
1833
+ if (notInit) return notInit;
1834
+ const since = this._resolveSince(config.since);
1835
+ const sections = [];
1836
+ let allEmpty = true;
1837
+ for (const section of config.sections) {
1838
+ const cols = this._ensureColumnCache(section.query.table);
1839
+ const hasTimestamp = cols.has("timestamp");
1840
+ const conditions = [];
1841
+ const params = [];
1842
+ if (hasTimestamp) {
1843
+ conditions.push("timestamp >= ?");
1844
+ params.push(since);
1845
+ }
1846
+ if (cols.has("deleted_at")) {
1847
+ conditions.push("deleted_at IS NULL");
1848
+ }
1849
+ if (section.query.filters) {
1850
+ for (const f of section.query.filters) {
1851
+ switch (f.op) {
1852
+ case "eq":
1853
+ conditions.push(`"${f.col}" = ?`);
1854
+ params.push(f.val);
1855
+ break;
1856
+ case "ne":
1857
+ conditions.push(`"${f.col}" != ?`);
1858
+ params.push(f.val);
1859
+ break;
1860
+ case "gt":
1861
+ conditions.push(`"${f.col}" > ?`);
1862
+ params.push(f.val);
1863
+ break;
1864
+ case "gte":
1865
+ conditions.push(`"${f.col}" >= ?`);
1866
+ params.push(f.val);
1867
+ break;
1868
+ case "lt":
1869
+ conditions.push(`"${f.col}" < ?`);
1870
+ params.push(f.val);
1871
+ break;
1872
+ case "lte":
1873
+ conditions.push(`"${f.col}" <= ?`);
1874
+ params.push(f.val);
1875
+ break;
1876
+ case "like":
1877
+ conditions.push(`"${f.col}" LIKE ?`);
1878
+ params.push(f.val);
1879
+ break;
1880
+ case "isNull":
1881
+ conditions.push(`"${f.col}" IS NULL`);
1882
+ break;
1883
+ case "isNotNull":
1884
+ conditions.push(`"${f.col}" IS NOT NULL`);
1885
+ break;
1886
+ case "in": {
1887
+ const arr = f.val;
1888
+ if (arr.length > 0) {
1889
+ conditions.push(`"${f.col}" IN (${arr.map(() => "?").join(", ")})`);
1890
+ params.push(...arr);
1891
+ }
1892
+ break;
1893
+ }
1894
+ }
1895
+ }
1896
+ }
1897
+ const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
1898
+ const orderBy = section.query.orderBy ? ` ORDER BY "${section.query.orderBy}" ${section.query.orderDir === "desc" ? "DESC" : "ASC"}` : "";
1899
+ const limit = section.query.limit ? ` LIMIT ${section.query.limit}` : "";
1900
+ const rows = this._adapter.all(`SELECT * FROM "${section.query.table}"${where}${orderBy}${limit}`, params);
1901
+ if (rows.length > 0) allEmpty = false;
1902
+ let formatted = "";
1903
+ if (section.format === "custom" && section.customFormat) {
1904
+ formatted = section.customFormat(rows);
1905
+ } else if (section.format === "counts" && section.query.groupBy) {
1906
+ const groups = /* @__PURE__ */ new Map();
1907
+ for (const row of rows) {
1908
+ const type = String(row[section.query.groupBy] ?? "other");
1909
+ const prefix = type.includes(".") ? type.split(".")[0] : type;
1910
+ groups.set(prefix, (groups.get(prefix) ?? 0) + 1);
1911
+ }
1912
+ formatted = [...groups.entries()].map(([k, v]) => `${k}: ${v}`).join("\n");
1913
+ } else if (section.format === "count_and_list") {
1914
+ formatted = `Count: ${rows.length}
1915
+ ` + rows.map((r) => `- ${r.summary ?? r.name ?? r.title ?? JSON.stringify(r)}`).join("\n");
1916
+ } else {
1917
+ formatted = rows.map((r) => `- ${r.summary ?? r.name ?? r.title ?? JSON.stringify(r)}`).join("\n");
1918
+ }
1919
+ sections.push({ name: section.name, rows, count: rows.length, formatted });
1920
+ }
1921
+ return { sections, isEmpty: allEmpty, since };
1922
+ }
1923
+ /** Parse duration shorthand ('8h', '24h', '7d') into ISO timestamp. */
1924
+ _resolveSince(since) {
1925
+ const match = since.match(/^(\d+)([hmd])$/);
1926
+ if (!match) return since;
1927
+ const [, numStr, unit] = match;
1928
+ const num = parseInt(numStr, 10);
1929
+ const ms = unit === "h" ? num * 36e5 : unit === "d" ? num * 864e5 : num * 6e4;
1930
+ return new Date(Date.now() - ms).toISOString();
1931
+ }
1824
1932
  query(table, opts = {}) {
1825
1933
  const notInit = this._notInitError();
1826
1934
  if (notInit) return notInit;
package/dist/index.cjs CHANGED
@@ -1851,6 +1851,114 @@ var Lattice = class {
1851
1851
  _inferFk(table) {
1852
1852
  return `${table}_id`;
1853
1853
  }
1854
+ // -------------------------------------------------------------------------
1855
+ // Report framework (v0.14+)
1856
+ // -------------------------------------------------------------------------
1857
+ /**
1858
+ * Build a report by querying data from tables within a time window.
1859
+ * Each section runs a filtered query and formats the results.
1860
+ */
1861
+ async buildReport(config) {
1862
+ const notInit = this._notInitError();
1863
+ if (notInit) return notInit;
1864
+ const since = this._resolveSince(config.since);
1865
+ const sections = [];
1866
+ let allEmpty = true;
1867
+ for (const section of config.sections) {
1868
+ const cols = this._ensureColumnCache(section.query.table);
1869
+ const hasTimestamp = cols.has("timestamp");
1870
+ const conditions = [];
1871
+ const params = [];
1872
+ if (hasTimestamp) {
1873
+ conditions.push("timestamp >= ?");
1874
+ params.push(since);
1875
+ }
1876
+ if (cols.has("deleted_at")) {
1877
+ conditions.push("deleted_at IS NULL");
1878
+ }
1879
+ if (section.query.filters) {
1880
+ for (const f of section.query.filters) {
1881
+ switch (f.op) {
1882
+ case "eq":
1883
+ conditions.push(`"${f.col}" = ?`);
1884
+ params.push(f.val);
1885
+ break;
1886
+ case "ne":
1887
+ conditions.push(`"${f.col}" != ?`);
1888
+ params.push(f.val);
1889
+ break;
1890
+ case "gt":
1891
+ conditions.push(`"${f.col}" > ?`);
1892
+ params.push(f.val);
1893
+ break;
1894
+ case "gte":
1895
+ conditions.push(`"${f.col}" >= ?`);
1896
+ params.push(f.val);
1897
+ break;
1898
+ case "lt":
1899
+ conditions.push(`"${f.col}" < ?`);
1900
+ params.push(f.val);
1901
+ break;
1902
+ case "lte":
1903
+ conditions.push(`"${f.col}" <= ?`);
1904
+ params.push(f.val);
1905
+ break;
1906
+ case "like":
1907
+ conditions.push(`"${f.col}" LIKE ?`);
1908
+ params.push(f.val);
1909
+ break;
1910
+ case "isNull":
1911
+ conditions.push(`"${f.col}" IS NULL`);
1912
+ break;
1913
+ case "isNotNull":
1914
+ conditions.push(`"${f.col}" IS NOT NULL`);
1915
+ break;
1916
+ case "in": {
1917
+ const arr = f.val;
1918
+ if (arr.length > 0) {
1919
+ conditions.push(`"${f.col}" IN (${arr.map(() => "?").join(", ")})`);
1920
+ params.push(...arr);
1921
+ }
1922
+ break;
1923
+ }
1924
+ }
1925
+ }
1926
+ }
1927
+ const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
1928
+ const orderBy = section.query.orderBy ? ` ORDER BY "${section.query.orderBy}" ${section.query.orderDir === "desc" ? "DESC" : "ASC"}` : "";
1929
+ const limit = section.query.limit ? ` LIMIT ${section.query.limit}` : "";
1930
+ const rows = this._adapter.all(`SELECT * FROM "${section.query.table}"${where}${orderBy}${limit}`, params);
1931
+ if (rows.length > 0) allEmpty = false;
1932
+ let formatted = "";
1933
+ if (section.format === "custom" && section.customFormat) {
1934
+ formatted = section.customFormat(rows);
1935
+ } else if (section.format === "counts" && section.query.groupBy) {
1936
+ const groups = /* @__PURE__ */ new Map();
1937
+ for (const row of rows) {
1938
+ const type = String(row[section.query.groupBy] ?? "other");
1939
+ const prefix = type.includes(".") ? type.split(".")[0] : type;
1940
+ groups.set(prefix, (groups.get(prefix) ?? 0) + 1);
1941
+ }
1942
+ formatted = [...groups.entries()].map(([k, v]) => `${k}: ${v}`).join("\n");
1943
+ } else if (section.format === "count_and_list") {
1944
+ formatted = `Count: ${rows.length}
1945
+ ` + rows.map((r) => `- ${r.summary ?? r.name ?? r.title ?? JSON.stringify(r)}`).join("\n");
1946
+ } else {
1947
+ formatted = rows.map((r) => `- ${r.summary ?? r.name ?? r.title ?? JSON.stringify(r)}`).join("\n");
1948
+ }
1949
+ sections.push({ name: section.name, rows, count: rows.length, formatted });
1950
+ }
1951
+ return { sections, isEmpty: allEmpty, since };
1952
+ }
1953
+ /** Parse duration shorthand ('8h', '24h', '7d') into ISO timestamp. */
1954
+ _resolveSince(since) {
1955
+ const match = since.match(/^(\d+)([hmd])$/);
1956
+ if (!match) return since;
1957
+ const [, numStr, unit] = match;
1958
+ const num = parseInt(numStr, 10);
1959
+ const ms = unit === "h" ? num * 36e5 : unit === "d" ? num * 864e5 : num * 6e4;
1960
+ return new Date(Date.now() - ms).toISOString();
1961
+ }
1854
1962
  query(table, opts = {}) {
1855
1963
  const notInit = this._notInitError();
1856
1964
  if (notInit) return notInit;
package/dist/index.d.cts CHANGED
@@ -859,6 +859,57 @@ interface SeedConfig {
859
859
  /** Organization ID for org-scoped tables. */
860
860
  orgId?: string;
861
861
  }
862
+ /**
863
+ * A single section in a report.
864
+ */
865
+ interface ReportSection {
866
+ /** Section name (used as key in result). */
867
+ name: string;
868
+ /** Query configuration. */
869
+ query: {
870
+ /** Table to query. */
871
+ table: string;
872
+ /** Additional filters beyond the time window. */
873
+ filters?: Filter[];
874
+ /** Group results by column value prefix (e.g., type prefix). */
875
+ groupBy?: string;
876
+ /** Order results. */
877
+ orderBy?: string;
878
+ /** Sort direction. */
879
+ orderDir?: 'asc' | 'desc';
880
+ /** Max rows. */
881
+ limit?: number;
882
+ };
883
+ /** Output format. */
884
+ format: 'count_and_list' | 'counts' | 'list' | 'custom';
885
+ /** Custom formatter (when format='custom'). */
886
+ customFormat?: (rows: Row[]) => string;
887
+ }
888
+ /**
889
+ * Configuration for {@link Lattice.buildReport}.
890
+ */
891
+ interface ReportConfig {
892
+ /** Time window: ISO timestamp or shorthand ('8h', '24h', '7d'). */
893
+ since: string;
894
+ /** Report sections to generate. */
895
+ sections: ReportSection[];
896
+ /** Message when all sections are empty. */
897
+ emptyMessage?: string;
898
+ }
899
+ /**
900
+ * Result from {@link Lattice.buildReport}.
901
+ */
902
+ interface ReportSectionResult {
903
+ name: string;
904
+ rows: Row[];
905
+ count: number;
906
+ formatted: string;
907
+ }
908
+ interface ReportResult {
909
+ sections: ReportSectionResult[];
910
+ isEmpty: boolean;
911
+ since: string;
912
+ }
862
913
  /**
863
914
  * Context passed to write hook handlers.
864
915
  */
@@ -1015,6 +1066,13 @@ declare class Lattice {
1015
1066
  seed(config: SeedConfig): Promise<SeedResult>;
1016
1067
  /** Infer FK column name from table name (e.g., 'rule' → 'rule_id'). */
1017
1068
  private _inferFk;
1069
+ /**
1070
+ * Build a report by querying data from tables within a time window.
1071
+ * Each section runs a filtered query and formats the results.
1072
+ */
1073
+ buildReport(config: ReportConfig): Promise<ReportResult>;
1074
+ /** Parse duration shorthand ('8h', '24h', '7d') into ISO timestamp. */
1075
+ private _resolveSince;
1018
1076
  query(table: string, opts?: QueryOptions): Promise<Row[]>;
1019
1077
  count(table: string, opts?: CountOptions): Promise<number>;
1020
1078
  render(outputDir: string): Promise<RenderResult>;
@@ -1509,4 +1567,4 @@ declare function createReadOnlyHeader(options?: ReadOnlyHeaderOptions): string;
1509
1567
  */
1510
1568
  declare const READ_ONLY_HEADER: string;
1511
1569
 
1512
- 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, InMemoryStateStore, type InitOptions, Lattice, type LatticeConfig, type LatticeConfigInput, type LatticeEntityDef, type LatticeEntityRenderSpec, type LatticeFieldDef, type LatticeFieldType, type LatticeManifest, type LatticeOptions, type LinkOptions, 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 SeedConfig, type SeedLinkSpec, type SeedResult, type SelfSource, type SessionEntry, type SessionParseOptions, type SessionWriteEntry, type SessionWriteOp, type SessionWriteParseResult, type SourceQueryOptions, type StopFn, type SyncResult, type TableDefinition, type TemplateRenderSpec, type UpsertByNaturalKeyOptions, type WatchOptions, type WriteHook, type WriteHookContext, type WritebackDefinition, type WritebackStateStore, applyWriteEntry, createReadOnlyHeader, createSQLiteStateStore, frontmatter, generateEntryId, generateWriteEntryId, manifestPath, markdownTable, parseConfigFile, parseConfigString, parseMarkdownEntries, parseSessionMD, parseSessionWrites, readManifest, slugify, truncate, validateEntryId, writeManifest };
1570
+ 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, InMemoryStateStore, type InitOptions, Lattice, type LatticeConfig, type LatticeConfigInput, type LatticeEntityDef, type LatticeEntityRenderSpec, type LatticeFieldDef, type LatticeFieldType, type LatticeManifest, type LatticeOptions, type LinkOptions, 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 ReportConfig, type ReportResult, type ReportSection, type ReportSectionResult, type Row, type SecurityOptions, type SeedConfig, type SeedLinkSpec, type SeedResult, type SelfSource, type SessionEntry, type SessionParseOptions, type SessionWriteEntry, type SessionWriteOp, type SessionWriteParseResult, type SourceQueryOptions, type StopFn, type SyncResult, type TableDefinition, type TemplateRenderSpec, type UpsertByNaturalKeyOptions, type WatchOptions, type WriteHook, type WriteHookContext, type WritebackDefinition, type WritebackStateStore, applyWriteEntry, createReadOnlyHeader, createSQLiteStateStore, frontmatter, generateEntryId, generateWriteEntryId, manifestPath, markdownTable, parseConfigFile, parseConfigString, parseMarkdownEntries, parseSessionMD, parseSessionWrites, readManifest, slugify, truncate, validateEntryId, writeManifest };
package/dist/index.d.ts CHANGED
@@ -859,6 +859,57 @@ interface SeedConfig {
859
859
  /** Organization ID for org-scoped tables. */
860
860
  orgId?: string;
861
861
  }
862
+ /**
863
+ * A single section in a report.
864
+ */
865
+ interface ReportSection {
866
+ /** Section name (used as key in result). */
867
+ name: string;
868
+ /** Query configuration. */
869
+ query: {
870
+ /** Table to query. */
871
+ table: string;
872
+ /** Additional filters beyond the time window. */
873
+ filters?: Filter[];
874
+ /** Group results by column value prefix (e.g., type prefix). */
875
+ groupBy?: string;
876
+ /** Order results. */
877
+ orderBy?: string;
878
+ /** Sort direction. */
879
+ orderDir?: 'asc' | 'desc';
880
+ /** Max rows. */
881
+ limit?: number;
882
+ };
883
+ /** Output format. */
884
+ format: 'count_and_list' | 'counts' | 'list' | 'custom';
885
+ /** Custom formatter (when format='custom'). */
886
+ customFormat?: (rows: Row[]) => string;
887
+ }
888
+ /**
889
+ * Configuration for {@link Lattice.buildReport}.
890
+ */
891
+ interface ReportConfig {
892
+ /** Time window: ISO timestamp or shorthand ('8h', '24h', '7d'). */
893
+ since: string;
894
+ /** Report sections to generate. */
895
+ sections: ReportSection[];
896
+ /** Message when all sections are empty. */
897
+ emptyMessage?: string;
898
+ }
899
+ /**
900
+ * Result from {@link Lattice.buildReport}.
901
+ */
902
+ interface ReportSectionResult {
903
+ name: string;
904
+ rows: Row[];
905
+ count: number;
906
+ formatted: string;
907
+ }
908
+ interface ReportResult {
909
+ sections: ReportSectionResult[];
910
+ isEmpty: boolean;
911
+ since: string;
912
+ }
862
913
  /**
863
914
  * Context passed to write hook handlers.
864
915
  */
@@ -1015,6 +1066,13 @@ declare class Lattice {
1015
1066
  seed(config: SeedConfig): Promise<SeedResult>;
1016
1067
  /** Infer FK column name from table name (e.g., 'rule' → 'rule_id'). */
1017
1068
  private _inferFk;
1069
+ /**
1070
+ * Build a report by querying data from tables within a time window.
1071
+ * Each section runs a filtered query and formats the results.
1072
+ */
1073
+ buildReport(config: ReportConfig): Promise<ReportResult>;
1074
+ /** Parse duration shorthand ('8h', '24h', '7d') into ISO timestamp. */
1075
+ private _resolveSince;
1018
1076
  query(table: string, opts?: QueryOptions): Promise<Row[]>;
1019
1077
  count(table: string, opts?: CountOptions): Promise<number>;
1020
1078
  render(outputDir: string): Promise<RenderResult>;
@@ -1509,4 +1567,4 @@ declare function createReadOnlyHeader(options?: ReadOnlyHeaderOptions): string;
1509
1567
  */
1510
1568
  declare const READ_ONLY_HEADER: string;
1511
1569
 
1512
- 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, InMemoryStateStore, type InitOptions, Lattice, type LatticeConfig, type LatticeConfigInput, type LatticeEntityDef, type LatticeEntityRenderSpec, type LatticeFieldDef, type LatticeFieldType, type LatticeManifest, type LatticeOptions, type LinkOptions, 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 SeedConfig, type SeedLinkSpec, type SeedResult, type SelfSource, type SessionEntry, type SessionParseOptions, type SessionWriteEntry, type SessionWriteOp, type SessionWriteParseResult, type SourceQueryOptions, type StopFn, type SyncResult, type TableDefinition, type TemplateRenderSpec, type UpsertByNaturalKeyOptions, type WatchOptions, type WriteHook, type WriteHookContext, type WritebackDefinition, type WritebackStateStore, applyWriteEntry, createReadOnlyHeader, createSQLiteStateStore, frontmatter, generateEntryId, generateWriteEntryId, manifestPath, markdownTable, parseConfigFile, parseConfigString, parseMarkdownEntries, parseSessionMD, parseSessionWrites, readManifest, slugify, truncate, validateEntryId, writeManifest };
1570
+ 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, InMemoryStateStore, type InitOptions, Lattice, type LatticeConfig, type LatticeConfigInput, type LatticeEntityDef, type LatticeEntityRenderSpec, type LatticeFieldDef, type LatticeFieldType, type LatticeManifest, type LatticeOptions, type LinkOptions, 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 ReportConfig, type ReportResult, type ReportSection, type ReportSectionResult, type Row, type SecurityOptions, type SeedConfig, type SeedLinkSpec, type SeedResult, type SelfSource, type SessionEntry, type SessionParseOptions, type SessionWriteEntry, type SessionWriteOp, type SessionWriteParseResult, type SourceQueryOptions, type StopFn, type SyncResult, type TableDefinition, type TemplateRenderSpec, type UpsertByNaturalKeyOptions, type WatchOptions, type WriteHook, type WriteHookContext, type WritebackDefinition, type WritebackStateStore, applyWriteEntry, createReadOnlyHeader, createSQLiteStateStore, frontmatter, generateEntryId, generateWriteEntryId, manifestPath, markdownTable, parseConfigFile, parseConfigString, parseMarkdownEntries, parseSessionMD, parseSessionWrites, readManifest, slugify, truncate, validateEntryId, writeManifest };
package/dist/index.js CHANGED
@@ -1793,6 +1793,114 @@ var Lattice = class {
1793
1793
  _inferFk(table) {
1794
1794
  return `${table}_id`;
1795
1795
  }
1796
+ // -------------------------------------------------------------------------
1797
+ // Report framework (v0.14+)
1798
+ // -------------------------------------------------------------------------
1799
+ /**
1800
+ * Build a report by querying data from tables within a time window.
1801
+ * Each section runs a filtered query and formats the results.
1802
+ */
1803
+ async buildReport(config) {
1804
+ const notInit = this._notInitError();
1805
+ if (notInit) return notInit;
1806
+ const since = this._resolveSince(config.since);
1807
+ const sections = [];
1808
+ let allEmpty = true;
1809
+ for (const section of config.sections) {
1810
+ const cols = this._ensureColumnCache(section.query.table);
1811
+ const hasTimestamp = cols.has("timestamp");
1812
+ const conditions = [];
1813
+ const params = [];
1814
+ if (hasTimestamp) {
1815
+ conditions.push("timestamp >= ?");
1816
+ params.push(since);
1817
+ }
1818
+ if (cols.has("deleted_at")) {
1819
+ conditions.push("deleted_at IS NULL");
1820
+ }
1821
+ if (section.query.filters) {
1822
+ for (const f of section.query.filters) {
1823
+ switch (f.op) {
1824
+ case "eq":
1825
+ conditions.push(`"${f.col}" = ?`);
1826
+ params.push(f.val);
1827
+ break;
1828
+ case "ne":
1829
+ conditions.push(`"${f.col}" != ?`);
1830
+ params.push(f.val);
1831
+ break;
1832
+ case "gt":
1833
+ conditions.push(`"${f.col}" > ?`);
1834
+ params.push(f.val);
1835
+ break;
1836
+ case "gte":
1837
+ conditions.push(`"${f.col}" >= ?`);
1838
+ params.push(f.val);
1839
+ break;
1840
+ case "lt":
1841
+ conditions.push(`"${f.col}" < ?`);
1842
+ params.push(f.val);
1843
+ break;
1844
+ case "lte":
1845
+ conditions.push(`"${f.col}" <= ?`);
1846
+ params.push(f.val);
1847
+ break;
1848
+ case "like":
1849
+ conditions.push(`"${f.col}" LIKE ?`);
1850
+ params.push(f.val);
1851
+ break;
1852
+ case "isNull":
1853
+ conditions.push(`"${f.col}" IS NULL`);
1854
+ break;
1855
+ case "isNotNull":
1856
+ conditions.push(`"${f.col}" IS NOT NULL`);
1857
+ break;
1858
+ case "in": {
1859
+ const arr = f.val;
1860
+ if (arr.length > 0) {
1861
+ conditions.push(`"${f.col}" IN (${arr.map(() => "?").join(", ")})`);
1862
+ params.push(...arr);
1863
+ }
1864
+ break;
1865
+ }
1866
+ }
1867
+ }
1868
+ }
1869
+ const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
1870
+ const orderBy = section.query.orderBy ? ` ORDER BY "${section.query.orderBy}" ${section.query.orderDir === "desc" ? "DESC" : "ASC"}` : "";
1871
+ const limit = section.query.limit ? ` LIMIT ${section.query.limit}` : "";
1872
+ const rows = this._adapter.all(`SELECT * FROM "${section.query.table}"${where}${orderBy}${limit}`, params);
1873
+ if (rows.length > 0) allEmpty = false;
1874
+ let formatted = "";
1875
+ if (section.format === "custom" && section.customFormat) {
1876
+ formatted = section.customFormat(rows);
1877
+ } else if (section.format === "counts" && section.query.groupBy) {
1878
+ const groups = /* @__PURE__ */ new Map();
1879
+ for (const row of rows) {
1880
+ const type = String(row[section.query.groupBy] ?? "other");
1881
+ const prefix = type.includes(".") ? type.split(".")[0] : type;
1882
+ groups.set(prefix, (groups.get(prefix) ?? 0) + 1);
1883
+ }
1884
+ formatted = [...groups.entries()].map(([k, v]) => `${k}: ${v}`).join("\n");
1885
+ } else if (section.format === "count_and_list") {
1886
+ formatted = `Count: ${rows.length}
1887
+ ` + rows.map((r) => `- ${r.summary ?? r.name ?? r.title ?? JSON.stringify(r)}`).join("\n");
1888
+ } else {
1889
+ formatted = rows.map((r) => `- ${r.summary ?? r.name ?? r.title ?? JSON.stringify(r)}`).join("\n");
1890
+ }
1891
+ sections.push({ name: section.name, rows, count: rows.length, formatted });
1892
+ }
1893
+ return { sections, isEmpty: allEmpty, since };
1894
+ }
1895
+ /** Parse duration shorthand ('8h', '24h', '7d') into ISO timestamp. */
1896
+ _resolveSince(since) {
1897
+ const match = since.match(/^(\d+)([hmd])$/);
1898
+ if (!match) return since;
1899
+ const [, numStr, unit] = match;
1900
+ const num = parseInt(numStr, 10);
1901
+ const ms = unit === "h" ? num * 36e5 : unit === "d" ? num * 864e5 : num * 6e4;
1902
+ return new Date(Date.now() - ms).toISOString();
1903
+ }
1796
1904
  query(table, opts = {}) {
1797
1905
  const notInit = this._notInitError();
1798
1906
  if (notInit) return notInit;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "latticesql",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Persistent structured memory for AI agent systems — SQLite ↔ LLM context bridge",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",