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 +344 -37
- package/dist/index.cjs +282 -4
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +281 -4
- package/package.json +1 -1
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
|
|
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
|
|
4966
|
-
import { basename as basename6, dirname as
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
11188
|
-
import { basename as basename4, dirname as
|
|
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 =
|
|
11546
|
+
const dir = dirname6(activeConfigPath);
|
|
11241
11547
|
const out = [];
|
|
11242
|
-
if (!
|
|
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 =
|
|
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
|
|
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
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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
|
-
|
|
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 =
|
|
12079
|
-
const relPath =
|
|
12080
|
-
if (!
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
12508
|
+
writeFileSync7(configPath, doc.toString(), "utf8");
|
|
12203
12509
|
}
|
|
12204
12510
|
function createBlankConfig(activeConfigPath, dbName) {
|
|
12205
|
-
const dir =
|
|
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 =
|
|
12209
|
-
if (
|
|
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
|
-
|
|
12222
|
-
mkdirSync6(
|
|
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 (!
|
|
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
|
|
12860
|
-
import { join as
|
|
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 (
|
|
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 =
|
|
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 });
|