latticesql 3.4.0 → 3.4.1
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 +225 -57
- package/dist/index.cjs +224 -56
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +224 -56
- 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,17 @@ 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
|
+
}
|
|
8038
8134
|
async function installCloudRls(db) {
|
|
8039
8135
|
if (!isPg(db)) return;
|
|
8040
8136
|
const schema = await cloudSchema(db);
|
|
@@ -8068,6 +8164,24 @@ CREATE POLICY "lattice_changelog_ins" ON "__lattice_changelog" FOR INSERT WITH C
|
|
|
8068
8164
|
`
|
|
8069
8165
|
);
|
|
8070
8166
|
}
|
|
8167
|
+
async function enableChatPrivacyRls(db) {
|
|
8168
|
+
if (!isPg(db)) return;
|
|
8169
|
+
for (const t8 of ["chat_threads", "chat_messages"]) {
|
|
8170
|
+
const reg = await getAsyncOrSync(db.adapter, `SELECT to_regclass($1) AS reg`, [t8]);
|
|
8171
|
+
if (reg?.reg == null) continue;
|
|
8172
|
+
const q3 = `"${t8}"`;
|
|
8173
|
+
await runCloudBootstrapSql(
|
|
8174
|
+
db,
|
|
8175
|
+
`
|
|
8176
|
+
ALTER TABLE ${q3} ENABLE ROW LEVEL SECURITY;
|
|
8177
|
+
ALTER TABLE ${q3} FORCE ROW LEVEL SECURITY;
|
|
8178
|
+
DROP POLICY IF EXISTS "lattice_chat_owner" ON ${q3};
|
|
8179
|
+
CREATE POLICY "lattice_chat_owner" ON ${q3} AS RESTRICTIVE FOR SELECT
|
|
8180
|
+
USING ("owner_user_id" IS NOT NULL AND "owner_user_id" = session_user);
|
|
8181
|
+
`
|
|
8182
|
+
);
|
|
8183
|
+
}
|
|
8184
|
+
}
|
|
8071
8185
|
async function enableRlsForTable(db, table, pkCols) {
|
|
8072
8186
|
if (!isPg(db)) return;
|
|
8073
8187
|
const schema = await cloudSchema(db);
|
|
@@ -55860,12 +55974,17 @@ var appJs = `
|
|
|
55860
55974
|
fetchJson('/api/gui-meta/columns').catch(function () { return {}; }),
|
|
55861
55975
|
fetchJson('/api/system-tables').catch(function () { return { tables: [] }; }),
|
|
55862
55976
|
fetchJson('/api/workspaces').catch(function () { return null; }),
|
|
55977
|
+
fetchJson('/api/dbconfig').catch(function () { return {}; }),
|
|
55863
55978
|
]).then(function (results) {
|
|
55864
55979
|
state.entities = results[0];
|
|
55865
55980
|
state.iconOverrides = results[1] || {};
|
|
55866
55981
|
state.columnMeta = results[2] || {};
|
|
55867
55982
|
state.systemTables = (results[3] && results[3].tables) || [];
|
|
55868
55983
|
renderWsSwitcher(results[4]);
|
|
55984
|
+
// Re-point the header logo at the NEW workspace's mark \u2014 the switch path
|
|
55985
|
+
// must refresh branding the way boot does (the etag cache-busts the
|
|
55986
|
+
// <img>), else the previous workspace's logo stays until a hard refresh.
|
|
55987
|
+
applyWorkspaceLogo((results[5] || {}).logoEtag);
|
|
55869
55988
|
renderSidebar();
|
|
55870
55989
|
// renderWsSwitcher set cloudMode from the new workspace's kind; re-render
|
|
55871
55990
|
// the composer so the Private-mode toggle reflects local vs cloud (it is
|
|
@@ -62913,9 +63032,11 @@ async function secureCloud(db) {
|
|
|
62913
63032
|
if (db.getDialect() !== "postgres") return;
|
|
62914
63033
|
await registerPostgresPolyfills((sql) => runAsyncOrSync(db.adapter, sql));
|
|
62915
63034
|
await installCloudRls(db);
|
|
63035
|
+
await ownPolyfillsByGroup(db);
|
|
62916
63036
|
await installCloudSettings(db);
|
|
62917
63037
|
await db.ensureObservationSubstrate();
|
|
62918
63038
|
await enableChangelogRls(db);
|
|
63039
|
+
await enableChatPrivacyRls(db);
|
|
62919
63040
|
await convergeLegacyColumnAudience(db);
|
|
62920
63041
|
const registered = db.getRegisteredTableNames();
|
|
62921
63042
|
for (const table of registered) {
|
|
@@ -65024,6 +65145,7 @@ function activeCloudCoords(configPath) {
|
|
|
65024
65145
|
init_assistant_routes();
|
|
65025
65146
|
|
|
65026
65147
|
// src/gui/chat-routes.ts
|
|
65148
|
+
init_adapter();
|
|
65027
65149
|
init_assistant_routes();
|
|
65028
65150
|
init_chat();
|
|
65029
65151
|
init_user_config();
|
|
@@ -65049,6 +65171,15 @@ function sendJson3(res, body, status = 200) {
|
|
|
65049
65171
|
function asStr(v2, fallback = "") {
|
|
65050
65172
|
return typeof v2 === "string" ? v2 : fallback;
|
|
65051
65173
|
}
|
|
65174
|
+
function isCloudChat(db) {
|
|
65175
|
+
return db.getDialect() === "postgres";
|
|
65176
|
+
}
|
|
65177
|
+
async function resolveChatOwnerId(db) {
|
|
65178
|
+
if (!isCloudChat(db)) return null;
|
|
65179
|
+
const row = await getAsyncOrSync(db.adapter, "SELECT session_user AS u");
|
|
65180
|
+
const u2 = row?.u;
|
|
65181
|
+
return typeof u2 === "string" && u2.length > 0 ? u2 : null;
|
|
65182
|
+
}
|
|
65052
65183
|
function readJson3(req) {
|
|
65053
65184
|
return new Promise((resolve12, reject) => {
|
|
65054
65185
|
let raw = "";
|
|
@@ -65241,12 +65372,20 @@ async function persistMessage(db, threadId, role, text, ownerUserId, turns, star
|
|
|
65241
65372
|
});
|
|
65242
65373
|
}
|
|
65243
65374
|
async function dispatchChatRoute(req, res, ctx) {
|
|
65375
|
+
const ownerUserId = await resolveChatOwnerId(ctx.db);
|
|
65376
|
+
const cloud = isCloudChat(ctx.db);
|
|
65377
|
+
const ownedByMe = (r6) => !cloud || r6.owner_user_id != null && r6.owner_user_id === ownerUserId;
|
|
65244
65378
|
if (ctx.method === "GET" && ctx.pathname === "/api/chat/threads") {
|
|
65379
|
+
if (cloud && ownerUserId == null) {
|
|
65380
|
+
sendJson3(res, { threads: [] });
|
|
65381
|
+
return true;
|
|
65382
|
+
}
|
|
65245
65383
|
const filters = [
|
|
65246
65384
|
{ col: "deleted_at", op: "isNull" }
|
|
65247
65385
|
];
|
|
65386
|
+
if (ownerUserId != null) filters.push({ col: "owner_user_id", op: "eq", val: ownerUserId });
|
|
65248
65387
|
const rows = await ctx.db.query("chat_threads", { filters, limit: 100 });
|
|
65249
|
-
const threads = rows.filter((r6) => !r6.deleted_at).map((r6) => ({
|
|
65388
|
+
const threads = rows.filter((r6) => !r6.deleted_at && ownedByMe(r6)).map((r6) => ({
|
|
65250
65389
|
id: asStr(r6.id),
|
|
65251
65390
|
title: asStr(r6.title, "Chat"),
|
|
65252
65391
|
created_at: asStr(r6.created_at)
|
|
@@ -65257,12 +65396,19 @@ async function dispatchChatRoute(req, res, ctx) {
|
|
|
65257
65396
|
const msgMatch = /^\/api\/chat\/threads\/([^/]+)\/messages$/.exec(ctx.pathname);
|
|
65258
65397
|
if (ctx.method === "GET" && msgMatch) {
|
|
65259
65398
|
const threadId2 = decodeURIComponent(msgMatch[1] ?? "");
|
|
65260
|
-
|
|
65399
|
+
if (cloud && ownerUserId == null) {
|
|
65400
|
+
sendJson3(res, { messages: [] });
|
|
65401
|
+
return true;
|
|
65402
|
+
}
|
|
65403
|
+
const msgFilters = [
|
|
65404
|
+
{ col: "thread_id", op: "eq", val: threadId2 }
|
|
65405
|
+
];
|
|
65406
|
+
if (ownerUserId != null) msgFilters.push({ col: "owner_user_id", op: "eq", val: ownerUserId });
|
|
65261
65407
|
const rows = await ctx.db.query("chat_messages", {
|
|
65262
65408
|
filters: msgFilters,
|
|
65263
65409
|
limit: 1e3
|
|
65264
65410
|
});
|
|
65265
|
-
const messages = rows.filter((r6) => r6.thread_id === threadId2 && !r6.deleted_at).map((r6) => {
|
|
65411
|
+
const messages = rows.filter((r6) => r6.thread_id === threadId2 && !r6.deleted_at && ownedByMe(r6)).map((r6) => {
|
|
65266
65412
|
let text = "";
|
|
65267
65413
|
let turns2;
|
|
65268
65414
|
let startedAt;
|
|
@@ -65315,12 +65461,21 @@ async function dispatchChatRoute(req, res, ctx) {
|
|
|
65315
65461
|
return true;
|
|
65316
65462
|
}
|
|
65317
65463
|
const requestedThread = typeof body.threadId === "string" ? body.threadId : null;
|
|
65464
|
+
if (cloud && ownerUserId == null) {
|
|
65465
|
+
sendJson3(res, { error: "Could not resolve your cloud identity; chat is disabled." }, 500);
|
|
65466
|
+
return true;
|
|
65467
|
+
}
|
|
65318
65468
|
const activeContext = parseActiveContext(body.activeContext, ctx.validTables);
|
|
65319
|
-
const history = await rehydrateHistory(
|
|
65469
|
+
const history = await rehydrateHistory(
|
|
65470
|
+
ctx.db,
|
|
65471
|
+
requestedThread,
|
|
65472
|
+
mapHistory(body.history),
|
|
65473
|
+
ownerUserId
|
|
65474
|
+
);
|
|
65320
65475
|
let threadId = "";
|
|
65321
65476
|
try {
|
|
65322
|
-
threadId = await ensureThread(ctx.db, requestedThread, message,
|
|
65323
|
-
await persistMessage(ctx.db, threadId, "user", message,
|
|
65477
|
+
threadId = await ensureThread(ctx.db, requestedThread, message, ownerUserId);
|
|
65478
|
+
await persistMessage(ctx.db, threadId, "user", message, ownerUserId);
|
|
65324
65479
|
} catch (e6) {
|
|
65325
65480
|
console.warn("[chat] persist user message failed:", e6.message);
|
|
65326
65481
|
}
|
|
@@ -65392,7 +65547,7 @@ async function dispatchChatRoute(req, res, ctx) {
|
|
|
65392
65547
|
threadId,
|
|
65393
65548
|
"assistant",
|
|
65394
65549
|
assistantText,
|
|
65395
|
-
|
|
65550
|
+
ownerUserId,
|
|
65396
65551
|
cleanTurns,
|
|
65397
65552
|
turnStartedAt,
|
|
65398
65553
|
assistantMsgId
|
|
@@ -66541,9 +66696,11 @@ async function openConfig(configPath, outputDir, autoRender = false, realtimeWat
|
|
|
66541
66696
|
if (await cloudRlsInstalled(db) && await canManageRoles(db)) {
|
|
66542
66697
|
await registerPostgresPolyfills((sql) => runAsyncOrSync(db.adapter, sql));
|
|
66543
66698
|
await installCloudRls(db);
|
|
66699
|
+
await ownPolyfillsByGroup(db);
|
|
66544
66700
|
await installCloudSettings(db);
|
|
66545
66701
|
await db.ensureObservationSubstrate();
|
|
66546
66702
|
await enableChangelogRls(db);
|
|
66703
|
+
await enableChatPrivacyRls(db);
|
|
66547
66704
|
const access = await reconcileCloudMemberAccess(db);
|
|
66548
66705
|
convergeWarnings = access.skipped;
|
|
66549
66706
|
for (const s2 of convergeWarnings) {
|
|
@@ -66705,11 +66862,22 @@ function startBackgroundRender(active) {
|
|
|
66705
66862
|
active.eagerRenderWired = true;
|
|
66706
66863
|
let lastFire = 0;
|
|
66707
66864
|
let trailing;
|
|
66865
|
+
const pendingTables = /* @__PURE__ */ new Set();
|
|
66866
|
+
let pendingFull = false;
|
|
66708
66867
|
const fire = () => {
|
|
66709
66868
|
lastFire = Date.now();
|
|
66710
|
-
|
|
66869
|
+
if (pendingFull || pendingTables.size === 0) {
|
|
66870
|
+
pendingFull = false;
|
|
66871
|
+
pendingTables.clear();
|
|
66872
|
+
active.db.requestRender();
|
|
66873
|
+
return;
|
|
66874
|
+
}
|
|
66875
|
+
for (const t8 of pendingTables) active.db.requestRender(t8);
|
|
66876
|
+
pendingTables.clear();
|
|
66711
66877
|
};
|
|
66712
|
-
active.realtime.subscribePayload(() => {
|
|
66878
|
+
active.realtime.subscribePayload((payload) => {
|
|
66879
|
+
if (payload.table_name) pendingTables.add(payload.table_name);
|
|
66880
|
+
else pendingFull = true;
|
|
66713
66881
|
const since = Date.now() - lastFire;
|
|
66714
66882
|
if (since >= EAGER_RERENDER_MIN_INTERVAL_MS) {
|
|
66715
66883
|
fire();
|
|
@@ -69130,7 +69298,7 @@ function printHelp() {
|
|
|
69130
69298
|
);
|
|
69131
69299
|
}
|
|
69132
69300
|
function getVersion() {
|
|
69133
|
-
if (true) return "3.4.
|
|
69301
|
+
if (true) return "3.4.1";
|
|
69134
69302
|
try {
|
|
69135
69303
|
const pkgPath = new URL("../package.json", import.meta.url).pathname;
|
|
69136
69304
|
const pkg = JSON.parse(readFileSync20(pkgPath, "utf-8"));
|