@malloy-publisher/server 0.0.235 → 0.0.236

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/dist/app/api-doc.yaml +47 -17
  2. package/dist/package_load_worker.mjs +8 -1
  3. package/dist/server.mjs +471 -54
  4. package/package.json +5 -2
  5. package/.eslintrc.json +0 -44
  6. package/.prettierignore +0 -1
  7. package/.prettierrc +0 -4
  8. package/build.ts +0 -97
  9. package/eslint.config.mjs +0 -76
  10. package/k6-tests/README.md +0 -144
  11. package/k6-tests/bun.lock +0 -797
  12. package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
  13. package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
  14. package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
  15. package/k6-tests/load-test/load-test-crud.ts +0 -142
  16. package/k6-tests/load-test/load-test.ts +0 -784
  17. package/k6-tests/package.json +0 -27
  18. package/k6-tests/scripts/run-with-otel.sh +0 -50
  19. package/k6-tests/smoke-test/smoke-test.ts +0 -158
  20. package/k6-tests/tsconfig.json +0 -29
  21. package/k6-tests/utils/client_factory.ts +0 -128
  22. package/k6-tests/utils/common.ts +0 -489
  23. package/k6-tests/utils/logger.ts +0 -62
  24. package/k6-tests/utils/types.d.ts +0 -35
  25. package/malloy_mcp.dxt +0 -0
  26. package/publisher.config.example.bigquery.json +0 -33
  27. package/publisher.config.example.duckdb.json +0 -23
  28. package/publisher.config.json +0 -23
  29. package/scripts/bake-duckdb-extensions.js +0 -110
  30. package/src/config.spec.ts +0 -1535
  31. package/src/config.theme.spec.ts +0 -178
  32. package/src/config.ts +0 -1142
  33. package/src/constants.ts +0 -105
  34. package/src/controller/compile.controller.ts +0 -40
  35. package/src/controller/connection.controller.spec.ts +0 -849
  36. package/src/controller/connection.controller.ts +0 -901
  37. package/src/controller/database.controller.ts +0 -24
  38. package/src/controller/materialization.controller.spec.ts +0 -293
  39. package/src/controller/materialization.controller.ts +0 -319
  40. package/src/controller/model.controller.ts +0 -164
  41. package/src/controller/package.controller.spec.ts +0 -273
  42. package/src/controller/package.controller.ts +0 -306
  43. package/src/controller/query.controller.ts +0 -152
  44. package/src/controller/theme.controller.ts +0 -83
  45. package/src/controller/watch-mode.controller.ts +0 -243
  46. package/src/data_styles.ts +0 -71
  47. package/src/default-publisher.config.json +0 -23
  48. package/src/ducklake_version.spec.ts +0 -163
  49. package/src/ducklake_version.ts +0 -153
  50. package/src/errors.spec.ts +0 -119
  51. package/src/errors.ts +0 -237
  52. package/src/filter_deprecation.spec.ts +0 -64
  53. package/src/filter_deprecation.ts +0 -42
  54. package/src/health.spec.ts +0 -123
  55. package/src/health.ts +0 -233
  56. package/src/heap_check.spec.ts +0 -144
  57. package/src/heap_check.ts +0 -144
  58. package/src/instrumentation.ts +0 -214
  59. package/src/json_utils.spec.ts +0 -51
  60. package/src/json_utils.ts +0 -33
  61. package/src/logger.spec.ts +0 -210
  62. package/src/logger.ts +0 -251
  63. package/src/malloy_pin_prereqs.spec.ts +0 -25
  64. package/src/materialization_metrics.spec.ts +0 -169
  65. package/src/materialization_metrics.ts +0 -346
  66. package/src/mcp/error_messages.spec.ts +0 -35
  67. package/src/mcp/error_messages.ts +0 -183
  68. package/src/mcp/handler_utils.spec.ts +0 -108
  69. package/src/mcp/handler_utils.ts +0 -251
  70. package/src/mcp/mcp_constants.ts +0 -6
  71. package/src/mcp/query_envelope.spec.ts +0 -229
  72. package/src/mcp/query_envelope.ts +0 -240
  73. package/src/mcp/server.protocol.spec.ts +0 -250
  74. package/src/mcp/server.ts +0 -87
  75. package/src/mcp/skills/build_skills_bundle.spec.ts +0 -51
  76. package/src/mcp/skills/build_skills_bundle.ts +0 -176
  77. package/src/mcp/skills/skills_bundle.json +0 -1
  78. package/src/mcp/skills/skills_bundle.spec.ts +0 -171
  79. package/src/mcp/tool_response.spec.ts +0 -108
  80. package/src/mcp/tool_response.ts +0 -138
  81. package/src/mcp/tools/compile_tool.spec.ts +0 -315
  82. package/src/mcp/tools/compile_tool.ts +0 -208
  83. package/src/mcp/tools/docs_search/build_docs_index.ts +0 -132
  84. package/src/mcp/tools/docs_search/malloy_docs_index.json +0 -1
  85. package/src/mcp/tools/docs_search_tool.spec.ts +0 -32
  86. package/src/mcp/tools/docs_search_tool.ts +0 -128
  87. package/src/mcp/tools/embedding_index.spec.ts +0 -1236
  88. package/src/mcp/tools/embedding_index.ts +0 -808
  89. package/src/mcp/tools/execute_query_tool.spec.ts +0 -294
  90. package/src/mcp/tools/execute_query_tool.ts +0 -316
  91. package/src/mcp/tools/get_context_eval.ts +0 -275
  92. package/src/mcp/tools/get_context_tool.spec.ts +0 -592
  93. package/src/mcp/tools/get_context_tool.ts +0 -585
  94. package/src/mcp/tools/reload_package_tool.spec.ts +0 -232
  95. package/src/mcp/tools/reload_package_tool.ts +0 -132
  96. package/src/mcp_config.spec.ts +0 -919
  97. package/src/mcp_config.ts +0 -425
  98. package/src/oom_guards.integration.spec.ts +0 -269
  99. package/src/package_load/package_load_pool.spec.ts +0 -252
  100. package/src/package_load/package_load_pool.ts +0 -931
  101. package/src/package_load/package_load_worker.ts +0 -1034
  102. package/src/package_load/protocol.ts +0 -364
  103. package/src/package_load/rpc_wait_accountant.spec.ts +0 -109
  104. package/src/package_load/rpc_wait_accountant.ts +0 -76
  105. package/src/package_load_metrics.spec.ts +0 -114
  106. package/src/package_load_metrics.ts +0 -127
  107. package/src/path_safety.spec.ts +0 -158
  108. package/src/path_safety.ts +0 -146
  109. package/src/pg_helpers.spec.ts +0 -223
  110. package/src/pg_helpers.ts +0 -52
  111. package/src/query_cap_metrics.spec.ts +0 -89
  112. package/src/query_cap_metrics.ts +0 -116
  113. package/src/query_concurrency.spec.ts +0 -247
  114. package/src/query_concurrency.ts +0 -237
  115. package/src/query_metadata_metrics.ts +0 -49
  116. package/src/query_param_utils.ts +0 -29
  117. package/src/query_timeout.spec.ts +0 -224
  118. package/src/query_timeout.ts +0 -179
  119. package/src/runtime/publisher.js +0 -328
  120. package/src/server-old.ts +0 -988
  121. package/src/server.ts +0 -2059
  122. package/src/service/annotations.spec.ts +0 -118
  123. package/src/service/annotations.ts +0 -91
  124. package/src/service/authorize.spec.ts +0 -154
  125. package/src/service/authorize.ts +0 -498
  126. package/src/service/authorize_integration.spec.ts +0 -2041
  127. package/src/service/build_plan.spec.ts +0 -685
  128. package/src/service/build_plan.ts +0 -797
  129. package/src/service/compile_authorize.spec.ts +0 -85
  130. package/src/service/compile_fragment_techniques.spec.ts +0 -156
  131. package/src/service/connection.spec.ts +0 -2360
  132. package/src/service/connection.ts +0 -2187
  133. package/src/service/connection_config.spec.ts +0 -734
  134. package/src/service/connection_config.ts +0 -899
  135. package/src/service/connection_federation.spec.ts +0 -184
  136. package/src/service/connection_fingerprint.spec.ts +0 -102
  137. package/src/service/connection_service.spec.ts +0 -711
  138. package/src/service/connection_service.ts +0 -291
  139. package/src/service/cron_evaluator.spec.ts +0 -79
  140. package/src/service/cron_evaluator.ts +0 -105
  141. package/src/service/db_utils.spec.ts +0 -1085
  142. package/src/service/db_utils.ts +0 -1502
  143. package/src/service/duckdb_instance_isolation.spec.ts +0 -137
  144. package/src/service/ducklake_lazy_attach.spec.ts +0 -110
  145. package/src/service/embedding_provider.spec.ts +0 -329
  146. package/src/service/embedding_provider.ts +0 -236
  147. package/src/service/environment.ts +0 -2191
  148. package/src/service/environment_admission.spec.ts +0 -344
  149. package/src/service/environment_compile.spec.ts +0 -197
  150. package/src/service/environment_store.spec.ts +0 -2274
  151. package/src/service/environment_store.ts +0 -2434
  152. package/src/service/environment_store_anchoring.spec.ts +0 -107
  153. package/src/service/environment_store_clone.spec.ts +0 -350
  154. package/src/service/explore_visibility.spec.ts +0 -434
  155. package/src/service/exports_probe.spec.ts +0 -107
  156. package/src/service/extension_fetch_policy.spec.ts +0 -256
  157. package/src/service/filter.spec.ts +0 -458
  158. package/src/service/filter.ts +0 -341
  159. package/src/service/filter_bypass.spec.ts +0 -418
  160. package/src/service/filter_integration.spec.ts +0 -1004
  161. package/src/service/freshness.spec.ts +0 -183
  162. package/src/service/freshness.ts +0 -112
  163. package/src/service/gcs_s3_utils.ts +0 -427
  164. package/src/service/given.ts +0 -105
  165. package/src/service/givens_integration.spec.ts +0 -219
  166. package/src/service/manifest_loader.spec.ts +0 -189
  167. package/src/service/manifest_loader.ts +0 -156
  168. package/src/service/materialization_build_session.spec.ts +0 -435
  169. package/src/service/materialization_build_session.ts +0 -681
  170. package/src/service/materialization_config_validation.spec.ts +0 -99
  171. package/src/service/materialization_config_validation.ts +0 -120
  172. package/src/service/materialization_eligibility.spec.ts +0 -158
  173. package/src/service/materialization_eligibility.ts +0 -305
  174. package/src/service/materialization_schedule_surface.spec.ts +0 -256
  175. package/src/service/materialization_scheduler.spec.ts +0 -289
  176. package/src/service/materialization_scheduler.ts +0 -304
  177. package/src/service/materialization_serve_transform.spec.ts +0 -1003
  178. package/src/service/materialization_serve_transform.ts +0 -779
  179. package/src/service/materialization_service.spec.ts +0 -2373
  180. package/src/service/materialization_service.ts +0 -2342
  181. package/src/service/materialization_test_fixtures.ts +0 -249
  182. package/src/service/model.spec.ts +0 -1157
  183. package/src/service/model.ts +0 -3370
  184. package/src/service/model_limits.spec.ts +0 -209
  185. package/src/service/model_limits.ts +0 -131
  186. package/src/service/model_storage_serve.spec.ts +0 -193
  187. package/src/service/model_storage_serve_joins.spec.ts +0 -193
  188. package/src/service/package.spec.ts +0 -612
  189. package/src/service/package.ts +0 -1799
  190. package/src/service/package_manifest.spec.ts +0 -230
  191. package/src/service/package_manifest.ts +0 -241
  192. package/src/service/package_memory_governor.spec.ts +0 -173
  193. package/src/service/package_memory_governor.ts +0 -233
  194. package/src/service/package_quote_bound_tables.spec.ts +0 -171
  195. package/src/service/package_race.spec.ts +0 -208
  196. package/src/service/package_reload_safety.spec.ts +0 -193
  197. package/src/service/package_rollback.spec.ts +0 -190
  198. package/src/service/package_worker_path.spec.ts +0 -645
  199. package/src/service/path_injection.spec.ts +0 -39
  200. package/src/service/persist_annotation_validation.spec.ts +0 -89
  201. package/src/service/persist_annotation_validation.ts +0 -59
  202. package/src/service/persistence_policy.spec.ts +0 -374
  203. package/src/service/proxy.spec.ts +0 -414
  204. package/src/service/proxy.ts +0 -248
  205. package/src/service/query_boundary.spec.ts +0 -470
  206. package/src/service/query_metadata.spec.ts +0 -408
  207. package/src/service/query_metadata.ts +0 -492
  208. package/src/service/query_metadata_identity.spec.ts +0 -149
  209. package/src/service/query_text.spec.ts +0 -79
  210. package/src/service/query_text.ts +0 -43
  211. package/src/service/quoting.spec.ts +0 -135
  212. package/src/service/quoting.ts +0 -78
  213. package/src/service/resolve_environment.ts +0 -15
  214. package/src/service/restricted_mode.spec.ts +0 -299
  215. package/src/service/source_extraction.ts +0 -225
  216. package/src/service/theme_store.ts +0 -199
  217. package/src/storage/DatabaseInterface.ts +0 -198
  218. package/src/storage/StorageManager.ts +0 -113
  219. package/src/storage/duckdb/ConnectionRepository.ts +0 -143
  220. package/src/storage/duckdb/DuckDBConnection.ts +0 -123
  221. package/src/storage/duckdb/DuckDBRepository.ts +0 -208
  222. package/src/storage/duckdb/EnvironmentRepository.ts +0 -148
  223. package/src/storage/duckdb/MaterializationRepository.spec.ts +0 -191
  224. package/src/storage/duckdb/MaterializationRepository.ts +0 -336
  225. package/src/storage/duckdb/PackageRepository.ts +0 -135
  226. package/src/storage/duckdb/schema.ts +0 -229
  227. package/src/stream_helpers.spec.ts +0 -280
  228. package/src/stream_helpers.ts +0 -162
  229. package/src/telemetry.ts +0 -20
  230. package/src/test_helpers/metrics_harness.ts +0 -166
  231. package/src/theme_key_parity.spec.ts +0 -57
  232. package/src/utils.ts +0 -29
  233. package/tests/fixtures/authorize-compile/model.malloy +0 -9
  234. package/tests/fixtures/authorize-compile/publisher.json +0 -4
  235. package/tests/fixtures/html-pages-nopublic/model.malloy +0 -1
  236. package/tests/fixtures/html-pages-nopublic/publisher.json +0 -5
  237. package/tests/fixtures/html-pages-test/data.csv +0 -3
  238. package/tests/fixtures/html-pages-test/public/assets/app.css +0 -3
  239. package/tests/fixtures/html-pages-test/public/barehtml.html +0 -4
  240. package/tests/fixtures/html-pages-test/public/bodymeta.html +0 -8
  241. package/tests/fixtures/html-pages-test/public/data.json +0 -1
  242. package/tests/fixtures/html-pages-test/public/fitcomment.html +0 -11
  243. package/tests/fixtures/html-pages-test/public/index.html +0 -9
  244. package/tests/fixtures/html-pages-test/public/nohead.html +0 -8
  245. package/tests/fixtures/html-pages-test/public/notfit.html +0 -13
  246. package/tests/fixtures/html-pages-test/public/slides.html +0 -12
  247. package/tests/fixtures/html-pages-test/public/sub/page2.html +0 -9
  248. package/tests/fixtures/html-pages-test/public/unterminated.html +0 -10
  249. package/tests/fixtures/html-pages-test/publisher.json +0 -5
  250. package/tests/fixtures/html-pages-test/report.malloy +0 -1
  251. package/tests/fixtures/persist-multi-level/data/orders.csv +0 -5
  252. package/tests/fixtures/persist-multi-level/multi_level.malloy +0 -18
  253. package/tests/fixtures/persist-multi-level/publisher.json +0 -5
  254. package/tests/fixtures/persist-schedule-test/data/orders.csv +0 -5
  255. package/tests/fixtures/persist-schedule-test/persist_schedule_test.malloy +0 -11
  256. package/tests/fixtures/persist-schedule-test/publisher.json +0 -7
  257. package/tests/fixtures/persist-schedule-warn-test/data/orders.csv +0 -5
  258. package/tests/fixtures/persist-schedule-warn-test/persist_warn_test.malloy +0 -11
  259. package/tests/fixtures/persist-schedule-warn-test/publisher.json +0 -6
  260. package/tests/fixtures/persist-test/data/orders.csv +0 -5
  261. package/tests/fixtures/persist-test/persist_test.malloy +0 -11
  262. package/tests/fixtures/persist-test/publisher.json +0 -5
  263. package/tests/fixtures/publisher.config.json +0 -15
  264. package/tests/fixtures/query-givens/data/orders.csv +0 -7
  265. package/tests/fixtures/query-givens/model.malloy +0 -34
  266. package/tests/fixtures/query-givens/publisher.json +0 -5
  267. package/tests/fixtures/xlsx/database.xlsx +0 -0
  268. package/tests/harness/mcp_test_setup.ts +0 -332
  269. package/tests/harness/rest_e2e.ts +0 -68
  270. package/tests/integration/authorize/compile_authorize_http.integration.spec.ts +0 -92
  271. package/tests/integration/concurrent_package/concurrent_package.integration.spec.ts +0 -280
  272. package/tests/integration/duckdb_storage/duckdb_storage.integration.spec.ts +0 -138
  273. package/tests/integration/first_boot/readiness_line.integration.spec.ts +0 -177
  274. package/tests/integration/html_pages/html_pages.integration.spec.ts +0 -440
  275. package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +0 -259
  276. package/tests/integration/materialization/environment_list.integration.spec.ts +0 -133
  277. package/tests/integration/materialization/freshness_gate.integration.spec.ts +0 -292
  278. package/tests/integration/materialization/manifest_binding.integration.spec.ts +0 -430
  279. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -441
  280. package/tests/integration/materialization/orchestrated_rebind.integration.spec.ts +0 -150
  281. package/tests/integration/materialization/reference_manifest.integration.spec.ts +0 -251
  282. package/tests/integration/materialization/schedule_edit.integration.spec.ts +0 -126
  283. package/tests/integration/materialization/scheduler.integration.spec.ts +0 -200
  284. package/tests/integration/materialization/scheduler_recovery.integration.spec.ts +0 -172
  285. package/tests/integration/materialization/scheduler_transitions.integration.spec.ts +0 -256
  286. package/tests/integration/materialization/scheduler_wiring.integration.spec.ts +0 -143
  287. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -438
  288. package/tests/integration/mcp/mcp_get_context_semantic.integration.spec.ts +0 -235
  289. package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -98
  290. package/tests/integration/query_givens/query_givens.integration.spec.ts +0 -146
  291. package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +0 -121
  292. package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +0 -110
  293. package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -421
  294. package/tests/unit/controller/theme.controller.spec.ts +0 -141
  295. package/tests/unit/duckdb/attached_databases.test.ts +0 -1243
  296. package/tests/unit/duckdb/duckdb_connection.test.ts +0 -181
  297. package/tests/unit/duckdb/legacy_schema_migration.test.ts +0 -197
  298. package/tests/unit/duckdb/repositories.test.ts +0 -208
  299. package/tests/unit/ducklake/ducklake.test.ts +0 -952
  300. package/tests/unit/service/theme_store.spec.ts +0 -139
  301. package/tsconfig.json +0 -32
