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

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 (295) hide show
  1. package/dist/{add-collection-hv29px39.mjs → add-collection-COtKcy6Y.mjs} +4 -4
  2. package/dist/{alert-DnTCjYIu.mjs → alert-DMlSjPEv.mjs} +7 -7
  3. package/dist/{alerts-_ELZ12ye.mjs → alerts-s3al16NZ.mjs} +6 -6
  4. package/dist/{append-B9sLLPYT.mjs → append-CbGmdeLw.mjs} +4 -4
  5. package/dist/{archive-CwBk5vxQ.mjs → archive-5EY9UJU_.mjs} +5 -5
  6. package/dist/{archive-DDUSmbjn.mjs → archive-BFmx4B91.mjs} +3 -3
  7. package/dist/{archive-CdfyB0To.mjs → archive-BZCYIBhQ.mjs} +3 -3
  8. package/dist/{archive-BRX5fKPb.mjs → archive-C_UHeanE.mjs} +3 -3
  9. package/dist/{archive-BhIeDoLk.mjs → archive-Ce4eArrs.mjs} +3 -3
  10. package/dist/{archive-BAHzxkPR.mjs → archive-CnDriekB.mjs} +5 -5
  11. package/dist/{archive-CKWWyI1i.mjs → archive-DDO2XX0E.mjs} +4 -4
  12. package/dist/{archive-DD8C0Krt2.mjs → archive-DW7x0IFB.mjs} +4 -4
  13. package/dist/{archive-D22VL5Bx.mjs → archive-DvbaSglr.mjs} +3 -3
  14. package/dist/{archive-ZnHzMJaM.mjs → archive-DzSdJVqE.mjs} +5 -5
  15. package/dist/{archive-BdX0Uvf2.mjs → archive-a05W75XH.mjs} +3 -3
  16. package/dist/{auth-Ca84a5fv.mjs → auth-DOz7KW5E.mjs} +5 -5
  17. package/dist/{body-BVrHLwJU.mjs → body-Di7940x_.mjs} +2 -3
  18. package/dist/{branch-flag-Czh8JT1G.mjs → branch-flag-CfJ8vwgu.mjs} +1 -1
  19. package/dist/{branches-frSt4WcS.mjs → branches-BC-vlk6i.mjs} +3 -3
  20. package/dist/{breaking-zlgxxwdd.mjs → breaking-ByrjsFjq.mjs} +6 -6
  21. package/dist/{broken-BUAzpKLc.mjs → broken-DLSk1T88.mjs} +8 -8
  22. package/dist/{bulk-discard-values-DU15VJPb.mjs → bulk-discard-values-VKGj8c7z.mjs} +5 -5
  23. package/dist/{bulk-edit-TSa4lNkM.mjs → bulk-edit-CpuleeCQ.mjs} +5 -5
  24. package/dist/{bulk-rescan-values-BYJAsPC1.mjs → bulk-rescan-values-DeGwiqLS.mjs} +5 -5
  25. package/dist/{bulk-sync-schema-DtaOwEtW.mjs → bulk-sync-schema-BKVhauW1.mjs} +5 -5
  26. package/dist/{cancel-DCOvSQsq.mjs → cancel-qKj45001.mjs} +3 -3
  27. package/dist/{cancel-task-osQLETJh.mjs → cancel-task-nmBBd1Ky.mjs} +3 -3
  28. package/dist/card-Buc-X4Mh.mjs +25 -0
  29. package/dist/{card-P5mBknsl.mjs → card-Bv4ZN0oV.mjs} +12 -2
  30. package/dist/{card-CQNUT9pm.mjs → card-DSrgU7lT.mjs} +1 -1
  31. package/dist/{cards-B1pyPl77.mjs → cards-Bq73s48q.mjs} +4 -4
  32. package/dist/cli.mjs +628 -239
  33. package/dist/{client-1UIUBA_V.mjs → client-BmURNkkW.mjs} +998 -444
  34. package/dist/{collection-DPiztU_l.mjs → collection-C03LXnCT.mjs} +1 -1
  35. package/dist/collection-D-sS0AFw.mjs +21 -0
  36. package/dist/{collection-namespace-Cizv6-94.mjs → collection-namespace-DaDzK31i.mjs} +1 -2
  37. package/dist/{collection-BgPYl2c5.mjs → collection-zrZoJfgR.mjs} +1 -1
  38. package/dist/{content-translation-XnHISl5-.mjs → content-translation-BlG41YrA.mjs} +3 -3
  39. package/dist/{copy-BCVW48Jq.mjs → copy-DOKlVa7H.mjs} +6 -6
  40. package/dist/{copy-DAKdksBm.mjs → copy-DTiYs_a2.mjs} +4 -4
  41. package/dist/{copy-flags-D7NTgAz1.mjs → copy-flags-BhGWC5kS.mjs} +2 -2
  42. package/dist/{create-qCgnB-oZ.mjs → create-1rLk8b5q.mjs} +3 -3
  43. package/dist/{create-BfA1pnmS.mjs → create-B1QDNU1x.mjs} +6 -6
  44. package/dist/{create-BOvtRwRz.mjs → create-B1bX6ngX.mjs} +4 -4
  45. package/dist/{create-XnYN2zoc.mjs → create-B5BwdPwT.mjs} +6 -6
  46. package/dist/{create-CuhQ1Khk.mjs → create-BKKNIe93.mjs} +3 -3
  47. package/dist/{create-KyeJjhxH2.mjs → create-BXX_n64u.mjs} +4 -4
  48. package/dist/{create-B1ifNWcE.mjs → create-BpwZJZxb.mjs} +5 -5
  49. package/dist/{create-B57D5E-o.mjs → create-C0kz0oYc.mjs} +3 -3
  50. package/dist/{create-CW4WF073.mjs → create-Cob0gLsA.mjs} +3 -3
  51. package/dist/{create-D7zeAm-0.mjs → create-DPggIXfn.mjs} +4 -4
  52. package/dist/{create-Br5JxAeE.mjs → create-DUytP2Y1.mjs} +4 -4
  53. package/dist/{create-cqbpWaOi.mjs → create-DraZzrLO.mjs} +6 -6
  54. package/dist/{create-B5eo9PSN.mjs → create-Duj_2YIF.mjs} +4 -4
  55. package/dist/{create-D2iZsTRV.mjs → create-JaFhqysM.mjs} +5 -5
  56. package/dist/{create-branch-Cli5cXvk.mjs → create-branch-B5jB4tKk.mjs} +3 -3
  57. package/dist/{create-BzE4q6F_.mjs → create-d0trLAzB.mjs} +4 -4
  58. package/dist/{create-g4pZa_Xa.mjs → create-vOw8gmBU.mjs} +3 -3
  59. package/dist/{csv-CWIcAm_-.mjs → csv-DfExp9H_.mjs} +4 -4
  60. package/dist/{current-task-eBjusv2o.mjs → current-task-CbXIH_RA.mjs} +4 -4
  61. package/dist/dashboard-DH_soSQp.mjs +35 -0
  62. package/dist/{database-BT7DVgSr.mjs → database-BdKkae1m.mjs} +2 -2
  63. package/dist/{database-Kq2AL5jH.mjs → database-DoNoZ8Yc.mjs} +1 -1
  64. package/dist/{dataset-BmvCxGoE.mjs → dataset-DZgJm5hP.mjs} +3 -14
  65. package/dist/{db-_H9HAS6H.mjs → db-C82M3Gpg.mjs} +7 -7
  66. package/dist/{delete-C7QmQlp9.mjs → delete-9xHRd2CG.mjs} +4 -4
  67. package/dist/{delete-BMvVkW-m.mjs → delete-B4BfGfiJ.mjs} +4 -4
  68. package/dist/{delete-BW5QXiyJ.mjs → delete-D0xFMRRq.mjs} +4 -4
  69. package/dist/{delete-CynbfvS2.mjs → delete-DJ7SNXiH.mjs} +4 -4
  70. package/dist/{delete-BztmAfPc.mjs → delete-hcw_4pBG.mjs} +4 -4
  71. package/dist/{delete-DOn2uk0u.mjs → delete-hwPqKXeY.mjs} +4 -4
  72. package/dist/{delete-runtime-Cc7TASG7.mjs → delete-runtime-DkgYUQ_n.mjs} +1 -1
  73. package/dist/{delete-table-DFqm6bAq.mjs → delete-table-DhTMft7u.mjs} +4 -4
  74. package/dist/{dependencies-DZ-kMsDa.mjs → dependencies-B8H2gA04.mjs} +5 -5
  75. package/dist/dependency-CLznxtLE.mjs +15 -0
  76. package/dist/{dependency-BHKwKjhO.mjs → dependency-Dc33mjgd.mjs} +2 -2
  77. package/dist/{dependency-B4J1bwFh.mjs → dependency-tQBxYjFX.mjs} +2 -2
  78. package/dist/{dependents-Ia2fe_KK.mjs → dependents-B2GcS5kB.mjs} +9 -9
  79. package/dist/{dirty-BrXAPows.mjs → dirty-Cz2rUd6S.mjs} +4 -4
  80. package/dist/{discard-values-B9mTgFbV.mjs → discard-values-QWuWGaVK.mjs} +5 -5
  81. package/dist/{document-COQzVLjW.mjs → document-BDobDiap.mjs} +1 -1
  82. package/dist/document-WhSEyI-3.mjs +20 -0
  83. package/dist/{document-BsaKF8UR.mjs → document-wfWbV0B_.mjs} +1 -1
  84. package/dist/{download-CZdsDdhG.mjs → download-Dw15fUMy.mjs} +2 -2
  85. package/dist/{eid-DsGNJ16z.mjs → eid-Ex4G-ZlN.mjs} +4 -4
  86. package/dist/{entity-BFajNdQp.mjs → entity-DBKPaMr5.mjs} +2 -2
  87. package/dist/{entry-flags-BaZUoxUR.mjs → entry-flags-DgY3IXFe.mjs} +4 -4
  88. package/dist/{events-rkMIMpnS.mjs → events-BibJ4ya0.mjs} +4 -4
  89. package/dist/{export-BCkzx9zk.mjs → export-U8MdaatW.mjs} +7 -7
  90. package/dist/{export-flags-TuwwRhD6.mjs → export-flags-BrfeyE8D.mjs} +3 -3
  91. package/dist/{export-preflight-p7botJyN.mjs → export-preflight-A28L45au.mjs} +5 -5
  92. package/dist/field-qF1KRJYB.mjs +21 -0
  93. package/dist/{fields-CJVXOLbB.mjs → fields-i1TFa_th.mjs} +4 -4
  94. package/dist/{filter-flags-yY4RM312.mjs → filter-flags-Bm3vuSM-.mjs} +4 -5
  95. package/dist/{fks-B6hjrdJP.mjs → fks-Dcfn3-73.mjs} +6 -6
  96. package/dist/{get-Eforof5e.mjs → get-B4_3DID6.mjs} +5 -5
  97. package/dist/{get-zotSW00n.mjs → get-BCdtCwaG.mjs} +3 -3
  98. package/dist/{get-DpYTiE4D.mjs → get-BKDTRMNU.mjs} +3 -3
  99. package/dist/{get-DsbDxSpy.mjs → get-BiPSePQc.mjs} +3 -3
  100. package/dist/{get-BYtFVJHT.mjs → get-C3ojRVJ_.mjs} +4 -4
  101. package/dist/{get-D7HPiBB0.mjs → get-CCLWWqTD.mjs} +4 -4
  102. package/dist/{get-CoJcgFky.mjs → get-CVpIQb8v.mjs} +5 -5
  103. package/dist/{get-CXeFg0F2.mjs → get-CcwRn8Y2.mjs} +5 -5
  104. package/dist/{get-CWBN0G-4.mjs → get-CobILQ_7.mjs} +5 -5
  105. package/dist/{get-DPlSzRI0.mjs → get-Cwj1Q-at.mjs} +3 -3
  106. package/dist/{get-C6uLL4bJ.mjs → get-D0NZ0ZO1.mjs} +3 -3
  107. package/dist/{get-BvUNnNRq.mjs → get-DPZUIPjv.mjs} +3 -3
  108. package/dist/{get-Do1LC4I52.mjs → get-DUwX-RU82.mjs} +3 -3
  109. package/dist/{get-BxUgfEPW.mjs → get-DqYn9mez.mjs} +4 -4
  110. package/dist/{get-oLc5jgid2.mjs → get-Dz3dNirC.mjs} +4 -4
  111. package/dist/{get-DyS1mRI6.mjs → get-EKBYludD.mjs} +3 -3
  112. package/dist/{get-D1p-5ibI.mjs → get-HFQSttF3.mjs} +5 -5
  113. package/dist/{get-xmhwvAQJ.mjs → get-IjoHBExI.mjs} +5 -5
  114. package/dist/{get-B5L4BUGK.mjs → get-hAoJtVCK.mjs} +3 -3
  115. package/dist/{get-run-bU-d--nM.mjs → get-run-B2SnsZSY.mjs} +4 -4
  116. package/dist/git-sync-BlgB_-8-.mjs +29 -0
  117. package/dist/{git-sync-DspFahwV.mjs → git-sync-Ch1V1fEK.mjs} +1 -1
  118. package/dist/{glossary-CdW6RFId.mjs → glossary-6LLpItof.mjs} +1 -1
  119. package/dist/glossary-BvVxqjxJ.mjs +14 -0
  120. package/dist/{glossary-Del97Jqg.mjs → glossary-wnuxoE_I.mjs} +1 -1
  121. package/dist/{graph-CHXJ6mB7.mjs → graph-CHlfxVy1.mjs} +6 -6
  122. package/dist/{group-D4w87fXL.mjs → group-B3X3LTcU.mjs} +1 -1
  123. package/dist/{has-remote-changes-BXEe8Sv5.mjs → has-remote-changes-DCMpqGgE.mjs} +3 -3
  124. package/dist/{import-DZQb4bUo.mjs → import-C7MZhKo6.mjs} +7 -7
  125. package/dist/{input-DjHsV-kD.mjs → input-sRdxNc3T.mjs} +1 -1
  126. package/dist/{is-dirty-DwQ2_CCB.mjs → is-dirty-DpLyHebS.mjs} +2 -2
  127. package/dist/{items-DNdICQ7M.mjs → items-1ME_eg27.mjs} +6 -6
  128. package/dist/{key-BTaxFn_7.mjs → key-DMtgTOwN.mjs} +1 -1
  129. package/dist/{library-DmkpcZPb.mjs → library-CP-9yepy.mjs} +1 -1
  130. package/dist/{library-Cdj_uj1H.mjs → library-D2ogEIfI.mjs} +5 -5
  131. package/dist/{library-C4M8TF2N.mjs → library-fWEv38OB.mjs} +2 -2
  132. package/dist/{list-CYlPgzD1.mjs → list-B0h-j1E7.mjs} +3 -3
  133. package/dist/{list-CLJvuPXn.mjs → list-BUZ91ctZ.mjs} +5 -5
  134. package/dist/{list-89ZgbsvW.mjs → list-BUwDGyIu.mjs} +5 -5
  135. package/dist/{list-C-WGqRqN.mjs → list-BXcDPU5d.mjs} +6 -6
  136. package/dist/{list-C7PV9fYj.mjs → list-BoVeF8QA.mjs} +6 -6
  137. package/dist/{list-M20foZz2.mjs → list-C1oEpUZG.mjs} +3 -3
  138. package/dist/{list-OKHcr76i.mjs → list-C9996vbS.mjs} +3 -3
  139. package/dist/{list-2EWHwbQs.mjs → list-CJ5FyCnL.mjs} +4 -4
  140. package/dist/{list-DcHPY1Ti.mjs → list-CQi3AgS5.mjs} +4 -4
  141. package/dist/{list-epIn3uFZ.mjs → list-CVh8DHOO.mjs} +5 -5
  142. package/dist/{list-03zaydQe.mjs → list-DBAEy3Rx.mjs} +3 -3
  143. package/dist/{list-hINHmdMT.mjs → list-DkCadZ2E.mjs} +3 -3
  144. package/dist/{list-DsyvHBAv.mjs → list-DzP7YXdZ.mjs} +3 -3
  145. package/dist/{list-gqemddWB.mjs → list-LMlJ4uAY.mjs} +6 -6
  146. package/dist/{list-F4ENGV7j.mjs → list-NINy7ybI.mjs} +4 -4
  147. package/dist/{list-CZoaZc9g.mjs → list-OXiXtdiM.mjs} +5 -5
  148. package/dist/{list-Be6kDkyT.mjs → list-STy8UHs2.mjs} +6 -6
  149. package/dist/{list-DoLqxEjq.mjs → list-Ud7F0XdU.mjs} +3 -3
  150. package/dist/{list-__Q79YXk.mjs → list-dCi6OaIE.mjs} +4 -4
  151. package/dist/{login-BSqGgX8S.mjs → login-BMON3Fft.mjs} +6 -6
  152. package/dist/{logout-D92O4mQK.mjs → logout-Cx934GzW.mjs} +2 -2
  153. package/dist/{measure-DhX_mmZK.mjs → measure-CUqJtDNm.mjs} +6 -6
  154. package/dist/{notification-Cuuwoksm.mjs → notification-CCZ9mrOw.mjs} +1 -1
  155. package/dist/{parameter-values-BY2WA9qg.mjs → parameter-values-DCrU02Az.mjs} +3 -3
  156. package/dist/{parse-enum-BPKULOSw.mjs → parse-enum-Do5Cptbv.mjs} +1 -1
  157. package/dist/{parse-id-CxHp2akh.mjs → parse-id-oCbmouCn.mjs} +1 -1
  158. package/dist/{parse-ref-8bVX_9IN.mjs → parse-ref-Df8gusl4.mjs} +1 -1
  159. package/dist/{parse-text-Bm6eUa-p.mjs → parse-text-CVrP7GmZ.mjs} +1 -1
  160. package/dist/{path-D4Q1ZzCw.mjs → path-DTuSDGd6.mjs} +4 -4
  161. package/dist/{poll-DFZRtTA_.mjs → poll-BKo5dK0l.mjs} +2 -2
  162. package/dist/{preflight-BPaonBKy.mjs → preflight-UeYuGDPr.mjs} +2 -3
  163. package/dist/{process-B3Di_HtV.mjs → process-BpmZXbcw.mjs} +1 -1
  164. package/dist/{publish-BhgyrJ6c.mjs → publish-D3GwLC-K.mjs} +4 -4
  165. package/dist/{pulse-CIza18rO.mjs → pulse-DzVPfb5J.mjs} +1 -1
  166. package/dist/{query-CsBe-kJn.mjs → query-B1Cp9EO6.mjs} +6 -6
  167. package/dist/{query-DvlGWjgG.mjs → query-Ct697cY-.mjs} +13 -15
  168. package/dist/{remapping-CCEivr42.mjs → remapping-RsoPWE7c.mjs} +4 -4
  169. package/dist/{remove-collection-LpgGBYCl.mjs → remove-collection-BJo3Wd9K.mjs} +4 -4
  170. package/dist/{render-D5ehBS-7.mjs → render-C_ev6qoW.mjs} +2 -2
  171. package/dist/{replace-DeWgkbfZ.mjs → replace-BSAGp3cR.mjs} +4 -4
  172. package/dist/requirement-check-T3bjQ0Mr.mjs +60 -0
  173. package/dist/{rescan-values-CDDVS1tl.mjs → rescan-values-CE62_2dc.mjs} +5 -5
  174. package/dist/{rescan-values-aF18XAv6.mjs → rescan-values-rpro4Kw2.mjs} +5 -5
  175. package/dist/{run-DyGMvbMm.mjs → run-B3BRodEp.mjs} +3 -3
  176. package/dist/{run-Buf4B8QH.mjs → run-BI1QO9Kn.mjs} +7 -7
  177. package/dist/{runs-D2CXgW10.mjs → runs-CUseZezf.mjs} +5 -5
  178. package/dist/{runtime-X6hVzSMW.mjs → runtime-C5Ha6Ou8.mjs} +248 -534
  179. package/dist/{schema-tables-FUOIyoUU.mjs → schema-tables-C7YkyRh0.mjs} +6 -6
  180. package/dist/{schemas-CmfNeqzG.mjs → schemas-Bj5sJ9uB.mjs} +4 -4
  181. package/dist/{search-CLpDqYpY.mjs → search-BsAwK7Rk.mjs} +5 -5
  182. package/dist/{search-Dl7xzF6Q.mjs → search-CB1A4HWx.mjs} +6 -6
  183. package/dist/{segment-Bx28f38v.mjs → segment-BEjaeLc5.mjs} +6 -6
  184. package/dist/{send-CoGV2zkp.mjs → send-Dfh8d1gG.mjs} +3 -3
  185. package/dist/{set-CrR9IumN.mjs → set-DA8dKyzi.mjs} +4 -4
  186. package/dist/{set-active-CbiJv8Hg.mjs → set-active-AyjKtBD-.mjs} +3 -3
  187. package/dist/{set-sensitivity-CTHVxpnU.mjs → set-sensitivity-D0613dHA.mjs} +4 -4
  188. package/dist/{setting-CUxdweNJ.mjs → setting-B-bxCkC0.mjs} +4 -4
  189. package/dist/{setup-4uySIB-t.mjs → setup-Bvvqlz9d.mjs} +3 -3
  190. package/dist/{signal-0Yt8JTMn.mjs → signal-Dm4N0Bcc.mjs} +1 -1
  191. package/dist/{skill-list-goCqmfFF.mjs → skill-list-DkuCf0k4.mjs} +9 -3
  192. package/dist/{skills-DeWsLMwC.mjs → skills-DlDCTGVE.mjs} +2 -2
  193. package/dist/{skills-86j411Qz.mjs → skills-DxrO7HUz.mjs} +3 -3
  194. package/dist/snippet-B7Kau5U8.mjs +19 -0
  195. package/dist/{stash-BNKlbxtD.mjs → stash-j9JPv3ns.mjs} +5 -5
  196. package/dist/{status-B7bwHLkk.mjs → status-DcbvbRc5.mjs} +3 -3
  197. package/dist/{status-Bul9El-c.mjs → status-DxNcrfnI.mjs} +16 -7
  198. package/dist/{subscription-DPAGevGU.mjs → subscription-CSR2t-xK.mjs} +6 -6
  199. package/dist/{subscriptions-DIRsHcKP.mjs → subscriptions--0YvW40u.mjs} +6 -6
  200. package/dist/{summary-CeVt80Kb.mjs → summary-Dslv78-r.mjs} +3 -3
  201. package/dist/{sync-schema-B9fLcZoa.mjs → sync-schema-BSJiokQf.mjs} +6 -6
  202. package/dist/{sync-schema-oDTbhjW1.mjs → sync-schema-Dgij5ICd.mjs} +5 -5
  203. package/dist/{sync-task-BAMOf6_t.mjs → sync-task-a1zyF1Qb.mjs} +4 -5
  204. package/dist/{table-BFIOH6Pb.mjs → table-Uvu1roSx.mjs} +2 -2
  205. package/dist/table-qjgz2eek.mjs +26 -0
  206. package/dist/{table-selector-flags-BUR0-n6j.mjs → table-selector-flags-B9OiA3Z1.mjs} +3 -3
  207. package/dist/{table-Ci6bhZkb.mjs → table-zAdW5_3H.mjs} +5 -5
  208. package/dist/{text-CdtEI6l6.mjs → text-821Rn8M9.mjs} +1 -1
  209. package/dist/timeline-CNkrY3e3.mjs +21 -0
  210. package/dist/{timeline-event-BY2Hj72e.mjs → timeline-event-CSzx8EDP.mjs} +6 -6
  211. package/dist/{transform-D4z70PIN.mjs → transform-BKX6tl3i.mjs} +1 -1
  212. package/dist/transform-Y86R7_2F.mjs +28 -0
  213. package/dist/transform-job-Cg_1jzWI.mjs +22 -0
  214. package/dist/{transform-tag-CzjLKILh.mjs → transform-tag-DHg7j15d.mjs} +5 -5
  215. package/dist/{transforms-0uhBh0H4.mjs → transforms-DwxPfMiG.mjs} +5 -5
  216. package/dist/{tree-oBEw8hDj.mjs → tree-COFmA20J.mjs} +3 -3
  217. package/dist/{unpublish-BJ9OKN1N.mjs → unpublish-BQClh4U7.mjs} +3 -3
  218. package/dist/{unreferenced-D6UyA_Nu.mjs → unreferenced-DKc0zZDl.mjs} +6 -6
  219. package/dist/{update-CKJ6Das4.mjs → update-B-wcIl8T.mjs} +4 -4
  220. package/dist/{update-BqrZ-8IK.mjs → update-B8jQTspb.mjs} +8 -8
  221. package/dist/{update-BZgHTH5P.mjs → update-BBLbEPEP.mjs} +4 -4
  222. package/dist/{update-M7yDPmJ-.mjs → update-BNwjjMk6.mjs} +5 -5
  223. package/dist/{update-CEhub3Wz.mjs → update-BVFfoopT.mjs} +5 -5
  224. package/dist/{update-DGwGjOnH.mjs → update-BVMgrMy7.mjs} +5 -5
  225. package/dist/{update-CKe2Bwpv.mjs → update-Bgy2amR0.mjs} +6 -6
  226. package/dist/{update-D9eLVWFQ.mjs → update-C9HK3i8s.mjs} +5 -5
  227. package/dist/{update-Cv6IMLyu.mjs → update-CFfr1ZdL.mjs} +4 -4
  228. package/dist/{update-BK_SpSrG.mjs → update-CMRWXoZ2.mjs} +4 -4
  229. package/dist/{update-DKagaJw0.mjs → update-CPmPnEvm.mjs} +6 -6
  230. package/dist/{update-bGDXSnBh.mjs → update-Ca8LSZer.mjs} +5 -5
  231. package/dist/{update-DgmlUn64.mjs → update-DJwu8UT2.mjs} +4 -4
  232. package/dist/{update-DneJLQXe.mjs → update-Dwr3n7_n.mjs} +7 -7
  233. package/dist/{update-CYQkWLpZ.mjs → update-MxvqmZ3z.mjs} +6 -6
  234. package/dist/{update-dashcard-DNsf-gJ1.mjs → update-dashcard-C8QN2Qcu.mjs} +4 -4
  235. package/dist/{update-CHA4VWKT.mjs → update-kM3TDlEp.mjs} +7 -7
  236. package/dist/{update-7zsDGAWa.mjs → update-myfBF5qV.mjs} +4 -4
  237. package/dist/{upgrade-BRJH93W0.mjs → upgrade-BBOEVIRa.mjs} +4 -4
  238. package/dist/{upload-CGz8u2wG.mjs → upload-CoBd4qBp.mjs} +3 -3
  239. package/dist/{upload-DJB7DMA1.mjs → upload-CxpXpjCf.mjs} +2 -2
  240. package/dist/upload-fcim1xtK.mjs +13 -0
  241. package/dist/{uuid-3qmLYgrG.mjs → uuid-DW30GY7B.mjs} +2 -2
  242. package/dist/{validate-D_iikPkc.mjs → validate-n85yMraN.mjs} +1 -1
  243. package/dist/{validate-query-DIcK8alR.mjs → validate-query-BwXvFFYi.mjs} +3 -7
  244. package/dist/{values-Bn2Wcz4t.mjs → values-B6eFE4K5.mjs} +3 -3
  245. package/dist/{verify-C2K-zMuQ.mjs → verify-BSq7x66g.mjs} +3 -3
  246. package/dist/{verify-CCX2N98Q.mjs → verify-DnU-wXIs.mjs} +4 -4
  247. package/dist/{verify-CA4PEHlO.mjs → verify-Dp8tUbP3.mjs} +3 -3
  248. package/dist/{wait-DCgCJBqX.mjs → wait-D6Tw4ZxO.mjs} +6 -6
  249. package/dist/{wait-flags-aRQHTjwg.mjs → wait-flags-BRUzMI6S.mjs} +2 -2
  250. package/dist/{window-2ybHqui2.mjs → window-BqMFpWMh.mjs} +1 -1
  251. package/package.json +1 -1
  252. package/skill-data/core/SKILL.md +5 -5
  253. package/skill-data/dashboard/SKILL.md +6 -0
  254. package/skill-data/git-sync/SKILL.md +64 -14
  255. package/skill-data/native-sql/SKILL.md +1 -1
  256. package/skill-data/transform/SKILL.md +1 -63
  257. package/dist/archive-3MQyc5Ih.mjs +0 -32
  258. package/dist/card-P2-4BkXV.mjs +0 -25
  259. package/dist/collection-BI8Fy9vS.mjs +0 -21
  260. package/dist/create--b64pMVM.mjs +0 -34
  261. package/dist/create-C7fVbQbQ.mjs +0 -48
  262. package/dist/dashboard-C-YJxDam.mjs +0 -35
  263. package/dist/data-action-DER3EMD7.mjs +0 -24
  264. package/dist/data-action-DZxVB_hD.mjs +0 -85
  265. package/dist/data-action-R9qxJDPA.mjs +0 -40
  266. package/dist/delete-B0O-mf63.mjs +0 -44
  267. package/dist/delete-DPU-uQxU.mjs +0 -44
  268. package/dist/dependency-v9fHJ-N-.mjs +0 -15
  269. package/dist/document-CGvUmD9i.mjs +0 -20
  270. package/dist/execute-DvBse4aF.mjs +0 -45
  271. package/dist/field-C9AcBe8x.mjs +0 -21
  272. package/dist/get-BwKQw_rR.mjs +0 -31
  273. package/dist/get-DSjITPED.mjs +0 -32
  274. package/dist/git-sync-D4R0cHvk.mjs +0 -29
  275. package/dist/glossary-C6EnoEXg.mjs +0 -14
  276. package/dist/list-CzxrdWB8.mjs +0 -35
  277. package/dist/list-Owrzgtkc.mjs +0 -28
  278. package/dist/query-BKtMP5eH.mjs +0 -13
  279. package/dist/run-DlG3Ykj1.mjs +0 -63
  280. package/dist/snippet-C3gamfFK.mjs +0 -19
  281. package/dist/table-C--k-JXA.mjs +0 -26
  282. package/dist/timeline-ClMmTycg.mjs +0 -21
  283. package/dist/transform-BZaNFGeK.mjs +0 -28
  284. package/dist/transform-job-QMI2qWin.mjs +0 -22
  285. package/dist/transform-test-B9le1jKH.mjs +0 -16
  286. package/dist/transform-test-BjjCQ2xm.mjs +0 -169
  287. package/dist/transform-test-DHGKeuHa.mjs +0 -32
  288. package/dist/transport-BmNrb0Lz.mjs +0 -313
  289. package/dist/update-Bdi40RG0.mjs +0 -52
  290. package/dist/update-CXfLvhmK.mjs +0 -41
  291. package/dist/upload-T5wS1XtL.mjs +0 -13
  292. package/skill-data/data-action/SKILL.md +0 -125
  293. package/skill-data/transform-test-plan/SKILL.md +0 -82
  294. package/skill-data/transform-test-plan/references/checklist.md +0 -32
  295. package/skill-data/transform-test-plan/references/checks.md +0 -156
