@malloy-publisher/server 0.0.87 → 0.0.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/build.ts +26 -0
  2. package/dist/app/api-doc.yaml +126 -5
  3. package/dist/app/assets/RenderedResult-BAZuT25g-QakVAbYy.js +2 -0
  4. package/dist/app/assets/{index-BbW5TZg_.js → index-Bq29VQqL.js} +2 -2
  5. package/dist/app/assets/{index-2xWCh-ya.css → index-CcIq0aEZ.css} +1 -1
  6. package/dist/app/assets/index-DZMePHJ5.js +251 -0
  7. package/dist/app/assets/{index-CIfV3yj1.js → index-TslDWlxH.js} +6 -6
  8. package/dist/app/assets/{index.umd-x-naS8R7.js → index.umd-BN4_E5KD.js} +259 -259
  9. package/dist/app/assets/mui-BEbinrI-.js +161 -0
  10. package/dist/app/assets/vendor-c5ypKtDW.js +17 -0
  11. package/dist/app/index.html +4 -2
  12. package/dist/instrumentation.js +67818 -35196
  13. package/dist/server.js +80404 -82231
  14. package/package.json +11 -5
  15. package/publisher.config.json +1 -1
  16. package/src/config.ts +20 -0
  17. package/src/constants.ts +14 -0
  18. package/src/controller/connection.controller.ts +21 -4
  19. package/src/controller/package.controller.ts +52 -2
  20. package/src/controller/schedule.controller.ts +3 -3
  21. package/src/controller/watch-mode.controller.ts +83 -0
  22. package/src/errors.ts +2 -1
  23. package/src/logger.ts +9 -0
  24. package/src/server.ts +33 -19
  25. package/src/service/connection.ts +159 -161
  26. package/src/service/model.ts +6 -6
  27. package/src/service/package.spec.ts +12 -10
  28. package/src/service/package.ts +15 -8
  29. package/src/service/project.ts +77 -36
  30. package/src/service/project_store.spec.ts +83 -56
  31. package/src/service/project_store.ts +330 -50
  32. package/src/utils.ts +0 -18
  33. package/tests/harness/mcp_test_setup.ts +5 -5
  34. package/dist/app/assets/RenderedResult-BAZuT25g-BMU632YI.js +0 -2
  35. package/dist/app/assets/index-C7whj6wK.js +0 -432
@@ -1,24 +1,35 @@
1
- import * as fs from "fs/promises";
1
+ import { GetObjectCommand, S3 } from "@aws-sdk/client-s3";
2
+ import { Storage } from "@google-cloud/storage";
3
+ import * as fs from "fs";
2
4
  import * as path from "path";
5
+ import AdmZip from "adm-zip";
6
+ import simpleGit from "simple-git";
7
+ import { Writable } from "stream";
3
8
  import { components } from "../api";
4
- import { API_PREFIX } from "../constants";
9
+ import { getPublisherConfig, isPublisherConfigFrozen } from "../config";
10
+ import { API_PREFIX, PUBLISHER_CONFIG_NAME, publisherPath } from "../constants";
5
11
  import { FrozenConfigError, ProjectNotFoundError } from "../errors";
6
12
  import { logger } from "../logger";
7
- import { isPublisherConfigFrozen } from "../utils";
8
13
  import { Project } from "./project";
9
14
  type ApiProject = components["schemas"]["Project"];
10
15
 
