@malloy-publisher/server 0.0.234 → 0.0.236

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/dist/app/api-doc.yaml +47 -17
  2. package/dist/app/assets/{EnvironmentPage-DTZQ4Gxc.js → EnvironmentPage-BsAnavYN.js} +1 -1
  3. package/dist/app/assets/{HomePage-C5mlDPXK.js → HomePage-CADE138j.js} +1 -1
  4. package/dist/app/assets/{LightMode-DGNmhG0u.js → LightMode-Cfh7KzN8.js} +1 -1
  5. package/dist/app/assets/{MainPage-CVL_wmP4.js → MainPage-CO3pRlnV.js} +1 -1
  6. package/dist/app/assets/{MaterializationsPage-DmzMBCpy.js → MaterializationsPage-p9YjkRXZ.js} +1 -1
  7. package/dist/app/assets/{ModelPage-Dbvf4QbB.js → ModelPage-C1OSTv-x.js} +1 -1
  8. package/dist/app/assets/{PackagePage-DxdHc2Qs.js → PackagePage-e4kN75YR.js} +1 -1
  9. package/dist/app/assets/{RouteError-OJdT4tCd.js → RouteError-CzbfOkng.js} +1 -1
  10. package/dist/app/assets/{ThemeEditorPage-Bk7s0KXY.js → ThemeEditorPage-CciagFTq.js} +1 -1
  11. package/dist/app/assets/{WorkbookPage-j_vCWdN3.js → WorkbookPage-DNWmkCXa.js} +1 -1
  12. package/dist/app/assets/{core-Rj_4rRnA.es-DoIfLxDJ.js → core-Rj_4rRnA.es-BZyvITuO.js} +1 -1
  13. package/dist/app/assets/{index-B_jKMR35.js → index-CH2AcDzc.js} +4 -4
  14. package/dist/app/assets/{index-hw-xn0X7.js → index-DQa463gC.js} +2 -2
  15. package/dist/app/assets/{index-DWIe_hK0.js → index-DQpV7MyA.js} +1 -1
  16. package/dist/app/assets/{index-BabP-V-S.js → index-DzaYbhnD.js} +1 -1
  17. package/dist/app/assets/{index-D-rDyK11.js → index-VBbcc8s6.js} +1 -1
  18. package/dist/app/index.html +1 -1
  19. package/dist/package_load_worker.mjs +8 -1
  20. package/dist/server.mjs +471 -54
  21. package/package.json +16 -13
  22. package/.eslintrc.json +0 -44
  23. package/.prettierignore +0 -1
  24. package/.prettierrc +0 -4
  25. package/build.ts +0 -97
  26. package/eslint.config.mjs +0 -76
  27. package/k6-tests/README.md +0 -144
  28. package/k6-tests/bun.lock +0 -797
  29. package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
  30. package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
  31. package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
  32. package/k6-tests/load-test/load-test-crud.ts +0 -142
  33. package/k6-tests/load-test/load-test.ts +0 -784
  34. package/k6-tests/package.json +0 -27
  35. package/k6-tests/scripts/run-with-otel.sh +0 -50
  36. package/k6-tests/smoke-test/smoke-test.ts +0 -158
  37. package/k6-tests/tsconfig.json +0 -29
  38. package/k6-tests/utils/client_factory.ts +0 -128
  39. package/k6-tests/utils/common.ts +0 -489
  40. package/k6-tests/utils/logger.ts +0 -62
  41. package/k6-tests/utils/types.d.ts +0 -35
  42. package/malloy_mcp.dxt +0 -0
  43. package/publisher.config.example.bigquery.json +0 -33
  44. package/publisher.config.example.duckdb.json +0 -23
  45. package/publisher.config.json +0 -23
  46. package/scripts/bake-duckdb-extensions.js +0 -110
  47. package/src/config.spec.ts +0 -1535
  48. package/src/config.theme.spec.ts +0 -178
  49. package/src/config.ts +0 -1142
  50. package/src/constants.ts +0 -105
  51. package/src/controller/compile.controller.ts +0 -40
  52. package/src/controller/connection.controller.spec.ts +0 -849
  53. package/src/controller/connection.controller.ts +0 -901
  54. package/src/controller/database.controller.ts +0 -24
  55. package/src/controller/materialization.controller.spec.ts +0 -293
  56. package/src/controller/materialization.controller.ts +0 -319
  57. package/src/controller/model.controller.ts +0 -164
  58. package/src/controller/package.controller.spec.ts +0 -273
  59. package/src/controller/package.controller.ts +0 -306
  60. package/src/controller/query.controller.ts +0 -152
  61. package/src/controller/theme.controller.ts +0 -83
  62. package/src/controller/watch-mode.controller.ts +0 -243
  63. package/src/data_styles.ts +0 -71
  64. package/src/default-publisher.config.json +0 -23
  65. package/src/ducklake_version.spec.ts +0 -163
  66. package/src/ducklake_version.ts +0 -153
  67. package/src/errors.spec.ts +0 -119
  68. package/src/errors.ts +0 -237
  69. package/src/filter_deprecation.spec.ts +0 -64
  70. package/src/filter_deprecation.ts +0 -42
  71. package/src/health.spec.ts +0 -123
  72. package/src/health.ts +0 -233
  73. package/src/heap_check.spec.ts +0 -144
  74. package/src/heap_check.ts +0 -144
  75. package/src/instrumentation.ts +0 -214
  76. package/src/json_utils.spec.ts +0 -51
  77. package/src/json_utils.ts +0 -33
  78. package/src/logger.spec.ts +0 -210
  79. package/src/logger.ts +0 -251
  80. package/src/malloy_pin_prereqs.spec.ts +0 -25
  81. package/src/materialization_metrics.spec.ts +0 -169
  82. package/src/materialization_metrics.ts +0 -346
  83. package/src/mcp/error_messages.spec.ts +0 -35
  84. package/src/mcp/error_messages.ts +0 -183
  85. package/src/mcp/handler_utils.spec.ts +0 -108
  86. package/src/mcp/handler_utils.ts +0 -251
  87. package/src/mcp/mcp_constants.ts +0 -6
  88. package/src/mcp/query_envelope.spec.ts +0 -229
  89. package/src/mcp/query_envelope.ts +0 -240
  90. package/src/mcp/server.protocol.spec.ts +0 -250
  91. package/src/mcp/server.ts +0 -87
  92. package/src/mcp/skills/build_skills_bundle.spec.ts +0 -51
  93. package/src/mcp/skills/build_skills_bundle.ts +0 -176
  94. package/src/mcp/skills/skills_bundle.json +0 -1
  95. package/src/mcp/skills/skills_bundle.spec.ts +0 -171
  96. package/src/mcp/tool_response.spec.ts +0 -108
  97. package/src/mcp/tool_response.ts +0 -138
  98. package/src/mcp/tools/compile_tool.spec.ts +0 -315
  99. package/src/mcp/tools/compile_tool.ts +0 -208
  100. package/src/mcp/tools/docs_search/build_docs_index.ts +0 -132
  101. package/src/mcp/tools/docs_search/malloy_docs_index.json +0 -1
  102. package/src/mcp/tools/docs_search_tool.spec.ts +0 -32
  103. package/src/mcp/tools/docs_search_tool.ts +0 -128
  104. package/src/mcp/tools/embedding_index.spec.ts +0 -1236
  105. package/src/mcp/tools/embedding_index.ts +0 -808
  106. package/src/mcp/tools/execute_query_tool.spec.ts +0 -294
  107. package/src/mcp/tools/execute_query_tool.ts +0 -316
  108. package/src/mcp/tools/get_context_eval.ts +0 -275
  109. package/src/mcp/tools/get_context_tool.spec.ts +0 -592
  110. package/src/mcp/tools/get_context_tool.ts +0 -585
  111. package/src/mcp/tools/reload_package_tool.spec.ts +0 -232
  112. package/src/mcp/tools/reload_package_tool.ts +0 -132
  113. package/src/mcp_config.spec.ts +0 -919
  114. package/src/mcp_config.ts +0 -425
  115. package/src/oom_guards.integration.spec.ts +0 -269
  116. package/src/package_load/package_load_pool.spec.ts +0 -252
  117. package/src/package_load/package_load_pool.ts +0 -931
  118. package/src/package_load/package_load_worker.ts +0 -1034
  119. package/src/package_load/protocol.ts +0 -364
  120. package/src/package_load/rpc_wait_accountant.spec.ts +0 -109
  121. package/src/package_load/rpc_wait_accountant.ts +0 -76
  122. package/src/package_load_metrics.spec.ts +0 -114
  123. package/src/package_load_metrics.ts +0 -127
  124. package/src/path_safety.spec.ts +0 -158
  125. package/src/path_safety.ts +0 -146
  126. package/src/pg_helpers.spec.ts +0 -223
  127. package/src/pg_helpers.ts +0 -52
  128. package/src/query_cap_metrics.spec.ts +0 -89
  129. package/src/query_cap_metrics.ts +0 -116
  130. package/src/query_concurrency.spec.ts +0 -247
  131. package/src/query_concurrency.ts +0 -237
  132. package/src/query_metadata_metrics.ts +0 -49
  133. package/src/query_param_utils.ts +0 -29
  134. package/src/query_timeout.spec.ts +0 -224
  135. package/src/query_timeout.ts +0 -179
  136. package/src/runtime/publisher.js +0 -328
  137. package/src/server-old.ts +0 -988
  138. package/src/server.ts +0 -2059
  139. package/src/service/annotations.spec.ts +0 -118
  140. package/src/service/annotations.ts +0 -91
  141. package/src/service/authorize.spec.ts +0 -154
  142. package/src/service/authorize.ts +0 -498
  143. package/src/service/authorize_integration.spec.ts +0 -2041
  144. package/src/service/build_plan.spec.ts +0 -685
  145. package/src/service/build_plan.ts +0 -797
  146. package/src/service/compile_authorize.spec.ts +0 -85
  147. package/src/service/compile_fragment_techniques.spec.ts +0 -156
  148. package/src/service/connection.spec.ts +0 -2360
  149. package/src/service/connection.ts +0 -2187
  150. package/src/service/connection_config.spec.ts +0 -734
  151. package/src/service/connection_config.ts +0 -899
  152. package/src/service/connection_federation.spec.ts +0 -184
  153. package/src/service/connection_fingerprint.spec.ts +0 -102
  154. package/src/service/connection_service.spec.ts +0 -711
  155. package/src/service/connection_service.ts +0 -291
  156. package/src/service/cron_evaluator.spec.ts +0 -79
  157. package/src/service/cron_evaluator.ts +0 -105
  158. package/src/service/db_utils.spec.ts +0 -1085
  159. package/src/service/db_utils.ts +0 -1502
  160. package/src/service/duckdb_instance_isolation.spec.ts +0 -137
  161. package/src/service/ducklake_lazy_attach.spec.ts +0 -110
  162. package/src/service/embedding_provider.spec.ts +0 -329
  163. package/src/service/embedding_provider.ts +0 -236
  164. package/src/service/environment.ts +0 -2191
  165. package/src/service/environment_admission.spec.ts +0 -344
  166. package/src/service/environment_compile.spec.ts +0 -197
  167. package/src/service/environment_store.spec.ts +0 -2274
  168. package/src/service/environment_store.ts +0 -2434
  169. package/src/service/environment_store_anchoring.spec.ts +0 -107
  170. package/src/service/environment_store_clone.spec.ts +0 -350
  171. package/src/service/explore_visibility.spec.ts +0 -434
  172. package/src/service/exports_probe.spec.ts +0 -107
  173. package/src/service/extension_fetch_policy.spec.ts +0 -256
  174. package/src/service/filter.spec.ts +0 -458
  175. package/src/service/filter.ts +0 -341
  176. package/src/service/filter_bypass.spec.ts +0 -418
  177. package/src/service/filter_integration.spec.ts +0 -1004
  178. package/src/service/freshness.spec.ts +0 -183
  179. package/src/service/freshness.ts +0 -112
  180. package/src/service/gcs_s3_utils.ts +0 -427
  181. package/src/service/given.ts +0 -105
  182. package/src/service/givens_integration.spec.ts +0 -219
  183. package/src/service/manifest_loader.spec.ts +0 -189
  184. package/src/service/manifest_loader.ts +0 -156
  185. package/src/service/materialization_build_session.spec.ts +0 -435
  186. package/src/service/materialization_build_session.ts +0 -681
  187. package/src/service/materialization_config_validation.spec.ts +0 -99
  188. package/src/service/materialization_config_validation.ts +0 -120
  189. package/src/service/materialization_eligibility.spec.ts +0 -158
  190. package/src/service/materialization_eligibility.ts +0 -305
  191. package/src/service/materialization_schedule_surface.spec.ts +0 -256
  192. package/src/service/materialization_scheduler.spec.ts +0 -289
  193. package/src/service/materialization_scheduler.ts +0 -304
  194. package/src/service/materialization_serve_transform.spec.ts +0 -1003
  195. package/src/service/materialization_serve_transform.ts +0 -779
  196. package/src/service/materialization_service.spec.ts +0 -2373
  197. package/src/service/materialization_service.ts +0 -2342
  198. package/src/service/materialization_test_fixtures.ts +0 -249
  199. package/src/service/model.spec.ts +0 -1157
  200. package/src/service/model.ts +0 -3370
  201. package/src/service/model_limits.spec.ts +0 -209
  202. package/src/service/model_limits.ts +0 -131
  203. package/src/service/model_storage_serve.spec.ts +0 -193
  204. package/src/service/model_storage_serve_joins.spec.ts +0 -193
  205. package/src/service/package.spec.ts +0 -612
  206. package/src/service/package.ts +0 -1799
  207. package/src/service/package_manifest.spec.ts +0 -230
  208. package/src/service/package_manifest.ts +0 -241
  209. package/src/service/package_memory_governor.spec.ts +0 -173
  210. package/src/service/package_memory_governor.ts +0 -233
  211. package/src/service/package_quote_bound_tables.spec.ts +0 -171
  212. package/src/service/package_race.spec.ts +0 -208
  213. package/src/service/package_reload_safety.spec.ts +0 -193
  214. package/src/service/package_rollback.spec.ts +0 -190
  215. package/src/service/package_worker_path.spec.ts +0 -645
  216. package/src/service/path_injection.spec.ts +0 -39
  217. package/src/service/persist_annotation_validation.spec.ts +0 -89
  218. package/src/service/persist_annotation_validation.ts +0 -59
  219. package/src/service/persistence_policy.spec.ts +0 -374
  220. package/src/service/proxy.spec.ts +0 -414
  221. package/src/service/proxy.ts +0 -248
  222. package/src/service/query_boundary.spec.ts +0 -470
  223. package/src/service/query_metadata.spec.ts +0 -408
  224. package/src/service/query_metadata.ts +0 -492
  225. package/src/service/query_metadata_identity.spec.ts +0 -149
  226. package/src/service/query_text.spec.ts +0 -79
  227. package/src/service/query_text.ts +0 -43
  228. package/src/service/quoting.spec.ts +0 -135
  229. package/src/service/quoting.ts +0 -78
  230. package/src/service/resolve_environment.ts +0 -15
  231. package/src/service/restricted_mode.spec.ts +0 -299
  232. package/src/service/source_extraction.ts +0 -225
  233. package/src/service/theme_store.ts +0 -199
  234. package/src/storage/DatabaseInterface.ts +0 -198
  235. package/src/storage/StorageManager.ts +0 -113
  236. package/src/storage/duckdb/ConnectionRepository.ts +0 -143
  237. package/src/storage/duckdb/DuckDBConnection.ts +0 -123
  238. package/src/storage/duckdb/DuckDBRepository.ts +0 -208
  239. package/src/storage/duckdb/EnvironmentRepository.ts +0 -148
  240. package/src/storage/duckdb/MaterializationRepository.spec.ts +0 -191
  241. package/src/storage/duckdb/MaterializationRepository.ts +0 -336
  242. package/src/storage/duckdb/PackageRepository.ts +0 -135
  243. package/src/storage/duckdb/schema.ts +0 -229
  244. package/src/stream_helpers.spec.ts +0 -280
  245. package/src/stream_helpers.ts +0 -162
  246. package/src/telemetry.ts +0 -20
  247. package/src/test_helpers/metrics_harness.ts +0 -166
  248. package/src/theme_key_parity.spec.ts +0 -57
  249. package/src/utils.ts +0 -29
  250. package/tests/fixtures/authorize-compile/model.malloy +0 -9
  251. package/tests/fixtures/authorize-compile/publisher.json +0 -4
  252. package/tests/fixtures/html-pages-nopublic/model.malloy +0 -1
  253. package/tests/fixtures/html-pages-nopublic/publisher.json +0 -5
  254. package/tests/fixtures/html-pages-test/data.csv +0 -3
  255. package/tests/fixtures/html-pages-test/public/assets/app.css +0 -3
  256. package/tests/fixtures/html-pages-test/public/barehtml.html +0 -4
  257. package/tests/fixtures/html-pages-test/public/bodymeta.html +0 -8
  258. package/tests/fixtures/html-pages-test/public/data.json +0 -1
  259. package/tests/fixtures/html-pages-test/public/fitcomment.html +0 -11
  260. package/tests/fixtures/html-pages-test/public/index.html +0 -9
  261. package/tests/fixtures/html-pages-test/public/nohead.html +0 -8
  262. package/tests/fixtures/html-pages-test/public/notfit.html +0 -13
  263. package/tests/fixtures/html-pages-test/public/slides.html +0 -12
  264. package/tests/fixtures/html-pages-test/public/sub/page2.html +0 -9
  265. package/tests/fixtures/html-pages-test/public/unterminated.html +0 -10
  266. package/tests/fixtures/html-pages-test/publisher.json +0 -5
  267. package/tests/fixtures/html-pages-test/report.malloy +0 -1
  268. package/tests/fixtures/persist-multi-level/data/orders.csv +0 -5
  269. package/tests/fixtures/persist-multi-level/multi_level.malloy +0 -18
  270. package/tests/fixtures/persist-multi-level/publisher.json +0 -5
  271. package/tests/fixtures/persist-schedule-test/data/orders.csv +0 -5
  272. package/tests/fixtures/persist-schedule-test/persist_schedule_test.malloy +0 -11
  273. package/tests/fixtures/persist-schedule-test/publisher.json +0 -7
  274. package/tests/fixtures/persist-schedule-warn-test/data/orders.csv +0 -5
  275. package/tests/fixtures/persist-schedule-warn-test/persist_warn_test.malloy +0 -11
  276. package/tests/fixtures/persist-schedule-warn-test/publisher.json +0 -6
  277. package/tests/fixtures/persist-test/data/orders.csv +0 -5
  278. package/tests/fixtures/persist-test/persist_test.malloy +0 -11
  279. package/tests/fixtures/persist-test/publisher.json +0 -5
  280. package/tests/fixtures/publisher.config.json +0 -15
  281. package/tests/fixtures/query-givens/data/orders.csv +0 -7
  282. package/tests/fixtures/query-givens/model.malloy +0 -34
  283. package/tests/fixtures/query-givens/publisher.json +0 -5
  284. package/tests/fixtures/xlsx/database.xlsx +0 -0
  285. package/tests/harness/mcp_test_setup.ts +0 -332
  286. package/tests/harness/rest_e2e.ts +0 -68
  287. package/tests/integration/authorize/compile_authorize_http.integration.spec.ts +0 -92
  288. package/tests/integration/concurrent_package/concurrent_package.integration.spec.ts +0 -280
  289. package/tests/integration/duckdb_storage/duckdb_storage.integration.spec.ts +0 -138
  290. package/tests/integration/first_boot/readiness_line.integration.spec.ts +0 -177
  291. package/tests/integration/html_pages/html_pages.integration.spec.ts +0 -440
  292. package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +0 -259
  293. package/tests/integration/materialization/environment_list.integration.spec.ts +0 -133
  294. package/tests/integration/materialization/freshness_gate.integration.spec.ts +0 -292
  295. package/tests/integration/materialization/manifest_binding.integration.spec.ts +0 -430
  296. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -441
  297. package/tests/integration/materialization/orchestrated_rebind.integration.spec.ts +0 -150
  298. package/tests/integration/materialization/reference_manifest.integration.spec.ts +0 -251
  299. package/tests/integration/materialization/schedule_edit.integration.spec.ts +0 -126
  300. package/tests/integration/materialization/scheduler.integration.spec.ts +0 -200
  301. package/tests/integration/materialization/scheduler_recovery.integration.spec.ts +0 -172
  302. package/tests/integration/materialization/scheduler_transitions.integration.spec.ts +0 -256
  303. package/tests/integration/materialization/scheduler_wiring.integration.spec.ts +0 -143
  304. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -438
  305. package/tests/integration/mcp/mcp_get_context_semantic.integration.spec.ts +0 -235
  306. package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -98
  307. package/tests/integration/query_givens/query_givens.integration.spec.ts +0 -146
  308. package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +0 -121
  309. package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +0 -110
  310. package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -421
  311. package/tests/unit/controller/theme.controller.spec.ts +0 -141
  312. package/tests/unit/duckdb/attached_databases.test.ts +0 -1243
  313. package/tests/unit/duckdb/duckdb_connection.test.ts +0 -181
  314. package/tests/unit/duckdb/legacy_schema_migration.test.ts +0 -197
  315. package/tests/unit/duckdb/repositories.test.ts +0 -208
  316. package/tests/unit/ducklake/ducklake.test.ts +0 -952
  317. package/tests/unit/service/theme_store.spec.ts +0 -139
  318. package/tsconfig.json +0 -32
