@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.
- package/dist/app/api-doc.yaml +47 -17
- package/dist/package_load_worker.mjs +8 -1
- package/dist/server.mjs +471 -54
- package/package.json +5 -2
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -97
- package/eslint.config.mjs +0 -76
- package/k6-tests/README.md +0 -144
- package/k6-tests/bun.lock +0 -797
- package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
- package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
- package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
- package/k6-tests/load-test/load-test-crud.ts +0 -142
- package/k6-tests/load-test/load-test.ts +0 -784
- package/k6-tests/package.json +0 -27
- package/k6-tests/scripts/run-with-otel.sh +0 -50
- package/k6-tests/smoke-test/smoke-test.ts +0 -158
- package/k6-tests/tsconfig.json +0 -29
- package/k6-tests/utils/client_factory.ts +0 -128
- package/k6-tests/utils/common.ts +0 -489
- package/k6-tests/utils/logger.ts +0 -62
- package/k6-tests/utils/types.d.ts +0 -35
- package/malloy_mcp.dxt +0 -0
- package/publisher.config.example.bigquery.json +0 -33
- package/publisher.config.example.duckdb.json +0 -23
- package/publisher.config.json +0 -23
- package/scripts/bake-duckdb-extensions.js +0 -110
- package/src/config.spec.ts +0 -1535
- package/src/config.theme.spec.ts +0 -178
- package/src/config.ts +0 -1142
- package/src/constants.ts +0 -105
- package/src/controller/compile.controller.ts +0 -40
- package/src/controller/connection.controller.spec.ts +0 -849
- package/src/controller/connection.controller.ts +0 -901
- package/src/controller/database.controller.ts +0 -24
- package/src/controller/materialization.controller.spec.ts +0 -293
- package/src/controller/materialization.controller.ts +0 -319
- package/src/controller/model.controller.ts +0 -164
- package/src/controller/package.controller.spec.ts +0 -273
- package/src/controller/package.controller.ts +0 -306
- package/src/controller/query.controller.ts +0 -152
- package/src/controller/theme.controller.ts +0 -83
- package/src/controller/watch-mode.controller.ts +0 -243
- package/src/data_styles.ts +0 -71
- package/src/default-publisher.config.json +0 -23
- package/src/ducklake_version.spec.ts +0 -163
- package/src/ducklake_version.ts +0 -153
- package/src/errors.spec.ts +0 -119
- package/src/errors.ts +0 -237
- package/src/filter_deprecation.spec.ts +0 -64
- package/src/filter_deprecation.ts +0 -42
- package/src/health.spec.ts +0 -123
- package/src/health.ts +0 -233
- package/src/heap_check.spec.ts +0 -144
- package/src/heap_check.ts +0 -144
- package/src/instrumentation.ts +0 -214
- package/src/json_utils.spec.ts +0 -51
- package/src/json_utils.ts +0 -33
- package/src/logger.spec.ts +0 -210
- package/src/logger.ts +0 -251
- package/src/malloy_pin_prereqs.spec.ts +0 -25
- package/src/materialization_metrics.spec.ts +0 -169
- package/src/materialization_metrics.ts +0 -346
- package/src/mcp/error_messages.spec.ts +0 -35
- package/src/mcp/error_messages.ts +0 -183
- package/src/mcp/handler_utils.spec.ts +0 -108
- package/src/mcp/handler_utils.ts +0 -251
- package/src/mcp/mcp_constants.ts +0 -6
- package/src/mcp/query_envelope.spec.ts +0 -229
- package/src/mcp/query_envelope.ts +0 -240
- package/src/mcp/server.protocol.spec.ts +0 -250
- package/src/mcp/server.ts +0 -87
- package/src/mcp/skills/build_skills_bundle.spec.ts +0 -51
- package/src/mcp/skills/build_skills_bundle.ts +0 -176
- package/src/mcp/skills/skills_bundle.json +0 -1
- package/src/mcp/skills/skills_bundle.spec.ts +0 -171
- package/src/mcp/tool_response.spec.ts +0 -108
- package/src/mcp/tool_response.ts +0 -138
- package/src/mcp/tools/compile_tool.spec.ts +0 -315
- package/src/mcp/tools/compile_tool.ts +0 -208
- package/src/mcp/tools/docs_search/build_docs_index.ts +0 -132
- package/src/mcp/tools/docs_search/malloy_docs_index.json +0 -1
- package/src/mcp/tools/docs_search_tool.spec.ts +0 -32
- package/src/mcp/tools/docs_search_tool.ts +0 -128
- package/src/mcp/tools/embedding_index.spec.ts +0 -1236
- package/src/mcp/tools/embedding_index.ts +0 -808
- package/src/mcp/tools/execute_query_tool.spec.ts +0 -294
- package/src/mcp/tools/execute_query_tool.ts +0 -316
- package/src/mcp/tools/get_context_eval.ts +0 -275
- package/src/mcp/tools/get_context_tool.spec.ts +0 -592
- package/src/mcp/tools/get_context_tool.ts +0 -585
- package/src/mcp/tools/reload_package_tool.spec.ts +0 -232
- package/src/mcp/tools/reload_package_tool.ts +0 -132
- package/src/mcp_config.spec.ts +0 -919
- package/src/mcp_config.ts +0 -425
- package/src/oom_guards.integration.spec.ts +0 -269
- package/src/package_load/package_load_pool.spec.ts +0 -252
- package/src/package_load/package_load_pool.ts +0 -931
- package/src/package_load/package_load_worker.ts +0 -1034
- package/src/package_load/protocol.ts +0 -364
- package/src/package_load/rpc_wait_accountant.spec.ts +0 -109
- package/src/package_load/rpc_wait_accountant.ts +0 -76
- package/src/package_load_metrics.spec.ts +0 -114
- package/src/package_load_metrics.ts +0 -127
- package/src/path_safety.spec.ts +0 -158
- package/src/path_safety.ts +0 -146
- package/src/pg_helpers.spec.ts +0 -223
- package/src/pg_helpers.ts +0 -52
- package/src/query_cap_metrics.spec.ts +0 -89
- package/src/query_cap_metrics.ts +0 -116
- package/src/query_concurrency.spec.ts +0 -247
- package/src/query_concurrency.ts +0 -237
- package/src/query_metadata_metrics.ts +0 -49
- package/src/query_param_utils.ts +0 -29
- package/src/query_timeout.spec.ts +0 -224
- package/src/query_timeout.ts +0 -179
- package/src/runtime/publisher.js +0 -328
- package/src/server-old.ts +0 -988
- package/src/server.ts +0 -2059
- package/src/service/annotations.spec.ts +0 -118
- package/src/service/annotations.ts +0 -91
- package/src/service/authorize.spec.ts +0 -154
- package/src/service/authorize.ts +0 -498
- package/src/service/authorize_integration.spec.ts +0 -2041
- package/src/service/build_plan.spec.ts +0 -685
- package/src/service/build_plan.ts +0 -797
- package/src/service/compile_authorize.spec.ts +0 -85
- package/src/service/compile_fragment_techniques.spec.ts +0 -156
- package/src/service/connection.spec.ts +0 -2360
- package/src/service/connection.ts +0 -2187
- package/src/service/connection_config.spec.ts +0 -734
- package/src/service/connection_config.ts +0 -899
- package/src/service/connection_federation.spec.ts +0 -184
- package/src/service/connection_fingerprint.spec.ts +0 -102
- package/src/service/connection_service.spec.ts +0 -711
- package/src/service/connection_service.ts +0 -291
- package/src/service/cron_evaluator.spec.ts +0 -79
- package/src/service/cron_evaluator.ts +0 -105
- package/src/service/db_utils.spec.ts +0 -1085
- package/src/service/db_utils.ts +0 -1502
- package/src/service/duckdb_instance_isolation.spec.ts +0 -137
- package/src/service/ducklake_lazy_attach.spec.ts +0 -110
- package/src/service/embedding_provider.spec.ts +0 -329
- package/src/service/embedding_provider.ts +0 -236
- package/src/service/environment.ts +0 -2191
- package/src/service/environment_admission.spec.ts +0 -344
- package/src/service/environment_compile.spec.ts +0 -197
- package/src/service/environment_store.spec.ts +0 -2274
- package/src/service/environment_store.ts +0 -2434
- package/src/service/environment_store_anchoring.spec.ts +0 -107
- package/src/service/environment_store_clone.spec.ts +0 -350
- package/src/service/explore_visibility.spec.ts +0 -434
- package/src/service/exports_probe.spec.ts +0 -107
- package/src/service/extension_fetch_policy.spec.ts +0 -256
- package/src/service/filter.spec.ts +0 -458
- package/src/service/filter.ts +0 -341
- package/src/service/filter_bypass.spec.ts +0 -418
- package/src/service/filter_integration.spec.ts +0 -1004
- package/src/service/freshness.spec.ts +0 -183
- package/src/service/freshness.ts +0 -112
- package/src/service/gcs_s3_utils.ts +0 -427
- package/src/service/given.ts +0 -105
- package/src/service/givens_integration.spec.ts +0 -219
- package/src/service/manifest_loader.spec.ts +0 -189
- package/src/service/manifest_loader.ts +0 -156
- package/src/service/materialization_build_session.spec.ts +0 -435
- package/src/service/materialization_build_session.ts +0 -681
- package/src/service/materialization_config_validation.spec.ts +0 -99
- package/src/service/materialization_config_validation.ts +0 -120
- package/src/service/materialization_eligibility.spec.ts +0 -158
- package/src/service/materialization_eligibility.ts +0 -305
- package/src/service/materialization_schedule_surface.spec.ts +0 -256
- package/src/service/materialization_scheduler.spec.ts +0 -289
- package/src/service/materialization_scheduler.ts +0 -304
- package/src/service/materialization_serve_transform.spec.ts +0 -1003
- package/src/service/materialization_serve_transform.ts +0 -779
- package/src/service/materialization_service.spec.ts +0 -2373
- package/src/service/materialization_service.ts +0 -2342
- package/src/service/materialization_test_fixtures.ts +0 -249
- package/src/service/model.spec.ts +0 -1157
- package/src/service/model.ts +0 -3370
- package/src/service/model_limits.spec.ts +0 -209
- package/src/service/model_limits.ts +0 -131
- package/src/service/model_storage_serve.spec.ts +0 -193
- package/src/service/model_storage_serve_joins.spec.ts +0 -193
- package/src/service/package.spec.ts +0 -612
- package/src/service/package.ts +0 -1799
- package/src/service/package_manifest.spec.ts +0 -230
- package/src/service/package_manifest.ts +0 -241
- package/src/service/package_memory_governor.spec.ts +0 -173
- package/src/service/package_memory_governor.ts +0 -233
- package/src/service/package_quote_bound_tables.spec.ts +0 -171
- package/src/service/package_race.spec.ts +0 -208
- package/src/service/package_reload_safety.spec.ts +0 -193
- package/src/service/package_rollback.spec.ts +0 -190
- package/src/service/package_worker_path.spec.ts +0 -645
- package/src/service/path_injection.spec.ts +0 -39
- package/src/service/persist_annotation_validation.spec.ts +0 -89
- package/src/service/persist_annotation_validation.ts +0 -59
- package/src/service/persistence_policy.spec.ts +0 -374
- package/src/service/proxy.spec.ts +0 -414
- package/src/service/proxy.ts +0 -248
- package/src/service/query_boundary.spec.ts +0 -470
- package/src/service/query_metadata.spec.ts +0 -408
- package/src/service/query_metadata.ts +0 -492
- package/src/service/query_metadata_identity.spec.ts +0 -149
- package/src/service/query_text.spec.ts +0 -79
- package/src/service/query_text.ts +0 -43
- package/src/service/quoting.spec.ts +0 -135
- package/src/service/quoting.ts +0 -78
- package/src/service/resolve_environment.ts +0 -15
- package/src/service/restricted_mode.spec.ts +0 -299
- package/src/service/source_extraction.ts +0 -225
- package/src/service/theme_store.ts +0 -199
- package/src/storage/DatabaseInterface.ts +0 -198
- package/src/storage/StorageManager.ts +0 -113
- package/src/storage/duckdb/ConnectionRepository.ts +0 -143
- package/src/storage/duckdb/DuckDBConnection.ts +0 -123
- package/src/storage/duckdb/DuckDBRepository.ts +0 -208
- package/src/storage/duckdb/EnvironmentRepository.ts +0 -148
- package/src/storage/duckdb/MaterializationRepository.spec.ts +0 -191
- package/src/storage/duckdb/MaterializationRepository.ts +0 -336
- package/src/storage/duckdb/PackageRepository.ts +0 -135
- package/src/storage/duckdb/schema.ts +0 -229
- package/src/stream_helpers.spec.ts +0 -280
- package/src/stream_helpers.ts +0 -162
- package/src/telemetry.ts +0 -20
- package/src/test_helpers/metrics_harness.ts +0 -166
- package/src/theme_key_parity.spec.ts +0 -57
- package/src/utils.ts +0 -29
- package/tests/fixtures/authorize-compile/model.malloy +0 -9
- package/tests/fixtures/authorize-compile/publisher.json +0 -4
- package/tests/fixtures/html-pages-nopublic/model.malloy +0 -1
- package/tests/fixtures/html-pages-nopublic/publisher.json +0 -5
- package/tests/fixtures/html-pages-test/data.csv +0 -3
- package/tests/fixtures/html-pages-test/public/assets/app.css +0 -3
- package/tests/fixtures/html-pages-test/public/barehtml.html +0 -4
- package/tests/fixtures/html-pages-test/public/bodymeta.html +0 -8
- package/tests/fixtures/html-pages-test/public/data.json +0 -1
- package/tests/fixtures/html-pages-test/public/fitcomment.html +0 -11
- package/tests/fixtures/html-pages-test/public/index.html +0 -9
- package/tests/fixtures/html-pages-test/public/nohead.html +0 -8
- package/tests/fixtures/html-pages-test/public/notfit.html +0 -13
- package/tests/fixtures/html-pages-test/public/slides.html +0 -12
- package/tests/fixtures/html-pages-test/public/sub/page2.html +0 -9
- package/tests/fixtures/html-pages-test/public/unterminated.html +0 -10
- package/tests/fixtures/html-pages-test/publisher.json +0 -5
- package/tests/fixtures/html-pages-test/report.malloy +0 -1
- package/tests/fixtures/persist-multi-level/data/orders.csv +0 -5
- package/tests/fixtures/persist-multi-level/multi_level.malloy +0 -18
- package/tests/fixtures/persist-multi-level/publisher.json +0 -5
- package/tests/fixtures/persist-schedule-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-schedule-test/persist_schedule_test.malloy +0 -11
- package/tests/fixtures/persist-schedule-test/publisher.json +0 -7
- package/tests/fixtures/persist-schedule-warn-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-schedule-warn-test/persist_warn_test.malloy +0 -11
- package/tests/fixtures/persist-schedule-warn-test/publisher.json +0 -6
- package/tests/fixtures/persist-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-test/persist_test.malloy +0 -11
- package/tests/fixtures/persist-test/publisher.json +0 -5
- package/tests/fixtures/publisher.config.json +0 -15
- package/tests/fixtures/query-givens/data/orders.csv +0 -7
- package/tests/fixtures/query-givens/model.malloy +0 -34
- package/tests/fixtures/query-givens/publisher.json +0 -5
- package/tests/fixtures/xlsx/database.xlsx +0 -0
- package/tests/harness/mcp_test_setup.ts +0 -332
- package/tests/harness/rest_e2e.ts +0 -68
- package/tests/integration/authorize/compile_authorize_http.integration.spec.ts +0 -92
- package/tests/integration/concurrent_package/concurrent_package.integration.spec.ts +0 -280
- package/tests/integration/duckdb_storage/duckdb_storage.integration.spec.ts +0 -138
- package/tests/integration/first_boot/readiness_line.integration.spec.ts +0 -177
- package/tests/integration/html_pages/html_pages.integration.spec.ts +0 -440
- package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +0 -259
- package/tests/integration/materialization/environment_list.integration.spec.ts +0 -133
- package/tests/integration/materialization/freshness_gate.integration.spec.ts +0 -292
- package/tests/integration/materialization/manifest_binding.integration.spec.ts +0 -430
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -441
- package/tests/integration/materialization/orchestrated_rebind.integration.spec.ts +0 -150
- package/tests/integration/materialization/reference_manifest.integration.spec.ts +0 -251
- package/tests/integration/materialization/schedule_edit.integration.spec.ts +0 -126
- package/tests/integration/materialization/scheduler.integration.spec.ts +0 -200
- package/tests/integration/materialization/scheduler_recovery.integration.spec.ts +0 -172
- package/tests/integration/materialization/scheduler_transitions.integration.spec.ts +0 -256
- package/tests/integration/materialization/scheduler_wiring.integration.spec.ts +0 -143
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -438
- package/tests/integration/mcp/mcp_get_context_semantic.integration.spec.ts +0 -235
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -98
- package/tests/integration/query_givens/query_givens.integration.spec.ts +0 -146
- package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +0 -121
- package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +0 -110
- package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -421
- package/tests/unit/controller/theme.controller.spec.ts +0 -141
- package/tests/unit/duckdb/attached_databases.test.ts +0 -1243
- package/tests/unit/duckdb/duckdb_connection.test.ts +0 -181
- package/tests/unit/duckdb/legacy_schema_migration.test.ts +0 -197
- package/tests/unit/duckdb/repositories.test.ts +0 -208
- package/tests/unit/ducklake/ducklake.test.ts +0 -952
- package/tests/unit/service/theme_store.spec.ts +0 -139
- 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.
|
|
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.
|
|
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
|
-
|
|
276966
|
+
destinationName: entry.storageDestinationName,
|
|
276753
276967
|
virtualHandle: entry.sourceEntityId,
|
|
276754
|
-
tablePath: `${entry.
|
|
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.
|
|
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.
|
|
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.
|
|
277240
|
+
let inner = map.get(b.destinationName);
|
|
277027
277241
|
if (!inner) {
|
|
277028
277242
|
inner = new Map;
|
|
277029
|
-
map.set(b.
|
|
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
|
-
|
|
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
|
-
|
|
278485
|
-
this.
|
|
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.
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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),
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
291647
|
-
const destinationConnection =
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|