@metabase/cli 0.4.1-alpha.fix-transform-test-ee-path.b41b46c → 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 (280) hide show
  1. package/dist/{add-collection-CcjydN8e.mjs → add-collection-COtKcy6Y.mjs} +4 -4
  2. package/dist/{alert-DBTU8iHe.mjs → alert-DMlSjPEv.mjs} +7 -7
  3. package/dist/{alerts-DsgnumqY.mjs → alerts-s3al16NZ.mjs} +6 -6
  4. package/dist/{append-DUFn3_Og.mjs → append-CbGmdeLw.mjs} +4 -4
  5. package/dist/{archive-CWs273rN.mjs → archive-5EY9UJU_.mjs} +3 -3
  6. package/dist/{archive-Bxl1oeJr.mjs → archive-BFmx4B91.mjs} +3 -3
  7. package/dist/{archive-CcPTctIo.mjs → archive-BZCYIBhQ.mjs} +3 -3
  8. package/dist/{archive-sfqadV_a.mjs → archive-C_UHeanE.mjs} +3 -3
  9. package/dist/{archive-CC6THE3k.mjs → archive-Ce4eArrs.mjs} +3 -3
  10. package/dist/{archive-CI-WE7dA.mjs → archive-CnDriekB.mjs} +5 -5
  11. package/dist/{archive-DVgglC0F.mjs → archive-DDO2XX0E.mjs} +4 -4
  12. package/dist/{archive-DjQkD1WI.mjs → archive-DW7x0IFB.mjs} +4 -4
  13. package/dist/{archive-BEzfnrwM.mjs → archive-DvbaSglr.mjs} +3 -3
  14. package/dist/{archive-BTfOiOnx.mjs → archive-DzSdJVqE.mjs} +5 -5
  15. package/dist/{archive-jwyopr47.mjs → archive-a05W75XH.mjs} +3 -3
  16. package/dist/{auth-IQKgyb_x.mjs → auth-DOz7KW5E.mjs} +5 -5
  17. package/dist/{body-CT1cQrhp.mjs → body-Di7940x_.mjs} +2 -3
  18. package/dist/{branch-flag-Czh8JT1G.mjs → branch-flag-CfJ8vwgu.mjs} +1 -1
  19. package/dist/{branches-ByUeNOBP.mjs → branches-BC-vlk6i.mjs} +3 -3
  20. package/dist/{breaking-BKR88YVq.mjs → breaking-ByrjsFjq.mjs} +6 -6
  21. package/dist/{broken-DeS3_Cg2.mjs → broken-DLSk1T88.mjs} +8 -8
  22. package/dist/{bulk-discard-values-DY7svs0d.mjs → bulk-discard-values-VKGj8c7z.mjs} +5 -5
  23. package/dist/{bulk-edit-3EfcsqCu.mjs → bulk-edit-CpuleeCQ.mjs} +5 -5
  24. package/dist/{bulk-rescan-values-BpwnC_WY.mjs → bulk-rescan-values-DeGwiqLS.mjs} +5 -5
  25. package/dist/{bulk-sync-schema-Dwbs0TsG.mjs → bulk-sync-schema-BKVhauW1.mjs} +5 -5
  26. package/dist/{cancel-Nz4jJ-Ey.mjs → cancel-qKj45001.mjs} +3 -3
  27. package/dist/{cancel-task-CX_qYiJ0.mjs → cancel-task-nmBBd1Ky.mjs} +3 -3
  28. package/dist/card-Buc-X4Mh.mjs +25 -0
  29. package/dist/{cards-FUW8-5PO.mjs → cards-Bq73s48q.mjs} +4 -4
  30. package/dist/cli.mjs +628 -229
  31. package/dist/{client-5y-BaV3d.mjs → client-BmURNkkW.mjs} +997 -365
  32. package/dist/{collection-DPiztU_l.mjs → collection-C03LXnCT.mjs} +1 -1
  33. package/dist/collection-D-sS0AFw.mjs +21 -0
  34. package/dist/{collection-namespace-Q_up_tSg.mjs → collection-namespace-DaDzK31i.mjs} +1 -2
  35. package/dist/{collection-BgPYl2c5.mjs → collection-zrZoJfgR.mjs} +1 -1
  36. package/dist/{content-translation-CSkaKlqa.mjs → content-translation-BlG41YrA.mjs} +3 -3
  37. package/dist/{copy-WlNBviDZ.mjs → copy-DOKlVa7H.mjs} +6 -6
  38. package/dist/{copy-fikpWJO4.mjs → copy-DTiYs_a2.mjs} +4 -4
  39. package/dist/{copy-flags-j38p0var.mjs → copy-flags-BhGWC5kS.mjs} +2 -2
  40. package/dist/{create-C4dDHOzR.mjs → create-1rLk8b5q.mjs} +3 -3
  41. package/dist/{create-BmCz5lro.mjs → create-B1QDNU1x.mjs} +6 -6
  42. package/dist/{create-BI73JzZN.mjs → create-B1bX6ngX.mjs} +4 -4
  43. package/dist/{create-9sPJd45D.mjs → create-B5BwdPwT.mjs} +6 -6
  44. package/dist/{create-CC89lXeE.mjs → create-BKKNIe93.mjs} +3 -3
  45. package/dist/{create-COv2mYmk.mjs → create-BXX_n64u.mjs} +4 -4
  46. package/dist/{create-CEezFRTJ.mjs → create-BpwZJZxb.mjs} +5 -5
  47. package/dist/{create-DqGen4jU.mjs → create-C0kz0oYc.mjs} +3 -3
  48. package/dist/{create-CLLcjbzq.mjs → create-Cob0gLsA.mjs} +3 -3
  49. package/dist/{create-yLNBBrwG.mjs → create-DPggIXfn.mjs} +4 -4
  50. package/dist/{create-DdnC7PkI.mjs → create-DUytP2Y1.mjs} +4 -4
  51. package/dist/{create-CO6gBV7I.mjs → create-DraZzrLO.mjs} +4 -4
  52. package/dist/{create-Dp1597uC.mjs → create-Duj_2YIF.mjs} +4 -4
  53. package/dist/{create-C0MNevKK.mjs → create-JaFhqysM.mjs} +5 -5
  54. package/dist/{create-branch-Cflee_NY.mjs → create-branch-B5jB4tKk.mjs} +3 -3
  55. package/dist/{create-CYqqBKhE.mjs → create-d0trLAzB.mjs} +4 -4
  56. package/dist/{create-Ci7o6-Fk.mjs → create-vOw8gmBU.mjs} +3 -3
  57. package/dist/{csv-CX1wws5-.mjs → csv-DfExp9H_.mjs} +4 -4
  58. package/dist/{current-task-BTnORR7t.mjs → current-task-CbXIH_RA.mjs} +4 -4
  59. package/dist/dashboard-DH_soSQp.mjs +35 -0
  60. package/dist/{database-D-CRyRIv.mjs → database-BdKkae1m.mjs} +1 -1
  61. package/dist/{database-MlUPpitM.mjs → database-DoNoZ8Yc.mjs} +1 -1
  62. package/dist/{dataset-p5oNPQgL.mjs → dataset-DZgJm5hP.mjs} +3 -14
  63. package/dist/{db-DNevs1XU.mjs → db-C82M3Gpg.mjs} +7 -7
  64. package/dist/{delete-BJ18ifIG.mjs → delete-9xHRd2CG.mjs} +4 -4
  65. package/dist/{delete-dfrM_M6T.mjs → delete-B4BfGfiJ.mjs} +4 -4
  66. package/dist/{delete-D5Jy83V-.mjs → delete-D0xFMRRq.mjs} +4 -4
  67. package/dist/{delete-CCUYbNae.mjs → delete-DJ7SNXiH.mjs} +4 -4
  68. package/dist/{delete-ChmqxYa7.mjs → delete-hcw_4pBG.mjs} +4 -4
  69. package/dist/{delete-Ch797MmM.mjs → delete-hwPqKXeY.mjs} +4 -4
  70. package/dist/{delete-runtime-Cc7TASG7.mjs → delete-runtime-DkgYUQ_n.mjs} +1 -1
  71. package/dist/{delete-table-0xxoSknN.mjs → delete-table-DhTMft7u.mjs} +4 -4
  72. package/dist/{dependencies-GvzMLOS2.mjs → dependencies-B8H2gA04.mjs} +5 -5
  73. package/dist/dependency-CLznxtLE.mjs +15 -0
  74. package/dist/{dependency-BZxbh7qe.mjs → dependency-Dc33mjgd.mjs} +2 -2
  75. package/dist/{dependency-DaGeVAHF.mjs → dependency-tQBxYjFX.mjs} +1 -1
  76. package/dist/{dependents-BRqKb0Pu.mjs → dependents-B2GcS5kB.mjs} +9 -9
  77. package/dist/{dirty-Bq2cf5AG.mjs → dirty-Cz2rUd6S.mjs} +4 -4
  78. package/dist/{discard-values-k5-ePxWl.mjs → discard-values-QWuWGaVK.mjs} +5 -5
  79. package/dist/{document-COQzVLjW.mjs → document-BDobDiap.mjs} +1 -1
  80. package/dist/document-WhSEyI-3.mjs +20 -0
  81. package/dist/{document-BsaKF8UR.mjs → document-wfWbV0B_.mjs} +1 -1
  82. package/dist/{download-Dpbarf8I.mjs → download-Dw15fUMy.mjs} +2 -2
  83. package/dist/{eid-BEc1p_um.mjs → eid-Ex4G-ZlN.mjs} +4 -4
  84. package/dist/{entity-c_Br4SFc.mjs → entity-DBKPaMr5.mjs} +2 -2
  85. package/dist/{entry-flags-z8cZTfaQ.mjs → entry-flags-DgY3IXFe.mjs} +4 -4
  86. package/dist/{events-DBpOpy2y.mjs → events-BibJ4ya0.mjs} +4 -4
  87. package/dist/{export-F-R_vF48.mjs → export-U8MdaatW.mjs} +7 -7
  88. package/dist/{export-flags-QbLI0sKz.mjs → export-flags-BrfeyE8D.mjs} +2 -2
  89. package/dist/{export-preflight-0BvDa8Oz.mjs → export-preflight-A28L45au.mjs} +5 -5
  90. package/dist/field-qF1KRJYB.mjs +21 -0
  91. package/dist/{fields-DOdrazv2.mjs → fields-i1TFa_th.mjs} +4 -4
  92. package/dist/{filter-flags-DiUgZ6vZ.mjs → filter-flags-Bm3vuSM-.mjs} +3 -3
  93. package/dist/{fks-41Ig_ZQ2.mjs → fks-Dcfn3-73.mjs} +6 -6
  94. package/dist/{get-Dwp_BfIp.mjs → get-B4_3DID6.mjs} +5 -5
  95. package/dist/{get-TbBkN-jS.mjs → get-BCdtCwaG.mjs} +3 -3
  96. package/dist/{get-BVaN-tgY.mjs → get-BKDTRMNU.mjs} +3 -3
  97. package/dist/{get-CVJFF8Lb.mjs → get-BiPSePQc.mjs} +3 -3
  98. package/dist/{get-DaxwG0AP.mjs → get-C3ojRVJ_.mjs} +4 -4
  99. package/dist/{get-B8hREA2A.mjs → get-CCLWWqTD.mjs} +4 -4
  100. package/dist/{get-aC5jUA1y.mjs → get-CVpIQb8v.mjs} +5 -5
  101. package/dist/{get-BjJdy_Iw.mjs → get-CcwRn8Y2.mjs} +5 -5
  102. package/dist/{get-BAV-KpLU.mjs → get-CobILQ_7.mjs} +3 -3
  103. package/dist/{get-OKOOd2yb.mjs → get-Cwj1Q-at.mjs} +3 -3
  104. package/dist/{get-DwUB0K2G.mjs → get-D0NZ0ZO1.mjs} +3 -3
  105. package/dist/{get-2l2CbHc6.mjs → get-DPZUIPjv.mjs} +3 -3
  106. package/dist/{get-D1c-C-_Q2.mjs → get-DUwX-RU82.mjs} +3 -3
  107. package/dist/{get-Bby33eun.mjs → get-DqYn9mez.mjs} +4 -4
  108. package/dist/{get-CPhMYZDP.mjs → get-Dz3dNirC.mjs} +4 -4
  109. package/dist/{get-CfeNeyGE.mjs → get-EKBYludD.mjs} +3 -3
  110. package/dist/{get-YinpF0y5.mjs → get-HFQSttF3.mjs} +5 -5
  111. package/dist/{get-CjK2GzFn.mjs → get-IjoHBExI.mjs} +5 -5
  112. package/dist/{get-BlLUy1G0.mjs → get-hAoJtVCK.mjs} +3 -3
  113. package/dist/{get-run-PeXiUJwf.mjs → get-run-B2SnsZSY.mjs} +4 -4
  114. package/dist/git-sync-BlgB_-8-.mjs +29 -0
  115. package/dist/{git-sync-DspFahwV.mjs → git-sync-Ch1V1fEK.mjs} +1 -1
  116. package/dist/{glossary-CdW6RFId.mjs → glossary-6LLpItof.mjs} +1 -1
  117. package/dist/glossary-BvVxqjxJ.mjs +14 -0
  118. package/dist/{glossary-Del97Jqg.mjs → glossary-wnuxoE_I.mjs} +1 -1
  119. package/dist/{graph-M7V_js_w.mjs → graph-CHlfxVy1.mjs} +6 -6
  120. package/dist/{group-D4w87fXL.mjs → group-B3X3LTcU.mjs} +1 -1
  121. package/dist/{has-remote-changes-D7uABGR0.mjs → has-remote-changes-DCMpqGgE.mjs} +3 -3
  122. package/dist/{import-CABYSyWS.mjs → import-C7MZhKo6.mjs} +7 -7
  123. package/dist/{input-DjHsV-kD.mjs → input-sRdxNc3T.mjs} +1 -1
  124. package/dist/{is-dirty-DpQBXuVa.mjs → is-dirty-DpLyHebS.mjs} +2 -2
  125. package/dist/{items-BqUCPf-J.mjs → items-1ME_eg27.mjs} +6 -6
  126. package/dist/{key-BTaxFn_7.mjs → key-DMtgTOwN.mjs} +1 -1
  127. package/dist/{library-DmkpcZPb.mjs → library-CP-9yepy.mjs} +1 -1
  128. package/dist/{library-DDrpsgMv.mjs → library-D2ogEIfI.mjs} +5 -5
  129. package/dist/{library-C4M8TF2N.mjs → library-fWEv38OB.mjs} +2 -2
  130. package/dist/{list-Dd9o6Tut.mjs → list-B0h-j1E7.mjs} +3 -3
  131. package/dist/{list-ISpYiawJ.mjs → list-BUZ91ctZ.mjs} +5 -5
  132. package/dist/{list-CLRezReA.mjs → list-BUwDGyIu.mjs} +5 -5
  133. package/dist/{list-C9H24xr5.mjs → list-BXcDPU5d.mjs} +6 -6
  134. package/dist/{list-BQXq0A65.mjs → list-BoVeF8QA.mjs} +6 -6
  135. package/dist/{list-CHUwj_Qh.mjs → list-C1oEpUZG.mjs} +3 -3
  136. package/dist/{list-Hiq5T4Bt.mjs → list-C9996vbS.mjs} +3 -3
  137. package/dist/{list-DO767sO1.mjs → list-CJ5FyCnL.mjs} +4 -4
  138. package/dist/{list-BFAc460G.mjs → list-CQi3AgS5.mjs} +4 -4
  139. package/dist/{list-yDJq3wNs.mjs → list-CVh8DHOO.mjs} +5 -5
  140. package/dist/{list-D217irjK.mjs → list-DBAEy3Rx.mjs} +3 -3
  141. package/dist/{list-glgnTw7w.mjs → list-DkCadZ2E.mjs} +3 -3
  142. package/dist/{list-CEU9UXaZ.mjs → list-DzP7YXdZ.mjs} +3 -3
  143. package/dist/{list-aHFBopQd.mjs → list-LMlJ4uAY.mjs} +4 -4
  144. package/dist/{list-BXRUiZuH.mjs → list-NINy7ybI.mjs} +4 -4
  145. package/dist/{list-CONZkA9i.mjs → list-OXiXtdiM.mjs} +5 -5
  146. package/dist/{list-BygxQkq7.mjs → list-STy8UHs2.mjs} +6 -6
  147. package/dist/{list-CZF_gQnI.mjs → list-Ud7F0XdU.mjs} +3 -3
  148. package/dist/{list-D0MlsdSX.mjs → list-dCi6OaIE.mjs} +4 -4
  149. package/dist/{login-DCWam2ec.mjs → login-BMON3Fft.mjs} +6 -6
  150. package/dist/{logout-CmrJqhkc.mjs → logout-Cx934GzW.mjs} +2 -2
  151. package/dist/{measure-c_VUyXFN.mjs → measure-CUqJtDNm.mjs} +6 -6
  152. package/dist/{notification-Cuuwoksm.mjs → notification-CCZ9mrOw.mjs} +1 -1
  153. package/dist/{parameter-values-CnwWZrJ_.mjs → parameter-values-DCrU02Az.mjs} +3 -3
  154. package/dist/{parse-enum-BPKULOSw.mjs → parse-enum-Do5Cptbv.mjs} +1 -1
  155. package/dist/{parse-id-aMXePsUP.mjs → parse-id-oCbmouCn.mjs} +1 -1
  156. package/dist/{parse-ref-8bVX_9IN.mjs → parse-ref-Df8gusl4.mjs} +1 -1
  157. package/dist/{parse-text-Bm6eUa-p.mjs → parse-text-CVrP7GmZ.mjs} +1 -1
  158. package/dist/{path-CGbz-QSr.mjs → path-DTuSDGd6.mjs} +4 -4
  159. package/dist/{poll-DFZRtTA_.mjs → poll-BKo5dK0l.mjs} +2 -2
  160. package/dist/{preflight-ChJ0pemy.mjs → preflight-UeYuGDPr.mjs} +2 -3
  161. package/dist/{process-B3Di_HtV.mjs → process-BpmZXbcw.mjs} +1 -1
  162. package/dist/{publish-7ke00CtK.mjs → publish-D3GwLC-K.mjs} +4 -4
  163. package/dist/{pulse-CIza18rO.mjs → pulse-DzVPfb5J.mjs} +1 -1
  164. package/dist/{query-8M7ZN2Ca.mjs → query-B1Cp9EO6.mjs} +4 -4
  165. package/dist/{query-DNa-fGS7.mjs → query-Ct697cY-.mjs} +11 -12
  166. package/dist/{remapping-Cy1CEown.mjs → remapping-RsoPWE7c.mjs} +4 -4
  167. package/dist/{remove-collection-B2CL66No.mjs → remove-collection-BJo3Wd9K.mjs} +4 -4
  168. package/dist/{render-ColPG5G7.mjs → render-C_ev6qoW.mjs} +2 -2
  169. package/dist/{replace-COC3ggyx.mjs → replace-BSAGp3cR.mjs} +4 -4
  170. package/dist/requirement-check-T3bjQ0Mr.mjs +60 -0
  171. package/dist/{rescan-values-DUX58myu.mjs → rescan-values-CE62_2dc.mjs} +5 -5
  172. package/dist/{rescan-values-DXzXTw9a.mjs → rescan-values-rpro4Kw2.mjs} +5 -5
  173. package/dist/{run-C5wf-lug.mjs → run-B3BRodEp.mjs} +3 -3
  174. package/dist/{run-T2a4m1x9.mjs → run-BI1QO9Kn.mjs} +7 -7
  175. package/dist/{runs-Datc76Wu.mjs → runs-CUseZezf.mjs} +5 -5
  176. package/dist/{runtime-C3AL15Dp.mjs → runtime-C5Ha6Ou8.mjs} +248 -534
  177. package/dist/{schema-tables-CR2S5mNc.mjs → schema-tables-C7YkyRh0.mjs} +6 -6
  178. package/dist/{schemas-OrGjEuVc.mjs → schemas-Bj5sJ9uB.mjs} +4 -4
  179. package/dist/{search-BEXBlfak.mjs → search-BsAwK7Rk.mjs} +5 -5
  180. package/dist/{search-CJYSB5eP.mjs → search-CB1A4HWx.mjs} +6 -6
  181. package/dist/{segment-Tt8GaRnc.mjs → segment-BEjaeLc5.mjs} +6 -6
  182. package/dist/{send-uNLODakO.mjs → send-Dfh8d1gG.mjs} +3 -3
  183. package/dist/{set-DRp3F4wz.mjs → set-DA8dKyzi.mjs} +4 -4
  184. package/dist/{set-active-CAt6ieXS.mjs → set-active-AyjKtBD-.mjs} +3 -3
  185. package/dist/{set-sensitivity--Qq2RXVE.mjs → set-sensitivity-D0613dHA.mjs} +4 -4
  186. package/dist/{setting-DMqvipRp.mjs → setting-B-bxCkC0.mjs} +4 -4
  187. package/dist/{setup-B2PC1NVz.mjs → setup-Bvvqlz9d.mjs} +3 -3
  188. package/dist/{signal-0Yt8JTMn.mjs → signal-Dm4N0Bcc.mjs} +1 -1
  189. package/dist/{skill-list-Cyx1935Q.mjs → skill-list-DkuCf0k4.mjs} +9 -3
  190. package/dist/{skills-Dlp0MK6g.mjs → skills-DlDCTGVE.mjs} +2 -2
  191. package/dist/{skills-D7wu2iff.mjs → skills-DxrO7HUz.mjs} +3 -3
  192. package/dist/snippet-B7Kau5U8.mjs +19 -0
  193. package/dist/{stash-CHeQPI8Z.mjs → stash-j9JPv3ns.mjs} +5 -5
  194. package/dist/{status-Bdt5XWrQ.mjs → status-DcbvbRc5.mjs} +3 -3
  195. package/dist/{status-gTN3G-rs.mjs → status-DxNcrfnI.mjs} +16 -7
  196. package/dist/{subscription-C9alueg1.mjs → subscription-CSR2t-xK.mjs} +6 -6
  197. package/dist/{subscriptions-DJKbcxDy.mjs → subscriptions--0YvW40u.mjs} +6 -6
  198. package/dist/{summary-kaC0aINr.mjs → summary-Dslv78-r.mjs} +3 -3
  199. package/dist/{sync-schema-CyKovAb9.mjs → sync-schema-BSJiokQf.mjs} +6 -6
  200. package/dist/{sync-schema-C29bGCtt.mjs → sync-schema-Dgij5ICd.mjs} +5 -5
  201. package/dist/{sync-task-BAMOf6_t.mjs → sync-task-a1zyF1Qb.mjs} +4 -5
  202. package/dist/{table-BFIOH6Pb.mjs → table-Uvu1roSx.mjs} +2 -2
  203. package/dist/table-qjgz2eek.mjs +26 -0
  204. package/dist/{table-selector-flags-CSoedTgm.mjs → table-selector-flags-B9OiA3Z1.mjs} +3 -3
  205. package/dist/{table-Ci6bhZkb.mjs → table-zAdW5_3H.mjs} +5 -5
  206. package/dist/{text-CdtEI6l6.mjs → text-821Rn8M9.mjs} +1 -1
  207. package/dist/timeline-CNkrY3e3.mjs +21 -0
  208. package/dist/{timeline-event-DN3y8vwW.mjs → timeline-event-CSzx8EDP.mjs} +6 -6
  209. package/dist/{transform-D4z70PIN.mjs → transform-BKX6tl3i.mjs} +1 -1
  210. package/dist/transform-Y86R7_2F.mjs +28 -0
  211. package/dist/transform-job-Cg_1jzWI.mjs +22 -0
  212. package/dist/{transform-tag-CIKnNaQH.mjs → transform-tag-DHg7j15d.mjs} +5 -5
  213. package/dist/{transforms-BQjwZKTN.mjs → transforms-DwxPfMiG.mjs} +5 -5
  214. package/dist/{tree-C21CG5rm.mjs → tree-COFmA20J.mjs} +3 -3
  215. package/dist/{unpublish-C7MRZTmS.mjs → unpublish-BQClh4U7.mjs} +3 -3
  216. package/dist/{unreferenced-BJUIC_Ct.mjs → unreferenced-DKc0zZDl.mjs} +6 -6
  217. package/dist/{update-CuYvtXQY.mjs → update-B-wcIl8T.mjs} +4 -4
  218. package/dist/{update-DDH6FTe6.mjs → update-B8jQTspb.mjs} +8 -8
  219. package/dist/{update-9b4JHJWR.mjs → update-BBLbEPEP.mjs} +4 -4
  220. package/dist/{update-C0QcHFIP.mjs → update-BNwjjMk6.mjs} +5 -5
  221. package/dist/{update-BXFGGYT8.mjs → update-BVFfoopT.mjs} +5 -5
  222. package/dist/{update-CVO8Srng.mjs → update-BVMgrMy7.mjs} +5 -5
  223. package/dist/{update-CCHTQjsP.mjs → update-Bgy2amR0.mjs} +6 -6
  224. package/dist/{update-CYy-r_Jq.mjs → update-C9HK3i8s.mjs} +5 -5
  225. package/dist/{update-IEVAFvpi.mjs → update-CFfr1ZdL.mjs} +4 -4
  226. package/dist/{update-B2F22ryV.mjs → update-CMRWXoZ2.mjs} +4 -4
  227. package/dist/{update-D6oHLn1g.mjs → update-CPmPnEvm.mjs} +6 -6
  228. package/dist/{update-Duxk_-dC.mjs → update-Ca8LSZer.mjs} +5 -5
  229. package/dist/{update-CBcJTZo6.mjs → update-DJwu8UT2.mjs} +4 -4
  230. package/dist/{update-DX1s6KnO.mjs → update-Dwr3n7_n.mjs} +7 -7
  231. package/dist/{update-DEnsF5zT.mjs → update-MxvqmZ3z.mjs} +6 -6
  232. package/dist/{update-dashcard-C4ZndfvP.mjs → update-dashcard-C8QN2Qcu.mjs} +4 -4
  233. package/dist/{update-EpHReUGk.mjs → update-kM3TDlEp.mjs} +5 -5
  234. package/dist/{update-CZ2eSAV5.mjs → update-myfBF5qV.mjs} +4 -4
  235. package/dist/{upgrade-o2DpB66j.mjs → upgrade-BBOEVIRa.mjs} +4 -4
  236. package/dist/{upload-CEtKrueq.mjs → upload-CoBd4qBp.mjs} +3 -3
  237. package/dist/{upload-DJB7DMA1.mjs → upload-CxpXpjCf.mjs} +2 -2
  238. package/dist/upload-fcim1xtK.mjs +13 -0
  239. package/dist/{uuid-D9oozo8p.mjs → uuid-DW30GY7B.mjs} +2 -2
  240. package/dist/{validate-D_iikPkc.mjs → validate-n85yMraN.mjs} +1 -1
  241. package/dist/{validate-query-BEYjmSv2.mjs → validate-query-BwXvFFYi.mjs} +2 -2
  242. package/dist/{values-De8aGDiJ.mjs → values-B6eFE4K5.mjs} +3 -3
  243. package/dist/{verify-DVjaammv.mjs → verify-BSq7x66g.mjs} +3 -3
  244. package/dist/{verify-ob8JSLxv.mjs → verify-DnU-wXIs.mjs} +4 -4
  245. package/dist/{verify-DKmz4xJs.mjs → verify-Dp8tUbP3.mjs} +3 -3
  246. package/dist/{wait-BN_MzTng.mjs → wait-D6Tw4ZxO.mjs} +6 -6
  247. package/dist/{wait-flags-DQlX2yM_.mjs → wait-flags-BRUzMI6S.mjs} +2 -2
  248. package/dist/{window-2ybHqui2.mjs → window-BqMFpWMh.mjs} +1 -1
  249. package/package.json +1 -1
  250. package/skill-data/core/SKILL.md +5 -8
  251. package/skill-data/dashboard/SKILL.md +6 -0
  252. package/skill-data/git-sync/SKILL.md +64 -14
  253. package/skill-data/transform/SKILL.md +1 -63
  254. package/dist/card-DmQO85ej.mjs +0 -25
  255. package/dist/collection-BPVrTfTv.mjs +0 -21
  256. package/dist/create-DQM_AB3I.mjs +0 -34
  257. package/dist/dashboard-c_wOMtS9.mjs +0 -35
  258. package/dist/delete-CZTG4wI2.mjs +0 -44
  259. package/dist/dependency-D6DMbPVm.mjs +0 -15
  260. package/dist/document-DCqe5MO9.mjs +0 -20
  261. package/dist/field-CcCQ0goz.mjs +0 -21
  262. package/dist/get-DxpDAUz4.mjs +0 -32
  263. package/dist/git-sync-CO199xCu.mjs +0 -29
  264. package/dist/glossary-LdAlftZW.mjs +0 -14
  265. package/dist/list-CQJABPGk.mjs +0 -35
  266. package/dist/run-C_nqwWmC.mjs +0 -63
  267. package/dist/snippet-D-5bKT_U.mjs +0 -19
  268. package/dist/table-8c-V5afO.mjs +0 -26
  269. package/dist/timeline-BetOmLYk.mjs +0 -21
  270. package/dist/transform-job-DALBzYVV.mjs +0 -22
  271. package/dist/transform-p-6_thIh.mjs +0 -28
  272. package/dist/transform-test-BjjCQ2xm.mjs +0 -169
  273. package/dist/transform-test-C5Havg4o.mjs +0 -16
  274. package/dist/transform-test-DHGKeuHa.mjs +0 -32
  275. package/dist/transport-DuU3fLui.mjs +0 -313
  276. package/dist/update-BQben_Xm.mjs +0 -41
  277. package/dist/upload-BqNErDXE.mjs +0 -13
  278. package/skill-data/transform-test-plan/SKILL.md +0 -82
  279. package/skill-data/transform-test-plan/references/checklist.md +0 -32
  280. package/skill-data/transform-test-plan/references/checks.md +0 -156
