@nmnmcc/ability 2.1.2 → 2.1.4

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.
Files changed (43) hide show
  1. package/dist/Authorizer.d.ts +11 -11
  2. package/dist/Authorizer.d.ts.map +1 -1
  3. package/dist/Authorizer.js.map +1 -1
  4. package/dist/Condition.d.ts +1 -1
  5. package/dist/HttpApi.js +1 -1
  6. package/dist/Policy.d.ts +16 -6
  7. package/dist/Policy.d.ts.map +1 -1
  8. package/dist/Policy.js.map +1 -1
  9. package/dist/internal/algebra.js +8 -5
  10. package/dist/internal/algebra.js.map +1 -1
  11. package/dist/internal/authorizer.d.ts.map +1 -1
  12. package/dist/internal/authorizer.js +35 -9
  13. package/dist/internal/authorizer.js.map +1 -1
  14. package/dist/internal/bind.js +37 -27
  15. package/dist/internal/bind.js.map +1 -1
  16. package/dist/internal/catalog.d.ts +0 -31
  17. package/dist/internal/catalog.d.ts.map +1 -1
  18. package/dist/internal/catalog.js +39 -17
  19. package/dist/internal/catalog.js.map +1 -1
  20. package/dist/internal/decide.js +1 -1
  21. package/dist/internal/decide.js.map +1 -1
  22. package/dist/internal/domain.js +8 -5
  23. package/dist/internal/domain.js.map +1 -1
  24. package/dist/internal/patterns.js +1 -1
  25. package/dist/internal/policy-json.js +2 -2
  26. package/dist/internal/policy-json.js.map +1 -1
  27. package/dist/internal/policy-nodes.js +21 -12
  28. package/dist/internal/policy-nodes.js.map +1 -1
  29. package/dist/internal/policy.js +1 -1
  30. package/dist/internal/search.js +39 -27
  31. package/dist/internal/search.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/Authorizer.ts +10 -10
  34. package/src/Policy.ts +16 -5
  35. package/src/internal/algebra.ts +12 -8
  36. package/src/internal/authorizer.ts +77 -34
  37. package/src/internal/bind.ts +43 -36
  38. package/src/internal/catalog.ts +69 -40
  39. package/src/internal/decide.ts +5 -1
  40. package/src/internal/domain.ts +6 -11
  41. package/src/internal/policy-json.ts +24 -8
  42. package/src/internal/policy-nodes.ts +19 -13
  43. package/src/internal/search.ts +31 -21
