@malloydata/malloyyo 0.2.40 → 0.2.41

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.
@@ -35,10 +35,16 @@ const givensToUrl = (dashboard, givens) => {
35
35
 
36
36
  mountInPage({
37
37
  root: document.getElementById("root"),
38
- // Governed query — the shell's trusted /api/run (the same endpoint the iframe
38
+ // Governed query — the shell's trusted api/run (the same endpoint the iframe
39
39
  // broker forwards to). Returns the raw result the runtime normalizes.
40
+ //
41
+ // RELATIVE. A leading slash resolves against the ORIGIN, so behind anything
42
+ // serving this page under a prefix — code-server's `/proxy/<port>/`, a
43
+ // Codespaces forwarded port — the POST went to the proxy's own root and came
44
+ // back `405 Method Not Allowed` from the proxy rather than reaching the dev
45
+ // server at all. Resolved against the document, it is correct at any depth.
40
46
  run: (req, givens) =>
41
- fetch("/api/run", {
47
+ fetch("api/run", {
42
48
  method: "POST",
43
49
  headers: { "content-type": "application/json" },
44
50
  body: JSON.stringify({ d: name, query: req.query, malloy: req.malloy, givens }),
package/dist/index.js CHANGED
@@ -2881,7 +2881,7 @@ function clearCreds(url6) {
2881
2881
  }
2882
2882
 
2883
2883
  // package.json
2884
- var version = "0.2.40";
2884
+ var version = "0.2.41";
2885
2885
 
2886
2886
  // src/http.ts
2887
2887
  var USER_AGENT = `malloyyo/${version}`;
@@ -3382,6 +3382,7 @@ function siblingList(current, all, href) {
3382
3382
  // src/dashboard.ts
3383
3383
  var resolveFrameEntry = () => path6.join(resolveRuntimeDir(), "..", "frame-entry.tsx");
3384
3384
  var resolveInPageEntry = () => path6.join(resolveRuntimeDir(), "..", "frame-inpage-entry.tsx");
3385
+ var inlineScript = (js) => js.replace(/<\/(script)/gi, String.raw`<\/$1`);
3385
3386
  var esc2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3386
3387
  function makeBundler() {
3387
3388
  const cache = /* @__PURE__ */ new Map();
@@ -3457,9 +3458,26 @@ function makeInPageBundler() {
3457
3458
  };
3458
3459
  }
3459
3460
  var html = (body, title) => `<!doctype html><html><head><meta charset="utf-8"><title>${esc2(title)}</title><meta name="viewport" content="width=device-width,initial-scale=1"><style>${NAV_CSS}</style></head><body style="margin:0">${body}</body></html>`;
3460
- var devSiblings = (dash, all) => siblingList(dash.name, all, (n) => `/?d=${encodeURIComponent(n)}`);
3461
+ var dashLink = (n) => `?d=${encodeURIComponent(n)}`;
3462
+ var DEV_PATHS = {
3463
+ /** Sibling dashboard, for the switcher in the nav bar. */
3464
+ dashboard: dashLink,
3465
+ /** In-page (tag-only) bundle. */
3466
+ inPage: (n) => `inpage.js?d=${encodeURIComponent(n)}`,
3467
+ /** Artifact bundle, for a dashboard with a Dashboard.tsx. */
3468
+ bundle: (n) => `bundle.js?d=${encodeURIComponent(n)}`,
3469
+ /** The sandboxed artifact document. Same origin as the shell, relative like
3470
+ everything else — the iframe is isolated by its OPAQUE origin (the
3471
+ `allow-scripts`-only sandbox), not by living on a second port. */
3472
+ frame: (n) => `frame?d=${encodeURIComponent(n)}`,
3473
+ /** Live-reload stream. */
3474
+ events: "events",
3475
+ /** The privileged query broker the parent shell brokers postMessage into. */
3476
+ run: "api/run"
3477
+ };
3478
+ var devSiblings = (dash, all) => siblingList(dash.name, all, dashLink);
3461
3479
  function navHtml2(dash, all) {
3462
- return navHtml(dash.name, all, (n) => `/?d=${encodeURIComponent(n)}`);
3480
+ return navHtml(dash.name, all, dashLink);
3463
3481
  }
3464
3482
  function inPageShell(dash, all, givenSpecs, initialGivens, initialUrlState, tileSpecs) {
3465
3483
  const info = {
@@ -3474,21 +3492,20 @@ function inPageShell(dash, all, givenSpecs, initialGivens, initialUrlState, tile
3474
3492
  autorun: dash.autorun
3475
3493
  };
3476
3494
  return html(
3477
- navHtml2(dash, all) + `<div id="root"></div><script>window.__DASHBOARD__=${safeJson(info)};window.__DASHBOARDS__=${safeJson(devSiblings(dash, all))};window.__GIVENS__=${safeJson(givenSpecs)};window.__INITIAL_GIVENS__=${safeJson(initialGivens)};window.__INITIAL_URLSTATE__=${safeJson(initialUrlState)}</script><script>try{new EventSource('/events').onmessage=()=>location.reload();}catch(e){}</script><script src="/inpage.js?d=${encodeURIComponent(dash.name)}"></script>`,
3495
+ navHtml2(dash, all) + `<div id="root"></div><script>window.__DASHBOARD__=${safeJson(info)};window.__DASHBOARDS__=${safeJson(devSiblings(dash, all))};window.__GIVENS__=${safeJson(givenSpecs)};window.__INITIAL_GIVENS__=${safeJson(initialGivens)};window.__INITIAL_URLSTATE__=${safeJson(initialUrlState)}</script><script>try{new EventSource('${DEV_PATHS.events}').onmessage=()=>location.reload();}catch(e){}</script><script src="${DEV_PATHS.inPage(dash.name)}"></script>`,
3478
3496
  dash.title
3479
3497
  );
3480
3498
  }
3481
- function parentShell(dash, frameBase, all, initialGivens, initialUrlState) {
3499
+ function parentShell(dash, all, initialGivens, initialUrlState) {
3482
3500
  const givensQs = Object.entries({ ...initialGivens, ...initialUrlState }).map(([k, v]) => `&${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("");
3483
3501
  const d = safeJson(dash.name);
3484
- const fb = safeJson(frameBase);
3485
3502
  const nav = navHtml2(dash, all);
3486
3503
  return html(
3487
3504
  `<div style="display:flex;flex-direction:column;height:100vh">` + nav + // allow-popups(+escape-sandbox): let a # link mark open its target in a
3488
3505
  // normal new tab on click instead of being blocked by the sandbox.
3489
- `<iframe id="f" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" src="${frameBase}/frame?d=${encodeURIComponent(dash.name)}${givensQs}" style="border:0;flex:1;width:100%"></iframe></div><script>
3506
+ `<iframe id="f" sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox" src="${DEV_PATHS.frame(dash.name)}${givensQs}" style="border:0;flex:1;width:100%"></iframe></div><script>
3490
3507
  const f=document.getElementById('f');
3491
- try{new EventSource('/events').onmessage=()=>location.reload();}catch(e){}
3508
+ try{new EventSource('${DEV_PATHS.events}').onmessage=()=>location.reload();}catch(e){}
3492
3509
  // The shareable URL has TWO namespaces the frame syncs independently:
3493
3510
  // '$NAME' = a given (the governed query contract), '~key' = a custom
3494
3511
  // component's useUrlState view-state. Each write must re-emit the other's
@@ -3503,7 +3520,7 @@ function shareUrl(dashboard){
3503
3520
  return u.pathname+u.search;
3504
3521
  }
3505
3522
  window.addEventListener('message',async(e)=>{
3506
- if(e.source!==f.contentWindow||e.origin!==${fb})return;
3523
+ if(e.source!==f.contentWindow)return;
3507
3524
  const m=e.data;
3508
3525
  if(m&&m.type==='givens'){
3509
3526
  G=m.givens||{};
@@ -3525,11 +3542,11 @@ window.addEventListener('message',async(e)=>{
3525
3542
  if(!m||m.type!=='run')return;
3526
3543
  let out;
3527
3544
  try{
3528
- const res=await fetch('/api/run',{method:'POST',headers:{'content-type':'application/json'},
3545
+ const res=await fetch('${DEV_PATHS.run}',{method:'POST',headers:{'content-type':'application/json'},
3529
3546
  body:JSON.stringify({d:${d},query:m.query,malloy:m.malloy,givens:m.givens,dashboard:m.dashboard})});
3530
3547
  out=await res.json();
3531
3548
  }catch(err){ out={ok:false,problems:[{message:String(err)}]}; }
3532
- f.contentWindow.postMessage({type:'result',id:m.id,...out},${fb});
3549
+ f.contentWindow.postMessage({type:'result',id:m.id,...out},'*');
3533
3550
  });
3534
3551
  </script>`,
3535
3552
  dash.title
@@ -3541,7 +3558,7 @@ function givensFromUrl(url6) {
3541
3558
  function urlStateFromUrl(url6) {
3542
3559
  return urlStateFromSearch(url6.search);
3543
3560
  }
3544
- function frameDoc(dash, all, givenSpecs, initialGivens, initialUrlState, tileSpecs) {
3561
+ function frameDoc(dash, all, givenSpecs, initialGivens, initialUrlState, bundleJs, tileSpecs) {
3545
3562
  const info = {
3546
3563
  name: dash.name,
3547
3564
  query: dash.query,
@@ -3556,7 +3573,7 @@ function frameDoc(dash, all, givenSpecs, initialGivens, initialUrlState, tileSpe
3556
3573
  autorun: dash.autorun
3557
3574
  };
3558
3575
  return html(
3559
- `<div id="root"></div><script>window.__DASHBOARD__=${safeJson(info)};window.__DASHBOARDS__=${safeJson(devSiblings(dash, all))};window.__GIVENS__=${safeJson(givenSpecs)};window.__INITIAL_GIVENS__=${safeJson(initialGivens)};window.__INITIAL_URLSTATE__=${safeJson(initialUrlState)}</script><script src="/bundle.js?d=${encodeURIComponent(dash.name)}"></script>`,
3576
+ `<div id="root"></div><script>window.__DASHBOARD__=${safeJson(info)};window.__DASHBOARDS__=${safeJson(devSiblings(dash, all))};window.__GIVENS__=${safeJson(givenSpecs)};window.__INITIAL_GIVENS__=${safeJson(initialGivens)};window.__INITIAL_URLSTATE__=${safeJson(initialUrlState)}</script><script>${inlineScript(bundleJs)}</script>`,
3560
3577
  dash.title
3561
3578
  );
3562
3579
  }
@@ -3569,8 +3586,6 @@ async function serveDashboard(opts) {
3569
3586
  await initConnections();
3570
3587
  const root = path6.resolve(opts.root ?? process.cwd());
3571
3588
  const port = opts.port ?? 4173;
3572
- const framePort = port + 1;
3573
- const frameBase = `http://localhost:${framePort}`;
3574
3589
  const runner = await makeRunner(root);
3575
3590
  if (!runner.entryExists()) {
3576
3591
  throw new Error(`No index.malloy at ${root} \u2014 run this from a Malloy model repo.`);
@@ -3618,34 +3633,39 @@ async function serveDashboard(opts) {
3618
3633
  console.error(` (file watch unavailable: ${e.message} \u2014 edits won't auto-reload)`);
3619
3634
  }
3620
3635
  const handler = async (req, res) => {
3621
- const onFramePort = (req.socket.localPort ?? port) === framePort;
3622
- const url6 = new URL(req.url ?? "/", `http://localhost:${onFramePort ? framePort : port}`);
3636
+ const url6 = new URL(req.url ?? "/", `http://localhost:${port}`);
3623
3637
  const send = (code, type, body, extra = {}) => {
3624
3638
  res.writeHead(code, { "content-type": type, ...extra });
3625
3639
  res.end(body);
3626
3640
  };
3627
3641
  try {
3628
- if (onFramePort) {
3629
- if (url6.pathname === "/frame") {
3630
- const dash = pick(url6);
3631
- const g = await resolveGivens(dash);
3632
- if (!g.ok) {
3633
- return send(
3634
- 200,
3635
- "text/html; charset=utf-8",
3636
- html(`<pre style="color:crimson;padding:16px">model error: ${esc2(g.error)}</pre>`, dash.title)
3637
- );
3638
- }
3642
+ if (url6.pathname === "/frame") {
3643
+ const dash = pick(url6);
3644
+ const g = await resolveGivens(dash);
3645
+ if (!g.ok) {
3639
3646
  return send(
3640
3647
  200,
3641
3648
  "text/html; charset=utf-8",
3642
- frameDoc(dash, dashboards, g.union, givensFromUrl(url6), urlStateFromUrl(url6), g.tiles)
3649
+ html(`<pre style="color:crimson;padding:16px">model error: ${esc2(g.error)}</pre>`, dash.title)
3643
3650
  );
3644
3651
  }
3645
- if (url6.pathname === "/bundle.js") {
3646
- return send(200, "application/javascript; charset=utf-8", await bundle(pick(url6)));
3647
- }
3648
- return send(404, "text/plain", "not found");
3652
+ return send(
3653
+ 200,
3654
+ "text/html; charset=utf-8",
3655
+ frameDoc(
3656
+ dash,
3657
+ dashboards,
3658
+ g.union,
3659
+ givensFromUrl(url6),
3660
+ urlStateFromUrl(url6),
3661
+ await bundle(dash),
3662
+ g.tiles
3663
+ ),
3664
+ { "cache-control": "no-store" }
3665
+ );
3666
+ }
3667
+ if (url6.pathname === "/bundle.js") {
3668
+ return send(200, "application/javascript; charset=utf-8", await bundle(pick(url6)));
3649
3669
  }
3650
3670
  if (url6.pathname === "/events") {
3651
3671
  res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
@@ -3668,17 +3688,24 @@ async function serveDashboard(opts) {
3668
3688
  return send(
3669
3689
  200,
3670
3690
  "text/html; charset=utf-8",
3671
- inPageShell(dash, dashboards, g.union, givensFromUrl(url6), urlStateFromUrl(url6), g.tiles)
3691
+ inPageShell(dash, dashboards, g.union, givensFromUrl(url6), urlStateFromUrl(url6), g.tiles),
3692
+ { "cache-control": "no-store" }
3672
3693
  );
3673
3694
  }
3674
3695
  return send(
3675
3696
  200,
3676
3697
  "text/html; charset=utf-8",
3677
- parentShell(dash, frameBase, dashboards, givensFromUrl(url6), urlStateFromUrl(url6))
3698
+ parentShell(dash, dashboards, givensFromUrl(url6), urlStateFromUrl(url6)),
3699
+ { "cache-control": "no-store" }
3678
3700
  );
3679
3701
  }
3680
3702
  if (url6.pathname === "/inpage.js") {
3681
- return send(200, "application/javascript; charset=utf-8", await inPageBundle());
3703
+ return send(
3704
+ 200,
3705
+ "application/javascript; charset=utf-8",
3706
+ await inPageBundle(),
3707
+ { "cache-control": "no-store" }
3708
+ );
3682
3709
  }
3683
3710
  if (url6.pathname === "/api/run" && req.method === "POST") {
3684
3711
  const { d, query, malloy, givens } = JSON.parse(await readBody(req));
@@ -3694,12 +3721,10 @@ async function serveDashboard(opts) {
3694
3721
  }
3695
3722
  };
3696
3723
  const shellServer = http2.createServer(handler);
3697
- const frameServer = http2.createServer(handler);
3698
3724
  await new Promise((r) => shellServer.listen(port, r));
3699
- await new Promise((r) => frameServer.listen(framePort, r));
3700
3725
  console.error(`
3701
3726
  malloyyo dashboard dev \u2014 model: ${root}`);
3702
- console.error(` http://localhost:${port}/ (artifact origin: ${frameBase})`);
3727
+ console.error(` http://localhost:${port}/`);
3703
3728
  for (const d of dashboards) {
3704
3729
  const kind = d.tsxPath ? "custom (iframe)" : "tag-only (in-page)";
3705
3730
  console.error(` \u2022 ${d.name} (${kind}) \u2192 http://localhost:${port}/?d=${d.name}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloyyo",
3
- "version": "0.2.40",
3
+ "version": "0.2.41",
4
4
  "description": "Publish Malloy models to a Malloyyo instance",
5
5
  "license": "MIT",
6
6
  "repository": {