@malloy-publisher/server 0.0.219 → 0.0.221

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 (69) hide show
  1. package/dist/app/api-doc.yaml +291 -20
  2. package/dist/app/assets/{EnvironmentPage-gehnjfC6.js → EnvironmentPage-D6G5n6mY.js} +1 -1
  3. package/dist/app/assets/{HomePage-8LQBytE4.js → HomePage-DgZluD8u.js} +1 -1
  4. package/dist/app/assets/LightMode-w9v0pEbg.js +1 -0
  5. package/dist/app/assets/MainPage-Bi_Tukvr.js +2 -0
  6. package/dist/app/assets/{MaterializationsPage-D7P1Kp6O.js → MaterializationsPage-BVERjnmG.js} +1 -1
  7. package/dist/app/assets/ModelPage-2OjS259-.js +1 -0
  8. package/dist/app/assets/{PackagePage-Cz9fVwSG.js → PackagePage-C8L2On4z.js} +1 -1
  9. package/dist/app/assets/{RouteError-UONCloyN.js → RouteError-DJoNC_Vl.js} +1 -1
  10. package/dist/app/assets/ThemeEditorPage-B-hJ1zgd.js +1 -0
  11. package/dist/app/assets/{WorkbookPage-Bhzqvbq_.js → WorkbookPage-CTbDgGeJ.js} +1 -1
  12. package/dist/app/assets/{core-BiGj7BML.es-kMHAa8tP.js → core-D9Hl0IDY.es-CmgzHn4c.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-BEjJCJjX.js +1761 -0
  16. package/dist/app/assets/index-BF8PkFm8.js +1 -0
  17. package/dist/app/assets/index-Cr-asgoV.js +527 -0
  18. package/dist/app/assets/index-DcCw_qxr.js +18 -0
  19. package/dist/app/assets/index-w_0OQJgZ.js +23 -0
  20. package/dist/app/index.html +1 -1
  21. package/dist/package_load_worker.mjs +20 -2
  22. package/dist/runtime/publisher.js +6 -1
  23. package/dist/server.mjs +412 -55
  24. package/dist/sshcrypto-8m50vnmb.node +0 -0
  25. package/package.json +12 -12
  26. package/src/config.theme.spec.ts +178 -0
  27. package/src/config.ts +179 -0
  28. package/src/controller/materialization.controller.spec.ts +4 -4
  29. package/src/controller/materialization.controller.ts +2 -2
  30. package/src/controller/package.controller.spec.ts +89 -1
  31. package/src/controller/package.controller.ts +28 -3
  32. package/src/controller/theme.controller.ts +83 -0
  33. package/src/dto/connection.dto.ts +6 -0
  34. package/src/materialization_metrics.ts +1 -1
  35. package/src/package_load/package_load_pool.ts +2 -1
  36. package/src/package_load/package_load_worker.ts +5 -2
  37. package/src/package_load/protocol.ts +2 -1
  38. package/src/runtime/publisher.js +6 -1
  39. package/src/server.ts +58 -1
  40. package/src/service/build_plan.spec.ts +61 -17
  41. package/src/service/build_plan.ts +35 -11
  42. package/src/service/connection.ts +52 -6
  43. package/src/service/connection_fingerprint.spec.ts +102 -0
  44. package/src/service/manifest_loader.spec.ts +5 -5
  45. package/src/service/manifest_loader.ts +4 -4
  46. package/src/service/materialization_cron_gate.spec.ts +137 -0
  47. package/src/service/materialization_schedule_surface.spec.ts +30 -1
  48. package/src/service/materialization_service.spec.ts +47 -37
  49. package/src/service/materialization_service.ts +59 -50
  50. package/src/service/materialization_test_fixtures.ts +22 -6
  51. package/src/service/model.ts +1 -1
  52. package/src/service/package.ts +50 -4
  53. package/src/service/package_manifest.spec.ts +42 -4
  54. package/src/service/package_manifest.ts +53 -4
  55. package/src/service/theme_store.ts +199 -0
  56. package/src/storage/DatabaseInterface.ts +1 -1
  57. package/src/storage/StorageManager.ts +17 -0
  58. package/src/storage/duckdb/schema.ts +27 -6
  59. package/src/theme_key_parity.spec.ts +57 -0
  60. package/tests/integration/materialization/manifest_binding.integration.spec.ts +1 -1
  61. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +5 -5
  62. package/tests/unit/controller/theme.controller.spec.ts +141 -0
  63. package/tests/unit/service/theme_store.spec.ts +139 -0
  64. package/dist/app/assets/MainPage-DDaZLJw-.js +0 -2
  65. package/dist/app/assets/ModelPage-Do_vhxOc.js +0 -1
  66. package/dist/app/assets/index-VzRbxcF7.js +0 -455
  67. package/dist/app/assets/index-ddq4-5hu.js +0 -1760
  68. package/dist/app/assets/index-qOQF9CXq.js +0 -40
  69. package/dist/app/assets/index.umd-B2kmxDh7.js +0 -2467
package/dist/server.mjs CHANGED
@@ -158407,6 +158407,11 @@ var require_utils74 = __commonJS((exports, module) => {
158407
158407
  };
158408
158408
  });
158409
158409
 
