@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,193 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import * as fs from "fs/promises";
|
|
3
|
-
import * as os from "os";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
|
|
6
|
-
import { Environment, PackageStatus } from "./environment";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Regression tests for RELOAD data loss.
|
|
10
|
-
*
|
|
11
|
-
* `Package.create`'s failure cleanup was written for a fresh install, where the
|
|
12
|
-
* half-built tree is Publisher's to remove. A reload runs the same code against
|
|
13
|
-
* a directory that already exists and is already serving, so a model that
|
|
14
|
-
* failed to compile deleted the user's package directory and evicted the
|
|
15
|
-
* last-good compiled model, taking the package offline until it was
|
|
16
|
-
* re-provisioned. That is reachable from the reload endpoint and the
|
|
17
|
-
* malloy_reloadPackage MCP tool by any caller that saves a broken model and
|
|
18
|
-
* reloads, which is the ordinary authoring mistake.
|
|
19
|
-
*
|
|
20
|
-
* Directory cleanup is now opt-in and only `installPackage` asks for it, so
|
|
21
|
-
* these two guard the reload path. Run against a real `Environment` and a real
|
|
22
|
-
* `Package.create` over temp dirs, the same way package_rollback.spec.ts does.
|
|
23
|
-
*/
|
|
24
|
-
describe("failed reload does not destroy a serving package", () => {
|
|
25
|
-
let rootDir: string;
|
|
26
|
-
let envPath: string;
|
|
27
|
-
|
|
28
|
-
const GOOD_MODEL = `source: ones is duckdb.sql("SELECT 1 as x")\n`;
|
|
29
|
-
const BROKEN_MODEL = `source: broken is @@@ not valid malloy !!!\n`;
|
|
30
|
-
|
|
31
|
-
async function writePackageDir(dir: string, model: string): Promise<void> {
|
|
32
|
-
await fs.mkdir(dir, { recursive: true });
|
|
33
|
-
await fs.writeFile(
|
|
34
|
-
path.join(dir, "publisher.json"),
|
|
35
|
-
JSON.stringify({ name: "pkg", description: "reload fixture" }),
|
|
36
|
-
);
|
|
37
|
-
await fs.writeFile(path.join(dir, "model.malloy"), model);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function copyDir(src: string, dst: string): Promise<void> {
|
|
41
|
-
await fs.mkdir(dst, { recursive: true });
|
|
42
|
-
await fs.cp(src, dst, { recursive: true });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
beforeEach(async () => {
|
|
46
|
-
rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "publisher-reload-"));
|
|
47
|
-
envPath = path.join(rootDir, "env");
|
|
48
|
-
await fs.mkdir(envPath, { recursive: true });
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
afterEach(async () => {
|
|
52
|
-
await fs.rm(rootDir, { recursive: true, force: true }).catch(() => {});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("keeps the package directory on disk when the reloaded model does not compile", async () => {
|
|
56
|
-
const env = await Environment.create("testEnv", envPath, []);
|
|
57
|
-
const pkgDir = path.join(envPath, "pkg");
|
|
58
|
-
await writePackageDir(pkgDir, GOOD_MODEL);
|
|
59
|
-
await env.addPackage("pkg");
|
|
60
|
-
|
|
61
|
-
// The ordinary authoring mistake: save something that does not compile,
|
|
62
|
-
// then reload.
|
|
63
|
-
await fs.writeFile(path.join(pkgDir, "model.malloy"), BROKEN_MODEL);
|
|
64
|
-
await expect(env.getPackage("pkg", true)).rejects.toThrow();
|
|
65
|
-
|
|
66
|
-
// The directory and the file the user was editing both survive.
|
|
67
|
-
const modelText = await fs.readFile(
|
|
68
|
-
path.join(pkgDir, "model.malloy"),
|
|
69
|
-
"utf-8",
|
|
70
|
-
);
|
|
71
|
-
expect(modelText).toBe(BROKEN_MODEL);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("keeps serving the last good model after a failed reload", async () => {
|
|
75
|
-
const env = await Environment.create("testEnv", envPath, []);
|
|
76
|
-
const pkgDir = path.join(envPath, "pkg");
|
|
77
|
-
await writePackageDir(pkgDir, GOOD_MODEL);
|
|
78
|
-
await env.addPackage("pkg");
|
|
79
|
-
|
|
80
|
-
const servingBefore = await env.getPackage("pkg", false);
|
|
81
|
-
|
|
82
|
-
await fs.writeFile(path.join(pkgDir, "model.malloy"), BROKEN_MODEL);
|
|
83
|
-
await expect(env.getPackage("pkg", true)).rejects.toThrow();
|
|
84
|
-
|
|
85
|
-
// The package is still loaded and answerable: a failed reload reports the
|
|
86
|
-
// compile error, it does not take the package down. Assert the exact
|
|
87
|
-
// state, not just that something is there: a status stranded at LOADING
|
|
88
|
-
// would satisfy toBeDefined() while listPackages skips it, so the package
|
|
89
|
-
// would answer getPackage and be invisible to listings and discovery.
|
|
90
|
-
const stillServing = await env.getPackage("pkg", false);
|
|
91
|
-
expect(stillServing).toBe(servingBefore);
|
|
92
|
-
expect(env.getPackageStatus("pkg")?.status).toBe(PackageStatus.SERVING);
|
|
93
|
-
expect((await env.listPackages()).map((p) => p.name)).toContain("pkg");
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("keeps a rolled-back reinstall listed and serving", async () => {
|
|
97
|
-
// The other reload path. A package with an install location reloads via
|
|
98
|
-
// installPackage, whose rollback restores the previous tree but used to
|
|
99
|
-
// drop the status while `packages` kept the old package: it answered
|
|
100
|
-
// getPackage but vanished from listPackages and discovery until a
|
|
101
|
-
// restart. Both maps must agree that it is still serving.
|
|
102
|
-
const env = await Environment.create("testEnv", envPath, []);
|
|
103
|
-
const goodFixture = path.join(rootDir, "good");
|
|
104
|
-
const brokenFixture = path.join(rootDir, "broken");
|
|
105
|
-
await writePackageDir(goodFixture, GOOD_MODEL);
|
|
106
|
-
await writePackageDir(brokenFixture, BROKEN_MODEL);
|
|
107
|
-
|
|
108
|
-
await env.installPackage("pkg", (stagingPath) =>
|
|
109
|
-
copyDir(goodFixture, stagingPath),
|
|
110
|
-
);
|
|
111
|
-
const servingBefore = await env.getPackage("pkg", false);
|
|
112
|
-
|
|
113
|
-
await expect(
|
|
114
|
-
env.installPackage("pkg", (stagingPath) =>
|
|
115
|
-
copyDir(brokenFixture, stagingPath),
|
|
116
|
-
),
|
|
117
|
-
).rejects.toThrow();
|
|
118
|
-
|
|
119
|
-
expect(env.getPackageStatus("pkg")?.status).toBe(PackageStatus.SERVING);
|
|
120
|
-
expect((await env.listPackages()).map((p) => p.name)).toContain("pkg");
|
|
121
|
-
expect(await env.getPackage("pkg", false)).toBe(servingBefore);
|
|
122
|
-
// The on-disk half of the same claim: the rejected tree is gone and the
|
|
123
|
-
// user's is back. Without this, nothing pins "your files are left alone".
|
|
124
|
-
expect(
|
|
125
|
-
await fs.readFile(path.join(envPath, "pkg", "model.malloy"), "utf-8"),
|
|
126
|
-
).toBe(GOOD_MODEL);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("does not advertise a package as serving when the rollback could not restore it", async () => {
|
|
130
|
-
// Keeping the last-good package served is only honest when the rollback
|
|
131
|
-
// actually put its tree back. If nothing was restored, the cached package
|
|
132
|
-
// no longer matches disk, and claiming SERVING would advertise a package
|
|
133
|
-
// over a canonical path that is missing or still holds rejected content.
|
|
134
|
-
const env = await Environment.create("testEnv", envPath, []);
|
|
135
|
-
const pkgDir = path.join(envPath, "pkg");
|
|
136
|
-
await writePackageDir(pkgDir, GOOD_MODEL);
|
|
137
|
-
await env.addPackage("pkg");
|
|
138
|
-
expect(env.getPackageStatus("pkg")?.status).toBe(PackageStatus.SERVING);
|
|
139
|
-
|
|
140
|
-
// Record retirements rather than scheduling the real drain, which is a
|
|
141
|
-
// 30s timer this test has no reason to wait on.
|
|
142
|
-
const retiredLabels: string[] = [];
|
|
143
|
-
(
|
|
144
|
-
env as unknown as {
|
|
145
|
-
retireConnectionGeneration: (label: string) => void;
|
|
146
|
-
}
|
|
147
|
-
).retireConnectionGeneration = (label: string) => {
|
|
148
|
-
retiredLabels.push(label);
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
// The tree disappears from under Publisher, so the reinstall has nothing
|
|
152
|
-
// to retire and the rollback has nothing to restore.
|
|
153
|
-
await fs.rm(pkgDir, { recursive: true, force: true });
|
|
154
|
-
|
|
155
|
-
const brokenFixture = path.join(rootDir, "broken-nofallback");
|
|
156
|
-
await writePackageDir(brokenFixture, BROKEN_MODEL);
|
|
157
|
-
await expect(
|
|
158
|
-
env.installPackage("pkg", (stagingPath) =>
|
|
159
|
-
copyDir(brokenFixture, stagingPath),
|
|
160
|
-
),
|
|
161
|
-
).rejects.toThrow();
|
|
162
|
-
|
|
163
|
-
// Eviction is the honest outcome here, and both maps must agree on it.
|
|
164
|
-
expect(env.getPackageStatus("pkg")).toBeUndefined();
|
|
165
|
-
expect((await env.listPackages()).map((p) => p.name)).not.toContain(
|
|
166
|
-
"pkg",
|
|
167
|
-
);
|
|
168
|
-
// Assert the packages map too, not just the status: asserting only the
|
|
169
|
-
// status leaves the half that actually strands a package untested.
|
|
170
|
-
await expect(env.getPackage("pkg", false)).rejects.toThrow();
|
|
171
|
-
|
|
172
|
-
// Evicting must retire the old package's connections. Once it leaves
|
|
173
|
-
// this.packages, closeAllConnections cannot reach its MalloyConfig, so
|
|
174
|
-
// skipping the retire orphans its native handles for the process's life.
|
|
175
|
-
// The drain is timer-based, so assert it was scheduled, not that it ran.
|
|
176
|
-
expect(retiredLabels).toContain("package pkg");
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it("recovers on the next reload once the model compiles again", async () => {
|
|
180
|
-
const env = await Environment.create("testEnv", envPath, []);
|
|
181
|
-
const pkgDir = path.join(envPath, "pkg");
|
|
182
|
-
await writePackageDir(pkgDir, GOOD_MODEL);
|
|
183
|
-
await env.addPackage("pkg");
|
|
184
|
-
|
|
185
|
-
await fs.writeFile(path.join(pkgDir, "model.malloy"), BROKEN_MODEL);
|
|
186
|
-
await expect(env.getPackage("pkg", true)).rejects.toThrow();
|
|
187
|
-
|
|
188
|
-
// Because nothing was deleted, fixing the model and reloading is enough.
|
|
189
|
-
await fs.writeFile(path.join(pkgDir, "model.malloy"), GOOD_MODEL);
|
|
190
|
-
const reloaded = await env.getPackage("pkg", true);
|
|
191
|
-
expect(reloaded).toBeDefined();
|
|
192
|
-
});
|
|
193
|
-
});
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import * as fs from "fs/promises";
|
|
3
|
-
import * as os from "os";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
|
|
6
|
-
import { BadRequestError } from "../errors";
|
|
7
|
-
import { Environment } from "./environment";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Regression tests for explores-validation ROLLBACK, guarding against the two
|
|
11
|
-
* data-loss bugs a code review found:
|
|
12
|
-
*
|
|
13
|
-
* 1. No-location create rollback must NOT delete a pre-existing user
|
|
14
|
-
* directory. The controller now `unloadPackage`s (evict from memory, keep
|
|
15
|
-
* files) instead of `deletePackage` (which renames + rm's the tree).
|
|
16
|
-
*
|
|
17
|
-
* 2. A location update/reinstall with invalid explores must roll back to the
|
|
18
|
-
* PREVIOUS tree rather than swapping the rejected one in and 400-ing after.
|
|
19
|
-
* Validation now happens inside `installPackage`'s swap window.
|
|
20
|
-
*
|
|
21
|
-
* Both run against a real `Environment` + real `Package.create` over temp dirs.
|
|
22
|
-
*/
|
|
23
|
-
describe("explores-validation rollback (real Environment)", () => {
|
|
24
|
-
let rootDir: string;
|
|
25
|
-
let envPath: string;
|
|
26
|
-
|
|
27
|
-
const GOOD_MODEL = `source: ones is duckdb.sql("SELECT 1 as x")\n`;
|
|
28
|
-
const BAD_MODEL = `source: twos is duckdb.sql("SELECT 2 as y")\n`;
|
|
29
|
-
|
|
30
|
-
async function writePackageDir(
|
|
31
|
-
dir: string,
|
|
32
|
-
manifest: Record<string, unknown>,
|
|
33
|
-
model: string,
|
|
34
|
-
): Promise<void> {
|
|
35
|
-
await fs.mkdir(dir, { recursive: true });
|
|
36
|
-
await fs.writeFile(
|
|
37
|
-
path.join(dir, "publisher.json"),
|
|
38
|
-
JSON.stringify({
|
|
39
|
-
name: "pkg",
|
|
40
|
-
description: "rollback fixture",
|
|
41
|
-
...manifest,
|
|
42
|
-
}),
|
|
43
|
-
);
|
|
44
|
-
await fs.writeFile(path.join(dir, "model.malloy"), model);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function copyDir(src: string, dst: string): Promise<void> {
|
|
48
|
-
await fs.mkdir(dst, { recursive: true });
|
|
49
|
-
await fs.cp(src, dst, { recursive: true });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
beforeEach(async () => {
|
|
53
|
-
rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "publisher-rollback-"));
|
|
54
|
-
envPath = path.join(rootDir, "env");
|
|
55
|
-
await fs.mkdir(envPath, { recursive: true });
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
afterEach(async () => {
|
|
59
|
-
await fs.rm(rootDir, { recursive: true, force: true }).catch(() => {});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("Fix 1: unloadPackage evicts from memory but keeps the on-disk directory", async () => {
|
|
63
|
-
// Mimics the no-location create path: a pre-existing package directory is
|
|
64
|
-
// registered, then rolled back. unloadPackage must not touch the files.
|
|
65
|
-
const env = await Environment.create("testEnv", envPath, []);
|
|
66
|
-
const pkgDir = path.join(envPath, "pkg");
|
|
67
|
-
await writePackageDir(pkgDir, {}, GOOD_MODEL);
|
|
68
|
-
|
|
69
|
-
await env.addPackage("pkg");
|
|
70
|
-
expect(env.getPackageStatus("pkg")).toBeDefined();
|
|
71
|
-
|
|
72
|
-
await env.unloadPackage("pkg");
|
|
73
|
-
|
|
74
|
-
// Evicted from memory...
|
|
75
|
-
expect(env.getPackageStatus("pkg")).toBeUndefined();
|
|
76
|
-
// ...but the user's files are untouched.
|
|
77
|
-
const modelText = await fs.readFile(
|
|
78
|
-
path.join(pkgDir, "model.malloy"),
|
|
79
|
-
"utf-8",
|
|
80
|
-
);
|
|
81
|
-
expect(modelText).toBe(GOOD_MODEL);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("Fix 2: a rejected location reinstall restores the previous tree", async () => {
|
|
85
|
-
const env = await Environment.create("testEnv", envPath, []);
|
|
86
|
-
|
|
87
|
-
const goodFixture = path.join(rootDir, "good");
|
|
88
|
-
const badFixture = path.join(rootDir, "bad");
|
|
89
|
-
await writePackageDir(goodFixture, {}, GOOD_MODEL);
|
|
90
|
-
await writePackageDir(
|
|
91
|
-
badFixture,
|
|
92
|
-
{ explores: ["missing.malloy"] },
|
|
93
|
-
BAD_MODEL,
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
// First install: the good tree is served.
|
|
97
|
-
await env.installPackage("pkg", (stagingPath) =>
|
|
98
|
-
copyDir(goodFixture, stagingPath),
|
|
99
|
-
);
|
|
100
|
-
expect(await env.getModelFileText("pkg", "model.malloy")).toBe(
|
|
101
|
-
GOOD_MODEL,
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
// Reinstall with an invalid-explores tree, validated inside the swap.
|
|
105
|
-
await expect(
|
|
106
|
-
env.installPackage(
|
|
107
|
-
"pkg",
|
|
108
|
-
(stagingPath) => copyDir(badFixture, stagingPath),
|
|
109
|
-
(pkg) => pkg.formatInvalidExplores(),
|
|
110
|
-
),
|
|
111
|
-
).rejects.toBeInstanceOf(BadRequestError);
|
|
112
|
-
|
|
113
|
-
// The previous (good) tree is still the one on disk — the rejected tree
|
|
114
|
-
// was rolled back, not swapped in.
|
|
115
|
-
expect(await env.getModelFileText("pkg", "model.malloy")).toBe(
|
|
116
|
-
GOOD_MODEL,
|
|
117
|
-
);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("a valid location reinstall still succeeds (no false rollback)", async () => {
|
|
121
|
-
const env = await Environment.create("testEnv", envPath, []);
|
|
122
|
-
const goodFixture = path.join(rootDir, "good");
|
|
123
|
-
const nextFixture = path.join(rootDir, "next");
|
|
124
|
-
await writePackageDir(goodFixture, {}, GOOD_MODEL);
|
|
125
|
-
await writePackageDir(
|
|
126
|
-
nextFixture,
|
|
127
|
-
{ explores: ["model.malloy"] },
|
|
128
|
-
BAD_MODEL,
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
await env.installPackage("pkg", (stagingPath) =>
|
|
132
|
-
copyDir(goodFixture, stagingPath),
|
|
133
|
-
);
|
|
134
|
-
await env.installPackage(
|
|
135
|
-
"pkg",
|
|
136
|
-
(stagingPath) => copyDir(nextFixture, stagingPath),
|
|
137
|
-
(pkg) => pkg.formatInvalidExplores(),
|
|
138
|
-
);
|
|
139
|
-
expect(await env.getModelFileText("pkg", "model.malloy")).toBe(BAD_MODEL);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it("updatePackage normalizes a ./-prefixed body explores (no false 400, persists normalized)", async () => {
|
|
143
|
-
// API-body explores must go through the same normalization the worker
|
|
144
|
-
// applies to on-disk explores, so `./model.malloy` validates and persists
|
|
145
|
-
// as `model.malloy` rather than being rejected with a misleading 404.
|
|
146
|
-
const env = await Environment.create("testEnv", envPath, []);
|
|
147
|
-
const pkgDir = path.join(envPath, "pkg");
|
|
148
|
-
await writePackageDir(pkgDir, {}, GOOD_MODEL);
|
|
149
|
-
await env.addPackage("pkg");
|
|
150
|
-
|
|
151
|
-
const updated = await env.updatePackage("pkg", {
|
|
152
|
-
name: "pkg",
|
|
153
|
-
explores: ["./model.malloy"],
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// Accepted (no BadRequestError) and stored in normalized form...
|
|
157
|
-
expect(updated.explores).toEqual(["model.malloy"]);
|
|
158
|
-
// ...both in memory and on disk.
|
|
159
|
-
const manifest = JSON.parse(
|
|
160
|
-
await fs.readFile(path.join(pkgDir, "publisher.json"), "utf-8"),
|
|
161
|
-
);
|
|
162
|
-
expect(manifest.explores).toEqual(["model.malloy"]);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("compileSource rejects a notebook path; a model path still compiles", async () => {
|
|
166
|
-
// /compile appends source to the target MODEL's content for context; a
|
|
167
|
-
// notebook isn't a valid target and must be rejected up front (not left to
|
|
168
|
-
// a confusing downstream parse error).
|
|
169
|
-
const env = await Environment.create("testEnv", envPath, []);
|
|
170
|
-
const pkgDir = path.join(envPath, "pkg");
|
|
171
|
-
await writePackageDir(pkgDir, {}, GOOD_MODEL);
|
|
172
|
-
await fs.writeFile(
|
|
173
|
-
path.join(pkgDir, "report.malloynb"),
|
|
174
|
-
`>>>markdown\n# Report\n`,
|
|
175
|
-
);
|
|
176
|
-
await env.addPackage("pkg");
|
|
177
|
-
|
|
178
|
-
await expect(
|
|
179
|
-
env.compileSource("pkg", "report.malloynb", "run: ones"),
|
|
180
|
-
).rejects.toBeInstanceOf(BadRequestError);
|
|
181
|
-
|
|
182
|
-
// The .malloy model still compiles ad-hoc source (no regression).
|
|
183
|
-
const { problems } = await env.compileSource(
|
|
184
|
-
"pkg",
|
|
185
|
-
"model.malloy",
|
|
186
|
-
"run: ones -> { select: x }",
|
|
187
|
-
);
|
|
188
|
-
expect(problems.some((p) => p.severity === "error")).toBe(false);
|
|
189
|
-
});
|
|
190
|
-
});
|