@malloy-publisher/server 0.0.137 → 0.0.138

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
@@ -133002,6 +133002,18 @@ class ModelController {
133002
133002
  }
133003
133003
  return model.getNotebook();
133004
133004
  }
133005
+ async executeNotebookCell(projectName, packageName, notebookPath, cellIndex) {
133006
+ const project = await this.projectStore.getProject(projectName, false);
133007
+ const p = await project.getPackage(packageName, false);
133008
+ const model = p.getModel(notebookPath);
133009
+ if (!model) {
133010
+ throw new ModelNotFoundError(`${notebookPath} does not exist`);
133011
+ }
133012
+ if (model.getType() === "model") {
133013
+ throw new ModelNotFoundError(`${notebookPath} is a model`);
133014
+ }
133015
+ return model.executeNotebookCell(cellIndex);
133016
+ }
133005
133017
  }
133006
133018
 
133007
133019
  // src/controller/package.controller.ts
@@ -139036,7 +139048,14 @@ class Model {
139036
139048
  return new Model(packageName, modelPath, dataStyles, modelType, modelMaterializer, modelDef, sources, queries, runnableNotebookCells, undefined);
139037
139049
  } catch (error) {
139038
139050
  let computedError = error;
139051
+ if (error instanceof Error && error.stack) {
139052
+ console.error("Error stack", error.stack);
139053
+ }
139039
139054
  if (error instanceof import_malloy2.MalloyError) {
139055
+ const problems = error.problems;
139056
+ for (const problem of problems) {
139057
+ console.error("Problem", problem);
139058
+ }
139040
139059
  computedError = new ModelCompilationError(error);
139041
139060
  }
139042
139061
  return new Model(packageName, modelPath, dataStyles, modelType, undefined, undefined, undefined, undefined, undefined, computedError);
@@ -139171,27 +139190,12 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`);
139171
139190
  };
139172
139191
  }
139173
139192
  async getNotebookModel() {
139174
- const notebookCells = await Promise.all(this.runnableNotebookCells.map(async (cell) => {
139175
- let queryName = undefined;
139176
- let queryResult = undefined;
139177
- if (cell.runnable) {
139178
- try {
139179
- const rowLimit = (await cell.runnable.getPreparedResult()).resultExplore.limit || ROW_LIMIT;
139180
- const result = await cell.runnable.run({ rowLimit });
139181
- const query = (await cell.runnable.getPreparedQuery())._query;
139182
- queryName = query.as || query.name;
139183
- queryResult = result?._queryResult && this.modelInfo && JSON.stringify(import_malloy2.API.util.wrapResult(result));
139184
- } catch {
139185
- }
139186
- }
139193
+ const notebookCells = this.runnableNotebookCells.map((cell) => {
139187
139194
  return {
139188
139195
  type: cell.type,
139189
- text: cell.text,
139190
- queryName,
139191
- result: queryResult,
139192
- newSources: cell.newSources?.map((source) => JSON.stringify(source))
139196
+ text: cell.text
139193
139197
  };
139194
- }));
139198
+ });
139195
139199
  return {
139196
139200
  type: "notebook",
139197
139201
  packageName: this.packageName,
@@ -139203,6 +139207,57 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`);
139203
139207
  notebookCells
139204
139208
  };
139205
139209
  }
