permdock 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +327 -1
- package/dist/a2a/index.d.ts +169 -0
- package/dist/a2a/index.js +232 -0
- package/dist/adapter-context-mlWsVGGg.js +23 -0
- package/dist/ai-sdk/index.d.ts +95 -0
- package/dist/ai-sdk/index.js +154 -0
- package/dist/approval-required-TzNB9oQA.js +6 -0
- package/dist/approvals/index.d.ts +3 -0
- package/dist/approvals/index.js +5 -0
- package/dist/approvals-DbYAuNkG.js +224 -0
- package/dist/approvers-DT4AVyIg.js +52 -0
- package/dist/arazzo-BAgw9ap6.js +70 -0
- package/dist/arazzo-cmvX2jDe.js +54 -0
- package/dist/authzen/index.d.ts +39 -0
- package/dist/authzen/index.js +302 -0
- package/dist/better-auth/index.d.ts +109 -0
- package/dist/better-auth/index.js +344 -0
- 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/budget-JqcRqyhK.d.ts +11 -0
- package/dist/budget-NbEtbJC_.js +11 -0
- package/dist/canonical-json-DemiK4Vp.js +26 -0
- package/dist/capability-4qPdO9gk.js +173 -0
- package/dist/capability-Dq43tT1u.d.ts +99 -0
- package/dist/catalog/index.d.ts +12 -0
- package/dist/catalog/index.js +2 -0
- package/dist/catalog-BjZSovXQ.js +2 -0
- package/dist/catalog-doc-Dsc9y7sd.js +309 -0
- package/dist/catalog-h2tlKFkq.js +48 -0
- package/dist/catalog-path-CnI0XTFK.js +12 -0
- package/dist/catalog-z8q6bi_r.js +55 -0
- package/dist/claude-agent/index.d.ts +72 -0
- package/dist/claude-agent/index.js +77 -0
- package/dist/clerk/index.d.ts +65 -0
- package/dist/clerk/index.js +301 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +20 -0
- package/dist/cli/index.d.ts +46 -0
- package/dist/cli/index.js +5 -0
- package/dist/clients-C7zLFuWT.d.ts +12 -0
- package/dist/clients-DjU5ABMy.js +23 -0
- package/dist/cloud/index.d.ts +90 -0
- package/dist/cloud/index.js +404 -0
- package/dist/cloud-BGhHS-lY.js +50 -0
- package/dist/cloud-Bo3e9Ak3.js +118 -0
- package/dist/collect-DDDv_ESs.js +502 -0
- package/dist/collect-DLlujdHC.js +83 -0
- package/dist/collect-rG2IQ3t0.js +2 -0
- package/dist/commands-DWOrSQic.js +97 -0
- package/dist/compact-y6vfu2_f.js +19 -0
- package/dist/compare-pfyVnBjE.js +12 -0
- package/dist/compile/index.d.ts +3 -0
- package/dist/compile/index.js +2 -0
- package/dist/compile-Ba4blROR.js +613 -0
- 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-CU8MQxRi.js +194 -0
- package/dist/context-Ttp0ekMT.js +82 -0
- package/dist/convex/index.d.ts +45 -0
- package/dist/convex/index.js +65 -0
- package/dist/create-BWyx3jua.d.ts +245 -0
- package/dist/create-CPhDL6qx.js +253 -0
- package/dist/credential-8P81ff4u.js +306 -0
- package/dist/delegation-D8pUEuce.js +103 -0
- package/dist/describe-B_gVZEVi.js +94 -0
- package/dist/describe-PdoqESvg.d.ts +36 -0
- package/dist/diff-B8Tunc3o.js +451 -0
- package/dist/diff-CzqLxZ77.js +48 -0
- package/dist/digest-B3kc_PgQ.d.ts +19 -0
- package/dist/digest-BI-6aa6d.js +27 -0
- package/dist/doctor-WpvG6p4k.js +1194 -0
- package/dist/doctor-collect-ByfYlNv1.js +664 -0
- package/dist/doctor-rOr-4ZfS.js +53 -0
- package/dist/drizzle/index.d.ts +73 -0
- package/dist/drizzle/index.js +160 -0
- package/dist/duration-xK2jGFVc.js +25 -0
- package/dist/elysia/index.d.ts +49 -0
- package/dist/elysia/index.js +59 -0
- package/dist/errors-CWfoNg9N.js +47 -0
- package/dist/errors-Cz5zn0mW.js +123 -0
- package/dist/errors-DAqevpNk.d.ts +125 -0
- package/dist/errors-DRnGZHzF.js +30 -0
- package/dist/evaluation-items-CfCCejQd.js +40 -0
- package/dist/evaluations-tz6D9A2G.js +461 -0
- package/dist/eve/index.d.ts +86 -0
- package/dist/eve/index.js +176 -0
- package/dist/expiry-BoBJ8Tvc.js +7 -0
- package/dist/express/index.d.ts +23 -0
- package/dist/express/index.js +78 -0
- package/dist/fastify/index.d.ts +33 -0
- package/dist/fastify/index.js +92 -0
- package/dist/fields-BMl1RxoX.js +26 -0
- package/dist/files-DWUN9PzI.js +125 -0
- package/dist/fixtures-ht28Cplr.js +67 -0
- package/dist/freeze-CrU8nQeX.js +60 -0
- package/dist/from-snapshot-C3sHpZNS.js +569 -0
- package/dist/generate-CH7ThJ14.js +173 -0
- package/dist/grantee-Cv5Sogfv.js +602 -0
- package/dist/graph-sql-B-dsAT6j.d.ts +29 -0
- package/dist/headers-BZSDieNT.js +6 -0
- package/dist/headers-Cf-zWvsO.d.ts +6 -0
- package/dist/helpers-H_KARDVC.js +421 -0
- package/dist/hono/index.d.ts +48 -0
- package/dist/hono/index.js +99 -0
- package/dist/hosted-DJQw46hn.js +266 -0
- package/dist/http-BIhmLfCc.js +95 -0
- package/dist/http-DPuDaChb.d.ts +13 -0
- package/dist/http-SO7qGttf.js +44 -0
- package/dist/ids-DC4V4CFu.js +18 -0
- package/dist/index-A0YNL8jZ.d.ts +177 -0
- package/dist/index-CtE07CBF.d.ts +6 -0
- package/dist/index-D3vbkoSI.d.ts +483 -0
- package/dist/index-D6_cSa1z.d.ts +201 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +26 -0
- package/dist/instance-options-BK_rNOSC.d.ts +11 -0
- package/dist/instance-options-Dhhcnm3y.js +22 -0
- package/dist/json-schema-BsAflUGW.js +106 -0
- package/dist/jwt/index.d.ts +91 -0
- package/dist/jwt/index.js +590 -0
- package/dist/kernel-GSQnMz3O.js +208 -0
- package/dist/kysely/index.d.ts +78 -0
- package/dist/kysely/index.js +129 -0
- package/dist/live-options-H2eG8dfJ.js +60 -0
- package/dist/load-DhWEB9P1.js +75 -0
- package/dist/load-DwJUafYN.js +2 -0
- package/dist/load-jose-BL6LH4bL.js +11 -0
- package/dist/lru-8q4WZjBV.js +28 -0
- package/dist/manifest-iaoBXHKc.js +783 -0
- package/dist/manifest-uDS9y5de.d.ts +206 -0
- package/dist/map-AcxK9o_D.js +147 -0
- package/dist/map-claims-Z4QZOiFF.js +369 -0
- package/dist/map-error-pgUWNNtG.js +23 -0
- package/dist/markers-B0hYLvMD.js +60 -0
- package/dist/may-use-DndQ5tQ-.js +77 -0
- package/dist/mcp/index.d.ts +159 -0
- package/dist/mcp/index.js +603 -0
- package/dist/nest/index.d.ts +75 -0
- package/dist/nest/index.js +261 -0
- package/dist/next/client.d.ts +23 -0
- package/dist/next/client.js +33 -0
- package/dist/next/index.d.ts +93 -0
- package/dist/next/index.js +180 -0
- package/dist/next/plugin.d.ts +14 -0
- package/dist/next/plugin.js +33 -0
- package/dist/node/index.d.ts +24 -0
- package/dist/node/index.js +34 -0
- package/dist/openai/index.d.ts +62 -0
- package/dist/openai/index.js +92 -0
- package/dist/openapi/index.d.ts +176 -0
- package/dist/openapi/index.js +3 -0
- package/dist/openapi-Bbg4wGVL.js +164 -0
- package/dist/openapi-BqRBvAng.js +538 -0
- package/dist/openapi-CqqEbHsL.js +250 -0
- package/dist/openapi-import-cmwetbzw.js +5959 -0
- package/dist/openapi-schema--ARjyfQr.js +6902 -0
- package/dist/orpc/index.d.ts +51 -0
- package/dist/orpc/index.js +145 -0
- package/dist/otel/index.d.ts +13 -0
- package/dist/otel/index.js +202 -0
- package/dist/package-root-zGkxO3oN.js +30 -0
- 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 +22 -0
- package/dist/pdp/index.js +682 -0
- package/dist/peer-BOr_kJE1.js +31 -0
- package/dist/permdock-CzqXjCpU.js +3471 -0
- package/dist/permissions-CDgvKG5L.d.ts +52 -0
- package/dist/permissions-CpZrivYv.js +142 -0
- package/dist/permissions-DVoF6dMt.js +662 -0
- package/dist/permissions-DttnqqxC.d.ts +214 -0
- package/dist/pins-8ILTftC9.js +12 -0
- package/dist/policy-CFZAr_Al.d.ts +2252 -0
- package/dist/policy-D8UFyERw.d.ts +20 -0
- package/dist/policy-xyWg7QdR.js +1122 -0
- 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 +136 -0
- package/dist/prisma/index.js +455 -0
- package/dist/problem-B43ztKIm.js +191 -0
- package/dist/problem-details-CkL50EEf.d.ts +7 -0
- package/dist/problem-details-_AMOYz-w.js +144 -0
- package/dist/prompt-CXYhLfsS.js +32 -0
- package/dist/protected-CNsMzrVd.d.ts +27 -0
- package/dist/protected-Cs6kTaHo.js +100 -0
- package/dist/protected-bZs6rbT1.d.ts +40 -0
- package/dist/provider-Cjr62YMV.d.ts +6 -0
- package/dist/provider-DLzpb2Xz.js +52 -0
- package/dist/provider-store-DNyC1y48.js +43 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.js +7 -0
- package/dist/react/provider-client.d.ts +2 -0
- package/dist/react/provider-client.js +3 -0
- package/dist/react-native/index.d.ts +198 -0
- package/dist/react-native/index.js +696 -0
- package/dist/refs-KYb4ntaz.js +91 -0
- package/dist/relations-cjOL01hZ.js +505 -0
- 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/revocations-Cy06JNx2.d.ts +21 -0
- package/dist/rls-BMNydpHk.js +2521 -0
- package/dist/rls-CEvs64Kj.js +238 -0
- package/dist/rls-fields-BMasREhR.js +3502 -0
- package/dist/rls-graph-pc9fuXYm.js +435 -0
- package/dist/rls-import-D8F7j35Q.js +943 -0
- package/dist/rls-migrate-7wikfrVI.js +367 -0
- 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-Dt35czLy.d.ts +40 -0
- package/dist/rpc-DOK54_CS.js +89 -0
- package/dist/run-YXCJDNYk.js +174 -0
- package/dist/runtime-CB8gQFvS.js +11 -0
- package/dist/saas-Bj_FFkjN.js +1203 -0
- package/dist/schema-D3MnDMxa.js +300 -0
- package/dist/scim/index.d.ts +2 -0
- package/dist/scim/index.js +2 -0
- package/dist/scim-BFGBBAqR.js +1445 -0
- package/dist/scopes-CVdPrWN-.js +239 -0
- package/dist/server/index.d.ts +134 -0
- package/dist/server/index.js +11 -0
- package/dist/server-DlZ28Oz5.js +268 -0
- package/dist/sha256-BO0swRXL.js +150 -0
- package/dist/shared-DRu8VFVM.js +123 -0
- package/dist/sink-Bd9PSyfq.js +141 -0
- package/dist/sink-Gs6TTVa-.d.ts +104 -0
- package/dist/skills-Biq5uup3.js +192 -0
- package/dist/skills-JaymNCdK.js +56 -0
- package/dist/snapshot-Enxd0n-s.js +975 -0
- package/dist/snapshot-cache-D6jLelln.js +44 -0
- package/dist/snapshot-cache-DwQH8ufx.d.ts +45 -0
- package/dist/solid/index.d.ts +93 -0
- package/dist/solid/index.js +188 -0
- package/dist/sources-B0UvrJ21.js +534 -0
- package/dist/sources-BcJgw0lt.d.ts +192 -0
- package/dist/sql-xdg5f4Fi.js +27 -0
- package/dist/src-CSbUiozF.js +568 -0
- package/dist/ssf/index.d.ts +4 -0
- package/dist/ssf/index.js +596 -0
- package/dist/store-DMAWwAJV.js +371 -0
- package/dist/store-D_pjn91-.d.ts +36 -0
- package/dist/store-options-yO0V6gRn.js +716 -0
- package/dist/stores-4_sqiOA1.js +87 -0
- package/dist/stores-DVR5NHLE.d.ts +52 -0
- package/dist/stream-DDpvynSB.js +66 -0
- package/dist/stream-DlvG2hs-.d.ts +12 -0
- package/dist/style-CTm1AXDT.js +22 -0
- package/dist/subject-C6dLxqHU.js +24 -0
- package/dist/subject-settings-BwBmCdTR.js +128 -0
- package/dist/supabase/index.d.ts +183 -0
- package/dist/supabase/index.js +5 -0
- package/dist/supabase/middleware.d.ts +82 -0
- package/dist/supabase/middleware.js +102 -0
- package/dist/supabase-CwafwxxF.js +83 -0
- package/dist/supabase-DTBaC20O.js +774 -0
- package/dist/supabase-config-Bs44dare.js +472 -0
- package/dist/supabase-hook-BuxLQLpU.js +1814 -0
- 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 +40 -0
- package/dist/svelte/index.d.ts +13 -0
- package/dist/svelte/index.js +107 -0
- package/dist/svelte/runtime.d.ts +7 -0
- package/dist/svelte/runtime.js +4 -0
- package/dist/svelte/source.d.ts +13 -0
- package/dist/svelte/source.js +11 -0
- package/dist/terminal/index.d.ts +163 -0
- package/dist/terminal/index.js +833 -0
- package/dist/testing/index.d.ts +745 -0
- package/dist/testing/index.js +3672 -0
- package/dist/testing/saas/index.d.ts +194 -0
- package/dist/testing/saas/index.js +3 -0
- package/dist/testing/saas/permissions.d.ts +2 -0
- package/dist/testing/saas/permissions.js +2 -0
- package/dist/text-diff-BdkkYSbi.js +15 -0
- package/dist/thenable-okc2xDsj.js +10 -0
- package/dist/timeout-D51niuQP.js +8 -0
- package/dist/token-CIWxJOCh.js +50 -0
- package/dist/trpc/index.d.ts +61 -0
- package/dist/trpc/index.js +122 -0
- package/dist/types-9AWO9EGt.d.ts +197 -0
- package/dist/types-BPxgszlT.d.ts +550 -0
- package/dist/types-BVyImy5I.d.ts +120 -0
- package/dist/types-Bvc4oWT6.d.ts +9 -0
- package/dist/types-C3s9xVJG.d.ts +48 -0
- package/dist/types-Dnemwd77.d.ts +183 -0
- package/dist/types-kxznc7cn.d.ts +52 -0
- package/dist/types-vKyAMtcX.d.ts +157 -0
- package/dist/types-vNSDF7Dq.d.ts +66 -0
- package/dist/types-yF5LmQVD.d.ts +306 -0
- package/dist/unplugin/index.d.ts +7 -0
- package/dist/unplugin/index.js +31 -0
- package/dist/usage-D07N9QJ3.js +42 -0
- package/dist/usage-D82l1CwK.js +2 -0
- package/dist/usage-DG1oeCca.js +693 -0
- package/dist/validation-error-CnjZ4t92.js +33 -0
- package/dist/validation-error-CtF23SrC.d.ts +26 -0
- package/dist/validity-uOp8UpYn.js +245 -0
- package/dist/verifier-BxBo6I44.js +466 -0
- package/dist/version-BvacVTZR.js +17 -0
- package/dist/views-Soe7bGSC.js +95 -0
- package/dist/vocabulary-DA8a8zif.d.ts +56 -0
- package/dist/vue/index.d.ts +79 -0
- package/dist/vue/index.js +196 -0
- package/dist/watch-Etoa_Mvr.js +2 -0
- package/dist/watch-UihESpsJ.js +128 -0
- package/dist/webmcp/index.d.ts +75 -0
- package/dist/webmcp/index.js +307 -0
- package/package.json +450 -5
- package/rulesets/permdock.yaml +29 -0
- package/schemas/approval-request-v1.json +175 -0
- package/schemas/catalog-v1.json +535 -0
- package/schemas/openapi/oas-3.1.json +1298 -0
- package/schemas/openapi/oas-3.2.json +1516 -0
- package/schemas/openapi/overlay-1.1.json +79 -0
- package/schemas/supabase-claims-v1.json +144 -0
- package/schemas/supabase-manifest-v1.json +361 -0
- package/skills/permdock/SKILL.md +66 -0
- package/skills/permdock-agents/SKILL.md +77 -0
- package/skills/permdock-agents/references/runtimes.md +213 -0
- package/skills/permdock-approvals/SKILL.md +80 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +94 -0
- package/skills/permdock-audit/SKILL.md +67 -0
- package/skills/permdock-credentials/SKILL.md +55 -0
- package/skills/permdock-credentials/references/tokens-keys-links.md +132 -0
- package/skills/permdock-data/SKILL.md +57 -0
- package/skills/permdock-data/references/queries-and-rls.md +62 -0
- package/skills/permdock-data/references/relationships.md +67 -0
- package/skills/permdock-tenancy/SKILL.md +80 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +89 -0
- package/skills/permdock-wire/SKILL.md +116 -0
- package/skills/permdock-wire/references/adapters.md +492 -0
- package/skills/permdock-wire/references/policy-options.md +45 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/core/compact.ts
|
|
2
|
+
function compact(value) {
|
|
3
|
+
const result = {};
|
|
4
|
+
for (const key of Object.keys(value)) {
|
|
5
|
+
const next = value[key];
|
|
6
|
+
if (next !== void 0) result[key] = next;
|
|
7
|
+
}
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
/** `Array.isArray` that keeps `readonly T[]` instead of widening to `any[]`. */
|
|
11
|
+
function isReadonlyArray(value) {
|
|
12
|
+
return Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
/** The element of a one-element list, else `undefined`. */
|
|
15
|
+
function sole(items) {
|
|
16
|
+
return items.length === 1 ? items[0] : void 0;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { isReadonlyArray as n, sole as r, compact as t };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/core/compare.ts
|
|
2
|
+
/**
|
|
3
|
+
* Orders strings by UTF-16 code unit, the same in every locale and runtime,
|
|
4
|
+
* so sorted output is byte-stable where `localeCompare` would follow the ICU
|
|
5
|
+
* build.
|
|
6
|
+
*/
|
|
7
|
+
function byCodePoint(a, b) {
|
|
8
|
+
if (a === b) return 0;
|
|
9
|
+
return a < b ? -1 : 1;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { byCodePoint as t };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { n as GraphSqlPart, r as RelationsMapping, t as GraphSql } from "../graph-sql-B-dsAT6j.js";
|
|
2
|
+
import { a as CompiledWhere, c as compileWhere, i as CompiledSql, l as escapeLike, n as CompiledCompare, o as MembershipTable, r as CompiledExists, s as MembershipsMapping, t as CompileWhereOptions } from "../compile-CNnl6uIt.js";
|
|
3
|
+
export { type CompileWhereOptions, type CompiledCompare, type CompiledExists, type CompiledSql, type CompiledWhere, type GraphSql, type GraphSqlPart, type MembershipTable, type MembershipsMapping, type RelationsMapping, compileWhere, escapeLike };
|
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
import { n as isReadonlyArray, r as sole, t as compact } from "./compact-y6vfu2_f.js";
|
|
2
|
+
import { S as restrictedFor, d as isEdgeRelation, f as isFieldRelation, g as isSelfParented, l as groupEntries, m as isPrincipalRelation, r as expandRelation, w as assertSafeKey } from "./permissions-DVoF6dMt.js";
|
|
3
|
+
import { c as isConditionRef, i as resolveConditionRef, s as isConditionDate, u as parentHop } from "./refs-KYb4ntaz.js";
|
|
4
|
+
import { n as PermDockValidationError } from "./validation-error-CnjZ4t92.js";
|
|
5
|
+
import { t as expiredAt } from "./expiry-BoBJ8Tvc.js";
|
|
6
|
+
import { c as rootScope, d as scopeList, l as scopeChain, m as tenantOf, o as resolveScope, p as subjectMemberships } from "./scopes-CVdPrWN-.js";
|
|
7
|
+
//#region src/conditions/graph-sql.ts
|
|
8
|
+
/** A Postgres identifier, schema-qualified names split on `.`. */
|
|
9
|
+
function quoteSqlName(name) {
|
|
10
|
+
return name.split(".").map((part) => {
|
|
11
|
+
assertSafeKey(part, "sql identifier");
|
|
12
|
+
if (part === "" || part.includes("\0")) throw new Error(`PermDock: unsafe SQL identifier '${name}'`);
|
|
13
|
+
return `"${part.replaceAll("\"", "\"\"")}"`;
|
|
14
|
+
}).join(".");
|
|
15
|
+
}
|
|
16
|
+
function text(value) {
|
|
17
|
+
return { text: value };
|
|
18
|
+
}
|
|
19
|
+
function nextAlias(build, prefix) {
|
|
20
|
+
build.alias += 1;
|
|
21
|
+
return `${prefix}${String(build.alias)}`;
|
|
22
|
+
}
|
|
23
|
+
function nodeOf(build, resource) {
|
|
24
|
+
const node = build.options.resources.get(resource);
|
|
25
|
+
if (node === void 0) throw new Error(`PermDock: graph resource '${resource}' is not declared`);
|
|
26
|
+
return node;
|
|
27
|
+
}
|
|
28
|
+
function tableName(build, name) {
|
|
29
|
+
return quoteSqlName(build.options.qualify?.(name) ?? name);
|
|
30
|
+
}
|
|
31
|
+
function tableOf(build, resource) {
|
|
32
|
+
return tableName(build, build.options.tables?.[resource] ?? resource);
|
|
33
|
+
}
|
|
34
|
+
function col(alias, name) {
|
|
35
|
+
return `${alias}.${quoteSqlName(name)}`;
|
|
36
|
+
}
|
|
37
|
+
function notExpired(column) {
|
|
38
|
+
return `(${column} is null or ${column} > now())`;
|
|
39
|
+
}
|
|
40
|
+
function edgeRowFilter(spec, alias) {
|
|
41
|
+
const parts = [];
|
|
42
|
+
for (const [column, value] of Object.entries(spec.match ?? {})) parts.push(text(` and ${col(alias, column)} = `), { value });
|
|
43
|
+
if (spec.expiresAt !== void 0) parts.push(text(` and ${notExpired(col(alias, spec.expiresAt))}`));
|
|
44
|
+
return parts;
|
|
45
|
+
}
|
|
46
|
+
function groupHolders(build, resource, relation) {
|
|
47
|
+
return build.options.holders?.(resource, relation) ?? holderIds(build, resource, relation);
|
|
48
|
+
}
|
|
49
|
+
/** Who a row of the edge names, when it is not a group of the edge's own resource. */
|
|
50
|
+
function edgeHolderFilter(build, node, spec, alias) {
|
|
51
|
+
const holder = col(alias, spec.subject ?? "user_id");
|
|
52
|
+
const groups = spec.groups;
|
|
53
|
+
if (groups === void 0) return [text(`${holder} = `), { subject: true }];
|
|
54
|
+
const kind = groups.column === void 0 ? void 0 : col(alias, groups.column);
|
|
55
|
+
const direct = kind === void 0 ? [
|
|
56
|
+
text(`(${holder} = `),
|
|
57
|
+
{ subject: true },
|
|
58
|
+
text(")")
|
|
59
|
+
] : [
|
|
60
|
+
text("(("),
|
|
61
|
+
...groups.direct === void 0 ? [text(`${kind} is null`)] : [text(`${kind} is null or ${kind} = `), { value: groups.direct }],
|
|
62
|
+
text(`) and ${holder} = `),
|
|
63
|
+
{ subject: true },
|
|
64
|
+
text(")")
|
|
65
|
+
];
|
|
66
|
+
const parts = [text("("), ...direct];
|
|
67
|
+
for (const group of groupEntries(spec)) {
|
|
68
|
+
if (group.resource === node.name) continue;
|
|
69
|
+
const id = `${col(alias, group.subject)}::text in (`;
|
|
70
|
+
parts.push(...kind === void 0 ? [text(` or (${id}`)] : [
|
|
71
|
+
text(` or (${kind} = `),
|
|
72
|
+
{ value: group.resource },
|
|
73
|
+
text(` and ${id}`)
|
|
74
|
+
], ...groupHolders(build, group.resource, group.relation), text("))"));
|
|
75
|
+
}
|
|
76
|
+
parts.push(text(")"));
|
|
77
|
+
return parts;
|
|
78
|
+
}
|
|
79
|
+
/** The nested-group step of an edge whose groups include its own resource: the row names a group of `node`. */
|
|
80
|
+
function selfGroupWhere(spec, node, subject, alias) {
|
|
81
|
+
const column = spec.groups?.column;
|
|
82
|
+
return column === void 0 ? [text(`${subject} is not null`)] : [text(`${col(alias, column)} = `), { value: node.name }];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* One concrete relation's arm: `select <id>::text as id ...` for every
|
|
86
|
+
* instance of `node` the subject holds `relation` on, through groups too.
|
|
87
|
+
*/
|
|
88
|
+
function relationArm(build, node, relation) {
|
|
89
|
+
const spec = node.relations[relation];
|
|
90
|
+
if (isEdgeRelation(spec)) {
|
|
91
|
+
const edge = tableName(build, spec.edge);
|
|
92
|
+
const object = spec.object ?? `${node.name}_id`;
|
|
93
|
+
const e = nextAlias(build, "e");
|
|
94
|
+
const base = [
|
|
95
|
+
text(`select ${col(e, object)}::text as id from ${edge} ${e} where `),
|
|
96
|
+
...edgeHolderFilter(build, node, spec, e),
|
|
97
|
+
...edgeRowFilter(spec, e)
|
|
98
|
+
];
|
|
99
|
+
const self = groupEntries(spec).find((group) => group.resource === node.name);
|
|
100
|
+
if (self === void 0) return base;
|
|
101
|
+
const g = nextAlias(build, "g");
|
|
102
|
+
const n = nextAlias(build, "e");
|
|
103
|
+
const subject = col(n, self.subject);
|
|
104
|
+
return [
|
|
105
|
+
text(`select ${g}.id from (with recursive ${g}(id, level) as (select b.id, 0 from (`),
|
|
106
|
+
...base,
|
|
107
|
+
text(`) b union select ${col(n, object)}::text, ${g}.level + 1 from ${edge} ${n} join ${g} on ${subject}::text = ${g}.id where `),
|
|
108
|
+
...selfGroupWhere(spec, node, subject, n),
|
|
109
|
+
text(` and ${g}.level < ${String(16)}`),
|
|
110
|
+
...edgeRowFilter(spec, n),
|
|
111
|
+
text(`) select ${g}.id from ${g}) ${g}`)
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
const table = tableOf(build, node.name);
|
|
115
|
+
const t = nextAlias(build, "t");
|
|
116
|
+
const id = `${col(t, node.id)}::text`;
|
|
117
|
+
if (isPrincipalRelation(spec)) {
|
|
118
|
+
const parts = [text(`select ${id} as id from ${table} ${t} where ${col(t, spec.principal)} = `), { subject: true }];
|
|
119
|
+
const startsAt = spec.period?.startsAt;
|
|
120
|
+
if (startsAt !== void 0) {
|
|
121
|
+
const column = col(t, startsAt);
|
|
122
|
+
parts.push(text(` and (${column} is null or ${column} <= now())`));
|
|
123
|
+
}
|
|
124
|
+
const expiresAt = spec.period?.expiresAt;
|
|
125
|
+
if (expiresAt !== void 0) parts.push(text(` and ${notExpired(col(t, expiresAt))}`));
|
|
126
|
+
return parts;
|
|
127
|
+
}
|
|
128
|
+
if (isFieldRelation(spec) && spec.memberOf === void 0) return [text(`select ${id} as id from ${table} ${t} where ${col(t, spec.field)} = `), { subject: true }];
|
|
129
|
+
throw new Error(`PermDock: relation '${relation}' on ${node.name} has no graph SQL form`);
|
|
130
|
+
}
|
|
131
|
+
function holderIds(build, resource, relation) {
|
|
132
|
+
const node = nodeOf(build, resource);
|
|
133
|
+
const arms = expandRelation(node, relation).map((name) => relationArm(build, node, name));
|
|
134
|
+
if (arms.length === 0) return [text("select null::text as id where false")];
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const [index, arm] of arms.entries()) {
|
|
137
|
+
if (index > 0) out.push(text(" union "));
|
|
138
|
+
out.push(...arm);
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
/** One concrete relation's arm, for helpers that guard each arm by the relation asked for. */
|
|
143
|
+
function relationArmSql(resource, relation, options) {
|
|
144
|
+
const build = {
|
|
145
|
+
options,
|
|
146
|
+
alias: 0
|
|
147
|
+
};
|
|
148
|
+
return relationArm(build, nodeOf(build, resource), relation);
|
|
149
|
+
}
|
|
150
|
+
function heldIds(build, condition) {
|
|
151
|
+
if (condition.permission !== void 0) {
|
|
152
|
+
const permitted = build.options.permitted;
|
|
153
|
+
if (permitted === void 0) throw new Error(`PermDock: inherit(${condition.permission}) has no query form outside generated RLS`);
|
|
154
|
+
return permitted(condition.resource, condition.permission);
|
|
155
|
+
}
|
|
156
|
+
if (condition.ids === void 0) return groupHolders(build, condition.resource, condition.relation);
|
|
157
|
+
if (condition.ids.length === 0) return [text("select null::text as id where false")];
|
|
158
|
+
const parts = [text("select v.id from (values ")];
|
|
159
|
+
for (const [index, id] of condition.ids.entries()) parts.push(text(index === 0 ? "(" : ", ("), { value: id }, text("::text)"));
|
|
160
|
+
parts.push(text(") as v(id)"));
|
|
161
|
+
return parts;
|
|
162
|
+
}
|
|
163
|
+
/** Instances of the condition's resource at or below the held ones, within its depth and never through a restricted child. */
|
|
164
|
+
function reached(build, condition) {
|
|
165
|
+
const held = heldIds(build, condition);
|
|
166
|
+
if (condition.depth === 0) return held;
|
|
167
|
+
const node = nodeOf(build, condition.resource);
|
|
168
|
+
if (!isSelfParented(node) || node.parent === void 0) throw new Error(`PermDock: '${condition.resource}' is not self-parented, so a related walk of depth ${String(condition.depth)} has no SQL form`);
|
|
169
|
+
const { closure, closureDepths } = build.options;
|
|
170
|
+
if (closure !== void 0 && (closureDepths === void 0 || closureDepths[condition.resource] !== void 0)) {
|
|
171
|
+
const c = nextAlias(build, "c");
|
|
172
|
+
const cap = closureDepths?.[condition.resource];
|
|
173
|
+
const depth = cap !== void 0 && condition.depth >= cap ? "" : ` and ${c}.depth <= ${String(condition.depth)}`;
|
|
174
|
+
return [
|
|
175
|
+
text(`select ${c}.descendant as id from ${quoteSqlName(closure)} ${c} where ${c}.resource = `),
|
|
176
|
+
{ value: condition.resource },
|
|
177
|
+
text(`${depth} and ${c}.ancestor in (`),
|
|
178
|
+
...held,
|
|
179
|
+
text(")")
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
const d = nextAlias(build, "d");
|
|
183
|
+
const t = nextAlias(build, "t");
|
|
184
|
+
const stop = restrictedFor(node, "parent");
|
|
185
|
+
const restricted = stop === void 0 ? "" : ` and ${col(t, stop)} is not true`;
|
|
186
|
+
return [
|
|
187
|
+
text(`select ${d}.id from (with recursive ${d}(id, level) as (select h.id, 0 from (`),
|
|
188
|
+
...held,
|
|
189
|
+
text(`) h union select ${col(t, node.id)}::text, ${d}.level + 1 from ${tableOf(build, node.name)} ${t} join ${d} on ${col(t, node.parent.field)}::text = ${d}.id where ${d}.level < ${String(condition.depth)}${restricted}) select ${d}.id from ${d}) ${d}`)
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
/** Ids the row's `field` may hold: the reached instances, carried back over each link hop. */
|
|
193
|
+
function targets(build, condition) {
|
|
194
|
+
let set = reached(build, condition);
|
|
195
|
+
const hops = condition.hops ?? [];
|
|
196
|
+
for (let index = hops.length - 1; index >= 1; index -= 1) {
|
|
197
|
+
const from = hops[index - 1];
|
|
198
|
+
const hop = hops[index];
|
|
199
|
+
if (from === void 0 || hop === void 0)
|
|
200
|
+
/* v8 ignore next */
|
|
201
|
+
break;
|
|
202
|
+
const custom = build.options.linked?.(from.resource, hop.link, set);
|
|
203
|
+
if (custom !== void 0) {
|
|
204
|
+
set = custom;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const node = nodeOf(build, from.resource);
|
|
208
|
+
const link = Object.hasOwn(node.links, hop.link) ? node.links[hop.link] : void 0;
|
|
209
|
+
if (link === void 0) throw new Error(`PermDock: '${from.resource}' has no link '${hop.link}'`);
|
|
210
|
+
const t = nextAlias(build, "t");
|
|
211
|
+
set = [
|
|
212
|
+
text(`select ${col(t, node.id)}::text as id from ${tableOf(build, node.name)} ${t} where ${col(t, link.field)}::text in (`),
|
|
213
|
+
...set,
|
|
214
|
+
text(")")
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
return set;
|
|
218
|
+
}
|
|
219
|
+
/** The ids the row's field may hold for `condition` to match, as one text column `id`. */
|
|
220
|
+
function relatedTargetsSql(condition, options) {
|
|
221
|
+
return targets({
|
|
222
|
+
options,
|
|
223
|
+
alias: 0
|
|
224
|
+
}, condition);
|
|
225
|
+
}
|
|
226
|
+
function closedRows(build, above) {
|
|
227
|
+
const custom = build.options.restrictedRows?.(above.resource);
|
|
228
|
+
if (custom !== void 0) return custom;
|
|
229
|
+
const table = tableOf(build, nodeOf(build, above.resource).name);
|
|
230
|
+
const span = above.depth - 1;
|
|
231
|
+
const { closure, closureDepths } = build.options;
|
|
232
|
+
const cap = closureDepths?.[above.resource];
|
|
233
|
+
if (closure !== void 0 && (closureDepths === void 0 || cap !== void 0 && cap >= span)) {
|
|
234
|
+
const c = nextAlias(build, "c");
|
|
235
|
+
const a = nextAlias(build, "a");
|
|
236
|
+
const q = nextAlias(build, "q");
|
|
237
|
+
return [
|
|
238
|
+
text(`select ${c}.descendant as id from ${quoteSqlName(closure)} ${c} join ${table} ${a} on ${col(a, above.id)}::text = ${c}.ancestor where ${c}.resource = `),
|
|
239
|
+
{ value: above.resource },
|
|
240
|
+
text(` and ${c}.depth <= ${String(span)} and (${col(a, above.field)} is true or (${c}.depth = ${String(span)} and exists (select 1 from ${table} ${q} where ${col(q, above.id)} = ${col(a, above.parent)})))`)
|
|
241
|
+
];
|
|
242
|
+
}
|
|
243
|
+
const d = nextAlias(build, "d");
|
|
244
|
+
const r = nextAlias(build, "r");
|
|
245
|
+
const t = nextAlias(build, "t");
|
|
246
|
+
const u = nextAlias(build, "t");
|
|
247
|
+
const v = nextAlias(build, "t");
|
|
248
|
+
const w = nextAlias(build, "t");
|
|
249
|
+
const q = nextAlias(build, "q");
|
|
250
|
+
return [text(`select ${d}.id from (with recursive ${d}(id, level) as (select ${col(t, above.id)}::text, 0 from ${table} ${t} where ${col(t, above.field)} is true union select ${col(u, above.id)}::text, ${d}.level + 1 from ${table} ${u} join ${d} on ${col(u, above.parent)}::text = ${d}.id where ${d}.level < ${String(span)}) select ${d}.id from ${d}) ${d} union select ${col(w, above.id)}::text from ${table} ${w} where ${col(w, above.id)}::text not in (select ${r}.id from (with recursive ${r}(id, level) as (select ${col(v, above.id)}::text, 0 from ${table} ${v} where ${col(v, above.parent)} is null or not exists (select 1 from ${table} ${q} where ${col(q, above.id)} = ${col(v, above.parent)}) union select ${col(t, above.id)}::text, ${r}.level + 1 from ${table} ${t} join ${r} on ${col(t, above.parent)}::text = ${r}.id where ${r}.level < ${String(span)}) select ${r}.id from ${r}) ${r})`)];
|
|
251
|
+
}
|
|
252
|
+
function restrictedRowsSql(above, options) {
|
|
253
|
+
return closedRows({
|
|
254
|
+
options,
|
|
255
|
+
alias: 0
|
|
256
|
+
}, above);
|
|
257
|
+
}
|
|
258
|
+
/** Whether the row's restricted column keeps it out of `condition` whatever the graph says. */
|
|
259
|
+
function relatedRowGuard(condition) {
|
|
260
|
+
const hopped = condition.hops !== void 0 && condition.hops.length > 0;
|
|
261
|
+
return (condition.parent === true || hopped) && condition.restricted !== void 0 ? condition.restricted : void 0;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* `condition` as a boolean over the filtered row that is never NULL: the
|
|
265
|
+
* row's field is among the reached ids, and with `parent` or `hops` the row
|
|
266
|
+
* is not restricted.
|
|
267
|
+
*/
|
|
268
|
+
function relatedSql(condition, options) {
|
|
269
|
+
const build = {
|
|
270
|
+
options,
|
|
271
|
+
alias: 0
|
|
272
|
+
};
|
|
273
|
+
const parts = [
|
|
274
|
+
text("coalesce("),
|
|
275
|
+
{ column: condition.field },
|
|
276
|
+
text("::text in ("),
|
|
277
|
+
...targets(build, condition),
|
|
278
|
+
text("), false)")
|
|
279
|
+
];
|
|
280
|
+
const guard = relatedRowGuard(condition);
|
|
281
|
+
const above = condition.restrictedAncestors;
|
|
282
|
+
if (guard === void 0 && above === void 0) return parts;
|
|
283
|
+
const out = [text("("), ...parts];
|
|
284
|
+
if (guard !== void 0) out.push(text(" and "), { column: guard }, text(" is not true"));
|
|
285
|
+
if (above !== void 0) out.push(text(" and ("), { column: above.parent }, text(" is null or "), { column: above.parent }, text("::text not in ("), ...closedRows(build, above), text("))"));
|
|
286
|
+
out.push(text(")"));
|
|
287
|
+
return out;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Renders parts to one SQL string: `placeholder(n)` names the n-th bound
|
|
291
|
+
* value (from 1), `column` names a row column, and the subject id binds as
|
|
292
|
+
* a value.
|
|
293
|
+
*/
|
|
294
|
+
function renderGraphSql(parts, render) {
|
|
295
|
+
let sql = "";
|
|
296
|
+
const values = [];
|
|
297
|
+
for (const part of parts) if ("text" in part) sql += part.text;
|
|
298
|
+
else if ("column" in part) sql += render.column(part.column);
|
|
299
|
+
else {
|
|
300
|
+
values.push("subject" in part ? render.subject : part.value);
|
|
301
|
+
sql += render.placeholder(values.length);
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
sql,
|
|
305
|
+
values
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/conditions/compile.ts
|
|
310
|
+
/** The membership table mapped for scope `name`, through the `tenant` / `team` shorthands. */
|
|
311
|
+
function scopeMembershipTable(mapping, scopes, name) {
|
|
312
|
+
const table = mapping?.scopes?.[name];
|
|
313
|
+
if (table !== void 0) return table;
|
|
314
|
+
if (name === scopes[0]?.name) return mapping?.tenant;
|
|
315
|
+
if (name === scopes[1]?.name) return mapping?.team;
|
|
316
|
+
}
|
|
317
|
+
/** The column of `table` holding scope `name`'s id, through the `tenant` / `team` shorthands. */
|
|
318
|
+
function scopeColumn(table, scopes, name) {
|
|
319
|
+
const column = table.columns?.[name];
|
|
320
|
+
if (column !== void 0) return column;
|
|
321
|
+
if (name === scopes[0]?.name) return table.tenant;
|
|
322
|
+
if (name === scopes[1]?.name) return table.team;
|
|
323
|
+
}
|
|
324
|
+
const NEVER = { kind: "never" };
|
|
325
|
+
const ALWAYS = { kind: "always" };
|
|
326
|
+
function isScalar(value) {
|
|
327
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value instanceof Date;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* The SQL-bound value of a condition operand. A ref the subject does not
|
|
331
|
+
* resolve, or resolves to an object, becomes `null`: the in-memory evaluator
|
|
332
|
+
* never matches it, so the compiled form must not either.
|
|
333
|
+
*/
|
|
334
|
+
function unwrap(value, subject) {
|
|
335
|
+
if (isConditionRef(value)) {
|
|
336
|
+
const resolved = resolveConditionRef(value.ref, subject);
|
|
337
|
+
if (Array.isArray(resolved)) return resolved.filter((item) => isScalar(item));
|
|
338
|
+
return isScalar(resolved) ? resolved : null;
|
|
339
|
+
}
|
|
340
|
+
if (isConditionDate(value)) {
|
|
341
|
+
const date = new Date(value.date);
|
|
342
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
343
|
+
}
|
|
344
|
+
if (isReadonlyArray(value)) return value.map((item) => unwrap(item, subject));
|
|
345
|
+
return value;
|
|
346
|
+
}
|
|
347
|
+
function nonPortable(detail) {
|
|
348
|
+
return new PermDockValidationError({
|
|
349
|
+
code: "non-portable-condition",
|
|
350
|
+
permission: "",
|
|
351
|
+
resource: "",
|
|
352
|
+
boundary: "where",
|
|
353
|
+
message: `PermDock: non-portable-condition: ${detail}`
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function asPortableCondition(input) {
|
|
357
|
+
if ("partial" in input && input.partial) throw nonPortable("closure grant");
|
|
358
|
+
if ("condition" in input && !("op" in input)) return input.condition;
|
|
359
|
+
return input;
|
|
360
|
+
}
|
|
361
|
+
/** A `contains` pattern for `LIKE`: `%`, `_` and the escape itself match literally. */
|
|
362
|
+
function escapeLike(value) {
|
|
363
|
+
return value.replaceAll(/[\\%_]/gu, (char) => `\\${char}`);
|
|
364
|
+
}
|
|
365
|
+
function allOf(items) {
|
|
366
|
+
if (items.some((item) => item.kind === "never")) return NEVER;
|
|
367
|
+
const kept = items.filter((item) => item.kind !== "always");
|
|
368
|
+
if (kept.length === 0) return ALWAYS;
|
|
369
|
+
return sole(kept) ?? {
|
|
370
|
+
kind: "and",
|
|
371
|
+
items: kept
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function anyOf(items) {
|
|
375
|
+
if (items.some((item) => item.kind === "always")) return ALWAYS;
|
|
376
|
+
const kept = items.filter((item) => item.kind !== "never");
|
|
377
|
+
if (kept.length === 0) return NEVER;
|
|
378
|
+
return sole(kept) ?? {
|
|
379
|
+
kind: "or",
|
|
380
|
+
items: kept
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Negation normal form. In memory a comparison against a NULL field is
|
|
385
|
+
* false, so its negation is true; SQL's `NOT (NULL)` stays NULL. Every
|
|
386
|
+
* negated comparison therefore also admits the NULL field explicitly, and
|
|
387
|
+
* `not` only ever wraps a comparison or an `exists`.
|
|
388
|
+
*/
|
|
389
|
+
function negate(node) {
|
|
390
|
+
switch (node.kind) {
|
|
391
|
+
case "never": return ALWAYS;
|
|
392
|
+
case "always": return NEVER;
|
|
393
|
+
case "and": return anyOf(node.items.map((item) => negate(item)));
|
|
394
|
+
case "or": return allOf(node.items.map((item) => negate(item)));
|
|
395
|
+
case "not": return node.item;
|
|
396
|
+
case "isNull": return {
|
|
397
|
+
...node,
|
|
398
|
+
negated: !node.negated
|
|
399
|
+
};
|
|
400
|
+
case "compare": return anyOf([{
|
|
401
|
+
kind: "not",
|
|
402
|
+
item: node
|
|
403
|
+
}, {
|
|
404
|
+
kind: "isNull",
|
|
405
|
+
field: node.field,
|
|
406
|
+
negated: false
|
|
407
|
+
}]);
|
|
408
|
+
case "exists":
|
|
409
|
+
case "sql": return {
|
|
410
|
+
kind: "not",
|
|
411
|
+
item: node
|
|
412
|
+
};
|
|
413
|
+
default: throw nonPortable(`unknown compiled node '${String(node)}'`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function matchingMemberships(condition, subject, now, scopes) {
|
|
417
|
+
const memberships = subjectMemberships(subject?.principal?.memberships, scopes);
|
|
418
|
+
const wanted = new Set(condition.roles);
|
|
419
|
+
return memberships.filter((membership) => {
|
|
420
|
+
if (expiredAt(membership, now)) return false;
|
|
421
|
+
if (wanted.size === 0) return true;
|
|
422
|
+
return membership.roles.some((role) => wanted.has(role));
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
function inList(field, values) {
|
|
426
|
+
const unique = [...new Set(values.filter((value) => value !== null && value !== void 0))];
|
|
427
|
+
if (unique.length === 0) return NEVER;
|
|
428
|
+
if (unique.length === 1) return {
|
|
429
|
+
kind: "compare",
|
|
430
|
+
op: "eq",
|
|
431
|
+
field,
|
|
432
|
+
value: unique[0]
|
|
433
|
+
};
|
|
434
|
+
return {
|
|
435
|
+
kind: "compare",
|
|
436
|
+
op: "in",
|
|
437
|
+
field,
|
|
438
|
+
value: unique
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function activeTenant(subject) {
|
|
442
|
+
const tenant = subject?.principal?.tenant;
|
|
443
|
+
return tenant === void 0 || tenant === "" ? void 0 : tenant;
|
|
444
|
+
}
|
|
445
|
+
function existsOn(table, subject, roles, rowColumn, rowField, scoped) {
|
|
446
|
+
assertSafeKey(table.table, "membership table");
|
|
447
|
+
assertSafeKey(rowColumn, "membership column");
|
|
448
|
+
assertSafeKey(rowField, "condition field");
|
|
449
|
+
const tenantValue = scoped.tenantColumn === void 0 ? void 0 : activeTenant(subject);
|
|
450
|
+
return compact({
|
|
451
|
+
kind: "exists",
|
|
452
|
+
table: table.table,
|
|
453
|
+
user: table.user,
|
|
454
|
+
userValue: subject.principal?.id ?? "",
|
|
455
|
+
role: table.role,
|
|
456
|
+
roles,
|
|
457
|
+
rowColumn,
|
|
458
|
+
rowField,
|
|
459
|
+
expiresAt: table.expiresAt,
|
|
460
|
+
now: scoped.now,
|
|
461
|
+
tenantColumn: tenantValue === void 0 ? void 0 : scoped.tenantColumn,
|
|
462
|
+
tenantValue,
|
|
463
|
+
resourceColumn: scoped.resource === void 0 ? void 0 : table.resource,
|
|
464
|
+
resourceValue: table.resource === void 0 ? void 0 : scoped.resource
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
function compileExists(condition, table, subject, now, mappings, scopes) {
|
|
468
|
+
const userValue = subject?.principal?.id;
|
|
469
|
+
if (subject === void 0 || typeof userValue !== "string" || userValue === "") return NEVER;
|
|
470
|
+
const scope = condition.scope === "resource" ? void 0 : resolveScope(scopes, condition.scope);
|
|
471
|
+
const rowColumn = scope === void 0 ? table.id : scopeColumn(table, scopes, scope);
|
|
472
|
+
if (rowColumn === void 0) return NEVER;
|
|
473
|
+
const root = rootScope(scopes);
|
|
474
|
+
const own = existsOn(table, subject, condition.roles, rowColumn, condition.field, {
|
|
475
|
+
now,
|
|
476
|
+
tenantColumn: scope !== void 0 && root !== void 0 && scopeChain(scopes, scope).includes(root) ? scopeColumn(table, scopes, root) : void 0,
|
|
477
|
+
resource: condition.scope === "resource" ? condition.resource : void 0
|
|
478
|
+
});
|
|
479
|
+
if (condition.scope !== "resource") return own;
|
|
480
|
+
return anyOf([own, ...(condition.parents ?? []).map((parent) => {
|
|
481
|
+
const hop = parentHop(parent);
|
|
482
|
+
if (hop.resource === void 0) return existsOn(table, subject, condition.roles, rowColumn, hop.field, {
|
|
483
|
+
now,
|
|
484
|
+
tenantColumn: void 0,
|
|
485
|
+
resource: void 0
|
|
486
|
+
});
|
|
487
|
+
const hopTable = mappings?.resource?.[hop.resource];
|
|
488
|
+
if (hopTable?.id === void 0) return NEVER;
|
|
489
|
+
return existsOn(hopTable, subject, condition.roles, hopTable.id, hop.field, {
|
|
490
|
+
now,
|
|
491
|
+
tenantColumn: void 0,
|
|
492
|
+
resource: hop.resource
|
|
493
|
+
});
|
|
494
|
+
})]);
|
|
495
|
+
}
|
|
496
|
+
function compileMemberOf(condition, options) {
|
|
497
|
+
assertSafeKey(condition.field, "condition field");
|
|
498
|
+
const now = options.now ?? Date.now() / 1e3;
|
|
499
|
+
const scopes = options.scopes ?? scopeList(void 0);
|
|
500
|
+
const scope = condition.scope === "resource" ? void 0 : resolveScope(scopes, condition.scope);
|
|
501
|
+
if (condition.scope !== "resource" && scope === void 0) return NEVER;
|
|
502
|
+
const mapping = scope === void 0 ? condition.resource === void 0 ? void 0 : options.memberships?.resource?.[condition.resource] : scopeMembershipTable(options.memberships, scopes, scope);
|
|
503
|
+
if (mapping !== void 0) return compileExists(condition, mapping, options.subject, Math.ceil(now), options.memberships, scopes);
|
|
504
|
+
const matched = matchingMemberships(condition, options.subject, now, scopes);
|
|
505
|
+
const active = activeTenant(options.subject);
|
|
506
|
+
if (scope !== void 0) return inList(condition.field, matched.flatMap((membership) => {
|
|
507
|
+
if (membership.scope !== scope || membership.id === void 0) return [];
|
|
508
|
+
const tenant = tenantOf(membership, scopes);
|
|
509
|
+
return active !== void 0 && tenant !== void 0 && tenant !== active ? [] : [membership.id];
|
|
510
|
+
}));
|
|
511
|
+
const ids = matched.flatMap((membership) => {
|
|
512
|
+
if (membership.on === void 0) return [];
|
|
513
|
+
if (condition.resource !== void 0 && membership.on.resource !== condition.resource) return [];
|
|
514
|
+
return [membership.on.id];
|
|
515
|
+
});
|
|
516
|
+
return anyOf([inList(condition.field, ids), ...(condition.parents ?? []).map((parent) => {
|
|
517
|
+
const hop = parentHop(parent);
|
|
518
|
+
assertSafeKey(hop.field, "condition field");
|
|
519
|
+
return inList(hop.field, matched.flatMap((membership) => membership.on === void 0 || hop.resource !== void 0 && membership.on.resource !== hop.resource ? [] : [membership.on.id]));
|
|
520
|
+
})]);
|
|
521
|
+
}
|
|
522
|
+
function compileCompare(condition, options) {
|
|
523
|
+
if (condition.field === "_" && condition.op === "eq") return condition.value === true ? ALWAYS : NEVER;
|
|
524
|
+
assertSafeKey(condition.field, "condition field");
|
|
525
|
+
const value = unwrap(condition.value, options.subject);
|
|
526
|
+
if (condition.op === "in" || condition.op === "notIn") {
|
|
527
|
+
const list = Array.isArray(value) ? [...new Set(value.filter((item) => item !== null && item !== void 0))] : [];
|
|
528
|
+
if (list.length === 0) return condition.op === "in" ? NEVER : {
|
|
529
|
+
kind: "isNull",
|
|
530
|
+
field: condition.field,
|
|
531
|
+
negated: true
|
|
532
|
+
};
|
|
533
|
+
return {
|
|
534
|
+
kind: "compare",
|
|
535
|
+
op: condition.op,
|
|
536
|
+
field: condition.field,
|
|
537
|
+
value: list
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
if (value === null || value === void 0) return NEVER;
|
|
541
|
+
return {
|
|
542
|
+
kind: "compare",
|
|
543
|
+
op: condition.op,
|
|
544
|
+
field: condition.field,
|
|
545
|
+
value
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function compileRelated(condition, options) {
|
|
549
|
+
if (options.relations === void 0 || options.resources === void 0) throw nonPortable("related (the relation graph): pass `relations` so it compiles to a subquery, or resolve it to ids first (permdock/prisma resolveRelated)");
|
|
550
|
+
assertSafeKey(condition.field, "condition field");
|
|
551
|
+
const principal = options.subject?.principal?.id;
|
|
552
|
+
if (condition.ids === void 0 && (principal === void 0 || principal === "")) return NEVER;
|
|
553
|
+
return {
|
|
554
|
+
kind: "sql",
|
|
555
|
+
parts: relatedSql(condition, {
|
|
556
|
+
...options.relations,
|
|
557
|
+
resources: options.resources
|
|
558
|
+
}),
|
|
559
|
+
subject: principal ?? ""
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function compileNode(condition, options) {
|
|
563
|
+
switch (condition.op) {
|
|
564
|
+
case "and": return allOf(condition.conditions.map((child) => compileNode(child, options)));
|
|
565
|
+
case "or": return anyOf(condition.conditions.map((child) => compileNode(child, options)));
|
|
566
|
+
case "not": return negate(compileNode(condition.condition, options));
|
|
567
|
+
case "isNull":
|
|
568
|
+
assertSafeKey(condition.field, "condition field");
|
|
569
|
+
return {
|
|
570
|
+
kind: "isNull",
|
|
571
|
+
field: condition.field,
|
|
572
|
+
negated: !condition.value
|
|
573
|
+
};
|
|
574
|
+
case "opaque": throw nonPortable("opaque SQL");
|
|
575
|
+
case "sqlFunction": return compileNode(condition.twin, options);
|
|
576
|
+
case "memberOf": return compileMemberOf(condition, options);
|
|
577
|
+
case "related": return compileRelated(condition, options);
|
|
578
|
+
case "liveSession": return options.subject?.liveSession === true ? ALWAYS : NEVER;
|
|
579
|
+
case "eq":
|
|
580
|
+
case "ne":
|
|
581
|
+
case "gt":
|
|
582
|
+
case "gte":
|
|
583
|
+
case "lt":
|
|
584
|
+
case "lte":
|
|
585
|
+
case "contains":
|
|
586
|
+
case "in":
|
|
587
|
+
case "notIn": return compileCompare(condition, options);
|
|
588
|
+
default: throw nonPortable("unknown condition");
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/** The subject `permdock.where()` was built for, when the input carries one. */
|
|
592
|
+
function whereSubject(input) {
|
|
593
|
+
return "partial" in input ? input.subject : void 0;
|
|
594
|
+
}
|
|
595
|
+
function whereScopes(input) {
|
|
596
|
+
return "partial" in input ? input.scopes : void 0;
|
|
597
|
+
}
|
|
598
|
+
function whereResources(input) {
|
|
599
|
+
return "partial" in input ? input.resources : void 0;
|
|
600
|
+
}
|
|
601
|
+
function compileWhere(input, options = {}) {
|
|
602
|
+
const subject = options.subject ?? whereSubject(input);
|
|
603
|
+
const scopes = options.scopes ?? whereScopes(input);
|
|
604
|
+
const resources = options.resources ?? whereResources(input);
|
|
605
|
+
return compileNode(asPortableCondition(input), compact({
|
|
606
|
+
...options,
|
|
607
|
+
subject,
|
|
608
|
+
scopes,
|
|
609
|
+
resources
|
|
610
|
+
}));
|
|
611
|
+
}
|
|
612
|
+
//#endregion
|
|
613
|
+
export { relatedRowGuard as a, relationArmSql as c, scopeMembershipTable as i, renderGraphSql as l, escapeLike as n, relatedSql as o, scopeColumn as r, relatedTargetsSql as s, compileWhere as t, restrictedRowsSql as u };
|