@metabase/cli 0.4.1-alpha.release-65-integration-branch.5cbe297 → 0.5.1

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 (292) hide show
  1. package/dist/{add-collection-hv29px39.mjs → add-collection-BnGPc0Vv.mjs} +4 -4
  2. package/dist/{alert-DnTCjYIu.mjs → alert-DQkF8jYR.mjs} +7 -7
  3. package/dist/{alerts-_ELZ12ye.mjs → alerts-BncG7w1_.mjs} +6 -6
  4. package/dist/{append-B9sLLPYT.mjs → append-D4ekYCuD.mjs} +4 -4
  5. package/dist/{archive-BhIeDoLk.mjs → archive-BQ4OA-R4.mjs} +3 -3
  6. package/dist/{archive-CwBk5vxQ.mjs → archive-BXr9kFNz.mjs} +3 -3
  7. package/dist/{archive-BdX0Uvf2.mjs → archive-BifJXCHF.mjs} +3 -3
  8. package/dist/{archive-DD8C0Krt2.mjs → archive-CxNUeWII2.mjs} +4 -4
  9. package/dist/{archive-BRX5fKPb.mjs → archive-D2WCz3Oe.mjs} +3 -3
  10. package/dist/{archive-DDUSmbjn.mjs → archive-DjHLWUfC.mjs} +5 -5
  11. package/dist/{archive-CdfyB0To.mjs → archive-DnCF-Ecm.mjs} +5 -5
  12. package/dist/{archive-ZnHzMJaM.mjs → archive-DocyUIME.mjs} +5 -5
  13. package/dist/{archive-CKWWyI1i.mjs → archive-DpCP4yR1.mjs} +4 -4
  14. package/dist/{archive-D22VL5Bx.mjs → archive-EhW6blNc.mjs} +3 -3
  15. package/dist/{archive-3MQyc5Ih.mjs → archive-ULXc3xzI.mjs} +3 -3
  16. package/dist/{archive-BAHzxkPR.mjs → archive-v08JESCM.mjs} +5 -5
  17. package/dist/{auth-Ca84a5fv.mjs → auth-BPbsm8yV.mjs} +5 -5
  18. package/dist/{body-BVrHLwJU.mjs → body-BkkQkGJg.mjs} +2 -3
  19. package/dist/{branch-flag-Czh8JT1G.mjs → branch-flag-CTDvbaBa.mjs} +1 -1
  20. package/dist/{branches-frSt4WcS.mjs → branches-lPHtM_xR.mjs} +3 -3
  21. package/dist/{breaking-zlgxxwdd.mjs → breaking-DjMlCD96.mjs} +6 -6
  22. package/dist/{broken-BUAzpKLc.mjs → broken-DUX3GWS5.mjs} +8 -8
  23. package/dist/{bulk-discard-values-DU15VJPb.mjs → bulk-discard-values-Dl8Lzypf.mjs} +5 -5
  24. package/dist/{bulk-edit-TSa4lNkM.mjs → bulk-edit-N2xLVtfG.mjs} +5 -5
  25. package/dist/{bulk-rescan-values-BYJAsPC1.mjs → bulk-rescan-values-BK2rH2Ik.mjs} +5 -5
  26. package/dist/{bulk-sync-schema-DtaOwEtW.mjs → bulk-sync-schema-BKJc9Rzq.mjs} +5 -5
  27. package/dist/{cancel-DCOvSQsq.mjs → cancel-DzkRVGIk.mjs} +3 -3
  28. package/dist/{cancel-task-osQLETJh.mjs → cancel-task-BiTlDY7a.mjs} +3 -3
  29. package/dist/card-BTBDBsu-.mjs +25 -0
  30. package/dist/{cards-B1pyPl77.mjs → cards-CSL8ukS1.mjs} +4 -4
  31. package/dist/cli.mjs +619 -198
  32. package/dist/{client-1UIUBA_V.mjs → client-BBwYPXDW.mjs} +443 -380
  33. package/dist/collection-B19phRNU.mjs +21 -0
  34. package/dist/{collection-BgPYl2c5.mjs → collection-B2wRXyTF.mjs} +11 -3
  35. package/dist/{collection-DPiztU_l.mjs → collection-CTnYZ2Jj.mjs} +1 -1
  36. package/dist/{collection-namespace-Cizv6-94.mjs → collection-namespace-B1OkM7p1.mjs} +1 -2
  37. package/dist/{content-translation-XnHISl5-.mjs → content-translation-DoGTQnvH.mjs} +3 -3
  38. package/dist/{copy-DAKdksBm.mjs → copy-BYukQEtf.mjs} +4 -4
  39. package/dist/{copy-BCVW48Jq.mjs → copy-XlCT3gvG.mjs} +6 -6
  40. package/dist/{copy-flags-D7NTgAz1.mjs → copy-flags-ByB2MicL.mjs} +2 -2
  41. package/dist/{create-qCgnB-oZ.mjs → create-3_oY9KHY.mjs} +3 -3
  42. package/dist/{create-KyeJjhxH2.mjs → create-B3fm5I-l2.mjs} +4 -4
  43. package/dist/{create-CuhQ1Khk.mjs → create-BP39g5Jk.mjs} +5 -5
  44. package/dist/{create-cqbpWaOi.mjs → create-Bl9sS7v7.mjs} +4 -4
  45. package/dist/{create-BzE4q6F_.mjs → create-Bnb1abv_.mjs} +4 -4
  46. package/dist/{create-Br5JxAeE.mjs → create-CLZ7w83R.mjs} +4 -4
  47. package/dist/{create-B5eo9PSN.mjs → create-CYoarNrb.mjs} +4 -4
  48. package/dist/{create-B57D5E-o.mjs → create-CbT4SPHV.mjs} +5 -5
  49. package/dist/{create-BfA1pnmS.mjs → create-Cdt8u1wF.mjs} +6 -6
  50. package/dist/{create-C7fVbQbQ.mjs → create-CqF7vqXC.mjs} +4 -4
  51. package/dist/{create-XnYN2zoc.mjs → create-CrtIqtXN.mjs} +6 -6
  52. package/dist/{create-B1ifNWcE.mjs → create-D5_c5SHo.mjs} +5 -5
  53. package/dist/{create-CW4WF073.mjs → create-DBNxy5rN.mjs} +5 -5
  54. package/dist/{create-g4pZa_Xa.mjs → create-DZJQp3r7.mjs} +3 -3
  55. package/dist/{create-D2iZsTRV.mjs → create-DxMmIHI4.mjs} +5 -5
  56. package/dist/{create-BOvtRwRz.mjs → create-T4PZPVE9.mjs} +4 -4
  57. package/dist/{create-branch-Cli5cXvk.mjs → create-branch-CUNFdKyB.mjs} +3 -3
  58. package/dist/{create-D7zeAm-0.mjs → create-jr4xJgSI.mjs} +4 -4
  59. package/dist/{create--b64pMVM.mjs → create-k1Bo7h7g.mjs} +5 -5
  60. package/dist/{csv-CWIcAm_-.mjs → csv-CcnhKpuj.mjs} +4 -4
  61. package/dist/{current-task-eBjusv2o.mjs → current-task-nl2z8sHI.mjs} +4 -4
  62. package/dist/dashboard-BP0p62fp.mjs +35 -0
  63. package/dist/data-action-Cr17CfgE.mjs +24 -0
  64. package/dist/{database-BT7DVgSr.mjs → database-2HYCdHqK.mjs} +1 -1
  65. package/dist/{database-Kq2AL5jH.mjs → database-BewllGR7.mjs} +1 -1
  66. package/dist/{dataset-BmvCxGoE.mjs → dataset-D-x2Ho2i.mjs} +3 -14
  67. package/dist/{db-_H9HAS6H.mjs → db-m5bQcXdn.mjs} +7 -7
  68. package/dist/{delete-B0O-mf63.mjs → delete-BOma6qIq.mjs} +4 -4
  69. package/dist/{delete-BMvVkW-m.mjs → delete-BUCh72Bq.mjs} +4 -4
  70. package/dist/{delete-CynbfvS2.mjs → delete-BXMc-g3Q.mjs} +4 -4
  71. package/dist/{delete-BW5QXiyJ.mjs → delete-BrbmJ3sn.mjs} +4 -4
  72. package/dist/{delete-C7QmQlp9.mjs → delete-DulVnxD_.mjs} +4 -4
  73. package/dist/{delete-DPU-uQxU.mjs → delete-Oc2GE170.mjs} +4 -4
  74. package/dist/{delete-DOn2uk0u.mjs → delete-QHsiWYay.mjs} +4 -4
  75. package/dist/{delete-runtime-Cc7TASG7.mjs → delete-runtime-CkNvZfFA.mjs} +1 -1
  76. package/dist/{delete-table-DFqm6bAq.mjs → delete-table-LgJ17t6j.mjs} +4 -4
  77. package/dist/{delete-BztmAfPc.mjs → delete-xf2n7SNg.mjs} +4 -4
  78. package/dist/{dependencies-DZ-kMsDa.mjs → dependencies-BE5QW7Lb.mjs} +5 -5
  79. package/dist/{dependency-BHKwKjhO.mjs → dependency-BACk_cVP.mjs} +2 -2
  80. package/dist/dependency-BlTYysYG.mjs +15 -0
  81. package/dist/{dependency-B4J1bwFh.mjs → dependency-qP1hx2YN.mjs} +1 -1
  82. package/dist/{dependents-Ia2fe_KK.mjs → dependents-mbgHQNiv.mjs} +9 -9
  83. package/dist/{dirty-BrXAPows.mjs → dirty-fYlA1N1e.mjs} +4 -4
  84. package/dist/{discard-values-B9mTgFbV.mjs → discard-values-cIRBP5nT.mjs} +5 -5
  85. package/dist/document-BaRQ5fBd.mjs +20 -0
  86. package/dist/{document-BsaKF8UR.mjs → document-DVIu3Vj4.mjs} +1 -1
  87. package/dist/{document-COQzVLjW.mjs → document-UMG6me2P.mjs} +1 -1
  88. package/dist/{download-CZdsDdhG.mjs → download-CokVpeK6.mjs} +2 -2
  89. package/dist/{eid-DsGNJ16z.mjs → eid-CaumGUFD.mjs} +5 -5
  90. package/dist/{eid-translation-zEHtr5Oc.mjs → eid-translation-Ba92VOHe.mjs} +1 -0
  91. package/dist/{entity-BFajNdQp.mjs → entity-BHYJtRxp.mjs} +2 -2
  92. package/dist/{entry-flags-BaZUoxUR.mjs → entry-flags-DjICpQkc.mjs} +4 -4
  93. package/dist/{events-rkMIMpnS.mjs → events-jEo9fytL.mjs} +6 -6
  94. package/dist/{execute-DvBse4aF.mjs → execute-H09Qus4_.mjs} +4 -4
  95. package/dist/{export-BCkzx9zk.mjs → export-YtdZo7LZ.mjs} +7 -7
  96. package/dist/{export-flags-TuwwRhD6.mjs → export-flags-DzW0Ktkh.mjs} +2 -2
  97. package/dist/{export-preflight-p7botJyN.mjs → export-preflight-Db_TChn5.mjs} +5 -5
  98. package/dist/field-DktidTPB.mjs +21 -0
  99. package/dist/{fields-CJVXOLbB.mjs → fields-DUXlosmx.mjs} +4 -4
  100. package/dist/{filter-flags-yY4RM312.mjs → filter-flags-DTDcU7OI.mjs} +3 -3
  101. package/dist/{fks-B6hjrdJP.mjs → fks-Cl2sq-c0.mjs} +6 -6
  102. package/dist/{get-D1p-5ibI.mjs → get-B-WT6ess.mjs} +5 -5
  103. package/dist/{get-DpYTiE4D.mjs → get-BilMWiFq.mjs} +5 -5
  104. package/dist/{get-CWBN0G-4.mjs → get-Bt_zhviQ.mjs} +3 -3
  105. package/dist/{get-DsbDxSpy.mjs → get-CM596pmP.mjs} +3 -3
  106. package/dist/{get-BxUgfEPW.mjs → get-CTtzccwr.mjs} +4 -4
  107. package/dist/{get-oLc5jgid2.mjs → get-CVMWhbA82.mjs} +4 -4
  108. package/dist/{get-Eforof5e.mjs → get-CWyRAHpz.mjs} +5 -5
  109. package/dist/{get-BvUNnNRq.mjs → get-C_rjxpoe.mjs} +3 -3
  110. package/dist/{get-B5L4BUGK.mjs → get-Cg0up9gm.mjs} +3 -3
  111. package/dist/{get-xmhwvAQJ.mjs → get-CqlNUyFY.mjs} +5 -5
  112. package/dist/{get-CXeFg0F2.mjs → get-DPiNYmXo.mjs} +5 -5
  113. package/dist/{get-DyS1mRI6.mjs → get-DTDvlLT3.mjs} +3 -3
  114. package/dist/{get-C6uLL4bJ.mjs → get-DtcChy9h.mjs} +5 -5
  115. package/dist/{get-BYtFVJHT.mjs → get-DyK_bvg6.mjs} +4 -4
  116. package/dist/{get-Do1LC4I52.mjs → get-DySyvotk2.mjs} +5 -5
  117. package/dist/{get-D7HPiBB0.mjs → get-FxCF0rxc.mjs} +4 -4
  118. package/dist/{get-CoJcgFky.mjs → get-GuWW4J24.mjs} +5 -5
  119. package/dist/{get-DPlSzRI0.mjs → get-XS-6R81E.mjs} +3 -3
  120. package/dist/{get-zotSW00n.mjs → get-oQNL6pLY.mjs} +3 -3
  121. package/dist/{get-BwKQw_rR.mjs → get-okEsnkvC.mjs} +3 -3
  122. package/dist/{get-run-bU-d--nM.mjs → get-run-CFGxSm_S.mjs} +4 -4
  123. package/dist/{get-DSjITPED.mjs → get-zPMv3kZE.mjs} +5 -5
  124. package/dist/git-sync-BwkWeJAz.mjs +29 -0
  125. package/dist/{git-sync-DspFahwV.mjs → git-sync-Ch1V1fEK.mjs} +1 -1
  126. package/dist/glossary-BSgRQ2HO.mjs +14 -0
  127. package/dist/{glossary-Del97Jqg.mjs → glossary-BkGmCI4X.mjs} +1 -1
  128. package/dist/{glossary-CdW6RFId.mjs → glossary-QN-hHfOT.mjs} +1 -1
  129. package/dist/{graph-CHXJ6mB7.mjs → graph-Wf4e0XIa.mjs} +6 -6
  130. package/dist/{group-D4w87fXL.mjs → group-81zoQcEP.mjs} +1 -1
  131. package/dist/{has-remote-changes-BXEe8Sv5.mjs → has-remote-changes-BsS3XB0P.mjs} +3 -3
  132. package/dist/{import-DZQb4bUo.mjs → import-CXd5HdB0.mjs} +7 -7
  133. package/dist/{input-DjHsV-kD.mjs → input-AXP2LCOV.mjs} +1 -1
  134. package/dist/{is-dirty-DwQ2_CCB.mjs → is-dirty-TDcfeOde.mjs} +2 -2
  135. package/dist/{items-DNdICQ7M.mjs → items-BxvhoxYm.mjs} +6 -6
  136. package/dist/{key-BTaxFn_7.mjs → key-CUPbYxkk.mjs} +1 -1
  137. package/dist/{library-C4M8TF2N.mjs → library-BKSFtIV9.mjs} +2 -2
  138. package/dist/{library-DmkpcZPb.mjs → library-DuIrR8LS.mjs} +1 -1
  139. package/dist/{library-Cdj_uj1H.mjs → library-DzejNMqX.mjs} +5 -5
  140. package/dist/{list-DoLqxEjq.mjs → list-3Dh5yeq-.mjs} +3 -3
  141. package/dist/{list-__Q79YXk.mjs → list-6nGX0Rqc.mjs} +4 -4
  142. package/dist/{list-hINHmdMT.mjs → list-B3Pngm2j.mjs} +5 -5
  143. package/dist/{list-89ZgbsvW.mjs → list-B5kRBn7k.mjs} +5 -5
  144. package/dist/{list-DsyvHBAv.mjs → list-B8V_OESj.mjs} +3 -3
  145. package/dist/{list-DcHPY1Ti.mjs → list-BUjI5G0_.mjs} +4 -4
  146. package/dist/{list-gqemddWB.mjs → list-Ba7u5Ai9.mjs} +4 -4
  147. package/dist/{list-CzxrdWB8.mjs → list-CCCy6cBI.mjs} +6 -6
  148. package/dist/{list-Owrzgtkc.mjs → list-CRXTfEFk.mjs} +3 -3
  149. package/dist/{list-C7PV9fYj.mjs → list-CUj04tg3.mjs} +6 -6
  150. package/dist/{list-OKHcr76i.mjs → list-Cf6A9Csi.mjs} +3 -3
  151. package/dist/{list-F4ENGV7j.mjs → list-Cr5Hoq-E.mjs} +4 -4
  152. package/dist/{list-C-WGqRqN.mjs → list-D1Q8H3jn.mjs} +6 -6
  153. package/dist/{list-epIn3uFZ.mjs → list-D7yH-64Z.mjs} +5 -5
  154. package/dist/{list-M20foZz2.mjs → list-DA1Fo6S6.mjs} +3 -3
  155. package/dist/{list-CYlPgzD1.mjs → list-D_fsrh8w.mjs} +5 -5
  156. package/dist/{list-03zaydQe.mjs → list-DjlfBd7W.mjs} +3 -3
  157. package/dist/{list-CZoaZc9g.mjs → list-DlgjWr4r.mjs} +5 -5
  158. package/dist/{list-2EWHwbQs.mjs → list-Do-Xnz0I.mjs} +4 -4
  159. package/dist/{list-Be6kDkyT.mjs → list-DpK3ioES.mjs} +6 -6
  160. package/dist/{list-CLJvuPXn.mjs → list-DsR58aMq.mjs} +5 -5
  161. package/dist/{login-BSqGgX8S.mjs → login-CXbCAcuB.mjs} +6 -6
  162. package/dist/{logout-D92O4mQK.mjs → logout-B6IGzkpG.mjs} +2 -2
  163. package/dist/{measure-DhX_mmZK.mjs → measure-CbMgNhlG.mjs} +6 -6
  164. package/dist/{notification-Cuuwoksm.mjs → notification-W2SVssyY.mjs} +1 -1
  165. package/dist/{parameter-values-BY2WA9qg.mjs → parameter-values-IWi_DNhp.mjs} +3 -3
  166. package/dist/{parse-enum-BPKULOSw.mjs → parse-enum-B2rGiC6p.mjs} +1 -1
  167. package/dist/{parse-id-CxHp2akh.mjs → parse-id-Cv5eM7sg.mjs} +1 -1
  168. package/dist/{parse-ref-8bVX_9IN.mjs → parse-ref-DW7ik6ny.mjs} +1 -1
  169. package/dist/{parse-text-Bm6eUa-p.mjs → parse-text-CUCSWOeR.mjs} +1 -1
  170. package/dist/{path-D4Q1ZzCw.mjs → path-C9r9urRK.mjs} +4 -4
  171. package/dist/{poll-DFZRtTA_.mjs → poll-CX3jPtXb.mjs} +2 -2
  172. package/dist/{preflight-BPaonBKy.mjs → preflight-CiTgqNUy.mjs} +2 -3
  173. package/dist/{process-B3Di_HtV.mjs → process-DImdroix.mjs} +1 -1
  174. package/dist/{publish-BhgyrJ6c.mjs → publish-xH1n74gd.mjs} +4 -4
  175. package/dist/{pulse-CIza18rO.mjs → pulse-CFj1WfIC.mjs} +1 -1
  176. package/dist/{query-CsBe-kJn.mjs → query-BOCw0FBK.mjs} +4 -4
  177. package/dist/{query-DvlGWjgG.mjs → query-CN3BW5WB.mjs} +11 -12
  178. package/dist/{remapping-CCEivr42.mjs → remapping-Dggq2Fv0.mjs} +4 -4
  179. package/dist/{remove-collection-LpgGBYCl.mjs → remove-collection-cxSSFxza.mjs} +4 -4
  180. package/dist/{render-D5ehBS-7.mjs → render-Cb8Hct67.mjs} +2 -2
  181. package/dist/{replace-DeWgkbfZ.mjs → replace-wVi_XDut.mjs} +4 -4
  182. package/dist/requirement-check-BE3PuGsC.mjs +60 -0
  183. package/dist/{rescan-values-CDDVS1tl.mjs → rescan-values-C7b1Ghy-.mjs} +5 -5
  184. package/dist/{rescan-values-aF18XAv6.mjs → rescan-values-DAp_avxY.mjs} +5 -5
  185. package/dist/{run-DyGMvbMm.mjs → run-CE6ei7s7.mjs} +4 -4
  186. package/dist/{run-Buf4B8QH.mjs → run-CUGs7mok.mjs} +4 -4
  187. package/dist/{run-DlG3Ykj1.mjs → run-DywhwjNG.mjs} +6 -6
  188. package/dist/{runs-D2CXgW10.mjs → runs--14jim_0.mjs} +5 -5
  189. package/dist/{runtime-X6hVzSMW.mjs → runtime-Ckns6TKK.mjs} +248 -534
  190. package/dist/{schema-tables-FUOIyoUU.mjs → schema-tables-CeW0kpA5.mjs} +6 -6
  191. package/dist/{schemas-CmfNeqzG.mjs → schemas-CHMD0HrY.mjs} +4 -4
  192. package/dist/{search-BAXg-EJw.mjs → search-CXRavT7G.mjs} +1 -0
  193. package/dist/{search-Dl7xzF6Q.mjs → search-DyC54bGf.mjs} +7 -7
  194. package/dist/{search-CLpDqYpY.mjs → search-LmBQu1Jg.mjs} +5 -5
  195. package/dist/{segment-Bx28f38v.mjs → segment-DeT_Yic6.mjs} +6 -6
  196. package/dist/{send-CoGV2zkp.mjs → send-BWSJMrDL.mjs} +3 -3
  197. package/dist/{set-active-CbiJv8Hg.mjs → set-active-C5cLG4_O.mjs} +4 -4
  198. package/dist/{set-CrR9IumN.mjs → set-r2xiXrT_.mjs} +4 -4
  199. package/dist/{set-sensitivity-CTHVxpnU.mjs → set-sensitivity-BMLKazua.mjs} +4 -4
  200. package/dist/{setting-CUxdweNJ.mjs → setting-CDRusiqP.mjs} +4 -4
  201. package/dist/{setup-4uySIB-t.mjs → setup-BZaJVww3.mjs} +3 -3
  202. package/dist/{signal-0Yt8JTMn.mjs → signal-4tpCIkqR.mjs} +1 -1
  203. package/dist/{skill-list-goCqmfFF.mjs → skill-list-D2FhhYS3.mjs} +9 -3
  204. package/dist/{skills-86j411Qz.mjs → skills-9ihLaSmM.mjs} +3 -3
  205. package/dist/{skills-DeWsLMwC.mjs → skills-y2iVfW9k.mjs} +2 -2
  206. package/dist/snippet-Ce0o33Rj.mjs +19 -0
  207. package/dist/{stash-BNKlbxtD.mjs → stash-Dj-mnIa4.mjs} +5 -5
  208. package/dist/{status-B7bwHLkk.mjs → status-B2Xj_q8f.mjs} +3 -3
  209. package/dist/{status-Bul9El-c.mjs → status-BtWz7Pzn.mjs} +16 -7
  210. package/dist/{subscription-DPAGevGU.mjs → subscription-Dh--agqE.mjs} +6 -6
  211. package/dist/{subscriptions-DIRsHcKP.mjs → subscriptions-CJ8Jdp9A.mjs} +6 -6
  212. package/dist/{summary-CeVt80Kb.mjs → summary-ccbkNDbC.mjs} +3 -3
  213. package/dist/{sync-schema-B9fLcZoa.mjs → sync-schema-CewUOkmm.mjs} +6 -6
  214. package/dist/{sync-schema-oDTbhjW1.mjs → sync-schema-HwQectZd.mjs} +5 -5
  215. package/dist/{sync-task-BAMOf6_t.mjs → sync-task-DEzhiaU9.mjs} +3 -3
  216. package/dist/{table-BFIOH6Pb.mjs → table-CFZU-BRg.mjs} +2 -2
  217. package/dist/table-P9ovwcaB.mjs +26 -0
  218. package/dist/{table-selector-flags-BUR0-n6j.mjs → table-selector-flags-p4jun7-v.mjs} +3 -3
  219. package/dist/{table-Ci6bhZkb.mjs → table-zAdW5_3H.mjs} +5 -5
  220. package/dist/{text-CdtEI6l6.mjs → text-C2VwKF9D.mjs} +1 -1
  221. package/dist/{timeline-uDCmwGFT.mjs → timeline-BgWz8DYK.mjs} +1 -1
  222. package/dist/timeline-DLaBypse.mjs +21 -0
  223. package/dist/{timeline-CCSU2qOS.mjs → timeline-DcsCXNUt.mjs} +5 -1
  224. package/dist/{timeline-event-BY2Hj72e.mjs → timeline-event-xAAR6b-v.mjs} +6 -6
  225. package/dist/transform-10YE8j5K.mjs +28 -0
  226. package/dist/{transform-D4z70PIN.mjs → transform-CWGJB8N_.mjs} +1 -1
  227. package/dist/{transform-job-AaMZRxAq.mjs → transform-job-BBZ5L46v.mjs} +1 -1
  228. package/dist/{transform-job-CCguL6LP.mjs → transform-job-ClX8e5iO.mjs} +2 -1
  229. package/dist/transform-job-D97AutnF.mjs +22 -0
  230. package/dist/{transform-tag-CzjLKILh.mjs → transform-tag-BXusFzMX.mjs} +5 -5
  231. package/dist/{transform-test-BjjCQ2xm.mjs → transform-test-Bexa_K89.mjs} +5 -1
  232. package/dist/{transform-test-DHGKeuHa.mjs → transform-test-C49ZTK7M.mjs} +1 -1
  233. package/dist/transform-test-UsH1udc4.mjs +16 -0
  234. package/dist/{transforms-0uhBh0H4.mjs → transforms-CqV01bMM.mjs} +5 -5
  235. package/dist/{transport-BmNrb0Lz.mjs → transport-0qFZhEuP.mjs} +144 -41
  236. package/dist/{tree-oBEw8hDj.mjs → tree-Td6rgyOa.mjs} +3 -3
  237. package/dist/{unpublish-BJ9OKN1N.mjs → unpublish-D4XTZsFW.mjs} +3 -3
  238. package/dist/{unreferenced-D6UyA_Nu.mjs → unreferenced-cR-l4zXN.mjs} +6 -6
  239. package/dist/{update-7zsDGAWa.mjs → update-BJL5975i.mjs} +6 -6
  240. package/dist/{update-DGwGjOnH.mjs → update-BqkqMOVN.mjs} +5 -5
  241. package/dist/{update-CKe2Bwpv.mjs → update-C4iu-vQm.mjs} +6 -6
  242. package/dist/{update-D9eLVWFQ.mjs → update-C7Wmom6x.mjs} +5 -5
  243. package/dist/{update-BK_SpSrG.mjs → update-C7dZLbsf.mjs} +4 -4
  244. package/dist/{update-BZgHTH5P.mjs → update-C8xdeYI2.mjs} +4 -4
  245. package/dist/{update-BqrZ-8IK.mjs → update-CHB4kvz-.mjs} +8 -8
  246. package/dist/{update-M7yDPmJ-.mjs → update-CND9At61.mjs} +5 -5
  247. package/dist/{update-bGDXSnBh.mjs → update-CpwWURTp.mjs} +5 -5
  248. package/dist/{update-CKJ6Das4.mjs → update-D-cTgmb0.mjs} +6 -6
  249. package/dist/{update-DKagaJw0.mjs → update-DFK2iUSP.mjs} +6 -6
  250. package/dist/{update-Cv6IMLyu.mjs → update-DROjGe3Q.mjs} +4 -4
  251. package/dist/{update-Bdi40RG0.mjs → update-DZHgr6Gu.mjs} +5 -5
  252. package/dist/{update-CHA4VWKT.mjs → update-Dk0E66Va.mjs} +5 -5
  253. package/dist/{update-DgmlUn64.mjs → update-DqCZsbLh.mjs} +6 -6
  254. package/dist/{update-CEhub3Wz.mjs → update-DrVc25R8.mjs} +5 -5
  255. package/dist/{update-CYQkWLpZ.mjs → update-Js0IiqCq.mjs} +6 -6
  256. package/dist/{update-DneJLQXe.mjs → update-dQeji6RA.mjs} +7 -7
  257. package/dist/{update-dashcard-DNsf-gJ1.mjs → update-dashcard-BmkfJ7t9.mjs} +4 -4
  258. package/dist/{update-CXfLvhmK.mjs → update-nGURR-y6.mjs} +6 -6
  259. package/dist/{upgrade-BRJH93W0.mjs → upgrade-B6V_1cUh.mjs} +4 -4
  260. package/dist/{upload-DJB7DMA1.mjs → upload-B6zBzMPW.mjs} +2 -2
  261. package/dist/{upload-CGz8u2wG.mjs → upload-CfYmBv51.mjs} +3 -3
  262. package/dist/upload-Dvjfz16M.mjs +13 -0
  263. package/dist/{uuid-3qmLYgrG.mjs → uuid-B8ynbkqG.mjs} +2 -2
  264. package/dist/{validate-D_iikPkc.mjs → validate-C7DGkwb-.mjs} +1 -1
  265. package/dist/{validate-query-DIcK8alR.mjs → validate-query-CYUvRvVM.mjs} +2 -2
  266. package/dist/{values-Bn2Wcz4t.mjs → values-36bzf9cK.mjs} +3 -3
  267. package/dist/{verify-CA4PEHlO.mjs → verify-BdW-GExk.mjs} +3 -3
  268. package/dist/{verify-C2K-zMuQ.mjs → verify-C4Tml4fD.mjs} +3 -3
  269. package/dist/{verify-CCX2N98Q.mjs → verify-Cs9e28YA.mjs} +4 -4
  270. package/dist/{wait-DCgCJBqX.mjs → wait-BV6JOdgi.mjs} +6 -6
  271. package/dist/{wait-flags-aRQHTjwg.mjs → wait-flags-DjyK03cm.mjs} +2 -2
  272. package/dist/{window-2ybHqui2.mjs → window-CfK_j6m1.mjs} +1 -1
  273. package/package.json +1 -1
  274. package/skill-data/core/SKILL.md +11 -5
  275. package/skill-data/dashboard/SKILL.md +6 -0
  276. package/skill-data/git-sync/SKILL.md +64 -14
  277. package/dist/card-P2-4BkXV.mjs +0 -25
  278. package/dist/collection-BI8Fy9vS.mjs +0 -21
  279. package/dist/dashboard-C-YJxDam.mjs +0 -35
  280. package/dist/data-action-DER3EMD7.mjs +0 -24
  281. package/dist/dependency-v9fHJ-N-.mjs +0 -15
  282. package/dist/document-CGvUmD9i.mjs +0 -20
  283. package/dist/field-C9AcBe8x.mjs +0 -21
  284. package/dist/git-sync-D4R0cHvk.mjs +0 -29
  285. package/dist/glossary-C6EnoEXg.mjs +0 -14
  286. package/dist/snippet-C3gamfFK.mjs +0 -19
  287. package/dist/table-C--k-JXA.mjs +0 -26
  288. package/dist/timeline-ClMmTycg.mjs +0 -21
  289. package/dist/transform-BZaNFGeK.mjs +0 -28
  290. package/dist/transform-job-QMI2qWin.mjs +0 -22
  291. package/dist/transform-test-B9le1jKH.mjs +0 -16
  292. package/dist/upload-T5wS1XtL.mjs +0 -13