@@ -1,1034 +0,0 @@
1
- /**
2
- * Package-load worker entry point.
3
- *
4
- * Runs inside a worker_threads `Worker`. Owns **no native connection
5
- * state of any kind** — every connection lookup, schema fetch, SQL
6
- * block schema, and non-file URL read is proxied back to the main
7
- * thread via correlated RPC messages. The worker is intentionally
8
- * pure-CPU: only Malloy parser / type-checker / IR-builder runs here.
9
- *
10
- * Why no DuckDB in the worker?
11
- * ----------------------------
12
- * DuckDB's native bindings cannot be safely loaded into more than one
13
- * isolate of the same Node/Bun process (we hit Bun crash 0x20131 when
14
- * the worker isolate and the main isolate both `dlopen` duckdb-native).
15
- * Even if Bun fixes that, holding a duckdb handle in the worker
16
- * duplicates the in-memory DB state and adds native-module load
17
- * latency to every worker spawn. Database probing (`readDatabases`)
18
- * stays on the main thread where it can reuse the package's existing
19
- * DuckDB connection.
20
- *
21
- * Boundary
22
- * --------
23
- * 1. Worker: read `publisher.json` (package manifest).
24
- * 2. Worker: compile every `.malloy` and `.malloynb` via Malloy.
25
- * All `lookupConnection(name)` / schema-fetch calls Malloy makes
26
- * during compile are proxied to the main thread's live
27
- * connection pool over RPC.
28
- * 3. Worker: for each model, capture the structured-clonable fields
29
- * the main-thread `Model` constructor needs: `modelDef`,
30
- * `sourceInfos`, `sources`, `queries`, `filterMap`, `givens`,
31
- * dataStyles, plus per-cell `modelDef` + `queryDef` for
32
- * notebooks (so per-cell materializers / runnables can be
33
- * hydrated on the main thread via `Runtime._loadModelFromModelDef`
34
- * / `ModelMaterializer._loadQueryFromQueryDef` — no recompile).
35
- * 4. Main thread: probe embedded `.parquet` / `.csv` databases
36
- * against the package's existing DuckDB connection.
37
- *
38
- * Per-model compile failures are returned in-band on
39
- * `SerializedModel.compilationError`; the rest of the package keeps
40
- * loading. Whole-package failures (e.g. manifest missing) come back
41
- * as `LoadPackageError`.
42
- *
43
- * Bundled separately by `build.ts` as `dist/package_load_worker.mjs`
44
- * so `new Worker(...)` can load it without dragging in the entire
45
- * server module graph.
46
- */
47
- import {
48
- contextOverlay,
49
- type BuildManifestEntry,
50
- type Connection,
51
- type FetchSchemaOptions,
52
- type LookupConnection,
53
- MalloyConfig,
54
- MalloyError,
55
- type ModelDef,
56
- type ModelMaterializer,
57
- modelDefToModelInfo,
58
- type NamedQueryDef,
59
- type Query,
60
- Runtime,
61
- type SQLSourceDef,
62
- type SQLSourceRequest,
63
- type TableSourceDef,
64
- } from "@malloydata/malloy";
65
- import * as Malloy from "@malloydata/malloy-interfaces";
66
- import {
67
- MalloySQLParser,
68
- MalloySQLStatementType,
69
- } from "@malloydata/malloy-sql";
70
- import * as fs from "fs";
71
- import * as path from "path";
72
- import { parentPort, threadId } from "node:worker_threads";
73
- import recursive from "recursive-readdir";
74
- import { fileURLToPath, pathToFileURL } from "url";
75
-
76
- import {
77
- MODEL_FILE_SUFFIX,
78
- normalizeModelPath,
79
- NOTEBOOK_FILE_SUFFIX,
80
- PACKAGE_MANIFEST_NAME,
81
- } from "../constants";
82
- import { HackyDataStylesAccumulator } from "../data_styles";
83
- import { ModelCompilationError } from "../errors";
84
- import { validateAuthorizeProbes } from "../service/authorize";
85
- import { type FilterDefinition } from "../service/filter";
86
- import {
87
- PackageMaterializationConfig,
88
- PackageScope,
89
- packageMaterializationWarnings,
90
- parsePackageMaterialization,
91
- resolvePackageScope,
92
- } from "../service/package_manifest";
93
- import {
94
- extractQueriesFromModelDef,
95
- extractSourcesFromModelDef,
96
- } from "../service/source_extraction";
97
- import {
98
- malloyGivenToApi,
99
- type MalloyGiven,
100
- type MalloyGivenApi,
101
- } from "../service/given";
102
- import { ignoreDotfiles } from "../utils";
103
- import { RpcWaitAccountant } from "./rpc_wait_accountant";
104
- import type {
105
- ConnectionMetadata,
106
- ConnectionMetadataRequest,
107
- ConnectionMetadataResponse,
108
- LoadPackageError,
109
- LoadPackageRequest,
110
- LoadPackageResult,
111
- MainToWorkerMessage,
112
- ReadUrlRequest,
113
- ReadUrlResponse,
114
- SchemaForSqlRequest,
115
- SchemaForSqlResponse,
116
- SchemaForTablesRequest,
117
- SchemaForTablesResponse,
118
- SerializedError,
119
- SerializedModel,
120
- SerializedNotebookCell,
121
- } from "./protocol";
122
-
123
- if (!parentPort) {
124
- throw new Error(
125
- "package_load_worker.ts must be loaded inside a worker_threads Worker",
126
- );
127
- }
128
-
129
- const port = parentPort;
130
-
131
- // ──────────────────────────────────────────────────────────────────────
132
- // RPC plumbing for worker → main calls
133
- // ──────────────────────────────────────────────────────────────────────
134
-
135
- let nextRpcId = 0;
136
- const pendingRpc = new Map<
137
- string,
138
- { resolve: (value: unknown) => void; reject: (err: Error) => void }
139
- >();
140
-
141
- function newRpcId(): string {
142
- nextRpcId += 1;
143
- return `w${threadId}-rpc-${nextRpcId}`;
144
- }
145
-
146
- function callMain<T>(send: (requestId: string) => void): Promise<T> {
147
- const requestId = newRpcId();
148
- return new Promise<T>((resolve, reject) => {
149
- pendingRpc.set(requestId, {
150
- resolve: (value) => resolve(value as T),
151
- reject,
152
- });
153
- send(requestId);
154
- });
155
- }
156
-
157
- // Per-load phase timing: brackets the wall-clock spent awaiting proxied schema
158
- // fetches so `loadPackage` can separate connection I/O from compile CPU. The
159
- // pool runs one load per worker at a time, so a single module-level instance
160
- // is safe; it's still keyed by jobId to shrug off a straggler fetch from a
161
- // prior, reused-worker load. See {@link RpcWaitAccountant}.
162
- const schemaWait = new RpcWaitAccountant();
163
-
164
- function dispatchMainResponse(message: MainToWorkerMessage): void {
165
- if (
166
- message.type === "schema-for-tables-response" ||
167
- message.type === "schema-for-sql-response" ||
168
- message.type === "read-url-response" ||
169
- message.type === "connection-metadata-response"
170
- ) {
171
- const pending = pendingRpc.get(message.requestId);
172
- if (!pending) return;
173
- pendingRpc.delete(message.requestId);
174
- pending.resolve(message);
175
- return;
176
- }
177
- if (message.type === "rpc-error") {
178
- const pending = pendingRpc.get(message.requestId);
179
- if (!pending) return;
180
- pendingRpc.delete(message.requestId);
181
- pending.reject(deserializeError(message.error));
182
- return;
183
- }
184
- }
185
-
186
- // ──────────────────────────────────────────────────────────────────────
187
- // Proxy connection: stand-in for non-duckdb connections at compile time
188
- // ──────────────────────────────────────────────────────────────────────
189
-
190
- class ProxyConnection {
191
- public readonly name: string;
192
- public readonly dialectName: string;
193
- private readonly digest: string;
194
- private readonly jobId: string;
195
-
196
- constructor(metadata: ConnectionMetadata, jobId: string) {
197
- this.name = metadata.name;
198
- this.dialectName = metadata.dialectName;
199
- this.digest = metadata.digest;
200
- this.jobId = jobId;
201
- }
202
-
203
- getDigest(): string {
204
- return this.digest;
205
- }
206
-
207
- async fetchSchemaForTables(
208
- tables: Record<string, string>,
209
- options: FetchSchemaOptions,
210
- ): Promise<{
211
- schemas: Record<string, TableSourceDef>;
212
- errors: Record<string, string>;
213
- }> {
214
- schemaWait.noteStart(this.jobId);
215
- try {
216
- const response = await callMain<SchemaForTablesResponse>(
217
- (requestId) => {
218
- const req: SchemaForTablesRequest = {
219
- type: "schema-for-tables",
220
- requestId,
221
- jobId: this.jobId,
222
- connectionName: this.name,
223
- tables,
224
- options: serializeFetchOptions(options),
225
- };
226
- port.postMessage(req);
227
- },
228
- );
229
- return { schemas: response.schemas, errors: response.errors };
230
- } finally {
231
- schemaWait.noteSettle(this.jobId);
232
- }
233
- }
234
-
235
- async fetchSchemaForSQLStruct(
236
- sentence: SQLSourceRequest,
237
- options: FetchSchemaOptions,
238
- ): Promise<
239
- | { structDef: SQLSourceDef; error?: undefined }
240
- | { error: string; structDef?: undefined }
241
- > {
242
- schemaWait.noteStart(this.jobId);
243
- let response: SchemaForSqlResponse;
244
- try {
245
- response = await callMain<SchemaForSqlResponse>((requestId) => {
246
- const req: SchemaForSqlRequest = {
247
- type: "schema-for-sql",
248
- requestId,
249
- jobId: this.jobId,
250
- connectionName: this.name,
251
- sentence: sentence as unknown,
252
- options: serializeFetchOptions(options),
253
- };
254
- port.postMessage(req);
255
- });
256
- } finally {
257
- schemaWait.noteSettle(this.jobId);
258
- }
259
- if (response.error !== undefined) return { error: response.error };
260
- if (response.structDef === undefined) {
261
- return { error: "Empty SQL schema response from main thread" };
262
- }
263
- return { structDef: response.structDef };
264
- }
265
-
266
- // Compile path never calls these on a non-duckdb connection (the
267
- // worker doesn't execute non-duckdb SQL). We throw rather than no-op
268
- // so a misrouted call surfaces loudly.
269
- async runSQL(): Promise<never> {
270
- throw new Error(
271
- `ProxyConnection(${this.name}): runSQL is not available in package-load workers`,
272
- );
273
- }
274
- isPool(): false {
275
- return false;
276
- }
277
- canPersist(): false {
278
- return false;
279
- }
280
- canStream(): false {
281
- return false;
282
- }
283
- async close(): Promise<void> {
284
- /* no-op */
285
- }
286
- async idle(): Promise<void> {
287
- /* no-op */
288
- }
289
- async estimateQueryCost(): Promise<never> {
290
- throw new Error(
291
- `ProxyConnection(${this.name}): estimateQueryCost not available in package-load workers`,
292
- );
293
- }
294
- async fetchMetadata(): Promise<Record<string, unknown>> {
295
- return {};
296
- }
297
- async fetchTableMetadata(): Promise<Record<string, unknown>> {
298
- return {};
299
- }
300
- }
301
-
302
- function serializeFetchOptions(options: FetchSchemaOptions): {
303
- refreshTimestamp?: number;
304
- } {
305
- const out: {
306
- refreshTimestamp?: number;
307
- } = {};
308
- if (options.refreshTimestamp !== undefined) {
309
- out.refreshTimestamp = options.refreshTimestamp;
310
- }
311
- return out;
312
- }
313
-
314
- // ──────────────────────────────────────────────────────────────────────
315
- // URLReader: file:// → fs; everything else proxies to main thread
316
- // ──────────────────────────────────────────────────────────────────────
317
-
318
- function makeWorkerUrlReader(jobId: string): {
319
- readURL: (url: URL) => Promise<string>;
320
- } {
321
- return {
322
- readURL: async (url: URL): Promise<string> => {
323
- if (url.protocol === "file:") {
324
- const filePath = fileURLToPath(url);
325
- return fs.promises.readFile(filePath, "utf8");
326
- }
327
- const response = await callMain<ReadUrlResponse>((requestId) => {
328
- const req: ReadUrlRequest = {
329
- type: "read-url",
330
- requestId,
331
- jobId,
332
- url: url.toString(),
333
- };
334
- port.postMessage(req);
335
- });
336
- return response.contents;
337
- },
338
- };
339
- }
340
-
341
- // ──────────────────────────────────────────────────────────────────────
342
- // MalloyConfig assembly inside the worker
343
- // ──────────────────────────────────────────────────────────────────────
344
-
345
- /**
346
- * Build the package's MalloyConfig inside the worker. Every
347
- * connection name — including `"duckdb"` — resolves to a
348
- * {@link ProxyConnection} that RPCs schema fetches back to the main
349
- * thread. The worker never holds a native connection handle, which
350
- * keeps it pure-CPU and avoids dlopen'ing duckdb in a second isolate.
351
- *
352
- * Concurrent `lookupConnection(name)` calls for the same name are
353
- * deduped via `inflight` — Malloy's compile pipeline can fan out
354
- * many schema fetches that all hit `lookupConnection` before any
355
- * resolve, and we don't want to N-multiply the metadata RPC.
356
- */
357
- function buildWorkerMalloyConfig(job: LoadPackageRequest): MalloyConfig {
358
- const proxies = new Map<string, ProxyConnection>();
359
- const inflight = new Map<string, Promise<ProxyConnection>>();
360
-
361
- const config = new MalloyConfig(
362
- { connections: {} },
363
- { config: contextOverlay({ rootDirectory: job.packagePath }) },
364
- );
365
- config.wrapConnections(
366
- (_base: LookupConnection<Connection>): LookupConnection<Connection> => ({
367
- lookupConnection: async (name?: string): Promise<Connection> => {
368
- const effectiveName = name ?? job.defaultConnectionName ?? "duckdb";
369
- const cached = proxies.get(effectiveName);
370
- if (cached) return cached as unknown as Connection;
371
- let pending = inflight.get(effectiveName);
372
- if (!pending) {
373
- pending = (async () => {
374
- const response = await callMain<ConnectionMetadataResponse>(
375
- (requestId) => {
376
- const req: ConnectionMetadataRequest = {
377
- type: "connection-metadata",
378
- requestId,
379
- jobId: job.requestId,
380
- connectionName: effectiveName,
381
- };
382
- port.postMessage(req);
383
- },
384
- );
385
- const proxy = new ProxyConnection(
386
- response.metadata,
387
- job.requestId,
388
- );
389
- proxies.set(effectiveName, proxy);
390
- inflight.delete(effectiveName);
391
- return proxy;
392
- })();
393
- inflight.set(effectiveName, pending);
394
- }
395
- return (await pending) as unknown as Connection;
396
- },
397
- }),
398
- );
399
- return config;
400
- }
401
-
402
- // ──────────────────────────────────────────────────────────────────────
403
- // Filesystem helpers (replicated from service/package.ts so the worker
404
- // stays decoupled from the main-thread service module graph)
405
- // ──────────────────────────────────────────────────────────────────────
406
-
407
- async function readPackageMetadata(packagePath: string): Promise<{
408
- name?: string;
409
- description?: string;
410
- explores?: string[];
411
- queryableSources?: "declared" | "all";
412
- manifestLocation?: string | null;
413
- materialization?: PackageMaterializationConfig | null;
414
- scope?: PackageScope;
415
- manifestWarnings?: string[];
416
- }> {
417
- const manifestPath = path.join(packagePath, PACKAGE_MANIFEST_NAME);
418
- const contents = await fs.promises.readFile(manifestPath, "utf8");
419
- const parsed = JSON.parse(contents) as {
420
- name?: string;
421
- description?: string;
422
- explores?: string[];
423
- queryableSources?: unknown;
424
- manifestLocation?: unknown;
425
- materialization?: unknown;
426
- scope?: unknown;
427
- };
428
- // Scope has two homes (canonical `materialization.scope`, deprecated root);
429
- // an invalid value or a conflict between the two throws and fails the load,
430
- // and the deprecation rides back as a warning.
431
- const scope = resolvePackageScope(parsed.scope, parsed.materialization);
432
- const manifestWarnings = [
433
- ...scope.warnings,
434
- ...packageMaterializationWarnings(parsed.materialization),
435
- ];
436
- return {
437
- name: parsed.name,
438
- description: parsed.description,
439
- explores: Array.isArray(parsed.explores)
440
- ? parsed.explores.map(normalizeModelPath)
441
- : undefined,
442
- // Default + invalid fall back to "declared" (fail-safe: queryable ==
443
- // discoverable). Only an explicit "all" opts out of the query boundary.
444
- queryableSources: parsed.queryableSources === "all" ? "all" : "declared",
445
- // URI (gs://, s3://, file://, or local path) of the control-plane-computed
446
- // build manifest. The main thread fetches + binds it after load.
447
- manifestLocation:
448
- typeof parsed.manifestLocation === "string"
449
- ? parsed.manifestLocation
450
- : null,
451
- // Package-level Malloy Persistence policy; surfaced to the control plane,
452
- // which owns scheduling. `schedule`/`freshness` are for the control plane;
453
- // `queryMetadata` is the publisher's own package-level layer.
454
- materialization: parsePackageMaterialization(parsed.materialization),
455
- // Package-level persist scope mode; defaults to "package".
456
- scope: scope.scope,
457
- manifestWarnings:
458
- manifestWarnings.length > 0 ? manifestWarnings : undefined,
459
- };
460
- }
461
-
462
- async function listPackageFiles(packagePath: string): Promise<string[]> {
463
- const files = await recursive(packagePath, [ignoreDotfiles]);
464
- return files.map((full: string) =>
465
- path.relative(packagePath, full).replace(/\\/g, "/"),
466
- );
467
- }
468
-
469
- function filterModelPaths(allRelative: string[]): string[] {
470
- return allRelative.filter(
471
- (p) => p.endsWith(MODEL_FILE_SUFFIX) || p.endsWith(NOTEBOOK_FILE_SUFFIX),
472
- );
473
- }
474
-
475
- // `normalizeModelPath` (shared, from ../constants) runs here at parse time so
476
- // the keys stored in Package.models are already normalized, and the API
477
- // publish/update path normalizes its `explores` input through the same helper —
478
- // so on-disk and API-written manifests share one representation.
479
-
480
- // explores validation is intentionally NOT done here. The worker is the
481
- // shared load path (startup, reload, AND publish), but the policy differs by
482
- // context — strict-reject at publish, warn-and-fail-safe at load — so it lives
483
- // on the main thread (see Package.getInvalidExplores / loadViaWorker and the
484
- // publish path in package.controller). An unmatched entry simply lists nothing
485
- // (listModels filters by set membership), which is the fail-safe outcome.
486
-
487
- // ──────────────────────────────────────────────────────────────────────
488
- // Model compile (mirrors service/model.ts but produces SerializedModel)
489
- // ──────────────────────────────────────────────────────────────────────
490
-
491
- interface ApiSourceWire {
492
- name: string;
493
- annotations?: string[];
494
- views?: { name: string; annotations?: string[] }[];
495
- filters?: unknown[];
496
- givens?: unknown[];
497
- authorize?: string[];
498
- }
499
- interface ApiQueryWire {
500
- name: string;
501
- sourceName?: string;
502
- annotations?: string[];
503
- }
504
- type ApiGivenWire = MalloyGivenApi;
505
-
506
- async function collectImportedSourceInfos(
507
- modelDef: ModelDef,
508
- runtime: Runtime,
509
- importBaseURL: URL,
510
- ): Promise<{
511
- sourceInfos: Malloy.SourceInfo[];
512
- importedNames: Set<string>;
513
- }> {
514
- const sourceInfos: Malloy.SourceInfo[] = [];
515
- const importedNames = new Set<string>();
516
- const imports = modelDef.imports ?? [];
517
- for (const importLocation of imports) {
518
- try {
519
- const modelString = await runtime.urlReader.readURL(
520
- new URL(importLocation.importURL),
521
- );
522
- const importedModelDef = (
523
- await runtime
524
- .loadModel(modelString as string, { importBaseURL })
525
- .getModel()
526
- )._modelDef;
527
- const importedInfo = modelDefToModelInfo(importedModelDef);
528
- const importedSources = importedInfo.entries.filter(
529
- (entry) => entry.kind === "source",
530
- ) as Malloy.SourceInfo[];
531
- for (const source of importedSources) {
532
- if (!importedNames.has(source.name)) {
533
- sourceInfos.push(source);
534
- importedNames.add(source.name);
535
- }
536
- }
537
- } catch {
538
- // Best-effort, matches the in-process Model.create behaviour
539
- // of warning-and-skipping when an import can't be loaded.
540
- }
541
- }
542
- return { sourceInfos, importedNames };
543
- }
544
-
545
- function appendLocalSourceInfos(
546
- modelDef: ModelDef,
547
- target: Malloy.SourceInfo[],
548
- importedNames: Set<string>,
549
- ): void {
550
- const localInfo = modelDefToModelInfo(modelDef);
551
- const localSources = localInfo.entries.filter(
552
- (entry) => entry.kind === "source",
553
- ) as Malloy.SourceInfo[];
554
- for (const source of localSources) {
555
- if (!importedNames.has(source.name)) target.push(source);
556
- }
557
- }
558
-
559
- // Source / query introspection is shared with the in-process path; see
560
- // service/source_extraction.ts. The worker has no logger, so a filter parse
561
- // failure is swallowed silently (no `onParseError` callback) — matching the
562
- // prior worker-local behavior.
563
- function extractSources(
564
- modelDef: ModelDef,
565
- givens: ApiGivenWire[] | undefined,
566
- ): { sources: ApiSourceWire[]; filterMap: Map<string, FilterDefinition[]> } {
567
- const { sources, filterMap } = extractSourcesFromModelDef(modelDef, givens);
568
- return { sources: sources as unknown as ApiSourceWire[], filterMap };
569
- }
570
-
571
- function extractQueries(modelDef: ModelDef): ApiQueryWire[] {
572
- return extractQueriesFromModelDef(modelDef) as ApiQueryWire[];
573
- }
574
-
575
- function buildRuntimeForModel(
576
- job: LoadPackageRequest,
577
- malloyConfig: MalloyConfig,
578
- jobId: string,
579
- ): { runtime: Runtime; urlReader: HackyDataStylesAccumulator } {
580
- const urlReader = new HackyDataStylesAccumulator(makeWorkerUrlReader(jobId));
581
- const runtime = new Runtime({
582
- urlReader,
583
- config: malloyConfig,
584
- buildManifest:
585
- job.buildManifest !== undefined && job.buildManifest !== null
586
- ? {
587
- entries: job.buildManifest as Record<
588
- string,
589
- BuildManifestEntry
590
- >,
591
- strict: false,
592
- }
593
- : undefined,
594
- });
595
- return { runtime, urlReader };
596
- }
597
-
598
- async function compileMalloyModel(
599
- job: LoadPackageRequest,
600
- malloyConfig: MalloyConfig,
601
- modelPath: string,
602
- ): Promise<SerializedModel> {
603
- const compileStart = performance.now();
604
- const fullPath = path.join(job.packagePath, modelPath);
605
- // `pathToFileURL` produces a valid URL on every platform; the
606
- // naïve `file://${fullPath}` template parses host=`D:` on Windows.
607
- const modelURL = pathToFileURL(fullPath);
608
- const importBaseURL = new URL(".", modelURL);
609
-
610
- const { runtime, urlReader } = buildRuntimeForModel(
611
- job,
612
- malloyConfig,
613
- job.requestId,
614
- );
615
- const mm = runtime.loadModel(modelURL, { importBaseURL });
616
- const compiled = await mm.getModel();
617
- const modelDef = compiled._modelDef;
618
-
619
- const malloyGivens = Array.from(compiled.givens.values());
620
- const givens =
621
- malloyGivens.length > 0
622
- ? malloyGivens.map((g) => malloyGivenToApi(g as MalloyGiven))
623
- : undefined;
624
-
625
- const { sourceInfos, importedNames } = await collectImportedSourceInfos(
626
- modelDef,
627
- runtime,
628
- importBaseURL,
629
- );
630
- appendLocalSourceInfos(modelDef, sourceInfos, importedNames);
631
-
632
- const { sources, filterMap } = extractSources(modelDef, givens);
633
- const queries = extractQueries(modelDef);
634
- // Validate #(authorize) at compile time (shared with Model.create). Throws
635
- // on an unknown given / source-field reference; compileOneModel's catch
636
- // turns it into this model's compilationError.
637
- await validateAuthorizeProbes(mm, sources);
638
-
639
- return {
640
- modelPath,
641
- modelType: "model",
642
- modelDef,
643
- modelInfo: modelDefToModelInfo(modelDef),
644
- sourceInfos,
645
- // `sources`/`queries` ship complete (authorize + filter enforcement and
646
- // join resolution read the full set); the Model's discovery accessors
647
- // filter them down to the export closure (`modelDef.exports`) to match
648
- // `modelInfo`/`sourceInfos`.
649
- sources,
650
- queries,
651
- filterMap: Array.from(filterMap.entries()),
652
- givens,
653
- dataStyles: urlReader.getHackyAccumulatedDataStyles(),
654
- compileDurationMs: performance.now() - compileStart,
655
- };
656
- }
657
-
658
- async function compileNotebookModel(
659
- job: LoadPackageRequest,
660
- malloyConfig: MalloyConfig,
661
- modelPath: string,
662
- ): Promise<SerializedModel> {
663
- const compileStart = performance.now();
664
- const fullPath = path.join(job.packagePath, modelPath);
665
- // See compileMalloyModel above: `pathToFileURL` is the only
666
- // cross-platform way to build a file URL from an OS path.
667
- const modelURL = pathToFileURL(fullPath);
668
- const importBaseURL = new URL(".", modelURL);
669
-
670
- const { runtime, urlReader } = buildRuntimeForModel(
671
- job,
672
- malloyConfig,
673
- job.requestId,
674
- );
675
-
676
- const fileContents = await fs.promises.readFile(modelURL, "utf8");
677
- const parse = MalloySQLParser.parse(fileContents, modelPath);
678
-
679
- // Build the extendModel chain synchronously so per-cell materializers
680
- // line up with statement order. Matches the in-process flow in
681
- // Model.getNotebookModelMaterializer.
682
- let mm: ModelMaterializer | undefined;
683
- const perCellMM: (ModelMaterializer | undefined)[] = parse.statements.map(
684
- (stmt) => {
685
- if (stmt.type === MalloySQLStatementType.MALLOY) {
686
- mm =
687
- mm === undefined
688
- ? runtime.loadModel(stmt.text, { importBaseURL })
689
- : mm.extendModel(stmt.text, { importBaseURL });
690
- }
691
- return mm;
692
- },
693
- );
694
-
695
- const oldImports: string[] = [];
696
- const oldSources: Record<string, Malloy.SourceInfo> = {};
697
- const notebookCells: SerializedNotebookCell[] = [];
698
- for (let i = 0; i < parse.statements.length; i++) {
699
- const stmt = parse.statements[i];
700
- if (stmt.type === MalloySQLStatementType.MARKDOWN) {
701
- notebookCells.push({ type: "markdown", text: stmt.text });
702
- continue;
703
- }
704
- if (stmt.type !== MalloySQLStatementType.MALLOY) continue;
705
-
706
- const localMM = perCellMM[i];
707
- if (!localMM) {
708
- // Shouldn't happen for a MALLOY statement, but guard rather
709
- // than crash a whole notebook compile on one corrupt cell.
710
- continue;
711
- }
712
- const currentModelDef = (await localMM.getModel())._modelDef;
713
-
714
- // newSources via the import chain — mirrors in-process logic.
715
- let newSources: Malloy.SourceInfo[] = [];
716
- const newImports = currentModelDef.imports?.slice(oldImports.length);
717
- if (newImports) {
718
- for (const importLocation of newImports) {
719
- try {
720
- const modelString = await runtime.urlReader.readURL(
721
- new URL(importLocation.importURL),
722
- );
723
- const importModel = (
724
- await runtime
725
- .loadModel(modelString as string, { importBaseURL })
726
- .getModel()
727
- )._modelDef;
728
- const importInfo = modelDefToModelInfo(importModel);
729
- newSources = importInfo.entries
730
- .filter((e) => e.kind === "source")
731
- .filter(
732
- (s) => !(s.name in oldSources),
733
- ) as Malloy.SourceInfo[];
734
- oldImports.push(importLocation.importURL.toString());
735
- } catch {
736
- // Same best-effort policy as the in-process path.
737
- }
738
- }
739
- }
740
- const currentInfo = modelDefToModelInfo(currentModelDef);
741
- newSources = newSources.concat(
742
- currentInfo.entries
743
- .filter((e) => e.kind === "source")
744
- .filter((s) => !(s.name in oldSources)) as Malloy.SourceInfo[],
745
- );
746
- for (const s of newSources) oldSources[s.name] = s;
747
-
748
- // Capture the per-cell final-query queryDef so the main thread can
749
- // hydrate a QueryMaterializer via
750
- // `ModelMaterializer._loadQueryFromQueryDef` without a recompile.
751
- const runnable = localMM.loadFinalQuery();
752
- let cellQueryDef: Query | undefined;
753
- let queryInfo: Malloy.QueryInfo | undefined;
754
- try {
755
- const prepared = await runnable.getPreparedQuery();
756
- cellQueryDef = prepared._query;
757
- const queryName =
758
- (prepared._query as NamedQueryDef).as ||
759
- (prepared._query as NamedQueryDef).name;
760
- const anonymous =
761
- currentInfo.anonymous_queries[
762
- currentInfo.anonymous_queries.length - 1
763
- ];
764
- if (anonymous) {
765
- queryInfo = {
766
- name: queryName,
767
- schema: anonymous.schema,
768
- annotations: anonymous.annotations,
769
- definition: anonymous.definition,
770
- code: anonymous.code,
771
- location: anonymous.location,
772
- } as Malloy.QueryInfo;
773
- }
774
- } catch {
775
- // Some cells (source-only) have no final query; that's fine.
776
- }
777
-
778
- notebookCells.push({
779
- type: "code",
780
- text: stmt.text,
781
- cellModelDef: currentModelDef,
782
- cellQueryDef,
783
- newSources,
784
- queryInfo,
785
- });
786
- }
787
-
788
- // Aggregate (notebook-level) artifacts — derived from the final mm
789
- // if any MALLOY statements were present. If the notebook is all
790
- // markdown, modelDef stays undefined and the main thread treats
791
- // this as a notebook with no compiled content.
792
- let finalModelDef: ModelDef | undefined;
793
- let finalSources: ApiSourceWire[] | undefined;
794
- let finalQueries: ApiQueryWire[] | undefined;
795
- let finalSourceInfos: Malloy.SourceInfo[] | undefined;
796
- let finalFilterMap: Map<string, FilterDefinition[]> | undefined;
797
- let finalGivens: ApiGivenWire[] | undefined;
798
- if (mm) {
799
- const compiled = await mm.getModel();
800
- finalModelDef = compiled._modelDef;
801
- const malloyGivens = Array.from(compiled.givens.values());
802
- finalGivens =
803
- malloyGivens.length > 0
804
- ? malloyGivens.map((g) => malloyGivenToApi(g as MalloyGiven))
805
- : undefined;
806
- const collected = await collectImportedSourceInfos(
807
- finalModelDef,
808
- runtime,
809
- importBaseURL,
810
- );
811
- appendLocalSourceInfos(
812
- finalModelDef,
813
- collected.sourceInfos,
814
- collected.importedNames,
815
- );
816
- finalSourceInfos = collected.sourceInfos;
817
- const extracted = extractSources(finalModelDef, finalGivens);
818
- finalSources = extracted.sources;
819
- finalFilterMap = extracted.filterMap;
820
- finalQueries = extractQueries(finalModelDef);
821
- // Validate #(authorize) at compile time (shared with Model.create).
822
- await validateAuthorizeProbes(mm, finalSources);
823
- }
824
-
825
- return {
826
- modelPath,
827
- modelType: "notebook",
828
- modelDef: finalModelDef,
829
- modelInfo: finalModelDef ? modelDefToModelInfo(finalModelDef) : undefined,
830
- sourceInfos: finalSourceInfos,
831
- sources: finalSources,
832
- queries: finalQueries,
833
- filterMap: finalFilterMap
834
- ? Array.from(finalFilterMap.entries())
835
- : undefined,
836
- givens: finalGivens,
837
- notebookCells,
838
- dataStyles: urlReader.getHackyAccumulatedDataStyles(),
839
- compileDurationMs: performance.now() - compileStart,
840
- };
841
- }
842
-
843
- async function compileOneModel(
844
- job: LoadPackageRequest,
845
- malloyConfig: MalloyConfig,
846
- modelPath: string,
847
- ): Promise<SerializedModel> {
848
- try {
849
- if (modelPath.endsWith(MODEL_FILE_SUFFIX)) {
850
- return await compileMalloyModel(job, malloyConfig, modelPath);
851
- }
852
- if (modelPath.endsWith(NOTEBOOK_FILE_SUFFIX)) {
853
- return await compileNotebookModel(job, malloyConfig, modelPath);
854
- }
855
- return {
856
- modelPath,
857
- modelType: "model",
858
- compilationError: {
859
- name: "Error",
860
- message: `Unknown model file suffix: ${modelPath}`,
861
- },
862
- };
863
- } catch (error) {
864
- const modelType: SerializedModel["modelType"] = modelPath.endsWith(
865
- NOTEBOOK_FILE_SUFFIX,
866
- )
867
- ? "notebook"
868
- : "model";
869
- return {
870
- modelPath,
871
- modelType,
872
- compilationError: serializeError(error),
873
- };
874
- }
875
- }
876
-
877
- // ──────────────────────────────────────────────────────────────────────
878
- // The actual load-package job
879
- // ──────────────────────────────────────────────────────────────────────
880
-
881
- async function loadPackage(
882
- job: LoadPackageRequest,
883
- ): Promise<LoadPackageResult> {
884
- const loadStart = performance.now();
885
-
886
- const packageMetadata = await readPackageMetadata(job.packagePath);
887
- const malloyConfig = buildWorkerMalloyConfig(job);
888
-
889
- const allFiles = await listPackageFiles(job.packagePath);
890
- const modelPaths = filterModelPaths(allFiles);
891
-
892
- // Bracket the compile region: only work from here on is compilation +
893
- // proxied schema fetches. The setup above (manifest read + file listing)
894
- // is excluded so it can't inflate the compile figure — it stays in the
895
- // derivable remainder (loadDuration - compile - schemaFetch). Begin the
896
- // schema-fetch accounting HERE, at the region boundary, not at load start:
897
- // that keeps `compileDurationMs = compileRegion - schemaFetchWait` exact
898
- // regardless of whether any fetch ever happens during setup (none do
899
- // today, but this stops that assumption from silently mattering).
900
- const compileRegionStart = performance.now();
901
- schemaWait.begin(job.requestId);
902
- const models = await Promise.all(
903
- modelPaths.map((modelPath) =>
904
- compileOneModel(job, malloyConfig, modelPath),
905
- ),
906
- );
907
-
908
- const loadEnd = performance.now();
909
- const schemaFetchDurationMs = schemaWait.waitMs;
910
- return {
911
- type: "load-package-result",
912
- requestId: job.requestId,
913
- packageMetadata,
914
- models,
915
- loadDurationMs: loadEnd - loadStart,
916
- timings: {
917
- // Compile-region wall minus the schema-fetch wait it contains — a
918
- // conservative ceiling on compile CPU (clamped: rounding can nudge
919
- // it slightly negative).
920
- compileDurationMs: Math.max(
921
- 0,
922
- loadEnd - compileRegionStart - schemaFetchDurationMs,
923
- ),
924
- schemaFetchDurationMs,
925
- schemaFetchCount: schemaWait.fetches,
926
- },
927
- };
928
- }
929
-
930
- // ──────────────────────────────────────────────────────────────────────
931
- // Error serialization
932
- // ──────────────────────────────────────────────────────────────────────
933
-
934
- function serializeError(error: unknown): SerializedError {
935
- if (error instanceof MalloyError) {
936
- return {
937
- name: error.name,
938
- message: error.message,
939
- stack: error.stack,
940
- malloyProblems: error.problems as unknown[],
941
- isCompilationError: true,
942
- };
943
- }
944
- // ModelCompilationError (e.g. an invalid #(authorize) annotation caught by
945
- // validateAuthorizeProbes) carries no Malloy `problems`, but it must keep its
946
- // compilation-error classification across the worker boundary so the main
947
- // thread re-wraps it as a 424, not a generic 500.
948
- if (error instanceof ModelCompilationError) {
949
- return {
950
- name: error.name,
951
- message: error.message,
952
- stack: error.stack,
953
- isCompilationError: true,
954
- };
955
- }
956
- if (error instanceof Error) {
957
- return {
958
- name: error.name,
959
- message: error.message,
960
- stack: error.stack,
961
- };
962
- }
963
- return { name: "Error", message: String(error) };
964
- }
965
-
966
- function deserializeError(serialized: SerializedError): Error {
967
- const err = new Error(serialized.message);
968
- err.name = serialized.name;
969
- if (serialized.stack) err.stack = serialized.stack;
970
- return err;
971
- }
972
-
973
- // ──────────────────────────────────────────────────────────────────────
974
- // Message dispatcher
975
- // ──────────────────────────────────────────────────────────────────────
976
-
977
- let shuttingDown = false;
978
- const inFlightJobs = new Set<string>();
979
-
980
- port.on("message", (message: MainToWorkerMessage) => {
981
- if (message.type === "shutdown") {
982
- shuttingDown = true;
983
- maybeExit();
984
- return;
985
- }
986
- if (message.type === "load-package") {
987
- if (shuttingDown) {
988
- const errMsg: LoadPackageError = {
989
- type: "load-package-error",
990
- requestId: message.requestId,
991
- error: {
992
- name: "ShuttingDown",
993
- message: "Package-load worker is shutting down",
994
- },
995
- };
996
- port.postMessage(errMsg);
997
- return;
998
- }
999
- inFlightJobs.add(message.requestId);
1000
- void runJob(message);
1001
- return;
1002
- }
1003
- dispatchMainResponse(message);
1004
- });
1005
-
1006
- async function runJob(job: LoadPackageRequest): Promise<void> {
1007
- try {
1008
- const result = await loadPackage(job);
1009
- port.postMessage(result);
1010
- } catch (error) {
1011
- const errMsg: LoadPackageError = {
1012
- type: "load-package-error",
1013
- requestId: job.requestId,
1014
- error: serializeError(error),
1015
- };
1016
- port.postMessage(errMsg);
1017
- } finally {
1018
- inFlightJobs.delete(job.requestId);
1019
- maybeExit();
1020
- }
1021
- }
1022
-
1023
- function maybeExit(): void {
1024
- if (shuttingDown && inFlightJobs.size === 0 && pendingRpc.size === 0) {
1025
- // Give the postMessage queue a tick to flush before exit so the
1026
- // last result actually reaches the parent.
1027
- setImmediate(() => process.exit(0));
1028
- }
1029
- }
1030
-
1031
- // Announce readiness — the pool waits for this before dispatching jobs
1032
- // to a newly-spawned worker so we don't race the worker's module-init
1033
- // time.
1034
- port.postMessage({ type: "ready" });