permdock 0.0.0 → 0.2.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 (344) hide show
  1. package/README.md +327 -1
  2. package/dist/a2a/index.d.ts +169 -0
  3. package/dist/a2a/index.js +232 -0
  4. package/dist/adapter-context-mlWsVGGg.js +23 -0
  5. package/dist/ai-sdk/index.d.ts +95 -0
  6. package/dist/ai-sdk/index.js +154 -0
  7. package/dist/approval-required-TzNB9oQA.js +6 -0
  8. package/dist/approvals/index.d.ts +3 -0
  9. package/dist/approvals/index.js +5 -0
  10. package/dist/approvals-DbYAuNkG.js +224 -0
  11. package/dist/approvers-DT4AVyIg.js +52 -0
  12. package/dist/arazzo-BAgw9ap6.js +70 -0
  13. package/dist/arazzo-cmvX2jDe.js +54 -0
  14. package/dist/authzen/index.d.ts +39 -0
  15. package/dist/authzen/index.js +302 -0
  16. package/dist/better-auth/index.d.ts +109 -0
  17. package/dist/better-auth/index.js +344 -0
  18. package/dist/better-supabase/index.d.ts +206 -0
  19. package/dist/better-supabase/index.js +620 -0
  20. package/dist/bind-CVy3Espv.d.ts +25 -0
  21. package/dist/bind-VnYn2HKq.js +97 -0
  22. package/dist/boundary-BM8a_P6E.js +13 -0
  23. package/dist/boundary-BmHxVH1J.d.ts +15 -0
  24. package/dist/budget-JqcRqyhK.d.ts +11 -0
  25. package/dist/budget-NbEtbJC_.js +11 -0
  26. package/dist/canonical-json-DemiK4Vp.js +26 -0
  27. package/dist/capability-4qPdO9gk.js +173 -0
  28. package/dist/capability-Dq43tT1u.d.ts +99 -0
  29. package/dist/catalog/index.d.ts +12 -0
  30. package/dist/catalog/index.js +2 -0
  31. package/dist/catalog-BjZSovXQ.js +2 -0
  32. package/dist/catalog-doc-Dsc9y7sd.js +309 -0
  33. package/dist/catalog-h2tlKFkq.js +48 -0
  34. package/dist/catalog-path-CnI0XTFK.js +12 -0
  35. package/dist/catalog-z8q6bi_r.js +55 -0
  36. package/dist/claude-agent/index.d.ts +72 -0
  37. package/dist/claude-agent/index.js +77 -0
  38. package/dist/clerk/index.d.ts +65 -0
  39. package/dist/clerk/index.js +301 -0
  40. package/dist/cli/bin.d.ts +1 -0
  41. package/dist/cli/bin.js +20 -0
  42. package/dist/cli/index.d.ts +46 -0
  43. package/dist/cli/index.js +5 -0
  44. package/dist/clients-C7zLFuWT.d.ts +12 -0
  45. package/dist/clients-DjU5ABMy.js +23 -0
  46. package/dist/cloud/index.d.ts +90 -0
  47. package/dist/cloud/index.js +404 -0
  48. package/dist/cloud-BGhHS-lY.js +50 -0
  49. package/dist/cloud-Bo3e9Ak3.js +118 -0
  50. package/dist/collect-DDDv_ESs.js +502 -0
  51. package/dist/collect-DLlujdHC.js +83 -0
  52. package/dist/collect-rG2IQ3t0.js +2 -0
  53. package/dist/commands-DWOrSQic.js +97 -0
  54. package/dist/compact-y6vfu2_f.js +19 -0
  55. package/dist/compare-pfyVnBjE.js +12 -0
  56. package/dist/compile/index.d.ts +3 -0
  57. package/dist/compile/index.js +2 -0
  58. package/dist/compile-Ba4blROR.js +613 -0
  59. package/dist/compile-CNnl6uIt.d.ts +94 -0
  60. package/dist/config-BJiM-xAm.js +157 -0
  61. package/dist/config-Bn00Z3ks.js +36 -0
  62. package/dist/config-report-Dw_JcUqT.js +44 -0
  63. package/dist/connection-CU8MQxRi.js +194 -0
  64. package/dist/context-Ttp0ekMT.js +82 -0
  65. package/dist/convex/index.d.ts +45 -0
  66. package/dist/convex/index.js +65 -0
  67. package/dist/create-BWyx3jua.d.ts +245 -0
  68. package/dist/create-CPhDL6qx.js +253 -0
  69. package/dist/credential-8P81ff4u.js +306 -0
  70. package/dist/delegation-D8pUEuce.js +103 -0
  71. package/dist/describe-B_gVZEVi.js +94 -0
  72. package/dist/describe-PdoqESvg.d.ts +36 -0
  73. package/dist/diff-B8Tunc3o.js +451 -0
  74. package/dist/diff-CzqLxZ77.js +48 -0
  75. package/dist/digest-B3kc_PgQ.d.ts +19 -0
  76. package/dist/digest-BI-6aa6d.js +27 -0
  77. package/dist/doctor-WpvG6p4k.js +1194 -0
  78. package/dist/doctor-collect-ByfYlNv1.js +664 -0
  79. package/dist/doctor-rOr-4ZfS.js +53 -0
  80. package/dist/drizzle/index.d.ts +73 -0
  81. package/dist/drizzle/index.js +160 -0
  82. package/dist/duration-xK2jGFVc.js +25 -0
  83. package/dist/elysia/index.d.ts +49 -0
  84. package/dist/elysia/index.js +59 -0
  85. package/dist/errors-CWfoNg9N.js +47 -0
  86. package/dist/errors-Cz5zn0mW.js +123 -0
  87. package/dist/errors-DAqevpNk.d.ts +125 -0
  88. package/dist/errors-DRnGZHzF.js +30 -0
  89. package/dist/evaluation-items-CfCCejQd.js +40 -0
  90. package/dist/evaluations-tz6D9A2G.js +461 -0
  91. package/dist/eve/index.d.ts +86 -0
  92. package/dist/eve/index.js +176 -0
  93. package/dist/expiry-BoBJ8Tvc.js +7 -0
  94. package/dist/express/index.d.ts +23 -0
  95. package/dist/express/index.js +78 -0
  96. package/dist/fastify/index.d.ts +33 -0
  97. package/dist/fastify/index.js +92 -0
  98. package/dist/fields-BMl1RxoX.js +26 -0
  99. package/dist/files-DWUN9PzI.js +125 -0
  100. package/dist/fixtures-ht28Cplr.js +67 -0
  101. package/dist/freeze-CrU8nQeX.js +60 -0
  102. package/dist/from-snapshot-C3sHpZNS.js +569 -0
  103. package/dist/generate-CH7ThJ14.js +173 -0
  104. package/dist/grantee-Cv5Sogfv.js +602 -0
  105. package/dist/graph-sql-B-dsAT6j.d.ts +29 -0
  106. package/dist/headers-BZSDieNT.js +6 -0
  107. package/dist/headers-Cf-zWvsO.d.ts +6 -0
  108. package/dist/helpers-H_KARDVC.js +421 -0
  109. package/dist/hono/index.d.ts +48 -0
  110. package/dist/hono/index.js +99 -0
  111. package/dist/hosted-DJQw46hn.js +266 -0
  112. package/dist/http-BIhmLfCc.js +95 -0
  113. package/dist/http-DPuDaChb.d.ts +13 -0
  114. package/dist/http-SO7qGttf.js +44 -0
  115. package/dist/ids-DC4V4CFu.js +18 -0
  116. package/dist/index-A0YNL8jZ.d.ts +177 -0
  117. package/dist/index-CtE07CBF.d.ts +6 -0
  118. package/dist/index-D3vbkoSI.d.ts +483 -0
  119. package/dist/index-D6_cSa1z.d.ts +201 -0
  120. package/dist/index.d.ts +16 -0
  121. package/dist/index.js +26 -0
  122. package/dist/instance-options-BK_rNOSC.d.ts +11 -0
  123. package/dist/instance-options-Dhhcnm3y.js +22 -0
  124. package/dist/json-schema-BsAflUGW.js +106 -0
  125. package/dist/jwt/index.d.ts +91 -0
  126. package/dist/jwt/index.js +590 -0
  127. package/dist/kernel-GSQnMz3O.js +208 -0
  128. package/dist/kysely/index.d.ts +78 -0
  129. package/dist/kysely/index.js +129 -0
  130. package/dist/live-options-H2eG8dfJ.js +60 -0
  131. package/dist/load-DhWEB9P1.js +75 -0
  132. package/dist/load-DwJUafYN.js +2 -0
  133. package/dist/load-jose-BL6LH4bL.js +11 -0
  134. package/dist/lru-8q4WZjBV.js +28 -0
  135. package/dist/manifest-iaoBXHKc.js +783 -0
  136. package/dist/manifest-uDS9y5de.d.ts +206 -0
  137. package/dist/map-AcxK9o_D.js +147 -0
  138. package/dist/map-claims-Z4QZOiFF.js +369 -0
  139. package/dist/map-error-pgUWNNtG.js +23 -0
  140. package/dist/markers-B0hYLvMD.js +60 -0
  141. package/dist/may-use-DndQ5tQ-.js +77 -0
  142. package/dist/mcp/index.d.ts +159 -0
  143. package/dist/mcp/index.js +603 -0
  144. package/dist/nest/index.d.ts +75 -0
  145. package/dist/nest/index.js +261 -0
  146. package/dist/next/client.d.ts +23 -0
  147. package/dist/next/client.js +33 -0
  148. package/dist/next/index.d.ts +93 -0
  149. package/dist/next/index.js +180 -0
  150. package/dist/next/plugin.d.ts +14 -0
  151. package/dist/next/plugin.js +33 -0
  152. package/dist/node/index.d.ts +24 -0
  153. package/dist/node/index.js +34 -0
  154. package/dist/openai/index.d.ts +62 -0
  155. package/dist/openai/index.js +92 -0
  156. package/dist/openapi/index.d.ts +176 -0
  157. package/dist/openapi/index.js +3 -0
  158. package/dist/openapi-Bbg4wGVL.js +164 -0
  159. package/dist/openapi-BqRBvAng.js +538 -0
  160. package/dist/openapi-CqqEbHsL.js +250 -0
  161. package/dist/openapi-import-cmwetbzw.js +5959 -0
  162. package/dist/openapi-schema--ARjyfQr.js +6902 -0
  163. package/dist/orpc/index.d.ts +51 -0
  164. package/dist/orpc/index.js +145 -0
  165. package/dist/otel/index.d.ts +13 -0
  166. package/dist/otel/index.js +202 -0
  167. package/dist/package-root-zGkxO3oN.js +30 -0
  168. package/dist/parse-BuyjxhZV.js +53 -0
  169. package/dist/parse-local-manifest-BkGQNzAh.d.ts +10 -0
  170. package/dist/parse-local-manifest-BrMozJCo.js +36 -0
  171. package/dist/pdp/index.d.ts +22 -0
  172. package/dist/pdp/index.js +682 -0
  173. package/dist/peer-BOr_kJE1.js +31 -0
  174. package/dist/permdock-CzqXjCpU.js +3471 -0
  175. package/dist/permissions-CDgvKG5L.d.ts +52 -0
  176. package/dist/permissions-CpZrivYv.js +142 -0
  177. package/dist/permissions-DVoF6dMt.js +662 -0
  178. package/dist/permissions-DttnqqxC.d.ts +214 -0
  179. package/dist/pins-8ILTftC9.js +12 -0
  180. package/dist/policy-CFZAr_Al.d.ts +2252 -0
  181. package/dist/policy-D8UFyERw.d.ts +20 -0
  182. package/dist/policy-xyWg7QdR.js +1122 -0
  183. package/dist/powersync-A8x_Yyvh.js +2 -0
  184. package/dist/powersync-C7kLglqX.js +540 -0
  185. package/dist/powersync-yW1UUFzp.js +62 -0
  186. package/dist/prisma/index.d.ts +136 -0
  187. package/dist/prisma/index.js +455 -0
  188. package/dist/problem-B43ztKIm.js +191 -0
  189. package/dist/problem-details-CkL50EEf.d.ts +7 -0
  190. package/dist/problem-details-_AMOYz-w.js +144 -0
  191. package/dist/prompt-CXYhLfsS.js +32 -0
  192. package/dist/protected-CNsMzrVd.d.ts +27 -0
  193. package/dist/protected-Cs6kTaHo.js +100 -0
  194. package/dist/protected-bZs6rbT1.d.ts +40 -0
  195. package/dist/provider-Cjr62YMV.d.ts +6 -0
  196. package/dist/provider-DLzpb2Xz.js +52 -0
  197. package/dist/provider-store-DNyC1y48.js +43 -0
  198. package/dist/react/index.d.ts +8 -0
  199. package/dist/react/index.js +7 -0
  200. package/dist/react/provider-client.d.ts +2 -0
  201. package/dist/react/provider-client.js +3 -0
  202. package/dist/react-native/index.d.ts +198 -0
  203. package/dist/react-native/index.js +696 -0
  204. package/dist/refs-KYb4ntaz.js +91 -0
  205. package/dist/relations-cjOL01hZ.js +505 -0
  206. package/dist/replay-BsOyTrdV.js +28 -0
  207. package/dist/replay-sqBGMnNs.d.ts +5 -0
  208. package/dist/resource-ref-CaS9yVPi.js +20 -0
  209. package/dist/revocations-Bhr0aMIC.js +27 -0
  210. package/dist/revocations-Cy06JNx2.d.ts +21 -0
  211. package/dist/rls-BMNydpHk.js +2521 -0
  212. package/dist/rls-CEvs64Kj.js +238 -0
  213. package/dist/rls-fields-BMasREhR.js +3502 -0
  214. package/dist/rls-graph-pc9fuXYm.js +435 -0
  215. package/dist/rls-import-D8F7j35Q.js +943 -0
  216. package/dist/rls-migrate-7wikfrVI.js +367 -0
  217. package/dist/rls-ownership-B5Lkq62W.js +843 -0
  218. package/dist/rls-shared-aOQ4hGTO.js +493 -0
  219. package/dist/rls-verify-D8B7dOTh.js +745 -0
  220. package/dist/row-check-Dt35czLy.d.ts +40 -0
  221. package/dist/rpc-DOK54_CS.js +89 -0
  222. package/dist/run-YXCJDNYk.js +174 -0
  223. package/dist/runtime-CB8gQFvS.js +11 -0
  224. package/dist/saas-Bj_FFkjN.js +1203 -0
  225. package/dist/schema-D3MnDMxa.js +300 -0
  226. package/dist/scim/index.d.ts +2 -0
  227. package/dist/scim/index.js +2 -0
  228. package/dist/scim-BFGBBAqR.js +1445 -0
  229. package/dist/scopes-CVdPrWN-.js +239 -0
  230. package/dist/server/index.d.ts +134 -0
  231. package/dist/server/index.js +11 -0
  232. package/dist/server-DlZ28Oz5.js +268 -0
  233. package/dist/sha256-BO0swRXL.js +150 -0
  234. package/dist/shared-DRu8VFVM.js +123 -0
  235. package/dist/sink-Bd9PSyfq.js +141 -0
  236. package/dist/sink-Gs6TTVa-.d.ts +104 -0
  237. package/dist/skills-Biq5uup3.js +192 -0
  238. package/dist/skills-JaymNCdK.js +56 -0
  239. package/dist/snapshot-Enxd0n-s.js +975 -0
  240. package/dist/snapshot-cache-D6jLelln.js +44 -0
  241. package/dist/snapshot-cache-DwQH8ufx.d.ts +45 -0
  242. package/dist/solid/index.d.ts +93 -0
  243. package/dist/solid/index.js +188 -0
  244. package/dist/sources-B0UvrJ21.js +534 -0
  245. package/dist/sources-BcJgw0lt.d.ts +192 -0
  246. package/dist/sql-xdg5f4Fi.js +27 -0
  247. package/dist/src-CSbUiozF.js +568 -0
  248. package/dist/ssf/index.d.ts +4 -0
  249. package/dist/ssf/index.js +596 -0
  250. package/dist/store-DMAWwAJV.js +371 -0
  251. package/dist/store-D_pjn91-.d.ts +36 -0
  252. package/dist/store-options-yO0V6gRn.js +716 -0
  253. package/dist/stores-4_sqiOA1.js +87 -0
  254. package/dist/stores-DVR5NHLE.d.ts +52 -0
  255. package/dist/stream-DDpvynSB.js +66 -0
  256. package/dist/stream-DlvG2hs-.d.ts +12 -0
  257. package/dist/style-CTm1AXDT.js +22 -0
  258. package/dist/subject-C6dLxqHU.js +24 -0
  259. package/dist/subject-settings-BwBmCdTR.js +128 -0
  260. package/dist/supabase/index.d.ts +183 -0
  261. package/dist/supabase/index.js +5 -0
  262. package/dist/supabase/middleware.d.ts +82 -0
  263. package/dist/supabase/middleware.js +102 -0
  264. package/dist/supabase-CwafwxxF.js +83 -0
  265. package/dist/supabase-DTBaC20O.js +774 -0
  266. package/dist/supabase-config-Bs44dare.js +472 -0
  267. package/dist/supabase-hook-BuxLQLpU.js +1814 -0
  268. package/dist/supabase-hook-D5qVd_D8.js +2 -0
  269. package/dist/supautils-DYZnzRWV.js +91 -0
  270. package/dist/svelte/PermissionBoundary.svelte +37 -0
  271. package/dist/svelte/Protected.svelte +40 -0
  272. package/dist/svelte/index.d.ts +13 -0
  273. package/dist/svelte/index.js +107 -0
  274. package/dist/svelte/runtime.d.ts +7 -0
  275. package/dist/svelte/runtime.js +4 -0
  276. package/dist/svelte/source.d.ts +13 -0
  277. package/dist/svelte/source.js +11 -0
  278. package/dist/terminal/index.d.ts +163 -0
  279. package/dist/terminal/index.js +833 -0
  280. package/dist/testing/index.d.ts +745 -0
  281. package/dist/testing/index.js +3672 -0
  282. package/dist/testing/saas/index.d.ts +194 -0
  283. package/dist/testing/saas/index.js +3 -0
  284. package/dist/testing/saas/permissions.d.ts +2 -0
  285. package/dist/testing/saas/permissions.js +2 -0
  286. package/dist/text-diff-BdkkYSbi.js +15 -0
  287. package/dist/thenable-okc2xDsj.js +10 -0
  288. package/dist/timeout-D51niuQP.js +8 -0
  289. package/dist/token-CIWxJOCh.js +50 -0
  290. package/dist/trpc/index.d.ts +61 -0
  291. package/dist/trpc/index.js +122 -0
  292. package/dist/types-9AWO9EGt.d.ts +197 -0
  293. package/dist/types-BPxgszlT.d.ts +550 -0
  294. package/dist/types-BVyImy5I.d.ts +120 -0
  295. package/dist/types-Bvc4oWT6.d.ts +9 -0
  296. package/dist/types-C3s9xVJG.d.ts +48 -0
  297. package/dist/types-Dnemwd77.d.ts +183 -0
  298. package/dist/types-kxznc7cn.d.ts +52 -0
  299. package/dist/types-vKyAMtcX.d.ts +157 -0
  300. package/dist/types-vNSDF7Dq.d.ts +66 -0
  301. package/dist/types-yF5LmQVD.d.ts +306 -0
  302. package/dist/unplugin/index.d.ts +7 -0
  303. package/dist/unplugin/index.js +31 -0
  304. package/dist/usage-D07N9QJ3.js +42 -0
  305. package/dist/usage-D82l1CwK.js +2 -0
  306. package/dist/usage-DG1oeCca.js +693 -0
  307. package/dist/validation-error-CnjZ4t92.js +33 -0
  308. package/dist/validation-error-CtF23SrC.d.ts +26 -0
  309. package/dist/validity-uOp8UpYn.js +245 -0
  310. package/dist/verifier-BxBo6I44.js +466 -0
  311. package/dist/version-BvacVTZR.js +17 -0
  312. package/dist/views-Soe7bGSC.js +95 -0
  313. package/dist/vocabulary-DA8a8zif.d.ts +56 -0
  314. package/dist/vue/index.d.ts +79 -0
  315. package/dist/vue/index.js +196 -0
  316. package/dist/watch-Etoa_Mvr.js +2 -0
  317. package/dist/watch-UihESpsJ.js +128 -0
  318. package/dist/webmcp/index.d.ts +75 -0
  319. package/dist/webmcp/index.js +307 -0
  320. package/package.json +450 -5
  321. package/rulesets/permdock.yaml +29 -0
  322. package/schemas/approval-request-v1.json +175 -0
  323. package/schemas/catalog-v1.json +535 -0
  324. package/schemas/openapi/oas-3.1.json +1298 -0
  325. package/schemas/openapi/oas-3.2.json +1516 -0
  326. package/schemas/openapi/overlay-1.1.json +79 -0
  327. package/schemas/supabase-claims-v1.json +144 -0
  328. package/schemas/supabase-manifest-v1.json +361 -0
  329. package/skills/permdock/SKILL.md +66 -0
  330. package/skills/permdock-agents/SKILL.md +77 -0
  331. package/skills/permdock-agents/references/runtimes.md +213 -0
  332. package/skills/permdock-approvals/SKILL.md +80 -0
  333. package/skills/permdock-approvals/references/stores-and-resume.md +94 -0
  334. package/skills/permdock-audit/SKILL.md +67 -0
  335. package/skills/permdock-credentials/SKILL.md +55 -0
  336. package/skills/permdock-credentials/references/tokens-keys-links.md +132 -0
  337. package/skills/permdock-data/SKILL.md +57 -0
  338. package/skills/permdock-data/references/queries-and-rls.md +62 -0
  339. package/skills/permdock-data/references/relationships.md +67 -0
  340. package/skills/permdock-tenancy/SKILL.md +80 -0
  341. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +89 -0
  342. package/skills/permdock-wire/SKILL.md +116 -0
  343. package/skills/permdock-wire/references/adapters.md +492 -0
  344. package/skills/permdock-wire/references/policy-options.md +45 -0
