latticesql 3.3.2 → 3.3.4
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 +100 -17
- package/dist/index.cjs +99 -16
- package/dist/index.js +99 -16
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1061,13 +1061,24 @@ function moduleContext() {
|
|
|
1061
1061
|
return _moduleContext;
|
|
1062
1062
|
}
|
|
1063
1063
|
async function registerPostgresPolyfills(run) {
|
|
1064
|
+
let permissionDenied = false;
|
|
1064
1065
|
for (const { warn, sql } of POSTGRES_POLYFILLS) {
|
|
1065
1066
|
try {
|
|
1066
1067
|
await run(sql);
|
|
1067
1068
|
} catch (err) {
|
|
1068
|
-
|
|
1069
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1070
|
+
if (/permission denied/i.test(msg)) {
|
|
1071
|
+
permissionDenied = true;
|
|
1072
|
+
} else {
|
|
1073
|
+
console.warn(`[PostgresAdapter] ${warn}`, msg);
|
|
1074
|
+
}
|
|
1069
1075
|
}
|
|
1070
1076
|
}
|
|
1077
|
+
if (permissionDenied) {
|
|
1078
|
+
console.debug(
|
|
1079
|
+
"[PostgresAdapter] SQLite-compat polyfills are owner-managed on this cloud; skipping member-side (re)creation (expected)."
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1071
1082
|
}
|
|
1072
1083
|
function translateDialect(sql) {
|
|
1073
1084
|
if (/INSERT\s+OR\s+REPLACE\s+INTO/i.test(sql)) {
|
|
@@ -5103,7 +5114,23 @@ var init_lattice = __esm({
|
|
|
5103
5114
|
}
|
|
5104
5115
|
/** Async tail of init(). See {@link init} for the sync-validation phase. */
|
|
5105
5116
|
async _initAsync(options) {
|
|
5106
|
-
|
|
5117
|
+
let introspectOnly = options.introspectOnly === true;
|
|
5118
|
+
if (!introspectOnly && this.getDialect() === "postgres") {
|
|
5119
|
+
try {
|
|
5120
|
+
const [marker, role] = await Promise.all([
|
|
5121
|
+
getAsyncOrSync(this._adapter, `SELECT to_regclass('__lattice_owners') AS reg`),
|
|
5122
|
+
getAsyncOrSync(
|
|
5123
|
+
this._adapter,
|
|
5124
|
+
`SELECT rolcreaterole FROM pg_roles WHERE rolname = current_user`
|
|
5125
|
+
)
|
|
5126
|
+
]);
|
|
5127
|
+
const provisioned = !!marker && marker.reg != null;
|
|
5128
|
+
const canCreateRoles = !!role && role.rolcreaterole === true;
|
|
5129
|
+
introspectOnly = provisioned && !canCreateRoles;
|
|
5130
|
+
} catch {
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
if (introspectOnly) {
|
|
5107
5134
|
for (const tableName of this._schema.getTables().keys()) {
|
|
5108
5135
|
try {
|
|
5109
5136
|
const cols = await introspectColumnsAsyncOrSync(this._adapter, tableName);
|
|
@@ -57051,7 +57078,7 @@ var appJs = `
|
|
|
57051
57078
|
'<div class="view-header">' +
|
|
57052
57079
|
'<span class="entity-icon">\u2699</span>' +
|
|
57053
57080
|
'<h1>' + escapeHtml(tableName) + '</h1>' +
|
|
57054
|
-
'<span class="count">' + entry.rowCount + ' row' + (entry.rowCount === 1 ? '' : 's') +
|
|
57081
|
+
'<span class="count">' + (entry.rowCount == null ? 'no access' : (entry.rowCount + ' row' + (entry.rowCount === 1 ? '' : 's'))) +
|
|
57055
57082
|
' \xB7 read-only</span>' +
|
|
57056
57083
|
'</div>' +
|
|
57057
57084
|
'<div class="muted" style="margin-bottom:12px;font-size:13px;">' +
|
|
@@ -61766,6 +61793,52 @@ async function reconcileCloudMemberAccess(db) {
|
|
|
61766
61793
|
);
|
|
61767
61794
|
}
|
|
61768
61795
|
}
|
|
61796
|
+
const memberSystemGrants = [
|
|
61797
|
+
["_lattice_gui_meta", "SELECT, INSERT, UPDATE"],
|
|
61798
|
+
["_lattice_gui_column_meta", "SELECT, INSERT, UPDATE"],
|
|
61799
|
+
["_lattice_gui_audit", "SELECT, INSERT"],
|
|
61800
|
+
["__lattice_user_identity", "SELECT, INSERT, UPDATE"]
|
|
61801
|
+
];
|
|
61802
|
+
for (const [tbl, privs] of memberSystemGrants) {
|
|
61803
|
+
await runAsyncOrSync(
|
|
61804
|
+
db.adapter,
|
|
61805
|
+
`DO $LATTICE$ BEGIN
|
|
61806
|
+
IF to_regclass('${tbl}') IS NOT NULL THEN
|
|
61807
|
+
EXECUTE 'GRANT ${privs} ON "${tbl}" TO ${MEMBER_GROUP}';
|
|
61808
|
+
END IF;
|
|
61809
|
+
END $LATTICE$`
|
|
61810
|
+
);
|
|
61811
|
+
}
|
|
61812
|
+
try {
|
|
61813
|
+
await runAsyncOrSync(
|
|
61814
|
+
db.adapter,
|
|
61815
|
+
`GRANT EXECUTE ON FUNCTION json_extract(text, text), strftime(text, text) TO ${MEMBER_GROUP}`
|
|
61816
|
+
);
|
|
61817
|
+
} catch (err) {
|
|
61818
|
+
console.warn(
|
|
61819
|
+
"[reconcileCloudMemberAccess] could not grant EXECUTE on polyfills (will retry next open):",
|
|
61820
|
+
err instanceof Error ? err.message : String(err)
|
|
61821
|
+
);
|
|
61822
|
+
}
|
|
61823
|
+
for (const table of registered) {
|
|
61824
|
+
if (table.startsWith("__lattice_") || table.startsWith("_lattice_")) continue;
|
|
61825
|
+
const cols = db.getRegisteredColumns(table);
|
|
61826
|
+
if (cols && !("deleted_at" in cols)) {
|
|
61827
|
+
const q3 = `"${table.replace(/"/g, '""')}"`;
|
|
61828
|
+
await runAsyncOrSync(
|
|
61829
|
+
db.adapter,
|
|
61830
|
+
`ALTER TABLE ${q3} ADD COLUMN IF NOT EXISTS "deleted_at" TEXT`
|
|
61831
|
+
);
|
|
61832
|
+
}
|
|
61833
|
+
}
|
|
61834
|
+
await runAsyncOrSync(
|
|
61835
|
+
db.adapter,
|
|
61836
|
+
`DO $LATTICE$ BEGIN
|
|
61837
|
+
IF to_regclass('__lattice_changelog') IS NOT NULL THEN
|
|
61838
|
+
EXECUTE 'GRANT SELECT, INSERT ON "__lattice_changelog" TO ${MEMBER_GROUP}';
|
|
61839
|
+
END IF;
|
|
61840
|
+
END $LATTICE$`
|
|
61841
|
+
);
|
|
61769
61842
|
}
|
|
61770
61843
|
async function secureNewCloudTable(db, table, pk) {
|
|
61771
61844
|
if (db.getDialect() !== "postgres") return;
|
|
@@ -61791,14 +61864,6 @@ async function secureCloud(db) {
|
|
|
61791
61864
|
await secureNewCloudTable(db, table, db.getPrimaryKey(table));
|
|
61792
61865
|
}
|
|
61793
61866
|
await reconcileCloudMemberAccess(db);
|
|
61794
|
-
await runAsyncOrSync(
|
|
61795
|
-
db.adapter,
|
|
61796
|
-
`DO $LATTICE$ BEGIN
|
|
61797
|
-
IF to_regclass('__lattice_user_identity') IS NOT NULL THEN
|
|
61798
|
-
EXECUTE 'GRANT SELECT, INSERT, UPDATE ON "__lattice_user_identity" TO ${MEMBER_GROUP}';
|
|
61799
|
-
END IF;
|
|
61800
|
-
END $LATTICE$`
|
|
61801
|
-
);
|
|
61802
61867
|
}
|
|
61803
61868
|
|
|
61804
61869
|
// src/gui/meta-gen.ts
|
|
@@ -65307,6 +65372,7 @@ async function openConfig(configPath, outputDir, autoRender = false, realtimeWat
|
|
|
65307
65372
|
]);
|
|
65308
65373
|
const views = viewsRaw;
|
|
65309
65374
|
const knownTables = /* @__PURE__ */ new Set([...declared, ...discovered.map((t8) => t8.name)]);
|
|
65375
|
+
const memberEntityDefs = [];
|
|
65310
65376
|
for (const t8 of discovered) {
|
|
65311
65377
|
if (declared.has(t8.name)) continue;
|
|
65312
65378
|
if (t8.columns.length === 0) continue;
|
|
@@ -65314,17 +65380,25 @@ async function openConfig(configPath, outputDir, autoRender = false, realtimeWat
|
|
|
65314
65380
|
discoveredJunctions.add(t8.name);
|
|
65315
65381
|
continue;
|
|
65316
65382
|
}
|
|
65317
|
-
|
|
65383
|
+
const def = {
|
|
65318
65384
|
columns: Object.fromEntries(t8.columns.map((c6) => [c6, "TEXT"])),
|
|
65319
65385
|
...t8.pk.length > 0 ? { primaryKey: t8.pk.length === 1 ? t8.pk[0] : t8.pk } : {},
|
|
65320
65386
|
render: () => "",
|
|
65321
65387
|
outputFile: `${t8.name}/.lattice/${t8.name}.md`
|
|
65322
|
-
}
|
|
65388
|
+
};
|
|
65389
|
+
db.define(t8.name, def);
|
|
65390
|
+
memberEntityDefs.push({ name: t8.name, definition: def });
|
|
65323
65391
|
}
|
|
65324
65392
|
for (const { name } of views) {
|
|
65325
65393
|
const base = name.slice(0, -2);
|
|
65326
65394
|
if (knownTables.has(base)) maskedReadViews.set(base, name);
|
|
65327
65395
|
}
|
|
65396
|
+
if (autoRender && memberEntityDefs.length > 0) {
|
|
65397
|
+
const existingContexts = db.entityContexts();
|
|
65398
|
+
for (const { table, definition } of deriveCanonicalContexts(memberEntityDefs)) {
|
|
65399
|
+
if (!existingContexts.has(table)) db.defineEntityContext(table, definition);
|
|
65400
|
+
}
|
|
65401
|
+
}
|
|
65328
65402
|
}
|
|
65329
65403
|
}
|
|
65330
65404
|
} catch {
|
|
@@ -66815,9 +66889,18 @@ async function startGuiServer(options) {
|
|
|
66815
66889
|
}
|
|
66816
66890
|
const tables = [];
|
|
66817
66891
|
for (const r6 of rows) {
|
|
66818
|
-
|
|
66819
|
-
|
|
66820
|
-
|
|
66892
|
+
try {
|
|
66893
|
+
const cols = await active.db.introspectColumns(r6.name);
|
|
66894
|
+
const rowCount = await active.db.count(r6.name);
|
|
66895
|
+
tables.push({ name: r6.name, columns: cols, rowCount });
|
|
66896
|
+
} catch (err) {
|
|
66897
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
66898
|
+
if (/permission denied|does not exist/i.test(msg)) {
|
|
66899
|
+
tables.push({ name: r6.name, columns: [], rowCount: null });
|
|
66900
|
+
} else {
|
|
66901
|
+
throw err;
|
|
66902
|
+
}
|
|
66903
|
+
}
|
|
66821
66904
|
}
|
|
66822
66905
|
sendJson(res, { tables });
|
|
66823
66906
|
return;
|
|
@@ -67783,7 +67866,7 @@ function printHelp() {
|
|
|
67783
67866
|
);
|
|
67784
67867
|
}
|
|
67785
67868
|
function getVersion() {
|
|
67786
|
-
if (true) return "3.3.
|
|
67869
|
+
if (true) return "3.3.4";
|
|
67787
67870
|
try {
|
|
67788
67871
|
const pkgPath = new URL("../package.json", import.meta.url).pathname;
|
|
67789
67872
|
const pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
|
package/dist/index.cjs
CHANGED
|
@@ -437,13 +437,24 @@ function moduleContext() {
|
|
|
437
437
|
return _moduleContext;
|
|
438
438
|
}
|
|
439
439
|
async function registerPostgresPolyfills(run) {
|
|
440
|
+
let permissionDenied = false;
|
|
440
441
|
for (const { warn, sql } of POSTGRES_POLYFILLS) {
|
|
441
442
|
try {
|
|
442
443
|
await run(sql);
|
|
443
444
|
} catch (err) {
|
|
444
|
-
|
|
445
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
446
|
+
if (/permission denied/i.test(msg)) {
|
|
447
|
+
permissionDenied = true;
|
|
448
|
+
} else {
|
|
449
|
+
console.warn(`[PostgresAdapter] ${warn}`, msg);
|
|
450
|
+
}
|
|
445
451
|
}
|
|
446
452
|
}
|
|
453
|
+
if (permissionDenied) {
|
|
454
|
+
console.debug(
|
|
455
|
+
"[PostgresAdapter] SQLite-compat polyfills are owner-managed on this cloud; skipping member-side (re)creation (expected)."
|
|
456
|
+
);
|
|
457
|
+
}
|
|
447
458
|
}
|
|
448
459
|
function translateDialect(sql) {
|
|
449
460
|
if (/INSERT\s+OR\s+REPLACE\s+INTO/i.test(sql)) {
|
|
@@ -5266,7 +5277,23 @@ var init_lattice = __esm({
|
|
|
5266
5277
|
}
|
|
5267
5278
|
/** Async tail of init(). See {@link init} for the sync-validation phase. */
|
|
5268
5279
|
async _initAsync(options) {
|
|
5269
|
-
|
|
5280
|
+
let introspectOnly = options.introspectOnly === true;
|
|
5281
|
+
if (!introspectOnly && this.getDialect() === "postgres") {
|
|
5282
|
+
try {
|
|
5283
|
+
const [marker, role] = await Promise.all([
|
|
5284
|
+
getAsyncOrSync(this._adapter, `SELECT to_regclass('__lattice_owners') AS reg`),
|
|
5285
|
+
getAsyncOrSync(
|
|
5286
|
+
this._adapter,
|
|
5287
|
+
`SELECT rolcreaterole FROM pg_roles WHERE rolname = current_user`
|
|
5288
|
+
)
|
|
5289
|
+
]);
|
|
5290
|
+
const provisioned = !!marker && marker.reg != null;
|
|
5291
|
+
const canCreateRoles = !!role && role.rolcreaterole === true;
|
|
5292
|
+
introspectOnly = provisioned && !canCreateRoles;
|
|
5293
|
+
} catch {
|
|
5294
|
+
}
|
|
5295
|
+
}
|
|
5296
|
+
if (introspectOnly) {
|
|
5270
5297
|
for (const tableName of this._schema.getTables().keys()) {
|
|
5271
5298
|
try {
|
|
5272
5299
|
const cols = await introspectColumnsAsyncOrSync(this._adapter, tableName);
|
|
@@ -54016,6 +54043,52 @@ async function reconcileCloudMemberAccess(db) {
|
|
|
54016
54043
|
);
|
|
54017
54044
|
}
|
|
54018
54045
|
}
|
|
54046
|
+
const memberSystemGrants = [
|
|
54047
|
+
["_lattice_gui_meta", "SELECT, INSERT, UPDATE"],
|
|
54048
|
+
["_lattice_gui_column_meta", "SELECT, INSERT, UPDATE"],
|
|
54049
|
+
["_lattice_gui_audit", "SELECT, INSERT"],
|
|
54050
|
+
["__lattice_user_identity", "SELECT, INSERT, UPDATE"]
|
|
54051
|
+
];
|
|
54052
|
+
for (const [tbl, privs] of memberSystemGrants) {
|
|
54053
|
+
await runAsyncOrSync(
|
|
54054
|
+
db.adapter,
|
|
54055
|
+
`DO $LATTICE$ BEGIN
|
|
54056
|
+
IF to_regclass('${tbl}') IS NOT NULL THEN
|
|
54057
|
+
EXECUTE 'GRANT ${privs} ON "${tbl}" TO ${MEMBER_GROUP}';
|
|
54058
|
+
END IF;
|
|
54059
|
+
END $LATTICE$`
|
|
54060
|
+
);
|
|
54061
|
+
}
|
|
54062
|
+
try {
|
|
54063
|
+
await runAsyncOrSync(
|
|
54064
|
+
db.adapter,
|
|
54065
|
+
`GRANT EXECUTE ON FUNCTION json_extract(text, text), strftime(text, text) TO ${MEMBER_GROUP}`
|
|
54066
|
+
);
|
|
54067
|
+
} catch (err) {
|
|
54068
|
+
console.warn(
|
|
54069
|
+
"[reconcileCloudMemberAccess] could not grant EXECUTE on polyfills (will retry next open):",
|
|
54070
|
+
err instanceof Error ? err.message : String(err)
|
|
54071
|
+
);
|
|
54072
|
+
}
|
|
54073
|
+
for (const table of registered) {
|
|
54074
|
+
if (table.startsWith("__lattice_") || table.startsWith("_lattice_")) continue;
|
|
54075
|
+
const cols = db.getRegisteredColumns(table);
|
|
54076
|
+
if (cols && !("deleted_at" in cols)) {
|
|
54077
|
+
const q3 = `"${table.replace(/"/g, '""')}"`;
|
|
54078
|
+
await runAsyncOrSync(
|
|
54079
|
+
db.adapter,
|
|
54080
|
+
`ALTER TABLE ${q3} ADD COLUMN IF NOT EXISTS "deleted_at" TEXT`
|
|
54081
|
+
);
|
|
54082
|
+
}
|
|
54083
|
+
}
|
|
54084
|
+
await runAsyncOrSync(
|
|
54085
|
+
db.adapter,
|
|
54086
|
+
`DO $LATTICE$ BEGIN
|
|
54087
|
+
IF to_regclass('__lattice_changelog') IS NOT NULL THEN
|
|
54088
|
+
EXECUTE 'GRANT SELECT, INSERT ON "__lattice_changelog" TO ${MEMBER_GROUP}';
|
|
54089
|
+
END IF;
|
|
54090
|
+
END $LATTICE$`
|
|
54091
|
+
);
|
|
54019
54092
|
}
|
|
54020
54093
|
async function secureNewCloudTable(db, table, pk) {
|
|
54021
54094
|
if (db.getDialect() !== "postgres") return;
|
|
@@ -54041,14 +54114,6 @@ async function secureCloud(db) {
|
|
|
54041
54114
|
await secureNewCloudTable(db, table, db.getPrimaryKey(table));
|
|
54042
54115
|
}
|
|
54043
54116
|
await reconcileCloudMemberAccess(db);
|
|
54044
|
-
await runAsyncOrSync(
|
|
54045
|
-
db.adapter,
|
|
54046
|
-
`DO $LATTICE$ BEGIN
|
|
54047
|
-
IF to_regclass('__lattice_user_identity') IS NOT NULL THEN
|
|
54048
|
-
EXECUTE 'GRANT SELECT, INSERT, UPDATE ON "__lattice_user_identity" TO ${MEMBER_GROUP}';
|
|
54049
|
-
END IF;
|
|
54050
|
-
END $LATTICE$`
|
|
54051
|
-
);
|
|
54052
54117
|
}
|
|
54053
54118
|
|
|
54054
54119
|
// src/index.ts
|
|
@@ -58921,7 +58986,7 @@ var appJs = `
|
|
|
58921
58986
|
'<div class="view-header">' +
|
|
58922
58987
|
'<span class="entity-icon">\u2699</span>' +
|
|
58923
58988
|
'<h1>' + escapeHtml(tableName) + '</h1>' +
|
|
58924
|
-
'<span class="count">' + entry.rowCount + ' row' + (entry.rowCount === 1 ? '' : 's') +
|
|
58989
|
+
'<span class="count">' + (entry.rowCount == null ? 'no access' : (entry.rowCount + ' row' + (entry.rowCount === 1 ? '' : 's'))) +
|
|
58925
58990
|
' \xB7 read-only</span>' +
|
|
58926
58991
|
'</div>' +
|
|
58927
58992
|
'<div class="muted" style="margin-bottom:12px;font-size:13px;">' +
|
|
@@ -66479,6 +66544,7 @@ async function openConfig(configPath, outputDir, autoRender = false, realtimeWat
|
|
|
66479
66544
|
]);
|
|
66480
66545
|
const views = viewsRaw;
|
|
66481
66546
|
const knownTables = /* @__PURE__ */ new Set([...declared, ...discovered.map((t8) => t8.name)]);
|
|
66547
|
+
const memberEntityDefs = [];
|
|
66482
66548
|
for (const t8 of discovered) {
|
|
66483
66549
|
if (declared.has(t8.name)) continue;
|
|
66484
66550
|
if (t8.columns.length === 0) continue;
|
|
@@ -66486,17 +66552,25 @@ async function openConfig(configPath, outputDir, autoRender = false, realtimeWat
|
|
|
66486
66552
|
discoveredJunctions.add(t8.name);
|
|
66487
66553
|
continue;
|
|
66488
66554
|
}
|
|
66489
|
-
|
|
66555
|
+
const def = {
|
|
66490
66556
|
columns: Object.fromEntries(t8.columns.map((c6) => [c6, "TEXT"])),
|
|
66491
66557
|
...t8.pk.length > 0 ? { primaryKey: t8.pk.length === 1 ? t8.pk[0] : t8.pk } : {},
|
|
66492
66558
|
render: () => "",
|
|
66493
66559
|
outputFile: `${t8.name}/.lattice/${t8.name}.md`
|
|
66494
|
-
}
|
|
66560
|
+
};
|
|
66561
|
+
db.define(t8.name, def);
|
|
66562
|
+
memberEntityDefs.push({ name: t8.name, definition: def });
|
|
66495
66563
|
}
|
|
66496
66564
|
for (const { name } of views) {
|
|
66497
66565
|
const base = name.slice(0, -2);
|
|
66498
66566
|
if (knownTables.has(base)) maskedReadViews.set(base, name);
|
|
66499
66567
|
}
|
|
66568
|
+
if (autoRender && memberEntityDefs.length > 0) {
|
|
66569
|
+
const existingContexts = db.entityContexts();
|
|
66570
|
+
for (const { table, definition } of deriveCanonicalContexts(memberEntityDefs)) {
|
|
66571
|
+
if (!existingContexts.has(table)) db.defineEntityContext(table, definition);
|
|
66572
|
+
}
|
|
66573
|
+
}
|
|
66500
66574
|
}
|
|
66501
66575
|
}
|
|
66502
66576
|
} catch {
|
|
@@ -67987,9 +68061,18 @@ async function startGuiServer(options) {
|
|
|
67987
68061
|
}
|
|
67988
68062
|
const tables = [];
|
|
67989
68063
|
for (const r6 of rows) {
|
|
67990
|
-
|
|
67991
|
-
|
|
67992
|
-
|
|
68064
|
+
try {
|
|
68065
|
+
const cols = await active.db.introspectColumns(r6.name);
|
|
68066
|
+
const rowCount = await active.db.count(r6.name);
|
|
68067
|
+
tables.push({ name: r6.name, columns: cols, rowCount });
|
|
68068
|
+
} catch (err) {
|
|
68069
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
68070
|
+
if (/permission denied|does not exist/i.test(msg)) {
|
|
68071
|
+
tables.push({ name: r6.name, columns: [], rowCount: null });
|
|
68072
|
+
} else {
|
|
68073
|
+
throw err;
|
|
68074
|
+
}
|
|
68075
|
+
}
|
|
67993
68076
|
}
|
|
67994
68077
|
sendJson(res, { tables });
|
|
67995
68078
|
return;
|
package/dist/index.js
CHANGED
|
@@ -430,13 +430,24 @@ function moduleContext() {
|
|
|
430
430
|
return _moduleContext;
|
|
431
431
|
}
|
|
432
432
|
async function registerPostgresPolyfills(run) {
|
|
433
|
+
let permissionDenied = false;
|
|
433
434
|
for (const { warn, sql } of POSTGRES_POLYFILLS) {
|
|
434
435
|
try {
|
|
435
436
|
await run(sql);
|
|
436
437
|
} catch (err) {
|
|
437
|
-
|
|
438
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
439
|
+
if (/permission denied/i.test(msg)) {
|
|
440
|
+
permissionDenied = true;
|
|
441
|
+
} else {
|
|
442
|
+
console.warn(`[PostgresAdapter] ${warn}`, msg);
|
|
443
|
+
}
|
|
438
444
|
}
|
|
439
445
|
}
|
|
446
|
+
if (permissionDenied) {
|
|
447
|
+
console.debug(
|
|
448
|
+
"[PostgresAdapter] SQLite-compat polyfills are owner-managed on this cloud; skipping member-side (re)creation (expected)."
|
|
449
|
+
);
|
|
450
|
+
}
|
|
440
451
|
}
|
|
441
452
|
function translateDialect(sql) {
|
|
442
453
|
if (/INSERT\s+OR\s+REPLACE\s+INTO/i.test(sql)) {
|
|
@@ -5262,7 +5273,23 @@ var init_lattice = __esm({
|
|
|
5262
5273
|
}
|
|
5263
5274
|
/** Async tail of init(). See {@link init} for the sync-validation phase. */
|
|
5264
5275
|
async _initAsync(options) {
|
|
5265
|
-
|
|
5276
|
+
let introspectOnly = options.introspectOnly === true;
|
|
5277
|
+
if (!introspectOnly && this.getDialect() === "postgres") {
|
|
5278
|
+
try {
|
|
5279
|
+
const [marker, role] = await Promise.all([
|
|
5280
|
+
getAsyncOrSync(this._adapter, `SELECT to_regclass('__lattice_owners') AS reg`),
|
|
5281
|
+
getAsyncOrSync(
|
|
5282
|
+
this._adapter,
|
|
5283
|
+
`SELECT rolcreaterole FROM pg_roles WHERE rolname = current_user`
|
|
5284
|
+
)
|
|
5285
|
+
]);
|
|
5286
|
+
const provisioned = !!marker && marker.reg != null;
|
|
5287
|
+
const canCreateRoles = !!role && role.rolcreaterole === true;
|
|
5288
|
+
introspectOnly = provisioned && !canCreateRoles;
|
|
5289
|
+
} catch {
|
|
5290
|
+
}
|
|
5291
|
+
}
|
|
5292
|
+
if (introspectOnly) {
|
|
5266
5293
|
for (const tableName of this._schema.getTables().keys()) {
|
|
5267
5294
|
try {
|
|
5268
5295
|
const cols = await introspectColumnsAsyncOrSync(this._adapter, tableName);
|
|
@@ -53829,6 +53856,52 @@ async function reconcileCloudMemberAccess(db) {
|
|
|
53829
53856
|
);
|
|
53830
53857
|
}
|
|
53831
53858
|
}
|
|
53859
|
+
const memberSystemGrants = [
|
|
53860
|
+
["_lattice_gui_meta", "SELECT, INSERT, UPDATE"],
|
|
53861
|
+
["_lattice_gui_column_meta", "SELECT, INSERT, UPDATE"],
|
|
53862
|
+
["_lattice_gui_audit", "SELECT, INSERT"],
|
|
53863
|
+
["__lattice_user_identity", "SELECT, INSERT, UPDATE"]
|
|
53864
|
+
];
|
|
53865
|
+
for (const [tbl, privs] of memberSystemGrants) {
|
|
53866
|
+
await runAsyncOrSync(
|
|
53867
|
+
db.adapter,
|
|
53868
|
+
`DO $LATTICE$ BEGIN
|
|
53869
|
+
IF to_regclass('${tbl}') IS NOT NULL THEN
|
|
53870
|
+
EXECUTE 'GRANT ${privs} ON "${tbl}" TO ${MEMBER_GROUP}';
|
|
53871
|
+
END IF;
|
|
53872
|
+
END $LATTICE$`
|
|
53873
|
+
);
|
|
53874
|
+
}
|
|
53875
|
+
try {
|
|
53876
|
+
await runAsyncOrSync(
|
|
53877
|
+
db.adapter,
|
|
53878
|
+
`GRANT EXECUTE ON FUNCTION json_extract(text, text), strftime(text, text) TO ${MEMBER_GROUP}`
|
|
53879
|
+
);
|
|
53880
|
+
} catch (err) {
|
|
53881
|
+
console.warn(
|
|
53882
|
+
"[reconcileCloudMemberAccess] could not grant EXECUTE on polyfills (will retry next open):",
|
|
53883
|
+
err instanceof Error ? err.message : String(err)
|
|
53884
|
+
);
|
|
53885
|
+
}
|
|
53886
|
+
for (const table of registered) {
|
|
53887
|
+
if (table.startsWith("__lattice_") || table.startsWith("_lattice_")) continue;
|
|
53888
|
+
const cols = db.getRegisteredColumns(table);
|
|
53889
|
+
if (cols && !("deleted_at" in cols)) {
|
|
53890
|
+
const q3 = `"${table.replace(/"/g, '""')}"`;
|
|
53891
|
+
await runAsyncOrSync(
|
|
53892
|
+
db.adapter,
|
|
53893
|
+
`ALTER TABLE ${q3} ADD COLUMN IF NOT EXISTS "deleted_at" TEXT`
|
|
53894
|
+
);
|
|
53895
|
+
}
|
|
53896
|
+
}
|
|
53897
|
+
await runAsyncOrSync(
|
|
53898
|
+
db.adapter,
|
|
53899
|
+
`DO $LATTICE$ BEGIN
|
|
53900
|
+
IF to_regclass('__lattice_changelog') IS NOT NULL THEN
|
|
53901
|
+
EXECUTE 'GRANT SELECT, INSERT ON "__lattice_changelog" TO ${MEMBER_GROUP}';
|
|
53902
|
+
END IF;
|
|
53903
|
+
END $LATTICE$`
|
|
53904
|
+
);
|
|
53832
53905
|
}
|
|
53833
53906
|
async function secureNewCloudTable(db, table, pk) {
|
|
53834
53907
|
if (db.getDialect() !== "postgres") return;
|
|
@@ -53854,14 +53927,6 @@ async function secureCloud(db) {
|
|
|
53854
53927
|
await secureNewCloudTable(db, table, db.getPrimaryKey(table));
|
|
53855
53928
|
}
|
|
53856
53929
|
await reconcileCloudMemberAccess(db);
|
|
53857
|
-
await runAsyncOrSync(
|
|
53858
|
-
db.adapter,
|
|
53859
|
-
`DO $LATTICE$ BEGIN
|
|
53860
|
-
IF to_regclass('__lattice_user_identity') IS NOT NULL THEN
|
|
53861
|
-
EXECUTE 'GRANT SELECT, INSERT, UPDATE ON "__lattice_user_identity" TO ${MEMBER_GROUP}';
|
|
53862
|
-
END IF;
|
|
53863
|
-
END $LATTICE$`
|
|
53864
|
-
);
|
|
53865
53930
|
}
|
|
53866
53931
|
|
|
53867
53932
|
// src/index.ts
|
|
@@ -58741,7 +58806,7 @@ var appJs = `
|
|
|
58741
58806
|
'<div class="view-header">' +
|
|
58742
58807
|
'<span class="entity-icon">\u2699</span>' +
|
|
58743
58808
|
'<h1>' + escapeHtml(tableName) + '</h1>' +
|
|
58744
|
-
'<span class="count">' + entry.rowCount + ' row' + (entry.rowCount === 1 ? '' : 's') +
|
|
58809
|
+
'<span class="count">' + (entry.rowCount == null ? 'no access' : (entry.rowCount + ' row' + (entry.rowCount === 1 ? '' : 's'))) +
|
|
58745
58810
|
' \xB7 read-only</span>' +
|
|
58746
58811
|
'</div>' +
|
|
58747
58812
|
'<div class="muted" style="margin-bottom:12px;font-size:13px;">' +
|
|
@@ -66298,6 +66363,7 @@ async function openConfig(configPath, outputDir, autoRender = false, realtimeWat
|
|
|
66298
66363
|
]);
|
|
66299
66364
|
const views = viewsRaw;
|
|
66300
66365
|
const knownTables = /* @__PURE__ */ new Set([...declared, ...discovered.map((t8) => t8.name)]);
|
|
66366
|
+
const memberEntityDefs = [];
|
|
66301
66367
|
for (const t8 of discovered) {
|
|
66302
66368
|
if (declared.has(t8.name)) continue;
|
|
66303
66369
|
if (t8.columns.length === 0) continue;
|
|
@@ -66305,17 +66371,25 @@ async function openConfig(configPath, outputDir, autoRender = false, realtimeWat
|
|
|
66305
66371
|
discoveredJunctions.add(t8.name);
|
|
66306
66372
|
continue;
|
|
66307
66373
|
}
|
|
66308
|
-
|
|
66374
|
+
const def = {
|
|
66309
66375
|
columns: Object.fromEntries(t8.columns.map((c6) => [c6, "TEXT"])),
|
|
66310
66376
|
...t8.pk.length > 0 ? { primaryKey: t8.pk.length === 1 ? t8.pk[0] : t8.pk } : {},
|
|
66311
66377
|
render: () => "",
|
|
66312
66378
|
outputFile: `${t8.name}/.lattice/${t8.name}.md`
|
|
66313
|
-
}
|
|
66379
|
+
};
|
|
66380
|
+
db.define(t8.name, def);
|
|
66381
|
+
memberEntityDefs.push({ name: t8.name, definition: def });
|
|
66314
66382
|
}
|
|
66315
66383
|
for (const { name } of views) {
|
|
66316
66384
|
const base = name.slice(0, -2);
|
|
66317
66385
|
if (knownTables.has(base)) maskedReadViews.set(base, name);
|
|
66318
66386
|
}
|
|
66387
|
+
if (autoRender && memberEntityDefs.length > 0) {
|
|
66388
|
+
const existingContexts = db.entityContexts();
|
|
66389
|
+
for (const { table, definition } of deriveCanonicalContexts(memberEntityDefs)) {
|
|
66390
|
+
if (!existingContexts.has(table)) db.defineEntityContext(table, definition);
|
|
66391
|
+
}
|
|
66392
|
+
}
|
|
66319
66393
|
}
|
|
66320
66394
|
}
|
|
66321
66395
|
} catch {
|
|
@@ -67806,9 +67880,18 @@ async function startGuiServer(options) {
|
|
|
67806
67880
|
}
|
|
67807
67881
|
const tables = [];
|
|
67808
67882
|
for (const r6 of rows) {
|
|
67809
|
-
|
|
67810
|
-
|
|
67811
|
-
|
|
67883
|
+
try {
|
|
67884
|
+
const cols = await active.db.introspectColumns(r6.name);
|
|
67885
|
+
const rowCount = await active.db.count(r6.name);
|
|
67886
|
+
tables.push({ name: r6.name, columns: cols, rowCount });
|
|
67887
|
+
} catch (err) {
|
|
67888
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
67889
|
+
if (/permission denied|does not exist/i.test(msg)) {
|
|
67890
|
+
tables.push({ name: r6.name, columns: [], rowCount: null });
|
|
67891
|
+
} else {
|
|
67892
|
+
throw err;
|
|
67893
|
+
}
|
|
67894
|
+
}
|
|
67812
67895
|
}
|
|
67813
67896
|
sendJson(res, { tables });
|
|
67814
67897
|
return;
|
package/package.json
CHANGED