@@ -1,26 +1,25 @@
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 isNonInterruptFailure, Ct as ResponseShapeError, Dt as errorMessage, Et as ValidationError, G as isRetryableStatus, J as isPlainObject, N as CapabilityError, Ot as formatZodIssue, St as PartialWriteError, U as HttpError, X as parseJson, Y as JSON_CONTENT_TYPE, Z as parseJsonOrPlain, n as isMethodKey, r as methodRequirements, vt as ConfigError, wt as TimeoutError, xt as NetworkError, yt as InternalError } 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 { B as credentialAuthHeader, F as probeBudget, J as createServerProfile, L as profileFromProperties, M as buildNetworkError, N as isConnectionClosed, P as PROBE_PATH, R as serverInfoFromProperties, V as credentialSecrets, W as normalizeUrl, Z as SessionProperties, z as assertCredentialHeaderSafe } from "./runtime-C5Ha6Ou8.mjs";
4
+ import { n as throwIfAborted, r as untilAborted, t as combineAborts } from "./signal-Dm4N0Bcc.mjs";
5
+ import { n as featureFailures, r as supersedingFeature, t as checkFeatures } from "./requirement-check-T3bjQ0Mr.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
- import { r as PivotExportFormat, t as DatasetQuery } from "./query-BKtMP5eH.mjs";
8
- 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";
8
+ import { a as CardQueryResult, d as PivotExportFormat, l as DatasetQuery, t as Card } from "./card-Bv4ZN0oV.mjs";
9
+ import { b as TableVisibilityType, p as TableQueryMetadata, s as TableDataLayerTier, t as Table, u as TableForeignKey, v as TableSelectors } from "./table-zAdW5_3H.mjs";
10
+ import { t as Database } from "./database-BdKkae1m.mjs";
11
11
  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";
12
+ import { r as QueryMetadata, t as CompiledQuery } from "./dataset-DZgJm5hP.mjs";
13
+ import { c as CollectionItem, m as CollectionTreeNode, r as Collection } from "./collection-zrZoJfgR.mjs";
14
14
  import { t as ContentTranslationUploadResult } from "./content-translation-BrHOrmj9.mjs";
15
15
  import { l as Dashcard, o as DashboardDetail, r as DashboardCopy, t as Dashboard } from "./dashboard-Phz3XpRM.mjs";
16
- 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";
16
+ import { r as pollUntil } from "./poll-BKo5dK0l.mjs";
17
+ import { i as DependencyEntity, s as DependencyGraph, t as BreakingSource, u as DependencyNode } from "./dependency-tQBxYjFX.mjs";
18
+ import { t as Document } from "./document-BDobDiap.mjs";
20
19
  import { i as EidTranslateResult } from "./eid-translation-zEHtr5Oc.mjs";
21
20
  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";
21
+ import { t as Glossary } from "./glossary-6LLpItof.mjs";
22
+ import { a as libraryWireSchema, i as LibraryPublishTablesInput, n as LibraryCollectionInfo, o as toLibrary } from "./library-fWEv38OB.mjs";
24
23
  import { t as Measure } from "./measure-CtfjqJFP.mjs";
25
24
  import { r as ModerationReviewCreateInput, t as ModerationReview } from "./moderation-review-BdqEH6N1.mjs";
26
25
  import { t as Pulse } from "./pulse-Dz0ocv0l.mjs";
@@ -32,9 +31,9 @@ import { n as SetupResult } from "./setup-CO7QV-ao.mjs";
32
31
  import { i as TimelineEvent, t as Timeline } from "./timeline-CCSU2qOS.mjs";
33
32
  import { c as transformJobSchema, n as TransformJobActiveResult, s as transformJobRunResultSchema } from "./transform-job-CCguL6LP.mjs";
34
33
  import { t as TransformTag } from "./transform-tag-CcElGp3C.mjs";
35
- import { i as TransformTestRunResult, t as TransformTest } from "./transform-test-BjjCQ2xm.mjs";
36
34
  import { t as UploadResult } from "./upload-DAtxBBms.mjs";
37
35
  import { z } from "zod";
36
+ import { setTimeout } from "node:timers/promises";
38
37
  //#region \0rolldown/runtime.js
39
38
  var __defProp = Object.defineProperty;
40
39
  var __exportAll = (all, no_symbols) => {
@@ -47,15 +46,425 @@ var __exportAll = (all, no_symbols) => {
47
46
  return target;
48
47
  };
49
48
  //#endregion
49
+ //#region ../client/src/http/response-shape.ts
50
+ async function parseJsonResponse(text, schema, context) {
51
+ try {
52
+ return parseJson(text, schema, { source: context.url });
53
+ } catch (error) {
54
+ if (error instanceof ValidationError) throw ResponseShapeError.fromZodIssues({
55
+ kind: "zod",
56
+ method: context.method,
57
+ url: context.url,
58
+ status: context.status,
59
+ zodIssues: error.developerDetail.zodIssues,
60
+ serverTag: await context.getServerTag(),
61
+ serverSkew: context.serverSkew
62
+ });
63
+ throw error;
64
+ }
65
+ }
66
+ //#endregion
67
+ //#region ../client/src/http/retry.ts
68
+ const BASE_DELAY_MS = 250;
69
+ const MAX_DELAY_MS = 8e3;
70
+ const MS_PER_SECOND = 1e3;
71
+ function backoffDelay({ attempt, retryAfterHeader }) {
72
+ const fromHeader = parseRetryAfter(retryAfterHeader);
73
+ if (fromHeader !== null) return fromHeader;
74
+ return Math.min(MAX_DELAY_MS, BASE_DELAY_MS * 2 ** attempt);
75
+ }
76
+ function parseRetryAfter(header) {
77
+ if (!header) return null;
78
+ const numeric = Number(header);
79
+ if (Number.isFinite(numeric) && numeric >= 0) return Math.round(numeric * MS_PER_SECOND);
80
+ const dateMs = Date.parse(header);
81
+ if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
82
+ return null;
83
+ }
84
+ function sleep(ms, signal) {
85
+ return setTimeout(ms, void 0, { signal });
86
+ }
87
+ async function runWithRetries(attempt, signal) {
88
+ let attemptIndex = 0;
89
+ while (true) {
90
+ const outcome = await attempt(attemptIndex);
91
+ if (outcome.kind === "success") return outcome.response;
92
+ await sleep(outcome.delayMs, signal);
93
+ attemptIndex += 1;
94
+ }
95
+ }
96
+ const DEFAULT_TIMEOUT_MS = 3e4;
97
+ const OCTET_STREAM_CONTENT_TYPE = "application/octet-stream";
98
+ const TEXT_CONTENT_TYPE_PREFIX = "text/";
99
+ const ERROR_BODY_BYTE_CAP = 64 * 1024;
100
+ const IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
101
+ "GET",
102
+ "HEAD",
103
+ "OPTIONS"
104
+ ]);
105
+ function createTransport(config, options) {
106
+ const baseUrl = normalizeUrl(config.url);
107
+ assertCredentialHeaderSafe(config.credential);
108
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
109
+ let inForce = options.server ?? null;
110
+ let newest = null;
111
+ let probesStarted = 0;
112
+ let applied = null;
113
+ const inFlight = /* @__PURE__ */ new Set();
114
+ const getServerTag = options.getServerTag ?? (async () => tagOf(inForce));
115
+ const refreshCredential = options.refreshCredential;
116
+ let credential = config.credential;
117
+ const knownSecrets = new Set(credentialSecrets(credential));
118
+ const redactionContext = { knownSecrets };
119
+ let refreshInFlight = null;
120
+ function tryRefreshCredential() {
121
+ const refresh = refreshCredential;
122
+ if (credential.kind !== "oauth" || refresh === void 0) return Promise.resolve(false);
123
+ refreshInFlight ??= refreshOnce(refresh).finally(() => {
124
+ refreshInFlight = null;
125
+ });
126
+ return refreshInFlight;
127
+ }
128
+ async function refreshOnce(refresh) {
129
+ const next = await refresh();
130
+ if (next === null) return false;
131
+ credential = next;
132
+ for (const secret of credentialSecrets(next)) knownSecrets.add(secret);
133
+ return true;
134
+ }
135
+ async function attemptOnce(prepared, attempt) {
136
+ const hasRetriesLeft = attempt < prepared.retries;
137
+ const timeoutSignal = AbortSignal.timeout(prepared.timeoutMs);
138
+ const combined = combineAborts(timeoutSignal, prepared.cancelSignal);
139
+ let response;
140
+ try {
141
+ response = await fetchImpl(prepared.url, {
142
+ method: prepared.method,
143
+ headers: prepared.headers,
144
+ body: prepared.body,
145
+ signal: combined
146
+ });
147
+ } catch (error) {
148
+ throwIfAborted(prepared.cancelSignal);
149
+ const isStaleSocketFirstTry = attempt === 0 && isConnectionClosed(error);
150
+ if (prepared.idempotent && (hasRetriesLeft || isStaleSocketFirstTry)) return {
151
+ kind: "retry",
152
+ delayMs: backoffDelay({ attempt })
153
+ };
154
+ if (timeoutSignal.aborted) throw new TimeoutError(`Request timed out after ${prepared.timeoutMs}ms`, {
155
+ kind: "http",
156
+ method: prepared.method,
157
+ url: prepared.url,
158
+ timeoutMs: prepared.timeoutMs
159
+ });
160
+ throw buildNetworkError(error, prepared.method, prepared.url);
161
+ }
162
+ const canRetryStatus = hasRetriesLeft && prepared.idempotent;
163
+ if (!response.ok && isRetryableStatus(response.status) && canRetryStatus) {
164
+ const retryAfter = response.headers.get("Retry-After");
165
+ response.body?.cancel().catch(() => void 0);
166
+ return {
167
+ kind: "retry",
168
+ delayMs: backoffDelay({
169
+ attempt,
170
+ retryAfterHeader: retryAfter
171
+ })
172
+ };
173
+ }
174
+ if (!response.ok) {
175
+ const rawBody = await readBodyForError(response);
176
+ const serverTag = await getServerTag();
177
+ throw new HttpError({
178
+ status: response.status,
179
+ statusText: response.statusText,
180
+ method: prepared.method,
181
+ url: prepared.url,
182
+ responseHeaders: response.headers,
183
+ rawBody,
184
+ serverTag,
185
+ redactionContext
186
+ });
187
+ }
188
+ assertContentType(response, prepared);
189
+ return {
190
+ kind: "success",
191
+ response
192
+ };
193
+ }
194
+ async function executeRaw(prepared) {
195
+ try {
196
+ return await runWithRetries((attempt) => attemptOnce(prepared, attempt), prepared.cancelSignal);
197
+ } catch (error) {
198
+ throwIfAborted(prepared.cancelSignal);
199
+ throw error;
200
+ }
201
+ }
202
+ async function executeWithAuthRefresh(path, opts) {
203
+ const prepared = prepare(path, opts);
204
+ try {
205
+ return {
206
+ response: await executeRaw(prepared),
207
+ prepared
208
+ };
209
+ } catch (error) {
210
+ if (error instanceof HttpError && error.status === 401) {
211
+ if (await tryRefreshCredential()) {
212
+ const retried = prepare(path, opts);
213
+ return {
214
+ response: await executeRaw(retried),
215
+ prepared: retried
216
+ };
217
+ }
218
+ }
219
+ throw error;
220
+ }
221
+ }
222
+ function prepare(path, opts = {}) {
223
+ const method = opts.method ?? "GET";
224
+ const expectContentType = opts.expectContentType ?? "json";
225
+ const url = buildUrl(baseUrl, path, opts.query);
226
+ const headers = new Headers();
227
+ const auth = credentialAuthHeader(credential);
228
+ headers.set(auth.name, auth.value);
229
+ headers.set("accept", acceptHeader(expectContentType));
230
+ headers.set("user-agent", options.userAgent);
231
+ let body = null;
232
+ if (opts.body !== void 0 && opts.body !== null) if (typeof opts.body === "string" || opts.body instanceof URLSearchParams) body = opts.body;
233
+ else if (opts.body instanceof FormData || opts.body instanceof ReadableStream) body = opts.body;
234
+ else if (opts.body instanceof Uint8Array) {
235
+ body = opts.body;
236
+ headers.set("content-type", OCTET_STREAM_CONTENT_TYPE);
237
+ } else {
238
+ body = JSON.stringify(opts.body);
239
+ headers.set("content-type", JSON_CONTENT_TYPE);
240
+ }
241
+ return {
242
+ url,
243
+ method,
244
+ headers,
245
+ body,
246
+ expectContentType,
247
+ retries: opts.retries ?? 3,
248
+ idempotent: opts.idempotent ?? IDEMPOTENT_METHODS.has(method),
249
+ timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
250
+ cancelSignal: combineAborts(opts.signal, options.signal)
251
+ };
252
+ }
253
+ async function readJson(path, opts = {}) {
254
+ const chosenFor = inForce;
255
+ const { response, prepared } = await executeWithAuthRefresh(path, {
256
+ ...opts,
257
+ expectContentType: "json"
258
+ });
259
+ return {
260
+ text: await response.text(),
261
+ context: {
262
+ method: prepared.method,
263
+ url: prepared.url,
264
+ status: response.status,
265
+ ...namingOf(chosenFor)
266
+ }
267
+ };
268
+ }
269
+ function namingOf(profile) {
270
+ if (profile === null) return {
271
+ getServerTag,
272
+ serverSkew: null
273
+ };
274
+ return {
275
+ getServerTag: async () => profile.version.tag,
276
+ serverSkew: profile.skew
277
+ };
278
+ }
279
+ async function runProbe(order, budget) {
280
+ const read = await readJson(PROBE_PATH, probeBudget(budget));
281
+ const properties = await parseJsonResponse(read.text, SessionProperties, read.context);
282
+ const info = serverInfoFromProperties(properties);
283
+ const profile = createServerProfile(info);
284
+ if (applied === null || applied.order < order) {
285
+ options.onServerProbed?.(info, profile);
286
+ applied = {
287
+ order,
288
+ profile
289
+ };
290
+ inForce = profile;
291
+ }
292
+ return {
293
+ properties,
294
+ profile,
295
+ read
296
+ };
297
+ }
298
+ function startProbe(budget) {
299
+ probesStarted += 1;
300
+ const probed = runProbe(probesStarted, budget);
301
+ newest = probed;
302
+ inFlight.add(probed);
303
+ probed.then(() => {
304
+ inFlight.delete(probed);
305
+ }, () => {
306
+ inFlight.delete(probed);
307
+ if (newest === probed) newest = null;
308
+ });
309
+ return probed;
310
+ }
311
+ function appliedProfile() {
312
+ return applied === null ? null : applied.profile;
313
+ }
314
+ function verifying() {
315
+ const settled = appliedProfile();
316
+ if (newest === null && settled !== null) return Promise.resolve(settled);
317
+ return (newest ?? startProbe({})).then((probed) => probed.profile, fallBackTo(appliedProfile));
318
+ }
319
+ function profileInForce() {
320
+ const current = inForce;
321
+ if (current === null) return verifying();
322
+ if (newest === null) return Promise.resolve(current);
323
+ return newest.then((probed) => probed.profile, fallBackTo(() => inForce));
324
+ }
325
+ async function server(wait = {}) {
326
+ throwIfAborted(wait.signal);
327
+ return untilAborted(profileInForce(), wait.signal);
328
+ }
329
+ async function verifiedServer(wait = {}) {
330
+ throwIfAborted(wait.signal);
331
+ return untilAborted(verifying(), wait.signal);
332
+ }
333
+ async function probe(reader, read = {}) {
334
+ const { signal, ...budget } = read;
335
+ throwIfAborted(signal);
336
+ const probed = await untilAborted(startProbe(budget), signal);
337
+ const context = {
338
+ ...probed.read.context,
339
+ ...namingOf(probed.profile)
340
+ };
341
+ return parseJsonResponse(probed.read.text, reader, context);
342
+ }
343
+ async function probesSettled() {
344
+ while (inFlight.size > 0) await Promise.allSettled(inFlight);
345
+ }
346
+ async function requireFeatures(features, wait = {}) {
347
+ if (features.length === 0) return;
348
+ const profile = await preflightServer(wait);
349
+ if (profile === null) return;
350
+ for (const failure of featureFailures(features, profile)) refuseBeforeSending(new CapabilityError(failure));
351
+ }
352
+ async function preflightServer(wait = {}) {
353
+ try {
354
+ return await verifiedServer(wait);
355
+ } catch (error) {
356
+ if (!isNonInterruptFailure(error)) throw error;
357
+ skipOrThrow({
358
+ kind: "unverified",
359
+ failure: error
360
+ });
361
+ return null;
362
+ }
363
+ }
364
+ function refuseBeforeSending(refusal) {
365
+ skipOrThrow({
366
+ kind: "refused",
367
+ refusal
368
+ });
369
+ }
370
+ function skipOrThrow(skipped) {
371
+ const hook = options.onPreflightSkipped;
372
+ if (hook === void 0) throw skippedError(skipped);
373
+ hook(skipped);
374
+ }
375
+ return {
376
+ server,
377
+ verifiedServer,
378
+ probe,
379
+ requireFeatures,
380
+ preflightServer,
381
+ refuseBeforeSending,
382
+ probesSettled,
383
+ async requestRaw(path, opts) {
384
+ return (await executeWithAuthRefresh(path, opts ?? {})).response;
385
+ },
386
+ async requestParsed(schema, path, opts) {
387
+ const read = await readJson(path, opts);
388
+ return parseJsonResponse(read.text, schema, read.context);
389
+ },
390
+ async requestStream(path, opts) {
391
+ const { response, prepared } = await executeWithAuthRefresh(path, {
392
+ ...opts,
393
+ expectContentType: opts?.expectContentType ?? "binary"
394
+ });
395
+ if (!response.body) throw new NetworkError("Response had no body to stream", {
396
+ method: prepared.method,
397
+ url: prepared.url,
398
+ cause: "missing body"
399
+ });
400
+ return response.body;
401
+ }
402
+ };
403
+ }
404
+ function skippedError(skipped) {
405
+ return skipped.kind === "refused" ? skipped.refusal : skipped.failure;
406
+ }
407
+ function fallBackTo(fallback) {
408
+ return (error) => {
409
+ const profile = fallback();
410
+ if (profile !== null && isNonInterruptFailure(error)) return profile;
411
+ throw error;
412
+ };
413
+ }
414
+ function tagOf(profile) {
415
+ return profile === null ? null : profile.version.tag;
416
+ }
417
+ function buildUrl(baseUrl, path, query) {
418
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
419
+ const target = new URL(baseUrl + normalizedPath);
420
+ if (query) for (const [key, value] of Object.entries(query)) {
421
+ if (value === void 0) continue;
422
+ if (Array.isArray(value)) for (const entry of value) target.searchParams.append(key, String(entry));
423
+ else target.searchParams.append(key, String(value));
424
+ }
425
+ return target.toString();
426
+ }
427
+ function acceptHeader(expected) {
428
+ if (expected === "json") return JSON_CONTENT_TYPE;
429
+ if (expected === "text") return "text/*";
430
+ return "*/*";
431
+ }
432
+ function assertContentType(response, prepared) {
433
+ if (prepared.expectContentType === "binary") return;
434
+ const contentType = response.headers.get("content-type");
435
+ if (contentType === null) throwContentTypeMismatch(response, prepared, prepared.expectContentType);
436
+ if (prepared.expectContentType === "json" && !contentType.includes("application/json")) throwContentTypeMismatch(response, prepared, "json");
437
+ if (prepared.expectContentType === "text" && !contentType.startsWith(TEXT_CONTENT_TYPE_PREFIX)) throwContentTypeMismatch(response, prepared, "text");
438
+ }
439
+ function throwContentTypeMismatch(response, prepared, expected) {
440
+ const actual = response.headers.get("content-type") ?? "no content-type";
441
+ throw new HttpError({
442
+ status: response.status,
443
+ statusText: response.statusText,
444
+ method: prepared.method,
445
+ url: prepared.url,
446
+ responseHeaders: response.headers,
447
+ rawBody: null,
448
+ overrideUserMessage: `Expected ${expected} response but got ${actual}`
449
+ });
450
+ }
451
+ async function readBodyForError(response) {
452
+ try {
453
+ return Buffer.from(await response.arrayBuffer()).subarray(0, ERROR_BODY_BYTE_CAP).toString("utf8");
454
+ } catch (error) {
455
+ return `[body read failed: ${errorMessage(error)}]`;
456
+ }
457
+ }
458
+ //#endregion
50
459
  //#region ../client/src/resources/pivot-export.ts
