@malloy-publisher/server 0.0.192 → 0.0.193
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/api-doc.yaml +522 -1
- package/dist/app/assets/{HomePage-H1OH-VW5.js → HomePage-Di9MU3lS.js} +1 -1
- package/dist/app/assets/{MainPage-GL06aMke.js → MainPage-yZQo2HSL.js} +1 -1
- package/dist/app/assets/{ModelPage-Crau5hgZ.js → ModelPage-Dx2mHWeT.js} +1 -1
- package/dist/app/assets/{PackagePage-CbubRhgE.js → PackagePage-Q386Py9t.js} +1 -1
- package/dist/app/assets/{ProjectPage-DUlJkYJ4.js → ProjectPage-WR7wPQB-.js} +1 -1
- package/dist/app/assets/{RouteError-DrNXNihc.js → RouteError-stRGU4aW.js} +1 -1
- package/dist/app/assets/{WorkbookPage-CBBv7n5U.js → WorkbookPage-D3iX0djH.js} +1 -1
- package/dist/app/assets/{core-Dzx75uJR.es-DwnFZnyO.js → core-QH4HZQVz.es-CqlQLZdl.js} +1 -1
- package/dist/app/assets/{index-d5rvmoZ7.js → index-CVHzPJwN.js} +119 -119
- package/dist/app/assets/{index-CzjyS9cx.js → index-DavAceYD.js} +50 -50
- package/dist/app/assets/{index-HHdhLUpv.js → index-Y3Y-VRna.js} +1 -1
- package/dist/app/assets/{index.umd-CetYIBQY.js → index.umd-Bp8OIhfV.js} +46 -46
- package/dist/app/index.html +1 -1
- package/dist/server.mjs +1389 -984
- package/package.json +10 -10
- package/src/controller/connection.controller.ts +102 -27
- package/src/dto/connection.dto.spec.ts +4 -0
- package/src/dto/connection.dto.ts +46 -2
- package/src/server.ts +201 -2
- package/src/service/connection.spec.ts +250 -4
- package/src/service/connection.ts +326 -473
- package/src/service/connection_config.ts +514 -0
- package/src/service/connection_service.spec.ts +50 -0
- package/src/service/connection_service.ts +125 -32
- package/src/service/materialization_service.spec.ts +18 -12
- package/src/service/materialization_service.ts +54 -7
- package/src/service/model.ts +24 -27
- package/src/service/package.spec.ts +125 -1
- package/src/service/package.ts +86 -44
- package/src/service/project.ts +172 -94
- package/src/service/project_store.spec.ts +72 -0
- package/src/service/project_store.ts +98 -81
- package/tests/unit/duckdb/attached_databases.test.ts +1 -19
|
@@ -4,6 +4,7 @@ import path from "path";
|
|
|
4
4
|
import sinon from "sinon";
|
|
5
5
|
import { DuckDBConnection } from "@malloydata/db-duckdb";
|
|
6
6
|
import { createProjectConnections, testConnectionConfig } from "./connection";
|
|
7
|
+
import { assembleProjectConnections } from "./connection_config";
|
|
7
8
|
import { components } from "../api";
|
|
8
9
|
|
|
9
10
|
type ApiConnection = components["schemas"]["Connection"];
|
|
@@ -1121,19 +1122,264 @@ describe("connection integration tests", () => {
|
|
|
1121
1122
|
).rejects.toThrow(/cannot be 'duckdb'/);
|
|
1122
1123
|
});
|
|
1123
1124
|
|
|
1124
|
-
it("should
|
|
1125
|
+
it("should allow DuckDB connections with no attachments", async () => {
|
|
1126
|
+
const { malloyConnections } = await createProjectConnections(
|
|
1127
|
+
[
|
|
1128
|
+
{
|
|
1129
|
+
name: "empty_duckdb",
|
|
1130
|
+
type: "duckdb",
|
|
1131
|
+
duckdbConnection: { attachedDatabases: [] },
|
|
1132
|
+
},
|
|
1133
|
+
],
|
|
1134
|
+
testProjectPath,
|
|
1135
|
+
);
|
|
1136
|
+
|
|
1137
|
+
const connection = malloyConnections.get("empty_duckdb");
|
|
1138
|
+
expect(connection).toBeDefined();
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
it("should reject unsupported DuckDB connector fields", async () => {
|
|
1125
1142
|
await expect(
|
|
1126
1143
|
createProjectConnections(
|
|
1127
1144
|
[
|
|
1128
1145
|
{
|
|
1129
|
-
name: "
|
|
1146
|
+
name: "duckdb_with_setup_sql",
|
|
1130
1147
|
type: "duckdb",
|
|
1131
|
-
duckdbConnection: {
|
|
1148
|
+
duckdbConnection: {
|
|
1149
|
+
attachedDatabases: [],
|
|
1150
|
+
setupSQL: "INSTALL httpfs",
|
|
1151
|
+
},
|
|
1152
|
+
} as unknown as ApiConnection,
|
|
1153
|
+
],
|
|
1154
|
+
testProjectPath,
|
|
1155
|
+
),
|
|
1156
|
+
).rejects.toThrow(/Unsupported DuckDB connection field/);
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
it("should reject project-authored DuckDB policy fields", async () => {
|
|
1160
|
+
await expect(
|
|
1161
|
+
createProjectConnections(
|
|
1162
|
+
[
|
|
1163
|
+
{
|
|
1164
|
+
name: "duckdb_with_policy",
|
|
1165
|
+
type: "duckdb",
|
|
1166
|
+
duckdbConnection: {
|
|
1167
|
+
attachedDatabases: [],
|
|
1168
|
+
securityPolicy: "sandboxed",
|
|
1169
|
+
},
|
|
1170
|
+
} as unknown as ApiConnection,
|
|
1171
|
+
],
|
|
1172
|
+
testProjectPath,
|
|
1173
|
+
),
|
|
1174
|
+
).rejects.toThrow(/Unsupported DuckDB connection field/);
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
it("should preserve Snowflake private-key auth options", async () => {
|
|
1178
|
+
const { malloyConnections, releaseConnections } =
|
|
1179
|
+
await createProjectConnections(
|
|
1180
|
+
[
|
|
1181
|
+
{
|
|
1182
|
+
name: "sf_private_key",
|
|
1183
|
+
type: "snowflake",
|
|
1184
|
+
snowflakeConnection: {
|
|
1185
|
+
account: "test-account",
|
|
1186
|
+
username: "test-user",
|
|
1187
|
+
privateKey:
|
|
1188
|
+
"-----BEGIN PRIVATE KEY-----MIIB-----END PRIVATE KEY-----",
|
|
1189
|
+
warehouse: "test-warehouse",
|
|
1190
|
+
},
|
|
1191
|
+
},
|
|
1192
|
+
],
|
|
1193
|
+
testProjectPath,
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1196
|
+
try {
|
|
1197
|
+
const connection = malloyConnections.get(
|
|
1198
|
+
"sf_private_key",
|
|
1199
|
+
) as unknown as { connOptions: Record<string, unknown> };
|
|
1200
|
+
expect(connection.connOptions.authenticator).toBe(
|
|
1201
|
+
"SNOWFLAKE_JWT",
|
|
1202
|
+
);
|
|
1203
|
+
expect(connection.connOptions.privateKey).toContain(
|
|
1204
|
+
"BEGIN PRIVATE KEY",
|
|
1205
|
+
);
|
|
1206
|
+
} finally {
|
|
1207
|
+
await releaseConnections();
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
it("should translate Trino Peaka credentials to core extraCredential", () => {
|
|
1212
|
+
const assembled = assembleProjectConnections(
|
|
1213
|
+
[
|
|
1214
|
+
{
|
|
1215
|
+
name: "trino_peaka",
|
|
1216
|
+
type: "trino",
|
|
1217
|
+
trinoConnection: {
|
|
1218
|
+
server: "https://example.com",
|
|
1219
|
+
port: 443,
|
|
1220
|
+
catalog: "catalog",
|
|
1221
|
+
schema: "schema",
|
|
1222
|
+
user: "user",
|
|
1223
|
+
peakaKey: "peaka-secret",
|
|
1224
|
+
},
|
|
1225
|
+
},
|
|
1226
|
+
],
|
|
1227
|
+
testProjectPath,
|
|
1228
|
+
);
|
|
1229
|
+
|
|
1230
|
+
expect(
|
|
1231
|
+
assembled.pojo.connections.trino_peaka.extraCredential,
|
|
1232
|
+
).toEqual({ peakaKey: "peaka-secret" });
|
|
1233
|
+
expect(
|
|
1234
|
+
assembled.pojo.connections.trino_peaka.extraConfig,
|
|
1235
|
+
).toBeUndefined();
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
it("should validate project-level BigQuery service account keys", () => {
|
|
1239
|
+
expect(() =>
|
|
1240
|
+
assembleProjectConnections(
|
|
1241
|
+
[
|
|
1242
|
+
{
|
|
1243
|
+
name: "bq_invalid",
|
|
1244
|
+
type: "bigquery",
|
|
1245
|
+
bigqueryConnection: {
|
|
1246
|
+
defaultProjectId: "test-project",
|
|
1247
|
+
serviceAccountKeyJson: '{"invalid":"key"}',
|
|
1248
|
+
},
|
|
1132
1249
|
},
|
|
1133
1250
|
],
|
|
1134
1251
|
testProjectPath,
|
|
1135
1252
|
),
|
|
1136
|
-
).
|
|
1253
|
+
).toThrow(/missing "type" field/);
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
it("should preserve PGSSLMODE for project-level Postgres", () => {
|
|
1257
|
+
const previousPgSslMode = process.env.PGSSLMODE;
|
|
1258
|
+
process.env.PGSSLMODE = "require";
|
|
1259
|
+
try {
|
|
1260
|
+
const assembled = assembleProjectConnections(
|
|
1261
|
+
[
|
|
1262
|
+
{
|
|
1263
|
+
name: "pg_ssl",
|
|
1264
|
+
type: "postgres",
|
|
1265
|
+
postgresConnection: {
|
|
1266
|
+
host: "localhost",
|
|
1267
|
+
port: 5432,
|
|
1268
|
+
userName: "user",
|
|
1269
|
+
password: "pass",
|
|
1270
|
+
databaseName: "db",
|
|
1271
|
+
},
|
|
1272
|
+
},
|
|
1273
|
+
],
|
|
1274
|
+
testProjectPath,
|
|
1275
|
+
);
|
|
1276
|
+
|
|
1277
|
+
expect(
|
|
1278
|
+
assembled.pojo.connections.pg_ssl.connectionString,
|
|
1279
|
+
).toContain("sslmode=require");
|
|
1280
|
+
} finally {
|
|
1281
|
+
if (previousPgSslMode === undefined) {
|
|
1282
|
+
delete process.env.PGSSLMODE;
|
|
1283
|
+
} else {
|
|
1284
|
+
process.env.PGSSLMODE = previousPgSslMode;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
it("should use project-root-relative file paths for project-level DuckDB", async () => {
|
|
1290
|
+
const insideCsvPath = path.join(testProjectPath, "inside.csv");
|
|
1291
|
+
await fs.writeFile(insideCsvPath, "id\n1\n");
|
|
1292
|
+
|
|
1293
|
+
const { malloyConnections } = await createProjectConnections(
|
|
1294
|
+
[
|
|
1295
|
+
{
|
|
1296
|
+
name: "project_scoped_duckdb",
|
|
1297
|
+
type: "duckdb",
|
|
1298
|
+
duckdbConnection: { attachedDatabases: [] },
|
|
1299
|
+
},
|
|
1300
|
+
],
|
|
1301
|
+
testProjectPath,
|
|
1302
|
+
);
|
|
1303
|
+
|
|
1304
|
+
const connection = malloyConnections.get(
|
|
1305
|
+
"project_scoped_duckdb",
|
|
1306
|
+
) as DuckDBConnection;
|
|
1307
|
+
createdConnections.push(connection);
|
|
1308
|
+
|
|
1309
|
+
const assembled = assembleProjectConnections(
|
|
1310
|
+
[
|
|
1311
|
+
{
|
|
1312
|
+
name: "project_scoped_duckdb",
|
|
1313
|
+
type: "duckdb",
|
|
1314
|
+
duckdbConnection: { attachedDatabases: [] },
|
|
1315
|
+
},
|
|
1316
|
+
],
|
|
1317
|
+
testProjectPath,
|
|
1318
|
+
);
|
|
1319
|
+
expect(
|
|
1320
|
+
assembled.pojo.connections.project_scoped_duckdb
|
|
1321
|
+
.workingDirectory,
|
|
1322
|
+
).toBeUndefined();
|
|
1323
|
+
expect(
|
|
1324
|
+
assembled.pojo.connections.project_scoped_duckdb.securityPolicy,
|
|
1325
|
+
).toBeUndefined();
|
|
1326
|
+
|
|
1327
|
+
await expect(
|
|
1328
|
+
connection.runSQL("SELECT * FROM read_csv_auto('inside.csv')"),
|
|
1329
|
+
).resolves.toBeDefined();
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
it("should keep external access available for federated DuckDB entries", () => {
|
|
1333
|
+
const assembled = assembleProjectConnections(
|
|
1334
|
+
[
|
|
1335
|
+
{
|
|
1336
|
+
name: "federated_duckdb",
|
|
1337
|
+
type: "duckdb",
|
|
1338
|
+
duckdbConnection: {
|
|
1339
|
+
attachedDatabases: [
|
|
1340
|
+
{
|
|
1341
|
+
name: "pg",
|
|
1342
|
+
type: "postgres",
|
|
1343
|
+
postgresConnection: {
|
|
1344
|
+
host: "localhost",
|
|
1345
|
+
port: 5432,
|
|
1346
|
+
userName: "user",
|
|
1347
|
+
password: "pass",
|
|
1348
|
+
databaseName: "db",
|
|
1349
|
+
},
|
|
1350
|
+
},
|
|
1351
|
+
],
|
|
1352
|
+
},
|
|
1353
|
+
},
|
|
1354
|
+
],
|
|
1355
|
+
testProjectPath,
|
|
1356
|
+
);
|
|
1357
|
+
|
|
1358
|
+
const entry = assembled.pojo.connections.federated_duckdb;
|
|
1359
|
+
expect(entry.securityPolicy).toBeUndefined();
|
|
1360
|
+
expect(entry.enableExternalAccess).toBeUndefined();
|
|
1361
|
+
expect(entry.allowedDirectories).toBeUndefined();
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
it("should keep external access available for MotherDuck entries", () => {
|
|
1365
|
+
const assembled = assembleProjectConnections(
|
|
1366
|
+
[
|
|
1367
|
+
{
|
|
1368
|
+
name: "md",
|
|
1369
|
+
type: "motherduck",
|
|
1370
|
+
motherduckConnection: {
|
|
1371
|
+
accessToken: "token",
|
|
1372
|
+
database: "db",
|
|
1373
|
+
},
|
|
1374
|
+
},
|
|
1375
|
+
],
|
|
1376
|
+
testProjectPath,
|
|
1377
|
+
);
|
|
1378
|
+
|
|
1379
|
+
const entry = assembled.pojo.connections.md;
|
|
1380
|
+
expect(entry.securityPolicy).toBeUndefined();
|
|
1381
|
+
expect(entry.enableExternalAccess).toBeUndefined();
|
|
1382
|
+
expect(entry.allowedDirectories).toBeUndefined();
|
|
1137
1383
|
});
|
|
1138
1384
|
|
|
1139
1385
|
it("should handle already attached database gracefully", async () => {
|