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/cli.js CHANGED
@@ -1704,7 +1704,7 @@ async function registerPostgresPolyfills(run) {
1704
1704
  await run(sql);
1705
1705
  } catch (err) {
1706
1706
  const msg = err instanceof Error ? err.message : String(err);
1707
- if (/permission denied/i.test(msg)) {
1707
+ if (/permission denied/i.test(msg) || /must be owner of/i.test(msg)) {
1708
1708
  permissionDenied = true;
1709
1709
  } else {
1710
1710
  console.warn(`[PostgresAdapter] ${warn}`, msg);
@@ -2222,40 +2222,111 @@ var init_postgres = __esm({
2222
2222
  SELECT doc::jsonb #>> string_to_array(regexp_replace(path, '^\\$\\.?', ''), '.')
2223
2223
  $fn$;
2224
2224
  END IF;
2225
+ END $do$;`
2226
+ },
2227
+ {
2228
+ warn: "could not register strftime format helper:",
2229
+ // Shared format translator (SQLite strftime tokens → to_char patterns), in UTC.
2230
+ // Factored out so the 2-arg and 3-arg strftime overloads share one definition.
2231
+ // Each polyfill below is CREATE OR REPLACE (so an existing cloud's prior copy is
2232
+ // UPGRADED when the owner opens), wrapped in a DO block whose EXCEPTION swallows a
2233
+ // scoped member's insufficient-privilege failure IN A SUBTRANSACTION — so a member
2234
+ // that runs registration inside a transaction is NOT aborted. ("must be owner of
2235
+ // function", raised when a non-owner CREATE OR REPLACEs another role's function, is
2236
+ // SQLSTATE 42501 / `insufficient_privilege`, same class as "permission denied".) The
2237
+ // owner already owns + replaced the function; the member simply uses the owner's
2238
+ // copy — and `ownPolyfillsByGroup` re-owns the whole set to the member group so any
2239
+ // member can replace them on a later upgrade.
2240
+ sql: `DO $do$ BEGIN
2241
+ CREATE OR REPLACE FUNCTION __lattice_strftime_fmt(ts timestamptz, format text)
2242
+ RETURNS text
2243
+ LANGUAGE sql
2244
+ IMMUTABLE
2245
+ AS $fn$
2246
+ SELECT to_char(
2247
+ ts AT TIME ZONE 'UTC',
2248
+ replace(replace(replace(replace(replace(replace(replace(replace(
2249
+ format,
2250
+ '%Y', 'YYYY'),
2251
+ '%m', 'MM'),
2252
+ '%d', 'DD'),
2253
+ '%H', 'HH24'),
2254
+ '%M', 'MI'),
2255
+ '%S', 'SS'),
2256
+ '%f', 'MS'),
2257
+ 'T', '"T"')
2258
+ );
2259
+ $fn$;
2260
+ EXCEPTION WHEN insufficient_privilege THEN NULL;
2225
2261
  END $do$;`
2226
2262
  },
2227
2263
  {
2228
2264
  warn: "could not register strftime polyfill:",
2265
+ // The pre-4.3.3 strftime cast the modifier straight to timestamptz and threw
2266
+ // `invalid input syntax for type timestamp with time zone: ""` on a legacy ''
2267
+ // value (3.x stored nullable TEXT timestamps as ''), bricking the whole workspace
2268
+ // open. Now an empty, whitespace, or unparseable time string returns NULL (SQLite's
2269
+ // strftime semantics) instead of aborting the query.
2229
2270
  sql: `DO $do$ BEGIN
2230
- IF to_regprocedure('strftime(text, text)') IS NULL THEN
2231
- CREATE FUNCTION strftime(format text, modifier text)
2232
- RETURNS text
2233
- LANGUAGE plpgsql
2234
- IMMUTABLE
2235
- AS $fn$
2236
- DECLARE ts timestamptz;
2237
- BEGIN
2238
- IF modifier = 'now' THEN
2239
- ts := now();
2240
- ELSE
2271
+ CREATE OR REPLACE FUNCTION strftime(format text, modifier text)
2272
+ RETURNS text
2273
+ LANGUAGE plpgsql
2274
+ IMMUTABLE
2275
+ AS $fn$
2276
+ DECLARE ts timestamptz;
2277
+ BEGIN
2278
+ IF modifier = 'now' THEN
2279
+ ts := now();
2280
+ ELSIF NULLIF(btrim(modifier), '') IS NULL THEN
2281
+ RETURN NULL;
2282
+ ELSE
2283
+ BEGIN
2241
2284
  ts := modifier::timestamptz;
2242
- END IF;
2243
- RETURN to_char(
2244
- ts AT TIME ZONE 'UTC',
2245
- replace(replace(replace(replace(replace(replace(replace(replace(
2246
- format,
2247
- '%Y', 'YYYY'),
2248
- '%m', 'MM'),
2249
- '%d', 'DD'),
2250
- '%H', 'HH24'),
2251
- '%M', 'MI'),
2252
- '%S', 'SS'),
2253
- '%f', 'MS'),
2254
- 'T', '"T"')
2255
- );
2285
+ EXCEPTION WHEN others THEN
2286
+ RETURN NULL;
2287
+ END;
2288
+ END IF;
2289
+ RETURN __lattice_strftime_fmt(ts, format);
2290
+ END;
2291
+ $fn$;
2292
+ EXCEPTION WHEN insufficient_privilege THEN NULL;
2293
+ END $do$;`
2294
+ },
2295
+ {
2296
+ warn: "could not register 3-arg strftime polyfill:",
2297
+ // SQLite's 3-arg strftime(format, timestring, modifier) — used by the changelog
2298
+ // retention prune (e.g. strftime('%Y-...','now','-30 days')). Postgres had no
2299
+ // 3-arg overload, so that prune threw `function strftime(...) does not exist` on
2300
+ // every PG cloud with retention. Resolve the base time, apply the modifier as an
2301
+ // interval, then reuse the shared formatter. Empty/invalid → NULL, never throws.
2302
+ sql: `DO $do$ BEGIN
2303
+ CREATE OR REPLACE FUNCTION strftime(format text, timestring text, modifier text)
2304
+ RETURNS text
2305
+ LANGUAGE plpgsql
2306
+ IMMUTABLE
2307
+ AS $fn$
2308
+ DECLARE ts timestamptz;
2309
+ BEGIN
2310
+ IF timestring = 'now' THEN
2311
+ ts := now();
2312
+ ELSIF NULLIF(btrim(timestring), '') IS NULL THEN
2313
+ RETURN NULL;
2314
+ ELSE
2315
+ BEGIN
2316
+ ts := timestring::timestamptz;
2317
+ EXCEPTION WHEN others THEN
2318
+ RETURN NULL;
2319
+ END;
2320
+ END IF;
2321
+ BEGIN
2322
+ ts := ts + modifier::interval;
2323
+ EXCEPTION WHEN others THEN
2324
+ RETURN NULL;
2256
2325
  END;
2257
- $fn$;
2258
- END IF;
2326
+ RETURN __lattice_strftime_fmt(ts, format);
2327
+ END;
2328
+ $fn$;
2329
+ EXCEPTION WHEN insufficient_privilege THEN NULL;
2259
2330
  END $do$;`
2260
2331
  }
2261
2332
  ];
@@ -9180,7 +9251,12 @@ CREATE TRIGGER "${trg}" AFTER INSERT OR UPDATE OR DELETE ON ${q3}
9180
9251
  async function ownPolyfillsByGroup(db) {
9181
9252
  if (!isPg(db)) return;
9182
9253
  const group3 = await memberGroupFor(db);
9183
- for (const sig of ["json_extract(text, text)", "strftime(text, text)"]) {
9254
+ for (const sig of [
9255
+ "json_extract(text, text)",
9256
+ "strftime(text, text)",
9257
+ "strftime(text, text, text)",
9258
+ "__lattice_strftime_fmt(timestamptz, text)"
9259
+ ]) {
9184
9260
  try {
9185
9261
  const reg = await getAsyncOrSync(db.adapter, `SELECT to_regprocedure($1) AS reg`, [sig]);
9186
9262
  if (reg?.reg == null) continue;
@@ -29084,12 +29160,12 @@ var getNodeModulesParentDirs;
29084
29160
  var init_getNodeModulesParentDirs = __esm({
29085
29161
  "node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-node/getNodeModulesParentDirs.js"() {
29086
29162
  "use strict";
29087
- getNodeModulesParentDirs = (dirname19) => {
29163
+ getNodeModulesParentDirs = (dirname20) => {
29088
29164
  const cwd = process.cwd();
29089
- if (!dirname19) {
29165
+ if (!dirname20) {
29090
29166
  return [cwd];
29091
29167
  }
29092
- const normalizedPath = normalize(dirname19);
29168
+ const normalizedPath = normalize(dirname20);
29093
29169
  const parts = normalizedPath.split(sep6);
29094
29170
  const nodeModulesIndex = parts.indexOf("node_modules");
29095
29171
  const parentDir = nodeModulesIndex !== -1 ? parts.slice(0, nodeModulesIndex).join(sep6) : normalizedPath;
@@ -29167,8 +29243,8 @@ var init_getTypeScriptUserAgentPair = __esm({
29167
29243
  tscVersion = null;
29168
29244
  return void 0;
29169
29245
  }
29170
- const dirname19 = typeof __dirname !== "undefined" ? __dirname : void 0;
29171
- const nodeModulesParentDirs = getNodeModulesParentDirs(dirname19);
29246
+ const dirname20 = typeof __dirname !== "undefined" ? __dirname : void 0;
29247
+ const nodeModulesParentDirs = getNodeModulesParentDirs(dirname20);
29172
29248
  let versionFromApp;
29173
29249
  for (const nodeModulesParentDir of nodeModulesParentDirs) {
29174
29250
  try {
@@ -57954,8 +58030,8 @@ var init_dist_es22 = __esm({
57954
58030
  });
57955
58031
 
57956
58032
  // src/cli.ts
57957
- import { resolve as resolve17, dirname as dirname18 } from "path";
57958
- import { readFileSync as readFileSync26, existsSync as existsSync33 } from "fs";
58033
+ import { resolve as resolve17, dirname as dirname19 } from "path";
58034
+ import { readFileSync as readFileSync26, existsSync as existsSync34 } from "fs";
57959
58035
  import { execSync } from "child_process";
57960
58036
  import { parse as parse3 } from "yaml";
57961
58037
 
@@ -58222,9 +58298,9 @@ function installLatest(ctx, version, opts = {}) {
58222
58298
  init_http();
58223
58299
  import { createServer } from "http";
58224
58300
  import { spawn as spawn2 } from "child_process";
58225
- import { existsSync as existsSync32 } from "fs";
58301
+ import { existsSync as existsSync33 } from "fs";
58226
58302
  import { WebSocketServer, WebSocket } from "ws";
58227
- import { dirname as dirname17, resolve as resolve16 } from "path";
58303
+ import { dirname as dirname18, resolve as resolve16 } from "path";
58228
58304
 
58229
58305
  // src/gui/active-db.ts
58230
58306
  init_adapter();
@@ -76758,8 +76834,16 @@ async function dispatchIngestRoute(req, res, ctx) {
76758
76834
 
76759
76835
  // src/gui/sources-routes.ts
76760
76836
  init_user_config();
76761
- import { statSync as statSync9, readdirSync as readdirSync8, readFileSync as readFileSync23, writeFileSync as writeFileSync10 } from "fs";
76762
- import { resolve as resolve12, join as join29, basename as basename12, sep as sep8 } from "path";
76837
+ import {
76838
+ statSync as statSync9,
76839
+ readdirSync as readdirSync8,
76840
+ readFileSync as readFileSync23,
76841
+ writeFileSync as writeFileSync10,
76842
+ existsSync as existsSync27,
76843
+ mkdirSync as mkdirSync12,
76844
+ rmSync
76845
+ } from "fs";
76846
+ import { resolve as resolve12, join as join29, basename as basename12, sep as sep8, dirname as dirname16 } from "path";
76763
76847
  import { execFile } from "child_process";
76764
76848
  import { randomUUID as randomUUID4 } from "crypto";
76765
76849
  init_http();
@@ -76767,20 +76851,49 @@ var MAX_LIST_ENTRIES = 2e3;
76767
76851
  var MAX_INGEST_FILES = 500;
76768
76852
  var MAX_INGEST_DEPTH = 8;
76769
76853
  var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store", "__pycache__", ".venv", "venv"]);
76770
- function rootsFile() {
76771
- return join29(configDir(), "sources.json");
76854
+ function rootsFile(configPath) {
76855
+ return join29(dirname16(configPath), "sources.json");
76856
+ }
76857
+ function migrateGlobalRootsIfNeeded(configPath) {
76858
+ const wsFile = rootsFile(configPath);
76859
+ if (existsSync27(wsFile)) return;
76860
+ const globalFile = join29(configDir(), "sources.json");
76861
+ if (globalFile === wsFile || !existsSync27(globalFile)) return;
76862
+ let roots;
76863
+ try {
76864
+ const parsed = JSON.parse(readFileSync23(globalFile, "utf8"));
76865
+ if (!Array.isArray(parsed.roots) || parsed.roots.length === 0) return;
76866
+ roots = parsed.roots;
76867
+ } catch {
76868
+ return;
76869
+ }
76870
+ try {
76871
+ mkdirSync12(dirname16(wsFile), { recursive: true });
76872
+ writeFileSync10(wsFile, JSON.stringify({ roots }, null, 2), "utf8");
76873
+ } catch {
76874
+ return;
76875
+ }
76876
+ try {
76877
+ rmSync(globalFile, { force: true });
76878
+ } catch {
76879
+ try {
76880
+ writeFileSync10(globalFile, JSON.stringify({ roots: [] }, null, 2), "utf8");
76881
+ } catch {
76882
+ }
76883
+ }
76772
76884
  }
76773
- function readRoots() {
76885
+ function readRoots(configPath) {
76886
+ migrateGlobalRootsIfNeeded(configPath);
76774
76887
  try {
76775
- const raw = readFileSync23(rootsFile(), "utf8");
76888
+ const raw = readFileSync23(rootsFile(configPath), "utf8");
76776
76889
  const parsed = JSON.parse(raw);
76777
76890
  return Array.isArray(parsed.roots) ? parsed.roots : [];
76778
76891
  } catch {
76779
76892
  return [];
76780
76893
  }
76781
76894
  }
76782
- function writeRoots(roots) {
76783
- writeFileSync10(rootsFile(), JSON.stringify({ roots }, null, 2), "utf8");
76895
+ function writeRoots(configPath, roots) {
76896
+ writeFileSync10(rootsFile(configPath), JSON.stringify({ roots }, null, 2), "utf8");
76784
76897
  }
76785
76898
  function safeResolveInside2(target, roots) {
76786
76899
  const abs = resolve12(target);
@@ -76870,14 +76983,14 @@ async function ingestFolder(abs, ingestFile) {
76870
76983
  return { ingested, skipped };
76871
76984
  }
76872
76985
  async function dispatchSourcesRoute(req, res, deps) {
76873
- const { pathname, method, ingestFile } = deps;
76986
+ const { pathname, method, ingestFile, configPath } = deps;
76874
76987
  if (!pathname.startsWith("/api/sources/")) return false;
76875
76988
  if (!localFileOpenEnabled()) {
76876
76989
  sendJson(res, { enabled: false });
76877
76990
  return true;
76878
76991
  }
76879
76992
  if (pathname === "/api/sources/roots" && method === "GET") {
76880
- sendJson(res, { enabled: true, roots: readRoots() });
76993
+ sendJson(res, { enabled: true, roots: readRoots(configPath) });
76881
76994
  return true;
76882
76995
  }
76883
76996
  if (pathname === "/api/sources/roots" && method === "POST") {
@@ -76903,12 +77016,12 @@ async function dispatchSourcesRoute(req, res, deps) {
76903
77016
  sendJson(res, { error: `path not found: ${abs}` }, 400);
76904
77017
  return true;
76905
77018
  }
76906
- const roots = readRoots();
77019
+ const roots = readRoots(configPath);
76907
77020
  let root6 = roots.find((r6) => resolve12(r6.path) === abs);
76908
77021
  if (!root6) {
76909
77022
  root6 = { id: randomUUID4(), path: abs, kind, name: basename12(abs) || abs };
76910
77023
  roots.push(root6);
76911
- writeRoots(roots);
77024
+ writeRoots(configPath, roots);
76912
77025
  }
76913
77026
  let result;
76914
77027
  if (kind === "folder") result = await ingestFolder(abs, ingestFile);
@@ -76919,8 +77032,8 @@ async function dispatchSourcesRoute(req, res, deps) {
76919
77032
  const delMatch = /^\/api\/sources\/roots\/([^/]+)$/.exec(pathname);
76920
77033
  if (delMatch && method === "DELETE") {
76921
77034
  const id = decodeURIComponent(delMatch[1] ?? "");
76922
- const roots = readRoots().filter((r6) => r6.id !== id);
76923
- writeRoots(roots);
77035
+ const roots = readRoots(configPath).filter((r6) => r6.id !== id);
77036
+ writeRoots(configPath, roots);
76924
77037
  sendJson(res, { ok: true });
76925
77038
  return true;
76926
77039
  }
@@ -76938,7 +77051,7 @@ async function dispatchSourcesRoute(req, res, deps) {
76938
77051
  sendJson(res, { error: "path is required" }, 400);
76939
77052
  return true;
76940
77053
  }
76941
- const abs = safeResolveInside2(target, readRoots());
77054
+ const abs = safeResolveInside2(target, readRoots(configPath));
76942
77055
  if (!abs) {
76943
77056
  sendJson(res, { error: "path is outside any registered source root" }, 403);
76944
77057
  return true;
@@ -76953,7 +77066,7 @@ async function dispatchSourcesRoute(req, res, deps) {
76953
77066
  if (pathname === "/api/sources/ingest-folder" && method === "POST") {
76954
77067
  const body = await readJson(req).catch(() => ({}));
76955
77068
  const target = typeof body.path === "string" ? body.path : "";
76956
- const abs = safeResolveInside2(target, readRoots());
77069
+ const abs = safeResolveInside2(target, readRoots(configPath));
76957
77070
  if (!abs) {
76958
77071
  sendJson(res, { error: "path is outside any registered source root" }, 403);
76959
77072
  return true;
@@ -76967,7 +77080,7 @@ async function dispatchSourcesRoute(req, res, deps) {
76967
77080
  // src/gui/import-routes.ts
76968
77081
  init_adapter();
76969
77082
  init_http();
76970
- import { existsSync as existsSync27, readFileSync as readFileSync24, statSync as statSync10 } from "fs";
77083
+ import { existsSync as existsSync28, readFileSync as readFileSync24, statSync as statSync10 } from "fs";
76971
77084
  import { isAbsolute as isAbsolute4, join as join30 } from "path";
76972
77085
  init_native_entities();
76973
77086
  function badRequest(message) {
@@ -77001,7 +77114,7 @@ async function readImportSourceFromFile(db, fileId, latticeRoot) {
77001
77114
  );
77002
77115
  if (!row) throw badRequest("Unknown import file: " + fileId);
77003
77116
  const path3 = localPathOf2(row, latticeRoot);
77004
- if (!path3 || !existsSync27(path3)) {
77117
+ if (!path3 || !existsSync28(path3)) {
77005
77118
  throw badRequest("The import file\u2019s bytes are not available locally.");
77006
77119
  }
77007
77120
  const sizeBytes = statSync10(path3).size;
@@ -79142,7 +79255,7 @@ init_dispatch();
79142
79255
  init_column_descriptions();
79143
79256
  init_mutations();
79144
79257
  init_native_entities();
79145
- import { createReadStream as createReadStream3, existsSync as existsSync28, realpathSync as realpathSync2, statSync as statSync11 } from "fs";
79258
+ import { createReadStream as createReadStream3, existsSync as existsSync29, realpathSync as realpathSync2, statSync as statSync11 } from "fs";
79146
79259
  import { extname as extname3, join as join31, normalize as normalize2, sep as sep9 } from "path";
79147
79260
 
79148
79261
  // src/gui/count-many.ts
@@ -79350,7 +79463,7 @@ async function handleReadRoutes(req, res, ctx, deps) {
79350
79463
  const base = deps.guiAssetsDir.replace(/[/\\]+$/, "");
79351
79464
  const target = normalize2(join31(base, rel));
79352
79465
  const within = target === base || target.startsWith(base + sep9);
79353
- if (!within || !existsSync28(target) || !statSync11(target).isFile()) {
79466
+ if (!within || !existsSync29(target) || !statSync11(target).isFile()) {
79354
79467
  sendJson(res, { error: "asset not found" }, 404);
79355
79468
  return true;
79356
79469
  }
@@ -80558,16 +80671,16 @@ async function handleHistoryRoutes(req, res, ctx, deps) {
80558
80671
  // src/gui/workspaces-routes.ts
80559
80672
  init_http();
80560
80673
  import { resolve as resolve13 } from "path";
80561
- import { existsSync as existsSync29, rmSync } from "fs";
80674
+ import { existsSync as existsSync30, rmSync as rmSync2 } from "fs";
80562
80675
  init_workspace();
80563
80676
  init_lattice_root();
80564
80677
  init_user_config();
80565
80678
  function cleanupWorkspaceFiles(root6, ws) {
80566
80679
  if (!ws.configPath && ws.kind === "local") {
80567
- rmSync(workspaceDir(root6, ws.dir), { recursive: true, force: true });
80680
+ rmSync2(workspaceDir(root6, ws.dir), { recursive: true, force: true });
80568
80681
  } else if (ws.kind === "cloud") {
80569
- if (ws.configPath && existsSync29(ws.configPath)) {
80570
- rmSync(ws.configPath, { force: true });
80682
+ if (ws.configPath && existsSync30(ws.configPath)) {
80683
+ rmSync2(ws.configPath, { force: true });
80571
80684
  }
80572
80685
  const labelMatch = /^\$\{LATTICE_DB:([A-Za-z0-9._-]+)\}$/.exec(ws.db.trim());
80573
80686
  const label = labelMatch?.[1];
@@ -80770,15 +80883,15 @@ async function handleWorkspacesRoutes(req, res, ctx, deps) {
80770
80883
  // src/gui/databases-routes.ts
80771
80884
  init_http();
80772
80885
  import { basename as basename14, resolve as resolve15 } from "path";
80773
- import { existsSync as existsSync31 } from "fs";
80886
+ import { existsSync as existsSync32 } from "fs";
80774
80887
  init_parser();
80775
80888
 
80776
80889
  // src/gui/config-paths.ts
80777
80890
  init_parser();
80778
- import { basename as basename13, dirname as dirname16, join as join32, resolve as resolve14 } from "path";
80891
+ import { basename as basename13, dirname as dirname17, join as join32, resolve as resolve14 } from "path";
80779
80892
  import {
80780
- existsSync as existsSync30,
80781
- mkdirSync as mkdirSync12,
80893
+ existsSync as existsSync31,
80894
+ mkdirSync as mkdirSync13,
80782
80895
  readFileSync as readFileSync25,
80783
80896
  readdirSync as readdirSync9,
80784
80897
  unlinkSync as unlinkSync5,
@@ -80786,10 +80899,10 @@ import {
80786
80899
  } from "fs";
80787
80900
  import { parseDocument as parseDocument7 } from "yaml";
80788
80901
  function resolveOutputDirForConfig(configPath) {
80789
- const base = dirname16(resolve14(configPath));
80902
+ const base = dirname17(resolve14(configPath));
80790
80903
  for (const dir of ["context", ".", "generated"]) {
80791
80904
  const abs = resolve14(base, dir);
80792
- if (existsSync30(join32(abs, ".lattice", "manifest.json"))) return abs;
80905
+ if (existsSync31(join32(abs, ".lattice", "manifest.json"))) return abs;
80793
80906
  }
80794
80907
  return resolve14(base, "context");
80795
80908
  }
@@ -80798,7 +80911,7 @@ function friendlyConfigName(parsedName, configPath) {
80798
80911
  return basename13(configPath).replace(/\.(ya?ml)$/, "");
80799
80912
  }
80800
80913
  function listConfigs(activeConfigPath) {
80801
- const dir = dirname16(activeConfigPath);
80914
+ const dir = dirname17(activeConfigPath);
80802
80915
  const entries = [];
80803
80916
  for (const fname of readdirSync9(dir)) {
80804
80917
  if (!fname.endsWith(".yml") && !fname.endsWith(".yaml")) continue;
@@ -80827,17 +80940,17 @@ function listConfigs(activeConfigPath) {
80827
80940
  return entries.sort((a6, b6) => a6.label.localeCompare(b6.label));
80828
80941
  }
80829
80942
  function createBlankConfig(activeConfigPath, dbName) {
80830
- const dir = dirname16(activeConfigPath);
80943
+ const dir = dirname17(activeConfigPath);
80831
80944
  const slug = dbName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
80832
80945
  if (!slug) throw new Error("Workspace name must contain at least one alphanumeric character");
80833
80946
  const configPath = join32(dir, `${slug}.config.yml`);
80834
- if (existsSync30(configPath)) throw new Error(`Config already exists: ${slug}.config.yml`);
80947
+ if (existsSync31(configPath)) throw new Error(`Config already exists: ${slug}.config.yml`);
80835
80948
  const yaml = `db: ./data/${slug}.db
80836
80949
 
80837
80950
  entities: {}
80838
80951
  `;
80839
80952
  writeFileSync11(configPath, yaml, "utf8");
80840
- mkdirSync12(join32(dir, "data"), { recursive: true });
80953
+ mkdirSync13(join32(dir, "data"), { recursive: true });
80841
80954
  return configPath;
80842
80955
  }
80843
80956
  function sqliteFileForConfig(configPath) {
@@ -80846,18 +80959,18 @@ function sqliteFileForConfig(configPath) {
80846
80959
  if (!raw) return null;
80847
80960
  if (isPostgresUrl(raw) || raw.startsWith("${LATTICE_DB:")) return null;
80848
80961
  if (raw === ":memory:" || raw.startsWith("file:")) return null;
80849
- return resolve14(dirname16(configPath), raw);
80962
+ return resolve14(dirname17(configPath), raw);
80850
80963
  }
80851
80964
  function deleteDatabaseFiles(targetConfigPath) {
80852
80965
  const sqliteFile = sqliteFileForConfig(targetConfigPath);
80853
80966
  unlinkSync5(targetConfigPath);
80854
80967
  let deletedDbFile = null;
80855
- if (sqliteFile && existsSync30(sqliteFile)) {
80968
+ if (sqliteFile && existsSync31(sqliteFile)) {
80856
80969
  unlinkSync5(sqliteFile);
80857
80970
  deletedDbFile = sqliteFile;
80858
80971
  for (const suffix of ["-wal", "-shm", "-journal"]) {
80859
80972
  const sidecar = sqliteFile + suffix;
80860
- if (existsSync30(sidecar)) unlinkSync5(sidecar);
80973
+ if (existsSync31(sidecar)) unlinkSync5(sidecar);
80861
80974
  }
80862
80975
  }
80863
80976
  return { deletedConfig: basename13(targetConfigPath), deletedDbFile };
@@ -80891,7 +81004,7 @@ async function handleDatabasesRoutes(req, res, ctx, deps) {
80891
81004
  return true;
80892
81005
  }
80893
81006
  const newPath = resolve15(body.path);
80894
- if (!existsSync31(newPath)) {
81007
+ if (!existsSync32(newPath)) {
80895
81008
  sendJson(res, { error: `Config not found: ${newPath}` }, 400);
80896
81009
  return true;
80897
81010
  }
@@ -80997,8 +81110,8 @@ async function handleDatabasesRoutes(req, res, ctx, deps) {
80997
81110
  // src/gui/server.ts
80998
81111
  init_user_config();
80999
81112
  function resolveDefaultGuiAssetsDir() {
81000
- const bundleSibling = process.argv[1] ? resolve16(dirname17(process.argv[1]), "gui-assets") : null;
81001
- if (bundleSibling && existsSync32(bundleSibling)) return bundleSibling;
81113
+ const bundleSibling = process.argv[1] ? resolve16(dirname18(process.argv[1]), "gui-assets") : null;
81114
+ if (bundleSibling && existsSync33(bundleSibling)) return bundleSibling;
81002
81115
  return resolve16(process.cwd(), "dist", "gui-assets");
81003
81116
  }
81004
81117
  function sendText(res, body, status = 200, contentType = "text/plain; charset=utf-8") {
@@ -81055,7 +81168,7 @@ async function startGuiServer(options) {
81055
81168
  const sessionId = crypto.randomUUID();
81056
81169
  let updateService = null;
81057
81170
  let activeRef = bootConfigPath && bootOutputDir ? await openConfig(bootConfigPath, bootOutputDir, autoRender, options.realtimeWatchdogMs) : null;
81058
- const latticeRoot = (bootConfigPath ? findLatticeRoot(dirname17(bootConfigPath)) : null) ?? (options.latticeRoot ? resolve16(options.latticeRoot) : null);
81171
+ const latticeRoot = (bootConfigPath ? findLatticeRoot(dirname18(bootConfigPath)) : null) ?? (options.latticeRoot ? resolve16(options.latticeRoot) : null);
81059
81172
  let currentWorkspaceId = null;
81060
81173
  if (latticeRoot && bootConfigPath) {
81061
81174
  const launched = listWorkspaces(latticeRoot).find(
@@ -81409,7 +81522,7 @@ async function startGuiServer(options) {
81409
81522
  createJunction: (otherTable) => createFileJunction(active, otherTable, sessionId),
81410
81523
  createEntity: (entity, columns) => createUserEntity(active, entity, columns, sessionId),
81411
81524
  aggressiveness: getAggressiveness(),
81412
- latticeRoot: dirname17(active.configPath),
81525
+ latticeRoot: dirname18(active.configPath),
81413
81526
  configPath: active.configPath,
81414
81527
  outputDir: active.outputDir,
81415
81528
  sessionId,
@@ -81434,7 +81547,7 @@ async function startGuiServer(options) {
81434
81547
  createJunction: (otherTable) => createFileJunction(active, otherTable, sessionId),
81435
81548
  createEntity: (entity, columns) => createUserEntity(active, entity, columns, sessionId),
81436
81549
  aggressiveness: getAggressiveness(),
81437
- latticeRoot: dirname17(active.configPath),
81550
+ latticeRoot: dirname18(active.configPath),
81438
81551
  configPath: active.configPath,
81439
81552
  outputDir: active.outputDir,
81440
81553
  sessionId,
@@ -81445,6 +81558,7 @@ async function startGuiServer(options) {
81445
81558
  return await dispatchSourcesRoute(req2, res2, {
81446
81559
  db: active.db,
81447
81560
  ingestFile: (p3) => ingestLocalFile(ingestCtx, mctx, p3, false),
81561
+ configPath: active.configPath,
81448
81562
  pathname,
81449
81563
  method
81450
81564
  });
@@ -81460,7 +81574,7 @@ async function startGuiServer(options) {
81460
81574
  return await dispatchImportRoute(req2, res2, {
81461
81575
  db: active.db,
81462
81576
  configPath: active.configPath,
81463
- latticeRoot: dirname17(active.configPath),
81577
+ latticeRoot: dirname18(active.configPath),
81464
81578
  validTables: active.validTables,
81465
81579
  softDeletable: active.softDeletable
81466
81580
  });
@@ -81489,7 +81603,7 @@ async function startGuiServer(options) {
81489
81603
  if (!pathname.startsWith("/api/files/")) return false;
81490
81604
  return await dispatchFilesRoute(req2, res2, {
81491
81605
  db: active.db,
81492
- latticeRoot: dirname17(active.configPath),
81606
+ latticeRoot: dirname18(active.configPath),
81493
81607
  configPath: active.configPath,
81494
81608
  pathname,
81495
81609
  method
@@ -81982,7 +82096,7 @@ function printHelp() {
81982
82096
  );
81983
82097
  }
81984
82098
  function getVersion() {
81985
- if (true) return "4.3.1";
82099
+ if (true) return "4.3.3";
81986
82100
  try {
81987
82101
  const pkgPath = new URL("../package.json", import.meta.url).pathname;
81988
82102
  const pkg = JSON.parse(readFileSync26(pkgPath, "utf-8"));
@@ -81996,7 +82110,7 @@ function printVersion() {
81996
82110
  }
81997
82111
  function getGuiAssetsDir() {
81998
82112
  const fromBundle = new URL("./gui-assets", import.meta.url).pathname;
81999
- if (existsSync33(fromBundle)) return fromBundle;
82113
+ if (existsSync34(fromBundle)) return fromBundle;
82000
82114
  return new URL("../dist/gui-assets", import.meta.url).pathname;
82001
82115
  }
82002
82116
  async function runUpdate() {
@@ -82040,7 +82154,7 @@ function runGenerate(args) {
82040
82154
  console.error('Error: config must have an "entities" key');
82041
82155
  process.exit(1);
82042
82156
  }
82043
- const configDir2 = dirname18(configPath);
82157
+ const configDir2 = dirname19(configPath);
82044
82158
  const outDir = resolve17(args.out);
82045
82159
  try {
82046
82160
  const result = generateAll({ config, configDir: configDir2, outDir, scaffold: args.scaffold });