permdock 0.1.0 → 0.3.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 (350) hide show
  1. package/README.md +106 -47
  2. package/dist/a2a/index.d.ts +7 -16
  3. package/dist/a2a/index.js +59 -98
  4. package/dist/adapter-context-mlWsVGGg.js +23 -0
  5. package/dist/ai-sdk/index.d.ts +37 -16
  6. package/dist/ai-sdk/index.js +30 -13
  7. package/dist/approvals/index.d.ts +3 -3
  8. package/dist/approvals/index.js +5 -4
  9. package/dist/{approvals-DaxY-Bdg.js → approvals-DbYAuNkG.js} +17 -11
  10. package/dist/approvers-DT4AVyIg.js +52 -0
  11. package/dist/{arazzo-CTeVuXr5.js → arazzo-BAgw9ap6.js} +2 -2
  12. package/dist/{arazzo-aoTkqYsJ.js → arazzo-CKwwqFWz.js} +2 -2
  13. package/dist/authzen/index.d.ts +4 -13
  14. package/dist/authzen/index.js +16 -50
  15. package/dist/better-auth/index.d.ts +20 -3
  16. package/dist/better-auth/index.js +27 -7
  17. package/dist/better-supabase/index.d.ts +223 -0
  18. package/dist/better-supabase/index.js +648 -0
  19. package/dist/bind-C_ob3LKa.d.ts +25 -0
  20. package/dist/bind-CuzGytRf.js +97 -0
  21. package/dist/boundary-BM8a_P6E.js +13 -0
  22. package/dist/boundary-BmHxVH1J.d.ts +15 -0
  23. package/dist/canonical-json-DemiK4Vp.js +26 -0
  24. package/dist/{capability-BDG_nkxt.d.ts → capability-2kYTMWDv.d.ts} +2 -2
  25. package/dist/{capability-B68OFnZx.js → capability-4qPdO9gk.js} +7 -5
  26. package/dist/catalog/index.d.ts +1 -1
  27. package/dist/catalog/index.js +1 -1
  28. package/dist/catalog-CRW5Mx_B.js +2 -0
  29. package/dist/{catalog-H-vFGwWK.js → catalog-DWF5cYqB.js} +4 -4
  30. package/dist/{catalog-B0E6bggc.js → catalog-dFZdWCGN.js} +2 -2
  31. package/dist/{catalog-doc-BSo_Q_-6.js → catalog-doc-DdGm82XT.js} +20 -32
  32. package/dist/claude-agent/index.d.ts +5 -14
  33. package/dist/claude-agent/index.js +4 -10
  34. package/dist/clerk/index.d.ts +12 -3
  35. package/dist/clerk/index.js +26 -5
  36. package/dist/cli/bin.js +1 -1
  37. package/dist/cli/index.d.ts +3 -3
  38. package/dist/cli/index.js +2 -2
  39. package/dist/clients-C7zLFuWT.d.ts +12 -0
  40. package/dist/clients-DjU5ABMy.js +23 -0
  41. package/dist/cloud/index.d.ts +4 -5
  42. package/dist/cloud/index.js +20 -6
  43. package/dist/{cloud-BNQTrmrr.js → cloud-CB_ELYuX.js} +2 -2
  44. package/dist/{cloud-Cuxr6Czw.js → cloud-DWLXO2af.js} +7 -6
  45. package/dist/collect-DClJsheJ.js +83 -0
  46. package/dist/{collect-O-tOtbnk.js → collect-DSTE3ml9.js} +194 -42
  47. package/dist/collect-DSWOk19R.js +2 -0
  48. package/dist/{commands-BMh_Etm2.js → commands-DSxCqG9A.js} +16 -12
  49. package/dist/compile/index.d.ts +3 -0
  50. package/dist/compile/index.js +2 -0
  51. package/dist/{compile-D22iwvuV.js → compile-Ba4blROR.js} +85 -30
  52. package/dist/compile-BkRyBDBw.d.ts +94 -0
  53. package/dist/config-BJiM-xAm.js +157 -0
  54. package/dist/config-Dnszbv-6.js +36 -0
  55. package/dist/config-report-DMvWKUxL.js +44 -0
  56. package/dist/{connection-JvqwHODC.js → connection-CU8MQxRi.js} +1 -1
  57. package/dist/{context-CKEAabd7.js → context-Bcy85rGi.js} +36 -27
  58. package/dist/convex/index.d.ts +9 -4
  59. package/dist/convex/index.js +14 -4
  60. package/dist/create-B1d519XK.d.ts +245 -0
  61. package/dist/create-CMwIl-w2.js +253 -0
  62. package/dist/{credential-B_rkUNlY.js → credential-8P81ff4u.js} +26 -46
  63. package/dist/delegation-D8pUEuce.js +103 -0
  64. package/dist/{describe-Bt1YD5uS.js → describe-B_gVZEVi.js} +10 -15
  65. package/dist/{describe-DernyItf.d.ts → describe-DMYiCjJV.d.ts} +9 -10
  66. package/dist/{diff-x5Il8LVa.js → diff-2VaeChzX.js} +44 -11
  67. package/dist/{diff-BUzf5Poi.js → diff-DBIhz_f2.js} +2 -2
  68. package/dist/{doctor-Bqo6FwUa.js → doctor-BwCAMLx_.js} +2 -2
  69. package/dist/doctor-collect-CHk6XWOA.js +665 -0
  70. package/dist/doctor-igIiM_Hl.js +1194 -0
  71. package/dist/drizzle/index.d.ts +4 -3
  72. package/dist/drizzle/index.js +3 -3
  73. package/dist/elysia/index.d.ts +6 -28
  74. package/dist/elysia/index.js +16 -60
  75. package/dist/{errors-DSMnAVNa.js → errors-CWfoNg9N.js} +8 -4
  76. package/dist/{errors-Iy71s7Oj.js → errors-Cz5zn0mW.js} +17 -71
  77. package/dist/{errors-DbZ1YOnb.d.ts → errors-DPdqI50v.d.ts} +8 -21
  78. package/dist/{errors-m3u9TmDu.js → errors-DRnGZHzF.js} +4 -1
  79. package/dist/evaluation-items-CfCCejQd.js +40 -0
  80. package/dist/{evaluations-DsAU9ppl.js → evaluations-tz6D9A2G.js} +28 -46
  81. package/dist/eve/index.d.ts +6 -15
  82. package/dist/eve/index.js +7 -12
  83. package/dist/expiry-BoBJ8Tvc.js +7 -0
  84. package/dist/express/index.d.ts +6 -29
  85. package/dist/express/index.js +28 -58
  86. package/dist/fastify/index.d.ts +6 -29
  87. package/dist/fastify/index.js +16 -65
  88. package/dist/fields-BMl1RxoX.js +26 -0
  89. package/dist/{files-B_wAGYmt.js → files-DWUN9PzI.js} +5 -1
  90. package/dist/{fixtures-Cvaw0gDG.js → fixtures-ht28Cplr.js} +5 -2
  91. package/dist/{freeze-y1_jQL4v.js → freeze-CrU8nQeX.js} +31 -2
  92. package/dist/{from-snapshot-BMGnhmgv.js → from-snapshot-CTLijvdm.js} +66 -48
  93. package/dist/{grantee-Bv04_Vzf.js → grantee-Cv5Sogfv.js} +88 -7
  94. package/dist/graph-sql-B-dsAT6j.d.ts +29 -0
  95. package/dist/{headers-B5RRv3Xm.js → headers-BZSDieNT.js} +1 -1
  96. package/dist/{headers-C8SHQccD.d.ts → headers-Cf-zWvsO.d.ts} +1 -1
  97. package/dist/helpers-H_KARDVC.js +421 -0
  98. package/dist/hono/index.d.ts +6 -28
  99. package/dist/hono/index.js +11 -43
  100. package/dist/{hosted-BwKQ1BTA.js → hosted-DJQw46hn.js} +6 -5
  101. package/dist/{http-GgVApX1k.js → http-BIhmLfCc.js} +4 -20
  102. package/dist/http-SO7qGttf.js +44 -0
  103. package/dist/ids-DC4V4CFu.js +18 -0
  104. package/dist/index-0nNqHspG.d.ts +6 -0
  105. package/dist/{index-xIxJLwOQ.d.ts → index-BNc7Wer_.d.ts} +75 -7
  106. package/dist/{index-CltcQ4Bf.d.ts → index-C7Tw7MaW.d.ts} +68 -5
  107. package/dist/{index-LF_hS-yn.d.ts → index-C8z4RjQV.d.ts} +1 -1
  108. package/dist/index.d.ts +14 -9
  109. package/dist/index.js +22 -17
  110. package/dist/instance-options-Dhhcnm3y.js +22 -0
  111. package/dist/instance-options-jARdwfPN.d.ts +11 -0
  112. package/dist/{parse-DPfIvFMU.js → json-schema-BsAflUGW.js} +28 -57
  113. package/dist/jwt/index.d.ts +10 -6
  114. package/dist/jwt/index.js +32 -13
  115. package/dist/kernel-_RRHmnMY.js +208 -0
  116. package/dist/kysely/index.d.ts +4 -3
  117. package/dist/kysely/index.js +3 -3
  118. package/dist/live-options-H2eG8dfJ.js +60 -0
  119. package/dist/{load-BZ24CpZP.js → load-DhWEB9P1.js} +16 -10
  120. package/dist/load-DwJUafYN.js +2 -0
  121. package/dist/manifest-iaoBXHKc.js +783 -0
  122. package/dist/manifest-uDS9y5de.d.ts +206 -0
  123. package/dist/{map-DowSO96j.js → map-AcxK9o_D.js} +4 -15
  124. package/dist/{map-claims-B_6I1Rn-.js → map-claims-Z4QZOiFF.js} +13 -4
  125. package/dist/map-error-pgUWNNtG.js +23 -0
  126. package/dist/may-use-DndQ5tQ-.js +77 -0
  127. package/dist/mcp/index.d.ts +54 -22
  128. package/dist/mcp/index.js +218 -178
  129. package/dist/nest/index.d.ts +26 -31
  130. package/dist/nest/index.js +75 -84
  131. package/dist/next/client.d.ts +7 -8
  132. package/dist/next/client.js +11 -9
  133. package/dist/next/index.d.ts +30 -47
  134. package/dist/next/index.js +45 -60
  135. package/dist/next/plugin.d.ts +1 -1
  136. package/dist/next/plugin.js +32 -1
  137. package/dist/node/index.d.ts +9 -28
  138. package/dist/node/index.js +11 -43
  139. package/dist/openai/index.d.ts +6 -15
  140. package/dist/openai/index.js +7 -20
  141. package/dist/openapi/index.d.ts +87 -4
  142. package/dist/openapi/index.js +3 -3
  143. package/dist/{openapi-DcqM8nU7.js → openapi-BqRBvAng.js} +237 -5
  144. package/dist/{openapi-DWD0Uc_4.js → openapi-CqqEbHsL.js} +5 -5
  145. package/dist/{openapi-import-BWRUNNeW.js → openapi-import-cmwetbzw.js} +1 -1
  146. package/dist/{openapi-CwePaF7C.js → openapi-l0xYHkcv.js} +3 -3
  147. package/dist/{openapi-schema-D8j_T9tO.js → openapi-schema--ARjyfQr.js} +2 -2
  148. package/dist/orpc/index.d.ts +15 -28
  149. package/dist/orpc/index.js +74 -117
  150. package/dist/otel/index.d.ts +2 -2
  151. package/dist/parse-BuyjxhZV.js +53 -0
  152. package/dist/parse-local-manifest-BrMozJCo.js +36 -0
  153. package/dist/parse-local-manifest-JLnaBH0S.d.ts +10 -0
  154. package/dist/pdp/index.d.ts +2 -2
  155. package/dist/pdp/index.js +14 -127
  156. package/dist/{permdock-yoZ7EjxX.js → permdock-CbgvLnuo.js} +672 -149
  157. package/dist/{permissions-B8vCg_bI.d.ts → permissions-CDgvKG5L.d.ts} +2 -2
  158. package/dist/{permissions-DC7GROPA.js → permissions-DVoF6dMt.js} +77 -15
  159. package/dist/{permissions-B4apnahQ.d.ts → permissions-DttnqqxC.d.ts} +34 -5
  160. package/dist/{permissions-CofRqesP.js → permissions-RJrovw98.js} +3 -3
  161. package/dist/{pins-CqE1jZuF.js → pins-8ILTftC9.js} +2 -2
  162. package/dist/{policy-Cq2fB8Q5.d.ts → policy-D2W7uv19.d.ts} +2 -2
  163. package/dist/{policy-CInmNGkM.d.ts → policy-IB6yxpC3.d.ts} +351 -42
  164. package/dist/{policy-ANp36PnI.js → policy-xyWg7QdR.js} +173 -22
  165. package/dist/powersync-BylawD_U.js +540 -0
  166. package/dist/powersync-DliZK_-y.js +62 -0
  167. package/dist/powersync-e_AZrKbg.js +2 -0
  168. package/dist/prisma/index.d.ts +3 -2
  169. package/dist/prisma/index.js +45 -20
  170. package/dist/{problem-Cgp9-KZA.js → problem-B43ztKIm.js} +74 -109
  171. package/dist/problem-details-DX075l9V.d.ts +7 -0
  172. package/dist/problem-details-_AMOYz-w.js +144 -0
  173. package/dist/{protected-BuPAycJT.d.ts → protected-BdnIEFkY.d.ts} +5 -4
  174. package/dist/protected-utU-SLuy.js +100 -0
  175. package/dist/protected-xAr24FEC.d.ts +40 -0
  176. package/dist/provider-CSQd7m92.js +52 -0
  177. package/dist/{provider-wTAUda2v.d.ts → provider-DZY281jO.d.ts} +1 -1
  178. package/dist/provider-store-Cfbvo4DE.js +43 -0
  179. package/dist/react/index.d.ts +7 -5
  180. package/dist/react/index.js +5 -4
  181. package/dist/react/provider-client.d.ts +1 -1
  182. package/dist/react/provider-client.js +1 -1
  183. package/dist/react-native/index.d.ts +166 -10
  184. package/dist/react-native/index.js +607 -85
  185. package/dist/{refs-ChLSxbST.js → refs-KYb4ntaz.js} +4 -3
  186. package/dist/{relations-r4soIFZB.js → relations-cjOL01hZ.js} +110 -31
  187. package/dist/replay-BsOyTrdV.js +28 -0
  188. package/dist/replay-CYyiLiwO.d.ts +5 -0
  189. package/dist/resource-ref-CaS9yVPi.js +20 -0
  190. package/dist/revocations-Bhr0aMIC.js +27 -0
  191. package/dist/{rls-BOFU3chG.js → rls-B4ur8nKQ.js} +8 -3
  192. package/dist/rls-DQtdgTHB.js +2521 -0
  193. package/dist/rls-fields-74oZPXWL.js +3582 -0
  194. package/dist/{rls-graph-DxQ39PJj.js → rls-graph-CHwx0V8q.js} +100 -36
  195. package/dist/{rls-import-CXV6LBlS.js → rls-import-BTFVk3jH.js} +7 -6
  196. package/dist/{rls-migrate-DrFPbf8p.js → rls-migrate-CN4TlJTO.js} +6 -5
  197. package/dist/rls-ownership-RuFqaqGz.js +843 -0
  198. package/dist/rls-shared-Cewd_-zI.js +495 -0
  199. package/dist/rls-verify-CVQTHTh5.js +746 -0
  200. package/dist/{row-check-BajTgQZ-.d.ts → row-check-Dt35czLy.d.ts} +12 -15
  201. package/dist/row-conditions-h2HythXQ.js +27 -0
  202. package/dist/rpc-DL9Sqvb1.js +89 -0
  203. package/dist/{run-CJ6UvS3a.js → run-DKDbM4Du.js} +31 -12
  204. package/dist/runtime-CB8gQFvS.js +11 -0
  205. package/dist/{saas-BRrZzJkM.js → saas-DhpJjnBA.js} +17 -7
  206. package/dist/{schema-P8yoma__.js → schema-D3MnDMxa.js} +8 -4
  207. package/dist/scim/index.d.ts +1 -1
  208. package/dist/scim/index.js +1 -1
  209. package/dist/{scim-9NFa0XVZ.js → scim-BFGBBAqR.js} +1 -1
  210. package/dist/{scopes-cKVp_WTw.js → scopes-CVdPrWN-.js} +35 -8
  211. package/dist/server/index.d.ts +22 -11
  212. package/dist/server/index.js +11 -8
  213. package/dist/{credentials-BgRX3WEg.js → server-DlZ28Oz5.js} +1 -1
  214. package/dist/shared-DRu8VFVM.js +123 -0
  215. package/dist/{sink-Cfr9-7YC.d.ts → sink-B1fZzIMf.d.ts} +1 -1
  216. package/dist/skills-Biq5uup3.js +192 -0
  217. package/dist/{skills-C_lpV4Jx.js → skills-Bx-U0irG.js} +10 -4
  218. package/dist/{snapshot-DE8dE_hz.js → snapshot-Enxd0n-s.js} +138 -214
  219. package/dist/snapshot-cache-B-LxUahj.d.ts +45 -0
  220. package/dist/snapshot-cache-D6jLelln.js +44 -0
  221. package/dist/solid/index.d.ts +39 -9
  222. package/dist/solid/index.js +76 -85
  223. package/dist/sources-4p0-4xQm.js +545 -0
  224. package/dist/sources-BdOYLw9j.d.ts +192 -0
  225. package/dist/{sql-Bll1d7y0.js → sql-xdg5f4Fi.js} +6 -1
  226. package/dist/{src-CTNT-PBW.js → src-BDwEZWrZ.js} +149 -11
  227. package/dist/ssf/index.d.ts +3 -2
  228. package/dist/ssf/index.js +3 -29
  229. package/dist/store-DMAWwAJV.js +371 -0
  230. package/dist/{store-Bq90395i.d.ts → store-jOBYpiXC.d.ts} +13 -4
  231. package/dist/store-options-ByhZq1jV.js +716 -0
  232. package/dist/{stores-CkG2HrSg.js → stores-BT2pSCub.js} +10 -63
  233. package/dist/{stores-CvOnu_2T.d.ts → stores-C0IMQrGI.d.ts} +20 -9
  234. package/dist/{stream-BhMfZOwr.d.ts → stream-D1RXubw2.d.ts} +2 -2
  235. package/dist/{stream-Q_xlB6Ko.js → stream-DDpvynSB.js} +16 -2
  236. package/dist/subject-settings-BwBmCdTR.js +128 -0
  237. package/dist/supabase/index.d.ts +133 -6
  238. package/dist/supabase/index.js +4 -3
  239. package/dist/supabase/middleware.d.ts +32 -27
  240. package/dist/supabase/middleware.js +56 -22
  241. package/dist/{supabase-D8UlBA7Z.js → supabase-Boqb9a_Y.js} +3 -3
  242. package/dist/{supabase-DTxGtYdi.js → supabase-C0qESOa-.js} +347 -367
  243. package/dist/supabase-config-BpPXt3x1.js +472 -0
  244. package/dist/supabase-hook-6sYLXg_u.js +2 -0
  245. package/dist/{supabase-hook-D_OqGb_V.js → supabase-hook-CSLQ4zJ3.js} +1037 -705
  246. package/dist/supautils-Cn4R4cJ4.js +91 -0
  247. package/dist/svelte/PermissionBoundary.svelte +37 -0
  248. package/dist/svelte/Protected.svelte +8 -2
  249. package/dist/svelte/index.d.ts +9 -6
  250. package/dist/svelte/index.js +61 -7
  251. package/dist/svelte/runtime.d.ts +4 -3
  252. package/dist/svelte/runtime.js +4 -3
  253. package/dist/svelte/source.d.ts +9 -6
  254. package/dist/svelte/source.js +7 -4
  255. package/dist/terminal/index.d.ts +6 -14
  256. package/dist/terminal/index.js +44 -32
  257. package/dist/testing/index.d.ts +105 -16
  258. package/dist/testing/index.js +504 -40
  259. package/dist/testing/saas/index.d.ts +4 -4
  260. package/dist/testing/saas/index.js +2 -2
  261. package/dist/testing/saas/permissions.d.ts +1 -1
  262. package/dist/testing/saas/permissions.js +1 -1
  263. package/dist/token-CIWxJOCh.js +50 -0
  264. package/dist/trpc/index.d.ts +7 -26
  265. package/dist/trpc/index.js +17 -100
  266. package/dist/types-BnIEz1sU.d.ts +48 -0
  267. package/dist/{types-CxquUByI.d.ts → types-Bvc4oWT6.d.ts} +1 -1
  268. package/dist/{sources-DOpW5Ila.d.ts → types-C97IskBw.d.ts} +86 -127
  269. package/dist/types-CKNSyIf_.d.ts +52 -0
  270. package/dist/types-Cq_H8N_X.d.ts +197 -0
  271. package/dist/{types-D6Z6-rKJ.d.ts → types-DbjRN55U.d.ts} +273 -22
  272. package/dist/{types-BBK-exvJ.d.ts → types-Dnemwd77.d.ts} +7 -1
  273. package/dist/{types-BvBk1wgn.d.ts → types-DxiCGmmd.d.ts} +5 -3
  274. package/dist/{types-B9dmiiab.d.ts → types-QCuYJh4q.d.ts} +1 -1
  275. package/dist/{types-B8w5PFtS.d.ts → types-aqOHTHcp.d.ts} +17 -1
  276. package/dist/unplugin/index.d.ts +1 -1
  277. package/dist/unplugin/index.js +10 -9
  278. package/dist/usage-DorAJiU_.js +2 -0
  279. package/dist/{usage-1ozMubLY.js → usage-GTixvDDU.js} +2 -2
  280. package/dist/{usage-CUnK6Ou9.js → usage-ItS7nLcl.js} +225 -62
  281. package/dist/validation-error-Ba264OaJ.d.ts +26 -0
  282. package/dist/validation-error-CnjZ4t92.js +33 -0
  283. package/dist/{validity-D0uRmJKJ.js → validity-uOp8UpYn.js} +57 -36
  284. package/dist/{verifier-B90agnhl.js → verifier-BxBo6I44.js} +10 -5
  285. package/dist/version-BvacVTZR.js +17 -0
  286. package/dist/views-Soe7bGSC.js +95 -0
  287. package/dist/{vocabulary-Dk0fs-Jk.d.ts → vocabulary-DA8a8zif.d.ts} +1 -1
  288. package/dist/vue/index.d.ts +35 -13
  289. package/dist/vue/index.js +86 -93
  290. package/dist/watch-BwxLTYMZ.js +2 -0
  291. package/dist/watch-DVBJ9cBq.js +128 -0
  292. package/dist/webmcp/index.d.ts +2 -2
  293. package/dist/webmcp/index.js +7 -13
  294. package/package.json +62 -36
  295. package/schemas/approval-request-v1.json +175 -0
  296. package/schemas/catalog-v1.json +29 -1
  297. package/schemas/supabase-claims-v1.json +6 -1
  298. package/schemas/supabase-manifest-v1.json +150 -6
  299. package/skills/permdock/SKILL.md +7 -7
  300. package/skills/permdock-agents/SKILL.md +5 -5
  301. package/skills/permdock-agents/references/runtimes.md +12 -10
  302. package/skills/permdock-approvals/SKILL.md +4 -4
  303. package/skills/permdock-approvals/references/stores-and-resume.md +15 -4
  304. package/skills/permdock-audit/SKILL.md +3 -3
  305. package/skills/permdock-credentials/SKILL.md +4 -4
  306. package/skills/permdock-credentials/references/tokens-keys-links.md +7 -2
  307. package/skills/permdock-data/SKILL.md +4 -4
  308. package/skills/permdock-data/references/queries-and-rls.md +14 -6
  309. package/skills/permdock-data/references/relationships.md +5 -3
  310. package/skills/permdock-tenancy/SKILL.md +9 -6
  311. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +14 -8
  312. package/skills/permdock-wire/SKILL.md +4 -4
  313. package/skills/permdock-wire/references/adapters.md +42 -30
  314. package/skills/permdock-wire/references/policy-options.md +5 -3
  315. package/dist/approvers-aH0PJkmf.js +0 -20
  316. package/dist/catalog-idsHc0vo.js +0 -2
  317. package/dist/collect-B7_TIfmg.js +0 -49
  318. package/dist/collect-fRPAQIWb.js +0 -2
  319. package/dist/compile-CQbvyIBr.d.ts +0 -30
  320. package/dist/config-BPYmjTOp.js +0 -26
  321. package/dist/context-gtAwAILx.js +0 -6
  322. package/dist/create-BKF3dgFk.d.ts +0 -162
  323. package/dist/create-CSkhDU9T.js +0 -222
  324. package/dist/doctor-BGle8Gqi.js +0 -1314
  325. package/dist/helpers-DnSOrhB9.js +0 -682
  326. package/dist/index-fG6NSXlc.d.ts +0 -9
  327. package/dist/kernel-YOGCWNLf.js +0 -211
  328. package/dist/load-QOOm1z70.js +0 -2
  329. package/dist/manifest-DDQbC6K0.d.ts +0 -118
  330. package/dist/map-error-RJ3ZsUyA.js +0 -16
  331. package/dist/may-use-CnFEjVEp.js +0 -37
  332. package/dist/pg-4GJWDUxG.js +0 -953
  333. package/dist/plugin-4gr7IxFJ.js +0 -65
  334. package/dist/protected-B4K3BKoX.js +0 -138
  335. package/dist/protected-wY7nXhY9.d.ts +0 -23
  336. package/dist/provider-_wx46HpB.js +0 -48
  337. package/dist/rls-CJVja9Ww.js +0 -2119
  338. package/dist/rls-helpers-BxRuif0l.js +0 -1346
  339. package/dist/runtime-DP9ttFh1.js +0 -27
  340. package/dist/skills-Dx3QLnMY.js +0 -110
  341. package/dist/source-DoBBYJi2.js +0 -6
  342. package/dist/sources-DVHM3Mwq.js +0 -284
  343. package/dist/store-C6hFW6ta.js +0 -508
  344. package/dist/subject-settings-xt7V845g.js +0 -82
  345. package/dist/supabase-hook-DW-xP4hU.js +0 -2
  346. package/dist/types-DbWdEfcm.d.ts +0 -87
  347. package/dist/types-bva3i4Gf.d.ts +0 -98
  348. package/dist/types-i4BAOvdb.d.ts +0 -97
  349. package/dist/usage-PyEor6GA.js +0 -2
  350. package/dist/version-CnSh33Fj.js +0 -16
