@malloy-publisher/server 0.0.165 → 0.0.168

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 (41) hide show
  1. package/.eslintrc.json +9 -1
  2. package/dist/app/api-doc.yaml +143 -1
  3. package/dist/app/assets/HomePage-D2tUw_9U.js +1 -0
  4. package/dist/app/assets/{MainPage-DAyUfYba.js → MainPage-DBQW76L7.js} +2 -2
  5. package/dist/app/assets/{ModelPage-CrMryV1s.js → ModelPage-BnfOKuhQ.js} +1 -1
  6. package/dist/app/assets/PackagePage-zPhE-rDg.js +1 -0
  7. package/dist/app/assets/ProjectPage-BpSTvuW6.js +1 -0
  8. package/dist/app/assets/RouteError-Cp9-yCK5.js +1 -0
  9. package/dist/app/assets/{WorkbookPage-DZEVYGW3.js → WorkbookPage-FD_gmxeE.js} +1 -1
  10. package/dist/app/assets/{index-BvVmB5sv.js → index-D5QBYuLK.js} +150 -150
  11. package/dist/app/assets/{index-CsC07BYd.js → index-DNCvL_5f.js} +1 -1
  12. package/dist/app/assets/{index-DWhjtyBB.js → index-x9S1fsYn.js} +1 -1
  13. package/dist/app/assets/{index.umd-DvM-lTQa.js → index.umd-CTYdFEHH.js} +1 -1
  14. package/dist/app/index.html +1 -1
  15. package/dist/instrumentation.js +85955 -88560
  16. package/dist/server.js +197441 -106276
  17. package/package.json +2 -1
  18. package/src/controller/compile.controller.ts +35 -0
  19. package/src/controller/connection.controller.ts +22 -2
  20. package/src/controller/model.controller.ts +20 -9
  21. package/src/health.ts +8 -0
  22. package/src/instrumentation.ts +123 -34
  23. package/src/server.ts +49 -3
  24. package/src/service/connection.spec.ts +1331 -0
  25. package/src/service/connection.ts +407 -29
  26. package/src/service/db_utils.ts +104 -45
  27. package/src/service/gcs_s3_utils.ts +115 -40
  28. package/src/service/model.ts +5 -5
  29. package/src/service/project.ts +140 -4
  30. package/src/service/project_compile.spec.ts +197 -0
  31. package/src/service/project_store.ts +49 -21
  32. package/src/storage/StorageManager.ts +4 -3
  33. package/src/storage/duckdb/schema.ts +6 -5
  34. package/tests/harness/e2e.ts +4 -0
  35. package/tests/harness/mcp_test_setup.ts +172 -28
  36. package/tests/unit/duckdb/attached_databases.test.ts +61 -3
  37. package/tests/unit/ducklake/ducklake.test.ts +950 -0
  38. package/dist/app/assets/HomePage-QekMXs8r.js +0 -1
  39. package/dist/app/assets/PackagePage-DDaABD2A.js +0 -1
  40. package/dist/app/assets/ProjectPage-FAYUFGhL.js +0 -1
  41. package/dist/app/assets/RouteError-BKYctANX.js +0 -1
@@ -4,7 +4,7 @@ import { MySQLConnection } from "@malloydata/db-mysql";
4
4
  import { PostgresConnection } from "@malloydata/db-postgres";
5
5
  import { SnowflakeConnection } from "@malloydata/db-snowflake";
6
6
  import { TrinoConnection } from "@malloydata/db-trino";
7
- import { Connection } from "@malloydata/malloy";
7
+ import { Connection, TableSourceDef } from "@malloydata/malloy";
8
8
  import { BaseConnection } from "@malloydata/malloy/connection";
9
9
  import { AxiosError } from "axios";
10
10
  import fs from "fs/promises";
@@ -29,6 +29,7 @@ export type InternalConnection = ApiConnection & {
29
29
  trinoConnection?: components["schemas"]["TrinoConnection"];
30
30
  mysqlConnection?: components["schemas"]["MysqlConnection"];
31
31
  duckdbConnection?: components["schemas"]["DuckdbConnection"];
32
+ ducklakeConnection?: components["schemas"]["DucklakeConnection"];
32
33
  };
33
34
 