@@ -1,89 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { ModelCompilationError } from "../errors";
3
- import { assertPersistNamesQuoted } from "./persist_annotation_validation";
4
-
5
- describe("assertPersistNamesQuoted", () => {
6
- it("throws ModelCompilationError on a bare (unquoted) persist name", () => {
7
- expect(() =>
8
- assertPersistNamesQuoted(
9
- `#@ persist name=engaged_events\nsource: engaged_events is x -> { select: * }`,
10
- "m.malloy",
11
- ),
12
- ).toThrow(ModelCompilationError);
13
- });
14
-
15
- it("tolerates whitespace around = but still flags an unquoted value", () => {
16
- expect(() =>
17
- assertPersistNamesQuoted(
18
- `#@ persist name = engaged_events`,
19
- "m.malloy",
20
- ),
21
- ).toThrow(/must be quoted/);
22
- });
23
-
24
- it("accepts a double-quoted name", () => {
25
- expect(() =>
26
- assertPersistNamesQuoted(
27
- `#@ persist name="engaged_events"`,
28
- "m.malloy",
29
- ),
30
- ).not.toThrow();
31
- });
32
-
33
- it("accepts a single-quoted name", () => {
34
- expect(() =>
35
- assertPersistNamesQuoted(
36
- `#@ persist name='engaged_events'`,
37
- "m.malloy",
38
- ),
39
- ).not.toThrow();
40
- });
41
-
42
- it("accepts a dotted, quoted dialect path", () => {
43
- expect(() =>
44
- assertPersistNamesQuoted(
45
- `#@ persist name="my_dataset.engaged_events"`,
46
- "m.malloy",
47
- ),
48
- ).not.toThrow();
49
- });
50
-
51
- it("ignores non-persist annotations and persist lines with no name field", () => {
52
- expect(() =>
53
- assertPersistNamesQuoted(
54
- `# number=2\n#@ persist realization="COPY"\nsource: x is y -> { select: * }`,
55
- "m.malloy",
56
- ),
57
- ).not.toThrow();
58
- });
59
-
60
- it("does not mistake a neighbouring key for the name field", () => {
61
- expect(() =>
62
- assertPersistNamesQuoted(
63
- `#@ persist tablename=foo name="bar"`,
64
- "m.malloy",
65
- ),
66
- ).not.toThrow();
67
- });
68
-
69
- it("does not mistake a DOTTED key for the name field", () => {
70
- // `.` is a word boundary, so a `\b`-anchored pattern reads
71
- // `queryMetadata.name=` as the persist name and fails the whole model
72
- // load with a 424 about quoting a name the author never declared.
73
- expect(() =>
74
- assertPersistNamesQuoted(
75
- `#@ persist name="bar" queryMetadata.name=finance`,
76
- "m.malloy",
77
- ),
78
- ).not.toThrow();
79
- });
80
-
81
- it("reports every offending annotation in the message", () => {
82
- expect(() =>
83
- assertPersistNamesQuoted(
84
- `#@ persist name=a\nsource: a is x -> { select: * }\n#@ persist name=b\nsource: b is x -> { select: * }`,
85
- "m.malloy",
86
- ),
87
- ).toThrow(/name=a.*name=b/s);
88
- });
89
- });
@@ -1,59 +0,0 @@
1
- import { ModelCompilationError } from "../errors";
2
-
3
- /** A line whose first non-whitespace content is a `#@ persist` directive. */
4
- const PERSIST_LINE_PATTERN = /^\s*#@\s+persist\b/;
5
- /**
6
- * A `name=` key whose value is NOT immediately opened by a single or double
7
- * quote — i.e. a bare `name=engaged_events` (whitespace around `=` tolerated).
8
- * `name="..."` and `name='...'` pass.
9
- *
10
- * The lookbehind requires a standalone key: neither an `_`-joined neighbour
11
- * (`tablename=`, `realization_name=`) nor a DOTTED one (`queryMetadata.name=`)
12
- * is the persist name. A `\b` word boundary would catch the underscore case but
13
- * not the dotted one, because `.` is itself a boundary — so a legitimate
14
- * `#@ persist queryMetadata.name=finance` would fail the load with a 424 about
15
- * quoting a persist name it never declared.
16
- */
17
- const UNQUOTED_NAME_PATTERN = /(?<![.\w])name\s*=\s*(?!["'])/;
18
-
19
- /**
20
- * Reject `#@ persist name=<value>` annotations whose name is unquoted.
21
- *
22
- * The persist build plan requires a quoted name — a dialect-style table path
23
- * (`name="engaged_events"`, or `name="my_dataset.engaged_events"`). A bare
24
- * value is dropped from the build plan, so the source publishes and serves but
25
- * is never materialized, with no error anywhere. Throwing a
26
- * `ModelCompilationError` (HTTP 424) fails the publish/load with a clear,
27
- * actionable message — the same hard-stop `Model.validateRenderTags` applies to
28
- * a misconfigured render tag.
29
- *
30
- * Scans the raw model source line-by-line rather than the compiled annotation
31
- * objects: the check must fire regardless of how the compiler attaches the
32
- * annotation, and the raw text is the ground truth for whether the author
33
- * quoted the value (the tag parser discards quote information once parsed).
34
- *
35
- * @throws {ModelCompilationError} listing every offending annotation.
36
- */
37
- export function assertPersistNamesQuoted(
38
- modelSource: string,
39
- modelPath: string,
40
- ): void {
41
- const offenders: string[] = [];
42
- for (const rawLine of modelSource.split("\n")) {
43
- if (!PERSIST_LINE_PATTERN.test(rawLine)) continue;
44
- if (UNQUOTED_NAME_PATTERN.test(rawLine)) {
45
- offenders.push(rawLine.trim());
46
- }
47
- }
48
- if (offenders.length > 0) {
49
- throw new ModelCompilationError({
50
- message:
51
- `${modelPath}: persist annotation name must be quoted. Write a quoted ` +
52
- `value like name="engaged_events" (or a dialect table path such as ` +
53
- `name="my_dataset.engaged_events"), not a bare value — an unquoted ` +
54
- `persist name is dropped from the build plan, so the source would ` +
55
- `publish but never materialize. Offending annotation(s): ` +
56
- `${offenders.join("; ")}.`,
57
- });
58
- }
59
- }
@@ -1,374 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "bun:test";
2
- import * as fs from "fs/promises";
3
- import * as os from "os";
4
- import * as path from "path";
5
-
6
- import { Environment } from "./environment";
7
- import type { Package } from "./package";
8
-
9
- /**
10
- * The publish gate for the package's Malloy Persistence policy (persistence.md
11
- * §3.1, §9.2–§9.5). Scope is a single package-level mode (`Package.scope`) and a
12
- * materialization cron is package-root-only + version-scope-only:
13
- *
14
- * - per-source `#@ persist ... sharing=`/`schedule=` are retired (declaring
15
- * either on a source is a manifest error);
16
- * - `materialization.schedule` is legal only when `scope: version`;
17
- * - `materialization.schedule` and freshness (package or source) are mutually
18
- * exclusive.
19
- *
20
- * These tests run a real `Environment` + `Package.create` over temp dirs, so
21
- * they also prove end-to-end that the pinned compiler still ACCEPTS the
22
- * `#@ persist` annotation keys (`sharing`/`schedule`/`freshness.*`) at compile
23
- * — the rejection is the publisher's manifest-level gate, not a compile error —
24
- * and that `scope` is parsed from the manifest root and surfaced on the package.
25
- */
26
- describe("persistence policy gate", () => {
27
- let rootDir: string;
28
- let envPath: string;
29
-
30
- async function loadPackage(
31
- model: string,
32
- manifest: Record<string, unknown> = {},
33
- ): Promise<Package> {
34
- const dir = path.join(envPath, "pkg");
35
- await fs.mkdir(dir, { recursive: true });
36
- await fs.writeFile(
37
- path.join(dir, "publisher.json"),
38
- JSON.stringify({ name: "pkg", description: "fixture", ...manifest }),
39
- );
40
- await fs.writeFile(path.join(dir, "model.malloy"), model);
41
- const env = await Environment.create("testEnv", envPath, []);
42
- await env.addPackage("pkg");
43
- return env.getPackage("pkg", false);
44
- }
45
-
46
- function sourceByName(pkg: Package, name: string) {
47
- const sources = pkg.getBuildPlan()?.sources ?? {};
48
- const found = Object.values(sources).find((s) => s.name === name);
49
- if (!found) throw new Error(`persist source '${name}' not in build plan`);
50
- return found;
51
- }
52
-
53
- beforeEach(async () => {
54
- rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "publisher-policy-"));
55
- envPath = path.join(rootDir, "env");
56
- await fs.mkdir(envPath, { recursive: true });
57
- });
58
-
59
- afterEach(async () => {
60
- await fs.rm(rootDir, { recursive: true, force: true }).catch(() => {});
61
- });
62
-
63
- const PLAIN_MODEL = `##! experimental.persistence
64
-
65
- #@ persist name="a_table"
66
- source: a is duckdb.sql("SELECT 1 as x")
67
-
68
- #@ persist name="b_table" refresh=full
69
- source: b is duckdb.sql("SELECT 2 as x")
70
- `;
71
-
72
- const SHARING_SOURCE_MODEL = `##! experimental.persistence
73
-
74
- #@ persist name="s_table" sharing=private
75
- source: s is duckdb.sql("SELECT 1 as x")
76
- `;
77
-
78
- const SCHEDULE_SOURCE_MODEL = `##! experimental.persistence
79
-
80
- #@ persist name="s_table" schedule="0 */6 * * *"
81
- source: s is duckdb.sql("SELECT 1 as x")
82
- `;
83
-
84
- const FRESHNESS_SOURCE_MODEL = `##! experimental.persistence
85
-
86
- #@ persist name="f_table" freshness.window="1h"
87
- source: f is duckdb.sql("SELECT 1 as x")
88
- `;
89
-
90
- // ── scope parsing + surfacing ─────────────────────────────────────────
91
-
92
- it(
93
- "defaults scope to 'package' when the manifest omits it",
94
- async () => {
95
- const pkg = await loadPackage(PLAIN_MODEL);
96
- expect(pkg.getPackageMetadata().scope).toBe("package");
97
- },
98
- { timeout: 30000 },
99
- );
100
-
101
- it(
102
- "surfaces an explicit scope: version from the manifest root",
103
- async () => {
104
- const pkg = await loadPackage(PLAIN_MODEL, { scope: "version" });
105
- expect(pkg.getPackageMetadata().scope).toBe("version");
106
- },
107
- { timeout: 30000 },
108
- );
109
-
110
- it(
111
- "fails the load on an invalid scope value",
112
- async () => {
113
- await expect(
114
- loadPackage(PLAIN_MODEL, { scope: "shared" }),
115
- ).rejects.toThrow(/scope/i);
116
- },
117
- { timeout: 30000 },
118
- );
119
-
120
- // ── Rule 1: per-source sharing/schedule retired ───────────────────────
121
-
122
- it(
123
- "rejects a per-source sharing= annotation, pointing at root scope",
124
- async () => {
125
- const pkg = await loadPackage(SHARING_SOURCE_MODEL, {
126
- scope: "version",
127
- });
128
- const joined = pkg.formatInvalidPersistencePolicy();
129
- expect(joined).toContain('"s"');
130
- expect(joined).toContain("sharing");
131
- expect(joined).toContain('"scope"');
132
- },
133
- { timeout: 30000 },
134
- );
135
-
136
- it(
137
- "rejects a per-source schedule= annotation, pointing at the root schedule",
138
- async () => {
139
- const pkg = await loadPackage(SCHEDULE_SOURCE_MODEL, {
140
- scope: "version",
141
- });
142
- const joined = pkg.formatInvalidPersistencePolicy();
143
- expect(joined).toContain('"s"');
144
- expect(joined).toContain("schedule");
145
- expect(joined).toContain("materialization.schedule");
146
- // The retired field is not emitted on the wire plan.
147
- const source = sourceByName(pkg, "s") as Record<string, unknown>;
148
- expect(source.schedule).toBeUndefined();
149
- expect(source.sharing).toBeUndefined();
150
- },
151
- { timeout: 30000 },
152
- );
153
-
154
- // ── Rule 2: schedule requires scope: version ──────────────────────────
155
-
156
- it(
157
- "rejects a package schedule when scope is package (the default)",
158
- async () => {
159
- const pkg = await loadPackage(PLAIN_MODEL, {
160
- materialization: { schedule: "0 6 * * *" },
161
- });
162
- const joined = pkg.formatInvalidPersistencePolicy();
163
- expect(joined).toContain("materialization.schedule");
164
- expect(joined).toContain('"scope": "version"');
165
- },
166
- { timeout: 30000 },
167
- );
168
-
169
- it(
170
- "accepts a package schedule when scope is version and no freshness is set",
171
- async () => {
172
- const pkg = await loadPackage(PLAIN_MODEL, {
173
- scope: "version",
174
- materialization: { schedule: "0 6 * * *" },
175
- });
176
- expect(pkg.persistencePolicyWarnings()).toEqual([]);
177
- },
178
- { timeout: 30000 },
179
- );
180
-
181
- // ── Rule 3: schedule XOR freshness ────────────────────────────────────
182
-
183
- it(
184
- "rejects a schedule declared alongside package-level freshness",
185
- async () => {
186
- const pkg = await loadPackage(PLAIN_MODEL, {
187
- scope: "version",
188
- materialization: {
189
- schedule: "0 6 * * *",
190
- freshness: { window: "24h" },
191
- },
192
- });
193
- const joined = pkg.formatInvalidPersistencePolicy();
194
- expect(joined).toContain("mutually exclusive");
195
- },
196
- { timeout: 30000 },
197
- );
198
-
199
- it(
200
- "rejects a schedule declared alongside a source-level freshness",
201
- async () => {
202
- const pkg = await loadPackage(FRESHNESS_SOURCE_MODEL, {
203
- scope: "version",
204
- materialization: { schedule: "0 6 * * *" },
205
- });
206
- const joined = pkg.formatInvalidPersistencePolicy();
207
- expect(joined).toContain("mutually exclusive");
208
- },
209
- { timeout: 30000 },
210
- );
211
-
212
- // ── Rule 4: the cron must be a valid 5-field UNIX expression ──────────
213
-
214
- it(
215
- "rejects an unparseable schedule cron",
216
- async () => {
217
- const pkg = await loadPackage(PLAIN_MODEL, {
218
- scope: "version",
219
- materialization: { schedule: "not a cron" },
220
- });
221
- const joined = pkg.formatInvalidPersistencePolicy();
222
- expect(joined).toContain("valid");
223
- expect(joined).toContain("UNIX cron");
224
- },
225
- { timeout: 30000 },
226
- );
227
-
228
- it(
229
- "rejects a cron-parser extension the UNIX grammar lacks (L)",
230
- async () => {
231
- // Guards config-parity: cron-parser accepts `0 6 L * *` but the control
232
- // plane's UNIX parser does not, so publish must reject it too rather
233
- // than let it silently never arm in production.
234
- const pkg = await loadPackage(PLAIN_MODEL, {
235
- scope: "version",
236
- materialization: { schedule: "0 6 L * *" },
237
- });
238
- expect(pkg.formatInvalidPersistencePolicy()).toContain("UNIX cron");
239
- },
240
- { timeout: 30000 },
241
- );
242
-
243
- // ── inert when nothing is declared ────────────────────────────────────
244
-
245
- it(
246
- "is inert for a plain package with no schedule and no forbidden knobs",
247
- async () => {
248
- const pkg = await loadPackage(PLAIN_MODEL, { scope: "package" });
249
- expect(pkg.persistencePolicyWarnings()).toEqual([]);
250
- },
251
- { timeout: 30000 },
252
- );
253
-
254
- it(
255
- "accepts source-level freshness without a schedule in either scope",
256
- async () => {
257
- const pkg = await loadPackage(FRESHNESS_SOURCE_MODEL, {
258
- scope: "package",
259
- });
260
- expect(pkg.persistencePolicyWarnings()).toEqual([]);
261
- expect(sourceByName(pkg, "f").freshness).toEqual({ window: "1h" });
262
- },
263
- { timeout: 30000 },
264
- );
265
-
266
- // ── within-package persist-target collisions ──────────────────────────
267
-
268
- // Two DISTINCT sources both resolving to name="dup" in the same destination
269
- // (here their own duckdb warehouse — colocated) — a self-assign clobber.
270
- const COLLIDING_MODEL = `##! experimental.persistence
271
-
272
- #@ persist name="dup"
273
- source: a is duckdb.sql("SELECT 1 as x")
274
-
275
- #@ persist name="dup"
276
- source: b is duckdb.sql("SELECT 2 as y")
277
- `;
278
-
279
- // Same name= but DIFFERENT destinations — no collision (different tables).
280
- const SAME_NAME_DIFF_DEST_MODEL = `##! experimental.persistence
281
-
282
- #@ persist name="dup" storage=lake
283
- source: a is duckdb.sql("SELECT 1 as x")
284
-
285
- #@ persist name="dup"
286
- source: b is duckdb.sql("SELECT 2 as y")
287
- `;
288
-
289
- it(
290
- "warns when two distinct sources resolve to the same name= + destination",
291
- async () => {
292
- const pkg = await loadPackage(COLLIDING_MODEL);
293
- const warnings = pkg.persistenceCollisionWarnings();
294
- expect(warnings).toHaveLength(1);
295
- expect(warnings[0]).toContain('"a"');
296
- expect(warnings[0]).toContain('"b"');
297
- expect(warnings[0]).toContain('"dup"');
298
- },
299
- { timeout: 30000 },
300
- );
301
-
302
- it(
303
- "surfaces the collision on the package status warnings (not just the log)",
304
- async () => {
305
- const pkg = await loadPackage(COLLIDING_MODEL);
306
- const warnings = pkg.getPackageMetadata().warnings ?? [];
307
- const collision = warnings.find((w) =>
308
- w.message?.includes("resolve to the same materialized table"),
309
- );
310
- expect(collision).toBeDefined();
311
- expect(collision!.message).toContain('"dup"');
312
- },
313
- { timeout: 30000 },
314
- );
315
-
316
- it(
317
- "does not warn when the same name= targets different destinations",
318
- async () => {
319
- const pkg = await loadPackage(SAME_NAME_DIFF_DEST_MODEL);
320
- expect(pkg.persistenceCollisionWarnings()).toEqual([]);
321
- const warnings = pkg.getPackageMetadata().warnings ?? [];
322
- expect(
323
- warnings.some((w) =>
324
- w.message?.includes("resolve to the same materialized table"),
325
- ),
326
- ).toBe(false);
327
- },
328
- { timeout: 30000 },
329
- );
330
-
331
- it(
332
- "does not warn for a plain package with distinct persist names",
333
- async () => {
334
- const pkg = await loadPackage(PLAIN_MODEL);
335
- expect(pkg.persistenceCollisionWarnings()).toEqual([]);
336
- },
337
- { timeout: 30000 },
338
- );
339
-
340
- it(
341
- "collisions never enter persistencePolicyWarnings (the strict-publish set)",
342
- async () => {
343
- const pkg = await loadPackage(COLLIDING_MODEL);
344
- // The collision is real…
345
- expect(pkg.persistenceCollisionWarnings()).toHaveLength(1);
346
- // …but it must not ride the always-strict-at-publish policy set.
347
- expect(pkg.persistencePolicyWarnings()).toEqual([]);
348
- },
349
- { timeout: 30000 },
350
- );
351
-
352
- it(
353
- "gates the publish rejection behind PERSIST_COLLISION_ENFORCE",
354
- async () => {
355
- const pkg = await loadPackage(COLLIDING_MODEL);
356
- const prev = process.env.PERSIST_COLLISION_ENFORCE;
357
- try {
358
- delete process.env.PERSIST_COLLISION_ENFORCE;
359
- // Default: warn-only, so the publish gate string is empty.
360
- expect(pkg.formatPersistenceCollisionRejections()).toBe("");
361
- process.env.PERSIST_COLLISION_ENFORCE = "true";
362
- // Enforced: the collision becomes a publish rejection.
363
- expect(pkg.formatPersistenceCollisionRejections()).toContain(
364
- '"dup"',
365
- );
366
- } finally {
367
- if (prev === undefined)
368
- delete process.env.PERSIST_COLLISION_ENFORCE;
369
- else process.env.PERSIST_COLLISION_ENFORCE = prev;
370
- }
371
- },
372
- { timeout: 30000 },
373
- );
374
- });