@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.
Files changed (319) hide show
  1. package/dist/app/api-doc.yaml +83 -27
  2. package/dist/app/assets/{EnvironmentPage-BsAnavYN.js → EnvironmentPage-DaP6CpgK.js} +1 -1
  3. package/dist/app/assets/{HomePage-CADE138j.js → HomePage-C9yNw3iu.js} +1 -1
  4. package/dist/app/assets/{LightMode-Cfh7KzN8.js → LightMode-Be4-G-dX.js} +1 -1
  5. package/dist/app/assets/{MainPage-CO3pRlnV.js → MainPage-J_rU2SgI.js} +2 -2
  6. package/dist/app/assets/{MaterializationsPage-p9YjkRXZ.js → MaterializationsPage-DHgPI4MO.js} +1 -1
  7. package/dist/app/assets/ModelPage-BzXfjTu5.js +1 -0
  8. package/dist/app/assets/{PackagePage-e4kN75YR.js → PackagePage-ByoSTLJ4.js} +1 -1
  9. package/dist/app/assets/RouteError-CrSeKN2r.js +1 -0
  10. package/dist/app/assets/{ThemeEditorPage-CciagFTq.js → ThemeEditorPage-Btxnmhtk.js} +1 -1
  11. package/dist/app/assets/{WorkbookPage-DNWmkCXa.js → WorkbookPage-D6JLPnWO.js} +1 -1
  12. package/dist/app/assets/{core-Rj_4rRnA.es-BZyvITuO.js → core-BSRgt1mG.es-Bc_D2Lq4.js} +1 -1
  13. package/dist/app/assets/{index-CH2AcDzc.js → index-BRijWj6K.js} +15 -15
  14. package/dist/app/assets/{index-DQa463gC.js → index-BX4D94xw.js} +1 -1
  15. package/dist/app/assets/{index-VBbcc8s6.js → index-Cvy0xtb7.js} +1 -1
  16. package/dist/app/assets/{index-DQpV7MyA.js → index-DMAhIJNC.js} +1 -1
  17. package/dist/app/index.html +1 -1
  18. package/dist/package_load_worker.mjs +59 -20
  19. package/dist/server.mjs +1342 -616
  20. package/package.json +8 -2
  21. package/.eslintrc.json +0 -44
  22. package/.prettierignore +0 -1
  23. package/.prettierrc +0 -4
  24. package/build.ts +0 -97
  25. package/dist/app/assets/ModelPage-C1OSTv-x.js +0 -1
  26. package/dist/app/assets/RouteError-CzbfOkng.js +0 -1
  27. package/eslint.config.mjs +0 -76
  28. package/k6-tests/README.md +0 -144
  29. package/k6-tests/bun.lock +0 -797
  30. package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
  31. package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
  32. package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
  33. package/k6-tests/load-test/load-test-crud.ts +0 -142
  34. package/k6-tests/load-test/load-test.ts +0 -784
  35. package/k6-tests/package.json +0 -27
  36. package/k6-tests/scripts/run-with-otel.sh +0 -50
  37. package/k6-tests/smoke-test/smoke-test.ts +0 -158
  38. package/k6-tests/tsconfig.json +0 -29
  39. package/k6-tests/utils/client_factory.ts +0 -128
  40. package/k6-tests/utils/common.ts +0 -489
  41. package/k6-tests/utils/logger.ts +0 -62
  42. package/k6-tests/utils/types.d.ts +0 -35
  43. package/malloy_mcp.dxt +0 -0
  44. package/publisher.config.example.bigquery.json +0 -33
  45. package/publisher.config.example.duckdb.json +0 -23
  46. package/publisher.config.json +0 -23
  47. package/scripts/bake-duckdb-extensions.js +0 -110
  48. package/src/config.spec.ts +0 -1535
  49. package/src/config.theme.spec.ts +0 -178
  50. package/src/config.ts +0 -1142
  51. package/src/constants.ts +0 -105
  52. package/src/controller/compile.controller.ts +0 -40
  53. package/src/controller/connection.controller.spec.ts +0 -849
  54. package/src/controller/connection.controller.ts +0 -901
  55. package/src/controller/database.controller.ts +0 -24
  56. package/src/controller/materialization.controller.spec.ts +0 -293
  57. package/src/controller/materialization.controller.ts +0 -319
  58. package/src/controller/model.controller.ts +0 -164
  59. package/src/controller/package.controller.spec.ts +0 -273
  60. package/src/controller/package.controller.ts +0 -306
  61. package/src/controller/query.controller.ts +0 -152
  62. package/src/controller/theme.controller.ts +0 -83
  63. package/src/controller/watch-mode.controller.ts +0 -243
  64. package/src/data_styles.ts +0 -71
  65. package/src/default-publisher.config.json +0 -23
  66. package/src/ducklake_version.spec.ts +0 -163
  67. package/src/ducklake_version.ts +0 -153
  68. package/src/errors.spec.ts +0 -119
  69. package/src/errors.ts +0 -237
  70. package/src/filter_deprecation.spec.ts +0 -64
  71. package/src/filter_deprecation.ts +0 -42
  72. package/src/health.spec.ts +0 -123
  73. package/src/health.ts +0 -233
  74. package/src/heap_check.spec.ts +0 -144
  75. package/src/heap_check.ts +0 -144
  76. package/src/instrumentation.ts +0 -214
  77. package/src/json_utils.spec.ts +0 -51
  78. package/src/json_utils.ts +0 -33
  79. package/src/logger.spec.ts +0 -210
  80. package/src/logger.ts +0 -251
  81. package/src/malloy_pin_prereqs.spec.ts +0 -25
  82. package/src/materialization_metrics.spec.ts +0 -169
  83. package/src/materialization_metrics.ts +0 -346
  84. package/src/mcp/error_messages.spec.ts +0 -35
  85. package/src/mcp/error_messages.ts +0 -183
  86. package/src/mcp/handler_utils.spec.ts +0 -108
  87. package/src/mcp/handler_utils.ts +0 -251
  88. package/src/mcp/mcp_constants.ts +0 -6
  89. package/src/mcp/query_envelope.spec.ts +0 -229
  90. package/src/mcp/query_envelope.ts +0 -240
  91. package/src/mcp/server.protocol.spec.ts +0 -250
  92. package/src/mcp/server.ts +0 -87
  93. package/src/mcp/skills/build_skills_bundle.spec.ts +0 -51
  94. package/src/mcp/skills/build_skills_bundle.ts +0 -176
  95. package/src/mcp/skills/skills_bundle.json +0 -1
  96. package/src/mcp/skills/skills_bundle.spec.ts +0 -171
  97. package/src/mcp/tool_response.spec.ts +0 -108
  98. package/src/mcp/tool_response.ts +0 -138
  99. package/src/mcp/tools/compile_tool.spec.ts +0 -315
  100. package/src/mcp/tools/compile_tool.ts +0 -208
  101. package/src/mcp/tools/docs_search/build_docs_index.ts +0 -132
  102. package/src/mcp/tools/docs_search/malloy_docs_index.json +0 -1
  103. package/src/mcp/tools/docs_search_tool.spec.ts +0 -32
  104. package/src/mcp/tools/docs_search_tool.ts +0 -128
  105. package/src/mcp/tools/embedding_index.spec.ts +0 -1236
  106. package/src/mcp/tools/embedding_index.ts +0 -808
  107. package/src/mcp/tools/execute_query_tool.spec.ts +0 -294
  108. package/src/mcp/tools/execute_query_tool.ts +0 -316
  109. package/src/mcp/tools/get_context_eval.ts +0 -275
  110. package/src/mcp/tools/get_context_tool.spec.ts +0 -592
  111. package/src/mcp/tools/get_context_tool.ts +0 -585
  112. package/src/mcp/tools/reload_package_tool.spec.ts +0 -232
  113. package/src/mcp/tools/reload_package_tool.ts +0 -132
  114. package/src/mcp_config.spec.ts +0 -919
  115. package/src/mcp_config.ts +0 -425
  116. package/src/oom_guards.integration.spec.ts +0 -269
  117. package/src/package_load/package_load_pool.spec.ts +0 -252
  118. package/src/package_load/package_load_pool.ts +0 -931
  119. package/src/package_load/package_load_worker.ts +0 -1034
  120. package/src/package_load/protocol.ts +0 -364
  121. package/src/package_load/rpc_wait_accountant.spec.ts +0 -109
  122. package/src/package_load/rpc_wait_accountant.ts +0 -76
  123. package/src/package_load_metrics.spec.ts +0 -114
  124. package/src/package_load_metrics.ts +0 -127
  125. package/src/path_safety.spec.ts +0 -158
  126. package/src/path_safety.ts +0 -146
  127. package/src/pg_helpers.spec.ts +0 -223
  128. package/src/pg_helpers.ts +0 -52
  129. package/src/query_cap_metrics.spec.ts +0 -89
  130. package/src/query_cap_metrics.ts +0 -116
  131. package/src/query_concurrency.spec.ts +0 -247
  132. package/src/query_concurrency.ts +0 -237
  133. package/src/query_metadata_metrics.ts +0 -49
  134. package/src/query_param_utils.ts +0 -29
  135. package/src/query_timeout.spec.ts +0 -224
  136. package/src/query_timeout.ts +0 -179
  137. package/src/runtime/publisher.js +0 -328
  138. package/src/server-old.ts +0 -988
  139. package/src/server.ts +0 -2059
  140. package/src/service/annotations.spec.ts +0 -118
  141. package/src/service/annotations.ts +0 -91
  142. package/src/service/authorize.spec.ts +0 -154
  143. package/src/service/authorize.ts +0 -498
  144. package/src/service/authorize_integration.spec.ts +0 -2041
  145. package/src/service/build_plan.spec.ts +0 -685
  146. package/src/service/build_plan.ts +0 -797
  147. package/src/service/compile_authorize.spec.ts +0 -85
  148. package/src/service/compile_fragment_techniques.spec.ts +0 -156
  149. package/src/service/connection.spec.ts +0 -2360
  150. package/src/service/connection.ts +0 -2187
  151. package/src/service/connection_config.spec.ts +0 -734
  152. package/src/service/connection_config.ts +0 -899
  153. package/src/service/connection_federation.spec.ts +0 -184
  154. package/src/service/connection_fingerprint.spec.ts +0 -102
  155. package/src/service/connection_service.spec.ts +0 -711
  156. package/src/service/connection_service.ts +0 -291
  157. package/src/service/cron_evaluator.spec.ts +0 -79
  158. package/src/service/cron_evaluator.ts +0 -105
  159. package/src/service/db_utils.spec.ts +0 -1085
  160. package/src/service/db_utils.ts +0 -1502
  161. package/src/service/duckdb_instance_isolation.spec.ts +0 -137
  162. package/src/service/ducklake_lazy_attach.spec.ts +0 -110
  163. package/src/service/embedding_provider.spec.ts +0 -329
  164. package/src/service/embedding_provider.ts +0 -236
  165. package/src/service/environment.ts +0 -2191
  166. package/src/service/environment_admission.spec.ts +0 -344
  167. package/src/service/environment_compile.spec.ts +0 -197
  168. package/src/service/environment_store.spec.ts +0 -2274
  169. package/src/service/environment_store.ts +0 -2434
  170. package/src/service/environment_store_anchoring.spec.ts +0 -107
  171. package/src/service/environment_store_clone.spec.ts +0 -350
  172. package/src/service/explore_visibility.spec.ts +0 -434
  173. package/src/service/exports_probe.spec.ts +0 -107
  174. package/src/service/extension_fetch_policy.spec.ts +0 -256
  175. package/src/service/filter.spec.ts +0 -458
  176. package/src/service/filter.ts +0 -341
  177. package/src/service/filter_bypass.spec.ts +0 -418
  178. package/src/service/filter_integration.spec.ts +0 -1004
  179. package/src/service/freshness.spec.ts +0 -183
  180. package/src/service/freshness.ts +0 -112
  181. package/src/service/gcs_s3_utils.ts +0 -427
  182. package/src/service/given.ts +0 -105
  183. package/src/service/givens_integration.spec.ts +0 -219
  184. package/src/service/manifest_loader.spec.ts +0 -189
  185. package/src/service/manifest_loader.ts +0 -156
  186. package/src/service/materialization_build_session.spec.ts +0 -435
  187. package/src/service/materialization_build_session.ts +0 -681
  188. package/src/service/materialization_config_validation.spec.ts +0 -99
  189. package/src/service/materialization_config_validation.ts +0 -120
  190. package/src/service/materialization_eligibility.spec.ts +0 -158
  191. package/src/service/materialization_eligibility.ts +0 -305
  192. package/src/service/materialization_schedule_surface.spec.ts +0 -256
  193. package/src/service/materialization_scheduler.spec.ts +0 -289
  194. package/src/service/materialization_scheduler.ts +0 -304
  195. package/src/service/materialization_serve_transform.spec.ts +0 -1003
  196. package/src/service/materialization_serve_transform.ts +0 -779
  197. package/src/service/materialization_service.spec.ts +0 -2373
  198. package/src/service/materialization_service.ts +0 -2342
  199. package/src/service/materialization_test_fixtures.ts +0 -249
  200. package/src/service/model.spec.ts +0 -1157
  201. package/src/service/model.ts +0 -3370
  202. package/src/service/model_limits.spec.ts +0 -209
  203. package/src/service/model_limits.ts +0 -131
  204. package/src/service/model_storage_serve.spec.ts +0 -193
  205. package/src/service/model_storage_serve_joins.spec.ts +0 -193
  206. package/src/service/package.spec.ts +0 -612
  207. package/src/service/package.ts +0 -1799
  208. package/src/service/package_manifest.spec.ts +0 -230
  209. package/src/service/package_manifest.ts +0 -241
  210. package/src/service/package_memory_governor.spec.ts +0 -173
  211. package/src/service/package_memory_governor.ts +0 -233
  212. package/src/service/package_quote_bound_tables.spec.ts +0 -171
  213. package/src/service/package_race.spec.ts +0 -208
  214. package/src/service/package_reload_safety.spec.ts +0 -193
  215. package/src/service/package_rollback.spec.ts +0 -190
  216. package/src/service/package_worker_path.spec.ts +0 -645
  217. package/src/service/path_injection.spec.ts +0 -39
  218. package/src/service/persist_annotation_validation.spec.ts +0 -89
  219. package/src/service/persist_annotation_validation.ts +0 -59
  220. package/src/service/persistence_policy.spec.ts +0 -374
  221. package/src/service/proxy.spec.ts +0 -414
  222. package/src/service/proxy.ts +0 -248
  223. package/src/service/query_boundary.spec.ts +0 -470
  224. package/src/service/query_metadata.spec.ts +0 -408
  225. package/src/service/query_metadata.ts +0 -492
  226. package/src/service/query_metadata_identity.spec.ts +0 -149
  227. package/src/service/query_text.spec.ts +0 -79
  228. package/src/service/query_text.ts +0 -43
  229. package/src/service/quoting.spec.ts +0 -135
  230. package/src/service/quoting.ts +0 -78
  231. package/src/service/resolve_environment.ts +0 -15
  232. package/src/service/restricted_mode.spec.ts +0 -299
  233. package/src/service/source_extraction.ts +0 -225
  234. package/src/service/theme_store.ts +0 -199
  235. package/src/storage/DatabaseInterface.ts +0 -198
  236. package/src/storage/StorageManager.ts +0 -113
  237. package/src/storage/duckdb/ConnectionRepository.ts +0 -143
  238. package/src/storage/duckdb/DuckDBConnection.ts +0 -123
  239. package/src/storage/duckdb/DuckDBRepository.ts +0 -208
  240. package/src/storage/duckdb/EnvironmentRepository.ts +0 -148
  241. package/src/storage/duckdb/MaterializationRepository.spec.ts +0 -191
  242. package/src/storage/duckdb/MaterializationRepository.ts +0 -336
  243. package/src/storage/duckdb/PackageRepository.ts +0 -135
  244. package/src/storage/duckdb/schema.ts +0 -229
  245. package/src/stream_helpers.spec.ts +0 -280
  246. package/src/stream_helpers.ts +0 -162
  247. package/src/telemetry.ts +0 -20
  248. package/src/test_helpers/metrics_harness.ts +0 -166
  249. package/src/theme_key_parity.spec.ts +0 -57
  250. package/src/utils.ts +0 -29
  251. package/tests/fixtures/authorize-compile/model.malloy +0 -9
  252. package/tests/fixtures/authorize-compile/publisher.json +0 -4
  253. package/tests/fixtures/html-pages-nopublic/model.malloy +0 -1
  254. package/tests/fixtures/html-pages-nopublic/publisher.json +0 -5
  255. package/tests/fixtures/html-pages-test/data.csv +0 -3
  256. package/tests/fixtures/html-pages-test/public/assets/app.css +0 -3
  257. package/tests/fixtures/html-pages-test/public/barehtml.html +0 -4
  258. package/tests/fixtures/html-pages-test/public/bodymeta.html +0 -8
  259. package/tests/fixtures/html-pages-test/public/data.json +0 -1
  260. package/tests/fixtures/html-pages-test/public/fitcomment.html +0 -11
  261. package/tests/fixtures/html-pages-test/public/index.html +0 -9
  262. package/tests/fixtures/html-pages-test/public/nohead.html +0 -8
  263. package/tests/fixtures/html-pages-test/public/notfit.html +0 -13
  264. package/tests/fixtures/html-pages-test/public/slides.html +0 -12
  265. package/tests/fixtures/html-pages-test/public/sub/page2.html +0 -9
  266. package/tests/fixtures/html-pages-test/public/unterminated.html +0 -10
  267. package/tests/fixtures/html-pages-test/publisher.json +0 -5
  268. package/tests/fixtures/html-pages-test/report.malloy +0 -1
  269. package/tests/fixtures/persist-multi-level/data/orders.csv +0 -5
  270. package/tests/fixtures/persist-multi-level/multi_level.malloy +0 -18
  271. package/tests/fixtures/persist-multi-level/publisher.json +0 -5
  272. package/tests/fixtures/persist-schedule-test/data/orders.csv +0 -5
  273. package/tests/fixtures/persist-schedule-test/persist_schedule_test.malloy +0 -11
  274. package/tests/fixtures/persist-schedule-test/publisher.json +0 -7
  275. package/tests/fixtures/persist-schedule-warn-test/data/orders.csv +0 -5
  276. package/tests/fixtures/persist-schedule-warn-test/persist_warn_test.malloy +0 -11
  277. package/tests/fixtures/persist-schedule-warn-test/publisher.json +0 -6
  278. package/tests/fixtures/persist-test/data/orders.csv +0 -5
  279. package/tests/fixtures/persist-test/persist_test.malloy +0 -11
  280. package/tests/fixtures/persist-test/publisher.json +0 -5
  281. package/tests/fixtures/publisher.config.json +0 -15
  282. package/tests/fixtures/query-givens/data/orders.csv +0 -7
  283. package/tests/fixtures/query-givens/model.malloy +0 -34
  284. package/tests/fixtures/query-givens/publisher.json +0 -5
  285. package/tests/fixtures/xlsx/database.xlsx +0 -0
  286. package/tests/harness/mcp_test_setup.ts +0 -332
  287. package/tests/harness/rest_e2e.ts +0 -68
  288. package/tests/integration/authorize/compile_authorize_http.integration.spec.ts +0 -92
  289. package/tests/integration/concurrent_package/concurrent_package.integration.spec.ts +0 -280
  290. package/tests/integration/duckdb_storage/duckdb_storage.integration.spec.ts +0 -138
  291. package/tests/integration/first_boot/readiness_line.integration.spec.ts +0 -177
  292. package/tests/integration/html_pages/html_pages.integration.spec.ts +0 -440
  293. package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +0 -259
  294. package/tests/integration/materialization/environment_list.integration.spec.ts +0 -133
  295. package/tests/integration/materialization/freshness_gate.integration.spec.ts +0 -292
  296. package/tests/integration/materialization/manifest_binding.integration.spec.ts +0 -430
  297. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -441
  298. package/tests/integration/materialization/orchestrated_rebind.integration.spec.ts +0 -150
  299. package/tests/integration/materialization/reference_manifest.integration.spec.ts +0 -251
  300. package/tests/integration/materialization/schedule_edit.integration.spec.ts +0 -126
  301. package/tests/integration/materialization/scheduler.integration.spec.ts +0 -200
  302. package/tests/integration/materialization/scheduler_recovery.integration.spec.ts +0 -172
  303. package/tests/integration/materialization/scheduler_transitions.integration.spec.ts +0 -256
  304. package/tests/integration/materialization/scheduler_wiring.integration.spec.ts +0 -143
  305. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -438
  306. package/tests/integration/mcp/mcp_get_context_semantic.integration.spec.ts +0 -235
  307. package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -98
  308. package/tests/integration/query_givens/query_givens.integration.spec.ts +0 -146
  309. package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +0 -121
  310. package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +0 -110
  311. package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -421
  312. package/tests/unit/controller/theme.controller.spec.ts +0 -141
  313. package/tests/unit/duckdb/attached_databases.test.ts +0 -1243
  314. package/tests/unit/duckdb/duckdb_connection.test.ts +0 -181
  315. package/tests/unit/duckdb/legacy_schema_migration.test.ts +0 -197
  316. package/tests/unit/duckdb/repositories.test.ts +0 -208
  317. package/tests/unit/ducklake/ducklake.test.ts +0 -952
  318. package/tests/unit/service/theme_store.spec.ts +0 -139
  319. package/tsconfig.json +0 -32
