latticesql 1.13.5 → 1.13.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
  });
8
8
 
9
9
  // src/cli.ts
10
- import { resolve as resolve9, dirname as dirname7 } from "path";
10
+ import { resolve as resolve9, dirname as dirname8 } from "path";
11
11
  import { readFileSync as readFileSync12 } from "fs";
12
12
  import { execSync } from "child_process";
13
13
  import { parse as parse2 } from "yaml";
@@ -189,6 +189,23 @@ function saveDbCredential(label, url) {
189
189
  creds[label] = url;
190
190
  saveCredentials(creds);
191
191
  }
192
+ function sanitizeTeamLabel(teamName) {
193
+ const stripped = teamName.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]+/g, "");
194
+ if (stripped.length === 0) return "team";
195
+ return stripped.startsWith(".") ? `team-${stripped}` : stripped;
196
+ }
197
+ function saveDbCredentialForTeam(opts) {
198
+ const base = sanitizeTeamLabel(opts.teamName);
199
+ let label = `${base}.config`;
200
+ const creds = loadCredentials();
201
+ if (label in creds && creds[label] !== opts.cloudUrl) {
202
+ const shortId = opts.teamId.split("-")[0] ?? opts.teamId.slice(0, 8);
203
+ label = `${base}-${shortId}.config`;
204
+ }
205
+ creds[label] = opts.cloudUrl;
206
+ saveCredentials(creds);
207
+ return label;
208
+ }
192
209
  var KEYS_SUBDIR = "keys";
193
210
  var TOKEN_EXT = ".token";
194
211
  function ensureKeysDir() {
@@ -4962,8 +4979,8 @@ async function checkForUpdate(pkgName, currentVersion) {
4962
4979
  // src/gui/server.ts
4963
4980
  import { createServer } from "http";
4964
4981
  import { spawn } from "child_process";
4965
- import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync11, readdirSync as readdirSync6, writeFileSync as writeFileSync6 } from "fs";
4966
- import { basename as basename6, dirname as dirname6, join as join13, resolve as resolve6, sep as sep3 } from "path";
4982
+ import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync11, readdirSync as readdirSync6, writeFileSync as writeFileSync7 } from "fs";
4983
+ import { basename as basename6, dirname as dirname7, join as join14, resolve as resolve6, sep as sep3 } from "path";
4967
4984
  import { parseDocument as parseDocument2 } from "yaml";
4968
4985
 
4969
4986
  // src/gui/data.ts
@@ -9930,7 +9947,7 @@ async function handleDeleteRow(res, ctx, teamId, tableName, pk) {
9930
9947
  }
9931
9948
 
9932
9949
  // src/teams/client.ts
9933
- import { randomUUID } from "crypto";
9950
+ import { randomUUID as randomUUID2 } from "crypto";
9934
9951
 
9935
9952
  // src/framework/cloud-connect.ts