@@ -1,24 +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-C3AL15Dp.mjs";
4
- import { n as createTransport } from "./transport-DuU3fLui.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
8
  import { a as CardQueryResult, d as PivotExportFormat, l as DatasetQuery, t as Card } from "./card-Bv4ZN0oV.mjs";
8
- import { b as TableVisibilityType, p as TableQueryMetadata, s as TableDataLayerTier, t as Table, u as TableForeignKey, v as TableSelectors } from "./table-Ci6bhZkb.mjs";
9
- import { t as Database } from "./database-D-CRyRIv.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";
10
11
  import { t as Snippet } from "./snippet-CyKAgIZl.mjs";
11
- import { o as compiledQuerySchema, r as QueryMetadata } from "./dataset-p5oNPQgL.mjs";
12
- 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";
13
14
  import { t as ContentTranslationUploadResult } from "./content-translation-BrHOrmj9.mjs";
14
15
  import { l as Dashcard, o as DashboardDetail, r as DashboardCopy, t as Dashboard } from "./dashboard-Phz3XpRM.mjs";
15
- import { r as pollUntil } from "./poll-DFZRtTA_.mjs";
16
- import { i as DependencyEntity, s as DependencyGraph, t as BreakingSource, u as DependencyNode } from "./dependency-DaGeVAHF.mjs";
17
- 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";
18
19
  import { i as EidTranslateResult } from "./eid-translation-zEHtr5Oc.mjs";
19
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";
20
- import { t as Glossary } from "./glossary-CdW6RFId.mjs";
21
- 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";
22
23
  import { t as Measure } from "./measure-CtfjqJFP.mjs";
23
24
  import { r as ModerationReviewCreateInput, t as ModerationReview } from "./moderation-review-BdqEH6N1.mjs";
24
25
  import { t as Pulse } from "./pulse-Dz0ocv0l.mjs";
@@ -30,9 +31,9 @@ import { n as SetupResult } from "./setup-CO7QV-ao.mjs";
30
31
  import { i as TimelineEvent, t as Timeline } from "./timeline-CCSU2qOS.mjs";
31
32
  import { c as transformJobSchema, n as TransformJobActiveResult, s as transformJobRunResultSchema } from "./transform-job-CCguL6LP.mjs";
32
33
  import { t as TransformTag } from "./transform-tag-CcElGp3C.mjs";
33
- import { i as TransformTestRunResult, t as TransformTest } from "./transform-test-BjjCQ2xm.mjs";
34
34
  import { t as UploadResult } from "./upload-DAtxBBms.mjs";
35
35
  import { z } from "zod";
36
+ import { setTimeout } from "node:timers/promises";
36
37
  //#region \0rolldown/runtime.js
37
38
  var __defProp = Object.defineProperty;
