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
@@ -1,1346 +0,0 @@
1
- import { n as isReadonlyArray, r as sole } from "./compact-y6vfu2_f.js";
2
- import { S as isForbiddenKey } from "./permissions-DC7GROPA.js";
3
- import { l as isSqlFunctionField } from "./refs-ChLSxbST.js";
4
- import { a as resolveScope, c as scopeChain, s as rootScope } from "./scopes-cKVp_WTw.js";
5
- import { t as byCodePoint } from "./compare-pfyVnBjE.js";
6
- import { i as scopeMembershipTable, o as relatedSql, r as scopeColumn } from "./compile-D22iwvuV.js";
7
- import { a as quoteSqlTable, i as quoteSqlLiteral, r as quoteSqlIdent, t as SQL_IDENT } from "./sql-Bll1d7y0.js";
8
- import "./sources-DVHM3Mwq.js";
9
- //#region src/cli/rls-sql.ts
10
- const CLI = "PermDock CLI";
11
- const CLAIM = SQL_IDENT;
12
- function checkActiveRow(label, row) {
13
- quoteTable(row.table);
14
- quoteIdent(row.id);
15
- if (row.disabledAt !== void 0) quoteIdent(row.disabledAt);
16
- if (row.status !== void 0) {
17
- quoteIdent(row.status);
18
- if (row.active === void 0 || row.active.length === 0) throw new Error(`PermDock CLI: ${label}.status needs the active values in ${label}.active`);
19
- }
20
- if (row.disabledAt === void 0 && row.status === void 0) throw new Error(`PermDock CLI: ${label} needs disabledAt or status to tell an active row`);
21
- }
22
- /** Validates `rls.suspension` and keys its scopes by declared name. */
23
- function checkSuspension(suspension, scopes) {
24
- if (suspension === void 0) return;
25
- if (suspension.users !== void 0) checkActiveRow("rls.suspension.users", suspension.users);
26
- const byName = {};
27
- for (const [key, row] of Object.entries(suspension.scopes ?? {})) {
28
- const name = resolveScope(scopes, key);
29
- if (name === void 0) throw new Error(`PermDock CLI: rls.suspension.scopes.${key} names a scope the policy does not declare`);
30
- checkActiveRow(`rls.suspension.scopes.${key}`, row);
31
- byName[name] = row;
32
- }
33
- return {
34
- ...suspension.users === void 0 ? {} : { users: suspension.users },
35
- ...Object.keys(byName).length === 0 ? {} : { scopes: byName }
36
- };
37
- }
38
- /**
39
- * `exists` over an active row whose id column equals `id`: a missing row is
40
- * suspended, so the check fails closed.
41
- */
42
- function activeRowSql(row, id) {
43
- const table = quoteTable(row.table.includes(".") ? row.table : `public.${row.table}`);
44
- const parts = [`s.${quoteIdent(row.id)} = ${id}`];
45
- if (row.disabledAt !== void 0) parts.push(`s.${quoteIdent(row.disabledAt)} is null`);
46
- if (row.status !== void 0) {
47
- const values = (row.active ?? []).map(quoteLiteral).join(", ");
48
- parts.push(`s.${quoteIdent(row.status)}::text = any(array[${values}]::text[])`);
49
- }
50
- return `exists (select 1 from ${table} s where ${parts.join(" and ")})`;
51
- }
52
- /** The active-user check for `user`; empty without `rls.suspension.users`. */
53
- function activeUserSql(ctx, user = subjectIdSql(ctx)) {
54
- const row = ctx.suspension?.users;
55
- return row === void 0 ? [] : [activeRowSql(row, user)];
56
- }
57
- /**
58
- * The active-instance check for every suspendable scope on `scope`'s chain:
59
- * its own and each ancestor's. `idOf` gives the SQL for the id the membership
60
- * holds for a scope on that chain.
61
- */
62
- function activeInstancesSql(ctx, scope, idOf) {
63
- const parts = [];
64
- for (const name of scopeChain(ctx.scopes, scope)) {
65
- const row = ctx.suspension?.scopes?.[name];
66
- if (row === void 0) continue;
67
- const id = idOf(name);
68
- if (id === void 0) throw new Error(`PermDock CLI: rls.suspension.scopes.${name} needs the ${name} id on ${scope} memberships: add columns.${name} to the ${scope} memberships table`);
69
- parts.push(activeRowSql(row, `(${id})::${scopeTypeOf(ctx, name)}`));
70
- }
71
- return parts;
72
- }
73
- const SQL_TYPE = /^[A-Za-z_][A-Za-z0-9_]*( [A-Za-z_][A-Za-z0-9_]*)*(\[\])?$/u;
74
- function sqlType(name) {
75
- if (!SQL_TYPE.test(name)) throw new Error(`PermDock CLI: unsafe SQL type '${name}'`);
76
- return name;
77
- }
78
- function tenantTypeOf(ctx) {
79
- return sqlType(ctx.tenantType ?? "uuid");
80
- }
81
- function teamTypeOf(ctx) {
82
- return sqlType(ctx.teamType ?? ctx.tenantType ?? "uuid");
83
- }
84
- /** Postgres type of scope `name`'s id: `scopeTypes`, then the tenant / team type by position. */
85
- function scopeTypeOf(ctx, name) {
86
- const declared = ctx.scopeTypes?.[name];
87
- if (declared !== void 0) return sqlType(declared);
88
- return name === ctx.scopes[1]?.name ? teamTypeOf(ctx) : tenantTypeOf(ctx);
89
- }
90
- /**
91
- * A role with `for` counts only on a membership of one of those kinds:
92
- * `case role when 'admin' then via = any(...) ... else true end`. A missing
93
- * kind (`viaExpr` null) holds none of them. `undefined` when no role has `for`.
94
- */
95
- function kindFilterSql(ctx, roleExpr, viaExpr) {
96
- const kinds = Object.entries(ctx.ownership?.kinds ?? {}).toSorted(([a], [b]) => byCodePoint(a, b));
97
- if (kinds.length === 0) return;
98
- return `case ${roleExpr} ${kinds.map(([role, allowed]) => `when ${quoteLiteral(role)} then coalesce(${viaExpr}, '') = any(array[${allowed.map(quoteLiteral).join(", ")}]::text[])`).join(" ")} else true end`;
99
- }
100
- /** The kind check for one known role; `undefined` when it has no `for`. */
101
- function roleKindSql(ctx, role, viaExpr) {
102
- const allowed = ctx.ownership?.kinds[role];
103
- return allowed === void 0 ? void 0 : `coalesce(${viaExpr}, '') = any(array[${allowed.map(quoteLiteral).join(", ")}]::text[])`;
104
- }
105
- /** Roles with `for` held globally (no membership, so no kind) grant nothing. */
106
- function globalKindFilterSql(ctx, roleExpr) {
107
- const roles = Object.keys(ctx.ownership?.kinds ?? {}).toSorted();
108
- return roles.length === 0 ? void 0 : `not (${roleExpr} = any(array[${roles.map(quoteLiteral).join(", ")}]::text[]))`;
109
- }
110
- /** The closure table graph grants read. Part of the SQL contract. */
111
- const CLOSURE = { table: "permdock_closure" };
112
- /** The helper returning the ids of `resource` the subject holds a relation on. */
113
- function graphHelper(resource) {
114
- return permittedIdsHelper(resource);
115
- }
116
- /** The helper returning the ids of `resource` whose `link` points into the ids it is given. */
117
- function linkHelper(resource, link) {
118
- if (!/^[a-z][a-z0-9_]*$/u.test(link)) throw new Error(`PermDock CLI: link '${link}' on ${resource} is not a lowercase SQL name, so rls generate cannot name its helper`);
119
- permittedIdsHelper(resource);
120
- return `permdock_link_${resource}_${link}`;
121
- }
122
- /** Graph SQL parts as RLS text: values inline as literals, the subject from the dialect's claim. */
123
- function graphSqlText(parts, ctx) {
124
- return parts.map((part) => {
125
- if ("text" in part) return part.text;
126
- if ("column" in part) return quoteIdent(part.column);
127
- if ("subject" in part) return subjectIdSql(ctx);
128
- return typeof part.value === "string" ? quoteLiteral(part.value) : String(part.value);
129
- }).join("");
130
- }
131
- /** The schema-qualified name of a table under `search_path = ''`. */
132
- function qualifiedTable(name) {
133
- return name.includes(".") ? name : `public.${name}`;
134
- }
135
- /** The helper returning the ids of scope `name` a grant key reaches. */
136
- function permittedIdsHelper(name) {
137
- if (!/^[a-z][a-z0-9_]*$/u.test(name)) throw new Error(`PermDock CLI: unsafe scope name '${name}'`);
138
- return `permitted_${name}_ids`;
139
- }
140
- /** The helper returning the ids of scope `name` the subject holds any live membership of. */
141
- function memberIdsHelper(name) {
142
- if (!/^[a-z][a-z0-9_]*$/u.test(name)) throw new Error(`PermDock CLI: unsafe scope name '${name}'`);
143
- return `member_${name}_ids`;
144
- }
145
- /** `member_<scope>_ids_for(p_user uuid)`: the same rows for a user the caller names; the token hook's helper. */
146
- function memberForHelper(name) {
147
- return `${memberIdsHelper(name)}_for`;
148
- }
149
- /**
150
- * Whether `member_<scope>_ids_for` is generated for `name`: Supabase only
151
- * (its one caller is `supabase_auth_admin`), and only where `memberships` maps
152
- * a table for the scope or a membership source can hold it.
153
- */
154
- function hasMemberFor(input, name) {
155
- if (input.dialect !== "supabase") return false;
156
- return memberForTable(input, name) !== void 0 || memberForSources(input, name).length > 0;
157
- }
158
- /** The table `member_<scope>_ids_for` reads for scope `name`, when `memberships` maps one with its id column. */
159
- function memberForTable(input, name) {
160
- const table = scopeMembershipTable(input.memberships, input.scopes, name);
161
- return table !== void 0 && scopeColumn(table, input.scopes, name) !== void 0 ? table : void 0;
162
- }
163
- /** The sources `member_<scope>_ids_for` reads: none when a table is mapped, as in `database` mode. */
164
- function memberForSources(input, name) {
165
- if (memberForTable(input, name) !== void 0) return [];
166
- return (input.memberSources ?? []).filter((source) => source.sql.scope === void 0 || source.sql.scope === name);
167
- }
168
- /**
169
- * The membership sources that can hold scope `name` when `memberships` maps
170
- * no table for it: every `fromTable` source and the `fromJunction` sources of
171
- * that scope. Empty outside `database` mode.
172
- */
173
- function scopeSources(ctx, name) {
174
- if (ctx.authorize !== "database" || scopeTable(ctx, name) !== void 0) return [];
175
- return (ctx.sources ?? []).filter((source) => source.sql.scope === void 0 || source.sql.scope === name);
176
- }
177
- /** The membership table mapped for scope `name`, with the column of its id and of the first scope's id. */
178
- function scopeTable(ctx, name) {
179
- const table = scopeMembershipTable(ctx.memberships, ctx.scopes, name);
180
- if (table === void 0) return;
181
- const column = scopeColumn(table, ctx.scopes, name);
182
- if (column === void 0) return;
183
- const root = rootScope(ctx.scopes);
184
- const tenantColumn = root !== void 0 && scopeChain(ctx.scopes, name).includes(root) ? scopeColumn(table, ctx.scopes, root) : void 0;
185
- return tenantColumn === void 0 ? {
186
- table,
187
- column
188
- } : {
189
- table,
190
- column,
191
- tenantColumn
192
- };
193
- }
194
- /** The active-tenant claim cast to the tenant column's type, so the comparison uses the column's index. */
195
- function tenantClaimSql(ctx) {
196
- return `${subjectClaimSql(ctx, ctx.tenantClaim)}::${tenantTypeOf(ctx)}`;
197
- }
198
- function quoteIdent(name) {
199
- return quoteSqlIdent(name, CLI);
200
- }
201
- function quoteTable(name) {
202
- return quoteSqlTable(name, CLI);
203
- }
204
- function quoteLiteral(value) {
205
- return quoteSqlLiteral(value);
206
- }
207
- function subjectIdSql(ctx) {
208
- switch (ctx.dialect) {
209
- case "supabase": return "(select auth.uid())";
210
- case "neon": return "(select auth.user_id())";
211
- case "guc": return `(select current_setting(${quoteLiteral(`${ctx.gucPrefix}.user_id`)}, true))`;
212
- default: return ctx.dialect;
213
- }
214
- }
215
- function subjectClaimSql(ctx, claim) {
216
- if (!CLAIM.test(claim)) throw new Error(`PermDock CLI: unsafe claim name '${claim}'`);
217
- switch (ctx.dialect) {
218
- case "supabase": return `((select auth.jwt()) ->> ${quoteLiteral(claim)})`;
219
- case "neon": return `((select auth.session()) ->> ${quoteLiteral(claim)})`;
220
- case "guc": return `(select current_setting(${quoteLiteral(`${ctx.gucPrefix}.${claim}`)}, true))`;
221
- default: return ctx.dialect;
222
- }
223
- }
224
- /** A claim as `jsonb`; the `guc` dialect stores JSON text in `<prefix>.<claim>`. */
225
- function subjectClaimJsonSql(ctx, claim) {
226
- if (!CLAIM.test(claim)) throw new Error(`PermDock CLI: unsafe claim name '${claim}'`);
227
- switch (ctx.dialect) {
228
- case "supabase": return `((select auth.jwt()) -> ${quoteLiteral(claim)})`;
229
- case "neon": return `((select auth.session()) -> ${quoteLiteral(claim)})`;
230
- case "guc": return `nullif((select current_setting(${quoteLiteral(`${ctx.gucPrefix}.${claim}`)}, true)), '')::jsonb`;
231
- default: return ctx.dialect;
232
- }
233
- }
234
- function isRecord(value) {
235
- return value !== null && typeof value === "object" && !Array.isArray(value);
236
- }
237
- /** The Postgres type a JSON Schema property compares as; `undefined` for text or an unknown shape. */
238
- function columnTypeOf(property) {
239
- if (!isRecord(property)) return;
240
- const variants = [property["anyOf"], property["oneOf"]].find(Array.isArray);
241
- if (variants !== void 0) {
242
- const present = variants.filter((item) => !(isRecord(item) && item["type"] === "null"));
243
- return present.length === 1 ? columnTypeOf(present[0]) : void 0;
244
- }
245
- const types = (Array.isArray(property["type"]) ? property["type"] : [property["type"]]).filter((item) => item !== "null");
246
- if (types.length !== 1) return;
247
- switch (types[0]) {
248
- case "integer":
249
- case "number": return "numeric";
250
- case "boolean": return "boolean";
251
- case "string": switch (property["format"]) {
252
- case "date-time": return "timestamptz";
253
- case "date": return "date";
254
- case "uuid": return "uuid";
255
- default: return;
256
- }
257
- default: return;
258
- }
259
- }
260
- /**
261
- * Column types from a resource's JSON Schema (`~standard.jsonSchema`): numbers
262
- * compare as `numeric`, booleans as `boolean`, and `date-time`, `date` and
263
- * `uuid` strings as `timestamptz`, `date` and `uuid`. Other columns are left
264
- * out and compare as text.
265
- */
266
- function columnTypesOf(schema) {
267
- const properties = isRecord(schema) ? schema["properties"] : void 0;
268
- if (!isRecord(properties)) return {};
269
- const types = {};
270
- for (const [name, property] of Object.entries(properties)) {
271
- const type = isForbiddenKey(name) ? void 0 : columnTypeOf(property);
272
- if (type !== void 0) types[name] = type;
273
- }
274
- return types;
275
- }
276
- /**
277
- * Array columns from a resource's JSON Schema, each with the type its items
278
- * compare as (`text` when `columnTypesOf` would leave the item out).
279
- */
280
- function arrayColumnsOf(schema) {
281
- const properties = isRecord(schema) ? schema["properties"] : void 0;
282
- if (!isRecord(properties)) return {};
283
- const arrays = {};
284
- for (const [name, property] of Object.entries(properties)) {
285
- if (isForbiddenKey(name) || !isRecord(property)) continue;
286
- const types = (Array.isArray(property["type"]) ? property["type"] : [property["type"]]).filter((item) => item !== "null");
287
- if (types.length === 1 && types[0] === "array") arrays[name] = columnTypeOf(property["items"]) ?? "text";
288
- }
289
- return arrays;
290
- }
291
- const MAX_CLAIM_DEPTH = 8;
292
- /**
293
- * The claim path of a `principal.claim.*` / `principal.claims.*` ref, one
294
- * segment per JSON key; `undefined` for any other ref.
295
- */
296
- function claimPath(ref) {
297
- const prefix = ["principal.claim.", "principal.claims."].find((item) => ref.startsWith(item));
298
- if (prefix === void 0) return;
299
- const segments = ref.slice(prefix.length).split(".");
300
- if (segments.length > MAX_CLAIM_DEPTH) throw new Error(`PermDock CLI: claim path '${ref}' is deeper than ${MAX_CLAIM_DEPTH}`);
301
- for (const segment of segments) if (!CLAIM.test(segment) || isForbiddenKey(segment)) throw new Error(`PermDock CLI: unsafe claim name '${segment}'`);
302
- return segments;
303
- }
304
- function jsonPath(base, keys, last) {
305
- let sql = base;
306
- for (const [index, key] of keys.entries()) sql += ` ${index === keys.length - 1 ? last : "->"} ${quoteLiteral(key)}`;
307
- return sql;
308
- }
309
- /**
310
- * The JSON document a claim path starts from and the keys to walk in it. The
311
- * `guc` dialect keeps each top-level claim in its own setting as JSON text.
312
- */
313
- function claimRoot(ctx, path) {
314
- const [head, ...rest] = path;
315
- if (head === void 0) throw new Error("PermDock CLI: empty claim path");
316
- switch (ctx.dialect) {
317
- case "supabase": return {
318
- root: "(select auth.jwt())",
319
- keys: path
320
- };
321
- case "neon": return {
322
- root: "(select auth.session())",
323
- keys: path
324
- };
325
- case "guc": return {
326
- root: subjectClaimJsonSql(ctx, head),
327
- keys: rest
328
- };
329
- default: return ctx.dialect;
330
- }
331
- }
332
- /** The claim at `path` as `jsonb`. */
333
- function claimJsonSql(ctx, path) {
334
- const { root, keys } = claimRoot(ctx, path);
335
- return keys.length === 0 ? root : `(${jsonPath(root, keys, "->")})`;
336
- }
337
- /** The claim at `path` as text: `->>` on the last key, or the plain setting for a one-segment `guc` claim. */
338
- function claimTextSql(ctx, path) {
339
- const [head] = path;
340
- if (path.length === 1 && head !== void 0) return subjectClaimSql(ctx, head);
341
- const { root, keys } = claimRoot(ctx, path);
342
- return `(${jsonPath(root, keys, "->>")})`;
343
- }
344
- /** The JSON kind a claim must have to compare with a column of `type`. */
345
- function jsonKindOf(type) {
346
- if (type === "numeric") return "number";
347
- return type === "boolean" ? "boolean" : "string";
348
- }
349
- /**
350
- * A claim compared with a column of `type`. Numbers and booleans must be JSON
351
- * of that kind (anything else is `null`, so the comparison is false, as in
352
- * memory); other types cast the text form. A one-segment `guc` claim is text
353
- * and is cast as is.
354
- */
355
- function typedClaimSql(ctx, path, type) {
356
- const text = claimTextSql(ctx, path);
357
- if (type === void 0 || type === "text") return text;
358
- const cast = `${text}::${sqlType(type)}`;
359
- const kind = jsonKindOf(type);
360
- if (kind === "string" || ctx.dialect === "guc" && path.length === 1) return `(${cast})`;
361
- return `(case when jsonb_typeof(${claimJsonSql(ctx, path)}) = '${kind}' then ${cast} end)`;
362
- }
363
- /**
364
- * The elements of an array claim as a Postgres array of the column's type,
365
- * built once per statement (an uncorrelated `array(select ...)` is an InitPlan).
366
- * A missing or non-array claim is the empty array; elements of another JSON
367
- * kind are left out.
368
- */
369
- function claimArraySql(ctx, path, type) {
370
- const json = claimJsonSql(ctx, path);
371
- return `array(select ${type === void 0 || type === "text" ? `(e #>> '{}')` : `(e #>> '{}')::${sqlType(type)}`} from jsonb_array_elements(case when jsonb_typeof(${json}) = 'array' then ${json} else '[]'::jsonb end) e where jsonb_typeof(e) = '${jsonKindOf(type)}')`;
372
- }
373
- function sqlValue(value, ctx, field) {
374
- if (value !== null && typeof value === "object" && "ref" in value) return compileRef(value.ref, ctx, field);
375
- if (value !== null && typeof value === "object" && "date" in value) return quoteLiteral(value.date);
376
- if (isReadonlyArray(value)) return `array[${value.map((item) => sqlValue(item, ctx)).join(", ")}]`;
377
- if (value === null) return "null";
378
- if (typeof value === "boolean") return value ? "true" : "false";
379
- if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
380
- if (typeof value === "string") return quoteLiteral(value);
381
- throw new Error("PermDock CLI: non-portable condition value");
382
- }
383
- function compileRef(ref, ctx, field) {
384
- if (ref === "principal.id") return subjectIdSql(ctx);
385
- if (ref === "now") return "now()";
386
- const path = claimPath(ref);
387
- if (ref === "principal.tenant" || path?.length === 1 && path[0] === ctx.tenantClaim) return tenantClaimSql(ctx);
388
- if (path !== void 0) return typedClaimSql(ctx, path, field === void 0 ? void 0 : ctx.columnTypes?.[field]);
389
- if (ref === "context" || ref.startsWith("context.")) throw new Error(`PermDock CLI: '${ref}' is request context, which is not in the token; RLS cannot read it (permdock doctor PD027)`);
390
- throw new Error(`PermDock CLI: non-portable subject ref '${ref}'`);
391
- }
392
- /** `value` with `\`, `%` and `_` escaped for `like ... escape '\'`, as `escapeLike` does in process. */
393
- function likeLiteralSql(value) {
394
- return `replace(replace(replace(${value}, '\\', '\\\\'), '%', '\\%'), '_', '\\_')`;
395
- }
396
- function compareSql(op, field, value) {
397
- const left = quoteIdent(field);
398
- switch (op) {
399
- case "eq": return `${left} = ${value}`;
400
- case "ne": return `${left} <> ${value}`;
401
- case "gt": return `${left} > ${value}`;
402
- case "gte": return `${left} >= ${value}`;
403
- case "lt": return `${left} < ${value}`;
404
- case "lte": return `${left} <= ${value}`;
405
- case "contains": return `${left}::text like '%' || ${likeLiteralSql(`${value}::text`)} || '%' escape '\\'`;
406
- default: return op;
407
- }
408
- }
409
- function existsSql(table, rowColumn, rowField, roles, ctx, tenantColumn, scope) {
410
- const parts = [`m.${quoteIdent(rowColumn)} = ${quoteIdent(rowField)}`, `m.${quoteIdent(table.user)} = ${subjectIdSql(ctx)}`];
411
- if (roles.length > 0) {
412
- const roleList = roles.map((role) => role.replaceAll("'", "''")).join(",");
413
- parts.push(`m.${quoteIdent(table.role)} = any('{${roleList}}')`);
414
- const via = table.via === void 0 ? "null::text" : `m.${quoteIdent(table.via)}::text`;
415
- const single = sole(roles);
416
- const kind = single === void 0 ? kindFilterSql(ctx, `m.${quoteIdent(table.role)}::text`, via) : roleKindSql(ctx, single, via);
417
- if (kind !== void 0) parts.push(kind);
418
- }
419
- if (table.expiresAt !== void 0) parts.push(`(m.${quoteIdent(table.expiresAt)} is null or m.${quoteIdent(table.expiresAt)} > now())`);
420
- if (tenantColumn !== void 0) parts.push(`m.${quoteIdent(tenantColumn)} = ${tenantClaimSql(ctx)}`);
421
- parts.push(...activeUserSql(ctx));
422
- if (scope !== void 0) parts.push(...activeInstancesSql(ctx, scope, (name) => {
423
- const held = scopeColumn(table, ctx.scopes, name);
424
- return held === void 0 ? void 0 : `m.${quoteIdent(held)}`;
425
- }));
426
- return `exists (select 1 from ${quoteTable(table.table)} m where ${parts.join(" and ")})`;
427
- }
428
- function compileMemberOf(condition, ctx) {
429
- const scope = condition.scope === "resource" ? void 0 : resolveScope(ctx.scopes, condition.scope);
430
- if (condition.scope !== "resource" && scope === void 0) throw new Error(`PermDock CLI: memberOf ${condition.scope} names a scope the policy does not declare`);
431
- const mapping = scope === void 0 ? condition.resource === void 0 ? void 0 : ctx.memberships?.resource?.[condition.resource] : scopeMembershipTable(ctx.memberships, ctx.scopes, scope);
432
- if (mapping !== void 0) {
433
- const named = scope === void 0 ? void 0 : scopeTable(ctx, scope);
434
- const rowColumn = scope === void 0 ? mapping.id : named?.column;
435
- if (rowColumn === void 0) throw new Error(`PermDock CLI: memberships mapping for ${condition.scope} is missing the row column`);
436
- const tenantColumn = scope === void 0 || scope === rootScope(ctx.scopes) ? void 0 : named?.tenantColumn;
437
- const primary = existsSql(mapping, rowColumn, condition.field, condition.roles, ctx, tenantColumn, scope);
438
- if (condition.scope !== "resource" || condition.parents === void 0) return primary;
439
- return `(${[primary, ...condition.parents.flatMap((parent) => {
440
- if (typeof parent === "string") return [existsSql(mapping, rowColumn, parent, condition.roles, ctx, tenantColumn)];
441
- const hopTable = ctx.memberships?.resource?.[parent.resource];
442
- return hopTable?.id === void 0 ? [] : [existsSql(hopTable, hopTable.id, parent.field, condition.roles, ctx)];
443
- })].join(" or ")})`;
444
- }
445
- if (scope !== void 0 && condition.roles.length === 0 && scopeSources(ctx, scope).length > 0) return `${quoteIdent(condition.field)} in (select ${quoteIdent(ctx.schema ?? "permdock")}.${memberIdsHelper(scope)}())`;
446
- if (scope !== void 0 && scope === rootScope(ctx.scopes)) {
447
- const parts = [
448
- `${quoteIdent(condition.field)} = ${tenantClaimSql(ctx)}`,
449
- ...activeUserSql(ctx),
450
- ...activeInstancesSql(ctx, scope, () => quoteIdent(condition.field))
451
- ];
452
- return parts.length === 1 ? parts.join("") : `(${parts.join(" and ")})`;
453
- }
454
- throw new Error(`PermDock CLI: memberOf ${condition.scope} needs a memberships table mapping`);
455
- }
456
- function compileConditionSql(condition, ctx) {
457
- switch (condition.op) {
458
- case "eq":
459
- case "ne":
460
- case "gt":
461
- case "gte":
462
- case "lt":
463
- case "lte": return compareSql(condition.op, condition.field, sqlValue(condition.value, ctx, condition.field));
464
- case "contains": {
465
- const element = ctx.arrayColumns?.[condition.field];
466
- if (element !== void 0) return `${sqlValue(condition.value, {
467
- ...ctx,
468
- columnTypes: {
469
- ...ctx.columnTypes,
470
- [condition.field]: element
471
- }
472
- }, condition.field)} = any(${quoteIdent(condition.field)})`;
473
- return compareSql(condition.op, condition.field, sqlValue(condition.value, ctx, condition.field));
474
- }
475
- case "in":
476
- case "notIn": {
477
- const keyword = condition.op === "in" ? "in" : "not in";
478
- if (!Array.isArray(condition.value) && typeof condition.value === "object" && "ref" in condition.value) {
479
- const path = claimPath(condition.value.ref);
480
- if (path === void 0) {
481
- compileRef(condition.value.ref, ctx);
482
- throw new Error(`PermDock CLI: non-portable ${condition.op} against '${condition.value.ref}'`);
483
- }
484
- const column = quoteIdent(condition.field);
485
- const list = claimArraySql(ctx, path, ctx.columnTypes?.[condition.field]);
486
- return condition.op === "in" ? `${column} = any (${list})` : `(${column} is not null and not (${column} = any (${list})))`;
487
- }
488
- const values = condition.value.map((item) => sqlValue(item, ctx));
489
- return `${quoteIdent(condition.field)} ${keyword} (${values.join(", ")})`;
490
- }
491
- case "isNull": return `${quoteIdent(condition.field)} is ${condition.value ? "" : "not "}null`;
492
- case "and": return `(${condition.conditions.map((item) => compileConditionSql(item, ctx)).join(" and ")})`;
493
- case "or":
494
- if (condition.conditions.length === 0) return "false";
495
- return `(${condition.conditions.map((item) => compileConditionSql(item, ctx)).join(" or ")})`;
496
- case "not": return `not (${compileConditionSql(condition.condition, ctx)})`;
497
- case "memberOf": return compileMemberOf(condition, ctx);
498
- case "related": return compileRelatedSql(condition, ctx);
499
- case "sqlFunction":
500
- if (ctx.inlineFunctions === true) return compileConditionSql(condition.twin, ctx);
501
- return `${quoteTable(condition.name)}(${condition.args.map((arg) => isSqlFunctionField(arg) ? quoteIdent(arg.field) : sqlValue(arg, ctx)).join(", ")})`;
502
- case "opaque": return condition.sql;
503
- default: return condition;
504
- }
505
- }
506
- function parseMembershipsFlag(raw) {
507
- if (raw === void 0 || raw === "") return;
508
- const colon = raw.indexOf(":");
509
- const table = colon === -1 ? raw : raw.slice(0, colon);
510
- const cols = (colon === -1 ? "" : raw.slice(colon + 1)).split(",").map((item) => item.trim()).filter((item) => item !== "");
511
- return { tenant: {
512
- table,
513
- tenant: cols[0] ?? "tenant_id",
514
- user: cols[1] ?? "user_id",
515
- role: cols[2] ?? "role",
516
- ...cols[3] === void 0 ? {} : { expiresAt: cols[3] }
517
- } };
518
- }
519
- function andConditions(left, right) {
520
- if (left === void 0) return right;
521
- if (right === void 0) return left;
522
- return {
523
- op: "and",
524
- conditions: [left, right]
525
- };
526
- }
527
- function valueContextRefs(value) {
528
- if (Array.isArray(value)) return value.flatMap(valueContextRefs);
529
- if (isRecord(value) && typeof value["ref"] === "string" && (value["ref"] === "context" || value["ref"].startsWith("context."))) return [value["ref"]];
530
- return [];
531
- }
532
- /**
533
- * The `context.*` refs a condition reads. The request context is not in the
534
- * token, so RLS cannot evaluate them.
535
- */
536
- function contextRefs(condition) {
537
- if (condition === void 0) return [];
538
- switch (condition.op) {
539
- case "eq":
540
- case "ne":
541
- case "gt":
542
- case "gte":
543
- case "lt":
544
- case "lte":
545
- case "contains":
546
- case "in":
547
- case "notIn": return valueContextRefs(condition.value);
548
- case "and":
549
- case "or": return condition.conditions.flatMap((child) => contextRefs(child));
550
- case "not": return contextRefs(condition.condition);
551
- case "sqlFunction": return [...condition.args.flatMap(valueContextRefs), ...contextRefs(condition.twin)];
552
- case "isNull":
553
- case "memberOf":
554
- case "related":
555
- case "opaque": return [];
556
- default: return condition;
557
- }
558
- }
559
- function sqlFunctionNames(condition) {
560
- if (condition === void 0) return [];
561
- switch (condition.op) {
562
- case "sqlFunction": return [condition.name, ...sqlFunctionNames(condition.twin)];
563
- case "and":
564
- case "or": return condition.conditions.flatMap((child) => sqlFunctionNames(child));
565
- case "not": return sqlFunctionNames(condition.condition);
566
- case "eq":
567
- case "ne":
568
- case "gt":
569
- case "gte":
570
- case "lt":
571
- case "lte":
572
- case "contains":
573
- case "in":
574
- case "notIn":
575
- case "isNull":
576
- case "memberOf":
577
- case "related":
578
- case "opaque": return [];
579
- default: return condition;
580
- }
581
- }
582
- /**
583
- * A `related` condition as uncorrelated subqueries, so Postgres runs the
584
- * helper once per statement: the row's field in the closure's descendants of
585
- * the ids the subject holds the relation on (or those ids alone at depth 0).
586
- * The helper sits in `array(...)` so it stays an InitPlan; a plain `in` gets
587
- * pulled into a join that can rescan it per closure row.
588
- */
589
- function compileRelatedSql(condition, ctx) {
590
- if (condition.hops !== void 0 && condition.hops.length > 0) return compileHoppedSql(condition, ctx);
591
- const type = ctx.columnTypes?.[condition.field];
592
- const cast = type === void 0 || type === "text" ? "" : `::${type}`;
593
- const column = type === void 0 ? `${quoteIdent(condition.field)}::text` : quoteIdent(condition.field);
594
- const helper = `${`${quoteIdent(ctx.schema ?? "permdock")}.${graphHelper(condition.resource)}`}(${quoteLiteral(condition.relation)})`;
595
- const cap = ctx.graph?.closures[condition.resource];
596
- let inner;
597
- if (condition.depth > 0 && cap !== void 0) {
598
- const depth = condition.depth < cap ? ` and depth <= ${String(condition.depth)}` : "";
599
- inner = `select descendant${cast} from ${`${quoteIdent(ctx.schema ?? "permdock")}.${CLOSURE.table}`} where resource = ${quoteLiteral(condition.resource)}${depth} and ancestor = any (array(select ${helper}))`;
600
- } else inner = cast === "" ? `select ${helper}` : `select p.id${cast} from ${helper} as p(id)`;
601
- const reach = `${column} in (${inner})`;
602
- if (condition.parent === true && condition.restricted !== void 0) return `(${reach} and ${quoteIdent(condition.restricted)} is not true)`;
603
- return reach;
604
- }
605
- /**
606
- * A `related` condition that crosses link hops: the reached instances of the
607
- * last resource, carried back over each link by its security definer helper,
608
- * so the hop reads a table the subject may not see.
609
- */
610
- function compileHoppedSql(condition, ctx) {
611
- const resources = ctx.graph?.resources;
612
- if (resources === void 0) throw new Error("PermDock CLI: a related condition with link hops needs the policy resources in the RLS context");
613
- const schema = quoteIdent(ctx.schema ?? "permdock");
614
- return graphSqlText(relatedSql(condition, {
615
- resources,
616
- ...ctx.graph?.tables === void 0 ? {} : { tables: ctx.graph.tables },
617
- closure: `${ctx.schema ?? "permdock"}.${CLOSURE.table}`,
618
- closureDepths: ctx.graph?.closures ?? {},
619
- qualify: qualifiedTable,
620
- holders: (resource, relation) => [{ text: `select ${schema}.${graphHelper(resource)}(${quoteLiteral(relation)})` }],
621
- linked: (resource, link, targets) => [
622
- { text: `select ${schema}.${linkHelper(resource, link)}(array(` },
623
- ...targets,
624
- { text: "))" }
625
- ]
626
- }), ctx);
627
- }
628
- //#endregion
629
- //#region src/cli/global-roles.ts
630
- function qualify(name, schema) {
631
- return name.includes(".") ? name : `${schema}.${name}`;
632
- }
633
- /** Resolves `source` against `schema`, aliasing the table `alias` and the roles table `<alias>k`. */
634
- function globalRoleSource(source, schema, alias) {
635
- const table = qualify(source.table, schema);
636
- const user = source.user ?? "user_id";
637
- const userSql = `${alias}.${quoteIdent(user)}`;
638
- const role = source.role ?? "role";
639
- if (typeof role === "string") return {
640
- table,
641
- user,
642
- from: `${quoteTable(table)} ${alias}`,
643
- userSql,
644
- roleSql: `${alias}.${quoteIdent(role)}::text`
645
- };
646
- const pairs = Object.entries(role.on);
647
- const [pair] = pairs;
648
- if (pair === void 0 || pairs.length !== 1) throw new Error(`PermDock CLI: roles.role.on must map exactly one column of ${table} to ${role.through}, for example { role_id: 'id' }`);
649
- const [ref, id] = pair;
650
- const through = qualify(role.through, table.split(".")[0] ?? schema);
651
- const keys = `${alias}k`;
652
- return {
653
- table,
654
- user,
655
- from: `${quoteTable(table)} ${alias}
656
- join ${quoteTable(through)} ${keys} on ${keys}.${quoteIdent(id)} = ${alias}.${quoteIdent(ref)}`,
657
- userSql,
658
- roleSql: `${keys}.${quoteIdent(role.column)}::text`,
659
- through: {
660
- table: through,
661
- id,
662
- key: role.column,
663
- ref
664
- }
665
- };
666
- }
667
- //#endregion
668
- //#region src/cli/rls-helpers.ts
669
- /**
670
- * The per-statement helpers every generated policy calls: `permdock_has` for
671
- * global roles and one `permitted_<scope>_ids` per scope
672
- * (`permittedIdsHelper`). Names are part of the SQL contract.
673
- */
674
- const HELPERS = { has: "permdock_has" };
675
- /** The helper `--capabilities` adds: ids of one resource a link capability claim reaches. Part of the SQL contract. */
676
- const CAPABILITIES = { ids: "permdock_capability_ids" };
677
- /** Objects `--custom-roles` adds next to the helpers. Names are part of the SQL contract. */
678
- const CUSTOM_ROLES = {
679
- permissions: "custom_role_permissions",
680
- includes: "custom_role_includes",
681
- ceiling: "permdock_ceiling",
682
- keys: "permdock_custom_keys"
683
- };
684
- function helperSchema(ctx) {
685
- return ctx.schema ?? "permdock";
686
- }
687
- function qualified(ctx, name) {
688
- return `${quoteIdent(helperSchema(ctx))}.${name}`;
689
- }
690
- /**
691
- * The policy-side call for a grant key. Every form is uncorrelated, so
692
- * Postgres runs the helper once per statement (an InitPlan, or a hashed
693
- * SubPlan for `in`), never once per row.
694
- */
695
- function accessSql(ctx, scope, grantKey, column) {
696
- const key = quoteLiteral(grantKey);
697
- if (scope === "global") return `(select ${qualified(ctx, HELPERS.has)}(${key}))`;
698
- if (column === void 0) throw new Error(`PermDock CLI: ${scope}-scoped grant needs definePolicy({ scopes.${scope} })`);
699
- return `${quoteIdent(column)} in (select ${qualified(ctx, permittedIdsHelper(scope))}(${key}))`;
700
- }
701
- /**
702
- * The `anon` branch access for a resource-scoped grant reached through a link
703
- * capability: the row's `field` holds an id the capability claim names for
704
- * `resource`, `role` and `permission`. Uncorrelated, so it runs once per
705
- * statement; the column is compared as text because the claim holds text.
706
- */
707
- function capabilityAccessSql(ctx, field, resource, role, permission) {
708
- const args = [
709
- resource,
710
- role,
711
- permission
712
- ].map(quoteLiteral).join(", ");
713
- return `${quoteIdent(field)}::text in (select ${qualified(ctx, CAPABILITIES.ids)}(${args}))`;
714
- }
715
- function capabilitiesSql(ctx) {
716
- if (ctx.capabilities !== true) return "";
717
- const fn = qualified(ctx, CAPABILITIES.ids);
718
- return `-- link capabilities: the capability claim exchangeCapability mints (role anon, short-lived)
719
- create or replace function ${fn}(p_resource text, p_role text, p_permission text)
720
- returns setof text
721
- language sql
722
- stable
723
- set search_path = ''
724
- as $$
725
- select c -> 'on' ->> 'id'
726
- from (select ${subjectClaimJsonSql(ctx, "capability")} as c) capability
727
- where jsonb_typeof(c) = 'object'
728
- and c ->> 'v' = '1'
729
- and c ->> 'holder' = 'link'
730
- and c -> 'on' ->> 'resource' = p_resource
731
- and jsonb_typeof(c -> 'roles') = 'array'
732
- and c -> 'roles' @> jsonb_build_array(p_role)
733
- and (c -> 'permissions' is null or c -> 'permissions' @> jsonb_build_array(p_permission))
734
- and jsonb_typeof(c -> 'expiresAt') = 'number'
735
- and (c ->> 'expiresAt')::numeric > extract(epoch from now())
736
- $$;
737
- revoke execute on function ${fn}(text, text, text) from public;
738
- grant execute on function ${fn}(text, text, text) to anon, authenticated;`;
739
- }
740
- function membershipTable(name) {
741
- return quoteTable(name.includes(".") ? name : `public.${name}`);
742
- }
743
- /** `user` is the SQL for the user id: `auth.uid()` (the default) or the `p_user` parameter of a `_for` helper. */
744
- function signedIn(ctx, user = subjectIdSql(ctx)) {
745
- return `coalesce(${user}::text, '') <> ''`;
746
- }
747
- function activeTenant(ctx) {
748
- return `nullif(${subjectClaimSql(ctx, ctx.tenantClaim)}, '')`;
749
- }
750
- function rootName(ctx) {
751
- return ctx.scopes[0]?.name ?? "tenant";
752
- }
753
- /** Whether the active tenant narrows memberships of `scope`: the first scope is on its chain. */
754
- function underRoot(ctx, scope) {
755
- const root = ctx.scopes[0]?.name;
756
- let current = ctx.scopes.find((item) => item.name === scope);
757
- const seen = /* @__PURE__ */ new Set();
758
- while (current !== void 0 && !seen.has(current.name)) {
759
- if (current.name === root) return true;
760
- seen.add(current.name);
761
- const parent = current.within;
762
- current = parent === void 0 ? void 0 : ctx.scopes.find((item) => item.name === parent);
763
- }
764
- return false;
765
- }
766
- /** `and exists (...)` lines for an active user; empty without `rls.suspension.users`. */
767
- function userActive(ctx, indent, user) {
768
- return activeUserSql(ctx, user).map((part) => `${indent}and ${part}`);
769
- }
770
- /** `and exists (...)` lines for every suspendable instance on the chain of `scope`'s membership. */
771
- function instancesActive(ctx, scope, idOf, indent) {
772
- return activeInstancesSql(ctx, scope, idOf).map((part) => `${indent}and ${part}`);
773
- }
774
- function roleRows(ctx) {
775
- const claim = ctx.roleClaim ?? "user_role";
776
- switch (ctx.dialect) {
777
- case "guc": return `unnest(string_to_array(nullif(current_setting(${quoteLiteral(`${ctx.gucPrefix}.${claim}`)}, true), ''), ',')) r(role)`;
778
- case "supabase":
779
- case "neon": {
780
- const raw = subjectClaimJsonSql(ctx, claim);
781
- const value = ctx.dialect === "supabase" ? `coalesce(nullif(${raw}, 'null'::jsonb), (select auth.jwt()) -> 'app_metadata' -> ${quoteLiteral(claim)})` : raw;
782
- return `jsonb_array_elements_text(
783
- case jsonb_typeof(${value})
784
- when 'array' then ${value}
785
- when 'string' then jsonb_build_array(${value})
786
- else '[]'::jsonb
787
- end
788
- ) r(role)`;
789
- }
790
- default: return ctx.dialect;
791
- }
792
- }
793
- function membershipRows(ctx) {
794
- const raw = subjectClaimJsonSql(ctx, "memberships");
795
- const value = ctx.dialect === "supabase" ? `coalesce(${raw}, (select auth.jwt()) -> 'app_metadata' -> 'memberships')` : raw;
796
- return `jsonb_array_elements(
797
- case jsonb_typeof(${value}) when 'array' then ${value} else '[]'::jsonb end
798
- ) m
799
- cross join lateral jsonb_array_elements_text(
800
- case jsonb_typeof(m -> 'roles') when 'array' then m -> 'roles' else '[]'::jsonb end
801
- ) r(role)`;
802
- }
803
- function andLine(indent, condition) {
804
- return condition === void 0 ? "" : `\n${indent}and ${condition}`;
805
- }
806
- /** `rls.roles`, or the generated `user_roles (user_id, role)`, aliased `ur`. */
807
- function globalRoleRows(ctx) {
808
- return ctx.roles === void 0 ? {
809
- from: `${qualified(ctx, "user_roles")} ur`,
810
- userSql: "ur.user_id",
811
- roleSql: "ur.role::text"
812
- } : globalRoleSource(ctx.roles, "public", "ur");
813
- }
814
- function hasBody(ctx) {
815
- const rp = qualified(ctx, "role_permissions");
816
- const active = userActive(ctx, " ").map((line) => `\n${line}`).join("");
817
- const custom = ctx.customRoles;
818
- if (ctx.authorize === "database") {
819
- const ur = globalRoleRows(ctx);
820
- const declared = ` select exists (
821
- select 1
822
- from ${ur.from}
823
- join ${rp} rp on rp.role = ${ur.roleSql}
824
- where ${ur.userSql} = ${subjectIdSql(ctx)}
825
- and rp.grant_key = p_grant
826
- and rp.scope = 'global'${andLine(" ", globalKindFilterSql(ctx, ur.roleSql))}${active}
827
- )`;
828
- if (custom === void 0) return declared;
829
- const match = `c.tenant_id is null and c.scope = 'global' and c.scope_id is null and c.role = ${ur.roleSql}`;
830
- const rows = (source, value, extra) => `array(select c.${value} from ${qualified(ctx, source)} c where ${match}${extra})`;
831
- return `${declared}
832
- or exists (
833
- select 1
834
- from ${ur.from}
835
- where ${ur.userSql} = ${subjectIdSql(ctx)}
836
- and not (${ur.roleSql} = any(${textArray(custom.declared)}))${active}
837
- ${customKeysSql(ctx, "global", rows(CUSTOM_ROLES.permissions, "permission", " and c.effect = 'allow'"), rows(CUSTOM_ROLES.permissions, "permission", " and c.effect = 'deny'"), rows(CUSTOM_ROLES.includes, "include_role", "")).trimStart()}
838
- )`;
839
- }
840
- const declared = ` select ${signedIn(ctx)} and exists (
841
- select 1
842
- from ${roleRows(ctx)}
843
- join ${rp} rp on rp.role = r.role
844
- where rp.grant_key = p_grant
845
- and rp.scope = 'global'${andLine(" ", globalKindFilterSql(ctx, "r.role"))}${active}
846
- )`;
847
- if (custom === void 0) return declared;
848
- const claim = subjectClaimJsonSql(ctx, GLOBAL_GRANTS_CLAIM);
849
- return `${declared}
850
- or (${signedIn(ctx)} and exists (
851
- select 1
852
- from ${roleRows(ctx)}
853
- cross join lateral (select ${claim} -> r.role as g) cg
854
- where jsonb_typeof(cg.g) = 'array'
855
- and not (r.role = any(${textArray(custom.declared)}))${active}
856
- ${customKeysSql(ctx, "global", claimEntries("left(e, 1) not in ('-', '@')", "e"), claimEntries("left(e, 1) = '-'", "substr(e, 2)"), claimEntries("left(e, 1) = '@'", "substr(e, 2)")).trimStart()}
857
- ))`;
858
- }
859
- /** The claim `jwt` mode reads platform custom roles from: `customRoleClaim(globalRoles)`. */
860
- const GLOBAL_GRANTS_CLAIM = "role_grants";
861
- function memberColumn(name) {
862
- return `m.${quoteIdent(name)}`;
863
- }
864
- /** Entries of a compact custom-role claim (`cg.g`) that match `where`. */
865
- function claimEntries(where, value) {
866
- return `array(select ${value} from jsonb_array_elements_text(cg.g) e where ${where})`;
867
- }
868
- function textArray(values) {
869
- return values.length === 0 ? `'{}'::text[]` : `array[${values.map(quoteLiteral).join(", ")}]::text[]`;
870
- }
871
- /** `p_grant in (select permdock_custom_keys(allows, denies, includes, scope))`. */
872
- function customKeysSql(ctx, scope, allows, denies, includes) {
873
- return ` and p_grant in (select ${qualified(ctx, CUSTOM_ROLES.keys)}(
874
- ${allows},
875
- ${denies},
876
- ${includes},
877
- ${quoteLiteral(scope)}
878
- ))`;
879
- }
880
- function tableBody(ctx, scope, type) {
881
- const mapped = scopeTable(ctx, scope);
882
- if (mapped === void 0) return ` select null::${type} where false -- no ${scope} memberships table configured`;
883
- const { table, column, tenantColumn } = mapped;
884
- const filters = [` where ${memberColumn(table.user)} = ${subjectIdSql(ctx)}`];
885
- if (table.expiresAt !== void 0) {
886
- const expires = memberColumn(table.expiresAt);
887
- filters.push(` and (${expires} is null or ${expires} > now())`);
888
- }
889
- if (tenantColumn !== void 0) filters.push(` and (${activeTenant(ctx)} is null or ${memberColumn(tenantColumn)}::text = ${activeTenant(ctx)})`);
890
- filters.push(...userActive(ctx, " "), ...instancesActive(ctx, scope, (name) => {
891
- const held = scopeColumn(table, ctx.scopes, name);
892
- return held === void 0 ? void 0 : memberColumn(held);
893
- }, " "));
894
- const [owner, ...rest] = filters;
895
- const kind = kindFilterSql(ctx, `${memberColumn(table.role)}::text`, table.via === void 0 ? "null::text" : `${memberColumn(table.via)}::text`);
896
- const lines = [
897
- ` select ${memberColumn(column)}::${type}`,
898
- ` from ${membershipTable(table.table)} m`,
899
- ` join ${qualified(ctx, "role_permissions")} rp on rp.role = ${memberColumn(table.role)}::text`,
900
- owner ?? "",
901
- " and rp.grant_key = p_grant",
902
- ` and rp.scope = ${quoteLiteral(scope)}`,
903
- ...kind === void 0 ? [] : [` and ${kind}`],
904
- ...rest
905
- ];
906
- const custom = ctx.customRoles;
907
- if (custom === void 0) return lines.join("\n");
908
- const match = [
909
- `c.tenant_id::text = ${memberColumn(tenantColumn ?? column)}::text`,
910
- `c.scope = ${quoteLiteral(scope)}`,
911
- `(c.scope_id is null or c.scope_id = ${memberColumn(column)}::text)`,
912
- `c.role = ${memberColumn(table.role)}::text`
913
- ].join(" and ");
914
- const rows = (source, value, extra) => `array(select c.${value} from ${qualified(ctx, source)} c where ${match}${extra})`;
915
- return [
916
- ...lines,
917
- " union",
918
- ` select ${memberColumn(column)}::${type}`,
919
- ` from ${membershipTable(table.table)} m`,
920
- ...filters,
921
- ` and not (${memberColumn(table.role)}::text = any(${textArray(custom.declared)}))`,
922
- customKeysSql(ctx, scope, rows(CUSTOM_ROLES.permissions, "permission", " and c.effect = 'allow'"), rows(CUSTOM_ROLES.permissions, "permission", " and c.effect = 'deny'"), rows(CUSTOM_ROLES.includes, "include_role", ""))
923
- ].join("\n");
924
- }
925
- /** The membership's instance of the first scope, from the canonical claim. */
926
- function claimTenant(ctx, scope) {
927
- const root = rootName(ctx);
928
- return scope === root ? `m ->> 'id'` : `m -> 'within' ->> ${quoteLiteral(root)}`;
929
- }
930
- function claimBody(ctx, scope, type) {
931
- const narrow = underRoot(ctx, scope) ? `
932
- and (${activeTenant(ctx)} is null or ${claimTenant(ctx, scope)} = ${activeTenant(ctx)})` : "";
933
- const filters = ` and m ->> 'scope' = ${quoteLiteral(scope)}
934
- and m ->> 'id' is not null${narrow}
935
- and case jsonb_typeof(m -> 'expiresAt')
936
- when 'number' then (m ->> 'expiresAt')::numeric > extract(epoch from now())
937
- else true
938
- end${[...userActive(ctx, " "), ...instancesActive(ctx, scope, (name) => name === scope ? `m ->> 'id'` : `m -> 'within' ->> ${quoteLiteral(name)}`, " ")].map((line) => `\n${line}`).join("")}`;
939
- const declared = ` select (m ->> 'id')::${type}
940
- from ${membershipRows(ctx)}
941
- join ${qualified(ctx, "role_permissions")} rp on rp.role = r.role
942
- where ${signedIn(ctx)}
943
- and rp.grant_key = p_grant
944
- and rp.scope = ${quoteLiteral(scope)}${andLine(" ", kindFilterSql(ctx, "r.role", "m ->> 'via'"))}
945
- ${filters}`;
946
- const custom = ctx.customRoles;
947
- if (custom === void 0) return declared;
948
- return `${declared}
949
- union
950
- select (m ->> 'id')::${type}
951
- from ${membershipRows(ctx)}
952
- cross join lateral (select m -> 'grants' -> r.role as g) cg
953
- where ${signedIn(ctx)}
954
- and jsonb_typeof(cg.g) = 'array'
955
- and not (r.role = any(${textArray(custom.declared)}))
956
- ${filters}
957
- ${customKeysSql(ctx, scope, claimEntries("left(e, 1) not in ('-', '@')", "e"), claimEntries("left(e, 1) = '-'", "substr(e, 2)"), claimEntries("left(e, 1) = '@'", "substr(e, 2)"))}`;
958
- }
959
- /**
960
- * The custom-role objects: the tables (`database` mode), the ceiling view
961
- * over the `assignable` declared roles, and `permdock_custom_keys`, which
962
- * resolves one custom role to the grant keys it holds in a scope. A row in
963
- * the tables can never reach a key outside the view.
964
- */
965
- function customRolesSql(ctx) {
966
- const custom = ctx.customRoles;
967
- if (custom === void 0) return "";
968
- const rp = qualified(ctx, "role_permissions");
969
- const ceiling = qualified(ctx, CUSTOM_ROLES.ceiling);
970
- const keys = qualified(ctx, CUSTOM_ROLES.keys);
971
- const chunks = [];
972
- if (ctx.authorize === "database") {
973
- const tenantType = tenantTypeOf(ctx);
974
- for (const [name, column, check] of [[
975
- CUSTOM_ROLES.permissions,
976
- "permission",
977
- `,
978
- effect text not null default 'allow' check (effect in ('allow', 'deny'))`
979
- ], [
980
- CUSTOM_ROLES.includes,
981
- "include_role",
982
- ""
983
- ]]) {
984
- const table = qualified(ctx, name);
985
- const unique = `${column}${name === CUSTOM_ROLES.permissions ? ", effect" : ""}`;
986
- chunks.push(`create table if not exists ${table} (
987
- tenant_id ${tenantType},
988
- scope text not null default ${quoteLiteral(rootName(ctx))},
989
- scope_id text,
990
- role text not null,
991
- ${column} text not null${check},
992
- check ((scope = 'global') = (tenant_id is null)),
993
- check (scope <> 'global' or scope_id is null)
994
- );
995
- create unique index if not exists ${quoteIdent(`${name}_key`)}
996
- on ${table} (coalesce(tenant_id::text, ''), scope, coalesce(scope_id, ''), role, ${unique});
997
- alter table ${table} enable row level security;
998
- revoke all on table ${table} from anon, authenticated, public;`);
999
- }
1000
- }
1001
- chunks.push(`-- the ceiling: keys of assignable declared roles, and the denies of those roles on the same permissions
1002
- create or replace view ${ceiling} with (security_invoker = true) as
1003
- select rp.scope, rp.role, rp.permission, rp.grant_key, rp.effect
1004
- from ${rp} rp
1005
- where rp.role = any(${textArray(custom.assignable)})
1006
- and exists (
1007
- select 1 from ${rp} a
1008
- where a.role = rp.role
1009
- and a.permission = rp.permission
1010
- and a.scope = rp.scope
1011
- and a.effect = 'allow'
1012
- );
1013
- revoke all on table ${ceiling} from anon, authenticated, public;`);
1014
- const rename = renamesSql(Object.entries(custom.renamed ?? {}).toSorted(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
1015
- chunks.push(`-- one custom role: included ceiling keys and same-scope denies, plus every ceiling key of an allowed permission, minus denied permissions
1016
- create or replace function ${keys}(p_allow text[], p_deny text[], p_include text[], p_scope text)
1017
- returns setof text
1018
- language sql
1019
- stable
1020
- set search_path = ''
1021
- as $$
1022
- with ${rename.prelude}ceiling as (
1023
- select c.role, ${rename.permission("c")}, c.grant_key, c.effect
1024
- from ${ceiling} c${rename.join("c")}
1025
- where c.scope = p_scope
1026
- ),
1027
- included as (
1028
- select rp.role, ${rename.permission("rp")}, rp.grant_key, rp.scope, rp.effect
1029
- from ${rp} rp${rename.join("rp")}
1030
- where rp.role = any(coalesce(p_include, '{}'::text[]))
1031
- ),
1032
- kept as (
1033
- select i.permission, i.grant_key
1034
- from included i
1035
- where i.effect = 'allow'
1036
- and i.scope = p_scope
1037
- and exists (
1038
- select 1 from ceiling c
1039
- where c.role = i.role and c.grant_key = i.grant_key and c.effect = 'allow'
1040
- )
1041
- ),
1042
- wanted as (
1043
- ${rename.allow}
1044
- union
1045
- select i.permission
1046
- from included i
1047
- where i.effect = 'allow'
1048
- and not (i.scope = p_scope and exists (
1049
- select 1 from ceiling c
1050
- where c.role = i.role and c.grant_key = i.grant_key and c.effect = 'allow'
1051
- ))
1052
- ),
1053
- allowed as (
1054
- select w.permission
1055
- from wanted w
1056
- where not (${rename.denied("w")})
1057
- and not exists (select 1 from kept k where k.permission = w.permission)
1058
- )
1059
- select k.grant_key from kept k
1060
- where not (${rename.denied("k")})
1061
- union
1062
- select i.grant_key from included i
1063
- where i.effect = 'deny' and i.scope = p_scope
1064
- union
1065
- select c.grant_key from ceiling c
1066
- where c.permission in (select a.permission from allowed a)
1067
- $$;
1068
- revoke execute on function ${keys}(text[], text[], text[], text) from public, anon, authenticated;`);
1069
- return chunks.join("\n\n");
1070
- }
1071
- function currentKeysSql(array) {
1072
- return `select coalesce(r.key, e.permission) as permission
1073
- from unnest(coalesce(${array}, '{}'::text[])) as e(permission)
1074
- left join renamed r on r.former = e.permission`;
1075
- }
1076
- /**
1077
- * SQL fragments that read a stored former key as its current key inside
1078
- * `permdock_custom_keys`. Without renames they are the plain column and
1079
- * array reads.
1080
- */
1081
- function renamesSql(renames) {
1082
- if (renames.length === 0) return {
1083
- prelude: "",
1084
- permission: (alias) => `${alias}.permission`,
1085
- join: () => "",
1086
- allow: "select unnest(coalesce(p_allow, '{}'::text[])) as permission",
1087
- denied: (alias) => `${alias}.permission = any(coalesce(p_deny, '{}'::text[]))`
1088
- };
1089
- return {
1090
- prelude: `renamed (former, key) as (values ${renames.map(([former, current]) => `(${quoteLiteral(former)}, ${quoteLiteral(current)})`).join(", ")}),
1091
- denied as (
1092
- ${currentKeysSql("p_deny")}
1093
- ),
1094
- `,
1095
- permission: (alias) => `coalesce(r_${alias}.key, ${alias}.permission) as permission`,
1096
- join: (alias) => ` left join renamed r_${alias} on r_${alias}.former = ${alias}.permission`,
1097
- allow: currentKeysSql("p_allow"),
1098
- denied: (alias) => `${alias}.permission in (select d.permission from denied d)`
1099
- };
1100
- }
1101
- /**
1102
- * The rows the membership sources select for the subject, one per instance
1103
- * (`scope`, `id`, `within`, `roles`, `via`), with each source's own expiry
1104
- * and suspension filters: the statements the token hook runs.
1105
- */
1106
- function sourceRows(sources) {
1107
- return sources.map((source, index) => source.sql.select(sourceUser(index)).replaceAll(/^/gmu, " ")).join("\n union all\n");
1108
- }
1109
- function sourceUser(index) {
1110
- return `v_user_${String(index)}`;
1111
- }
1112
- function sqlBody(sql) {
1113
- return {
1114
- sql,
1115
- vars: []
1116
- };
1117
- }
1118
- function sourcesBodyOf(sql, sources, user) {
1119
- return {
1120
- sql,
1121
- vars: sources.map((source, index) => `${sourceUser(index)} ${source.sql.userType} := ${user};`)
1122
- };
1123
- }
1124
- /** `language sql`, or `plpgsql` returning the query when the body declares variables. */
1125
- function functionBody(body) {
1126
- if (body.vars.length === 0) return `language sql
1127
- stable
1128
- security definer
1129
- set search_path = ''
1130
- as $$
1131
- ${body.sql}
1132
- $$;`;
1133
- return `language plpgsql
1134
- stable
1135
- security definer
1136
- set search_path = ''
1137
- as $$
1138
- declare
1139
- ${body.vars.map((line) => ` ${line}`).join("\n")}
1140
- begin
1141
- return query
1142
- ${body.sql};
1143
- end;
1144
- $$;`;
1145
- }
1146
- /**
1147
- * `ms.id` for the scope itself and `ms.within ->> name` for an ancestor. The
1148
- * alias is not `s`, which `activeRowSql` binds inside its `exists`.
1149
- */
1150
- function sourceIdOf(scope) {
1151
- return (name) => name === scope ? "ms.id" : `ms.within ->> ${quoteLiteral(name)}`;
1152
- }
1153
- function sourceFilters(ctx, scope, user = subjectIdSql(ctx)) {
1154
- return [...userActive(ctx, " ", user), ...instancesActive(ctx, scope, sourceIdOf(scope), " ")].map((line) => `\n${line}`).join("");
1155
- }
1156
- function sourcesBody(ctx, scope, type) {
1157
- if (ctx.customRoles !== void 0) throw new Error(`PermDock CLI: --custom-roles in database mode needs rls.memberships.scopes.${scope}: the membership sources carry no custom roles`);
1158
- const root = rootName(ctx);
1159
- const narrow = underRoot(ctx, scope) ? `\n and (${activeTenant(ctx)} is null or ${sourceIdOf(scope)(root)} = ${activeTenant(ctx)})` : "";
1160
- const sources = scopeSources(ctx, scope);
1161
- return sourcesBodyOf(` select (ms.id)::${type}
1162
- from (
1163
- ${sourceRows(sources)}
1164
- ) ms
1165
- cross join lateral jsonb_array_elements_text(
1166
- case jsonb_typeof(ms.roles) when 'array' then ms.roles else '[]'::jsonb end
1167
- ) r(role)
1168
- join ${qualified(ctx, "role_permissions")} rp on rp.role = r.role
1169
- where ${signedIn(ctx)}
1170
- and ms.scope = ${quoteLiteral(scope)}
1171
- and rp.grant_key = p_grant
1172
- and rp.scope = ${quoteLiteral(scope)}${andLine(" ", kindFilterSql(ctx, "r.role", "ms.via"))}${narrow}${sourceFilters(ctx, scope)}`, sources, subjectIdSql(ctx));
1173
- }
1174
- function scopedBody(ctx, scope, type) {
1175
- if (ctx.authorize !== "database") return sqlBody(claimBody(ctx, scope, type));
1176
- return scopeSources(ctx, scope).length > 0 ? sourcesBody(ctx, scope, type) : sqlBody(tableBody(ctx, scope, type));
1177
- }
1178
- /**
1179
- * The body of `member_<scope>_ids()`: instances of `scope` the subject holds
1180
- * a live membership of, with any role. Not narrowed to the active tenant, and
1181
- * no cascade: a membership of a child scope is not one of its parent.
1182
- */
1183
- function memberBody(ctx, scope, type) {
1184
- if (ctx.authorize !== "database") return sqlBody(` select distinct (m ->> 'id')::${type}
1185
- from jsonb_array_elements(
1186
- case jsonb_typeof(${membershipClaim(ctx)}) when 'array' then ${membershipClaim(ctx)} else '[]'::jsonb end
1187
- ) m
1188
- where ${signedIn(ctx)}
1189
- and m ->> 'scope' = ${quoteLiteral(scope)}
1190
- and m ->> 'id' is not null
1191
- and jsonb_typeof(m -> 'roles') = 'array'
1192
- and jsonb_array_length(m -> 'roles') > 0
1193
- and case jsonb_typeof(m -> 'expiresAt')
1194
- when 'number' then (m ->> 'expiresAt')::numeric > extract(epoch from now())
1195
- else true
1196
- end${[...userActive(ctx, " "), ...instancesActive(ctx, scope, (name) => name === scope ? `m ->> 'id'` : `m -> 'within' ->> ${quoteLiteral(name)}`, " ")].map((line) => `\n${line}`).join("")}`);
1197
- return memberRowsBody(ctx, scope, type, subjectIdSql(ctx), scopeSources(ctx, scope));
1198
- }
1199
- /**
1200
- * The membership rows of `user` for `scope`, from the sources or else the
1201
- * mapped table, with the same expiry and suspension filters either way.
1202
- */
1203
- function memberRowsBody(ctx, scope, type, user, sources) {
1204
- if (sources.length > 0) return sourcesBodyOf(` select distinct (ms.id)::${type}
1205
- from (
1206
- ${sourceRows(sources)}
1207
- ) ms
1208
- where ${signedIn(ctx, user)}
1209
- and ms.scope = ${quoteLiteral(scope)}
1210
- and jsonb_typeof(ms.roles) = 'array'
1211
- and jsonb_array_length(ms.roles) > 0${sourceFilters(ctx, scope, user)}`, sources, user);
1212
- const mapped = scopeTable(ctx, scope);
1213
- if (mapped === void 0) return sqlBody(` select null::${type} where false -- no ${scope} memberships table configured`);
1214
- const { table, column } = mapped;
1215
- const lines = [
1216
- ` select distinct ${memberColumn(column)}::${type}`,
1217
- ` from ${membershipTable(table.table)} m`,
1218
- ` where ${memberColumn(table.user)} = ${user}`,
1219
- ` and ${memberColumn(table.role)} is not null`
1220
- ];
1221
- if (table.expiresAt !== void 0) {
1222
- const expires = memberColumn(table.expiresAt);
1223
- lines.push(` and (${expires} is null or ${expires} > now())`);
1224
- }
1225
- lines.push(...userActive(ctx, " ", user), ...instancesActive(ctx, scope, (name) => {
1226
- const held = scopeColumn(table, ctx.scopes, name);
1227
- return held === void 0 ? void 0 : memberColumn(held);
1228
- }, " "));
1229
- return sqlBody(lines.join("\n"));
1230
- }
1231
- function membershipClaim(ctx) {
1232
- const raw = subjectClaimJsonSql(ctx, "memberships");
1233
- return ctx.dialect === "supabase" ? `coalesce(${raw}, (select auth.jwt()) -> 'app_metadata' -> 'memberships')` : raw;
1234
- }
1235
- function memberFunction(ctx, scope, type, anonExecute) {
1236
- const fn = qualified(ctx, memberIdsHelper(scope));
1237
- const grants = anonExecute ? `revoke execute on function ${fn}() from public;
1238
- grant execute on function ${fn}() to anon, authenticated;` : `revoke execute on function ${fn}() from public, anon;
1239
- grant execute on function ${fn}() to authenticated;`;
1240
- return `create or replace function ${fn}()
1241
- returns setof ${type}
1242
- ${functionBody(memberBody(ctx, scope, type))}
1243
- ${grants}`;
1244
- }
1245
- /**
1246
- * `member_<scope>_ids_for(p_user uuid)`: `member_<scope>_ids()` for a user the
1247
- * caller names, read from the membership sources (or the mapped table) in
1248
- * either mode, so a token hook can call it before any claim exists. Naming
1249
- * the user is why no client role may execute it; the hook's grants give
1250
- * `supabase_auth_admin` execute.
1251
- */
1252
- function memberForFunction(ctx, scope, type) {
1253
- const fn = qualified(ctx, memberForHelper(scope));
1254
- return `create or replace function ${fn}(p_user uuid)
1255
- returns setof ${type}
1256
- ${functionBody(memberRowsBody(ctx, scope, type, "p_user", memberForSources(ctx, scope)))}
1257
- revoke execute on function ${fn}(uuid) from public, anon, authenticated;`;
1258
- }
1259
- function helperFunction(ctx, name, returns, body, anonExecute) {
1260
- const fn = qualified(ctx, name);
1261
- const grants = anonExecute ? `revoke execute on function ${fn}(text) from public;
1262
- grant execute on function ${fn}(text) to anon, authenticated;` : `revoke execute on function ${fn}(text) from public, anon;
1263
- grant execute on function ${fn}(text) to authenticated;`;
1264
- return `create or replace function ${fn}(p_grant text)
1265
- returns ${returns}
1266
- ${functionBody(body)}
1267
- ${grants}`;
1268
- }
1269
- function userIdType(ctx) {
1270
- return ctx.dialect === "supabase" ? "uuid not null references auth.users on delete cascade" : "text not null";
1271
- }
1272
- /**
1273
- * The `role_permissions` rows the policy compiles to: an upsert and a delete
1274
- * of every other row. Data, not schema, so `supabase db diff` drops it from a
1275
- * declarative schema; `--split ...,seeds` writes it as its own migration.
1276
- */
1277
- function seedSql(ctx, rows) {
1278
- const table = qualified(ctx, "role_permissions");
1279
- if (rows.length === 0) return `delete from ${table};`;
1280
- return `insert into ${table} (role, permission, grant_key, scope, effect) values
1281
- ${rows.map((row) => ` (${[
1282
- row.role,
1283
- row.permission,
1284
- row.grantKey,
1285
- row.scope,
1286
- row.effect
1287
- ].map(quoteLiteral).join(", ")})`).join(",\n")}
1288
- on conflict (role, grant_key, scope) do update
1289
- set permission = excluded.permission, effect = excluded.effect;
1290
- delete from ${table}
1291
- where (role, grant_key, scope) not in (values
1292
- ${rows.map((row) => ` (${[
1293
- row.role,
1294
- row.grantKey,
1295
- row.scope
1296
- ].map(quoteLiteral).join(", ")})`).join(",\n")}
1297
- );`;
1298
- }
1299
- /**
1300
- * `role_permissions`, its seeds, and the three `security definer` helpers
1301
- * with `search_path = ''`. `execute` goes to `authenticated` only.
1302
- */
1303
- function helpersSql(ctx, rows, options) {
1304
- const schema = helperSchema(ctx);
1305
- const s = quoteIdent(schema);
1306
- const rp = qualified(ctx, "role_permissions");
1307
- const chunks = [`-- permdock helpers (${ctx.authorize === "database" ? "database: reads the membership and user_roles tables" : "jwt: reads the role and memberships claims"})
1308
- -- policies call them uncorrelated, so Postgres evaluates each once per statement`];
1309
- if (schema !== "public") chunks.push(`create schema if not exists ${s};\nrevoke all on schema ${s} from public;\ngrant usage on schema ${s} to ${options.anonExecute === true ? "anon, authenticated" : "authenticated"};`);
1310
- chunks.push(`create table if not exists ${rp} (
1311
- role text not null,
1312
- permission text not null,
1313
- grant_key text not null,
1314
- scope text not null check (scope ~ '^[a-z][a-z0-9_]*$'),
1315
- effect text not null default 'allow' check (effect in ('allow', 'deny')),
1316
- primary key (role, grant_key, scope)
1317
- );
1318
- alter table ${rp} enable row level security;
1319
- revoke all on table ${rp} from anon, authenticated, public;`);
1320
- if (options.withoutSeeds !== true) chunks.push(seedSql(ctx, rows));
1321
- if (ctx.authorize === "database" && options.userRoles && ctx.roles === void 0) {
1322
- const ur = qualified(ctx, "user_roles");
1323
- chunks.push(`create table if not exists ${ur} (
1324
- user_id ${userIdType(ctx)},
1325
- role text not null,
1326
- primary key (user_id, role)
1327
- );
1328
- alter table ${ur} enable row level security;
1329
- revoke all on table ${ur} from anon, authenticated, public;`);
1330
- }
1331
- const custom = customRolesSql(ctx);
1332
- if (custom !== "") chunks.push(custom);
1333
- const anon = options.anonExecute === true;
1334
- chunks.push(helperFunction(ctx, HELPERS.has, "boolean", sqlBody(hasBody(ctx)), anon));
1335
- const capabilities = capabilitiesSql(ctx);
1336
- if (capabilities !== "") chunks.push(capabilities);
1337
- for (const scope of ctx.scopes) {
1338
- const type = scopeTypeOf(ctx, scope.name);
1339
- chunks.push(helperFunction(ctx, permittedIdsHelper(scope.name), `setof ${type}`, scopedBody(ctx, scope.name, type), anon));
1340
- chunks.push(memberFunction(ctx, scope.name, type, anon));
1341
- if (hasMemberFor(ctx, scope.name)) chunks.push(memberForFunction(ctx, scope.name, type));
1342
- }
1343
- return `${chunks.join("\n\n")}\n`;
1344
- }
1345
- //#endregion
1346
- export { memberForSources as A, scopeSources as B, globalKindFilterSql as C, kindFilterSql as D, hasMemberFor as E, qualifiedTable as F, subjectClaimSql as G, scopeTypeOf as H, quoteIdent as I, subjectIdSql as K, quoteLiteral as L, memberIdsHelper as M, parseMembershipsFlag as N, linkHelper as O, permittedIdsHelper as P, quoteTable as R, contextRefs as S, graphSqlText as T, sqlFunctionNames as U, scopeTable as V, subjectClaimJsonSql as W, andConditions as _, helpersSql as a, columnTypesOf as b, membershipTable as c, seedSql as d, signedIn as f, activeUserSql as g, activeRowSql as h, globalRoleRows as i, memberForTable as j, memberForHelper as k, qualified as l, CLOSURE as m, accessSql as n, memberColumn as o, globalRoleSource as p, capabilityAccessSql as r, membershipRows as s, HELPERS as t, roleRows as u, arrayColumnsOf as v, graphHelper as w, compileConditionSql as x, checkSuspension as y, roleKindSql as z };