@nmnmcc/ability 2.0.2 → 2.0.5
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 +20 -14
- package/dist/Condition.d.ts +18 -8
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +2 -1
- package/dist/Condition.js.map +1 -1
- package/dist/Limits.d.ts +3 -1
- package/dist/Limits.d.ts.map +1 -1
- package/dist/Limits.js +3 -1
- package/dist/Limits.js.map +1 -1
- package/dist/Policy.d.ts +2 -6
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js.map +1 -1
- package/dist/internal/algebra.js +84 -21
- package/dist/internal/algebra.js.map +1 -1
- package/dist/internal/analysis.js +4 -2
- package/dist/internal/analysis.js.map +1 -1
- package/dist/internal/bind.js +36 -23
- package/dist/internal/bind.js.map +1 -1
- package/dist/internal/catalog.d.ts +9 -0
- package/dist/internal/catalog.d.ts.map +1 -1
- package/dist/internal/catalog.js +38 -23
- package/dist/internal/catalog.js.map +1 -1
- package/dist/internal/decide.js +2 -1
- package/dist/internal/decide.js.map +1 -1
- package/dist/internal/place.js +19 -0
- package/dist/internal/place.js.map +1 -0
- package/dist/internal/policy-json.js +39 -39
- package/dist/internal/policy-json.js.map +1 -1
- package/dist/internal/rule.d.ts +20 -22
- package/dist/internal/rule.d.ts.map +1 -1
- package/dist/internal/rule.js +32 -4
- package/dist/internal/rule.js.map +1 -1
- package/dist/internal/search.d.ts.map +1 -1
- package/dist/internal/search.js +51 -34
- package/dist/internal/search.js.map +1 -1
- package/package.json +1 -1
- package/src/Condition.ts +46 -25
- package/src/Limits.ts +3 -1
- package/src/Policy.ts +1 -7
- package/src/internal/algebra.ts +110 -32
- package/src/internal/analysis.ts +5 -0
- package/src/internal/bind.ts +88 -32
- package/src/internal/catalog.ts +78 -30
- package/src/internal/decide.ts +3 -0
- package/src/internal/place.ts +32 -0
- package/src/internal/policy-json.ts +49 -29
- package/src/internal/rule.ts +59 -11
- package/src/internal/search.ts +78 -75
package/dist/Policy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Policy.js","names":["RuleData.builder","RuleData.makePlain","Internal.PolicyError","Internal.define","Internal.combine","Internal.ceiling","Internal.provideContext","Decide.decideAny","Decide.decideSync","Decide.explain","Decide.permittedFields","Analysis.residual","Analysis.coverageAny","Internal.schema"],"sources":["../src/Policy.ts"],"sourcesContent":["/**\n * A policy is a tree that gives a {@link Decision.Decision} for a request.\n *\n * The leaves of the tree are lists of rules. A combining algorithm decides\n * which rule gives the answer when more than one rule applies:\n * - `denyOverrides` (the default): a deny rule wins over every allow rule.\n * - `permitOverrides`: an allow rule wins over every deny rule.\n * - `firstApplicable`: the first rule that applies wins.\n * - `lastApplicable`: the last rule that applies wins.\n *\n * The branches put policies together:\n * - {@link all}: every policy must allow, and one deny is enough to deny. Use\n * it when a user token may never do more than its user.\n * - {@link any}: one allow is enough.\n * - {@link firstOf}: the first policy that gives an answer wins. Use it for\n * grants first, then rules.\n * - {@link ceiling}: the deny rules of a second policy are a limit for\n * everyone. Its allow rules do nothing.\n *\n * A policy gives three kinds of answers:\n * - {@link decide}: the decision for one resource value.\n * - {@link residual}: a condition on resource values. A value passes when the\n * policy allows the request for that value. Use it to make a database\n * query, or to ask \"may this principal do this act on some resource of\n * this kind?\"\n * - {@link permittedFields}: the fields that the policy lets a request use.\n *\n * Each of them takes the policy first and the request second, as in\n * `Policy.decide(policy, request)`. The policy gives the domain, so\n * TypeScript checks the request against it.\n *\n * A policy without predicates can become JSON with {@link schema}.\n *\n * @stability stable\n * @since 0.3.0\n */\nimport type * as Context from \"effect/Context\"\nimport type * as Data from \"effect/Data\"\nimport type * as Effect from \"effect/Effect\"\nimport type * as Pipeable from \"effect/Pipeable\"\nimport type * as Schema from \"effect/Schema\"\nimport type * as Condition from \"./Condition.ts\"\nimport * as Analysis from \"./internal/analysis.ts\"\nimport * as Decide from \"./internal/decide.ts\"\nimport * as Internal from \"./internal/policy.ts\"\nimport type * as PolicyNodes from \"./internal/policy-nodes.ts\"\nimport * as RuleData from \"./internal/rule.ts\"\nimport type * as Decision from \"./Decision.ts\"\nimport type * as Domain from \"./Domain.ts\"\nimport type * as Resource from \"./Resource.ts\"\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Algorithm =\n\t\"denyOverrides\" | \"permitOverrides\" | \"firstApplicable\" | \"lastApplicable\"\n\n/**\n * A list of rules with a combining algorithm.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type RulesNode = PolicyNodes.Rules\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type AllNode = PolicyNodes.All\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type AnyNode = PolicyNodes.Any\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FirstOfNode = PolicyNodes.FirstOf\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type CeilingNode = PolicyNodes.Ceiling\n\n/**\n * The nodes of a policy tree, without types. Each node is a class, and only\n * the functions of this module and the Schema make one.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Node = PolicyNodes.Node\n\n/**\n * A policy for the domain `D`. `X` is `Condition.Opaque` when a rule of the\n * policy has a predicate, and `never` when the policy is plain data. A plain\n * policy can become JSON, and `decideSync` takes it.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Policy<D extends Domain.Any, X = never> = Internal.PolicyData<D, X>\n\n/**\n * Any policy.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Any = Policy<any, any>\n\n/**\n * The domain of a policy.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type DomainOf<P> = P extends Policy<infer D, any> ? D : never\n\n/**\n * The mark of a policy or a rule: `Condition.Opaque` when it has a predicate.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type OpaqueOf<P> = P extends { readonly \"~Opaque\": () => infer X }\n\t? X\n\t: never\n\n/**\n * One act on one resource value, for each kind of the domain. The action must\n * be an action of the kind, and the field must be a field of the kind.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Target<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly resource: Domain.ValueOf<D, K>\n\t\treadonly field?: Condition.FieldSelector<Domain.DataOf<D, K>>\n\t\t/** A target has a resource value or a kind, never both. */\n\t\treadonly kind?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A {@link Target} with no field. Each kind keeps its own actions.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FieldlessTarget<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly resource: Domain.ValueOf<D, K>\n\t\treadonly kind?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A scope: a part of a kind, as a condition on its values. It gets a builder\n * with the types of the kind and of the principal, like the condition of a\n * rule. A scope is plain data, so it can not have a predicate.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport type Scope<D, K extends string> = (\n\tbuilder: Condition.Builder<\n\t\tDomain.DataOf<D, K>,\n\t\tDomain.ValueOf<D, K>,\n\t\tDomain.Principal<D>\n\t>,\n) => Condition.Condition<\n\tCondition.Reads<Domain.ValueOf<D, K>, Domain.Principal<D>>\n>\n\n/**\n * One act on one kind of resource, with no value. With `within`, the target is\n * only the values of the kind in the scope. Without it, the target is every\n * value of the kind.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type KindTarget<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly kind: K\n\t\treadonly field?: Condition.FieldSelector<Domain.DataOf<D, K>>\n\t\t/** The part of the kind. See {@link Scope}. */\n\t\treadonly within?: Scope<D, K>\n\t\t/** A target has a resource value or a kind, never both. */\n\t\treadonly resource?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A request: a principal, an action, and a resource value.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Request<D> = Target<D> & { readonly principal: Domain.Principal<D> }\n\n/**\n * A {@link Request} with no field.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FieldlessRequest<D> = FieldlessTarget<D> & {\n\treadonly principal: Domain.Principal<D>\n}\n\n/**\n * A request about a kind of resource, with no value.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type KindRequest<D> = KindTarget<D> & {\n\treadonly principal: Domain.Principal<D>\n}\n\n/**\n * A request with no types.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface AnyRequest {\n\treadonly principal: object\n\treadonly action: string\n\treadonly resource: object\n\treadonly field?: string | undefined\n}\n\n/**\n * One rule that a decision tested. `path` gives the place of the rule in the\n * policy tree: the index of each child, then the index of the rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Step {\n\treadonly path: ReadonlyArray<number>\n\treadonly rule: AnyRule\n\treadonly applies: boolean\n}\n\n/**\n * A decision, and the rules that were tested to make it, in order. Keep it in\n * an audit log.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Explanation {\n\treadonly decision: Decision.Decision\n\treadonly steps: ReadonlyArray<Step>\n}\n\n// Rules\n\n/**\n * A rule for the domain `D`. `I` is what the tests of its condition read, and\n * `X` is its mark: see `Condition.Condition`. A predicate reads the values and\n * the principal of its domain, so a rule may go only into a policy of its own\n * domain. A rule from {@link rule} has the domain `any`.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Rule<D = any, I = unknown, X = never>\n\textends RuleShape<I, X>, Pipeable.Pipeable {\n\treadonly \"~Opaque\": () => X\n\treadonly \"~Domain\": (domain: D) => D\n\t[Symbol.iterator](): Generator<Rule<D, I, X>, Rule<D, I, X>, unknown>\n}\n\n/**\n * The data of a rule.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport interface RuleShape<I = unknown, X = never> {\n\treadonly _tag: \"Allow\" | \"Deny\"\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: Condition.Condition<I, X>\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n\t/**\n\t * The domain of the builder that made the rule. Two domains may have the\n\t * same types but other paths, so the types can not tell them apart. It is\n\t * not in the JSON form of a rule.\n\t */\n\treadonly domain?: Domain.Any\n}\n\n/**\n * Any rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\n// Every rule fits `never` as what it reads, and a condition taken from such a\n// rule fits no rule that reads a value or a principal.\nexport type AnyRule = Rule<any, never, any>\n\n/**\n * A resource in a rule: a kind name, a resource of the domain, or `\"*\"`.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type RuleTarget<D> = Domain.Kind<D> | Domain.Resources<D> | \"*\"\n\n/**\n * The kind name of a target.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type KindOf<T> = T extends string ? T : Resource.Kind<T>\n\n/**\n * The actions that a rule may use for the kinds `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ActionFor<D, K> =\n\t| \"*\"\n\t| (\"*\" extends K\n\t\t\t? Domain.AnyAction<D>\n\t\t\t: Domain.ActionOf<D, Extract<K, string>>)\n\n/**\n * The targets in the resource argument of a rule: the one target, or the\n * targets of a list.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type TargetOf<Res> = Res extends ReadonlyArray<infer X> ? X : Res\n\n/**\n * The actions that a rule may use for its resource argument. A list of\n * resources may use an action of any of them. One target with a union type\n * may use only an action of every kind in the union, because its value may be\n * any one of them. When the argument may be a list or one target, the rule\n * may use only an action that each of these shapes allows.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type ActionForTargets<D, Res> = (\n\tRes extends ReadonlyArray<infer X>\n\t\t? (action: ActionFor<D, KindOf<X>>) => void\n\t\t: (action: ActionForTarget<D, Res>) => void\n) extends (action: infer A) => void\n\t? A\n\t: never\n\n/** The actions for one target that is not a list. */\ntype ActionForTarget<D, Res> =\n\t| \"*\"\n\t| (\"*\" extends KindOf<Res>\n\t\t\t? Domain.AnyAction<D>\n\t\t\t: Domain.CommonAction<D, Extract<KindOf<Res>, string>>)\n\n/**\n * The data type of the kinds `K`. For `\"*\"` it has no fields.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type DataFor<D, K> = \"*\" extends K\n\t? {}\n\t: Domain.DataOf<D, Extract<K, string>>\n\n/**\n * The value type of the kinds `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ValueFor<D, K> = \"*\" extends K\n\t? Domain.AnyValue<D>\n\t: Domain.ValueOf<D, Extract<K, string>>\n\n/**\n * A field name or a field pattern of the kinds `K`.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FieldFor<D, K> =\n\tCondition.FieldName<DataFor<D, K>> | `${string}*${string}`\n\n/**\n * A condition that a rule for the kinds `K` may have. Its predicates may read\n * only the values of these kinds and the principal of the domain.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport type ConditionFor<D, K> = Condition.Condition<\n\tCondition.Reads<ValueFor<D, K>, Domain.Principal<D>>,\n\tany\n>\n\n/**\n * The options of a rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface RuleOptions<D, K, C extends Condition.Any> {\n\t/** The condition. It gets a builder with the types of the resource. */\n\treadonly when?: (\n\t\tbuilder: Condition.Builder<\n\t\t\tDataFor<D, K>,\n\t\t\tValueFor<D, K>,\n\t\t\tDomain.Principal<D>\n\t\t>,\n\t) => C\n\treadonly fields?: ReadonlyArray<FieldFor<D, K>>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: Domain.Meta<D>\n}\n\ntype SingleOrArray<A> = A | ReadonlyArray<A>\n\n/**\n * Makes rules with the types of a domain.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Builder<D> {\n\treadonly allow: <\n\t\tconst Res extends SingleOrArray<RuleTarget<D>>,\n\t\tconst A extends ActionForTargets<D, Res>,\n\t\tconst C extends ConditionFor<D, KindOf<TargetOf<Res>>> =\n\t\t\tCondition.Condition,\n\t>(\n\t\taction: SingleOrArray<A>,\n\t\tresource: Res,\n\t\toptions?: RuleOptions<D, KindOf<TargetOf<Res>>, C>,\n\t) => Rule<D, Condition.ReadsOf<C>, Condition.OpaqueOf<C>>\n\treadonly deny: <\n\t\tconst Res extends SingleOrArray<RuleTarget<D>>,\n\t\tconst A extends ActionForTargets<D, Res>,\n\t\tconst C extends ConditionFor<D, KindOf<TargetOf<Res>>> =\n\t\t\tCondition.Condition,\n\t>(\n\t\taction: SingleOrArray<A>,\n\t\tresource: Res,\n\t\toptions?: RuleOptions<D, KindOf<TargetOf<Res>>, C>,\n\t) => Rule<D, Condition.ReadsOf<C>, Condition.OpaqueOf<C>>\n}\n\n/**\n * Makes rules with the types of a domain. `Policy.define` gives one to its\n * generator. Use this one to make rules outside of a generator.\n *\n * @example\n * ```ts\n * const _ = Policy.builder(App)\n * const rule = _.allow(\"read\", Document, {\n * when: ($) => $.eq(\"ownerId\", $.principal(\"id\")),\n * })\n * ```\n *\n * @stability stable\n * @category constructors\n * @since 2.0.0\n */\nexport const builder = <D extends Domain.Any>(domain: D): Builder<D> =>\n\tRuleData.builder(domain) as never\n\n/**\n * Makes a rule from its data, with no types. It does not check the data\n * against a domain: a policy does that when the rule goes into it. Use\n * {@link builder} for rules with types, or {@link schema} for rules from JSON.\n *\n * The condition may not have a predicate. A policy checks the fields of a\n * condition against the resources of its rule, but it can not check what\n * the code of a predicate reads. A rule from the builder keeps that in its\n * types; this one could give a predicate of one kind to another kind.\n *\n * @stability stable\n * @category constructors\n * @since 2.0.0\n */\nexport const rule: <I = unknown>(\n\tdata: RuleShape<I, never>,\n\t// The types come from the data only, not from where the rule goes.\n) => Rule<any, NoInfer<I>> = RuleData.makePlain as never\n\n/**\n * The options of a list of rules.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Options {\n\t/** The default is `denyOverrides`. */\n\treadonly algorithm?: Algorithm\n}\n\n/**\n * The error when a rule does not fit its domain: it names a kind, an action,\n * a field or a field of the principal that the domain does not have, or it\n * tests a field with a value that the field can not hold. It has one issue\n * for each problem, with its path, and a suggestion when a name has a mistake.\n *\n * @stability stable\n * @category errors\n * @since 2.0.0\n */\nexport const PolicyError: typeof Internal.PolicyError = Internal.PolicyError\n\n/**\n * @stability stable\n * @category errors\n * @since 2.0.0\n */\nexport type PolicyError = Internal.PolicyError\n\n/** The mark of a union of rules: `Condition.Opaque` when one has a predicate. */\ntype RulesOpaque<Rules> = Rules extends { readonly \"~Opaque\": () => infer X }\n\t? X\n\t: never\n\n/**\n * Makes a policy from a generator function that gives rules. It is pure and it\n * gives its result at once.\n *\n * @example\n * ```ts\n * const policy = Policy.define(App)(function* (_) {\n * yield* _.allow(\"read\", Document)\n * yield* _.allow([\"update\", \"delete\"], Document, {\n * when: ($) => $.eq(\"ownerId\", $.principal(\"id\")),\n * })\n * })\n * ```\n *\n * @stability stable\n * @category constructors\n * @since 0.3.0\n */\nexport const define: <D extends Domain.Any>(\n\tdomain: D,\n\toptions?: Options,\n) => <Rules extends Rule<D, never, any>, A>(\n\tbody: (builder: Builder<D>) => Generator<Rules, A, unknown>,\n) => Policy<D, RulesOpaque<Rules>> = Internal.define as never\n\ntype Combined<D extends Domain.Any, Ps extends ReadonlyArray<Any>> = Policy<\n\tD,\n\tOpaqueOf<Ps[number]>\n>\n\n/**\n * Every policy must allow, and one deny is enough to deny. With some policies\n * not applicable and none that deny, the result is not applicable.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const all: <const Ps extends readonly [Any, ...Array<Any>]>(\n\tpolicies: Ps,\n) => Combined<DomainOf<Ps[0]>, Ps> = Internal.combine(\"All\")\n\n/**\n * One allow is enough. Without an allow, one deny gives a deny.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const any: <const Ps extends readonly [Any, ...Array<Any>]>(\n\tpolicies: Ps,\n) => Combined<DomainOf<Ps[0]>, Ps> = Internal.combine(\"Any\")\n\n/**\n * The first policy that allows or denies gives the answer.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const firstOf: <const Ps extends readonly [Any, ...Array<Any>]>(\n\tpolicies: Ps,\n) => Combined<DomainOf<Ps[0]>, Ps> = Internal.combine(\"FirstOf\")\n\n/**\n * Puts a limit on a policy. A deny from `cap` always wins. An allow from `cap`\n * does nothing.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const ceiling: {\n\t<D extends Domain.Any, X2>(\n\t\tcap: Policy<D, X2>,\n\t): <X>(policy: Policy<D, X>) => Policy<D, X | X2>\n\t<D extends Domain.Any, X, X2>(\n\t\tpolicy: Policy<D, X>,\n\t\tcap: Policy<D, X2>,\n\t): Policy<D, X | X2>\n} = Internal.ceiling as never\n\n/**\n * Gives the services in `context` to every predicate of a policy, so the\n * policy needs no services when it runs.\n *\n * @stability stable\n * @category services\n * @since 1.0.0\n */\nexport const provideContext: {\n\t<R>(\n\t\tcontext: Context.Context<R>,\n\t): <D extends Domain.Any, X>(\n\t\tpolicy: Policy<D, X> &\n\t\t\t([Condition.ServicesOf<X>] extends [R] ? unknown : never),\n\t) => Policy<D, Condition.Provided<X>>\n\t<D extends Domain.Any, X>(\n\t\tpolicy: Policy<D, X>,\n\t\tcontext: Context.Context<Condition.ServicesOf<X>>,\n\t): Policy<D, Condition.Provided<X>>\n} = Internal.provideContext as never\n\n// Decisions\n\n/**\n * Gives the decision of a policy for a request.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const decide: <D extends Domain.Any, X>(\n\tpolicy: Policy<D, X>,\n\trequest: Request<NoInfer<D>>,\n) => Effect.Effect<\n\tDecision.Decision,\n\tCondition.ErrorOf<X>,\n\tCondition.ServicesOf<X>\n> = Decide.decideAny as never\n\n/**\n * Gives the decision of a policy with no predicates, at once.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const decideSync: <D extends Domain.Any>(\n\tpolicy: Policy<D>,\n\trequest: Request<NoInfer<D>>,\n) => Decision.Decision = Decide.decideSync\n\n/**\n * Gives the decision of a policy and the rules that were tested to make it.\n *\n * One node may be a child of many nodes. Its rules are tested, and are in the\n * steps, each time that the decision comes to it.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const explain: <D extends Domain.Any, X>(\n\tpolicy: Policy<D, X>,\n\trequest: Request<NoInfer<D>>,\n) => Effect.Effect<Explanation, Condition.ErrorOf<X>, Condition.ServicesOf<X>> =\n\tDecide.explain as never\n\n/**\n * Gives the fields of a resource value that the policy lets a request use. It\n * tests each field that a list of fields shows: each scalar, record or array\n * of the resource Schema, with dots for nested fields.\n *\n * A field that holds more than one value, such as a record, is left out when a\n * deny rule names a part of it. The list can not name that part, and the whole\n * field would show it.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const permittedFields: <D extends Domain.Any, X>(\n\tpolicy: Policy<D, X>,\n\trequest: FieldlessRequest<NoInfer<D>>,\n) => Effect.Effect<\n\tReadonlyArray<string>,\n\tCondition.ErrorOf<X>,\n\tCondition.ServicesOf<X>\n> = Decide.permittedFields as never\n\n// Residual conditions\n\n/**\n * What a residual condition reads: a value of the kind `K`. The principal is\n * already in it. A residual is for one kind, so when `K` is a union of kinds,\n * the value must be a value of each of them. No value is, so such a residual\n * can not be evaluated on a value of the wrong kind.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type ResidualReads<D, K> = Condition.Reads<\n\t(\n\t\tK extends unknown ? (value: Domain.ValueOf<D, K & string>) => void : never\n\t) extends (value: infer V) => void\n\t\t? V\n\t\t: never,\n\tunknown\n>\n\n/**\n * Gives the condition on resource values for which the policy allows a\n * request. It has no principal fields and no `Under` nodes, so you can give it\n * to `Condition.fold`. It is made smaller, so it is often `True` or `False`\n * when the answer does not depend on the value, but it does no search: a\n * condition that is true for every value may stay a test. To know if the\n * policy allows every value, or some value, use {@link coverage}.\n *\n * With a scope (`within`), the condition is the scope and the residual\n * together, so it is false for every value outside the scope. Give the policy\n * first to use a scope. The condition comes from its parts, with no search. To\n * know if it allows all of the scope or none of it, use {@link coverage}.\n *\n * When a rule that the request may use has a predicate, the condition is one\n * predicate with the name `\"policy\"`. It runs the whole policy for each value,\n * so its answers and its failures are the same as those of {@link decide}. It\n * takes the principal with it. Give it resource values with their `_tag`. It\n * is false for a value of another kind.\n *\n * A policy tree may use the condition of a child two times, so a small policy\n * may give a very large condition. When the condition would have more than\n * 10000 nodes, this function throws an error. In an `Authorizer`, the error\n * is a defect, so the check fails.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const residual: <\n\tD extends Domain.Any,\n\tX,\n\tconst Req extends KindRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D, X>,\n\trequest: Req,\n) => Condition.Condition<ResidualReads<D, Req[\"kind\"]>, X> =\n\tAnalysis.residual as never\n\n/**\n * How much of a scope a policy allows. In each case, `condition` is the exact\n * condition on the values in the scope that the policy allows: give it to\n * `Condition.fold` for a list query.\n * - `All`: the policy allows every value in the scope. `condition` is the\n * scope (`True` with no scope).\n * - `None`: the policy allows no value in the scope. `condition` is `False`.\n * An empty scope is `None` too, so a check on a scope that has a mistake,\n * such as a principal field with no value, never passes.\n * - `Some`: the policy allows some values in the scope and not others.\n * `allowed` is an example of a value that it allows, and `refused` of a\n * value in the scope that it does not allow.\n * - `Unknown`: the answer needs a predicate, or the search went past its\n * budget.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport type Coverage<I = unknown, X = never> = Data.TaggedEnum<{\n\tAll: { readonly condition: Condition.Condition<I, X> }\n\tNone: { readonly condition: Condition.Condition<I, X> }\n\tSome: {\n\t\treadonly condition: Condition.Condition<I, X>\n\t\treadonly allowed: Condition.Example\n\t\treadonly refused: Condition.Example\n\t}\n\tUnknown: {\n\t\treadonly condition: Condition.Condition<I, X>\n\t\treadonly reason: \"predicate\" | \"budget\"\n\t}\n}>\n\n/**\n * Tells how much of a scope the policy allows: all of it, none of it, or some\n * of it. See {@link Coverage}. With no scope (`within`), the scope is every\n * value of the kind.\n *\n * The answer comes from {@link Condition.satisfiable} with the shape of the\n * Schema of the resource. `All` and `None` hold for every value of the\n * Schema. `Some` means that, by the shape of the Schema, a value may get\n * either answer: a check such as a minimum length may still stop one of the\n * examples. `Unknown` comes only from a predicate or from the budget of the\n * search.\n *\n * `Some` does not mean that such a value is in your database. To know that,\n * run `condition` as a query.\n *\n * @stability stable\n * @category evaluation\n * @since 1.0.0\n */\nexport const coverage: <\n\tD extends Domain.Any,\n\tX,\n\tconst Req extends KindRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D, X>,\n\trequest: Req,\n) => Coverage<ResidualReads<D, Req[\"kind\"]>, X> = Analysis.coverageAny as never\n\n// Schema\n\n/**\n * The JSON form of a rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface EncodedRule {\n\treadonly _tag: \"Allow\" | \"Deny\"\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: Condition.Encoded\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n}\n\n/**\n * The JSON form of a policy.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type EncodedNode =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly algorithm: Algorithm\n\t\t\treadonly rules: ReadonlyArray<EncodedRule>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly policies: ReadonlyArray<EncodedNode>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly cap: EncodedNode\n\t\t\treadonly policy: EncodedNode\n\t }\n\n/**\n * The Schema of the policies of a domain with no predicates. Decoding checks\n * every kind, action, field and principal field against the domain, and the\n * `meta` of each rule against the `meta` Schema of the domain.\n *\n * The decoder takes at most 256 levels of policies and 500 levels of\n * conditions, and at most 1000000 objects and array items, with a shared\n * object counted for each place. It reads only the own data fields of the\n * input.\n *\n * @stability stable\n * @category schemas\n * @since 0.3.0\n */\nexport const schema: <D extends Domain.Any>(\n\tdomain: D,\n) => Schema.Codec<Policy<D>, EncodedNode> = Internal.schema as never\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+gBA,MAAa,WAAiC,WAC7CA,UAAiB,MAAM;;;;;;;;;;;;;;;AAgBxB,MAAa,OAGgBC;;;;;;;;;;;AAwB7B,MAAa,cAA2CC;;;;;;;;;;;;;;;;;;;AAgCxD,MAAa,SAKwBC;;;;;;;;;AAerC,MAAa,MAEwBC,QAAiB,KAAK;;;;;;;;AAS3D,MAAa,MAEwBA,QAAiB,KAAK;;;;;;;;AAS3D,MAAa,UAEwBA,QAAiB,SAAS;;;;;;;;;AAU/D,MAAa,UAQTC;;;;;;;;;AAUJ,MAAa,iBAWTC;;;;;;;;AAWJ,MAAa,SAOTC;;;;;;;;AASJ,MAAa,aAGYC;;;;;;;;;;;AAYzB,MAAa,UAIZC;;;;;;;;;;;;;;AAeD,MAAa,kBAOTC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDJ,MAAa,WAQZC;;;;;;;;;;;;;;;;;;;;AAsDD,MAAa,WAOqCC;;;;;;;;;;;;;;;AA2DlD,MAAa,SAE+BC"}
|
|
1
|
+
{"version":3,"file":"Policy.js","names":["RuleData.builder","RuleData.makePlain","Internal.PolicyError","Internal.define","Internal.combine","Internal.ceiling","Internal.provideContext","Decide.decideAny","Decide.decideSync","Decide.explain","Decide.permittedFields","Analysis.residual","Analysis.coverageAny","Internal.schema"],"sources":["../src/Policy.ts"],"sourcesContent":["/**\n * A policy is a tree that gives a {@link Decision.Decision} for a request.\n *\n * The leaves of the tree are lists of rules. A combining algorithm decides\n * which rule gives the answer when more than one rule applies:\n * - `denyOverrides` (the default): a deny rule wins over every allow rule.\n * - `permitOverrides`: an allow rule wins over every deny rule.\n * - `firstApplicable`: the first rule that applies wins.\n * - `lastApplicable`: the last rule that applies wins.\n *\n * The branches put policies together:\n * - {@link all}: every policy must allow, and one deny is enough to deny. Use\n * it when a user token may never do more than its user.\n * - {@link any}: one allow is enough.\n * - {@link firstOf}: the first policy that gives an answer wins. Use it for\n * grants first, then rules.\n * - {@link ceiling}: the deny rules of a second policy are a limit for\n * everyone. Its allow rules do nothing.\n *\n * A policy gives three kinds of answers:\n * - {@link decide}: the decision for one resource value.\n * - {@link residual}: a condition on resource values. A value passes when the\n * policy allows the request for that value. Use it to make a database\n * query, or to ask \"may this principal do this act on some resource of\n * this kind?\"\n * - {@link permittedFields}: the fields that the policy lets a request use.\n *\n * Each of them takes the policy first and the request second, as in\n * `Policy.decide(policy, request)`. The policy gives the domain, so\n * TypeScript checks the request against it.\n *\n * A policy without predicates can become JSON with {@link schema}.\n *\n * @stability stable\n * @since 0.3.0\n */\nimport type * as Context from \"effect/Context\"\nimport type * as Data from \"effect/Data\"\nimport type * as Effect from \"effect/Effect\"\nimport type * as Schema from \"effect/Schema\"\nimport type * as Condition from \"./Condition.ts\"\nimport * as Analysis from \"./internal/analysis.ts\"\nimport * as Decide from \"./internal/decide.ts\"\nimport * as Internal from \"./internal/policy.ts\"\nimport type * as PolicyNodes from \"./internal/policy-nodes.ts\"\nimport * as RuleData from \"./internal/rule.ts\"\nimport type * as Decision from \"./Decision.ts\"\nimport type * as Domain from \"./Domain.ts\"\nimport type * as Resource from \"./Resource.ts\"\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Algorithm =\n\t\"denyOverrides\" | \"permitOverrides\" | \"firstApplicable\" | \"lastApplicable\"\n\n/**\n * A list of rules with a combining algorithm.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type RulesNode = PolicyNodes.Rules\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type AllNode = PolicyNodes.All\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type AnyNode = PolicyNodes.Any\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FirstOfNode = PolicyNodes.FirstOf\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type CeilingNode = PolicyNodes.Ceiling\n\n/**\n * The nodes of a policy tree, without types. Each node is a class, and only\n * the functions of this module and the Schema make one.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Node = PolicyNodes.Node\n\n/**\n * A policy for the domain `D`. `X` is `Condition.Opaque` when a rule of the\n * policy has a predicate, and `never` when the policy is plain data. A plain\n * policy can become JSON, and `decideSync` takes it.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Policy<D extends Domain.Any, X = never> = Internal.PolicyData<D, X>\n\n/**\n * Any policy.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Any = Policy<any, any>\n\n/**\n * The domain of a policy.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type DomainOf<P> = P extends Policy<infer D, any> ? D : never\n\n/**\n * The mark of a policy or a rule: `Condition.Opaque` when it has a predicate.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type OpaqueOf<P> = P extends { readonly \"~Opaque\": () => infer X }\n\t? X\n\t: never\n\n/**\n * One act on one resource value, for each kind of the domain. The action must\n * be an action of the kind, and the field must be a field of the kind.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Target<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly resource: Domain.ValueOf<D, K>\n\t\treadonly field?: Condition.FieldSelector<Domain.DataOf<D, K>>\n\t\t/** A target has a resource value or a kind, never both. */\n\t\treadonly kind?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A {@link Target} with no field. Each kind keeps its own actions.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FieldlessTarget<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly resource: Domain.ValueOf<D, K>\n\t\treadonly kind?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A scope: a part of a kind, as a condition on its values. It gets a builder\n * with the types of the kind and of the principal, like the condition of a\n * rule. A scope is plain data, so it can not have a predicate.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport type Scope<D, K extends string> = (\n\tbuilder: Condition.Builder<\n\t\tDomain.DataOf<D, K>,\n\t\tDomain.ValueOf<D, K>,\n\t\tDomain.Principal<D>\n\t>,\n) => Condition.Condition<\n\tCondition.Reads<Domain.ValueOf<D, K>, Domain.Principal<D>>\n>\n\n/**\n * One act on one kind of resource, with no value. With `within`, the target is\n * only the values of the kind in the scope. Without it, the target is every\n * value of the kind.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type KindTarget<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly kind: K\n\t\treadonly field?: Condition.FieldSelector<Domain.DataOf<D, K>>\n\t\t/** The part of the kind. See {@link Scope}. */\n\t\treadonly within?: Scope<D, K>\n\t\t/** A target has a resource value or a kind, never both. */\n\t\treadonly resource?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A request: a principal, an action, and a resource value.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Request<D> = Target<D> & { readonly principal: Domain.Principal<D> }\n\n/**\n * A {@link Request} with no field.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FieldlessRequest<D> = FieldlessTarget<D> & {\n\treadonly principal: Domain.Principal<D>\n}\n\n/**\n * A request about a kind of resource, with no value.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type KindRequest<D> = KindTarget<D> & {\n\treadonly principal: Domain.Principal<D>\n}\n\n/**\n * A request with no types.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface AnyRequest {\n\treadonly principal: object\n\treadonly action: string\n\treadonly resource: object\n\treadonly field?: string | undefined\n}\n\n/**\n * One rule that a decision tested. `path` gives the place of the rule in the\n * policy tree: the index of each child, then the index of the rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Step {\n\treadonly path: ReadonlyArray<number>\n\treadonly rule: AnyRule\n\treadonly applies: boolean\n}\n\n/**\n * A decision, and the rules that were tested to make it, in order. Keep it in\n * an audit log.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Explanation {\n\treadonly decision: Decision.Decision\n\treadonly steps: ReadonlyArray<Step>\n}\n\n// Rules\n\n/**\n * A rule for the domain `D`. `I` is what the tests of its condition read, and\n * `X` is its mark: see `Condition.Condition`. A predicate reads the values and\n * the principal of its domain, so a rule may go only into a policy of its own\n * domain. A rule from {@link rule} has the domain `any`.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Rule<D = any, I = unknown, X = never> = RuleData.Rule<D, I, X>\n\n/**\n * The data of a rule.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport interface RuleShape<I = unknown, X = never> {\n\treadonly _tag: \"Allow\" | \"Deny\"\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: Condition.Condition<I, X>\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n\t/**\n\t * The domain of the builder that made the rule. Two domains may have the\n\t * same types but other paths, so the types can not tell them apart. It is\n\t * not in the JSON form of a rule.\n\t */\n\treadonly domain?: Domain.Any\n}\n\n/**\n * Any rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\n// Every rule fits `never` as what it reads, and a condition taken from such a\n// rule fits no rule that reads a value or a principal.\nexport type AnyRule = Rule<any, never, any>\n\n/**\n * A resource in a rule: a kind name, a resource of the domain, or `\"*\"`.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type RuleTarget<D> = Domain.Kind<D> | Domain.Resources<D> | \"*\"\n\n/**\n * The kind name of a target.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type KindOf<T> = T extends string ? T : Resource.Kind<T>\n\n/**\n * The actions that a rule may use for the kinds `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ActionFor<D, K> =\n\t| \"*\"\n\t| (\"*\" extends K\n\t\t\t? Domain.AnyAction<D>\n\t\t\t: Domain.ActionOf<D, Extract<K, string>>)\n\n/**\n * The targets in the resource argument of a rule: the one target, or the\n * targets of a list.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type TargetOf<Res> = Res extends ReadonlyArray<infer X> ? X : Res\n\n/**\n * The actions that a rule may use for its resource argument. A list of\n * resources may use an action of any of them. One target with a union type\n * may use only an action of every kind in the union, because its value may be\n * any one of them. When the argument may be a list or one target, the rule\n * may use only an action that each of these shapes allows.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type ActionForTargets<D, Res> = (\n\tRes extends ReadonlyArray<infer X>\n\t\t? (action: ActionFor<D, KindOf<X>>) => void\n\t\t: (action: ActionForTarget<D, Res>) => void\n) extends (action: infer A) => void\n\t? A\n\t: never\n\n/** The actions for one target that is not a list. */\ntype ActionForTarget<D, Res> =\n\t| \"*\"\n\t| (\"*\" extends KindOf<Res>\n\t\t\t? Domain.AnyAction<D>\n\t\t\t: Domain.CommonAction<D, Extract<KindOf<Res>, string>>)\n\n/**\n * The data type of the kinds `K`. For `\"*\"` it has no fields.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type DataFor<D, K> = \"*\" extends K\n\t? {}\n\t: Domain.DataOf<D, Extract<K, string>>\n\n/**\n * The value type of the kinds `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ValueFor<D, K> = \"*\" extends K\n\t? Domain.AnyValue<D>\n\t: Domain.ValueOf<D, Extract<K, string>>\n\n/**\n * A field name or a field pattern of the kinds `K`.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FieldFor<D, K> =\n\tCondition.FieldName<DataFor<D, K>> | `${string}*${string}`\n\n/**\n * A condition that a rule for the kinds `K` may have. Its predicates may read\n * only the values of these kinds and the principal of the domain.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport type ConditionFor<D, K> = Condition.Condition<\n\tCondition.Reads<ValueFor<D, K>, Domain.Principal<D>>,\n\tany\n>\n\n/**\n * The options of a rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface RuleOptions<D, K, C extends Condition.Any> {\n\t/** The condition. It gets a builder with the types of the resource. */\n\treadonly when?: (\n\t\tbuilder: Condition.Builder<\n\t\t\tDataFor<D, K>,\n\t\t\tValueFor<D, K>,\n\t\t\tDomain.Principal<D>\n\t\t>,\n\t) => C\n\treadonly fields?: ReadonlyArray<FieldFor<D, K>>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: Domain.Meta<D>\n}\n\ntype SingleOrArray<A> = A | ReadonlyArray<A>\n\n/**\n * Makes rules with the types of a domain.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Builder<D> {\n\treadonly allow: <\n\t\tconst Res extends SingleOrArray<RuleTarget<D>>,\n\t\tconst A extends ActionForTargets<D, Res>,\n\t\tconst C extends ConditionFor<D, KindOf<TargetOf<Res>>> =\n\t\t\tCondition.Condition,\n\t>(\n\t\taction: SingleOrArray<A>,\n\t\tresource: Res,\n\t\toptions?: RuleOptions<D, KindOf<TargetOf<Res>>, C>,\n\t) => Rule<D, Condition.ReadsOf<C>, Condition.OpaqueOf<C>>\n\treadonly deny: <\n\t\tconst Res extends SingleOrArray<RuleTarget<D>>,\n\t\tconst A extends ActionForTargets<D, Res>,\n\t\tconst C extends ConditionFor<D, KindOf<TargetOf<Res>>> =\n\t\t\tCondition.Condition,\n\t>(\n\t\taction: SingleOrArray<A>,\n\t\tresource: Res,\n\t\toptions?: RuleOptions<D, KindOf<TargetOf<Res>>, C>,\n\t) => Rule<D, Condition.ReadsOf<C>, Condition.OpaqueOf<C>>\n}\n\n/**\n * Makes rules with the types of a domain. `Policy.define` gives one to its\n * generator. Use this one to make rules outside of a generator.\n *\n * @example\n * ```ts\n * const _ = Policy.builder(App)\n * const rule = _.allow(\"read\", Document, {\n * when: ($) => $.eq(\"ownerId\", $.principal(\"id\")),\n * })\n * ```\n *\n * @stability stable\n * @category constructors\n * @since 2.0.0\n */\nexport const builder = <D extends Domain.Any>(domain: D): Builder<D> =>\n\tRuleData.builder(domain) as never\n\n/**\n * Makes a rule from its data, with no types. It does not check the data\n * against a domain: a policy does that when the rule goes into it. Use\n * {@link builder} for rules with types, or {@link schema} for rules from JSON.\n *\n * The condition may not have a predicate. A policy checks the fields of a\n * condition against the resources of its rule, but it can not check what\n * the code of a predicate reads. A rule from the builder keeps that in its\n * types; this one could give a predicate of one kind to another kind.\n *\n * @stability stable\n * @category constructors\n * @since 2.0.0\n */\nexport const rule: <I = unknown>(\n\tdata: RuleShape<I, never>,\n\t// The types come from the data only, not from where the rule goes.\n) => Rule<any, NoInfer<I>> = RuleData.makePlain as never\n\n/**\n * The options of a list of rules.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Options {\n\t/** The default is `denyOverrides`. */\n\treadonly algorithm?: Algorithm\n}\n\n/**\n * The error when a rule does not fit its domain: it names a kind, an action,\n * a field or a field of the principal that the domain does not have, or it\n * tests a field with a value that the field can not hold. It has one issue\n * for each problem, with its path, and a suggestion when a name has a mistake.\n *\n * @stability stable\n * @category errors\n * @since 2.0.0\n */\nexport const PolicyError: typeof Internal.PolicyError = Internal.PolicyError\n\n/**\n * @stability stable\n * @category errors\n * @since 2.0.0\n */\nexport type PolicyError = Internal.PolicyError\n\n/** The mark of a union of rules: `Condition.Opaque` when one has a predicate. */\ntype RulesOpaque<Rules> = Rules extends { readonly \"~Opaque\": () => infer X }\n\t? X\n\t: never\n\n/**\n * Makes a policy from a generator function that gives rules. It is pure and it\n * gives its result at once.\n *\n * @example\n * ```ts\n * const policy = Policy.define(App)(function* (_) {\n * yield* _.allow(\"read\", Document)\n * yield* _.allow([\"update\", \"delete\"], Document, {\n * when: ($) => $.eq(\"ownerId\", $.principal(\"id\")),\n * })\n * })\n * ```\n *\n * @stability stable\n * @category constructors\n * @since 0.3.0\n */\nexport const define: <D extends Domain.Any>(\n\tdomain: D,\n\toptions?: Options,\n) => <Rules extends Rule<D, never, any>, A>(\n\tbody: (builder: Builder<D>) => Generator<Rules, A, unknown>,\n) => Policy<D, RulesOpaque<Rules>> = Internal.define as never\n\ntype Combined<D extends Domain.Any, Ps extends ReadonlyArray<Any>> = Policy<\n\tD,\n\tOpaqueOf<Ps[number]>\n>\n\n/**\n * Every policy must allow, and one deny is enough to deny. With some policies\n * not applicable and none that deny, the result is not applicable.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const all: <const Ps extends readonly [Any, ...Array<Any>]>(\n\tpolicies: Ps,\n) => Combined<DomainOf<Ps[0]>, Ps> = Internal.combine(\"All\")\n\n/**\n * One allow is enough. Without an allow, one deny gives a deny.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const any: <const Ps extends readonly [Any, ...Array<Any>]>(\n\tpolicies: Ps,\n) => Combined<DomainOf<Ps[0]>, Ps> = Internal.combine(\"Any\")\n\n/**\n * The first policy that allows or denies gives the answer.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const firstOf: <const Ps extends readonly [Any, ...Array<Any>]>(\n\tpolicies: Ps,\n) => Combined<DomainOf<Ps[0]>, Ps> = Internal.combine(\"FirstOf\")\n\n/**\n * Puts a limit on a policy. A deny from `cap` always wins. An allow from `cap`\n * does nothing.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const ceiling: {\n\t<D extends Domain.Any, X2>(\n\t\tcap: Policy<D, X2>,\n\t): <X>(policy: Policy<D, X>) => Policy<D, X | X2>\n\t<D extends Domain.Any, X, X2>(\n\t\tpolicy: Policy<D, X>,\n\t\tcap: Policy<D, X2>,\n\t): Policy<D, X | X2>\n} = Internal.ceiling as never\n\n/**\n * Gives the services in `context` to every predicate of a policy, so the\n * policy needs no services when it runs.\n *\n * @stability stable\n * @category services\n * @since 1.0.0\n */\nexport const provideContext: {\n\t<R>(\n\t\tcontext: Context.Context<R>,\n\t): <D extends Domain.Any, X>(\n\t\tpolicy: Policy<D, X> &\n\t\t\t([Condition.ServicesOf<X>] extends [R] ? unknown : never),\n\t) => Policy<D, Condition.Provided<X>>\n\t<D extends Domain.Any, X>(\n\t\tpolicy: Policy<D, X>,\n\t\tcontext: Context.Context<Condition.ServicesOf<X>>,\n\t): Policy<D, Condition.Provided<X>>\n} = Internal.provideContext as never\n\n// Decisions\n\n/**\n * Gives the decision of a policy for a request.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const decide: <D extends Domain.Any, X>(\n\tpolicy: Policy<D, X>,\n\trequest: Request<NoInfer<D>>,\n) => Effect.Effect<\n\tDecision.Decision,\n\tCondition.ErrorOf<X>,\n\tCondition.ServicesOf<X>\n> = Decide.decideAny as never\n\n/**\n * Gives the decision of a policy with no predicates, at once.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const decideSync: <D extends Domain.Any>(\n\tpolicy: Policy<D>,\n\trequest: Request<NoInfer<D>>,\n) => Decision.Decision = Decide.decideSync\n\n/**\n * Gives the decision of a policy and the rules that were tested to make it.\n *\n * One node may be a child of many nodes. Its rules are tested, and are in the\n * steps, each time that the decision comes to it.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const explain: <D extends Domain.Any, X>(\n\tpolicy: Policy<D, X>,\n\trequest: Request<NoInfer<D>>,\n) => Effect.Effect<Explanation, Condition.ErrorOf<X>, Condition.ServicesOf<X>> =\n\tDecide.explain as never\n\n/**\n * Gives the fields of a resource value that the policy lets a request use. It\n * tests each field that a list of fields shows: each scalar, record or array\n * of the resource Schema, with dots for nested fields.\n *\n * A field that holds more than one value, such as a record, is left out when a\n * deny rule names a part of it. The list can not name that part, and the whole\n * field would show it.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const permittedFields: <D extends Domain.Any, X>(\n\tpolicy: Policy<D, X>,\n\trequest: FieldlessRequest<NoInfer<D>>,\n) => Effect.Effect<\n\tReadonlyArray<string>,\n\tCondition.ErrorOf<X>,\n\tCondition.ServicesOf<X>\n> = Decide.permittedFields as never\n\n// Residual conditions\n\n/**\n * What a residual condition reads: a value of the kind `K`. The principal is\n * already in it. A residual is for one kind, so when `K` is a union of kinds,\n * the value must be a value of each of them. No value is, so such a residual\n * can not be evaluated on a value of the wrong kind.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type ResidualReads<D, K> = Condition.Reads<\n\t(\n\t\tK extends unknown ? (value: Domain.ValueOf<D, K & string>) => void : never\n\t) extends (value: infer V) => void\n\t\t? V\n\t\t: never,\n\tunknown\n>\n\n/**\n * Gives the condition on resource values for which the policy allows a\n * request. It has no principal fields and no `Under` nodes, so you can give it\n * to `Condition.fold`. It is made smaller, so it is often `True` or `False`\n * when the answer does not depend on the value, but it does no search: a\n * condition that is true for every value may stay a test. To know if the\n * policy allows every value, or some value, use {@link coverage}.\n *\n * With a scope (`within`), the condition is the scope and the residual\n * together, so it is false for every value outside the scope. Give the policy\n * first to use a scope. The condition comes from its parts, with no search. To\n * know if it allows all of the scope or none of it, use {@link coverage}.\n *\n * When a rule that the request may use has a predicate, the condition is one\n * predicate with the name `\"policy\"`. It runs the whole policy for each value,\n * so its answers and its failures are the same as those of {@link decide}. It\n * takes the principal with it. Give it resource values with their `_tag`. It\n * is false for a value of another kind.\n *\n * A policy tree may use the condition of a child two times, so a small policy\n * may give a very large condition. When the condition would have more than\n * 10000 nodes, this function throws an error. In an `Authorizer`, the error\n * is a defect, so the check fails.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const residual: <\n\tD extends Domain.Any,\n\tX,\n\tconst Req extends KindRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D, X>,\n\trequest: Req,\n) => Condition.Condition<ResidualReads<D, Req[\"kind\"]>, X> =\n\tAnalysis.residual as never\n\n/**\n * How much of a scope a policy allows. In each case, `condition` is the exact\n * condition on the values in the scope that the policy allows: give it to\n * `Condition.fold` for a list query.\n * - `All`: the policy allows every value in the scope. `condition` is the\n * scope (`True` with no scope).\n * - `None`: the policy allows no value in the scope. `condition` is `False`.\n * An empty scope is `None` too, so a check on a scope that has a mistake,\n * such as a principal field with no value, never passes.\n * - `Some`: the policy allows some values in the scope and not others.\n * `allowed` is an example of a value that it allows, and `refused` of a\n * value in the scope that it does not allow.\n * - `Unknown`: the answer needs a predicate, or the search went past its\n * budget.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport type Coverage<I = unknown, X = never> = Data.TaggedEnum<{\n\tAll: { readonly condition: Condition.Condition<I, X> }\n\tNone: { readonly condition: Condition.Condition<I, X> }\n\tSome: {\n\t\treadonly condition: Condition.Condition<I, X>\n\t\treadonly allowed: Condition.Example\n\t\treadonly refused: Condition.Example\n\t}\n\tUnknown: {\n\t\treadonly condition: Condition.Condition<I, X>\n\t\treadonly reason: \"predicate\" | \"budget\"\n\t}\n}>\n\n/**\n * Tells how much of a scope the policy allows: all of it, none of it, or some\n * of it. See {@link Coverage}. With no scope (`within`), the scope is every\n * value of the kind.\n *\n * The answer comes from {@link Condition.satisfiable} with the shape of the\n * Schema of the resource. `All` and `None` hold for every value of the\n * Schema. `Some` means that, by the shape of the Schema, a value may get\n * either answer: a check such as a minimum length may still stop one of the\n * examples. `Unknown` comes only from a predicate or from the budget of the\n * search.\n *\n * `Some` does not mean that such a value is in your database. To know that,\n * run `condition` as a query.\n *\n * @stability stable\n * @category evaluation\n * @since 1.0.0\n */\nexport const coverage: <\n\tD extends Domain.Any,\n\tX,\n\tconst Req extends KindRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D, X>,\n\trequest: Req,\n) => Coverage<ResidualReads<D, Req[\"kind\"]>, X> = Analysis.coverageAny as never\n\n// Schema\n\n/**\n * The JSON form of a rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface EncodedRule {\n\treadonly _tag: \"Allow\" | \"Deny\"\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: Condition.Encoded\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n}\n\n/**\n * The JSON form of a policy.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type EncodedNode =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly algorithm: Algorithm\n\t\t\treadonly rules: ReadonlyArray<EncodedRule>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly policies: ReadonlyArray<EncodedNode>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly cap: EncodedNode\n\t\t\treadonly policy: EncodedNode\n\t }\n\n/**\n * The Schema of the policies of a domain with no predicates. Decoding checks\n * every kind, action, field and principal field against the domain, and the\n * `meta` of each rule against the `meta` Schema of the domain.\n *\n * The decoder takes at most 256 levels of policies and 500 levels of\n * conditions, and at most 1000000 objects and array items, with a shared\n * object counted for each place. It reads only the own data fields of the\n * input.\n *\n * @stability stable\n * @category schemas\n * @since 0.3.0\n */\nexport const schema: <D extends Domain.Any>(\n\tdomain: D,\n) => Schema.Codec<Policy<D>, EncodedNode> = Internal.schema as never\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAygBA,MAAa,WAAiC,WAC7CA,UAAiB,MAAM;;;;;;;;;;;;;;;AAgBxB,MAAa,OAGgBC;;;;;;;;;;;AAwB7B,MAAa,cAA2CC;;;;;;;;;;;;;;;;;;;AAgCxD,MAAa,SAKwBC;;;;;;;;;AAerC,MAAa,MAEwBC,QAAiB,KAAK;;;;;;;;AAS3D,MAAa,MAEwBA,QAAiB,KAAK;;;;;;;;AAS3D,MAAa,UAEwBA,QAAiB,SAAS;;;;;;;;;AAU/D,MAAa,UAQTC;;;;;;;;;AAUJ,MAAa,iBAWTC;;;;;;;;AAWJ,MAAa,SAOTC;;;;;;;;AASJ,MAAa,aAGYC;;;;;;;;;;;AAYzB,MAAa,UAIZC;;;;;;;;;;;;;;AAeD,MAAa,kBAOTC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDJ,MAAa,WAQZC;;;;;;;;;;;;;;;;;;;;AAsDD,MAAa,WAOqCC;;;;;;;;;;;;;;;AA2DlD,MAAa,SAE+BC"}
|
package/dist/internal/algebra.js
CHANGED
|
@@ -2,7 +2,6 @@ import { And, Compare, In, Not, Or, Predicate, isScalar, literal, nodes_exports,
|
|
|
2
2
|
import { readPath } from "./read.js";
|
|
3
3
|
import * as Effect from "effect/Effect";
|
|
4
4
|
import * as Arr from "effect/Array";
|
|
5
|
-
import * as HashSet from "effect/HashSet";
|
|
6
5
|
import * as Option from "effect/Option";
|
|
7
6
|
// src/internal/algebra.ts
|
|
8
7
|
/**
|
|
@@ -14,18 +13,72 @@ import * as Option from "effect/Option";
|
|
|
14
13
|
* each place, and the limits of the nodes keep that work small.
|
|
15
14
|
*/
|
|
16
15
|
const { always, never } = nodes_exports;
|
|
16
|
+
/** The text of an operand. The type is in it, so `"1"` is not `1`. */
|
|
17
|
+
const operandText = (operand) => operand._tag === "Literal" ? [typeof operand.value, String(operand.value)] : ["principal", operand.path];
|
|
18
|
+
/** The parts of the text of a condition with no predicate. */
|
|
19
|
+
const shapeOf = (node) => {
|
|
20
|
+
switch (node._tag) {
|
|
21
|
+
case "True":
|
|
22
|
+
case "False": return [node._tag];
|
|
23
|
+
case "Compare": return [
|
|
24
|
+
node._tag,
|
|
25
|
+
node.op,
|
|
26
|
+
node.field,
|
|
27
|
+
operandText(node.value)
|
|
28
|
+
];
|
|
29
|
+
case "In": return [
|
|
30
|
+
node._tag,
|
|
31
|
+
node.field,
|
|
32
|
+
Arr.map(node.values, operandText)
|
|
33
|
+
];
|
|
34
|
+
case "Exists": return [node._tag, node.field];
|
|
35
|
+
case "Under": return [node._tag, Arr.map(node.prefix, operandText)];
|
|
36
|
+
case "And":
|
|
37
|
+
case "Or": return [node._tag, Arr.map(node.conditions, shapeOf)];
|
|
38
|
+
case "Not": return [node._tag, shapeOf(node.condition)];
|
|
39
|
+
case "Predicate": throw new Error("A predicate has no text of structure");
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The text of the structure of a condition with no predicate. Two conditions
|
|
44
|
+
* with one text give the same answer for every value. It reads all of the
|
|
45
|
+
* condition, so a search counts its size in the budget.
|
|
46
|
+
*/
|
|
47
|
+
const structureText = (node) => JSON.stringify(shapeOf(node));
|
|
48
|
+
/**
|
|
49
|
+
* The most nodes of a condition that a smart constructor reads for its key.
|
|
50
|
+
* A larger child is its own key, so the work of a constructor stays small.
|
|
51
|
+
*/
|
|
52
|
+
const keyNodes = 64;
|
|
17
53
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
54
|
+
* The key of a child in a smart constructor: the text of its structure, or
|
|
55
|
+
* the node itself when it is large. The keys go into a native `Set`, not a
|
|
56
|
+
* `HashSet` of Effect. The hash of a string in Effect has no random seed, so
|
|
57
|
+
* a policy could hold very many strings with one hash, and each test of the
|
|
58
|
+
* set would read all of them. A native `Set` finds a node by the node itself,
|
|
59
|
+
* and it hashes a string with the random seed of the engine.
|
|
20
60
|
*/
|
|
21
|
-
const
|
|
61
|
+
const keyOf = (node) => node.size > keyNodes ? node : structureText(node);
|
|
62
|
+
/**
|
|
63
|
+
* Keeps the first node of each structure, in order. A large node is a copy
|
|
64
|
+
* only of itself.
|
|
65
|
+
*/
|
|
66
|
+
const dedupe = (nodes) => {
|
|
67
|
+
const seen = /* @__PURE__ */ new Set();
|
|
68
|
+
return Arr.filter(nodes, (node) => {
|
|
69
|
+
const key = keyOf(node);
|
|
70
|
+
if (seen.has(key)) return false;
|
|
71
|
+
seen.add(key);
|
|
72
|
+
return true;
|
|
73
|
+
});
|
|
74
|
+
};
|
|
22
75
|
/**
|
|
23
76
|
* Removes the children that the other children make useless. In an `Or`,
|
|
24
77
|
* `a OR (a AND b)` is `a`. In an `And`, `a AND (a OR b)` is `a`.
|
|
25
78
|
*/
|
|
26
79
|
const absorb = (inner, nodes) => {
|
|
27
|
-
const all =
|
|
28
|
-
return Arr.filter(nodes, (node) => node._tag !== inner || !Arr.some(node.conditions, (part) =>
|
|
80
|
+
const all = new Set(Arr.map(nodes, keyOf));
|
|
81
|
+
return Arr.filter(nodes, (node) => node._tag !== inner || !Arr.some(node.conditions, (part) => all.has(keyOf(part))));
|
|
29
82
|
};
|
|
30
83
|
/**
|
|
31
84
|
* Gives the result of an `And` with a `False` child, or of an `Or` with a
|
|
@@ -138,8 +191,8 @@ const testUnder = (prefix, input) => {
|
|
|
138
191
|
return value !== void 0 && value === values[index];
|
|
139
192
|
});
|
|
140
193
|
};
|
|
141
|
-
/** Tests a condition with no predicate on a value. */
|
|
142
|
-
const
|
|
194
|
+
/** Tests a simple condition with no predicate on a value. */
|
|
195
|
+
const testSync = (node, input) => {
|
|
143
196
|
switch (node._tag) {
|
|
144
197
|
case "True": return true;
|
|
145
198
|
case "False": return false;
|
|
@@ -150,27 +203,37 @@ const evaluateSync = (node, input) => {
|
|
|
150
203
|
}
|
|
151
204
|
case "Exists": return getField(input.value, node.field) !== null;
|
|
152
205
|
case "Under": return testUnder(node.prefix, input);
|
|
153
|
-
case "And": return Arr.every(node.conditions, (child) =>
|
|
154
|
-
case "Or": return Arr.some(node.conditions, (child) =>
|
|
155
|
-
case "Not": return !
|
|
206
|
+
case "And": return Arr.every(node.conditions, (child) => testSync(child, input));
|
|
207
|
+
case "Or": return Arr.some(node.conditions, (child) => testSync(child, input));
|
|
208
|
+
case "Not": return !testSync(node.condition, input);
|
|
156
209
|
case "Predicate": throw new Error(`The predicate "${node.name}" needs an Effect. Use Condition.evaluate.`);
|
|
157
210
|
}
|
|
158
211
|
};
|
|
159
212
|
/**
|
|
160
|
-
* Tests a condition on a value. The children of `And` and `Or` run in
|
|
161
|
-
* and they stop at the first child that gives the answer.
|
|
213
|
+
* Tests a simple condition on a value. The children of `And` and `Or` run in
|
|
214
|
+
* order, and they stop at the first child that gives the answer.
|
|
162
215
|
*/
|
|
163
|
-
const
|
|
164
|
-
if (!node.opaque) return Effect.sync(() =>
|
|
216
|
+
const test = (node, input) => {
|
|
217
|
+
if (!node.opaque) return Effect.sync(() => testSync(node, input));
|
|
165
218
|
switch (node._tag) {
|
|
166
219
|
case "Predicate": return runOf(node)(input.value, input.principal);
|
|
167
|
-
case "Not": return Effect.map(
|
|
168
|
-
case "And": return Effect.map(Effect.findFirst(node.conditions, (child) => Effect.map(
|
|
169
|
-
case "Or": return Effect.map(Effect.findFirst(node.conditions, (child) =>
|
|
170
|
-
default: return Effect.sync(() =>
|
|
220
|
+
case "Not": return Effect.map(test(node.condition, input), (value) => !value);
|
|
221
|
+
case "And": return Effect.map(Effect.findFirst(node.conditions, (child) => Effect.map(test(child, input), (value) => !value)), Option.isNone);
|
|
222
|
+
case "Or": return Effect.map(Effect.findFirst(node.conditions, (child) => test(child, input)), Option.isSome);
|
|
223
|
+
default: return Effect.sync(() => testSync(node, input));
|
|
171
224
|
}
|
|
172
225
|
};
|
|
173
226
|
/**
|
|
227
|
+
* Tests a condition with no predicate on a value. The test runs on the
|
|
228
|
+
* simple form of the condition with the principal in it, as a residual does.
|
|
229
|
+
* So a test reads only the fields that the residual reads: a read of a field
|
|
230
|
+
* that is not plain data is a defect, and a field that the answer does not
|
|
231
|
+
* need, as in `x AND False`, is not read.
|
|
232
|
+
*/
|
|
233
|
+
const evaluateSync = (node, input) => testSync(partial(node, input), input);
|
|
234
|
+
/** Tests a condition on a value, as {@link evaluateSync} does. */
|
|
235
|
+
const evaluate = (node, input) => Effect.suspend(() => test(partial(node, input), input));
|
|
236
|
+
/**
|
|
174
237
|
* Puts the value of the principal into an operand. It gives `undefined` for a
|
|
175
238
|
* principal field with no value. See {@link resolve}.
|
|
176
239
|
*/
|
|
@@ -260,8 +323,8 @@ const fields = (node) => {
|
|
|
260
323
|
default: return [];
|
|
261
324
|
}
|
|
262
325
|
};
|
|
263
|
-
return
|
|
326
|
+
return Array.from(new Set(all(node)));
|
|
264
327
|
};
|
|
265
|
-
export { and, evaluate, evaluateSync, fields, not, or, partial, provide, simplify, testCompare };
|
|
328
|
+
export { and, evaluate, evaluateSync, fields, not, or, partial, provide, simplify, structureText, testCompare };
|
|
266
329
|
|
|
267
330
|
//# sourceMappingURL=algebra.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"algebra.js","names":["Nodes","Nodes.And","Nodes.Or","Nodes.Compare","Nodes.Not","Nodes.isScalar","Nodes.runOf","Nodes.literal","Nodes.In","Nodes.Predicate"],"sources":["../../src/internal/algebra.ts"],"sourcesContent":["/**\n * The work on condition nodes: smart constructors, simplification, the step\n * that puts the principal in, tests on a value, and folds.\n *\n * The constructors of the nodes keep each condition small and low, so each\n * walk here is a plain walk with no memo. A shared child is walked again for\n * each place, and the limits of the nodes keep that work small.\n */\n// A predicate gives an Effect with an error type and a service type that this\n// code does not know, so they are `unknown` here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport type * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as HashSet from \"effect/HashSet\"\nimport * as Option from \"effect/Option\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport { readPath } from \"./read.ts\"\n\ntype Node = Nodes.Node\n\nconst { always, never } = Nodes\n\n// Smart constructors\n\n/**\n * Keeps the first node of each structure, in order. A node with a predicate is\n * equal only to itself, as its test is a function.\n */\nconst dedupe = (nodes: ReadonlyArray<Node>): ReadonlyArray<Node> =>\n\tArr.flatten(\n\t\tArr.mapAccum(nodes, HashSet.empty<Node>(), (seen, node) => [\n\t\t\tHashSet.add(seen, node),\n\t\t\tHashSet.has(seen, node) ? [] : [node],\n\t\t])[1],\n\t)\n\n/**\n * Removes the children that the other children make useless. In an `Or`,\n * `a OR (a AND b)` is `a`. In an `And`, `a AND (a OR b)` is `a`.\n */\nconst absorb = (\n\tinner: \"And\" | \"Or\",\n\tnodes: ReadonlyArray<Node>,\n): ReadonlyArray<Node> => {\n\tconst all = HashSet.fromIterable(nodes)\n\treturn Arr.filter(\n\t\tnodes,\n\t\t(node) =>\n\t\t\tnode._tag !== inner ||\n\t\t\t!Arr.some(node.conditions, (part) => HashSet.has(all, part)),\n\t)\n}\n\n/**\n * Gives the result of an `And` with a `False` child, or of an `Or` with a\n * `True` child. The children are tested in order, so a predicate before the\n * constant still runs, and it may fail. When there is such a predicate, the\n * children before the constant stay, so the failure is not lost.\n */\nconst shortCircuit = (\n\ttag: \"And\" | \"Or\",\n\tchildren: ReadonlyArray<Node>,\n\tindex: number,\n): Node => {\n\tconst constant = tag === \"And\" ? never : always\n\tconst neutral = tag === \"And\" ? \"True\" : \"False\"\n\tconst before = Arr.filter(\n\t\tArr.take(children, index),\n\t\t(child) => child._tag !== neutral,\n\t)\n\tif (!Arr.some(before, (child) => child.opaque)) {\n\t\treturn constant\n\t}\n\treturn tag === \"And\"\n\t\t? new Nodes.And([...before, constant])\n\t\t: new Nodes.Or([...before, constant])\n}\n\n/**\n * Makes an `And` or an `Or` of simple children, and makes it simple. It looks\n * one level down only:\n * - it puts the children of a child of the same kind into the node;\n * - it stops at a `False` in an `And`, or at a `True` in an `Or`;\n * - it removes `True` from an `And`, `False` from an `Or`, copies, and the\n * children that other children make useless.\n *\n * A predicate may fail, and it may give another answer when it runs again. So\n * when a child has a predicate, the children stay as they are, and each one\n * runs in its place. A child with a predicate is not put into the node either.\n */\nconst junction = (tag: \"And\" | \"Or\", children: ReadonlyArray<Node>): Node => {\n\tconst constant = tag === \"And\" ? \"False\" : \"True\"\n\tconst neutral = tag === \"And\" ? \"True\" : \"False\"\n\tconst flat = Arr.flatMap(children, (child) =>\n\t\tchild._tag === tag && !child.opaque ? child.conditions : [child],\n\t)\n\treturn Option.match(\n\t\tArr.findFirstIndex(flat, (child) => child._tag === constant),\n\t\t{\n\t\t\tonSome: (stop) => shortCircuit(tag, flat, stop),\n\t\t\tonNone: () => {\n\t\t\t\tconst kept = Arr.filter(flat, (child) => child._tag !== neutral)\n\t\t\t\tconst unique = Arr.some(kept, (child) => child.opaque)\n\t\t\t\t\t? kept\n\t\t\t\t\t: absorb(tag === \"And\" ? \"Or\" : \"And\", dedupe(kept))\n\t\t\t\treturn unique.length === 0\n\t\t\t\t\t? tag === \"And\"\n\t\t\t\t\t\t? always\n\t\t\t\t\t\t: never\n\t\t\t\t\t: unique.length === 1\n\t\t\t\t\t\t? unique[0]!\n\t\t\t\t\t\t: tag === \"And\"\n\t\t\t\t\t\t\t? new Nodes.And(unique)\n\t\t\t\t\t\t\t: new Nodes.Or(unique)\n\t\t\t},\n\t\t},\n\t)\n}\n\n/** An `And` of simple children, made simple. */\nexport const and = (children: ReadonlyArray<Node>): Node =>\n\tjunction(\"And\", children)\n\n/** An `Or` of simple children, made simple. */\nexport const or = (children: ReadonlyArray<Node>): Node =>\n\tjunction(\"Or\", children)\n\n/** A `Not` of a simple child, made simple. */\nexport const not = (child: Node): Node => {\n\tswitch (child._tag) {\n\t\tcase \"True\":\n\t\t\treturn never\n\t\tcase \"False\":\n\t\t\treturn always\n\t\tcase \"Not\":\n\t\t\treturn child.condition\n\t\tcase \"Compare\":\n\t\t\t// A principal field with no value makes `eq` and `ne` both false, so\n\t\t\t// only a fixed value lets `not eq` become `ne`.\n\t\t\treturn child.value._tag === \"Literal\" &&\n\t\t\t\t(child.op === \"eq\" || child.op === \"ne\")\n\t\t\t\t? new Nodes.Compare({\n\t\t\t\t\t\top: child.op === \"eq\" ? \"ne\" : \"eq\",\n\t\t\t\t\t\tfield: child.field,\n\t\t\t\t\t\tvalue: child.value,\n\t\t\t\t\t})\n\t\t\t\t: new Nodes.Not(child)\n\t\tdefault:\n\t\t\treturn new Nodes.Not(child)\n\t}\n}\n\n/** Makes a condition simple, from its leaves up. */\nexport const simplify = (node: Node): Node => {\n\tswitch (node._tag) {\n\t\tcase \"And\":\n\t\tcase \"Or\":\n\t\t\treturn junction(node._tag, Arr.map(node.conditions, simplify))\n\t\tcase \"Not\":\n\t\t\treturn node.condition.pipe(simplify, not)\n\t\tcase \"In\":\n\t\t\treturn node.values.length === 0 ? never : node\n\t\tdefault:\n\t\t\treturn node\n\t}\n}\n\n// Tests on a value\n\n/** The data that a condition needs for a test. */\nexport interface Input {\n\treadonly value: object\n\treadonly principal?: object | undefined\n\treadonly path?: ReadonlyArray<Resource.PathSegment> | undefined\n}\n\n/**\n * Gets a field of a value by a path with dots. It reads only plain data: see\n * `readPart`. A missing field, or a field under a value that is not an object,\n * gives `null`.\n */\nconst getField = (value: unknown, path: string): unknown =>\n\treadPath(value, path.split(\".\"))\n\n/**\n * Gives the value of an operand. A principal field that is `null`, missing, or\n * not a scalar gives `undefined`. No field of a resource is `undefined`, so a\n * test with it is never true. This keeps the check closed: a principal with no\n * id never matches a resource with no owner.\n */\nconst resolve = (operand: Nodes.Operand, principal: unknown): unknown => {\n\tif (operand._tag === \"Literal\") {\n\t\treturn operand.value\n\t}\n\tconst value = getField(principal, operand.path)\n\treturn value !== null && Nodes.isScalar(value) ? value : undefined\n}\n\nexport const testCompare = (\n\top: Nodes.CompareOperator,\n\tleft: unknown,\n\tright: unknown,\n): boolean => {\n\tif (right === undefined) {\n\t\treturn false\n\t}\n\tswitch (op) {\n\t\tcase \"eq\":\n\t\t\treturn left === right\n\t\tcase \"ne\":\n\t\t\treturn left !== right\n\t\tdefault: {\n\t\t\tconst ordered =\n\t\t\t\t(typeof left === \"number\" && typeof right === \"number\") ||\n\t\t\t\t(typeof left === \"string\" && typeof right === \"string\")\n\t\t\tif (!ordered) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst a = left as number | string\n\t\t\tconst b = right as number | string\n\t\t\tswitch (op) {\n\t\t\t\tcase \"lt\":\n\t\t\t\t\treturn a < b\n\t\t\t\tcase \"lte\":\n\t\t\t\t\treturn a <= b\n\t\t\t\tcase \"gt\":\n\t\t\t\t\treturn a > b\n\t\t\t\tcase \"gte\":\n\t\t\t\t\treturn a >= b\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Gives the values of the path of a resource, or `undefined` for no path. */\nconst pathValues = (\n\tvalue: unknown,\n\tpath: ReadonlyArray<Resource.PathSegment> | undefined,\n): ReadonlyArray<unknown> | undefined =>\n\tpath?.map((segment) =>\n\t\ttypeof segment === \"string\" ? segment : getField(value, segment.field),\n\t)\n\nconst testUnder = (\n\tprefix: ReadonlyArray<Nodes.Operand>,\n\tinput: Input,\n): boolean => {\n\tconst values = pathValues(input.value, input.path)\n\tif (values === undefined || prefix.length > values.length) {\n\t\treturn false\n\t}\n\t// An operand with no value never matches, also a hole in a path that gives\n\t// `undefined` too.\n\treturn prefix.every((operand, index) => {\n\t\tconst value = resolve(operand, input.principal)\n\t\treturn value !== undefined && value === values[index]\n\t})\n}\n\n/** Tests a condition with no predicate on a value. */\nexport const evaluateSync = (node: Node, input: Input): boolean => {\n\tswitch (node._tag) {\n\t\tcase \"True\":\n\t\t\treturn true\n\t\tcase \"False\":\n\t\t\treturn false\n\t\tcase \"Compare\":\n\t\t\treturn testCompare(\n\t\t\t\tnode.op,\n\t\t\t\tgetField(input.value, node.field),\n\t\t\t\tresolve(node.value, input.principal),\n\t\t\t)\n\t\tcase \"In\": {\n\t\t\tconst field = getField(input.value, node.field)\n\t\t\treturn node.values.some(\n\t\t\t\t(operand) => resolve(operand, input.principal) === field,\n\t\t\t)\n\t\t}\n\t\tcase \"Exists\":\n\t\t\treturn getField(input.value, node.field) !== null\n\t\tcase \"Under\":\n\t\t\treturn testUnder(node.prefix, input)\n\t\tcase \"And\":\n\t\t\treturn Arr.every(node.conditions, (child) => evaluateSync(child, input))\n\t\tcase \"Or\":\n\t\t\treturn Arr.some(node.conditions, (child) => evaluateSync(child, input))\n\t\tcase \"Not\":\n\t\t\treturn !evaluateSync(node.condition, input)\n\t\tcase \"Predicate\":\n\t\t\t// The types stop this. A predicate needs `evaluate`.\n\t\t\tthrow new Error(\n\t\t\t\t`The predicate \"${node.name}\" needs an Effect. Use Condition.evaluate.`,\n\t\t\t)\n\t}\n}\n\n/**\n * Tests a condition on a value. The children of `And` and `Or` run in order,\n * and they stop at the first child that gives the answer.\n */\nexport const evaluate = (\n\tnode: Node,\n\tinput: Input,\n): Effect.Effect<boolean, unknown, unknown> => {\n\tif (!node.opaque) {\n\t\treturn Effect.sync(() => evaluateSync(node, input))\n\t}\n\tswitch (node._tag) {\n\t\tcase \"Predicate\":\n\t\t\treturn Nodes.runOf(node)(input.value, input.principal)\n\t\tcase \"Not\":\n\t\t\treturn Effect.map(evaluate(node.condition, input), (value) => !value)\n\t\tcase \"And\":\n\t\t\treturn Effect.map(\n\t\t\t\tEffect.findFirst(node.conditions, (child) =>\n\t\t\t\t\tEffect.map(evaluate(child, input), (value) => !value),\n\t\t\t\t),\n\t\t\t\tOption.isNone,\n\t\t\t)\n\t\tcase \"Or\":\n\t\t\treturn Effect.map(\n\t\t\t\tEffect.findFirst(node.conditions, (child) => evaluate(child, input)),\n\t\t\t\tOption.isSome,\n\t\t\t)\n\t\tdefault:\n\t\t\treturn Effect.sync(() => evaluateSync(node, input))\n\t}\n}\n\n// The principal and the path\n\n/**\n * Puts the value of the principal into an operand. It gives `undefined` for a\n * principal field with no value. See {@link resolve}.\n */\nconst bindOperand = (\n\toperand: Nodes.Operand,\n\tprincipal: unknown,\n): Nodes.Literal | undefined => {\n\tif (operand._tag === \"Literal\") {\n\t\treturn operand\n\t}\n\tconst value = resolve(operand, principal)\n\treturn value === undefined ? undefined : Nodes.literal(value as Nodes.Scalar)\n}\n\nconst expandUnder = (\n\tprefix: ReadonlyArray<Nodes.Operand>,\n\tprincipal: unknown,\n\tpath: ReadonlyArray<Resource.PathSegment> | undefined,\n): Node => {\n\tif (path === undefined || prefix.length > path.length) {\n\t\treturn never\n\t}\n\treturn and(\n\t\tArr.map(prefix, (operand, index): Node => {\n\t\t\tconst value = resolve(operand, principal)\n\t\t\tconst segment = path[index]!\n\t\t\treturn value === undefined\n\t\t\t\t? never\n\t\t\t\t: typeof segment === \"string\"\n\t\t\t\t\t? segment === value\n\t\t\t\t\t\t? always\n\t\t\t\t\t\t: never\n\t\t\t\t\t: new Nodes.Compare({\n\t\t\t\t\t\t\top: \"eq\",\n\t\t\t\t\t\t\tfield: segment.field,\n\t\t\t\t\t\t\tvalue: Nodes.literal(value as Nodes.Scalar),\n\t\t\t\t\t\t})\n\t\t}),\n\t)\n}\n\n/** The principal and the path of the resource, for {@link partial}. */\nexport interface PartialInput {\n\treadonly principal?: object | undefined\n\treadonly path?: ReadonlyArray<Resource.PathSegment> | undefined\n}\n\n/**\n * Puts the principal into a condition and expands `Under` with the path of the\n * resource. The result is simple. It has no principal fields and no `Under`\n * nodes. A predicate keeps the principal.\n */\nexport const partial = (node: Node, input: PartialInput): Node => {\n\tconst bind = (current: Node): Node => {\n\t\tswitch (current._tag) {\n\t\t\tcase \"Compare\": {\n\t\t\t\tconst value = bindOperand(current.value, input.principal)\n\t\t\t\treturn value === undefined\n\t\t\t\t\t? never\n\t\t\t\t\t: value === current.value\n\t\t\t\t\t\t? current\n\t\t\t\t\t\t: new Nodes.Compare({ op: current.op, field: current.field, value })\n\t\t\t}\n\t\t\tcase \"In\": {\n\t\t\t\tconst values = Arr.flatMap(current.values, (operand) =>\n\t\t\t\t\tOption.toArray(\n\t\t\t\t\t\tOption.fromUndefinedOr(bindOperand(operand, input.principal)),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\treturn values.length === 0\n\t\t\t\t\t? never\n\t\t\t\t\t: new Nodes.In({ field: current.field, values })\n\t\t\t}\n\t\t\tcase \"Under\":\n\t\t\t\treturn expandUnder(current.prefix, input.principal, input.path)\n\t\t\tcase \"And\":\n\t\t\t\treturn and(Arr.map(current.conditions, bind))\n\t\t\tcase \"Or\":\n\t\t\t\treturn or(Arr.map(current.conditions, bind))\n\t\t\tcase \"Not\":\n\t\t\t\treturn current.condition.pipe(bind, not)\n\t\t\tcase \"Predicate\": {\n\t\t\t\tconst run = Nodes.runOf(current)\n\t\t\t\treturn new Nodes.Predicate({\n\t\t\t\t\tname: current.name,\n\t\t\t\t\trun: (value) => run(value, input.principal),\n\t\t\t\t})\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn current\n\t\t}\n\t}\n\treturn bind(node)\n}\n\n/** Gives the services in `context` to every predicate of a condition. */\nexport const provide = (node: Node, context: Context.Context<never>): Node => {\n\tif (!node.opaque) {\n\t\treturn node\n\t}\n\tswitch (node._tag) {\n\t\tcase \"Predicate\": {\n\t\t\tconst run = Nodes.runOf(node)\n\t\t\treturn new Nodes.Predicate({\n\t\t\t\tname: node.name,\n\t\t\t\trun: (value, principal) =>\n\t\t\t\t\tEffect.provideContext(run(value, principal), context),\n\t\t\t})\n\t\t}\n\t\tcase \"And\":\n\t\t\treturn new Nodes.And(\n\t\t\t\tArr.map(node.conditions, (child) => provide(child, context)),\n\t\t\t)\n\t\tcase \"Or\":\n\t\t\treturn new Nodes.Or(\n\t\t\t\tArr.map(node.conditions, (child) => provide(child, context)),\n\t\t\t)\n\t\tcase \"Not\":\n\t\t\treturn new Nodes.Not(provide(node.condition, context))\n\t\tdefault:\n\t\t\treturn node\n\t}\n}\n\n/** The fields that a condition tests, in the order that they first come. */\nexport const fields = (node: Node): ReadonlyArray<string> => {\n\tconst all = (current: Node): ReadonlyArray<string> => {\n\t\tswitch (current._tag) {\n\t\t\tcase \"Compare\":\n\t\t\tcase \"In\":\n\t\t\tcase \"Exists\":\n\t\t\t\treturn [current.field]\n\t\t\tcase \"And\":\n\t\t\tcase \"Or\":\n\t\t\t\treturn Arr.flatMap(current.conditions, all)\n\t\t\tcase \"Not\":\n\t\t\t\treturn all(current.condition)\n\t\t\tdefault:\n\t\t\t\treturn []\n\t\t}\n\t}\n\treturn Arr.flatten(\n\t\tArr.mapAccum(all(node), HashSet.empty<string>(), (seen, field) => [\n\t\t\tHashSet.add(seen, field),\n\t\t\tHashSet.has(seen, field) ? [] : [field],\n\t\t])[1],\n\t)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"algebra.js","names":["Nodes","Nodes.And","Nodes.Or","Nodes.Compare","Nodes.Not","Nodes.isScalar","Nodes.runOf","Nodes.literal","Nodes.In","Nodes.Predicate"],"sources":["../../src/internal/algebra.ts"],"sourcesContent":["/**\n * The work on condition nodes: smart constructors, simplification, the step\n * that puts the principal in, tests on a value, and folds.\n *\n * The constructors of the nodes keep each condition small and low, so each\n * walk here is a plain walk with no memo. A shared child is walked again for\n * each place, and the limits of the nodes keep that work small.\n */\n// A predicate gives an Effect with an error type and a service type that this\n// code does not know, so they are `unknown` here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport type * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport { readPath } from \"./read.ts\"\n\ntype Node = Nodes.Node\n\nconst { always, never } = Nodes\n\n// Keys of structure\n\n/** The text of an operand. The type is in it, so `\"1\"` is not `1`. */\nconst operandText = (operand: Nodes.Operand): ReadonlyArray<string> =>\n\toperand._tag === \"Literal\"\n\t\t? [typeof operand.value, String(operand.value)]\n\t\t: [\"principal\", operand.path]\n\n/** The parts of the text of a condition with no predicate. */\nconst shapeOf = (node: Node): unknown => {\n\tswitch (node._tag) {\n\t\tcase \"True\":\n\t\tcase \"False\":\n\t\t\treturn [node._tag]\n\t\tcase \"Compare\":\n\t\t\treturn [node._tag, node.op, node.field, operandText(node.value)]\n\t\tcase \"In\":\n\t\t\treturn [node._tag, node.field, Arr.map(node.values, operandText)]\n\t\tcase \"Exists\":\n\t\t\treturn [node._tag, node.field]\n\t\tcase \"Under\":\n\t\t\treturn [node._tag, Arr.map(node.prefix, operandText)]\n\t\tcase \"And\":\n\t\tcase \"Or\":\n\t\t\treturn [node._tag, Arr.map(node.conditions, shapeOf)]\n\t\tcase \"Not\":\n\t\t\treturn [node._tag, shapeOf(node.condition)]\n\t\tcase \"Predicate\":\n\t\t\t// A smart constructor and a search do not ask for the text of a\n\t\t\t// predicate: it is equal only to itself.\n\t\t\tthrow new Error(\"A predicate has no text of structure\")\n\t}\n}\n\n/**\n * The text of the structure of a condition with no predicate. Two conditions\n * with one text give the same answer for every value. It reads all of the\n * condition, so a search counts its size in the budget.\n */\nexport const structureText = (node: Node): string =>\n\tJSON.stringify(shapeOf(node))\n\n/**\n * The most nodes of a condition that a smart constructor reads for its key.\n * A larger child is its own key, so the work of a constructor stays small.\n */\nconst keyNodes = 64\n\n/**\n * The key of a child in a smart constructor: the text of its structure, or\n * the node itself when it is large. The keys go into a native `Set`, not a\n * `HashSet` of Effect. The hash of a string in Effect has no random seed, so\n * a policy could hold very many strings with one hash, and each test of the\n * set would read all of them. A native `Set` finds a node by the node itself,\n * and it hashes a string with the random seed of the engine.\n */\nconst keyOf = (node: Node): string | Node =>\n\tnode.size > keyNodes ? node : structureText(node)\n\n/**\n * Keeps the first node of each structure, in order. A large node is a copy\n * only of itself.\n */\nconst dedupe = (nodes: ReadonlyArray<Node>): ReadonlyArray<Node> => {\n\t// The set belongs to this call.\n\tconst seen = new Set<string | Node>()\n\treturn Arr.filter(nodes, (node) => {\n\t\tconst key = keyOf(node)\n\t\tif (seen.has(key)) {\n\t\t\treturn false\n\t\t}\n\t\tseen.add(key)\n\t\treturn true\n\t})\n}\n\n/**\n * Removes the children that the other children make useless. In an `Or`,\n * `a OR (a AND b)` is `a`. In an `And`, `a AND (a OR b)` is `a`.\n */\nconst absorb = (\n\tinner: \"And\" | \"Or\",\n\tnodes: ReadonlyArray<Node>,\n): ReadonlyArray<Node> => {\n\t// A native `Set`, as in `dedupe`.\n\tconst all = new Set(Arr.map(nodes, keyOf))\n\treturn Arr.filter(\n\t\tnodes,\n\t\t(node) =>\n\t\t\tnode._tag !== inner ||\n\t\t\t!Arr.some(node.conditions, (part) => all.has(keyOf(part))),\n\t)\n}\n\n// Smart constructors\n\n/**\n * Gives the result of an `And` with a `False` child, or of an `Or` with a\n * `True` child. The children are tested in order, so a predicate before the\n * constant still runs, and it may fail. When there is such a predicate, the\n * children before the constant stay, so the failure is not lost.\n */\nconst shortCircuit = (\n\ttag: \"And\" | \"Or\",\n\tchildren: ReadonlyArray<Node>,\n\tindex: number,\n): Node => {\n\tconst constant = tag === \"And\" ? never : always\n\tconst neutral = tag === \"And\" ? \"True\" : \"False\"\n\tconst before = Arr.filter(\n\t\tArr.take(children, index),\n\t\t(child) => child._tag !== neutral,\n\t)\n\tif (!Arr.some(before, (child) => child.opaque)) {\n\t\treturn constant\n\t}\n\treturn tag === \"And\"\n\t\t? new Nodes.And([...before, constant])\n\t\t: new Nodes.Or([...before, constant])\n}\n\n/**\n * Makes an `And` or an `Or` of simple children, and makes it simple. It looks\n * one level down only:\n * - it puts the children of a child of the same kind into the node;\n * - it stops at a `False` in an `And`, or at a `True` in an `Or`;\n * - it removes `True` from an `And`, `False` from an `Or`, copies, and the\n * children that other children make useless.\n *\n * A predicate may fail, and it may give another answer when it runs again. So\n * when a child has a predicate, the children stay as they are, and each one\n * runs in its place. A child with a predicate is not put into the node either.\n */\nconst junction = (tag: \"And\" | \"Or\", children: ReadonlyArray<Node>): Node => {\n\tconst constant = tag === \"And\" ? \"False\" : \"True\"\n\tconst neutral = tag === \"And\" ? \"True\" : \"False\"\n\tconst flat = Arr.flatMap(children, (child) =>\n\t\tchild._tag === tag && !child.opaque ? child.conditions : [child],\n\t)\n\treturn Option.match(\n\t\tArr.findFirstIndex(flat, (child) => child._tag === constant),\n\t\t{\n\t\t\tonSome: (stop) => shortCircuit(tag, flat, stop),\n\t\t\tonNone: () => {\n\t\t\t\tconst kept = Arr.filter(flat, (child) => child._tag !== neutral)\n\t\t\t\tconst unique = Arr.some(kept, (child) => child.opaque)\n\t\t\t\t\t? kept\n\t\t\t\t\t: absorb(tag === \"And\" ? \"Or\" : \"And\", dedupe(kept))\n\t\t\t\treturn unique.length === 0\n\t\t\t\t\t? tag === \"And\"\n\t\t\t\t\t\t? always\n\t\t\t\t\t\t: never\n\t\t\t\t\t: unique.length === 1\n\t\t\t\t\t\t? unique[0]!\n\t\t\t\t\t\t: tag === \"And\"\n\t\t\t\t\t\t\t? new Nodes.And(unique)\n\t\t\t\t\t\t\t: new Nodes.Or(unique)\n\t\t\t},\n\t\t},\n\t)\n}\n\n/** An `And` of simple children, made simple. */\nexport const and = (children: ReadonlyArray<Node>): Node =>\n\tjunction(\"And\", children)\n\n/** An `Or` of simple children, made simple. */\nexport const or = (children: ReadonlyArray<Node>): Node =>\n\tjunction(\"Or\", children)\n\n/** A `Not` of a simple child, made simple. */\nexport const not = (child: Node): Node => {\n\tswitch (child._tag) {\n\t\tcase \"True\":\n\t\t\treturn never\n\t\tcase \"False\":\n\t\t\treturn always\n\t\tcase \"Not\":\n\t\t\treturn child.condition\n\t\tcase \"Compare\":\n\t\t\t// A principal field with no value makes `eq` and `ne` both false, so\n\t\t\t// only a fixed value lets `not eq` become `ne`.\n\t\t\treturn child.value._tag === \"Literal\" &&\n\t\t\t\t(child.op === \"eq\" || child.op === \"ne\")\n\t\t\t\t? new Nodes.Compare({\n\t\t\t\t\t\top: child.op === \"eq\" ? \"ne\" : \"eq\",\n\t\t\t\t\t\tfield: child.field,\n\t\t\t\t\t\tvalue: child.value,\n\t\t\t\t\t})\n\t\t\t\t: new Nodes.Not(child)\n\t\tdefault:\n\t\t\treturn new Nodes.Not(child)\n\t}\n}\n\n/** Makes a condition simple, from its leaves up. */\nexport const simplify = (node: Node): Node => {\n\tswitch (node._tag) {\n\t\tcase \"And\":\n\t\tcase \"Or\":\n\t\t\treturn junction(node._tag, Arr.map(node.conditions, simplify))\n\t\tcase \"Not\":\n\t\t\treturn node.condition.pipe(simplify, not)\n\t\tcase \"In\":\n\t\t\treturn node.values.length === 0 ? never : node\n\t\tdefault:\n\t\t\treturn node\n\t}\n}\n\n// Tests on a value\n\n/** The data that a condition needs for a test. */\nexport interface Input {\n\treadonly value: object\n\treadonly principal?: object | undefined\n\treadonly path?: ReadonlyArray<Resource.PathSegment> | undefined\n}\n\n/**\n * Gets a field of a value by a path with dots. It reads only plain data: see\n * `readPart`. A missing field, or a field under a value that is not an object,\n * gives `null`.\n */\nconst getField = (value: unknown, path: string): unknown =>\n\treadPath(value, path.split(\".\"))\n\n/**\n * Gives the value of an operand. A principal field that is `null`, missing, or\n * not a scalar gives `undefined`. No field of a resource is `undefined`, so a\n * test with it is never true. This keeps the check closed: a principal with no\n * id never matches a resource with no owner.\n */\nconst resolve = (operand: Nodes.Operand, principal: unknown): unknown => {\n\tif (operand._tag === \"Literal\") {\n\t\treturn operand.value\n\t}\n\tconst value = getField(principal, operand.path)\n\treturn value !== null && Nodes.isScalar(value) ? value : undefined\n}\n\nexport const testCompare = (\n\top: Nodes.CompareOperator,\n\tleft: unknown,\n\tright: unknown,\n): boolean => {\n\tif (right === undefined) {\n\t\treturn false\n\t}\n\tswitch (op) {\n\t\tcase \"eq\":\n\t\t\treturn left === right\n\t\tcase \"ne\":\n\t\t\treturn left !== right\n\t\tdefault: {\n\t\t\tconst ordered =\n\t\t\t\t(typeof left === \"number\" && typeof right === \"number\") ||\n\t\t\t\t(typeof left === \"string\" && typeof right === \"string\")\n\t\t\tif (!ordered) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst a = left as number | string\n\t\t\tconst b = right as number | string\n\t\t\tswitch (op) {\n\t\t\t\tcase \"lt\":\n\t\t\t\t\treturn a < b\n\t\t\t\tcase \"lte\":\n\t\t\t\t\treturn a <= b\n\t\t\t\tcase \"gt\":\n\t\t\t\t\treturn a > b\n\t\t\t\tcase \"gte\":\n\t\t\t\t\treturn a >= b\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Gives the values of the path of a resource, or `undefined` for no path. */\nconst pathValues = (\n\tvalue: unknown,\n\tpath: ReadonlyArray<Resource.PathSegment> | undefined,\n): ReadonlyArray<unknown> | undefined =>\n\tpath?.map((segment) =>\n\t\ttypeof segment === \"string\" ? segment : getField(value, segment.field),\n\t)\n\nconst testUnder = (\n\tprefix: ReadonlyArray<Nodes.Operand>,\n\tinput: Input,\n): boolean => {\n\tconst values = pathValues(input.value, input.path)\n\tif (values === undefined || prefix.length > values.length) {\n\t\treturn false\n\t}\n\t// An operand with no value never matches, also a hole in a path that gives\n\t// `undefined` too.\n\treturn prefix.every((operand, index) => {\n\t\tconst value = resolve(operand, input.principal)\n\t\treturn value !== undefined && value === values[index]\n\t})\n}\n\n/** Tests a simple condition with no predicate on a value. */\nconst testSync = (node: Node, input: Input): boolean => {\n\tswitch (node._tag) {\n\t\tcase \"True\":\n\t\t\treturn true\n\t\tcase \"False\":\n\t\t\treturn false\n\t\tcase \"Compare\":\n\t\t\treturn testCompare(\n\t\t\t\tnode.op,\n\t\t\t\tgetField(input.value, node.field),\n\t\t\t\tresolve(node.value, input.principal),\n\t\t\t)\n\t\tcase \"In\": {\n\t\t\tconst field = getField(input.value, node.field)\n\t\t\treturn node.values.some(\n\t\t\t\t(operand) => resolve(operand, input.principal) === field,\n\t\t\t)\n\t\t}\n\t\tcase \"Exists\":\n\t\t\treturn getField(input.value, node.field) !== null\n\t\tcase \"Under\":\n\t\t\treturn testUnder(node.prefix, input)\n\t\tcase \"And\":\n\t\t\treturn Arr.every(node.conditions, (child) => testSync(child, input))\n\t\tcase \"Or\":\n\t\t\treturn Arr.some(node.conditions, (child) => testSync(child, input))\n\t\tcase \"Not\":\n\t\t\treturn !testSync(node.condition, input)\n\t\tcase \"Predicate\":\n\t\t\t// The types stop this. A predicate needs `evaluate`.\n\t\t\tthrow new Error(\n\t\t\t\t`The predicate \"${node.name}\" needs an Effect. Use Condition.evaluate.`,\n\t\t\t)\n\t}\n}\n\n/**\n * Tests a simple condition on a value. The children of `And` and `Or` run in\n * order, and they stop at the first child that gives the answer.\n */\nconst test = (\n\tnode: Node,\n\tinput: Input,\n): Effect.Effect<boolean, unknown, unknown> => {\n\tif (!node.opaque) {\n\t\treturn Effect.sync(() => testSync(node, input))\n\t}\n\tswitch (node._tag) {\n\t\tcase \"Predicate\":\n\t\t\treturn Nodes.runOf(node)(input.value, input.principal)\n\t\tcase \"Not\":\n\t\t\treturn Effect.map(test(node.condition, input), (value) => !value)\n\t\tcase \"And\":\n\t\t\treturn Effect.map(\n\t\t\t\tEffect.findFirst(node.conditions, (child) =>\n\t\t\t\t\tEffect.map(test(child, input), (value) => !value),\n\t\t\t\t),\n\t\t\t\tOption.isNone,\n\t\t\t)\n\t\tcase \"Or\":\n\t\t\treturn Effect.map(\n\t\t\t\tEffect.findFirst(node.conditions, (child) => test(child, input)),\n\t\t\t\tOption.isSome,\n\t\t\t)\n\t\tdefault:\n\t\t\treturn Effect.sync(() => testSync(node, input))\n\t}\n}\n\n/**\n * Tests a condition with no predicate on a value. The test runs on the\n * simple form of the condition with the principal in it, as a residual does.\n * So a test reads only the fields that the residual reads: a read of a field\n * that is not plain data is a defect, and a field that the answer does not\n * need, as in `x AND False`, is not read.\n */\nexport const evaluateSync = (node: Node, input: Input): boolean =>\n\ttestSync(partial(node, input), input)\n\n/** Tests a condition on a value, as {@link evaluateSync} does. */\nexport const evaluate = (\n\tnode: Node,\n\tinput: Input,\n): Effect.Effect<boolean, unknown, unknown> =>\n\tEffect.suspend(() => test(partial(node, input), input))\n\n// The principal and the path\n\n/**\n * Puts the value of the principal into an operand. It gives `undefined` for a\n * principal field with no value. See {@link resolve}.\n */\nconst bindOperand = (\n\toperand: Nodes.Operand,\n\tprincipal: unknown,\n): Nodes.Literal | undefined => {\n\tif (operand._tag === \"Literal\") {\n\t\treturn operand\n\t}\n\tconst value = resolve(operand, principal)\n\treturn value === undefined ? undefined : Nodes.literal(value as Nodes.Scalar)\n}\n\nconst expandUnder = (\n\tprefix: ReadonlyArray<Nodes.Operand>,\n\tprincipal: unknown,\n\tpath: ReadonlyArray<Resource.PathSegment> | undefined,\n): Node => {\n\tif (path === undefined || prefix.length > path.length) {\n\t\treturn never\n\t}\n\treturn and(\n\t\tArr.map(prefix, (operand, index): Node => {\n\t\t\tconst value = resolve(operand, principal)\n\t\t\tconst segment = path[index]!\n\t\t\treturn value === undefined\n\t\t\t\t? never\n\t\t\t\t: typeof segment === \"string\"\n\t\t\t\t\t? segment === value\n\t\t\t\t\t\t? always\n\t\t\t\t\t\t: never\n\t\t\t\t\t: new Nodes.Compare({\n\t\t\t\t\t\t\top: \"eq\",\n\t\t\t\t\t\t\tfield: segment.field,\n\t\t\t\t\t\t\tvalue: Nodes.literal(value as Nodes.Scalar),\n\t\t\t\t\t\t})\n\t\t}),\n\t)\n}\n\n/** The principal and the path of the resource, for {@link partial}. */\nexport interface PartialInput {\n\treadonly principal?: object | undefined\n\treadonly path?: ReadonlyArray<Resource.PathSegment> | undefined\n}\n\n/**\n * Puts the principal into a condition and expands `Under` with the path of the\n * resource. The result is simple. It has no principal fields and no `Under`\n * nodes. A predicate keeps the principal.\n */\nexport const partial = (node: Node, input: PartialInput): Node => {\n\tconst bind = (current: Node): Node => {\n\t\tswitch (current._tag) {\n\t\t\tcase \"Compare\": {\n\t\t\t\tconst value = bindOperand(current.value, input.principal)\n\t\t\t\treturn value === undefined\n\t\t\t\t\t? never\n\t\t\t\t\t: value === current.value\n\t\t\t\t\t\t? current\n\t\t\t\t\t\t: new Nodes.Compare({ op: current.op, field: current.field, value })\n\t\t\t}\n\t\t\tcase \"In\": {\n\t\t\t\tconst values = Arr.flatMap(current.values, (operand) =>\n\t\t\t\t\tOption.toArray(\n\t\t\t\t\t\tOption.fromUndefinedOr(bindOperand(operand, input.principal)),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\treturn values.length === 0\n\t\t\t\t\t? never\n\t\t\t\t\t: new Nodes.In({ field: current.field, values })\n\t\t\t}\n\t\t\tcase \"Under\":\n\t\t\t\treturn expandUnder(current.prefix, input.principal, input.path)\n\t\t\tcase \"And\":\n\t\t\t\treturn and(Arr.map(current.conditions, bind))\n\t\t\tcase \"Or\":\n\t\t\t\treturn or(Arr.map(current.conditions, bind))\n\t\t\tcase \"Not\":\n\t\t\t\treturn current.condition.pipe(bind, not)\n\t\t\tcase \"Predicate\": {\n\t\t\t\tconst run = Nodes.runOf(current)\n\t\t\t\treturn new Nodes.Predicate({\n\t\t\t\t\tname: current.name,\n\t\t\t\t\trun: (value) => run(value, input.principal),\n\t\t\t\t})\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn current\n\t\t}\n\t}\n\treturn bind(node)\n}\n\n/** Gives the services in `context` to every predicate of a condition. */\nexport const provide = (node: Node, context: Context.Context<never>): Node => {\n\tif (!node.opaque) {\n\t\treturn node\n\t}\n\tswitch (node._tag) {\n\t\tcase \"Predicate\": {\n\t\t\tconst run = Nodes.runOf(node)\n\t\t\treturn new Nodes.Predicate({\n\t\t\t\tname: node.name,\n\t\t\t\trun: (value, principal) =>\n\t\t\t\t\tEffect.provideContext(run(value, principal), context),\n\t\t\t})\n\t\t}\n\t\tcase \"And\":\n\t\t\treturn new Nodes.And(\n\t\t\t\tArr.map(node.conditions, (child) => provide(child, context)),\n\t\t\t)\n\t\tcase \"Or\":\n\t\t\treturn new Nodes.Or(\n\t\t\t\tArr.map(node.conditions, (child) => provide(child, context)),\n\t\t\t)\n\t\tcase \"Not\":\n\t\t\treturn new Nodes.Not(provide(node.condition, context))\n\t\tdefault:\n\t\t\treturn node\n\t}\n}\n\n/** The fields that a condition tests, in the order that they first come. */\nexport const fields = (node: Node): ReadonlyArray<string> => {\n\tconst all = (current: Node): ReadonlyArray<string> => {\n\t\tswitch (current._tag) {\n\t\t\tcase \"Compare\":\n\t\t\tcase \"In\":\n\t\t\tcase \"Exists\":\n\t\t\t\treturn [current.field]\n\t\t\tcase \"And\":\n\t\t\tcase \"Or\":\n\t\t\t\treturn Arr.flatMap(current.conditions, all)\n\t\t\tcase \"Not\":\n\t\t\t\treturn all(current.condition)\n\t\t\tdefault:\n\t\t\t\treturn []\n\t\t}\n\t}\n\t// A native `Set`, as in `dedupe`: a field of a condition that is not bound\n\t// to a domain may be any string.\n\treturn Array.from(new Set(all(node)))\n}\n"],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ import { kindOf } from "./resource.js";
|
|
|
7
7
|
import { always as always$1, builder, never as never$1, partial } from "../Condition.js";
|
|
8
8
|
import { ruleIssues } from "./bind.js";
|
|
9
9
|
import { descriptorOf } from "./domain.js";
|
|
10
|
-
import { matchesTarget } from "./rule.js";
|
|
10
|
+
import { fitsOf, matchesTarget } from "./rule.js";
|
|
11
11
|
import { rulesOf } from "./policy-nodes.js";
|
|
12
12
|
import { nodeOf } from "./policy.js";
|
|
13
13
|
import { decideNode, fitsKind } from "./decide.js";
|
|
@@ -178,7 +178,8 @@ const scopedResiduals = (policy, request) => {
|
|
|
178
178
|
kind: request.kind,
|
|
179
179
|
field: request.field,
|
|
180
180
|
input,
|
|
181
|
-
descriptor
|
|
181
|
+
descriptor,
|
|
182
|
+
fits: fitsOf(descriptor.catalog)
|
|
182
183
|
};
|
|
183
184
|
const context = {
|
|
184
185
|
...target,
|
|
@@ -195,6 +196,7 @@ const scopedResiduals = (policy, request) => {
|
|
|
195
196
|
path: descriptor.path
|
|
196
197
|
},
|
|
197
198
|
descriptor,
|
|
199
|
+
fits: fitsOf(descriptor.catalog),
|
|
198
200
|
trace: void 0
|
|
199
201
|
}, []);
|
|
200
202
|
const test = (tag) => builder().predicate(policyPredicate, (value) => kindOf(value) === request.kind ? Effect.map(decision(value), (result) => result._tag === tag) : Effect.succeed(false));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analysis.js","names":["Condition.always","Condition.never","Limits.exceeded","Limits.residualNodes","Algebra.and","Algebra.or","Algebra.not","RuleData.matchesTarget","Condition.partial","Nodes.toNode","Condition.builder","Bind.ruleIssues","DomainData.descriptorOf","PolicyNodes.rulesOf","ResourceData.kindOf","Search.satisfiable","Search.kindsIn","Decision.Deny","Decision.Allow","Decision.NotApplicable"],"sources":["../../src/internal/analysis.ts"],"sourcesContent":["/**\n * The residual conditions and the coverage of a policy. The types are in\n * `Policy.ts`.\n */\n// A policy keeps the error type and the service type of its predicates in the\n// public types. The code below works on the nodes with no types, where they\n// are `unknown`, so the language service check for `unknown` in an Effect is\n// off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Data from \"effect/Data\"\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Algebra from \"./algebra.ts\"\nimport * as Bind from \"./bind.ts\"\nimport type * as Catalog from \"./catalog.ts\"\nimport { decideNode, fitsKind } from \"./decide.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport { nodeOf } from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as RuleData from \"./rule.ts\"\nimport * as Search from \"./search.ts\"\n\ntype Any = Policy.Any\n\ntype Ast = Condition.Ast\n\nconst always: Ast = Condition.always\nconst never: Ast = Condition.never\n\n/**\n * Makes conditions for one residual, with no types. Each step makes the\n * condition simple, and it stops the work when the condition has more nodes\n * than `residualNodes`. A policy tree may use a condition of a child two\n * times, in its allow condition and in its deny condition, so a small policy\n * may give a very large condition.\n */\ninterface Build {\n\t// A list goes in as an array, not as arguments: a call may take only so\n\t// many arguments, and a policy may have more rules.\n\treadonly and: (values: ReadonlyArray<Ast>) => Ast\n\treadonly or: (values: ReadonlyArray<Ast>) => Ast\n\treadonly not: (value: Ast) => Ast\n}\n\nconst limit = (node: Nodes.Node): Ast => {\n\tif (node.size > Limits.residualNodes) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A residual condition\",\n\t\t\t\"residualNodes\",\n\t\t\t\"nodes\",\n\t\t\tnode.size,\n\t\t\tLimits.residualNodes,\n\t\t)\n\t}\n\treturn node\n}\n\nconst build: Build = {\n\tand: (values) => limit(Algebra.and(values as ReadonlyArray<Nodes.Node>)),\n\tor: (values) => limit(Algebra.or(values as ReadonlyArray<Nodes.Node>)),\n\tnot: (value) => (value as Nodes.Node).pipe(Algebra.not, limit),\n}\n\n/** The two residual conditions of a node, with no types. */\ninterface Pair {\n\treadonly allow: Ast\n\treadonly deny: Ast\n}\n\ninterface ResidualContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly input: {\n\t\treadonly principal: object\n\t\treadonly path: Condition.Input[\"path\"]\n\t}\n\t/** The resource of the kind, for the fields that a rule may grant. */\n\treadonly descriptor: Resource.Descriptor\n\t/**\n\t * The condition of each rule, with the principal in it. They are made\n\t * first, from a flat list of the rules, so the walk does not go down the\n\t * levels of the tree and of a condition at once. It is a native map,\n\t * because it must find a rule by the rule itself: two rules with the same\n\t * data are `Equal`.\n\t */\n\treadonly conditions: Map<RuleData.Rule, Ast>\n}\n\nconst makeCondition = (\n\tvalue: RuleData.Rule,\n\tcontext: Omit<ResidualContext, \"conditions\">,\n): Ast =>\n\t!RuleData.matchesTarget(value, context)\n\t\t? never\n\t\t: value.when === undefined\n\t\t\t? always\n\t\t\t: Condition.partial(value.when as Condition.Any, context.input)\n\nconst ruleCondition = (value: RuleData.Rule, context: ResidualContext): Ast =>\n\tcontext.conditions.get(value) ?? makeCondition(value, context)\n\n/**\n * Gives the conditions for \"the first rule that applies is an allow rule\" and\n * \"the first rule that applies is a deny rule\".\n */\nconst firstApplicableResidual = (\n\tvalues: ReadonlyArray<RuleData.Rule>,\n\tcontext: ResidualContext,\n): Pair => {\n\tconst { and, not, or } = build\n\t// The rules go in runs of one tag, from the last run to the first. The\n\t// conditions of a run go into one node at once. With one node for each\n\t// rule, each step would flatten the whole condition again.\n\tconst runs = Arr.isReadonlyArrayNonEmpty(values)\n\t\t? Arr.groupWith(values, (a, b) => a._tag === b._tag)\n\t\t: []\n\treturn Arr.reduceRight(\n\t\truns,\n\t\t{ allow: never, deny: never },\n\t\t(rest: Pair, run): Pair => {\n\t\t\tconst applies = or(Arr.map(run, (value) => ruleCondition(value, context)))\n\t\t\treturn run[0]._tag === \"Allow\"\n\t\t\t\t? {\n\t\t\t\t\t\tallow: or([applies, rest.allow]),\n\t\t\t\t\t\tdeny: and([not(applies), rest.deny]),\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\tallow: and([not(applies), rest.allow]),\n\t\t\t\t\t\tdeny: or([applies, rest.deny]),\n\t\t\t\t\t}\n\t\t},\n\t)\n}\n\n/**\n * Gives the residual pair of a node. A shared child is worked again for each\n * place. The limits of the nodes and of a residual keep this work small.\n */\nconst residualNode = (\n\tnode: PolicyNodes.Node,\n\tcontext: ResidualContext,\n): Pair => {\n\tconst { and, not, or } = build\n\tswitch (node._tag) {\n\t\tcase \"Rules\": {\n\t\t\tswitch (node.algorithm) {\n\t\t\t\tcase \"denyOverrides\": {\n\t\t\t\t\tconst deny = or(\n\t\t\t\t\t\tnode.rules\n\t\t\t\t\t\t\t.filter((value) => value._tag === \"Deny\")\n\t\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t\t)\n\t\t\t\t\tconst allow = and([\n\t\t\t\t\t\tnot(deny),\n\t\t\t\t\t\tor(\n\t\t\t\t\t\t\tnode.rules\n\t\t\t\t\t\t\t\t.filter((value) => value._tag === \"Allow\")\n\t\t\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t\t\t),\n\t\t\t\t\t])\n\t\t\t\t\treturn { allow, deny }\n\t\t\t\t}\n\t\t\t\tcase \"permitOverrides\": {\n\t\t\t\t\tconst allow = or(\n\t\t\t\t\t\tnode.rules\n\t\t\t\t\t\t\t.filter((value) => value._tag === \"Allow\")\n\t\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t\t)\n\t\t\t\t\tconst deny = and([\n\t\t\t\t\t\tnot(allow),\n\t\t\t\t\t\tor(\n\t\t\t\t\t\t\tnode.rules\n\t\t\t\t\t\t\t\t.filter((value) => value._tag === \"Deny\")\n\t\t\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t\t\t),\n\t\t\t\t\t])\n\t\t\t\t\treturn { allow, deny }\n\t\t\t\t}\n\t\t\t\tcase \"firstApplicable\":\n\t\t\t\t\treturn firstApplicableResidual(node.rules, context)\n\t\t\t\tcase \"lastApplicable\":\n\t\t\t\t\treturn firstApplicableResidual(node.rules.slice().reverse(), context)\n\t\t\t}\n\t\t}\n\t\t// falls through: every algorithm returns above\n\t\tcase \"All\": {\n\t\t\tif (node.policies.length === 0) {\n\t\t\t\treturn { allow: never, deny: never }\n\t\t\t}\n\t\t\tconst pairs = node.policies.map((child) => residualNode(child, context))\n\t\t\tconst deny = or(pairs.map((pair) => pair.deny))\n\t\t\tconst allow = and([...pairs.map((pair) => pair.allow), not(deny)])\n\t\t\treturn { allow, deny }\n\t\t}\n\t\tcase \"Any\": {\n\t\t\tconst pairs = node.policies.map((child) => residualNode(child, context))\n\t\t\tconst allow = or(pairs.map((pair) => pair.allow))\n\t\t\tconst deny = and([not(allow), or(pairs.map((pair) => pair.deny))])\n\t\t\treturn { allow, deny }\n\t\t}\n\t\tcase \"FirstOf\": {\n\t\t\t// The first child that gives an answer wins.\n\t\t\treturn Arr.reduceRight(\n\t\t\t\tnode.policies,\n\t\t\t\t{ allow: never, deny: never },\n\t\t\t\t(rest: Pair, child): Pair => {\n\t\t\t\t\tconst pair = residualNode(child, context)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tallow: or([pair.allow, and([not(pair.deny), rest.allow])]),\n\t\t\t\t\t\tdeny: or([pair.deny, and([not(pair.allow), rest.deny])]),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t\tcase \"Ceiling\": {\n\t\t\tconst cap = residualNode(node.cap, context)\n\t\t\tconst inner = residualNode(node.policy, context)\n\t\t\treturn {\n\t\t\t\tallow: and([not(cap.deny), inner.allow]),\n\t\t\t\tdeny: or([cap.deny, inner.deny]),\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** The name of the predicate of a residual condition that runs `decide`. */\nconst policyPredicate = \"policy\"\n\n/** What {@link usesPredicate} needs: the request. */\ninterface PredicateContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly descriptor: Resource.Descriptor\n}\n\n/** Tests if a rule that the request may use has a predicate. */\nconst usesPredicate = (\n\tnode: PolicyNodes.Node,\n\tcontext: PredicateContext,\n): boolean => {\n\tif (!node.opaque) {\n\t\treturn false\n\t}\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn node.rules.some(\n\t\t\t\t(value) =>\n\t\t\t\t\tvalue.when !== undefined &&\n\t\t\t\t\tNodes.toNode(value.when).opaque &&\n\t\t\t\t\tRuleData.matchesTarget(value, context),\n\t\t\t)\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\":\n\t\t\treturn node.policies.some((child) => usesPredicate(child, context))\n\t\tcase \"Ceiling\":\n\t\t\treturn (\n\t\t\t\tusesPredicate(node.cap, context) || usesPredicate(node.policy, context)\n\t\t\t)\n\t}\n}\n\n/**\n * Makes the scope of a request, with the principal in it. A scope comes from\n * code, so a scope that does not fit the domain is a defect. It is bound as a\n * rule for the kind and the action of the request is.\n */\nconst scopeOf = (\n\trequest: ScopedRequest,\n\tdomain: Domain.Any,\n\tinput: ResidualContext[\"input\"],\n): Ast => {\n\tif (request.within === undefined) {\n\t\treturn always\n\t}\n\t// The builder stops a value that a condition can not hold, such as\n\t// `undefined` from a route parameter. A predicate could make a check pass\n\t// on the wrong values, so a scope with one is a defect too.\n\tconst ast = (request.within as (builder: unknown) => Ast)(Condition.builder())\n\tif (Nodes.toNode(ast).opaque) {\n\t\tthrow new globalThis.Error(\"A scope can not have a predicate\")\n\t}\n\tconst issues = Bind.ruleIssues(\n\t\tdomain,\n\t\t{ actions: [request.action], resources: [request.kind], when: ast },\n\t\t[\"within\"],\n\t)\n\tif (issues.length > 0) {\n\t\tthrow new globalThis.Error(\n\t\t\t`A scope does not fit the domain: ${issues.map((issue) => issue.issue).join(\"; \")}`,\n\t\t)\n\t}\n\treturn Condition.partial(ast as Condition.Condition, input)\n}\n\n/** A request about a kind with no types, with the scope of a target. */\nexport interface ScopedRequest {\n\treadonly principal: object\n\treadonly action: string\n\treadonly kind: string\n\treadonly field?: string | undefined\n\treadonly within?: unknown\n}\n\n/** The scope of a request, and the residual conditions inside it. */\ninterface Scoped extends Pair {\n\treadonly scope: Ast\n\t/** The catalog of the kind, when the request fits the kind. */\n\treadonly catalog: Catalog.Catalog | undefined\n}\n\nexport const residualsAny = (policy: Any, request: ScopedRequest): Pair =>\n\tscopedResiduals(policy, request)\n\nconst scopedResiduals = (policy: Any, request: ScopedRequest): Scoped => {\n\tconst descriptor = DomainData.descriptorOf(policy.domain, request.kind)\n\tif (descriptor === undefined || !fitsKind(descriptor, request)) {\n\t\treturn { scope: never, allow: never, deny: never, catalog: undefined }\n\t}\n\tconst input = { principal: request.principal, path: descriptor.path }\n\tconst scope = scopeOf(request, policy.domain, input)\n\tconst and = build.and\n\tconst inScope = (pair: Pair): Scoped => ({\n\t\tscope,\n\t\tallow: and([scope, pair.allow]),\n\t\tdeny: and([scope, pair.deny]),\n\t\tcatalog: descriptor.catalog,\n\t})\n\tif (scope._tag === \"False\") {\n\t\treturn inScope({ allow: never, deny: never })\n\t}\n\tconst target = {\n\t\taction: request.action,\n\t\tkind: request.kind,\n\t\tfield: request.field,\n\t\tinput,\n\t\tdescriptor,\n\t}\n\tconst context: ResidualContext = {\n\t\t...target,\n\t\tconditions: new Map(\n\t\t\tArr.map(policy.pipe(nodeOf, PolicyNodes.rulesOf), (value) => [\n\t\t\t\tvalue,\n\t\t\t\tmakeCondition(value, target),\n\t\t\t]),\n\t\t),\n\t}\n\t// The type of the residual of a plain policy says that it has no\n\t// predicate, so it can not be the predicate below. A residual past the\n\t// limit is a defect.\n\tif (!usesPredicate(nodeOf(policy), context)) {\n\t\treturn inScope(residualNode(nodeOf(policy), context))\n\t}\n\t// A predicate may fail, and it may give another answer each time it runs.\n\t// A tree of `And` and `Or` nodes can not run the predicates in the same\n\t// order and the same number of times as `decide`. So the residual is one\n\t// predicate that runs `decide` for each value.\n\tconst decision = (value: object) =>\n\t\tdecideNode(\n\t\t\tnodeOf(policy),\n\t\t\t{\n\t\t\t\taction: request.action,\n\t\t\t\tkind: request.kind,\n\t\t\t\tfield: request.field,\n\t\t\t\tinput: {\n\t\t\t\t\tvalue,\n\t\t\t\t\tprincipal: request.principal,\n\t\t\t\t\tpath: descriptor.path,\n\t\t\t\t},\n\t\t\t\tdescriptor,\n\t\t\t\ttrace: undefined,\n\t\t\t},\n\t\t\t[],\n\t\t)\n\t// A value of another kind is not a value that the residual is about, so\n\t// the rules of this kind must not run on it.\n\tconst test = (tag: Decision.Decision[\"_tag\"]): Ast =>\n\t\tCondition.builder<any, any, any>().predicate(policyPredicate, (value) =>\n\t\t\tResourceData.kindOf(value) === request.kind\n\t\t\t\t? Effect.map(decision(value), (result) => result._tag === tag)\n\t\t\t\t: Effect.succeed(false),\n\t\t)\n\t// The scope comes first in each `And`, so the policy runs only on values\n\t// in the scope.\n\treturn inScope({ allow: test(\"Allow\"), deny: test(\"Deny\") })\n}\n\n/** Gives the condition on resource values for which the policy allows. */\nexport const residual = (policy: Any, request: ScopedRequest): Ast =>\n\tresidualsAny(policy, request).allow\n\n/** A {@link Policy.Coverage} with no types. */\nexport type AnyCoverage = Data.TaggedEnum<{\n\tAll: { readonly condition: Ast }\n\tNone: { readonly condition: Ast }\n\tSome: {\n\t\treadonly condition: Ast\n\t\treadonly allowed: Condition.Example\n\t\treadonly refused: Condition.Example\n\t}\n\tUnknown: {\n\t\treadonly condition: Ast\n\t\treadonly reason: \"predicate\" | \"budget\"\n\t}\n}>\n\nconst coverages = Data.taggedEnum<AnyCoverage>()\n\n/** Searches for a value of the kind of a scope that makes a condition true. */\nconst search = (scoped: Scoped, ast: Ast): Condition.Satisfiability =>\n\tSearch.satisfiable(ast as Nodes.Node, Search.kindsIn(scoped.catalog))\n\n/** The values in the scope that the policy does not allow. */\nconst refusedOf = (scoped: Scoped): Ast =>\n\t// Only two nodes more than the residual, so it needs no size limit.\n\tAlgebra.and([\n\t\tscoped.scope as Nodes.Node,\n\t\tAlgebra.not(scoped.allow as Nodes.Node),\n\t])\n\nexport const coverageAny = (\n\tpolicy: Any,\n\trequest: ScopedRequest,\n): AnyCoverage => {\n\tconst scoped = scopedResiduals(policy, request)\n\tconst allowed = scoped.allow\n\tconst some = search(scoped, allowed)\n\tif (some._tag === \"Unsatisfiable\") {\n\t\treturn coverages.None({ condition: never })\n\t}\n\tif (some._tag === \"Unknown\") {\n\t\treturn coverages.Unknown({ condition: allowed, reason: some.reason })\n\t}\n\tconst missing = search(scoped, refusedOf(scoped))\n\tif (missing._tag === \"Unsatisfiable\") {\n\t\treturn coverages.All({ condition: scoped.scope })\n\t}\n\tif (missing._tag === \"Unknown\") {\n\t\treturn coverages.Unknown({ condition: allowed, reason: missing.reason })\n\t}\n\treturn coverages.Some({\n\t\tcondition: allowed,\n\t\tallowed: some.example,\n\t\trefused: missing.example,\n\t})\n}\n\n/** Decides a request about every value in a scope. */\nexport const decideKind = (\n\tpolicy: Any,\n\trequest: ScopedRequest,\n): Decision.Decision => {\n\tconst scoped = scopedResiduals(policy, request)\n\tconst missing = search(scoped, refusedOf(scoped))\n\tswitch (missing._tag) {\n\t\tcase \"Unknown\":\n\t\t\treturn Decision.Deny({})\n\t\tcase \"Unsatisfiable\":\n\t\t\treturn search(scoped, scoped.scope)._tag === \"Satisfiable\"\n\t\t\t\t? Decision.Allow({})\n\t\t\t\t: Decision.Deny({})\n\t\tcase \"Satisfiable\":\n\t\t\t// A refused value is in the scope, so the scope has a value.\n\t\t\treturn search(scoped, scoped.deny)._tag === \"Unsatisfiable\"\n\t\t\t\t? Decision.NotApplicable\n\t\t\t\t: Decision.Deny({})\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"analysis.js","names":["Condition.always","Condition.never","Limits.exceeded","Limits.residualNodes","Algebra.and","Algebra.or","Algebra.not","RuleData.matchesTarget","Condition.partial","Nodes.toNode","Condition.builder","Bind.ruleIssues","DomainData.descriptorOf","RuleData.fitsOf","PolicyNodes.rulesOf","ResourceData.kindOf","Search.satisfiable","Search.kindsIn","Decision.Deny","Decision.Allow","Decision.NotApplicable"],"sources":["../../src/internal/analysis.ts"],"sourcesContent":["/**\n * The residual conditions and the coverage of a policy. The types are in\n * `Policy.ts`.\n */\n// A policy keeps the error type and the service type of its predicates in the\n// public types. The code below works on the nodes with no types, where they\n// are `unknown`, so the language service check for `unknown` in an Effect is\n// off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Data from \"effect/Data\"\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Algebra from \"./algebra.ts\"\nimport * as Bind from \"./bind.ts\"\nimport type * as Catalog from \"./catalog.ts\"\nimport { decideNode, fitsKind } from \"./decide.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport { nodeOf } from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as RuleData from \"./rule.ts\"\nimport * as Search from \"./search.ts\"\n\ntype Any = Policy.Any\n\ntype Ast = Condition.Ast\n\nconst always: Ast = Condition.always\nconst never: Ast = Condition.never\n\n/**\n * Makes conditions for one residual, with no types. Each step makes the\n * condition simple, and it stops the work when the condition has more nodes\n * than `residualNodes`. A policy tree may use a condition of a child two\n * times, in its allow condition and in its deny condition, so a small policy\n * may give a very large condition.\n */\ninterface Build {\n\t// A list goes in as an array, not as arguments: a call may take only so\n\t// many arguments, and a policy may have more rules.\n\treadonly and: (values: ReadonlyArray<Ast>) => Ast\n\treadonly or: (values: ReadonlyArray<Ast>) => Ast\n\treadonly not: (value: Ast) => Ast\n}\n\nconst limit = (node: Nodes.Node): Ast => {\n\tif (node.size > Limits.residualNodes) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A residual condition\",\n\t\t\t\"residualNodes\",\n\t\t\t\"nodes\",\n\t\t\tnode.size,\n\t\t\tLimits.residualNodes,\n\t\t)\n\t}\n\treturn node\n}\n\nconst build: Build = {\n\tand: (values) => limit(Algebra.and(values as ReadonlyArray<Nodes.Node>)),\n\tor: (values) => limit(Algebra.or(values as ReadonlyArray<Nodes.Node>)),\n\tnot: (value) => (value as Nodes.Node).pipe(Algebra.not, limit),\n}\n\n/** The two residual conditions of a node, with no types. */\ninterface Pair {\n\treadonly allow: Ast\n\treadonly deny: Ast\n}\n\ninterface ResidualContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly input: {\n\t\treadonly principal: object\n\t\treadonly path: Condition.Input[\"path\"]\n\t}\n\t/** The resource of the kind, for the fields that a rule may grant. */\n\treadonly descriptor: Resource.Descriptor\n\t/** The test of `RuleData.fitsOf` for the catalog, for this request. */\n\treadonly fits: (pattern: string) => boolean\n\t/**\n\t * The condition of each rule, with the principal in it. They are made\n\t * first, from a flat list of the rules, so the walk does not go down the\n\t * levels of the tree and of a condition at once. It is a native map,\n\t * because it must find a rule by the rule itself: two rules with the same\n\t * data are `Equal`.\n\t */\n\treadonly conditions: Map<RuleData.Rule, Ast>\n}\n\nconst makeCondition = (\n\tvalue: RuleData.Rule,\n\tcontext: Omit<ResidualContext, \"conditions\">,\n): Ast =>\n\t!RuleData.matchesTarget(value, context)\n\t\t? never\n\t\t: value.when === undefined\n\t\t\t? always\n\t\t\t: Condition.partial(value.when as Condition.Any, context.input)\n\nconst ruleCondition = (value: RuleData.Rule, context: ResidualContext): Ast =>\n\tcontext.conditions.get(value) ?? makeCondition(value, context)\n\n/**\n * Gives the conditions for \"the first rule that applies is an allow rule\" and\n * \"the first rule that applies is a deny rule\".\n */\nconst firstApplicableResidual = (\n\tvalues: ReadonlyArray<RuleData.Rule>,\n\tcontext: ResidualContext,\n): Pair => {\n\tconst { and, not, or } = build\n\t// The rules go in runs of one tag, from the last run to the first. The\n\t// conditions of a run go into one node at once. With one node for each\n\t// rule, each step would flatten the whole condition again.\n\tconst runs = Arr.isReadonlyArrayNonEmpty(values)\n\t\t? Arr.groupWith(values, (a, b) => a._tag === b._tag)\n\t\t: []\n\treturn Arr.reduceRight(\n\t\truns,\n\t\t{ allow: never, deny: never },\n\t\t(rest: Pair, run): Pair => {\n\t\t\tconst applies = or(Arr.map(run, (value) => ruleCondition(value, context)))\n\t\t\treturn run[0]._tag === \"Allow\"\n\t\t\t\t? {\n\t\t\t\t\t\tallow: or([applies, rest.allow]),\n\t\t\t\t\t\tdeny: and([not(applies), rest.deny]),\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\tallow: and([not(applies), rest.allow]),\n\t\t\t\t\t\tdeny: or([applies, rest.deny]),\n\t\t\t\t\t}\n\t\t},\n\t)\n}\n\n/**\n * Gives the residual pair of a node. A shared child is worked again for each\n * place. The limits of the nodes and of a residual keep this work small.\n */\nconst residualNode = (\n\tnode: PolicyNodes.Node,\n\tcontext: ResidualContext,\n): Pair => {\n\tconst { and, not, or } = build\n\tswitch (node._tag) {\n\t\tcase \"Rules\": {\n\t\t\tswitch (node.algorithm) {\n\t\t\t\tcase \"denyOverrides\": {\n\t\t\t\t\tconst deny = or(\n\t\t\t\t\t\tnode.rules\n\t\t\t\t\t\t\t.filter((value) => value._tag === \"Deny\")\n\t\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t\t)\n\t\t\t\t\tconst allow = and([\n\t\t\t\t\t\tnot(deny),\n\t\t\t\t\t\tor(\n\t\t\t\t\t\t\tnode.rules\n\t\t\t\t\t\t\t\t.filter((value) => value._tag === \"Allow\")\n\t\t\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t\t\t),\n\t\t\t\t\t])\n\t\t\t\t\treturn { allow, deny }\n\t\t\t\t}\n\t\t\t\tcase \"permitOverrides\": {\n\t\t\t\t\tconst allow = or(\n\t\t\t\t\t\tnode.rules\n\t\t\t\t\t\t\t.filter((value) => value._tag === \"Allow\")\n\t\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t\t)\n\t\t\t\t\tconst deny = and([\n\t\t\t\t\t\tnot(allow),\n\t\t\t\t\t\tor(\n\t\t\t\t\t\t\tnode.rules\n\t\t\t\t\t\t\t\t.filter((value) => value._tag === \"Deny\")\n\t\t\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t\t\t),\n\t\t\t\t\t])\n\t\t\t\t\treturn { allow, deny }\n\t\t\t\t}\n\t\t\t\tcase \"firstApplicable\":\n\t\t\t\t\treturn firstApplicableResidual(node.rules, context)\n\t\t\t\tcase \"lastApplicable\":\n\t\t\t\t\treturn firstApplicableResidual(node.rules.slice().reverse(), context)\n\t\t\t}\n\t\t}\n\t\t// falls through: every algorithm returns above\n\t\tcase \"All\": {\n\t\t\tif (node.policies.length === 0) {\n\t\t\t\treturn { allow: never, deny: never }\n\t\t\t}\n\t\t\tconst pairs = node.policies.map((child) => residualNode(child, context))\n\t\t\tconst deny = or(pairs.map((pair) => pair.deny))\n\t\t\tconst allow = and([...pairs.map((pair) => pair.allow), not(deny)])\n\t\t\treturn { allow, deny }\n\t\t}\n\t\tcase \"Any\": {\n\t\t\tconst pairs = node.policies.map((child) => residualNode(child, context))\n\t\t\tconst allow = or(pairs.map((pair) => pair.allow))\n\t\t\tconst deny = and([not(allow), or(pairs.map((pair) => pair.deny))])\n\t\t\treturn { allow, deny }\n\t\t}\n\t\tcase \"FirstOf\": {\n\t\t\t// The first child that gives an answer wins.\n\t\t\treturn Arr.reduceRight(\n\t\t\t\tnode.policies,\n\t\t\t\t{ allow: never, deny: never },\n\t\t\t\t(rest: Pair, child): Pair => {\n\t\t\t\t\tconst pair = residualNode(child, context)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tallow: or([pair.allow, and([not(pair.deny), rest.allow])]),\n\t\t\t\t\t\tdeny: or([pair.deny, and([not(pair.allow), rest.deny])]),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t\tcase \"Ceiling\": {\n\t\t\tconst cap = residualNode(node.cap, context)\n\t\t\tconst inner = residualNode(node.policy, context)\n\t\t\treturn {\n\t\t\t\tallow: and([not(cap.deny), inner.allow]),\n\t\t\t\tdeny: or([cap.deny, inner.deny]),\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** The name of the predicate of a residual condition that runs `decide`. */\nconst policyPredicate = \"policy\"\n\n/** What {@link usesPredicate} needs: the request. */\ninterface PredicateContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly descriptor: Resource.Descriptor\n\treadonly fits: (pattern: string) => boolean\n}\n\n/** Tests if a rule that the request may use has a predicate. */\nconst usesPredicate = (\n\tnode: PolicyNodes.Node,\n\tcontext: PredicateContext,\n): boolean => {\n\tif (!node.opaque) {\n\t\treturn false\n\t}\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn node.rules.some(\n\t\t\t\t(value) =>\n\t\t\t\t\tvalue.when !== undefined &&\n\t\t\t\t\tNodes.toNode(value.when).opaque &&\n\t\t\t\t\tRuleData.matchesTarget(value, context),\n\t\t\t)\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\":\n\t\t\treturn node.policies.some((child) => usesPredicate(child, context))\n\t\tcase \"Ceiling\":\n\t\t\treturn (\n\t\t\t\tusesPredicate(node.cap, context) || usesPredicate(node.policy, context)\n\t\t\t)\n\t}\n}\n\n/**\n * Makes the scope of a request, with the principal in it. A scope comes from\n * code, so a scope that does not fit the domain is a defect. It is bound as a\n * rule for the kind and the action of the request is.\n */\nconst scopeOf = (\n\trequest: ScopedRequest,\n\tdomain: Domain.Any,\n\tinput: ResidualContext[\"input\"],\n): Ast => {\n\tif (request.within === undefined) {\n\t\treturn always\n\t}\n\t// The builder stops a value that a condition can not hold, such as\n\t// `undefined` from a route parameter. A predicate could make a check pass\n\t// on the wrong values, so a scope with one is a defect too.\n\tconst ast = (request.within as (builder: unknown) => Ast)(Condition.builder())\n\tif (Nodes.toNode(ast).opaque) {\n\t\tthrow new globalThis.Error(\"A scope can not have a predicate\")\n\t}\n\tconst issues = Bind.ruleIssues(\n\t\tdomain,\n\t\t{ actions: [request.action], resources: [request.kind], when: ast },\n\t\t[\"within\"],\n\t)\n\tif (issues.length > 0) {\n\t\tthrow new globalThis.Error(\n\t\t\t`A scope does not fit the domain: ${issues.map((issue) => issue.issue).join(\"; \")}`,\n\t\t)\n\t}\n\treturn Condition.partial(ast as Condition.Condition, input)\n}\n\n/** A request about a kind with no types, with the scope of a target. */\nexport interface ScopedRequest {\n\treadonly principal: object\n\treadonly action: string\n\treadonly kind: string\n\treadonly field?: string | undefined\n\treadonly within?: unknown\n}\n\n/** The scope of a request, and the residual conditions inside it. */\ninterface Scoped extends Pair {\n\treadonly scope: Ast\n\t/** The catalog of the kind, when the request fits the kind. */\n\treadonly catalog: Catalog.Catalog | undefined\n}\n\nexport const residualsAny = (policy: Any, request: ScopedRequest): Pair =>\n\tscopedResiduals(policy, request)\n\nconst scopedResiduals = (policy: Any, request: ScopedRequest): Scoped => {\n\tconst descriptor = DomainData.descriptorOf(policy.domain, request.kind)\n\tif (descriptor === undefined || !fitsKind(descriptor, request)) {\n\t\treturn { scope: never, allow: never, deny: never, catalog: undefined }\n\t}\n\tconst input = { principal: request.principal, path: descriptor.path }\n\tconst scope = scopeOf(request, policy.domain, input)\n\tconst and = build.and\n\tconst inScope = (pair: Pair): Scoped => ({\n\t\tscope,\n\t\tallow: and([scope, pair.allow]),\n\t\tdeny: and([scope, pair.deny]),\n\t\tcatalog: descriptor.catalog,\n\t})\n\tif (scope._tag === \"False\") {\n\t\treturn inScope({ allow: never, deny: never })\n\t}\n\tconst target = {\n\t\taction: request.action,\n\t\tkind: request.kind,\n\t\tfield: request.field,\n\t\tinput,\n\t\tdescriptor,\n\t\tfits: RuleData.fitsOf(descriptor.catalog),\n\t}\n\tconst context: ResidualContext = {\n\t\t...target,\n\t\tconditions: new Map(\n\t\t\tArr.map(policy.pipe(nodeOf, PolicyNodes.rulesOf), (value) => [\n\t\t\t\tvalue,\n\t\t\t\tmakeCondition(value, target),\n\t\t\t]),\n\t\t),\n\t}\n\t// The type of the residual of a plain policy says that it has no\n\t// predicate, so it can not be the predicate below. A residual past the\n\t// limit is a defect.\n\tif (!usesPredicate(nodeOf(policy), context)) {\n\t\treturn inScope(residualNode(nodeOf(policy), context))\n\t}\n\t// A predicate may fail, and it may give another answer each time it runs.\n\t// A tree of `And` and `Or` nodes can not run the predicates in the same\n\t// order and the same number of times as `decide`. So the residual is one\n\t// predicate that runs `decide` for each value.\n\tconst decision = (value: object) =>\n\t\tdecideNode(\n\t\t\tnodeOf(policy),\n\t\t\t{\n\t\t\t\taction: request.action,\n\t\t\t\tkind: request.kind,\n\t\t\t\tfield: request.field,\n\t\t\t\tinput: {\n\t\t\t\t\tvalue,\n\t\t\t\t\tprincipal: request.principal,\n\t\t\t\t\tpath: descriptor.path,\n\t\t\t\t},\n\t\t\t\tdescriptor,\n\t\t\t\tfits: RuleData.fitsOf(descriptor.catalog),\n\t\t\t\ttrace: undefined,\n\t\t\t},\n\t\t\t[],\n\t\t)\n\t// A value of another kind is not a value that the residual is about, so\n\t// the rules of this kind must not run on it.\n\tconst test = (tag: Decision.Decision[\"_tag\"]): Ast =>\n\t\tCondition.builder<any, any, any>().predicate(policyPredicate, (value) =>\n\t\t\tResourceData.kindOf(value) === request.kind\n\t\t\t\t? Effect.map(decision(value), (result) => result._tag === tag)\n\t\t\t\t: Effect.succeed(false),\n\t\t)\n\t// The scope comes first in each `And`, so the policy runs only on values\n\t// in the scope.\n\treturn inScope({ allow: test(\"Allow\"), deny: test(\"Deny\") })\n}\n\n/** Gives the condition on resource values for which the policy allows. */\nexport const residual = (policy: Any, request: ScopedRequest): Ast =>\n\tresidualsAny(policy, request).allow\n\n/** A {@link Policy.Coverage} with no types. */\nexport type AnyCoverage = Data.TaggedEnum<{\n\tAll: { readonly condition: Ast }\n\tNone: { readonly condition: Ast }\n\tSome: {\n\t\treadonly condition: Ast\n\t\treadonly allowed: Condition.Example\n\t\treadonly refused: Condition.Example\n\t}\n\tUnknown: {\n\t\treadonly condition: Ast\n\t\treadonly reason: \"predicate\" | \"budget\"\n\t}\n}>\n\nconst coverages = Data.taggedEnum<AnyCoverage>()\n\n/** Searches for a value of the kind of a scope that makes a condition true. */\nconst search = (scoped: Scoped, ast: Ast): Condition.Satisfiability =>\n\tSearch.satisfiable(ast as Nodes.Node, Search.kindsIn(scoped.catalog))\n\n/** The values in the scope that the policy does not allow. */\nconst refusedOf = (scoped: Scoped): Ast =>\n\t// Only two nodes more than the residual, so it needs no size limit.\n\tAlgebra.and([\n\t\tscoped.scope as Nodes.Node,\n\t\tAlgebra.not(scoped.allow as Nodes.Node),\n\t])\n\nexport const coverageAny = (\n\tpolicy: Any,\n\trequest: ScopedRequest,\n): AnyCoverage => {\n\tconst scoped = scopedResiduals(policy, request)\n\tconst allowed = scoped.allow\n\tconst some = search(scoped, allowed)\n\tif (some._tag === \"Unsatisfiable\") {\n\t\treturn coverages.None({ condition: never })\n\t}\n\tif (some._tag === \"Unknown\") {\n\t\treturn coverages.Unknown({ condition: allowed, reason: some.reason })\n\t}\n\tconst missing = search(scoped, refusedOf(scoped))\n\tif (missing._tag === \"Unsatisfiable\") {\n\t\treturn coverages.All({ condition: scoped.scope })\n\t}\n\tif (missing._tag === \"Unknown\") {\n\t\treturn coverages.Unknown({ condition: allowed, reason: missing.reason })\n\t}\n\treturn coverages.Some({\n\t\tcondition: allowed,\n\t\tallowed: some.example,\n\t\trefused: missing.example,\n\t})\n}\n\n/** Decides a request about every value in a scope. */\nexport const decideKind = (\n\tpolicy: Any,\n\trequest: ScopedRequest,\n): Decision.Decision => {\n\tconst scoped = scopedResiduals(policy, request)\n\tconst missing = search(scoped, refusedOf(scoped))\n\tswitch (missing._tag) {\n\t\tcase \"Unknown\":\n\t\t\treturn Decision.Deny({})\n\t\tcase \"Unsatisfiable\":\n\t\t\treturn search(scoped, scoped.scope)._tag === \"Satisfiable\"\n\t\t\t\t? Decision.Allow({})\n\t\t\t\t: Decision.Deny({})\n\t\tcase \"Satisfiable\":\n\t\t\t// A refused value is in the scope, so the scope has a value.\n\t\t\treturn search(scoped, scoped.deny)._tag === \"Unsatisfiable\"\n\t\t\t\t? Decision.NotApplicable\n\t\t\t\t: Decision.Deny({})\n\t}\n}\n"],"mappings":"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"}
|