@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
package/src/errors.spec.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
AccessDeniedError,
|
|
4
|
-
BadRequestError,
|
|
5
|
-
ConnectionAuthError,
|
|
6
|
-
ConnectionError,
|
|
7
|
-
internalErrorToHttpError,
|
|
8
|
-
ModelCompilationError,
|
|
9
|
-
NotQueryableError,
|
|
10
|
-
PayloadTooLargeError,
|
|
11
|
-
QueryTimeoutError,
|
|
12
|
-
ServiceUnavailableError,
|
|
13
|
-
} from "./errors";
|
|
14
|
-
|
|
15
|
-
describe("internalErrorToHttpError", () => {
|
|
16
|
-
it("maps ConnectionAuthError to 422", () => {
|
|
17
|
-
const { status, json } = internalErrorToHttpError(
|
|
18
|
-
new ConnectionAuthError("creds rejected for db_x"),
|
|
19
|
-
);
|
|
20
|
-
expect(status).toBe(422);
|
|
21
|
-
expect(json).toEqual({
|
|
22
|
-
code: 422,
|
|
23
|
-
message: "creds rejected for db_x",
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("maps BadRequestError to 400", () => {
|
|
28
|
-
const { status, json } = internalErrorToHttpError(
|
|
29
|
-
new BadRequestError("bad input"),
|
|
30
|
-
);
|
|
31
|
-
expect(status).toBe(400);
|
|
32
|
-
expect(json).toEqual({ code: 400, message: "bad input" });
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("maps AccessDeniedError to 403 (authorize gate)", () => {
|
|
36
|
-
const { status, json } = internalErrorToHttpError(
|
|
37
|
-
new AccessDeniedError('Access denied for source "gated".'),
|
|
38
|
-
);
|
|
39
|
-
expect(status).toBe(403);
|
|
40
|
-
expect(json).toEqual({
|
|
41
|
-
code: 403,
|
|
42
|
-
message: 'Access denied for source "gated".',
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("maps NotQueryableError to 404 (explore boundary)", () => {
|
|
47
|
-
const { status, json } = internalErrorToHttpError(
|
|
48
|
-
new NotQueryableError('No queryable source "hidden".'),
|
|
49
|
-
);
|
|
50
|
-
expect(status).toBe(404);
|
|
51
|
-
expect(json).toEqual({
|
|
52
|
-
code: 404,
|
|
53
|
-
message: 'No queryable source "hidden".',
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("maps ModelCompilationError to 424", () => {
|
|
58
|
-
const { status, json } = internalErrorToHttpError(
|
|
59
|
-
new ModelCompilationError({ message: "compile failed" }),
|
|
60
|
-
);
|
|
61
|
-
expect(status).toBe(424);
|
|
62
|
-
expect(json).toEqual({ code: 424, message: "compile failed" });
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("maps ConnectionError to 502 (distinct from auth, still retryable)", () => {
|
|
66
|
-
const { status, json } = internalErrorToHttpError(
|
|
67
|
-
new ConnectionError("upstream broken"),
|
|
68
|
-
);
|
|
69
|
-
expect(status).toBe(502);
|
|
70
|
-
expect(json).toEqual({ code: 502, message: "upstream broken" });
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("falls through to 500 for unrecognized errors", () => {
|
|
74
|
-
const { status, json } = internalErrorToHttpError(new Error("boom"));
|
|
75
|
-
expect(status).toBe(500);
|
|
76
|
-
expect(json.message).toBe("boom");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("maps PayloadTooLargeError to 413", () => {
|
|
80
|
-
const { status, json } = internalErrorToHttpError(
|
|
81
|
-
new PayloadTooLargeError(
|
|
82
|
-
"Query returned more than 100000 rows; refine the query or raise PUBLISHER_MAX_QUERY_ROWS.",
|
|
83
|
-
),
|
|
84
|
-
);
|
|
85
|
-
expect(status).toBe(413);
|
|
86
|
-
expect(json).toEqual({
|
|
87
|
-
code: 413,
|
|
88
|
-
message:
|
|
89
|
-
"Query returned more than 100000 rows; refine the query or raise PUBLISHER_MAX_QUERY_ROWS.",
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("maps ServiceUnavailableError to 503 (load shedding / back-pressure)", () => {
|
|
94
|
-
const { status, json } = internalErrorToHttpError(
|
|
95
|
-
new ServiceUnavailableError(
|
|
96
|
-
"Pod at max concurrent queries (32); retry later.",
|
|
97
|
-
),
|
|
98
|
-
);
|
|
99
|
-
expect(status).toBe(503);
|
|
100
|
-
expect(json).toEqual({
|
|
101
|
-
code: 503,
|
|
102
|
-
message: "Pod at max concurrent queries (32); retry later.",
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("maps QueryTimeoutError to 504 (gateway timeout, distinct from 503 back-pressure)", () => {
|
|
107
|
-
const { status, json } = internalErrorToHttpError(
|
|
108
|
-
new QueryTimeoutError(
|
|
109
|
-
"Query exceeded PUBLISHER_QUERY_TIMEOUT_MS (300000ms) and was aborted.",
|
|
110
|
-
),
|
|
111
|
-
);
|
|
112
|
-
expect(status).toBe(504);
|
|
113
|
-
expect(json).toEqual({
|
|
114
|
-
code: 504,
|
|
115
|
-
message:
|
|
116
|
-
"Query exceeded PUBLISHER_QUERY_TIMEOUT_MS (300000ms) and was aborted.",
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
});
|
package/src/errors.ts
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import { MalloyError } from "@malloydata/malloy";
|
|
2
|
-
import { PUBLISHER_CONFIG_NAME } from "./constants";
|
|
3
|
-
|
|
4
|
-
export function internalErrorToHttpError(error: Error) {
|
|
5
|
-
if (error instanceof BadRequestError) {
|
|
6
|
-
return httpError(400, error.message);
|
|
7
|
-
} else if (error instanceof FrozenConfigError) {
|
|
8
|
-
return httpError(403, error.message);
|
|
9
|
-
} else if (error instanceof AccessDeniedError) {
|
|
10
|
-
return httpError(403, error.message);
|
|
11
|
-
} else if (error instanceof EnvironmentNotFoundError) {
|
|
12
|
-
return httpError(404, error.message);
|
|
13
|
-
} else if (error instanceof PackageNotFoundError) {
|
|
14
|
-
return httpError(404, error.message);
|
|
15
|
-
} else if (error instanceof ModelNotFoundError) {
|
|
16
|
-
return httpError(404, error.message);
|
|
17
|
-
} else if (error instanceof NotQueryableError) {
|
|
18
|
-
return httpError(404, error.message);
|
|
19
|
-
} else if (error instanceof MalloyError) {
|
|
20
|
-
return httpError(400, error.message);
|
|
21
|
-
} else if (error instanceof ConnectionNotFoundError) {
|
|
22
|
-
return httpError(404, error.message);
|
|
23
|
-
} else if (error instanceof ConnectionAuthError) {
|
|
24
|
-
return httpError(422, error.message);
|
|
25
|
-
} else if (error instanceof UnsupportedCatalogFormatError) {
|
|
26
|
-
return httpError(422, error.message);
|
|
27
|
-
} else if (error instanceof MaterializationEligibilityError) {
|
|
28
|
-
return httpError(422, error.message);
|
|
29
|
-
} else if (error instanceof ModelCompilationError) {
|
|
30
|
-
return httpError(424, error.message);
|
|
31
|
-
} else if (error instanceof ConnectionError) {
|
|
32
|
-
return httpError(502, error.message);
|
|
33
|
-
} else if (error instanceof MaterializationNotFoundError) {
|
|
34
|
-
return httpError(404, error.message);
|
|
35
|
-
} else if (error instanceof MaterializationConflictError) {
|
|
36
|
-
return httpError(409, error.message);
|
|
37
|
-
} else if (error instanceof InvalidStateTransitionError) {
|
|
38
|
-
return httpError(409, error.message);
|
|
39
|
-
} else if (error instanceof ServiceUnavailableError) {
|
|
40
|
-
return httpError(503, error.message);
|
|
41
|
-
} else if (error instanceof PayloadTooLargeError) {
|
|
42
|
-
return httpError(413, error.message);
|
|
43
|
-
} else if (error instanceof QueryTimeoutError) {
|
|
44
|
-
return httpError(504, error.message);
|
|
45
|
-
} else {
|
|
46
|
-
return httpError(500, error.message);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function httpError(code: number, message: string) {
|
|
51
|
-
return {
|
|
52
|
-
status: code,
|
|
53
|
-
json: {
|
|
54
|
-
code,
|
|
55
|
-
message: message,
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export class NotImplementedError extends Error {
|
|
61
|
-
constructor(message: string) {
|
|
62
|
-
super(message);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export class BadRequestError extends Error {
|
|
67
|
-
constructor(message: string) {
|
|
68
|
-
super(message);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export class EnvironmentNotFoundError extends Error {
|
|
73
|
-
constructor(message: string) {
|
|
74
|
-
super(message);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export class PackageNotFoundError extends Error {
|
|
79
|
-
constructor(message: string) {
|
|
80
|
-
super(message);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export class ModelNotFoundError extends Error {
|
|
85
|
-
constructor(message: string) {
|
|
86
|
-
super(message);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export class ConnectionNotFoundError extends Error {
|
|
91
|
-
constructor(message: string) {
|
|
92
|
-
super(message);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export class ConnectionError extends Error {
|
|
97
|
-
constructor(message: string) {
|
|
98
|
-
super(message);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export class ConnectionAuthError extends Error {
|
|
103
|
-
constructor(message: string) {
|
|
104
|
-
super(message);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// A catalog was reached and authenticated fine, but its on-disk format is
|
|
109
|
-
// outside the range the pinned engine's extension can attach (see
|
|
110
|
-
// ducklake_version.ts). Distinct from ConnectionAuthError so the 422 doesn't
|
|
111
|
-
// read as a credentials problem. Maps to HTTP 422.
|
|
112
|
-
export class UnsupportedCatalogFormatError extends Error {
|
|
113
|
-
constructor(message: string) {
|
|
114
|
-
super(message);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export class ModelCompilationError extends Error {
|
|
119
|
-
// Accepts a MalloyError or any message-bearing object, so callers that add
|
|
120
|
-
// context around a compile failure (e.g. naming the source whose authorize
|
|
121
|
-
// annotation failed) can reuse this 424 mapping without a separate class.
|
|
122
|
-
constructor(error: { message: string }) {
|
|
123
|
-
super(error.message);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* A persist source was asked to materialize into a `storage=` destination (the
|
|
129
|
-
* DuckDB/DuckLake tier) but is ineligible: it has an unbound free parameter, it
|
|
130
|
-
* references a given (an RLAC/tenant-isolation refusal), or its served shape
|
|
131
|
-
* does not compile in DuckDB. Mapped to HTTP **422** (the request is
|
|
132
|
-
* well-formed, but the source cannot be processed into a materialized artifact)
|
|
133
|
-
* — a hard refuse, never a silent fallback. Kept a distinct class so the
|
|
134
|
-
* givens/RLAC refusal is greppable for security review. Accepts a
|
|
135
|
-
* message-bearing object to match {@link ModelCompilationError}'s ergonomics.
|
|
136
|
-
*/
|
|
137
|
-
export class MaterializationEligibilityError extends Error {
|
|
138
|
-
constructor(error: { message: string }) {
|
|
139
|
-
super(error.message);
|
|
140
|
-
this.name = "MaterializationEligibilityError";
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export class FrozenConfigError extends Error {
|
|
145
|
-
constructor(
|
|
146
|
-
message = `Publisher config can't be updated when ${PUBLISHER_CONFIG_NAME} has { "frozenConfig": true }`,
|
|
147
|
-
) {
|
|
148
|
-
super(message);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* A request was denied by a source's `#(authorize)` gate (HTTP 403). Thrown by
|
|
154
|
-
* the runtime authorize check when no in-scope expression evaluates true for
|
|
155
|
-
* the supplied givens (including when a referenced given has no value).
|
|
156
|
-
*/
|
|
157
|
-
export class AccessDeniedError extends Error {
|
|
158
|
-
constructor(message: string) {
|
|
159
|
-
super(message);
|
|
160
|
-
this.name = "AccessDeniedError";
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* A query targeted a source/model that is not part of the package's queryable
|
|
166
|
-
* surface under `queryableSources: "declared"` (a non-`explores` model file, or
|
|
167
|
-
* a source not in a model's `export {}` closure). Mapped to HTTP **404**, not
|
|
168
|
-
* 403, and with a deliberately generic message: unlike `#(authorize)` (which is
|
|
169
|
-
* identity-scoped and answers "who"), the explore boundary is identity-free and
|
|
170
|
-
* answers "what is queryable" — so a hidden target should be indistinguishable
|
|
171
|
-
* from a non-existent one (no enumeration / existence oracle).
|
|
172
|
-
*/
|
|
173
|
-
export class NotQueryableError extends Error {
|
|
174
|
-
constructor(message: string) {
|
|
175
|
-
super(message);
|
|
176
|
-
this.name = "NotQueryableError";
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export class MaterializationNotFoundError extends Error {
|
|
181
|
-
constructor(message: string) {
|
|
182
|
-
super(message);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export class MaterializationConflictError extends Error {
|
|
187
|
-
constructor(message: string) {
|
|
188
|
-
super(message);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export class InvalidStateTransitionError extends Error {
|
|
193
|
-
constructor(message: string) {
|
|
194
|
-
super(message);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Thrown when the publisher is temporarily refusing a request to keep
|
|
200
|
-
* RSS under the configured `PUBLISHER_MAX_MEMORY_BYTES` cap. Mapped to
|
|
201
|
-
* HTTP 503 so an upstream proxy / client can retry with back-off.
|
|
202
|
-
*/
|
|
203
|
-
export class ServiceUnavailableError extends Error {
|
|
204
|
-
constructor(message: string) {
|
|
205
|
-
super(message);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Thrown when a response would exceed a server-side size cap (e.g. an
|
|
211
|
-
* ad-hoc connection SQL query that returned more than
|
|
212
|
-
* `PUBLISHER_MAX_QUERY_ROWS` rows). Mapped to HTTP 413 so callers know
|
|
213
|
-
* the request was well-formed but the result is too large for the
|
|
214
|
-
* publisher to materialize; the remediation is "refine the query" or
|
|
215
|
-
* "raise the cap", not "retry".
|
|
216
|
-
*/
|
|
217
|
-
export class PayloadTooLargeError extends Error {
|
|
218
|
-
constructor(message: string) {
|
|
219
|
-
super(message);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Thrown when a query exceeded the configured wall-clock budget
|
|
225
|
-
* (`PUBLISHER_QUERY_TIMEOUT_MS`) and the publisher aborted it
|
|
226
|
-
* mid-execution. Mapped to HTTP 504 (`Gateway Timeout`) because the
|
|
227
|
-
* publisher acts as a gateway to the underlying database — the
|
|
228
|
-
* upstream caller did nothing wrong, but the downstream query took
|
|
229
|
-
* too long. Distinct from {@link ServiceUnavailableError} so clients
|
|
230
|
-
* can distinguish "back off, the pod is loaded" (503, retryable)
|
|
231
|
-
* from "this specific query is too expensive" (504, refine it).
|
|
232
|
-
*/
|
|
233
|
-
export class QueryTimeoutError extends Error {
|
|
234
|
-
constructor(message: string) {
|
|
235
|
-
super(message);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import { setFilterDeprecationHeaders } from "./filter_deprecation";
|
|
3
|
-
|
|
4
|
-
const makeRes = () => {
|
|
5
|
-
const headers: Record<string, string> = {};
|
|
6
|
-
const res = {
|
|
7
|
-
headers,
|
|
8
|
-
setHeader(name: string, value: string) {
|
|
9
|
-
headers[name] = value;
|
|
10
|
-
return res;
|
|
11
|
-
},
|
|
12
|
-
};
|
|
13
|
-
return res;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const DEPRECATION_LINK =
|
|
17
|
-
'<https://github.com/malloydata/publisher/blob/main/docs/givens.md>; rel="deprecation"; type="text/markdown"';
|
|
18
|
-
|
|
19
|
-
describe("setFilterDeprecationHeaders", () => {
|
|
20
|
-
it("does not set headers when neither filterParams nor bypassFilters is supplied", () => {
|
|
21
|
-
const res = makeRes();
|
|
22
|
-
setFilterDeprecationHeaders(res, {});
|
|
23
|
-
expect(res.headers.Deprecation).toBeUndefined();
|
|
24
|
-
expect(res.headers.Link).toBeUndefined();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("does not set headers for an explicit empty filterParams object (no-op opt-out)", () => {
|
|
28
|
-
const res = makeRes();
|
|
29
|
-
setFilterDeprecationHeaders(res, { filterParams: {} });
|
|
30
|
-
expect(res.headers.Deprecation).toBeUndefined();
|
|
31
|
-
expect(res.headers.Link).toBeUndefined();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("sets RFC 8594 headers when filterParams carries values", () => {
|
|
35
|
-
const res = makeRes();
|
|
36
|
-
setFilterDeprecationHeaders(res, {
|
|
37
|
-
filterParams: { region: ["US"] },
|
|
38
|
-
});
|
|
39
|
-
expect(res.headers.Deprecation).toBe("true");
|
|
40
|
-
expect(res.headers.Link).toBe(DEPRECATION_LINK);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("sets RFC 8594 headers when bypassFilters is true", () => {
|
|
44
|
-
const res = makeRes();
|
|
45
|
-
setFilterDeprecationHeaders(res, { bypassFilters: true });
|
|
46
|
-
expect(res.headers.Deprecation).toBe("true");
|
|
47
|
-
expect(res.headers.Link).toBe(DEPRECATION_LINK);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("does not set headers when bypassFilters is undefined and filterParams is undefined", () => {
|
|
51
|
-
const res = makeRes();
|
|
52
|
-
setFilterDeprecationHeaders(res, {
|
|
53
|
-
filterParams: undefined,
|
|
54
|
-
bypassFilters: undefined,
|
|
55
|
-
});
|
|
56
|
-
expect(res.headers.Deprecation).toBeUndefined();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("does not set headers when filterParams is null", () => {
|
|
60
|
-
const res = makeRes();
|
|
61
|
-
setFilterDeprecationHeaders(res, { filterParams: null });
|
|
62
|
-
expect(res.headers.Deprecation).toBeUndefined();
|
|
63
|
-
});
|
|
64
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal structural type the helper needs from an HTTP response. Kept
|
|
3
|
-
* narrower than `express.Response` so tests can pass a tiny stub instead
|
|
4
|
-
* of constructing a full Express response.
|
|
5
|
-
*/
|
|
6
|
-
export interface HeaderSetter {
|
|
7
|
-
setHeader(name: string, value: string): unknown;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Attach RFC 8594 deprecation headers when the request carries any of the
|
|
12
|
-
* legacy `#(filter)` API surface (`filterParams` / `bypassFilters` on POST,
|
|
13
|
-
* `filter_params` / `bypass_filters` on the notebook-cell GET). The
|
|
14
|
-
* complementary operator-facing notice for legacy *models* (independent of
|
|
15
|
-
* whether the caller used the deprecated request fields) ships as a
|
|
16
|
-
* one-time warn log in `Model`'s constructor — see service/model.ts.
|
|
17
|
-
*
|
|
18
|
-
* `filterParams` here is the *parsed* value (after JSON.parse for the GET
|
|
19
|
-
* notebook-cell path), so an empty `{}` is treated as a no-op opt-out — the
|
|
20
|
-
* header only fires when the caller actually supplied filter values.
|
|
21
|
-
*/
|
|
22
|
-
export const setFilterDeprecationHeaders = (
|
|
23
|
-
res: HeaderSetter,
|
|
24
|
-
options: { filterParams?: unknown; bypassFilters?: unknown },
|
|
25
|
-
): void => {
|
|
26
|
-
const hasFilterParams =
|
|
27
|
-
options.filterParams !== undefined &&
|
|
28
|
-
options.filterParams !== null &&
|
|
29
|
-
!(
|
|
30
|
-
typeof options.filterParams === "object" &&
|
|
31
|
-
!Array.isArray(options.filterParams) &&
|
|
32
|
-
Object.keys(options.filterParams as Record<string, unknown>).length ===
|
|
33
|
-
0
|
|
34
|
-
);
|
|
35
|
-
if (hasFilterParams || options.bypassFilters !== undefined) {
|
|
36
|
-
res.setHeader("Deprecation", "true");
|
|
37
|
-
res.setHeader(
|
|
38
|
-
"Link",
|
|
39
|
-
'<https://github.com/malloydata/publisher/blob/main/docs/givens.md>; rel="deprecation"; type="text/markdown"',
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
};
|
package/src/health.spec.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
afterAll,
|
|
3
|
-
afterEach,
|
|
4
|
-
beforeEach,
|
|
5
|
-
describe,
|
|
6
|
-
expect,
|
|
7
|
-
it,
|
|
8
|
-
spyOn,
|
|
9
|
-
} from "bun:test";
|
|
10
|
-
import { Server } from "http";
|
|
11
|
-
import { performGracefulShutdownAfterDrain } from "./health";
|
|
12
|
-
import { logger } from "./logger";
|
|
13
|
-
import { __setPackageLoadPoolForTests } from "./package_load/package_load_pool";
|
|
14
|
-
|
|
15
|
-
// Regression test for the graceful-shutdown ordering bug that caused
|
|
16
|
-
// [winston] Attempt to write logs with no transports: {"message":"Waiting 50 seconds..."}
|
|
17
|
-
// to appear in production logs. logger.close() must run after every
|
|
18
|
-
// logger.* call, including the "Waiting ... seconds after server close
|
|
19
|
-
// before exit..." message.
|
|
20
|
-
//
|
|
21
|
-
// Tests call performGracefulShutdownAfterDrain directly rather than
|
|
22
|
-
// emitting SIGTERM, so module-level operationalState is not mutated
|
|
23
|
-
// and the spec stays isolated from sibling tests in the same process.
|
|
24
|
-
describe("performGracefulShutdownAfterDrain: shutdown ordering", () => {
|
|
25
|
-
const originalExit = process.exit;
|
|
26
|
-
let callOrder: string[];
|
|
27
|
-
|
|
28
|
-
afterAll(async () => {
|
|
29
|
-
// performGracefulShutdownAfterDrain drains the package-load worker pool
|
|
30
|
-
// via getPackageLoadPool().shutdown() — which lazily CREATES the global
|
|
31
|
-
// singleton and leaves it installed in its shut-down state. In prod the
|
|
32
|
-
// process exits right after, so that's fine; in this shared test process
|
|
33
|
-
// it poisons every later spec that touches the worker path
|
|
34
|
-
// (Package.create → "PackageLoadPool is shutting down"), with failures
|
|
35
|
-
// that come and go with bun's platform-dependent file order. Reset the
|
|
36
|
-
// singleton so the next user lazily creates a fresh pool.
|
|
37
|
-
await __setPackageLoadPoolForTests(null);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
callOrder = [];
|
|
42
|
-
|
|
43
|
-
spyOn(logger, "info").mockImplementation(((msg: string) => {
|
|
44
|
-
callOrder.push(`info:${msg}`);
|
|
45
|
-
return logger;
|
|
46
|
-
}) as never);
|
|
47
|
-
spyOn(logger, "close").mockImplementation((() => {
|
|
48
|
-
callOrder.push("close");
|
|
49
|
-
return logger;
|
|
50
|
-
}) as never);
|
|
51
|
-
// Silence warn/error calls so spec output stays clean. They are
|
|
52
|
-
// not load-bearing for these assertions.
|
|
53
|
-
spyOn(logger, "warn").mockImplementation((() => logger) as never);
|
|
54
|
-
spyOn(logger, "error").mockImplementation((() => logger) as never);
|
|
55
|
-
|
|
56
|
-
process.exit = ((_code?: number) => {
|
|
57
|
-
callOrder.push("exit");
|
|
58
|
-
}) as never;
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
afterEach(() => {
|
|
62
|
-
process.exit = originalExit;
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// performGracefulShutdownAfterDrain lazily creates and shuts down the
|
|
66
|
-
// module-level PackageLoadPool singleton. If we leave that shut-down
|
|
67
|
-
// pool installed, sibling specs that run later in the same Bun process
|
|
68
|
-
// (e.g. package.spec.ts, package_race.spec.ts) inherit a dead pool and
|
|
69
|
-
// fail with "PackageLoadPool is shutting down". Reset the singleton so
|
|
70
|
-
// the next getPackageLoadPool() spins up a fresh one. The leaked-pool
|
|
71
|
-
// ordering is filesystem-readdir dependent, which is why it only
|
|
72
|
-
// surfaced on Linux CI.
|
|
73
|
-
afterAll(async () => {
|
|
74
|
-
await __setPackageLoadPoolForTests(null);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const fakeServer = (): Server => ({ listening: false }) as unknown as Server;
|
|
78
|
-
|
|
79
|
-
it("logs the 'Waiting ...' message before closing the logger", async () => {
|
|
80
|
-
await performGracefulShutdownAfterDrain(fakeServer(), fakeServer(), 0.05);
|
|
81
|
-
|
|
82
|
-
const waitingIdx = callOrder.findIndex((entry) =>
|
|
83
|
-
entry.startsWith("info:Waiting"),
|
|
84
|
-
);
|
|
85
|
-
const closeIdx = callOrder.indexOf("close");
|
|
86
|
-
const exitIdx = callOrder.indexOf("exit");
|
|
87
|
-
|
|
88
|
-
expect(waitingIdx).toBeGreaterThanOrEqual(0);
|
|
89
|
-
expect(closeIdx).toBeGreaterThanOrEqual(0);
|
|
90
|
-
expect(exitIdx).toBeGreaterThanOrEqual(0);
|
|
91
|
-
expect(waitingIdx).toBeLessThan(closeIdx);
|
|
92
|
-
expect(closeIdx).toBeLessThan(exitIdx);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("emits no logger.info calls after logger.close", async () => {
|
|
96
|
-
await performGracefulShutdownAfterDrain(fakeServer(), fakeServer(), 0.05);
|
|
97
|
-
|
|
98
|
-
const closeIdx = callOrder.indexOf("close");
|
|
99
|
-
const lateInfoIdx = callOrder.findIndex(
|
|
100
|
-
(entry, idx) => idx > closeIdx && entry.startsWith("info:"),
|
|
101
|
-
);
|
|
102
|
-
expect(closeIdx).toBeGreaterThanOrEqual(0);
|
|
103
|
-
expect(lateInfoIdx).toBe(-1);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("closes the logger exactly once", async () => {
|
|
107
|
-
await performGracefulShutdownAfterDrain(fakeServer(), fakeServer(), 0.05);
|
|
108
|
-
|
|
109
|
-
const closes = callOrder.filter((entry) => entry === "close").length;
|
|
110
|
-
expect(closes).toBe(1);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("skips the 'Waiting ...' message when gracefulCloseTimeoutSeconds is 0", async () => {
|
|
114
|
-
await performGracefulShutdownAfterDrain(fakeServer(), fakeServer(), 0);
|
|
115
|
-
|
|
116
|
-
const waitingCalls = callOrder.filter((entry) =>
|
|
117
|
-
entry.startsWith("info:Waiting"),
|
|
118
|
-
);
|
|
119
|
-
expect(waitingCalls.length).toBe(0);
|
|
120
|
-
expect(callOrder.indexOf("close")).toBeGreaterThanOrEqual(0);
|
|
121
|
-
expect(callOrder.indexOf("exit")).toBeGreaterThanOrEqual(0);
|
|
122
|
-
});
|
|
123
|
-
});
|