@malloy-publisher/server 0.0.235 → 0.0.236

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 (301) hide show
  1. package/dist/app/api-doc.yaml +47 -17
  2. package/dist/package_load_worker.mjs +8 -1
  3. package/dist/server.mjs +471 -54
  4. package/package.json +5 -2
  5. package/.eslintrc.json +0 -44
  6. package/.prettierignore +0 -1
  7. package/.prettierrc +0 -4
  8. package/build.ts +0 -97
  9. package/eslint.config.mjs +0 -76
  10. package/k6-tests/README.md +0 -144
  11. package/k6-tests/bun.lock +0 -797
  12. package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
  13. package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
  14. package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
  15. package/k6-tests/load-test/load-test-crud.ts +0 -142
  16. package/k6-tests/load-test/load-test.ts +0 -784
  17. package/k6-tests/package.json +0 -27
  18. package/k6-tests/scripts/run-with-otel.sh +0 -50
  19. package/k6-tests/smoke-test/smoke-test.ts +0 -158
  20. package/k6-tests/tsconfig.json +0 -29
  21. package/k6-tests/utils/client_factory.ts +0 -128
  22. package/k6-tests/utils/common.ts +0 -489
  23. package/k6-tests/utils/logger.ts +0 -62
  24. package/k6-tests/utils/types.d.ts +0 -35
  25. package/malloy_mcp.dxt +0 -0
  26. package/publisher.config.example.bigquery.json +0 -33
  27. package/publisher.config.example.duckdb.json +0 -23
  28. package/publisher.config.json +0 -23
  29. package/scripts/bake-duckdb-extensions.js +0 -110
  30. package/src/config.spec.ts +0 -1535
  31. package/src/config.theme.spec.ts +0 -178
  32. package/src/config.ts +0 -1142
  33. package/src/constants.ts +0 -105
  34. package/src/controller/compile.controller.ts +0 -40
  35. package/src/controller/connection.controller.spec.ts +0 -849
  36. package/src/controller/connection.controller.ts +0 -901
  37. package/src/controller/database.controller.ts +0 -24
  38. package/src/controller/materialization.controller.spec.ts +0 -293
  39. package/src/controller/materialization.controller.ts +0 -319
  40. package/src/controller/model.controller.ts +0 -164
  41. package/src/controller/package.controller.spec.ts +0 -273
  42. package/src/controller/package.controller.ts +0 -306
  43. package/src/controller/query.controller.ts +0 -152
  44. package/src/controller/theme.controller.ts +0 -83
  45. package/src/controller/watch-mode.controller.ts +0 -243
  46. package/src/data_styles.ts +0 -71
  47. package/src/default-publisher.config.json +0 -23
  48. package/src/ducklake_version.spec.ts +0 -163
  49. package/src/ducklake_version.ts +0 -153
  50. package/src/errors.spec.ts +0 -119
  51. package/src/errors.ts +0 -237
  52. package/src/filter_deprecation.spec.ts +0 -64
  53. package/src/filter_deprecation.ts +0 -42
  54. package/src/health.spec.ts +0 -123
  55. package/src/health.ts +0 -233
  56. package/src/heap_check.spec.ts +0 -144
  57. package/src/heap_check.ts +0 -144
  58. package/src/instrumentation.ts +0 -214
  59. package/src/json_utils.spec.ts +0 -51
  60. package/src/json_utils.ts +0 -33
  61. package/src/logger.spec.ts +0 -210
  62. package/src/logger.ts +0 -251
  63. package/src/malloy_pin_prereqs.spec.ts +0 -25
  64. package/src/materialization_metrics.spec.ts +0 -169
  65. package/src/materialization_metrics.ts +0 -346
  66. package/src/mcp/error_messages.spec.ts +0 -35
  67. package/src/mcp/error_messages.ts +0 -183
  68. package/src/mcp/handler_utils.spec.ts +0 -108
  69. package/src/mcp/handler_utils.ts +0 -251
  70. package/src/mcp/mcp_constants.ts +0 -6
  71. package/src/mcp/query_envelope.spec.ts +0 -229
  72. package/src/mcp/query_envelope.ts +0 -240
  73. package/src/mcp/server.protocol.spec.ts +0 -250
  74. package/src/mcp/server.ts +0 -87
  75. package/src/mcp/skills/build_skills_bundle.spec.ts +0 -51
  76. package/src/mcp/skills/build_skills_bundle.ts +0 -176
  77. package/src/mcp/skills/skills_bundle.json +0 -1
  78. package/src/mcp/skills/skills_bundle.spec.ts +0 -171
  79. package/src/mcp/tool_response.spec.ts +0 -108
  80. package/src/mcp/tool_response.ts +0 -138
  81. package/src/mcp/tools/compile_tool.spec.ts +0 -315
  82. package/src/mcp/tools/compile_tool.ts +0 -208
  83. package/src/mcp/tools/docs_search/build_docs_index.ts +0 -132
  84. package/src/mcp/tools/docs_search/malloy_docs_index.json +0 -1
  85. package/src/mcp/tools/docs_search_tool.spec.ts +0 -32
  86. package/src/mcp/tools/docs_search_tool.ts +0 -128
  87. package/src/mcp/tools/embedding_index.spec.ts +0 -1236
  88. package/src/mcp/tools/embedding_index.ts +0 -808
  89. package/src/mcp/tools/execute_query_tool.spec.ts +0 -294
  90. package/src/mcp/tools/execute_query_tool.ts +0 -316
  91. package/src/mcp/tools/get_context_eval.ts +0 -275
  92. package/src/mcp/tools/get_context_tool.spec.ts +0 -592
  93. package/src/mcp/tools/get_context_tool.ts +0 -585
  94. package/src/mcp/tools/reload_package_tool.spec.ts +0 -232
  95. package/src/mcp/tools/reload_package_tool.ts +0 -132
  96. package/src/mcp_config.spec.ts +0 -919
  97. package/src/mcp_config.ts +0 -425
  98. package/src/oom_guards.integration.spec.ts +0 -269
  99. package/src/package_load/package_load_pool.spec.ts +0 -252
  100. package/src/package_load/package_load_pool.ts +0 -931
  101. package/src/package_load/package_load_worker.ts +0 -1034
  102. package/src/package_load/protocol.ts +0 -364
  103. package/src/package_load/rpc_wait_accountant.spec.ts +0 -109
  104. package/src/package_load/rpc_wait_accountant.ts +0 -76
  105. package/src/package_load_metrics.spec.ts +0 -114
  106. package/src/package_load_metrics.ts +0 -127
  107. package/src/path_safety.spec.ts +0 -158
  108. package/src/path_safety.ts +0 -146
  109. package/src/pg_helpers.spec.ts +0 -223
  110. package/src/pg_helpers.ts +0 -52
  111. package/src/query_cap_metrics.spec.ts +0 -89
  112. package/src/query_cap_metrics.ts +0 -116
  113. package/src/query_concurrency.spec.ts +0 -247
  114. package/src/query_concurrency.ts +0 -237
  115. package/src/query_metadata_metrics.ts +0 -49
  116. package/src/query_param_utils.ts +0 -29
  117. package/src/query_timeout.spec.ts +0 -224
  118. package/src/query_timeout.ts +0 -179
  119. package/src/runtime/publisher.js +0 -328
  120. package/src/server-old.ts +0 -988
  121. package/src/server.ts +0 -2059
  122. package/src/service/annotations.spec.ts +0 -118
  123. package/src/service/annotations.ts +0 -91
  124. package/src/service/authorize.spec.ts +0 -154
  125. package/src/service/authorize.ts +0 -498
  126. package/src/service/authorize_integration.spec.ts +0 -2041
  127. package/src/service/build_plan.spec.ts +0 -685
  128. package/src/service/build_plan.ts +0 -797
  129. package/src/service/compile_authorize.spec.ts +0 -85
  130. package/src/service/compile_fragment_techniques.spec.ts +0 -156
  131. package/src/service/connection.spec.ts +0 -2360
  132. package/src/service/connection.ts +0 -2187
  133. package/src/service/connection_config.spec.ts +0 -734
  134. package/src/service/connection_config.ts +0 -899
  135. package/src/service/connection_federation.spec.ts +0 -184
  136. package/src/service/connection_fingerprint.spec.ts +0 -102
  137. package/src/service/connection_service.spec.ts +0 -711
  138. package/src/service/connection_service.ts +0 -291
  139. package/src/service/cron_evaluator.spec.ts +0 -79
  140. package/src/service/cron_evaluator.ts +0 -105
  141. package/src/service/db_utils.spec.ts +0 -1085
  142. package/src/service/db_utils.ts +0 -1502
  143. package/src/service/duckdb_instance_isolation.spec.ts +0 -137
  144. package/src/service/ducklake_lazy_attach.spec.ts +0 -110
  145. package/src/service/embedding_provider.spec.ts +0 -329
  146. package/src/service/embedding_provider.ts +0 -236
  147. package/src/service/environment.ts +0 -2191
  148. package/src/service/environment_admission.spec.ts +0 -344
  149. package/src/service/environment_compile.spec.ts +0 -197
  150. package/src/service/environment_store.spec.ts +0 -2274
  151. package/src/service/environment_store.ts +0 -2434
  152. package/src/service/environment_store_anchoring.spec.ts +0 -107
  153. package/src/service/environment_store_clone.spec.ts +0 -350
  154. package/src/service/explore_visibility.spec.ts +0 -434
  155. package/src/service/exports_probe.spec.ts +0 -107
  156. package/src/service/extension_fetch_policy.spec.ts +0 -256
  157. package/src/service/filter.spec.ts +0 -458
  158. package/src/service/filter.ts +0 -341
  159. package/src/service/filter_bypass.spec.ts +0 -418
  160. package/src/service/filter_integration.spec.ts +0 -1004
  161. package/src/service/freshness.spec.ts +0 -183
  162. package/src/service/freshness.ts +0 -112
  163. package/src/service/gcs_s3_utils.ts +0 -427
  164. package/src/service/given.ts +0 -105
  165. package/src/service/givens_integration.spec.ts +0 -219
  166. package/src/service/manifest_loader.spec.ts +0 -189
  167. package/src/service/manifest_loader.ts +0 -156
  168. package/src/service/materialization_build_session.spec.ts +0 -435
  169. package/src/service/materialization_build_session.ts +0 -681
  170. package/src/service/materialization_config_validation.spec.ts +0 -99
  171. package/src/service/materialization_config_validation.ts +0 -120
  172. package/src/service/materialization_eligibility.spec.ts +0 -158
  173. package/src/service/materialization_eligibility.ts +0 -305
  174. package/src/service/materialization_schedule_surface.spec.ts +0 -256
  175. package/src/service/materialization_scheduler.spec.ts +0 -289
  176. package/src/service/materialization_scheduler.ts +0 -304
  177. package/src/service/materialization_serve_transform.spec.ts +0 -1003
  178. package/src/service/materialization_serve_transform.ts +0 -779
  179. package/src/service/materialization_service.spec.ts +0 -2373
  180. package/src/service/materialization_service.ts +0 -2342
  181. package/src/service/materialization_test_fixtures.ts +0 -249
  182. package/src/service/model.spec.ts +0 -1157
  183. package/src/service/model.ts +0 -3370
  184. package/src/service/model_limits.spec.ts +0 -209
  185. package/src/service/model_limits.ts +0 -131
  186. package/src/service/model_storage_serve.spec.ts +0 -193
  187. package/src/service/model_storage_serve_joins.spec.ts +0 -193
  188. package/src/service/package.spec.ts +0 -612
  189. package/src/service/package.ts +0 -1799
  190. package/src/service/package_manifest.spec.ts +0 -230
  191. package/src/service/package_manifest.ts +0 -241
  192. package/src/service/package_memory_governor.spec.ts +0 -173
  193. package/src/service/package_memory_governor.ts +0 -233
  194. package/src/service/package_quote_bound_tables.spec.ts +0 -171
  195. package/src/service/package_race.spec.ts +0 -208
  196. package/src/service/package_reload_safety.spec.ts +0 -193
  197. package/src/service/package_rollback.spec.ts +0 -190
  198. package/src/service/package_worker_path.spec.ts +0 -645
  199. package/src/service/path_injection.spec.ts +0 -39
  200. package/src/service/persist_annotation_validation.spec.ts +0 -89
  201. package/src/service/persist_annotation_validation.ts +0 -59
  202. package/src/service/persistence_policy.spec.ts +0 -374
  203. package/src/service/proxy.spec.ts +0 -414
  204. package/src/service/proxy.ts +0 -248
  205. package/src/service/query_boundary.spec.ts +0 -470
  206. package/src/service/query_metadata.spec.ts +0 -408
  207. package/src/service/query_metadata.ts +0 -492
  208. package/src/service/query_metadata_identity.spec.ts +0 -149
  209. package/src/service/query_text.spec.ts +0 -79
  210. package/src/service/query_text.ts +0 -43
  211. package/src/service/quoting.spec.ts +0 -135
  212. package/src/service/quoting.ts +0 -78
  213. package/src/service/resolve_environment.ts +0 -15
  214. package/src/service/restricted_mode.spec.ts +0 -299
  215. package/src/service/source_extraction.ts +0 -225
  216. package/src/service/theme_store.ts +0 -199
  217. package/src/storage/DatabaseInterface.ts +0 -198
  218. package/src/storage/StorageManager.ts +0 -113
  219. package/src/storage/duckdb/ConnectionRepository.ts +0 -143
  220. package/src/storage/duckdb/DuckDBConnection.ts +0 -123
  221. package/src/storage/duckdb/DuckDBRepository.ts +0 -208
  222. package/src/storage/duckdb/EnvironmentRepository.ts +0 -148
  223. package/src/storage/duckdb/MaterializationRepository.spec.ts +0 -191
  224. package/src/storage/duckdb/MaterializationRepository.ts +0 -336
  225. package/src/storage/duckdb/PackageRepository.ts +0 -135
  226. package/src/storage/duckdb/schema.ts +0 -229
  227. package/src/stream_helpers.spec.ts +0 -280
  228. package/src/stream_helpers.ts +0 -162
  229. package/src/telemetry.ts +0 -20
  230. package/src/test_helpers/metrics_harness.ts +0 -166
  231. package/src/theme_key_parity.spec.ts +0 -57
  232. package/src/utils.ts +0 -29
  233. package/tests/fixtures/authorize-compile/model.malloy +0 -9
  234. package/tests/fixtures/authorize-compile/publisher.json +0 -4
  235. package/tests/fixtures/html-pages-nopublic/model.malloy +0 -1
  236. package/tests/fixtures/html-pages-nopublic/publisher.json +0 -5
  237. package/tests/fixtures/html-pages-test/data.csv +0 -3
  238. package/tests/fixtures/html-pages-test/public/assets/app.css +0 -3
  239. package/tests/fixtures/html-pages-test/public/barehtml.html +0 -4
  240. package/tests/fixtures/html-pages-test/public/bodymeta.html +0 -8
  241. package/tests/fixtures/html-pages-test/public/data.json +0 -1
  242. package/tests/fixtures/html-pages-test/public/fitcomment.html +0 -11
  243. package/tests/fixtures/html-pages-test/public/index.html +0 -9
  244. package/tests/fixtures/html-pages-test/public/nohead.html +0 -8
  245. package/tests/fixtures/html-pages-test/public/notfit.html +0 -13
  246. package/tests/fixtures/html-pages-test/public/slides.html +0 -12
  247. package/tests/fixtures/html-pages-test/public/sub/page2.html +0 -9
  248. package/tests/fixtures/html-pages-test/public/unterminated.html +0 -10
  249. package/tests/fixtures/html-pages-test/publisher.json +0 -5
  250. package/tests/fixtures/html-pages-test/report.malloy +0 -1
  251. package/tests/fixtures/persist-multi-level/data/orders.csv +0 -5
  252. package/tests/fixtures/persist-multi-level/multi_level.malloy +0 -18
  253. package/tests/fixtures/persist-multi-level/publisher.json +0 -5
  254. package/tests/fixtures/persist-schedule-test/data/orders.csv +0 -5
  255. package/tests/fixtures/persist-schedule-test/persist_schedule_test.malloy +0 -11
  256. package/tests/fixtures/persist-schedule-test/publisher.json +0 -7
  257. package/tests/fixtures/persist-schedule-warn-test/data/orders.csv +0 -5
  258. package/tests/fixtures/persist-schedule-warn-test/persist_warn_test.malloy +0 -11
  259. package/tests/fixtures/persist-schedule-warn-test/publisher.json +0 -6
  260. package/tests/fixtures/persist-test/data/orders.csv +0 -5
  261. package/tests/fixtures/persist-test/persist_test.malloy +0 -11
  262. package/tests/fixtures/persist-test/publisher.json +0 -5
  263. package/tests/fixtures/publisher.config.json +0 -15
  264. package/tests/fixtures/query-givens/data/orders.csv +0 -7
  265. package/tests/fixtures/query-givens/model.malloy +0 -34
  266. package/tests/fixtures/query-givens/publisher.json +0 -5
  267. package/tests/fixtures/xlsx/database.xlsx +0 -0
  268. package/tests/harness/mcp_test_setup.ts +0 -332
  269. package/tests/harness/rest_e2e.ts +0 -68
  270. package/tests/integration/authorize/compile_authorize_http.integration.spec.ts +0 -92
  271. package/tests/integration/concurrent_package/concurrent_package.integration.spec.ts +0 -280
  272. package/tests/integration/duckdb_storage/duckdb_storage.integration.spec.ts +0 -138
  273. package/tests/integration/first_boot/readiness_line.integration.spec.ts +0 -177
  274. package/tests/integration/html_pages/html_pages.integration.spec.ts +0 -440
  275. package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +0 -259
  276. package/tests/integration/materialization/environment_list.integration.spec.ts +0 -133
  277. package/tests/integration/materialization/freshness_gate.integration.spec.ts +0 -292
  278. package/tests/integration/materialization/manifest_binding.integration.spec.ts +0 -430
  279. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -441
  280. package/tests/integration/materialization/orchestrated_rebind.integration.spec.ts +0 -150
  281. package/tests/integration/materialization/reference_manifest.integration.spec.ts +0 -251
  282. package/tests/integration/materialization/schedule_edit.integration.spec.ts +0 -126
  283. package/tests/integration/materialization/scheduler.integration.spec.ts +0 -200
  284. package/tests/integration/materialization/scheduler_recovery.integration.spec.ts +0 -172
  285. package/tests/integration/materialization/scheduler_transitions.integration.spec.ts +0 -256
  286. package/tests/integration/materialization/scheduler_wiring.integration.spec.ts +0 -143
  287. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -438
  288. package/tests/integration/mcp/mcp_get_context_semantic.integration.spec.ts +0 -235
  289. package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -98
  290. package/tests/integration/query_givens/query_givens.integration.spec.ts +0 -146
  291. package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +0 -121
  292. package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +0 -110
  293. package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -421
  294. package/tests/unit/controller/theme.controller.spec.ts +0 -141
  295. package/tests/unit/duckdb/attached_databases.test.ts +0 -1243
  296. package/tests/unit/duckdb/duckdb_connection.test.ts +0 -181
  297. package/tests/unit/duckdb/legacy_schema_migration.test.ts +0 -197
  298. package/tests/unit/duckdb/repositories.test.ts +0 -208
  299. package/tests/unit/ducklake/ducklake.test.ts +0 -952
  300. package/tests/unit/service/theme_store.spec.ts +0 -139
  301. package/tsconfig.json +0 -32
