@malloy-publisher/server 0.0.235 → 0.0.237
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 +83 -27
- package/dist/app/assets/{EnvironmentPage-BsAnavYN.js → EnvironmentPage-DaP6CpgK.js} +1 -1
- package/dist/app/assets/{HomePage-CADE138j.js → HomePage-C9yNw3iu.js} +1 -1
- package/dist/app/assets/{LightMode-Cfh7KzN8.js → LightMode-Be4-G-dX.js} +1 -1
- package/dist/app/assets/{MainPage-CO3pRlnV.js → MainPage-J_rU2SgI.js} +2 -2
- package/dist/app/assets/{MaterializationsPage-p9YjkRXZ.js → MaterializationsPage-DHgPI4MO.js} +1 -1
- package/dist/app/assets/ModelPage-BzXfjTu5.js +1 -0
- package/dist/app/assets/{PackagePage-e4kN75YR.js → PackagePage-ByoSTLJ4.js} +1 -1
- package/dist/app/assets/RouteError-CrSeKN2r.js +1 -0
- package/dist/app/assets/{ThemeEditorPage-CciagFTq.js → ThemeEditorPage-Btxnmhtk.js} +1 -1
- package/dist/app/assets/{WorkbookPage-DNWmkCXa.js → WorkbookPage-D6JLPnWO.js} +1 -1
- package/dist/app/assets/{core-Rj_4rRnA.es-BZyvITuO.js → core-BSRgt1mG.es-Bc_D2Lq4.js} +1 -1
- package/dist/app/assets/{index-CH2AcDzc.js → index-BRijWj6K.js} +15 -15
- package/dist/app/assets/{index-DQa463gC.js → index-BX4D94xw.js} +1 -1
- package/dist/app/assets/{index-VBbcc8s6.js → index-Cvy0xtb7.js} +1 -1
- package/dist/app/assets/{index-DQpV7MyA.js → index-DMAhIJNC.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/package_load_worker.mjs +59 -20
- package/dist/server.mjs +1342 -616
- package/package.json +8 -2
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -97
- package/dist/app/assets/ModelPage-C1OSTv-x.js +0 -1
- package/dist/app/assets/RouteError-CzbfOkng.js +0 -1
- 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,118 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import type { ModelDef } from "@malloydata/malloy";
|
|
3
|
-
import {
|
|
4
|
-
annotationTexts,
|
|
5
|
-
isReservedRoute,
|
|
6
|
-
modelAnnotations,
|
|
7
|
-
} from "./annotations";
|
|
8
|
-
|
|
9
|
-
// Minimal `ModelDef` carrying only what `modelAnnotations` reads: `modelID`
|
|
10
|
-
// and the `modelAnnotations` registry (modelID → { ownNotes, inheritsFrom }).
|
|
11
|
-
const makeModelDef = (
|
|
12
|
-
modelID: string,
|
|
13
|
-
registry: Record<
|
|
14
|
-
string,
|
|
15
|
-
{
|
|
16
|
-
ownNotes: { notes?: string[]; blockNotes?: string[] };
|
|
17
|
-
inheritsFrom: string[];
|
|
18
|
-
}
|
|
19
|
-
>,
|
|
20
|
-
): ModelDef =>
|
|
21
|
-
({
|
|
22
|
-
modelID,
|
|
23
|
-
modelAnnotations: Object.fromEntries(
|
|
24
|
-
Object.entries(registry).map(([id, { ownNotes, inheritsFrom }]) => [
|
|
25
|
-
id,
|
|
26
|
-
{
|
|
27
|
-
ownNotes: {
|
|
28
|
-
notes: ownNotes.notes?.map((text) => ({ text, at: {} })),
|
|
29
|
-
blockNotes: ownNotes.blockNotes?.map((text) => ({
|
|
30
|
-
text,
|
|
31
|
-
at: {},
|
|
32
|
-
})),
|
|
33
|
-
},
|
|
34
|
-
inheritsFrom,
|
|
35
|
-
},
|
|
36
|
-
]),
|
|
37
|
-
),
|
|
38
|
-
}) as unknown as ModelDef;
|
|
39
|
-
|
|
40
|
-
const noteTexts = (def: ModelDef): string[] =>
|
|
41
|
-
(modelAnnotations(def).notes ?? []).map((note) => note.text);
|
|
42
|
-
|
|
43
|
-
describe("isReservedRoute", () => {
|
|
44
|
-
it("treats the empty route (MOTLY / render config) as reserved", () => {
|
|
45
|
-
expect(isReservedRoute("")).toBe(true);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("treats Malloy's punctuation sigils as reserved", () => {
|
|
49
|
-
// Form 2 reserves the entire punct-only namespace, not just the
|
|
50
|
-
// currently-claimed sigils (`!` `@` `"` `:`).
|
|
51
|
-
for (const route of ["!", "@", '"', ":", "%", "-", "::"]) {
|
|
52
|
-
expect(isReservedRoute(route)).toBe(true);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("treats bracketed app routes as not reserved", () => {
|
|
57
|
-
for (const route of [
|
|
58
|
-
"doc",
|
|
59
|
-
"label",
|
|
60
|
-
"filter",
|
|
61
|
-
"bar-chart",
|
|
62
|
-
"https://example.com/ns",
|
|
63
|
-
"123",
|
|
64
|
-
"══SECURITY══",
|
|
65
|
-
]) {
|
|
66
|
-
expect(isReservedRoute(route)).toBe(false);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe("modelAnnotations", () => {
|
|
72
|
-
it("returns the local model's own `##` notes", () => {
|
|
73
|
-
const def = makeModelDef("local", {
|
|
74
|
-
local: { ownNotes: { notes: ["## local"] }, inheritsFrom: [] },
|
|
75
|
-
});
|
|
76
|
-
expect(noteTexts(def)).toEqual(["## local"]);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("falls back to an imported model's notes when the local model has no own `##` (authorize must not fail open)", () => {
|
|
80
|
-
// Regression: a `##(authorize)` declared in an imported file must still
|
|
81
|
-
// flow into the importing file's file-level gate even when the importing
|
|
82
|
-
// file declares no `##` of its own. An earlier fold kept an empty local
|
|
83
|
-
// link at the top, so `.notes` returned [] and the gate failed open.
|
|
84
|
-
const def = makeModelDef("local", {
|
|
85
|
-
local: { ownNotes: {}, inheritsFrom: ["base"] },
|
|
86
|
-
base: {
|
|
87
|
-
ownNotes: { notes: ['## (authorize) request.user == "admin"'] },
|
|
88
|
-
inheritsFrom: [],
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
expect(noteTexts(def)).toEqual([
|
|
92
|
-
'## (authorize) request.user == "admin"',
|
|
93
|
-
]);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("surfaces the local model's notes at the top when both local and import have `##`", () => {
|
|
97
|
-
const def = makeModelDef("local", {
|
|
98
|
-
local: { ownNotes: { notes: ["## local"] }, inheritsFrom: ["base"] },
|
|
99
|
-
base: { ownNotes: { notes: ["## base"] }, inheritsFrom: [] },
|
|
100
|
-
});
|
|
101
|
-
// `.notes` is the top (local); `.texts()` flattens the whole lineage
|
|
102
|
-
// ancestral-first.
|
|
103
|
-
expect(noteTexts(def)).toEqual(["## local"]);
|
|
104
|
-
expect(annotationTexts(modelAnnotations(def))).toEqual([
|
|
105
|
-
"## base",
|
|
106
|
-
"## local",
|
|
107
|
-
]);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("is cycle-safe and returns an empty bundle for a model with no annotations", () => {
|
|
111
|
-
const def = makeModelDef("local", {
|
|
112
|
-
local: { ownNotes: {}, inheritsFrom: ["base"] },
|
|
113
|
-
base: { ownNotes: {}, inheritsFrom: ["local"] }, // back-edge cycle
|
|
114
|
-
});
|
|
115
|
-
expect(modelAnnotations(def)).toEqual({});
|
|
116
|
-
expect(noteTexts(def)).toEqual([]);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { Annotations, type ModelDef } from "@malloydata/malloy";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The raw IR annotation bundle. Workaround: `@malloydata/malloy` exports the
|
|
5
|
-
* `Annotations` view but not the underlying `AnnotationsDef` type, so we
|
|
6
|
-
* recover it from the view's constructor parameter. Replace with a direct
|
|
7
|
-
* `import type { AnnotationsDef }` once malloy exports it.
|
|
8
|
-
*/
|
|
9
|
-
export type AnnotationsDef = NonNullable<
|
|
10
|
-
ConstructorParameters<typeof Annotations>[0]
|
|
11
|
-
>;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* True if a route belongs to Malloy's own reserved namespace rather than an
|
|
15
|
-
* application. Malloy claims the empty route (`''` — MOTLY tags / render
|
|
16
|
-
* config) and the whole punctuation-sigil namespace (`!` `@` `"` `:`, and
|
|
17
|
-
* any punct-only route — Form 2 reserves all of them). Everything else is a
|
|
18
|
-
* bracketed app route (`#(doc)`, `#<label>`, …).
|
|
19
|
-
*
|
|
20
|
-
* TODO: this route classification belongs in `@malloydata/malloy` core,
|
|
21
|
-
* beside `parsePrefix` — otherwise every consumer reinvents it. Remove when
|
|
22
|
-
* core exports an equivalent.
|
|
23
|
-
*/
|
|
24
|
-
export function isReservedRoute(route: string): boolean {
|
|
25
|
-
return route === "" || !/[\p{L}\p{N}]/u.test(route);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* The model (`##`) annotation bundle for one model, folded across its
|
|
30
|
-
* import/extend lineage.
|
|
31
|
-
*
|
|
32
|
-
* Workaround: malloy 0.0.405 moved model annotations off `ModelDef.annotation`
|
|
33
|
-
* and onto `ModelDef.modelAnnotations` (a `modelID → {ownNotes, inheritsFrom}`
|
|
34
|
-
* registry), folded by the `getModelAnnotations` helper — which malloy does
|
|
35
|
-
* NOT export from its public barrel. We replicate it (matching
|
|
36
|
-
* `@malloydata/malloy/dist/model/annotation_utils.js`): a post-order DFS over
|
|
37
|
-
* `inheritsFrom` (cycle-safe, each model emitted once at its most-ancestral
|
|
38
|
-
* slot) yields ancestral-first / local-last order, folded into an
|
|
39
|
-
* `AnnotationsDef` whose `inherits` chain runs most-ancestral-deepest / local
|
|
40
|
-
* at the top.
|
|
41
|
-
*
|
|
42
|
-
* A model that contributes no `##` of its own adds NO link to the chain (we
|
|
43
|
-
* skip empty `ownNotes`), so `.notes` returns the nearest ancestor that
|
|
44
|
-
* actually has notes — not an empty local node. This matters because `.notes`
|
|
45
|
-
* feeds file-level `##(authorize)` enforcement: an imported model's
|
|
46
|
-
* `##(authorize)` must still flow into an importing file that declares no `##`
|
|
47
|
-
* of its own. We also copy only `notes`/`blockNotes` rather than spreading
|
|
48
|
-
* `ownNotes`, whose own `inherits` would otherwise leak in. Replace with a
|
|
49
|
-
* direct `import { getModelAnnotations }` once malloy exports it.
|
|
50
|
-
*/
|
|
51
|
-
export function modelAnnotations(modelDef: ModelDef): AnnotationsDef {
|
|
52
|
-
const registry = modelDef.modelAnnotations ?? {};
|
|
53
|
-
const visited = new Set<string>();
|
|
54
|
-
const order: string[] = [];
|
|
55
|
-
const visit = (id: string): void => {
|
|
56
|
-
if (visited.has(id)) return;
|
|
57
|
-
visited.add(id);
|
|
58
|
-
const entry = registry[id];
|
|
59
|
-
if (!entry) return;
|
|
60
|
-
for (const dep of entry.inheritsFrom) visit(dep);
|
|
61
|
-
order.push(id); // post-order: ancestors precede the model itself
|
|
62
|
-
};
|
|
63
|
-
visit(modelDef.modelID);
|
|
64
|
-
|
|
65
|
-
// Fold most-ancestral → local so the local model lands at the top of the
|
|
66
|
-
// resulting `inherits` chain. Models with no own notes add no link.
|
|
67
|
-
let folded: AnnotationsDef | undefined;
|
|
68
|
-
for (const id of order) {
|
|
69
|
-
const own = registry[id].ownNotes;
|
|
70
|
-
if (!own.notes?.length && !own.blockNotes?.length) continue;
|
|
71
|
-
folded = {
|
|
72
|
-
notes: own.notes,
|
|
73
|
-
blockNotes: own.blockNotes,
|
|
74
|
-
inherits: folded,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
return folded ?? {};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Every annotation text on an entity — its own `notes` and `blockNotes`
|
|
82
|
-
* plus everything inherited from its ancestors. All of an entity's
|
|
83
|
-
* annotations apply; none are dropped by source location. Returns
|
|
84
|
-
* `undefined`, not `[]`, when empty, to match the optional API shape.
|
|
85
|
-
*/
|
|
86
|
-
export function annotationTexts(
|
|
87
|
-
annote: AnnotationsDef | undefined,
|
|
88
|
-
): string[] | undefined {
|
|
89
|
-
const texts = new Annotations(annote).texts();
|
|
90
|
-
return texts.length > 0 ? texts : undefined;
|
|
91
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
collectAuthorizeExprs,
|
|
4
|
-
isProbeTrue,
|
|
5
|
-
parseAuthorizeAnnotation,
|
|
6
|
-
referencedGivenNames,
|
|
7
|
-
} from "./authorize";
|
|
8
|
-
|
|
9
|
-
describe("referencedGivenNames", () => {
|
|
10
|
-
it("returns the $NAME tokens deduped in first-seen order", () => {
|
|
11
|
-
expect(
|
|
12
|
-
referencedGivenNames("$ROLE = 'admin' and $ROLE != $PRIOR"),
|
|
13
|
-
).toEqual(["ROLE", "PRIOR"]);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("ignores a $NAME inside a string literal (not a real reference)", () => {
|
|
17
|
-
// Otherwise a joined gate's referenced-given count is inflated and the
|
|
18
|
-
// full-coverage check wrongly denies a correctly-authorized request.
|
|
19
|
-
expect(referencedGivenNames("$ROLE = 'the $BOSS role'")).toEqual([
|
|
20
|
-
"ROLE",
|
|
21
|
-
]);
|
|
22
|
-
expect(referencedGivenNames("'$A $B $C'")).toEqual([]);
|
|
23
|
-
expect(referencedGivenNames("$X = 'it\\'s $Y' or $Z = 1")).toEqual([
|
|
24
|
-
"X",
|
|
25
|
-
"Z",
|
|
26
|
-
]);
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe("isProbeTrue", () => {
|
|
31
|
-
it("grants only on a genuine true / 1 / 'true'", () => {
|
|
32
|
-
expect(isProbeTrue(true)).toBe(true);
|
|
33
|
-
expect(isProbeTrue(1)).toBe(true);
|
|
34
|
-
expect(isProbeTrue("true")).toBe(true);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("denies on anything else (fail closed)", () => {
|
|
38
|
-
for (const v of [false, 0, "false", "", null, undefined, {}, "TRUE", 2]) {
|
|
39
|
-
expect(isProbeTrue(v)).toBe(false);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe("parseAuthorizeAnnotation", () => {
|
|
45
|
-
it("parses a source-level #(authorize) expression", () => {
|
|
46
|
-
expect(parseAuthorizeAnnotation(`#(authorize) "$ROLE = 'analyst'"`)).toBe(
|
|
47
|
-
"$ROLE = 'analyst'",
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("parses a file-level ##(authorize) expression", () => {
|
|
52
|
-
expect(parseAuthorizeAnnotation(`##(authorize) "$ROLE = 'admin'"`)).toBe(
|
|
53
|
-
"$ROLE = 'admin'",
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("tolerates the trailing newline Malloy keeps on note text", () => {
|
|
58
|
-
expect(
|
|
59
|
-
parseAuthorizeAnnotation(`#(authorize) "$REGION = 'us-west'"\n`),
|
|
60
|
-
).toBe("$REGION = 'us-west'");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("preserves inner single quotes (Malloy string literals)", () => {
|
|
64
|
-
expect(
|
|
65
|
-
parseAuthorizeAnnotation(`#(authorize) "$TENANT in ['a', 'b']"`),
|
|
66
|
-
).toBe("$TENANT in ['a', 'b']");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("unescapes escaped inner double quotes", () => {
|
|
70
|
-
expect(parseAuthorizeAnnotation(`#(authorize) "$NAME = \\"foo\\""`)).toBe(
|
|
71
|
-
`$NAME = "foo"`,
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("handles a constant false gate", () => {
|
|
76
|
-
expect(parseAuthorizeAnnotation(`#(authorize) "false"`)).toBe("false");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("returns null for non-authorize annotations", () => {
|
|
80
|
-
expect(
|
|
81
|
-
parseAuthorizeAnnotation(`#(filter) dimension=x type=equal`),
|
|
82
|
-
).toBeNull();
|
|
83
|
-
expect(parseAuthorizeAnnotation(`##! experimental.givens`)).toBeNull();
|
|
84
|
-
expect(parseAuthorizeAnnotation(`## just a doc comment`)).toBeNull();
|
|
85
|
-
expect(parseAuthorizeAnnotation(`# plain`)).toBeNull();
|
|
86
|
-
expect(parseAuthorizeAnnotation(``)).toBeNull();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("throws when the body is not quoted", () => {
|
|
90
|
-
expect(() =>
|
|
91
|
-
parseAuthorizeAnnotation(`#(authorize) $ROLE = 'analyst'`),
|
|
92
|
-
).toThrow(/double-quoted/);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("throws on mismatched / unterminated quotes", () => {
|
|
96
|
-
expect(() =>
|
|
97
|
-
parseAuthorizeAnnotation(`#(authorize) "$ROLE = 'analyst'`),
|
|
98
|
-
).toThrow(/mismatched quotes/);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("throws on an empty expression body", () => {
|
|
102
|
-
expect(() => parseAuthorizeAnnotation(`#(authorize) ""`)).toThrow(
|
|
103
|
-
/empty expression/,
|
|
104
|
-
);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("throws on content after the closing quote", () => {
|
|
108
|
-
expect(() =>
|
|
109
|
-
parseAuthorizeAnnotation(`#(authorize) "$ROLE = 'a'" extra`),
|
|
110
|
-
).toThrow(/unexpected content/);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("throws when the prefix has no body", () => {
|
|
114
|
-
expect(() => parseAuthorizeAnnotation(`#(authorize)`)).toThrow(
|
|
115
|
-
/double-quoted/,
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe("collectAuthorizeExprs", () => {
|
|
121
|
-
it("collects authorize expressions in declaration order", () => {
|
|
122
|
-
expect(
|
|
123
|
-
collectAuthorizeExprs([
|
|
124
|
-
`##(authorize) "$ROLE = 'admin'"`,
|
|
125
|
-
`#(filter) dimension=x type=equal`,
|
|
126
|
-
`#(authorize) "$REGION = 'us-west'"`,
|
|
127
|
-
]),
|
|
128
|
-
).toEqual(["$ROLE = 'admin'", "$REGION = 'us-west'"]);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("returns [] when there are no authorize annotations", () => {
|
|
132
|
-
expect(
|
|
133
|
-
collectAuthorizeExprs([`#(filter) dimension=x type=equal`, `## doc`]),
|
|
134
|
-
).toEqual([]);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it("keeps duplicate gates (no dedup — OR semantics)", () => {
|
|
138
|
-
expect(
|
|
139
|
-
collectAuthorizeExprs([
|
|
140
|
-
`#(authorize) "$ROLE = 'admin'"`,
|
|
141
|
-
`#(authorize) "$ROLE = 'admin'"`,
|
|
142
|
-
]),
|
|
143
|
-
).toEqual(["$ROLE = 'admin'", "$ROLE = 'admin'"]);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("propagates the throw from a malformed authorize annotation", () => {
|
|
147
|
-
expect(() =>
|
|
148
|
-
collectAuthorizeExprs([
|
|
149
|
-
`#(authorize) "$ROLE = 'admin'"`,
|
|
150
|
-
`#(authorize) "unterminated`,
|
|
151
|
-
]),
|
|
152
|
-
).toThrow(/mismatched quotes/);
|
|
153
|
-
});
|
|
154
|
-
});
|