@metabase/cli 0.5.2 → 0.5.4

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 (345) hide show
  1. package/README.md +1 -1
  2. package/dist/{add-collection-C0BQHjJd.mjs → add-collection-D2x34KVO.mjs} +3 -3
  3. package/dist/{alert-mXHOt00T.mjs → alert-B-w0c61D.mjs} +7 -7
  4. package/dist/{alerts-CyB1Z7AU.mjs → alerts-nnrpqD-U.mjs} +6 -6
  5. package/dist/api-key-9jVV67hF.mjs +38 -0
  6. package/dist/api-key-CTyXuYui.mjs +13 -0
  7. package/dist/api-key-DbsqHlN6.mjs +31 -0
  8. package/dist/{append-DeHhUAeY.mjs → append-BPrVW4RB.mjs} +4 -4
  9. package/dist/{archive-DRMXvkiI.mjs → archive-BYo35OGC.mjs} +5 -5
  10. package/dist/{archive-Bi-Sfq5T.mjs → archive-BZVRJYTT.mjs} +5 -5
  11. package/dist/{archive-CCKxZEuq.mjs → archive-Bw_cvk_4.mjs} +3 -3
  12. package/dist/{archive-qy0CFo1s2.mjs → archive-CJFUFozc.mjs} +5 -5
  13. package/dist/{archive-CfNLlebd.mjs → archive-CTZs6Hl6.mjs} +5 -5
  14. package/dist/{archive-CDSAXKKB.mjs → archive-Ccf6Qqrf.mjs} +3 -3
  15. package/dist/{archive-DfDqPgtk.mjs → archive-Cwetg5kK.mjs} +5 -5
  16. package/dist/{archive-CjVd1fJ5.mjs → archive-DBQnauVo.mjs} +5 -5
  17. package/dist/{archive-CpvpGZlz.mjs → archive-F5hoWW7f.mjs} +5 -5
  18. package/dist/{archive-DpgtAdEa.mjs → archive-OZQaaQkK.mjs} +5 -5
  19. package/dist/{archive-B1MA5MZp.mjs → archive-p7gGwh0n.mjs} +5 -5
  20. package/dist/{archive-HCVTRXI2.mjs → archive-tmrsG41w.mjs} +3 -3
  21. package/dist/{auth-_cdypXeX.mjs → auth-eogOlU4X.mjs} +5 -5
  22. package/dist/{branch-flag-CPTQ4wXp.mjs → branch-flag-BvIdNB5z.mjs} +1 -1
  23. package/dist/{branches-C7mNYAky.mjs → branches-CERgKqFq.mjs} +3 -3
  24. package/dist/{breaking-BindR3ZW.mjs → breaking-B8f_vl0i.mjs} +6 -6
  25. package/dist/{broken-SoXfkx7d.mjs → broken-mrkOdV4j.mjs} +8 -8
  26. package/dist/{bulk-discard-values-D_d5_1w5.mjs → bulk-discard-values-BQ3z6nJb.mjs} +5 -5
  27. package/dist/{bulk-edit-Cce05vb_.mjs → bulk-edit-CJxoxDGQ.mjs} +7 -7
  28. package/dist/{bulk-rescan-values-DIQN4VT_.mjs → bulk-rescan-values-DflzyxEj.mjs} +5 -5
  29. package/dist/{bulk-sync-schema-DLWf5dCl.mjs → bulk-sync-schema-CBIs4yHs.mjs} +5 -5
  30. package/dist/{cancel-rnd6UI-6.mjs → cancel-CyTRWSOr.mjs} +3 -3
  31. package/dist/{cancel-task-acq1GT6Z.mjs → cancel-task-BuZsQuLp.mjs} +2 -2
  32. package/dist/{card-QR3zEu6E.mjs → card-BJRZ5D5Q.mjs} +2 -2
  33. package/dist/card-DA1W3Axw.mjs +25 -0
  34. package/dist/{card-3NmUiU5F.mjs → card-VipF12MJ.mjs} +1 -1
  35. package/dist/{cards-DmzPtx81.mjs → cards-BEeFNiCt.mjs} +6 -6
  36. package/dist/cli.mjs +142 -292
  37. package/dist/{client-D-Aw6JLk.mjs → client-DvWllzKd.mjs} +401 -189
  38. package/dist/{collection-D5vt2IJ_.mjs → collection-Bf3lv-zo.mjs} +1 -1
  39. package/dist/collection-C98jgufi.mjs +21 -0
  40. package/dist/{collection-DNmFKALA.mjs → collection-Cz2qLX2m.mjs} +1 -1
  41. package/dist/{collection-namespace-Dl8_7xII.mjs → collection-namespace-C0JcSEwN.mjs} +2 -1
  42. package/dist/{content-translation-DxT1OMnP.mjs → content-translation-1coWME1P.mjs} +3 -3
  43. package/dist/{copy-BTdTiDQo.mjs → copy-DVe4rcLK.mjs} +6 -6
  44. package/dist/{copy-flags-CTM91pxH.mjs → copy-flags-DZ5Cy-Gk.mjs} +2 -2
  45. package/dist/{copy-CyFf3e2Y.mjs → copy-hE6SaHfb.mjs} +6 -6
  46. package/dist/{create-CuJJd7Rh.mjs → create-B3P62NaN.mjs} +4 -5
  47. package/dist/{create-BH6zpwKD.mjs → create-B3kz_fHg.mjs} +4 -5
  48. package/dist/{create-Dn8V5siS.mjs → create-BPRuU7Mv.mjs} +7 -8
  49. package/dist/{create-DeCPAYJi.mjs → create-BjH0i2BC.mjs} +9 -10
  50. package/dist/{create-Ch-NbeMr.mjs → create-Btwnm6D2.mjs} +9 -10
  51. package/dist/{create-Ce5YVusp.mjs → create-CCb2_Kcc.mjs} +6 -7
  52. package/dist/{create-DeiBnRRz.mjs → create-D271V9Cf.mjs} +9 -9
  53. package/dist/create-D6Xm2b2D.mjs +37 -0
  54. package/dist/{create-roJbQOmm.mjs → create-DCoo3sI2.mjs} +4 -4
  55. package/dist/{create-B6dABrs7.mjs → create-DEIIUohY2.mjs} +10 -11
  56. package/dist/{create-qtTB8rIE.mjs → create-DJg0Ohj5.mjs} +9 -10
  57. package/dist/{create-C3rabvE_.mjs → create-DR5tpMUA.mjs} +4 -5
  58. package/dist/{create-BwIOlpLc.mjs → create-DR8g_rEL.mjs} +6 -7
  59. package/dist/{create-GU8_GNbN.mjs → create-DXivfl69.mjs} +4 -5
  60. package/dist/create-DdfFgF-w.mjs +33 -0
  61. package/dist/{create-DsCydgm22.mjs → create-DkFCjBp3.mjs} +6 -7
  62. package/dist/{create-B3QZpifA.mjs → create-Dz4c8E33.mjs} +9 -10
  63. package/dist/{create-Bg3WTMMU.mjs → create-LpdQKxUt.mjs} +7 -7
  64. package/dist/{create-D6NLpIzV.mjs → create-XCzUuT42.mjs} +4 -5
  65. package/dist/{create-DiG165B2.mjs → create-XVjr8IDS.mjs} +6 -7
  66. package/dist/{create-branch-C4Ql9G2g.mjs → create-branch-D_nbKxDS.mjs} +3 -2
  67. package/dist/{csv-DmuyyLdz.mjs → csv-LP7sE-CS.mjs} +4 -4
  68. package/dist/{current-task-C2PPBVVv.mjs → current-task-DhgwtGN3.mjs} +3 -3
  69. package/dist/{dashboard-D5E3fMKh.mjs → dashboard-Bx2sYijR.mjs} +1 -1
  70. package/dist/dashboard-CR97_zXn.mjs +35 -0
  71. package/dist/{dashboard-Chjy5GKR.mjs → dashboard-Cwsf-e9T.mjs} +23 -6
  72. package/dist/data-action-C4jE-RLb.mjs +24 -0
  73. package/dist/{data-action-DZxVB_hD.mjs → data-action-CuvI17XF.mjs} +8 -2
  74. package/dist/{data-action-R9qxJDPA.mjs → data-action-DLj9cgpu.mjs} +1 -1
  75. package/dist/data-app-BUW-HlUo.mjs +52 -0
  76. package/dist/data-app-BlBvvRzC.mjs +41 -0
  77. package/dist/data-app-Blz61mG5.mjs +29 -0
  78. package/dist/data-app-project-DjDDWgUF.mjs +58 -0
  79. package/dist/{database-CxykzTAA.mjs → database-C5ucihp3.mjs} +2 -2
  80. package/dist/{database-D8XzgJEz.mjs → database-u_spHEa0.mjs} +1 -1
  81. package/dist/{dataset-DIQLAp2P.mjs → dataset-LJxNN6p5.mjs} +3 -3
  82. package/dist/{db-BgKKN1-4.mjs → db-D8mPRqZc.mjs} +8 -8
  83. package/dist/{delete-DiZg2wit.mjs → delete-8k7oYDFr.mjs} +4 -4
  84. package/dist/{delete-_XC6R-Jn.mjs → delete-B1SbEVIc.mjs} +4 -4
  85. package/dist/{delete-CS6hsBnd.mjs → delete-CaaPrNOf.mjs} +4 -4
  86. package/dist/{delete-5ZOvFr-7.mjs → delete-CqyBkMCd.mjs} +4 -4
  87. package/dist/{delete-DeqGTuwO.mjs → delete-CuyGTe0i.mjs} +4 -4
  88. package/dist/delete-DC-DEYSq.mjs +44 -0
  89. package/dist/{delete-B3aylPHD.mjs → delete-DWTqIlbr.mjs} +4 -4
  90. package/dist/delete-DaqzPLcG.mjs +40 -0
  91. package/dist/{delete-DtIbkF42.mjs → delete-DuaJ8PUI.mjs} +4 -4
  92. package/dist/{delete-CH32Ew4m.mjs → delete-ZjLpkwgx.mjs} +4 -4
  93. package/dist/delete-runtime-DOOy3gzH.mjs +73 -0
  94. package/dist/{delete-table-BEuMlqw1.mjs → delete-table-CnGv1gx9.mjs} +4 -4
  95. package/dist/{dependencies-DD48u9i3.mjs → dependencies-D6SNdLW8.mjs} +6 -6
  96. package/dist/dependency-Bbg9x1V9.mjs +15 -0
  97. package/dist/{dependency-CaC6XuTY.mjs → dependency-C1DExO6k.mjs} +8 -5
  98. package/dist/{dependency-Dxvb2Znu.mjs → dependency-DcM96XJ-.mjs} +4 -3
  99. package/dist/{dependents-ByfHs8pO.mjs → dependents-BizQb14Q.mjs} +9 -9
  100. package/dist/{dirty-DsrGfLit.mjs → dirty-BHwdHOEj.mjs} +3 -3
  101. package/dist/{discard-values-BfpwwC62.mjs → discard-values-BWlh2ce8.mjs} +5 -5
  102. package/dist/document-B6254tue.mjs +20 -0
  103. package/dist/{document-UMG6me2P.mjs → document-BHOQq2T_.mjs} +2 -2
  104. package/dist/{document-DVIu3Vj4.mjs → document-lS1XZEF3.mjs} +1 -1
  105. package/dist/{download-kpgGtfpw.mjs → download-D2wEAgi_.mjs} +2 -2
  106. package/dist/{eid-Tp-ACZ4O.mjs → eid-KtWIPUCC.mjs} +5 -5
  107. package/dist/{entity-DjDkaMkB.mjs → entity-DICgy4Bl.mjs} +2 -2
  108. package/dist/{entry-flags-nhBrUS6p.mjs → entry-flags-DLS6r8Vf.mjs} +5 -6
  109. package/dist/{events-Dm9-ofXu.mjs → events-B8yMqrrD.mjs} +4 -4
  110. package/dist/{execute-BL0dAlrp.mjs → execute-Bebvab7D.mjs} +7 -8
  111. package/dist/{export-CIW-dHft.mjs → export-BNj3RvAl.mjs} +6 -5
  112. package/dist/{export-flags-CpukgETM.mjs → export-flags-iXfEILf7.mjs} +3 -2
  113. package/dist/{export-preflight-BMlz3Hp9.mjs → export-preflight-F3OapAD5.mjs} +3 -3
  114. package/dist/{field-DoWQUl7V.mjs → field-C2qOViFZ.mjs} +8 -2
  115. package/dist/{field-BVz4AS3f.mjs → field-CT4r6b92.mjs} +1 -1
  116. package/dist/field-DQAcCjXr.mjs +21 -0
  117. package/dist/{fields-CcerFaiu.mjs → fields-BkAS_wIA.mjs} +6 -6
  118. package/dist/{filter-flags-DhE4olyf.mjs → filter-flags-ezqqsOQL.mjs} +4 -4
  119. package/dist/{fks-CYfBesHV.mjs → fks--SaqvPw1.mjs} +6 -6
  120. package/dist/{get-DKbGKa6F.mjs → get-B1S1kW2N.mjs} +5 -5
  121. package/dist/{get-Be7yjY9v.mjs → get-B1uiZyyi.mjs} +5 -5
  122. package/dist/{get-BaiyMem3.mjs → get-B2nBYYCw.mjs} +4 -4
  123. package/dist/{get-CmxYFUuR.mjs → get-BSKpQJVB.mjs} +5 -5
  124. package/dist/{get-BFloqjrN.mjs → get-BjgB5l7X.mjs} +5 -5
  125. package/dist/{get-RCDF-hEq.mjs → get-Boql3_0n.mjs} +5 -5
  126. package/dist/{get-CEJE9Rmp.mjs → get-BseHija1.mjs} +5 -5
  127. package/dist/{get-eNRULmE5.mjs → get-C9VumVZ3.mjs} +6 -5
  128. package/dist/{get--E7UvR-v.mjs → get-CGPxAoow.mjs} +3 -3
  129. package/dist/{get-HzDQViDP2.mjs → get-CM9IOr82.mjs} +3 -3
  130. package/dist/{get-CRfrkxqc.mjs → get-CTTKQDr7.mjs} +5 -5
  131. package/dist/{get-DHSuVxrH.mjs → get-CU1zKfBS.mjs} +5 -5
  132. package/dist/{get-Dp45rXRS.mjs → get-CWDz6eV2.mjs} +3 -3
  133. package/dist/{get-p2sZvp7v.mjs → get-C_hatP_z.mjs} +3 -3
  134. package/dist/get-Cdcj1c9U.mjs +28 -0
  135. package/dist/{get-BLnQ2aut.mjs → get-CiLYhFjU.mjs} +5 -5
  136. package/dist/{get-LRTRu85E.mjs → get-De6qpuzO.mjs} +5 -5
  137. package/dist/{get-CZgAr8rM.mjs → get-VpGfaL7c.mjs} +5 -5
  138. package/dist/{get-BvEHWLGL.mjs → get-f2km9wsq.mjs} +3 -3
  139. package/dist/{get-Cj3-w_Nu.mjs → get-gGSl7Se4.mjs} +5 -5
  140. package/dist/{get-B0RQPTV-2.mjs → get-iOs1ci6x.mjs} +5 -5
  141. package/dist/{get-run-C3AHKn3-.mjs → get-run-CHup8cMl.mjs} +5 -5
  142. package/dist/{get-DRhcS76J.mjs → get-uHs9Ve-4.mjs} +5 -5
  143. package/dist/git-sync-Czena0TW.mjs +29 -0
  144. package/dist/glossary-BVxRpD7o.mjs +14 -0
  145. package/dist/{glossary-BkGmCI4X.mjs → glossary-CQRW3Z4d.mjs} +1 -1
  146. package/dist/{glossary-QN-hHfOT.mjs → glossary-iPIWrEbP.mjs} +1 -1
  147. package/dist/{graph-BGOYocJc.mjs → graph-B7SlzpkC.mjs} +6 -6
  148. package/dist/{group-81zoQcEP.mjs → group--6adAsqG.mjs} +2 -2
  149. package/dist/{has-remote-changes-DxsRvZsw.mjs → has-remote-changes-DZq7q2Wk.mjs} +2 -2
  150. package/dist/{import-izn2_OsW.mjs → import-DKZF0kMU.mjs} +6 -5
  151. package/dist/input-gate-registry-Dh8QjKgW.mjs +14 -0
  152. package/dist/{is-dirty-DHYYRl6Y.mjs → is-dirty-haRLODo1.mjs} +2 -2
  153. package/dist/{items-mIoZBqUc.mjs → items-BGWE5hLl.mjs} +7 -6
  154. package/dist/{key-BYEv4Wgq.mjs → key-DLrpCgdZ.mjs} +1 -1
  155. package/dist/{library-wUiD2jbw.mjs → library-BprxuqZl.mjs} +1 -1
  156. package/dist/{library-DKIqzyUk.mjs → library-C4g9pdiS.mjs} +8 -3
  157. package/dist/{library-5I0TlXmf.mjs → library-e3aiJ1Yb.mjs} +5 -5
  158. package/dist/{list-BKhJfFyB.mjs → list-B9yhUzCc.mjs} +5 -5
  159. package/dist/{list-CpxEOoqw.mjs → list-BNel4hYs.mjs} +6 -6
  160. package/dist/{list-D8jiz8YP.mjs → list-Bbc8VYDi.mjs} +3 -3
  161. package/dist/{list-CT7AOXTS.mjs → list-BiIZdUoN.mjs} +5 -5
  162. package/dist/list-C-AnwSRP.mjs +33 -0
  163. package/dist/list-CNiCM1NL.mjs +29 -0
  164. package/dist/{list-DV4wojtv.mjs → list-COPGHkWL.mjs} +5 -5
  165. package/dist/{list-CTlIVHeG.mjs → list-CsXdnNyp.mjs} +5 -5
  166. package/dist/{list-DVnPzc5m.mjs → list-D0ASRA_6.mjs} +6 -6
  167. package/dist/{list-B6TYzXyW.mjs → list-DAa4EWlv.mjs} +5 -5
  168. package/dist/{list-CmOTwt_z.mjs → list-DDt1iLXj.mjs} +4 -4
  169. package/dist/{list-C6roDyBd.mjs → list-DFuydMdV.mjs} +6 -6
  170. package/dist/{list-C1dEh2yq.mjs → list-DFxfpa4W.mjs} +6 -6
  171. package/dist/{list-OWPg67JC.mjs → list-DJyIkrY1.mjs} +5 -5
  172. package/dist/{list-CuEPFHrk.mjs → list-DUCxqhtn.mjs} +5 -5
  173. package/dist/{list-B8FPpgct.mjs → list-D_8GeDG-.mjs} +3 -3
  174. package/dist/{list-NeImhwLD.mjs → list-DcXqKZyg.mjs} +5 -5
  175. package/dist/{list-C3CXHKTW.mjs → list-DednUvr3.mjs} +3 -3
  176. package/dist/{list-O_nmgwSj.mjs → list-Dvf1iB8r.mjs} +3 -3
  177. package/dist/{list-jT5ndEGr.mjs → list-HI7Mpoy-.mjs} +5 -5
  178. package/dist/{list-BEBAq1xc.mjs → list-OUSeeuXb.mjs} +5 -5
  179. package/dist/{list-BxIulkp3.mjs → list-P7FnSsbZ.mjs} +6 -6
  180. package/dist/{list-C-f-G-cC.mjs → list-gcDxZ7Th.mjs} +5 -5
  181. package/dist/{login-DOW1mr40.mjs → login-APHXId1p.mjs} +6 -5
  182. package/dist/{logout-BQ8wywf7.mjs → logout-V0pxj6AM.mjs} +3 -2
  183. package/dist/{measure-CtfjqJFP.mjs → measure-BMWrX1ca.mjs} +3 -1
  184. package/dist/{measure-Bz-bGn-Z.mjs → measure-DnGWkKyx.mjs} +6 -6
  185. package/dist/{measure-4P0bOlZ3.mjs → measure-DqZG65of.mjs} +1 -1
  186. package/dist/{notification-DaA4bBHW.mjs → notification-B5yMuPmo.mjs} +6 -1
  187. package/dist/{notification-W2SVssyY.mjs → notification-BEJaEZS2.mjs} +2 -2
  188. package/dist/{parameter-DvRQMKd2.mjs → parameter-Bucz3cLJ.mjs} +1 -1
  189. package/dist/{parameter-values-B7rr6fcy.mjs → parameter-values-DfZSEK3O.mjs} +6 -5
  190. package/dist/{parse-enum-DAcTzG1q.mjs → parse-enum-CEXPuN9H.mjs} +1 -1
  191. package/dist/{parse-id-5BZGQ9Vd.mjs → parse-id-EXZan40V.mjs} +1 -1
  192. package/dist/{parse-ref-ByXc-pls.mjs → parse-ref-D28NvwpR.mjs} +1 -1
  193. package/dist/{parse-text-FgX60vIg.mjs → parse-text-C4LYzIFa.mjs} +1 -1
  194. package/dist/{path-CHQguAPa.mjs → path-Bni-pnKS.mjs} +4 -4
  195. package/dist/{poll-CgtcJopL.mjs → poll-vxN__4cz.mjs} +2 -2
  196. package/dist/predicates-0C0TsWcJ.mjs +219 -0
  197. package/dist/{preflight-ChTKhB8Y.mjs → preflight-4iy2qzsh.mjs} +3 -2
  198. package/dist/{process-aqXm5QgC.mjs → process-BQC3mK2k.mjs} +1 -1
  199. package/dist/{publish-CaoFOucS.mjs → publish-DkYn86ak.mjs} +5 -4
  200. package/dist/{pulse-CC81N2_0.mjs → pulse-B2AmIWsO.mjs} +11 -4
  201. package/dist/{pulse-JPrq6NFP.mjs → pulse-CPSpum5U.mjs} +2 -2
  202. package/dist/{query-0u1WR1bV.mjs → query-CZbI4Rlv.mjs} +17 -29
  203. package/dist/{query-CzDBwAZD.mjs → query-gHeYfWj_.mjs} +7 -6
  204. package/dist/{remapping-CodmT28F.mjs → remapping-hcWkFQsx.mjs} +5 -5
  205. package/dist/{remove-collection-DfUsf6Dd.mjs → remove-collection-Qne5ww6M.mjs} +3 -3
  206. package/dist/{render-DP6UoZNT.mjs → render-d1-sCPn_.mjs} +3 -2
  207. package/dist/{replace-qWiUOgmn.mjs → replace-DoU0IFGd.mjs} +4 -4
  208. package/dist/{requirement-check-sfmyH2PH.mjs → requirement-check-BoVSK1uK.mjs} +2 -2
  209. package/dist/{rescan-values-BuDmeda3.mjs → rescan-values-COzM86P5.mjs} +5 -5
  210. package/dist/{rescan-values-BcrhdSj_.mjs → rescan-values-s82n_You.mjs} +5 -5
  211. package/dist/{run-BG_5lR0x.mjs → run-Bwj4Uuxk.mjs} +4 -4
  212. package/dist/{run-BBRnFayr.mjs → run-JYNbEO_V.mjs} +6 -5
  213. package/dist/{run-DXXD-CnS.mjs → run-Q7fHux1F.mjs} +5 -4
  214. package/dist/{runs-CovgMSwy.mjs → runs-GQD6FO2k.mjs} +6 -6
  215. package/dist/{runtime-Cmes70jO.mjs → runtime-iIsqiOwO.mjs} +51 -9
  216. package/dist/scaffold-CJo9l-2p.mjs +50 -0
  217. package/dist/schema-2JhmK8FM.mjs +283 -0
  218. package/dist/{schema-tables-CjKl8jr5.mjs → schema-tables-CarGLayY.mjs} +6 -6
  219. package/dist/{schemas-CAGvBAD_.mjs → schemas-oSjtuQ4G.mjs} +4 -4
  220. package/dist/{search-DFU6FHRd.mjs → search-Ca0sIr28.mjs} +7 -6
  221. package/dist/{search-OpYS2Ub4.mjs → search-CvGEL2h-.mjs} +6 -5
  222. package/dist/{segment-D-SKpFzF.mjs → segment-BJCEJwNY.mjs} +6 -6
  223. package/dist/{segment-C_gl5ag6.mjs → segment-CWVASaM1.mjs} +1 -1
  224. package/dist/{segment-OsrbUDzw.mjs → segment-Cf-DrdjE.mjs} +3 -1
  225. package/dist/{send-DNu83_3E.mjs → send-B4nZI6CU.mjs} +3 -3
  226. package/dist/{set-CGYZcLVo.mjs → set-BjMZCdN8.mjs} +5 -6
  227. package/dist/{set-active-32IaLx1O.mjs → set-active-CWDdv3kN.mjs} +4 -4
  228. package/dist/set-bundle-C2uk4jdt.mjs +40 -0
  229. package/dist/{set-data-actions-DRhgAfhE.mjs → set-data-actions-lWl05ekf.mjs} +6 -6
  230. package/dist/set-enabled-CPhw1AX4.mjs +41 -0
  231. package/dist/{set-sensitivity-D_i3XEe8.mjs → set-sensitivity-B7WI5BJC.mjs} +6 -6
  232. package/dist/{setting-BBn9Lv9J.mjs → setting-BvPpFjJT.mjs} +4 -4
  233. package/dist/{setup-CO7QV-ao.mjs → setup-BnczkG96.mjs} +1 -1
  234. package/dist/{setup-8d7Uplhy.mjs → setup-DPRa8Uxm.mjs} +5 -6
  235. package/dist/{signal-D8MH3LMN.mjs → signal-BNUlQjpc.mjs} +1 -1
  236. package/dist/{skill-list-D2FhhYS3.mjs → skill-list-BoJITnw8.mjs} +1 -1
  237. package/dist/{skills-BeIDC-_H.mjs → skills-BCivFuuZ.mjs} +3 -3
  238. package/dist/{skills-BZGd_9d3.mjs → skills-BY3MCR8i.mjs} +3 -2
  239. package/dist/slug-COdwq63O.mjs +15 -0
  240. package/dist/snippet-BcN1UwoS.mjs +19 -0
  241. package/dist/{stash-Dyc1xZ8u.mjs → stash-leoW1fby.mjs} +5 -4
  242. package/dist/{status-CQZwl1PX.mjs → status-AA4APlwZ.mjs} +3 -3
  243. package/dist/{status-lCN8unWe.mjs → status-BezD_K-g.mjs} +4 -4
  244. package/dist/{subscription-CEFxmtGH.mjs → subscription-BDukZFlC.mjs} +6 -6
  245. package/dist/{subscriptions-AU14uznL.mjs → subscriptions-CU4E_p7B.mjs} +6 -6
  246. package/dist/{summary-Do9IBOQM.mjs → summary-BFspP71W.mjs} +5 -5
  247. package/dist/{sync-schema-_gw9RQyF.mjs → sync-schema-BhRhsm8Y.mjs} +6 -6
  248. package/dist/{sync-schema-B3nyoKZl.mjs → sync-schema-BtjC-PNY.mjs} +5 -5
  249. package/dist/{sync-task-DNKG9Bk4.mjs → sync-task-Gf86CU9l.mjs} +1 -1
  250. package/dist/{table-BDsqRU7a.mjs → table-BNA9Or05.mjs} +3 -3
  251. package/dist/table-Dzp1uANs.mjs +26 -0
  252. package/dist/{table-CF7zW9cI.mjs → table-hgrX1i-R.mjs} +36 -5
  253. package/dist/{table-selector-flags-CHfrVpYQ.mjs → table-selector-flags-BpFFoSaT.mjs} +4 -3
  254. package/dist/{text-C2VwKF9D.mjs → text-8nOxfC17.mjs} +1 -1
  255. package/dist/timeline-Cr5d4Oer.mjs +21 -0
  256. package/dist/{timeline-event-B-nGnoVL.mjs → timeline-event-BiYDbW3r.mjs} +6 -6
  257. package/dist/{transform-DiW7bREn.mjs → transform-B62craFM.mjs} +76 -7
  258. package/dist/transform-C-0nV5Et.mjs +28 -0
  259. package/dist/{transform-CWGJB8N_.mjs → transform-DKPtUV54.mjs} +2 -2
  260. package/dist/transform-job-B9MPk7S5.mjs +22 -0
  261. package/dist/{transform-job-ClX8e5iO.mjs → transform-job-CDIb3B9o.mjs} +6 -1
  262. package/dist/{transform-job-BBZ5L46v.mjs → transform-job-D18XRLBK.mjs} +1 -1
  263. package/dist/{transform-tag-dKCqQPTd.mjs → transform-tag-DQXOPhmt.mjs} +5 -5
  264. package/dist/transform-test-wmAjyaka.mjs +16 -0
  265. package/dist/{transforms-DRAws1f4.mjs → transforms-DcXL4Dox.mjs} +6 -6
  266. package/dist/{transport-Ck4anhoc.mjs → transport-DZSxmmtY.mjs} +5 -4
  267. package/dist/{tree-Ddsj298M.mjs → tree-DrYYCdXP.mjs} +4 -3
  268. package/dist/typed-schema-DEzmTY3J.mjs +45 -0
  269. package/dist/{unpublish-BMfjQ8UV.mjs → unpublish-D8hmulCx.mjs} +3 -3
  270. package/dist/{unreferenced-DRQrvH5r.mjs → unreferenced-C3CqO3tu.mjs} +6 -6
  271. package/dist/{update-C1VwtJWu.mjs → update-3lRE8G9k.mjs} +10 -11
  272. package/dist/{update-BCC1Igr2.mjs → update-4TDRNrV-.mjs} +7 -8
  273. package/dist/{update-0AoF6QJ_.mjs → update-4sOfpwpv.mjs} +5 -6
  274. package/dist/{update-BQLTaw3z.mjs → update-552UGPAG.mjs} +7 -8
  275. package/dist/{update-DpP1EEC0.mjs → update-7iXRB23T2.mjs} +7 -8
  276. package/dist/{update-Bpsdi6kM.mjs → update-B21GAnzT.mjs} +7 -8
  277. package/dist/{update-6sfqiInZ.mjs → update-BVz0oIwS.mjs} +10 -11
  278. package/dist/{update-CGpiLu7N.mjs → update-BxRFktpB.mjs} +8 -9
  279. package/dist/{update-CnxzmPRG.mjs → update-CEDMvgJI.mjs} +11 -12
  280. package/dist/{update-BIL0JPqm.mjs → update-CcNYCalv.mjs} +5 -6
  281. package/dist/{update-DlFHoUxe.mjs → update-CctMr2T1.mjs} +8 -8
  282. package/dist/{update-DrT10fxQ.mjs → update-Ch6AAOeh.mjs} +5 -6
  283. package/dist/{update-_hHnM1Hx.mjs → update-CtuLA42g.mjs} +11 -11
  284. package/dist/{update-C19B1Sry.mjs → update-DVD77mVR.mjs} +5 -6
  285. package/dist/{update-C1x90FSH.mjs → update-DmMHHn75.mjs} +10 -11
  286. package/dist/{update-D1aKeWfo.mjs → update-DvGIaZdl.mjs} +5 -6
  287. package/dist/{update-BtfUX9CN.mjs → update-LlJM4tUd.mjs} +10 -11
  288. package/dist/{update-8eXJso_l.mjs → update-Zv7PP8nl.mjs} +7 -8
  289. package/dist/{update-dashcard-CDI0Tnxy.mjs → update-dashcard-BijqiYQd.mjs} +7 -8
  290. package/dist/{update-pXVhyiFo.mjs → update-l4WRN6Jh.mjs} +7 -8
  291. package/dist/update-sma1ZRm2.mjs +36 -0
  292. package/dist/{upgrade-Cfouv1Ii.mjs → upgrade-TzILOkiM.mjs} +5 -4
  293. package/dist/upload-B37bUVl_.mjs +13 -0
  294. package/dist/{upload-DnLXgUA-.mjs → upload-CdiDKe69.mjs} +2 -2
  295. package/dist/{upload-DIEcI5iM.mjs → upload-Do-kOGtc.mjs} +3 -3
  296. package/dist/{uuid-D0sH5txV.mjs → uuid-Dqq2WfAT.mjs} +3 -2
  297. package/dist/{validate-BQ7vI8_x.mjs → validate-moGYlHxB.mjs} +8 -18
  298. package/dist/validate-query-Bf5FhhMI.mjs +72 -0
  299. package/dist/{values-BFPO6y5o.mjs → values-CtY5zv0m.mjs} +5 -5
  300. package/dist/{verify-C-StvB4f.mjs → verify-BiB8jzXB.mjs} +3 -3
  301. package/dist/{verify-CuGs0X3C.mjs → verify-DBTlWx-S.mjs} +3 -3
  302. package/dist/{verify-7fW020r_.mjs → verify-DJ7fGyLL.mjs} +4 -3
  303. package/dist/{wait-Cvt8xO0h.mjs → wait-DEYagfq4.mjs} +5 -5
  304. package/dist/{wait-flags-BnZ-dfE2.mjs → wait-flags-C3cu_O0w.mjs} +2 -2
  305. package/dist/{window-CfK_j6m1.mjs → window-D1w7B0vh.mjs} +1 -1
  306. package/package.json +2 -2
  307. package/skill-data/core/SKILL.md +14 -13
  308. package/skill-data/data-action/SKILL.md +1 -1
  309. package/skill-data/data-app/SKILL.md +138 -0
  310. package/skill-data/data-app/template/.env.local.example +2 -0
  311. package/skill-data/data-app/template/LICENSE +21 -0
  312. package/skill-data/data-app/template/README.md +29 -0
  313. package/skill-data/data-app/template/gitignore +5 -0
  314. package/skill-data/data-app/template/package.json +25 -0
  315. package/skill-data/data-app/template/src/App.tsx +9 -0
  316. package/skill-data/data-app/template/src/index.tsx +14 -0
  317. package/skill-data/data-app/template/src/theme.ts +25 -0
  318. package/skill-data/data-app/template/tsconfig.json +25 -0
  319. package/skill-data/data-app/template/vite.config.ts +3 -0
  320. package/skill-data/data-app-actions/SKILL.md +96 -0
  321. package/skill-data/data-app-data/SKILL.md +158 -0
  322. package/skill-data/data-app-filters/SKILL.md +87 -0
  323. package/skill-data/data-app-ui/SKILL.md +120 -0
  324. package/skill-data/mbql/SKILL.md +3 -3
  325. package/skill-data/native-sql/SKILL.md +1 -1
  326. package/skill-data/native-sql/references/template-tags.md +1 -1
  327. package/dist/body-C0VnEiRV.mjs +0 -16
  328. package/dist/card-BnL92zoT.mjs +0 -25
  329. package/dist/collection-DRHGwFSL.mjs +0 -21
  330. package/dist/dashboard-Bq1pae4N.mjs +0 -35
  331. package/dist/data-action-BZ2PvlaP.mjs +0 -24
  332. package/dist/delete-runtime-DRGveT8H.mjs +0 -55
  333. package/dist/dependency-6kfpR8Ga.mjs +0 -15
  334. package/dist/document-C0mZEg5w.mjs +0 -20
  335. package/dist/field-BUkyWiWk.mjs +0 -21
  336. package/dist/git-sync-CNTB5jvI.mjs +0 -29
  337. package/dist/glossary-B9Eo4hXc.mjs +0 -14
  338. package/dist/snippet-CpZ5cJ_y.mjs +0 -19
  339. package/dist/table-Cb_Uvw8m.mjs +0 -26
  340. package/dist/timeline-x0s_3sfc.mjs +0 -21
  341. package/dist/transform-job-Dlx2c6II.mjs +0 -22
  342. package/dist/transform-kP11Mv9_.mjs +0 -28
  343. package/dist/transform-test-jFuzVhlT.mjs +0 -16
  344. package/dist/upload-Cqv-In6j.mjs +0 -13
  345. package/dist/validate-query-YtH3d5BP.mjs +0 -38
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "jsx": "react-jsx",
8
+ "esModuleInterop": true,
9
+ "resolveJsonModule": true,
10
+ "isolatedModules": true,
11
+ "skipLibCheck": true,
12
+ "noEmit": true,
13
+ "strict": true,
14
+ "allowJs": false,
15
+ "checkJs": false,
16
+ "noImplicitAny": true,
17
+ "strictNullChecks": true,
18
+ "forceConsistentCasingInFileNames": true,
19
+ "noUnusedLocals": false,
20
+ "noUnusedParameters": false,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "types": ["vite/client"]
23
+ },
24
+ "include": ["src", "vite.config.ts"]
25
+ }
@@ -0,0 +1,3 @@
1
+ import { dataAppConfig } from "@metabase/embedding-sdk-react/data-app-dev/config";
2
+
3
+ export default dataAppConfig();
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: data-app-actions
3
+ description: Make a Metabase data app write data — an action in the app's collection, its typed definition in `src/metabase.ts`, and the `useAction` hook from `@metabase/embedding-sdk-react/data-app` behind a form or a button, with inputs typed from each parameter, the server's error shown as it comes, and every affected query refreshed. Triggers — "a form that saves a row", "a button that updates a record", "delete an entry from the app", "submit doesn't refresh the list", "the action fails in the app". Creating the action itself is `data-action`; reads are `data-app-data`.
4
+ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
5
+ requires: [dataApps]
6
+ ---
7
+
8
+ # Writes in a data app
9
+
10
+ An **action** is a saved, parameterized SQL write — `INSERT`, `UPDATE` or `DELETE`. A data app runs only the actions in **the app's collection** (`data-app`, _Create the app_).
11
+
12
+ 1. Draft the action as `data-action` describes — the database must allow actions — with `collection_id` the app's collection. Show the user its SQL and parameters, and create it once they agree.
13
+ 2. Regenerate `src/metabase.ts` (`data-app-data`).
14
+ 3. Pass `actions.<name>` from it to `useAction`.
15
+
16
+ Each action there lists its parameters: the parameter slug (the key `execute` takes), `displayName` (a label, never a key), `jsType` (`string`, `number`, `Date`, `boolean` or `unknown`) and `required`. Never fake a call to an action `src/metabase.ts` doesn't list.
17
+
18
+ ## The hook
19
+
20
+ ```tsx
21
+ import { useAction } from "@metabase/embedding-sdk-react/data-app";
22
+ import { actions } from "../metabase";
23
+
24
+ const { execute, isExecuting, result, error, reset } = useAction(actions.setOrderNote);
25
+ ```
26
+
27
+ - **Import it from `/data-app`.** The main `@metabase/embedding-sdk-react` entry exports the general SDK `useAction`, which takes raw ids and isn't typed from the definition. Write no generics: the definition types `execute`'s keys and values.
28
+ - `execute(params)` resolves to the response body; on failure it both throws and sets `error`. It resolves to `null` without sending anything while the SDK is still starting, so guard a call that can happen at mount. It never runs on its own and has no `enabled`: return early in the handler to skip it.
29
+ - `isExecuting` is true until it settles: disable the trigger and show progress, so a double click can't send twice.
30
+ - `result` is the last response, `null` before the first call; `result?.["rows-affected"]` is a quick confirmation. `reset()` clears `result` and `error`, for example when the user dismisses a message.
31
+
32
+ ## A form
33
+
34
+ ```tsx
35
+ export function NoteForm({ orderId, onSaved }: { orderId: number; onSaved: () => Promise<void> }) {
36
+ const { execute, isExecuting, error } = useAction(actions.setOrderNote);
37
+ const [note, setNote] = useState("");
38
+ const [isValid, setIsValid] = useState(false);
39
+
40
+ async function onSubmit(event: FormEvent<HTMLFormElement>) {
41
+ event.preventDefault();
42
+ try {
43
+ await execute({ order_id: orderId, note }); // keys are parameter slugs
44
+ await onSaved(); // the parent refetches what the write changed
45
+ setNote("");
46
+ } catch {
47
+ // shown from `error`
48
+ }
49
+ }
50
+
51
+ return (
52
+ <form onSubmit={onSubmit} onChange={(event) => setIsValid(event.currentTarget.checkValidity())}>
53
+ <input type="text" required value={note} onChange={(event) => setNote(event.target.value)} />
54
+ <button type="submit" disabled={isExecuting || !isValid}>
55
+ {isExecuting ? "Saving…" : "Save"}
56
+ </button>
57
+ {error ? (
58
+ <pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
59
+ {error.data.message ?? "Action failed."}
60
+ </pre>
61
+ ) : null}
62
+ </form>
63
+ );
64
+ }
65
+ ```
66
+
67
+ **Inputs.** Pick each input's type from `jsType`, never from the parameter slug — one called `email` with `jsType: "string"` is still `type="text"`.
68
+
69
+ | `jsType` | Input |
70
+ | --------- | ---------------------------------------------------------- |
71
+ | `string` | `type="text"` |
72
+ | `number` | `type="number"`, read with `valueAsNumber` |
73
+ | `boolean` | a checkbox or switch |
74
+ | `Date` | `type="date"`, or `datetime-local` when a time is expected |
75
+ | `unknown` | `type="text"`, converted at the call |
76
+
77
+ **Validation** is `required` on each required parameter and nothing else — no length, range or format checks of your own. Disable submit from the browser's verdict, `form.checkValidity()`, never from hand-rolled checks. Anything stricter comes back from the server.
78
+
79
+ **Errors.** `error` is `{ status?, data: { message?, errors? }, isCancelled }` — read it without a cast. A failing SQL action comes back as a 500 whose `data.message` wraps the driver's text (`Error executing Action: …`): show it as it comes, in a `white-space: pre-wrap` element, since driver messages span lines — never "Something went wrong", `String(error)` or a friendlier paraphrase. `error.data.errors?.[parameterSlug]`, when present, goes beside its field (`aria-invalid` on the input).
80
+
81
+ ## Refresh
82
+
83
+ After a write succeeds, refetch every query on screen it could have changed: `await Promise.all([orders.refetch(), totals.refetch()])`, the `refetch` that `useMetabaseQuery` returns.
84
+
85
+ - Those queries live in a component above the trigger, which passes the refresh down; a query in the trigger's own component or below it can't be wired to it.
86
+ - Await the refresh before clearing the form or closing the modal, so stale rows never flash.
87
+ - Never prepend `result` to a local list instead: ids, timestamps and computed columns come from the server.
88
+
89
+ ## When it doesn't work
90
+
91
+ 1. Log `result`, `error` and the object passed to `execute`: every key is a parameter slug, every value matches its `jsType`.
92
+ 2. `execute` resolved to `null` and nothing was sent: it ran before the SDK started.
93
+ 3. The screen doesn't update: see _Refresh_.
94
+ 4. The action is missing from `src/metabase.ts`: it isn't in the app's collection, or the file is stale — regenerate it.
95
+
96
+ Then build and publish (`data-app`, _Build and publish_).
@@ -0,0 +1,158 @@
1
+ ---
2
+ name: data-app-data
3
+ description: Give a Metabase data app its data — questions in the app's collection over the Library's published tables and metrics, the typed `src/metabase.ts` that `mb data-app typed-schema` generates, and the `useMetabaseQuery` and `useMetabaseQueryObject` hooks that run them, with runtime filter clauses, sorting and aggregation. Triggers — "show revenue by month in my data app", "list customers in the app", "add a KPI", "a chart of orders by category", "regenerate metabase.ts". Creating and publishing the app is `data-app`; filter controls are `data-app-filters`; writes are `data-app-actions`; chart choice and sizing are `data-app-ui`.
4
+ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
5
+ requires: [dataApps]
6
+ ---
7
+
8
+ # Data in a data app
9
+
10
+ A data app runs only the questions and actions in **the app's collection** (`data-app`, _Create the app_); its viewers can read nothing else. So every piece of data starts there:
11
+
12
+ 1. Create a question (below) or an action (`data-app-actions`) in the app's collection.
13
+ 2. Regenerate `src/metabase.ts`, the typed list of everything in the collection.
14
+ 3. Pass `queries.<name>` or `actions.<name>` from it to a hook.
15
+
16
+ ## Sources: the semantic layer only
17
+
18
+ An app reads only what the user curated in the Library. These are hard rules:
19
+
20
+ - **Tables published to the Library**, and nothing else: `mb table list --published-only --json`. That covers the question's source table, every join, and every table reached through a foreign key column.
21
+ - **Metrics in the Library's Metrics collection** (its id is in `mb library get`; list it with `mb collection items <id> --json`), and the segments and measures of published tables (`mb segment list`, `mb measure list`). Prefer one that matches the concept over rebuilding it with filters and thresholds of your own.
22
+ - **Other questions in the app's collection**, as a source. Never a question, model or metric from anywhere else.
23
+ - **MBQL only**, never native SQL.
24
+
25
+ When the app needs a table or metric the Library lacks, stop: name what's missing and ask the user to publish the table or move the metric into the Library. Don't publish it yourself unless the user says to, and never work around the gap with an unpublished table, a copy of the data, or SQL.
26
+
27
+ ## Questions
28
+
29
+ Write each question's query in MBQL (the `mbql` skill), finding ids with `mb table list --published-only --term <name>` and `mb table get <id> --include fields --json`. Write the `dataset_query` object to `query.json`, dry-run it, then create the question in the app's collection:
30
+
31
+ ```bash
32
+ mb query --dry-run --file query.json
33
+ mb card create --json --body '{
34
+ "name": "Revenue by month",
35
+ "type": "question",
36
+ "display": "line",
37
+ "collection_id": 42,
38
+ "visualization_settings": {},
39
+ "dataset_query": { … }
40
+ }'
41
+ mb card query <id> --json --max-bytes 0 | jq '{row_count, first: .data.rows[0]}'
42
+ ```
43
+
44
+ Plan the questions from what the app shows and picks from — each chart, KPI, table and filter option list needs one, and several panels may share one. Each takes one of two shapes:
45
+
46
+ - **Aggregated**, when the breakdown is fixed: revenue by month, top products. Runtime clauses can then name only the columns it returns, its breakouts and aggregations.
47
+ - **Rows**, when a runtime filter targets a column an aggregation would drop — revenue by month, filtered by product category. The question returns the raw columns (`CREATED_AT`, `TOTAL`, `CATEGORY`), and the hook filters and aggregates them at runtime (_Runtime clauses_).
48
+
49
+ Also:
50
+
51
+ - A question filtered at runtime has no `limit` of its own: it would apply before the filters. Put the limit in the hook. A question nothing filters, such as "the 20 latest reviews", keeps its sort and limit.
52
+ - The name becomes the key in `queries`, camelCased: `Revenue by month` → `queries.revenueByMonth`.
53
+ - `display` and `visualization_settings` show only in Metabase; the app picks its own chart type.
54
+ - Nothing in the app's collection is archived, public or embedded. To drop a question, move it out (`mb card update <id> --body '{"collection_id":<other>}'`), then archive it.
55
+
56
+ ## `src/metabase.ts`
57
+
58
+ ```bash
59
+ mb data-app typed-schema <slug> --out src/metabase.ts
60
+ ```
61
+
62
+ Run it after **every** change to the app's collection, then `npm run typecheck`, which catches code still using a renamed or removed column. Never edit the file. It names every question and action by entity ID, so it works unchanged on every instance the app is synced to.
63
+
64
+ ```ts
65
+ export const queries = {
66
+ revenueByMonth: defineQuery({
67
+ source: {
68
+ type: "card",
69
+ entityId: "…",
70
+ name: "Revenue by month",
71
+ columns: [
72
+ { type: "column", name: "CREATED_AT", displayName: "Created At: Month", jsType: "Date", semanticType: "type/CreationTimestamp" },
73
+ { type: "column", name: "sum", displayName: "Sum of Total", jsType: "number", semanticType: "type/Currency" },
74
+ ],
75
+ },
76
+ }),
77
+ };
78
+ export const actions = { setOrderNote: defineAction({ action: { id: "…", parameters: [ … ] } }) };
79
+ ```
80
+
81
+ A column's `name` keys the rows and names the column in runtime clauses. Take it from this file, never from SQL:
82
+
83
+ - A column reached through a foreign key keeps its own name (`CATEGORY`, not `PRODUCTS__via__PRODUCT_ID__CATEGORY`), and a named aggregation keeps its name.
84
+ - A foreign key that Metabase displays as a name returns that name as an extra column: `USER_ID` comes with a `NAME` column whose `displayName` is "User ID". Show `NAME`; filter by `USER_ID`.
85
+ - A `Date` column's values arrive as ISO strings.
86
+
87
+ ## Rows: `useMetabaseQuery`
88
+
89
+ ```tsx
90
+ import { useMetabaseQuery } from "@metabase/embedding-sdk-react/data-app";
91
+ import { queries } from "../metabase";
92
+
93
+ const { data, error, refetch } = useMetabaseQuery(queries.revenueByMonth);
94
+ const rows = data?.rows ?? []; // row.CREATED_AT, row.sum, typed from jsType
95
+ ```
96
+
97
+ - Rows are objects keyed by column `name`. Never index them by position or cast them, and guard `null` values.
98
+ - `data` is `null` until the first answer, and stays `null` while the query is held back with `enabled: false` — show the before-a-choice state then. Otherwise `data === null && !error` is loading, and `data.rows.length === 0` is empty.
99
+ - `error` is `unknown`: render `error instanceof Error ? error.message : String(error)` in the panel.
100
+ - Call each hook once, in the highest component that needs the data, and pass the result down. Never call a hook in a loop or a condition.
101
+
102
+ ## Charts: `useMetabaseQueryObject`
103
+
104
+ A chart, KPI or table Metabase can draw renders as an SDK question:
105
+
106
+ ```tsx
107
+ import { StaticQuestion, type MetabaseCard } from "@metabase/embedding-sdk-react";
108
+ import { useMetabaseQueryObject } from "@metabase/embedding-sdk-react/data-app";
109
+ import { useMemo } from "react";
110
+
111
+ const { query, isLoading, error } = useMetabaseQueryObject(queries.revenueByMonth);
112
+ const card = useMemo(() => ({ query, visualization: "line" }) satisfies MetabaseCard, [query]);
113
+
114
+ // PanelError and Spinner are the app's own components
115
+ if (error) return <PanelError error={error} />;
116
+ if (isLoading || !query) return <Spinner />;
117
+ return <StaticQuestion card={card} height={360} />;
118
+ ```
119
+
120
+ - Always pass `visualization`: the question's own `display` doesn't reach the app, and without one Metabase picks a default, often a table. Which chart, its settings and its size: `data-app-ui`.
121
+ - Build `card` with `useMemo` keyed on `query`: a new object re-runs the question.
122
+ - Render `error` wherever this hook is used: reading only `query` makes a failed question look like endless loading.
123
+
124
+ ## Runtime clauses
125
+
126
+ Both hooks take a second argument: one object with the clauses the UI changes, and `enabled`. They run on top of the question and name its columns:
127
+
128
+ ```tsx
129
+ import { aggregations, filter, orderBy } from "@metabase/embedding-sdk-react/data-app";
130
+
131
+ const [start, end] = range; // from DateRangePopover (data-app-filters)
132
+ const { query } = useMetabaseQueryObject(queries.orderRows, {
133
+ filters:
134
+ start && end ? [filter({ type: "column", name: "CREATED_AT" }, "between", [start, end])] : [],
135
+ aggregations: [aggregations.sum({ type: "column", name: "TOTAL" }, { name: "revenue" })],
136
+ breakouts: [{ type: "column", name: "CREATED_AT", unit: "month" }],
137
+ orderBys: [orderBy({ type: "column", name: "CREATED_AT" }, "asc")],
138
+ });
139
+ ```
140
+
141
+ - A column is `{ type: "column", name }` with a `name` from `src/metabase.ts`; a wrong name, or an operator its type doesn't take, fails to compile. A date breakout takes a `unit` (`day`, `week`, `month`, `quarter`, `year`).
142
+
143
+ | Column type | Operators |
144
+ | ----------- | --------------------------------------------------------------------------------------------------------------------- |
145
+ | string | `=`, `!=`, `contains`, `does-not-contain`, `starts-with`, `ends-with`, `is-empty`, `not-empty`, `is-null`, `not-null` |
146
+ | number | `=`, `!=`, `>`, `>=`, `<`, `<=`, `between`, `is-null`, `not-null` |
147
+ | date | `=`, `!=`, `>`, `>=`, `<`, `<=`, `between`, `time-interval`, `is-null`, `not-null` |
148
+ | boolean | `=`, `is-null`, `not-null` |
149
+
150
+ - `filter(...)` takes one value: `filter(col, "=", [a, b])` compiles but is not an IN filter, and several filters AND together. So a filter picks one value or All; for a multi-select, ask the user whether a single choice will do.
151
+ - Text operators are case-sensitive, with no option to change it: say so in the search box's placeholder.
152
+ - `time-interval` (`[-30, "day"]`, `[0, "year"]` for this year) is relative to when the query runs: a fixed window belongs in the question, not the hook.
153
+ - On a rows question, aggregate with any of `aggregations.count`, `sum`, `avg`, `median`, `min`, `max`, `distinct`, naming each result (`{ name: "revenue" }`) to order or read it by — two unnamed results of one operator collide, and generated names such as `sum_2` aren't stable. On an aggregated question, re-aggregate only sums and counts, which add up.
154
+ - A sort the user picks: map each choice to a column explicitly (`{ revenue: "revenue", orders: "order_count" }`) rather than passing the user's value through.
155
+ - `enabled: false` holds the query back, for example until the user picks an item.
156
+ - Write the arrays inline (spreads are fine): the hooks compare by content, so a new array per render doesn't refetch. A clauses object built outside the call needs `as const`, or `aggregations` loses the tuple type `breakouts` need; a shared `ReturnType<typeof filter>[]` built with `push` breaks the operator types the same way.
157
+
158
+ The controls that feed these clauses are `data-app-filters`. When the data is in place, build and publish (`data-app`, _Build and publish_).
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: data-app-filters
3
+ description: Build the filter controls of a Metabase data app — the filter contract, which cards a filter reaches, All by default, options from their own question, a searchable combobox for entities, date presets, `DateRangePopover` and `DateRangeCalendar` with labels from `useDateFormatter`, and a single-date picker. Triggers — "add a filter to the app", "filter the chart by date", "a date range picker", "filter by customer", "a dropdown of categories", "the filter doesn't change the chart", "the date shows the previous day". The query side — runtime clauses and their limits — is in `data-app-data`.
4
+ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
5
+ requires: [dataApps]
6
+ ---
7
+
8
+ # Filter controls in a data app
9
+
10
+ A filter is an ordinary React control whose state becomes `filter(...)` clauses in a hook's second argument, on a column the question returns (`data-app-data`, _Runtime clauses_). This skill covers the controls; "panel" below means a chart, KPI or table on the page.
11
+
12
+ ## Write the contract first
13
+
14
+ Before any control, write one row per filter: the question that gives its options, the raw value `filter(...)` receives, the panels whose question returns that column, and the panels whose question doesn't.
15
+
16
+ | Filter | Options question | Raw value | Panels | Not these |
17
+ | --------- | -------------------------- | ------------ | ---------------------- | -------------- |
18
+ | Franchise | franchises, by id and name | franchise id | orders, revenue, stock | — |
19
+ | Plan | orders broken out by plan | plan text | orders | revenue, stock |
20
+
21
+ - A filter in the page's global bar changes every panel that can take it. One that can't reach a panel is scoped to its section, visibly, or left out. A KPI and its detail table about the same thing take the same filters, unless the UI says why not.
22
+ - One date control per scope. Never a global date range beside a page-specific date where one of the two does nothing; a page whose cards use another date column gets one control for that column.
23
+ - Build each panel's clauses from its own question's columns, inline at its hook call. Never reuse an array built for another question.
24
+
25
+ ## Values and options
26
+
27
+ - Every filter starts at **All**. Keep it as `"all"` in state and send it as no clause (`[]`); treat `""`, `null` and `undefined` the same way. In a Yes/No/All filter, Yes and No each filter (`true`, `false`), and only All sends nothing.
28
+ - Options come at runtime from their own question in the app's collection, broken out on the filtered column — never hardcoded unless the user gave a closed list. Dedupe them from its rows, and keep the label apart from the value.
29
+ - Filter by a stable raw value (an id) and show its label; never filter by a display label when an id exists, and never feed free text into an exact-id filter.
30
+ - All stays selectable while options load, come back empty or fail; don't disable the control. When fresh options no longer hold the selected value, reset to All:
31
+
32
+ ```tsx
33
+ useEffect(() => {
34
+ if (selected !== "all" && !options.some((option) => option.value === selected)) {
35
+ setSelected("all");
36
+ }
37
+ }, [options, selected]);
38
+ ```
39
+
40
+ ## Entities: a searchable combobox
41
+
42
+ Any list whose options come from a question — an id shown as a name, a category, anything that can grow — is one searchable combobox, even while it's short. A `<select>` is only for a short closed list the user gave. Prefer the app's component library or a small established combobox package; hand-roll only a small one. Either way:
43
+
44
+ - Click or focus opens the list at once, before any typing; search matches labels, not ids.
45
+ - All is always an option, and picking an option stores its raw value.
46
+ - The list is capped or scrolls, and looks like the app's other controls.
47
+ - Don't close the list from the trigger's `onBlur` when the popover holds an auto-focused input — moving focus into it closes the list at once. Close on selection, Escape, or focus leaving the whole wrapper.
48
+
49
+ ## Date ranges
50
+
51
+ A preset bar lists the durations, then All time, then Custom last — leave Custom out only if the user asked for fixed presets. Every option ends in the same `[start, end]` range (All time is `[null, null]`), so one `between` filter serves them all. Custom is `DateRangePopover` around your own trigger, or `DateRangeCalendar` inline, both from `@metabase/embedding-sdk-react/data-app`. No date-picker library and no `<input type="date">` for a range.
52
+
53
+ ```tsx
54
+ import { DateRangePopover, filter, useDateFormatter } from "@metabase/embedding-sdk-react/data-app";
55
+
56
+ const { formatDateRange } = useDateFormatter();
57
+ const [range, setRange] = useState<[string | null, string | null]>([null, null]);
58
+ const [start, end] = range;
59
+ // at the hook: filters: start && end ? [filter({ type: "column", name: "CREATED_AT" }, "between", [start, end])] : []
60
+
61
+ <DateRangePopover value={range} onChange={setRange}>
62
+ <button className="filter-button">{formatDateRange(range) || "Custom"}</button>
63
+ </DateRangePopover>;
64
+ ```
65
+
66
+ - Both ends are `YYYY-MM-DD` strings, the shape `filter(...)` takes. `onChange` fires on every click, so a half-picked range arrives as `[start, null]`: filter only once both ends are set, never with a sentinel date.
67
+ - The child is the trigger: one element or a `forwardRef` component, since the popover attaches a ref and a click handler.
68
+ - Label it with `formatDateRange(range)` (or `formatDate` for one end), which formats in the instance's locale; both take a dayjs `format`, and `formatDateRange` a `separator`. It returns `""` until the SDK loads. Never `new Date("YYYY-MM-DD")`: a date-only string parses as UTC and shows the previous day west of Greenwich.
69
+ - The popover closes once both ends are picked (`closeOnSelect`); `opened` and `onOpenedChange` control it yourself. Also `defaultValue`, `minDate` and `maxDate` (`YYYY-MM-DD`), `numberOfColumns` (default 2) and `position` (default `bottom-start`). `DateRangeCalendar` takes the same value props plus `className` and `style`.
70
+
71
+ A preset's range, from local dates — never `toISOString()`, which shifts the day:
72
+
73
+ ```ts
74
+ const ymd = (d: Date) =>
75
+ `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
76
+ const lastMonths = (n: number): [string, string] => {
77
+ const end = new Date();
78
+ const start = new Date(end.getFullYear(), end.getMonth() - n, end.getDate());
79
+ return [ymd(start), ymd(end)];
80
+ };
81
+ ```
82
+
83
+ **A single date or a date-time** is the one case for a library: `npm install react-datepicker`, import `react-datepicker/dist/react-datepicker.css`, type the callback (`(date: Date | null) => …`), and convert with `ymd` above. Pass `null`, never `new Date("")`, for an empty value.
84
+
85
+ ## Check
86
+
87
+ For every visible filter: All runs the unfiltered question; its options come from a question; each option changes every panel the contract says it reaches, and no other; a preset bar ends with Custom; and no scope has two date controls. Search the filter controls for `<select` and `type="date"` — an action form keeps its date inputs. Then build and publish (`data-app`, _Build and publish_).
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: data-app-ui
3
+ description: Write a Metabase data app's UI — what the sandbox blocks and what replaces it, external APIs through `allowed_hosts`, layout and sizing, the SDK theme, a custom error state, the SDK components on offer, a Metabase chart versus a hand-drawn one and its visualization settings, pages with `DataAppRouter`, and what the app may show. Triggers — "change the layout", "theme the app", "add a page", "link between pages", "call an external API from the app", "the chart overflows", "invisible chart labels", "make it look like our brand". Creating and publishing is `data-app`; its data is `data-app-data`.
4
+ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
5
+ requires: [dataApps]
6
+ ---
7
+
8
+ # A data app's UI
9
+
10
+ Everything here runs inside the sandbox, the same in the dev preview as in Metabase; check each change in the preview and its diagnostics feed (`data-app`).
11
+
12
+ ## Source
13
+
14
+ - Plain ESM and TSX. SDK components come from `@metabase/embedding-sdk-react`; the data hooks, routing, `copy`, `DateRangePopover`, `DateRangeCalendar` and `useDateFormatter` from `@metabase/embedding-sdk-react/data-app`. JSX needs no `import React`; import hooks and React types by name.
15
+ - React, React DOM and both SDK entries come from the host. Never read SDK values off `globalThis`: the sandbox globals there are private.
16
+ - The build is one self-contained file: CSS is inlined, `import logo from "./logo.png"` is a data URI, `import Icon from "./icon.svg?react"` a component (add `declare module "*.svg?react";` in a `.d.ts` under `src/` if the editor doesn't know it). Keep large binaries out. There's no `index.html` and no Vite plugin to add: solve it in `src/`.
17
+ - Once the app grows: `src/App.tsx` for routing and composition, `pages/` one file per screen, `components/`, `hooks/`, `lib/` for pure helpers.
18
+
19
+ ## What the app shows
20
+
21
+ - Only what Metabase returned, or deterministic transforms of it: no sample data, invented KPIs, trends, statuses, or claims such as "real-time" or "at risk" the data doesn't support. A section no question supports is left out.
22
+ - Don't read a `rate`, `score`, `margin` or `percent` column as a percentage or threshold — no `%`, `* 100`, colors or stars — unless its `semanticType` in `src/metabase.ts`, or the user, says so.
23
+ - Every panel has a loading state, its error, and an empty state for no rows — never an empty slot or a stale value.
24
+ - Label with names, not ids; currency to two decimals, counts as integers; a `null` breakout is "Unknown". A trend needs several points: never a sparkline from one value. Cap long lists with a limit, pages or a scroll area.
25
+
26
+ ## What the sandbox blocks
27
+
28
+ | Blocked | Use instead |
29
+ | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
30
+ | `fetch` to Metabase or to any origin outside the app's `allowed_hosts`; `XMLHttpRequest`; `WebSocket` | The data hooks for Metabase; _External APIs_ for anything else. |
31
+ | `alert`, `confirm`, `prompt` | A modal in your own tree. |
32
+ | `localStorage`, `sessionStorage`, `indexedDB`, `document.cookie` | Nothing persists across reloads; persist through an action (`data-app-actions`). |
33
+ | `window.open`, `history.pushState`, `history.replaceState` | `useDataAppLocation().navigate`; `<a target="_blank" rel="noopener">` for external links. |
34
+ | `navigator.clipboard`, `execCommand("copy")` | `await copy(text)` in a user event — write-only, there's no paste. |
35
+ | `navigator.geolocation` and other device APIs | Nothing. |
36
+ | Key, input, composition and clipboard listeners (`keydown`, `paste`, `input`, …) on `window` or `document` | A React handler (`onKeyDown`, `onPaste`, …) on your own element. |
37
+
38
+ **External APIs.** A raw `fetch` reaches only the origins in the app's `allowed_hosts`, exact or with a `*.` subdomain wildcard: `mb data-app update <slug> --body '{"allowed_hosts":["https://api.example.com"]}'`, plus an `allowed_hosts:` list in `data_app.yaml` beside `package.json` for the preview, then restart `npm run dev`. Never list the Metabase URL. In Metabase the same list governs `<iframe src>` and native `<form action>` submits, and the target must allow framing; the preview blocks both. Use `<form onSubmit>` with `preventDefault`: a native submit navigates the app away.
39
+
40
+ ## Layout
41
+
42
+ - The root element gets `minHeight: "100vh"` (plus `boxSizing: "border-box"` with padding) and the page background; never `height`. Regions that reach the bottom, such as a sidebar, go in a flex-column root with `flex: 1`. Check the emptiest screens — loading, empty, a lone KPI — since they render shortest.
43
+ - SDK questions don't size to their parent: pass `height` and `width` to the component that draws (`StaticQuestion`, `InteractiveQuestion`, or `InteractiveQuestion.QuestionVisualization`) — the height actually left in its panel, or `height="100%"` in a fixed-height box. A KPI needs little: about `120` for a `scalar`, more for a `smartscalar`'s comparison or a `gauge`.
44
+ - Don't wrap an SDK question in `overflow: hidden`, hover transforms or hover-driven layout shifts: its menus and tooltips need stable geometry.
45
+
46
+ ## Theme
47
+
48
+ `src/theme.ts`, passed as `providerProps.theme`, is the only way to style SDK components, one theme for the whole app; it never styles your own UI (inline styles or CSS modules).
49
+
50
+ | Key | Rule |
51
+ | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
52
+ | `colors.brand` | The app's accent. |
53
+ | `colors["brand-hover"]`, `colors["brand-hover-light"]` | A light tint (`#EAF4FF` for a blue brand), never the saturated brand. |
54
+ | `colors.charts` | All 8 slots, starting with `brand`, vivid and distinct — never pale tints, which can color labels. An empty slot keeps Metabase's color. |
55
+ | `colors.background`, `colors["background-secondary"]` | The color of the SDK component's own container: `white` inside a white card. |
56
+ | `colors["text-primary"]`, `-secondary`, `-tertiary` | Contrast with `background`; set them whenever you set it. Missing contrast is the "invisible chart labels" bug. |
57
+ | `colors.positive`, `colors.negative`, `fontFamily` | Up and down in trends and changes; the SDK's typeface. A `text-hover` must contrast with both brand-hover colors in open menus. |
58
+
59
+ **Error state.** A question that can't load shows Metabase's neutral error. To match the app, add an `errorComponent` to `providerProps` — it replaces the error of every SDK component, so keep the wording generic, render its `message` (a `ReactNode`) as it is, and use no data hooks in it:
60
+
61
+ ```tsx
62
+ import type { SdkErrorComponentProps } from "@metabase/embedding-sdk-react";
63
+
64
+ export function AppError({ message }: SdkErrorComponentProps) {
65
+ return <div style={{ padding: 16, color: "#6b7280" }}>{message}</div>;
66
+ }
67
+ // src/index.tsx: providerProps: { theme, errorComponent: AppError }
68
+ ```
69
+
70
+ ## SDK components
71
+
72
+ | Import | For |
73
+ | -------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
74
+ | `StaticQuestion` | A chart, KPI or table without drill-through — the default. |
75
+ | `InteractiveQuestion` | One meant to be explored: drill-through, chart switching, the editor. |
76
+ | `StaticDashboard`, `InteractiveDashboard`, `EditableDashboard` | A dashboard. |
77
+ | `CreateQuestion`, `MetabotQuestion`, `CollectionBrowser`, `CreateDashboardModal` | Authoring flows, rarely in an app. |
78
+
79
+ ## A Metabase chart or your own
80
+
81
+ Ask "can Metabase draw this at all?", never "which looks closer to the design?" — and decide per element, not per page.
82
+
83
+ - **Yes** — bar, line, area, combo, row, pie, scalar, smartscalar, gauge, progress, funnel, scatter, waterfall, boxplot, sankey, pivot, map, tables: render it with `useMetabaseQueryObject` and `StaticQuestion` (`data-app-data`). It keeps the instance's formatting, tooltips, theming and drill-through. Styling is never a reason to draw it yourself.
84
+ - **No**, or the user asked for a custom one — scorecards, timelines, calendar grids, one visual from several queries: draw it from `useMetabaseQuery` rows, with SVG or a chart library the app already has.
85
+ - A KPI is `scalar`, `smartscalar`, `gauge` or `progress`; put what Metabase doesn't draw (a caption, stars) beside it.
86
+ - `InteractiveQuestion`: pass `isSaveEnabled={false}`, and `title={false}` in a panel with its own heading. Children replace its layout: `<InteractiveQuestion card={card}><InteractiveQuestion.QuestionVisualization height={360} /></InteractiveQuestion>` is the chart alone.
87
+
88
+ **Visualization settings.** Add `visualizationSettings` only for a change the user asked for. Their keys are a question's `visualization_settings` keys — the `visualization` skill lists them — and the installed types decide what's legal: `rg -n "<key>|declare type .*VisualizationSettings" node_modules/@metabase/embedding-sdk-react/dist/index.d.ts`, never the whole file, never from memory. Keep them few: keys such as `graph.dimensions`, `pie.metric` and `column_settings` name result columns and break when the question changes.
89
+
90
+ - A `table` with two breakouts and one aggregation renders as a pivot when a breakout has at most 16 values; `{ "table.pivot": false }` keeps one row per group.
91
+ - A scalar shows the first column: with a key column first, point it at the number with `{ "scalar.field": "avg_rating" }`.
92
+ - A header: `column_settings: { '["name","approvers"]': { column_title: "Approvers" } }`.
93
+
94
+ ## Pages
95
+
96
+ No router library — it calls `history.pushState`, which the sandbox blocks. Wrap the app once in `<DataAppRouter>`, link with `<DataAppLink to="/customers/42">` (an `<a href>` that takes the usual anchor props), and read `{ pathname, navigate }` from `useDataAppLocation()`. There's no `useParams`: match `pathname` yourself.
97
+
98
+ ```tsx
99
+ const TABS = [
100
+ { path: "/", label: "Overview", render: () => <Overview /> },
101
+ { path: "/customers", label: "Customers", render: () => <Customers /> },
102
+ ];
103
+ // `/` and any unknown path open the first tab
104
+ const activeTab = (pathname: string) =>
105
+ TABS.find((tab) => tab.path !== "/" && pathname.startsWith(tab.path)) ?? TABS[0];
106
+
107
+ function Page() {
108
+ const { pathname } = useDataAppLocation();
109
+ const customerId = pathname.match(/^\/customers\/(\d+)$/)?.[1];
110
+ return customerId ? <CustomerDetail id={Number(customerId)} /> : activeTab(pathname).render();
111
+ }
112
+ // nav: <DataAppLink to={tab.path} aria-current={tab === activeTab(pathname) ? "page" : undefined}>
113
+ // App: <DataAppRouter><Nav /><Page /></DataAppRouter>
114
+ ```
115
+
116
+ Metabase mirrors each route to `/apps/<slug>/<path>`, and a reload there opens that page. A middle-click opens a new tab only in the preview; in Metabase that tab shows "This data app can't be opened directly." Check in the preview: reload on a sub-path, then on `/` — the first tab shows, marked selected.
117
+
118
+ ## Before handoff
119
+
120
+ `npm run dev` only transpiles: run `npm run typecheck` after every round of edits and fix what it reports, and report the result, errors grouped by cause. Search the changed files and fix what turns up: `card={{` (every `card` comes from `useMemo`), `row[0]` and `as unknown as` (rows are keyed by column name), `<select` for a list from a question (`data-app-filters`), `type="date"` and `new Date(` in a date filter, `* 100` and `%`. Read the diagnostics feed with every panel mounted, then build and publish (`data-app`, _Build and publish_).
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mbql
3
- description: Author and debug MBQL query bodies for the `mb` CLI — the query shape, clauses, filters, aggregation and breakout, order and limit, expressions, joins and FK columns, multi-stage pipelines, saved questions and definitions as sources, output column names, and the print-schema → dry-run → run loop. Use when writing or fixing any query body — `mb query`, a card's `dataset_query`, a transform's `source.query`, or a segment/measure `definition` — or when `--dry-run` or a run reports errors. Triggers — "write an MBQL query", "the dataset_query is wrong", "aggregate and group by", "join two tables", "month-over-month".
3
+ description: Author and debug MBQL query bodies for the `mb` CLI — the query shape, clauses, filters, aggregation and breakout, order and limit, expressions, joins and FK columns, multi-stage pipelines, saved questions and definitions as sources, output column names, and the schema → dry-run → run loop. Use when writing or fixing any query body — `mb query`, a card's `dataset_query`, a transform's `source.query`, or a segment/measure `definition` — or when `--dry-run` or a run reports errors. Triggers — "write an MBQL query", "the dataset_query is wrong", "aggregate and group by", "join two tables", "month-over-month".
4
4
  allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