@@ -1,38 +1,39 @@
1
- import { ct as InternalError, dt as ResponseShapeError, ft as TimeoutError, gt as formatZodIssue, ht as errorMessage, ot as ConfigError } from "./cli.mjs";
1
+ import { At as formatZodIssue, Et as TimeoutError, Mt as isNonInterruptFailure, P as CapabilityError, Q as parseJsonOrPlain, Tt as ResponseShapeError, W as HttpError, Y as isPlainObject, kt as errorMessage, n as isMethodKey, r as methodRequirements, wt as PartialWriteError, xt as InternalError, yt as ConfigError } from "./cli.mjs";
2
2
  import { n as Notification, r as NotificationCardPayload, t as CARD_PAYLOAD_TYPE } from "./notification-DaA4bBHW.mjs";
3
- import { P as HttpError, Z as PROBE_PATH, z as parseJsonOrPlain } from "./runtime-X6hVzSMW.mjs";
4
- import { n as createTransport } from "./transport-BmNrb0Lz.mjs";
3
+ import { L as profileFromProperties, Z as SessionProperties } from "./runtime-Ckns6TKK.mjs";
4
+ import { n as featureFailures, r as supersedingFeature, t as checkFeatures } from "./requirement-check-BE3PuGsC.mjs";
5
+ import { n as createTransport } from "./transport-0qFZhEuP.mjs";
5
6
  import { f as FieldWithDataSensitivity, h as fieldRemappedValueSchema, m as fieldDetailSchema, n as FieldBaseType, o as FieldSearchMatches, s as FieldSemanticType, u as FieldValues } from "./field-5nivEfd6.mjs";
6
7
  import { a as TemporalUnit, r as ParameterValues } from "./parameter-Celg0wZa.mjs";
7
8
  import { r as PivotExportFormat, t as DatasetQuery } from "./query-BKtMP5eH.mjs";
8
9
  import { a as CardQueryResult, t as Card } from "./card-P5mBknsl.mjs";
9
- import { b as TableVisibilityType, p as TableQueryMetadata, s as TableDataLayerTier, t as Table, u as TableForeignKey, v as TableSelectors } from "./table-Ci6bhZkb.mjs";
10
- import { t as Database } from "./database-BT7DVgSr.mjs";
10
+ import { b as TableVisibilityType, p as TableQueryMetadata, s as TableDataLayerTier, t as Table, u as TableForeignKey, v as TableSelectors } from "./table-zAdW5_3H.mjs";
11
+ import { t as Database } from "./database-2HYCdHqK.mjs";
11
12
  import { t as Snippet } from "./snippet-CyKAgIZl.mjs";
12
- import { o as compiledQuerySchema, r as QueryMetadata } from "./dataset-BmvCxGoE.mjs";
13
- import { c as CollectionItem, m as CollectionTreeNode, r as Collection } from "./collection-BgPYl2c5.mjs";
13
+ import { r as QueryMetadata, t as CompiledQuery } from "./dataset-D-x2Ho2i.mjs";
14
+ import { c as CollectionItem, m as CollectionTreeNode, r as Collection } from "./collection-B2wRXyTF.mjs";
14
15
  import { t as ContentTranslationUploadResult } from "./content-translation-BrHOrmj9.mjs";
15
16
  import { l as Dashcard, o as DashboardDetail, r as DashboardCopy, t as Dashboard } from "./dashboard-Phz3XpRM.mjs";
16
17
  import { a as DataActionExecuteResult, s as dataActionSchema } from "./data-action-DZxVB_hD.mjs";
17
- import { r as pollUntil } from "./poll-DFZRtTA_.mjs";
18
- import { i as DependencyEntity, s as DependencyGraph, t as BreakingSource, u as DependencyNode } from "./dependency-B4J1bwFh.mjs";
19
- import { t as Document } from "./document-COQzVLjW.mjs";
20
- import { i as EidTranslateResult } from "./eid-translation-zEHtr5Oc.mjs";
18
+ import { r as pollUntil } from "./poll-CX3jPtXb.mjs";
19
+ import { i as DependencyEntity, s as DependencyGraph, t as BreakingSource, u as DependencyNode } from "./dependency-qP1hx2YN.mjs";
20
+ import { t as Document } from "./document-UMG6me2P.mjs";
21
+ import { i as EidTranslateResult } from "./eid-translation-Ba92VOHe.mjs";
21
22
  import { c as SyncRemoteChanges, d as SyncTask, i as SyncExportPreflight, l as SyncSettingsUpdateResult, m as isSyncTaskTerminal, n as SyncDirtyItem, t as SyncBranchCreated } from "./git-sync-COokpYgc.mjs";
22
- import { t as Glossary } from "./glossary-CdW6RFId.mjs";
23
- import { a as libraryWireSchema, i as LibraryPublishTablesInput, n as LibraryCollectionInfo, o as toLibrary } from "./library-C4M8TF2N.mjs";
23
+ import { t as Glossary } from "./glossary-QN-hHfOT.mjs";
24
+ import { a as libraryWireSchema, i as LibraryPublishTablesInput, n as LibraryCollectionInfo, o as toLibrary } from "./library-BKSFtIV9.mjs";
24
25
  import { t as Measure } from "./measure-CtfjqJFP.mjs";
25
26
  import { r as ModerationReviewCreateInput, t as ModerationReview } from "./moderation-review-BdqEH6N1.mjs";
26
27
  import { t as Pulse } from "./pulse-Dz0ocv0l.mjs";
27
28
  import { a as TransformDagTransform, d as TransformTarget, g as transformRowSchema, h as transformDetailSchema, i as TransformDagRunResult, l as TransformRunSummary, m as isTransformRunTerminal, o as TransformRun, p as isTransformRunFailed, u as TransformSourceTableEntry } from "./transform-DiW7bREn.mjs";
28
- import { r as SearchResult } from "./search-BAXg-EJw.mjs";
29
+ import { r as SearchResult } from "./search-CXRavT7G.mjs";
29
30
  import { t as Segment } from "./segment-OsrbUDzw.mjs";
30
31
  import { t as Setting } from "./setting-DTiLCW8g.mjs";
31
32
  import { n as SetupResult } from "./setup-CO7QV-ao.mjs";
32
- import { i as TimelineEvent, t as Timeline } from "./timeline-CCSU2qOS.mjs";
33
- import { c as transformJobSchema, n as TransformJobActiveResult, s as transformJobRunResultSchema } from "./transform-job-CCguL6LP.mjs";
33
+ import { i as TimelineEvent, t as Timeline } from "./timeline-DcsCXNUt.mjs";
34
+ import { c as transformJobSchema, n as TransformJobActiveResult, s as transformJobRunResultSchema } from "./transform-job-ClX8e5iO.mjs";
34
35
  import { t as TransformTag } from "./transform-tag-CcElGp3C.mjs";
35
- import { i as TransformTestRunResult, t as TransformTest } from "./transform-test-BjjCQ2xm.mjs";
36
+ import { i as TransformTestRunResult, t as TransformTest } from "./transform-test-Bexa_K89.mjs";
36
37
  import { t as UploadResult } from "./upload-DAtxBBms.mjs";
37
38
  import { z } from "zod";
38
39
  //#region \0rolldown/runtime.js
@@ -48,14 +49,14 @@ var __exportAll = (all, no_symbols) => {
48
49
  };
49
50
  //#endregion
50
51
  //#region ../client/src/resources/pivot-export.ts
51
- const PivotedExportsSetting = z.object({ "enable-pivoted-exports": z.boolean() });
52
+ const PivotedExportsProperty = SessionProperties.extend({ "enable-pivoted-exports": z.boolean() });
52
53
  /**
53
54
  * Refuse a pivoted export the server would answer with plain rows: a JSON export keeps only the
54
55
  * ungrouped rows, and an admin can turn pivoted CSV and XLSX exports off.
55
56
  */
56
57
  async function assertPivotedExport(transport, format, options) {
57
58
  if (!PivotExportFormat.safeParse(format).success) throw new ConfigError(`a ${format} export cannot be pivoted; only ${PivotExportFormat.options.join(" and ")} can`);
58
- if (!(await transport.requestParsed(PivotedExportsSetting, "/api/session/properties", { ...options }))["enable-pivoted-exports"]) throw new ConfigError("the server has pivoted exports turned off (the enable-pivoted-exports setting), so it would export the plain rows");
59
+ if (!(await transport.probe(PivotedExportsProperty, options))["enable-pivoted-exports"]) throw new ConfigError("the server has pivoted exports turned off (the enable-pivoted-exports setting), so it would export the plain rows");
59
60
  }
60
61
  //#endregion
61
62
  //#region ../client/src/resources/card.ts