package/dist/server.mjs CHANGED
@@ -156020,6 +156020,8 @@ function internalErrorToHttpError(error) {
156020
156020
  return httpError(400, error.message);
156021
156021
  } else if (error instanceof ConnectionNotFoundError) {
156022
156022
  return httpError(404, error.message);
156023
+ } else if (error instanceof DestinationNotFoundError) {
156024
+ return httpError(422, error.message);
156023
156025
  } else if (error instanceof ConnectionAuthError) {
156024
156026
  return httpError(422, error.message);
156025
156027
  } else if (error instanceof UnsupportedCatalogFormatError) {
@@ -156055,7 +156057,7 @@ function httpError(code, message) {
156055
156057
  }
156056
156058
  };
156057
156059
  }
156058
- var NotImplementedError, BadRequestError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, ConnectionNotFoundError, ConnectionError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, MaterializationEligibilityError, FrozenConfigError, AccessDeniedError, NotQueryableError, MaterializationNotFoundError, MaterializationConflictError, InvalidStateTransitionError, ServiceUnavailableError, PayloadTooLargeError, QueryTimeoutError;
156060
+ var NotImplementedError, BadRequestError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, ConnectionNotFoundError, ConnectionError, DestinationNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, MaterializationEligibilityError, FrozenConfigError, AccessDeniedError, NotQueryableError, MaterializationNotFoundError, MaterializationConflictError, InvalidStateTransitionError, ServiceUnavailableError, PayloadTooLargeError, QueryTimeoutError;
156059
156061
  var init_errors = __esm(() => {
156060
156062
  init_constants();
156061
156063
  NotImplementedError = class NotImplementedError extends Error {
@@ -156093,6 +156095,11 @@ var init_errors = __esm(() => {
156093
156095
  super(message);
156094
156096
  }
156095
156097
  };
156098
+ DestinationNotFoundError = class DestinationNotFoundError extends Error {
156099
+ constructor(message) {
156100
+ super(message);
156101
+ }
156102
+ };
156096
156103
  ConnectionAuthError = class ConnectionAuthError extends Error {
156097
156104
  constructor(message) {
156098
156105
  super(message);
@@ -261655,6 +261662,7 @@ var getProcessedPublisherConfig = (serverRoot) => {
261655
261662
  name: environment.name,
261656
261663
  packages: validPackages,
261657
261664
  connections: convertConnectionsToApiConnections(environment.connections || []),
261665
+ storageDestinations: Array.isArray(environment.storageDestinations) ? environment.storageDestinations : [],
261658
261666
  ...resolvedTheme ? { theme: resolvedTheme } : {}
261659
261667
  });
261660
261668
  }
@@ -265185,6 +265193,7 @@ function safeJoinUnderRoot(root, ...segments) {
265185
265193
  }
265186
265194
 
265187
265195
  // src/service/connection_config.ts
265196
+ init_errors();
265188
265197
  init_logger();
265189
265198
  import { createPrivateKey } from "crypto";
265190
265199
  import { existsSync } from "fs";
@@ -265865,6 +265874,106 @@ function validateConnectionShape(connection) {
265865
265874
  }
265866
265875
  }
265867
265876
  }
265877
+ var DECLARABLE_STORAGE_DESTINATION_TYPES = new Set(["ducklake"]);
265878
+ var STORAGE_DESTINATIONS_DIR = ".storage-destinations";
265879
+ function storageDestinationRoot(environmentPath) {
265880
+ return path3.join(environmentPath, STORAGE_DESTINATIONS_DIR);
265881
+ }
265882
+ function validateStorageDestinations(destinations = []) {
265883
+ const accepted = [];
265884
+ const rejected = [];
265885
+ if (!Array.isArray(destinations)) {
265886
+ return {
265887
+ accepted,
265888
+ rejected: [{ reason: "the value is not a list of destinations." }]
265889
+ };
265890
+ }
265891
+ const seen = new Set;
265892
+ for (const destination of destinations) {
265893
+ if (!destination || typeof destination !== "object") {
265894
+ rejected.push({ reason: "the entry is not an object." });
265895
+ continue;
265896
+ }
265897
+ const { name, type } = destination;
265898
+ if (!name || typeof name !== "string") {
265899
+ rejected.push({
265900
+ reason: `the "name" field is missing or not a string.`
265901
+ });
265902
+ continue;
265903
+ }
265904
+ if (!type || !DECLARABLE_STORAGE_DESTINATION_TYPES.has(type)) {
265905
+ rejected.push({
265906
+ name,
265907
+ reason: `the type "${type}" is not a supported destination type ` + `(supported: ${[...DECLARABLE_STORAGE_DESTINATION_TYPES].join(", ")}).`
265908
+ });
265909
+ continue;
265910
+ }
265911
+ if (name === "duckdb") {
265912
+ rejected.push({
265913
+ name,
265914
+ reason: `the name "duckdb" is reserved for per-package sandboxes.`
265915
+ });
265916
+ continue;
265917
+ }
265918
+ if (seen.has(name)) {
265919
+ rejected.push({
265920
+ name,
265921
+ reason: "an earlier entry already uses this name."
265922
+ });
265923
+ continue;
265924
+ }
265925
+ try {
265926
+ validateConnectionShape(destination);
265927
+ } catch (error) {
265928
+ rejected.push({ name, reason: error.message });
265929
+ continue;
265930
+ }
265931
+ seen.add(name);
265932
+ const { resource: _resource, ...body } = destination;
265933
+ accepted.push(body);
265934
+ }
265935
+ return { accepted, rejected };
265936
+ }
265937
+ function describeRejection(rejection) {
265938
+ return rejection.name ? `storage destination "${rejection.name}": ${rejection.reason}` : `storage destination: ${rejection.reason}`;
265939
+ }
265940
+ function processStorageDestinations(destinations = []) {
265941
+ const { accepted, rejected } = validateStorageDestinations(destinations);
265942
+ for (const rejection of rejected) {
265943
+ logger.warn(`Invalid ${describeRejection(rejection)} Skipping.`);
265944
+ }
265945
+ return accepted;
265946
+ }
265947
+ function processStorageDestinationsOrThrow(destinations = []) {
265948
+ const { accepted, rejected } = validateStorageDestinations(destinations);
265949
+ if (rejected.length > 0) {
265950
+ throw new BadRequestError(`storageDestinations was not accepted — ${rejected.map(describeRejection).join(" ")}`);
265951
+ }
265952
+ return accepted;
265953
+ }
265954
+ function storageDestinationsEqual(left, right) {
265955
+ if (left.length !== right.length) {
265956
+ return false;
265957
+ }
265958
+ return canonicalDestinationList(left) === canonicalDestinationList(right);
265959
+ }
265960
+ function canonicalDestinationList(destinations) {
265961
+ return JSON.stringify([...destinations].sort((left, right) => (left.name ?? "") < (right.name ?? "") ? -1 : 1).map(canonicalizeJsonValue));
265962
+ }
265963
+ function canonicalizeJsonValue(value) {
265964
+ if (Array.isArray(value)) {
265965
+ return value.map(canonicalizeJsonValue);
265966
+ }
265967
+ if (value === null || typeof value !== "object") {
265968
+ return value;
265969
+ }
265970
+ const entries = value;
265971
+ const sorted = {};
265972
+ for (const key of Object.keys(entries).sort()) {
265973
+ sorted[key] = canonicalizeJsonValue(entries[key]);
265974
+ }
265975
+ return sorted;
265976
+ }
265868
265977
  function assembleEnvironmentConnections(connections = [], environmentPath = "") {
265869
265978
  const pojo = { connections: {} };
265870
265979
  const metadata = new Map;
@@ -266798,6 +266907,18 @@ async function deleteDuckLakeConnectionFile(connectionName, environmentPath) {
266798
266907
  }
266799
266908
  }
266800
266909
  }
