latticesql 1.13.0 → 1.13.1

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/README.md CHANGED
@@ -2066,12 +2066,21 @@ npx lattice gui --config ./lattice.config.yml --output ./context --port 4317
2066
2066
 
2067
2067
  **Options**
2068
2068
 
2069
- | Flag | Default | Description |
2070
- | --------------------- | ---------------------- | ----------------------------------------------------- |
2071
- | `--config, -c <path>` | `./lattice.config.yml` | Path to the config file |
2072
- | `--output <dir>` | `./context` | Output directory (used by the relationship graph) |
2073
- | `--port <number>` | `4317` | Localhost port; auto-increments when the port is busy |
2074
- | `--no-open` | off | Print the URL without opening a browser |
2069
+ | Flag | Default | Description |
2070
+ | --------------------- | ---------------------- | -------------------------------------------------------- |
2071
+ | `--config, -c <path>` | `./lattice.config.yml` | Path to the config file |
2072
+ | `--output <dir>` | (auto-detected) | Output directory containing rendered context see below |
2073
+ | `--port <number>` | `4317` | Localhost port; auto-increments when the port is busy |
2074
+ | `--no-open` | off | Print the URL without opening a browser |
2075
+
2076
+ **Output-directory auto-detection (v1.13.1+).** When `--output` is not passed explicitly, the GUI probes `./context`, `.`, and `./generated` in order and uses the first directory containing a `.lattice/manifest.json` (announced via a one-line `auto-detected rendered context at "<dir>"` log on stdout). Projects whose `lattice render` writes into the project root no longer need to pass `--output .` every time. An explicit `--output` is always honoured.
2077
+
2078
+ **Entity-context discovery (v1.13.1+).** The Database panel's row-context viewer reads entity contexts from two layered sources so it works regardless of how you register them:
2079
+
2080
+ 1. **Live Lattice schema** — anything declared in `lattice.config.yml` or added programmatically via `db.defineEntityContext()` against the active Lattice. Exposed via the new public `Lattice.entityContexts()` accessor.
2081
+ 2. **Render manifest fallback** — when a table has no schema-registered entity context but the on-disk `.lattice/manifest.json` names it (typical for projects that register entity contexts in a JS / TS module like `lattice.schema.mjs` that the GUI process never imports), the GUI derives the row → slug mapping heuristically from `row.slug` / `row.id` / `row.name` and surfaces the rendered files anyway.
2082
+
2083
+ The convergence means you don't need to duplicate entity-context definitions in YAML for the GUI to find rendered files.
2075
2084
 
2076
2085
  **Views**
2077
2086
 
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
  });
8
8
 
9
9
  // src/cli.ts
10
- import { resolve as resolve8, dirname as dirname7 } from "path";
10
+ import { resolve as resolve9, dirname as dirname7 } from "path";
11
11
  import { readFileSync as readFileSync12 } from "fs";
12
12
  import { execSync } from "child_process";
13
13
  import { parse as parse2 } from "yaml";
@@ -3369,6 +3369,16 @@ var Lattice = class {
3369
3369
  this._schema.defineEntityContext(table, def);
3370
3370
  return this;
3371
3371
  }
3372
+ /**
3373
+ * All entity contexts currently registered on this Lattice — both those
3374
+ * declared in `lattice.config.yml` and those added programmatically via
3375
+ * `defineEntityContext()`.
3376
+ *
3377
+ * Returns a defensive copy so callers can't mutate the schema.
3378
+ */
3379
+ entityContexts() {
3380
+ return new Map(this._schema.getEntityContexts());
3381
+ }
3372
3382
  /**
3373
3383
  * Register a write hook that fires after insert/update/delete operations.
3374
3384
  * Hooks run synchronously after the DB write and audit emit.
@@ -5358,8 +5368,13 @@ var guiAppHtml = `<!doctype html>
5358
5368
  }
5359
5369
 
5360
5370
  /* \u2500\u2500 Layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
5371
+ /* minmax(0, 1fr) on the content track lets a wide child (a table with
5372
+ chip-heavy cells) shrink instead of forcing the page wider than the
5373
+ viewport. Without the explicit 0 lower bound, the implicit auto
5374
+ minimum keeps the track at content-width and the whole page scrolls
5375
+ horizontally. */
5361
5376
  .layout {
5362
- display: grid; grid-template-columns: 220px 1fr;
5377
+ display: grid; grid-template-columns: 220px minmax(0, 1fr);
5363
5378
  height: calc(100vh - 56px);
5364
5379
  }