@@ -1,306 +0,0 @@
1
- import { components } from "../api";
2
- import { normalizeModelPath } from "../constants";
3
- import { BadRequestError, FrozenConfigError } from "../errors";
4
- import { EnvironmentStore } from "../service/environment_store";
5
-
6
- type ApiPackage = components["schemas"]["Package"];
7
-
8
- /**
9
- * Which path a reload took. `in-place` recompiles the tree already on disk and
10
- * leaves it alone; `reinstalled` re-fetches from the package's install location,
11
- * which overwrites on-disk edits.
12
- */
13
- export type PackageReloadMode = "in-place" | "reinstalled";
14
-
15
- /**
16
- * Everything that is strict-at-publish, joined into one 400 message (or
17
- * undefined when the package is publishable): invalid explores entries plus
18
- * the Malloy Persistence policy gate (scope is package-level; a
19
- * `materialization.schedule` is package-root-only + version-scope-only and
20
- * mutually exclusive with freshness; per-source `sharing`/`schedule` are
21
- * retired — see Package.persistencePolicyWarnings), plus persist-target
22
- * collisions ONLY when `PERSIST_COLLISION_ENFORCE` is set (otherwise those are
23
- * surfaced warn-only so a pre-existing latent collision doesn't block a routine
24
- * re-publish — see Package.formatPersistenceCollisionRejections). At
25
- * startup/reload all are warn-only instead (fail-safe; see Package.loadViaWorker).
26
- */
27
- function formatPublishRejections(
28
- pkg: {
29
- formatInvalidExplores(exploresOverride?: string[]): string;
30
- formatInvalidPersistencePolicy(): string;
31
- formatPersistenceCollisionRejections(): string;
32
- },
33
- exploresOverride?: string[],
34
- ): string | undefined {
35
- const message = [
36
- pkg.formatInvalidExplores(exploresOverride),
37
- pkg.formatInvalidPersistencePolicy(),
38
- pkg.formatPersistenceCollisionRejections(),
39
- ]
40
- .filter(Boolean)
41
- .join("\n");
42
- return message || undefined;
43
- }
44
-
45
- export class PackageController {
46
- private environmentStore: EnvironmentStore;
47
-
48
- constructor(environmentStore: EnvironmentStore) {
49
- this.environmentStore = environmentStore;
50
- }
51
-
52
- public async listPackages(environmentName: string): Promise<ApiPackage[]> {
53
- const environment = await this.environmentStore.getEnvironment(
54
- environmentName,
55
- false,
56
- );
57
- return environment.listPackages();
58
- }
59
-
60
- public async getPackage(
61
- environmentName: string,
62
- packageName: string,
63
- reload: boolean,
64
- ): Promise<ApiPackage> {
65
- if (reload) {
66
- return (await this.reloadPackage(environmentName, packageName))
67
- .metadata;
68
- }
69
-
70
- const environment = await this.environmentStore.getEnvironment(
71
- environmentName,
72
- false,
73
- );
74
- const _package = await environment.getPackage(packageName, false);
75
- return _package.getPackageMetadata();
76
- }
77
-
78
- /**
79
- * Reload a package and report WHICH path ran. The two are not equivalent to
80
- * anyone holding on-disk edits: an in-place reload recompiles the tree that
81
- * is already there, while a reinstall re-fetches from the package's install
82
- * location and overwrites it. A caller that surfaces the reload to a user
83
- * needs to be able to say which happened, so it is returned rather than
84
- * inferred. `getPackage` keeps returning bare metadata, so the REST contract
85
- * is unchanged.
86
- */
87
- public async reloadPackage(
88
- environmentName: string,
89
- packageName: string,
90
- ): Promise<{ metadata: ApiPackage; mode: PackageReloadMode }> {
91
- const environment = await this.environmentStore.getEnvironment(
92
- environmentName,
93
- false,
94
- );
95
-
96
- // Resolve the package's source location from the currently-cached
97
- // metadata WITHOUT triggering a stale-state reload. If a `location`
98
- // is set, route the reload through `installPackage` so that
99
- // download-then-load happens atomically; otherwise fall back to an
100
- // in-place reload of the existing on-disk content.
101
- let location: string | undefined;
102
- try {
103
- const cached = await environment.getPackage(packageName, false);
104
- location = cached.getPackageMetadata().location;
105
- } catch {
106
- // Not previously loaded, so there is nothing to reinstall from.
107
- }
108
- if (location) {
109
- const reinstalled = await environment.installPackage(
110
- packageName,
111
- (stagingPath) =>
112
- this.downloadInto(
113
- environmentName,
114
- packageName,
115
- location,
116
- stagingPath,
117
- ),
118
- );
119
- return {
120
- metadata: reinstalled.getPackageMetadata(),
121
- mode: "reinstalled",
122
- };
123
- }
124
- const _package = await environment.getPackage(packageName, true);
125
- return { metadata: _package.getPackageMetadata(), mode: "in-place" };
126
- }
127
-
128
- async addPackage(environmentName: string, body: ApiPackage) {
129
- if (this.environmentStore.publisherConfigIsFrozen) {
130
- throw new FrozenConfigError();
131
- }
132
- if (!body.name) {
133
- throw new BadRequestError("Package name is required");
134
- }
135
- const packageName = body.name;
136
- const environment = await this.environmentStore.getEnvironment(
137
- environmentName,
138
- false,
139
- );
140
- // Strict at publish: the author is in the loop here, so reject a bad
141
- // explores with an actionable 400 instead of silently serving a hidden
142
- // surface. (At startup/reload we fail safe and only warn — see
143
- // Package.loadViaWorker.) The rollback differs by path so a rejected
144
- // publish never destroys user content:
145
- // - location: the tree was just downloaded into a fresh canonical, so
146
- // validation runs inside installPackage's swap window and a failure
147
- // wipes that download (the existing rollback) — nothing pre-existing
148
- // to lose.
149
- // - no-location: addPackage registered a *pre-existing* user directory,
150
- // so we validate after the fact and `unloadPackage` (evict from
151
- // memory, keep the files) rather than delete it.
152
- let result;
153
- if (body.location) {
154
- const bodyLocation = body.location;
155
- result = await environment.installPackage(
156
- packageName,
157
- (stagingPath) =>
158
- this.downloadInto(
159
- environmentName,
160
- packageName,
161
- bodyLocation,
162
- stagingPath,
163
- ),
164
- (pkg) => formatPublishRejections(pkg),
165
- );
166
- } else {
167
- result = await environment.addPackage(packageName);
168
- }
169
-
170
- // `addPackage`/`installPackage` are typed `Package | undefined`; a missing
171
- // result here is a should-never-happen internal fault. Fail loudly rather
172
- // than letting optional chaining silently skip the validation below.
173
- if (!result) {
174
- throw new Error(`Failed to create package ${packageName}`);
175
- }
176
-
177
- if (!body.location) {
178
- const invalidMsg = formatPublishRejections(result);
179
- if (invalidMsg) {
180
- await environment.unloadPackage(packageName).catch(() => {
181
- /* best-effort; the package is not persisted below */
182
- });
183
- throw new BadRequestError(invalidMsg);
184
- }
185
- }
186
-
187
- await this.environmentStore.addPackageToDatabase(
188
- environmentName,
189
- packageName,
190
- );
191
-
192
- return result;
193
- }
194
-
195
- public async deletePackage(environmentName: string, packageName: string) {
196
- if (this.environmentStore.publisherConfigIsFrozen) {
197
- throw new FrozenConfigError();
198
- }
199
- const environment = await this.environmentStore.getEnvironment(
200
- environmentName,
201
- false,
202
- );
203
- const result = await environment.deletePackage(packageName);
204
- await this.environmentStore.deletePackageFromDatabase(
205
- environmentName,
206
- packageName,
207
- );
208
-
209
- return result;
210
- }
211
-
212
- public async updatePackage(
213
- environmentName: string,
214
- packageName: string,
215
- body: ApiPackage,
216
- ) {
217
- if (this.environmentStore.publisherConfigIsFrozen) {
218
- throw new FrozenConfigError();
219
- }
220
- const environment = await this.environmentStore.getEnvironment(
221
- environmentName,
222
- false,
223
- );
224
- if (body.location) {
225
- // Re-install: stream the new content into a staging dir (no lock)
226
- // and atomically swap it in (under the lock). Validate the effective
227
- // explores (the body override, else the new tree's own manifest)
228
- // INSIDE the swap window, so a rejected update rolls back to the
229
- // previous tree instead of swapping the bad one in and 400-ing after.
230
- const bodyLocation = body.location;
231
- await environment.installPackage(
232
- packageName,
233
- (stagingPath) =>
234
- this.downloadInto(
235
- environmentName,
236
- packageName,
237
- bodyLocation,
238
- stagingPath,
239
- ),
240
- (pkg) =>
241
- formatPublishRejections(
242
- pkg,
243
- body.explores?.map(normalizeModelPath),
244
- ),
245
- );
246
- }
247
- // Apply metadata changes (publisher.json) under the same per-package
248
- // mutex via `Environment.updatePackage`.
249
- const result = await environment.updatePackage(packageName, body);
250
- await this.environmentStore.addPackageToDatabase(
251
- environmentName,
252
- packageName,
253
- );
254
-
255
- return result;
256
- }
257
-
258
- /**
259
- * Run the right downloader for the given location into `targetPath`.
260
- * Callers pass a sibling staging dir (not the canonical package
261
- * directory) so the long-running download doesn't hold the per-package
262
- * mutex.
263
- */
264
- private async downloadInto(
265
- environmentName: string,
266
- packageName: string,
267
- packageLocation: string,
268
- targetPath: string,
269
- ) {
270
- const isCompressedFile = packageLocation.endsWith(".zip");
271
- if (
272
- packageLocation.startsWith("https://") ||
273
- packageLocation.startsWith("git@")
274
- ) {
275
- await this.environmentStore.downloadGitHubDirectory(
276
- packageLocation,
277
- targetPath,
278
- );
279
- } else if (packageLocation.startsWith("gs://")) {
280
- await this.environmentStore.downloadGcsDirectory(
281
- packageLocation,
282
- environmentName,
283
- targetPath,
284
- isCompressedFile,
285
- );
286
- } else if (packageLocation.startsWith("s3://")) {
287
- await this.environmentStore.downloadS3Directory(
288
- packageLocation,
289
- environmentName,
290
- targetPath,
291
- isCompressedFile,
292
- );
293
- }
294
-
295
- if (packageLocation.startsWith("/")) {
296
- // Absolute paths from the publisher.config could be placed outside of /etc/publisher,
297
- // so we need to mount them on the right place.
298
- await this.environmentStore.mountLocalDirectory(
299
- packageLocation,
300
- targetPath,
301
- environmentName,
302
- packageName,
303
- );
304
- }
305
- }
306
- }
@@ -1,152 +0,0 @@
1
- import { validateRenderTags } from "@malloydata/render-validator";
2
- import { components } from "../api";
3
- import { getQueryTimeoutMs } from "../config";
4
- import { API_PREFIX } from "../constants";
5
- import { BadRequestError, ModelNotFoundError } from "../errors";
6
- import { bigIntReplacer } from "../json_utils";
7
- import { logger } from "../logger";
8
- import {
9
- mintCorrelationId,
10
- parseQueryClass,
11
- parseSuppliedQueryMetadata,
12
- type QueryClass,
13
- type QueryMetadata,
14
- } from "../service/query_metadata";
15
- import { runWithQueryTimeout } from "../query_timeout";
16
- import { EnvironmentStore } from "../service/environment_store";
17
- import type { FilterParams } from "../service/filter";
18
- import type { GivenValue } from "@malloydata/malloy";
19
-
20
- type ApiQuery = components["schemas"]["QueryResult"];
21
-
22
- export class QueryController {
23
- private environmentStore: EnvironmentStore;
24
-
25
- constructor(environmentStore: EnvironmentStore) {
26
- this.environmentStore = environmentStore;
27
- }
28
-
29
- public async getQuery(
30
- environmentName: string,
31
- packageName: string,
32
- modelPath: string,
33
- sourceName: string,
34
- queryName: string,
35
- query: string,
36
- compactJson: boolean = false,
37
- filterParams?: FilterParams,
38
- bypassFilters?: boolean,
39
- givens?: Record<string, GivenValue>,
40
- /**
41
- * The request's per-query metadata fields, unvalidated — this controller is
42
- * the boundary that turns a bad bag into a 400 rather than letting the
43
- * connector refuse the statement at dispatch.
44
- */
45
- metadata?: {
46
- queryMetadata?: unknown;
47
- queryClass?: unknown;
48
- versionId?: string;
49
- },
50
- ): Promise<ApiQuery> {
51
- let requestMetadata: QueryMetadata | undefined;
52
- let queryClass: QueryClass | undefined;
53
- try {
54
- requestMetadata = parseSuppliedQueryMetadata(metadata?.queryMetadata);
55
- queryClass = parseQueryClass(metadata?.queryClass);
56
- } catch (error) {
57
- throw new BadRequestError((error as Error).message);
58
- }
59
-
60
- const environment = await this.environmentStore.getEnvironment(
61
- environmentName,
62
- false,
63
- );
64
- // Shed load before any disk / DB work so the publisher returns 503
65
- // immediately under memory pressure instead of starting a query and
66
- // crashing partway through. Already-loaded packages bypass the
67
- // package-load admission gate, so this is the only thing protecting
68
- // query traffic on a hot pod.
69
- environment.assertCanAdmitQuery();
70
- const p = await environment.getPackage(packageName, false);
71
- const model = p.getModel(modelPath);
72
-
73
- if (!model) {
74
- throw new ModelNotFoundError(`${modelPath} does not exist`);
75
- } else {
76
- const {
77
- result,
78
- compactResult,
79
- rowLimit,
80
- rowLimitSource,
81
- queryCorrelationId,
82
- } = await runWithQueryTimeout(
83
- (abortSignal) =>
84
- model.getQueryResults(
85
- sourceName,
86
- queryName,
87
- query,
88
- filterParams,
89
- bypassFilters,
90
- givens,
91
- abortSignal,
92
- {
93
- request: requestMetadata,
94
- queryClass,
95
- environment: environmentName,
96
- // Always undefined today: the route 501s any versionId
97
- // before this runs. Wired so that lifting that rejection
98
- // is the whole change.
99
- version: metadata?.versionId,
100
- // Minted here because this is the boundary that returns
101
- // it; a path with nowhere to put it does not mint one.
102
- correlationId: mintCorrelationId(),
103
- // The environment owns the connection configs, so the
104
- // default and enforced layers are read here rather than
105
- // from the model.
106
- connectionMetadata: (connectionName) => {
107
- try {
108
- const connection =
109
- environment.getApiConnection(connectionName);
110
- return {
111
- default: connection.queryMetadata,
112
- enforced: connection.queryMetadataEnforced,
113
- };
114
- } catch (error) {
115
- // Failing open is right — a tag must not fail a
116
- // query — but this is the one drop with no metric
117
- // behind it, and what it costs is the ENFORCED
118
- // layer. Log it so it is diagnosable.
119
- logger.debug(
120
- "No query-metadata layers for connection",
121
- { connectionName, error },
122
- );
123
- return null;
124
- }
125
- },
126
- },
127
- ),
128
- getQueryTimeoutMs(),
129
- );
130
- const renderLogs = validateRenderTags(result);
131
- return {
132
- result: compactJson
133
- ? JSON.stringify(compactResult, bigIntReplacer)
134
- : JSON.stringify(result),
135
- resource: `${API_PREFIX}/environments/${environmentName}/packages/${packageName}/models/${modelPath}/query`,
136
- renderLogs: renderLogs.length > 0 ? renderLogs : undefined,
137
- // The cap the database applied. A caller counting the rows it got
138
- // back cannot otherwise tell a complete result from one the limit
139
- // cut off: a query with no LIMIT of its own silently gets the
140
- // server default, which is well under the hard ceiling, so nothing
141
- // raises. Deriving it client-side is not possible, since it depends
142
- // on server config and on the query's own LIMIT.
143
- queryRowLimit: rowLimit,
144
- // Which of the two that cap was. Without it a caller cannot tell a
145
- // deliberate `limit:`/`top:` from the silently-applied default, and
146
- // so cannot reproduce the MCP envelope's _limit_hit.
147
- queryRowLimitSource: rowLimitSource,
148
- queryCorrelationId,
149
- } as ApiQuery;
150
- }
151
- }
152
- }
@@ -1,83 +0,0 @@
1
- import { components } from "../api";
2
- import { sanitizeTheme, Theme } from "../config";
3
- import { BadRequestError, FrozenConfigError } from "../errors";
4
- import { isPublisherConfigFrozen } from "../config";
5
- import { ThemeStore } from "../service/theme_store";
6
-
7
- type ApiTheme = components["schemas"]["Theme"];
8
-
9
- /**
10
- * Handlers for the in-app Theme Editor.
11
- *
12
- * GET /api/v0/theme → current saved theme (or built-in default)
13
- * PUT /api/v0/theme → save the supplied theme
14
- * DELETE /api/v0/theme → reset to boot seed / built-in default
15
- */
16
- export class ThemeController {
17
- constructor(
18
- private themeStore: ThemeStore,
19
- private serverRoot: string,
20
- ) {}
21
-
22
- getTheme = async (): Promise<ApiTheme> => {
23
- const theme = await this.themeStore.get();
24
- // Always return an object so the client never has to deal with the
25
- // null/undefined ambiguity. An empty object means "no overrides yet,
26
- // use SDK defaults."
27
- return theme ?? {};
28
- };
29
-
30
- /**
31
- * PUT persists the supplied theme verbatim, including a literal `{}`.
32
- * An empty `{}` is an intentional "clear all overrides" save: it writes
33
- * an (empty) row so the boot seed from publisher.config.json's `theme`
34
- * block is suppressed on subsequent loads. This deliberately differs
35
- * from DELETE (resetTheme), which drops the row and lets that config
36
- * seed re-apply. Note the persisted `"{}"` sanitizes back to `undefined`
37
- * on reload (sanitizeTheme returns undefined for `{}`), but the row
38
- * still exists, so the seed stays suppressed. This PUT-`{}` path is not
39
- * reachable from the Theme Editor UI today (its clear affordance is the
40
- * Reset button, which calls DELETE); it exists only via the API.
41
- */
42
- putTheme = async (body: unknown): Promise<ApiTheme> => {
43
- if (isPublisherConfigFrozen(this.serverRoot)) {
44
- throw new FrozenConfigError(
45
- 'Cannot edit theme: publisher.config.json has "frozenConfig": true.',
46
- );
47
- }
48
- // Treat a literal `{}` as an intentional "clear all overrides" PUT
49
- // (the only other clear path is DELETE, which re-seeds from the
50
- // config file). sanitizeTheme returns undefined for both `{}` and
51
- // garbage, so we have to disambiguate here.
52
- const isEmptyObject =
53
- typeof body === "object" &&
54
- body !== null &&
55
- !Array.isArray(body) &&
56
- Object.keys(body as Record<string, unknown>).length === 0;
57
- const sanitized = sanitizeTheme(body, "PUT /api/v0/theme");
58
- if (!sanitized && !isEmptyObject) {
59
- throw new BadRequestError(
60
- "Theme payload was malformed (expected an object).",
61
- );
62
- }
63
- const saved: Theme = await this.themeStore.set(sanitized ?? {});
64
- return saved;
65
- };
66
-
67
- /**
68
- * DELETE drops the persisted row so the next load re-seeds from
69
- * publisher.config.json's `theme` block (or ends up unthemed if the
70
- * config defines none). Contrast with PUT `{}`, which persists an empty
71
- * row and thereby suppresses that seed instead of restoring it. See
72
- * putTheme for the full divergence.
73
- */
74
- resetTheme = async (): Promise<ApiTheme> => {
75
- if (isPublisherConfigFrozen(this.serverRoot)) {
76
- throw new FrozenConfigError(
77
- 'Cannot reset theme: publisher.config.json has "frozenConfig": true.',
78
- );
79
- }
80
- const reseeded = await this.themeStore.reset();
81
- return reseeded ?? {};
82
- };
83
- }