permdock 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -35
- package/dist/{openapi-schema--ARjyfQr.js → 2020-C9VN-x8c.js} +5 -132
- package/dist/a2a/index.d.ts +8 -6
- package/dist/a2a/index.js +21 -31
- package/dist/{adapter-context-mlWsVGGg.js → adapter-context-CRhqyzCk.js} +1 -1
- package/dist/adoption-scan-B4R5fnMU.js +107 -0
- package/dist/agent/index.d.ts +3 -0
- package/dist/agent/index.js +1 -0
- package/dist/ai-sdk/index.d.ts +18 -23
- package/dist/ai-sdk/index.js +38 -21
- package/dist/app-data-MuyzVqX8.js +60 -0
- package/dist/approval-header-CJACtHkv.js +23 -0
- package/dist/approval-prompt-Dg5LeAcT.js +62 -0
- package/dist/approval-prompt-Djo_PyZc.d.ts +57 -0
- package/dist/approvals/index.d.ts +5 -3
- package/dist/approvals/index.js +8 -5
- package/dist/approvals-DJ6Yd5z1.js +995 -0
- package/dist/{approvers-DT4AVyIg.js → approvers-CqPOLD-o.js} +1 -1
- package/dist/arazzo-B6lebmiQ.js +36 -0
- package/dist/{arazzo-BAgw9ap6.js → arazzo-BWhNF-k0.js} +24 -17
- package/dist/args-IIolxxKA.js +197 -0
- package/dist/astro/index.d.ts +43 -0
- package/dist/astro/index.js +27 -0
- package/dist/audience-Dg8XsRaO.js +55 -0
- package/dist/auth-DRLEizbL.js +178 -0
- package/dist/authzen/index.d.ts +26 -6
- package/dist/authzen/index.js +228 -75
- package/dist/automation-BMZPIVUM.d.ts +114 -0
- package/dist/automation-DVjOckjV.js +157 -0
- package/dist/better-auth/index.d.ts +8 -6
- package/dist/better-auth/index.js +11 -23
- package/dist/better-supabase/index.d.ts +438 -86
- package/dist/better-supabase/index.js +1027 -184
- package/dist/bind--9EFVYWF.d.ts +38 -0
- package/dist/{bind-CuzGytRf.js → bind-CZzhx-Vj.js} +22 -10
- package/dist/body-CH5of8Ne.js +10 -0
- package/dist/body-text-QcXok_SI.js +52 -0
- package/dist/build-instance-BMnAudHn.js +52 -0
- package/dist/{canonical-json-DemiK4Vp.js → canonical-json-BC8_nMTw.js} +6 -6
- package/dist/{capability-2kYTMWDv.d.ts → capability-BSslw1QV.d.ts} +44 -25
- package/dist/{capability-4qPdO9gk.js → capability-N1K9IpqS.js} +69 -42
- package/dist/casbin-Cr7RLtA3.js +30 -0
- package/dist/casbin-Dyu2gnUD.js +234 -0
- package/dist/casl-64gpT-8x.js +24 -0
- package/dist/casl-DjEDhTCP.js +299 -0
- package/dist/catalog/index.d.ts +74 -2
- package/dist/catalog/index.js +51 -2
- package/dist/catalog-BECMPrwc.js +100 -0
- package/dist/catalog-DjhMYYCq.js +1 -0
- package/dist/catalog-R_A__RsH.js +52 -0
- package/dist/{catalog-doc-DdGm82XT.js → catalog-doc-C9wjsfNY.js} +54 -25
- package/dist/catalog-fingerprint-CCP0-yQS.js +153 -0
- package/dist/chat/index.d.ts +2 -0
- package/dist/chat/index.js +224 -0
- package/dist/chat-sdk/index.d.ts +87 -0
- package/dist/chat-sdk/index.js +197 -0
- package/dist/claims-map-7oQ3ue4X.js +13 -0
- package/dist/claims-map-Ca-QX8C6.d.ts +32 -0
- package/dist/claude-agent/index.d.ts +7 -14
- package/dist/claude-agent/index.js +26 -30
- package/dist/clerk/index.d.ts +9 -8
- package/dist/clerk/index.js +15 -45
- package/dist/cli/bin.js +36 -17
- package/dist/cli/index.d.ts +20 -6
- package/dist/cli/index.js +164 -4
- package/dist/cloud/index.d.ts +102 -63
- package/dist/cloud/index.js +240 -347
- package/dist/cloud-CIoHNevm.js +87 -0
- package/dist/codemod-Bmj9dWMH.js +37 -0
- package/dist/codemod-Cxvvc70T.js +2084 -0
- package/dist/{collect-DSTE3ml9.js → collect-CrZJJnEF.js} +79 -17
- package/dist/collect-DloHbDVa.js +55 -0
- package/dist/collect-FlXJlhus.js +1 -0
- package/dist/command-L8EstFNm.js +6 -0
- package/dist/{compact-y6vfu2_f.js → compact-CAf557ZB.js} +11 -1
- package/dist/compile/index.d.ts +1 -1
- package/dist/compile/index.js +1 -2
- package/dist/{compile-Ba4blROR.js → compile-CMqMx_IN.js} +13 -9
- package/dist/{compile-BkRyBDBw.d.ts → compile-Ckm2HqBZ.d.ts} +5 -3
- package/dist/completion-BqhRBJD8.js +143 -0
- package/dist/config-B46rm9sc.js +26 -0
- package/dist/{config-BJiM-xAm.js → config-bs_kZeYq.js} +15 -10
- package/dist/{config-report-CdCgbRnV.js → config-report-DUgj62Ai.js} +17 -8
- package/dist/{connection-CU8MQxRi.js → connection-DjhIJSs2.js} +8 -6
- package/dist/constants-DcJABbLW.js +20 -0
- package/dist/{context-Bcy85rGi.js → context-bbFHR-0D.js} +16 -5
- package/dist/convex/index.d.ts +11 -10
- package/dist/convex/index.js +18 -18
- package/dist/create-0VZ7Pdiu.d.ts +107 -0
- package/dist/create-BDS7wc3y.d.ts +138 -0
- package/dist/create-BFfm17UA.d.ts +110 -0
- package/dist/create-BWRSXgOF.js +403 -0
- package/dist/create-C3b4B5eF.js +130 -0
- package/dist/create-DnrmLnLW.js +323 -0
- package/dist/{credential-8P81ff4u.js → credential-DM2um3ha.js} +153 -32
- package/dist/credentials-DPyHmYz5.js +74 -0
- package/dist/deciding-columns-Sq12mEA6.js +123 -0
- package/dist/delegation-DaXVdDhM.js +286 -0
- package/dist/delegation-input-FTxMK3ji.js +19 -0
- package/dist/delivery/index.d.ts +5 -0
- package/dist/delivery/index.js +5 -0
- package/dist/delivery-DCAgNyCn.js +499 -0
- package/dist/delivery-event-xX4obB8F.js +40 -0
- package/dist/denied-text-D8jDHgsl.d.ts +19 -0
- package/dist/{describe-DMYiCjJV.d.ts → describe-BZUsixiL.d.ts} +11 -2
- package/dist/{describe-B_gVZEVi.js → describe-BybMRY7A.js} +10 -5
- package/dist/device-BARQ8eYG.js +193 -0
- package/dist/diff-CxZo9pTq.js +37 -0
- package/dist/{diff-2VaeChzX.js → diff-DCePiN6M.js} +61 -55
- package/dist/doctor-CxiwSfqH.js +44 -0
- package/dist/doctor-DFjUxf2f.js +2361 -0
- package/dist/drizzle/index.d.ts +5 -3
- package/dist/drizzle/index.js +6 -14
- package/dist/elysia/index.d.ts +16 -13
- package/dist/elysia/index.js +14 -13
- package/dist/env-DJZrPjRi.js +8 -0
- package/dist/errors-BDUwg2qC.js +27 -0
- package/dist/{errors-Cz5zn0mW.js → errors-BKy0EfhN.js} +6 -10
- package/dist/errors-BWXBMaKK.d.ts +11 -0
- package/dist/{errors-DPdqI50v.d.ts → errors-C2PXIv4c.d.ts} +2 -2
- package/dist/errors-CWTY6uih.js +143 -0
- package/dist/evaluate-_R8QkWaf.js +1732 -0
- package/dist/evaluations-16UqqQ6W.d.ts +32 -0
- package/dist/{evaluations-tz6D9A2G.js → evaluations-DZ2CbV5k.js} +181 -32
- package/dist/eve/index.d.ts +12 -16
- package/dist/eve/index.js +23 -29
- package/dist/event-time-CsegxXxB.js +23 -0
- package/dist/expiry-D4_YKRKD.js +52 -0
- package/dist/express/index.d.ts +13 -10
- package/dist/express/index.js +14 -12
- package/dist/fastify/index.d.ts +26 -13
- package/dist/fastify/index.js +17 -36
- package/dist/fetch-adapter-g0ypLkID.js +120 -0
- package/dist/fetch-adapter-qKJBuRqy.d.ts +31 -0
- package/dist/fields-D9tPVUUS.js +203 -0
- package/dist/files-C5ZI27eV.js +124 -0
- package/dist/{files-DWUN9PzI.js → files-D_JIzUrP.js} +12 -3
- package/dist/{fixtures-ht28Cplr.js → fixtures-B7XrLeY7.js} +12 -12
- package/dist/flags-C3DktBDY.js +86 -0
- package/dist/{from-snapshot-CTLijvdm.js → from-snapshot-D2lZt4Dd.js} +78 -65
- package/dist/{generate-CH7ThJ14.js → generate-CNU1pIOo.js} +1 -3
- package/dist/grantee-Cn1HyUsI.js +254 -0
- package/dist/grantee-conditions-CcXf8hMM.js +271 -0
- package/dist/grantee-match-DHArqY82.js +142 -0
- package/dist/h3/index.d.ts +37 -0
- package/dist/h3/index.js +35 -0
- package/dist/headers-BIjfMaoC.js +8 -0
- package/dist/headers-CIbDTUTK.d.ts +7 -0
- package/dist/{helpers-H_KARDVC.js → helpers-DTcZOzhF.js} +47 -39
- package/dist/hono/index.d.ts +12 -9
- package/dist/hono/index.js +14 -13
- package/dist/{hosted-DJQw46hn.js → hosted-BZIAYvon.js} +64 -15
- package/dist/hosted-DBDX0M-P.d.ts +78 -0
- package/dist/{http-SO7qGttf.js → http-BBoKefeS.js} +35 -2
- package/dist/{http-DPuDaChb.d.ts → http-B_LphE5Y.d.ts} +1 -2
- package/dist/http-DXJ37RDA.js +46 -0
- package/dist/{http-BIhmLfCc.js → http-ujKm0JXE.js} +3 -26
- package/dist/{openapi-import-cmwetbzw.js → import-CNV7I2Dw.js} +30 -26
- package/dist/{rls-import-4lLW_pbb.js → import-DMmOG27O.js} +271 -128
- package/dist/index-BcHcPyIo.d.ts +110 -0
- package/dist/index-CaxrMD1A.d.ts +1 -0
- package/dist/index-Chrs5fQ-.d.ts +210 -0
- package/dist/index-CxGUswIC.d.ts +431 -0
- package/dist/index-D8EovfsP.d.ts +91 -0
- package/dist/index-DOVf7RJv.d.ts +900 -0
- package/dist/{index-C8z4RjQV.d.ts → index-SGdIQyzc.d.ts} +34 -16
- package/dist/index.d.ts +20 -14
- package/dist/index.js +33 -23
- package/dist/init-DeMYsnYJ.js +96 -0
- package/dist/init-GG6Jh8HD.js +95 -0
- package/dist/{instance-options-jARdwfPN.d.ts → instance-options-Omfd793B.d.ts} +2 -2
- package/dist/instance-snapshot-Cxq4ygFc.js +600 -0
- package/dist/instant-BluqDvyS.js +19 -0
- package/dist/internals-Dk8A8fX3.js +17 -0
- package/dist/{json-schema-BsAflUGW.js → json-schema-B-vAwlde.js} +46 -9
- package/dist/json-schema-CN_iLFH2.d.ts +40 -0
- package/dist/jwt/index.d.ts +18 -11
- package/dist/jwt/index.js +166 -145
- package/dist/{kernel-_RRHmnMY.js → kernel-5JatXyND.js} +120 -43
- package/dist/kysely/index.d.ts +5 -3
- package/dist/kysely/index.js +6 -14
- package/dist/{live-options-H2eG8dfJ.js → live-options-BaAFCIiu.js} +16 -3
- package/dist/load-BxEtwlBS.js +1 -0
- package/dist/{load-DhWEB9P1.js → load-DFS_ry0W.js} +31 -7
- package/dist/{load-jose-BL6LH4bL.js → load-jose-DZ4dnjri.js} +1 -4
- package/dist/login-Dfe5RlYk.js +68 -0
- package/dist/login-imhuwvn3.js +254 -0
- package/dist/manifest-C8IzW3Do.js +42 -0
- package/dist/manifest-CtKR3GXL.js +370 -0
- package/dist/manifest-json-CAGzls-C.js +9 -0
- package/dist/{manifest-uDS9y5de.d.ts → manifest-mx5ghSFM.d.ts} +9 -2
- package/dist/manifest-schema-C0kK_8V0.js +399 -0
- package/dist/{map-claims-Z4QZOiFF.js → map-claims-D3oRmTk0.js} +19 -25
- package/dist/{map-error-pgUWNNtG.js → map-error-TCAdw-eW.js} +5 -5
- package/dist/{markers-B0hYLvMD.js → markers-C6joDI3o.js} +8 -3
- package/dist/mcp/index.d.ts +36 -9
- package/dist/mcp/index.js +476 -448
- package/dist/{rls-migrate-CN4TlJTO.js → migrate-BShP7Vf4.js} +119 -20
- package/dist/nest/index.d.ts +12 -9
- package/dist/nest/index.js +20 -20
- package/dist/next/index.d.ts +40 -20
- package/dist/next/index.js +37 -39
- package/dist/next/plugin.d.ts +1 -1
- package/dist/next/plugin.js +2 -2
- package/dist/node/index.d.ts +10 -10
- package/dist/node/index.js +7 -9
- package/dist/ocsf-BFp-PZZA.js +126 -0
- package/dist/openai/index.d.ts +9 -16
- package/dist/openai/index.js +18 -25
- package/dist/openapi/index.d.ts +3 -3
- package/dist/openapi/index.js +1 -1
- package/dist/{openapi-BqRBvAng.js → openapi-B7HZ0f-r.js} +9 -12
- package/dist/{openapi-CqqEbHsL.js → openapi-DTaEmTSW.js} +23 -26
- package/dist/openapi-ahr8rHu3.js +138 -0
- package/dist/operation-findings-DgATDpvh.js +36 -0
- package/dist/operations-CxybZErY.d.ts +59 -0
- package/dist/orpc/index.d.ts +13 -10
- package/dist/orpc/index.js +23 -20
- package/dist/otel/index.d.ts +3 -2
- package/dist/otel/index.js +53 -27
- package/dist/output-Dyw8fyPF.js +88 -0
- package/dist/output-files-B3IZJksC.js +1 -0
- package/dist/output-files-DhRBBE4n.js +138 -0
- package/dist/{rls-fields-CZOMlwHl.js → ownership-BatFgAA4.js} +2393 -2349
- package/dist/{package-root-zGkxO3oN.js → package-root-Dk_ETdkp.js} +4 -3
- package/dist/{parse-BuyjxhZV.js → parse-Cxq0WxrA.js} +3 -3
- package/dist/{parse-local-manifest-BrMozJCo.js → parse-local-manifest-CVSs6J9I.js} +2 -4
- package/dist/{parse-local-manifest-JLnaBH0S.d.ts → parse-local-manifest-DgqtruPn.d.ts} +1 -1
- package/dist/parse-request-D_tUXG6M.js +26 -0
- package/dist/paths-DXSHAMIa.js +42 -0
- package/dist/pdp/index.d.ts +7 -3
- package/dist/pdp/index.js +47 -29
- package/dist/{peer-BOr_kJE1.js → peer-C8swJ2GH.js} +23 -4
- package/dist/peer-CRvC7Hg1.js +1 -0
- package/dist/permdock-B9JxkBwC.js +868 -0
- package/dist/permdock-D7eeLJER.d.ts +502 -0
- package/dist/permission-registry-Br4jSNVi.js +385 -0
- package/dist/permissions-B-wCP5ZC.js +30 -0
- package/dist/{permissions-RJrovw98.js → permissions-CZKiUfFM.js} +4 -3
- package/dist/{permissions-CDgvKG5L.d.ts → permissions-oEWfl-kl.d.ts} +1 -2
- package/dist/policies-CO1oOymN.js +295 -0
- package/dist/{policy-D2W7uv19.d.ts → policy-Czo4n3Hs.d.ts} +2 -2
- package/dist/policy-DLZJ5gAp.js +399 -0
- package/dist/policy-from-catalog-DzhEc3Y9.js +450 -0
- package/dist/policy-module-CTlwMvLI.js +89 -0
- package/dist/{policy-IB6yxpC3.d.ts → policy-types-DzwivBgR.d.ts} +939 -1057
- package/dist/postgrest-CW0IqC-U.js +566 -0
- package/dist/postgrest-NUqZT-RB.d.ts +100 -0
- package/dist/powersync-BHfuehf3.js +62 -0
- package/dist/powersync-CbzkqL_M.js +1 -0
- package/dist/{powersync-Dv1_aWF7.js → powersync-bYUDLOAO.js} +30 -32
- package/dist/preset/index.d.ts +146 -0
- package/dist/preset/index.js +1 -0
- package/dist/preset-DZdU0tne.js +1768 -0
- package/dist/principal-source-pHneukrx.js +103 -0
- package/dist/prisma/index.d.ts +6 -3
- package/dist/prisma/index.js +6 -12
- package/dist/{problem-B43ztKIm.js → problem-CTviyH5L.js} +18 -27
- package/dist/{problem-details-DX075l9V.d.ts → problem-details-DXlt-vks.d.ts} +1 -1
- package/dist/{problem-details-_AMOYz-w.js → problem-details-cn5X30gZ.js} +45 -42
- package/dist/problem-types-CJ2325SZ.js +5 -0
- package/dist/{prompt-CXYhLfsS.js → prompt-DfZLJLIn.js} +2 -2
- package/dist/protected-DEPHc2xV.d.ts +38 -0
- package/dist/{protected-utU-SLuy.js → protected-DfjY5mLA.js} +37 -8
- package/dist/{provider-CSQd7m92.js → provider-CC-u3bYP.js} +23 -10
- package/dist/{provider-DZY281jO.d.ts → provider-DMVt6yYn.d.ts} +1 -1
- package/dist/{provider-store-Cfbvo4DE.js → provider-store-B5Yrkhzr.js} +6 -3
- package/dist/pull-BgIfno6w.js +256 -0
- package/dist/pull-ByOks_Tg.js +1 -0
- package/dist/push-CmFVBOeI.js +116 -0
- package/dist/pushed-policy-BImMTTIM.d.ts +57 -0
- package/dist/pushed-policy-DobP7AHI.js +238 -0
- package/dist/react/index.d.ts +8 -8
- package/dist/react/index.js +6 -6
- package/dist/react/provider-client.d.ts +1 -1
- package/dist/react/provider-client.js +1 -2
- package/dist/react-native/index.d.ts +24 -63
- package/dist/react-native/index.js +45 -27
- package/dist/{refs-KYb4ntaz.js → refs-K6eWUJ0H.js} +1 -1
- package/dist/relation-walk-3OyDBF0S.js +186 -0
- package/dist/relations-BnCWnJGI.js +163 -0
- package/dist/renames-CGj7YwcQ.js +175 -0
- package/dist/{replay-CYyiLiwO.d.ts → replay-D7iKcLFe.d.ts} +1 -1
- package/dist/{replay-BsOyTrdV.js → replay-Du1tJjG7.js} +2 -1
- package/dist/request-context-BTRTG7OT.js +71 -0
- package/dist/request-context-C2_1lchh.d.ts +5 -0
- package/dist/request-schema-CMreVuFM.js +148 -0
- package/dist/resend/index.d.ts +126 -0
- package/dist/resend/index.js +379 -0
- package/dist/resource-relations-D_jw0BFx.js +243 -0
- package/dist/retire-CALjIQ7e.js +240 -0
- package/dist/{revocations-Cy06JNx2.d.ts → revocations-R07lFBnX.d.ts} +2 -0
- package/dist/rls-BA8an5Ap.js +336 -0
- package/dist/{rls-DiinlRML.js → rls-j6t43t7n.js} +1066 -1068
- package/dist/role-builder-CXbis5-X.js +623 -0
- package/dist/{rpc-DL9Sqvb1.js → rpc-aPyNi6tA.js} +18 -8
- package/dist/run-CNsApDpd.js +1 -0
- package/dist/run-DOfMtc9V.js +723 -0
- package/dist/run-ticket-BjhTOi6u.d.ts +124 -0
- package/dist/run-ticket-sxFI6LAk.js +337 -0
- package/dist/{runtime-CB8gQFvS.js → runtime-D3VTAiCA.js} +1 -1
- package/dist/{saas-DhpJjnBA.js → saas-FO-Q8oj0.js} +18 -15
- package/dist/scan-BOI5tJ-6.js +29 -0
- package/dist/{schema-D3MnDMxa.js → schema-B4v0Qrac.js} +106 -10
- package/dist/schema-yLuBWWsh.js +130 -0
- package/dist/scim/index.d.ts +2 -2
- package/dist/scim/index.js +1 -2
- package/dist/{scim-BFGBBAqR.js → scim-ClHtpyV3.js} +213 -87
- package/dist/{scopes-CVdPrWN-.js → scopes-CUPSjo9q.js} +20 -7
- package/dist/server/index.d.ts +13 -134
- package/dist/server/index.js +13 -10
- package/dist/server-DqCT4CgZ.js +453 -0
- package/dist/{create-B1d519XK.d.ts → server-types-BLd4MnBX.d.ts} +89 -70
- package/dist/{sha256-BO0swRXL.js → sha256-D9ihqmvd.js} +8 -1
- package/dist/shared-CD11nBbC.js +304 -0
- package/dist/{shared-DRu8VFVM.js → shared-DvvFzYXe.js} +8 -9
- package/dist/signals-D3NcTKUS.js +32 -0
- package/dist/sink-BEZSlMS_.js +957 -0
- package/dist/sink-Bj_lDUDq.d.ts +225 -0
- package/dist/{skills-Biq5uup3.js → skills-B6lVdD_W.js} +16 -8
- package/dist/skills-CTYwkl44.js +51 -0
- package/dist/snapshot-CXUWfsNy.js +269 -0
- package/dist/{snapshot-cache-D6jLelln.js → snapshot-cache-DZHQ0KF6.js} +11 -5
- package/dist/{snapshot-cache-B-LxUahj.d.ts → snapshot-cache-DqK9eDmM.d.ts} +7 -5
- package/dist/solid/index.d.ts +24 -25
- package/dist/solid/index.js +41 -11
- package/dist/solid-start/index.d.ts +52 -0
- package/dist/solid-start/index.js +49 -0
- package/dist/{sources-BdOYLw9j.d.ts → sources-BV9j0DeZ.d.ts} +11 -13
- package/dist/{sources-4p0-4xQm.js → sources-vs5dGOsS.js} +147 -142
- package/dist/sql-B5T1IL83.js +55 -0
- package/dist/src-DLfUWFAI.js +1851 -0
- package/dist/ssf/index.d.ts +4 -4
- package/dist/ssf/index.js +440 -106
- package/dist/standard-webhooks-6lg8v_is.d.ts +88 -0
- package/dist/standard-webhooks-DgFFD-Ga.js +228 -0
- package/dist/storage-C2bWdvJH.js +132 -0
- package/dist/{store-DMAWwAJV.js → store-4u_hxjWa.js} +42 -55
- package/dist/{store-options-ByhZq1jV.js → store-options-BqEakYRa.js} +38 -24
- package/dist/{stores-BT2pSCub.js → stores-BbsD4fAR.js} +7 -3
- package/dist/stores-ClZUgeoP.d.ts +30 -0
- package/dist/{stream-D1RXubw2.d.ts → stream-7-xmnf3U.d.ts} +2 -2
- package/dist/{stream-DDpvynSB.js → stream-CvdUS9V1.js} +10 -2
- package/dist/style-DiKWnAvn.js +1 -0
- package/dist/{style-CTm1AXDT.js → style-Uao76wO7.js} +3 -2
- package/dist/subject-BGoM5o8B.js +17 -0
- package/dist/{subject-settings-BwBmCdTR.js → subject-settings-CkEwrw3W.js} +23 -5
- package/dist/supabase/index.d.ts +23 -106
- package/dist/supabase/index.js +5 -4
- package/dist/supabase/middleware.d.ts +13 -6
- package/dist/supabase/middleware.js +16 -11
- package/dist/supabase-9pqI57pW.js +92 -0
- package/dist/{supabase-C0qESOa-.js → supabase-Dxlp8A94.js} +124 -247
- package/dist/supabase-hook-CXcGpHqq.js +1 -0
- package/dist/{supabase-hook-ntETYCQu.js → supabase-hook-DdDQhMlw.js} +1659 -819
- package/dist/{supautils-u8jXTGrt.js → supautils-B6HYL9eL.js} +2 -2
- package/dist/support-session-C6xJQYut.js +158 -0
- package/dist/svelte/Protected.svelte +11 -4
- package/dist/svelte/index.d.ts +7 -7
- package/dist/svelte/index.js +26 -12
- package/dist/svelte/runtime.d.ts +10 -3
- package/dist/svelte/runtime.js +4 -3
- package/dist/svelte/source.d.ts +7 -7
- package/dist/svelte/source.js +5 -5
- package/dist/sveltekit/index.d.ts +51 -0
- package/dist/sveltekit/index.js +27 -0
- package/dist/tanstack-start/index.d.ts +51 -0
- package/dist/tanstack-start/index.js +40 -0
- package/dist/target-Bw37TFNA.js +51 -0
- package/dist/{validity-uOp8UpYn.js → tenancy-DN2QAx9z.js} +6 -42
- package/dist/terminal/index.d.ts +11 -9
- package/dist/terminal/index.js +36 -316
- package/dist/testing/index.d.ts +391 -106
- package/dist/testing/index.js +4574 -1286
- package/dist/testing/saas/index.d.ts +9 -8
- package/dist/testing/saas/index.js +2 -2
- package/dist/testing/saas/permissions.d.ts +1 -1
- package/dist/testing/saas/permissions.js +1 -2
- package/dist/text-BMFRfBqc.js +8 -0
- package/dist/{text-diff-BdkkYSbi.js → text-diff-BQcUapOb.js} +1 -1
- package/dist/thenable-yD7SabqG.js +31 -0
- package/dist/time-DkWjdL98.js +71 -0
- package/dist/{token-CIWxJOCh.js → token-BLZcjQgt.js} +15 -6
- package/dist/tool-refusal-CMLN5xhn.js +41 -0
- package/dist/trpc/index.d.ts +12 -9
- package/dist/trpc/index.js +18 -17
- package/dist/{types-DbjRN55U.d.ts → types-5Ut_pbKy.d.ts} +141 -13
- package/dist/types-BNORmDM8.d.ts +172 -0
- package/dist/types-BjV-fqcA.d.ts +57 -0
- package/dist/{types-QCuYJh4q.d.ts → types-BqAKcRNW.d.ts} +2 -1
- package/dist/types-C8-KNsFf.d.ts +107 -0
- package/dist/{types-aqOHTHcp.d.ts → types-CCpc2Dcv.d.ts} +77 -17
- package/dist/{types-Dnemwd77.d.ts → types-CZTnPFzM.d.ts} +50 -11
- package/dist/{types-C97IskBw.d.ts → types-CdlxsYQh.d.ts} +23 -11
- package/dist/{types-DxiCGmmd.d.ts → types-ClZaWzfb.d.ts} +9 -6
- package/dist/types-DX0LaKTb.d.ts +157 -0
- package/dist/types-DiQjupG2.d.ts +332 -0
- package/dist/types-DqzsnlSA.js +67 -0
- package/dist/types-_sHy8BQf.d.ts +65 -0
- package/dist/types-fbZ7JEXr.d.ts +89 -0
- package/dist/types-tIgIeIKJ.d.ts +66 -0
- package/dist/unplugin/index.d.ts +1 -1
- package/dist/unplugin/index.js +2 -2
- package/dist/usage-CHOtwL19.js +1 -0
- package/dist/usage-DHtE8j4E.js +32 -0
- package/dist/{usage-ItS7nLcl.js → usage-T5mXq98v.js} +56 -13
- package/dist/{validation-error-Ba264OaJ.d.ts → validation-error-Bg5Tpluk.d.ts} +1 -1
- package/dist/{validation-error-CnjZ4t92.js → validation-error-ZfUHbKvf.js} +1 -1
- package/dist/validity-CZ7-y0Cj.js +79 -0
- package/dist/{verifier-BxBo6I44.js → verifier-DHuh-okk.js} +68 -21
- package/dist/verify-DbCL9bnB.js +1770 -0
- package/dist/{version-BvacVTZR.js → version-CEbQ5UeM.js} +4 -3
- package/dist/version-DXkYUISx.js +1 -0
- package/dist/{views-Soe7bGSC.js → views-DTIIO-oQ.js} +41 -2
- package/dist/{permissions-DttnqqxC.d.ts → vocabulary-D6lPdb4Z.d.ts} +120 -29
- package/dist/vue/index.d.ts +25 -21
- package/dist/vue/index.js +25 -9
- package/dist/waiter-BGUtGYoU.js +67 -0
- package/dist/waiter-CKy4MQSY.d.ts +36 -0
- package/dist/watch-Cb-NcaUw.js +1 -0
- package/dist/{watch-DVBJ9cBq.js → watch-CszoTJMq.js} +47 -8
- package/dist/webmcp/index.d.ts +2 -2
- package/dist/webmcp/index.js +13 -37
- package/dist/where-scope-Dv-WTP-w.js +306 -0
- package/dist/who-can-D0QoYbEb.js +364 -0
- package/dist/wire-delegation-Cv3W2eaM.js +36 -0
- package/dist/wire-denial-C7PaVYEM.js +27 -0
- package/dist/workflow/index.d.ts +5 -0
- package/dist/workflow/index.js +223 -0
- package/dist/workflow-sdk/index.d.ts +56 -0
- package/dist/workflow-sdk/index.js +78 -0
- package/dist/wrap-Bd1Tejur.d.ts +15 -0
- package/dist/wrap-gX61EtpB.js +27 -0
- package/package.json +140 -41
- package/schemas/access-event-v1.json +118 -0
- package/schemas/approval-request-v1.json +34 -1
- package/schemas/approval-signature-v1.json +94 -0
- package/schemas/automation-event-v1.json +113 -0
- package/schemas/capability-v1.json +84 -0
- package/schemas/catalog-event-v1.json +87 -0
- package/schemas/catalog-v1.json +129 -12
- package/schemas/cli-commands-v1.json +164 -0
- package/schemas/cli-result-v1.json +96 -0
- package/schemas/cloud-api-v1.json +5407 -0
- package/schemas/connector-v1.json +408 -0
- package/schemas/credential-event-v1.json +81 -0
- package/schemas/credential-v1.json +134 -0
- package/schemas/decision-event-v1.json +288 -0
- package/schemas/delivery-event-v1.json +117 -0
- package/schemas/directory-event-v1.json +85 -0
- package/schemas/evidence-export-v1.json +54 -0
- package/schemas/membership-event-v1.json +93 -0
- package/schemas/policy-v1.json +68 -0
- package/schemas/pushed-policy-v1.json +236 -0
- package/schemas/run-v1.json +162 -0
- package/schemas/snapshot-v1.json +138 -0
- package/schemas/supabase-claims-v1.json +4 -4
- package/schemas/supabase-manifest-v1.json +5 -1
- package/schemas/support-session-v1.json +111 -0
- package/skills/permdock/SKILL.md +16 -12
- package/skills/permdock-agents/SKILL.md +40 -23
- package/skills/permdock-agents/references/operations-automations-chat.md +221 -0
- package/skills/permdock-agents/references/runtimes.md +22 -18
- package/skills/permdock-approvals/SKILL.md +29 -13
- package/skills/permdock-approvals/references/delivery-and-waiters.md +172 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +21 -7
- package/skills/permdock-audit/SKILL.md +3 -3
- package/skills/permdock-credentials/SKILL.md +5 -5
- package/skills/permdock-credentials/references/tokens-keys-links.md +19 -10
- package/skills/permdock-data/SKILL.md +6 -6
- package/skills/permdock-data/references/queries-and-rls.md +13 -8
- package/skills/permdock-data/references/relationships.md +3 -3
- package/skills/permdock-tenancy/SKILL.md +20 -7
- package/skills/permdock-tenancy/references/contacts-and-support-sessions.md +122 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +6 -4
- package/skills/permdock-wire/SKILL.md +18 -12
- package/skills/permdock-wire/references/adapters.md +146 -50
- package/skills/permdock-wire/references/policy-options.md +15 -6
- package/skills/permdock-wire/references/presets-and-recipes.md +58 -0
- package/dist/approvals-DbYAuNkG.js +0 -224
- package/dist/arazzo-C9chp3ST.js +0 -54
- package/dist/bind-C_ob3LKa.d.ts +0 -25
- package/dist/catalog-CRW5Mx_B.js +0 -2
- package/dist/catalog-CYj-mX2O.js +0 -48
- package/dist/catalog-DWF5cYqB.js +0 -55
- package/dist/cloud-CfwZ39Rl.js +0 -50
- package/dist/cloud-DWLXO2af.js +0 -118
- package/dist/collect-CHB22Sx4.js +0 -83
- package/dist/collect-DSWOk19R.js +0 -2
- package/dist/commands-BeaGNj_o.js +0 -97
- package/dist/config-ir40y8m0.js +0 -36
- package/dist/create-CMwIl-w2.js +0 -253
- package/dist/delegation-D8pUEuce.js +0 -103
- package/dist/diff-C9RDjjhs.js +0 -48
- package/dist/doctor-Djh6JDEk.js +0 -53
- package/dist/doctor-DvH0IQff.js +0 -1194
- package/dist/doctor-collect-CjdO0wQX.js +0 -665
- package/dist/duration-xK2jGFVc.js +0 -25
- package/dist/errors-CWfoNg9N.js +0 -47
- package/dist/errors-DRnGZHzF.js +0 -30
- package/dist/evaluation-items-CfCCejQd.js +0 -40
- package/dist/expiry-BoBJ8Tvc.js +0 -7
- package/dist/fields-BMl1RxoX.js +0 -26
- package/dist/grantee-Cv5Sogfv.js +0 -602
- package/dist/headers-BZSDieNT.js +0 -6
- package/dist/headers-Cf-zWvsO.d.ts +0 -6
- package/dist/ids-DC4V4CFu.js +0 -18
- package/dist/index-0nNqHspG.d.ts +0 -6
- package/dist/index-BNc7Wer_.d.ts +0 -483
- package/dist/index-C7Tw7MaW.d.ts +0 -201
- package/dist/instance-options-Dhhcnm3y.js +0 -22
- package/dist/load-DwJUafYN.js +0 -2
- package/dist/manifest-iaoBXHKc.js +0 -783
- package/dist/map-AcxK9o_D.js +0 -147
- package/dist/may-use-DndQ5tQ-.js +0 -77
- package/dist/openapi-CQTsgKoB.js +0 -164
- package/dist/permdock-CbgvLnuo.js +0 -3479
- package/dist/permissions-DVoF6dMt.js +0 -662
- package/dist/policy-xyWg7QdR.js +0 -1122
- package/dist/powersync-CrBX8hY6.js +0 -2
- package/dist/powersync-DE19b9dT.js +0 -62
- package/dist/protected-BdnIEFkY.d.ts +0 -27
- package/dist/protected-xAr24FEC.d.ts +0 -40
- package/dist/relations-cjOL01hZ.js +0 -505
- package/dist/rls-P-BeBx0n.js +0 -238
- package/dist/rls-graph-CHwx0V8q.js +0 -435
- package/dist/rls-ownership-DcYlZ09B.js +0 -843
- package/dist/rls-shared-Cewd_-zI.js +0 -495
- package/dist/rls-verify-CMv9H3wV.js +0 -746
- package/dist/run-BWc9qqvE.js +0 -174
- package/dist/server-DlZ28Oz5.js +0 -268
- package/dist/sink-B1fZzIMf.d.ts +0 -104
- package/dist/sink-Bd9PSyfq.js +0 -141
- package/dist/skills-C5CbhhpG.js +0 -56
- package/dist/snapshot-Enxd0n-s.js +0 -975
- package/dist/sql-xdg5f4Fi.js +0 -27
- package/dist/src-BDwEZWrZ.js +0 -568
- package/dist/store-jOBYpiXC.d.ts +0 -36
- package/dist/stores-C0IMQrGI.d.ts +0 -52
- package/dist/subject-C6dLxqHU.js +0 -24
- package/dist/supabase-config-Bj3iDH2I.js +0 -472
- package/dist/supabase-hook-gd3s8bxB.js +0 -2
- package/dist/supabase-s4o9s1O1.js +0 -83
- package/dist/thenable-okc2xDsj.js +0 -10
- package/dist/types-BnIEz1sU.d.ts +0 -48
- package/dist/types-Bvc4oWT6.d.ts +0 -9
- package/dist/types-CKNSyIf_.d.ts +0 -52
- package/dist/types-Cq_H8N_X.d.ts +0 -197
- package/dist/usage-B0YuTQRO.js +0 -42
- package/dist/usage-DorAJiU_.js +0 -2
- package/dist/vocabulary-DA8a8zif.d.ts +0 -56
- package/dist/watch-BwxLTYMZ.js +0 -2
|
@@ -0,0 +1,1768 @@
|
|
|
1
|
+
//#region src/preset/catalog.ts
|
|
2
|
+
const ARCHETYPES = [
|
|
3
|
+
{
|
|
4
|
+
id: "b2b",
|
|
5
|
+
title: "B2B SaaS",
|
|
6
|
+
description: "Organisations with owner, admin, member and viewer roles, custom roles and a decision log.",
|
|
7
|
+
requires: [
|
|
8
|
+
"tenancy",
|
|
9
|
+
"custom-roles",
|
|
10
|
+
"decision-log"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: "agent",
|
|
15
|
+
title: "AI agent product",
|
|
16
|
+
description: "Tools an agent calls on a user's behalf, with human approval on the ones that change data.",
|
|
17
|
+
requires: [
|
|
18
|
+
"operations",
|
|
19
|
+
"approvals",
|
|
20
|
+
"decision-log"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "marketplace",
|
|
25
|
+
title: "Marketplace",
|
|
26
|
+
description: "Buyers and sellers who manage what they own, with refunds behind an approval.",
|
|
27
|
+
requires: ["ownership", "approvals"]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "api",
|
|
31
|
+
title: "API product",
|
|
32
|
+
description: "Projects reached through API keys, for developers and their integrations.",
|
|
33
|
+
requires: [
|
|
34
|
+
"tenancy",
|
|
35
|
+
"api-keys",
|
|
36
|
+
"decision-log"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "internal",
|
|
41
|
+
title: "Internal tool",
|
|
42
|
+
description: "Support staff, leads and admins working tickets, with approval on destructive actions.",
|
|
43
|
+
requires: ["approvals", "decision-log"]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "consumer",
|
|
47
|
+
title: "Consumer app",
|
|
48
|
+
description: "Users who own their data, with free and pro plans and a daily limit on the free plan.",
|
|
49
|
+
requires: [
|
|
50
|
+
"ownership",
|
|
51
|
+
"plans",
|
|
52
|
+
"limits"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
const FEATURES = [
|
|
57
|
+
{
|
|
58
|
+
id: "tenancy",
|
|
59
|
+
title: "Organisations",
|
|
60
|
+
description: "Roles held per organisation; every row carries an orgId and a member sees only their organisations."
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "custom-roles",
|
|
64
|
+
title: "Custom roles",
|
|
65
|
+
description: "Organisation admins compose their own roles from the permissions they hold.",
|
|
66
|
+
requires: ["tenancy"]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "approvals",
|
|
70
|
+
title: "Approvals",
|
|
71
|
+
description: "Destructive actions return approval-required until a person approves them."
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "operations",
|
|
75
|
+
title: "Operations",
|
|
76
|
+
description: "Tools an agent may call, each bound to one permission, in defineOperations."
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "decision-log",
|
|
80
|
+
title: "Decision log",
|
|
81
|
+
description: "Every decision goes to a DecisionSink."
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "limits",
|
|
85
|
+
title: "Limits",
|
|
86
|
+
description: "A grant allows a number of calls per time window."
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "plans",
|
|
90
|
+
title: "Plans",
|
|
91
|
+
description: "Free and pro plans; pro unlocks actions the free plan lacks."
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "ownership",
|
|
95
|
+
title: "Ownership",
|
|
96
|
+
description: "A user updates and deletes the rows they own."
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "api-keys",
|
|
100
|
+
title: "API keys",
|
|
101
|
+
description: "Keys created with decideCredential, never wider than the person who creates them."
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "rls",
|
|
105
|
+
title: "Row-level security",
|
|
106
|
+
description: "Postgres RLS policies generated from the same policy, with permdock rls generate."
|
|
107
|
+
}
|
|
108
|
+
];
|
|
109
|
+
const AUTH = [
|
|
110
|
+
{
|
|
111
|
+
id: "custom",
|
|
112
|
+
title: "Your own session",
|
|
113
|
+
description: "currentUser() in session.ts returns your signed-in user; until it does, every check denies."
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "supabase",
|
|
117
|
+
title: "Supabase Auth",
|
|
118
|
+
description: "subjectFromSupabase over the claims supabase.auth.getClaims() verified."
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "better-auth",
|
|
122
|
+
title: "Better Auth",
|
|
123
|
+
description: "subjectFromBetterAuth over the session auth.api.getSession() returns."
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "clerk",
|
|
127
|
+
title: "Clerk",
|
|
128
|
+
description: "subjectFromClerk over Clerk's auth object."
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "jwt",
|
|
132
|
+
title: "Any OIDC provider",
|
|
133
|
+
description: "subjectFromJwt verifies the bearer token through OpenID Connect Discovery."
|
|
134
|
+
}
|
|
135
|
+
];
|
|
136
|
+
const BACKENDS = [
|
|
137
|
+
{
|
|
138
|
+
id: "memory",
|
|
139
|
+
title: "In memory",
|
|
140
|
+
description: "Demo rows in a Map and memory sources, for the first run."
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "supabase",
|
|
144
|
+
title: "Supabase",
|
|
145
|
+
description: "Memberships read through PostgREST with postgrestSources(), from the subject_for function."
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "drizzle",
|
|
149
|
+
title: "Drizzle",
|
|
150
|
+
description: "A Drizzle schema and client; rows load by id and lists filter with toWhere()."
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "prisma",
|
|
154
|
+
title: "Prisma",
|
|
155
|
+
description: "A Prisma schema and client; rows load by id and lists filter with toWhere()."
|
|
156
|
+
}
|
|
157
|
+
];
|
|
158
|
+
const FRAMEWORKS = [
|
|
159
|
+
{
|
|
160
|
+
id: "next",
|
|
161
|
+
title: "Next.js",
|
|
162
|
+
description: "permdock/next for Server Components, actions and routes."
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "hono",
|
|
166
|
+
title: "Hono",
|
|
167
|
+
description: "permdock/hono middleware and route guards."
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "express",
|
|
171
|
+
title: "Express",
|
|
172
|
+
description: "permdock/express middleware and route guards."
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "none",
|
|
176
|
+
title: "No HTTP framework",
|
|
177
|
+
description: "A per-request instance from core getPermDock."
|
|
178
|
+
}
|
|
179
|
+
];
|
|
180
|
+
/**
|
|
181
|
+
* Agent runtimes and UI recipes. The preset code keeps one bitset per
|
|
182
|
+
* `kind`, each in this list's order, so a new entry goes after the last
|
|
183
|
+
* one of its kind.
|
|
184
|
+
*/
|
|
185
|
+
const INTEGRATIONS = [
|
|
186
|
+
{
|
|
187
|
+
id: "ai-sdk",
|
|
188
|
+
kind: "agent",
|
|
189
|
+
title: "Vercel AI SDK",
|
|
190
|
+
description: "toolApproval and needsApproval for AI SDK tools.",
|
|
191
|
+
requires: ["operations"]
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: "mcp",
|
|
195
|
+
kind: "agent",
|
|
196
|
+
title: "MCP server",
|
|
197
|
+
description: "protectServer for an MCP server's tools.",
|
|
198
|
+
requires: ["operations"]
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: "shadcn",
|
|
202
|
+
kind: "ui",
|
|
203
|
+
title: "shadcn/ui",
|
|
204
|
+
description: "@permdock recipes built from your shadcn components."
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "react-native",
|
|
208
|
+
kind: "ui",
|
|
209
|
+
title: "React Native Reusables",
|
|
210
|
+
description: "@permdock/*-native recipes built from React Native Reusables."
|
|
211
|
+
}
|
|
212
|
+
];
|
|
213
|
+
const SERVICES = [
|
|
214
|
+
{
|
|
215
|
+
id: "cloud-decision-log",
|
|
216
|
+
title: "Cloud decision log",
|
|
217
|
+
description: "PermDock Cloud as the DecisionSink, with evidence exports."
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "cloud-approvals",
|
|
221
|
+
title: "Cloud approvals inbox",
|
|
222
|
+
description: "PermDock Cloud as the ApprovalStore, with Slack and Teams delivery."
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: "cloud-snapshots",
|
|
226
|
+
title: "Cloud snapshots",
|
|
227
|
+
description: "Signed snapshots served by PermDock Cloud, through permdockCloud.snapshotsFor()."
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: "cloud-hosted-grants",
|
|
231
|
+
title: "Cloud hosted grants",
|
|
232
|
+
description: "Grants a Cloud admin adds to hostable permissions."
|
|
233
|
+
}
|
|
234
|
+
];
|
|
235
|
+
/** The choices a preset starts from, in `/create` and in `permdock init`. */
|
|
236
|
+
const PRESET_DEFAULTS = Object.freeze({
|
|
237
|
+
archetype: "b2b",
|
|
238
|
+
features: [],
|
|
239
|
+
auth: "custom",
|
|
240
|
+
backend: "memory",
|
|
241
|
+
framework: "next",
|
|
242
|
+
integrations: [],
|
|
243
|
+
services: []
|
|
244
|
+
});
|
|
245
|
+
/** The integrations of one `kind` in `preset`, in catalogue order. */
|
|
246
|
+
function integrationsOf(preset, kind) {
|
|
247
|
+
return INTEGRATIONS.filter((item) => item.kind === kind && preset.integrations.includes(item.id)).map((item) => item.id);
|
|
248
|
+
}
|
|
249
|
+
function requiredBy(choices, picked) {
|
|
250
|
+
return choices.filter((choice) => picked.includes(choice.id)).flatMap((choice) => choice.requires ?? []);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The preset `input` describes, with every feature its archetype,
|
|
254
|
+
* integrations and other features require turned on, and lists in
|
|
255
|
+
* catalogue order. Features are only ever added, so resolving twice is a
|
|
256
|
+
* no-op.
|
|
257
|
+
*/
|
|
258
|
+
function resolvePreset(input) {
|
|
259
|
+
const integrations = INTEGRATIONS.map((item) => item.id).filter((id) => (input.integrations ?? PRESET_DEFAULTS.integrations).includes(id));
|
|
260
|
+
const features = /* @__PURE__ */ new Set([
|
|
261
|
+
...input.features ?? PRESET_DEFAULTS.features,
|
|
262
|
+
...requiredBy(ARCHETYPES, [input.archetype]),
|
|
263
|
+
...requiredBy(INTEGRATIONS, integrations)
|
|
264
|
+
]);
|
|
265
|
+
for (let added = true; added;) {
|
|
266
|
+
added = false;
|
|
267
|
+
for (const feature of requiredBy(FEATURES, [...features])) if (!features.has(feature)) {
|
|
268
|
+
features.add(feature);
|
|
269
|
+
added = true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return Object.freeze({
|
|
273
|
+
archetype: input.archetype,
|
|
274
|
+
features: FEATURES.map((feature) => feature.id).filter((id) => features.has(id)),
|
|
275
|
+
auth: input.auth ?? PRESET_DEFAULTS.auth,
|
|
276
|
+
backend: input.backend ?? PRESET_DEFAULTS.backend,
|
|
277
|
+
framework: input.framework ?? PRESET_DEFAULTS.framework,
|
|
278
|
+
integrations,
|
|
279
|
+
services: SERVICES.map((service) => service.id).filter((id) => (input.services ?? PRESET_DEFAULTS.services).includes(id))
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region src/preset/codec.ts
|
|
284
|
+
/**
|
|
285
|
+
* Preset codes: `p1` followed by base64url bytes. Version 1 packs, in
|
|
286
|
+
* order, one byte each for the archetype, auth, backend and framework
|
|
287
|
+
* index, then a bitset per multi-select list (features, agent
|
|
288
|
+
* integrations, UI integrations, services), each as many bytes as the list
|
|
289
|
+
* needs. A list only grows at the end, so a code keeps its meaning as the
|
|
290
|
+
* catalogue grows; a new layout gets a new version prefix.
|
|
291
|
+
*/
|
|
292
|
+
const VERSION = "p1";
|
|
293
|
+
const AGENT_INTEGRATIONS = INTEGRATIONS.filter((item) => item.kind === "agent");
|
|
294
|
+
const UI_INTEGRATIONS = INTEGRATIONS.filter((item) => item.kind === "ui");
|
|
295
|
+
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
296
|
+
function toBase64Url(bytes) {
|
|
297
|
+
let out = "";
|
|
298
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
299
|
+
const a = bytes[index] ?? 0;
|
|
300
|
+
const b = bytes[index + 1] ?? 0;
|
|
301
|
+
const c = bytes[index + 2] ?? 0;
|
|
302
|
+
const chunk = a << 16 | b << 8 | c;
|
|
303
|
+
const chars = index + 1 >= bytes.length ? 2 : index + 2 >= bytes.length ? 3 : 4;
|
|
304
|
+
for (let char = 0; char < chars; char += 1) out += ALPHABET[chunk >> 18 - 6 * char & 63];
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
function fromBase64Url(text) {
|
|
309
|
+
const bytes = [];
|
|
310
|
+
for (let index = 0; index < text.length; index += 4) {
|
|
311
|
+
const group = text.slice(index, index + 4);
|
|
312
|
+
if (group.length === 1) return;
|
|
313
|
+
let chunk = 0;
|
|
314
|
+
for (let char = 0; char < 4; char += 1) {
|
|
315
|
+
const symbol = group[char];
|
|
316
|
+
const value = symbol === void 0 ? 0 : ALPHABET.indexOf(symbol);
|
|
317
|
+
if (value < 0) return;
|
|
318
|
+
chunk |= value << 18 - 6 * char;
|
|
319
|
+
}
|
|
320
|
+
bytes.push(chunk >> 16 & 255);
|
|
321
|
+
if (group.length > 2) bytes.push(chunk >> 8 & 255);
|
|
322
|
+
if (group.length > 3) bytes.push(chunk & 255);
|
|
323
|
+
}
|
|
324
|
+
return bytes;
|
|
325
|
+
}
|
|
326
|
+
function bitset(list, picked) {
|
|
327
|
+
const bytes = Array.from({ length: Math.ceil(list.length / 8) }, () => 0);
|
|
328
|
+
for (const [index, item] of list.entries()) if (picked.includes(item.id)) bytes[index >> 3] = (bytes[index >> 3] ?? 0) | 1 << (index & 7);
|
|
329
|
+
return bytes;
|
|
330
|
+
}
|
|
331
|
+
function readBitset(list, bytes) {
|
|
332
|
+
return list.filter((_, index) => (bytes[index >> 3] ?? 0) >> (index & 7) & 1).map((item) => item.id);
|
|
333
|
+
}
|
|
334
|
+
function indexOf(list, id) {
|
|
335
|
+
return Math.max(0, list.findIndex((item) => item.id === id));
|
|
336
|
+
}
|
|
337
|
+
/** The preset code for `preset`, such as `p1AQIDBAc`. */
|
|
338
|
+
function encodePreset(preset) {
|
|
339
|
+
const index = indexOf;
|
|
340
|
+
return VERSION + toBase64Url([
|
|
341
|
+
index(ARCHETYPES, preset.archetype),
|
|
342
|
+
index(AUTH, preset.auth),
|
|
343
|
+
index(BACKENDS, preset.backend),
|
|
344
|
+
index(FRAMEWORKS, preset.framework),
|
|
345
|
+
...bitset(FEATURES, preset.features),
|
|
346
|
+
...bitset(AGENT_INTEGRATIONS, preset.integrations),
|
|
347
|
+
...bitset(UI_INTEGRATIONS, preset.integrations),
|
|
348
|
+
...bitset(SERVICES, preset.services)
|
|
349
|
+
]);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* The preset a code stands for, resolved, or `undefined` for a code that
|
|
353
|
+
* is not a version this package reads or does not decode.
|
|
354
|
+
*/
|
|
355
|
+
function decodePreset(code) {
|
|
356
|
+
if (!code.startsWith(VERSION)) return;
|
|
357
|
+
const bytes = fromBase64Url(code.slice(2));
|
|
358
|
+
if (bytes === void 0) return;
|
|
359
|
+
const pick = (list, at) => list[bytes[at] ?? 0]?.id;
|
|
360
|
+
const archetype = pick(ARCHETYPES, 0);
|
|
361
|
+
const auth = pick(AUTH, 1);
|
|
362
|
+
const backend = pick(BACKENDS, 2);
|
|
363
|
+
const framework = pick(FRAMEWORKS, 3);
|
|
364
|
+
if (archetype === void 0 || auth === void 0 || backend === void 0 || framework === void 0) return;
|
|
365
|
+
let at = 4;
|
|
366
|
+
const take = (size) => {
|
|
367
|
+
const slice = bytes.slice(at, at + size);
|
|
368
|
+
at += size;
|
|
369
|
+
return slice;
|
|
370
|
+
};
|
|
371
|
+
const features = readBitset(FEATURES, take(Math.ceil(FEATURES.length / 8)));
|
|
372
|
+
const agents = readBitset(AGENT_INTEGRATIONS, take(Math.ceil(AGENT_INTEGRATIONS.length / 8)));
|
|
373
|
+
const ui = readBitset(UI_INTEGRATIONS, take(Math.ceil(UI_INTEGRATIONS.length / 8)));
|
|
374
|
+
const services = readBitset(SERVICES, take(Math.ceil(SERVICES.length / 8)));
|
|
375
|
+
return resolvePreset({
|
|
376
|
+
archetype,
|
|
377
|
+
auth,
|
|
378
|
+
backend,
|
|
379
|
+
framework,
|
|
380
|
+
features,
|
|
381
|
+
integrations: [...agents, ...ui],
|
|
382
|
+
services
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region src/preset/flags.ts
|
|
387
|
+
/** One `permdock init` flag per preset key, with the catalogue it takes values from. */
|
|
388
|
+
const PRESET_FLAGS = {
|
|
389
|
+
archetype: {
|
|
390
|
+
flag: "archetype",
|
|
391
|
+
choices: ARCHETYPES,
|
|
392
|
+
repeatable: false
|
|
393
|
+
},
|
|
394
|
+
features: {
|
|
395
|
+
flag: "feature",
|
|
396
|
+
choices: FEATURES,
|
|
397
|
+
repeatable: true
|
|
398
|
+
},
|
|
399
|
+
auth: {
|
|
400
|
+
flag: "auth",
|
|
401
|
+
choices: AUTH,
|
|
402
|
+
repeatable: false
|
|
403
|
+
},
|
|
404
|
+
backend: {
|
|
405
|
+
flag: "backend",
|
|
406
|
+
choices: BACKENDS,
|
|
407
|
+
repeatable: false
|
|
408
|
+
},
|
|
409
|
+
framework: {
|
|
410
|
+
flag: "framework",
|
|
411
|
+
choices: FRAMEWORKS,
|
|
412
|
+
repeatable: false
|
|
413
|
+
},
|
|
414
|
+
integrations: {
|
|
415
|
+
flag: "integration",
|
|
416
|
+
choices: INTEGRATIONS,
|
|
417
|
+
repeatable: true
|
|
418
|
+
},
|
|
419
|
+
services: {
|
|
420
|
+
flag: "service",
|
|
421
|
+
choices: SERVICES,
|
|
422
|
+
repeatable: true
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
/**
|
|
426
|
+
* The `permdock init` flags that select `preset`, leaving out defaults and
|
|
427
|
+
* the features its archetype and integrations already turn on, so
|
|
428
|
+
* `permdock init <flags>` resolves to the same preset.
|
|
429
|
+
*/
|
|
430
|
+
function presetFlags(preset) {
|
|
431
|
+
const implied = resolvePreset({
|
|
432
|
+
archetype: preset.archetype,
|
|
433
|
+
integrations: preset.integrations
|
|
434
|
+
}).features;
|
|
435
|
+
const args = [];
|
|
436
|
+
const single = (key) => {
|
|
437
|
+
if (key === "archetype" || preset[key] !== PRESET_DEFAULTS[key]) args.push(`--${PRESET_FLAGS[key].flag}`, preset[key]);
|
|
438
|
+
};
|
|
439
|
+
const many = (key) => {
|
|
440
|
+
const values = key === "features" ? preset.features.filter((feature) => !implied.includes(feature)) : preset[key];
|
|
441
|
+
for (const value of values) args.push(`--${PRESET_FLAGS[key].flag}`, value);
|
|
442
|
+
};
|
|
443
|
+
single("archetype");
|
|
444
|
+
single("framework");
|
|
445
|
+
single("auth");
|
|
446
|
+
single("backend");
|
|
447
|
+
many("features");
|
|
448
|
+
many("integrations");
|
|
449
|
+
many("services");
|
|
450
|
+
return args;
|
|
451
|
+
}
|
|
452
|
+
//#endregion
|
|
453
|
+
//#region src/preset/domain.ts
|
|
454
|
+
function all(resource, actions) {
|
|
455
|
+
return actions.map((action) => ({
|
|
456
|
+
resource,
|
|
457
|
+
action
|
|
458
|
+
}));
|
|
459
|
+
}
|
|
460
|
+
const CRUD = [
|
|
461
|
+
"read",
|
|
462
|
+
"update",
|
|
463
|
+
"delete",
|
|
464
|
+
"create",
|
|
465
|
+
"list"
|
|
466
|
+
];
|
|
467
|
+
const READ = ["read", "list"];
|
|
468
|
+
const DOMAINS = {
|
|
469
|
+
b2b: {
|
|
470
|
+
resources: [{
|
|
471
|
+
name: "project",
|
|
472
|
+
fields: {
|
|
473
|
+
name: "z.string()",
|
|
474
|
+
ownerId: "z.string()"
|
|
475
|
+
},
|
|
476
|
+
preset: "crud",
|
|
477
|
+
actions: ["archive"],
|
|
478
|
+
owner: "ownerId"
|
|
479
|
+
}, {
|
|
480
|
+
name: "invoice",
|
|
481
|
+
fields: {
|
|
482
|
+
amount: "z.number()",
|
|
483
|
+
ownerId: "z.string()"
|
|
484
|
+
},
|
|
485
|
+
preset: "readable",
|
|
486
|
+
actions: ["pay"],
|
|
487
|
+
collection: ["create"],
|
|
488
|
+
owner: "ownerId",
|
|
489
|
+
sensitive: ["pay"]
|
|
490
|
+
}],
|
|
491
|
+
roles: [
|
|
492
|
+
{
|
|
493
|
+
name: "owner",
|
|
494
|
+
top: true,
|
|
495
|
+
grants: [...all("project", [...CRUD, "archive"]), ...all("invoice", [
|
|
496
|
+
...READ,
|
|
497
|
+
"create",
|
|
498
|
+
"pay"
|
|
499
|
+
])],
|
|
500
|
+
assigns: [
|
|
501
|
+
"owner",
|
|
502
|
+
"admin",
|
|
503
|
+
"member",
|
|
504
|
+
"viewer"
|
|
505
|
+
]
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
name: "admin",
|
|
509
|
+
grants: [...all("project", [...CRUD, "archive"]), ...all("invoice", [
|
|
510
|
+
...READ,
|
|
511
|
+
"create",
|
|
512
|
+
"pay"
|
|
513
|
+
])],
|
|
514
|
+
assigns: ["member", "viewer"]
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
name: "member",
|
|
518
|
+
grants: [
|
|
519
|
+
...all("project", [
|
|
520
|
+
"read",
|
|
521
|
+
"list",
|
|
522
|
+
"create"
|
|
523
|
+
]),
|
|
524
|
+
{
|
|
525
|
+
resource: "project",
|
|
526
|
+
action: "update",
|
|
527
|
+
own: true
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
resource: "project",
|
|
531
|
+
action: "delete",
|
|
532
|
+
own: true
|
|
533
|
+
},
|
|
534
|
+
...all("invoice", READ)
|
|
535
|
+
]
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
name: "viewer",
|
|
539
|
+
grants: [...all("project", READ), ...all("invoice", READ)]
|
|
540
|
+
}
|
|
541
|
+
],
|
|
542
|
+
demoRole: "member",
|
|
543
|
+
pro: {
|
|
544
|
+
resource: "project",
|
|
545
|
+
action: "archive"
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
agent: {
|
|
549
|
+
resources: [{
|
|
550
|
+
name: "document",
|
|
551
|
+
fields: {
|
|
552
|
+
title: "z.string()",
|
|
553
|
+
ownerId: "z.string()"
|
|
554
|
+
},
|
|
555
|
+
preset: "crud",
|
|
556
|
+
owner: "ownerId"
|
|
557
|
+
}, {
|
|
558
|
+
name: "email",
|
|
559
|
+
fields: { to: "z.string()" },
|
|
560
|
+
preset: "readable",
|
|
561
|
+
collection: ["send"],
|
|
562
|
+
sensitive: ["send"]
|
|
563
|
+
}],
|
|
564
|
+
roles: [{
|
|
565
|
+
name: "member",
|
|
566
|
+
grants: [
|
|
567
|
+
...all("document", [
|
|
568
|
+
"read",
|
|
569
|
+
"list",
|
|
570
|
+
"create"
|
|
571
|
+
]),
|
|
572
|
+
{
|
|
573
|
+
resource: "document",
|
|
574
|
+
action: "update",
|
|
575
|
+
own: true
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
resource: "document",
|
|
579
|
+
action: "delete",
|
|
580
|
+
own: true
|
|
581
|
+
},
|
|
582
|
+
...all("email", [...READ, "send"])
|
|
583
|
+
]
|
|
584
|
+
}, {
|
|
585
|
+
name: "admin",
|
|
586
|
+
top: true,
|
|
587
|
+
grants: [...all("document", CRUD), ...all("email", [...READ, "send"])],
|
|
588
|
+
assigns: ["admin", "member"]
|
|
589
|
+
}],
|
|
590
|
+
demoRole: "member",
|
|
591
|
+
pro: {
|
|
592
|
+
resource: "email",
|
|
593
|
+
action: "send"
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
marketplace: {
|
|
597
|
+
resources: [{
|
|
598
|
+
name: "listing",
|
|
599
|
+
fields: {
|
|
600
|
+
title: "z.string()",
|
|
601
|
+
sellerId: "z.string()"
|
|
602
|
+
},
|
|
603
|
+
preset: "crud",
|
|
604
|
+
actions: ["publish"],
|
|
605
|
+
owner: "sellerId"
|
|
606
|
+
}, {
|
|
607
|
+
name: "order",
|
|
608
|
+
fields: {
|
|
609
|
+
listingId: "z.string()",
|
|
610
|
+
buyerId: "z.string()"
|
|
611
|
+
},
|
|
612
|
+
preset: "readable",
|
|
613
|
+
actions: ["refund"],
|
|
614
|
+
collection: ["create"],
|
|
615
|
+
owner: "buyerId",
|
|
616
|
+
sensitive: ["refund"]
|
|
617
|
+
}],
|
|
618
|
+
roles: [
|
|
619
|
+
{
|
|
620
|
+
name: "buyer",
|
|
621
|
+
grants: [
|
|
622
|
+
...all("listing", READ),
|
|
623
|
+
{
|
|
624
|
+
resource: "order",
|
|
625
|
+
action: "create"
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
resource: "order",
|
|
629
|
+
action: "list"
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
resource: "order",
|
|
633
|
+
action: "read",
|
|
634
|
+
own: true
|
|
635
|
+
}
|
|
636
|
+
]
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
name: "seller",
|
|
640
|
+
grants: [
|
|
641
|
+
...all("listing", [
|
|
642
|
+
"read",
|
|
643
|
+
"list",
|
|
644
|
+
"create"
|
|
645
|
+
]),
|
|
646
|
+
{
|
|
647
|
+
resource: "listing",
|
|
648
|
+
action: "update",
|
|
649
|
+
own: true
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
resource: "listing",
|
|
653
|
+
action: "delete",
|
|
654
|
+
own: true
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
resource: "listing",
|
|
658
|
+
action: "publish",
|
|
659
|
+
own: true
|
|
660
|
+
},
|
|
661
|
+
...all("order", READ),
|
|
662
|
+
{
|
|
663
|
+
resource: "order",
|
|
664
|
+
action: "refund"
|
|
665
|
+
}
|
|
666
|
+
]
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
name: "admin",
|
|
670
|
+
top: true,
|
|
671
|
+
grants: [...all("listing", [...CRUD, "publish"]), ...all("order", [
|
|
672
|
+
...READ,
|
|
673
|
+
"create",
|
|
674
|
+
"refund"
|
|
675
|
+
])],
|
|
676
|
+
assigns: [
|
|
677
|
+
"admin",
|
|
678
|
+
"seller",
|
|
679
|
+
"buyer"
|
|
680
|
+
]
|
|
681
|
+
}
|
|
682
|
+
],
|
|
683
|
+
demoRole: "seller",
|
|
684
|
+
pro: {
|
|
685
|
+
resource: "listing",
|
|
686
|
+
action: "publish"
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
api: {
|
|
690
|
+
resources: [{
|
|
691
|
+
name: "project",
|
|
692
|
+
fields: {
|
|
693
|
+
name: "z.string()",
|
|
694
|
+
ownerId: "z.string()"
|
|
695
|
+
},
|
|
696
|
+
preset: "crud",
|
|
697
|
+
owner: "ownerId"
|
|
698
|
+
}, {
|
|
699
|
+
name: "deployment",
|
|
700
|
+
fields: { projectId: "z.string()" },
|
|
701
|
+
preset: "readable",
|
|
702
|
+
actions: ["rollback"],
|
|
703
|
+
collection: ["create"],
|
|
704
|
+
sensitive: ["rollback"]
|
|
705
|
+
}],
|
|
706
|
+
roles: [
|
|
707
|
+
{
|
|
708
|
+
name: "owner",
|
|
709
|
+
top: true,
|
|
710
|
+
grants: [...all("project", CRUD), ...all("deployment", [
|
|
711
|
+
...READ,
|
|
712
|
+
"create",
|
|
713
|
+
"rollback"
|
|
714
|
+
])],
|
|
715
|
+
assigns: [
|
|
716
|
+
"owner",
|
|
717
|
+
"developer",
|
|
718
|
+
"viewer"
|
|
719
|
+
]
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
name: "developer",
|
|
723
|
+
grants: [...all("project", [
|
|
724
|
+
"read",
|
|
725
|
+
"list",
|
|
726
|
+
"create",
|
|
727
|
+
"update"
|
|
728
|
+
]), ...all("deployment", [
|
|
729
|
+
...READ,
|
|
730
|
+
"create",
|
|
731
|
+
"rollback"
|
|
732
|
+
])]
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
name: "viewer",
|
|
736
|
+
grants: [...all("project", READ), ...all("deployment", READ)]
|
|
737
|
+
}
|
|
738
|
+
],
|
|
739
|
+
demoRole: "developer",
|
|
740
|
+
pro: {
|
|
741
|
+
resource: "deployment",
|
|
742
|
+
action: "rollback"
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
internal: {
|
|
746
|
+
resources: [{
|
|
747
|
+
name: "ticket",
|
|
748
|
+
fields: {
|
|
749
|
+
subject: "z.string()",
|
|
750
|
+
ownerId: "z.string()"
|
|
751
|
+
},
|
|
752
|
+
preset: "crud",
|
|
753
|
+
actions: ["assign", "close"],
|
|
754
|
+
owner: "ownerId"
|
|
755
|
+
}, {
|
|
756
|
+
name: "report",
|
|
757
|
+
fields: { title: "z.string()" },
|
|
758
|
+
preset: "readable"
|
|
759
|
+
}],
|
|
760
|
+
roles: [
|
|
761
|
+
{
|
|
762
|
+
name: "agent",
|
|
763
|
+
grants: [
|
|
764
|
+
...all("ticket", [
|
|
765
|
+
"read",
|
|
766
|
+
"list",
|
|
767
|
+
"create"
|
|
768
|
+
]),
|
|
769
|
+
{
|
|
770
|
+
resource: "ticket",
|
|
771
|
+
action: "update",
|
|
772
|
+
own: true
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
resource: "ticket",
|
|
776
|
+
action: "close",
|
|
777
|
+
own: true
|
|
778
|
+
}
|
|
779
|
+
]
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
name: "lead",
|
|
783
|
+
grants: [...all("ticket", [
|
|
784
|
+
...CRUD,
|
|
785
|
+
"assign",
|
|
786
|
+
"close"
|
|
787
|
+
]), ...all("report", READ)]
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
name: "admin",
|
|
791
|
+
top: true,
|
|
792
|
+
grants: [...all("ticket", [
|
|
793
|
+
...CRUD,
|
|
794
|
+
"assign",
|
|
795
|
+
"close"
|
|
796
|
+
]), ...all("report", READ)],
|
|
797
|
+
assigns: [
|
|
798
|
+
"admin",
|
|
799
|
+
"lead",
|
|
800
|
+
"agent"
|
|
801
|
+
]
|
|
802
|
+
}
|
|
803
|
+
],
|
|
804
|
+
demoRole: "agent",
|
|
805
|
+
pro: {
|
|
806
|
+
resource: "report",
|
|
807
|
+
action: "read"
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
consumer: {
|
|
811
|
+
resources: [{
|
|
812
|
+
name: "note",
|
|
813
|
+
fields: {
|
|
814
|
+
body: "z.string()",
|
|
815
|
+
ownerId: "z.string()"
|
|
816
|
+
},
|
|
817
|
+
preset: "crud",
|
|
818
|
+
actions: ["export"],
|
|
819
|
+
owner: "ownerId"
|
|
820
|
+
}],
|
|
821
|
+
roles: [{
|
|
822
|
+
name: "user",
|
|
823
|
+
grants: [
|
|
824
|
+
{
|
|
825
|
+
resource: "note",
|
|
826
|
+
action: "create"
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
resource: "note",
|
|
830
|
+
action: "list"
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
resource: "note",
|
|
834
|
+
action: "read",
|
|
835
|
+
own: true
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
resource: "note",
|
|
839
|
+
action: "update",
|
|
840
|
+
own: true
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
resource: "note",
|
|
844
|
+
action: "delete",
|
|
845
|
+
own: true
|
|
846
|
+
}
|
|
847
|
+
]
|
|
848
|
+
}, {
|
|
849
|
+
name: "admin",
|
|
850
|
+
top: true,
|
|
851
|
+
grants: all("note", [...CRUD, "export"]),
|
|
852
|
+
assigns: ["admin", "user"]
|
|
853
|
+
}],
|
|
854
|
+
demoRole: "user",
|
|
855
|
+
pro: {
|
|
856
|
+
resource: "note",
|
|
857
|
+
action: "export"
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
//#endregion
|
|
862
|
+
//#region src/preset/generate-shared.ts
|
|
863
|
+
function pascal(name) {
|
|
864
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
865
|
+
}
|
|
866
|
+
function quote(value) {
|
|
867
|
+
return JSON.stringify(value);
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* An environment read in the generated code. Built from parts so this
|
|
871
|
+
* runtime chunk carries no `process.` token, which the WinterTC check reads
|
|
872
|
+
* as a Node global; the generated file has it whole.
|
|
873
|
+
*/
|
|
874
|
+
function env(name) {
|
|
875
|
+
return ["process", `env[${quote(name)}]`].join(".");
|
|
876
|
+
}
|
|
877
|
+
function ownedFile(path, contents) {
|
|
878
|
+
return {
|
|
879
|
+
path,
|
|
880
|
+
contents,
|
|
881
|
+
kind: "owned"
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
function fieldsOf(ctx, resource) {
|
|
885
|
+
const fields = [["id", "z.string()"], ...Object.entries(resource.fields)];
|
|
886
|
+
if (ctx.has("tenancy")) fields.push(["orgId", "z.string()"]);
|
|
887
|
+
return fields;
|
|
888
|
+
}
|
|
889
|
+
function firstInstance(ctx) {
|
|
890
|
+
const [resource] = ctx.domain.resources;
|
|
891
|
+
if (resource === void 0) throw new Error("PermDock: a preset domain declares at least one resource");
|
|
892
|
+
return {
|
|
893
|
+
resource,
|
|
894
|
+
action: "update"
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
//#endregion
|
|
898
|
+
//#region src/preset/generate-auth.ts
|
|
899
|
+
function sessionCall(ctx) {
|
|
900
|
+
return {
|
|
901
|
+
name: ctx.preset.auth === "custom" ? "currentUser" : "currentSubject",
|
|
902
|
+
takesRequest: ctx.preset.framework !== "next"
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
function headersOf(ctx) {
|
|
906
|
+
return sessionCall(ctx).takesRequest ? {
|
|
907
|
+
imports: [],
|
|
908
|
+
param: "request: Request",
|
|
909
|
+
read: "request.headers"
|
|
910
|
+
} : {
|
|
911
|
+
imports: [`import { headers } from "next/headers";`],
|
|
912
|
+
param: "",
|
|
913
|
+
read: "(await headers())"
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
const BEARER = `const authorization = requestHeaders.get("authorization") ?? "";
|
|
917
|
+
const token = authorization.startsWith("Bearer ")
|
|
918
|
+
? authorization.slice("Bearer ".length)
|
|
919
|
+
: undefined;`;
|
|
920
|
+
function moduleText(imports, body) {
|
|
921
|
+
return `${imports.join("\n")}\n\n${body}`;
|
|
922
|
+
}
|
|
923
|
+
function customSession(ctx) {
|
|
924
|
+
return `import type { User } from "./policy";
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* The signed-in user, read from your session.
|
|
928
|
+
* It returns null until you wire it, so every check fails closed.
|
|
929
|
+
*/
|
|
930
|
+
export async function currentUser(${sessionCall(ctx).takesRequest ? "_request: Request" : ""}): Promise<User | null> {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
`;
|
|
934
|
+
}
|
|
935
|
+
function supabaseSession(ctx) {
|
|
936
|
+
const h = headersOf(ctx);
|
|
937
|
+
const browser = sessionCall(ctx).takesRequest ? "" : "\n * A browser session keeps the token in cookies: read it there with\n * createServerClient from @supabase/ssr.";
|
|
938
|
+
return moduleText([
|
|
939
|
+
`import { createClient } from "@supabase/supabase-js";`,
|
|
940
|
+
...h.imports,
|
|
941
|
+
`import { subjectFromSupabase } from "permdock/supabase";`
|
|
942
|
+
], `/**
|
|
943
|
+
* The subject the Supabase access token in the Authorization header proves;
|
|
944
|
+
* anonymous without a valid one.${browser}
|
|
945
|
+
*/
|
|
946
|
+
export async function currentSubject(${h.param}) {
|
|
947
|
+
const requestHeaders = ${h.read};
|
|
948
|
+
${BEARER}
|
|
949
|
+
if (token === undefined) {
|
|
950
|
+
return subjectFromSupabase(null);
|
|
951
|
+
}
|
|
952
|
+
const supabase = createClient(
|
|
953
|
+
${env("SUPABASE_URL")} ?? "",
|
|
954
|
+
${env("SUPABASE_PUBLISHABLE_KEY")} ?? "",
|
|
955
|
+
);
|
|
956
|
+
const { data, error } = await supabase.auth.getClaims(token);
|
|
957
|
+
return subjectFromSupabase(error ? null : (data?.claims ?? null));
|
|
958
|
+
}
|
|
959
|
+
`);
|
|
960
|
+
}
|
|
961
|
+
function betterAuthSession(ctx) {
|
|
962
|
+
const h = headersOf(ctx);
|
|
963
|
+
return moduleText([
|
|
964
|
+
...h.imports,
|
|
965
|
+
`import { subjectFromBetterAuth } from "permdock/better-auth";`,
|
|
966
|
+
"",
|
|
967
|
+
`import { auth } from "./auth";`
|
|
968
|
+
], `/** The subject of the Better Auth session on the request; anonymous without one. */
|
|
969
|
+
export async function currentSubject(${h.param}) {
|
|
970
|
+
const session = await auth.api.getSession({ headers: ${h.read} });
|
|
971
|
+
return subjectFromBetterAuth(session, { auth });
|
|
972
|
+
}
|
|
973
|
+
`);
|
|
974
|
+
}
|
|
975
|
+
function betterAuthFile() {
|
|
976
|
+
return `import { betterAuth } from "better-auth";
|
|
977
|
+
import { organization } from "better-auth/plugins";
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Your Better Auth instance: add the database adapter and sign-in methods here.
|
|
981
|
+
* subjectFromBetterAuth reads roles through the organization plugin.
|
|
982
|
+
*/
|
|
983
|
+
export const auth = betterAuth({
|
|
984
|
+
plugins: [organization()],
|
|
985
|
+
});
|
|
986
|
+
`;
|
|
987
|
+
}
|
|
988
|
+
function clerkSession(ctx) {
|
|
989
|
+
if (!sessionCall(ctx).takesRequest) return `import { auth } from "@clerk/nextjs/server";
|
|
990
|
+
import { subjectFromClerk } from "permdock/clerk";
|
|
991
|
+
|
|
992
|
+
/** The subject of the Clerk session; anonymous when signed out. */
|
|
993
|
+
export async function currentSubject() {
|
|
994
|
+
return subjectFromClerk(await auth());
|
|
995
|
+
}
|
|
996
|
+
`;
|
|
997
|
+
return `import { createClerkClient } from "@clerk/backend";
|
|
998
|
+
import { subjectFromClerk } from "permdock/clerk";
|
|
999
|
+
|
|
1000
|
+
const clerk = createClerkClient({
|
|
1001
|
+
secretKey: ${env("CLERK_SECRET_KEY")} ?? "",
|
|
1002
|
+
publishableKey: ${env("CLERK_PUBLISHABLE_KEY")} ?? "",
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
/** The subject of the Clerk session on the request; anonymous when signed out. */
|
|
1006
|
+
export async function currentSubject(request: Request) {
|
|
1007
|
+
const state = await clerk.authenticateRequest(request);
|
|
1008
|
+
return subjectFromClerk(state.toAuth());
|
|
1009
|
+
}
|
|
1010
|
+
`;
|
|
1011
|
+
}
|
|
1012
|
+
function jwtSession(ctx) {
|
|
1013
|
+
const h = headersOf(ctx);
|
|
1014
|
+
const request = sessionCall(ctx).takesRequest ? ", request" : "";
|
|
1015
|
+
return moduleText([...h.imports, `import { jwtSubjectResolver } from "permdock/jwt";`], `let resolve: ReturnType<typeof jwtSubjectResolver> | undefined;
|
|
1016
|
+
|
|
1017
|
+
/** One resolver for the process, so Discovery and the JWKS are fetched once. */
|
|
1018
|
+
function resolver() {
|
|
1019
|
+
resolve ??= jwtSubjectResolver({
|
|
1020
|
+
discovery: ${env("OIDC_ISSUER")} ?? "",
|
|
1021
|
+
audience: ${env("OIDC_AUDIENCE")} ?? "",
|
|
1022
|
+
});
|
|
1023
|
+
return resolve;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/** The subject the bearer token proves; anonymous without a valid one. */
|
|
1027
|
+
export async function currentSubject(${h.param}) {
|
|
1028
|
+
const requestHeaders = ${h.read};
|
|
1029
|
+
${BEARER}
|
|
1030
|
+
return resolver()(token${request});
|
|
1031
|
+
}
|
|
1032
|
+
`);
|
|
1033
|
+
}
|
|
1034
|
+
/** `session.ts`, plus `auth.ts` for Better Auth. */
|
|
1035
|
+
function sessionFiles(ctx) {
|
|
1036
|
+
const at = (name) => `${ctx.src}/${name}`;
|
|
1037
|
+
switch (ctx.preset.auth) {
|
|
1038
|
+
case "custom": return [ownedFile(at("session.ts"), customSession(ctx))];
|
|
1039
|
+
case "supabase": return [ownedFile(at("session.ts"), supabaseSession(ctx))];
|
|
1040
|
+
case "better-auth": return [ownedFile(at("auth.ts"), betterAuthFile()), ownedFile(at("session.ts"), betterAuthSession(ctx))];
|
|
1041
|
+
case "clerk": return [ownedFile(at("session.ts"), clerkSession(ctx))];
|
|
1042
|
+
case "jwt": return [ownedFile(at("session.ts"), jwtSession(ctx))];
|
|
1043
|
+
default: return ctx.preset.auth;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
function authDependencies(ctx) {
|
|
1047
|
+
switch (ctx.preset.auth) {
|
|
1048
|
+
case "custom": return [];
|
|
1049
|
+
case "supabase": return ["@supabase/supabase-js"];
|
|
1050
|
+
case "better-auth": return ["better-auth"];
|
|
1051
|
+
case "clerk": return [sessionCall(ctx).takesRequest ? "@clerk/backend" : "@clerk/nextjs"];
|
|
1052
|
+
case "jwt": return ["jose"];
|
|
1053
|
+
default: return ctx.preset.auth;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
function authDocs(ctx) {
|
|
1057
|
+
return ctx.preset.auth === "custom" ? ["/docs/concepts/authentication"] : [`/docs/adapters/${ctx.preset.auth}`];
|
|
1058
|
+
}
|
|
1059
|
+
//#endregion
|
|
1060
|
+
//#region src/preset/generate-backend.ts
|
|
1061
|
+
function memoryData(ctx) {
|
|
1062
|
+
const tenancy = ctx.has("tenancy");
|
|
1063
|
+
const blocks = ctx.domain.resources.map((resource) => {
|
|
1064
|
+
const type = pascal(resource.name);
|
|
1065
|
+
const row = fieldsOf(ctx, resource).map(([name, kind]) => {
|
|
1066
|
+
if (name === "id") return `id: "${resource.name}_1"`;
|
|
1067
|
+
if (name === "orgId") return `orgId: "o1"`;
|
|
1068
|
+
if (name === resource.owner) return `${name}: "u1"`;
|
|
1069
|
+
return kind.startsWith("z.number") ? `${name}: 1` : `${name}: ""`;
|
|
1070
|
+
}).join(", ");
|
|
1071
|
+
return `const ${resource.name}Rows = new Map<string, ${type}>([
|
|
1072
|
+
["${resource.name}_1", { ${row} }],
|
|
1073
|
+
]);
|
|
1074
|
+
|
|
1075
|
+
/** Replace with your database read. */
|
|
1076
|
+
export function find${type}(id: string): ${type} | null {
|
|
1077
|
+
return ${resource.name}Rows.get(id) ?? null;
|
|
1078
|
+
}`;
|
|
1079
|
+
});
|
|
1080
|
+
return `import type { ${ctx.domain.resources.map((resource) => pascal(resource.name)).join(", ")} } from "./permissions";
|
|
1081
|
+
|
|
1082
|
+
${tenancy ? "// Demo rows in organisation o1, owned by the demo user u1.\n" : "// Demo rows owned by the demo user u1.\n"}${blocks.join("\n\n")}
|
|
1083
|
+
`;
|
|
1084
|
+
}
|
|
1085
|
+
function isNumber(kind) {
|
|
1086
|
+
return kind.startsWith("z.number");
|
|
1087
|
+
}
|
|
1088
|
+
function drizzleSchema(ctx) {
|
|
1089
|
+
const numbers = ctx.domain.resources.some((resource) => fieldsOf(ctx, resource).some(([, kind]) => isNumber(kind)));
|
|
1090
|
+
const tables = ctx.domain.resources.map((resource) => {
|
|
1091
|
+
const columns = fieldsOf(ctx, resource).map(([name, kind]) => {
|
|
1092
|
+
return ` ${name}: ${isNumber(kind) ? `doublePrecision("${name}")` : `text("${name}")`}${name === "id" ? ".primaryKey()" : ".notNull()"},`;
|
|
1093
|
+
});
|
|
1094
|
+
return `export const ${resource.name} = pgTable("${resource.name}", {\n${columns.join("\n")}\n});`;
|
|
1095
|
+
});
|
|
1096
|
+
return `import { ${[
|
|
1097
|
+
...numbers ? ["doublePrecision"] : [],
|
|
1098
|
+
"pgTable",
|
|
1099
|
+
"text"
|
|
1100
|
+
].join(", ")} } from "drizzle-orm/pg-core";
|
|
1101
|
+
|
|
1102
|
+
// One table per resource, named after it, as permdock rls generate --target drizzle expects.
|
|
1103
|
+
${tables.join("\n\n")}
|
|
1104
|
+
`;
|
|
1105
|
+
}
|
|
1106
|
+
function drizzleDb() {
|
|
1107
|
+
return `import { drizzle } from "drizzle-orm/node-postgres";
|
|
1108
|
+
|
|
1109
|
+
export const db = drizzle(${env("DATABASE_URL")} ?? "");
|
|
1110
|
+
`;
|
|
1111
|
+
}
|
|
1112
|
+
function drizzleData(ctx) {
|
|
1113
|
+
const blocks = ctx.domain.resources.map((resource) => {
|
|
1114
|
+
const type = pascal(resource.name);
|
|
1115
|
+
return `/** One ${resource.name} by id, or null. */
|
|
1116
|
+
export async function find${type}(id: string): Promise<${type} | null> {
|
|
1117
|
+
const [row] = await db.select().from(${resource.name}).where(eq(${resource.name}.id, id));
|
|
1118
|
+
return row ?? null;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/** The ${resource.name}s \`permdock\` may list, filtered in the query. */
|
|
1122
|
+
export function list${type}s(permdock: PermDock) {
|
|
1123
|
+
return db
|
|
1124
|
+
.select()
|
|
1125
|
+
.from(${resource.name})
|
|
1126
|
+
.where(toWhere(permdock.where(permissions.${resource.name}.list), ${resource.name}));
|
|
1127
|
+
}`;
|
|
1128
|
+
});
|
|
1129
|
+
const names = ctx.domain.resources.map((resource) => resource.name);
|
|
1130
|
+
return `import type { PermDock } from "permdock";
|
|
1131
|
+
|
|
1132
|
+
import { eq } from "drizzle-orm";
|
|
1133
|
+
import { toWhere } from "permdock/drizzle";
|
|
1134
|
+
|
|
1135
|
+
import { db } from "./db";
|
|
1136
|
+
import { type ${names.map(pascal).join(", type ")}, permissions } from "./permissions";
|
|
1137
|
+
import { ${names.join(", ")} } from "./schema";
|
|
1138
|
+
|
|
1139
|
+
${blocks.join("\n\n")}
|
|
1140
|
+
`;
|
|
1141
|
+
}
|
|
1142
|
+
function prismaModel(ctx, resource) {
|
|
1143
|
+
const fields = fieldsOf(ctx, resource).map(([name, kind]) => ` ${name} ${isNumber(kind) ? "Float" : "String"}${name === "id" ? " @id" : ""}`);
|
|
1144
|
+
return `model ${pascal(resource.name)} {\n${fields.join("\n")}\n\n @@map("${resource.name}")\n}`;
|
|
1145
|
+
}
|
|
1146
|
+
function prismaSchema(ctx) {
|
|
1147
|
+
return `generator client {
|
|
1148
|
+
provider = "prisma-client"
|
|
1149
|
+
output = "../${ctx.src}/generated"
|
|
1150
|
+
moduleFormat = "esm"
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
datasource db {
|
|
1154
|
+
provider = "postgresql"
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
${ctx.domain.resources.map((resource) => prismaModel(ctx, resource)).join("\n\n")}
|
|
1158
|
+
`;
|
|
1159
|
+
}
|
|
1160
|
+
function prismaConfig() {
|
|
1161
|
+
return `import { defineConfig, env } from "prisma/config";
|
|
1162
|
+
|
|
1163
|
+
export default defineConfig({
|
|
1164
|
+
schema: "prisma/schema.prisma",
|
|
1165
|
+
datasource: { url: env("DATABASE_URL") },
|
|
1166
|
+
});
|
|
1167
|
+
`;
|
|
1168
|
+
}
|
|
1169
|
+
function prismaDb() {
|
|
1170
|
+
return `import { PrismaPg } from "@prisma/adapter-pg";
|
|
1171
|
+
|
|
1172
|
+
import { PrismaClient } from "./generated/client";
|
|
1173
|
+
|
|
1174
|
+
// prisma generate writes ./generated from prisma/schema.prisma.
|
|
1175
|
+
export const prisma = new PrismaClient({
|
|
1176
|
+
adapter: new PrismaPg({ connectionString: ${env("DATABASE_URL")} ?? "" }),
|
|
1177
|
+
});
|
|
1178
|
+
`;
|
|
1179
|
+
}
|
|
1180
|
+
function prismaData(ctx) {
|
|
1181
|
+
const blocks = ctx.domain.resources.map((resource) => {
|
|
1182
|
+
const type = pascal(resource.name);
|
|
1183
|
+
return `/** One ${resource.name} by id, or null. */
|
|
1184
|
+
export async function find${type}(id: string): Promise<${type} | null> {
|
|
1185
|
+
return prisma.${resource.name}.findUnique({ where: { id } });
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/** The ${resource.name}s \`permdock\` may list, filtered in the query. */
|
|
1189
|
+
export function list${type}s(permdock: PermDock) {
|
|
1190
|
+
return prisma.${resource.name}.findMany({
|
|
1191
|
+
where: toWhere<Prisma.${type}WhereInput>(permdock.where(permissions.${resource.name}.list)),
|
|
1192
|
+
});
|
|
1193
|
+
}`;
|
|
1194
|
+
});
|
|
1195
|
+
return `import type { PermDock } from "permdock";
|
|
1196
|
+
|
|
1197
|
+
import { toWhere } from "permdock/prisma";
|
|
1198
|
+
|
|
1199
|
+
import type { Prisma } from "./generated/client";
|
|
1200
|
+
|
|
1201
|
+
import { prisma } from "./db";
|
|
1202
|
+
import { type ${ctx.domain.resources.map((resource) => pascal(resource.name)).join(", type ")}, permissions } from "./permissions";
|
|
1203
|
+
|
|
1204
|
+
${blocks.join("\n\n")}
|
|
1205
|
+
`;
|
|
1206
|
+
}
|
|
1207
|
+
/** `data.ts` and the files the backend reads it through. */
|
|
1208
|
+
function backendFiles(ctx) {
|
|
1209
|
+
const at = (name) => `${ctx.src}/${name}`;
|
|
1210
|
+
switch (ctx.preset.backend) {
|
|
1211
|
+
case "memory":
|
|
1212
|
+
case "supabase": return [ownedFile(at("data.ts"), memoryData(ctx))];
|
|
1213
|
+
case "drizzle": return [
|
|
1214
|
+
ownedFile(at("schema.ts"), drizzleSchema(ctx)),
|
|
1215
|
+
ownedFile(at("db.ts"), drizzleDb()),
|
|
1216
|
+
ownedFile(at("data.ts"), drizzleData(ctx))
|
|
1217
|
+
];
|
|
1218
|
+
case "prisma": return [
|
|
1219
|
+
ownedFile("prisma/schema.prisma", prismaSchema(ctx)),
|
|
1220
|
+
ownedFile("prisma.config.ts", prismaConfig()),
|
|
1221
|
+
ownedFile(at("db.ts"), prismaDb()),
|
|
1222
|
+
ownedFile(at("data.ts"), prismaData(ctx))
|
|
1223
|
+
];
|
|
1224
|
+
default: return ctx.preset.backend;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
/** What `permdock.ts` adds for the backend: imports, statements before `defineApp`, and its options. */
|
|
1228
|
+
function backendApp(ctx) {
|
|
1229
|
+
if (ctx.preset.backend !== "supabase") return {
|
|
1230
|
+
imports: [],
|
|
1231
|
+
preamble: [],
|
|
1232
|
+
options: []
|
|
1233
|
+
};
|
|
1234
|
+
return {
|
|
1235
|
+
imports: [`import { createClient } from "@supabase/supabase-js";`, `import { postgrestSources } from "permdock/supabase";`],
|
|
1236
|
+
preamble: [
|
|
1237
|
+
`// Server-only: the secret key lets subject_for read any user's memberships.`,
|
|
1238
|
+
`const sources = postgrestSources(`,
|
|
1239
|
+
` createClient(${env("SUPABASE_URL")} ?? "", ${env("SUPABASE_SECRET_KEY")} ?? ""),`,
|
|
1240
|
+
` { policy },`,
|
|
1241
|
+
`);`
|
|
1242
|
+
],
|
|
1243
|
+
options: [" memberships: sources.memberships,"]
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
function backendDependencies(ctx) {
|
|
1247
|
+
switch (ctx.preset.backend) {
|
|
1248
|
+
case "memory": return [];
|
|
1249
|
+
case "supabase": return ["@supabase/supabase-js"];
|
|
1250
|
+
case "drizzle": return ["drizzle-orm", "pg"];
|
|
1251
|
+
case "prisma": return [
|
|
1252
|
+
"@prisma/client",
|
|
1253
|
+
"@prisma/adapter-pg",
|
|
1254
|
+
"prisma"
|
|
1255
|
+
];
|
|
1256
|
+
default: return ctx.preset.backend;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
function backendDocs(ctx) {
|
|
1260
|
+
return ctx.preset.backend === "memory" ? [] : [`/docs/adapters/${ctx.preset.backend}`];
|
|
1261
|
+
}
|
|
1262
|
+
//#endregion
|
|
1263
|
+
//#region src/preset/generate.ts
|
|
1264
|
+
const LIMIT = "{ count: 100, per: 86_400 }";
|
|
1265
|
+
function permissionsFile(ctx) {
|
|
1266
|
+
const schemas = ctx.domain.resources.map((resource) => {
|
|
1267
|
+
const fields = fieldsOf(ctx, resource).map(([name, type]) => ` ${name}: ${type},`).join("\n");
|
|
1268
|
+
return `export const ${pascal(resource.name)} = z.object({\n${fields}\n});\nexport type ${pascal(resource.name)} = z.infer<typeof ${pascal(resource.name)}>;`;
|
|
1269
|
+
});
|
|
1270
|
+
const entries = ctx.domain.resources.map((resource) => {
|
|
1271
|
+
const extra = [...resource.actions === void 0 || resource.actions.length === 0 ? [] : [`actions: [${resource.actions.map(quote).join(", ")}]`], ...resource.collection === void 0 || resource.collection.length === 0 ? [] : [`collection: [${resource.collection.map(quote).join(", ")}]`]];
|
|
1272
|
+
const options = [
|
|
1273
|
+
`id: "id"`,
|
|
1274
|
+
...ctx.has("tenancy") ? [`relations: { org: { field: "orgId", memberOf: "tenant" } }`] : [],
|
|
1275
|
+
...extra
|
|
1276
|
+
].join(", ");
|
|
1277
|
+
return ` ${resource.name}: resource(${pascal(resource.name)}, ${resource.preset}({ ${options} })),`;
|
|
1278
|
+
});
|
|
1279
|
+
return `import { definePermissions, ${[...new Set(ctx.domain.resources.map((resource) => resource.preset))].toSorted().join(", ")}, resource } from "permdock";
|
|
1280
|
+
import { z } from "zod";
|
|
1281
|
+
|
|
1282
|
+
${schemas.join("\n\n")}
|
|
1283
|
+
|
|
1284
|
+
export const permissions = definePermissions({
|
|
1285
|
+
${entries.join("\n")}
|
|
1286
|
+
});
|
|
1287
|
+
`;
|
|
1288
|
+
}
|
|
1289
|
+
/** What a role's grant becomes under the preset's features; the matrix and the code read the same answer. */
|
|
1290
|
+
function grantShape(preset, role, grant) {
|
|
1291
|
+
const has = (feature) => preset.features.includes(feature);
|
|
1292
|
+
const resource = DOMAINS[preset.archetype].resources.find((item) => item.name === grant.resource);
|
|
1293
|
+
const risky = grant.action === "delete" || (resource?.sensitive ?? []).includes(grant.action);
|
|
1294
|
+
return {
|
|
1295
|
+
owner: grant.own === true && has("ownership") ? resource?.owner : void 0,
|
|
1296
|
+
approval: has("approvals") && risky && role.top !== true,
|
|
1297
|
+
limit: has("limits") && role.name === DOMAINS[preset.archetype].demoRole && grant.action === "create"
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
function grantLine(ctx, role, grant) {
|
|
1301
|
+
const shape = grantShape(ctx.preset, role, grant);
|
|
1302
|
+
const options = [];
|
|
1303
|
+
if (shape.owner !== void 0) options.push(`where: { ${shape.owner}: principal.id }`);
|
|
1304
|
+
if (shape.approval) options.push(`approval: "human"`);
|
|
1305
|
+
if (shape.limit) options.push(`limit: ${LIMIT}`);
|
|
1306
|
+
const target = `permissions.${grant.resource}.${grant.action}`;
|
|
1307
|
+
return options.length === 0 ? `allow(${target})` : `allow(${target}, { ${options.join(", ")} })`;
|
|
1308
|
+
}
|
|
1309
|
+
function isPro(ctx, grant) {
|
|
1310
|
+
const pro = ctx.domain.pro;
|
|
1311
|
+
return ctx.has("plans") && pro !== void 0 && grant.resource === pro.resource && grant.action === pro.action;
|
|
1312
|
+
}
|
|
1313
|
+
function policyFile(ctx) {
|
|
1314
|
+
const tenancy = ctx.has("tenancy");
|
|
1315
|
+
const roles = ctx.domain.roles.map((role) => {
|
|
1316
|
+
const grants = role.grants.filter((grant) => role.top === true || !isPro(ctx, grant)).map((grant) => ` ${grantLine(ctx, role, grant)},`).join("\n");
|
|
1317
|
+
const options = [];
|
|
1318
|
+
if (tenancy) {
|
|
1319
|
+
options.push(`on: "tenant"`);
|
|
1320
|
+
if (role.top === true) options.push("min: 1");
|
|
1321
|
+
if (role.assigns !== void 0) options.push(`assigns: [${role.assigns.map(quote).join(", ")}]`);
|
|
1322
|
+
}
|
|
1323
|
+
const tail = options.length === 0 ? "" : `, { ${options.join(", ")} }`;
|
|
1324
|
+
return ` role("${role.name}", [\n${grants}\n ]${tail}),`;
|
|
1325
|
+
});
|
|
1326
|
+
const pro = ctx.domain.pro;
|
|
1327
|
+
const proGrant = ctx.has("plans") && pro !== void 0 ? ` grants: [allow(permissions.${pro.resource}.${pro.action}, { to: plan("pro") })],\n` : "";
|
|
1328
|
+
const usesPrincipal = ctx.has("ownership") && ctx.domain.roles.some((role) => role.grants.some((grant) => grant.own));
|
|
1329
|
+
const imports = [
|
|
1330
|
+
"allow",
|
|
1331
|
+
"definePolicy",
|
|
1332
|
+
...ctx.has("plans") ? ["plan"] : [],
|
|
1333
|
+
...usesPrincipal ? ["principal"] : [],
|
|
1334
|
+
"role"
|
|
1335
|
+
];
|
|
1336
|
+
const userFields = [
|
|
1337
|
+
" readonly id: string;",
|
|
1338
|
+
" readonly roles: readonly string[];",
|
|
1339
|
+
...tenancy ? [" readonly orgId: string;"] : [],
|
|
1340
|
+
...ctx.has("plans") ? [" readonly plans: readonly string[];"] : []
|
|
1341
|
+
];
|
|
1342
|
+
const principalFields = [
|
|
1343
|
+
"id: user.id",
|
|
1344
|
+
...tenancy ? ["tenant: user.orgId", `memberships: [{ scope: "tenant", id: user.orgId, roles: user.roles }]`] : ["roles: user.roles"],
|
|
1345
|
+
...ctx.has("plans") ? ["plans: user.plans"] : []
|
|
1346
|
+
];
|
|
1347
|
+
const demo = [
|
|
1348
|
+
`id: "u1"`,
|
|
1349
|
+
`roles: ["${ctx.domain.demoRole}"]`,
|
|
1350
|
+
...tenancy ? [`orgId: "o1"`] : [],
|
|
1351
|
+
...ctx.has("plans") ? [`plans: ["free"]`] : []
|
|
1352
|
+
];
|
|
1353
|
+
return `import { ${imports.join(", ")} } from "permdock";
|
|
1354
|
+
|
|
1355
|
+
import { permissions } from "./permissions";
|
|
1356
|
+
|
|
1357
|
+
/** The signed-in user as your session or auth provider returns it. */
|
|
1358
|
+
export type User = {
|
|
1359
|
+
${userFields.join("\n")}
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1362
|
+
export const policy = definePolicy(permissions, {
|
|
1363
|
+
${tenancy ? ` scopes: { tenant: { key: "orgId" } },\n` : ""} roles: [
|
|
1364
|
+
${roles.join("\n")}
|
|
1365
|
+
],
|
|
1366
|
+
${proGrant} subject: (user: User | null) =>
|
|
1367
|
+
user === null ? null : { ${principalFields.join(", ")} },
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
/** A user for tests and local runs. */
|
|
1371
|
+
export const demoUser: User = { ${demo.join(", ")} };
|
|
1372
|
+
`;
|
|
1373
|
+
}
|
|
1374
|
+
function appFile(ctx) {
|
|
1375
|
+
const valueImports = /* @__PURE__ */ new Set(["defineApp"]);
|
|
1376
|
+
const lines = [];
|
|
1377
|
+
const preamble = [];
|
|
1378
|
+
const services = ctx.preset.services;
|
|
1379
|
+
const cloudServices = services.length > 0;
|
|
1380
|
+
if (cloudServices) preamble.push(`import { cloud } from "permdock/cloud";`);
|
|
1381
|
+
if (services.includes("cloud-hosted-grants")) preamble.push(`import { cloudEndpoints } from "permdock/cloud";`, `import { joseTokenVerifier } from "permdock/jwt";`);
|
|
1382
|
+
if (ctx.has("decision-log")) {
|
|
1383
|
+
if (services.includes("cloud-decision-log")) lines.push(" sink: permdockCloud.sink,");
|
|
1384
|
+
else {
|
|
1385
|
+
valueImports.add("memoryDecisionSink");
|
|
1386
|
+
lines.push(" // A DecisionSink: memory for now; permdock/otel, PermDock Cloud or your own in production.", " sink: memoryDecisionSink(),");
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (ctx.has("custom-roles")) {
|
|
1390
|
+
valueImports.add("memoryCustomRoleSource");
|
|
1391
|
+
lines.push(" // Custom roles from your database; postgrestSources() reads them on Supabase.", " customRoles: memoryCustomRoleSource([]),");
|
|
1392
|
+
}
|
|
1393
|
+
if (services.includes("cloud-approvals")) lines.push(" store: permdockCloud.approvals,");
|
|
1394
|
+
else if (ctx.has("approvals")) lines.push(" // profile dev uses a memory ApprovalStore; set a persistent one (Postgres, PermDock Cloud) for prod.");
|
|
1395
|
+
if (ctx.has("limits")) lines.push(" // profile dev uses a memory LimitStore; set limits(store) with a shared one (Redis, Postgres) for prod.");
|
|
1396
|
+
if (services.includes("cloud-hosted-grants")) {
|
|
1397
|
+
valueImports.add("hostedPolicies");
|
|
1398
|
+
lines.push(" policies: hostedPolicies(permdockCloud.policies),");
|
|
1399
|
+
}
|
|
1400
|
+
const cloudCall = services.includes("cloud-hosted-grants") ? `cloud({\n verifier: joseTokenVerifier({ jwks: cloudEndpoints().jwks }),\n})` : "cloud()";
|
|
1401
|
+
const snapshots = services.includes("cloud-snapshots") ? `\n/** A client's signed snapshot, served by PermDock Cloud for the caller's token. */\nexport const snapshotsFor = (token: string) => permdockCloud.snapshotsFor(token);\n` : "";
|
|
1402
|
+
const cloudLine = cloudServices ? `\n// PERMDOCK_CLOUD_URL and PERMDOCK_CLOUD_KEY come from the environment.\nexport const permdockCloud = ${cloudCall};\n${snapshots}` : "";
|
|
1403
|
+
const backend = backendApp(ctx);
|
|
1404
|
+
preamble.push(...backend.imports);
|
|
1405
|
+
lines.unshift(...backend.options);
|
|
1406
|
+
const sources = backend.preamble.length === 0 ? "" : `\n${backend.preamble.join("\n")}\n`;
|
|
1407
|
+
return `import { ${[...valueImports].toSorted().join(", ")} } from "permdock";
|
|
1408
|
+
${preamble.length === 0 ? "" : `${preamble.join("\n")}\n`}
|
|
1409
|
+
import { policy } from "./policy";
|
|
1410
|
+
${cloudLine}${sources}
|
|
1411
|
+
/**
|
|
1412
|
+
* The app definition every adapter reads. Your own code: change sources,
|
|
1413
|
+
* features and services here; the rules stay in the permdock package.
|
|
1414
|
+
*/
|
|
1415
|
+
export const app = defineApp(policy, {
|
|
1416
|
+
profile: ${env("NODE_ENV")} === "production" ? "prod" : "dev",
|
|
1417
|
+
${lines.join("\n")}${lines.length === 0 ? "" : "\n"}});
|
|
1418
|
+
`;
|
|
1419
|
+
}
|
|
1420
|
+
function frameworkFiles(ctx) {
|
|
1421
|
+
const { resource, action } = firstInstance(ctx);
|
|
1422
|
+
const type = pascal(resource.name);
|
|
1423
|
+
const owned = ownedFile;
|
|
1424
|
+
const { name: current } = sessionCall(ctx);
|
|
1425
|
+
switch (ctx.preset.framework) {
|
|
1426
|
+
case "next": return [owned(`${ctx.src}/permdock/server.ts`, `import { createPermDock } from "permdock/next";
|
|
1427
|
+
|
|
1428
|
+
import { app } from "../permdock";
|
|
1429
|
+
import { ${current} } from "../session";
|
|
1430
|
+
|
|
1431
|
+
// Server-only: Server Components, Server Actions and Route Handlers import
|
|
1432
|
+
// from here; client components import hooks from permdock/react.
|
|
1433
|
+
export const {
|
|
1434
|
+
getPermDock,
|
|
1435
|
+
getPermission,
|
|
1436
|
+
getSnapshot,
|
|
1437
|
+
requirePermission,
|
|
1438
|
+
permdockHandler,
|
|
1439
|
+
} = createPermDock(app, { subject: ${current} });
|
|
1440
|
+
`)];
|
|
1441
|
+
case "hono": return [owned(`${ctx.src}/server.ts`, `import { Hono } from "hono";
|
|
1442
|
+
import { createPermDock } from "permdock/hono";
|
|
1443
|
+
|
|
1444
|
+
import { find${type} } from "./data";
|
|
1445
|
+
import { app as permdockApp } from "./permdock";
|
|
1446
|
+
import { permissions } from "./permissions";
|
|
1447
|
+
import { ${current} } from "./session";
|
|
1448
|
+
|
|
1449
|
+
const { protect } = createPermDock(permdockApp, {
|
|
1450
|
+
subject: (c) => ${current}(c.req.raw),
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
export const app = new Hono();
|
|
1454
|
+
|
|
1455
|
+
app.patch(
|
|
1456
|
+
"/${resource.name}s/:id",
|
|
1457
|
+
protect(permissions.${resource.name}.${action}, (c) => find${type}(c.req.param("id") ?? "")),
|
|
1458
|
+
(c) => c.json({ ok: true }),
|
|
1459
|
+
);
|
|
1460
|
+
`)];
|
|
1461
|
+
case "express": return [owned(`${ctx.src}/server.ts`, `import express from "express";
|
|
1462
|
+
import { createPermDock, toRequest } from "permdock/express";
|
|
1463
|
+
|
|
1464
|
+
import { find${type} } from "./data";
|
|
1465
|
+
import { app as permdockApp } from "./permdock";
|
|
1466
|
+
import { permissions } from "./permissions";
|
|
1467
|
+
import { ${current} } from "./session";
|
|
1468
|
+
|
|
1469
|
+
const { protect } = createPermDock(permdockApp, {
|
|
1470
|
+
subject: (req) => ${current}(toRequest(req)),
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
export const app = express();
|
|
1474
|
+
|
|
1475
|
+
app.patch(
|
|
1476
|
+
"/${resource.name}s/:id",
|
|
1477
|
+
protect(permissions.${resource.name}.${action}, (req) => find${type}(String(req.params["id"]))),
|
|
1478
|
+
(_req, res) => {
|
|
1479
|
+
res.json({ ok: true });
|
|
1480
|
+
},
|
|
1481
|
+
);
|
|
1482
|
+
`)];
|
|
1483
|
+
case "none":
|
|
1484
|
+
if (current === "currentSubject") return [owned(`${ctx.src}/server.ts`, `import { getPermDock } from "permdock";
|
|
1485
|
+
|
|
1486
|
+
import { app } from "./permdock";
|
|
1487
|
+
import { currentSubject } from "./session";
|
|
1488
|
+
|
|
1489
|
+
/** The request-scoped decision object for the caller of \`request\`; create one per request. */
|
|
1490
|
+
export async function permdockFor(request: Request) {
|
|
1491
|
+
return getPermDock(app, await currentSubject(request));
|
|
1492
|
+
}
|
|
1493
|
+
`)];
|
|
1494
|
+
return [owned(`${ctx.src}/server.ts`, `import { getPermDockSync } from "permdock";
|
|
1495
|
+
|
|
1496
|
+
import { app } from "./permdock";
|
|
1497
|
+
import type { User } from "./policy";
|
|
1498
|
+
|
|
1499
|
+
/** The request-scoped decision object for \`user\`; create one per request. */
|
|
1500
|
+
export function permdockFor(user: User | null) {
|
|
1501
|
+
return getPermDockSync(app, user);
|
|
1502
|
+
}
|
|
1503
|
+
`)];
|
|
1504
|
+
default: return ctx.preset.framework;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
function operationsFile(ctx) {
|
|
1508
|
+
const entries = ctx.domain.resources.flatMap((resource) => {
|
|
1509
|
+
const type = pascal(resource.name);
|
|
1510
|
+
const ops = [` list_${resource.name}s: { permission: permissions.${resource.name}.list },`];
|
|
1511
|
+
if (resource.preset === "crud") ops.push(` delete_${resource.name}: {\n permission: permissions.${resource.name}.delete,\n data: (input: { readonly id: string }) => find${type}(input.id),\n },`);
|
|
1512
|
+
for (const action of resource.collection ?? []) ops.push(` ${action}_${resource.name}: { permission: permissions.${resource.name}.${action} },`);
|
|
1513
|
+
return ops;
|
|
1514
|
+
});
|
|
1515
|
+
const finds = ctx.domain.resources.filter((resource) => resource.preset === "crud").map((resource) => `find${pascal(resource.name)}`);
|
|
1516
|
+
return `import { defineOperations } from "permdock";
|
|
1517
|
+
|
|
1518
|
+
${finds.length === 0 ? "" : `import { ${finds.join(", ")} } from "./data";\n`}import { permissions } from "./permissions";
|
|
1519
|
+
|
|
1520
|
+
/** The tools an agent may call, each bound to one permission. */
|
|
1521
|
+
export const operations = defineOperations(permissions, {
|
|
1522
|
+
${entries.join("\n")}
|
|
1523
|
+
});
|
|
1524
|
+
`;
|
|
1525
|
+
}
|
|
1526
|
+
function agentFiles(ctx) {
|
|
1527
|
+
const files = [];
|
|
1528
|
+
if (!ctx.has("operations")) return files;
|
|
1529
|
+
files.push({
|
|
1530
|
+
path: `${ctx.src}/operations.ts`,
|
|
1531
|
+
contents: operationsFile(ctx),
|
|
1532
|
+
kind: "owned"
|
|
1533
|
+
});
|
|
1534
|
+
if (ctx.preset.integrations.includes("ai-sdk")) {
|
|
1535
|
+
const session = sessionCall(ctx);
|
|
1536
|
+
const subject = session.takesRequest ? `// Pass the incoming Request as the AI SDK runtimeContext.
|
|
1537
|
+
subject: ({ runtimeContext }) =>
|
|
1538
|
+
runtimeContext instanceof Request ? ${session.name}(runtimeContext) : null,` : `subject: () => ${session.name}(),`;
|
|
1539
|
+
files.push({
|
|
1540
|
+
path: `${ctx.src}/agent.ts`,
|
|
1541
|
+
contents: `import { createPermDock } from "permdock/ai-sdk";
|
|
1542
|
+
|
|
1543
|
+
import { operations } from "./operations";
|
|
1544
|
+
import { app } from "./permdock";
|
|
1545
|
+
import { ${session.name} } from "./session";
|
|
1546
|
+
|
|
1547
|
+
// Pass toolApproval to the AI SDK's tool approval hook, or needsApproval to each tool.
|
|
1548
|
+
export const { toolApproval, needsApproval, capabilityMiddleware } =
|
|
1549
|
+
createPermDock(app, {
|
|
1550
|
+
${subject}
|
|
1551
|
+
actor: () => ({ id: "assistant", kind: "ai-sdk" }),
|
|
1552
|
+
operations,
|
|
1553
|
+
});
|
|
1554
|
+
`,
|
|
1555
|
+
kind: "owned"
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
if (ctx.preset.integrations.includes("mcp")) files.push({
|
|
1559
|
+
path: `${ctx.src}/mcp.ts`,
|
|
1560
|
+
contents: `import { createPermDock } from "permdock/mcp";
|
|
1561
|
+
|
|
1562
|
+
import { app } from "./permdock";
|
|
1563
|
+
import type { User } from "./policy";
|
|
1564
|
+
|
|
1565
|
+
/** Map the verified MCP auth info to your user; null is anonymous. */
|
|
1566
|
+
function userFor(_authInfo: unknown): User | null {
|
|
1567
|
+
return null;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// protectServer(new McpServer(...)) returns a server whose registerTool takes a permission.
|
|
1571
|
+
export const { protectServer } = createPermDock(app, {
|
|
1572
|
+
subject: (authInfo) => userFor(authInfo),
|
|
1573
|
+
});
|
|
1574
|
+
`,
|
|
1575
|
+
kind: "owned"
|
|
1576
|
+
});
|
|
1577
|
+
return files;
|
|
1578
|
+
}
|
|
1579
|
+
function keysFile(ctx) {
|
|
1580
|
+
if (!ctx.has("api-keys")) return [];
|
|
1581
|
+
const { resource } = firstInstance(ctx);
|
|
1582
|
+
return [{
|
|
1583
|
+
path: `${ctx.src}/keys.ts`,
|
|
1584
|
+
contents: `import { type PermDock, decideCredential } from "permdock";
|
|
1585
|
+
|
|
1586
|
+
import { permissions } from "./permissions";
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* Decides whether the signed-in user may create a read-only key for
|
|
1590
|
+
* ${resource.name}s${ctx.has("tenancy") ? " in their organisation" : ""}. Store the granted credential's hash; show the
|
|
1591
|
+
* key once.
|
|
1592
|
+
*/
|
|
1593
|
+
export function decideReadKey(permdock: PermDock, id: string) {
|
|
1594
|
+
return decideCredential(permdock, {
|
|
1595
|
+
kind: "service",
|
|
1596
|
+
id,
|
|
1597
|
+
principal: \`service:\${id}\`,
|
|
1598
|
+
tenant: permdock.subject.principal?.tenant ?? "",
|
|
1599
|
+
roles: ["${ctx.domain.demoRole}"],
|
|
1600
|
+
permissions: [permissions.${resource.name}.read, permissions.${resource.name}.list],
|
|
1601
|
+
name: "read-only key",
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
`,
|
|
1605
|
+
kind: "owned"
|
|
1606
|
+
}];
|
|
1607
|
+
}
|
|
1608
|
+
function rlsOption(ctx) {
|
|
1609
|
+
if (!ctx.has("rls")) return null;
|
|
1610
|
+
const table = `{\n table: "org_members",\n tenant: "org_id",\n user: "user_id",\n role: "role",\n }`;
|
|
1611
|
+
return ctx.preset.backend === "supabase" ? `supabaseRls({${ctx.has("tenancy") ? `\n memberships: ${table},\n ` : ""}})` : `{\n dialect: "guc",${ctx.has("tenancy") ? `\n memberships: { tenant: ${table} },` : ""}\n }`;
|
|
1612
|
+
}
|
|
1613
|
+
function configFile(ctx) {
|
|
1614
|
+
const rls = rlsOption(ctx);
|
|
1615
|
+
const options = [
|
|
1616
|
+
` permissions: "./${ctx.src}/permissions.ts",`,
|
|
1617
|
+
` policy: "./${ctx.src}/policy.ts",`,
|
|
1618
|
+
...ctx.has("operations") ? [` operations: "./${ctx.src}/operations.ts",`] : [],
|
|
1619
|
+
...rls === null ? [] : [` rls: ${rls},`]
|
|
1620
|
+
];
|
|
1621
|
+
return ownedFile("permdock.config.ts", `import { defineConfig } from "permdock/cli";
|
|
1622
|
+
${rls !== null && ctx.preset.backend === "supabase" ? `import { supabaseRls } from "permdock/supabase";\n` : ""}
|
|
1623
|
+
// The files permdock doctor, check and rls generate read.
|
|
1624
|
+
export default defineConfig({
|
|
1625
|
+
${options.join("\n")}
|
|
1626
|
+
});
|
|
1627
|
+
`);
|
|
1628
|
+
}
|
|
1629
|
+
/** The `@permdock` registry recipes for the preset's UI; native has the gate and the upgrade prompt. */
|
|
1630
|
+
function recipesFor(ctx) {
|
|
1631
|
+
const web = ["gated-button"];
|
|
1632
|
+
if (ctx.has("approvals")) web.push("approval-request-dialog");
|
|
1633
|
+
if (ctx.has("tenancy")) web.push("tenant-switcher", "role-picker");
|
|
1634
|
+
if (ctx.has("custom-roles")) web.push("custom-role-editor");
|
|
1635
|
+
if (ctx.has("plans")) web.push("upgrade-prompt");
|
|
1636
|
+
const native = ["gated-button", ...ctx.has("plans") ? ["upgrade-prompt"] : []];
|
|
1637
|
+
return [...ctx.preset.integrations.includes("shadcn") ? web.map((name) => `@permdock/${name}`) : [], ...ctx.preset.integrations.includes("react-native") ? native.map((name) => `@permdock/${name}-native`) : []];
|
|
1638
|
+
}
|
|
1639
|
+
function dependenciesFor(ctx) {
|
|
1640
|
+
const deps = ["permdock", "zod"];
|
|
1641
|
+
deps.push(...{
|
|
1642
|
+
next: [
|
|
1643
|
+
"next",
|
|
1644
|
+
"react",
|
|
1645
|
+
"react-dom"
|
|
1646
|
+
],
|
|
1647
|
+
hono: ["hono"],
|
|
1648
|
+
express: ["express"],
|
|
1649
|
+
none: []
|
|
1650
|
+
}[ctx.preset.framework]);
|
|
1651
|
+
if (ctx.preset.integrations.includes("ai-sdk")) deps.push("ai");
|
|
1652
|
+
if (ctx.preset.integrations.includes("mcp")) deps.push("@modelcontextprotocol/server");
|
|
1653
|
+
deps.push(...authDependencies(ctx), ...backendDependencies(ctx));
|
|
1654
|
+
if (ctx.preset.services.includes("cloud-hosted-grants")) deps.push("jose");
|
|
1655
|
+
return [...new Set(deps)];
|
|
1656
|
+
}
|
|
1657
|
+
function docsFor(ctx) {
|
|
1658
|
+
const docs = ["/docs/concepts/apps", "/docs/concepts/policies"];
|
|
1659
|
+
const byFeature = {
|
|
1660
|
+
tenancy: "/docs/concepts/tenancy",
|
|
1661
|
+
"custom-roles": "/docs/concepts/custom-roles",
|
|
1662
|
+
approvals: "/docs/adapters/approvals",
|
|
1663
|
+
operations: "/docs/concepts/operations",
|
|
1664
|
+
"decision-log": "/docs/concepts/audit-and-observability",
|
|
1665
|
+
"api-keys": "/docs/concepts/credentials",
|
|
1666
|
+
rls: "/docs/adapters/rls"
|
|
1667
|
+
};
|
|
1668
|
+
for (const feature of ctx.preset.features) {
|
|
1669
|
+
const page = byFeature[feature];
|
|
1670
|
+
if (page !== void 0) docs.push(page);
|
|
1671
|
+
}
|
|
1672
|
+
if (ctx.preset.framework !== "none") docs.push(`/docs/adapters/${ctx.preset.framework}`);
|
|
1673
|
+
docs.push(...authDocs(ctx), ...backendDocs(ctx));
|
|
1674
|
+
if (ctx.preset.services.length > 0) docs.push("/docs/adapters/cloud");
|
|
1675
|
+
return [...new Set(docs)];
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* The files, dependencies and recipes for `preset`. Pure: no file system
|
|
1679
|
+
* and no network, so the same function runs in the CLI, in a registry route
|
|
1680
|
+
* and in the browser preview on /create.
|
|
1681
|
+
*/
|
|
1682
|
+
function generatePreset(preset, options = {}) {
|
|
1683
|
+
const ctx = {
|
|
1684
|
+
preset,
|
|
1685
|
+
domain: DOMAINS[preset.archetype],
|
|
1686
|
+
has: (feature) => preset.features.includes(feature),
|
|
1687
|
+
src: options.src ?? "src"
|
|
1688
|
+
};
|
|
1689
|
+
const owned = (path, contents) => ownedFile(`${ctx.src}/${path}`, contents);
|
|
1690
|
+
return Object.freeze({
|
|
1691
|
+
code: encodePreset(preset),
|
|
1692
|
+
files: Object.freeze([
|
|
1693
|
+
owned("permissions.ts", permissionsFile(ctx)),
|
|
1694
|
+
owned("policy.ts", policyFile(ctx)),
|
|
1695
|
+
owned("permdock.ts", appFile(ctx)),
|
|
1696
|
+
...backendFiles(ctx),
|
|
1697
|
+
...sessionFiles(ctx),
|
|
1698
|
+
...frameworkFiles(ctx),
|
|
1699
|
+
...agentFiles(ctx),
|
|
1700
|
+
...keysFile(ctx),
|
|
1701
|
+
configFile(ctx)
|
|
1702
|
+
]),
|
|
1703
|
+
dependencies: Object.freeze(dependenciesFor(ctx)),
|
|
1704
|
+
recipes: Object.freeze(recipesFor(ctx)),
|
|
1705
|
+
docs: Object.freeze(docsFor(ctx))
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
//#endregion
|
|
1709
|
+
//#region src/preset/matrix.ts
|
|
1710
|
+
const NONE = {
|
|
1711
|
+
access: "none",
|
|
1712
|
+
approval: false,
|
|
1713
|
+
limit: false
|
|
1714
|
+
};
|
|
1715
|
+
/**
|
|
1716
|
+
* Who may do what under `preset`, from the same model the generated policy
|
|
1717
|
+
* is written from, for a page that shows the policy without running it.
|
|
1718
|
+
*/
|
|
1719
|
+
function presetMatrix(preset) {
|
|
1720
|
+
const domain = DOMAINS[preset.archetype];
|
|
1721
|
+
const plans = preset.features.includes("plans") && domain.pro !== void 0;
|
|
1722
|
+
const rows = domain.resources.flatMap((resource) => {
|
|
1723
|
+
return [
|
|
1724
|
+
...resource.preset === "crud" ? [
|
|
1725
|
+
"read",
|
|
1726
|
+
"list",
|
|
1727
|
+
"create",
|
|
1728
|
+
"update",
|
|
1729
|
+
"delete"
|
|
1730
|
+
] : ["read", "list"],
|
|
1731
|
+
...resource.actions ?? [],
|
|
1732
|
+
...resource.collection ?? []
|
|
1733
|
+
].map((action) => ({
|
|
1734
|
+
resource: resource.name,
|
|
1735
|
+
action
|
|
1736
|
+
}));
|
|
1737
|
+
}).map(({ resource, action }) => {
|
|
1738
|
+
const cells = {};
|
|
1739
|
+
const isPro = plans && domain.pro?.resource === resource && domain.pro.action === action;
|
|
1740
|
+
for (const role of domain.roles) {
|
|
1741
|
+
const grant = role.grants.find((item) => item.resource === resource && item.action === action);
|
|
1742
|
+
if (grant === void 0 || isPro && role.top !== true) {
|
|
1743
|
+
cells[role.name] = isPro ? {
|
|
1744
|
+
...NONE,
|
|
1745
|
+
access: "pro"
|
|
1746
|
+
} : NONE;
|
|
1747
|
+
continue;
|
|
1748
|
+
}
|
|
1749
|
+
const shape = grantShape(preset, role, grant);
|
|
1750
|
+
cells[role.name] = {
|
|
1751
|
+
access: shape.owner === void 0 ? "allow" : "own",
|
|
1752
|
+
approval: shape.approval,
|
|
1753
|
+
limit: shape.limit
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
return {
|
|
1757
|
+
resource,
|
|
1758
|
+
action,
|
|
1759
|
+
cells
|
|
1760
|
+
};
|
|
1761
|
+
});
|
|
1762
|
+
return {
|
|
1763
|
+
roles: domain.roles.map((role) => role.name),
|
|
1764
|
+
rows
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
//#endregion
|
|
1768
|
+
export { decodePreset as a, AUTH as c, FRAMEWORKS as d, INTEGRATIONS as f, resolvePreset as g, integrationsOf as h, presetFlags as i, BACKENDS as l, SERVICES as m, generatePreset as n, encodePreset as o, PRESET_DEFAULTS as p, PRESET_FLAGS as r, ARCHETYPES as s, presetMatrix as t, FEATURES as u };
|