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