@malloy-publisher/server 0.0.227 → 0.0.229

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.docker.md +8 -8
  2. package/dist/app/api-doc.yaml +84 -1
  3. package/dist/app/assets/EnvironmentPage-QOoHiVeJ.js +1 -0
  4. package/dist/app/assets/{HomePage-CXguJsXS.js → HomePage-C71GOfVW.js} +1 -1
  5. package/dist/app/assets/{LightMode-ZsshUznu.js → LightMode-CrgCAwLe.js} +1 -1
  6. package/dist/app/assets/MainPage-BG5__FN3.js +2 -0
  7. package/dist/app/assets/MaterializationsPage-DE6PnrDR.js +1 -0
  8. package/dist/app/assets/ModelPage-CcBjcbLm.js +1 -0
  9. package/dist/app/assets/PackagePage-JTy3ztkB.js +1 -0
  10. package/dist/app/assets/{RouteError-Chn7lL96.js → RouteError-Cymbp47a.js} +1 -1
  11. package/dist/app/assets/ThemeEditorPage-C_nMnHr8.js +1 -0
  12. package/dist/app/assets/WorkbookPage-CPQu-DQx.js +1 -0
  13. package/dist/app/assets/{core-vVgoO8IR.es-BD_THWs_.js → core-Coi3caGs.es-CSOmajHS.js} +10 -10
  14. package/dist/app/assets/{index-gEWxu09x.js → index-CM2qhQCI.js} +1 -1
  15. package/dist/app/assets/{index-D6YtyiJ0.js → index-CcuuST2X.js} +1 -1
  16. package/dist/app/assets/{index-OEjKNSYb.js → index-CkmABCAw.js} +388 -241
  17. package/dist/app/assets/{index-BioohWQj.js → index-DlWCXghy.js} +1 -1
  18. package/dist/app/assets/index-DxArlgRD.js +529 -0
  19. package/dist/app/index.html +1 -1
  20. package/dist/default-publisher.config.json +7 -7
  21. package/dist/instrumentation.mjs +1 -1
  22. package/dist/package_load_worker.mjs +1 -1
  23. package/dist/runtime/publisher.js +5 -0
  24. package/dist/server.mjs +7675 -899
  25. package/package.json +4 -2
  26. package/publisher.config.example.bigquery.json +7 -7
  27. package/publisher.config.example.duckdb.json +7 -7
  28. package/publisher.config.json +7 -11
  29. package/src/config.spec.ts +89 -3
  30. package/src/config.ts +102 -1
  31. package/src/controller/materialization.controller.spec.ts +9 -0
  32. package/src/controller/materialization.controller.ts +15 -0
  33. package/src/controller/package.controller.ts +62 -31
  34. package/src/default-publisher.config.json +7 -7
  35. package/src/materialization_metrics.ts +33 -0
  36. package/src/mcp/handler_utils.spec.ts +108 -0
  37. package/src/mcp/handler_utils.ts +98 -4
  38. package/src/mcp/server.protocol.spec.ts +58 -0
  39. package/src/mcp/server.ts +29 -1
  40. package/src/mcp/skills/build_skills_bundle.ts +29 -9
  41. package/src/mcp/skills/skills_bundle.json +1 -1
  42. package/src/mcp/skills/skills_bundle.spec.ts +31 -2
  43. package/src/mcp/tools/compile_tool.spec.ts +207 -0
  44. package/src/mcp/tools/compile_tool.ts +177 -0
  45. package/src/mcp/tools/execute_query_tool.spec.ts +143 -0
  46. package/src/mcp/tools/execute_query_tool.ts +37 -4
  47. package/src/mcp/tools/get_context_tool.ts +1 -1
  48. package/src/mcp/tools/reload_package_tool.spec.ts +232 -0
  49. package/src/mcp/tools/reload_package_tool.ts +158 -0
  50. package/src/query_param_utils.ts +11 -0
  51. package/src/runtime/publisher.js +5 -0
  52. package/src/server.ts +74 -14
  53. package/src/service/connection.spec.ts +8 -7
  54. package/src/service/cron_evaluator.spec.ts +79 -0
  55. package/src/service/cron_evaluator.ts +105 -0
  56. package/src/service/environment.ts +188 -19
  57. package/src/service/environment_store.spec.ts +372 -2
  58. package/src/service/environment_store.ts +149 -17
  59. package/src/service/environment_store_anchoring.spec.ts +107 -0
  60. package/src/service/manifest_loader.spec.ts +3 -1
  61. package/src/service/manifest_loader.ts +9 -5
  62. package/src/service/materialization_scheduler.spec.ts +260 -0
  63. package/src/service/materialization_scheduler.ts +304 -0
  64. package/src/service/materialization_service.spec.ts +167 -4
  65. package/src/service/materialization_service.ts +121 -8
  66. package/src/service/materialization_test_fixtures.ts +10 -1
  67. package/src/service/model.spec.ts +92 -0
  68. package/src/service/model.ts +58 -7
  69. package/src/service/package.ts +121 -14
  70. package/src/service/package_quote_bound_tables.spec.ts +171 -0
  71. package/src/service/package_reload_safety.spec.ts +193 -0
  72. package/src/service/persistence_policy.spec.ts +31 -0
  73. package/src/service/quoting.spec.ts +57 -1
  74. package/src/service/quoting.ts +33 -0
  75. package/src/storage/DatabaseInterface.ts +15 -0
  76. package/src/storage/duckdb/DuckDBRepository.ts +17 -0
  77. package/src/storage/duckdb/MaterializationRepository.spec.ts +154 -0
  78. package/src/storage/duckdb/MaterializationRepository.ts +56 -0
  79. package/tests/fixtures/persist-schedule-test/data/orders.csv +5 -0
  80. package/tests/fixtures/persist-schedule-test/persist_schedule_test.malloy +11 -0
  81. package/tests/fixtures/persist-schedule-test/publisher.json +7 -0
  82. package/tests/fixtures/persist-schedule-warn-test/data/orders.csv +5 -0
  83. package/tests/fixtures/persist-schedule-warn-test/persist_warn_test.malloy +11 -0
  84. package/tests/fixtures/persist-schedule-warn-test/publisher.json +6 -0
  85. package/tests/fixtures/query-givens/data/orders.csv +7 -0
  86. package/tests/fixtures/query-givens/model.malloy +34 -0
  87. package/tests/fixtures/query-givens/publisher.json +5 -0
  88. package/tests/integration/materialization/environment_list.integration.spec.ts +133 -0
  89. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +7 -2
  90. package/tests/integration/materialization/orchestrated_rebind.integration.spec.ts +150 -0
  91. package/tests/integration/materialization/schedule_edit.integration.spec.ts +126 -0
  92. package/tests/integration/materialization/scheduler.integration.spec.ts +200 -0
  93. package/tests/integration/materialization/scheduler_recovery.integration.spec.ts +172 -0
  94. package/tests/integration/materialization/scheduler_wiring.integration.spec.ts +143 -0
  95. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +22 -22
  96. package/tests/integration/query_givens/query_givens.integration.spec.ts +146 -0
  97. package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +121 -0
  98. package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +110 -0
  99. package/dist/app/assets/EnvironmentPage-DvOJ7L_b.js +0 -1
  100. package/dist/app/assets/MainPage-BIe0VwBa.js +0 -2
  101. package/dist/app/assets/MaterializationsPage-BuZ6UJVx.js +0 -1
  102. package/dist/app/assets/ModelPage-DsPf-s8B.js +0 -1
  103. package/dist/app/assets/PackagePage-CEVNAKZa.js +0 -1
  104. package/dist/app/assets/ThemeEditorPage-DWC_FdNU.js +0 -1
  105. package/dist/app/assets/WorkbookPage-CGrsFz8p.js +0 -1
  106. package/dist/app/assets/index-DNUZpnaa.js +0 -527