5365
5380
  nav.sidebar {
@@ -5431,6 +5446,19 @@ var guiAppHtml = `<!doctype html>
5431
5446
  tbody tr { cursor: pointer; }
5432
5447
  tbody tr:hover td { background: var(--row-hover); }
5433
5448
  td.muted { color: var(--text-muted); }
5449
+ /* Row cells truncate at 3 lines so a row with many chips or a long text
5450
+ blob stays one consistent visual height instead of wrapping into a
5451
+ paragraph. The wrapping <div class="cell-clip"> is necessary because
5452
+ -webkit-line-clamp doesn't apply to <td> directly in all engines. */
5453
+ td .cell-clip {
5454
+ display: -webkit-box;
5455
+ -webkit-line-clamp: 3;
5456
+ -webkit-box-orient: vertical;
5457
+ overflow: hidden;
5458
+ line-height: 1.45;
5459
+ max-height: calc(1.45em * 3);
5460
+ word-break: break-word;
5461
+ }
5434
5462
  .chip {
5435
5463
  display: inline-block; padding: 2px 8px; margin: 1px 3px 1px 0;
5436
5464
  background: var(--accent-soft); color: var(--accent);
@@ -6390,11 +6418,11 @@ var guiAppHtml = `<!doctype html>
6390
6418
  if (isSecretColumn(tableName, c) && r[c] != null && r[c] !== '') {
6391
6419
  return '<td class="muted">' + SECRET_MASK + '</td>';
6392
6420
  }
6393
- return '<td>' + escapeHtml(truncate(r[c], 120)) + '</td>';
6421
+ return '<td><div class="cell-clip">' + escapeHtml(truncate(r[c], 120)) + '</div></td>';
6394
6422
  });
6395
6423
  belongsTo.forEach(function (b) {
6396
6424
  var ref = (loadedTables[b.rel.table] || []).find(function (x) { return x.id === r[b.rel.foreignKey]; });
6397
- tds.push('<td>' + chipLink(b.rel.table, ref) + '</td>');
6425
+ tds.push('<td><div class="cell-clip">' + chipLink(b.rel.table, ref) + '</div></td>');
6398
6426
  });