@@ -0,0 +1,843 @@
1
+ import { l as scopeChain, o as resolveScope } from "./scopes-CVdPrWN-.js";
2
+ import { S as findRole } from "./grantee-Cv5Sogfv.js";
3
+ import { l as roleColumn } from "./sources-4p0-4xQm.js";
4
+ import { B as memberVia, G as quoteIdent, I as memberForSources, K as quoteLiteral, L as memberForTable, M as kindFilterSql, S as globalKindFilterSql, W as qualifiedTable, X as scopeSources, Y as roleKindSql, Z as scopeTable, at as tenantTypeOf, c as signedIn, o as qualified, q as quoteTable, rt as subjectIdSql, t as CUSTOM_ROLES, z as memberRoleOf } from "./rls-shared-Cewd_-zI.js";
5
+ import { D as claimKeptLines, I as sourceFilters, L as sourcesKeep, M as membershipRows, N as membershipTable, O as disabledColumn, P as roleRows, R as hasCustomRoleChecksFor, j as memberColumn, k as globalRoleRows } from "./rls-fields-74oZPXWL.js";
6
+ //#region src/cli/rls-ownership.ts
7
+ /** Objects the ownership rules add next to the helpers. Names are part of the SQL contract. */
8
+ const OWNERSHIP = {
9
+ canAssign: "permdock_can_assign",
10
+ canAssignFor: "permdock_can_assign_for",
11
+ canAssignAny: "permdock_can_assign_any",
12
+ canAssignAnyFor: "permdock_can_assign_any_for",
13
+ holders: "permdock_holders",
14
+ transferOnly: "permdock_transfer_only"
15
+ };
16
+ function roleScope(policy, scopes, name) {
17
+ const on = policy.rolesByName.get(name)?.on ?? findRole(policy.vocabulary.roles, name)?.on;
18
+ return typeof on === "string" ? resolveScope(scopes, on) : void 0;
19
+ }
20
+ function isGlobalRole(policy, name) {
21
+ const binding = policy.rolesByName.get(name);
22
+ if (binding !== void 0) return binding.on === void 0;
23
+ const leaf = findRole(policy.vocabulary.roles, name);
24
+ return leaf !== void 0 && leaf.on === void 0;
25
+ }
26
+ /** The rules `rls generate` enforces; `undefined` when no role declares one. */
27
+ function ownershipRules(policy, scopes) {
28
+ const kinds = {};
29
+ const assigns = [];
30
+ const counted = [];
31
+ for (const binding of policy.roles) {
32
+ const scope = roleScope(policy, scopes, binding.name);
33
+ if (binding.for !== void 0) kinds[binding.name] = binding.for;
34
+ const assigner = scope ?? (binding.on === void 0 ? "global" : void 0);
35
+ for (const target of binding.assigns ?? []) {
36
+ const at = roleScope(policy, scopes, target);
37
+ if (assigner !== void 0 && at !== void 0 && (assigner === "global" || scopeChain(scopes, at).includes(assigner))) assigns.push({
38
+ assigner: binding.name,
39
+ scope: assigner,
40
+ role: target,
41
+ at
42
+ });
43
+ else if (assigner === "global" && isGlobalRole(policy, target)) assigns.push({
44
+ assigner: binding.name,
45
+ scope: "global",
46
+ role: target,
47
+ at: "global"
48
+ });
49
+ }
50
+ const min = binding.min ?? 0;
51
+ const transferOnly = binding.transferOnly === true;
52
+ if (scope !== void 0 && (min > 0 || binding.max !== void 0 || transferOnly)) counted.push(binding.max === void 0 ? {
53
+ role: binding.name,
54
+ scope,
55
+ min,
56
+ transferOnly
57
+ } : {
58
+ role: binding.name,
59
+ scope,
60
+ min,
61
+ max: binding.max,
62
+ transferOnly
63
+ });
64
+ }
65
+ if (Object.keys(kinds).length === 0 && assigns.length === 0 && counted.length === 0) return;
66
+ return {
67
+ kinds,
68
+ assigns,
69
+ counted
70
+ };
71
+ }
72
+ function objectName(prefix, scope) {
73
+ return quoteIdent(`${prefix}_${scope}`);
74
+ }
75
+ /** Live holders of `roleExpr` in instance `idExpr`, with the same kind and expiry rules as the helpers. */
76
+ function holdersSql(ctx, table, column, idExpr, role, into) {
77
+ const roleExpr = typeof role === "string" ? quoteLiteral(role) : role.expr;
78
+ const held = memberRoleOf(table, "m", " ");
79
+ const filters = [`m.${quoteIdent(column)} = ${idExpr}`, `${held.sql} = ${roleExpr}`];
80
+ if (table.expiresAt !== void 0) {
81
+ const expires = memberColumn(table.expiresAt);
82
+ filters.push(`(${expires} is null or ${expires} > now())`);
83
+ }
84
+ const disabled = disabledColumn(table);
85
+ if (disabled !== void 0) filters.push(`${disabled} is null`);
86
+ const via = memberVia(table);
87
+ const kind = typeof role === "string" ? roleKindSql(ctx, role, via) : kindFilterSql(ctx, roleExpr, via);
88
+ if (kind !== void 0) filters.push(kind);
89
+ return `select count(distinct m.${quoteIdent(table.user)}) into ${into}
90
+ from ${membershipTable(table.table)} m${held.join}
91
+ where ${filters.join("\n and ")}`;
92
+ }
93
+ /**
94
+ * `min` and `max` as a deferred constraint trigger: checked at commit, so a
95
+ * transfer (demote one holder, promote another) passes in one transaction. An
96
+ * instance with no memberships left is being torn down and skips `min`.
97
+ */
98
+ function holdersTriggerSql(ctx, scope, table, column, counted) {
99
+ const fn = qualified(ctx, `${OWNERSHIP.holders}_${scope}`);
100
+ const col = quoteIdent(column);
101
+ const checks = counted.map((rule) => {
102
+ const lines = [` select count(*) into v_total from ${membershipTable(table.table)} m where m.${col} = v_key;`, ` ${holdersSql(ctx, table, column, "v_key", rule.role, "v_count")};`];
103
+ if (rule.min > 0) lines.push(` if v_total > 0 and v_count < ${String(rule.min)} then
104
+ raise exception using
105
+ errcode = '23514',
106
+ message = ${quoteLiteral(`permdock: ${scope} `)} || v_id || ${quoteLiteral(` keeps at least ${String(rule.min)} ${rule.role}`)},
107
+ hint = 'last-holder';
108
+ end if;`);
109
+ if (rule.max !== void 0) lines.push(` if v_count > ${String(rule.max)} then
110
+ raise exception using
111
+ errcode = '23514',
112
+ message = ${quoteLiteral(`permdock: ${scope} `)} || v_id || ${quoteLiteral(` has at most ${String(rule.max)} ${rule.role}`)},
113
+ hint = 'max-holders';
114
+ end if;`);
115
+ return lines.join("\n");
116
+ });
117
+ return `-- ${scope}: holder counts (min / max), checked at commit
118
+ create or replace function ${fn}()
119
+ returns trigger
120
+ language plpgsql
121
+ security definer
122
+ set search_path = ''
123
+ as $$
124
+ declare
125
+ v_ids text[] := '{}';
126
+ v_id text;
127
+ v_key ${membershipTable(table.table)}.${col}%type;
128
+ v_total bigint;
129
+ v_count bigint;
130
+ begin
131
+ if tg_op in ('UPDATE', 'DELETE') then
132
+ v_ids := v_ids || old.${col}::text;
133
+ end if;
134
+ if tg_op in ('INSERT', 'UPDATE') then
135
+ v_ids := v_ids || new.${col}::text;
136
+ end if;
137
+ foreach v_id in array v_ids loop
138
+ continue when v_id is null;
139
+ v_key := v_id;
140
+ ${checks.join("\n")}
141
+ end loop;
142
+ return null;
143
+ end;
144
+ $$;
145
+ drop trigger if exists ${objectName(OWNERSHIP.holders, scope)} on ${membershipTable(table.table)};
146
+ create constraint trigger ${objectName(OWNERSHIP.holders, scope)}
147
+ after insert or update or delete on ${membershipTable(table.table)}
148
+ deferrable initially deferred
149
+ for each row execute function ${fn}();`;
150
+ }
151
+ /**
152
+ * `transferOnly` as statement triggers over transition tables: a statement
153
+ * that changes a role's holder count in an instance that had holders before
154
+ * and still has some after is refused. Creating the first holder and removing
155
+ * the last (which `min` guards) are not transfers.
156
+ */
157
+ function transferTriggerSql(ctx, scope, table, column, roles) {
158
+ const fn = qualified(ctx, `${OWNERSHIP.transferOnly}_${scope}`);
159
+ const col = quoteIdent(column);
160
+ const list = `array[${roles.map(quoteLiteral).join(", ")}]::text[]`;
161
+ const rows = (alias, source, delta) => {
162
+ const role = memberRoleOf(table, alias);
163
+ return `select ${alias}.${col} as id, ${role.sql} as role, ${delta} as delta from ${source} ${alias}${role.join} where ${role.sql} = any(${list})`;
164
+ };
165
+ const loop = (source) => ` for v_change in
166
+ select c.id, c.role, sum(c.delta) as delta
167
+ from (${source}) c
168
+ group by c.id, c.role
169
+ loop
170
+ continue when v_change.delta = 0 or v_change.id is null;
171
+ ${holdersSql(ctx, table, column, "v_change.id", { expr: "v_change.role" }, "v_after")};
172
+ if v_after > 0 and v_after - v_change.delta > 0 then
173
+ raise exception using
174
+ errcode = '23514',
175
+ message = 'permdock: ' || v_change.role || ${quoteLiteral(` in ${scope} `)} || v_change.id || ' moves only by transfer',
176
+ hint = 'transfer-only';
177
+ end if;
178
+ end loop;`;
179
+ const inserted = rows("n", "permdock_new", "1");
180
+ const deleted = rows("o", "permdock_old", "-1");
181
+ const target = membershipTable(table.table);
182
+ const trigger = (op) => {
183
+ const name = quoteIdent(`${OWNERSHIP.transferOnly}_${scope}_${op}`);
184
+ return `drop trigger if exists ${name} on ${target};
185
+ create trigger ${name}
186
+ after ${op} on ${target}
187
+ referencing ${op === "insert" ? "new table as permdock_new" : op === "delete" ? "old table as permdock_old" : "old table as permdock_old new table as permdock_new"}
188
+ for each statement execute function ${fn}();`;
189
+ };
190
+ return `-- ${scope}: transfer-only roles (${roles.join(", ")}) keep their holder count per statement
191
+ create or replace function ${fn}()
192
+ returns trigger
193
+ language plpgsql
194
+ security definer
195
+ set search_path = ''
196
+ as $$
197
+ declare
198
+ v_change record;
199
+ v_after bigint;
200
+ begin
201
+ if tg_op = 'INSERT' then
202
+ ${loop(inserted)}
203
+ elsif tg_op = 'DELETE' then
204
+ ${loop(deleted)}
205
+ else
206
+ ${loop(`${inserted} union all ${deleted}`)}
207
+ end if;
208
+ return null;
209
+ end;
210
+ $$;
211
+ ${trigger("insert")}
212
+ ${trigger("update")}
213
+ ${trigger("delete")}`;
214
+ }
215
+ /**
216
+ * The membership sources holding scope `name`: `undefined` when there are
217
+ * none, `'not-tables'` when one of them carries no table to put a trigger on.
218
+ */
219
+ function holderSources(ctx, name) {
220
+ const sources = memberForSources(ctx, name);
221
+ if (sources.length === 0) return;
222
+ const held = [];
223
+ for (const source of sources) {
224
+ if (source.sql.holders === void 0) return "not-tables";
225
+ held.push({
226
+ table: source.sql.table,
227
+ holders: source.sql.holders
228
+ });
229
+ }
230
+ return held;
231
+ }
232
+ function indent(sql, by) {
233
+ return sql.replaceAll(/^/gmu, by);
234
+ }
235
+ /** Every source's rows of one instance, each compared with its own typed `v_key_<n>`. */
236
+ function sourceRowsOf(scope, sources) {
237
+ return sources.map((source, index) => indent(source.holders.rows(scope, { id: `v_key_${String(index)}` }), " ")).join("\n union all\n");
238
+ }
239
+ function sourceKeys(sources) {
240
+ return {
241
+ declare: sources.map((source, index) => ` v_key_${String(index)} ${source.holders.idType};`).join("\n"),
242
+ assign: (value, by) => sources.map((_, index) => `${by}v_key_${String(index)} := ${value};`).join("\n")
243
+ };
244
+ }
245
+ function regclassOf(table) {
246
+ return `${quoteLiteral(membershipTable(table))}::regclass`;
247
+ }
248
+ function sourceTables(sources) {
249
+ return [...new Set(sources.map((source) => membershipTable(source.table)))];
250
+ }
251
+ /** `min` and `max` over membership sources: the same commit-time check as on a mapped table, counted over every source of the scope. */
252
+ function sourceHoldersTriggerSql(ctx, scope, sources, counted) {
253
+ const fn = qualified(ctx, `${OWNERSHIP.holders}_${scope}`);
254
+ const keys = sourceKeys(sources);
255
+ const rows = sourceRowsOf(scope, sources);
256
+ const collect = sources.map((source) => ` if tg_relid = ${regclassOf(source.table)} then
257
+ if tg_op in ('UPDATE', 'DELETE') then
258
+ v_ids := v_ids || ${source.holders.id(scope, "old")};
259
+ end if;
260
+ if tg_op in ('INSERT', 'UPDATE') then
261
+ v_ids := v_ids || ${source.holders.id(scope, "new")};
262
+ end if;
263
+ end if;`);
264
+ const checks = counted.map((rule) => {
265
+ const filters = ["h.live", `h.role = ${quoteLiteral(rule.role)}`];
266
+ const kind = roleKindSql(ctx, rule.role, "h.via");
267
+ if (kind !== void 0) filters.push(kind);
268
+ const lines = [` select count(distinct h.user_id) into v_count
269
+ from (
270
+ ${rows}
271
+ ) h
272
+ where ${filters.join("\n and ")};`];
273
+ if (rule.min > 0) lines.push(` if v_total > 0 and v_count < ${String(rule.min)} then
274
+ raise exception using
275
+ errcode = '23514',
276
+ message = ${quoteLiteral(`permdock: ${scope} `)} || v_id || ${quoteLiteral(` keeps at least ${String(rule.min)} ${rule.role}`)},
277
+ hint = 'last-holder';
278
+ end if;`);
279
+ if (rule.max !== void 0) lines.push(` if v_count > ${String(rule.max)} then
280
+ raise exception using
281
+ errcode = '23514',
282
+ message = ${quoteLiteral(`permdock: ${scope} `)} || v_id || ${quoteLiteral(` has at most ${String(rule.max)} ${rule.role}`)},
283
+ hint = 'max-holders';
284
+ end if;`);
285
+ return lines.join("\n");
286
+ });
287
+ const triggers = sourceTables(sources).map((table) => `drop trigger if exists ${objectName(OWNERSHIP.holders, scope)} on ${table};
288
+ create constraint trigger ${objectName(OWNERSHIP.holders, scope)}
289
+ after insert or update or delete on ${table}
290
+ deferrable initially deferred
291
+ for each row execute function ${fn}();`);
292
+ return `-- ${scope}: holder counts (min / max) over the membership sources, checked at commit
293
+ create or replace function ${fn}()
294
+ returns trigger
295
+ language plpgsql
296
+ security definer
297
+ set search_path = ''
298
+ as $$
299
+ declare
300
+ v_ids text[] := '{}';
301
+ v_id text;
302
+ ${keys.declare}
303
+ v_total bigint;
304
+ v_count bigint;
305
+ begin
306
+ ${collect.join("\n")}
307
+ foreach v_id in array v_ids loop
308
+ continue when v_id is null;
309
+ ${keys.assign("v_id", " ")}
310
+ select count(*) into v_total
311
+ from (
312
+ ${rows}
313
+ ) h;
314
+ ${checks.join("\n")}
315
+ end loop;
316
+ return null;
317
+ end;
318
+ $$;
319
+ ${triggers.join("\n")}`;
320
+ }
321
+ function collectChangesSql(changed) {
322
+ return ` v_changes := v_changes || coalesce((select jsonb_agg(to_jsonb(c)) from (${changed}) c), '[]'::jsonb);`;
323
+ }
324
+ /** `transferOnly` over membership sources: each source table's statement triggers diff its transition tables, and the count after reads every source. */
325
+ function sourceTransferTriggerSql(ctx, scope, sources, roles) {
326
+ const fn = qualified(ctx, `${OWNERSHIP.transferOnly}_${scope}`);
327
+ const keys = sourceKeys(sources);
328
+ const list = `array[${roles.map(quoteLiteral).join(", ")}]::text[]`;
329
+ const kind = kindFilterSql(ctx, "h.role", "h.via");
330
+ const changes = (source, from, delta) => `select h.id, h.role, ${delta} as delta from (${source.holders.rows(scope, { from }).replaceAll("\n", " ")}) h where h.role = any(${list})`;
331
+ const branches = sources.map((source) => ` if tg_relid = ${regclassOf(source.table)} then
332
+ if tg_op in ('INSERT', 'UPDATE') then
333
+ ${collectChangesSql(changes(source, "permdock_new", "1"))}
334
+ end if;
335
+ if tg_op in ('UPDATE', 'DELETE') then
336
+ ${collectChangesSql(changes(source, "permdock_old", "-1"))}
337
+ end if;
338
+ end if;`);
339
+ const triggers = sourceTables(sources).flatMap((table) => [
340
+ "insert",
341
+ "update",
342
+ "delete"
343
+ ].map((op) => {
344
+ const name = quoteIdent(`${OWNERSHIP.transferOnly}_${scope}_${op}`);
345
+ return `drop trigger if exists ${name} on ${table};
346
+ create trigger ${name}
347
+ after ${op} on ${table}
348
+ referencing ${op === "insert" ? "new table as permdock_new" : op === "delete" ? "old table as permdock_old" : "old table as permdock_old new table as permdock_new"}
349
+ for each statement execute function ${fn}();`;
350
+ }));
351
+ return `-- ${scope}: transfer-only roles (${roles.join(", ")}) keep their holder count per statement, over the membership sources
352
+ create or replace function ${fn}()
353
+ returns trigger
354
+ language plpgsql
355
+ security definer
356
+ set search_path = ''
357
+ as $$
358
+ declare
359
+ v_changes jsonb := '[]'::jsonb;
360
+ v_change record;
361
+ v_after bigint;
362
+ ${keys.declare}
363
+ begin
364
+ ${branches.join("\n")}
365
+ for v_change in
366
+ select x.id, x.role, sum(x.delta) as delta
367
+ from jsonb_to_recordset(v_changes) x(id text, role text, delta bigint)
368
+ group by x.id, x.role
369
+ loop
370
+ continue when v_change.delta = 0 or v_change.id is null;
371
+ ${keys.assign("v_change.id", " ")}
372
+ select count(distinct h.user_id) into v_after
373
+ from (
374
+ ${sourceRowsOf(scope, sources)}
375
+ ) h
376
+ where h.live
377
+ and h.role = v_change.role${kind === void 0 ? "" : `\n and ${kind}`};
378
+ if v_after > 0 and v_after - v_change.delta > 0 then
379
+ raise exception using
380
+ errcode = '23514',
381
+ message = 'permdock: ' || v_change.role || ${quoteLiteral(` in ${scope} `)} || v_change.id || ' moves only by transfer',
382
+ hint = 'transfer-only';
383
+ end if;
384
+ end loop;
385
+ return null;
386
+ end;
387
+ $$;
388
+ ${triggers.join("\n")}`;
389
+ }
390
+ function pairsSql(pairs) {
391
+ return `values ${pairs.map((pair) => `(${quoteLiteral(pair.assigner)}, ${quoteLiteral(pair.role)})`).join(", ")}`;
392
+ }
393
+ /**
394
+ * Membership rows of the membership sources for scope `name`, each source's
395
+ * `select` comparing its user column with a variable `user` names.
396
+ */
397
+ function sourceRowsSql(sources, user) {
398
+ const keep = sources.some((source) => source.sql.keeps);
399
+ return sources.map((source) => source.sql.select(user(source), keep).replaceAll(/^/gmu, " ")).join("\n union all\n");
400
+ }
401
+ /** Whether the signed-in user (or `p_user` in the `_for` form) holds, in instance `p_scope_id`, a role whose `assigns` lists `p_role`. */
402
+ function canAssignSql(base, own, forUser = false) {
403
+ const ctx = forUser ? {
404
+ ...base,
405
+ subjectId: "p_user",
406
+ tenants: "all"
407
+ } : base;
408
+ const fn = qualified(ctx, forUser ? OWNERSHIP.canAssignFor : OWNERSHIP.canAssign);
409
+ const parts = [];
410
+ const used = [];
411
+ const userOf = (source) => {
412
+ const index = used.includes(source) ? used.indexOf(source) : used.push(source) - 1;
413
+ return `v_user_${String(index)}`;
414
+ };
415
+ const global = own.assigns.filter((pair) => pair.scope === "global");
416
+ for (const [instance, pairs] of [["p_scope_id is not null", global.filter((pair) => pair.at !== "global")], ["p_scope_id is null", global.filter((pair) => pair.at === "global")]]) {
417
+ if (pairs.length === 0) continue;
418
+ if (ctx.authorize === "database") {
419
+ const ur = globalRoleRows(ctx);
420
+ const kind = globalKindFilterSql(ctx, ur.roleSql);
421
+ parts.push(`${instance} and exists (
422
+ select 1 from ${ur.from}
423
+ where ${ur.userSql} = ${subjectIdSql(ctx)}
424
+ and (${ur.roleSql}, p_role) in (${pairsSql(pairs)})${kind === void 0 ? "" : `\n and ${kind}`}
425
+ )`);
426
+ } else {
427
+ const kind = globalKindFilterSql(ctx, "r.role");
428
+ parts.push(`${instance} and exists (
429
+ select 1 from ${roleRows(ctx)}
430
+ where (r.role, p_role) in (${pairsSql(pairs)})${kind === void 0 ? "" : `\n and ${kind}`}
431
+ )`);
432
+ }
433
+ }
434
+ for (const { name } of ctx.scopes) {
435
+ const pairs = own.assigns.filter((pair) => pair.scope === name);
436
+ if (pairs.length === 0) continue;
437
+ const sources = scopeSources(ctx, name);
438
+ if (sources.length > 0) {
439
+ const kind = kindFilterSql(ctx, "r.role", "ms.via");
440
+ parts.push(`exists (
441
+ select 1
442
+ from (
443
+ ${sourceRowsSql(sources, userOf)}
444
+ ) ms
445
+ cross join lateral jsonb_array_elements_text(
446
+ case jsonb_typeof(ms.roles) when 'array' then ms.roles else '[]'::jsonb end
447
+ ) r(role)
448
+ where ms.scope = ${quoteLiteral(name)}
449
+ and ms.id = p_scope_id
450
+ and (r.role, p_role) in (${pairsSql(pairs)})${kind === void 0 ? "" : `\n and ${kind}`}${sourceFilters(ctx, name, void 0, void 0, sourcesKeep(sources)).replaceAll("\n and ", "\n and ")}
451
+ )`);
452
+ continue;
453
+ }
454
+ if (ctx.authorize === "database") {
455
+ const mapped = scopeTable(ctx, name);
456
+ if (mapped === void 0) continue;
457
+ const { table, column } = mapped;
458
+ const role = memberRoleOf(table, "m", " ");
459
+ const filters = [
460
+ `${memberColumn(table.user)} = ${subjectIdSql(ctx)}`,
461
+ `${memberColumn(column)}::text = p_scope_id`,
462
+ `(${role.sql}, p_role) in (${pairsSql(pairs)})`
463
+ ];
464
+ if (table.expiresAt !== void 0) {
465
+ const expires = memberColumn(table.expiresAt);
466
+ filters.push(`(${expires} is null or ${expires} > now())`);
467
+ }
468
+ const disabled = disabledColumn(table);
469
+ if (disabled !== void 0) filters.push(`${disabled} is null`);
470
+ const kind = kindFilterSql(ctx, role.sql, memberVia(table));
471
+ if (kind !== void 0) filters.push(kind);
472
+ parts.push(`exists (
473
+ select 1 from ${membershipTable(table.table)} m${role.join}
474
+ where ${filters.join("\n and ")}
475
+ )`);
476
+ } else {
477
+ const kind = kindFilterSql(ctx, "r.role", "m ->> 'via'");
478
+ parts.push(`exists (
479
+ select 1 from ${membershipRows(ctx)}
480
+ where m ->> 'scope' = ${quoteLiteral(name)}
481
+ and m ->> 'id' = p_scope_id
482
+ and (r.role, p_role) in (${pairsSql(pairs)})
483
+ and case jsonb_typeof(m -> 'expiresAt')
484
+ when 'number' then (m ->> 'expiresAt')::numeric > extract(epoch from now())
485
+ else true
486
+ end${kind === void 0 ? "" : `\n and ${kind}`}${claimKeptLines(ctx, " ").map((line) => `\n${line}`).join("")}
487
+ )`);
488
+ }
489
+ }
490
+ const body = parts.length === 0 ? "false" : parts.join("\n or ");
491
+ const language = used.length === 0 ? `language sql
492
+ stable
493
+ security definer
494
+ set search_path = ''
495
+ as $$
496
+ select ${signedIn(ctx)} and (
497
+ ${body}
498
+ )
499
+ $$;` : `language plpgsql
500
+ stable
501
+ security definer
502
+ set search_path = ''
503
+ as $$
504
+ declare
505
+ ${used.map((source, index) => ` v_user_${String(index)} ${source.sql.userType} := ${subjectIdSql(ctx)};`).join("\n")}
506
+ begin
507
+ return ${signedIn(ctx)} and (
508
+ ${body}
509
+ );
510
+ end;
511
+ $$;`;
512
+ if (forUser) {
513
+ const user = ctx.dialect === "supabase" ? "uuid" : "text";
514
+ return `-- who may assign, for a user the caller names: trusted SQL acting later for a stored user; no client role may execute it
515
+ create or replace function ${fn}(p_user ${user}, p_role text, p_scope_id text)
516
+ returns boolean
517
+ ${language}
518
+ revoke execute on function ${fn}(${user}, text, text) from public, anon, authenticated;`;
519
+ }
520
+ return `-- who may assign: a held role whose assigns lists p_role, in instance p_scope_id (its scope or an ancestor's); a null p_scope_id assigns a global role, which only global assigners may
521
+ create or replace function ${fn}(p_role text, p_scope_id text)
522
+ returns boolean
523
+ ${language}
524
+ revoke execute on function ${fn}(text, text) from public, anon;
525
+ grant execute on function ${fn}(text, text) to authenticated;`;
526
+ }
527
+ /**
528
+ * The ownership objects: holder-count and transfer-only triggers on each
529
+ * scope's membership table, and `permdock_can_assign` for the application's
530
+ * own policies on those tables. Empty when no role declares a rule.
531
+ */
532
+ function ownershipSql(ctx) {
533
+ const own = ctx.ownership;
534
+ if (own === void 0) return "";
535
+ const chunks = [];
536
+ const skipped = ctx.skipOwnershipTriggers;
537
+ for (const { name } of ctx.scopes) {
538
+ const counted = own.counted.filter((rule) => rule.scope === name);
539
+ if (counted.length === 0) continue;
540
+ if (skipped === "all" || skipped?.includes(name) === true) {
541
+ chunks.push(`-- ${name}: rls.ownershipTriggers leaves out the triggers, so min, max and transferOnly are checked only by decideRoleChange`);
542
+ continue;
543
+ }
544
+ const transfer = counted.filter((rule) => rule.transferOnly).map((rule) => rule.role);
545
+ const mapped = scopeTable(ctx, name);
546
+ if (mapped === void 0) {
547
+ const sources = holderSources(ctx, name);
548
+ if (sources === void 0) chunks.push(`-- ${name}: no memberships table configured, so min, max and transferOnly are checked only by decideRoleChange`);
549
+ else if (sources === "not-tables") chunks.push(`-- ${name}: a membership source is not a table, so min, max and transferOnly are checked only by decideRoleChange`);
550
+ else {
551
+ chunks.push(sourceHoldersTriggerSql(ctx, name, sources, counted));
552
+ if (transfer.length > 0) chunks.push(sourceTransferTriggerSql(ctx, name, sources, transfer));
553
+ }
554
+ continue;
555
+ }
556
+ chunks.push(holdersTriggerSql(ctx, name, mapped.table, mapped.column, counted));
557
+ if (transfer.length > 0) chunks.push(transferTriggerSql(ctx, name, mapped.table, mapped.column, transfer));
558
+ }
559
+ if (own.assigns.length > 0) {
560
+ chunks.push(canAssignSql(ctx, own));
561
+ if (ctx.authorize === "database") chunks.push(canAssignSql(ctx, own, true));
562
+ const custom = assignsCustomRoles(ctx);
563
+ if (custom) {
564
+ chunks.push(canAssignCustomSql(ctx));
565
+ if (hasCustomRoleChecksFor(ctx)) chunks.push(canAssignCustomSql(ctx, true));
566
+ }
567
+ chunks.push(canAssignAnySql(ctx, custom));
568
+ if (hasCanAssignAnyFor(ctx)) chunks.push(canAssignAnySql(ctx, custom, true));
569
+ }
570
+ return chunks.length === 0 ? "" : `${chunks.join("\n\n")}\n`;
571
+ }
572
+ /** Whether the helpers keep custom roles in tables, so assigning one is checked against its stored definition. */
573
+ function assignsCustomRoles(ctx) {
574
+ return ctx.customRoles !== void 0 && ctx.authorize === "database";
575
+ }
576
+ /** Whether `permdock_can_assign_any_for` is written: every function it calls has a `_for` form. */
577
+ function hasCanAssignAnyFor(ctx) {
578
+ return ctx.authorize === "database" && (!assignsCustomRoles(ctx) || hasCustomRoleChecksFor(ctx));
579
+ }
580
+ /**
581
+ * `permdock_can_assign_any`: one check for a declared or a custom role, so
582
+ * SQL that assigns roles calls one function: `permdock_can_assign` for a
583
+ * declared role, `permdock_can_assign_custom_role` for a custom one. `p_scope`
584
+ * `'global'` (no tenant, no instance) assigns a platform role.
585
+ */
586
+ function canAssignAnySql(ctx, custom, forUser = false) {
587
+ const tenantType = tenantTypeOf(ctx);
588
+ const fn = qualified(ctx, forUser ? OWNERSHIP.canAssignAnyFor : OWNERSHIP.canAssignAny);
589
+ const declaredFn = qualified(ctx, forUser ? OWNERSHIP.canAssignFor : OWNERSHIP.canAssign);
590
+ const customFn = qualified(ctx, forUser ? ASSIGNMENTS.customFor : ASSIGNMENTS.custom);
591
+ const userType = ctx.dialect === "supabase" ? "uuid" : "text";
592
+ const userParam = forUser ? `p_user ${userType}, ` : "";
593
+ const userArg = forUser ? `${userType}, ` : "";
594
+ const user = forUser ? "p_user, " : "";
595
+ const declared = `${declaredFn}(${user}p_role, case when p_scope = 'global' then null else p_scope_id end)`;
596
+ const body = custom && ctx.customRoles !== void 0 ? `case
597
+ when p_role = any(${declaredArray(ctx.customRoles.declared)}) then ${declared}
598
+ when p_scope = 'global' then ${customFn}(${user}null, 'global', null, p_role)
599
+ else ${customFn}(${user}p_tenant, p_scope, p_scope_id, p_role)
600
+ end` : declared;
601
+ const grants = forUser ? `revoke execute on function ${fn}(${userArg}text, ${tenantType}, text, text) from public, anon, authenticated;` : `revoke execute on function ${fn}(text, ${tenantType}, text, text) from public, anon;
602
+ grant execute on function ${fn}(text, ${tenantType}, text, text) to authenticated;`;
603
+ return `-- whether ${forUser ? "user p_user" : "the caller"} may assign p_role at instance p_scope_id of p_scope in tenant p_tenant, declared or custom (p_scope 'global' for a platform role)${forUser ? "; no client role may execute it" : ""}
604
+ create or replace function ${fn}(${userParam}p_role text, p_tenant ${tenantType}, p_scope text, p_scope_id text)
605
+ returns boolean
606
+ language sql
607
+ stable
608
+ security definer
609
+ set search_path = ''
610
+ as $$
611
+ select coalesce(${body}, false)
612
+ $$;
613
+ ${grants}`;
614
+ }
615
+ /** Objects `rls.assignments` adds. Names are part of the SQL contract. */
616
+ const ASSIGNMENTS = {
617
+ custom: "permdock_can_assign_custom_role",
618
+ customFor: "permdock_can_assign_custom_role_for",
619
+ trigger: "permdock_assignment"
620
+ };
621
+ /** The client roles whose writes the assignment triggers check; any other role is a trusted path. */
622
+ const CLIENT_ROLES = [
623
+ "anon",
624
+ "anonymous",
625
+ "authenticated"
626
+ ];
627
+ /** The mapped membership tables, `rls.assignments.tables` and the global-roles table `rls.roles`. */
628
+ function assignedTables(ctx) {
629
+ const out = [];
630
+ for (const { name } of ctx.scopes) {
631
+ const mapped = scopeTable(ctx, name);
632
+ if (mapped === void 0) continue;
633
+ out.push({
634
+ table: mapped.table.table,
635
+ scope: name,
636
+ id: mapped.column,
637
+ tenant: mapped.tenantColumn ?? mapped.column,
638
+ role: mapped.table.role,
639
+ user: mapped.table.user
640
+ });
641
+ }
642
+ if (ctx.roles !== void 0) out.push({
643
+ table: ctx.roles.table,
644
+ scope: "global",
645
+ role: ctx.roles.role ?? "role",
646
+ user: ctx.roles.user ?? "user_id"
647
+ });
648
+ for (const extra of ctx.assignments?.tables ?? []) {
649
+ const scope = resolveScope(ctx.scopes, extra.scope);
650
+ if (scope === void 0) throw new Error(`PermDock CLI: rls.assignments.tables names scope '${extra.scope}', which the policy does not declare`);
651
+ if (scope !== ctx.scopes[0]?.name && extra.tenant === void 0) throw new Error(`PermDock CLI: rls.assignments.tables entry ${extra.table} assigns at ${scope}, below the first scope: name its tenant column`);
652
+ out.push({
653
+ table: extra.table,
654
+ scope,
655
+ id: extra.id,
656
+ tenant: extra.tenant ?? extra.id,
657
+ role: extra.role,
658
+ ...extra.user === void 0 ? {} : { user: extra.user }
659
+ });
660
+ }
661
+ return out;
662
+ }
663
+ /** The tables `rls.assignments.ownRole` refuses a caller's own rows on, by table key. */
664
+ function ownRoleTables(ctx, tables) {
665
+ const own = ctx.assignments?.ownRole;
666
+ if (own === void 0) return /* @__PURE__ */ new Set();
667
+ if (own === "refuse") return new Set(tables.filter((entry) => entry.user !== void 0).map((entry) => qualifiedTable(entry.table)));
668
+ const out = /* @__PURE__ */ new Set();
669
+ for (const [name, value] of Object.entries(own)) {
670
+ const entry = tables.find((table) => qualifiedTable(table.table) === qualifiedTable(name));
671
+ if (entry === void 0) throw new Error(`PermDock CLI: rls.assignments.ownRole names ${name}, which no assignment trigger guards`);
672
+ if (value !== "refuse") throw new Error(`PermDock CLI: rls.assignments.ownRole.${name} must be 'refuse'`);
673
+ if (entry.user === void 0) throw new Error(`PermDock CLI: rls.assignments.ownRole names ${name}, which has no user column: set its user in rls.assignments.tables`);
674
+ out.add(qualifiedTable(entry.table));
675
+ }
676
+ return out;
677
+ }
678
+ /**
679
+ * The tables `rls.assignments` guards, schema-qualified: each scope's mapped
680
+ * membership table, the global-roles table (`rls.roles`, else
681
+ * `supabase.hook.roles`) and `rls.assignments.tables`. Empty without
682
+ * `rls.assignments`.
683
+ */
684
+ function guardedTables(config, scopes) {
685
+ const rls = config.rls;
686
+ if (rls?.assignments === void 0) return [];
687
+ const hook = config.supabase?.hook?.roles;
688
+ const roles = rls.roles ?? (hook === false ? void 0 : hook);
689
+ const tables = [
690
+ ...scopes.flatMap((scope) => {
691
+ const mapped = memberForTable({
692
+ scopes,
693
+ ...rls.memberships === void 0 ? {} : { memberships: rls.memberships }
694
+ }, scope.name);
695
+ return mapped === void 0 ? [] : [mapped.table];
696
+ }),
697
+ ...roles === void 0 ? [] : [roles.table],
698
+ ...(rls.assignments === true ? [] : rls.assignments.tables ?? []).map((entry) => entry.table)
699
+ ];
700
+ return [...new Set(tables.map(qualifiedTable))];
701
+ }
702
+ function isRoleList(role) {
703
+ return Array.isArray(role);
704
+ }
705
+ /** The role keys a row variable holds, one scalar per role source. */
706
+ function rowRoles(entry, row) {
707
+ return (isRoleList(entry.role) ? entry.role : [entry.role]).map((part) => roleColumn(part, qualifiedTable(entry.table), row, {
708
+ label: `rls.assignments ${entry.table} role`,
709
+ prefix: "PermDock CLI"
710
+ }).lookup);
711
+ }
712
+ /**
713
+ * `permdock_can_assign_custom_role`: whether the signed-in caller may assign a
714
+ * custom role of the tenant at the instance, by the checks the custom-role
715
+ * write functions run on its stored definition.
716
+ */
717
+ function canAssignCustomSql(ctx, forUser = false) {
718
+ const tenantType = tenantTypeOf(ctx);
719
+ const fn = qualified(ctx, forUser ? ASSIGNMENTS.customFor : ASSIGNMENTS.custom);
720
+ const perms = qualified(ctx, CUSTOM_ROLES.permissions);
721
+ const includes = qualified(ctx, CUSTOM_ROLES.includes);
722
+ const guard = qualified(ctx, forUser ? CUSTOM_ROLES.guardFor : CUSTOM_ROLES.guard);
723
+ const userParam = forUser ? "p_user uuid, " : "";
724
+ const userArg = forUser ? "uuid, " : "";
725
+ const stored = `c.tenant_id is not distinct from p_tenant and c.scope = p_scope and c.role = p_role and (c.scope_id is null or c.scope_id = p_scope_id)`;
726
+ const grants = forUser ? `revoke execute on function ${fn}(${userArg}${tenantType}, text, text, text) from public, anon, authenticated;` : `revoke execute on function ${fn}(${tenantType}, text, text, text) from public, anon;
727
+ grant execute on function ${fn}(${tenantType}, text, text, text) to authenticated;`;
728
+ return `-- whether ${forUser ? "user p_user" : "the caller"} may assign custom role p_role at instance p_scope_id (a platform role: null tenant, scope global): the custom-role write checks on its stored definition${forUser ? "; no client role may execute it" : ""}
729
+ create or replace function ${fn}(${userParam}p_tenant ${tenantType}, p_scope text, p_scope_id text, p_role text)
730
+ returns boolean
731
+ language plpgsql
732
+ stable
733
+ security definer
734
+ set search_path = ''
735
+ as $$
736
+ declare
737
+ v_scope_id text;
738
+ v_found boolean := false;
739
+ begin
740
+ select true, c.scope_id into v_found, v_scope_id
741
+ from (
742
+ select c.scope_id from ${perms} c where ${stored}
743
+ union all
744
+ select c.scope_id from ${includes} c where ${stored}
745
+ ) c
746
+ order by c.scope_id nulls first
747
+ limit 1;
748
+ if not coalesce(v_found, false) then
749
+ return false;
750
+ end if;
751
+ perform ${guard}(${forUser ? "p_user, " : ""}p_tenant, p_scope, v_scope_id, p_role);
752
+ return true;
753
+ exception
754
+ when insufficient_privilege or invalid_parameter_value then
755
+ return false;
756
+ end;
757
+ $$;
758
+ ${grants}`;
759
+ }
760
+ /**
761
+ * The assignment triggers (\`rls.assignments\`): before a client role inserts,
762
+ * updates or deletes a row that assigns a role, every role the old and new
763
+ * row hold must be one the caller may assign at that instance, by
764
+ * \`permdock_can_assign\` for a declared role and
765
+ * \`permdock_can_assign_custom_role\` for a custom one. The functions run as
766
+ * the invoker, so \`current_user\` tells a client write from a trusted one.
767
+ */
768
+ function assignmentSql(ctx) {
769
+ const own = ctx.ownership;
770
+ if (ctx.assignments === void 0 || own === void 0 || own.assigns.length === 0) return "";
771
+ const canAssign = qualified(ctx, OWNERSHIP.canAssign);
772
+ const custom = ctx.customRoles !== void 0 && ctx.authorize === "database";
773
+ const declared = ctx.customRoles?.declared;
774
+ const tenantType = tenantTypeOf(ctx);
775
+ const chunks = [];
776
+ const clients = `current_user::text = any(array[${CLIENT_ROLES.map(quoteLiteral).join(", ")}])`;
777
+ const tables = assignedTables(ctx);
778
+ const ownRole = ownRoleTables(ctx, tables);
779
+ for (const entry of tables) {
780
+ const table = quoteTable(qualifiedTable(entry.table));
781
+ const suffix = entry.table.replaceAll(/[^a-z0-9_]/gu, "_");
782
+ const fn = qualified(ctx, quoteIdent(`${ASSIGNMENTS.trigger}_${suffix}`));
783
+ const ownUser = entry.user !== void 0 && ownRole.has(qualifiedTable(entry.table)) ? entry.user : void 0;
784
+ const ownCheck = (row, reason) => ownUser === void 0 ? "" : ` if ${row}.${quoteIdent(ownUser)}::text = (${subjectIdSql(ctx)})::text then
785
+ raise exception using
786
+ errcode = '42501',
787
+ message = 'permdock: the caller may not change their own roles',
788
+ hint = '${reason}';
789
+ end if;
790
+ `;
791
+ const check = (row) => {
792
+ const id = entry.id === void 0 ? "null::text" : `${row}.${quoteIdent(entry.id)}::text`;
793
+ const customFn = qualified(ctx, ASSIGNMENTS.custom);
794
+ let allowed = `${canAssign}(v_role, ${id})`;
795
+ if (custom && declared !== void 0) {
796
+ const platform = `${customFn}(null, 'global', null, v_role)`;
797
+ allowed = entry.id === void 0 || entry.tenant === void 0 ? `case when v_role = any(${declaredArray(declared)}) then ${canAssign}(v_role, ${id}) else ${platform} end` : `case when v_role = any(${declaredArray(declared)}) then ${canAssign}(v_role, ${id}) when ${id} is null then ${platform} else ${customFn}(${row}.${quoteIdent(entry.tenant)}::${tenantType}, ${quoteLiteral(entry.scope)}, ${id}, v_role) end`;
798
+ }
799
+ return ` foreach v_role in array array[${rowRoles(entry, row).join(", ")}]::text[] loop
800
+ if v_role is not null and not coalesce(${allowed}, false) then
801
+ raise exception using
802
+ errcode = '42501',
803
+ message = 'permdock: the caller may not assign ' || v_role || ' in ' || coalesce(${id}, 'null'),
804
+ hint = 'not-assignable-by';
805
+ end if;
806
+ end loop;`;
807
+ };
808
+ const what = entry.id === void 0 ? "global roles" : `${entry.scope} roles`;
809
+ chunks.push(`-- ${entry.table}: a client role may write only the ${what} it may assign${ownUser === void 0 ? "" : ", and none on its own rows"}
810
+ create or replace function ${fn}()
811
+ returns trigger
812
+ language plpgsql
813
+ volatile
814
+ set search_path = ''
815
+ as $$
816
+ declare
817
+ v_role text;
818
+ begin
819
+ if not (${clients}) then
820
+ return coalesce(new, old);
821
+ end if;
822
+ if tg_op in ('UPDATE', 'DELETE') then
823
+ ${ownCheck("old", "self-demotion")}${check("old")}
824
+ end if;
825
+ if tg_op in ('INSERT', 'UPDATE') then
826
+ ${ownCheck("new", "not-assignable-by")}${check("new")}
827
+ end if;
828
+ return coalesce(new, old);
829
+ end;
830
+ $$;
831
+ revoke execute on function ${fn}() from public, anon, authenticated;
832
+ drop trigger if exists "permdock_assignment" on ${table};
833
+ create trigger "permdock_assignment"
834
+ before insert or update or delete on ${table}
835
+ for each row execute function ${fn}();`);
836
+ }
837
+ return `${chunks.join("\n\n")}\n`;
838
+ }
839
+ function declaredArray(values) {
840
+ return values.length === 0 ? `'{}'::text[]` : `array[${values.map(quoteLiteral).join(", ")}]::text[]`;
841
+ }
842
+ //#endregion
843
+ export { ownershipRules as a, guardedTables as i, OWNERSHIP as n, ownershipSql as o, assignmentSql as r, ASSIGNMENTS as t };