@malloy-publisher/server 0.0.218 → 0.0.220

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.
Files changed (63) hide show
  1. package/dist/app/api-doc.yaml +262 -25
  2. package/dist/app/assets/{EnvironmentPage-DxOiCxcd.js → EnvironmentPage-BqLiaatL.js} +1 -1
  3. package/dist/app/assets/{HomePage-i8qAtD6x.js → HomePage-G_xpt9XE.js} +1 -1
  4. package/dist/app/assets/LightMode-s1PDRIsF.js +1 -0
  5. package/dist/app/assets/MainPage-C6TfpC92.js +2 -0
  6. package/dist/app/assets/{MaterializationsPage-6OEVM543.js → MaterializationsPage-BMO1afhm.js} +1 -1
  7. package/dist/app/assets/ModelPage-zHOJwMnU.js +1 -0
  8. package/dist/app/assets/{PackagePage-BVmoVsPQ.js → PackagePage-GCaWmELQ.js} +1 -1
  9. package/dist/app/assets/{RouteError-DFX521_t.js → RouteError-CIC588k4.js} +1 -1
  10. package/dist/app/assets/ThemeEditorPage-BdbIfIGO.js +1 -0
  11. package/dist/app/assets/{WorkbookPage-CzucDJ-T.js → WorkbookPage-C8ucK_H8.js} +1 -1
  12. package/dist/app/assets/{core-B95VQkAV.es-Cbn-yKG-.js → core-DFvqRVqM.es-NtfPVC7h.js} +1 -1
  13. package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
  14. package/dist/app/assets/index-8E2uLeV9.js +2541 -0
  15. package/dist/app/assets/index-B3NcDPbp.js +18 -0
  16. package/dist/app/assets/index-BxDMCn3s.js +527 -0
  17. package/dist/app/assets/index-aYNf0kTZ.js +1 -0
  18. package/dist/app/assets/index-aYtt-ovi.js +1761 -0
  19. package/dist/app/assets/index-w_0OQJgZ.js +23 -0
  20. package/dist/app/index.html +1 -1
  21. package/dist/runtime/publisher.js +6 -1
  22. package/dist/server.mjs +637 -263
  23. package/package.json +12 -12
  24. package/src/config.theme.spec.ts +178 -0
  25. package/src/config.ts +179 -0
  26. package/src/controller/materialization.controller.spec.ts +4 -4
  27. package/src/controller/materialization.controller.ts +2 -2
  28. package/src/controller/theme.controller.ts +83 -0
  29. package/src/dto/connection.dto.ts +6 -0
  30. package/src/materialization_metrics.ts +1 -1
  31. package/src/runtime/publisher.js +6 -1
  32. package/src/server.ts +58 -1
  33. package/src/service/build_plan.spec.ts +17 -17
  34. package/src/service/build_plan.ts +25 -10
  35. package/src/service/connection.spec.ts +70 -0
  36. package/src/service/connection.ts +102 -11
  37. package/src/service/connection_config.spec.ts +138 -0
  38. package/src/service/connection_config.ts +81 -2
  39. package/src/service/connection_fingerprint.spec.ts +102 -0
  40. package/src/service/manifest_loader.spec.ts +5 -5
  41. package/src/service/manifest_loader.ts +4 -4
  42. package/src/service/materialization_service.spec.ts +47 -37
  43. package/src/service/materialization_service.ts +59 -50
  44. package/src/service/materialization_test_fixtures.ts +5 -5
  45. package/src/service/model.ts +1 -1
  46. package/src/service/package.ts +4 -4
  47. package/src/service/proxy.spec.ts +82 -35
  48. package/src/service/proxy.ts +55 -40
  49. package/src/service/theme_store.ts +199 -0
  50. package/src/storage/DatabaseInterface.ts +1 -1
  51. package/src/storage/StorageManager.ts +17 -0
  52. package/src/storage/duckdb/schema.ts +27 -6
  53. package/src/theme_key_parity.spec.ts +57 -0
  54. package/tests/integration/materialization/manifest_binding.integration.spec.ts +1 -1
  55. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +5 -5
  56. package/tests/unit/controller/theme.controller.spec.ts +141 -0
  57. package/tests/unit/service/theme_store.spec.ts +139 -0
  58. package/dist/app/assets/MainPage-B58d5pmX.js +0 -2
  59. package/dist/app/assets/ModelPage-8d5l7YkU.js +0 -1
  60. package/dist/app/assets/index--xJ1pzL-.js +0 -455
  61. package/dist/app/assets/index-Bxkza-hz.js +0 -40
  62. package/dist/app/assets/index-DxRKma36.js +0 -1760
  63. package/dist/app/assets/index.umd-o-yUIEtS.js +0 -2467
package/dist/server.mjs CHANGED
@@ -161487,11 +161487,11 @@ var require_agent = __commonJS((exports, module) => {
161487
161487
  };
161488
161488
  })();
161489
161489
  var WINDOWS_PIPE_REGEX = /^[/\\][/\\]\.[/\\]pipe[/\\].+/;