34
35
  function validateAndBuildTrinoConfig(
@@ -345,6 +346,40 @@ async function attachSnowflake(
345
346
  logger.info(`Successfully attached Snowflake database: ${attachedDb.name}`);
346
347
  }
347
348
 
349
+ function buildPgConnectionString(
350
+ pg: components["schemas"]["PostgresConnection"],
351
+ ): string {
352
+ if (pg.connectionString) {
353
+ return pg.connectionString;
354
+ }
355
+
356
+ const parts: string[] = [];
357
+ if (pg.host) parts.push(`host=${pg.host}`);
358
+ if (pg.port) parts.push(`port=${pg.port}`);
359
+ if (pg.databaseName) parts.push(`dbname=${pg.databaseName}`);
360
+ if (pg.userName) parts.push(`user=${pg.userName}`);
361
+ if (pg.password) parts.push(`password=${pg.password}`);
362
+
363
+ const pgSSLMode = process.env.PGSSLMODE;
364
+
365
+ if (pgSSLMode) {
366
+ const mapping: Record<string, string> = {
367
+ "no-verify": "disable",
368
+ disable: "disable",
369
+ allow: "allow",
370
+ prefer: "prefer",
371
+ require: "require",
372
+ "verify-ca": "verify-ca",
373
+ "verify-full": "verify-full",
374
+ };
375
+
376
+ const sslmode = mapping[pgSSLMode.toLowerCase()];
377
+ if (sslmode) parts.push(`sslmode=${sslmode}`);
378
+ }
379
+
380
+ return parts.join(" ");
381
+ }
382
+
348
383
  async function attachPostgres(
349
384
  connection: DuckDBConnection,
350
385
  attachedDb: AttachedDatabase,
@@ -358,26 +393,82 @@ async function attachPostgres(
358
393
  await installAndLoadExtension(connection, "postgres");
359
394
 
360
395
  const config = attachedDb.postgresConnection;
361
- let attachString: string;
362
-
363
- if (config.connectionString) {
364
- attachString = config.connectionString;
365
- } else {
366
- const parts: string[] = [];
367
- if (config.host) parts.push(`host=${config.host}`);
368
- if (config.port) parts.push(`port=${config.port}`);
369
- if (config.databaseName) parts.push(`dbname=${config.databaseName}`);
370
- if (config.userName) parts.push(`user=${config.userName}`);
371
- if (config.password) parts.push(`password=${config.password}`);
372
- if (process.env.PGSSLMODE === "no-verify") parts.push(`sslmode=disable`);
373
- attachString = parts.join(" ");
374
- }
396
+ const attachString: string = buildPgConnectionString(config);
375
397
 
376
- const attachCommand = `ATTACH '${attachString}' AS ${attachedDb.name} (TYPE postgres, READ_ONLY);`;
398
+ const attachCommand = `ATTACH '${escapeSQL(attachString)}' AS ${attachedDb.name} (TYPE postgres, READ_ONLY);`;
377
399
  await connection.runSQL(attachCommand);
378
400
  logger.info(`Successfully attached PostgreSQL database: ${attachedDb.name}`);
379
401
  }
380
402
 
403
+ async function attachDuckLake(
404
+ connection: DuckDBConnection,
405
+ dbName: string,
406
+ ducklakeConfig: components["schemas"]["DucklakeConnection"],
407
+ ): Promise<void> {
408
+ await installAndLoadExtension(connection, "ducklake");
409
+ await installAndLoadExtension(connection, "postgres");
410
+ await installAndLoadExtension(connection, "aws");
411
+ await installAndLoadExtension(connection, "httpfs");
412
+ if (!ducklakeConfig.catalog?.postgresConnection) {
413
+ throw new Error(
414
+ `PostgreSQL connection configuration is required for DuckLake catalog: ${dbName}`,
415
+ );
416
+ }
417
+
418
+ if (!ducklakeConfig.storage?.bucketUrl) {
419
+ throw new Error(`Storage bucketUrl is required for DuckLake: ${dbName}`);
420
+ }
421
+
422
+ // Set up cloud storage secret so DuckDB can access S3/GCS
423
+ const hasS3 = !!ducklakeConfig.storage.s3Connection;
424
+ const hasGCS = !!ducklakeConfig.storage.gcsConnection;
425
+
426
+ if (hasS3) {
427
+ await attachCloudStorage(connection, {
428
+ name: `${dbName}_storage`,
429
+ type: "s3",
430
+ s3Connection: ducklakeConfig.storage.s3Connection,
431
+ });
432
+ } else if (hasGCS) {
433
+ await attachCloudStorage(connection, {
434
+ name: `${dbName}_storage`,
435
+ type: "gcs",
436
+ gcsConnection: ducklakeConfig.storage.gcsConnection,
437
+ });
438
+ }
439
+
440
+ const pg = ducklakeConfig.catalog.postgresConnection;
441
+ const pgConnString: string = buildPgConnectionString(pg);
442
+ // Attach DuckLake with Postgres catalog and cloud storage data path in READ_ONLY mode
443
+ // The client manages metadata - we only read from the catalogs
444
+ logger.info(`pgConnString: ${pgConnString}`);
445
+ const escapedPgConnString = escapeSQL(pgConnString);
446
+ logger.info(`Final escaped connection string: ${escapedPgConnString}`);
447
+ const escapedBucketUrl = escapeSQL(ducklakeConfig.storage.bucketUrl);
448
+ logger.info(`escapedBucketUrl: ${escapedBucketUrl}`);
449
+ const attachCommand = `ATTACH OR REPLACE 'ducklake:postgres:${escapedPgConnString}' AS ${dbName} (DATA_PATH '${escapedBucketUrl}', OVERRIDE_DATA_PATH true, READ_ONLY true);`;
450
+ logger.info(`Attaching DuckLake database using command: ${attachCommand}`);
451
+ try {
452
+ await connection.runSQL(attachCommand);
453
+ logger.info(
454
+ `Successfully attached DuckLake database in READ_ONLY mode: ${dbName}`,
455
+ );
456
+ } catch (error) {
457
+ // Handle case where DuckLake database is already attached
458
+ if (
459
+ error instanceof Error &&
460
+ (error.message.includes("already exists") ||
461
+ error.message.includes("already attached"))
462
+ ) {
463
+ logger.info(
464
+ `DuckLake database ${dbName} is already attached, skipping`,
465
+ );
466
+ } else {
467
+ throw error;
468
+ }
469
+ }
470
+ }
471
+
381
472
  async function attachCloudStorage(
382
473
  connection: DuckDBConnection,
383
474
  attachedDb: AttachedDatabase,
@@ -488,11 +579,30 @@ async function attachCloudStorage(
488
579
  }
489
580
  }
490
581
 
582
+ if (await doesSecretExistInDuckDB(connection, secretName)) {
583
+ // Force refresh attachments using this storage
584
+ await connection.runSQL(`DETACH ${attachedDb.name};`).catch(() => {});
585
+ }
491
586
  await connection.runSQL(createSecretCommand);
587
+
492
588
  logger.info(`Created ${storageType} secret: ${secretName}`);
493
589
  logger.info(`${storageType} connection configured for: ${attachedDb.name}`);
494
590
  }
495
591
 
592
+ async function doesSecretExistInDuckDB(
593
+ connection: DuckDBConnection,
594
+ secretName: string,
595
+ ): Promise<boolean> {
596
+ const escapedSecretName = escapeSQL(secretName);
597
+ const result = await connection.runSQL(`
598
+ SELECT COUNT(*) AS count
599
+ FROM duckdb_secrets()
600
+ WHERE name = '${escapedSecretName}';
601
+ `);
602
+ const rows = result.rows;
603
+ return Number(rows?.[0]?.count ?? 0) > 0;
604
+ }
605
+
496
606
  // Main attachment function
497
607
  async function attachDatabasesToDuckDB(
498
608
  duckdbConnection: DuckDBConnection,
@@ -540,9 +650,95 @@ async function attachDatabasesToDuckDB(
540
650
  }
541
651
  }
542
652
 
653
+ class DuckLakeConnection extends DuckDBConnection {
654
+ private connectionName: string;
655
+
656
+ constructor(
657
+ connectionName: string,
658
+ databasePath: string,
659
+ workingDirectory: string,
660
+ ) {
661
+ super(connectionName, databasePath, workingDirectory);
662
+
663
+ // Validate that this is a DuckLake connection by checking the database path pattern
664
+ if (!databasePath.endsWith("_ducklake.duckdb")) {
665
+ throw new Error(
666
+ `DuckLakeConnection should only be used for DuckLake connections. ` +
667
+ `Expected database path ending with '_ducklake.duckdb', got: ${databasePath}`,
668
+ );
669
+ }
670
+
671
+ this.connectionName = connectionName;
672
+ }
673
+
674
+ async fetchTableSchema(
675
+ tableKey: string,
676
+ tablePath: string,
677
+ ): Promise<TableSourceDef> {
678
+ // DuckLake-specific logic: prefix table path with connection name if needed
679
+ const parts = tablePath.split(".");
680
+ if (
681
+ !tablePath.startsWith(this.connectionName) &&
682
+ (parts.length === 1 || parts.length === 2)
683
+ ) {
684
+ const prefixedPath = `${this.connectionName}.${tablePath}`;
685
+ logger.debug("Prefixing DuckLake table path", {
686
+ original: tablePath,
687
+ prefixed: prefixedPath,
688
+ connectionName: this.connectionName,
689
+ });
690
+ const result = await super.fetchTableSchema(tableKey, prefixedPath);
691
+ if (!result) {
692
+ throw new Error(
693
+ `Table ${prefixedPath} not found in connection ${this.connectionName}`,
694
+ );
695
+ }
696
+ return result;
697
+ }
698
+ // If already prefixed or has 3+ parts, use as-is;
699
+ // For attached databases, in the future
700
+ const result = await super.fetchTableSchema(tableKey, tablePath);
701
+ if (!result) {
702
+ throw new Error(
703
+ `Table ${tablePath} not found in connection ${this.connectionName}`,
704
+ );
705
+ }
706
+ return result;
707
+ }
708
+ }
709
+
710
+ export async function deleteDuckLakeConnectionFile(
711
+ connectionName: string,
712
+ projectPath: string,
713
+ ): Promise<void> {
714
+ const ducklakePath = path.join(
715
+ projectPath,
716
+ `${connectionName}_ducklake.duckdb`,
717
+ );
718
+ try {
719
+ await fs.access(ducklakePath);
720
+ await fs.rm(ducklakePath);
721
+ logger.info(
722
+ `Removed DuckLake connection file ${connectionName}_ducklake.duckdb from ${projectPath}`,
723
+ );
724
+ } catch (error) {
725
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
726
+ logger.debug(
727
+ `DuckLake connection file ${connectionName}_ducklake.duckdb does not exist, skipping deletion`,
728
+ );
729
+ } else {
730
+ logger.error(
731
+ `Failed to remove DuckLake connection file ${connectionName}_ducklake.duckdb from ${projectPath}`,
732
+ { error },
733
+ );
734
+ }
735
+ }
736
+ }
737
+
543
738
  export async function createProjectConnections(
544
739
  connections: ApiConnection[] = [],
545
740
  projectPath: string = "",
741
+ isUpdateConnectionRequest: boolean = false,
546
742
  ): Promise<{
547
743
  malloyConnections: Map<string, BaseConnection>;
548
744
  apiConnections: InternalConnection[];
@@ -826,6 +1022,40 @@ export async function createProjectConnections(
826
1022
  break;
827
1023
  }
828
1024
 
1025
+ case "ducklake": {
1026
+ if (!connection.ducklakeConnection) {
1027
+ throw new Error("DuckLake connection configuration is missing.");
1028
+ }
1029
+
1030
+ // Creating one Connection per DuckLake connection to avoid conflicts with other connections and better isolation.
1031
+ const ducklakeDuckdbConnection = new DuckLakeConnection(
1032
+ connection.name,
1033
+ path.join(projectPath, `${connection.name}_ducklake.duckdb`),
1034
+ projectPath,
1035
+ );
1036
+
1037
+ // Only attach DuckLake if it's not already attached or is it an update connection request
1038
+ if (
1039
+ isUpdateConnectionRequest ||
1040
+ !(await isDatabaseAttached(
1041
+ ducklakeDuckdbConnection,
1042
+ connection.name,
1043
+ ))
1044
+ ) {
1045
+ await attachDuckLake(
1046
+ ducklakeDuckdbConnection,
1047
+ connection.name,
1048
+ connection.ducklakeConnection,
1049
+ );
1050
+ }
1051
+
1052
+ connectionMap.set(connection.name, ducklakeDuckdbConnection);
1053
+ connection.attributes = getConnectionAttributes(
1054
+ ducklakeDuckdbConnection,
1055
+ );
1056
+ break;
1057
+ }
1058
+
829
1059
  default: {
830
1060
  throw new Error(`Unsupported connection type: ${connection.type}`);
831
1061
  }
@@ -858,9 +1088,107 @@ function getConnectionAttributes(
858
1088
  };
859
1089
  }