@@ -0,0 +1,774 @@
1
+ import { t as compact } from "./compact-y6vfu2_f.js";
2
+ import { t as canonicalJson } from "./canonical-json-DemiK4Vp.js";
3
+ import { o as parseCapability } from "./capability-4qPdO9gk.js";
4
+ import { t as ApprovalError } from "./errors-DRnGZHzF.js";
5
+ import { a as decodeApprovalCursor, i as approvalPageSize, o as encodeApprovalCursor, t as applyApprovalVerdict } from "./store-DMAWwAJV.js";
6
+ import { t as loadJose } from "./load-jose-BL6LH4bL.js";
7
+ import "./budget-NbEtbJC_.js";
8
+ import { a as quoteSqlTable, i as quoteSqlLiteral, r as quoteSqlIdent } from "./sql-xdg5f4Fi.js";
9
+ import { l as keptKeys, s as roleColumn } from "./sources-B0UvrJ21.js";
10
+ import { i as readMemberships } from "./manifest-iaoBXHKc.js";
11
+ //#region src/supabase/capability.ts
12
+ const DEFAULT_TTL = 300;
13
+ const MAX_TTL = 3600;
14
+ function isSecret(key) {
15
+ return typeof key.secret === "string";
16
+ }
17
+ /**
18
+ * Exchanges a verified link subject (from `subjectFromCapability`) for a
19
+ * short-lived Supabase access token with role `anon` and the capability in a
20
+ * `capability` claim, which `permdock rls generate --capabilities` policies
21
+ * read. `undefined` for any subject that is not a live link.
22
+ */
23
+ async function exchangeCapability(subject, options) {
24
+ const alg = options.alg ?? "ES256";
25
+ const secret = isSecret(options.key);
26
+ if (secret !== (alg === "HS256")) throw new Error("PermDock: exchangeCapability signs HS256 with { secret } only, and ES256 / RS256 with a private JWK only.");
27
+ if (!secret && (options.kid === void 0 || options.kid.length === 0)) throw new Error("PermDock: exchangeCapability needs the kid of the imported signing key.");
28
+ const principal = subject.principal;
29
+ if (principal === null || principal.kind !== "link") return;
30
+ const capability = parseCapability(principal["capability"]);
31
+ if (capability?.holder !== "link" || capability.id !== principal.id) return;
32
+ const now = Math.floor(options.now ?? Date.now() / 1e3);
33
+ const ttl = Math.min(Math.max(1, options.ttl ?? DEFAULT_TTL), MAX_TTL);
34
+ const exp = Math.min(now + ttl, Math.floor(capability.expiresAt));
35
+ if (exp <= now) return;
36
+ const jose = await loadJose();
37
+ const key = isSecret(options.key) ? new TextEncoder().encode(options.key.secret) : await jose.importJWK(options.key, alg);
38
+ const jwt = new jose.SignJWT({
39
+ role: "anon",
40
+ capability
41
+ }).setProtectedHeader(secret ? {
42
+ alg: "HS256",
43
+ typ: "JWT"
44
+ } : {
45
+ alg,
46
+ ...options.kid === void 0 ? {} : { kid: options.kid },
47
+ typ: "JWT"
48
+ }).setIssuedAt(now).setExpirationTime(exp);
49
+ if (options.issuer !== void 0) jwt.setIssuer(options.issuer);
50
+ return jwt.sign(key);
51
+ }
52
+ //#endregion
53
+ //#region src/supabase/claims.ts
54
+ const KNOWN = /* @__PURE__ */ new Set([
55
+ "user_role",
56
+ "roles",
57
+ "memberships",
58
+ "memberships_truncated",
59
+ "attrs",
60
+ "authz_ver",
61
+ "client_id",
62
+ "scope",
63
+ "act",
64
+ "app_metadata"
65
+ ]);
66
+ function isRecord$2(value) {
67
+ return value !== null && typeof value === "object" && !Array.isArray(value);
68
+ }
69
+ function own(record, key) {
70
+ return Object.hasOwn(record, key) ? record[key] : void 0;
71
+ }
72
+ function issue(issues, path, message) {
73
+ issues.push({
74
+ message,
75
+ path: [...path]
76
+ });
77
+ }
78
+ function isStrings(value) {
79
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
80
+ }
81
+ function optional(record, key, path, issues, check, expected) {
82
+ const value = own(record, key);
83
+ if (value !== void 0 && !check(value)) issue(issues, [...path, key], `Expected ${expected}`);
84
+ }
85
+ const isString = (value) => typeof value === "string";
86
+ function checkMembership(item, path, issues) {
87
+ if (!isRecord$2(item)) {
88
+ issue(issues, path, "Expected a membership object");
89
+ return;
90
+ }
91
+ const roles = own(item, "roles");
92
+ if (!isStrings(roles) || roles.length === 0) issue(issues, [...path, "roles"], "Expected a non-empty array of strings");
93
+ for (const key of [
94
+ "scope",
95
+ "id",
96
+ "tenant",
97
+ "team",
98
+ "via",
99
+ "grantedBy",
100
+ "reason"
101
+ ]) optional(item, key, path, issues, isString, "a string");
102
+ optional(item, "within", path, issues, (value) => isRecord$2(value) && Object.values(value).every((id) => typeof id === "string"), "an object of string ids");
103
+ optional(item, "on", path, issues, (value) => isRecord$2(value) && typeof own(value, "resource") === "string" && typeof own(value, "id") === "string", "an object with string resource and id");
104
+ optional(item, "expiresAt", path, issues, (value) => typeof value === "number" && Number.isFinite(value), "a number of seconds since the epoch");
105
+ optional(item, "member", path, issues, (value) => isRecord$2(value) && typeof own(value, "group") === "string" && own(value, "group") !== "", "an object with a non-empty string group");
106
+ optional(item, "managedBy", path, issues, (value) => value === "idp", "'idp'");
107
+ optional(item, "entitlements", path, issues, isStrings, "an array of strings");
108
+ optional(item, "keep", path, issues, isStrings, "an array of strings");
109
+ optional(item, "grants", path, issues, isRecord$2, "an object");
110
+ if (!(own(item, "scope") !== void 0 && own(item, "id") !== void 0 || own(item, "tenant") !== void 0 || own(item, "team") !== void 0 || own(item, "on") !== void 0)) issue(issues, path, "Expected scope and id, tenant, team or on");
111
+ }
112
+ function checkPermDockClaims(record, tenantClaim, path, issues) {
113
+ optional(record, "user_role", path, issues, (value) => value === null || typeof value === "string" || isStrings(value), "a string, an array of strings or null");
114
+ optional(record, "roles", path, issues, isStrings, "an array of strings");
115
+ const memberships = own(record, "memberships");
116
+ if (memberships !== void 0) {
117
+ if (Array.isArray(memberships)) for (const [index, item] of memberships.entries()) checkMembership(item, [
118
+ ...path,
119
+ "memberships",
120
+ index
121
+ ], issues);
122
+ else issue(issues, [...path, "memberships"], "Expected an array");
123
+ }
124
+ optional(record, "memberships_truncated", path, issues, (value) => typeof value === "boolean", "a boolean");
125
+ optional(record, tenantClaim, path, issues, isString, "a string");
126
+ optional(record, "attrs", path, issues, isRecord$2, "an object");
127
+ optional(record, "authz_ver", path, issues, (value) => Number.isInteger(value), "an integer");
128
+ }
129
+ /** The outer level's `kind`: none, `support` with a `session_id`, or `impersonation`. */
130
+ function checkActKind(level, path, issues) {
131
+ const marked = own(level, "kind");
132
+ const kind = marked === void 0 && own(level, "session_id") !== void 0 ? "support" : marked;
133
+ optional(level, "reason", path, issues, isString, "a string");
134
+ if (kind === void 0 || kind === "impersonation") return;
135
+ if (kind !== "support") {
136
+ issue(issues, [...path, "kind"], "Expected 'support' or 'impersonation'");
137
+ return;
138
+ }
139
+ const session = own(level, "session_id");
140
+ if (typeof session !== "string" || session === "") issue(issues, [...path, "session_id"], "Expected a non-empty string");
141
+ optional(level, "read_only", path, issues, (value) => typeof value === "boolean", "a boolean");
142
+ }
143
+ function checkAct(value, issues) {
144
+ let current = value;
145
+ const path = ["act"];
146
+ while (current !== void 0) {
147
+ if (!isRecord$2(current)) {
148
+ issue(issues, path, "Expected an object");
149
+ return;
150
+ }
151
+ const sub = own(current, "sub");
152
+ if (typeof sub !== "string" || sub === "") issue(issues, [...path, "sub"], "Expected a non-empty string");
153
+ if (path.length === 1) checkActKind(current, path, issues);
154
+ current = own(current, "act");
155
+ path.push("act");
156
+ }
157
+ }
158
+ function checkClaims(value, tenantClaim) {
159
+ const issues = [];
160
+ if (!isRecord$2(value)) {
161
+ issue(issues, [], "Expected a claims object");
162
+ return issues;
163
+ }
164
+ checkPermDockClaims(value, tenantClaim, [], issues);
165
+ optional(value, "client_id", [], issues, isString, "a string");
166
+ optional(value, "scope", [], issues, (scope) => typeof scope === "string" || isStrings(scope), "a string or an array of strings");
167
+ const act = own(value, "act");
168
+ if (act !== void 0) checkAct(act, issues);
169
+ const meta = own(value, "app_metadata");
170
+ if (meta !== void 0) {
171
+ if (isRecord$2(meta)) checkPermDockClaims(meta, tenantClaim, ["app_metadata"], issues);
172
+ else issue(issues, ["app_metadata"], "Expected an object");
173
+ }
174
+ return issues;
175
+ }
176
+ function merge(base, app) {
177
+ const issues = [...base.issues ?? [], ...app.issues ?? []];
178
+ if (issues.length > 0) return { issues };
179
+ const baseValue = base.value;
180
+ const appValue = app.value;
181
+ if (!isRecord$2(appValue)) return { issues: [{ message: "Expected the extension to output an object" }] };
182
+ return { value: isRecord$2(baseValue) ? {
183
+ ...baseValue,
184
+ ...appValue
185
+ } : appValue };
186
+ }
187
+ function schemaOf(validate) {
188
+ const schema = {
189
+ "~standard": Object.freeze({
190
+ version: 1,
191
+ vendor: "permdock",
192
+ validate
193
+ }),
194
+ extend(app) {
195
+ if (!isRecord$2(app) && typeof app !== "function") throw new TypeError("PermDock: supabaseClaims().extend needs a Standard Schema");
196
+ const props = Reflect.get(app, "~standard");
197
+ if (!isRecord$2(props) || typeof props["validate"] !== "function") throw new TypeError("PermDock: supabaseClaims().extend needs a Standard Schema");
198
+ const run = app["~standard"].validate.bind(app["~standard"]);
199
+ return schemaOf((value) => {
200
+ const base = validate(value);
201
+ const extra = run(value);
202
+ if (base instanceof Promise || extra instanceof Promise) return Promise.all([base, extra]).then(([b, e]) => merge(b, e));
203
+ return merge(base, extra);
204
+ });
205
+ }
206
+ };
207
+ return Object.freeze(schema);
208
+ }
209
+ /**
210
+ * A Standard Schema v1 for PermDock's Supabase claims. Unknown claims pass
211
+ * through unchanged; a known claim with the wrong shape is an issue, so the
212
+ * whole token is rejected as `schemas/supabase-claims-v1.json` rejects it.
213
+ * Needs no validation library.
214
+ *
215
+ * ```ts
216
+ * sb.claims(supabaseClaims().extend(z.object({ datetime_preferences: Prefs })))
217
+ * ```
218
+ */
219
+ function supabaseClaims(options = {}) {
220
+ const tenantClaim = options.tenantClaim ?? "tenant_id";
221
+ if (typeof tenantClaim !== "string" || tenantClaim === "" || KNOWN.has(tenantClaim)) throw new TypeError(`PermDock: supabaseClaims tenantClaim must be a claim name other than ${[...KNOWN].join(", ")}`);
222
+ return schemaOf((value) => {
223
+ const issues = checkClaims(value, tenantClaim);
224
+ return issues.length > 0 ? { issues } : { value };
225
+ });
226
+ }
227
+ //#endregion
228
+ //#region src/supabase/postgrest.ts
229
+ function callRpc(client, schema, fn, args) {
230
+ return client.schema(schema).rpc(fn, args);
231
+ }
232
+ function isRecord$1(value) {
233
+ return value !== null && typeof value === "object" && !Array.isArray(value);
234
+ }
235
+ function strings(value) {
236
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
237
+ }
238
+ function grantOf(value) {
239
+ if (!isRecord$1(value) || typeof value["permission"] !== "string") return;
240
+ return compact({
241
+ permission: value["permission"],
242
+ effect: value["effect"] === "deny" ? "deny" : "allow",
243
+ level: typeof value["level"] === "string" ? value["level"] : void 0
244
+ });
245
+ }
246
+ function customRoleOf(value) {
247
+ if (!isRecord$1(value) || typeof value["name"] !== "string") return;
248
+ const grants = Array.isArray(value["grants"]) ? value["grants"].flatMap((item) => {
249
+ const grant = grantOf(item);
250
+ return grant === void 0 ? [] : [grant];
251
+ }) : [];
252
+ const includes = strings(value["includes"]);
253
+ const body = {
254
+ name: value["name"],
255
+ grants,
256
+ ...includes.length === 0 ? {} : { includes }
257
+ };
258
+ if (value["scope"] === "global") return value["tenant"] === void 0 ? {
259
+ ...body,
260
+ scope: "global"
261
+ } : void 0;
262
+ if (typeof value["tenant"] !== "string") return;
263
+ return compact({
264
+ ...body,
265
+ tenant: value["tenant"],
266
+ scope: typeof value["scope"] === "string" ? value["scope"] : void 0,
267
+ id: typeof value["id"] === "string" ? value["id"] : void 0
268
+ });
269
+ }
270
+ function readMemberEntries(value) {
271
+ if (!Array.isArray(value)) return [];
272
+ return value.flatMap((item) => {
273
+ if (!isRecord$1(item) || !isRecord$1(item["principal"])) return [];
274
+ const id = item["principal"]["id"];
275
+ const [membership] = readMemberships([item["membership"]]).memberships;
276
+ return typeof id === "string" && id !== "" && membership !== void 0 ? [{
277
+ principal: { id },
278
+ membership
279
+ }] : [];
280
+ });
281
+ }
282
+ function versionOf(value) {
283
+ const version = typeof value === "string" ? Number(value) : value;
284
+ return typeof version === "number" && Number.isFinite(version) ? version : void 0;
285
+ }
286
+ /** Reads `subject_for`'s JSON; anything it cannot read is left out, and a malformed record is no record. */
287
+ function readSubjectRecord(value) {
288
+ if (!isRecord$1(value) || typeof value["id"] !== "string") return;
289
+ if (value["active"] !== true) return {
290
+ id: value["id"],
291
+ active: false,
292
+ roles: [],
293
+ memberships: [],
294
+ customRoles: []
295
+ };
296
+ const version = value["authzVersion"];
297
+ return compact({
298
+ id: value["id"],
299
+ active: true,
300
+ roles: strings(value["roles"]),
301
+ memberships: readMemberships(value["memberships"]).memberships,
302
+ customRoles: Array.isArray(value["customRoles"]) ? value["customRoles"].flatMap((item) => {
303
+ const role = customRoleOf(item);
304
+ return role === void 0 ? [] : [role];
305
+ }) : [],
306
+ authzVersion: typeof version === "number" && Number.isFinite(version) ? version : void 0
307
+ });
308
+ }
309
+ /**
310
+ * Membership, custom-role and subject sources over the `subject_for(p_user)`,
311
+ * `authz_version_for(p_user)` and `members_of(p_scope, p_id)` functions
312
+ * `permdock supabase hook generate` writes, for a backend that reaches
313
+ * Postgres only through PostgREST. `memberships.version` reads only the
314
+ * version, so `claimsFirst` with `onStale: 'reread'` reads the full subject
315
+ * only for a stale token; when the version function is missing it falls back
316
+ * to `subject_for`. With `policy`, a token that claims a custom role has its
317
+ * subject read in one `subject_for` call instead, and custom roles are read
318
+ * only for a subject that holds one. Each user and each instance is read once per
319
+ * `postgrestSources` call, so create it per request or per job.
320
+ */
321
+ function postgrestSources(client, options = {}) {
322
+ const schema = options.schema ?? "permdock";
323
+ const fn = options.fn ?? "subject_for";
324
+ const membersFn = options.membersFn ?? "members_of";
325
+ const versionFn = options.versionFn ?? "authz_version_for";
326
+ const declared = options.policy === void 0 ? void 0 : options.policy.index.declaredRoles;
327
+ const onlyDeclared = (held) => declared !== void 0 && held?.every((name) => declared.has(name)) === true;
328
+ const loaded = /* @__PURE__ */ new Map();
329
+ const versions = /* @__PURE__ */ new Map();
330
+ const listed = /* @__PURE__ */ new Map();
331
+ const list = (query) => {
332
+ const key = JSON.stringify([query.scope, query.id]);
333
+ const cached = listed.get(key);
334
+ if (cached !== void 0) return cached.then((entries) => [...entries]);
335
+ const pending = Promise.resolve(callRpc(client, schema, membersFn, {
336
+ p_scope: query.scope,
337
+ p_id: query.id
338
+ })).then((result) => {
339
+ if (result.error !== null) throw new Error(`PermDock: ${schema}.${membersFn} failed: ${result.error.message}`);
340
+ return readMemberEntries(result.data);
341
+ });
342
+ listed.set(key, pending);
343
+ return pending.then((entries) => [...entries]);
344
+ };
345
+ const record = (userId) => {
346
+ const cached = loaded.get(userId);
347
+ if (cached !== void 0) return cached;
348
+ const pending = Promise.resolve(callRpc(client, schema, fn, { p_user: userId })).then((result) => {
349
+ if (result.error !== null) throw new Error(`PermDock: ${schema}.${fn} failed: ${result.error.message}`);
350
+ return readSubjectRecord(result.data);
351
+ });
352
+ loaded.set(userId, pending);
353
+ return pending;
354
+ };
355
+ const version = (userId) => {
356
+ const read = loaded.get(userId);
357
+ if (read !== void 0) return read.then((found) => found?.authzVersion);
358
+ const cached = versions.get(userId);
359
+ if (cached !== void 0) return cached;
360
+ const pending = Promise.resolve(callRpc(client, schema, versionFn, { p_user: userId })).then((result) => result.error === null ? versionOf(result.data) : record(userId).then((found) => found?.authzVersion));
361
+ versions.set(userId, pending);
362
+ return pending;
363
+ };
364
+ const live = async (userId) => {
365
+ const found = await record(userId);
366
+ return found?.active === true ? found : void 0;
367
+ };
368
+ return {
369
+ memberships: {
370
+ async membershipsFor(principal) {
371
+ return [...(await live(principal.id))?.memberships ?? []];
372
+ },
373
+ version(principal) {
374
+ return declared !== void 0 && !onlyDeclared([...principal.roles ?? [], ...(principal.memberships ?? []).flatMap((membership) => membership.roles)]) ? record(principal.id).then((found) => found?.authzVersion) : version(principal.id);
375
+ },
376
+ list
377
+ },
378
+ customRoles(principal) {
379
+ return {
380
+ async rolesFor(tenant, context) {
381
+ if (onlyDeclared(context?.held)) return [];
382
+ return ((await live(principal.id))?.customRoles ?? []).filter((role) => role.tenant === tenant);
383
+ },
384
+ async globalRoles(context) {
385
+ if (onlyDeclared(context?.held)) return [];
386
+ return ((await live(principal.id))?.customRoles ?? []).filter((role) => role.scope === "global");
387
+ }
388
+ };
389
+ },
390
+ async subject(userId) {
391
+ const found = await live(userId);
392
+ if (found === void 0) return {
393
+ principal: null,
394
+ context: {}
395
+ };
396
+ return {
397
+ principal: compact({
398
+ id: found.id,
399
+ roles: found.roles,
400
+ memberships: found.memberships,
401
+ authzVersion: found.authzVersion
402
+ }),
403
+ context: {}
404
+ };
405
+ },
406
+ record
407
+ };
408
+ }
409
+ //#endregion
410
+ //#region src/supabase/approval-store.ts
411
+ function isRecord(value) {
412
+ return value !== null && typeof value === "object" && !Array.isArray(value);
413
+ }
414
+ /** A stored body as the request it is; the store wrote it from an `ApprovalRequest`. */
415
+ function requestOf(value) {
416
+ if (!isRecord(value) || value["v"] !== 1 || typeof value["token"] !== "string") return null;
417
+ return value;
418
+ }
419
+ function filterOf(filter) {
420
+ return compact({
421
+ status: filter.status,
422
+ tenant: filter.tenant,
423
+ principalId: filter.principalId,
424
+ actorId: filter.actorId,
425
+ session: filter.session
426
+ });
427
+ }
428
+ /**
429
+ * An `ApprovalStore` over the table and functions `rls.approvals` generates,
430
+ * called through supabase-js (`client.schema(schema).rpc(...)`) with a
431
+ * client that may execute them, such as a `service_role` client. Each method
432
+ * is one atomic statement in the database: a repeated ask keeps the open
433
+ * request, two racing approvers both count towards a quorum, and two racing
434
+ * resumes never both consume an approval.
435
+ */
436
+ function supabaseApprovalStore(client, options = {}) {
437
+ const schema = options.schema ?? "permdock";
438
+ const call = async (fn, args) => {
439
+ const result = await callRpc(client, schema, fn, args);
440
+ if (result.error !== null) throw new Error(`PermDock: ${schema}.${fn} failed: ${result.error.message}`);
441
+ return result.data;
442
+ };
443
+ const get = async (token) => requestOf(await call("permdock_approval_get", { p_token: token }));
444
+ return {
445
+ ...options.ttl === void 0 ? {} : { ttl: options.ttl },
446
+ async create(request) {
447
+ await call("permdock_approval_open", { p_request: request });
448
+ if (options.onOpen !== void 0) {
449
+ const stored = await get(request.token);
450
+ if (stored !== null && canonicalJson(stored) === canonicalJson(request)) await options.onOpen(stored);
451
+ }
452
+ },
453
+ get,
454
+ async resolve(token, verdict) {
455
+ const current = await get(token);
456
+ if (current === null) throw new ApprovalError("approval-not-found", "approval was not found");
457
+ const next = applyApprovalVerdict(current, verdict);
458
+ const written = requestOf(await call("permdock_approval_resolve", {
459
+ p_token: token,
460
+ p_next: next,
461
+ p_seen: current.approvals?.length ?? 0
462
+ }));
463
+ if (written === null) throw new ApprovalError("approval-not-pending", "approval is not pending");
464
+ return written;
465
+ },
466
+ async consume(token, now = /* @__PURE__ */ new Date()) {
467
+ return requestOf(await call("permdock_approval_consume", {
468
+ p_token: token,
469
+ p_now: now.toISOString()
470
+ }));
471
+ },
472
+ async list(query) {
473
+ const after = query.cursor === void 0 ? void 0 : decodeApprovalCursor(query.cursor);
474
+ if (after === null) return { items: [] };
475
+ const size = approvalPageSize(query.limit);
476
+ const data = await call("permdock_approval_list", {
477
+ p_filter: filterOf(query),
478
+ p_after_created: after?.[0] ?? null,
479
+ p_after_token: after?.[1] ?? null,
480
+ p_limit: size + 1
481
+ });
482
+ const rows = Array.isArray(data) ? data.flatMap((row) => {
483
+ const request = requestOf(row);
484
+ return request === null ? [] : [request];
485
+ }) : [];
486
+ const items = rows.slice(0, size);
487
+ const last = items.at(-1);
488
+ return rows.length > size && last !== void 0 ? {
489
+ items,
490
+ next: encodeApprovalCursor(last)
491
+ } : { items };
492
+ },
493
+ async expire(now = /* @__PURE__ */ new Date()) {
494
+ const count = await call("permdock_approval_expire", { p_now: now.toISOString() });
495
+ return typeof count === "number" ? count : 0;
496
+ },
497
+ async cancel(filter, meta) {
498
+ const count = await call("permdock_approval_cancel", {
499
+ p_filter: filterOf(filter),
500
+ p_by: meta.by,
501
+ p_note: meta.note ?? null,
502
+ p_now: (/* @__PURE__ */ new Date()).toISOString()
503
+ });
504
+ return typeof count === "number" ? count : 0;
505
+ }
506
+ };
507
+ }
508
+ //#endregion
509
+ //#region src/supabase/rls.ts
510
+ function isTable(value) {
511
+ return value !== null && typeof value === "object" && "table" in value && typeof value.table === "string";
512
+ }
513
+ function supabaseRls(options = {}) {
514
+ const raw = options.memberships;
515
+ const memberships = raw === void 0 ? void 0 : isTable(raw) ? { tenant: raw } : raw;
516
+ return compact({
517
+ dialect: "supabase",
518
+ roleClaim: options.roleClaim ?? "user_role",
519
+ tenantClaim: options.tenantClaim ?? "tenant_id",
520
+ tenantType: options.tenantType,
521
+ memberships,
522
+ suspension: options.suspension,
523
+ realtime: options.realtime,
524
+ storage: options.storage
525
+ });
526
+ }
527
+ const ident = (name) => quoteSqlIdent(name);
528
+ const table = (name) => quoteSqlTable(name);
529
+ const literal = quoteSqlLiteral;
530
+ function qualifiedTable(name) {
531
+ return table(name.includes(".") ? name : `public.${name}`);
532
+ }
533
+ /** The memberships table's role key over alias `m`, joined to its roles table when it holds a reference. */
534
+ function tenantRole(memberships) {
535
+ return roleColumn(memberships.role, memberships.table.includes(".") ? memberships.table : `public.${memberships.table}`, "m", {
536
+ label: "authorizeSql tenant.role",
537
+ indent: " "
538
+ });
539
+ }
540
+ function textArray(values) {
541
+ return values.length === 0 ? `'{}'::text[]` : `array[${values.map(literal).join(", ")}]::text[]`;
542
+ }
543
+ function activeRow(row, id, text = false) {
544
+ const parts = [`s.${ident(row.id)}${text ? "::text" : ""} = ${id}`];
545
+ if (row.disabledAt !== void 0) parts.push(`s.${ident(row.disabledAt)} is null`);
546
+ if (row.status !== void 0) {
547
+ if (row.active === void 0 || row.active.length === 0) throw new TypeError("PermDock: a suspension status column needs its active values");
548
+ parts.push(`s.${ident(row.status)}::text = any(${textArray(row.active)})`);
549
+ }
550
+ if (row.disabledAt === void 0 && row.status === void 0) throw new TypeError("PermDock: a suspension table needs disabledAt or status");
551
+ return `exists (select 1 from ${qualifiedTable(row.table)} s where ${parts.join(" and ")})`;
552
+ }
553
+ /**
554
+ * Early `return false` for a suspended user, or a tenant request for a
555
+ * suspended instance unless its scope keeps the requested permission.
556
+ */
557
+ function suspendedGuards(suspension, scope, uid) {
558
+ const lines = [];
559
+ const users = suspension?.users;
560
+ if (users !== void 0) lines.push(` if not ${activeRow(users, uid)} then
561
+ return false; -- suspended user
562
+ end if;`);
563
+ const tenant = suspension?.scopes?.[scope];
564
+ if (tenant !== void 0) {
565
+ const keep = keptKeys(tenant);
566
+ const kept = keep.length === 0 ? "" : ` and not (requested_permission::text = any(${textArray(keep)}))`;
567
+ lines.push(` if requested_tenant is not null and not ${activeRow(tenant, "requested_tenant", true)}${kept} then
568
+ return false; -- suspended ${scope}
569
+ end if;`);
570
+ }
571
+ return lines.map((line) => `\n${line}`).join("");
572
+ }
573
+ function claimEntries(where, value) {
574
+ return `array(select ${value} from jsonb_array_elements_text(cg.g) e where ${where})`;
575
+ }
576
+ /** A custom role's keys hold `requested_permission` in the first scope. */
577
+ function customHolds(q, scope, allows, denies, includes) {
578
+ return `exists (
579
+ select 1 from ${q("role_permissions")} rp
580
+ where rp.permission = requested_permission::text
581
+ and rp.scope = ${literal(scope)}
582
+ and rp.effect = 'allow'
583
+ and rp.grant_key in (select ${q("permdock_custom_keys")}(
584
+ ${allows},
585
+ ${denies},
586
+ ${includes},
587
+ ${literal(scope)}
588
+ ))
589
+ )`;
590
+ }
591
+ function disabledLine(memberships, suspension) {
592
+ if (memberships.disabledAt === void 0) return "";
593
+ const column = `m.${ident(memberships.disabledAt)}`;
594
+ const keep = keptKeys(suspension?.memberships);
595
+ return keep.length === 0 ? `\n and ${column} is null` : `\n and (${column} is null or requested_permission::text = any(${textArray(keep)}))`;
596
+ }
597
+ function claimKept(suspension) {
598
+ return keptKeys(suspension?.memberships).length === 0 ? "" : `\n and case coalesce(jsonb_typeof(m -> 'keep'), 'null') when 'null' then true when 'array' then m -> 'keep' @> jsonb_build_array(requested_permission::text) else false end`;
599
+ }
600
+ function customDatabaseBranch(q, scope, memberships, declared, levels, suspension) {
601
+ const role = tenantRole(memberships);
602
+ const match = `c.tenant_id::text = requested_tenant and c.scope = ${literal(scope)} and c.scope_id is null and c.role = ${role.sql}`;
603
+ const rows = (source, value, extra) => `array(select c.${value} from ${q(source)} c where ${match}${extra})`;
604
+ return ` or exists (
605
+ select 1
606
+ from ${qualifiedTable(memberships.table)} m${role.join}
607
+ where m.${ident(memberships.user)} = v_member_user
608
+ and m.${ident(memberships.tenant)} = v_member_tenant
609
+ and not (${role.sql} = any(${textArray(declared)}))${memberships.expiresAt === void 0 ? "" : `\n and (m.${ident(memberships.expiresAt)} is null or m.${ident(memberships.expiresAt)} > now())`}${disabledLine(memberships, suspension)}
610
+ and ${customHolds(q, scope, rows("custom_role_permissions", levels ? "permission || coalesce('@' || c.level, '')" : "permission", " and c.effect = 'allow'"), rows("custom_role_permissions", "permission", " and c.effect = 'deny'"), rows("custom_role_includes", "include_role", ""))}
611
+ )`;
612
+ }
613
+ /**
614
+ * The `role_permissions` match for one effect. An allow counts only without
615
+ * row conditions (`grant_key = permission`): `authorize()` never sees the row.
616
+ * A deny counts with or without them, so a conditional deny still denies.
617
+ */
618
+ function effectMatch(scope, effect) {
619
+ return `rp.permission = requested_permission::text
620
+ and rp.scope = ${scope}
621
+ and rp.effect = '${effect}'${effect === "allow" ? "\n and rp.grant_key = rp.permission" : ""}`;
622
+ }
623
+ function databaseBody(q, scope, memberships, custom, suspension) {
624
+ const global = (effect) => `exists (
625
+ select 1
626
+ from ${q("user_roles")} ur
627
+ join ${q("role_permissions")} rp on rp.role = ur.role::text
628
+ where ur.user_id = uid
629
+ and ${effectMatch(`'global'`, effect)}
630
+ )`;
631
+ const tenantColumn = memberships?.tenant;
632
+ let tenantBranch = ` if requested_tenant is not null then
633
+ return false; -- no memberships table configured
634
+ end if;`;
635
+ if (memberships !== void 0 && tenantColumn !== void 0) {
636
+ const role = tenantRole(memberships);
637
+ const held = (effect) => `exists (
638
+ select 1
639
+ from ${qualifiedTable(memberships.table)} m${role.join}
640
+ join ${q("role_permissions")} rp on rp.role = ${role.sql}
641
+ where m.${ident(memberships.user)} = v_member_user
642
+ and m.${ident(tenantColumn)} = v_member_tenant
643
+ and ${effectMatch(literal(scope), effect)}${memberships.expiresAt === void 0 ? "" : `\n and (m.${ident(memberships.expiresAt)} is null or m.${ident(memberships.expiresAt)} > now())`}${disabledLine(memberships, suspension)}
644
+ )`;
645
+ tenantBranch = ` if requested_tenant is not null then
646
+ begin
647
+ v_member_user := uid;
648
+ v_member_tenant := requested_tenant;
649
+ exception when invalid_text_representation or numeric_value_out_of_range then
650
+ return false; -- not an id of the memberships table
651
+ end;
652
+ return (${held("allow")}${custom === void 0 ? "" : customDatabaseBranch(q, scope, {
653
+ ...memberships,
654
+ tenant: tenantColumn
655
+ }, custom.declared, custom.levels === true, suspension)})
656
+ and not ${held("deny")}
657
+ and not ${global("deny")};
658
+ end if;`;
659
+ }
660
+ return `declare
661
+ uid uuid := (select auth.uid());${memberships !== void 0 && tenantColumn !== void 0 ? `
662
+ v_member_user ${qualifiedTable(memberships.table)}.${ident(memberships.user)}%type;
663
+ v_member_tenant ${qualifiedTable(memberships.table)}.${ident(tenantColumn)}%type;` : ""}
664
+ begin
665
+ if uid is null then
666
+ return false;
667
+ end if;${suspendedGuards(suspension, scope, "uid")}
668
+ ${tenantBranch}
669
+ return ${global("allow")}
670
+ and not ${global("deny")};
671
+ end;`;
672
+ }
673
+ /** A claim membership whose `expiresAt` (epoch seconds) has passed no longer holds its roles. */
674
+ const CLAIM_UNEXPIRED = `case jsonb_typeof(m -> 'expiresAt')
675
+ when 'number' then (m ->> 'expiresAt')::numeric > extract(epoch from now())
676
+ else true
677
+ end`;
678
+ function jwtBody(q, scope, custom, suspension) {
679
+ const memberships = `jsonb_array_elements(
680
+ case jsonb_typeof(coalesce(claims -> 'memberships', claims -> 'app_metadata' -> 'memberships'))
681
+ when 'array' then coalesce(claims -> 'memberships', claims -> 'app_metadata' -> 'memberships')
682
+ else '[]'::jsonb
683
+ end
684
+ ) m
685
+ cross join lateral jsonb_array_elements_text(
686
+ case jsonb_typeof(m -> 'roles') when 'array' then m -> 'roles' else '[]'::jsonb end
687
+ ) r(role)`;
688
+ const customBranch = custom === void 0 ? "" : ` or exists (
689
+ select 1
690
+ from ${memberships}
691
+ cross join lateral (select m -> 'grants' -> r.role as g) cg
692
+ where m ->> 'scope' = ${literal(scope)}
693
+ and m ->> 'id' = requested_tenant
694
+ and ${CLAIM_UNEXPIRED}${claimKept(suspension)}
695
+ and jsonb_typeof(cg.g) = 'array'
696
+ and not (r.role = any(${textArray(custom.declared)}))
697
+ and ${customHolds(q, scope, claimEntries("left(e, 1) not in ('-', '@')", "e"), claimEntries("left(e, 1) = '-'", "substr(e, 2)"), claimEntries("left(e, 1) = '@'", "substr(e, 2)"))}
698
+ )`;
699
+ const held = (effect) => `exists (
700
+ select 1
701
+ from ${memberships}
702
+ join ${q("role_permissions")} rp on rp.role = r.role
703
+ where m ->> 'scope' = ${literal(scope)}
704
+ and m ->> 'id' = requested_tenant
705
+ and ${CLAIM_UNEXPIRED}${claimKept(suspension)}
706
+ and ${effectMatch(literal(scope), effect)}
707
+ )`;
708
+ const global = (effect) => `exists (
709
+ select 1
710
+ from jsonb_array_elements_text(
711
+ case jsonb_typeof(role_claim)
712
+ when 'array' then role_claim
713
+ when 'string' then jsonb_build_array(role_claim)
714
+ else '[]'::jsonb
715
+ end
716
+ ) r(role)
717
+ join ${q("role_permissions")} rp on rp.role = r.role
718
+ where ${effectMatch(`'global'`, effect)}
719
+ )`;
720
+ return `declare
721
+ claims jsonb := (select auth.jwt());
722
+ role_claim jsonb;
723
+ begin
724
+ if claims is null or (select auth.uid()) is null then
725
+ return false;
726
+ end if;${suspendedGuards(suspension, scope, "(select auth.uid())")}
727
+ -- a top-level null (no role row) falls back to app_metadata, like subjectFromSupabase
728
+ role_claim := coalesce(nullif(claims -> 'user_role', 'null'::jsonb), claims -> 'app_metadata' -> 'user_role');
729
+ if requested_tenant is not null then
730
+ return (${held("allow")}${customBranch})
731
+ and not ${held("deny")}
732
+ and not ${global("deny")};
733
+ end if;
734
+ return ${global("allow")}
735
+ and not ${global("deny")};
736
+ end;`;
737
+ }
738
+ /**
739
+ * `authorize(requested_permission, requested_tenant text default null)` for Supabase's RBAC
740
+ * scaffold, over the `role_permissions (role, permission, grant_key, scope, effect)` table that
741
+ * `permdock rls generate` seeds. `database` reads `user_roles` (and the memberships table for a
742
+ * tenant) on every call; `jwt` reads the hook-injected `user_role` and `memberships` claims. A
743
+ * tenant request with no memberships source is denied, never answered from global roles. It
744
+ * answers "does a role hold this permission" without seeing a row: an allow with row conditions
745
+ * does not count, and a deny the user's tenant or global roles hold overrides every allow.
746
+ * Generated policies call the per-statement RLS helpers (`permdock_has`, `permitted_<scope>_ids`)
747
+ * instead. Only `authenticated` may execute it.
748
+ */
749
+ function authorizeSql(options = {}) {
750
+ const schema = options.schema ?? "permdock";
751
+ const q = (name) => table(`${schema}.${name}`);
752
+ const memberships = typeof options.tenant === "object" ? options.tenant : void 0;
753
+ const scope = options.scope ?? "tenant";
754
+ if (!/^[a-z][a-z0-9_]*$/u.test(scope)) throw new TypeError(`PermDock: unsafe scope name '${scope}'`);
755
+ const body = options.authorize === "jwt" ? jwtBody(q, scope, options.customRoles, options.suspension) : databaseBody(q, scope, memberships, options.customRoles, options.suspension);
756
+ const signature = `${q("authorize")}(${q("app_permission")}, text)`;
757
+ return `create or replace function ${q("authorize")}(
758
+ requested_permission ${q("app_permission")},
759
+ requested_tenant text default null
760
+ )
761
+ returns boolean
762
+ language plpgsql
763
+ stable
764
+ security definer
765
+ set search_path = ''
766
+ as $$
767
+ ${body}
768
+ $$;
769
+ revoke execute on function ${signature} from public, anon;
770
+ grant execute on function ${signature} to authenticated;
771
+ `;
772
+ }
773
+ //#endregion
774
+ export { readSubjectRecord as a, postgrestSources as i, supabaseRls as n, supabaseClaims as o, supabaseApprovalStore as r, exchangeCapability as s, authorizeSql as t };