latticesql 3.4.0 → 3.4.2
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 +375 -79
- package/dist/index.cjs +372 -78
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +369 -75
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1637,43 +1637,57 @@ var init_postgres = __esm({
|
|
|
1637
1637
|
},
|
|
1638
1638
|
{
|
|
1639
1639
|
warn: "could not register json_extract polyfill:",
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1640
|
+
// Create ONLY if absent. `CREATE OR REPLACE` on an existing function requires
|
|
1641
|
+
// ownership, but on a cloud the function is owned by whichever single role
|
|
1642
|
+
// created it first — so every OTHER member's per-connect replace raised "must
|
|
1643
|
+
// be owner of function" and (sharing the render transaction) aborted it,
|
|
1644
|
+
// yielding an empty render. The IF-absent guard makes a present function a
|
|
1645
|
+
// clean no-op for everyone, regardless of who owns it.
|
|
1646
|
+
sql: `DO $do$ BEGIN
|
|
1647
|
+
IF to_regprocedure('json_extract(text, text)') IS NULL THEN
|
|
1648
|
+
CREATE FUNCTION json_extract(doc text, path text)
|
|
1649
|
+
RETURNS text
|
|
1650
|
+
LANGUAGE sql
|
|
1651
|
+
IMMUTABLE
|
|
1652
|
+
AS $fn$
|
|
1653
|
+
SELECT doc::jsonb #>> string_to_array(regexp_replace(path, '^\\$\\.?', ''), '.')
|
|
1654
|
+
$fn$;
|
|
1655
|
+
END IF;
|
|
1656
|
+
END $do$;`
|
|
1647
1657
|
},
|
|
1648
1658
|
{
|
|
1649
1659
|
warn: "could not register strftime polyfill:",
|
|
1650
|
-
sql: `
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1660
|
+
sql: `DO $do$ BEGIN
|
|
1661
|
+
IF to_regprocedure('strftime(text, text)') IS NULL THEN
|
|
1662
|
+
CREATE FUNCTION strftime(format text, modifier text)
|
|
1663
|
+
RETURNS text
|
|
1664
|
+
LANGUAGE plpgsql
|
|
1665
|
+
IMMUTABLE
|
|
1666
|
+
AS $fn$
|
|
1667
|
+
DECLARE ts timestamptz;
|
|
1668
|
+
BEGIN
|
|
1669
|
+
IF modifier = 'now' THEN
|
|
1670
|
+
ts := now();
|
|
1671
|
+
ELSE
|
|
1672
|
+
ts := modifier::timestamptz;
|
|
1673
|
+
END IF;
|
|
1674
|
+
RETURN to_char(
|
|
1675
|
+
ts AT TIME ZONE 'UTC',
|
|
1676
|
+
replace(replace(replace(replace(replace(replace(replace(replace(
|
|
1677
|
+
format,
|
|
1678
|
+
'%Y', 'YYYY'),
|
|
1679
|
+
'%m', 'MM'),
|
|
1680
|
+
'%d', 'DD'),
|
|
1681
|
+
'%H', 'HH24'),
|
|
1682
|
+
'%M', 'MI'),
|
|
1683
|
+
'%S', 'SS'),
|
|
1684
|
+
'%f', 'MS'),
|
|
1685
|
+
'T', '"T"')
|
|
1686
|
+
);
|
|
1687
|
+
END;
|
|
1688
|
+
$fn$;
|
|
1689
|
+
END IF;
|
|
1690
|
+
END $do$;`
|
|
1677
1691
|
}
|
|
1678
1692
|
];
|
|
1679
1693
|
}
|
|
@@ -3077,6 +3091,34 @@ var init_engine = __esm({
|
|
|
3077
3091
|
setRenderFold(fn) {
|
|
3078
3092
|
this._foldRows = fn;
|
|
3079
3093
|
}
|
|
3094
|
+
/**
|
|
3095
|
+
* Incremental scope: is this entity-context table affected by a change to one
|
|
3096
|
+
* of `changed`? Affected when the table itself changed (its own rows / `self`
|
|
3097
|
+
* source / index) OR any of its files SOURCES from a changed table (a cross-
|
|
3098
|
+
* table dependent — e.g. an AGENT.md that lists the agent's tasks must re-render
|
|
3099
|
+
* when `tasks` changes). A `custom` source runs an arbitrary query, so we can't
|
|
3100
|
+
* prove independence — treat it as always-affected (conservative, never stale).
|
|
3101
|
+
*/
|
|
3102
|
+
_entityAffected(table, def, changed) {
|
|
3103
|
+
if (changed.has(table)) return true;
|
|
3104
|
+
for (const spec of Object.values(def.files)) {
|
|
3105
|
+
if (this._sourceTouches(spec.source, changed)) return true;
|
|
3106
|
+
}
|
|
3107
|
+
return false;
|
|
3108
|
+
}
|
|
3109
|
+
_sourceTouches(source, changed) {
|
|
3110
|
+
if (source == null || typeof source !== "object") return false;
|
|
3111
|
+
const s2 = source;
|
|
3112
|
+
if (s2.type === "custom") return true;
|
|
3113
|
+
if (typeof s2.table === "string" && changed.has(s2.table)) return true;
|
|
3114
|
+
if (typeof s2.junctionTable === "string" && changed.has(s2.junctionTable)) return true;
|
|
3115
|
+
if (s2.sources != null && typeof s2.sources === "object") {
|
|
3116
|
+
for (const sub of Object.values(s2.sources)) {
|
|
3117
|
+
if (this._sourceTouches(sub, changed)) return true;
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
return false;
|
|
3121
|
+
}
|
|
3080
3122
|
async render(outputDir, opts = {}) {
|
|
3081
3123
|
const start = Date.now();
|
|
3082
3124
|
const filesWritten = [];
|
|
@@ -3086,6 +3128,7 @@ var init_engine = __esm({
|
|
|
3086
3128
|
for (const [name, def] of this._schema.getTables()) {
|
|
3087
3129
|
if (signal?.aborted) return this._abortedResult(filesWritten, counters, start);
|
|
3088
3130
|
if (this._skipEmpty && def.render === NOOP_RENDER) continue;
|
|
3131
|
+
if (opts.changedTables && !opts.changedTables.has(name)) continue;
|
|
3089
3132
|
let rows = await this._schema.queryTable(this._adapter, name, this._readRel);
|
|
3090
3133
|
if (def.relevanceFilter) {
|
|
3091
3134
|
const ctx = this._getTaskContext();
|
|
@@ -3138,6 +3181,9 @@ var init_engine = __esm({
|
|
|
3138
3181
|
}
|
|
3139
3182
|
for (const [name, def] of this._schema.getMultis()) {
|
|
3140
3183
|
if (signal?.aborted) return this._abortedResult(filesWritten, counters, start);
|
|
3184
|
+
if (opts.changedTables && def.tables && !def.tables.some((t8) => opts.changedTables?.has(t8))) {
|
|
3185
|
+
continue;
|
|
3186
|
+
}
|
|
3141
3187
|
const keys = await def.keys();
|
|
3142
3188
|
const tables = {};
|
|
3143
3189
|
if (def.tables) {
|
|
@@ -3169,16 +3215,22 @@ var init_engine = __esm({
|
|
|
3169
3215
|
filesWritten,
|
|
3170
3216
|
counters,
|
|
3171
3217
|
throttle,
|
|
3172
|
-
signal
|
|
3218
|
+
signal,
|
|
3219
|
+
opts.changedTables
|
|
3173
3220
|
);
|
|
3174
3221
|
if (entityContextManifest === null) {
|
|
3175
3222
|
return this._abortedResult(filesWritten, counters, start);
|
|
3176
3223
|
}
|
|
3177
3224
|
if (this._schema.getEntityContexts().size > 0) {
|
|
3225
|
+
let entityContexts = entityContextManifest;
|
|
3226
|
+
if (opts.changedTables) {
|
|
3227
|
+
const prev = readManifest(outputDir);
|
|
3228
|
+
entityContexts = { ...prev?.entityContexts ?? {}, ...entityContextManifest };
|
|
3229
|
+
}
|
|
3178
3230
|
writeManifest(outputDir, {
|
|
3179
3231
|
version: 2,
|
|
3180
3232
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3181
|
-
entityContexts
|
|
3233
|
+
entityContexts
|
|
3182
3234
|
});
|
|
3183
3235
|
}
|
|
3184
3236
|
const result = {
|
|
@@ -3244,12 +3296,14 @@ var init_engine = __esm({
|
|
|
3244
3296
|
* partial tree). Progress is reported through `throttle`; abort is observed
|
|
3245
3297
|
* via `signal`.
|
|
3246
3298
|
*/
|
|
3247
|
-
async _renderEntityContexts(outputDir, filesWritten, counters, throttle, signal) {
|
|
3299
|
+
async _renderEntityContexts(outputDir, filesWritten, counters, throttle, signal, changedTables) {
|
|
3248
3300
|
const protectedTables = /* @__PURE__ */ new Set();
|
|
3249
3301
|
for (const [t8, d6] of this._schema.getEntityContexts()) {
|
|
3250
3302
|
if (d6.protected) protectedTables.add(t8);
|
|
3251
3303
|
}
|
|
3252
|
-
const entityTables = [...this._schema.getEntityContexts()]
|
|
3304
|
+
const entityTables = [...this._schema.getEntityContexts()].filter(
|
|
3305
|
+
([table, def]) => !changedTables || this._entityAffected(table, def, changedTables)
|
|
3306
|
+
);
|
|
3253
3307
|
const tableCount = entityTables.length;
|
|
3254
3308
|
if (signal?.aborted) return null;
|
|
3255
3309
|
const renderedEntries = await mapWithConcurrency(
|
|
@@ -5077,6 +5131,16 @@ var init_lattice = __esm({
|
|
|
5077
5131
|
_autoRenderPending = false;
|
|
5078
5132
|
_autoRenderInFlight = false;
|
|
5079
5133
|
_autoRenderDebounceMs = 250;
|
|
5134
|
+
/**
|
|
5135
|
+
* Incremental auto-render scope, accumulated between debounced renders. A write
|
|
5136
|
+
* or a remote (cloud) change records the AFFECTED table here, so the next
|
|
5137
|
+
* auto-render re-renders only that entity (+ its cross-table dependents) instead
|
|
5138
|
+
* of the whole tree. `_pendingRenderAll` forces a full render (the initial
|
|
5139
|
+
* render, or a change with no known table). Captured + reset when a render
|
|
5140
|
+
* starts, so changes during a render re-accumulate and re-trigger.
|
|
5141
|
+
*/
|
|
5142
|
+
_pendingRenderTables = /* @__PURE__ */ new Set();
|
|
5143
|
+
_pendingRenderAll = true;
|
|
5080
5144
|
/** Cache of actual table columns (from PRAGMA), populated after init(). */
|
|
5081
5145
|
_columnCache = /* @__PURE__ */ new Map();
|
|
5082
5146
|
/** Derived encryption key (from options.encryptionKey via scrypt). */
|
|
@@ -6172,7 +6236,7 @@ var init_lattice = __esm({
|
|
|
6172
6236
|
`${verb} INTO "${junctionTable}" (${colNames}) VALUES (${placeholders})`,
|
|
6173
6237
|
Object.values(filtered)
|
|
6174
6238
|
);
|
|
6175
|
-
this._scheduleAutoRender();
|
|
6239
|
+
this._scheduleAutoRender(junctionTable);
|
|
6176
6240
|
}
|
|
6177
6241
|
/**
|
|
6178
6242
|
* Delete rows from a junction table matching all given conditions.
|
|
@@ -6188,7 +6252,7 @@ var init_lattice = __esm({
|
|
|
6188
6252
|
`DELETE FROM "${junctionTable}" WHERE ${where}`,
|
|
6189
6253
|
entries.map(([, v2]) => v2)
|
|
6190
6254
|
);
|
|
6191
|
-
this._scheduleAutoRender();
|
|
6255
|
+
this._scheduleAutoRender(junctionTable);
|
|
6192
6256
|
}
|
|
6193
6257
|
// -------------------------------------------------------------------------
|
|
6194
6258
|
// Seeding DSL (v0.13+)
|
|
@@ -6444,6 +6508,11 @@ var init_lattice = __esm({
|
|
|
6444
6508
|
async renderInBackground(outputDir, opts = {}) {
|
|
6445
6509
|
const notInit = this._notInitError();
|
|
6446
6510
|
if (notInit) return notInit;
|
|
6511
|
+
if (!opts.changedTables) {
|
|
6512
|
+
this._pendingRenderAll = false;
|
|
6513
|
+
this._pendingRenderTables = /* @__PURE__ */ new Set();
|
|
6514
|
+
this._autoRenderPending = false;
|
|
6515
|
+
}
|
|
6447
6516
|
return this._renderGuarded(outputDir, opts);
|
|
6448
6517
|
}
|
|
6449
6518
|
/**
|
|
@@ -6473,9 +6542,12 @@ var init_lattice = __esm({
|
|
|
6473
6542
|
* tree when a REMOTE change arrives — notably an owner re-sharing or un-sharing
|
|
6474
6543
|
* a row, after which the member's per-viewer projection must be recompiled. A
|
|
6475
6544
|
* no-op when auto-render isn't enabled.
|
|
6545
|
+
*
|
|
6546
|
+
* Pass the CHANGED table so only that entity (+ its cross-table dependents) is
|
|
6547
|
+
* re-rendered instead of the whole tree; omit it to force a full render.
|
|
6476
6548
|
*/
|
|
6477
|
-
requestRender() {
|
|
6478
|
-
this._scheduleAutoRender();
|
|
6549
|
+
requestRender(table) {
|
|
6550
|
+
this._scheduleAutoRender(table);
|
|
6479
6551
|
}
|
|
6480
6552
|
/**
|
|
6481
6553
|
* True while a render is actively writing the context tree + manifest (auto-
|
|
@@ -6876,7 +6948,7 @@ var init_lattice = __esm({
|
|
|
6876
6948
|
for (const h6 of this._errorHandlers) h6(err instanceof Error ? err : new Error(String(err)));
|
|
6877
6949
|
}
|
|
6878
6950
|
}
|
|
6879
|
-
this._scheduleAutoRender();
|
|
6951
|
+
this._scheduleAutoRender(table);
|
|
6880
6952
|
}
|
|
6881
6953
|
/**
|
|
6882
6954
|
* Turn on automatic rendering into `outputDir`. After this, every insert /
|
|
@@ -6900,10 +6972,18 @@ var init_lattice = __esm({
|
|
|
6900
6972
|
this._autoRenderPending = false;
|
|
6901
6973
|
return this;
|
|
6902
6974
|
}
|
|
6903
|
-
_scheduleAutoRender() {
|
|
6975
|
+
_scheduleAutoRender(table) {
|
|
6904
6976
|
if (!this._autoRenderDir) return;
|
|
6977
|
+
if (table === void 0) this._pendingRenderAll = true;
|
|
6978
|
+
else this._pendingRenderTables.add(table);
|
|
6905
6979
|
this._autoRenderPending = true;
|
|
6906
|
-
|
|
6980
|
+
this._armAutoRenderTimer();
|
|
6981
|
+
}
|
|
6982
|
+
/** Arm the debounce timer if not already armed. Does NOT change the render
|
|
6983
|
+
* scope — used both by `_scheduleAutoRender` and the post-render re-arm so a
|
|
6984
|
+
* re-arm never escalates a pending incremental render to a full one. */
|
|
6985
|
+
_armAutoRenderTimer() {
|
|
6986
|
+
if (!this._autoRenderDir || this._autoRenderTimer) return;
|
|
6907
6987
|
this._autoRenderTimer = setTimeout(() => {
|
|
6908
6988
|
this._autoRenderTimer = void 0;
|
|
6909
6989
|
void this._runAutoRender();
|
|
@@ -6943,10 +7023,15 @@ var init_lattice = __esm({
|
|
|
6943
7023
|
}
|
|
6944
7024
|
if (!this._autoRenderPending) return;
|
|
6945
7025
|
this._autoRenderPending = false;
|
|
7026
|
+
const renderAll = this._pendingRenderAll;
|
|
7027
|
+
const changed = this._pendingRenderTables;
|
|
7028
|
+
this._pendingRenderAll = false;
|
|
7029
|
+
this._pendingRenderTables = /* @__PURE__ */ new Set();
|
|
6946
7030
|
this._autoRenderInFlight = true;
|
|
6947
7031
|
try {
|
|
6948
7032
|
const prevManifest = readManifest(dir);
|
|
6949
|
-
const
|
|
7033
|
+
const renderOpts = renderAll || changed.size === 0 ? {} : { changedTables: changed };
|
|
7034
|
+
const result = await this._render.render(dir, renderOpts);
|
|
6950
7035
|
for (const h6 of this._renderHandlers) h6(result);
|
|
6951
7036
|
const newManifest = readManifest(dir);
|
|
6952
7037
|
await this._render.cleanup(dir, prevManifest, {}, newManifest);
|
|
@@ -6958,7 +7043,7 @@ var init_lattice = __esm({
|
|
|
6958
7043
|
}
|
|
6959
7044
|
}
|
|
6960
7045
|
_rearmAutoRenderIfPending() {
|
|
6961
|
-
if (this._autoRenderPending) this.
|
|
7046
|
+
if (this._autoRenderPending) this._armAutoRenderTimer();
|
|
6962
7047
|
}
|
|
6963
7048
|
/**
|
|
6964
7049
|
* Update or remove the embedding for a row.
|
|
@@ -8035,6 +8120,44 @@ CREATE TRIGGER "${trg}" AFTER INSERT OR UPDATE OR DELETE ON ${q3}
|
|
|
8035
8120
|
FOR EACH ROW EXECUTE FUNCTION "${trg}"();
|
|
8036
8121
|
`;
|
|
8037
8122
|
}
|
|
8123
|
+
async function ownPolyfillsByGroup(db) {
|
|
8124
|
+
if (!isPg(db)) return;
|
|
8125
|
+
for (const sig of ["json_extract(text, text)", "strftime(text, text)"]) {
|
|
8126
|
+
try {
|
|
8127
|
+
const reg = await getAsyncOrSync(db.adapter, `SELECT to_regprocedure($1) AS reg`, [sig]);
|
|
8128
|
+
if (reg?.reg == null) continue;
|
|
8129
|
+
await runAsyncOrSync(db.adapter, `ALTER FUNCTION ${sig} OWNER TO "${MEMBER_GROUP}"`);
|
|
8130
|
+
} catch {
|
|
8131
|
+
}
|
|
8132
|
+
}
|
|
8133
|
+
}
|
|
8134
|
+
async function enableGuiAuditRls(db) {
|
|
8135
|
+
if (!isPg(db)) return;
|
|
8136
|
+
const reg = await getAsyncOrSync(db.adapter, `SELECT to_regclass($1) AS reg`, [
|
|
8137
|
+
"_lattice_gui_audit"
|
|
8138
|
+
]);
|
|
8139
|
+
if (reg?.reg == null) return;
|
|
8140
|
+
await runCloudBootstrapSql(
|
|
8141
|
+
db,
|
|
8142
|
+
`
|
|
8143
|
+
ALTER TABLE "_lattice_gui_audit" ENABLE ROW LEVEL SECURITY;
|
|
8144
|
+
ALTER TABLE "_lattice_gui_audit" FORCE ROW LEVEL SECURITY;
|
|
8145
|
+
DROP POLICY IF EXISTS "lattice_gui_audit_owner" ON "_lattice_gui_audit";
|
|
8146
|
+
DROP POLICY IF EXISTS "lattice_gui_audit_sel" ON "_lattice_gui_audit";
|
|
8147
|
+
CREATE POLICY "lattice_gui_audit_sel" ON "_lattice_gui_audit" FOR SELECT
|
|
8148
|
+
USING ("row_id" IS NULL OR lattice_row_visible("table_name", "row_id"));
|
|
8149
|
+
DROP POLICY IF EXISTS "lattice_gui_audit_ins" ON "_lattice_gui_audit";
|
|
8150
|
+
CREATE POLICY "lattice_gui_audit_ins" ON "_lattice_gui_audit" FOR INSERT
|
|
8151
|
+
WITH CHECK ("row_id" IS NULL OR lattice_row_visible("table_name", "row_id"));
|
|
8152
|
+
DROP POLICY IF EXISTS "lattice_gui_audit_upd" ON "_lattice_gui_audit";
|
|
8153
|
+
CREATE POLICY "lattice_gui_audit_upd" ON "_lattice_gui_audit" FOR UPDATE
|
|
8154
|
+
USING ("row_id" IS NULL OR lattice_row_visible("table_name", "row_id"));
|
|
8155
|
+
DROP POLICY IF EXISTS "lattice_gui_audit_del" ON "_lattice_gui_audit";
|
|
8156
|
+
CREATE POLICY "lattice_gui_audit_del" ON "_lattice_gui_audit" FOR DELETE
|
|
8157
|
+
USING ("row_id" IS NULL OR lattice_row_visible("table_name", "row_id"));
|
|
8158
|
+
`
|
|
8159
|
+
);
|
|
8160
|
+
}
|
|
8038
8161
|
async function installCloudRls(db) {
|
|
8039
8162
|
if (!isPg(db)) return;
|
|
8040
8163
|
const schema = await cloudSchema(db);
|
|
@@ -8068,6 +8191,24 @@ CREATE POLICY "lattice_changelog_ins" ON "__lattice_changelog" FOR INSERT WITH C
|
|
|
8068
8191
|
`
|
|
8069
8192
|
);
|
|
8070
8193
|
}
|
|
8194
|
+
async function enableChatPrivacyRls(db) {
|
|
8195
|
+
if (!isPg(db)) return;
|
|
8196
|
+
for (const t8 of ["chat_threads", "chat_messages"]) {
|
|
8197
|
+
const reg = await getAsyncOrSync(db.adapter, `SELECT to_regclass($1) AS reg`, [t8]);
|
|
8198
|
+
if (reg?.reg == null) continue;
|
|
8199
|
+
const q3 = `"${t8}"`;
|
|
8200
|
+
await runCloudBootstrapSql(
|
|
8201
|
+
db,
|
|
8202
|
+
`
|
|
8203
|
+
ALTER TABLE ${q3} ENABLE ROW LEVEL SECURITY;
|
|
8204
|
+
ALTER TABLE ${q3} FORCE ROW LEVEL SECURITY;
|
|
8205
|
+
DROP POLICY IF EXISTS "lattice_chat_owner" ON ${q3};
|
|
8206
|
+
CREATE POLICY "lattice_chat_owner" ON ${q3} AS RESTRICTIVE FOR SELECT
|
|
8207
|
+
USING ("owner_user_id" IS NOT NULL AND "owner_user_id" = session_user);
|
|
8208
|
+
`
|
|
8209
|
+
);
|
|
8210
|
+
}
|
|
8211
|
+
}
|
|
8071
8212
|
async function enableRlsForTable(db, table, pkCols) {
|
|
8072
8213
|
if (!isPg(db)) return;
|
|
8073
8214
|
const schema = await cloudSchema(db);
|
|
@@ -8182,6 +8323,18 @@ CREATE TABLE IF NOT EXISTS "__lattice_member_invites" (
|
|
|
8182
8323
|
-- bootstrap is now run directly + idempotently, not version-gated).
|
|
8183
8324
|
ALTER TABLE "__lattice_member_invites" ADD COLUMN IF NOT EXISTS "email" text;
|
|
8184
8325
|
|
|
8326
|
+
-- Owner-published entity/render LAYOUT (the entities + entityContexts config
|
|
8327
|
+
-- blocks), so a joined member \u2014 whose generated config has entities: {} \u2014 can
|
|
8328
|
+
-- hydrate the full render layout and produce a complete context tree. This holds
|
|
8329
|
+
-- schema CONFIG, not row data, so it is safe to share with members (granted
|
|
8330
|
+
-- SELECT). A shared singleton, like __lattice_user_identity: no per-row RLS.
|
|
8331
|
+
CREATE TABLE IF NOT EXISTS "__lattice_shared_schema" (
|
|
8332
|
+
"id" TEXT PRIMARY KEY DEFAULT 'singleton',
|
|
8333
|
+
"entities_json" TEXT,
|
|
8334
|
+
"contexts_json" TEXT,
|
|
8335
|
+
"updated_at" TEXT
|
|
8336
|
+
);
|
|
8337
|
+
|
|
8185
8338
|
-- #3.1 \u2014 one-time-use + revocation enforcement. After a member authenticates to
|
|
8186
8339
|
-- the cloud with their minted credential, the join path calls this to CLAIM the
|
|
8187
8340
|
-- invite. The single atomic UPDATE stamps redeemed_at and returns true ONLY when
|
|
@@ -10059,7 +10212,7 @@ var init_registry = __esm({
|
|
|
10059
10212
|
});
|
|
10060
10213
|
|
|
10061
10214
|
// src/gui/ai/lattice-docs.ts
|
|
10062
|
-
import { readFileSync as
|
|
10215
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync5, existsSync as existsSync17 } from "fs";
|
|
10063
10216
|
import { dirname as dirname8, join as join16 } from "path";
|
|
10064
10217
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10065
10218
|
function findDocsDir() {
|
|
@@ -10119,7 +10272,7 @@ function allSections() {
|
|
|
10119
10272
|
}
|
|
10120
10273
|
for (const f6 of files) {
|
|
10121
10274
|
try {
|
|
10122
|
-
out.push(...sectionsOf(f6,
|
|
10275
|
+
out.push(...sectionsOf(f6, readFileSync14(join16(dir, f6), "utf8")));
|
|
10123
10276
|
} catch {
|
|
10124
10277
|
}
|
|
10125
10278
|
}
|
|
@@ -55860,12 +56013,17 @@ var appJs = `
|
|
|
55860
56013
|
fetchJson('/api/gui-meta/columns').catch(function () { return {}; }),
|
|
55861
56014
|
fetchJson('/api/system-tables').catch(function () { return { tables: [] }; }),
|
|
55862
56015
|
fetchJson('/api/workspaces').catch(function () { return null; }),
|
|
56016
|
+
fetchJson('/api/dbconfig').catch(function () { return {}; }),
|
|
55863
56017
|
]).then(function (results) {
|
|
55864
56018
|
state.entities = results[0];
|
|
55865
56019
|
state.iconOverrides = results[1] || {};
|
|
55866
56020
|
state.columnMeta = results[2] || {};
|
|
55867
56021
|
state.systemTables = (results[3] && results[3].tables) || [];
|
|
55868
56022
|
renderWsSwitcher(results[4]);
|
|
56023
|
+
// Re-point the header logo at the NEW workspace's mark \u2014 the switch path
|
|
56024
|
+
// must refresh branding the way boot does (the etag cache-busts the
|
|
56025
|
+
// <img>), else the previous workspace's logo stays until a hard refresh.
|
|
56026
|
+
applyWorkspaceLogo((results[5] || {}).logoEtag);
|
|
55869
56027
|
renderSidebar();
|
|
55870
56028
|
// renderWsSwitcher set cloudMode from the new workspace's kind; re-render
|
|
55871
56029
|
// the composer so the Private-mode toggle reflects local vs cloud (it is
|
|
@@ -62648,6 +62806,87 @@ async function discoverCloudTables(db) {
|
|
|
62648
62806
|
// src/gui/server.ts
|
|
62649
62807
|
init_rls();
|
|
62650
62808
|
|
|
62809
|
+
// src/cloud/shared-schema.ts
|
|
62810
|
+
init_lattice();
|
|
62811
|
+
init_adapter();
|
|
62812
|
+
|
|
62813
|
+
// src/gui/config-io.ts
|
|
62814
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
62815
|
+
import { parseDocument as parseDocument2 } from "yaml";
|
|
62816
|
+
async function execSql(db, sql) {
|
|
62817
|
+
const adapter = db._adapter;
|
|
62818
|
+
if (!adapter.runAsync) throw new Error("Adapter does not support runAsync");
|
|
62819
|
+
await adapter.runAsync(sql);
|
|
62820
|
+
}
|
|
62821
|
+
function loadConfigDoc(configPath) {
|
|
62822
|
+
return parseDocument2(readFileSync13(configPath, "utf8"));
|
|
62823
|
+
}
|
|
62824
|
+
function saveConfigDoc(configPath, doc) {
|
|
62825
|
+
writeFileSync6(configPath, doc.toString(), "utf8");
|
|
62826
|
+
}
|
|
62827
|
+
|
|
62828
|
+
// src/cloud/shared-schema.ts
|
|
62829
|
+
async function publishSharedSchema(db, configPath) {
|
|
62830
|
+
if (db.getDialect() !== "postgres") return;
|
|
62831
|
+
const cfg = loadConfigDoc(configPath).toJSON();
|
|
62832
|
+
const entities = cfg.entities ?? {};
|
|
62833
|
+
if (Object.keys(entities).length === 0) return;
|
|
62834
|
+
await runAsyncOrSync(
|
|
62835
|
+
db.adapter,
|
|
62836
|
+
`INSERT INTO "__lattice_shared_schema" ("id","entities_json","contexts_json","updated_at")
|
|
62837
|
+
VALUES ('singleton', $1, $2, $3)
|
|
62838
|
+
ON CONFLICT ("id") DO UPDATE SET
|
|
62839
|
+
"entities_json" = EXCLUDED."entities_json",
|
|
62840
|
+
"contexts_json" = EXCLUDED."contexts_json",
|
|
62841
|
+
"updated_at" = EXCLUDED."updated_at"`,
|
|
62842
|
+
[
|
|
62843
|
+
JSON.stringify(entities),
|
|
62844
|
+
JSON.stringify(cfg.entityContexts ?? null),
|
|
62845
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
62846
|
+
]
|
|
62847
|
+
);
|
|
62848
|
+
}
|
|
62849
|
+
async function hydrateMemberConfigFromCloud(configPath, dbUrl, encryptionKey) {
|
|
62850
|
+
if (!isPostgresUrl(dbUrl)) return false;
|
|
62851
|
+
const existing = loadConfigDoc(configPath).toJSON();
|
|
62852
|
+
if (Object.keys(existing.entities ?? {}).length > 0) return false;
|
|
62853
|
+
try {
|
|
62854
|
+
const peek = new Lattice({ config: configPath }, { encryptionKey });
|
|
62855
|
+
try {
|
|
62856
|
+
await peek.init({ introspectOnly: true });
|
|
62857
|
+
const reg = await getAsyncOrSync(
|
|
62858
|
+
peek.adapter,
|
|
62859
|
+
"SELECT to_regclass('__lattice_shared_schema') AS reg"
|
|
62860
|
+
);
|
|
62861
|
+
if (reg?.reg == null) return false;
|
|
62862
|
+
const row = await getAsyncOrSync(
|
|
62863
|
+
peek.adapter,
|
|
62864
|
+
'SELECT "entities_json","contexts_json" FROM "__lattice_shared_schema" WHERE "id" = $1',
|
|
62865
|
+
["singleton"]
|
|
62866
|
+
);
|
|
62867
|
+
if (row?.entities_json == null) return false;
|
|
62868
|
+
const entities = JSON.parse(row.entities_json);
|
|
62869
|
+
if (Object.keys(entities).length === 0) return false;
|
|
62870
|
+
const doc = loadConfigDoc(configPath);
|
|
62871
|
+
doc.setIn(["entities"], entities);
|
|
62872
|
+
if (row.contexts_json != null) {
|
|
62873
|
+
const ctx = JSON.parse(row.contexts_json);
|
|
62874
|
+
if (ctx) doc.setIn(["entityContexts"], ctx);
|
|
62875
|
+
}
|
|
62876
|
+
saveConfigDoc(configPath, doc);
|
|
62877
|
+
return true;
|
|
62878
|
+
} finally {
|
|
62879
|
+
peek.close();
|
|
62880
|
+
}
|
|
62881
|
+
} catch (e6) {
|
|
62882
|
+
console.warn(
|
|
62883
|
+
"[hydrateMemberConfigFromCloud] could not hydrate member schema:",
|
|
62884
|
+
e6.message
|
|
62885
|
+
);
|
|
62886
|
+
return false;
|
|
62887
|
+
}
|
|
62888
|
+
}
|
|
62889
|
+
|
|
62651
62890
|
// src/cloud/settings.ts
|
|
62652
62891
|
init_adapter();
|
|
62653
62892
|
init_rls();
|
|
@@ -62751,7 +62990,7 @@ var MEMBER_READABLE_BOOKKEEPING = [
|
|
|
62751
62990
|
{
|
|
62752
62991
|
name: "_lattice_gui_audit",
|
|
62753
62992
|
privs: "SELECT, INSERT",
|
|
62754
|
-
why: "
|
|
62993
|
+
why: "GUI undo/redo + version history; RLS (enableGuiAuditRls) scopes reads to entries whose underlying row the member can see"
|
|
62755
62994
|
},
|
|
62756
62995
|
{
|
|
62757
62996
|
name: "__lattice_user_identity",
|
|
@@ -62762,6 +63001,11 @@ var MEMBER_READABLE_BOOKKEEPING = [
|
|
|
62762
63001
|
name: "__lattice_changelog",
|
|
62763
63002
|
privs: "SELECT, INSERT",
|
|
62764
63003
|
why: "per-viewer-RLS-filtered change history for observe()/history (the policy filters reads, so the base grant is safe)"
|
|
63004
|
+
},
|
|
63005
|
+
{
|
|
63006
|
+
name: "__lattice_shared_schema",
|
|
63007
|
+
privs: "SELECT",
|
|
63008
|
+
why: "owner-published entity/render layout (entities + entityContexts) a joined member hydrates its config from so render produces the full context tree"
|
|
62765
63009
|
}
|
|
62766
63010
|
];
|
|
62767
63011
|
var MEMBER_EXECUTE_FUNCTIONS = [
|
|
@@ -62913,9 +63157,12 @@ async function secureCloud(db) {
|
|
|
62913
63157
|
if (db.getDialect() !== "postgres") return;
|
|
62914
63158
|
await registerPostgresPolyfills((sql) => runAsyncOrSync(db.adapter, sql));
|
|
62915
63159
|
await installCloudRls(db);
|
|
63160
|
+
await ownPolyfillsByGroup(db);
|
|
62916
63161
|
await installCloudSettings(db);
|
|
62917
63162
|
await db.ensureObservationSubstrate();
|
|
62918
63163
|
await enableChangelogRls(db);
|
|
63164
|
+
await enableChatPrivacyRls(db);
|
|
63165
|
+
await enableGuiAuditRls(db);
|
|
62919
63166
|
await convergeLegacyColumnAudience(db);
|
|
62920
63167
|
const registered = db.getRegisteredTableNames();
|
|
62921
63168
|
for (const table of registered) {
|
|
@@ -63012,21 +63259,6 @@ var FeedBus = class {
|
|
|
63012
63259
|
init_fts();
|
|
63013
63260
|
init_mutations();
|
|
63014
63261
|
|
|
63015
|
-
// src/gui/config-io.ts
|
|
63016
|
-
import { readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
63017
|
-
import { parseDocument as parseDocument2 } from "yaml";
|
|
63018
|
-
async function execSql(db, sql) {
|
|
63019
|
-
const adapter = db._adapter;
|
|
63020
|
-
if (!adapter.runAsync) throw new Error("Adapter does not support runAsync");
|
|
63021
|
-
await adapter.runAsync(sql);
|
|
63022
|
-
}
|
|
63023
|
-
function loadConfigDoc(configPath) {
|
|
63024
|
-
return parseDocument2(readFileSync14(configPath, "utf8"));
|
|
63025
|
-
}
|
|
63026
|
-
function saveConfigDoc(configPath, doc) {
|
|
63027
|
-
writeFileSync6(configPath, doc.toString(), "utf8");
|
|
63028
|
-
}
|
|
63029
|
-
|
|
63030
63262
|
// src/gui/schema-ops.ts
|
|
63031
63263
|
init_parser();
|
|
63032
63264
|
init_canonical_context();
|
|
@@ -64562,6 +64794,7 @@ async function dispatchDbConfigRoute(req, res, ctx) {
|
|
|
64562
64794
|
const result = await migrateLatticeData(ctx.db, target);
|
|
64563
64795
|
await target.rebuildFtsIndexes();
|
|
64564
64796
|
await secureCloud(target);
|
|
64797
|
+
await publishSharedSchema(target, ctx.configPath);
|
|
64565
64798
|
target.close();
|
|
64566
64799
|
const sourceDbPath = parseConfigFile(ctx.configPath).dbPath;
|
|
64567
64800
|
const backupPath = archiveLocalSqlite(sourceDbPath);
|
|
@@ -65024,6 +65257,7 @@ function activeCloudCoords(configPath) {
|
|
|
65024
65257
|
init_assistant_routes();
|
|
65025
65258
|
|
|
65026
65259
|
// src/gui/chat-routes.ts
|
|
65260
|
+
init_adapter();
|
|
65027
65261
|
init_assistant_routes();
|
|
65028
65262
|
init_chat();
|
|
65029
65263
|
init_user_config();
|
|
@@ -65049,6 +65283,15 @@ function sendJson3(res, body, status = 200) {
|
|
|
65049
65283
|
function asStr(v2, fallback = "") {
|
|
65050
65284
|
return typeof v2 === "string" ? v2 : fallback;
|
|
65051
65285
|
}
|
|
65286
|
+
function isCloudChat(db) {
|
|
65287
|
+
return db.getDialect() === "postgres";
|
|
65288
|
+
}
|
|
65289
|
+
async function resolveChatOwnerId(db) {
|
|
65290
|
+
if (!isCloudChat(db)) return null;
|
|
65291
|
+
const row = await getAsyncOrSync(db.adapter, "SELECT session_user AS u");
|
|
65292
|
+
const u2 = row?.u;
|
|
65293
|
+
return typeof u2 === "string" && u2.length > 0 ? u2 : null;
|
|
65294
|
+
}
|
|
65052
65295
|
function readJson3(req) {
|
|
65053
65296
|
return new Promise((resolve12, reject) => {
|
|
65054
65297
|
let raw = "";
|
|
@@ -65241,12 +65484,20 @@ async function persistMessage(db, threadId, role, text, ownerUserId, turns, star
|
|
|
65241
65484
|
});
|
|
65242
65485
|
}
|
|
65243
65486
|
async function dispatchChatRoute(req, res, ctx) {
|
|
65487
|
+
const ownerUserId = await resolveChatOwnerId(ctx.db);
|
|
65488
|
+
const cloud = isCloudChat(ctx.db);
|
|
65489
|
+
const ownedByMe = (r6) => !cloud || r6.owner_user_id != null && r6.owner_user_id === ownerUserId;
|
|
65244
65490
|
if (ctx.method === "GET" && ctx.pathname === "/api/chat/threads") {
|
|
65491
|
+
if (cloud && ownerUserId == null) {
|
|
65492
|
+
sendJson3(res, { threads: [] });
|
|
65493
|
+
return true;
|
|
65494
|
+
}
|
|
65245
65495
|
const filters = [
|
|
65246
65496
|
{ col: "deleted_at", op: "isNull" }
|
|
65247
65497
|
];
|
|
65498
|
+
if (ownerUserId != null) filters.push({ col: "owner_user_id", op: "eq", val: ownerUserId });
|
|
65248
65499
|
const rows = await ctx.db.query("chat_threads", { filters, limit: 100 });
|
|
65249
|
-
const threads = rows.filter((r6) => !r6.deleted_at).map((r6) => ({
|
|
65500
|
+
const threads = rows.filter((r6) => !r6.deleted_at && ownedByMe(r6)).map((r6) => ({
|
|
65250
65501
|
id: asStr(r6.id),
|
|
65251
65502
|
title: asStr(r6.title, "Chat"),
|
|
65252
65503
|
created_at: asStr(r6.created_at)
|
|
@@ -65257,12 +65508,19 @@ async function dispatchChatRoute(req, res, ctx) {
|
|
|
65257
65508
|
const msgMatch = /^\/api\/chat\/threads\/([^/]+)\/messages$/.exec(ctx.pathname);
|
|
65258
65509
|
if (ctx.method === "GET" && msgMatch) {
|
|
65259
65510
|
const threadId2 = decodeURIComponent(msgMatch[1] ?? "");
|
|
65260
|
-
|
|
65511
|
+
if (cloud && ownerUserId == null) {
|
|
65512
|
+
sendJson3(res, { messages: [] });
|
|
65513
|
+
return true;
|
|
65514
|
+
}
|
|
65515
|
+
const msgFilters = [
|
|
65516
|
+
{ col: "thread_id", op: "eq", val: threadId2 }
|
|
65517
|
+
];
|
|
65518
|
+
if (ownerUserId != null) msgFilters.push({ col: "owner_user_id", op: "eq", val: ownerUserId });
|
|
65261
65519
|
const rows = await ctx.db.query("chat_messages", {
|
|
65262
65520
|
filters: msgFilters,
|
|
65263
65521
|
limit: 1e3
|
|
65264
65522
|
});
|
|
65265
|
-
const messages = rows.filter((r6) => r6.thread_id === threadId2 && !r6.deleted_at).map((r6) => {
|
|
65523
|
+
const messages = rows.filter((r6) => r6.thread_id === threadId2 && !r6.deleted_at && ownedByMe(r6)).map((r6) => {
|
|
65266
65524
|
let text = "";
|
|
65267
65525
|
let turns2;
|
|
65268
65526
|
let startedAt;
|
|
@@ -65315,12 +65573,21 @@ async function dispatchChatRoute(req, res, ctx) {
|
|
|
65315
65573
|
return true;
|
|
65316
65574
|
}
|
|
65317
65575
|
const requestedThread = typeof body.threadId === "string" ? body.threadId : null;
|
|
65576
|
+
if (cloud && ownerUserId == null) {
|
|
65577
|
+
sendJson3(res, { error: "Could not resolve your cloud identity; chat is disabled." }, 500);
|
|
65578
|
+
return true;
|
|
65579
|
+
}
|
|
65318
65580
|
const activeContext = parseActiveContext(body.activeContext, ctx.validTables);
|
|
65319
|
-
const history = await rehydrateHistory(
|
|
65581
|
+
const history = await rehydrateHistory(
|
|
65582
|
+
ctx.db,
|
|
65583
|
+
requestedThread,
|
|
65584
|
+
mapHistory(body.history),
|
|
65585
|
+
ownerUserId
|
|
65586
|
+
);
|
|
65320
65587
|
let threadId = "";
|
|
65321
65588
|
try {
|
|
65322
|
-
threadId = await ensureThread(ctx.db, requestedThread, message,
|
|
65323
|
-
await persistMessage(ctx.db, threadId, "user", message,
|
|
65589
|
+
threadId = await ensureThread(ctx.db, requestedThread, message, ownerUserId);
|
|
65590
|
+
await persistMessage(ctx.db, threadId, "user", message, ownerUserId);
|
|
65324
65591
|
} catch (e6) {
|
|
65325
65592
|
console.warn("[chat] persist user message failed:", e6.message);
|
|
65326
65593
|
}
|
|
@@ -65392,7 +65659,7 @@ async function dispatchChatRoute(req, res, ctx) {
|
|
|
65392
65659
|
threadId,
|
|
65393
65660
|
"assistant",
|
|
65394
65661
|
assistantText,
|
|
65395
|
-
|
|
65662
|
+
ownerUserId,
|
|
65396
65663
|
cleanTurns,
|
|
65397
65664
|
turnStartedAt,
|
|
65398
65665
|
assistantMsgId
|
|
@@ -66386,10 +66653,13 @@ function resolveOutputDirForConfig(configPath) {
|
|
|
66386
66653
|
async function openConfig(configPath, outputDir, autoRender = false, realtimeWatchdogMs) {
|
|
66387
66654
|
healRawDbUrl(configPath);
|
|
66388
66655
|
const parsed = parseConfigFile(configPath);
|
|
66656
|
+
const encryptionKey = getOrCreateMasterKey();
|
|
66389
66657
|
if (!/^postgres(ql)?:\/\//i.test(parsed.dbPath) && !parsed.dbPath.startsWith("file:") && parsed.dbPath !== ":memory:") {
|
|
66390
66658
|
mkdirSync11(dirname14(parsed.dbPath), { recursive: true });
|
|
66391
66659
|
}
|
|
66392
|
-
|
|
66660
|
+
if (/^postgres(ql)?:\/\//i.test(parsed.dbPath)) {
|
|
66661
|
+
await hydrateMemberConfigFromCloud(configPath, parsed.dbPath, encryptionKey);
|
|
66662
|
+
}
|
|
66393
66663
|
const db = new Lattice({ config: configPath }, { encryptionKey });
|
|
66394
66664
|
registerNativeEntities(db);
|
|
66395
66665
|
db.define("_lattice_gui_meta", {
|
|
@@ -66541,19 +66811,32 @@ async function openConfig(configPath, outputDir, autoRender = false, realtimeWat
|
|
|
66541
66811
|
if (await cloudRlsInstalled(db) && await canManageRoles(db)) {
|
|
66542
66812
|
await registerPostgresPolyfills((sql) => runAsyncOrSync(db.adapter, sql));
|
|
66543
66813
|
await installCloudRls(db);
|
|
66814
|
+
await ownPolyfillsByGroup(db);
|
|
66544
66815
|
await installCloudSettings(db);
|
|
66545
66816
|
await db.ensureObservationSubstrate();
|
|
66546
66817
|
await enableChangelogRls(db);
|
|
66818
|
+
await enableChatPrivacyRls(db);
|
|
66819
|
+
await enableGuiAuditRls(db);
|
|
66547
66820
|
const access = await reconcileCloudMemberAccess(db);
|
|
66548
66821
|
convergeWarnings = access.skipped;
|
|
66549
66822
|
for (const s2 of convergeWarnings) {
|
|
66550
66823
|
console.warn(`[openConfig] cloud converge could not manage "${s2.table}": ${s2.reason}`);
|
|
66551
66824
|
}
|
|
66825
|
+
await publishSharedSchema(db, configPath);
|
|
66552
66826
|
}
|
|
66553
66827
|
} catch (e6) {
|
|
66554
66828
|
console.error("[openConfig] cloud bootstrap converge failed:", e6.message);
|
|
66555
66829
|
}
|
|
66556
66830
|
}
|
|
66831
|
+
if (memberOpen) {
|
|
66832
|
+
const userTables = db.getRegisteredTableNames().filter((t8) => !t8.startsWith("_") && !discoveredJunctions.has(t8));
|
|
66833
|
+
if (userTables.length === 0) {
|
|
66834
|
+
convergeWarnings.push({
|
|
66835
|
+
table: "(schema)",
|
|
66836
|
+
reason: "No entity layout is configured for this cloud workspace yet \u2014 ask the cloud owner to open the workspace once so it publishes the schema, then reopen. Until then, render produces no context files."
|
|
66837
|
+
});
|
|
66838
|
+
}
|
|
66839
|
+
}
|
|
66557
66840
|
const validTables = new Set(parsed.tables.map((t8) => t8.name));
|
|
66558
66841
|
for (const name of db.getRegisteredTableNames()) {
|
|
66559
66842
|
if (name.startsWith("__lattice_") || name.startsWith("_lattice_")) continue;
|
|
@@ -66705,11 +66988,22 @@ function startBackgroundRender(active) {
|
|
|
66705
66988
|
active.eagerRenderWired = true;
|
|
66706
66989
|
let lastFire = 0;
|
|
66707
66990
|
let trailing;
|
|
66991
|
+
const pendingTables = /* @__PURE__ */ new Set();
|
|
66992
|
+
let pendingFull = false;
|
|
66708
66993
|
const fire = () => {
|
|
66709
66994
|
lastFire = Date.now();
|
|
66710
|
-
|
|
66995
|
+
if (pendingFull || pendingTables.size === 0) {
|
|
66996
|
+
pendingFull = false;
|
|
66997
|
+
pendingTables.clear();
|
|
66998
|
+
active.db.requestRender();
|
|
66999
|
+
return;
|
|
67000
|
+
}
|
|
67001
|
+
for (const t8 of pendingTables) active.db.requestRender(t8);
|
|
67002
|
+
pendingTables.clear();
|
|
66711
67003
|
};
|
|
66712
|
-
active.realtime.subscribePayload(() => {
|
|
67004
|
+
active.realtime.subscribePayload((payload) => {
|
|
67005
|
+
if (payload.table_name) pendingTables.add(payload.table_name);
|
|
67006
|
+
else pendingFull = true;
|
|
66713
67007
|
const since = Date.now() - lastFire;
|
|
66714
67008
|
if (since >= EAGER_RERENDER_MIN_INTERVAL_MS) {
|
|
66715
67009
|
fire();
|
|
@@ -69130,7 +69424,7 @@ function printHelp() {
|
|
|
69130
69424
|
);
|
|
69131
69425
|
}
|
|
69132
69426
|
function getVersion() {
|
|
69133
|
-
if (true) return "3.4.
|
|
69427
|
+
if (true) return "3.4.2";
|
|
69134
69428
|
try {
|
|
69135
69429
|
const pkgPath = new URL("../package.json", import.meta.url).pathname;
|
|
69136
69430
|
const pkg = JSON.parse(readFileSync20(pkgPath, "utf-8"));
|
|
@@ -69198,14 +69492,17 @@ function runGenerate(args) {
|
|
|
69198
69492
|
}
|
|
69199
69493
|
async function runRender(args) {
|
|
69200
69494
|
const outputDir = resolve11(args.output);
|
|
69495
|
+
const configPath = resolve11(args.config);
|
|
69201
69496
|
let parsed;
|
|
69202
69497
|
try {
|
|
69203
|
-
parsed = parseConfigFile(
|
|
69498
|
+
parsed = parseConfigFile(configPath);
|
|
69204
69499
|
} catch (e6) {
|
|
69205
69500
|
console.error(`Error: ${e6.message}`);
|
|
69206
69501
|
process.exit(1);
|
|
69207
69502
|
}
|
|
69208
|
-
const
|
|
69503
|
+
const encryptionKey = getOrCreateMasterKey();
|
|
69504
|
+
await hydrateMemberConfigFromCloud(configPath, parsed.dbPath, encryptionKey);
|
|
69505
|
+
const db = new Lattice({ config: configPath }, { encryptionKey });
|
|
69209
69506
|
try {
|
|
69210
69507
|
await db.init();
|
|
69211
69508
|
const start = Date.now();
|
|
@@ -69221,7 +69518,6 @@ async function runRender(args) {
|
|
|
69221
69518
|
} finally {
|
|
69222
69519
|
db.close();
|
|
69223
69520
|
}
|
|
69224
|
-
void parsed;
|
|
69225
69521
|
}
|
|
69226
69522
|
async function runReconcile(args, isDryRun) {
|
|
69227
69523
|
const outputDir = resolve11(args.output);
|