5
5
  ---
6
6
 
@@ -147,12 +147,12 @@ A measure is an aggregation, `["measure", {}, <measure id>]`, on its table, and
147
147
 
148
148
  <!-- /requires -->
149
149
 
150
- ## Authoring loop: print-schema → dry-run → run
150
+ ## Authoring loop: schema → dry-run → run
151
151
 
152
152
  ```bash
153
153
  mb query --file q.json --dry-run --profile <n> # check + compile on the server, no run
154
154
  mb query --file q.json --profile <n> --json # check + run
155
- mb query --print-schema --profile <n> > ./.scratch/mbql-schema.json # the full JSON Schema
155
+ mb schema query > ./.scratch/mbql-schema.json # the JSON Schema bundle, no server needed
156
156
  mb query --file q.json --compile --profile <n> # the SQL the server generates
157
157
  mb query --file q.json --metadata --profile <n> --json # tables (with FK targets), fields, snippets it touches
158
158
  mb query --file q.json --export-format csv --profile <n> > rows.csv # stream the rows up to the server's download limit, no envelope cap
@@ -28,7 +28,7 @@ A native `dataset_query` is a query with one **native stage** — the `lib/type:
28
28
  }
29
29
  ```
30
30
 
31
- This is the form a card stores and returns — author it. The CLI **pre-flight-validates** it — the envelope, the template-tag shapes, the field refs — through the usual `--print-schema → --dry-run → run` loop (`mbql`), and a saved card **round-trips** in exactly this shape: `mb card get <id> --full --json`, edit the `stages[0].native` string, send it straight back. Only the warehouse parses the SQL string, so a **SQL** syntax error surfaces when you run it, not at `--dry-run`. A parameterless query needs no `template-tags` — just the `native` string.
31
+ Author it in this form. The CLI **pre-flight-validates** it — the envelope, the template-tag shapes, the field refs — through the usual `mb schema query` → `--dry-run` → run loop (`mbql`). A saved card **round-trips**: `mb card get <id> --full --json` returns the same stage with `template-tags` as a list of the same entries, each carrying its `name`; edit the `stages[0].native` string and send it straight back, list and all. Only the warehouse parses the SQL string, so a **SQL** syntax error surfaces when you run it, not at `--dry-run`. A parameterless query needs no `template-tags` — just the `native` string.
32
32
 
33
33
  You may see an older flat form in cards created long ago — `{database, type:"native", native:{query}}`. The server still accepts it (it normalizes to the above) but it skips pre-flight and doesn't round-trip — **don't author it**.
34
34
 
@@ -4,7 +4,7 @@ Every template-tag body, the widget-type vocabulary, and the parameter-object sh
4
4
 
5
5
  ## Template-tag bodies by `type`
6
6
 
7
- The `template-tags` value is a map keyed by tag name; each entry's `name` must equal its key and the `{{name}}` in the SQL. `id` is any string unique among the tags; the tag's name serves.
7
+ Author the `template-tags` value as a map keyed by tag name; each entry's `name` must equal its key and the `{{name}}` in the SQL. A card read back carries the same entries as a list, which the server also takes. `id` is any string unique among the tags; the tag's name serves.
8
8
 
9
9
  ### Raw variable — `text` / `number` / `date` / `boolean`
10
10
 
@@ -1,16 +0,0 @@
1
- import { St as ConfigError, Z as parseJson } from "./cli.mjs";
2
- import { E as DEFAULT_FLAG_NAME, O as givenValue, k as readInput } from "./runtime-Cmes70jO.mjs";
3
- //#region src/runtime/body.ts
4
- async function readBody(sources, schema) {
5
- assertSingleSource(sources);
6
- return parseJson(await readInput(sources), schema, { source: sources.source ?? "request body" });
7
- }
8
- function assertSingleSource(sources) {
9
- const provided = [];
10
- if (givenValue(sources.flag) !== null) provided.push(DEFAULT_FLAG_NAME);
11
- if (givenValue(sources.file) !== null) provided.push("--file");
12
- if (givenValue(sources.positional) !== null) provided.push("positional");
13
- if (provided.length > 1) throw new ConfigError(`multiple body sources given (${provided.join(", ")}); pass exactly one`);
14
- }
15
- //#endregion
16
- export { readBody as t };
@@ -1,25 +0,0 @@
1
- import { t as defineCommandGroup } from "./group-81zoQcEP.mjs";
2
- //#region src/commands/card/index.ts
3
- var card_default = defineCommandGroup({
4
- name: "card",
5
- description: "Manage Metabase cards (questions, models, metrics)",
6
- skills: [{
7
- skill: "mbql",
8
- purpose: "author the dataset_query"
9
- }, {
10
- skill: "visualization",
11
- purpose: "choose display and visualization_settings"
12
- }],
13
- subCommands: {
14
- list: () => import("./list-C6roDyBd.mjs").then((mod) => mod.default),
15
- get: () => import("./get-Be7yjY9v.mjs").then((mod) => mod.default),
16
- query: () => import("./query-CzDBwAZD.mjs").then((mod) => mod.default),
17
- alerts: () => import("./alerts-CyB1Z7AU.mjs").then((mod) => mod.default),
18
- create: () => import("./create-B3QZpifA.mjs").then((mod) => mod.default),
19
- update: () => import("./update-BtfUX9CN.mjs").then((mod) => mod.default),
20
- verify: () => import("./verify-C-StvB4f.mjs").then((mod) => mod.default),
21
- archive: () => import("./archive-CfNLlebd.mjs").then((mod) => mod.default)
22
- }
23
- });
24
- //#endregion
25
- export { card_default as default };
@@ -1,21 +0,0 @@
1
- import { t as defineCommandGroup } from "./group-81zoQcEP.mjs";
2
- //#region src/commands/collection/index.ts
3
- var collection_default = defineCommandGroup({
4
- name: "collection",
5
- description: "Manage Metabase collections",
6
- skills: [{
7
- skill: "core",
8
- purpose: "collection ref forms and the transforms namespace"
9
- }],
10
- subCommands: {
11
- list: () => import("./list-CpxEOoqw.mjs").then((mod) => mod.default),
12
- get: () => import("./get-RCDF-hEq.mjs").then((mod) => mod.default),
13
- items: () => import("./items-mIoZBqUc.mjs").then((mod) => mod.default),
14
- tree: () => import("./tree-Ddsj298M.mjs").then((mod) => mod.default),
15
- create: () => import("./create-Dn8V5siS.mjs").then((mod) => mod.default),
16
- update: () => import("./update-_hHnM1Hx.mjs").then((mod) => mod.default),
17
- archive: () => import("./archive-DpgtAdEa.mjs").then((mod) => mod.default)
18
- }
19
- });
20
- //#endregion
21
- export { collection_default as default };