@malloy-publisher/server 0.10.4 → 0.10.6

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.
@@ -234,7 +234,10 @@ paths:
234
234
  the bytes are rewritten as the catalog's own Parquet, never registered
235
235
  in place and never left as rows in the catalog database, so an
236
236
  encrypted catalog stays encrypted. One DuckLake transaction:
237
- a reader sees the old table or the new one. `201` when the table is new,
237
+ a reader sees the old table or the new one. A file the reader refuses
238
+ (not the format it claims, a value it cannot convert) is `400
239
+ INVALID_REQUEST` carrying the reader's own message, with the object
240
+ named by its file name. `201` when the table is new,
238
241
  `200` when it replaced one; with `If-None-Match: *` an existing table is
239
242
  refused with `412 TABLE_EXISTS`. Synchronous: it answers when the table
240
243
  is written.
@@ -6685,8 +6685,11 @@ components:
6685
6685
  `sqlSource` answer 400 for anything else (another statement type, or
6686
6686
  more than one statement), and `sqlTemporaryTable` answers 400 always.
6687
6687
  This is a rule on statements, not on what a SELECT reads: that is
6688
- bounded by the session's storage secret and catalog role. Absent or
6689
- false is the pre-existing behavior.
6688
+ bounded by the catalog role and by the session's storage secret,
6689
+ which on a managed connection attached read-only is scoped to the
6690
+ `main` schema's recorded path, so a SELECT reads only that schema's
6691
+ files; another schema's files, and any other object in the bucket,
6692
+ are unsigned (403). Absent or false is the pre-existing behavior.
6690
6693
  fingerprint:
6691
6694
  type: string
6692
6695
  description: >
@@ -7157,7 +7160,11 @@ components:
7157
7160
  every attach uses the data path the catalog recorded at its
7158
7161
  creation, and no attach ever creates a catalog: the shape for a
7159
7162
  catalog created elsewhere, through the admin API
7160
- (admin-api-doc.yaml) or by another tool.
7163
+ (admin-api-doc.yaml) or by another tool. Either way the storage
7164
+ secret is scoped to the data path, the given one or the
7165
+ recorded one: a read of any other object through this
7166
+ connection is unsigned, and on S3 the host's AWS_* environment
7167
+ never signs it either. A catalog with neither is not attached.
7161
7168
  s3Connection:
7162
7169
  $ref: "#/components/schemas/S3Connection"
7163
7170
  description: AWS S3 connection configuration for data storage
@@ -164831,7 +164831,7 @@ var init_telemetry = __esm(() => {
164831
164831
  });
164832
164832
 
164833
164833
  // package.json
164834
- var version = "0.10.4";
164834
+ var version = "0.10.6";
164835
164835
  var init_package = () => {};
164836
164836
 
164837
164837
  // src/version.ts
package/dist/server.mjs CHANGED
@@ -164832,7 +164832,7 @@ var init_telemetry = __esm(() => {
164832
164832
  });
164833
164833
 
164834
164834
  // package.json
164835
- var version = "0.10.4";
164835
+ var version = "0.10.6";
164836
164836
  var init_package = () => {};
164837
164837
 
164838
164838
  // src/version.ts
@@ -268347,23 +268347,12 @@ var require_bcrypt_pbkdf = __commonJS((exports, module) => {
268347
268347
  };
268348
268348
  });
268349
268349
 
268350
- // ../../node_modules/cpu-features/build/Release/cpufeatures.node
268351
- var require_cpufeatures = __commonJS((exports, module) => {
268352
- module.exports = __require("./cpufeatures-1yrn0vtw.node");
268353
- });
268354
-
268355
- // ../../node_modules/cpu-features/lib/index.js
268356
- var require_lib5 = __commonJS((exports, module) => {
268357
- var binding = require_cpufeatures();
268358
- module.exports = binding.getCPUInfo;
268359
- });
268360
-
268361
268350
  // ../../node_modules/ssh2/lib/protocol/constants.js
