@nmnmcc/ability 2.0.0 → 2.0.1

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/dist/Policy.d.ts CHANGED
@@ -393,11 +393,16 @@ export declare const builder: <D extends Any$2>(domain: D) => Builder<D>;
393
393
  * against a domain: a policy does that when the rule goes into it. Use
394
394
  * {@link builder} for rules with types, or {@link schema} for rules from JSON.
395
395
  *
396
+ * The condition may not have a predicate. A policy checks the fields of a
397
+ * condition against the resources of its rule, but it can not check what
398
+ * the code of a predicate reads. A rule from the builder keeps that in its
399
+ * types; this one could give a predicate of one kind to another kind.
400
+ *
396
401
  * @stability stable
397
402
  * @category constructors
398
403
  * @since 2.0.0
399
404
  */
400
- export declare const rule: <I = unknown, X = never>(data: RuleShape<I, X>) => Rule<any, NoInfer<I>, NoInfer<X>>;
405
+ export declare const rule: <I = unknown>(data: RuleShape<I, never>) => Rule<any, NoInfer<I>>;
401
406
  /**
402
407
  * The options of a list of rules.
403
408
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Policy.d.ts","names":["PolicyNodes.Rules","PolicyNodes.All","PolicyNodes.Any","PolicyNodes.FirstOf","PolicyNodes.Ceiling","PolicyNodes.Node","Domain.Any","Internal.PolicyData","Domain.Kind","Domain.ActionOf","Domain.ValueOf","Domain.DataOf","Condition.FieldSelector","Domain.Principal","Condition.Builder","Condition.Reads","Condition.Condition","Decision.Decision","Domain.Resources","Resource.Kind","Domain.AnyAction","Domain.CommonAction","Domain.AnyValue","Condition.FieldName","Condition.Any","Domain.Meta","Condition.ReadsOf","Condition.OpaqueOf","Internal.PolicyError","Condition.ServicesOf","Condition.Provided","Condition.ErrorOf","Condition.Example","Condition.Encoded"],"sources":["../src/Policy.ts"],"mappings":";;;;;;;;;;;;;;;;;;;YAwDY;;;;;;;;YAUA,YAAYA;;;;;;YAOZ,UAAUC;;;;;;YAOV,UAAUC;;;;;;YAOV,cAAcC;;;;;;YAOd,cAAcC;;;;;;;;;YAUd,OAAOC;;;;;;;;;;YAWP,OAAO,UAAUC,OAAY,aAAaC,WAAoB,GAAG;;;;;;;;YASjE,MAAM;;;;;;;;YASN,SAAS,KAAK,UAAU,aAAa,UAAU;;;;;;;;YAS/C,SAAS,KAAK;WAAqB,uBAAuB;IACnE;;;;;;;;;YAWS,OAAO,QACjB,KAAKC,OAAY;WACR,QAAQC,SAAgB,GAAG;WAC3B,UAAUC,QAAe,GAAG;WAC5B,QAAQE,cAAwBD,OAAc,GAAG;;WAEjD;KAETH,OAAY;;;;;;;;YASF,gBAAgB,QAC1B,KAAKA,OAAY;WACR,QAAQC,SAAgB,GAAG;WAC3B,UAAUC,QAAe,GAAG;WAC5B;KAETF,OAAY;;;;;;;;;;YAWF,MAAM,GAAG,qBACpB,SAASM,UACRH,OAAc,GAAG,IACjBD,QAAe,GAAG,IAClBG,UAAiB,QAEdG,UACJD,MAAgBL,QAAe,GAAG,IAAIG,UAAiB;;;;;;;;;;YAY5C,WAAW,QACrB,KAAKL,OAAY;WACR,QAAQC,SAAgB,GAAG;WAC3B,MAAM;WACN,QAAQG,cAAwBD,OAAc,GAAG;;WAEjD,SAAS,MAAM,GAAG;;WAElB;KAETH,OAAY;;;;;;;;YASF,QAAQ,KAAK,OAAO;WAAgB,WAAWK,UAAiB;;;;;;;;;YAShE,iBAAiB,KAAK,gBAAgB;WACxC,WAAWA,UAAiB;;;;;;;;;YAU1B,YAAY,KAAK,WAAW;WAC9B,WAAWA,UAAiB;;;;;;;;;iBAUrB;WACP;WACA;WACA;WACA;;;;;;;;;;iBAWO;WACP,MAAM;WACN,MAAM;WACN;;;;;;;;;;iBAWO;WACP,UAAUI;WACV,OAAO,cAAc;;;;;;;;;;;;iBAed,KAAK,SAAS,aAAa,mBACnC,UAAU,GAAG,IAAI,SAAS;WACzB,iBAAiB;WACjB,YAAY,QAAQ,MAAM;GAClC,OAAO,aAAa,UAAU,KAAK,GAAG,GAAG,IAAI,KAAK,GAAG,GAAG;;;;;;;;;iBAUzC,UAAU,aAAa;WAC9B;WACA,SAAS;WACT,WAAW;WACX,OAAOD,UAAoB,GAAG;WAC9B,SAAS;WACT;WACA;WACA;;;;;;WAMA,SAASV;;;;;;;;;YAYP,UAAU;;;;;;;;YASV,WAAW,KAAKE,OAAY,KAAKU,UAAiB;;;;;;;;YASlD,OAAO,KAAK,mBAAmB,IAAIC,KAAc;;;;;;;;YASjD,UAAU,GAAG,wBAET,IACXC,UAAiB,KACjBX,SAAgB,GAAG,QAAQ;;;;;;;;;YAUpB,SAAS,OAAO,YAAY,oBAAoB,KAAK,IAAI;;;;;;;;;;;;YAazD,iBAAiB,GAAG,QAC/B,YAAY,oBAAoB,MAC5B,QAAQ,UAAU,GAAG,OAAO,gBAC5B,QAAQ,gBAAgB,GAAG,yBACrB,cAAc,cACtB;;KAIE,gBAAgB,GAAG,0BAER,OAAO,OAClBW,UAAiB,KACjBC,aAAoB,GAAG,QAAQ,OAAO;;;;;;;;YAS/B,QAAQ,GAAG,iBAAiB,SAErCV,OAAc,GAAG,QAAQ;;;;;;;;YAShB,SAAS,GAAG,iBAAiB,IACtCW,SAAgB,KAChBZ,QAAe,GAAG,QAAQ;;;;;;;;YASjB,SAAS,GAAG,KACvBa,UAAoB,QAAQ,GAAG;;;;;;;;;YAUpB,aAAa,GAAG,KAAKP,UAChCD,MAAgB,SAAS,GAAG,IAAIF,UAAiB;;;;;;;;iBAWjC,YAAY,GAAG,GAAG,UAAUW;;WAEnC,QACR,SAASV,UACR,QAAQ,GAAG,IACX,SAAS,GAAG,IACZD,UAAiB,QAEd;WACI,SAAS,cAAc,SAAS,GAAG;WACnC;WACA;WACA,OAAOY,KAAY;;KAGxB,cAAc,KAAK,IAAI,cAAc;;;;;;;;iBASzB,QAAQ;WACf,cACF,YAAY,cAAc,WAAW,WACrC,UAAU,iBAAiB,GAAG,YAC9B,UAAU,aAAa,GAAG,OAAO,SAAS,SAC/CT,WAED,QAAQ,cAAc,IACtB,UAAU,KACV,UAAU,YAAY,GAAG,OAAO,SAAS,OAAO,OAC5C,KAAK,GAAGU,QAAkB,IAAIC,WAAmB;WAC7C,aACF,YAAY,cAAc,WAAW,WACrC,UAAU,iBAAiB,GAAG,YAC9B,UAAU,aAAa,GAAG,OAAO,SAAS,SAC/CX,WAED,QAAQ,cAAc,IACtB,UAAU,KACV,UAAU,YAAY,GAAG,OAAO,SAAS,OAAO,OAC5C,KAAK,GAAGU,QAAkB,IAAIC,WAAmB;;;;;;;;;;;;;;;;;;qBAmB1C,UAAW,UAAUrB,OAAU,QAAU,MAAI,QAAQ;;;;;;;;;;qBAYrD,OAAQ,aAAa,WAAS,MACpC,UAAU,GAAG,OAEjB,UAAU,QAAQ,IAAI,QAAQ;;;;;;;;iBAShB;;WAEP,YAAY;;;;;;;;;;;;qBAaT,oBAAoBsB;;;;;;YAOrB,cAAcA;;KAGrB,YAAY,SAAS;WAAyB,uBAAuB;IACvE;;;;;;;;;;;;;;;;;;;qBAqBU,SAAS,UAAUtB,OAC/B,QAAQ,GACR,UAAU,aACL,cAAc,KAAK,gBAAgB,GACxC,OAAO,SAAS,QAAQ,OAAO,UAAU,OAAO,gBAC5C,OAAO,GAAG,YAAY;KAEtB,SAAS,UAAUA,OAAY,WAAW,cAAc,QAAQ,OACpE,GACA,SAAS;;;;;;;;;qBAWG,YAAY,qBAAqB,QAAQ,MAAM,OAC3D,UAAU,OACN,SAAS,SAAS,QAAQ;;;;;;;;qBASlB,YAAY,qBAAqB,QAAQ,MAAM,OAC3D,UAAU,OACN,SAAS,SAAS,QAAQ;;;;;;;;qBASlB,gBAAgB,qBAAqB,QAAQ,MAAM,OAC/D,UAAU,OACN,SAAS,SAAS,QAAQ;;;;;;;;;qBAUlB;GACX,UAAUA,OAAY,IACtB,KAAK,OAAO,GAAG,OACZ,GAAG,QAAQ,OAAO,GAAG,OAAO,OAAO,GAAG,IAAI;GAC7C,UAAUA,OAAY,GAAG,IACzB,QAAQ,OAAO,GAAG,IAClB,KAAK,OAAO,GAAG,MACb,OAAO,GAAG,IAAI;;;;;;;;;;qBAWL;GACX,GACA,SAAS,QAAQ,QAAQ,MACtB,UAAUA,OAAY,GACzB,QAAQ,OAAO,GAAG,OACfuB,WAAqB,aAAa,0BACjC,OAAO,GAAGC,SAAmB;GACjC,UAAUxB,OAAY,GACtB,QAAQ,OAAO,GAAG,IAClB,SAAS,QAAQ,QAAQuB,WAAqB,MAC5C,OAAO,GAAGC,SAAmB;;;;;;;;;qBAYpB,SAAS,UAAUxB,OAAY,GAC3C,QAAQ,OAAO,GAAG,IAClB,SAAS,QAAQ,QAAQ,QACrB,OAAO,OACXW,UACAc,QAAkB,IAClBF,WAAqB;;;;;;;;qBAUT,aAAa,UAAUvB,OACnC,QAAQ,OAAO,IACf,SAAS,QAAQ,QAAQ,QACrBW;;;;;;;;;;;qBAYQ,UAAU,UAAUX,OAAY,GAC5C,QAAQ,OAAO,GAAG,IAClB,SAAS,QAAQ,QAAQ,QACrB,OAAO,OAAO,aAAayB,QAAkB,IAAIF,WAAqB;;;;;;;;;;;;;;qBAgB9D,kBAAkB,UAAUvB,OAAY,GACpD,QAAQ,OAAO,GAAG,IAClB,SAAS,iBAAiB,QAAQ,QAC9B,OAAO,OACX,uBACAyB,QAAkB,IAClBF,WAAqB;;;;;;;;;;;YAeV,cAAc,GAAG,KAAKd,OAEhC,qBAAqB,OAAOL,QAAe,GAAG,wCACpC,aAAa,cACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA+BS,WACZ,UAAUJ,OACV,SACM,YAAY,YAAY,QAAQ,KAEtC,QAAQ,OAAO,GAAG,IAClB,SAAS,QACLU,UAAoB,cAAc,GAAG,cAAc;;;;;;;;;;;;;;;;;;;;YAsB5C,SAAS,aAAa,aAAa,KAAK;EACnD;aAAgB,WAAWA,UAAoB,GAAG;;EAClD;aAAiB,WAAWA,UAAoB,GAAG;;EACnD;aACU,WAAWA,UAAoB,GAAG;aAClC,SAASgB;aACT,SAASA;;EAEnB;aACU,WAAWhB,UAAoB,GAAG;aAClC;;;;;;;;;;;;;;;;;;;;;;qBAuBE,WACZ,UAAUV,OACV,SACM,YAAY,YAAY,QAAQ,KAEtC,QAAQ,OAAO,GAAG,IAClB,SAAS,QACL,SAAS,cAAc,GAAG,cAAc;;;;;;;;iBAW5B;WACP;WACA,SAAS;WACT,WAAW;WACX,OAAO2B;WACP,SAAS;WACT;WACA;WACA;;;;;;;;;YAUE;WAEA;WACA,WAAW;WACX,OAAO,cAAc;;WAGrB;WACA,UAAU,cAAc;;WAGxB;WACA,KAAK;WACL,QAAQ;;;;;;;;;;;;;;;;qBAiBP,SAAS,UAAU3B,OAC/B,QAAQ,MACJ,OAAO,MAAM,OAAO,IAAI"}
1
+ {"version":3,"file":"Policy.d.ts","names":["PolicyNodes.Rules","PolicyNodes.All","PolicyNodes.Any","PolicyNodes.FirstOf","PolicyNodes.Ceiling","PolicyNodes.Node","Domain.Any","Internal.PolicyData","Domain.Kind","Domain.ActionOf","Domain.ValueOf","Domain.DataOf","Condition.FieldSelector","Domain.Principal","Condition.Builder","Condition.Reads","Condition.Condition","Decision.Decision","Domain.Resources","Resource.Kind","Domain.AnyAction","Domain.CommonAction","Domain.AnyValue","Condition.FieldName","Condition.Any","Domain.Meta","Condition.ReadsOf","Condition.OpaqueOf","Internal.PolicyError","Condition.ServicesOf","Condition.Provided","Condition.ErrorOf","Condition.Example","Condition.Encoded"],"sources":["../src/Policy.ts"],"mappings":"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"}
package/dist/Policy.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
- import { builder as builder$1, make } from "./internal/rule.js";
2
+ import { builder as builder$1, makePlain } from "./internal/rule.js";
3
3
  import { PolicyError as PolicyError$1, ceiling as ceiling$1, combine, define as define$1, provideContext as provideContext$1, schema as schema$1 } from "./internal/policy.js";
4
4
  import { decideAny, decideSync as decideSync$1, explain as explain$1, permittedFields as permittedFields$1 } from "./internal/decide.js";
5
5
  import { coverageAny, residual as residual$1 } from "./internal/analysis.js";
@@ -44,11 +44,16 @@ const builder = (domain) => builder$1(domain);
44
44
  * against a domain: a policy does that when the rule goes into it. Use
45
45
  * {@link builder} for rules with types, or {@link schema} for rules from JSON.
46
46
  *
47
+ * The condition may not have a predicate. A policy checks the fields of a
48
+ * condition against the resources of its rule, but it can not check what
49
+ * the code of a predicate reads. A rule from the builder keeps that in its
50
+ * types; this one could give a predicate of one kind to another kind.
51
+ *
47
52
  * @stability stable
48
53
  * @category constructors
49
54
  * @since 2.0.0
50
55
  */