51
- const PivotedExportsSetting = z.object({ "enable-pivoted-exports": z.boolean() });
460
+ const PivotedExportsProperty = SessionProperties.extend({ "enable-pivoted-exports": z.boolean() });
52
461
  /**
53
462
  * Refuse a pivoted export the server would answer with plain rows: a JSON export keeps only the
54
463
  * ungrouped rows, and an admin can turn pivoted CSV and XLSX exports off.
55
464
  */
56
465
  async function assertPivotedExport(transport, format, options) {
57
466
  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");
467
+ 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
468
  }
60
469
  //#endregion
61
470
  //#region ../client/src/resources/card.ts
@@ -63,7 +472,6 @@ const CardApiList = z.array(Card);
63
472
  function cardResource(transport) {
64
473
  /** List cards. `f` picks a server-side preset; `model_id` scopes the presets that need an id. */
65
474
  async function list(params = {}, options = {}) {
66
- await transport.require("card.list", options);
67
475
  return {
68
476
  data: await transport.requestParsed(CardApiList, "/api/card", {
69
477
  ...options,
@@ -77,12 +485,10 @@ function cardResource(transport) {
77
485
  }
78
486
  /** Get one card by id. */
79
487
  async function get(id, options = {}) {
80
- await transport.require("card.get", options);
81
488
  return transport.requestParsed(Card, `/api/card/${id}`, { ...options });
82
489
  }
83
490
  /** Create a card — a question, a model, or a metric — from a full card body. */
84
491
  async function create(params, options = {}) {
85
- await transport.require("card.create", options);
86
492
  return transport.requestParsed(Card, "/api/card", {
87
493
  ...options,
88
494
  method: "POST",
@@ -91,7 +497,6 @@ function cardResource(transport) {
91
497
  }
92
498
  /** Update a card by id, patching only the fields the body carries. */
93
499
  async function update(id, params, options = {}) {
94
- await transport.require("card.update", options);
95
500
  return transport.requestParsed(Card, `/api/card/${id}`, {
96
501
  ...options,
97
502
  method: "PUT",
@@ -100,12 +505,10 @@ function cardResource(transport) {
100
505
  }
101
506
  /** Archive (soft-delete) a card by id. Metabase models this as an update, not its own endpoint. */
102
507
  async function archive(id, options = {}) {
103
- await transport.require("card.archive", options);
104
508
  return update(id, { archived: true }, options);
105
509
  }
106
510
  /** Run a saved card and return the query result envelope. */
107
511
  async function query(id, params, options = {}) {
108
- await transport.require("card.query", options);
109
512
  return transport.requestParsed(CardQueryResult, `/api/card/${id}/query`, {
110
513
  ...options,
111
514
  method: "POST",
@@ -121,9 +524,8 @@ function cardResource(transport) {
121
524
  * there before the wire.
122
525
  */
123
526
  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
527
  if (params.pivot_results) await assertPivotedExport(transport, format, options);
528
+ await transport.requireFeatures(params.csv_include_bom ? ["exportCsvByteOrderMark"] : [], options);
127
529
  const body = new URLSearchParams({
128
530
  parameters: JSON.stringify(params.parameters),
129
531
  format_rows: String(params.format_rows),
@@ -141,7 +543,6 @@ function cardResource(transport) {
141
543
  * snippets its query references, plus the card's own metadata for a model or a native query.
142
544
  */
143
545
  async function queryMetadata(id, options = {}) {
144
- await transport.require("card.queryMetadata", options);
145
546
  return transport.requestParsed(QueryMetadata, `/api/card/${id}/query_metadata`, { ...options });
146
547
  }
147
548
  return {
@@ -227,7 +628,6 @@ function collectionResource(transport) {
227
628
  * preset includes the Library collections that match it.
228
629
  */
229
630
  async function list(params = {}, options = {}) {
230
- await transport.require("collection.list", options);
231
631
  return {
232
632
  data: await transport.requestParsed(CollectionApiList, "/api/collection", {
233
633
  ...options,
@@ -238,12 +638,10 @@ function collectionResource(transport) {
238
638
  }
239
639
  /** Get one collection by id, entity id, or the `root`/`trash` alias. */
240
640
  async function get(ref, options = {}) {
241
- await transport.require("collection.get", options);
242
641
  return transport.requestParsed(Collection, `/api/collection/${refPath(ref)}`, { ...options });
243
642
  }
244
643
  /** Create a new collection. */
245
644
  async function create(params, options = {}) {
246
- await transport.require("collection.create", options);
247
645
  return transport.requestParsed(Collection, "/api/collection", {
248
646
  ...options,
249
647
  method: "POST",
@@ -256,7 +654,6 @@ function collectionResource(transport) {
256
654
  * moves a collection into the trash ignores `parent_id`.
257
655
  */
258
656
  async function update(ref, params, options = {}) {
259
- await transport.require("collection.update", options);
260
657
  return transport.requestParsed(Collection, `/api/collection/${refPath(ref)}`, {
261
658
  ...options,
262
659
  method: "PUT",
@@ -265,7 +662,6 @@ function collectionResource(transport) {
265
662
  }
266
663
  /** Archive (soft-delete) a collection. Metabase models this as an update, not its own endpoint. */
267
664
  async function archive(ref, options = {}) {
268
- await transport.require("collection.archive", options);
269
665
  return update(ref, { archived: true }, options);
270
666
  }
271
667
  /**
@@ -273,7 +669,6 @@ function collectionResource(transport) {
273
669
  * the caller consumes pages rather than a single list and decides how far to pull.
274
670
  */
275
671
  async function* itemPages(ref, params = {}, options = {}) {
276
- await transport.require("collection.itemPages", options);
277
672
  yield* paginatePages(transport, `/api/collection/${refPath(ref)}/items`, CollectionItem, {
278
673
  query: {
279
674
  models: params.models,
@@ -288,7 +683,6 @@ function collectionResource(transport) {
288
683
  }
289
684
  /** Fetch the collection hierarchy as a forest of nested nodes. */
290
685
  async function tree(params = {}, options = {}) {
291
- await transport.require("collection.tree", options);
292
686
  return {
293
687
  data: await transport.requestParsed(CollectionTreeApiList, "/api/collection/tree", {
294
688
  ...options,
@@ -308,6 +702,128 @@ function collectionResource(transport) {
308
702
  };
309
703
  }
310
704
  //#endregion
705
+ //#region ../client/src/version/refusal.ts
706
+ const REFUSAL_STATUSES = /* @__PURE__ */ new Set([
707
+ 400,
708
+ 402,
709
+ 404
710
+ ]);
711
+ function explainer(transport, namespace) {
712
+ function explain(name, method, parameters) {
713
+ const key = methodKey(namespace, name);
714
+ return async (...args) => {
715
+ try {
716
+ return await method(...args);
717
+ } catch (error) {
718
+ throw await explainRefusal(transport, callRequirement(key, parameters, args), error);
719
+ }
720
+ };
721
+ }
722
+ function explainWalk(name, method, parameters) {
723
+ const key = methodKey(namespace, name);
724
+ return async function* (...args) {
725
+ try {
726
+ yield* method(...args);
727
+ } catch (error) {
728
+ throw await explainRefusal(transport, callRequirement(key, parameters, args), error);
729
+ }
730
+ };
731
+ }
732
+ function refuse(name, method) {
733
+ const key = methodKey(namespace, name);
734
+ return async (...args) => {
735
+ await transport.requireFeatures(methodRequirements(key), trailingWait(args));
736
+ return method(...args);
737
+ };
738
+ }
739
+ function refuseAfterReading(name, reader, method) {
740
+ const key = methodKey(namespace, name);
741
+ return async (options = {}) => {
742
+ const properties = await transport.probe(reader, options);
743
+ const profile = profileFromProperties(properties);
744
+ for (const failure of featureFailures(methodRequirements(key), profile)) transport.refuseBeforeSending(new CapabilityError(failure));
745
+ return method(properties);
746
+ };
747
+ }
748
+ return {
749
+ explain,
750
+ explainWalk,
751
+ refuse,
752
+ refuseAfterReading
753
+ };
754
+ }
755
+ function trailingWait(args) {
756
+ const last = args.at(-1);
757
+ if (isPlainObject(last) && last["signal"] instanceof AbortSignal) return { signal: last["signal"] };
758
+ return {};
759
+ }
760
+ function methodKey(namespace, name) {
761
+ const key = `${namespace}.${name}`;
762
+ if (!isMethodKey(key)) throw new InternalError(`no requirements entry for ${key}`);
763
+ return key;
764
+ }
765
+ function callRequirement(key, parameters, args) {
766
+ return {
767
+ parameters: parameters === void 0 ? [] : parameters(...args),
768
+ method: methodRequirements(key)
769
+ };
770
+ }
771
+ /**
772
+ * The error a call failed with, as a `CapabilityError` carrying the server's answer when the server
773
+ * refused it for lacking one of the features `call` needs, and unchanged otherwise. The server's own
774
+ * refusal is the verdict; the profile only names what was missing. The explaining probe is bounded
775
+ * by its own timeout and the client's signal; the error stands when the probe fails, and an
776
+ * interrupt surfaces as one.
777
+ */
778
+ async function explainRefusal(transport, call, error) {
779
+ if (!mayBeRefusal(call, error)) return error;
780
+ let profile;
781
+ try {
782
+ profile = await transport.verifiedServer();
783
+ } catch (probeError) {
784
+ if (isNonInterruptFailure(probeError)) return error;
785
+ throw probeError;
786
+ }
787
+ const failure = explainedFailure(call, profile, error);
788
+ return failure === null ? error : new CapabilityError(failure, error);
789
+ }
790
+ /** Every feature `call` needs, parameters first, since a parameter's floor sits above its method's. */
791
+ function callFeatures(call) {
792
+ return [...call.parameters.map((parameter) => parameter.feature), ...call.method];
793
+ }
794
+ /**
795
+ * Whether `error` may be the server refusing `call` for a feature it lacks. A call that needs no
796
+ * feature cannot be, and neither can a 404 for a missing row, which comes from a route the server
797
+ * serves.
798
+ */
799
+ function mayBeRefusal(call, error) {
800
+ if (callFeatures(call).length === 0) return false;
801
+ if (!(error instanceof HttpError) || error.kind === "resource-missing") return false;
802
+ return REFUSAL_STATUSES.has(error.status);
803
+ }
804
+ /**
805
+ * The feature `profile` lacks that explains `refusal` of `call`, or `null` when the server has every
806
+ * feature the call needs, so it failed for some other reason, or when the refusal points elsewhere.
807
+ */
808
+ function explainedFailure(call, profile, refusal) {
809
+ const failure = checkFeatures(callFeatures(call), profile);
810
+ if (failure === null || pointsElsewhere(call, profile, refusal)) return null;
811
+ return failure;
812
+ }
813
+ function pointsElsewhere(call, profile, refusal) {
814
+ if (refusal.status !== 400 || checkFeatures(call.method, profile) !== null) return false;
815
+ const named = rejectedFields(refusal);
816
+ const gated = new Set(call.parameters.filter((parameter) => checkFeatures([parameter.feature], profile) !== null).flatMap((parameter) => parameter.fields));
817
+ return named.some((field) => !gated.has(field));
818
+ }
819
+ function rejectedFields(refusal) {
820
+ return [refusal.fieldErrors, refusal.specificFieldErrors].flatMap((errors) => errors === null ? [] : Object.keys(errors).map(topLevelField));
821
+ }
822
+ function topLevelField(path) {
823
+ const end = path.indexOf(".");
824
+ return end === -1 ? path : path.slice(0, end);
825
+ }
826
+ //#endregion
311
827
  //#region ../client/src/resources/csv-upload.ts
312
828
  const CSV_CONTENT_TYPE = "text/csv";
313
829
  function buildCsvFormData(file) {
@@ -319,14 +835,13 @@ function buildCsvFormData(file) {
319
835
  //#region ../client/src/resources/content-translation.ts
320
836
  const CONTENT_TRANSLATION_PATH = "/api/ee/content-translation";
321
837
  function contentTranslationResource(transport) {
838
+ const { explain } = explainer(transport, "contentTranslation");
322
839
  /** Download the complete content translation dictionary as CSV. */
323
840
  async function download(options = {}) {
324
- await transport.require("contentTranslation.download", options);
325
841
  return transport.requestStream(`${CONTENT_TRANSLATION_PATH}/csv`, options);
326
842
  }
327
843
  /** Replace the complete content translation dictionary with the attached CSV. */
328
844
  async function upload(file, options = {}) {
329
- await transport.require("contentTranslation.upload", options);
330
845
  const form = buildCsvFormData(file);
331
846
  return transport.requestParsed(ContentTranslationUploadResult, `${CONTENT_TRANSLATION_PATH}/upload-dictionary`, {
332
847
  ...options,
@@ -335,8 +850,8 @@ function contentTranslationResource(transport) {
335
850
  });
336
851
  }
337
852
  return {
338
- download,
339
- upload
853
+ download: explain("download", download),
854
+ upload: explain("upload", upload)
340
855
  };
341
856
  }
342
857
  //#endregion
@@ -347,7 +862,6 @@ function dashboardResource(transport) {
347
862
  const card = cardResource(transport);
348
863
  /** List dashboards. `f` picks a server-side preset. */
349
864
  async function list(params = {}, options = {}) {
350
- await transport.require("dashboard.list", options);
351
865
  return {
352
866
  data: await transport.requestParsed(DashboardApiList, "/api/dashboard", {
353
867
  ...options,
@@ -358,7 +872,6 @@ function dashboardResource(transport) {
358
872
  }
359
873
  /** Get one dashboard by id, with its dashcards, tabs and parameters. */
360
874
  async function get(id, options = {}) {
361
- await transport.require("dashboard.get", options);
362
875
  return transport.requestParsed(DashboardDetail, `/api/dashboard/${id}`, { ...options });
363
876
  }
364
877
  /**
@@ -366,7 +879,6 @@ function dashboardResource(transport) {
366
879
  * puts content on a fresh dashboard.
367
880
  */
368
881
  async function create(params, options = {}) {
369
- await transport.require("dashboard.create", options);
370
882
  return transport.requestParsed(Dashboard, "/api/dashboard", {
371
883
  ...options,
372
884
  method: "POST",
@@ -378,7 +890,6 @@ function dashboardResource(transport) {
378
890
  * full replacements: whatever the body omits is deleted from the dashboard.
379
891
  */
380
892
  async function update(id, params, options = {}) {
381
- await transport.require("dashboard.update", options);
382
893
  return transport.requestParsed(DashboardDetail, `/api/dashboard/${id}`, {
383
894
  ...options,
384
895
  method: "PUT",
@@ -396,7 +907,6 @@ function dashboardResource(transport) {
396
907
  * is duplicated. Action, link and placeholder dashcards are not copied.
397
908
  */
398
909
  async function copy(id, params = {}, options = {}) {
399
- await transport.require("dashboard.copy", options);
400
910
  return transport.requestParsed(DashboardCopy, `/api/dashboard/${id}/copy`, {
401
911
  ...options,
402
912
  method: "POST",
@@ -409,7 +919,6 @@ function dashboardResource(transport) {
409
919
  * an archive is a state change on the dashboard itself, and its dashcards are not part of it.
410
920
  */
411
921
  async function archive(id, options = {}) {
412
- await transport.require("dashboard.archive", options);
413
922
  return transport.requestParsed(Dashboard, `/api/dashboard/${id}`, {
414
923
  ...options,
415
924
  method: "PUT",
@@ -422,7 +931,6 @@ function dashboardResource(transport) {
422
931
  * currently stands.
423
932
  */
424
933
  async function updateDashcard(dashboardId, dashcardId, params, options = {}) {
425
- await transport.require("dashboard.updateDashcard", options);
426
934
  const dashboard = await get(dashboardId, options);
427
935
  const target = dashboard.dashcards.find((dashcard) => dashcard.id === dashcardId);
428
936
  if (target === void 0) throw new ConfigError(`dashcard ${dashcardId} not found on dashboard ${dashboardId}`);
@@ -441,7 +949,6 @@ function dashboardResource(transport) {
441
949
  * any other way propagates as the failure it was.
442
950
  */
443
951
  async function checkCardReferences(dashcards, options = {}) {
444
- await transport.require("dashboard.checkCardReferences", options);
445
952
  const grouped = groupByCardId(collectCardReferences(dashcards));
446
953
  return (await Promise.all(Array.from(grouped.entries()).map(async ([cardId, references]) => ({
447
954
  references,
@@ -461,7 +968,7 @@ function dashboardResource(transport) {
461
968
  } catch (error) {
462
969
  if (!(error instanceof HttpError)) throw error;
463
970
  if (error.kind === "resource-missing") return { reason: "missing" };
464
- if (error.kind === "auth") return {
971
+ if (error.kind === "auth" || error.kind === "forbidden") return {
465
972
  reason: "unreadable",
466
973
  detail: error.userMessage
467
974
  };
@@ -472,7 +979,6 @@ function dashboardResource(transport) {
472
979
  * Fetch the selectable values for a dashboard parameter, through the chain-filter value endpoint.
473
980
  */
474
981
  async function parameterValues(dashboardId, parameterId, options = {}) {
475
- await transport.require("dashboard.parameterValues", options);
476
982
  return transport.requestParsed(ParameterValues, `/api/dashboard/${dashboardId}/params/${encodeURIComponent(parameterId)}/values`, { ...options });
477
983
  }
478
984
  /**
@@ -480,7 +986,6 @@ function dashboardResource(transport) {
480
986
  * substring, capped at the first 1000 matches.
481
987
  */
482
988
  async function searchParameterValues(dashboardId, parameterId, query, options = {}) {
483
- await transport.require("dashboard.searchParameterValues", options);
484
989
  const parameter = encodeURIComponent(parameterId);
485
990
  return transport.requestParsed(ParameterValues, `/api/dashboard/${dashboardId}/params/${parameter}/search/${encodeURIComponent(query)}`, { ...options });
486
991
  }
@@ -532,81 +1037,6 @@ function stripEntityId(dashcard) {
532
1037
  };
533
1038
  }
534
1039
  //#endregion
535
- //#region ../client/src/resources/data-action.ts
536
- function dataActionResource(transport) {
537
- async function readSchema(options) {
538
- const { features } = await transport.server(options);
539
- return dataActionSchema(features);
540
- }
541
- /** List the unarchived data actions the caller can see. `GET /api/action` answers a bare array. */
542
- async function list(options = {}) {
543
- await transport.require("dataAction.list", options);
544
- const schema = await readSchema(options);
545
- return {
546
- data: await transport.requestParsed(z.array(schema), "/api/action", { ...options }),
547
- total: null
548
- };
549
- }
550
- /** Get one unarchived data action by id. */
551
- async function get(id, options = {}) {
552
- await transport.require("dataAction.get", options);
553
- const schema = await readSchema(options);
554
- return transport.requestParsed(schema, `/api/action/${id}`, { ...options });
555
- }
556
- /** Create a data action — a parameterized native query that writes to a database — in a collection. */
557
- async function create(params, options = {}) {
558
- await transport.require("dataAction.create", options);
559
- const schema = await readSchema(options);
560
- return transport.requestParsed(schema, "/api/action", {
561
- ...options,
562
- method: "POST",
563
- body: params
564
- });
565
- }
566
- /** Update a data action by id, patching only the fields the body carries. */
567
- async function update(id, params, options = {}) {
568
- await transport.require("dataAction.update", options);
569
- const schema = await readSchema(options);
570
- return transport.requestParsed(schema, `/api/action/${id}`, {
571
- ...options,
572
- method: "PUT",
573
- body: params
574
- });
575
- }
576
- /** Archive (soft-delete) a data action by id. Metabase models this as an update, not its own endpoint. */
577
- async function archive(id, options = {}) {
578
- await transport.require("dataAction.archive", options);
579
- return update(id, { archived: true }, options);
580
- }
581
- /** Delete a data action by id, removing it and the dashboard buttons that run it. */
582
- async function remove(id, options = {}) {
583
- await transport.require("dataAction.delete", options);
584
- await transport.requestRaw(`/api/action/${id}`, {
585
- ...options,
586
- method: "DELETE",
587
- expectContentType: "binary"
588
- });
589
- }
590
- /** Execute a data action by id with parameter values keyed by parameter id. */
591
- async function execute(id, params, options = {}) {
592
- await transport.require("dataAction.execute", options);
593
- return transport.requestParsed(DataActionExecuteResult, `/api/action/${id}/execute`, {
594
- ...options,
595
- method: "POST",
596
- body: params
597
- });
598
- }
599
- return {
600
- list,
601
- get,
602
- create,
603
- update,
604
- archive,
605
- delete: remove,
606
- execute
607
- };
608
- }
609
- //#endregion
610
1040
  //#region ../client/src/resources/database.ts
611
1041
  const DatabaseApiList = z.object({
612
1042
  data: z.array(Database),
@@ -619,7 +1049,6 @@ const SYNC_COMPLETE = "complete";
619
1049
  function databaseResource(transport) {
620
1050
  /** List databases. `include` hydrates each database's tables; `saved` adds the Saved Questions virtual database. */
621
1051
  async function list(params = {}, options = {}) {
622
- await transport.require("database.list", options);
623
1052
  const response = await transport.requestParsed(DatabaseApiList, "/api/database", {
624
1053
  ...options,
625
1054
  query: {
@@ -634,7 +1063,6 @@ function databaseResource(transport) {
634
1063
  }
635
1064
  /** Get one database by id. `include` hydrates its tables, and `tables.fields` their fields too. */
636
1065
  async function get(id, params = {}, options = {}) {
637
- await transport.require("database.get", options);
638
1066
  return transport.requestParsed(Database, `/api/database/${id}`, {
639
1067
  ...options,
640
1068
  query: { include: params.include }
@@ -642,7 +1070,6 @@ function databaseResource(transport) {
642
1070
  }
643
1071
  /** List the schema names in a database. */
644
1072
  async function schemas(id, options = {}) {
645
- await transport.require("database.schemas", options);
646
1073
  return {
647
1074
  data: await transport.requestParsed(DatabaseApiSchemaList, `/api/database/${id}/schemas`, { ...options }),
648
1075
  total: null
@@ -650,7 +1077,6 @@ function databaseResource(transport) {
650
1077
  }
651
1078
  /** List the tables one schema of a database holds. */
652
1079
  async function schemaTables(id, schema, options = {}) {
653
- await transport.require("database.schemaTables", options);
654
1080
  return {
655
1081
  data: await transport.requestParsed(DatabaseApiSchemaTableList, `/api/database/${id}/schema/${encodeURIComponent(schema)}`, { ...options }),
656
1082
  total: null
@@ -661,7 +1087,6 @@ function databaseResource(transport) {
661
1087
  * pass `wait` to poll the database until it reports its initial sync complete.
662
1088
  */
663
1089
  async function syncSchema(id, params = {}, options = {}) {
664
- await transport.require("database.syncSchema", options);
665
1090
  const ack = await transport.requestParsed(DatabaseTaskAck, `/api/database/${id}/sync_schema`, {
666
1091
  ...options,
667
1092
  method: "POST"
@@ -683,7 +1108,6 @@ function databaseResource(transport) {
683
1108
  }
684
1109
  /** Trigger a rescan of the cached field values across a database. */
685
1110
  async function rescanValues(id, options = {}) {
686
- await transport.require("database.rescanValues", options);
687
1111
  return {
688
1112
  id,
689
1113
  status: (await transport.requestParsed(DatabaseTaskAck, `/api/database/${id}/rescan_values`, {
@@ -709,7 +1133,6 @@ function datasetResource(transport) {
709
1133
  * MBQL, or native — rather than a reference to a saved one, so nothing here is a card.
710
1134
  */
711
1135
  async function query(body, options = {}) {
712
- await transport.require("dataset.query", options);
713
1136
  return transport.requestParsed(CardQueryResult, "/api/dataset", {
714
1137
  ...options,
715
1138
  method: "POST",
@@ -721,9 +1144,7 @@ function datasetResource(transport) {
721
1144
  * defaults server-side to true.
722
1145
  */
723
1146
  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", {
1147
+ return transport.requestParsed(CompiledQuery, "/api/dataset/native", {
727
1148
  ...options,
728
1149
  method: "POST",
729
1150
  body: {
@@ -738,7 +1159,6 @@ function datasetResource(transport) {
738
1159
  * widens the field filter.
739
1160
  */
740
1161
  async function queryMetadata(datasetQuery, options = {}) {
741
- await transport.require("dataset.queryMetadata", options);
742
1162
  return transport.requestParsed(QueryMetadata, "/api/dataset/query_metadata", {
743
1163
  ...options,
744
1164
  method: "POST",
@@ -757,9 +1177,8 @@ function datasetResource(transport) {
757
1177
  * there before the wire.
758
1178
  */
759
1179
  async function exportQuery(format, params, options = {}) {
760
- await transport.require("dataset.exportQuery", options);
761
- await transport.requireFeatures(params.csv_include_bom ? ["exportCsvByteOrderMark"] : [], options);
762
1180
  if (params.pivot_results) await assertPivotedExport(transport, format, options);
1181
+ await transport.requireFeatures(params.csv_include_bom ? ["exportCsvByteOrderMark"] : [], options);
763
1182
  const exported = params.pivot_results ? {
764
1183
  ...params.query,
765
1184
  "was-pivot": true
@@ -787,15 +1206,20 @@ function datasetResource(transport) {
787
1206
  //#region ../client/src/resources/dependency.ts
788
1207
  const DependencyNodeApiList = z.array(DependencyNode);
789
1208
  const DependencyEntityApiList = z.array(DependencyEntity);
1209
+ function graphTypeFeatures(type) {
1210
+ return type === "measure" ? [{
1211
+ feature: "measureDependencyGraph",
1212
+ fields: ["type"]
1213
+ }] : [];
1214
+ }
790
1215
  function dependencyResource(transport) {
1216
+ const { explain, explainWalk } = explainer(transport, "dependency");
791
1217
  /**
792
1218
  * The upstream dependency graph of one entity. `nodes` holds the starting entity plus every
793
1219
  * entity it depends on, directly or transitively; each edge runs from the dependent to what it
794
1220
  * depends on. A measure starts a graph only on a server with `measureDependencyGraph`.
795
1221
  */
796
1222
  async function graph(type, id, options = {}) {
797
- await transport.require("dependency.graph", options);
798
- if (type === "measure") await transport.requireFeatures(["measureDependencyGraph"], options);
799
1223
  return transport.requestParsed(DependencyGraph, "/api/ee/dependencies/graph", {
800
1224
  ...options,
801
1225
  query: {
@@ -811,7 +1235,6 @@ function dependencyResource(transport) {
811
1235
  * personal collections, and `sort-column` / `sort-direction` order the result.
812
1236
  */
813
1237
  async function dependents(type, id, params = {}, options = {}) {
814
- await transport.require("dependency.dependents", options);
815
1238
  return {
816
1239
  data: await transport.requestParsed(DependencyNodeApiList, "/api/ee/dependencies/graph/dependents", {
817
1240
  ...options,
@@ -837,7 +1260,6 @@ function dependencyResource(transport) {
837
1260
  * `sort-direction` order the result.
838
1261
  */
839
1262
  async function broken(type, id, params = {}, options = {}) {
840
- await transport.require("dependency.broken", options);
841
1263
  return {
842
1264
  data: await transport.requestParsed(DependencyEntityApiList, "/api/ee/dependencies/graph/broken", {
843
1265
  ...options,
@@ -864,7 +1286,6 @@ function dependencyResource(transport) {
864
1286
  * This endpoint pages on the server, so the caller consumes pages and decides how far to pull.
865
1287
  */
866
1288
  async function* unreferencedPages(params = {}, options = {}) {
867
- await transport.require("dependency.unreferencedPages", options);
868
1289
  if (queryLeavesNoKind(params)) {
869
1290
  yield {
870
1291
  items: [],
@@ -888,7 +1309,6 @@ function dependencyResource(transport) {
888
1309
  * caller consumes pages and decides how far to pull.
889
1310
  */
890
1311
  async function* breakingPages(params = {}, options = {}) {
891
- await transport.require("dependency.breakingPages", options);
892
1312
  if (queryLeavesNoKind(params)) {
893
1313
  yield {
894
1314
  items: [],
@@ -905,11 +1325,11 @@ function dependencyResource(transport) {
905
1325
  });
906
1326
  }
907
1327
  return {
908
- graph,
909
- dependents,
910
- broken,
911
- unreferencedPages,
912
- breakingPages
1328
+ graph: explain("graph", graph, graphTypeFeatures),
1329
+ dependents: explain("dependents", dependents),
1330
+ broken: explain("broken", broken),
1331
+ unreferencedPages: explainWalk("unreferencedPages", unreferencedPages),
1332
+ breakingPages: explainWalk("breakingPages", breakingPages)
913
1333
  };
914
1334
  }
915
1335
  function queryLeavesNoKind(params) {
@@ -931,9 +1351,9 @@ function itemListQuery(params) {
931
1351
  //#region ../client/src/resources/document.ts
932
1352
  const DocumentApiList = z.object({ items: z.array(Document) }).loose();
933
1353
  function documentResource(transport) {
1354
+ const { explain } = explainer(transport, "document");
934
1355
  /** Get existing documents. */
935
1356
  async function list(options = {}) {
936
- await transport.require("document.list", options);
937
1357
  return {
938
1358
  data: (await transport.requestParsed(DocumentApiList, "/api/document", { ...options })).items,
939
1359
  total: null
@@ -941,12 +1361,10 @@ function documentResource(transport) {
941
1361
  }
942
1362
  /** Return an existing document by id. */
943
1363
  async function get(id, options = {}) {
944
- await transport.require("document.get", options);
945
1364
  return transport.requestParsed(Document, `/api/document/${id}`, { ...options });
946
1365
  }
947
1366
  /** Create a new document. */
948
1367
  async function create(params, options = {}) {
949
- await transport.require("document.create", options);
950
1368
  return transport.requestParsed(Document, "/api/document", {
951
1369
  ...options,
952
1370
  method: "POST",
@@ -955,7 +1373,6 @@ function documentResource(transport) {
955
1373
  }
956
1374
  /** Update an existing document by id. */
957
1375
  async function update(id, params, options = {}) {
958
- await transport.require("document.update", options);
959
1376
  return transport.requestParsed(Document, `/api/document/${id}`, {
960
1377
  ...options,
961
1378
  method: "PUT",
@@ -964,7 +1381,6 @@ function documentResource(transport) {
964
1381
  }
965
1382
  /** Archive (soft-delete) a document by id. Metabase models this as an update, not its own endpoint. */
966
1383
  async function archive(id, options = {}) {
967
- await transport.require("document.archive", options);
968
1384
  return update(id, { archived: true }, options);
969
1385
  }
970
1386
  /**
@@ -972,7 +1388,6 @@ function documentResource(transport) {
972
1388
  * it. An archived source is not found.
973
1389
  */
974
1390
  async function copy(id, params = {}, options = {}) {
975
- await transport.require("document.copy", options);
976
1391
  return transport.requestParsed(Document, `/api/document/${id}/copy`, {
977
1392
  ...options,
978
1393
  method: "POST",
@@ -985,7 +1400,7 @@ function documentResource(transport) {
985
1400
  create,
986
1401
  update,
987
1402
  archive,
988
- copy
1403
+ copy: explain("copy", copy)
989
1404
  };
990
1405
  }
991
1406
  //#endregion
@@ -997,7 +1412,6 @@ function eidTranslationResource(transport) {
997
1412
  * lookup succeeded — the numeric id.
998
1413
  */
999
1414
  async function translate(params, options = {}) {
1000
- await transport.require("eidTranslation.translate", options);
1001
1415
  return transport.requestParsed(EidTranslateResult, "/api/eid-translation/translate", {
1002
1416
  ...options,
1003
1417
  method: "POST",
@@ -1051,6 +1465,7 @@ const Erd = z.object({
1051
1465
  //#region ../client/src/resources/erd.ts
1052
1466
  const ERD_PATH = "/api/ee/erd";
1053
1467
  function erdResource(transport) {
1468
+ const { explain } = explainer(transport, "erd");
1054
1469
  /**
1055
1470
  * Get the entity relationship diagram of a database: the readable tables as nodes, each with its
1056
1471
  * fields, and the foreign keys between them as edges. `table-ids` makes those tables the focal
@@ -1058,7 +1473,6 @@ function erdResource(transport) {
1058
1473
  * none.
1059
1474
  */
1060
1475
  async function get(params, options = {}) {
1061
- await transport.require("erd.get", options);
1062
1476
  return transport.requestParsed(Erd, ERD_PATH, {
1063
1477
  ...options,
1064
1478
  query: {
@@ -1068,7 +1482,7 @@ function erdResource(transport) {
1068
1482
  }
1069
1483
  });
1070
1484
  }
1071
- return { get };
1485
+ return { get: explain("get", get) };
1072
1486
  }
1073
1487
  //#endregion
1074
1488
  //#region ../client/src/resources/optional-parsed.ts
@@ -1086,12 +1500,12 @@ async function fetchOptionalParsed(client, path, schema, options = {}) {
1086
1500
  //#region ../client/src/resources/field.ts
1087
1501
  const FieldApiSummary = z.tuple([z.tuple([z.literal("count"), z.number().int()]), z.tuple([z.literal("distincts"), z.number().int()])]);
1088
1502
  function fieldResource(transport) {
1503
+ const { refuse } = explainer(transport, "field");
1089
1504
  /**
1090
1505
  * Get one field by id. A server that labels data sensitivity answers `data_sensitivity`, `null`
1091
1506
  * for an unlabelled field; any other answers the field without the key (`hasDataSensitivity`).
1092
1507
  */
1093
1508
  async function get(id, options = {}) {
1094
- await transport.require("field.get", options);
1095
1509
  const { features } = await transport.server(options);
1096
1510
  return transport.requestParsed(fieldDetailSchema(features), `/api/field/${id}`, { ...options });
1097
1511
  }
@@ -1102,7 +1516,6 @@ function fieldResource(transport) {
1102
1516
  * the label is refused there before the wire. Answers the field the way `get` does.
1103
1517
  */
1104
1518
  async function update(id, params, options = {}) {
1105
- await transport.require("field.update", options);
1106
1519
  await transport.requireFeatures(params.data_sensitivity === void 0 ? [] : ["fieldDataSensitivity"], options);
1107
1520
  const { features } = await transport.server(options);
1108
1521
  return transport.requestParsed(fieldDetailSchema(features), `/api/field/${id}`, {
@@ -1113,11 +1526,11 @@ function fieldResource(transport) {
1113
1526
  }
1114
1527
  /**
1115
1528
  * 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.
1529
+ * classifier; `null` withdraws it, so whatever label the classifier wrote shows again. A server
1530
+ * without the column drops the key silently, so the label is refused there before the wire.
1531
+ * Answers the field with the label it now carries.
1118
1532
  */
1119
1533
  async function setDataSensitivity(id, params, options = {}) {
1120
- await transport.require("field.setDataSensitivity", options);
1121
1534
  return transport.requestParsed(FieldWithDataSensitivity, `/api/field/${id}`, {
1122
1535
  ...options,
1123
1536
  method: "PUT",
@@ -1133,7 +1546,6 @@ function fieldResource(transport) {
1133
1546
  * `value` against the display value, ignoring both `searchId` and `limit`.
1134
1547
  */
1135
1548
  async function search(id, searchId, params, options = {}) {
1136
- await transport.require("field.search", options);
1137
1549
  return transport.requestParsed(FieldSearchMatches, `/api/field/${id}/search/${searchId}`, {
1138
1550
  ...options,
1139
1551
  query: {
@@ -1148,7 +1560,6 @@ function fieldResource(transport) {
1148
1560
  * `value` and ignores any text after it, refusing only a `value` that has none.
1149
1561
  */
1150
1562
  async function remapping(id, remappedId, params, options = {}) {
1151
- await transport.require("field.remapping", options);
1152
1563
  const { features } = await transport.server(options);
1153
1564
  return fetchOptionalParsed(transport, `/api/field/${id}/remapping/${remappedId}`, fieldRemappedValueSchema(features, id === remappedId), {
1154
1565
  ...options,
@@ -1157,7 +1568,6 @@ function fieldResource(transport) {
1157
1568
  }
1158
1569
  /** Get the row count and the distinct-value count for a field. */
1159
1570
  async function summary(id, options = {}) {
1160
- await transport.require("field.summary", options);
1161
1571
  const [[, count], [, distincts]] = await transport.requestParsed(FieldApiSummary, `/api/field/${id}/summary`, { ...options });
1162
1572
  return {
1163
1573
  field_id: id,
@@ -1167,13 +1577,12 @@ function fieldResource(transport) {
1167
1577
  }
1168
1578
  /** Get the cached distinct values Metabase holds for a field. */
1169
1579
  async function values(id, options = {}) {
1170
- await transport.require("field.values", options);
1171
1580
  return transport.requestParsed(FieldValues, `/api/field/${id}/values`, { ...options });
1172
1581
  }
1173
1582
  return {
1174
1583
  get,
1175
1584
  update,
1176
- setDataSensitivity,
1585
+ setDataSensitivity: refuse("setDataSensitivity", setDataSensitivity),
1177
1586
  search,
1178
1587
  remapping,
1179
1588
  summary,
@@ -1205,22 +1614,22 @@ const SyncStashStarted = z.object({
1205
1614
  task_id: z.number().int().positive()
1206
1615
  });
1207
1616
  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;
1617
+ const RemoteSyncBranchProperty = SessionProperties.extend({ "remote-sync-branch": z.string().nullable().optional() });
1618
+ const RemoteSyncUrlProperty = SessionProperties.extend({ "remote-sync-url": z.string().nullable().optional() });
1619
+ const BRANCH_UNREADABLE_MESSAGE = "the remote-sync-branch setting is not readable: only admins see it, and a server without remote sync has none";
1620
+ const BRANCH_UNSET_MESSAGE = "git-sync tracks no branch: the remote-sync-branch setting is unset";
1211
1621
  function isRemoteUnreadable(error) {
1212
1622
  if (!(error instanceof HttpError)) return false;
1213
- return error.status === FORBIDDEN_STATUS || error.status === UNREGISTERED_STATUS;
1623
+ return error.status === 403 || error.status === 404;
1214
1624
  }
1215
1625
  function gitSyncResource(transport) {
1626
+ const { explain, refuse, refuseAfterReading } = explainer(transport, "gitSync");
1216
1627
  /** Get the running or most recently finished sync task, or null when the server has none. */
1217
1628
  async function currentTask(options = {}) {
1218
- await transport.require("gitSync.currentTask", options);
1219
1629
  return fetchOptionalParsed(transport, "/api/ee/remote-sync/current-task", SyncTask, options);
1220
1630
  }
1221
1631
  /** Request cancellation of the running sync task, and answer it in the state that left it. */
1222
1632
  async function cancelTask(options = {}) {
1223
- await transport.require("gitSync.cancelTask", options);
1224
1633
  return transport.requestParsed(SyncTask, "/api/ee/remote-sync/current-task/cancel", {
1225
1634
  ...options,
1226
1635
  method: "POST"
@@ -1228,12 +1637,10 @@ function gitSyncResource(transport) {
1228
1637
  }
1229
1638
  /** Whether Metabase holds content changes the remote has not been told about. */
1230
1639
  async function isDirty(options = {}) {
1231
- await transport.require("gitSync.isDirty", options);
1232
1640
  return (await transport.requestParsed(SyncDirtyFlag, "/api/ee/remote-sync/is-dirty", { ...options })).is_dirty;
1233
1641
  }
1234
1642
  /** List the objects whose local state differs from the remote. */
1235
1643
  async function dirty(options = {}) {
1236
- await transport.require("gitSync.dirty", options);
1237
1644
  return {
1238
1645
  data: (await transport.requestParsed(SyncDirtyList, "/api/ee/remote-sync/dirty", { ...options })).dirty,
1239
1646
  total: null
@@ -1244,7 +1651,6 @@ function gitSyncResource(transport) {
1244
1651
  * comparison; `force-refresh` re-reads the remote instead.
1245
1652
  */
1246
1653
  async function hasRemoteChanges(params = {}, options = {}) {
1247
- await transport.require("gitSync.hasRemoteChanges", options);
1248
1654
  return transport.requestParsed(SyncRemoteChanges, "/api/ee/remote-sync/has-remote-changes", {
1249
1655
  ...options,
1250
1656
  query: { "force-refresh": params["force-refresh"] }
@@ -1255,14 +1661,16 @@ function gitSyncResource(transport) {
1255
1661
  * pass `wait` to poll that task until it reaches a terminal status. A server already up to date
1256
1662
  * answers no task id, and there is then nothing to poll. A server that guards the tracked branch
1257
1663
  * 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
1664
+ * branch as read just before the request, on every server with remote sync. A server that guards
1665
+ * the branch also requires it, so there the request is refused before the wire when the setting
1666
+ * is unset or hidden from the caller; a server without the guard ignores the assertion, and gets
1667
+ * none when there is none to read. `merge` folds the remote's changes into local content by a
1668
+ * three-way merge and keeps un-pushed local changes, where a plain import refuses on a dirty
1260
1669
  * instance.
1261
1670
  */
1262
1671
  async function importFromRemote(params = {}, options = {}) {
1263
- await transport.require("gitSync.import", options);
1672
+ const expectedBranch = await expectedBranchFor(params.expected_branch, options);
1264
1673
  await requireMerge(params.merge, options);
1265
- const expectedBranch = await guardedBranch(params.expected_branch, options);
1266
1674
  const started = await transport.requestParsed(SyncImportStarted, "/api/ee/remote-sync/import", {
1267
1675
  ...options,
1268
1676
  method: "POST",
@@ -1284,28 +1692,38 @@ function gitSyncResource(transport) {
1284
1692
  final: await settle(params.wait, options)
1285
1693
  };
1286
1694
  }
1287
- async function requireMerge(merge, options) {
1288
- if (merge === true) await transport.requireFeatures(["remoteSyncMerge"], options);
1695
+ async function expectedBranchFor(given, options) {
1696
+ if (given !== void 0) return given;
1697
+ return importAssertion(await readTrackedBranch(options));
1289
1698
  }
1290
- async function guardedBranch(given, options) {
1699
+ async function exportBranchFor(given, options) {
1291
1700
  if (given !== void 0) return given;
1292
- const { features } = await transport.server(options);
1293
- if (!features.remoteSyncBranchGuard) return;
1294
- return trackedBranch(options);
1701
+ return exportBranch(await readTrackedBranch(options));
1702
+ }
1703
+ async function readTrackedBranch(options) {
1704
+ const properties = await transport.probe(RemoteSyncBranchProperty, options);
1705
+ return {
1706
+ tracked: properties["remote-sync-branch"],
1707
+ features: profileFromProperties(properties).features
1708
+ };
1709
+ }
1710
+ async function requireMerge(merge, options) {
1711
+ if (merge === true) await transport.requireFeatures(["remoteSyncMerge"], options);
1295
1712
  }
1296
1713
  /**
1297
1714
  * Export Metabase's content to the remote. The endpoint queues a task and returns at once; pass
1298
1715
  * `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.
1716
+ * branch refuses with 409 when `branch` is not the tracked one; left out, it is the tracked
1717
+ * branch as read just before the request, and the request is refused before the wire when the
1718
+ * setting is unset or hidden from the caller. A server without the guard exports to a named
1719
+ * `branch` and tracks it from then on; left out, it gets none and exports to its own. When the
1720
+ * remote has moved past the last sync, a plain export ends in a `conflict` task where `merge` is
1721
+ * available, and is refused with 400 before any task elsewhere; `merge` folds the remote's
1722
+ * changes in by a three-way merge instead, and `force` overwrites them.
1304
1723
  */
1305
1724
  async function exportToRemote(params = {}, options = {}) {
1306
- await transport.require("gitSync.export", options);
1725
+ const branchName = await exportBranchFor(params.branch, options);
1307
1726
  await requireMerge(params.merge, options);
1308
- const branchName = await guardedBranch(params.branch, options);
1309
1727
  const started = await transport.requestParsed(SyncExportStarted, "/api/ee/remote-sync/export", {
1310
1728
  ...options,
1311
1729
  method: "POST",
@@ -1333,7 +1751,6 @@ function gitSyncResource(transport) {
1333
1751
  * otherwise.
1334
1752
  */
1335
1753
  async function exportPreflight(params, options = {}) {
1336
- await transport.require("gitSync.exportPreflight", options);
1337
1754
  return transport.requestParsed(SyncExportPreflight, "/api/ee/remote-sync/export-preflight", {
1338
1755
  ...options,
1339
1756
  query: { branch: params.branch }
@@ -1344,7 +1761,6 @@ function gitSyncResource(transport) {
1344
1761
  * queues a task and returns at once; pass `wait` to poll that task to a terminal status.
1345
1762
  */
1346
1763
  async function stash(params, options = {}) {
1347
- await transport.require("gitSync.stash", options);
1348
1764
  const started = await transport.requestParsed(SyncStashStarted, "/api/ee/remote-sync/stash", {
1349
1765
  ...options,
1350
1766
  method: "POST",
@@ -1367,7 +1783,6 @@ function gitSyncResource(transport) {
1367
1783
  }
1368
1784
  /** List the branches the configured remote carries. */
1369
1785
  async function branches(options = {}) {
1370
- await transport.require("gitSync.branches", options);
1371
1786
  return {
1372
1787
  data: (await transport.requestParsed(SyncBranchList, "/api/ee/remote-sync/branches", { ...options })).items,
1373
1788
  total: null
@@ -1375,7 +1790,6 @@ function gitSyncResource(transport) {
1375
1790
  }
1376
1791
  /** Create a branch on the remote and point git-sync at it. */
1377
1792
  async function createBranch(params, options = {}) {
1378
- await transport.require("gitSync.createBranch", options);
1379
1793
  return transport.requestParsed(SyncBranchCreated, "/api/ee/remote-sync/create-branch", {
1380
1794
  ...options,
1381
1795
  method: "POST",
@@ -1384,19 +1798,42 @@ function gitSyncResource(transport) {
1384
1798
  }
1385
1799
  /**
1386
1800
  * 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.
1801
+ * by location prefix, and may queue an import task to bring the instance in line with the
1802
+ * remote. A server without `remoteSyncCollectionsOnlyUpdate` validates every settings update as
1803
+ * a remote configuration and fails one naming no remote, after it has applied the flag, so it is
1804
+ * sent the remote's URL and its token as the server shows it, which it checks against the remote
1805
+ * and keeps as they are, or a blank URL when no remote is configured, which clears the remote
1806
+ * settings that are already empty.
1388
1807
  */
1389
1808
  async function setCollectionSynced(collectionId, synced, options = {}) {
1390
- await transport.require("gitSync.setCollectionSynced", options);
1391
1809
  return transport.requestParsed(SyncSettingsUpdateResult, "/api/ee/remote-sync/settings", {
1392
1810
  ...options,
1393
1811
  method: "PUT",
1394
- body: { collections: { [collectionId]: synced } }
1812
+ body: await settingsUpdateBody({ [collectionId]: synced }, options)
1395
1813
  });
1396
1814
  }
1815
+ async function settingsUpdateBody(collections, options) {
1816
+ const { features } = await transport.server(options);
1817
+ if (features.remoteSyncCollectionsOnlyUpdate) return { collections };
1818
+ const url = (await transport.probe(RemoteSyncUrlProperty, options))["remote-sync-url"];
1819
+ if (url === void 0) return { collections };
1820
+ if (url === null) return {
1821
+ collections,
1822
+ "remote-sync-url": ""
1823
+ };
1824
+ const token = await fetchOptionalParsed(transport, "/api/setting/remote-sync-token", RemoteSyncSetting, options);
1825
+ if (token === null) return {
1826
+ collections,
1827
+ "remote-sync-url": url
1828
+ };
1829
+ return {
1830
+ collections,
1831
+ "remote-sync-url": url,
1832
+ "remote-sync-token": token
1833
+ };
1834
+ }
1397
1835
  /** The collections currently in git-sync's scope. */
1398
1836
  async function syncedCollections(options = {}) {
1399
- await transport.require("gitSync.syncedCollections", options);
1400
1837
  return {
1401
1838
  data: (await listCollectionsAs(transport, SyncScopeCollection, options)).filter((entry) => entry.is_remote_synced === true),
1402
1839
  total: null
@@ -1404,7 +1841,6 @@ function gitSyncResource(transport) {
1404
1841
  }
1405
1842
  /** The remote's URL, or null when none is configured or the caller may not read it. */
1406
1843
  async function remoteUrl(options = {}) {
1407
- await transport.require("gitSync.remoteUrl", options);
1408
1844
  try {
1409
1845
  const url = await fetchOptionalParsed(transport, "/api/setting/remote-sync-url", RemoteSyncSetting, options);
1410
1846
  return url === "" ? null : url;
@@ -1414,21 +1850,22 @@ function gitSyncResource(transport) {
1414
1850
  }
1415
1851
  }
1416
1852
  /**
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.
1853
+ * The branch git-sync tracks, or null when none is configured. Refuses when the server has no
1854
+ * remote sync or the caller may not read the setting (it is admin-only), rather than reading
1855
+ * either as unset.
1420
1856
  */
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");
1857
+ function branch(properties) {
1858
+ const tracked = properties["remote-sync-branch"];
1859
+ if (tracked === void 0) throw new ConfigError(BRANCH_UNREADABLE_MESSAGE);
1425
1860
  return tracked;
1426
1861
  }
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");
1862
+ /**
1863
+ * The branch git-sync tracks, refused when none is configured, the server has no remote sync, or
1864
+ * the caller may not read it.
1865
+ */
1866
+ function trackedBranch(properties) {
1867
+ const tracked = branch(properties);
1868
+ if (tracked === null) throw new ConfigError(BRANCH_UNSET_MESSAGE);
1432
1869
  return tracked;
1433
1870
  }
1434
1871
  /**
@@ -1436,7 +1873,6 @@ function gitSyncResource(transport) {
1436
1873
  * answers null, which is terminal in the same sense: nothing further will happen.
1437
1874
  */
1438
1875
  async function waitForTask(wait, options = {}) {
1439
- await transport.require("gitSync.waitForTask", options);
1440
1876
  return settle(wait, options);
1441
1877
  }
1442
1878
  async function settle(wait, options) {
@@ -1446,25 +1882,38 @@ function gitSyncResource(transport) {
1446
1882
  }), (task) => task === null || isSyncTaskTerminal(task.status), wait);
1447
1883
  }
1448
1884
  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,
1885
+ currentTask: explain("currentTask", currentTask),
1886
+ cancelTask: explain("cancelTask", cancelTask),
1887
+ isDirty: explain("isDirty", isDirty),
1888
+ dirty: explain("dirty", dirty),
1889
+ hasRemoteChanges: explain("hasRemoteChanges", hasRemoteChanges),
1890
+ import: explain("import", importFromRemote),
1891
+ export: explain("export", exportToRemote),
1892
+ exportPreflight: explain("exportPreflight", exportPreflight),
1893
+ stash: explain("stash", stash),
1894
+ branches: explain("branches", branches),
1895
+ createBranch: explain("createBranch", createBranch),
1896
+ setCollectionSynced: explain("setCollectionSynced", setCollectionSynced),
1897
+ syncedCollections: refuse("syncedCollections", syncedCollections),
1462
1898
  remoteUrl,
1463
- branch,
1464
- trackedBranch,
1465
- waitForTask
1899
+ branch: refuseAfterReading("branch", RemoteSyncBranchProperty, branch),
1900
+ trackedBranch: refuseAfterReading("trackedBranch", RemoteSyncBranchProperty, trackedBranch),
1901
+ waitForTask: explain("waitForTask", waitForTask)
1466
1902
  };
1467
1903
  }
1904
+ function importAssertion(read) {
1905
+ if (!read.features.remoteSync) return;
1906
+ if (read.features.remoteSyncBranchGuard) return requiredBranch(read.tracked);
1907
+ return typeof read.tracked === "string" ? read.tracked : void 0;
1908
+ }
1909
+ function exportBranch(read) {
1910
+ if (!read.features.remoteSync || !read.features.remoteSyncBranchGuard) return;
1911
+ return requiredBranch(read.tracked);
1912
+ }
1913
+ function requiredBranch(tracked) {
1914
+ if (typeof tracked === "string") return tracked;
1915
+ throw new ConfigError(tracked === null ? BRANCH_UNSET_MESSAGE : BRANCH_UNREADABLE_MESSAGE);
1916
+ }
1468
1917
  //#endregion
1469
1918
  //#region ../client/src/resources/glossary.ts
1470
1919
  const GlossaryApiList = z.object({ data: z.array(Glossary) }).loose();
@@ -1474,7 +1923,6 @@ function glossaryResource(transport) {
1474
1923
  * contains it, case-insensitively.
1475
1924
  */
1476
1925
  async function list(params = {}, options = {}) {
1477
- await transport.require("glossary.list", options);
1478
1926
  return {
1479
1927
  data: (await transport.requestParsed(GlossaryApiList, "/api/glossary", {
1480
1928
  ...options,
@@ -1485,7 +1933,6 @@ function glossaryResource(transport) {
1485
1933
  }
1486
1934
  /** Create a glossary entry, a term and its definition. Terms are unique. */
1487
1935
  async function create(params, options = {}) {
1488
- await transport.require("glossary.create", options);
1489
1936
  return transport.requestParsed(Glossary, "/api/glossary", {
1490
1937
  ...options,
1491
1938
  method: "POST",
@@ -1494,7 +1941,6 @@ function glossaryResource(transport) {
1494
1941
  }
1495
1942
  /** Replace the term and definition of a glossary entry by id. */
1496
1943
  async function update(id, params, options = {}) {
1497
- await transport.require("glossary.update", options);
1498
1944
  return transport.requestParsed(Glossary, `/api/glossary/${id}`, {
1499
1945
  ...options,
1500
1946
  method: "PUT",
@@ -1503,7 +1949,6 @@ function glossaryResource(transport) {
1503
1949
  }
1504
1950
  /** Delete a glossary entry by id. */
1505
1951
  async function remove(id, options = {}) {
1506
- await transport.require("glossary.delete", options);
1507
1952
  await transport.requestRaw(`/api/glossary/${id}`, {
1508
1953
  ...options,
1509
1954
  method: "DELETE",
@@ -1536,9 +1981,9 @@ const UNPUBLISH_TABLES_PATH = "/api/ee/data-studio/table/unpublish-tables";
1536
1981
  const LIBRARY_DATA_TYPE = "library-data";
1537
1982
  const PublishTablesResponse = z.object({ target_collection: Collection.nullable() });
1538
1983
  function libraryResource(transport) {
1984
+ const { explain } = explainer(transport, "library");
1539
1985
  /** Get the Library root and its child collections, or `null` on an instance that has none. */
1540
1986
  async function get(options = {}) {
1541
- await transport.require("library.get", options);
1542
1987
  const { features } = await transport.server(options);
1543
1988
  const wire = await transport.requestParsed(libraryWireSchema(features), LIBRARY_ROOT_PATH, { ...options });
1544
1989
  if (wire === null) return null;
@@ -1557,7 +2002,6 @@ function libraryResource(transport) {
1557
2002
  * back from a refetch, which together make this idempotent.
1558
2003
  */
1559
2004
  async function create(options = {}) {
1560
- await transport.require("library.create", options);
1561
2005
  const existing = await get(options);
1562
2006
  if (existing !== null) return existing;
1563
2007
  await transport.requestRaw(LIBRARY_ROOT_PATH, {
@@ -1570,7 +2014,6 @@ function libraryResource(transport) {
1570
2014
  }
1571
2015
  /** The id of the Library's Data collection, creating the Library first when it does not exist. */
1572
2016
  async function ensureDataCollectionId(options = {}) {
1573
- await transport.require("library.ensureDataCollectionId", options);
1574
2017
  const data = (await create(options)).effective_children.find((child) => child.type === LIBRARY_DATA_TYPE);
1575
2018
  if (data === void 0) throw new Error("Library has no Data collection to publish into");
1576
2019
  if (typeof data.id !== "number") throw new Error(`Library Data collection has a non-numeric id ${String(data.id)}`);
@@ -1583,7 +2026,6 @@ function libraryResource(transport) {
1583
2026
  * `LibraryPublishTablesInput` is refused before any request.
1584
2027
  */
1585
2028
  async function publishTables(params, options = {}) {
1586
- await transport.require("library.publishTables", options);
1587
2029
  const body = parseRequestBody(LibraryPublishTablesInput, params, "tables to publish");
1588
2030
  return (await transport.requestParsed(PublishTablesResponse, PUBLISH_TABLES_PATH, {
1589
2031
  ...options,
@@ -1597,7 +2039,6 @@ function libraryResource(transport) {
1597
2039
  * table, so selectors that do not match `TableSelectors` are refused before any request.
1598
2040
  */
1599
2041
  async function unpublishTables(params, options = {}) {
1600
- await transport.require("library.unpublishTables", options);
1601
2042
  const body = parseRequestBody(TableSelectors, params, "table selectors");
1602
2043
  await transport.requestRaw(UNPUBLISH_TABLES_PATH, {
1603
2044
  ...options,
@@ -1607,20 +2048,20 @@ function libraryResource(transport) {
1607
2048
  });
1608
2049
  }
1609
2050
  return {
1610
- get,
1611
- create,
1612
- ensureDataCollectionId,
1613
- publishTables,
1614
- unpublishTables
2051
+ get: explain("get", get),
2052
+ create: explain("create", create),
2053
+ ensureDataCollectionId: explain("ensureDataCollectionId", ensureDataCollectionId),
2054
+ publishTables: explain("publishTables", publishTables),
2055
+ unpublishTables: explain("unpublishTables", unpublishTables)
1615
2056
  };
1616
2057
  }
1617
2058
  //#endregion
1618
2059
  //#region ../client/src/resources/measure.ts
1619
2060
  const MeasureApiList = z.array(Measure);
1620
2061
  function measureResource(transport) {
2062
+ const { explain } = explainer(transport, "measure");
1621
2063
  /** List measures. */
1622
2064
  async function list(options = {}) {
1623
- await transport.require("measure.list", options);
1624
2065
  return {
1625
2066
  data: await transport.requestParsed(MeasureApiList, "/api/measure", { ...options }),
1626
2067
  total: null
@@ -1628,12 +2069,10 @@ function measureResource(transport) {
1628
2069
  }
1629
2070
  /** Get one measure by id. */
1630
2071
  async function get(id, options = {}) {
1631
- await transport.require("measure.get", options);
1632
2072
  return transport.requestParsed(Measure, `/api/measure/${id}`, { ...options });
1633
2073
  }
1634
2074
  /** Create a measure — a saved aggregation tied to a table — from a full measure body. */
1635
2075
  async function create(params, options = {}) {
1636
- await transport.require("measure.create", options);
1637
2076
  return transport.requestParsed(Measure, "/api/measure", {
1638
2077
  ...options,
1639
2078
  method: "POST",
@@ -1645,7 +2084,6 @@ function measureResource(transport) {
1645
2084
  * required and lands in the audit log.
1646
2085
  */
1647
2086
  async function update(id, params, options = {}) {
1648
- await transport.require("measure.update", options);
1649
2087
  return transport.requestParsed(Measure, `/api/measure/${id}`, {
1650
2088
  ...options,
1651
2089
  method: "PUT",
@@ -1657,18 +2095,17 @@ function measureResource(transport) {
1657
2095
  * so it carries the same required `revision_message`.
1658
2096
  */
1659
2097
  async function archive(id, params, options = {}) {
1660
- await transport.require("measure.archive", options);
1661
2098
  return update(id, {
1662
2099
  archived: true,
1663
2100
  revision_message: params.revision_message
1664
2101
  }, options);
1665
2102
  }
1666
2103
  return {
1667
- list,
1668
- get,
1669
- create,
1670
- update,
1671
- archive
2104
+ list: explain("list", list),
2105
+ get: explain("get", get),
2106
+ create: explain("create", create),
2107
+ update: explain("update", update),
2108
+ archive: explain("archive", archive)
1672
2109
  };
1673
2110
  }
1674
2111
  //#endregion
@@ -1809,12 +2246,12 @@ const MetricDimensionListing = z.object({
1809
2246
  //#endregion
1810
2247
  //#region ../client/src/resources/metric.ts
1811
2248
  function metricResource(transport) {
2249
+ const { explain } = explainer(transport, "metric");
1812
2250
  /**
1813
2251
  * Run a metric definition and return the query result envelope: one metric or measure, or an
1814
2252
  * arithmetic expression over several, each leaf filtered and projected on its own `lib/uuid`.
1815
2253
  */
1816
2254
  async function query(definition, options = {}) {
1817
- await transport.require("metric.query", options);
1818
2255
  return transport.requestParsed(CardQueryResult, "/api/metric/dataset", {
1819
2256
  ...options,
1820
2257
  method: "POST",
@@ -1826,7 +2263,6 @@ function metricResource(transport) {
1826
2263
  * Takes the same definition `query` does.
1827
2264
  */
1828
2265
  async function breakoutValues(definition, options = {}) {
1829
- await transport.require("metric.breakoutValues", options);
1830
2266
  return transport.requestParsed(MetricBreakoutValues, "/api/metric/breakout-values", {
1831
2267
  ...options,
1832
2268
  method: "POST",
@@ -1838,7 +2274,6 @@ function metricResource(transport) {
1838
2274
  * add, grouped by source table. `query` filters both by a name substring.
1839
2275
  */
1840
2276
  async function dimensions(id, params = {}, options = {}) {
1841
- await transport.require("metric.dimensions", options);
1842
2277
  return transport.requestParsed(MetricDimensionListing, `/api/metric/${id}/dimension`, {
1843
2278
  ...options,
1844
2279
  query: {
@@ -1849,14 +2284,15 @@ function metricResource(transport) {
1849
2284
  });
1850
2285
  }
1851
2286
  return {
1852
- query,
1853
- breakoutValues,
1854
- dimensions
2287
+ query: explain("query", query),
2288
+ breakoutValues: explain("breakoutValues", breakoutValues),
2289
+ dimensions: explain("dimensions", dimensions)
1855
2290
  };
1856
2291
  }
1857
2292
  //#endregion
1858
2293
  //#region ../client/src/resources/moderation-review.ts
1859
2294
  function moderationReviewResource(transport) {
2295
+ const { explain } = explainer(transport, "moderationReview");
1860
2296
  /**
1861
2297
  * Create a moderation review on a card or dashboard. `status: "verified"` marks the item
1862
2298
  * verified, `null` or an absent status records a note that leaves it unverified. The new review
@@ -1864,7 +2300,6 @@ function moderationReviewResource(transport) {
1864
2300
  * A body that does not match `ModerationReviewCreateInput` is refused before any request.
1865
2301
  */
1866
2302
  async function create(params, options = {}) {
1867
- await transport.require("moderationReview.create", options);
1868
2303
  const body = parseRequestBody(ModerationReviewCreateInput, params, "moderation review");
1869
2304
  return transport.requestParsed(ModerationReview, "/api/moderation-review", {
1870
2305
  ...options,
@@ -1872,7 +2307,7 @@ function moderationReviewResource(transport) {
1872
2307
  body
1873
2308
  });
1874
2309
  }
1875
- return { create };
2310
+ return { create: explain("create", create) };
1876
2311
  }
1877
2312
  //#endregion
1878
2313
  //#region ../client/src/resources/notification.ts
@@ -1903,7 +2338,6 @@ function notificationResource(transport) {
1903
2338
  * the request pins the card payload type.
1904
2339
  */
1905
2340
  async function list(params = {}, options = {}) {
1906
- await transport.require("notification.list", options);
1907
2341
  return {
1908
2342
  data: await transport.requestParsed(NotificationApiList, "/api/notification", {
1909
2343
  ...options,
@@ -1920,12 +2354,10 @@ function notificationResource(transport) {
1920
2354
  }
1921
2355
  /** Get one question alert by id, refusing an id that names a system-event notification. */
1922
2356
  async function get(id, options = {}) {
1923
- await transport.require("notification.get", options);
1924
2357
  return assertCardAlert(await transport.requestParsed(Notification, `/api/notification/${id}`, { ...options }));
1925
2358
  }
1926
2359
  /** Create a question alert — its card payload, schedules and handlers — from a full body. */
1927
2360
  async function create(params, options = {}) {
1928
- await transport.require("notification.create", options);
1929
2361
  return transport.requestParsed(Notification, "/api/notification", {
1930
2362
  ...options,
1931
2363
  method: "POST",
@@ -1934,7 +2366,6 @@ function notificationResource(transport) {
1934
2366
  }
1935
2367
  /** Update a question alert by id, merging the patch over the stored notification. */
1936
2368
  async function update(id, params, options = {}) {
1937
- await transport.require("notification.update", options);
1938
2369
  const current = await get(id, options);
1939
2370
  return transport.requestParsed(Notification, `/api/notification/${id}`, {
1940
2371
  ...options,
@@ -1944,7 +2375,6 @@ function notificationResource(transport) {
1944
2375
  }
1945
2376
  /** Archive a question alert by id, stopping every delivery. Metabase models this as an update. */
1946
2377
  async function archive(id, options = {}) {
1947
- await transport.require("notification.archive", options);
1948
2378
  return update(id, { active: false }, options);
1949
2379
  }
1950
2380
  /**
@@ -1952,7 +2382,6 @@ function notificationResource(transport) {
1952
2382
  * body.
1953
2383
  */
1954
2384
  async function send(id, options = {}) {
1955
- await transport.require("notification.send", options);
1956
2385
  await get(id, options);
1957
2386
  await transport.requestRaw(`/api/notification/${id}/send`, {
1958
2387
  ...options,
@@ -2012,7 +2441,6 @@ function permissionResource(transport) {
2012
2441
  * one database is the question.
2013
2442
  */
2014
2443
  async function graph(options = {}) {
2015
- await transport.require("permission.graph", options);
2016
2444
  return transport.requestParsed(PermissionsGraph, "/api/permissions/graph", { ...options });
2017
2445
  }
2018
2446
  /**
@@ -2021,12 +2449,10 @@ function permissionResource(transport) {
2021
2449
  * group up under the database and read `view-data` and `create-queries` down to the table id.
2022
2450
  */
2023
2451
  async function databaseGraph(id, options = {}) {
2024
- await transport.require("permission.databaseGraph", options);
2025
2452
  return transport.requestParsed(PermissionsGraph, `/api/permissions/graph/db/${id}`, { ...options });
2026
2453
  }
2027
2454
  /** Get the data permissions of one group on every database. Admin only. */
2028
2455
  async function groupGraph(id, options = {}) {
2029
- await transport.require("permission.groupGraph", options);
2030
2456
  return transport.requestParsed(PermissionsGraph, `/api/permissions/graph/group/${id}`, { ...options });
2031
2457
  }
2032
2458
  return {
@@ -2048,7 +2474,6 @@ function mergePulseUpdate(current, patch) {
2048
2474
  function pulseResource(transport) {
2049
2475
  /** List dashboard subscriptions, optionally narrowed to one dashboard or to the archived ones. */
2050
2476
  async function list(params = {}, options = {}) {
2051
- await transport.require("pulse.list", options);
2052
2477
  return {
2053
2478
  data: await transport.requestParsed(PulseApiList, "/api/pulse", {
2054
2479
  ...options,
@@ -2062,21 +2487,27 @@ function pulseResource(transport) {
2062
2487
  }
2063
2488
  /** Get one dashboard subscription by id. */
2064
2489
  async function get(id, options = {}) {
2065
- await transport.require("pulse.get", options);
2066
2490
  return transport.requestParsed(Pulse, `/api/pulse/${id}`, { ...options });
2067
2491
  }
2068
- /** Create a dashboard subscription — its cards and its delivery channels — from a full body. */
2492
+ /**
2493
+ * Create a dashboard subscription — its cards and its delivery channels — from a full body.
2494
+ * Non-empty `parameters` need the `dashboard_subscription_filters` premium feature, and are
2495
+ * refused before the wire without it.
2496
+ */
2069
2497
  async function create(params, options = {}) {
2070
- await transport.require("pulse.create", options);
2498
+ await requireSubscriptionFilters(params.parameters, options);
2071
2499
  return transport.requestParsed(Pulse, "/api/pulse", {
2072
2500
  ...options,
2073
2501
  method: "POST",
2074
2502
  body: params
2075
2503
  });
2076
2504
  }
2077
- /** Update a dashboard subscription by id, carrying the server-defaulted flags forward. */
2505
+ /**
2506
+ * Update a dashboard subscription by id, carrying the server-defaulted flags forward. Non-empty
2507
+ * `parameters` are refused as on `create`.
2508
+ */
2078
2509
  async function update(id, params, options = {}) {
2079
- await transport.require("pulse.update", options);
2510
+ await requireSubscriptionFilters(params.parameters, options);
2080
2511
  const current = await get(id, options);
2081
2512
  return transport.requestParsed(Pulse, `/api/pulse/${id}`, {
2082
2513
  ...options,
@@ -2089,9 +2520,11 @@ function pulseResource(transport) {
2089
2520
  * update, and disables each of the subscription's channels as a side effect.
2090
2521
  */
2091
2522
  async function archive(id, options = {}) {
2092
- await transport.require("pulse.archive", options);
2093
2523
  return update(id, { archived: true }, options);
2094
2524
  }
2525
+ async function requireSubscriptionFilters(parameters, options) {
2526
+ if (parameters !== void 0 && parameters.length > 0) await transport.requireFeatures(["dashboardSubscriptionFilters"], options);
2527
+ }
2095
2528
  return {
2096
2529
  list,
2097
2530
  get,
@@ -2194,6 +2627,7 @@ const ReplacementRunStarted = z.object({ run_id: z.number().int().positive() });
2194
2627
  const ReplacementApiRunList = z.array(ReplacementRun);
2195
2628
  const CancelRunResponse = z.object({ success: z.literal(true) });
2196
2629
  function replacementResource(transport) {
2630
+ const { explain } = explainer(transport, "replacement");
2197
2631
  /**
2198
2632
  * Check whether every usage of a source entity could be rewritten to read a target entity
2199
2633
  * instead: the two must share a database, the target must not depend on the source, every source
@@ -2201,7 +2635,6 @@ function replacementResource(transport) {
2201
2635
  * implicitly or sandboxed. Admins only.
2202
2636
  */
2203
2637
  async function checkReplaceSource(params, options = {}) {
2204
- await transport.require("replacement.checkReplaceSource", options);
2205
2638
  return transport.requestParsed(ReplacementCheck, "/api/ee/replacement/check-replace-source", {
2206
2639
  ...options,
2207
2640
  method: "POST",
@@ -2214,7 +2647,6 @@ function replacementResource(transport) {
2214
2647
  * while another replacement is running. Admins only.
2215
2648
  */
2216
2649
  async function replaceSource(params, options = {}) {
2217
- await transport.require("replacement.replaceSource", options);
2218
2650
  return transport.requestParsed(ReplacementRunStarted, "/api/ee/replacement/replace-source", {
2219
2651
  ...options,
2220
2652
  method: "POST",
@@ -2229,7 +2661,6 @@ function replacementResource(transport) {
2229
2661
  * replacement is running. Admins only.
2230
2662
  */
2231
2663
  async function replaceModelWithTransform(params, options = {}) {
2232
- await transport.require("replacement.replaceModelWithTransform", options);
2233
2664
  return transport.requestParsed(ReplacementRunStarted, "/api/ee/replacement/replace-model-with-transform", {
2234
2665
  ...options,
2235
2666
  method: "POST",
@@ -2241,7 +2672,6 @@ function replacementResource(transport) {
2241
2672
  * `false` only the ended ones. Admins only.
2242
2673
  */
2243
2674
  async function listRuns(params = {}, options = {}) {
2244
- await transport.require("replacement.listRuns", options);
2245
2675
  return {
2246
2676
  data: await transport.requestParsed(ReplacementApiRunList, "/api/ee/replacement/runs", {
2247
2677
  ...options,
@@ -2252,24 +2682,22 @@ function replacementResource(transport) {
2252
2682
  }
2253
2683
  /** Get one replacement run by id, with its status and progress. Admins only. */
2254
2684
  async function getRun(id, options = {}) {
2255
- await transport.require("replacement.getRun", options);
2256
2685
  return transport.requestParsed(ReplacementRun, `/api/ee/replacement/runs/${id}`, { ...options });
2257
2686
  }
2258
2687
  /** Cancel the replacement in progress. Refuses with a 409 once the run has ended. Admins only. */
2259
2688
  async function cancelRun(id, options = {}) {
2260
- await transport.require("replacement.cancelRun", options);
2261
2689
  await transport.requestParsed(CancelRunResponse, `/api/ee/replacement/runs/${id}/cancel`, {
2262
2690
  ...options,
2263
2691
  method: "POST"
2264
2692
  });
2265
2693
  }
2266
2694
  return {
2267
- checkReplaceSource,
2268
- replaceSource,
2269
- replaceModelWithTransform,
2270
- listRuns,
2271
- getRun,
2272
- cancelRun
2695
+ checkReplaceSource: explain("checkReplaceSource", checkReplaceSource),
2696
+ replaceSource: explain("replaceSource", replaceSource),
2697
+ replaceModelWithTransform: explain("replaceModelWithTransform", replaceModelWithTransform),
2698
+ listRuns: explain("listRuns", listRuns),
2699
+ getRun: explain("getRun", getRun),
2700
+ cancelRun: explain("cancelRun", cancelRun)
2273
2701
  };
2274
2702
  }
2275
2703
  //#endregion
@@ -2348,13 +2776,15 @@ const ENTITY_FEATURES = {
2348
2776
  };
2349
2777
  function entityFeatures(entity) {
2350
2778
  const feature = ENTITY_FEATURES[entity];
2351
- return feature === void 0 ? [] : [feature];
2779
+ return feature === void 0 ? [] : [{
2780
+ feature,
2781
+ fields: ["entity"]
2782
+ }];
2352
2783
  }
2353
2784
  function revisionResource(transport) {
2785
+ const { explain } = explainer(transport, "revision");
2354
2786
  /** List the revisions of an entity the caller may read, newest first, each with its diff. */
2355
2787
  async function list(entity, id, options = {}) {
2356
- await transport.require("revision.list", options);
2357
- await transport.requireFeatures(entityFeatures(entity), options);
2358
2788
  return {
2359
2789
  data: await transport.requestParsed(RevisionApiList, `/api/revision/${entity}/${id}`, { ...options }),
2360
2790
  total: null
@@ -2365,8 +2795,6 @@ function revisionResource(transport) {
2365
2795
  * card the permission to run the query being restored.
2366
2796
  */
2367
2797
  async function revert(params, options = {}) {
2368
- await transport.require("revision.revert", options);
2369
- await transport.requireFeatures(entityFeatures(params.entity), options);
2370
2798
  return transport.requestParsed(RevisionApiRevert, "/api/revision/revert", {
2371
2799
  ...options,
2372
2800
  method: "POST",
@@ -2374,8 +2802,8 @@ function revisionResource(transport) {
2374
2802
  });
2375
2803
  }
2376
2804
  return {
2377
- list,
2378
- revert
2805
+ list: explain("list", list, entityFeatures),
2806
+ revert: explain("revert", revert, (params) => entityFeatures(params.entity))
2379
2807
  };
2380
2808
  }
2381
2809
  //#endregion
@@ -2384,7 +2812,14 @@ const SearchApiResponse = z.object({
2384
2812
  data: z.array(SearchResult),
2385
2813
  total: z.number().int().nonnegative()
2386
2814
  }).loose();
2815
+ function verifiedFeatures(params = {}) {
2816
+ return params.verified === true ? [{
2817
+ feature: "contentVerification",
2818
+ fields: ["verified"]
2819
+ }] : [];
2820
+ }
2387
2821
  function searchResource(transport) {
2822
+ const { explain } = explainer(transport, "search");
2388
2823
  /**
2389
2824
  * Search over the instance's content, ranked against `q`. `models` narrows which kinds of entity
2390
2825
  * may match, `archived` swaps the active set for the archived one, `table_db_id` restricts to
@@ -2394,9 +2829,12 @@ function searchResource(transport) {
2394
2829
  * actions, transforms), `include_metadata` attaches each card's `result_metadata`,
2395
2830
  * `include_dashboard_questions` also matches questions saved into a dashboard (excluded by
2396
2831
  * default), and `limit`/`offset` are the window the server applies before ranking hydration.
2832
+ * `verified` needs the `content_verification` premium feature, and is refused before the wire
2833
+ * without it: a server granting neither it nor `official_collections` refuses the filter, and one
2834
+ * granting `official_collections` alone takes it without applying it.
2397
2835
  */
2398
2836
  async function query(params = {}, options = {}) {
2399
- await transport.require("search.query", options);
2837
+ await transport.requireFeatures(verifiedFeatures(params).map((requirement) => requirement.feature), options);
2400
2838
  const response = await transport.requestParsed(SearchApiResponse, "/api/search", {
2401
2839
  ...options,
2402
2840
  query: {
@@ -2419,7 +2857,7 @@ function searchResource(transport) {
2419
2857
  total: response.total
2420
2858
  };
2421
2859
  }
2422
- return { query };
2860
+ return { query: explain("query", query, verifiedFeatures) };
2423
2861
  }
2424
2862
  //#endregion
2425
2863
  //#region ../client/src/resources/segment.ts
@@ -2427,7 +2865,6 @@ const SegmentApiList = z.array(Segment);
2427
2865
  function segmentResource(transport) {
2428
2866
  /** List every segment the caller can see. */
2429
2867
  async function list(options = {}) {
2430
- await transport.require("segment.list", options);
2431
2868
  return {
2432
2869
  data: await transport.requestParsed(SegmentApiList, "/api/segment", { ...options }),
2433
2870
  total: null
@@ -2435,12 +2872,10 @@ function segmentResource(transport) {
2435
2872
  }
2436
2873
  /** Get one segment by id. */
2437
2874
  async function get(id, options = {}) {
2438
- await transport.require("segment.get", options);
2439
2875
  return transport.requestParsed(Segment, `/api/segment/${id}`, { ...options });
2440
2876
  }
2441
2877
  /** Create a segment — a reusable row filter over one table — from a full segment body. */
2442
2878
  async function create(params, options = {}) {
2443
- await transport.require("segment.create", options);
2444
2879
  return transport.requestParsed(Segment, "/api/segment", {
2445
2880
  ...options,
2446
2881
  method: "POST",
@@ -2452,7 +2887,6 @@ function segmentResource(transport) {
2452
2887
  * required: Metabase records every segment change in its revision history.
2453
2888
  */
2454
2889
  async function update(id, params, options = {}) {
2455
- await transport.require("segment.update", options);
2456
2890
  return transport.requestParsed(Segment, `/api/segment/${id}`, {
2457
2891
  ...options,
2458
2892
  method: "PUT",
@@ -2464,7 +2898,6 @@ function segmentResource(transport) {
2464
2898
  * same required `revision_message`.
2465
2899
  */
2466
2900
  async function archive(id, params, options = {}) {
2467
- await transport.require("segment.archive", options);
2468
2901
  return update(id, {
2469
2902
  archived: true,
2470
2903
  revision_message: params.revision_message
@@ -2489,7 +2922,6 @@ function rethrowSettingError(error, key) {
2489
2922
  function settingResource(transport) {
2490
2923
  /** Get all settings and their values, with descriptions and environment-variable names. */
2491
2924
  async function list(options = {}) {
2492
- await transport.require("setting.list", options);
2493
2925
  return {
2494
2926
  data: await transport.requestParsed(SettingApiList, "/api/setting", { ...options }),
2495
2927
  total: null
@@ -2500,12 +2932,10 @@ function settingResource(transport) {
2500
2932
  * reaches the caller as `null`; a string-typed setting answers bare text rather than JSON.
2501
2933
  */
2502
2934
  async function get(key, options = {}) {
2503
- await transport.require("setting.get", options);
2504
2935
  return fetchOptionalParsed(transport, `/api/setting/${encodeURIComponent(key)}`, z.unknown(), options).catch((error) => rethrowSettingError(error, key));
2505
2936
  }
2506
2937
  /** Set a single setting's value by key. The endpoint answers 204 with no body. */
2507
2938
  async function set(key, value, options = {}) {
2508
- await transport.require("setting.set", options);
2509
2939
  await transport.requestRaw(`/api/setting/${encodeURIComponent(key)}`, {
2510
2940
  ...options,
2511
2941
  method: "PUT",
@@ -2527,7 +2957,6 @@ function setupResource(transport) {
2527
2957
  * applying the instance preferences. The setup token is single-use.
2528
2958
  */
2529
2959
  async function create(params, options = {}) {
2530
- await transport.require("setup.create", options);
2531
2960
  return transport.requestParsed(SetupResult, "/api/setup", {
2532
2961
  ...options,
2533
2962
  method: "POST",
@@ -2542,7 +2971,6 @@ const SnippetApiList = z.array(Snippet);
2542
2971
  function snippetResource(transport) {
2543
2972
  /** List native query snippets. `archived` swaps the listing to archived snippets. */
2544
2973
  async function list(params = {}, options = {}) {
2545
- await transport.require("snippet.list", options);
2546
2974
  return {
2547
2975
  data: await transport.requestParsed(SnippetApiList, "/api/native-query-snippet", {
2548
2976
  ...options,
@@ -2553,12 +2981,10 @@ function snippetResource(transport) {
2553
2981
  }
2554
2982
  /** Get one native query snippet by id. */
2555
2983
  async function get(id, options = {}) {
2556
- await transport.require("snippet.get", options);
2557
2984
  return transport.requestParsed(Snippet, `/api/native-query-snippet/${id}`, { ...options });
2558
2985
  }
2559
2986
  /** Create a native query snippet from a name, its SQL fragment, and an optional collection. */
2560
2987
  async function create(params, options = {}) {
2561
- await transport.require("snippet.create", options);
2562
2988
  return transport.requestParsed(Snippet, "/api/native-query-snippet", {
2563
2989
  ...options,
2564
2990
  method: "POST",
@@ -2567,7 +2993,6 @@ function snippetResource(transport) {
2567
2993
  }
2568
2994
  /** Update a native query snippet by id, patching only the fields the body carries. */
2569
2995
  async function update(id, params, options = {}) {
2570
- await transport.require("snippet.update", options);
2571
2996
  return transport.requestParsed(Snippet, `/api/native-query-snippet/${id}`, {
2572
2997
  ...options,
2573
2998
  method: "PUT",
@@ -2579,7 +3004,6 @@ function snippetResource(transport) {
2579
3004
  * own endpoint.
2580
3005
  */
2581
3006
  async function archive(id, options = {}) {
2582
- await transport.require("snippet.archive", options);
2583
3007
  return update(id, { archived: true }, options);
2584
3008
  }
2585
3009
  return {
@@ -2609,13 +3033,23 @@ const LIST_PARAM_FEATURES = [
2609
3033
  ["published-only", "tableListPublishedFilter"]
2610
3034
  ];
2611
3035
  function listParamFeatures(params) {
2612
- return LIST_PARAM_FEATURES.filter(([param]) => params[param] !== void 0).map(([, feature]) => feature);
3036
+ return LIST_PARAM_FEATURES.filter(([param]) => params[param] === true).map(([, feature]) => feature);
3037
+ }
3038
+ function dataLayerFeatures(value, field) {
3039
+ return isTierName(value) ? [{
3040
+ feature: "tableDataLayerTiers",
3041
+ fields: [field]
3042
+ }] : [];
3043
+ }
3044
+ function isTierName(value) {
3045
+ return value !== null && value !== void 0 && TableDataLayerTier.safeParse(value).success;
2613
3046
  }
2614
3047
  const UPLOAD_UPDATE_PATHS = {
2615
3048
  append: "append-csv",
2616
3049
  replace: "replace-csv"
2617
3050
  };
2618
3051
  function tableResource(transport) {
3052
+ const { explain } = explainer(transport, "table");
2619
3053
  /**
2620
3054
  * List every table the caller can see, across all databases. `term` matches names and display
2621
3055
  * names; `can-query` and `can-write` keep only the tables the caller may query or edit the
@@ -2623,9 +3057,7 @@ function tableResource(transport) {
2623
3057
  * tables nothing depends on; `published-only` keeps tables published to the library.
2624
3058
  */
2625
3059
  async function list(params = {}, options = {}) {
2626
- await transport.require("table.list", options);
2627
3060
  await transport.requireFeatures(listParamFeatures(params), options);
2628
- await requireDataLayer(params["data-layer"], options);
2629
3061
  return {
2630
3062
  data: await transport.requestParsed(TableApiList, "/api/table", {
2631
3063
  ...options,
@@ -2649,31 +3081,30 @@ function tableResource(transport) {
2649
3081
  }
2650
3082
  /** Get one table by id, without its fields. */
2651
3083
  async function get(id, options = {}) {
2652
- await transport.require("table.get", options);
2653
3084
  return transport.requestParsed(Table, `/api/table/${id}`, { ...options });
2654
3085
  }
2655
3086
  /** Update a table by id, patching only the fields the body carries. */
2656
3087
  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}`, {
3088
+ await refuseRewrittenDataLayer(params.data_layer, options);
3089
+ await requireDataAuthorityNull(params.data_authority, options);
3090
+ const path = `/api/table/${id}`;
3091
+ const updated = await transport.requestParsed(Table, path, {
2660
3092
  ...options,
2661
3093
  method: "PUT",
2662
3094
  body: params
2663
3095
  });
3096
+ assertCollectionWritten(path, params.collection_id, updated);
3097
+ return updated;
2664
3098
  }
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(", ")}`);
3099
+ async function refuseRewrittenDataLayer(value, options) {
3100
+ if (value === null || value === void 0 || isTierName(value)) return;
3101
+ const profile = await transport.preflightServer(options);
3102
+ if (profile === null) return;
3103
+ 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`);
3104
+ if (failure !== null) transport.refuseBeforeSending(new CapabilityError(failure));
2673
3105
  }
2674
3106
  /** Get a table by id with its fields hydrated — the metadata the query builder runs on. */
2675
3107
  async function queryMetadata(id, options = {}) {
2676
- await transport.require("table.queryMetadata", options);
2677
3108
  return transport.requestParsed(TableQueryMetadata, `/api/table/${id}/query_metadata`, { ...options });
2678
3109
  }
2679
3110
  /**
@@ -2681,7 +3112,6 @@ function tableResource(transport) {
2681
3112
  * fields answers no content, which is the same empty list.
2682
3113
  */
2683
3114
  async function fks(id, options = {}) {
2684
- await transport.require("table.fks", options);
2685
3115
  return {
2686
3116
  data: await fetchOptionalParsed(transport, `/api/table/${id}/fks`, TableApiFks, { ...options }) ?? [],
2687
3117
  total: null
@@ -2693,7 +3123,6 @@ function tableResource(transport) {
2693
3123
  * connect to is a 422.
2694
3124
  */
2695
3125
  async function syncSchema(id, options = {}) {
2696
- await transport.require("table.syncSchema", options);
2697
3126
  return {
2698
3127
  id,
2699
3128
  status: (await transport.requestParsed(SyncSchemaResponse, `/api/table/${id}/sync_schema`, {
@@ -2707,7 +3136,6 @@ function tableResource(transport) {
2707
3136
  * cached set. The scan runs after the call returns.
2708
3137
  */
2709
3138
  async function rescanValues(id, options = {}) {
2710
- await transport.require("table.rescanValues", options);
2711
3139
  return {
2712
3140
  id,
2713
3141
  status: (await transport.requestParsed(FieldValuesResponse, `/api/table/${id}/rescan_values`, {
@@ -2722,7 +3150,6 @@ function tableResource(transport) {
2722
3150
  * time the field's values are read.
2723
3151
  */
2724
3152
  async function discardValues(id, options = {}) {
2725
- await transport.require("table.discardValues", options);
2726
3153
  return {
2727
3154
  id,
2728
3155
  status: (await transport.requestParsed(FieldValuesResponse, `/api/table/${id}/discard_values`, {
@@ -2737,7 +3164,6 @@ function tableResource(transport) {
2737
3164
  * selectors matched a table.
2738
3165
  */
2739
3166
  async function bulkEdit(params, options = {}) {
2740
- await transport.require("table.bulkEdit", options);
2741
3167
  await requireDataAuthorityNull(params.data_authority, options);
2742
3168
  await transport.requestParsed(BulkEditResponse, BULK_EDIT_PATH, {
2743
3169
  ...options,
@@ -2771,7 +3197,6 @@ function tableResource(transport) {
2771
3197
  * call is a 422; the syncs run after it returns.
2772
3198
  */
2773
3199
  async function bulkSyncSchema(selectors, options = {}) {
2774
- await transport.require("table.bulkSyncSchema", options);
2775
3200
  return postSelection(BULK_SYNC_SCHEMA_PATH, selectors, options);
2776
3201
  }
2777
3202
  /**
@@ -2779,7 +3204,6 @@ function tableResource(transport) {
2779
3204
  * are already cached. The scans run after the call returns.
2780
3205
  */
2781
3206
  async function bulkRescanValues(selectors, options = {}) {
2782
- await transport.require("table.bulkRescanValues", options);
2783
3207
  return postSelection(BULK_RESCAN_VALUES_PATH, selectors, options);
2784
3208
  }
2785
3209
  /**
@@ -2787,7 +3211,6 @@ function tableResource(transport) {
2787
3211
  * included. No scan recreates a discarded set.
2788
3212
  */
2789
3213
  async function bulkDiscardValues(selectors, options = {}) {
2790
- await transport.require("table.bulkDiscardValues", options);
2791
3214
  return postSelection(BULK_DISCARD_VALUES_PATH, selectors, options);
2792
3215
  }
2793
3216
  async function updateFromCsv(id, action, file, options) {
@@ -2809,7 +3232,6 @@ function tableResource(transport) {
2809
3232
  * The file may be at most 50 MB; larger uploads are rejected with a 413 response.
2810
3233
  */
2811
3234
  async function appendCsv(id, file, options = {}) {
2812
- await transport.require("table.appendCsv", options);
2813
3235
  return updateFromCsv(id, "append", file, options);
2814
3236
  }
2815
3237
  /**
@@ -2819,37 +3241,53 @@ function tableResource(transport) {
2819
3241
  * The file may be at most 50 MB; larger uploads are rejected with a 413 response.
2820
3242
  */
2821
3243
  async function replaceCsv(id, file, options = {}) {
2822
- await transport.require("table.replaceCsv", options);
2823
3244
  return updateFromCsv(id, "replace", file, options);
2824
3245
  }
2825
3246
  return {
2826
- list,
3247
+ list: explain("list", list, (params) => dataLayerFeatures(params?.["data-layer"], "data-layer")),
2827
3248
  get,
2828
- update,
3249
+ update: explain("update", update, (_id, params) => dataLayerFeatures(params.data_layer, "data_layer")),
2829
3250
  queryMetadata,
2830
3251
  fks,
2831
3252
  syncSchema,
2832
3253
  rescanValues,
2833
3254
  discardValues,
2834
- bulkEdit,
2835
- bulkSyncSchema,
2836
- bulkRescanValues,
2837
- bulkDiscardValues,
3255
+ bulkEdit: explain("bulkEdit", bulkEdit),
3256
+ bulkSyncSchema: explain("bulkSyncSchema", bulkSyncSchema),
3257
+ bulkRescanValues: explain("bulkRescanValues", bulkRescanValues),
3258
+ bulkDiscardValues: explain("bulkDiscardValues", bulkDiscardValues),
2838
3259
  appendCsv,
2839
3260
  replaceCsv
2840
3261
  };
2841
3262
  }
3263
+ function assertCollectionWritten(path, requested, table) {
3264
+ if (requested === void 0 || table.collection_id === requested) return;
3265
+ const answered = table.collection_id === void 0 ? { kind: "unreported" } : {
3266
+ kind: "reported",
3267
+ value: table.collection_id
3268
+ };
3269
+ 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`, {
3270
+ method: "PUT",
3271
+ path,
3272
+ field: "collection_id",
3273
+ requested,
3274
+ answered
3275
+ });
3276
+ }
3277
+ function describeCollectionAnswer(answered) {
3278
+ if (answered.kind === "unreported") return "it answered the table without reporting a collection";
3279
+ if (answered.value === null) return "the table stays in no collection";
3280
+ return `the table stays in collection ${answered.value}`;
3281
+ }
2842
3282
  //#endregion
2843
3283
  //#region ../client/src/resources/timeline-event.ts
2844
3284
  function timelineEventResource(transport) {
2845
3285
  /** Get one timeline event by id. */
2846
3286
  async function get(id, options = {}) {
2847
- await transport.require("timelineEvent.get", options);
2848
3287
  return transport.requestParsed(TimelineEvent, `/api/timeline-event/${id}`, { ...options });
2849
3288
  }
2850
3289
  /** Create a timeline event on an existing timeline. */
2851
3290
  async function create(params, options = {}) {
2852
- await transport.require("timelineEvent.create", options);
2853
3291
  return transport.requestParsed(TimelineEvent, "/api/timeline-event", {
2854
3292
  ...options,
2855
3293
  method: "POST",
@@ -2861,7 +3299,6 @@ function timelineEventResource(transport) {
2861
3299
  * the event to another timeline.
2862
3300
  */
2863
3301
  async function update(id, params, options = {}) {
2864
- await transport.require("timelineEvent.update", options);
2865
3302
  return transport.requestParsed(TimelineEvent, `/api/timeline-event/${id}`, {
2866
3303
  ...options,
2867
3304
  method: "PUT",
@@ -2873,12 +3310,10 @@ function timelineEventResource(transport) {
2873
3310
  * endpoint.
2874
3311
  */
2875
3312
  async function archive(id, options = {}) {
2876
- await transport.require("timelineEvent.archive", options);
2877
3313
  return update(id, { archived: true }, options);
2878
3314
  }
2879
3315
  /** Permanently delete a timeline event by id. The server answers with no body. */
2880
3316
  async function remove(id, options = {}) {
2881
- await transport.require("timelineEvent.delete", options);
2882
3317
  await transport.requestRaw(`/api/timeline-event/${id}`, {
2883
3318
  ...options,
2884
3319
  method: "DELETE",
@@ -2900,7 +3335,6 @@ const TimelineWithEvents = Timeline.required({ events: true });
2900
3335
  function timelineResource(transport) {
2901
3336
  /** List timelines. `archived` swaps the active listing for the archived one. */
2902
3337
  async function list(params = {}, options = {}) {
2903
- await transport.require("timeline.list", options);
2904
3338
  return {
2905
3339
  data: await transport.requestParsed(TimelineApiList, "/api/timeline", {
2906
3340
  ...options,
@@ -2911,7 +3345,6 @@ function timelineResource(transport) {
2911
3345
  }
2912
3346
  /** Get one timeline by id, without its events. */
2913
3347
  async function get(id, options = {}) {
2914
- await transport.require("timeline.get", options);
2915
3348
  return transport.requestParsed(Timeline, `/api/timeline/${id}`, { ...options });
2916
3349
  }
2917
3350
  /**
@@ -2919,7 +3352,6 @@ function timelineResource(transport) {
2919
3352
  * timeline read hydrates them on request, and `archived` chooses which of them come back.
2920
3353
  */
2921
3354
  async function events(id, params = {}, options = {}) {
2922
- await transport.require("timeline.events", options);
2923
3355
  return {
2924
3356
  data: (await transport.requestParsed(TimelineWithEvents, `/api/timeline/${id}`, {
2925
3357
  ...options,
@@ -2933,7 +3365,6 @@ function timelineResource(transport) {
2933
3365
  }
2934
3366
  /** Create a timeline from a full timeline body. */
2935
3367
  async function create(params, options = {}) {
2936
- await transport.require("timeline.create", options);
2937
3368
  return transport.requestParsed(Timeline, "/api/timeline", {
2938
3369
  ...options,
2939
3370
  method: "POST",
@@ -2945,7 +3376,6 @@ function timelineResource(transport) {
2945
3376
  * cascades to every event on the timeline.
2946
3377
  */
2947
3378
  async function update(id, params, options = {}) {
2948
- await transport.require("timeline.update", options);
2949
3379
  return transport.requestParsed(Timeline, `/api/timeline/${id}`, {
2950
3380
  ...options,
2951
3381
  method: "PUT",
@@ -2957,12 +3387,10 @@ function timelineResource(transport) {
2957
3387
  * not its own endpoint.
2958
3388
  */
2959
3389
  async function archive(id, options = {}) {
2960
- await transport.require("timeline.archive", options);
2961
3390
  return update(id, { archived: true }, options);
2962
3391
  }
2963
3392
  /** Permanently delete a timeline and all its events by id. The server answers with no body. */
2964
3393
  async function remove(id, options = {}) {
2965
- await transport.require("timeline.delete", options);
2966
3394
  await transport.requestRaw(`/api/timeline/${id}`, {
2967
3395
  ...options,
2968
3396
  method: "DELETE",
@@ -2999,12 +3427,12 @@ TransformMemberRun.pick({
2999
3427
  //#region ../client/src/resources/transform-dag-run.ts
3000
3428
  const TransformMemberRunList = z.array(TransformMemberRun);
3001
3429
  function transformDagRunResource(transport) {
3430
+ const { explain } = explainer(transport, "transformDagRun");
3002
3431
  /**
3003
3432
  * List the transform runs a DAG run coordinated so far. A member run is inserted when its
3004
3433
  * transform starts, so the list grows while the DAG run is in progress.
3005
3434
  */
3006
3435
  async function transformRuns(runId, options = {}) {
3007
- await transport.require("transformDagRun.transformRuns", options);
3008
3436
  return {
3009
3437
  data: await transport.requestParsed(TransformMemberRunList, `/api/transform-dag-run/${runId}/transform-runs`, { ...options }),
3010
3438
  total: null
@@ -3012,7 +3440,6 @@ function transformDagRunResource(transport) {
3012
3440
  }
3013
3441
  /** Cancel an in-progress DAG run and request cancellation of its still-running transforms. */
3014
3442
  async function cancel(runId, options = {}) {
3015
- await transport.require("transformDagRun.cancel", options);
3016
3443
  await transport.requestRaw(`/api/transform-dag-run/${runId}/cancel`, {
3017
3444
  ...options,
3018
3445
  method: "POST",
@@ -3020,8 +3447,8 @@ function transformDagRunResource(transport) {
3020
3447
  });
3021
3448
  }
3022
3449
  return {
3023
- transformRuns,
3024
- cancel
3450
+ transformRuns: explain("transformRuns", transformRuns),
3451
+ cancel: explain("cancel", cancel)
3025
3452
  };
3026
3453
  }
3027
3454
  //#endregion
@@ -3169,12 +3596,12 @@ function lensPath(id, lensId) {
3169
3596
  return `/api/ee/transforms/${id}/inspect/${encodeURIComponent(lensId)}`;
3170
3597
  }
3171
3598
  function transformInspectorResource(transport) {
3599
+ const { explain } = explainer(transport, "transformInspector");
3172
3600
  /**
3173
3601
  * Discover what the inspector can show for a transform: its source and target tables with field
3174
3602
  * statistics, the fields its query touches, and the lenses available on it.
3175
3603
  */
3176
3604
  async function discover(id, options = {}) {
3177
- await transport.require("transformInspector.discover", options);
3178
3605
  return transport.requestParsed(TransformInspection, `/api/ee/transforms/${id}/inspect`, { ...options });
3179
3606
  }
3180
3607
  /**
@@ -3183,7 +3610,6 @@ function transformInspectorResource(transport) {
3183
3610
  * the `params` its trigger emitted.
3184
3611
  */
3185
3612
  async function lens(id, lensId, params = {}, options = {}) {
3186
- await transport.require("transformInspector.lens", options);
3187
3613
  return transport.requestParsed(TransformLens, lensPath(id, lensId), {
3188
3614
  ...options,
3189
3615
  query: { join_step: params.join_step }
@@ -3195,7 +3621,6 @@ function transformInspectorResource(transport) {
3195
3621
  * opened with.
3196
3622
  */
3197
3623
  async function query(id, lensId, params, options = {}) {
3198
- await transport.require("transformInspector.query", options);
3199
3624
  return transport.requestParsed(CardQueryResult, `${lensPath(id, lensId)}/query`, {
3200
3625
  ...options,
3201
3626
  method: "POST",
@@ -3203,17 +3628,17 @@ function transformInspectorResource(transport) {
3203
3628
  });
3204
3629
  }
3205
3630
  return {
3206
- discover,
3207
- lens,
3208
- query
3631
+ discover: explain("discover", discover),
3632
+ lens: explain("lens", lens),
3633
+ query: explain("query", query)
3209
3634
  };
3210
3635
  }
3211
3636
  //#endregion
3212
3637
  //#region ../client/src/resources/transform-job.ts
3213
3638
  function transformJobResource(transport) {
3639
+ const { explain } = explainer(transport, "transformJob");
3214
3640
  /** List every transform job the caller can see. */
3215
3641
  async function list(options = {}) {
3216
- await transport.require("transformJob.list", options);
3217
3642
  const { features } = await transport.server(options);
3218
3643
  return {
3219
3644
  data: await transport.requestParsed(z.array(transformJobSchema(features)), "/api/transform-job", { ...options }),
@@ -3222,13 +3647,11 @@ function transformJobResource(transport) {
3222
3647
  }
3223
3648
  /** Get one transform job by id. */
3224
3649
  async function get(id, options = {}) {
3225
- await transport.require("transformJob.get", options);
3226
3650
  const { features } = await transport.server(options);
3227
3651
  return transport.requestParsed(transformJobSchema(features), `/api/transform-job/${id}`, { ...options });
3228
3652
  }
3229
3653
  /** Create a transform job — a schedule plus the tags it runs — from a full body. */
3230
3654
  async function create(params, options = {}) {
3231
- await transport.require("transformJob.create", options);
3232
3655
  const { features } = await transport.server(options);
3233
3656
  return transport.requestParsed(transformJobSchema(features), "/api/transform-job", {
3234
3657
  ...options,
@@ -3238,7 +3661,6 @@ function transformJobResource(transport) {
3238
3661
  }
3239
3662
  /** Update a transform job by id, patching only the fields the body carries. */
3240
3663
  async function update(id, params, options = {}) {
3241
- await transport.require("transformJob.update", options);
3242
3664
  const { features } = await transport.server(options);
3243
3665
  return transport.requestParsed(transformJobSchema(features), `/api/transform-job/${id}`, {
3244
3666
  ...options,
@@ -3248,7 +3670,6 @@ function transformJobResource(transport) {
3248
3670
  }
3249
3671
  /** Delete a transform job by id, leaving the transforms it ran untouched. */
3250
3672
  async function remove(id, options = {}) {
3251
- await transport.require("transformJob.delete", options);
3252
3673
  await transport.requestRaw(`/api/transform-job/${id}`, {
3253
3674
  ...options,
3254
3675
  method: "DELETE",
@@ -3260,7 +3681,6 @@ function transformJobResource(transport) {
3260
3681
  * plus those transforms' dependencies; `run_all` re-runs dependencies that are already fresh.
3261
3682
  */
3262
3683
  async function run(id, params = {}, options = {}) {
3263
- await transport.require("transformJob.run", options);
3264
3684
  const { features } = await transport.server(options);
3265
3685
  return transport.requestParsed(transformJobRunResultSchema(features), `/api/transform-job/${id}/run`, {
3266
3686
  ...options,
@@ -3270,7 +3690,6 @@ function transformJobResource(transport) {
3270
3690
  }
3271
3691
  /** List the transforms a job would execute, resolved from the job's tags. */
3272
3692
  async function transforms(id, options = {}) {
3273
- await transport.require("transformJob.transforms", options);
3274
3693
  const { features } = await transport.server(options);
3275
3694
  return {
3276
3695
  data: await transport.requestParsed(z.array(transformRowSchema(features)), `/api/transform-job/${id}/transforms`, { ...options }),
@@ -3282,7 +3701,6 @@ function transformJobResource(transport) {
3282
3701
  * manual runs ignore the flag. The path carries no id and is a different endpoint from `update`.
3283
3702
  */
3284
3703
  async function setActive(active, options = {}) {
3285
- await transport.require("transformJob.setActive", options);
3286
3704
  return transport.requestParsed(TransformJobActiveResult, "/api/transform-job/active", {
3287
3705
  ...options,
3288
3706
  method: "PUT",
@@ -3290,14 +3708,14 @@ function transformJobResource(transport) {
3290
3708
  });
3291
3709
  }
3292
3710
  return {
3293
- list,
3294
- get,
3295
- create,
3296
- update,
3297
- delete: remove,
3298
- run,
3299
- transforms,
3300
- setActive
3711
+ list: explain("list", list),
3712
+ get: explain("get", get),
3713
+ create: explain("create", create),
3714
+ update: explain("update", update),
3715
+ delete: explain("delete", remove),
3716
+ run: explain("run", run),
3717
+ transforms: explain("transforms", transforms),
3718
+ setActive: explain("setActive", setActive)
3301
3719
  };
3302
3720
  }
3303
3721
  //#endregion
@@ -3357,14 +3775,13 @@ function libraryPath(path) {
3357
3775
  return `/api/ee/transforms-python/library/${encodeURIComponent(path)}`;
3358
3776
  }
3359
3777
  function transformPythonResource(transport) {
3778
+ const { explain } = explainer(transport, "transformPython");
3360
3779
  /** Get the Python library at a path, the user module every Python transform can import. */
3361
3780
  async function getLibrary(path, options = {}) {
3362
- await transport.require("transformPython.getLibrary", options);
3363
3781
  return transport.requestParsed(PythonLibrary, libraryPath(path), { ...options });
3364
3782
  }
3365
3783
  /** Replace the source of the Python library at a path, creating the library when none exists. */
3366
3784
  async function updateLibrary(path, params, options = {}) {
3367
- await transport.require("transformPython.updateLibrary", options);
3368
3785
  return transport.requestParsed(PythonLibrary, libraryPath(path), {
3369
3786
  ...options,
3370
3787
  method: "PUT",
@@ -3377,7 +3794,6 @@ function transformPythonResource(transport) {
3377
3794
  * sizes are capped at 100 rows.
3378
3795
  */
3379
3796
  async function testRun(params, options = {}) {
3380
- await transport.require("transformPython.testRun", options);
3381
3797
  return transport.requestParsed(PythonApiTestRun, "/api/ee/transforms-python/test-run", {
3382
3798
  ...options,
3383
3799
  method: "POST",
@@ -3385,9 +3801,9 @@ function transformPythonResource(transport) {
3385
3801
  });
3386
3802
  }
3387
3803
  return {
3388
- getLibrary,
3389
- updateLibrary,
3390
- testRun
3804
+ getLibrary: explain("getLibrary", getLibrary),
3805
+ updateLibrary: explain("updateLibrary", updateLibrary),
3806
+ testRun: explain("testRun", testRun)
3391
3807
  };
3392
3808
  }
3393
3809
  //#endregion
@@ -3401,6 +3817,7 @@ const TransformRunKickoff = z.object({
3401
3817
  });
3402
3818
  const TransformDagTransformList = z.array(TransformDagTransform);
3403
3819
  function transformResource(transport) {
3820
+ const { explain, explainWalk } = explainer(transport, "transform");
3404
3821
  async function requestTransformList(path, opts) {
3405
3822
  const { features } = await transport.server(opts);
3406
3823
  return {
@@ -3414,18 +3831,15 @@ function transformResource(transport) {
3414
3831
  }
3415
3832
  /** List every transform the caller can see. */
3416
3833
  async function list(options = {}) {
3417
- await transport.require("transform.list", options);
3418
3834
  return requestTransformList("/api/transform", { ...options });
3419
3835
  }
3420
3836
  /** Get one transform by id. */
3421
3837
  async function get(id, options = {}) {
3422
- await transport.require("transform.get", options);
3423
3838
  const { features } = await transport.server(options);
3424
3839
  return transport.requestParsed(transformDetailSchema(features), `/api/transform/${id}`, { ...options });
3425
3840
  }
3426
3841
  /** Create a transform — a query plus the warehouse table it writes — from a full body. */
3427
3842
  async function create(params, options = {}) {
3428
- await transport.require("transform.create", options);
3429
3843
  return requestTransform("/api/transform", {
3430
3844
  ...options,
3431
3845
  method: "POST",
@@ -3434,7 +3848,6 @@ function transformResource(transport) {
3434
3848
  }
3435
3849
  /** Update a transform by id, patching only the fields the body carries. */
3436
3850
  async function update(id, params, options = {}) {
3437
- await transport.require("transform.update", options);
3438
3851
  return requestTransform(`/api/transform/${id}`, {
3439
3852
  ...options,
3440
3853
  method: "PUT",
@@ -3443,7 +3856,6 @@ function transformResource(transport) {
3443
3856
  }
3444
3857
  /** Delete a transform by id, leaving any table it already materialized in place. */
3445
3858
  async function remove(id, options = {}) {
3446
- await transport.require("transform.delete", options);
3447
3859
  await transport.requestRaw(`/api/transform/${id}`, {
3448
3860
  ...options,
3449
3861
  method: "DELETE",
@@ -3452,7 +3864,6 @@ function transformResource(transport) {
3452
3864
  }
3453
3865
  /** Drop a transform's materialized output table, keeping the transform definition. */
3454
3866
  async function deleteTable(id, options = {}) {
3455
- await transport.require("transform.deleteTable", options);
3456
3867
  await transport.requestRaw(`/api/transform/${id}/table`, {
3457
3868
  ...options,
3458
3869
  method: "DELETE",
@@ -3461,12 +3872,10 @@ function transformResource(transport) {
3461
3872
  }
3462
3873
  /** List the upstream transforms a transform depends on. */
3463
3874
  async function dependencies(id, options = {}) {
3464
- await transport.require("transform.dependencies", options);
3465
3875
  return requestTransformList(`/api/transform/${id}/dependencies`, { ...options });
3466
3876
  }
3467
3877
  /** Request cancellation of a transform's current run. */
3468
3878
  async function cancel(id, options = {}) {
3469
- await transport.require("transform.cancel", options);
3470
3879
  await transport.requestRaw(`/api/transform/${id}/cancel`, {
3471
3880
  ...options,
3472
3881
  method: "POST",
@@ -3475,12 +3884,10 @@ function transformResource(transport) {
3475
3884
  }
3476
3885
  /** Get one transform run by run id — not by the id of the transform that produced it. */
3477
3886
  async function getRun(runId, options = {}) {
3478
- await transport.require("transform.getRun", options);
3479
3887
  return transport.requestParsed(TransformRun, `/api/transform/run/${runId}`, { ...options });
3480
3888
  }
3481
3889
  /** Walk the run history, newest first, optionally narrowed to a single transform. */
3482
3890
  async function* runPages(params = {}, options = {}) {
3483
- await transport.require("transform.runPages", options);
3484
3891
  yield* paginatePages(transport, "/api/transform/run", TransformRun, {
3485
3892
  query: { "transform-ids": params["transform-ids"] },
3486
3893
  ...options.offset !== void 0 && { offset: options.offset },
@@ -3494,7 +3901,6 @@ function transformResource(transport) {
3494
3901
  * DAG reprocess run, or a standalone transform run — and never a member run of a job or DAG.
3495
3902
  */
3496
3903
  async function* runSummaryPages(params = {}, options = {}) {
3497
- await transport.require("transform.runSummaryPages", options);
3498
3904
  yield* paginatePages(transport, "/api/transform/runs", TransformRunSummary, {
3499
3905
  query: {
3500
3906
  types: params.types,
@@ -3517,7 +3923,6 @@ function transformResource(transport) {
3517
3923
  * source rather than the rows past the last checkpoint.
3518
3924
  */
3519
3925
  async function resetCheckpoint(id, options = {}) {
3520
- await transport.require("transform.resetCheckpoint", options);
3521
3926
  await transport.requestRaw(`/api/transform/${id}/reset-checkpoint`, {
3522
3927
  ...options,
3523
3928
  method: "POST",
@@ -3532,7 +3937,6 @@ function transformResource(transport) {
3532
3937
  * `runSummaryPages` with `types: ["dag"]`, and the member runs off `transformDagRun.transformRuns`.
3533
3938
  */
3534
3939
  async function runDag(id, params, options = {}) {
3535
- await transport.require("transform.runDag", options);
3536
3940
  return transport.requestParsed(TransformDagRunResult, `/api/transform/${id}/run-dag`, {
3537
3941
  ...options,
3538
3942
  method: "POST",
@@ -3541,7 +3945,6 @@ function transformResource(transport) {
3541
3945
  }
3542
3946
  /** Preview the transforms a DAG reprocess seeded at a transform would run, in execution order. */
3543
3947
  async function dagTransforms(id, params, options = {}) {
3544
- await transport.require("transform.dagTransforms", options);
3545
3948
  return {
3546
3949
  data: await transport.requestParsed(TransformDagTransformList, `/api/transform/${id}/dag-transforms`, {
3547
3950
  ...options,
@@ -3556,7 +3959,6 @@ function transformResource(transport) {
3556
3959
  * A server that answers no run id started nothing, and there is then nothing to poll.
3557
3960
  */
3558
3961
  async function run(id, params = {}, options = {}) {
3559
- await transport.require("transform.run", options);
3560
3962
  const kickoff = await transport.requestParsed(TransformRunKickoff, `/api/transform/${id}/run`, {
3561
3963
  ...options,
3562
3964
  method: "POST"
@@ -3597,30 +3999,30 @@ function transformResource(transport) {
3597
3999
  }
3598
4000
  }
3599
4001
  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
4002
+ list: explain("list", list),
4003
+ get: explain("get", get),
4004
+ create: explain("create", create),
4005
+ update: explain("update", update),
4006
+ delete: explain("delete", remove),
4007
+ deleteTable: explain("deleteTable", deleteTable),
4008
+ dependencies: explain("dependencies", dependencies),
4009
+ cancel: explain("cancel", cancel),
4010
+ getRun: explain("getRun", getRun),
4011
+ runPages: explainWalk("runPages", runPages),
4012
+ runSummaryPages: explainWalk("runSummaryPages", runSummaryPages),
4013
+ resetCheckpoint: explain("resetCheckpoint", resetCheckpoint),
4014
+ runDag: explain("runDag", runDag),
4015
+ dagTransforms: explain("dagTransforms", dagTransforms),
4016
+ run: explain("run", run)
3615
4017
  };
3616
4018
  }
3617
4019
  //#endregion
3618
4020
  //#region ../client/src/resources/transform-tag.ts
3619
4021
  const TransformTagApiList = z.array(TransformTag);
3620
4022
  function transformTagResource(transport) {
4023
+ const { explain } = explainer(transport, "transformTag");
3621
4024
  /** List every transform tag the caller can see, built-in tags included. */
3622
4025
  async function list(options = {}) {
3623
- await transport.require("transformTag.list", options);
3624
4026
  return {
3625
4027
  data: await transport.requestParsed(TransformTagApiList, "/api/transform-tag", { ...options }),
3626
4028
  total: null
@@ -3628,7 +4030,6 @@ function transformTagResource(transport) {
3628
4030
  }
3629
4031
  /** Create a transform tag, the label a transform job matches transforms on. */
3630
4032
  async function create(params, options = {}) {
3631
- await transport.require("transformTag.create", options);
3632
4033
  return transport.requestParsed(TransformTag, "/api/transform-tag", {
3633
4034
  ...options,
3634
4035
  method: "POST",
@@ -3637,7 +4038,6 @@ function transformTagResource(transport) {
3637
4038
  }
3638
4039
  /** Rename a transform tag by id. */
3639
4040
  async function update(id, params, options = {}) {
3640
- await transport.require("transformTag.update", options);
3641
4041
  return transport.requestParsed(TransformTag, `/api/transform-tag/${id}`, {
3642
4042
  ...options,
3643
4043
  method: "PUT",
@@ -3646,7 +4046,6 @@ function transformTagResource(transport) {
3646
4046
  }
3647
4047
  /** Delete a transform tag by id, detaching it from every transform and job carrying it. */
3648
4048
  async function remove(id, options = {}) {
3649
- await transport.require("transformTag.delete", options);
3650
4049
  await transport.requestRaw(`/api/transform-tag/${id}`, {
3651
4050
  ...options,
3652
4051
  method: "DELETE",
@@ -3654,19 +4053,180 @@ function transformTagResource(transport) {
3654
4053
  });
3655
4054
  }
3656
4055
  return {
3657
- list,
3658
- create,
3659
- update,
3660
- delete: remove
4056
+ list: explain("list", list),
4057
+ create: explain("create", create),
4058
+ update: explain("update", update),
4059
+ delete: explain("delete", remove)
3661
4060
  };
3662
4061
  }
3663
4062
  //#endregion
4063
+ //#region ../client/src/domain/transform-test.ts
4064
+ const TransformTestTable = z.object({
4065
+ schema: z.string().min(1).nullable().optional(),
4066
+ name: z.string().min(1)
4067
+ }).loose();
4068
+ const TransformTestColumn = z.object({
4069
+ name: z.string().min(1),
4070
+ cast_type: z.string().min(1)
4071
+ }).loose();
4072
+ const TransformTestCell = z.union([
4073
+ z.boolean(),
4074
+ z.number(),
4075
+ z.string(),
4076
+ z.null()
4077
+ ]);
4078
+ const TransformTestRow = z.record(z.string(), TransformTestCell);
4079
+ const TransformTestSqlInput = z.object({
4080
+ table: TransformTestTable,
4081
+ format: z.literal("sql"),
4082
+ sql: z.string().min(1)
4083
+ }).loose();
4084
+ const TransformTestRowsInput = z.object({
4085
+ table: TransformTestTable,
4086
+ format: z.literal("rows"),
4087
+ columns: z.array(TransformTestColumn).min(1),
4088
+ rows: z.array(TransformTestRow)
4089
+ }).loose();
4090
+ const TransformTestInput = z.discriminatedUnion("format", [TransformTestSqlInput, TransformTestRowsInput]);
4091
+ const TransformTestEqualsSqlExpectation = z.object({
4092
+ type: z.literal("equals"),
4093
+ name: z.string().min(1),
4094
+ format: z.literal("sql"),
4095
+ sql: z.string().min(1)
4096
+ }).loose();
4097
+ const TransformTestEqualsRowsExpectation = z.object({
4098
+ type: z.literal("equals"),
4099
+ name: z.string().min(1),
4100
+ format: z.literal("rows"),
4101
+ columns: z.array(TransformTestColumn).min(1),
4102
+ rows: z.array(TransformTestRow)
4103
+ }).loose();
4104
+ const TransformTestEqualsExpectation = z.discriminatedUnion("format", [TransformTestEqualsSqlExpectation, TransformTestEqualsRowsExpectation]);
4105
+ const TransformTestEmptyExpectation = z.object({
4106
+ type: z.literal("empty"),
4107
+ name: z.string().min(1),
4108
+ sql: z.string().min(1)
4109
+ }).loose();
4110
+ const TransformTestExpectation = z.discriminatedUnion("type", [TransformTestEqualsExpectation, TransformTestEmptyExpectation]);
4111
+ const TransformTest = z.object({
4112
+ id: z.number().int(),
4113
+ entity_id: z.string(),
4114
+ transform_id: z.number().int(),
4115
+ creator_id: z.number().int(),
4116
+ name: z.string(),
4117
+ description: z.string().nullable(),
4118
+ inputs: z.array(TransformTestInput),
4119
+ expectations: z.array(TransformTestExpectation),
4120
+ created_at: z.string(),
4121
+ updated_at: z.string()
4122
+ }).loose();
4123
+ TransformTest.pick({
4124
+ id: true,
4125
+ transform_id: true,
4126
+ name: true,
4127
+ description: true
4128
+ }).strip();
4129
+ z.object({
4130
+ transform_id: z.number().int().positive(),
4131
+ name: z.string().min(1),
4132
+ description: z.string().nullable().optional(),
4133
+ inputs: z.array(TransformTestInput),
4134
+ expectations: z.array(TransformTestExpectation)
4135
+ }).loose();
4136
+ z.object({
4137
+ transform_id: z.number().int().positive().optional(),
4138
+ name: z.string().min(1).optional(),
4139
+ description: z.string().nullable().optional(),
4140
+ inputs: z.array(TransformTestInput).optional(),
4141
+ expectations: z.array(TransformTestExpectation).optional()
4142
+ }).loose();
4143
+ const TransformTestResultColumn = z.object({
4144
+ name: z.string(),
4145
+ database_type: z.string()
4146
+ }).loose();
4147
+ const TransformTestExpectationError = z.object({
4148
+ type: z.string(),
4149
+ message: z.string()
4150
+ }).loose();
4151
+ const TransformTestCellMismatch = z.object({
4152
+ column: z.string(),
4153
+ expected: TransformTestCell,
4154
+ actual: TransformTestCell
4155
+ }).loose();
4156
+ const TransformTestRowCounts = z.object({
4157
+ actual: z.number().int(),
4158
+ expected: z.number().int()
4159
+ }).loose();
4160
+ const TransformTestEqualsFindings = z.object({
4161
+ name: z.string(),
4162
+ type: z.literal("equals"),
4163
+ status: z.enum(["passed", "failed"]),
4164
+ columns: z.array(TransformTestResultColumn),
4165
+ "row-counts": TransformTestRowCounts,
4166
+ "extra-rows": z.array(TransformTestRow),
4167
+ "missing-rows": z.array(TransformTestRow),
4168
+ "cell-mismatches": z.array(TransformTestCellMismatch),
4169
+ truncated: z.number().int()
4170
+ }).loose();
4171
+ const TransformTestEqualsError = z.object({
4172
+ name: z.string(),
4173
+ type: z.literal("equals"),
4174
+ status: z.literal("error"),
4175
+ error: TransformTestExpectationError
4176
+ }).loose();
4177
+ const TransformTestEqualsResult = z.discriminatedUnion("status", [TransformTestEqualsFindings, TransformTestEqualsError]);
4178
+ const TransformTestEmptyPassed = z.object({
4179
+ name: z.string(),
4180
+ type: z.literal("empty"),
4181
+ status: z.literal("passed")
4182
+ }).loose();
4183
+ const TransformTestEmptyFailed = z.object({
4184
+ name: z.string(),
4185
+ type: z.literal("empty"),
4186
+ status: z.literal("failed"),
4187
+ columns: z.array(TransformTestResultColumn),
4188
+ sample: z.array(TransformTestRow),
4189
+ truncated: z.number().int()
4190
+ }).loose();
4191
+ const TransformTestEmptyError = z.object({
4192
+ name: z.string(),
4193
+ type: z.literal("empty"),
4194
+ status: z.literal("error"),
4195
+ error: TransformTestExpectationError
4196
+ }).loose();
4197
+ const TransformTestEmptyResult = z.discriminatedUnion("status", [
4198
+ TransformTestEmptyPassed,
4199
+ TransformTestEmptyFailed,
4200
+ TransformTestEmptyError
4201
+ ]);
4202
+ const TransformTestExpectationResult = z.discriminatedUnion("type", [TransformTestEqualsResult, TransformTestEmptyResult]);
4203
+ const TransformTestRunResult = z.object({
4204
+ status: z.enum(["passed", "failed"]),
4205
+ expectations: z.array(TransformTestExpectationResult),
4206
+ tables: z.record(z.string(), z.string())
4207
+ }).loose();
4208
+ z.enum([
4209
+ "transform-test.unknown-column",
4210
+ "transform-test.ambiguous-column",
4211
+ "transform-test.missing-inputs",
4212
+ "transform-test.unused-inputs",
4213
+ "transform-test.duplicate-input-table",
4214
+ "transform-test.unparseable-source",
4215
+ "transform-test.unremapped-reference",
4216
+ "transform-test.unsupported-transform",
4217
+ "transform-test.unsupported-driver",
4218
+ "transform-test.transform-failed",
4219
+ "transform-test.setup-failed",
4220
+ "transform-test.expectation-failed",
4221
+ "transform-test.unsupported-format"
4222
+ ]);
4223
+ //#endregion
3664
4224
  //#region ../client/src/resources/transform-test.ts
3665
4225
  const TransformTestApiList = z.array(TransformTest);
3666
4226
  function transformTestResource(transport) {
4227
+ const { explain } = explainer(transport, "transformTest");
3667
4228
  /** List the transform tests, optionally only those of one transform. */
3668
4229
  async function list(params = {}, options = {}) {
3669
- await transport.require("transformTest.list", options);
3670
4230
  return {
3671
4231
  data: await transport.requestParsed(TransformTestApiList, "/api/ee/transform-test", {
3672
4232
  ...options,
@@ -3677,7 +4237,6 @@ function transformTestResource(transport) {
3677
4237
  }
3678
4238
  /** Get a transform test by id. */
3679
4239
  async function get(id, options = {}) {
3680
- await transport.require("transformTest.get", options);
3681
4240
  return transport.requestParsed(TransformTest, `/api/ee/transform-test/${id}`, { ...options });
3682
4241
  }
3683
4242
  /**
@@ -3686,7 +4245,6 @@ function transformTestResource(transport) {
3686
4245
  * `TransformTestRefusalCode` when they cannot run.
3687
4246
  */
3688
4247
  async function create(params, options = {}) {
3689
- await transport.require("transformTest.create", options);
3690
4248
  return transport.requestParsed(TransformTest, "/api/ee/transform-test", {
3691
4249
  ...options,
3692
4250
  method: "POST",
@@ -3698,7 +4256,6 @@ function transformTestResource(transport) {
3698
4256
  * transform, inputs or expectations is validated as on create, with the same refusal.
3699
4257
  */
3700
4258
  async function update(id, params, options = {}) {
3701
- await transport.require("transformTest.update", options);
3702
4259
  return transport.requestParsed(TransformTest, `/api/ee/transform-test/${id}`, {
3703
4260
  ...options,
3704
4261
  method: "PUT",
@@ -3707,7 +4264,6 @@ function transformTestResource(transport) {
3707
4264
  }
3708
4265
  /** Delete a transform test by id. */
3709
4266
  async function remove(id, options = {}) {
3710
- await transport.require("transformTest.delete", options);
3711
4267
  await transport.requestRaw(`/api/ee/transform-test/${id}`, {
3712
4268
  ...options,
3713
4269
  method: "DELETE",
@@ -3721,19 +4277,18 @@ function transformTestResource(transport) {
3721
4277
  * refused and nothing meaningful ran.
3722
4278
  */
3723
4279
  async function run(id, options = {}) {
3724
- await transport.require("transformTest.run", options);
3725
4280
  return transport.requestParsed(TransformTestRunResult, `/api/ee/transform-test/${id}/run`, {
3726
4281
  ...options,
3727
4282
  method: "POST"
3728
4283
  });
3729
4284
  }
3730
4285
  return {
3731
- list,
3732
- get,
3733
- create,
3734
- update,
3735
- delete: remove,
3736
- run
4286
+ list: explain("list", list),
4287
+ get: explain("get", get),
4288
+ create: explain("create", create),
4289
+ update: explain("update", update),
4290
+ delete: explain("delete", remove),
4291
+ run: explain("run", run)
3737
4292
  };
3738
4293
  }
3739
4294
  //#endregion
@@ -3747,7 +4302,6 @@ function uploadResource(transport) {
3747
4302
  * The file may be at most 50 MB; larger uploads are rejected with a 413 response.
3748
4303
  */
3749
4304
  async function createFromCsv(file, params, options = {}) {
3750
- await transport.require("upload.createFromCsv", options);
3751
4305
  const form = buildCsvFormData(file);
3752
4306
  form.append("collection_id", params.collection_id);
3753
4307
  const response = await transport.requestRaw("/api/upload/csv", {
@@ -3800,7 +4354,6 @@ CurrentUser.pick({
3800
4354
  function userResource(transport) {
3801
4355
  /** Get the user the request's credentials authenticate as. */
3802
4356
  async function current(options = {}) {
3803
- await transport.require("user.current", options);
3804
4357
  return transport.requestParsed(CurrentUser, "/api/user/current", { ...options });
3805
4358
  }
3806
4359
  return { current };
@@ -3815,7 +4368,6 @@ function createClient(config, options) {
3815
4368
  collection: collectionResource(transport),
3816
4369
  contentTranslation: contentTranslationResource(transport),
3817
4370
  dashboard: dashboardResource(transport),
3818
- dataAction: dataActionResource(transport),
3819
4371
  database: databaseResource(transport),
3820
4372
  dataset: datasetResource(transport),
3821
4373
  dependency: dependencyResource(transport),
@@ -3852,6 +4404,8 @@ function createClient(config, options) {
3852
4404
  upload: uploadResource(transport),
3853
4405
  user: userResource(transport),
3854
4406
  server: transport.server,
4407
+ verifiedServer: transport.verifiedServer,
4408
+ probesSettled: transport.probesSettled,
3855
4409
  requestParsed: transport.requestParsed,
3856
4410
  requestRaw: transport.requestRaw,
3857
4411
  requestStream: transport.requestStream