9936
9953
  async function probeCloud(targetUrl) {
@@ -10055,6 +10072,7 @@ async function registerDirectViaPostgres(cloudUrl, email, name, teamName) {
10055
10072
  }
10056
10073
 
10057
10074
  // src/teams/direct-ops.ts
10075
+ import { randomUUID } from "crypto";
10058
10076
  async function listMembersDirect(db, teamId) {
10059
10077
  const members = await db.query("__lattice_team_members", {
10060
10078
  filters: [{ col: "team_id", op: "eq", val: teamId }]
@@ -10190,6 +10208,224 @@ async function redeemInviteDirect(cloudUrl, inviteToken, email, name) {
10190
10208
  }
10191
10209
  }
10192
10210
  }
10211
+ async function openCloud(cloudUrl) {
10212
+ if (!isPostgresUrl(cloudUrl)) {
10213
+ throw new Error(
10214
+ `direct-ops: cloudUrl must be a postgres:// URL (got ${cloudUrl.slice(0, 12)}\u2026)`
10215
+ );
10216
+ }
10217
+ const db = new Lattice(cloudUrl);
10218
+ await db.init();
10219
+ for (const [table, def] of Object.entries(CLOUD_INTERNAL_TABLE_DEFS)) {
10220
+ await db.defineLate(table, def);
10221
+ }
10222
+ return db;
10223
+ }
10224
+ function closeQuiet(db) {
10225
+ try {
10226
+ db.close();
10227
+ } catch {
10228
+ }
10229
+ }
10230
+ async function appendChangeEnvelopeDirect(db, args) {
10231
+ const existing = await db.query("__lattice_change_log", {
10232
+ filters: [{ col: "team_id", op: "eq", val: args.team_id }],
10233
+ orderBy: "seq",
10234
+ orderDir: "desc",
10235
+ limit: 1
10236
+ });
10237
+ const nextSeq = (existing[0]?.seq ?? 0) + 1;
10238
+ await db.insert("__lattice_change_log", {
10239
+ id: randomUUID(),
10240
+ seq: nextSeq,
10241
+ team_id: args.team_id,
10242
+ table_name: args.table_name,
10243
+ pk: args.pk,
10244
+ op: args.op,
10245
+ payload_json: args.payload_json,
10246
+ owner_user_id: args.owner_user_id,
10247
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
10248
+ });
10249
+ return nextSeq;
10250
+ }
10251
+ async function shareObjectDirect(cloudUrl, teamId, inviterUserId, table, spec) {
10252
+ const db = await openCloud(cloudUrl);
10253
+ try {
10254
+ const existing = await db.query("__lattice_shared_objects", {
10255
+ filters: [
10256
+ { col: "team_id", op: "eq", val: teamId },
10257
+ { col: "table_name", op: "eq", val: table }
10258
+ ],
10259
+ limit: 1
10260
+ });
10261
+ const prior = existing[0];
10262
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10263
+ let schemaVersion;
10264
+ let outSpec;
10265
+ if (prior && !prior.deleted_at) {
10266
+ schemaVersion = prior.schema_version + 1;
10267
+ outSpec = { ...spec, schemaVersion };
10268
+ await db.upsert("__lattice_shared_objects", {
10269
+ team_id: teamId,
10270
+ table_name: table,
10271
+ schema_spec_json: JSON.stringify(outSpec),
10272
+ schema_version: schemaVersion,
10273
+ created_by_user_id: prior.created_by_user_id,
10274
+ created_at: prior.created_at,
10275
+ updated_at: now,
10276
+ deleted_at: null
10277
+ });
10278
+ } else {
10279
+ schemaVersion = 1;
10280
+ outSpec = { ...spec, schemaVersion };
10281
+ await db.upsert("__lattice_shared_objects", {
10282
+ team_id: teamId,
10283
+ table_name: table,
10284
+ schema_spec_json: JSON.stringify(outSpec),
10285
+ schema_version: schemaVersion,
10286
+ created_by_user_id: inviterUserId,
10287
+ created_at: prior?.created_at ?? now,
10288
+ updated_at: now,
10289
+ deleted_at: null
10290
+ });
10291
+ }
10292
+ await applySchemaSpec(db, table, outSpec);
10293
+ const seq = await appendChangeEnvelopeDirect(db, {
10294
+ team_id: teamId,
10295
+ table_name: table,
10296
+ pk: null,
10297
+ op: "schema",
10298
+ payload_json: JSON.stringify(outSpec),
10299
+ owner_user_id: null
10300
+ });
10301
+ return { table, schema_version: schemaVersion, seq, schema_spec: outSpec };
10302
+ } finally {
10303
+ closeQuiet(db);
10304
+ }
10305
+ }
10306
+ async function listSharedObjectsDirect(cloudUrl, teamId) {
10307
+ const db = await openCloud(cloudUrl);
10308
+ try {
10309
+ const rows = await db.query("__lattice_shared_objects", {
10310
+ filters: [
10311
+ { col: "team_id", op: "eq", val: teamId },
10312
+ { col: "deleted_at", op: "isNull" }
10313
+ ]
10314
+ });
10315
+ return rows.map((r) => ({
10316
+ table: r.table_name,
10317
+ schema_version: r.schema_version,
10318
+ created_by_user_id: r.created_by_user_id,
10319
+ created_at: r.created_at,
10320
+ updated_at: r.updated_at,
10321
+ schema_spec: JSON.parse(r.schema_spec_json)
10322
+ }));
10323
+ } finally {
10324
+ closeQuiet(db);
10325
+ }
10326
+ }
10327
+ async function unshareObjectDirect(cloudUrl, teamId, table) {
10328
+ const db = await openCloud(cloudUrl);
10329
+ try {
10330
+ const existing = await db.query("__lattice_shared_objects", {
10331
+ filters: [
10332
+ { col: "team_id", op: "eq", val: teamId },
10333
+ { col: "table_name", op: "eq", val: table },
10334
+ { col: "deleted_at", op: "isNull" }
10335
+ ],
10336
+ limit: 1
10337
+ });
10338
+ const row = existing[0];
10339
+ if (!row) return;
10340
+ await db.upsert("__lattice_shared_objects", {
10341
+ team_id: teamId,
10342
+ table_name: table,
10343
+ schema_spec_json: row.schema_spec_json,
10344
+ schema_version: row.schema_version,
10345
+ created_by_user_id: row.created_by_user_id,
10346
+ created_at: row.created_at,
10347
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
10348
+ deleted_at: (/* @__PURE__ */ new Date()).toISOString()
10349
+ });
10350
+ await appendChangeEnvelopeDirect(db, {
10351
+ team_id: teamId,
10352
+ table_name: table,
10353
+ pk: null,
10354
+ op: "unshare",
10355
+ payload_json: null,
10356
+ owner_user_id: null
10357
+ });
10358
+ } finally {
10359
+ closeQuiet(db);
10360
+ }
10361
+ }
10362
+ async function meDirect(cloudUrl, bearerToken) {
10363
+ const db = await openCloud(cloudUrl);
10364
+ try {
10365
+ const tokens = await db.query("__lattice_api_tokens", {
10366
+ filters: [{ col: "token_hash", op: "eq", val: hashToken(bearerToken) }],
10367
+ limit: 1
10368
+ });
10369
+ const tok = tokens[0];
10370
+ if (!tok || tok.revoked_at) throw new Error("Unauthorized");
10371
+ const user = await db.get("__lattice_users", tok.user_id);
10372
+ if (!user || user.deleted_at) throw new Error("Unauthorized");
10373
+ return { user: { id: user.id, email: user.email, name: user.name } };
10374
+ } finally {
10375
+ closeQuiet(db);
10376
+ }
10377
+ }
10378
+ async function linkRowDirect(local, cloudUrl, teamId, myUserId, table, pk) {
10379
+ const cloud = await openCloud(cloudUrl);
10380
+ let seq;
10381
+ try {
10382
+ await cloud.upsert("__lattice_row_links", {
10383
+ team_id: teamId,
10384
+ table_name: table,
10385
+ pk,
10386
+ owner_user_id: myUserId,
10387
+ linked_at: (/* @__PURE__ */ new Date()).toISOString()
10388
+ });
10389
+ seq = await appendChangeEnvelopeDirect(cloud, {
10390
+ team_id: teamId,
10391
+ table_name: table,
10392
+ pk,
10393
+ op: "link",
10394
+ payload_json: JSON.stringify({ owner_user_id: myUserId }),
10395
+ owner_user_id: myUserId
10396
+ });
10397
+ } finally {
10398
+ closeQuiet(cloud);
10399
+ }
10400
+ await local.upsert("__lattice_local_links", {
10401
+ team_id: teamId,
10402
+ table_name: table,
10403
+ pk,
10404
+ owner_user_id: myUserId,
10405
+ linked_at: (/* @__PURE__ */ new Date()).toISOString()
10406
+ });
10407
+ return { owner_user_id: myUserId, seq };
10408
+ }
10409
+ async function unlinkRowDirect(local, cloudUrl, teamId, table, pk) {
10410
+ const cloud = await openCloud(cloudUrl);
10411
+ try {
10412
+ await cloud.delete("__lattice_row_links", { team_id: teamId, table_name: table, pk });
10413
+ await appendChangeEnvelopeDirect(cloud, {
10414
+ team_id: teamId,
10415
+ table_name: table,
10416
+ pk,
10417
+ op: "unlink",
10418
+ payload_json: null,
10419
+ owner_user_id: null
10420
+ });
10421
+ } finally {
10422
+ closeQuiet(cloud);
10423
+ }
10424
+ try {
10425
+ await local.delete("__lattice_local_links", { team_id: teamId, table_name: table, pk });
10426
+ } catch {
10427
+ }
10428
+ }
10193
10429
 
10194
10430
  // src/teams/client.ts
10195
10431
  var TeamsClient = class {
@@ -10391,6 +10627,9 @@ var TeamsClient = class {
10391
10627
  );
10392
10628
  }
10393
10629
  async me(cloudUrl, token) {
10630
+ if (isPostgresUrl(cloudUrl)) {
10631
+ return meDirect(cloudUrl, token);
10632
+ }
10394
10633
  return this.fetchAuthed(cloudUrl, token, "GET", "/api/auth/me");
10395
10634
  }
10396
10635
  // ── Object sharing (Phase 3) ────────────────────────────────────────────
@@ -10399,7 +10638,15 @@ var TeamsClient = class {
10399
10638
  * `schema_version` and replaces the stored spec — useful for evolving
10400
10639
  * shared schemas additively.
10401
10640
  */
10402
- async shareObject(cloudUrl, token, teamId, table, schemaSpec) {
10641
+ async shareObject(cloudUrl, token, teamId, table, schemaSpec, inviterUserId) {
10642
+ if (isPostgresUrl(cloudUrl)) {
10643
+ if (!inviterUserId) {
10644
+ throw new Error(
10645
+ "shareObject: inviterUserId is required for direct-Postgres cloud URLs (read it from __lattice_team_connections.my_user_id)"
10646
+ );
10647
+ }
10648
+ return shareObjectDirect(cloudUrl, teamId, inviterUserId, table, schemaSpec);
10649
+ }
10403
10650
  return this.fetchAuthed(
10404
10651
  cloudUrl,
10405
10652
  token,
@@ -10414,6 +10661,10 @@ var TeamsClient = class {
10414
10661
  * row and appends an `unshare` envelope to the change log.
10415
10662
  */
10416
10663
  async unshareObject(cloudUrl, token, teamId, table) {
10664
+ if (isPostgresUrl(cloudUrl)) {
10665
+ await unshareObjectDirect(cloudUrl, teamId, table);
10666
+ return;
10667
+ }
10417
10668
  await this.fetchAuthed(
10418
10669
  cloudUrl,
10419
10670
  token,
@@ -10422,6 +10673,9 @@ var TeamsClient = class {
10422
10673
  );
10423
10674
  }
10424
10675
  async listSharedObjects(cloudUrl, token, teamId) {
10676
+ if (isPostgresUrl(cloudUrl)) {
10677
+ return listSharedObjectsDirect(cloudUrl, teamId);
10678
+ }
10425
10679
  const r = await this.fetchAuthed(
10426
10680
  cloudUrl,
10427
10681
  token,
@@ -10561,6 +10815,18 @@ var TeamsClient = class {
10561
10815
  if (!snapshot) {
10562
10816
  throw new Error(`Row not found in local table "${table}" with pk "${pk}"`);
10563
10817
  }
10818
+ if (isPostgresUrl(connection.cloud_url)) {
10819
+ const result2 = await linkRowDirect(
10820
+ this.local,
10821
+ connection.cloud_url,
10822
+ connection.team_id,
10823
+ connection.my_user_id,
10824
+ table,
10825
+ pk
10826
+ );
10827
+ this.ensureWriteHook(table);
10828
+ return result2;
10829
+ }
10564
10830
  const result = await this.fetchAuthed(
10565
10831
  connection.cloud_url,
10566
10832
  connection.api_token,
@@ -10586,6 +10852,10 @@ var TeamsClient = class {
10586
10852
  */
10587
10853
  async unlinkRow(connection, table, pk) {
10588
10854
  await this.ensureLocalTables();
10855
+ if (isPostgresUrl(connection.cloud_url)) {
10856
+ await unlinkRowDirect(this.local, connection.cloud_url, connection.team_id, table, pk);
10857
+ return;
10858
+ }
10589
10859
  await this.fetchAuthed(
10590
10860
  connection.cloud_url,
10591
10861
  connection.api_token,
@@ -10661,7 +10931,7 @@ var TeamsClient = class {
10661
10931
  if (conn.my_user_id !== link.owner_user_id) continue;
10662
10932
  const now = (/* @__PURE__ */ new Date()).toISOString();
10663
10933
  await this.local.insert("__lattice_team_outbox", {
10664
- id: randomUUID(),
10934
+ id: randomUUID2(),
10665
10935
  team_id: link.team_id,
10666
10936
  table_name: ctx.table,
10667
10937
  pk: ctx.pk,
@@ -10687,6 +10957,9 @@ var TeamsClient = class {
10687
10957
  */
10688
10958
  async drainOutbox(connection) {
10689
10959
  await this.ensureLocalTables();
10960
+ if (isPostgresUrl(connection.cloud_url)) {
10961
+ return { pushed: 0, failed: 0 };
10962
+ }
10690
10963
  const now = (/* @__PURE__ */ new Date()).toISOString();
10691
10964
  const rows = await this.local.query("__lattice_team_outbox", {
10692
10965
  filters: [
@@ -10747,6 +11020,9 @@ var TeamsClient = class {
10747
11020
  */
10748
11021
  async pullChanges(connection, batchSize = 500) {
10749
11022
  await this.ensureLocalTables();
11023
+ if (isPostgresUrl(connection.cloud_url)) {
11024
+ return { applied: 0, last_seq: 0, dlq_count: 0 };
11025
+ }
10750
11026
  const connRow = await this.local.get(
10751
11027
  "__lattice_team_connections",
10752
11028
  connection.team_id
@@ -10770,7 +11046,7 @@ var TeamsClient = class {
10770
11046
  totalApplied++;
10771
11047
  } catch (e) {
10772
11048
  await this.local.insert("__lattice_team_dlq", {
10773
- id: randomUUID(),
11049
+ id: randomUUID2(),
10774
11050
  team_id: connection.team_id,
10775
11051
  envelope_json: JSON.stringify(env),
10776
11052
  error: e.message,
@@ -10912,6 +11188,23 @@ var TeamsHttpError = class extends Error {
10912
11188
  };
10913
11189
 
10914
11190
  // src/gui/teams-routes.ts
11191
+ import { existsSync as existsSync12, writeFileSync as writeFileSync5 } from "fs";
11192
+ import { dirname as dirname5, join as join12 } from "path";
11193
+ function writeTeamConfigYaml(activeConfigPath, credentialLabel, teamName) {
11194
+ const projectDir = dirname5(activeConfigPath);
11195
+ const yamlPath = join12(projectDir, `${credentialLabel}.yml`);
11196
+ if (existsSync12(yamlPath)) return yamlPath;
11197
+ const yaml = `# Joined-team config \u2014 managed by lattice gui. Edit entities: to add
11198
+ # locally-projected tables of the team's shared data; the cloud DB at
11199
+ # ${credentialLabel} is the authoritative source.
11200
+ db: \${LATTICE_DB:${credentialLabel}}
11201
+
11202
+ # Team: ${teamName.replace(/[\r\n]/g, " ")}
11203
+ entities: {}
11204
+ `;
11205
+ writeFileSync5(yamlPath, yaml, "utf8");
11206
+ return yamlPath;
11207
+ }
10915
11208
  function sendJson2(res, body, status = 200) {
10916
11209
  res.writeHead(status, {
10917
11210
  "content-type": "application/json; charset=utf-8",
@@ -11084,7 +11377,8 @@ async function dispatchTeamSubroute(req, res, ctx, teamId, subpath) {
11084
11377
  conn.api_token,
11085
11378
  teamId,
11086
11379
  table,
11087
- spec
11380
+ spec,
11381
+ conn.my_user_id
11088
11382
  );
11089
11383
  sendJson2(res, result);
11090
11384
  return;
@@ -11136,7 +11430,19 @@ async function handleJoin(req, res, ctx) {
11136
11430
  my_user_id: result.user.id,
11137
11431
  api_token: result.raw_token
11138
11432
  });
11139
- sendJson2(res, { ok: true, team: result.team, user: result.user });
11433
+ const credentialLabel = saveDbCredentialForTeam({
11434
+ teamName: result.team.name,
11435
+ teamId: result.team.id,
11436
+ cloudUrl
11437
+ });
11438
+ const configYamlPath = writeTeamConfigYaml(ctx.configPath, credentialLabel, result.team.name);
11439
+ sendJson2(res, {
11440
+ ok: true,
11441
+ team: result.team,
11442
+ user: result.user,
11443
+ credential_label: credentialLabel,
11444
+ config_path: configYamlPath
11445
+ });
11140
11446
  }
11141
11447
  async function handleRegisterAndCreate(req, res, ctx) {
11142
11448
  const body = await readJson2(req);
@@ -11184,8 +11490,8 @@ async function handleLeave(res, ctx, teamId) {
11184
11490
  }
11185
11491
 
11186
11492
  // src/gui/userconfig-routes.ts
11187
- import { existsSync as existsSync12, readdirSync as readdirSync5 } from "fs";
11188
- import { basename as basename4, dirname as dirname5, join as join12 } from "path";
11493
+ import { existsSync as existsSync13, readdirSync as readdirSync5 } from "fs";
11494
+ import { basename as basename4, dirname as dirname6, join as join13 } from "path";
11189
11495
  function sendJson3(res, body, status = 200) {
11190
11496
  res.writeHead(status, {
11191
11497
  "content-type": "application/json; charset=utf-8",
@@ -11237,12 +11543,12 @@ async function upsertIdentityRow(db, identity) {
11237
11543
  }
11238
11544
  }
11239
11545
  function listProjectConfigs(activeConfigPath) {
11240
- const dir = dirname5(activeConfigPath);
11546
+ const dir = dirname6(activeConfigPath);
11241
11547
  const out = [];
11242
- if (!existsSync12(dir)) return out;
11548
+ if (!existsSync13(dir)) return out;
11243
11549
  for (const fname of readdirSync5(dir)) {
11244
11550
  if (!fname.endsWith(".yml") && !fname.endsWith(".yaml")) continue;
11245
- const full = join12(dir, fname);
11551
+ const full = join13(dir, fname);
11246
11552
  try {
11247
11553
  const parsed = parseConfigFile(full);
11248
11554
  out.push({
@@ -11307,12 +11613,12 @@ async function dispatchUserConfigRoute(req, res, ctx) {
11307
11613
  }
11308
11614
 
11309
11615
  // src/gui/dbconfig-routes.ts
11310
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
11616
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
11311
11617
  import { basename as basename5, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve5 } from "path";
11312
11618
  import { parseDocument } from "yaml";
11313
11619
 
11314
11620
  // src/framework/cloud-migration.ts
11315
- import { existsSync as existsSync13, renameSync as renameSync2, unlinkSync as unlinkSync4 } from "fs";
11621
+ import { existsSync as existsSync14, renameSync as renameSync2, unlinkSync as unlinkSync4 } from "fs";
11316
11622
 
11317
11623
  // src/framework/native-entities.ts
11318
11624
  var NATIVE_ENTITY_DEFS = {
@@ -11419,14 +11725,14 @@ async function openTargetLatticeForMigration(configPath, targetUrl, encryptionKe
11419
11725
  return target;
11420
11726
  }
11421
11727
  function archiveLocalSqlite(dbPath) {
11422
- if (!existsSync13(dbPath)) {
11728
+ if (!existsSync14(dbPath)) {
11423
11729
  throw new Error(`archiveLocalSqlite: source file does not exist: ${dbPath}`);
11424
11730
  }
11425
11731
  const backupPath = `${dbPath}.local-bak`;
11426
11732
  const siblings = ["", "-shm", "-wal"];
11427
11733
  for (const suffix of siblings) {
11428
11734
  const stale = `${dbPath}.local-bak${suffix}`;
11429
- if (existsSync13(stale)) {
11735
+ if (existsSync14(stale)) {
11430
11736
  try {
11431
11737
  unlinkSync4(stale);
11432
11738
  } catch {
@@ -11435,7 +11741,7 @@ function archiveLocalSqlite(dbPath) {
11435
11741
  }
11436
11742
  for (const suffix of siblings) {
11437
11743
  const src = `${dbPath}${suffix}`;
11438
- if (!existsSync13(src)) continue;
11744
+ if (!existsSync14(src)) continue;
11439
11745
  const dest = `${dbPath}.local-bak${suffix}`;
11440
11746
  renameSync2(src, dest);
11441
11747
  }
@@ -11604,7 +11910,7 @@ async function detectTeamEnabled(db) {
11604
11910
  function rewriteDbLine(configPath, newValue) {
11605
11911
  const doc = parseDocument(readFileSync10(configPath, "utf8"));
11606
11912
  doc.set("db", newValue);
11607
- writeFileSync5(configPath, doc.toString(), "utf8");
11913
+ writeFileSync6(configPath, doc.toString(), "utf8");
11608
11914
  }
11609
11915
  function parseSaveBody(body) {
11610
11916
  const type = body.type;
@@ -12075,9 +12381,9 @@ function readRowContext(outputDir, locator, secretCols) {
12075
12381
  throw new Error(`Path traversal detected: slug "${slug}" escapes output directory`);
12076
12382
  }
12077
12383
  return fileNames.map((filename) => {
12078
- const absPath = join13(entityDir, filename);
12079
- const relPath = join13(directoryRoot, slug, filename);
12080
- if (!existsSync14(absPath)) return { name: filename, path: relPath, content: "" };
12384
+ const absPath = join14(entityDir, filename);
12385
+ const relPath = join14(directoryRoot, slug, filename);
12386
+ if (!existsSync15(absPath)) return { name: filename, path: relPath, content: "" };
12081
12387
  let content = readFileSync11(absPath, "utf8");
12082
12388
  for (const col of secretCols) {
12083
12389
  const re = new RegExp(`^(${col}):.*$`, "gm");
@@ -12088,7 +12394,7 @@ function readRowContext(outputDir, locator, secretCols) {
12088
12394
  }
12089
12395
  async function openConfig(configPath, outputDir) {
12090
12396
  const parsed = parseConfigFile(configPath);
12091
- mkdirSync6(dirname6(parsed.dbPath), { recursive: true });
12397
+ mkdirSync6(dirname7(parsed.dbPath), { recursive: true });
12092
12398
  const encryptionKey = getOrCreateMasterKey();
12093
12399
  const db = new Lattice({ config: configPath }, { encryptionKey });
12094
12400
  registerNativeEntities(db);
@@ -12172,11 +12478,11 @@ async function openConfig(configPath, outputDir) {
12172
12478
  };
12173
12479
  }
12174
12480
  function listConfigs(activeConfigPath) {
12175
- const dir = dirname6(activeConfigPath);
12481
+ const dir = dirname7(activeConfigPath);
12176
12482
  const entries = [];
12177
12483
  for (const fname of readdirSync6(dir)) {
12178
12484
  if (!fname.endsWith(".yml") && !fname.endsWith(".yaml")) continue;
12179
- const full = join13(dir, fname);
12485
+ const full = join14(dir, fname);
12180
12486
  try {
12181
12487
  const parsed = parseConfigFile(full);
12182
12488
  entries.push({
@@ -12199,14 +12505,14 @@ function loadConfigDoc(configPath) {
12199
12505
  return parseDocument2(readFileSync11(configPath, "utf8"));
12200
12506
  }
12201
12507
  function saveConfigDoc(configPath, doc) {
12202
- writeFileSync6(configPath, doc.toString(), "utf8");
12508
+ writeFileSync7(configPath, doc.toString(), "utf8");
12203
12509
  }
12204
12510
  function createBlankConfig(activeConfigPath, dbName) {
12205
- const dir = dirname6(activeConfigPath);
12511
+ const dir = dirname7(activeConfigPath);
12206
12512
  const slug = dbName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
12207
12513
  if (!slug) throw new Error("Database name must contain at least one alphanumeric character");
12208
- const configPath = join13(dir, `${slug}.config.yml`);
12209
- if (existsSync14(configPath)) throw new Error(`Config already exists: ${slug}.config.yml`);
12514
+ const configPath = join14(dir, `${slug}.config.yml`);
12515
+ if (existsSync15(configPath)) throw new Error(`Config already exists: ${slug}.config.yml`);
12210
12516
  const yaml = `db: ./data/${slug}.db
12211
12517
 
12212
12518
  entities:
@@ -12218,8 +12524,8 @@ entities:
12218
12524
  deleted_at: { type: text }
12219
12525
  outputFile: ITEMS.md
12220
12526
  `;
12221
- writeFileSync6(configPath, yaml, "utf8");
12222
- mkdirSync6(join13(dir, "data"), { recursive: true });
12527
+ writeFileSync7(configPath, yaml, "utf8");
12528
+ mkdirSync6(join14(dir, "data"), { recursive: true });
12223
12529
  return configPath;
12224
12530
  }
12225
12531
  async function registerTeamCloudTables(db) {
@@ -12597,7 +12903,7 @@ async function startGuiServer(options) {
12597
12903
  return;
12598
12904
  }
12599
12905
  const newPath = resolve6(body.path);
12600
- if (!existsSync14(newPath)) {
12906
+ if (!existsSync15(newPath)) {
12601
12907
  sendJson5(res, { error: `Config not found: ${newPath}` }, 400);
12602
12908
  return;
12603
12909
  }
@@ -12799,6 +13105,7 @@ async function startGuiServer(options) {
12799
13105
  const handled = await dispatchTeamsGuiRoute(req, res, {
12800
13106
  db: active.db,
12801
13107
  client: active.teamsClient,
13108
+ configPath: active.configPath,
12802
13109
  pathname,
12803
13110
  method,
12804
13111
  validTables: active.validTables
@@ -12856,13 +13163,13 @@ async function startGuiServer(options) {
12856
13163
  }
12857
13164
 
12858
13165
  // src/gui/discover-output-dir.ts
12859
- import { existsSync as existsSync15 } from "fs";
12860
- import { join as join14, resolve as resolve7 } from "path";
13166
+ import { existsSync as existsSync16 } from "fs";
13167
+ import { join as join15, resolve as resolve7 } from "path";
12861
13168
  function discoverOutputDir(explicitOutput, explicit) {
12862
13169
  if (explicit) return explicitOutput;
12863
13170
  const candidates = ["./context", ".", "./generated"];
12864
13171
  for (const dir of candidates) {
12865
- if (existsSync15(join14(resolve7(dir), ".lattice", "manifest.json"))) return dir;
13172
+ if (existsSync16(join15(resolve7(dir), ".lattice", "manifest.json"))) return dir;
12866
13173
  }
12867
13174
  return explicitOutput;
12868
13175
  }
@@ -13580,7 +13887,7 @@ function runGenerate(args) {
13580
13887
  console.error('Error: config must have an "entities" key');
13581
13888
  process.exit(1);
13582
13889
  }
13583
- const configDir2 = dirname7(configPath);
13890
+ const configDir2 = dirname8(configPath);
13584
13891
  const outDir = resolve9(args.out);
13585
13892
  try {
13586
13893
  const result = generateAll({ config, configDir: configDir2, outDir, scaffold: args.scaffold });