permdock 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -47
- package/dist/a2a/index.d.ts +7 -16
- package/dist/a2a/index.js +59 -98
- package/dist/adapter-context-mlWsVGGg.js +23 -0
- package/dist/ai-sdk/index.d.ts +37 -16
- package/dist/ai-sdk/index.js +30 -13
- package/dist/approvals/index.d.ts +3 -3
- package/dist/approvals/index.js +5 -4
- package/dist/{approvals-DaxY-Bdg.js → approvals-DbYAuNkG.js} +17 -11
- package/dist/approvers-DT4AVyIg.js +52 -0
- package/dist/{arazzo-CTeVuXr5.js → arazzo-BAgw9ap6.js} +2 -2
- package/dist/{arazzo-aoTkqYsJ.js → arazzo-cmvX2jDe.js} +2 -2
- package/dist/authzen/index.d.ts +4 -13
- package/dist/authzen/index.js +16 -50
- package/dist/better-auth/index.d.ts +20 -3
- package/dist/better-auth/index.js +27 -7
- package/dist/better-supabase/index.d.ts +206 -0
- package/dist/better-supabase/index.js +620 -0
- package/dist/bind-CVy3Espv.d.ts +25 -0
- package/dist/bind-VnYn2HKq.js +97 -0
- package/dist/boundary-BM8a_P6E.js +13 -0
- package/dist/boundary-BmHxVH1J.d.ts +15 -0
- package/dist/canonical-json-DemiK4Vp.js +26 -0
- package/dist/{capability-B68OFnZx.js → capability-4qPdO9gk.js} +7 -5
- package/dist/{capability-BDG_nkxt.d.ts → capability-Dq43tT1u.d.ts} +2 -2
- package/dist/catalog/index.d.ts +1 -1
- package/dist/catalog/index.js +1 -1
- package/dist/catalog-BjZSovXQ.js +2 -0
- package/dist/{catalog-doc-BSo_Q_-6.js → catalog-doc-Dsc9y7sd.js} +23 -12
- package/dist/{catalog-B0E6bggc.js → catalog-h2tlKFkq.js} +2 -2
- package/dist/{catalog-H-vFGwWK.js → catalog-z8q6bi_r.js} +4 -4
- package/dist/claude-agent/index.d.ts +5 -14
- package/dist/claude-agent/index.js +4 -10
- package/dist/clerk/index.d.ts +12 -3
- package/dist/clerk/index.js +26 -5
- package/dist/cli/bin.js +1 -1
- package/dist/cli/index.d.ts +3 -3
- package/dist/cli/index.js +2 -2
- package/dist/clients-C7zLFuWT.d.ts +12 -0
- package/dist/clients-DjU5ABMy.js +23 -0
- package/dist/cloud/index.d.ts +4 -5
- package/dist/cloud/index.js +20 -6
- package/dist/{cloud-BNQTrmrr.js → cloud-BGhHS-lY.js} +2 -2
- package/dist/{cloud-Cuxr6Czw.js → cloud-Bo3e9Ak3.js} +7 -6
- package/dist/{collect-O-tOtbnk.js → collect-DDDv_ESs.js} +194 -42
- package/dist/collect-DLlujdHC.js +83 -0
- package/dist/collect-rG2IQ3t0.js +2 -0
- package/dist/{commands-BMh_Etm2.js → commands-DWOrSQic.js} +16 -12
- package/dist/compile/index.d.ts +3 -0
- package/dist/compile/index.js +2 -0
- package/dist/{compile-D22iwvuV.js → compile-Ba4blROR.js} +85 -30
- package/dist/compile-CNnl6uIt.d.ts +94 -0
- package/dist/config-BJiM-xAm.js +157 -0
- package/dist/config-Bn00Z3ks.js +36 -0
- package/dist/config-report-Dw_JcUqT.js +44 -0
- package/dist/{connection-JvqwHODC.js → connection-CU8MQxRi.js} +1 -1
- package/dist/{context-CKEAabd7.js → context-Ttp0ekMT.js} +36 -27
- package/dist/convex/index.d.ts +9 -4
- package/dist/convex/index.js +14 -4
- package/dist/create-BWyx3jua.d.ts +245 -0
- package/dist/create-CPhDL6qx.js +253 -0
- package/dist/{credential-B_rkUNlY.js → credential-8P81ff4u.js} +26 -46
- package/dist/delegation-D8pUEuce.js +103 -0
- package/dist/{describe-Bt1YD5uS.js → describe-B_gVZEVi.js} +10 -15
- package/dist/{describe-DernyItf.d.ts → describe-PdoqESvg.d.ts} +9 -10
- package/dist/{diff-x5Il8LVa.js → diff-B8Tunc3o.js} +44 -11
- package/dist/{diff-BUzf5Poi.js → diff-CzqLxZ77.js} +2 -2
- package/dist/doctor-WpvG6p4k.js +1194 -0
- package/dist/doctor-collect-ByfYlNv1.js +664 -0
- package/dist/{doctor-Bqo6FwUa.js → doctor-rOr-4ZfS.js} +2 -2
- package/dist/drizzle/index.d.ts +4 -3
- package/dist/drizzle/index.js +3 -3
- package/dist/elysia/index.d.ts +6 -28
- package/dist/elysia/index.js +16 -60
- package/dist/{errors-DSMnAVNa.js → errors-CWfoNg9N.js} +8 -4
- package/dist/{errors-Iy71s7Oj.js → errors-Cz5zn0mW.js} +17 -71
- package/dist/{errors-DbZ1YOnb.d.ts → errors-DAqevpNk.d.ts} +8 -21
- package/dist/{errors-m3u9TmDu.js → errors-DRnGZHzF.js} +4 -1
- package/dist/evaluation-items-CfCCejQd.js +40 -0
- package/dist/{evaluations-DsAU9ppl.js → evaluations-tz6D9A2G.js} +28 -46
- package/dist/eve/index.d.ts +6 -15
- package/dist/eve/index.js +7 -12
- package/dist/expiry-BoBJ8Tvc.js +7 -0
- package/dist/express/index.d.ts +6 -29
- package/dist/express/index.js +28 -58
- package/dist/fastify/index.d.ts +6 -29
- package/dist/fastify/index.js +16 -65
- package/dist/fields-BMl1RxoX.js +26 -0
- package/dist/{files-B_wAGYmt.js → files-DWUN9PzI.js} +5 -1
- package/dist/{fixtures-Cvaw0gDG.js → fixtures-ht28Cplr.js} +5 -2
- package/dist/{freeze-y1_jQL4v.js → freeze-CrU8nQeX.js} +31 -2
- package/dist/{from-snapshot-BMGnhmgv.js → from-snapshot-C3sHpZNS.js} +65 -48
- package/dist/{grantee-Bv04_Vzf.js → grantee-Cv5Sogfv.js} +88 -7
- package/dist/graph-sql-B-dsAT6j.d.ts +29 -0
- package/dist/{headers-B5RRv3Xm.js → headers-BZSDieNT.js} +1 -1
- package/dist/{headers-C8SHQccD.d.ts → headers-Cf-zWvsO.d.ts} +1 -1
- package/dist/helpers-H_KARDVC.js +421 -0
- package/dist/hono/index.d.ts +6 -28
- package/dist/hono/index.js +11 -43
- package/dist/{hosted-BwKQ1BTA.js → hosted-DJQw46hn.js} +6 -5
- package/dist/{http-GgVApX1k.js → http-BIhmLfCc.js} +4 -20
- package/dist/http-SO7qGttf.js +44 -0
- package/dist/ids-DC4V4CFu.js +18 -0
- package/dist/{index-LF_hS-yn.d.ts → index-A0YNL8jZ.d.ts} +1 -1
- package/dist/index-CtE07CBF.d.ts +6 -0
- package/dist/{index-xIxJLwOQ.d.ts → index-D3vbkoSI.d.ts} +75 -7
- package/dist/{index-CltcQ4Bf.d.ts → index-D6_cSa1z.d.ts} +68 -5
- package/dist/index.d.ts +14 -9
- package/dist/index.js +22 -17
- package/dist/instance-options-BK_rNOSC.d.ts +11 -0
- package/dist/instance-options-Dhhcnm3y.js +22 -0
- package/dist/{parse-DPfIvFMU.js → json-schema-BsAflUGW.js} +28 -57
- package/dist/jwt/index.d.ts +10 -6
- package/dist/jwt/index.js +32 -13
- package/dist/kernel-GSQnMz3O.js +208 -0
- package/dist/kysely/index.d.ts +4 -3
- package/dist/kysely/index.js +3 -3
- package/dist/live-options-H2eG8dfJ.js +60 -0
- package/dist/{load-BZ24CpZP.js → load-DhWEB9P1.js} +16 -10
- package/dist/load-DwJUafYN.js +2 -0
- package/dist/manifest-iaoBXHKc.js +783 -0
- package/dist/manifest-uDS9y5de.d.ts +206 -0
- package/dist/{map-DowSO96j.js → map-AcxK9o_D.js} +4 -15
- package/dist/{map-claims-B_6I1Rn-.js → map-claims-Z4QZOiFF.js} +13 -4
- package/dist/map-error-pgUWNNtG.js +23 -0
- package/dist/may-use-DndQ5tQ-.js +77 -0
- package/dist/mcp/index.d.ts +54 -22
- package/dist/mcp/index.js +218 -178
- package/dist/nest/index.d.ts +26 -31
- package/dist/nest/index.js +75 -84
- package/dist/next/client.d.ts +7 -8
- package/dist/next/client.js +11 -9
- package/dist/next/index.d.ts +30 -47
- package/dist/next/index.js +45 -60
- package/dist/next/plugin.d.ts +1 -1
- package/dist/next/plugin.js +32 -1
- package/dist/node/index.d.ts +9 -28
- package/dist/node/index.js +11 -43
- package/dist/openai/index.d.ts +6 -15
- package/dist/openai/index.js +7 -20
- package/dist/openapi/index.d.ts +87 -4
- package/dist/openapi/index.js +3 -3
- package/dist/{openapi-CwePaF7C.js → openapi-Bbg4wGVL.js} +3 -3
- package/dist/{openapi-DcqM8nU7.js → openapi-BqRBvAng.js} +237 -5
- package/dist/{openapi-DWD0Uc_4.js → openapi-CqqEbHsL.js} +5 -5
- package/dist/{openapi-import-BWRUNNeW.js → openapi-import-cmwetbzw.js} +1 -1
- package/dist/{openapi-schema-D8j_T9tO.js → openapi-schema--ARjyfQr.js} +2 -2
- package/dist/orpc/index.d.ts +15 -28
- package/dist/orpc/index.js +74 -117
- package/dist/otel/index.d.ts +2 -2
- package/dist/parse-BuyjxhZV.js +53 -0
- package/dist/parse-local-manifest-BkGQNzAh.d.ts +10 -0
- package/dist/parse-local-manifest-BrMozJCo.js +36 -0
- package/dist/pdp/index.d.ts +2 -2
- package/dist/pdp/index.js +14 -127
- package/dist/{permdock-yoZ7EjxX.js → permdock-CzqXjCpU.js} +641 -126
- package/dist/{permissions-B8vCg_bI.d.ts → permissions-CDgvKG5L.d.ts} +2 -2
- package/dist/{permissions-CofRqesP.js → permissions-CpZrivYv.js} +3 -3
- package/dist/{permissions-DC7GROPA.js → permissions-DVoF6dMt.js} +77 -15
- package/dist/{permissions-B4apnahQ.d.ts → permissions-DttnqqxC.d.ts} +34 -5
- package/dist/{pins-CqE1jZuF.js → pins-8ILTftC9.js} +2 -2
- package/dist/{policy-CInmNGkM.d.ts → policy-CFZAr_Al.d.ts} +330 -37
- package/dist/{policy-Cq2fB8Q5.d.ts → policy-D8UFyERw.d.ts} +2 -2
- package/dist/{policy-ANp36PnI.js → policy-xyWg7QdR.js} +173 -22
- package/dist/powersync-A8x_Yyvh.js +2 -0
- package/dist/powersync-C7kLglqX.js +540 -0
- package/dist/powersync-yW1UUFzp.js +62 -0
- package/dist/prisma/index.d.ts +3 -2
- package/dist/prisma/index.js +45 -20
- package/dist/{problem-Cgp9-KZA.js → problem-B43ztKIm.js} +74 -109
- package/dist/problem-details-CkL50EEf.d.ts +7 -0
- package/dist/problem-details-_AMOYz-w.js +144 -0
- package/dist/{protected-BuPAycJT.d.ts → protected-CNsMzrVd.d.ts} +5 -4
- package/dist/protected-Cs6kTaHo.js +100 -0
- package/dist/protected-bZs6rbT1.d.ts +40 -0
- package/dist/{provider-wTAUda2v.d.ts → provider-Cjr62YMV.d.ts} +1 -1
- package/dist/provider-DLzpb2Xz.js +52 -0
- package/dist/provider-store-DNyC1y48.js +43 -0
- package/dist/react/index.d.ts +7 -5
- package/dist/react/index.js +5 -4
- package/dist/react/provider-client.d.ts +1 -1
- package/dist/react/provider-client.js +1 -1
- package/dist/react-native/index.d.ts +166 -10
- package/dist/react-native/index.js +607 -85
- package/dist/{refs-ChLSxbST.js → refs-KYb4ntaz.js} +4 -3
- package/dist/{relations-r4soIFZB.js → relations-cjOL01hZ.js} +110 -31
- package/dist/replay-BsOyTrdV.js +28 -0
- package/dist/replay-sqBGMnNs.d.ts +5 -0
- package/dist/resource-ref-CaS9yVPi.js +20 -0
- package/dist/revocations-Bhr0aMIC.js +27 -0
- package/dist/rls-BMNydpHk.js +2521 -0
- package/dist/{rls-BOFU3chG.js → rls-CEvs64Kj.js} +8 -3
- package/dist/rls-fields-BMasREhR.js +3502 -0
- package/dist/{rls-graph-DxQ39PJj.js → rls-graph-pc9fuXYm.js} +100 -36
- package/dist/{rls-import-CXV6LBlS.js → rls-import-D8F7j35Q.js} +7 -6
- package/dist/{rls-migrate-DrFPbf8p.js → rls-migrate-7wikfrVI.js} +6 -5
- package/dist/rls-ownership-B5Lkq62W.js +843 -0
- package/dist/rls-shared-aOQ4hGTO.js +493 -0
- package/dist/rls-verify-D8B7dOTh.js +745 -0
- package/dist/{row-check-BajTgQZ-.d.ts → row-check-Dt35czLy.d.ts} +12 -15
- package/dist/rpc-DOK54_CS.js +89 -0
- package/dist/{run-CJ6UvS3a.js → run-YXCJDNYk.js} +31 -12
- package/dist/runtime-CB8gQFvS.js +11 -0
- package/dist/{saas-BRrZzJkM.js → saas-Bj_FFkjN.js} +17 -7
- package/dist/{schema-P8yoma__.js → schema-D3MnDMxa.js} +8 -4
- package/dist/scim/index.d.ts +1 -1
- package/dist/scim/index.js +1 -1
- package/dist/{scim-9NFa0XVZ.js → scim-BFGBBAqR.js} +1 -1
- package/dist/{scopes-cKVp_WTw.js → scopes-CVdPrWN-.js} +35 -8
- package/dist/server/index.d.ts +22 -11
- package/dist/server/index.js +11 -8
- package/dist/{credentials-BgRX3WEg.js → server-DlZ28Oz5.js} +1 -1
- package/dist/shared-DRu8VFVM.js +123 -0
- package/dist/{sink-Cfr9-7YC.d.ts → sink-Gs6TTVa-.d.ts} +1 -1
- package/dist/skills-Biq5uup3.js +192 -0
- package/dist/{skills-C_lpV4Jx.js → skills-JaymNCdK.js} +10 -4
- package/dist/{snapshot-DE8dE_hz.js → snapshot-Enxd0n-s.js} +138 -214
- package/dist/snapshot-cache-D6jLelln.js +44 -0
- package/dist/snapshot-cache-DwQH8ufx.d.ts +45 -0
- package/dist/solid/index.d.ts +39 -9
- package/dist/solid/index.js +76 -85
- package/dist/sources-B0UvrJ21.js +534 -0
- package/dist/sources-BcJgw0lt.d.ts +192 -0
- package/dist/{sql-Bll1d7y0.js → sql-xdg5f4Fi.js} +6 -1
- package/dist/{src-CTNT-PBW.js → src-CSbUiozF.js} +149 -11
- package/dist/ssf/index.d.ts +3 -2
- package/dist/ssf/index.js +3 -29
- package/dist/store-DMAWwAJV.js +371 -0
- package/dist/{store-Bq90395i.d.ts → store-D_pjn91-.d.ts} +13 -4
- package/dist/store-options-yO0V6gRn.js +716 -0
- package/dist/{stores-CkG2HrSg.js → stores-4_sqiOA1.js} +10 -63
- package/dist/{stores-CvOnu_2T.d.ts → stores-DVR5NHLE.d.ts} +20 -9
- package/dist/{stream-Q_xlB6Ko.js → stream-DDpvynSB.js} +16 -2
- package/dist/{stream-BhMfZOwr.d.ts → stream-DlvG2hs-.d.ts} +2 -2
- package/dist/subject-settings-BwBmCdTR.js +128 -0
- package/dist/supabase/index.d.ts +122 -6
- package/dist/supabase/index.js +4 -3
- package/dist/supabase/middleware.d.ts +32 -27
- package/dist/supabase/middleware.js +56 -22
- package/dist/{supabase-D8UlBA7Z.js → supabase-CwafwxxF.js} +3 -3
- package/dist/{supabase-DTxGtYdi.js → supabase-DTBaC20O.js} +345 -367
- package/dist/supabase-config-Bs44dare.js +472 -0
- package/dist/{supabase-hook-D_OqGb_V.js → supabase-hook-BuxLQLpU.js} +994 -708
- package/dist/supabase-hook-D5qVd_D8.js +2 -0
- package/dist/supautils-DYZnzRWV.js +91 -0
- package/dist/svelte/PermissionBoundary.svelte +37 -0
- package/dist/svelte/Protected.svelte +8 -2
- package/dist/svelte/index.d.ts +9 -6
- package/dist/svelte/index.js +61 -7
- package/dist/svelte/runtime.d.ts +4 -3
- package/dist/svelte/runtime.js +4 -3
- package/dist/svelte/source.d.ts +9 -6
- package/dist/svelte/source.js +7 -4
- package/dist/terminal/index.d.ts +6 -14
- package/dist/terminal/index.js +44 -32
- package/dist/testing/index.d.ts +105 -16
- package/dist/testing/index.js +504 -40
- package/dist/testing/saas/index.d.ts +4 -4
- package/dist/testing/saas/index.js +2 -2
- package/dist/testing/saas/permissions.d.ts +1 -1
- package/dist/testing/saas/permissions.js +1 -1
- package/dist/token-CIWxJOCh.js +50 -0
- package/dist/trpc/index.d.ts +7 -26
- package/dist/trpc/index.js +17 -100
- package/dist/types-9AWO9EGt.d.ts +197 -0
- package/dist/{types-D6Z6-rKJ.d.ts → types-BPxgszlT.d.ts} +260 -22
- package/dist/{types-BvBk1wgn.d.ts → types-BVyImy5I.d.ts} +5 -3
- package/dist/{types-CxquUByI.d.ts → types-Bvc4oWT6.d.ts} +1 -1
- package/dist/types-C3s9xVJG.d.ts +48 -0
- package/dist/{types-BBK-exvJ.d.ts → types-Dnemwd77.d.ts} +7 -1
- package/dist/types-kxznc7cn.d.ts +52 -0
- package/dist/{types-B8w5PFtS.d.ts → types-vKyAMtcX.d.ts} +17 -1
- package/dist/{types-B9dmiiab.d.ts → types-vNSDF7Dq.d.ts} +1 -1
- package/dist/{sources-DOpW5Ila.d.ts → types-yF5LmQVD.d.ts} +86 -127
- package/dist/unplugin/index.d.ts +1 -1
- package/dist/unplugin/index.js +10 -9
- package/dist/{usage-1ozMubLY.js → usage-D07N9QJ3.js} +2 -2
- package/dist/usage-D82l1CwK.js +2 -0
- package/dist/{usage-CUnK6Ou9.js → usage-DG1oeCca.js} +225 -62
- package/dist/validation-error-CnjZ4t92.js +33 -0
- package/dist/validation-error-CtF23SrC.d.ts +26 -0
- package/dist/{validity-D0uRmJKJ.js → validity-uOp8UpYn.js} +57 -36
- package/dist/{verifier-B90agnhl.js → verifier-BxBo6I44.js} +10 -5
- package/dist/version-BvacVTZR.js +17 -0
- package/dist/views-Soe7bGSC.js +95 -0
- package/dist/{vocabulary-Dk0fs-Jk.d.ts → vocabulary-DA8a8zif.d.ts} +1 -1
- package/dist/vue/index.d.ts +35 -13
- package/dist/vue/index.js +86 -93
- package/dist/watch-Etoa_Mvr.js +2 -0
- package/dist/watch-UihESpsJ.js +128 -0
- package/dist/webmcp/index.d.ts +2 -2
- package/dist/webmcp/index.js +7 -13
- package/package.json +62 -36
- package/schemas/approval-request-v1.json +175 -0
- package/schemas/catalog-v1.json +29 -1
- package/schemas/supabase-claims-v1.json +6 -1
- package/schemas/supabase-manifest-v1.json +150 -6
- package/skills/permdock/SKILL.md +7 -7
- package/skills/permdock-agents/SKILL.md +5 -5
- package/skills/permdock-agents/references/runtimes.md +12 -10
- package/skills/permdock-approvals/SKILL.md +4 -4
- package/skills/permdock-approvals/references/stores-and-resume.md +15 -4
- package/skills/permdock-audit/SKILL.md +3 -3
- package/skills/permdock-credentials/SKILL.md +4 -4
- package/skills/permdock-credentials/references/tokens-keys-links.md +7 -2
- package/skills/permdock-data/SKILL.md +4 -4
- package/skills/permdock-data/references/queries-and-rls.md +14 -6
- package/skills/permdock-data/references/relationships.md +5 -3
- package/skills/permdock-tenancy/SKILL.md +9 -6
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +14 -8
- package/skills/permdock-wire/SKILL.md +4 -4
- package/skills/permdock-wire/references/adapters.md +42 -30
- package/skills/permdock-wire/references/policy-options.md +5 -3
- package/dist/approvers-aH0PJkmf.js +0 -20
- package/dist/catalog-idsHc0vo.js +0 -2
- package/dist/collect-B7_TIfmg.js +0 -49
- package/dist/collect-fRPAQIWb.js +0 -2
- package/dist/compile-CQbvyIBr.d.ts +0 -30
- package/dist/config-BPYmjTOp.js +0 -26
- package/dist/context-gtAwAILx.js +0 -6
- package/dist/create-BKF3dgFk.d.ts +0 -162
- package/dist/create-CSkhDU9T.js +0 -222
- package/dist/doctor-BGle8Gqi.js +0 -1314
- package/dist/helpers-DnSOrhB9.js +0 -682
- package/dist/index-fG6NSXlc.d.ts +0 -9
- package/dist/kernel-YOGCWNLf.js +0 -211
- package/dist/load-QOOm1z70.js +0 -2
- package/dist/manifest-DDQbC6K0.d.ts +0 -118
- package/dist/map-error-RJ3ZsUyA.js +0 -16
- package/dist/may-use-CnFEjVEp.js +0 -37
- package/dist/pg-4GJWDUxG.js +0 -953
- package/dist/plugin-4gr7IxFJ.js +0 -65
- package/dist/protected-B4K3BKoX.js +0 -138
- package/dist/protected-wY7nXhY9.d.ts +0 -23
- package/dist/provider-_wx46HpB.js +0 -48
- package/dist/rls-CJVja9Ww.js +0 -2119
- package/dist/rls-helpers-BxRuif0l.js +0 -1346
- package/dist/runtime-DP9ttFh1.js +0 -27
- package/dist/skills-Dx3QLnMY.js +0 -110
- package/dist/source-DoBBYJi2.js +0 -6
- package/dist/sources-DVHM3Mwq.js +0 -284
- package/dist/store-C6hFW6ta.js +0 -508
- package/dist/subject-settings-xt7V845g.js +0 -82
- package/dist/supabase-hook-DW-xP4hU.js +0 -2
- package/dist/types-DbWdEfcm.d.ts +0 -87
- package/dist/types-bva3i4Gf.d.ts +0 -98
- package/dist/types-i4BAOvdb.d.ts +0 -97
- package/dist/usage-PyEor6GA.js +0 -2
- package/dist/version-CnSh33Fj.js +0 -16
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { i as findPermission } from "./permissions-DVoF6dMt.js";
|
|
2
|
+
import { c as isConditionRef, s as isConditionDate } from "./refs-KYb4ntaz.js";
|
|
3
|
+
import { _ as memoryRoleSource, p as localSnapshotManifest } from "./src-CSbUiozF.js";
|
|
4
|
+
import { d as scopeList, o as resolveScope } from "./scopes-CVdPrWN-.js";
|
|
5
|
+
import { t as requiresApproval } from "./approval-required-TzNB9oQA.js";
|
|
6
|
+
import { t as createPermDock } from "./permdock-CzqXjCpU.js";
|
|
7
|
+
import { o as usageResult } from "./errors-CWfoNg9N.js";
|
|
8
|
+
import { a as loadModule, n as asPolicy, o as pickNamed } from "./load-DhWEB9P1.js";
|
|
9
|
+
import { i as scopeMembershipTable, r as scopeColumn } from "./compile-Ba4blROR.js";
|
|
10
|
+
import { i as quoteSqlLiteral, t as SQL_IDENT } from "./sql-xdg5f4Fi.js";
|
|
11
|
+
import "./sources-B0UvrJ21.js";
|
|
12
|
+
import { W as quoteIdent, t as CUSTOM_ROLES } from "./rls-shared-aOQ4hGTO.js";
|
|
13
|
+
import { H as collectGrants, W as contextRefs, h as tableFor, w as connectPg } from "./rls-fields-BMasREhR.js";
|
|
14
|
+
import { n as fixtureSubject, r as loadFixtures, t as fixtureRow } from "./fixtures-ht28Cplr.js";
|
|
15
|
+
import { a as ownershipRules } from "./rls-ownership-B5Lkq62W.js";
|
|
16
|
+
import { t as canFixture } from "./rls-verify-D8B7dOTh.js";
|
|
17
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { resolve } from "node:path";
|
|
19
|
+
//#region src/cli/powersync-streams.ts
|
|
20
|
+
function ident(name) {
|
|
21
|
+
if (!SQL_IDENT.test(name)) throw new TypeError(`PermDock CLI: '${name}' is not a plain identifier, which Sync Streams need`);
|
|
22
|
+
return name;
|
|
23
|
+
}
|
|
24
|
+
const POWERSYNC = {
|
|
25
|
+
user: "auth.user_id()",
|
|
26
|
+
claim: (name) => `auth.parameter(${quoteSqlLiteral(name)})`,
|
|
27
|
+
claims: (name) => `(SELECT value FROM json_each(auth.parameter(${quoteSqlLiteral(name)})))`,
|
|
28
|
+
column: (table, name) => `${table}.${ident(name)}`,
|
|
29
|
+
literal: (value) => typeof value === "string" ? quoteSqlLiteral(value) : String(value)
|
|
30
|
+
};
|
|
31
|
+
const POSTGRES = {
|
|
32
|
+
user: "$1::text",
|
|
33
|
+
claim: (name) => `($2::jsonb ->> ${quoteSqlLiteral(name)})`,
|
|
34
|
+
claims: (name) => `(select jsonb_array_elements_text(case jsonb_typeof($2::jsonb -> ${quoteSqlLiteral(name)}) when 'array' then $2::jsonb -> ${quoteSqlLiteral(name)} else '[]'::jsonb end))`,
|
|
35
|
+
column: (table, name, numeric) => `${table}.${ident(name)}${numeric === true ? "" : "::text"}`,
|
|
36
|
+
literal: (value) => typeof value === "number" ? String(value) : quoteSqlLiteral(String(value))
|
|
37
|
+
};
|
|
38
|
+
/** At most this many queries per stream; a wider `or` product is left out rather than truncated. */
|
|
39
|
+
const MAX_QUERIES = 32;
|
|
40
|
+
/** Every combination of one clause list from each input, ANDed. */
|
|
41
|
+
function product(lists) {
|
|
42
|
+
let out = [[]];
|
|
43
|
+
for (const list of lists) out = out.flatMap((left) => list.map((right) => left.concat(right)));
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
function every(parts) {
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const part of parts) {
|
|
49
|
+
if (part === void 0) return;
|
|
50
|
+
out.push(part);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/** The table as a stream query names it: unqualified in `public`, `schema.table` otherwise. */
|
|
55
|
+
function bare(table) {
|
|
56
|
+
return (table.startsWith("public.") ? table.slice(7) : table).split(".").map(ident).join(".");
|
|
57
|
+
}
|
|
58
|
+
/** A row value: a literal, or a principal ref the token carries. `undefined` when neither. */
|
|
59
|
+
function valueSql(value, ctx) {
|
|
60
|
+
if (isConditionRef(value)) {
|
|
61
|
+
if (value.ref === "principal.id") return ctx.dialect.user;
|
|
62
|
+
const claim = /^principal\.claims\.([A-Za-z_][A-Za-z0-9_]*)$/u.exec(value.ref)?.[1];
|
|
63
|
+
return claim === void 0 ? void 0 : ctx.dialect.claim(claim);
|
|
64
|
+
}
|
|
65
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return ctx.dialect.literal(value);
|
|
66
|
+
}
|
|
67
|
+
const COMPARE = {
|
|
68
|
+
eq: "=",
|
|
69
|
+
ne: "!=",
|
|
70
|
+
gt: ">",
|
|
71
|
+
gte: ">=",
|
|
72
|
+
lt: "<",
|
|
73
|
+
lte: "<="
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* A row condition as alternatives of ANDed clauses over `table`. `undefined`
|
|
77
|
+
* when part of it has no Sync Streams form, so the grant syncs nothing.
|
|
78
|
+
*/
|
|
79
|
+
function conditionSql(condition, table, ctx) {
|
|
80
|
+
const column = (field) => SQL_IDENT.test(field) ? ctx.dialect.column(table, field) : void 0;
|
|
81
|
+
switch (condition.op) {
|
|
82
|
+
case "eq":
|
|
83
|
+
case "ne":
|
|
84
|
+
case "gt":
|
|
85
|
+
case "gte":
|
|
86
|
+
case "lt":
|
|
87
|
+
case "lte": {
|
|
88
|
+
const left = typeof condition.value === "number" && SQL_IDENT.test(condition.field) ? ctx.dialect.column(table, condition.field, true) : column(condition.field);
|
|
89
|
+
if (condition.value === null && left !== void 0) return condition.op === "eq" ? [[`${left} IS NULL`]] : condition.op === "ne" ? [[`${left} IS NOT NULL`]] : void 0;
|
|
90
|
+
const right = valueSql(condition.value, ctx);
|
|
91
|
+
return left === void 0 || right === void 0 ? void 0 : [[`${left} ${COMPARE[condition.op]} ${right}`]];
|
|
92
|
+
}
|
|
93
|
+
case "isNull": {
|
|
94
|
+
const left = column(condition.field);
|
|
95
|
+
return left === void 0 ? void 0 : [[`${left} IS ${condition.value ? "" : "NOT "}NULL`]];
|
|
96
|
+
}
|
|
97
|
+
case "in":
|
|
98
|
+
case "notIn": {
|
|
99
|
+
const left = column(condition.field);
|
|
100
|
+
if (left === void 0) return;
|
|
101
|
+
const negate = condition.op === "notIn" ? "NOT " : "";
|
|
102
|
+
if (isConditionRef(condition.value)) {
|
|
103
|
+
const claim = /^principal\.claims\.([A-Za-z_][A-Za-z0-9_]*)$/u.exec(condition.value.ref)?.[1];
|
|
104
|
+
return claim === void 0 || negate !== "" ? void 0 : [[`${left} IN ${ctx.dialect.claims(claim)}`]];
|
|
105
|
+
}
|
|
106
|
+
const values = condition.value.map((value) => isConditionRef(value) || isConditionDate(value) || Array.isArray(value) ? void 0 : value === null ? void 0 : valueSql(value, ctx));
|
|
107
|
+
if (values.some((value) => value === void 0)) return;
|
|
108
|
+
return values.length === 0 ? negate === "" ? [] : [[]] : [[`${left} ${negate}IN (${values.join(", ")})`]];
|
|
109
|
+
}
|
|
110
|
+
case "and":
|
|
111
|
+
case "or": {
|
|
112
|
+
const parts = every(condition.conditions.map((child) => conditionSql(child, table, ctx)));
|
|
113
|
+
if (parts === void 0) return;
|
|
114
|
+
return condition.op === "and" ? product(parts) : parts.flat();
|
|
115
|
+
}
|
|
116
|
+
case "memberOf": {
|
|
117
|
+
if (condition.scope === "resource" || condition.parents !== void 0) return;
|
|
118
|
+
const scope = resolveScope(ctx.scopes, condition.scope);
|
|
119
|
+
const left = column(condition.field);
|
|
120
|
+
if (scope === void 0 || left === void 0) return;
|
|
121
|
+
return membershipSql(scope, left, condition.roles, ctx);
|
|
122
|
+
}
|
|
123
|
+
case "not":
|
|
124
|
+
case "contains":
|
|
125
|
+
case "related":
|
|
126
|
+
case "opaque":
|
|
127
|
+
case "sqlFunction":
|
|
128
|
+
case "liveSession": return;
|
|
129
|
+
default: return condition;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** One `IN (SELECT …)` per role source of the membership table: the row's `left` is an instance the user holds `roles` in. */
|
|
133
|
+
function membershipIn(table, idColumn, left, roles, ctx) {
|
|
134
|
+
if (table.expiresAt !== void 0 || table.disabledAt !== void 0) return;
|
|
135
|
+
const name = bare(table.table);
|
|
136
|
+
const sources = Array.isArray(table.role) ? table.role : [table.role];
|
|
137
|
+
const kinds = [...new Set(roles.flatMap((role) => ctx.kinds[role] ?? []))];
|
|
138
|
+
const restricted = roles.some((role) => ctx.kinds[role] !== void 0);
|
|
139
|
+
if (restricted && (table.via === void 0 || roles.length !== 1)) return;
|
|
140
|
+
const via = table.via;
|
|
141
|
+
if (restricted && typeof via === "object" && !kinds.includes(via.value)) return [];
|
|
142
|
+
const filters = [`${ctx.dialect.column(name, table.user)} = ${ctx.dialect.user}`, ...restricted && typeof via === "string" ? [`${ctx.dialect.column(name, via)} IN (${kinds.map((kind) => ctx.dialect.literal(kind)).join(", ")})`] : []];
|
|
143
|
+
const list = roles.map((role) => ctx.dialect.literal(role)).join(", ");
|
|
144
|
+
return sources.map((source) => {
|
|
145
|
+
if (typeof source === "string") return [`${left} IN (SELECT ${ctx.dialect.column(name, idColumn)} FROM ${name} WHERE ${[...filters, `${ctx.dialect.column(name, source)} IN (${list})`].join(" AND ")})`];
|
|
146
|
+
const [pair] = Object.entries(source.on);
|
|
147
|
+
const keys = bare(source.through);
|
|
148
|
+
const [ref, id] = pair ?? ["", ""];
|
|
149
|
+
return [`${left} IN (SELECT ${ctx.dialect.column(name, idColumn)} FROM ${name} INNER JOIN ${keys} ON ${ctx.dialect.column(keys, id)} = ${ctx.dialect.column(name, ref)} WHERE ${[...filters, `${ctx.dialect.column(keys, source.column)} IN (${list})`].join(" AND ")})`];
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function membershipSql(scope, left, roles, ctx) {
|
|
153
|
+
const table = scopeMembershipTable(ctx.config.rls?.memberships, ctx.scopes, scope);
|
|
154
|
+
const idColumn = table === void 0 ? void 0 : scopeColumn(table, ctx.scopes, scope);
|
|
155
|
+
return table === void 0 || idColumn === void 0 ? void 0 : membershipIn(table, idColumn, left, roles, ctx);
|
|
156
|
+
}
|
|
157
|
+
/** Who the grant reaches, as alternatives of clauses; `undefined` when Sync Streams cannot express it. */
|
|
158
|
+
function accessSql(item, table, ctx) {
|
|
159
|
+
const { access } = item;
|
|
160
|
+
switch (access.kind) {
|
|
161
|
+
case "anyone":
|
|
162
|
+
case "authenticated": return [[]];
|
|
163
|
+
case "role": {
|
|
164
|
+
if (access.scope === "global") return `global role ${access.role} is not a row filter`;
|
|
165
|
+
const field = ctx.policy.scopes.find((scope) => scope.name === access.scope)?.key;
|
|
166
|
+
if (field === void 0 || !SQL_IDENT.test(field)) return `scope ${access.scope} has no row key`;
|
|
167
|
+
return membershipSql(access.scope, ctx.dialect.column(table, field), [access.role], ctx) ?? `no rls.memberships table without an expiry or disabledAt and with the kinds of ${access.role} for scope ${access.scope}`;
|
|
168
|
+
}
|
|
169
|
+
case "resource": {
|
|
170
|
+
const holder = ctx.config.rls?.memberships?.resource?.[access.resource];
|
|
171
|
+
const node = ctx.policy.resources.get(item.grant.permission.resource);
|
|
172
|
+
const field = node?.name === access.resource ? node.id : node?.parent?.resource === access.resource ? node.parent.field : void 0;
|
|
173
|
+
if (holder === void 0 || field === void 0 || !SQL_IDENT.test(field)) return `no rls.memberships.resource table for ${access.resource} on the row`;
|
|
174
|
+
return membershipIn(holder, holder.id ?? "id", ctx.dialect.column(table, field), [access.role], ctx) ?? `the ${access.resource} memberships table has an expiry or disabledAt, or lacks via`;
|
|
175
|
+
}
|
|
176
|
+
case "actor": return "actor grantees are not in the sync token";
|
|
177
|
+
default: return access;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** `permdock_<table>`, with a schema joined by `_`; a table in the `permdock` schema keeps one prefix. */
|
|
181
|
+
function subjectStreamName(table) {
|
|
182
|
+
const name = table.replaceAll(".", "_");
|
|
183
|
+
return name.startsWith("permdock_") ? name : `permdock_${name}`;
|
|
184
|
+
}
|
|
185
|
+
function roleThroughs(role) {
|
|
186
|
+
return (Array.isArray(role) ? role : [role]).filter((item) => typeof item === "object" && item !== null);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Streams of the rows a device builds the user's snapshot from: the user's
|
|
190
|
+
* own membership and global-role rows, the roles tables they reference, and
|
|
191
|
+
* the custom roles of the user's tenants. Each query starts from the user's
|
|
192
|
+
* own rows.
|
|
193
|
+
*/
|
|
194
|
+
function subjectStreams(ctx) {
|
|
195
|
+
const rls = ctx.config.rls;
|
|
196
|
+
const { dialect } = ctx;
|
|
197
|
+
const tables = /* @__PURE__ */ new Map();
|
|
198
|
+
const add = (table, query, user) => {
|
|
199
|
+
const entry = tables.get(table) ?? {
|
|
200
|
+
user: void 0,
|
|
201
|
+
queries: /* @__PURE__ */ new Set()
|
|
202
|
+
};
|
|
203
|
+
if (entry.queries.size === 0) entry.user = user;
|
|
204
|
+
else if (entry.user !== user) entry.user = void 0;
|
|
205
|
+
entry.queries.add(query);
|
|
206
|
+
tables.set(table, entry);
|
|
207
|
+
};
|
|
208
|
+
const own = (table, user, role) => {
|
|
209
|
+
const name = bare(table);
|
|
210
|
+
const mine = `${dialect.column(name, user)} = ${dialect.user}`;
|
|
211
|
+
add(name, `SELECT * FROM ${name} WHERE ${mine}`, user);
|
|
212
|
+
for (const through of roleThroughs(role)) {
|
|
213
|
+
const keys = bare(through.through);
|
|
214
|
+
const [pair] = Object.entries(through.on);
|
|
215
|
+
if (pair === void 0) continue;
|
|
216
|
+
const [ref, id] = pair;
|
|
217
|
+
add(keys, `SELECT * FROM ${keys} WHERE ${dialect.column(keys, id)} IN (SELECT ${dialect.column(name, ref)} FROM ${name} WHERE ${mine})`);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const memberships = rls?.memberships;
|
|
221
|
+
for (const table of [
|
|
222
|
+
...Object.values(memberships?.scopes ?? {}),
|
|
223
|
+
...memberships?.tenant === void 0 ? [] : [memberships.tenant],
|
|
224
|
+
...memberships?.team === void 0 ? [] : [memberships.team],
|
|
225
|
+
...Object.values(memberships?.resource ?? {})
|
|
226
|
+
]) own(table.table, table.user, table.role);
|
|
227
|
+
const schema = rls?.schema ?? "permdock";
|
|
228
|
+
const database = rls?.authorize === "database";
|
|
229
|
+
const globalRoles = rls?.roles ?? (database ? { table: `${schema}.user_roles` } : void 0);
|
|
230
|
+
if (globalRoles !== void 0) own(globalRoles.table, globalRoles.user ?? "user_id", globalRoles.role ?? "role");
|
|
231
|
+
if (database && rls?.customRoles === true) {
|
|
232
|
+
const root = ctx.scopes[0]?.name;
|
|
233
|
+
const tenants = root === void 0 ? void 0 : scopeMembershipTable(memberships, ctx.scopes, root);
|
|
234
|
+
const column = tenants === void 0 || root === void 0 ? void 0 : scopeColumn(tenants, ctx.scopes, root);
|
|
235
|
+
for (const name of [CUSTOM_ROLES.permissions, CUSTOM_ROLES.includes]) {
|
|
236
|
+
const table = bare(`${schema}.${name}`);
|
|
237
|
+
const tenant = dialect.column(table, "tenant_id");
|
|
238
|
+
add(table, `SELECT * FROM ${table} WHERE ${tenant} IS NULL`);
|
|
239
|
+
if (tenants !== void 0 && column !== void 0) {
|
|
240
|
+
const members = bare(tenants.table);
|
|
241
|
+
add(table, `SELECT * FROM ${table} WHERE ${tenant} IN (SELECT ${dialect.column(members, column)} FROM ${members} WHERE ${dialect.column(members, tenants.user)} = ${dialect.user})`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const out = [];
|
|
246
|
+
for (const [table, entry] of tables) {
|
|
247
|
+
const name = subjectStreamName(table);
|
|
248
|
+
const queries = [...entry.queries];
|
|
249
|
+
out.push(entry.user === void 0 ? {
|
|
250
|
+
name,
|
|
251
|
+
table,
|
|
252
|
+
queries
|
|
253
|
+
} : {
|
|
254
|
+
name,
|
|
255
|
+
table,
|
|
256
|
+
user: entry.user,
|
|
257
|
+
queries
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
262
|
+
/** Why a grant cannot become a stream query, before its conditions are compiled. */
|
|
263
|
+
function unsupported(item) {
|
|
264
|
+
const { grant } = item;
|
|
265
|
+
if (grant.breakGlass !== void 0) return "break-glass";
|
|
266
|
+
if (grant.closure !== void 0 || grant.portable === false) return "not portable";
|
|
267
|
+
if (requiresApproval(grant.approval)) return "needs approval";
|
|
268
|
+
if (grant.validity !== void 0) return "time-bounded";
|
|
269
|
+
const context = contextRefs(item.where);
|
|
270
|
+
return context.length > 0 ? `reads ${context.join(", ")}` : void 0;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Sync Streams for each synced resource: the union of the rows its read
|
|
274
|
+
* permission's allow grants reach. A grant with any part Sync Streams cannot
|
|
275
|
+
* express syncs nothing, and a resource with a deny grant gets no stream, so
|
|
276
|
+
* a stream never holds a row the policy denies.
|
|
277
|
+
*/
|
|
278
|
+
function powersyncPlan(policy, config, dialect = POWERSYNC) {
|
|
279
|
+
const scopes = scopeList(policy.scopes);
|
|
280
|
+
const ctx = {
|
|
281
|
+
policy,
|
|
282
|
+
config,
|
|
283
|
+
scopes,
|
|
284
|
+
kinds: ownershipRules(policy, scopes)?.kinds ?? {},
|
|
285
|
+
dialect
|
|
286
|
+
};
|
|
287
|
+
const action = config.powersync?.action ?? "read";
|
|
288
|
+
const grants = collectGrants(policy);
|
|
289
|
+
const resources = config.powersync?.resources ?? [...policy.resources.keys()].filter((name) => grants.some((item) => item.grant.permission.resource === name && item.grant.permission.action === action));
|
|
290
|
+
const streams = [];
|
|
291
|
+
const omitted = [];
|
|
292
|
+
const warnings = [];
|
|
293
|
+
for (const resource of resources) {
|
|
294
|
+
const node = policy.resources.get(resource);
|
|
295
|
+
const key = `${resource}.${action}`;
|
|
296
|
+
const items = grants.filter((item) => item.grant.permission.key === key);
|
|
297
|
+
if (node === void 0 || items.length === 0) {
|
|
298
|
+
omitted.push({
|
|
299
|
+
resource,
|
|
300
|
+
reason: `no grant of ${key}`
|
|
301
|
+
});
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (items.some((item) => item.grant.effect === "deny")) {
|
|
305
|
+
omitted.push({
|
|
306
|
+
resource,
|
|
307
|
+
reason: `${key} has a deny grant, which Sync Streams cannot subtract`
|
|
308
|
+
});
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
const table = bare(tableFor(resource, config.rls?.tables));
|
|
312
|
+
const { id } = node;
|
|
313
|
+
const queries = /* @__PURE__ */ new Set();
|
|
314
|
+
for (const item of items) {
|
|
315
|
+
const label = `${item.label}/${key}`;
|
|
316
|
+
const access = unsupported(item) ?? accessSql(item, table, ctx);
|
|
317
|
+
if (typeof access === "string") {
|
|
318
|
+
warnings.push(`${label} does not sync: ${access}`);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const where = item.where === void 0 ? [[]] : conditionSql(item.where, table, ctx);
|
|
322
|
+
if (where === void 0) {
|
|
323
|
+
warnings.push(`${label} does not sync: its condition has no Sync Streams form`);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const columns = item.grant.fields === void 0 ? "*" : [.../* @__PURE__ */ new Set([id, ...item.grant.fields])].map((field) => ctx.dialect.column(table, field)).join(", ");
|
|
327
|
+
for (const clauses of product([access, where])) queries.add(`SELECT ${columns} FROM ${table}${clauses.length === 0 ? "" : ` WHERE ${clauses.join(" AND ")}`}`);
|
|
328
|
+
}
|
|
329
|
+
if (queries.size === 0) omitted.push({
|
|
330
|
+
resource,
|
|
331
|
+
reason: `no grant of ${key} compiles`
|
|
332
|
+
});
|
|
333
|
+
else if (queries.size > MAX_QUERIES) omitted.push({
|
|
334
|
+
resource,
|
|
335
|
+
reason: `${String(queries.size)} queries, over the limit of ${String(MAX_QUERIES)}`
|
|
336
|
+
});
|
|
337
|
+
else streams.push({
|
|
338
|
+
name: resource,
|
|
339
|
+
resource,
|
|
340
|
+
table,
|
|
341
|
+
id,
|
|
342
|
+
queries: [...queries]
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
streams,
|
|
347
|
+
subject: subjectStreams(ctx),
|
|
348
|
+
omitted,
|
|
349
|
+
warnings
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
/** `sync-config.yaml`: edition 3 Sync Streams, one auto-subscribed stream per synced resource. */
|
|
353
|
+
function powersyncYaml(plan) {
|
|
354
|
+
const lines = [
|
|
355
|
+
"# Generated by permdock powersync generate from the policy. Do not edit.",
|
|
356
|
+
"config:",
|
|
357
|
+
" edition: 3",
|
|
358
|
+
"",
|
|
359
|
+
"streams:"
|
|
360
|
+
];
|
|
361
|
+
if (plan.streams.length === 0 && plan.subject.length === 0) lines[lines.length - 1] = "streams: {}";
|
|
362
|
+
for (const stream of [...plan.streams, ...plan.subject]) lines.push(` ${stream.name}:`, " auto_subscribe: true", " queries:", ...stream.queries.map((query) => ` - ${JSON.stringify(query)}`));
|
|
363
|
+
for (const item of plan.omitted) lines.push(`# ${item.resource}: not synced, ${item.reason}`);
|
|
364
|
+
return `${lines.join("\n")}\n`;
|
|
365
|
+
}
|
|
366
|
+
//#endregion
|
|
367
|
+
//#region src/cli/powersync.ts
|
|
368
|
+
const POWERSYNC_HELP = `permdock powersync generate [--out sync-config.yaml] [--check]
|
|
369
|
+
permdock powersync verify [--db <url>] [--fixtures rls.fixtures.json]`;
|
|
370
|
+
const DEFAULT_OUT = "sync-config.yaml";
|
|
371
|
+
function powersyncOut(config, out) {
|
|
372
|
+
return out ?? config.powersync?.out ?? DEFAULT_OUT;
|
|
373
|
+
}
|
|
374
|
+
async function loadPolicy(cwd, config, from) {
|
|
375
|
+
const path = from ?? config.policy;
|
|
376
|
+
if (path === void 0) throw new Error("PermDock CLI: powersync needs policy in the config or --from");
|
|
377
|
+
return asPolicy(pickNamed(await loadModule(resolve(cwd, path)), ["policy"]));
|
|
378
|
+
}
|
|
379
|
+
function sameJson(current, expected) {
|
|
380
|
+
try {
|
|
381
|
+
return JSON.stringify(JSON.parse(current)) === JSON.stringify(JSON.parse(expected));
|
|
382
|
+
} catch {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* The files the policy compiles to: the Sync Streams file and, with
|
|
388
|
+
* `powersync.manifest`, the local-snapshot manifest. `warnings` lists the
|
|
389
|
+
* grants that do not sync.
|
|
390
|
+
*/
|
|
391
|
+
function powersyncFiles(policy, config, out) {
|
|
392
|
+
const plan = powersyncPlan(policy, config);
|
|
393
|
+
const files = [{
|
|
394
|
+
path: powersyncOut(config, out),
|
|
395
|
+
text: powersyncYaml(plan)
|
|
396
|
+
}];
|
|
397
|
+
const manifest = config.powersync?.manifest;
|
|
398
|
+
if (manifest !== void 0) files.push({
|
|
399
|
+
path: manifest,
|
|
400
|
+
json: true,
|
|
401
|
+
text: `${JSON.stringify(localSnapshotManifest(policy), null, 2)}\n`
|
|
402
|
+
});
|
|
403
|
+
return {
|
|
404
|
+
files,
|
|
405
|
+
warnings: plan.warnings
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
/** The files whose content on disk is not what the policy compiles to, with `missing` for an absent one. */
|
|
409
|
+
function staleFiles(cwd, files) {
|
|
410
|
+
const stale = [];
|
|
411
|
+
for (const file of files) {
|
|
412
|
+
const path = resolve(cwd, file.path);
|
|
413
|
+
const current = existsSync(path) ? readFileSync(path, "utf8") : void 0;
|
|
414
|
+
if (!(current !== void 0 && (file.json === true ? sameJson(current, file.text) : current === file.text))) stale.push({
|
|
415
|
+
path: file.path,
|
|
416
|
+
missing: current === void 0
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return stale;
|
|
420
|
+
}
|
|
421
|
+
function rowValue(row, key) {
|
|
422
|
+
const value = key !== void 0 && row !== null && typeof row === "object" && !Array.isArray(row) ? Object.entries(row).find(([name]) => name === key)?.[1] : void 0;
|
|
423
|
+
return value === void 0 || value === null ? void 0 : String(value);
|
|
424
|
+
}
|
|
425
|
+
function claimsJson(fixture) {
|
|
426
|
+
return JSON.stringify(fixture.subject.claims ?? {});
|
|
427
|
+
}
|
|
428
|
+
/** Whether a stream of the user's own rows holds a row of another user. */
|
|
429
|
+
async function leaks(query, stream, user, fixture) {
|
|
430
|
+
for (const text of stream.queries) if ((await query(`select 1 from (${text}) q where q.${quoteIdent(user)}::text is distinct from $1::text and $2::jsonb is not null limit 1`, [fixture.subject.id, claimsJson(fixture)])).length > 0) return true;
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
/** Whether the stream holds the fixture's row for its subject. */
|
|
434
|
+
async function synced(query, stream, fixture) {
|
|
435
|
+
const value = rowValue(fixture.row, stream.id);
|
|
436
|
+
if (value === void 0) return false;
|
|
437
|
+
for (const text of stream.queries) if ((await query(`select 1 from (${text}) q where q.${quoteIdent(stream.id)}::text = $3::text and $1::text is not null and $2::jsonb is not null limit 1`, [
|
|
438
|
+
fixture.subject.id,
|
|
439
|
+
claimsJson(fixture),
|
|
440
|
+
value
|
|
441
|
+
])).length > 0) return true;
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
async function verifyStreams(input) {
|
|
445
|
+
const plan = powersyncPlan(input.policy, input.config, POSTGRES);
|
|
446
|
+
const mismatches = [];
|
|
447
|
+
const notes = [];
|
|
448
|
+
const client = await input.connect(input.db);
|
|
449
|
+
const query = async (sql, values) => (await client.query(sql, values)).rows;
|
|
450
|
+
const root = input.policy.scopes.find((scope) => scope.within === void 0)?.key;
|
|
451
|
+
try {
|
|
452
|
+
const checked = /* @__PURE__ */ new Set();
|
|
453
|
+
for (const fixture of input.fixtures) {
|
|
454
|
+
if (checked.has(fixture.subject.id)) continue;
|
|
455
|
+
checked.add(fixture.subject.id);
|
|
456
|
+
for (const stream of plan.subject) if (stream.user !== void 0 && await leaks(query, stream, stream.user, fixture)) mismatches.push(`stream ${stream.name} syncs another user's ${stream.table} rows to ${fixture.subject.id}`);
|
|
457
|
+
}
|
|
458
|
+
for (const fixture of input.fixtures) {
|
|
459
|
+
const permission = findPermission(input.policy.permissions, fixture.action);
|
|
460
|
+
const stream = plan.streams.find((item) => permission !== void 0 && item.resource === permission.resource && permission.action === (input.config.powersync?.action ?? "read"));
|
|
461
|
+
if (permission === void 0 || stream === void 0) continue;
|
|
462
|
+
const tenant = rowValue(fixture.row, root);
|
|
463
|
+
const permdock = await createPermDock(input.policy, fixtureSubject({
|
|
464
|
+
...fixture.subject,
|
|
465
|
+
...tenant === void 0 ? {} : { tenant }
|
|
466
|
+
}), { customRoles: memoryRoleSource(input.customRoles) });
|
|
467
|
+
const granted = canFixture(permdock, permission, fixtureRow(fixture, permission.kind));
|
|
468
|
+
const holds = await synced(query, stream, fixture);
|
|
469
|
+
if (holds && !granted) mismatches.push(`${fixture.action} for ${fixture.subject.id}: stream ${stream.name} syncs a row the policy denies`);
|
|
470
|
+
else if (granted && !holds) notes.push(`${fixture.action} for ${fixture.subject.id}: granted, not synced (the app reads it from the server)`);
|
|
471
|
+
}
|
|
472
|
+
} finally {
|
|
473
|
+
await client.end();
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
mismatches,
|
|
477
|
+
notes
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
async function runPowerSync(input) {
|
|
481
|
+
const [verb] = input.rest;
|
|
482
|
+
if (verb !== "generate" && verb !== "verify") return {
|
|
483
|
+
code: 2,
|
|
484
|
+
output: POWERSYNC_HELP
|
|
485
|
+
};
|
|
486
|
+
let policy;
|
|
487
|
+
let compiled;
|
|
488
|
+
try {
|
|
489
|
+
policy = await loadPolicy(input.cwd, input.config, input.from);
|
|
490
|
+
compiled = powersyncFiles(policy, input.config, input.out);
|
|
491
|
+
} catch (cause) {
|
|
492
|
+
return usageResult(cause);
|
|
493
|
+
}
|
|
494
|
+
const { files, warnings } = compiled;
|
|
495
|
+
const out = powersyncOut(input.config, input.out);
|
|
496
|
+
const stale = staleFiles(input.cwd, files);
|
|
497
|
+
if (verb === "generate") {
|
|
498
|
+
if (input.check) return stale.length === 0 ? {
|
|
499
|
+
code: 0,
|
|
500
|
+
output: files.map((file) => `${file.path} is current`).join("\n")
|
|
501
|
+
} : {
|
|
502
|
+
code: 1,
|
|
503
|
+
output: stale.map((file) => `${file.path} is stale: run permdock powersync generate`).join("\n")
|
|
504
|
+
};
|
|
505
|
+
for (const file of files) writeFileSync(resolve(input.cwd, file.path), file.text);
|
|
506
|
+
return {
|
|
507
|
+
code: 0,
|
|
508
|
+
output: [...files.map((file) => `wrote ${file.path}`), ...warnings].join("\n")
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
const mismatches = stale.map((file) => `${file.path} ${file.missing ? "is missing" : "differs from the policy"}: run permdock powersync generate`);
|
|
512
|
+
const notes = [...warnings];
|
|
513
|
+
if (input.db !== void 0) try {
|
|
514
|
+
const fixturesPath = input.fixtures ?? input.config.rls?.fixtures ?? "rls.fixtures.json";
|
|
515
|
+
const { fixtures, customRoles } = await loadFixtures(input.cwd, fixturesPath);
|
|
516
|
+
const database = await verifyStreams({
|
|
517
|
+
policy,
|
|
518
|
+
config: input.config,
|
|
519
|
+
fixtures,
|
|
520
|
+
customRoles,
|
|
521
|
+
db: input.db,
|
|
522
|
+
connect: input.connect ?? ((db) => connectPg(db, "permdock powersync verify --db"))
|
|
523
|
+
});
|
|
524
|
+
mismatches.push(...database.mismatches);
|
|
525
|
+
notes.push(...database.notes);
|
|
526
|
+
} catch (cause) {
|
|
527
|
+
return usageResult(cause);
|
|
528
|
+
}
|
|
529
|
+
if (mismatches.length > 0) return {
|
|
530
|
+
code: 1,
|
|
531
|
+
output: [...mismatches, ...notes].join("\n")
|
|
532
|
+
};
|
|
533
|
+
const verified = input.db === void 0 ? `${out} matches the policy` : `${out} matches the policy; no stream syncs a row the fixtures deny`;
|
|
534
|
+
return {
|
|
535
|
+
code: 0,
|
|
536
|
+
output: notes.length === 0 ? verified : `${verified}\n${notes.join("\n")}`
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
//#endregion
|
|
540
|
+
export { runPowerSync as n, staleFiles as r, powersyncFiles as t };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { i as globalArgs, s as stringArg, t as COMMAND_DESCRIPTIONS } from "./commands-DWOrSQic.js";
|
|
2
|
+
import { defineCommand } from "citty";
|
|
3
|
+
//#region src/cli/commands/powersync.ts
|
|
4
|
+
function powersync(ctx) {
|
|
5
|
+
return defineCommand({
|
|
6
|
+
meta: {
|
|
7
|
+
name: "powersync",
|
|
8
|
+
description: COMMAND_DESCRIPTIONS.powersync
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
...globalArgs,
|
|
12
|
+
verb: {
|
|
13
|
+
type: "positional",
|
|
14
|
+
required: false,
|
|
15
|
+
description: "generate or verify"
|
|
16
|
+
},
|
|
17
|
+
out: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "The Sync Streams file (default sync-config.yaml)",
|
|
20
|
+
valueHint: "file"
|
|
21
|
+
},
|
|
22
|
+
check: {
|
|
23
|
+
type: "boolean",
|
|
24
|
+
description: "generate: exit 1 when the file on disk is stale; write nothing"
|
|
25
|
+
},
|
|
26
|
+
db: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "verify: Postgres connection string to run the fixtures against",
|
|
29
|
+
valueHint: "url"
|
|
30
|
+
},
|
|
31
|
+
fixtures: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "verify: fixtures file (default rls.fixtures.json)",
|
|
34
|
+
valueHint: "file"
|
|
35
|
+
},
|
|
36
|
+
from: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "Module exporting policy (default: policy in the config)",
|
|
39
|
+
valueHint: "file"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
async run({ args: parsed }) {
|
|
43
|
+
const out = stringArg(parsed.out);
|
|
44
|
+
const db = stringArg(parsed.db);
|
|
45
|
+
const fixtures = stringArg(parsed.fixtures);
|
|
46
|
+
const from = stringArg(parsed.from);
|
|
47
|
+
const { runPowerSync } = await import("./powersync-A8x_Yyvh.js");
|
|
48
|
+
ctx.report(await runPowerSync({
|
|
49
|
+
cwd: ctx.cwd,
|
|
50
|
+
config: ctx.config,
|
|
51
|
+
rest: parsed._,
|
|
52
|
+
check: parsed.check === true,
|
|
53
|
+
...out === void 0 ? {} : { out },
|
|
54
|
+
...db === void 0 ? {} : { db },
|
|
55
|
+
...fixtures === void 0 ? {} : { fixtures },
|
|
56
|
+
...from === void 0 ? {} : { from }
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { powersync };
|
package/dist/prisma/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Ir as Condition, Pr as Subject, mn as WhereResult } from "../policy-CFZAr_Al.js";
|
|
2
|
+
import { r as RelationsMapping } from "../graph-sql-B-dsAT6j.js";
|
|
3
|
+
import { n as WithSubjectOptions, t as RowCheck } from "../row-check-Dt35czLy.js";
|
|
3
4
|
//#region src/prisma/model-fields.d.ts
|
|
4
5
|
/** A model's non-nullable scalar fields and its scalar list fields, by Prisma field name. */
|
|
5
6
|
type PrismaModelFields = {
|