@@ -63,7 +64,6 @@ const CardApiList = z.array(Card);
63
64
  function cardResource(transport) {
64
65
  /** List cards. `f` picks a server-side preset; `model_id` scopes the presets that need an id. */
65
66
  async function list(params = {}, options = {}) {
66
- await transport.require("card.list", options);
67
67
  return {
68
68
  data: await transport.requestParsed(CardApiList, "/api/card", {
69
69
  ...options,
@@ -77,12 +77,10 @@ function cardResource(transport) {
77
77
  }
78
78
  /** Get one card by id. */
79
79
  async function get(id, options = {}) {
80
- await transport.require("card.get", options);
81
80
  return transport.requestParsed(Card, `/api/card/${id}`, { ...options });
82
81
  }
83
82
  /** Create a card — a question, a model, or a metric — from a full card body. */
84
83
  async function create(params, options = {}) {
85
- await transport.require("card.create", options);
86
84
  return transport.requestParsed(Card, "/api/card", {
87
85
  ...options,
88
86
  method: "POST",
@@ -91,7 +89,6 @@ function cardResource(transport) {
91
89
  }
92
90
  /** Update a card by id, patching only the fields the body carries. */
93
91
  async function update(id, params, options = {}) {
94
- await transport.require("card.update", options);
95
92
  return transport.requestParsed(Card, `/api/card/${id}`, {
96
93
  ...options,
97
94
  method: "PUT",
@@ -100,12 +97,10 @@ function cardResource(transport) {
100
97
  }
101
98
  /** Archive (soft-delete) a card by id. Metabase models this as an update, not its own endpoint. */
102
99
  async function archive(id, options = {}) {
103
- await transport.require("card.archive", options);
104
100
  return update(id, { archived: true }, options);
105
101
  }
106
102
  /** Run a saved card and return the query result envelope. */
107
103
  async function query(id, params, options = {}) {
108
- await transport.require("card.query", options);
109
104
  return transport.requestParsed(CardQueryResult, `/api/card/${id}/query`, {
110
105
  ...options,
111
106
  method: "POST",
@@ -121,9 +116,8 @@ function cardResource(transport) {
121
116
  * there before the wire.
122
117
  */
123
118
  async function exportQuery(id, format, params, options = {}) {
124
- await transport.require("card.exportQuery", options);
125
- await transport.requireFeatures(params.csv_include_bom ? ["exportCsvByteOrderMark"] : [], options);
126
119
  if (params.pivot_results) await assertPivotedExport(transport, format, options);
120
+ await transport.requireFeatures(params.csv_include_bom ? ["exportCsvByteOrderMark"] : [], options);
127
121
  const body = new URLSearchParams({
128
122
  parameters: JSON.stringify(params.parameters),
129
123
  format_rows: String(params.format_rows),
@@ -141,7 +135,6 @@ function cardResource(transport) {
141
135
  * snippets its query references, plus the card's own metadata for a model or a native query.
142
136
  */
143
137
  async function queryMetadata(id, options = {}) {
144
- await transport.require("card.queryMetadata", options);
145
138
  return transport.requestParsed(QueryMetadata, `/api/card/${id}/query_metadata`, { ...options });
146
139
  }
147
140
  return {
@@ -227,7 +220,6 @@ function collectionResource(transport) {
227
220
  * preset includes the Library collections that match it.
228
221
  */
229
222
  async function list(params = {}, options = {}) {
230
- await transport.require("collection.list", options);
231
223
  return {
232
224
  data: await transport.requestParsed(CollectionApiList, "/api/collection", {
233
225
  ...options,
@@ -238,12 +230,10 @@ function collectionResource(transport) {
238
230
  }
239
231
  /** Get one collection by id, entity id, or the `root`/`trash` alias. */
240
232
  async function get(ref, options = {}) {
241
- await transport.require("collection.get", options);
242
233
  return transport.requestParsed(Collection, `/api/collection/${refPath(ref)}`, { ...options });
243
234
  }
244
235
  /** Create a new collection. */
245
236
  async function create(params, options = {}) {
246
- await transport.require("collection.create", options);
247
237
  return transport.requestParsed(Collection, "/api/collection", {
248
238
  ...options,
249
239
  method: "POST",
@@ -256,7 +246,6 @@ function collectionResource(transport) {
256
246
  * moves a collection into the trash ignores `parent_id`.
257
247
  */
258
248
  async function update(ref, params, options = {}) {
259
- await transport.require("collection.update", options);
260
249
  return transport.requestParsed(Collection, `/api/collection/${refPath(ref)}`, {
261
250
  ...options,
262
251
  method: "PUT",
@@ -265,7 +254,6 @@ function collectionResource(transport) {
265
254
  }
266
255
  /** Archive (soft-delete) a collection. Metabase models this as an update, not its own endpoint. */
267
256
  async function archive(ref, options = {}) {
268
- await transport.require("collection.archive", options);
269
257
  return update(ref, { archived: true }, options);
270
258
  }
271
259
  /**
@@ -273,7 +261,6 @@ function collectionResource(transport) {
273
261
  * the caller consumes pages rather than a single list and decides how far to pull.
274
262
  */
275
263
  async function* itemPages(ref, params = {}, options = {}) {
276
- await transport.require("collection.itemPages", options);
277
264
  yield* paginatePages(transport, `/api/collection/${refPath(ref)}/items`, CollectionItem, {
278
265
  query: {
279
266
  models: params.models,
@@ -288,7 +275,6 @@ function collectionResource(transport) {
288
275
  }
289
276
  /** Fetch the collection hierarchy as a forest of nested nodes. */
290
277
  async function tree(params = {}, options = {}) {
291
- await transport.require("collection.tree", options);
292
278
  return {
293
279
  data: await transport.requestParsed(CollectionTreeApiList, "/api/collection/tree", {
294
280
  ...options,
@@ -308,6 +294,128 @@ function collectionResource(transport) {
308
294
  };
309
295
  }
310
296
  //#endregion
297
+ //#region ../client/src/version/refusal.ts
298
+ const REFUSAL_STATUSES = /* @__PURE__ */ new Set([
299
+ 400,
300
+ 402,
301
+ 404
302
+ ]);
303
+ function explainer(transport, namespace) {
304
+ function explain(name, method, parameters) {
305
+ const key = methodKey(namespace, name);
306
+ return async (...args) => {
307
+ try {
308
+ return await method(...args);
309
+ } catch (error) {
310
+ throw await explainRefusal(transport, callRequirement(key, parameters, args), error);
311
+ }
312
+ };
313
+ }
314
+ function explainWalk(name, method, parameters) {
315
+ const key = methodKey(namespace, name);
316
+ return async function* (...args) {
317
+ try {
318
+ yield* method(...args);
319
+ } catch (error) {
320
+ throw await explainRefusal(transport, callRequirement(key, parameters, args), error);
321
+ }
322
+ };
323
+ }
324
+ function refuse(name, method) {
325
+ const key = methodKey(namespace, name);
326
+ return async (...args) => {
327
+ await transport.requireFeatures(methodRequirements(key), trailingWait(args));
328
+ return method(...args);
329
+ };
330
+ }
331
+ function refuseAfterReading(name, reader, method) {
332
+ const key = methodKey(namespace, name);
333
+ return async (options = {}) => {
334
+ const properties = await transport.probe(reader, options);
335
+ const profile = profileFromProperties(properties);
336
+ for (const failure of featureFailures(methodRequirements(key), profile)) transport.refuseBeforeSending(new CapabilityError(failure));
337
+ return method(properties);
338
+ };
339
+ }
340
+ return {
341
+ explain,
342
+ explainWalk,
343
+ refuse,
344
+ refuseAfterReading
345
+ };
346
+ }
347
+ function trailingWait(args) {
348
+ const last = args.at(-1);
349
+ if (isPlainObject(last) && last["signal"] instanceof AbortSignal) return { signal: last["signal"] };
350
+ return {};
351
+ }
352
+ function methodKey(namespace, name) {
353
+ const key = `${namespace}.${name}`;
354
+ if (!isMethodKey(key)) throw new InternalError(`no requirements entry for ${key}`);
355
+ return key;
356
+ }
357
+ function callRequirement(key, parameters, args) {
358
+ return {
359
+ parameters: parameters === void 0 ? [] : parameters(...args),
360
+ method: methodRequirements(key)
361
+ };
362
+ }
363
+ /**
364
+ * The error a call failed with, as a `CapabilityError` carrying the server's answer when the server
365
+ * refused it for lacking one of the features `call` needs, and unchanged otherwise. The server's own
366
+ * refusal is the verdict; the profile only names what was missing. The explaining probe is bounded
367
+ * by its own timeout and the client's signal; the error stands when the probe fails, and an
368
+ * interrupt surfaces as one.
369
+ */
370
+ async function explainRefusal(transport, call, error) {
371
+ if (!mayBeRefusal(call, error)) return error;
372
+ let profile;
373
+ try {
374
+ profile = await transport.verifiedServer();
375
+ } catch (probeError) {
376
+ if (isNonInterruptFailure(probeError)) return error;
377
+ throw probeError;
378
+ }
379
+ const failure = explainedFailure(call, profile, error);
380
+ return failure === null ? error : new CapabilityError(failure, error);
381
+ }
382
+ /** Every feature `call` needs, parameters first, since a parameter's floor sits above its method's. */
383
+ function callFeatures(call) {
384
+ return [...call.parameters.map((parameter) => parameter.feature), ...call.method];
385
+ }
386
+ /**
387
+ * Whether `error` may be the server refusing `call` for a feature it lacks. A call that needs no
388
+ * feature cannot be, and neither can a 404 for a missing row, which comes from a route the server
389
+ * serves.
390
+ */
391
+ function mayBeRefusal(call, error) {
392
+ if (callFeatures(call).length === 0) return false;
393
+ if (!(error instanceof HttpError) || error.kind === "resource-missing") return false;
394
+ return REFUSAL_STATUSES.has(error.status);
395
+ }
396
+ /**
397
+ * The feature `profile` lacks that explains `refusal` of `call`, or `null` when the server has every
398
+ * feature the call needs, so it failed for some other reason, or when the refusal points elsewhere.
399
+ */
400
+ function explainedFailure(call, profile, refusal) {
401
+ const failure = checkFeatures(callFeatures(call), profile);
402
+ if (failure === null || pointsElsewhere(call, profile, refusal)) return null;
403
+ return failure;
404
+ }
405
+ function pointsElsewhere(call, profile, refusal) {
406
+ if (refusal.status !== 400 || checkFeatures(call.method, profile) !== null) return false;
407
+ const named = rejectedFields(refusal);
408
+ const gated = new Set(call.parameters.filter((parameter) => checkFeatures([parameter.feature], profile) !== null).flatMap((parameter) => parameter.fields));
409
+ return named.some((field) => !gated.has(field));
410
+ }
411
+ function rejectedFields(refusal) {
412
+ return [refusal.fieldErrors, refusal.specificFieldErrors].flatMap((errors) => errors === null ? [] : Object.keys(errors).map(topLevelField));
413
+ }
414
+ function topLevelField(path) {
415
+ const end = path.indexOf(".");
416
+ return end === -1 ? path : path.slice(0, end);
417
+ }
418
+ //#endregion
311
419
  //#region ../client/src/resources/csv-upload.ts
312
420
  const CSV_CONTENT_TYPE = "text/csv";
313
421
  function buildCsvFormData(file) {
@@ -319,14 +427,13 @@ function buildCsvFormData(file) {
319
427
  //#region ../client/src/resources/content-translation.ts
320
428
  const CONTENT_TRANSLATION_PATH = "/api/ee/content-translation";
321
429
  function contentTranslationResource(transport) {
430
+ const { explain } = explainer(transport, "contentTranslation");
322
431
  /** Download the complete content translation dictionary as CSV. */
323
432
  async function download(options = {}) {
324
- await transport.require("contentTranslation.download", options);
325
433
  return transport.requestStream(`${CONTENT_TRANSLATION_PATH}/csv`, options);
326
434
  }
327
435
  /** Replace the complete content translation dictionary with the attached CSV. */
328
436
  async function upload(file, options = {}) {
329
- await transport.require("contentTranslation.upload", options);
330
437
  const form = buildCsvFormData(file);
331
438
  return transport.requestParsed(ContentTranslationUploadResult, `${CONTENT_TRANSLATION_PATH}/upload-dictionary`, {
332
439
  ...options,
@@ -335,8 +442,8 @@ function contentTranslationResource(transport) {
335
442
  });
336
443
  }
337
444
  return {
338
- download,
339
- upload
445
+ download: explain("download", download),
446
+ upload: explain("upload", upload)
340
447
  };
341
448
  }
342
449
  //#endregion
@@ -347,7 +454,6 @@ function dashboardResource(transport) {
347
454
  const card = cardResource(transport);
348
455
  /** List dashboards. `f` picks a server-side preset. */
349
456
  async function list(params = {}, options = {}) {
350
- await transport.require("dashboard.list", options);
351
457
  return {
352
458
  data: await transport.requestParsed(DashboardApiList, "/api/dashboard", {
353
459
  ...options,
@@ -358,7 +464,6 @@ function dashboardResource(transport) {
358
464
  }
359
465
  /** Get one dashboard by id, with its dashcards, tabs and parameters. */
360
466
  async function get(id, options = {}) {
361
- await transport.require("dashboard.get", options);
362
467
  return transport.requestParsed(DashboardDetail, `/api/dashboard/${id}`, { ...options });
363
468
  }
364
469
  /**
@@ -366,7 +471,6 @@ function dashboardResource(transport) {
366
471
  * puts content on a fresh dashboard.
367
472
  */
368
473
  async function create(params, options = {}) {
369
- await transport.require("dashboard.create", options);
370
474
  return transport.requestParsed(Dashboard, "/api/dashboard", {
371
475
  ...options,
372
476
  method: "POST",
@@ -378,7 +482,6 @@ function dashboardResource(transport) {
378
482
  * full replacements: whatever the body omits is deleted from the dashboard.
379
483
  */
380
484
  async function update(id, params, options = {}) {
381
- await transport.require("dashboard.update", options);
382
485
  return transport.requestParsed(DashboardDetail, `/api/dashboard/${id}`, {
383
486
  ...options,
384
487
  method: "PUT",
@@ -396,7 +499,6 @@ function dashboardResource(transport) {
396
499
  * is duplicated. Action, link and placeholder dashcards are not copied.
397
500
  */
398
501
  async function copy(id, params = {}, options = {}) {
399
- await transport.require("dashboard.copy", options);
400
502
  return transport.requestParsed(DashboardCopy, `/api/dashboard/${id}/copy`, {
401
503
  ...options,
402
504
  method: "POST",
@@ -409,7 +511,6 @@ function dashboardResource(transport) {
409
511
  * an archive is a state change on the dashboard itself, and its dashcards are not part of it.
410
512
  */
411
513
  async function archive(id, options = {}) {
412
- await transport.require("dashboard.archive", options);
413
514
  return transport.requestParsed(Dashboard, `/api/dashboard/${id}`, {
414
515
  ...options,
415
516
  method: "PUT",
@@ -422,7 +523,6 @@ function dashboardResource(transport) {
422
523
  * currently stands.
423
524
  */
424
525
  async function updateDashcard(dashboardId, dashcardId, params, options = {}) {
425
- await transport.require("dashboard.updateDashcard", options);
426
526
  const dashboard = await get(dashboardId, options);
427
527
  const target = dashboard.dashcards.find((dashcard) => dashcard.id === dashcardId);
428
528
  if (target === void 0) throw new ConfigError(`dashcard ${dashcardId} not found on dashboard ${dashboardId}`);
@@ -441,7 +541,6 @@ function dashboardResource(transport) {
441
541
  * any other way propagates as the failure it was.
442
542
  */
443
543
  async function checkCardReferences(dashcards, options = {}) {
444
- await transport.require("dashboard.checkCardReferences", options);
445
544
  const grouped = groupByCardId(collectCardReferences(dashcards));
446
545
  return (await Promise.all(Array.from(grouped.entries()).map(async ([cardId, references]) => ({
447
546
  references,
@@ -461,7 +560,7 @@ function dashboardResource(transport) {
461
560
  } catch (error) {
462
561
  if (!(error instanceof HttpError)) throw error;
463
562
  if (error.kind === "resource-missing") return { reason: "missing" };
464
- if (error.kind === "auth") return {
563
+ if (error.kind === "auth" || error.kind === "forbidden") return {
465
564
  reason: "unreadable",
466
565
  detail: error.userMessage
467
566
  };
@@ -472,7 +571,6 @@ function dashboardResource(transport) {
472
571
  * Fetch the selectable values for a dashboard parameter, through the chain-filter value endpoint.
473
572
  */
474
573
  async function parameterValues(dashboardId, parameterId, options = {}) {
475
- await transport.require("dashboard.parameterValues", options);
476
574
  return transport.requestParsed(ParameterValues, `/api/dashboard/${dashboardId}/params/${encodeURIComponent(parameterId)}/values`, { ...options });
477
575
  }
478
576
  /**
@@ -480,7 +578,6 @@ function dashboardResource(transport) {
480
578
  * substring, capped at the first 1000 matches.
481
579
  */
482
580
  async function searchParameterValues(dashboardId, parameterId, query, options = {}) {
483
- await transport.require("dashboard.searchParameterValues", options);
484
581
  const parameter = encodeURIComponent(parameterId);
485
582
  return transport.requestParsed(ParameterValues, `/api/dashboard/${dashboardId}/params/${parameter}/search/${encodeURIComponent(query)}`, { ...options });
486
583
  }
@@ -533,14 +630,20 @@ function stripEntityId(dashcard) {
533
630
  }
534
631
  //#endregion
535
632
  //#region ../client/src/resources/data-action.ts
633
+ function collectionFeatures(params) {
634
+ return params.collection_id === void 0 ? [] : [{
635
+ feature: "dataActionCollections",
636
+ fields: ["collection_id"]
637
+ }];
638
+ }
536
639
  function dataActionResource(transport) {
640
+ const { explain } = explainer(transport, "dataAction");
537
641
  async function readSchema(options) {
538
642
  const { features } = await transport.server(options);
539
643
  return dataActionSchema(features);
540
644
  }
541
645
  /** List the unarchived data actions the caller can see. `GET /api/action` answers a bare array. */
542
646
  async function list(options = {}) {
543
- await transport.require("dataAction.list", options);
544
647
  const schema = await readSchema(options);
545
648
  return {
546
649
  data: await transport.requestParsed(z.array(schema), "/api/action", { ...options }),
@@ -549,13 +652,11 @@ function dataActionResource(transport) {
549
652
  }
550
653
  /** Get one unarchived data action by id. */
551
654
  async function get(id, options = {}) {
552
- await transport.require("dataAction.get", options);
553
655
  const schema = await readSchema(options);
554
656
  return transport.requestParsed(schema, `/api/action/${id}`, { ...options });
555
657
  }
556
658
  /** Create a data action — a parameterized native query that writes to a database — in a collection. */
557
659
  async function create(params, options = {}) {
558
- await transport.require("dataAction.create", options);
559
660
  const schema = await readSchema(options);
560
661
  return transport.requestParsed(schema, "/api/action", {
561
662
  ...options,
@@ -565,7 +666,6 @@ function dataActionResource(transport) {
565
666
  }
566
667
  /** Update a data action by id, patching only the fields the body carries. */
567
668
  async function update(id, params, options = {}) {
568
- await transport.require("dataAction.update", options);
569
669
  const schema = await readSchema(options);
570
670
  return transport.requestParsed(schema, `/api/action/${id}`, {
571
671
  ...options,
@@ -575,12 +675,10 @@ function dataActionResource(transport) {
575
675
  }
576
676
  /** Archive (soft-delete) a data action by id. Metabase models this as an update, not its own endpoint. */
577
677
  async function archive(id, options = {}) {
578
- await transport.require("dataAction.archive", options);
579
678
  return update(id, { archived: true }, options);
580
679
  }
581
680
  /** Delete a data action by id, removing it and the dashboard buttons that run it. */
582
681
  async function remove(id, options = {}) {
583
- await transport.require("dataAction.delete", options);
584
682
  await transport.requestRaw(`/api/action/${id}`, {
585
683
  ...options,
586
684
  method: "DELETE",
@@ -589,7 +687,6 @@ function dataActionResource(transport) {
589
687
  }
590
688
  /** Execute a data action by id with parameter values keyed by parameter id. */
591
689
  async function execute(id, params, options = {}) {
592
- await transport.require("dataAction.execute", options);
593
690
  return transport.requestParsed(DataActionExecuteResult, `/api/action/${id}/execute`, {
594
691
  ...options,
595
692
  method: "POST",
@@ -599,8 +696,8 @@ function dataActionResource(transport) {
599
696
  return {
600
697
  list,
601
698
  get,
602
- create,
603
- update,
699
+ create: explain("create", create),
700
+ update: explain("update", update, (_id, params) => collectionFeatures(params)),
604
701
  archive,
605
702
  delete: remove,
606
703
  execute
@@ -619,7 +716,6 @@ const SYNC_COMPLETE = "complete";
619
716
  function databaseResource(transport) {
620
717
  /** List databases. `include` hydrates each database's tables; `saved` adds the Saved Questions virtual database. */
621
718
  async function list(params = {}, options = {}) {
622
- await transport.require("database.list", options);
623
719
  const response = await transport.requestParsed(DatabaseApiList, "/api/database", {
624
720
  ...options,
625
721
  query: {
@@ -634,7 +730,6 @@ function databaseResource(transport) {
634
730
  }
635
731
  /** Get one database by id. `include` hydrates its tables, and `tables.fields` their fields too. */
636
732
  async function get(id, params = {}, options = {}) {
637
- await transport.require("database.get", options);
638
733
  return transport.requestParsed(Database, `/api/database/${id}`, {
639
734
  ...options,
640
735
  query: { include: params.include }
@@ -642,7 +737,6 @@ function databaseResource(transport) {
642
737
  }
643
738
  /** List the schema names in a database. */
644
739
  async function schemas(id, options = {}) {
645
- await transport.require("database.schemas", options);
646
740
  return {
647
741
  data: await transport.requestParsed(DatabaseApiSchemaList, `/api/database/${id}/schemas`, { ...options }),
648
742
  total: null
@@ -650,7 +744,6 @@ function databaseResource(transport) {
650
744
  }
651
745
  /** List the tables one schema of a database holds. */
652
746
  async function schemaTables(id, schema, options = {}) {
653
- await transport.require("database.schemaTables", options);
654
747
  return {
655
748
  data: await transport.requestParsed(DatabaseApiSchemaTableList, `/api/database/${id}/schema/${encodeURIComponent(schema)}`, { ...options }),
656
749
  total: null
@@ -661,7 +754,6 @@ function databaseResource(transport) {
661
754
  * pass `wait` to poll the database until it reports its initial sync complete.
662
755
  */
663
756
  async function syncSchema(id, params = {}, options = {}) {
664
- await transport.require("database.syncSchema", options);
665
757
  const ack = await transport.requestParsed(DatabaseTaskAck, `/api/database/${id}/sync_schema`, {
666
758
  ...options,
667
759
  method: "POST"
@@ -683,7 +775,6 @@ function databaseResource(transport) {
683
775
  }
684
776
  /** Trigger a rescan of the cached field values across a database. */
685
777
  async function rescanValues(id, options = {}) {
686
- await transport.require("database.rescanValues", options);
687
778
  return {
688
779
  id,
689
780
  status: (await transport.requestParsed(DatabaseTaskAck, `/api/database/${id}/rescan_values`, {
@@ -709,7 +800,6 @@ function datasetResource(transport) {
709
800
  * MBQL, or native — rather than a reference to a saved one, so nothing here is a card.
710
801
  */
711
802
  async function query(body, options = {}) {
712
- await transport.require("dataset.query", options);
713
803
  return transport.requestParsed(CardQueryResult, "/api/dataset", {
714
804
  ...options,
715
805
  method: "POST",
@@ -721,9 +811,7 @@ function datasetResource(transport) {
721
811
  * defaults server-side to true.
722
812
  */
723
813
  async function native(datasetQuery, params = {}, options = {}) {
724
- await transport.require("dataset.native", options);
725
- const { features } = await transport.server(options);
726
- return transport.requestParsed(compiledQuerySchema(features), "/api/dataset/native", {
814
+ return transport.requestParsed(CompiledQuery, "/api/dataset/native", {
727
815
  ...options,
728
816
  method: "POST",
729
817
  body: {
@@ -738,7 +826,6 @@ function datasetResource(transport) {
738
826
  * widens the field filter.
739
827
  */
740
828
  async function queryMetadata(datasetQuery, options = {}) {
741
- await transport.require("dataset.queryMetadata", options);
742
829
  return transport.requestParsed(QueryMetadata, "/api/dataset/query_metadata", {
743
830
  ...options,
744
831
  method: "POST",
@@ -757,9 +844,8 @@ function datasetResource(transport) {
757
844
  * there before the wire.
758
845
  */
759
846
  async function exportQuery(format, params, options = {}) {
760
- await transport.require("dataset.exportQuery", options);
761
- await transport.requireFeatures(params.csv_include_bom ? ["exportCsvByteOrderMark"] : [], options);
762
847
  if (params.pivot_results) await assertPivotedExport(transport, format, options);
848
+ await transport.requireFeatures(params.csv_include_bom ? ["exportCsvByteOrderMark"] : [], options);
763
849
  const exported = params.pivot_results ? {
764
850
  ...params.query,
765
851
  "was-pivot": true
@@ -787,15 +873,20 @@ function datasetResource(transport) {
787
873
  //#region ../client/src/resources/dependency.ts
788
874
  const DependencyNodeApiList = z.array(DependencyNode);
789
875
  const DependencyEntityApiList = z.array(DependencyEntity);
876
+ function graphTypeFeatures(type) {
877
+ return type === "measure" ? [{
878
+ feature: "measureDependencyGraph",
879
+ fields: ["type"]
880
+ }] : [];
881
+ }
790
882
  function dependencyResource(transport) {
883
+ const { explain, explainWalk } = explainer(transport, "dependency");
791
884
  /**
792
885
  * The upstream dependency graph of one entity. `nodes` holds the starting entity plus every
793
886
  * entity it depends on, directly or transitively; each edge runs from the dependent to what it
794
887
  * depends on. A measure starts a graph only on a server with `measureDependencyGraph`.
795
888
  */
796
889
  async function graph(type, id, options = {}) {
797
- await transport.require("dependency.graph", options);
798
- if (type === "measure") await transport.requireFeatures(["measureDependencyGraph"], options);
799
890
  return transport.requestParsed(DependencyGraph, "/api/ee/dependencies/graph", {
800
891
  ...options,
801
892
  query: {
@@ -811,7 +902,6 @@ function dependencyResource(transport) {
811
902
  * personal collections, and `sort-column` / `sort-direction` order the result.
812
903
  */
813
904
  async function dependents(type, id, params = {}, options = {}) {
814
- await transport.require("dependency.dependents", options);
815
905
  return {
816
906
  data: await transport.requestParsed(DependencyNodeApiList, "/api/ee/dependencies/graph/dependents", {
817
907
  ...options,
@@ -837,7 +927,6 @@ function dependencyResource(transport) {
837
927
  * `sort-direction` order the result.
838
928
  */
839
929
  async function broken(type, id, params = {}, options = {}) {
840
- await transport.require("dependency.broken", options);
841
930
  return {
842
931
  data: await transport.requestParsed(DependencyEntityApiList, "/api/ee/dependencies/graph/broken", {
843
932
  ...options,
@@ -864,7 +953,6 @@ function dependencyResource(transport) {
864
953
  * This endpoint pages on the server, so the caller consumes pages and decides how far to pull.
865
954
  */
866
955
  async function* unreferencedPages(params = {}, options = {}) {
867
- await transport.require("dependency.unreferencedPages", options);
868
956
  if (queryLeavesNoKind(params)) {
869
957
  yield {
870
958
  items: [],
@@ -888,7 +976,6 @@ function dependencyResource(transport) {
888
976
  * caller consumes pages and decides how far to pull.
889
977
  */
890
978
  async function* breakingPages(params = {}, options = {}) {
891
- await transport.require("dependency.breakingPages", options);
892
979
  if (queryLeavesNoKind(params)) {
893
980
  yield {
894
981
  items: [],
@@ -905,11 +992,11 @@ function dependencyResource(transport) {
905
992
  });
906
993
  }
907
994
  return {
908
- graph,
909
- dependents,
910
- broken,
911
- unreferencedPages,
912
- breakingPages
995
+ graph: explain("graph", graph, graphTypeFeatures),
996
+ dependents: explain("dependents", dependents),
997
+ broken: explain("broken", broken),
998
+ unreferencedPages: explainWalk("unreferencedPages", unreferencedPages),
999
+ breakingPages: explainWalk("breakingPages", breakingPages)
913
1000
  };
914
1001
  }
915
1002
  function queryLeavesNoKind(params) {
@@ -931,9 +1018,9 @@ function itemListQuery(params) {
931
1018
  //#region ../client/src/resources/document.ts
932
1019
  const DocumentApiList = z.object({ items: z.array(Document) }).loose();
933
1020
  function documentResource(transport) {
1021
+ const { explain } = explainer(transport, "document");
934
1022
  /** Get existing documents. */
935
1023
  async function list(options = {}) {
936
- await transport.require("document.list", options);
937
1024
  return {
938
1025
  data: (await transport.requestParsed(DocumentApiList, "/api/document", { ...options })).items,
939
1026
  total: null
@@ -941,12 +1028,10 @@ function documentResource(transport) {
941
1028
  }
942
1029
  /** Return an existing document by id. */
943
1030
  async function get(id, options = {}) {
944
- await transport.require("document.get", options);
945
1031
  return transport.requestParsed(Document, `/api/document/${id}`, { ...options });
946
1032
  }
947
1033
  /** Create a new document. */
948
1034
  async function create(params, options = {}) {
949
- await transport.require("document.create", options);
950
1035
  return transport.requestParsed(Document, "/api/document", {
951
1036
  ...options,
952
1037
  method: "POST",
@@ -955,7 +1040,6 @@ function documentResource(transport) {
955
1040
  }
956
1041
  /** Update an existing document by id. */
957
1042
  async function update(id, params, options = {}) {
958
- await transport.require("document.update", options);
959
1043
  return transport.requestParsed(Document, `/api/document/${id}`, {
960
1044
  ...options,
961
1045
  method: "PUT",
@@ -964,7 +1048,6 @@ function documentResource(transport) {
964
1048
  }
965
1049
  /** Archive (soft-delete) a document by id. Metabase models this as an update, not its own endpoint. */
966
1050
  async function archive(id, options = {}) {
967
- await transport.require("document.archive", options);
968
1051
  return update(id, { archived: true }, options);
969
1052
  }
970
1053
  /**
@@ -972,7 +1055,6 @@ function documentResource(transport) {
972
1055
  * it. An archived source is not found.
973
1056
  */
974
1057
  async function copy(id, params = {}, options = {}) {
975
- await transport.require("document.copy", options);
976
1058
  return transport.requestParsed(Document, `/api/document/${id}/copy`, {
977
1059
  ...options,
978
1060
  method: "POST",
@@ -985,7 +1067,7 @@ function documentResource(transport) {
985
1067
  create,
986
1068
  update,
987
1069
  archive,
988
- copy
1070
+ copy: explain("copy", copy)
989
1071
  };
990
1072
  }
991
1073
  //#endregion
@@ -997,7 +1079,6 @@ function eidTranslationResource(transport) {
997
1079
  * lookup succeeded — the numeric id.
998
1080
  */
999
1081
  async function translate(params, options = {}) {
1000
- await transport.require("eidTranslation.translate", options);
1001
1082
  return transport.requestParsed(EidTranslateResult, "/api/eid-translation/translate", {
1002
1083
  ...options,
1003
1084
  method: "POST",
@@ -1051,6 +1132,7 @@ const Erd = z.object({
1051
1132
  //#region ../client/src/resources/erd.ts
1052
1133
  const ERD_PATH = "/api/ee/erd";
1053
1134
  function erdResource(transport) {
1135
+ const { explain } = explainer(transport, "erd");
1054
1136
  /**
1055
1137
  * Get the entity relationship diagram of a database: the readable tables as nodes, each with its
1056
1138
  * fields, and the foreign keys between them as edges. `table-ids` makes those tables the focal
@@ -1058,7 +1140,6 @@ function erdResource(transport) {
1058
1140
  * none.
1059
1141
  */
1060
1142
  async function get(params, options = {}) {
1061
- await transport.require("erd.get", options);
1062
1143
  return transport.requestParsed(Erd, ERD_PATH, {
1063
1144
  ...options,
1064
1145
  query: {
@@ -1068,7 +1149,7 @@ function erdResource(transport) {
1068
1149
  }
1069
1150
  });
1070
1151
  }
1071
- return { get };
1152
+ return { get: explain("get", get) };
1072
1153
  }
1073
1154
  //#endregion
1074
1155
  //#region ../client/src/resources/optional-parsed.ts
@@ -1086,12 +1167,12 @@ async function fetchOptionalParsed(client, path, schema, options = {}) {
1086
1167
  //#region ../client/src/resources/field.ts
1087
1168
  const FieldApiSummary = z.tuple([z.tuple([z.literal("count"), z.number().int()]), z.tuple([z.literal("distincts"), z.number().int()])]);
1088
1169
  function fieldResource(transport) {
1170
+ const { refuse } = explainer(transport, "field");
1089
1171
  /**
1090
1172
  * Get one field by id. A server that labels data sensitivity answers `data_sensitivity`, `null`
1091
1173
  * for an unlabelled field; any other answers the field without the key (`hasDataSensitivity`).
1092
1174
  */
1093
1175
  async function get(id, options = {}) {
1094
- await transport.require("field.get", options);
1095
1176
  const { features } = await transport.server(options);
1096
1177
  return transport.requestParsed(fieldDetailSchema(features), `/api/field/${id}`, { ...options });
1097
1178
  }
@@ -1102,7 +1183,6 @@ function fieldResource(transport) {
1102
1183
  * the label is refused there before the wire. Answers the field the way `get` does.
1103
1184
  */
1104
1185
  async function update(id, params, options = {}) {
1105
- await transport.require("field.update", options);
1106
1186
  await transport.requireFeatures(params.data_sensitivity === void 0 ? [] : ["fieldDataSensitivity"], options);
1107
1187
  const { features } = await transport.server(options);
1108
1188
  return transport.requestParsed(fieldDetailSchema(features), `/api/field/${id}`, {
@@ -1113,11 +1193,11 @@ function fieldResource(transport) {
1113
1193
  }
1114
1194
  /**
1115
1195
  * Label a field's data sensitivity by hand. A person's label is never overwritten by the server's
1116
- * classifier; `null` withdraws it, so whatever label the classifier wrote shows again. Answers
1117
- * the field with the label it now carries.
1196
+ * classifier; `null` withdraws it, so whatever label the classifier wrote shows again. A server
1197
+ * without the column drops the key silently, so the label is refused there before the wire.
1198
+ * Answers the field with the label it now carries.
1118
1199
  */
1119
1200
  async function setDataSensitivity(id, params, options = {}) {
1120
- await transport.require("field.setDataSensitivity", options);
1121
1201
  return transport.requestParsed(FieldWithDataSensitivity, `/api/field/${id}`, {
1122
1202
  ...options,
1123
1203
  method: "PUT",
@@ -1133,7 +1213,6 @@ function fieldResource(transport) {
1133
1213
  * `value` against the display value, ignoring both `searchId` and `limit`.
1134
1214
  */
1135
1215
  async function search(id, searchId, params, options = {}) {
1136
- await transport.require("field.search", options);
1137
1216
  return transport.requestParsed(FieldSearchMatches, `/api/field/${id}/search/${searchId}`, {
1138
1217
  ...options,
1139
1218
  query: {
@@ -1148,7 +1227,6 @@ function fieldResource(transport) {
1148
1227
  * `value` and ignores any text after it, refusing only a `value` that has none.
1149
1228
  */
1150
1229
  async function remapping(id, remappedId, params, options = {}) {
1151
- await transport.require("field.remapping", options);
1152
1230
  const { features } = await transport.server(options);
1153
1231
  return fetchOptionalParsed(transport, `/api/field/${id}/remapping/${remappedId}`, fieldRemappedValueSchema(features, id === remappedId), {
1154
1232
  ...options,
@@ -1157,7 +1235,6 @@ function fieldResource(transport) {
1157
1235
  }
1158
1236
  /** Get the row count and the distinct-value count for a field. */
1159
1237
  async function summary(id, options = {}) {
1160
- await transport.require("field.summary", options);
1161
1238
  const [[, count], [, distincts]] = await transport.requestParsed(FieldApiSummary, `/api/field/${id}/summary`, { ...options });
1162
1239
  return {
1163
1240
  field_id: id,
@@ -1167,13 +1244,12 @@ function fieldResource(transport) {
1167
1244
  }
1168
1245
  /** Get the cached distinct values Metabase holds for a field. */
1169
1246
  async function values(id, options = {}) {
1170
- await transport.require("field.values", options);
1171
1247
  return transport.requestParsed(FieldValues, `/api/field/${id}/values`, { ...options });
1172
1248
  }
1173
1249
  return {
1174
1250
  get,
1175
1251
  update,
1176
- setDataSensitivity,
1252
+ setDataSensitivity: refuse("setDataSensitivity", setDataSensitivity),
1177
1253
  search,
1178
1254
  remapping,
1179
1255
  summary,
@@ -1205,22 +1281,22 @@ const SyncStashStarted = z.object({
1205
1281
  task_id: z.number().int().positive()
1206
1282
  });
1207
1283
  const RemoteSyncSetting = z.string().nullable();
1208
- const RemoteSyncBranchProperty = z.object({ "remote-sync-branch": z.string().nullable().optional() });
1209
- const FORBIDDEN_STATUS = 403;
1210
- const UNREGISTERED_STATUS = 404;
1284
+ const RemoteSyncBranchProperty = SessionProperties.extend({ "remote-sync-branch": z.string().nullable().optional() });
1285
+ const RemoteSyncUrlProperty = SessionProperties.extend({ "remote-sync-url": z.string().nullable().optional() });
1286
+ const BRANCH_UNREADABLE_MESSAGE = "the remote-sync-branch setting is not readable: only admins see it, and a server without remote sync has none";
1287
+ const BRANCH_UNSET_MESSAGE = "git-sync tracks no branch: the remote-sync-branch setting is unset";
1211
1288
  function isRemoteUnreadable(error) {
1212
1289
  if (!(error instanceof HttpError)) return false;
1213
- return error.status === FORBIDDEN_STATUS || error.status === UNREGISTERED_STATUS;
1290
+ return error.status === 403 || error.status === 404;
1214
1291
  }
1215
1292
  function gitSyncResource(transport) {
1293
+ const { explain, refuse, refuseAfterReading } = explainer(transport, "gitSync");
1216
1294
  /** Get the running or most recently finished sync task, or null when the server has none. */
1217
1295
  async function currentTask(options = {}) {
1218
- await transport.require("gitSync.currentTask", options);
1219
1296
  return fetchOptionalParsed(transport, "/api/ee/remote-sync/current-task", SyncTask, options);
1220
1297
  }
1221
1298
  /** Request cancellation of the running sync task, and answer it in the state that left it. */
1222
1299
  async function cancelTask(options = {}) {
1223
- await transport.require("gitSync.cancelTask", options);
1224
1300
  return transport.requestParsed(SyncTask, "/api/ee/remote-sync/current-task/cancel", {
1225
1301
  ...options,
1226
1302
  method: "POST"
@@ -1228,12 +1304,10 @@ function gitSyncResource(transport) {
1228
1304
  }
1229
1305
  /** Whether Metabase holds content changes the remote has not been told about. */
1230
1306
  async function isDirty(options = {}) {
1231
- await transport.require("gitSync.isDirty", options);
1232
1307
  return (await transport.requestParsed(SyncDirtyFlag, "/api/ee/remote-sync/is-dirty", { ...options })).is_dirty;
1233
1308
  }
1234
1309
  /** List the objects whose local state differs from the remote. */
1235
1310
  async function dirty(options = {}) {
1236
- await transport.require("gitSync.dirty", options);
1237
1311
  return {
1238
1312
  data: (await transport.requestParsed(SyncDirtyList, "/api/ee/remote-sync/dirty", { ...options })).dirty,
1239
1313
  total: null
@@ -1244,7 +1318,6 @@ function gitSyncResource(transport) {
1244
1318
  * comparison; `force-refresh` re-reads the remote instead.
1245
1319
  */
1246
1320
  async function hasRemoteChanges(params = {}, options = {}) {
1247
- await transport.require("gitSync.hasRemoteChanges", options);
1248
1321
  return transport.requestParsed(SyncRemoteChanges, "/api/ee/remote-sync/has-remote-changes", {
1249
1322
  ...options,
1250
1323
  query: { "force-refresh": params["force-refresh"] }
@@ -1255,14 +1328,16 @@ function gitSyncResource(transport) {
1255
1328
  * pass `wait` to poll that task until it reaches a terminal status. A server already up to date
1256
1329
  * answers no task id, and there is then nothing to poll. A server that guards the tracked branch
1257
1330
  * refuses with 409 when `expected_branch` disagrees with the setting; left out, it is the tracked
1258
- * branch as read just before the request. `merge` folds the remote's changes into local content
1259
- * by a three-way merge and keeps un-pushed local changes, where a plain import refuses on a dirty
1331
+ * branch as read just before the request, on every server with remote sync. A server that guards
1332
+ * the branch also requires it, so there the request is refused before the wire when the setting
1333
+ * is unset or hidden from the caller; a server without the guard ignores the assertion, and gets
1334
+ * none when there is none to read. `merge` folds the remote's changes into local content by a
1335
+ * three-way merge and keeps un-pushed local changes, where a plain import refuses on a dirty
1260
1336
  * instance.
1261
1337
  */
1262
1338
  async function importFromRemote(params = {}, options = {}) {
1263
- await transport.require("gitSync.import", options);
1339
+ const expectedBranch = await expectedBranchFor(params.expected_branch, options);
1264
1340
  await requireMerge(params.merge, options);
1265
- const expectedBranch = await guardedBranch(params.expected_branch, options);
1266
1341
  const started = await transport.requestParsed(SyncImportStarted, "/api/ee/remote-sync/import", {
1267
1342
  ...options,
1268
1343
  method: "POST",
@@ -1284,28 +1359,38 @@ function gitSyncResource(transport) {
1284
1359
  final: await settle(params.wait, options)
1285
1360
  };
1286
1361
  }
1287
- async function requireMerge(merge, options) {
1288
- if (merge === true) await transport.requireFeatures(["remoteSyncMerge"], options);
1362
+ async function expectedBranchFor(given, options) {
1363
+ if (given !== void 0) return given;
1364
+ return importAssertion(await readTrackedBranch(options));
1289
1365
  }
1290
- async function guardedBranch(given, options) {
1366
+ async function exportBranchFor(given, options) {
1291
1367
  if (given !== void 0) return given;
1292
- const { features } = await transport.server(options);
1293
- if (!features.remoteSyncBranchGuard) return;
1294
- return trackedBranch(options);
1368
+ return exportBranch(await readTrackedBranch(options));
1369
+ }
1370
+ async function readTrackedBranch(options) {
1371
+ const properties = await transport.probe(RemoteSyncBranchProperty, options);
1372
+ return {
1373
+ tracked: properties["remote-sync-branch"],
1374
+ features: profileFromProperties(properties).features
1375
+ };
1376
+ }
1377
+ async function requireMerge(merge, options) {
1378
+ if (merge === true) await transport.requireFeatures(["remoteSyncMerge"], options);
1295
1379
  }
1296
1380
  /**
1297
1381
  * Export Metabase's content to the remote. The endpoint queues a task and returns at once; pass
1298
1382
  * `wait` to poll that task until it reaches a terminal status. A server that guards the tracked
1299
- * branch refuses with 409 when `branch` is not the tracked one; left out, it is the tracked branch
1300
- * as read just before the request there, and the server's own default elsewhere. When the remote
1301
- * has moved past the last sync, a plain export ends in a `conflict` task where `merge` is
1302
- * available, and is refused with 400 before any task elsewhere; `merge` folds the remote's changes
1303
- * in by a three-way merge instead, and `force` overwrites them.
1383
+ * branch refuses with 409 when `branch` is not the tracked one; left out, it is the tracked
1384
+ * branch as read just before the request, and the request is refused before the wire when the
1385
+ * setting is unset or hidden from the caller. A server without the guard exports to a named
1386
+ * `branch` and tracks it from then on; left out, it gets none and exports to its own. When the
1387
+ * remote has moved past the last sync, a plain export ends in a `conflict` task where `merge` is
1388
+ * available, and is refused with 400 before any task elsewhere; `merge` folds the remote's
1389
+ * changes in by a three-way merge instead, and `force` overwrites them.
1304
1390
  */
1305
1391
  async function exportToRemote(params = {}, options = {}) {
1306
- await transport.require("gitSync.export", options);
1392
+ const branchName = await exportBranchFor(params.branch, options);
1307
1393
  await requireMerge(params.merge, options);
1308
- const branchName = await guardedBranch(params.branch, options);
1309
1394
  const started = await transport.requestParsed(SyncExportStarted, "/api/ee/remote-sync/export", {
1310
1395
  ...options,
1311
1396
  method: "POST",
@@ -1333,7 +1418,6 @@ function gitSyncResource(transport) {
1333
1418
  * otherwise.
1334
1419
  */
1335
1420
  async function exportPreflight(params, options = {}) {
1336
- await transport.require("gitSync.exportPreflight", options);
1337
1421
  return transport.requestParsed(SyncExportPreflight, "/api/ee/remote-sync/export-preflight", {
1338
1422
  ...options,
1339
1423
  query: { branch: params.branch }
@@ -1344,7 +1428,6 @@ function gitSyncResource(transport) {
1344
1428
  * queues a task and returns at once; pass `wait` to poll that task to a terminal status.
1345
1429
  */
1346
1430
  async function stash(params, options = {}) {
1347
- await transport.require("gitSync.stash", options);
1348
1431
  const started = await transport.requestParsed(SyncStashStarted, "/api/ee/remote-sync/stash", {
1349
1432
  ...options,
1350
1433
  method: "POST",
@@ -1367,7 +1450,6 @@ function gitSyncResource(transport) {
1367
1450
  }
1368
1451
  /** List the branches the configured remote carries. */
1369
1452
  async function branches(options = {}) {
1370
- await transport.require("gitSync.branches", options);
1371
1453
  return {
1372
1454
  data: (await transport.requestParsed(SyncBranchList, "/api/ee/remote-sync/branches", { ...options })).items,
1373
1455
  total: null
@@ -1375,7 +1457,6 @@ function gitSyncResource(transport) {
1375
1457
  }
1376
1458
  /** Create a branch on the remote and point git-sync at it. */
1377
1459
  async function createBranch(params, options = {}) {
1378
- await transport.require("gitSync.createBranch", options);
1379
1460
  return transport.requestParsed(SyncBranchCreated, "/api/ee/remote-sync/create-branch", {
1380
1461
  ...options,
1381
1462
  method: "POST",
@@ -1384,19 +1465,42 @@ function gitSyncResource(transport) {
1384
1465
  }
1385
1466
  /**
1386
1467
  * Mark one collection as git-synced, or unmark it. The server cascades the flag to descendants
1387
- * by location prefix, and may queue an export task to carry the change to the remote.
1468
+ * by location prefix, and may queue an import task to bring the instance in line with the
1469
+ * remote. A server without `remoteSyncCollectionsOnlyUpdate` validates every settings update as
1470
+ * a remote configuration and fails one naming no remote, after it has applied the flag, so it is
1471
+ * sent the remote's URL and its token as the server shows it, which it checks against the remote
1472
+ * and keeps as they are, or a blank URL when no remote is configured, which clears the remote
1473
+ * settings that are already empty.
1388
1474
  */
1389
1475
  async function setCollectionSynced(collectionId, synced, options = {}) {
1390
- await transport.require("gitSync.setCollectionSynced", options);
1391
1476
  return transport.requestParsed(SyncSettingsUpdateResult, "/api/ee/remote-sync/settings", {
1392
1477
  ...options,
1393
1478
  method: "PUT",
1394
- body: { collections: { [collectionId]: synced } }
1479
+ body: await settingsUpdateBody({ [collectionId]: synced }, options)
1395
1480
  });
1396
1481
  }
1482
+ async function settingsUpdateBody(collections, options) {
1483
+ const { features } = await transport.server(options);
1484
+ if (features.remoteSyncCollectionsOnlyUpdate) return { collections };
1485
+ const url = (await transport.probe(RemoteSyncUrlProperty, options))["remote-sync-url"];
1486
+ if (url === void 0) return { collections };
1487
+ if (url === null) return {
1488
+ collections,
1489
+ "remote-sync-url": ""
1490
+ };
1491
+ const token = await fetchOptionalParsed(transport, "/api/setting/remote-sync-token", RemoteSyncSetting, options);
1492
+ if (token === null) return {
1493
+ collections,
1494
+ "remote-sync-url": url
1495
+ };
1496
+ return {
1497
+ collections,
1498
+ "remote-sync-url": url,
1499
+ "remote-sync-token": token
1500
+ };
1501
+ }
1397
1502
  /** The collections currently in git-sync's scope. */
1398
1503
  async function syncedCollections(options = {}) {
1399
- await transport.require("gitSync.syncedCollections", options);
1400
1504
  return {
1401
1505
  data: (await listCollectionsAs(transport, SyncScopeCollection, options)).filter((entry) => entry.is_remote_synced === true),
1402
1506
  total: null
@@ -1404,7 +1508,6 @@ function gitSyncResource(transport) {
1404
1508
  }
1405
1509
  /** The remote's URL, or null when none is configured or the caller may not read it. */
1406
1510
  async function remoteUrl(options = {}) {
1407
- await transport.require("gitSync.remoteUrl", options);
1408
1511
  try {
1409
1512
  const url = await fetchOptionalParsed(transport, "/api/setting/remote-sync-url", RemoteSyncSetting, options);
1410
1513
  return url === "" ? null : url;
@@ -1414,21 +1517,22 @@ function gitSyncResource(transport) {
1414
1517
  }
1415
1518
  }
1416
1519
  /**
1417
- * The branch git-sync tracks, or null when none is configured. Refuses when the caller may not
1418
- * read the setting (it is admin-only) or the server has no remote-sync module, rather than
1419
- * reading either as unset.
1520
+ * The branch git-sync tracks, or null when none is configured. Refuses when the server has no
1521
+ * remote sync or the caller may not read the setting (it is admin-only), rather than reading
1522
+ * either as unset.
1420
1523
  */
1421
- async function branch(options = {}) {
1422
- await transport.require("gitSync.branch", options);
1423
- const tracked = (await transport.requestParsed(RemoteSyncBranchProperty, PROBE_PATH, { ...options }))["remote-sync-branch"];
1424
- if (tracked === void 0) throw new ConfigError("the remote-sync-branch setting is not readable: it is visible to admins only, and absent on a server without the remote-sync module");
1524
+ function branch(properties) {
1525
+ const tracked = properties["remote-sync-branch"];
1526
+ if (tracked === void 0) throw new ConfigError(BRANCH_UNREADABLE_MESSAGE);
1425
1527
  return tracked;
1426
1528
  }
1427
- /** The branch git-sync tracks, refused when none is configured or the caller may not read it. */
1428
- async function trackedBranch(options = {}) {
1429
- await transport.require("gitSync.trackedBranch", options);
1430
- const tracked = await branch(options);
1431
- if (tracked === null) throw new ConfigError("git-sync tracks no branch: the remote-sync-branch setting is unset");
1529
+ /**
1530
+ * The branch git-sync tracks, refused when none is configured, the server has no remote sync, or
1531
+ * the caller may not read it.
1532
+ */
1533
+ function trackedBranch(properties) {
1534
+ const tracked = branch(properties);
1535
+ if (tracked === null) throw new ConfigError(BRANCH_UNSET_MESSAGE);
1432
1536
  return tracked;
1433
1537
  }
1434
1538
  /**
@@ -1436,7 +1540,6 @@ function gitSyncResource(transport) {
1436
1540
  * answers null, which is terminal in the same sense: nothing further will happen.
1437
1541
  */
1438
1542
  async function waitForTask(wait, options = {}) {
1439
- await transport.require("gitSync.waitForTask", options);
1440
1543
  return settle(wait, options);
1441
1544
  }
1442
1545
  async function settle(wait, options) {
@@ -1446,25 +1549,38 @@ function gitSyncResource(transport) {
1446
1549
  }), (task) => task === null || isSyncTaskTerminal(task.status), wait);
1447
1550
  }
1448
1551
  return {
1449
- currentTask,
1450
- cancelTask,
1451
- isDirty,
1452
- dirty,
1453
- hasRemoteChanges,
1454
- import: importFromRemote,
1455
- export: exportToRemote,
1456
- exportPreflight,
1457
- stash,
1458
- branches,
1459
- createBranch,
1460
- setCollectionSynced,
1461
- syncedCollections,
1552
+ currentTask: explain("currentTask", currentTask),
1553
+ cancelTask: explain("cancelTask", cancelTask),
1554
+ isDirty: explain("isDirty", isDirty),
1555
+ dirty: explain("dirty", dirty),
1556
+ hasRemoteChanges: explain("hasRemoteChanges", hasRemoteChanges),
1557
+ import: explain("import", importFromRemote),
1558
+ export: explain("export", exportToRemote),
1559
+ exportPreflight: explain("exportPreflight", exportPreflight),
1560
+ stash: explain("stash", stash),
1561
+ branches: explain("branches", branches),
1562
+ createBranch: explain("createBranch", createBranch),
1563
+ setCollectionSynced: explain("setCollectionSynced", setCollectionSynced),
1564
+ syncedCollections: refuse("syncedCollections", syncedCollections),
1462
1565
  remoteUrl,
1463
- branch,
1464
- trackedBranch,
1465
- waitForTask
1566
+ branch: refuseAfterReading("branch", RemoteSyncBranchProperty, branch),
1567
+ trackedBranch: refuseAfterReading("trackedBranch", RemoteSyncBranchProperty, trackedBranch),
1568
+ waitForTask: explain("waitForTask", waitForTask)
1466
1569
  };
1467
1570
  }
1571
+ function importAssertion(read) {
1572
+ if (!read.features.remoteSync) return;
1573
+ if (read.features.remoteSyncBranchGuard) return requiredBranch(read.tracked);
1574
+ return typeof read.tracked === "string" ? read.tracked : void 0;
1575
+ }
1576
+ function exportBranch(read) {
1577
+ if (!read.features.remoteSync || !read.features.remoteSyncBranchGuard) return;
1578
+ return requiredBranch(read.tracked);
1579
+ }
1580
+ function requiredBranch(tracked) {
1581
+ if (typeof tracked === "string") return tracked;
1582
+ throw new ConfigError(tracked === null ? BRANCH_UNSET_MESSAGE : BRANCH_UNREADABLE_MESSAGE);
1583
+ }
1468
1584
  //#endregion
1469
1585
  //#region ../client/src/resources/glossary.ts
1470
1586
  const GlossaryApiList = z.object({ data: z.array(Glossary) }).loose();
@@ -1474,7 +1590,6 @@ function glossaryResource(transport) {
1474
1590
  * contains it, case-insensitively.
1475
1591
  */
1476
1592
  async function list(params = {}, options = {}) {
1477
- await transport.require("glossary.list", options);
1478
1593
  return {
1479
1594
  data: (await transport.requestParsed(GlossaryApiList, "/api/glossary", {
1480
1595
  ...options,
@@ -1485,7 +1600,6 @@ function glossaryResource(transport) {
1485
1600
  }
1486
1601
  /** Create a glossary entry, a term and its definition. Terms are unique. */
1487
1602
  async function create(params, options = {}) {
1488
- await transport.require("glossary.create", options);
1489
1603
  return transport.requestParsed(Glossary, "/api/glossary", {
1490
1604
  ...options,
1491
1605
  method: "POST",
@@ -1494,7 +1608,6 @@ function glossaryResource(transport) {
1494
1608
  }
1495
1609
  /** Replace the term and definition of a glossary entry by id. */
1496
1610
  async function update(id, params, options = {}) {
1497
- await transport.require("glossary.update", options);
1498
1611
  return transport.requestParsed(Glossary, `/api/glossary/${id}`, {
1499
1612
  ...options,
1500
1613
  method: "PUT",
@@ -1503,7 +1616,6 @@ function glossaryResource(transport) {
1503
1616
  }
1504
1617
  /** Delete a glossary entry by id. */
1505
1618
  async function remove(id, options = {}) {
1506
- await transport.require("glossary.delete", options);
1507
1619
  await transport.requestRaw(`/api/glossary/${id}`, {
1508
1620
  ...options,
1509
1621
  method: "DELETE",
@@ -1536,9 +1648,9 @@ const UNPUBLISH_TABLES_PATH = "/api/ee/data-studio/table/unpublish-tables";
1536
1648
  const LIBRARY_DATA_TYPE = "library-data";
1537
1649
  const PublishTablesResponse = z.object({ target_collection: Collection.nullable() });
1538
1650
  function libraryResource(transport) {
1651
+ const { explain } = explainer(transport, "library");
1539
1652
  /** Get the Library root and its child collections, or `null` on an instance that has none. */
1540
1653
  async function get(options = {}) {
1541
- await transport.require("library.get", options);
1542
1654
  const { features } = await transport.server(options);
1543
1655
  const wire = await transport.requestParsed(libraryWireSchema(features), LIBRARY_ROOT_PATH, { ...options });
1544
1656
  if (wire === null) return null;
@@ -1557,7 +1669,6 @@ function libraryResource(transport) {
1557
1669
  * back from a refetch, which together make this idempotent.
1558
1670
  */
1559
1671
  async function create(options = {}) {
1560
- await transport.require("library.create", options);
1561
1672
  const existing = await get(options);
1562
1673
  if (existing !== null) return existing;
1563
1674
  await transport.requestRaw(LIBRARY_ROOT_PATH, {
@@ -1570,7 +1681,6 @@ function libraryResource(transport) {
1570
1681
  }
1571
1682
  /** The id of the Library's Data collection, creating the Library first when it does not exist. */
1572
1683
  async function ensureDataCollectionId(options = {}) {
1573
- await transport.require("library.ensureDataCollectionId", options);
1574
1684
  const data = (await create(options)).effective_children.find((child) => child.type === LIBRARY_DATA_TYPE);
1575
1685
  if (data === void 0) throw new Error("Library has no Data collection to publish into");
1576
1686
  if (typeof data.id !== "number") throw new Error(`Library Data collection has a non-numeric id ${String(data.id)}`);
@@ -1583,7 +1693,6 @@ function libraryResource(transport) {
1583
1693
  * `LibraryPublishTablesInput` is refused before any request.
1584
1694
  */
1585
1695
  async function publishTables(params, options = {}) {
1586
- await transport.require("library.publishTables", options);
1587
1696
  const body = parseRequestBody(LibraryPublishTablesInput, params, "tables to publish");
1588
1697
  return (await transport.requestParsed(PublishTablesResponse, PUBLISH_TABLES_PATH, {
1589
1698
  ...options,
@@ -1597,7 +1706,6 @@ function libraryResource(transport) {
1597
1706
  * table, so selectors that do not match `TableSelectors` are refused before any request.
1598
1707
  */
1599
1708
  async function unpublishTables(params, options = {}) {
1600
- await transport.require("library.unpublishTables", options);
1601
1709
  const body = parseRequestBody(TableSelectors, params, "table selectors");
1602
1710
  await transport.requestRaw(UNPUBLISH_TABLES_PATH, {
1603
1711
  ...options,
@@ -1607,20 +1715,20 @@ function libraryResource(transport) {
1607
1715
  });
1608
1716
  }
1609
1717
  return {
1610
- get,
1611
- create,
1612
- ensureDataCollectionId,
1613
- publishTables,
1614
- unpublishTables
1718
+ get: explain("get", get),
1719
+ create: explain("create", create),
1720
+ ensureDataCollectionId: explain("ensureDataCollectionId", ensureDataCollectionId),
1721
+ publishTables: explain("publishTables", publishTables),
1722
+ unpublishTables: explain("unpublishTables", unpublishTables)
1615
1723
  };
1616
1724
  }
1617
1725
  //#endregion
1618
1726
  //#region ../client/src/resources/measure.ts
1619
1727
  const MeasureApiList = z.array(Measure);
1620
1728
  function measureResource(transport) {
1729
+ const { explain } = explainer(transport, "measure");
1621
1730
  /** List measures. */
1622
1731
  async function list(options = {}) {
1623
- await transport.require("measure.list", options);
1624
1732
  return {
1625
1733
  data: await transport.requestParsed(MeasureApiList, "/api/measure", { ...options }),
1626
1734
  total: null
@@ -1628,12 +1736,10 @@ function measureResource(transport) {
1628
1736
  }
1629
1737
  /** Get one measure by id. */
1630
1738
  async function get(id, options = {}) {
1631
- await transport.require("measure.get", options);
1632
1739
  return transport.requestParsed(Measure, `/api/measure/${id}`, { ...options });
1633
1740
  }
1634
1741
  /** Create a measure — a saved aggregation tied to a table — from a full measure body. */
1635
1742
  async function create(params, options = {}) {
1636
- await transport.require("measure.create", options);
1637
1743
  return transport.requestParsed(Measure, "/api/measure", {
1638
1744
  ...options,
1639
1745
  method: "POST",
@@ -1645,7 +1751,6 @@ function measureResource(transport) {
1645
1751
  * required and lands in the audit log.
1646
1752
  */
1647
1753
  async function update(id, params, options = {}) {
1648
- await transport.require("measure.update", options);
1649
1754
  return transport.requestParsed(Measure, `/api/measure/${id}`, {
1650
1755
  ...options,
1651
1756
  method: "PUT",
@@ -1657,18 +1762,17 @@ function measureResource(transport) {
1657
1762
  * so it carries the same required `revision_message`.
1658
1763
  */
1659
1764
  async function archive(id, params, options = {}) {
1660
- await transport.require("measure.archive", options);
1661
1765
  return update(id, {
1662
1766
  archived: true,
1663
1767
  revision_message: params.revision_message
1664
1768
  }, options);
1665
1769
  }
1666
1770
  return {
1667
- list,
1668
- get,
1669
- create,
1670
- update,
1671
- archive
1771
+ list: explain("list", list),
1772
+ get: explain("get", get),
1773
+ create: explain("create", create),
1774
+ update: explain("update", update),
1775
+ archive: explain("archive", archive)
1672
1776
  };
1673
1777
  }
1674
1778
  //#endregion
@@ -1809,12 +1913,12 @@ const MetricDimensionListing = z.object({
1809
1913
  //#endregion
1810
1914
  //#region ../client/src/resources/metric.ts
1811
1915
  function metricResource(transport) {
1916
+ const { explain } = explainer(transport, "metric");
1812
1917
  /**
1813
1918
  * Run a metric definition and return the query result envelope: one metric or measure, or an
1814
1919
  * arithmetic expression over several, each leaf filtered and projected on its own `lib/uuid`.
1815
1920
  */
1816
1921
  async function query(definition, options = {}) {
1817
- await transport.require("metric.query", options);
1818
1922
  return transport.requestParsed(CardQueryResult, "/api/metric/dataset", {
1819
1923
  ...options,
1820
1924
  method: "POST",
@@ -1826,7 +1930,6 @@ function metricResource(transport) {
1826
1930
  * Takes the same definition `query` does.
1827
1931
  */
1828
1932
  async function breakoutValues(definition, options = {}) {
1829
- await transport.require("metric.breakoutValues", options);
1830
1933
  return transport.requestParsed(MetricBreakoutValues, "/api/metric/breakout-values", {
1831
1934
  ...options,
1832
1935
  method: "POST",
@@ -1838,7 +1941,6 @@ function metricResource(transport) {
1838
1941
  * add, grouped by source table. `query` filters both by a name substring.
1839
1942
  */
1840
1943
  async function dimensions(id, params = {}, options = {}) {
1841
- await transport.require("metric.dimensions", options);
1842
1944
  return transport.requestParsed(MetricDimensionListing, `/api/metric/${id}/dimension`, {
1843
1945
  ...options,
1844
1946
  query: {
@@ -1849,14 +1951,15 @@ function metricResource(transport) {
1849
1951
  });
1850
1952
  }
1851
1953
  return {
1852
- query,
1853
- breakoutValues,
1854
- dimensions
1954
+ query: explain("query", query),
1955
+ breakoutValues: explain("breakoutValues", breakoutValues),
1956
+ dimensions: explain("dimensions", dimensions)
1855
1957
  };
1856
1958
  }
1857
1959
  //#endregion
1858
1960
  //#region ../client/src/resources/moderation-review.ts
1859
1961
  function moderationReviewResource(transport) {
1962
+ const { explain } = explainer(transport, "moderationReview");
1860
1963
  /**
1861
1964
  * Create a moderation review on a card or dashboard. `status: "verified"` marks the item
1862
1965
  * verified, `null` or an absent status records a note that leaves it unverified. The new review
@@ -1864,7 +1967,6 @@ function moderationReviewResource(transport) {
1864
1967
  * A body that does not match `ModerationReviewCreateInput` is refused before any request.
1865
1968
  */
1866
1969
  async function create(params, options = {}) {
1867
- await transport.require("moderationReview.create", options);
1868
1970
  const body = parseRequestBody(ModerationReviewCreateInput, params, "moderation review");
1869
1971
  return transport.requestParsed(ModerationReview, "/api/moderation-review", {
1870
1972
  ...options,
@@ -1872,7 +1974,7 @@ function moderationReviewResource(transport) {
1872
1974
  body
1873
1975
  });
1874
1976
  }
1875
- return { create };
1977
+ return { create: explain("create", create) };
1876
1978
  }
1877
1979
  //#endregion
1878
1980
  //#region ../client/src/resources/notification.ts
@@ -1903,7 +2005,6 @@ function notificationResource(transport) {
1903
2005
  * the request pins the card payload type.
1904
2006
  */
1905
2007
  async function list(params = {}, options = {}) {
1906
- await transport.require("notification.list", options);
1907
2008
  return {
1908
2009
  data: await transport.requestParsed(NotificationApiList, "/api/notification", {
1909
2010
  ...options,
@@ -1920,12 +2021,10 @@ function notificationResource(transport) {
1920
2021
  }
1921
2022
  /** Get one question alert by id, refusing an id that names a system-event notification. */
1922
2023
  async function get(id, options = {}) {
1923
- await transport.require("notification.get", options);
1924
2024
  return assertCardAlert(await transport.requestParsed(Notification, `/api/notification/${id}`, { ...options }));
1925
2025
  }
1926
2026
  /** Create a question alert — its card payload, schedules and handlers — from a full body. */
1927
2027
  async function create(params, options = {}) {
1928
- await transport.require("notification.create", options);
1929
2028
  return transport.requestParsed(Notification, "/api/notification", {
1930
2029
  ...options,
1931
2030
  method: "POST",
@@ -1934,7 +2033,6 @@ function notificationResource(transport) {
1934
2033
  }
1935
2034
  /** Update a question alert by id, merging the patch over the stored notification. */
1936
2035
  async function update(id, params, options = {}) {
1937
- await transport.require("notification.update", options);
1938
2036
  const current = await get(id, options);
1939
2037
  return transport.requestParsed(Notification, `/api/notification/${id}`, {
1940
2038
  ...options,
@@ -1944,7 +2042,6 @@ function notificationResource(transport) {
1944
2042
  }
1945
2043
  /** Archive a question alert by id, stopping every delivery. Metabase models this as an update. */
1946
2044
  async function archive(id, options = {}) {
1947
- await transport.require("notification.archive", options);
1948
2045
  return update(id, { active: false }, options);
1949
2046
  }
1950
2047
  /**
@@ -1952,7 +2049,6 @@ function notificationResource(transport) {
1952
2049
  * body.
1953
2050
  */
1954
2051
  async function send(id, options = {}) {
1955
- await transport.require("notification.send", options);
1956
2052
  await get(id, options);
1957
2053
  await transport.requestRaw(`/api/notification/${id}/send`, {
1958
2054
  ...options,
@@ -2012,7 +2108,6 @@ function permissionResource(transport) {
2012
2108
  * one database is the question.
2013
2109
  */
2014
2110
  async function graph(options = {}) {
2015
- await transport.require("permission.graph", options);
2016
2111
  return transport.requestParsed(PermissionsGraph, "/api/permissions/graph", { ...options });
2017
2112
  }
2018
2113
  /**
@@ -2021,12 +2116,10 @@ function permissionResource(transport) {
2021
2116
  * group up under the database and read `view-data` and `create-queries` down to the table id.
2022
2117
  */
2023
2118
  async function databaseGraph(id, options = {}) {
2024
- await transport.require("permission.databaseGraph", options);
2025
2119
  return transport.requestParsed(PermissionsGraph, `/api/permissions/graph/db/${id}`, { ...options });
2026
2120
  }
2027
2121
  /** Get the data permissions of one group on every database. Admin only. */
2028
2122
  async function groupGraph(id, options = {}) {
2029
- await transport.require("permission.groupGraph", options);
2030
2123
  return transport.requestParsed(PermissionsGraph, `/api/permissions/graph/group/${id}`, { ...options });
2031
2124
  }
2032
2125
  return {
@@ -2048,7 +2141,6 @@ function mergePulseUpdate(current, patch) {
2048
2141
  function pulseResource(transport) {
2049
2142
  /** List dashboard subscriptions, optionally narrowed to one dashboard or to the archived ones. */
2050
2143
  async function list(params = {}, options = {}) {
2051
- await transport.require("pulse.list", options);
2052
2144
  return {
2053
2145
  data: await transport.requestParsed(PulseApiList, "/api/pulse", {
2054
2146
  ...options,
@@ -2062,21 +2154,27 @@ function pulseResource(transport) {
2062
2154
  }
2063
2155
  /** Get one dashboard subscription by id. */
2064
2156
  async function get(id, options = {}) {
2065
- await transport.require("pulse.get", options);
2066
2157
  return transport.requestParsed(Pulse, `/api/pulse/${id}`, { ...options });
2067
2158
  }
2068
- /** Create a dashboard subscription — its cards and its delivery channels — from a full body. */
2159
+ /**
2160
+ * Create a dashboard subscription — its cards and its delivery channels — from a full body.
2161
+ * Non-empty `parameters` need the `dashboard_subscription_filters` premium feature, and are
2162
+ * refused before the wire without it.
2163
+ */
2069
2164
  async function create(params, options = {}) {
2070
- await transport.require("pulse.create", options);
2165
+ await requireSubscriptionFilters(params.parameters, options);
2071
2166
  return transport.requestParsed(Pulse, "/api/pulse", {
2072
2167
  ...options,
2073
2168
  method: "POST",
2074
2169
  body: params
2075
2170
  });
2076
2171
  }
2077
- /** Update a dashboard subscription by id, carrying the server-defaulted flags forward. */
2172
+ /**
2173
+ * Update a dashboard subscription by id, carrying the server-defaulted flags forward. Non-empty
2174
+ * `parameters` are refused as on `create`.
2175
+ */
2078
2176
  async function update(id, params, options = {}) {
2079
- await transport.require("pulse.update", options);
2177
+ await requireSubscriptionFilters(params.parameters, options);
2080
2178
  const current = await get(id, options);
2081
2179
  return transport.requestParsed(Pulse, `/api/pulse/${id}`, {
2082
2180
  ...options,
@@ -2089,9 +2187,11 @@ function pulseResource(transport) {
2089
2187
  * update, and disables each of the subscription's channels as a side effect.
2090
2188
  */
2091
2189
  async function archive(id, options = {}) {
2092
- await transport.require("pulse.archive", options);
2093
2190
  return update(id, { archived: true }, options);
2094
2191
  }
2192
+ async function requireSubscriptionFilters(parameters, options) {
2193
+ if (parameters !== void 0 && parameters.length > 0) await transport.requireFeatures(["dashboardSubscriptionFilters"], options);
2194
+ }
2095
2195
  return {
2096
2196
  list,
2097
2197
  get,
@@ -2194,6 +2294,7 @@ const ReplacementRunStarted = z.object({ run_id: z.number().int().positive() });
2194
2294
  const ReplacementApiRunList = z.array(ReplacementRun);
2195
2295
  const CancelRunResponse = z.object({ success: z.literal(true) });
2196
2296
  function replacementResource(transport) {
2297
+ const { explain } = explainer(transport, "replacement");
2197
2298
  /**
2198
2299
  * Check whether every usage of a source entity could be rewritten to read a target entity
2199
2300
  * instead: the two must share a database, the target must not depend on the source, every source
@@ -2201,7 +2302,6 @@ function replacementResource(transport) {
2201
2302
  * implicitly or sandboxed. Admins only.
2202
2303
  */
2203
2304
  async function checkReplaceSource(params, options = {}) {
2204
- await transport.require("replacement.checkReplaceSource", options);
2205
2305
  return transport.requestParsed(ReplacementCheck, "/api/ee/replacement/check-replace-source", {
2206
2306
  ...options,
2207
2307
  method: "POST",
@@ -2214,7 +2314,6 @@ function replacementResource(transport) {
2214
2314
  * while another replacement is running. Admins only.
2215
2315
  */
2216
2316
  async function replaceSource(params, options = {}) {
2217
- await transport.require("replacement.replaceSource", options);
2218
2317
  return transport.requestParsed(ReplacementRunStarted, "/api/ee/replacement/replace-source", {
2219
2318
  ...options,
2220
2319
  method: "POST",
@@ -2229,7 +2328,6 @@ function replacementResource(transport) {
2229
2328
  * replacement is running. Admins only.
2230
2329
  */
2231
2330
  async function replaceModelWithTransform(params, options = {}) {
2232
- await transport.require("replacement.replaceModelWithTransform", options);
2233
2331
  return transport.requestParsed(ReplacementRunStarted, "/api/ee/replacement/replace-model-with-transform", {
2234
2332
  ...options,
2235
2333
  method: "POST",
@@ -2241,7 +2339,6 @@ function replacementResource(transport) {
2241
2339
  * `false` only the ended ones. Admins only.
2242
2340
  */
2243
2341
  async function listRuns(params = {}, options = {}) {
2244
- await transport.require("replacement.listRuns", options);
2245
2342
  return {
2246
2343
  data: await transport.requestParsed(ReplacementApiRunList, "/api/ee/replacement/runs", {
2247
2344
  ...options,
@@ -2252,24 +2349,22 @@ function replacementResource(transport) {
2252
2349
  }
2253
2350
  /** Get one replacement run by id, with its status and progress. Admins only. */
2254
2351
  async function getRun(id, options = {}) {
2255
- await transport.require("replacement.getRun", options);
2256
2352
  return transport.requestParsed(ReplacementRun, `/api/ee/replacement/runs/${id}`, { ...options });
2257
2353
  }
2258
2354
  /** Cancel the replacement in progress. Refuses with a 409 once the run has ended. Admins only. */
2259
2355
  async function cancelRun(id, options = {}) {
2260
- await transport.require("replacement.cancelRun", options);
2261
2356
  await transport.requestParsed(CancelRunResponse, `/api/ee/replacement/runs/${id}/cancel`, {
2262
2357
  ...options,
2263
2358
  method: "POST"
2264
2359
  });
2265
2360
  }
2266
2361
  return {
2267
- checkReplaceSource,
2268
- replaceSource,
2269
- replaceModelWithTransform,
2270
- listRuns,
2271
- getRun,
2272
- cancelRun
2362
+ checkReplaceSource: explain("checkReplaceSource", checkReplaceSource),
2363
+ replaceSource: explain("replaceSource", replaceSource),
2364
+ replaceModelWithTransform: explain("replaceModelWithTransform", replaceModelWithTransform),
2365
+ listRuns: explain("listRuns", listRuns),
2366
+ getRun: explain("getRun", getRun),
2367
+ cancelRun: explain("cancelRun", cancelRun)
2273
2368
  };
2274
2369
  }
2275
2370
  //#endregion
@@ -2280,7 +2375,8 @@ const RevisionEntity = z.enum([
2280
2375
  "document",
2281
2376
  "measure",
2282
2377
  "segment",
2283
- "transform"
2378
+ "transform",
2379
+ "transform-test"
2284
2380
  ]);
2285
2381
  const RevisionUser = z.object({
2286
2382
  id: z.number().int(),
@@ -2344,17 +2440,20 @@ const RevisionApiRevert = z.union([Revision.transform((revision) => ({
2344
2440
  }))]);
2345
2441
  const ENTITY_FEATURES = {
2346
2442
  measure: "measures",
2347
- transform: "transforms"
2443
+ transform: "transforms",
2444
+ "transform-test": "transformTests"
2348
2445
  };
2349
2446
  function entityFeatures(entity) {
2350
2447
  const feature = ENTITY_FEATURES[entity];
2351
- return feature === void 0 ? [] : [feature];
2448
+ return feature === void 0 ? [] : [{
2449
+ feature,
2450
+ fields: ["entity"]
2451
+ }];
2352
2452
  }
2353
2453
  function revisionResource(transport) {
2454
+ const { explain } = explainer(transport, "revision");
2354
2455
  /** List the revisions of an entity the caller may read, newest first, each with its diff. */
2355
2456
  async function list(entity, id, options = {}) {
2356
- await transport.require("revision.list", options);
2357
- await transport.requireFeatures(entityFeatures(entity), options);
2358
2457
  return {
2359
2458
  data: await transport.requestParsed(RevisionApiList, `/api/revision/${entity}/${id}`, { ...options }),
2360
2459
  total: null
@@ -2365,8 +2464,6 @@ function revisionResource(transport) {
2365
2464
  * card the permission to run the query being restored.
2366
2465
  */
2367
2466
  async function revert(params, options = {}) {
2368
- await transport.require("revision.revert", options);
2369
- await transport.requireFeatures(entityFeatures(params.entity), options);
2370
2467
  return transport.requestParsed(RevisionApiRevert, "/api/revision/revert", {
2371
2468
  ...options,
2372
2469
  method: "POST",
@@ -2374,8 +2471,8 @@ function revisionResource(transport) {
2374
2471
  });
2375
2472
  }
2376
2473
  return {
2377
- list,
2378
- revert
2474
+ list: explain("list", list, entityFeatures),
2475
+ revert: explain("revert", revert, (params) => entityFeatures(params.entity))
2379
2476
  };
2380
2477
  }
2381
2478
  //#endregion
@@ -2384,7 +2481,14 @@ const SearchApiResponse = z.object({
2384
2481
  data: z.array(SearchResult),
2385
2482
  total: z.number().int().nonnegative()
2386
2483
  }).loose();
2484
+ function verifiedFeatures(params = {}) {
2485
+ return params.verified === true ? [{
2486
+ feature: "contentVerification",
2487
+ fields: ["verified"]
2488
+ }] : [];
2489
+ }
2387
2490
  function searchResource(transport) {
2491
+ const { explain } = explainer(transport, "search");
2388
2492
  /**
2389
2493
  * Search over the instance's content, ranked against `q`. `models` narrows which kinds of entity
2390
2494
  * may match, `archived` swaps the active set for the archived one, `table_db_id` restricts to
@@ -2394,9 +2498,12 @@ function searchResource(transport) {
2394
2498
  * actions, transforms), `include_metadata` attaches each card's `result_metadata`,
2395
2499
  * `include_dashboard_questions` also matches questions saved into a dashboard (excluded by
2396
2500
  * default), and `limit`/`offset` are the window the server applies before ranking hydration.
2501
+ * `verified` needs the `content_verification` premium feature, and is refused before the wire
2502
+ * without it: a server granting neither it nor `official_collections` refuses the filter, and one
2503
+ * granting `official_collections` alone takes it without applying it.
2397
2504
  */
2398
2505
  async function query(params = {}, options = {}) {
2399
- await transport.require("search.query", options);
2506
+ await transport.requireFeatures(verifiedFeatures(params).map((requirement) => requirement.feature), options);
2400
2507
  const response = await transport.requestParsed(SearchApiResponse, "/api/search", {
2401
2508
  ...options,
2402
2509
  query: {
@@ -2419,7 +2526,7 @@ function searchResource(transport) {
2419
2526
  total: response.total
2420
2527
  };
2421
2528
  }
2422
- return { query };
2529
+ return { query: explain("query", query, verifiedFeatures) };
2423
2530
  }
2424
2531
  //#endregion
2425
2532
  //#region ../client/src/resources/segment.ts
@@ -2427,7 +2534,6 @@ const SegmentApiList = z.array(Segment);
2427
2534
  function segmentResource(transport) {
2428
2535
  /** List every segment the caller can see. */
2429
2536
  async function list(options = {}) {
2430
- await transport.require("segment.list", options);
2431
2537
  return {
2432
2538
  data: await transport.requestParsed(SegmentApiList, "/api/segment", { ...options }),
2433
2539
  total: null
@@ -2435,12 +2541,10 @@ function segmentResource(transport) {
2435
2541
  }
2436
2542
  /** Get one segment by id. */
2437
2543
  async function get(id, options = {}) {
2438
- await transport.require("segment.get", options);
2439
2544
  return transport.requestParsed(Segment, `/api/segment/${id}`, { ...options });
2440
2545
  }
2441
2546
  /** Create a segment — a reusable row filter over one table — from a full segment body. */
2442
2547
  async function create(params, options = {}) {
2443
- await transport.require("segment.create", options);
2444
2548
  return transport.requestParsed(Segment, "/api/segment", {
2445
2549
  ...options,
2446
2550
  method: "POST",
@@ -2452,7 +2556,6 @@ function segmentResource(transport) {
2452
2556
  * required: Metabase records every segment change in its revision history.
2453
2557
  */
2454
2558
  async function update(id, params, options = {}) {
2455
- await transport.require("segment.update", options);
2456
2559
  return transport.requestParsed(Segment, `/api/segment/${id}`, {
2457
2560
  ...options,
2458
2561
  method: "PUT",
@@ -2464,7 +2567,6 @@ function segmentResource(transport) {
2464
2567
  * same required `revision_message`.
2465
2568
  */
2466
2569
  async function archive(id, params, options = {}) {
2467
- await transport.require("segment.archive", options);
2468
2570
  return update(id, {
2469
2571
  archived: true,
2470
2572
  revision_message: params.revision_message
@@ -2489,7 +2591,6 @@ function rethrowSettingError(error, key) {
2489
2591
  function settingResource(transport) {
2490
2592
  /** Get all settings and their values, with descriptions and environment-variable names. */
2491
2593
  async function list(options = {}) {
2492
- await transport.require("setting.list", options);
2493
2594
  return {
2494
2595
  data: await transport.requestParsed(SettingApiList, "/api/setting", { ...options }),
2495
2596
  total: null
@@ -2500,12 +2601,10 @@ function settingResource(transport) {
2500
2601
  * reaches the caller as `null`; a string-typed setting answers bare text rather than JSON.
2501
2602
  */
2502
2603
  async function get(key, options = {}) {
2503
- await transport.require("setting.get", options);
2504
2604
  return fetchOptionalParsed(transport, `/api/setting/${encodeURIComponent(key)}`, z.unknown(), options).catch((error) => rethrowSettingError(error, key));
2505
2605
  }
2506
2606
  /** Set a single setting's value by key. The endpoint answers 204 with no body. */
2507
2607
  async function set(key, value, options = {}) {
2508
- await transport.require("setting.set", options);
2509
2608
  await transport.requestRaw(`/api/setting/${encodeURIComponent(key)}`, {
2510
2609
  ...options,
2511
2610
  method: "PUT",
@@ -2527,7 +2626,6 @@ function setupResource(transport) {
2527
2626
  * applying the instance preferences. The setup token is single-use.
2528
2627
  */
2529
2628
  async function create(params, options = {}) {
2530
- await transport.require("setup.create", options);
2531
2629
  return transport.requestParsed(SetupResult, "/api/setup", {
2532
2630
  ...options,
2533
2631
  method: "POST",
@@ -2542,7 +2640,6 @@ const SnippetApiList = z.array(Snippet);
2542
2640
  function snippetResource(transport) {
2543
2641
  /** List native query snippets. `archived` swaps the listing to archived snippets. */
2544
2642
  async function list(params = {}, options = {}) {
2545
- await transport.require("snippet.list", options);
2546
2643
  return {
2547
2644
  data: await transport.requestParsed(SnippetApiList, "/api/native-query-snippet", {
2548
2645
  ...options,
@@ -2553,12 +2650,10 @@ function snippetResource(transport) {
2553
2650
  }
2554
2651
  /** Get one native query snippet by id. */
2555
2652
  async function get(id, options = {}) {
2556
- await transport.require("snippet.get", options);
2557
2653
  return transport.requestParsed(Snippet, `/api/native-query-snippet/${id}`, { ...options });
2558
2654
  }
2559
2655
  /** Create a native query snippet from a name, its SQL fragment, and an optional collection. */
2560
2656
  async function create(params, options = {}) {
2561
- await transport.require("snippet.create", options);
2562
2657
  return transport.requestParsed(Snippet, "/api/native-query-snippet", {
2563
2658
  ...options,
2564
2659
  method: "POST",
@@ -2567,7 +2662,6 @@ function snippetResource(transport) {
2567
2662
  }
2568
2663
  /** Update a native query snippet by id, patching only the fields the body carries. */
2569
2664
  async function update(id, params, options = {}) {
2570
- await transport.require("snippet.update", options);
2571
2665
  return transport.requestParsed(Snippet, `/api/native-query-snippet/${id}`, {
2572
2666
  ...options,
2573
2667
  method: "PUT",
@@ -2579,7 +2673,6 @@ function snippetResource(transport) {
2579
2673
  * own endpoint.
2580
2674
  */
2581
2675
  async function archive(id, options = {}) {
2582
- await transport.require("snippet.archive", options);
2583
2676
  return update(id, { archived: true }, options);
2584
2677
  }
2585
2678
  return {
@@ -2609,13 +2702,23 @@ const LIST_PARAM_FEATURES = [
2609
2702
  ["published-only", "tableListPublishedFilter"]
2610
2703
  ];
2611
2704
  function listParamFeatures(params) {
2612
- return LIST_PARAM_FEATURES.filter(([param]) => params[param] !== void 0).map(([, feature]) => feature);
2705
+ return LIST_PARAM_FEATURES.filter(([param]) => params[param] === true).map(([, feature]) => feature);
2706
+ }
2707
+ function dataLayerFeatures(value, field) {
2708
+ return isTierName(value) ? [{
2709
+ feature: "tableDataLayerTiers",
2710
+ fields: [field]
2711
+ }] : [];
2712
+ }
2713
+ function isTierName(value) {
2714
+ return value !== null && value !== void 0 && TableDataLayerTier.safeParse(value).success;
2613
2715
  }
2614
2716
  const UPLOAD_UPDATE_PATHS = {
2615
2717
  append: "append-csv",
2616
2718
  replace: "replace-csv"
2617
2719
  };
2618
2720
  function tableResource(transport) {
2721
+ const { explain } = explainer(transport, "table");
2619
2722
  /**
2620
2723
  * List every table the caller can see, across all databases. `term` matches names and display
2621
2724
  * names; `can-query` and `can-write` keep only the tables the caller may query or edit the
@@ -2623,9 +2726,7 @@ function tableResource(transport) {
2623
2726
  * tables nothing depends on; `published-only` keeps tables published to the library.
2624
2727
  */
2625
2728
  async function list(params = {}, options = {}) {
2626
- await transport.require("table.list", options);
2627
2729
  await transport.requireFeatures(listParamFeatures(params), options);
2628
- await requireDataLayer(params["data-layer"], options);
2629
2730
  return {
2630
2731
  data: await transport.requestParsed(TableApiList, "/api/table", {
2631
2732
  ...options,
@@ -2649,31 +2750,30 @@ function tableResource(transport) {
2649
2750
  }
2650
2751
  /** Get one table by id, without its fields. */
2651
2752
  async function get(id, options = {}) {
2652
- await transport.require("table.get", options);
2653
2753
  return transport.requestParsed(Table, `/api/table/${id}`, { ...options });
2654
2754
  }
2655
2755
  /** Update a table by id, patching only the fields the body carries. */
2656
2756
  async function update(id, params, options = {}) {
2657
- await transport.require("table.update", options);
2658
- await requireDataLayer(params.data_layer, options);
2659
- return transport.requestParsed(Table, `/api/table/${id}`, {
2757
+ await refuseRewrittenDataLayer(params.data_layer, options);
2758
+ await requireDataAuthorityNull(params.data_authority, options);
2759
+ const path = `/api/table/${id}`;
2760
+ const updated = await transport.requestParsed(Table, path, {
2660
2761
  ...options,
2661
2762
  method: "PUT",
2662
2763
  body: params
2663
2764
  });
2765
+ assertCollectionWritten(path, params.collection_id, updated);
2766
+ return updated;
2664
2767
  }
2665
- async function requireDataLayer(value, options) {
2666
- if (value === null || value === void 0) return;
2667
- if (TableDataLayerTier.safeParse(value).success) {
2668
- await transport.requireFeatures(["tableDataLayerTiers"], options);
2669
- return;
2670
- }
2671
- const { features } = await transport.server(options);
2672
- if (features.tableDataLayerTiers) throw new ConfigError(`data_layer "${value}" is a medallion name; this server names a table's layer ${TableDataLayerTier.options.join(", ")}`);
2768
+ async function refuseRewrittenDataLayer(value, options) {
2769
+ if (value === null || value === void 0 || isTierName(value)) return;
2770
+ const profile = await transport.preflightServer(options);
2771
+ if (profile === null) return;
2772
+ const failure = supersedingFeature("tableDataLayerTiers", profile, `data_layer "${value}" is a medallion name; this server names a table's layer ${TableDataLayerTier.options.join(", ")} and would map it onto one of those without a word`);
2773
+ if (failure !== null) transport.refuseBeforeSending(new CapabilityError(failure));
2673
2774
  }
2674
2775
  /** Get a table by id with its fields hydrated — the metadata the query builder runs on. */
2675
2776
  async function queryMetadata(id, options = {}) {
2676
- await transport.require("table.queryMetadata", options);
2677
2777
  return transport.requestParsed(TableQueryMetadata, `/api/table/${id}/query_metadata`, { ...options });
2678
2778
  }
2679
2779
  /**
@@ -2681,7 +2781,6 @@ function tableResource(transport) {
2681
2781
  * fields answers no content, which is the same empty list.
2682
2782
  */
2683
2783
  async function fks(id, options = {}) {
2684
- await transport.require("table.fks", options);
2685
2784
  return {
2686
2785
  data: await fetchOptionalParsed(transport, `/api/table/${id}/fks`, TableApiFks, { ...options }) ?? [],
2687
2786
  total: null
@@ -2693,7 +2792,6 @@ function tableResource(transport) {
2693
2792
  * connect to is a 422.
2694
2793
  */
2695
2794
  async function syncSchema(id, options = {}) {
2696
- await transport.require("table.syncSchema", options);
2697
2795
  return {
2698
2796
  id,
2699
2797
  status: (await transport.requestParsed(SyncSchemaResponse, `/api/table/${id}/sync_schema`, {
@@ -2707,7 +2805,6 @@ function tableResource(transport) {
2707
2805
  * cached set. The scan runs after the call returns.
2708
2806
  */
2709
2807
  async function rescanValues(id, options = {}) {
2710
- await transport.require("table.rescanValues", options);
2711
2808
  return {
2712
2809
  id,
2713
2810
  status: (await transport.requestParsed(FieldValuesResponse, `/api/table/${id}/rescan_values`, {
@@ -2722,7 +2819,6 @@ function tableResource(transport) {
2722
2819
  * time the field's values are read.
2723
2820
  */
2724
2821
  async function discardValues(id, options = {}) {
2725
- await transport.require("table.discardValues", options);
2726
2822
  return {
2727
2823
  id,
2728
2824
  status: (await transport.requestParsed(FieldValuesResponse, `/api/table/${id}/discard_values`, {
@@ -2737,7 +2833,6 @@ function tableResource(transport) {
2737
2833
  * selectors matched a table.
2738
2834
  */
2739
2835
  async function bulkEdit(params, options = {}) {
2740
- await transport.require("table.bulkEdit", options);
2741
2836
  await requireDataAuthorityNull(params.data_authority, options);
2742
2837
  await transport.requestParsed(BulkEditResponse, BULK_EDIT_PATH, {
2743
2838
  ...options,
@@ -2771,7 +2866,6 @@ function tableResource(transport) {
2771
2866
  * call is a 422; the syncs run after it returns.
2772
2867
  */
2773
2868
  async function bulkSyncSchema(selectors, options = {}) {
2774
- await transport.require("table.bulkSyncSchema", options);
2775
2869
  return postSelection(BULK_SYNC_SCHEMA_PATH, selectors, options);
2776
2870
  }
2777
2871
  /**
@@ -2779,7 +2873,6 @@ function tableResource(transport) {
2779
2873
  * are already cached. The scans run after the call returns.
2780
2874
  */
2781
2875
  async function bulkRescanValues(selectors, options = {}) {
2782
- await transport.require("table.bulkRescanValues", options);
2783
2876
  return postSelection(BULK_RESCAN_VALUES_PATH, selectors, options);
2784
2877
  }
2785
2878
  /**
@@ -2787,7 +2880,6 @@ function tableResource(transport) {
2787
2880
  * included. No scan recreates a discarded set.
2788
2881
  */
2789
2882
  async function bulkDiscardValues(selectors, options = {}) {
2790
- await transport.require("table.bulkDiscardValues", options);
2791
2883
  return postSelection(BULK_DISCARD_VALUES_PATH, selectors, options);
2792
2884
  }
2793
2885
  async function updateFromCsv(id, action, file, options) {
@@ -2809,7 +2901,6 @@ function tableResource(transport) {
2809
2901
  * The file may be at most 50 MB; larger uploads are rejected with a 413 response.
2810
2902
  */
2811
2903
  async function appendCsv(id, file, options = {}) {
2812
- await transport.require("table.appendCsv", options);
2813
2904
  return updateFromCsv(id, "append", file, options);
2814
2905
  }
2815
2906
  /**
@@ -2819,37 +2910,53 @@ function tableResource(transport) {
2819
2910
  * The file may be at most 50 MB; larger uploads are rejected with a 413 response.
2820
2911
  */
2821
2912
  async function replaceCsv(id, file, options = {}) {
2822
- await transport.require("table.replaceCsv", options);
2823
2913
  return updateFromCsv(id, "replace", file, options);
2824
2914
  }
2825
2915
  return {
2826
- list,
2916
+ list: explain("list", list, (params) => dataLayerFeatures(params?.["data-layer"], "data-layer")),
2827
2917
  get,
2828
- update,
2918
+ update: explain("update", update, (_id, params) => dataLayerFeatures(params.data_layer, "data_layer")),
2829
2919
  queryMetadata,
2830
2920
  fks,
2831
2921
  syncSchema,
2832
2922
  rescanValues,
2833
2923
  discardValues,
2834
- bulkEdit,
2835
- bulkSyncSchema,
2836
- bulkRescanValues,
2837
- bulkDiscardValues,
2924
+ bulkEdit: explain("bulkEdit", bulkEdit),
2925
+ bulkSyncSchema: explain("bulkSyncSchema", bulkSyncSchema),
2926
+ bulkRescanValues: explain("bulkRescanValues", bulkRescanValues),
2927
+ bulkDiscardValues: explain("bulkDiscardValues", bulkDiscardValues),
2838
2928
  appendCsv,
2839
2929
  replaceCsv
2840
2930
  };
2841
2931
  }
2932
+ function assertCollectionWritten(path, requested, table) {
2933
+ if (requested === void 0 || table.collection_id === requested) return;
2934
+ const answered = table.collection_id === void 0 ? { kind: "unreported" } : {
2935
+ kind: "reported",
2936
+ value: table.collection_id
2937
+ };
2938
+ throw new PartialWriteError(`the server applied the rest of the update to table ${table.id} but not collection_id: ${describeCollectionAnswer(answered)}, because this server does not move a table to a collection through an update`, {
2939
+ method: "PUT",
2940
+ path,
2941
+ field: "collection_id",
2942
+ requested,
2943
+ answered
2944
+ });
2945
+ }
2946
+ function describeCollectionAnswer(answered) {
2947
+ if (answered.kind === "unreported") return "it answered the table without reporting a collection";
2948
+ if (answered.value === null) return "the table stays in no collection";
2949
+ return `the table stays in collection ${answered.value}`;
2950
+ }
2842
2951
  //#endregion
2843
2952
  //#region ../client/src/resources/timeline-event.ts
2844
2953
  function timelineEventResource(transport) {
2845
2954
  /** Get one timeline event by id. */
2846
2955
  async function get(id, options = {}) {
2847
- await transport.require("timelineEvent.get", options);
2848
2956
  return transport.requestParsed(TimelineEvent, `/api/timeline-event/${id}`, { ...options });
2849
2957
  }
2850
2958
  /** Create a timeline event on an existing timeline. */
2851
2959
  async function create(params, options = {}) {
2852
- await transport.require("timelineEvent.create", options);
2853
2960
  return transport.requestParsed(TimelineEvent, "/api/timeline-event", {
2854
2961
  ...options,
2855
2962
  method: "POST",
@@ -2861,7 +2968,6 @@ function timelineEventResource(transport) {
2861
2968
  * the event to another timeline.
2862
2969
  */
2863
2970
  async function update(id, params, options = {}) {
2864
- await transport.require("timelineEvent.update", options);
2865
2971
  return transport.requestParsed(TimelineEvent, `/api/timeline-event/${id}`, {
2866
2972
  ...options,
2867
2973
  method: "PUT",
@@ -2873,12 +2979,10 @@ function timelineEventResource(transport) {
2873
2979
  * endpoint.
2874
2980
  */
2875
2981
  async function archive(id, options = {}) {
2876
- await transport.require("timelineEvent.archive", options);
2877
2982
  return update(id, { archived: true }, options);
2878
2983
  }
2879
2984
  /** Permanently delete a timeline event by id. The server answers with no body. */
2880
2985
  async function remove(id, options = {}) {
2881
- await transport.require("timelineEvent.delete", options);
2882
2986
  await transport.requestRaw(`/api/timeline-event/${id}`, {
2883
2987
  ...options,
2884
2988
  method: "DELETE",
@@ -2900,7 +3004,6 @@ const TimelineWithEvents = Timeline.required({ events: true });
2900
3004
  function timelineResource(transport) {
2901
3005
  /** List timelines. `archived` swaps the active listing for the archived one. */
2902
3006
  async function list(params = {}, options = {}) {
2903
- await transport.require("timeline.list", options);
2904
3007
  return {
2905
3008
  data: await transport.requestParsed(TimelineApiList, "/api/timeline", {
2906
3009
  ...options,
@@ -2911,7 +3014,6 @@ function timelineResource(transport) {
2911
3014
  }
2912
3015
  /** Get one timeline by id, without its events. */
2913
3016
  async function get(id, options = {}) {
2914
- await transport.require("timeline.get", options);
2915
3017
  return transport.requestParsed(Timeline, `/api/timeline/${id}`, { ...options });
2916
3018
  }
2917
3019
  /**
@@ -2919,7 +3021,6 @@ function timelineResource(transport) {
2919
3021
  * timeline read hydrates them on request, and `archived` chooses which of them come back.
2920
3022
  */
2921
3023
  async function events(id, params = {}, options = {}) {
2922
- await transport.require("timeline.events", options);
2923
3024
  return {
2924
3025
  data: (await transport.requestParsed(TimelineWithEvents, `/api/timeline/${id}`, {
2925
3026
  ...options,
@@ -2933,7 +3034,6 @@ function timelineResource(transport) {
2933
3034
  }
2934
3035
  /** Create a timeline from a full timeline body. */
2935
3036
  async function create(params, options = {}) {
2936
- await transport.require("timeline.create", options);
2937
3037
  return transport.requestParsed(Timeline, "/api/timeline", {
2938
3038
  ...options,
2939
3039
  method: "POST",
@@ -2945,7 +3045,6 @@ function timelineResource(transport) {
2945
3045
  * cascades to every event on the timeline.
2946
3046
  */
2947
3047
  async function update(id, params, options = {}) {
2948
- await transport.require("timeline.update", options);
2949
3048
  return transport.requestParsed(Timeline, `/api/timeline/${id}`, {
2950
3049
  ...options,
2951
3050
  method: "PUT",
@@ -2957,12 +3056,10 @@ function timelineResource(transport) {
2957
3056
  * not its own endpoint.
2958
3057
  */
2959
3058
  async function archive(id, options = {}) {
2960
- await transport.require("timeline.archive", options);
2961
3059
  return update(id, { archived: true }, options);
2962
3060
  }
2963
3061
  /** Permanently delete a timeline and all its events by id. The server answers with no body. */
2964
3062
  async function remove(id, options = {}) {
2965
- await transport.require("timeline.delete", options);
2966
3063
  await transport.requestRaw(`/api/timeline/${id}`, {
2967
3064
  ...options,
2968
3065
  method: "DELETE",
@@ -2999,12 +3096,12 @@ TransformMemberRun.pick({
2999
3096
  //#region ../client/src/resources/transform-dag-run.ts
3000
3097
  const TransformMemberRunList = z.array(TransformMemberRun);
3001
3098
  function transformDagRunResource(transport) {
3099
+ const { explain } = explainer(transport, "transformDagRun");
3002
3100
  /**
3003
3101
  * List the transform runs a DAG run coordinated so far. A member run is inserted when its
3004
3102
  * transform starts, so the list grows while the DAG run is in progress.
3005
3103
  */
3006
3104
  async function transformRuns(runId, options = {}) {
3007
- await transport.require("transformDagRun.transformRuns", options);
3008
3105
  return {
3009
3106
  data: await transport.requestParsed(TransformMemberRunList, `/api/transform-dag-run/${runId}/transform-runs`, { ...options }),
3010
3107
  total: null
@@ -3012,7 +3109,6 @@ function transformDagRunResource(transport) {
3012
3109
  }
3013
3110
  /** Cancel an in-progress DAG run and request cancellation of its still-running transforms. */
3014
3111
  async function cancel(runId, options = {}) {
3015
- await transport.require("transformDagRun.cancel", options);
3016
3112
  await transport.requestRaw(`/api/transform-dag-run/${runId}/cancel`, {
3017
3113
  ...options,
3018
3114
  method: "POST",
@@ -3020,8 +3116,8 @@ function transformDagRunResource(transport) {
3020
3116
  });
3021
3117
  }
3022
3118
  return {
3023
- transformRuns,
3024
- cancel
3119
+ transformRuns: explain("transformRuns", transformRuns),
3120
+ cancel: explain("cancel", cancel)
3025
3121
  };
3026
3122
  }
3027
3123
  //#endregion
@@ -3169,12 +3265,12 @@ function lensPath(id, lensId) {
3169
3265
  return `/api/ee/transforms/${id}/inspect/${encodeURIComponent(lensId)}`;
3170
3266
  }
3171
3267
  function transformInspectorResource(transport) {
3268
+ const { explain } = explainer(transport, "transformInspector");
3172
3269
  /**
3173
3270
  * Discover what the inspector can show for a transform: its source and target tables with field
3174
3271
  * statistics, the fields its query touches, and the lenses available on it.
3175
3272
  */
3176
3273
  async function discover(id, options = {}) {
3177
- await transport.require("transformInspector.discover", options);
3178
3274
  return transport.requestParsed(TransformInspection, `/api/ee/transforms/${id}/inspect`, { ...options });
3179
3275
  }
3180
3276
  /**
@@ -3183,7 +3279,6 @@ function transformInspectorResource(transport) {
3183
3279
  * the `params` its trigger emitted.
3184
3280
  */
3185
3281
  async function lens(id, lensId, params = {}, options = {}) {
3186
- await transport.require("transformInspector.lens", options);
3187
3282
  return transport.requestParsed(TransformLens, lensPath(id, lensId), {
3188
3283
  ...options,
3189
3284
  query: { join_step: params.join_step }
@@ -3195,7 +3290,6 @@ function transformInspectorResource(transport) {
3195
3290
  * opened with.
3196
3291
  */
3197
3292
  async function query(id, lensId, params, options = {}) {
3198
- await transport.require("transformInspector.query", options);
3199
3293
  return transport.requestParsed(CardQueryResult, `${lensPath(id, lensId)}/query`, {
3200
3294
  ...options,
3201
3295
  method: "POST",
@@ -3203,17 +3297,17 @@ function transformInspectorResource(transport) {
3203
3297
  });
3204
3298
  }
3205
3299
  return {
3206
- discover,
3207
- lens,
3208
- query
3300
+ discover: explain("discover", discover),
3301
+ lens: explain("lens", lens),
3302
+ query: explain("query", query)
3209
3303
  };
3210
3304
  }
3211
3305
  //#endregion
3212
3306
  //#region ../client/src/resources/transform-job.ts
3213
3307
  function transformJobResource(transport) {
3308
+ const { explain } = explainer(transport, "transformJob");
3214
3309
  /** List every transform job the caller can see. */
3215
3310
  async function list(options = {}) {
3216
- await transport.require("transformJob.list", options);
3217
3311
  const { features } = await transport.server(options);
3218
3312
  return {
3219
3313
  data: await transport.requestParsed(z.array(transformJobSchema(features)), "/api/transform-job", { ...options }),
@@ -3222,13 +3316,11 @@ function transformJobResource(transport) {
3222
3316
  }
3223
3317
  /** Get one transform job by id. */
3224
3318
  async function get(id, options = {}) {
3225
- await transport.require("transformJob.get", options);
3226
3319
  const { features } = await transport.server(options);
3227
3320
  return transport.requestParsed(transformJobSchema(features), `/api/transform-job/${id}`, { ...options });
3228
3321
  }
3229
3322
  /** Create a transform job — a schedule plus the tags it runs — from a full body. */
3230
3323
  async function create(params, options = {}) {
3231
- await transport.require("transformJob.create", options);
3232
3324
  const { features } = await transport.server(options);
3233
3325
  return transport.requestParsed(transformJobSchema(features), "/api/transform-job", {
3234
3326
  ...options,
@@ -3238,7 +3330,6 @@ function transformJobResource(transport) {
3238
3330
  }
3239
3331
  /** Update a transform job by id, patching only the fields the body carries. */
3240
3332
  async function update(id, params, options = {}) {
3241
- await transport.require("transformJob.update", options);
3242
3333
  const { features } = await transport.server(options);
3243
3334
  return transport.requestParsed(transformJobSchema(features), `/api/transform-job/${id}`, {
3244
3335
  ...options,
@@ -3248,7 +3339,6 @@ function transformJobResource(transport) {
3248
3339
  }
3249
3340
  /** Delete a transform job by id, leaving the transforms it ran untouched. */
3250
3341
  async function remove(id, options = {}) {
3251
- await transport.require("transformJob.delete", options);
3252
3342
  await transport.requestRaw(`/api/transform-job/${id}`, {
3253
3343
  ...options,
3254
3344
  method: "DELETE",
@@ -3260,7 +3350,6 @@ function transformJobResource(transport) {
3260
3350
  * plus those transforms' dependencies; `run_all` re-runs dependencies that are already fresh.
3261
3351
  */
3262
3352
  async function run(id, params = {}, options = {}) {
3263
- await transport.require("transformJob.run", options);
3264
3353
  const { features } = await transport.server(options);
3265
3354
  return transport.requestParsed(transformJobRunResultSchema(features), `/api/transform-job/${id}/run`, {
3266
3355
  ...options,
@@ -3270,7 +3359,6 @@ function transformJobResource(transport) {
3270
3359
  }
3271
3360
  /** List the transforms a job would execute, resolved from the job's tags. */
3272
3361
  async function transforms(id, options = {}) {
3273
- await transport.require("transformJob.transforms", options);
3274
3362
  const { features } = await transport.server(options);
3275
3363
  return {
3276
3364
  data: await transport.requestParsed(z.array(transformRowSchema(features)), `/api/transform-job/${id}/transforms`, { ...options }),
@@ -3282,7 +3370,6 @@ function transformJobResource(transport) {
3282
3370
  * manual runs ignore the flag. The path carries no id and is a different endpoint from `update`.
3283
3371
  */
3284
3372
  async function setActive(active, options = {}) {
3285
- await transport.require("transformJob.setActive", options);
3286
3373
  return transport.requestParsed(TransformJobActiveResult, "/api/transform-job/active", {
3287
3374
  ...options,
3288
3375
  method: "PUT",
@@ -3290,14 +3377,14 @@ function transformJobResource(transport) {
3290
3377
  });
3291
3378
  }
3292
3379
  return {
3293
- list,
3294
- get,
3295
- create,
3296
- update,
3297
- delete: remove,
3298
- run,
3299
- transforms,
3300
- setActive
3380
+ list: explain("list", list),
3381
+ get: explain("get", get),
3382
+ create: explain("create", create),
3383
+ update: explain("update", update),
3384
+ delete: explain("delete", remove),
3385
+ run: explain("run", run),
3386
+ transforms: explain("transforms", transforms),
3387
+ setActive: explain("setActive", setActive)
3301
3388
  };
3302
3389
  }
3303
3390
  //#endregion
@@ -3357,14 +3444,13 @@ function libraryPath(path) {
3357
3444
  return `/api/ee/transforms-python/library/${encodeURIComponent(path)}`;
3358
3445
  }
3359
3446
  function transformPythonResource(transport) {
3447
+ const { explain } = explainer(transport, "transformPython");
3360
3448
  /** Get the Python library at a path, the user module every Python transform can import. */
3361
3449
  async function getLibrary(path, options = {}) {
3362
- await transport.require("transformPython.getLibrary", options);
3363
3450
  return transport.requestParsed(PythonLibrary, libraryPath(path), { ...options });
3364
3451
  }
3365
3452
  /** Replace the source of the Python library at a path, creating the library when none exists. */
3366
3453
  async function updateLibrary(path, params, options = {}) {
3367
- await transport.require("transformPython.updateLibrary", options);
3368
3454
  return transport.requestParsed(PythonLibrary, libraryPath(path), {
3369
3455
  ...options,
3370
3456
  method: "PUT",
@@ -3377,7 +3463,6 @@ function transformPythonResource(transport) {
3377
3463
  * sizes are capped at 100 rows.
3378
3464
  */
3379
3465
  async function testRun(params, options = {}) {
3380
- await transport.require("transformPython.testRun", options);
3381
3466
  return transport.requestParsed(PythonApiTestRun, "/api/ee/transforms-python/test-run", {
3382
3467
  ...options,
3383
3468
  method: "POST",
@@ -3385,9 +3470,9 @@ function transformPythonResource(transport) {
3385
3470
  });
3386
3471
  }
3387
3472
  return {
3388
- getLibrary,
3389
- updateLibrary,
3390
- testRun
3473
+ getLibrary: explain("getLibrary", getLibrary),
3474
+ updateLibrary: explain("updateLibrary", updateLibrary),
3475
+ testRun: explain("testRun", testRun)
3391
3476
  };
3392
3477
  }
3393
3478
  //#endregion
@@ -3401,6 +3486,7 @@ const TransformRunKickoff = z.object({
3401
3486
  });
3402
3487
  const TransformDagTransformList = z.array(TransformDagTransform);
3403
3488
  function transformResource(transport) {
3489
+ const { explain, explainWalk } = explainer(transport, "transform");
3404
3490
  async function requestTransformList(path, opts) {
3405
3491
  const { features } = await transport.server(opts);
3406
3492
  return {
@@ -3414,18 +3500,15 @@ function transformResource(transport) {
3414
3500
  }
3415
3501
  /** List every transform the caller can see. */
3416
3502
  async function list(options = {}) {
3417
- await transport.require("transform.list", options);
3418
3503
  return requestTransformList("/api/transform", { ...options });
3419
3504
  }
3420
3505
  /** Get one transform by id. */
3421
3506
  async function get(id, options = {}) {
3422
- await transport.require("transform.get", options);
3423
3507
  const { features } = await transport.server(options);
3424
3508
  return transport.requestParsed(transformDetailSchema(features), `/api/transform/${id}`, { ...options });
3425
3509
  }
3426
3510
  /** Create a transform — a query plus the warehouse table it writes — from a full body. */
3427
3511
  async function create(params, options = {}) {
3428
- await transport.require("transform.create", options);
3429
3512
  return requestTransform("/api/transform", {
3430
3513
  ...options,
3431
3514
  method: "POST",
@@ -3434,7 +3517,6 @@ function transformResource(transport) {
3434
3517
  }
3435
3518
  /** Update a transform by id, patching only the fields the body carries. */
3436
3519
  async function update(id, params, options = {}) {
3437
- await transport.require("transform.update", options);
3438
3520
  return requestTransform(`/api/transform/${id}`, {
3439
3521
  ...options,
3440
3522
  method: "PUT",
@@ -3443,7 +3525,6 @@ function transformResource(transport) {
3443
3525
  }
3444
3526
  /** Delete a transform by id, leaving any table it already materialized in place. */
3445
3527
  async function remove(id, options = {}) {
3446
- await transport.require("transform.delete", options);
3447
3528
  await transport.requestRaw(`/api/transform/${id}`, {
3448
3529
  ...options,
3449
3530
  method: "DELETE",
@@ -3452,7 +3533,6 @@ function transformResource(transport) {
3452
3533
  }
3453
3534
  /** Drop a transform's materialized output table, keeping the transform definition. */
3454
3535
  async function deleteTable(id, options = {}) {
3455
- await transport.require("transform.deleteTable", options);
3456
3536
  await transport.requestRaw(`/api/transform/${id}/table`, {
3457
3537
  ...options,
3458
3538
  method: "DELETE",
@@ -3461,12 +3541,10 @@ function transformResource(transport) {
3461
3541
  }
3462
3542
  /** List the upstream transforms a transform depends on. */
3463
3543
  async function dependencies(id, options = {}) {
3464
- await transport.require("transform.dependencies", options);
3465
3544
  return requestTransformList(`/api/transform/${id}/dependencies`, { ...options });
3466
3545
  }
3467
3546
  /** Request cancellation of a transform's current run. */
3468
3547
  async function cancel(id, options = {}) {
3469
- await transport.require("transform.cancel", options);
3470
3548
  await transport.requestRaw(`/api/transform/${id}/cancel`, {
3471
3549
  ...options,
3472
3550
  method: "POST",
@@ -3475,12 +3553,10 @@ function transformResource(transport) {
3475
3553
  }
3476
3554
  /** Get one transform run by run id — not by the id of the transform that produced it. */
3477
3555
  async function getRun(runId, options = {}) {
3478
- await transport.require("transform.getRun", options);
3479
3556
  return transport.requestParsed(TransformRun, `/api/transform/run/${runId}`, { ...options });
3480
3557
  }
3481
3558
  /** Walk the run history, newest first, optionally narrowed to a single transform. */
3482
3559
  async function* runPages(params = {}, options = {}) {
3483
- await transport.require("transform.runPages", options);
3484
3560
  yield* paginatePages(transport, "/api/transform/run", TransformRun, {
3485
3561
  query: { "transform-ids": params["transform-ids"] },
3486
3562
  ...options.offset !== void 0 && { offset: options.offset },
@@ -3494,7 +3570,6 @@ function transformResource(transport) {
3494
3570
  * DAG reprocess run, or a standalone transform run — and never a member run of a job or DAG.
3495
3571
  */
3496
3572
  async function* runSummaryPages(params = {}, options = {}) {
3497
- await transport.require("transform.runSummaryPages", options);
3498
3573
  yield* paginatePages(transport, "/api/transform/runs", TransformRunSummary, {
3499
3574
  query: {
3500
3575
  types: params.types,
@@ -3517,7 +3592,6 @@ function transformResource(transport) {
3517
3592
  * source rather than the rows past the last checkpoint.
3518
3593
  */
3519
3594
  async function resetCheckpoint(id, options = {}) {
3520
- await transport.require("transform.resetCheckpoint", options);
3521
3595
  await transport.requestRaw(`/api/transform/${id}/reset-checkpoint`, {
3522
3596
  ...options,
3523
3597
  method: "POST",
@@ -3532,7 +3606,6 @@ function transformResource(transport) {
3532
3606
  * `runSummaryPages` with `types: ["dag"]`, and the member runs off `transformDagRun.transformRuns`.
3533
3607
  */
3534
3608
  async function runDag(id, params, options = {}) {
3535
- await transport.require("transform.runDag", options);
3536
3609
  return transport.requestParsed(TransformDagRunResult, `/api/transform/${id}/run-dag`, {
3537
3610
  ...options,
3538
3611
  method: "POST",
@@ -3541,7 +3614,6 @@ function transformResource(transport) {
3541
3614
  }
3542
3615
  /** Preview the transforms a DAG reprocess seeded at a transform would run, in execution order. */
3543
3616
  async function dagTransforms(id, params, options = {}) {
3544
- await transport.require("transform.dagTransforms", options);
3545
3617
  return {
3546
3618
  data: await transport.requestParsed(TransformDagTransformList, `/api/transform/${id}/dag-transforms`, {
3547
3619
  ...options,
@@ -3556,7 +3628,6 @@ function transformResource(transport) {
3556
3628
  * A server that answers no run id started nothing, and there is then nothing to poll.
3557
3629
  */
3558
3630
  async function run(id, params = {}, options = {}) {
3559
- await transport.require("transform.run", options);
3560
3631
  const kickoff = await transport.requestParsed(TransformRunKickoff, `/api/transform/${id}/run`, {
3561
3632
  ...options,
3562
3633
  method: "POST"
@@ -3597,30 +3668,30 @@ function transformResource(transport) {
3597
3668
  }
3598
3669
  }
3599
3670
  return {
3600
- list,
3601
- get,
3602
- create,
3603
- update,
3604
- delete: remove,
3605
- deleteTable,
3606
- dependencies,
3607
- cancel,
3608
- getRun,
3609
- runPages,
3610
- runSummaryPages,
3611
- resetCheckpoint,
3612
- runDag,
3613
- dagTransforms,
3614
- run
3671
+ list: explain("list", list),
3672
+ get: explain("get", get),
3673
+ create: explain("create", create),
3674
+ update: explain("update", update),
3675
+ delete: explain("delete", remove),
3676
+ deleteTable: explain("deleteTable", deleteTable),
3677
+ dependencies: explain("dependencies", dependencies),
3678
+ cancel: explain("cancel", cancel),
3679
+ getRun: explain("getRun", getRun),
3680
+ runPages: explainWalk("runPages", runPages),
3681
+ runSummaryPages: explainWalk("runSummaryPages", runSummaryPages),
3682
+ resetCheckpoint: explain("resetCheckpoint", resetCheckpoint),
3683
+ runDag: explain("runDag", runDag),
3684
+ dagTransforms: explain("dagTransforms", dagTransforms),
3685
+ run: explain("run", run)
3615
3686
  };
3616
3687
  }
3617
3688
  //#endregion
3618
3689
  //#region ../client/src/resources/transform-tag.ts
3619
3690
  const TransformTagApiList = z.array(TransformTag);
3620
3691
  function transformTagResource(transport) {
3692
+ const { explain } = explainer(transport, "transformTag");
3621
3693
  /** List every transform tag the caller can see, built-in tags included. */
3622
3694
  async function list(options = {}) {
3623
- await transport.require("transformTag.list", options);
3624
3695
  return {
3625
3696
  data: await transport.requestParsed(TransformTagApiList, "/api/transform-tag", { ...options }),
3626
3697
  total: null
@@ -3628,7 +3699,6 @@ function transformTagResource(transport) {
3628
3699
  }
3629
3700
  /** Create a transform tag, the label a transform job matches transforms on. */
3630
3701
  async function create(params, options = {}) {
3631
- await transport.require("transformTag.create", options);
3632
3702
  return transport.requestParsed(TransformTag, "/api/transform-tag", {
3633
3703
  ...options,
3634
3704
  method: "POST",
@@ -3637,7 +3707,6 @@ function transformTagResource(transport) {
3637
3707
  }
3638
3708
  /** Rename a transform tag by id. */
3639
3709
  async function update(id, params, options = {}) {
3640
- await transport.require("transformTag.update", options);
3641
3710
  return transport.requestParsed(TransformTag, `/api/transform-tag/${id}`, {
3642
3711
  ...options,
3643
3712
  method: "PUT",
@@ -3646,7 +3715,6 @@ function transformTagResource(transport) {
3646
3715
  }
3647
3716
  /** Delete a transform tag by id, detaching it from every transform and job carrying it. */
3648
3717
  async function remove(id, options = {}) {
3649
- await transport.require("transformTag.delete", options);
3650
3718
  await transport.requestRaw(`/api/transform-tag/${id}`, {
3651
3719
  ...options,
3652
3720
  method: "DELETE",
@@ -3654,19 +3722,19 @@ function transformTagResource(transport) {
3654
3722
  });
3655
3723
  }
3656
3724
  return {
3657
- list,
3658
- create,
3659
- update,
3660
- delete: remove
3725
+ list: explain("list", list),
3726
+ create: explain("create", create),
3727
+ update: explain("update", update),
3728
+ delete: explain("delete", remove)
3661
3729
  };
3662
3730
  }
3663
3731
  //#endregion
3664
3732
  //#region ../client/src/resources/transform-test.ts
3665
3733
  const TransformTestApiList = z.array(TransformTest);
3666
3734
  function transformTestResource(transport) {
3735
+ const { explain } = explainer(transport, "transformTest");
3667
3736
  /** List the transform tests, optionally only those of one transform. */
3668
3737
  async function list(params = {}, options = {}) {
3669
- await transport.require("transformTest.list", options);
3670
3738
  return {
3671
3739
  data: await transport.requestParsed(TransformTestApiList, "/api/ee/transform-test", {
3672
3740
  ...options,
@@ -3677,7 +3745,6 @@ function transformTestResource(transport) {
3677
3745
  }
3678
3746
  /** Get a transform test by id. */
3679
3747
  async function get(id, options = {}) {
3680
- await transport.require("transformTest.get", options);
3681
3748
  return transport.requestParsed(TransformTest, `/api/ee/transform-test/${id}`, { ...options });
3682
3749
  }
3683
3750
  /**
@@ -3686,7 +3753,6 @@ function transformTestResource(transport) {
3686
3753
  * `TransformTestRefusalCode` when they cannot run.
3687
3754
  */
3688
3755
  async function create(params, options = {}) {
3689
- await transport.require("transformTest.create", options);
3690
3756
  return transport.requestParsed(TransformTest, "/api/ee/transform-test", {
3691
3757
  ...options,
3692
3758
  method: "POST",
@@ -3698,7 +3764,6 @@ function transformTestResource(transport) {
3698
3764
  * transform, inputs or expectations is validated as on create, with the same refusal.
3699
3765
  */
3700
3766
  async function update(id, params, options = {}) {
3701
- await transport.require("transformTest.update", options);
3702
3767
  return transport.requestParsed(TransformTest, `/api/ee/transform-test/${id}`, {
3703
3768
  ...options,
3704
3769
  method: "PUT",
@@ -3707,7 +3772,6 @@ function transformTestResource(transport) {
3707
3772
  }
3708
3773
  /** Delete a transform test by id. */
3709
3774
  async function remove(id, options = {}) {
3710
- await transport.require("transformTest.delete", options);
3711
3775
  await transport.requestRaw(`/api/ee/transform-test/${id}`, {
3712
3776
  ...options,
3713
3777
  method: "DELETE",
@@ -3721,19 +3785,18 @@ function transformTestResource(transport) {
3721
3785
  * refused and nothing meaningful ran.
3722
3786
  */
3723
3787
  async function run(id, options = {}) {
3724
- await transport.require("transformTest.run", options);
3725
3788
  return transport.requestParsed(TransformTestRunResult, `/api/ee/transform-test/${id}/run`, {
3726
3789
  ...options,
3727
3790
  method: "POST"
3728
3791
  });
3729
3792
  }
3730
3793
  return {
3731
- list,
3732
- get,
3733
- create,
3734
- update,
3735
- delete: remove,
3736
- run
3794
+ list: explain("list", list),
3795
+ get: explain("get", get),
3796
+ create: explain("create", create),
3797
+ update: explain("update", update),
3798
+ delete: explain("delete", remove),
3799
+ run: explain("run", run)
3737
3800
  };
3738
3801
  }
3739
3802
  //#endregion
@@ -3747,7 +3810,6 @@ function uploadResource(transport) {
3747
3810
  * The file may be at most 50 MB; larger uploads are rejected with a 413 response.
3748
3811
  */
3749
3812
  async function createFromCsv(file, params, options = {}) {
3750
- await transport.require("upload.createFromCsv", options);
3751
3813
  const form = buildCsvFormData(file);
3752
3814
  form.append("collection_id", params.collection_id);
3753
3815
  const response = await transport.requestRaw("/api/upload/csv", {
@@ -3800,7 +3862,6 @@ CurrentUser.pick({
3800
3862
  function userResource(transport) {
3801
3863
  /** Get the user the request's credentials authenticate as. */
3802
3864
  async function current(options = {}) {
3803
- await transport.require("user.current", options);
3804
3865
  return transport.requestParsed(CurrentUser, "/api/user/current", { ...options });
3805
3866
  }
3806
3867
  return { current };
@@ -3852,6 +3913,8 @@ function createClient(config, options) {
3852
3913
  upload: uploadResource(transport),
3853
3914
  user: userResource(transport),
3854
3915
  server: transport.server,
3916
+ verifiedServer: transport.verifiedServer,
3917
+ probesSettled: transport.probesSettled,
3855
3918
  requestParsed: transport.requestParsed,
3856
3919
  requestRaw: transport.requestRaw,
3857
3920
  requestStream: transport.requestStream