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 +108 -0
- package/dist/index.cjs +108 -0
- package/dist/index.d.cts +59 -1
- package/dist/index.d.ts +59 -1
- package/dist/index.js +108 -0
- package/package.json +1 -1
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;
|