139210
+ async executeNotebookCell(cellIndex) {
139211
+ if (this.compilationError) {
139212
+ throw this.compilationError;
139213
+ }
139214
+ if (!this.runnableNotebookCells) {
139215
+ throw new BadRequestError("No notebook cells available");
139216
+ }
139217
+ if (cellIndex < 0 || cellIndex >= this.runnableNotebookCells.length) {
139218
+ throw new BadRequestError(`Cell index ${cellIndex} out of range (0-${this.runnableNotebookCells.length - 1})`);
139219
+ }
139220
+ const cell = this.runnableNotebookCells[cellIndex];
139221
+ if (cell.type === "markdown") {
139222
+ return {
139223
+ type: cell.type,
139224
+ text: cell.text
139225
+ };
139226
+ }
139227
+ let queryName = undefined;
139228
+ let queryResult = undefined;
139229
+ if (cell.runnable) {
139230
+ try {
139231
+ const rowLimit = (await cell.runnable.getPreparedResult()).resultExplore.limit || ROW_LIMIT;
139232
+ const result = await cell.runnable.run({ rowLimit });
139233
+ const query = (await cell.runnable.getPreparedQuery())._query;
139234
+ queryName = query.as || query.name;
139235
+ queryResult = result?._queryResult && this.modelInfo && JSON.stringify(import_malloy2.API.util.wrapResult(result));
139236
+ } catch (error) {
139237
+ if (error instanceof import_malloy2.MalloyError) {
139238
+ throw error;
139239
+ }
139240
+ const errorMessage = error instanceof Error ? error.message : String(error);
139241
+ if (errorMessage.trim() === "Model has no queries.") {
139242
+ return {
139243
+ type: "code",
139244
+ text: cell.text
139245
+ };
139246
+ } else {
139247
+ console.log("Error message: ", errorMessage);
139248
+ }
139249
+ console.log("Cell content: ", cellIndex, cell.type, cell.text);
139250
+ throw new BadRequestError(`Cell execution failed: ${errorMessage}`);
139251
+ }
139252
+ }
139253
+ return {
139254
+ type: cell.type,
139255
+ text: cell.text,
139256
+ queryName,
139257
+ result: queryResult,
139258
+ newSources: cell.newSources?.map((source) => JSON.stringify(source))
139259
+ };
139260
+ }
139206
139261
  static async getModelRuntime(packagePath, modelPath, connections) {
139207
139262
  const fullModelPath = path4.join(packagePath, modelPath);
139208
139263
  try {
@@ -144712,8 +144767,8 @@ app.get(`${API_PREFIX2}/projects/:projectName/packages/:packageName/models/*?`,
144712
144767
  return;
144713
144768
  }
144714
144769
  try {
144715
- const zero = 0;
144716
- res.status(200).json(await modelController.getModel(req.params.projectName, req.params.packageName, req.params[zero]));
144770
+ const modelPath = req.params["0"];
144771
+ res.status(200).json(await modelController.getModel(req.params.projectName, req.params.packageName, modelPath));
144717
144772
  } catch (error) {
144718
144773
  logger2.error(error);
144719
144774
  const { json: json2, status } = internalErrorToHttpError(error);
@@ -144733,14 +144788,35 @@ app.get(`${API_PREFIX2}/projects/:projectName/packages/:packageName/notebooks`,
144733
144788
  res.status(status).json(json2);
144734
144789
  }
144735
144790
  });
144791
+ app.get(`${API_PREFIX2}/projects/:projectName/packages/:packageName/notebooks/*/cells/:cellIndex`, async (req, res) => {
144792
+ if (req.query.versionId) {
144793
+ setVersionIdError(res);
144794
+ return;
144795
+ }
144796
+ try {
144797
+ const cellIndex = parseInt(req.params.cellIndex, 10);
144798
+ if (isNaN(cellIndex)) {
144799
+ res.status(400).json({
144800
+ error: "Invalid cell index"
144801
+ });
144802
+ return;
144803
+ }
144804
+ const notebookPath = req.params["0"];
144805
+ res.status(200).json(await modelController.executeNotebookCell(req.params.projectName, req.params.packageName, notebookPath, cellIndex));
144806
+ } catch (error) {
144807
+ logger2.error(error);
144808
+ const { json: json2, status } = internalErrorToHttpError(error);
144809
+ res.status(status).json(json2);
144810
+ }
144811
+ });
144736
144812
  app.get(`${API_PREFIX2}/projects/:projectName/packages/:packageName/notebooks/*?`, async (req, res) => {
144737
144813
  if (req.query.versionId) {
144738
144814
  setVersionIdError(res);
144739
144815
  return;
144740
144816
  }
144741
144817
  try {
144742
- const zero = 0;
144743
- res.status(200).json(await modelController.getNotebook(req.params.projectName, req.params.packageName, req.params[zero]));
144818
+ const notebookPath = req.params["0"];
144819
+ res.status(200).json(await modelController.getNotebook(req.params.projectName, req.params.packageName, notebookPath));
144744
144820
  } catch (error) {
144745
144821
  logger2.error(error);
144746
144822
  const { json: json2, status } = internalErrorToHttpError(error);
@@ -144753,8 +144829,8 @@ app.post(`${API_PREFIX2}/projects/:projectName/packages/:packageName/models/*?/q
144753
144829
  return;
144754
144830
  }
144755
144831
  try {
144756
- const zero = 0;
144757
- res.status(200).json(await queryController.getQuery(req.params.projectName, req.params.packageName, req.params[zero], req.body.sourceName, req.body.queryName, req.body.query));
144832
+ const modelPath = req.params["0"];
144833
+ res.status(200).json(await queryController.getQuery(req.params.projectName, req.params.packageName, modelPath, req.body.sourceName, req.body.queryName, req.body.query));
144758
144834
  } catch (error) {
144759
144835
  logger2.error(error);
144760
144836
  const { json: json2, status } = internalErrorToHttpError(error);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@malloy-publisher/server",
3
3
  "description": "Malloy Publisher Server",
4
- "version": "0.0.137",
4
+ "version": "0.0.138",
5
5
  "main": "dist/server.js",
6
6
  "bin": {
7
7
  "malloy-publisher": "dist/server.js"
@@ -5,7 +5,7 @@ import { ProjectStore } from "../service/project_store";
5
5
  type ApiNotebook = components["schemas"]["Notebook"];
6
6
  type ApiModel = components["schemas"]["Model"];
7
7
  type ApiCompiledModel = components["schemas"]["CompiledModel"];
8
- type ApiCompiledNotebook = components["schemas"]["CompiledNotebook"];
8
+ type ApiRawNotebook = components["schemas"]["RawNotebook"];
9
9
  export type ListModelsFilterEnum =
10
10
  components["parameters"]["ListModelsFilterEnum"];
11
11
  export class ModelController {
@@ -54,7 +54,7 @@ export class ModelController {
54
54
  projectName: string,
55
55
  packageName: string,
56
56
  notebookPath: string,
57
- ): Promise<ApiCompiledNotebook> {
57
+ ): Promise<ApiRawNotebook> {
58
58
  const project = await this.projectStore.getProject(projectName, false);
59
59
  const p = await project.getPackage(packageName, false);
60
60
  const model = p.getModel(notebookPath);
@@ -67,4 +67,29 @@ export class ModelController {
67
67
 
68
68
  return model.getNotebook();
69
69
  }
70
+
71
+ public async executeNotebookCell(
72
+ projectName: string,
73
+ packageName: string,
74
+ notebookPath: string,
75
+ cellIndex: number,
76
+ ): Promise<{
77
+ type: "code" | "markdown";
78
+ text: string;
79
+ queryName?: string;
80
+ result?: string;
81
+ newSources?: string[];
82
+ }> {
83
+ const project = await this.projectStore.getProject(projectName, false);
84
+ const p = await project.getPackage(packageName, false);
85
+ const model = p.getModel(notebookPath);
86
+ if (!model) {
87
+ throw new ModelNotFoundError(`${notebookPath} does not exist`);
88
+ }
89
+ if (model.getType() === "model") {
90
+ throw new ModelNotFoundError(`${notebookPath} is a model`);
91
+ }
92
+
93
+ return model.executeNotebookCell(cellIndex);
94
+ }
70
95
  }
@@ -82,9 +82,9 @@ export function registerNotebookResource(
82
82
  throw new McpGetResourceError(errorDetails);
83
83
  }
84
84
 
85
- // Now try to compile/get the actual notebook content
86
- const notebookContent: components["schemas"]["CompiledNotebook"] =
87
- await modelInstance.getNotebook(); // This can throw ModelCompilationError
85
+ // Now try to get the actual notebook content
86
+ const notebookContent: components["schemas"]["RawNotebook"] =
87
+ await modelInstance.getNotebook();
88
88
  return notebookContent;
89
89
  } catch (error) {
90
90
  if (error instanceof McpGetResourceError) {
package/src/server.ts CHANGED
@@ -653,12 +653,13 @@ app.get(
653
653
  }
654
654
 
655
655
  try {
656
- const zero = 0 as unknown;
656
+ // Express stores wildcard matches in params['0']
657
+ const modelPath = (req.params as Record<string, string>)["0"];
657
658
  res.status(200).json(
658
659
  await modelController.getModel(
659
660
  req.params.projectName,
660
661
  req.params.packageName,
661
- req.params[zero as keyof typeof req.params],
662
+ modelPath,
662
663
  ),
663
664
  );
664
665
  } catch (error) {
@@ -692,6 +693,44 @@ app.get(
692
693
  },
693
694
  );
694
695
 
696
+ // Execute notebook cell route must come BEFORE the general get notebook route
697
+ // to avoid the wildcard matching incorrectly
698
+ app.get(
699
+ `${API_PREFIX}/projects/:projectName/packages/:packageName/notebooks/*/cells/:cellIndex`,
700
+ async (req, res) => {
701
+ if (req.query.versionId) {
702
+ setVersionIdError(res);
703
+ return;
704
+ }
705
+
706
+ try {
707
+ const cellIndex = parseInt(req.params.cellIndex, 10);
708
+ if (isNaN(cellIndex)) {
709
+ res.status(400).json({
710
+ error: "Invalid cell index",
711
+ });
712
+ return;
713
+ }
714
+
715
+ // Express stores wildcard matches in params['0']
716
+ const notebookPath = (req.params as Record<string, string>)["0"];
717
+
718
+ res.status(200).json(
719
+ await modelController.executeNotebookCell(
720
+ req.params.projectName,
721
+ req.params.packageName,
722
+ notebookPath,
723
+ cellIndex,
724
+ ),
725
+ );
726
+ } catch (error) {
727
+ logger.error(error);
728
+ const { json, status } = internalErrorToHttpError(error as Error);
729
+ res.status(status).json(json);
730
+ }
731
+ },
732
+ );
733
+
695
734
  app.get(
696
735
  `${API_PREFIX}/projects/:projectName/packages/:packageName/notebooks/*?`,
697
736
  async (req, res) => {
@@ -701,12 +740,13 @@ app.get(
701
740
  }
702
741
 
703
742
  try {
704
- const zero = 0 as unknown;
743
+ // Express stores wildcard matches in params['0']
744
+ const notebookPath = (req.params as Record<string, string>)["0"];
705
745
  res.status(200).json(
706
746
  await modelController.getNotebook(
707
747
  req.params.projectName,
708
748
  req.params.packageName,
709
- req.params[zero as keyof typeof req.params],
749
+ notebookPath,
710
750
  ),
711
751
  );
712
752
  } catch (error) {
@@ -726,12 +766,13 @@ app.post(
726
766
  }
727
767
 
728
768
  try {
729
- const zero = 0 as unknown;
769
+ // Express stores wildcard matches in params['0']
770
+ const modelPath = (req.params as Record<string, string>)["0"];
730
771
  res.status(200).json(
731
772
  await queryController.getQuery(
732
773
  req.params.projectName,
733
774
  req.params.packageName,
734
- req.params[zero as keyof typeof req.params],
775
+ modelPath,
735
776
  req.body.sourceName as string,
736
777
  req.body.queryName as string,
737
778
  req.body.query as string,
@@ -41,7 +41,7 @@ import { URL_READER } from "../utils";
41
41
 
42
42
  type ApiCompiledModel = components["schemas"]["CompiledModel"];
43
43
  type ApiNotebookCell = components["schemas"]["NotebookCell"];
44
- type ApiCompiledNotebook = components["schemas"]["CompiledNotebook"];
44
+ type ApiRawNotebook = components["schemas"]["RawNotebook"];
45
45
  // @ts-expect-error TODO: Fix missing Source type in API
46
46
  type ApiSource = components["schemas"]["Source"];
47
47
  type ApiView = components["schemas"]["View"];
@@ -155,7 +155,15 @@ export class Model {
155
155
  );
156
156
  } catch (error) {
157
157
  let computedError = error;
158
+ if (error instanceof Error && error.stack) {
159
+ console.error("Error stack", error.stack);
160
+ }
161
+
158
162
  if (error instanceof MalloyError) {
163
+ const problems = error.problems;
164
+ for (const problem of problems) {
165
+ console.error("Problem", problem);
166
+ }
159
167
  computedError = new ModelCompilationError(error);
160
168
  }
161
169
  return new Model(
@@ -215,7 +223,7 @@ export class Model {
215
223
  return this.compilationError;
216
224
  }
217
225
 
218
- public async getNotebook(): Promise<ApiCompiledNotebook> {
226
+ public async getNotebook(): Promise<ApiRawNotebook> {
219
227
  if (this.compilationError) {
220
228
  throw this.compilationError;
221
229
  }
@@ -341,42 +349,16 @@ export class Model {
341
349
  } as ApiCompiledModel;
342
350
  }
343
351
 
344
- private async getNotebookModel(): Promise<ApiCompiledNotebook> {
345
- const notebookCells: ApiNotebookCell[] = await Promise.all(
346
- (this.runnableNotebookCells as RunnableNotebookCell[]).map(
347
- async (cell) => {
348
- let queryName: string | undefined = undefined;
349
- let queryResult: string | undefined = undefined;
350
- if (cell.runnable) {
351
- try {
352
- const rowLimit =
353
- (await cell.runnable.getPreparedResult()).resultExplore
354
- .limit || ROW_LIMIT;
355
- const result = await cell.runnable.run({ rowLimit });
356
- const query = (await cell.runnable.getPreparedQuery())
357
- ._query;
358
- queryName = (query as NamedQuery).as || query.name;
359
- queryResult =
360
- result?._queryResult &&
361
- this.modelInfo &&
362
- JSON.stringify(API.util.wrapResult(result));
363
- } catch {
364
- // Catch block intentionally left empty as per previous logic review.
365
- // Error handling for specific cases might be added here if needed.
366
- }
367
- }
368
- return {
369
- type: cell.type,
370
- text: cell.text,
371
- queryName: queryName,
372
- result: queryResult,
373
- newSources: cell.newSources?.map((source) =>
374
- JSON.stringify(source),
375
- ),
376
- } as ApiNotebookCell;
377
- },
378
- ),
379
- );
352
+ private async getNotebookModel(): Promise<ApiRawNotebook> {
353
+ // Return raw cell contents without executing them
354
+ const notebookCells: ApiNotebookCell[] = (
355
+ this.runnableNotebookCells as RunnableNotebookCell[]
356
+ ).map((cell) => {
357
+ return {
358
+ type: cell.type,
359
+ text: cell.text,
360
+ } as ApiNotebookCell;
361
+ });
380
362
 
381
363
  return {
382
364
  type: "notebook",
@@ -389,7 +371,82 @@ export class Model {
389
371
  sources: this.modelDef && this.sources,
390
372
  queries: this.modelDef && this.queries,
391
373
  notebookCells,
392
- } as ApiCompiledModel;
374
+ } as ApiRawNotebook;
375
+ }
376
+
377
+ public async executeNotebookCell(cellIndex: number): Promise<{
378
+ type: "code" | "markdown";
379
+ text: string;
380
+ queryName?: string;
381
+ result?: string;
382
+ newSources?: string[];
383
+ }> {
384
+ if (this.compilationError) {
385
+ throw this.compilationError;
386
+ }
387
+
388
+ if (!this.runnableNotebookCells) {
389
+ throw new BadRequestError("No notebook cells available");
390
+ }
391
+
392
+ if (cellIndex < 0 || cellIndex >= this.runnableNotebookCells.length) {
393
+ throw new BadRequestError(
394
+ `Cell index ${cellIndex} out of range (0-${this.runnableNotebookCells.length - 1})`,
395
+ );
396
+ }
397
+
398
+ const cell = this.runnableNotebookCells[cellIndex];
399
+
400
+ if (cell.type === "markdown") {
401
+ return {
402
+ type: cell.type,
403
+ text: cell.text,
404
+ };
405
+ }
406
+
407
+ // For code cells, execute the runnable if available
408
+ let queryName: string | undefined = undefined;
409
+ let queryResult: string | undefined = undefined;
410
+
411
+ if (cell.runnable) {
412
+ try {
413
+ const rowLimit =
414
+ (await cell.runnable.getPreparedResult()).resultExplore.limit ||
415
+ ROW_LIMIT;
416
+ const result = await cell.runnable.run({ rowLimit });
417
+ const query = (await cell.runnable.getPreparedQuery())._query;
418
+ queryName = (query as NamedQuery).as || query.name;
419
+ queryResult =
420
+ result?._queryResult &&
421
+ this.modelInfo &&
422
+ JSON.stringify(API.util.wrapResult(result));
423
+ } catch (error) {
424
+ // Re-throw execution errors so the client knows about them
425
+ if (error instanceof MalloyError) {
426
+ throw error;
427
+ }
428
+ const errorMessage =
429
+ error instanceof Error ? error.message : String(error);
430
+ if (errorMessage.trim() === "Model has no queries.") {
431
+ return {
432
+ type: "code",
433
+ text: cell.text,
434
+ };
435
+ } else {
436
+ console.log("Error message: ", errorMessage);
437
+ }
438
+ console.log("Cell content: ", cellIndex, cell.type, cell.text);
439
+ throw new BadRequestError(`Cell execution failed: ${errorMessage}`);
440
+ }
441
+ }
442
+
443
+ return {
444
+ type: cell.type,
445
+ text: cell.text,
446
+ queryName: queryName,
447
+ result: queryResult,
448
+ newSources: cell.newSources?.map((source) => JSON.stringify(source)),
449
+ };
393
450
  }
394
451
 
395
452
  static async getModelRuntime(