51
- const rule = (data) => make(data);
56
+ const rule = makePlain;
52
57
  /**
53
58
  * The error when a rule does not fit its domain: it names a kind, an action,
54
59
  * a field or a field of the principal that the domain does not have, or it
@@ -1 +1 @@
1
- {"version":3,"file":"Policy.js","names":["RuleData.builder","RuleData.make","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 * @stability stable\n * @category constructors\n * @since 2.0.0\n */\nexport const rule = <I = unknown, X = never>(\n\tdata: RuleShape<I, X>,\n\t// The types come from the data only, not from where the rule goes.\n): Rule<any, NoInfer<I>, NoInfer<X>> => RuleData.make(data as never) 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 `True` when the policy allows every value of the\n * kind, and `False` when it allows none.\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;;;;;;;;;;AAWxB,MAAa,QACZ,SAEuCC,KAAc,IAAa;;;;;;;;;;;AAwBnE,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;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDJ,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 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 `True` when the policy allows every value of the\n * kind, and `False` when it allows none.\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;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDJ,MAAa,WAQZC;;;;;;;;;;;;;;;;;;;;AAsDD,MAAa,WAOqCC;;;;;;;;;;;;;;;AA2DlD,MAAa,SAE+BC"}
@@ -17,7 +17,11 @@ import * as MutableHashMap from "effect/MutableHashMap";
17
17
  */
18
18
  const makeMemo = () => ({
19
19
  suggestions: MutableHashMap.empty(),
20
- fits: MutableHashMap.empty()
20
+ fits: MutableHashMap.empty(),
21
+ issues: {
22
+ count: 0,
23
+ noted: false
24
+ }
21
25
  });
22
26
  /**
23
27
  * The most names that one binding makes a suggestion for. A suggestion
@@ -179,6 +183,30 @@ const notWhole = (at) => ({
179
183
  issue: "The condition is not a whole condition tree"
180
184
  });
181
185
  /**
186
+ * The most issues that the walk of the conditions of one binding gives. A
187
+ * policy may have very many wrong tests, and each issue keeps its path.
188
+ */
189
+ const maxIssues = 100;
190
+ /**
191
+ * Puts the path of a node before the paths of its issues. A walk keeps no
192
+ * path for a node with no issue, so a deep and wide condition costs little.
193
+ */
194
+ const under = (memo, at, issues) => {
195
+ if (issues.length === 0) return issues;
196
+ const room = maxIssues - memo.issues.count;
197
+ memo.issues.count += issues.length;
198
+ const kept = Arr.map(Arr.take(issues, Math.max(room, 0)), (issue) => ({
199
+ path: [...at(), ...issue.path],
200
+ issue: issue.issue
201
+ }));
202
+ const first = memo.issues.count > maxIssues && !memo.issues.noted;
203
+ memo.issues.noted ||= first;
204
+ return first ? [...kept, {
205
+ path: [],
206
+ issue: `The policy has more problems. The first ${String(maxIssues)} are here.`
207
+ }] : kept;
208
+ };
209
+ /**
182
210
  * The issues of a condition. The constructors of the nodes checked the shape
183
211
  * of each node, so this checks the names and the types against the domain. A
184
212
  * node that the condition uses in many places is checked one time. The nodes
@@ -188,28 +216,25 @@ const notWhole = (at) => ({
188
216
  const conditionIssues = (context, node, at, seen) => {
189
217
  if (seen.has(node)) return [];
190
218
  seen.add(node);
219
+ const here = (issues) => under(context.memo, at, issues);
191
220
  switch (node._tag) {
192
221
  case "Compare": {
193
- const exists = fieldIssue(context, node.field, [...at, "field"]);
194
- return exists.length > 0 ? exists : operandIssues(context, node.field, node.op !== "eq" && node.op !== "ne", node.value, [...at, "value"]);
222
+ const exists = fieldIssue(context, node.field, ["field"]);
223
+ return here(exists.length > 0 ? exists : operandIssues(context, node.field, node.op !== "eq" && node.op !== "ne", node.value, ["value"]));
195
224
  }
196
225
  case "In": {
197
- const exists = fieldIssue(context, node.field, [...at, "field"]);
198
- return exists.length > 0 ? exists : Arr.flatMap(node.values, (operand, index) => operandIssues(context, node.field, false, operand, [
199
- ...at,
200
- "values",
201
- index
202
- ]));
226
+ const exists = fieldIssue(context, node.field, ["field"]);
227
+ return here(exists.length > 0 ? exists : Arr.flatMap(node.values, (operand, index) => operandIssues(context, node.field, false, operand, ["values", index])));
203
228
  }
204
- case "Exists": return fieldIssue(context, node.field, [...at, "field"]);
205
- case "Under": return underIssues(context, node.prefix, at);
229
+ case "Exists": return here(fieldIssue(context, node.field, ["field"]));
230
+ case "Under": return here(underIssues(context, node.prefix, []));
206
231
  case "And":
207
- case "Or": return Arr.flatMap(node.conditions, (child, index) => conditionIssues(context, child, [
208
- ...at,
232
+ case "Or": return Arr.flatMap(node.conditions, (child, index) => conditionIssues(context, child, () => [
233
+ ...at(),
209
234
  "conditions",
210
235
  index
211
236
  ], seen));
212
- case "Not": return conditionIssues(context, node.condition, [...at, "condition"], seen);
237
+ case "Not": return conditionIssues(context, node.condition, () => [...at(), "condition"], seen);
213
238
  default: return [];
214
239
  }
215
240
  };
@@ -252,7 +277,7 @@ const ruleIssues = (domain, rule, at, memo = makeMemo()) => {
252
277
  "fields",
253
278
  index
254
279
  ])),
255
- ...rule.when === void 0 ? [] : isNode(rule.when) ? conditionIssues(context, rule.when, [...at, "when"], /* @__PURE__ */ new Set()) : [notWhole([...at, "when"])]
280
+ ...rule.when === void 0 ? [] : isNode(rule.when) ? conditionIssues(context, rule.when, () => [...at, "when"], /* @__PURE__ */ new Set()) : [notWhole([...at, "when"])]
256
281
  ];
257
282
  };
258
283
  /** Checks each rule of a policy tree against a domain. */