@@ -1 +1 @@
1
- {"version":3,"file":"decide.js","names":["RuleData.matchesTarget","Condition.evaluate","Place.pathOf","Decision.Allow","Decision.Deny","Place.at","at","Decision.NotApplicable","Catalog.lookup","ResourceData.kindOf","DomainData.descriptorOf","RuleData.fitsOf","RuleData.matchesInside","PolicyNodes.rulesOf","Catalog.fieldsOf"],"sources":["../../src/internal/decide.ts"],"sourcesContent":["/**\n * The decision walk of a policy: `decide`, `explain` and `permittedFields`.\n * The types are in `Policy.ts`.\n */\n// A policy keeps the error type and the service type of its predicates in the\n// public types. The code below works on the nodes with no types, where they\n// are `unknown`, so the language service check for `unknown` in an Effect is\n// off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Chunk from \"effect/Chunk\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Ref from \"effect/Ref\"\nimport * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport { nodeOf } from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as RuleData from \"./rule.ts\"\n\ntype Any = Policy.Any\ntype AnyRequest = Policy.AnyRequest\ntype AnyRule = Policy.AnyRule\ntype Explanation = Policy.Explanation\ntype Step = Policy.Step\n\ninterface DecideContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly input: Condition.Input\n\t/** The resource of the kind, for the fields that a rule may grant. */\n\treadonly descriptor: Resource.Descriptor\n\t/** The test of `RuleData.fitsOf` for the catalog, for this decision. */\n\treadonly fits: (pattern: string) => boolean\n\t/** The most parts of a field path in the domain. */\n\treadonly parts: number\n\t/** The rules that were tested, for {@link explain}. */\n\treadonly trace: Ref.Ref<Chunk.Chunk<Step>> | undefined\n}\n\n/**\n * The place of a node or a rule in a tree: the index of each child, then\n * the index of the rule. It shares the path of its parent, and a decision\n * makes the full path only for a step of `explain`: see `Place`.\n */\ntype At = Place.Place | undefined\n\nconst ruleApplies = (\n\tvalue: RuleData.Rule,\n\tcontext: DecideContext,\n\tat: Place.Place,\n): Effect.Effect<boolean, unknown, unknown> => {\n\tif (!RuleData.matchesTarget(value, context)) {\n\t\treturn Effect.succeed(false)\n\t}\n\tconst test =\n\t\tvalue.when === undefined\n\t\t\t? Effect.succeed(true)\n\t\t\t: // A rule of a node keeps no type of what it reads. The decision gives\n\t\t\t\t// it the value and the principal of the request.\n\t\t\t\tCondition.evaluate(value.when as Condition.Any, context.input)\n\tconst trace = context.trace\n\treturn trace === undefined\n\t\t? test\n\t\t: Effect.tap(test, (applies) =>\n\t\t\t\t// A chunk adds a step with no copy of the steps before.\n\t\t\t\tRef.update(\n\t\t\t\t\ttrace,\n\t\t\t\t\tChunk.append({\n\t\t\t\t\t\tpath: Place.pathOf(at) as ReadonlyArray<number>,\n\t\t\t\t\t\trule: typed(value),\n\t\t\t\t\t\tapplies,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t)\n}\n\n/** A rule of a node, with the type of a rule of no domain. */\nconst typed = (value: RuleData.Rule): AnyRule => value as never\n\nconst fromRule = (value: RuleData.Rule): Decision.Decision =>\n\tvalue._tag === \"Allow\"\n\t\t? Decision.Allow({ rule: typed(value) })\n\t\t: value.reason === undefined\n\t\t\t? Decision.Deny({ rule: typed(value) })\n\t\t\t: Decision.Deny({ rule: typed(value), reason: value.reason })\n\n/** Gives the first rule that applies, in order. */\nconst firstApplying = (\n\tcandidates: ReadonlyArray<readonly [RuleData.Rule, number]>,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Option.Option<RuleData.Rule>, unknown, unknown> =>\n\tEffect.map(\n\t\tEffect.findFirst(candidates, ([value, index]) =>\n\t\t\truleApplies(value, context, Place.at(at, index)),\n\t\t),\n\t\tOption.map(([value]) => value),\n\t)\n\n/** Gives the first result that is found, and tests `next` only when none is. */\nconst orElse = <A, E, R>(\n\tfirst: Effect.Effect<Option.Option<A>, E, R>,\n\tnext: () => Effect.Effect<Option.Option<A>, E, R>,\n): Effect.Effect<Option.Option<A>, E, R> =>\n\tEffect.filterOrElse(first, Option.isSome, next)\n\nconst decideRules = (\n\tnode: PolicyNodes.Rules,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst indexed = Arr.map(node.rules, (value, index) => [value, index] as const)\n\tconst denies = Arr.filter(indexed, ([value]) => value._tag === \"Deny\")\n\tconst allows = Arr.filter(indexed, ([value]) => value._tag === \"Allow\")\n\tconst first = (candidates: ReadonlyArray<readonly [RuleData.Rule, number]>) =>\n\t\tfirstApplying(candidates, context, at)\n\tconst found = (() => {\n\t\tswitch (node.algorithm) {\n\t\t\tcase \"denyOverrides\":\n\t\t\t\treturn orElse(first(denies), () => first(allows))\n\t\t\tcase \"permitOverrides\":\n\t\t\t\treturn orElse(first(allows), () => first(denies))\n\t\t\tcase \"firstApplicable\":\n\t\t\t\treturn first(indexed)\n\t\t\tcase \"lastApplicable\":\n\t\t\t\treturn first(Arr.reverse(indexed))\n\t\t}\n\t})()\n\treturn Effect.map(\n\t\tfound,\n\t\tOption.match({ onNone: () => Decision.NotApplicable, onSome: fromRule }),\n\t)\n}\n\n/** The next step of a walk over children: stop with a decision, or go on. */\ntype WalkStep<S> =\n\t| { readonly _tag: \"Stop\"; readonly decision: Decision.Decision }\n\t| { readonly _tag: \"Next\"; readonly state: S }\n\n/**\n * Decides the children in order. After each child, `step` stops with a\n * decision, or gives the state for the next child. After the last child,\n * `end` gives the decision. A child after a stop is not decided, so its\n * predicates do not run.\n */\nconst walkChildren = <S>(\n\tchildren: ReadonlyArray<PolicyNodes.Node>,\n\tcontext: DecideContext,\n\tat: At,\n\tstart: S,\n\tstep: (state: S, decision: Decision.Decision) => WalkStep<S>,\n\tend: (state: S) => Decision.Decision,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst go = (\n\t\tindex: number,\n\t\tstate: S,\n\t): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t\tindex === children.length\n\t\t\t? Effect.succeed(end(state))\n\t\t\t: Effect.flatMap(\n\t\t\t\t\t// Each call for a child is a step of the Effect, so a deep tree\n\t\t\t\t\t// does not fill the stack.\n\t\t\t\t\tEffect.suspend(() =>\n\t\t\t\t\t\tdecideNode(children[index]!, context, Place.at(at, index)),\n\t\t\t\t\t),\n\t\t\t\t\t(decision) => {\n\t\t\t\t\t\tconst next = step(state, decision)\n\t\t\t\t\t\treturn next._tag === \"Stop\"\n\t\t\t\t\t\t\t? Effect.succeed(next.decision)\n\t\t\t\t\t\t\t: go(index + 1, next.state)\n\t\t\t\t\t},\n\t\t\t\t)\n\treturn go(0, start)\n}\n\nconst stop = (decision: Decision.Decision): WalkStep<never> => ({\n\t_tag: \"Stop\",\n\tdecision,\n})\n\nconst next = <S>(state: S): WalkStep<S> => ({ _tag: \"Next\", state })\n\n/**\n * Decides a node. A shared child is decided again for each place, so its\n * predicates run each time, as the policy says. The limits of the nodes keep\n * this work small.\n */\nexport const decideNode = (\n\tnode: PolicyNodes.Node,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn decideRules(node, context, at)\n\t\tcase \"All\":\n\t\t\t// One deny is enough to deny. Every child must allow.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\t{\n\t\t\t\t\tfirstAllow: Option.none<Decision.Decision>(),\n\t\t\t\t\tallAllow: node.policies.length > 0,\n\t\t\t\t},\n\t\t\t\t(state, decision) =>\n\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next({\n\t\t\t\t\t\t\t\tfirstAllow: Option.orElse(state.firstAllow, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tallAllow: state.allAllow && decision._tag === \"Allow\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t(state) =>\n\t\t\t\t\tstate.allAllow\n\t\t\t\t\t\t? Option.getOrElse(state.firstAllow, () => Decision.NotApplicable)\n\t\t\t\t\t\t: Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Any\":\n\t\t\t// One allow is enough. Without one, the first deny denies.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\tOption.none<Decision.Decision>(),\n\t\t\t\t(firstDeny, decision) =>\n\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next(\n\t\t\t\t\t\t\t\tOption.orElse(firstDeny, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t(firstDeny) =>\n\t\t\t\t\tOption.getOrElse(firstDeny, () => Decision.NotApplicable),\n\t\t\t)\n\t\tcase \"FirstOf\":\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\tundefined,\n\t\t\t\t(_, decision) =>\n\t\t\t\t\tdecision._tag === \"NotApplicable\" ? next(undefined) : stop(decision),\n\t\t\t\t() => Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Ceiling\":\n\t\t\t// A deny of the cap wins. Else the policy decides.\n\t\t\treturn Effect.filterOrElse(\n\t\t\t\tEffect.suspend(() => decideNode(node.cap, context, Place.at(at, 0))),\n\t\t\t\t(capped) => capped._tag === \"Deny\",\n\t\t\t\t() => decideNode(node.policy, context, Place.at(at, 1)),\n\t\t\t)\n\t}\n}\n\n/**\n * Tests if a request fits its kind: the action is an action of the kind, and\n * the field, if there is one, is a field of the kind.\n */\nexport const fitsKind = (\n\tdescriptor: Resource.Descriptor,\n\trequest: { readonly action: string; readonly field?: string | undefined },\n\tparts: number,\n): boolean =>\n\tdescriptor.actions.includes(request.action) &&\n\t(request.field === undefined ||\n\t\t(Catalog.lookup(descriptor.catalog, request.field, parts)?.listed ?? false))\n\nconst makeContext = (\n\tdomain: Domain.Any,\n\trequest: AnyRequest,\n\ttrace: Ref.Ref<Chunk.Chunk<Step>> | undefined,\n): DecideContext | undefined => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor =\n\t\tkind === undefined ? undefined : DomainData.descriptorOf(domain, kind)\n\tif (\n\t\tkind === undefined ||\n\t\tdescriptor === undefined ||\n\t\t!fitsKind(descriptor, request, domain.limits.fieldPathParts)\n\t) {\n\t\treturn undefined\n\t}\n\treturn {\n\t\taction: request.action,\n\t\tkind,\n\t\tfield: request.field,\n\t\tinput: {\n\t\t\tvalue: request.resource,\n\t\t\tprincipal: request.principal,\n\t\t\tpath: descriptor.path,\n\t\t},\n\t\tdescriptor,\n\t\tfits: RuleData.fitsOf(descriptor.catalog, domain.limits.fieldPathParts),\n\t\tparts: domain.limits.fieldPathParts,\n\t\ttrace,\n\t}\n}\n\nexport const decideAny = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t// The context reads the resource value, so it is made for each run.\n\tEffect.suspend(() => {\n\t\tconst context = makeContext(policy.domain, request, undefined)\n\t\treturn context === undefined\n\t\t\t? Effect.succeed(Decision.NotApplicable)\n\t\t\t: decideNode(nodeOf(policy), context, undefined)\n\t})\n\n/** Gives the decision of a policy with no predicates, at once. */\nexport const decideSync = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Decision.Decision =>\n\tEffect.runSync(\n\t\t// The public type of `decideSync` takes only policies with no\n\t\t// predicates, so this Effect has no error and needs no services.\n\t\t// @effect-diagnostics-next-line unsafeEffectTypeAssertion:off\n\t\tdecideAny(policy, request) as Effect.Effect<Decision.Decision>,\n\t)\n\n/** Gives the decision of a policy and the rules that were tested to make it. */\nexport const explain = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Explanation, unknown, unknown> =>\n\t// Each run of the Effect needs its own trace, so the trace is made inside\n\t// the Effect.\n\tEffect.gen(function* () {\n\t\tconst trace = yield* Ref.make(Chunk.empty<Step>())\n\t\tconst context = makeContext(policy.domain, request, trace)\n\t\tif (context === undefined) {\n\t\t\treturn { decision: Decision.NotApplicable, steps: [] }\n\t\t}\n\t\tconst decision = yield* decideNode(nodeOf(policy), context, undefined)\n\t\treturn { decision, steps: Chunk.toReadonlyArray(yield* Ref.get(trace)) }\n\t})\n\n/**\n * Tests if a deny rule of the request names a part inside `field`.\n */\nconst deniesInside = (\n\trules: ReadonlyArray<RuleData.Rule>,\n\ttarget: { readonly action: string; readonly kind: string },\n\tfield: string,\n): boolean =>\n\tArr.some(\n\t\trules,\n\t\t(value) =>\n\t\t\tvalue._tag === \"Deny\" &&\n\t\t\tvalue.fields !== undefined &&\n\t\t\tvalue.fields.some((pattern) => RuleData.matchesInside(pattern, field)) &&\n\t\t\t(value.actions.includes(\"*\") || value.actions.includes(target.action)) &&\n\t\t\t(value.resources.includes(\"*\") || value.resources.includes(target.kind)),\n\t)\n\n/** Gives the fields of a resource value that the policy lets a request use. */\nexport const permittedFields = (\n\tpolicy: Any,\n\trequest: Omit<AnyRequest, \"field\">,\n): Effect.Effect<ReadonlyArray<string>, unknown, unknown> => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor: Resource.Descriptor | undefined =\n\t\tkind === undefined\n\t\t\t? undefined\n\t\t\t: DomainData.descriptorOf(policy.domain, kind)\n\tif (descriptor === undefined) {\n\t\treturn Effect.succeed([])\n\t}\n\t// The rules of the tree, in one flat list for all the fields.\n\tconst rules = policy.pipe(nodeOf, PolicyNodes.rulesOf)\n\t// The fields with names only: a list can not name every key of a record.\n\tconst listed = Arr.filter(\n\t\t// The list for the limit of the parts of a path of the domain, as the\n\t\t// decision of each field reads it.\n\t\tCatalog.fieldsOf(descriptor.catalog, policy.domain.limits.fieldPathParts),\n\t\t(field) =>\n\t\t\tfield.listed &&\n\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t)\n\treturn Effect.map(\n\t\tEffect.filter(listed, (field) =>\n\t\t\tEffect.map(\n\t\t\t\tdecideAny(policy, { ...request, field: field.text }),\n\t\t\t\t(result) =>\n\t\t\t\t\tresult._tag === \"Allow\" &&\n\t\t\t\t\t!(\n\t\t\t\t\t\tfield.open &&\n\t\t\t\t\t\tdeniesInside(\n\t\t\t\t\t\t\trules,\n\t\t\t\t\t\t\t{ ...request, kind: descriptor.kind },\n\t\t\t\t\t\t\tfield.text,\n\t\t\t\t\t\t)\n\t\t\t\t\t),\n\t\t\t),\n\t\t),\n\t\tArr.map((field) => field.text),\n\t)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"decide.js","names":["RuleData.matchesTarget","Condition.evaluate","Place.pathOf","Decision.Allow","Decision.Deny","Place.at","at","Decision.NotApplicable","Catalog.lookup","ResourceData.kindOf","DomainData.descriptorOf","RuleData.fitsOf","RuleData.matchesInside","PolicyNodes.rulesOf","Catalog.fieldsOf"],"sources":["../../src/internal/decide.ts"],"sourcesContent":["/**\n * The decision walk of a policy: `decide`, `explain` and `permittedFields`.\n * The types are in `Policy.ts`.\n */\n// A policy keeps the error type and the service type of its predicates in the\n// public types. The code below works on the nodes with no types, where they\n// are `unknown`, so the language service check for `unknown` in an Effect is\n// off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Chunk from \"effect/Chunk\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Ref from \"effect/Ref\"\nimport * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport { nodeOf } from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as RuleData from \"./rule.ts\"\n\ntype Any = Policy.Any\ntype AnyRequest = Policy.AnyRequest\ntype AnyRule = Policy.AnyRule\ntype Explanation = Policy.Explanation\ntype Step = Policy.Step\n\ninterface DecideContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly input: Condition.Input\n\t/** The resource of the kind, for the fields that a rule may grant. */\n\treadonly descriptor: Resource.Descriptor\n\t/** The test of `RuleData.fitsOf` for the catalog, for this decision. */\n\treadonly fits: (pattern: string) => boolean\n\t/** The most parts of a field path in the domain. */\n\treadonly parts: number\n\t/** The rules that were tested, for {@link explain}. */\n\treadonly trace: Ref.Ref<Chunk.Chunk<Step>> | undefined\n}\n\n/**\n * The place of a node or a rule in a tree: the index of each child, then\n * the index of the rule. It shares the path of its parent, and a decision\n * makes the full path only for a step of `explain`: see `Place`.\n */\ntype At = Place.Place | undefined\n\nconst ruleApplies = (\n\tvalue: RuleData.Rule,\n\tcontext: DecideContext,\n\tat: Place.Place,\n): Effect.Effect<boolean, unknown, unknown> => {\n\tif (!RuleData.matchesTarget(value, context)) {\n\t\treturn Effect.succeed(false)\n\t}\n\tconst test =\n\t\tvalue.when === undefined\n\t\t\t? Effect.succeed(true)\n\t\t\t: // A rule of a node keeps no type of what it reads. The decision gives\n\t\t\t\t// it the value and the principal of the request.\n\t\t\t\tCondition.evaluate(value.when as Condition.Any, context.input)\n\tconst trace = context.trace\n\treturn trace === undefined\n\t\t? test\n\t\t: Effect.tap(test, (applies) =>\n\t\t\t\t// A chunk adds a step with no copy of the steps before.\n\t\t\t\tRef.update(\n\t\t\t\t\ttrace,\n\t\t\t\t\tChunk.append({\n\t\t\t\t\t\tpath: Place.pathOf(at) as ReadonlyArray<number>,\n\t\t\t\t\t\trule: typed(value),\n\t\t\t\t\t\tapplies,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t)\n}\n\n/** A rule of a node, with the type of a rule of no domain. */\nconst typed = (value: RuleData.Rule): AnyRule => value as never\n\nconst fromRule = (value: RuleData.Rule): Decision.Decision =>\n\tvalue._tag === \"Allow\"\n\t\t? Decision.Allow({ rule: typed(value) })\n\t\t: value.reason === undefined\n\t\t\t? Decision.Deny({ rule: typed(value) })\n\t\t\t: Decision.Deny({ rule: typed(value), reason: value.reason })\n\n/** Gives the first rule that applies, in order. */\nconst firstApplying = (\n\tcandidates: ReadonlyArray<readonly [RuleData.Rule, number]>,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Option.Option<RuleData.Rule>, unknown, unknown> =>\n\tEffect.map(\n\t\tEffect.findFirst(candidates, ([value, index]) =>\n\t\t\truleApplies(value, context, Place.at(at, index)),\n\t\t),\n\t\tOption.map(([value]) => value),\n\t)\n\n/** Gives the first result that is found, and tests `next` only when none is. */\nconst orElse = <A, E, R>(\n\tfirst: Effect.Effect<Option.Option<A>, E, R>,\n\tnext: () => Effect.Effect<Option.Option<A>, E, R>,\n): Effect.Effect<Option.Option<A>, E, R> =>\n\tEffect.filterOrElse(first, Option.isSome, next)\n\nconst decideRules = (\n\tnode: PolicyNodes.Rules,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst indexed = Arr.map(node.rules, (value, index) => [value, index] as const)\n\tconst denies = Arr.filter(indexed, ([value]) => value._tag === \"Deny\")\n\tconst allows = Arr.filter(indexed, ([value]) => value._tag === \"Allow\")\n\tconst first = (candidates: ReadonlyArray<readonly [RuleData.Rule, number]>) =>\n\t\tfirstApplying(candidates, context, at)\n\tconst found = (() => {\n\t\tswitch (node.algorithm) {\n\t\t\tcase \"denyOverrides\":\n\t\t\t\treturn orElse(first(denies), () => first(allows))\n\t\t\tcase \"permitOverrides\":\n\t\t\t\treturn orElse(first(allows), () => first(denies))\n\t\t\tcase \"firstApplicable\":\n\t\t\t\treturn first(indexed)\n\t\t\tcase \"lastApplicable\":\n\t\t\t\treturn first(Arr.reverse(indexed))\n\t\t}\n\t})()\n\treturn Effect.map(\n\t\tfound,\n\t\tOption.match({ onNone: () => Decision.NotApplicable, onSome: fromRule }),\n\t)\n}\n\n/** The next step of a walk over children: stop with a decision, or go on. */\ntype WalkStep<S> =\n\t| { readonly _tag: \"Stop\"; readonly decision: Decision.Decision }\n\t| { readonly _tag: \"Next\"; readonly state: S }\n\n/**\n * Decides the children in order. After each child, `step` stops with a\n * decision, or gives the state for the next child. After the last child,\n * `end` gives the decision. A child after a stop is not decided, so its\n * predicates do not run.\n */\nconst walkChildren = <S>(\n\tchildren: ReadonlyArray<PolicyNodes.Node>,\n\tcontext: DecideContext,\n\tat: At,\n\tstart: S,\n\tstep: (state: S, decision: Decision.Decision) => WalkStep<S>,\n\tend: (state: S) => Decision.Decision,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst go = (\n\t\tindex: number,\n\t\tstate: S,\n\t): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t\tindex === children.length\n\t\t\t? Effect.succeed(end(state))\n\t\t\t: Effect.flatMap(\n\t\t\t\t\t// Each call for a child is a step of the Effect, so a deep tree\n\t\t\t\t\t// does not fill the stack.\n\t\t\t\t\tEffect.suspend(() =>\n\t\t\t\t\t\tdecideNode(children[index]!, context, Place.at(at, index)),\n\t\t\t\t\t),\n\t\t\t\t\t(decision) => {\n\t\t\t\t\t\tconst next = step(state, decision)\n\t\t\t\t\t\treturn next._tag === \"Stop\"\n\t\t\t\t\t\t\t? Effect.succeed(next.decision)\n\t\t\t\t\t\t\t: go(index + 1, next.state)\n\t\t\t\t\t},\n\t\t\t\t)\n\treturn go(0, start)\n}\n\nconst stop = (decision: Decision.Decision): WalkStep<never> => ({\n\t_tag: \"Stop\",\n\tdecision,\n})\n\nconst next = <S>(state: S): WalkStep<S> => ({ _tag: \"Next\", state })\n\n/**\n * Decides a node. A shared child is decided again for each place, so its\n * predicates run each time, as the policy says. The limits of the nodes keep\n * this work small.\n */\nexport const decideNode = (\n\tnode: PolicyNodes.Node,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn decideRules(node, context, at)\n\t\tcase \"All\":\n\t\t\t// One deny is enough to deny. Every child must allow.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\t{\n\t\t\t\t\tfirstAllow: Option.none<Decision.Decision>(),\n\t\t\t\t\tallAllow: node.policies.length > 0,\n\t\t\t\t},\n\t\t\t\t(state, decision) =>\n\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next({\n\t\t\t\t\t\t\t\tfirstAllow: Option.orElse(state.firstAllow, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tallAllow: state.allAllow && decision._tag === \"Allow\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t(state) =>\n\t\t\t\t\tstate.allAllow\n\t\t\t\t\t\t? Option.getOrElse(state.firstAllow, () => Decision.NotApplicable)\n\t\t\t\t\t\t: Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Any\":\n\t\t\t// One allow is enough. Without one, the first deny denies.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\tOption.none<Decision.Decision>(),\n\t\t\t\t(firstDeny, decision) =>\n\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next(\n\t\t\t\t\t\t\t\tOption.orElse(firstDeny, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t(firstDeny) =>\n\t\t\t\t\tOption.getOrElse(firstDeny, () => Decision.NotApplicable),\n\t\t\t)\n\t\tcase \"FirstOf\":\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\tundefined,\n\t\t\t\t(_, decision) =>\n\t\t\t\t\tdecision._tag === \"NotApplicable\" ? next(undefined) : stop(decision),\n\t\t\t\t() => Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Ceiling\":\n\t\t\t// A deny of the cap wins. Else the policy decides.\n\t\t\treturn Effect.filterOrElse(\n\t\t\t\tEffect.suspend(() => decideNode(node.cap, context, Place.at(at, 0))),\n\t\t\t\t(capped) => capped._tag === \"Deny\",\n\t\t\t\t() => decideNode(node.policy, context, Place.at(at, 1)),\n\t\t\t)\n\t}\n}\n\n/**\n * Tests if a request fits its kind: the action is an action of the kind, and\n * the field, if there is one, is a field of the kind.\n */\nexport const fitsKind = (\n\tdescriptor: Resource.Descriptor,\n\trequest: { readonly action: string; readonly field?: string | undefined },\n\tparts: number,\n): boolean =>\n\tdescriptor.actions.includes(request.action) &&\n\t(request.field === undefined ||\n\t\t(Catalog.lookup(descriptor.catalog, request.field, parts)?.listed ?? false))\n\nconst makeContext = (\n\tdomain: Domain.Any,\n\trequest: AnyRequest,\n\ttrace: Ref.Ref<Chunk.Chunk<Step>> | undefined,\n): DecideContext | undefined => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor =\n\t\tkind === undefined ? undefined : DomainData.descriptorOf(domain, kind)\n\tif (\n\t\tkind === undefined ||\n\t\tdescriptor === undefined ||\n\t\t!fitsKind(descriptor, request, domain.limits.fieldPathParts)\n\t) {\n\t\treturn undefined\n\t}\n\treturn {\n\t\taction: request.action,\n\t\tkind,\n\t\tfield: request.field,\n\t\tinput: {\n\t\t\tvalue: request.resource,\n\t\t\tprincipal: request.principal,\n\t\t\tpath: descriptor.path,\n\t\t},\n\t\tdescriptor,\n\t\tfits: RuleData.fitsOf(descriptor.catalog, domain.limits.fieldPathParts),\n\t\tparts: domain.limits.fieldPathParts,\n\t\ttrace,\n\t}\n}\n\nexport const decideAny = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t// The context reads the resource value, so it is made for each run.\n\tEffect.suspend(() => {\n\t\tconst context = makeContext(policy.domain, request, undefined)\n\t\treturn context === undefined\n\t\t\t? Effect.succeed(Decision.NotApplicable)\n\t\t\t: decideNode(nodeOf(policy), context, undefined)\n\t})\n\n/** Gives the decision of a policy with no predicates, at once. */\nexport const decideSync = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Decision.Decision =>\n\tEffect.runSync(\n\t\t// The public type of `decideSync` takes only policies with no\n\t\t// predicates, so this Effect has no error and needs no services.\n\t\t// @effect-diagnostics-next-line unsafeEffectTypeAssertion:off\n\t\tdecideAny(policy, request) as Effect.Effect<Decision.Decision>,\n\t)\n\n/** Gives the decision of a policy and the rules that were tested to make it. */\nexport const explain = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Explanation, unknown, unknown> =>\n\t// Each run of the Effect needs its own trace, so the trace is made inside\n\t// the Effect.\n\tEffect.gen(function* () {\n\t\tconst trace = yield* Ref.make(Chunk.empty<Step>())\n\t\tconst context = makeContext(policy.domain, request, trace)\n\t\tif (context === undefined) {\n\t\t\treturn { decision: Decision.NotApplicable, steps: [] }\n\t\t}\n\t\tconst decision = yield* decideNode(nodeOf(policy), context, undefined)\n\t\treturn { decision, steps: Chunk.toReadonlyArray(yield* Ref.get(trace)) }\n\t})\n\n/**\n * Tests if a deny rule of the request names a part inside `field`.\n */\nconst deniesInside = (\n\trules: ReadonlyArray<RuleData.Rule>,\n\ttarget: { readonly action: string; readonly kind: string },\n\tfield: string,\n): boolean =>\n\tArr.some(\n\t\trules,\n\t\t(value) =>\n\t\t\tvalue._tag === \"Deny\" &&\n\t\t\tvalue.fields !== undefined &&\n\t\t\tvalue.fields.some((pattern) => RuleData.matchesInside(pattern, field)) &&\n\t\t\t(value.actions.includes(\"*\") || value.actions.includes(target.action)) &&\n\t\t\t(value.resources.includes(\"*\") || value.resources.includes(target.kind)),\n\t)\n\n/** Gives the fields of a resource value that the policy lets a request use. */\nexport const permittedFields = (\n\tpolicy: Any,\n\trequest: Omit<AnyRequest, \"field\">,\n): Effect.Effect<ReadonlyArray<string>, unknown, unknown> => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor: Resource.Descriptor | undefined =\n\t\tkind === undefined\n\t\t\t? undefined\n\t\t\t: DomainData.descriptorOf(policy.domain, kind)\n\tif (descriptor === undefined) {\n\t\treturn Effect.succeed([])\n\t}\n\t// The rules of the tree, in one flat list for all the fields.\n\tconst rules = policy.pipe(nodeOf, PolicyNodes.rulesOf)\n\t// The fields with names only: a list can not name every key of a record.\n\tconst listed = Arr.filter(\n\t\t// The list for the limit of the parts of a path of the domain, as the\n\t\t// decision of each field reads it.\n\t\tCatalog.fieldsOf(\n\t\t\tdescriptor.catalog,\n\t\t\tpolicy.domain.limits.fieldPathParts,\n\t\t\tpolicy.domain.limits.catalogFields,\n\t\t),\n\t\t(field) =>\n\t\t\tfield.listed &&\n\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t)\n\treturn Effect.map(\n\t\tEffect.filter(listed, (field) =>\n\t\t\tEffect.map(\n\t\t\t\tdecideAny(policy, { ...request, field: field.text }),\n\t\t\t\t(result) =>\n\t\t\t\t\tresult._tag === \"Allow\" &&\n\t\t\t\t\t!(\n\t\t\t\t\t\tfield.open &&\n\t\t\t\t\t\tdeniesInside(\n\t\t\t\t\t\t\trules,\n\t\t\t\t\t\t\t{ ...request, kind: descriptor.kind },\n\t\t\t\t\t\t\tfield.text,\n\t\t\t\t\t\t)\n\t\t\t\t\t),\n\t\t\t),\n\t\t),\n\t\tArr.map((field) => field.text),\n\t)\n}\n"],"mappings":"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"}
@@ -1,20 +1,23 @@
1
- import { exceeded, make as make$1 } from "./limits.js";
1
+ import { make as make$1 } from "./limits.js";
2
2
  import { catalogOf, fieldsOf } from "./catalog.js";
3
3
  import { descriptor } from "./resource.js";
4
4
  import * as Arr from "effect/Array";
5
5
  import * as HashMap from "effect/HashMap";
6
- import * as Data from "effect/Data";
7
6
  import * as Option from "effect/Option";
7
+ import * as Data from "effect/Data";
8
8
  // src/internal/domain.ts
9
9
  /**
10
10
  * The values of domains. The types are in `Domain.ts`.
11
11
  */
12
12
  /** The class of every domain value. `isDomain` uses it to know a domain. */
13
13
  var DomainData = class extends Data.Class {};
14
- /** Stops a catalog with more fields in its list than the domain takes. */
14
+ /**
15
+ * Stops a catalog with more fields in its list than the domain takes. The
16
+ * list stops at the first field past the limit, so a Schema with very many
17
+ * fields does not take very much memory first.
18
+ */
15
19
  const checkCatalog = (catalog, limits, name) => {
16
- const fields = fieldsOf(catalog, limits.fieldPathParts).length;
17
- if (fields > limits.catalogFields) throw exceeded(name, "catalogFields", "fields", fields, limits.catalogFields);
20
+ fieldsOf(catalog, limits.fieldPathParts, limits.catalogFields, name);
18
21
  };
19
22
  /**
20
23
  * Makes a domain. Each resource must have its own kind name. The limits that
@@ -1 +1 @@
1
- {"version":3,"file":"domain.js","names":["Catalog.fieldsOf","Limits.exceeded","Limits.make","ResourceData.descriptor","Catalog.catalogOf"],"sources":["../../src/internal/domain.ts"],"sourcesContent":["/**\n * The values of domains. The types are in `Domain.ts`.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as Option from \"effect/Option\"\nimport type * as Schema from \"effect/Schema\"\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 ResourceData from \"./resource.ts\"\n\n/** The class of every domain value. `isDomain` uses it to know a domain. */\nclass DomainData extends Data.Class<{\n\treadonly principal: Schema.Top\n\treadonly principalCatalog: Catalog.Catalog\n\treadonly resources: HashMap.HashMap<string, Resource.Descriptor>\n\treadonly meta: Schema.Top | undefined\n\treadonly limits: Limits.Limits\n}> {}\n\n/** Stops a catalog with more fields in its list than the domain takes. */\nconst checkCatalog = (\n\tcatalog: Catalog.Catalog,\n\tlimits: Limits.Limits,\n\tname: string,\n): void => {\n\tconst fields = Catalog.fieldsOf(catalog, limits.fieldPathParts).length\n\tif (fields > limits.catalogFields) {\n\t\tthrow Limits.exceeded(\n\t\t\tname,\n\t\t\t\"catalogFields\",\n\t\t\t\"fields\",\n\t\t\tfields,\n\t\t\tlimits.catalogFields,\n\t\t)\n\t}\n}\n\n/**\n * Makes a domain. Each resource must have its own kind name. The limits that\n * the domain does not set have their defaults.\n */\nexport const make = (options: {\n\treadonly principal: Schema.Top\n\treadonly resources: ReadonlyArray<Resource.Any>\n\treadonly meta?: Schema.Top\n\treadonly limits?: Partial<Limits.Limits>\n}): DomainData => {\n\tconst limits = Limits.make(options.limits)\n\tconst descriptors = Arr.map(options.resources, ResourceData.descriptor)\n\tconst kinds = Arr.map(descriptors, (descriptor) => descriptor.kind)\n\t// Two resources with one kind name is a mistake in the code, not in the\n\t// data, so it is a defect.\n\tconst twice = Arr.findFirst(\n\t\tkinds,\n\t\t(kind, index) => kinds.indexOf(kind) !== index,\n\t)\n\tif (Option.isSome(twice)) {\n\t\tthrow new Error(`Two resources have the kind name \"${twice.value}\"`)\n\t}\n\tif (kinds.includes(\"*\")) {\n\t\tthrow new Error(`A resource may not have the kind name \"*\"`)\n\t}\n\tconst resources = HashMap.fromIterable(\n\t\tArr.map(\n\t\t\tdescriptors,\n\t\t\t(descriptor) => [descriptor.kind, descriptor] as const,\n\t\t),\n\t)\n\t// The catalog checks the field names of the principal, so a bad name is a\n\t// defect when the domain is made. A Schema with more fields than the\n\t// domain takes is a defect too.\n\tconst principalCatalog = Catalog.catalogOf(options.principal.ast)\n\tcheckCatalog(principalCatalog, limits, \"The Schema of the principal\")\n\tArr.forEach(descriptors, (descriptor) =>\n\t\tcheckCatalog(\n\t\t\tdescriptor.catalog,\n\t\t\tlimits,\n\t\t\t`The Schema of the resource \"${descriptor.kind}\"`,\n\t\t),\n\t)\n\treturn new DomainData({\n\t\tprincipal: options.principal,\n\t\tprincipalCatalog,\n\t\tresources,\n\t\tmeta: options.meta,\n\t\tlimits,\n\t})\n}\n\n/** Gives the descriptor of the resource with a kind name. */\nexport const descriptorOf = (\n\tdomain: Domain.Any,\n\tkind: string,\n): Resource.Descriptor | undefined =>\n\tOption.getOrUndefined(HashMap.get(domain.resources, kind))\n\n/** Tests if a value is a domain. */\nexport const isDomain = (u: unknown): u is Domain.Any => u instanceof DomainData\n"],"mappings":";;;;;;;;;;;;AAeA,IAAM,aAAN,cAAyB,KAAK,MAM3B,CAAC;;AAGJ,MAAM,gBACL,SACA,QACA,SACU;CACV,MAAM,SAASA,SAAiB,SAAS,OAAO,cAAc,CAAC,CAAC;CAChE,IAAI,SAAS,OAAO,eACnB,MAAMC,SACL,MACA,iBACA,UACA,QACA,OAAO,aACR;AAEF;;;;;AAMA,MAAa,QAAQ,YAKH;CACjB,MAAM,SAASC,OAAY,QAAQ,MAAM;CACzC,MAAM,cAAc,IAAI,IAAI,QAAQ,WAAWC,UAAuB;CACtE,MAAM,QAAQ,IAAI,IAAI,cAAc,eAAe,WAAW,IAAI;CAGlE,MAAM,QAAQ,IAAI,UACjB,QACC,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,KAC1C;CACA,IAAI,OAAO,OAAO,KAAK,GACtB,MAAM,IAAI,MAAM,qCAAqC,MAAM,MAAM,EAAE;CAEpE,IAAI,MAAM,SAAS,GAAG,GACrB,MAAM,IAAI,MAAM,2CAA2C;CAE5D,MAAM,YAAY,QAAQ,aACzB,IAAI,IACH,cACC,eAAe,CAAC,WAAW,MAAM,UAAU,CAC7C,CACD;CAIA,MAAM,mBAAmBC,UAAkB,QAAQ,UAAU,GAAG;CAChE,aAAa,kBAAkB,QAAQ,6BAA6B;CACpE,IAAI,QAAQ,cAAc,eACzB,aACC,WAAW,SACX,QACA,+BAA+B,WAAW,KAAK,EAChD,CACD;CACA,OAAO,IAAI,WAAW;EACrB,WAAW,QAAQ;EACnB;EACA;EACA,MAAM,QAAQ;EACd;CACD,CAAC;AACF;;AAGA,MAAa,gBACZ,QACA,SAEA,OAAO,eAAe,QAAQ,IAAI,OAAO,WAAW,IAAI,CAAC"}
1
+ {"version":3,"file":"domain.js","names":["Limits.make","ResourceData.descriptor","Catalog.catalogOf"],"sources":["../../src/internal/domain.ts"],"sourcesContent":["/**\n * The values of domains. The types are in `Domain.ts`.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as Option from \"effect/Option\"\nimport type * as Schema from \"effect/Schema\"\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 ResourceData from \"./resource.ts\"\n\n/** The class of every domain value. `isDomain` uses it to know a domain. */\nclass DomainData extends Data.Class<{\n\treadonly principal: Schema.Top\n\treadonly principalCatalog: Catalog.Catalog\n\treadonly resources: HashMap.HashMap<string, Resource.Descriptor>\n\treadonly meta: Schema.Top | undefined\n\treadonly limits: Limits.Limits\n}> {}\n\n/**\n * Stops a catalog with more fields in its list than the domain takes. The\n * list stops at the first field past the limit, so a Schema with very many\n * fields does not take very much memory first.\n */\nconst checkCatalog = (\n\tcatalog: Catalog.Catalog,\n\tlimits: Limits.Limits,\n\tname: string,\n): void => {\n\tCatalog.fieldsOf(catalog, limits.fieldPathParts, limits.catalogFields, name)\n}\n\n/**\n * Makes a domain. Each resource must have its own kind name. The limits that\n * the domain does not set have their defaults.\n */\nexport const make = (options: {\n\treadonly principal: Schema.Top\n\treadonly resources: ReadonlyArray<Resource.Any>\n\treadonly meta?: Schema.Top\n\treadonly limits?: Partial<Limits.Limits>\n}): DomainData => {\n\tconst limits = Limits.make(options.limits)\n\tconst descriptors = Arr.map(options.resources, ResourceData.descriptor)\n\tconst kinds = Arr.map(descriptors, (descriptor) => descriptor.kind)\n\t// Two resources with one kind name is a mistake in the code, not in the\n\t// data, so it is a defect.\n\tconst twice = Arr.findFirst(\n\t\tkinds,\n\t\t(kind, index) => kinds.indexOf(kind) !== index,\n\t)\n\tif (Option.isSome(twice)) {\n\t\tthrow new Error(`Two resources have the kind name \"${twice.value}\"`)\n\t}\n\tif (kinds.includes(\"*\")) {\n\t\tthrow new Error(`A resource may not have the kind name \"*\"`)\n\t}\n\tconst resources = HashMap.fromIterable(\n\t\tArr.map(\n\t\t\tdescriptors,\n\t\t\t(descriptor) => [descriptor.kind, descriptor] as const,\n\t\t),\n\t)\n\t// The catalog checks the field names of the principal, so a bad name is a\n\t// defect when the domain is made. A Schema with more fields than the\n\t// domain takes is a defect too.\n\tconst principalCatalog = Catalog.catalogOf(options.principal.ast)\n\tcheckCatalog(principalCatalog, limits, \"The Schema of the principal\")\n\tArr.forEach(descriptors, (descriptor) =>\n\t\tcheckCatalog(\n\t\t\tdescriptor.catalog,\n\t\t\tlimits,\n\t\t\t`The Schema of the resource \"${descriptor.kind}\"`,\n\t\t),\n\t)\n\treturn new DomainData({\n\t\tprincipal: options.principal,\n\t\tprincipalCatalog,\n\t\tresources,\n\t\tmeta: options.meta,\n\t\tlimits,\n\t})\n}\n\n/** Gives the descriptor of the resource with a kind name. */\nexport const descriptorOf = (\n\tdomain: Domain.Any,\n\tkind: string,\n): Resource.Descriptor | undefined =>\n\tOption.getOrUndefined(HashMap.get(domain.resources, kind))\n\n/** Tests if a value is a domain. */\nexport const isDomain = (u: unknown): u is Domain.Any => u instanceof DomainData\n"],"mappings":";;;;;;;;;;;;AAeA,IAAM,aAAN,cAAyB,KAAK,MAM3B,CAAC;;;;;;AAOJ,MAAM,gBACL,SACA,QACA,SACU;CACV,SAAiB,SAAS,OAAO,gBAAgB,OAAO,eAAe,IAAI;AAC5E;;;;;AAMA,MAAa,QAAQ,YAKH;CACjB,MAAM,SAASA,OAAY,QAAQ,MAAM;CACzC,MAAM,cAAc,IAAI,IAAI,QAAQ,WAAWC,UAAuB;CACtE,MAAM,QAAQ,IAAI,IAAI,cAAc,eAAe,WAAW,IAAI;CAGlE,MAAM,QAAQ,IAAI,UACjB,QACC,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,KAC1C;CACA,IAAI,OAAO,OAAO,KAAK,GACtB,MAAM,IAAI,MAAM,qCAAqC,MAAM,MAAM,EAAE;CAEpE,IAAI,MAAM,SAAS,GAAG,GACrB,MAAM,IAAI,MAAM,2CAA2C;CAE5D,MAAM,YAAY,QAAQ,aACzB,IAAI,IACH,cACC,eAAe,CAAC,WAAW,MAAM,UAAU,CAC7C,CACD;CAIA,MAAM,mBAAmBC,UAAkB,QAAQ,UAAU,GAAG;CAChE,aAAa,kBAAkB,QAAQ,6BAA6B;CACpE,IAAI,QAAQ,cAAc,eACzB,aACC,WAAW,SACX,QACA,+BAA+B,WAAW,KAAK,EAChD,CACD;CACA,OAAO,IAAI,WAAW;EACrB,WAAW,QAAQ;EACnB;EACA;EACA,MAAM,QAAQ;EACd;CACD,CAAC;AACF;;AAGA,MAAa,gBACZ,QACA,SAEA,OAAO,eAAe,QAAQ,IAAI,OAAO,WAAW,IAAI,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import { ceilings, fieldPathParts, patternParts } from "./limits.js";
2
2
  import { keyFits } from "./catalog.js";
3
3
  import * as Arr from "effect/Array";
4
- import * as Result from "effect/Result";
5
4
  import * as Option from "effect/Option";
5
+ import * as Result from "effect/Result";
6
6
  // src/internal/patterns.ts
7
7
  /**
8
8
  * Field patterns, as in `settings.*`, `secret*` or `**.secret`. A pattern is
@@ -136,11 +136,11 @@ const decodeMeta = (rule, domain, budget) => {
136
136
  value,
137
137
  schema: domain.meta
138
138
  };
139
- })), (found) => found === void 0 ? Effect.void : Effect.mapError(Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value), (error) => new Problem({
139
+ })), (found) => found === void 0 ? Effect.void : Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value).pipe(Effect.mapError((error) => new Problem({
140
140
  path: pathOf(at$4),
141
141
  message: error.message,
142
142
  issue: error.issue
143
- })));
143
+ })), Effect.catchDefect((defect) => defect instanceof RangeError ? Effect.fail(problemAt(at$4, "The meta of the rule has too many levels for the Schema of meta")) : Effect.die(defect))));
144
144
  };
145
145
  const decodeRule = (rule, meta, budget, domain) => {
146
146
  const { input, place } = rule;
@@ -1 +1 @@
1
- {"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Trampoline.run","Json.readList","at","Json.problemAt","Place.at","read","Trampoline.adaptive","Json.read","Json.Problem","Place.pathOf","Json.decodeAt","Json.readString","Rule.make","PolicyNodes.isAlgorithm","Limits.LimitExceeded","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling"],"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 Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport * as Trampoline from \"./trampoline.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\t// The walk of the tree takes the steps of a trampoline, so a deep tree\n\t// does not fill the stack.\n\treturn Trampoline.run(node.depth, (R) => {\n\t\tconst tree = (current: Node): Trampoline.Step<unknown> => {\n\t\t\tswitch (current._tag) {\n\t\t\t\tcase \"Rules\":\n\t\t\t\t\treturn R.succeed({\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t\t),\n\t\t\t\t\t})\n\t\t\t\tcase \"All\":\n\t\t\t\tcase \"Any\":\n\t\t\t\tcase \"FirstOf\": {\n\t\t\t\t\tconst tag = current._tag\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach(current.policies, (child) =>\n\t\t\t\t\t\t\tR.suspend(() => tree(child)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t(policies) => ({ _tag: tag, policies }),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"Ceiling\":\n\t\t\t\t\treturn R.flatMap(\n\t\t\t\t\t\tR.suspend(() => tree(current.cap)),\n\t\t\t\t\t\t(cap) =>\n\t\t\t\t\t\t\tR.map(\n\t\t\t\t\t\t\t\tR.suspend(() => tree(current.policy)),\n\t\t\t\t\t\t\t\t(policy) => ({ _tag: \"Ceiling\", cap, policy }),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn tree(node)\n\t})\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\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, at, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tPlace.at(at, name, index),\n\t\t\t\t\"The item must be a string\",\n\t\t\t)\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\tat: Place.Place | undefined,\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, at)\n\treturn undefined\n}\n\n/** A value of the input to count, with its place. */\ninterface Part {\n\treadonly value: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * Counts the objects and items of a value, as the other parts count. It is a\n * walk of the trampoline, so a deep value does not fill the stack.\n */\nconst spendValue = (budget: Json.Budget): ((part: Part) => void) =>\n\tTrampoline.adaptive<Part, void>((R, child) => (part, depth) => {\n\t\tconst { value, at } = part\n\t\tif (typeof value !== \"object\" || value === null) {\n\t\t\treturn R.succeed(undefined)\n\t\t}\n\t\t// The length of an array counts before its items are read, so a sparse\n\t\t// array with a very large length stops at once.\n\t\tconst list = Array.isArray(value)\n\t\tif (list) {\n\t\t\tJson.spend(budget, 1 + value.length, at)\n\t\t}\n\t\tconst names = list\n\t\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t\t: Object.keys(value)\n\t\tif (!list) {\n\t\t\tJson.spend(budget, 1 + names.length, at)\n\t\t}\n\t\treturn R.map(\n\t\t\tR.forEach(names, (name) =>\n\t\t\t\tchild(\n\t\t\t\t\t{ value: Json.read(value, name, at), at: Place.at(at, name) },\n\t\t\t\t\tdepth + 1,\n\t\t\t\t),\n\t\t\t),\n\t\t\t() => undefined,\n\t\t)\n\t})\n\n/**\n * A rule in the input, with its place, to decode after the tree. The place\n * shares the path of its parent: see `Place`.\n */\ninterface RuleInput {\n\treadonly input: unknown\n\treadonly place: Place.Place\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 place: Place.Place | undefined\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 place: Place.Place | undefined\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 place: Place.Place | undefined\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\trule: RuleInput,\n\tdomain: Domain.Any,\n\tbudget: Json.Budget,\n): Effect.Effect<unknown, Json.Problem> => {\n\tconst { input, place } = rule\n\tconst at = Place.at(place, \"meta\")\n\treturn Effect.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\", place, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", place)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problemAt(at, \"The domain has no Schema for meta\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(budget)({ value, at })\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: Place.pathOf(at),\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}\n\nconst decodeRule = (\n\trule: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tconst { input, place } = rule\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problemAt(place, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, place)\n\tconst tag = Json.read(input, \"_tag\", place)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problemAt(\n\t\t\tPlace.at(place, \"_tag\"),\n\t\t\t'A rule must be \"Allow\" or \"Deny\"',\n\t\t)\n\t}\n\tconst when = readOptional(input, \"when\", place, () =>\n\t\tJson.decodeAt(\n\t\t\tJson.read(input, \"when\", place),\n\t\t\tPlace.at(place, \"when\"),\n\t\t\tbudget,\n\t\t),\n\t)\n\tconst fields = readOptional(input, \"fields\", place, () =>\n\t\treadStrings(input, \"fields\", place, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", place, () =>\n\t\tJson.readString(input, \"reason\", place),\n\t)\n\tconst id = readOptional(input, \"id\", place, () =>\n\t\tJson.readString(input, \"id\", place),\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\", place, budget),\n\t\tresources: readStrings(input, \"resources\", place, 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/** A node of the input of a tree, with its place. */\ninterface TreePart {\n\treadonly input: unknown\n\treadonly at: Place.Place | undefined\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. The walk is a walk of\n * the trampoline, so a deep tree does not fill the stack.\n */\nconst readTree = (\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): ((part: TreePart) => Plan) => {\n\t// The nodes and rules of the tree that the walk has read. A tree with more\n\t// of them than `policyNodes` does not load, so the walk stops at the first\n\t// one past it, and it does not read the rest of a very large input. The\n\t// count belongs to one decode.\n\tconst count = { nodes: 0 }\n\tconst counted = (more: number, at: Place.Place | undefined): void => {\n\t\tcount.nodes += more\n\t\tconst most = budget.limits.policyNodes\n\t\tif (count.nodes > most) {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tat,\n\t\t\t\t`A policy tree has more than ${String(most)} nodes. The most is ${String(most)}.`,\n\t\t\t)\n\t\t}\n\t}\n\treturn Trampoline.adaptive<TreePart, Plan>((R, child) => (part, depth) => {\n\t\tconst { input, at } = part\n\t\tif (typeof input !== \"object\" || input === null) {\n\t\t\tthrow Json.problemAt(at, \"A node of a policy must be an object\")\n\t\t}\n\t\tJson.spend(budget, 1, at)\n\t\tcounted(1, at)\n\t\tconst below = (value: unknown, ...parts: ReadonlyArray<PropertyKey>) =>\n\t\t\tchild({ input: value, at: Place.at(at, ...parts) }, depth + 1)\n\t\tconst tag = Json.read(input, \"_tag\", at)\n\t\tswitch (tag) {\n\t\t\tcase \"Rules\": {\n\t\t\t\tconst algorithm = Json.readString(input, \"algorithm\", at)\n\t\t\t\tif (!PolicyNodes.isAlgorithm(algorithm)) {\n\t\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\t\tPlace.at(at, \"algorithm\"),\n\t\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst list = Json.readList(input, \"rules\", at, budget)\n\t\t\t\tcounted(list.length, at)\n\t\t\t\tconst found = Arr.map(\n\t\t\t\t\tlist,\n\t\t\t\t\t(rule, index) =>\n\t\t\t\t\t\trules.push({\n\t\t\t\t\t\t\tinput: rule,\n\t\t\t\t\t\t\tplace: Place.at(at, \"rules\", index),\n\t\t\t\t\t\t}) - 1,\n\t\t\t\t)\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\tsize: 1 + found.length,\n\t\t\t\t\t\talgorithm,\n\t\t\t\t\t\trules: found,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(\n\t\t\t\t\t\tJson.readList(input, \"policies\", at, budget),\n\t\t\t\t\t\t(value, index) => below(value, \"policies\", index),\n\t\t\t\t\t),\n\t\t\t\t\t(policies) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: tag,\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\t\t\t\tpolicies,\n\t\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\": {\n\t\t\t\tconst cap = Json.read(input, \"cap\", at)\n\t\t\t\tconst policy = Json.read(input, \"policy\", at)\n\t\t\t\treturn R.flatMap(below(cap, \"cap\"), (capPlan) =>\n\t\t\t\t\tR.map(below(policy, \"policy\"), (policyPlan) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: 1 + capPlan.size + policyPlan.size,\n\t\t\t\t\t\t\tcap: capPlan,\n\t\t\t\t\t\t\tpolicy: policyPlan,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow Json.problemAt(Place.at(at, \"_tag\"), \"The node has no known tag\")\n\t\t}\n\t})\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (budget: Json.Budget, plan: Plan): Plan => {\n\tconst most = budget.limits.policyNodes\n\tif (plan.size > most) {\n\t\tthrow Json.problemAt(\n\t\t\tplan.place,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(most)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/**\n * Makes the nodes of a plan, with the decoded rules. It is a walk of the\n * trampoline, so a deep plan does not fill the stack.\n */\nconst build = (\n\tbudget: Json.Budget,\n\trules: ReadonlyArray<Rule.Rule>,\n): ((plan: Plan) => Node) =>\n\tTrampoline.adaptive<Plan, Node>((R, child) => (plan, depth) => {\n\t\t// A node past a limit is a problem at its place. The constructor checks\n\t\t// the ceilings, and the decoder checks the limits of the domain, with\n\t\t// the nodes and values of the conditions.\n\t\tconst make = (made: () => Node): Node => {\n\t\t\tconst node = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn made()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\t\t\t\tthrow Json.problemAt(plan.place, error.message)\n\t\t\t\t\t}\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t})()\n\t\t\tconst most = budget.limits.policyNodes\n\t\t\tif (node.size > most) {\n\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\tplan.place,\n\t\t\t\t\t`A policy tree has ${String(node.size)} nodes. The most is ${String(most)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn node\n\t\t}\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew PolicyNodes.Rules({\n\t\t\t\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t\t\t\t}),\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\tconst tag = plan._tag\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(plan.policies, (inner) => child(inner, depth + 1)),\n\t\t\t\t\t(policies) => make(() => new PolicyNodes[tag](policies)),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn R.flatMap(child(plan.cap, depth + 1), (cap) =>\n\t\t\t\t\tR.map(child(plan.policy, depth + 1), (policy) =>\n\t\t\t\t\t\tmake(() => new PolicyNodes.Ceiling({ cap, policy })),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t}\n\t})\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, limits: domain.limits }\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(() =>\n\t\t\treadTree(budget, inputs)({ input, at: undefined }),\n\t\t)\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(budget, rules)(plan))\n\t})\n"],"mappings":"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"}
1
+ {"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Trampoline.run","Json.readList","at","Json.problemAt","Place.at","read","Trampoline.adaptive","Json.read","Json.Problem","Place.pathOf","Json.decodeAt","Json.readString","Rule.make","PolicyNodes.isAlgorithm","Limits.LimitExceeded","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling"],"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 Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport * as Trampoline from \"./trampoline.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\t// The walk of the tree takes the steps of a trampoline, so a deep tree\n\t// does not fill the stack.\n\treturn Trampoline.run(node.depth, (R) => {\n\t\tconst tree = (current: Node): Trampoline.Step<unknown> => {\n\t\t\tswitch (current._tag) {\n\t\t\t\tcase \"Rules\":\n\t\t\t\t\treturn R.succeed({\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t\t),\n\t\t\t\t\t})\n\t\t\t\tcase \"All\":\n\t\t\t\tcase \"Any\":\n\t\t\t\tcase \"FirstOf\": {\n\t\t\t\t\tconst tag = current._tag\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach(current.policies, (child) =>\n\t\t\t\t\t\t\tR.suspend(() => tree(child)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t(policies) => ({ _tag: tag, policies }),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"Ceiling\":\n\t\t\t\t\treturn R.flatMap(\n\t\t\t\t\t\tR.suspend(() => tree(current.cap)),\n\t\t\t\t\t\t(cap) =>\n\t\t\t\t\t\t\tR.map(\n\t\t\t\t\t\t\t\tR.suspend(() => tree(current.policy)),\n\t\t\t\t\t\t\t\t(policy) => ({ _tag: \"Ceiling\", cap, policy }),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn tree(node)\n\t})\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\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, at, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tPlace.at(at, name, index),\n\t\t\t\t\"The item must be a string\",\n\t\t\t)\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\tat: Place.Place | undefined,\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, at)\n\treturn undefined\n}\n\n/** A value of the input to count, with its place. */\ninterface Part {\n\treadonly value: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * Counts the objects and items of a value, as the other parts count. It is a\n * walk of the trampoline, so a deep value does not fill the stack.\n */\nconst spendValue = (budget: Json.Budget): ((part: Part) => void) =>\n\tTrampoline.adaptive<Part, void>((R, child) => (part, depth) => {\n\t\tconst { value, at } = part\n\t\tif (typeof value !== \"object\" || value === null) {\n\t\t\treturn R.succeed(undefined)\n\t\t}\n\t\t// The length of an array counts before its items are read, so a sparse\n\t\t// array with a very large length stops at once.\n\t\tconst list = Array.isArray(value)\n\t\tif (list) {\n\t\t\tJson.spend(budget, 1 + value.length, at)\n\t\t}\n\t\tconst names = list\n\t\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t\t: Object.keys(value)\n\t\tif (!list) {\n\t\t\tJson.spend(budget, 1 + names.length, at)\n\t\t}\n\t\treturn R.map(\n\t\t\tR.forEach(names, (name) =>\n\t\t\t\tchild(\n\t\t\t\t\t{ value: Json.read(value, name, at), at: Place.at(at, name) },\n\t\t\t\t\tdepth + 1,\n\t\t\t\t),\n\t\t\t),\n\t\t\t() => undefined,\n\t\t)\n\t})\n\n/**\n * A rule in the input, with its place, to decode after the tree. The place\n * shares the path of its parent: see `Place`.\n */\ninterface RuleInput {\n\treadonly input: unknown\n\treadonly place: Place.Place\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 place: Place.Place | undefined\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 place: Place.Place | undefined\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 place: Place.Place | undefined\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\trule: RuleInput,\n\tdomain: Domain.Any,\n\tbudget: Json.Budget,\n): Effect.Effect<unknown, Json.Problem> => {\n\tconst { input, place } = rule\n\tconst at = Place.at(place, \"meta\")\n\treturn Effect.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\", place, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", place)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problemAt(at, \"The domain has no Schema for meta\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(budget)({ value, at })\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: Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value).pipe(\n\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\t\tnew Json.Problem({\n\t\t\t\t\t\t\t\t\tpath: Place.pathOf(at),\n\t\t\t\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\t\t\t\tissue: error.issue,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// The decoder of a Schema in Effect calls itself for each level of\n\t\t\t\t\t\t// the value, so a deep value that the budget takes, as 5,000 levels\n\t\t\t\t\t\t// of a recursive `meta` Schema, fills its stack. That is a defect\n\t\t\t\t\t\t// of the decoder, not of the policy, so it is a problem of the\n\t\t\t\t\t\t// input here, and the policy does not load.\n\t\t\t\t\t\tEffect.catchDefect((defect) =>\n\t\t\t\t\t\t\tdefect instanceof RangeError\n\t\t\t\t\t\t\t\t? Effect.fail(\n\t\t\t\t\t\t\t\t\t\tJson.problemAt(\n\t\t\t\t\t\t\t\t\t\t\tat,\n\t\t\t\t\t\t\t\t\t\t\t\"The meta of the rule has too many levels for the Schema of meta\",\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: Effect.die(defect),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t)\n}\n\nconst decodeRule = (\n\trule: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tconst { input, place } = rule\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problemAt(place, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, place)\n\tconst tag = Json.read(input, \"_tag\", place)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problemAt(\n\t\t\tPlace.at(place, \"_tag\"),\n\t\t\t'A rule must be \"Allow\" or \"Deny\"',\n\t\t)\n\t}\n\tconst when = readOptional(input, \"when\", place, () =>\n\t\tJson.decodeAt(\n\t\t\tJson.read(input, \"when\", place),\n\t\t\tPlace.at(place, \"when\"),\n\t\t\tbudget,\n\t\t),\n\t)\n\tconst fields = readOptional(input, \"fields\", place, () =>\n\t\treadStrings(input, \"fields\", place, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", place, () =>\n\t\tJson.readString(input, \"reason\", place),\n\t)\n\tconst id = readOptional(input, \"id\", place, () =>\n\t\tJson.readString(input, \"id\", place),\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\", place, budget),\n\t\tresources: readStrings(input, \"resources\", place, 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/** A node of the input of a tree, with its place. */\ninterface TreePart {\n\treadonly input: unknown\n\treadonly at: Place.Place | undefined\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. The walk is a walk of\n * the trampoline, so a deep tree does not fill the stack.\n */\nconst readTree = (\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): ((part: TreePart) => Plan) => {\n\t// The nodes and rules of the tree that the walk has read. A tree with more\n\t// of them than `policyNodes` does not load, so the walk stops at the first\n\t// one past it, and it does not read the rest of a very large input. The\n\t// count belongs to one decode.\n\tconst count = { nodes: 0 }\n\tconst counted = (more: number, at: Place.Place | undefined): void => {\n\t\tcount.nodes += more\n\t\tconst most = budget.limits.policyNodes\n\t\tif (count.nodes > most) {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tat,\n\t\t\t\t`A policy tree has more than ${String(most)} nodes. The most is ${String(most)}.`,\n\t\t\t)\n\t\t}\n\t}\n\treturn Trampoline.adaptive<TreePart, Plan>((R, child) => (part, depth) => {\n\t\tconst { input, at } = part\n\t\tif (typeof input !== \"object\" || input === null) {\n\t\t\tthrow Json.problemAt(at, \"A node of a policy must be an object\")\n\t\t}\n\t\tJson.spend(budget, 1, at)\n\t\tcounted(1, at)\n\t\tconst below = (value: unknown, ...parts: ReadonlyArray<PropertyKey>) =>\n\t\t\tchild({ input: value, at: Place.at(at, ...parts) }, depth + 1)\n\t\tconst tag = Json.read(input, \"_tag\", at)\n\t\tswitch (tag) {\n\t\t\tcase \"Rules\": {\n\t\t\t\tconst algorithm = Json.readString(input, \"algorithm\", at)\n\t\t\t\tif (!PolicyNodes.isAlgorithm(algorithm)) {\n\t\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\t\tPlace.at(at, \"algorithm\"),\n\t\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst list = Json.readList(input, \"rules\", at, budget)\n\t\t\t\tcounted(list.length, at)\n\t\t\t\tconst found = Arr.map(\n\t\t\t\t\tlist,\n\t\t\t\t\t(rule, index) =>\n\t\t\t\t\t\trules.push({\n\t\t\t\t\t\t\tinput: rule,\n\t\t\t\t\t\t\tplace: Place.at(at, \"rules\", index),\n\t\t\t\t\t\t}) - 1,\n\t\t\t\t)\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\tsize: 1 + found.length,\n\t\t\t\t\t\talgorithm,\n\t\t\t\t\t\trules: found,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(\n\t\t\t\t\t\tJson.readList(input, \"policies\", at, budget),\n\t\t\t\t\t\t(value, index) => below(value, \"policies\", index),\n\t\t\t\t\t),\n\t\t\t\t\t(policies) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: tag,\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\t\t\t\tpolicies,\n\t\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\": {\n\t\t\t\tconst cap = Json.read(input, \"cap\", at)\n\t\t\t\tconst policy = Json.read(input, \"policy\", at)\n\t\t\t\treturn R.flatMap(below(cap, \"cap\"), (capPlan) =>\n\t\t\t\t\tR.map(below(policy, \"policy\"), (policyPlan) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: 1 + capPlan.size + policyPlan.size,\n\t\t\t\t\t\t\tcap: capPlan,\n\t\t\t\t\t\t\tpolicy: policyPlan,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow Json.problemAt(Place.at(at, \"_tag\"), \"The node has no known tag\")\n\t\t}\n\t})\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (budget: Json.Budget, plan: Plan): Plan => {\n\tconst most = budget.limits.policyNodes\n\tif (plan.size > most) {\n\t\tthrow Json.problemAt(\n\t\t\tplan.place,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(most)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/**\n * Makes the nodes of a plan, with the decoded rules. It is a walk of the\n * trampoline, so a deep plan does not fill the stack.\n */\nconst build = (\n\tbudget: Json.Budget,\n\trules: ReadonlyArray<Rule.Rule>,\n): ((plan: Plan) => Node) =>\n\tTrampoline.adaptive<Plan, Node>((R, child) => (plan, depth) => {\n\t\t// A node past a limit is a problem at its place. The constructor checks\n\t\t// the ceilings, and the decoder checks the limits of the domain, with\n\t\t// the nodes and values of the conditions.\n\t\tconst make = (made: () => Node): Node => {\n\t\t\tconst node = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn made()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\t\t\t\tthrow Json.problemAt(plan.place, error.message)\n\t\t\t\t\t}\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t})()\n\t\t\tconst most = budget.limits.policyNodes\n\t\t\tif (node.size > most) {\n\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\tplan.place,\n\t\t\t\t\t`A policy tree has ${String(node.size)} nodes. The most is ${String(most)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn node\n\t\t}\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew PolicyNodes.Rules({\n\t\t\t\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t\t\t\t}),\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\tconst tag = plan._tag\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(plan.policies, (inner) => child(inner, depth + 1)),\n\t\t\t\t\t(policies) => make(() => new PolicyNodes[tag](policies)),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn R.flatMap(child(plan.cap, depth + 1), (cap) =>\n\t\t\t\t\tR.map(child(plan.policy, depth + 1), (policy) =>\n\t\t\t\t\t\tmake(() => new PolicyNodes.Ceiling({ cap, policy })),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t}\n\t})\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, limits: domain.limits }\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(() =>\n\t\t\treadTree(budget, inputs)({ input, at: undefined }),\n\t\t)\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(budget, rules)(plan))\n\t})\n"],"mappings":"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"}
@@ -141,18 +141,27 @@ const isNode = (u) => u instanceof Base;
141
141
  * into the condition of each rule takes the rules from this list first, so it
142
142
  * does not go down the levels of the tree and of a condition at once.
143
143
  */
144
- const rulesOf = (root) => run(root.depth, (R) => {
145
- const go = (node) => {
146
- switch (node._tag) {
147
- case "Rules": return R.succeed(node.rules);
148
- case "All":
149
- case "Any":
150
- case "FirstOf": return R.map(R.forEach(node.policies, (child) => R.suspend(() => go(child))), Arr.flatten);
151
- case "Ceiling": return R.map(R.forEach([node.cap, node.policy], (child) => R.suspend(() => go(child))), Arr.flatten);
152
- }
153
- };
154
- return go(root);
155
- });
144
+ const rulesOf = (root) => {
145
+ const rules = [];
146
+ run(root.depth, (R) => {
147
+ const children = (nodes) => R.map(R.forEach(nodes, (child) => R.suspend(() => go(child))), () => void 0);
148
+ const go = (node) => {
149
+ switch (node._tag) {
150
+ case "Rules":
151
+ Arr.forEach(node.rules, (rule) => {
152
+ rules.push(rule);
153
+ });
154
+ return R.succeed(void 0);
155
+ case "All":
156
+ case "Any":
157
+ case "FirstOf": return children(node.policies);
158
+ case "Ceiling": return children([node.cap, node.policy]);
159
+ }
160
+ };
161
+ return go(root);
162
+ });
163
+ return rules;
164
+ };
156
165
  export { Algorithm, All, Any, Ceiling, FirstOf, Rules, isAlgorithm, isNode, policy_nodes_exports, rulesOf };
157
166
 
158
167
  //# sourceMappingURL=policy-nodes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"policy-nodes.js","names":["Limits.exceeded","Nodes.isNode","Rule.isRule","Trampoline.run"],"sources":["../../src/internal/policy-nodes.ts"],"sourcesContent":["/**\n * The nodes of a policy tree. As with the nodes of a condition, each node is\n * a class, only its constructor makes one, and it works out three facts once:\n * - `size`: the number of nodes, with each rule and each node of its condition,\n * and a shared child counted for each place;\n * - `depth`: the number of levels of the tree;\n * - `opaque`: a rule of the tree has a predicate.\n *\n * The constructor stops a tree past `policyNodes` or `policyDepth`. So a walk\n * over any tree, such as a decision, needs no memo.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Schema from \"effect/Schema\"\nimport * as Rule from \"./rule.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\n/** The combining algorithms of a list of rules. The decoder reads this list too. */\nexport const Algorithm = Schema.Literals([\n\t\"denyOverrides\",\n\t\"permitOverrides\",\n\t\"firstApplicable\",\n\t\"lastApplicable\",\n])\n\nexport type Algorithm = typeof Algorithm.Type\n\n/**\n * Tests if a value is a combining algorithm. It reads the list of the Schema\n * with `includes`, as `Nodes.isCompareOperator` does.\n */\nexport const isAlgorithm = (u: unknown): u is Algorithm =>\n\t(Algorithm.literals as ReadonlyArray<unknown>).includes(u)\n\ninterface Facts {\n\treadonly size: number\n\treadonly depth: number\n\treadonly opaque: boolean\n}\n\n/** Checks the facts of a node against the limits. */\nconst checked = (facts: Facts): Facts => {\n\t// A node does not know its domain, so it checks the ceiling. A policy\n\t// checks the limit of its domain.\n\tif (facts.size > Limits.ceilings.policyNodes) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A policy tree\",\n\t\t\t\"policyNodes\",\n\t\t\t\"nodes\",\n\t\t\tfacts.size,\n\t\t\tLimits.ceilings.policyNodes,\n\t\t)\n\t}\n\treturn facts\n}\n\nconst factsOf = (children: ReadonlyArray<Node>): Facts =>\n\tchecked({\n\t\tsize: Arr.reduce(children, 1, (total, child) => total + child.size),\n\t\tdepth:\n\t\t\t1 + Arr.reduce(children, 0, (top, child) => Math.max(top, child.depth)),\n\t\topaque: Arr.some(children, (child) => child.opaque),\n\t})\n\n/** The base of every node. It keeps the facts. */\nabstract class Base extends Data.Class {\n\treadonly #facts: Facts\n\n\tconstructor(props: object, facts: Facts) {\n\t\tsuper(props as never)\n\t\tthis.#facts = facts\n\t}\n\n\t/** The number of nodes, with the rules and their conditions. */\n\tget size(): number {\n\t\treturn this.#facts.size\n\t}\n\n\t/** The number of levels of the tree. */\n\tget depth(): number {\n\t\treturn this.#facts.depth\n\t}\n\n\t/** A rule of the tree has a predicate. */\n\tget opaque(): boolean {\n\t\treturn this.#facts.opaque\n\t}\n}\n\n/**\n * The condition of a rule. A value that is not a node counts as one node\n * here: the binding of the rule gives the issue for it, before any walk.\n */\nconst whenOf = (rule: Rule.Rule): Nodes.Node | undefined =>\n\tNodes.isNode(rule.when) ? rule.when : undefined\n\n/** A list of rules with a combining algorithm. */\nexport class Rules extends Base {\n\treadonly _tag = \"Rules\"\n\tdeclare readonly algorithm: Algorithm\n\tdeclare readonly rules: ReadonlyArray<Rule.Rule>\n\tconstructor(props: {\n\t\treadonly algorithm: Algorithm\n\t\treadonly rules: ReadonlyArray<Rule.Rule>\n\t}) {\n\t\tif (!isAlgorithm(props.algorithm)) {\n\t\t\tthrow new Error(\n\t\t\t\t`\"${String(props.algorithm)}\" is not a combining algorithm`,\n\t\t\t)\n\t\t}\n\t\tconst rules = Object.freeze(Array.from(props.rules))\n\t\t// A value that is not a rule, such as a copy of a rule made with a\n\t\t// spread, may keep the type fields of another rule, and it does not\n\t\t// work in a generator. It is a mistake in the code, so it is a defect.\n\t\tif (!Arr.every(rules, Rule.isRule)) {\n\t\t\tthrow new Error(\"A value in the list is not a rule\")\n\t\t}\n\t\tconst conditions = Arr.map(rules, whenOf)\n\t\tsuper(\n\t\t\t{ algorithm: props.algorithm, rules },\n\t\t\tchecked({\n\t\t\t\tsize: Arr.reduce(\n\t\t\t\t\tconditions,\n\t\t\t\t\t1,\n\t\t\t\t\t// A condition counts its nodes and the values of its tests.\n\t\t\t\t\t(total, when) => total + 1 + (when?.weight ?? 0),\n\t\t\t\t),\n\t\t\t\tdepth: 1,\n\t\t\t\topaque: Arr.some(conditions, (when) => when?.opaque ?? false),\n\t\t\t}),\n\t\t)\n\t}\n}\n\nconst checkChildren = (\n\tpolicies: ReadonlyArray<unknown>,\n): ReadonlyArray<Node> => {\n\tconst children = Array.from(policies)\n\tif (!Arr.every(children, isNode)) {\n\t\tthrow new Error(\"A child of a policy tree is not a node of a policy\")\n\t}\n\treturn Object.freeze(children)\n}\n\n/** Every policy must allow, and one deny is enough to deny. */\nexport class All extends Base {\n\treadonly _tag = \"All\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** One allow is enough. Without an allow, one deny gives a deny. */\nexport class Any extends Base {\n\treadonly _tag = \"Any\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** The first policy that allows or denies gives the answer. */\nexport class FirstOf extends Base {\n\treadonly _tag = \"FirstOf\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** A deny from `cap` always wins. An allow from `cap` does nothing. */\nexport class Ceiling extends Base {\n\treadonly _tag = \"Ceiling\"\n\tdeclare readonly cap: Node\n\tdeclare readonly policy: Node\n\tconstructor(props: { readonly cap: Node; readonly policy: Node }) {\n\t\tconst [cap, policy] = checkChildren([props.cap, props.policy])\n\t\tsuper({ cap, policy }, factsOf([cap!, policy!]))\n\t}\n}\n\n/** A node of a policy tree. */\nexport type Node = Rules | All | Any | FirstOf | Ceiling\n\n/** Tests if a value is a node of a policy tree. */\nexport const isNode = (u: unknown): u is Node => u instanceof Base\n\n/**\n * The rules of a tree, in order, once for each place. A walk that also goes\n * into the condition of each rule takes the rules from this list first, so it\n * does not go down the levels of the tree and of a condition at once.\n */\nexport const rulesOf = (root: Node): ReadonlyArray<Rule.Rule> =>\n\t// The walk takes the steps of a trampoline, so a deep tree does not fill\n\t// the stack.\n\tTrampoline.run(root.depth, (R) => {\n\t\tconst go = (node: Node): Trampoline.Step<ReadonlyArray<Rule.Rule>> => {\n\t\t\tswitch (node._tag) {\n\t\t\t\tcase \"Rules\":\n\t\t\t\t\treturn R.succeed(node.rules)\n\t\t\t\tcase \"All\":\n\t\t\t\tcase \"Any\":\n\t\t\t\tcase \"FirstOf\":\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach(node.policies, (child) => R.suspend(() => go(child))),\n\t\t\t\t\t\tArr.flatten,\n\t\t\t\t\t)\n\t\t\t\tcase \"Ceiling\":\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach([node.cap, node.policy], (child) =>\n\t\t\t\t\t\t\tR.suspend(() => go(child)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tArr.flatten,\n\t\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn go(root)\n\t})\n"],"mappings":"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"}
1
+ {"version":3,"file":"policy-nodes.js","names":["Limits.exceeded","Nodes.isNode","Rule.isRule"],"sources":["../../src/internal/policy-nodes.ts"],"sourcesContent":["/**\n * The nodes of a policy tree. As with the nodes of a condition, each node is\n * a class, only its constructor makes one, and it works out three facts once:\n * - `size`: the number of nodes, with each rule and each node of its condition,\n * and a shared child counted for each place;\n * - `depth`: the number of levels of the tree;\n * - `opaque`: a rule of the tree has a predicate.\n *\n * The constructor stops a tree past `policyNodes` or `policyDepth`. So a walk\n * over any tree, such as a decision, needs no memo.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Schema from \"effect/Schema\"\nimport * as Rule from \"./rule.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\n/** The combining algorithms of a list of rules. The decoder reads this list too. */\nexport const Algorithm = Schema.Literals([\n\t\"denyOverrides\",\n\t\"permitOverrides\",\n\t\"firstApplicable\",\n\t\"lastApplicable\",\n])\n\nexport type Algorithm = typeof Algorithm.Type\n\n/**\n * Tests if a value is a combining algorithm. It reads the list of the Schema\n * with `includes`, as `Nodes.isCompareOperator` does.\n */\nexport const isAlgorithm = (u: unknown): u is Algorithm =>\n\t(Algorithm.literals as ReadonlyArray<unknown>).includes(u)\n\ninterface Facts {\n\treadonly size: number\n\treadonly depth: number\n\treadonly opaque: boolean\n}\n\n/** Checks the facts of a node against the limits. */\nconst checked = (facts: Facts): Facts => {\n\t// A node does not know its domain, so it checks the ceiling. A policy\n\t// checks the limit of its domain.\n\tif (facts.size > Limits.ceilings.policyNodes) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A policy tree\",\n\t\t\t\"policyNodes\",\n\t\t\t\"nodes\",\n\t\t\tfacts.size,\n\t\t\tLimits.ceilings.policyNodes,\n\t\t)\n\t}\n\treturn facts\n}\n\nconst factsOf = (children: ReadonlyArray<Node>): Facts =>\n\tchecked({\n\t\tsize: Arr.reduce(children, 1, (total, child) => total + child.size),\n\t\tdepth:\n\t\t\t1 + Arr.reduce(children, 0, (top, child) => Math.max(top, child.depth)),\n\t\topaque: Arr.some(children, (child) => child.opaque),\n\t})\n\n/** The base of every node. It keeps the facts. */\nabstract class Base extends Data.Class {\n\treadonly #facts: Facts\n\n\tconstructor(props: object, facts: Facts) {\n\t\tsuper(props as never)\n\t\tthis.#facts = facts\n\t}\n\n\t/** The number of nodes, with the rules and their conditions. */\n\tget size(): number {\n\t\treturn this.#facts.size\n\t}\n\n\t/** The number of levels of the tree. */\n\tget depth(): number {\n\t\treturn this.#facts.depth\n\t}\n\n\t/** A rule of the tree has a predicate. */\n\tget opaque(): boolean {\n\t\treturn this.#facts.opaque\n\t}\n}\n\n/**\n * The condition of a rule. A value that is not a node counts as one node\n * here: the binding of the rule gives the issue for it, before any walk.\n */\nconst whenOf = (rule: Rule.Rule): Nodes.Node | undefined =>\n\tNodes.isNode(rule.when) ? rule.when : undefined\n\n/** A list of rules with a combining algorithm. */\nexport class Rules extends Base {\n\treadonly _tag = \"Rules\"\n\tdeclare readonly algorithm: Algorithm\n\tdeclare readonly rules: ReadonlyArray<Rule.Rule>\n\tconstructor(props: {\n\t\treadonly algorithm: Algorithm\n\t\treadonly rules: ReadonlyArray<Rule.Rule>\n\t}) {\n\t\tif (!isAlgorithm(props.algorithm)) {\n\t\t\tthrow new Error(\n\t\t\t\t`\"${String(props.algorithm)}\" is not a combining algorithm`,\n\t\t\t)\n\t\t}\n\t\tconst rules = Object.freeze(Array.from(props.rules))\n\t\t// A value that is not a rule, such as a copy of a rule made with a\n\t\t// spread, may keep the type fields of another rule, and it does not\n\t\t// work in a generator. It is a mistake in the code, so it is a defect.\n\t\tif (!Arr.every(rules, Rule.isRule)) {\n\t\t\tthrow new Error(\"A value in the list is not a rule\")\n\t\t}\n\t\tconst conditions = Arr.map(rules, whenOf)\n\t\tsuper(\n\t\t\t{ algorithm: props.algorithm, rules },\n\t\t\tchecked({\n\t\t\t\tsize: Arr.reduce(\n\t\t\t\t\tconditions,\n\t\t\t\t\t1,\n\t\t\t\t\t// A condition counts its nodes and the values of its tests.\n\t\t\t\t\t(total, when) => total + 1 + (when?.weight ?? 0),\n\t\t\t\t),\n\t\t\t\tdepth: 1,\n\t\t\t\topaque: Arr.some(conditions, (when) => when?.opaque ?? false),\n\t\t\t}),\n\t\t)\n\t}\n}\n\nconst checkChildren = (\n\tpolicies: ReadonlyArray<unknown>,\n): ReadonlyArray<Node> => {\n\tconst children = Array.from(policies)\n\tif (!Arr.every(children, isNode)) {\n\t\tthrow new Error(\"A child of a policy tree is not a node of a policy\")\n\t}\n\treturn Object.freeze(children)\n}\n\n/** Every policy must allow, and one deny is enough to deny. */\nexport class All extends Base {\n\treadonly _tag = \"All\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** One allow is enough. Without an allow, one deny gives a deny. */\nexport class Any extends Base {\n\treadonly _tag = \"Any\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** The first policy that allows or denies gives the answer. */\nexport class FirstOf extends Base {\n\treadonly _tag = \"FirstOf\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** A deny from `cap` always wins. An allow from `cap` does nothing. */\nexport class Ceiling extends Base {\n\treadonly _tag = \"Ceiling\"\n\tdeclare readonly cap: Node\n\tdeclare readonly policy: Node\n\tconstructor(props: { readonly cap: Node; readonly policy: Node }) {\n\t\tconst [cap, policy] = checkChildren([props.cap, props.policy])\n\t\tsuper({ cap, policy }, factsOf([cap!, policy!]))\n\t}\n}\n\n/** A node of a policy tree. */\nexport type Node = Rules | All | Any | FirstOf | Ceiling\n\n/** Tests if a value is a node of a policy tree. */\nexport const isNode = (u: unknown): u is Node => u instanceof Base\n\n/**\n * The rules of a tree, in order, once for each place. A walk that also goes\n * into the condition of each rule takes the rules from this list first, so it\n * does not go down the levels of the tree and of a condition at once.\n */\nexport const rulesOf = (root: Node): ReadonlyArray<Rule.Rule> => {\n\t// The rules go into one list, in order. A walk that joined the lists of\n\t// the children at each node would copy the list of a deep tree at each\n\t// level. The list belongs to this walk.\n\tconst rules: Array<Rule.Rule> = []\n\t// The walk takes the steps of a trampoline, so a deep tree does not fill\n\t// the stack.\n\tTrampoline.run(root.depth, (R) => {\n\t\tconst children = (nodes: ReadonlyArray<Node>) =>\n\t\t\tR.map(\n\t\t\t\tR.forEach(nodes, (child) => R.suspend(() => go(child))),\n\t\t\t\t() => undefined,\n\t\t\t)\n\t\tconst go = (node: Node): Trampoline.Step<undefined> => {\n\t\t\tswitch (node._tag) {\n\t\t\t\tcase \"Rules\":\n\t\t\t\t\tArr.forEach(node.rules, (rule) => {\n\t\t\t\t\t\trules.push(rule)\n\t\t\t\t\t})\n\t\t\t\t\treturn R.succeed(undefined)\n\t\t\t\tcase \"All\":\n\t\t\t\tcase \"Any\":\n\t\t\t\tcase \"FirstOf\":\n\t\t\t\t\treturn children(node.policies)\n\t\t\t\tcase \"Ceiling\":\n\t\t\t\t\treturn children([node.cap, node.policy])\n\t\t\t}\n\t\t}\n\t\treturn go(root)\n\t})\n\treturn rules\n}\n"],"mappings":"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"}
@@ -8,12 +8,12 @@ import { Ceiling, Rules, policy_nodes_exports, rulesOf } from "./policy-nodes.js
8
8
  import { decode, encode, syncMeta, toJson } from "./policy-json.js";
9
9
  import * as Arr from "effect/Array";
10
10
  import * as Effect from "effect/Effect";
11
+ import * as Option from "effect/Option";
11
12
  import * as Schema from "effect/Schema";
12
13
  import * as Data from "effect/Data";
13
14
  import { dual } from "effect/Function";
14
15
  import * as SchemaIssue from "effect/SchemaIssue";
15
16
  import * as SchemaTransformation from "effect/SchemaTransformation";
16
- import * as Option from "effect/Option";
17
17
  // src/internal/policy.ts
18
18
  /**
19
19
  * The class of every policy value. Two policies of one domain with the same
@@ -4,9 +4,9 @@ import { always, never } from "./nodes.js";
4
4
  import { and, keyOf, not, or, simplify, testCompare } from "./algebra.js";
5
5
  import { anyKinds, lookup } from "./catalog.js";
6
6
  import * as Arr from "effect/Array";
7
+ import * as Option from "effect/Option";
7
8
  import * as Data from "effect/Data";
8
9
  import { isObject } from "effect/Predicate";
9
- import * as Option from "effect/Option";
10
10
  // src/internal/search.ts
11
11
  /**
12
12
  * The search for a value that makes a condition true. Each test of a
@@ -145,20 +145,26 @@ const fix = (root, field, value, search) => run(root.depth, (R) => {
145
145
  return go(root);
146
146
  });
147
147
  /** The fields of the tests of a condition, once for each test. */
148
- const testedFields = (root) => run(root.depth, (R) => {
149
- const go = (node) => {
150
- switch (node._tag) {
151
- case "Compare":
152
- case "In":
153
- case "Exists": return R.succeed([node.field]);
154
- case "And":
155
- case "Or": return R.map(R.forEach(node.conditions, (child) => R.suspend(() => go(child))), Arr.flatten);
156
- case "Not": return R.suspend(() => go(node.condition));
157
- default: return R.succeed([]);
158
- }
159
- };
160
- return go(root);
161
- });
148
+ const testedFields = (root) => {
149
+ const fields = [];
150
+ run(root.depth, (R) => {
151
+ const go = (node) => {
152
+ switch (node._tag) {
153
+ case "Compare":
154
+ case "In":
155
+ case "Exists":
156
+ fields.push(node.field);
157
+ return R.succeed(void 0);
158
+ case "And":
159
+ case "Or": return R.map(R.forEach(node.conditions, (child) => R.suspend(() => go(child))), () => void 0);
160
+ case "Not": return R.suspend(() => go(node.condition));
161
+ default: return R.succeed(void 0);
162
+ }
163
+ };
164
+ return go(root);
165
+ });
166
+ return fields;
167
+ };
162
168
  /**
163
169
  * Picks the next field to split on. A field comes before the fields below it,
164
170
  * so a value of a field can fix them. Then a field in more tests comes first,
@@ -240,27 +246,33 @@ const exampleOf = (assignment) => Arr.reduce(assignment, {}, (root, [field, valu
240
246
  * belongs to one search.
241
247
  */
242
248
  const constantsOf = (node) => {
243
- const pairs = run(node.depth, (R) => {
249
+ const all = /* @__PURE__ */ new Map();
250
+ const add = (field, values) => {
251
+ const known = all.get(field) ?? /* @__PURE__ */ new Set();
252
+ Arr.forEach(values, (value) => known.add(value));
253
+ all.set(field, known);
254
+ };
255
+ run(node.depth, (R) => {
244
256
  const go = (current) => {
245
257
  switch (current._tag) {
246
- case "Compare": return R.succeed([[current.field, [literalOf(current.value)]]]);
247
- case "In": return R.succeed([[current.field, Arr.map(current.values, literalOf)]]);
248
- case "Exists": return R.succeed([[current.field, []]]);
258
+ case "Compare":
259
+ add(current.field, [literalOf(current.value)]);
260
+ return R.succeed(void 0);
261
+ case "In":
262
+ add(current.field, Arr.map(current.values, literalOf));
263
+ return R.succeed(void 0);
264
+ case "Exists":
265
+ add(current.field, []);
266
+ return R.succeed(void 0);
249
267
  case "Under": throw new Error("Condition.satisfiable needs a condition with no Under nodes. Call Condition.partial first.");
250
268
  case "And":
251
- case "Or": return R.map(R.forEach(current.conditions, (child) => R.suspend(() => go(child))), Arr.flatten);
269
+ case "Or": return R.map(R.forEach(current.conditions, (child) => R.suspend(() => go(child))), () => void 0);
252
270
  case "Not": return R.suspend(() => go(current.condition));
253
- default: return R.succeed([]);
271
+ default: return R.succeed(void 0);
254
272
  }
255
273
  };
256
274
  return go(node);
257
275
  });
258
- const all = /* @__PURE__ */ new Map();
259
- Arr.forEach(pairs, ([field, values]) => {
260
- const known = all.get(field) ?? /* @__PURE__ */ new Set();
261
- Arr.forEach(values, (value) => known.add(value));
262
- all.set(field, known);
263
- });
264
276
  return all;
265
277
  };
266
278
  /** Gives the kinds of each field of a catalog. A field that it does not have may hold any value. */