268362
268351
  var require_constants12 = __commonJS((exports, module) => {
268363
268352
  var crypto3 = __require("crypto");
268364
268353
  var cpuInfo;
268365
268354
  try {
268366
- cpuInfo = require_lib5()();
268355
+ cpuInfo = (()=>{throw new Error("Cannot require module "+"cpu-features");})()();
268367
268356
  } catch {}
268368
268357
  var { bindingAvailable, CIPHER_INFO, MAC_INFO } = require_crypto();
268369
268358
  var eddsaSupported = (() => {
@@ -268960,6 +268949,11 @@ var require_utils93 = __commonJS((exports, module) => {
268960
268949
  };
268961
268950
  });
268962
268951
 
268952
+ // ../../node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node
268953
+ var require_sshcrypto = __commonJS((exports, module) => {
268954
+ module.exports = __require("./sshcrypto-a4xyg5e8.node");
268955
+ });
268956
+
268963
268957
  // ../../node_modules/ssh2/lib/protocol/crypto/poly1305.js
268964
268958
  var require_poly1305 = __commonJS((exports, module) => {
268965
268959
  var __dirname = "/home/runner/work/publisher/publisher/node_modules/ssh2/lib/protocol/crypto", __filename = "/home/runner/work/publisher/publisher/node_modules/ssh2/lib/protocol/crypto/poly1305.js";
@@ -269446,7 +269440,7 @@ var require_crypto = __commonJS((exports, module) => {
269446
269440
  var ChaChaPolyDecipher;
269447
269441
  var GenericDecipher;
269448
269442
  try {
269449
- binding = (()=>{throw new Error("Cannot require module "+"./crypto/build/Release/sshcrypto.node");})();
269443
+ binding = require_sshcrypto();
269450
269444
  ({
269451
269445
  AESGCMCipher,
269452
269446
  ChaChaPolyCipher,
@@ -283001,7 +282995,7 @@ var require_keygen = __commonJS((exports, module) => {
283001
282995
  });
283002
282996
 
283003
282997
  // ../../node_modules/ssh2/lib/index.js
283004
- var require_lib6 = __commonJS((exports, module) => {
282998
+ var require_lib5 = __commonJS((exports, module) => {
283005
282999
  var {
283006
283000
  AgentProtocol,
283007
283001
  BaseAgent,
@@ -283184,7 +283178,7 @@ function openSshProxy(ssh, target) {
283184
283178
  var import_ssh2, ALLOW_UNVERIFIED_SSH_HOST_KEY_ENV = "PUBLISHER_ALLOW_UNVERIFIED_SSH_HOST_KEY", TRUTHY_ENV_VALUES, SSH_CONNECT_TIMEOUT_MS = 15000, SSH_KEEPALIVE_INTERVAL_MS = 15000;
283185
283179
  var init_proxy = __esm(() => {
283186
283180
  init_logger();
283187
- import_ssh2 = __toESM(require_lib6(), 1);
283181
+ import_ssh2 = __toESM(require_lib5(), 1);
283188
283182
  TRUTHY_ENV_VALUES = new Set(["1", "true", "yes", "on"]);
283189
283183
  });
283190
283184
 
@@ -283780,7 +283774,7 @@ function validateMotherDuckShape(connection) {
283780
283774
  throw new Error("MotherDuck access token is required.");
283781
283775
  }
283782
283776
  }
283783
- function validateDuckLakeShape(connection) {
283777
+ function validateDuckLakeShape(connection, origin2) {
283784
283778
  if (!connection.ducklakeConnection) {
283785
283779
  throw new Error("DuckLake connection configuration is missing.");
283786
283780
  }
@@ -283789,7 +283783,7 @@ function validateDuckLakeShape(connection) {
283789
283783
  }
283790
283784
  const storage = connection.ducklakeConnection.storage;
283791
283785
  if (storage?.s3Connection) {
283792
- validateS3ProviderShape(storage.s3Connection, connection.name);
283786
+ validateS3ProviderShape(storage.s3Connection, connection.name, origin2);
283793
283787
  }
283794
283788
  if (connection.ducklakeConnection.catalog.metadataSchema !== undefined) {
283795
283789
  const schema = connection.ducklakeConnection.catalog.metadataSchema;
@@ -283866,7 +283860,7 @@ function validatePublisherShape(connection) {
283866
283860
  throw new Error(`Invalid publisher connection '${connection.name}': connectionUri must use http or https (got '${parsedUri.protocol}'). ` + `Fix: set connectionUri to an absolute https URL like "https://…/connections/${connection.name}".`);
283867
283861
  }
283868
283862
  }
283869
- function validateTypeShape(connection) {
283863
+ function validateTypeShape(connection, origin2) {
283870
283864
  switch (connection.type) {
283871
283865
  case "sqlserver":
283872
283866
  return validateSqlServerShape(connection);
@@ -283877,7 +283871,7 @@ function validateTypeShape(connection) {
283877
283871
  case "motherduck":
283878
283872
  return validateMotherDuckShape(connection);
283879
283873
  case "ducklake":
283880
- return validateDuckLakeShape(connection);
283874
+ return validateDuckLakeShape(connection, origin2);
283881
283875
  case "trino":
283882
283876
  return validateTrinoShape(connection);
283883
283877
  case "databricks":
@@ -283888,7 +283882,7 @@ function validateTypeShape(connection) {
283888
283882
  return validatePublisherShape(connection);
283889
283883
  }
283890
283884
  }
283891
- function validateConnectionShape(connection) {
283885
+ function validateConnectionShape(connection, origin2 = "connection") {
283892
283886
  if (connection.proxy) {
283893
283887
  validateProxyShape(connection, connection.proxy);
283894
283888
  }
@@ -283901,7 +283895,7 @@ function validateConnectionShape(connection) {
283901
283895
  if (connection.postgresConnection) {
283902
283896
  postgresStatementTimeoutMs(connection.name ?? "", connection.postgresConnection);
283903
283897
  }
283904
- validateTypeShape(connection);
283898
+ validateTypeShape(connection, origin2);
283905
283899
  }
283906
283900
  function storageDestinationRoot(environmentPath) {
283907
283901
  return path4.join(environmentPath, STORAGE_DESTINATIONS_DIR);
@@ -283950,7 +283944,7 @@ function validateStorageDestinations(destinations = []) {
283950
283944
  continue;
283951
283945
  }
283952
283946
  try {
283953
- validateConnectionShape(destination);
283947
+ validateConnectionShape(destination, "storage-destination");
283954
283948
  const storage = destination.ducklakeConnection?.storage;
283955
283949
  if (storage?.s3Connection) {
283956
283950
  resolveCloudStorageCredentials({
@@ -284168,7 +284162,7 @@ function buildCoreEntry(connection, environmentPath) {
284168
284162
  throw new Error(`Unsupported connection type: ${connection.type}`);
284169
284163
  }
284170
284164
  }
284171
- function assembleEnvironmentConnections(connections = [], environmentPath = "") {
284165
+ function assembleEnvironmentConnections(connections = [], environmentPath = "", origin2 = "connection") {
284172
284166
  const pojo = { connections: {} };
284173
284167
  const metadata = new Map;
284174
284168
  const apiConnections = [];
@@ -284185,7 +284179,7 @@ function assembleEnvironmentConnections(connections = [], environmentPath = "")
284185
284179
  }
284186
284180
  processedConnections.add(connection.name);
284187
284181
  validateDuckdbApiSurface(connection);
284188
- validateConnectionShape(connection);
284182
+ validateConnectionShape(connection, origin2);
284189
284183
  warnOnConnectionQueryMetadata(connection);
284190
284184
  const apiConnection = cloneApiConnection(connection);
284191
284185
  apiConnection.attributes = getStaticConnectionAttributes(connection.type);
@@ -284256,7 +284250,7 @@ var init_connection_config = __esm(() => {
284256
284250
  });
284257
284251
 
284258
284252
  // ../../node_modules/webidl-conversions/lib/index.js
284259
- var require_lib7 = __commonJS((exports, module) => {
284253
+ var require_lib6 = __commonJS((exports, module) => {
284260
284254
  var conversions = {};
284261
284255
  module.exports = conversions;
284262
284256
  function sign(x2) {
@@ -285813,7 +285807,7 @@ var require_URL_impl = __commonJS((exports) => {
285813
285807
 
285814
285808
  // ../../node_modules/whatwg-url/lib/URL.js
285815
285809
  var require_URL = __commonJS((exports, module) => {
285816
- var conversions = require_lib7();
285810
+ var conversions = require_lib6();
285817
285811
  var utils = require_utils95();
285818
285812
  var Impl = require_URL_impl();
285819
285813
  var impl = utils.implSymbol;
@@ -286005,7 +285999,7 @@ var require_public_api2 = __commonJS((exports) => {
286005
285999
  });
286006
286000
 
286007
286001
  // ../../node_modules/node-fetch/lib/index.js
286008
- var require_lib8 = __commonJS((exports, module) => {
286002
+ var require_lib7 = __commonJS((exports, module) => {
286009
286003
  Object.defineProperty(exports, "__esModule", { value: true });
286010
286004
  function _interopDefault(ex) {
286011
286005
  return ex && typeof ex === "object" && "default" in ex ? ex["default"] : ex;
@@ -288478,7 +288472,7 @@ var require_gaxios2 = __commonJS((exports) => {
288478
288472
  exports.Gaxios = undefined;
288479
288473
  var extend_1 = __importDefault(require_extend());
288480
288474
  var https_1 = __require("https");
288481
- var node_fetch_1 = __importDefault(require_lib8());
288475
+ var node_fetch_1 = __importDefault(require_lib7());
288482
288476
  var querystring_1 = __importDefault(__require("querystring"));
288483
288477
  var is_stream_1 = __importDefault(require_is_stream());
288484
288478
  var url_1 = __require("url");
@@ -295166,17 +295160,19 @@ async function attachDuckLakeWithMode(connection, dbName, ducklakeConfig, option
295166
295160
  const hasS3 = !!ducklakeConfig.storage?.s3Connection;
295167
295161
  const hasGCS = !!ducklakeConfig.storage?.gcsConnection;
295168
295162
  if (hasS3) {
295169
- await attachCloudStorage(connection, {
295170
- name: `${dbName}_storage`,
295171
- type: "s3",
295172
- s3Connection: ducklakeConfig.storage.s3Connection
295173
- });
295174
- } else if (hasGCS) {
295175
- await attachCloudStorage(connection, {
295176
- name: `${dbName}_storage`,
295177
- type: "gcs",
295178
- gcsConnection: ducklakeConfig.storage.gcsConnection
295179
- });
295163
+ await connection.runSQL("SET GLOBAL enable_global_s3_configuration=false");
295164
+ }
295165
+ const storage = hasS3 ? {
295166
+ name: `${dbName}_storage`,
295167
+ type: "s3",
295168
+ s3Connection: ducklakeConfig.storage.s3Connection
295169
+ } : hasGCS ? {
295170
+ name: `${dbName}_storage`,
295171
+ type: "gcs",
295172
+ gcsConnection: ducklakeConfig.storage.gcsConnection
295173
+ } : undefined;
295174
+ if (storage) {
295175
+ resolveCloudStorageCredentials(storage, options.origin ?? "connection");
295180
295176
  }
295181
295177
  const pg = ducklakeConfig.catalog.postgresConnection;
295182
295178
  const pgConnString = buildPgConnectionString(pg, {
@@ -295189,8 +295185,8 @@ async function attachDuckLakeWithMode(connection, dbName, ducklakeConfig, option
295189
295185
  logger.debug(`Final escaped connection string: ${redactPgSecrets(escapedPgConnString)}`);
295190
295186
  const dataPath = ducklakeConfig.storage?.bucketUrl;
295191
295187
  const metadataSchema = ducklakeConfig.catalog.metadataSchema;
295192
- const presence = await preflightDuckLakeCatalogFormat(connection, dbName, pgConnString, metadataSchema);
295193
- if (presence === "absent") {
295188
+ const preflight = await preflightDuckLakeCatalogFormat(connection, dbName, pgConnString, metadataSchema);
295189
+ if (preflight === "absent") {
295194
295190
  if (options.skipIfAbsent) {
295195
295191
  return false;
295196
295192
  }
@@ -295215,14 +295211,44 @@ async function attachDuckLakeWithMode(connection, dbName, ducklakeConfig, option
295215
295211
  throw error;
295216
295212
  }
295217
295213
  }
295214
+ if (storage) {
295215
+ try {
295216
+ const scope = await attachedDuckLakeScope(connection, dbName, {
295217
+ configuredDataPath: dataPath,
295218
+ metadataSchema,
295219
+ readOnly: options.readOnly,
295220
+ managed: options.managed === true && options.origin !== "storage-destination"
295221
+ });
295222
+ if (!scope) {
295223
+ throw new Error(`No storage secret could be scoped for DuckLake catalog '${dbName}': ` + `the config states no data path, and the catalog records no data ` + `path (or, for a managed view, no path for its main schema) to ` + `scope it to. The catalog was not attached.`);
295224
+ }
295225
+ await attachCloudStorage(connection, storage, options.origin, scope);
295226
+ } catch (error) {
295227
+ await connection.runSQL(`DETACH ${quoteIdentifier(dbName, "duckdb")};`).catch(() => {});
295228
+ throw error;
295229
+ }
295230
+ }
295218
295231
  return true;
295219
295232
  }
295220
- async function recordedCatalogSettings(connection, alias, metadataSchema) {
295221
- const metadataCatalog = [
295233
+ async function attachedDuckLakeScope(connection, dbName, input) {
295234
+ const recordedDataPath = input.configuredDataPath ? undefined : (await recordedCatalogSettings(connection, dbName, input.metadataSchema)).data_path;
295235
+ const mainSchemaPath = input.managed && input.readOnly ? await recordedSchemaPath(connection, dbName, input.metadataSchema, "main") : undefined;
295236
+ return storageSecretScope({
295237
+ configuredDataPath: input.configuredDataPath,
295238
+ recordedDataPath,
295239
+ mainSchemaPath,
295240
+ readOnly: input.readOnly,
295241
+ managed: input.managed
295242
+ });
295243
+ }
295244
+ function metadataCatalogRef(alias, metadataSchema) {
295245
+ return [
295222
295246
  `__ducklake_metadata_${alias}`,
295223
295247
  ...metadataSchema ? [metadataSchema] : []
295224
295248
  ].map((part) => quoteIdentifier(part, "duckdb")).join(".");
295225
- const rows = runSQLRows(await connection.runSQL(`SELECT key, value FROM ${metadataCatalog}.ducklake_metadata WHERE scope IS NULL`));
295249
+ }
295250
+ async function recordedCatalogSettings(connection, alias, metadataSchema) {
295251
+ const rows = runSQLRows(await connection.runSQL(`SELECT key, value FROM ${metadataCatalogRef(alias, metadataSchema)}.ducklake_metadata WHERE scope IS NULL`));
295226
295252
  const settings = {};
295227
295253
  for (const row of rows) {
295228
295254
  if (typeof row.key === "string" && row.value != null) {
@@ -295231,6 +295257,43 @@ async function recordedCatalogSettings(connection, alias, metadataSchema) {
295231
295257
  }
295232
295258
  return settings;
295233
295259
  }
295260
+ async function recordedSchemaPath(connection, alias, metadataSchema, schemaName) {
295261
+ const row = runSQLRows(await connection.runSQL(`SELECT path, path_is_relative FROM ${metadataCatalogRef(alias, metadataSchema)}.ducklake_schema ` + `WHERE schema_name = '${escapeSQL(schemaName)}' AND end_snapshot IS NULL LIMIT 1`))[0];
295262
+ if (!row || typeof row.path !== "string")
295263
+ return;
295264
+ return { path: row.path, relative: row.path_is_relative !== false };
295265
+ }
295266
+ function stripTrailingSlashes(url3) {
295267
+ let end = url3.length;
295268
+ while (end > 0 && url3[end - 1] === "/")
295269
+ end--;
295270
+ return url3.slice(0, end);
295271
+ }
295272
+ function oneTrailingSlash(url3) {
295273
+ return `${stripTrailingSlashes(url3)}/`;
295274
+ }
295275
+ function isBucketlessUrl(url3) {
295276
+ const at = url3.indexOf("://");
295277
+ return at >= 0 && stripTrailingSlashes(url3.slice(at + 3)) === "";
295278
+ }
295279
+ function storageSecretScope(input) {
295280
+ const root = input.configuredDataPath || input.recordedDataPath;
295281
+ if (!root || isBucketlessUrl(root))
295282
+ return;
295283
+ if (input.managed && input.readOnly) {
295284
+ const main = input.mainSchemaPath;
295285
+ if (!main)
295286
+ return;
295287
+ if (!main.relative) {
295288
+ return isBucketlessUrl(main.path) ? undefined : oneTrailingSlash(main.path);
295289
+ }
295290
+ let start = 0;
295291
+ while (start < main.path.length && main.path[start] === "/")
295292
+ start++;
295293
+ return oneTrailingSlash(`${oneTrailingSlash(root)}${main.path.slice(start)}`);
295294
+ }
295295
+ return oneTrailingSlash(root);
295296
+ }
295234
295297
  function dataPathClauses(dataPath, readOnly) {
295235
295298
  if (dataPath) {
295236
295299
  return `DATA_PATH '${escapeSQL(dataPath)}', OVERRIDE_DATA_PATH true`;
@@ -295250,13 +295313,16 @@ function firstAttachClauses(options) {
295250
295313
  async function attachDuckLakeForCreate(connection, dbName, ducklakeConfig, settings) {
295251
295314
  await attachDuckLakeWithMode(connection, dbName, ducklakeConfig, {
295252
295315
  readOnly: false,
295316
+ origin: "storage-destination",
295253
295317
  encrypted: settings.encrypted,
295254
295318
  dataInliningRowLimit: settings.dataInliningRowLimit
295255
295319
  });
295256
295320
  }
295257
- async function attachDuckLake(connection, dbName, ducklakeConfig) {
295321
+ async function attachDuckLake(connection, dbName, ducklakeConfig, options = {}) {
295258
295322
  await attachDuckLakeWithMode(connection, dbName, ducklakeConfig, {
295259
- readOnly: ducklakeConfig.readOnly !== false
295323
+ readOnly: ducklakeConfig.readOnly !== false,
295324
+ origin: options.origin,
295325
+ managed: options.managed
295260
295326
  });
295261
295327
  }
295262
295328
  function managedSearchPathSql(dbName) {
@@ -295267,17 +295333,21 @@ async function setManagedSearchPath(connection, dbName) {
295267
295333
  await connection.runSQL(managedSearchPathSql(dbName));
295268
295334
  }
295269
295335
  async function attachDuckLakeReadOnly(connection, dbName, ducklakeConfig) {
295270
- await attachDuckLake(connection, dbName, ducklakeConfig);
295336
+ await attachDuckLake(connection, dbName, ducklakeConfig, {
295337
+ origin: "storage-destination"
295338
+ });
295271
295339
  }
295272
295340
  async function attachDuckLakeReadOnlyIfPresent(connection, dbName, ducklakeConfig) {
295273
295341
  return attachDuckLakeWithMode(connection, dbName, ducklakeConfig, {
295274
295342
  readOnly: true,
295275
- skipIfAbsent: true
295343
+ skipIfAbsent: true,
295344
+ origin: "storage-destination"
295276
295345
  });
295277
295346
  }
295278
295347
  async function attachDuckLakeReadWrite(connection, dbName, ducklakeConfig) {
295279
295348
  await attachDuckLakeWithMode(connection, dbName, ducklakeConfig, {
295280
- readOnly: false
295349
+ readOnly: false,
295350
+ origin: "storage-destination"
295281
295351
  });
295282
295352
  }
295283
295353
  async function federateSourceForPassthrough(connection, sourceType, config, deps = {}) {
@@ -295481,13 +295551,15 @@ function buildCloudStorageSecretSQL(secretName, credentials) {
295481
295551
  const isGCS = credentials.type === "gcs";
295482
295552
  const escapedKeyId = escapeSQL(credentials.accessKeyId);
295483
295553
  const escapedSecret = escapeSQL(credentials.secretAccessKey);
295554
+ const scopeClause = credentials.scope ? `SCOPE '${escapeSQL(credentials.scope)}'` : "";
295484
295555
  let createSecretCommand;
295485
295556
  if (isGCS) {
295486
295557
  createSecretCommand = `
295487
295558
  CREATE OR REPLACE SECRET ${secretName} (
295488
295559
  TYPE gcs,
295489
295560
  KEY_ID '${escapedKeyId}',
295490
- SECRET '${escapedSecret}'
295561
+ SECRET '${escapedSecret}'${scopeClause ? `,
295562
+ ${scopeClause}` : ""}
295491
295563
  );
295492
295564
  `;
295493
295565
  } else {
@@ -295523,6 +295595,9 @@ function buildCloudStorageSecretSQL(secretName, credentials) {
295523
295595
  if (credentials.provider !== "credential_chain" && credentials.sessionToken) {
295524
295596
  clauses.push(`SESSION_TOKEN '${escapeSQL(credentials.sessionToken)}'`);
295525
295597
  }
295598
+ if (scopeClause) {
295599
+ clauses.push(scopeClause);
295600
+ }
295526
295601
  createSecretCommand = `
295527
295602
  CREATE OR REPLACE SECRET ${secretName} (
295528
295603
  ${clauses.join(`,
@@ -295532,9 +295607,12 @@ function buildCloudStorageSecretSQL(secretName, credentials) {
295532
295607
  }
295533
295608
  return createSecretCommand;
295534
295609
  }
295535
- async function attachCloudStorage(connection, attachedDb) {
295610
+ async function attachCloudStorage(connection, attachedDb, origin2 = "connection", scope) {
295536
295611
  const storageType = attachedDb.type?.toUpperCase() || "";
295537
- const credentials = resolveCloudStorageCredentials(attachedDb);
295612
+ const credentials = {
295613
+ ...resolveCloudStorageCredentials(attachedDb, origin2),
295614
+ scope
295615
+ };
295538
295616
  await installAndLoadExtension(connection, "httpfs");
295539
295617
  if (credentials.provider === "credential_chain") {
295540
295618
  await installAndLoadExtension(connection, "aws");
@@ -295826,8 +295904,8 @@ function applyConnectionFingerprint(connection, metadata) {
295826
295904
  function isDuckDBConnection(connection) {
295827
295905
  return connection instanceof DuckDBConnection;
295828
295906
  }
295829
- function buildEnvironmentMalloyConfig(connections = [], environmentPath = "", isUpdateConnectionRequest = false) {
295830
- const assembled = assembleEnvironmentConnections(connections, environmentPath);
295907
+ function buildEnvironmentMalloyConfig(connections = [], environmentPath = "", isUpdateConnectionRequest = false, { origin: origin2 = "connection" } = {}) {
295908
+ const assembled = assembleEnvironmentConnections(connections, environmentPath, origin2);
295831
295909
  const duckLakeCache = new Map;
295832
295910
  const snowflakeJwtCache = new Map;
295833
295911
  const azureDuckDBCache = new Map;
@@ -295866,7 +295944,10 @@ function buildEnvironmentMalloyConfig(connections = [], environmentPath = "", is
295866
295944
  }
295867
295945
  const connection2 = await connectionPromise;
295868
295946
  if (isUpdateConnectionRequest || !await isDatabaseAttached(connection2, name)) {
295869
- await attachDuckLake(connection2, name, metadata.apiConnection.ducklakeConnection);
295947
+ await attachDuckLake(connection2, name, metadata.apiConnection.ducklakeConnection, {
295948
+ origin: origin2,
295949
+ managed: metadata.apiConnection.managed === true
295950
+ });
295870
295951
  if (metadata.apiConnection.managed === true) {
295871
295952
  await setManagedSearchPath(connection2, name);
295872
295953
  }
@@ -298447,7 +298528,7 @@ var require_stream_events = __commonJS((exports, module) => {
298447
298528
  var require_src140 = __commonJS((exports) => {
298448
298529
  Object.defineProperty(exports, "__esModule", { value: true });
298449
298530
  exports.teenyRequest = exports.RequestError = undefined;
298450
- var node_fetch_1 = require_lib8();
298531
+ var node_fetch_1 = require_lib7();
298451
298532
  var stream_1 = __require("stream");
298452
298533
  var uuid = require_dist10();
298453
298534
  var agents_1 = require_agents();
@@ -349769,6 +349850,9 @@ function carriesAuthorSql(def) {
349769
349850
  if (record.type === "sql_select" || record.node === "genericSQLExpr") {
349770
349851
  return true;
349771
349852
  }
349853
+ if (record.node === "function_call") {
349854
+ return Object.entries(record).some(([key, child]) => key !== "overload" && walk2(child, depth + 1));
349855
+ }
349772
349856
  if (record.type === "table" && typeof record.tablePath === "string" && !CATALOG_TABLE_PATH.test(record.tablePath)) {
349773
349857
  return true;
349774
349858
  }
@@ -350112,7 +350196,7 @@ async function buildSameCatalogIntoStorage(params) {
350112
350196
  const { destinationName, destinationConnection, physicalTableName } = params;
350113
350197
  const alias = params.sourceConnection.name || destinationName;
350114
350198
  if (params.sourceCarriesAuthorSql !== false) {
350115
- throw new BadRequestError(`Cannot materialize a source that reads storage destination ` + `'${destinationName}' and carries SQL its author wrote (an .sql() source ` + `or a sql_* expression): a build on the catalog runs only Malloy's own SQL. ` + `Express it in Malloy, or persist it without storage=.`);
350199
+ throw new BadRequestError(`Cannot materialize a source that reads storage destination ` + `'${destinationName}' and carries SQL its author wrote (an .sql() source ` + `or a sql_* expression) or reads a file or URL as a table: a build on the catalog ` + `runs only Malloy's own SQL over the catalog's tables. Express it that way. ` + `Dropping storage= does not help when the source's own connection is the catalog.`);
350116
350200
  }
350117
350201
  const { session, dispose, workDir } = createIsolatedBuildSession(`build_${destinationName}`);
350118
350202
  try {
@@ -370400,10 +370484,10 @@ class Environment {
370400
370484
  if (this.destinations.length > 0) {
370401
370485
  fs17.mkdirSync(destinationRoot, { recursive: true });
370402
370486
  }
370403
- this.destinationMalloyConfig = buildEnvironmentMalloyConfig(this.destinations, destinationRoot);
370487
+ this.destinationMalloyConfig = buildEnvironmentMalloyConfig(this.destinations, destinationRoot, false, { origin: "storage-destination" });
370404
370488
  } catch (error) {
370405
370489
  logger.error(`Failed to assemble storage destinations for environment ${this.environmentName}; serving without them`, { error });
370406
- this.destinationMalloyConfig = buildEnvironmentMalloyConfig([], storageDestinationRoot(this.environmentPath));
370490
+ this.destinationMalloyConfig = buildEnvironmentMalloyConfig([], storageDestinationRoot(this.environmentPath), false, { origin: "storage-destination" });
370407
370491
  }
370408
370492
  if (previous && previous !== this.destinationMalloyConfig) {
370409
370493
  this.retireConnectionGeneration(`environment ${this.environmentName} destinations`, () => previous.releaseConnections());
@@ -377082,7 +377166,7 @@ function readerSql(uri, format, options = {}) {
377082
377166
  return `read_parquet(${path25})`;
377083
377167
  }
377084
377168
  function assertUnderDataPath(uri, dataPath) {
377085
- const root = dataPath.replace(/\/+$/, "");
377169
+ const root = stripTrailingSlashes(dataPath);
377086
377170
  const outside = () => new BadRequestError(`The object must sit under the catalog's data path (${root}/).`);
377087
377171
  if (!root || !uri.startsWith(`${root}/`))
377088
377172
  throw outside();
@@ -377121,11 +377205,21 @@ async function loadStorageTable(params) {
377121
377205
  if (params.format === "xlsx") {
377122
377206
  await installAndLoadExtension(session, "excel");
377123
377207
  }
377124
- return await writeTableAs(session, params, reader);
377208
+ return await writeTableAs(session, params, reader, (error) => readerRefusal(error, params.uri));
377125
377209
  } finally {
377126
377210
  await dispose();
377127
377211
  }
377128
377212
  }
377213
+ function readerRefusal(error, uri) {
377214
+ if (!(error instanceof Error))
377215
+ return null;
377216
+ if (!/^(Invalid Input|Conversion) Error:/.test(error.message))
377217
+ return null;
377218
+ logInternalFailure("Load refused by the file reader", error, "warn");
377219
+ const fileName = path24.posix.basename(uri);
377220
+ const words2 = error.message.split(/\n\s*LINE \d+:/)[0].split(uri).join(fileName).trim();
377221
+ return new BadRequestError(words2);
377222
+ }
377129
377223
  async function copyStorageTable(params) {
377130
377224
  const { destinationName, destinationConnection, source } = params;
377131
377225
  for (const conn of [destinationConnection, source.connection]) {
@@ -377158,7 +377252,7 @@ async function copyStorageTable(params) {
377158
377252
  await dispose();
377159
377253
  }
377160
377254
  }
377161
- async function writeTableAs(session, params, from) {
377255
+ async function writeTableAs(session, params, from, refused) {
377162
377256
  const { destinationName, schemaName, tableName } = params;
377163
377257
  await session.runSQL("SET ducklake_default_data_inlining_row_limit = 0");
377164
377258
  const target = [destinationName, schemaName, tableName].map((part) => quoteIdentifier(part, "duckdb")).join(".");
@@ -377168,7 +377262,7 @@ async function writeTableAs(session, params, from) {
377168
377262
  if (params.mode === "create" && error instanceof Error && /already exists/i.test(error.message)) {
377169
377263
  throw new ConflictError(`Table '${schemaName}.${tableName}' already exists in '${destinationName}'.`);
377170
377264
  }
377171
- throw error;
377265
+ throw refused?.(error) ?? error;
377172
377266
  }
377173
377267
  await session.runSQL(`CALL ducklake_flush_inlined_data('${escapeSQL(destinationName)}', schema_name => '${escapeSQL(schemaName)}', table_name => '${escapeSQL(tableName)}')`);
377174
377268
  const counted = rows(await session.runSQL(`SELECT count(*) AS n FROM ${target}`));
Binary file
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "snowflake"
20
20
  ],
21
21
  "homepage": "https://docs.malloydata.dev/documentation/user_guides/publishing/mcp_agents",
22
- "version": "0.10.4",
22
+ "version": "0.10.6",
23
23
  "main": "dist/server.mjs",
24
24
  "bin": {
25
25
  "malloy-publisher": "dist/server.mjs"
Binary file