latticesql 4.3.1 → 4.3.3

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/index.js CHANGED
@@ -909,7 +909,7 @@ async function registerPostgresPolyfills(run) {
909
909
  await run(sql);
910
910
  } catch (err) {
911
911
  const msg = err instanceof Error ? err.message : String(err);
912
- if (/permission denied/i.test(msg)) {
912
+ if (/permission denied/i.test(msg) || /must be owner of/i.test(msg)) {
913
913
  permissionDenied = true;
914
914
  } else {
915
915
  console.warn(`[PostgresAdapter] ${warn}`, msg);
@@ -1427,40 +1427,111 @@ var init_postgres = __esm({
1427
1427
  SELECT doc::jsonb #>> string_to_array(regexp_replace(path, '^\\$\\.?', ''), '.')
1428
1428
  $fn$;
1429
1429
  END IF;
1430
+ END $do$;`
1431
+ },
1432
+ {
1433
+ warn: "could not register strftime format helper:",
1434
+ // Shared format translator (SQLite strftime tokens → to_char patterns), in UTC.
1435
+ // Factored out so the 2-arg and 3-arg strftime overloads share one definition.
1436
+ // Each polyfill below is CREATE OR REPLACE (so an existing cloud's prior copy is
1437
+ // UPGRADED when the owner opens), wrapped in a DO block whose EXCEPTION swallows a
1438
+ // scoped member's insufficient-privilege failure IN A SUBTRANSACTION — so a member
1439
+ // that runs registration inside a transaction is NOT aborted. ("must be owner of
1440
+ // function", raised when a non-owner CREATE OR REPLACEs another role's function, is
1441
+ // SQLSTATE 42501 / `insufficient_privilege`, same class as "permission denied".) The
1442
+ // owner already owns + replaced the function; the member simply uses the owner's
1443
+ // copy — and `ownPolyfillsByGroup` re-owns the whole set to the member group so any
1444
+ // member can replace them on a later upgrade.
1445
+ sql: `DO $do$ BEGIN
1446
+ CREATE OR REPLACE FUNCTION __lattice_strftime_fmt(ts timestamptz, format text)
1447
+ RETURNS text
1448
+ LANGUAGE sql
1449
+ IMMUTABLE
1450
+ AS $fn$
1451
+ SELECT to_char(
1452
+ ts AT TIME ZONE 'UTC',
1453
+ replace(replace(replace(replace(replace(replace(replace(replace(
1454
+ format,
1455
+ '%Y', 'YYYY'),
1456
+ '%m', 'MM'),
1457
+ '%d', 'DD'),
1458
+ '%H', 'HH24'),
1459
+ '%M', 'MI'),
1460
+ '%S', 'SS'),
1461
+ '%f', 'MS'),
1462
+ 'T', '"T"')
1463
+ );
1464
+ $fn$;
1465
+ EXCEPTION WHEN insufficient_privilege THEN NULL;
1430
1466
  END $do$;`
1431
1467
  },
1432
1468
  {
1433
1469
  warn: "could not register strftime polyfill:",
1470
+ // The pre-4.3.3 strftime cast the modifier straight to timestamptz and threw
1471
+ // `invalid input syntax for type timestamp with time zone: ""` on a legacy ''
1472
+ // value (3.x stored nullable TEXT timestamps as ''), bricking the whole workspace
1473
+ // open. Now an empty, whitespace, or unparseable time string returns NULL (SQLite's
1474
+ // strftime semantics) instead of aborting the query.
1434
1475
  sql: `DO $do$ BEGIN
1435
- IF to_regprocedure('strftime(text, text)') IS NULL THEN
1436
- CREATE FUNCTION strftime(format text, modifier text)
1437
- RETURNS text
1438
- LANGUAGE plpgsql
1439
- IMMUTABLE
1440
- AS $fn$
1441
- DECLARE ts timestamptz;
1442
- BEGIN
1443
- IF modifier = 'now' THEN
1444
- ts := now();
1445
- ELSE
1476
+ CREATE OR REPLACE FUNCTION strftime(format text, modifier text)
1477
+ RETURNS text
1478
+ LANGUAGE plpgsql
1479
+ IMMUTABLE
1480
+ AS $fn$
1481
+ DECLARE ts timestamptz;
1482
+ BEGIN
1483
+ IF modifier = 'now' THEN
1484
+ ts := now();
1485
+ ELSIF NULLIF(btrim(modifier), '') IS NULL THEN
1486
+ RETURN NULL;
1487
+ ELSE
1488
+ BEGIN
1446
1489
  ts := modifier::timestamptz;
1447
- END IF;
1448
- RETURN to_char(
1449
- ts AT TIME ZONE 'UTC',
1450
- replace(replace(replace(replace(replace(replace(replace(replace(
1451
- format,
1452
- '%Y', 'YYYY'),
1453
- '%m', 'MM'),
1454
- '%d', 'DD'),
1455
- '%H', 'HH24'),
1456
- '%M', 'MI'),
1457
- '%S', 'SS'),
1458
- '%f', 'MS'),
1459
- 'T', '"T"')
1460
- );
1490
+ EXCEPTION WHEN others THEN
1491
+ RETURN NULL;
1492
+ END;
1493
+ END IF;
1494
+ RETURN __lattice_strftime_fmt(ts, format);
1495
+ END;
1496
+ $fn$;
1497
+ EXCEPTION WHEN insufficient_privilege THEN NULL;
1498
+ END $do$;`
1499
+ },
1500
+ {
1501
+ warn: "could not register 3-arg strftime polyfill:",
1502
+ // SQLite's 3-arg strftime(format, timestring, modifier) — used by the changelog
1503
+ // retention prune (e.g. strftime('%Y-...','now','-30 days')). Postgres had no
1504
+ // 3-arg overload, so that prune threw `function strftime(...) does not exist` on
1505
+ // every PG cloud with retention. Resolve the base time, apply the modifier as an
1506
+ // interval, then reuse the shared formatter. Empty/invalid → NULL, never throws.
1507
+ sql: `DO $do$ BEGIN
1508
+ CREATE OR REPLACE FUNCTION strftime(format text, timestring text, modifier text)
1509
+ RETURNS text
1510
+ LANGUAGE plpgsql
1511
+ IMMUTABLE
1512
+ AS $fn$
1513
+ DECLARE ts timestamptz;
1514
+ BEGIN
1515
+ IF timestring = 'now' THEN
1516
+ ts := now();
1517
+ ELSIF NULLIF(btrim(timestring), '') IS NULL THEN
1518
+ RETURN NULL;
1519
+ ELSE
1520
+ BEGIN
1521
+ ts := timestring::timestamptz;
1522
+ EXCEPTION WHEN others THEN
1523
+ RETURN NULL;
1524
+ END;
1525
+ END IF;
1526
+ BEGIN
1527
+ ts := ts + modifier::interval;
1528
+ EXCEPTION WHEN others THEN
1529
+ RETURN NULL;
1461
1530
  END;
1462
- $fn$;
1463
- END IF;
1531
+ RETURN __lattice_strftime_fmt(ts, format);
1532
+ END;
1533
+ $fn$;
1534
+ EXCEPTION WHEN insufficient_privilege THEN NULL;
1464
1535
  END $do$;`
1465
1536
  }
1466
1537
  ];
@@ -9397,7 +9468,12 @@ CREATE TRIGGER "${trg}" AFTER INSERT OR UPDATE OR DELETE ON ${q3}
9397
9468
  async function ownPolyfillsByGroup(db) {
9398
9469
  if (!isPg(db)) return;
9399
9470
  const group3 = await memberGroupFor(db);
9400
- for (const sig of ["json_extract(text, text)", "strftime(text, text)"]) {
9471
+ for (const sig of [
9472
+ "json_extract(text, text)",
9473
+ "strftime(text, text)",
9474
+ "strftime(text, text, text)",
9475
+ "__lattice_strftime_fmt(timestamptz, text)"
9476
+ ]) {
9401
9477
  try {
9402
9478
  const reg = await getAsyncOrSync(db.adapter, `SELECT to_regprocedure($1) AS reg`, [sig]);
9403
9479
  if (reg?.reg == null) continue;
@@ -23495,12 +23571,12 @@ var getNodeModulesParentDirs;
23495
23571
  var init_getNodeModulesParentDirs = __esm({
23496
23572
  "node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-node/getNodeModulesParentDirs.js"() {
23497
23573
  "use strict";
23498
- getNodeModulesParentDirs = (dirname18) => {
23574
+ getNodeModulesParentDirs = (dirname19) => {
23499
23575
  const cwd = process.cwd();
23500
- if (!dirname18) {
23576
+ if (!dirname19) {
23501
23577
  return [cwd];
23502
23578
  }
23503
- const normalizedPath = normalize(dirname18);
23579
+ const normalizedPath = normalize(dirname19);
23504
23580
  const parts = normalizedPath.split(sep3);
23505
23581
  const nodeModulesIndex = parts.indexOf("node_modules");
23506
23582
  const parentDir = nodeModulesIndex !== -1 ? parts.slice(0, nodeModulesIndex).join(sep3) : normalizedPath;
@@ -23578,8 +23654,8 @@ var init_getTypeScriptUserAgentPair = __esm({
23578
23654
  tscVersion = null;
23579
23655
  return void 0;
23580
23656
  }
23581
- const dirname18 = typeof __dirname !== "undefined" ? __dirname : void 0;
23582
- const nodeModulesParentDirs = getNodeModulesParentDirs(dirname18);
23657
+ const dirname19 = typeof __dirname !== "undefined" ? __dirname : void 0;
23658
+ const nodeModulesParentDirs = getNodeModulesParentDirs(dirname19);
23583
23659
  let versionFromApp;
23584
23660
  for (const nodeModulesParentDir of nodeModulesParentDirs) {
23585
23661
  try {
@@ -62153,9 +62229,9 @@ init_summarize();
62153
62229
  init_http2();
62154
62230
  import { createServer } from "http";
62155
62231
  import { spawn as spawn2 } from "child_process";
62156
- import { existsSync as existsSync31 } from "fs";
62232
+ import { existsSync as existsSync32 } from "fs";
62157
62233
  import { WebSocketServer, WebSocket } from "ws";
62158
- import { dirname as dirname16, resolve as resolve16 } from "path";
62234
+ import { dirname as dirname17, resolve as resolve16 } from "path";
62159
62235
 
62160
62236
  // src/gui/active-db.ts
62161
62237
  init_adapter();
@@ -79982,8 +80058,16 @@ async function dispatchIngestRoute(req, res, ctx) {
79982
80058
 
79983
80059
  // src/gui/sources-routes.ts
79984
80060
  init_user_config();
79985
- import { statSync as statSync10, readdirSync as readdirSync8, readFileSync as readFileSync24, writeFileSync as writeFileSync9 } from "fs";
79986
- import { resolve as resolve12, join as join30, basename as basename13, sep as sep8 } from "path";
80061
+ import {
80062
+ statSync as statSync10,
80063
+ readdirSync as readdirSync8,
80064
+ readFileSync as readFileSync24,
80065
+ writeFileSync as writeFileSync9,
80066
+ existsSync as existsSync26,
80067
+ mkdirSync as mkdirSync11,
80068
+ rmSync
80069
+ } from "fs";
80070
+ import { resolve as resolve12, join as join30, basename as basename13, sep as sep8, dirname as dirname15 } from "path";
79987
80071
  import { execFile } from "child_process";
79988
80072
  import { randomUUID as randomUUID4 } from "crypto";
79989
80073
  init_http2();
@@ -79991,20 +80075,49 @@ var MAX_LIST_ENTRIES = 2e3;
79991
80075
  var MAX_INGEST_FILES = 500;
79992
80076
  var MAX_INGEST_DEPTH = 8;
79993
80077
  var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store", "__pycache__", ".venv", "venv"]);
79994
- function rootsFile() {
79995
- return join30(configDir(), "sources.json");
80078
+ function rootsFile(configPath) {
80079
+ return join30(dirname15(configPath), "sources.json");
80080
+ }
80081
+ function migrateGlobalRootsIfNeeded(configPath) {
80082
+ const wsFile = rootsFile(configPath);
80083
+ if (existsSync26(wsFile)) return;
80084
+ const globalFile = join30(configDir(), "sources.json");
80085
+ if (globalFile === wsFile || !existsSync26(globalFile)) return;
80086
+ let roots;
80087
+ try {
80088
+ const parsed = JSON.parse(readFileSync24(globalFile, "utf8"));
80089
+ if (!Array.isArray(parsed.roots) || parsed.roots.length === 0) return;
80090
+ roots = parsed.roots;
80091
+ } catch {
80092
+ return;
80093
+ }
80094
+ try {
80095
+ mkdirSync11(dirname15(wsFile), { recursive: true });
80096
+ writeFileSync9(wsFile, JSON.stringify({ roots }, null, 2), "utf8");
80097
+ } catch {
80098
+ return;
80099
+ }
80100
+ try {
80101
+ rmSync(globalFile, { force: true });
80102
+ } catch {
80103
+ try {
80104
+ writeFileSync9(globalFile, JSON.stringify({ roots: [] }, null, 2), "utf8");
80105
+ } catch {
80106
+ }
80107
+ }
79996
80108
  }
79997
- function readRoots() {
80109
+ function readRoots(configPath) {
80110
+ migrateGlobalRootsIfNeeded(configPath);
79998
80111
  try {
79999
- const raw = readFileSync24(rootsFile(), "utf8");
80112
+ const raw = readFileSync24(rootsFile(configPath), "utf8");
80000
80113
  const parsed = JSON.parse(raw);
80001
80114
  return Array.isArray(parsed.roots) ? parsed.roots : [];
80002
80115
  } catch {
80003
80116
  return [];
80004
80117
  }
80005
80118
  }
80006
- function writeRoots(roots) {
80007
- writeFileSync9(rootsFile(), JSON.stringify({ roots }, null, 2), "utf8");
80119
+ function writeRoots(configPath, roots) {
80120
+ writeFileSync9(rootsFile(configPath), JSON.stringify({ roots }, null, 2), "utf8");
80008
80121
  }
80009
80122
  function safeResolveInside2(target, roots) {
80010
80123
  const abs = resolve12(target);
@@ -80094,14 +80207,14 @@ async function ingestFolder(abs, ingestFile) {
80094
80207
  return { ingested, skipped };
80095
80208
  }
80096
80209
  async function dispatchSourcesRoute(req, res, deps) {
80097
- const { pathname, method, ingestFile } = deps;
80210
+ const { pathname, method, ingestFile, configPath } = deps;
80098
80211
  if (!pathname.startsWith("/api/sources/")) return false;
80099
80212
  if (!localFileOpenEnabled()) {
80100
80213
  sendJson(res, { enabled: false });
80101
80214
  return true;
80102
80215
  }
80103
80216
  if (pathname === "/api/sources/roots" && method === "GET") {
80104
- sendJson(res, { enabled: true, roots: readRoots() });
80217
+ sendJson(res, { enabled: true, roots: readRoots(configPath) });
80105
80218
  return true;
80106
80219
  }
80107
80220
  if (pathname === "/api/sources/roots" && method === "POST") {
@@ -80127,12 +80240,12 @@ async function dispatchSourcesRoute(req, res, deps) {
80127
80240
  sendJson(res, { error: `path not found: ${abs}` }, 400);
80128
80241
  return true;
80129
80242
  }
80130
- const roots = readRoots();
80243
+ const roots = readRoots(configPath);
80131
80244
  let root6 = roots.find((r6) => resolve12(r6.path) === abs);
80132
80245
  if (!root6) {
80133
80246
  root6 = { id: randomUUID4(), path: abs, kind, name: basename13(abs) || abs };
80134
80247
  roots.push(root6);
80135
- writeRoots(roots);
80248
+ writeRoots(configPath, roots);
80136
80249
  }
80137
80250
  let result;
80138
80251
  if (kind === "folder") result = await ingestFolder(abs, ingestFile);
@@ -80143,8 +80256,8 @@ async function dispatchSourcesRoute(req, res, deps) {
80143
80256
  const delMatch = /^\/api\/sources\/roots\/([^/]+)$/.exec(pathname);
80144
80257
  if (delMatch && method === "DELETE") {
80145
80258
  const id = decodeURIComponent(delMatch[1] ?? "");
80146
- const roots = readRoots().filter((r6) => r6.id !== id);
80147
- writeRoots(roots);
80259
+ const roots = readRoots(configPath).filter((r6) => r6.id !== id);
80260
+ writeRoots(configPath, roots);
80148
80261
  sendJson(res, { ok: true });
80149
80262
  return true;
80150
80263
  }
@@ -80162,7 +80275,7 @@ async function dispatchSourcesRoute(req, res, deps) {
80162
80275
  sendJson(res, { error: "path is required" }, 400);
80163
80276
  return true;
80164
80277
  }
80165
- const abs = safeResolveInside2(target, readRoots());
80278
+ const abs = safeResolveInside2(target, readRoots(configPath));
80166
80279
  if (!abs) {
80167
80280
  sendJson(res, { error: "path is outside any registered source root" }, 403);
80168
80281
  return true;
@@ -80177,7 +80290,7 @@ async function dispatchSourcesRoute(req, res, deps) {
80177
80290
  if (pathname === "/api/sources/ingest-folder" && method === "POST") {
80178
80291
  const body = await readJson(req).catch(() => ({}));
80179
80292
  const target = typeof body.path === "string" ? body.path : "";
80180
- const abs = safeResolveInside2(target, readRoots());
80293
+ const abs = safeResolveInside2(target, readRoots(configPath));
80181
80294
  if (!abs) {
80182
80295
  sendJson(res, { error: "path is outside any registered source root" }, 403);
80183
80296
  return true;
@@ -80191,7 +80304,7 @@ async function dispatchSourcesRoute(req, res, deps) {
80191
80304
  // src/gui/import-routes.ts
80192
80305
  init_adapter();
80193
80306
  init_http2();
80194
- import { existsSync as existsSync26, readFileSync as readFileSync25, statSync as statSync11 } from "fs";
80307
+ import { existsSync as existsSync27, readFileSync as readFileSync25, statSync as statSync11 } from "fs";
80195
80308
  import { isAbsolute as isAbsolute4, join as join31 } from "path";
80196
80309
  init_native_entities();
80197
80310
  function badRequest(message) {
@@ -80225,7 +80338,7 @@ async function readImportSourceFromFile(db, fileId, latticeRoot) {
80225
80338
  );
80226
80339
  if (!row) throw badRequest("Unknown import file: " + fileId);
80227
80340
  const path3 = localPathOf2(row, latticeRoot);
80228
- if (!path3 || !existsSync26(path3)) {
80341
+ if (!path3 || !existsSync27(path3)) {
80229
80342
  throw badRequest("The import file\u2019s bytes are not available locally.");
80230
80343
  }
80231
80344
  const sizeBytes = statSync11(path3).size;
@@ -80520,7 +80633,7 @@ init_dispatch();
80520
80633
  init_column_descriptions();
80521
80634
  init_mutations();
80522
80635
  init_native_entities();
80523
- import { createReadStream as createReadStream3, existsSync as existsSync27, realpathSync as realpathSync2, statSync as statSync12 } from "fs";
80636
+ import { createReadStream as createReadStream3, existsSync as existsSync28, realpathSync as realpathSync2, statSync as statSync12 } from "fs";
80524
80637
  import { extname as extname3, join as join32, normalize as normalize2, sep as sep9 } from "path";
80525
80638
 
80526
80639
  // src/gui/count-many.ts
@@ -80728,7 +80841,7 @@ async function handleReadRoutes(req, res, ctx, deps) {
80728
80841
  const base = deps.guiAssetsDir.replace(/[/\\]+$/, "");
80729
80842
  const target = normalize2(join32(base, rel));
80730
80843
  const within = target === base || target.startsWith(base + sep9);
80731
- if (!within || !existsSync27(target) || !statSync12(target).isFile()) {
80844
+ if (!within || !existsSync28(target) || !statSync12(target).isFile()) {
80732
80845
  sendJson(res, { error: "asset not found" }, 404);
80733
80846
  return true;
80734
80847
  }
@@ -81936,16 +82049,16 @@ async function handleHistoryRoutes(req, res, ctx, deps) {
81936
82049
  // src/gui/workspaces-routes.ts
81937
82050
  init_http2();
81938
82051
  import { resolve as resolve13 } from "path";
81939
- import { existsSync as existsSync28, rmSync } from "fs";
82052
+ import { existsSync as existsSync29, rmSync as rmSync2 } from "fs";
81940
82053
  init_workspace();
81941
82054
  init_lattice_root();
81942
82055
  init_user_config();
81943
82056
  function cleanupWorkspaceFiles(root6, ws) {
81944
82057
  if (!ws.configPath && ws.kind === "local") {
81945
- rmSync(workspaceDir(root6, ws.dir), { recursive: true, force: true });
82058
+ rmSync2(workspaceDir(root6, ws.dir), { recursive: true, force: true });
81946
82059
  } else if (ws.kind === "cloud") {
81947
- if (ws.configPath && existsSync28(ws.configPath)) {
81948
- rmSync(ws.configPath, { force: true });
82060
+ if (ws.configPath && existsSync29(ws.configPath)) {
82061
+ rmSync2(ws.configPath, { force: true });
81949
82062
  }
81950
82063
  const labelMatch = /^\$\{LATTICE_DB:([A-Za-z0-9._-]+)\}$/.exec(ws.db.trim());
81951
82064
  const label = labelMatch?.[1];
@@ -82148,15 +82261,15 @@ async function handleWorkspacesRoutes(req, res, ctx, deps) {
82148
82261
  // src/gui/databases-routes.ts
82149
82262
  init_http2();
82150
82263
  import { basename as basename15, resolve as resolve15 } from "path";
82151
- import { existsSync as existsSync30 } from "fs";
82264
+ import { existsSync as existsSync31 } from "fs";
82152
82265
  init_parser();
82153
82266
 
82154
82267
  // src/gui/config-paths.ts
82155
82268
  init_parser();
82156
- import { basename as basename14, dirname as dirname15, join as join33, resolve as resolve14 } from "path";
82269
+ import { basename as basename14, dirname as dirname16, join as join33, resolve as resolve14 } from "path";
82157
82270
  import {
82158
- existsSync as existsSync29,
82159
- mkdirSync as mkdirSync11,
82271
+ existsSync as existsSync30,
82272
+ mkdirSync as mkdirSync12,
82160
82273
  readFileSync as readFileSync26,
82161
82274
  readdirSync as readdirSync9,
82162
82275
  unlinkSync as unlinkSync5,
@@ -82164,10 +82277,10 @@ import {
82164
82277
  } from "fs";
82165
82278
  import { parseDocument as parseDocument7 } from "yaml";
82166
82279
  function resolveOutputDirForConfig(configPath) {
82167
- const base = dirname15(resolve14(configPath));
82280
+ const base = dirname16(resolve14(configPath));
82168
82281
  for (const dir of ["context", ".", "generated"]) {
82169
82282
  const abs = resolve14(base, dir);
82170
- if (existsSync29(join33(abs, ".lattice", "manifest.json"))) return abs;
82283
+ if (existsSync30(join33(abs, ".lattice", "manifest.json"))) return abs;
82171
82284
  }
82172
82285
  return resolve14(base, "context");
82173
82286
  }
@@ -82176,7 +82289,7 @@ function friendlyConfigName(parsedName, configPath) {
82176
82289
  return basename14(configPath).replace(/\.(ya?ml)$/, "");
82177
82290
  }
82178
82291
  function listConfigs(activeConfigPath) {
82179
- const dir = dirname15(activeConfigPath);
82292
+ const dir = dirname16(activeConfigPath);
82180
82293
  const entries = [];
82181
82294
  for (const fname of readdirSync9(dir)) {
82182
82295
  if (!fname.endsWith(".yml") && !fname.endsWith(".yaml")) continue;
@@ -82205,17 +82318,17 @@ function listConfigs(activeConfigPath) {
82205
82318
  return entries.sort((a6, b6) => a6.label.localeCompare(b6.label));
82206
82319
  }
82207
82320
  function createBlankConfig(activeConfigPath, dbName) {
82208
- const dir = dirname15(activeConfigPath);
82321
+ const dir = dirname16(activeConfigPath);
82209
82322
  const slug = dbName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
82210
82323
  if (!slug) throw new Error("Workspace name must contain at least one alphanumeric character");
82211
82324
  const configPath = join33(dir, `${slug}.config.yml`);
82212
- if (existsSync29(configPath)) throw new Error(`Config already exists: ${slug}.config.yml`);
82325
+ if (existsSync30(configPath)) throw new Error(`Config already exists: ${slug}.config.yml`);
82213
82326
  const yaml = `db: ./data/${slug}.db
82214
82327
 
82215
82328
  entities: {}
82216
82329
  `;
82217
82330
  writeFileSync10(configPath, yaml, "utf8");
82218
- mkdirSync11(join33(dir, "data"), { recursive: true });
82331
+ mkdirSync12(join33(dir, "data"), { recursive: true });
82219
82332
  return configPath;
82220
82333
  }
82221
82334
  function sqliteFileForConfig(configPath) {
@@ -82224,18 +82337,18 @@ function sqliteFileForConfig(configPath) {
82224
82337
  if (!raw) return null;
82225
82338
  if (isPostgresUrl(raw) || raw.startsWith("${LATTICE_DB:")) return null;
82226
82339
  if (raw === ":memory:" || raw.startsWith("file:")) return null;
82227
- return resolve14(dirname15(configPath), raw);
82340
+ return resolve14(dirname16(configPath), raw);
82228
82341
  }
82229
82342
  function deleteDatabaseFiles(targetConfigPath) {
82230
82343
  const sqliteFile = sqliteFileForConfig(targetConfigPath);
82231
82344
  unlinkSync5(targetConfigPath);
82232
82345
  let deletedDbFile = null;
82233
- if (sqliteFile && existsSync29(sqliteFile)) {
82346
+ if (sqliteFile && existsSync30(sqliteFile)) {
82234
82347
  unlinkSync5(sqliteFile);
82235
82348
  deletedDbFile = sqliteFile;
82236
82349
  for (const suffix of ["-wal", "-shm", "-journal"]) {
82237
82350
  const sidecar = sqliteFile + suffix;
82238
- if (existsSync29(sidecar)) unlinkSync5(sidecar);
82351
+ if (existsSync30(sidecar)) unlinkSync5(sidecar);
82239
82352
  }
82240
82353
  }
82241
82354
  return { deletedConfig: basename14(targetConfigPath), deletedDbFile };
@@ -82269,7 +82382,7 @@ async function handleDatabasesRoutes(req, res, ctx, deps) {
82269
82382
  return true;
82270
82383
  }
82271
82384
  const newPath = resolve15(body.path);
82272
- if (!existsSync30(newPath)) {
82385
+ if (!existsSync31(newPath)) {
82273
82386
  sendJson(res, { error: `Config not found: ${newPath}` }, 400);
82274
82387
  return true;
82275
82388
  }
@@ -82375,8 +82488,8 @@ async function handleDatabasesRoutes(req, res, ctx, deps) {
82375
82488
  // src/gui/server.ts
82376
82489
  init_user_config();
82377
82490
  function resolveDefaultGuiAssetsDir() {
82378
- const bundleSibling = process.argv[1] ? resolve16(dirname16(process.argv[1]), "gui-assets") : null;
82379
- if (bundleSibling && existsSync31(bundleSibling)) return bundleSibling;
82491
+ const bundleSibling = process.argv[1] ? resolve16(dirname17(process.argv[1]), "gui-assets") : null;
82492
+ if (bundleSibling && existsSync32(bundleSibling)) return bundleSibling;
82380
82493
  return resolve16(process.cwd(), "dist", "gui-assets");
82381
82494
  }
82382
82495
  function sendText(res, body, status = 200, contentType = "text/plain; charset=utf-8") {
@@ -82433,7 +82546,7 @@ async function startGuiServer(options) {
82433
82546
  const sessionId = crypto.randomUUID();
82434
82547
  let updateService = null;
82435
82548
  let activeRef = bootConfigPath && bootOutputDir ? await openConfig(bootConfigPath, bootOutputDir, autoRender, options.realtimeWatchdogMs) : null;
82436
- const latticeRoot = (bootConfigPath ? findLatticeRoot(dirname16(bootConfigPath)) : null) ?? (options.latticeRoot ? resolve16(options.latticeRoot) : null);
82549
+ const latticeRoot = (bootConfigPath ? findLatticeRoot(dirname17(bootConfigPath)) : null) ?? (options.latticeRoot ? resolve16(options.latticeRoot) : null);
82437
82550
  let currentWorkspaceId = null;
82438
82551
  if (latticeRoot && bootConfigPath) {
82439
82552
  const launched = listWorkspaces(latticeRoot).find(
@@ -82787,7 +82900,7 @@ async function startGuiServer(options) {
82787
82900
  createJunction: (otherTable) => createFileJunction(active, otherTable, sessionId),
82788
82901
  createEntity: (entity, columns) => createUserEntity(active, entity, columns, sessionId),
82789
82902
  aggressiveness: getAggressiveness(),
82790
- latticeRoot: dirname16(active.configPath),
82903
+ latticeRoot: dirname17(active.configPath),
82791
82904
  configPath: active.configPath,
82792
82905
  outputDir: active.outputDir,
82793
82906
  sessionId,
@@ -82812,7 +82925,7 @@ async function startGuiServer(options) {
82812
82925
  createJunction: (otherTable) => createFileJunction(active, otherTable, sessionId),
82813
82926
  createEntity: (entity, columns) => createUserEntity(active, entity, columns, sessionId),
82814
82927
  aggressiveness: getAggressiveness(),
82815
- latticeRoot: dirname16(active.configPath),
82928
+ latticeRoot: dirname17(active.configPath),
82816
82929
  configPath: active.configPath,
82817
82930
  outputDir: active.outputDir,
82818
82931
  sessionId,
@@ -82823,6 +82936,7 @@ async function startGuiServer(options) {
82823
82936
  return await dispatchSourcesRoute(req2, res2, {
82824
82937
  db: active.db,
82825
82938
  ingestFile: (p3) => ingestLocalFile(ingestCtx, mctx, p3, false),
82939
+ configPath: active.configPath,
82826
82940
  pathname,
82827
82941
  method
82828
82942
  });
@@ -82838,7 +82952,7 @@ async function startGuiServer(options) {
82838
82952
  return await dispatchImportRoute(req2, res2, {
82839
82953
  db: active.db,
82840
82954
  configPath: active.configPath,
82841
- latticeRoot: dirname16(active.configPath),
82955
+ latticeRoot: dirname17(active.configPath),
82842
82956
  validTables: active.validTables,
82843
82957
  softDeletable: active.softDeletable
82844
82958
  });
@@ -82867,7 +82981,7 @@ async function startGuiServer(options) {
82867
82981
  if (!pathname.startsWith("/api/files/")) return false;
82868
82982
  return await dispatchFilesRoute(req2, res2, {
82869
82983
  db: active.db,
82870
- latticeRoot: dirname16(active.configPath),
82984
+ latticeRoot: dirname17(active.configPath),
82871
82985
  configPath: active.configPath,
82872
82986
  pathname,
82873
82987
  method
@@ -83078,8 +83192,8 @@ ${e6.stack ?? ""}`
83078
83192
  }
83079
83193
 
83080
83194
  // src/cloud/file-source-key-store.ts
83081
- import { readFileSync as readFileSync27, writeFileSync as writeFileSync11, existsSync as existsSync32, mkdirSync as mkdirSync12, renameSync as renameSync5, chmodSync as chmodSync3 } from "fs";
83082
- import { dirname as dirname17, resolve as resolve17 } from "path";
83195
+ import { readFileSync as readFileSync27, writeFileSync as writeFileSync11, existsSync as existsSync33, mkdirSync as mkdirSync13, renameSync as renameSync5, chmodSync as chmodSync3 } from "fs";
83196
+ import { dirname as dirname18, resolve as resolve17 } from "path";
83083
83197
  import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, randomBytes as randomBytes9, scryptSync as scryptSync3 } from "crypto";
83084
83198
  var ENC_HEADER = "LATTICE-KMS-v1\n";
83085
83199
  var SCRYPT_N = 1 << 15;
@@ -83130,9 +83244,9 @@ var FileSourceKeyStore = class {
83130
83244
  // ── internals ────────────────────────────────────────────────────────
83131
83245
  load() {
83132
83246
  const out = /* @__PURE__ */ new Map();
83133
- if (!existsSync32(this.path)) {
83134
- const dir = dirname17(this.path);
83135
- if (!existsSync32(dir)) mkdirSync12(dir, { recursive: true, mode: 448 });
83247
+ if (!existsSync33(this.path)) {
83248
+ const dir = dirname18(this.path);
83249
+ if (!existsSync33(dir)) mkdirSync13(dir, { recursive: true, mode: 448 });
83136
83250
  return out;
83137
83251
  }
83138
83252
  const raw = readFileSync27(this.path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "latticesql",
3
- "version": "4.3.1",
3
+ "version": "4.3.3",
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",