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 +200 -86
- package/dist/desktop-entry.js +196 -82
- package/dist/index.cjs +154 -48
- package/dist/index.js +200 -86
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -917,7 +917,7 @@ async function registerPostgresPolyfills(run) {
|
|
|
917
917
|
await run(sql);
|
|
918
918
|
} catch (err) {
|
|
919
919
|
const msg = err instanceof Error ? err.message : String(err);
|
|
920
|
-
if (/permission denied/i.test(msg)) {
|
|
920
|
+
if (/permission denied/i.test(msg) || /must be owner of/i.test(msg)) {
|
|
921
921
|
permissionDenied = true;
|
|
922
922
|
} else {
|
|
923
923
|
console.warn(`[PostgresAdapter] ${warn}`, msg);
|
|
@@ -1439,40 +1439,111 @@ var init_postgres = __esm({
|
|
|
1439
1439
|
SELECT doc::jsonb #>> string_to_array(regexp_replace(path, '^\\$\\.?', ''), '.')
|
|
1440
1440
|
$fn$;
|
|
1441
1441
|
END IF;
|
|
1442
|
+
END $do$;`
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
warn: "could not register strftime format helper:",
|
|
1446
|
+
// Shared format translator (SQLite strftime tokens → to_char patterns), in UTC.
|
|
1447
|
+
// Factored out so the 2-arg and 3-arg strftime overloads share one definition.
|
|
1448
|
+
// Each polyfill below is CREATE OR REPLACE (so an existing cloud's prior copy is
|
|
1449
|
+
// UPGRADED when the owner opens), wrapped in a DO block whose EXCEPTION swallows a
|
|
1450
|
+
// scoped member's insufficient-privilege failure IN A SUBTRANSACTION — so a member
|
|
1451
|
+
// that runs registration inside a transaction is NOT aborted. ("must be owner of
|
|
1452
|
+
// function", raised when a non-owner CREATE OR REPLACEs another role's function, is
|
|
1453
|
+
// SQLSTATE 42501 / `insufficient_privilege`, same class as "permission denied".) The
|
|
1454
|
+
// owner already owns + replaced the function; the member simply uses the owner's
|
|
1455
|
+
// copy — and `ownPolyfillsByGroup` re-owns the whole set to the member group so any
|
|
1456
|
+
// member can replace them on a later upgrade.
|
|
1457
|
+
sql: `DO $do$ BEGIN
|
|
1458
|
+
CREATE OR REPLACE FUNCTION __lattice_strftime_fmt(ts timestamptz, format text)
|
|
1459
|
+
RETURNS text
|
|
1460
|
+
LANGUAGE sql
|
|
1461
|
+
IMMUTABLE
|
|
1462
|
+
AS $fn$
|
|
1463
|
+
SELECT to_char(
|
|
1464
|
+
ts AT TIME ZONE 'UTC',
|
|
1465
|
+
replace(replace(replace(replace(replace(replace(replace(replace(
|
|
1466
|
+
format,
|
|
1467
|
+
'%Y', 'YYYY'),
|
|
1468
|
+
'%m', 'MM'),
|
|
1469
|
+
'%d', 'DD'),
|
|
1470
|
+
'%H', 'HH24'),
|
|
1471
|
+
'%M', 'MI'),
|
|
1472
|
+
'%S', 'SS'),
|
|
1473
|
+
'%f', 'MS'),
|
|
1474
|
+
'T', '"T"')
|
|
1475
|
+
);
|
|
1476
|
+
$fn$;
|
|
1477
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
1442
1478
|
END $do$;`
|
|
1443
1479
|
},
|
|
1444
1480
|
{
|
|
1445
1481
|
warn: "could not register strftime polyfill:",
|
|
1482
|
+
// The pre-4.3.3 strftime cast the modifier straight to timestamptz and threw
|
|
1483
|
+
// `invalid input syntax for type timestamp with time zone: ""` on a legacy ''
|
|
1484
|
+
// value (3.x stored nullable TEXT timestamps as ''), bricking the whole workspace
|
|
1485
|
+
// open. Now an empty, whitespace, or unparseable time string returns NULL (SQLite's
|
|
1486
|
+
// strftime semantics) instead of aborting the query.
|
|
1446
1487
|
sql: `DO $do$ BEGIN
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1488
|
+
CREATE OR REPLACE FUNCTION strftime(format text, modifier text)
|
|
1489
|
+
RETURNS text
|
|
1490
|
+
LANGUAGE plpgsql
|
|
1491
|
+
IMMUTABLE
|
|
1492
|
+
AS $fn$
|
|
1493
|
+
DECLARE ts timestamptz;
|
|
1494
|
+
BEGIN
|
|
1495
|
+
IF modifier = 'now' THEN
|
|
1496
|
+
ts := now();
|
|
1497
|
+
ELSIF NULLIF(btrim(modifier), '') IS NULL THEN
|
|
1498
|
+
RETURN NULL;
|
|
1499
|
+
ELSE
|
|
1500
|
+
BEGIN
|
|
1458
1501
|
ts := modifier::timestamptz;
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1502
|
+
EXCEPTION WHEN others THEN
|
|
1503
|
+
RETURN NULL;
|
|
1504
|
+
END;
|
|
1505
|
+
END IF;
|
|
1506
|
+
RETURN __lattice_strftime_fmt(ts, format);
|
|
1507
|
+
END;
|
|
1508
|
+
$fn$;
|
|
1509
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
1510
|
+
END $do$;`
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
warn: "could not register 3-arg strftime polyfill:",
|
|
1514
|
+
// SQLite's 3-arg strftime(format, timestring, modifier) — used by the changelog
|
|
1515
|
+
// retention prune (e.g. strftime('%Y-...','now','-30 days')). Postgres had no
|
|
1516
|
+
// 3-arg overload, so that prune threw `function strftime(...) does not exist` on
|
|
1517
|
+
// every PG cloud with retention. Resolve the base time, apply the modifier as an
|
|
1518
|
+
// interval, then reuse the shared formatter. Empty/invalid → NULL, never throws.
|
|
1519
|
+
sql: `DO $do$ BEGIN
|
|
1520
|
+
CREATE OR REPLACE FUNCTION strftime(format text, timestring text, modifier text)
|
|
1521
|
+
RETURNS text
|
|
1522
|
+
LANGUAGE plpgsql
|
|
1523
|
+
IMMUTABLE
|
|
1524
|
+
AS $fn$
|
|
1525
|
+
DECLARE ts timestamptz;
|
|
1526
|
+
BEGIN
|
|
1527
|
+
IF timestring = 'now' THEN
|
|
1528
|
+
ts := now();
|
|
1529
|
+
ELSIF NULLIF(btrim(timestring), '') IS NULL THEN
|
|
1530
|
+
RETURN NULL;
|
|
1531
|
+
ELSE
|
|
1532
|
+
BEGIN
|
|
1533
|
+
ts := timestring::timestamptz;
|
|
1534
|
+
EXCEPTION WHEN others THEN
|
|
1535
|
+
RETURN NULL;
|
|
1536
|
+
END;
|
|
1537
|
+
END IF;
|
|
1538
|
+
BEGIN
|
|
1539
|
+
ts := ts + modifier::interval;
|
|
1540
|
+
EXCEPTION WHEN others THEN
|
|
1541
|
+
RETURN NULL;
|
|
1473
1542
|
END;
|
|
1474
|
-
|
|
1475
|
-
|
|
1543
|
+
RETURN __lattice_strftime_fmt(ts, format);
|
|
1544
|
+
END;
|
|
1545
|
+
$fn$;
|
|
1546
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
1476
1547
|
END $do$;`
|
|
1477
1548
|
}
|
|
1478
1549
|
];
|
|
@@ -9391,7 +9462,12 @@ CREATE TRIGGER "${trg}" AFTER INSERT OR UPDATE OR DELETE ON ${q3}
|
|
|
9391
9462
|
async function ownPolyfillsByGroup(db) {
|
|
9392
9463
|
if (!isPg(db)) return;
|
|
9393
9464
|
const group3 = await memberGroupFor(db);
|
|
9394
|
-
for (const sig of [
|
|
9465
|
+
for (const sig of [
|
|
9466
|
+
"json_extract(text, text)",
|
|
9467
|
+
"strftime(text, text)",
|
|
9468
|
+
"strftime(text, text, text)",
|
|
9469
|
+
"__lattice_strftime_fmt(timestamptz, text)"
|
|
9470
|
+
]) {
|
|
9395
9471
|
try {
|
|
9396
9472
|
const reg = await getAsyncOrSync(db.adapter, `SELECT to_regprocedure($1) AS reg`, [sig]);
|
|
9397
9473
|
if (reg?.reg == null) continue;
|
|
@@ -23491,12 +23567,12 @@ var init_getNodeModulesParentDirs = __esm({
|
|
|
23491
23567
|
"node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-node/getNodeModulesParentDirs.js"() {
|
|
23492
23568
|
"use strict";
|
|
23493
23569
|
import_node_path21 = require("path");
|
|
23494
|
-
getNodeModulesParentDirs = (
|
|
23570
|
+
getNodeModulesParentDirs = (dirname19) => {
|
|
23495
23571
|
const cwd = process.cwd();
|
|
23496
|
-
if (!
|
|
23572
|
+
if (!dirname19) {
|
|
23497
23573
|
return [cwd];
|
|
23498
23574
|
}
|
|
23499
|
-
const normalizedPath = (0, import_node_path21.normalize)(
|
|
23575
|
+
const normalizedPath = (0, import_node_path21.normalize)(dirname19);
|
|
23500
23576
|
const parts = normalizedPath.split(import_node_path21.sep);
|
|
23501
23577
|
const nodeModulesIndex = parts.indexOf("node_modules");
|
|
23502
23578
|
const parentDir = nodeModulesIndex !== -1 ? parts.slice(0, nodeModulesIndex).join(import_node_path21.sep) : normalizedPath;
|
|
@@ -23574,8 +23650,8 @@ var init_getTypeScriptUserAgentPair = __esm({
|
|
|
23574
23650
|
tscVersion = null;
|
|
23575
23651
|
return void 0;
|
|
23576
23652
|
}
|
|
23577
|
-
const
|
|
23578
|
-
const nodeModulesParentDirs = getNodeModulesParentDirs(
|
|
23653
|
+
const dirname19 = typeof __dirname !== "undefined" ? __dirname : void 0;
|
|
23654
|
+
const nodeModulesParentDirs = getNodeModulesParentDirs(dirname19);
|
|
23579
23655
|
let versionFromApp;
|
|
23580
23656
|
for (const nodeModulesParentDir of nodeModulesParentDirs) {
|
|
23581
23657
|
try {
|
|
@@ -80292,20 +80368,49 @@ var MAX_LIST_ENTRIES = 2e3;
|
|
|
80292
80368
|
var MAX_INGEST_FILES = 500;
|
|
80293
80369
|
var MAX_INGEST_DEPTH = 8;
|
|
80294
80370
|
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store", "__pycache__", ".venv", "venv"]);
|
|
80295
|
-
function rootsFile() {
|
|
80296
|
-
return (0, import_node_path44.join)(
|
|
80371
|
+
function rootsFile(configPath) {
|
|
80372
|
+
return (0, import_node_path44.join)((0, import_node_path44.dirname)(configPath), "sources.json");
|
|
80373
|
+
}
|
|
80374
|
+
function migrateGlobalRootsIfNeeded(configPath) {
|
|
80375
|
+
const wsFile = rootsFile(configPath);
|
|
80376
|
+
if ((0, import_node_fs41.existsSync)(wsFile)) return;
|
|
80377
|
+
const globalFile = (0, import_node_path44.join)(configDir(), "sources.json");
|
|
80378
|
+
if (globalFile === wsFile || !(0, import_node_fs41.existsSync)(globalFile)) return;
|
|
80379
|
+
let roots;
|
|
80380
|
+
try {
|
|
80381
|
+
const parsed = JSON.parse((0, import_node_fs41.readFileSync)(globalFile, "utf8"));
|
|
80382
|
+
if (!Array.isArray(parsed.roots) || parsed.roots.length === 0) return;
|
|
80383
|
+
roots = parsed.roots;
|
|
80384
|
+
} catch {
|
|
80385
|
+
return;
|
|
80386
|
+
}
|
|
80387
|
+
try {
|
|
80388
|
+
(0, import_node_fs41.mkdirSync)((0, import_node_path44.dirname)(wsFile), { recursive: true });
|
|
80389
|
+
(0, import_node_fs41.writeFileSync)(wsFile, JSON.stringify({ roots }, null, 2), "utf8");
|
|
80390
|
+
} catch {
|
|
80391
|
+
return;
|
|
80392
|
+
}
|
|
80393
|
+
try {
|
|
80394
|
+
(0, import_node_fs41.rmSync)(globalFile, { force: true });
|
|
80395
|
+
} catch {
|
|
80396
|
+
try {
|
|
80397
|
+
(0, import_node_fs41.writeFileSync)(globalFile, JSON.stringify({ roots: [] }, null, 2), "utf8");
|
|
80398
|
+
} catch {
|
|
80399
|
+
}
|
|
80400
|
+
}
|
|
80297
80401
|
}
|
|
80298
|
-
function readRoots() {
|
|
80402
|
+
function readRoots(configPath) {
|
|
80403
|
+
migrateGlobalRootsIfNeeded(configPath);
|
|
80299
80404
|
try {
|
|
80300
|
-
const raw = (0, import_node_fs41.readFileSync)(rootsFile(), "utf8");
|
|
80405
|
+
const raw = (0, import_node_fs41.readFileSync)(rootsFile(configPath), "utf8");
|
|
80301
80406
|
const parsed = JSON.parse(raw);
|
|
80302
80407
|
return Array.isArray(parsed.roots) ? parsed.roots : [];
|
|
80303
80408
|
} catch {
|
|
80304
80409
|
return [];
|
|
80305
80410
|
}
|
|
80306
80411
|
}
|
|
80307
|
-
function writeRoots(roots) {
|
|
80308
|
-
(0, import_node_fs41.writeFileSync)(rootsFile(), JSON.stringify({ roots }, null, 2), "utf8");
|
|
80412
|
+
function writeRoots(configPath, roots) {
|
|
80413
|
+
(0, import_node_fs41.writeFileSync)(rootsFile(configPath), JSON.stringify({ roots }, null, 2), "utf8");
|
|
80309
80414
|
}
|
|
80310
80415
|
function safeResolveInside2(target, roots) {
|
|
80311
80416
|
const abs = (0, import_node_path44.resolve)(target);
|
|
@@ -80395,14 +80500,14 @@ async function ingestFolder(abs, ingestFile) {
|
|
|
80395
80500
|
return { ingested, skipped };
|
|
80396
80501
|
}
|
|
80397
80502
|
async function dispatchSourcesRoute(req, res, deps) {
|
|
80398
|
-
const { pathname, method, ingestFile } = deps;
|
|
80503
|
+
const { pathname, method, ingestFile, configPath } = deps;
|
|
80399
80504
|
if (!pathname.startsWith("/api/sources/")) return false;
|
|
80400
80505
|
if (!localFileOpenEnabled()) {
|
|
80401
80506
|
sendJson(res, { enabled: false });
|
|
80402
80507
|
return true;
|
|
80403
80508
|
}
|
|
80404
80509
|
if (pathname === "/api/sources/roots" && method === "GET") {
|
|
80405
|
-
sendJson(res, { enabled: true, roots: readRoots() });
|
|
80510
|
+
sendJson(res, { enabled: true, roots: readRoots(configPath) });
|
|
80406
80511
|
return true;
|
|
80407
80512
|
}
|
|
80408
80513
|
if (pathname === "/api/sources/roots" && method === "POST") {
|
|
@@ -80428,12 +80533,12 @@ async function dispatchSourcesRoute(req, res, deps) {
|
|
|
80428
80533
|
sendJson(res, { error: `path not found: ${abs}` }, 400);
|
|
80429
80534
|
return true;
|
|
80430
80535
|
}
|
|
80431
|
-
const roots = readRoots();
|
|
80536
|
+
const roots = readRoots(configPath);
|
|
80432
80537
|
let root6 = roots.find((r6) => (0, import_node_path44.resolve)(r6.path) === abs);
|
|
80433
80538
|
if (!root6) {
|
|
80434
80539
|
root6 = { id: (0, import_node_crypto24.randomUUID)(), path: abs, kind, name: (0, import_node_path44.basename)(abs) || abs };
|
|
80435
80540
|
roots.push(root6);
|
|
80436
|
-
writeRoots(roots);
|
|
80541
|
+
writeRoots(configPath, roots);
|
|
80437
80542
|
}
|
|
80438
80543
|
let result;
|
|
80439
80544
|
if (kind === "folder") result = await ingestFolder(abs, ingestFile);
|
|
@@ -80444,8 +80549,8 @@ async function dispatchSourcesRoute(req, res, deps) {
|
|
|
80444
80549
|
const delMatch = /^\/api\/sources\/roots\/([^/]+)$/.exec(pathname);
|
|
80445
80550
|
if (delMatch && method === "DELETE") {
|
|
80446
80551
|
const id = decodeURIComponent(delMatch[1] ?? "");
|
|
80447
|
-
const roots = readRoots().filter((r6) => r6.id !== id);
|
|
80448
|
-
writeRoots(roots);
|
|
80552
|
+
const roots = readRoots(configPath).filter((r6) => r6.id !== id);
|
|
80553
|
+
writeRoots(configPath, roots);
|
|
80449
80554
|
sendJson(res, { ok: true });
|
|
80450
80555
|
return true;
|
|
80451
80556
|
}
|
|
@@ -80463,7 +80568,7 @@ async function dispatchSourcesRoute(req, res, deps) {
|
|
|
80463
80568
|
sendJson(res, { error: "path is required" }, 400);
|
|
80464
80569
|
return true;
|
|
80465
80570
|
}
|
|
80466
|
-
const abs = safeResolveInside2(target, readRoots());
|
|
80571
|
+
const abs = safeResolveInside2(target, readRoots(configPath));
|
|
80467
80572
|
if (!abs) {
|
|
80468
80573
|
sendJson(res, { error: "path is outside any registered source root" }, 403);
|
|
80469
80574
|
return true;
|
|
@@ -80478,7 +80583,7 @@ async function dispatchSourcesRoute(req, res, deps) {
|
|
|
80478
80583
|
if (pathname === "/api/sources/ingest-folder" && method === "POST") {
|
|
80479
80584
|
const body = await readJson(req).catch(() => ({}));
|
|
80480
80585
|
const target = typeof body.path === "string" ? body.path : "";
|
|
80481
|
-
const abs = safeResolveInside2(target, readRoots());
|
|
80586
|
+
const abs = safeResolveInside2(target, readRoots(configPath));
|
|
80482
80587
|
if (!abs) {
|
|
80483
80588
|
sendJson(res, { error: "path is outside any registered source root" }, 403);
|
|
80484
80589
|
return true;
|
|
@@ -83117,6 +83222,7 @@ async function startGuiServer(options) {
|
|
|
83117
83222
|
return await dispatchSourcesRoute(req2, res2, {
|
|
83118
83223
|
db: active.db,
|
|
83119
83224
|
ingestFile: (p3) => ingestLocalFile(ingestCtx, mctx, p3, false),
|
|
83225
|
+
configPath: active.configPath,
|
|
83120
83226
|
pathname,
|
|
83121
83227
|
method
|
|
83122
83228
|
});
|