11
16
  export class ProjectStore {
12
- private serverRootPath: string;
17
+ public serverRootPath: string;
13
18
  private projects: Map<string, Project> = new Map();
14
19
  public publisherConfigIsFrozen: boolean;
20
+ public finishedInitialization: Promise<void>;
21
+ private s3Client = new S3({
22
+ followRegionRedirects: true,
23
+ });
24
+ private gcsClient = new Storage();
15
25
 
16
26
  constructor(serverRootPath: string) {
17
27
  this.serverRootPath = serverRootPath;
18
- void this.initialize();
28
+ this.finishedInitialization = this.initialize();
19
29
  }
20
30
 
21
31
  private async initialize() {
32
+ const initialTime = performance.now();
22
33
  try {
23
34
  this.publisherConfigIsFrozen = isPublisherConfigFrozen(
24
35
  this.serverRootPath,
@@ -26,46 +37,55 @@ export class ProjectStore {
26
37
  const projectManifest = await ProjectStore.reloadProjectManifest(
27
38
  this.serverRootPath,
28
39
  );
29
- for (const projectName of Object.keys(projectManifest.projects)) {
30
- const projectPath = projectManifest.projects[projectName];
31
- const absoluteProjectPath = path.join(
32
- this.serverRootPath,
33
- projectPath,
34
- );
35
- const project = await Project.create(
36
- projectName,
37
- absoluteProjectPath,
38
- );
39
- this.projects.set(projectName, project);
40
- }
41
- logger.info("Project store successfully initialized");
40
+ logger.info(`Initializing project store.`);
41
+ await Promise.all(
42
+ Object.keys(projectManifest.projects).map(async (projectName) => {
43
+ logger.info(`Adding project "${projectName}"`);
44
+ const project = await this.addProject(
45
+ {
46
+ name: projectName,
47
+ resource: `${API_PREFIX}/projects/${projectName}`,
48
+ location: projectManifest.projects[projectName],
49
+ },
50
+ true,
51
+ );
52
+ return project.listPackages();
53
+ }),
54
+ );
55
+ logger.info(
56
+ `Project store successfully initialized in ${performance.now() - initialTime}ms`,
57
+ );
42
58
  } catch (error) {
43
59
  logger.error("Error initializing project store", { error });
60
+ console.error(error);
44
61
  process.exit(1);
45
62
  }
46
63
  }
47
64
 
48
- public listProjects() {
49
- return Array.from(this.projects.values()).map(
50
- (project) => project.metadata,
65
+ public async listProjects() {
66
+ await this.finishedInitialization;
67
+ return Promise.all(
68
+ Array.from(this.projects.values()).map((project) =>
69
+ project.serialize(),
70
+ ),
51
71
  );
52
72
  }
53
73
 
54
74
  public async getProject(
55
75
  projectName: string,
56
- reload: boolean,
76
+ reload: boolean = false,
57
77
  ): Promise<Project> {
78
+ await this.finishedInitialization;
58
79
  let project = this.projects.get(projectName);
59
80
  if (project === undefined || reload) {
60
81
  const projectManifest = await ProjectStore.reloadProjectManifest(
61
82
  this.serverRootPath,
62
83
  );
63
- if (
64
- !projectManifest.projects ||
65
- !projectManifest.projects[projectName]
66
- ) {
84
+ const projectPath =
85
+ project?.metadata.location || projectManifest.projects[projectName];
86
+ if (!projectPath) {
67
87
  throw new ProjectNotFoundError(
68
- `Project "${projectName}" not found in publisher`,
88
+ `Project "${projectName}" could not be resolved to a path.`,
69
89
  );
70
90
  }
71
91
  project = await this.addProject({
@@ -76,10 +96,17 @@ export class ProjectStore {
76
96
  return project;
77
97
  }
78
98
 
79
- public async addProject(project: ApiProject) {
80
- if (this.publisherConfigIsFrozen) {
99
+ public async addProject(
100
+ project: ApiProject,
101
+ skipInitialization: boolean = false,
102
+ ) {
103
+ if (!skipInitialization) {
104
+ await this.finishedInitialization;
105
+ }
106
+ if (!skipInitialization && this.publisherConfigIsFrozen) {
81
107
  throw new FrozenConfigError();
82
108
  }
109
+
83
110
  const projectName = project.name;
84
111
  if (!projectName) {
85
112
  throw new Error("Project name is required");
@@ -87,24 +114,48 @@ export class ProjectStore {
87
114
  const projectManifest = await ProjectStore.reloadProjectManifest(
88
115
  this.serverRootPath,
89
116
  );
90
- const projectPath = projectManifest.projects[projectName];
91
- if (!projectPath) {
92
- throw new ProjectNotFoundError(
93
- `Project "${projectName}" not found in publisher.config.json`,
94
- );
95
- }
96
- const absoluteProjectPath = path.join(this.serverRootPath, projectPath);
97
- if (!(await fs.stat(absoluteProjectPath)).isDirectory()) {
98
- throw new ProjectNotFoundError(
99
- `Project ${projectName} not found in ${absoluteProjectPath}`,
117
+ const projectPath =
118
+ project.location || projectManifest.projects[projectName];
119
+ let absoluteProjectPath: string;
120
+ if (projectPath) {
121
+ absoluteProjectPath = await this.loadProjectIntoDisk(
122
+ projectName,
123
+ projectPath,
100
124
  );
125
+ if (absoluteProjectPath.endsWith(".zip")) {
126
+ absoluteProjectPath = await this.unzipProject(absoluteProjectPath);
127
+ }
128
+ } else {
129
+ absoluteProjectPath = await this.scaffoldProject(project);
101
130
  }
102
- const newProject = await Project.create(projectName, absoluteProjectPath);
131
+ const newProject = await Project.create(
132
+ projectName,
133
+ absoluteProjectPath,
134
+ project.connections || [],
135
+ );
103
136
  this.projects.set(projectName, newProject);
104
137
  return newProject;
105
138
  }
106
139
 
140
+ public async unzipProject(absoluteProjectPath: string) {
141
+ logger.info(
142
+ `Detected zip file at "${absoluteProjectPath}". Unzipping...`,
143
+ );
144
+ const unzippedProjectPath = absoluteProjectPath.replace(".zip", "");
145
+ await fs.promises.rm(unzippedProjectPath, {
146
+ recursive: true,
147
+ force: true,
148
+ });
149
+ await fs.promises.mkdir(unzippedProjectPath, { recursive: true });
150
+
151
+ const zip = new AdmZip(absoluteProjectPath);
152
+ zip.extractAllTo(unzippedProjectPath, true);
153
+
154
+ return unzippedProjectPath;
155
+ }
156
+
107
157
  public async updateProject(project: ApiProject) {
158
+ await this.finishedInitialization;
108
159
  if (this.publisherConfigIsFrozen) {
109
160
  throw new FrozenConfigError();
110
161
  }
@@ -122,6 +173,7 @@ export class ProjectStore {
122
173
  }
123
174
 
124
175
  public async deleteProject(projectName: string) {
176
+ await this.finishedInitialization;
125
177
  if (this.publisherConfigIsFrozen) {
126
178
  throw new FrozenConfigError();
127
179
  }
@@ -133,26 +185,20 @@ export class ProjectStore {
133
185
  return project;
134
186
  }
135
187
 
136
- private static async reloadProjectManifest(
137
- serverRootPath: string,
138
- ): Promise<{ projects: { [key: string]: string } }> {
188
+ public static async reloadProjectManifest(serverRootPath: string) {
139
189
  try {
140
- const projectManifestContent = await fs.readFile(
141
- path.join(serverRootPath, "publisher.config.json"),
142
- "utf8",
143
- );
144
- return JSON.parse(projectManifestContent);
190
+ return getPublisherConfig(serverRootPath);
145
191
  } catch (error) {
146
192
  if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
147
193
  logger.error(
148
- `Error reading publisher.config.json. Generating from directory`,
194
+ `Error reading ${PUBLISHER_CONFIG_NAME}. Generating from directory`,
149
195
  { error },
150
196
  );
151
197
  return { projects: {} };
152
198
  } else {
153
199
  // If publisher.config.json is missing, generate the manifest from directories
154
200
  try {
155
- const entries = await fs.readdir(serverRootPath, {
201
+ const entries = await fs.promises.readdir(serverRootPath, {
156
202
  withFileTypes: true,
157
203
  });
158
204
  const projects: { [key: string]: string } = {};
@@ -171,4 +217,238 @@ export class ProjectStore {
171
217
  }
172
218
  }
173
219
  }
220
+
221
+ private async scaffoldProject(project: ApiProject) {
222
+ const projectName = project.name;
223
+ if (!projectName) {
224
+ throw new Error("Project name is required");
225
+ }
226
+ const absoluteProjectPath = `${publisherPath}/${projectName}`;
227
+ await fs.promises.mkdir(absoluteProjectPath, { recursive: true });
228
+ if (project.readme) {
229
+ await fs.promises.writeFile(
230
+ path.join(absoluteProjectPath, "README.md"),
231
+ project.readme,
232
+ );
233
+ }
234
+ return absoluteProjectPath;
235
+ }
236
+
237
+ private async loadProjectIntoDisk(projectName: string, projectPath: string) {
238
+ const absoluteTargetPath = `${publisherPath}/${projectName}`;
239
+ // Handle absolute paths
240
+ if (projectPath.startsWith("/")) {
241
+ try {
242
+ logger.info(`Mounting local directory at "${projectPath}"`);
243
+ await this.mountLocalDirectory(
244
+ projectPath,
245
+ absoluteTargetPath,
246
+ projectName,
247
+ );
248
+ return absoluteTargetPath;
249
+ } catch (error) {
250
+ logger.error(`Failed to mount local directory "${projectPath}"`, {
251
+ error,
252
+ });
253
+ throw error;
254
+ }
255
+ }
256
+
257
+ // Handle GCS URIs
258
+ if (projectPath.startsWith("gs://")) {
259
+ // Download from GCS
260
+ try {
261
+ logger.info(
262
+ `Downloading GCS path "${projectPath}" to "${absoluteTargetPath}"`,
263
+ );
264
+ await this.downloadGcsDirectory(
265
+ projectPath,
266
+ projectName,
267
+ absoluteTargetPath,
268
+ );
269
+ } catch (error) {
270
+ logger.error(`Failed to download GCS path "${projectPath}"`, {
271
+ error,
272
+ });
273
+ throw error;
274
+ }
275
+ return absoluteTargetPath;
276
+ }
277
+
278
+ // Handle S3 URIs
279
+ if (projectPath.startsWith("s3://")) {
280
+ try {
281
+ logger.info(`Mounting S3 path "${projectPath}"`);
282
+ await this.downloadS3Directory(
283
+ projectPath,
284
+ projectName,
285
+ absoluteTargetPath,
286
+ );
287
+ return absoluteTargetPath;
288
+ } catch (error) {
289
+ logger.error(`Failed to mount S3 path "${projectPath}"`, { error });
290
+ throw error;
291
+ }
292
+ }
293
+
294
+ // Handle GitHub URIs
295
+ if (
296
+ projectPath.startsWith("https://github.com/") ||
297
+ projectPath.startsWith("git@")
298
+ ) {
299
+ try {
300
+ logger.info(`Mounting GitHub path "${projectPath}"`);
301
+ await this.downloadGitHubDirectory(projectPath, absoluteTargetPath);
302
+ return absoluteTargetPath;
303
+ } catch (error) {
304
+ logger.error(`Failed to mount GitHub path "${projectPath}"`, {
305
+ error,
306
+ });
307
+ throw error;
308
+ }
309
+ }
310
+
311
+ const errorMsg = `Invalid project path: "${projectPath}". Must be an absolute mounted path or a GCS/S3/GitHub URI.`;
312
+ logger.error(errorMsg, { projectName, projectPath });
313
+ throw new ProjectNotFoundError(errorMsg);
314
+ }
315
+
316
+ public async mountLocalDirectory(
317
+ projectPath: string,
318
+ absoluteTargetPath: string,
319
+ projectName: string,
320
+ ) {
321
+ const projectDirExists = (
322
+ await fs.promises.stat(projectPath)
323
+ ).isDirectory();
324
+ if (projectDirExists) {
325
+ await fs.promises.rm(absoluteTargetPath, {
326
+ recursive: true,
327
+ force: true,
328
+ });
329
+ await fs.promises.mkdir(absoluteTargetPath, { recursive: true });
330
+ await fs.promises.cp(projectPath, absoluteTargetPath, {
331
+ recursive: true,
332
+ });
333
+ } else {
334
+ throw new ProjectNotFoundError(
335
+ `Project ${projectName} not found in "${projectPath}"`,
336
+ );
337
+ }
338
+ }
339
+
340
+ async downloadGcsDirectory(
341
+ gcsPath: string,
342
+ projectName: string,
343
+ absoluteDirPath: string,
344
+ ) {
345
+ const trimmedPath = gcsPath.slice(5);
346
+ const [bucketName, ...prefixParts] = trimmedPath.split("/");
347
+ const prefix = prefixParts.join("/");
348
+ const [files] = await this.gcsClient.bucket(bucketName).getFiles({
349
+ prefix,
350
+ });
351
+ if (files.length === 0) {
352
+ throw new ProjectNotFoundError(
353
+ `Project ${projectName} not found in ${gcsPath}`,
354
+ );
355
+ }
356
+ await fs.promises.rm(absoluteDirPath, { recursive: true, force: true });
357
+ await fs.promises.mkdir(absoluteDirPath, { recursive: true });
358
+ await Promise.all(
359
+ files.map(async (file) => {
360
+ const relativeFilePath = file.name.replace(prefix, "");
361
+ const absoluteFilePath = path.join(
362
+ absoluteDirPath,
363
+ relativeFilePath,
364
+ );
365
+ if (file.name.endsWith("/")) {
366
+ return;
367
+ }
368
+ await fs.promises.mkdir(path.dirname(absoluteFilePath), {
369
+ recursive: true,
370
+ });
371
+ return fs.promises.writeFile(
372
+ absoluteFilePath,
373
+ await file.download(),
374
+ );
375
+ }),
376
+ );
377
+ }
378
+
379
+ async downloadS3Directory(
380
+ s3Path: string,
381
+ projectName: string,
382
+ absoluteDirPath: string,
383
+ ) {
384
+ const trimmedPath = s3Path.slice(5);
385
+ const [bucketName, ...prefixParts] = trimmedPath.split("/");
386
+ const prefix = prefixParts.join("/");
387
+ const objects = await this.s3Client.listObjectsV2({
388
+ Bucket: bucketName,
389
+ Prefix: prefix,
390
+ });
391
+ await fs.promises.rm(absoluteDirPath, { recursive: true, force: true });
392
+ await fs.promises.mkdir(absoluteDirPath, { recursive: true });
393
+
394
+ if (!objects.Contents || objects.Contents.length === 0) {
395
+ throw new ProjectNotFoundError(
396
+ `Project ${projectName} not found in ${s3Path}`,
397
+ );
398
+ }
399
+ await Promise.all(
400
+ objects.Contents?.map(async (object) => {
401
+ const key = object.Key;
402
+ if (!key) {
403
+ return;
404
+ }
405
+ const relativeFilePath = key.replace(prefix, "");
406
+ if (!relativeFilePath || relativeFilePath.endsWith("/")) {
407
+ return;
408
+ }
409
+ const absoluteFilePath = path.join(
410
+ absoluteDirPath,
411
+ relativeFilePath,
412
+ );
413
+ await fs.promises.mkdir(path.dirname(absoluteFilePath), {
414
+ recursive: true,
415
+ });
416
+ const command = new GetObjectCommand({
417
+ Bucket: bucketName,
418
+ Key: key,
419
+ });
420
+ const item = await this.s3Client.send(command);
421
+ if (!item.Body) {
422
+ return;
423
+ }
424
+ const file = fs.createWriteStream(absoluteFilePath);
425
+ item.Body.transformToWebStream().pipeTo(Writable.toWeb(file));
426
+ await new Promise<void>((resolve, reject) => {
427
+ file.on("error", reject);
428
+ file.on("finish", resolve);
429
+ });
430
+ }),
431
+ );
432
+ }
433
+
434
+ async downloadGitHubDirectory(githubUrl: string, absoluteDirPath: string) {
435
+ await fs.promises.rm(absoluteDirPath, { recursive: true, force: true });
436
+ await fs.promises.mkdir(absoluteDirPath, { recursive: true });
437
+
438
+ await new Promise<void>((resolve, reject) => {
439
+ simpleGit().clone(githubUrl, absoluteDirPath, {}, (err) => {
440
+ if (err) {
441
+ console.error(err);
442
+ logger.error(
443
+ `Failed to clone GitHub repository "${githubUrl}"`,
444
+ {
445
+ error: err,
446
+ },
447
+ );
448
+ reject(err);
449
+ }
450
+ resolve();
451
+ });
452
+ });
453
+ }
174
454
  }
package/src/utils.ts CHANGED
@@ -1,15 +1,7 @@
1
1
  import { URLReader } from "@malloydata/malloy";
2
2
  import * as fs from "fs";
3
- import path from "path";
4
3
  import { fileURLToPath } from "url";
5
4
 
6
- export const PACKAGE_MANIFEST_NAME = "publisher.json";
7
- export const CONNECTIONS_MANIFEST_NAME = "publisher.connections.json";
8
- export const MODEL_FILE_SUFFIX = ".malloy";
9
- export const NOTEBOOK_FILE_SUFFIX = ".malloynb";
10
- // TODO: Move this to server config.
11
- export const ROW_LIMIT = 1000;
12
-
13
5
  export const URL_READER: URLReader = {
14
6
  readURL: (url: URL) => {
15
7
  let path = url.toString();
@@ -20,13 +12,3 @@ export const URL_READER: URLReader = {
20
12
  },
21
13
  };
22
14
 
23
- export const isPublisherConfigFrozen = (serverRoot: string) => {
24
- const publisherConfigPath = path.join(serverRoot, "publisher.config.json");
25
- if (!fs.existsSync(publisherConfigPath)) {
26
- return false;
27
- }
28
- const publisherConfig = JSON.parse(
29
- fs.readFileSync(publisherConfigPath, "utf8"),
30
- );
31
- return Boolean(publisherConfig.frozenConfig);
32
- };
@@ -1,14 +1,14 @@
1
- import http from "http";
2
- import { AddressInfo } from "net";
3
- import { URL } from "url";
4
- import path from "path";
5
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
2
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
7
3
  import {
8
- Request,
9
4
  Notification,
5
+ Request,
10
6
  Result,
11
7
  } from "@modelcontextprotocol/sdk/types.js";
8
+ import http from "http";
9
+ import { AddressInfo } from "net";
10
+ import path from "path";
11
+ import { URL } from "url";
12
12
 
13
13
  // --- Real Server Import ---
14
14
  // Import the actual configured Express app instance
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index.umd-x-naS8R7.js","assets/index-C7whj6wK.js","assets/index-2xWCh-ya.css"])))=>i.map(i=>d[i]);
2
- import{j as a,r as c,_ as w}from"./index-C7whj6wK.js";const x=async e=>{if(typeof window>"u")throw new Error("MalloyRenderer can only be used in browser environment");const{MalloyRenderer:d}=await w(async()=>{const{MalloyRenderer:i}=await import("./index.umd-x-naS8R7.js").then(l=>l.i);return{MalloyRenderer:i}},__vite__mapDeps([0,1,2]));return new d({onClick:e}).createViz()};function m({result:e,height:d,isFillElement:i,onSizeChange:l,onDrill:y}){const u=c.useRef(null),[g,f]=c.useState(!1);return c.useLayoutEffect(()=>{if(!u.current||!e)return;let t=!0;const r=u.current;for(;r.firstChild;)r.removeChild(r.firstChild);return x(y).then(o=>{if(!t)return;const n=new MutationObserver(s=>{for(const h of s)if(h.type==="childList"&&h.addedNodes.length>0&&Array.from(h.addedNodes).some(p=>p.nodeType===Node.ELEMENT_NODE)){n.disconnect(),setTimeout(()=>{t&&f(!0)},50);break}});n.observe(r,{childList:!0,subtree:!0,characterData:!0});try{o.setResult(JSON.parse(e)),o.render(r)}catch(s){console.error("Error rendering visualization:",s),n.disconnect(),t&&f(!0)}}).catch(o=>{console.error("Failed to create renderer:",o),t&&f(!0)}),()=>{t=!1}},[e,y]),c.useEffect(()=>{if(!u.current||!g)return;const t=u.current,r=()=>{if(t){const s=t.offsetHeight;s>0?l&&l(s):i&&t.firstChild&&(t.firstChild.offsetHeight==0?i(!0):i(!1))}},o=setTimeout(r,100);let n=null;return n=new MutationObserver(r),n.observe(t,{childList:!0,subtree:!0,attributes:!0}),()=>{clearTimeout(o),n?.disconnect()}},[l,e,i,g]),a.jsx("div",{ref:u,style:{width:"100%",height:d?`${d}px`:"100%"}})}function E(e){return typeof window>"u"?a.jsx("div",{style:{width:"100%",height:e.height?`${e.height}px`:"100%",display:"flex",alignItems:"center",justifyContent:"center",color:"#666"},children:"Loading..."}):a.jsx(c.Suspense,{fallback:a.jsx("div",{style:{width:"100%",height:e.height?`${e.height}px`:"100%",display:"flex",alignItems:"center",justifyContent:"center",color:"#666"},children:"Loading visualization..."}),children:a.jsx(m,{...e})})}export{E as default};