6399
6427
  junctions.forEach(function (j) {
6400
6428
  var matches = (loadedTables[j.junction] || []).filter(function (jr) { return jr[j.localFk] === r.id; });
@@ -6403,7 +6431,7 @@ var guiAppHtml = `<!doctype html>
6403
6431
  var ref = (loadedTables[j.remoteRel.table] || []).find(function (x) { return x.id === jr[remoteFkCol]; });
6404
6432
  return ref ? chipLink(j.remoteRel.table, ref) : '';
6405
6433
  }).join('');
6406
- tds.push('<td>' + (chips || '<span class="muted">\u2014</span>') + '</td>');
6434
+ tds.push('<td><div class="cell-clip">' + (chips || '<span class="muted">\u2014</span>') + '</div></td>');
6407
6435
  });
6408
6436
  if (viewMode === 'trash') {
6409
6437
  tds.push('<td class="row-actions">' +
@@ -8703,7 +8731,7 @@ function sendJson(res, body, status = 200) {
8703
8731
  res.end(JSON.stringify(body));
8704
8732
  }
8705
8733
  function readJson(req) {
8706
- return new Promise((resolve9, reject) => {
8734
+ return new Promise((resolve10, reject) => {
8707
8735
  let raw = "";
8708
8736
  req.setEncoding("utf8");
8709
8737
  req.on("data", (chunk) => {
@@ -8712,7 +8740,7 @@ function readJson(req) {
8712
8740
  });
8713
8741
  req.on("end", () => {
8714
8742
  try {
8715
- resolve9(raw ? JSON.parse(raw) : {});
8743
+ resolve10(raw ? JSON.parse(raw) : {});
8716
8744
  } catch (e) {
8717
8745
  reject(new Error(`Invalid JSON body: ${e.message}`));
8718
8746
  }
@@ -10403,7 +10431,7 @@ function sendJson2(res, body, status = 200) {
10403
10431
  res.end(JSON.stringify(body));
10404
10432
  }
10405
10433
  function readJson2(req) {
10406
- return new Promise((resolve9, reject) => {
10434
+ return new Promise((resolve10, reject) => {
10407
10435
  let raw = "";
10408
10436
  req.setEncoding("utf8");
10409
10437
  req.on("data", (chunk) => {
@@ -10412,7 +10440,7 @@ function readJson2(req) {
10412
10440
  });
10413
10441
  req.on("end", () => {
10414
10442
  try {
10415
- resolve9(raw ? JSON.parse(raw) : {});
10443
+ resolve10(raw ? JSON.parse(raw) : {});
10416
10444
  } catch (e) {
10417
10445
  reject(new Error(`Invalid JSON body: ${e.message}`));
10418
10446
  }
@@ -10676,7 +10704,7 @@ function sendJson3(res, body, status = 200) {
10676
10704
  res.end(JSON.stringify(body));
10677
10705
  }
10678
10706
  function readJson3(req) {
10679
- return new Promise((resolve9, reject) => {
10707
+ return new Promise((resolve10, reject) => {
10680
10708
  let raw = "";
10681
10709
  req.setEncoding("utf8");
10682
10710
  req.on("data", (chunk) => {
@@ -10685,7 +10713,7 @@ function readJson3(req) {
10685
10713
  });
10686
10714
  req.on("end", () => {
10687
10715
  try {
10688
- resolve9(raw ? JSON.parse(raw) : {});
10716
+ resolve10(raw ? JSON.parse(raw) : {});
10689
10717
  } catch (e) {
10690
10718
  reject(new Error(`Invalid JSON body: ${e.message}`));
10691
10719
  }
@@ -11520,15 +11548,43 @@ async function applyForward(db, entry) {
11520
11548
  }
11521
11549
  void before;
11522
11550
  }
11523
- function readRowContext(outputDir, def, row, secretCols) {
11524
- const slug = def.slug(row);
11525
- const directoryRoot = def.directoryRoot ?? "";
11551
+ function deriveSlugFromManifest(row, knownSlugs) {
11552
+ const candidateFields = ["slug", "id", "name"];
11553
+ for (const field of candidateFields) {
11554
+ const value = row[field];
11555
+ if (typeof value === "string" && knownSlugs.has(value)) return value;
11556
+ }
11557
+ return null;
11558
+ }
11559
+ function buildRowContextLocator(table, row, schemaDef, manifest) {
11560
+ if (schemaDef) {
11561
+ return {
11562
+ directoryRoot: schemaDef.directoryRoot ?? "",
11563
+ slug: schemaDef.slug(row),
11564
+ fileNames: Object.keys(schemaDef.files)
11565
+ };
11566
+ }
11567
+ const manifestEntry = manifest?.entityContexts[table];
11568
+ if (!manifestEntry) return null;
11569
+ const knownSlugs = new Set(Object.keys(manifestEntry.entities));
11570
+ const derivedSlug = deriveSlugFromManifest(row, knownSlugs);
11571
+ if (!derivedSlug) return null;
11572
+ const entityFiles = manifestEntry.entities[derivedSlug];
11573
+ const fileNames = entityFiles ? entityFileNames(entityFiles) : manifestEntry.declaredFiles;
11574
+ return {
11575
+ directoryRoot: manifestEntry.directoryRoot,
11576
+ slug: derivedSlug,
11577
+ fileNames
11578
+ };
11579
+ }
11580
+ function readRowContext(outputDir, locator, secretCols) {
11581
+ const { slug, directoryRoot, fileNames } = locator;
11526
11582
  const entityDir = resolve6(outputDir, directoryRoot, slug);
11527
11583
  const resolvedBase = resolve6(outputDir);
11528
11584
  if (entityDir !== resolvedBase && !entityDir.startsWith(resolvedBase + sep3)) {
11529
11585
  throw new Error(`Path traversal detected: slug "${slug}" escapes output directory`);
11530
11586
  }
11531
- return Object.keys(def.files).map((filename) => {
11587
+ return fileNames.map((filename) => {
11532
11588
  const absPath = join13(entityDir, filename);
11533
11589
  const relPath = join13(directoryRoot, slug, filename);
11534
11590
  if (!existsSync14(absPath)) return { name: filename, path: relPath, content: "" };
@@ -11598,10 +11654,8 @@ async function openConfig(configPath, outputDir) {
11598
11654
  const junctionTables = new Set(
11599
11655
  getGuiEntities(configPath, outputDir).tables.filter(isJunctionTable).map((t) => t.name)
11600
11656
  );
11601
- const entityContextByTable = /* @__PURE__ */ new Map();
11602
- for (const { table, definition } of parsed.entityContexts) {
11603
- entityContextByTable.set(table, definition);
11604
- }
11657
+ const entityContextByTable = db.entityContexts();
11658
+ const manifest = readManifest(outputDir);
11605
11659
  const softDeletable = new Set(
11606
11660
  parsed.tables.filter(({ definition }) => "deleted_at" in definition.columns).map(({ name }) => name)
11607
11661
  );
@@ -11615,6 +11669,7 @@ async function openConfig(configPath, outputDir) {
11615
11669
  validTables,
11616
11670
  junctionTables,
11617
11671
  entityContextByTable,
11672
+ manifest,
11618
11673
  softDeletable
11619
11674
  };
11620
11675
  }
@@ -12116,16 +12171,17 @@ async function startGuiServer(options) {
12116
12171
  sendJson5(res, { error: `Unknown table: ${ctxTable}` }, 400);
12117
12172
  return;
12118
12173
  }
12119
- const def = active.entityContextByTable.get(ctxTable);
12120
- if (!def) {
12121
- sendJson5(res, { files: [] });
12122
- return;
12123
- }
12124
12174
  const row = await active.db.get(ctxTable, ctxId);
12125
12175
  if (row === null) {
12126
12176
  sendJson5(res, { error: "Row not found" }, 404);
12127
12177
  return;
12128
12178
  }
12179
+ const def = active.entityContextByTable.get(ctxTable);
12180
+ const locator = buildRowContextLocator(ctxTable, row, def, active.manifest);
12181
+ if (!locator) {
12182
+ sendJson5(res, { files: [] });
12183
+ return;
12184
+ }
12129
12185
  const colMetaRows = await active.db.query("_lattice_gui_column_meta", {
12130
12186
  filters: [
12131
12187
  { col: "table_name", op: "eq", val: ctxTable },
@@ -12133,7 +12189,7 @@ async function startGuiServer(options) {
12133
12189
  ]
12134
12190
  });
12135
12191
  const secretCols = new Set(colMetaRows.map((r) => r.column_name));
12136
- sendJson5(res, { files: readRowContext(active.outputDir, def, row, secretCols) });
12192
+ sendJson5(res, { files: readRowContext(active.outputDir, locator, secretCols) });
12137
12193
  return;
12138
12194
  }
12139
12195
  const rowsMatch = ROWS_PATH.exec(pathname);
@@ -12288,8 +12344,20 @@ async function startGuiServer(options) {
12288
12344
  };
12289
12345
  }
12290
12346
 
12347
+ // src/gui/discover-output-dir.ts
12348
+ import { existsSync as existsSync15 } from "fs";
12349
+ import { join as join14, resolve as resolve7 } from "path";
12350
+ function discoverOutputDir(explicitOutput, explicit) {
12351
+ if (explicit) return explicitOutput;
12352
+ const candidates = ["./context", ".", "./generated"];
12353
+ for (const dir of candidates) {
12354
+ if (existsSync15(join14(resolve7(dir), ".lattice", "manifest.json"))) return dir;
12355
+ }
12356
+ return explicitOutput;
12357
+ }
12358
+
12291
12359
  // src/teams/cli-commands.ts
12292
- import { resolve as resolve7 } from "path";
12360
+ import { resolve as resolve8 } from "path";
12293
12361
  var TEAMS_USAGE = [
12294
12362
  "lattice teams <subcommand> [options]",
12295
12363
  "",
@@ -12405,7 +12473,7 @@ function requireArg(args, key, label) {
12405
12473
  return v.trim();
12406
12474
  }
12407
12475
  async function openLocal(configPath) {
12408
- const db = new Lattice({ config: resolve7(configPath) });
12476
+ const db = new Lattice({ config: resolve8(configPath) });
12409
12477
  await db.init();
12410
12478
  return db;
12411
12479
  }
@@ -12729,6 +12797,7 @@ function parseArgs(argv) {
12729
12797
  let config = "./lattice.config.yml";
12730
12798
  let out = "./generated";
12731
12799
  let output = "./context";
12800
+ let outputExplicit = false;
12732
12801
  let scaffold = false;
12733
12802
  let help = false;
12734
12803
  let version = false;
@@ -12779,6 +12848,7 @@ function parseArgs(argv) {
12779
12848
  } else if ((arg === "--output" || arg === "--output-dir") && i + 1 < argv.length) {
12780
12849
  i++;
12781
12850
  output = argv[i] ?? output;
12851
+ outputExplicit = true;
12782
12852
  } else if (arg === "--scaffold") {
12783
12853
  scaffold = true;
12784
12854
  } else if (arg === "--dry-run") {
@@ -12854,6 +12924,7 @@ function parseArgs(argv) {
12854
12924
  config,
12855
12925
  out,
12856
12926
  output,
12927
+ outputExplicit,
12857
12928
  scaffold,
12858
12929
  help,
12859
12930
  version,
@@ -12979,7 +13050,7 @@ async function runUpdate() {
12979
13050
  }
12980
13051
  }
12981
13052
  function runGenerate(args) {
12982
- const configPath = resolve8(args.config);
13053
+ const configPath = resolve9(args.config);
12983
13054
  let raw;
12984
13055
  try {
12985
13056
  raw = readFileSync12(configPath, "utf-8");
@@ -12999,7 +13070,7 @@ function runGenerate(args) {
12999
13070
  process.exit(1);
13000
13071
  }
13001
13072
  const configDir2 = dirname7(configPath);
13002
- const outDir = resolve8(args.out);
13073
+ const outDir = resolve9(args.out);
13003
13074
  try {
13004
13075
  const result = generateAll({ config, configDir: configDir2, outDir, scaffold: args.scaffold });
13005
13076
  console.log(`Generated ${String(result.filesWritten.length)} file(s):`);
@@ -13012,15 +13083,15 @@ function runGenerate(args) {
13012
13083
  }
13013
13084
  }
13014
13085
  async function runRender(args) {
13015
- const outputDir = resolve8(args.output);
13086
+ const outputDir = resolve9(args.output);
13016
13087
  let parsed;
13017
13088
  try {
13018
- parsed = parseConfigFile(resolve8(args.config));
13089
+ parsed = parseConfigFile(resolve9(args.config));
13019
13090
  } catch (e) {
13020
13091
  console.error(`Error: ${e.message}`);
13021
13092
  process.exit(1);
13022
13093
  }
13023
- const db = new Lattice({ config: resolve8(args.config) });
13094
+ const db = new Lattice({ config: resolve9(args.config) });
13024
13095
  try {
13025
13096
  await db.init();
13026
13097
  const start = Date.now();
@@ -13039,8 +13110,8 @@ async function runRender(args) {
13039
13110
  void parsed;
13040
13111
  }
13041
13112
  async function runReconcile(args, isDryRun) {
13042
- const outputDir = resolve8(args.output);
13043
- const db = new Lattice({ config: resolve8(args.config) });
13113
+ const outputDir = resolve9(args.output);
13114
+ const db = new Lattice({ config: resolve9(args.config) });
13044
13115
  try {
13045
13116
  await db.init();
13046
13117
  const start = Date.now();
@@ -13099,8 +13170,8 @@ function formatTimestamp() {
13099
13170
  return `${hh}:${mm}:${ss}`;
13100
13171
  }
13101
13172
  async function runWatch(args) {
13102
- const outputDir = resolve8(args.output);
13103
- const db = new Lattice({ config: resolve8(args.config) });
13173
+ const outputDir = resolve9(args.output);
13174
+ const db = new Lattice({ config: resolve9(args.config) });
13104
13175
  try {
13105
13176
  await db.init();
13106
13177
  } catch (e) {
@@ -13141,9 +13212,15 @@ async function runWatch(args) {
13141
13212
  }
13142
13213
  async function runGui(args) {
13143
13214
  try {
13215
+ const resolvedOutput = discoverOutputDir(args.output, args.outputExplicit);
13216
+ if (!args.outputExplicit && resolvedOutput !== args.output) {
13217
+ console.log(
13218
+ `Lattice GUI: auto-detected rendered context at "${resolvedOutput}" (use --output to override).`
13219
+ );
13220
+ }
13144
13221
  const handle = await startGuiServer({
13145
- configPath: resolve8(args.config),
13146
- outputDir: resolve8(args.output),
13222
+ configPath: resolve9(args.config),
13223
+ outputDir: resolve9(resolvedOutput),
13147
13224
  port: args.port,
13148
13225
  openBrowser: !args.noOpen
13149
13226
  });
@@ -13162,8 +13239,8 @@ async function runGui(args) {
13162
13239
  async function runServe(args) {
13163
13240
  try {
13164
13241
  const handle = await startGuiServer({
13165
- configPath: resolve8(args.config),
13166
- outputDir: resolve8(args.output),
13242
+ configPath: resolve9(args.config),
13243
+ outputDir: resolve9(args.output),
13167
13244
  host: args.host,
13168
13245
  port: args.port,
13169
13246
  openBrowser: false,
package/dist/index.cjs CHANGED
@@ -3434,6 +3434,16 @@ var Lattice = class {
3434
3434
  this._schema.defineEntityContext(table, def);
3435
3435
  return this;
3436
3436
  }
3437
+ /**
3438
+ * All entity contexts currently registered on this Lattice — both those
3439
+ * declared in `lattice.config.yml` and those added programmatically via
3440
+ * `defineEntityContext()`.
3441
+ *
3442
+ * Returns a defensive copy so callers can't mutate the schema.
3443
+ */
3444
+ entityContexts() {
3445
+ return new Map(this._schema.getEntityContexts());
3446
+ }
3437
3447
  /**
3438
3448
  * Register a write hook that fires after insert/update/delete operations.
3439
3449
  * Hooks run synchronously after the DB write and audit emit.
package/dist/index.d.cts CHANGED
@@ -1608,6 +1608,14 @@ declare class Lattice {
1608
1608
  private _registerTable;
1609
1609
  defineMulti(name: string, def: MultiTableDefinition): this;
1610
1610
  defineEntityContext(table: string, def: EntityContextDefinition): this;
1611
+ /**
1612
+ * All entity contexts currently registered on this Lattice — both those
1613
+ * declared in `lattice.config.yml` and those added programmatically via
1614
+ * `defineEntityContext()`.
1615
+ *
1616
+ * Returns a defensive copy so callers can't mutate the schema.
1617
+ */
1618
+ entityContexts(): Map<string, EntityContextDefinition>;
1611
1619
  /**
1612
1620
  * Register a write hook that fires after insert/update/delete operations.
1613
1621
  * Hooks run synchronously after the DB write and audit emit.
@@ -2731,8 +2739,8 @@ declare function attachBlob(srcPath: string, latticeRoot: string): Promise<BlobM
2731
2739
  * keys/<label>.token per-joined-team bearer tokens (added later).
2732
2740
  * db-credentials.enc encrypted Postgres URLs (added later).
2733
2741
  *
2734
- * Per Rule 7 (public-repo isolation), do NOT log filesystem paths or
2735
- * user identity values from this module.
2742
+ * Security: do NOT log filesystem paths or user identity values from
2743
+ * this module. Errors must be thrown without echoing sensitive arguments.
2736
2744
  */
2737
2745
  /** Root directory for machine-local lattice config. Override via env. */
2738
2746
  declare function configDir(): string;
package/dist/index.d.ts CHANGED
@@ -1608,6 +1608,14 @@ declare class Lattice {
1608
1608
  private _registerTable;
1609
1609
  defineMulti(name: string, def: MultiTableDefinition): this;
1610
1610
  defineEntityContext(table: string, def: EntityContextDefinition): this;
1611
+ /**
1612
+ * All entity contexts currently registered on this Lattice — both those
1613
+ * declared in `lattice.config.yml` and those added programmatically via
1614
+ * `defineEntityContext()`.
1615
+ *
1616
+ * Returns a defensive copy so callers can't mutate the schema.
1617
+ */
1618
+ entityContexts(): Map<string, EntityContextDefinition>;
1611
1619
  /**
1612
1620
  * Register a write hook that fires after insert/update/delete operations.
1613
1621
  * Hooks run synchronously after the DB write and audit emit.
@@ -2731,8 +2739,8 @@ declare function attachBlob(srcPath: string, latticeRoot: string): Promise<BlobM
2731
2739
  * keys/<label>.token per-joined-team bearer tokens (added later).
2732
2740
  * db-credentials.enc encrypted Postgres URLs (added later).
2733
2741
  *
2734
- * Per Rule 7 (public-repo isolation), do NOT log filesystem paths or
2735
- * user identity values from this module.
2742
+ * Security: do NOT log filesystem paths or user identity values from
2743
+ * this module. Errors must be thrown without echoing sensitive arguments.
2736
2744
  */
2737
2745
  /** Root directory for machine-local lattice config. Override via env. */
2738
2746
  declare function configDir(): string;
package/dist/index.js CHANGED
@@ -3362,6 +3362,16 @@ var Lattice = class {
3362
3362
  this._schema.defineEntityContext(table, def);
3363
3363
  return this;
3364
3364
  }
3365
+ /**
3366
+ * All entity contexts currently registered on this Lattice — both those
3367
+ * declared in `lattice.config.yml` and those added programmatically via
3368
+ * `defineEntityContext()`.
3369
+ *
3370
+ * Returns a defensive copy so callers can't mutate the schema.
3371
+ */
3372
+ entityContexts() {
3373
+ return new Map(this._schema.getEntityContexts());
3374
+ }
3365
3375
  /**
3366
3376
  * Register a write hook that fires after insert/update/delete operations.
3367
3377
  * Hooks run synchronously after the DB write and audit emit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "latticesql",
3
- "version": "1.13.0",
3
+ "version": "1.13.1",
4
4
  "description": "Persistent structured memory for AI agent systems — pluggable SQLite or Postgres backend, LLM context bridge",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",