@nmnmcc/ability 2.0.5 → 2.0.6

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 (49) hide show
  1. package/dist/Condition.d.ts +4 -4
  2. package/dist/Condition.d.ts.map +1 -1
  3. package/dist/Condition.js.map +1 -1
  4. package/dist/Domain.d.ts +1 -1
  5. package/dist/Policy.d.ts +2 -2
  6. package/dist/Policy.d.ts.map +1 -1
  7. package/dist/Policy.js.map +1 -1
  8. package/dist/internal/algebra.js +28 -6
  9. package/dist/internal/algebra.js.map +1 -1
  10. package/dist/internal/authorizer.d.ts.map +1 -1
  11. package/dist/internal/authorizer.js +37 -14
  12. package/dist/internal/authorizer.js.map +1 -1
  13. package/dist/internal/bind.js +7 -8
  14. package/dist/internal/bind.js.map +1 -1
  15. package/dist/internal/catalog.d.ts +6 -0
  16. package/dist/internal/catalog.d.ts.map +1 -1
  17. package/dist/internal/catalog.js +12 -9
  18. package/dist/internal/catalog.js.map +1 -1
  19. package/dist/internal/decide.js +1 -1
  20. package/dist/internal/domain.js +1 -1
  21. package/dist/internal/json.js +2 -10
  22. package/dist/internal/json.js.map +1 -1
  23. package/dist/internal/nodes.d.ts +4 -1
  24. package/dist/internal/nodes.d.ts.map +1 -1
  25. package/dist/internal/nodes.js +17 -7
  26. package/dist/internal/nodes.js.map +1 -1
  27. package/dist/internal/patterns.js +1 -1
  28. package/dist/internal/patterns.js.map +1 -1
  29. package/dist/internal/policy-json.js +2 -8
  30. package/dist/internal/policy-json.js.map +1 -1
  31. package/dist/internal/policy-nodes.d.ts +4 -1
  32. package/dist/internal/policy-nodes.d.ts.map +1 -1
  33. package/dist/internal/policy-nodes.js +13 -4
  34. package/dist/internal/policy-nodes.js.map +1 -1
  35. package/dist/internal/search.js +4 -4
  36. package/dist/internal/search.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/Condition.ts +5 -2
  39. package/src/Policy.ts +1 -2
  40. package/src/internal/algebra.ts +44 -8
  41. package/src/internal/authorizer.ts +56 -14
  42. package/src/internal/bind.ts +18 -14
  43. package/src/internal/catalog.ts +40 -27
  44. package/src/internal/json.ts +2 -11
  45. package/src/internal/nodes.ts +21 -11
  46. package/src/internal/patterns.ts +7 -3
  47. package/src/internal/policy-json.ts +2 -9
  48. package/src/internal/policy-nodes.ts +14 -6
  49. package/src/internal/search.ts +13 -10
