@malloy-publisher/server 0.0.85 → 0.0.86

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/server.js CHANGED
@@ -228489,14 +228489,14 @@ var require_brace_expansion2 = __commonJS((exports2, module2) => {
228489
228489
  var require_minimatch = __commonJS((exports2, module2) => {
228490
228490
  module2.exports = minimatch;
228491
228491
  minimatch.Minimatch = Minimatch;
228492
- var path3 = function() {
228492
+ var path4 = function() {
228493
228493
  try {
228494
228494
  return require("path");
228495
228495
  } catch (e) {}
228496
228496
  }() || {
228497
228497
  sep: "/"
228498
228498
  };
228499
- minimatch.sep = path3.sep;
228499
+ minimatch.sep = path4.sep;
228500
228500
  var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {};
228501
228501
  var expand = require_brace_expansion2();
228502
228502
  var plTypes = {
@@ -228587,8 +228587,8 @@ var require_minimatch = __commonJS((exports2, module2) => {
228587
228587
  if (!options)
228588
228588
  options = {};
228589
228589
  pattern = pattern.trim();
228590
- if (!options.allowWindowsEscape && path3.sep !== "/") {
228591
- pattern = pattern.split(path3.sep).join("/");
228590
+ if (!options.allowWindowsEscape && path4.sep !== "/") {
228591
+ pattern = pattern.split(path4.sep).join("/");
228592
228592
  }
228593
228593
  this.options = options;
228594
228594
  this.set = [];
@@ -228965,8 +228965,8 @@ var require_minimatch = __commonJS((exports2, module2) => {
228965
228965
  if (f === "/" && partial)
228966
228966
  return true;
228967
228967
  var options = this.options;
228968
- if (path3.sep !== "/") {
228969
- f = f.split(path3.sep).join("/");
228968
+ if (path4.sep !== "/") {
228969
+ f = f.split(path4.sep).join("/");
228970
228970
  }
228971
228971
  f = f.split(slashSplit);
228972
228972
  this.debug(this.pattern, "split", f);
@@ -229077,9 +229077,9 @@ var require_recursive_readdir = __commonJS((exports2, module2) => {
229077
229077
  var p = require("path");
229078
229078
  var minimatch = require_minimatch();
229079
229079
  function patternMatcher(pattern) {
229080
- return function(path3, stats) {
229080
+ return function(path4, stats) {
229081
229081
  var minimatcher = new minimatch.Minimatch(pattern, { matchBase: true });
229082
- return (!minimatcher.negate || stats.isFile()) && minimatcher.match(path3);
229082
+ return (!minimatcher.negate || stats.isFile()) && minimatcher.match(path4);
229083
229083
  };
229084
229084
  }
229085
229085
  function toMatcherFunction(ignoreEntry) {
@@ -229089,14 +229089,14 @@ var require_recursive_readdir = __commonJS((exports2, module2) => {
229089
229089
  return patternMatcher(ignoreEntry);
229090
229090
  }
229091
229091
  }
229092
- function readdir(path3, ignores, callback) {
229092
+ function readdir(path4, ignores, callback) {
229093
229093
  if (typeof ignores == "function") {
229094
229094
  callback = ignores;
229095
229095
  ignores = [];
229096
229096
  }
229097
229097
  if (!callback) {
229098
229098
  return new Promise(function(resolve, reject) {
229099
- readdir(path3, ignores || [], function(err, data) {
229099
+ readdir(path4, ignores || [], function(err, data) {
229100
229100
  if (err) {
229101
229101
  reject(err);
229102
229102
  } else {
@@ -229107,7 +229107,7 @@ var require_recursive_readdir = __commonJS((exports2, module2) => {
229107
229107
  }
229108
229108
  ignores = ignores.map(toMatcherFunction);
229109
229109
  var list = [];
229110
- fs4.readdir(path3, function(err, files) {
229110
+ fs4.readdir(path4, function(err, files) {
229111
229111
  if (err) {
229112
229112
  return callback(err);
229113
229113
  }
@@ -229116,7 +229116,7 @@ var require_recursive_readdir = __commonJS((exports2, module2) => {
229116
229116
  return callback(null, list);
229117
229117
  }
229118
229118
  files.forEach(function(file) {
229119
- var filePath = p.join(path3, file);
229119
+ var filePath = p.join(path4, file);
229120
229120
  fs4.stat(filePath, function(_err, stats) {
229121
229121
  if (_err) {
229122
229122
  return callback(_err);
@@ -230033,7 +230033,7 @@ var require_scheduled_task = __commonJS((exports2, module2) => {
230033
230033
  var require_background_scheduled_task = __commonJS((exports2, module2) => {
230034
230034
  var __dirname = "/home/runner/work/publisher/publisher/node_modules/node-cron/src/background-scheduled-task";
230035
230035
  var EventEmitter = require("events");
230036
- var path4 = require("path");
230036
+ var path5 = require("path");
230037
230037
  var { fork } = require("child_process");
230038
230038
  var uuid = require_dist19();
230039
230039
  var daemonPath = `${__dirname}/daemon.js`;
@@ -230069,7 +230069,7 @@ var require_background_scheduled_task = __commonJS((exports2, module2) => {
230069
230069
  options.scheduled = true;
230070
230070
  this.forkProcess.send({
230071
230071
  type: "register",
230072
- path: path4.resolve(this.taskPath),
230072
+ path: path5.resolve(this.taskPath),
230073
230073
  cron: this.cronExpression,
230074
230074
  options
230075
230075
  });
@@ -235134,16 +235134,23 @@ var import_cors = __toESM(require_lib4());
235134
235134
  var import_express = __toESM(require_express2());
235135
235135
  var http = __toESM(require("http"));
235136
235136
  var import_http_proxy_middleware = __toESM(require_dist());
235137
- var path7 = __toESM(require("path"));
235137
+ var path8 = __toESM(require("path"));
235138
235138
 
235139
235139
  // src/errors.ts
235140
+ var import_malloy = require("@malloydata/malloy");
235140
235141
  function internalErrorToHttpError(error) {
235141
235142
  if (error instanceof BadRequestError) {
235142
235143
  return httpError(400, error.message);
235144
+ } else if (error instanceof FrozenConfigError) {
235145
+ return httpError(403, error.message);
235146
+ } else if (error instanceof ProjectNotFoundError) {
235147
+ return httpError(404, error.message);
235143
235148
  } else if (error instanceof PackageNotFoundError) {
235144
235149
  return httpError(404, error.message);
235145
235150
  } else if (error instanceof ModelNotFoundError) {
235146
235151
  return httpError(404, error.message);
235152
+ } else if (error instanceof import_malloy.MalloyError) {
235153
+ return httpError(400, error.message);
235147
235154
  } else if (error instanceof ConnectionNotFoundError) {
235148
235155
  return httpError(404, error.message);
235149
235156
  } else if (error instanceof ModelCompilationError) {
@@ -235212,10 +235219,17 @@ class ModelCompilationError extends Error {
235212
235219
  }
235213
235220
  }
235214
235221
 
235222
+ class FrozenConfigError extends Error {
235223
+ constructor(message = "Publisher config can't be updated when publisher.config.json has `frozenConfig: true`") {
235224
+ super(message);
235225
+ }
235226
+ }
235227
+
235215
235228
  // src/logger.ts
235216
235229
  var import_winston = __toESM(require_winston());
235230
+ var isTelemetryEnabled = Boolean(process.env.OTEL_EXPORTER_OTLP_ENDPOINT);
235217
235231
  var logger = import_winston.default.createLogger({
235218
- format: import_winston.default.format.combine(import_winston.default.format.uncolorize(), import_winston.default.format.timestamp(), import_winston.default.format.json()),
235232
+ format: isTelemetryEnabled ? import_winston.default.format.combine(import_winston.default.format.uncolorize(), import_winston.default.format.timestamp(), import_winston.default.format.json()) : import_winston.default.format.combine(import_winston.default.format.colorize(), import_winston.default.format.simple()),
235219
235233
  transports: [new import_winston.default.transports.Console]
235220
235234
  });
235221
235235
  var loggerMiddleware = (req, res, next) => {
@@ -235352,9 +235366,9 @@ async function getSchemasForConnection(connection) {
235352
235366
  }
235353
235367
  try {
235354
235368
  const bigquery = getBigqueryConnection(connection);
235355
- const [datasets] = await bigquery.getDatasets({
235356
- ...connection.bigqueryConnection.defaultProjectId ? { projectId: connection.bigqueryConnection.defaultProjectId } : {}
235357
- });
235369
+ const projectId = connection.bigqueryConnection.defaultProjectId;
235370
+ const options = projectId ? { projectId } : {};
235371
+ const [datasets] = await bigquery.getDatasets(options);
235358
235372
  return datasets.filter((dataset) => dataset.id).map((dataset) => {
235359
235373
  return {
235360
235374
  name: dataset.id,
@@ -235699,6 +235713,30 @@ class PackageController {
235699
235713
  const p = await project.getPackage(packageName, reload);
235700
235714
  return p.getPackageMetadata();
235701
235715
  }
235716
+ async addPackage(projectName, body) {
235717
+ if (this.projectStore.publisherConfigIsFrozen) {
235718
+ throw new FrozenConfigError;
235719
+ }
235720
+ if (!body.name) {
235721
+ throw new BadRequestError("Package name is required");
235722
+ }
235723
+ const project = await this.projectStore.getProject(projectName, false);
235724
+ return project.addPackage(body.name);
235725
+ }
235726
+ async deletePackage(projectName, packageName) {
235727
+ if (this.projectStore.publisherConfigIsFrozen) {
235728
+ throw new FrozenConfigError;
235729
+ }
235730
+ const project = await this.projectStore.getProject(projectName, false);
235731
+ return project.deletePackage(packageName);
235732
+ }
235733
+ async updatePackage(projectName, packageName, body) {
235734
+ if (this.projectStore.publisherConfigIsFrozen) {
235735
+ throw new FrozenConfigError;
235736
+ }
235737
+ const project = await this.projectStore.getProject(projectName, false);
235738
+ return project.updatePackage(packageName, body);
235739
+ }
235702
235740
  }
235703
235741
 
235704
235742
  // src/constants.ts
@@ -243179,7 +243217,7 @@ function registerTools(mcpServer, projectStore) {
243179
243217
  const mappedResources = await Promise.all(allProjects.map(async (project) => {
243180
243218
  const name = project.name;
243181
243219
  const projectInstance = await project.project;
243182
- const metadata = await projectInstance.getProjectMetadata();
243220
+ const metadata = await projectInstance.reloadProjectMetadata();
243183
243221
  const readme = metadata.readme;
243184
243222
  return {
243185
243223
  name,
@@ -243480,7 +243518,34 @@ function initializeMcpServer(projectStore) {
243480
243518
 
243481
243519
  // src/service/project_store.ts
243482
243520
  var fs7 = __toESM(require("fs/promises"));
243483
- var path6 = __toESM(require("path"));
243521
+ var path7 = __toESM(require("path"));
243522
+
243523
+ // src/utils.ts
243524
+ var fs2 = __toESM(require("fs"));
243525
+ var import_path2 = __toESM(require("path"));
243526
+ var import_url = require("url");
243527
+ var PACKAGE_MANIFEST_NAME = "publisher.json";
243528
+ var CONNECTIONS_MANIFEST_NAME = "publisher.connections.json";
243529
+ var MODEL_FILE_SUFFIX = ".malloy";
243530
+ var NOTEBOOK_FILE_SUFFIX = ".malloynb";
243531
+ var ROW_LIMIT = 1000;
243532
+ var URL_READER = {
243533
+ readURL: (url) => {
243534
+ let path3 = url.toString();
243535
+ if (url.protocol == "file:") {
243536
+ path3 = import_url.fileURLToPath(url);
243537
+ }
243538
+ return fs2.promises.readFile(path3, "utf8");
243539
+ }
243540
+ };
243541
+ var isPublisherConfigFrozen = (serverRoot) => {
243542
+ const publisherConfigPath = import_path2.default.join(serverRoot, "publisher.config.json");
243543
+ if (!fs2.existsSync(publisherConfigPath)) {
243544
+ return false;
243545
+ }
243546
+ const publisherConfig = JSON.parse(fs2.readFileSync(publisherConfigPath, "utf8"));
243547
+ return Boolean(publisherConfig.frozenConfig);
243548
+ };
243484
243549
 
243485
243550
  // ../../node_modules/async-mutex/index.mjs
243486
243551
  var E_TIMEOUT = new Error("timeout while waiting for mutex to become available");
@@ -243692,7 +243757,7 @@ class Mutex {
243692
243757
 
243693
243758
  // src/service/project.ts
243694
243759
  var fs6 = __toESM(require("fs/promises"));
243695
- var path5 = __toESM(require("path"));
243760
+ var path6 = __toESM(require("path"));
243696
243761
 
243697
243762
  // src/service/connection.ts
243698
243763
  var import_db_postgres = require("@malloydata/db-postgres");
@@ -243700,30 +243765,10 @@ var import_db_bigquery = require("@malloydata/db-bigquery");
243700
243765
  var import_db_snowflake = require("@malloydata/db-snowflake");
243701
243766
  var import_db_trino = require("@malloydata/db-trino");
243702
243767
  var import_db_mysql = require("@malloydata/db-mysql");
243703
- var import_path2 = __toESM(require("path"));
243768
+ var import_path3 = __toESM(require("path"));
243704
243769
  var import_promises = __toESM(require("fs/promises"));
243705
-
243706
- // src/utils.ts
243707
- var import_fs2 = require("fs");
243708
- var import_url = require("url");
243709
- var PACKAGE_MANIFEST_NAME = "publisher.json";
243710
- var CONNECTIONS_MANIFEST_NAME = "publisher.connections.json";
243711
- var MODEL_FILE_SUFFIX = ".malloy";
243712
- var NOTEBOOK_FILE_SUFFIX = ".malloynb";
243713
- var ROW_LIMIT = 1000;
243714
- var URL_READER = {
243715
- readURL: (url) => {
243716
- let path2 = url.toString();
243717
- if (url.protocol == "file:") {
243718
- path2 = import_url.fileURLToPath(url);
243719
- }
243720
- return import_fs2.promises.readFile(path2, "utf8");
243721
- }
243722
- };
243723
-
243724
- // src/service/connection.ts
243725
243770
  async function readConnectionConfig(basePath) {
243726
- const fullPath = import_path2.default.join(basePath, CONNECTIONS_MANIFEST_NAME);
243771
+ const fullPath = import_path3.default.join(basePath, CONNECTIONS_MANIFEST_NAME);
243727
243772
  try {
243728
243773
  await import_promises.default.stat(fullPath);
243729
243774
  } catch {
@@ -243782,7 +243827,7 @@ async function createConnections(basePath) {
243782
243827
  }
243783
243828
  let serviceAccountKeyPath = undefined;
243784
243829
  if (connection.bigqueryConnection.serviceAccountKeyJson) {
243785
- serviceAccountKeyPath = import_path2.default.join("/tmp", `${connection.name}-${v4_default()}-service-account-key.json`);
243830
+ serviceAccountKeyPath = import_path3.default.join("/tmp", `${connection.name}-${v4_default()}-service-account-key.json`);
243786
243831
  await import_promises.default.writeFile(serviceAccountKeyPath, connection.bigqueryConnection.serviceAccountKeyJson);
243787
243832
  }
243788
243833
  const bigqueryConnectionOptions = {
@@ -243873,19 +243918,19 @@ function getConnectionAttributes(connection) {
243873
243918
 
243874
243919
  // src/service/package.ts
243875
243920
  var fs5 = __toESM(require("fs/promises"));
243876
- var path4 = __toESM(require("path"));
243921
+ var path5 = __toESM(require("path"));
243877
243922
  var import_db_duckdb = require("@malloydata/db-duckdb");
243878
- var import_malloy2 = require("@malloydata/malloy");
243923
+ var import_malloy3 = require("@malloydata/malloy");
243879
243924
  var import_api2 = __toESM(require_src25());
243880
243925
  var import_recursive_readdir = __toESM(require_recursive_readdir());
243881
243926
 
243882
243927
  // src/service/model.ts
243883
- var import_malloy = require("@malloydata/malloy");
243928
+ var import_malloy2 = require("@malloydata/malloy");
243884
243929
  var import_malloy_sql = require("@malloydata/malloy-sql");
243885
243930
  var import__package = __toESM(require("@malloydata/malloy/package.json"));
243886
243931
  var import_api = __toESM(require_src25());
243887
243932
  var fs4 = __toESM(require("fs/promises"));
243888
- var path3 = __toESM(require("path"));
243933
+ var path4 = __toESM(require("path"));
243889
243934
 
243890
243935
  // src/data_styles.ts
243891
243936
  function compileDataStyles(styles) {
@@ -243965,7 +244010,7 @@ class Model {
243965
244010
  this.queries = queries;
243966
244011
  this.runnableNotebookCells = runnableNotebookCells;
243967
244012
  this.compilationError = compilationError;
243968
- this.modelInfo = this.modelDef ? import_malloy.modelDefToModelInfo(this.modelDef) : undefined;
244013
+ this.modelInfo = this.modelDef ? import_malloy2.modelDefToModelInfo(this.modelDef) : undefined;
243969
244014
  }
243970
244015
  static async create(packageName, packagePath, modelPath, connections) {
243971
244016
  const { runtime, modelURL, importBaseURL, dataStyles, modelType } = await Model.getModelRuntime(packagePath, modelPath, connections);
@@ -243982,7 +244027,7 @@ class Model {
243982
244027
  return new Model(packageName, modelPath, dataStyles, modelType, modelMaterializer, modelDef, sources, queries, runnableNotebookCells, undefined);
243983
244028
  } catch (error) {
243984
244029
  let computedError = error;
243985
- if (error instanceof import_malloy.MalloyError) {
244030
+ if (error instanceof import_malloy2.MalloyError) {
243986
244031
  computedError = new ModelCompilationError(error);
243987
244032
  }
243988
244033
  return new Model(packageName, modelPath, dataStyles, modelType, undefined, undefined, undefined, undefined, undefined, computedError);
@@ -243998,7 +244043,7 @@ class Model {
243998
244043
  return this.sources;
243999
244044
  }
244000
244045
  getSourceInfos() {
244001
- return this.modelDef ? import_malloy.modelDefToModelInfo(this.modelDef).entries.filter((entry) => {
244046
+ return this.modelDef ? import_malloy2.modelDefToModelInfo(this.modelDef).entries.filter((entry) => {
244002
244047
  return entry.kind === "source";
244003
244048
  }) : undefined;
244004
244049
  }
@@ -244070,7 +244115,7 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`);
244070
244115
  "malloy.model.query.status": "success"
244071
244116
  });
244072
244117
  return {
244073
- result: import_malloy.API.util.wrapResult(queryResults),
244118
+ result: import_malloy2.API.util.wrapResult(queryResults),
244074
244119
  modelInfo: this.modelInfo,
244075
244120
  dataStyles: this.dataStyles
244076
244121
  };
@@ -244083,7 +244128,7 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`);
244083
244128
  malloyVersion: MALLOY_VERSION,
244084
244129
  dataStyles: JSON.stringify(this.dataStyles),
244085
244130
  modelDef: JSON.stringify(this.modelDef),
244086
- modelInfo: JSON.stringify(this.modelDef ? import_malloy.modelDefToModelInfo(this.modelDef) : {}),
244131
+ modelInfo: JSON.stringify(this.modelDef ? import_malloy2.modelDefToModelInfo(this.modelDef) : {}),
244087
244132
  sourceInfos: this.getSourceInfos()?.map((sourceInfo) => JSON.stringify(sourceInfo)),
244088
244133
  sources: this.sources,
244089
244134
  queries: this.queries
@@ -244099,7 +244144,7 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`);
244099
244144
  const result = await cell.runnable.run({ rowLimit });
244100
244145
  const query = (await cell.runnable.getPreparedQuery())._query;
244101
244146
  queryName = query.as || query.name;
244102
- queryResult = result?._queryResult && this.modelInfo && JSON.stringify(import_malloy.API.util.wrapResult(result));
244147
+ queryResult = result?._queryResult && this.modelInfo && JSON.stringify(import_malloy2.API.util.wrapResult(result));
244103
244148
  } catch {}
244104
244149
  }
244105
244150
  return {
@@ -244115,14 +244160,14 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`);
244115
244160
  packageName: this.packageName,
244116
244161
  modelPath: this.modelPath,
244117
244162
  malloyVersion: MALLOY_VERSION,
244118
- modelInfo: JSON.stringify(this.modelDef ? import_malloy.modelDefToModelInfo(this.modelDef) : {}),
244163
+ modelInfo: JSON.stringify(this.modelDef ? import_malloy2.modelDefToModelInfo(this.modelDef) : {}),
244119
244164
  sources: this.modelDef && this.sources,
244120
244165
  queries: this.modelDef && this.queries,
244121
244166
  notebookCells
244122
244167
  };
244123
244168
  }
244124
244169
  static async getModelRuntime(packagePath, modelPath, connections) {
244125
- const fullModelPath = path3.join(packagePath, modelPath);
244170
+ const fullModelPath = path4.join(packagePath, modelPath);
244126
244171
  try {
244127
244172
  if (!(await fs4.stat(fullModelPath)).isFile()) {
244128
244173
  throw new ModelNotFoundError(`${modelPath} is not a file.`);
@@ -244138,12 +244183,12 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`);
244138
244183
  } else {
244139
244184
  throw new ModelNotFoundError(`${modelPath} is not a valid model name. Model files must end in .malloy or .malloynb.`);
244140
244185
  }
244141
- const importBaseURL = new URL("file://" + path3.dirname(fullModelPath) + "/");
244186
+ const importBaseURL = new URL("file://" + path4.dirname(fullModelPath) + "/");
244142
244187
  const modelURL = new URL("file://" + fullModelPath);
244143
244188
  const urlReader = new HackyDataStylesAccumulator(URL_READER);
244144
- const runtime = new import_malloy.Runtime({
244189
+ const runtime = new import_malloy2.Runtime({
244145
244190
  urlReader,
244146
- connections: new import_malloy.FixedConnectionMap(connections, "duckdb")
244191
+ connections: new import_malloy2.FixedConnectionMap(connections, "duckdb")
244147
244192
  });
244148
244193
  const dataStyles = urlReader.getHackyAccumulatedDataStyles();
244149
244194
  return { runtime, modelURL, importBaseURL, dataStyles, modelType };
@@ -244157,7 +244202,7 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`);
244157
244202
  }));
244158
244203
  }
244159
244204
  static getSources(modelPath, modelDef) {
244160
- return Object.values(modelDef.contents).filter((obj) => import_malloy.isSourceDef(obj)).map((sourceObj) => ({
244205
+ return Object.values(modelDef.contents).filter((obj) => import_malloy2.isSourceDef(obj)).map((sourceObj) => ({
244161
244206
  name: sourceObj.as || sourceObj.name,
244162
244207
  annotations: sourceObj.annotation?.blockNotes?.filter((note) => note.at.url.includes(modelPath)).map((note) => note.text),
244163
244208
  views: sourceObj.fields.filter((turtleObj) => turtleObj.type === "turtle").filter((turtleObj) => turtleObj.pipeline.map((stage) => stage.type).every((type) => type == "reduce")).map((turtleObj) => ({
@@ -244231,12 +244276,12 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`);
244231
244276
  const importModel = (await runtime.loadModel(modelString, {
244232
244277
  importBaseURL
244233
244278
  }).getModel())._modelDef;
244234
- const importModelInfo = import_malloy.modelDefToModelInfo(importModel);
244279
+ const importModelInfo = import_malloy2.modelDefToModelInfo(importModel);
244235
244280
  newSources = importModelInfo.entries.filter((entry) => entry.kind === "source").filter((source) => !(source.name in oldSources));
244236
244281
  oldImports.push(importLocation.importURL.toString());
244237
244282
  }));
244238
244283
  }
244239
- const currentModelInfo = import_malloy.modelDefToModelInfo(currentModelDef);
244284
+ const currentModelInfo = import_malloy2.modelDefToModelInfo(currentModelDef);
244240
244285
  newSources = newSources.concat(currentModelInfo.entries.filter((entry) => entry.kind === "source").filter((source) => !(source.name in oldSources)));
244241
244286
  for (const source of newSources) {
244242
244287
  oldSources[source.name] = source;
@@ -244266,7 +244311,7 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`);
244266
244311
  return this.modelType;
244267
244312
  }
244268
244313
  async getFileText(packagePath) {
244269
- const fullPath = path3.join(packagePath, this.modelPath);
244314
+ const fullPath = path4.join(packagePath, this.modelPath);
244270
244315
  try {
244271
244316
  return await fs4.readFile(fullPath, "utf8");
244272
244317
  } catch {
@@ -244577,11 +244622,11 @@ class Package {
244577
244622
  throw new PackageNotFoundError(`Package config for ${packagePath} does not exist.`);
244578
244623
  }
244579
244624
  return files.map((fullPath) => {
244580
- return path4.relative(packagePath, fullPath).replace(/\\/g, "/");
244625
+ return path5.relative(packagePath, fullPath).replace(/\\/g, "/");
244581
244626
  }).filter((modelPath) => modelPath.endsWith(MODEL_FILE_SUFFIX) || modelPath.endsWith(NOTEBOOK_FILE_SUFFIX));
244582
244627
  }
244583
244628
  static async validatePackageManifestExistsOrThrowError(packagePath) {
244584
- const packageConfigPath = path4.join(packagePath, PACKAGE_MANIFEST_NAME);
244629
+ const packageConfigPath = path5.join(packagePath, PACKAGE_MANIFEST_NAME);
244585
244630
  try {
244586
244631
  await fs5.stat(packageConfigPath);
244587
244632
  } catch {
@@ -244589,7 +244634,7 @@ class Package {
244589
244634
  }
244590
244635
  }
244591
244636
  static async readPackageConfig(packagePath) {
244592
- const packageConfigPath = path4.join(packagePath, PACKAGE_MANIFEST_NAME);
244637
+ const packageConfigPath = path5.join(packagePath, PACKAGE_MANIFEST_NAME);
244593
244638
  const packageConfigContents = await fs5.readFile(packageConfigPath);
244594
244639
  const packageManifest = JSON.parse(packageConfigContents.toString());
244595
244640
  return {
@@ -244611,13 +244656,13 @@ class Package {
244611
244656
  let files = undefined;
244612
244657
  files = await import_recursive_readdir.default(packagePath);
244613
244658
  return files.map((fullPath) => {
244614
- return path4.relative(packagePath, fullPath).replace(/\\/g, "/");
244659
+ return path5.relative(packagePath, fullPath).replace(/\\/g, "/");
244615
244660
  }).filter((modelPath) => modelPath.endsWith(".parquet") || modelPath.endsWith(".csv"));
244616
244661
  }
244617
244662
  static async getDatabaseInfo(packagePath, databasePath) {
244618
- const fullPath = path4.join(packagePath, databasePath);
244619
- const runtime = new import_malloy2.ConnectionRuntime({
244620
- urlReader: new import_malloy2.EmptyURLReader,
244663
+ const fullPath = path5.join(packagePath, databasePath);
244664
+ const runtime = new import_malloy3.ConnectionRuntime({
244665
+ urlReader: new import_malloy3.EmptyURLReader,
244621
244666
  connections: [new import_db_duckdb.DuckDBConnection("duckdb")]
244622
244667
  });
244623
244668
  const model = runtime.loadModel(`source: temp is duckdb.table('${fullPath}')`);
@@ -244631,6 +244676,15 @@ class Package {
244631
244676
  const rowCount = result.data.value[0].row_count?.valueOf();
244632
244677
  return { name: databasePath, rowCount, columns: schema };
244633
244678
  }
244679
+ setName(name) {
244680
+ this.packageName = name;
244681
+ }
244682
+ setProjectName(projectName) {
244683
+ this.projectName = projectName;
244684
+ }
244685
+ setPackageMetadata(packageMetadata) {
244686
+ this.packageMetadata = packageMetadata;
244687
+ }
244634
244688
  }
244635
244689
 
244636
244690
  // src/service/project.ts
@@ -244642,12 +244696,36 @@ class Project {
244642
244696
  internalConnections;
244643
244697
  projectPath;
244644
244698
  projectName;
244699
+ metadata;
244645
244700
  constructor(projectName, projectPath, malloyConnections, internalConnections, apiConnections) {
244646
244701
  this.projectName = projectName;
244647
244702
  this.projectPath = projectPath;
244648
244703
  this.malloyConnections = malloyConnections;
244649
244704
  this.internalConnections = internalConnections;
244650
244705
  this.apiConnections = apiConnections;
244706
+ this.metadata = {
244707
+ resource: `${API_PREFIX}/projects/${this.projectName}`,
244708
+ name: this.projectName
244709
+ };
244710
+ this.reloadProjectMetadata();
244711
+ }
244712
+ async update(payload) {
244713
+ if (payload.name) {
244714
+ this.projectName = payload.name;
244715
+ this.packages.forEach((_package) => {
244716
+ _package.setProjectName(this.projectName);
244717
+ });
244718
+ this.metadata.name = this.projectName;
244719
+ }
244720
+ if (payload.resource) {
244721
+ this.projectPath = payload.resource.replace(`${API_PREFIX}/projects/`, "");
244722
+ if (!await fs6.exists(this.projectPath)) {
244723
+ throw new ProjectNotFoundError(`Project path "${this.projectPath}" not found`);
244724
+ }
244725
+ this.metadata.resource = payload.resource;
244726
+ }
244727
+ this.metadata.readme = payload.readme;
244728
+ return this;
244651
244729
  }
244652
244730
  static async create(projectName, projectPath) {
244653
244731
  if (!(await fs6.stat(projectPath)).isDirectory()) {
@@ -244663,16 +244741,17 @@ class Project {
244663
244741
  };
244664
244742
  }));
244665
244743
  }
244666
- async getProjectMetadata() {
244744
+ async reloadProjectMetadata() {
244667
244745
  let readme = "";
244668
244746
  try {
244669
- readme = (await fs6.readFile(path5.join(this.projectPath, README_NAME))).toString();
244747
+ readme = (await fs6.readFile(path6.join(this.projectPath, README_NAME))).toString();
244670
244748
  } catch {}
244671
- return {
244749
+ this.metadata = {
244672
244750
  resource: `${API_PREFIX}/projects/${this.projectName}`,
244673
244751
  name: this.projectName,
244674
244752
  readme
244675
244753
  };
244754
+ return this.metadata;
244676
244755
  }
244677
244756
  listApiConnections() {
244678
244757
  return this.apiConnections;
@@ -244729,7 +244808,7 @@ class Project {
244729
244808
  return _package;
244730
244809
  }
244731
244810
  try {
244732
- const _package2 = await Package.create(this.projectName, packageName, path5.join(this.projectPath, packageName), this.malloyConnections);
244811
+ const _package2 = await Package.create(this.projectName, packageName, path6.join(this.projectPath, packageName), this.malloyConnections);
244733
244812
  this.packages.set(packageName, _package2);
244734
244813
  return _package2;
244735
244814
  } catch (error) {
@@ -244738,41 +244817,131 @@ class Project {
244738
244817
  }
244739
244818
  });
244740
244819
  }
244820
+ async addPackage(packageName) {
244821
+ const packagePath = path6.join(this.projectPath, packageName);
244822
+ if (!await fs6.exists(packagePath) || !(await fs6.stat(packagePath)).isDirectory()) {
244823
+ throw new PackageNotFoundError(`Package ${packageName} not found`);
244824
+ }
244825
+ this.packages.set(packageName, await Package.create(this.projectName, packageName, packagePath, this.malloyConnections));
244826
+ return this.packages.get(packageName);
244827
+ }
244828
+ async updatePackage(packageName, body) {
244829
+ const _package = this.packages.get(packageName);
244830
+ if (!_package) {
244831
+ throw new PackageNotFoundError(`Package ${packageName} not found`);
244832
+ }
244833
+ if (body.name) {
244834
+ _package.setName(body.name);
244835
+ }
244836
+ _package.setPackageMetadata({
244837
+ name: body.name,
244838
+ description: body.description,
244839
+ resource: body.resource
244840
+ });
244841
+ return _package.getPackageMetadata();
244842
+ }
244843
+ async deletePackage(packageName) {
244844
+ const _package = this.packages.get(packageName);
244845
+ if (!_package) {
244846
+ throw new PackageNotFoundError(`Package ${packageName} not found`);
244847
+ }
244848
+ this.packages.delete(packageName);
244849
+ }
244741
244850
  }
244742
244851
 
244743
244852
  // src/service/project_store.ts
244744
244853
  class ProjectStore {
244745
244854
  serverRootPath;
244746
244855
  projects = new Map;
244856
+ publisherConfigIsFrozen;
244747
244857
  constructor(serverRootPath) {
244748
244858
  this.serverRootPath = serverRootPath;
244859
+ this.initialize();
244749
244860
  }
244750
- async listProjects() {
244751
- const projectManifest = await ProjectStore.getProjectManifest(this.serverRootPath);
244752
- if (!projectManifest.projects) {
244753
- return [];
244754
- } else {
244755
- return Object.keys(projectManifest.projects).map((projectName) => ({
244756
- name: projectName,
244757
- resource: `${API_PREFIX}/projects/${projectName}`
244758
- }));
244861
+ async initialize() {
244862
+ try {
244863
+ this.publisherConfigIsFrozen = isPublisherConfigFrozen(this.serverRootPath);
244864
+ const projectManifest = await ProjectStore.reloadProjectManifest(this.serverRootPath);
244865
+ for (const projectName of Object.keys(projectManifest.projects)) {
244866
+ const projectPath = projectManifest.projects[projectName];
244867
+ const absoluteProjectPath = path7.join(this.serverRootPath, projectPath);
244868
+ const project = await Project.create(projectName, absoluteProjectPath);
244869
+ this.projects.set(projectName, project);
244870
+ }
244871
+ logger.info("Project store successfully initialized");
244872
+ } catch (error) {
244873
+ logger.error("Error initializing project store", { error });
244874
+ process.exit(1);
244759
244875
  }
244760
244876
  }
244877
+ listProjects() {
244878
+ return Array.from(this.projects.values()).map((project) => project.metadata);
244879
+ }
244761
244880
  async getProject(projectName, reload) {
244762
244881
  let project = this.projects.get(projectName);
244763
244882
  if (project === undefined || reload) {
244764
- const projectManifest = await ProjectStore.getProjectManifest(this.serverRootPath);
244883
+ const projectManifest = await ProjectStore.reloadProjectManifest(this.serverRootPath);
244765
244884
  if (!projectManifest.projects || !projectManifest.projects[projectName]) {
244766
- throw new ProjectNotFoundError(`Project ${projectName} not found in publisher.config.json`);
244885
+ throw new ProjectNotFoundError(`Project "${projectName}" not found in publisher`);
244767
244886
  }
244768
- project = await Project.create(projectName, path6.join(this.serverRootPath, projectManifest.projects[projectName]));
244769
- this.projects.set(projectName, project);
244887
+ project = await this.addProject({
244888
+ name: projectName,
244889
+ resource: `${API_PREFIX}/projects/${projectName}`
244890
+ });
244770
244891
  }
244771
244892
  return project;
244772
244893
  }
244773
- static async getProjectManifest(serverRootPath) {
244894
+ async addProject(project) {
244895
+ if (this.publisherConfigIsFrozen) {
244896
+ throw new FrozenConfigError;
244897
+ }
244898
+ const projectName = project.name;
244899
+ if (!projectName) {
244900
+ throw new Error("Project name is required");
244901
+ }
244902
+ const projectManifest = await ProjectStore.reloadProjectManifest(this.serverRootPath);
244903
+ const projectPath = projectManifest.projects[projectName];
244904
+ if (!projectPath) {
244905
+ throw new ProjectNotFoundError(`Project "${projectName}" not found in publisher.config.json`);
244906
+ }
244907
+ const absoluteProjectPath = path7.join(this.serverRootPath, projectPath);
244908
+ if (!(await fs7.stat(absoluteProjectPath)).isDirectory()) {
244909
+ throw new ProjectNotFoundError(`Project ${projectName} not found in ${absoluteProjectPath}`);
244910
+ }
244911
+ const newProject = await Project.create(projectName, absoluteProjectPath);
244912
+ this.projects.set(projectName, newProject);
244913
+ return newProject;
244914
+ }
244915
+ async updateProject(project) {
244916
+ if (this.publisherConfigIsFrozen) {
244917
+ throw new FrozenConfigError;
244918
+ }
244919
+ const projectName = project.name;
244920
+ if (!projectName) {
244921
+ throw new Error("Project name is required");
244922
+ }
244923
+ const existingProject = this.projects.get(projectName);
244924
+ if (!existingProject) {
244925
+ throw new ProjectNotFoundError(`Project ${projectName} not found`);
244926
+ }
244927
+ const updatedProject = await existingProject.update(project);
244928
+ this.projects.set(projectName, updatedProject);
244929
+ return updatedProject;
244930
+ }
244931
+ async deleteProject(projectName) {
244932
+ if (this.publisherConfigIsFrozen) {
244933
+ throw new FrozenConfigError;
244934
+ }
244935
+ const project = this.projects.get(projectName);
244936
+ if (!project) {
244937
+ throw new ProjectNotFoundError(`Project ${projectName} not found`);
244938
+ }
244939
+ this.projects.delete(projectName);
244940
+ return project;
244941
+ }
244942
+ static async reloadProjectManifest(serverRootPath) {
244774
244943
  try {
244775
- const projectManifestContent = await fs7.readFile(path6.join(serverRootPath, "publisher.config.json"), "utf8");
244944
+ const projectManifestContent = await fs7.readFile(path7.join(serverRootPath, "publisher.config.json"), "utf8");
244776
244945
  return JSON.parse(projectManifestContent);
244777
244946
  } catch (error) {
244778
244947
  if (error.code !== "ENOENT") {
@@ -244841,11 +245010,11 @@ var MCP_PORT = Number(process.env.MCP_PORT || 4040);
244841
245010
  var MCP_ENDPOINT = "/mcp";
244842
245011
  var ROOT;
244843
245012
  if (require.main) {
244844
- ROOT = path7.join(path7.dirname(require.main.filename), "app");
245013
+ ROOT = path8.join(path8.dirname(require.main.filename), "app");
244845
245014
  } else {
244846
- ROOT = path7.join(path7.dirname(process.argv[1] || __filename), "app");
245015
+ ROOT = path8.join(path8.dirname(process.argv[1] || __filename), "app");
244847
245016
  }
244848
- var SERVER_ROOT = path7.resolve(process.cwd(), process.env.SERVER_ROOT || ".");
245017
+ var SERVER_ROOT = path8.resolve(process.cwd(), process.env.SERVER_ROOT || ".");
244849
245018
  var API_PREFIX2 = "/api/v0";
244850
245019
  var isDevelopment = process.env["NODE_ENV"] === "development";
244851
245020
  var app = import_express.default();
@@ -244859,7 +245028,6 @@ var databaseController = new DatabaseController(projectStore);
244859
245028
  var queryController = new QueryController(projectStore);
244860
245029
  var scheduleController = new ScheduleController(projectStore);
244861
245030
  var mcpApp = import_express.default();
244862
- initProjects();
244863
245031
  mcpApp.use(MCP_ENDPOINT, import_express.default.json());
244864
245032
  mcpApp.use(MCP_ENDPOINT, import_cors.default());
244865
245033
  mcpApp.all(MCP_ENDPOINT, async (req, res) => {
@@ -244921,14 +245089,14 @@ mcpApp.all(MCP_ENDPOINT, async (req, res) => {
244921
245089
  });
244922
245090
  if (!isDevelopment) {
244923
245091
  app.use("/", import_express.default.static(ROOT));
244924
- app.use("/api-doc.html", import_express.default.static(path7.join(ROOT, "api-doc.html")));
245092
+ app.use("/api-doc.html", import_express.default.static(path8.join(ROOT, "api-doc.html")));
244925
245093
  } else {
244926
245094
  app.use(`${API_PREFIX2}`, loggerMiddleware);
244927
245095
  app.use(import_http_proxy_middleware.createProxyMiddleware({
244928
245096
  target: "http://localhost:5173",
244929
245097
  changeOrigin: true,
244930
245098
  ws: true,
244931
- pathFilter: (path8) => !path8.startsWith("/api")
245099
+ pathFilter: (path9) => !path9.startsWith("/api/")
244932
245100
  }));
244933
245101
  }
244934
245102
  var setVersionIdError = (res) => {
@@ -244946,10 +245114,37 @@ app.get(`${API_PREFIX2}/projects`, async (_req, res) => {
244946
245114
  res.status(status).json(json2);
244947
245115
  }
244948
245116
  });
245117
+ app.post(`${API_PREFIX2}/projects`, async (req, res) => {
245118
+ try {
245119
+ res.status(200).json(await projectStore.addProject(req.body));
245120
+ } catch (error) {
245121
+ logger.error(error);
245122
+ const { json: json2, status } = internalErrorToHttpError(error);
245123
+ res.status(status).json(json2);
245124
+ }
245125
+ });
244949
245126
  app.get(`${API_PREFIX2}/projects/:projectName`, async (req, res) => {
244950
245127
  try {
244951
245128
  const project = await projectStore.getProject(req.params.projectName, req.query.reload === "true");
244952
- res.status(200).json(await project.getProjectMetadata());
245129
+ res.status(200).json(project.metadata);
245130
+ } catch (error) {
245131
+ logger.error(error);
245132
+ const { json: json2, status } = internalErrorToHttpError(error);
245133
+ res.status(status).json(json2);
245134
+ }
245135
+ });
245136
+ app.patch(`${API_PREFIX2}/projects/:projectName`, async (req, res) => {
245137
+ try {
245138
+ res.status(200).json(await projectStore.updateProject(req.body));
245139
+ } catch (error) {
245140
+ logger.error(error);
245141
+ const { json: json2, status } = internalErrorToHttpError(error);
245142
+ res.status(status).json(json2);
245143
+ }
245144
+ });
245145
+ app.delete(`${API_PREFIX2}/projects/:projectName`, async (req, res) => {
245146
+ try {
245147
+ res.status(200).json(await projectStore.deleteProject(req.params.projectName));
244953
245148
  } catch (error) {
244954
245149
  logger.error(error);
244955
245150
  const { json: json2, status } = internalErrorToHttpError(error);
@@ -245053,6 +245248,15 @@ app.get(`${API_PREFIX2}/projects/:projectName/packages`, async (req, res) => {
245053
245248
  res.status(status).json(json2);
245054
245249
  }
245055
245250
  });
245251
+ app.post(`${API_PREFIX2}/projects/:projectName/packages`, async (req, res) => {
245252
+ try {
245253
+ res.status(200).json(await packageController.addPackage(req.params.projectName, req.body));
245254
+ } catch (error) {
245255
+ logger.error(error);
245256
+ const { json: json2, status } = internalErrorToHttpError(error);
245257
+ res.status(status).json(json2);
245258
+ }
245259
+ });
245056
245260
  app.get(`${API_PREFIX2}/projects/:projectName/packages/:packageName`, async (req, res) => {
245057
245261
  if (req.query.versionId) {
245058
245262
  setVersionIdError(res);
@@ -245066,6 +245270,24 @@ app.get(`${API_PREFIX2}/projects/:projectName/packages/:packageName`, async (req
245066
245270
  res.status(status).json(json2);
245067
245271
  }
245068
245272
  });
245273
+ app.patch(`${API_PREFIX2}/projects/:projectName/packages/:packageName`, async (req, res) => {
245274
+ try {
245275
+ res.status(200).json(await packageController.updatePackage(req.params.projectName, req.params.packageName, req.body));
245276
+ } catch (error) {
245277
+ logger.error(error);
245278
+ const { json: json2, status } = internalErrorToHttpError(error);
245279
+ res.status(status).json(json2);
245280
+ }
245281
+ });
245282
+ app.delete(`${API_PREFIX2}/projects/:projectName/packages/:packageName`, async (req, res) => {
245283
+ try {
245284
+ res.status(200).json(await packageController.deletePackage(req.params.projectName, req.params.packageName));
245285
+ } catch (error) {
245286
+ logger.error(error);
245287
+ const { json: json2, status } = internalErrorToHttpError(error);
245288
+ res.status(status).json(json2);
245289
+ }
245290
+ });
245069
245291
  app.get(`${API_PREFIX2}/projects/:projectName/packages/:packageName/models`, async (req, res) => {
245070
245292
  if (req.query.versionId) {
245071
245293
  setVersionIdError(res);
@@ -245161,9 +245383,9 @@ app.get(`${API_PREFIX2}/projects/:projectName/packages/:packageName/databases`,
245161
245383
  }
245162
245384
  });
245163
245385
  if (!isDevelopment) {
245164
- app.get("*", (_req, res) => res.sendFile(path7.resolve(ROOT, "index.html")));
245386
+ app.get("*", (_req, res) => res.sendFile(path8.resolve(ROOT, "index.html")));
245165
245387
  }
245166
- app.use((err, _req, res) => {
245388
+ app.use((err, _req, res, _next) => {
245167
245389
  logger.error("Unhandled error:", err);
245168
245390
  const { json: json2, status } = internalErrorToHttpError(err);
245169
245391
  res.status(status).json(json2);
@@ -245179,12 +245401,3 @@ mainServer.listen(PUBLISHER_PORT, PUBLISHER_HOST, () => {
245179
245401
  var mcpHttpServer = mcpApp.listen(MCP_PORT, PUBLISHER_HOST, () => {
245180
245402
  logger.info(`MCP server listening at http://${PUBLISHER_HOST}:${MCP_PORT}`);
245181
245403
  });
245182
- function initProjects() {
245183
- projectStore.listProjects().then((projects) => {
245184
- projects.forEach((project) => {
245185
- projectStore.getProject(project.name, false).then((project2) => {
245186
- project2.listPackages();
245187
- });
245188
- });
245189
- });
245190
- }