@malloy-publisher/server 0.0.235 → 0.0.237
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 +83 -27
- package/dist/app/assets/{EnvironmentPage-BsAnavYN.js → EnvironmentPage-DaP6CpgK.js} +1 -1
- package/dist/app/assets/{HomePage-CADE138j.js → HomePage-C9yNw3iu.js} +1 -1
- package/dist/app/assets/{LightMode-Cfh7KzN8.js → LightMode-Be4-G-dX.js} +1 -1
- package/dist/app/assets/{MainPage-CO3pRlnV.js → MainPage-J_rU2SgI.js} +2 -2
- package/dist/app/assets/{MaterializationsPage-p9YjkRXZ.js → MaterializationsPage-DHgPI4MO.js} +1 -1
- package/dist/app/assets/ModelPage-BzXfjTu5.js +1 -0
- package/dist/app/assets/{PackagePage-e4kN75YR.js → PackagePage-ByoSTLJ4.js} +1 -1
- package/dist/app/assets/RouteError-CrSeKN2r.js +1 -0
- package/dist/app/assets/{ThemeEditorPage-CciagFTq.js → ThemeEditorPage-Btxnmhtk.js} +1 -1
- package/dist/app/assets/{WorkbookPage-DNWmkCXa.js → WorkbookPage-D6JLPnWO.js} +1 -1
- package/dist/app/assets/{core-Rj_4rRnA.es-BZyvITuO.js → core-BSRgt1mG.es-Bc_D2Lq4.js} +1 -1
- package/dist/app/assets/{index-CH2AcDzc.js → index-BRijWj6K.js} +15 -15
- package/dist/app/assets/{index-DQa463gC.js → index-BX4D94xw.js} +1 -1
- package/dist/app/assets/{index-VBbcc8s6.js → index-Cvy0xtb7.js} +1 -1
- package/dist/app/assets/{index-DQpV7MyA.js → index-DMAhIJNC.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/package_load_worker.mjs +59 -20
- package/dist/server.mjs +1342 -616
- package/package.json +8 -2
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -97
- package/dist/app/assets/ModelPage-C1OSTv-x.js +0 -1
- package/dist/app/assets/RouteError-CzbfOkng.js +0 -1
- 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
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralized telemetry for materialization builds.
|
|
3
|
-
*
|
|
4
|
-
* Operators need to answer "are builds completing, how long do they take, and
|
|
5
|
-
* where are they failing?" without grepping logs. The run counter carries
|
|
6
|
-
* `mode` (`auto`|`orchestrated`) and `outcome` (`success`|`failed`|`cancelled`);
|
|
7
|
-
* the run-duration histogram carries `mode` so a dashboard can render auto-run
|
|
8
|
-
* vs orchestrated build latency side by side.
|
|
9
|
-
*
|
|
10
|
-
* Instruments are created lazily for the same reason as {@link ./query_cap_metrics}:
|
|
11
|
-
* one created before `setGlobalMeterProvider` binds to a NoOp meter
|
|
12
|
-
* (https://github.com/open-telemetry/opentelemetry-js/issues/3505).
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { type Counter, type Histogram } from "@opentelemetry/api";
|
|
16
|
-
import { publisherMeter } from "./telemetry";
|
|
17
|
-
|
|
18
|
-
export type MaterializationMode = "auto" | "orchestrated";
|
|
19
|
-
export type MaterializationOutcome = "success" | "failed" | "cancelled";
|
|
20
|
-
/** Manifest bind outcome: timeout is split out from generic failure on purpose. */
|
|
21
|
-
export type ManifestBindOutcome = "success" | "failure" | "timeout";
|
|
22
|
-
/**
|
|
23
|
-
* Which engine a source build / table drop ran against: the source's own
|
|
24
|
-
* warehouse (colocated, in-warehouse CTAS) or a DuckDB/DuckLake `storage=`
|
|
25
|
-
* destination (federated passthrough CTAS). The two have very different latency
|
|
26
|
-
* and failure profiles, so build/drop metrics are labeled with it rather than
|
|
27
|
-
* pooling the two into one series.
|
|
28
|
-
*/
|
|
29
|
-
export type StorageBuildEngine = "storage" | "in_warehouse";
|
|
30
|
-
/** Why a source was refused materialization into a storage destination. */
|
|
31
|
-
export type EligibilityRefusalReason =
|
|
32
|
-
| "free_parameter"
|
|
33
|
-
| "given"
|
|
34
|
-
| "authorize"
|
|
35
|
-
| "not_duckdb_portable"
|
|
36
|
-
| "public_surface_unknown";
|
|
37
|
-
/**
|
|
38
|
-
* How a chained `storage=` source (one that reads a storage-materialized
|
|
39
|
-
* upstream) was built: `parent_reuse` = built by reading the upstream's stored
|
|
40
|
-
* lake table ("stack on the parent" — reuses the parent's work and is
|
|
41
|
-
* consistent-by-construction); `inline_fallback` = stacking was ineligible/failed
|
|
42
|
-
* so the upstream was recomputed from raw against the warehouse (non-strict);
|
|
43
|
-
* `strict_refused` = stacking was ineligible under `strictUpstreams`, so
|
|
44
|
-
* the build failed loudly rather than silently recomputing. This is the headline
|
|
45
|
-
* signal for how far the parent-reuse path gets us in practice.
|
|
46
|
-
*/
|
|
47
|
-
export type ChainedStorageBuildOutcome =
|
|
48
|
-
| "parent_reuse"
|
|
49
|
-
| "inline_fallback"
|
|
50
|
-
| "strict_refused"
|
|
51
|
-
// The parent-reuse attempt failed on infrastructure (attach, CTAS, the
|
|
52
|
-
// destination being unreachable) rather than on shape. Distinct from
|
|
53
|
-
// `inline_fallback` because it is an outage, not a modelling limit: the build
|
|
54
|
-
// fails rather than recomputing from raw against the same destination.
|
|
55
|
-
| "infra_failure";
|
|
56
|
-
|
|
57
|
-
const resetHooks: (() => void)[] = [];
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* A lazily-created instrument: built from the global meter on first use (so it
|
|
61
|
-
* binds to the real MeterProvider, not the NoOp default) and memoized. Each
|
|
62
|
-
* registers a reset hook so a test can swap in a fresh MeterProvider.
|
|
63
|
-
*/
|
|
64
|
-
function lazyCounter(name: string, description: string): () => Counter {
|
|
65
|
-
let instrument: Counter | null = null;
|
|
66
|
-
resetHooks.push(() => (instrument = null));
|
|
67
|
-
return () =>
|
|
68
|
-
(instrument ??= publisherMeter().createCounter(name, { description }));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function lazyHistogram(
|
|
72
|
-
name: string,
|
|
73
|
-
description: string,
|
|
74
|
-
unit: string,
|
|
75
|
-
): () => Histogram {
|
|
76
|
-
let instrument: Histogram | null = null;
|
|
77
|
-
resetHooks.push(() => (instrument = null));
|
|
78
|
-
return () =>
|
|
79
|
-
(instrument ??= publisherMeter().createHistogram(name, {
|
|
80
|
-
description,
|
|
81
|
-
unit,
|
|
82
|
-
}));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const runCounter = lazyCounter(
|
|
86
|
-
"publisher_materialization_runs_total",
|
|
87
|
-
"Materialization builds completed. Labels: mode ('auto'|'orchestrated'), outcome ('success'|'failed'|'cancelled').",
|
|
88
|
-
);
|
|
89
|
-
const runDuration = lazyHistogram(
|
|
90
|
-
"publisher_materialization_run_duration_ms",
|
|
91
|
-
"Wall-clock duration of a materialization build. Label: mode ('auto'|'orchestrated').",
|
|
92
|
-
"ms",
|
|
93
|
-
);
|
|
94
|
-
const sourcesCounter = lazyCounter(
|
|
95
|
-
"publisher_materialization_sources_total",
|
|
96
|
-
"Persist sources processed by a materialization run. Label: outcome ('built'|'reused').",
|
|
97
|
-
);
|
|
98
|
-
const buildPlanComputeDuration = lazyHistogram(
|
|
99
|
-
"publisher_materialization_build_plan_compute_duration_ms",
|
|
100
|
-
"Wall-clock duration of compiling a package's build plan (Package.buildPlan).",
|
|
101
|
-
"ms",
|
|
102
|
-
);
|
|
103
|
-
const autoLoadCounter = lazyCounter(
|
|
104
|
-
"publisher_materialization_auto_load_total",
|
|
105
|
-
"Auto-run manifest auto-load attempts. Label: outcome ('success'|'failure').",
|
|
106
|
-
);
|
|
107
|
-
const connectionDigestSkipCounter = lazyCounter(
|
|
108
|
-
"publisher_materialization_connection_digest_skipped_total",
|
|
109
|
-
"Connection digests skipped during build-plan compile because the connection did not resolve.",
|
|
110
|
-
);
|
|
111
|
-
const manifestBindCounter = lazyCounter(
|
|
112
|
-
"publisher_materialization_manifest_bind_total",
|
|
113
|
-
"Manifest bind attempts. Label: outcome ('success'|'failure'|'timeout').",
|
|
114
|
-
);
|
|
115
|
-
const manifestBindDegradedCounter = lazyCounter(
|
|
116
|
-
"publisher_materialization_manifest_bind_degraded_total",
|
|
117
|
-
"Manifest entries bound with an UNQUOTED table path because their connection " +
|
|
118
|
-
"could not be resolved (serve-side bind) or is absent from the build " +
|
|
119
|
-
"(build-side seed). A misconfiguration that breaks the source on a " +
|
|
120
|
-
"case-folding engine (Snowflake); alertable.",
|
|
121
|
-
);
|
|
122
|
-
const sourceBuildDuration = lazyHistogram(
|
|
123
|
-
"publisher_materialization_source_build_duration_ms",
|
|
124
|
-
"Wall-clock duration of building a single persist source.",
|
|
125
|
-
"ms",
|
|
126
|
-
);
|
|
127
|
-
const dropTablesCounter = lazyCounter(
|
|
128
|
-
"publisher_materialization_drop_tables_total",
|
|
129
|
-
"Physical tables dropped on delete. Label: outcome ('success'|'failure').",
|
|
130
|
-
);
|
|
131
|
-
const scheduledFireCounter = lazyCounter(
|
|
132
|
-
"publisher_materialization_scheduled_fires_total",
|
|
133
|
-
"Standalone-scheduler attempts to fire a package's materialization.schedule. " +
|
|
134
|
-
"Label: outcome ('fired'|'conflict'|'error').",
|
|
135
|
-
);
|
|
136
|
-
const storageServeRoutingCounter = lazyCounter(
|
|
137
|
-
"publisher_storage_serve_routing_total",
|
|
138
|
-
"storage= serve routing decisions. Label: outcome ('storage'|'live_fallback'|'runtime_live_fallback').",
|
|
139
|
-
);
|
|
140
|
-
const storageBuildFailureCounter = lazyCounter(
|
|
141
|
-
"publisher_storage_build_failures_total",
|
|
142
|
-
"storage= build failures (federation/passthrough/attach/CTAS), distinct from " +
|
|
143
|
-
"in-warehouse build failures. Label: destination (connection name).",
|
|
144
|
-
);
|
|
145
|
-
const eligibilityRefusedCounter = lazyCounter(
|
|
146
|
-
"publisher_materialization_eligibility_refused_total",
|
|
147
|
-
"storage= materialization-eligibility refusals. Label: reason " +
|
|
148
|
-
"('free_parameter'|'given'|'authorize'|'not_duckdb_portable'|" +
|
|
149
|
-
"'public_surface_unknown').",
|
|
150
|
-
);
|
|
151
|
-
const serveShapeTierDropCounter = lazyCounter(
|
|
152
|
-
"publisher_storage_serve_shape_tier_drop_total",
|
|
153
|
-
"storage serve-shape compile escalations: a refinement tier failed to " +
|
|
154
|
-
"compile and the riskiest category was dropped. Label: tier (the failed " +
|
|
155
|
-
"tier index, 0=full).",
|
|
156
|
-
);
|
|
157
|
-
const serveShapeTypeFallbackCounter = lazyCounter(
|
|
158
|
-
"publisher_storage_serve_shape_type_fallback_total",
|
|
159
|
-
"Captured DuckDB column types mapped to json in the serve shape (type " +
|
|
160
|
-
"fidelity loss). Label: kind ('array'|'unrecognized').",
|
|
161
|
-
);
|
|
162
|
-
const chainedStorageBuildCounter = lazyCounter(
|
|
163
|
-
"publisher_storage_chained_build_total",
|
|
164
|
-
"Chained storage= source builds (a source reading a storage-materialized " +
|
|
165
|
-
"upstream). Label: outcome ('parent_reuse'|'inline_fallback'|" +
|
|
166
|
-
"'strict_refused'). The parent_reuse share is the headline signal for how " +
|
|
167
|
-
"far the stack-on-the-parent path gets us vs recompute-from-raw.",
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Record a standalone-scheduler fire attempt. `fired` = a SCHEDULER run started;
|
|
172
|
-
* `conflict` = a materialization was already active (this occurrence coalesces
|
|
173
|
-
* into it); `error` = the fire failed unexpectedly.
|
|
174
|
-
*/
|
|
175
|
-
export function recordScheduledFire(
|
|
176
|
-
outcome: "fired" | "conflict" | "error",
|
|
177
|
-
): void {
|
|
178
|
-
scheduledFireCounter().add(1, { outcome });
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/** Record the outcome and duration of a materialization build. */
|
|
182
|
-
export function recordMaterializationRun(
|
|
183
|
-
mode: MaterializationMode,
|
|
184
|
-
outcome: MaterializationOutcome,
|
|
185
|
-
durationMs: number,
|
|
186
|
-
): void {
|
|
187
|
-
runCounter().add(1, { mode, outcome });
|
|
188
|
-
runDuration().record(durationMs, { mode });
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Record how many persist sources a run built vs. reused (carried forward
|
|
193
|
-
* unchanged via skip-if-unchanged). Lets a dashboard show the reuse ratio,
|
|
194
|
-
* the main lever on materialization cost.
|
|
195
|
-
*/
|
|
196
|
-
export function recordSourcesOutcome(
|
|
197
|
-
outcome: "built" | "reused",
|
|
198
|
-
count: number,
|
|
199
|
-
): void {
|
|
200
|
-
if (count <= 0) return;
|
|
201
|
-
sourcesCounter().add(count, { outcome });
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Record the wall-clock cost of compiling a package's build plan
|
|
206
|
-
* (`Package.buildPlan`). This recompiles models at load, so it is worth
|
|
207
|
-
* tracking as a discrete cost separate from the build itself.
|
|
208
|
-
*/
|
|
209
|
-
export function recordBuildPlanComputeDuration(durationMs: number): void {
|
|
210
|
-
buildPlanComputeDuration().record(durationMs);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Record an auto-run manifest auto-load attempt. Auto-load is best-effort (a
|
|
215
|
-
* failure does not fail the run), so a failure here is otherwise invisible:
|
|
216
|
-
* the run is MANIFEST_FILE_READY but queries still resolve to the old tables.
|
|
217
|
-
*/
|
|
218
|
-
export function recordAutoLoadOutcome(outcome: "success" | "failure"): void {
|
|
219
|
-
autoLoadCounter().add(1, { outcome });
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Record that a connection digest could not be computed during build-plan
|
|
224
|
-
* compilation because the connection failed to resolve. The resulting sourceEntityIds
|
|
225
|
-
* are computed without a digest, so this is a correctness signal, not just noise.
|
|
226
|
-
*/
|
|
227
|
-
export function recordConnectionDigestSkipped(): void {
|
|
228
|
-
connectionDigestSkipCounter().add(1);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Record a manifest-bind attempt (publisher binding a configured manifest to a
|
|
233
|
-
* package at load). `timeout` is distinguished from `failure` so operators can
|
|
234
|
-
* tell an unreachable/slow manifest store from a malformed manifest.
|
|
235
|
-
*/
|
|
236
|
-
export function recordManifestBind(outcome: ManifestBindOutcome): void {
|
|
237
|
-
manifestBindCounter().add(1, { outcome });
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* A single manifest entry bound with an unquoted table path because its
|
|
242
|
-
* connection could not be resolved for quoting. Per-entry degradation (the rest
|
|
243
|
-
* of the bind succeeds), distinct from the whole-bind {@link recordManifestBind}
|
|
244
|
-
* outcome — hence its own counter rather than a label on that one.
|
|
245
|
-
*/
|
|
246
|
-
export function recordManifestBindDegraded(): void {
|
|
247
|
-
manifestBindDegradedCounter().add(1);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Record the wall-clock duration of building one persist source's table,
|
|
252
|
-
* labeled by engine so the DuckDB passthrough path and the in-warehouse CTAS
|
|
253
|
-
* path (very different latency profiles) don't pool into one series.
|
|
254
|
-
*/
|
|
255
|
-
export function recordSourceBuildDuration(
|
|
256
|
-
durationMs: number,
|
|
257
|
-
engine: StorageBuildEngine,
|
|
258
|
-
): void {
|
|
259
|
-
sourceBuildDuration().record(durationMs, { engine });
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/** Record a best-effort physical-table drop on materialization delete. */
|
|
263
|
-
export function recordDropTables(
|
|
264
|
-
outcome: "success" | "failure",
|
|
265
|
-
engine: StorageBuildEngine,
|
|
266
|
-
): void {
|
|
267
|
-
dropTablesCounter().add(1, { outcome, engine });
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Record a `storage=` build failure (federation / passthrough / attach / CTAS),
|
|
272
|
-
* counted separately from in-warehouse build failures because the storage path
|
|
273
|
-
* has its own failure modes and destination axis.
|
|
274
|
-
*/
|
|
275
|
-
export function recordStorageBuildFailure(destination: string): void {
|
|
276
|
-
storageBuildFailureCounter().add(1, { destination });
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Record a materialization-eligibility refusal (a source that can't be safely
|
|
281
|
-
* materialized into a storage destination). The `given` reason is a security
|
|
282
|
-
* refusal (a frozen given-filtered table would leak rows across tenants); worth
|
|
283
|
-
* tracking how often authors attempt it.
|
|
284
|
-
*/
|
|
285
|
-
export function recordEligibilityRefused(
|
|
286
|
-
reason: EligibilityRefusalReason,
|
|
287
|
-
): void {
|
|
288
|
-
eligibilityRefusedCounter().add(1, { reason });
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Record a serve-shape compile escalation: the refinement tier at `failedTier`
|
|
293
|
-
* did not compile, so the riskiest category was dropped and the shape retried.
|
|
294
|
-
* A systematically-dropping source tells authors which refinements aren't
|
|
295
|
-
* servable from storage.
|
|
296
|
-
*/
|
|
297
|
-
export function recordServeShapeTierDrop(failedTier: number): void {
|
|
298
|
-
serveShapeTierDropCounter().add(1, { tier: String(failedTier) });
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Record a captured DuckDB column type that could not map onto a Malloy basic
|
|
303
|
-
* type and was carried as json in the serve shape — a type-fidelity loss
|
|
304
|
-
* specific to the storage tier. `array` = a collection/array type; `unrecognized`
|
|
305
|
-
* = an unknown/nested/struct/enum/blob type.
|
|
306
|
-
*/
|
|
307
|
-
export function recordServeShapeTypeFallback(
|
|
308
|
-
kind: "array" | "unrecognized",
|
|
309
|
-
): void {
|
|
310
|
-
serveShapeTypeFallbackCounter().add(1, { kind });
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Record a `storage=` serve-routing decision: `storage` = the query was served
|
|
315
|
-
* from the materialized table via the virtual-source transform; `live_fallback`
|
|
316
|
-
* = the transform was ineligible for this query (a refinement it can't
|
|
317
|
-
* reproduce, an unbound source, mode not `on`) so it was served live;
|
|
318
|
-
* `runtime_live_fallback` = the query DID route to storage and the store then
|
|
319
|
-
* failed underneath it, and every binding's `freshnessFallback=live` allowed it
|
|
320
|
-
* to degrade. That last one is the operationally interesting label: it means the
|
|
321
|
-
* tier is broken while queries still succeed, which is invisible in the hit rate
|
|
322
|
-
* alone. This hit rate is the headline KPI of the storage tier — otherwise the
|
|
323
|
-
* fallback side is only a DEBUG log.
|
|
324
|
-
*/
|
|
325
|
-
export function recordStorageServeRouting(
|
|
326
|
-
outcome: "storage" | "live_fallback" | "runtime_live_fallback",
|
|
327
|
-
): void {
|
|
328
|
-
storageServeRoutingCounter().add(1, { outcome });
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Record how a chained `storage=` source was built (see
|
|
333
|
-
* {@link ChainedStorageBuildOutcome}). Only fired for a source that actually
|
|
334
|
-
* reads a storage-materialized upstream — a single-source build emits nothing
|
|
335
|
-
* here. The parent_reuse : inline_fallback ratio is the spike's key learning.
|
|
336
|
-
*/
|
|
337
|
-
export function recordChainedStorageBuild(
|
|
338
|
-
outcome: ChainedStorageBuildOutcome,
|
|
339
|
-
): void {
|
|
340
|
-
chainedStorageBuildCounter().add(1, { outcome });
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/** Visible for tests. Drops cached instruments so a fresh MeterProvider can capture emissions. */
|
|
344
|
-
export function resetMaterializationTelemetryForTesting(): void {
|
|
345
|
-
for (const reset of resetHooks) reset();
|
|
346
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import { AccessDeniedError } from "../errors";
|
|
3
|
-
import { getMalloyErrorDetails } from "./error_messages";
|
|
4
|
-
|
|
5
|
-
describe("getMalloyErrorDetails — access-denied branch", () => {
|
|
6
|
-
it("recognizes an authorize denial and gives access-relevant (not syntax) advice", () => {
|
|
7
|
-
const details = getMalloyErrorDetails(
|
|
8
|
-
"executeQuery",
|
|
9
|
-
"env/pkg/model.malloy",
|
|
10
|
-
new AccessDeniedError('Access denied for source "gated".'),
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
// Message carries the source name, never the gate expression.
|
|
14
|
-
expect(details.message).toContain('Access denied for source "gated".');
|
|
15
|
-
|
|
16
|
-
// The suggestion is about satisfying access (givens/role), and the
|
|
17
|
-
// generic Malloy-syntax suggestions are replaced, not appended.
|
|
18
|
-
expect(details.suggestions).toHaveLength(1);
|
|
19
|
-
expect(details.suggestions[0]).toMatch(/given|authorize|restricted/i);
|
|
20
|
-
// Not the generic "check the database connection / consult the language
|
|
21
|
-
// docs" advice that an unrecognized error would yield.
|
|
22
|
-
expect(details.suggestions.join(" ")).not.toMatch(
|
|
23
|
-
/database connection configuration|language documentation/i,
|
|
24
|
-
);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("still falls back to generic suggestions for an unrecognized error", () => {
|
|
28
|
-
const details = getMalloyErrorDetails(
|
|
29
|
-
"executeQuery",
|
|
30
|
-
"env/pkg/model.malloy",
|
|
31
|
-
new Error("something unexpected"),
|
|
32
|
-
);
|
|
33
|
-
expect(details.suggestions.length).toBeGreaterThan(1);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
export interface ErrorDetails {
|
|
2
|
-
message: string;
|
|
3
|
-
suggestions: string[];
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Generates error details for a resource not found scenario.
|
|
8
|
-
* @param resourceUriOrContext The URI that was not found, or a context string (e.g., "Package 'X'", "Model 'Y' in package 'X'").
|
|
9
|
-
* @returns ErrorDetails object with message and suggestions.
|
|
10
|
-
*/
|
|
11
|
-
export function getNotFoundError(resourceUriOrContext: string): ErrorDetails {
|
|
12
|
-
const baseMessage = `Resource not found: ${resourceUriOrContext}`;
|
|
13
|
-
const suggestions: string[] = [
|
|
14
|
-
`Verify the identifier (${resourceUriOrContext}) is spelled correctly and exists. Check capitalization and path separators.`,
|
|
15
|
-
"Check if the resource exists and is correctly named in your Malloy environment structure or the specific model file.",
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
message: baseMessage,
|
|
20
|
-
suggestions: suggestions,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Generates generic error details for internal server errors.
|
|
26
|
-
* @param operation The operation that failed (e.g., 'executeQuery').
|
|
27
|
-
* @param error Optional: The underlying error object or message.
|
|
28
|
-
* @returns ErrorDetails object.
|
|
29
|
-
*/
|
|
30
|
-
export function getInternalError(
|
|
31
|
-
operation: string,
|
|
32
|
-
error?: unknown,
|
|
33
|
-
): ErrorDetails {
|
|
34
|
-
const baseMessage = `An unexpected internal error occurred during ${operation}.`;
|
|
35
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
36
|
-
return {
|
|
37
|
-
message: error ? `${baseMessage}: ${errorMessage}` : baseMessage,
|
|
38
|
-
suggestions: [
|
|
39
|
-
"Try the request again later.",
|
|
40
|
-
"If the problem persists, check server logs or contact support.",
|
|
41
|
-
],
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Generates detailed error information for Malloy compilation or query execution errors.
|
|
47
|
-
* @param operation The operation that failed (e.g., 'executeQuery (load model)', 'executeQuery').
|
|
48
|
-
* @param modelIdentifier A path or URI identifying the model/notebook involved.
|
|
49
|
-
* @param error The underlying Malloy error object or other error.
|
|
50
|
-
* @returns ErrorDetails object.
|
|
51
|
-
*/
|
|
52
|
-
export function getMalloyErrorDetails(
|
|
53
|
-
operation: string,
|
|
54
|
-
modelIdentifier: string,
|
|
55
|
-
error: unknown,
|
|
56
|
-
): ErrorDetails {
|
|
57
|
-
let baseMessage = `Error during ${operation} for resource '${modelIdentifier}'.`;
|
|
58
|
-
const initialSuggestions: string[] = [
|
|
59
|
-
`Verify the structure and syntax within the Malloy file: ${modelIdentifier}. Check for typos in source, query, or view names (e.g., 'source: my_source is ...', 'query: my_query is ...', 'view: my_view is { ... }'). See Model Structure: https://docs.malloydata.dev/documentation/language/statement`,
|
|
60
|
-
"Ensure all referenced sources, queries, or views are correctly defined within the model or imported files using expected keywords (e.g., 'source:', 'query:', 'view:'). See Sources: https://docs.malloydata.dev/documentation/language/source",
|
|
61
|
-
"Check the database connection configuration used by the model, if applicable.",
|
|
62
|
-
"Consult the Malloy language documentation for syntax rules: https://docs.malloydata.dev/documentation/",
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
let suggestions: string[] = [...initialSuggestions]; // Clone initial suggestions
|
|
66
|
-
let refined = false; // Flag to track if specific suggestions were added
|
|
67
|
-
|
|
68
|
-
// Attempt to extract more specific info if it's a MalloyError or similar
|
|
69
|
-
if (error instanceof Error) {
|
|
70
|
-
// Prepend the specific error message
|
|
71
|
-
baseMessage = `Error during ${operation} for resource '${modelIdentifier}': ${error.message}`;
|
|
72
|
-
|
|
73
|
-
// --- Add more specific suggestions based on common Malloy error patterns ---
|
|
74
|
-
// Note: Order matters, check for more specific patterns first.
|
|
75
|
-
|
|
76
|
-
const viewNotFoundMatch = error.message.match(
|
|
77
|
-
/View '([^']+)' not found in source '([^']+)'/i,
|
|
78
|
-
);
|
|
79
|
-
const sourceNotFoundMatch = error.message.match(
|
|
80
|
-
/Source '([^']+)' not found/i,
|
|
81
|
-
);
|
|
82
|
-
const queryNotFoundMatch = error.message.match(
|
|
83
|
-
/Query '([^']+)' not found/i,
|
|
84
|
-
);
|
|
85
|
-
const referenceErrorMatch = error.message.match(
|
|
86
|
-
/Reference to undefined object(?: '([^']+)')?/i,
|
|
87
|
-
);
|
|
88
|
-
const syntaxErrorMatch = error.message.match(
|
|
89
|
-
/no viable alternative at input/i,
|
|
90
|
-
);
|
|
91
|
-
const connectionErrorMatch = error.message.match(
|
|
92
|
-
/Cannot connect to database/i,
|
|
93
|
-
);
|
|
94
|
-
const fieldNotFoundMatch = error.message.match(
|
|
95
|
-
/Field '([^']+)' not found in (source|query|view) '([^']+)'/i,
|
|
96
|
-
);
|
|
97
|
-
const invalidRequestMatch = error.message.match(
|
|
98
|
-
/Invalid query request\\. Query OR queryName must be defined/i,
|
|
99
|
-
);
|
|
100
|
-
// `#(authorize)` gate denial. The message names only the source (gate
|
|
101
|
-
// logic is never leaked), so the suggestion is about satisfying access,
|
|
102
|
-
// not fixing syntax — otherwise this falls through to generic Malloy
|
|
103
|
-
// syntax advice that misleads the caller.
|
|
104
|
-
const accessDeniedMatch = error.message.match(
|
|
105
|
-
/Access denied for source "([^"]+)"/i,
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
if (accessDeniedMatch) {
|
|
109
|
-
refined = true;
|
|
110
|
-
const [, sourceName] = accessDeniedMatch;
|
|
111
|
-
suggestions = [
|
|
112
|
-
`Suggestion: Access to source '${sourceName}' is restricted by an #(authorize) gate. Supply the givens its authorize expression requires (e.g. a role/region given) and retry. This is an authorization denial, not a syntax error.`,
|
|
113
|
-
];
|
|
114
|
-
} else if (viewNotFoundMatch) {
|
|
115
|
-
refined = true;
|
|
116
|
-
const [, viewName, sourceName] = viewNotFoundMatch;
|
|
117
|
-
suggestions.unshift(
|
|
118
|
-
`Suggestion: View '${viewName}' was not found in source '${sourceName}'. Check the view name spelling or call malloy_getContext with sourceName '${sourceName}' to see the list of available views. Views are defined within sources like 'source: ${sourceName} is ... extend { view: ${viewName} is { ... } }'.`,
|
|
119
|
-
);
|
|
120
|
-
} else if (sourceNotFoundMatch) {
|
|
121
|
-
refined = true;
|
|
122
|
-
const [, sourceName] = sourceNotFoundMatch;
|
|
123
|
-
suggestions.unshift(
|
|
124
|
-
`Suggestion: Source '${sourceName}' was not found or could not be accessed. Verify its definition (e.g., \`source: ${sourceName} is table('...')\` or \`duckdb.sql("...")\`) and ensure any associated connections are valid.`,
|
|
125
|
-
`Suggestion: Check the spelling of '${sourceName}'. You can list the sources in the package using malloy_getContext.`,
|
|
126
|
-
);
|
|
127
|
-
} else if (queryNotFoundMatch) {
|
|
128
|
-
refined = true;
|
|
129
|
-
const [, queryName] = queryNotFoundMatch;
|
|
130
|
-
suggestions.unshift(
|
|
131
|
-
`Suggestion: Named query '${queryName}' was not found. Verify its definition (e.g., \`query: ${queryName} is source_name -> { ... }\`) within the model '${modelIdentifier}'.`,
|
|
132
|
-
`Suggestion: Check the spelling of '${queryName}'. Ensure it's a named query (defined with \`query:\`), not a view. You can list named queries using malloy_getContext.`,
|
|
133
|
-
);
|
|
134
|
-
} else if (fieldNotFoundMatch) {
|
|
135
|
-
refined = true;
|
|
136
|
-
const [, fieldName, fieldContextType, fieldContextName] =
|
|
137
|
-
fieldNotFoundMatch;
|
|
138
|
-
suggestions.unshift(
|
|
139
|
-
`Suggestion: Field '${fieldName}' was not found in ${fieldContextType} '${fieldContextName}'. Check the spelling of the field name within the definition of '${fieldContextName}'.`,
|
|
140
|
-
`Suggestion: Ensure the field is defined directly or inherited correctly in the '${fieldContextName}' ${fieldContextType}. You can inspect the ${fieldContextType}'s fields using malloy_getContext.`,
|
|
141
|
-
);
|
|
142
|
-
} else if (referenceErrorMatch) {
|
|
143
|
-
refined = true;
|
|
144
|
-
const objectName = referenceErrorMatch[1] || "[unknown object]";
|
|
145
|
-
suggestions.unshift(
|
|
146
|
-
`Suggestion: Check if the referenced object '${objectName}' (source, query, view, field, dimension, measure) is defined correctly and spelled correctly (e.g., 'source: my_source is ...', 'query: my_query is ...'). Ensure it's defined *before* being used in the model file.`,
|
|
147
|
-
);
|
|
148
|
-
} else if (syntaxErrorMatch) {
|
|
149
|
-
refined = true;
|
|
150
|
-
suggestions.unshift(
|
|
151
|
-
"Suggestion: There seems to be a syntax error near the location indicated in the error message. Check for misplaced/missing keywords (like 'is', '->', 'extend', 'where', 'aggregate', 'group_by', 'nest'), commas, brackets '{ }', or parentheses '( )'. See Query Syntax: https://docs.malloydata.dev/documentation/language/query",
|
|
152
|
-
);
|
|
153
|
-
} else if (connectionErrorMatch) {
|
|
154
|
-
refined = true;
|
|
155
|
-
suggestions.unshift(
|
|
156
|
-
"Suggestion: Verify the database connection details (credentials, host, database name, connection name in Malloy) associated with this model.",
|
|
157
|
-
);
|
|
158
|
-
suggestions.push(
|
|
159
|
-
"Ensure the database server is running and accessible from the publisher server.",
|
|
160
|
-
);
|
|
161
|
-
} else if (invalidRequestMatch) {
|
|
162
|
-
refined = true;
|
|
163
|
-
suggestions.unshift(
|
|
164
|
-
"Suggestion: This error can occur when specifying a view using 'sourceName' and 'queryName'. Try providing the full query string directly in the 'query' parameter instead (e.g., 'query': 'run: your_source_name->your_view_name').",
|
|
165
|
-
"Suggestion: Also verify you are providing either the 'query' parameter OR the correct combination of 'queryName' (and optionally 'sourceName' for views).",
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// If no specific pattern matched, keep the initial suggestions
|
|
170
|
-
if (!refined) {
|
|
171
|
-
suggestions = initialSuggestions;
|
|
172
|
-
}
|
|
173
|
-
} else {
|
|
174
|
-
// Generic message if it's not a standard Error object
|
|
175
|
-
baseMessage += `: ${String(error)}`;
|
|
176
|
-
suggestions = initialSuggestions; // Use initial for non-errors too
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return {
|
|
180
|
-
message: baseMessage,
|
|
181
|
-
suggestions: suggestions,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import { MalloyError } from "@malloydata/malloy";
|
|
3
|
-
import { classifyToolError } from "./handler_utils";
|
|
4
|
-
import {
|
|
5
|
-
AccessDeniedError,
|
|
6
|
-
BadRequestError,
|
|
7
|
-
ModelCompilationError,
|
|
8
|
-
PackageNotFoundError,
|
|
9
|
-
ServiceUnavailableError,
|
|
10
|
-
} from "../errors";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* classifyToolError decides what an agent is told to do about a failure, so
|
|
14
|
-
* every branch is pinned by the advice it produces, not just by "an error came
|
|
15
|
-
* back". Asserting only that an error exists is what let every class funnel
|
|
16
|
-
* through the Malloy helper unnoticed.
|
|
17
|
-
*
|
|
18
|
-
* The routing is by error CLASS on purpose. getMalloyErrorDetails refines on
|
|
19
|
-
* message patterns the shipped engine does not emit, so "did it refine?" would
|
|
20
|
-
* send real compile errors to the internal branch.
|
|
21
|
-
*/
|
|
22
|
-
describe("classifyToolError", () => {
|
|
23
|
-
const advice = (e: unknown) =>
|
|
24
|
-
JSON.stringify(classifyToolError("op", "env/pkg", e).suggestions);
|
|
25
|
-
|
|
26
|
-
it("homes a missing package as not-found, not as Malloy", () => {
|
|
27
|
-
const details = classifyToolError(
|
|
28
|
-
"op",
|
|
29
|
-
"env/pkg",
|
|
30
|
-
new PackageNotFoundError("Package 'nope' not found"),
|
|
31
|
-
);
|
|
32
|
-
expect(details.message).toContain("Resource not found");
|
|
33
|
-
expect(JSON.stringify(details.suggestions)).not.toContain("Malloy file");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("homes back-pressure as retryable, not as Malloy", () => {
|
|
37
|
-
const details = classifyToolError(
|
|
38
|
-
"op",
|
|
39
|
-
"env/pkg",
|
|
40
|
-
new ServiceUnavailableError("Memory limit reached"),
|
|
41
|
-
);
|
|
42
|
-
expect(details.message).toContain("Memory limit reached");
|
|
43
|
-
expect(JSON.stringify(details.suggestions)).toContain("Retry");
|
|
44
|
-
expect(JSON.stringify(details.suggestions)).not.toContain("Malloy file");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("keeps Malloy advice for a raw engine error", () => {
|
|
48
|
-
// The regression guard for executeQuery. A bad query throws MalloyError,
|
|
49
|
-
// NOT ModelCompilationError: the engine's error reaches the tool's catch
|
|
50
|
-
// unwrapped. Route it by the internal branch and a syntax error tells the
|
|
51
|
-
// agent an "unexpected internal error occurred", which is worse than the
|
|
52
|
-
// bug this classifier was written to fix.
|
|
53
|
-
const details = classifyToolError(
|
|
54
|
-
"executeQuery",
|
|
55
|
-
"env/pkg/m.malloy",
|
|
56
|
-
new MalloyError("unexpected '@'", []),
|
|
57
|
-
);
|
|
58
|
-
expect(details.message).not.toContain("unexpected internal error");
|
|
59
|
-
expect(advice(new MalloyError("unexpected '@'", []))).toContain("Malloy");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("keeps Malloy advice for a wrapped compile error", () => {
|
|
63
|
-
// What a failed reload throws. #890's promise is that a failed reload
|
|
64
|
-
// returns the compile errors, so this must not read as an internal fault.
|
|
65
|
-
const details = classifyToolError(
|
|
66
|
-
"reloadPackage",
|
|
67
|
-
"env/pkg",
|
|
68
|
-
new ModelCompilationError({
|
|
69
|
-
message: "Error(s) compiling model: unexpected '@'",
|
|
70
|
-
}),
|
|
71
|
-
);
|
|
72
|
-
expect(details.message).not.toContain("unexpected internal error");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("keeps Malloy advice for an authorize denial and a malformed request", () => {
|
|
76
|
-
expect(
|
|
77
|
-
classifyToolError(
|
|
78
|
-
"op",
|
|
79
|
-
"env/pkg",
|
|
80
|
-
new AccessDeniedError('Access denied for source "orders"'),
|
|
81
|
-
).message,
|
|
82
|
-
).not.toContain("unexpected internal error");
|
|
83
|
-
expect(
|
|
84
|
-
classifyToolError(
|
|
85
|
-
"op",
|
|
86
|
-
"env/pkg",
|
|
87
|
-
new BadRequestError("Invalid query request."),
|
|
88
|
-
).message,
|
|
89
|
-
).not.toContain("unexpected internal error");
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("reports anything else as internal rather than blaming the Malloy", () => {
|
|
93
|
-
// A bug in our own code, a filesystem failure, or a worker crash. Telling
|
|
94
|
-
// the agent to check its syntax sends it to edit a model that is fine.
|
|
95
|
-
for (const error of [
|
|
96
|
-
new TypeError("cannot read properties of undefined"),
|
|
97
|
-
Object.assign(new Error("EACCES: permission denied"), {
|
|
98
|
-
code: "EACCES",
|
|
99
|
-
}),
|
|
100
|
-
]) {
|
|
101
|
-
const details = classifyToolError("op", "env/pkg", error);
|
|
102
|
-
expect(details.message).toContain("unexpected internal error");
|
|
103
|
-
expect(JSON.stringify(details.suggestions)).not.toContain(
|
|
104
|
-
"Malloy file",
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
});
|