@@ -23,8 +23,8 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
23
23
  let env: McpE2ETestEnvironment | null = null;
24
24
  let mcpClient: Client;
25
25
 
26
- const ENVIRONMENT_NAME = "malloy-samples";
27
- const PACKAGE_NAME = "faa";
26
+ const ENVIRONMENT_NAME = "examples";
27
+ const PACKAGE_NAME = "storefront";
28
28
 
29
29
  beforeAll(async () => {
30
30
  // Setup the E2E environment (starts server, connects client)
@@ -49,8 +49,8 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
49
49
  arguments: {
50
50
  environmentName: ENVIRONMENT_NAME,
51
51
  packageName: PACKAGE_NAME,
52
- modelPath: "flights.malloy",
53
- query: "run: flights->{ aggregate: c is count() }",
52
+ modelPath: "storefront.malloy",
53
+ query: "run: order_items->{ aggregate: c is count() }",
54
54
  },
55
55
  });
56
56
 
@@ -97,9 +97,9 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
97
97
  const params = {
98
98
  environmentName: ENVIRONMENT_NAME,
99
99
  packageName: PACKAGE_NAME,
100
- modelPath: "flights.malloy",
101
- sourceName: "flights", // Added sourceName
102
- queryName: "top_carriers",
100
+ modelPath: "storefront.malloy",
101
+ sourceName: "order_items", // Added sourceName
102
+ queryName: "top_products",
103
103
  };
104
104
 
105
105
  // Expect RESOLUTION with success
@@ -142,8 +142,8 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
142
142
  const params = {
143
143
  environmentName: ENVIRONMENT_NAME,
144
144
  packageName: PACKAGE_NAME,
145
- modelPath: "flights.malloy",
146
- query: "run: flights->{BAD SYNTAX aggregate: flight_count is count()}",
145
+ modelPath: "storefront.malloy",
146
+ query: "run: order_items->{BAD SYNTAX aggregate: order_count is count()}",
147
147
  };
148
148
 
149
149
  // Application Error (Malloy Compilation): Expect RESOLUTION with isError: true
@@ -181,9 +181,9 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
181
181
  const params = {
182
182
  environmentName: ENVIRONMENT_NAME,
183
183
  packageName: PACKAGE_NAME,
184
- modelPath: "flights.malloy",
185
- query: "run: flights->{aggregate: c is count()}",
186
- queryName: "top_carriers",
184
+ modelPath: "storefront.malloy",
185
+ query: "run: order_items->{aggregate: c is count()}",
186
+ queryName: "top_products",
187
187
  };
188
188
 
189
189
  // Expect RESOLUTION because the error is thrown *inside* the handler
@@ -209,7 +209,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
209
209
  const params = {
210
210
  environmentName: ENVIRONMENT_NAME,
211
211
  packageName: PACKAGE_NAME,
212
- modelPath: "flights.malloy",
212
+ modelPath: "storefront.malloy",
213
213
  // Missing query AND queryName
214
214
  };
215
215
 
@@ -237,7 +237,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
237
237
  // Missing modelPath
238
238
  environmentName: ENVIRONMENT_NAME,
239
239
  packageName: PACKAGE_NAME,
240
- query: "run: flights->{aggregate: flight_count is count()}",
240
+ query: "run: order_items->{aggregate: c is count()}",
241
241
  };
242
242
 
243
243
  // Protocol Error (Caught by Zod/MCP): Expect REJECTION
@@ -259,8 +259,8 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
259
259
  const params = {
260
260
  environmentName: ENVIRONMENT_NAME,
261
261
  packageName: "nonexistent_package", // Use a package that doesn't exist
262
- modelPath: "flights.malloy",
263
- query: "run: flights->{aggregate: c is count()}",
262
+ modelPath: "storefront.malloy",
263
+ query: "run: order_items->{aggregate: c is count()}",
264
264
  };
265
265
 
266
266
  // Application Error (Service Layer): Expect RESOLUTION with isError: true
@@ -304,7 +304,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
304
304
  environmentName: ENVIRONMENT_NAME,
305
305
  packageName: PACKAGE_NAME,
306
306
  modelPath: "nonexistent_model.malloy", // Use a model that doesn't exist
307
- query: "run: flights->{aggregate: c is count()}",
307
+ query: "run: order_items->{aggregate: c is count()}",
308
308
  };
309
309
 
310
310
  // Application Error (Service Layer): Expect RESOLUTION with isError: true
@@ -364,8 +364,8 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
364
364
  arguments: {
365
365
  environmentName: ENVIRONMENT_NAME,
366
366
  packageName: PACKAGE_NAME,
367
- modelPath: "flights.malloy",
368
- query: "run: flights->{aggregate: c is count()}",
367
+ modelPath: "storefront.malloy",
368
+ query: "run: order_items->{aggregate: c is count()}",
369
369
  },
370
370
  }),
371
371
  ).rejects.toThrow();
@@ -377,8 +377,8 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
377
377
  const params = {
378
378
  environmentName: ENVIRONMENT_NAME,
379
379
  packageName: PACKAGE_NAME,
380
- modelPath: "flights.malloy",
381
- queryName: "top_carriers", // Nested view, but sourceName is missing
380
+ modelPath: "storefront.malloy",
381
+ queryName: "top_products", // Nested view, but sourceName is missing
382
382
  };
383
383
 
384
384
  // Expect RESOLUTION with error because it's invalid usage processed by the handler
@@ -405,7 +405,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
405
405
  const errorPayload = JSON.parse(errorJsonText);
406
406
  // Expect error about the query/view itself not found
407
407
  expect(errorPayload.error).toMatch(
408
- /Query 'top_carriers' not found|Reference to undefined object 'top_carriers'/i,
408
+ /Query 'top_products' not found|Reference to undefined object 'top_products'/i,
409
409
  );
410
410
  expect(Array.isArray(errorPayload.suggestions)).toBe(true);
411
411
  });