@@ -1 +1 @@
1
- {"version":3,"file":"bind.js","names":["Catalog.accepts","Catalog.lookup","Patterns.parse","Patterns.fitsCatalog","Catalog.anyKinds","Nodes.isNode"],"sources":["../../src/internal/bind.ts"],"sourcesContent":["/**\n * The binding of rules to a domain. It is the one check of a rule, for a rule\n * from the builder, from `Policy.rule`, and from JSON alike. It reads the\n * catalogs of the resources and of the principal, and it gives one issue for\n * each problem, with the path of the problem and, for a name with a mistake,\n * the name that it may mean.\n */\nimport * as Arr from \"effect/Array\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as MutableHashMap from \"effect/MutableHashMap\"\nimport * as Option from \"effect/Option\"\nimport * as Result from \"effect/Result\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Patterns from \"./patterns.ts\"\nimport { withSuggestion } from \"./suggest.ts\"\n\n/** One problem of a rule or a policy, with its path. */\nexport interface Issue {\n\treadonly path: ReadonlyArray<PropertyKey>\n\treadonly issue: string\n}\n\n/** The data of a rule that the binding reads. */\nexport interface RuleData {\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: unknown\n\treadonly fields?: ReadonlyArray<string>\n}\n\n/**\n * The memo of one binding, for all its rules. A policy may name one wrong\n * field or one pattern in very many places, so the work on each is done one\n * time. It goes away when the binding ends.\n */\ninterface Memo {\n\t/** The suggestion for each name, by the place where it was looked for. */\n\treadonly suggestions: MutableHashMap.MutableHashMap<string, string>\n\t/** If a pattern matches a field of a kind, by the kind and the pattern. */\n\treadonly fits: MutableHashMap.MutableHashMap<string, boolean>\n}\n\nconst makeMemo = (): Memo => ({\n\tsuggestions: MutableHashMap.empty(),\n\tfits: MutableHashMap.empty(),\n})\n\n/**\n * The most names that one binding makes a suggestion for. A suggestion\n * compares the name with every name of a catalog, so a policy with very many\n * wrong names would take a long time.\n */\nconst maxSuggestions = 32\n\n/** Adds a suggestion to a message, with the memo of the binding. */\nconst suggestIn = (\n\tmemo: Memo,\n\tmessage: string,\n\tname: string,\n\tscope: string,\n\tnames: () => ReadonlyArray<string>,\n): string => {\n\tconst key = `${scope}\\u0000${name}`\n\tconst known = MutableHashMap.get(memo.suggestions, key)\n\tif (Option.isSome(known)) {\n\t\treturn known.value\n\t}\n\tif (MutableHashMap.size(memo.suggestions) >= maxSuggestions) {\n\t\treturn message\n\t}\n\tconst made = withSuggestion(message, name, names())\n\tMutableHashMap.set(memo.suggestions, key, made)\n\treturn made\n}\n\n/** The kinds that a rule may run on, from the view of its checks. */\ninterface Context {\n\treadonly domain: Domain.Any\n\treadonly memo: Memo\n\t/** The rule is for every resource (`\"*\"`). */\n\treadonly all: boolean\n\t/** The resources that the rule names and that the domain has. */\n\treadonly named: ReadonlyArray<Resource.Descriptor>\n\t/** The named resources that have one of the actions of the rule. */\n\treadonly applicable: ReadonlyArray<Resource.Descriptor>\n\t/**\n\t * The resources whose paths a path test may read: every resource with one\n\t * of the actions, for a rule for every resource.\n\t */\n\treadonly paths: ReadonlyArray<Resource.Descriptor>\n}\n\ntype Operand = Nodes.Operand\n\n/**\n * Tests if a field may hold a value of the type of a fixed value. A test with\n * `null` is a test that the field is missing, so `null` always fits. A number\n * fits any field of numbers: a test of an integer field with `1.5` or\n * `Infinity` has a meaning, as `lt` shows.\n */\nconst fitsLiteral = (kinds: Catalog.Kinds, value: unknown): boolean =>\n\tvalue === null ||\n\t(typeof value === \"number\" && kinds.number !== \"none\") ||\n\tCatalog.accepts(kinds, value)\n\n/**\n * Tests if two fields may hold one scalar. A principal field that is `null`\n * or that holds an object is a missing value, and a test with a missing value\n * is false, so only scalars count.\n */\nconst meet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(left.string && right.string) ||\n\t(left.boolean && right.boolean) ||\n\t(left.number !== \"none\" && right.number !== \"none\") ||\n\tArr.some(left.literals, (value) => Catalog.accepts(right, value)) ||\n\tArr.some(right.literals, (value) => Catalog.accepts(left, value))\n\nconst hasStrings = (kinds: Catalog.Kinds): boolean =>\n\tkinds.string || Arr.some(kinds.literals, (value) => typeof value === \"string\")\n\nconst hasNumbers = (kinds: Catalog.Kinds): boolean =>\n\tkinds.number !== \"none\" ||\n\tArr.some(kinds.literals, (value) => typeof value === \"number\")\n\n/** Tests if a field may hold a value with an order: a number or a string. */\nconst isOrdered = (kinds: Catalog.Kinds): boolean =>\n\thasStrings(kinds) || hasNumbers(kinds)\n\n/** Tests if two fields may hold values of one ordered type. */\nconst orderedMeet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(hasStrings(left) && hasStrings(right)) ||\n\t(hasNumbers(left) && hasNumbers(right))\n\n/** The texts of the fields of a catalog that have only names, for suggestions. */\nconst namesOf = (catalog: Catalog.Catalog): ReadonlyArray<string> =>\n\tArr.map(\n\t\tArr.filter(catalog.fields, (field) =>\n\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t\t),\n\t\t(field) => field.text,\n\t)\n\n/** The issue of a field that is not a field of every resource of the rule. */\nconst fieldIssue = (\n\tcontext: Context,\n\tfield: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst message = `The field \"${field}\" is not a field of every resource of the rule`\n\tif (context.all || context.named.length === 0) {\n\t\treturn [{ path: at, issue: message }]\n\t}\n\tconst without = Arr.filter(\n\t\tcontext.named,\n\t\t(descriptor) => Catalog.lookup(descriptor.catalog, field) === undefined,\n\t)\n\treturn without.length === 0\n\t\t? []\n\t\t: [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: suggestIn(context.memo, message, field, without[0]!.kind, () =>\n\t\t\t\t\t\tnamesOf(without[0]!.catalog),\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t]\n}\n\n/**\n * The issues of a field pattern: a pattern that does not parse, or that\n * matches no field of the resources of the rule, as a pattern with a mistake\n * in a name does.\n */\nconst patternIssues = (\n\tmemo: Memo,\n\tknown: ReadonlyArray<Resource.Descriptor>,\n\tpattern: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> =>\n\tResult.match(Patterns.parse(pattern), {\n\t\tonFailure: (issue) => [{ path: at, issue }],\n\t\tonSuccess: (parsed) =>\n\t\t\t// A rule with no known resource has an issue for that already.\n\t\t\tknown.length === 0 ||\n\t\t\tArr.some(known, (descriptor) => {\n\t\t\t\tconst key = `${descriptor.kind}\\u0000${pattern}`\n\t\t\t\treturn Option.getOrElse(MutableHashMap.get(memo.fits, key), () => {\n\t\t\t\t\tconst fits = Patterns.fitsCatalog(parsed, descriptor.catalog)\n\t\t\t\t\tMutableHashMap.set(memo.fits, key, fits)\n\t\t\t\t\treturn fits\n\t\t\t\t})\n\t\t\t})\n\t\t\t\t? []\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field pattern \"${pattern}\" matches no field of the resources of the rule`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t})\n\n/** The fields of the resources that a test of the rule may read. */\nconst fieldsAt = (\n\tcontext: Context,\n\tfield: string,\n): ReadonlyArray<Catalog.Field> =>\n\tArr.flatMap(context.applicable, (descriptor) => {\n\t\tconst found = Catalog.lookup(descriptor.catalog, field)\n\t\treturn found === undefined ? [] : [found]\n\t})\n\n/** The field of the principal that an operand names, or the issue when there is none. */\nconst principalField = (\n\tcontext: Context,\n\tref: string,\n\tat: ReadonlyArray<PropertyKey>,\n): Catalog.Field | Issue => {\n\tconst found = Catalog.lookup(context.domain.principalCatalog, ref)\n\treturn found !== undefined\n\t\t? found\n\t\t: {\n\t\t\t\tpath: at,\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tcontext.memo,\n\t\t\t\t\t`The principal has no field \"${ref}\"`,\n\t\t\t\t\tref,\n\t\t\t\t\t\"~principal\",\n\t\t\t\t\t() => namesOf(context.domain.principalCatalog),\n\t\t\t\t),\n\t\t\t}\n}\n\nconst isIssue = (value: Catalog.Field | Issue): value is Issue =>\n\t\"issue\" in value\n\n/** The issues of one operand of a test of a field. */\nconst operandIssues = (\n\tcontext: Context,\n\tfield: string,\n\tordered: boolean,\n\toperand: Operand,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst fields = fieldsAt(context, field)\n\tif (operand._tag === \"Literal\") {\n\t\tconst value = operand.value\n\t\tif (ordered && typeof value !== \"string\" && typeof value !== \"number\") {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\treturn [\n\t\t\t...(Arr.some(fields, (found) => !fitsLiteral(found.kinds, value))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" can not hold the value ${JSON.stringify(value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t\t...(ordered && Arr.some(fields, (found) => !isOrdered(found.kinds))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t]\n\t}\n\tconst principal = principalField(context, operand.path, at)\n\tif (isIssue(principal)) {\n\t\treturn [principal]\n\t}\n\treturn [\n\t\t...(Arr.some(fields, (found) => !meet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" can not hold a value of the principal field \"${operand.path}\"`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t\t...(ordered &&\n\t\tArr.some(fields, (found) => !orderedMeet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t]\n}\n\n/**\n * Tests if the path of a resource may start with a prefix: it is as long as\n * the prefix, and each fixed string of the prefix is the string of the path\n * at its place, when the path has a string there.\n */\nconst mayStartWith = (\n\tdescriptor: Resource.Descriptor,\n\tstrings: ReadonlyArray<string | null>,\n): boolean =>\n\t(descriptor.path?.length ?? 0) >= strings.length &&\n\tArr.every(strings, (string, index) => {\n\t\tconst segment = descriptor.path?.[index]\n\t\treturn string === null || typeof segment !== \"string\" || segment === string\n\t})\n\n/** The kinds of the part of the path of a resource at a place. */\nconst pathPartKinds = (\n\tdescriptor: Resource.Descriptor,\n\tindex: number,\n): Catalog.Kinds | string | undefined => {\n\tconst segment = descriptor.path?.[index]\n\treturn segment === undefined || typeof segment === \"string\"\n\t\t? segment\n\t\t: (Catalog.lookup(descriptor.catalog, segment.field)?.kinds ??\n\t\t\t\tCatalog.anyKinds)\n}\n\n/** The issues of the prefix of a path test. */\nconst underIssues = (\n\tcontext: Context,\n\tprefix: ReadonlyArray<Operand>,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst strings = Arr.map(prefix, (operand) =>\n\t\toperand._tag === \"Literal\" && typeof operand.value === \"string\"\n\t\t\t? operand.value\n\t\t\t: null,\n\t)\n\t// The test can match only a path that starts with the fixed strings of\n\t// the prefix, so the other resources are left out.\n\tconst candidates = Arr.filter(context.paths, (descriptor) =>\n\t\tmayStartWith(descriptor, strings),\n\t)\n\treturn Arr.flatMap(prefix, (operand, index): ReadonlyArray<Issue> => {\n\t\tconst place = [...at, \"prefix\", index]\n\t\tif (operand._tag === \"Literal\") {\n\t\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\t\treturn typeof kinds === \"object\" && !fitsLiteral(kinds, operand.value)\n\t\t\t})\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\t\tissue: `The path part ${String(index)} can not hold the value ${JSON.stringify(operand.value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []\n\t\t}\n\t\tconst principal = principalField(context, operand.path, place)\n\t\tif (isIssue(principal)) {\n\t\t\treturn [principal]\n\t\t}\n\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\treturn kinds === undefined\n\t\t\t\t? true\n\t\t\t\t: typeof kinds === \"string\"\n\t\t\t\t\t? !Catalog.accepts(principal.kinds, kinds)\n\t\t\t\t\t: !meet(kinds, principal.kinds)\n\t\t})\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\tissue: `The principal field \"${operand.path}\" can not hold a part of the path of the resource`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []\n\t})\n}\n\nconst notWhole = (at: ReadonlyArray<PropertyKey>): Issue => ({\n\tpath: at,\n\tissue: \"The condition is not a whole condition tree\",\n})\n\n/**\n * The issues of a condition. The constructors of the nodes checked the shape\n * of each node, so this checks the names and the types against the domain. A\n * node that the condition uses in many places is checked one time. The nodes\n * it has seen are in a native `Set`, because it must find a node by the node\n * itself: `Equal` compares nodes by structure.\n */\nconst conditionIssues = (\n\tcontext: Context,\n\tnode: Nodes.Node,\n\tat: ReadonlyArray<PropertyKey>,\n\tseen: Set<Nodes.Node>,\n): ReadonlyArray<Issue> => {\n\tif (seen.has(node)) {\n\t\treturn []\n\t}\n\tseen.add(node)\n\tswitch (node._tag) {\n\t\tcase \"Compare\": {\n\t\t\tconst exists = fieldIssue(context, node.field, [...at, \"field\"])\n\t\t\treturn exists.length > 0\n\t\t\t\t? exists\n\t\t\t\t: operandIssues(\n\t\t\t\t\t\tcontext,\n\t\t\t\t\t\tnode.field,\n\t\t\t\t\t\tnode.op !== \"eq\" && node.op !== \"ne\",\n\t\t\t\t\t\tnode.value,\n\t\t\t\t\t\t[...at, \"value\"],\n\t\t\t\t\t)\n\t\t}\n\t\tcase \"In\": {\n\t\t\tconst exists = fieldIssue(context, node.field, [...at, \"field\"])\n\t\t\treturn exists.length > 0\n\t\t\t\t? exists\n\t\t\t\t: Arr.flatMap(node.values, (operand, index) =>\n\t\t\t\t\t\toperandIssues(context, node.field, false, operand, [\n\t\t\t\t\t\t\t...at,\n\t\t\t\t\t\t\t\"values\",\n\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t]),\n\t\t\t\t\t)\n\t\t}\n\t\tcase \"Exists\":\n\t\t\treturn fieldIssue(context, node.field, [...at, \"field\"])\n\t\tcase \"Under\":\n\t\t\treturn underIssues(context, node.prefix, at)\n\t\tcase \"And\":\n\t\tcase \"Or\":\n\t\t\treturn Arr.flatMap(node.conditions, (child, index) =>\n\t\t\t\tconditionIssues(context, child, [...at, \"conditions\", index], seen),\n\t\t\t)\n\t\tcase \"Not\":\n\t\t\treturn conditionIssues(\n\t\t\t\tcontext,\n\t\t\t\tnode.condition,\n\t\t\t\t[...at, \"condition\"],\n\t\t\t\tseen,\n\t\t\t)\n\t\tdefault:\n\t\t\treturn []\n\t}\n}\n\nconst hasAction = (\n\tdescriptor: Resource.Descriptor,\n\tactions: ReadonlyArray<string>,\n): boolean =>\n\tactions.includes(\"*\") ||\n\tArr.some(actions, (action) => descriptor.actions.includes(action))\n\n/**\n * Checks one rule against a domain. It gives one issue for each problem. `at`\n * is the path of the rule, for the paths of the issues.\n */\nexport const ruleIssues = (\n\tdomain: Domain.Any,\n\trule: RuleData,\n\tat: ReadonlyArray<PropertyKey>,\n\tmemo: Memo = makeMemo(),\n): ReadonlyArray<Issue> => {\n\tconst all = rule.resources.includes(\"*\")\n\t// Each kind is checked one time, also when the list names it again.\n\tconst names = Arr.dedupe(Arr.filter(rule.resources, (kind) => kind !== \"*\"))\n\tconst kinds = Array.from(HashMap.keys(domain.resources))\n\tconst named = Arr.flatMap(names, (kind) =>\n\t\tOption.toArray(HashMap.get(domain.resources, kind)),\n\t)\n\tconst known = all ? Array.from(HashMap.values(domain.resources)) : named\n\t// The rule never runs on a kind without one of its actions, so the types\n\t// of its tests are checked only on the kinds with one.\n\tconst applicable = Arr.filter(named, (descriptor) =>\n\t\thasAction(descriptor, rule.actions),\n\t)\n\tconst context: Context = {\n\t\tdomain,\n\t\tmemo,\n\t\tall,\n\t\tnamed,\n\t\tapplicable,\n\t\tpaths: all\n\t\t\t? Arr.filter(known, (descriptor) => hasAction(descriptor, rule.actions))\n\t\t\t: applicable,\n\t}\n\tconst actions = Arr.dedupe(\n\t\tArr.flatMap(known, (descriptor) => descriptor.actions),\n\t)\n\treturn [\n\t\t...Arr.map(\n\t\t\tArr.filter(names, (kind) => !HashMap.has(domain.resources, kind)),\n\t\t\t(kind): Issue => ({\n\t\t\t\tpath: [...at, \"resources\"],\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tmemo,\n\t\t\t\t\t`There is no resource with the kind \"${kind}\"`,\n\t\t\t\t\tkind,\n\t\t\t\t\t\"~kinds\",\n\t\t\t\t\t() => kinds,\n\t\t\t\t),\n\t\t\t}),\n\t\t),\n\t\t...Arr.map(\n\t\t\tArr.filter(\n\t\t\t\trule.actions,\n\t\t\t\t(action) => action !== \"*\" && !actions.includes(action),\n\t\t\t),\n\t\t\t(action): Issue => ({\n\t\t\t\tpath: [...at, \"actions\"],\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tmemo,\n\t\t\t\t\t`The action \"${action}\" is not an action of these resources`,\n\t\t\t\t\taction,\n\t\t\t\t\t`~actions\\u0000${names.join(\",\")}`,\n\t\t\t\t\t() => actions,\n\t\t\t\t),\n\t\t\t}),\n\t\t),\n\t\t...Arr.flatMap(rule.fields ?? [], (field, index) =>\n\t\t\tfield.includes(\"*\")\n\t\t\t\t? patternIssues(memo, known, field, [...at, \"fields\", index])\n\t\t\t\t: fieldIssue(context, field, [...at, \"fields\", index]),\n\t\t),\n\t\t...(rule.when === undefined\n\t\t\t? []\n\t\t\t: Nodes.isNode(rule.when)\n\t\t\t\t? conditionIssues(context, rule.when, [...at, \"when\"], new Set())\n\t\t\t\t: [notWhole([...at, \"when\"])]),\n\t]\n}\n\n/** The JSON form of a policy tree, or a policy tree, as the binding reads it. */\ntype TreeNode =\n\t| { readonly _tag: \"Rules\"; readonly rules: ReadonlyArray<RuleData> }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly policies: ReadonlyArray<TreeNode>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly cap: TreeNode\n\t\t\treadonly policy: TreeNode\n\t }\n\n/** Checks each rule of a policy tree against a domain. */\nexport const treeIssues = (\n\tdomain: Domain.Any,\n\troot: TreeNode,\n): ReadonlyArray<Issue> => {\n\t// The rules come out of the tree first, and each one is checked after.\n\t// A check of a condition goes down its levels, so a check inside the walk\n\t// of the tree would go down the levels of both.\n\tconst rulesOf = (\n\t\tnode: TreeNode,\n\t\tpath: ReadonlyArray<PropertyKey>,\n\t\tlevel: number,\n\t): ReadonlyArray<Issue | readonly [RuleData, ReadonlyArray<PropertyKey>]> => {\n\t\tif (level > Limits.policyDepth) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tpath,\n\t\t\t\t\tissue: `A policy tree has more than ${String(Limits.policyDepth)} levels. The most is ${String(Limits.policyDepth)}.`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\tswitch (node._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn Arr.map(\n\t\t\t\t\tnode.rules,\n\t\t\t\t\t(rule, index) => [rule, [...path, \"rules\", index]] as const,\n\t\t\t\t)\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn Arr.flatMap(node.policies, (child, index) =>\n\t\t\t\t\trulesOf(child, [...path, \"policies\", index], level + 1),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn [\n\t\t\t\t\t...rulesOf(node.cap, [...path, \"cap\"], level + 1),\n\t\t\t\t\t...rulesOf(node.policy, [...path, \"policy\"], level + 1),\n\t\t\t\t]\n\t\t}\n\t}\n\tconst memo = makeMemo()\n\treturn Arr.flatMap(rulesOf(root, [], 1), (found) =>\n\t\tArray.isArray(found)\n\t\t\t? ruleIssues(domain, found[0], found[1], memo)\n\t\t\t: [found as Issue],\n\t)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bind.js","names":["Catalog.accepts","Catalog.lookup","Patterns.parse","Patterns.fitsCatalog","Catalog.anyKinds","Nodes.isNode"],"sources":["../../src/internal/bind.ts"],"sourcesContent":["/**\n * The binding of rules to a domain. It is the one check of a rule, for a rule\n * from the builder, from `Policy.rule`, and from JSON alike. It reads the\n * catalogs of the resources and of the principal, and it gives one issue for\n * each problem, with the path of the problem and, for a name with a mistake,\n * the name that it may mean.\n */\nimport * as Arr from \"effect/Array\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as MutableHashMap from \"effect/MutableHashMap\"\nimport * as Option from \"effect/Option\"\nimport * as Result from \"effect/Result\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Patterns from \"./patterns.ts\"\nimport { withSuggestion } from \"./suggest.ts\"\n\n/** One problem of a rule or a policy, with its path. */\nexport interface Issue {\n\treadonly path: ReadonlyArray<PropertyKey>\n\treadonly issue: string\n}\n\n/** The data of a rule that the binding reads. */\nexport interface RuleData {\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: unknown\n\treadonly fields?: ReadonlyArray<string>\n}\n\n/**\n * The memo of one binding, for all its rules. A policy may name one wrong\n * field or one pattern in very many places, so the work on each is done one\n * time. It goes away when the binding ends.\n */\ninterface Memo {\n\t/** The suggestion for each name, by the place where it was looked for. */\n\treadonly suggestions: MutableHashMap.MutableHashMap<string, string>\n\t/** If a pattern matches a field of a kind, by the kind and the pattern. */\n\treadonly fits: MutableHashMap.MutableHashMap<string, boolean>\n\t/** The count of the issues of conditions so far. It belongs to one binding. */\n\treadonly issues: { count: number; noted: boolean }\n}\n\nconst makeMemo = (): Memo => ({\n\tsuggestions: MutableHashMap.empty(),\n\tfits: MutableHashMap.empty(),\n\tissues: { count: 0, noted: false },\n})\n\n/**\n * The most names that one binding makes a suggestion for. A suggestion\n * compares the name with every name of a catalog, so a policy with very many\n * wrong names would take a long time.\n */\nconst maxSuggestions = 32\n\n/** Adds a suggestion to a message, with the memo of the binding. */\nconst suggestIn = (\n\tmemo: Memo,\n\tmessage: string,\n\tname: string,\n\tscope: string,\n\tnames: () => ReadonlyArray<string>,\n): string => {\n\tconst key = `${scope}\\u0000${name}`\n\tconst known = MutableHashMap.get(memo.suggestions, key)\n\tif (Option.isSome(known)) {\n\t\treturn known.value\n\t}\n\tif (MutableHashMap.size(memo.suggestions) >= maxSuggestions) {\n\t\treturn message\n\t}\n\tconst made = withSuggestion(message, name, names())\n\tMutableHashMap.set(memo.suggestions, key, made)\n\treturn made\n}\n\n/** The kinds that a rule may run on, from the view of its checks. */\ninterface Context {\n\treadonly domain: Domain.Any\n\treadonly memo: Memo\n\t/** The rule is for every resource (`\"*\"`). */\n\treadonly all: boolean\n\t/** The resources that the rule names and that the domain has. */\n\treadonly named: ReadonlyArray<Resource.Descriptor>\n\t/** The named resources that have one of the actions of the rule. */\n\treadonly applicable: ReadonlyArray<Resource.Descriptor>\n\t/**\n\t * The resources whose paths a path test may read: every resource with one\n\t * of the actions, for a rule for every resource.\n\t */\n\treadonly paths: ReadonlyArray<Resource.Descriptor>\n}\n\ntype Operand = Nodes.Operand\n\n/**\n * Tests if a field may hold a value of the type of a fixed value. A test with\n * `null` is a test that the field is missing, so `null` always fits. A number\n * fits any field of numbers: a test of an integer field with `1.5` or\n * `Infinity` has a meaning, as `lt` shows.\n */\nconst fitsLiteral = (kinds: Catalog.Kinds, value: unknown): boolean =>\n\tvalue === null ||\n\t(typeof value === \"number\" && kinds.number !== \"none\") ||\n\tCatalog.accepts(kinds, value)\n\n/**\n * Tests if two fields may hold one scalar. A principal field that is `null`\n * or that holds an object is a missing value, and a test with a missing value\n * is false, so only scalars count.\n */\nconst meet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(left.string && right.string) ||\n\t(left.boolean && right.boolean) ||\n\t(left.number !== \"none\" && right.number !== \"none\") ||\n\tArr.some(left.literals, (value) => Catalog.accepts(right, value)) ||\n\tArr.some(right.literals, (value) => Catalog.accepts(left, value))\n\nconst hasStrings = (kinds: Catalog.Kinds): boolean =>\n\tkinds.string || Arr.some(kinds.literals, (value) => typeof value === \"string\")\n\nconst hasNumbers = (kinds: Catalog.Kinds): boolean =>\n\tkinds.number !== \"none\" ||\n\tArr.some(kinds.literals, (value) => typeof value === \"number\")\n\n/** Tests if a field may hold a value with an order: a number or a string. */\nconst isOrdered = (kinds: Catalog.Kinds): boolean =>\n\thasStrings(kinds) || hasNumbers(kinds)\n\n/** Tests if two fields may hold values of one ordered type. */\nconst orderedMeet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(hasStrings(left) && hasStrings(right)) ||\n\t(hasNumbers(left) && hasNumbers(right))\n\n/** The texts of the fields of a catalog that have only names, for suggestions. */\nconst namesOf = (catalog: Catalog.Catalog): ReadonlyArray<string> =>\n\tArr.map(\n\t\tArr.filter(catalog.fields, (field) =>\n\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t\t),\n\t\t(field) => field.text,\n\t)\n\n/** The issue of a field that is not a field of every resource of the rule. */\nconst fieldIssue = (\n\tcontext: Context,\n\tfield: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst message = `The field \"${field}\" is not a field of every resource of the rule`\n\tif (context.all || context.named.length === 0) {\n\t\treturn [{ path: at, issue: message }]\n\t}\n\tconst without = Arr.filter(\n\t\tcontext.named,\n\t\t(descriptor) => Catalog.lookup(descriptor.catalog, field) === undefined,\n\t)\n\treturn without.length === 0\n\t\t? []\n\t\t: [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: suggestIn(context.memo, message, field, without[0]!.kind, () =>\n\t\t\t\t\t\tnamesOf(without[0]!.catalog),\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t]\n}\n\n/**\n * The issues of a field pattern: a pattern that does not parse, or that\n * matches no field of the resources of the rule, as a pattern with a mistake\n * in a name does.\n */\nconst patternIssues = (\n\tmemo: Memo,\n\tknown: ReadonlyArray<Resource.Descriptor>,\n\tpattern: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> =>\n\tResult.match(Patterns.parse(pattern), {\n\t\tonFailure: (issue) => [{ path: at, issue }],\n\t\tonSuccess: (parsed) =>\n\t\t\t// A rule with no known resource has an issue for that already.\n\t\t\tknown.length === 0 ||\n\t\t\tArr.some(known, (descriptor) => {\n\t\t\t\tconst key = `${descriptor.kind}\\u0000${pattern}`\n\t\t\t\treturn Option.getOrElse(MutableHashMap.get(memo.fits, key), () => {\n\t\t\t\t\tconst fits = Patterns.fitsCatalog(parsed, descriptor.catalog)\n\t\t\t\t\tMutableHashMap.set(memo.fits, key, fits)\n\t\t\t\t\treturn fits\n\t\t\t\t})\n\t\t\t})\n\t\t\t\t? []\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field pattern \"${pattern}\" matches no field of the resources of the rule`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t})\n\n/** The fields of the resources that a test of the rule may read. */\nconst fieldsAt = (\n\tcontext: Context,\n\tfield: string,\n): ReadonlyArray<Catalog.Field> =>\n\tArr.flatMap(context.applicable, (descriptor) => {\n\t\tconst found = Catalog.lookup(descriptor.catalog, field)\n\t\treturn found === undefined ? [] : [found]\n\t})\n\n/** The field of the principal that an operand names, or the issue when there is none. */\nconst principalField = (\n\tcontext: Context,\n\tref: string,\n\tat: ReadonlyArray<PropertyKey>,\n): Catalog.Field | Issue => {\n\tconst found = Catalog.lookup(context.domain.principalCatalog, ref)\n\treturn found !== undefined\n\t\t? found\n\t\t: {\n\t\t\t\tpath: at,\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tcontext.memo,\n\t\t\t\t\t`The principal has no field \"${ref}\"`,\n\t\t\t\t\tref,\n\t\t\t\t\t\"~principal\",\n\t\t\t\t\t() => namesOf(context.domain.principalCatalog),\n\t\t\t\t),\n\t\t\t}\n}\n\nconst isIssue = (value: Catalog.Field | Issue): value is Issue =>\n\t\"issue\" in value\n\n/** The issues of one operand of a test of a field. */\nconst operandIssues = (\n\tcontext: Context,\n\tfield: string,\n\tordered: boolean,\n\toperand: Operand,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst fields = fieldsAt(context, field)\n\tif (operand._tag === \"Literal\") {\n\t\tconst value = operand.value\n\t\tif (ordered && typeof value !== \"string\" && typeof value !== \"number\") {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\treturn [\n\t\t\t...(Arr.some(fields, (found) => !fitsLiteral(found.kinds, value))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" can not hold the value ${JSON.stringify(value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t\t...(ordered && Arr.some(fields, (found) => !isOrdered(found.kinds))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t]\n\t}\n\tconst principal = principalField(context, operand.path, at)\n\tif (isIssue(principal)) {\n\t\treturn [principal]\n\t}\n\treturn [\n\t\t...(Arr.some(fields, (found) => !meet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" can not hold a value of the principal field \"${operand.path}\"`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t\t...(ordered &&\n\t\tArr.some(fields, (found) => !orderedMeet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t]\n}\n\n/**\n * Tests if the path of a resource may start with a prefix: it is as long as\n * the prefix, and each fixed string of the prefix is the string of the path\n * at its place, when the path has a string there.\n */\nconst mayStartWith = (\n\tdescriptor: Resource.Descriptor,\n\tstrings: ReadonlyArray<string | null>,\n): boolean =>\n\t(descriptor.path?.length ?? 0) >= strings.length &&\n\tArr.every(strings, (string, index) => {\n\t\tconst segment = descriptor.path?.[index]\n\t\treturn string === null || typeof segment !== \"string\" || segment === string\n\t})\n\n/** The kinds of the part of the path of a resource at a place. */\nconst pathPartKinds = (\n\tdescriptor: Resource.Descriptor,\n\tindex: number,\n): Catalog.Kinds | string | undefined => {\n\tconst segment = descriptor.path?.[index]\n\treturn segment === undefined || typeof segment === \"string\"\n\t\t? segment\n\t\t: (Catalog.lookup(descriptor.catalog, segment.field)?.kinds ??\n\t\t\t\tCatalog.anyKinds)\n}\n\n/** The issues of the prefix of a path test. */\nconst underIssues = (\n\tcontext: Context,\n\tprefix: ReadonlyArray<Operand>,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst strings = Arr.map(prefix, (operand) =>\n\t\toperand._tag === \"Literal\" && typeof operand.value === \"string\"\n\t\t\t? operand.value\n\t\t\t: null,\n\t)\n\t// The test can match only a path that starts with the fixed strings of\n\t// the prefix, so the other resources are left out.\n\tconst candidates = Arr.filter(context.paths, (descriptor) =>\n\t\tmayStartWith(descriptor, strings),\n\t)\n\treturn Arr.flatMap(prefix, (operand, index): ReadonlyArray<Issue> => {\n\t\tconst place = [...at, \"prefix\", index]\n\t\tif (operand._tag === \"Literal\") {\n\t\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\t\treturn typeof kinds === \"object\" && !fitsLiteral(kinds, operand.value)\n\t\t\t})\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\t\tissue: `The path part ${String(index)} can not hold the value ${JSON.stringify(operand.value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []\n\t\t}\n\t\tconst principal = principalField(context, operand.path, place)\n\t\tif (isIssue(principal)) {\n\t\t\treturn [principal]\n\t\t}\n\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\treturn kinds === undefined\n\t\t\t\t? true\n\t\t\t\t: typeof kinds === \"string\"\n\t\t\t\t\t? !Catalog.accepts(principal.kinds, kinds)\n\t\t\t\t\t: !meet(kinds, principal.kinds)\n\t\t})\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\tissue: `The principal field \"${operand.path}\" can not hold a part of the path of the resource`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []\n\t})\n}\n\nconst notWhole = (at: ReadonlyArray<PropertyKey>): Issue => ({\n\tpath: at,\n\tissue: \"The condition is not a whole condition tree\",\n})\n\n/**\n * The most issues that the walk of the conditions of one binding gives. A\n * policy may have very many wrong tests, and each issue keeps its path.\n */\nconst maxIssues = 100\n\n/** A path that the walk makes only when it needs it, for an issue. */\ntype LazyPath = () => ReadonlyArray<PropertyKey>\n\n/**\n * Puts the path of a node before the paths of its issues. A walk keeps no\n * path for a node with no issue, so a deep and wide condition costs little.\n */\nconst under = (\n\tmemo: Memo,\n\tat: LazyPath,\n\tissues: ReadonlyArray<Issue>,\n): ReadonlyArray<Issue> => {\n\tif (issues.length === 0) {\n\t\treturn issues\n\t}\n\tconst room = maxIssues - memo.issues.count\n\tmemo.issues.count += issues.length\n\tconst kept = Arr.map(Arr.take(issues, Math.max(room, 0)), (issue) => ({\n\t\tpath: [...at(), ...issue.path],\n\t\tissue: issue.issue,\n\t}))\n\tconst first = memo.issues.count > maxIssues && !memo.issues.noted\n\tmemo.issues.noted ||= first\n\treturn first\n\t\t? [\n\t\t\t\t...kept,\n\t\t\t\t{\n\t\t\t\t\tpath: [],\n\t\t\t\t\tissue: `The policy has more problems. The first ${String(maxIssues)} are here.`,\n\t\t\t\t},\n\t\t\t]\n\t\t: kept\n}\n\n/**\n * The issues of a condition. The constructors of the nodes checked the shape\n * of each node, so this checks the names and the types against the domain. A\n * node that the condition uses in many places is checked one time. The nodes\n * it has seen are in a native `Set`, because it must find a node by the node\n * itself: `Equal` compares nodes by structure.\n */\nconst conditionIssues = (\n\tcontext: Context,\n\tnode: Nodes.Node,\n\tat: LazyPath,\n\tseen: Set<Nodes.Node>,\n): ReadonlyArray<Issue> => {\n\tif (seen.has(node)) {\n\t\treturn []\n\t}\n\tseen.add(node)\n\tconst here = (issues: ReadonlyArray<Issue>) => under(context.memo, at, issues)\n\tswitch (node._tag) {\n\t\tcase \"Compare\": {\n\t\t\tconst exists = fieldIssue(context, node.field, [\"field\"])\n\t\t\treturn here(\n\t\t\t\texists.length > 0\n\t\t\t\t\t? exists\n\t\t\t\t\t: operandIssues(\n\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\tnode.field,\n\t\t\t\t\t\t\tnode.op !== \"eq\" && node.op !== \"ne\",\n\t\t\t\t\t\t\tnode.value,\n\t\t\t\t\t\t\t[\"value\"],\n\t\t\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\tcase \"In\": {\n\t\t\tconst exists = fieldIssue(context, node.field, [\"field\"])\n\t\t\treturn here(\n\t\t\t\texists.length > 0\n\t\t\t\t\t? exists\n\t\t\t\t\t: Arr.flatMap(node.values, (operand, index) =>\n\t\t\t\t\t\t\toperandIssues(context, node.field, false, operand, [\n\t\t\t\t\t\t\t\t\"values\",\n\t\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\tcase \"Exists\":\n\t\t\treturn here(fieldIssue(context, node.field, [\"field\"]))\n\t\tcase \"Under\":\n\t\t\treturn here(underIssues(context, node.prefix, []))\n\t\tcase \"And\":\n\t\tcase \"Or\":\n\t\t\treturn Arr.flatMap(node.conditions, (child, index) =>\n\t\t\t\tconditionIssues(\n\t\t\t\t\tcontext,\n\t\t\t\t\tchild,\n\t\t\t\t\t() => [...at(), \"conditions\", index],\n\t\t\t\t\tseen,\n\t\t\t\t),\n\t\t\t)\n\t\tcase \"Not\":\n\t\t\treturn conditionIssues(\n\t\t\t\tcontext,\n\t\t\t\tnode.condition,\n\t\t\t\t() => [...at(), \"condition\"],\n\t\t\t\tseen,\n\t\t\t)\n\t\tdefault:\n\t\t\treturn []\n\t}\n}\n\nconst hasAction = (\n\tdescriptor: Resource.Descriptor,\n\tactions: ReadonlyArray<string>,\n): boolean =>\n\tactions.includes(\"*\") ||\n\tArr.some(actions, (action) => descriptor.actions.includes(action))\n\n/**\n * Checks one rule against a domain. It gives one issue for each problem. `at`\n * is the path of the rule, for the paths of the issues.\n */\nexport const ruleIssues = (\n\tdomain: Domain.Any,\n\trule: RuleData,\n\tat: ReadonlyArray<PropertyKey>,\n\tmemo: Memo = makeMemo(),\n): ReadonlyArray<Issue> => {\n\tconst all = rule.resources.includes(\"*\")\n\t// Each kind is checked one time, also when the list names it again.\n\tconst names = Arr.dedupe(Arr.filter(rule.resources, (kind) => kind !== \"*\"))\n\tconst kinds = Array.from(HashMap.keys(domain.resources))\n\tconst named = Arr.flatMap(names, (kind) =>\n\t\tOption.toArray(HashMap.get(domain.resources, kind)),\n\t)\n\tconst known = all ? Array.from(HashMap.values(domain.resources)) : named\n\t// The rule never runs on a kind without one of its actions, so the types\n\t// of its tests are checked only on the kinds with one.\n\tconst applicable = Arr.filter(named, (descriptor) =>\n\t\thasAction(descriptor, rule.actions),\n\t)\n\tconst context: Context = {\n\t\tdomain,\n\t\tmemo,\n\t\tall,\n\t\tnamed,\n\t\tapplicable,\n\t\tpaths: all\n\t\t\t? Arr.filter(known, (descriptor) => hasAction(descriptor, rule.actions))\n\t\t\t: applicable,\n\t}\n\tconst actions = Arr.dedupe(\n\t\tArr.flatMap(known, (descriptor) => descriptor.actions),\n\t)\n\treturn [\n\t\t...Arr.map(\n\t\t\tArr.filter(names, (kind) => !HashMap.has(domain.resources, kind)),\n\t\t\t(kind): Issue => ({\n\t\t\t\tpath: [...at, \"resources\"],\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tmemo,\n\t\t\t\t\t`There is no resource with the kind \"${kind}\"`,\n\t\t\t\t\tkind,\n\t\t\t\t\t\"~kinds\",\n\t\t\t\t\t() => kinds,\n\t\t\t\t),\n\t\t\t}),\n\t\t),\n\t\t...Arr.map(\n\t\t\tArr.filter(\n\t\t\t\trule.actions,\n\t\t\t\t(action) => action !== \"*\" && !actions.includes(action),\n\t\t\t),\n\t\t\t(action): Issue => ({\n\t\t\t\tpath: [...at, \"actions\"],\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tmemo,\n\t\t\t\t\t`The action \"${action}\" is not an action of these resources`,\n\t\t\t\t\taction,\n\t\t\t\t\t`~actions\\u0000${names.join(\",\")}`,\n\t\t\t\t\t() => actions,\n\t\t\t\t),\n\t\t\t}),\n\t\t),\n\t\t...Arr.flatMap(rule.fields ?? [], (field, index) =>\n\t\t\tfield.includes(\"*\")\n\t\t\t\t? patternIssues(memo, known, field, [...at, \"fields\", index])\n\t\t\t\t: fieldIssue(context, field, [...at, \"fields\", index]),\n\t\t),\n\t\t...(rule.when === undefined\n\t\t\t? []\n\t\t\t: Nodes.isNode(rule.when)\n\t\t\t\t? conditionIssues(context, rule.when, () => [...at, \"when\"], new Set())\n\t\t\t\t: [notWhole([...at, \"when\"])]),\n\t]\n}\n\n/** The JSON form of a policy tree, or a policy tree, as the binding reads it. */\ntype TreeNode =\n\t| { readonly _tag: \"Rules\"; readonly rules: ReadonlyArray<RuleData> }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly policies: ReadonlyArray<TreeNode>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly cap: TreeNode\n\t\t\treadonly policy: TreeNode\n\t }\n\n/** Checks each rule of a policy tree against a domain. */\nexport const treeIssues = (\n\tdomain: Domain.Any,\n\troot: TreeNode,\n): ReadonlyArray<Issue> => {\n\t// The rules come out of the tree first, and each one is checked after.\n\t// A check of a condition goes down its levels, so a check inside the walk\n\t// of the tree would go down the levels of both.\n\tconst rulesOf = (\n\t\tnode: TreeNode,\n\t\tpath: ReadonlyArray<PropertyKey>,\n\t\tlevel: number,\n\t): ReadonlyArray<Issue | readonly [RuleData, ReadonlyArray<PropertyKey>]> => {\n\t\tif (level > Limits.policyDepth) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tpath,\n\t\t\t\t\tissue: `A policy tree has more than ${String(Limits.policyDepth)} levels. The most is ${String(Limits.policyDepth)}.`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\tswitch (node._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn Arr.map(\n\t\t\t\t\tnode.rules,\n\t\t\t\t\t(rule, index) => [rule, [...path, \"rules\", index]] as const,\n\t\t\t\t)\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn Arr.flatMap(node.policies, (child, index) =>\n\t\t\t\t\trulesOf(child, [...path, \"policies\", index], level + 1),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn [\n\t\t\t\t\t...rulesOf(node.cap, [...path, \"cap\"], level + 1),\n\t\t\t\t\t...rulesOf(node.policy, [...path, \"policy\"], level + 1),\n\t\t\t\t]\n\t\t}\n\t}\n\tconst memo = makeMemo()\n\treturn Arr.flatMap(rulesOf(root, [], 1), (found) =>\n\t\tArray.isArray(found)\n\t\t\t? ruleIssues(domain, found[0], found[1], memo)\n\t\t\t: [found as Issue],\n\t)\n}\n"],"mappings":"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"}
@@ -14,11 +14,18 @@ import * as Arr from "effect/Array";
14
14
  * after, in the Schema of the policy.
15
15
  */
16
16
  /**
17
+ * The codec of the `meta` Schema of a domain to and from JSON. The JSON form
18
+ * of a policy must stay the same through `JSON.stringify` and `JSON.parse`, so
19
+ * a `Date` in `meta`, for one, becomes a string in it, and a string again
20
+ * when it is decoded.
21
+ */
22
+ const metaCodec = (meta) => Schema.toCodecJson(meta);
23
+ /**
17
24
  * Gives the JSON form of the `meta` of each rule at once, with the `meta`
18
25
  * Schema of a domain. `JSON.stringify` needs it at once, so the Schema must
19
26
  * not need an async step there.
20
27
  */
21
- const syncMeta = (meta) => (rule) => rule.meta === void 0 || meta === void 0 ? void 0 : Schema.encodeUnknownSync(meta)(rule.meta);
28
+ const syncMeta = (meta) => (rule) => rule.meta === void 0 || meta === void 0 ? void 0 : Schema.encodeUnknownSync(metaCodec(meta))(rule.meta);
22
29
  /** Gives the JSON form of a rule. The domain of a rule is not in it. */
23
30
  const ruleJson = (rule, metaJson, whenJson) => {
24
31
  const meta = metaJson(rule);
@@ -69,7 +76,7 @@ const toJson = (node, meta) => {
69
76
  */
70
77
  const encode = (node, meta) => Effect.gen(function* () {
71
78
  const rules = Arr.filter(rulesOf(node), (rule) => rule.meta !== void 0);
72
- const encoded = meta === void 0 ? [] : yield* Effect.forEach(rules, (rule) => Effect.map(Schema.encodeUnknownEffect(meta)(rule.meta), (json) => [rule, json]));
79
+ const encoded = meta === void 0 ? [] : yield* Effect.forEach(rules, (rule) => Effect.map(Schema.encodeUnknownEffect(metaCodec(meta))(rule.meta), (json) => [rule, json]));
73
80
  const forms = new Map(encoded);
74
81
  return toJson(node, (rule) => forms.get(rule));
75
82
  });
@@ -116,8 +123,11 @@ const decodeMeta = ({ input, path }, domain, budget) => Effect.flatMap(attempt((
116
123
  const value = read(input, "meta", path);
117
124
  if (domain.meta === void 0) throw problem([...path, "meta"], "The domain has no Schema for meta");
118
125
  spendValue(value, [...path, "meta"], 1, budget);
119
- return { value };
120
- })), (found) => found === void 0 ? Effect.void : Effect.mapError(Schema.decodeUnknownEffect(domain.meta)(found.value), (error) => new Problem({
126
+ return {
127
+ value,
128
+ schema: domain.meta
129
+ };
130
+ })), (found) => found === void 0 ? Effect.void : Effect.mapError(Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value), (error) => new Problem({
121
131
  path: [...path, "meta"],
122
132
  message: error.message,
123
133
  issue: error.issue
@@ -1 +1 @@
1
- {"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Json.readList","Json.problem","read","Json.read","Json.Problem","Json.decodeAt","Json.readString","Rule.make","Limits.policyNodes","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling","Limits.LimitExceeded"],"sources":["../../src/internal/policy-json.ts"],"sourcesContent":["/**\n * The JSON form of a policy tree. The decoder is a plain walk, as the decoder\n * of a condition is, and the two share one budget of `jsonNodes` objects and\n * items. It makes each node with its constructor, so the limits of the nodes\n * hold for a decoded tree too. The binding of the rules to the domain comes\n * after, in the Schema of the policy.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport * as Rule from \"./rule.ts\"\nimport * as Json from \"./json.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\n\ntype Node = PolicyNodes.Node\n\n/** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */\nexport type MetaJson = (rule: Rule.Rule) => unknown\n\n/**\n * Gives the JSON form of the `meta` of each rule at once, with the `meta`\n * Schema of a domain. `JSON.stringify` needs it at once, so the Schema must\n * not need an async step there.\n */\nexport const syncMeta =\n\t(meta: Schema.Top | undefined): MetaJson =>\n\t(rule) =>\n\t\trule.meta === undefined || meta === undefined\n\t\t\t? undefined\n\t\t\t: Schema.encodeUnknownSync(meta as Schema.Codec<unknown, unknown>)(\n\t\t\t\t\trule.meta,\n\t\t\t\t)\n\n/** The JSON form of the condition of each rule, by the rule. */\ntype WhenJson = (rule: Rule.Rule) => unknown\n\n/** Gives the JSON form of a rule. The domain of a rule is not in it. */\nconst ruleJson = (\n\trule: Rule.Rule,\n\tmetaJson: MetaJson,\n\twhenJson: WhenJson,\n): unknown => {\n\tconst meta = metaJson(rule)\n\treturn {\n\t\t_tag: rule._tag,\n\t\tactions: rule.actions,\n\t\tresources: rule.resources,\n\t\t...(rule.when === undefined ? {} : { when: whenJson(rule) }),\n\t\t...(rule.fields === undefined ? {} : { fields: rule.fields }),\n\t\t...(rule.reason === undefined ? {} : { reason: rule.reason }),\n\t\t...(rule.id === undefined ? {} : { id: rule.id }),\n\t\t// The `meta` of a rule is a decoded value. Its JSON form comes from the\n\t\t// `meta` Schema of the domain.\n\t\t...(meta === undefined ? {} : { meta }),\n\t}\n}\n\n/**\n * Gives the JSON form of a policy tree with no predicate. A condition with a\n * predicate has no JSON form, so it throws an error. The conditions are\n * written first, from a flat list of the rules, so the walk does not go down\n * the levels of the tree and of a condition at once.\n */\nexport const toJson = (node: Node, meta: MetaJson): unknown => {\n\t// A native map, because it must find a rule by the rule itself: two rules\n\t// with the same data are `Equal`.\n\tconst whens = new Map<Rule.Rule, unknown>(\n\t\tArr.map(PolicyNodes.rulesOf(node), (rule) => [\n\t\t\trule,\n\t\t\trule.when === undefined ? undefined : Json.toJson(rule.when as never),\n\t\t]),\n\t)\n\tconst tree = (current: Node): unknown => {\n\t\tswitch (current._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: current._tag,\n\t\t\t\t\tpolicies: Arr.map(current.policies, tree),\n\t\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\tcap: tree(current.cap),\n\t\t\t\t\tpolicy: tree(current.policy),\n\t\t\t\t}\n\t\t}\n\t}\n\treturn tree(node)\n}\n\n/**\n * Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the\n * domain may need an async step, so each `meta` is encoded as an Effect.\n */\nexport const encode = (\n\tnode: Node,\n\tmeta: Schema.Top | undefined,\n): Effect.Effect<unknown, Schema.SchemaError> =>\n\tEffect.gen(function* () {\n\t\tconst rules = Arr.filter(\n\t\t\tPolicyNodes.rulesOf(node),\n\t\t\t(rule) => rule.meta !== undefined,\n\t\t)\n\t\tconst encoded =\n\t\t\tmeta === undefined\n\t\t\t\t? []\n\t\t\t\t: yield* Effect.forEach(rules, (rule) =>\n\t\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t\tSchema.encodeUnknownEffect(\n\t\t\t\t\t\t\t\tmeta as Schema.Codec<unknown, unknown>,\n\t\t\t\t\t\t\t)(rule.meta),\n\t\t\t\t\t\t\t(json) => [rule, json] as const,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t// A native map, because it must find a rule by the rule itself: two\n\t\t// rules with the same data are `Equal`, and each keeps its own form.\n\t\tconst forms = new Map<Rule.Rule, unknown>(encoded)\n\t\treturn toJson(node, (rule) => forms.get(rule))\n\t})\n\nconst algorithms: ReadonlyArray<string> = [\n\t\"denyOverrides\",\n\t\"permitOverrides\",\n\t\"firstApplicable\",\n\t\"lastApplicable\",\n]\n\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, path, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problem([...path, name, index], \"The item must be a string\")\n\t\t}\n\t\treturn value\n\t})\n\n/**\n * Reads a field that a rule may leave out. A field that the input has only\n * from the prototype of a class is not plain data, so it is a problem: a rule\n * that loses its condition would allow more.\n */\nconst readOptional = <A>(\n\tinput: object,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tread: () => A,\n): A | undefined => {\n\tif (Object.hasOwn(input, name)) {\n\t\treturn read()\n\t}\n\t// A read of the field throws a problem when the field is not data.\n\tJson.read(input, name, path)\n\treturn undefined\n}\n\n/** Counts the objects and items of a value, as the other parts count. */\nconst spendValue = (\n\tvalue: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Json.Budget,\n): void => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn\n\t}\n\tif (depth > Limits.conditionDepth) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`The value has more than ${String(Limits.conditionDepth)} levels. The most is ${String(Limits.conditionDepth)}.`,\n\t\t)\n\t}\n\t// The length of an array counts before its items are read, so a sparse\n\t// array with a very large length stops at once.\n\tconst list = Array.isArray(value)\n\tif (list) {\n\t\tJson.spend(budget, 1 + value.length, path)\n\t}\n\tconst names = list\n\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t: Object.keys(value)\n\tif (!list) {\n\t\tJson.spend(budget, 1 + names.length, path)\n\t}\n\tArr.forEach(names, (name) =>\n\t\tspendValue(\n\t\t\tJson.read(value, name, path),\n\t\t\t[...path, name],\n\t\t\tdepth + 1,\n\t\t\tbudget,\n\t\t),\n\t)\n}\n\n/** A rule in the input, with its place, to decode after the tree. */\ninterface RuleInput {\n\treadonly input: unknown\n\treadonly path: ReadonlyArray<PropertyKey>\n}\n\n/**\n * The shape of a tree, with the place of each rule in a flat list. `size`\n * counts the nodes and the rules, as the nodes do, so a shared input stops\n * at `policyNodes` before the decoder reads all of it.\n */\ntype Plan =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly path: ReadonlyArray<PropertyKey>\n\t\t\treadonly size: number\n\t\t\treadonly algorithm: PolicyNodes.Algorithm\n\t\t\treadonly rules: ReadonlyArray<number>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly path: ReadonlyArray<PropertyKey>\n\t\t\treadonly size: number\n\t\t\treadonly policies: ReadonlyArray<Plan>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly path: ReadonlyArray<PropertyKey>\n\t\t\treadonly size: number\n\t\t\treadonly cap: Plan\n\t\t\treadonly policy: Plan\n\t }\n\n/**\n * Decodes the `meta` of a rule with the `meta` Schema of the domain, as an\n * Effect, since the Schema may need an async step. The Schema reads all of\n * the value, so its size counts in the budget.\n */\nconst decodeMeta = (\n\t{ input, path }: RuleInput,\n\tdomain: Domain.Any,\n\tbudget: Json.Budget,\n): Effect.Effect<unknown, Json.Problem> =>\n\tEffect.flatMap(\n\t\tattempt(() =>\n\t\t\ttypeof input !== \"object\" || input === null\n\t\t\t\t? undefined\n\t\t\t\t: readOptional(input, \"meta\", path, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", path)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problem(\n\t\t\t\t\t\t\t\t[...path, \"meta\"],\n\t\t\t\t\t\t\t\t\"The domain has no Schema for meta\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(value, [...path, \"meta\"], 1, budget)\n\t\t\t\t\t\treturn { value }\n\t\t\t\t\t}),\n\t\t),\n\t\t(found) =>\n\t\t\tfound === undefined\n\t\t\t\t? Effect.void\n\t\t\t\t: Effect.mapError(\n\t\t\t\t\t\tSchema.decodeUnknownEffect(\n\t\t\t\t\t\t\tdomain.meta as Schema.Codec<unknown, unknown>,\n\t\t\t\t\t\t)(found.value),\n\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\tnew Json.Problem({\n\t\t\t\t\t\t\t\tpath: [...path, \"meta\"],\n\t\t\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\t\t\tissue: error.issue,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t)\n\nconst decodeRule = (\n\t{ input, path }: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problem(path, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, path)\n\tconst tag = Json.read(input, \"_tag\", path)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problem([...path, \"_tag\"], 'A rule must be \"Allow\" or \"Deny\"')\n\t}\n\tconst when = readOptional(input, \"when\", path, () =>\n\t\tJson.decodeAt(Json.read(input, \"when\", path), [...path, \"when\"], budget),\n\t)\n\tconst fields = readOptional(input, \"fields\", path, () =>\n\t\treadStrings(input, \"fields\", path, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", path, () =>\n\t\tJson.readString(input, \"reason\", path),\n\t)\n\tconst id = readOptional(input, \"id\", path, () =>\n\t\tJson.readString(input, \"id\", path),\n\t)\n\t// Each rule gets the domain of the Schema, as a rule from the builder of\n\t// the domain has it.\n\treturn Rule.make({\n\t\t_tag: tag,\n\t\tactions: readStrings(input, \"actions\", path, budget),\n\t\tresources: readStrings(input, \"resources\", path, budget),\n\t\t...(when === undefined ? {} : { when: when as never }),\n\t\t...(fields === undefined ? {} : { fields }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t\t...(id === undefined ? {} : { id }),\n\t\t...(meta === undefined ? {} : { meta }),\n\t\tdomain,\n\t})\n}\n\n/**\n * Reads the shape of a tree, and puts each rule in `rules`. The rules are\n * decoded after the walk: a condition has levels of its own, and a decode\n * inside the walk would go down the levels of both.\n */\nconst readTree = (\n\tinput: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): Plan => {\n\tif (depth > Limits.policyDepth) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`A policy tree has more than ${String(Limits.policyDepth)} levels. The most is ${String(Limits.policyDepth)}.`,\n\t\t)\n\t}\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problem(path, \"A node of a policy must be an object\")\n\t}\n\tJson.spend(budget, 1, path)\n\tconst child = (value: unknown, at: ReadonlyArray<PropertyKey>) =>\n\t\treadTree(value, at, depth + 1, budget, rules)\n\tconst tag = Json.read(input, \"_tag\", path)\n\tswitch (tag) {\n\t\tcase \"Rules\": {\n\t\t\tconst algorithm = Json.readString(input, \"algorithm\", path)\n\t\t\tif (!algorithms.includes(algorithm)) {\n\t\t\t\tthrow Json.problem(\n\t\t\t\t\t[...path, \"algorithm\"],\n\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst found = Arr.map(\n\t\t\t\tJson.readList(input, \"rules\", path, budget),\n\t\t\t\t(rule, index) =>\n\t\t\t\t\trules.push({ input: rule, path: [...path, \"rules\", index] }) - 1,\n\t\t\t)\n\t\t\treturn sized({\n\t\t\t\t_tag: \"Rules\",\n\t\t\t\tpath,\n\t\t\t\tsize: 1 + found.length,\n\t\t\t\talgorithm: algorithm as PolicyNodes.Algorithm,\n\t\t\t\trules: found,\n\t\t\t})\n\t\t}\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\": {\n\t\t\tconst policies = Arr.map(\n\t\t\t\tJson.readList(input, \"policies\", path, budget),\n\t\t\t\t(value, index) => child(value, [...path, \"policies\", index]),\n\t\t\t)\n\t\t\treturn sized({\n\t\t\t\t_tag: tag,\n\t\t\t\tpath,\n\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\tpolicies,\n\t\t\t})\n\t\t}\n\t\tcase \"Ceiling\": {\n\t\t\tconst cap = child(Json.read(input, \"cap\", path), [...path, \"cap\"])\n\t\t\tconst policy = child(Json.read(input, \"policy\", path), [\n\t\t\t\t...path,\n\t\t\t\t\"policy\",\n\t\t\t])\n\t\t\treturn sized({\n\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\tpath,\n\t\t\t\tsize: 1 + cap.size + policy.size,\n\t\t\t\tcap,\n\t\t\t\tpolicy,\n\t\t\t})\n\t\t}\n\t\tdefault:\n\t\t\tthrow Json.problem([...path, \"_tag\"], \"The node has no known tag\")\n\t}\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (plan: Plan): Plan => {\n\tif (plan.size > Limits.policyNodes) {\n\t\tthrow Json.problem(\n\t\t\tplan.path,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(Limits.policyNodes)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/** Makes the nodes of a plan, with the decoded rules. */\nconst build = (plan: Plan, rules: ReadonlyArray<Rule.Rule>): Node => {\n\ttry {\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn new PolicyNodes.Rules({\n\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t})\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn new PolicyNodes[plan._tag](\n\t\t\t\t\tArr.map(plan.policies, (child) => build(child, rules)),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn new PolicyNodes.Ceiling({\n\t\t\t\t\tcap: build(plan.cap, rules),\n\t\t\t\t\tpolicy: build(plan.policy, rules),\n\t\t\t\t})\n\t\t}\n\t} catch (error) {\n\t\t// A node past a limit is a problem at its place.\n\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\tthrow Json.problem(plan.path, error.message)\n\t\t}\n\t\tthrow error\n\t}\n}\n\n/** Runs a step that may throw a problem, and puts the problem in the error. */\nconst attempt = <A>(step: () => A): Effect.Effect<A, Json.Problem> =>\n\tEffect.try({\n\t\ttry: step,\n\t\tcatch: (error) => {\n\t\t\tif (error instanceof Json.Problem) {\n\t\t\t\treturn error\n\t\t\t}\n\t\t\tthrow error\n\t\t},\n\t})\n\n/**\n * Decodes the JSON form of a policy tree for a domain. It reads the tree\n * first, then the `meta` of each rule as an Effect, then the rules, and it\n * makes the nodes last.\n */\nexport const decode = (\n\tinput: unknown,\n\tdomain: Domain.Any,\n): Effect.Effect<Node, Json.Problem> =>\n\tEffect.gen(function* () {\n\t\tconst budget: Json.Budget = { used: 0 }\n\t\t// The rules of this decode. The list belongs to this decode.\n\t\tconst inputs: Array<RuleInput> = []\n\t\tconst plan = yield* attempt(() => readTree(input, [], 1, budget, inputs))\n\t\tconst metas = yield* Effect.forEach(inputs, (rule) =>\n\t\t\tdecodeMeta(rule, domain, budget),\n\t\t)\n\t\tconst rules = yield* attempt(() =>\n\t\t\tArr.map(inputs, (rule, index) =>\n\t\t\t\tdecodeRule(rule, metas[index], budget, domain),\n\t\t\t),\n\t\t)\n\t\treturn yield* attempt(() => build(plan, rules))\n\t})\n"],"mappings":"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"}
1
+ {"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Json.readList","Json.problem","read","Json.read","Json.Problem","Json.decodeAt","Json.readString","Rule.make","Limits.policyNodes","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling","Limits.LimitExceeded"],"sources":["../../src/internal/policy-json.ts"],"sourcesContent":["/**\n * The JSON form of a policy tree. The decoder is a plain walk, as the decoder\n * of a condition is, and the two share one budget of `jsonNodes` objects and\n * items. It makes each node with its constructor, so the limits of the nodes\n * hold for a decoded tree too. The binding of the rules to the domain comes\n * after, in the Schema of the policy.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport * as Rule from \"./rule.ts\"\nimport * as Json from \"./json.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\n\ntype Node = PolicyNodes.Node\n\n/**\n * The codec of the `meta` Schema of a domain to and from JSON. The JSON form\n * of a policy must stay the same through `JSON.stringify` and `JSON.parse`, so\n * a `Date` in `meta`, for one, becomes a string in it, and a string again\n * when it is decoded.\n */\nconst metaCodec = (meta: Schema.Top): Schema.Codec<unknown, unknown> =>\n\tSchema.toCodecJson(meta) as unknown as Schema.Codec<unknown, unknown>\n\n/** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */\nexport type MetaJson = (rule: Rule.Rule) => unknown\n\n/**\n * Gives the JSON form of the `meta` of each rule at once, with the `meta`\n * Schema of a domain. `JSON.stringify` needs it at once, so the Schema must\n * not need an async step there.\n */\nexport const syncMeta =\n\t(meta: Schema.Top | undefined): MetaJson =>\n\t(rule) =>\n\t\trule.meta === undefined || meta === undefined\n\t\t\t? undefined\n\t\t\t: Schema.encodeUnknownSync(metaCodec(meta))(rule.meta)\n\n/** The JSON form of the condition of each rule, by the rule. */\ntype WhenJson = (rule: Rule.Rule) => unknown\n\n/** Gives the JSON form of a rule. The domain of a rule is not in it. */\nconst ruleJson = (\n\trule: Rule.Rule,\n\tmetaJson: MetaJson,\n\twhenJson: WhenJson,\n): unknown => {\n\tconst meta = metaJson(rule)\n\treturn {\n\t\t_tag: rule._tag,\n\t\tactions: rule.actions,\n\t\tresources: rule.resources,\n\t\t...(rule.when === undefined ? {} : { when: whenJson(rule) }),\n\t\t...(rule.fields === undefined ? {} : { fields: rule.fields }),\n\t\t...(rule.reason === undefined ? {} : { reason: rule.reason }),\n\t\t...(rule.id === undefined ? {} : { id: rule.id }),\n\t\t// The `meta` of a rule is a decoded value. Its JSON form comes from the\n\t\t// `meta` Schema of the domain.\n\t\t...(meta === undefined ? {} : { meta }),\n\t}\n}\n\n/**\n * Gives the JSON form of a policy tree with no predicate. A condition with a\n * predicate has no JSON form, so it throws an error. The conditions are\n * written first, from a flat list of the rules, so the walk does not go down\n * the levels of the tree and of a condition at once.\n */\nexport const toJson = (node: Node, meta: MetaJson): unknown => {\n\t// A native map, because it must find a rule by the rule itself: two rules\n\t// with the same data are `Equal`.\n\tconst whens = new Map<Rule.Rule, unknown>(\n\t\tArr.map(PolicyNodes.rulesOf(node), (rule) => [\n\t\t\trule,\n\t\t\trule.when === undefined ? undefined : Json.toJson(rule.when as never),\n\t\t]),\n\t)\n\tconst tree = (current: Node): unknown => {\n\t\tswitch (current._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: current._tag,\n\t\t\t\t\tpolicies: Arr.map(current.policies, tree),\n\t\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\tcap: tree(current.cap),\n\t\t\t\t\tpolicy: tree(current.policy),\n\t\t\t\t}\n\t\t}\n\t}\n\treturn tree(node)\n}\n\n/**\n * Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the\n * domain may need an async step, so each `meta` is encoded as an Effect.\n */\nexport const encode = (\n\tnode: Node,\n\tmeta: Schema.Top | undefined,\n): Effect.Effect<unknown, Schema.SchemaError> =>\n\tEffect.gen(function* () {\n\t\tconst rules = Arr.filter(\n\t\t\tPolicyNodes.rulesOf(node),\n\t\t\t(rule) => rule.meta !== undefined,\n\t\t)\n\t\tconst encoded =\n\t\t\tmeta === undefined\n\t\t\t\t? []\n\t\t\t\t: yield* Effect.forEach(rules, (rule) =>\n\t\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t\tSchema.encodeUnknownEffect(metaCodec(meta))(rule.meta),\n\t\t\t\t\t\t\t(json) => [rule, json] as const,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t// A native map, because it must find a rule by the rule itself: two\n\t\t// rules with the same data are `Equal`, and each keeps its own form.\n\t\tconst forms = new Map<Rule.Rule, unknown>(encoded)\n\t\treturn toJson(node, (rule) => forms.get(rule))\n\t})\n\nconst algorithms: ReadonlyArray<string> = [\n\t\"denyOverrides\",\n\t\"permitOverrides\",\n\t\"firstApplicable\",\n\t\"lastApplicable\",\n]\n\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, path, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problem([...path, name, index], \"The item must be a string\")\n\t\t}\n\t\treturn value\n\t})\n\n/**\n * Reads a field that a rule may leave out. A field that the input has only\n * from the prototype of a class is not plain data, so it is a problem: a rule\n * that loses its condition would allow more.\n */\nconst readOptional = <A>(\n\tinput: object,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tread: () => A,\n): A | undefined => {\n\tif (Object.hasOwn(input, name)) {\n\t\treturn read()\n\t}\n\t// A read of the field throws a problem when the field is not data.\n\tJson.read(input, name, path)\n\treturn undefined\n}\n\n/** Counts the objects and items of a value, as the other parts count. */\nconst spendValue = (\n\tvalue: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Json.Budget,\n): void => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn\n\t}\n\tif (depth > Limits.conditionDepth) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`The value has more than ${String(Limits.conditionDepth)} levels. The most is ${String(Limits.conditionDepth)}.`,\n\t\t)\n\t}\n\t// The length of an array counts before its items are read, so a sparse\n\t// array with a very large length stops at once.\n\tconst list = Array.isArray(value)\n\tif (list) {\n\t\tJson.spend(budget, 1 + value.length, path)\n\t}\n\tconst names = list\n\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t: Object.keys(value)\n\tif (!list) {\n\t\tJson.spend(budget, 1 + names.length, path)\n\t}\n\tArr.forEach(names, (name) =>\n\t\tspendValue(\n\t\t\tJson.read(value, name, path),\n\t\t\t[...path, name],\n\t\t\tdepth + 1,\n\t\t\tbudget,\n\t\t),\n\t)\n}\n\n/** A rule in the input, with its place, to decode after the tree. */\ninterface RuleInput {\n\treadonly input: unknown\n\treadonly path: ReadonlyArray<PropertyKey>\n}\n\n/**\n * The shape of a tree, with the place of each rule in a flat list. `size`\n * counts the nodes and the rules, as the nodes do, so a shared input stops\n * at `policyNodes` before the decoder reads all of it.\n */\ntype Plan =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly path: ReadonlyArray<PropertyKey>\n\t\t\treadonly size: number\n\t\t\treadonly algorithm: PolicyNodes.Algorithm\n\t\t\treadonly rules: ReadonlyArray<number>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly path: ReadonlyArray<PropertyKey>\n\t\t\treadonly size: number\n\t\t\treadonly policies: ReadonlyArray<Plan>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly path: ReadonlyArray<PropertyKey>\n\t\t\treadonly size: number\n\t\t\treadonly cap: Plan\n\t\t\treadonly policy: Plan\n\t }\n\n/**\n * Decodes the `meta` of a rule with the `meta` Schema of the domain, as an\n * Effect, since the Schema may need an async step. The Schema reads all of\n * the value, so its size counts in the budget.\n */\nconst decodeMeta = (\n\t{ input, path }: RuleInput,\n\tdomain: Domain.Any,\n\tbudget: Json.Budget,\n): Effect.Effect<unknown, Json.Problem> =>\n\tEffect.flatMap(\n\t\tattempt(() =>\n\t\t\ttypeof input !== \"object\" || input === null\n\t\t\t\t? undefined\n\t\t\t\t: readOptional(input, \"meta\", path, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", path)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problem(\n\t\t\t\t\t\t\t\t[...path, \"meta\"],\n\t\t\t\t\t\t\t\t\"The domain has no Schema for meta\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(value, [...path, \"meta\"], 1, budget)\n\t\t\t\t\t\treturn { value, schema: domain.meta }\n\t\t\t\t\t}),\n\t\t),\n\t\t(found) =>\n\t\t\tfound === undefined\n\t\t\t\t? Effect.void\n\t\t\t\t: Effect.mapError(\n\t\t\t\t\t\tSchema.decodeUnknownEffect(metaCodec(found.schema))(found.value),\n\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\tnew Json.Problem({\n\t\t\t\t\t\t\t\tpath: [...path, \"meta\"],\n\t\t\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\t\t\tissue: error.issue,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t)\n\nconst decodeRule = (\n\t{ input, path }: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problem(path, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, path)\n\tconst tag = Json.read(input, \"_tag\", path)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problem([...path, \"_tag\"], 'A rule must be \"Allow\" or \"Deny\"')\n\t}\n\tconst when = readOptional(input, \"when\", path, () =>\n\t\tJson.decodeAt(Json.read(input, \"when\", path), [...path, \"when\"], budget),\n\t)\n\tconst fields = readOptional(input, \"fields\", path, () =>\n\t\treadStrings(input, \"fields\", path, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", path, () =>\n\t\tJson.readString(input, \"reason\", path),\n\t)\n\tconst id = readOptional(input, \"id\", path, () =>\n\t\tJson.readString(input, \"id\", path),\n\t)\n\t// Each rule gets the domain of the Schema, as a rule from the builder of\n\t// the domain has it.\n\treturn Rule.make({\n\t\t_tag: tag,\n\t\tactions: readStrings(input, \"actions\", path, budget),\n\t\tresources: readStrings(input, \"resources\", path, budget),\n\t\t...(when === undefined ? {} : { when: when as never }),\n\t\t...(fields === undefined ? {} : { fields }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t\t...(id === undefined ? {} : { id }),\n\t\t...(meta === undefined ? {} : { meta }),\n\t\tdomain,\n\t})\n}\n\n/**\n * Reads the shape of a tree, and puts each rule in `rules`. The rules are\n * decoded after the walk: a condition has levels of its own, and a decode\n * inside the walk would go down the levels of both.\n */\nconst readTree = (\n\tinput: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): Plan => {\n\tif (depth > Limits.policyDepth) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`A policy tree has more than ${String(Limits.policyDepth)} levels. The most is ${String(Limits.policyDepth)}.`,\n\t\t)\n\t}\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problem(path, \"A node of a policy must be an object\")\n\t}\n\tJson.spend(budget, 1, path)\n\tconst child = (value: unknown, at: ReadonlyArray<PropertyKey>) =>\n\t\treadTree(value, at, depth + 1, budget, rules)\n\tconst tag = Json.read(input, \"_tag\", path)\n\tswitch (tag) {\n\t\tcase \"Rules\": {\n\t\t\tconst algorithm = Json.readString(input, \"algorithm\", path)\n\t\t\tif (!algorithms.includes(algorithm)) {\n\t\t\t\tthrow Json.problem(\n\t\t\t\t\t[...path, \"algorithm\"],\n\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst found = Arr.map(\n\t\t\t\tJson.readList(input, \"rules\", path, budget),\n\t\t\t\t(rule, index) =>\n\t\t\t\t\trules.push({ input: rule, path: [...path, \"rules\", index] }) - 1,\n\t\t\t)\n\t\t\treturn sized({\n\t\t\t\t_tag: \"Rules\",\n\t\t\t\tpath,\n\t\t\t\tsize: 1 + found.length,\n\t\t\t\talgorithm: algorithm as PolicyNodes.Algorithm,\n\t\t\t\trules: found,\n\t\t\t})\n\t\t}\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\": {\n\t\t\tconst policies = Arr.map(\n\t\t\t\tJson.readList(input, \"policies\", path, budget),\n\t\t\t\t(value, index) => child(value, [...path, \"policies\", index]),\n\t\t\t)\n\t\t\treturn sized({\n\t\t\t\t_tag: tag,\n\t\t\t\tpath,\n\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\tpolicies,\n\t\t\t})\n\t\t}\n\t\tcase \"Ceiling\": {\n\t\t\tconst cap = child(Json.read(input, \"cap\", path), [...path, \"cap\"])\n\t\t\tconst policy = child(Json.read(input, \"policy\", path), [\n\t\t\t\t...path,\n\t\t\t\t\"policy\",\n\t\t\t])\n\t\t\treturn sized({\n\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\tpath,\n\t\t\t\tsize: 1 + cap.size + policy.size,\n\t\t\t\tcap,\n\t\t\t\tpolicy,\n\t\t\t})\n\t\t}\n\t\tdefault:\n\t\t\tthrow Json.problem([...path, \"_tag\"], \"The node has no known tag\")\n\t}\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (plan: Plan): Plan => {\n\tif (plan.size > Limits.policyNodes) {\n\t\tthrow Json.problem(\n\t\t\tplan.path,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(Limits.policyNodes)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/** Makes the nodes of a plan, with the decoded rules. */\nconst build = (plan: Plan, rules: ReadonlyArray<Rule.Rule>): Node => {\n\ttry {\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn new PolicyNodes.Rules({\n\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t})\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn new PolicyNodes[plan._tag](\n\t\t\t\t\tArr.map(plan.policies, (child) => build(child, rules)),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn new PolicyNodes.Ceiling({\n\t\t\t\t\tcap: build(plan.cap, rules),\n\t\t\t\t\tpolicy: build(plan.policy, rules),\n\t\t\t\t})\n\t\t}\n\t} catch (error) {\n\t\t// A node past a limit is a problem at its place.\n\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\tthrow Json.problem(plan.path, error.message)\n\t\t}\n\t\tthrow error\n\t}\n}\n\n/** Runs a step that may throw a problem, and puts the problem in the error. */\nconst attempt = <A>(step: () => A): Effect.Effect<A, Json.Problem> =>\n\tEffect.try({\n\t\ttry: step,\n\t\tcatch: (error) => {\n\t\t\tif (error instanceof Json.Problem) {\n\t\t\t\treturn error\n\t\t\t}\n\t\t\tthrow error\n\t\t},\n\t})\n\n/**\n * Decodes the JSON form of a policy tree for a domain. It reads the tree\n * first, then the `meta` of each rule as an Effect, then the rules, and it\n * makes the nodes last.\n */\nexport const decode = (\n\tinput: unknown,\n\tdomain: Domain.Any,\n): Effect.Effect<Node, Json.Problem> =>\n\tEffect.gen(function* () {\n\t\tconst budget: Json.Budget = { used: 0 }\n\t\t// The rules of this decode. The list belongs to this decode.\n\t\tconst inputs: Array<RuleInput> = []\n\t\tconst plan = yield* attempt(() => readTree(input, [], 1, budget, inputs))\n\t\tconst metas = yield* Effect.forEach(inputs, (rule) =>\n\t\t\tdecodeMeta(rule, domain, budget),\n\t\t)\n\t\tconst rules = yield* attempt(() =>\n\t\t\tArr.map(inputs, (rule, index) =>\n\t\t\t\tdecodeRule(rule, metas[index], budget, domain),\n\t\t\t),\n\t\t)\n\t\treturn yield* attempt(() => build(plan, rules))\n\t})\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"rule.d.ts","names":["Condition.Ast","Domain.Any"],"sources":["../../src/internal/rule.ts"],"mappings":";;;;;iBAiBiB;WACP;WACA,SAAS;WACT,WAAW;WACX,OAAOA;WACP,SAAS;WACT;WACA;WACA;;;;;;WAMA,SAASC;;;qBAIN,aAAa,KAAK,MAAM;GAClC,OAAO,aAAa"}
1
+ {"version":3,"file":"rule.d.ts","names":["Condition.Ast","Domain.Any"],"sources":["../../src/internal/rule.ts"],"mappings":";;;;;iBAkBiB;WACP;WACA,SAAS;WACT,WAAW;WACX,OAAOA;WACP,SAAS;WACT;WACA;WACA;;;;;;WAMA,SAASC;;;qBAIN,aAAa,KAAK,MAAM;GAClC,OAAO,aAAa"}
@@ -1,3 +1,4 @@
1
+ import { isNode } from "./nodes.js";
1
2
  import { lookup } from "./catalog.js";
2
3
  import { descriptor, isResource } from "./resource.js";
3
4
  import { builder as builder$1 } from "../Condition.js";
@@ -31,6 +32,15 @@ const make = (data) => new Rule({
31
32
  ...data.meta === void 0 ? {} : { meta: data.meta },
32
33
  ...data.domain === void 0 ? {} : { domain: data.domain }
33
34
  });
35
+ /**
36
+ * Makes a rule from its data, as `Policy.rule` does. A condition with a
37
+ * predicate is a defect: the types of the predicate tie it to the kinds of
38
+ * the rule of a builder, and the binding can not check it.
39
+ */
40
+ const makePlain = (data) => {
41
+ if (data.when !== void 0 && isNode(data.when) && data.when.opaque) throw new Error("Policy.rule takes no condition with a predicate. Make the rule with Policy.builder.");
42
+ return make(data);
43
+ };
34
44
  /** Tests if a value is a rule. */
35
45
  const isRule = (u) => u instanceof Rule;
36
46
  const toArray = (value) => Array.isArray(value) ? value : [value];
@@ -88,6 +98,6 @@ const matchesTarget = (rule, target) => {
88
98
  const open = rule._tag === "Deny" && target.descriptor !== void 0 && (lookup(target.descriptor.catalog, field)?.open ?? false);
89
99
  return rule.fields.some((pattern) => matchesField(pattern, field) || open && matchesInside(pattern, field));
90
100
  };
91
- export { Rule, builder, isRule, make, matchesField, matchesInside, matchesTarget };
101
+ export { Rule, builder, isRule, make, makePlain, matchesField, matchesInside, matchesTarget };
92
102
 
93
103
  //# sourceMappingURL=rule.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rule.js","names":["ResourceData.isResource","ResourceData.descriptor","Condition.builder","Patterns.matches","Patterns.parseBound","Patterns.matchesBelow","Patterns.fitsCatalog","Catalog.lookup"],"sources":["../../src/internal/rule.ts"],"sourcesContent":["/**\n * The values of rules, with no types. The types are in `Policy.ts`.\n *\n * Rules do not change, and two rules with the same data are equal. A rule\n * works in a generator: it gives itself, so `yield* _.allow(...)` gives the\n * rule to `Policy.define`.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Condition from \"../Condition.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as Patterns from \"./patterns.ts\"\n\n/** The data of a rule. */\nexport interface Shape {\n\treadonly _tag: \"Allow\" | \"Deny\"\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: Condition.Ast\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/** The class of every rule value. */\nexport class Rule extends Data.Class<Shape> {\n\t*[Symbol.iterator](): Generator<this, this, unknown> {\n\t\tyield this\n\t\treturn this\n\t}\n}\n\n/** Makes a rule from its data. It does not check the data against a domain. */\nexport const make = (data: Shape): Rule =>\n\t// A missing option is left out, so two rules with the same data are equal.\n\tnew Rule({\n\t\t_tag: data._tag,\n\t\tactions: [...data.actions],\n\t\tresources: [...data.resources],\n\t\t...(data.when === undefined ? {} : { when: data.when }),\n\t\t...(data.fields === undefined ? {} : { fields: [...data.fields] }),\n\t\t...(data.reason === undefined ? {} : { reason: data.reason }),\n\t\t...(data.id === undefined ? {} : { id: data.id }),\n\t\t...(data.meta === undefined ? {} : { meta: data.meta }),\n\t\t...(data.domain === undefined ? {} : { domain: data.domain }),\n\t})\n\n/** Tests if a value is a rule. */\nexport const isRule = (u: unknown): u is Rule => u instanceof Rule\n\nconst toArray = <A>(value: A | ReadonlyArray<A>): ReadonlyArray<A> =>\n\tArray.isArray(value) ? value : [value as A]\n\nconst kindName = (target: unknown): string =>\n\tResourceData.isResource(target)\n\t\t? ResourceData.descriptor(target).kind\n\t\t: (target as string)\n\n/** The options of a rule from a builder, with no types. */\ninterface Options {\n\treadonly when?: (builder: never) => Condition.Ast\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n}\n\nconst fromOptions =\n\t(tag: \"Allow\" | \"Deny\", domain: Domain.Any) =>\n\t(\n\t\taction: string | ReadonlyArray<string>,\n\t\tresource: unknown,\n\t\toptions?: Options,\n\t): Rule =>\n\t\tmake({\n\t\t\t_tag: tag,\n\t\t\tactions: toArray(action),\n\t\t\tresources: Arr.map(toArray(resource), kindName),\n\t\t\t...(options?.when === undefined\n\t\t\t\t? {}\n\t\t\t\t: { when: options.when(Condition.builder() as never) }),\n\t\t\t...(options?.fields === undefined ? {} : { fields: options.fields }),\n\t\t\t...(options?.reason === undefined ? {} : { reason: options.reason }),\n\t\t\t...(options?.id === undefined ? {} : { id: options.id }),\n\t\t\t...(options?.meta === undefined ? {} : { meta: options.meta }),\n\t\t\tdomain,\n\t\t})\n\n/** Makes the rule builder of a domain. */\nexport const builder = (domain: Domain.Any) => ({\n\tallow: fromOptions(\"Allow\", domain),\n\tdeny: fromOptions(\"Deny\", domain),\n})\n\n/**\n * Tests if a field name matches a field name or a field pattern of a rule. A\n * pattern matches the fields below a field that it matches too, so `settings`\n * matches `settings.model`.\n */\nexport const matchesField = (pattern: string, field: string): boolean =>\n\tPatterns.matches(Patterns.parseBound(pattern), field.split(\".\"))\n\n/**\n * Tests if a field pattern may match a field inside `field`, such as\n * `settings.secret` inside `settings`.\n */\nexport const matchesInside = (pattern: string, field: string): boolean =>\n\tPatterns.matchesBelow(Patterns.parseBound(pattern), field.split(\".\"))\n\n/**\n * Tests the parts of a rule that do not need the resource value: the action,\n * the kind and the field.\n *\n * A rule with an empty list of fields covers no request. A rule with fields\n * covers a request with no field only when it is an allow rule. So \"may\n * update some fields\" is a yes. A deny rule for some fields does not stop a\n * request for the whole value. With the `descriptor` of the kind, such an\n * allow rule covers the request only when one of its patterns matches a field\n * of the kind, and a deny rule for a part of a field that holds parts covers\n * a request for the whole field. A grant of fields that the kind does not have is not a grant of\n * some fields.\n */\nexport const matchesTarget = (\n\trule: Shape,\n\ttarget: {\n\t\treadonly action: string\n\t\treadonly kind: string\n\t\treadonly field?: string | undefined\n\t\treadonly descriptor?: Resource.Descriptor | undefined\n\t},\n): boolean => {\n\tif (!rule.actions.includes(\"*\") && !rule.actions.includes(target.action)) {\n\t\treturn false\n\t}\n\tif (!rule.resources.includes(\"*\") && !rule.resources.includes(target.kind)) {\n\t\treturn false\n\t}\n\tif (rule.fields === undefined) {\n\t\treturn true\n\t}\n\t// A rule for no fields is a rule for nothing. A grant with its last field\n\t// taken away must not cover the whole value.\n\tif (rule.fields.length === 0) {\n\t\treturn false\n\t}\n\tif (target.field === undefined) {\n\t\tconst descriptor = target.descriptor\n\t\treturn (\n\t\t\trule._tag === \"Allow\" &&\n\t\t\t(descriptor === undefined ||\n\t\t\t\tArr.some(rule.fields, (pattern) =>\n\t\t\t\t\tPatterns.fitsCatalog(\n\t\t\t\t\t\tPatterns.parseBound(pattern),\n\t\t\t\t\t\tdescriptor.catalog,\n\t\t\t\t\t),\n\t\t\t\t))\n\t\t)\n\t}\n\tconst field = target.field\n\t// A field that holds parts, as a record does, shows its parts too. So a\n\t// deny rule for a part inside it, as `data.secret` inside `data`, also\n\t// covers a request for the whole field.\n\tconst open =\n\t\trule._tag === \"Deny\" &&\n\t\ttarget.descriptor !== undefined &&\n\t\t(Catalog.lookup(target.descriptor.catalog, field)?.open ?? false)\n\treturn rule.fields.some(\n\t\t(pattern) =>\n\t\t\tmatchesField(pattern, field) || (open && matchesInside(pattern, field)),\n\t)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"rule.js","names":["Nodes.isNode","ResourceData.isResource","ResourceData.descriptor","Condition.builder","Patterns.matches","Patterns.parseBound","Patterns.matchesBelow","Patterns.fitsCatalog","Catalog.lookup"],"sources":["../../src/internal/rule.ts"],"sourcesContent":["/**\n * The values of rules, with no types. The types are in `Policy.ts`.\n *\n * Rules do not change, and two rules with the same data are equal. A rule\n * works in a generator: it gives itself, so `yield* _.allow(...)` gives the\n * rule to `Policy.define`.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Condition from \"../Condition.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as Patterns from \"./patterns.ts\"\n\n/** The data of a rule. */\nexport interface Shape {\n\treadonly _tag: \"Allow\" | \"Deny\"\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: Condition.Ast\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/** The class of every rule value. */\nexport class Rule extends Data.Class<Shape> {\n\t*[Symbol.iterator](): Generator<this, this, unknown> {\n\t\tyield this\n\t\treturn this\n\t}\n}\n\n/** Makes a rule from its data. It does not check the data against a domain. */\nexport const make = (data: Shape): Rule =>\n\t// A missing option is left out, so two rules with the same data are equal.\n\tnew Rule({\n\t\t_tag: data._tag,\n\t\tactions: [...data.actions],\n\t\tresources: [...data.resources],\n\t\t...(data.when === undefined ? {} : { when: data.when }),\n\t\t...(data.fields === undefined ? {} : { fields: [...data.fields] }),\n\t\t...(data.reason === undefined ? {} : { reason: data.reason }),\n\t\t...(data.id === undefined ? {} : { id: data.id }),\n\t\t...(data.meta === undefined ? {} : { meta: data.meta }),\n\t\t...(data.domain === undefined ? {} : { domain: data.domain }),\n\t})\n\n/**\n * Makes a rule from its data, as `Policy.rule` does. A condition with a\n * predicate is a defect: the types of the predicate tie it to the kinds of\n * the rule of a builder, and the binding can not check it.\n */\nexport const makePlain = (data: Shape): Rule => {\n\tif (data.when !== undefined && Nodes.isNode(data.when) && data.when.opaque) {\n\t\tthrow new Error(\n\t\t\t\"Policy.rule takes no condition with a predicate. Make the rule with Policy.builder.\",\n\t\t)\n\t}\n\treturn make(data)\n}\n\n/** Tests if a value is a rule. */\nexport const isRule = (u: unknown): u is Rule => u instanceof Rule\n\nconst toArray = <A>(value: A | ReadonlyArray<A>): ReadonlyArray<A> =>\n\tArray.isArray(value) ? value : [value as A]\n\nconst kindName = (target: unknown): string =>\n\tResourceData.isResource(target)\n\t\t? ResourceData.descriptor(target).kind\n\t\t: (target as string)\n\n/** The options of a rule from a builder, with no types. */\ninterface Options {\n\treadonly when?: (builder: never) => Condition.Ast\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n}\n\nconst fromOptions =\n\t(tag: \"Allow\" | \"Deny\", domain: Domain.Any) =>\n\t(\n\t\taction: string | ReadonlyArray<string>,\n\t\tresource: unknown,\n\t\toptions?: Options,\n\t): Rule =>\n\t\tmake({\n\t\t\t_tag: tag,\n\t\t\tactions: toArray(action),\n\t\t\tresources: Arr.map(toArray(resource), kindName),\n\t\t\t...(options?.when === undefined\n\t\t\t\t? {}\n\t\t\t\t: { when: options.when(Condition.builder() as never) }),\n\t\t\t...(options?.fields === undefined ? {} : { fields: options.fields }),\n\t\t\t...(options?.reason === undefined ? {} : { reason: options.reason }),\n\t\t\t...(options?.id === undefined ? {} : { id: options.id }),\n\t\t\t...(options?.meta === undefined ? {} : { meta: options.meta }),\n\t\t\tdomain,\n\t\t})\n\n/** Makes the rule builder of a domain. */\nexport const builder = (domain: Domain.Any) => ({\n\tallow: fromOptions(\"Allow\", domain),\n\tdeny: fromOptions(\"Deny\", domain),\n})\n\n/**\n * Tests if a field name matches a field name or a field pattern of a rule. A\n * pattern matches the fields below a field that it matches too, so `settings`\n * matches `settings.model`.\n */\nexport const matchesField = (pattern: string, field: string): boolean =>\n\tPatterns.matches(Patterns.parseBound(pattern), field.split(\".\"))\n\n/**\n * Tests if a field pattern may match a field inside `field`, such as\n * `settings.secret` inside `settings`.\n */\nexport const matchesInside = (pattern: string, field: string): boolean =>\n\tPatterns.matchesBelow(Patterns.parseBound(pattern), field.split(\".\"))\n\n/**\n * Tests the parts of a rule that do not need the resource value: the action,\n * the kind and the field.\n *\n * A rule with an empty list of fields covers no request. A rule with fields\n * covers a request with no field only when it is an allow rule. So \"may\n * update some fields\" is a yes. A deny rule for some fields does not stop a\n * request for the whole value. With the `descriptor` of the kind, such an\n * allow rule covers the request only when one of its patterns matches a field\n * of the kind, and a deny rule for a part of a field that holds parts covers\n * a request for the whole field. A grant of fields that the kind does not have is not a grant of\n * some fields.\n */\nexport const matchesTarget = (\n\trule: Shape,\n\ttarget: {\n\t\treadonly action: string\n\t\treadonly kind: string\n\t\treadonly field?: string | undefined\n\t\treadonly descriptor?: Resource.Descriptor | undefined\n\t},\n): boolean => {\n\tif (!rule.actions.includes(\"*\") && !rule.actions.includes(target.action)) {\n\t\treturn false\n\t}\n\tif (!rule.resources.includes(\"*\") && !rule.resources.includes(target.kind)) {\n\t\treturn false\n\t}\n\tif (rule.fields === undefined) {\n\t\treturn true\n\t}\n\t// A rule for no fields is a rule for nothing. A grant with its last field\n\t// taken away must not cover the whole value.\n\tif (rule.fields.length === 0) {\n\t\treturn false\n\t}\n\tif (target.field === undefined) {\n\t\tconst descriptor = target.descriptor\n\t\treturn (\n\t\t\trule._tag === \"Allow\" &&\n\t\t\t(descriptor === undefined ||\n\t\t\t\tArr.some(rule.fields, (pattern) =>\n\t\t\t\t\tPatterns.fitsCatalog(\n\t\t\t\t\t\tPatterns.parseBound(pattern),\n\t\t\t\t\t\tdescriptor.catalog,\n\t\t\t\t\t),\n\t\t\t\t))\n\t\t)\n\t}\n\tconst field = target.field\n\t// A field that holds parts, as a record does, shows its parts too. So a\n\t// deny rule for a part inside it, as `data.secret` inside `data`, also\n\t// covers a request for the whole field.\n\tconst open =\n\t\trule._tag === \"Deny\" &&\n\t\ttarget.descriptor !== undefined &&\n\t\t(Catalog.lookup(target.descriptor.catalog, field)?.open ?? false)\n\treturn rule.fields.some(\n\t\t(pattern) =>\n\t\t\tmatchesField(pattern, field) || (open && matchesInside(pattern, field)),\n\t)\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nmnmcc/ability",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Typed, composable authorization policies for Effect programs.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/Policy.ts CHANGED
@@ -533,14 +533,19 @@ export const builder = <D extends Domain.Any>(domain: D): Builder<D> =>
533
533
  * against a domain: a policy does that when the rule goes into it. Use
534
534
  * {@link builder} for rules with types, or {@link schema} for rules from JSON.
535
535
  *
536
+ * The condition may not have a predicate. A policy checks the fields of a
537
+ * condition against the resources of its rule, but it can not check what
538
+ * the code of a predicate reads. A rule from the builder keeps that in its
539
+ * types; this one could give a predicate of one kind to another kind.
540
+ *
536
541
  * @stability stable
537
542
  * @category constructors
538
543
  * @since 2.0.0
539
544
  */
540
- export const rule = <I = unknown, X = never>(
541
- data: RuleShape<I, X>,
545
+ export const rule: <I = unknown>(
546
+ data: RuleShape<I, never>,
542
547
  // The types come from the data only, not from where the rule goes.
543
- ): Rule<any, NoInfer<I>, NoInfer<X>> => RuleData.make(data as never) as never
548
+ ) => Rule<any, NoInfer<I>> = RuleData.makePlain as never
544
549
 
545
550
  /**
546
551
  * The options of a list of rules.
@@ -42,11 +42,14 @@ interface Memo {
42
42
  readonly suggestions: MutableHashMap.MutableHashMap<string, string>
43
43
  /** If a pattern matches a field of a kind, by the kind and the pattern. */
44
44
  readonly fits: MutableHashMap.MutableHashMap<string, boolean>
45
+ /** The count of the issues of conditions so far. It belongs to one binding. */
46
+ readonly issues: { count: number; noted: boolean }
45
47
  }
46
48
 
47
49
  const makeMemo = (): Memo => ({
48
50
  suggestions: MutableHashMap.empty(),
49
51
  fits: MutableHashMap.empty(),
52
+ issues: { count: 0, noted: false },
50
53
  })
51
54
 
52
55
  /**
@@ -385,6 +388,46 @@ const notWhole = (at: ReadonlyArray<PropertyKey>): Issue => ({
385
388
  issue: "The condition is not a whole condition tree",
386
389
  })
387
390
 
391
+ /**
392
+ * The most issues that the walk of the conditions of one binding gives. A
393
+ * policy may have very many wrong tests, and each issue keeps its path.
394
+ */
395
+ const maxIssues = 100
396
+
397
+ /** A path that the walk makes only when it needs it, for an issue. */
398
+ type LazyPath = () => ReadonlyArray<PropertyKey>
399
+
400
+ /**
401
+ * Puts the path of a node before the paths of its issues. A walk keeps no
402
+ * path for a node with no issue, so a deep and wide condition costs little.
403
+ */
404
+ const under = (
405
+ memo: Memo,
406
+ at: LazyPath,
407
+ issues: ReadonlyArray<Issue>,
408
+ ): ReadonlyArray<Issue> => {
409
+ if (issues.length === 0) {
410
+ return issues
411
+ }
412
+ const room = maxIssues - memo.issues.count
413
+ memo.issues.count += issues.length
414
+ const kept = Arr.map(Arr.take(issues, Math.max(room, 0)), (issue) => ({
415
+ path: [...at(), ...issue.path],
416
+ issue: issue.issue,
417
+ }))
418
+ const first = memo.issues.count > maxIssues && !memo.issues.noted
419
+ memo.issues.noted ||= first
420
+ return first
421
+ ? [
422
+ ...kept,
423
+ {
424
+ path: [],
425
+ issue: `The policy has more problems. The first ${String(maxIssues)} are here.`,
426
+ },
427
+ ]
428
+ : kept
429
+ }
430
+
388
431
  /**
389
432
  * The issues of a condition. The constructors of the nodes checked the shape
390
433
  * of each node, so this checks the names and the types against the domain. A
@@ -395,52 +438,61 @@ const notWhole = (at: ReadonlyArray<PropertyKey>): Issue => ({
395
438
  const conditionIssues = (
396
439
  context: Context,
397
440
  node: Nodes.Node,
398
- at: ReadonlyArray<PropertyKey>,
441
+ at: LazyPath,
399
442
  seen: Set<Nodes.Node>,
400
443
  ): ReadonlyArray<Issue> => {
401
444
  if (seen.has(node)) {
402
445
  return []
403
446
  }
404
447
  seen.add(node)
448
+ const here = (issues: ReadonlyArray<Issue>) => under(context.memo, at, issues)
405
449
  switch (node._tag) {
406
450
  case "Compare": {
407
- const exists = fieldIssue(context, node.field, [...at, "field"])
408
- return exists.length > 0
409
- ? exists
410
- : operandIssues(
411
- context,
412
- node.field,
413
- node.op !== "eq" && node.op !== "ne",
414
- node.value,
415
- [...at, "value"],
416
- )
451
+ const exists = fieldIssue(context, node.field, ["field"])
452
+ return here(
453
+ exists.length > 0
454
+ ? exists
455
+ : operandIssues(
456
+ context,
457
+ node.field,
458
+ node.op !== "eq" && node.op !== "ne",
459
+ node.value,
460
+ ["value"],
461
+ ),
462
+ )
417
463
  }
418
464
  case "In": {
419
- const exists = fieldIssue(context, node.field, [...at, "field"])
420
- return exists.length > 0
421
- ? exists
422
- : Arr.flatMap(node.values, (operand, index) =>
423
- operandIssues(context, node.field, false, operand, [
424
- ...at,
425
- "values",
426
- index,
427
- ]),
428
- )
465
+ const exists = fieldIssue(context, node.field, ["field"])
466
+ return here(
467
+ exists.length > 0
468
+ ? exists
469
+ : Arr.flatMap(node.values, (operand, index) =>
470
+ operandIssues(context, node.field, false, operand, [
471
+ "values",
472
+ index,
473
+ ]),
474
+ ),
475
+ )
429
476
  }
430
477
  case "Exists":
431
- return fieldIssue(context, node.field, [...at, "field"])
478
+ return here(fieldIssue(context, node.field, ["field"]))
432
479
  case "Under":
433
- return underIssues(context, node.prefix, at)
480
+ return here(underIssues(context, node.prefix, []))
434
481
  case "And":
435
482
  case "Or":
436
483
  return Arr.flatMap(node.conditions, (child, index) =>
437
- conditionIssues(context, child, [...at, "conditions", index], seen),
484
+ conditionIssues(
485
+ context,
486
+ child,
487
+ () => [...at(), "conditions", index],
488
+ seen,
489
+ ),
438
490
  )
439
491
  case "Not":
440
492
  return conditionIssues(
441
493
  context,
442
494
  node.condition,
443
- [...at, "condition"],
495
+ () => [...at(), "condition"],
444
496
  seen,
445
497
  )
446
498
  default:
@@ -529,7 +581,7 @@ export const ruleIssues = (
529
581
  ...(rule.when === undefined
530
582
  ? []
531
583
  : Nodes.isNode(rule.when)
532
- ? conditionIssues(context, rule.when, [...at, "when"], new Set())
584
+ ? conditionIssues(context, rule.when, () => [...at, "when"], new Set())
533
585
  : [notWhole([...at, "when"])]),
534
586
  ]
535
587
  }
@@ -16,6 +16,15 @@ import * as PolicyNodes from "./policy-nodes.ts"
16
16
 
17
17
  type Node = PolicyNodes.Node
18
18
 
19
+ /**
20
+ * The codec of the `meta` Schema of a domain to and from JSON. The JSON form
21
+ * of a policy must stay the same through `JSON.stringify` and `JSON.parse`, so
22
+ * a `Date` in `meta`, for one, becomes a string in it, and a string again
23
+ * when it is decoded.
24
+ */
25
+ const metaCodec = (meta: Schema.Top): Schema.Codec<unknown, unknown> =>
26
+ Schema.toCodecJson(meta) as unknown as Schema.Codec<unknown, unknown>
27
+
19
28
  /** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */
20
29
  export type MetaJson = (rule: Rule.Rule) => unknown
21
30
 
@@ -29,9 +38,7 @@ export const syncMeta =
29
38
  (rule) =>
30
39
  rule.meta === undefined || meta === undefined
31
40
  ? undefined
32
- : Schema.encodeUnknownSync(meta as Schema.Codec<unknown, unknown>)(
33
- rule.meta,
34
- )
41
+ : Schema.encodeUnknownSync(metaCodec(meta))(rule.meta)
35
42
 
36
43
  /** The JSON form of the condition of each rule, by the rule. */
37
44
  type WhenJson = (rule: Rule.Rule) => unknown
@@ -118,9 +125,7 @@ export const encode = (
118
125
  ? []
119
126
  : yield* Effect.forEach(rules, (rule) =>
120
127
  Effect.map(
121
- Schema.encodeUnknownEffect(
122
- meta as Schema.Codec<unknown, unknown>,
123
- )(rule.meta),
128
+ Schema.encodeUnknownEffect(metaCodec(meta))(rule.meta),
124
129
  (json) => [rule, json] as const,
125
130
  ),
126
131
  )
@@ -264,16 +269,14 @@ const decodeMeta = (
264
269
  )
265
270
  }
266
271
  spendValue(value, [...path, "meta"], 1, budget)
267
- return { value }
272
+ return { value, schema: domain.meta }
268
273
  }),
269
274
  ),
270
275
  (found) =>
271
276
  found === undefined
272
277
  ? Effect.void
273
278
  : Effect.mapError(
274
- Schema.decodeUnknownEffect(
275
- domain.meta as Schema.Codec<unknown, unknown>,
276
- )(found.value),
279
+ Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value),
277
280
  (error) =>
278
281
  new Json.Problem({
279
282
  path: [...path, "meta"],
@@ -11,6 +11,7 @@ import * as Condition from "../Condition.ts"
11
11
  import type * as Domain from "../Domain.ts"
12
12
  import type * as Resource from "../Resource.ts"
13
13
  import * as Catalog from "./catalog.ts"
14
+ import * as Nodes from "./nodes.ts"
14
15
  import * as ResourceData from "./resource.ts"
15
16
  import * as Patterns from "./patterns.ts"
16
17
 
@@ -55,6 +56,20 @@ export const make = (data: Shape): Rule =>
55
56
  ...(data.domain === undefined ? {} : { domain: data.domain }),
56
57
  })
57
58
 
59
+ /**
60
+ * Makes a rule from its data, as `Policy.rule` does. A condition with a
61
+ * predicate is a defect: the types of the predicate tie it to the kinds of
62
+ * the rule of a builder, and the binding can not check it.
63
+ */
64
+ export const makePlain = (data: Shape): Rule => {
65
+ if (data.when !== undefined && Nodes.isNode(data.when) && data.when.opaque) {
66
+ throw new Error(
67
+ "Policy.rule takes no condition with a predicate. Make the rule with Policy.builder.",
68
+ )
69
+ }
70
+ return make(data)
71
+ }
72
+
58
73
  /** Tests if a value is a rule. */
59
74
  export const isRule = (u: unknown): u is Rule => u instanceof Rule
60
75