266910
+ function restrictMalloyConfigToConnections(base, allowed) {
266911
+ const restricted = new MalloyConfig({ connections: {} });
266912
+ restricted.wrapConnections(() => ({
266913
+ lookupConnection: async (name) => {
266914
+ if (!name || !allowed.has(name)) {
266915
+ throw new ConnectionNotFoundError(`Connection ${name ?? "(default)"} is not available to a materialization serve shape`);
266916
+ }
266917
+ return base.connections.lookupConnection(name);
266918
+ }
266919
+ }));
266920
+ return restricted;
266921
+ }
266801
266922
  function entryToDuckDBOptions(name, entry, workingDirectory) {
266802
266923
  const { is: _is, ...rest } = entry;
266803
266924
  if (workingDirectory !== undefined) {
@@ -275885,6 +276006,66 @@ class EnvironmentRepository {
275885
276006
  }
275886
276007
  }
275887
276008
 
276009
+ // src/storage/duckdb/StorageDestinationRepository.ts
276010
+ class StorageDestinationRepository {
276011
+ db;
276012
+ constructor(db) {
276013
+ this.db = db;
276014
+ }
276015
+ generateId() {
276016
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
276017
+ }
276018
+ async list(environmentId) {
276019
+ const rows = await this.db.all("SELECT * FROM storage_destinations WHERE environment_id = ? ORDER BY name", [environmentId]);
276020
+ return rows.map(this.mapToDestination);
276021
+ }
276022
+ async getByName(environmentId, name) {
276023
+ const row = await this.db.get("SELECT * FROM storage_destinations WHERE environment_id = ? AND name = ?", [environmentId, name]);
276024
+ return row ? this.mapToDestination(row) : null;
276025
+ }
276026
+ async upsert(destination) {
276027
+ const existing = await this.getByName(destination.environmentId, destination.name);
276028
+ const now = new Date;
276029
+ const configJson = JSON.stringify(destination.config);
276030
+ if (existing) {
276031
+ await this.db.run(`UPDATE storage_destinations
276032
+ SET type = ?, config = ?, updated_at = ?
276033
+ WHERE id = ?`, [destination.type, configJson, now.toISOString(), existing.id]);
276034
+ return { ...existing, ...destination, updatedAt: now };
276035
+ }
276036
+ const id = this.generateId();
276037
+ await this.db.run(`INSERT INTO storage_destinations
276038
+ (id, environment_id, name, type, config, created_at, updated_at)
276039
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, [
276040
+ id,
276041
+ destination.environmentId,
276042
+ destination.name,
276043
+ destination.type,
276044
+ configJson,
276045
+ now.toISOString(),
276046
+ now.toISOString()
276047
+ ]);
276048
+ return { id, ...destination, createdAt: now, updatedAt: now };
276049
+ }
276050
+ async deleteById(id) {
276051
+ await this.db.run("DELETE FROM storage_destinations WHERE id = ?", [id]);
276052
+ }
276053
+ async deleteByEnvironmentId(environmentId) {
276054
+ await this.db.run("DELETE FROM storage_destinations WHERE environment_id = ?", [environmentId]);
276055
+ }
276056
+ mapToDestination(row) {
276057
+ return {
276058
+ id: row.id,
276059
+ environmentId: row.environment_id,
276060
+ name: row.name,
276061
+ type: row.type,
276062
+ config: JSON.parse(row.config),
276063
+ createdAt: new Date(row.created_at),
276064
+ updatedAt: new Date(row.updated_at)
276065
+ };
276066
+ }
276067
+ }
276068
+
275888
276069
  // src/storage/duckdb/MaterializationRepository.ts
275889
276070
  var TERMINAL_STATUSES = new Set([
275890
276071
  "MANIFEST_FILE_READY",
@@ -276173,12 +276354,14 @@ class DuckDBRepository {
276173
276354
  environmentRepo;
276174
276355
  packageRepo;
276175
276356
  connectionRepo;
276357
+ destinationRepo;
276176
276358
  materializationRepo;
276177
276359
  constructor(db) {
276178
276360
  this.db = db;
276179
276361
  this.environmentRepo = new EnvironmentRepository(db);
276180
276362
  this.packageRepo = new PackageRepository(db);
276181
276363
  this.connectionRepo = new ConnectionRepository(db);
276364
+ this.destinationRepo = new StorageDestinationRepository(db);
276182
276365
  this.materializationRepo = new MaterializationRepository(db);
276183
276366
  }
276184
276367
  async listEnvironments() {
@@ -276199,6 +276382,7 @@ class DuckDBRepository {
276199
276382
  async deleteEnvironment(id) {
276200
276383
  await this.materializationRepo.deleteByEnvironmentId(id);
276201
276384
  await this.connectionRepo.deleteConnectionsByEnvironmentId(id);
276385
+ await this.destinationRepo.deleteByEnvironmentId(id);
276202
276386
  await this.packageRepo.deletePackagesByEnvironmentId(id);
276203
276387
  await this.environmentRepo.deleteEnvironment(id);
276204
276388
  }
@@ -276248,6 +276432,21 @@ class DuckDBRepository {
276248
276432
  async deleteConnectionsByEnvironmentId(id) {
276249
276433
  return this.connectionRepo.deleteConnectionsByEnvironmentId(id);
276250
276434
  }
276435
+ async listStorageDestinations(environmentId) {
276436
+ return this.destinationRepo.list(environmentId);
276437
+ }
276438
+ async getStorageDestinationByName(environmentId, name) {
276439
+ return this.destinationRepo.getByName(environmentId, name);
276440
+ }
276441
+ async upsertStorageDestination(destination) {
276442
+ return this.destinationRepo.upsert(destination);
276443
+ }
276444
+ async deleteStorageDestination(id) {
276445
+ return this.destinationRepo.deleteById(id);
276446
+ }
276447
+ async deleteStorageDestinationsByEnvironmentId(id) {
276448
+ return this.destinationRepo.deleteByEnvironmentId(id);
276449
+ }
276251
276450
  async listMaterializations(environmentId, packageName, options) {
276252
276451
  return this.materializationRepo.list(environmentId, packageName, options);
276253
276452
  }
@@ -276323,6 +276522,19 @@ async function initializeSchema(db, force = false) {
276323
276522
  UNIQUE (environment_id, name)
276324
276523
  )
276325
276524
  `);
276525
+ await db.run(`
276526
+ CREATE TABLE IF NOT EXISTS storage_destinations (
276527
+ id VARCHAR PRIMARY KEY,
276528
+ environment_id VARCHAR NOT NULL,
276529
+ name VARCHAR NOT NULL,
276530
+ type VARCHAR NOT NULL,
276531
+ config JSON NOT NULL,
276532
+ created_at TIMESTAMP NOT NULL,
276533
+ updated_at TIMESTAMP NOT NULL,
276534
+ FOREIGN KEY (environment_id) REFERENCES environments(id),
276535
+ UNIQUE (environment_id, name)
276536
+ )
276537
+ `);
276326
276538
  await db.run(`
276327
276539
  CREATE TABLE IF NOT EXISTS materializations (
276328
276540
  id VARCHAR PRIMARY KEY,
@@ -276351,6 +276563,7 @@ async function initializeSchema(db, force = false) {
276351
276563
  await createEntityEmbeddingsTable(db);
276352
276564
  await db.run("CREATE INDEX IF NOT EXISTS idx_packages_environment_id ON packages(environment_id)");
276353
276565
  await db.run("CREATE INDEX IF NOT EXISTS idx_connections_environment_id ON connections(environment_id)");
276566
+ await db.run("CREATE INDEX IF NOT EXISTS idx_storage_destinations_environment_id ON storage_destinations(environment_id)");
276354
276567
  await db.run("CREATE INDEX IF NOT EXISTS idx_materializations_environment_package ON materializations(environment_id, package_name)");
276355
276568
  await db.run("CREATE UNIQUE INDEX IF NOT EXISTS idx_materializations_active_key ON materializations(active_key)");
276356
276569
  }
@@ -276399,6 +276612,7 @@ async function dropAllTables(db) {
276399
276612
  "materializations",
276400
276613
  "packages",
276401
276614
  "connections",
276615
+ "storage_destinations",
276402
276616
  "environments",
276403
276617
  "themes",
276404
276618
  "entity_embeddings"
@@ -276647,7 +276861,7 @@ function splitManifestEntries(entries, source) {
276647
276861
  });
276648
276862
  continue;
276649
276863
  }
276650
- if (entry.storageConnectionName) {
276864
+ if (entry.storageDestinationName) {
276651
276865
  storageEntries[sourceEntityId] = entry;
276652
276866
  continue;
276653
276867
  }
@@ -276741,7 +276955,7 @@ import {
276741
276955
  function deriveServeBindings(entries) {
276742
276956
  const bindings = [];
276743
276957
  for (const entry of Object.values(entries)) {
276744
- if (!entry.sourceName || !entry.storageConnectionName || !entry.physicalTableName) {
276958
+ if (!entry.sourceName || !entry.storageDestinationName || !entry.physicalTableName) {
276745
276959
  continue;
276746
276960
  }
276747
276961
  const schema = (entry.schema ?? []).filter((c) => c.name && c.type).map((c) => ({ name: c.name, type: c.type }));
@@ -276749,9 +276963,9 @@ function deriveServeBindings(entries) {
276749
276963
  continue;
276750
276964
  bindings.push({
276751
276965
  sourceName: entry.sourceName,
276752
- connectionName: entry.storageConnectionName,
276966
+ destinationName: entry.storageDestinationName,
276753
276967
  virtualHandle: entry.sourceEntityId,
276754
- tablePath: `${entry.storageConnectionName}.${entry.physicalTableName}`,
276968
+ tablePath: `${entry.storageDestinationName}.${entry.physicalTableName}`,
276755
276969
  schema,
276756
276970
  freshAsOf: entry.dataAsOf,
276757
276971
  freshnessWindowSeconds: entry.freshnessWindowSeconds,
@@ -276830,7 +277044,7 @@ function buildServeShapeModel(sourceName, binding) {
276830
277044
  type: ${shapeTypeName} is {
276831
277045
  ${fields}
276832
277046
  }
276833
- source: ${sourceName} is ${binding.connectionName}.virtual('${binding.virtualHandle}')::${shapeTypeName}
277047
+ source: ${sourceName} is ${binding.destinationName}.virtual('${binding.virtualHandle}')::${shapeTypeName}
276834
277048
  `;
276835
277049
  return { modelText, shapeTypeName };
276836
277050
  }
@@ -276838,7 +277052,7 @@ function serveShapeFragment(binding) {
276838
277052
  const shapeTypeName = `${binding.sourceName}__shape`;
276839
277053
  const fields = binding.schema.map((c) => ` ${emitFieldName(c.name)}::${duckdbTypeToMalloy(c.type)}`).join(`,
276840
277054
  `);
276841
- let source = `source: ${binding.sourceName} is ` + `${binding.connectionName}.virtual('${binding.virtualHandle}')::${shapeTypeName}`;
277055
+ let source = `source: ${binding.sourceName} is ` + `${binding.destinationName}.virtual('${binding.virtualHandle}')::${shapeTypeName}`;
276842
277056
  const refinements = binding.refinements ?? [];
276843
277057
  const lines = [];
276844
277058
  for (const r of refinements) {
@@ -277023,10 +277237,10 @@ function extractViews(fields, liftText) {
277023
277237
  function buildVirtualMap(bindings) {
277024
277238
  const map = new Map;
277025
277239
  for (const b of bindings) {
277026
- let inner = map.get(b.connectionName);
277240
+ let inner = map.get(b.destinationName);
277027
277241
  if (!inner) {
277028
277242
  inner = new Map;
277029
- map.set(b.connectionName, inner);
277243
+ map.set(b.destinationName, inner);
277030
277244
  }
277031
277245
  inner.set(b.virtualHandle, quoteManifestTablePath(b.tablePath, "duckdb"));
277032
277246
  }
@@ -277894,7 +278108,7 @@ class Model {
277894
278108
  modelMaterializer;
277895
278109
  modelDef;
277896
278110
  modelInfo;
277897
- serveMalloyConfig;
278111
+ serveDestinationConfig;
277898
278112
  serveBindings = [];
277899
278113
  serveShapeCache;
277900
278114
  sources;
@@ -278282,7 +278496,6 @@ class Model {
278282
278496
  const modelMaterializer = runtime._loadModelFromModelDef(modelDef);
278283
278497
  const runnableNotebookCells = data.modelType === "notebook" ? hydrateNotebookCells(runtime, data.notebookCells) : undefined;
278284
278498
  const model = new Model(packageName, data.modelPath, dataStyles, data.modelType, modelMaterializer, modelDef, sources, queries, sourceInfos, runnableNotebookCells, undefined, filterMap, givens, modelInfo);
278285
- model.setServeMalloyConfig(malloyConfig);
278286
278499
  return model;
278287
278500
  }
278288
278501
  static fromCompilationError(packageName, modelPath, modelType, error) {
@@ -278481,8 +278694,12 @@ class Model {
278481
278694
  throw new ModelNotFoundError(`${this.modelPath} is not a valid notebook name. Notebook files must end in .malloynb.`);
278482
278695
  }
278483
278696
  }
278484
- setServeMalloyConfig(config) {
278485
- this.serveMalloyConfig = config;
278697
+ setServeDestinationConfig(provider) {
278698
+ this.serveDestinationConfig = provider;
278699
+ this.invalidateServeShapeCache();
278700
+ }
278701
+ invalidateServeShapeCache() {
278702
+ this.serveShapeCache = undefined;
278486
278703
  }
278487
278704
  setServeBindings(bindings) {
278488
278705
  this.serveBindings = bindings;
@@ -278502,7 +278719,7 @@ class Model {
278502
278719
  if (freshBindings.length === 0) {
278503
278720
  throw new Error("no fresh storage serve bindings for this query");
278504
278721
  }
278505
- const key = freshBindings.map((b) => `${b.sourceName}@${b.connectionName}/${b.virtualHandle}`).sort().join("|");
278722
+ const key = freshBindings.map((b) => `${b.sourceName}@${b.destinationName}/${b.virtualHandle}`).sort().join("|");
278506
278723
  if (!this.serveShapeCache || this.serveShapeCache.key !== key) {
278507
278724
  this.serveShapeCache = {
278508
278725
  key,
@@ -278553,7 +278770,7 @@ class Model {
278553
278770
  const url2 = `${root}shape.malloy`;
278554
278771
  const runtime = new Runtime2({
278555
278772
  urlReader: new InMemoryURLReader2(new Map([[url2, modelText]])),
278556
- config: Model.toMalloyConfig(this.serveMalloyConfig)
278773
+ config: restrictMalloyConfigToConnections(this.serveDestinationConfig(), new Set(bindings.map((binding) => binding.destinationName)))
278557
278774
  });
278558
278775
  return runtime.loadModel(new URL(url2), {
278559
278776
  importBaseURL: new URL(root)
@@ -278650,7 +278867,7 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`;
278650
278867
  }
278651
278868
  runnable = this.modelMaterializer.loadRestrictedQuery(queryString);
278652
278869
  liveRunnable = runnable;
278653
- if (getPersistStorageMode() === "on" && this.serveBindings.length > 0 && this.serveMalloyConfig) {
278870
+ if (getPersistStorageMode() === "on" && this.serveBindings.length > 0 && this.serveDestinationConfig) {
278654
278871
  try {
278655
278872
  const shaped = await this.loadServeShapeQuery(queryString);
278656
278873
  runnable = shaped.runnable;
@@ -279681,6 +279898,7 @@ class Package {
279681
279898
  models = new Map;
279682
279899
  packagePath;
279683
279900
  malloyConfig;
279901
+ serveDestinationConfig;
279684
279902
  buildManifestEntries;
279685
279903
  freshnessEntries;
279686
279904
  storageServeBindings = [];
@@ -279940,7 +280158,7 @@ class Package {
279940
280158
  if (this.storageServeBindings.length > 0) {
279941
280159
  metadata.storageServeBindings = this.storageServeBindings.map((b) => ({
279942
280160
  sourceName: b.sourceName,
279943
- storageConnectionName: b.connectionName,
280161
+ storageDestinationName: b.destinationName,
279944
280162
  tablePath: b.tablePath
279945
280163
  }));
279946
280164
  }
@@ -280016,6 +280234,22 @@ class Package {
280016
280234
  model.setServeBindings(this.storageServeBindings);
280017
280235
  }
280018
280236
  }
280237
+ setServeDestinationConfig(provider) {
280238
+ this.serveDestinationConfig = provider;
280239
+ this.pushServeDestinationConfigToModels();
280240
+ }
280241
+ pushServeDestinationConfigToModels() {
280242
+ if (!this.serveDestinationConfig)
280243
+ return;
280244
+ for (const model of this.models.values()) {
280245
+ model.setServeDestinationConfig(this.serveDestinationConfig);
280246
+ }
280247
+ }
280248
+ invalidateServeShapes() {
280249
+ for (const model of this.models.values()) {
280250
+ model.invalidateServeShapeCache();
280251
+ }
280252
+ }
280019
280253
  getInvalidExplores(exploresOverride) {
280020
280254
  const declared = exploresOverride ?? this.packageMetadata.explores;
280021
280255
  if (!declared || declared.length === 0)
@@ -280258,6 +280492,7 @@ class Package {
280258
280492
  }
280259
280493
  this.models = nextModels;
280260
280494
  this.pushStorageServeBindingsToModels();
280495
+ this.pushServeDestinationConfigToModels();
280261
280496
  this.renderTagWarnings = renderTagWarnings;
280262
280497
  this.manifestWarnings = outcome.packageMetadata.manifestWarnings ?? [];
280263
280498
  this.packageMetadata.explores = outcome.packageMetadata.explores;
@@ -280435,6 +280670,11 @@ var STAGING_DIR_NAME = ".staging";
280435
280670
  var RETIRED_DIR_NAME = ".retired";
280436
280671
  var MANIFEST_FETCH_TIMEOUT_MS = 15000;
280437
280672
  var RETIRED_CONNECTION_DRAIN_MS = 30000;
280673
+ var DESTINATION_READ_FIELDS = new Set([
280674
+ "name",
280675
+ "type",
280676
+ "resource"
280677
+ ]);
280438
280678
  var queryAdmissionRejectionsCounter = null;
280439
280679
  var packageAdmissionRejectionsCounter = null;
280440
280680
  function getQueryAdmissionRejectionsCounter() {
@@ -280464,6 +280704,9 @@ class Environment {
280464
280704
  connectionMutex = new Mutex;
280465
280705
  retiredConnectionGenerations = new Set;
280466
280706
  apiConnections;
280707
+ destinations;
280708
+ destinationMalloyConfig;
280709
+ destinationsAuthoritative = true;
280467
280710
  environmentPath;
280468
280711
  environmentName;
280469
280712
  storageBindingResolver;
@@ -280475,12 +280718,14 @@ class Environment {
280475
280718
  getEnvironmentName() {
280476
280719
  return this.environmentName;
280477
280720
  }
280478
- constructor(environmentName, environmentPath, malloyConfig, apiConnections) {
280721
+ constructor(environmentName, environmentPath, malloyConfig, apiConnections, storageDestinations = []) {
280479
280722
  assertSafeEnvironmentPath(environmentPath);
280480
280723
  this.environmentName = environmentName;
280481
280724
  this.environmentPath = environmentPath;
280482
280725
  this.malloyConfig = malloyConfig;
280483
280726
  this.apiConnections = apiConnections;
280727
+ this.destinations = [];
280728
+ this.setStorageDestinations(storageDestinations);
280484
280729
  this.metadata = {
280485
280730
  resource: `${API_PREFIX}/environments/${this.environmentName}`,
280486
280731
  name: this.environmentName,
@@ -280501,6 +280746,11 @@ class Environment {
280501
280746
  }
280502
280747
  }
280503
280748
  async update(payload) {
280749
+ if (payload.storageDestinations !== undefined) {
280750
+ this.setStorageDestinations(payload.storageDestinations, {
280751
+ rejectInvalid: true
280752
+ });
280753
+ }
280504
280754
  if (payload.readme !== undefined) {
280505
280755
  this.metadata.readme = payload.readme;
280506
280756
  await this.writeEnvironmentReadme(payload.readme);
@@ -280520,7 +280770,7 @@ class Environment {
280520
280770
  }
280521
280771
  return this;
280522
280772
  }
280523
- static async create(environmentName, environmentPath, connections) {
280773
+ static async create(environmentName, environmentPath, connections, storageDestinations = []) {
280524
280774
  assertSafeEnvironmentPath(environmentPath);
280525
280775
  if (!(await fs7.promises.stat(environmentPath))?.isDirectory()) {
280526
280776
  throw new EnvironmentNotFoundError(`Environment path ${environmentPath} not found`);
@@ -280533,7 +280783,7 @@ class Environment {
280533
280783
  type: c.type
280534
280784
  }))
280535
280785
  });
280536
- const environment = new Environment(environmentName, environmentPath, malloyConfig, malloyConfig.apiConnections);
280786
+ const environment = new Environment(environmentName, environmentPath, malloyConfig, malloyConfig.apiConnections, storageDestinations);
280537
280787
  await Environment.sweepStaleInstallDirs(environmentPath);
280538
280788
  return environment;
280539
280789
  }
@@ -280633,6 +280883,76 @@ ${source}` : source;
280633
280883
  }
280634
280884
  return connection;
280635
280885
  }
280886
+ setStorageDestinations(storageDestinations, { rejectInvalid = false } = {}) {
280887
+ const previous = this.destinations;
280888
+ const requested = Array.isArray(storageDestinations) ? storageDestinations.map((destination) => this.resolveDestinationReference(destination)) : storageDestinations;
280889
+ this.destinations = rejectInvalid ? processStorageDestinationsOrThrow(requested) : processStorageDestinations(requested);
280890
+ this.destinationsAuthoritative = true;
280891
+ if (this.destinationMalloyConfig && storageDestinationsEqual(previous, this.destinations)) {
280892
+ return;
280893
+ }
280894
+ this.rebuildDestinationMalloyConfig();
280895
+ if (previous.length > 0 || this.destinations.length > 0) {
280896
+ logger.info(`Environment ${this.environmentName} has ${this.destinations.length} storage destination(s)`, { destinations: this.destinations.map((d) => d.name) });
280897
+ }
280898
+ }
280899
+ resolveDestinationReference(destination) {
280900
+ if (!destination || typeof destination !== "object") {
280901
+ return destination;
280902
+ }
280903
+ const carriesOnlyReportedFields = Object.keys(destination).every((field) => DESTINATION_READ_FIELDS.has(field));
280904
+ if (!carriesOnlyReportedFields) {
280905
+ return destination;
280906
+ }
280907
+ return this.destinations.find((stored) => stored.name === destination.name) ?? destination;
280908
+ }
280909
+ attachDestinationServeConfig(_package) {
280910
+ _package.setServeDestinationConfig(() => this.getStorageDestinationMalloyConfig());
280911
+ }
280912
+ rebuildDestinationMalloyConfig() {
280913
+ const previous = this.destinationMalloyConfig;
280914
+ try {
280915
+ const destinationRoot = storageDestinationRoot(this.environmentPath);
280916
+ if (this.destinations.length > 0) {
280917
+ fs7.mkdirSync(destinationRoot, { recursive: true });
280918
+ }
280919
+ this.destinationMalloyConfig = buildEnvironmentMalloyConfig(this.destinations, destinationRoot);
280920
+ } catch (error) {
280921
+ logger.error(`Failed to assemble storage destinations for environment ${this.environmentName}; serving without them`, { error });
280922
+ this.destinationMalloyConfig = buildEnvironmentMalloyConfig([], storageDestinationRoot(this.environmentPath));
280923
+ }
280924
+ if (previous && previous !== this.destinationMalloyConfig) {
280925
+ this.retireConnectionGeneration(`environment ${this.environmentName} destinations`, () => previous.releaseConnections());
280926
+ this.invalidateServeShapes();
280927
+ }
280928
+ }
280929
+ invalidateServeShapes() {
280930
+ for (const _package of this.packages.values()) {
280931
+ _package.invalidateServeShapes();
280932
+ }
280933
+ }
280934
+ getStorageDestinationMalloyConfig() {
280935
+ return this.destinationMalloyConfig.malloyConfig;
280936
+ }
280937
+ markStorageDestinationsUnknown() {
280938
+ this.destinationsAuthoritative = false;
280939
+ }
280940
+ hasAuthoritativeStorageDestinations() {
280941
+ return this.destinationsAuthoritative;
280942
+ }
280943
+ listStorageDestinations() {
280944
+ return this.destinations;
280945
+ }
280946
+ hasStorageDestination(destinationName) {
280947
+ return this.destinations.some((destination) => destination.name === destinationName);
280948
+ }
280949
+ getStorageDestination(destinationName) {
280950
+ const destination = this.destinations.find((destination2) => destination2.name === destinationName);
280951
+ if (!destination) {
280952
+ throw new DestinationNotFoundError(`Storage destination ${destinationName} not found`);
280953
+ }
280954
+ return destination;
280955
+ }
280636
280956
  async getMalloyConnection(connectionName) {
280637
280957
  return this.malloyConfig.malloyConfig.connections.lookupConnection(connectionName);
280638
280958
  }
@@ -280804,6 +281124,7 @@ ${source}` : source;
280804
281124
  logger.debug(`Loading package ${packageName}...`);
280805
281125
  const packagePath = safeJoinUnderRoot(this.environmentPath, packageName);
280806
281126
  const _package = await Package.create(this.environmentName, packageName, packagePath, () => this.malloyConfig.malloyConfig);
281127
+ this.attachDestinationServeConfig(_package);
280807
281128
  await this.bindManifestIfConfigured(_package);
280808
281129
  await this.rebindServeBindingsFromLocalStore(_package);
280809
281130
  if (existingPackage !== undefined && reload) {
@@ -280845,7 +281166,9 @@ ${source}` : source;
280845
281166
  }
280846
281167
  this.setPackageStatus(packageName, "loading" /* LOADING */);
280847
281168
  try {
280848
- this.packages.set(packageName, await Package.create(this.environmentName, packageName, packagePath, () => this.malloyConfig.malloyConfig));
281169
+ const addedPackage = await Package.create(this.environmentName, packageName, packagePath, () => this.malloyConfig.malloyConfig);
281170
+ this.attachDestinationServeConfig(addedPackage);
281171
+ this.packages.set(packageName, addedPackage);
280849
281172
  } catch (error) {
280850
281173
  logger.error("Error adding package", { error });
280851
281174
  this.deletePackageStatus(packageName);
@@ -280902,6 +281225,7 @@ ${source}` : source;
280902
281225
  await fs7.promises.rename(stagingPath, canonicalPath);
280903
281226
  this.setPackageStatus(packageName, "loading" /* LOADING */);
280904
281227
  newPackage = await Package.create(this.environmentName, packageName, canonicalPath, () => this.malloyConfig.malloyConfig, true);
281228
+ this.attachDestinationServeConfig(newPackage);
280905
281229
  const validationMsg = validate?.(newPackage);
280906
281230
  if (validationMsg) {
280907
281231
  throw new BadRequestError(validationMsg);
@@ -281312,6 +281636,13 @@ ${source}` : source;
281312
281636
  } catch (error) {
281313
281637
  logger.error(`Error closing connections for environment ${this.environmentName}`, { error });
281314
281638
  }
281639
+ try {
281640
+ await this.destinationMalloyConfig.releaseConnections();
281641
+ } catch (error) {
281642
+ logger.error(`Error closing storage destinations for environment ${this.environmentName}`, { error: error instanceof Error ? error.message : String(error) });
281643
+ }
281644
+ this.destinations = [];
281645
+ this.destinationsAuthoritative = false;
281315
281646
  await this.releaseAllRetiredConnectionGenerations();
281316
281647
  this.apiConnections = [];
281317
281648
  logger.info(`Closed all connections for environment ${this.environmentName}`);
@@ -281320,6 +281651,10 @@ ${source}` : source;
281320
281651
  return {
281321
281652
  ...this.metadata,
281322
281653
  connections: this.listApiConnections(),
281654
+ storageDestinations: this.destinations.map(({ name, type }) => ({
281655
+ name,
281656
+ type
281657
+ })),
281323
281658
  packages: await this.listPackages()
281324
281659
  };
281325
281660
  }
@@ -281609,6 +281944,9 @@ class EnvironmentStore {
281609
281944
  name: environment.name,
281610
281945
  resource: `${API_PREFIX}/environments/${environment.name}`,
281611
281946
  connections: environment.connections,
281947
+ ...environment.storageDestinations.length ? {
281948
+ storageDestinations: environment.storageDestinations
281949
+ } : {},
281612
281950
  packages: environment.packages
281613
281951
  }, true);
281614
281952
  } catch (error) {
@@ -281641,15 +281979,16 @@ class EnvironmentStore {
281641
281979
  await Promise.all(existingEnvironments.map(async (dbEnvironment) => {
281642
281980
  try {
281643
281981
  const environmentExists = await fs8.promises.access(dbEnvironment.path).then(() => true).catch(() => false);
281982
+ const environmentConfig = environmentManifest.environments.find((p) => p.name === dbEnvironment.name);
281644
281983
  if (!environmentExists) {
281645
- const environmentConfig = environmentManifest.environments.find((p) => p.name === dbEnvironment.name);
281646
281984
  if (environmentConfig) {
281647
281985
  const environmentInstance2 = await this.addEnvironment({
281648
281986
  name: environmentConfig.name,
281649
281987
  resource: `${API_PREFIX}/environments/${environmentConfig.name}`,
281650
281988
  connections: environmentConfig.connections,
281989
+ storageDestinations: environmentConfig.storageDestinations,
281651
281990
  packages: environmentConfig.packages
281652
- }, true);
281991
+ }, true, { storageDestinationsAreFallback: true });
281653
281992
  await repository.updateEnvironment(dbEnvironment.id, {
281654
281993
  path: environmentInstance2.metadata.location
281655
281994
  });
@@ -281660,12 +281999,30 @@ class EnvironmentStore {
281660
281999
  }
281661
282000
  }
281662
282001
  const connections = await repository.listConnections(dbEnvironment.id);
282002
+ let destinations = environmentConfig?.storageDestinations;
282003
+ let destinationsRead = true;
282004
+ try {
282005
+ const stored = await repository.listStorageDestinations(dbEnvironment.id);
282006
+ if (stored.length) {
282007
+ destinations = stored.map((row) => ({
282008
+ name: row.name,
282009
+ type: row.type,
282010
+ ...row.config
282011
+ }));
282012
+ }
282013
+ } catch (error) {
282014
+ destinationsRead = false;
282015
+ logger.error(`Error reading storage destinations for "${dbEnvironment.name}"; continuing without the stored ones`, { error });
282016
+ }
281663
282017
  const environmentInstance = await Environment.create(dbEnvironment.name, dbEnvironment.path, connections.map((conn) => ({
281664
282018
  name: conn.name,
281665
282019
  type: conn.type,
281666
282020
  resource: `${API_PREFIX}/connections/${conn.name}`,
281667
282021
  ...conn.config
281668
- })));
282022
+ })), destinations);
282023
+ if (!destinationsRead) {
282024
+ environmentInstance.markStorageDestinationsUnknown();
282025
+ }
281669
282026
  environmentInstance.setMemoryGovernor(this.memoryGovernor);
281670
282027
  const envId = dbEnvironment.id;
281671
282028
  environmentInstance.setStorageBindingResolver(async (packageName) => {
@@ -281732,6 +282089,7 @@ class EnvironmentStore {
281732
282089
  const repository = this.storageManager.getRepository();
281733
282090
  const dbEnvironment = await this.addEnvironmentMetadata(environment, repository);
281734
282091
  await this.addConnections(environment, dbEnvironment.id, repository);
282092
+ await this.syncStorageDestinations(environment, dbEnvironment.id, repository);
281735
282093
  await this.addPackages(environment, dbEnvironment.id, repository);
281736
282094
  logger.info(`Synced environment "${environmentName}" to database`);
281737
282095
  }
@@ -281832,6 +282190,38 @@ class EnvironmentStore {
281832
282190
  logger.error(`Error syncing connections for "${environmentName}":`, error);
281833
282191
  }
281834
282192
  }
282193
+ async syncStorageDestinations(environment, environmentId, repository) {
282194
+ try {
282195
+ const destinations = environment.listStorageDestinations();
282196
+ const kept = new Set;
282197
+ for (const destination of destinations) {
282198
+ if (!destination.name || !destination.type) {
282199
+ continue;
282200
+ }
282201
+ kept.add(destination.name);
282202
+ await repository.upsertStorageDestination({
282203
+ environmentId,
282204
+ name: destination.name,
282205
+ type: destination.type,
282206
+ config: destination
282207
+ });
282208
+ }
282209
+ if (!environment.hasAuthoritativeStorageDestinations()) {
282210
+ logger.warn(`Not reconciling stored storage destinations for "${environment.metadata?.name}": this environment's destinations were never read, so the in-memory list is not the desired state`);
282211
+ return;
282212
+ }
282213
+ const stored = await repository.listStorageDestinations(environmentId);
282214
+ for (const row of stored) {
282215
+ if (!kept.has(row.name)) {
282216
+ await repository.deleteStorageDestination(row.id);
282217
+ logger.info(`Removed storage destination ${row.name} from environment ${environment.metadata?.name}`);
282218
+ }
282219
+ }
282220
+ } catch (err) {
282221
+ const error = err;
282222
+ logger.error(`Error syncing storage destinations for "${environment.metadata?.name}":`, error);
282223
+ }
282224
+ }
281835
282225
  async addConnection(conn, environmentId, repository) {
281836
282226
  if (!conn.name) {
281837
282227
  logger.warn("Skipping connection with undefined name");
@@ -282055,11 +282445,14 @@ class EnvironmentStore {
282055
282445
  return await this.addEnvironment({
282056
282446
  name: environmentName,
282057
282447
  resource: `${API_PREFIX}/environments/${environmentName}`,
282058
- connections: environmentConfig?.connections || []
282448
+ connections: environmentConfig?.connections || [],
282449
+ ...environmentConfig?.storageDestinations?.length ? {
282450
+ storageDestinations: environmentConfig.storageDestinations
282451
+ } : {}
282059
282452
  });
282060
282453
  });
282061
282454
  }
282062
- async addEnvironment(environment, skipInitialization = false) {
282455
+ async addEnvironment(environment, skipInitialization = false, { storageDestinationsAreFallback = false } = {}) {
282063
282456
  if (!skipInitialization) {
282064
282457
  await this.finishedInitialization;
282065
282458
  }
@@ -282075,6 +282468,9 @@ class EnvironmentStore {
282075
282468
  if (existingEnvironment) {
282076
282469
  const updatedEnvironment = await existingEnvironment.update(environment);
282077
282470
  this.environments.set(environmentName, updatedEnvironment);
282471
+ if (storageDestinationsAreFallback) {
282472
+ updatedEnvironment.markStorageDestinationsUnknown();
282473
+ }
282078
282474
  await this.addEnvironmentToDatabase(updatedEnvironment);
282079
282475
  return updatedEnvironment;
282080
282476
  }
@@ -282094,7 +282490,7 @@ class EnvironmentStore {
282094
282490
  } else {
282095
282491
  absoluteEnvironmentPath = await this.scaffoldEnvironment(environment);
282096
282492
  }
282097
- const newEnvironment = await Environment.create(environmentName, absoluteEnvironmentPath, environment.connections || []);
282493
+ const newEnvironment = await Environment.create(environmentName, absoluteEnvironmentPath, environment.connections || [], environment.storageDestinations || []);
282098
282494
  newEnvironment.setMemoryGovernor(this.memoryGovernor);
282099
282495
  if (!newEnvironment.metadata)
282100
282496
  newEnvironment.metadata = {};
@@ -282109,6 +282505,9 @@ class EnvironmentStore {
282109
282505
  newEnvironment.setPackageStatus(_package.name, "serving" /* SERVING */);
282110
282506
  }
282111
282507
  });
282508
+ if (storageDestinationsAreFallback) {
282509
+ newEnvironment.markStorageDestinationsUnknown();
282510
+ }
282112
282511
  await this.addEnvironmentToDatabase(newEnvironment);
282113
282512
  return newEnvironment;
282114
282513
  }
@@ -282207,7 +282606,8 @@ class EnvironmentStore {
282207
282606
  location: `./${entry.name}`
282208
282607
  }
282209
282608
  ],
282210
- connections: []
282609
+ connections: [],
282610
+ storageDestinations: []
282211
282611
  });
282212
282612
  }
282213
282613
  }
@@ -289910,7 +290310,8 @@ function registerLegacyRoutes(app, controllers) {
289910
290310
  });
289911
290311
  app.post(`${LEGACY_API_PREFIX}/projects`, async (req, res) => {
289912
290312
  try {
289913
- logger.info("Adding project", { body: req.body });
290313
+ logger.info("Adding project", { body: redactSensitive(req.body) });
290314
+ processStorageDestinationsOrThrow(req.body?.storageDestinations ?? []);
289914
290315
  const environment = await environmentStore.addEnvironment(req.body);
289915
290316
  res.status(200).json(await environment.serialize());
289916
290317
  } catch (error) {
@@ -290523,7 +290924,7 @@ import {
290523
290924
  InMemoryURLReader as InMemoryURLReader3,
290524
290925
  Runtime as Runtime4
290525
290926
  } from "@malloydata/malloy";
290526
- import { mkdtempSync, rmSync } from "node:fs";
290927
+ import { mkdirSync as mkdirSync2, mkdtempSync, rmSync } from "node:fs";
290527
290928
  import os4 from "node:os";
290528
290929
  import path12 from "node:path";
290529
290930
  var sharedGateSession;
@@ -290616,7 +291017,7 @@ async function buildSourceIntoStorage(params) {
290616
291017
  const target = quoteManifestTablePath(`${destinationName}.${physicalTableName}`, "duckdb");
290617
291018
  const passthrough = wrapPassthrough(sourceType, federated.handle, buildSQL);
290618
291019
  const schema = await createTableAndDescribe(session, target, passthrough);
290619
- return { storageConnectionName: destinationName, schema };
291020
+ return { storageDestinationName: destinationName, schema };
290620
291021
  } finally {
290621
291022
  await dispose();
290622
291023
  }
@@ -290668,7 +291069,7 @@ async function buildDownstreamIntoStorage(params) {
290668
291069
  const sql = projectToPublicColumns(downstream, downstream.getSQL({ virtualMap }));
290669
291070
  const target = quoteManifestTablePath(`${destinationName}.${physicalTableName}`, "duckdb");
290670
291071
  const schema = await createTableAndDescribe(session, target, sql);
290671
- return { storageConnectionName: destinationName, schema };
291072
+ return { storageDestinationName: destinationName, schema };
290672
291073
  } finally {
290673
291074
  await dispose();
290674
291075
  }
@@ -290677,7 +291078,7 @@ async function assertStorageServeShapeCompiles(params) {
290677
291078
  const { destinationName, sourceName, virtualHandle, physicalTableName } = params;
290678
291079
  const binding = {
290679
291080
  sourceName,
290680
- connectionName: destinationName,
291081
+ destinationName,
290681
291082
  virtualHandle,
290682
291083
  tablePath: `${destinationName}.${physicalTableName}`,
290683
291084
  schema: params.schema.filter((c) => c.name && c.type).map((c) => ({ name: c.name, type: c.type }))
@@ -290713,7 +291114,9 @@ async function attachDestinationReadWrite(session, destinationName, destinationC
290713
291114
  await attachDuckLakeReadWrite(session, destinationName, cfg);
290714
291115
  return;
290715
291116
  }
290716
- const dbPath = path12.join(environmentPath, `${destinationName}.duckdb`);
291117
+ const destinationRoot = storageDestinationRoot(environmentPath);
291118
+ mkdirSync2(destinationRoot, { recursive: true });
291119
+ const dbPath = path12.join(destinationRoot, `${destinationName}.duckdb`);
290717
291120
  await session.runSQL(`ATTACH '${escapeSQL(dbPath)}' AS ${quoteIdentifier(destinationName, "duckdb")}`);
290718
291121
  }
290719
291122
  function assertSupportedDestination(destinationName, destinationConnection) {
@@ -290802,7 +291205,7 @@ function manifestExcludingStorage(manifest, builtEntries) {
290802
291205
  reduced.strict = manifest.strict;
290803
291206
  const quoted = manifest.buildManifest.entries;
290804
291207
  for (const [id, entry] of Object.entries(builtEntries)) {
290805
- if (!entry.storageConnectionName && quoted[id]) {
291208
+ if (!entry.storageDestinationName && quoted[id]) {
290806
291209
  reduced.update(id, { tableName: quoted[id].tableName });
290807
291210
  }
290808
291211
  }
@@ -291050,7 +291453,7 @@ class MaterializationService {
291050
291453
  continue;
291051
291454
  seen.add(sourceEntityId);
291052
291455
  const prior = priorEntries[sourceEntityId];
291053
- if (prior && prior.physicalTableName && (prior.storageConnectionName ?? undefined) === destination) {
291456
+ if (prior && prior.physicalTableName && (prior.storageDestinationName ?? undefined) === destination) {
291054
291457
  carried[sourceEntityId] = prior;
291055
291458
  continue;
291056
291459
  }
@@ -291159,7 +291562,7 @@ class MaterializationService {
291159
291562
  async autoLoadManifest(environment, packageName, entries) {
291160
291563
  const manifestEntries = {};
291161
291564
  for (const [sourceEntityId, entry] of Object.entries(entries)) {
291162
- if (entry.physicalTableName && !entry.storageConnectionName) {
291565
+ if (entry.physicalTableName && !entry.storageDestinationName) {
291163
291566
  manifestEntries[sourceEntityId] = {
291164
291567
  tableName: entry.physicalTableName,
291165
291568
  connectionName: entry.connectionName
@@ -291254,7 +291657,7 @@ class MaterializationService {
291254
291657
  }
291255
291658
  const entry = await this.buildOneSource(persistSource, instruction, connection, connectionDigests, manifest, environment, entries, buildMetadata);
291256
291659
  entries[sourceEntityId] = entry;
291257
- if (entry.storageConnectionName)
291660
+ if (entry.storageDestinationName)
291258
291661
  builtThisRun.push(entry);
291259
291662
  }
291260
291663
  }
@@ -291266,6 +291669,14 @@ class MaterializationService {
291266
291669
  }
291267
291670
  return entries;
291268
291671
  }
291672
+ destinationForCleanup(environment, destinationName) {
291673
+ try {
291674
+ return environment.getStorageDestination(destinationName);
291675
+ } catch (error) {
291676
+ logger.warn("Skipping a table drop: its storage destination is no longer configured", { destinationName, error: errMessage(error) });
291677
+ return;
291678
+ }
291679
+ }
291269
291680
  async reclaimStorageTablesFromFailedRun(builtThisRun, environment, owner) {
291270
291681
  if (builtThisRun.length === 0)
291271
291682
  return;
@@ -291277,14 +291688,14 @@ class MaterializationService {
291277
291688
  if (other.status !== "MANIFEST_FILE_READY")
291278
291689
  continue;
291279
291690
  for (const e of Object.values(other.manifest?.entries ?? {})) {
291280
- const dest = e.storageConnectionName ?? e.connectionName;
291691
+ const dest = e.storageDestinationName ?? e.connectionName;
291281
291692
  if (dest && e.physicalTableName) {
291282
291693
  stillReferenced.add(tableKey(dest, e.physicalTableName));
291283
291694
  }
291284
291695
  }
291285
291696
  }
291286
291697
  for (const entry of builtThisRun) {
291287
- const dest = entry.storageConnectionName;
291698
+ const dest = entry.storageDestinationName;
291288
291699
  const table = entry.physicalTableName;
291289
291700
  if (!dest || !table)
291290
291701
  continue;
@@ -291292,10 +291703,13 @@ class MaterializationService {
291292
291703
  logger.info("Keeping a table from a failed run: a live manifest still serves it", { destinationName: dest, physicalTableName: table });
291293
291704
  continue;
291294
291705
  }
291706
+ const destinationConnection = this.destinationForCleanup(environment, dest);
291707
+ if (!destinationConnection)
291708
+ continue;
291295
291709
  try {
291296
291710
  await dropStorageTable({
291297
291711
  destinationName: dest,
291298
- destinationConnection: environment.getApiConnection(dest),
291712
+ destinationConnection,
291299
291713
  physicalTableName: table,
291300
291714
  environmentPath: environment.getEnvironmentPath()
291301
291715
  });
@@ -291309,7 +291723,7 @@ class MaterializationService {
291309
291723
  logger.warn("Failed to reclaim a table stranded by a failed build", {
291310
291724
  destinationName: dest,
291311
291725
  physicalTableName: table,
291312
- error: redactConnectionSecrets(errMessage(dropErr), environment.getApiConnection(dest))
291726
+ error: redactConnectionSecrets(errMessage(dropErr), destinationConnection)
291313
291727
  });
291314
291728
  }
291315
291729
  }
@@ -291415,7 +291829,7 @@ class MaterializationService {
291415
291829
  const physicalTableName = instruction.physicalTableName;
291416
291830
  const destinationName = instruction.destination;
291417
291831
  const sourceConnection = environment.getApiConnection(persistSource.connectionName);
291418
- const destinationConnection = environment.getApiConnection(destinationName);
291832
+ const destinationConnection = environment.getStorageDestination(destinationName);
291419
291833
  const startTime = performance.now();
291420
291834
  let result;
291421
291835
  if (dependsOnStorageUpstream) {
@@ -291494,7 +291908,7 @@ class MaterializationService {
291494
291908
  recordSourceBuildDuration(durationMs, "storage");
291495
291909
  logger.info(`Built materialized source ${persistSource.name} into storage`, {
291496
291910
  physicalTableName,
291497
- storageConnectionName: result.storageConnectionName,
291911
+ storageDestinationName: result.storageDestinationName,
291498
291912
  columns: result.schema.length,
291499
291913
  durationMs
291500
291914
  });
@@ -291504,14 +291918,14 @@ class MaterializationService {
291504
291918
  materializedTableId: instruction.materializedTableId,
291505
291919
  physicalTableName,
291506
291920
  connectionName: persistSource.connectionName,
291507
- storageConnectionName: result.storageConnectionName,
291921
+ storageDestinationName: result.storageDestinationName,
291508
291922
  schema: result.schema,
291509
291923
  realization: instruction.realization,
291510
291924
  rowCount: null
291511
291925
  };
291512
291926
  }
291513
291927
  async buildDownstreamViaParents(persistSource, destinationName, destinationConnection, builtEntries, environment, physicalTableName) {
291514
- const upstreams = deriveServeBindings(builtEntries).filter((b) => b.connectionName === destinationName);
291928
+ const upstreams = deriveServeBindings(builtEntries).filter((b) => b.destinationName === destinationName);
291515
291929
  if (upstreams.length === 0) {
291516
291930
  throw new MaterializationEligibilityError({
291517
291931
  message: "no materialized upstream is available in this destination to build on"
@@ -291618,7 +292032,7 @@ class MaterializationService {
291618
292032
  if (other.status !== "MANIFEST_FILE_READY")
291619
292033
  continue;
291620
292034
  for (const e of Object.values(other.manifest?.entries ?? {})) {
291621
- const dest = e.storageConnectionName ?? e.connectionName;
292035
+ const dest = e.storageDestinationName ?? e.connectionName;
291622
292036
  if (dest && e.physicalTableName) {
291623
292037
  stillReferenced.add(tableKey(dest, e.physicalTableName));
291624
292038
  }
@@ -291634,7 +292048,7 @@ class MaterializationService {
291634
292048
  });
291635
292049
  continue;
291636
292050
  }
291637
- const destForKey = entry.storageConnectionName ?? connectionName;
292051
+ const destForKey = entry.storageDestinationName ?? connectionName;
291638
292052
  if (stillReferenced.has(tableKey(destForKey, physicalTableName))) {
291639
292053
  logger.info("Skipping drop: table still referenced by another materialization", {
291640
292054
  materializationId: m.id,
@@ -291643,11 +292057,13 @@ class MaterializationService {
291643
292057
  });
291644
292058
  continue;
291645
292059
  }
291646
- if (entry.storageConnectionName) {
291647
- const destinationConnection = environment.getApiConnection(entry.storageConnectionName);
292060
+ if (entry.storageDestinationName) {
292061
+ const destinationConnection = this.destinationForCleanup(environment, entry.storageDestinationName);
292062
+ if (!destinationConnection)
292063
+ continue;
291648
292064
  try {
291649
292065
  await dropStorageTable({
291650
- destinationName: entry.storageConnectionName,
292066
+ destinationName: entry.storageDestinationName,
291651
292067
  destinationConnection,
291652
292068
  physicalTableName,
291653
292069
  environmentPath: environment.getEnvironmentPath()
@@ -291656,14 +292072,14 @@ class MaterializationService {
291656
292072
  logger.info("Dropped materialized storage table on delete", {
291657
292073
  materializationId: m.id,
291658
292074
  physicalTableName,
291659
- storageConnectionName: entry.storageConnectionName
292075
+ storageDestinationName: entry.storageDestinationName
291660
292076
  });
291661
292077
  } catch (err) {
291662
292078
  recordDropTables("failure", "storage");
291663
292079
  logger.warn("Failed to drop a storage-materialized table", {
291664
292080
  materializationId: m.id,
291665
292081
  physicalTableName,
291666
- storageConnectionName: entry.storageConnectionName,
292082
+ storageDestinationName: entry.storageDestinationName,
291667
292083
  error: redactConnectionSecrets(errMessage(err), destinationConnection)
291668
292084
  });
291669
292085
  }
@@ -292461,7 +292877,8 @@ app.get(`${API_PREFIX2}/environments`, async (_req, res) => {
292461
292877
  });
292462
292878
  app.post(`${API_PREFIX2}/environments`, async (req, res) => {
292463
292879
  try {
292464
- logger.info("Adding environment", { body: req.body });
292880
+ logger.info("Adding environment", { body: redactSensitive(req.body) });
292881
+ processStorageDestinationsOrThrow(req.body?.storageDestinations ?? []);
292465
292882
  const environment = await environmentStore.addEnvironment(req.body);
292466
292883
  res.status(200).json(await environment.serialize());
292467
292884
  } catch (error) {