860
1090
 
1091
+ async function testDuckDBConnection(
1092
+ duckdbConnection: DuckDBConnection,
1093
+ connectionConfig: InternalConnection,
1094
+ ): Promise<void> {
1095
+ // Test base DuckDB connection with a simple query
1096
+ try {
1097
+ await duckdbConnection.runSQL("SELECT 1 AS test");
1098
+ logger.info(
1099
+ `DuckDB base connection test passed for: ${connectionConfig.name}`,
1100
+ );
1101
+ } catch (error) {
1102
+ throw new Error(
1103
+ `DuckDB base connection test failed: ${(error as Error).message}`,
1104
+ );
1105
+ }
1106
+
1107
+ // Test each attached database if configured
1108
+ const attachedDatabases =
1109
+ connectionConfig.duckdbConnection?.attachedDatabases;
1110
+ if (!attachedDatabases || attachedDatabases.length === 0) {
1111
+ return;
1112
+ }
1113
+
1114
+ const failedAttachments: string[] = [];
1115
+
1116
+ for (const attachedDb of attachedDatabases) {
1117
+ if (!attachedDb.name) {
1118
+ continue;
1119
+ }
1120
+
1121
+ try {
1122
+ // Test the attached database by querying its tables/schemas
1123
+ // Different database types require different test queries
1124
+ switch (attachedDb.type) {
1125
+ case "postgres": {
1126
+ // Test postgres attachment by listing schemas
1127
+ await duckdbConnection.runSQL(
1128
+ `SELECT schema_name FROM information_schema.schemata WHERE catalog_name = '${attachedDb.name}' LIMIT 1`,
1129
+ );
1130
+ logger.info(
1131
+ `Attached Postgres database test passed: ${attachedDb.name}`,
1132
+ );
1133
+ break;
1134
+ }
1135
+ case "bigquery": {
1136
+ // Test BigQuery attachment by listing datasets
1137
+ // BigQuery attached databases show as catalogs
1138
+ await duckdbConnection.runSQL(
1139
+ `SELECT database_name FROM duckdb_databases() WHERE database_name = '${attachedDb.name}'`,
1140
+ );
1141
+ logger.info(
1142
+ `Attached BigQuery database test passed: ${attachedDb.name}`,
1143
+ );
1144
+ break;
1145
+ }
1146
+ case "snowflake": {
1147
+ // Test Snowflake attachment by verifying database is attached
1148
+ await duckdbConnection.runSQL(
1149
+ `SELECT database_name FROM duckdb_databases() WHERE database_name = '${attachedDb.name}'`,
1150
+ );
1151
+ logger.info(
1152
+ `Attached Snowflake database test passed: ${attachedDb.name}`,
1153
+ );
1154
+ break;
1155
+ }
1156
+ case "gcs":
1157
+ case "s3": {
1158
+ // For cloud storage, verify the secret was created
1159
+ // Cloud storage doesn't attach as a database, it uses secrets for auth
1160
+ await duckdbConnection.runSQL(
1161
+ `SELECT name FROM duckdb_secrets() WHERE name LIKE '%${attachedDb.name}%' LIMIT 1`,
1162
+ );
1163
+ logger.info(
1164
+ `Cloud storage credentials test passed: ${attachedDb.name}`,
1165
+ );
1166
+ break;
1167
+ }
1168
+ default: {
1169
+ logger.warn(
1170
+ `Unknown attached database type: ${attachedDb.type}`,
1171
+ );
1172
+ }
1173
+ }
1174
+ } catch (error) {
1175
+ const errorMessage = `Attached database '${attachedDb.name}' (${attachedDb.type}) test failed: ${(error as Error).message}`;
1176
+ logger.error(errorMessage);
1177
+ failedAttachments.push(errorMessage);
1178
+ }
1179
+ }
1180
+
1181
+ if (failedAttachments.length > 0) {
1182
+ throw new Error(
1183
+ `DuckDB connection test failed for attached databases:\n${failedAttachments.join("\n")}`,
1184
+ );
1185
+ }
1186
+ }
1187
+
861
1188
  export async function testConnectionConfig(
862
1189
  connectionConfig: ApiConnection,
863
1190
  ): Promise<ApiConnectionStatus> {
1191
+ let malloyConnections: Map<string, BaseConnection> | null = null;
864
1192
  try {
865
1193
  // Validate that connection name is provided
866
1194
  if (!connectionConfig.name) {
@@ -868,11 +1196,10 @@ export async function testConnectionConfig(
868
1196
  }
869
1197
 
870
1198
  // Use createProjectConnections to create the connection, then test it
871
- // TODO: Test duckdb connections?
872
-
873
- const { malloyConnections } = await createProjectConnections(
1199
+ const result = await createProjectConnections(
874
1200
  [connectionConfig], // Pass the single connection config
875
1201
  );
1202
+ malloyConnections = result.malloyConnections;
876
1203
 
877
1204
  // Get the created connection
878
1205
  const connection = malloyConnections.get(connectionConfig.name);
@@ -882,16 +1209,42 @@ export async function testConnectionConfig(
882
1209
  );
883
1210
  }
884
1211
 
885
- // Test the connection - cast to union type of connection classes that have test method
886
- await (
887
- connection as
888
- | PostgresConnection
889
- | BigQueryConnection
890
- | SnowflakeConnection
891
- | TrinoConnection
892
- | MySQLConnection
893
- | DuckDBConnection
894
- ).test();
1212
+ // Handle DuckDB connections specially since they have attached databases
1213
+ if (connectionConfig.type === "duckdb") {
1214
+ await testDuckDBConnection(
1215
+ connection as DuckDBConnection,
1216
+ connectionConfig as InternalConnection,
1217
+ );
1218
+ } else if (connectionConfig.type === "ducklake") {
1219
+ // DuckLake uses DuckDB internally — verify the database is attached
1220
+ const duckConn = connection as DuckDBConnection;
1221
+ const attached = await isDatabaseAttached(
1222
+ duckConn,
1223
+ connectionConfig.name as string,
1224
+ );
1225
+ if (!attached) {
1226
+ throw new Error(
1227
+ `DuckLake connection test failed: Error attaching database '${connectionConfig.name}'`,
1228
+ );
1229
+ }
1230
+ await duckConn.runSQL(
1231
+ `SELECT schema_name FROM information_schema.schemata WHERE catalog_name = '${connectionConfig.name}' LIMIT 1`,
1232
+ );
1233
+
1234
+ logger.info(
1235
+ `DuckLake connection test passed: ${connectionConfig.name}`,
1236
+ );
1237
+ } else {
1238
+ // Test other connection types using their test() method
1239
+ await (
1240
+ connection as
1241
+ | PostgresConnection
1242
+ | BigQueryConnection
1243
+ | SnowflakeConnection
1244
+ | TrinoConnection
1245
+ | MySQLConnection
1246
+ ).test();
1247
+ }
895
1248
 
896
1249
  return {
897
1250
  status: "ok",
@@ -908,5 +1261,30 @@ export async function testConnectionConfig(
908
1261
  status: "failed",
909
1262
  errorMessage: (error as Error).message,
910
1263
  };
1264
+ } finally {
1265
+ // Cleanup: close all connections and remove ducklake files created during testing
1266
+ if (malloyConnections) {
1267
+ for (const [connName, conn] of malloyConnections) {
1268
+ try {
1269
+ // Close the connection
1270
+ if (
1271
+ conn &&
1272
+ typeof (conn as DuckDBConnection).close === "function"
1273
+ ) {
1274
+ await (conn as DuckDBConnection).close();
1275
+ }
1276
+ } catch (closeError) {
1277
+ logger.warn(
1278
+ `Error closing connection ${connName} during test cleanup`,
1279
+ { error: closeError },
1280
+ );
1281
+ } finally {
1282
+ // Remove ducklake files created during testing (only for ducklake connections)
1283
+ if (connectionConfig.type === "ducklake") {
1284
+ await deleteDuckLakeConnectionFile(connName, process.cwd());
1285
+ }
1286
+ }
1287
+ }
1288
+ }
911
1289
  }
912
1290
  }