161490
- function createAgent(path4) {
161491
- if (process.platform === "win32" && !WINDOWS_PIPE_REGEX.test(path4)) {
161492
- return path4 === "pageant" ? new PageantAgent : new CygwinAgent(path4);
161490
+ function createAgent(path3) {
161491
+ if (process.platform === "win32" && !WINDOWS_PIPE_REGEX.test(path3)) {
161492
+ return path3 === "pageant" ? new PageantAgent : new CygwinAgent(path3);
161493
161493
  }
161494
- return new OpenSSHAgent(path4);
161494
+ return new OpenSSHAgent(path3);
161495
161495
  }
161496
161496
  var AgentProtocol = (() => {
161497
161497
  const SSH_AGENTC_REQUEST_IDENTITIES = 11;
@@ -166093,17 +166093,17 @@ var require_SFTP = __commonJS((exports, module) => {
166093
166093
  if (!this.server)
166094
166094
  sendOrBuffer(this, CLIENT_VERSION_BUFFER);
166095
166095
  }
166096
- createReadStream(path4, options) {
166096
+ createReadStream(path3, options) {
166097
166097
  if (this.server)
166098
166098
  throw new Error("Client-only method called in server mode");
166099
- return new ReadStream(this, path4, options);
166099
+ return new ReadStream(this, path3, options);
166100
166100
  }
166101
- createWriteStream(path4, options) {
166101
+ createWriteStream(path3, options) {
166102
166102
  if (this.server)
166103
166103
  throw new Error("Client-only method called in server mode");
166104
- return new WriteStream(this, path4, options);
166104
+ return new WriteStream(this, path3, options);
166105
166105
  }
166106
- open(path4, flags_, attrs, cb) {
166106
+ open(path3, flags_, attrs, cb) {
166107
166107
  if (this.server)
166108
166108
  throw new Error("Client-only method called in server mode");
166109
166109
  if (typeof attrs === "function") {
@@ -166122,7 +166122,7 @@ var require_SFTP = __commonJS((exports, module) => {
166122
166122
  attrsFlags = attrs.flags;
166123
166123
  attrsLen = attrs.nb;
166124
166124
  }
166125
- const pathLen = Buffer.byteLength(path4);
166125
+ const pathLen = Buffer.byteLength(path3);
166126
166126
  let p = 9;
166127
166127
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen + 4 + 4 + attrsLen);
166128
166128
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -166130,7 +166130,7 @@ var require_SFTP = __commonJS((exports, module) => {
166130
166130
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
166131
166131
  writeUInt32BE(buf, reqid, 5);
166132
166132
  writeUInt32BE(buf, pathLen, p);
166133
- buf.utf8Write(path4, p += 4, pathLen);
166133
+ buf.utf8Write(path3, p += 4, pathLen);
166134
166134
  writeUInt32BE(buf, flags, p += pathLen);
166135
166135
  writeUInt32BE(buf, attrsFlags, p += 4);
166136
166136
  if (attrsLen) {
@@ -166250,7 +166250,7 @@ var require_SFTP = __commonJS((exports, module) => {
166250
166250
  throw new Error("Client-only method called in server mode");
166251
166251
  fastXfer(fs2, this, localPath, remotePath, opts, cb);
166252
166252
  }
166253
- readFile(path4, options, callback_) {
166253
+ readFile(path3, options, callback_) {
166254
166254
  if (this.server)
166255
166255
  throw new Error("Client-only method called in server mode");
166256
166256
  let callback;
@@ -166323,13 +166323,13 @@ var require_SFTP = __commonJS((exports, module) => {
166323
166323
  return callback && callback(er, buffer);
166324
166324
  });
166325
166325
  };
166326
- this.open(path4, flag, 438, (er, handle_) => {
166326
+ this.open(path3, flag, 438, (er, handle_) => {
166327
166327
  if (er)
166328
166328
  return callback && callback(er);
166329
166329
  handle = handle_;
166330
166330
  const tryStat = (er2, st) => {
166331
166331
  if (er2) {
166332
- this.stat(path4, (er_, st_) => {
166332
+ this.stat(path3, (er_, st_) => {
166333
166333
  if (er_) {
166334
166334
  return this.close(handle, () => {
166335
166335
  callback && callback(er2);
@@ -166350,7 +166350,7 @@ var require_SFTP = __commonJS((exports, module) => {
166350
166350
  this.fstat(handle, tryStat);
166351
166351
  });
166352
166352
  }
166353
- writeFile(path4, data, options, callback_) {
166353
+ writeFile(path3, data, options, callback_) {
166354
166354
  if (this.server)
166355
166355
  throw new Error("Client-only method called in server mode");
166356
166356
  let callback;
@@ -166369,7 +166369,7 @@ var require_SFTP = __commonJS((exports, module) => {
166369
166369
  if (options.encoding && !Buffer.isEncoding(options.encoding))
166370
166370
  throw new Error(`Unknown encoding: ${options.encoding}`);
166371
166371
  const flag = options.flag || "w";
166372
- this.open(path4, flag, options.mode, (openErr, handle) => {
166372
+ this.open(path3, flag, options.mode, (openErr, handle) => {
166373
166373
  if (openErr) {
166374
166374
  callback && callback(openErr);
166375
166375
  } else {
@@ -166378,7 +166378,7 @@ var require_SFTP = __commonJS((exports, module) => {
166378
166378
  if (position === null) {
166379
166379
  const tryStat = (er, st) => {
166380
166380
  if (er) {
166381
- this.stat(path4, (er_, st_) => {
166381
+ this.stat(path3, (er_, st_) => {
166382
166382
  if (er_) {
166383
166383
  return this.close(handle, () => {
166384
166384
  callback && callback(er);
@@ -166397,7 +166397,7 @@ var require_SFTP = __commonJS((exports, module) => {
166397
166397
  }
166398
166398
  });
166399
166399
  }
166400
- appendFile(path4, data, options, callback_) {
166400
+ appendFile(path3, data, options, callback_) {
166401
166401
  if (this.server)
166402
166402
  throw new Error("Client-only method called in server mode");
166403
166403
  let callback;
@@ -166415,12 +166415,12 @@ var require_SFTP = __commonJS((exports, module) => {
166415
166415
  throw new TypeError("Bad arguments");
166416
166416
  if (!options.flag)
166417
166417
  options = Object.assign({ flag: "a" }, options);
166418
- this.writeFile(path4, data, options, callback);
166418
+ this.writeFile(path3, data, options, callback);
166419
166419
  }
166420
- exists(path4, cb) {
166420
+ exists(path3, cb) {
166421
166421
  if (this.server)
166422
166422
  throw new Error("Client-only method called in server mode");
166423
- this.stat(path4, (err) => {
166423
+ this.stat(path3, (err) => {
166424
166424
  cb && cb(err ? false : true);
166425
166425
  });
166426
166426
  }
@@ -166459,7 +166459,7 @@ var require_SFTP = __commonJS((exports, module) => {
166459
166459
  const isBuffered = sendOrBuffer(this, buf);
166460
166460
  this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} RENAME`);
166461
166461
  }
166462
- mkdir(path4, attrs, cb) {
166462
+ mkdir(path3, attrs, cb) {
166463
166463
  if (this.server)
166464
166464
  throw new Error("Client-only method called in server mode");
166465
166465
  let flags = 0;
@@ -166473,7 +166473,7 @@ var require_SFTP = __commonJS((exports, module) => {
166473
166473
  flags = attrs.flags;
166474
166474
  attrsLen = attrs.nb;
166475
166475
  }
166476
- const pathLen = Buffer.byteLength(path4);
166476
+ const pathLen = Buffer.byteLength(path3);
166477
166477
  let p = 9;
166478
166478
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen + 4 + attrsLen);
166479
166479
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -166481,7 +166481,7 @@ var require_SFTP = __commonJS((exports, module) => {
166481
166481
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
166482
166482
  writeUInt32BE(buf, reqid, 5);
166483
166483
  writeUInt32BE(buf, pathLen, p);
166484
- buf.utf8Write(path4, p += 4, pathLen);
166484
+ buf.utf8Write(path3, p += 4, pathLen);
166485
166485
  writeUInt32BE(buf, flags, p += pathLen);
166486
166486
  if (attrsLen) {
166487
166487
  p += 4;
@@ -166495,10 +166495,10 @@ var require_SFTP = __commonJS((exports, module) => {
166495
166495
  const isBuffered = sendOrBuffer(this, buf);
166496
166496
  this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} MKDIR`);
166497
166497
  }
166498
- rmdir(path4, cb) {
166498
+ rmdir(path3, cb) {
166499
166499
  if (this.server)
166500
166500
  throw new Error("Client-only method called in server mode");
166501
- const pathLen = Buffer.byteLength(path4);
166501
+ const pathLen = Buffer.byteLength(path3);
166502
166502
  let p = 9;
166503
166503
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
166504
166504
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -166506,7 +166506,7 @@ var require_SFTP = __commonJS((exports, module) => {
166506
166506
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
166507
166507
  writeUInt32BE(buf, reqid, 5);
166508
166508
  writeUInt32BE(buf, pathLen, p);
166509
- buf.utf8Write(path4, p += 4, pathLen);
166509
+ buf.utf8Write(path3, p += 4, pathLen);
166510
166510
  this._requests[reqid] = { cb };
166511
166511
  const isBuffered = sendOrBuffer(this, buf);
166512
166512
  this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} RMDIR`);
@@ -166590,10 +166590,10 @@ var require_SFTP = __commonJS((exports, module) => {
166590
166590
  const isBuffered = sendOrBuffer(this, buf);
166591
166591
  this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} FSTAT`);
166592
166592
  }
166593
- stat(path4, cb) {
166593
+ stat(path3, cb) {
166594
166594
  if (this.server)
166595
166595
  throw new Error("Client-only method called in server mode");
166596
- const pathLen = Buffer.byteLength(path4);
166596
+ const pathLen = Buffer.byteLength(path3);
166597
166597
  let p = 9;
166598
166598
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
166599
166599
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -166601,15 +166601,15 @@ var require_SFTP = __commonJS((exports, module) => {
166601
166601
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
166602
166602
  writeUInt32BE(buf, reqid, 5);
166603
166603
  writeUInt32BE(buf, pathLen, p);
166604
- buf.utf8Write(path4, p += 4, pathLen);
166604
+ buf.utf8Write(path3, p += 4, pathLen);
166605
166605
  this._requests[reqid] = { cb };
166606
166606
  const isBuffered = sendOrBuffer(this, buf);
166607
166607
  this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} STAT`);
166608
166608
  }
166609
- lstat(path4, cb) {
166609
+ lstat(path3, cb) {
166610
166610
  if (this.server)
166611
166611
  throw new Error("Client-only method called in server mode");
166612
- const pathLen = Buffer.byteLength(path4);
166612
+ const pathLen = Buffer.byteLength(path3);
166613
166613
  let p = 9;
166614
166614
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
166615
166615
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -166617,15 +166617,15 @@ var require_SFTP = __commonJS((exports, module) => {
166617
166617
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
166618
166618
  writeUInt32BE(buf, reqid, 5);
166619
166619
  writeUInt32BE(buf, pathLen, p);
166620
- buf.utf8Write(path4, p += 4, pathLen);
166620
+ buf.utf8Write(path3, p += 4, pathLen);
166621
166621
  this._requests[reqid] = { cb };
166622
166622
  const isBuffered = sendOrBuffer(this, buf);
166623
166623
  this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} LSTAT`);
166624
166624
  }
166625
- opendir(path4, cb) {
166625
+ opendir(path3, cb) {
166626
166626
  if (this.server)
166627
166627
  throw new Error("Client-only method called in server mode");
166628
- const pathLen = Buffer.byteLength(path4);
166628
+ const pathLen = Buffer.byteLength(path3);
166629
166629
  let p = 9;
166630
166630
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
166631
166631
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -166633,12 +166633,12 @@ var require_SFTP = __commonJS((exports, module) => {
166633
166633
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
166634
166634
  writeUInt32BE(buf, reqid, 5);
166635
166635
  writeUInt32BE(buf, pathLen, p);
166636
- buf.utf8Write(path4, p += 4, pathLen);
166636
+ buf.utf8Write(path3, p += 4, pathLen);
166637
166637
  this._requests[reqid] = { cb };
166638
166638
  const isBuffered = sendOrBuffer(this, buf);
166639
166639
  this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} OPENDIR`);
166640
166640
  }
166641
- setstat(path4, attrs, cb) {
166641
+ setstat(path3, attrs, cb) {
166642
166642
  if (this.server)
166643
166643
  throw new Error("Client-only method called in server mode");
166644
166644
  let flags = 0;
@@ -166650,7 +166650,7 @@ var require_SFTP = __commonJS((exports, module) => {
166650
166650
  } else if (typeof attrs === "function") {
166651
166651
  cb = attrs;
166652
166652
  }
166653
- const pathLen = Buffer.byteLength(path4);
166653
+ const pathLen = Buffer.byteLength(path3);
166654
166654
  let p = 9;
166655
166655
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen + 4 + attrsLen);
166656
166656
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -166658,7 +166658,7 @@ var require_SFTP = __commonJS((exports, module) => {
166658
166658
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
166659
166659
  writeUInt32BE(buf, reqid, 5);
166660
166660
  writeUInt32BE(buf, pathLen, p);
166661
- buf.utf8Write(path4, p += 4, pathLen);
166661
+ buf.utf8Write(path3, p += 4, pathLen);
166662
166662
  writeUInt32BE(buf, flags, p += pathLen);
166663
166663
  if (attrsLen) {
166664
166664
  p += 4;
@@ -166714,8 +166714,8 @@ var require_SFTP = __commonJS((exports, module) => {
166714
166714
  mtime: toUnixTimestamp(mtime)
166715
166715
  }, cb);
166716
166716
  }
166717
- utimes(path4, atime, mtime, cb) {
166718
- return this.setstat(path4, {
166717
+ utimes(path3, atime, mtime, cb) {
166718
+ return this.setstat(path3, {
166719
166719
  atime: toUnixTimestamp(atime),
166720
166720
  mtime: toUnixTimestamp(mtime)
166721
166721
  }, cb);
@@ -166726,8 +166726,8 @@ var require_SFTP = __commonJS((exports, module) => {
166726
166726
  gid
166727
166727
  }, cb);
166728
166728
  }
166729
- chown(path4, uid, gid, cb) {
166730
- return this.setstat(path4, {
166729
+ chown(path3, uid, gid, cb) {
166730
+ return this.setstat(path3, {
166731
166731
  uid,
166732
166732
  gid
166733
166733
  }, cb);
@@ -166737,15 +166737,15 @@ var require_SFTP = __commonJS((exports, module) => {
166737
166737
  mode
166738
166738
  }, cb);
166739
166739
  }
166740
- chmod(path4, mode, cb) {
166741
- return this.setstat(path4, {
166740
+ chmod(path3, mode, cb) {
166741
+ return this.setstat(path3, {
166742
166742
  mode
166743
166743
  }, cb);
166744
166744
  }
166745
- readlink(path4, cb) {
166745
+ readlink(path3, cb) {
166746
166746
  if (this.server)
166747
166747
  throw new Error("Client-only method called in server mode");
166748
- const pathLen = Buffer.byteLength(path4);
166748
+ const pathLen = Buffer.byteLength(path3);
166749
166749
  let p = 9;
166750
166750
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
166751
166751
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -166753,7 +166753,7 @@ var require_SFTP = __commonJS((exports, module) => {
166753
166753
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
166754
166754
  writeUInt32BE(buf, reqid, 5);
166755
166755
  writeUInt32BE(buf, pathLen, p);
166756
- buf.utf8Write(path4, p += 4, pathLen);
166756
+ buf.utf8Write(path3, p += 4, pathLen);
166757
166757
  this._requests[reqid] = {
166758
166758
  cb: (err, names) => {
166759
166759
  if (typeof cb !== "function")
@@ -166794,10 +166794,10 @@ var require_SFTP = __commonJS((exports, module) => {
166794
166794
  const isBuffered = sendOrBuffer(this, buf);
166795
166795
  this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} SYMLINK`);
166796
166796
  }
166797
- realpath(path4, cb) {
166797
+ realpath(path3, cb) {
166798
166798
  if (this.server)
166799
166799
  throw new Error("Client-only method called in server mode");
166800
- const pathLen = Buffer.byteLength(path4);
166800
+ const pathLen = Buffer.byteLength(path3);
166801
166801
  let p = 9;
166802
166802
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
166803
166803
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -166805,7 +166805,7 @@ var require_SFTP = __commonJS((exports, module) => {
166805
166805
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
166806
166806
  writeUInt32BE(buf, reqid, 5);
166807
166807
  writeUInt32BE(buf, pathLen, p);
166808
- buf.utf8Write(path4, p += 4, pathLen);
166808
+ buf.utf8Write(path3, p += 4, pathLen);
166809
166809
  this._requests[reqid] = {
166810
166810
  cb: (err, names) => {
166811
166811
  if (typeof cb !== "function")
@@ -166847,13 +166847,13 @@ var require_SFTP = __commonJS((exports, module) => {
166847
166847
  this._debug(`SFTP: Outbound: ${which} posix-rename@openssh.com`);
166848
166848
  }
166849
166849
  }
166850
- ext_openssh_statvfs(path4, cb) {
166850
+ ext_openssh_statvfs(path3, cb) {
166851
166851
  if (this.server)
166852
166852
  throw new Error("Client-only method called in server mode");
166853
166853
  const ext = this._extensions["statvfs@openssh.com"];
166854
166854
  if (!ext || ext !== "2")
166855
166855
  throw new Error("Server does not support this extended request");
166856
- const pathLen = Buffer.byteLength(path4);
166856
+ const pathLen = Buffer.byteLength(path3);
166857
166857
  let p = 9;
166858
166858
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 19 + 4 + pathLen);
166859
166859
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -166863,7 +166863,7 @@ var require_SFTP = __commonJS((exports, module) => {
166863
166863
  writeUInt32BE(buf, 19, p);
166864
166864
  buf.utf8Write("statvfs@openssh.com", p += 4, 19);
166865
166865
  writeUInt32BE(buf, pathLen, p += 19);
166866
- buf.utf8Write(path4, p += 4, pathLen);
166866
+ buf.utf8Write(path3, p += 4, pathLen);
166867
166867
  this._requests[reqid] = { extended: "statvfs@openssh.com", cb };
166868
166868
  const isBuffered = sendOrBuffer(this, buf);
166869
166869
  if (this._debug) {
@@ -166947,7 +166947,7 @@ var require_SFTP = __commonJS((exports, module) => {
166947
166947
  const isBuffered = sendOrBuffer(this, buf);
166948
166948
  this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} fsync@openssh.com`);
166949
166949
  }
166950
- ext_openssh_lsetstat(path4, attrs, cb) {
166950
+ ext_openssh_lsetstat(path3, attrs, cb) {
166951
166951
  if (this.server)
166952
166952
  throw new Error("Client-only method called in server mode");
166953
166953
  const ext = this._extensions["lsetstat@openssh.com"];
@@ -166962,7 +166962,7 @@ var require_SFTP = __commonJS((exports, module) => {
166962
166962
  } else if (typeof attrs === "function") {
166963
166963
  cb = attrs;
166964
166964
  }
166965
- const pathLen = Buffer.byteLength(path4);
166965
+ const pathLen = Buffer.byteLength(path3);
166966
166966
  let p = 9;
166967
166967
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 20 + 4 + pathLen + 4 + attrsLen);
166968
166968
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -166972,7 +166972,7 @@ var require_SFTP = __commonJS((exports, module) => {
166972
166972
  writeUInt32BE(buf, 20, p);
166973
166973
  buf.utf8Write("lsetstat@openssh.com", p += 4, 20);
166974
166974
  writeUInt32BE(buf, pathLen, p += 20);
166975
- buf.utf8Write(path4, p += 4, pathLen);
166975
+ buf.utf8Write(path3, p += 4, pathLen);
166976
166976
  writeUInt32BE(buf, flags, p += pathLen);
166977
166977
  if (attrsLen) {
166978
166978
  p += 4;
@@ -166989,13 +166989,13 @@ var require_SFTP = __commonJS((exports, module) => {
166989
166989
  this._debug(`SFTP: Outbound: ${status} lsetstat@openssh.com`);
166990
166990
  }
166991
166991
  }
166992
- ext_openssh_expandPath(path4, cb) {
166992
+ ext_openssh_expandPath(path3, cb) {
166993
166993
  if (this.server)
166994
166994
  throw new Error("Client-only method called in server mode");
166995
166995
  const ext = this._extensions["expand-path@openssh.com"];
166996
166996
  if (ext !== "1")
166997
166997
  throw new Error("Server does not support this extended request");
166998
- const pathLen = Buffer.byteLength(path4);
166998
+ const pathLen = Buffer.byteLength(path3);
166999
166999
  let p = 9;
167000
167000
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 23 + 4 + pathLen);
167001
167001
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -167005,7 +167005,7 @@ var require_SFTP = __commonJS((exports, module) => {
167005
167005
  writeUInt32BE(buf, 23, p);
167006
167006
  buf.utf8Write("expand-path@openssh.com", p += 4, 23);
167007
167007
  writeUInt32BE(buf, pathLen, p += 20);
167008
- buf.utf8Write(path4, p += 4, pathLen);
167008
+ buf.utf8Write(path3, p += 4, pathLen);
167009
167009
  this._requests[reqid] = {
167010
167010
  cb: (err, names) => {
167011
167011
  if (typeof cb !== "function")
@@ -168222,12 +168222,12 @@ var require_SFTP = __commonJS((exports, module) => {
168222
168222
  [REQUEST.LSTAT]: (sftp, payload) => {
168223
168223
  bufferParser.init(payload, 1);
168224
168224
  const reqID = bufferParser.readUInt32BE();
168225
- const path4 = bufferParser.readString(true);
168225
+ const path3 = bufferParser.readString(true);
168226
168226
  bufferParser.clear();
168227
- if (path4 === undefined)
168227
+ if (path3 === undefined)
168228
168228
  return doFatalSFTPError(sftp, "Malformed LSTAT packet");
168229
168229
  sftp._debug && sftp._debug(`SFTP: Inbound: Received LSTAT (id:${reqID})`);
168230
- if (!sftp.emit("LSTAT", reqID, path4)) {
168230
+ if (!sftp.emit("LSTAT", reqID, path3)) {
168231
168231
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
168232
168232
  }
168233
168233
  },
@@ -168246,13 +168246,13 @@ var require_SFTP = __commonJS((exports, module) => {
168246
168246
  [REQUEST.SETSTAT]: (sftp, payload) => {
168247
168247
  bufferParser.init(payload, 1);
168248
168248
  const reqID = bufferParser.readUInt32BE();
168249
- const path4 = bufferParser.readString(true);
168249
+ const path3 = bufferParser.readString(true);
168250
168250
  const attrs = readAttrs(sftp._biOpt);
168251
168251
  bufferParser.clear();
168252
168252
  if (attrs === undefined)
168253
168253
  return doFatalSFTPError(sftp, "Malformed SETSTAT packet");
168254
168254
  sftp._debug && sftp._debug(`SFTP: Inbound: Received SETSTAT (id:${reqID})`);
168255
- if (!sftp.emit("SETSTAT", reqID, path4, attrs)) {
168255
+ if (!sftp.emit("SETSTAT", reqID, path3, attrs)) {
168256
168256
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
168257
168257
  }
168258
168258
  },
@@ -168272,12 +168272,12 @@ var require_SFTP = __commonJS((exports, module) => {
168272
168272
  [REQUEST.OPENDIR]: (sftp, payload) => {
168273
168273
  bufferParser.init(payload, 1);
168274
168274
  const reqID = bufferParser.readUInt32BE();
168275
- const path4 = bufferParser.readString(true);
168275
+ const path3 = bufferParser.readString(true);
168276
168276
  bufferParser.clear();
168277
- if (path4 === undefined)
168277
+ if (path3 === undefined)
168278
168278
  return doFatalSFTPError(sftp, "Malformed OPENDIR packet");
168279
168279
  sftp._debug && sftp._debug(`SFTP: Inbound: Received OPENDIR (id:${reqID})`);
168280
- if (!sftp.emit("OPENDIR", reqID, path4)) {
168280
+ if (!sftp.emit("OPENDIR", reqID, path3)) {
168281
168281
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
168282
168282
  }
168283
168283
  },
@@ -168296,61 +168296,61 @@ var require_SFTP = __commonJS((exports, module) => {
168296
168296
  [REQUEST.REMOVE]: (sftp, payload) => {
168297
168297
  bufferParser.init(payload, 1);
168298
168298
  const reqID = bufferParser.readUInt32BE();
168299
- const path4 = bufferParser.readString(true);
168299
+ const path3 = bufferParser.readString(true);
168300
168300
  bufferParser.clear();
168301
- if (path4 === undefined)
168301
+ if (path3 === undefined)
168302
168302
  return doFatalSFTPError(sftp, "Malformed REMOVE packet");
168303
168303
  sftp._debug && sftp._debug(`SFTP: Inbound: Received REMOVE (id:${reqID})`);
168304
- if (!sftp.emit("REMOVE", reqID, path4)) {
168304
+ if (!sftp.emit("REMOVE", reqID, path3)) {
168305
168305
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
168306
168306
  }
168307
168307
  },
168308
168308
  [REQUEST.MKDIR]: (sftp, payload) => {
168309
168309
  bufferParser.init(payload, 1);
168310
168310
  const reqID = bufferParser.readUInt32BE();
168311
- const path4 = bufferParser.readString(true);
168311
+ const path3 = bufferParser.readString(true);
168312
168312
  const attrs = readAttrs(sftp._biOpt);
168313
168313
  bufferParser.clear();
168314
168314
  if (attrs === undefined)
168315
168315
  return doFatalSFTPError(sftp, "Malformed MKDIR packet");
168316
168316
  sftp._debug && sftp._debug(`SFTP: Inbound: Received MKDIR (id:${reqID})`);
168317
- if (!sftp.emit("MKDIR", reqID, path4, attrs)) {
168317
+ if (!sftp.emit("MKDIR", reqID, path3, attrs)) {
168318
168318
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
168319
168319
  }
168320
168320
  },
168321
168321
  [REQUEST.RMDIR]: (sftp, payload) => {
168322
168322
  bufferParser.init(payload, 1);
168323
168323
  const reqID = bufferParser.readUInt32BE();
168324
- const path4 = bufferParser.readString(true);
168324
+ const path3 = bufferParser.readString(true);
168325
168325
  bufferParser.clear();
168326
- if (path4 === undefined)
168326
+ if (path3 === undefined)
168327
168327
  return doFatalSFTPError(sftp, "Malformed RMDIR packet");
168328
168328
  sftp._debug && sftp._debug(`SFTP: Inbound: Received RMDIR (id:${reqID})`);
168329
- if (!sftp.emit("RMDIR", reqID, path4)) {
168329
+ if (!sftp.emit("RMDIR", reqID, path3)) {
168330
168330
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
168331
168331
  }
168332
168332
  },
168333
168333
  [REQUEST.REALPATH]: (sftp, payload) => {
168334
168334
  bufferParser.init(payload, 1);
168335
168335
  const reqID = bufferParser.readUInt32BE();
168336
- const path4 = bufferParser.readString(true);
168336
+ const path3 = bufferParser.readString(true);
168337
168337
  bufferParser.clear();
168338
- if (path4 === undefined)
168338
+ if (path3 === undefined)
168339
168339
  return doFatalSFTPError(sftp, "Malformed REALPATH packet");
168340
168340
  sftp._debug && sftp._debug(`SFTP: Inbound: Received REALPATH (id:${reqID})`);
168341
- if (!sftp.emit("REALPATH", reqID, path4)) {
168341
+ if (!sftp.emit("REALPATH", reqID, path3)) {
168342
168342
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
168343
168343
  }
168344
168344
  },
168345
168345
  [REQUEST.STAT]: (sftp, payload) => {
168346
168346
  bufferParser.init(payload, 1);
168347
168347
  const reqID = bufferParser.readUInt32BE();
168348
- const path4 = bufferParser.readString(true);
168348
+ const path3 = bufferParser.readString(true);
168349
168349
  bufferParser.clear();
168350
- if (path4 === undefined)
168350
+ if (path3 === undefined)
168351
168351
  return doFatalSFTPError(sftp, "Malformed STAT packet");
168352
168352
  sftp._debug && sftp._debug(`SFTP: Inbound: Received STAT (id:${reqID})`);
168353
- if (!sftp.emit("STAT", reqID, path4)) {
168353
+ if (!sftp.emit("STAT", reqID, path3)) {
168354
168354
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
168355
168355
  }
168356
168356
  },
@@ -168370,12 +168370,12 @@ var require_SFTP = __commonJS((exports, module) => {
168370
168370
  [REQUEST.READLINK]: (sftp, payload) => {
168371
168371
  bufferParser.init(payload, 1);
168372
168372
  const reqID = bufferParser.readUInt32BE();
168373
- const path4 = bufferParser.readString(true);
168373
+ const path3 = bufferParser.readString(true);
168374
168374
  bufferParser.clear();
168375
- if (path4 === undefined)
168375
+ if (path3 === undefined)
168376
168376
  return doFatalSFTPError(sftp, "Malformed READLINK packet");
168377
168377
  sftp._debug && sftp._debug(`SFTP: Inbound: Received READLINK (id:${reqID})`);
168378
- if (!sftp.emit("READLINK", reqID, path4)) {
168378
+ if (!sftp.emit("READLINK", reqID, path3)) {
168379
168379
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
168380
168380
  }
168381
168381
  },
@@ -168444,7 +168444,7 @@ var require_SFTP = __commonJS((exports, module) => {
168444
168444
  function roundUpToMultipleOf8(n) {
168445
168445
  return n + 7 & ~7;
168446
168446
  }
168447
- function ReadStream(sftp, path4, options) {
168447
+ function ReadStream(sftp, path3, options) {
168448
168448
  if (options === undefined)
168449
168449
  options = {};
168450
168450
  else if (typeof options === "string")
@@ -168458,7 +168458,7 @@ var require_SFTP = __commonJS((exports, module) => {
168458
168458
  options.emitClose = false;
168459
168459
  options.autoDestroy = false;
168460
168460
  ReadableStream3.call(this, options);
168461
- this.path = path4;
168461
+ this.path = path3;
168462
168462
  this.flags = options.flags === undefined ? "r" : options.flags;
168463
168463
  this.mode = options.mode === undefined ? 438 : options.mode;
168464
168464
  this.start = options.start;
@@ -168578,7 +168578,7 @@ var require_SFTP = __commonJS((exports, module) => {
168578
168578
  },
168579
168579
  configurable: true
168580
168580
  });
168581
- function WriteStream(sftp, path4, options) {
168581
+ function WriteStream(sftp, path3, options) {
168582
168582
  if (options === undefined)
168583
168583
  options = {};
168584
168584
  else if (typeof options === "string")
@@ -168590,7 +168590,7 @@ var require_SFTP = __commonJS((exports, module) => {
168590
168590
  options.emitClose = false;
168591
168591
  options.autoDestroy = false;
168592
168592
  WritableStream.call(this, options);
168593
- this.path = path4;
168593
+ this.path = path3;
168594
168594
  this.flags = options.flags === undefined ? "w" : options.flags;
168595
168595
  this.mode = options.mode === undefined ? 438 : options.mode;
168596
168596
  this.start = options.start;
@@ -242929,6 +242929,15 @@ function resolvePublisherConfigPath(serverRoot) {
242929
242929
  }
242930
242930
  return null;
242931
242931
  }
242932
+ var PER_MODE_COLOR_KEYS = [
242933
+ "background",
242934
+ "tableHeader",
242935
+ "tableHeaderBackground",
242936
+ "tableBody",
242937
+ "tile",
242938
+ "tileTitle",
242939
+ "mapColor"
242940
+ ];
242932
242941
  var DEFAULT_HIGH_WATER_FRACTION = 0.8;
242933
242942
  var DEFAULT_LOW_WATER_FRACTION = 0.7;
242934
242943
  var DEFAULT_CHECK_INTERVAL_MS = 5000;
@@ -243118,11 +243127,98 @@ var getPublisherConfig = (serverRoot) => {
243118
243127
  if (processedConfig && typeof processedConfig === "object" && "frozenConfig" in processedConfig) {
243119
243128
  frozenConfig = Boolean(processedConfig.frozenConfig);
243120
243129
  }
243130
+ const instanceTheme = sanitizeTheme(processedConfig && typeof processedConfig === "object" && "theme" in processedConfig ? processedConfig.theme : undefined, "publisher.config.json");
243121
243131
  return {
243122
243132
  frozenConfig,
243133
+ ...instanceTheme ? { theme: instanceTheme } : {},
243123
243134
  environments
243124
243135
  };
243125
243136
  };
243137
+ function sanitizeTheme(raw, context) {
243138
+ if (raw === undefined || raw === null)
243139
+ return;
243140
+ if (typeof raw !== "object" || Array.isArray(raw)) {
243141
+ logger.warn(`Invalid "theme" in ${context}: expected an object. Ignoring.`);
243142
+ return;
243143
+ }
243144
+ const obj = raw;
243145
+ const theme = {};
243146
+ if ("defaultMode" in obj) {
243147
+ const mode = obj.defaultMode;
243148
+ if (mode === "light" || mode === "dark" || mode === "auto") {
243149
+ theme.defaultMode = mode;
243150
+ } else {
243151
+ logger.warn(`Invalid "theme.defaultMode" in ${context}: expected "light" | "dark" | "auto" (got ${JSON.stringify(mode)}). Ignoring field.`);
243152
+ }
243153
+ }
243154
+ if ("allowUserToggle" in obj) {
243155
+ const value = obj.allowUserToggle;
243156
+ if (typeof value === "boolean") {
243157
+ theme.allowUserToggle = value;
243158
+ } else {
243159
+ logger.warn(`Invalid "theme.allowUserToggle" in ${context}: expected a boolean (got ${JSON.stringify(value)}). Ignoring field.`);
243160
+ }
243161
+ }
243162
+ if ("palette" in obj && obj.palette && typeof obj.palette === "object") {
243163
+ const palette = obj.palette;
243164
+ const sanitized = {};
243165
+ if (Array.isArray(palette.series)) {
243166
+ sanitized.series = palette.series.filter((c) => typeof c === "string");
243167
+ }
243168
+ for (const key of PER_MODE_COLOR_KEYS) {
243169
+ const raw2 = palette[key];
243170
+ if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2))
243171
+ continue;
243172
+ const r = raw2;
243173
+ const out = {};
243174
+ if (typeof r.light === "string")
243175
+ out.light = r.light;
243176
+ if (typeof r.dark === "string")
243177
+ out.dark = r.dark;
243178
+ if (Object.keys(out).length > 0) {
243179
+ sanitized[key] = out;
243180
+ }
243181
+ }
243182
+ if (Object.keys(sanitized).length > 0)
243183
+ theme.palette = sanitized;
243184
+ }
243185
+ if ("font" in obj && obj.font && typeof obj.font === "object") {
243186
+ const font = obj.font;
243187
+ const sanitized = {};
243188
+ if (typeof font.family === "string")
243189
+ sanitized.family = font.family;
243190
+ if (typeof font.size === "number" && Number.isFinite(font.size)) {
243191
+ sanitized.size = font.size;
243192
+ }
243193
+ if (Object.keys(sanitized).length > 0)
243194
+ theme.font = sanitized;
243195
+ }
243196
+ return Object.keys(theme).length > 0 ? theme : undefined;
243197
+ }
243198
+ function mergeThemes(base, override) {
243199
+ if (!base)
243200
+ return override;
243201
+ if (!override)
243202
+ return base;
243203
+ const merged = { ...base, ...override };
243204
+ if (base.palette || override.palette) {
243205
+ merged.palette = {
243206
+ ...base.palette ?? {},
243207
+ ...override.palette ?? {}
243208
+ };
243209
+ for (const key of PER_MODE_COLOR_KEYS) {
243210
+ const b = base.palette?.[key];
243211
+ const o = override.palette?.[key];
243212
+ if (b || o) {
243213
+ merged.palette[key] = { ...b ?? {}, ...o ?? {} };
243214
+ }
243215
+ }
243216
+ }
243217
+ if (base.font || override.font) {
243218
+ merged.font = { ...base.font ?? {}, ...override.font ?? {} };
243219
+ }
243220
+ return merged;
243221
+ }
243126
243222
  var isPublisherConfigFrozen = (serverRoot) => {
243127
243223
  try {
243128
243224
  const publisherConfig = getPublisherConfig(serverRoot);
@@ -243198,17 +243294,29 @@ var getProcessedPublisherConfig = (serverRoot) => {
243198
243294
  logger.warn(`Environment "${environment.name}" has no valid packages. Skipping entry.`);
243199
243295
  continue;
243200
243296
  }
243297
+ const envTheme = sanitizeTheme(environment.theme, `environment "${environment.name}"`);
243298
+ const resolvedTheme = mergeThemes(rawConfig.theme, envTheme);
243201
243299
  validEnvironments.push({
243202
243300
  name: environment.name,
243203
243301
  packages: validPackages,
243204
- connections: convertConnectionsToApiConnections(environment.connections || [])
243302
+ connections: convertConnectionsToApiConnections(environment.connections || []),
243303
+ ...resolvedTheme ? { theme: resolvedTheme } : {}
243205
243304
  });
243206
243305
  }
243207
243306
  return {
243208
243307
  frozenConfig: rawConfig.frozenConfig ?? false,
243308
+ ...rawConfig.theme ? { theme: rawConfig.theme } : {},
243209
243309
  environments: validEnvironments
243210
243310
  };
243211
243311
  };
243312
+ var getInstanceTheme = (serverRoot) => {
243313
+ try {
243314
+ return getPublisherConfig(serverRoot).theme;
243315
+ } catch (error) {
243316
+ logger.error(`Error reading instance theme from ${PUBLISHER_CONFIG_NAME}`, { error });
243317
+ return;
243318
+ }
243319
+ };
243212
243320
 
243213
243321
  // src/controller/connection.controller.ts
243214
243322
  init_errors();
@@ -246665,7 +246773,138 @@ function safeJoinUnderRoot(root, ...segments) {
246665
246773
 
246666
246774
  // src/service/connection_config.ts
246667
246775
  import { createPrivateKey } from "crypto";
246776
+ import { existsSync } from "fs";
246668
246777
  import path3 from "path";
246778
+
246779
+ // src/service/proxy.ts
246780
+ init_logger();
246781
+ var import_ssh2 = __toESM(require_lib10(), 1);
246782
+ import net from "net";
246783
+ var SSH_CONNECT_TIMEOUT_MS = 15000;
246784
+ var SSH_KEEPALIVE_INTERVAL_MS = 15000;
246785
+ function parseHostKeys(raw) {
246786
+ const keys = new Set;
246787
+ for (const line of raw.split(/\r?\n/)) {
246788
+ const trimmed = line.trim();
246789
+ if (!trimmed || trimmed.startsWith("#"))
246790
+ continue;
246791
+ const tokens = trimmed.split(/\s+/);
246792
+ const blob = tokens.find((t) => /^AAAA[A-Za-z0-9+/]+={0,2}$/.test(t));
246793
+ const key = blob ?? tokens[tokens.length - 1];
246794
+ if (key)
246795
+ keys.add(key);
246796
+ }
246797
+ return keys;
246798
+ }
246799
+ async function openProxy(proxy, target) {
246800
+ if (proxy.type === "ssh") {
246801
+ if (!proxy.ssh) {
246802
+ throw new Error("ConnectionProxy type is 'ssh' but the 'ssh' config object is missing.");
246803
+ }
246804
+ return openSshProxy(proxy.ssh, target);
246805
+ }
246806
+ throw new Error(`Proxy type '${proxy.type}' is not supported yet. Only 'ssh' is implemented.`);
246807
+ }
246808
+ function openSshProxy(ssh, target) {
246809
+ return new Promise((resolve2, reject) => {
246810
+ const client = new import_ssh2.Client;
246811
+ let localPort = 0;
246812
+ let server;
246813
+ let settled = false;
246814
+ const sockets = new Set;
246815
+ function fail(err) {
246816
+ if (settled)
246817
+ return;
246818
+ settled = true;
246819
+ server?.close();
246820
+ client.end();
246821
+ reject(err);
246822
+ }
246823
+ const connectConfig = {
246824
+ host: ssh.host,
246825
+ port: ssh.port ?? 22,
246826
+ username: ssh.username,
246827
+ privateKey: ssh.privateKey,
246828
+ ...ssh.privateKeyPass ? { passphrase: ssh.privateKeyPass } : {},
246829
+ readyTimeout: SSH_CONNECT_TIMEOUT_MS,
246830
+ keepaliveInterval: SSH_KEEPALIVE_INTERVAL_MS,
246831
+ keepaliveCountMax: 3,
246832
+ hostVerifier: (key) => {
246833
+ const presented = key.toString("base64");
246834
+ if (ssh.hostKey) {
246835
+ const pinned = parseHostKeys(ssh.hostKey);
246836
+ if (pinned.has(presented)) {
246837
+ return true;
246838
+ }
246839
+ fail(new Error(`SSH host-key verification failed for ${ssh.host}: the presented key ` + `(${presented.slice(0, 24)}…) is not among the ${pinned.size} pinned ` + `host key(s).`));
246840
+ return false;
246841
+ }
246842
+ logger.warn(`Connecting to SSH bastion ${ssh.host} without host-key verification (no hostKey pinned).`);
246843
+ return true;
246844
+ }
246845
+ };
246846
+ client.on("error", (err) => fail(err));
246847
+ client.on("ready", () => {
246848
+ server = net.createServer((socket) => {
246849
+ sockets.add(socket);
246850
+ socket.on("close", () => sockets.delete(socket));
246851
+ const earlyData = [];
246852
+ socket.on("data", (chunk) => earlyData.push(chunk));
246853
+ client.forwardOut("127.0.0.1", localPort, target.host, target.port, (err, channel) => {
246854
+ if (err) {
246855
+ socket.destroy(err);
246856
+ return;
246857
+ }
246858
+ if (socket.destroyed) {
246859
+ channel.destroy();
246860
+ return;
246861
+ }
246862
+ for (const chunk of earlyData) {
246863
+ channel.write(chunk);
246864
+ }
246865
+ earlyData.length = 0;
246866
+ socket.removeAllListeners("data");
246867
+ socket.pipe(channel);
246868
+ channel.pipe(socket);
246869
+ socket.on("error", () => channel.destroy());
246870
+ channel.on("error", () => socket.destroy());
246871
+ socket.on("close", () => channel.destroy());
246872
+ channel.on("close", () => socket.destroy());
246873
+ });
246874
+ });
246875
+ server.listen(0, "127.0.0.1", () => {
246876
+ const addr = server.address();
246877
+ if (!addr || typeof addr === "string") {
246878
+ fail(new Error("Failed to obtain local listen port for SSH proxy."));
246879
+ return;
246880
+ }
246881
+ localPort = addr.port;
246882
+ if (settled)
246883
+ return;
246884
+ settled = true;
246885
+ resolve2({
246886
+ host: "127.0.0.1",
246887
+ port: localPort,
246888
+ close() {
246889
+ return new Promise((res) => {
246890
+ for (const s of sockets)
246891
+ s.destroy();
246892
+ server.close(() => {
246893
+ client.end();
246894
+ res();
246895
+ });
246896
+ });
246897
+ }
246898
+ });
246899
+ });
246900
+ server.on("error", (err) => fail(err));
246901
+ });
246902
+ client.connect(connectConfig);
246903
+ });
246904
+ }
246905
+
246906
+ // src/service/connection_config.ts
246907
+ var PROXIED_SSLMODES = ["disable", "no-verify", "verify-ca"];
246669
246908
  var PUBLISHER_DUCKDB_API_FIELDS = new Set(["attachedDatabases"]);
246670
246909
  function normalizeSnowflakePrivateKey(privateKey) {
246671
246910
  let privateKeyContent = privateKey.trim();
@@ -246850,6 +247089,29 @@ function validateConnectionShape(connection) {
246850
247089
  if (!connection.postgresConnection?.host || !connection.postgresConnection?.port) {
246851
247090
  throw new Error(`Connection proxy on '${connection.name}' requires explicit host and port on the ` + `postgres connection; the connectionString form is not supported with a proxy.`);
246852
247091
  }
247092
+ const hostKey = connection.proxy.ssh?.hostKey;
247093
+ if (hostKey && parseHostKeys(hostKey).size === 0) {
247094
+ throw new Error(`Connection proxy on '${connection.name}' has a hostKey with no usable host-key line ` + `(only blanks/comments). Provide an OpenSSH known_hosts line or base64 blob, or omit ` + `hostKey to connect unpinned.`);
247095
+ }
247096
+ const sslmode = connection.postgresConnection?.sslmode;
247097
+ if (sslmode != null) {
247098
+ if (!PROXIED_SSLMODES.includes(sslmode)) {
247099
+ throw new Error(`Connection proxy on '${connection.name}' has unsupported sslmode '${sslmode}' ` + `(expected ${PROXIED_SSLMODES.join(" | ")}).`);
247100
+ }
247101
+ if (sslmode === "verify-ca") {
247102
+ const caBundle = process.env.NODE_EXTRA_CA_CERTS;
247103
+ if (!caBundle || !existsSync(caBundle)) {
247104
+ throw new Error(`Connection proxy on '${connection.name}' uses sslmode 'verify-ca' but no readable ` + `CA bundle is available (NODE_EXTRA_CA_CERTS is unset or points at a missing file). ` + `Add the CA bundle to the image or use sslmode 'no-verify'.`);
247105
+ }
247106
+ }
247107
+ }
247108
+ const dbName = connection.postgresConnection?.databaseName;
247109
+ if (dbName && decodeURI(encodeURIComponent(dbName)) !== dbName) {
247110
+ throw new Error(`Connection proxy on '${connection.name}' has a database name with characters that can't ` + `be carried over a proxied connection (${JSON.stringify(dbName)}). Use a database name ` + `without URI-reserved characters (; , / ? : @ & = + $ #).`);
247111
+ }
247112
+ }
247113
+ if (!connection.proxy && connection.postgresConnection?.sslmode) {
247114
+ throw new Error(`Connection '${connection.name}' sets postgresConnection.sslmode but has no proxy; sslmode is ` + `only supported for proxied connections (direct connections use the deployment PGSSLMODE).`);
246853
247115
  }
246854
247116
  switch (connection.type) {
246855
247117
  case "postgres":
@@ -247126,126 +247388,6 @@ function validateAndBuildTrinoCoreConfig(trinoConfig) {
247126
247388
  throw new Error(`Invalid Trino connection: expected "http://server:port" or "https://server:port".`);
247127
247389
  }
247128
247390
 
247129
- // src/service/proxy.ts
247130
- var import_ssh2 = __toESM(require_lib10(), 1);
247131
- import net from "net";
247132
- var SSH_CONNECT_TIMEOUT_MS = 15000;
247133
- var SSH_KEEPALIVE_INTERVAL_MS = 15000;
247134
- function extractHostKeyBase64(raw) {
247135
- const tokens = raw.trim().split(/\s+/);
247136
- const blob = tokens.find((t) => /^AAAA[A-Za-z0-9+/]+={0,2}$/.test(t));
247137
- return blob ?? tokens[tokens.length - 1] ?? "";
247138
- }
247139
- async function openProxy(proxy, target) {
247140
- if (proxy.type === "ssh") {
247141
- if (!proxy.ssh) {
247142
- throw new Error("ConnectionProxy type is 'ssh' but the 'ssh' config object is missing.");
247143
- }
247144
- return openSshProxy(proxy.ssh, target);
247145
- }
247146
- throw new Error(`Proxy type '${proxy.type}' is not supported yet. Only 'ssh' is implemented.`);
247147
- }
247148
- function openSshProxy(ssh, target) {
247149
- return new Promise((resolve2, reject) => {
247150
- const client = new import_ssh2.Client;
247151
- let localPort = 0;
247152
- let server;
247153
- let settled = false;
247154
- const sockets = new Set;
247155
- function fail(err) {
247156
- if (settled)
247157
- return;
247158
- settled = true;
247159
- server?.close();
247160
- client.end();
247161
- reject(err);
247162
- }
247163
- const connectConfig = {
247164
- host: ssh.host,
247165
- port: ssh.port ?? 22,
247166
- username: ssh.username,
247167
- privateKey: ssh.privateKey,
247168
- ...ssh.privateKeyPass ? { passphrase: ssh.privateKeyPass } : {},
247169
- readyTimeout: SSH_CONNECT_TIMEOUT_MS,
247170
- keepaliveInterval: SSH_KEEPALIVE_INTERVAL_MS,
247171
- keepaliveCountMax: 3,
247172
- hostVerifier: (key) => {
247173
- const presented = key.toString("base64");
247174
- if (ssh.hostKey) {
247175
- const expected = extractHostKeyBase64(ssh.hostKey);
247176
- if (presented !== expected) {
247177
- fail(new Error(`SSH host-key mismatch for ${ssh.host}: expected ${expected.slice(0, 24)}… but got ${presented.slice(0, 24)}….`));
247178
- return false;
247179
- }
247180
- return true;
247181
- }
247182
- if (process.env.PUBLISHER_SSH_ALLOW_UNKNOWN_HOSTKEY === "true") {
247183
- return true;
247184
- }
247185
- fail(new Error(`SSH connection to ${ssh.host} refused: no hostKey provided and ` + `PUBLISHER_SSH_ALLOW_UNKNOWN_HOSTKEY is not 'true'. ` + `Set hostKey to the bastion's host public key (an OpenSSH ` + `known_hosts line or its base64 blob, e.g. from ssh-keyscan).`));
247186
- return false;
247187
- }
247188
- };
247189
- client.on("error", (err) => fail(err));
247190
- client.on("ready", () => {
247191
- server = net.createServer((socket) => {
247192
- sockets.add(socket);
247193
- socket.on("close", () => sockets.delete(socket));
247194
- const earlyData = [];
247195
- socket.on("data", (chunk) => earlyData.push(chunk));
247196
- client.forwardOut("127.0.0.1", localPort, target.host, target.port, (err, channel) => {
247197
- if (err) {
247198
- socket.destroy(err);
247199
- return;
247200
- }
247201
- if (socket.destroyed) {
247202
- channel.destroy();
247203
- return;
247204
- }
247205
- for (const chunk of earlyData) {
247206
- channel.write(chunk);
247207
- }
247208
- earlyData.length = 0;
247209
- socket.removeAllListeners("data");
247210
- socket.pipe(channel);
247211
- channel.pipe(socket);
247212
- socket.on("error", () => channel.destroy());
247213
- channel.on("error", () => socket.destroy());
247214
- socket.on("close", () => channel.destroy());
247215
- channel.on("close", () => socket.destroy());
247216
- });
247217
- });
247218
- server.listen(0, "127.0.0.1", () => {
247219
- const addr = server.address();
247220
- if (!addr || typeof addr === "string") {
247221
- fail(new Error("Failed to obtain local listen port for SSH proxy."));
247222
- return;
247223
- }
247224
- localPort = addr.port;
247225
- if (settled)
247226
- return;
247227
- settled = true;
247228
- resolve2({
247229
- host: "127.0.0.1",
247230
- port: localPort,
247231
- close() {
247232
- return new Promise((res) => {
247233
- for (const s of sockets)
247234
- s.destroy();
247235
- server.close(() => {
247236
- client.end();
247237
- res();
247238
- });
247239
- });
247240
- }
247241
- });
247242
- });
247243
- server.on("error", (err) => fail(err));
247244
- });
247245
- client.connect(connectConfig);
247246
- });
247247
- }
247248
-
247249
247391
  // src/service/connection.ts
247250
247392
  async function installAndLoadExtension(connection, extensionName, fromCommunity = false) {
247251
247393
  try {
@@ -247798,19 +247940,37 @@ function buildSnowflakePrivateKeyConnection(metadata) {
247798
247940
  poolOptions: buildPoolOptions({ poolMin: 1, poolMax: 20 })
247799
247941
  });
247800
247942
  }
247943
+ function buildProxiedSslQuery(name, sslmode) {
247944
+ const caBundle = process.env.NODE_EXTRA_CA_CERTS;
247945
+ const mode = sslmode ?? "no-verify";
247946
+ switch (mode) {
247947
+ case "disable":
247948
+ return "?sslmode=disable";
247949
+ case "no-verify":
247950
+ return "?sslmode=no-verify";
247951
+ case "verify-ca":
247952
+ if (!caBundle) {
247953
+ throw new Error(`Connection proxy on '${name}' uses sslmode 'verify-ca' but no trusted CA bundle is available (NODE_EXTRA_CA_CERTS is unset). Add the CA bundle to the image or use sslmode 'no-verify'.`);
247954
+ }
247955
+ return `?uselibpqcompat=true&sslmode=verify-ca&sslrootcert=${encodeURIComponent(caBundle)}`;
247956
+ default:
247957
+ throw new Error(`Connection proxy on '${name}' has unsupported sslmode '${mode}' (expected disable | no-verify | verify-ca).`);
247958
+ }
247959
+ }
247801
247960
  function buildProxiedPostgresConnection(metadata, endpoint) {
247802
247961
  const name = metadata.apiConnection.name;
247803
247962
  const pg = metadata.apiConnection.postgresConnection;
247804
247963
  if (!pg) {
247805
247964
  throw new Error(`Proxied connection '${name}' has type 'postgres' but no postgresConnection config.`);
247806
247965
  }
247966
+ const enc = encodeURIComponent;
247967
+ const auth = pg.userName ? `${enc(pg.userName)}${pg.password ? `:${enc(pg.password)}` : ""}@` : "";
247968
+ const db = pg.databaseName ? `/${enc(pg.databaseName)}` : "";
247969
+ const ssl = buildProxiedSslQuery(name, pg.sslmode);
247970
+ const connectionString = `postgresql://${auth}${endpoint.host}:${endpoint.port}${db}${ssl}`;
247807
247971
  return new PooledPostgresConnection({
247808
247972
  name,
247809
- host: endpoint.host,
247810
- port: endpoint.port,
247811
- username: pg.userName,
247812
- password: pg.password,
247813
- databaseName: pg.databaseName,
247973
+ connectionString,
247814
247974
  poolMin: 1,
247815
247975
  poolMax: 20
247816
247976
  });
@@ -247824,6 +247984,13 @@ function buildAzureDuckDBConnection(metadata, entry) {
247824
247984
  function getMetadataForLookup(metadata, name) {
247825
247985
  return name ? metadata.get(name) : undefined;
247826
247986
  }
247987
+ function applyConnectionFingerprint(connection, metadata) {
247988
+ const fingerprint = metadata?.apiConnection.fingerprint;
247989
+ if (fingerprint) {
247990
+ connection.getDigest = () => fingerprint;
247991
+ }
247992
+ return connection;
247993
+ }
247827
247994
  function isDuckDBConnection(connection) {
247828
247995
  return connection instanceof DuckDBConnection;
247829
247996
  }
@@ -247849,9 +248016,8 @@ function buildEnvironmentMalloyConfig(connections = [], environmentPath = "", is
247849
248016
  }
247850
248017
  await attachPromise;
247851
248018
  }
247852
- malloyConfig.wrapConnections((base) => ({
247853
- lookupConnection: async (name) => {
247854
- const metadata = getMetadataForLookup(assembled.metadata, name);
248019
+ malloyConfig.wrapConnections((base) => {
248020
+ const resolveConnection = async (name, metadata) => {
247855
248021
  if (metadata?.isDuckLake) {
247856
248022
  let connectionPromise = duckLakeCache.get(name);
247857
248023
  if (!connectionPromise) {
@@ -247923,8 +248089,14 @@ function buildEnvironmentMalloyConfig(connections = [], environmentPath = "", is
247923
248089
  await attachOnce(connection, metadata);
247924
248090
  }
247925
248091
  return connection;
247926
- }
247927
- }));
248092
+ };
248093
+ return {
248094
+ lookupConnection: async (name) => {
248095
+ const metadata = getMetadataForLookup(assembled.metadata, name);
248096
+ return applyConnectionFingerprint(await resolveConnection(name, metadata), metadata);
248097
+ }
248098
+ };
248099
+ });
247928
248100
  return {
247929
248101
  malloyConfig,
247930
248102
  apiConnections: assembled.apiConnections,
@@ -256465,13 +256637,10 @@ class DuckDBRepository {
256465
256637
  init_logger();
256466
256638
  async function initializeSchema(db, force = false) {
256467
256639
  const initialized = await db.isInitialized();
256468
- if (initialized && !force) {
256469
- return;
256470
- }
256471
256640
  if (force) {
256472
256641
  logger.info("Reinitializing database schema dropping and recreating all tables");
256473
256642
  await dropAllTables(db);
256474
- } else {
256643
+ } else if (!initialized) {
256475
256644
  await dropLegacyProjectSchema(db);
256476
256645
  logger.info("Creating database schema for the first time...");
256477
256646
  }
@@ -256530,6 +256699,14 @@ async function initializeSchema(db, force = false) {
256530
256699
  FOREIGN KEY (environment_id) REFERENCES environments(id)
256531
256700
  )
256532
256701
  `);
256702
+ await db.run(`
256703
+ CREATE TABLE IF NOT EXISTS themes (
256704
+ id VARCHAR PRIMARY KEY,
256705
+ payload JSON NOT NULL,
256706
+ created_at TIMESTAMP NOT NULL,
256707
+ updated_at TIMESTAMP NOT NULL
256708
+ )
256709
+ `);
256533
256710
  await db.run("CREATE INDEX IF NOT EXISTS idx_packages_environment_id ON packages(environment_id)");
256534
256711
  await db.run("CREATE INDEX IF NOT EXISTS idx_connections_environment_id ON connections(environment_id)");
256535
256712
  await db.run("CREATE INDEX IF NOT EXISTS idx_materializations_environment_package ON materializations(environment_id, package_name)");
@@ -256562,7 +256739,8 @@ async function dropAllTables(db) {
256562
256739
  "materializations",
256563
256740
  "packages",
256564
256741
  "connections",
256565
- "environments"
256742
+ "environments",
256743
+ "themes"
256566
256744
  ];
256567
256745
  logger.info("Dropping tables:", tables.join(", "));
256568
256746
  for (const table of tables) {
@@ -256608,6 +256786,15 @@ class StorageManager {
256608
256786
  this.connection = connection;
256609
256787
  this.repository = new DuckDBRepository(connection);
256610
256788
  }
256789
+ getDuckDbConnection() {
256790
+ if (!this.connection) {
256791
+ throw new Error("Storage not initialized");
256792
+ }
256793
+ if (!(this.connection instanceof DuckDBConnection2)) {
256794
+ throw new Error("Underlying storage connection is not DuckDB");
256795
+ }
256796
+ return this.connection;
256797
+ }
256611
256798
  getRepository() {
256612
256799
  if (!this.repository) {
256613
256800
  throw new Error("Storage not initialized. Call initialize() first.");
@@ -256754,16 +256941,16 @@ async function fetchManifestEntries(uri) {
256754
256941
  throw new Error(`Failed to parse build manifest at ${uri}: ${err instanceof Error ? err.message : String(err)}`);
256755
256942
  }
256756
256943
  const entries = {};
256757
- for (const [buildId, entry] of Object.entries(parsed.entries ?? {})) {
256944
+ for (const [sourceEntityId, entry] of Object.entries(parsed.entries ?? {})) {
256758
256945
  const physicalTableName = entry?.physicalTableName;
256759
256946
  if (!physicalTableName) {
256760
256947
  logger.warn("Manifest entry has no physicalTableName; skipping", {
256761
256948
  uri,
256762
- buildId
256949
+ sourceEntityId
256763
256950
  });
256764
256951
  continue;
256765
256952
  }
256766
- entries[buildId] = { tableName: physicalTableName };
256953
+ entries[sourceEntityId] = { tableName: physicalTableName };
256767
256954
  }
256768
256955
  return entries;
256769
256956
  }
@@ -258255,7 +258442,7 @@ function* iterGraphSources(graph, sources) {
258255
258442
  }
258256
258443
  }
258257
258444
  }
258258
- function computeBuildId(source, connectionDigests) {
258445
+ function computeSourceEntityId(source, connectionDigests) {
258259
258446
  return source.makeBuildId(connectionDigests[source.connectionName], source.getSQL());
258260
258447
  }
258261
258448
  async function resolvePackageConnections(pkg, names) {
@@ -258343,7 +258530,7 @@ function deriveBuildPlan(graphs, sources, connectionDigests, sourceNames, source
258343
258530
  sourceID: source.sourceID,
258344
258531
  connectionName: source.connectionName,
258345
258532
  dialect: source.dialectName,
258346
- buildId: computeBuildId(source, connectionDigests),
258533
+ sourceEntityId: computeSourceEntityId(source, connectionDigests),
258347
258534
  sql: source.getSQL(),
258348
258535
  columns: deriveColumns(source),
258349
258536
  annotationFields: deriveAnnotationFields(source),
@@ -260983,7 +261170,7 @@ class MaterializationController {
260983
261170
  }
260984
261171
  const instruction = raw;
260985
261172
  const required = [
260986
- "buildId",
261173
+ "sourceEntityId",
260987
261174
  "materializedTableId",
260988
261175
  "physicalTableName",
260989
261176
  "realization"
@@ -260997,7 +261184,7 @@ class MaterializationController {
260997
261184
  throw new BadRequestError("Build instruction 'realization' must be COPY or SNAPSHOT");
260998
261185
  }
260999
261186
  return {
261000
- buildId: instruction.buildId,
261187
+ sourceEntityId: instruction.sourceEntityId,
261001
261188
  sourceID: typeof instruction.sourceID === "string" ? instruction.sourceID : undefined,
261002
261189
  materializedTableId: instruction.materializedTableId,
261003
261190
  physicalTableName: instruction.physicalTableName,
@@ -261018,6 +261205,40 @@ class MaterializationController {
261018
261205
  }
261019
261206
  }
261020
261207
 
261208
+ // src/controller/theme.controller.ts
261209
+ init_errors();
261210
+ class ThemeController {
261211
+ themeStore;
261212
+ serverRoot;
261213
+ constructor(themeStore, serverRoot) {
261214
+ this.themeStore = themeStore;
261215
+ this.serverRoot = serverRoot;
261216
+ }
261217
+ getTheme = async () => {
261218
+ const theme = await this.themeStore.get();
261219
+ return theme ?? {};
261220
+ };
261221
+ putTheme = async (body) => {
261222
+ if (isPublisherConfigFrozen(this.serverRoot)) {
261223
+ throw new FrozenConfigError('Cannot edit theme: publisher.config.json has "frozenConfig": true.');
261224
+ }
261225
+ const isEmptyObject2 = typeof body === "object" && body !== null && !Array.isArray(body) && Object.keys(body).length === 0;
261226
+ const sanitized = sanitizeTheme(body, "PUT /api/v0/theme");
261227
+ if (!sanitized && !isEmptyObject2) {
261228
+ throw new BadRequestError("Theme payload was malformed (expected an object).");
261229
+ }
261230
+ const saved = await this.themeStore.set(sanitized ?? {});
261231
+ return saved;
261232
+ };
261233
+ resetTheme = async () => {
261234
+ if (isPublisherConfigFrozen(this.serverRoot)) {
261235
+ throw new FrozenConfigError('Cannot reset theme: publisher.config.json has "frozenConfig": true.');
261236
+ }
261237
+ const reseeded = await this.themeStore.reset();
261238
+ return reseeded ?? {};
261239
+ };
261240
+ }
261241
+
261021
261242
  // ../../node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
261022
261243
  var DEFAULT_REQUEST_TIMEOUT_MSEC = 60000;
261023
261244
 
@@ -267581,9 +267802,9 @@ async function resolveEnvironmentId(repository, environmentName) {
267581
267802
  }
267582
267803
 
267583
267804
  // src/service/materialization_service.ts
267584
- var STAGING_BUILD_ID_LEN = 12;
267585
- function stagingSuffix(buildId) {
267586
- return `_${buildId.substring(0, STAGING_BUILD_ID_LEN)}`;
267805
+ var STAGING_ID_LEN = 12;
267806
+ function stagingSuffix(sourceEntityId) {
267807
+ return `_${sourceEntityId.replace(/-/g, "").substring(0, STAGING_ID_LEN)}`;
267587
267808
  }
267588
267809
  function selfAssignTableName(persistSource) {
267589
267810
  return deriveAnnotationFields(persistSource).name || persistSource.name;
@@ -267745,18 +267966,18 @@ class MaterializationService {
267745
267966
  for (const persistSource of iterGraphSources(graph, compiled.sources)) {
267746
267967
  if (include && !include.has(persistSource.name))
267747
267968
  continue;
267748
- const buildId = computeBuildId(persistSource, compiled.connectionDigests);
267749
- if (seen.has(buildId))
267969
+ const sourceEntityId = computeSourceEntityId(persistSource, compiled.connectionDigests);
267970
+ if (seen.has(sourceEntityId))
267750
267971
  continue;
267751
- seen.add(buildId);
267752
- const prior = priorEntries[buildId];
267972
+ seen.add(sourceEntityId);
267973
+ const prior = priorEntries[sourceEntityId];
267753
267974
  if (prior && prior.physicalTableName) {
267754
- carried[buildId] = prior;
267975
+ carried[sourceEntityId] = prior;
267755
267976
  continue;
267756
267977
  }
267757
267978
  instructions.push({
267758
- buildId,
267759
- materializedTableId: `local-${buildId.substring(0, STAGING_BUILD_ID_LEN)}`,
267979
+ sourceEntityId,
267980
+ materializedTableId: `local-${sourceEntityId.substring(0, STAGING_ID_LEN)}`,
267760
267981
  physicalTableName: selfAssignTableName(persistSource),
267761
267982
  realization: "COPY"
267762
267983
  });
@@ -267777,9 +267998,11 @@ class MaterializationService {
267777
267998
  }
267778
267999
  async autoLoadManifest(environment, packageName, entries) {
267779
268000
  const manifestEntries = {};
267780
- for (const [buildId, entry] of Object.entries(entries)) {
268001
+ for (const [sourceEntityId, entry] of Object.entries(entries)) {
267781
268002
  if (entry.physicalTableName) {
267782
- manifestEntries[buildId] = { tableName: entry.physicalTableName };
268003
+ manifestEntries[sourceEntityId] = {
268004
+ tableName: entry.physicalTableName
268005
+ };
267783
268006
  }
267784
268007
  }
267785
268008
  try {
@@ -267801,13 +268024,13 @@ class MaterializationService {
267801
268024
  if (!plan) {
267802
268025
  throw new BadRequestError("Package has no persist sources; buildInstructions cannot be applied");
267803
268026
  }
267804
- const plannedBuildIds = new Set;
268027
+ const plannedSourceEntityIds = new Set;
267805
268028
  for (const source of Object.values(plan.sources)) {
267806
- plannedBuildIds.add(source.buildId);
268029
+ plannedSourceEntityIds.add(source.sourceEntityId);
267807
268030
  }
267808
268031
  for (const instruction of instructions) {
267809
- if (!plannedBuildIds.has(instruction.buildId)) {
267810
- throw new BadRequestError(`Instruction references unknown buildId '${instruction.buildId}'`);
268032
+ if (!plannedSourceEntityIds.has(instruction.sourceEntityId)) {
268033
+ throw new BadRequestError(`Instruction references unknown sourceEntityId '${instruction.sourceEntityId}'`);
267811
268034
  }
267812
268035
  if (instruction.realization === "SNAPSHOT") {
267813
268036
  throw new BadRequestError("realization=SNAPSHOT is not supported (COPY only)");
@@ -267817,20 +268040,22 @@ class MaterializationService {
267817
268040
  async executeInstructedBuild(compiled, instructions, seedEntries, signal) {
267818
268041
  const { graphs, sources, connectionDigests, connections } = compiled;
267819
268042
  const bySourceID = new Map;
267820
- const byBuildId = new Map;
268043
+ const bySourceEntityId = new Map;
267821
268044
  for (const instruction of instructions) {
267822
268045
  if (instruction.sourceID) {
267823
268046
  bySourceID.set(instruction.sourceID, instruction);
267824
268047
  }
267825
- byBuildId.set(instruction.buildId, instruction);
268048
+ bySourceEntityId.set(instruction.sourceEntityId, instruction);
267826
268049
  }
267827
268050
  const manifest = new Manifest;
267828
268051
  const entries = {};
267829
- for (const [buildId, entry] of Object.entries(seedEntries)) {
268052
+ for (const [sourceEntityId, entry] of Object.entries(seedEntries)) {
267830
268053
  if (entry.physicalTableName) {
267831
- manifest.update(buildId, { tableName: entry.physicalTableName });
268054
+ manifest.update(sourceEntityId, {
268055
+ tableName: entry.physicalTableName
268056
+ });
267832
268057
  }
267833
- entries[buildId] = entry;
268058
+ entries[sourceEntityId] = entry;
267834
268059
  }
267835
268060
  for (const graph of graphs) {
267836
268061
  const connection = connections.get(graph.connectionName);
@@ -267840,25 +268065,25 @@ class MaterializationService {
267840
268065
  for (const persistSource of iterGraphSources(graph, sources)) {
267841
268066
  if (signal.aborted)
267842
268067
  throw new Error("Build cancelled");
267843
- const buildId = computeBuildId(persistSource, connectionDigests);
267844
- const instruction = bySourceID.get(persistSource.sourceID) ?? byBuildId.get(buildId);
268068
+ const sourceEntityId = computeSourceEntityId(persistSource, connectionDigests);
268069
+ const instruction = bySourceID.get(persistSource.sourceID) ?? bySourceEntityId.get(sourceEntityId);
267845
268070
  if (!instruction)
267846
268071
  continue;
267847
268072
  const entry = await this.buildOneSource(persistSource, instruction, connection, connectionDigests, manifest);
267848
- entries[buildId] = entry;
268073
+ entries[sourceEntityId] = entry;
267849
268074
  }
267850
268075
  }
267851
268076
  return entries;
267852
268077
  }
267853
268078
  async buildOneSource(persistSource, instruction, connection, connectionDigests, manifest) {
267854
- const buildId = instruction.buildId;
268079
+ const sourceEntityId = instruction.sourceEntityId;
267855
268080
  const physicalTableName = instruction.physicalTableName;
267856
268081
  const buildSQL = persistSource.getSQL({
267857
268082
  buildManifest: manifest.buildManifest,
267858
268083
  connectionDigests
267859
268084
  });
267860
268085
  const bareName = bareTableName(physicalTableName);
267861
- const stagingTableName = `${physicalTableName}${stagingSuffix(buildId)}`;
268086
+ const stagingTableName = `${physicalTableName}${stagingSuffix(sourceEntityId)}`;
267862
268087
  const dialect = persistSource.dialectName;
267863
268088
  const quotedStaging = quoteTablePath(stagingTableName, dialect);
267864
268089
  const quotedPhysical = quoteTablePath(physicalTableName, dialect);
@@ -267881,7 +268106,7 @@ class MaterializationService {
267881
268106
  }
267882
268107
  throw err;
267883
268108
  }
267884
- manifest.update(buildId, { tableName: physicalTableName });
268109
+ manifest.update(sourceEntityId, { tableName: physicalTableName });
267885
268110
  const durationMs = Math.round(performance.now() - startTime);
267886
268111
  recordSourceBuildDuration(durationMs);
267887
268112
  logger.info(`Built materialized source ${persistSource.name}`, {
@@ -267889,7 +268114,7 @@ class MaterializationService {
267889
268114
  durationMs
267890
268115
  });
267891
268116
  return {
267892
- buildId,
268117
+ sourceEntityId,
267893
268118
  sourceName: persistSource.name,
267894
268119
  materializedTableId: instruction.materializedTableId,
267895
268120
  physicalTableName,
@@ -267946,7 +268171,7 @@ class MaterializationService {
267946
268171
  if (!connectionName || !physicalTableName) {
267947
268172
  logger.warn("Skipping manifest entry with no connection/table", {
267948
268173
  materializationId: m.id,
267949
- buildId: entry.buildId
268174
+ sourceEntityId: entry.sourceEntityId
267950
268175
  });
267951
268176
  continue;
267952
268177
  }
@@ -267958,7 +268183,7 @@ class MaterializationService {
267958
268183
  }
267959
268184
  const dialect = connection.dialectName;
267960
268185
  await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(physicalTableName, dialect)}`);
267961
- await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(`${physicalTableName}${stagingSuffix(entry.buildId)}`, dialect)}`);
268186
+ await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(`${physicalTableName}${stagingSuffix(entry.sourceEntityId)}`, dialect)}`);
267962
268187
  recordDropTables("success");
267963
268188
  logger.info("Dropped materialized table on delete", {
267964
268189
  materializationId: m.id,
@@ -268125,6 +268350,122 @@ class PackageMemoryGovernor {
268125
268350
  }
268126
268351
  }
268127
268352
 
268353
+ // src/service/theme_store.ts
268354
+ init_logger();
268355
+ var THEME_ROW_ID = "default";
268356
+
268357
+ class ThemeStore {
268358
+ storageManager;
268359
+ serverRoot;
268360
+ mutex = new Mutex;
268361
+ cached;
268362
+ cacheLoaded = false;
268363
+ constructor(storageManager, serverRoot) {
268364
+ this.storageManager = storageManager;
268365
+ this.serverRoot = serverRoot;
268366
+ }
268367
+ get db() {
268368
+ return this.storageManager.getDuckDbConnection();
268369
+ }
268370
+ async initialize() {
268371
+ await this.mutex.runExclusive(async () => {
268372
+ await this.loadLocked();
268373
+ });
268374
+ }
268375
+ async get() {
268376
+ if (!this.cacheLoaded) {
268377
+ await this.initialize();
268378
+ }
268379
+ return this.cached;
268380
+ }
268381
+ async set(theme) {
268382
+ return this.mutex.runExclusive(async () => {
268383
+ await this.persistLocked(theme);
268384
+ this.cached = theme;
268385
+ this.cacheLoaded = true;
268386
+ return theme;
268387
+ });
268388
+ }
268389
+ async reset() {
268390
+ return this.mutex.runExclusive(async () => {
268391
+ await this.db.run("DELETE FROM themes WHERE id = ?", [THEME_ROW_ID]);
268392
+ this.cached = undefined;
268393
+ this.cacheLoaded = false;
268394
+ await this.loadLocked();
268395
+ return this.cached;
268396
+ });
268397
+ }
268398
+ async loadLocked() {
268399
+ if (this.cacheLoaded)
268400
+ return;
268401
+ const row = await this.db.get("SELECT payload FROM themes WHERE id = ?", [THEME_ROW_ID]);
268402
+ if (row?.payload) {
268403
+ let parsed;
268404
+ try {
268405
+ parsed = JSON.parse(row.payload);
268406
+ } catch (error) {
268407
+ logger.warn("ThemeStore: stored theme JSON is unparseable; treating as empty", { error });
268408
+ this.cacheLoaded = true;
268409
+ return;
268410
+ }
268411
+ const sanitized = sanitizeTheme(parsed, "ThemeStore.load");
268412
+ const dropped = listSanitizerDrops(parsed, sanitized);
268413
+ if (dropped.length > 0) {
268414
+ logger.info("ThemeStore: dropped stale fields from stored theme during load", { dropped });
268415
+ }
268416
+ this.cached = sanitized;
268417
+ } else {
268418
+ const seed = getInstanceTheme(this.serverRoot);
268419
+ if (seed) {
268420
+ this.cached = seed;
268421
+ await this.persistLocked(seed);
268422
+ }
268423
+ }
268424
+ this.cacheLoaded = true;
268425
+ }
268426
+ async persistLocked(theme) {
268427
+ const now = new Date().toISOString();
268428
+ const payload = JSON.stringify(theme);
268429
+ await this.db.run(`INSERT INTO themes (id, payload, created_at, updated_at)
268430
+ VALUES (?, ?, ?, ?)
268431
+ ON CONFLICT (id) DO UPDATE SET
268432
+ payload = EXCLUDED.payload,
268433
+ updated_at = EXCLUDED.updated_at`, [THEME_ROW_ID, payload, now, now]);
268434
+ }
268435
+ }
268436
+ function listSanitizerDrops(raw, sanitized) {
268437
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
268438
+ return [];
268439
+ const r = raw;
268440
+ const drops = [];
268441
+ const topKeys = ["defaultMode", "allowUserToggle"];
268442
+ for (const k of topKeys) {
268443
+ if (r[k] !== undefined && sanitized?.[k] === undefined)
268444
+ drops.push(k);
268445
+ }
268446
+ if (r.palette && typeof r.palette === "object" && !Array.isArray(r.palette)) {
268447
+ const rp = r.palette;
268448
+ const sp = sanitized?.palette ?? {};
268449
+ for (const k of Object.keys(rp)) {
268450
+ if (rp[k] !== undefined && sp[k] === undefined)
268451
+ drops.push(`palette.${k}`);
268452
+ }
268453
+ } else if (r.palette !== undefined && (!sanitized || sanitized.palette === undefined)) {
268454
+ drops.push("palette");
268455
+ }
268456
+ if (r.font && typeof r.font === "object" && !Array.isArray(r.font)) {
268457
+ const rf = r.font;
268458
+ const sf = sanitized?.font ?? {};
268459
+ for (const k of Object.keys(rf)) {
268460
+ if (rf[k] !== undefined && sf[k] === undefined)
268461
+ drops.push(`font.${k}`);
268462
+ }
268463
+ } else if (r.font !== undefined && (!sanitized || sanitized.font === undefined)) {
268464
+ drops.push("font");
268465
+ }
268466
+ return drops;
268467
+ }
268468
+
268128
268469
  // src/server.ts
268129
268470
  function parseArgs() {
268130
268471
  const args = process.argv.slice(2);
@@ -268219,6 +268560,8 @@ var queryController = new QueryController(environmentStore);
268219
268560
  var compileController = new CompileController(environmentStore);
268220
268561
  var materializationService = new MaterializationService(environmentStore);
268221
268562
  var materializationController = new MaterializationController(materializationService);
268563
+ var themeStore = new ThemeStore(environmentStore.storageManager, SERVER_ROOT);
268564
+ var themeController = new ThemeController(themeStore, SERVER_ROOT);
268222
268565
  var mcpApp = import_express2.default();
268223
268566
  registerHealthEndpoints(mcpApp);
268224
268567
  mcpApp.use(MCP_ENDPOINT, import_express2.default.json());
@@ -268492,13 +268835,44 @@ app.get(`${API_PREFIX2}/environments/:environmentName/packages/:packageName/page
268492
268835
  app.get(`${API_PREFIX2}/status`, async (_req, res) => {
268493
268836
  try {
268494
268837
  const status = await environmentStore.getStatus();
268495
- res.status(200).json(status);
268838
+ const theme = environmentStore.storageManager.isInitialized() ? await themeStore.get() : undefined;
268839
+ res.status(200).json({ ...status, theme: theme ?? {} });
268496
268840
  } catch (error) {
268497
268841
  logger.error("Error getting status", { error });
268498
268842
  const { json, status } = internalErrorToHttpError(error);
268499
268843
  res.status(status).json(json);
268500
268844
  }
268501
268845
  });
268846
+ app.get(`${API_PREFIX2}/theme`, async (_req, res) => {
268847
+ try {
268848
+ if (!environmentStore.storageManager.isInitialized()) {
268849
+ throw new ServiceUnavailableError("Theme storage is still initializing. Retry shortly.");
268850
+ }
268851
+ res.status(200).json(await themeController.getTheme());
268852
+ } catch (error) {
268853
+ logger.error("Error getting theme", { error });
268854
+ const { json, status } = internalErrorToHttpError(error);
268855
+ res.status(status).json(json);
268856
+ }
268857
+ });
268858
+ app.put(`${API_PREFIX2}/theme`, async (req, res) => {
268859
+ try {
268860
+ res.status(200).json(await themeController.putTheme(req.body));
268861
+ } catch (error) {
268862
+ logger.error("Error saving theme", { error });
268863
+ const { json, status } = internalErrorToHttpError(error);
268864
+ res.status(status).json(json);
268865
+ }
268866
+ });
268867
+ app.delete(`${API_PREFIX2}/theme`, async (_req, res) => {
268868
+ try {
268869
+ res.status(200).json(await themeController.resetTheme());
268870
+ } catch (error) {
268871
+ logger.error("Error resetting theme", { error });
268872
+ const { json, status } = internalErrorToHttpError(error);
268873
+ res.status(status).json(json);
268874
+ }
268875
+ });
268502
268876
  app.get(`${API_PREFIX2}/watch-mode/status`, watchModeController.getWatchStatus);
268503
268877
  app.post(`${API_PREFIX2}/watch-mode/start`, watchModeController.startWatching);
268504
268878
  app.post(`${API_PREFIX2}/watch-mode/stop`, watchModeController.stopWatchMode);