@@ -1 +1 @@
1
- {"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Json.readList","Json.problem","read","Json.read","Place.pathOf","Json.Problem","Json.decodeAt","Json.readString","Rule.make","Place.at","Limits.policyNodes","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling","Limits.LimitExceeded"],"sources":["../../src/internal/policy-json.ts"],"sourcesContent":["/**\n * The JSON form of a policy tree. The decoder is a plain walk, as the decoder\n * of a condition is, and the two share one budget of `jsonNodes` objects and\n * items. It makes each node with its constructor, so the limits of the nodes\n * hold for a decoded tree too. The binding of the rules to the domain comes\n * after, in the Schema of the policy.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport * as Rule from \"./rule.ts\"\nimport * as Json from \"./json.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\n\ntype Node = PolicyNodes.Node\n\n/**\n * The codec of the `meta` Schema of a domain to and from JSON. The JSON form\n * of a policy must stay the same through `JSON.stringify` and `JSON.parse`, so\n * a `Date` in `meta`, for one, becomes a string in it, and a string again\n * when it is decoded.\n */\nconst metaCodec = (meta: Schema.Top): Schema.Codec<unknown, unknown> =>\n\tSchema.toCodecJson(meta) as unknown as Schema.Codec<unknown, unknown>\n\n/** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */\nexport type MetaJson = (rule: Rule.Rule) => unknown\n\n/**\n * Gives the JSON form of the `meta` of each rule at once, with the `meta`\n * Schema of a domain. `JSON.stringify` needs it at once, so the Schema must\n * not need an async step there.\n */\nexport const syncMeta =\n\t(meta: Schema.Top | undefined): MetaJson =>\n\t(rule) =>\n\t\trule.meta === undefined || meta === undefined\n\t\t\t? undefined\n\t\t\t: Schema.encodeUnknownSync(metaCodec(meta))(rule.meta)\n\n/** The JSON form of the condition of each rule, by the rule. */\ntype WhenJson = (rule: Rule.Rule) => unknown\n\n/** Gives the JSON form of a rule. The domain of a rule is not in it. */\nconst ruleJson = (\n\trule: Rule.Rule,\n\tmetaJson: MetaJson,\n\twhenJson: WhenJson,\n): unknown => {\n\tconst meta = metaJson(rule)\n\treturn {\n\t\t_tag: rule._tag,\n\t\tactions: rule.actions,\n\t\tresources: rule.resources,\n\t\t...(rule.when === undefined ? {} : { when: whenJson(rule) }),\n\t\t...(rule.fields === undefined ? {} : { fields: rule.fields }),\n\t\t...(rule.reason === undefined ? {} : { reason: rule.reason }),\n\t\t...(rule.id === undefined ? {} : { id: rule.id }),\n\t\t// The `meta` of a rule is a decoded value. Its JSON form comes from the\n\t\t// `meta` Schema of the domain.\n\t\t...(meta === undefined ? {} : { meta }),\n\t}\n}\n\n/**\n * Gives the JSON form of a policy tree with no predicate. A condition with a\n * predicate has no JSON form, so it throws an error. The conditions are\n * written first, from a flat list of the rules, so the walk does not go down\n * the levels of the tree and of a condition at once.\n */\nexport const toJson = (node: Node, meta: MetaJson): unknown => {\n\t// A native map, because it must find a rule by the rule itself: two rules\n\t// with the same data are `Equal`.\n\tconst whens = new Map<Rule.Rule, unknown>(\n\t\tArr.map(PolicyNodes.rulesOf(node), (rule) => [\n\t\t\trule,\n\t\t\trule.when === undefined ? undefined : Json.toJson(rule.when as never),\n\t\t]),\n\t)\n\tconst tree = (current: Node): unknown => {\n\t\tswitch (current._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: current._tag,\n\t\t\t\t\tpolicies: Arr.map(current.policies, tree),\n\t\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\tcap: tree(current.cap),\n\t\t\t\t\tpolicy: tree(current.policy),\n\t\t\t\t}\n\t\t}\n\t}\n\treturn tree(node)\n}\n\n/**\n * Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the\n * domain may need an async step, so each `meta` is encoded as an Effect.\n */\nexport const encode = (\n\tnode: Node,\n\tmeta: Schema.Top | undefined,\n): Effect.Effect<unknown, Schema.SchemaError> =>\n\tEffect.gen(function* () {\n\t\tconst rules = Arr.filter(\n\t\t\tPolicyNodes.rulesOf(node),\n\t\t\t(rule) => rule.meta !== undefined,\n\t\t)\n\t\tconst encoded =\n\t\t\tmeta === undefined\n\t\t\t\t? []\n\t\t\t\t: yield* Effect.forEach(rules, (rule) =>\n\t\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t\tSchema.encodeUnknownEffect(metaCodec(meta))(rule.meta),\n\t\t\t\t\t\t\t(json) => [rule, json] as const,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t// A native map, because it must find a rule by the rule itself: two\n\t\t// rules with the same data are `Equal`, and each keeps its own form.\n\t\tconst forms = new Map<Rule.Rule, unknown>(encoded)\n\t\treturn toJson(node, (rule) => forms.get(rule))\n\t})\n\nconst algorithms: ReadonlyArray<string> = [\n\t\"denyOverrides\",\n\t\"permitOverrides\",\n\t\"firstApplicable\",\n\t\"lastApplicable\",\n]\n\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, path, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problem([...path, name, index], \"The item must be a string\")\n\t\t}\n\t\treturn value\n\t})\n\n/**\n * Reads a field that a rule may leave out. A field that the input has only\n * from the prototype of a class is not plain data, so it is a problem: a rule\n * that loses its condition would allow more.\n */\nconst readOptional = <A>(\n\tinput: object,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tread: () => A,\n): A | undefined => {\n\tif (Object.hasOwn(input, name)) {\n\t\treturn read()\n\t}\n\t// A read of the field throws a problem when the field is not data.\n\tJson.read(input, name, path)\n\treturn undefined\n}\n\n/** Counts the objects and items of a value, as the other parts count. */\nconst spendValue = (\n\tvalue: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Json.Budget,\n): void => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn\n\t}\n\tif (depth > Limits.conditionDepth) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`The value has more than ${String(Limits.conditionDepth)} levels. The most is ${String(Limits.conditionDepth)}.`,\n\t\t)\n\t}\n\t// The length of an array counts before its items are read, so a sparse\n\t// array with a very large length stops at once.\n\tconst list = Array.isArray(value)\n\tif (list) {\n\t\tJson.spend(budget, 1 + value.length, path)\n\t}\n\tconst names = list\n\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t: Object.keys(value)\n\tif (!list) {\n\t\tJson.spend(budget, 1 + names.length, path)\n\t}\n\tArr.forEach(names, (name) =>\n\t\tspendValue(\n\t\t\tJson.read(value, name, path),\n\t\t\t[...path, name],\n\t\t\tdepth + 1,\n\t\t\tbudget,\n\t\t),\n\t)\n}\n\n/**\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 } = rule\n\tconst path = Place.pathOf(rule.place)\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\", path, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", path)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problem(\n\t\t\t\t\t\t\t\t[...path, \"meta\"],\n\t\t\t\t\t\t\t\t\"The domain has no Schema for meta\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(value, [...path, \"meta\"], 1, budget)\n\t\t\t\t\t\treturn { value, schema: domain.meta }\n\t\t\t\t\t}),\n\t\t),\n\t\t(found) =>\n\t\t\tfound === undefined\n\t\t\t\t? Effect.void\n\t\t\t\t: Effect.mapError(\n\t\t\t\t\t\tSchema.decodeUnknownEffect(metaCodec(found.schema))(found.value),\n\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\tnew Json.Problem({\n\t\t\t\t\t\t\t\tpath: [...path, \"meta\"],\n\t\t\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\t\t\tissue: error.issue,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t)\n}\n\nconst decodeRule = (\n\trule: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tconst { input } = rule\n\tconst path = Place.pathOf(rule.place)\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problem(path, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, path)\n\tconst tag = Json.read(input, \"_tag\", path)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problem([...path, \"_tag\"], 'A rule must be \"Allow\" or \"Deny\"')\n\t}\n\tconst when = readOptional(input, \"when\", path, () =>\n\t\tJson.decodeAt(Json.read(input, \"when\", path), [...path, \"when\"], budget),\n\t)\n\tconst fields = readOptional(input, \"fields\", path, () =>\n\t\treadStrings(input, \"fields\", path, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", path, () =>\n\t\tJson.readString(input, \"reason\", path),\n\t)\n\tconst id = readOptional(input, \"id\", path, () =>\n\t\tJson.readString(input, \"id\", path),\n\t)\n\t// Each rule gets the domain of the Schema, as a rule from the builder of\n\t// the domain has it.\n\treturn Rule.make({\n\t\t_tag: tag,\n\t\tactions: readStrings(input, \"actions\", path, budget),\n\t\tresources: readStrings(input, \"resources\", path, budget),\n\t\t...(when === undefined ? {} : { when: when as never }),\n\t\t...(fields === undefined ? {} : { fields }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t\t...(id === undefined ? {} : { id }),\n\t\t...(meta === undefined ? {} : { meta }),\n\t\tdomain,\n\t})\n}\n\n/**\n * Reads the shape of a tree, and puts each rule in `rules`. The rules are\n * decoded after the walk: a condition has levels of its own, and a decode\n * inside the walk would go down the levels of both. `path` is the path of the\n * node for a problem here, and `place` keeps it for later.\n */\nconst readTree = (\n\tinput: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tplace: Place.Place | undefined,\n\tdepth: number,\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): Plan => {\n\tif (depth > Limits.policyDepth) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`A policy tree has more than ${String(Limits.policyDepth)} levels. The most is ${String(Limits.policyDepth)}.`,\n\t\t)\n\t}\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problem(path, \"A node of a policy must be an object\")\n\t}\n\tJson.spend(budget, 1, path)\n\tconst child = (value: unknown, ...parts: ReadonlyArray<PropertyKey>) =>\n\t\treadTree(\n\t\t\tvalue,\n\t\t\t[...path, ...parts],\n\t\t\tPlace.at(place, ...parts),\n\t\t\tdepth + 1,\n\t\t\tbudget,\n\t\t\trules,\n\t\t)\n\tconst tag = Json.read(input, \"_tag\", path)\n\tswitch (tag) {\n\t\tcase \"Rules\": {\n\t\t\tconst algorithm = Json.readString(input, \"algorithm\", path)\n\t\t\tif (!algorithms.includes(algorithm)) {\n\t\t\t\tthrow Json.problem(\n\t\t\t\t\t[...path, \"algorithm\"],\n\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst found = Arr.map(\n\t\t\t\tJson.readList(input, \"rules\", path, budget),\n\t\t\t\t(rule, index) =>\n\t\t\t\t\trules.push({\n\t\t\t\t\t\tinput: rule,\n\t\t\t\t\t\tplace: Place.at(place, \"rules\", index),\n\t\t\t\t\t}) - 1,\n\t\t\t)\n\t\t\treturn sized(path, {\n\t\t\t\t_tag: \"Rules\",\n\t\t\t\tplace,\n\t\t\t\tsize: 1 + found.length,\n\t\t\t\talgorithm: algorithm as PolicyNodes.Algorithm,\n\t\t\t\trules: found,\n\t\t\t})\n\t\t}\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\": {\n\t\t\tconst policies = Arr.map(\n\t\t\t\tJson.readList(input, \"policies\", path, budget),\n\t\t\t\t(value, index) => child(value, \"policies\", index),\n\t\t\t)\n\t\t\treturn sized(path, {\n\t\t\t\t_tag: tag,\n\t\t\t\tplace,\n\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\tpolicies,\n\t\t\t})\n\t\t}\n\t\tcase \"Ceiling\": {\n\t\t\tconst cap = child(Json.read(input, \"cap\", path), \"cap\")\n\t\t\tconst policy = child(Json.read(input, \"policy\", path), \"policy\")\n\t\t\treturn sized(path, {\n\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\tplace,\n\t\t\t\tsize: 1 + cap.size + policy.size,\n\t\t\t\tcap,\n\t\t\t\tpolicy,\n\t\t\t})\n\t\t}\n\t\tdefault:\n\t\t\tthrow Json.problem([...path, \"_tag\"], \"The node has no known tag\")\n\t}\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (path: ReadonlyArray<PropertyKey>, plan: Plan): Plan => {\n\tif (plan.size > Limits.policyNodes) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(Limits.policyNodes)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/** Makes the nodes of a plan, with the decoded rules. */\nconst build = (plan: Plan, rules: ReadonlyArray<Rule.Rule>): Node => {\n\ttry {\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn new PolicyNodes.Rules({\n\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t})\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn new PolicyNodes[plan._tag](\n\t\t\t\t\tArr.map(plan.policies, (child) => build(child, rules)),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn new PolicyNodes.Ceiling({\n\t\t\t\t\tcap: build(plan.cap, rules),\n\t\t\t\t\tpolicy: build(plan.policy, rules),\n\t\t\t\t})\n\t\t}\n\t} catch (error) {\n\t\t// A node past a limit is a problem at its place.\n\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\tthrow Json.problem(Place.pathOf(plan.place), error.message)\n\t\t}\n\t\tthrow error\n\t}\n}\n\n/** Runs a step that may throw a problem, and puts the problem in the error. */\nconst attempt = <A>(step: () => A): Effect.Effect<A, Json.Problem> =>\n\tEffect.try({\n\t\ttry: step,\n\t\tcatch: (error) => {\n\t\t\tif (error instanceof Json.Problem) {\n\t\t\t\treturn error\n\t\t\t}\n\t\t\tthrow error\n\t\t},\n\t})\n\n/**\n * Decodes the JSON form of a policy tree for a domain. It reads the tree\n * first, then the `meta` of each rule as an Effect, then the rules, and it\n * makes the nodes last.\n */\nexport const decode = (\n\tinput: unknown,\n\tdomain: Domain.Any,\n): Effect.Effect<Node, Json.Problem> =>\n\tEffect.gen(function* () {\n\t\tconst budget: Json.Budget = { used: 0 }\n\t\t// The rules of this decode. The list belongs to this decode.\n\t\tconst inputs: Array<RuleInput> = []\n\t\tconst plan = yield* attempt(() =>\n\t\t\treadTree(input, [], undefined, 1, budget, inputs),\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(plan, rules))\n\t})\n"],"mappings":"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"}
1
+ {"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Json.readList","Json.problem","read","Json.read","Place.pathOf","Json.Problem","Json.decodeAt","Json.readString","Rule.make","Place.at","PolicyNodes.isAlgorithm","Limits.policyNodes","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling","Limits.LimitExceeded"],"sources":["../../src/internal/policy-json.ts"],"sourcesContent":["/**\n * The JSON form of a policy tree. The decoder is a plain walk, as the decoder\n * of a condition is, and the two share one budget of `jsonNodes` objects and\n * items. It makes each node with its constructor, so the limits of the nodes\n * hold for a decoded tree too. The binding of the rules to the domain comes\n * after, in the Schema of the policy.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport * as Rule from \"./rule.ts\"\nimport * as Json from \"./json.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\n\ntype Node = PolicyNodes.Node\n\n/**\n * The codec of the `meta` Schema of a domain to and from JSON. The JSON form\n * of a policy must stay the same through `JSON.stringify` and `JSON.parse`, so\n * a `Date` in `meta`, for one, becomes a string in it, and a string again\n * when it is decoded.\n */\nconst metaCodec = (meta: Schema.Top): Schema.Codec<unknown, unknown> =>\n\tSchema.toCodecJson(meta) as unknown as Schema.Codec<unknown, unknown>\n\n/** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */\nexport type MetaJson = (rule: Rule.Rule) => unknown\n\n/**\n * Gives the JSON form of the `meta` of each rule at once, with the `meta`\n * Schema of a domain. `JSON.stringify` needs it at once, so the Schema must\n * not need an async step there.\n */\nexport const syncMeta =\n\t(meta: Schema.Top | undefined): MetaJson =>\n\t(rule) =>\n\t\trule.meta === undefined || meta === undefined\n\t\t\t? undefined\n\t\t\t: Schema.encodeUnknownSync(metaCodec(meta))(rule.meta)\n\n/** The JSON form of the condition of each rule, by the rule. */\ntype WhenJson = (rule: Rule.Rule) => unknown\n\n/** Gives the JSON form of a rule. The domain of a rule is not in it. */\nconst ruleJson = (\n\trule: Rule.Rule,\n\tmetaJson: MetaJson,\n\twhenJson: WhenJson,\n): unknown => {\n\tconst meta = metaJson(rule)\n\treturn {\n\t\t_tag: rule._tag,\n\t\tactions: rule.actions,\n\t\tresources: rule.resources,\n\t\t...(rule.when === undefined ? {} : { when: whenJson(rule) }),\n\t\t...(rule.fields === undefined ? {} : { fields: rule.fields }),\n\t\t...(rule.reason === undefined ? {} : { reason: rule.reason }),\n\t\t...(rule.id === undefined ? {} : { id: rule.id }),\n\t\t// The `meta` of a rule is a decoded value. Its JSON form comes from the\n\t\t// `meta` Schema of the domain.\n\t\t...(meta === undefined ? {} : { meta }),\n\t}\n}\n\n/**\n * Gives the JSON form of a policy tree with no predicate. A condition with a\n * predicate has no JSON form, so it throws an error. The conditions are\n * written first, from a flat list of the rules, so the walk does not go down\n * the levels of the tree and of a condition at once.\n */\nexport const toJson = (node: Node, meta: MetaJson): unknown => {\n\t// A native map, because it must find a rule by the rule itself: two rules\n\t// with the same data are `Equal`.\n\tconst whens = new Map<Rule.Rule, unknown>(\n\t\tArr.map(PolicyNodes.rulesOf(node), (rule) => [\n\t\t\trule,\n\t\t\trule.when === undefined ? undefined : Json.toJson(rule.when as never),\n\t\t]),\n\t)\n\tconst tree = (current: Node): unknown => {\n\t\tswitch (current._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: current._tag,\n\t\t\t\t\tpolicies: Arr.map(current.policies, tree),\n\t\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\tcap: tree(current.cap),\n\t\t\t\t\tpolicy: tree(current.policy),\n\t\t\t\t}\n\t\t}\n\t}\n\treturn tree(node)\n}\n\n/**\n * Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the\n * domain may need an async step, so each `meta` is encoded as an Effect.\n */\nexport const encode = (\n\tnode: Node,\n\tmeta: Schema.Top | undefined,\n): Effect.Effect<unknown, Schema.SchemaError> =>\n\tEffect.gen(function* () {\n\t\tconst rules = Arr.filter(\n\t\t\tPolicyNodes.rulesOf(node),\n\t\t\t(rule) => rule.meta !== undefined,\n\t\t)\n\t\tconst encoded =\n\t\t\tmeta === undefined\n\t\t\t\t? []\n\t\t\t\t: yield* Effect.forEach(rules, (rule) =>\n\t\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t\tSchema.encodeUnknownEffect(metaCodec(meta))(rule.meta),\n\t\t\t\t\t\t\t(json) => [rule, json] as const,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t// A native map, because it must find a rule by the rule itself: two\n\t\t// rules with the same data are `Equal`, and each keeps its own form.\n\t\tconst forms = new Map<Rule.Rule, unknown>(encoded)\n\t\treturn toJson(node, (rule) => forms.get(rule))\n\t})\n\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, path, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problem([...path, name, index], \"The item must be a string\")\n\t\t}\n\t\treturn value\n\t})\n\n/**\n * Reads a field that a rule may leave out. A field that the input has only\n * from the prototype of a class is not plain data, so it is a problem: a rule\n * that loses its condition would allow more.\n */\nconst readOptional = <A>(\n\tinput: object,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tread: () => A,\n): A | undefined => {\n\tif (Object.hasOwn(input, name)) {\n\t\treturn read()\n\t}\n\t// A read of the field throws a problem when the field is not data.\n\tJson.read(input, name, path)\n\treturn undefined\n}\n\n/** Counts the objects and items of a value, as the other parts count. */\nconst spendValue = (\n\tvalue: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Json.Budget,\n): void => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn\n\t}\n\tif (depth > Limits.conditionDepth) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`The value has more than ${String(Limits.conditionDepth)} levels. The most is ${String(Limits.conditionDepth)}.`,\n\t\t)\n\t}\n\t// The length of an array counts before its items are read, so a sparse\n\t// array with a very large length stops at once.\n\tconst list = Array.isArray(value)\n\tif (list) {\n\t\tJson.spend(budget, 1 + value.length, path)\n\t}\n\tconst names = list\n\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t: Object.keys(value)\n\tif (!list) {\n\t\tJson.spend(budget, 1 + names.length, path)\n\t}\n\tArr.forEach(names, (name) =>\n\t\tspendValue(\n\t\t\tJson.read(value, name, path),\n\t\t\t[...path, name],\n\t\t\tdepth + 1,\n\t\t\tbudget,\n\t\t),\n\t)\n}\n\n/**\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 } = rule\n\tconst path = Place.pathOf(rule.place)\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\", path, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", path)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problem(\n\t\t\t\t\t\t\t\t[...path, \"meta\"],\n\t\t\t\t\t\t\t\t\"The domain has no Schema for meta\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(value, [...path, \"meta\"], 1, budget)\n\t\t\t\t\t\treturn { value, schema: domain.meta }\n\t\t\t\t\t}),\n\t\t),\n\t\t(found) =>\n\t\t\tfound === undefined\n\t\t\t\t? Effect.void\n\t\t\t\t: Effect.mapError(\n\t\t\t\t\t\tSchema.decodeUnknownEffect(metaCodec(found.schema))(found.value),\n\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\tnew Json.Problem({\n\t\t\t\t\t\t\t\tpath: [...path, \"meta\"],\n\t\t\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\t\t\tissue: error.issue,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t)\n}\n\nconst decodeRule = (\n\trule: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tconst { input } = rule\n\tconst path = Place.pathOf(rule.place)\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problem(path, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, path)\n\tconst tag = Json.read(input, \"_tag\", path)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problem([...path, \"_tag\"], 'A rule must be \"Allow\" or \"Deny\"')\n\t}\n\tconst when = readOptional(input, \"when\", path, () =>\n\t\tJson.decodeAt(Json.read(input, \"when\", path), [...path, \"when\"], budget),\n\t)\n\tconst fields = readOptional(input, \"fields\", path, () =>\n\t\treadStrings(input, \"fields\", path, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", path, () =>\n\t\tJson.readString(input, \"reason\", path),\n\t)\n\tconst id = readOptional(input, \"id\", path, () =>\n\t\tJson.readString(input, \"id\", path),\n\t)\n\t// Each rule gets the domain of the Schema, as a rule from the builder of\n\t// the domain has it.\n\treturn Rule.make({\n\t\t_tag: tag,\n\t\tactions: readStrings(input, \"actions\", path, budget),\n\t\tresources: readStrings(input, \"resources\", path, budget),\n\t\t...(when === undefined ? {} : { when: when as never }),\n\t\t...(fields === undefined ? {} : { fields }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t\t...(id === undefined ? {} : { id }),\n\t\t...(meta === undefined ? {} : { meta }),\n\t\tdomain,\n\t})\n}\n\n/**\n * Reads the shape of a tree, and puts each rule in `rules`. The rules are\n * decoded after the walk: a condition has levels of its own, and a decode\n * inside the walk would go down the levels of both. `path` is the path of the\n * node for a problem here, and `place` keeps it for later.\n */\nconst readTree = (\n\tinput: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tplace: Place.Place | undefined,\n\tdepth: number,\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): Plan => {\n\tif (depth > Limits.policyDepth) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`A policy tree has more than ${String(Limits.policyDepth)} levels. The most is ${String(Limits.policyDepth)}.`,\n\t\t)\n\t}\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problem(path, \"A node of a policy must be an object\")\n\t}\n\tJson.spend(budget, 1, path)\n\tconst child = (value: unknown, ...parts: ReadonlyArray<PropertyKey>) =>\n\t\treadTree(\n\t\t\tvalue,\n\t\t\t[...path, ...parts],\n\t\t\tPlace.at(place, ...parts),\n\t\t\tdepth + 1,\n\t\t\tbudget,\n\t\t\trules,\n\t\t)\n\tconst tag = Json.read(input, \"_tag\", path)\n\tswitch (tag) {\n\t\tcase \"Rules\": {\n\t\t\tconst algorithm = Json.readString(input, \"algorithm\", path)\n\t\t\tif (!PolicyNodes.isAlgorithm(algorithm)) {\n\t\t\t\tthrow Json.problem(\n\t\t\t\t\t[...path, \"algorithm\"],\n\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst found = Arr.map(\n\t\t\t\tJson.readList(input, \"rules\", path, budget),\n\t\t\t\t(rule, index) =>\n\t\t\t\t\trules.push({\n\t\t\t\t\t\tinput: rule,\n\t\t\t\t\t\tplace: Place.at(place, \"rules\", index),\n\t\t\t\t\t}) - 1,\n\t\t\t)\n\t\t\treturn sized(path, {\n\t\t\t\t_tag: \"Rules\",\n\t\t\t\tplace,\n\t\t\t\tsize: 1 + found.length,\n\t\t\t\talgorithm,\n\t\t\t\trules: found,\n\t\t\t})\n\t\t}\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\": {\n\t\t\tconst policies = Arr.map(\n\t\t\t\tJson.readList(input, \"policies\", path, budget),\n\t\t\t\t(value, index) => child(value, \"policies\", index),\n\t\t\t)\n\t\t\treturn sized(path, {\n\t\t\t\t_tag: tag,\n\t\t\t\tplace,\n\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\tpolicies,\n\t\t\t})\n\t\t}\n\t\tcase \"Ceiling\": {\n\t\t\tconst cap = child(Json.read(input, \"cap\", path), \"cap\")\n\t\t\tconst policy = child(Json.read(input, \"policy\", path), \"policy\")\n\t\t\treturn sized(path, {\n\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\tplace,\n\t\t\t\tsize: 1 + cap.size + policy.size,\n\t\t\t\tcap,\n\t\t\t\tpolicy,\n\t\t\t})\n\t\t}\n\t\tdefault:\n\t\t\tthrow Json.problem([...path, \"_tag\"], \"The node has no known tag\")\n\t}\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (path: ReadonlyArray<PropertyKey>, plan: Plan): Plan => {\n\tif (plan.size > Limits.policyNodes) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(Limits.policyNodes)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/** Makes the nodes of a plan, with the decoded rules. */\nconst build = (plan: Plan, rules: ReadonlyArray<Rule.Rule>): Node => {\n\ttry {\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn new PolicyNodes.Rules({\n\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t})\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn new PolicyNodes[plan._tag](\n\t\t\t\t\tArr.map(plan.policies, (child) => build(child, rules)),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn new PolicyNodes.Ceiling({\n\t\t\t\t\tcap: build(plan.cap, rules),\n\t\t\t\t\tpolicy: build(plan.policy, rules),\n\t\t\t\t})\n\t\t}\n\t} catch (error) {\n\t\t// A node past a limit is a problem at its place.\n\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\tthrow Json.problem(Place.pathOf(plan.place), error.message)\n\t\t}\n\t\tthrow error\n\t}\n}\n\n/** Runs a step that may throw a problem, and puts the problem in the error. */\nconst attempt = <A>(step: () => A): Effect.Effect<A, Json.Problem> =>\n\tEffect.try({\n\t\ttry: step,\n\t\tcatch: (error) => {\n\t\t\tif (error instanceof Json.Problem) {\n\t\t\t\treturn error\n\t\t\t}\n\t\t\tthrow error\n\t\t},\n\t})\n\n/**\n * Decodes the JSON form of a policy tree for a domain. It reads the tree\n * first, then the `meta` of each rule as an Effect, then the rules, and it\n * makes the nodes last.\n */\nexport const decode = (\n\tinput: unknown,\n\tdomain: Domain.Any,\n): Effect.Effect<Node, Json.Problem> =>\n\tEffect.gen(function* () {\n\t\tconst budget: Json.Budget = { used: 0 }\n\t\t// The rules of this decode. The list belongs to this decode.\n\t\tconst inputs: Array<RuleInput> = []\n\t\tconst plan = yield* attempt(() =>\n\t\t\treadTree(input, [], undefined, 1, budget, inputs),\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(plan, rules))\n\t})\n"],"mappings":"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"}
@@ -1,6 +1,9 @@
1
1
  import { Rule } from "./rule.js";
2
+ import * as Schema from "effect/Schema";
2
3
  import * as Data from "effect/Data";
3
- export type Algorithm = "denyOverrides" | "permitOverrides" | "firstApplicable" | "lastApplicable";
4
+ /** The combining algorithms of a list of rules. The decoder reads this list too. */
5
+ export declare const Algorithm: Schema.Literals<readonly ["denyOverrides", "permitOverrides", "firstApplicable", "lastApplicable"]>;
6
+ export type Algorithm = typeof Algorithm.Type;
4
7
  interface Facts {
5
8
  readonly size: number;
6
9
  readonly depth: number;
@@ -1 +1 @@
1
- {"version":3,"file":"policy-nodes.d.ts","names":["Rule.Rule"],"sources":["../../src/internal/policy-nodes.ts"],"mappings":";;YAiBY;UAUF;WACA;WACA;WACA;;;uBAmCK,aAAa,KAAK;;EAGhC,YAAY,eAAe,OAAO;;MAM9B;;MAKA;;MAKA;;;qBAaQ,cAAc;WACjB;WACQ,WAAW;WACX,OAAO,cAAcA;EACtC,YAAY;aACF,WAAW;aACX,OAAO,cAAcA;;;;qBAyCnB,YAAY;WACf;WACQ,UAAU,cAAc;EACzC,YAAY,UAAU,cAAc;;;qBAOxB,YAAY;WACf;WACQ,UAAU,cAAc;EACzC,YAAY,UAAU,cAAc;;;qBAOxB,gBAAgB;WACnB;WACQ,UAAU,cAAc;EACzC,YAAY,UAAU,cAAc;;;qBAOxB,gBAAgB;WACnB;WACQ,KAAK;WACL,QAAQ;EACzB,YAAY;aAAkB,KAAK;aAAe,QAAQ;;;;YAO/C,OAAO,QAAQ,MAAM,MAAM,UAAU"}
1
+ {"version":3,"file":"policy-nodes.d.ts","names":["Rule.Rule"],"sources":["../../src/internal/policy-nodes.ts"],"mappings":";;;;qBAmBa,WAAS,OAAA;YAOV,mBAAmB,UAAU;UAS/B;WACA;WACA;WACA;;;uBAmCK,aAAa,KAAK;;EAGhC,YAAY,eAAe,OAAO;;MAM9B;;MAKA;;MAKA;;;qBAaQ,cAAc;WACjB;WACQ,WAAW;WACX,OAAO,cAAcA;EACtC,YAAY;aACF,WAAW;aACX,OAAO,cAAcA;;;;qBAyCnB,YAAY;WACf;WACQ,UAAU,cAAc;EACzC,YAAY,UAAU,cAAc;;;qBAOxB,YAAY;WACf;WACQ,UAAU,cAAc;EACzC,YAAY,UAAU,cAAc;;;qBAOxB,gBAAgB;WACnB;WACQ,UAAU,cAAc;EACzC,YAAY,UAAU,cAAc;;;qBAOxB,gBAAgB;WACnB;WACQ,KAAK;WACL,QAAQ;EACzB,YAAY;aAAkB,KAAK;aAAe,QAAQ;;;;YAO/C,OAAO,QAAQ,MAAM,MAAM,UAAU"}
@@ -2,6 +2,7 @@ import { __exportAll } from "../_virtual/_rolldown/runtime.js";
2
2
  import { exceeded, policyNodes } from "./limits.js";
3
3
  import { isNode as isNode$1 } from "./nodes.js";
4
4
  import { isRule } from "./rule.js";
5
+ import * as Schema from "effect/Schema";
5
6
  import * as Data from "effect/Data";
6
7
  import * as Arr from "effect/Array";
7
8
  // src/internal/policy-nodes.ts
@@ -17,20 +18,28 @@ import * as Arr from "effect/Array";
17
18
  * over any tree, such as a decision, needs no memo.
18
19
  */
19
20
  var policy_nodes_exports = /* @__PURE__ */ __exportAll({
21
+ Algorithm: () => Algorithm,
20
22
  All: () => All,
21
23
  Any: () => Any,
22
24
  Ceiling: () => Ceiling,
23
25
  FirstOf: () => FirstOf,
24
26
  Rules: () => Rules,
27
+ isAlgorithm: () => isAlgorithm,
25
28
  isNode: () => isNode,
26
29
  rulesOf: () => rulesOf
27
30
  });
28
- const algorithms = [
31
+ /** The combining algorithms of a list of rules. The decoder reads this list too. */
32
+ const Algorithm = Schema.Literals([
29
33
  "denyOverrides",
30
34
  "permitOverrides",
31
35
  "firstApplicable",
32
36
  "lastApplicable"
33
- ];
37
+ ]);
38
+ /**
39
+ * Tests if a value is a combining algorithm. It reads the list of the Schema
40
+ * with `includes`, as `Nodes.isCompareOperator` does.
41
+ */
42
+ const isAlgorithm = (u) => Algorithm.literals.includes(u);
34
43
  /** Checks the facts of a node against the limits. */
35
44
  const checked = (facts) => {
36
45
  if (facts.size > 1e5) throw exceeded("A policy tree", "policyNodes", "nodes", facts.size, policyNodes);
@@ -71,7 +80,7 @@ const whenOf = (rule) => isNode$1(rule.when) ? rule.when : void 0;
71
80
  var Rules = class extends Base {
72
81
  _tag = "Rules";
73
82
  constructor(props) {
74
- if (!algorithms.includes(props.algorithm)) throw new Error(`"${String(props.algorithm)}" is not a combining algorithm`);
83
+ if (!isAlgorithm(props.algorithm)) throw new Error(`"${String(props.algorithm)}" is not a combining algorithm`);
75
84
  const rules = Object.freeze(Array.from(props.rules));
76
85
  if (!Arr.every(rules, isRule)) throw new Error("A value in the list is not a rule");
77
86
  const conditions = Arr.map(rules, whenOf);
@@ -141,6 +150,6 @@ const rulesOf = (node) => {
141
150
  case "Ceiling": return [...rulesOf(node.cap), ...rulesOf(node.policy)];
142
151
  }
143
152
  };
144
- export { All, Any, Ceiling, FirstOf, Rules, isNode, policy_nodes_exports, rulesOf };
153
+ export { Algorithm, All, Any, Ceiling, FirstOf, Rules, isAlgorithm, isNode, policy_nodes_exports, rulesOf };
145
154
 
146
155
  //# sourceMappingURL=policy-nodes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"policy-nodes.js","names":["Limits.exceeded","Limits.policyNodes","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 Rule from \"./rule.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\n\nexport type Algorithm =\n\t\"denyOverrides\" | \"permitOverrides\" | \"firstApplicable\" | \"lastApplicable\"\n\nconst algorithms: ReadonlyArray<string> = [\n\t\"denyOverrides\",\n\t\"permitOverrides\",\n\t\"firstApplicable\",\n\t\"lastApplicable\",\n]\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\tif (facts.size > Limits.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.policyNodes,\n\t\t)\n\t}\n\tif (facts.depth > Limits.policyDepth) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A policy tree\",\n\t\t\t\"policyDepth\",\n\t\t\t\"levels\",\n\t\t\tfacts.depth,\n\t\t\tLimits.policyDepth,\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 (!algorithms.includes(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(total, when) => total + 1 + (when?.size ?? 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 = (node: Node): ReadonlyArray<Rule.Rule> => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn node.rules\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\":\n\t\t\treturn Arr.flatMap(node.policies, rulesOf)\n\t\tcase \"Ceiling\":\n\t\t\treturn [...rulesOf(node.cap), ...rulesOf(node.policy)]\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"policy-nodes.js","names":["Limits.exceeded","Limits.policyNodes","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\"\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\tif (facts.size > Limits.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.policyNodes,\n\t\t)\n\t}\n\tif (facts.depth > Limits.policyDepth) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A policy tree\",\n\t\t\t\"policyDepth\",\n\t\t\t\"levels\",\n\t\t\tfacts.depth,\n\t\t\tLimits.policyDepth,\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(total, when) => total + 1 + (when?.size ?? 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 = (node: Node): ReadonlyArray<Rule.Rule> => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn node.rules\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\":\n\t\t\treturn Arr.flatMap(node.policies, rulesOf)\n\t\tcase \"Ceiling\":\n\t\t\treturn [...rulesOf(node.cap), ...rulesOf(node.policy)]\n\t}\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { searchSteps } from "./limits.js";
2
2
  import { always, never } from "./nodes.js";
3
- import { and, not, or, simplify, structureText, testCompare } from "./algebra.js";
3
+ import { and, keyOf, not, or, simplify, testCompare } from "./algebra.js";
4
4
  import { anyKinds, lookup } from "./catalog.js";
5
5
  import * as Data from "effect/Data";
6
6
  import * as Arr from "effect/Array";
@@ -178,8 +178,8 @@ const searchNode = (node, search, state, assignment) => {
178
178
  assignment: Option.none(),
179
179
  state
180
180
  };
181
- const text = structureText(node);
182
- if (search.failed.has(text)) return {
181
+ const key = keyOf(node);
182
+ if (search.failed.has(key)) return {
183
183
  assignment: Option.none(),
184
184
  state
185
185
  };
@@ -200,7 +200,7 @@ const searchNode = (node, search, state, assignment) => {
200
200
  steps
201
201
  }, [...assignment, [field, cell]]);
202
202
  });
203
- if (Option.isNone(result.assignment)) search.failed.add(text);
203
+ if (Option.isNone(result.assignment)) search.failed.add(key);
204
204
  return result;
205
205
  };
206
206
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","names":["Algebra.testCompare","Nodes.always","Nodes.never","Algebra.and","Algebra.or","Algebra.not","Algebra.structureText","Catalog.lookup","Catalog.anyKinds","Limits.searchSteps","Algebra.simplify"],"sources":["../../src/internal/search.ts"],"sourcesContent":["/**\n * The search for a value that makes a condition true. Each test of a\n * condition compares one field with constants, so the constants cut the\n * values of each field into a few cells, and every test gives the same answer\n * for all the values of one cell. The search tries one value from each cell,\n * one field at a time.\n *\n * The values of a field come from its kinds in a catalog. The kinds hold every\n * real value, so a \"no\" holds for the real values too. A \"yes\" holds for the\n * kinds: a check of the Schema may still stop the example.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Option from \"effect/Option\"\nimport { isObject } from \"effect/Predicate\"\nimport * as Algebra from \"./algebra.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\n\ntype Node = Nodes.Node\n\n/**\n * Values of fields, as a nested object. A field that is not in it may have any\n * value. A value with parts that the search does not look into, such as a\n * date, is an empty object.\n */\nexport interface Example {\n\treadonly [field: string]: unknown\n}\n\n/** The answer of a search. */\nexport type Satisfiability = Data.TaggedEnum<{\n\tSatisfiable: { readonly example: Example }\n\tUnsatisfiable: {}\n\tUnknown: { readonly reason: \"predicate\" | \"budget\" }\n}>\n\nconst satisfiability = Data.taggedEnum<Satisfiability>()\n\n/** Stands for a value with parts, such as a struct or a date. */\nconst opaque: object = Object.freeze({})\n\n/** Marks a field that a step of the search did not fix. */\nconst free: unique symbol = Symbol(\"free\")\n\n/** The least number that is greater than `value`. */\nconst nextUp = (value: number): number => {\n\tif (Number.isNaN(value) || value === Infinity) {\n\t\treturn value\n\t}\n\tif (value === 0) {\n\t\treturn Number.MIN_VALUE\n\t}\n\tconst view = new DataView(new ArrayBuffer(8))\n\tview.setFloat64(0, value)\n\tconst bits = view.getBigUint64(0)\n\tview.setBigUint64(0, value > 0 ? bits + 1n : bits - 1n)\n\treturn view.getFloat64(0)\n}\n\n/**\n * Gives one number from each part of the number line that the constants\n * make: each constant, and one number between each two neighbours when there\n * is one. The ends of the domain are in the list too.\n */\nconst numberCells = (\n\tkind: \"integer\" | \"finite\" | \"any\",\n\tconstants: ReadonlyArray<number>,\n): ReadonlyArray<number> => {\n\tconst integer = kind === \"integer\"\n\tconst low = integer\n\t\t? Number.MIN_SAFE_INTEGER\n\t\t: kind === \"finite\"\n\t\t\t? -Number.MAX_VALUE\n\t\t\t: -Infinity\n\tconst high = integer\n\t\t? Number.MAX_SAFE_INTEGER\n\t\t: kind === \"finite\"\n\t\t\t? Number.MAX_VALUE\n\t\t\t: Infinity\n\tconst known: ReadonlyArray<number> = Arr.filter(\n\t\tconstants,\n\t\t(value: number) => !Number.isNaN(value),\n\t)\n\tconst points = Arr.filter(\n\t\tArr.flatMap(known, (value: number) =>\n\t\t\tinteger && Number.isFinite(value)\n\t\t\t\t? [Math.floor(value), Math.ceil(value)]\n\t\t\t\t: [value],\n\t\t),\n\t\t(value: number) => value >= low && value <= high,\n\t)\n\t// `0` and `-0` are one point: every test gives the same answer for both,\n\t// and `===` calls them equal.\n\tconst ordered: Array<number> = [low, high, ...points].sort((a, b) => a - b)\n\tconst sorted = Arr.dedupeAdjacentWith(\n\t\tordered,\n\t\t(a: number, b: number) => a === b,\n\t)\n\tconst cells = Arr.flatMap(sorted, (value, index) => {\n\t\tconst after = sorted[index + 1]\n\t\tconst between = integer ? value + 1 : nextUp(value)\n\t\treturn after !== undefined && between < after ? [value, between] : [value]\n\t})\n\treturn kind === \"any\" ? Arr.append(cells, Number.NaN) : cells\n}\n\n/**\n * Gives one string from each part that the constants make. Strings are in the\n * order of their UTF-16 code units, as `<` sees them. The least string after\n * `a` is `a + \"\\0\"`, so there is a string between `a` and `b` exactly when\n * `b` is not `a + \"\\0\"`.\n */\nconst stringCells = (\n\tconstants: ReadonlyArray<string>,\n): ReadonlyArray<string> => {\n\tconst ordered: Array<string> = [...constants].sort()\n\tconst sorted = Arr.dedupeAdjacentWith(\n\t\tordered,\n\t\t(a: string, b: string) => a === b,\n\t)\n\tif (sorted.length === 0) {\n\t\treturn [\"\"]\n\t}\n\treturn [\n\t\t...(sorted[0] === \"\" ? [] : [\"\"]),\n\t\t...Arr.flatMap(sorted, (value, index) => {\n\t\t\tconst next = `${value}\\0`\n\t\t\treturn next === sorted[index + 1] ? [value] : [value, next]\n\t\t}),\n\t]\n}\n\n/**\n * A text for a cell. Two cells get one text when every test gives the same\n * answer for them: `0` and `-0` are one, and `NaN` is one.\n */\nconst cellText = (cell: unknown): string =>\n\tcell === opaque\n\t\t? \"opaque\"\n\t\t: typeof cell === \"number\"\n\t\t\t? `number:${Object.is(cell, -0) ? \"0\" : String(cell)}`\n\t\t\t: `${typeof cell}:${String(cell)}`\n\n/**\n * Keeps the first cell of each text, in order. The texts come from the\n * values of a policy, so they go into a native `Set`: the hash of a string in\n * Effect has no random seed, and a policy could hold very many strings with\n * one hash. The engine hashes a string with a random seed.\n */\nconst dedupeCells = (cells: ReadonlyArray<unknown>): ReadonlyArray<unknown> => {\n\t// The set belongs to this call.\n\tconst seen = new Set<string>()\n\treturn Arr.filter(cells, (cell) => {\n\t\tconst text = cellText(cell)\n\t\tif (seen.has(text)) {\n\t\t\treturn false\n\t\t}\n\t\tseen.add(text)\n\t\treturn true\n\t})\n}\n\n/** Gives one value from each cell of a field. */\nconst cellsOf = (\n\tkinds: Catalog.Kinds,\n\tconstants: ReadonlyArray<Nodes.Scalar>,\n): ReadonlyArray<unknown> =>\n\tdedupeCells([\n\t\t...(kinds.null ? [null] : []),\n\t\t...(kinds.object ? [opaque] : []),\n\t\t...(kinds.boolean ? [true, false] : []),\n\t\t...(kinds.string\n\t\t\t? stringCells(Arr.filter(constants, (value) => typeof value === \"string\"))\n\t\t\t: []),\n\t\t...(kinds.number === \"none\"\n\t\t\t? []\n\t\t\t: numberCells(\n\t\t\t\t\tkinds.number,\n\t\t\t\t\tArr.filter(constants, (value) => typeof value === \"number\"),\n\t\t\t\t)),\n\t\t...kinds.literals,\n\t])\n\nclass OutOfBudget extends Data.Error {}\n\n/** What one search keeps for all its steps. It lives only as long as the search. */\ninterface Search {\n\treadonly cells: (field: string) => ReadonlyArray<unknown>\n\t/**\n\t * The texts of the values of each `In` test, so one test of a value does\n\t * not read every value of the test. It is a native `Map`, because it must\n\t * find a node by the node itself: a hash of a node reads all its values.\n\t * The texts are in a native `Set`, as in {@link dedupeCells}.\n\t */\n\treadonly members: Map<Nodes.In, Set<string>>\n\t/**\n\t * The texts of the structure of the conditions that the search found to be\n\t * unsatisfiable. A condition with the same structure has the same free\n\t * fields, so its answer is the same. The set only grows, so the steps of a\n\t * search may share it. It is a native `Set`, as in {@link dedupeCells}.\n\t */\n\treadonly failed: Set<string>\n\treadonly budget: number\n}\n\n/** What a search has done up to now. Each step gives a new one. */\ninterface SearchState {\n\treadonly steps: number\n}\n\ninterface Found {\n\treadonly assignment: Option.Option<ReadonlyArray<readonly [string, unknown]>>\n\treadonly state: SearchState\n}\n\nconst literalOf = (operand: Nodes.Operand): Nodes.Scalar => {\n\tif (operand._tag !== \"Literal\") {\n\t\tthrow new Error(\n\t\t\t\"Condition.satisfiable needs a condition with no principal fields. Call Condition.partial first.\",\n\t\t)\n\t}\n\treturn operand.value\n}\n\n/**\n * The value of the field `name` after `field` gets `value`. A field below\n * `field` is `null` when `value` is not a value with parts.\n */\nconst fixedValue = (\n\tname: string,\n\tfield: string,\n\tvalue: unknown,\n): unknown | typeof free =>\n\tname === field\n\t\t? value\n\t\t: name.startsWith(`${field}.`)\n\t\t\t? value === opaque\n\t\t\t\t? free\n\t\t\t\t: null\n\t\t\t: free\n\nconst membersOf = (node: Nodes.In, search: Search): Set<string> => {\n\tconst known = search.members.get(node)\n\tif (known !== undefined) {\n\t\treturn known\n\t}\n\t// `===` finds no `NaN`, so the set has none, and `NaN` is in no test.\n\tconst members = new Set(\n\t\tArr.map(\n\t\t\tArr.filter(\n\t\t\t\tArr.map(node.values, literalOf),\n\t\t\t\t(literal) => !Number.isNaN(literal),\n\t\t\t),\n\t\t\tcellText,\n\t\t),\n\t)\n\tsearch.members.set(node, members)\n\treturn members\n}\n\nconst testFixed = (\n\tnode: Nodes.Compare | Nodes.In | Nodes.Exists,\n\tvalue: unknown,\n\tsearch: Search,\n): boolean => {\n\tswitch (node._tag) {\n\t\tcase \"Compare\":\n\t\t\treturn Algebra.testCompare(node.op, value, literalOf(node.value))\n\t\tcase \"In\":\n\t\t\treturn (\n\t\t\t\t!Number.isNaN(value) && membersOf(node, search).has(cellText(value))\n\t\t\t)\n\t\tcase \"Exists\":\n\t\t\treturn value !== null\n\t}\n}\n\n/** Puts one value into a field, and into the fields below it. */\nconst fix = (\n\tnode: Node,\n\tfield: string,\n\tvalue: unknown,\n\tsearch: Search,\n): Node => {\n\tconst fixChild = (child: Node) => fix(child, field, value, search)\n\tswitch (node._tag) {\n\t\tcase \"Compare\":\n\t\tcase \"In\":\n\t\tcase \"Exists\": {\n\t\t\tconst known = fixedValue(node.field, field, value)\n\t\t\treturn known === free\n\t\t\t\t? node\n\t\t\t\t: testFixed(node, known, search)\n\t\t\t\t\t? Nodes.always\n\t\t\t\t\t: Nodes.never\n\t\t}\n\t\tcase \"And\":\n\t\t\treturn Algebra.and(Arr.map(node.conditions, fixChild))\n\t\tcase \"Or\":\n\t\t\treturn Algebra.or(Arr.map(node.conditions, fixChild))\n\t\tcase \"Not\":\n\t\t\treturn node.condition.pipe(fixChild, Algebra.not)\n\t\tdefault:\n\t\t\treturn node\n\t}\n}\n\n/** The fields of the tests of a condition, once for each test. */\nconst testedFields = (node: Node): ReadonlyArray<string> => {\n\tswitch (node._tag) {\n\t\tcase \"Compare\":\n\t\tcase \"In\":\n\t\tcase \"Exists\":\n\t\t\treturn [node.field]\n\t\tcase \"And\":\n\t\tcase \"Or\":\n\t\t\treturn Arr.flatMap(node.conditions, testedFields)\n\t\tcase \"Not\":\n\t\t\treturn testedFields(node.condition)\n\t\tdefault:\n\t\t\treturn []\n\t}\n}\n\n/**\n * Picks the next field to split on. A field comes before the fields below it,\n * so a value of a field can fix them. Then a field in more tests comes first,\n * and then the first name in order, so the choice is the same each time.\n */\nconst nextField = (node: Node): string => {\n\t// A native `Map`, as in `dedupeCells`: a condition that is not bound to a\n\t// domain may have any field names. The map belongs to this call.\n\tconst counts = new Map<string, number>()\n\tArr.forEach(testedFields(node), (field) =>\n\t\tcounts.set(field, (counts.get(field) ?? 0) + 1),\n\t)\n\tconst depth = (field: string) => field.split(\".\").length\n\tconst [first, ...rest] = Array.from(counts)\n\treturn Arr.reduce(rest, first!, (best, next) =>\n\t\tdepth(next[0]) < depth(best[0]) ||\n\t\t(depth(next[0]) === depth(best[0]) &&\n\t\t\t(next[1] > best[1] || (next[1] === best[1] && next[0] < best[0])))\n\t\t\t? next\n\t\t\t: best,\n\t)[0]\n}\n\n/**\n * Finds values of the fields that make a simple condition true. It splits on\n * one field at a time, and tries one value from each cell of the field. Each\n * step reads the whole condition, so the budget counts its size.\n */\nconst searchNode = (\n\tnode: Node,\n\tsearch: Search,\n\tstate: SearchState,\n\tassignment: ReadonlyArray<readonly [string, unknown]>,\n): Found => {\n\tif (node._tag === \"True\") {\n\t\treturn { assignment: Option.some(assignment), state }\n\t}\n\tif (node._tag === \"False\") {\n\t\treturn { assignment: Option.none(), state }\n\t}\n\t// The text reads all of the node. The step before counted the size of a\n\t// larger node, so the budget counts this work too.\n\tconst text = Algebra.structureText(node)\n\tif (search.failed.has(text)) {\n\t\treturn { assignment: Option.none(), state }\n\t}\n\tif (state.steps + node.size > search.budget) {\n\t\tthrow new OutOfBudget()\n\t}\n\tconst field = nextField(node)\n\t// The cells are tried in order, until one gives values. A fold over the\n\t// cells does no work after a found answer, and it does not grow the stack\n\t// with the number of cells.\n\tconst result = Arr.reduce(\n\t\tsearch.cells(field),\n\t\t{\n\t\t\tassignment: Option.none(),\n\t\t\tstate: { ...state, steps: state.steps + node.size },\n\t\t} as Found,\n\t\t(before, cell): Found => {\n\t\t\tif (Option.isSome(before.assignment)) {\n\t\t\t\treturn before\n\t\t\t}\n\t\t\tconst steps = before.state.steps + node.size\n\t\t\tif (steps > search.budget) {\n\t\t\t\tthrow new OutOfBudget()\n\t\t\t}\n\t\t\treturn searchNode(\n\t\t\t\tfix(node, field, cell, search),\n\t\t\t\tsearch,\n\t\t\t\t{ ...before.state, steps },\n\t\t\t\t[...assignment, [field, cell]],\n\t\t\t)\n\t\t},\n\t)\n\tif (Option.isNone(result.assignment)) {\n\t\tsearch.failed.add(text)\n\t}\n\treturn result\n}\n\n/**\n * Puts a value at a path in an object, and gives a new object. A value with\n * parts (`opaque`) becomes an empty object, and it keeps an object that is\n * there.\n */\nconst setPath = (\n\troot: Readonly<Record<string, unknown>>,\n\tnames: ReadonlyArray<string>,\n\tvalue: unknown,\n): Readonly<Record<string, unknown>> => {\n\tconst [name, ...rest] = names\n\tif (name === undefined) {\n\t\treturn root\n\t}\n\t// Only an own field counts, as a condition reads it. An inherited one,\n\t// such as `__proto__`, is not a field of the example.\n\tconst inner = Object.hasOwn(root, name) ? root[name] : undefined\n\tif (rest.length === 0) {\n\t\treturn value === opaque && isObject(inner)\n\t\t\t? root\n\t\t\t: { ...root, [name]: value === opaque ? {} : value }\n\t}\n\tconst base =\n\t\tisObject(inner) && inner !== opaque\n\t\t\t? (inner as Readonly<Record<string, unknown>>)\n\t\t\t: {}\n\treturn { ...root, [name]: setPath(base, rest, value) }\n}\n\nconst exampleOf = (\n\tassignment: ReadonlyArray<readonly [string, unknown]>,\n): Example =>\n\tArr.reduce(\n\t\tassignment,\n\t\t{} as Readonly<Record<string, unknown>>,\n\t\t(root, [field, value]) => setPath(root, field.split(\".\"), value),\n\t)\n\n/**\n * The constants that the tests of each field use. They come from a policy, so\n * they go into a native `Map` and `Set`, as in {@link dedupeCells}. The map\n * belongs to one search.\n */\nconst constantsOf = (node: Node): Map<string, Set<Nodes.Scalar>> => {\n\tconst pairs = (\n\t\tcurrent: Node,\n\t): ReadonlyArray<readonly [string, ReadonlyArray<Nodes.Scalar>]> => {\n\t\tswitch (current._tag) {\n\t\t\tcase \"Compare\":\n\t\t\t\treturn [[current.field, [literalOf(current.value)]]]\n\t\t\tcase \"In\":\n\t\t\t\treturn [[current.field, Arr.map(current.values, literalOf)]]\n\t\t\tcase \"Exists\":\n\t\t\t\treturn [[current.field, []]]\n\t\t\tcase \"Under\":\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Condition.satisfiable needs a condition with no Under nodes. Call Condition.partial first.\",\n\t\t\t\t)\n\t\t\tcase \"And\":\n\t\t\tcase \"Or\":\n\t\t\t\treturn Arr.flatMap(current.conditions, pairs)\n\t\t\tcase \"Not\":\n\t\t\t\treturn pairs(current.condition)\n\t\t\tdefault:\n\t\t\t\treturn []\n\t\t}\n\t}\n\tconst all = new Map<string, Set<Nodes.Scalar>>()\n\tArr.forEach(pairs(node), ([field, values]) => {\n\t\tconst known = all.get(field) ?? new Set<Nodes.Scalar>()\n\t\tArr.forEach(values, (value) => known.add(value))\n\t\tall.set(field, known)\n\t})\n\treturn all\n}\n\n/** Gives the kinds of each field of a catalog. A field that it does not have may hold any value. */\nexport const kindsIn =\n\t(catalog: Catalog.Catalog | undefined) =>\n\t(field: string): Catalog.Kinds =>\n\t\t(catalog === undefined ? undefined : Catalog.lookup(catalog, field))\n\t\t\t?.kinds ?? Catalog.anyKinds\n\n/**\n * Searches for values that make a condition true. `kindsOf` gives the kinds\n * of values of each field. The condition must have no principal fields and no\n * `Under` nodes.\n */\nexport const satisfiable = (\n\tnode: Node,\n\tkindsOf: (field: string) => Catalog.Kinds,\n\tbudget: number = Limits.searchSteps,\n): Satisfiability => {\n\tif (node.opaque) {\n\t\treturn satisfiability.Unknown({ reason: \"predicate\" })\n\t}\n\tconst constants = constantsOf(node)\n\t// The search calls itself one time for each field that it fixes.\n\tif (constants.size > Limits.searchFields) {\n\t\treturn satisfiability.Unknown({ reason: \"budget\" })\n\t}\n\t// The cells of each field, in a native `Map` as the constants are.\n\tconst cells = new Map<string, ReadonlyArray<unknown>>()\n\tconst search: Search = {\n\t\tmembers: new Map(),\n\t\tfailed: new Set(),\n\t\tcells: (field) => {\n\t\t\tconst made = cells.get(field)\n\t\t\tif (made !== undefined) {\n\t\t\t\treturn made\n\t\t\t}\n\t\t\t// The cells come from the constants, so many constants are work\n\t\t\t// that the budget must count before the cells are made.\n\t\t\tconst known = constants.get(field) ?? new Set<Nodes.Scalar>()\n\t\t\tif (known.size > budget) {\n\t\t\t\tthrow new OutOfBudget()\n\t\t\t}\n\t\t\tconst found = cellsOf(kindsOf(field), Array.from(known))\n\t\t\tcells.set(field, found)\n\t\t\treturn found\n\t\t},\n\t\tbudget,\n\t}\n\ttry {\n\t\tconst found = searchNode(Algebra.simplify(node), search, { steps: 0 }, [])\n\t\treturn Option.match(found.assignment, {\n\t\t\tonNone: () => satisfiability.Unsatisfiable(),\n\t\t\tonSome: (assignment) =>\n\t\t\t\tsatisfiability.Satisfiable({ example: exampleOf(assignment) }),\n\t\t})\n\t} catch (error) {\n\t\tif (error instanceof OutOfBudget) {\n\t\t\treturn satisfiability.Unknown({ reason: \"budget\" })\n\t\t}\n\t\tthrow error\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"search.js","names":["Algebra.testCompare","Nodes.always","Nodes.never","Algebra.and","Algebra.or","Algebra.not","Algebra.keyOf","Catalog.lookup","Catalog.anyKinds","Limits.searchSteps","Algebra.simplify"],"sources":["../../src/internal/search.ts"],"sourcesContent":["/**\n * The search for a value that makes a condition true. Each test of a\n * condition compares one field with constants, so the constants cut the\n * values of each field into a few cells, and every test gives the same answer\n * for all the values of one cell. The search tries one value from each cell,\n * one field at a time.\n *\n * The values of a field come from its kinds in a catalog. The kinds hold every\n * real value, so a \"no\" holds for the real values too. A \"yes\" holds for the\n * kinds: a check of the Schema may still stop the example.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Option from \"effect/Option\"\nimport { isObject } from \"effect/Predicate\"\nimport * as Algebra from \"./algebra.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\n\ntype Node = Nodes.Node\n\n/**\n * Values of fields, as a nested object. A field that is not in it may have any\n * value. A value with parts that the search does not look into, such as a\n * date, is an empty object.\n */\nexport interface Example {\n\treadonly [field: string]: unknown\n}\n\n/** The answer of a search. */\nexport type Satisfiability = Data.TaggedEnum<{\n\tSatisfiable: { readonly example: Example }\n\tUnsatisfiable: {}\n\tUnknown: { readonly reason: \"predicate\" | \"budget\" }\n}>\n\nconst satisfiability = Data.taggedEnum<Satisfiability>()\n\n/** Stands for a value with parts, such as a struct or a date. */\nconst opaque: object = Object.freeze({})\n\n/** Marks a field that a step of the search did not fix. */\nconst free: unique symbol = Symbol(\"free\")\n\n/** The least number that is greater than `value`. */\nconst nextUp = (value: number): number => {\n\tif (Number.isNaN(value) || value === Infinity) {\n\t\treturn value\n\t}\n\tif (value === 0) {\n\t\treturn Number.MIN_VALUE\n\t}\n\tconst view = new DataView(new ArrayBuffer(8))\n\tview.setFloat64(0, value)\n\tconst bits = view.getBigUint64(0)\n\tview.setBigUint64(0, value > 0 ? bits + 1n : bits - 1n)\n\treturn view.getFloat64(0)\n}\n\n/**\n * Gives one number from each part of the number line that the constants\n * make: each constant, and one number between each two neighbours when there\n * is one. The ends of the domain are in the list too.\n */\nconst numberCells = (\n\tkind: \"integer\" | \"finite\" | \"any\",\n\tconstants: ReadonlyArray<number>,\n): ReadonlyArray<number> => {\n\tconst integer = kind === \"integer\"\n\tconst low = integer\n\t\t? Number.MIN_SAFE_INTEGER\n\t\t: kind === \"finite\"\n\t\t\t? -Number.MAX_VALUE\n\t\t\t: -Infinity\n\tconst high = integer\n\t\t? Number.MAX_SAFE_INTEGER\n\t\t: kind === \"finite\"\n\t\t\t? Number.MAX_VALUE\n\t\t\t: Infinity\n\tconst known: ReadonlyArray<number> = Arr.filter(\n\t\tconstants,\n\t\t(value: number) => !Number.isNaN(value),\n\t)\n\tconst points = Arr.filter(\n\t\tArr.flatMap(known, (value: number) =>\n\t\t\tinteger && Number.isFinite(value)\n\t\t\t\t? [Math.floor(value), Math.ceil(value)]\n\t\t\t\t: [value],\n\t\t),\n\t\t(value: number) => value >= low && value <= high,\n\t)\n\t// `0` and `-0` are one point: every test gives the same answer for both,\n\t// and `===` calls them equal.\n\tconst ordered: Array<number> = [low, high, ...points].sort((a, b) => a - b)\n\tconst sorted = Arr.dedupeAdjacentWith(\n\t\tordered,\n\t\t(a: number, b: number) => a === b,\n\t)\n\tconst cells = Arr.flatMap(sorted, (value, index) => {\n\t\tconst after = sorted[index + 1]\n\t\tconst between = integer ? value + 1 : nextUp(value)\n\t\treturn after !== undefined && between < after ? [value, between] : [value]\n\t})\n\treturn kind === \"any\" ? Arr.append(cells, Number.NaN) : cells\n}\n\n/**\n * Gives one string from each part that the constants make. Strings are in the\n * order of their UTF-16 code units, as `<` sees them. The least string after\n * `a` is `a + \"\\0\"`, so there is a string between `a` and `b` exactly when\n * `b` is not `a + \"\\0\"`.\n */\nconst stringCells = (\n\tconstants: ReadonlyArray<string>,\n): ReadonlyArray<string> => {\n\tconst ordered: Array<string> = [...constants].sort()\n\tconst sorted = Arr.dedupeAdjacentWith(\n\t\tordered,\n\t\t(a: string, b: string) => a === b,\n\t)\n\tif (sorted.length === 0) {\n\t\treturn [\"\"]\n\t}\n\treturn [\n\t\t...(sorted[0] === \"\" ? [] : [\"\"]),\n\t\t...Arr.flatMap(sorted, (value, index) => {\n\t\t\tconst next = `${value}\\0`\n\t\t\treturn next === sorted[index + 1] ? [value] : [value, next]\n\t\t}),\n\t]\n}\n\n/**\n * A text for a cell. Two cells get one text when every test gives the same\n * answer for them: `0` and `-0` are one, and `NaN` is one.\n */\nconst cellText = (cell: unknown): string =>\n\tcell === opaque\n\t\t? \"opaque\"\n\t\t: typeof cell === \"number\"\n\t\t\t? `number:${Object.is(cell, -0) ? \"0\" : String(cell)}`\n\t\t\t: `${typeof cell}:${String(cell)}`\n\n/**\n * Keeps the first cell of each text, in order. The texts come from the\n * values of a policy, so they go into a native `Set`: the hash of a string in\n * Effect has no random seed, and a policy could hold very many strings with\n * one hash. The engine hashes a string with a random seed.\n */\nconst dedupeCells = (cells: ReadonlyArray<unknown>): ReadonlyArray<unknown> => {\n\t// The set belongs to this call.\n\tconst seen = new Set<string>()\n\treturn Arr.filter(cells, (cell) => {\n\t\tconst text = cellText(cell)\n\t\tif (seen.has(text)) {\n\t\t\treturn false\n\t\t}\n\t\tseen.add(text)\n\t\treturn true\n\t})\n}\n\n/** Gives one value from each cell of a field. */\nconst cellsOf = (\n\tkinds: Catalog.Kinds,\n\tconstants: ReadonlyArray<Nodes.Scalar>,\n): ReadonlyArray<unknown> =>\n\tdedupeCells([\n\t\t...(kinds.null ? [null] : []),\n\t\t...(kinds.object ? [opaque] : []),\n\t\t...(kinds.boolean ? [true, false] : []),\n\t\t...(kinds.string\n\t\t\t? stringCells(Arr.filter(constants, (value) => typeof value === \"string\"))\n\t\t\t: []),\n\t\t...(kinds.number === \"none\"\n\t\t\t? []\n\t\t\t: numberCells(\n\t\t\t\t\tkinds.number,\n\t\t\t\t\tArr.filter(constants, (value) => typeof value === \"number\"),\n\t\t\t\t)),\n\t\t...kinds.literals,\n\t])\n\nclass OutOfBudget extends Data.Error {}\n\n/** What one search keeps for all its steps. It lives only as long as the search. */\ninterface Search {\n\treadonly cells: (field: string) => ReadonlyArray<unknown>\n\t/**\n\t * The texts of the values of each `In` test, so one test of a value does\n\t * not read every value of the test. It is a native `Map`, because it must\n\t * find a node by the node itself: a hash of a node reads all its values.\n\t * The texts are in a native `Set`, as in {@link dedupeCells}.\n\t */\n\treadonly members: Map<Nodes.In, Set<string>>\n\t/**\n\t * The keys of the conditions that the search found to be unsatisfiable:\n\t * see `Algebra.keyOf`. A condition with the same structure has the same\n\t * free fields, so its answer is the same. The set only grows, so the steps\n\t * of a search may share it. It is a native `Set`, as in\n\t * {@link dedupeCells}.\n\t */\n\treadonly failed: Set<string | Node>\n\treadonly budget: number\n}\n\n/** What a search has done up to now. Each step gives a new one. */\ninterface SearchState {\n\treadonly steps: number\n}\n\ninterface Found {\n\treadonly assignment: Option.Option<ReadonlyArray<readonly [string, unknown]>>\n\treadonly state: SearchState\n}\n\nconst literalOf = (operand: Nodes.Operand): Nodes.Scalar => {\n\tif (operand._tag !== \"Literal\") {\n\t\tthrow new Error(\n\t\t\t\"Condition.satisfiable needs a condition with no principal fields. Call Condition.partial first.\",\n\t\t)\n\t}\n\treturn operand.value\n}\n\n/**\n * The value of the field `name` after `field` gets `value`. A field below\n * `field` is `null` when `value` is not a value with parts.\n */\nconst fixedValue = (\n\tname: string,\n\tfield: string,\n\tvalue: unknown,\n): unknown | typeof free =>\n\tname === field\n\t\t? value\n\t\t: name.startsWith(`${field}.`)\n\t\t\t? value === opaque\n\t\t\t\t? free\n\t\t\t\t: null\n\t\t\t: free\n\nconst membersOf = (node: Nodes.In, search: Search): Set<string> => {\n\tconst known = search.members.get(node)\n\tif (known !== undefined) {\n\t\treturn known\n\t}\n\t// `===` finds no `NaN`, so the set has none, and `NaN` is in no test.\n\tconst members = new Set(\n\t\tArr.map(\n\t\t\tArr.filter(\n\t\t\t\tArr.map(node.values, literalOf),\n\t\t\t\t(literal) => !Number.isNaN(literal),\n\t\t\t),\n\t\t\tcellText,\n\t\t),\n\t)\n\tsearch.members.set(node, members)\n\treturn members\n}\n\nconst testFixed = (\n\tnode: Nodes.Compare | Nodes.In | Nodes.Exists,\n\tvalue: unknown,\n\tsearch: Search,\n): boolean => {\n\tswitch (node._tag) {\n\t\tcase \"Compare\":\n\t\t\treturn Algebra.testCompare(node.op, value, literalOf(node.value))\n\t\tcase \"In\":\n\t\t\treturn (\n\t\t\t\t!Number.isNaN(value) && membersOf(node, search).has(cellText(value))\n\t\t\t)\n\t\tcase \"Exists\":\n\t\t\treturn value !== null\n\t}\n}\n\n/** Puts one value into a field, and into the fields below it. */\nconst fix = (\n\tnode: Node,\n\tfield: string,\n\tvalue: unknown,\n\tsearch: Search,\n): Node => {\n\tconst fixChild = (child: Node) => fix(child, field, value, search)\n\tswitch (node._tag) {\n\t\tcase \"Compare\":\n\t\tcase \"In\":\n\t\tcase \"Exists\": {\n\t\t\tconst known = fixedValue(node.field, field, value)\n\t\t\treturn known === free\n\t\t\t\t? node\n\t\t\t\t: testFixed(node, known, search)\n\t\t\t\t\t? Nodes.always\n\t\t\t\t\t: Nodes.never\n\t\t}\n\t\tcase \"And\":\n\t\t\treturn Algebra.and(Arr.map(node.conditions, fixChild))\n\t\tcase \"Or\":\n\t\t\treturn Algebra.or(Arr.map(node.conditions, fixChild))\n\t\tcase \"Not\":\n\t\t\treturn node.condition.pipe(fixChild, Algebra.not)\n\t\tdefault:\n\t\t\treturn node\n\t}\n}\n\n/** The fields of the tests of a condition, once for each test. */\nconst testedFields = (node: Node): ReadonlyArray<string> => {\n\tswitch (node._tag) {\n\t\tcase \"Compare\":\n\t\tcase \"In\":\n\t\tcase \"Exists\":\n\t\t\treturn [node.field]\n\t\tcase \"And\":\n\t\tcase \"Or\":\n\t\t\treturn Arr.flatMap(node.conditions, testedFields)\n\t\tcase \"Not\":\n\t\t\treturn testedFields(node.condition)\n\t\tdefault:\n\t\t\treturn []\n\t}\n}\n\n/**\n * Picks the next field to split on. A field comes before the fields below it,\n * so a value of a field can fix them. Then a field in more tests comes first,\n * and then the first name in order, so the choice is the same each time.\n */\nconst nextField = (node: Node): string => {\n\t// A native `Map`, as in `dedupeCells`: a condition that is not bound to a\n\t// domain may have any field names. The map belongs to this call.\n\tconst counts = new Map<string, number>()\n\tArr.forEach(testedFields(node), (field) =>\n\t\tcounts.set(field, (counts.get(field) ?? 0) + 1),\n\t)\n\tconst depth = (field: string) => field.split(\".\").length\n\tconst [first, ...rest] = Array.from(counts)\n\treturn Arr.reduce(rest, first!, (best, next) =>\n\t\tdepth(next[0]) < depth(best[0]) ||\n\t\t(depth(next[0]) === depth(best[0]) &&\n\t\t\t(next[1] > best[1] || (next[1] === best[1] && next[0] < best[0])))\n\t\t\t? next\n\t\t\t: best,\n\t)[0]\n}\n\n/**\n * Finds values of the fields that make a simple condition true. It splits on\n * one field at a time, and tries one value from each cell of the field. Each\n * step reads the whole condition, so the budget counts its size.\n */\nconst searchNode = (\n\tnode: Node,\n\tsearch: Search,\n\tstate: SearchState,\n\tassignment: ReadonlyArray<readonly [string, unknown]>,\n): Found => {\n\tif (node._tag === \"True\") {\n\t\treturn { assignment: Option.some(assignment), state }\n\t}\n\tif (node._tag === \"False\") {\n\t\treturn { assignment: Option.none(), state }\n\t}\n\t// The key reads at most a small part of the node: a node with very many\n\t// nodes, or with very many values in its `In` tests, is its own key. A\n\t// step reads the nodes but not the values, which `membersOf` keeps, so the\n\t// budget counts the size.\n\tconst key = Algebra.keyOf(node)\n\tif (search.failed.has(key)) {\n\t\treturn { assignment: Option.none(), state }\n\t}\n\tif (state.steps + node.size > search.budget) {\n\t\tthrow new OutOfBudget()\n\t}\n\tconst field = nextField(node)\n\t// The cells are tried in order, until one gives values. A fold over the\n\t// cells does no work after a found answer, and it does not grow the stack\n\t// with the number of cells.\n\tconst result = Arr.reduce(\n\t\tsearch.cells(field),\n\t\t{\n\t\t\tassignment: Option.none(),\n\t\t\tstate: { ...state, steps: state.steps + node.size },\n\t\t} as Found,\n\t\t(before, cell): Found => {\n\t\t\tif (Option.isSome(before.assignment)) {\n\t\t\t\treturn before\n\t\t\t}\n\t\t\tconst steps = before.state.steps + node.size\n\t\t\tif (steps > search.budget) {\n\t\t\t\tthrow new OutOfBudget()\n\t\t\t}\n\t\t\treturn searchNode(\n\t\t\t\tfix(node, field, cell, search),\n\t\t\t\tsearch,\n\t\t\t\t{ ...before.state, steps },\n\t\t\t\t[...assignment, [field, cell]],\n\t\t\t)\n\t\t},\n\t)\n\tif (Option.isNone(result.assignment)) {\n\t\tsearch.failed.add(key)\n\t}\n\treturn result\n}\n\n/**\n * Puts a value at a path in an object, and gives a new object. A value with\n * parts (`opaque`) becomes an empty object, and it keeps an object that is\n * there.\n */\nconst setPath = (\n\troot: Readonly<Record<string, unknown>>,\n\tnames: ReadonlyArray<string>,\n\tvalue: unknown,\n): Readonly<Record<string, unknown>> => {\n\tconst [name, ...rest] = names\n\tif (name === undefined) {\n\t\treturn root\n\t}\n\t// Only an own field counts, as a condition reads it. An inherited one,\n\t// such as `__proto__`, is not a field of the example.\n\tconst inner = Object.hasOwn(root, name) ? root[name] : undefined\n\tif (rest.length === 0) {\n\t\treturn value === opaque && isObject(inner)\n\t\t\t? root\n\t\t\t: { ...root, [name]: value === opaque ? {} : value }\n\t}\n\tconst base =\n\t\tisObject(inner) && inner !== opaque\n\t\t\t? (inner as Readonly<Record<string, unknown>>)\n\t\t\t: {}\n\treturn { ...root, [name]: setPath(base, rest, value) }\n}\n\nconst exampleOf = (\n\tassignment: ReadonlyArray<readonly [string, unknown]>,\n): Example =>\n\tArr.reduce(\n\t\tassignment,\n\t\t{} as Readonly<Record<string, unknown>>,\n\t\t(root, [field, value]) => setPath(root, field.split(\".\"), value),\n\t)\n\n/**\n * The constants that the tests of each field use. They come from a policy, so\n * they go into a native `Map` and `Set`, as in {@link dedupeCells}. The map\n * belongs to one search.\n */\nconst constantsOf = (node: Node): Map<string, Set<Nodes.Scalar>> => {\n\tconst pairs = (\n\t\tcurrent: Node,\n\t): ReadonlyArray<readonly [string, ReadonlyArray<Nodes.Scalar>]> => {\n\t\tswitch (current._tag) {\n\t\t\tcase \"Compare\":\n\t\t\t\treturn [[current.field, [literalOf(current.value)]]]\n\t\t\tcase \"In\":\n\t\t\t\treturn [[current.field, Arr.map(current.values, literalOf)]]\n\t\t\tcase \"Exists\":\n\t\t\t\treturn [[current.field, []]]\n\t\t\tcase \"Under\":\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Condition.satisfiable needs a condition with no Under nodes. Call Condition.partial first.\",\n\t\t\t\t)\n\t\t\tcase \"And\":\n\t\t\tcase \"Or\":\n\t\t\t\treturn Arr.flatMap(current.conditions, pairs)\n\t\t\tcase \"Not\":\n\t\t\t\treturn pairs(current.condition)\n\t\t\tdefault:\n\t\t\t\treturn []\n\t\t}\n\t}\n\tconst all = new Map<string, Set<Nodes.Scalar>>()\n\tArr.forEach(pairs(node), ([field, values]) => {\n\t\tconst known = all.get(field) ?? new Set<Nodes.Scalar>()\n\t\tArr.forEach(values, (value) => known.add(value))\n\t\tall.set(field, known)\n\t})\n\treturn all\n}\n\n/** Gives the kinds of each field of a catalog. A field that it does not have may hold any value. */\nexport const kindsIn =\n\t(catalog: Catalog.Catalog | undefined) =>\n\t(field: string): Catalog.Kinds =>\n\t\t(catalog === undefined ? undefined : Catalog.lookup(catalog, field))\n\t\t\t?.kinds ?? Catalog.anyKinds\n\n/**\n * Searches for values that make a condition true. `kindsOf` gives the kinds\n * of values of each field. The condition must have no principal fields and no\n * `Under` nodes.\n */\nexport const satisfiable = (\n\tnode: Node,\n\tkindsOf: (field: string) => Catalog.Kinds,\n\tbudget: number = Limits.searchSteps,\n): Satisfiability => {\n\tif (node.opaque) {\n\t\treturn satisfiability.Unknown({ reason: \"predicate\" })\n\t}\n\tconst constants = constantsOf(node)\n\t// The search calls itself one time for each field that it fixes.\n\tif (constants.size > Limits.searchFields) {\n\t\treturn satisfiability.Unknown({ reason: \"budget\" })\n\t}\n\t// The cells of each field, in a native `Map` as the constants are.\n\tconst cells = new Map<string, ReadonlyArray<unknown>>()\n\tconst search: Search = {\n\t\tmembers: new Map(),\n\t\tfailed: new Set(),\n\t\tcells: (field) => {\n\t\t\tconst made = cells.get(field)\n\t\t\tif (made !== undefined) {\n\t\t\t\treturn made\n\t\t\t}\n\t\t\t// The cells come from the constants, so many constants are work\n\t\t\t// that the budget must count before the cells are made.\n\t\t\tconst known = constants.get(field) ?? new Set<Nodes.Scalar>()\n\t\t\tif (known.size > budget) {\n\t\t\t\tthrow new OutOfBudget()\n\t\t\t}\n\t\t\tconst found = cellsOf(kindsOf(field), Array.from(known))\n\t\t\tcells.set(field, found)\n\t\t\treturn found\n\t\t},\n\t\tbudget,\n\t}\n\ttry {\n\t\tconst found = searchNode(Algebra.simplify(node), search, { steps: 0 }, [])\n\t\treturn Option.match(found.assignment, {\n\t\t\tonNone: () => satisfiability.Unsatisfiable(),\n\t\t\tonSome: (assignment) =>\n\t\t\t\tsatisfiability.Satisfiable({ example: exampleOf(assignment) }),\n\t\t})\n\t} catch (error) {\n\t\tif (error instanceof OutOfBudget) {\n\t\t\treturn satisfiability.Unknown({ reason: \"budget\" })\n\t\t}\n\t\tthrow error\n\t}\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nmnmcc/ability",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "Typed, composable authorization policies for Effect programs.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/Condition.ts CHANGED
@@ -412,7 +412,8 @@ type ObjectPaths<
412
412
  > = [O] extends [never]
413
413
  ? never
414
414
  : // A record is one field to a rule or a request, as `permittedFields`
415
- // lists it.
415
+ // lists it. So is a struct at the last part that a path may have, since
416
+ // a path does not go below it.
416
417
  | (Parents extends true
417
418
  ? K
418
419
  : Arrays extends true
@@ -420,7 +421,9 @@ type ObjectPaths<
420
421
  ? K
421
422
  : number extends keyof O
422
423
  ? K
423
- : never
424
+ : [Depth] extends [0]
425
+ ? K
426
+ : never
424
427
  : never)
425
428
  | `${K}.${FieldPathInternal<O, PreviousDepth[Depth], Arrays, Parents>}`
426
429
 
package/src/Policy.ts CHANGED
@@ -53,8 +53,7 @@ import type * as Resource from "./Resource.ts"
53
53
  * @category models
54
54
  * @since 0.3.0
55
55
  */
56
- export type Algorithm =
57
- "denyOverrides" | "permitOverrides" | "firstApplicable" | "lastApplicable"
56
+ export type Algorithm = PolicyNodes.Algorithm
58
57
 
59
58
  /**
60
59
  * A list of rules with a combining algorithm.
@@ -60,15 +60,46 @@ const shapeOf = (node: Node): unknown => {
60
60
  * with one text give the same answer for every value. It reads all of the
61
61
  * condition, so a search counts its size in the budget.
62
62
  */
63
- export const structureText = (node: Node): string =>
64
- JSON.stringify(shapeOf(node))
63
+ const structureText = (node: Node): string => JSON.stringify(shapeOf(node))
65
64
 
66
65
  /**
67
- * The most nodes of a condition that a smart constructor reads for its key.
68
- * A larger child is its own key, so the work of a constructor stays small.
66
+ * The work of the text of the structure of a condition: each node, and each
67
+ * value of an `In` test or an `Under` test. A node counts as one in `size`,
68
+ * also an `In` test with very many values. This walk reads the nodes only,
69
+ * so it takes `size` steps.
70
+ */
71
+ const weightOf = (node: Node): number => {
72
+ switch (node._tag) {
73
+ case "In":
74
+ return 1 + node.values.length
75
+ case "Under":
76
+ return 1 + node.prefix.length
77
+ case "And":
78
+ case "Or":
79
+ return Arr.reduce(
80
+ node.conditions,
81
+ 1,
82
+ (total, child) => total + weightOf(child),
83
+ )
84
+ case "Not":
85
+ return 1 + weightOf(node.condition)
86
+ default:
87
+ return 1
88
+ }
89
+ }
90
+
91
+ /**
92
+ * The most nodes of a condition that a smart constructor reads for its key,
93
+ * and the most children of a child that it puts into a node. A larger child
94
+ * is its own key, and it stays one child, so the work of a constructor stays
95
+ * small. A deep chain of `And` nodes, for one, does not copy all its leaves
96
+ * at each level.
69
97
  */
70
98
  const keyNodes = 64
71
99
 
100
+ /** The most work of a key, with the values of its tests: see {@link weightOf}. */
101
+ const keyWeight = 256
102
+
72
103
  /**
73
104
  * The key of a child in a smart constructor: the text of its structure, or
74
105
  * the node itself when it is large. The keys go into a native `Set`, not a
@@ -77,8 +108,10 @@ const keyNodes = 64
77
108
  * set would read all of them. A native `Set` finds a node by the node itself,
78
109
  * and it hashes a string with the random seed of the engine.
79
110
  */
80
- const keyOf = (node: Node): string | Node =>
81
- node.size > keyNodes ? node : structureText(node)
111
+ export const keyOf = (node: Node): string | Node =>
112
+ node.size > keyNodes || weightOf(node) > keyWeight
113
+ ? node
114
+ : structureText(node)
82
115
 
83
116
  /**
84
117
  * Keeps the first node of each structure, in order. A large node is a copy
@@ -145,7 +178,8 @@ const shortCircuit = (
145
178
  /**
146
179
  * Makes an `And` or an `Or` of simple children, and makes it simple. It looks
147
180
  * one level down only:
148
- * - it puts the children of a child of the same kind into the node;
181
+ * - it puts the children of a child of the same kind into the node, when
182
+ * the child has at most `keyNodes` of them;
149
183
  * - it stops at a `False` in an `And`, or at a `True` in an `Or`;
150
184
  * - it removes `True` from an `And`, `False` from an `Or`, copies, and the
151
185
  * children that other children make useless.
@@ -158,7 +192,9 @@ const junction = (tag: "And" | "Or", children: ReadonlyArray<Node>): Node => {
158
192
  const constant = tag === "And" ? "False" : "True"
159
193
  const neutral = tag === "And" ? "True" : "False"
160
194
  const flat = Arr.flatMap(children, (child) =>
161
- child._tag === tag && !child.opaque ? child.conditions : [child],
195
+ child._tag === tag && !child.opaque && child.conditions.length <= keyNodes
196
+ ? child.conditions
197
+ : [child],
162
198
  )
163
199
  return Option.match(
164
200
  Arr.findFirstIndex(flat, (child) => child._tag === constant),