38
39
  var __exportAll = (all, no_symbols) => {
@@ -45,15 +46,425 @@ var __exportAll = (all, no_symbols) => {
45
46
  return target;
46
47
  };
47
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
48
459
  //#region ../client/src/resources/pivot-export.ts
49
- const PivotedExportsSetting = z.object({ "enable-pivoted-exports": z.boolean() });
460
+ const PivotedExportsProperty = SessionProperties.extend({ "enable-pivoted-exports": z.boolean() });
50
461
  /**
51
462
  * Refuse a pivoted export the server would answer with plain rows: a JSON export keeps only the
52
463
  * ungrouped rows, and an admin can turn pivoted CSV and XLSX exports off.
53
464
  */
54
465
  async function assertPivotedExport(transport, format, options) {
55
466
  if (!PivotExportFormat.safeParse(format).success) throw new ConfigError(`a ${format} export cannot be pivoted; only ${PivotExportFormat.options.join(" and ")} can`);
56
- 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");
57
468
  }
58
469
  //#endregion
59
470
  //#region ../client/src/resources/card.ts
@@ -61,7 +472,6 @@ const CardApiList = z.array(Card);
61
472
  function cardResource(transport) {
62
473
  /** List cards. `f` picks a server-side preset; `model_id` scopes the presets that need an id. */
63
474
  async function list(params = {}, options = {}) {
64
- await transport.require("card.list", options);
65
475
  return {
66
476
  data: await transport.requestParsed(CardApiList, "/api/card", {
67
477
  ...options,
@@ -75,12 +485,10 @@ function cardResource(transport) {
75
485
  }
76
486
  /** Get one card by id. */
77
487
  async function get(id, options = {}) {
78
- await transport.require("card.get", options);
79
488
  return transport.requestParsed(Card, `/api/card/${id}`, { ...options });
80
489
  }
81
490
  /** Create a card — a question, a model, or a metric — from a full card body. */
82
491
  async function create(params, options = {}) {
83
- await transport.require("card.create", options);
84
492
  return transport.requestParsed(Card, "/api/card", {
85
493
  ...options,
86
494
  method: "POST",
@@ -89,7 +497,6 @@ function cardResource(transport) {
89
497
  }
90
498
  /** Update a card by id, patching only the fields the body carries. */
91
499
  async function update(id, params, options = {}) {
92
- await transport.require("card.update", options);
93
500
  return transport.requestParsed(Card, `/api/card/${id}`, {
94
501
  ...options,
95
502
  method: "PUT",
@@ -98,12 +505,10 @@ function cardResource(transport) {
98
505
  }
99
506
  /** Archive (soft-delete) a card by id. Metabase models this as an update, not its own endpoint. */
100
507
  async function archive(id, options = {}) {
101
- await transport.require("card.archive", options);
102
508
  return update(id, { archived: true }, options);
103
509
  }
104
510
  /** Run a saved card and return the query result envelope. */
105
511
  async function query(id, params, options = {}) {
106
- await transport.require("card.query", options);
107
512
  return transport.requestParsed(CardQueryResult, `/api/card/${id}/query`, {
108
513
  ...options,
109
514
  method: "POST",
@@ -119,9 +524,8 @@ function cardResource(transport) {
119
524
  * there before the wire.
120
525
  */
121
526
  async function exportQuery(id, format, params, options = {}) {
122
- await transport.require("card.exportQuery", options);
123
- await transport.requireFeatures(params.csv_include_bom ? ["exportCsvByteOrderMark"] : [], options);
124
527
  if (params.pivot_results) await assertPivotedExport(transport, format, options);
528
+ await transport.requireFeatures(params.csv_include_bom ? ["exportCsvByteOrderMark"] : [], options);
125
529
  const body = new URLSearchParams({
126
530
  parameters: JSON.stringify(params.parameters),
127
531
  format_rows: String(params.format_rows),
@@ -139,7 +543,6 @@ function cardResource(transport) {
139
543
  * snippets its query references, plus the card's own metadata for a model or a native query.
140
544
  */
141
545
  async function queryMetadata(id, options = {}) {
142
- await transport.require("card.queryMetadata", options);
143
546
  return transport.requestParsed(QueryMetadata, `/api/card/${id}/query_metadata`, { ...options });
144
547
  }
145
548
  return {
@@ -225,7 +628,6 @@ function collectionResource(transport) {
225
628
  * preset includes the Library collections that match it.
226
629
  */
227
630
  async function list(params = {}, options = {}) {
228
- await transport.require("collection.list", options);
229
631
  return {
230
632
  data: await transport.requestParsed(CollectionApiList, "/api/collection", {
231
633
  ...options,
@@ -236,12 +638,10 @@ function collectionResource(transport) {
236
638
  }
237
639
  /** Get one collection by id, entity id, or the `root`/`trash` alias. */
238
640
  async function get(ref, options = {}) {
239
- await transport.require("collection.get", options);
240
641
  return transport.requestParsed(Collection, `/api/collection/${refPath(ref)}`, { ...options });
241
642
  }
242
643
  /** Create a new collection. */
243
644
  async function create(params, options = {}) {
244
- await transport.require("collection.create", options);
245
645
  return transport.requestParsed(Collection, "/api/collection", {
246
646
  ...options,
247
647
  method: "POST",
@@ -254,7 +654,6 @@ function collectionResource(transport) {
254
654
  * moves a collection into the trash ignores `parent_id`.
255
655
  */
256
656
  async function update(ref, params, options = {}) {
257
- await transport.require("collection.update", options);
258
657
  return transport.requestParsed(Collection, `/api/collection/${refPath(ref)}`, {
259
658
  ...options,
260
659
  method: "PUT",
@@ -263,7 +662,6 @@ function collectionResource(transport) {
263
662
  }
264
663
  /** Archive (soft-delete) a collection. Metabase models this as an update, not its own endpoint. */
265
664
  async function archive(ref, options = {}) {
266
- await transport.require("collection.archive", options);
267
665
  return update(ref, { archived: true }, options);
268
666
  }
269
667
  /**
@@ -271,7 +669,6 @@ function collectionResource(transport) {
271
669
  * the caller consumes pages rather than a single list and decides how far to pull.
272
670
  */
273
671
  async function* itemPages(ref, params = {}, options = {}) {
274
- await transport.require("collection.itemPages", options);
275
672
  yield* paginatePages(transport, `/api/collection/${refPath(ref)}/items`, CollectionItem, {
276
673
  query: {
277
674
  models: params.models,
@@ -286,7 +683,6 @@ function collectionResource(transport) {
286
683
  }
287
684
  /** Fetch the collection hierarchy as a forest of nested nodes. */
288
685
  async function tree(params = {}, options = {}) {
289
- await transport.require("collection.tree", options);
290
686
  return {
291
687
  data: await transport.requestParsed(CollectionTreeApiList, "/api/collection/tree", {
292
688
  ...options,
@@ -306,6 +702,128 @@ function collectionResource(transport) {
306
702
  };
307
703
  }
308
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
309
827
  //#region ../client/src/resources/csv-upload.ts
310
828
  const CSV_CONTENT_TYPE = "text/csv";
311
829
  function buildCsvFormData(file) {
@@ -317,14 +835,13 @@ function buildCsvFormData(file) {
317
835
  //#region ../client/src/resources/content-translation.ts
318
836
  const CONTENT_TRANSLATION_PATH = "/api/ee/content-translation";
319
837
  function contentTranslationResource(transport) {
838
+ const { explain } = explainer(transport, "contentTranslation");
320
839
  /** Download the complete content translation dictionary as CSV. */
321
840
  async function download(options = {}) {
322
- await transport.require("contentTranslation.download", options);
323
841
  return transport.requestStream(`${CONTENT_TRANSLATION_PATH}/csv`, options);
324
842
  }
325
843
  /** Replace the complete content translation dictionary with the attached CSV. */
326
844
  async function upload(file, options = {}) {
327
- await transport.require("contentTranslation.upload", options);
328
845
  const form = buildCsvFormData(file);
329
846
  return transport.requestParsed(ContentTranslationUploadResult, `${CONTENT_TRANSLATION_PATH}/upload-dictionary`, {
330
847
  ...options,
@@ -333,8 +850,8 @@ function contentTranslationResource(transport) {
333
850
  });
334
851
  }
335
852
  return {
336
- download,
337
- upload
853
+ download: explain("download", download),
854
+ upload: explain("upload", upload)
338
855
  };
339
856
  }
340
857
  //#endregion
@@ -345,7 +862,6 @@ function dashboardResource(transport) {
345
862
  const card = cardResource(transport);
346
863
  /** List dashboards. `f` picks a server-side preset. */
347
864
  async function list(params = {}, options = {}) {
348
- await transport.require("dashboard.list", options);
349
865
  return {
350
866
  data: await transport.requestParsed(DashboardApiList, "/api/dashboard", {
351
867
  ...options,
@@ -356,7 +872,6 @@ function dashboardResource(transport) {
356
872
  }
357
873
  /** Get one dashboard by id, with its dashcards, tabs and parameters. */
358
874
  async function get(id, options = {}) {
359
- await transport.require("dashboard.get", options);
360
875
  return transport.requestParsed(DashboardDetail, `/api/dashboard/${id}`, { ...options });
361
876
  }
362
877
  /**
@@ -364,7 +879,6 @@ function dashboardResource(transport) {
364
879
  * puts content on a fresh dashboard.
365
880
  */
366
881
  async function create(params, options = {}) {
367
- await transport.require("dashboard.create", options);
368
882
  return transport.requestParsed(Dashboard, "/api/dashboard", {
369
883
  ...options,
370
884
  method: "POST",
@@ -376,7 +890,6 @@ function dashboardResource(transport) {
376
890
  * full replacements: whatever the body omits is deleted from the dashboard.
377
891
  */
378
892
  async function update(id, params, options = {}) {
379
- await transport.require("dashboard.update", options);
380
893
  return transport.requestParsed(DashboardDetail, `/api/dashboard/${id}`, {
381
894
  ...options,
382
895
  method: "PUT",
@@ -394,7 +907,6 @@ function dashboardResource(transport) {
394
907
  * is duplicated. Action, link and placeholder dashcards are not copied.
395
908
  */
396
909
  async function copy(id, params = {}, options = {}) {
397
- await transport.require("dashboard.copy", options);
398
910
  return transport.requestParsed(DashboardCopy, `/api/dashboard/${id}/copy`, {
399
911
  ...options,
400
912
  method: "POST",
@@ -407,7 +919,6 @@ function dashboardResource(transport) {
407
919
  * an archive is a state change on the dashboard itself, and its dashcards are not part of it.
408
920
  */
409
921
  async function archive(id, options = {}) {
410
- await transport.require("dashboard.archive", options);
411
922
  return transport.requestParsed(Dashboard, `/api/dashboard/${id}`, {
412
923
  ...options,
413
924
  method: "PUT",
@@ -420,7 +931,6 @@ function dashboardResource(transport) {
420
931
  * currently stands.
421
932
  */
422
933
  async function updateDashcard(dashboardId, dashcardId, params, options = {}) {
423
- await transport.require("dashboard.updateDashcard", options);
424
934
  const dashboard = await get(dashboardId, options);
425
935
  const target = dashboard.dashcards.find((dashcard) => dashcard.id === dashcardId);
426
936
  if (target === void 0) throw new ConfigError(`dashcard ${dashcardId} not found on dashboard ${dashboardId}`);
@@ -439,7 +949,6 @@ function dashboardResource(transport) {
439
949
  * any other way propagates as the failure it was.
440
950
  */
441
951
  async function checkCardReferences(dashcards, options = {}) {
442
- await transport.require("dashboard.checkCardReferences", options);
443
952
  const grouped = groupByCardId(collectCardReferences(dashcards));
444
953
  return (await Promise.all(Array.from(grouped.entries()).map(async ([cardId, references]) => ({
445
954
  references,
@@ -459,7 +968,7 @@ function dashboardResource(transport) {
459
968
  } catch (error) {
460
969
  if (!(error instanceof HttpError)) throw error;
461
970
  if (error.kind === "resource-missing") return { reason: "missing" };
462
- if (error.kind === "auth") return {
971
+ if (error.kind === "auth" || error.kind === "forbidden") return {
463
972
  reason: "unreadable",
464
973
  detail: error.userMessage
465
974
  };
@@ -470,7 +979,6 @@ function dashboardResource(transport) {
470
979
  * Fetch the selectable values for a dashboard parameter, through the chain-filter value endpoint.
471
980
  */
472
981
  async function parameterValues(dashboardId, parameterId, options = {}) {
473
- await transport.require("dashboard.parameterValues", options);
474
982
  return transport.requestParsed(ParameterValues, `/api/dashboard/${dashboardId}/params/${encodeURIComponent(parameterId)}/values`, { ...options });
475
983
  }
476
984
  /**
@@ -478,7 +986,6 @@ function dashboardResource(transport) {
478
986
  * substring, capped at the first 1000 matches.
479
987
  */
480
988
  async function searchParameterValues(dashboardId, parameterId, query, options = {}) {
481
- await transport.require("dashboard.searchParameterValues", options);
482
989
  const parameter = encodeURIComponent(parameterId);
483
990
  return transport.requestParsed(ParameterValues, `/api/dashboard/${dashboardId}/params/${parameter}/search/${encodeURIComponent(query)}`, { ...options });
484
991
  }
@@ -542,7 +1049,6 @@ const SYNC_COMPLETE = "complete";
542
1049
  function databaseResource(transport) {
543
1050
  /** List databases. `include` hydrates each database's tables; `saved` adds the Saved Questions virtual database. */
544
1051
  async function list(params = {}, options = {}) {
545
- await transport.require("database.list", options);
546
1052
  const response = await transport.requestParsed(DatabaseApiList, "/api/database", {
547
1053
  ...options,
548
1054
  query: {
@@ -557,7 +1063,6 @@ function databaseResource(transport) {
557
1063
  }
558
1064
  /** Get one database by id. `include` hydrates its tables, and `tables.fields` their fields too. */
559
1065
  async function get(id, params = {}, options = {}) {
560
- await transport.require("database.get", options);
561
1066
  return transport.requestParsed(Database, `/api/database/${id}`, {
562
1067
  ...options,
563
1068
  query: { include: params.include }
@@ -565,7 +1070,6 @@ function databaseResource(transport) {
565
1070
  }
566
1071
  /** List the schema names in a database. */
567
1072
  async function schemas(id, options = {}) {
568
- await transport.require("database.schemas", options);
569
1073
  return {
570
1074
  data: await transport.requestParsed(DatabaseApiSchemaList, `/api/database/${id}/schemas`, { ...options }),
571
1075
  total: null
@@ -573,7 +1077,6 @@ function databaseResource(transport) {
573
1077
  }
574
1078
  /** List the tables one schema of a database holds. */
575
1079
  async function schemaTables(id, schema, options = {}) {
576
- await transport.require("database.schemaTables", options);
577
1080
  return {
578
1081
  data: await transport.requestParsed(DatabaseApiSchemaTableList, `/api/database/${id}/schema/${encodeURIComponent(schema)}`, { ...options }),
579
1082
  total: null
@@ -584,7 +1087,6 @@ function databaseResource(transport) {
584
1087
  * pass `wait` to poll the database until it reports its initial sync complete.
585
1088
  */
586
1089
  async function syncSchema(id, params = {}, options = {}) {
587
- await transport.require("database.syncSchema", options);
588
1090
  const ack = await transport.requestParsed(DatabaseTaskAck, `/api/database/${id}/sync_schema`, {
589
1091
  ...options,
590
1092
  method: "POST"
@@ -606,7 +1108,6 @@ function databaseResource(transport) {
606
1108
  }
607
1109
  /** Trigger a rescan of the cached field values across a database. */
608
1110
  async function rescanValues(id, options = {}) {
609
- await transport.require("database.rescanValues", options);
610
1111
  return {
611
1112
  id,
612
1113
  status: (await transport.requestParsed(DatabaseTaskAck, `/api/database/${id}/rescan_values`, {
@@ -632,7 +1133,6 @@ function datasetResource(transport) {
632
1133
  * MBQL, or native — rather than a reference to a saved one, so nothing here is a card.
633
1134
  */
634
1135
  async function query(body, options = {}) {
635
- await transport.require("dataset.query", options);
636
1136
  return transport.requestParsed(CardQueryResult, "/api/dataset", {
637
1137
  ...options,
638
1138
  method: "POST",
@@ -644,9 +1144,7 @@ function datasetResource(transport) {
644
1144
  * defaults server-side to true.
645
1145
  */
646
1146
  async function native(datasetQuery, params = {}, options = {}) {
647
- await transport.require("dataset.native", options);
648
- const { features } = await transport.server(options);
649
- return transport.requestParsed(compiledQuerySchema(features), "/api/dataset/native", {
1147
+ return transport.requestParsed(CompiledQuery, "/api/dataset/native", {
650
1148
  ...options,
651
1149
  method: "POST",
652
1150
  body: {
@@ -661,7 +1159,6 @@ function datasetResource(transport) {
661
1159
  * widens the field filter.
662
1160
  */
663
1161
  async function queryMetadata(datasetQuery, options = {}) {
664
- await transport.require("dataset.queryMetadata", options);
665
1162
  return transport.requestParsed(QueryMetadata, "/api/dataset/query_metadata", {
666
1163
  ...options,
667
1164
  method: "POST",
@@ -680,9 +1177,8 @@ function datasetResource(transport) {
680
1177
  * there before the wire.
681
1178
  */
682
1179
  async function exportQuery(format, params, options = {}) {
683
- await transport.require("dataset.exportQuery", options);
684
- await transport.requireFeatures(params.csv_include_bom ? ["exportCsvByteOrderMark"] : [], options);
685
1180
  if (params.pivot_results) await assertPivotedExport(transport, format, options);
1181
+ await transport.requireFeatures(params.csv_include_bom ? ["exportCsvByteOrderMark"] : [], options);
686
1182
  const exported = params.pivot_results ? {
687
1183
  ...params.query,
688
1184
  "was-pivot": true
@@ -710,15 +1206,20 @@ function datasetResource(transport) {
710
1206
  //#region ../client/src/resources/dependency.ts
711
1207
  const DependencyNodeApiList = z.array(DependencyNode);
712
1208
  const DependencyEntityApiList = z.array(DependencyEntity);
1209
+ function graphTypeFeatures(type) {
1210
+ return type === "measure" ? [{
1211
+ feature: "measureDependencyGraph",
1212
+ fields: ["type"]
1213
+ }] : [];
1214
+ }
713
1215
  function dependencyResource(transport) {
1216
+ const { explain, explainWalk } = explainer(transport, "dependency");
714
1217
  /**
715
1218
  * The upstream dependency graph of one entity. `nodes` holds the starting entity plus every
716
1219
  * entity it depends on, directly or transitively; each edge runs from the dependent to what it
717
1220
  * depends on. A measure starts a graph only on a server with `measureDependencyGraph`.
718
1221
  */
719
1222
  async function graph(type, id, options = {}) {
720
- await transport.require("dependency.graph", options);
721
- if (type === "measure") await transport.requireFeatures(["measureDependencyGraph"], options);
722
1223
  return transport.requestParsed(DependencyGraph, "/api/ee/dependencies/graph", {
723
1224
  ...options,
724
1225
  query: {
@@ -734,7 +1235,6 @@ function dependencyResource(transport) {
734
1235
  * personal collections, and `sort-column` / `sort-direction` order the result.
735
1236
  */
736
1237
  async function dependents(type, id, params = {}, options = {}) {
737
- await transport.require("dependency.dependents", options);
738
1238
  return {
739
1239
  data: await transport.requestParsed(DependencyNodeApiList, "/api/ee/dependencies/graph/dependents", {
740
1240
  ...options,
@@ -760,7 +1260,6 @@ function dependencyResource(transport) {
760
1260
  * `sort-direction` order the result.
761
1261
  */
762
1262
  async function broken(type, id, params = {}, options = {}) {
763
- await transport.require("dependency.broken", options);
764
1263
  return {
765
1264
  data: await transport.requestParsed(DependencyEntityApiList, "/api/ee/dependencies/graph/broken", {
766
1265
  ...options,
@@ -787,7 +1286,6 @@ function dependencyResource(transport) {
787
1286
  * This endpoint pages on the server, so the caller consumes pages and decides how far to pull.
788
1287
  */
789
1288
  async function* unreferencedPages(params = {}, options = {}) {
790
- await transport.require("dependency.unreferencedPages", options);
791
1289
  if (queryLeavesNoKind(params)) {
792
1290
  yield {
793
1291
  items: [],
@@ -811,7 +1309,6 @@ function dependencyResource(transport) {
811
1309
  * caller consumes pages and decides how far to pull.
812
1310
  */
813
1311
  async function* breakingPages(params = {}, options = {}) {
814
- await transport.require("dependency.breakingPages", options);
815
1312
  if (queryLeavesNoKind(params)) {
816
1313
  yield {
817
1314
  items: [],
@@ -828,11 +1325,11 @@ function dependencyResource(transport) {
828
1325
  });
829
1326
  }
830
1327
  return {
831
- graph,
832
- dependents,
833
- broken,
834
- unreferencedPages,
835
- 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)
836
1333
  };
837
1334
  }
838
1335
  function queryLeavesNoKind(params) {
@@ -854,9 +1351,9 @@ function itemListQuery(params) {
854
1351
  //#region ../client/src/resources/document.ts
855
1352
  const DocumentApiList = z.object({ items: z.array(Document) }).loose();
856
1353
  function documentResource(transport) {
1354
+ const { explain } = explainer(transport, "document");
857
1355
  /** Get existing documents. */
858
1356
  async function list(options = {}) {
859
- await transport.require("document.list", options);
860
1357
  return {
861
1358
  data: (await transport.requestParsed(DocumentApiList, "/api/document", { ...options })).items,
862
1359
  total: null
@@ -864,12 +1361,10 @@ function documentResource(transport) {
864
1361
  }
865
1362
  /** Return an existing document by id. */
866
1363
  async function get(id, options = {}) {
867
- await transport.require("document.get", options);
868
1364
  return transport.requestParsed(Document, `/api/document/${id}`, { ...options });
869
1365
  }
870
1366
  /** Create a new document. */
871
1367
  async function create(params, options = {}) {
872
- await transport.require("document.create", options);
873
1368
  return transport.requestParsed(Document, "/api/document", {
874
1369
  ...options,
875
1370
  method: "POST",
@@ -878,7 +1373,6 @@ function documentResource(transport) {
878
1373
  }
879
1374
  /** Update an existing document by id. */
880
1375
  async function update(id, params, options = {}) {
881
- await transport.require("document.update", options);
882
1376
  return transport.requestParsed(Document, `/api/document/${id}`, {
883
1377
  ...options,
884
1378
  method: "PUT",
@@ -887,7 +1381,6 @@ function documentResource(transport) {
887
1381
  }
888
1382
  /** Archive (soft-delete) a document by id. Metabase models this as an update, not its own endpoint. */
889
1383
  async function archive(id, options = {}) {
890
- await transport.require("document.archive", options);
891
1384
  return update(id, { archived: true }, options);
892
1385
  }
893
1386
  /**
@@ -895,7 +1388,6 @@ function documentResource(transport) {
895
1388
  * it. An archived source is not found.
896
1389
  */
897
1390
  async function copy(id, params = {}, options = {}) {
898
- await transport.require("document.copy", options);
899
1391
  return transport.requestParsed(Document, `/api/document/${id}/copy`, {
900
1392
  ...options,
901
1393
  method: "POST",
@@ -908,7 +1400,7 @@ function documentResource(transport) {
908
1400
  create,
909
1401
  update,
910
1402
  archive,
911
- copy
1403
+ copy: explain("copy", copy)
912
1404
  };
913
1405
  }
914
1406
  //#endregion
@@ -920,7 +1412,6 @@ function eidTranslationResource(transport) {
920
1412
  * lookup succeeded — the numeric id.
921
1413
  */
922
1414
  async function translate(params, options = {}) {
923
- await transport.require("eidTranslation.translate", options);
924
1415
  return transport.requestParsed(EidTranslateResult, "/api/eid-translation/translate", {
925
1416
  ...options,
926
1417
  method: "POST",
@@ -974,6 +1465,7 @@ const Erd = z.object({
974
1465
  //#region ../client/src/resources/erd.ts
975
1466
  const ERD_PATH = "/api/ee/erd";
976
1467
  function erdResource(transport) {
1468
+ const { explain } = explainer(transport, "erd");
977
1469
  /**
978
1470
  * Get the entity relationship diagram of a database: the readable tables as nodes, each with its
979
1471
  * fields, and the foreign keys between them as edges. `table-ids` makes those tables the focal
@@ -981,7 +1473,6 @@ function erdResource(transport) {
981
1473
  * none.
982
1474
  */
983
1475
  async function get(params, options = {}) {
984
- await transport.require("erd.get", options);
985
1476
  return transport.requestParsed(Erd, ERD_PATH, {
986
1477
  ...options,
987
1478
  query: {
@@ -991,7 +1482,7 @@ function erdResource(transport) {
991
1482
  }
992
1483
  });
993
1484
  }
994
- return { get };
1485
+ return { get: explain("get", get) };
995
1486
  }
996
1487
  //#endregion
997
1488
  //#region ../client/src/resources/optional-parsed.ts
@@ -1009,12 +1500,12 @@ async function fetchOptionalParsed(client, path, schema, options = {}) {
1009
1500
  //#region ../client/src/resources/field.ts
1010
1501
  const FieldApiSummary = z.tuple([z.tuple([z.literal("count"), z.number().int()]), z.tuple([z.literal("distincts"), z.number().int()])]);
1011
1502
  function fieldResource(transport) {
1503
+ const { refuse } = explainer(transport, "field");
1012
1504
  /**
1013
1505
  * Get one field by id. A server that labels data sensitivity answers `data_sensitivity`, `null`
1014
1506
  * for an unlabelled field; any other answers the field without the key (`hasDataSensitivity`).
1015
1507
  */
1016
1508
  async function get(id, options = {}) {
1017
- await transport.require("field.get", options);
1018
1509
  const { features } = await transport.server(options);
1019
1510
  return transport.requestParsed(fieldDetailSchema(features), `/api/field/${id}`, { ...options });
1020
1511
  }
@@ -1025,7 +1516,6 @@ function fieldResource(transport) {
1025
1516
  * the label is refused there before the wire. Answers the field the way `get` does.
1026
1517
  */
1027
1518
  async function update(id, params, options = {}) {
1028
- await transport.require("field.update", options);
1029
1519
  await transport.requireFeatures(params.data_sensitivity === void 0 ? [] : ["fieldDataSensitivity"], options);
1030
1520
  const { features } = await transport.server(options);
1031
1521
  return transport.requestParsed(fieldDetailSchema(features), `/api/field/${id}`, {
@@ -1036,11 +1526,11 @@ function fieldResource(transport) {
1036
1526
  }
1037
1527
  /**
1038
1528
  * Label a field's data sensitivity by hand. A person's label is never overwritten by the server's
1039
- * classifier; `null` withdraws it, so whatever label the classifier wrote shows again. Answers
1040
- * 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.
1041
1532
  */
1042
1533
  async function setDataSensitivity(id, params, options = {}) {
1043
- await transport.require("field.setDataSensitivity", options);
1044
1534
  return transport.requestParsed(FieldWithDataSensitivity, `/api/field/${id}`, {
1045
1535
  ...options,
1046
1536
  method: "PUT",
@@ -1056,7 +1546,6 @@ function fieldResource(transport) {
1056
1546
  * `value` against the display value, ignoring both `searchId` and `limit`.
1057
1547
  */
1058
1548
  async function search(id, searchId, params, options = {}) {
1059
- await transport.require("field.search", options);
1060
1549
  return transport.requestParsed(FieldSearchMatches, `/api/field/${id}/search/${searchId}`, {
1061
1550
  ...options,
1062
1551
  query: {
@@ -1071,7 +1560,6 @@ function fieldResource(transport) {
1071
1560
  * `value` and ignores any text after it, refusing only a `value` that has none.
1072
1561
  */
1073
1562
  async function remapping(id, remappedId, params, options = {}) {
1074
- await transport.require("field.remapping", options);
1075
1563
  const { features } = await transport.server(options);
1076
1564
  return fetchOptionalParsed(transport, `/api/field/${id}/remapping/${remappedId}`, fieldRemappedValueSchema(features, id === remappedId), {
1077
1565
  ...options,
@@ -1080,7 +1568,6 @@ function fieldResource(transport) {
1080
1568
  }
1081
1569
  /** Get the row count and the distinct-value count for a field. */
1082
1570
  async function summary(id, options = {}) {
1083
- await transport.require("field.summary", options);
1084
1571
  const [[, count], [, distincts]] = await transport.requestParsed(FieldApiSummary, `/api/field/${id}/summary`, { ...options });
1085
1572
  return {
1086
1573
  field_id: id,
@@ -1090,13 +1577,12 @@ function fieldResource(transport) {
1090
1577
  }
1091
1578
  /** Get the cached distinct values Metabase holds for a field. */
1092
1579
  async function values(id, options = {}) {
1093
- await transport.require("field.values", options);
1094
1580
  return transport.requestParsed(FieldValues, `/api/field/${id}/values`, { ...options });
1095
1581
  }
1096
1582
  return {
1097
1583
  get,
1098
1584
  update,
1099
- setDataSensitivity,
1585
+ setDataSensitivity: refuse("setDataSensitivity", setDataSensitivity),
1100
1586
  search,
1101
1587
  remapping,
1102
1588
  summary,
@@ -1128,22 +1614,22 @@ const SyncStashStarted = z.object({
1128
1614
  task_id: z.number().int().positive()
1129
1615
  });
1130
1616
  const RemoteSyncSetting = z.string().nullable();
1131
- const RemoteSyncBranchProperty = z.object({ "remote-sync-branch": z.string().nullable().optional() });
1132
- const FORBIDDEN_STATUS = 403;
1133
- 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";
1134
1621
  function isRemoteUnreadable(error) {
1135
1622
  if (!(error instanceof HttpError)) return false;
1136
- return error.status === FORBIDDEN_STATUS || error.status === UNREGISTERED_STATUS;
1623
+ return error.status === 403 || error.status === 404;
1137
1624
  }
1138
1625
  function gitSyncResource(transport) {
1626
+ const { explain, refuse, refuseAfterReading } = explainer(transport, "gitSync");
1139
1627
  /** Get the running or most recently finished sync task, or null when the server has none. */
1140
1628
  async function currentTask(options = {}) {
1141
- await transport.require("gitSync.currentTask", options);
1142
1629
  return fetchOptionalParsed(transport, "/api/ee/remote-sync/current-task", SyncTask, options);
1143
1630
  }
1144
1631
  /** Request cancellation of the running sync task, and answer it in the state that left it. */
1145
1632
  async function cancelTask(options = {}) {
1146
- await transport.require("gitSync.cancelTask", options);
1147
1633
  return transport.requestParsed(SyncTask, "/api/ee/remote-sync/current-task/cancel", {
1148
1634
  ...options,
1149
1635
  method: "POST"
@@ -1151,12 +1637,10 @@ function gitSyncResource(transport) {
1151
1637
  }
1152
1638
  /** Whether Metabase holds content changes the remote has not been told about. */
1153
1639
  async function isDirty(options = {}) {
1154
- await transport.require("gitSync.isDirty", options);
1155
1640
  return (await transport.requestParsed(SyncDirtyFlag, "/api/ee/remote-sync/is-dirty", { ...options })).is_dirty;
1156
1641
  }
1157
1642
  /** List the objects whose local state differs from the remote. */
1158
1643
  async function dirty(options = {}) {
1159
- await transport.require("gitSync.dirty", options);
1160
1644
  return {
1161
1645
  data: (await transport.requestParsed(SyncDirtyList, "/api/ee/remote-sync/dirty", { ...options })).dirty,
1162
1646
  total: null
@@ -1167,7 +1651,6 @@ function gitSyncResource(transport) {
1167
1651
  * comparison; `force-refresh` re-reads the remote instead.
1168
1652
  */
1169
1653
  async function hasRemoteChanges(params = {}, options = {}) {
1170
- await transport.require("gitSync.hasRemoteChanges", options);
1171
1654
  return transport.requestParsed(SyncRemoteChanges, "/api/ee/remote-sync/has-remote-changes", {
1172
1655
  ...options,
1173
1656
  query: { "force-refresh": params["force-refresh"] }
@@ -1178,14 +1661,16 @@ function gitSyncResource(transport) {
1178
1661
  * pass `wait` to poll that task until it reaches a terminal status. A server already up to date
1179
1662
  * answers no task id, and there is then nothing to poll. A server that guards the tracked branch
1180
1663
  * refuses with 409 when `expected_branch` disagrees with the setting; left out, it is the tracked
1181
- * branch as read just before the request. `merge` folds the remote's changes into local content
1182
- * 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
1183
1669
  * instance.
1184
1670
  */
1185
1671
  async function importFromRemote(params = {}, options = {}) {
1186
- await transport.require("gitSync.import", options);
1672
+ const expectedBranch = await expectedBranchFor(params.expected_branch, options);
1187
1673
  await requireMerge(params.merge, options);
1188
- const expectedBranch = await guardedBranch(params.expected_branch, options);
1189
1674
  const started = await transport.requestParsed(SyncImportStarted, "/api/ee/remote-sync/import", {
1190
1675
  ...options,
1191
1676
  method: "POST",
@@ -1207,28 +1692,38 @@ function gitSyncResource(transport) {
1207
1692
  final: await settle(params.wait, options)
1208
1693
  };
1209
1694
  }
1210
- async function requireMerge(merge, options) {
1211
- 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));
1212
1698
  }
1213
- async function guardedBranch(given, options) {
1699
+ async function exportBranchFor(given, options) {
1214
1700
  if (given !== void 0) return given;
1215
- const { features } = await transport.server(options);
1216
- if (!features.remoteSyncBranchGuard) return;
1217
- 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);
1218
1712
  }
1219
1713
  /**
1220
1714
  * Export Metabase's content to the remote. The endpoint queues a task and returns at once; pass
1221
1715
  * `wait` to poll that task until it reaches a terminal status. A server that guards the tracked
1222
- * branch refuses with 409 when `branch` is not the tracked one; left out, it is the tracked branch
1223
- * as read just before the request there, and the server's own default elsewhere. When the remote
1224
- * has moved past the last sync, a plain export ends in a `conflict` task where `merge` is
1225
- * available, and is refused with 400 before any task elsewhere; `merge` folds the remote's changes
1226
- * 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.
1227
1723
  */
1228
1724
  async function exportToRemote(params = {}, options = {}) {
1229
- await transport.require("gitSync.export", options);
1725
+ const branchName = await exportBranchFor(params.branch, options);
1230
1726
  await requireMerge(params.merge, options);
1231
- const branchName = await guardedBranch(params.branch, options);
1232
1727
  const started = await transport.requestParsed(SyncExportStarted, "/api/ee/remote-sync/export", {
1233
1728
  ...options,
1234
1729
  method: "POST",
@@ -1256,7 +1751,6 @@ function gitSyncResource(transport) {
1256
1751
  * otherwise.
1257
1752
  */
1258
1753
  async function exportPreflight(params, options = {}) {
1259
- await transport.require("gitSync.exportPreflight", options);
1260
1754
  return transport.requestParsed(SyncExportPreflight, "/api/ee/remote-sync/export-preflight", {
1261
1755
  ...options,
1262
1756
  query: { branch: params.branch }
@@ -1267,7 +1761,6 @@ function gitSyncResource(transport) {
1267
1761
  * queues a task and returns at once; pass `wait` to poll that task to a terminal status.
1268
1762
  */
1269
1763
  async function stash(params, options = {}) {
1270
- await transport.require("gitSync.stash", options);
1271
1764
  const started = await transport.requestParsed(SyncStashStarted, "/api/ee/remote-sync/stash", {
1272
1765
  ...options,
1273
1766
  method: "POST",
@@ -1290,7 +1783,6 @@ function gitSyncResource(transport) {
1290
1783
  }
1291
1784
  /** List the branches the configured remote carries. */
1292
1785
  async function branches(options = {}) {
1293
- await transport.require("gitSync.branches", options);
1294
1786
  return {
1295
1787
  data: (await transport.requestParsed(SyncBranchList, "/api/ee/remote-sync/branches", { ...options })).items,
1296
1788
  total: null
@@ -1298,7 +1790,6 @@ function gitSyncResource(transport) {
1298
1790
  }
1299
1791
  /** Create a branch on the remote and point git-sync at it. */
1300
1792
  async function createBranch(params, options = {}) {
1301
- await transport.require("gitSync.createBranch", options);
1302
1793
  return transport.requestParsed(SyncBranchCreated, "/api/ee/remote-sync/create-branch", {
1303
1794
  ...options,
1304
1795
  method: "POST",
@@ -1307,19 +1798,42 @@ function gitSyncResource(transport) {
1307
1798
  }
1308
1799
  /**
1309
1800
  * Mark one collection as git-synced, or unmark it. The server cascades the flag to descendants
1310
- * 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.
1311
1807
  */
1312
1808
  async function setCollectionSynced(collectionId, synced, options = {}) {
1313
- await transport.require("gitSync.setCollectionSynced", options);
1314
1809
  return transport.requestParsed(SyncSettingsUpdateResult, "/api/ee/remote-sync/settings", {
1315
1810
  ...options,
1316
1811
  method: "PUT",
1317
- body: { collections: { [collectionId]: synced } }
1812
+ body: await settingsUpdateBody({ [collectionId]: synced }, options)
1318
1813
  });
1319
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
+ }
1320
1835
  /** The collections currently in git-sync's scope. */
1321
1836
  async function syncedCollections(options = {}) {
1322
- await transport.require("gitSync.syncedCollections", options);
1323
1837
  return {
1324
1838
  data: (await listCollectionsAs(transport, SyncScopeCollection, options)).filter((entry) => entry.is_remote_synced === true),
1325
1839
  total: null
@@ -1327,7 +1841,6 @@ function gitSyncResource(transport) {
1327
1841
  }
1328
1842
  /** The remote's URL, or null when none is configured or the caller may not read it. */
1329
1843
  async function remoteUrl(options = {}) {
1330
- await transport.require("gitSync.remoteUrl", options);
1331
1844
  try {
1332
1845
  const url = await fetchOptionalParsed(transport, "/api/setting/remote-sync-url", RemoteSyncSetting, options);
1333
1846
  return url === "" ? null : url;
@@ -1337,21 +1850,22 @@ function gitSyncResource(transport) {
1337
1850
  }
1338
1851
  }
1339
1852
  /**
1340
- * The branch git-sync tracks, or null when none is configured. Refuses when the caller may not
1341
- * read the setting (it is admin-only) or the server has no remote-sync module, rather than
1342
- * 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.
1343
1856
  */
1344
- async function branch(options = {}) {
1345
- await transport.require("gitSync.branch", options);
1346
- const tracked = (await transport.requestParsed(RemoteSyncBranchProperty, PROBE_PATH, { ...options }))["remote-sync-branch"];
1347
- 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);
1348
1860
  return tracked;
1349
1861
  }
1350
- /** The branch git-sync tracks, refused when none is configured or the caller may not read it. */
1351
- async function trackedBranch(options = {}) {
1352
- await transport.require("gitSync.trackedBranch", options);
1353
- const tracked = await branch(options);
1354
- 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);
1355
1869
  return tracked;
1356
1870
  }
1357
1871
  /**
@@ -1359,7 +1873,6 @@ function gitSyncResource(transport) {
1359
1873
  * answers null, which is terminal in the same sense: nothing further will happen.
1360
1874
  */
1361
1875
  async function waitForTask(wait, options = {}) {
1362
- await transport.require("gitSync.waitForTask", options);
1363
1876
  return settle(wait, options);
1364
1877
  }
1365
1878
  async function settle(wait, options) {
@@ -1369,25 +1882,38 @@ function gitSyncResource(transport) {
1369
1882
  }), (task) => task === null || isSyncTaskTerminal(task.status), wait);
1370
1883
  }
1371
1884
  return {
1372
- currentTask,
1373
- cancelTask,
1374
- isDirty,
1375
- dirty,
1376
- hasRemoteChanges,
1377
- import: importFromRemote,
1378
- export: exportToRemote,
1379
- exportPreflight,
1380
- stash,
1381
- branches,
1382
- createBranch,
1383
- setCollectionSynced,
1384
- 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),
1385
1898
  remoteUrl,
1386
- branch,
1387
- trackedBranch,
1388
- waitForTask
1899
+ branch: refuseAfterReading("branch", RemoteSyncBranchProperty, branch),
1900
+ trackedBranch: refuseAfterReading("trackedBranch", RemoteSyncBranchProperty, trackedBranch),
1901
+ waitForTask: explain("waitForTask", waitForTask)
1389
1902
  };
1390
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
+ }
1391
1917
  //#endregion
1392
1918
  //#region ../client/src/resources/glossary.ts
1393
1919
  const GlossaryApiList = z.object({ data: z.array(Glossary) }).loose();
@@ -1397,7 +1923,6 @@ function glossaryResource(transport) {
1397
1923
  * contains it, case-insensitively.
1398
1924
  */
1399
1925
  async function list(params = {}, options = {}) {
1400
- await transport.require("glossary.list", options);
1401
1926
  return {
1402
1927
  data: (await transport.requestParsed(GlossaryApiList, "/api/glossary", {
1403
1928
  ...options,
@@ -1408,7 +1933,6 @@ function glossaryResource(transport) {
1408
1933
  }
1409
1934
  /** Create a glossary entry, a term and its definition. Terms are unique. */
1410
1935
  async function create(params, options = {}) {
1411
- await transport.require("glossary.create", options);
1412
1936
  return transport.requestParsed(Glossary, "/api/glossary", {
1413
1937
  ...options,
1414
1938
  method: "POST",
@@ -1417,7 +1941,6 @@ function glossaryResource(transport) {
1417
1941
  }
1418
1942
  /** Replace the term and definition of a glossary entry by id. */
1419
1943
  async function update(id, params, options = {}) {
1420
- await transport.require("glossary.update", options);
1421
1944
  return transport.requestParsed(Glossary, `/api/glossary/${id}`, {
1422
1945
  ...options,
1423
1946
  method: "PUT",
@@ -1426,7 +1949,6 @@ function glossaryResource(transport) {
1426
1949
  }
1427
1950
  /** Delete a glossary entry by id. */
1428
1951
  async function remove(id, options = {}) {
1429
- await transport.require("glossary.delete", options);
1430
1952
  await transport.requestRaw(`/api/glossary/${id}`, {
1431
1953
  ...options,
1432
1954
  method: "DELETE",
@@ -1459,9 +1981,9 @@ const UNPUBLISH_TABLES_PATH = "/api/ee/data-studio/table/unpublish-tables";
1459
1981
  const LIBRARY_DATA_TYPE = "library-data";
1460
1982
  const PublishTablesResponse = z.object({ target_collection: Collection.nullable() });
1461
1983
  function libraryResource(transport) {
1984
+ const { explain } = explainer(transport, "library");
1462
1985
  /** Get the Library root and its child collections, or `null` on an instance that has none. */
1463
1986
  async function get(options = {}) {
1464
- await transport.require("library.get", options);
1465
1987
  const { features } = await transport.server(options);
1466
1988
  const wire = await transport.requestParsed(libraryWireSchema(features), LIBRARY_ROOT_PATH, { ...options });
1467
1989
  if (wire === null) return null;
@@ -1480,7 +2002,6 @@ function libraryResource(transport) {
1480
2002
  * back from a refetch, which together make this idempotent.
1481
2003
  */
1482
2004
  async function create(options = {}) {
1483
- await transport.require("library.create", options);
1484
2005
  const existing = await get(options);
1485
2006
  if (existing !== null) return existing;
1486
2007
  await transport.requestRaw(LIBRARY_ROOT_PATH, {
@@ -1493,7 +2014,6 @@ function libraryResource(transport) {
1493
2014
  }
1494
2015
  /** The id of the Library's Data collection, creating the Library first when it does not exist. */
1495
2016
  async function ensureDataCollectionId(options = {}) {
1496
- await transport.require("library.ensureDataCollectionId", options);
1497
2017
  const data = (await create(options)).effective_children.find((child) => child.type === LIBRARY_DATA_TYPE);
1498
2018
  if (data === void 0) throw new Error("Library has no Data collection to publish into");
1499
2019
  if (typeof data.id !== "number") throw new Error(`Library Data collection has a non-numeric id ${String(data.id)}`);
@@ -1506,7 +2026,6 @@ function libraryResource(transport) {
1506
2026
  * `LibraryPublishTablesInput` is refused before any request.
1507
2027
  */
1508
2028
  async function publishTables(params, options = {}) {
1509
- await transport.require("library.publishTables", options);
1510
2029
  const body = parseRequestBody(LibraryPublishTablesInput, params, "tables to publish");
1511
2030
  return (await transport.requestParsed(PublishTablesResponse, PUBLISH_TABLES_PATH, {
1512
2031
  ...options,
@@ -1520,7 +2039,6 @@ function libraryResource(transport) {
1520
2039
  * table, so selectors that do not match `TableSelectors` are refused before any request.
1521
2040
  */
1522
2041
  async function unpublishTables(params, options = {}) {
1523
- await transport.require("library.unpublishTables", options);
1524
2042
  const body = parseRequestBody(TableSelectors, params, "table selectors");
1525
2043
  await transport.requestRaw(UNPUBLISH_TABLES_PATH, {
1526
2044
  ...options,
@@ -1530,20 +2048,20 @@ function libraryResource(transport) {
1530
2048
  });
1531
2049
  }
1532
2050
  return {
1533
- get,
1534
- create,
1535
- ensureDataCollectionId,
1536
- publishTables,
1537
- 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)
1538
2056
  };
1539
2057
  }
1540
2058
  //#endregion
1541
2059
  //#region ../client/src/resources/measure.ts
1542
2060
  const MeasureApiList = z.array(Measure);
1543
2061
  function measureResource(transport) {
2062
+ const { explain } = explainer(transport, "measure");
1544
2063
  /** List measures. */
1545
2064
  async function list(options = {}) {
1546
- await transport.require("measure.list", options);
1547
2065
  return {
1548
2066
  data: await transport.requestParsed(MeasureApiList, "/api/measure", { ...options }),
1549
2067
  total: null
@@ -1551,12 +2069,10 @@ function measureResource(transport) {
1551
2069
  }
1552
2070
  /** Get one measure by id. */
1553
2071
  async function get(id, options = {}) {
1554
- await transport.require("measure.get", options);
1555
2072
  return transport.requestParsed(Measure, `/api/measure/${id}`, { ...options });
1556
2073
  }
1557
2074
  /** Create a measure — a saved aggregation tied to a table — from a full measure body. */
1558
2075
  async function create(params, options = {}) {
1559
- await transport.require("measure.create", options);
1560
2076
  return transport.requestParsed(Measure, "/api/measure", {
1561
2077
  ...options,
1562
2078
  method: "POST",
@@ -1568,7 +2084,6 @@ function measureResource(transport) {
1568
2084
  * required and lands in the audit log.
1569
2085
  */
1570
2086
  async function update(id, params, options = {}) {
1571
- await transport.require("measure.update", options);
1572
2087
  return transport.requestParsed(Measure, `/api/measure/${id}`, {
1573
2088
  ...options,
1574
2089
  method: "PUT",
@@ -1580,18 +2095,17 @@ function measureResource(transport) {
1580
2095
  * so it carries the same required `revision_message`.
1581
2096
  */
1582
2097
  async function archive(id, params, options = {}) {
1583
- await transport.require("measure.archive", options);
1584
2098
  return update(id, {
1585
2099
  archived: true,
1586
2100
  revision_message: params.revision_message
1587
2101
  }, options);
1588
2102
  }
1589
2103
  return {
1590
- list,
1591
- get,
1592
- create,
1593
- update,
1594
- 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)
1595
2109
  };
1596
2110
  }
1597
2111
  //#endregion
@@ -1732,12 +2246,12 @@ const MetricDimensionListing = z.object({
1732
2246
  //#endregion
1733
2247
  //#region ../client/src/resources/metric.ts
1734
2248
  function metricResource(transport) {
2249
+ const { explain } = explainer(transport, "metric");
1735
2250
  /**
1736
2251
  * Run a metric definition and return the query result envelope: one metric or measure, or an
1737
2252
  * arithmetic expression over several, each leaf filtered and projected on its own `lib/uuid`.
1738
2253
  */
1739
2254
  async function query(definition, options = {}) {
1740
- await transport.require("metric.query", options);
1741
2255
  return transport.requestParsed(CardQueryResult, "/api/metric/dataset", {
1742
2256
  ...options,
1743
2257
  method: "POST",
@@ -1749,7 +2263,6 @@ function metricResource(transport) {
1749
2263
  * Takes the same definition `query` does.
1750
2264
  */
1751
2265
  async function breakoutValues(definition, options = {}) {
1752
- await transport.require("metric.breakoutValues", options);
1753
2266
  return transport.requestParsed(MetricBreakoutValues, "/api/metric/breakout-values", {
1754
2267
  ...options,
1755
2268
  method: "POST",
@@ -1761,7 +2274,6 @@ function metricResource(transport) {
1761
2274
  * add, grouped by source table. `query` filters both by a name substring.
1762
2275
  */
1763
2276
  async function dimensions(id, params = {}, options = {}) {
1764
- await transport.require("metric.dimensions", options);
1765
2277
  return transport.requestParsed(MetricDimensionListing, `/api/metric/${id}/dimension`, {
1766
2278
  ...options,
1767
2279
  query: {
@@ -1772,14 +2284,15 @@ function metricResource(transport) {
1772
2284
  });
1773
2285
  }
1774
2286
  return {
1775
- query,
1776
- breakoutValues,
1777
- dimensions
2287
+ query: explain("query", query),
2288
+ breakoutValues: explain("breakoutValues", breakoutValues),
2289
+ dimensions: explain("dimensions", dimensions)
1778
2290
  };
1779
2291
  }
1780
2292
  //#endregion
1781
2293
  //#region ../client/src/resources/moderation-review.ts
1782
2294
  function moderationReviewResource(transport) {
2295
+ const { explain } = explainer(transport, "moderationReview");
1783
2296
  /**
1784
2297
  * Create a moderation review on a card or dashboard. `status: "verified"` marks the item
1785
2298
  * verified, `null` or an absent status records a note that leaves it unverified. The new review
@@ -1787,7 +2300,6 @@ function moderationReviewResource(transport) {
1787
2300
  * A body that does not match `ModerationReviewCreateInput` is refused before any request.
1788
2301
  */
1789
2302
  async function create(params, options = {}) {
1790
- await transport.require("moderationReview.create", options);
1791
2303
  const body = parseRequestBody(ModerationReviewCreateInput, params, "moderation review");
1792
2304
  return transport.requestParsed(ModerationReview, "/api/moderation-review", {
1793
2305
  ...options,
@@ -1795,7 +2307,7 @@ function moderationReviewResource(transport) {
1795
2307
  body
1796
2308
  });
1797
2309
  }
1798
- return { create };
2310
+ return { create: explain("create", create) };
1799
2311
  }
1800
2312
  //#endregion
1801
2313
  //#region ../client/src/resources/notification.ts
@@ -1826,7 +2338,6 @@ function notificationResource(transport) {
1826
2338
  * the request pins the card payload type.
1827
2339
  */
1828
2340
  async function list(params = {}, options = {}) {
1829
- await transport.require("notification.list", options);
1830
2341
  return {
1831
2342
  data: await transport.requestParsed(NotificationApiList, "/api/notification", {
1832
2343
  ...options,
@@ -1843,12 +2354,10 @@ function notificationResource(transport) {
1843
2354
  }
1844
2355
  /** Get one question alert by id, refusing an id that names a system-event notification. */
1845
2356
  async function get(id, options = {}) {
1846
- await transport.require("notification.get", options);
1847
2357
  return assertCardAlert(await transport.requestParsed(Notification, `/api/notification/${id}`, { ...options }));
1848
2358
  }
1849
2359
  /** Create a question alert — its card payload, schedules and handlers — from a full body. */
1850
2360
  async function create(params, options = {}) {
1851
- await transport.require("notification.create", options);
1852
2361
  return transport.requestParsed(Notification, "/api/notification", {
1853
2362
  ...options,
1854
2363
  method: "POST",
@@ -1857,7 +2366,6 @@ function notificationResource(transport) {
1857
2366
  }
1858
2367
  /** Update a question alert by id, merging the patch over the stored notification. */
1859
2368
  async function update(id, params, options = {}) {
1860
- await transport.require("notification.update", options);
1861
2369
  const current = await get(id, options);
1862
2370
  return transport.requestParsed(Notification, `/api/notification/${id}`, {
1863
2371
  ...options,
@@ -1867,7 +2375,6 @@ function notificationResource(transport) {
1867
2375
  }
1868
2376
  /** Archive a question alert by id, stopping every delivery. Metabase models this as an update. */
1869
2377
  async function archive(id, options = {}) {
1870
- await transport.require("notification.archive", options);
1871
2378
  return update(id, { active: false }, options);
1872
2379
  }
1873
2380
  /**
@@ -1875,7 +2382,6 @@ function notificationResource(transport) {
1875
2382
  * body.
1876
2383
  */
1877
2384
  async function send(id, options = {}) {
1878
- await transport.require("notification.send", options);
1879
2385
  await get(id, options);
1880
2386
  await transport.requestRaw(`/api/notification/${id}/send`, {
1881
2387
  ...options,
@@ -1935,7 +2441,6 @@ function permissionResource(transport) {
1935
2441
  * one database is the question.
1936
2442
  */
1937
2443
  async function graph(options = {}) {
1938
- await transport.require("permission.graph", options);
1939
2444
  return transport.requestParsed(PermissionsGraph, "/api/permissions/graph", { ...options });
1940
2445
  }
1941
2446
  /**
@@ -1944,12 +2449,10 @@ function permissionResource(transport) {
1944
2449
  * group up under the database and read `view-data` and `create-queries` down to the table id.
1945
2450
  */
1946
2451
  async function databaseGraph(id, options = {}) {
1947
- await transport.require("permission.databaseGraph", options);
1948
2452
  return transport.requestParsed(PermissionsGraph, `/api/permissions/graph/db/${id}`, { ...options });
1949
2453
  }
1950
2454
  /** Get the data permissions of one group on every database. Admin only. */
1951
2455
  async function groupGraph(id, options = {}) {
1952
- await transport.require("permission.groupGraph", options);
1953
2456
  return transport.requestParsed(PermissionsGraph, `/api/permissions/graph/group/${id}`, { ...options });
1954
2457
  }
1955
2458
  return {
@@ -1971,7 +2474,6 @@ function mergePulseUpdate(current, patch) {
1971
2474
  function pulseResource(transport) {
1972
2475
  /** List dashboard subscriptions, optionally narrowed to one dashboard or to the archived ones. */
1973
2476
  async function list(params = {}, options = {}) {
1974
- await transport.require("pulse.list", options);
1975
2477
  return {
1976
2478
  data: await transport.requestParsed(PulseApiList, "/api/pulse", {
1977
2479
  ...options,
@@ -1985,21 +2487,27 @@ function pulseResource(transport) {
1985
2487
  }
1986
2488
  /** Get one dashboard subscription by id. */
1987
2489
  async function get(id, options = {}) {
1988
- await transport.require("pulse.get", options);
1989
2490
  return transport.requestParsed(Pulse, `/api/pulse/${id}`, { ...options });
1990
2491
  }
1991
- /** 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
+ */
1992
2497
  async function create(params, options = {}) {
1993
- await transport.require("pulse.create", options);
2498
+ await requireSubscriptionFilters(params.parameters, options);
1994
2499
  return transport.requestParsed(Pulse, "/api/pulse", {
1995
2500
  ...options,
1996
2501
  method: "POST",
1997
2502
  body: params
1998
2503
  });
1999
2504
  }
2000
- /** 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
+ */
2001
2509
  async function update(id, params, options = {}) {
2002
- await transport.require("pulse.update", options);
2510
+ await requireSubscriptionFilters(params.parameters, options);
2003
2511
  const current = await get(id, options);
2004
2512
  return transport.requestParsed(Pulse, `/api/pulse/${id}`, {
2005
2513
  ...options,
@@ -2012,9 +2520,11 @@ function pulseResource(transport) {
2012
2520
  * update, and disables each of the subscription's channels as a side effect.
2013
2521
  */
2014
2522
  async function archive(id, options = {}) {
2015
- await transport.require("pulse.archive", options);
2016
2523
  return update(id, { archived: true }, options);
2017
2524
  }
2525
+ async function requireSubscriptionFilters(parameters, options) {
2526
+ if (parameters !== void 0 && parameters.length > 0) await transport.requireFeatures(["dashboardSubscriptionFilters"], options);
2527
+ }
2018
2528
  return {
2019
2529
  list,
2020
2530
  get,
@@ -2117,6 +2627,7 @@ const ReplacementRunStarted = z.object({ run_id: z.number().int().positive() });
2117
2627
  const ReplacementApiRunList = z.array(ReplacementRun);
2118
2628
  const CancelRunResponse = z.object({ success: z.literal(true) });
2119
2629
  function replacementResource(transport) {
2630
+ const { explain } = explainer(transport, "replacement");
2120
2631
  /**
2121
2632
  * Check whether every usage of a source entity could be rewritten to read a target entity
2122
2633
  * instead: the two must share a database, the target must not depend on the source, every source
@@ -2124,7 +2635,6 @@ function replacementResource(transport) {
2124
2635
  * implicitly or sandboxed. Admins only.
2125
2636
  */
2126
2637
  async function checkReplaceSource(params, options = {}) {
2127
- await transport.require("replacement.checkReplaceSource", options);
2128
2638
  return transport.requestParsed(ReplacementCheck, "/api/ee/replacement/check-replace-source", {
2129
2639
  ...options,
2130
2640
  method: "POST",
@@ -2137,7 +2647,6 @@ function replacementResource(transport) {
2137
2647
  * while another replacement is running. Admins only.
2138
2648
  */
2139
2649
  async function replaceSource(params, options = {}) {
2140
- await transport.require("replacement.replaceSource", options);
2141
2650
  return transport.requestParsed(ReplacementRunStarted, "/api/ee/replacement/replace-source", {
2142
2651
  ...options,
2143
2652
  method: "POST",
@@ -2152,7 +2661,6 @@ function replacementResource(transport) {
2152
2661
  * replacement is running. Admins only.
2153
2662
  */
2154
2663
  async function replaceModelWithTransform(params, options = {}) {
2155
- await transport.require("replacement.replaceModelWithTransform", options);
2156
2664
  return transport.requestParsed(ReplacementRunStarted, "/api/ee/replacement/replace-model-with-transform", {
2157
2665
  ...options,
2158
2666
  method: "POST",
@@ -2164,7 +2672,6 @@ function replacementResource(transport) {
2164
2672
  * `false` only the ended ones. Admins only.
2165
2673
  */
2166
2674
  async function listRuns(params = {}, options = {}) {
2167
- await transport.require("replacement.listRuns", options);
2168
2675
  return {
2169
2676
  data: await transport.requestParsed(ReplacementApiRunList, "/api/ee/replacement/runs", {
2170
2677
  ...options,
@@ -2175,24 +2682,22 @@ function replacementResource(transport) {
2175
2682
  }
2176
2683
  /** Get one replacement run by id, with its status and progress. Admins only. */
2177
2684
  async function getRun(id, options = {}) {
2178
- await transport.require("replacement.getRun", options);
2179
2685
  return transport.requestParsed(ReplacementRun, `/api/ee/replacement/runs/${id}`, { ...options });
2180
2686
  }
2181
2687
  /** Cancel the replacement in progress. Refuses with a 409 once the run has ended. Admins only. */
2182
2688
  async function cancelRun(id, options = {}) {
2183
- await transport.require("replacement.cancelRun", options);
2184
2689
  await transport.requestParsed(CancelRunResponse, `/api/ee/replacement/runs/${id}/cancel`, {
2185
2690
  ...options,
2186
2691
  method: "POST"
2187
2692
  });
2188
2693
  }
2189
2694
  return {
2190
- checkReplaceSource,
2191
- replaceSource,
2192
- replaceModelWithTransform,
2193
- listRuns,
2194
- getRun,
2195
- 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)
2196
2701
  };
2197
2702
  }
2198
2703
  //#endregion
@@ -2271,13 +2776,15 @@ const ENTITY_FEATURES = {
2271
2776
  };
2272
2777
  function entityFeatures(entity) {
2273
2778
  const feature = ENTITY_FEATURES[entity];
2274
- return feature === void 0 ? [] : [feature];
2779
+ return feature === void 0 ? [] : [{
2780
+ feature,
2781
+ fields: ["entity"]
2782
+ }];
2275
2783
  }
2276
2784
  function revisionResource(transport) {
2785
+ const { explain } = explainer(transport, "revision");
2277
2786
  /** List the revisions of an entity the caller may read, newest first, each with its diff. */
2278
2787
  async function list(entity, id, options = {}) {
2279
- await transport.require("revision.list", options);
2280
- await transport.requireFeatures(entityFeatures(entity), options);
2281
2788
  return {
2282
2789
  data: await transport.requestParsed(RevisionApiList, `/api/revision/${entity}/${id}`, { ...options }),
2283
2790
  total: null
@@ -2288,8 +2795,6 @@ function revisionResource(transport) {
2288
2795
  * card the permission to run the query being restored.
2289
2796
  */
2290
2797
  async function revert(params, options = {}) {
2291
- await transport.require("revision.revert", options);
2292
- await transport.requireFeatures(entityFeatures(params.entity), options);
2293
2798
  return transport.requestParsed(RevisionApiRevert, "/api/revision/revert", {
2294
2799
  ...options,
2295
2800
  method: "POST",
@@ -2297,8 +2802,8 @@ function revisionResource(transport) {
2297
2802
  });
2298
2803
  }
2299
2804
  return {
2300
- list,
2301
- revert
2805
+ list: explain("list", list, entityFeatures),
2806
+ revert: explain("revert", revert, (params) => entityFeatures(params.entity))
2302
2807
  };
2303
2808
  }
2304
2809
  //#endregion
@@ -2307,7 +2812,14 @@ const SearchApiResponse = z.object({
2307
2812
  data: z.array(SearchResult),
2308
2813
  total: z.number().int().nonnegative()
2309
2814
  }).loose();
2815
+ function verifiedFeatures(params = {}) {
2816
+ return params.verified === true ? [{
2817
+ feature: "contentVerification",
2818
+ fields: ["verified"]
2819
+ }] : [];
2820
+ }
2310
2821
  function searchResource(transport) {
2822
+ const { explain } = explainer(transport, "search");
2311
2823
  /**
2312
2824
  * Search over the instance's content, ranked against `q`. `models` narrows which kinds of entity
2313
2825
  * may match, `archived` swaps the active set for the archived one, `table_db_id` restricts to
@@ -2317,9 +2829,12 @@ function searchResource(transport) {
2317
2829
  * actions, transforms), `include_metadata` attaches each card's `result_metadata`,
2318
2830
  * `include_dashboard_questions` also matches questions saved into a dashboard (excluded by
2319
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.
2320
2835
  */
2321
2836
  async function query(params = {}, options = {}) {
2322
- await transport.require("search.query", options);
2837
+ await transport.requireFeatures(verifiedFeatures(params).map((requirement) => requirement.feature), options);
2323
2838
  const response = await transport.requestParsed(SearchApiResponse, "/api/search", {
2324
2839
  ...options,
2325
2840
  query: {
@@ -2342,7 +2857,7 @@ function searchResource(transport) {
2342
2857
  total: response.total
2343
2858
  };
2344
2859
  }
2345
- return { query };
2860
+ return { query: explain("query", query, verifiedFeatures) };
2346
2861
  }
2347
2862
  //#endregion
2348
2863
  //#region ../client/src/resources/segment.ts
@@ -2350,7 +2865,6 @@ const SegmentApiList = z.array(Segment);
2350
2865
  function segmentResource(transport) {
2351
2866
  /** List every segment the caller can see. */
2352
2867
  async function list(options = {}) {
2353
- await transport.require("segment.list", options);
2354
2868
  return {
2355
2869
  data: await transport.requestParsed(SegmentApiList, "/api/segment", { ...options }),
2356
2870
  total: null
@@ -2358,12 +2872,10 @@ function segmentResource(transport) {
2358
2872
  }
2359
2873
  /** Get one segment by id. */
2360
2874
  async function get(id, options = {}) {
2361
- await transport.require("segment.get", options);
2362
2875
  return transport.requestParsed(Segment, `/api/segment/${id}`, { ...options });
2363
2876
  }
2364
2877
  /** Create a segment — a reusable row filter over one table — from a full segment body. */
2365
2878
  async function create(params, options = {}) {
2366
- await transport.require("segment.create", options);
2367
2879
  return transport.requestParsed(Segment, "/api/segment", {
2368
2880
  ...options,
2369
2881
  method: "POST",
@@ -2375,7 +2887,6 @@ function segmentResource(transport) {
2375
2887
  * required: Metabase records every segment change in its revision history.
2376
2888
  */
2377
2889
  async function update(id, params, options = {}) {
2378
- await transport.require("segment.update", options);
2379
2890
  return transport.requestParsed(Segment, `/api/segment/${id}`, {
2380
2891
  ...options,
2381
2892
  method: "PUT",
@@ -2387,7 +2898,6 @@ function segmentResource(transport) {
2387
2898
  * same required `revision_message`.
2388
2899
  */
2389
2900
  async function archive(id, params, options = {}) {
2390
- await transport.require("segment.archive", options);
2391
2901
  return update(id, {
2392
2902
  archived: true,
2393
2903
  revision_message: params.revision_message
@@ -2412,7 +2922,6 @@ function rethrowSettingError(error, key) {
2412
2922
  function settingResource(transport) {
2413
2923
  /** Get all settings and their values, with descriptions and environment-variable names. */
2414
2924
  async function list(options = {}) {
2415
- await transport.require("setting.list", options);
2416
2925
  return {
2417
2926
  data: await transport.requestParsed(SettingApiList, "/api/setting", { ...options }),
2418
2927
  total: null
@@ -2423,12 +2932,10 @@ function settingResource(transport) {
2423
2932
  * reaches the caller as `null`; a string-typed setting answers bare text rather than JSON.
2424
2933
  */
2425
2934
  async function get(key, options = {}) {
2426
- await transport.require("setting.get", options);
2427
2935
  return fetchOptionalParsed(transport, `/api/setting/${encodeURIComponent(key)}`, z.unknown(), options).catch((error) => rethrowSettingError(error, key));
2428
2936
  }
2429
2937
  /** Set a single setting's value by key. The endpoint answers 204 with no body. */
2430
2938
  async function set(key, value, options = {}) {
2431
- await transport.require("setting.set", options);
2432
2939
  await transport.requestRaw(`/api/setting/${encodeURIComponent(key)}`, {
2433
2940
  ...options,
2434
2941
  method: "PUT",
@@ -2450,7 +2957,6 @@ function setupResource(transport) {
2450
2957
  * applying the instance preferences. The setup token is single-use.
2451
2958
  */
2452
2959
  async function create(params, options = {}) {
2453
- await transport.require("setup.create", options);
2454
2960
  return transport.requestParsed(SetupResult, "/api/setup", {
2455
2961
  ...options,
2456
2962
  method: "POST",
@@ -2465,7 +2971,6 @@ const SnippetApiList = z.array(Snippet);
2465
2971
  function snippetResource(transport) {
2466
2972
  /** List native query snippets. `archived` swaps the listing to archived snippets. */
2467
2973
  async function list(params = {}, options = {}) {
2468
- await transport.require("snippet.list", options);
2469
2974
  return {
2470
2975
  data: await transport.requestParsed(SnippetApiList, "/api/native-query-snippet", {
2471
2976
  ...options,
@@ -2476,12 +2981,10 @@ function snippetResource(transport) {
2476
2981
  }
2477
2982
  /** Get one native query snippet by id. */
2478
2983
  async function get(id, options = {}) {
2479
- await transport.require("snippet.get", options);
2480
2984
  return transport.requestParsed(Snippet, `/api/native-query-snippet/${id}`, { ...options });
2481
2985
  }
2482
2986
  /** Create a native query snippet from a name, its SQL fragment, and an optional collection. */
2483
2987
  async function create(params, options = {}) {
2484
- await transport.require("snippet.create", options);
2485
2988
  return transport.requestParsed(Snippet, "/api/native-query-snippet", {
2486
2989
  ...options,
2487
2990
  method: "POST",
@@ -2490,7 +2993,6 @@ function snippetResource(transport) {
2490
2993
  }
2491
2994
  /** Update a native query snippet by id, patching only the fields the body carries. */
2492
2995
  async function update(id, params, options = {}) {
2493
- await transport.require("snippet.update", options);
2494
2996
  return transport.requestParsed(Snippet, `/api/native-query-snippet/${id}`, {
2495
2997
  ...options,
2496
2998
  method: "PUT",
@@ -2502,7 +3004,6 @@ function snippetResource(transport) {
2502
3004
  * own endpoint.
2503
3005
  */
2504
3006
  async function archive(id, options = {}) {
2505
- await transport.require("snippet.archive", options);
2506
3007
  return update(id, { archived: true }, options);
2507
3008
  }
2508
3009
  return {
@@ -2532,13 +3033,23 @@ const LIST_PARAM_FEATURES = [
2532
3033
  ["published-only", "tableListPublishedFilter"]
2533
3034
  ];
2534
3035
  function listParamFeatures(params) {
2535
- 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;
2536
3046
  }
2537
3047
  const UPLOAD_UPDATE_PATHS = {
2538
3048
  append: "append-csv",
2539
3049
  replace: "replace-csv"
2540
3050
  };
2541
3051
  function tableResource(transport) {
3052
+ const { explain } = explainer(transport, "table");
2542
3053
  /**
2543
3054
  * List every table the caller can see, across all databases. `term` matches names and display
2544
3055
  * names; `can-query` and `can-write` keep only the tables the caller may query or edit the
@@ -2546,9 +3057,7 @@ function tableResource(transport) {
2546
3057
  * tables nothing depends on; `published-only` keeps tables published to the library.
2547
3058
  */
2548
3059
  async function list(params = {}, options = {}) {
2549
- await transport.require("table.list", options);
2550
3060
  await transport.requireFeatures(listParamFeatures(params), options);
2551
- await requireDataLayer(params["data-layer"], options);
2552
3061
  return {
2553
3062
  data: await transport.requestParsed(TableApiList, "/api/table", {
2554
3063
  ...options,
@@ -2572,31 +3081,30 @@ function tableResource(transport) {
2572
3081
  }
2573
3082
  /** Get one table by id, without its fields. */
2574
3083
  async function get(id, options = {}) {
2575
- await transport.require("table.get", options);
2576
3084
  return transport.requestParsed(Table, `/api/table/${id}`, { ...options });
2577
3085
  }
2578
3086
  /** Update a table by id, patching only the fields the body carries. */
2579
3087
  async function update(id, params, options = {}) {
2580
- await transport.require("table.update", options);
2581
- await requireDataLayer(params.data_layer, options);
2582
- 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, {
2583
3092
  ...options,
2584
3093
  method: "PUT",
2585
3094
  body: params
2586
3095
  });
3096
+ assertCollectionWritten(path, params.collection_id, updated);
3097
+ return updated;
2587
3098
  }
2588
- async function requireDataLayer(value, options) {
2589
- if (value === null || value === void 0) return;
2590
- if (TableDataLayerTier.safeParse(value).success) {
2591
- await transport.requireFeatures(["tableDataLayerTiers"], options);
2592
- return;
2593
- }
2594
- const { features } = await transport.server(options);
2595
- 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));
2596
3105
  }
2597
3106
  /** Get a table by id with its fields hydrated — the metadata the query builder runs on. */
2598
3107
  async function queryMetadata(id, options = {}) {
2599
- await transport.require("table.queryMetadata", options);
2600
3108
  return transport.requestParsed(TableQueryMetadata, `/api/table/${id}/query_metadata`, { ...options });
2601
3109
  }
2602
3110
  /**
@@ -2604,7 +3112,6 @@ function tableResource(transport) {
2604
3112
  * fields answers no content, which is the same empty list.
2605
3113
  */
2606
3114
  async function fks(id, options = {}) {
2607
- await transport.require("table.fks", options);
2608
3115
  return {
2609
3116
  data: await fetchOptionalParsed(transport, `/api/table/${id}/fks`, TableApiFks, { ...options }) ?? [],
2610
3117
  total: null
@@ -2616,7 +3123,6 @@ function tableResource(transport) {
2616
3123
  * connect to is a 422.
2617
3124
  */
2618
3125
  async function syncSchema(id, options = {}) {
2619
- await transport.require("table.syncSchema", options);
2620
3126
  return {
2621
3127
  id,
2622
3128
  status: (await transport.requestParsed(SyncSchemaResponse, `/api/table/${id}/sync_schema`, {
@@ -2630,7 +3136,6 @@ function tableResource(transport) {
2630
3136
  * cached set. The scan runs after the call returns.
2631
3137
  */
2632
3138
  async function rescanValues(id, options = {}) {
2633
- await transport.require("table.rescanValues", options);
2634
3139
  return {
2635
3140
  id,
2636
3141
  status: (await transport.requestParsed(FieldValuesResponse, `/api/table/${id}/rescan_values`, {
@@ -2645,7 +3150,6 @@ function tableResource(transport) {
2645
3150
  * time the field's values are read.
2646
3151
  */
2647
3152
  async function discardValues(id, options = {}) {
2648
- await transport.require("table.discardValues", options);
2649
3153
  return {
2650
3154
  id,
2651
3155
  status: (await transport.requestParsed(FieldValuesResponse, `/api/table/${id}/discard_values`, {
@@ -2660,7 +3164,6 @@ function tableResource(transport) {
2660
3164
  * selectors matched a table.
2661
3165
  */
2662
3166
  async function bulkEdit(params, options = {}) {
2663
- await transport.require("table.bulkEdit", options);
2664
3167
  await requireDataAuthorityNull(params.data_authority, options);
2665
3168
  await transport.requestParsed(BulkEditResponse, BULK_EDIT_PATH, {
2666
3169
  ...options,
@@ -2694,7 +3197,6 @@ function tableResource(transport) {
2694
3197
  * call is a 422; the syncs run after it returns.
2695
3198
  */
2696
3199
  async function bulkSyncSchema(selectors, options = {}) {
2697
- await transport.require("table.bulkSyncSchema", options);
2698
3200
  return postSelection(BULK_SYNC_SCHEMA_PATH, selectors, options);
2699
3201
  }
2700
3202
  /**
@@ -2702,7 +3204,6 @@ function tableResource(transport) {
2702
3204
  * are already cached. The scans run after the call returns.
2703
3205
  */
2704
3206
  async function bulkRescanValues(selectors, options = {}) {
2705
- await transport.require("table.bulkRescanValues", options);
2706
3207
  return postSelection(BULK_RESCAN_VALUES_PATH, selectors, options);
2707
3208
  }
2708
3209
  /**
@@ -2710,7 +3211,6 @@ function tableResource(transport) {
2710
3211
  * included. No scan recreates a discarded set.
2711
3212
  */
2712
3213
  async function bulkDiscardValues(selectors, options = {}) {
2713
- await transport.require("table.bulkDiscardValues", options);
2714
3214
  return postSelection(BULK_DISCARD_VALUES_PATH, selectors, options);
2715
3215
  }
2716
3216
  async function updateFromCsv(id, action, file, options) {
@@ -2732,7 +3232,6 @@ function tableResource(transport) {
2732
3232
  * The file may be at most 50 MB; larger uploads are rejected with a 413 response.
2733
3233
  */
2734
3234
  async function appendCsv(id, file, options = {}) {
2735
- await transport.require("table.appendCsv", options);
2736
3235
  return updateFromCsv(id, "append", file, options);
2737
3236
  }
2738
3237
  /**
@@ -2742,37 +3241,53 @@ function tableResource(transport) {
2742
3241
  * The file may be at most 50 MB; larger uploads are rejected with a 413 response.
2743
3242
  */
2744
3243
  async function replaceCsv(id, file, options = {}) {
2745
- await transport.require("table.replaceCsv", options);
2746
3244
  return updateFromCsv(id, "replace", file, options);
2747
3245
  }
2748
3246
  return {
2749
- list,
3247
+ list: explain("list", list, (params) => dataLayerFeatures(params?.["data-layer"], "data-layer")),
2750
3248
  get,
2751
- update,
3249
+ update: explain("update", update, (_id, params) => dataLayerFeatures(params.data_layer, "data_layer")),
2752
3250
  queryMetadata,
2753
3251
  fks,
2754
3252
  syncSchema,
2755
3253
  rescanValues,
2756
3254
  discardValues,
2757
- bulkEdit,
2758
- bulkSyncSchema,
2759
- bulkRescanValues,
2760
- bulkDiscardValues,
3255
+ bulkEdit: explain("bulkEdit", bulkEdit),
3256
+ bulkSyncSchema: explain("bulkSyncSchema", bulkSyncSchema),
3257
+ bulkRescanValues: explain("bulkRescanValues", bulkRescanValues),
3258
+ bulkDiscardValues: explain("bulkDiscardValues", bulkDiscardValues),
2761
3259
  appendCsv,
2762
3260
  replaceCsv
2763
3261
  };
2764
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
+ }
2765
3282
  //#endregion
2766
3283
  //#region ../client/src/resources/timeline-event.ts
2767
3284
  function timelineEventResource(transport) {
2768
3285
  /** Get one timeline event by id. */
2769
3286
  async function get(id, options = {}) {
2770
- await transport.require("timelineEvent.get", options);
2771
3287
  return transport.requestParsed(TimelineEvent, `/api/timeline-event/${id}`, { ...options });
2772
3288
  }
2773
3289
  /** Create a timeline event on an existing timeline. */
2774
3290
  async function create(params, options = {}) {
2775
- await transport.require("timelineEvent.create", options);
2776
3291
  return transport.requestParsed(TimelineEvent, "/api/timeline-event", {
2777
3292
  ...options,
2778
3293
  method: "POST",
@@ -2784,7 +3299,6 @@ function timelineEventResource(transport) {
2784
3299
  * the event to another timeline.
2785
3300
  */
2786
3301
  async function update(id, params, options = {}) {
2787
- await transport.require("timelineEvent.update", options);
2788
3302
  return transport.requestParsed(TimelineEvent, `/api/timeline-event/${id}`, {
2789
3303
  ...options,
2790
3304
  method: "PUT",
@@ -2796,12 +3310,10 @@ function timelineEventResource(transport) {
2796
3310
  * endpoint.
2797
3311
  */
2798
3312
  async function archive(id, options = {}) {
2799
- await transport.require("timelineEvent.archive", options);
2800
3313
  return update(id, { archived: true }, options);
2801
3314
  }
2802
3315
  /** Permanently delete a timeline event by id. The server answers with no body. */
2803
3316
  async function remove(id, options = {}) {
2804
- await transport.require("timelineEvent.delete", options);
2805
3317
  await transport.requestRaw(`/api/timeline-event/${id}`, {
2806
3318
  ...options,
2807
3319
  method: "DELETE",
@@ -2823,7 +3335,6 @@ const TimelineWithEvents = Timeline.required({ events: true });
2823
3335
  function timelineResource(transport) {
2824
3336
  /** List timelines. `archived` swaps the active listing for the archived one. */
2825
3337
  async function list(params = {}, options = {}) {
2826
- await transport.require("timeline.list", options);
2827
3338
  return {
2828
3339
  data: await transport.requestParsed(TimelineApiList, "/api/timeline", {
2829
3340
  ...options,
@@ -2834,7 +3345,6 @@ function timelineResource(transport) {
2834
3345
  }
2835
3346
  /** Get one timeline by id, without its events. */
2836
3347
  async function get(id, options = {}) {
2837
- await transport.require("timeline.get", options);
2838
3348
  return transport.requestParsed(Timeline, `/api/timeline/${id}`, { ...options });
2839
3349
  }
2840
3350
  /**
@@ -2842,7 +3352,6 @@ function timelineResource(transport) {
2842
3352
  * timeline read hydrates them on request, and `archived` chooses which of them come back.
2843
3353
  */
2844
3354
  async function events(id, params = {}, options = {}) {
2845
- await transport.require("timeline.events", options);
2846
3355
  return {
2847
3356
  data: (await transport.requestParsed(TimelineWithEvents, `/api/timeline/${id}`, {
2848
3357
  ...options,
@@ -2856,7 +3365,6 @@ function timelineResource(transport) {
2856
3365
  }
2857
3366
  /** Create a timeline from a full timeline body. */
2858
3367
  async function create(params, options = {}) {
2859
- await transport.require("timeline.create", options);
2860
3368
  return transport.requestParsed(Timeline, "/api/timeline", {
2861
3369
  ...options,
2862
3370
  method: "POST",
@@ -2868,7 +3376,6 @@ function timelineResource(transport) {
2868
3376
  * cascades to every event on the timeline.
2869
3377
  */
2870
3378
  async function update(id, params, options = {}) {
2871
- await transport.require("timeline.update", options);
2872
3379
  return transport.requestParsed(Timeline, `/api/timeline/${id}`, {
2873
3380
  ...options,
2874
3381
  method: "PUT",
@@ -2880,12 +3387,10 @@ function timelineResource(transport) {
2880
3387
  * not its own endpoint.
2881
3388
  */
2882
3389
  async function archive(id, options = {}) {
2883
- await transport.require("timeline.archive", options);
2884
3390
  return update(id, { archived: true }, options);
2885
3391
  }
2886
3392
  /** Permanently delete a timeline and all its events by id. The server answers with no body. */
2887
3393
  async function remove(id, options = {}) {
2888
- await transport.require("timeline.delete", options);
2889
3394
  await transport.requestRaw(`/api/timeline/${id}`, {
2890
3395
  ...options,
2891
3396
  method: "DELETE",
@@ -2922,12 +3427,12 @@ TransformMemberRun.pick({
2922
3427
  //#region ../client/src/resources/transform-dag-run.ts
2923
3428
  const TransformMemberRunList = z.array(TransformMemberRun);
2924
3429
  function transformDagRunResource(transport) {
3430
+ const { explain } = explainer(transport, "transformDagRun");
2925
3431
  /**
2926
3432
  * List the transform runs a DAG run coordinated so far. A member run is inserted when its
2927
3433
  * transform starts, so the list grows while the DAG run is in progress.
2928
3434
  */
2929
3435
  async function transformRuns(runId, options = {}) {
2930
- await transport.require("transformDagRun.transformRuns", options);
2931
3436
  return {
2932
3437
  data: await transport.requestParsed(TransformMemberRunList, `/api/transform-dag-run/${runId}/transform-runs`, { ...options }),
2933
3438
  total: null
@@ -2935,7 +3440,6 @@ function transformDagRunResource(transport) {
2935
3440
  }
2936
3441
  /** Cancel an in-progress DAG run and request cancellation of its still-running transforms. */
2937
3442
  async function cancel(runId, options = {}) {
2938
- await transport.require("transformDagRun.cancel", options);
2939
3443
  await transport.requestRaw(`/api/transform-dag-run/${runId}/cancel`, {
2940
3444
  ...options,
2941
3445
  method: "POST",
@@ -2943,8 +3447,8 @@ function transformDagRunResource(transport) {
2943
3447
  });
2944
3448
  }
2945
3449
  return {
2946
- transformRuns,
2947
- cancel
3450
+ transformRuns: explain("transformRuns", transformRuns),
3451
+ cancel: explain("cancel", cancel)
2948
3452
  };
2949
3453
  }
2950
3454
  //#endregion
@@ -3092,12 +3596,12 @@ function lensPath(id, lensId) {
3092
3596
  return `/api/ee/transforms/${id}/inspect/${encodeURIComponent(lensId)}`;
3093
3597
  }
3094
3598
  function transformInspectorResource(transport) {
3599
+ const { explain } = explainer(transport, "transformInspector");
3095
3600
  /**
3096
3601
  * Discover what the inspector can show for a transform: its source and target tables with field
3097
3602
  * statistics, the fields its query touches, and the lenses available on it.
3098
3603
  */
3099
3604
  async function discover(id, options = {}) {
3100
- await transport.require("transformInspector.discover", options);
3101
3605
  return transport.requestParsed(TransformInspection, `/api/ee/transforms/${id}/inspect`, { ...options });
3102
3606
  }
3103
3607
  /**
@@ -3106,7 +3610,6 @@ function transformInspectorResource(transport) {
3106
3610
  * the `params` its trigger emitted.
3107
3611
  */
3108
3612
  async function lens(id, lensId, params = {}, options = {}) {
3109
- await transport.require("transformInspector.lens", options);
3110
3613
  return transport.requestParsed(TransformLens, lensPath(id, lensId), {
3111
3614
  ...options,
3112
3615
  query: { join_step: params.join_step }
@@ -3118,7 +3621,6 @@ function transformInspectorResource(transport) {
3118
3621
  * opened with.
3119
3622
  */
3120
3623
  async function query(id, lensId, params, options = {}) {
3121
- await transport.require("transformInspector.query", options);
3122
3624
  return transport.requestParsed(CardQueryResult, `${lensPath(id, lensId)}/query`, {
3123
3625
  ...options,
3124
3626
  method: "POST",
@@ -3126,17 +3628,17 @@ function transformInspectorResource(transport) {
3126
3628
  });
3127
3629
  }
3128
3630
  return {
3129
- discover,
3130
- lens,
3131
- query
3631
+ discover: explain("discover", discover),
3632
+ lens: explain("lens", lens),
3633
+ query: explain("query", query)
3132
3634
  };
3133
3635
  }
3134
3636
  //#endregion
3135
3637
  //#region ../client/src/resources/transform-job.ts
3136
3638
  function transformJobResource(transport) {
3639
+ const { explain } = explainer(transport, "transformJob");
3137
3640
  /** List every transform job the caller can see. */
3138
3641
  async function list(options = {}) {
3139
- await transport.require("transformJob.list", options);
3140
3642
  const { features } = await transport.server(options);
3141
3643
  return {
3142
3644
  data: await transport.requestParsed(z.array(transformJobSchema(features)), "/api/transform-job", { ...options }),
@@ -3145,13 +3647,11 @@ function transformJobResource(transport) {
3145
3647
  }
3146
3648
  /** Get one transform job by id. */
3147
3649
  async function get(id, options = {}) {
3148
- await transport.require("transformJob.get", options);
3149
3650
  const { features } = await transport.server(options);
3150
3651
  return transport.requestParsed(transformJobSchema(features), `/api/transform-job/${id}`, { ...options });
3151
3652
  }
3152
3653
  /** Create a transform job — a schedule plus the tags it runs — from a full body. */
3153
3654
  async function create(params, options = {}) {
3154
- await transport.require("transformJob.create", options);
3155
3655
  const { features } = await transport.server(options);
3156
3656
  return transport.requestParsed(transformJobSchema(features), "/api/transform-job", {
3157
3657
  ...options,
@@ -3161,7 +3661,6 @@ function transformJobResource(transport) {
3161
3661
  }
3162
3662
  /** Update a transform job by id, patching only the fields the body carries. */
3163
3663
  async function update(id, params, options = {}) {
3164
- await transport.require("transformJob.update", options);
3165
3664
  const { features } = await transport.server(options);
3166
3665
  return transport.requestParsed(transformJobSchema(features), `/api/transform-job/${id}`, {
3167
3666
  ...options,
@@ -3171,7 +3670,6 @@ function transformJobResource(transport) {
3171
3670
  }
3172
3671
  /** Delete a transform job by id, leaving the transforms it ran untouched. */
3173
3672
  async function remove(id, options = {}) {
3174
- await transport.require("transformJob.delete", options);
3175
3673
  await transport.requestRaw(`/api/transform-job/${id}`, {
3176
3674
  ...options,
3177
3675
  method: "DELETE",
@@ -3183,7 +3681,6 @@ function transformJobResource(transport) {
3183
3681
  * plus those transforms' dependencies; `run_all` re-runs dependencies that are already fresh.
3184
3682
  */
3185
3683
  async function run(id, params = {}, options = {}) {
3186
- await transport.require("transformJob.run", options);
3187
3684
  const { features } = await transport.server(options);
3188
3685
  return transport.requestParsed(transformJobRunResultSchema(features), `/api/transform-job/${id}/run`, {
3189
3686
  ...options,
@@ -3193,7 +3690,6 @@ function transformJobResource(transport) {
3193
3690
  }
3194
3691
  /** List the transforms a job would execute, resolved from the job's tags. */
3195
3692
  async function transforms(id, options = {}) {
3196
- await transport.require("transformJob.transforms", options);
3197
3693
  const { features } = await transport.server(options);
3198
3694
  return {
3199
3695
  data: await transport.requestParsed(z.array(transformRowSchema(features)), `/api/transform-job/${id}/transforms`, { ...options }),
@@ -3205,7 +3701,6 @@ function transformJobResource(transport) {
3205
3701
  * manual runs ignore the flag. The path carries no id and is a different endpoint from `update`.
3206
3702
  */
3207
3703
  async function setActive(active, options = {}) {
3208
- await transport.require("transformJob.setActive", options);
3209
3704
  return transport.requestParsed(TransformJobActiveResult, "/api/transform-job/active", {
3210
3705
  ...options,
3211
3706
  method: "PUT",
@@ -3213,14 +3708,14 @@ function transformJobResource(transport) {
3213
3708
  });
3214
3709
  }
3215
3710
  return {
3216
- list,
3217
- get,
3218
- create,
3219
- update,
3220
- delete: remove,
3221
- run,
3222
- transforms,
3223
- 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)
3224
3719
  };
3225
3720
  }
3226
3721
  //#endregion
@@ -3280,14 +3775,13 @@ function libraryPath(path) {
3280
3775
  return `/api/ee/transforms-python/library/${encodeURIComponent(path)}`;
3281
3776
  }
3282
3777
  function transformPythonResource(transport) {
3778
+ const { explain } = explainer(transport, "transformPython");
3283
3779
  /** Get the Python library at a path, the user module every Python transform can import. */
3284
3780
  async function getLibrary(path, options = {}) {
3285
- await transport.require("transformPython.getLibrary", options);
3286
3781
  return transport.requestParsed(PythonLibrary, libraryPath(path), { ...options });
3287
3782
  }
3288
3783
  /** Replace the source of the Python library at a path, creating the library when none exists. */
3289
3784
  async function updateLibrary(path, params, options = {}) {
3290
- await transport.require("transformPython.updateLibrary", options);
3291
3785
  return transport.requestParsed(PythonLibrary, libraryPath(path), {
3292
3786
  ...options,
3293
3787
  method: "PUT",
@@ -3300,7 +3794,6 @@ function transformPythonResource(transport) {
3300
3794
  * sizes are capped at 100 rows.
3301
3795
  */
3302
3796
  async function testRun(params, options = {}) {
3303
- await transport.require("transformPython.testRun", options);
3304
3797
  return transport.requestParsed(PythonApiTestRun, "/api/ee/transforms-python/test-run", {
3305
3798
  ...options,
3306
3799
  method: "POST",
@@ -3308,9 +3801,9 @@ function transformPythonResource(transport) {
3308
3801
  });
3309
3802
  }
3310
3803
  return {
3311
- getLibrary,
3312
- updateLibrary,
3313
- testRun
3804
+ getLibrary: explain("getLibrary", getLibrary),
3805
+ updateLibrary: explain("updateLibrary", updateLibrary),
3806
+ testRun: explain("testRun", testRun)
3314
3807
  };
3315
3808
  }
3316
3809
  //#endregion
@@ -3324,6 +3817,7 @@ const TransformRunKickoff = z.object({
3324
3817
  });
3325
3818
  const TransformDagTransformList = z.array(TransformDagTransform);
3326
3819
  function transformResource(transport) {
3820
+ const { explain, explainWalk } = explainer(transport, "transform");
3327
3821
  async function requestTransformList(path, opts) {
3328
3822
  const { features } = await transport.server(opts);
3329
3823
  return {
@@ -3337,18 +3831,15 @@ function transformResource(transport) {
3337
3831
  }
3338
3832
  /** List every transform the caller can see. */
3339
3833
  async function list(options = {}) {
3340
- await transport.require("transform.list", options);
3341
3834
  return requestTransformList("/api/transform", { ...options });
3342
3835
  }
3343
3836
  /** Get one transform by id. */
3344
3837
  async function get(id, options = {}) {
3345
- await transport.require("transform.get", options);
3346
3838
  const { features } = await transport.server(options);
3347
3839
  return transport.requestParsed(transformDetailSchema(features), `/api/transform/${id}`, { ...options });
3348
3840
  }
3349
3841
  /** Create a transform — a query plus the warehouse table it writes — from a full body. */
3350
3842
  async function create(params, options = {}) {
3351
- await transport.require("transform.create", options);
3352
3843
  return requestTransform("/api/transform", {
3353
3844
  ...options,
3354
3845
  method: "POST",
@@ -3357,7 +3848,6 @@ function transformResource(transport) {
3357
3848
  }
3358
3849
  /** Update a transform by id, patching only the fields the body carries. */
3359
3850
  async function update(id, params, options = {}) {
3360
- await transport.require("transform.update", options);
3361
3851
  return requestTransform(`/api/transform/${id}`, {
3362
3852
  ...options,
3363
3853
  method: "PUT",
@@ -3366,7 +3856,6 @@ function transformResource(transport) {
3366
3856
  }
3367
3857
  /** Delete a transform by id, leaving any table it already materialized in place. */
3368
3858
  async function remove(id, options = {}) {
3369
- await transport.require("transform.delete", options);
3370
3859
  await transport.requestRaw(`/api/transform/${id}`, {
3371
3860
  ...options,
3372
3861
  method: "DELETE",
@@ -3375,7 +3864,6 @@ function transformResource(transport) {
3375
3864
  }
3376
3865
  /** Drop a transform's materialized output table, keeping the transform definition. */
3377
3866
  async function deleteTable(id, options = {}) {
3378
- await transport.require("transform.deleteTable", options);
3379
3867
  await transport.requestRaw(`/api/transform/${id}/table`, {
3380
3868
  ...options,
3381
3869
  method: "DELETE",
@@ -3384,12 +3872,10 @@ function transformResource(transport) {
3384
3872
  }
3385
3873
  /** List the upstream transforms a transform depends on. */
3386
3874
  async function dependencies(id, options = {}) {
3387
- await transport.require("transform.dependencies", options);
3388
3875
  return requestTransformList(`/api/transform/${id}/dependencies`, { ...options });
3389
3876
  }
3390
3877
  /** Request cancellation of a transform's current run. */
3391
3878
  async function cancel(id, options = {}) {
3392
- await transport.require("transform.cancel", options);
3393
3879
  await transport.requestRaw(`/api/transform/${id}/cancel`, {
3394
3880
  ...options,
3395
3881
  method: "POST",
@@ -3398,12 +3884,10 @@ function transformResource(transport) {
3398
3884
  }
3399
3885
  /** Get one transform run by run id — not by the id of the transform that produced it. */
3400
3886
  async function getRun(runId, options = {}) {
3401
- await transport.require("transform.getRun", options);
3402
3887
  return transport.requestParsed(TransformRun, `/api/transform/run/${runId}`, { ...options });
3403
3888
  }
3404
3889
  /** Walk the run history, newest first, optionally narrowed to a single transform. */
3405
3890
  async function* runPages(params = {}, options = {}) {
3406
- await transport.require("transform.runPages", options);
3407
3891
  yield* paginatePages(transport, "/api/transform/run", TransformRun, {
3408
3892
  query: { "transform-ids": params["transform-ids"] },
3409
3893
  ...options.offset !== void 0 && { offset: options.offset },
@@ -3417,7 +3901,6 @@ function transformResource(transport) {
3417
3901
  * DAG reprocess run, or a standalone transform run — and never a member run of a job or DAG.
3418
3902
  */
3419
3903
  async function* runSummaryPages(params = {}, options = {}) {
3420
- await transport.require("transform.runSummaryPages", options);
3421
3904
  yield* paginatePages(transport, "/api/transform/runs", TransformRunSummary, {
3422
3905
  query: {
3423
3906
  types: params.types,
@@ -3440,7 +3923,6 @@ function transformResource(transport) {
3440
3923
  * source rather than the rows past the last checkpoint.
3441
3924
  */
3442
3925
  async function resetCheckpoint(id, options = {}) {
3443
- await transport.require("transform.resetCheckpoint", options);
3444
3926
  await transport.requestRaw(`/api/transform/${id}/reset-checkpoint`, {
3445
3927
  ...options,
3446
3928
  method: "POST",
@@ -3455,7 +3937,6 @@ function transformResource(transport) {
3455
3937
  * `runSummaryPages` with `types: ["dag"]`, and the member runs off `transformDagRun.transformRuns`.
3456
3938
  */
3457
3939
  async function runDag(id, params, options = {}) {
3458
- await transport.require("transform.runDag", options);
3459
3940
  return transport.requestParsed(TransformDagRunResult, `/api/transform/${id}/run-dag`, {
3460
3941
  ...options,
3461
3942
  method: "POST",
@@ -3464,7 +3945,6 @@ function transformResource(transport) {
3464
3945
  }
3465
3946
  /** Preview the transforms a DAG reprocess seeded at a transform would run, in execution order. */
3466
3947
  async function dagTransforms(id, params, options = {}) {
3467
- await transport.require("transform.dagTransforms", options);
3468
3948
  return {
3469
3949
  data: await transport.requestParsed(TransformDagTransformList, `/api/transform/${id}/dag-transforms`, {
3470
3950
  ...options,
@@ -3479,7 +3959,6 @@ function transformResource(transport) {
3479
3959
  * A server that answers no run id started nothing, and there is then nothing to poll.
3480
3960
  */
3481
3961
  async function run(id, params = {}, options = {}) {
3482
- await transport.require("transform.run", options);
3483
3962
  const kickoff = await transport.requestParsed(TransformRunKickoff, `/api/transform/${id}/run`, {
3484
3963
  ...options,
3485
3964
  method: "POST"
@@ -3520,30 +3999,30 @@ function transformResource(transport) {
3520
3999
  }
3521
4000
  }
3522
4001
  return {
3523
- list,
3524
- get,
3525
- create,
3526
- update,
3527
- delete: remove,
3528
- deleteTable,
3529
- dependencies,
3530
- cancel,
3531
- getRun,
3532
- runPages,
3533
- runSummaryPages,
3534
- resetCheckpoint,
3535
- runDag,
3536
- dagTransforms,
3537
- 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)
3538
4017
  };
3539
4018
  }
3540
4019
  //#endregion
3541
4020
  //#region ../client/src/resources/transform-tag.ts
3542
4021
  const TransformTagApiList = z.array(TransformTag);
3543
4022
  function transformTagResource(transport) {
4023
+ const { explain } = explainer(transport, "transformTag");
3544
4024
  /** List every transform tag the caller can see, built-in tags included. */
3545
4025
  async function list(options = {}) {
3546
- await transport.require("transformTag.list", options);
3547
4026
  return {
3548
4027
  data: await transport.requestParsed(TransformTagApiList, "/api/transform-tag", { ...options }),
3549
4028
  total: null
@@ -3551,7 +4030,6 @@ function transformTagResource(transport) {
3551
4030
  }
3552
4031
  /** Create a transform tag, the label a transform job matches transforms on. */
3553
4032
  async function create(params, options = {}) {
3554
- await transport.require("transformTag.create", options);
3555
4033
  return transport.requestParsed(TransformTag, "/api/transform-tag", {
3556
4034
  ...options,
3557
4035
  method: "POST",
@@ -3560,7 +4038,6 @@ function transformTagResource(transport) {
3560
4038
  }
3561
4039
  /** Rename a transform tag by id. */
3562
4040
  async function update(id, params, options = {}) {
3563
- await transport.require("transformTag.update", options);
3564
4041
  return transport.requestParsed(TransformTag, `/api/transform-tag/${id}`, {
3565
4042
  ...options,
3566
4043
  method: "PUT",
@@ -3569,7 +4046,6 @@ function transformTagResource(transport) {
3569
4046
  }
3570
4047
  /** Delete a transform tag by id, detaching it from every transform and job carrying it. */
3571
4048
  async function remove(id, options = {}) {
3572
- await transport.require("transformTag.delete", options);
3573
4049
  await transport.requestRaw(`/api/transform-tag/${id}`, {
3574
4050
  ...options,
3575
4051
  method: "DELETE",
@@ -3577,19 +4053,180 @@ function transformTagResource(transport) {
3577
4053
  });
3578
4054
  }
3579
4055
  return {
3580
- list,
3581
- create,
3582
- update,
3583
- delete: remove
4056
+ list: explain("list", list),
4057
+ create: explain("create", create),
4058
+ update: explain("update", update),
4059
+ delete: explain("delete", remove)
3584
4060
  };
3585
4061
  }
3586
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
3587
4224
  //#region ../client/src/resources/transform-test.ts
3588
4225
  const TransformTestApiList = z.array(TransformTest);
3589
4226
  function transformTestResource(transport) {
4227
+ const { explain } = explainer(transport, "transformTest");
3590
4228
  /** List the transform tests, optionally only those of one transform. */
3591
4229
  async function list(params = {}, options = {}) {
3592
- await transport.require("transformTest.list", options);
3593
4230
  return {
3594
4231
  data: await transport.requestParsed(TransformTestApiList, "/api/ee/transform-test", {
3595
4232
  ...options,
@@ -3600,7 +4237,6 @@ function transformTestResource(transport) {
3600
4237
  }
3601
4238
  /** Get a transform test by id. */
3602
4239
  async function get(id, options = {}) {
3603
- await transport.require("transformTest.get", options);
3604
4240
  return transport.requestParsed(TransformTest, `/api/ee/transform-test/${id}`, { ...options });
3605
4241
  }
3606
4242
  /**
@@ -3609,7 +4245,6 @@ function transformTestResource(transport) {
3609
4245
  * `TransformTestRefusalCode` when they cannot run.
3610
4246
  */
3611
4247
  async function create(params, options = {}) {
3612
- await transport.require("transformTest.create", options);
3613
4248
  return transport.requestParsed(TransformTest, "/api/ee/transform-test", {
3614
4249
  ...options,
3615
4250
  method: "POST",
@@ -3621,7 +4256,6 @@ function transformTestResource(transport) {
3621
4256
  * transform, inputs or expectations is validated as on create, with the same refusal.
3622
4257
  */
3623
4258
  async function update(id, params, options = {}) {
3624
- await transport.require("transformTest.update", options);
3625
4259
  return transport.requestParsed(TransformTest, `/api/ee/transform-test/${id}`, {
3626
4260
  ...options,
3627
4261
  method: "PUT",
@@ -3630,7 +4264,6 @@ function transformTestResource(transport) {
3630
4264
  }
3631
4265
  /** Delete a transform test by id. */
3632
4266
  async function remove(id, options = {}) {
3633
- await transport.require("transformTest.delete", options);
3634
4267
  await transport.requestRaw(`/api/ee/transform-test/${id}`, {
3635
4268
  ...options,
3636
4269
  method: "DELETE",
@@ -3644,19 +4277,18 @@ function transformTestResource(transport) {
3644
4277
  * refused and nothing meaningful ran.
3645
4278
  */
3646
4279
  async function run(id, options = {}) {
3647
- await transport.require("transformTest.run", options);
3648
4280
  return transport.requestParsed(TransformTestRunResult, `/api/ee/transform-test/${id}/run`, {
3649
4281
  ...options,
3650
4282
  method: "POST"
3651
4283
  });
3652
4284
  }
3653
4285
  return {
3654
- list,
3655
- get,
3656
- create,
3657
- update,
3658
- delete: remove,
3659
- 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)
3660
4292
  };
3661
4293
  }
3662
4294
  //#endregion
@@ -3670,7 +4302,6 @@ function uploadResource(transport) {
3670
4302
  * The file may be at most 50 MB; larger uploads are rejected with a 413 response.
3671
4303
  */
3672
4304
  async function createFromCsv(file, params, options = {}) {
3673
- await transport.require("upload.createFromCsv", options);
3674
4305
  const form = buildCsvFormData(file);
3675
4306
  form.append("collection_id", params.collection_id);
3676
4307
  const response = await transport.requestRaw("/api/upload/csv", {
@@ -3723,7 +4354,6 @@ CurrentUser.pick({
3723
4354
  function userResource(transport) {
3724
4355
  /** Get the user the request's credentials authenticate as. */
3725
4356
  async function current(options = {}) {
3726
- await transport.require("user.current", options);
3727
4357
  return transport.requestParsed(CurrentUser, "/api/user/current", { ...options });
3728
4358
  }
3729
4359
  return { current };
@@ -3774,6 +4404,8 @@ function createClient(config, options) {
3774
4404
  upload: uploadResource(transport),
3775
4405
  user: userResource(transport),
3776
4406
  server: transport.server,
4407
+ verifiedServer: transport.verifiedServer,
4408
+ probesSettled: transport.probesSettled,
3777
4409
  requestParsed: transport.requestParsed,
3778
4410
  requestRaw: transport.requestRaw,
3779
4411
  requestStream: transport.requestStream