latticesql 0.18.3 → 1.0.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/README.md CHANGED
@@ -969,7 +969,11 @@ const task = await db.updateReturning('tasks', 'task-001', { status: 'done' });
969
969
  // task → { id: 'task-001', title: 'Write docs', status: 'done', priority: 3, ... }
970
970
 
971
971
  // Composite PK
972
- const seat = await db.updateReturning('event_seats', { event_id: 'e-1', seat_no: 3 }, { holder: 'Bob' });
972
+ const seat = await db.updateReturning(
973
+ 'event_seats',
974
+ { event_id: 'e-1', seat_no: 3 },
975
+ { holder: 'Bob' },
976
+ );
973
977
  // seat → { event_id: 'e-1', seat_no: 3, holder: 'Bob' }
974
978
  ```
975
979
 
@@ -2012,6 +2016,14 @@ Three complete, commented examples are in [docs/examples/](./docs/examples/):
2012
2016
 
2013
2017
  ---
2014
2018
 
2019
+ ## Staying up to date
2020
+
2021
+ **CLI users:** The `lattice` CLI checks for new versions automatically and prints a notice when an update is available. Run `lattice update` to upgrade in place. Alternatively, use `npx lattice` to always run the latest version without a global install.
2022
+
2023
+ **Library consumers:** By default, `npm install latticesql` adds a `^` semver range to your `package.json`, so patch and minor updates are picked up on your next `npm install`. For fully automated dependency updates, set up [Dependabot](https://docs.github.com/en/code-security/dependabot) or [Renovate](https://github.com/renovatebot/renovate) — they'll create PRs in your repo whenever a new version is published.
2024
+
2025
+ ---
2026
+
2015
2027
  ## Contributing
2016
2028
 
2017
2029
  See [CONTRIBUTING.md](./CONTRIBUTING.md) for dev setup, test commands, and contribution guidelines.
package/dist/cli.js CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { resolve as resolve4, dirname as dirname5 } from "path";
5
- import { readFileSync as readFileSync6 } from "fs";
5
+ import { readFileSync as readFileSync7 } from "fs";
6
+ import { execSync } from "child_process";
6
7
  import { parse as parse2 } from "yaml";
7
8
 
8
9
  // src/codegen/generate.ts
@@ -493,7 +494,7 @@ var SchemaManager = class {
493
494
  applySchema(adapter) {
494
495
  for (const [name, def] of this._tables) {
495
496
  const pkCols = this._tablePK.get(name) ?? ["id"];
496
- let constraints = def.tableConstraints ? [...def.tableConstraints] : [];
497
+ const constraints = def.tableConstraints ? [...def.tableConstraints] : [];
497
498
  if (pkCols.length > 1) {
498
499
  const alreadyHasPK = constraints.some((c) => c.toUpperCase().startsWith("PRIMARY KEY"));
499
500
  if (!alreadyHasPK) {
@@ -537,7 +538,7 @@ var SchemaManager = class {
537
538
  queryTable(adapter, name) {
538
539
  if (this._tables.has(name)) {
539
540
  const def = this._tables.get(name);
540
- if (def.columns && "deleted_at" in def.columns) {
541
+ if (def?.columns && "deleted_at" in def.columns) {
541
542
  return adapter.all(`SELECT * FROM "${name}" WHERE deleted_at IS NULL`);
542
543
  }
543
544
  return adapter.all(`SELECT * FROM "${name}"`);
@@ -721,7 +722,7 @@ function resolveEntitySource(source, entityRow, entityPk, adapter, protection) {
721
722
  case "self":
722
723
  return [entityRow];
723
724
  case "hasMany": {
724
- if (protection && protection.protectedTables.has(source.table)) {
725
+ if (protection?.protectedTables.has(source.table)) {
725
726
  if (source.table === protection.currentTable) return [entityRow];
726
727
  return [];
727
728
  }
@@ -733,7 +734,7 @@ function resolveEntitySource(source, entityRow, entityPk, adapter, protection) {
733
734
  return adapter.all(sql, params);
734
735
  }
735
736
  case "manyToMany": {
736
- if (protection && protection.protectedTables.has(source.remoteTable)) {
737
+ if (protection?.protectedTables.has(source.remoteTable)) {
737
738
  if (source.remoteTable === protection.currentTable) return [entityRow];
738
739
  return [];
739
740
  }
@@ -759,7 +760,7 @@ function resolveEntitySource(source, entityRow, entityPk, adapter, protection) {
759
760
  return adapter.all(sql, params);
760
761
  }
761
762
  case "belongsTo": {
762
- if (protection && protection.protectedTables.has(source.table)) {
763
+ if (protection?.protectedTables.has(source.table)) {
763
764
  if (source.table === protection.currentTable) return [entityRow];
764
765
  return [];
765
766
  }
@@ -1711,7 +1712,12 @@ var Lattice = class {
1711
1712
  this._assertNotInit("define");
1712
1713
  const compiledDef = {
1713
1714
  ...def,
1714
- render: def.render ? compileRender(def, table, this._schema, this._adapter) : () => "",
1715
+ render: def.render ? compileRender(
1716
+ def,
1717
+ table,
1718
+ this._schema,
1719
+ this._adapter
1720
+ ) : () => "",
1715
1721
  outputFile: def.outputFile ?? `.schema-only/${table}.md`
1716
1722
  };
1717
1723
  this._schema.define(table, compiledDef);
@@ -1790,9 +1796,7 @@ var Lattice = class {
1790
1796
  `Entity context "${table}" has encrypted: true but no encryptionKey was provided in Lattice options`
1791
1797
  );
1792
1798
  }
1793
- if (!this._encryptionKey) {
1794
- this._encryptionKey = deriveKey(this._encryptionKeyRaw);
1795
- }
1799
+ this._encryptionKey ??= deriveKey(this._encryptionKeyRaw);
1796
1800
  const pragmaRows = this._adapter.all(`PRAGMA table_info("${table}")`);
1797
1801
  const allCols = pragmaRows.map((r) => r.name);
1798
1802
  const encCols = resolveEncryptedColumns(def.encrypted, allCols);
@@ -2621,6 +2625,49 @@ var Lattice = class {
2621
2625
  }
2622
2626
  };
2623
2627
 
2628
+ // src/update-check.ts
2629
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync8 } from "fs";
2630
+ import { join as join8 } from "path";
2631
+ import { homedir } from "os";
2632
+ var ONE_DAY_MS = 864e5;
2633
+ function isNewer(latest, current) {
2634
+ const a = latest.split(".").map(Number);
2635
+ const b = current.split(".").map(Number);
2636
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
2637
+ const av = a[i] ?? 0;
2638
+ const bv = b[i] ?? 0;
2639
+ if (av > bv) return true;
2640
+ if (av < bv) return false;
2641
+ }
2642
+ return false;
2643
+ }
2644
+ async function checkForUpdate(pkgName, currentVersion) {
2645
+ const cacheDir = join8(homedir(), `.${pkgName}`);
2646
+ const cachePath = join8(cacheDir, "update-check.json");
2647
+ try {
2648
+ if (existsSync8(cachePath)) {
2649
+ const cached = JSON.parse(readFileSync6(cachePath, "utf-8"));
2650
+ if (Date.now() - cached.checked < ONE_DAY_MS) {
2651
+ return isNewer(cached.latest, currentVersion) ? cached.latest : null;
2652
+ }
2653
+ }
2654
+ } catch {
2655
+ }
2656
+ const res = await fetch(`https://registry.npmjs.org/${pkgName}/latest`, {
2657
+ headers: { accept: "application/json" },
2658
+ signal: AbortSignal.timeout(5e3)
2659
+ });
2660
+ if (!res.ok) return null;
2661
+ const data = await res.json();
2662
+ const latest = data.version;
2663
+ try {
2664
+ if (!existsSync8(cacheDir)) mkdirSync4(cacheDir, { recursive: true });
2665
+ writeFileSync3(cachePath, JSON.stringify({ latest, checked: Date.now() }));
2666
+ } catch {
2667
+ }
2668
+ return isNewer(latest, currentVersion) ? latest : null;
2669
+ }
2670
+
2624
2671
  // src/cli.ts
