@malloy-publisher/server 0.0.219 → 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.
- package/dist/app/api-doc.yaml +240 -19
- package/dist/app/assets/{EnvironmentPage-gehnjfC6.js → EnvironmentPage-BqLiaatL.js} +1 -1
- package/dist/app/assets/{HomePage-8LQBytE4.js → HomePage-G_xpt9XE.js} +1 -1
- package/dist/app/assets/LightMode-s1PDRIsF.js +1 -0
- package/dist/app/assets/MainPage-C6TfpC92.js +2 -0
- package/dist/app/assets/{MaterializationsPage-D7P1Kp6O.js → MaterializationsPage-BMO1afhm.js} +1 -1
- package/dist/app/assets/ModelPage-zHOJwMnU.js +1 -0
- package/dist/app/assets/{PackagePage-Cz9fVwSG.js → PackagePage-GCaWmELQ.js} +1 -1
- package/dist/app/assets/{RouteError-UONCloyN.js → RouteError-CIC588k4.js} +1 -1
- package/dist/app/assets/ThemeEditorPage-BdbIfIGO.js +1 -0
- package/dist/app/assets/{WorkbookPage-Bhzqvbq_.js → WorkbookPage-C8ucK_H8.js} +1 -1
- package/dist/app/assets/{core-BiGj7BML.es-kMHAa8tP.js → core-DFvqRVqM.es-NtfPVC7h.js} +1 -1
- package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
- package/dist/app/assets/index-8E2uLeV9.js +2541 -0
- package/dist/app/assets/index-B3NcDPbp.js +18 -0
- package/dist/app/assets/index-BxDMCn3s.js +527 -0
- package/dist/app/assets/index-aYNf0kTZ.js +1 -0
- package/dist/app/assets/index-aYtt-ovi.js +1761 -0
- package/dist/app/assets/index-w_0OQJgZ.js +23 -0
- package/dist/app/index.html +1 -1
- package/dist/runtime/publisher.js +6 -1
- package/dist/server.mjs +377 -50
- package/dist/sshcrypto-8m50vnmb.node +0 -0
- package/package.json +12 -12
- package/src/config.theme.spec.ts +178 -0
- package/src/config.ts +179 -0
- package/src/controller/materialization.controller.spec.ts +4 -4
- package/src/controller/materialization.controller.ts +2 -2
- package/src/controller/theme.controller.ts +83 -0
- package/src/dto/connection.dto.ts +6 -0
- package/src/materialization_metrics.ts +1 -1
- package/src/runtime/publisher.js +6 -1
- package/src/server.ts +58 -1
- package/src/service/build_plan.spec.ts +17 -17
- package/src/service/build_plan.ts +25 -10
- package/src/service/connection.ts +52 -6
- package/src/service/connection_fingerprint.spec.ts +102 -0
- package/src/service/manifest_loader.spec.ts +5 -5
- package/src/service/manifest_loader.ts +4 -4
- package/src/service/materialization_service.spec.ts +47 -37
- package/src/service/materialization_service.ts +59 -50
- package/src/service/materialization_test_fixtures.ts +5 -5
- package/src/service/model.ts +1 -1
- package/src/service/package.ts +4 -4
- package/src/service/theme_store.ts +199 -0
- package/src/storage/DatabaseInterface.ts +1 -1
- package/src/storage/StorageManager.ts +17 -0
- package/src/storage/duckdb/schema.ts +27 -6
- package/src/theme_key_parity.spec.ts +57 -0
- package/tests/integration/materialization/manifest_binding.integration.spec.ts +1 -1
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +5 -5
- package/tests/unit/controller/theme.controller.spec.ts +141 -0
- package/tests/unit/service/theme_store.spec.ts +139 -0
- package/dist/app/assets/MainPage-DDaZLJw-.js +0 -2
- package/dist/app/assets/ModelPage-Do_vhxOc.js +0 -1
- package/dist/app/assets/index-VzRbxcF7.js +0 -455
- package/dist/app/assets/index-ddq4-5hu.js +0 -1760
- package/dist/app/assets/index-qOQF9CXq.js +0 -40
- 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 = (
|
|
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
|
-
|
|
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,
|
|
@@ -256512,13 +256637,10 @@ class DuckDBRepository {
|
|
|
256512
256637
|
init_logger();
|
|
256513
256638
|
async function initializeSchema(db, force = false) {
|
|
256514
256639
|
const initialized = await db.isInitialized();
|
|
256515
|
-
if (initialized && !force) {
|
|
256516
|
-
return;
|
|
256517
|
-
}
|
|
256518
256640
|
if (force) {
|
|
256519
256641
|
logger.info("Reinitializing database schema dropping and recreating all tables");
|
|
256520
256642
|
await dropAllTables(db);
|
|
256521
|
-
} else {
|
|
256643
|
+
} else if (!initialized) {
|
|
256522
256644
|
await dropLegacyProjectSchema(db);
|
|
256523
256645
|
logger.info("Creating database schema for the first time...");
|
|
256524
256646
|
}
|
|
@@ -256577,6 +256699,14 @@ async function initializeSchema(db, force = false) {
|
|
|
256577
256699
|
FOREIGN KEY (environment_id) REFERENCES environments(id)
|
|
256578
256700
|
)
|
|
256579
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
|
+
`);
|
|
256580
256710
|
await db.run("CREATE INDEX IF NOT EXISTS idx_packages_environment_id ON packages(environment_id)");
|
|
256581
256711
|
await db.run("CREATE INDEX IF NOT EXISTS idx_connections_environment_id ON connections(environment_id)");
|
|
256582
256712
|
await db.run("CREATE INDEX IF NOT EXISTS idx_materializations_environment_package ON materializations(environment_id, package_name)");
|
|
@@ -256609,7 +256739,8 @@ async function dropAllTables(db) {
|
|
|
256609
256739
|
"materializations",
|
|
256610
256740
|
"packages",
|
|
256611
256741
|
"connections",
|
|
256612
|
-
"environments"
|
|
256742
|
+
"environments",
|
|
256743
|
+
"themes"
|
|
256613
256744
|
];
|
|
256614
256745
|
logger.info("Dropping tables:", tables.join(", "));
|
|
256615
256746
|
for (const table of tables) {
|
|
@@ -256655,6 +256786,15 @@ class StorageManager {
|
|
|
256655
256786
|
this.connection = connection;
|
|
256656
256787
|
this.repository = new DuckDBRepository(connection);
|
|
256657
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
|
+
}
|
|
256658
256798
|
getRepository() {
|
|
256659
256799
|
if (!this.repository) {
|
|
256660
256800
|
throw new Error("Storage not initialized. Call initialize() first.");
|
|
@@ -256801,16 +256941,16 @@ async function fetchManifestEntries(uri) {
|
|
|
256801
256941
|
throw new Error(`Failed to parse build manifest at ${uri}: ${err instanceof Error ? err.message : String(err)}`);
|
|
256802
256942
|
}
|
|
256803
256943
|
const entries = {};
|
|
256804
|
-
for (const [
|
|
256944
|
+
for (const [sourceEntityId, entry] of Object.entries(parsed.entries ?? {})) {
|
|
256805
256945
|
const physicalTableName = entry?.physicalTableName;
|
|
256806
256946
|
if (!physicalTableName) {
|
|
256807
256947
|
logger.warn("Manifest entry has no physicalTableName; skipping", {
|
|
256808
256948
|
uri,
|
|
256809
|
-
|
|
256949
|
+
sourceEntityId
|
|
256810
256950
|
});
|
|
256811
256951
|
continue;
|
|
256812
256952
|
}
|
|
256813
|
-
entries[
|
|
256953
|
+
entries[sourceEntityId] = { tableName: physicalTableName };
|
|
256814
256954
|
}
|
|
256815
256955
|
return entries;
|
|
256816
256956
|
}
|
|
@@ -258302,7 +258442,7 @@ function* iterGraphSources(graph, sources) {
|
|
|
258302
258442
|
}
|
|
258303
258443
|
}
|
|
258304
258444
|
}
|
|
258305
|
-
function
|
|
258445
|
+
function computeSourceEntityId(source, connectionDigests) {
|
|
258306
258446
|
return source.makeBuildId(connectionDigests[source.connectionName], source.getSQL());
|
|
258307
258447
|
}
|
|
258308
258448
|
async function resolvePackageConnections(pkg, names) {
|
|
@@ -258390,7 +258530,7 @@ function deriveBuildPlan(graphs, sources, connectionDigests, sourceNames, source
|
|
|
258390
258530
|
sourceID: source.sourceID,
|
|
258391
258531
|
connectionName: source.connectionName,
|
|
258392
258532
|
dialect: source.dialectName,
|
|
258393
|
-
|
|
258533
|
+
sourceEntityId: computeSourceEntityId(source, connectionDigests),
|
|
258394
258534
|
sql: source.getSQL(),
|
|
258395
258535
|
columns: deriveColumns(source),
|
|
258396
258536
|
annotationFields: deriveAnnotationFields(source),
|
|
@@ -261030,7 +261170,7 @@ class MaterializationController {
|
|
|
261030
261170
|
}
|
|
261031
261171
|
const instruction = raw;
|
|
261032
261172
|
const required = [
|
|
261033
|
-
"
|
|
261173
|
+
"sourceEntityId",
|
|
261034
261174
|
"materializedTableId",
|
|
261035
261175
|
"physicalTableName",
|
|
261036
261176
|
"realization"
|
|
@@ -261044,7 +261184,7 @@ class MaterializationController {
|
|
|
261044
261184
|
throw new BadRequestError("Build instruction 'realization' must be COPY or SNAPSHOT");
|
|
261045
261185
|
}
|
|
261046
261186
|
return {
|
|
261047
|
-
|
|
261187
|
+
sourceEntityId: instruction.sourceEntityId,
|
|
261048
261188
|
sourceID: typeof instruction.sourceID === "string" ? instruction.sourceID : undefined,
|
|
261049
261189
|
materializedTableId: instruction.materializedTableId,
|
|
261050
261190
|
physicalTableName: instruction.physicalTableName,
|
|
@@ -261065,6 +261205,40 @@ class MaterializationController {
|
|
|
261065
261205
|
}
|
|
261066
261206
|
}
|
|
261067
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
|
+
|
|
261068
261242
|
// ../../node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
|
|
261069
261243
|
var DEFAULT_REQUEST_TIMEOUT_MSEC = 60000;
|
|
261070
261244
|
|
|
@@ -267628,9 +267802,9 @@ async function resolveEnvironmentId(repository, environmentName) {
|
|
|
267628
267802
|
}
|
|
267629
267803
|
|
|
267630
267804
|
// src/service/materialization_service.ts
|
|
267631
|
-
var
|
|
267632
|
-
function stagingSuffix(
|
|
267633
|
-
return `_${
|
|
267805
|
+
var STAGING_ID_LEN = 12;
|
|
267806
|
+
function stagingSuffix(sourceEntityId) {
|
|
267807
|
+
return `_${sourceEntityId.replace(/-/g, "").substring(0, STAGING_ID_LEN)}`;
|
|
267634
267808
|
}
|
|
267635
267809
|
function selfAssignTableName(persistSource) {
|
|
267636
267810
|
return deriveAnnotationFields(persistSource).name || persistSource.name;
|
|
@@ -267792,18 +267966,18 @@ class MaterializationService {
|
|
|
267792
267966
|
for (const persistSource of iterGraphSources(graph, compiled.sources)) {
|
|
267793
267967
|
if (include && !include.has(persistSource.name))
|
|
267794
267968
|
continue;
|
|
267795
|
-
const
|
|
267796
|
-
if (seen.has(
|
|
267969
|
+
const sourceEntityId = computeSourceEntityId(persistSource, compiled.connectionDigests);
|
|
267970
|
+
if (seen.has(sourceEntityId))
|
|
267797
267971
|
continue;
|
|
267798
|
-
seen.add(
|
|
267799
|
-
const prior = priorEntries[
|
|
267972
|
+
seen.add(sourceEntityId);
|
|
267973
|
+
const prior = priorEntries[sourceEntityId];
|
|
267800
267974
|
if (prior && prior.physicalTableName) {
|
|
267801
|
-
carried[
|
|
267975
|
+
carried[sourceEntityId] = prior;
|
|
267802
267976
|
continue;
|
|
267803
267977
|
}
|
|
267804
267978
|
instructions.push({
|
|
267805
|
-
|
|
267806
|
-
materializedTableId: `local-${
|
|
267979
|
+
sourceEntityId,
|
|
267980
|
+
materializedTableId: `local-${sourceEntityId.substring(0, STAGING_ID_LEN)}`,
|
|
267807
267981
|
physicalTableName: selfAssignTableName(persistSource),
|
|
267808
267982
|
realization: "COPY"
|
|
267809
267983
|
});
|
|
@@ -267824,9 +267998,11 @@ class MaterializationService {
|
|
|
267824
267998
|
}
|
|
267825
267999
|
async autoLoadManifest(environment, packageName, entries) {
|
|
267826
268000
|
const manifestEntries = {};
|
|
267827
|
-
for (const [
|
|
268001
|
+
for (const [sourceEntityId, entry] of Object.entries(entries)) {
|
|
267828
268002
|
if (entry.physicalTableName) {
|
|
267829
|
-
manifestEntries[
|
|
268003
|
+
manifestEntries[sourceEntityId] = {
|
|
268004
|
+
tableName: entry.physicalTableName
|
|
268005
|
+
};
|
|
267830
268006
|
}
|
|
267831
268007
|
}
|
|
267832
268008
|
try {
|
|
@@ -267848,13 +268024,13 @@ class MaterializationService {
|
|
|
267848
268024
|
if (!plan) {
|
|
267849
268025
|
throw new BadRequestError("Package has no persist sources; buildInstructions cannot be applied");
|
|
267850
268026
|
}
|
|
267851
|
-
const
|
|
268027
|
+
const plannedSourceEntityIds = new Set;
|
|
267852
268028
|
for (const source of Object.values(plan.sources)) {
|
|
267853
|
-
|
|
268029
|
+
plannedSourceEntityIds.add(source.sourceEntityId);
|
|
267854
268030
|
}
|
|
267855
268031
|
for (const instruction of instructions) {
|
|
267856
|
-
if (!
|
|
267857
|
-
throw new BadRequestError(`Instruction references unknown
|
|
268032
|
+
if (!plannedSourceEntityIds.has(instruction.sourceEntityId)) {
|
|
268033
|
+
throw new BadRequestError(`Instruction references unknown sourceEntityId '${instruction.sourceEntityId}'`);
|
|
267858
268034
|
}
|
|
267859
268035
|
if (instruction.realization === "SNAPSHOT") {
|
|
267860
268036
|
throw new BadRequestError("realization=SNAPSHOT is not supported (COPY only)");
|
|
@@ -267864,20 +268040,22 @@ class MaterializationService {
|
|
|
267864
268040
|
async executeInstructedBuild(compiled, instructions, seedEntries, signal) {
|
|
267865
268041
|
const { graphs, sources, connectionDigests, connections } = compiled;
|
|
267866
268042
|
const bySourceID = new Map;
|
|
267867
|
-
const
|
|
268043
|
+
const bySourceEntityId = new Map;
|
|
267868
268044
|
for (const instruction of instructions) {
|
|
267869
268045
|
if (instruction.sourceID) {
|
|
267870
268046
|
bySourceID.set(instruction.sourceID, instruction);
|
|
267871
268047
|
}
|
|
267872
|
-
|
|
268048
|
+
bySourceEntityId.set(instruction.sourceEntityId, instruction);
|
|
267873
268049
|
}
|
|
267874
268050
|
const manifest = new Manifest;
|
|
267875
268051
|
const entries = {};
|
|
267876
|
-
for (const [
|
|
268052
|
+
for (const [sourceEntityId, entry] of Object.entries(seedEntries)) {
|
|
267877
268053
|
if (entry.physicalTableName) {
|
|
267878
|
-
manifest.update(
|
|
268054
|
+
manifest.update(sourceEntityId, {
|
|
268055
|
+
tableName: entry.physicalTableName
|
|
268056
|
+
});
|
|
267879
268057
|
}
|
|
267880
|
-
entries[
|
|
268058
|
+
entries[sourceEntityId] = entry;
|
|
267881
268059
|
}
|
|
267882
268060
|
for (const graph of graphs) {
|
|
267883
268061
|
const connection = connections.get(graph.connectionName);
|
|
@@ -267887,25 +268065,25 @@ class MaterializationService {
|
|
|
267887
268065
|
for (const persistSource of iterGraphSources(graph, sources)) {
|
|
267888
268066
|
if (signal.aborted)
|
|
267889
268067
|
throw new Error("Build cancelled");
|
|
267890
|
-
const
|
|
267891
|
-
const instruction = bySourceID.get(persistSource.sourceID) ??
|
|
268068
|
+
const sourceEntityId = computeSourceEntityId(persistSource, connectionDigests);
|
|
268069
|
+
const instruction = bySourceID.get(persistSource.sourceID) ?? bySourceEntityId.get(sourceEntityId);
|
|
267892
268070
|
if (!instruction)
|
|
267893
268071
|
continue;
|
|
267894
268072
|
const entry = await this.buildOneSource(persistSource, instruction, connection, connectionDigests, manifest);
|
|
267895
|
-
entries[
|
|
268073
|
+
entries[sourceEntityId] = entry;
|
|
267896
268074
|
}
|
|
267897
268075
|
}
|
|
267898
268076
|
return entries;
|
|
267899
268077
|
}
|
|
267900
268078
|
async buildOneSource(persistSource, instruction, connection, connectionDigests, manifest) {
|
|
267901
|
-
const
|
|
268079
|
+
const sourceEntityId = instruction.sourceEntityId;
|
|
267902
268080
|
const physicalTableName = instruction.physicalTableName;
|
|
267903
268081
|
const buildSQL = persistSource.getSQL({
|
|
267904
268082
|
buildManifest: manifest.buildManifest,
|
|
267905
268083
|
connectionDigests
|
|
267906
268084
|
});
|
|
267907
268085
|
const bareName = bareTableName(physicalTableName);
|
|
267908
|
-
const stagingTableName = `${physicalTableName}${stagingSuffix(
|
|
268086
|
+
const stagingTableName = `${physicalTableName}${stagingSuffix(sourceEntityId)}`;
|
|
267909
268087
|
const dialect = persistSource.dialectName;
|
|
267910
268088
|
const quotedStaging = quoteTablePath(stagingTableName, dialect);
|
|
267911
268089
|
const quotedPhysical = quoteTablePath(physicalTableName, dialect);
|
|
@@ -267928,7 +268106,7 @@ class MaterializationService {
|
|
|
267928
268106
|
}
|
|
267929
268107
|
throw err;
|
|
267930
268108
|
}
|
|
267931
|
-
manifest.update(
|
|
268109
|
+
manifest.update(sourceEntityId, { tableName: physicalTableName });
|
|
267932
268110
|
const durationMs = Math.round(performance.now() - startTime);
|
|
267933
268111
|
recordSourceBuildDuration(durationMs);
|
|
267934
268112
|
logger.info(`Built materialized source ${persistSource.name}`, {
|
|
@@ -267936,7 +268114,7 @@ class MaterializationService {
|
|
|
267936
268114
|
durationMs
|
|
267937
268115
|
});
|
|
267938
268116
|
return {
|
|
267939
|
-
|
|
268117
|
+
sourceEntityId,
|
|
267940
268118
|
sourceName: persistSource.name,
|
|
267941
268119
|
materializedTableId: instruction.materializedTableId,
|
|
267942
268120
|
physicalTableName,
|
|
@@ -267993,7 +268171,7 @@ class MaterializationService {
|
|
|
267993
268171
|
if (!connectionName || !physicalTableName) {
|
|
267994
268172
|
logger.warn("Skipping manifest entry with no connection/table", {
|
|
267995
268173
|
materializationId: m.id,
|
|
267996
|
-
|
|
268174
|
+
sourceEntityId: entry.sourceEntityId
|
|
267997
268175
|
});
|
|
267998
268176
|
continue;
|
|
267999
268177
|
}
|
|
@@ -268005,7 +268183,7 @@ class MaterializationService {
|
|
|
268005
268183
|
}
|
|
268006
268184
|
const dialect = connection.dialectName;
|
|
268007
268185
|
await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(physicalTableName, dialect)}`);
|
|
268008
|
-
await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(`${physicalTableName}${stagingSuffix(entry.
|
|
268186
|
+
await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(`${physicalTableName}${stagingSuffix(entry.sourceEntityId)}`, dialect)}`);
|
|
268009
268187
|
recordDropTables("success");
|
|
268010
268188
|
logger.info("Dropped materialized table on delete", {
|
|
268011
268189
|
materializationId: m.id,
|
|
@@ -268172,6 +268350,122 @@ class PackageMemoryGovernor {
|
|
|
268172
268350
|
}
|
|
268173
268351
|
}
|
|
268174
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
|
+
|
|
268175
268469
|
// src/server.ts
|
|
268176
268470
|
function parseArgs() {
|
|
268177
268471
|
const args = process.argv.slice(2);
|
|
@@ -268266,6 +268560,8 @@ var queryController = new QueryController(environmentStore);
|
|
|
268266
268560
|
var compileController = new CompileController(environmentStore);
|
|
268267
268561
|
var materializationService = new MaterializationService(environmentStore);
|
|
268268
268562
|
var materializationController = new MaterializationController(materializationService);
|
|
268563
|
+
var themeStore = new ThemeStore(environmentStore.storageManager, SERVER_ROOT);
|
|
268564
|
+
var themeController = new ThemeController(themeStore, SERVER_ROOT);
|
|
268269
268565
|
var mcpApp = import_express2.default();
|
|
268270
268566
|
registerHealthEndpoints(mcpApp);
|
|
268271
268567
|
mcpApp.use(MCP_ENDPOINT, import_express2.default.json());
|
|
@@ -268539,13 +268835,44 @@ app.get(`${API_PREFIX2}/environments/:environmentName/packages/:packageName/page
|
|
|
268539
268835
|
app.get(`${API_PREFIX2}/status`, async (_req, res) => {
|
|
268540
268836
|
try {
|
|
268541
268837
|
const status = await environmentStore.getStatus();
|
|
268542
|
-
|
|
268838
|
+
const theme = environmentStore.storageManager.isInitialized() ? await themeStore.get() : undefined;
|
|
268839
|
+
res.status(200).json({ ...status, theme: theme ?? {} });
|
|
268543
268840
|
} catch (error) {
|
|
268544
268841
|
logger.error("Error getting status", { error });
|
|
268545
268842
|
const { json, status } = internalErrorToHttpError(error);
|
|
268546
268843
|
res.status(status).json(json);
|
|
268547
268844
|
}
|
|
268548
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
|
+
});
|
|
268549
268876
|
app.get(`${API_PREFIX2}/watch-mode/status`, watchModeController.getWatchStatus);
|
|
268550
268877
|
app.post(`${API_PREFIX2}/watch-mode/start`, watchModeController.startWatching);
|
|
268551
268878
|
app.post(`${API_PREFIX2}/watch-mode/stop`, watchModeController.stopWatchMode);
|