158410
+ // ../../node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node
158411
+ var require_sshcrypto = __commonJS((exports, module) => {
158412
+ module.exports = __require("./sshcrypto-8m50vnmb.node");
158413
+ });
158414
+
158410
158415
  // ../../node_modules/ssh2/lib/protocol/crypto/poly1305.js
158411
158416
  var require_poly1305 = __commonJS((exports, module) => {
158412
158417
  var __dirname = "/home/runner/work/publisher/publisher/node_modules/ssh2/lib/protocol/crypto", __filename = "/home/runner/work/publisher/publisher/node_modules/ssh2/lib/protocol/crypto/poly1305.js";
@@ -158893,7 +158898,7 @@ var require_crypto = __commonJS((exports, module) => {
158893
158898
  var ChaChaPolyDecipher;
158894
158899
  var GenericDecipher;
158895
158900
  try {
158896
- binding = (()=>{throw new Error("Cannot require module "+"./crypto/build/Release/sshcrypto.node");})();
158901
+ binding = require_sshcrypto();
158897
158902
  ({
158898
158903
  AESGCMCipher,
158899
158904
  ChaChaPolyCipher,
@@ -242924,6 +242929,15 @@ function resolvePublisherConfigPath(serverRoot) {
242924
242929
  }
242925
242930
  return null;
242926
242931
  }
242932
+ var PER_MODE_COLOR_KEYS = [
242933
+ "background",
242934
+ "tableHeader",
242935
+ "tableHeaderBackground",
242936
+ "tableBody",
242937
+ "tile",
242938
+ "tileTitle",
242939
+ "mapColor"
242940
+ ];
242927
242941
  var DEFAULT_HIGH_WATER_FRACTION = 0.8;
242928
242942
  var DEFAULT_LOW_WATER_FRACTION = 0.7;
242929
242943
  var DEFAULT_CHECK_INTERVAL_MS = 5000;
@@ -243113,11 +243127,98 @@ var getPublisherConfig = (serverRoot) => {
243113
243127
  if (processedConfig && typeof processedConfig === "object" && "frozenConfig" in processedConfig) {
243114
243128
  frozenConfig = Boolean(processedConfig.frozenConfig);
243115
243129
  }
243130
+ const instanceTheme = sanitizeTheme(processedConfig && typeof processedConfig === "object" && "theme" in processedConfig ? processedConfig.theme : undefined, "publisher.config.json");
243116
243131
  return {
243117
243132
  frozenConfig,
243133
+ ...instanceTheme ? { theme: instanceTheme } : {},
243118
243134
  environments
243119
243135
  };
243120
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
+ }
243121
243222
  var isPublisherConfigFrozen = (serverRoot) => {
243122
243223
  try {
243123
243224
  const publisherConfig = getPublisherConfig(serverRoot);
@@ -243193,17 +243294,29 @@ var getProcessedPublisherConfig = (serverRoot) => {
243193
243294
  logger.warn(`Environment "${environment.name}" has no valid packages. Skipping entry.`);
243194
243295
  continue;
243195
243296
  }
243297
+ const envTheme = sanitizeTheme(environment.theme, `environment "${environment.name}"`);
243298
+ const resolvedTheme = mergeThemes(rawConfig.theme, envTheme);
243196
243299
  validEnvironments.push({
243197
243300
  name: environment.name,
243198
243301
  packages: validPackages,
243199
- connections: convertConnectionsToApiConnections(environment.connections || [])
243302
+ connections: convertConnectionsToApiConnections(environment.connections || []),
243303
+ ...resolvedTheme ? { theme: resolvedTheme } : {}
243200
243304
  });
243201
243305
  }
243202
243306
  return {
243203
243307
  frozenConfig: rawConfig.frozenConfig ?? false,
243308
+ ...rawConfig.theme ? { theme: rawConfig.theme } : {},
243204
243309
  environments: validEnvironments
243205
243310
  };
243206
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
+ };
243207
243320
 
243208
243321
  // src/controller/connection.controller.ts
243209
243322
  init_errors();
@@ -247871,6 +247984,13 @@ function buildAzureDuckDBConnection(metadata, entry) {
247871
247984
  function getMetadataForLookup(metadata, name) {
247872
247985
  return name ? metadata.get(name) : undefined;
247873
247986
  }
247987
+ function applyConnectionFingerprint(connection, metadata) {
247988
+ const fingerprint = metadata?.apiConnection.fingerprint;
247989
+ if (fingerprint) {
247990
+ connection.getDigest = () => fingerprint;
247991
+ }
247992
+ return connection;
247993
+ }
247874
247994
  function isDuckDBConnection(connection) {
247875
247995
  return connection instanceof DuckDBConnection;
247876
247996
  }
@@ -247896,9 +248016,8 @@ function buildEnvironmentMalloyConfig(connections = [], environmentPath = "", is
247896
248016
  }
247897
248017
  await attachPromise;
247898
248018
  }
247899
- malloyConfig.wrapConnections((base) => ({
247900
- lookupConnection: async (name) => {
247901
- const metadata = getMetadataForLookup(assembled.metadata, name);
248019
+ malloyConfig.wrapConnections((base) => {
248020
+ const resolveConnection = async (name, metadata) => {
247902
248021
  if (metadata?.isDuckLake) {
247903
248022
  let connectionPromise = duckLakeCache.get(name);
247904
248023
  if (!connectionPromise) {
@@ -247970,8 +248089,14 @@ function buildEnvironmentMalloyConfig(connections = [], environmentPath = "", is
247970
248089
  await attachOnce(connection, metadata);
247971
248090
  }
247972
248091
  return connection;
247973
- }
247974
- }));
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
+ });
247975
248100
  return {
247976
248101
  malloyConfig,
247977
248102
  apiConnections: assembled.apiConnections,
@@ -249832,6 +249957,14 @@ class ModelController {
249832
249957
  // src/controller/package.controller.ts
249833
249958
  init_constants();
249834
249959
  init_errors();
249960
+ function formatPublishRejections(pkg, exploresOverride) {
249961
+ const message = [
249962
+ pkg.formatInvalidExplores(exploresOverride),
249963
+ pkg.formatInvalidSchedule()
249964
+ ].filter(Boolean).join(`
249965
+ `);
249966
+ return message || undefined;
249967
+ }
249835
249968
 
249836
249969
  class PackageController {
249837
249970
  environmentStore;
@@ -249872,7 +250005,7 @@ class PackageController {
249872
250005
  let result;
249873
250006
  if (body.location) {
249874
250007
  const bodyLocation = body.location;
249875
- result = await environment.installPackage(packageName, (stagingPath) => this.downloadInto(environmentName, packageName, bodyLocation, stagingPath), (pkg) => pkg.formatInvalidExplores());
250008
+ result = await environment.installPackage(packageName, (stagingPath) => this.downloadInto(environmentName, packageName, bodyLocation, stagingPath), (pkg) => formatPublishRejections(pkg));
249876
250009
  } else {
249877
250010
  result = await environment.addPackage(packageName);
249878
250011
  }
@@ -249880,7 +250013,7 @@ class PackageController {
249880
250013
  throw new Error(`Failed to create package ${packageName}`);
249881
250014
  }
249882
250015
  if (!body.location) {
249883
- const invalidMsg = result.formatInvalidExplores();
250016
+ const invalidMsg = formatPublishRejections(result);
249884
250017
  if (invalidMsg) {
249885
250018
  await environment.unloadPackage(packageName).catch(() => {});
249886
250019
  throw new BadRequestError(invalidMsg);
@@ -249905,7 +250038,7 @@ class PackageController {
249905
250038
  const environment = await this.environmentStore.getEnvironment(environmentName, false);
249906
250039
  if (body.location) {
249907
250040
  const bodyLocation = body.location;
249908
- await environment.installPackage(packageName, (stagingPath) => this.downloadInto(environmentName, packageName, bodyLocation, stagingPath), (pkg) => pkg.formatInvalidExplores(body.explores?.map(normalizeModelPath)));
250041
+ await environment.installPackage(packageName, (stagingPath) => this.downloadInto(environmentName, packageName, bodyLocation, stagingPath), (pkg) => formatPublishRejections(pkg, body.explores?.map(normalizeModelPath)));
249909
250042
  }
249910
250043
  const result = await environment.updatePackage(packageName, body);
249911
250044
  await this.environmentStore.addPackageToDatabase(environmentName, packageName);
@@ -256512,13 +256645,10 @@ class DuckDBRepository {
256512
256645
  init_logger();
256513
256646
  async function initializeSchema(db, force = false) {
256514
256647
  const initialized = await db.isInitialized();
256515
- if (initialized && !force) {
256516
- return;
256517
- }
256518
256648
  if (force) {
256519
256649
  logger.info("Reinitializing database schema dropping and recreating all tables");
256520
256650
  await dropAllTables(db);
256521
- } else {
256651
+ } else if (!initialized) {
256522
256652
  await dropLegacyProjectSchema(db);
256523
256653
  logger.info("Creating database schema for the first time...");
256524
256654
  }
@@ -256577,6 +256707,14 @@ async function initializeSchema(db, force = false) {
256577
256707
  FOREIGN KEY (environment_id) REFERENCES environments(id)
256578
256708
  )
256579
256709
  `);
256710
+ await db.run(`
256711
+ CREATE TABLE IF NOT EXISTS themes (
256712
+ id VARCHAR PRIMARY KEY,
256713
+ payload JSON NOT NULL,
256714
+ created_at TIMESTAMP NOT NULL,
256715
+ updated_at TIMESTAMP NOT NULL
256716
+ )
256717
+ `);
256580
256718
  await db.run("CREATE INDEX IF NOT EXISTS idx_packages_environment_id ON packages(environment_id)");
256581
256719
  await db.run("CREATE INDEX IF NOT EXISTS idx_connections_environment_id ON connections(environment_id)");
256582
256720
  await db.run("CREATE INDEX IF NOT EXISTS idx_materializations_environment_package ON materializations(environment_id, package_name)");
@@ -256609,7 +256747,8 @@ async function dropAllTables(db) {
256609
256747
  "materializations",
256610
256748
  "packages",
256611
256749
  "connections",
256612
- "environments"
256750
+ "environments",
256751
+ "themes"
256613
256752
  ];
256614
256753
  logger.info("Dropping tables:", tables.join(", "));
256615
256754
  for (const table of tables) {
@@ -256655,6 +256794,15 @@ class StorageManager {
256655
256794
  this.connection = connection;
256656
256795
  this.repository = new DuckDBRepository(connection);
256657
256796
  }
256797
+ getDuckDbConnection() {
256798
+ if (!this.connection) {
256799
+ throw new Error("Storage not initialized");
256800
+ }
256801
+ if (!(this.connection instanceof DuckDBConnection2)) {
256802
+ throw new Error("Underlying storage connection is not DuckDB");
256803
+ }
256804
+ return this.connection;
256805
+ }
256658
256806
  getRepository() {
256659
256807
  if (!this.repository) {
256660
256808
  throw new Error("Storage not initialized. Call initialize() first.");
@@ -256801,16 +256949,16 @@ async function fetchManifestEntries(uri) {
256801
256949
  throw new Error(`Failed to parse build manifest at ${uri}: ${err instanceof Error ? err.message : String(err)}`);
256802
256950
  }
256803
256951
  const entries = {};
256804
- for (const [buildId, entry] of Object.entries(parsed.entries ?? {})) {
256952
+ for (const [sourceEntityId, entry] of Object.entries(parsed.entries ?? {})) {
256805
256953
  const physicalTableName = entry?.physicalTableName;
256806
256954
  if (!physicalTableName) {
256807
256955
  logger.warn("Manifest entry has no physicalTableName; skipping", {
256808
256956
  uri,
256809
- buildId
256957
+ sourceEntityId
256810
256958
  });
256811
256959
  continue;
256812
256960
  }
256813
- entries[buildId] = { tableName: physicalTableName };
256961
+ entries[sourceEntityId] = { tableName: physicalTableName };
256814
256962
  }
256815
256963
  return entries;
256816
256964
  }
@@ -258302,7 +258450,7 @@ function* iterGraphSources(graph, sources) {
258302
258450
  }
258303
258451
  }
258304
258452
  }
258305
- function computeBuildId(source, connectionDigests) {
258453
+ function computeSourceEntityId(source, connectionDigests) {
258306
258454
  return source.makeBuildId(connectionDigests[source.connectionName], source.getSQL());
258307
258455
  }
258308
258456
  async function resolvePackageConnections(pkg, names) {
@@ -258385,15 +258533,18 @@ function deriveBuildPlan(graphs, sources, connectionDigests, sourceNames, source
258385
258533
  for (const [sourceID, source] of Object.entries(sources)) {
258386
258534
  if (include && !include.has(source.name))
258387
258535
  continue;
258536
+ const annotationFields = deriveAnnotationFields(source);
258388
258537
  wireSources[sourceID] = {
258389
258538
  name: source.name,
258390
258539
  sourceID: source.sourceID,
258391
258540
  connectionName: source.connectionName,
258392
258541
  dialect: source.dialectName,
258393
- buildId: computeBuildId(source, connectionDigests),
258542
+ sourceEntityId: computeSourceEntityId(source, connectionDigests),
258394
258543
  sql: source.getSQL(),
258544
+ sharing: annotationFields.sharing ?? null,
258545
+ refresh: annotationFields.refresh ?? null,
258395
258546
  columns: deriveColumns(source),
258396
- annotationFields: deriveAnnotationFields(source),
258547
+ annotationFields,
258397
258548
  modelPath: sourceModelPaths?.[sourceID]
258398
258549
  };
258399
258550
  }
@@ -258559,7 +258710,8 @@ class Package {
258559
258710
  queryableSources: outcome.packageMetadata.queryableSources,
258560
258711
  manifestLocation: outcome.packageMetadata.manifestLocation ?? null,
258561
258712
  materialization: outcome.packageMetadata.materialization ?? {
258562
- schedule: null
258713
+ schedule: null,
258714
+ freshness: null
258563
258715
  }
258564
258716
  };
258565
258717
  const models = new Map;
@@ -258613,6 +258765,13 @@ class Package {
258613
258765
  detail: invalidMsg
258614
258766
  });
258615
258767
  }
258768
+ const invalidSchedule = pkg.formatInvalidSchedule();
258769
+ if (invalidSchedule) {
258770
+ logger.warn(`Package ${packageName} has an invalid cron schedule`, {
258771
+ packageName,
258772
+ detail: invalidSchedule
258773
+ });
258774
+ }
258616
258775
  pkg.logEmptyDiscoveryWarnings();
258617
258776
  return pkg;
258618
258777
  }
@@ -258684,6 +258843,17 @@ class Package {
258684
258843
  }
258685
258844
  formatInvalidExplores(exploresOverride) {
258686
258845
  return this.exploreWarnings(exploresOverride).join(`
258846
+ `);
258847
+ }
258848
+ scheduleWarnings() {
258849
+ const schedule = this.packageMetadata.materialization?.schedule;
258850
+ if (!schedule)
258851
+ return [];
258852
+ const sources = Object.values(this.buildPlan?.sources ?? {});
258853
+ return sources.filter((source) => source.sharing !== "private").map((source) => `materialization.schedule (cron) in ${PACKAGE_MANIFEST_NAME} requires every ` + `persist source to declare '#@ persist ... sharing=private'; source ` + `'${source.name}' resolves to ${source.sharing ? `'${source.sharing}'` : "unset"}. Declare 'materialization.freshness.window' instead (the control plane ` + `schedules refreshes from it), or mark every persist source sharing=private.`);
258854
+ }
258855
+ formatInvalidSchedule() {
258856
+ return this.scheduleWarnings().join(`
258687
258857
  `);
258688
258858
  }
258689
258859
  emptyDiscoveryWarnings() {
@@ -261030,7 +261200,7 @@ class MaterializationController {
261030
261200
  }
261031
261201
  const instruction = raw;
261032
261202
  const required = [
261033
- "buildId",
261203
+ "sourceEntityId",
261034
261204
  "materializedTableId",
261035
261205
  "physicalTableName",
261036
261206
  "realization"
@@ -261044,7 +261214,7 @@ class MaterializationController {
261044
261214
  throw new BadRequestError("Build instruction 'realization' must be COPY or SNAPSHOT");
261045
261215
  }
261046
261216
  return {
261047
- buildId: instruction.buildId,
261217
+ sourceEntityId: instruction.sourceEntityId,
261048
261218
  sourceID: typeof instruction.sourceID === "string" ? instruction.sourceID : undefined,
261049
261219
  materializedTableId: instruction.materializedTableId,
261050
261220
  physicalTableName: instruction.physicalTableName,
@@ -261065,6 +261235,40 @@ class MaterializationController {
261065
261235
  }
261066
261236
  }
261067
261237
 
261238
+ // src/controller/theme.controller.ts
261239
+ init_errors();
261240
+ class ThemeController {
261241
+ themeStore;
261242
+ serverRoot;
261243
+ constructor(themeStore, serverRoot) {
261244
+ this.themeStore = themeStore;
261245
+ this.serverRoot = serverRoot;
261246
+ }
261247
+ getTheme = async () => {
261248
+ const theme = await this.themeStore.get();
261249
+ return theme ?? {};
261250
+ };
261251
+ putTheme = async (body) => {
261252
+ if (isPublisherConfigFrozen(this.serverRoot)) {
261253
+ throw new FrozenConfigError('Cannot edit theme: publisher.config.json has "frozenConfig": true.');
261254
+ }
261255
+ const isEmptyObject2 = typeof body === "object" && body !== null && !Array.isArray(body) && Object.keys(body).length === 0;
261256
+ const sanitized = sanitizeTheme(body, "PUT /api/v0/theme");
261257
+ if (!sanitized && !isEmptyObject2) {
261258
+ throw new BadRequestError("Theme payload was malformed (expected an object).");
261259
+ }
261260
+ const saved = await this.themeStore.set(sanitized ?? {});
261261
+ return saved;
261262
+ };
261263
+ resetTheme = async () => {
261264
+ if (isPublisherConfigFrozen(this.serverRoot)) {
261265
+ throw new FrozenConfigError('Cannot reset theme: publisher.config.json has "frozenConfig": true.');
261266
+ }
261267
+ const reseeded = await this.themeStore.reset();
261268
+ return reseeded ?? {};
261269
+ };
261270
+ }
261271
+
261068
261272
  // ../../node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
261069
261273
  var DEFAULT_REQUEST_TIMEOUT_MSEC = 60000;
261070
261274
 
@@ -267628,9 +267832,9 @@ async function resolveEnvironmentId(repository, environmentName) {
267628
267832
  }
267629
267833
 
267630
267834
  // src/service/materialization_service.ts
267631
- var STAGING_BUILD_ID_LEN = 12;
267632
- function stagingSuffix(buildId) {
267633
- return `_${buildId.substring(0, STAGING_BUILD_ID_LEN)}`;
267835
+ var STAGING_ID_LEN = 12;
267836
+ function stagingSuffix(sourceEntityId) {
267837
+ return `_${sourceEntityId.replace(/-/g, "").substring(0, STAGING_ID_LEN)}`;
267634
267838
  }
267635
267839
  function selfAssignTableName(persistSource) {
267636
267840
  return deriveAnnotationFields(persistSource).name || persistSource.name;
@@ -267792,18 +267996,18 @@ class MaterializationService {
267792
267996
  for (const persistSource of iterGraphSources(graph, compiled.sources)) {
267793
267997
  if (include && !include.has(persistSource.name))
267794
267998
  continue;
267795
- const buildId = computeBuildId(persistSource, compiled.connectionDigests);
267796
- if (seen.has(buildId))
267999
+ const sourceEntityId = computeSourceEntityId(persistSource, compiled.connectionDigests);
268000
+ if (seen.has(sourceEntityId))
267797
268001
  continue;
267798
- seen.add(buildId);
267799
- const prior = priorEntries[buildId];
268002
+ seen.add(sourceEntityId);
268003
+ const prior = priorEntries[sourceEntityId];
267800
268004
  if (prior && prior.physicalTableName) {
267801
- carried[buildId] = prior;
268005
+ carried[sourceEntityId] = prior;
267802
268006
  continue;
267803
268007
  }
267804
268008
  instructions.push({
267805
- buildId,
267806
- materializedTableId: `local-${buildId.substring(0, STAGING_BUILD_ID_LEN)}`,
268009
+ sourceEntityId,
268010
+ materializedTableId: `local-${sourceEntityId.substring(0, STAGING_ID_LEN)}`,
267807
268011
  physicalTableName: selfAssignTableName(persistSource),
267808
268012
  realization: "COPY"
267809
268013
  });
@@ -267824,9 +268028,11 @@ class MaterializationService {
267824
268028
  }
267825
268029
  async autoLoadManifest(environment, packageName, entries) {
267826
268030
  const manifestEntries = {};
267827
- for (const [buildId, entry] of Object.entries(entries)) {
268031
+ for (const [sourceEntityId, entry] of Object.entries(entries)) {
267828
268032
  if (entry.physicalTableName) {
267829
- manifestEntries[buildId] = { tableName: entry.physicalTableName };
268033
+ manifestEntries[sourceEntityId] = {
268034
+ tableName: entry.physicalTableName
268035
+ };
267830
268036
  }
267831
268037
  }
267832
268038
  try {
@@ -267848,13 +268054,13 @@ class MaterializationService {
267848
268054
  if (!plan) {
267849
268055
  throw new BadRequestError("Package has no persist sources; buildInstructions cannot be applied");
267850
268056
  }
267851
- const plannedBuildIds = new Set;
268057
+ const plannedSourceEntityIds = new Set;
267852
268058
  for (const source of Object.values(plan.sources)) {
267853
- plannedBuildIds.add(source.buildId);
268059
+ plannedSourceEntityIds.add(source.sourceEntityId);
267854
268060
  }
267855
268061
  for (const instruction of instructions) {
267856
- if (!plannedBuildIds.has(instruction.buildId)) {
267857
- throw new BadRequestError(`Instruction references unknown buildId '${instruction.buildId}'`);
268062
+ if (!plannedSourceEntityIds.has(instruction.sourceEntityId)) {
268063
+ throw new BadRequestError(`Instruction references unknown sourceEntityId '${instruction.sourceEntityId}'`);
267858
268064
  }
267859
268065
  if (instruction.realization === "SNAPSHOT") {
267860
268066
  throw new BadRequestError("realization=SNAPSHOT is not supported (COPY only)");
@@ -267864,20 +268070,22 @@ class MaterializationService {
267864
268070
  async executeInstructedBuild(compiled, instructions, seedEntries, signal) {
267865
268071
  const { graphs, sources, connectionDigests, connections } = compiled;
267866
268072
  const bySourceID = new Map;
267867
- const byBuildId = new Map;
268073
+ const bySourceEntityId = new Map;
267868
268074
  for (const instruction of instructions) {
267869
268075
  if (instruction.sourceID) {
267870
268076
  bySourceID.set(instruction.sourceID, instruction);
267871
268077
  }
267872
- byBuildId.set(instruction.buildId, instruction);
268078
+ bySourceEntityId.set(instruction.sourceEntityId, instruction);
267873
268079
  }
267874
268080
  const manifest = new Manifest;
267875
268081
  const entries = {};
267876
- for (const [buildId, entry] of Object.entries(seedEntries)) {
268082
+ for (const [sourceEntityId, entry] of Object.entries(seedEntries)) {
267877
268083
  if (entry.physicalTableName) {
267878
- manifest.update(buildId, { tableName: entry.physicalTableName });
268084
+ manifest.update(sourceEntityId, {
268085
+ tableName: entry.physicalTableName
268086
+ });
267879
268087
  }
267880
- entries[buildId] = entry;
268088
+ entries[sourceEntityId] = entry;
267881
268089
  }
267882
268090
  for (const graph of graphs) {
267883
268091
  const connection = connections.get(graph.connectionName);
@@ -267887,25 +268095,25 @@ class MaterializationService {
267887
268095
  for (const persistSource of iterGraphSources(graph, sources)) {
267888
268096
  if (signal.aborted)
267889
268097
  throw new Error("Build cancelled");
267890
- const buildId = computeBuildId(persistSource, connectionDigests);
267891
- const instruction = bySourceID.get(persistSource.sourceID) ?? byBuildId.get(buildId);
268098
+ const sourceEntityId = computeSourceEntityId(persistSource, connectionDigests);
268099
+ const instruction = bySourceID.get(persistSource.sourceID) ?? bySourceEntityId.get(sourceEntityId);
267892
268100
  if (!instruction)
267893
268101
  continue;
267894
268102
  const entry = await this.buildOneSource(persistSource, instruction, connection, connectionDigests, manifest);
267895
- entries[buildId] = entry;
268103
+ entries[sourceEntityId] = entry;
267896
268104
  }
267897
268105
  }
267898
268106
  return entries;
267899
268107
  }
267900
268108
  async buildOneSource(persistSource, instruction, connection, connectionDigests, manifest) {
267901
- const buildId = instruction.buildId;
268109
+ const sourceEntityId = instruction.sourceEntityId;
267902
268110
  const physicalTableName = instruction.physicalTableName;
267903
268111
  const buildSQL = persistSource.getSQL({
267904
268112
  buildManifest: manifest.buildManifest,
267905
268113
  connectionDigests
267906
268114
  });
267907
268115
  const bareName = bareTableName(physicalTableName);
267908
- const stagingTableName = `${physicalTableName}${stagingSuffix(buildId)}`;
268116
+ const stagingTableName = `${physicalTableName}${stagingSuffix(sourceEntityId)}`;
267909
268117
  const dialect = persistSource.dialectName;
267910
268118
  const quotedStaging = quoteTablePath(stagingTableName, dialect);
267911
268119
  const quotedPhysical = quoteTablePath(physicalTableName, dialect);
@@ -267928,7 +268136,7 @@ class MaterializationService {
267928
268136
  }
267929
268137
  throw err;
267930
268138
  }
267931
- manifest.update(buildId, { tableName: physicalTableName });
268139
+ manifest.update(sourceEntityId, { tableName: physicalTableName });
267932
268140
  const durationMs = Math.round(performance.now() - startTime);
267933
268141
  recordSourceBuildDuration(durationMs);
267934
268142
  logger.info(`Built materialized source ${persistSource.name}`, {
@@ -267936,7 +268144,7 @@ class MaterializationService {
267936
268144
  durationMs
267937
268145
  });
267938
268146
  return {
267939
- buildId,
268147
+ sourceEntityId,
267940
268148
  sourceName: persistSource.name,
267941
268149
  materializedTableId: instruction.materializedTableId,
267942
268150
  physicalTableName,
@@ -267993,7 +268201,7 @@ class MaterializationService {
267993
268201
  if (!connectionName || !physicalTableName) {
267994
268202
  logger.warn("Skipping manifest entry with no connection/table", {
267995
268203
  materializationId: m.id,
267996
- buildId: entry.buildId
268204
+ sourceEntityId: entry.sourceEntityId
267997
268205
  });
267998
268206
  continue;
267999
268207
  }
@@ -268005,7 +268213,7 @@ class MaterializationService {
268005
268213
  }
268006
268214
  const dialect = connection.dialectName;
268007
268215
  await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(physicalTableName, dialect)}`);
268008
- await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(`${physicalTableName}${stagingSuffix(entry.buildId)}`, dialect)}`);
268216
+ await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(`${physicalTableName}${stagingSuffix(entry.sourceEntityId)}`, dialect)}`);
268009
268217
  recordDropTables("success");
268010
268218
  logger.info("Dropped materialized table on delete", {
268011
268219
  materializationId: m.id,
@@ -268172,6 +268380,122 @@ class PackageMemoryGovernor {
268172
268380
  }
268173
268381
  }
268174
268382
 
268383
+ // src/service/theme_store.ts
268384
+ init_logger();
268385
+ var THEME_ROW_ID = "default";
268386
+
268387
+ class ThemeStore {
268388
+ storageManager;
268389
+ serverRoot;
268390
+ mutex = new Mutex;
268391
+ cached;
268392
+ cacheLoaded = false;
268393
+ constructor(storageManager, serverRoot) {
268394
+ this.storageManager = storageManager;
268395
+ this.serverRoot = serverRoot;
268396
+ }
268397
+ get db() {
268398
+ return this.storageManager.getDuckDbConnection();
268399
+ }
268400
+ async initialize() {
268401
+ await this.mutex.runExclusive(async () => {
268402
+ await this.loadLocked();
268403
+ });
268404
+ }
268405
+ async get() {
268406
+ if (!this.cacheLoaded) {
268407
+ await this.initialize();
268408
+ }
268409
+ return this.cached;
268410
+ }
268411
+ async set(theme) {
268412
+ return this.mutex.runExclusive(async () => {
268413
+ await this.persistLocked(theme);
268414
+ this.cached = theme;
268415
+ this.cacheLoaded = true;
268416
+ return theme;
268417
+ });
268418
+ }
268419
+ async reset() {
268420
+ return this.mutex.runExclusive(async () => {
268421
+ await this.db.run("DELETE FROM themes WHERE id = ?", [THEME_ROW_ID]);
268422
+ this.cached = undefined;
268423
+ this.cacheLoaded = false;
268424
+ await this.loadLocked();
268425
+ return this.cached;
268426
+ });
268427
+ }
268428
+ async loadLocked() {
268429
+ if (this.cacheLoaded)
268430
+ return;
268431
+ const row = await this.db.get("SELECT payload FROM themes WHERE id = ?", [THEME_ROW_ID]);
268432
+ if (row?.payload) {
268433
+ let parsed;
268434
+ try {
268435
+ parsed = JSON.parse(row.payload);
268436
+ } catch (error) {
268437
+ logger.warn("ThemeStore: stored theme JSON is unparseable; treating as empty", { error });
268438
+ this.cacheLoaded = true;
268439
+ return;
268440
+ }
268441
+ const sanitized = sanitizeTheme(parsed, "ThemeStore.load");
268442
+ const dropped = listSanitizerDrops(parsed, sanitized);
268443
+ if (dropped.length > 0) {
268444
+ logger.info("ThemeStore: dropped stale fields from stored theme during load", { dropped });
268445
+ }
268446
+ this.cached = sanitized;
268447
+ } else {
268448
+ const seed = getInstanceTheme(this.serverRoot);
268449
+ if (seed) {
268450
+ this.cached = seed;
268451
+ await this.persistLocked(seed);
268452
+ }
268453
+ }
268454
+ this.cacheLoaded = true;
268455
+ }
268456
+ async persistLocked(theme) {
268457
+ const now = new Date().toISOString();
268458
+ const payload = JSON.stringify(theme);
268459
+ await this.db.run(`INSERT INTO themes (id, payload, created_at, updated_at)
268460
+ VALUES (?, ?, ?, ?)
268461
+ ON CONFLICT (id) DO UPDATE SET
268462
+ payload = EXCLUDED.payload,
268463
+ updated_at = EXCLUDED.updated_at`, [THEME_ROW_ID, payload, now, now]);
268464
+ }
268465
+ }
268466
+ function listSanitizerDrops(raw, sanitized) {
268467
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
268468
+ return [];
268469
+ const r = raw;
268470
+ const drops = [];
268471
+ const topKeys = ["defaultMode", "allowUserToggle"];
268472
+ for (const k of topKeys) {
268473
+ if (r[k] !== undefined && sanitized?.[k] === undefined)
268474
+ drops.push(k);
268475
+ }
268476
+ if (r.palette && typeof r.palette === "object" && !Array.isArray(r.palette)) {
268477
+ const rp = r.palette;
268478
+ const sp = sanitized?.palette ?? {};
268479
+ for (const k of Object.keys(rp)) {
268480
+ if (rp[k] !== undefined && sp[k] === undefined)
268481
+ drops.push(`palette.${k}`);
268482
+ }
268483
+ } else if (r.palette !== undefined && (!sanitized || sanitized.palette === undefined)) {
268484
+ drops.push("palette");
268485
+ }
268486
+ if (r.font && typeof r.font === "object" && !Array.isArray(r.font)) {
268487
+ const rf = r.font;
268488
+ const sf = sanitized?.font ?? {};
268489
+ for (const k of Object.keys(rf)) {
268490
+ if (rf[k] !== undefined && sf[k] === undefined)
268491
+ drops.push(`font.${k}`);
268492
+ }
268493
+ } else if (r.font !== undefined && (!sanitized || sanitized.font === undefined)) {
268494
+ drops.push("font");
268495
+ }
268496
+ return drops;
268497
+ }
268498
+
268175
268499
  // src/server.ts
268176
268500
  function parseArgs() {
268177
268501
  const args = process.argv.slice(2);
@@ -268266,6 +268590,8 @@ var queryController = new QueryController(environmentStore);
268266
268590
  var compileController = new CompileController(environmentStore);
268267
268591
  var materializationService = new MaterializationService(environmentStore);
268268
268592
  var materializationController = new MaterializationController(materializationService);
268593
+ var themeStore = new ThemeStore(environmentStore.storageManager, SERVER_ROOT);
268594
+ var themeController = new ThemeController(themeStore, SERVER_ROOT);
268269
268595
  var mcpApp = import_express2.default();
268270
268596
  registerHealthEndpoints(mcpApp);
268271
268597
  mcpApp.use(MCP_ENDPOINT, import_express2.default.json());
@@ -268539,13 +268865,44 @@ app.get(`${API_PREFIX2}/environments/:environmentName/packages/:packageName/page
268539
268865
  app.get(`${API_PREFIX2}/status`, async (_req, res) => {
268540
268866
  try {
268541
268867
  const status = await environmentStore.getStatus();
268542
- res.status(200).json(status);
268868
+ const theme = environmentStore.storageManager.isInitialized() ? await themeStore.get() : undefined;
268869
+ res.status(200).json({ ...status, theme: theme ?? {} });
268543
268870
  } catch (error) {
268544
268871
  logger.error("Error getting status", { error });
268545
268872
  const { json, status } = internalErrorToHttpError(error);
268546
268873
  res.status(status).json(json);
268547
268874
  }
268548
268875
  });
268876
+ app.get(`${API_PREFIX2}/theme`, async (_req, res) => {
268877
+ try {
268878
+ if (!environmentStore.storageManager.isInitialized()) {
268879
+ throw new ServiceUnavailableError("Theme storage is still initializing. Retry shortly.");
268880
+ }
268881
+ res.status(200).json(await themeController.getTheme());
268882
+ } catch (error) {
268883
+ logger.error("Error getting theme", { error });
268884
+ const { json, status } = internalErrorToHttpError(error);
268885
+ res.status(status).json(json);
268886
+ }
268887
+ });
268888
+ app.put(`${API_PREFIX2}/theme`, async (req, res) => {
268889
+ try {
268890
+ res.status(200).json(await themeController.putTheme(req.body));
268891
+ } catch (error) {
268892
+ logger.error("Error saving theme", { error });
268893
+ const { json, status } = internalErrorToHttpError(error);
268894
+ res.status(status).json(json);
268895
+ }
268896
+ });
268897
+ app.delete(`${API_PREFIX2}/theme`, async (_req, res) => {
268898
+ try {
268899
+ res.status(200).json(await themeController.resetTheme());
268900
+ } catch (error) {
268901
+ logger.error("Error resetting theme", { error });
268902
+ const { json, status } = internalErrorToHttpError(error);
268903
+ res.status(status).json(json);
268904
+ }
268905
+ });
268549
268906
  app.get(`${API_PREFIX2}/watch-mode/status`, watchModeController.getWatchStatus);
268550
268907
  app.post(`${API_PREFIX2}/watch-mode/start`, watchModeController.startWatching);
268551
268908
  app.post(`${API_PREFIX2}/watch-mode/stop`, watchModeController.stopWatchMode);