2625
2672
  function parseArgs(argv) {
2626
2673
  let command;
@@ -2707,6 +2754,7 @@ function printHelp() {
2707
2754
  " reconcile Render + cleanup orphaned entity directories and files",
2708
2755
  " status Dry-run reconcile \u2014 show what would change without writing",
2709
2756
  " watch Poll for changes and re-render on each cycle",
2757
+ " update Upgrade latticesql to the latest version",
2710
2758
  "",
2711
2759
  "Options (generate):",
2712
2760
  " --config, -c <path> Path to config file (default: ./lattice.config.yml)",
@@ -2744,20 +2792,40 @@ function printHelp() {
2744
2792
  ].join("\n")
2745
2793
  );
2746
2794
  }
2747
- function printVersion() {
2795
+ function getVersion() {
2748
2796
  try {
2749
2797
  const pkgPath = new URL("../package.json", import.meta.url).pathname;
2750
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
2751
- console.log(pkg.version);
2798
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
2799
+ return pkg.version;
2752
2800
  } catch {
2753
- console.log("unknown");
2801
+ return "unknown";
2802
+ }
2803
+ }
2804
+ function printVersion() {
2805
+ console.log(getVersion());
2806
+ }
2807
+ async function runUpdate() {
2808
+ const currentVersion = getVersion();
2809
+ console.log(`Current version: ${currentVersion}`);
2810
+ const latest = await checkForUpdate("latticesql", currentVersion);
2811
+ if (!latest) {
2812
+ console.log("Already up to date.");
2813
+ return;
2814
+ }
2815
+ console.log(`Updating to ${latest}...`);
2816
+ try {
2817
+ execSync("npm install -g latticesql@latest", { stdio: "inherit" });
2818
+ console.log(`Updated latticesql ${currentVersion} \u2192 ${latest}`);
2819
+ } catch {
2820
+ console.error("Update failed. Try running manually: npm install -g latticesql@latest");
2821
+ process.exit(1);
2754
2822
  }
2755
2823
  }
2756
2824
  function runGenerate(args) {
2757
2825
  const configPath = resolve4(args.config);
2758
2826
  let raw;
2759
2827
  try {
2760
- raw = readFileSync6(configPath, "utf-8");
2828
+ raw = readFileSync7(configPath, "utf-8");
2761
2829
  } catch {
2762
2830
  console.error(`Error: cannot read config file at "${configPath}"`);
2763
2831
  process.exit(1);
@@ -2924,6 +2992,19 @@ function main() {
2924
2992
  printHelp();
2925
2993
  process.exit(args.command === void 0 && !args.help ? 1 : 0);
2926
2994
  }
2995
+ const version = getVersion();
2996
+ if (version !== "unknown") {
2997
+ checkForUpdate("latticesql", version).then((latest) => {
2998
+ if (latest) {
2999
+ process.on("exit", () => {
3000
+ console.log(
3001
+ `
3002
+ Update available: ${version} \u2192 ${latest} \u2014 run "lattice update" to upgrade`
3003
+ );
3004
+ });
3005
+ }
3006
+ }).catch(() => void 0);
3007
+ }
2927
3008
  switch (args.command) {
2928
3009
  case "generate":
2929
3010
  runGenerate(args);
@@ -2940,6 +3021,9 @@ function main() {
2940
3021
  case "watch":
2941
3022
  void runWatch(args);
2942
3023
  break;
3024
+ case "update":
3025
+ void runUpdate();
3026
+ break;
2943
3027
  default:
2944
3028
  console.error(`Unknown command: ${args.command}`);
2945
3029
  printHelp();
package/dist/index.cjs CHANGED
@@ -246,7 +246,7 @@ var SchemaManager = class {
246
246
  applySchema(adapter) {
247
247
  for (const [name, def] of this._tables) {
248
248
  const pkCols = this._tablePK.get(name) ?? ["id"];
249
- let constraints = def.tableConstraints ? [...def.tableConstraints] : [];
249
+ const constraints = def.tableConstraints ? [...def.tableConstraints] : [];
250
250
  if (pkCols.length > 1) {
251
251
  const alreadyHasPK = constraints.some((c) => c.toUpperCase().startsWith("PRIMARY KEY"));
252
252
  if (!alreadyHasPK) {
@@ -290,7 +290,7 @@ var SchemaManager = class {
290
290
  queryTable(adapter, name) {
291
291
  if (this._tables.has(name)) {
292
292
  const def = this._tables.get(name);
293
- if (def.columns && "deleted_at" in def.columns) {
293
+ if (def?.columns && "deleted_at" in def.columns) {
294
294
  return adapter.all(`SELECT * FROM "${name}" WHERE deleted_at IS NULL`);
295
295
  }
296
296
  return adapter.all(`SELECT * FROM "${name}"`);
@@ -474,7 +474,7 @@ function resolveEntitySource(source, entityRow, entityPk, adapter, protection) {
474
474
  case "self":
475
475
  return [entityRow];
476
476
  case "hasMany": {
477
- if (protection && protection.protectedTables.has(source.table)) {
477
+ if (protection?.protectedTables.has(source.table)) {
478
478
  if (source.table === protection.currentTable) return [entityRow];
479
479
  return [];
480
480
  }
@@ -486,7 +486,7 @@ function resolveEntitySource(source, entityRow, entityPk, adapter, protection) {
486
486
  return adapter.all(sql, params);
487
487
  }
488
488
  case "manyToMany": {
489
- if (protection && protection.protectedTables.has(source.remoteTable)) {
489
+ if (protection?.protectedTables.has(source.remoteTable)) {
490
490
  if (source.remoteTable === protection.currentTable) return [entityRow];
491
491
  return [];
492
492
  }
@@ -512,7 +512,7 @@ function resolveEntitySource(source, entityRow, entityPk, adapter, protection) {
512
512
  return adapter.all(sql, params);
513
513
  }
514
514
  case "belongsTo": {
515
- if (protection && protection.protectedTables.has(source.table)) {
515
+ if (protection?.protectedTables.has(source.table)) {
516
516
  if (source.table === protection.currentTable) return [entityRow];
517
517
  return [];
518
518
  }
@@ -1755,7 +1755,12 @@ var Lattice = class {
1755
1755
  this._assertNotInit("define");
1756
1756
  const compiledDef = {
1757
1757
  ...def,
1758
- render: def.render ? compileRender(def, table, this._schema, this._adapter) : () => "",
1758
+ render: def.render ? compileRender(
1759
+ def,
1760
+ table,
1761
+ this._schema,
1762
+ this._adapter
1763
+ ) : () => "",
1759
1764
  outputFile: def.outputFile ?? `.schema-only/${table}.md`
1760
1765
  };
1761
1766
  this._schema.define(table, compiledDef);
@@ -1834,9 +1839,7 @@ var Lattice = class {
1834
1839
  `Entity context "${table}" has encrypted: true but no encryptionKey was provided in Lattice options`
1835
1840
  );
1836
1841
  }
1837
- if (!this._encryptionKey) {
1838
- this._encryptionKey = deriveKey(this._encryptionKeyRaw);
1839
- }
1842
+ this._encryptionKey ??= deriveKey(this._encryptionKeyRaw);
1840
1843
  const pragmaRows = this._adapter.all(`PRAGMA table_info("${table}")`);
1841
1844
  const allCols = pragmaRows.map((r) => r.name);
1842
1845
  const encCols = resolveEncryptedColumns(def.encrypted, allCols);
@@ -2677,10 +2680,8 @@ function fixSchemaConflicts(db, checks) {
2677
2680
  }
2678
2681
  if (tableExists(db, "__lattice_migrations")) {
2679
2682
  const versionCol = db.prepare('PRAGMA table_info("__lattice_migrations")').all().find((c) => c.name === "version");
2680
- if (versionCol && versionCol.type.toUpperCase().includes("INTEGER")) {
2681
- db.exec(
2682
- 'ALTER TABLE "__lattice_migrations" RENAME TO "__lattice_migrations_v1"'
2683
- );
2683
+ if (versionCol?.type.toUpperCase().includes("INTEGER")) {
2684
+ db.exec('ALTER TABLE "__lattice_migrations" RENAME TO "__lattice_migrations_v1"');
2684
2685
  }
2685
2686
  }
2686
2687
  }
package/dist/index.d.cts CHANGED
@@ -1539,10 +1539,10 @@ declare function contentHash(content: string): string;
1539
1539
  * // Now safe to call lattice.init()
1540
1540
  * ```
1541
1541
  */
1542
- declare function fixSchemaConflicts(db: Database.Database, checks: Array<{
1542
+ declare function fixSchemaConflicts(db: Database.Database, checks: {
1543
1543
  table: string;
1544
1544
  requiredColumns: string[];
1545
- }>): void;
1545
+ }[]): void;
1546
1546
 
1547
1547
  /**
1548
1548
  * Derive a 256-bit AES key from a master password using scrypt.
package/dist/index.d.ts CHANGED
@@ -1539,10 +1539,10 @@ declare function contentHash(content: string): string;
1539
1539
  * // Now safe to call lattice.init()
1540
1540
  * ```
1541
1541
  */
1542
- declare function fixSchemaConflicts(db: Database.Database, checks: Array<{
1542
+ declare function fixSchemaConflicts(db: Database.Database, checks: {
1543
1543
  table: string;
1544
1544
  requiredColumns: string[];
1545
- }>): void;
1545
+ }[]): void;
1546
1546
 
1547
1547
  /**
1548
1548
  * Derive a 256-bit AES key from a master password using scrypt.
package/dist/index.js CHANGED
@@ -179,7 +179,7 @@ var SchemaManager = class {
179
179
  applySchema(adapter) {
180
180
  for (const [name, def] of this._tables) {
181
181
  const pkCols = this._tablePK.get(name) ?? ["id"];
182
- let constraints = def.tableConstraints ? [...def.tableConstraints] : [];
182
+ const constraints = def.tableConstraints ? [...def.tableConstraints] : [];
183
183
  if (pkCols.length > 1) {
184
184
  const alreadyHasPK = constraints.some((c) => c.toUpperCase().startsWith("PRIMARY KEY"));
185
185
  if (!alreadyHasPK) {
@@ -223,7 +223,7 @@ var SchemaManager = class {
223
223
  queryTable(adapter, name) {
224
224
  if (this._tables.has(name)) {
225
225
  const def = this._tables.get(name);
226
- if (def.columns && "deleted_at" in def.columns) {
226
+ if (def?.columns && "deleted_at" in def.columns) {
227
227
  return adapter.all(`SELECT * FROM "${name}" WHERE deleted_at IS NULL`);
228
228
  }
229
229
  return adapter.all(`SELECT * FROM "${name}"`);
@@ -407,7 +407,7 @@ function resolveEntitySource(source, entityRow, entityPk, adapter, protection) {
407
407
  case "self":
408
408
  return [entityRow];
409
409
  case "hasMany": {
410
- if (protection && protection.protectedTables.has(source.table)) {
410
+ if (protection?.protectedTables.has(source.table)) {
411
411
  if (source.table === protection.currentTable) return [entityRow];
412
412
  return [];
413
413
  }
@@ -419,7 +419,7 @@ function resolveEntitySource(source, entityRow, entityPk, adapter, protection) {
419
419
  return adapter.all(sql, params);
420
420
  }
421
421
  case "manyToMany": {
422
- if (protection && protection.protectedTables.has(source.remoteTable)) {
422
+ if (protection?.protectedTables.has(source.remoteTable)) {
423
423
  if (source.remoteTable === protection.currentTable) return [entityRow];
424
424
  return [];
425
425
  }
@@ -445,7 +445,7 @@ function resolveEntitySource(source, entityRow, entityPk, adapter, protection) {
445
445
  return adapter.all(sql, params);
446
446
  }
447
447
  case "belongsTo": {
448
- if (protection && protection.protectedTables.has(source.table)) {
448
+ if (protection?.protectedTables.has(source.table)) {
449
449
  if (source.table === protection.currentTable) return [entityRow];
450
450
  return [];
451
451
  }
@@ -1688,7 +1688,12 @@ var Lattice = class {
1688
1688
  this._assertNotInit("define");
1689
1689
  const compiledDef = {
1690
1690
  ...def,
1691
- render: def.render ? compileRender(def, table, this._schema, this._adapter) : () => "",
1691
+ render: def.render ? compileRender(
1692
+ def,
1693
+ table,
1694
+ this._schema,
1695
+ this._adapter
1696
+ ) : () => "",
1692
1697
  outputFile: def.outputFile ?? `.schema-only/${table}.md`
1693
1698
  };
1694
1699
  this._schema.define(table, compiledDef);
@@ -1767,9 +1772,7 @@ var Lattice = class {
1767
1772
  `Entity context "${table}" has encrypted: true but no encryptionKey was provided in Lattice options`
1768
1773
  );
1769
1774
  }
1770
- if (!this._encryptionKey) {
1771
- this._encryptionKey = deriveKey(this._encryptionKeyRaw);
1772
- }
1775
+ this._encryptionKey ??= deriveKey(this._encryptionKeyRaw);
1773
1776
  const pragmaRows = this._adapter.all(`PRAGMA table_info("${table}")`);
1774
1777
  const allCols = pragmaRows.map((r) => r.name);
1775
1778
  const encCols = resolveEncryptedColumns(def.encrypted, allCols);
@@ -2610,10 +2613,8 @@ function fixSchemaConflicts(db, checks) {
2610
2613
  }
2611
2614
  if (tableExists(db, "__lattice_migrations")) {
2612
2615
  const versionCol = db.prepare('PRAGMA table_info("__lattice_migrations")').all().find((c) => c.name === "version");
2613
- if (versionCol && versionCol.type.toUpperCase().includes("INTEGER")) {
2614
- db.exec(
2615
- 'ALTER TABLE "__lattice_migrations" RENAME TO "__lattice_migrations_v1"'
2616
- );
2616
+ if (versionCol?.type.toUpperCase().includes("INTEGER")) {
2617
+ db.exec('ALTER TABLE "__lattice_migrations" RENAME TO "__lattice_migrations_v1"');
2617
2618
  }
2618
2619
  }
2619
2620
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "latticesql",
3
- "version": "0.18.3",
3
+ "version": "1.0.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",