@@ -0,0 +1,146 @@
1
+ /// <reference types="bun-types" />
2
+
3
+ /**
4
+ * HTTP E2E coverage for `given:` runtime parameters on POST .../query:
5
+ * givens retarget query results, unknown/mistyped givens surface as 400 (not
6
+ * 500), givens compose with filterParams, a `null` given value is an
7
+ * explicit SQL-NULL override, and a numeric-string value is accepted for a
8
+ * `number`-typed given (Malloy's own coercion). Malloy is the single validator:
9
+ * an unknown name or bad value throws a `runtime-given-*` error that
10
+ * model.ts maps to a 400 (see getQueryResults).
11
+ */
12
+
13
+ import { afterAll, beforeAll, describe, expect, it } from "bun:test";
14
+ import path from "path";
15
+ import { fileURLToPath } from "url";
16
+ import { type RestE2EEnv, startRestE2E } from "../../harness/rest_e2e";
17
+
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const ENV_NAME = "query-givens-env";
20
+ const PKG = "query-givens";
21
+ const MODEL = "model.malloy";
22
+
23
+ type Row = Record<string, unknown>;
24
+
25
+ describe("givens forwarding on /query (HTTP E2E)", () => {
26
+ let env: (RestE2EEnv & { stop(): Promise<void> }) | null = null;
27
+ let baseUrl: string;
28
+
29
+ beforeAll(async () => {
30
+ env = await startRestE2E();
31
+ baseUrl = env.baseUrl;
32
+ const fixtureDir = path.resolve(__dirname, "../../fixtures/query-givens");
33
+ const createRes = await fetch(`${baseUrl}/api/v0/environments`, {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify({
37
+ name: ENV_NAME,
38
+ packages: [{ name: PKG, location: fixtureDir }],
39
+ connections: [],
40
+ }),
41
+ });
42
+ if (!createRes.ok) {
43
+ throw new Error(
44
+ `Failed to create test environment (${createRes.status}): ${await createRes.text()}`,
45
+ );
46
+ }
47
+ const deadline = Date.now() + 30_000;
48
+ while (Date.now() < deadline) {
49
+ const res = await fetch(
50
+ `${baseUrl}/api/v0/environments/${ENV_NAME}/packages/${PKG}`,
51
+ );
52
+ if (res.ok) break;
53
+ await new Promise((r) => setTimeout(r, 250));
54
+ }
55
+ });
56
+
57
+ afterAll(async () => {
58
+ await fetch(`${baseUrl}/api/v0/environments/${ENV_NAME}`, {
59
+ method: "DELETE",
60
+ }).catch(() => {});
61
+ await env?.stop();
62
+ });
63
+
64
+ const query = (body: Record<string, unknown>) =>
65
+ fetch(
66
+ `${baseUrl}/api/v0/environments/${ENV_NAME}/packages/${PKG}/models/${MODEL}/query`,
67
+ {
68
+ method: "POST",
69
+ headers: { "Content-Type": "application/json" },
70
+ body: JSON.stringify({
71
+ sourceName: "orders",
72
+ queryName: "by_given_region",
73
+ compactJson: true,
74
+ ...body,
75
+ }),
76
+ },
77
+ );
78
+
79
+ async function rows(res: Response): Promise<Row[]> {
80
+ expect(res.status).toBe(200);
81
+ const body = (await res.json()) as { result: string };
82
+ return JSON.parse(body.result) as Row[];
83
+ }
84
+
85
+ it("retargets rows via a given override (EU vs default US)", async () => {
86
+ const res = await query({ givens: { target_region: "EU" } });
87
+ const r = await rows(res);
88
+ expect(r.length).toBe(1);
89
+ expect(Number(r[0].order_count)).toBe(3);
90
+ });
91
+
92
+ it("returns 400 for an unknown given name, naming it in the message", async () => {
93
+ // Malloy rejects the unknown name (`runtime-given-unknown`, with a
94
+ // did-you-mean hint) at prepare time; model.ts maps that to a 400 and
95
+ // forwards Malloy's message, which names the given.
96
+ const res = await query({ givens: { NOtaGiven: 1 } });
97
+ expect(res.status).toBe(400);
98
+ const body = (await res.json()) as { message: string };
99
+ expect(body.message).toContain("NOtaGiven");
100
+ });
101
+
102
+ it("returns 400 for a value-type mismatch on a number-typed given", async () => {
103
+ // "min_amount" is declared `:: number`; a non-numeric string can't be
104
+ // coerced, so Malloy rejects it at prepare time (`runtime-given-*`),
105
+ // surfaced as a 400 via the run try/catch in model.ts, not a 500.
106
+ const res = await query({ givens: { min_amount: "not-a-number" } });
107
+ expect(res.status).toBe(400);
108
+ });
109
+
110
+ it("composes givens with filterParams", async () => {
111
+ // target_region=EU narrows to ids 4,5,6; filterParams status=active
112
+ // narrows further to ids 4,6 -> order_count=2.
113
+ const res = await query({
114
+ givens: { target_region: "EU" },
115
+ filterParams: { status: "active" },
116
+ });
117
+ const r = await rows(res);
118
+ expect(r.length).toBe(1);
119
+ expect(Number(r[0].order_count)).toBe(2);
120
+ });
121
+
122
+ it("omitting givens entirely uses the declared defaults (US baseline)", async () => {
123
+ const res = await query({});
124
+ const r = await rows(res);
125
+ expect(r.length).toBe(1);
126
+ expect(Number(r[0].order_count)).toBe(3);
127
+ });
128
+
129
+ it("a null given value is an explicit SQL-NULL override", async () => {
130
+ // `region = NULL` is never true in SQL, so this should return zero
131
+ // matching rows rather than falling back to the 'US' default.
132
+ const res = await query({ givens: { target_region: null } });
133
+ const r = await rows(res);
134
+ expect(r.length).toBe(1);
135
+ expect(Number(r[0].order_count)).toBe(0);
136
+ });
137
+
138
+ it("accepts a numeric string for a number-typed given", async () => {
139
+ // Guards against Malloy's numeric-string coercion for `number` givens
140
+ // regressing; "5" should be accepted just like the literal 5.
141
+ const res = await query({ givens: { min_amount: "5" } });
142
+ const r = await rows(res);
143
+ expect(r.length).toBe(1);
144
+ expect(Number(r[0].order_count)).toBe(3);
145
+ });
146
+ });
@@ -0,0 +1,121 @@
1
+ /// <reference types="bun-types" />
2
+
3
+ /**
4
+ * HTTP E2E for the givens × `#(authorize)` interaction. Malloy is the single
5
+ * given validator, so on a GATED source a bad given (unknown NAME or wrong-typed
6
+ * VALUE) is caught inside the authorize probe, which fails closed — surfacing as
7
+ * a 403, not the 400 the ungated path returns. This suite pins that documented
8
+ * asymmetry so it stays intentional:
9
+ *
10
+ * - unknown given name -> 403 (probe can't bind it -> fails closed)
11
+ * - authorized + valid givens -> 200 (retargets rows)
12
+ * - authorize denies -> 403
13
+ * - valid name, BAD value -> 403 (probe can't evaluate it -> fails closed)
14
+ *
15
+ * On an UNGATED source the same unknown name / bad value are a clean 400 (Malloy
16
+ * `runtime-given-*` mapped by model.ts) — see query_givens.integration.spec.ts.
17
+ *
18
+ * See packages/server/src/service/authorize.ts (evaluateAuthorize, fail-closed).
19
+ */
20
+
21
+ import { afterAll, beforeAll, describe, expect, it } from "bun:test";
22
+ import path from "path";
23
+ import { fileURLToPath } from "url";
24
+ import { type RestE2EEnv, startRestE2E } from "../../harness/rest_e2e";
25
+
26
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
+ const ENV_NAME = "query-givens-authz-env";
28
+ const PKG = "query-givens";
29
+ const MODEL = "model.malloy";
30
+
31
+ type Row = Record<string, unknown>;
32
+
33
+ describe("givens × authorize on /query (HTTP E2E)", () => {
34
+ let env: (RestE2EEnv & { stop(): Promise<void> }) | null = null;
35
+ let baseUrl: string;
36
+
37
+ beforeAll(async () => {
38
+ env = await startRestE2E();
39
+ baseUrl = env.baseUrl;
40
+ const fixtureDir = path.resolve(__dirname, "../../fixtures/query-givens");
41
+ const createRes = await fetch(`${baseUrl}/api/v0/environments`, {
42
+ method: "POST",
43
+ headers: { "Content-Type": "application/json" },
44
+ body: JSON.stringify({
45
+ name: ENV_NAME,
46
+ packages: [{ name: PKG, location: fixtureDir }],
47
+ connections: [],
48
+ }),
49
+ });
50
+ if (!createRes.ok) {
51
+ throw new Error(
52
+ `Failed to create test environment (${createRes.status}): ${await createRes.text()}`,
53
+ );
54
+ }
55
+ const deadline = Date.now() + 30_000;
56
+ while (Date.now() < deadline) {
57
+ const res = await fetch(
58
+ `${baseUrl}/api/v0/environments/${ENV_NAME}/packages/${PKG}`,
59
+ );
60
+ if (res.ok) break;
61
+ await new Promise((r) => setTimeout(r, 250));
62
+ }
63
+ });
64
+
65
+ afterAll(async () => {
66
+ await fetch(`${baseUrl}/api/v0/environments/${ENV_NAME}`, {
67
+ method: "DELETE",
68
+ }).catch(() => {});
69
+ await env?.stop();
70
+ });
71
+
72
+ const queryGated = (body: Record<string, unknown>) =>
73
+ fetch(
74
+ `${baseUrl}/api/v0/environments/${ENV_NAME}/packages/${PKG}/models/${MODEL}/query`,
75
+ {
76
+ method: "POST",
77
+ headers: { "Content-Type": "application/json" },
78
+ body: JSON.stringify({
79
+ sourceName: "gated_orders",
80
+ queryName: "by_given_region",
81
+ compactJson: true,
82
+ ...body,
83
+ }),
84
+ },
85
+ );
86
+
87
+ it("unknown given name -> 403 on a gated source (authorize probe fails closed)", async () => {
88
+ // Even with role=admin, the unknown given makes the authorize probe throw
89
+ // (`runtime-given-unknown`), which the gate treats as not-granting. On an
90
+ // ungated source the same name is a clean 400 (see query_givens suite).
91
+ const res = await queryGated({
92
+ givens: { role: "admin", NOtaGiven: 1 },
93
+ });
94
+ expect(res.status).toBe(403);
95
+ });
96
+
97
+ it("authorized caller with valid givens -> 200 and retargets rows", async () => {
98
+ const res = await queryGated({
99
+ givens: { role: "admin", target_region: "EU" },
100
+ });
101
+ expect(res.status).toBe(200);
102
+ const body = (await res.json()) as { result: string };
103
+ const r = JSON.parse(body.result) as Row[];
104
+ expect(Number(r[0].order_count)).toBe(3);
105
+ });
106
+
107
+ it("authorize deny (non-admin role) -> 403", async () => {
108
+ const res = await queryGated({ givens: { role: "guest" } });
109
+ expect(res.status).toBe(403);
110
+ });
111
+
112
+ it("valid given name with a bad value -> 403 on a gated source (fail-closed authorize)", async () => {
113
+ // The authorize probe binds the supplied givens; a value it can't evaluate
114
+ // makes the probe throw and the gate denies. On the UNGATED path the same
115
+ // bad value is a 400 (Malloy at prepare time) — this asymmetry is by design.
116
+ const res = await queryGated({
117
+ givens: { role: "admin", min_amount: "not-a-number" },
118
+ });
119
+ expect(res.status).toBe(403);
120
+ });
121
+ });
@@ -0,0 +1,110 @@
1
+ /// <reference types="bun-types" />
2
+
3
+ /**
4
+ * Proves the browser SDK (`packages/server/src/runtime/publisher.js`, served
5
+ * at GET /sdk/publisher.js) actually puts `givens` on the wire: fetch the
6
+ * served source, evaluate it in a sandboxed vm context standing in for
7
+ * `window` (self === top, so the in-iframe/live-reload branches are
8
+ * inert no-ops), and call `window.Publisher.query(...)` with a `givens`
9
+ * override against the real HTTP server. Approach used: sandbox-eval via
10
+ * Node's `vm` module — evaluating the actual runtime rather than just
11
+ * grepping its source proves the SDK's fetch body really carries `givens`
12
+ * end-to-end.
13
+ */
14
+
15
+ import { afterAll, beforeAll, describe, expect, it } from "bun:test";
16
+ import path from "path";
17
+ import { fileURLToPath } from "url";
18
+ import vm from "vm";
19
+ import { type RestE2EEnv, startRestE2E } from "../../harness/rest_e2e";
20
+
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+ const ENV_NAME = "sdk-givens-env";
23
+ const PKG = "query-givens";
24
+ const MODEL = "model.malloy";
25
+
26
+ type Row = Record<string, unknown>;
27
+
28
+ describe("Publisher SDK forwards givens (sandbox-eval HTTP E2E)", () => {
29
+ let env: (RestE2EEnv & { stop(): Promise<void> }) | null = null;
30
+ let baseUrl: string;
31
+
32
+ beforeAll(async () => {
33
+ env = await startRestE2E();
34
+ baseUrl = env.baseUrl;
35
+ const fixtureDir = path.resolve(__dirname, "../../fixtures/query-givens");
36
+ const createRes = await fetch(`${baseUrl}/api/v0/environments`, {
37
+ method: "POST",
38
+ headers: { "Content-Type": "application/json" },
39
+ body: JSON.stringify({
40
+ name: ENV_NAME,
41
+ packages: [{ name: PKG, location: fixtureDir }],
42
+ connections: [],
43
+ }),
44
+ });
45
+ if (!createRes.ok) {
46
+ throw new Error(
47
+ `Failed to create test environment (${createRes.status}): ${await createRes.text()}`,
48
+ );
49
+ }
50
+ const deadline = Date.now() + 30_000;
51
+ while (Date.now() < deadline) {
52
+ const res = await fetch(
53
+ `${baseUrl}/api/v0/environments/${ENV_NAME}/packages/${PKG}`,
54
+ );
55
+ if (res.ok) break;
56
+ await new Promise((r) => setTimeout(r, 250));
57
+ }
58
+ });
59
+
60
+ afterAll(async () => {
61
+ await fetch(`${baseUrl}/api/v0/environments/${ENV_NAME}`, {
62
+ method: "DELETE",
63
+ }).catch(() => {});
64
+ await env?.stop();
65
+ });
66
+
67
+ it("Publisher.query() with a givens override reaches the server and retargets rows", async () => {
68
+ const sdkRes = await fetch(`${baseUrl}/sdk/publisher.js`);
69
+ expect(sdkRes.status).toBe(200);
70
+ const sdkSource = await sdkRes.text();
71
+
72
+ // Sandbox stands in for `window`. self === top so the runtime's
73
+ // in-iframe (postMessage resize) and live-reload (EventSource) branches
74
+ // are no-ops; document/EventSource are omitted since that code path
75
+ // never runs for a non-embedded page.
76
+ const sandbox: Record<string, unknown> = {
77
+ fetch,
78
+ location: {
79
+ pathname: `/environments/${ENV_NAME}/packages/${PKG}/`,
80
+ origin: baseUrl,
81
+ },
82
+ console,
83
+ };
84
+ sandbox.self = sandbox;
85
+ sandbox.top = sandbox;
86
+ sandbox.window = sandbox;
87
+
88
+ const context = vm.createContext(sandbox);
89
+ vm.runInContext(sdkSource, context, { filename: "publisher.js" });
90
+
91
+ const publisher = (sandbox.window as { Publisher: unknown })
92
+ .Publisher as {
93
+ query: (
94
+ modelPath: string,
95
+ malloyQuery: string | undefined,
96
+ opts: Record<string, unknown>,
97
+ ) => Promise<Row[]>;
98
+ };
99
+ expect(publisher).toBeDefined();
100
+
101
+ const rows = await publisher.query(MODEL, undefined, {
102
+ sourceName: "orders",
103
+ queryName: "by_given_region",
104
+ givens: { target_region: "EU" },
105
+ });
106
+
107
+ expect(rows.length).toBe(1);
108
+ expect(Number(rows[0].order_count)).toBe(3);
109
+ });
110
+ });
@@ -1 +0,0 @@
1
- import{W as r,G as t,j as e,$ as i,a5 as o}from"./index-DNUZpnaa.js";function m(){const s=r(),{environmentName:n}=t();if(n){const a=i({environmentName:n});return e.jsx(o,{onSelectPackage:s,resourceUri:a})}else return e.jsx("div",{children:e.jsx("h2",{children:"Missing environment name"})})}export{m as default};
@@ -1,2 +0,0 @@
1
- import{u as Se,g as Ee,r as u,R as Pe,a as Ce,c as S,j as t,s as M,B as Ie,m as J,e as oe,b as ie,d as le,f as ce,h as Me,i as O,k as de,T as U,l as Z,n as ze,o as Te,p as De,q as pe,t as Re,v as se,w as Be,x as Le,y as He,z as A,A as $,M as $e,P as Ne,C as Ae,D as We,E as Oe,Y as Ve,F as Q,I as V,G as ue,W as q,H as f,J as Ue,K as he,L as F,N as Qe,O as Fe,Q as xe,U as fe,V as me,X as Ye,Z as Xe,_ as _e}from"./index-DNUZpnaa.js";import{D as Ke,L as Ge}from"./LightMode-ZsshUznu.js";function Je(e,r,n,o,s){const[a,i]=u.useState(()=>s&&n?n(e).matches:o?o(e).matches:r);return Ce(()=>{if(!n)return;const c=n(e),h=()=>{i(c.matches)};return h(),c.addEventListener("change",h),()=>{c.removeEventListener("change",h)}},[e,n]),a}const Ze={...Pe},ge=Ze.useSyncExternalStore;function qe(e,r,n,o,s){const a=u.useCallback(()=>r,[r]),i=u.useMemo(()=>{if(s&&n)return()=>n(e).matches;if(o!==null){const{matches:p}=o(e);return()=>p}return a},[a,e,o,s,n]),[c,h]=u.useMemo(()=>{if(n===null)return[a,()=>()=>{}];const p=n(e);return[()=>p.matches,m=>(p.addEventListener("change",m),()=>{p.removeEventListener("change",m)})]},[a,n,e]);return ge(h,c,i)}function ye(e={}){const{themeId:r}=e;return function(o,s={}){let a=Se();a&&r&&(a=a[r]||a);const i=typeof window<"u"&&typeof window.matchMedia<"u",{defaultMatches:c=!1,matchMedia:h=i?window.matchMedia:null,ssrMatchMedia:x=null,noSsr:p=!1}=Ee({name:"MuiUseMediaQuery",props:s,theme:a});let m=typeof o=="function"?o(a):o;return m=m.replace(/^@media( ?)/m,""),m.includes("print")&&console.warn(["MUI: You have provided a `print` query to the `useMediaQuery` hook.","Using the print media query to modify print styles can lead to unexpected results.","Consider using the `displayPrint` field in the `sx` prop instead.","More information about `displayPrint` on our docs: https://mui.com/system/display/#display-in-print."].join(`
2
- `)),(ge!==void 0?qe:Je)(m,c,h,x,p)}}ye();const et=S(t.jsx("path",{d:"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"})),tt=M(Ie,{name:"MuiBreadcrumbCollapsed"})(J(({theme:e})=>({display:"flex",marginLeft:`calc(${e.spacing(1)} * 0.5)`,marginRight:`calc(${e.spacing(1)} * 0.5)`,...e.palette.mode==="light"?{backgroundColor:e.palette.grey[100],color:e.palette.grey[700]}:{backgroundColor:e.palette.grey[700],color:e.palette.grey[100]},borderRadius:2,"&:hover, &:focus":{...e.palette.mode==="light"?{backgroundColor:e.palette.grey[200]}:{backgroundColor:e.palette.grey[600]}},"&:active":{boxShadow:e.shadows[0],...e.palette.mode==="light"?{backgroundColor:oe(e.palette.grey[200],.12)}:{backgroundColor:oe(e.palette.grey[600],.12)}}}))),rt=M(et)({width:24,height:16});function nt(e){const{slots:r={},slotProps:n={},...o}=e,s=e;return t.jsx("li",{children:t.jsx(tt,{focusRipple:!0,...o,ownerState:s,children:t.jsx(rt,{as:r.CollapsedIcon,ownerState:s,...n.collapsedIcon})})})}function ot(e){return le("MuiBreadcrumbs",e)}const st=ie("MuiBreadcrumbs",["root","ol","li","separator"]),at=e=>{const{classes:r}=e;return de({root:["root"],li:["li"],ol:["ol"],separator:["separator"]},ot,r)},it=M(U,{name:"MuiBreadcrumbs",slot:"Root",overridesResolver:(e,r)=>[{[`& .${st.li}`]:r.li},r.root]})({}),lt=M("ol",{name:"MuiBreadcrumbs",slot:"Ol"})({display:"flex",flexWrap:"wrap",alignItems:"center",padding:0,margin:0,listStyle:"none"}),ct=M("li",{name:"MuiBreadcrumbs",slot:"Separator"})({display:"flex",userSelect:"none",marginLeft:8,marginRight:8});function dt(e,r,n,o){return e.reduce((s,a,i)=>(i<e.length-1?s=s.concat(a,t.jsx(ct,{"aria-hidden":!0,className:r,ownerState:o,children:n},`separator-${i}`)):s.push(a),s),[])}const pt=u.forwardRef(function(r,n){const o=ce({props:r,name:"MuiBreadcrumbs"}),{children:s,className:a,component:i="nav",slots:c={},slotProps:h={},expandText:x="Show path",itemsAfterCollapse:p=1,itemsBeforeCollapse:m=1,maxItems:y=8,separator:E="/",...H}=o,[z,P]=u.useState(!1),v={...o,component:i,expanded:z,expandText:x,itemsAfterCollapse:p,itemsBeforeCollapse:m,maxItems:y,separator:E},C=at(v),T=Me({elementType:c.CollapsedIcon,externalSlotProps:h.collapsedIcon,ownerState:v}),j=u.useRef(null),k=d=>{const w=()=>{P(!0);const b=j.current.querySelector("a[href],button,[tabindex]");b&&b.focus()};return m+p>=d.length?d:[...d.slice(0,m),t.jsx(nt,{"aria-label":x,slots:{CollapsedIcon:c.CollapsedIcon},slotProps:{collapsedIcon:T},onClick:w},"ellipsis"),...d.slice(d.length-p,d.length)]},D=u.Children.toArray(s).filter(d=>u.isValidElement(d)).map((d,w)=>t.jsx("li",{className:C.li,children:d},`child-${w}`));return t.jsx(it,{ref:n,component:i,color:"textSecondary",className:O(C.root,a),ownerState:v,...H,children:t.jsx(lt,{className:C.ol,ref:j,ownerState:v,children:dt(z||y&&D.length<=y?D:k(D),C.separator,E,v)})})});function ut(e,r,n){const o=r.getBoundingClientRect(),s=n&&n.getBoundingClientRect(),a=pe(r);let i;if(r.fakeTransform)i=r.fakeTransform;else{const x=a.getComputedStyle(r);i=x.getPropertyValue("-webkit-transform")||x.getPropertyValue("transform")}let c=0,h=0;if(i&&i!=="none"&&typeof i=="string"){const x=i.split("(")[1].split(")")[0].split(",");c=parseInt(x[4],10),h=parseInt(x[5],10)}return e==="left"?s?`translateX(${s.right+c-o.left}px)`:`translateX(${a.innerWidth+c-o.left}px)`:e==="right"?s?`translateX(-${o.right-s.left-c}px)`:`translateX(-${o.left+o.width-c}px)`:e==="up"?s?`translateY(${s.bottom+h-o.top}px)`:`translateY(${a.innerHeight+h-o.top}px)`:s?`translateY(-${o.top-s.top+o.height-h}px)`:`translateY(-${o.top+o.height-h}px)`}function ht(e){return typeof e=="function"?e():e}function W(e,r,n){const o=ht(n),s=ut(e,r,o);s&&(r.style.webkitTransform=s,r.style.transform=s)}const xt=u.forwardRef(function(r,n){const o=Z(),s={enter:o.transitions.easing.easeOut,exit:o.transitions.easing.sharp},a={enter:o.transitions.duration.enteringScreen,exit:o.transitions.duration.leavingScreen},{addEndListener:i,appear:c=!0,children:h,container:x,direction:p="down",easing:m=s,in:y,onEnter:E,onEntered:H,onEntering:z,onExit:P,onExited:v,onExiting:C,style:T,timeout:j=a,TransitionComponent:k=ze,...D}=r,d=u.useRef(null),w=Te(De(h),d,n),b=l=>g=>{l&&(g===void 0?l(d.current):l(d.current,g))},Y=b((l,g)=>{W(p,l,x),Be(l),E&&E(l,g)}),te=b((l,g)=>{const L=se({timeout:j,style:T,easing:m},{mode:"enter"});l.style.webkitTransition=o.transitions.create("-webkit-transform",{...L}),l.style.transition=o.transitions.create("transform",{...L}),l.style.webkitTransform="none",l.style.transform="none",z&&z(l,g)}),R=b(H),B=b(C),I=b(l=>{const g=se({timeout:j,style:T,easing:m},{mode:"exit"});l.style.webkitTransition=o.transitions.create("-webkit-transform",g),l.style.transition=o.transitions.create("transform",g),W(p,l,x),P&&P(l)}),X=b(l=>{l.style.webkitTransition="",l.style.transition="",v&&v(l)}),_=l=>{i&&i(d.current,l)},N=u.useCallback(()=>{d.current&&W(p,d.current,x)},[p,x]);return u.useEffect(()=>{if(y||p==="down"||p==="right")return;const l=Re(()=>{d.current&&W(p,d.current,x)}),g=pe(d.current);return g.addEventListener("resize",l),()=>{l.clear(),g.removeEventListener("resize",l)}},[p,y,x]),u.useEffect(()=>{y||N()},[y,N]),t.jsx(k,{nodeRef:d,onEnter:Y,onEntered:R,onEntering:te,onExit:I,onExited:X,onExiting:B,addEndListener:_,appear:c,in:y,timeout:j,...D,children:(l,{ownerState:g,...L})=>u.cloneElement(h,{ref:w,style:{visibility:l==="exited"&&!y?"hidden":void 0,...T,...h.props.style},...L})})});function ft(e){return le("MuiDrawer",e)}ie("MuiDrawer",["root","docked","paper","anchorLeft","anchorRight","anchorTop","anchorBottom","paperAnchorLeft","paperAnchorRight","paperAnchorTop","paperAnchorBottom","paperAnchorDockedLeft","paperAnchorDockedRight","paperAnchorDockedTop","paperAnchorDockedBottom","modal"]);const be=(e,r)=>{const{ownerState:n}=e;return[r.root,(n.variant==="permanent"||n.variant==="persistent")&&r.docked,r.modal]},mt=e=>{const{classes:r,anchor:n,variant:o}=e,s={root:["root",`anchor${$(n)}`],docked:[(o==="permanent"||o==="persistent")&&"docked"],modal:["modal"],paper:["paper",`paperAnchor${$(n)}`,o!=="temporary"&&`paperAnchorDocked${$(n)}`]};return de(s,ft,r)},gt=M($e,{name:"MuiDrawer",slot:"Root",overridesResolver:be})(J(({theme:e})=>({zIndex:(e.vars||e).zIndex.drawer}))),yt=M("div",{shouldForwardProp:Ae,name:"MuiDrawer",slot:"Docked",skipVariantsResolver:!1,overridesResolver:be})({flex:"0 0 auto"}),bt=M(Ne,{name:"MuiDrawer",slot:"Paper",overridesResolver:(e,r)=>{const{ownerState:n}=e;return[r.paper,r[`paperAnchor${$(n.anchor)}`],n.variant!=="temporary"&&r[`paperAnchorDocked${$(n.anchor)}`]]}})(J(({theme:e})=>({overflowY:"auto",display:"flex",flexDirection:"column",height:"100%",flex:"1 0 auto",zIndex:(e.vars||e).zIndex.drawer,WebkitOverflowScrolling:"touch",position:"fixed",top:0,outline:0,variants:[{props:{anchor:"left"},style:{left:0}},{props:{anchor:"top"},style:{top:0,left:0,right:0,height:"auto",maxHeight:"100%"}},{props:{anchor:"right"},style:{right:0}},{props:{anchor:"bottom"},style:{top:"auto",left:0,bottom:0,right:0,height:"auto",maxHeight:"100%"}},{props:({ownerState:r})=>r.anchor==="left"&&r.variant!=="temporary",style:{borderRight:`1px solid ${(e.vars||e).palette.divider}`}},{props:({ownerState:r})=>r.anchor==="top"&&r.variant!=="temporary",style:{borderBottom:`1px solid ${(e.vars||e).palette.divider}`}},{props:({ownerState:r})=>r.anchor==="right"&&r.variant!=="temporary",style:{borderLeft:`1px solid ${(e.vars||e).palette.divider}`}},{props:({ownerState:r})=>r.anchor==="bottom"&&r.variant!=="temporary",style:{borderTop:`1px solid ${(e.vars||e).palette.divider}`}}]}))),ve={left:"right",right:"left",top:"down",bottom:"up"};function vt(e){return["left","right"].includes(e)}function jt({direction:e},r){return e==="rtl"&&vt(r)?ve[r]:r}const kt=u.forwardRef(function(r,n){const o=ce({props:r,name:"MuiDrawer"}),s=Z(),a=Le(),i={enter:s.transitions.duration.enteringScreen,exit:s.transitions.duration.leavingScreen},{anchor:c="left",BackdropProps:h,children:x,className:p,elevation:m=16,hideBackdrop:y=!1,ModalProps:{BackdropProps:E,...H}={},onClose:z,open:P=!1,PaperProps:v={},SlideProps:C,TransitionComponent:T,transitionDuration:j=i,variant:k="temporary",slots:D={},slotProps:d={},...w}=o,b=u.useRef(!1);u.useEffect(()=>{b.current=!0},[]);const Y=jt({direction:a?"rtl":"ltr"},c),R={...o,anchor:c,elevation:m,open:P,variant:k,...w},B=mt(R),I={slots:{transition:T,...D},slotProps:{paper:v,transition:C,...d,backdrop:He(d.backdrop||{...h,...E},{transitionDuration:j})}},[X,_]=A("root",{ref:n,elementType:gt,className:O(B.root,B.modal,p),shouldForwardComponentProp:!0,ownerState:R,externalForwardedProps:{...I,...w,...H},additionalProps:{open:P,onClose:z,hideBackdrop:y,slots:{backdrop:I.slots.backdrop},slotProps:{backdrop:I.slotProps.backdrop}}}),[N,l]=A("paper",{elementType:bt,shouldForwardComponentProp:!0,className:O(B.paper,v.className),ownerState:R,externalForwardedProps:I,additionalProps:{elevation:k==="temporary"?m:0,square:!0,...k==="temporary"&&{role:"dialog","aria-modal":"true"}}}),[g,L]=A("docked",{elementType:yt,ref:n,className:O(B.root,B.docked,p),ownerState:R,externalForwardedProps:I,additionalProps:w}),[ke,we]=A("transition",{elementType:xt,ownerState:R,externalForwardedProps:I,additionalProps:{in:P,direction:ve[Y],timeout:j,appear:b.current}}),re=t.jsx(N,{...l,children:x});if(k==="permanent")return t.jsx(g,{...L,children:re});const ne=t.jsx(ke,{...we,children:re});return k==="persistent"?t.jsx(g,{...L,children:ne}):t.jsx(X,{..._,children:ne})}),wt=ye({themeId:We}),St=S([t.jsx("path",{d:"M19 5v14H5V5zm0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2"},"0"),t.jsx("path",{d:"M14 17H7v-2h7zm3-4H7v-2h10zm0-4H7V7h10z"},"1")]),Et=S(t.jsx("path",{d:"M10.85 12.65h2.3L12 9zM20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12zM14.3 16l-.7-2h-3.2l-.7 2H7.8L11 7h2l3.2 9z"})),Pt=S(t.jsx("path",{d:"M10 6 8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"})),Ct=S(t.jsx("path",{d:"M9.4 16.6 4.8 12l4.6-4.6L8 6l-6 6 6 6zm5.2 0 4.6-4.6-4.6-4.6L16 6l6 6-6 6z"})),It=S(t.jsx("path",{d:"m9.17 6 2 2H20v10H4V6zM10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8z"})),Mt=S(t.jsx("path",{d:"m12 5.69 5 4.5V18h-2v-6H9v6H7v-7.81zM12 3 2 12h3v8h6v-6h2v6h6v-8h3z"})),zt=S(t.jsx("path",{d:"M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3z"})),Tt=S([t.jsx("path",{d:"M12 22C6.49 22 2 17.51 2 12S6.49 2 12 2s10 4.04 10 9c0 3.31-2.69 6-6 6h-1.77c-.28 0-.5.22-.5.5 0 .12.05.23.13.33.41.47.64 1.06.64 1.67 0 1.38-1.12 2.5-2.5 2.5m0-18c-4.41 0-8 3.59-8 8s3.59 8 8 8c.28 0 .5-.22.5-.5 0-.16-.08-.28-.14-.35-.41-.46-.63-1.05-.63-1.65 0-1.38 1.12-2.5 2.5-2.5H16c2.21 0 4-1.79 4-4 0-3.86-3.59-7-8-7"},"0"),t.jsx("circle",{cx:"6.5",cy:"11.5",r:"1.5"},"1"),t.jsx("circle",{cx:"9.5",cy:"7.5",r:"1.5"},"2"),t.jsx("circle",{cx:"14.5",cy:"7.5",r:"1.5"},"3"),t.jsx("circle",{cx:"17.5",cy:"11.5",r:"1.5"},"4")]);function G(e){return t.jsxs(Oe,{...e,viewBox:"0 0 24 24",sx:{fill:"none",...e.sx},children:[t.jsx("rect",{x:"3.25",y:"4.75",width:"17.5",height:"14.5",rx:"2.25",stroke:"currentColor",strokeWidth:"1.5"}),t.jsx("line",{x1:"8.5",y1:"5",x2:"8.5",y2:"19",stroke:"currentColor",strokeWidth:"1.5"})]})}const Dt={light:"dark",dark:"auto",auto:"light"},ae={light:"Light mode (click for dark)",dark:"Dark mode (click for auto)",auto:"Auto mode (follows OS, click for light)"};function Rt(){const{mode:e,userChoice:r,setMode:n,allowUserToggle:o}=Ve();if(!o)return null;const s=r??e,a=Dt[s],i=s==="auto"?Et:s==="dark"?Ke:Ge;return t.jsx(Q,{title:ae[s],children:t.jsx(V,{"aria-label":ae[s],onClick:()=>n(a),size:"small",children:t.jsx(i,{fontSize:"small"})})})}function K({label:e,onClick:r}){return t.jsx(Ue,{clickable:!0,onClick:r,label:e,size:"small","aria-label":`Navigate to ${e}`,sx:n=>({backgroundColor:"background.paper",color:"text.primary",fontWeight:500,fontSize:"0.875rem",height:32,cursor:"pointer",borderRadius:"4px",maxWidth:320,"& .MuiChip-label":{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},"&:hover":{backgroundColor:n.palette.mode==="dark"?"rgba(255, 255, 255, 0.08)":"grey.100"}})})}function Bt(){const e=ue(),r=e["*"],n=q();return!e.environmentName&&!e.packageName&&!r?null:t.jsx(f,{sx:{display:"flex",alignItems:"center",minWidth:0},children:t.jsxs(pt,{"aria-label":"breadcrumb",separator:t.jsx(Pt,{sx:{fontSize:14,color:"text.secondary"}}),children:[e.environmentName&&t.jsx(K,{label:e.environmentName,onClick:o=>n(`/${e.environmentName}/`,o)}),e.packageName&&t.jsx(K,{label:e.packageName,onClick:o=>n(`/${e.environmentName}/${e.packageName}/`,o)}),r&&t.jsx(K,{label:r,onClick:o=>n(`/${e.environmentName}/${e.packageName}/${r}`,o)})]})})}const Lt=260,Ht=64;function je({isCollapsed:e,onToggleCollapse:r,logoHeader:n}){return t.jsxs(f,{sx:{height:"100dvh",width:e?Ht:Lt,flexShrink:0,display:"flex",flexDirection:"column",backgroundColor:"background.paper",transition:"width 0.2s cubic-bezier(0.4, 0, 0.2, 1)",overflow:"hidden"},children:[t.jsx($t,{isCollapsed:e,onToggleCollapse:r,logoHeader:n}),t.jsxs(f,{sx:{flex:1,overflowY:"auto",overflowX:"hidden",py:1},children:[t.jsx(Nt,{isCollapsed:e}),t.jsx(At,{isCollapsed:e}),t.jsx(Wt,{isCollapsed:e})]}),t.jsx(Ot,{isCollapsed:e})]})}function $t({isCollapsed:e,onToggleCollapse:r,logoHeader:n}){const o=q();return n?t.jsxs(f,{sx:{height:56,display:"flex",alignItems:"center",justifyContent:"space-between",px:e?0:2,flexShrink:0},children:[n,t.jsx(V,{size:"small",onClick:r,"aria-label":e?"Expand sidebar":"Collapse sidebar",children:t.jsx(G,{fontSize:"small"})})]}):t.jsxs(f,{sx:{minHeight:56,display:"flex",flexDirection:e?"column":"row",alignItems:"center",justifyContent:e?"center":"space-between",gap:e?.5:0,py:e?1:0,px:e?0:2,flexShrink:0},children:[t.jsxs(f,{onClick:s=>o("/",s),sx:{display:"flex",alignItems:"center",gap:1,cursor:"pointer",minWidth:0},children:[t.jsx(f,{component:"img",src:"/logo.svg",alt:"Malloy",sx:{width:24,height:24,flexShrink:0}}),!e&&t.jsx(U,{variant:"subtitle1",sx:{color:"text.primary",fontWeight:500,letterSpacing:"-0.025em",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},children:"Malloy Publisher"})]}),t.jsx(Q,{title:e?"Expand sidebar":"Collapse sidebar",placement:"right",children:t.jsx(V,{size:"small",onClick:r,"aria-label":e?"Expand sidebar":"Collapse sidebar",children:t.jsx(G,{fontSize:"small"})})})]})}function Nt({isCollapsed:e}){const n=he().pathname==="/";return t.jsx(F,{sx:{py:0},children:t.jsx(ee,{icon:t.jsx(Mt,{fontSize:"small"}),label:"Home",to:"/",selected:n,isCollapsed:e})})}function At({isCollapsed:e}){const{apiClients:r}=Qe(),n=ue(),{data:o}=Fe({queryKey:["environments"],queryFn:()=>r.environments.listEnvironments()}),s=o?.data??[];return s.length===0?null:t.jsxs(f,{sx:{mt:1},children:[!e&&t.jsx(U,{variant:"caption",sx:{display:"block",px:3,py:1,color:"text.secondary",fontWeight:500,textTransform:"uppercase",fontSize:"0.6875rem",letterSpacing:"0.5px"},children:"Environments"}),t.jsx(F,{sx:{py:0},children:s.map(a=>{const i=a.name??"";return t.jsx(ee,{icon:t.jsx(It,{fontSize:"small"}),label:i,to:`/${i}`,selected:n.environmentName===i,isCollapsed:e},i)})})]})}function Wt({isCollapsed:e}){const n=he().pathname.startsWith("/settings/theme");return t.jsxs(f,{sx:{mt:1},children:[!e&&t.jsx(U,{variant:"caption",sx:{display:"block",px:3,py:1,color:"text.secondary",fontWeight:500,textTransform:"uppercase",fontSize:"0.6875rem",letterSpacing:"0.5px"},children:"Settings"}),t.jsx(F,{sx:{py:0},children:t.jsx(ee,{icon:t.jsx(Tt,{fontSize:"small"}),label:"Visualization theme",to:"/settings/theme",selected:n,isCollapsed:e})})]})}function Ot({isCollapsed:e}){const r=[{label:"Malloy Docs",href:"https://docs.malloydata.dev/documentation/",icon:t.jsx(St,{fontSize:"small"}),external:!0},{label:"Publisher Docs",href:"https://github.com/malloydata/publisher/blob/main/README.md",icon:t.jsx(zt,{fontSize:"small"}),external:!0},{label:"Publisher API",href:"/api-doc.html",icon:t.jsx(Ct,{fontSize:"small"}),external:!1}];return t.jsx(F,{sx:{py:1},children:r.map(n=>t.jsx(Vt,{label:n.label,href:n.href,icon:n.icon,external:n.external,isCollapsed:e},n.label))})}function ee({icon:e,label:r,to:n,selected:o,isCollapsed:s}){const a=q(),i=t.jsxs(xe,{selected:o,onClick:c=>a(n,c),sx:{justifyContent:s?"center":"flex-start",px:s?0:2},children:[t.jsx(fe,{sx:{minWidth:s?0:36,justifyContent:"center",color:o?"text.primary":"text.secondary"},children:e}),!s&&t.jsx(me,{primary:r,primaryTypographyProps:{variant:"body2",sx:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}})]});return s?t.jsx(Q,{title:r,placement:"right",children:t.jsx(f,{children:i})}):i}function Vt({label:e,href:r,icon:n,external:o,isCollapsed:s}){const a=t.jsxs(xe,{component:"a",href:r,target:o?"_blank":void 0,rel:o?"noopener noreferrer":void 0,sx:{justifyContent:s?"center":"flex-start",px:s?0:2},children:[t.jsx(fe,{sx:{minWidth:s?0:36,justifyContent:"center",color:"text.secondary"},children:n}),!s&&t.jsx(me,{primary:e,primaryTypographyProps:{variant:"body2",sx:{color:"text.secondary",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}})]});return s?t.jsx(Q,{title:e,placement:"right",children:t.jsx(f,{children:a})}):a}function Ut({open:e,onClose:r,logoHeader:n}){return t.jsx(kt,{anchor:"left",open:e,onClose:r,ModalProps:{keepMounted:!0},sx:{display:{xs:"block",md:"none"},"& .MuiDrawer-paper":{boxSizing:"border-box",width:260}},children:t.jsx(je,{isCollapsed:!1,onToggleCollapse:r,logoHeader:n})})}function Yt({headerProps:e}){const r=Z(),n=wt(r.breakpoints.up("md")),[o,s]=u.useState(!1),[a,i]=u.useState(!1);return u.useEffect(()=>{n&&o&&s(!1)},[n,o]),t.jsxs(f,{sx:{height:"100dvh",display:"flex",flexDirection:"row",bgcolor:"background.default"},children:[n&&t.jsx(je,{isCollapsed:a,onToggleCollapse:()=>i(c=>!c),logoHeader:e?.logoHeader}),t.jsxs(f,{component:"main",sx:{flex:1,display:"flex",flexDirection:"column",minWidth:0},children:[t.jsxs(f,{sx:{flexShrink:0,display:"flex",alignItems:"center",justifyContent:"space-between",height:Ye.headerHeight,px:{xs:1,md:3},backgroundColor:"background.default"},children:[t.jsx(f,{sx:{display:{xs:"flex",md:"none"},alignItems:"center",mr:1},children:t.jsx(V,{size:"small",onClick:()=>s(!0),"aria-label":"Open navigation",children:t.jsx(G,{fontSize:"small"})})}),t.jsx(f,{sx:{flex:1,minWidth:0,display:"flex",alignItems:"center"},children:t.jsx(Bt,{})}),t.jsxs(f,{id:"header-actions-portal",sx:{display:"flex",alignItems:"center",flexShrink:0,gap:1},children:[t.jsx(Rt,{}),e?.endCap]})]}),t.jsx(f,{sx:{flex:1,overflow:"auto",minWidth:320,minHeight:0},children:t.jsx(u.Suspense,{fallback:t.jsx(_e,{}),children:t.jsx(Xe,{})})})]}),t.jsx(Ut,{open:o,onClose:()=>s(!1),logoHeader:e?.logoHeader})]})}export{Yt as default};
@@ -1 +0,0 @@
1
- import{G as r,W as t,j as e,$ as c,a4 as o}from"./index-DNUZpnaa.js";function m(){const{environmentName:a,packageName:n}=r(),s=t();if(a)if(n){const i=c({environmentName:a,packageName:n});return e.jsx(o,{onClickPackageFile:s,resourceUri:i})}else return e.jsx("div",{children:e.jsx("h2",{children:"Missing package name"})});else return e.jsx("div",{children:e.jsx("h2",{children:"Missing environment name"})})}export{m as default};
@@ -1 +0,0 @@
1
- import{G as c,j as e,$ as t,a0 as l,H as r,a1 as x,a2 as d}from"./index-DNUZpnaa.js";function u(){const n=c(),a=n["*"];if(!n.environmentName)return e.jsx("div",{children:e.jsx("h2",{children:"Missing environment name"})});if(!n.packageName)return e.jsx("div",{children:e.jsx("h2",{children:"Missing package name"})});const s={p:3,maxWidth:1200,mx:"auto"};if(a?.startsWith("pages/")&&!a.endsWith(".malloy")&&!a.endsWith(".malloynb")){const m=a.slice(6),o=t({environmentName:n.environmentName,packageName:n.packageName,modelPath:m});return e.jsx(l,{resourceUri:o})}const i=t({environmentName:n.environmentName,packageName:n.packageName,modelPath:a});return a?.endsWith(".malloy")?e.jsx(r,{sx:s,children:e.jsx(x,{resourceUri:i,runOnDemand:!0,maxResultSize:512*1024})}):a?.endsWith(".malloynb")?e.jsx(r,{sx:s,children:e.jsx(d,{resourceUri:i,maxResultSize:1024*1024})}):e.jsx(r,{sx:s,children:e.jsxs("h2",{children:["Unrecognized file type: ",a]})})}export{u as default};
@@ -1 +0,0 @@
1
- import{G as r,W as t,j as e,$ as c,a3 as o}from"./index-DNUZpnaa.js";function l(){const{environmentName:a,packageName:n}=r(),s=t();if(a)if(n){const i=c({environmentName:a,packageName:n});return e.jsx(o,{onClickPackageFile:s,resourceUri:i})}else return e.jsx("div",{children:e.jsx("h2",{children:"Missing package name"})});else return e.jsx("div",{children:e.jsx("h2",{children:"Missing environment name"})})}export{l as default};